Исправлена ​​ошибка, из-за которой 3D-камера двигалась в направлении, в котором она стоит?

Короткая версия (TL; DR)

у меня естьCamera прикреплен кSceneNode и движение работает нормально, покаSceneNodeВращение / оси ориентированы на мир. Однако, когда объект вращается, чтобы «смотреть» в другом направлении, и ему приказывают двигаться «вперед», это делаетне двигаться по новому «прямому» направлению. Вместо этого он продолжает двигаться в том же направлении, что и переддо вращение было применено.

Детали и пример

У меня есть график сцены для управления 3D-сценой. Граф это деревоSceneNode объекты, которые знают о своих трансформациях относительно своего родителя и мира.

Согласно TL; DR; фрагмент, представьте, что у вас естьcameraNode с нулевым вращением (например, лицом к северу), а затем повернитеcameraNode На 90 градусов влево вокруг оси + «вверх», то есть на запад. Пока все хорошо. Если вы сейчас попытаетесь переместитьcameraNode «вперед», который сейчас на западе,cameraNode вместо этого двигается, как будто «вперед» все еще смотрят на север.

Короче, двигаетсякак будто он никогда не вращался в первую очередь.

Приведенный ниже код показывает, что я пытался сделать совсем недавно, и мое (текущее) лучшее предположение о сужении областей, наиболее вероятно связанных с проблемой.

СоответствующийSceneNode члены

SceneNode реализация имеет следующие поля (показаны только те, которые имеют отношение к этому вопросу):

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

ДобавлениеCamera к сцене просто означает, что она привязана кSceneNode на графике. ПосколькуCamera не имеет собственной позиционной / вращательной информации, клиент просто обрабатываетSceneNode к которомуCamera прилагается и все.

За исключением проблемы, упомянутой в этом вопросе, все остальное работает как положено.

SceneNode Перевод

Математика для перевода узла в определенном направлении проста и в основном сводится к:

currentPosition = currentPosition + normalizedDirectionVector * offset;

SceneNode реализация следует:

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

Кроме проблемы, упомянутой в этом вопросе, все вокруг, как и ожидалось.

SceneNode вращение

Клиентское приложение вращаетcameraNode следующее:

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

ИSceneNode реализация также довольно проста:

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

Математический / код для вращения заключен в матричном объекте 3x3. Обратите внимание, что во время тестов вы можете видеть, что сцена вращается вокруг камеры, поэтому вращение действительно применяется, что делает эту проблему еще более загадочной для меня.

Векторы направления

Векторы направления - это просто столбцы, взятые из полученной матрицы вращения 3x3 относительно мира:

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

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

@Override
public Vector3 getDerivedForwardAxis() {
    return derivedRotation.column(2);
}
Вычисление производных преобразований

Если это актуально, то вот какparentNode преобразования объединяются для вычисления производных преобразованийthis пример:

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

Это используется для распространения преобразований через граф сцены, так что дочернийSceneNodes может принять во внимание преобразования своих родителей.

Другие / связанные вопросы

За последние ~ 3 недели до публикации этого вопроса я прошел через несколько ответов внутри и за пределами SO (например,Вот, Вот, Вот, а такжеВотсреди нескольких других). Очевидно, что, хотя и связаны, они действительно не помогли в моем случае.

Ответы на вопросы в комментариях

Вы уверены, что при вычисленииderivedTransform ваши родителиderivedTransform уже вычислено?

Да, родительSceneNode всегда обновляется перед обновлением детей.update логика это:

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

Этот кусок вызывает приватный метод в предыдущем разделе.