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 e70d41370f4..e711c0ed60c 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,119 @@ 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_offset.x = m_offset.y = m_offset.z = 0;
+ 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)
+ {
+ Coord3D offset = m_offset;
+ if (primaryMtx) {
+ if (m_orientToObject)
+ {
+ adjustVector(&offset, primaryMtx);
+ }
+ }
+
+ 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);
+
+ 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);
+ }
+ else
+ {
+ DEBUG_CRASH(("You must have a primary source for this effect"));
+ }
+ }
+
+ virtual void doFXObj(const Object* primary, const Object* secondary, FXSurfaceInfo* surfaceInfo) const
+ {
+ 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(DecalFXNugget, m_templateName) },
+ { "Lifetime", INI::parseDurationUnsignedInt, nullptr, offsetof(DecalFXNugget, m_lifetime) },
+ { "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) },
+ { "Probability", INI::parseReal, nullptr, offsetof(DecalFXNugget, m_probability) },
+ { nullptr, nullptr, nullptr, 0 }
+ };
+
+ DecalFXNugget* nugget = newInstance(DecalFXNugget);
+ ini->initFromINI(nugget, myFieldParse);
+ ((FXList*)instance)->addFXNugget(nugget);
+ }
+
+private:
+ AsciiString m_templateName;
+ UnsignedInt m_lifetime;
+ Coord3D m_offset;
+ Real m_angle;
+ Bool m_orientToObject;
+ Bool m_randomAngle;
+
+ // spawn parameters
+ Real m_probability;
+ // TODO: Height/Surface, etc.
+};
+EMPTY_DTOR(DecalFXNugget)
+
+
//-------------------------------------------------------------------------------------------------
class LightPulseFXNugget : public FXNugget
{
@@ -1025,6 +1143,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
new file mode 100644
index 00000000000..a7e781bf799
--- /dev/null
+++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDecalDraw.h
@@ -0,0 +1,100 @@
+/*
+** 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"
+#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);
+ // 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;
+
+ 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 c7d0cd2a593..cdbdf119471 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,12 +232,18 @@ 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; /// WeaponRecoilInfoVec;
typedef std::vector ParticleSystemIDVec;
@@ -522,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
new file mode 100644
index 00000000000..5e06850a625
--- /dev/null
+++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDecalDraw.cpp
@@ -0,0 +1,297 @@
+/*
+** 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
+
+#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/W3DProjectedShadow.h"
+//#include "W3DDevice/GameClient/BaseHeightMap.h"
+#include "W3DDevice/GameClient/W3DScene.h"
+
+//-------------------------------------------------------------------------------------------------
+W3DDecalDrawModuleData::W3DDecalDrawModuleData()
+{
+}
+
+//-------------------------------------------------------------------------------------------------
+W3DDecalDrawModuleData::~W3DDecalDrawModuleData()
+{
+}
+
+//-------------------------------------------------------------------------------------------------
+void W3DDecalDrawModuleData::buildFieldParse(MultiIniFieldParse& p)
+{
+ ModuleData::buildFieldParse(p);
+ static const FieldParse dataFieldParse[] =
+ {
+ { "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);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 )
+{
+ //Drawable* draw = getDrawable();
+ //const W3DDecalDrawModuleData* data = getW3DDecalDrawModuleData();
+ // draw->getExpirationDate();
+
+ m_fullyObscuredByShroud = false;
+ m_renderBox = nullptr;
+ m_shadow = nullptr;
+ m_frameCreated = 0;
+}
+
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+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::setFullyObscuredByShroud(Bool fullyObscured)
+{
+ if (m_fullyObscuredByShroud != fullyObscured)
+ {
+ m_fullyObscuredByShroud = fullyObscured;
+
+ if (m_shadow)
+ m_shadow->enableShadowInvisible(m_fullyObscuredByShroud);
+ }
+}
+
+//void W3DDecalDraw::setShadowsEnabled(Bool enable)
+//{
+// 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;
+
+}
+
+// ------------------------------------------------------------------------------------------------
+/** 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/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp
index b0d30cb6b48..5c78e58a90a 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;
}
//-------------------------------------------------------------------------------------------------
@@ -1052,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
}
@@ -1231,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 }
};
@@ -1374,6 +1399,28 @@ static void parseParticleSysBone(INI* ini, void *instance, void * store, const v
self->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*/ )
{
@@ -1488,7 +1535,9 @@ 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 },
+ { "AnimationBlendTime", INI::parseUnsignedInt, nullptr, offsetof(ModelConditionInfo, m_animBlendTime) },
{ nullptr, nullptr, nullptr, 0 }
};
@@ -1791,6 +1840,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,
@@ -2134,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;
@@ -2186,6 +2246,10 @@ void W3DModelDraw::doDrawModule(const Matrix3D* transformMtx)
handleClientRecoil();
+ handleFXEvents();
+
+ m_prevAnimHelper = getCurrentAnimHelper();
+
}
//-------------------------------------------------------------------------------------------------
@@ -2226,12 +2290,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 +2394,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);
+ }
}
}
@@ -2577,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;
@@ -2790,6 +2948,73 @@ 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
+ // 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();
+ Int boneIndex = m_renderObject ? m_renderObject->Get_Bone_Index(it->boneName.str()) : 0;
+ if (boneIndex != 0)
+ {
+
+ 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);
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
//-------------------------------------------------------------------------------------------------
@@ -2989,6 +3214,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.
//
@@ -3106,10 +3337,14 @@ 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
- 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.
@@ -3237,7 +3472,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
@@ -3973,6 +4210,8 @@ void W3DModelDraw::setAnimationFrame( int frame )
//-------------------------------------------------------------------------------------------------
void W3DModelDraw::setPauseAnimation(Bool pauseAnim)
{
+ // TODO: Animation Blending here?
+
if (m_pauseAnimation == pauseAnim)
{
return;
@@ -4012,7 +4251,7 @@ Real W3DModelDraw::getAnimationScrubScalar( void ) const
#endif
//-------------------------------------------------------------------------------------------------
-void W3DModelDraw::rebuildWeaponRecoilInfo(const ModelConditionInfo* state)
+void W3DModelDraw::rebuildWeaponRecoilInfo(const ModelConditionInfo* state, bool clear /*=TRUE*/)
{
Int wslot;
@@ -4034,9 +4273,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/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/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
{
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AnimationSteeringUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AnimationSteeringUpdate.h
index 49f3cc8e6a8..222f6d21453 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AnimationSteeringUpdate.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AnimationSteeringUpdate.h
@@ -50,6 +50,8 @@ class AnimationSteeringUpdateModuleData : public UpdateModuleData
static const FieldParse dataFieldParse[] =
{
{ "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);
@@ -57,6 +59,8 @@ 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 c8265828e57..16b91435b89 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp
@@ -35,14 +35,18 @@
#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"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AnimationSteeringUpdateModuleData::AnimationSteeringUpdateModuleData( void )
{
m_transitionFrames = 0;
+ m_minAngle = 0.0f;
+ m_skipCenteringAnims = false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -79,58 +83,105 @@ UpdateSleepTime AnimationSteeringUpdate::update( void )
{
PhysicsTurningType currentTurn = physics->getTurning();
- 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;
+ 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;
}
- 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;
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 );
}
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
/////////////////////////////////////////////////////////////////////////////