Короткая версия (TL; DR)
У меня есть Camera
, прикрепленный к SceneNode
, и движение работает отлично, пока оси вращения SceneNode
совпадают с мировыми. Однако, когда объект вращается, чтобы "смотреть" в другом направлении и ему говорят двигаться "вперед" , он не перемещается по новому "прямому" направлению. Вместо этого он продолжает двигаться в том же направлении, перед которым было применено вращение.
Подробности и пример
У меня есть график сцены для управления 3D-сценой. Граф - это дерево объектов SceneNode
, которые знают об их преобразованиях относительно их родителя и мира.
В соответствии с TL; DR; snippet, представьте, что у вас есть cameraNode
с нулевым вращением (например, на север), а затем поверните cameraNode
на 90 градусов влево вокруг оси + Y вверх, т.е. посмотрите на запад. До сих пор все в порядке. Если вы теперь попытаетесь переместить cameraNode
"вперед" , который теперь находится на западе, cameraNode
вместо этого движется так, как будто "вперед" все еще смотрят на север.
Короче говоря, он движется так, как будто он никогда не вращался в первую очередь.
В приведенном ниже коде показано, что я предпринял совсем недавно, и мое (текущее) лучшее предположение о сужении областей, наиболее вероятно связанных с проблемой.
Релевантно SceneNode
Члены
Реализация SceneNode
имеет следующие поля (показаны только те, которые относятся к этому вопросу):
class GenericSceneNode implements SceneNode {
// this node 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
Перевод
Математика для перевода node в определенном направлении является простой и в основном сводится к:
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);
}
Это используется для распространения преобразований через граф сцены, так что дочерний элемент SceneNode
может принимать во внимание свои родительские преобразования.
Другие/Вопросы, относящиеся
Я провел несколько ответов внутри и вне SO в течение последних ~ 3 недель до публикации этого вопроса (например, здесь, здесь, здесь и здесь, среди несколько других). Очевидно, что, хотя они и были связаны, они действительно не помогли в моем случае.
Ответы на вопросы в комментариях
Вы уверены, что при вычислении
derivedTransform
ваш родительскийderivedTransform
уже вычислен?
Да, родительский SceneNode
всегда обновляется перед обновлением дочерних элементов. Логика update
:
@Override
public void update(boolean updateChildren, boolean parentHasChanged) {
boolean updateRequired = parentHasChanged || isOutOfDate;
// update this node 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;
}
Эта часть вызывает частный метод в предыдущем разделе.