¿Arreglar que la cámara 3D se mueva en la dirección que está mirando?
tengo unCamera
adjunto a unSceneNode
y el movimiento funciona bien siempre queSceneNode
La 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.
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
MiembroslosSceneNode
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ónLa 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ónLa 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ónLos 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 derivadasSi 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ñoSceneNode
s pueden tomar en cuenta las transformaciones de sus padres.
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.