objective c - SceneKit-Mapeo de rotación física 360
objective-c quaternions (2)
Estoy teniendo dificultades para asignar el movimiento del dispositivo (fusión del sensor) a la rotación del nodo SceneKit. La premisa del problema es la siguiente,
Tengo una esfera, y la cámara está posicionada para estar dentro de la esfera de modo que el centro geométrico de la esfera y el centro de los nodos de la cámara coincidan. Lo que quiero lograr es que cuando gire físicamente alrededor de un punto, el movimiento también deba ser mapeado con precisión en la cámara.
Los detalles de la implementación son los siguientes:
Tengo un nodo, con una esfera como geomertry y es un nodo secundario al nodo raíz. Estoy usando la fusión de sensores para obtener preguntas de actitud y luego convertirlas a ángulos de Euler. El código para eso es:
- (SCNVector3)eulerAnglesFromCMQuaternion:(CMQuaternion) {
GLKQuaternion gq1 = GLKQuaternionMakeWithAngleAndAxis(GLKMathDegreesToRadians(90), 1, 0, 0);
GLKQuaternion gq2 = GLKQuaternionMake(q.x, q.y, q.z, q.w);
GLKQuaternion qp = GLKQuaternionMultiply(gq1, gq2);
CMQuaternion rq = {.x = qp.x, .y = qp.y, .z = qp.z, .w = qp.w};
CGFloat roll = atan2(-rq.x*rq.z - rq.w*rq.y, .5 - rq.y*rq.y - rq.z*rq.z);
CGFloat pitch = asin(-2*(rq.y*rq.z + rq.w*rq.x));
CGFloat yaw = atan2(rq.x*rq.y - rq.w*rq.z, .5 - rq.x*rq.x - rq.z*rq.z);
return SCNVector3Make(roll, pitch, yaw);
}
La ecuación de conversión es de 3D Math Primer para Gráficos y Desarrollo de Juegos . He utilizado para reservar como referencia, no lo he leído. Pero definitivamente en mi lista de lectura.
Ahora para simular la rotación física en SceneKit, estoy rotando mi nodo que contiene SCNCamera. Este es un hijo de rootNode. Y estoy usando la propiedad .rotation para hacer lo mismo. Una cosa a tener en cuenta aquí es que ahora el tema de actualización de movimiento de mi dispositivo se parece a esto
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryCorrectedZVertical toQueue:mq withHandler:^(CMDeviceMotion *motion, NSError *error) {
CMAttitude *currentAttitude = motion.attitude;
[SCNTransaction begin];
[SCNTransaction setDisableActions:YES];
SCNVector3 v = [self eulerAnglesFromCMQuaternion:currentAttitude.quaternion];
self.cameraNode.eulerAngles = SCNVector3Make( v.y, -v.x, 0); //SCNVector3Make(roll, yaw, pitch)
[SCNTransaction commit];
}];
Algo de precaución aquí es que los ángulos de euler son diferentes para el dispositivo y para el nodo SceneKit. Los enlaces los explican.
Los ángulos de Euler de la actitud
Y a mi entender, el mapeo entre dos es algo como esto.
Ahora el problema al que me enfrento es que la rotación de 360 grados de la cámara terminó antes de que mi rotación física sobre un punto finalice. Que estoy tratando de dpecit a través de la siguiente imagen.
Dudo que el quaternion -> euler angle conversion esté causando el error, y quaternion math está muy por encima de mi cabeza a partir de ahora.
Espero haber proporcionado toda la información, si es que se requiere algo más, me complace agregar.
¡Gracias por su descripción de su problema y el comienzo de una solución! Estoy bastante seguro de que esto no se puede hacer con los ángulos de Euler, debido a la cerradura de cardán (suelta de 1 grado de libertad).
La solución que funcionó es obtener la actitud y usar el cuaternión para establecer la orientación de la cámara:
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryCorrectedZVertical toQueue:myQueue withHandler:^(CMDeviceMotion *motion, NSError *error) {
CMAttitude *currentAttitude = motion.attitude;
[SCNTransaction begin];
[SCNTransaction setDisableActions:YES];
SCNQuaternion quaternion = [self orientationFromCMQuaternion:currentAttitude.quaternion];
_cameraNode.orientation = quaternion;
[SCNTransaction commit];
}];
- (SCNQuaternion)orientationFromCMQuaternion:(CMQuaternion)q
{
GLKQuaternion gq1 = GLKQuaternionMakeWithAngleAndAxis(GLKMathDegreesToRadians(-90), 1, 0, 0); // add a rotation of the pitch 90 degrees
GLKQuaternion gq2 = GLKQuaternionMake(q.x, q.y, q.z, q.w); // the current orientation
GLKQuaternion qp = GLKQuaternionMultiply(gq1, gq2); // get the "new" orientation
CMQuaternion rq = {.x = qp.x, .y = qp.y, .z = qp.z, .w = qp.w};
return SCNVector4Make(rq.x, rq.y, rq.z, rq.w);
}
Si CoreMotion y SceneKit no usan la misma convención para los ángulos de euler, es probable que pueda usar nodos intermedios para simplificar la conversión. Por ejemplo, al tener una jerarquía como:
YawNode
|
|___PitchNode
|
|___RollNode
|
|___CameraNode
YawNode, PitchNode, RollNode giraría solo alrededor de un solo eje. Luego, al jugar en el orden jerárquico, puede reproducir la convención de CoreMotion.
Dicho esto, no estoy seguro de que el cuaternión de conversión -> Euler sea una gran idea. Sospecho que no obtendrás una transición continua / suave en ángulos como 0 / PI / 2PI ... Intentaría quaternion-> matrix4 en su lugar y actualizaría la "transformación" de tu nodo.