5

私はここ数日、3D グラフのようなプログラムのユーザー インターフェイス用に仮想トラックボールを実際に実装しようと試みてきました。しかし、私は問題を抱えています。

数値と多くのテストを見ると、問題は私の四元数の実際の連結であるように見えますが、私は知らないか、そうは思いません。これまでクォータニオンや仮想トラックボールを使用したことはありません。これは私にとってまったく新しいことです。QuaternionJOGLが提供するクラスを使用しています。私は自分で作ってみましたが、うまくいきました(少なくとも私が知る限り)が、完全に混乱していたので、JOGLのものを使いました。

クォータニオンを連結しないと、わずかな回転が必要なように見えますが、もちろん、どの方向にも少しだけ動いているときは難しいです。このコードは、OpenGL wikiのトラックボール チュートリアルに基づいています。

Quaternionクラスのメソッドを使用するとmult (Quaternion q)、グラフはほとんど動きません (クォータニオンを連結しようとしないよりも少ない)。

楽しみのためにQuaternion add (Quaternion q)` メソッドを試してみclass'sたところ、少なくともグラフを回転させるものは得られましたが、一貫した方法ではありませんでした。マウスを動かすと、飛び散り、ランダムに回転します。ときどき、完全に NaN で満たされたクォータニオンが得られます。

私のコードでは、これらのいずれも表示しません。四元数をどうするか迷っています。私が知っている限り、それらが連結される方法であるため、それらを乗算したいことはわかっています。しかし、私が成功していないと言ったように、失敗は私のコードのどこかにあると思います。

とにかく、私のセットアップにはTrackballpublic Point3f projectMouse (int x, int y)メソッドとpublic void rotateFor (Point3f p1, Point3f p2)、WherePoint3fは私が作成したクラスを持つクラスがあります。呼び出される別のクラスにCamerapublic void transform (GLAutoDrawable g)、トラックボールの四元数に基づいて回転する OpenGL メソッドを呼び出すメソッドがあります。

コードは次のとおりです。

public Point3f projectMouse (int x, int y)
{
    int off = Screen.WIDTH / 2;  // Half the width of the GLCanvas
    
    x = x - objx_ - off;         // obj being the 2D center of the graph
    y = off - objy_ - y;
    
    float t = Util.sq(x) + Util.sq(y); // Util is a class I made with
    float rsq = Util.sq(off);          // simple some math stuff
                                       // off is also the radius of the sphere
    float z;
    
    if (t >= rsq)
        z = (rsq / 2.0F) / Util.sqrt(t);
    else
        z = Util.sqrt(rsq - t);
    
    Point3f result = new Point3f (x, y, z);
    return result;
}

回転方法は次のとおりです。

public void rotateFor (Point3f p1, Point3f p2)
{
    // Vector3f is a class I made, I already know it works
    // all methods in Vector3f modify the object's numbers
    // and return the new modify instance of itself
    Vector3f v1 = new Vector3f(p1.x, p1.y, p1.z).normalize();
    Vector3f v2 = new Vector3f(p2.x, p2.y, p2.z).normalize();
    
    Vector3f n = v1.copy().cross(v2);
    float theta = (float) Math.acos(v1.dot(v2));
    
    float real = (float) Math.cos(theta / 2.0F);
    n.multiply((float) Math.sin(theta / 2.0F));
    
    Quaternion q = new Quaternion(real, n.x, n.y, n.z);
    
    rotation = q;  // A member that can be accessed by a getter

    // Do magic on the quaternion
}

編集:

私は近づいています、私はいくつかの単純な間違いを見つけました。

1: JOGL の実装では、W を X ではなく実数として扱います。私は X を実数として使用していました。

2: クォータニオン 1 + 0i + 0j + 0k から始めていませんでした

3: クォータニオンを opengl の軸/角度に変換していませんでした

4: opengl の角度を度に変換していませんでした

また、Markus が指摘したように、私はノーマルをノーマライズしていませんでした。私がノーマライズしたとき、あまり変化が見られませんでした。見分けるのは難しいと思いましたが、彼は正しいです。

今の問題は、私がすべてを行うと、信じられないほど激しくグラフが揺れることです. それは(ちょっと)あなたが望む方向に動きますが、発作が激しすぎて何もできません.

いくつかの名前を変更した新しいコードを次に示します。

public void rotate (Vector3f v1, Vector3f v2)
{
    Vector3f v1p = v1.copy().normalize();
    Vector3f v2p = v2.copy().normalize();
    Vector3f n = v1p.copy().cross(v2p);
    
    if (n.length() == 0) return; // Sometimes v1p equals v2p
    
    float w = (float) Math.acos(v1p.dot(v2p));
    
    n.normalize().multiply((float) Math.sin(w / 2.0F));
    w = (float) Math.cos(w / 2.0F);
    
    Quaternion q = new Quaternion(n.x, n.y, n.z, w);
    q.mult(rot);

    rot_ = q;
}

OpenGL コードは次のとおりです。

    Vector3f p1 = tb_.project(x1, y1); // projectMouse [changed name]
    Vector3f p2 = tb_.project(x2, y2);
    tb_.rotate (p1, p2);
    
    float[] q = tb_.getRotation().toAxis(); // Converts to angle/axis
    gl.glRotatef((float)Math.toDegrees(q[0]), q[1], q[2], q[3]);

名前の変更の理由は、Trackballクラス内のすべてを削除して最初からやり直したためです。おそらく最高のアイデアではありませんが、まあ。

EDIT2:

球体に投影することに何の問題もないと、かなり確信を持って言えます。

また、全体として、問題は VECTOR にあると言えます。角度は問題ないように見えますが、ベクトルが飛び回っているように見えます。

EDIT3:

問題は 2 つの四元数の乗算です。他のすべてが期待どおりに機能することを確認できます。乗算中に軸がおかしい!

4

3 に答える 3

5

問題は 2 つの四元数の乗算です。他のすべてが期待どおりに機能することを確認できます。乗算中に軸がおかしい!

あなたは絶対に正しいです!! 最近正しい掛け算を提出したところ、Jogamp は私の変更を受け入れました。mult(quaternion) での乗算が正しくありませんでした。

最新の jogl リリースを入手すれば、正しい mult(Quaternion) が含まれていると確信しています。

于 2012-11-15T23:09:55.463 に答える
3

やったよ!

このC++ 実装のおかげで、機能するトラックボール/アークボール インターフェイスを開発することができました。問題が何であるかはまだわかりませんが、すべてを書き直し、独自のQuaternionsクラスを作成したところ、突然すべてが機能しました。Vectorsベクトル用のクラスも作成しました。私はVector3f以前にクラスを持っていましたがQuaternionsVectorsクラスは静的メソッドでいっぱいで、配列を取り込んでいます。クォータニオンでのベクトル計算、およびクォータニオンからのベクトル計算を簡単にするため。以下の 2 つのクラスのコードをリンクしますが、ここではTrackballクラスのみを示します。

私は今朝これらの 2 つのクラスを非常に迅速に作成したので、数学的なエラーがある場合は、まあ、ええと、おっと。必要なものだけを使用し、それらが正しいことを確認しました。これらのクラスは以下のとおりです。

クォータニオン: http://pastebin.com/raxS4Ma9

ベクトル: http://pastebin.com/fU3PKZB9

これが私のTrackballクラスです:

public class Trackball
{
    private static final float RADIUS_ = Screen.DFLT_WIDTH / 2.0F;
    private static final int REFRESH_ = 50;
    private static final float SQRT2_ = (float) Math.sqrt(2);
    private static final float SQRT2_INVERSE_ = 1.0F / SQRT2_;

    private int count_;
    private int objx_, objy_;
    private float[] v1_, v2_;
    private float[] rot_;

    public Trackball ()
    {
        v1_ = new float[4];
        v2_ = new float[4];
        rot_ = new float[] {0, 0, 0, 1};
    }

    public void click (int x, int y)
    {
        v1_ = project(x, y);
    }

    public void drag (int x, int y)
    {
        v2_ = project(x, y);

        if (Arrays.equals(v1_, v2_)) return;

        float[] n = Vectors.cross(v2_, v1_, null);
        float[] o = Vectors.sub(v1_, v2_, null);

        float dt = Vectors.len(o) / (2.0F * RADIUS_);

        dt = dt > 1.0F ? 1.0F : dt < -1.0F ? -1.0F : dt;

        float a = 2.0F * (float) Math.asin(dt);

        Vectors.norm_r(n);
        Vectors.mul_r(n, (float) Math.sin(a / 2.0F));

        if (count_++ == REFRESH_) { count_ = 0; Quaternions.norm_r(rot_); }

        float[] q = Arrays.copyOf(n, 4);
        q[3] = (float) Math.cos(a / 2.0F);

        rot_ = Quaternions.mul(q, rot_, rot_);
    }

    public float[] getAxis ()
    {
        return Quaternions.axis(rot_, null);
    }

    public float[] project (float x, float y)
    {
        x = RADIUS_ - objx_ - x;
        y = y - objy_ - RADIUS_;

        float[] v = new float[] {x, y, 0, 0};
        float len = Vectors.len(v);
        float tr = RADIUS_ * SQRT2_INVERSE_;

        if (len < tr)
            v[2] = (float) Math.sqrt(RADIUS_ * RADIUS_ - len * len);
        else
            v[2] = tr * tr / len;

        return v;
    }
}

C++ の例と多くの類似点があることがわかります。objx_また、とのobjy_値を設定する方法はまだないことに注意してください。これらは、移動できるグラフの中心を設定するためのものです。ただ言っているだけなので、それらのフィールドについて頭を悩ませないでください。

于 2012-07-04T18:23:55.500 に答える
1

2 つの正規化されたベクトルの外積は、それ自体は正規化されません。長さはsin(theta)です。代わりにこれを試してください:

n = n.normalize().multiply((float) Math.sin(theta / 2.0F));
于 2012-07-03T18:57:32.120 に答える