Fix, damit sich die 3D-Kamera in die Richtung bewegt, in die sie zeigt?

Die Kurzversion (TL; DR)

Ich habe einCamera an ein @ angehänSceneNode und Bewegung funktioniert gut, solange dieSceneNode 's Rotation / Achsen sind mit der Welt ausgerichtet. Wenn sich ein Objekt dreht, um in eine andere Richtung zu "schauen", und aufgefordert wird, sich "vorwärts" zu bewegen, geschieht dies jedochnich Bewegen Sie sich in die neue Richtung "Vorwärts". Stattdessen bewegt es sich weiter in die gleiche Richtung, in die es gewandt warVo Die Drehung wurde angewendet.

Details und Beispiel

Ich habe ein Szenendiagramm zum Verwalten einer 3D-Szene. Die Grafik ist ein Baum vonSceneNode Objekte, die über ihre Transformationen in Bezug auf ihre Eltern und die Welt Bescheid wissen.

Wie in der TL; DR; Snippet, stell dir vor, du hast eincameraNode mit Nullrotation (z. B. nach Norden) und drehen Sie dann dascameraNode 90 Grad nach links um die + Y "Auf" -Achse, d. H. Nach Westen schauen lassen. Soweit ist alles in Ordnung. Wenn Sie jetzt versuchen, das @ zu verschiebcameraNode "Vorwärts", das ist jetzt im Westen, diecameraNode bewegt sich stattdessen so, als ob "vorwärts" immer noch nach Norden zeigt.

urz gesagt, es bewegt sich als ob es noch nie gedreht worden wäre.

Der folgende Code zeigt, was ich zuletzt versucht habe und meine (derzeitige) Vermutung, die Bereiche einzugrenzen, die am wahrscheinlichsten mit dem Problem zusammenhängen.

RelevantSceneNode Mitglieder

DasSceneNodeie @ -Implementierung enthält die folgenden Felder (nur die für diese Frage relevanten Felder werden angezeigt):

class GenericSceneNode implements SceneNode {
    // this node's parent; always null for the root scene node in the graph
    private SceneNode parentNode;

    // transforms are relative to a parent scene node, if any
    private Vector3 relativePosition = Vector3f.createZeroVector();
    private Matrix3 relativeRotation = Matrix3f.createIdentityMatrix();
    private Vector3 relativeScale    = Vector3f.createFrom(1f, 1f, 1f);

    // transforms are derived by combining transforms from all parents;
    // these are relative to the world --in world space
    private Vector3 derivedPosition = Vector3f.createZeroVector();
    private Matrix3 derivedRotation = Matrix3f.createIdentityMatrix();
    private Vector3 derivedScale    = Vector3f.createFrom(1f, 1f, 1f);
    // ...
}

Adding aCamera zu einer Szene bedeutet einfach, dass sie an ein @ angehängt wiSceneNode in der Grafik. Seit derCamera hat keine eigenen Positions- / Rotationsinformationen, der Client handhabt einfach dasSceneNode zu dem dasCamera ist beigefügt und das war's.

Außer dem in dieser Frage erwähnten Problem scheint alles andere wie erwartet zu funktionieren.

SceneNode Übersetzun

Die Mathematik zur Übersetzung des Knotens in eine bestimmte Richtung ist unkompliziert und lautet im Grunde genommen:

currentPosition = currentPosition + normalizedDirectionVector * offset;

DasSceneNode Implementierung folgt:

@Override
public void moveForward(float offset) {
    translate(getDerivedForwardAxis().mult(-offset));
}

@Override
public void moveBackward(float offset) {
    translate(getDerivedForwardAxis().mult(offset));
}

@Override
public void moveLeft(float offset) {
    translate(getDerivedRightAxis().mult(-offset));
}

@Override
public void moveRight(float offset) {
    translate(getDerivedRightAxis().mult(offset));
}

@Override
public void moveUp(float offset) {
    translate(getDerivedUpAxis().mult(offset));
}

@Override
public void moveDown(float offset) {
    translate(getDerivedUpAxis().mult(-offset));
}

@Override
public void translate(Vector3 tv) {
    relativePosition = relativePosition.add(tv);
    isOutOfDate = true;
}

Ander als das in dieser Frage erwähnte Problem, Dinge herum wie erwartet.

SceneNode Rotation

Die Client-Anwendung dreht dascameraNode wie folgt

final Angle rotationAngle = new Degreef(-90f);
// ...
cameraNode.yaw(rotationAngle);

Und dieSceneNodeie Implementierung von @ ist auch ziemlich einfach:

@Override
public void yaw(Angle angle) {
    // FIXME?: rotate(angle, getDerivedUpAxis()) accumulates other rotations
    rotate(angle, Vector3f.createUnitVectorY());
}

@Override
public void rotate(Angle angle, Vector3 axis) {
    relativeRotation = relativeRotation.rotate(angle, axis);
    isOutOfDate = true;
}

Der Mathe- / Code für die Drehung ist in einem 3x3-Matrixobjekt eingekapselt. Beachten Sie, dass Sie während der Tests sehen können, wie die Szene um die Kamera gedreht wird, sodass tatsächlich Drehungen angewendet werden, was dieses Problem für mich noch rätselhafter macht.

Direction Vectors

Die Richtungsvektoren sind einfach Spalten aus der abgeleiteten 3x3-Rotationsmatrix relativ zur Welt:

@Override
public Vector3 getDerivedRightAxis() {
    return derivedRotation.column(0);
}

@Override
public Vector3 getDerivedUpAxis() {
    return derivedRotation.column(1);
}

@Override
public Vector3 getDerivedForwardAxis() {
    return derivedRotation.column(2);
}
Computing Derived Transforms

Wenn es relevant ist, ist dies, wie dieparentNode -Transformationen werden kombiniert, um die abgeleiteten Transformationen von @ zu berechnethis instance:

private void updateDerivedTransforms() {
    if (parentNode != null) {
        /**
         * derivedRotation = parent.derivedRotation * relativeRotation
         * derivedScale    = parent.derivedScale    * relativeScale
         * derivedPosition = parent.derivedPosition + parent.derivedRotation * (parent.derivedScale * relativePosition)
         */
        derivedRotation = parentNode.getDerivedRotation().mult(relativeRotation);
        derivedScale = parentNode.getDerivedScale().mult(relativeScale);

        Vector3 scaledPosition = parentNode.getDerivedScale().mult(relativePosition);
        derivedPosition = parentNode.getDerivedPosition().add(parentNode.getDerivedRotation().mult(scaledPosition));
    } else {
        derivedPosition = relativePosition;
        derivedRotation = relativeRotation;
        derivedScale = relativeScale;
    }

    Matrix4 t, r, s;

    t = Matrix4f.createTranslationFrom(relativePosition);
    r = Matrix4f.createFrom(relativeRotation);
    s = Matrix4f.createScalingFrom(relativeScale);
    relativeTransform = t.mult(r).mult(s);

    t = Matrix4f.createTranslationFrom(derivedPosition);
    r = Matrix4f.createFrom(derivedRotation);
    s = Matrix4f.createScalingFrom(derivedScale);
    derivedTransform = t.mult(r).mult(s);
}

Dies wird verwendet, um Transformationen durch das Szenendiagramm zu verbreiten, so dass untergeordneteSceneNodes können die Transformationen ihrer Eltern berücksichtigen.

Andere / Verwandte Fragen

Ich habe in den letzten ~ 3 Wochen vor dem Posten dieser Frage mehrere Antworten innerhalb und außerhalb von SO durchgesehen (z. B.Hie, Hie, Hie, undHie, unter anderem). Offensichtlich waren sie, obwohl verwandt, in meinem Fall wirklich nicht hilfreich.

Antworten auf Fragen in den Kommentaren

Sind Sie sicher, dass beim RechnenderivedTransform deine ElternderivedTransform wird bereits berechnet?

Ja, das übergeordneteSceneNode wird immer aktualisiert, bevor untergeordnete Elemente aktualisiert werden. Dasupdate Logik ist:

@Override
public void update(boolean updateChildren, boolean parentHasChanged) {
    boolean updateRequired = parentHasChanged || isOutOfDate;

    // update this node's transforms before updating children
    if (updateRequired)
        updateFromParent();

    if (updateChildren)
        for (Node n : childNodesMap.values())
            n.update(updateChildren, updateRequired);

    emitNodeUpdated(this);
}

@Override
public void updateFromParent() {
    updateDerivedTransforms();  // implementation above
    isOutOfDate = false;
}

Dieses Stück ruft die private Methode im vorherigen Abschnitt auf.

Antworten auf die Frage(2)

Ihre Antwort auf die Frage