From 0e92cfe34370c8f1e81f8279f040ca890e34a8bb Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 10 May 2026 17:52:48 +0200 Subject: [PATCH 1/6] basic anim blending --- .../GameClient/Module/W3DModelDraw.h | 14 + .../GameClient/Module/W3DWalkerDraw.h | 64 +++ .../GameClient/Drawable/Draw/W3DModelDraw.cpp | 143 +++++- .../Drawable/Draw/W3DWalkerDraw.cpp | 118 +++++ Core/Libraries/Source/WWVegas/WW3D2/htree.cpp | 123 ++++++ Core/Libraries/Source/WWVegas/WW3D2/htree.h | 8 + Core/Libraries/Source/WWVegas/WW3D2/rendobj.h | 10 + .../Source/WWVegas/WW3D2/animobj.cpp | 413 +++++++++++++++++- .../Libraries/Source/WWVegas/WW3D2/animobj.h | 50 ++- .../Libraries/Source/WWVegas/WW3D2/hlod.cpp | 18 + .../Libraries/Source/WWVegas/WW3D2/hlod.h | 12 + 11 files changed, 943 insertions(+), 30 deletions(-) create mode 100644 Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DWalkerDraw.h create mode 100644 Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DWalkerDraw.cpp diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h index c7d0cd2a593..ff305169fa0 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h @@ -228,6 +228,10 @@ struct ModelConditionInfo mutable Bool m_hasRecoilBonesOrMuzzleFlashes[WEAPONSLOT_COUNT]; mutable Byte m_validStuff; + // Animation Blending (needs WalkerDraw) + UnsignedInt m_animBlendTime; + + enum { PRISTINE_BONES_VALID = 0x0001, @@ -492,6 +496,16 @@ class W3DModelDraw : public DrawModule, public ObjectDrawInterface Int boneIndex; }; + struct AnimInfoHelper + { + Int frameNum; + Int mode; + float numFrames; + float fraction; + }; + AnimInfoHelper m_prevAnimHelper; + AnimInfoHelper getCurrentAnimHelper() const; + typedef std::vector WeaponRecoilInfoVec; typedef std::vector ParticleSystemIDVec; diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DWalkerDraw.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DWalkerDraw.h new file mode 100644 index 00000000000..6740c4cdc98 --- /dev/null +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DWalkerDraw.h @@ -0,0 +1,64 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: W3DWalkerDraw.h //////////////////////////////////////////////////////////////////////////// +// Author: Andi W, May 26 +// Desc: TODO +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "W3DDevice/GameClient/Module/W3DModelDraw.h" + + +//------------------------------------------------------------------------------------------------- +class W3DWalkerDrawModuleData : public W3DModelDrawModuleData +{ +public: + + W3DWalkerDrawModuleData(); + ~W3DWalkerDrawModuleData(); + static void buildFieldParse(MultiIniFieldParse& p); + +protected: + virtual void setAnimationBlendTime(void* instance, UnsignedInt value); + +}; + +//------------------------------------------------------------------------------------------------- +class W3DWalkerDraw : public W3DModelDraw +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( W3DWalkerDraw, "W3DWalkerDraw" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( W3DWalkerDraw, W3DWalkerDrawModuleData ) + +public: + + W3DWalkerDraw( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + virtual void doDrawModule(const Matrix3D* transformMtx);///< checks a property on the local player before passing this up + +protected: +}; diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp index b0d30cb6b48..c940b43673a 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp @@ -642,10 +642,26 @@ void ModelConditionInfo::validateCachedBones(RenderObjClass* robj, Real scale) c int mode = 0; float mult = 1.0f; + HAnimClass* curAnim1 = NULL; + int numFrames1 = 0; + float frame1 = 0.0f; + int mode1 = 0; + float mult1 = 1.0f; + float percentage = 1.0f; + int fadeOutTime = 0; + int startFadeTime = 0; + + if (robj->Class_ID() == RenderObjClass::CLASSID_HLOD) { hlod = (HLodClass*)robj; - curAnim = hlod->Peek_Animation_And_Info(frame, numFrames, mode, mult); + if (hlod->Is_Double_Anim()) { + curAnim = hlod->Peek_Animation_And_Info(frame, numFrames, mode, mult, &curAnim1, frame1, numFrames1, mode1, mult1, percentage, fadeOutTime, startFadeTime); + } + else + { + curAnim = hlod->Peek_Animation_And_Info(frame, numFrames, mode, mult); + } } // if we have any animations in this state, always choose the first, since the animations @@ -704,8 +720,14 @@ void ModelConditionInfo::validateCachedBones(RenderObjClass* robj, Real scale) c } robj->Set_Transform(originalTransform); // restore previous transform - if (curAnim != nullptr) + if (curAnim1 != nullptr) { + // DOUBLE_ANIM + robj->Set_Animation(curAnim, frame, curAnim1, frame1, percentage, mode, mode1, fadeOutTime, startFadeTime); + hlod->Set_Animation_Frame_Rate_Multiplier(mult, mult1); + } + else if (curAnim != nullptr) { + // SINGlE_ANIM robj->Set_Animation(curAnim, frame, mode); hlod->Set_Animation_Frame_Rate_Multiplier(mult); } @@ -1021,6 +1043,7 @@ void ModelConditionInfo::clear() m_animMaxSpeedFactor = 1.0f; m_pristineBones.clear(); m_validStuff = 0; + m_animBlendTime = 0; } //------------------------------------------------------------------------------------------------- @@ -1489,6 +1512,8 @@ void W3DModelDrawModuleData::parseConditionState(INI* ini, void *instance, void { "Flags", INI::parseBitString32, ACBitsNames, offsetof(ModelConditionInfo, m_flags) }, { "ParticleSysBone", parseParticleSysBone, nullptr, 0 }, { "AnimationSpeedFactorRange", parseRealRange, nullptr, 0 }, + // Not used in the base class, but needs to be defined here to avoid massive refactoring + { "AnimationBlendTime", INI::parseUnsignedInt, nullptr, offsetof(ModelConditionInfo, m_animBlendTime) }, { nullptr, nullptr, nullptr, 0 } }; @@ -1791,6 +1816,11 @@ W3DModelDraw::W3DModelDraw(Thing *thing, const ModuleData* moduleData) : DrawMod m_needRecalcBoneParticleSystems = false; m_fullyObscuredByShroud = false; + m_prevAnimHelper.frameNum = 0; + m_prevAnimHelper.mode = 0; + m_prevAnimHelper.numFrames = -1; + m_prevAnimHelper.fraction = -1; + // only validate the current time-of-day and weather conditions by default. getW3DModelDrawModuleData()->validateStuffForTimeAndWeather(getDrawable(), TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT, @@ -2226,12 +2256,55 @@ Real W3DModelDraw::getCurrentAnimFraction() const } } +//------------------------------------------------------------------------------------------------- +W3DModelDraw::AnimInfoHelper W3DModelDraw::getCurrentAnimHelper() const +{ + + AnimInfoHelper helper; + helper.frameNum = 0; + helper.mode = 0; + helper.numFrames = -1.0; + helper.fraction = -1.0; + + if (m_curState != nullptr + // && isAnyMaintainFrameFlagSet(m_curState->m_flags) + && m_renderObject != nullptr + && m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD) + { + float framenum, dummy; + int mode, numFrames; + + HLodClass* hlod = (HLodClass*)m_renderObject; + /*HAnimClass* anim =*/ hlod->Peek_Animation_And_Info(framenum, numFrames, mode, dummy); + if (framenum < 0.0) + helper.fraction = 0.0; + else if (framenum >= numFrames) + helper.fraction = 1.0; + else + helper.fraction = (Real)framenum / ((Real)numFrames - 1); + helper.frameNum = framenum; + helper.mode = mode; + helper.numFrames = numFrames; + } +#if defined(_DEBUG2) + else { + DEBUG_LOG((">*>*> W3DMD getCurrentAnimHelper - m_curState = '%s'\n", + m_curState == NULL ? "NULL" : "NOT NULL")); + } +#endif + + + return helper; +} + //------------------------------------------------------------------------------------------------- void W3DModelDraw::adjustAnimation(const ModelConditionInfo* prevState, Real prevAnimFraction) { if (!m_curState) return; + Int m_whichAnimInPrevState = m_whichAnimInCurState; + // if the current state has m_animations associated, do the right thing Int numAnims = m_curState->m_animations.size(); if (numAnims > 0) @@ -2287,15 +2360,61 @@ void W3DModelDraw::adjustAnimation(const ModelConditionInfo* prevState, Real pre startFrame = REAL_TO_INT(prevAnimFraction * animHandle->Get_Num_Frames()-1); } - m_renderObject->Set_Animation(animHandle, startFrame, m_curState->m_mode); - REF_PTR_RELEASE(animHandle); - animHandle = nullptr; + // ANIMATION BLENDING + if (prevState && + m_curState->m_animBlendTime > 0 && + prevState->m_animations[0].getAnimHandle() && + m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD) { - if (m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD) - { - HLodClass *hlod = (HLodClass*)m_renderObject; - Real factor = GameClientRandomValueReal( m_curState->m_animMinSpeedFactor, m_curState->m_animMaxSpeedFactor ); - hlod->Set_Animation_Frame_Rate_Multiplier( factor ); + DEBUG_LOG((">>>>BLEND ANIMS!!")); + + HLodClass* hlod = (HLodClass*)m_renderObject; + + const W3DAnimationInfo& animInfoPrev = prevState->m_animations[m_whichAnimInPrevState]; + + HAnimClass* animHandlePrev = animInfoPrev.getAnimHandle(); + + Real factor = GameClientRandomValueReal(m_curState->m_animMinSpeedFactor, m_curState->m_animMaxSpeedFactor); + + Int startFramePrev = REAL_TO_INT(prevAnimFraction * m_prevAnimHelper.numFrames - 1); + + // maxBlendTime = currentAnim duration in milliseconds + Int animBlendTime = m_curState->m_animBlendTime; + Int curAnimDurMS = REAL_TO_INT((animHandle->Get_Num_Frames() * 1000.0f / animHandle->Get_Frame_Rate()) * factor); + if (animBlendTime > curAnimDurMS) { + animBlendTime = curAnimDurMS; + } + hlod->Set_Animation( + animHandle, + startFrame, + animHandlePrev, + startFramePrev, + 1.0f, //We start with prev anim and fade into the new anim + m_curState->m_mode, + m_prevAnimHelper.mode, + animBlendTime + ); + REF_PTR_RELEASE(animHandle); + REF_PTR_RELEASE(animHandlePrev); + animHandle = NULL; + animHandlePrev = NULL; + + // Let's ignore speed factor for prev anim for now + // We need to find out when the prev anim is supposed to fade out: + + hlod->Set_Animation_Frame_Rate_Multiplier(factor); //This might need to be different + } + else { + m_renderObject->Set_Animation(animHandle, startFrame, m_curState->m_mode); + REF_PTR_RELEASE(animHandle); + animHandle = NULL; + + if (m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD) + { + HLodClass* hlod = (HLodClass*)m_renderObject; + Real factor = GameClientRandomValueReal(m_curState->m_animMinSpeedFactor, m_curState->m_animMaxSpeedFactor); + hlod->Set_Animation_Frame_Rate_Multiplier(factor); + } } } @@ -3060,6 +3179,7 @@ void W3DModelDraw::setModelState(const ModelConditionInfo* newState) } // get this here, before we change anything... we'll need it to pass to adjustAnimation (srj) + m_prevAnimHelper = getCurrentAnimHelper(); Real prevAnimFraction = getCurrentAnimFraction(); // @@ -3106,6 +3226,7 @@ void W3DModelDraw::setModelState(const ModelConditionInfo* newState) //BONEPOS_LOG(("validateStuff() from within W3DModelDraw::setModelState()")); //BONEPOS_DUMPREAL(draw->getScale()); + //TODO ! (idk what, but there was a TODO here?!) newState->validateStuff(m_renderObject, draw->getScale(), getW3DModelDrawModuleData()->m_extraPublicBones); // ensure that any muzzle flashes from the *new* state, start out hidden... // hideAllMuzzleFlashes(newState, m_renderObject);//moved to above @@ -3973,6 +4094,8 @@ void W3DModelDraw::setAnimationFrame( int frame ) //------------------------------------------------------------------------------------------------- void W3DModelDraw::setPauseAnimation(Bool pauseAnim) { + // TODO: Animation Blending here? + if (m_pauseAnimation == pauseAnim) { return; diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DWalkerDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DWalkerDraw.cpp new file mode 100644 index 00000000000..db24b2d0002 --- /dev/null +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DWalkerDraw.cpp @@ -0,0 +1,118 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: W3DWalkerDraw.cpp //////////////////////////////////////////////////////////////////////////// +// Author: Andi W, May 26 +// Desc: TODO +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "W3DDevice/GameClient/Module/W3DWalkerDraw.h" + +#include "Common/GameUtility.h" +#include "Common/Player.h" +#include "Common/Xfer.h" + +//------------------------------------------------------------------------------------------------- +W3DWalkerDrawModuleData::W3DWalkerDrawModuleData() +{ +} + +//------------------------------------------------------------------------------------------------- +W3DWalkerDrawModuleData::~W3DWalkerDrawModuleData() +{ +} + +//------------------------------------------------------------------------------------------------- +void W3DWalkerDrawModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + W3DModelDrawModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + //{ "RequiredScience", INI::parseScience, nullptr, offsetof(W3DWalkerDrawModuleData, m_requiredScience) }, + + { nullptr, nullptr, nullptr, 0 } + }; + p.add(dataFieldParse); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +W3DWalkerDraw::W3DWalkerDraw( Thing *thing, const ModuleData* moduleData ) : W3DModelDraw( thing, moduleData ) +{ +} + +//------------------------------------------------------------------------------------------------- +W3DWalkerDraw::~W3DWalkerDraw() +{ +} + +//------------------------------------------------------------------------------------------------- +// All this does is stop the call path if we haven't been cleared to draw yet +void W3DWalkerDraw::doDrawModule(const Matrix3D* transformMtx) +{ + W3DModelDraw::doDrawModule(transformMtx); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void W3DWalkerDraw::crc( Xfer *xfer ) +{ + + // extend base class + W3DModelDraw::crc( xfer ); + +} + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void W3DWalkerDraw::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + W3DModelDraw::xfer( xfer ); + +} + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void W3DWalkerDraw::loadPostProcess( void ) +{ + + // extend base class + W3DModelDraw::loadPostProcess(); + +} + + diff --git a/Core/Libraries/Source/WWVegas/WW3D2/htree.cpp b/Core/Libraries/Source/WWVegas/WW3D2/htree.cpp index 231d50ea97a..fad838d81d9 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/htree.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/htree.cpp @@ -760,6 +760,129 @@ void HTreeClass::Blend_Update } } +/*********************************************************************************************** + * HTreeClass::Blend_Update -- HRawAnimClass version * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 3/4/98 GTH : Created. * + *=============================================================================================*/ +void HTreeClass::Blend_Update +( + const Matrix3D& root, + HRawAnimClass* motion0, + float frame0, + HRawAnimClass* motion1, + float frame1, + float percentage // 0.0 = motion0. 1.0 = motion1 +) +{ + + //DEBUG_LOG((">>>htree Anim_Update - DOUBLE_ANIM (HRawAnimClass) - '%s'\n", Get_Name())); + PivotClass* pivot, * endpivot, * lastAnimPivot; + + //Matrix3D mtx; + + Pivot[0].Transform = root; + Pivot[0].IsVisible = true; + + int num_anim_pivots = MIN(motion0->Get_Num_Pivots(), motion1->Get_Num_Pivots()); + + //Get integer frame + int iframe0 = WWMath::Float_To_Long(frame0); + if (iframe0 >= motion0->Get_Num_Frames()) + iframe0 = 0; + int iframe1 = WWMath::Float_To_Long(frame1); + if (iframe1 >= motion1->Get_Num_Frames()) + iframe1 = 0; + + Vector3 trans0, trans1; + Quaternion q0, q1; + Matrix3D mtx; + + struct NodeMotionStruct* nodeMotion0 = ((HRawAnimClass*)motion0)->Get_Node_Motion_Array(); + struct NodeMotionStruct* nodeMotion1 = ((HRawAnimClass*)motion1)->Get_Node_Motion_Array(); + nodeMotion0 += 1; //skip the root node + nodeMotion1 += 1; //skip the root node + + pivot = &Pivot[1]; + endpivot = pivot + (NumPivots - 1); + lastAnimPivot = &Pivot[num_anim_pivots]; + + for (int piv_idx = 1; pivot < endpivot; pivot++, nodeMotion0++, nodeMotion1++) { + + // base pose + assert(pivot->Parent != NULL); + Matrix3D::Multiply(pivot->Parent->Transform, pivot->BaseTransform, &(pivot->Transform)); + + // Don't update this pivot if the HTree doesn't have animation data for it... + if (pivot < lastAnimPivot) + { + + // animation + trans0.Set(0.0f, 0.0f, 0.0f); + trans1.Set(0.0f, 0.0f, 0.0f); + Matrix3D* xform = &pivot->Transform; + + if (nodeMotion0->X != NULL) + nodeMotion0->X->Get_Vector(iframe0, &(trans0[0])); + if (nodeMotion0->Y != NULL) + nodeMotion0->Y->Get_Vector(iframe0, &(trans0[1])); + if (nodeMotion0->Z != NULL) + nodeMotion0->Z->Get_Vector(iframe0, &(trans0[2])); + + if (nodeMotion1->X != NULL) + nodeMotion1->X->Get_Vector(iframe1, &(trans1[0])); + if (nodeMotion1->Y != NULL) + nodeMotion1->Y->Get_Vector(iframe1, &(trans1[1])); + if (nodeMotion1->Z != NULL) + nodeMotion1->Z->Get_Vector(iframe1, &(trans1[2])); + + Vector3 lerped = (1.0 - percentage) * trans0 + (percentage)*trans1; + + if (ScaleFactor == 1.0f) + xform->Translate(lerped); + else + xform->Translate(lerped * ScaleFactor); + + if (nodeMotion0->Q != NULL) + { + Quaternion q; + nodeMotion0->Q->Get_Vector_As_Quat(iframe0, q0); + + if (nodeMotion1->Q != NULL) { + nodeMotion1->Q->Get_Vector_As_Quat(iframe1, q1); + Fast_Slerp(q, q0, q1, percentage); + } + else { + q = q0; + } + +#ifdef ALLOW_TEMPORARIES + * xform = *xform * ::Build_Matrix3D(q, mtx); +#else + xform->postMul(::Build_Matrix3D(q, mtx)); +#endif + } + // visibility + if (nodeMotion0->Vis != NULL) + pivot->IsVisible = (nodeMotion0->Vis->Get_Bit(iframe0) == 1); + else + pivot->IsVisible = 1; + } + + if (pivot->Is_Captured()) + { + pivot->Capture_Update(); + pivot->IsVisible = true; + } + } +} /*********************************************************************************************** diff --git a/Core/Libraries/Source/WWVegas/WW3D2/htree.h b/Core/Libraries/Source/WWVegas/WW3D2/htree.h index a3a2e42b71c..e4794ed9854 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/htree.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/htree.h @@ -104,6 +104,14 @@ class HTreeClass : public W3DMPO float frame1, float percentage); + void Blend_Update(const Matrix3D& root, + HRawAnimClass* motion0, + float frame0, + HRawAnimClass* motion1, + float frame1, + float percentage); + + void Combo_Update( const Matrix3D & root, HAnimComboClass * anim); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/rendobj.h b/Core/Libraries/Source/WWVegas/WW3D2/rendobj.h index 0b4861c00bd..1833e27b7f2 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/rendobj.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/rendobj.h @@ -327,6 +327,16 @@ class RenderObjClass : public RefCountClass , public PersistClass, public MultiL HAnimClass * motion1, float frame1, float percentage) { } + virtual void Set_Animation(HAnimClass* motion0, + float frame0, + HAnimClass* motion1, + float frame1, + float percentage, + int mode0, + int mode1, + int fadeOutTime, + int startFadeTime = 0) { } + virtual void Set_Animation( HAnimComboClass * anim_combo) { } virtual HAnimClass * Peek_Animation( void ) { return nullptr; } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/animobj.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/animobj.cpp index 56cab1628ff..f94167dfa71 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/animobj.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/animobj.cpp @@ -66,6 +66,7 @@ #include "ww3d.h" #include "wwmemlog.h" #include "animatedsoundmgr.h" +#include "hrawanim.h" /*********************************************************************************************** @@ -99,6 +100,13 @@ Animatable3DObjClass::Animatable3DObjClass(const char * htree_name) : ModeInterp.PrevFrame1=0.0f; ModeInterp.Frame1=0.0f; ModeInterp.Percentage=0.0f; + ModeInterp.LastSyncTime = WW3D::Get_Sync_Time(); + ModeInterp.FadeOutTime = 0; + ModeInterp.StartFadeTime = 0; + ModeInterp.frameRateMultiplier0 = 1.0; + ModeInterp.animDirection0 = 1.0; + ModeInterp.frameRateMultiplier1 = 1.0; + ModeInterp.animDirection1 = 1.0; ModeCombo.AnimCombo=nullptr; /* @@ -155,6 +163,13 @@ Animatable3DObjClass::Animatable3DObjClass(const Animatable3DObjClass & src) : ModeInterp.PrevFrame1=0.0f; ModeInterp.Frame1=0.0f; ModeInterp.Percentage=0.0f; + ModeInterp.FadeOutTime = 0; + ModeInterp.StartFadeTime = 0; + ModeInterp.LastSyncTime = WW3D::Get_Sync_Time(); + ModeInterp.frameRateMultiplier0 = 1.0; + ModeInterp.animDirection0 = 1.0; + ModeInterp.frameRateMultiplier1 = 1.0; + ModeInterp.animDirection1 = 1.0; ModeCombo.AnimCombo=nullptr; *this = src; @@ -215,6 +230,13 @@ Animatable3DObjClass & Animatable3DObjClass::operator = (const Animatable3DObjCl ModeInterp.PrevFrame1 = 0.0f; ModeInterp.Frame1 = 0.0f; ModeInterp.Percentage = 0.0f; + ModeInterp.FadeOutTime = 0; + ModeInterp.StartFadeTime = 0; + ModeInterp.LastSyncTime = WW3D::Get_Sync_Time(); + ModeInterp.frameRateMultiplier0 = 1.0; + ModeInterp.animDirection0 = 1.0; + ModeInterp.frameRateMultiplier1 = 1.0; + ModeInterp.animDirection1 = 1.0; ModeCombo.AnimCombo = nullptr; delete HTree; @@ -292,7 +314,17 @@ void Animatable3DObjClass::Render(RenderInfoClass & rinfo) // // Force the hierarchy to be recalculated for single animations. // - const bool isSingleAnim = CurMotionMode == SINGLE_ANIM && ModeAnim.AnimMode != ANIM_MODE_MANUAL; + bool isSingleAnim = CurMotionMode == SINGLE_ANIM && ModeAnim.AnimMode != ANIM_MODE_MANUAL; + + + if (CurMotionMode == DOUBLE_ANIM) { + //TODO: try to support fading into manual anim + if (ModeAnim.AnimMode != ANIM_MODE_MANUAL) { + Single_Anim_Progress(); + isSingleAnim = true; + } + } + if (isSingleAnim || !Is_Hierarchy_Valid() || Are_Sub_Object_Transforms_Dirty()) { Update_Sub_Object_Transforms(); @@ -318,7 +350,15 @@ void Animatable3DObjClass::Special_Render(SpecialRenderInfoClass & rinfo) // // Force the hierarchy to be recalculated for single animations. // - const bool isSingleAnim = CurMotionMode == SINGLE_ANIM && ModeAnim.AnimMode != ANIM_MODE_MANUAL; + bool isSingleAnim = CurMotionMode == SINGLE_ANIM && ModeAnim.AnimMode != ANIM_MODE_MANUAL; + + if (CurMotionMode == DOUBLE_ANIM) { + //TODO: try to support fading into manual anim + if (ModeAnim.AnimMode != ANIM_MODE_MANUAL) { + Single_Anim_Progress(); + isSingleAnim = true; + } + } if (isSingleAnim || !Is_Hierarchy_Valid()) { Update_Sub_Object_Transforms(); @@ -551,6 +591,80 @@ void Animatable3DObjClass::Set_Animation } +// ====================================================================== +void Animatable3DObjClass::Set_Animation +( + HAnimClass* motion0, + float frame0, + HAnimClass* motion1, + float frame1, + float percentage, + int mode0, + int mode1, + int fadeOutTime, + int startFadeTime +) +{ + //DEBUG_LOG2((">>> animobj.cpp - Set_Animation DOUBLE_ANIM - with mode0 '%d' and mode1 '%d'\n", mode0, mode1)); + Release(); + + CurMotionMode = DOUBLE_ANIM; + ModeInterp.Motion0 = motion0; + ModeInterp.Motion1 = motion1; + ModeInterp.PrevFrame0 = ModeInterp.Frame0; + ModeInterp.PrevFrame1 = ModeInterp.Frame1; + ModeInterp.Frame0 = frame0; + ModeInterp.Frame1 = frame1; + ModeInterp.Percentage = percentage; + ModeInterp.LastSyncTime = WW3D::Get_Sync_Time(); + ModeInterp.frameRateMultiplier0 = 1.0; + ModeInterp.animDirection0 = 1.0; + ModeInterp.frameRateMultiplier1 = 1.0; + ModeInterp.animDirection1 = 1.0; + ModeInterp.FadeOutTime = fadeOutTime; // This should be in milliseconds + if (startFadeTime == 0) { + ModeInterp.StartFadeTime = ModeInterp.LastSyncTime; + } + else { + ModeInterp.StartFadeTime = startFadeTime; + } + + ModeInterp.AnimMode0 = mode0; + ModeInterp.AnimMode1 = mode1; + + if (mode0 < ANIM_MODE_LOOP_BACKWARDS) + ModeInterp.animDirection0 = 1.0f; //assume playing forwards + else + ModeInterp.animDirection0 = -1.0f; //reverse animation playback + + if (mode1 < ANIM_MODE_LOOP_BACKWARDS) + ModeInterp.animDirection1 = 1.0f; //assume playing forwards + else + ModeInterp.animDirection1 = -1.0f; //reverse animation playback + + + Set_Hierarchy_Valid(false); + + if (ModeInterp.Motion0 != NULL) { + ModeInterp.Motion0->Add_Ref(); + const char* sound_name = AnimatedSoundMgrClass::Get_Embedded_Sound_Name(motion0); + if (sound_name) { + int bone_index = Get_Bone_Index(sound_name); + motion0->Set_Embedded_Sound_Bone_Index(bone_index); + } + } + + if (ModeInterp.Motion1 != NULL) { + ModeInterp.Motion1->Add_Ref(); + const char* sound_name = AnimatedSoundMgrClass::Get_Embedded_Sound_Name(motion1); + if (sound_name) { + int bone_index = Get_Bone_Index(sound_name); + motion1->Set_Embedded_Sound_Bone_Index(bone_index); + } + } +} + + /*********************************************************************************************** * Animatable3DObjClass::Set_Animation -- Set animation state with an anim combo * * * @@ -605,7 +719,11 @@ HAnimClass * Animatable3DObjClass::Peek_Animation( void ) { if ( CurMotionMode == SINGLE_ANIM ) { return ModeAnim.Motion; - } else { + } + else if (CurMotionMode == DOUBLE_ANIM) { + return ModeInterp.Motion0; + } + else { return nullptr; } } @@ -779,6 +897,9 @@ void Animatable3DObjClass::Update_Sub_Object_Transforms(void) */ CompositeRenderObjClass::Update_Sub_Object_Transforms(); + HRawAnimClass* motion0 = nullptr; + HRawAnimClass* motion1 = nullptr; + /* ** Update the transforms */ @@ -804,8 +925,16 @@ void Animatable3DObjClass::Update_Sub_Object_Transforms(void) break; case DOUBLE_ANIM: - Blend_Update(Transform,ModeInterp.Motion0,ModeInterp.Frame0, - ModeInterp.Motion1,ModeInterp.Frame1,ModeInterp.Percentage); + + if (ModeInterp.AnimMode0 != ANIM_MODE_MANUAL) { + Single_Anim_Progress(); + } + else { + ModeInterp.Percentage = Compute_Current_Percentage(); + } + + Blend_Update(Transform, ModeInterp.Motion0, ModeInterp.Frame0, + ModeInterp.Motion1, ModeInterp.Frame1, ModeInterp.Percentage); /* ** Play any sounds that are triggered by this frame of animation @@ -1032,6 +1161,152 @@ float Animatable3DObjClass::Compute_Current_Frame(float *newDirection) const return frame; } + +//*=============================================================================================*/ +float Animatable3DObjClass::Compute_Current_Frame(float* newFrame0, float* newFrame1, float* newDirection) const +{ + /*DEBUG_LOG2((">>> animobj.cpp - Compute_Current_Frame - DOUBLE_ANIM '%s': mode0 '%d' and mode1 '%d'\n", + Get_HTree()->Get_Name(), ModeInterp.AnimMode0, ModeInterp.AnimMode1));*/ + switch (CurMotionMode) + { + case SINGLE_ANIM: + { + break; + } + case DOUBLE_ANIM: + { + float direction0 = ModeInterp.animDirection0; + float direction1 = ModeInterp.animDirection1; + float frame0 = ModeInterp.Frame0; + float frame1 = ModeInterp.Frame1; + float percentage = ModeInterp.Percentage; + int sync_time = WW3D::Get_Sync_Time(); + // + // Compute the current frame based on elapsed time. + // + if (ModeInterp.AnimMode0 != ANIM_MODE_MANUAL) { + float sync_time_diff = sync_time - ModeInterp.LastSyncTime; + float delta = ModeInterp.Motion0->Get_Frame_Rate() * ModeInterp.frameRateMultiplier0 * ModeInterp.animDirection0 * sync_time_diff * 0.001f; + frame0 += delta; + frame0 = Compute_Current_Frame_For_Anim(ModeInterp.Motion0, ModeInterp.AnimMode0, frame0, direction0); + } + if (ModeInterp.AnimMode1 != ANIM_MODE_MANUAL) { + float sync_time_diff = sync_time - ModeInterp.LastSyncTime; + float delta = ModeInterp.Motion1->Get_Frame_Rate() * ModeInterp.frameRateMultiplier1 * ModeInterp.animDirection1 * sync_time_diff * 0.001f; + frame1 += delta; + frame1 = Compute_Current_Frame_For_Anim(ModeInterp.Motion1, ModeInterp.AnimMode1, frame1, direction1); + } + + /*if (ModeInterp.FadeOutTime > 0 && ModeInterp.StartFadeTime > 0) { + percentage = 1.0f - (float)(sync_time - ModeInterp.StartFadeTime) / (float)ModeInterp.FadeOutTime; + if (percentage > 1.0f) { + percentage = 1.0f; + } + if (percentage < 0.0f) { + percentage = 0.0f; + } + DEBUG_LOG2(("### >>> animobj.cpp - Compute_Current_Frame - '%s' - New Percentage = %f\n", Get_HTree()->Get_Name(), percentage)); + }*/ + + //percentage = Compute_Current_Percentage(); + + *newFrame0 = frame0; + *newFrame1 = frame1; + // *newPercentage = percentage; + + if (newDirection) + *newDirection = direction0; + + return frame0; + } + break; + } + +} + +float Animatable3DObjClass::Compute_Current_Frame_For_Anim(HAnimClass* anim, int animMode, float frame, float& direction) const { + //DEBUG_LOG2((">>> animobj.cpp - Compute_Current_Frame_For_Anim - DOUBLE_ANIM '%s'\n", Get_HTree()->Get_Name())); + switch (animMode) + { + case ANIM_MODE_ONCE: + if (frame >= anim->Get_Num_Frames() - 1) { + frame = anim->Get_Num_Frames() - 1; + } + break; + case ANIM_MODE_LOOP: + if (frame >= anim->Get_Num_Frames() - 1) { + frame -= anim->Get_Num_Frames() - 1; + } + // If it is still too far out, reset + if (frame >= anim->Get_Num_Frames() - 1) { + frame = 0; + } + break; + case ANIM_MODE_ONCE_BACKWARDS: //play animation one time but backwards + if (frame < 0) { + frame = 0; + } + break; + case ANIM_MODE_LOOP_BACKWARDS: //play animation backwards in a loop + if (frame < 0) { + frame += anim->Get_Num_Frames() - 1; + } + // If it is still too far out, reset + if (frame < 0) { + frame = anim->Get_Num_Frames() - 1; + } + break; + case ANIM_MODE_LOOP_PINGPONG: + if (direction >= 1.0f) + { //playing forwards, reverse direction + if (frame >= (anim->Get_Num_Frames() - 1)) + { //step backwards in animation by excess time + frame = (anim->Get_Num_Frames() - 1) * 2 - frame; + // If it is still too far out, reset + if (frame >= anim->Get_Num_Frames() - 1) + frame = (anim->Get_Num_Frames() - 1); + direction = direction * -1.0f; + } + } + else + { //playing backwards, reverse direction + if (frame < 0) + { //step forwards in animation by excess time + frame = -frame; + // If it is still too far out, reset + if (frame >= anim->Get_Num_Frames() - 1) + frame = 0; + direction = direction * -1.0f; + } + } + break; + } + return frame; +} + + +float Animatable3DObjClass::Compute_Current_Percentage() const +{ + if (CurMotionMode == SINGLE_ANIM) { + return 0.0f; + } + else if (CurMotionMode == DOUBLE_ANIM) { + if (ModeInterp.FadeOutTime > 0 && ModeInterp.StartFadeTime > 0) { + int sync_time = WW3D::Get_Sync_Time(); + float percentage = 1.0f - (float)(sync_time - ModeInterp.StartFadeTime) / (float)ModeInterp.FadeOutTime; + if (percentage > 1.0f) { + percentage = 1.0f; + } + if (percentage < 0.0f) { + percentage = 0.0f; + } + //DEBUG_LOG2(("### >>> animobj.cpp - Compute_Current_Frame - '%s' - New Percentage = %f\n", Get_HTree()->Get_Name(), percentage)); + return percentage; + } + return 0.0f; + } +} + /*********************************************************************************************** * Animatable3DObjClass::Single_Anim_Progress -- progress anims for loop and once * * * @@ -1049,14 +1324,41 @@ void Animatable3DObjClass::Single_Anim_Progress (void) // // Update the current frame (only works in "SINGLE_ANIM" mode!) // - WWASSERT(CurMotionMode == SINGLE_ANIM); + if (CurMotionMode == SINGLE_ANIM) { - // - // Update the frame number and sync time - // - ModeAnim.PrevFrame = ModeAnim.Frame; - ModeAnim.Frame = Compute_Current_Frame(&ModeAnim.animDirection); - ModeAnim.LastSyncTime = WW3D::Get_Logic_Time_Milliseconds(); + // + // Update the frame number and sync time + // + ModeAnim.PrevFrame = ModeAnim.Frame; + ModeAnim.Frame = Compute_Current_Frame(&ModeAnim.animDirection); + ModeAnim.LastSyncTime = WW3D::Get_Logic_Time_Milliseconds(); + } + else if (CurMotionMode == DOUBLE_ANIM) { + //DEBUG_LOG2(("## >>> animobj.cpp - Single_Anim_Progress '%s' - DOUBLE_ANIM\n", Get_HTree()->Get_Name())); + float oldprev0 = ModeInterp.PrevFrame0; + float oldprev1 = ModeInterp.PrevFrame1; + ModeInterp.PrevFrame0 = ModeInterp.Frame0; + ModeInterp.PrevFrame1 = ModeInterp.Frame1; + + Compute_Current_Frame(&ModeInterp.Frame0, &ModeInterp.Frame1, &ModeInterp.animDirection0); + ModeInterp.Percentage = Compute_Current_Percentage(); + + //DEBUG_LOG2(("### >>> animobj.cpp - CurrentFrameStatus ('%s'):\n Frame0 = '%f', Frame1 = '%f', Percentage = '%f'\n Mode0 = '%d', Mode1 = '%d'\n", + // Get_HTree()->Get_Name(), ModeInterp.Frame0, ModeInterp.Frame1, ModeInterp.Percentage, ModeInterp.AnimMode0, ModeInterp.AnimMode1)); + + ModeInterp.LastSyncTime = WW3D::Get_Sync_Time(); + + if (ModeInterp.Frame0 == ModeInterp.PrevFrame0) { + ModeInterp.PrevFrame0 = oldprev0; + } + if (ModeInterp.Frame1 == ModeInterp.PrevFrame1) { + ModeInterp.PrevFrame1 = oldprev1; + } + // + // Force the heirarchy to be recalculated + // + Set_Hierarchy_Valid(false); + } } @@ -1084,6 +1386,17 @@ bool Animatable3DObjClass::Is_Animation_Complete( void ) const { return ( ModeAnim.Frame == 0); } } + else if (CurMotionMode == DOUBLE_ANIM) { + //DEBUG_LOG2((">>> animobj.cpp - Is_Animation_Complete -> DOUBLE_ANIM'\n")); + if (ModeInterp.AnimMode0 == ANIM_MODE_ONCE) { + return (ModeInterp.Frame0 == ModeInterp.Motion0->Get_Num_Frames() - 1); + } + else + if (ModeInterp.AnimMode0 == ANIM_MODE_ONCE_BACKWARDS) + { + return (ModeInterp.Frame0 == 0); + } + } return false; } @@ -1098,19 +1411,86 @@ HAnimClass * Animatable3DObjClass::Peek_Animation_And_Info(float& frame, int& nu mode = ModeAnim.AnimMode; mult = ModeAnim.frameRateMultiplier; return ModeAnim.Motion; + } + else if (CurMotionMode == DOUBLE_ANIM) { + //DEBUG_LOG2((">>> animobj.cpp - Peek_Animation_And_Info - DOUBLE_ANIM'\n")); + frame = ModeInterp.Frame0; + numFrames = ModeInterp.Motion0 ? ModeInterp.Motion0->Get_Num_Frames() : 0; + mode = ModeInterp.AnimMode0; + mult = ModeInterp.frameRateMultiplier0; + return ModeInterp.Motion0; } else { return nullptr; } } +/*********************************************************************************************** + * Animatable3DObjClass::Peek_Animation_And_Info * + *=============================================================================================*/ +HAnimClass* Animatable3DObjClass::Peek_Animation_And_Info(float& frame0, int& numFrames0, int& mode0, float& mult0, + HAnimClass** anim1, float& frame1, int& numFrames1, int& mode1, float& mult1, + float& percentage, int& fadeOutTime, int& startFadeTime) +{ + //DEBUG_LOG2((">>> animobj.cpp - Peek_Animation_And_Info_DOUBLE '%s'\n", Get_HTree()->Get_Name())); + if (CurMotionMode == SINGLE_ANIM) { + //DEBUG_LOG2((">>> animobj.cpp - Peek_Animation_And_Info_DOUBLE - SINGLE_ANIM'\n")); + frame0 = ModeAnim.Frame; + numFrames0 = ModeAnim.Motion ? ModeAnim.Motion->Get_Num_Frames() : 0; + mode0 = ModeAnim.AnimMode; + mult0 = ModeAnim.frameRateMultiplier; + return ModeAnim.Motion; + } + else if (CurMotionMode == DOUBLE_ANIM) { + //DEBUG_LOG2((">>> animobj.cpp - Peek_Animation_And_Info_DOUBLE - DOUBLE_ANIM'\n")); + frame0 = ModeInterp.Frame0; + numFrames0 = ModeInterp.Motion0 ? ModeInterp.Motion0->Get_Num_Frames() : 0; + mode0 = ModeInterp.AnimMode0; + mult0 = ModeInterp.frameRateMultiplier0; + + *anim1 = ModeInterp.Motion1; + frame1 = ModeInterp.Frame1; + numFrames0 = ModeInterp.Motion1 ? ModeInterp.Motion1->Get_Num_Frames() : 0; + mode1 = ModeInterp.AnimMode1; + mult1 = ModeInterp.frameRateMultiplier1; + + percentage = ModeInterp.Percentage; + fadeOutTime = ModeInterp.FadeOutTime; + startFadeTime = ModeInterp.StartFadeTime; + + return ModeInterp.Motion0; + } + //DEBUG_LOG2((">>> animobj.cpp - Peek_Animation_And_Info - Done'\n")); +} + /*********************************************************************************************** * Animatable3DObjClass::Set_Animation_Frame_Rate_Multiplier * *=============================================================================================*/ void Animatable3DObjClass::Set_Animation_Frame_Rate_Multiplier(float multiplier) { - // 020607 srj -- added - ModeAnim.frameRateMultiplier = multiplier; + if (CurMotionMode == SINGLE_ANIM) { + ModeAnim.frameRateMultiplier = multiplier; + } + else if (CurMotionMode == DOUBLE_ANIM) { + ModeInterp.frameRateMultiplier0 = multiplier; + ModeInterp.frameRateMultiplier1 = multiplier; + } +} + +/*********************************************************************************************** + * Animatable3DObjClass::Set_Animation_Frame_Rate_Multiplier * + *=============================================================================================*/ +void Animatable3DObjClass::Set_Animation_Frame_Rate_Multiplier(float multiplier0, float multiplier1) +{ + //DEBUG_LOG2((">>> animobj.cpp - Set_Animation_Frame_Rate_Multiplier '%s'\n", Get_HTree()->Get_Name())); + if (CurMotionMode == SINGLE_ANIM) { + ModeAnim.frameRateMultiplier = multiplier0; + } + else if (CurMotionMode == DOUBLE_ANIM) { + ModeInterp.frameRateMultiplier0 = multiplier0; + ModeInterp.frameRateMultiplier1 = multiplier1; + } } +// ---------------------------------- // (gth) TESTING DYNAMICALLY SWAPPING SKELETONS! @@ -1125,5 +1505,10 @@ void Animatable3DObjClass::Set_HTree(HTreeClass * new_htree) HTree = W3DNEW HTreeClass(*new_htree); } +// ---------------------------------------------------- +bool Animatable3DObjClass::Is_Double_Anim(void) const { + return CurMotionMode == DOUBLE_ANIM; +} + // EOF - animobj.cpp diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/animobj.h b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/animobj.h index fed654af69b..00e7210b74f 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/animobj.h +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/animobj.h @@ -82,16 +82,29 @@ class Animatable3DObjClass : public CompositeRenderObjClass virtual void Set_Animation(void); virtual void Set_Animation( HAnimClass * motion, float frame, int anim_mode = ANIM_MODE_MANUAL); - virtual void Set_Animation( HAnimClass * motion0, + virtual void Set_Animation(HAnimClass* motion0, float frame0, - HAnimClass * motion1, + HAnimClass* motion1, float frame1, float percentage); - virtual void Set_Animation( HAnimComboClass * anim_combo); + virtual void Set_Animation(HAnimClass* motion0, + float frame0, + HAnimClass* motion1, + float frame1, + float percentage, + int mode0, + int mode1, + int fadeOutTime, + int startFadeTime = 0); + virtual void Set_Animation(HAnimComboClass* anim_combo); virtual void Set_Animation_Frame_Rate_Multiplier(float multiplier); // 020607 srj -- added + virtual void Set_Animation_Frame_Rate_Multiplier(float multiplier0, float multiplier1); - virtual HAnimClass * Peek_Animation_And_Info(float& frame, int& numFrames, int& mode, float& mult); // 020710 srj -- added + virtual HAnimClass* Peek_Animation_And_Info(float& frame, int& numFrames, int& mode, float& mult); // 020710 srj -- added + virtual HAnimClass* Peek_Animation_And_Info(float& frame0, int& numFrames0, int& mode0, float& mult0, + HAnimClass** anim1, float& frame1, int& numFrames1, int& mode1, float& mult1, + float& percentage, int& fadeOutTime, int& startFadeTime); virtual HAnimClass * Peek_Animation( void ); virtual bool Is_Animation_Complete( void ) const; @@ -114,6 +127,8 @@ class Animatable3DObjClass : public CompositeRenderObjClass virtual bool Simple_Evaluate_Bone(int boneindex, Matrix3D *tm) const; virtual bool Simple_Evaluate_Bone(int boneindex, float frame, Matrix3D *tm) const; + virtual bool Is_Double_Anim(void) const; + // (gth) TESTING DYNAMICALLY SWAPPING SKELETONS! virtual void Set_HTree(HTreeClass * htree); ///Generals change so we can set sub-object transforms directly without having them revert to base pose @@ -125,6 +140,9 @@ class Animatable3DObjClass : public CompositeRenderObjClass // internally used to compute the current frame if the object is in ANIM_MODE_MANUAL float Compute_Current_Frame(float *newDirection=nullptr) const; + //Double anim version + float Compute_Current_Frame(float* frame0, float* frame1, float* newDirection = NULL) const; + // Update the sub-object transforms according to the current anim state and root transform. virtual void Update_Sub_Object_Transforms(void); @@ -195,11 +213,20 @@ class Animatable3DObjClass : public CompositeRenderObjClass HAnimClass * Motion0; HAnimClass * Motion1; + int AnimMode0; + int AnimMode1; float Frame0; float Frame1; float PrevFrame0; float PrevFrame1; float Percentage; + mutable int LastSyncTime; + int FadeOutTime; + int StartFadeTime; + float animDirection0; + float frameRateMultiplier0; + float animDirection1; + float frameRateMultiplier1; } ModeInterp; // CurMotionMode == MULTIPLE_ANIM @@ -210,6 +237,12 @@ class Animatable3DObjClass : public CompositeRenderObjClass }; friend class SkinClass; + +private: + float Compute_Current_Frame_For_Anim(HAnimClass* anim, int animMode, float frame, float& direction) const; + + float Compute_Current_Percentage() const; + }; @@ -293,8 +326,13 @@ inline void Animatable3DObjClass::Blend_Update /* ** Apply motion to the base pose */ - if (HTree) { - HTree->Blend_Update(root,motion0,frame0,motion1,frame1,percentage); + if ((motion0) && (motion1) && (HTree)) { + if (ModeInterp.Motion0->Class_ID() == HAnimClass::CLASSID_HRAWANIM && ModeInterp.Motion1->Class_ID() == HAnimClass::CLASSID_HRAWANIM) { + HTree->Blend_Update(root, (HRawAnimClass*)ModeInterp.Motion0, frame0, (HRawAnimClass*)ModeInterp.Motion1, frame1, percentage); + } + else { + HTree->Blend_Update(root, motion0, frame0, motion1, frame1, percentage); + } } Set_Hierarchy_Valid(true); } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index 346597953ff..a435b4017e1 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -2642,6 +2642,24 @@ void HLodClass::Set_Animation Set_Sub_Object_Transforms_Dirty(true); } +// ====================================== +void HLodClass::Set_Animation +( + HAnimClass* motion0, + float frame0, + HAnimClass* motion1, + float frame1, + float percentage, + int mode0, + int mode1, + int fadeOutTime, + int startFadeTime +) +{ + Animatable3DObjClass::Set_Animation(motion0, frame0, motion1, frame1, percentage, mode0, mode1, fadeOutTime, startFadeTime); + Set_Sub_Object_Transforms_Dirty(true); +} + /*********************************************************************************************** * HLodClass::Set_Animation -- set animation state to a combination of anims * diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h index 2a8e05031ee..2f4427cbdd2 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h @@ -144,6 +144,18 @@ class HLodClass : public W3DMPO, public Animatable3DObjClass float percentage); virtual void Set_Animation( HAnimComboClass * anim_combo); + virtual void Set_Animation(HAnimClass* motion0, + float frame0, + HAnimClass* motion1, + float frame1, + float percentage, + int mode0, + int mode1, + int fadeOutTime, + int startFadeTime = 0); + + + ///////////////////////////////////////////////////////////////////////////// // Render Object Interface - Collision Detection, Ray Tracing ///////////////////////////////////////////////////////////////////////////// From 2f1a946689d1d4873a7f832389a3da7f6527e73d Mon Sep 17 00:00:00 2001 From: Andi Date: Thu, 14 May 2026 10:07:51 +0200 Subject: [PATCH 2/6] Add MinAngle to AnimationSteeringUpdate --- .../Module/AnimationSteeringUpdate.h | 2 ++ .../Object/Update/AnimationSteeringUpdate.cpp | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AnimationSteeringUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AnimationSteeringUpdate.h index 49f3cc8e6a8..a7508586ad7 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AnimationSteeringUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AnimationSteeringUpdate.h @@ -50,6 +50,7 @@ class AnimationSteeringUpdateModuleData : public UpdateModuleData static const FieldParse dataFieldParse[] = { { "MinTransitionTime", INI::parseDurationUnsignedInt, nullptr, offsetof( AnimationSteeringUpdateModuleData, m_transitionFrames ) }, + { "MinAngle", INI::parseAngleReal, nullptr, offsetof( AnimationSteeringUpdateModuleData, m_minAngle ) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -57,6 +58,7 @@ class AnimationSteeringUpdateModuleData : public UpdateModuleData } UnsignedInt m_transitionFrames; + Real m_minAngle; }; //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp index c8265828e57..6aa643a2aac 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp @@ -35,8 +35,10 @@ #include "GameClient/Drawable.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Object.h" +#include "GameLogic/Module/AIUpdate.h" #include "GameLogic/Module/AnimationSteeringUpdate.h" #include "GameLogic/Module/PhysicsUpdate.h" +#include "GameLogic/PartitionManager.h" //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- @@ -79,6 +81,26 @@ UpdateSleepTime AnimationSteeringUpdate::update( void ) { PhysicsTurningType currentTurn = physics->getTurning(); + if (data->m_minAngle > 0 && m_currentTurnAnim == MODELCONDITION_INVALID) { + AIUpdateInterface* ai = getObject()->getAI(); + + const Coord3D* targetPos; + if (ai->getGoalObject() != nullptr) + targetPos = ai->getGoalObject()->getPosition(); + else + targetPos = ai->getGoalPosition(); + + if (targetPos != nullptr) { + // get angle between object and target + Real angleRel = ThePartitionManager->getRelativeAngle2D(getObject(), targetPos); + DEBUG_LOG((">>> ASU: RelativeAngle = %f", angleRel * 180.0 / PI)); + + if (abs(angleRel) < data->m_minAngle) { + return UPDATE_SLEEP_NONE; + } + } + } + switch( m_currentTurnAnim ) { case MODELCONDITION_INVALID: From 9bec784cfa63ae908f8c382ee24620c5a8be2ccf Mon Sep 17 00:00:00 2001 From: Andi Date: Thu, 14 May 2026 11:55:07 +0200 Subject: [PATCH 3/6] FXEvent --- .../GameClient/Module/W3DModelDraw.h | 14 ++ .../GameClient/Drawable/Draw/W3DModelDraw.cpp | 165 +++++++++++++++++- .../Object/Update/AnimationSteeringUpdate.cpp | 2 +- 3 files changed, 178 insertions(+), 3 deletions(-) diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h index ff305169fa0..2055338a722 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h @@ -128,6 +128,16 @@ struct ParticleSysBoneInfo typedef std::vector ParticleSysBoneInfoVector; +//------------------------------------------------------------------------------------------------- +struct FXEventInfo +{ + AsciiString boneName; + const FXList* fx; + UnsignedInt frame; +}; + +typedef std::vector FXEventInfoVector; + //------------------------------------------------------------------------------------------------- struct PristineBoneInfo { @@ -222,6 +232,8 @@ struct ModelConditionInfo Real m_animMinSpeedFactor; //Min speed factor (randomized each time it's played) Real m_animMaxSpeedFactor; //Max speed factor (randomized each time it's played) + FXEventInfoVector m_fxEvents; ///m_particleSysBones.push_back(info); } +//------------------------------------------------------------------------------------------------- +static void parseFXEvent(INI* ini, void* instance, void* store, const void* /*userData*/) +{ + //const char* token; + FXEventInfo info; + + // Frame + //token = ini->getNextToken(); + //info.frame = ini->scanUnsignedInt(token); + ini->parseUnsignedInt(ini, instance, &(info.frame), nullptr); + + // BoneName + info.boneName = ini->getNextAsciiString(); + info.boneName.toLower(); + + // FXName + ini->parseFXList(ini, instance, &(info.fx), nullptr); + + ModelConditionInfo* self = (ModelConditionInfo*)instance; + self->m_fxEvents.push_back(info); +} + //------------------------------------------------------------------------------------------------- static void parseRealRange( INI *ini, void *instance, void *store, const void* /*userData*/ ) { @@ -1511,8 +1533,8 @@ void W3DModelDrawModuleData::parseConditionState(INI* ini, void *instance, void { "WaitForStateToFinishIfPossible", parseLowercaseNameKey, nullptr, offsetof(ModelConditionInfo, m_allowToFinishKey) }, { "Flags", INI::parseBitString32, ACBitsNames, offsetof(ModelConditionInfo, m_flags) }, { "ParticleSysBone", parseParticleSysBone, nullptr, 0 }, + { "FXEvent", parseFXEvent, nullptr, 0 }, { "AnimationSpeedFactorRange", parseRealRange, nullptr, 0 }, - // Not used in the base class, but needs to be defined here to avoid massive refactoring { "AnimationBlendTime", INI::parseUnsignedInt, nullptr, offsetof(ModelConditionInfo, m_animBlendTime) }, { nullptr, nullptr, nullptr, 0 } }; @@ -2216,6 +2238,10 @@ void W3DModelDraw::doDrawModule(const Matrix3D* transformMtx) handleClientRecoil(); + handleFXEvents(); + + m_prevAnimHelper = getCurrentAnimHelper(); + } //------------------------------------------------------------------------------------------------- @@ -2909,6 +2935,142 @@ Bool W3DModelDraw::updateBonesForClientParticleSystems() } +//------------------------------------------------------------------------------------------------- +/* + DANGER WARNING READ ME + DANGER WARNING READ ME + DANGER WARNING READ ME + + This function must not EVER do ANYTHING which can in any way, shape, or form + affect GameLogic in any way; if it does, net desyncs will occur and we + will all lose our jobs. This must remain pure-client-only, and ensure + that nothing it does can be detected, even in read-only form, by GameLogic! + + DANGER WARNING READ ME + DANGER WARNING READ ME + DANGER WARNING READ ME +*/ +void W3DModelDraw::handleFXEvents() +{ + + const Drawable* drawable = getDrawable(); + if (drawable != nullptr) + { + + if (m_curState != nullptr && ! m_curState->m_fxEvents.empty()) + { + AnimInfoHelper curAnimHelper = getCurrentAnimHelper(); + + for (std::vector::const_iterator it = m_curState->m_fxEvents.begin(); it != m_curState->m_fxEvents.end(); ++it) + { + // Check frame timing + if (it->frame <= curAnimHelper.frameNum && it->frame > m_prevAnimHelper.frameNum) { + // Get bone position + Coord3D pos; + pos.zero(); + //Real rotation = 0.0f; + + Int boneIndex = m_renderObject ? m_renderObject->Get_Bone_Index(it->boneName.str()) : 0; + if (boneIndex != 0) + { + //// ugh... kill the mtx so we get it in modelspace, not world space + //Matrix3D originalTransform = m_renderObject->Get_Transform(); // save the transform + //Matrix3D tmp(true); + //tmp.Scale(getDrawable()->getScale()); + //m_renderObject->Set_Transform(tmp); // set to identity transform + + //const Matrix3D boneTransform = m_renderObject->Get_Bone_Transform(boneIndex); + //Vector3 vpos = boneTransform.Get_Translation(); + //rotation = boneTransform.Get_Z_Rotation(); + + //m_renderObject->Set_Transform(originalTransform); // restore it + + //pos.x = vpos.X; + //pos.y = vpos.Y; + //pos.z = vpos.Z; + + if (!m_renderObject->Is_Hidden()) + { + // I can ask the drawable's bone position if I am not hidden (if I have no object I have no choice) + Matrix3D mtx = m_renderObject->Get_Bone_Transform(boneIndex); + Coord3D pos; + pos.x = mtx.Get_X_Translation(); + pos.y = mtx.Get_Y_Translation(); + pos.z = mtx.Get_Z_Translation(); + + DEBUG_LOG((">>> FXEVENT: CurrentFrame = %d, PrevFrame = %d, Frame=%d, Bone=%s, FXList=%s, X/Y/Z = %f/%f/f", + curAnimHelper.frameNum, + m_prevAnimHelper.frameNum, + it->frame, + it->boneName.str(), + "TODO", + pos.x, + pos.y, + pos.z + )); + + FXList::doFXPos(it->fx, &pos, &mtx, 0.0f, nullptr, 0.0f); + } + } + } + + //ParticleSystem* sys = TheParticleSystemManager->createParticleSystem(it->particleSystemTemplate); + //if (sys != nullptr) + //{ + // Coord3D pos; + // pos.zero(); + // Real rotation = 0.0f; + + // Int boneIndex = m_renderObject ? m_renderObject->Get_Bone_Index(it->boneName.str()) : 0; + // if (boneIndex != 0) + // { + // // ugh... kill the mtx so we get it in modelspace, not world space + // Matrix3D originalTransform = m_renderObject->Get_Transform(); // save the transform + // Matrix3D tmp(true); + // tmp.Scale(getDrawable()->getScale()); + // m_renderObject->Set_Transform(tmp); // set to identity transform + + // const Matrix3D boneTransform = m_renderObject->Get_Bone_Transform(boneIndex); + // Vector3 vpos = boneTransform.Get_Translation(); + // rotation = boneTransform.Get_Z_Rotation(); + + // m_renderObject->Set_Transform(originalTransform); // restore it + + // pos.x = vpos.X; + // pos.y = vpos.Y; + // pos.z = vpos.Z; + // } + + // // got the bone position... + // sys->setPosition(&pos); + + // // and the direction, so that the system is oriented like the bone is + // sys->rotateLocalTransformZ(rotation); + + // // Attach it to the object... + // sys->attachToDrawable(drawable); + + // // important: mark it as do-not-save, since we'll just re-create it when we reload. + // sys->setSaveable(FALSE); + + // if (drawable->isDrawableEffectivelyHidden() || m_fullyObscuredByShroud) + // { + // sys->stop(); // don't start the systems for drawables that are hidden. + // } + + // // store the particle system id so we can kill it later. + + // ParticleSysTrackerType tracker; + // tracker.id = sys->getSystemID(); + // tracker.boneIndex = boneIndex; + + // m_particleSystemIDs.push_back(tracker); + //} + } + } + } + +} //------------------------------------------------------------------------------------------------- @@ -3179,7 +3341,6 @@ void W3DModelDraw::setModelState(const ModelConditionInfo* newState) } // get this here, before we change anything... we'll need it to pass to adjustAnimation (srj) - m_prevAnimHelper = getCurrentAnimHelper(); Real prevAnimFraction = getCurrentAnimFraction(); // diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp index 6aa643a2aac..6ea769bd9bc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp @@ -93,7 +93,7 @@ UpdateSleepTime AnimationSteeringUpdate::update( void ) if (targetPos != nullptr) { // get angle between object and target Real angleRel = ThePartitionManager->getRelativeAngle2D(getObject(), targetPos); - DEBUG_LOG((">>> ASU: RelativeAngle = %f", angleRel * 180.0 / PI)); + //DEBUG_LOG((">>> ASU: RelativeAngle = %f", angleRel * 180.0 / PI)); if (abs(angleRel) < data->m_minAngle) { return UPDATE_SLEEP_NONE; From f9bb04f089ee7b83026c90266dc87846028ce492 Mon Sep 17 00:00:00 2001 From: Andi Date: Fri, 15 May 2026 07:51:09 +0200 Subject: [PATCH 4/6] FX decal wip --- Core/CMakeLists.txt | 2 +- Core/GameEngine/Source/GameClient/FXList.cpp | 151 ++++++++++++++++ .../GameClient/Module/W3DDecalDraw.h | 81 +++++++++ .../GameClient/Drawable/Draw/W3DDecalDraw.cpp | 161 ++++++++++++++++++ .../GameEngine/Include/Common/DrawModule.h | 11 ++ 5 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDecalDraw.h create mode 100644 Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDecalDraw.cpp diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 7c1269d1db9..fd0adb1f877 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -19,7 +19,7 @@ target_sources(corei_libraries_include PRIVATE Libraries/Include/Lib/trig.h Libraries/Include/rts/debug.h Libraries/Include/rts/profile.h -) + "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw (2).cpp") target_link_libraries(corei_always INTERFACE core_config core_utility diff --git a/Core/GameEngine/Source/GameClient/FXList.cpp b/Core/GameEngine/Source/GameClient/FXList.cpp index e70d41370f4..7d46ce850ec 100644 --- a/Core/GameEngine/Source/GameClient/FXList.cpp +++ b/Core/GameEngine/Source/GameClient/FXList.cpp @@ -30,6 +30,8 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine +#define DEFINE_SHADOW_NAMES + #include "GameClient/FXList.h" #include "Common/DrawModule.h" @@ -50,6 +52,9 @@ #include "GameClient/Drawable.h" #include "GameClient/ParticleSys.h" #include "GameLogic/PartitionManager.h" +#include "GameClient/Shadow.h" +#include "../../../GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h" +#include "../../../GameEngineDevice/Include/W3DDevice/GameClient/W3DShadow.h" /////////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// @@ -467,6 +472,152 @@ class RayEffectFXNugget : public FXNugget }; EMPTY_DTOR(RayEffectFXNugget) + +//------------------------------------------------------------------------------------------------- +class DecalFXNugget : public FXNugget +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(DecalFXNugget, "DecalFXNugget") +public: + + DecalFXNugget() + { + m_templateName.set("GenericDecal"); // TODO + m_templateName = AsciiString::TheEmptyString; + m_textureName = AsciiString::TheEmptyString; + m_opacity = 1.0; ///< value between 0 and 1 + m_color = 0; ///< color in ARGB format. (Alpha is ignored). + m_lifetime = 0; + m_fadeOutTime = 0; + m_fadeInTime = 0; + m_type = 0; /// type of projection + m_decalSizeX = 0.0; /// 1/(world space extent of texture in x direction) + m_decalSizeY = 0.0; /// 1/(world space extent of texture in y direction) + m_angle = 0.0; + m_orientToObject = FALSE; + m_randomAngle = FALSE; + m_probability = 1.0f; + } + + virtual void doFXPos(const Coord3D* primary, const Matrix3D* primaryMtx, const Real primarySpeed, const Coord3D* secondary, const Real /*overrideRadius*/, FXSurfaceInfo* /*surfaceInfo*/) const + { + if (m_probability <= GameClientRandomValueReal(0, 1)) + return; + + if (primary) + { + Drawable* drawable = TheThingFactory->newDrawable(TheThingFactory->findTemplate(m_templateName)); + if (!drawable) + return; + + // Does it even make sense to set the matrix? + if (primaryMtx && m_orientToObject) + drawable->setTransformMatrix(primaryMtx); + drawable->setPosition(primary); + + //TODO: + //TracerDrawInterface* tdi = nullptr; + //for (DrawModule** d = drawable->getDrawModules(); *d; ++d) + //{ + // if ((tdi = (*d)->getTracerDrawInterface()) != nullptr) + // { + // tdi->setTracerParms(speed, m_length, m_width, m_color, 1.0f); + // } + //} + + Object* obj = drawable->getObject(); + GeometryInfo newGeom(GEOMETRY_BOX, TRUE, 1.0f, m_decalSizeX/2.0f, m_decalSizeY/2.0f); + obj->setGeometryInfo(newGeom); + + if (m_randomAngle) + drawable->setOrientation(GameClientRandomValueReal(0, PI * 2)); + + drawable->setExpirationDate(TheGameLogic->getFrame() + m_lifetime); + + //Set Shadow + for (DrawModule** d = drawable->getDrawModules(); *d; ++d) + { + W3DModelDraw* draw = (W3DModelDraw*) (*d); + if (draw) { + RenderObjClass* robj = draw->getRenderObject(); + + Shadow::ShadowTypeInfo shadowInfo; + strlcpy(shadowInfo.m_ShadowName, m_textureName.str(), ARRAY_SIZE(shadowInfo.m_ShadowName)); + shadowInfo.allowUpdates = FALSE; //shadow image will never update + shadowInfo.allowWorldAlign = TRUE; //shadow image will wrap around world objects + shadowInfo.m_type = m_type; + shadowInfo.m_sizeX = m_decalSizeX; + shadowInfo.m_sizeY = m_decalSizeY; + Shadow* shadow = TheW3DShadowManager->addShadow(robj, &shadowInfo); + //if (shadow) + //{ + // shadow->enableShadowInvisible(m_fullyObscuredByShroud); + // if (m_renderObject->Is_Hidden() || !m_shadowEnabled) + // shadow->enableShadowRender(FALSE); + //} + + } + break; + } + + //static const NameKeyType key_draw = NAMEKEY("W3DModelDraw"); + //W3DModelDraw* draw = (W3DModelDraw*)obj->findUpdateModule(key_centerUpdate); + //if (centerModule) + + } + else + { + DEBUG_CRASH(("You must have a primary source for this effect")); + } + } + + static void parse(INI* ini, void* instance, void* /*store*/, const void* /*userData*/) + { + static const FieldParse myFieldParse[] = + { + { "DecalName", INI::parseAsciiString, nullptr, offsetof(TracerFXNugget, m_tracerName) }, + { "Texture", INI::parseAsciiString, nullptr, offsetof(TracerFXNugget, m_textureName) }, + { "Style", INI::parseBitString32, TheShadowNames, offsetof(TracerFXNugget, m_shadowType) }, + { "Color", INI::parseColorInt, nullptr, offsetof(TracerFXNugget, m_color) }, + { "Opacity", INI::parsePercentToReal, nullptr, offsetof(TracerFXNugget, m_opacity) }, + { "Lifetime", INI::parseDurationUnsignedInt, nullptr, offsetof(TracerFXNugget, m_lifetime) }, + { "FadeInTime", INI::parseDurationUnsignedInt, nullptr, offsetof(TracerFXNugget, m_fadeInTime) }, + { "FadeOutTime", INI::parseDurationUnsignedInt, nullptr, offsetof(TracerFXNugget, m_fadeOutTime) }, + { "SizeX", INI::parseReal, nullptr, offsetof(TracerFXNugget, m_decalSizeX) }, + { "SizeY", INI::parseReal, nullptr, offsetof(TracerFXNugget, m_decalSizeY) }, + { "Angle", INI::parseReal, nullptr, offsetof(TracerFXNugget, m_angle) }, + { "RandomAngle", INI::parseBool, nullptr, offsetof(TracerFXNugget, m_randomAngle) }, + { "OrientToObject", INI::parseBool, nullptr, offsetof(TracerFXNugget, m_orientToObject) }, + { "Probability", INI::parseReal, nullptr, offsetof(TracerFXNugget, m_probability) }, + { nullptr, nullptr, nullptr, 0 } + }; + + DecalFXNugget* nugget = newInstance(DecalFXNugget); + ini->initFromINI(nugget, myFieldParse); + ((FXList*)instance)->addFXNugget(nugget); + } + +private: + AsciiString m_templateName; + AsciiString m_textureName; + Real m_opacity; ///< value between 0 and 1 + UnsignedInt m_color; ///< color in ARGB format. (Alpha is ignored). + UnsignedInt m_lifetime; + UnsignedInt m_fadeOutTime; + UnsignedInt m_fadeInTime; + ShadowType m_type; /// type of projection + Real m_decalSizeX; /// 1/(world space extent of texture in x direction) + Real m_decalSizeY; /// 1/(world space extent of texture in y direction) + Real m_angle; + Bool m_orientToObject; + Bool m_randomAngle; + + // spawn parameters + Real m_probability; + // TODO: Height/Surface, etc. +}; +EMPTY_DTOR(TracerFXNugget) + + //------------------------------------------------------------------------------------------------- class LightPulseFXNugget : public FXNugget { diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDecalDraw.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDecalDraw.h new file mode 100644 index 00000000000..994ca0e0a11 --- /dev/null +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDecalDraw.h @@ -0,0 +1,81 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: W3DDecalDraw.h ////////////////////////////////////////////////////////////////////////// +// Author: Andi W, May 26 +// Desc: Draw module for Decal FXNugget +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Common/DrawModule.h" +#include "WW3D2/line3d.h" +#include "W3DDevice/GameClient/W3DShadow.h" + +//------------------------------------------------------------------------------------------------- +class W3DDecalDrawModuleData : public ModuleData +{ +public: + + W3DDecalDrawModuleData(); + ~W3DDecalDrawModuleData(); + static void buildFieldParse(MultiIniFieldParse& p); + // ugh, hack + virtual const W3DDecalDrawModuleData* getAsW3DDecalDrawModuleData() const { return this; } +}; + +//------------------------------------------------------------------------------------------------- +/** W3D prop draw */ +//------------------------------------------------------------------------------------------------- +class W3DDecalDraw : public DrawModule //, public DecalDrawInterface +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( W3DDecalDraw, "W3DDecalDraw" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( W3DDecalDraw, W3DDecalDrawModuleData ) + +public: + + W3DDecalDraw( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + virtual void doDrawModule(const Matrix3D* transformMtx); + virtual void setShadowsEnabled(Bool enable) { } + virtual void releaseShadows(void) {}; ///< we don't care about preserving temporary shadows. + virtual void allocateShadows(void) {}; ///< we don't care about preserving temporary shadows. + virtual void setFullyObscuredByShroud(Bool fullyObscured) { } + virtual void reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle) { } + virtual void reactToGeometryChange() { } + + + //virtual DecalDrawInterface* getDecalDrawInterface() { return this; } + //virtual const DecalDrawInterface* getDecalDrawInterface() const { return this; } + + //virtual void initDecal(AsciiString texture, Real opacity, Int color, ShadowType type, UnsignedInt lifetime, UnsignedInt fadeOutTime, UnsignedInt fadeInTime, Real sizeX, Real sizeY); + + +protected: + Bool m_propAdded; + +}; diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDecalDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDecalDraw.cpp new file mode 100644 index 00000000000..7c25446dfa1 --- /dev/null +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDecalDraw.cpp @@ -0,0 +1,161 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: W3DDecalDraw.cpp //////////////////////////////////////////////////////////////////////// +// Author: Andi W, May 26 +// Desc: Draw module for Decal FXNugget +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include + +#include "Common/Thing.h" +#include "Common/ThingTemplate.h" +#include "Common/Xfer.h" +#include "GameLogic/Object.h" +#include "GameClient/Drawable.h" +#include "W3DDevice/GameClient/Module/W3DDecalDraw.h" +#include "W3DDevice/GameClient/BaseHeightMap.h" + + +//------------------------------------------------------------------------------------------------- +W3DDecalDrawModuleData::W3DDecalDrawModuleData() +{ +} + +//------------------------------------------------------------------------------------------------- +W3DDecalDrawModuleData::~W3DDecalDrawModuleData() +{ +} + +//------------------------------------------------------------------------------------------------- +void W3DDecalDrawModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + ModuleData::buildFieldParse(p); + static const FieldParse dataFieldParse[] = + { + // TODO: Add all the fields here and it will be defined in ini + //{ "ModelName", INI::parseAsciiString, nullptr, offsetof(W3DDecalDrawModuleData, m_modelName) }, + { nullptr, nullptr, nullptr, 0 } + }; + p.add(dataFieldParse); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// +//void W3DDecalDraw::initDecal(AsciiString texture, Real opacity, Int color, ShadowType type, UnsignedInt lifetime, UnsignedInt fadeOutTime, UnsignedInt fadeInTime, Real sizeX, Real sizeY) +//{ +// +//} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +W3DDecalDraw::W3DDecalDraw( Thing *thing, const ModuleData* moduleData ) : DrawModule( thing, moduleData ), +m_propAdded(false) +{ + +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +W3DDecalDraw::~W3DDecalDraw( void ) +{ +} + +//------------------------------------------------------------------------------------------------- +//void W3DDecalDraw::reactToTransformChange( const Matrix3D *oldMtx, +// const Coord3D *oldPos, +// Real oldAngle ) +//{ +// Drawable *draw = getDrawable(); +// if (m_propAdded) { +// return; +// } +// if (draw->getPosition()->x==0.0f && draw->getPosition()->y == 0.0f) { +// return; +// } +// m_propAdded = true; +// const W3DDecalDrawModuleData *moduleData = getW3DDecalDrawModuleData(); +// if (!moduleData) { +// return; +// } +// Real scale = draw->getScale(); +// TheTerrainRenderObject->addProp((Int)draw->getID(), *draw->getPosition(), +// draw->getOrientation(), scale, moduleData->m_modelName); +// +//} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void W3DDecalDraw::doDrawModule(const Matrix3D* transformMtx) +{ + + return; + +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void W3DDecalDraw::crc( Xfer *xfer ) +{ + + // extend base class + DrawModule::crc( xfer ); + +} + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void W3DDecalDraw::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + DrawModule::xfer( xfer ); + + // no data to save here, nobody will ever notice + +} + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void W3DDecalDraw::loadPostProcess( void ) +{ + + // extend base class + DrawModule::loadPostProcess(); + +} diff --git a/GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h b/GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h index becf160ed60..42a56fab1cb 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h @@ -51,6 +51,7 @@ class DebrisDrawInterface; class TracerDrawInterface; class RopeDrawInterface; class LaserDrawInterface; +class DecalDrawInterface; class FXList; enum TerrainDecalType CPP_11(: Int); enum ShadowType CPP_11(: Int); @@ -108,6 +109,9 @@ class DrawModule : public DrawableModule virtual LaserDrawInterface* getLaserDrawInterface() { return nullptr; } virtual const LaserDrawInterface* getLaserDrawInterface() const { return nullptr; } + //virtual DecalDrawInterface* getDecalDrawInterface() { return nullptr; } + //virtual const DecalDrawInterface* getDecalDrawInterface() const { return nullptr; } + }; inline DrawModule::DrawModule( Thing *thing, const ModuleData* moduleData ) : DrawableModule( thing, moduleData ) { } inline DrawModule::~DrawModule() { } @@ -148,6 +152,13 @@ class LaserDrawInterface virtual Real getLaserTemplateWidth() const = 0; }; +//------------------------------------------------------------------------------------------------- +//class DecalDrawInterface +//{ +//public: +// virtual void initDecal(AsciiString texture, Real opacity, Int color, ShadowType type, UnsignedInt lifetime, UnsignedInt fadeOutTime, UnsignedInt fadeInTime, Real sizeX, Real sizeY) = 0; +//}; + //------------------------------------------------------------------------------------------------- class ObjectDrawInterface { From db82dad4eba533504e211852f9682cf35ea35ea2 Mon Sep 17 00:00:00 2001 From: Andi Date: Sat, 16 May 2026 11:40:24 +0200 Subject: [PATCH 5/6] Decal Draw and Recoil fixes --- Core/CMakeLists.txt | 2 +- .../System/GameMemoryInitPools_GeneralsMD.inl | 2 + Core/GameEngine/Source/GameClient/FXList.cpp | 119 ++++++----- Core/GameEngineDevice/CMakeLists.txt | 2 + .../GameClient/Module/W3DDecalDraw.h | 25 ++- .../GameClient/Module/W3DModelDraw.h | 4 +- .../GameClient/Drawable/Draw/W3DDecalDraw.cpp | 186 +++++++++++++++--- .../GameClient/Drawable/Draw/W3DModelDraw.cpp | 38 +++- .../Common/Thing/W3DModuleFactory.cpp | 2 + 9 files changed, 293 insertions(+), 87 deletions(-) diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index fd0adb1f877..7c1269d1db9 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -19,7 +19,7 @@ target_sources(corei_libraries_include PRIVATE Libraries/Include/Lib/trig.h Libraries/Include/rts/debug.h Libraries/Include/rts/profile.h - "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw (2).cpp") +) target_link_libraries(corei_always INTERFACE core_config core_utility diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl index 959a215472a..1ec6dcec40f 100644 --- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl +++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl @@ -334,6 +334,7 @@ static PoolSizeRec PoolSizes[] = { "W3DTankDraw", 256, 32 }, { "W3DTreeDraw", 16, 16 }, { "W3DPropDraw", 16, 16 }, + { "W3DDecalDraw", 16, 16 }, { "W3DTracerDraw", 64, 32 }, { "W3DTruckDraw", 128, 32 }, { "W3DTankTruckDraw", 32, 16 }, @@ -499,6 +500,7 @@ static PoolSizeRec PoolSizes[] = { "GenericObjectCreationNugget", 632, 32 }, { "SoundFXNugget", 320, 32 }, { "TracerFXNugget", 32, 32 }, + { "DecalFXNugget", 32, 32 }, { "RayEffectFXNugget", 32, 32 }, { "LightPulseFXNugget", 68, 32 }, { "ViewShakeFXNugget", 140, 32 }, diff --git a/Core/GameEngine/Source/GameClient/FXList.cpp b/Core/GameEngine/Source/GameClient/FXList.cpp index 7d46ce850ec..e9edb31b60d 100644 --- a/Core/GameEngine/Source/GameClient/FXList.cpp +++ b/Core/GameEngine/Source/GameClient/FXList.cpp @@ -482,16 +482,16 @@ class DecalFXNugget : public FXNugget DecalFXNugget() { m_templateName.set("GenericDecal"); // TODO - m_templateName = AsciiString::TheEmptyString; - m_textureName = AsciiString::TheEmptyString; - m_opacity = 1.0; ///< value between 0 and 1 - m_color = 0; ///< color in ARGB format. (Alpha is ignored). + //m_templateName = AsciiString::TheEmptyString; + //m_textureName = AsciiString::TheEmptyString; + //m_opacity = 1.0; ///< value between 0 and 1 + //m_color = 0; ///< color in ARGB format. (Alpha is ignored). m_lifetime = 0; - m_fadeOutTime = 0; + /* m_fadeOutTime = 0; m_fadeInTime = 0; m_type = 0; /// type of projection m_decalSizeX = 0.0; /// 1/(world space extent of texture in x direction) - m_decalSizeY = 0.0; /// 1/(world space extent of texture in y direction) + m_decalSizeY = 0.0; /// 1/(world space extent of texture in y direction)*/ m_angle = 0.0; m_orientToObject = FALSE; m_randomAngle = FALSE; @@ -500,6 +500,8 @@ class DecalFXNugget : public FXNugget virtual void doFXPos(const Coord3D* primary, const Matrix3D* primaryMtx, const Real primarySpeed, const Coord3D* secondary, const Real /*overrideRadius*/, FXSurfaceInfo* /*surfaceInfo*/) const { + DEBUG_LOG(("DecalFXNugget::doFXPos 1")); + if (m_probability <= GameClientRandomValueReal(0, 1)) return; @@ -524,9 +526,9 @@ class DecalFXNugget : public FXNugget // } //} - Object* obj = drawable->getObject(); - GeometryInfo newGeom(GEOMETRY_BOX, TRUE, 1.0f, m_decalSizeX/2.0f, m_decalSizeY/2.0f); - obj->setGeometryInfo(newGeom); + //Object* obj = drawable->getObject(); + //GeometryInfo newGeom(GEOMETRY_BOX, TRUE, 1.0f, m_decalSizeX/2.0f, m_decalSizeY/2.0f); + //obj->setGeometryInfo(newGeom); if (m_randomAngle) drawable->setOrientation(GameClientRandomValueReal(0, PI * 2)); @@ -534,30 +536,30 @@ class DecalFXNugget : public FXNugget drawable->setExpirationDate(TheGameLogic->getFrame() + m_lifetime); //Set Shadow - for (DrawModule** d = drawable->getDrawModules(); *d; ++d) - { - W3DModelDraw* draw = (W3DModelDraw*) (*d); - if (draw) { - RenderObjClass* robj = draw->getRenderObject(); - - Shadow::ShadowTypeInfo shadowInfo; - strlcpy(shadowInfo.m_ShadowName, m_textureName.str(), ARRAY_SIZE(shadowInfo.m_ShadowName)); - shadowInfo.allowUpdates = FALSE; //shadow image will never update - shadowInfo.allowWorldAlign = TRUE; //shadow image will wrap around world objects - shadowInfo.m_type = m_type; - shadowInfo.m_sizeX = m_decalSizeX; - shadowInfo.m_sizeY = m_decalSizeY; - Shadow* shadow = TheW3DShadowManager->addShadow(robj, &shadowInfo); - //if (shadow) - //{ - // shadow->enableShadowInvisible(m_fullyObscuredByShroud); - // if (m_renderObject->Is_Hidden() || !m_shadowEnabled) - // shadow->enableShadowRender(FALSE); - //} + //for (DrawModule** d = drawable->getDrawModules(); *d; ++d) + //{ + // W3DModelDraw* draw = (W3DModelDraw*) (*d); + // if (draw) { + // RenderObjClass* robj = draw->getRenderObject(); + + // Shadow::ShadowTypeInfo shadowInfo; + // strlcpy(shadowInfo.m_ShadowName, m_textureName.str(), ARRAY_SIZE(shadowInfo.m_ShadowName)); + // shadowInfo.allowUpdates = FALSE; //shadow image will never update + // shadowInfo.allowWorldAlign = TRUE; //shadow image will wrap around world objects + // shadowInfo.m_type = m_type; + // shadowInfo.m_sizeX = m_decalSizeX; + // shadowInfo.m_sizeY = m_decalSizeY; + // Shadow* shadow = TheW3DShadowManager->addShadow(robj, &shadowInfo); + // //if (shadow) + // //{ + // // shadow->enableShadowInvisible(m_fullyObscuredByShroud); + // // if (m_renderObject->Is_Hidden() || !m_shadowEnabled) + // // shadow->enableShadowRender(FALSE); + // //} - } - break; - } + // } + // break; + //} //static const NameKeyType key_draw = NAMEKEY("W3DModelDraw"); //W3DModelDraw* draw = (W3DModelDraw*)obj->findUpdateModule(key_centerUpdate); @@ -570,24 +572,38 @@ class DecalFXNugget : public FXNugget } } + virtual void doFXObj(const Object* primary, const Object* secondary, FXSurfaceInfo* surfaceInfo) const + { + DEBUG_LOG(("DecalFXNugget::doFXObj 1")); + + if (primary) + { + doFXPos(primary->getPosition(), primary->getTransformMatrix(), 0.0f, nullptr, 0.0f, surfaceInfo); + } + else + { + DEBUG_CRASH(("You must have a primary source for this effect")); + } + } + static void parse(INI* ini, void* instance, void* /*store*/, const void* /*userData*/) { static const FieldParse myFieldParse[] = { - { "DecalName", INI::parseAsciiString, nullptr, offsetof(TracerFXNugget, m_tracerName) }, - { "Texture", INI::parseAsciiString, nullptr, offsetof(TracerFXNugget, m_textureName) }, + { "DecalName", INI::parseAsciiString, nullptr, offsetof(DecalFXNugget, m_templateName) }, + /* { "Texture", INI::parseAsciiString, nullptr, offsetof(TracerFXNugget, m_textureName) }, { "Style", INI::parseBitString32, TheShadowNames, offsetof(TracerFXNugget, m_shadowType) }, { "Color", INI::parseColorInt, nullptr, offsetof(TracerFXNugget, m_color) }, - { "Opacity", INI::parsePercentToReal, nullptr, offsetof(TracerFXNugget, m_opacity) }, - { "Lifetime", INI::parseDurationUnsignedInt, nullptr, offsetof(TracerFXNugget, m_lifetime) }, - { "FadeInTime", INI::parseDurationUnsignedInt, nullptr, offsetof(TracerFXNugget, m_fadeInTime) }, + { "Opacity", INI::parsePercentToReal, nullptr, offsetof(TracerFXNugget, m_opacity) },*/ + { "Lifetime", INI::parseDurationUnsignedInt, nullptr, offsetof(DecalFXNugget, m_lifetime) }, + /* { "FadeInTime", INI::parseDurationUnsignedInt, nullptr, offsetof(TracerFXNugget, m_fadeInTime) }, { "FadeOutTime", INI::parseDurationUnsignedInt, nullptr, offsetof(TracerFXNugget, m_fadeOutTime) }, { "SizeX", INI::parseReal, nullptr, offsetof(TracerFXNugget, m_decalSizeX) }, - { "SizeY", INI::parseReal, nullptr, offsetof(TracerFXNugget, m_decalSizeY) }, - { "Angle", INI::parseReal, nullptr, offsetof(TracerFXNugget, m_angle) }, - { "RandomAngle", INI::parseBool, nullptr, offsetof(TracerFXNugget, m_randomAngle) }, - { "OrientToObject", INI::parseBool, nullptr, offsetof(TracerFXNugget, m_orientToObject) }, - { "Probability", INI::parseReal, nullptr, offsetof(TracerFXNugget, m_probability) }, + { "SizeY", INI::parseReal, nullptr, offsetof(TracerFXNugget, m_decalSizeY) },*/ + { "Angle", INI::parseReal, nullptr, offsetof(DecalFXNugget, m_angle) }, + { "RandomAngle", INI::parseBool, nullptr, offsetof(DecalFXNugget, m_randomAngle) }, + { "OrientToObject", INI::parseBool, nullptr, offsetof(DecalFXNugget, m_orientToObject) }, + { "Probability", INI::parseReal, nullptr, offsetof(DecalFXNugget, m_probability) }, { nullptr, nullptr, nullptr, 0 } }; @@ -598,15 +614,15 @@ class DecalFXNugget : public FXNugget private: AsciiString m_templateName; - AsciiString m_textureName; - Real m_opacity; ///< value between 0 and 1 - UnsignedInt m_color; ///< color in ARGB format. (Alpha is ignored). + // AsciiString m_textureName; + // Real m_opacity; ///< value between 0 and 1 + // UnsignedInt m_color; ///< color in ARGB format. (Alpha is ignored). UnsignedInt m_lifetime; - UnsignedInt m_fadeOutTime; - UnsignedInt m_fadeInTime; - ShadowType m_type; /// type of projection - Real m_decalSizeX; /// 1/(world space extent of texture in x direction) - Real m_decalSizeY; /// 1/(world space extent of texture in y direction) + // UnsignedInt m_fadeOutTime; + // UnsignedInt m_fadeInTime; + // ShadowType m_type; /// type of projection + // Real m_decalSizeX; /// 1/(world space extent of texture in x direction) + // Real m_decalSizeY; /// 1/(world space extent of texture in y direction) Real m_angle; Bool m_orientToObject; Bool m_randomAngle; @@ -615,7 +631,7 @@ class DecalFXNugget : public FXNugget Real m_probability; // TODO: Height/Surface, etc. }; -EMPTY_DTOR(TracerFXNugget) +EMPTY_DTOR(DecalFXNugget) //------------------------------------------------------------------------------------------------- @@ -1176,6 +1192,7 @@ static const FieldParse TheFXListFieldParse[] = { "TerrainScorch", TerrainScorchFXNugget::parse, nullptr, 0}, { "ParticleSystem", ParticleSystemFXNugget::parse, nullptr, 0}, { "FXListAtBonePos", FXListAtBonePosFXNugget::parse, nullptr, 0}, + { "Decal", DecalFXNugget::parse, nullptr, 0}, { nullptr, nullptr, nullptr, 0 } }; diff --git a/Core/GameEngineDevice/CMakeLists.txt b/Core/GameEngineDevice/CMakeLists.txt index 25a6583230d..c12bd6ced3c 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -21,6 +21,7 @@ set(GAMEENGINEDEVICE_SRC Include/W3DDevice/GameClient/Module/W3DPoliceCarDraw.h Include/W3DDevice/GameClient/Module/W3DProjectileStreamDraw.h Include/W3DDevice/GameClient/Module/W3DPropDraw.h + Include/W3DDevice/GameClient/Module/W3DDecalDraw.h Include/W3DDevice/GameClient/Module/W3DRopeDraw.h Include/W3DDevice/GameClient/Module/W3DScienceModelDraw.h Include/W3DDevice/GameClient/Module/W3DSupplyDraw.h @@ -110,6 +111,7 @@ set(GAMEENGINEDEVICE_SRC Source/W3DDevice/GameClient/Drawable/Draw/W3DPoliceCarDraw.cpp Source/W3DDevice/GameClient/Drawable/Draw/W3DProjectileStreamDraw.cpp Source/W3DDevice/GameClient/Drawable/Draw/W3DPropDraw.cpp + Source/W3DDevice/GameClient/Drawable/Draw/W3DDecalDraw.cpp Source/W3DDevice/GameClient/Drawable/Draw/W3DRopeDraw.cpp Source/W3DDevice/GameClient/Drawable/Draw/W3DScienceModelDraw.cpp Source/W3DDevice/GameClient/Drawable/Draw/W3DSupplyDraw.cpp diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDecalDraw.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDecalDraw.h index 994ca0e0a11..a7e781bf799 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDecalDraw.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDecalDraw.h @@ -33,12 +33,23 @@ #include "Common/DrawModule.h" #include "WW3D2/line3d.h" #include "W3DDevice/GameClient/W3DShadow.h" +#include "WW3D2/boxrobj.h" //------------------------------------------------------------------------------------------------- class W3DDecalDrawModuleData : public ModuleData { public: + AsciiString m_textureName; + Real m_opacity; ///< value between 0 and 1 + UnsignedInt m_color; ///< color in ARGB format. (Alpha is ignored). + // UnsignedInt m_lifetime; + UnsignedInt m_fadeOutTime; + UnsignedInt m_fadeInTime; + ShadowType m_type; /// type of projection + Real m_decalSizeX; /// 1/(world space extent of texture in x direction) + Real m_decalSizeY; /// 1/(world space extent of texture in y direction) + W3DDecalDrawModuleData(); ~W3DDecalDrawModuleData(); static void buildFieldParse(MultiIniFieldParse& p); @@ -64,8 +75,8 @@ class W3DDecalDraw : public DrawModule //, public DecalDrawInterface virtual void setShadowsEnabled(Bool enable) { } virtual void releaseShadows(void) {}; ///< we don't care about preserving temporary shadows. virtual void allocateShadows(void) {}; ///< we don't care about preserving temporary shadows. - virtual void setFullyObscuredByShroud(Bool fullyObscured) { } - virtual void reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle) { } + virtual void setFullyObscuredByShroud(Bool fullyObscured); + virtual void reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle); virtual void reactToGeometryChange() { } @@ -76,6 +87,14 @@ class W3DDecalDraw : public DrawModule //, public DecalDrawInterface protected: - Bool m_propAdded; + //Bool m_propAdded; + + OBBoxRenderObjClass* m_renderBox; // The render object + Shadow* m_shadow; // the decal + Bool m_fullyObscuredByShroud; + UnsignedInt m_frameCreated; +private: + void init_shadow(); + void init_renderBox(const Matrix3D* transformMtx); }; diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h index 2055338a722..cdbdf119471 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h @@ -332,6 +332,8 @@ class W3DModelDrawModuleData : public ModuleData Bool m_showForOwnerOnly; ///< show this model only to the owning player + Bool m_keepRecoilAcrossStates; ///< Don't reset recoil bones when switching states + // Bool m_disableMoveEffectsOverWater; ///< disable track marks and tread/wheel anims over water W3DModelDrawModuleData(); @@ -550,7 +552,7 @@ class W3DModelDraw : public DrawModule, public ObjectDrawInterface Real getCurrentAnimFraction() const; void applyCorrectModelStateAnimation(); const ModelConditionInfo* findTransitionForSig(TransitionSig sig) const; - void rebuildWeaponRecoilInfo(const ModelConditionInfo* state); + void rebuildWeaponRecoilInfo(const ModelConditionInfo* state, bool clear = TRUE); void doHideShowProjectileObjects( UnsignedInt showCount, UnsignedInt maxCount, WeaponSlotType slot );///< Means effectively, show m of n. void nukeCurrentRender(Matrix3D* xform); void doStartOrStopParticleSys(); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDecalDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDecalDraw.cpp index 7c25446dfa1..5e06850a625 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDecalDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDecalDraw.cpp @@ -30,14 +30,20 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include +#define DEFINE_SHADOW_NAMES + #include "Common/Thing.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "GameLogic/Object.h" #include "GameClient/Drawable.h" +#include "GameClient/Shadow.h" +#include "GameLogic/GameLogic.h" +#include "W3DDevice/GameClient/W3DDisplay.h" #include "W3DDevice/GameClient/Module/W3DDecalDraw.h" -#include "W3DDevice/GameClient/BaseHeightMap.h" - +#include "W3DDevice/GameClient/W3DProjectedShadow.h" +//#include "W3DDevice/GameClient/BaseHeightMap.h" +#include "W3DDevice/GameClient/W3DScene.h" //------------------------------------------------------------------------------------------------- W3DDecalDrawModuleData::W3DDecalDrawModuleData() @@ -55,8 +61,14 @@ void W3DDecalDrawModuleData::buildFieldParse(MultiIniFieldParse& p) ModuleData::buildFieldParse(p); static const FieldParse dataFieldParse[] = { - // TODO: Add all the fields here and it will be defined in ini - //{ "ModelName", INI::parseAsciiString, nullptr, offsetof(W3DDecalDrawModuleData, m_modelName) }, + { "Texture", INI::parseAsciiString, nullptr, offsetof(W3DDecalDrawModuleData, m_textureName) }, + { "Color", INI::parseColorInt, nullptr, offsetof(W3DDecalDrawModuleData, m_color) }, + { "Opacity", INI::parsePercentToReal, nullptr, offsetof(W3DDecalDrawModuleData, m_opacity) }, + { "Style", INI::parseBitString32, TheShadowNames, offsetof(W3DDecalDrawModuleData, m_type) }, + { "FadeOutTime", INI::parseDurationUnsignedInt, nullptr, offsetof(W3DDecalDrawModuleData, m_fadeOutTime) }, + { "FadeInTime", INI::parseDurationUnsignedInt, nullptr, offsetof(W3DDecalDrawModuleData, m_fadeInTime) }, + { "SizeX", INI::parseReal, nullptr, offsetof(W3DDecalDrawModuleData, m_decalSizeX) }, + { "SizeY", INI::parseReal, nullptr, offsetof(W3DDecalDrawModuleData, m_decalSizeY) }, { nullptr, nullptr, nullptr, 0 } }; p.add(dataFieldParse); @@ -73,10 +85,16 @@ void W3DDecalDrawModuleData::buildFieldParse(MultiIniFieldParse& p) //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- -W3DDecalDraw::W3DDecalDraw( Thing *thing, const ModuleData* moduleData ) : DrawModule( thing, moduleData ), -m_propAdded(false) +W3DDecalDraw::W3DDecalDraw( Thing *thing, const ModuleData* moduleData ) : DrawModule( thing, moduleData ) { + //Drawable* draw = getDrawable(); + //const W3DDecalDrawModuleData* data = getW3DDecalDrawModuleData(); + // draw->getExpirationDate(); + m_fullyObscuredByShroud = false; + m_renderBox = nullptr; + m_shadow = nullptr; + m_frameCreated = 0; } @@ -84,35 +102,153 @@ m_propAdded(false) //------------------------------------------------------------------------------------------------- W3DDecalDraw::~W3DDecalDraw( void ) { + if (m_shadow) + m_shadow->release(); + m_shadow = nullptr; + + if (m_renderBox) { + if (W3DDisplay::m_3DScene != nullptr) + W3DDisplay::m_3DScene->Remove_Render_Object(m_renderBox); + REF_PTR_RELEASE(m_renderBox); + m_renderBox = nullptr; + } + +} +//------------------------------------------------------------------------------------------------- +void W3DDecalDraw::init_shadow() +{ + const W3DDecalDrawModuleData* data = getW3DDecalDrawModuleData(); + + Shadow::ShadowTypeInfo shadowInfo; + strlcpy(shadowInfo.m_ShadowName, data->m_textureName.str(), ARRAY_SIZE(shadowInfo.m_ShadowName)); + shadowInfo.allowUpdates = FALSE; //shadow image will never update + shadowInfo.allowWorldAlign = TRUE; //shadow image will wrap around world objects + shadowInfo.m_type = data->m_type; + shadowInfo.m_sizeX = data->m_decalSizeX; + shadowInfo.m_sizeY = data->m_decalSizeY; + shadowInfo.m_offsetX = 0.0f; // TODO + shadowInfo.m_offsetY = 0.0f; // TODO + //shadowInfo.m_hasDynamicLength = FALSE; + + DEBUG_ASSERTCRASH(m_shadow == nullptr, ("m_shadow is not null")); + + //Drawable* draw = getDrawable(); + + if (TheProjectedShadowManager) + m_shadow = TheProjectedShadowManager->addDecal(m_renderBox, &shadowInfo); + if (m_shadow) + { + m_shadow->setColor(data->m_color); + m_shadow->setOpacity(REAL_TO_INT(data->m_opacity * 255.0f)); + m_shadow->enableShadowInvisible(m_fullyObscuredByShroud); + m_shadow->enableShadowRender(true); + } +} +//------------------------------------------------------------------------------------------------- +void W3DDecalDraw::init_renderBox(const Matrix3D* transformMtx) +{ + const W3DDecalDrawModuleData* data = getW3DDecalDrawModuleData(); + + Vector3 center = { 0, 0, 0 }; + Vector3 extent = { data->m_decalSizeX, data->m_decalSizeY, 1.0f }; + + m_renderBox = NEW OBBoxRenderObjClass( + OBBoxClass(center, extent) + ); + + if (W3DDisplay::m_3DScene != nullptr) + W3DDisplay::m_3DScene->Add_Render_Object(m_renderBox); + m_renderBox->Set_Transform(*transformMtx); + } //------------------------------------------------------------------------------------------------- -//void W3DDecalDraw::reactToTransformChange( const Matrix3D *oldMtx, -// const Coord3D *oldPos, -// Real oldAngle ) +void W3DDecalDraw::setFullyObscuredByShroud(Bool fullyObscured) +{ + if (m_fullyObscuredByShroud != fullyObscured) + { + m_fullyObscuredByShroud = fullyObscured; + + if (m_shadow) + m_shadow->enableShadowInvisible(m_fullyObscuredByShroud); + } +} + +//void W3DDecalDraw::setShadowsEnabled(Bool enable) //{ -// Drawable *draw = getDrawable(); -// if (m_propAdded) { -// return; -// } -// if (draw->getPosition()->x==0.0f && draw->getPosition()->y == 0.0f) { -// return; -// } -// m_propAdded = true; -// const W3DDecalDrawModuleData *moduleData = getW3DDecalDrawModuleData(); -// if (!moduleData) { -// return; -// } -// Real scale = draw->getScale(); -// TheTerrainRenderObject->addProp((Int)draw->getID(), *draw->getPosition(), -// draw->getOrientation(), scale, moduleData->m_modelName); -// +// if (m_shadow) +// m_shadow->enableShadowRender(enable); +// m_shadowEnabled = enable; //} +//------------------------------------------------------------------------------------------------- +void W3DDecalDraw::reactToTransformChange( const Matrix3D *oldMtx, + const Coord3D *oldPos, + Real oldAngle ) +{ + if (m_renderBox) + { + m_renderBox->Set_Transform(*getDrawable()->getTransformMatrix()); + } + +} + //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void W3DDecalDraw::doDrawModule(const Matrix3D* transformMtx) { + //DEBUG_LOG(("W3DDecalDraw::doDrawModule 1")); + + if (m_renderBox == nullptr) { + + init_renderBox(transformMtx); + + init_shadow(); + } + + if (m_shadow == nullptr) { + init_shadow(); + } + + if (m_renderBox) + { + Matrix3D mtx = *transformMtx; + m_renderBox->Set_Transform(mtx); + } + + // Update decal opacity from lifetime + const W3DDecalDrawModuleData* data = getW3DDecalDrawModuleData(); + UnsignedInt expDate = getDrawable()->getExpirationDate(); + + Real opacity = data->m_opacity; + + UnsignedInt now = TheGameLogic->getFrame(); + if (m_frameCreated == 0) + m_frameCreated = now; + + if (data->m_fadeInTime > 0) { + opacity = INT_TO_REAL(now - m_frameCreated) / INT_TO_REAL(data->m_fadeInTime); + if (opacity > 1.0f) + opacity = 1.0f; + } + + if (data->m_fadeOutTime > 0 && expDate != 0) { + opacity *= INT_TO_REAL(expDate - now) / INT_TO_REAL(data->m_fadeOutTime); + if (opacity > 1.0f) + opacity = 1.0f; + else if (opacity < 0.0f) + opacity = 0.0f; + } + + if (opacity != data->m_opacity) + m_shadow->setOpacity(REAL_TO_INT(opacity * 255.0f)); + + //if (expDate != 0) + //{ + // Real decay = m_opacity / (expDate - TheGameLogic->getFrame()); + // m_opacity -= decay; + // m_theTracer->Set_Opacity(m_opacity); + //} return; diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp index d1cf23a9e25..5f594c7702c 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp @@ -1075,6 +1075,7 @@ W3DModelDrawModuleData::W3DModelDrawModuleData() : m_ignoreAnimScaling = FALSE; m_ignoreRotation = FALSE; m_showForOwnerOnly = FALSE; + m_keepRecoilAcrossStates = FALSE; // m_ignoreConditionStates defaults to all zero, which is what we want } @@ -1254,6 +1255,7 @@ void W3DModelDrawModuleData::buildFieldParse(MultiIniFieldParse& p) { "IgnoreAnimationSpeedScaling", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_ignoreAnimScaling) }, { "IgnoreRotation", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_ignoreRotation) }, { "OnlyVisibleToOwningPlayer", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_showForOwnerOnly) }, + { "KeepRecoilAcrossStates", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_keepRecoilAcrossStates) }, //{ "DisableMovementEffectsOverWater", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_disableMoveEffectsOverWater) }, { nullptr, nullptr, nullptr, 0 } }; @@ -2186,6 +2188,12 @@ void W3DModelDraw::doDrawModule(const Matrix3D* transformMtx) { if (m_curState != nullptr && m_nextState != nullptr) { + + if (stricmp(m_curState->m_modelName.str(), "avjug_deploy") == 0) { + int i = 0; + i += 1; + } + //DEBUG_LOG(("transition %s is complete",m_curState->m_description.str())); const ModelConditionInfo* nextState = m_nextState; UnsignedInt nextDuration = m_nextStateAnimLoopDuration; @@ -2722,6 +2730,11 @@ void W3DModelDraw::handleClientRecoil() // do recoil, if any for (int wslot = 0; wslot < WEAPONSLOT_COUNT; ++wslot) { + if (wslot == 0 && stricmp(m_curState->m_modelName.str(), "avjug_deploy") == 0) { + int i = 0; + i += 1; + } + if (!m_curState->m_hasRecoilBonesOrMuzzleFlashes[wslot]) continue; @@ -3270,6 +3283,12 @@ void W3DModelDraw::setModelState(const ModelConditionInfo* newState) const ModelConditionInfo* nextState = nullptr; if (m_curState != nullptr && newState != nullptr) { + + if (stricmp(m_curState->m_modelName.str(), "avjug_deploy") == 0) { + int i = 0; + i += 1; + } + // if the requested state is the current state (and nothing is pending), // or if the requested state is pending, just punt. // @@ -3391,7 +3410,10 @@ void W3DModelDraw::setModelState(const ModelConditionInfo* newState) newState->validateStuff(m_renderObject, draw->getScale(), getW3DModelDrawModuleData()->m_extraPublicBones); // ensure that any muzzle flashes from the *new* state, start out hidden... // hideAllMuzzleFlashes(newState, m_renderObject);//moved to above - rebuildWeaponRecoilInfo(newState); + + //if (!getW3DModelDrawModuleData()->m_keepRecoilAcrossStates) + rebuildWeaponRecoilInfo(newState, !getW3DModelDrawModuleData()->m_keepRecoilAcrossStates); + doHideShowSubObjs(&newState->m_hideShowVec); #if defined(RTS_DEBUG) //art wants to see buildings without flags as a test. @@ -3519,7 +3541,9 @@ void W3DModelDraw::setModelState(const ModelConditionInfo* newState) //BONEPOS_DUMPREAL(getDrawable()->getScale()); newState->validateStuff(m_renderObject, getDrawable()->getScale(), getW3DModelDrawModuleData()->m_extraPublicBones); - rebuildWeaponRecoilInfo(newState); + + //if (!getW3DModelDrawModuleData()->m_keepRecoilAcrossStates) + rebuildWeaponRecoilInfo(newState, !getW3DModelDrawModuleData()->m_keepRecoilAcrossStates); // ensure that any muzzle flashes from the *previous* state, are hidden... // hideAllMuzzleFlashes(m_curState, m_renderObject);// moved to above @@ -4296,7 +4320,7 @@ Real W3DModelDraw::getAnimationScrubScalar( void ) const #endif //------------------------------------------------------------------------------------------------- -void W3DModelDraw::rebuildWeaponRecoilInfo(const ModelConditionInfo* state) +void W3DModelDraw::rebuildWeaponRecoilInfo(const ModelConditionInfo* state, bool clear /*=TRUE*/) { Int wslot; @@ -4318,9 +4342,11 @@ void W3DModelDraw::rebuildWeaponRecoilInfo(const ModelConditionInfo* state) m_weaponRecoilInfoVec[wslot].resize(ncount, tmp); } - for (WeaponRecoilInfoVec::iterator it = m_weaponRecoilInfoVec[wslot].begin(); it != m_weaponRecoilInfoVec[wslot].end(); ++it) - { - it->clear(); + if (clear) { + for (WeaponRecoilInfoVec::iterator it = m_weaponRecoilInfoVec[wslot].begin(); it != m_weaponRecoilInfoVec[wslot].end(); ++it) + { + it->clear(); + } } } } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/Common/Thing/W3DModuleFactory.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/Common/Thing/W3DModuleFactory.cpp index f4f1c61337f..9e60ddfe57a 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/Common/Thing/W3DModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/Common/Thing/W3DModuleFactory.cpp @@ -48,6 +48,7 @@ #include "W3DDevice/GameClient/Module/W3DTracerDraw.h" #include "W3DDevice/GameClient/Module/W3DTreeDraw.h" #include "W3DDevice/GameClient/Module/W3DPropDraw.h" +#include "W3DDevice/GameClient/Module/W3DDecalDraw.h" #include "W3DDevice/GameClient/Module/W3DDependencyCarrierDraw.h" //------------------------------------------------------------------------------------------------- @@ -79,6 +80,7 @@ void W3DModuleFactory::init( void ) addModule( W3DTankTruckDraw ); addModule( W3DTreeDraw ); addModule( W3DPropDraw ); + addModule( W3DDecalDraw ); addModule( W3DDependencyCarrierDraw ); } From ea14059bd6323803dbc27eca447a408515545a9e Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 17 May 2026 18:02:04 +0200 Subject: [PATCH 6/6] minor fixes and cleanup --- Core/GameEngine/Source/GameClient/FXList.cpp | 83 +++-------- .../GameClient/Module/W3DWalkerDraw.h | 64 --------- .../GameClient/Drawable/Draw/W3DModelDraw.cpp | 79 +---------- .../Drawable/Draw/W3DWalkerDraw.cpp | 118 ---------------- .../Module/AnimationSteeringUpdate.h | 2 + .../Object/Update/AnimationSteeringUpdate.cpp | 131 +++++++++++------- 6 files changed, 104 insertions(+), 373 deletions(-) delete mode 100644 Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DWalkerDraw.h delete mode 100644 Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DWalkerDraw.cpp diff --git a/Core/GameEngine/Source/GameClient/FXList.cpp b/Core/GameEngine/Source/GameClient/FXList.cpp index e9edb31b60d..e711c0ed60c 100644 --- a/Core/GameEngine/Source/GameClient/FXList.cpp +++ b/Core/GameEngine/Source/GameClient/FXList.cpp @@ -492,6 +492,7 @@ class DecalFXNugget : public FXNugget m_type = 0; /// type of projection m_decalSizeX = 0.0; /// 1/(world space extent of texture in x direction) m_decalSizeY = 0.0; /// 1/(world space extent of texture in y direction)*/ + m_offset.x = m_offset.y = m_offset.z = 0; m_angle = 0.0; m_orientToObject = FALSE; m_randomAngle = FALSE; @@ -500,13 +501,19 @@ class DecalFXNugget : public FXNugget virtual void doFXPos(const Coord3D* primary, const Matrix3D* primaryMtx, const Real primarySpeed, const Coord3D* secondary, const Real /*overrideRadius*/, FXSurfaceInfo* /*surfaceInfo*/) const { - DEBUG_LOG(("DecalFXNugget::doFXPos 1")); - if (m_probability <= GameClientRandomValueReal(0, 1)) return; if (primary) { + Coord3D offset = m_offset; + if (primaryMtx) { + if (m_orientToObject) + { + adjustVector(&offset, primaryMtx); + } + } + Drawable* drawable = TheThingFactory->newDrawable(TheThingFactory->findTemplate(m_templateName)); if (!drawable) return; @@ -514,57 +521,17 @@ class DecalFXNugget : public FXNugget // Does it even make sense to set the matrix? if (primaryMtx && m_orientToObject) drawable->setTransformMatrix(primaryMtx); - drawable->setPosition(primary); - - //TODO: - //TracerDrawInterface* tdi = nullptr; - //for (DrawModule** d = drawable->getDrawModules(); *d; ++d) - //{ - // if ((tdi = (*d)->getTracerDrawInterface()) != nullptr) - // { - // tdi->setTracerParms(speed, m_length, m_width, m_color, 1.0f); - // } - //} - - //Object* obj = drawable->getObject(); - //GeometryInfo newGeom(GEOMETRY_BOX, TRUE, 1.0f, m_decalSizeX/2.0f, m_decalSizeY/2.0f); - //obj->setGeometryInfo(newGeom); + + Coord3D newPos; + newPos.x = primary->x + offset.x; + newPos.y = primary->y + offset.y; + newPos.z = primary->z + offset.z; + drawable->setPosition(&newPos); if (m_randomAngle) drawable->setOrientation(GameClientRandomValueReal(0, PI * 2)); drawable->setExpirationDate(TheGameLogic->getFrame() + m_lifetime); - - //Set Shadow - //for (DrawModule** d = drawable->getDrawModules(); *d; ++d) - //{ - // W3DModelDraw* draw = (W3DModelDraw*) (*d); - // if (draw) { - // RenderObjClass* robj = draw->getRenderObject(); - - // Shadow::ShadowTypeInfo shadowInfo; - // strlcpy(shadowInfo.m_ShadowName, m_textureName.str(), ARRAY_SIZE(shadowInfo.m_ShadowName)); - // shadowInfo.allowUpdates = FALSE; //shadow image will never update - // shadowInfo.allowWorldAlign = TRUE; //shadow image will wrap around world objects - // shadowInfo.m_type = m_type; - // shadowInfo.m_sizeX = m_decalSizeX; - // shadowInfo.m_sizeY = m_decalSizeY; - // Shadow* shadow = TheW3DShadowManager->addShadow(robj, &shadowInfo); - // //if (shadow) - // //{ - // // shadow->enableShadowInvisible(m_fullyObscuredByShroud); - // // if (m_renderObject->Is_Hidden() || !m_shadowEnabled) - // // shadow->enableShadowRender(FALSE); - // //} - - // } - // break; - //} - - //static const NameKeyType key_draw = NAMEKEY("W3DModelDraw"); - //W3DModelDraw* draw = (W3DModelDraw*)obj->findUpdateModule(key_centerUpdate); - //if (centerModule) - } else { @@ -574,8 +541,6 @@ class DecalFXNugget : public FXNugget virtual void doFXObj(const Object* primary, const Object* secondary, FXSurfaceInfo* surfaceInfo) const { - DEBUG_LOG(("DecalFXNugget::doFXObj 1")); - if (primary) { doFXPos(primary->getPosition(), primary->getTransformMatrix(), 0.0f, nullptr, 0.0f, surfaceInfo); @@ -591,15 +556,8 @@ class DecalFXNugget : public FXNugget static const FieldParse myFieldParse[] = { { "DecalName", INI::parseAsciiString, nullptr, offsetof(DecalFXNugget, m_templateName) }, - /* { "Texture", INI::parseAsciiString, nullptr, offsetof(TracerFXNugget, m_textureName) }, - { "Style", INI::parseBitString32, TheShadowNames, offsetof(TracerFXNugget, m_shadowType) }, - { "Color", INI::parseColorInt, nullptr, offsetof(TracerFXNugget, m_color) }, - { "Opacity", INI::parsePercentToReal, nullptr, offsetof(TracerFXNugget, m_opacity) },*/ { "Lifetime", INI::parseDurationUnsignedInt, nullptr, offsetof(DecalFXNugget, m_lifetime) }, - /* { "FadeInTime", INI::parseDurationUnsignedInt, nullptr, offsetof(TracerFXNugget, m_fadeInTime) }, - { "FadeOutTime", INI::parseDurationUnsignedInt, nullptr, offsetof(TracerFXNugget, m_fadeOutTime) }, - { "SizeX", INI::parseReal, nullptr, offsetof(TracerFXNugget, m_decalSizeX) }, - { "SizeY", INI::parseReal, nullptr, offsetof(TracerFXNugget, m_decalSizeY) },*/ + { "Offset", INI::parseCoord3D, nullptr, offsetof(DecalFXNugget, m_offset) }, { "Angle", INI::parseReal, nullptr, offsetof(DecalFXNugget, m_angle) }, { "RandomAngle", INI::parseBool, nullptr, offsetof(DecalFXNugget, m_randomAngle) }, { "OrientToObject", INI::parseBool, nullptr, offsetof(DecalFXNugget, m_orientToObject) }, @@ -614,15 +572,8 @@ class DecalFXNugget : public FXNugget private: AsciiString m_templateName; - // AsciiString m_textureName; - // Real m_opacity; ///< value between 0 and 1 - // UnsignedInt m_color; ///< color in ARGB format. (Alpha is ignored). UnsignedInt m_lifetime; - // UnsignedInt m_fadeOutTime; - // UnsignedInt m_fadeInTime; - // ShadowType m_type; /// type of projection - // Real m_decalSizeX; /// 1/(world space extent of texture in x direction) - // Real m_decalSizeY; /// 1/(world space extent of texture in y direction) + Coord3D m_offset; Real m_angle; Bool m_orientToObject; Bool m_randomAngle; diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DWalkerDraw.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DWalkerDraw.h deleted file mode 100644 index 6740c4cdc98..00000000000 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DWalkerDraw.h +++ /dev/null @@ -1,64 +0,0 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: W3DWalkerDraw.h //////////////////////////////////////////////////////////////////////////// -// Author: Andi W, May 26 -// Desc: TODO -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "W3DDevice/GameClient/Module/W3DModelDraw.h" - - -//------------------------------------------------------------------------------------------------- -class W3DWalkerDrawModuleData : public W3DModelDrawModuleData -{ -public: - - W3DWalkerDrawModuleData(); - ~W3DWalkerDrawModuleData(); - static void buildFieldParse(MultiIniFieldParse& p); - -protected: - virtual void setAnimationBlendTime(void* instance, UnsignedInt value); - -}; - -//------------------------------------------------------------------------------------------------- -class W3DWalkerDraw : public W3DModelDraw -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( W3DWalkerDraw, "W3DWalkerDraw" ) - MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( W3DWalkerDraw, W3DWalkerDrawModuleData ) - -public: - - W3DWalkerDraw( Thing *thing, const ModuleData* moduleData ); - // virtual destructor prototype provided by memory pool declaration - virtual void doDrawModule(const Matrix3D* transformMtx);///< checks a property on the local player before passing this up - -protected: -}; diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp index 5f594c7702c..5c78e58a90a 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp @@ -2400,7 +2400,7 @@ void W3DModelDraw::adjustAnimation(const ModelConditionInfo* prevState, Real pre prevState->m_animations[0].getAnimHandle() && m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD) { - DEBUG_LOG((">>>>BLEND ANIMS!!")); + //DEBUG_LOG((">>>>BLEND ANIMS!!")); HLodClass* hlod = (HLodClass*)m_renderObject; @@ -2977,31 +2977,15 @@ void W3DModelDraw::handleFXEvents() for (std::vector::const_iterator it = m_curState->m_fxEvents.begin(); it != m_curState->m_fxEvents.end(); ++it) { // Check frame timing + // TODO: Make this work for backwards animations as well! if (it->frame <= curAnimHelper.frameNum && it->frame > m_prevAnimHelper.frameNum) { // Get bone position Coord3D pos; pos.zero(); - //Real rotation = 0.0f; - Int boneIndex = m_renderObject ? m_renderObject->Get_Bone_Index(it->boneName.str()) : 0; if (boneIndex != 0) { - //// ugh... kill the mtx so we get it in modelspace, not world space - //Matrix3D originalTransform = m_renderObject->Get_Transform(); // save the transform - //Matrix3D tmp(true); - //tmp.Scale(getDrawable()->getScale()); - //m_renderObject->Set_Transform(tmp); // set to identity transform - - //const Matrix3D boneTransform = m_renderObject->Get_Bone_Transform(boneIndex); - //Vector3 vpos = boneTransform.Get_Translation(); - //rotation = boneTransform.Get_Z_Rotation(); - - //m_renderObject->Set_Transform(originalTransform); // restore it - - //pos.x = vpos.X; - //pos.y = vpos.Y; - //pos.z = vpos.Z; - + if (!m_renderObject->Is_Hidden()) { // I can ask the drawable's bone position if I am not hidden (if I have no object I have no choice) @@ -3011,7 +2995,7 @@ void W3DModelDraw::handleFXEvents() pos.y = mtx.Get_Y_Translation(); pos.z = mtx.Get_Z_Translation(); - DEBUG_LOG((">>> FXEVENT: CurrentFrame = %d, PrevFrame = %d, Frame=%d, Bone=%s, FXList=%s, X/Y/Z = %f/%f/f", + /*DEBUG_LOG((">>> FXEVENT: CurrentFrame = %d, PrevFrame = %d, Frame=%d, Bone=%s, FXList=%s, X/Y/Z = %f/%f/f", curAnimHelper.frameNum, m_prevAnimHelper.frameNum, it->frame, @@ -3020,65 +3004,12 @@ void W3DModelDraw::handleFXEvents() pos.x, pos.y, pos.z - )); + ));*/ FXList::doFXPos(it->fx, &pos, &mtx, 0.0f, nullptr, 0.0f); } } } - - //ParticleSystem* sys = TheParticleSystemManager->createParticleSystem(it->particleSystemTemplate); - //if (sys != nullptr) - //{ - // Coord3D pos; - // pos.zero(); - // Real rotation = 0.0f; - - // Int boneIndex = m_renderObject ? m_renderObject->Get_Bone_Index(it->boneName.str()) : 0; - // if (boneIndex != 0) - // { - // // ugh... kill the mtx so we get it in modelspace, not world space - // Matrix3D originalTransform = m_renderObject->Get_Transform(); // save the transform - // Matrix3D tmp(true); - // tmp.Scale(getDrawable()->getScale()); - // m_renderObject->Set_Transform(tmp); // set to identity transform - - // const Matrix3D boneTransform = m_renderObject->Get_Bone_Transform(boneIndex); - // Vector3 vpos = boneTransform.Get_Translation(); - // rotation = boneTransform.Get_Z_Rotation(); - - // m_renderObject->Set_Transform(originalTransform); // restore it - - // pos.x = vpos.X; - // pos.y = vpos.Y; - // pos.z = vpos.Z; - // } - - // // got the bone position... - // sys->setPosition(&pos); - - // // and the direction, so that the system is oriented like the bone is - // sys->rotateLocalTransformZ(rotation); - - // // Attach it to the object... - // sys->attachToDrawable(drawable); - - // // important: mark it as do-not-save, since we'll just re-create it when we reload. - // sys->setSaveable(FALSE); - - // if (drawable->isDrawableEffectivelyHidden() || m_fullyObscuredByShroud) - // { - // sys->stop(); // don't start the systems for drawables that are hidden. - // } - - // // store the particle system id so we can kill it later. - - // ParticleSysTrackerType tracker; - // tracker.id = sys->getSystemID(); - // tracker.boneIndex = boneIndex; - - // m_particleSystemIDs.push_back(tracker); - //} } } } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DWalkerDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DWalkerDraw.cpp deleted file mode 100644 index db24b2d0002..00000000000 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DWalkerDraw.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: W3DWalkerDraw.cpp //////////////////////////////////////////////////////////////////////////// -// Author: Andi W, May 26 -// Desc: TODO -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#include "W3DDevice/GameClient/Module/W3DWalkerDraw.h" - -#include "Common/GameUtility.h" -#include "Common/Player.h" -#include "Common/Xfer.h" - -//------------------------------------------------------------------------------------------------- -W3DWalkerDrawModuleData::W3DWalkerDrawModuleData() -{ -} - -//------------------------------------------------------------------------------------------------- -W3DWalkerDrawModuleData::~W3DWalkerDrawModuleData() -{ -} - -//------------------------------------------------------------------------------------------------- -void W3DWalkerDrawModuleData::buildFieldParse(MultiIniFieldParse& p) -{ - W3DModelDrawModuleData::buildFieldParse(p); - - static const FieldParse dataFieldParse[] = - { - //{ "RequiredScience", INI::parseScience, nullptr, offsetof(W3DWalkerDrawModuleData, m_requiredScience) }, - - { nullptr, nullptr, nullptr, 0 } - }; - p.add(dataFieldParse); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -W3DWalkerDraw::W3DWalkerDraw( Thing *thing, const ModuleData* moduleData ) : W3DModelDraw( thing, moduleData ) -{ -} - -//------------------------------------------------------------------------------------------------- -W3DWalkerDraw::~W3DWalkerDraw() -{ -} - -//------------------------------------------------------------------------------------------------- -// All this does is stop the call path if we haven't been cleared to draw yet -void W3DWalkerDraw::doDrawModule(const Matrix3D* transformMtx) -{ - W3DModelDraw::doDrawModule(transformMtx); -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void W3DWalkerDraw::crc( Xfer *xfer ) -{ - - // extend base class - W3DModelDraw::crc( xfer ); - -} - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void W3DWalkerDraw::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // extend base class - W3DModelDraw::xfer( xfer ); - -} - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void W3DWalkerDraw::loadPostProcess( void ) -{ - - // extend base class - W3DModelDraw::loadPostProcess(); - -} - - diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AnimationSteeringUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AnimationSteeringUpdate.h index a7508586ad7..222f6d21453 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AnimationSteeringUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AnimationSteeringUpdate.h @@ -51,6 +51,7 @@ class AnimationSteeringUpdateModuleData : public UpdateModuleData { { "MinTransitionTime", INI::parseDurationUnsignedInt, nullptr, offsetof( AnimationSteeringUpdateModuleData, m_transitionFrames ) }, { "MinAngle", INI::parseAngleReal, nullptr, offsetof( AnimationSteeringUpdateModuleData, m_minAngle ) }, + { "SkipCenteringAnims", INI::parseBool, nullptr, offsetof( AnimationSteeringUpdateModuleData, m_skipCenteringAnims ) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -59,6 +60,7 @@ class AnimationSteeringUpdateModuleData : public UpdateModuleData UnsignedInt m_transitionFrames; Real m_minAngle; + Bool m_skipCenteringAnims; }; //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp index 6ea769bd9bc..16b91435b89 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp @@ -45,6 +45,8 @@ AnimationSteeringUpdateModuleData::AnimationSteeringUpdateModuleData( void ) { m_transitionFrames = 0; + m_minAngle = 0.0f; + m_skipCenteringAnims = false; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -101,58 +103,85 @@ UpdateSleepTime AnimationSteeringUpdate::update( void ) } } - switch( m_currentTurnAnim ) - { - case MODELCONDITION_INVALID: - //We're currently going straight. Check if we want to turn. - if( currentTurn == TURN_NEGATIVE ) - { - //Initiate a right turn - draw->setModelConditionState( MODELCONDITION_CENTER_TO_RIGHT ); - m_nextTransitionFrame = now + data->m_transitionFrames; - m_currentTurnAnim = MODELCONDITION_CENTER_TO_RIGHT; - } - else if( currentTurn == TURN_POSITIVE ) - { - //Initiate a left turn - draw->setModelConditionState( MODELCONDITION_CENTER_TO_LEFT ); - m_nextTransitionFrame = now + data->m_transitionFrames; - m_currentTurnAnim = MODELCONDITION_CENTER_TO_LEFT; - } - break; - case MODELCONDITION_CENTER_TO_RIGHT: - //We're currently initiating a turn to the right. The only thing - //we can do go back to center or maintain the turn. - if( currentTurn != TURN_NEGATIVE ) - { - //Recenter! - draw->clearAndSetModelConditionState( MODELCONDITION_CENTER_TO_RIGHT, MODELCONDITION_RIGHT_TO_CENTER ); - m_nextTransitionFrame = now + data->m_transitionFrames; - m_currentTurnAnim = MODELCONDITION_RIGHT_TO_CENTER; - } - break; - case MODELCONDITION_CENTER_TO_LEFT: - //We're currently initiating a turn to the left. The only thing - //we can do go back to center or maintain the turn. - if( currentTurn != TURN_POSITIVE ) - { - //Recenter! - draw->clearAndSetModelConditionState( MODELCONDITION_CENTER_TO_LEFT, MODELCONDITION_LEFT_TO_CENTER ); - m_nextTransitionFrame = now + data->m_transitionFrames; - m_currentTurnAnim = MODELCONDITION_LEFT_TO_CENTER; - } - break; - case MODELCONDITION_LEFT_TO_CENTER: - case MODELCONDITION_RIGHT_TO_CENTER: - if( currentTurn == TURN_NONE ) - { - //Finish the turn - draw->clearModelConditionFlags( MAKE_MODELCONDITION_MASK2( MODELCONDITION_LEFT_TO_CENTER, MODELCONDITION_RIGHT_TO_CENTER ) ); - m_nextTransitionFrame = now; - m_currentTurnAnim = MODELCONDITION_INVALID; - } - break; + if (data->m_skipCenteringAnims) { // Simplified version + if (currentTurn == TURN_NEGATIVE) + { + //Initiate a right turn + draw->clearAndSetModelConditionState(MODELCONDITION_CENTER_TO_LEFT, MODELCONDITION_CENTER_TO_RIGHT); + m_nextTransitionFrame = now + data->m_transitionFrames; + m_currentTurnAnim = MODELCONDITION_CENTER_TO_RIGHT; + } + else if (currentTurn == TURN_POSITIVE) + { + //Initiate a left turn + draw->clearAndSetModelConditionState(MODELCONDITION_CENTER_TO_RIGHT, MODELCONDITION_CENTER_TO_LEFT); + m_nextTransitionFrame = now + data->m_transitionFrames; + m_currentTurnAnim = MODELCONDITION_CENTER_TO_LEFT; + } + else if (currentTurn == TURN_NONE) + { + //Finish the turn + draw->clearModelConditionFlags(MAKE_MODELCONDITION_MASK2(MODELCONDITION_CENTER_TO_LEFT, MODELCONDITION_CENTER_TO_RIGHT)); + m_nextTransitionFrame = now; + m_currentTurnAnim = MODELCONDITION_INVALID; + } + } + else { // default behavior + switch( m_currentTurnAnim ) + { + case MODELCONDITION_INVALID: + //We're currently going straight. Check if we want to turn. + if( currentTurn == TURN_NEGATIVE ) + { + //Initiate a right turn + draw->setModelConditionState( MODELCONDITION_CENTER_TO_RIGHT ); + m_nextTransitionFrame = now + data->m_transitionFrames; + m_currentTurnAnim = MODELCONDITION_CENTER_TO_RIGHT; + } + else if( currentTurn == TURN_POSITIVE ) + { + //Initiate a left turn + draw->setModelConditionState( MODELCONDITION_CENTER_TO_LEFT ); + m_nextTransitionFrame = now + data->m_transitionFrames; + m_currentTurnAnim = MODELCONDITION_CENTER_TO_LEFT; + } + break; + case MODELCONDITION_CENTER_TO_RIGHT: + //We're currently initiating a turn to the right. The only thing + //we can do go back to center or maintain the turn. + if( currentTurn != TURN_NEGATIVE ) + { + //Recenter! + draw->clearAndSetModelConditionState( MODELCONDITION_CENTER_TO_RIGHT, MODELCONDITION_RIGHT_TO_CENTER ); + m_nextTransitionFrame = now + data->m_transitionFrames; + m_currentTurnAnim = MODELCONDITION_RIGHT_TO_CENTER; + } + break; + case MODELCONDITION_CENTER_TO_LEFT: + //We're currently initiating a turn to the left. The only thing + //we can do go back to center or maintain the turn. + if( currentTurn != TURN_POSITIVE ) + { + //Recenter! + draw->clearAndSetModelConditionState( MODELCONDITION_CENTER_TO_LEFT, MODELCONDITION_LEFT_TO_CENTER ); + m_nextTransitionFrame = now + data->m_transitionFrames; + m_currentTurnAnim = MODELCONDITION_LEFT_TO_CENTER; + } + break; + case MODELCONDITION_LEFT_TO_CENTER: + case MODELCONDITION_RIGHT_TO_CENTER: + if( currentTurn == TURN_NONE ) + { + //Finish the turn + draw->clearModelConditionFlags( MAKE_MODELCONDITION_MASK2( MODELCONDITION_LEFT_TO_CENTER, MODELCONDITION_RIGHT_TO_CENTER ) ); + m_nextTransitionFrame = now; + m_currentTurnAnim = MODELCONDITION_INVALID; + } + break; + } + } + } return UPDATE_SLEEP_NONE;