¿Arreglar que la cámara 3D se mueva en la dirección que está mirando?

La versión corta (TL; DR)

tengo unCamera adjunto a unSceneNode y el movimiento funciona bien siempre queSceneNodeLa rotación / ejes están alineados con los del mundo. Sin embargo, cuando un objeto gira para "mirar" en una dirección diferente y se le dice que se mueva "hacia adelante", lo haceno moverse a lo largo de la nueva dirección "hacia adelante". En cambio, continúa moviéndose en la misma dirección que estaba mirandoantes de Se aplicó la rotación.

Detalles y ejemplo

Tengo un gráfico de escena para administrar una escena 3D. El gráfico es un árbol deSceneNode objetos, que conocen sus transformaciones en relación con sus padres y el mundo.

Según el TL; DR; fragmento, imagina que tienes uncameraNode con rotación cero (por ejemplo, hacia el norte) y luego gire elcameraNode 90 grados a la izquierda alrededor del eje + Y "arriba", es decir, que se vea hacia el oeste. Las cosas están bien hasta ahora. Si ahora intenta mover elcameraNode "adelante", que ahora está al oeste, elcameraNode en cambio, se mueve como si "adelante" todavía estuviera mirando hacia el norte.

En resumen, se muevecomo si nunca hubiera sido girado en primer lugar.

El siguiente código muestra lo que he intentado más recientemente y mi mejor (actual) suposición para reducir las áreas que tienen más probabilidades de estar relacionadas con el problema.

PertinenteSceneNode Miembros

losSceneNode la implementación tiene los siguientes campos (solo se muestran los relevantes para esta pregunta):

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);
    // ...
}

Agregar unCamera a una escena simplemente significa que se adjunta a unSceneNode en el gráfico Desde elCamera no tiene información posicional / rotacional propia, el cliente simplemente maneja elSceneNode a lo que elCamera está adjunto y eso es todo.

Excepto por el problema mencionado en esta pregunta, todo lo demás parece estar funcionando como se esperaba.

SceneNode Traducción

La matemática para traducir el nodo en una dirección específica es sencilla y básicamente se reduce a:

currentPosition = currentPosition + normalizedDirectionVector * offset;

losSceneNode La implementación sigue:

@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;
}

Aparte del problema mencionado en esta pregunta, las cosas están como se esperaba.

SceneNode Rotación

La aplicación del cliente gira elcameraNode como sigue:

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

Y elSceneNode la implementación también es bastante sencilla:

@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;
}

La matemática / código para la rotación se encapsula en un objeto de matriz 3x3. Tenga en cuenta que, durante las pruebas, puede ver la escena girando alrededor de la cámara, por lo que se aplican rotaciones, lo que hace que este problema sea aún más desconcertante para mí.

Vectores de dirección

Los vectores direccionales son simplemente columnas tomadas de la matriz de rotación 3x3 derivada, en relación con el mundo:

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

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

@Override
public Vector3 getDerivedForwardAxis() {
    return derivedRotation.column(2);
}
Calcular transformaciones derivadas

Si es relevante, así es comoparentNode las transformaciones se combinan para calcular las transformaciones derivadas dethis ejemplo:

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);
}

Esto se usa para propagar transformaciones a través del gráfico de escena, de modo que el niñoSceneNodes pueden tomar en cuenta las transformaciones de sus padres.

Otras / preguntas relacionadas

Revisé varias respuestas dentro y fuera de SO durante las últimas ~ 3 semanas antes de publicar esta pregunta (p. Ej.aquí, aquí, aquíyaquí, entre varios otros). Obviamente, aunque relacionados, realmente no fueron útiles en mi caso.

Respuestas a preguntas en los comentarios

¿Estás seguro de que cuando computasderivedTransform tus padresderivedTransform ya está calculado?

Si el padreSceneNode siempre se actualiza antes de actualizar los hijos. losupdate la lógica es:

@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;
}

Esta pieza invoca el método privado en la sección anterior.

Respuestas a la pregunta(1)

Su respuesta a la pregunta