Краткая версия (TL; DR)
У меня есть Camera
, прикрепленный к SceneNode
, и движение работает нормально, пока вращение/оси SceneNode
выровнены с миром. Однако, когда объект поворачивается, чтобы «смотреть» в другом направлении, и получает указание двигаться «вперед», он не движется в новом направлении «вперед». Вместо этого он продолжает двигаться в том же направлении, в котором был до поворота.
Детали и пример
У меня есть график сцены для управления 3D-сценой. Граф представляет собой дерево из SceneNode
объектов, которые знают о своих преобразованиях относительно своего родителя и мира.
Согласно TL;DR; фрагмент, представьте, что у вас есть cameraNode
с нулевым поворотом (например, лицом на север), а затем поверните cameraNode
на 90 градусов влево вокруг оси +Y «вверх», то есть сделайте так, чтобы он смотрел на запад. Пока все в порядке. Если вы теперь попытаетесь переместить 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);
}
Это используется для распространения преобразований по графу сцены, чтобы дочерние SceneNode
могли учитывать преобразования своих родителей.
Другие/связанные вопросы
Я просмотрел несколько ответов внутри и вне SO за последние ~ 3 недели до публикации этого вопроса (например, здесь, здесь, здесь и здесь среди нескольких других). Очевидно, хотя и связанные, они действительно не помогли в моем случае.
Ответы на вопросы в комментариях
Вы уверены, что при вычислении
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;
}
Эта часть вызывает закрытый метод из предыдущего раздела.
Camera
иSceneNode
были определены мной. Даже если вы не знакомы с Java, вы знакомы с C++ и OpenGL, поэтому вы, вероятно, уже понимаете семантику приведенного выше кода (без перегрузки операторов). Возможно, вы все еще сможете заметить что-то, что я пропустил (например, математику или что-то еще). Если вы хотите, чтобы я добавил больше информации, дайте мне знать, что это будет. - person code_dredd   schedule 08.08.2016Camera
имеет свою собственную информацию, и ему не нужно полагаться наSceneNode
для своей позиции / поворота, и он может двигаться самостоятельно. Он ведет себя точно так же, как и выше, с той же проблемой. - person code_dredd   schedule 08.08.2016derivedTransform
производное преобразование вашего родителя уже вычислено? Или, может быть, вы могли бы создать сцену с одним узлом и подключенной камерой и отладить свои вычисления? - person kolenda   schedule 11.08.2016SceneNode
обновит себя, прежде чем пытаться обновить любые дочерние элементы, которые у него могут быть. - person code_dredd   schedule 12.08.2016