🗂

visionOSでモデルの骨を動かす

2024/02/07に公開

image

概要

Xcode上で骨が入ったモデルを読み込んだ時に、読み込んだモデルの骨を操作する方法をまとめました。

モデルの読み込み

骨を操作するためのモデルを読み込みます。
今回はプロジェクト内に usdz モデルを配置し、読み込む方法で読み込んでいます。

まずはModelsフォルダを追加しそこに動かす usdz を配置します。
プロジェクトの Add File to ... から 追加したフォルダを選択します。

image

この時 Added foldersCreate groupsAdd to targets対象のプロジェクト になっていることを確認してください。

コピーされる対象がフォルダなのか内部のコンテンツなのかが変わり読み込む時の named のパスが変わります。
Create groups にするとフォルダはプロジェクトの管理のための Group として使用されるだけで、フォルダ構造は維持せずコピーされるので、named に指定するパスは ファイル名のみになります。
Create folder references にすると フォルダがコピーされて使用されるため、フォルダ構成を維持したパスを指定する必要があります。

Create groupsで追加した状態
image

guard var robot = try? await ModelEntity(named: "robot") else { return }

Create folder referencesで追加した状態
image

guard var robot = try? await ModelEntity(named: "Models/robot") else { return }

骨の回転

読み込んだモデルの骨はjointNamesjointTransformsに配列で入っています。

for i in 0..<robot.jointNames.count {
    print("Joint(\(i)):\(robot.jointNames[i])")
}

こちらのjointNamesを出力して何番目に何が入っているか確認します。

Joint(0):root
Joint(1):root/hips_joint
Joint(2):root/hips_joint/left_upLeg_joint
Joint(3):root/hips_joint/left_upLeg_joint/left_leg_joint
Joint(4):root/hips_joint/left_upLeg_joint/left_leg_joint/left_foot_joint
Joint(5):root/hips_joint/left_upLeg_joint/left_leg_joint/left_foot_joint/left_toes_joint
Joint(6):root/hips_joint/left_upLeg_joint/left_leg_joint/left_foot_joint/left_toes_joint/left_toesEnd_joint
Joint(7):root/hips_joint/right_upLeg_joint
Joint(8):root/hips_joint/right_upLeg_joint/right_leg_joint
...

今回は 腰を回転させてみようかと思うので、Joint(14):root/hips_joint/spine_1_joint/spine_2_joint/spine_3_joint Join を使用したいと思います。

let spineJointIndex = 14
let rotateRadian = Float(Angle(degrees: 1).radians)
Timer.scheduledTimer(withTimeInterval: 0.016, repeats: true) { _ in
    robot.jointTransforms[spineJointIndex].rotation *= simd_quaternion(rotateRadian, [1,0,0])
}

image

これで腰の骨を回転させることができました!

アニメーションを使用しての回転

先ほどは 直接Transformを操作して回転をさせましたが、アニメーションを使用して回転させることも可能です。
こちらの方法では開始地点と終了地点を指定してアニメーションをさせることが可能です。

今回は 右肩を動かして手を振りたいと思います。
Joint(35):root/hips_joint/spine_1_joint/spine_2_joint/spine_3_joint/spine_4_joint/spine_5_joint/spine_6_joint/spine_7_joint/right_shoulder_1_joint が右肩だったため右肩を指定してアニメーションを作成します。

let rightShoulderJointName = "root/hips_joint/spine_1_joint/spine_2_joint/spine_3_joint/spine_4_joint/spine_5_joint/spine_6_joint/spine_7_joint/right_shoulder_1_joint"
let rightShoulderJointIndex = 35

// 右肩の骨を取得します
let rightSholderJoint = robot.jointTransforms[rightSholderJointIndex]

// アニメーションの開始ポーズの作成
let fromTransforms: [Transform] = [rightShoulderJoint]
let fromPose = JointTransforms(fromTransforms)

// アニメーションの終了ポーズの作成
let toTransforms: [Transform] = [Transform(scale: [1, 1, 1], rotation:simd_quatf(angle: -.pi, axis: [0,1,0]), translation:rightShoulderJoint.translation)]
let toPose = JointTransforms(toTransforms)

最初と最後のポーズが作成できたら FromToByAnimation<JointTransforms> から AnimationResource を作成していきます。

let jointNames = [rightShoulderJointName]
var fromToBy = FromToByAnimation<JointTransforms>()
// アニメーション名を指定(なんでも良い)
fromToBy.name = "anim"
// 1秒をかけてアニメーションする
fromToBy.duration = 1
// .autoReverse で終わったら戻るようにする
fromToBy.repeatMode = .autoReverse
// 対象の骨の名前を指定
fromToBy.jointNames = jointNames
// 最初のポーズを指定
fromToBy.fromValue = fromPose
// 最後のポーズを指定
fromToBy.toValue = toPose
// JointTransformsを動かすので .joinTransformsを指定
fromToBy.bindTarget = .jointTransforms
// アニメーションリソースを生成
let animationResource = try? AnimationResource.generate(with: fromToBy)

AnimationResourceが作成できたら、ModelEntity からそのアニメーションを再生します。

robot.playAnimation(animationResource!)

image

これでアニメーションを使用した回転もできました!

まとめ

今回は モデルの骨の動かし方についてまとめてみました。
簡単な動きであれば作れますが複雑な動きは難しので、複雑な動きを行いたい場合は 外部ツールでモデルにアニメーションを作るか、Unityを使用するのが良いかと思います!

書いた人

ひー

佐藤 寿樹

株式会社コナミデジタルエンタテインメントに入社し5年間ウイニングイレブンのオンライン実装に携わる。
その後、株式会社コロプラで9年間エンジニアとしてアプリ開発・運用を行い、位置情報やARを使用したARゲーム開発、OculusRiftやPSVRなどのVRゲーム開発を経験しMESONへ入社。

X

MESON Works

MESONの制作実績一覧もあります。ご興味ある方はぜひ見てみてください。

MESON Works

Discussion