Corre para que a câmera 3D se mova na direção em que está voltada?

A versão curta (TL; DR)

eu tenho umCamera anexado a umSceneNode e movimento funciona bem, desde que oSceneNodeOs eixos de rotação / alinhamento estão alinhados com o mundo. No entanto, quando um objeto gira para "olhar" em uma direção diferente e é instruído a avançar "para frente", ele faznão mova ao longo da nova direção "para frente". Em vez disso, continua se movendo na mesma direção que estava enfrentandoantes a rotação foi aplicada.

Detalhes e exemplo

Eu tenho um gráfico de cena para gerenciar uma cena 3D. O gráfico é uma árvore deSceneNode objetos, que conhecem suas transformações em relação aos pais e ao mundo.

Conforme o TL; DR; trecho, imagine que você tem umcameraNode com rotação zero (por exemplo, voltada para o norte) e depois gire ocameraNode 90 graus à esquerda em torno do eixo + Y "para cima", ou seja, faça com que pareça para o oeste. As coisas estão bem até agora. Se você agora tentar mover ocameraNode "forward", que está agora a oeste, ocameraNode em vez disso, se move como se "para a frente" ainda estivesse voltado para o norte.

Em suma, ele se movecomo se nunca tivesse sido girado em primeiro lugar.

O código abaixo mostra o que tentei mais recentemente e meu melhor palpite (atual) de restringir as áreas com maior probabilidade de estar relacionadas ao problema.

RelevanteSceneNode Membros

oSceneNode A implementação possui os seguintes campos (somente os relevantes para esta pergunta são mostrados):

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

Adicionando umCamera a uma cena significa simplesmente que ela se apega a umSceneNode no gráfico. Desde oCamera não possui informações de posição / rotação próprias, o cliente simplesmente lida comSceneNode para o qualCamera está anexado e é isso.

Exceto pelo problema mencionado nesta pergunta, tudo o resto parece estar funcionando conforme o esperado.

SceneNode Tradução

A matemática para traduzir o nó em uma direção específica é direta e basicamente se resume a:

currentPosition = currentPosition + normalizedDirectionVector * offset;

oSceneNode implementação segue:

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

Além do problema mencionado nesta pergunta, as coisas são esperadas.

SceneNode Rotação

O aplicativo cliente gira ocameraNode do seguinte modo:

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

E aSceneNode A implementação também é bastante direta:

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

O código / matemática para a rotação é encapsulado em um objeto de matriz 3x3. Observe que, durante os testes, você pode ver a cena sendo girada em torno da câmera e, portanto, as rotações estão sendo aplicadas, o que torna esse problema ainda mais intrigante para mim.

Vetores de direção

Os vetores direcionais são simplesmente colunas extraídas da matriz de rotação 3x3 derivada, em relação ao 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);
}
Computando transformações derivadas

Se for relevante, é assim que oparentNode transformações são combinadas para calcular as transformações derivadas dethis instância:

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

Isso é usado para propagar transformações através do gráfico de cena, para que o filhoSceneNodes podem levar em consideração as transformações de seus pais.

Outras perguntas relacionadas

Passei por várias respostas dentro e fora da SO durante as últimas 3 semanas antes de postar esta pergunta (por exemplo,aqui, aqui, aquieaqui, entre vários outros). Obviamente, apesar de relacionados, eles realmente não foram úteis no meu caso.

Respostas às perguntas nos comentários

Você tem certeza de que, ao calcularderivedTransform seus paisderivedTransform já está computado?

Sim, os paisSceneNode é sempre atualizado antes de atualizar filhos. oupdate lógica é:

@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 peça invoca o método privado na seção anterior.

questionAnswers(1)

yourAnswerToTheQuestion