fc2ブログ

PV3D 2.0のQuaternionクラスを使った球面線形補間について

2008-02-10 | 21:51

クォータニオンを使って球面線形補間を行う場合、姿勢を補間する場合も位置を補間する場合も計算式は同じである。
だが、PaperVision3D 2.0 の math.core.Quaternionクラスの球面線形補間を行うメソッドslerp()を使って位置を補間する場合、ちょっとした注意が必要である。

math.core.Quaternion.slerp()では
public static function slerp( qa:Quaternion, qb:Quaternion, alpha:Number ):Quaternion
{
var qm:Quaternion = new Quaternion();

// Calculate angle between them.
var cosHalfTheta:Number = qa.w * qb.w + qa.x * qb.x + qa.y * qb.y + qa.z * qb.z;

// if qa=qb or qa=-qb then theta = 0 and we can return qa
if(Math.abs(cosHalfTheta) >= 1.0)
{
qm.w = qa.w;
qm.x = qa.x;
qm.y = qa.y;
qm.z = qa.z;
return qm;
}
.............
という風に、引数で渡された二つのクォータニオンの内積を元に回転角を求めている。
内積をとる前に正規化していないので、呼び出し側で正規化したクォータニオンを渡す必要がある。
姿勢を補間する場合は、姿勢を表す情報である回転軸と回転角は正規化しても変わらないので問題は無い。しかし、位置をクォータニオンで表す場合は、正規化すると情報が失われてしまうので、slerp()呼び出し後に補正してやらないといけない。
(ベクトルの場合に正規化してもベクトルの方向は変わらないが、位置をベクトルで表現していた場合には正規化すると位置が変わるのと同じ)

次のプログラムは、org.math.Quaternionクラスのslerp()メソッドを使わずに、正規化していないクォータニオンを使って自分で補間を計算している。
このように正規化していないクォータニオンを使って計算すれば、計算結果のx,y,z要素がそのまま補間された座標になる。
(math.core.Quaternionのslerp()でも内部で一時変数を使って正規化してから角度を求めるようにしてくれてあればいいと思うが、何か別な意味があるんだろうか)
package {
    import flash.display.*;
    import flash.events.*;
    import flash.utils.*;

    import org.papervision3d.core.*;
    import org.papervision3d.core.geom.*;
    import org.papervision3d.core.math.*;
    import org.papervision3d.scenes.* ;
    import org.papervision3d.objects.*;
    import org.papervision3d.objects.primitives.*;
    import org.papervision3d.cameras.*;
    import org.papervision3d.materials.*;
    import org.papervision3d.materials.shadematerials.* ;
    import org.papervision3d.materials.utils.*;
    import org.papervision3d.view.*;
    import org.papervision3d.render.*;
    import org.papervision3d.lights.*;
    import org.papervision3d.materials.special.LineMaterial ;

    [SWF(width="600", height="400", frameRate="10", backgroundColor="#ffffff")]
        public class SlerpTest2 extends Sprite{
            private var renderer:BasicRenderEngine = new BasicRenderEngine();
            private var viewport:Viewport3D = new Viewport3D(600, 800, false, true);
            private var scene:Scene3D = new Scene3D();
            private var camera:FreeCamera3D = new FreeCamera3D();

            private var lines:Array = null;

            private var cube1:Cube;
            private var cube2:Cube;
            private var cube3:Cube;

            private var timer:Timer=null;
            private var cnt:int = 0;
           
            private var q1:Quaternion = new Quaternion(300, 0, 0);
            private var q2:Quaternion = new Quaternion(0, 200, 0);
            private var angle:Number;

            private var tt:Number = 0;
            private var fwd:Boolean = true;

            public function SlerpTest2():void
            {
                stage.align = StageAlign.TOP_LEFT;
                stage.scaleMode = StageScaleMode.NO_SCALE;

                addChild(viewport);

                camera.y = -300;
                camera.z = -1100;
                camera.x = 0;
                camera.focus = 1100;
                camera.zoom = 1;

                cube1= new Cube(new MaterialsList({all:new WireframeMaterial(0xff00)}), 
                        100, 200, 300);
                scene.addChild(cube1);

                var nq1:Quaternion = new Quaternion(q1.x, q1.y, q1.z);
                var nq2:Quaternion = new Quaternion(q2.x, q2.y, q2.z);
                nq1.normalize();
                nq2.normalize();
                var dot:Number = Quaternion.dot(nq1,nq2);
                angle = Math.acos(dot);

                timer = new Timer(100);
                timer.addEventListener(TimerEvent.TIMER, onTimer);
                timer.start();
            }

            private function onTimer(e:*):void{
                var qSrc:Quaternion;
                var qDst:Quaternion;

                if(fwd){
                    qSrc = q1;
                    qDst = q2;
                }else{
                    qSrc = q2;
                    qDst = q1;
                }

                var t1:Number = Math.sin((1-tt)*angle) / Math.sin(angle);
                var t2:Number = Math.sin(tt*angle) / Math.sin(angle);
                cube1.x = t1*qSrc.x + t2*qDst.x;
                cube1.y = t1*qSrc.y + t2*qDst.y;
                cube1.z = t1*qSrc.z + t2*qDst.z;

                tt += 0.1;
                if (tt > 1){
                    fwd = !fwd;
                    tt = 0;
                }

                renderer.renderScene(scene, camera, viewport);
            }
        }
}



下のソースは math.core.Quaternion.slerp()を使う場合のやり方。
最初に書いた通り slerp()には正規化したクォータニオンを渡す必要があり、計算されたクォータニオンに対して補正する必要がある。
package {
    import flash.display.*;
    import flash.events.*;
    import flash.utils.*;

    import org.papervision3d.core.*;
    import org.papervision3d.core.geom.*;
    import org.papervision3d.core.math.*;
    import org.papervision3d.scenes.* ;
    import org.papervision3d.objects.*;
    import org.papervision3d.objects.primitives.*;
    import org.papervision3d.cameras.*;
    import org.papervision3d.materials.*;
    import org.papervision3d.materials.shadematerials.* ;
    import org.papervision3d.materials.utils.*;
    import org.papervision3d.view.*;
    import org.papervision3d.render.*;
    import org.papervision3d.lights.*;
    import org.papervision3d.materials.special.LineMaterial ;

    [SWF(width="600", height="400", frameRate="10", backgroundColor="#ffffff")]
        public class SlerpTest22 extends Sprite{
            private var renderer:BasicRenderEngine = new BasicRenderEngine();
            private var viewport:Viewport3D = new Viewport3D(600, 800, false, true);
            private var scene:Scene3D = new Scene3D();
            private var camera:FreeCamera3D = new FreeCamera3D();

            private var lines:Array = null;

            private var cube1:Cube;
            private var cube2:Cube;
            private var cube3:Cube;

            private var timer:Timer=null;
            private var cnt:int = 0;
           
            private var q1:Quaternion = new Quaternion(300, 0, 0);
            private var q2:Quaternion = new Quaternion(0, 200, 0);
            private var nq1:Quaternion = new Quaternion(300, 0, 0);
            private var nq2:Quaternion = new Quaternion(0, 200, 0);

            private var tt:Number = 0;
            private var fwd:Boolean = true;

            public function SlerpTest22():void
            {
                stage.align = StageAlign.TOP_LEFT;
                stage.scaleMode = StageScaleMode.NO_SCALE;

                addChild(viewport);

                camera.y = -300;
                camera.z = -1100;
                camera.x = 0;
                camera.focus = 1100;
                camera.zoom = 1;

                cube1= new Cube(new MaterialsList({all:new WireframeMaterial(0xff00)}), 
                        100, 200, 300);
                scene.addChild(cube1);

                // core.math.Quaternion.slerp()には正規化したクォータニオンを渡す必要がある
                nq1= new Quaternion(q1.x, q1.y, q1.z);
                nq2= new Quaternion(q2.x, q2.y, q2.z);
                nq1.normalize();
                nq2.normalize();

                timer = new Timer(100);
                timer.addEventListener(TimerEvent.TIMER, onTimer);
                timer.start();
            }

            private function onTimer(e:*):void{
                tt += 0.1;
                if (tt > 1){
                    fwd = !fwd;
                    tt = 0;
                }

                var qSrc:Quaternion;
                var qDst:Quaternion;

                var rate:Number = q1.x / nq1.x;

                if(fwd){
                    qSrc = nq1;
                    qDst = nq2;
                }else{
                    qSrc = nq2;
                    qDst = nq1;
                }

                var q:Quaternion = Quaternion.slerp(qSrc, qDst, tt);

                // 計算されたクォータニオンは正規化したものを元にしたクォータニオンなので補正する
                cube1.x = q.x * rate;
                cube1.y = q.y * rate;
                cube1.z = q.z * rate;

                renderer.renderScene(scene, camera, viewport);
            }
        }
}



スポンサーサイト



Comment

管理人のみ閲覧できます

このコメントは管理人のみ閲覧できます

  • 2008-02-22 | 08:22 |
  • :
  • edit

こんにちは、
良い仕事!
私単純なローテーションを作成することを試みたが、私に失敗しました

http://kallesaas.com/pv3D/quaternion/Quaternion.html
http://kallesaas.com/pv3D/quaternion/Quaternion.zip

何がおかしいのか教えてもらえますか?または助言を与えるか?

多くのありがとう!

Kalle

(申し訳ありませんが私の貧しい人々のため日本)
http://www.google.de/translate_t ;)

承認待ちコメント

このコメントは管理者の承認待ちです

  • 2008-02-22 | 10:27 |
  • :
  • edit
承認待ちコメント

このコメントは管理者の承認待ちです

  • 2008-02-22 | 20:24 |
  • :
  • edit
承認待ちコメント

このコメントは管理者の承認待ちです

  • 2008-02-22 | 20:54 |
  • :
  • edit
承認待ちコメント

このコメントは管理者の承認待ちです

  • 2008-02-22 | 22:18 |
  • :
  • edit
承認待ちコメント

このコメントは管理者の承認待ちです

  • 2008-02-23 | 02:15 |
  • :
  • edit
承認待ちコメント

このコメントは管理者の承認待ちです

  • 2008-02-23 | 11:09 |
  • :
  • edit

THANK YOU !
this helps me a lot.
I updated my example:
http://kallesaas.com/pv3D/quaternion/v3/Quaternion.swf
http://kallesaas.com/pv3D/quaternion/v3/VirtualTrackball.as

its actually not working 100% correctly because the mouse movement didn`t correspond exactly to the rotation of the cube. but its getting better and i start to understand the quaternion logic....

greets kalle



  • 2008-02-26 | 17:42 |
  • kalle URL :
  • edit

Post a comment

Secret