How can I calculate the rotation when using a quat

2019-07-23 12:02发布

问题:

Given a quaternion camera, how can I calculate its rotation when the player can walk on different surface normals (walls).

I'm working on a game which allows the player to walk on ceilings and walls in 3D space. I've opted to use a quaternion camera system to avoid Gimbal Lock. Given a set of up(0,1,0), right(1,0,0), and forward(0,0,1) vectors, I construct a quaternion. The player rotates around the up vector for heading and around the right vector for pitch.

Without changing gravity vectors, the camera works fine and allows the player to move about the environment as if it were a standard FPS game.

For simplicity, let's say the player can press a key which grabs the normal from the nearest collision surface that differs from their current normal, and assigns that as the new gravity vector.

Unfortunately, I've run into a brainblock, and cannot figure out how to properly get the new up, right, and forward vectors from this new gravity vector and apply them to the current rotation quaternion, or even if that is the correct way to tackle the problem. If it helps, the movement and rotation code for my camera is below.

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

回答1:

Ok, so I found a solution that works well, and it turns out, I was thinking about it wrong. The only vector that needs to change is the up vector, the others two vectors, forward and right, should stay the same. The following code will align the current quaternion with a new up vector. The trick was that you use the square of the dot product between the current up vector and new up vector and the cross vector between those two to create a new quaternion. You then multiply that by your current heading and slerp between the two. Remember to set your new up vector afterwards. You can use any value you want in the slerp to make it smoother, 1.0 makes the transition instantly.

        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