Paranoia2/game_shared/procbones.cpp

303 lines
8.9 KiB
C++
Raw Normal View History

//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
2020-08-31 18:50:41 +02:00
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#include "mathlib.h"
#include "studio.h"
//-----------------------------------------------------------------------------
// Purpose: look at single column vector of another bones local transformation
// and generate a procedural transformation based on how that column
// points down the 6 cardinal axis (all negative weights are clamped to 0).
//-----------------------------------------------------------------------------
bool DoAxisInterpBone( mstudioaxisinterpbone_t *pProc, mstudiobone_t *pbones, int iBone, matrix3x4 *bonetransform )
{
Vector control;
if( pbones[pProc->control].parent != -1 ) // invert it back into parent's space.
control = bonetransform[pbones[pProc->control].parent].VectorIRotate( bonetransform[iBone][pProc->axis] );
else control = bonetransform[iBone][pProc->axis];
Vector4D *q1, *q2, *q3;
Vector *p1, *p2, *p3;
// find axial control inputs
float a1 = control.x;
float a2 = control.y;
float a3 = control.z;
if( a1 >= 0.0f )
{
q1 = &pProc->quat[0];
p1 = &pProc->pos[0];
}
else
{
a1 = -a1;
q1 = &pProc->quat[1];
p1 = &pProc->pos[1];
}
if( a2 >= 0.0f )
{
q2 = &pProc->quat[2];
p2 = &pProc->pos[2];
}
else
{
a2 = -a2;
q2 = &pProc->quat[3];
p2 = &pProc->pos[3];
}
if( a3 >= 0.0f )
{
q3 = &pProc->quat[4];
p3 = &pProc->pos[4];
}
else
{
a3 = -a3;
q3 = &pProc->quat[5];
p3 = &pProc->pos[5];
}
Vector4D v, tmp;
Vector p = g_vecZero;
// do a three-way blend
if( a1 + a2 > 0.0f )
{
float t = 1.0 / (a1 + a2 + a3);
// FIXME: do a proper 3-way Quat blend!
QuaternionSlerp( *q2, *q1, a1 / (a1 + a2), tmp );
QuaternionSlerp( tmp, *q3, a3 * t, v );
p += *p1 * ( a1 * t );
p += *p2 * ( a2 * t );
p += *p3 * ( a3 * t );
bonetransform[iBone] = bonetransform[pbones[iBone].parent].ConcatTransforms( matrix3x4( p, v ));
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Generate a procedural transformation based on how that another bones
// local transformation matches a set of target orientations.
//-----------------------------------------------------------------------------
bool DoQuatInterpBone( mstudioquatinterpbone_t *pProc, mstudiobone_t *pbones, mstudioquatinterpinfo_t *pTrigger, int iBone, matrix3x4 *bonetransform )
{
matrix3x4 bonematrix;
if( pbones[pProc->control].parent != -1 )
{
Vector4D src;
float weight[32]; // !!! MAXSTUDIOBONETRIGGERS
float scale = 0.0f;
Vector4D quat;
Vector pos;
int i;
matrix3x4 tmpmatrix = bonetransform[pbones[pProc->control].parent].Invert();
matrix3x4 controlmatrix = tmpmatrix.ConcatTransforms( bonetransform[pProc->control] );
src = controlmatrix.GetQuaternion();
for( i = 0; i < pProc->numtriggers; i++ )
{
float dot = fabs( DotProduct( pTrigger[i].trigger, src ));
// FIXME: a fast acos should be acceptable
dot = bound( -1.0f, dot, 1.0f );
weight[i] = 1.0f - ( 2.0f * acos( dot ) * pTrigger[i].inv_tolerance );
weight[i] = Q_max( 0.0f, weight[i] );
scale += weight[i];
}
if( scale <= 0.001f ) // EPSILON?
return false;
for( i = 0; i < pProc->numtriggers; i++ )
{
if( weight[i] != 0.0f )
break;
}
// triggers are not triggered
if( i == pProc->numtriggers )
return false;
scale = 1.0f / scale;
quat.Init();
pos.Init();
for( i = 0; i < pProc->numtriggers; i++ )
{
if( weight[i] == 0.0f )
continue;
float s = weight[i] * scale;
QuaternionAlign( pTrigger[i].quat, quat, quat );
// g-cont. why valve don't use slerp here?..
quat.x = quat.x + s * pTrigger[i].quat.x;
quat.y = quat.y + s * pTrigger[i].quat.y;
quat.z = quat.z + s * pTrigger[i].quat.z;
quat.w = quat.w + s * pTrigger[i].quat.w;
pos.x = pos.x + s * pTrigger[i].pos.x;
pos.y = pos.y + s * pTrigger[i].pos.y;
pos.z = pos.z + s * pTrigger[i].pos.z;
}
quat = quat.Normalize(); // g-cont. is this really needs?
bonematrix = matrix3x4( pos, quat );
bonetransform[iBone] = bonetransform[pbones[iBone].parent].ConcatTransforms( bonematrix );
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Generate a procedural transformation so that one bone points at
// another point on the model
//-----------------------------------------------------------------------------
void DoAimAtBone( mstudioaimatbone_t *pProc, Vector4D &q1, mstudiobone_t *pbones, int iBone, matrix3x4 *bonetransform, const studiohdr_t *pStudioHdr )
{
// The world matrix of the bone to change
matrix3x4 boneMatrix;
// Guaranteed to be unit length
const Vector &userAimVector = pProc->aimvector;
// Guaranteed to be unit length
const Vector &userUpVector = pProc->upvector;
// Get to get position of bone but also for up reference
matrix3x4 parentSpace = bonetransform[pProc->parent];
// World space position of the bone to aim
Vector aimWorldPosition = parentSpace.VectorTransform( pProc->basepos );
// The worldspace pos to aim at
Vector aimAtWorldPosition;
if( pStudioHdr )
{
// This means it's AIMATATTACH
mstudioattachment_t *pattachment = (mstudioattachment_t *) ((byte *)pStudioHdr + pStudioHdr->attachmentindex) + pProc->aim;
aimAtWorldPosition = bonetransform[pattachment->bone].VectorTransform( pattachment->org );
}
else
{
aimAtWorldPosition = bonetransform[pProc->aim].GetOrigin();
}
// The aim and up data is relative to this bone, not the parent bone
matrix3x4 bonematrix, boneLocalToWorld;
bonematrix = matrix3x4( pProc->basepos, q1 );
boneLocalToWorld = bonetransform[pProc->parent].ConcatTransforms( bonematrix );
Vector aimVector = (aimAtWorldPosition - aimWorldPosition).Normalize();
Vector axis = CrossProduct( userAimVector, aimVector ).Normalize();
float angle( acosf( DotProduct( userAimVector, aimVector )));
Vector4D aimRotation;
AxisAngleQuaternion( axis, RAD2DEG( angle ), aimRotation );
if(( 1.0f - fabs( DotProduct( userUpVector, userAimVector ))) > FLT_EPSILON )
{
matrix3x4 aimRotationMatrix = matrix3x4( g_vecZero, aimRotation );
Vector tmp_pUp = aimRotationMatrix.VectorRotate( userUpVector );
Vector tmpV = aimVector * DotProduct( aimVector, tmp_pUp );
Vector pUp = (tmp_pUp - tmpV).Normalize();
Vector tmp_pParentUp = boneLocalToWorld.VectorRotate( userUpVector );
tmpV = aimVector * DotProduct( aimVector, tmp_pParentUp );
Vector pParentUp = (tmp_pParentUp - tmpV).Normalize();
Vector4D upRotation;
if( 1.0f - fabs( DotProduct( pUp, pParentUp )) > FLT_EPSILON )
{
angle = acos( DotProduct( pUp, pParentUp ));
axis = CrossProduct( pUp, pParentUp );
}
else
{
angle = 0;
axis = pUp;
}
axis = axis.Normalize();
AxisAngleQuaternion( axis, RAD2DEG( angle ), upRotation );
Vector4D boneRotation;
QuaternionMult( upRotation, aimRotation, boneRotation );
boneMatrix = matrix3x4( aimWorldPosition, boneRotation );
}
else
{
boneMatrix = matrix3x4( aimWorldPosition, aimRotation );
}
bonetransform[iBone] = boneMatrix;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CalcProceduralBone( const studiohdr_t *pStudioHdr, int iBone, matrix3x4 *bonetransform )
{
if( !FBitSet( pStudioHdr->flags, STUDIO_HAS_BONEINFO ))
return false; // info about procedural bones is absent
mstudiobone_t *pbones = (mstudiobone_t *)((byte *)pStudioHdr + pStudioHdr->boneindex);
mstudioboneinfo_t *pinfo = (mstudioboneinfo_t *)((byte *)pbones + pStudioHdr->numbones * sizeof( mstudiobone_t ));
mstudioaxisinterpbone_t *pProcAxis;
mstudioquatinterpbone_t *pProcQuat;
mstudioquatinterpinfo_t *pTrigger;
mstudioaimatbone_t *pProcAimAt;
if( FBitSet( pbones[iBone].flags, BONE_ALWAYS_PROCEDURAL ) && pinfo[iBone].procindex )
{
Vector4D quat = pinfo[iBone].quat;
switch( pinfo[iBone].proctype )
{
case STUDIO_PROC_AXISINTERP:
pProcAxis = (mstudioaxisinterpbone_t *)((byte *)pStudioHdr + pinfo[iBone].procindex);
if( DoAxisInterpBone( pProcAxis, pbones, iBone, bonetransform ))
return true;
break;
case STUDIO_PROC_QUATINTERP:
pProcQuat = (mstudioquatinterpbone_t *)((byte *)pStudioHdr + pinfo[iBone].procindex);
pTrigger = (mstudioquatinterpinfo_t *)((byte *)pStudioHdr + pProcQuat->triggerindex);
if( DoQuatInterpBone( pProcQuat, pbones, pTrigger, iBone, bonetransform ))
return true;
break;
case STUDIO_PROC_AIMATBONE:
pProcAimAt = (mstudioaimatbone_t *)((byte *)pStudioHdr + pinfo[iBone].procindex);
DoAimAtBone( pProcAimAt, quat, pbones, iBone, bonetransform, NULL );
return true;
case STUDIO_PROC_AIMATATTACH:
pProcAimAt = (mstudioaimatbone_t *)((byte *)pStudioHdr + pinfo[iBone].procindex);
DoAimAtBone( pProcAimAt, quat, pbones, iBone, bonetransform, pStudioHdr );
return true;
default:
return false;
}
}
return false;
}