BGE bug #17657 fixed: dRotY doesn't work properly after 90 degrees rotation.

This problem is caused by discontinuities in the conversion
orientation matrix -> euler angles: the angle sign can
switch and thus the direction of the rotation produced
by the dRot Ipo.

To avoid this bug, the matrix->euler conversion must be 
avoided during the game. I took the following approach that 
is compatible with Blender (identical effect in the game and
in the 3D view):

- no change in Add mode: Rot and dRot are treated as additional
rotation to the orientation at the start of the Ipo. There is 
no matrix->euler conversion and thus no discontinuities. 

- Rot Ipo are treated as absolute rotation. All 3 axis should
be specified but if they are not, the startup object orientation
will be used to set the unspecified axis. By doing a matrix->
euler conversion once at the start, the discontinuities are
avoided. If there are also dRot curves, they are treated as
delta of the corresponding Rot curve or startup angle.

- dRot Ipo are treated as Add mode in Local axis.

Note about Add mode: Rot and dRot curves are treated identically
during the game. However, only dRot curves make sense because
they don't interfere with the object orientation in the 3D view.
This commit is contained in:
Benoit Bolsee 2008-09-23 20:07:15 +00:00
parent 3cf87f4c20
commit 005c5e6371
2 changed files with 85 additions and 36 deletions

@ -59,7 +59,9 @@ KX_IpoSGController::KX_IpoSGController()
m_ipo_local(false),
m_modified(true),
m_ipo_start_initialized(false),
m_ipotime(1.0)
m_ipotime(1.0),
m_ipo_start_euler(0.0,0.0,0.0),
m_ipo_euler_initialized(false)
{
m_game_object = NULL;
for (int i=0; i < KX_MAX_IPO_CHANNELS; i++)
@ -136,6 +138,11 @@ bool KX_IpoSGController::Update(double currentTime)
m_ipo_start_orient = ob->GetLocalOrientation();
m_ipo_start_scale = ob->GetLocalScale();
m_ipo_start_initialized = true;
if (!m_ipo_euler_initialized) {
// do it only once to avoid angle discontinuities
m_ipo_start_orient.getEuler(m_ipo_start_euler[0], m_ipo_start_euler[1], m_ipo_start_euler[2]);
m_ipo_euler_initialized = true;
}
}
//modifies position?
@ -199,51 +206,87 @@ bool KX_IpoSGController::Update(double currentTime)
ob->GetWorldOrientation() * m_ipo_xform.GetEulerAngles() :
m_ipo_xform.GetEulerAngles(), false);
}
} else {
double yaw=0, pitch=0, roll=0; //final Euler angles
double tempYaw=0, tempPitch=0, tempRoll=0; //temp holders
if (!m_ipo_add)
ob->GetLocalOrientation().getEuler(yaw, pitch, roll);
} else if (m_ipo_add) {
if (m_ipo_start_initialized) {
double yaw=0, pitch=0, roll=0; //delta Euler angles
//RotX and dRotX
if (m_ipo_channels_active[OB_ROT_X]) {
yaw = (m_ipo_channels_active[OB_DROT_X] ? (m_ipo_xform.GetEulerAngles()[0] + m_ipo_xform.GetDeltaEulerAngles()[0]) : m_ipo_xform.GetEulerAngles()[0] );
}
else if (m_ipo_channels_active[OB_DROT_X] && m_ipo_start_initialized) {
if (!m_ipo_add)
m_ipo_start_orient.getEuler(tempYaw, tempPitch, tempRoll);
yaw = tempYaw + m_ipo_xform.GetDeltaEulerAngles()[0];
}
//RotX and dRotX
if (m_ipo_channels_active[OB_ROT_X])
yaw += m_ipo_xform.GetEulerAngles()[0];
if (m_ipo_channels_active[OB_DROT_X])
yaw += m_ipo_xform.GetDeltaEulerAngles()[0];
//RotY dRotY
if (m_ipo_channels_active[OB_ROT_Y])
pitch += m_ipo_xform.GetEulerAngles()[1];
if (m_ipo_channels_active[OB_DROT_Y])
pitch += m_ipo_xform.GetDeltaEulerAngles()[1];
//RotZ and dRotZ
if (m_ipo_channels_active[OB_ROT_Z])
roll += m_ipo_xform.GetEulerAngles()[2];
if (m_ipo_channels_active[OB_DROT_Z])
roll += m_ipo_xform.GetDeltaEulerAngles()[2];
//RotY dRotY
if (m_ipo_channels_active[OB_ROT_Y]) {
pitch = (m_ipo_channels_active[OB_DROT_Y] ? (m_ipo_xform.GetEulerAngles()[1] + m_ipo_xform.GetDeltaEulerAngles()[1]) : m_ipo_xform.GetEulerAngles()[1] );
}
else if (m_ipo_channels_active[OB_DROT_Y] && m_ipo_start_initialized) {
if (!m_ipo_add)
m_ipo_start_orient.getEuler(tempYaw, tempPitch, tempRoll);
pitch = tempPitch + m_ipo_xform.GetDeltaEulerAngles()[1];
}
//RotZ and dRotZ
if (m_ipo_channels_active[OB_ROT_Z]) {
roll = (m_ipo_channels_active[OB_DROT_Z] ? (m_ipo_xform.GetEulerAngles()[2] + m_ipo_xform.GetDeltaEulerAngles()[2]) : m_ipo_xform.GetEulerAngles()[2] );
}
else if (m_ipo_channels_active[OB_DROT_Z] && m_ipo_start_initialized) {
if (!m_ipo_add)
m_ipo_start_orient.getEuler(tempYaw, tempPitch, tempRoll);
roll = tempRoll + m_ipo_xform.GetDeltaEulerAngles()[2];
}
if (m_ipo_add) {
MT_Matrix3x3 rotation(MT_Vector3(yaw, pitch, roll));
if (m_ipo_local)
rotation = m_ipo_start_orient * rotation;
else
rotation = rotation * m_ipo_start_orient;
ob->SetLocalOrientation(rotation);
} else {
}
} else if (m_ipo_channels_active[OB_ROT_X] || m_ipo_channels_active[OB_ROT_Y] || m_ipo_channels_active[OB_ROT_Z]) {
if (m_ipo_euler_initialized) {
// assume all channel absolute
// All 3 channels should be specified but if they are not, we will take
// the value at the start of the game to avoid angle sign reversal
double yaw=m_ipo_start_euler[0], pitch=m_ipo_start_euler[1], roll=m_ipo_start_euler[2];
//RotX and dRotX
if (m_ipo_channels_active[OB_ROT_X]) {
yaw = (m_ipo_channels_active[OB_DROT_X] ? (m_ipo_xform.GetEulerAngles()[0] + m_ipo_xform.GetDeltaEulerAngles()[0]) : m_ipo_xform.GetEulerAngles()[0] );
}
else if (m_ipo_channels_active[OB_DROT_X]) {
yaw += m_ipo_xform.GetDeltaEulerAngles()[0];
}
//RotY dRotY
if (m_ipo_channels_active[OB_ROT_Y]) {
pitch = (m_ipo_channels_active[OB_DROT_Y] ? (m_ipo_xform.GetEulerAngles()[1] + m_ipo_xform.GetDeltaEulerAngles()[1]) : m_ipo_xform.GetEulerAngles()[1] );
}
else if (m_ipo_channels_active[OB_DROT_Y]) {
pitch += m_ipo_xform.GetDeltaEulerAngles()[1];
}
//RotZ and dRotZ
if (m_ipo_channels_active[OB_ROT_Z]) {
roll = (m_ipo_channels_active[OB_DROT_Z] ? (m_ipo_xform.GetEulerAngles()[2] + m_ipo_xform.GetDeltaEulerAngles()[2]) : m_ipo_xform.GetEulerAngles()[2] );
}
else if (m_ipo_channels_active[OB_DROT_Z]) {
roll += m_ipo_xform.GetDeltaEulerAngles()[2];
}
ob->SetLocalOrientation(MT_Vector3(yaw, pitch, roll));
}
} else if (m_ipo_start_initialized) {
// only DROT, treat as Add
double yaw=0, pitch=0, roll=0; //delta Euler angles
//dRotX
if (m_ipo_channels_active[OB_DROT_X])
yaw = m_ipo_xform.GetDeltaEulerAngles()[0];
//dRotY
if (m_ipo_channels_active[OB_DROT_Y])
pitch = m_ipo_xform.GetDeltaEulerAngles()[1];
//dRotZ
if (m_ipo_channels_active[OB_DROT_Z])
roll = m_ipo_xform.GetDeltaEulerAngles()[2];
// dRot are always local
MT_Matrix3x3 rotation(MT_Vector3(yaw, pitch, roll));
rotation = m_ipo_start_orient * rotation;
ob->SetLocalOrientation(rotation);
}
}
//modifies scale?

@ -72,6 +72,12 @@ class KX_IpoSGController : public SG_Controller
/** if IPO initial position has been set for local normal IPO */
bool m_ipo_start_initialized;
/** Euler angles at the start of the game, needed for incomplete ROT Ipo curves */
class MT_Vector3 m_ipo_start_euler;
/** true is m_ipo_start_euler has been initialized */
bool m_ipo_euler_initialized;
/** A reference to the original game object. */
class KX_GameObject* m_game_object;