如何在SceneKit中实现旋转与欧拉角的相互转换?
替代虚拟节点的欧拉角与四元数转换实现
你当前通过创建临时SCNNode来完成转换的方式确实不够高效,因为每次转换都会生成一个无意义的节点对象。我们可以直接基于四元数与欧拉角的数学转换公式来实现,完全不需要依赖SceneKit的节点对象。
1. 四元数(SCNVector4)转欧拉角(SCNVector3)
SCNVector4的结构是(x, y, z, w),对应四元数的(qx, qy, qz, qw)分量。以下实现和SceneKit内部逻辑一致,按俯仰(pitch)-偏航(yaw)-滚转(roll)(X-Y-Z)的顺序计算欧拉角:
func rotationToEuler(rotation: SCNVector4) -> SCNVector3 { let qx = rotation.x let qy = rotation.y let qz = rotation.z let qw = rotation.w // 计算X轴俯仰角 let sinr = 2 * (qw * qx + qy * qz) let cosr = 1 - 2 * (qx * qx + qy * qy) let roll = atan2(sinr, cosr) // 计算Y轴偏航角(处理数值边界避免溢出) let sinp = 2 * (qw * qy - qz * qx) let pitch: CGFloat if abs(sinp) >= 1 { pitch = copysign(.pi / 2, sinp) } else { pitch = asin(sinp) } // 计算Z轴滚转角 let siny = 2 * (qw * qz + qx * qy) let cosy = 1 - 2 * (qy * qy + qz * qz) let yaw = atan2(siny, cosy) return SCNVector3(pitch, yaw, roll) }
2. 欧拉角(SCNVector3)转四元数(SCNVector4)
基于X-Y-Z顺序的欧拉角转四元数公式,计算出对应分量后返回SCNVector4:
func eulerToRotation(eulerAngles: SCNVector3) -> SCNVector4 { let pitch = eulerAngles.x let yaw = eulerAngles.y let roll = eulerAngles.z // 预计算半角的正余弦值,减少重复计算 let cy = cos(yaw * 0.5) let sy = sin(yaw * 0.5) let cp = cos(pitch * 0.5) let sp = sin(pitch * 0.5) let cr = cos(roll * 0.5) let sr = sin(roll * 0.5) // 计算四元数各分量 let qw = cy * cp * cr + sy * sp * sr let qx = cy * cp * sr - sy * sp * cr let qy = sy * cp * sr + cy * sp * cr let qz = sy * cp * cr - cy * sp * sr return SCNVector4(qx, qy, qz, qw) }
说明
- 这两个函数的转换结果和原节点中转的方式完全一致,保证兼容性。
- 避免了临时节点的创建开销,在频繁转换的场景下性能更优。
内容的提问来源于stack exchange,提问作者OMGPOP
相关产品推荐
相关产品推荐

