SceneKitで子ボーンの回転
ここのところ3DエディターっぽいmacOSアプリを作ってて、SceneKitでボーンを自在に回転させようとしたらえらい苦労した話。

↑Blenderで作成したボーンを持つオブジェクト。これをdaeで出力。

↑そのdaeファイルをSceneKitでインポートして表示(画像は作成中のアプリの一部、Xcodeではない)。ここまではええねん。
任意の子ボーンを任意の軸まわりで回転させたいわけだが、、、話を簡単にするためにz軸周りに回転させる、とする。
この「z軸周り」というのは世界座標系でのz軸ね。
一番の親ボーンの回転はできる。問題はその子ボーン、さらにその子ボーン、、、などを意図通り回転させられないことだ。
あるボーンを回転させても、それ以下の子ボーンオブジェクト(SCNNode)の持つ姿勢(orientationプロパティ)を表すクォータニオンは変わらない。
なので、ターゲットの子ボーンオブジェクトを回転させようとすれば、その親ボーンの姿勢を考慮して、さらにその親の、、、と一番の親まで考慮しなければならない、、、?なんか面倒だよなあ?
と思ってたらリファレンスに
- (SCNMatrix4)convertTransform:(SCNMatrix4)transform toNode:(SCNNode *)node;
- (SCNMatrix4)convertTransform:(SCNMatrix4)transform fromNode:(SCNNode *)node;
などのメソッドがあってnodeにnilを使えば世界座標系に変換できるから、ああ、これを使えばいいんだな、と思って取り組むもうまくいかなかった。
結局試行錯誤の結果、、、処理の流れとしては自分の姿勢を世界座標で表してそこでz軸周りの回転を与え、それを元にもどしてやる、という流れだとうまくいった。世界座標に変換するのに上記のメソッドでいけそうなのもんだけど、うまくいかず結局自作(GetQuaternionToWorldForBone)した。
↓コードはこんな感じで。MySceneKitUtilityなどところどころ自作のUtilityメソッドがあるけど、GLKitベースのメソッドをSceneKitで使えるようにしてるだけなのでここでは省略。
一番大元のボーンには TOP_BONE_NAME という名前を付けてる。
while(1) なんてやる場合には無限ループにならないようにしましょう(省略してるけど)。
実際に動かすと以下のような感じ。z軸周りに30度回転させてる。

↑一番大元のボーンを対象にすると全体が回転する。

↑途中のボーンを回転。

↑下側のボーンとか。
この方法だとBlenderから持って来たファイルを使う時のY-upとかも気にしなくていい。どちらにせよとにかくz軸周りに回転する。あー、すっきりした。

↑Blenderで作成したボーンを持つオブジェクト。これをdaeで出力。

↑そのdaeファイルをSceneKitでインポートして表示(画像は作成中のアプリの一部、Xcodeではない)。ここまではええねん。
任意の子ボーンを任意の軸まわりで回転させたいわけだが、、、話を簡単にするためにz軸周りに回転させる、とする。
この「z軸周り」というのは世界座標系でのz軸ね。
一番の親ボーンの回転はできる。問題はその子ボーン、さらにその子ボーン、、、などを意図通り回転させられないことだ。
あるボーンを回転させても、それ以下の子ボーンオブジェクト(SCNNode)の持つ姿勢(orientationプロパティ)を表すクォータニオンは変わらない。
なので、ターゲットの子ボーンオブジェクトを回転させようとすれば、その親ボーンの姿勢を考慮して、さらにその親の、、、と一番の親まで考慮しなければならない、、、?なんか面倒だよなあ?
と思ってたらリファレンスに
- (SCNMatrix4)convertTransform:(SCNMatrix4)transform toNode:(SCNNode *)node;
- (SCNMatrix4)convertTransform:(SCNMatrix4)transform fromNode:(SCNNode *)node;
などのメソッドがあってnodeにnilを使えば世界座標系に変換できるから、ああ、これを使えばいいんだな、と思って取り組むもうまくいかなかった。
結局試行錯誤の結果、、、処理の流れとしては自分の姿勢を世界座標で表してそこでz軸周りの回転を与え、それを元にもどしてやる、という流れだとうまくいった。世界座標に変換するのに上記のメソッドでいけそうなのもんだけど、うまくいかず結局自作(GetQuaternionToWorldForBone)した。
↓コードはこんな感じで。MySceneKitUtilityなどところどころ自作のUtilityメソッドがあるけど、GLKitベースのメソッドをSceneKitで使えるようにしてるだけなのでここでは省略。
{ SCNQuaternion(^GetQuaternionToWorldForBone)(SCNNode *) = ^(SCNNode *targetNode) { //世界座標に変換のためのquaternionを得る(自分自身は含まない) SCNQuaternion quat = QuaternionIdentity; while (1) { if ([targetNode.name isEqualToString:TOP_BONE_NAME]) { break; } SCNNode *pNode = [targetNode parentNode]; quat = [MySceneKitUtility quaternionMultiplyLeft:pNode.orientation right:quat]; targetNode = pNode; } return quat; }; SCNQuaternion toWorldQuat = GetQuaternionToWorldForBone(bone); SCNQuaternion toLocalQuat = [MySceneKitUtility scnQuaternionInvert:toWorldQuat]; SCNQuaternion localBoneQuat = bone.orientation; SCNQuaternion worldBoneQuat = [MySceneKitUtility quaternionMultiplyLeft:toWorldQuat right:localBoneQuat]; SCNQuaternion newWorldBoneQuat = [MySceneKitUtility quaternionMultiplyLeft:dQ right:worldBoneQuat]; SCNQuaternion newLocalBoneQuat = [MySceneKitUtility quaternionMultiplyLeft:toLocalQuat right:newWorldBoneQuat]; bone.orientation = newLocalBoneQuat; }z軸周りに少し回転させるクォータニオンがdQ。
一番大元のボーンには TOP_BONE_NAME という名前を付けてる。
while(1) なんてやる場合には無限ループにならないようにしましょう(省略してるけど)。
実際に動かすと以下のような感じ。z軸周りに30度回転させてる。

↑一番大元のボーンを対象にすると全体が回転する。

↑途中のボーンを回転。

↑下側のボーンとか。
この方法だとBlenderから持って来たファイルを使う時のY-upとかも気にしなくていい。どちらにせよとにかくz軸周りに回転する。あー、すっきりした。
スポンサーサイト
<< 機械学習で株価予測やってみたが TopPage 100均とANKERの車内用バッテリーチャージャー >>
トラックバック
トラックバックURL
https://ringsbell.blog.fc2.com/tb.php/1113-4021db52
https://ringsbell.blog.fc2.com/tb.php/1113-4021db52