2020-08-31 18:55:47 +02:00
|
|
|
//===== 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;
|
2020-08-31 00:15:53 +02:00
|
|
|
}
|