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