mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-13 16:51:14 +02:00
425 lines
13 KiB
C++
425 lines
13 KiB
C++
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "prey_local.h"
|
|
|
|
const idEventDef EV_CheckCycleRotate( "<checkCycleRotate>" );
|
|
const idEventDef EV_CheckThaw( "<checkThaw>" );
|
|
const idEventDef EV_SpawnFxAlongBone( "spawnFXAlongBone", "d" );
|
|
const idEventDef EV_Silent( "<silent>", NULL );
|
|
|
|
CLASS_DECLARATION( idAnimatedEntity, hhAnimatedEntity )
|
|
EVENT( EV_CheckCycleRotate, hhAnimatedEntity::Event_CheckAnimatorCycleRotate )
|
|
EVENT( EV_CheckThaw, hhAnimatedEntity::Event_CheckAnimatorThaw )
|
|
EVENT( EV_SpawnFxAlongBone, hhAnimatedEntity::Event_SpawnFXAlongBone )
|
|
EVENT( EV_Thread_SetSilenceCallback,hhAnimatedEntity::Event_SetSilenceCallback )
|
|
EVENT( EV_Silent, hhAnimatedEntity::Event_Silent )
|
|
END_CLASS
|
|
|
|
/*
|
|
==============
|
|
hhAnimatedEntity::Spawn
|
|
==============
|
|
*/
|
|
void hhAnimatedEntity::Spawn( void ) {
|
|
hasFlapInfo = false;
|
|
|
|
// nla - Added to allow for 1 frame anims to play/proper bone initialization
|
|
if ( GetAnimator() ) {
|
|
int anim = GetAnimator()->GetAnim("init");
|
|
if ( anim ) {
|
|
GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0);
|
|
GetAnimator()->ForceUpdate();
|
|
UpdateModel();
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhAnimatedEntity::Save(idSaveGame *savefile) const {
|
|
savefile->WriteInt( waitingThread );
|
|
savefile->WriteInt( silentTimeOffset );
|
|
savefile->WriteInt( nextSilentTime );
|
|
savefile->WriteFloat( lastAmplitude );
|
|
}
|
|
|
|
void hhAnimatedEntity::Restore( idRestoreGame *savefile ) {
|
|
savefile->ReadInt( waitingThread );
|
|
savefile->ReadInt( silentTimeOffset );
|
|
savefile->ReadInt( nextSilentTime );
|
|
savefile->ReadFloat( lastAmplitude );
|
|
hasFlapInfo = false;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhAnimatedEntity::hhAnimatedEntity
|
|
==============
|
|
*/
|
|
hhAnimatedEntity::hhAnimatedEntity() {
|
|
// WaitForSilence support
|
|
waitingThread = 0;
|
|
silentTimeOffset = 0;
|
|
nextSilentTime = 0;
|
|
lastAmplitude = 0.0f;
|
|
hasFlapInfo = false;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhAnimatedEntity::~hhAnimatedEntity
|
|
==============
|
|
*/
|
|
hhAnimatedEntity::~hhAnimatedEntity() {
|
|
Event_Silent();
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhAnimatedEntity::FillDebugVars
|
|
==============
|
|
*/
|
|
void hhAnimatedEntity::FillDebugVars(idDict *args, int page) {
|
|
switch(page) {
|
|
case 1:
|
|
args->SetInt("anims", GetAnimator()->NumAnims());
|
|
break;
|
|
}
|
|
idAnimatedEntity::FillDebugVars(args, page);
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhAnimatedEntity::Think
|
|
==============
|
|
*/
|
|
void hhAnimatedEntity::Think() {
|
|
idAnimatedEntity::Think();
|
|
|
|
//HUMANHEAD: aob - moved from idActor
|
|
UpdateWounds();
|
|
//HUMANHEAD END
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhAnimatedEntity::GetAnimator
|
|
=====================
|
|
*/
|
|
hhAnimator *hhAnimatedEntity::GetAnimator( void ) {
|
|
return &animator;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhAnimatedEntity::GetAnimator
|
|
=====================
|
|
*/
|
|
const hhAnimator *hhAnimatedEntity::GetAnimator( void ) const {
|
|
return &animator;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhAnimatedEntity::GetJointWorldTransform
|
|
=====================
|
|
*/
|
|
bool hhAnimatedEntity::GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) {
|
|
return idAnimatedEntity::GetJointWorldTransform( jointHandle, currentTime, offset, axis );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhAnimatedEntity::GetJointWorldTransform
|
|
=====================
|
|
*/
|
|
bool hhAnimatedEntity::GetJointWorldTransform( const char* jointName, idVec3 &offset, idMat3 &axis ) {
|
|
return GetJointWorldTransform( GetAnimator()->GetJointHandle(jointName), gameLocal.GetTime(), offset, axis );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhAnimatedEntity::GetJointWorldTransform
|
|
=====================
|
|
*/
|
|
bool hhAnimatedEntity::GetJointWorldTransform( jointHandle_t jointHandle, idVec3 &offset, idMat3 &axis ) {
|
|
return GetJointWorldTransform( jointHandle, gameLocal.GetTime(), offset, axis );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhAnimatedEntity::UpdateWounds
|
|
==============
|
|
*/
|
|
void hhAnimatedEntity::UpdateWounds( void ) {
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idEntity::SpawnFXAlongBonePrefixLocal
|
|
==================
|
|
*/
|
|
void hhAnimatedEntity::SpawnFxAlongBonePrefixLocal( const idDict* dict, const char* fxKeyPrefix, const char* bonePrefix, const hhFxInfo* const fxInfo, const idEventDef* eventDef ) {
|
|
idVec3 bonePos;
|
|
idMat3 boneAxis;
|
|
|
|
for( const idKeyValue* kv = dict->MatchPrefix(bonePrefix); kv; kv = dict->MatchPrefix(bonePrefix, kv) ) {
|
|
if( !kv->GetValue().Length() ) {
|
|
continue;
|
|
}
|
|
|
|
GetJointWorldTransform( kv->GetValue().c_str(), bonePos, boneAxis );
|
|
SpawnFXPrefixLocal( dict, fxKeyPrefix, bonePos, boneAxis, fxInfo, eventDef );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
hhAnimatedEntity::SpawnFXAlongBone
|
|
=================
|
|
*/
|
|
void hhAnimatedEntity::BroadcastFxInfoAlongBonePrefix( const idDict* args, const char* fxKey, const char* bonePrefix, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) {
|
|
if( !fxKey || !fxKey[0] || !bonePrefix || !bonePrefix[0] ) {
|
|
return;
|
|
}
|
|
|
|
idVec3 bonePos;
|
|
idMat3 boneAxis;
|
|
hhFxInfo localFxInfo( fxInfo );
|
|
|
|
localFxInfo.SetEntity( this );
|
|
localFxInfo.RemoveWhenDone( true );
|
|
|
|
const idKeyValue* keyValue = args->MatchPrefix( bonePrefix );
|
|
while( keyValue && keyValue->GetValue().Length() ) {
|
|
|
|
if( GetJointWorldTransform( keyValue->GetValue().c_str(), bonePos, boneAxis ) ) {
|
|
//AOB: HACK - crashed in idBitMsg::DirToBits because we didn't appear to be normalized
|
|
localFxInfo.SetNormal( boneAxis[0].ToNormal() );
|
|
localFxInfo.SetBindBone( keyValue->GetValue().c_str() );
|
|
|
|
BroadcastFxInfoPrefixed( fxKey, bonePos, boneAxis, &localFxInfo, eventDef, broadcast );
|
|
}
|
|
|
|
keyValue = args->MatchPrefix( bonePrefix, keyValue );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
hhAnimatedEntity::SpawnFXAlongBone
|
|
mdl
|
|
=================
|
|
*/
|
|
void hhAnimatedEntity::BroadcastFxInfoAlongBonePrefixUnique( const idDict* args, const char* fxKey, const char* bonePrefix, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) {
|
|
if( !fxKey || !fxKey[0] || !bonePrefix || !bonePrefix[0] ) {
|
|
return;
|
|
}
|
|
|
|
idVec3 bonePos;
|
|
idMat3 boneAxis;
|
|
hhFxInfo localFxInfo( fxInfo );
|
|
|
|
localFxInfo.SetEntity( this );
|
|
localFxInfo.RemoveWhenDone( true );
|
|
|
|
const idKeyValue* keyValue = args->MatchPrefix( bonePrefix );
|
|
idStr postfix;
|
|
int prefixLen = strlen( bonePrefix );
|
|
while( keyValue && keyValue->GetValue().Length() ) {
|
|
postfix = keyValue->GetKey().Right( keyValue->GetKey().Length() - prefixLen );
|
|
|
|
if( GetJointWorldTransform( keyValue->GetValue().c_str(), bonePos, boneAxis ) ) {
|
|
//AOB: HACK - crashed in idBitMsg::DirToBits because we didn't appear to be normalized
|
|
localFxInfo.SetNormal( boneAxis[0].ToNormal() );
|
|
localFxInfo.SetBindBone( keyValue->GetValue().c_str() );
|
|
|
|
// Changed this because it was causing every fx listed to be bound to every bone listed. -mdl
|
|
BroadcastFxInfo( spawnArgs.GetString( fxKey + postfix ), bonePos, boneAxis, &localFxInfo, eventDef, broadcast );
|
|
//BroadcastFxInfoPrefixed( fxKey, bonePos, boneAxis, &localFxInfo, eventDef );
|
|
}
|
|
|
|
keyValue = args->MatchPrefix( bonePrefix, keyValue );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhAnimatedEntity::SpawnFxAlongBone
|
|
================
|
|
*/
|
|
void hhAnimatedEntity::BroadcastFxInfoAlongBone( const char* fxName, const char* boneName, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) {
|
|
//mdc: pass through to our newer version of the function (this is mainly done for backwards compatibility)
|
|
BroadcastFxInfoAlongBone( false, fxName, boneName, fxInfo, eventDef, broadcast );
|
|
}
|
|
|
|
void hhAnimatedEntity::BroadcastFxInfoAlongBone( bool bNoRemoveWhenUnbound, const char* fxName, const char* boneName, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) {
|
|
if( !fxName || !fxName[0] || !boneName || !boneName[0] ) {
|
|
return;
|
|
}
|
|
|
|
idVec3 bonePos;
|
|
idMat3 boneAxis;
|
|
hhFxInfo localFxInfo( fxInfo );
|
|
|
|
GetJointWorldTransform( boneName, bonePos, boneAxis );
|
|
localFxInfo.SetNormal( boneAxis[0] );
|
|
localFxInfo.SetEntity( this );
|
|
localFxInfo.NoRemoveWhenUnbound( bNoRemoveWhenUnbound ); //mdc: added
|
|
localFxInfo.SetBindBone( boneName );
|
|
|
|
BroadcastFxInfo( fxName, bonePos, boneAxis, &localFxInfo, eventDef, broadcast );
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhAnimatedEntity::Event_SpawnFXAlongBone
|
|
================
|
|
*/
|
|
void hhAnimatedEntity::Event_SpawnFXAlongBone( idList<idStr>* fxParms ) {
|
|
if ( !fxParms ) {
|
|
return;
|
|
}
|
|
|
|
bool bNoRemoveWhenUnbound = false; //default to false (we normally want to delete fx when the bound-entity is deleted)
|
|
HH_ASSERT( fxParms->Num() >= 2 && fxParms->Num() <= 3 ); //allow 2-3 parameters
|
|
if( fxParms->Num() == 3 ) {
|
|
//make sure the 3rd paramater is our special noRemoveWhenUnbound flag
|
|
if( !idStr::Icmp((*fxParms)[2].c_str(), "noremovewhenunbound") ) {
|
|
bNoRemoveWhenUnbound = true;
|
|
}
|
|
else {
|
|
gameLocal.Warning( "unrecognized 3rd paramater on frame command for SpawnFXAlongBone on entity '%s'", this->GetName() );
|
|
}
|
|
}
|
|
BroadcastFxInfoAlongBone(bNoRemoveWhenUnbound, spawnArgs.GetString((*fxParms)[0].c_str()), (*fxParms)[1].c_str() );
|
|
}
|
|
|
|
|
|
// HUMANHEAD pdm: Jaw flapping support
|
|
// This provides generalized support for any animated entity to translate voice channel sound to bone movements
|
|
void hhAnimatedEntity::JawFlap(hhAnimator *theAnimator) {
|
|
PROFILE_SCOPE("Jaw Flap", PROFMASK_NORMAL);
|
|
float amplitude = 0.0f;
|
|
|
|
if (fl.noJawFlap || !g_jawflap.GetBool()) {
|
|
return;
|
|
}
|
|
|
|
// Get amplitude from head or body
|
|
idEntity *headEntity = NULL;
|
|
if (IsType(idActor::Type)) {
|
|
headEntity = static_cast<idActor*>(this)->GetHead();
|
|
if (headEntity && headEntity->GetSoundEmitter()) {
|
|
amplitude = headEntity->GetSoundEmitter()->CurrentVoiceAmplitude( SND_CHANNEL_VOICE );
|
|
}
|
|
}
|
|
|
|
if( amplitude == 0.0f && GetSoundEmitter() ) {
|
|
amplitude = GetSoundEmitter()->CurrentVoiceAmplitude( SND_CHANNEL_VOICE );
|
|
}
|
|
|
|
if (amplitude != 0.0f || lastAmplitude != 0.0f) {
|
|
//HUMANHEAD rww - only get the joint handles and info for jaw flapping once
|
|
//we have to do this here, since the head is not valid at hhAnimatedEntity::Spawn
|
|
if (!hasFlapInfo) {
|
|
jawFlapList.Clear();
|
|
const char *prefix = "jawbone_";
|
|
const idKeyValue *kv = spawnArgs.MatchPrefix(prefix);
|
|
while( kv && kv->GetValue().Length() ) {
|
|
jointHandle_t bone = theAnimator->GetJointHandle( kv->GetValue() );
|
|
if (bone != INVALID_JOINT) {
|
|
jawFlapInfo_t flapInfo;
|
|
|
|
idStr xformName = kv->GetKey();
|
|
xformName.Strip(prefix);
|
|
|
|
flapInfo.bone = bone;
|
|
flapInfo.rMagnitude = spawnArgs.GetVector( va("jawflapR_%s", xformName.c_str()) );
|
|
flapInfo.tMagnitude = spawnArgs.GetVector( va("jawflapT_%s", xformName.c_str()) );
|
|
flapInfo.rMinThreshold = spawnArgs.GetFloat( va("jawflapRMin_%s", xformName.c_str()) );
|
|
flapInfo.tMinThreshold = spawnArgs.GetFloat( va("jawflapTMin_%s", xformName.c_str()) );
|
|
jawFlapList.Append(flapInfo);
|
|
}
|
|
kv = spawnArgs.MatchPrefix(prefix, kv);
|
|
}
|
|
hasFlapInfo = true;
|
|
}
|
|
|
|
lastAmplitude = amplitude;
|
|
//HUMANHEAD rww - changed to use a list and not do constant runtime joint lookups
|
|
for (int i = 0; i < jawFlapList.Num(); i++) {
|
|
jawFlapInfo_t &flapInfo = jawFlapList[i];
|
|
|
|
// Handle Rotation
|
|
idAngles angles = ang_zero;
|
|
if (amplitude > flapInfo.rMinThreshold) {
|
|
float factor = amplitude - flapInfo.rMinThreshold;
|
|
angles = idAngles(factor*flapInfo.rMagnitude[0], factor*flapInfo.rMagnitude[1], factor*flapInfo.rMagnitude[2]);
|
|
}
|
|
theAnimator->SetJointAxis( flapInfo.bone, JOINTMOD_WORLD, angles.ToMat3() );
|
|
|
|
// Handle translation
|
|
idVec3 translate = vec3_origin;
|
|
if (amplitude > flapInfo.tMinThreshold) {
|
|
float factor = amplitude - flapInfo.tMinThreshold;
|
|
translate = idVec3(factor*flapInfo.tMagnitude[0], factor*flapInfo.tMagnitude[1], factor*flapInfo.tMagnitude[2]);
|
|
}
|
|
theAnimator->SetJointPos( flapInfo.bone, JOINTMOD_WORLD, translate );
|
|
}
|
|
}
|
|
}
|
|
|
|
// WaitForSilence support: Tracks the next time the voice channel will be silent, and if any threads are doing a waitForSilence() block, calls them back.
|
|
// If a waitForSilence() call is issued while no sound is playing on the voice channel, it will wait until there is something and then wait for silence.
|
|
bool hhAnimatedEntity::StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) {
|
|
int localLength = 0;
|
|
bool retVal = idAnimatedEntity::StartSoundShader(shader, channel, soundShaderFlags, broadcast, &localLength);
|
|
|
|
// need to track 'nextSilentTime' locally also, so if a tiny sound is played after a longer sound, it doesn't think silence will follow short sound
|
|
if (channel == SND_CHANNEL_VOICE && (localLength > 0) && (gameLocal.time + localLength > nextSilentTime)) {
|
|
nextSilentTime = gameLocal.time + localLength;
|
|
|
|
// If there's already a thread waiting for silence and this sound pushes silence out further, repost an event for it
|
|
if (waitingThread) {
|
|
CancelEvents(&EV_Silent);
|
|
int time = localLength+silentTimeOffset;
|
|
time = idMath::ClampInt(1, time, time); // Keep positive and wait at least 1ms
|
|
PostEventMS(&EV_Silent, time);
|
|
}
|
|
}
|
|
if (length) {
|
|
*length = localLength;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
void hhAnimatedEntity::Event_Silent() {
|
|
if (waitingThread) {
|
|
idThread::ObjectMoveDone( waitingThread, this );
|
|
waitingThread = 0;
|
|
}
|
|
}
|
|
|
|
void hhAnimatedEntity::Event_SetSilenceCallback(float plusOrMinusSeconds) {
|
|
|
|
if (!waitingThread) {
|
|
waitingThread = idThread::CurrentThreadNum();
|
|
silentTimeOffset = SEC2MS(plusOrMinusSeconds);
|
|
|
|
if (nextSilentTime > gameLocal.time) {
|
|
// If sound already playing
|
|
int time = (nextSilentTime - gameLocal.time) + silentTimeOffset;
|
|
time = idMath::ClampInt(1, time, time); // Keep positive and wait at least 1ms
|
|
CancelEvents(&EV_Silent);
|
|
PostEventMS(&EV_Silent, time);
|
|
}
|
|
else {
|
|
// No sound playing, wait for sound to play first
|
|
}
|
|
idThread::ReturnInt( true );
|
|
}
|
|
else {
|
|
idThread::ReturnInt( false );
|
|
}
|
|
}
|