Reputation: 83
I have loaded apple's robot.usdz model into a sceneview using mdlAsset. Right now the model loads into the view doing the standard t pose, but I would like to change the models pose by setting each joint location to 3d coordinates that I captured in a previous ARView.
Here is my code
import UIKit
import SceneKit
import AVKit
import SceneKit.ModelIO
class ProperFormOverlayViewController: UIViewController {
let sceneView = SCNView()
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
guard let urlPath = Bundle.main.url(forResource: "robot", withExtension: "usdz") else {
return
}
let mdlAsset = MDLAsset(url: urlPath)
let asset = mdlAsset.object(at: 0) // extract first object
let assetNode = SCNNode(mdlObject: asset)
scene.rootNode.addChildNode(assetNode)
let player = AVPlayer(url: URL(string: "")!)
scene.background.contents = player
sceneView.play(nil)
player.play()
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
sceneView.allowsCameraControl = true
}
}
I know it possible to access the joint locations when using ARView, but I don't think it would make sense to use ARView because I don't need access to the camera. Any suggestions on how to change the models pose? Thanks for the help!
Upvotes: 3
Views: 813
Reputation: 668
Alright. It took me some time to figure this out, so I'm answering this old question in case someone else needs to know. First you need to get the node that the SCNSkinner is attached to. For Apple's robot model it's on a node named "ace_PLY". So the first thing you do is get the skinner like so:
guard let acePLY = scene.rootNode.childNode(withName: "ace_PLY", recursively: true) else { return }
skinner = acePLY.skinner
Once you have the skinner, you can access the bones. The bones are what you need to move in order to animate the model programmatically, BUT you need to build in inverse kinematics in order to get the bones to move correctly. To do that you need to construct kinmatic chains. To do so for the right leg looks like:
guard let hipsJoint = skinner.bones.first(where: { bone in
bone.name == "hips_joint"
}) else { return }
guard let rightFootJoint = skinner.bones.first( where: { bone in
bone.name == "right_foot_joint"
}) else { return }
let ik = SCNIKConstraint.inverseKinematicsConstraint(chainRootNode: hipsJoint)
rightFootJoint.constraints = [ik]
You get the root node for the chain, which is the highest level node the chain should affect. Then get the end effect node, which is the node you'll be moving around. In this case, the hip joint is the root, and the right foot is the end node.
When you want to move the skeleton, you can now set targetPosition on your inverse kinematics constraint. Doing so inside of a SCNTransaction will animate it. Like so:
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.2
ik.targetPostion = newPosition
SCNTransaction.commit()
You'll need to build out all the inverse kinematic chains you need to get the skeleton to move the way you want, so repeat as necessary.
Upvotes: 2