Corre para que a câmera 3D se mova na direção em que está voltada?
eu tenho umCamera
anexado a umSceneNode
e movimento funciona bem, desde que oSceneNode
Os 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.
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
MembrosoSceneNode
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çãoA 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çãoO 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çãoOs 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 derivadasSe 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 filhoSceneNode
s podem levar em consideração as transformações de seus pais.
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áriosVocê 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.