//**************************************************************************** //* MODULE: Gel/Components //* FILENAME: AnimationComponent.cpp //* OWNER: Gary Jesdanun //* CREATION DATE: 10/24/2002 //**************************************************************************** #include #include #include #include // TODO: All the network code should // be at the skater- or the player- level #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Obj { extern bool DebugSkaterScripts; // if the animation time increment is too small, // then ignore it... const float vMIN_SPEED_THRESHOLD = 0.05f; #define nxANIMCOMPONENTFLAGS_FLIPPED (1<<29) // maximum number of degenerate animations const int vNUM_DEGENERATE_ANIMS = 3; // maximum number of procedural bones const int vMAXPROCEDURALBONES = 12; static bool s_updating_channels = false; /******************************************************************/ /* */ /* */ /******************************************************************/ // This static function is what is registered with the component // factory object, (currently the CCompositeObjectManager) CBaseComponent* CAnimationComponent::s_create() { return static_cast< CBaseComponent* >( new CAnimationComponent ); } /******************************************************************/ /* */ /* */ /******************************************************************/ CAnimationComponent::CAnimationComponent() : CBaseComponent() { SetType( CRC_ANIMATION ); m_animScriptName = 0; m_animEventTableName = 0; m_shouldBlend = false; Reset(); m_numProceduralBones = 0; mp_proceduralBones = NULL; } /******************************************************************/ /* */ /* */ /******************************************************************/ CAnimationComponent::~CAnimationComponent() { destroy_blend_channels(); if ( mp_proceduralBones ) { delete[] mp_proceduralBones; } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::Reset() { // stops all the animations... destroy_blend_channels(); // resets "global" blend parameters mGotBlendPeriodOut = false; mBlendPeriodOut = 0.0f; // reset script animation waits if (m_animation_script_block_active) { m_animation_script_block_active = false; if (GetObject()->GetScript()) { GetObject()->GetScript()->UnBlock(); } } m_animation_script_unblock_point = 0; // reset animation frame count m_last_animation_time = 0.0f; m_dont_interrupt=false; } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::SetAnims( uint32 anim_checksum ) { m_animScriptName = anim_checksum; PlayPrimarySequence( 0, false, 0.0f, 1000.0f, Gfx::LOOPING_HOLD, 0.3f, 1.0f ); } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::InitFromStructure( Script::CStruct* pParams ) { Dbg_Assert( pParams ); pParams->GetChecksum( CRCD(0x5b8c6dc2,"animEventTableName"), &m_animEventTableName, false ); uint32 animScriptName; if ( pParams->GetChecksum( CRCD(0x6c2bfb7f,"animName"), &animScriptName, false ) ) { SetAnims( animScriptName ); Reset(); // safety-check to make sure he doesn't start out in the blair witch position // (because of our animation LOD-ing system) // this must be done before the models get initialized? uint32 defaultAnimName; if ( pParams->GetChecksum( CRCD(0xeae64b47,"defaultAnimName"), &defaultAnimName, false ) ) { PlaySequence(defaultAnimName); SetLoopingType( Gfx::LOOPING_CYCLE, true ); } else { // generally, the blair witch position PlaySequence( CRCD(0x1ca1ff20,"default") ); } } // if it's the local skater, then give it some procedural animation as well // (the decision to do this should really be coming from a higher-level...) if ( GetObject()->GetID() == 0 ) { Dbg_MsgAssert( mp_proceduralBones == NULL, ( "Already has procedural bones!" ) ); mp_proceduralBones = new Gfx::CProceduralBone[vMAXPROCEDURALBONES]; Script::CStruct* pTempParams = new Script::CStruct; pTempParams->AppendStructure( pParams ); pTempParams->AddChecksum( CRCD(0x7321a8d6,"type"), CRCD(0xfdf0436c,"ProceduralAnim") ); pTempParams->AddChecksum( CRCD(0x3ed7262b,"bone_list"), CRCD(0x5d5c0e72,"procedural_skater_bones") ); initialize_procedural_bones( pTempParams ); delete pTempParams; } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::ToggleFlipState( void ) { // Flip the animation to the correct orientation GameNet::Manager* gamenet_man = GameNet::Manager::Instance(); Net::Client* client = gamenet_man->GetClient( 0 ); this->FlipAnimation( GetObject()->GetID(), !IsFlipped(), client->m_Timestamp, true ); } /******************************************************************/ /* */ /* */ /******************************************************************/ CBaseComponent::EMemberFunctionResult CAnimationComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript ) { switch ( Checksum ) { // @script | Obj_EnableAnimBlending | // @parm name | enabled | whether the anim blending should be enabled // peds should disable blending, as a speed optimization case 0x98a32669: // Obj_EnableAnimBlending { int shouldBlend; pParams->GetInteger( CRCD(0xaf06447b,"enabled"), &shouldBlend, Script::ASSERT ); EnableBlending( shouldBlend ); } break; // @script | Obj_SetAnimCycleMode | // @flag off | turn cycle off (otherwise it will turn it on) case 0x58c52f64: // Obj_SetAnimCycleMode { if ( pParams->ContainsFlag( CRCD(0xd443a2bc,"off") ) ) { SetLoopingType( Gfx::LOOPING_HOLD, true ); } else { // maybe we should clear the cycle time or num loops? // or should we leave it at whatever was set before? hmmm... SetLoopingType( Gfx::LOOPING_CYCLE, true ); } } break; // @script | Obj_PlayAnim | // @parm name | Anim | animation to play // @flag wobble | enable wobble // @parmopt float | BlendPeriod | 0.3f | blend period (default may change) // @parmopt int | BlendPeriodPercent | | blend period as a percentage of the anim length (20 means "20 percent") // @parmopt float | speed | 1.0 | // @flag NoRestart | // @flag Cycle | // @flag PingPong | // @flag Wobble | // @flag DontInterrupt | Makes further PlayAnims have no effect until the current // animation has finished. // @parmopt name | From | | start, end, or current // @parmopt int | From | | from can be specified as an int (60ths) // @parmopt name | To | | start, end, or current // @parmopt int | To | | to can be specified as an int (60ths) // @flag Backwards | play animation backwards case 0x2cb9ad09: // Obj_PlayAnim case 0x0b1e7291: // PlayAnim { PlayAnim( pParams, pScript ); Gfx::CBlendChannel* pPrimaryChannel = get_primary_channel(); if ( pPrimaryChannel && pPrimaryChannel->GetLoopingType() == Gfx::LOOPING_WOBBLE ) { // the skater balance trick needs to do some extra stuff to // handle wobbles, but needs to do it after the anim has been // launched. the following gives any component a chance to // handle the wobble first... if it is not handled by the // time it gets to the animation component, then it's ignored // (this works for the case of the skater balance trick, because // it comes early in the component list) GetObject()->CallMemberFunction( CRCD(0xea6d0efd,"SetWobbleDetails"), pParams, pScript ); } } break; case 0xea6d0efd: // SetWobbleDetails // this is a special case to make sure that the skater balance // trick component gets a chance to handle wobbles, if such // a component exists. return MF_TRUE; // @script | Obj_AnimComplete | returns true when the object // is done playing an anim. This is only valid with "hold on // last frame" animations. case 0x2889c6c9: // Obj_AnimComplete case 0x76cc99d5: // AnimFinished return ( IsAnimComplete() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE ); break; // @script | Obj_AnimEquals | True if specified animation is // the one currently playing on the object, False otherwise // @uparmopt name | single animation name to check // @uparmopt [] | array of animation names to check case 0xb5fb7eb6: // Obj_AnimEquals case 0x1a0f9646: // AnimEquals return ( AnimEquals( pParams, pScript ) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE ); break; // @script | Obj_GetAnimSpeed | // the one currently playing on the object case 0x5195bcbb: // Obj_GetAnimSpeed { pScript->GetParams()->AddFloat(CRCD(0xf0d90109,"speed"), GetAnimSpeed( pParams, pScript )); } break; // @script | BlendPeriodOut | next call to playanim will use this blend period // no matter what is specified in the actual call to playanim // @uparm 1.0 | blend period out value case 0x68c86aec: // BlendPeriodOut { if (!pParams->GetFloat(NONAME,&mBlendPeriodOut)) { Dbg_MsgAssert(0,("\n%s\nBlendPeriodOut requires a floating point value",pScript->GetScriptInfo())); } // Set the flag so that PlayAnim will use mBlendPeriodOut instead next time. mGotBlendPeriodOut=true; } break; // @script | LoopingAnim | true if the current animation will loop forever case 0x80fdbdf2: // LoopingAnim { return IsLoopingAnim() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE; } break; case 0x42ffc3ce: // GetAnimLength { #ifdef __USER_DAN__ printf("GetAnimLength\n"); Script::PrintContents(pParams); #endif uint32 anim_checksum=0; pParams->GetChecksum(CRCD(0x98549ba4, "anim"), &anim_checksum); if (AnimExists(anim_checksum)) { pScript->GetParams()->AddFloat(CRCD(0xfe82614d, "Length"), fabs(AnimDuration(anim_checksum))); } break; } // @script | FrameIs | checks if the current frame is equal // to the specified frame // @uparmopt 1 | frame number case 0x922e7d14: // FrameIs { #ifdef __USER_DAN__ printf("FrameIs\n"); Script::PrintContents(pParams); #endif float Start,Current,End; GetPrimaryAnimTimes(&Start,&Current,&End); float t=0; pParams->GetFloat(NO_NAME,&t); t/=60.0f; float new_frame=Current-Start; float old_frame=m_last_animation_time-Start; if (t==new_frame) { return CBaseComponent::MF_TRUE; } if (old_frameGetFloat(NO_NAME,&t); if (pParams->ContainsFlag(0x4a07c332/*"frame"*/) || pParams->ContainsFlag(0x19176c5/*"frames"*/)) { t/=60.0f; } else if (pParams->ContainsFlag(0x9e497fc6/*"percent"*/)) { t=Start+(End-Start)*t/100.0f; // Dodgy? } else if (pParams->ContainsFlag(0x49e0ee96/*"second"*/) || pParams->ContainsFlag(0xd029f619/*"seconds"*/)) { // t is in seconds, so nothing to do. } else { // If they did not specify any of the above, then t is in milliseconds, so convert to seconds. t/=1000.0f; } if (pParams->ContainsFlag(0x91a4c826/*"relative"*/)) { if (Start<=End) { m_animation_script_unblock_point=Current+t; } else { m_animation_script_unblock_point=Current-t; } } else if (pParams->ContainsFlag(0xe18cd075/*"fromend"*/)) { if (Start<=End) { m_animation_script_unblock_point=End-t; } else { m_animation_script_unblock_point=Start+t; } } else { m_animation_script_unblock_point=t; } // Dbg_MsgAssert( m_animation_script_unblock_point>=Start && m_animation_script_unblock_point<=End, ( "WaitAnim time %f out of range of anim (%f %f) in %s", m_animation_script_unblock_point, Start, End, pScript->GetScriptInfo() ) ); if ( !GetObject()->GetScript() ) { GetObject()->SetScript(new Script::CScript); } if ((Start<=End && Current>=m_animation_script_unblock_point) || (Start>=End && Current<=m_animation_script_unblock_point)) { GetObject()->GetScript()->UnBlock(); m_animation_script_block_active=false; } else { GetObject()->GetScript()->Block(); m_animation_script_block_active=true; } return CBaseComponent::MF_TRUE; } case 0x5f495ae0: // InvalidateCache { int num_channels = m_blendChannelList.CountItems(); Gfx::CBlendChannel* pChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetNext(); for ( int i = 0; i < num_channels; i++ ) { pChannel->InvalidateCache(); pChannel = (Gfx::CBlendChannel*)pChannel->GetNext(); } } break; // @script | Obj_WaitAnimFinished | wait for animation to complete case 0xb628a959: // Obj_WaitAnimFinished pScript->SetWait(Script::WAIT_TYPE_OBJECT_ANIM_FINISHED,this); break; // @script | AnimExists | returns true if the given anim exists // @param name | animation to look for case CRCC(0x9069f357, "AnimExists"): { uint32 anim_name; pParams->GetChecksum(NO_NAME, &anim_name); return AnimExists(anim_name) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE; } case 0x83654874: // AddAnimController case 0x02565bd1: // SetBoneTransMin case 0x3e5b6488: // SetBoneTransMax case 0xc85b9ac0: // SetBoneTransSpeed case 0x5ee9d5bd: // SetBoneTransCurrent case 0x5661fb72: // SetBoneTransActive case 0xa47767f2: // SetBoneRotMin case 0x987a58ab: // SetBoneRotMax case 0x599d3707: // SetBoneRotSpeed case 0x7235daa7: // SetBoneRotCurrent case 0x53f06acc: // SetBoneRotActive case 0xc6889e91: // SetBoneScaleMin case 0xfa85a1c8: // SetBoneScaleMax case 0xd32c2724: // SetBoneScaleSpeed case 0xd12b3713: // SetBoneScaleCurrent case 0xf11daaae: // SetBoneScaleActive case 0xd63a1b81: // GetPartialAnimParams case 0xbd4edd44: // SetPartialAnimSpeed case 0x6aaeb76f: // IncrementPartialAnimTime case 0xf5e2b871: // ReversePartialAnimDirection { Gfx::CBlendChannel* pPrimaryChannel = get_primary_channel(); if ( pPrimaryChannel ) { return pPrimaryChannel->CallMemberFunction( Checksum, pParams, pScript ) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE; } } break; case 0x986d274e: // RemoveAnimController { Gfx::CBlendChannel* pChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetNext(); while ( pChannel ) { pChannel->CallMemberFunction( Checksum, pParams, pScript ); pChannel = (Gfx::CBlendChannel*)pChannel->GetNext(); } return CBaseComponent::MF_TRUE; } break; // @script | Obj_Flip | case 0x27a98022: // Obj_Flip ToggleFlipState( ); break; // @script | Obj_AnimationFlipped | case 0x6eceb234: // Obj_AnimationFlipped return ( m_flags & nxANIMCOMPONENTFLAGS_FLIPPED ) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE; break; default: return CBaseComponent::MF_NOT_EXECUTED; } return CBaseComponent::MF_TRUE; } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::AddAnimController( Script::CStruct* pParams ) { Gfx::CBlendChannel* pPrimaryChannel = get_primary_channel(); if ( pPrimaryChannel ) { pPrimaryChannel->AddController( pParams ); } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::ProcessWait( Script::CScript * pScript ) { switch (pScript->GetWaitType()) { case Script::WAIT_TYPE_OBJECT_ANIM_FINISHED: if (IsAnimComplete()) { pScript->ClearWait(); } break; default: Dbg_MsgAssert(0,("\n%s\nWait type of %d not supported by CAnimationComponent",pScript->GetScriptInfo(),pScript->GetWaitType())); break; } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::GetDebugInfo( Script::CStruct* p_info ) { #ifdef __DEBUG_CODE__ Dbg_MsgAssert( p_info, ( "NULL p_info sent to CAnimationComponent::GetDebugInfo" ) ); // we call the base component's GetDebugInfo, so we can add info from the common base component CBaseComponent::GetDebugInfo(p_info); p_info->AddInteger( CRCD(0x87ed8a3f,"m_shouldblend"), m_shouldBlend ); p_info->AddInteger( CRCD(0xa2f4a5ea,"mGotBlendPeriodOut"), mGotBlendPeriodOut ); p_info->AddFloat( CRCD(0xd0902723,"mBlendPeriodOut"), mBlendPeriodOut ); p_info->AddChecksum( CRCD(0xc2fe9f39,"m_animScriptName"), m_animScriptName ); p_info->AddInteger(CRCD(0x9fdd4257,"m_dont_interrupt"),m_dont_interrupt); Script::CStruct* p_tempParams = new Script::CStruct; p_tempParams->Clear(); Gfx::CBlendChannel* pPrimaryChannel = get_primary_channel(); if ( pPrimaryChannel ) { pPrimaryChannel->GetDebugInfo( p_tempParams ); p_info->AddStructure( CRCD(0x969f358c,"primary_channel"), p_tempParams ); } delete p_tempParams; #endif } /******************************************************************/ /* */ /* */ /******************************************************************/ Gfx::CBlendChannel* CAnimationComponent::get_primary_channel() { // just grab the first item from the list... Gfx::CBlendChannel* pBlendChannel = NULL; pBlendChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetNext(); return pBlendChannel; } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::pack_degenerate_channels() { // this removes all the channels that have expired... Gfx::CBlendChannel* pChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetNext(); while ( pChannel ) { Gfx::CBlendChannel* pNext = (Gfx::CBlendChannel*)pChannel->GetNext(); if ( !pChannel->IsActive() ) { Dbg_MsgAssert(pChannel != get_primary_channel(), ("Removing primary channel")); Dbg_MsgAssert( !s_updating_channels, ( "Someone is trying to remove an animation channel while in channel update loop" ) ); pChannel->Remove(); delete pChannel; } pChannel = pNext; } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::create_new_blend_channel( float blend_period ) { // If there are too many channels, get rid of the tail channels Gfx::CBlendChannel* pChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetNext();//Item( 0 ); int channelCount = 0; while ( pChannel ) { channelCount++; Gfx::CBlendChannel* pNext = (Gfx::CBlendChannel*)pChannel->GetNext(); if ( channelCount >= vNUM_DEGENERATE_ANIMS ) { // remove the tail items pChannel->Remove(); Dbg_MsgAssert( !s_updating_channels, ( "Someone is trying to remove an animation channel while in channel update loop" ) ); delete pChannel; } pChannel = pNext; } // degenerate the existing blend channels // make the first channel degenerate Gfx::CBlendChannel* pBlendChannel = m_blendChannelList.CountItems() ? get_primary_channel() : NULL;// (Gfx::CBlendChannel*)m_blendChannelList.GetItem( 0 ); if ( pBlendChannel ) { if ( pBlendChannel->Degenerate( blend_period ) ) { // degeneration worked, so we want to do this cumulative // blend value thing (not really sure what it is though) float cumulative_blend_value = 0.0f; Gfx::CBlendChannel* pOtherChannels = (Gfx::CBlendChannel*)pBlendChannel->GetNext(); while ( pOtherChannels ) { // if( !pOtherChannels->IsActive() ) if( pOtherChannels->IsDegenerating() ) { cumulative_blend_value += pOtherChannels->GetDegenerationTime() * pOtherChannels->GetDegenerationTimeToBlendMultiplier(); } pOtherChannels = (Gfx::CBlendChannel*)pOtherChannels->GetNext(); } float blend_value1 = 1.0f - ( cumulative_blend_value ); pBlendChannel->SetDegenerationTimeToBlendMultiplier( blend_value1 / pBlendChannel->GetDegenerationTime() ); } } // now add a new channel to the front of the list Gfx::CBlendChannel* pPrimaryChannel = new Gfx::CBlendChannel( GetObject() ); m_blendChannelList.AddToHead( pPrimaryChannel ); Dbg_MsgAssert( !s_updating_channels, ( "Someone is trying to add an animation channel while in channel update loop" ) ); } /******************************************************************/ /* */ /* */ /******************************************************************/ bool CAnimationComponent::AnimEquals( Script::CStruct *pParams, Script::CScript *pScript ) { uint32 current_animation=GetCurrentSequence(); Script::CComponent *p_comp=NULL; while (true) { p_comp=pParams->GetNextComponent(p_comp); if (!p_comp) { break; } if (p_comp->mNameChecksum==0) { // It's an unnamed component Script::CArray *p_array=NULL; if (p_comp->mType==ESYMBOLTYPE_NAME) { // It's an unnamed name. Maybe it's the name of a global array ... Script::CSymbolTableEntry *p_entry=Script::Resolve(p_comp->mChecksum); if (p_entry && p_entry->mType==ESYMBOLTYPE_ARRAY) { // It is a global array p_array=p_entry->mpArray; } else { // Nope, it's just a name, so see if it is the name of the current animation. if (p_comp->mChecksum==current_animation) { return true; } } } else if (p_comp->mType==ESYMBOLTYPE_ARRAY) { p_array=p_comp->mpArray; } if (p_array) { Dbg_MsgAssert(p_array->GetType()==ESYMBOLTYPE_NAME,("\n%s\nAnimEquals: Array must be of names",pScript->GetScriptInfo())); for (uint32 i=0; iGetSize(); ++i) { if (p_array->GetChecksum(i)==current_animation) { return true; } } } } } return false; } /******************************************************************/ /* */ /* */ /******************************************************************/ float CAnimationComponent::AnimDuration( uint32 checksum ) { Gfx::CBonedAnimFrameData *p_anim = find_actual_anim( checksum ); Dbg_MsgAssert( p_anim, ( "Trying to get duration on an animation that doesn't exist %s %s %s", Script::FindChecksumName(m_animScriptName), Script::FindChecksumName(checksum), Script::FindChecksumName(GetObject()->GetID()) ) ); return ( p_anim->GetDuration() ); } /******************************************************************/ /* */ /* */ /******************************************************************/ bool CAnimationComponent::AnimExists( uint32 checksum ) { Gfx::CBonedAnimFrameData *p_anim = find_actual_anim( checksum ); return p_anim; } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::AddTime( float incVal ) { Gfx::CBlendChannel* pPrimaryChannel = get_primary_channel(); if ( pPrimaryChannel ) { pPrimaryChannel->AddTime( incVal ); } } /******************************************************************/ /* */ /* */ /******************************************************************/ Gfx::CBonedAnimFrameData* CAnimationComponent::find_actual_anim( uint32 checksum ) { // need to combine the animation name (Idle) // with the owner animscript (animload_thps5_human) // to get the asset name return Nx::GetCachedAnim( m_animScriptName + checksum, false ); } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::PrintStatus() { // for debugging the viewer object } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::Update() { if ( Gfx::CBlendChannel* primary_channel = get_primary_channel() ) { m_last_animation_time = primary_channel->GetCurrentAnimTime(); /* float s, c, e; primary_channel->GetAnimTimes(&s, &c, &e); printf("Animation Factor Complete = %f\n", c / (e - s)); */ } if (m_animation_script_block_active) { if ( !GetObject()->GetScript() ) { GetObject()->SetScript( new Script::CScript ); } // The script should be blocked at this point, if it isn't that must // be because the script just got reloaded. So clear the m_animation_script_block_active // flag so that it doesn't get stuck on forever. if ( !GetObject()->GetScript()->getBlocked() ) { m_animation_script_block_active = false; } else { float start, current, end; GetPrimaryAnimTimes( &start, ¤t, &end); if ( ( start <= end && current >= m_animation_script_unblock_point ) || ( start >= end && current <= m_animation_script_unblock_point ) ) { GetObject()->GetScript()->UnBlock(); m_animation_script_block_active = false; } } } // Alwyas update the channels s_updating_channels = true; int num_channels = m_blendChannelList.CountItems(); Gfx::CBlendChannel* pChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetNext(); for ( int i = 0; i < num_channels; i++ ) { pChannel->Update(); pChannel = (Gfx::CBlendChannel*)pChannel->GetNext(); } s_updating_channels = false; // remove channels which have decided they are complete pack_degenerate_channels(); // (Mick) If there is a suspend component // then ask it if we should animate // This call determines whether the object is sufficiently far away that no animation is disabled, // or possibly at an intermediate distance, interleaved. The distance from parent object to camera is cached // for subsequent animation LOD calculations. Dbg_Assert(mp_suspend_component); bool animate = mp_suspend_component->should_animate( &m_parent_object_dist_to_camera ); if ( animate ) { update_procedural_bones(); // This call determines whether the object is actually on screen; animation is not requried // for offscreen objects. if ( ShouldAnimate() ) { update_skeleton(); } } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::UpdateSkeleton() { // don't use the animation cache, because // the viewer object doesn't call Update(), // which normally is responsible for // invalidating the cache at the // appropriate times Gfx::CBlendChannel* pPrimaryChannel = get_primary_channel(); if ( pPrimaryChannel ) { pPrimaryChannel->InvalidateCache(); } // allows the viewer object to update the skeleton, // outside of the normal Update() function... // (the viewer object sometimes suspends the // component and updates the time manually, // in which case we'd still need to apply // the animation to the skeleton...) update_skeleton(); } /******************************************************************/ /* */ /* */ /******************************************************************/ bool CAnimationComponent::IsAnimComplete( void ) { if ( !get_primary_channel() ) { return false; } return get_primary_channel()->IsAnimComplete(); } /******************************************************************/ /* */ /* */ /******************************************************************/ bool CAnimationComponent::IsLoopingAnim( void ) { if ( !get_primary_channel() ) { return false; } return get_primary_channel()->IsLoopingAnim(); } /******************************************************************/ /* */ /* */ /******************************************************************/ uint32 CAnimationComponent::GetCurrentSequence( void ) { if ( !get_primary_channel() ) { return 0; } return get_primary_channel()->GetCurrentAnim(); } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::GetPrimaryAnimTimes(float *pStart, float *pCurrent, float *pEnd) { *pStart = 0.0f; *pCurrent = 0.0f; *pEnd = 0.0f; if ( get_primary_channel() ) { get_primary_channel()->GetAnimTimes( pStart, pCurrent, pEnd ); } } /******************************************************************/ /* */ /* */ /******************************************************************/ float CAnimationComponent::GetCurrentAnimTime( void ) { if ( !get_primary_channel() ) { return 0.0f; } return get_primary_channel()->GetCurrentAnimTime(); } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::PlaySequence( uint32 checksum, float BlendPeriod ) { #ifdef __NOPT_ASSERT__ if ( !AnimExists( checksum ) ) { Dbg_Message( "Couldn't find anim %s\n", Script::FindChecksumName(checksum) ); } #endif PlayPrimarySequence( checksum, false, 0.0f, AnimDuration(checksum), Gfx::LOOPING_HOLD, BlendPeriod, 1.0f ); } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::PlayPrimarySequence( uint32 animName, bool propagate, float start_time, float end_time, Gfx::EAnimLoopingType loop_type, float blend_period, float speed ) { if ( animName == 0 ) { animName = CRCD(0x1ca1ff20,"default"); } if ( propagate ) { GameNet::Manager * gamenet_man = GameNet::Manager::Instance(); GameNet::PlayerInfo* player; player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() ); if ( player && player->IsLocalPlayer()) { GameNet::MsgPlayPrimaryAnim anim_msg; Net::MsgDesc msg_desc; char msg[ Net::Manager::vMAX_PACKET_SIZE ]; char* stream; int size; Net::Client* client; client = gamenet_man->GetClient( player->GetSkaterNumber()); Dbg_Assert( client ); //anim_msg.m_Time = client->m_Timestamp; anim_msg.m_Index = animName; anim_msg.m_ObjId = GetObject()->GetID(); anim_msg.m_LoopingType = loop_type; anim_msg.m_StartTime = (unsigned short )( start_time * 4096.0f ); anim_msg.m_EndTime = ((unsigned short )( end_time * 4096.0f )); anim_msg.m_BlendPeriod = (unsigned short )( blend_period * 4096.0f ); anim_msg.m_Speed = (unsigned short )( speed * 4096.0f ); stream = msg; //memcpy( stream, &anim_msg.m_Time, sizeof( unsigned int )); //stream += sizeof( int ); *stream++ = anim_msg.m_LoopingType; *stream++ = anim_msg.m_ObjId; memcpy( stream, &anim_msg.m_Index, sizeof( uint32 )); stream += sizeof( uint32 ); memcpy( stream, &anim_msg.m_StartTime, sizeof( unsigned short )); stream += sizeof( unsigned short ); memcpy( stream, &anim_msg.m_EndTime, sizeof( unsigned short )); stream += sizeof( unsigned short ); memcpy( stream, &anim_msg.m_BlendPeriod, sizeof( unsigned short )); stream += sizeof( unsigned short ); memcpy( stream, &anim_msg.m_Speed, sizeof( unsigned short )); stream += sizeof( unsigned short ); size = (unsigned int) stream - (unsigned int) msg; msg_desc.m_Data = msg; msg_desc.m_Length = size; msg_desc.m_Id = GameNet::MSG_ID_PRIM_ANIM_START; client->EnqueueMessageToServer( &msg_desc ); } } // GJ: Should this be broken up into two commands/ // to degenerate the old, and start playing the new? // printf( "Playing anim %s\n", Script::FindChecksumName( animName ), blend_period ); #ifdef __NOPT_ASSERT__ // Define this to disable blending if ( Script::GetInteger( CRCD(0xf098f123,"disable_blending"), Script::NO_ASSERT ) ) { blend_period = 0.0f; } #endif if ( blend_period == 0.0f || !ShouldBlend() ) { destroy_blend_channels(); } create_new_blend_channel( blend_period ); Gfx::CBlendChannel* pPrimaryChannel = get_primary_channel(); Dbg_Assert( pPrimaryChannel ); pPrimaryChannel->PlaySequence( animName, start_time, end_time, loop_type, blend_period, speed, IsFlipped() ); delete_anim_tags(); add_anim_tags( animName ); } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::delete_anim_tags() { Script::CStruct* pTags = GetObject()->GetTags(); if ( pTags ) { pTags->RemoveComponent( CRCD(0x5db4115f,"AnimTags") ); } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::add_anim_tags( uint32 animName ) { Script::CStruct* pAnimTagTable = Script::GetStructure(CRCD(0x95807202,"AnimTagTable"), Script::ASSERT); if ( pAnimTagTable ) { Script::CStruct* pSubStruct; if ( pAnimTagTable->GetStructure( animName, &pSubStruct, Script::NO_ASSERT ) ) { Script::CStruct* pTempStruct = new Script::CStruct; // add a new anim tags structure to the tags Script::CStruct* pTags = new Script::CStruct; pTags->AppendStructure( pSubStruct ); pTempStruct->AddStructurePointer(CRCD(0x5db4115f,"AnimTags"), pTags); GetObject()->SetTagsFromScript( pTempStruct ); delete pTempStruct; } } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::SetWobbleTarget( float alpha, bool propagate ) { if ( propagate ) { GameNet::Manager * gamenet_man = GameNet::Manager::Instance(); GameNet::PlayerInfo* player; static unsigned char s_last_alpha = 255; player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() ); if ( player && player->IsLocalPlayer()) { Net::Client* client; GameNet::MsgSetWobbleTarget msg; client = gamenet_man->GetClient( player->GetSkaterNumber() ); Dbg_Assert( client ); if( alpha < 0 ) { alpha = 0; } else if( alpha > 1 ) { alpha = 1; } //msg.m_Time = client->m_Timestamp; msg.m_Alpha = (unsigned char ) ( alpha * 255.0f ); msg.m_ObjId = GetObject()->GetID(); if( s_last_alpha != msg.m_Alpha ) { Net::MsgDesc msg_desc; msg_desc.m_Data = &msg; msg_desc.m_Length = sizeof( GameNet::MsgSetWobbleTarget ); msg_desc.m_Id = GameNet::MSG_ID_SET_WOBBLE_TARGET; msg_desc.m_Singular = true; client->EnqueueMessageToServer( &msg_desc ); s_last_alpha = msg.m_Alpha; } } } if ( get_primary_channel() ) { Script::CStruct* pTempParams = new Script::CStruct; pTempParams->AddFloat( CRCD(0x4d747fa0,"wobbletargetalpha"), alpha ); get_primary_channel()->CallMemberFunction( CRCD(0xd0209498,"setwobbletarget"), pTempParams, NULL ); delete pTempParams; } } /******************************************************************/ /* */ /* */ /******************************************************************/ static int s_get_wobble_mask( int variable, float value ) { int mask; mask = 0; switch( variable ) { case GameNet::MsgSetWobbleDetails::vWOBBLE_AMP_A: if( value == 0.05f ) { mask = 0; } else if( value == 0.1f ) { mask = 1; } else if( value == 10.1f ) { mask = 2; } else { Dbg_Printf( "value is %f\n", value ); Dbg_Assert( 0 ); } break; case GameNet::MsgSetWobbleDetails::vWOBBLE_AMP_B: if( value == 0.04f ) { mask = 0; } else if( value == 10.1f ) { mask = 1; } else { Dbg_Printf( "VARIABLE: %d Value %f\n", variable, value ); Dbg_Assert( 0 ); } break; case GameNet::MsgSetWobbleDetails::vWOBBLE_K1: if( value == 0.0022f ) { mask = 0; } else if( value == 20.0f ) { mask = 1; } else { Dbg_Printf( "value is %f\n", value ); Dbg_Assert( 0 ); } break; case GameNet::MsgSetWobbleDetails::vWOBBLE_K2: if( value == 0.0017f ) { mask = 0; } else if( value == 10.0f ) { mask = 1; } else { Dbg_Assert( 0 ); } break; case GameNet::MsgSetWobbleDetails::vSPAZFACTOR: if( value == 1.5f ) { mask = 0; } else if( value == 1.0f ) { mask = 1; } else { Dbg_Printf( "value is %f\n", value ); Dbg_Assert( 0 ); } break; default: Dbg_Assert( 0 ); break; } return mask; } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::SetWobbleDetails( const Gfx::SWobbleDetails& wobble_details, bool propagate ) { if ( propagate ) { GameNet::Manager * gamenet_man = GameNet::Manager::Instance(); GameNet::PlayerInfo* player; player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() ); if ( player && player->IsLocalPlayer()) { Net::Client* client; GameNet::MsgSetWobbleDetails msg; int mask; static char s_last_mask = -1; client = gamenet_man->GetClient( player->GetSkaterNumber() ); Dbg_Assert( client ); //msg.m_Time = client->m_Timestamp; msg.m_WobbleDetails = 0; mask = s_get_wobble_mask( GameNet::MsgSetWobbleDetails::vWOBBLE_AMP_A, wobble_details.wobbleAmpA ); msg.m_WobbleDetails |= mask; mask = s_get_wobble_mask( GameNet::MsgSetWobbleDetails::vWOBBLE_AMP_B, wobble_details.wobbleAmpB ); msg.m_WobbleDetails |= ( mask << 2 ); mask = s_get_wobble_mask( GameNet::MsgSetWobbleDetails::vWOBBLE_K1, wobble_details.wobbleK1 ); msg.m_WobbleDetails |= ( mask << 3 ); mask = s_get_wobble_mask( GameNet::MsgSetWobbleDetails::vWOBBLE_K2, wobble_details.wobbleK2 ); msg.m_WobbleDetails |= ( mask << 4 ); mask = s_get_wobble_mask( GameNet::MsgSetWobbleDetails::vSPAZFACTOR, wobble_details.spazFactor ); msg.m_WobbleDetails |= ( mask << 5 ); msg.m_ObjId = GetObject()->GetID(); // Only propagate if it's actually different from our last wobble details message if( s_last_mask != msg.m_WobbleDetails ) { Net::MsgDesc msg_desc; msg_desc.m_Data = &msg; msg_desc.m_Length = sizeof( GameNet::MsgSetWobbleDetails ); msg_desc.m_Id = GameNet::MSG_ID_SET_WOBBLE_DETAILS; msg_desc.m_Singular = true; client->EnqueueMessageToServer( &msg_desc ); s_last_mask = msg.m_WobbleDetails; } } } if ( get_primary_channel() ) { Script::CStruct* pTempParams = new Script::CStruct; pTempParams->AddFloat( CRCD(0xfd266a26,"wobbleAmpA"), wobble_details.wobbleAmpA ); pTempParams->AddFloat( CRCD(0x642f3b9c,"wobbleAmpB"), wobble_details.wobbleAmpB ); pTempParams->AddFloat( CRCD(0x0f43fd49,"wobbleK1"), wobble_details.wobbleK1 ); pTempParams->AddFloat( CRCD(0x964aacf3,"wobbleK2"), wobble_details.wobbleK2 ); pTempParams->AddFloat( CRCD(0xf90b0824,"spazFactor"), wobble_details.spazFactor ); get_primary_channel()->CallMemberFunction( CRCD(0xea6d0efd,"setwobbledetails"), pTempParams, NULL ); delete pTempParams; } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::SetLoopingType( Gfx::EAnimLoopingType looping_type, bool propagate ) { if ( propagate ) { GameNet::Manager * gamenet_man = GameNet::Manager::Instance(); GameNet::PlayerInfo* player; static char s_last_type = -1; player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() ); if ( player && player->IsLocalPlayer()) { Net::Client* client; GameNet::MsgSetLoopingType msg; client = gamenet_man->GetClient( player->GetSkaterNumber() ); Dbg_Assert( client ); //msg.m_Time = client->m_Timestamp; msg.m_LoopingType = looping_type; msg.m_ObjId = GetObject()->GetID(); if( s_last_type != looping_type ) { Net::MsgDesc msg_desc; msg_desc.m_Data = &msg; msg_desc.m_Length = sizeof( GameNet::MsgSetLoopingType ); msg_desc.m_Id = GameNet::MSG_ID_SET_LOOPING_TYPE; client->EnqueueMessageToServer( &msg_desc ); s_last_type = looping_type; } } } if ( get_primary_channel() ) { get_primary_channel()->SetLoopingType( looping_type ); } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::ReverseDirection( bool propagate ) { Dbg_Assert( !propagate ); if ( get_primary_channel() ) { get_primary_channel()->ReverseDirection(); } // TODO: eventually turn this into a net message. // For now, only the viewer objects should be reversing the direction } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::SetAnimSpeed( float speed, bool propagate, bool all_channels ) { if ( !get_primary_channel() ) { // no primary channel, so do nothing return; } float cur_speed = get_primary_channel()->GetAnimSpeed(); if( !all_channels && Mth::Abs( cur_speed - speed ) < vMIN_SPEED_THRESHOLD ) { return; } if ( propagate ) { GameNet::Manager * gamenet_man = GameNet::Manager::Instance(); Net::Client* client; GameNet::MsgSetAnimSpeed msg; GameNet::PlayerInfo* player; player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() ); if ( player && player->IsLocalPlayer()) { client = gamenet_man->GetClient( player->GetSkaterNumber() ); if( client ) { Net::MsgDesc msg_desc; //msg.m_Time = client->m_Timestamp; msg.m_AnimSpeed = speed; msg.m_ObjId = GetObject()->GetID(); msg_desc.m_Data = &msg; msg_desc.m_Length = sizeof( GameNet::MsgSetAnimSpeed ); msg_desc.m_Id = GameNet::MSG_ID_SET_ANIM_SPEED; client->EnqueueMessageToServer( &msg_desc ); } } } if (all_channels) { Gfx::CBlendChannel* pChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetNext(); while ( pChannel ) { pChannel->SetAnimSpeed( speed ); pChannel = (Gfx::CBlendChannel*)pChannel->GetNext(); } } else { get_primary_channel()->SetAnimSpeed( speed ); } if (DebugSkaterScripts && GetObject()->GetID() == 0) { printf("%d: Setting Anim Speed: %f\n",(int)Tmr::GetRenderFrame(),speed); } } /******************************************************************/ /* */ /* */ /******************************************************************/ float CAnimationComponent::GetAnimSpeed( Script::CStruct* pParams, Script::CScript* pScript ) { if ( !get_primary_channel() ) { // no primary channel, so do nothing return 0; } float cur_speed = get_primary_channel()->GetAnimSpeed(); return cur_speed; } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::EnableBlending( bool enabled ) { m_shouldBlend = enabled; } /******************************************************************/ /* */ /* */ /******************************************************************/ bool CAnimationComponent::ShouldBlend() { return m_shouldBlend; } /******************************************************************/ /* */ /* */ /******************************************************************/ // Plays an animation given a set of parameters contained in pParams. // This allows both the skater and other moving objects such as pedestrians to share a common-ish interface. // Returns the checksum of the animation if it did get successfully played, 0 otherwise. // (This feature is used by some debug code in the CSkater's PlayAnim script command) uint32 CAnimationComponent::PlayAnim(Script::CStruct *pParams, Script::CScript *pScript, float defaultBlendPeriod ) { if (Gfx::CBlendChannel* primary_channel = get_primary_channel()) { m_last_animation_time = primary_channel->GetCurrentAnimTime(); } if (m_dont_interrupt && !IsAnimComplete()) { return 0; } if ( !has_anims() ) { // some items don't have animation data associated with it yet... return 0; } uint32 AnimChecksum=0; pParams->GetChecksum( CRCD(0x98549ba4,"Anim"), &AnimChecksum ); if ( !AnimChecksum ) { Script::PrintContents(pParams); Dbg_MsgAssert(0,("\n%s\nobj: %s\nPlayAnim requires an anim name",Script::FindChecksumName(GetObject()->GetID()),pScript->GetScriptInfo())); } float Speed=1.0f; pParams->GetFloat( CRCD(0xf0d90109,"Speed"), &Speed ); uint32 CurrentAnimChecksum = GetCurrentSequence(); if ( AnimChecksum == CRCD(0x230ccbf4,"Current") ) { AnimChecksum = CurrentAnimChecksum; } else { if (CurrentAnimChecksum==AnimChecksum) { if ( pParams->ContainsFlag( CRCD(0xfe09fe09,"NoRestart") ) ) { // Return without doing anything, since the user specified NoRestart, and the // current anim is the same as the requested one. return 0; } } } if ( !AnimExists( AnimChecksum ) ) { #ifdef __NOPT_ASSERT__ if ( Script::GetInt( CRCD(0x56d4128e,"AssertOnMissingAnims"), Script::NO_ASSERT ) ) { Dbg_MsgAssert( 0, ( "*** Anim %s (%s) was not defined for object %s!", Script::FindChecksumName(AnimChecksum), Script::FindChecksumName(m_animScriptName), Script::FindChecksumName(GetObject()->GetID() ) ) ); } else if ( Script::GetInt( CRCD(0xf42cb81a,"WarnOnMissingAnims"), Script::NO_ASSERT ) ) { Dbg_Message( "*** Anim %s (%s) was not defined for object %s!", Script::FindChecksumName(AnimChecksum), Script::FindChecksumName(m_animScriptName), Script::FindChecksumName(GetObject()->GetID() ) ); } #endif // by default, a missing anim will play the christ-pose anim AnimChecksum = CRCD(0x1ca1ff20,"default"); } float BlendPeriod=defaultBlendPeriod; float BlendPeriodPercent=0.0f; if (mGotBlendPeriodOut) { // If a BlendPeriodOut command was issued, use that value to override any // value specified by PlayAnim. BlendPeriod=mBlendPeriodOut; // but clear the flag for next time. mGotBlendPeriodOut=false; } else if (pParams->GetFloat(CRCD( 0x4f3abd6c,"BlendPeriodPercent"), &BlendPeriodPercent) ) { BlendPeriod = AnimDuration(AnimChecksum) * (BlendPeriodPercent/100.0f); } else { pParams->GetFloat( CRCD(0x8f0d24ed,"BlendPeriod"), &BlendPeriod ); } float Duration=AnimDuration(AnimChecksum); float From=0; float Current=0; float To=Duration; if ( pParams->ContainsFlag( CRCD(0x099371ae,"SyncToPreviousAnim") ) ) { float Dummy, PreviousAnimCurrent, PreviousAnimDuration; GetPrimaryAnimTimes(&Dummy, &PreviousAnimCurrent, &Dummy); PreviousAnimDuration = AnimDuration(CurrentAnimChecksum); pParams->GetFloat(CRCD(0x4b4a153a, "EffectivePreviousAnimDuration"), &PreviousAnimDuration); Current = Mth::Clamp(PreviousAnimCurrent / AnimDuration(CurrentAnimChecksum), 0.0f, 1.0f) * Duration; if ( pParams->ContainsFlag( CRCD(0xf8cfd515,"Backwards") ) ) { Current = -(Duration - Current); } } else if ( pParams->ContainsFlag( CRCD(0xff5ac79e, "SyncToReversePreviousAnim") ) ) { float Dummy, PreviousAnimCurrent, PreviousAnimDuration; GetPrimaryAnimTimes(&Dummy, &PreviousAnimCurrent, &Dummy); PreviousAnimDuration = AnimDuration(CurrentAnimChecksum); pParams->GetFloat(CRCD(0x4b4a153a, "EffectivePreviousAnimDuration"), &PreviousAnimDuration); Current = Mth::Clamp((PreviousAnimDuration - PreviousAnimCurrent) / PreviousAnimDuration, 0.0f, 1.0f) * Duration; if ( pParams->ContainsFlag( CRCD(0xf8cfd515,"Backwards") ) ) { Current = -(Duration - Current); } } // Need TempCurrent, so that it doesn't conflict with regular Current // which is possibly modified in the above "SyncToPreviousAnim" code block float TempCurrent; float Dummy; GetPrimaryAnimTimes(&Dummy,&TempCurrent,&Dummy); Gfx::GetTimeFromParams( &From, &To, TempCurrent, Duration, pParams, pScript ); Gfx::EAnimLoopingType LoopingType; Gfx::GetLoopingTypeFromParams( &LoopingType, pParams ); m_dont_interrupt=pParams->ContainsFlag( CRCD(0x84f13067,"DontInterrupt") ); #ifdef __NOPT_ASSERT__ if ( Script::GetInt( CRCD(0xca108bce,"DebugAnims"), false ) ) { if ( GetObject()->GetID() == 0 ) { printf( "DebugAnims: Playing skater anim %s\n", Script::FindChecksumName(AnimChecksum) ); } } if (DebugSkaterScripts && AnimChecksum && GetObject()->GetID() == 0) { printf("%d: Playing anim '%s'\n",(int)Tmr::GetRenderFrame(),Script::FindChecksumName(AnimChecksum)); } #endif PlayPrimarySequence( AnimChecksum, true, From, To, LoopingType, BlendPeriod, Speed ); if (Current != 0.0f) { get_primary_channel()->AddTime(Current); } /* if ( GetObject()->GetID() == 0 ) { printf("-standard-anim-\n"); printf("Anim = %s\n", Script::FindChecksumName(AnimChecksum)); printf("From = %f\n", From); printf("To = %f\n", To); printf("Duration = %f\n", Duration); printf("Current = %f\n", Current); printf("Speed = %f\n", Speed); printf("BlendPeriod = %f\n", BlendPeriod); } */ uint32 PartialAnim; if (pParams->GetChecksum(CRCD(0x6c565f57, "PartialAnimOverlay"), &PartialAnim)) { Script::CStruct* pPartialAnimParams = new Script::CStruct; uint32 partial_anim_id; if (pParams->GetChecksum(CRCD(0xf0ce9e0f, "PartialAnimOverlayId"), &partial_anim_id)) { pPartialAnimParams->AddChecksum(CRCD(0x40c698af, "Id"), partial_anim_id); } pPartialAnimParams->AddChecksum(CRCD(0x7321a8d6, "Type"), CRCD(0x659bf355, "PartialAnim")); pPartialAnimParams->AddChecksum(CRCD(0x6c2bfb7f, "AnimName"), PartialAnim); pPartialAnimParams->AddFloat(CRCD(0x46e55e8f, "From"), From); pPartialAnimParams->AddFloat(CRCD(0x28782d3b, "To"), To); pPartialAnimParams->AddChecksum(NO_NAME, CRCD(0xd029f619, "Seconds")); pPartialAnimParams->AddFloat(CRCD(0x230ccbf4, "Current"), Current); pPartialAnimParams->AddFloat(CRCD(0xf0d90109, "Speed"), Speed); switch (LoopingType) { case Gfx::LOOPING_HOLD: break; case Gfx::LOOPING_CYCLE: pPartialAnimParams->AddChecksum(NO_NAME, CRCD(0x4f792e6c, "Cycle")); break; case Gfx::LOOPING_PINGPONG: pPartialAnimParams->AddChecksum(NO_NAME, CRCD(0x3153e314, "PingPong")); break; case Gfx::LOOPING_WOBBLE: pPartialAnimParams->AddChecksum(NO_NAME, CRCD(0x6d941203, "Wobble")); break; } get_primary_channel()->AddController(pPartialAnimParams); delete pPartialAnimParams; #ifdef __NOPT_ASSERT__ if ( GetObject()->GetID() == 0 && Script::GetInt( CRCD(0xca108bce,"DebugAnims"), false ) ) { printf( "DebugAnims: Playing skater partial anim overlay %s\n", Script::FindChecksumName(PartialAnim) ); } if (DebugSkaterScripts && PartialAnim && GetObject()->GetID() == 0) { printf("%d: Playing partial anim overlay '%s'\n",(int)Tmr::GetRenderFrame(),Script::FindChecksumName(PartialAnim)); } #endif } return AnimChecksum; } /******************************************************************/ /* */ /* */ /******************************************************************/ // static workspace data static Gfx::CPose sBlendPoses[vNUM_DEGENERATE_ANIMS]; static float sBlendValues[vNUM_DEGENERATE_ANIMS]; static Gfx::CPose sResultPose; /******************************************************************/ /* */ /* */ /******************************************************************/ void blend( Gfx::CPose* pPoseList, float* blendVal, int numBones, Gfx::CPose* pResultPose, int numBlendChannels ) { // the blend value of the primary animation // should be 1.0, since it's active. Dbg_Assert( blendVal[0] == 1.0f ); float totalBlendVal = 0.0f; for ( int i = 1; i < numBlendChannels; i++ ) { if ( blendVal[i] ) { totalBlendVal += 1.0f; } } float degenerateBlendTotal = 0.0f; for ( int i = 1; i < numBlendChannels; i++ ) { degenerateBlendTotal += blendVal[i]; } // normalize them so that they all add up to total blend value blendVal[0] = totalBlendVal - degenerateBlendTotal; // Scan through the sets of frames, interpolating between the 2 based on the blend values. float sum = 0.0f; int i0, i1; for ( i1 = (numBlendChannels - 1); i1 > 0; --i1 ) { float blend1 = blendVal[i1]; if ( blend1 ) { blend1 += sum; i0 = i1 - 1; while(( blendVal[i0] <= 0.0f ) && ( i0 > 0 )) { --i0; } float blend0 = blendVal[i0]; sum += blendVal[i1]; // Need to normalise blend0 and blend1 so they sum to 1.0. blend0 /= ( blend0 + blend1 ); blend1 = 1.0f - blend0; Mth::Quat* pRotations0 = pPoseList[i0].m_rotations; Mth::Quat* pRotations1 = pPoseList[i1].m_rotations; Mth::Vector* pTranslations0 = pPoseList[i0].m_translations; Mth::Vector* pTranslations1 = pPoseList[i1].m_translations; for( int b = 0; b < numBones; b++ ) { *pRotations0 = Mth::FastSlerp( *pRotations0, *pRotations1, 1.0f - blend0 ); *pTranslations0 = Mth::Lerp( *pTranslations0, *pTranslations1, 1.0f - blend0 ); pRotations0++; pRotations1++; pTranslations0++; pTranslations1++; } } } // copy the values into the result pose: Mth::Quat* pSrcQuat = &pPoseList[0].m_rotations[0]; Mth::Vector* pSrcVector = &pPoseList[0].m_translations[0]; Mth::Quat* pDstQuat = &pResultPose->m_rotations[0]; Mth::Vector* pDstVector = &pResultPose->m_translations[0]; memcpy( pDstQuat, pSrcQuat, numBones * sizeof(Mth::Quat) ); memcpy( pDstVector, pSrcVector, numBones * sizeof(Mth::Vector) ); } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::get_blend_channel( int blendChannel, Gfx::CPose* pResultPose, float* pBlendVal ) { float blendVal = 0.0f; Gfx::CBlendChannel* pBlendChannel = NULL; if ( 1 || ShouldBlend() ) { Dbg_MsgAssert( blendChannel >= 0 && blendChannel < (int)m_blendChannelList.CountItems(), ( "out of range blend channel %d (0-%d)", blendChannel, m_blendChannelList.CountItems() ) ); pBlendChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetItem(blendChannel); blendVal = pBlendChannel->GetBlendValue(); Dbg_MsgAssert( blendChannel != 0 || blendVal == 1.0f, ( "Expected primary channel to have a blend value of 1.0f" ) ); } else { switch ( blendChannel ) { case 0: pBlendChannel = get_primary_channel(); blendVal = 1.0f; break; default: Dbg_MsgAssert( 0, ( "Invalid blend channel %d", blendChannel ) ); } } if ( blendVal <= 0.0f ) { // GJ TODO: I'm not sure why this case happens, // but it happened on THPS4 as well... i should // look into this at some point... *pBlendVal = 0.0f; return; } Gfx::CSkeleton* pSkeleton = NULL; Dbg_Assert(mp_skeleton_component); pSkeleton = mp_skeleton_component->GetSkeleton(); if ( !pBlendChannel->GetPose( pResultPose ) )//, // m_flags & nxANIMCOMPONENTFLAGS_FLIPPED, // m_flags & nxANIMCOMPONENTFLAGS_ROTATESKATEBOARD, // pSkeleton ) ) { // couldn't find pose... // if it's the primary animation, assert if ( blendChannel == 0 ) { Dbg_MsgAssert( 0, ( "No primary animation found! Is %08x/%s playing a default animation?", GetObject()->GetID(), Script::FindChecksumName(m_animScriptName) ) ); } } *pBlendVal = blendVal; } /******************************************************************/ /* */ /* */ /******************************************************************/ bool CAnimationComponent::ShouldAnimate() { #if 1 // The model will be inactive if off the screen Nx::CModel* p_model = mp_model_component->GetModel(); return ( p_model && p_model->GetActive() ); #else // Old version, kind of mixed up // this function will eventually be used to LOD // our animations (i.e. updating every Nth frame // based on distance) // if we did this, we would want to make sure that // the model's update function to be called at least // once to sync up the skeleton initially (we don't // want to do it in the constructor, because then // there's an order dependency on the components) // GJ: Checking whether the model is visible // is a speed optimization, but it's ugly // because it makes this component dependent on the // model component... Eventually, I'd like to // find a more elegant solution // update the skeleton, if it exists Dbg_Assert(mp_model_component); Nx::CModel* p_model = mp_model_component->GetModel(); if ( p_model && p_model->GetActive() ) { Mth::Vector sphere = p_model->GetBoundingSphere(); printf ("sphere = (%.2f,%.2f,%.2f,%.2f)\n",sphere[X],sphere[Y],sphere[Z],sphere[W]); // GJ: The bounding sphere may have a position // offset built into it, so need to factor that in Mth::Vector pos = GetObject()->GetPos() + sphere; pos[W] = 1.0f; // Mick: This visibility test always returns true // when in split screen mode if ( !Nx::CEngine::sIsVisible( pos,sphere[3] ) ) { return false; } } // for now, just always animate, even if no model, or the model is inactive !? return true; #endif } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::update_skeleton() { if ( has_anims() ) { Gfx::CSkeleton* pSkeleton = NULL; Dbg_Assert(mp_skeleton_component); pSkeleton = mp_skeleton_component->GetSkeleton(); if ( !pSkeleton ) { return; } // Set the current view distance for the skeleton so that it may decide on an appropriate set of // bones to use in the animation. pSkeleton->SetBoneSkipDistance( m_parent_object_dist_to_camera ); int numBlendChannels = m_blendChannelList.CountItems(); for ( int blendChannel = 0; blendChannel < numBlendChannels; blendChannel++ ) { // this wil loop through each blend channel's animation channels get_blend_channel( blendChannel, &sBlendPoses[blendChannel], &sBlendValues[blendChannel] ); } // TODO: Here, we could run any controllers that need to act on the final, post-blend pose... if( numBlendChannels > 1 ) { blend( &sBlendPoses[0], &sBlendValues[0], pSkeleton->GetNumBones(), &sResultPose, numBlendChannels ); pSkeleton->Update( &sResultPose ); } else { // In the case that there is just one blend channel, there is no requirement to call the blend // function, which will needlessly copy the quaternion and translation values for each bone from // the sBlendPoses buffer to the sResultPose buffer. Just use the sBlendPoses buffer directly. pSkeleton->Update( &sBlendPoses[0] ); } } } /******************************************************************/ /* */ /* */ /******************************************************************/ bool CAnimationComponent::FlipAnimation( uint32 objId, bool flip, uint32 time, bool propagate ) { if( propagate ) { GameNet::Manager * gamenet_man = GameNet::Manager::Instance(); GameNet::PlayerInfo* player; player = gamenet_man->GetPlayerByObjectID( objId ); if( player && player->IsLocalPlayer()) { Net::Client* client; GameNet::MsgFlipAnim msg; Net::MsgDesc msg_desc; client = gamenet_man->GetClient( player->GetSkaterNumber()); Dbg_Assert( client ); msg.m_ObjId = objId; msg.m_Flipped = flip; //msg.m_Time = time; msg_desc.m_Data = &msg; msg_desc.m_Length = sizeof( GameNet::MsgFlipAnim ); msg_desc.m_Id = GameNet::MSG_ID_FLIP_ANIM; client->EnqueueMessageToServer( &msg_desc ); } } bool oldFlipped = m_flags & nxANIMCOMPONENTFLAGS_FLIPPED; SetFlipState( flip ); return oldFlipped; } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::SetFlipState( bool shouldFlip ) { // TODO: should actually be in blend channel code // since we want certain blend channels to be flipped // (the animation component will still need to know // which flip state to make new channels) m_flags &= ~nxANIMCOMPONENTFLAGS_FLIPPED; m_flags |= ( shouldFlip ? nxANIMCOMPONENTFLAGS_FLIPPED : 0 ); } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::destroy_blend_channels() { m_blendChannelList.DestroyAllNodes(); } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::initialize_procedural_bones( Script::CStruct* pParams ) { Script::CArray* pArray; pParams->GetArray( CRCD(0x3ed7262b,"bone_list"), &pArray, Script::ASSERT ); for ( uint32 i = 0; i < pArray->GetSize(); i++ ) { this->InitializeProceduralBone( pArray->GetChecksum(i) ); } } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::update_procedural_bones() { int numProceduralBones = this->GetNumProceduralBones(); Gfx::CProceduralBone* p_current_procedural_bone = this->GetProceduralBones(); for ( int i = 0; i < numProceduralBones; i++ ) { // TODO: Factor in the frame rate... if ( p_current_procedural_bone->transEnabled ) { p_current_procedural_bone->currentTrans += p_current_procedural_bone->deltaTrans; p_current_procedural_bone->currentTrans[X] = (int)p_current_procedural_bone->currentTrans[X] & 0xfff; p_current_procedural_bone->currentTrans[Y] = (int)p_current_procedural_bone->currentTrans[Y] & 0xfff; p_current_procedural_bone->currentTrans[Z] = (int)p_current_procedural_bone->currentTrans[Z] & 0xfff; } if ( p_current_procedural_bone->rotEnabled ) { p_current_procedural_bone->currentRot += p_current_procedural_bone->deltaRot; p_current_procedural_bone->currentRot[X] = (int)p_current_procedural_bone->currentRot[X] & 0xfff; p_current_procedural_bone->currentRot[Y] = (int)p_current_procedural_bone->currentRot[Y] & 0xfff; p_current_procedural_bone->currentRot[Z] = (int)p_current_procedural_bone->currentRot[Z] & 0xfff; } if ( p_current_procedural_bone->scaleEnabled ) { p_current_procedural_bone->currentScale += p_current_procedural_bone->deltaScale; p_current_procedural_bone->currentScale[X] = (int)p_current_procedural_bone->currentScale[X] & 0xfff; p_current_procedural_bone->currentScale[Y] = (int)p_current_procedural_bone->currentScale[Y] & 0xfff; p_current_procedural_bone->currentScale[Z] = (int)p_current_procedural_bone->currentScale[Z] & 0xfff; } p_current_procedural_bone++; } } /******************************************************************/ /* */ /* */ /******************************************************************/ bool CAnimationComponent::InitializeProceduralBone( uint32 boneName ) { if ( m_numProceduralBones >= vMAXPROCEDURALBONES ) { Dbg_MsgAssert( 0, ( "Too many procedural bones" ) ); return false; } Gfx::CProceduralBone* pBone = &mp_proceduralBones[m_numProceduralBones]; pBone->m_name = boneName; pBone->transEnabled = false; pBone->rotEnabled = true; pBone->trans0[X] = Script::GetFloat( CRCD(0x802638f7,"trans_min_x"), Script::NO_ASSERT ); pBone->trans0[Y] = Script::GetFloat( CRCD(0xf7210861,"trans_min_y"), Script::NO_ASSERT ); pBone->trans0[Z] = Script::GetFloat( CRCD(0x6e2859db,"trans_min_z"), Script::NO_ASSERT); pBone->trans0[W] = 1.0f; pBone->trans1[X] = Script::GetFloat( CRCD(0x5d39cfda,"trans_max_x"), Script::NO_ASSERT ); pBone->trans1[Y] = Script::GetFloat( CRCD(0x2a3eff4c,"trans_max_y"), Script::NO_ASSERT ); pBone->trans1[Z] = Script::GetFloat( CRCD(0xb337aef6,"trans_max_z"), Script::NO_ASSERT ); pBone->trans1[W] = 1.0f; pBone->currentTrans = Mth::Vector(0.0f,0.0f,0.0f,0.0f); pBone->deltaTrans = Mth::Vector(32.0f,32.0f,32.0f,0.0f); pBone->rot0[X] = Script::GetFloat( CRCD(0x2dafa9c5,"rot_min_x"), Script::NO_ASSERT ) * 2.0f * Mth::PI / 4096.0f; pBone->rot0[Y] = Script::GetFloat( CRCD(0x5aa89953,"rot_min_y"), Script::NO_ASSERT ) * 2.0f * Mth::PI / 4096.0f; pBone->rot0[Z] = Script::GetFloat( CRCD(0xc3a1c8e9,"rot_min_z"), Script::NO_ASSERT ) * 2.0f * Mth::PI / 4096.0f; pBone->rot0[W] = 1.0f; pBone->rot1[X] = Script::GetFloat( CRCD(0xf0b05ee8,"rot_max_x"), Script::NO_ASSERT ) * 2.0f * Mth::PI / 4096.0f; pBone->rot1[Y] = Script::GetFloat( CRCD(0x87b76e7e,"rot_max_y"), Script::NO_ASSERT ) * 2.0f * Mth::PI / 4096.0f; pBone->rot1[Z] = Script::GetFloat( CRCD(0x1ebe3fc4,"rot_max_z"), Script::NO_ASSERT ) * 2.0f * Mth::PI / 4096.0f; pBone->rot1[W] = 1.0f; pBone->currentRot = Mth::Vector(0.0f,0.0f,0.0f,0.0f); pBone->deltaRot = Mth::Vector(32.0f,32.0f,32.0f,0.0f); pBone->scale0[X] = 1.0f; //Script::GetFloat("scale_min_x",Script::NO_ASSERT) * 2.0f * Mth::PI / 4096.0f; pBone->scale0[Y] = 1.0f; //Script::GetFloat("scale_min_y",Script::NO_ASSERT) * 2.0f * Mth::PI / 4096.0f; pBone->scale0[Z] = 1.0f; //Script::GetFloat("scale_min_z",Script::NO_ASSERT) * 2.0f * Mth::PI / 4096.0f; pBone->scale0[W] = 1.0f; pBone->scale1[X] = 1.0f; //Script::GetFloat("scale_max_x",Script::NO_ASSERT) * 2.0f * Mth::PI / 4096.0f; pBone->scale1[Y] = 1.0f; //Script::GetFloat("scale_max_y",Script::NO_ASSERT) * 2.0f * Mth::PI / 4096.0f; pBone->scale1[Z] = 1.0f; //Script::GetFloat("scale_max_z",Script::NO_ASSERT) * 2.0f * Mth::PI / 4096.0f; pBone->scale1[W] = 1.0f; pBone->currentScale = Mth::Vector(0.0f,0.0f,0.0f,0.0f); pBone->deltaScale = Mth::Vector(32.0f,32.0f,32.0f,0.0f); m_numProceduralBones++; return true; } /******************************************************************/ /* */ /* */ /******************************************************************/ Gfx::CProceduralBone* CAnimationComponent::GetProceduralBoneByName( uint32 boneName ) { for ( int i = 0; i < m_numProceduralBones; i++ ) { if ( boneName == mp_proceduralBones[i].m_name ) { return &mp_proceduralBones[i]; } } return NULL; } /******************************************************************/ /* */ /* */ /******************************************************************/ int CAnimationComponent::GetNumProceduralBones() { return m_numProceduralBones; } /******************************************************************/ /* */ /* */ /******************************************************************/ Gfx::CProceduralBone* CAnimationComponent::GetProceduralBones() { return mp_proceduralBones; } /******************************************************************/ /* */ /* */ /******************************************************************/ bool CAnimationComponent::IsFlipped() { return m_flags & nxANIMCOMPONENTFLAGS_FLIPPED; } /******************************************************************/ /* */ /* */ /******************************************************************/ void CAnimationComponent::Finalize() { mp_skeleton_component = GetSkeletonComponentFromObject( GetObject() ); mp_suspend_component = GetSuspendComponentFromObject( GetObject() ); mp_model_component = GetModelComponentFromObject( GetObject() ); } /******************************************************************/ /* */ /* */ /******************************************************************/ }