2012-01-03 115 views
2

给定一个四元数相机,当玩家可以在不同的表面法线(墙)上行走时,该如何计算其旋转角度。如何在使用四元数相机时计算旋转角度?

我正在制作一款游戏,允许玩家在3D空间中的天花板和墙壁上行走。我选择使用四元数镜头系统来避免Gimbal Lock。给定一组up(0,1,0),right(1,0,0)和forward(0,0,1)向量,我构造一个四元数。玩家围绕朝向旋转向上矢量,围绕右向量围绕俯仰旋转。

在不改变重力矢量的情况下,相机可以正常工作,并允许玩家在环境中移动,就好像它是标准的FPS游戏。

为了简单起见,我们假设玩家可以按下一个按键,该按键从与当前法线不同的最近碰撞表面捕捉法线,并将其指定为新的重力矢量。

不幸的是,我遇到了一个脑袋,并无法弄清楚如何从这个新的重力矢量正确地获取新的向上,向右和向前的矢量,并将它们应用到当前的旋转四元数,或者即使这是解决问题的正确方法。如果有帮助,我的相机的移动和旋转代码如下。

Field forwardVector:TVector = TVector.Create(0,0,1) 
Field rightVector:TVector = TVector.Create(1,0,0) 
Field upVector:TVector = TVector.Create(0,1,0) 

Field pos:TVector = New TVector 
Field headingQuaternion:TQuaternion = TQuaternion.Create() 
Field pitchQuaternion:TQuaternion = TQuaternion.Create() 
Field combinedRotation:TQuaternion = TQuaternion.Create() 
Field gravityVector:TVector = TVector.Create(0,1,0) 

'--------- 
'ChangeGravityVector 
'--------- 
Method ChangeGravityVector(newGravityVector:TVector) 

    gravityVector = newGravityVector 

End Method 

'--------- 
'MoveForward 
'--------- 
Method MoveForward(moveAmount:Float, noGravity:Byte = False) 

    Local vecRot:TVector 

    If(noGravity = True) 

     headingQuaternion.MultiplyByVector(forwardVector) 
     vecRot = combinedRotation.MultiplyByVector(forwardVector) 

    Else 

     vecRot = headingQuaternion.MultiplyByVector(forwardVector) 

    EndIf 

    vecRot.ScaleVector(moveAmount) 
    pos.AddVector(vecRot) 

End Method 

'--------- 
'MoveUp 
'--------- 
Method MoveUp(moveAmount:Float, noGravity:Byte = False ) 

    Local vecRot:TVector 

    If(noGravity = True) 

     headingQuaternion.MultiplyByVector(gravityVector) 
     vecRot = combinedRotation.MultiplyByVector(gravityVector) 

    Else 

     vecRot = headingQuaternion.MultiplyByVector(gravityVector) 

    EndIf 

    vecRot.ScaleVector(moveAmount) 
    pos.AddVector(vecRot) 

End Method 

'--------- 
'MoveRight 
'--------- 
Method MoveRight(moveAmount:Float, noGravity:Byte = False ) 

    Local vecRot:TVector 

    If(noGravity = True) 

     headingQuaternion.MultiplyByVector(rightVector) 
     vecRot = combinedRotation.MultiplyByVector(rightVector) 

    Else 

     vecRot = headingQuaternion.MultiplyByVector(rightVector) 

    EndIf 

    vecRot.ScaleVector(moveAmount) 
    pos.AddVector(vecRot) 

End Method 

'--------- 
'RotateX 
'--------- 
Method RotateX(rotateAmount:Float) 

    Local xRotQuat:TQuaternion = TQuaternion.Create() 
    xRotQuat.ConvertFromAxisAngle(rightVector, rotateAmount) 
    pitchQuaternion = pitchQuaternion.MultiplyByQuaternion(xRotQuat) 

End Method 

'--------- 
'RotateY 
'--------- 
Method RotateY(rotateAmount:Float) 

    Local yRotQuat:TQuaternion = TQuaternion.Create() 
    yRotQuat.ConvertFromAxisAngle(gravityVector, rotateAmount) 
    headingQuaternion = yRotQuat.MultiplyByQuaternion(headingQuaternion) 

End Method 

'--------- 
'GetCameraMatrix 
'--------- 
Method GetCameraMatrix:TMatrix4x4() 

    combinedRotation = headingQuaternion.MultiplyByQuaternion(pitchQuaternion) 
    Return combinedRotation.GetMatrix() 

End Method 

回答

2

好的,所以我找到了一个解决方案,效果很好,事实证明,我在想它是错的。唯一需要改变的向量是向上向量,其他两个向前向右的向量应该保持不变。下面的代码将把当前的四元数与一个新的向量对齐。诀窍是你使用当前向上向量和新向量之间的点积的平方以及这两个向量之间的交叉向量来创建一个新的四元数。然后你用当前的标题和slerp乘以两者之间的距离。记得在之后设置新的向量。你可以在slerp中使用任何你想要的值来使它更平滑,1.0立即进行转换。

 Local newQuat:TQuaternion = New TQuaternion 
     Local cross:TVector = upVector.GetCrossProduct(newGravityVector) 
     Local dot:Float = upVector.GetDotProduct(newGravityVector) 
     Local dotSquare:Float = Sqr((1.0 + dot) * 2.0) 
     Local scale:Float = 1.0/dotSquare 
     newQuat.x = cross.x*scale 
     newQuat.y = cross.y*scale 
     newQuat.z = cross.z*scale 
     newQuat.w = dotSquare*0.5 

     newQuat = newQuat.MultiplyByQuaternion(headingQuaternion) 

     headingQuaternion = headingQuaternion.Slerp(headingQuaternion, newQuat, 1.0) 

     gravityVector = newGravityVector 
     upVector = newGravityVector