Search code examples
swiftscenekitgameplay-kit

GameplayKit + SceneKit, GKAgent rotation conversion


I've been having some fun using GameplayKit in a Scenekit (and ARKit, although that isn't important to this question) application.

Specifically, I have been using Entities, Components, and Agents for Behavior, and it has all been working great, except for one thing.

I have managed to use an GKAgent inside a component to move around a scene, and to avoid other objects. That all seems to be working. However, I can only get the GKAgent position working, not the "rotation" property.

import Foundation
import SceneKit
import GameplayKit

class MoveComponent: GKScnComponent, GKAgentDelegate {

   //this class is abbreviated for perfunctory sakes   

   func agentWillUpdate(_ agent: GKAgent) {
    guard let visualComponent = entity?.component(ofType: self.visualType) as? VisualComponent else {
      return
    }

     //works with just "position", but turns possessed with both rotation and position
    agentSeeking.rotation = convertToFloat3x3(float4x4: visualComponent.parentMostNode.simdTransform)
    agentSeeking.position = visualComponent.parentMostNode.simdPosition
  }

  func agentDidUpdate(_ agent: GKAgent) {
    guard let visualComponent = entity?.component(ofType: self.visualType) as? VisualComponent else {
      return
    }

    //works with just "position", but turns possessed with both rotation and position
    visualComponent.parentMostNode.simdPosition = agentSeeking.position
    visualComponent.parentMostNode.simdTransform = convertToFloat4x4(float3x3: agentSeeking.rotation)
  }

}

I have written some conversion code between 3x3 matrices and 4x4 matrices, thanks to this question: Converting matrix_float3x3 rotation to SceneKit

import Foundation
import GameplayKit

func convertToFloat3x3(float4x4: simd_float4x4) -> simd_float3x3 {
  let column0 = convertToFloat3 ( float4: float4x4.columns.0 )
  let column1 = convertToFloat3 ( float4: float4x4.columns.1 )
  let column2 = convertToFloat3 ( float4: float4x4.columns.2 )

  return simd_float3x3.init(column0, column1, column2)
}

func convertToFloat3(float4: simd_float4) -> simd_float3 {
  return simd_float3.init(float4.x, float4.y, float4.z)
}

func convertToFloat4x4(float3x3: simd_float3x3) -> simd_float4x4 {
  let column0 = convertToFloat4 ( float3: float3x3.columns.0 )
  let column1 = convertToFloat4 ( float3: float3x3.columns.1 )
  let column2 = convertToFloat4 ( float3: float3x3.columns.2 )
  let identity3 = simd_float4.init(x: 0, y: 0, z: 0, w: 1)

  return simd_float4x4.init(column0, column1, column2, identity3)
}

func convertToFloat4(float3: simd_float3) -> simd_float4 {
  return simd_float4.init(float3.x, float3.y, float3.z, 0)
}

I'm a little new to all this, and I'm not a linear algebra guru, so I'm not 100% certain if my matrix conversion functions are doing exactly what they are supposed to do.

When I just use the "position" property of the agent, everything is fine, but when I add in rotation/transform from the agent to the node, everything starts acting possessed.

Any thoughts/pointers/help with what I'm doing wrong?


Solution

  • I figured it out. Matrix multiplication is the key to getting this working. Anyone with experience with 3d game development probably could have told me that without any mental exertion :)

    func agentWillUpdate(_ agent: GKAgent) {
        guard let visualComponent = entity?.component(ofType: self.visualType) as? VisualComponent else {
          return
        }
    
        agentSeeking.position = visualComponent.parentMostNode.simdPosition
    
        let rotation = visualComponent.parentMostNode.rotation
        let rotationMatrix = SCNMatrix4MakeRotation(rotation.w, rotation.x, rotation.y, rotation.z)
        let float4x4 = SCNMatrix4ToMat4(rotationMatrix)
    
        agentSeeking.rotation = convertToFloat3x3(float4x4: float4x4)
      }
    
    
    func agentDidUpdate(_ agent: GKAgent) {
        guard let visualComponent = entity?.component(ofType: self.visualType) as? VisualComponent else {
          return
        }
    
    //    visualComponent.parentMostNode.simdPosition = agentSeeking.position
        let rotation3x3 = agentSeeking.rotation
        let rotation4x4 = convertToFloat4x4(float3x3: rotation3x3)
        let rotationMatrix = SCNMatrix4FromMat4(rotation4x4)
        let position = agentSeeking.position
    
        let translationMatrix = SCNMatrix4MakeTranslation(position.x, position.y, position.z)
        let transformMatrix = SCNMatrix4Mult(rotationMatrix, translationMatrix)
    
        visualComponent.parentMostNode.transform = transformMatrix
      }
    

    I hope somebody finds this useful.