miscellaneous

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

クォータニオンを使って球面線形補間を行う場合、姿勢を補間する場合も位置を補間する場合も計算式は同じである。
だが、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);
            }
        }
}






  1. 2008/02/10(日) 21:51:58|
  2. ActionScript 3.0
  3. | トラックバック:0
  4. | コメント:10
<<2点と直線の位置関係をベクトルの内積の符号で判断する | ホーム | クォータニオンの補間の刻み幅を変えてみる>>

コメント

管理人のみ閲覧できます

このコメントは管理人のみ閲覧できます
  1. |
  2. 2008/02/22(金) 08:22:23 |
  3. #
  4. [ 編集]

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

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

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

多くのありがとう!

Kalle

(申し訳ありませんが私の貧しい人々のため日本)
http://www.google.de/translate_t ;)
  1. URL |
  2. 2008/02/22(金) 08:34:59 |
  3. kalle #-
  4. [ 編集]

Thank you for your message.
I can't open Quaternion.fla in Quaternion.zip, because I don't have Flash CS3 or other application which can open .fla file.
(I develope AS3 applications by using SDK only)
Can you please show your scripts in plain text?
  1. URL |
  2. 2008/02/22(金) 10:27:05 |
  3. yamasv #-
  4. [ 編集]

Yes sure:
http://kallesaas.com/pv3D/quaternion/VirtualTrackball.as
( maybe i should also switch to SDK. Never used the timeline yet ;)
thanks again!

kalle
  1. URL |
  2. 2008/02/22(金) 19:38:17 |
  3. kalle #-
  4. [ 編集]

How about normalize quaternions before you multiply two quaternions ?
(about line 170)


// applaying the rotation
// convert to Quaternion, the cube rotaion and the mouse drag
var deltaQ:Quaternion = new Quaternion(rotAxis.x, rotAxis.y, rotAxis.z, rotAngle);
deltaQ.normalize(); // here !!
var q = Quaternion.createFromEuler(cube.rotationX, cube.rotationY, cube.rotationZ)
q.normalize(); // and here !!
q.mult(deltaQ);
var newTransform = q.toMatrix();
cube.transform = newTransform
  1. URL |
  2. 2008/02/22(金) 20:24:15 |
  3. yamasv #-
  4. [ 編集]

I am sorry, you only have to normalize quaternion before convert it to matrix.


var deltaQ:Quaternion = new Quaternion(rotAxis.x, rotAxis.y, rotAxis.z, rotAngle);
var q = Quaternion.createFromEuler(cube.rotationX, cube.rotationY, cube.rotationZ)
q.mult(deltaQ);
q.normalize(); //// only here !!!
var newTransform = q.toMatrix();
cube.transform = newTransform
  1. URL |
  2. 2008/02/22(金) 20:54:31 |
  3. yamasv #-
  4. [ 編集]

yes i see.
but it didnt work like i expect:
http://kallesaas.com/pv3D/quaternion/v2/Quaternion.swf
http://kallesaas.com/pv3D/quaternion/v2/VirtualTrackball.as

i would like to do something like this: (click on the cube and click drag )
http://araesmojo.googlepages.com/OrbitalQuaternionCamera.html

my resource was that:
http://viewport3d.com/trackball.htm

strange rotation ;)
kalle
  1. URL |
  2. 2008/02/22(金) 22:18:44 |
  3. kalle #-
  4. [ 編集]

I am worried two points.

first:
You calculate and update cube.transform with last transform at every frame,
but I think you should calculate with transform of the points in time when you start mouse drag.
(because rotation axis and angle is calculated with distance from drag start point.)

second.
I have a feeling that a turn to multiply quaternion is wrong.


I'm sorry my English is not good.
  1. URL |
  2. 2008/02/23(土) 02:15:12 |
  3. yamasv #-
  4. [ 編集]

Hi. kalle.

Is this the one which you need?

http://yamasv.sakura.ne.jp/quaternion/VirtualTrackball.swf
http://yamasv.sakura.ne.jp/quaternion/VirtualTrackball.as


I found a another mistake in your scripts.
You have to call Quaternion.createFromAxisAngle() to create Quaternion from rotation axis and angle, not Quaternion's constructor.
  1. URL |
  2. 2008/02/23(土) 11:09:48 |
  3. yamasv #-
  4. [ 編集]

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



  1. URL |
  2. 2008/02/26(火) 17:42:58 |
  3. kalle #-
  4. [ 編集]

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバックURLはこちら
http://yamasv.blog92.fc2.com/tb.php/135-b438fc3a
この記事にトラックバックする(FC2ブログユーザー)
Google

プロフィール

Author:yamasv@gmail.com
コメント、トラックバック、リンクはお気軽に

最近の記事

ブログ検索

カテゴリー

-->

カレンダー

07 | 2008/08 | 09
- - - - - 1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31 - - - - - -

過去ログ

最近のコメント

最近のトラックバック

RSSフィード

リンク

このブログをリンクに追加する

全ての記事を表示する

全ての記事を表示する



あわせて読みたい