diff --git a/source/engine/gamelib/effects/electricbolt.cpp b/source/engine/gamelib/effects/electricbolt.cpp new file mode 100644 index 0000000..daa26ef --- /dev/null +++ b/source/engine/gamelib/effects/electricbolt.cpp @@ -0,0 +1,255 @@ +#include "gamelib/effects/electricbolt.h" + +#include +#include +#include + +bool GameLib_GetElectricBoltParameters(const idDeclElectricBolt* decl, + idElectricBoltParameters& parameters); +void GameLib_SubmitElectricBolt(idRenderModelBeam* beamModel, + const idMaterial* material, const segment_t* segments, int numSegments, + int currentTime, int startTime, float startWidth, float endWidth, + const idVec4& color, float brightness, bool applyGradient, + int revealTime, int branchLevel); + +namespace { + +struct BoltRandom { + explicit BoltRandom(const std::uint32_t seed) : state(seed) {} + + float Unit() { + state = state * 1664525u + 1013904223u; + return static_cast((state >> 8) & 0x00FFFFFFu) * + (1.0f / 16777216.0f); + } + + float Centered() { return Unit() * 2.0f - 1.0f; } + std::uint32_t state; +}; + +idVec3 SafePerpendicular(const idVec3& direction, BoltRandom& random) { + idVec3 reference = std::fabs(direction.z) < 0.75f + ? idVec3(0.0f, 0.0f, 1.0f) + : idVec3(0.0f, 1.0f, 0.0f); + idVec3 side = direction.Cross(reference); + if (side.NormalizeFast() == 0.0f) { + side = idVec3(1.0f, 0.0f, 0.0f); + } + idVec3 up = direction.Cross(side); + up.NormalizeFast(); + idVec3 result = side * random.Centered() + up * random.Centered(); + if (result.NormalizeFast() == 0.0f) { + return side; + } + return result; +} + +void SubdivideBolt_r(const idVec3& startPos, const idVec3& endPos, + boltStats_t& stats, BoltRandom& random, int subdivisionLevel, + int branchLevel, int numSubdivisions, float deviation, + idStaticList& segments) { + if (segments.Num() >= segments.Max()) { + return; + } + + const idVec3 delta = endPos - startPos; + const float length = delta.Length(); + if (subdivisionLevel >= numSubdivisions || length <= 0.001f) { + segment_t segment{}; + segment.startPos = startPos; + segment.endPos = endPos; + segment.lengthFrac = length * stats.invTotalLength; + segments.Append(segment); + ++stats.numNodes; + return; + } + + idVec3 direction = delta; + direction.NormalizeFast(); + const float falloff = 1.0f / static_cast(1 << subdivisionLevel); + const idVec3 offset = SafePerpendicular(direction, random) * + (deviation * falloff * random.Centered()); + const idVec3 middle = (startPos + endPos) * 0.5f + offset; + SubdivideBolt_r(startPos, middle, stats, random, + subdivisionLevel + 1, branchLevel, numSubdivisions, deviation, + segments); + SubdivideBolt_r(middle, endPos, stats, random, + subdivisionLevel + 1, branchLevel, numSubdivisions, deviation, + segments); +} + +void GenerateBranch(const idVec3& origin, const idVec3& parentDirection, + boltStats_t& stats, BoltRandom& random, + const idElectricBoltParameters& parameters, int branchLevel, + int currentTime, idRenderModelBeam* beamModel) { + if (branchLevel > parameters.maxBranchLevels) { + return; + } + + const float minimumLength = (std::min)(parameters.branchLength.x, + parameters.branchLength.y); + const float maximumLength = (std::max)(parameters.branchLength.x, + parameters.branchLength.y); + const float branchLength = minimumLength + + (maximumLength - minimumLength) * random.Unit(); + const float angleScale = std::sin(parameters.maxBranchAngle * + 0.01745329251994329577f * random.Unit()); + idVec3 direction = parentDirection + + SafePerpendicular(parentDirection, random) * angleScale; + direction.NormalizeFast(); + + idStaticList branchSegments; + SubdivideBolt_r(origin, origin + direction * branchLength, stats, random, + 0, branchLevel, (std::max)(0, parameters.branchSubdivisions), + parameters.maxBranchDeviation, branchSegments); + if (branchSegments.Num() == 0) { + return; + } + + const idMaterial* material = parameters.useBranchOverride && + parameters.branchMaterial != nullptr + ? parameters.branchMaterial : parameters.material; + float branchStartWidth = parameters.branchStartWidth; + float branchEndWidth = parameters.branchEndWidth; + if (parameters.useBranchOverride && parameters.branchWidth > 0) { + branchStartWidth = static_cast(parameters.branchWidth); + branchEndWidth = 0.0f; + } + GameLib_SubmitElectricBolt(beamModel, material, branchSegments.Ptr(), + branchSegments.Num(), currentTime, stats.boltStartTime, + branchStartWidth, branchEndWidth, parameters.color, + parameters.brightness, parameters.applyGradient, + parameters.revealTime, branchLevel); + ++stats.numBranches; +} + +} // namespace + +// The authoritative generator first recursively subdivides each control +// segment, then emits probabilistic branches from the resulting nodes. +void GenerateBolt(idStaticList& currentSegments, + boltStats_t& stats, idRenderModelBeam* beamModel, + const idDeclElectricBolt* eboltDecl, int currentTime, int diversity, + int branchLevel, float maxDeviation, int maxSubdivisions) { + idElectricBoltParameters parameters{}; + if (beamModel == nullptr || eboltDecl == nullptr || + !GameLib_GetElectricBoltParameters(eboltDecl, parameters)) { + return; + } + + BoltRandom random(static_cast(diversity) ^ + static_cast(stats.boltStartTime * 1103515245u)); + idStaticList generated; + for (int index = 0; index < currentSegments.Num(); ++index) { + const segment_t& source = currentSegments[index]; + SubdivideBolt_r(source.startPos, source.endPos, stats, random, 0, + branchLevel, (std::max)(0, maxSubdivisions), maxDeviation, + generated); + } + + if (generated.Num() == 0) { + return; + } + GameLib_SubmitElectricBolt(beamModel, parameters.material, + generated.Ptr(), generated.Num(), currentTime, stats.boltStartTime, + parameters.startWidth, parameters.endWidth, parameters.color, + parameters.brightness, parameters.applyGradient, + parameters.revealTime, branchLevel); + + if (branchLevel < parameters.maxBranchLevels && + parameters.branchProbability > 0.0f) { + for (int index = 0; index < generated.Num(); ++index) { + if (random.Unit() >= parameters.branchProbability) { + continue; + } + idVec3 parentDirection = generated[index].endPos - + generated[index].startPos; + if (parentDirection.NormalizeFast() == 0.0f) { + continue; + } + GenerateBranch(generated[index].endPos, parentDirection, stats, + random, parameters, branchLevel + 1, currentTime, beamModel); + } + } +} + +idElectricBolt::idElectricBolt() + : eboltDecl(nullptr) + , beamModel(nullptr) + , controlNodes() + , startTime(0) + , diversity(0) { +} + +idElectricBolt::~idElectricBolt() { + eboltDecl = nullptr; + beamModel = nullptr; + controlNodes.Clear(); +} + +void idElectricBolt::Init(idRenderModelBeam* const beamModel_, + const idDeclElectricBolt* const eboltDecl_) { + beamModel = beamModel_; + eboltDecl = eboltDecl_; +} + +void idElectricBolt::UpdateControlNodes( + const idStaticList& newControlNodes) { + controlNodes = newControlNodes; +} + +void idElectricBolt::StartElectricBolt(const int newStartTime, + const idVec3& startPos, const idVec3& endPos, + const float newDiversity) { + if (beamModel == nullptr || eboltDecl == nullptr) { + controlNodes.Clear(); + return; + } + startTime = newStartTime; + diversity = static_cast(newDiversity * 32767.0f); + controlNodes.SetNum(1); + controlNodes[0].startPos = startPos; + controlNodes[0].endPos = endPos; +} + +void idElectricBolt::StartElectricBolt(const int newStartTime, + const idStaticList& newControlNodes, + const float newDiversity) { + startTime = newStartTime; + diversity = static_cast(newDiversity * 32767.0f); + controlNodes = newControlNodes; +} + +void idElectricBolt::StopElectricBolt() { + controlNodes.Clear(); +} + +void idElectricBolt::Update(const int currentTime) { + if (beamModel == nullptr || eboltDecl == nullptr || + controlNodes.Num() == 0) { + return; + } + + idElectricBoltParameters parameters{}; + if (!GameLib_GetElectricBoltParameters(eboltDecl, parameters)) { + return; + } + + idStaticList segments; + float totalLength = 0.0f; + for (int index = 0; index < controlNodes.Num(); ++index) { + segment_t source{}; + source.startPos = controlNodes[index].startPos; + source.endPos = controlNodes[index].endPos; + source.lengthFrac = 1.0f; + segments.Append(source); + totalLength += (source.endPos - source.startPos).Length(); + } + + boltStats_t stats{}; + stats.invTotalLength = totalLength > 0.001f ? 1.0f / totalLength : 0.0f; + stats.boltStartPos = controlNodes[0].startPos; + stats.boltStartTime = startTime; + GenerateBolt(segments, stats, beamModel, eboltDecl, currentTime, + diversity, 0, parameters.maxDeviation, parameters.subdivisions); +} diff --git a/source/engine/gamelib/effects/electricbolt.h b/source/engine/gamelib/effects/electricbolt.h index 545ae18..8b2a254 100644 --- a/source/engine/gamelib/effects/electricbolt.h +++ b/source/engine/gamelib/effects/electricbolt.h @@ -1,197 +1,96 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effects\electricbolt.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/containers/staticlist.h" +#include "idlib/math/vector.h" +class idDeclElectricBolt; +class idDeclTable; +class idMaterial; +class idRenderModelBeam; -// IDA Local Type ordinal 15618; PDB kind: class. -class idElectricBolt -{ -public: - const idDeclElectricBolt *eboltDecl; - idRenderModelBeam *beamModel; - idStaticList controlNodes; - int startTime; - int diversity; +// Recovered declaration-facing data used by the bolt generator. The decl +// system owns the real idDeclElectricBolt and exposes this stable view to +// GameLib through GameLib_GetElectricBoltParameters. +struct idElectricBoltParameters { + const idMaterial* material; + bool applyGradient; + int revealTime; + float startWidth; + float endWidth; + float maxDeviation; + idVec4 color; + float brightness; + int subdivisions; + float branchProbability; + int maxBranchLevels; + float branchStartWidth; + float branchEndWidth; + idVec2 branchLength; + float maxBranchAngle; + int branchSubdivisions; + float maxBranchDeviation; + const idDeclTable* jitterTable; + const idDeclTable* jitterFalloffTable; + float jitterDecay; + float jitterSpeed; + idVec2 jitterLeftMag; + idVec2 jitterUpMag; + bool useBranchOverride; + const idMaterial* branchMaterial; + int branchFrames; + int branchWidth; }; -// IDA Local Type ordinal 19740; PDB kind: class. -class __declspec(align(8)) idElectricBoltEmitter : public idDynamicEntity -{ -public: - // Recovered virtual interface; IDA vtable ordinal 19741. - virtual idTypeInfo *GetType(); - virtual ~idElectricBoltEmitter(); - virtual idEventArg *CallEvent(idEventArg *result, const idEventDef *, const idEventArg *); - virtual bool RespondsTo(const idEventDef *); - virtual idEventArg *InternalCallEvent(idEventArg *result, const idEventDef *, const idEventArg *); - virtual bool InternalRespondsTo(const idEventDef *); - virtual void PostSpawn(); - virtual void Remove(); - virtual void DeleteSubEntities(); - virtual bool Draw(idPlayer *); - virtual void JobSync(); - virtual void Think(); - virtual void PauseThink(); - virtual bool ShouldEnterDormancy(); - virtual bool ShouldLeaveDormancy(); - virtual void DormantBegin(); - virtual void DormantEnd(const int); - virtual idRenderModelInfo *GetRenderModelInfo(); - virtual const idRenderModelInfo *GetRenderModelInfo_2(); - virtual void GetScale(idVec3 *); - virtual void SetScale(const idVec3 *); - virtual void SetModelByName(const char *); - virtual void SetModel(idRenderModel *); - virtual const idMaterial *GetCustomMaterial(); - virtual void SetColor(const idVec4 *); - virtual void SetColor_2(const idColor *); - virtual void SetColor_3(const idVec3 *); - virtual void SetColor_4(float, float, float); - virtual void SetColor_5(float, float, float, float); - virtual void GetColor(idVec4 *); - virtual void GetColor_2(idColor *); - virtual void GetColor_3(idVec3 *); - virtual void Hide(bool); - virtual void Hide_2(); - virtual void Show(); - virtual void GetModelTransform(idVec3 *, idMat3 *); - virtual void GetSoundTransform(idVec3 *, idMat3 *); - virtual void UpdateModelTransform(); - virtual void UpdateFX(); - virtual void ProjectOverlay(const idVec3 *, const idVec3 *, float, const char *); - virtual idPresentable *AllocPresentable(idRenderModel *); - virtual const idComponentTimeLine *GetComponentTimeLine(); - virtual idComponentTimeLine *GetComponentTimeLine_2(); - virtual bool UpdateAnimationControllers(); - virtual void UpdateAttachments(); - virtual const idAnimStack *GetAnimStack(); - virtual idAnimStack *GetAnimStack_2(); - virtual idIndex *GetJointIndexFromTrace(idIndex *result, trace_t); - virtual awPathResult_t ChangeAnimWebState(const char *, const char *); - virtual awPathResult_t ChangeAnimWebState_2(const char *); - virtual awPathResult_t ForceAnimWebState(const char *); - virtual awPathResult_t ChangeAnimWebStateVia(const char *, const char *, const char *, const char *); - virtual awPathResult_t ChangeAnimWebStateVia_2(const char *, const char *); - virtual idAnimWebCmdCtx *GetAnimWebCmdCtx(); - virtual const idAnimWebCmdCtx *GetAnimWebCmdCtx_2(); - virtual const idAnimator_AF *GetAF(); - virtual idAnimator_AF *GetAF_2(); - virtual void PreBind(); - virtual void PostBind(); - virtual void PreUnbind(); - virtual void PostUnbind(); - virtual const splineLocation_t *GetSplineLocation(); - virtual void SetAxis(const idMat3 *); - virtual bool CanDisablePhysics(const idEntity *); - virtual collide_t Collide(const int, trace_t *, const idVec3 *); - virtual collide_t Contact(const int, contactInfo_t *); - virtual void ApplyImpulse(const int, const int, const idVec3 *, const idVec3 *); - virtual void ApplyImpulseFromEntity(const idEntity *, const int, const idVec3 *, const idVec3 *); - virtual void ApplyForce(const int, const int, const idVec3 *, const idVec3 *); - virtual bool Crush(const int); - virtual void ApplyDamage(const int, const int, const idDeclDamage *); - virtual void ActivatePhysics(const int); - virtual void DeactivatePhysics(const int); - virtual void ApplyWaterEffects(const int, const int); - virtual void ApplyWaterSplashEffects(const int, const int, surfTypes_t, idPhysicsCallbacks::splashState_t); - virtual bool TakesDamage(); - virtual void DamageFeedback(idEntity *, idEntity *, const idDeclDamage *, float *); - virtual void KilledNotification(const idEntity *, const idEntity *, const idDeclDamage *, const float); - virtual float Damage(idEntity *, idEntity *, const idDeclDamage *, const float, const idVec3 *, trace_t *); - virtual bool CalcDamageImpulse(const idEntity *, const idEntity *, const idDeclDamage *, const float, const idVec3 *, const trace_t *, idVec3 *, idVec3 *); - virtual bool IsTargetLockable(const idDeclAmmo *); - virtual void AddProjectileLock(); - virtual void RemoveProjectileLock(); - virtual const idScriptObject *GetScriptObject(); - virtual idScriptObject *GetScriptObject_2(); - virtual bool ShouldConstructScriptObjectAtSpawn(); - virtual idThread *GetStateThread(); - virtual int AddThread(const idHandle); - virtual void RemoveThread(const idHandle); - virtual idHandle *GetThread(idHandle *result, const int); - virtual int NumThreads(); - virtual int MaxThreads(); - virtual void ExecuteThread(idThread *); - virtual void ResetFSMWaitThreadIfPossible(idThread *); - virtual bool HandleGuiEvent(const sysEvent_t *); - virtual void ActivateTargets(idEntity *); - virtual bool GetRcCarCanTarget(); - virtual const idBaseHealth *GetHealthComponent(); - virtual idBaseHealth *GetHealthComponent_2(); - virtual const idSmartLootComponent *GetSmartLootComponent(); - virtual idSmartLootComponent *GetSmartLootComponent_2(); - virtual void Teleport(const idVec3 *, const idAngles *); - virtual bool IsPusher(); - virtual const idList,5> *GetTriggerTouchList(); - virtual idList,5> *GetTriggerTouchList_2(); - virtual void TestFunctionality(); - virtual float GetUsableDistance(); - virtual float GetCrosshairIconDistance(); - virtual usableState_t GetUsableState(const idEntity *, const idFocusTrace *); - virtual bool ModifyCrosshairInfo(const idEntity *, const idFocusTrace *, const usableState_t, idCrosshairInfo *); - virtual bool IsCrosshairDisabled(const idEntity *, const idFocusTrace *, const usableState_t); - virtual bool IsCrosshairSubdued(const idEntity *, const idFocusTrace *, const usableState_t); - virtual bool IsEverUsable(const idEntity *); - virtual bool IsCurrentlyUsable(const idEntity *); - virtual bool Use(idEntity *, const usableState_t); - virtual void Dropped(idEntity *, const idDeclInventory *); - virtual const idInventoryCollection *GetInventory(); - virtual idInventoryCollection *GetInventory_2(); - virtual void InventoryAdded(idInventoryItem *, int); - virtual void InventoryRemoved(idInventoryItem *); - virtual const idAttachmentCollection *GetAttachments(); - virtual idAttachmentCollection *GetAttachments_2(); - virtual void EnableAIEventResponse(const idAIEvent::aiEventClass_t); - virtual void DisableAIEventResponse(const idAIEvent::aiEventClass_t); - virtual bool CanReceiveAIEvents(const int); - virtual bool RespondsToAIEvent(const idAIEvent *); - virtual void OnAIEvent(const idAIEvent *); - virtual bool IsDead(); - virtual bool IsDying(); - virtual idFaction *GetFaction(); - virtual const idFaction *GetFaction_2(); - virtual idEntityAuditor *GetAuditor(); - virtual void GetVisibilityPoint(const visPoint_t, idVec3 *); - virtual void GetAimPoint(const aimPoint_t, idVec3 *); - virtual void GetEyePos(idVec3 *); - virtual bool IsVisible(); - virtual idDynamicCoverMgr *GetDynamicCoverMgr(); - virtual const idDynamicCoverMgr *GetDynamicCoverMgr_2(); - virtual const idAAS2 *GetAAS(); - virtual void GetViewStateFOV(idVec3 *, unsigned __int8 *, unsigned __int8 *); - virtual void GetViewStateFOV_2(idVec3 *, unsigned __int8 *, unsigned __int8 *); - virtual int GetNumRepairBotTetherPoints(); - virtual bool GetRepairBotTetherPoint(const int, const int, idVec3 *); - virtual idEntityInterface *CreateEntityInterface(idGame *); - virtual void ShowEditingDialog(); - virtual void UpdateEditingDialog(); - virtual void UpdateModifiedProperties(); - virtual inputSettings_t *GetInputSettings(inputSettings_t *result, idPlayer *); - virtual bool EvaluateControls(usercmd_t *, usercmd_t *); - virtual void CheckForErrors(idList *); - virtual void DebugDrawEntity(const idColor *, int); - virtual void ClientThink(); - virtual void Serialize(idSerializer *); - virtual void PostSerializeRead(bool); - virtual void OnActivate(idEntity *); - virtual void OnMakeActivatable(const bool); - virtual void OnNotifyProgressionOwner(); - - int minResetTime; - int maxResetTime; - bool startOff; - const idDeclElectricBolt *electricBoltSystem; - const idSoundShader *sndPowerDown; - const idSoundShader *sndPowerUp; - float maxConeAngle; - idVec2 length; - idEntityPtr controlPath; - bool loopControlPath; - int controlPathGrowTime; - idElectricBolt *ebolt; - idCurve_Spline *spline; - int nextResetTime; +struct eboltControlNode_t { + idVec3 startPos; + idVec3 endPos; }; + +struct boltStats_t { + int numNodes; + int numBranches; + float invTotalLength; + idVec3 boltStartPos; + int boltStartTime; +}; + +struct segment_t { + idVec3 startPos; + idVec3 endPos; + float lengthFrac; +}; + +class idElectricBolt { +public: + idElectricBolt(); + ~idElectricBolt(); + + void Init(idRenderModelBeam* beamModel, + const idDeclElectricBolt* eboltDecl); + void UpdateControlNodes( + const idStaticList& controlNodes); + void StartElectricBolt(int startTime, const idVec3& startPos, + const idVec3& endPos, float diversity); + void StartElectricBolt(int startTime, + const idStaticList& controlNodes, + float diversity); + void StopElectricBolt(); + void Update(int currentTime); + + const idDeclElectricBolt* eboltDecl; + idRenderModelBeam* beamModel; + idStaticList controlNodes; + int startTime; + int diversity; +}; + +static_assert(sizeof(eboltControlNode_t) == 24, + "Recovered electric-bolt control-node ABI changed"); +static_assert(sizeof(boltStats_t) == 28, + "Recovered electric-bolt statistics ABI changed"); +static_assert(sizeof(segment_t) == 28, + "Recovered electric-bolt segment ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idElectricBolt) == 800, + "Recovered idElectricBolt ABI changed"); +#endif diff --git a/source/engine/gamelib/effects/fxmanager.cpp b/source/engine/gamelib/effects/fxmanager.cpp new file mode 100644 index 0000000..7a788d0 --- /dev/null +++ b/source/engine/gamelib/effects/fxmanager.cpp @@ -0,0 +1,738 @@ +#include "gamelib/effects/fxmanager.h" + +#include +#include +#include + +int GameLib_GetFXDeclActionCount(const idDeclFX* decl); +int GameLib_GetFXDeclChangeId(const idDeclFX* decl); +const char* GameLib_GetFXDeclName(const idDeclFX* decl); +bool GameLib_GetFXActionParameters(const idDeclFX* decl, int actionIndex, + idFXActionParameters& parameters); +void GameLib_CreateFXActionResource(idGameLibEffects* effects, + idRenderWorld* renderWorld, const idFXActionParameters& parameters, + idFXAction& action); +void GameLib_StartFXActionResource(idGameLibEffects* effects, + const idFXActionParameters& parameters, idFXAction& action, + const idVec3& origin, const idMat3& axis, int time); +void GameLib_UpdateFXActionResource(idGameLibEffects* effects, + const idFXActionParameters& parameters, idFXAction& action, + const idVec3& origin, const idMat3& axis, const idVec3& velocity, + const idVec4& color, int time, int frameNumber, float fovScale, + float depthHack); +void GameLib_StopFXActionResource(idGameLibEffects* effects, + const idFXActionParameters& parameters, idFXAction& action, + int time, bool immediate, bool recycleResources); +void GameLib_FreeFXActionResource(idGameLibEffects* effects, + const idFXActionParameters& parameters, idFXAction& action); +void GameLib_EnumerateFXActionTags(idTreeAnimator* animator, + const idDeclFX* decl, int actionIndex, idList& tags); +bool GameLib_GetFXTagVelocity(idTreeAnimator* animator, const tagData_t& tag, + int gameMsPerFrame, idVec3& velocity); +idVec4 GameLib_GetFXRenderParm(const idDeclRenderParm* parameter); +idVec4 GameLib_GetFXTableColor(const idFXSingleAction& action, + float fraction); +idList* GameLib_GetFXViewCallbacks(); +int GameLib_StartFXSound(idSoundEmitter* emitter, soundChannel_t channel, + const idSoundShader* shader); +void GameLib_SerializeFXManager(idSerializer& serializer, + idFXManager& manager); + +idRenderModelParticle* GameLib_AllocParticleFxModel(idRenderWorld* renderWorld, + const idDeclParticle* particleDecl); +void GameLib_FreeParticleFxModel(idRenderModelParticle* model); +idRenderModel* GameLib_AllocStaticFxModel(idRenderWorld* renderWorld, + const char* modelName); +void GameLib_FreeStaticFxModel(idRenderModel* model); + +namespace { + +int PointerKey(const void* pointer) { + return static_cast(reinterpret_cast(pointer) >> 4); +} + +float RandomRange(idRandom2& random, const idVec2& range) { + return range.x + (range.y - range.x) * random.RandomFloat(); +} + +float Clamp01(const float value) { + return (std::max)(0.0f, (std::min)(1.0f, value)); +} + +void ResetState(fxActionState_t& state) { + state.startDelay = 0; + state.startTime = -1; + state.stopTime = 0; + state.hidden = false; + state.started = false; + state.shouldTrigger = false; + state.forceStop = false; + state.fadeInStartTime = -1; + state.fadeInEndTime = 0; + state.fadeOutStartTime = 0; +} + +} // namespace + +idFXModelRecycler::idFXModelRecycler() + : fxPrtModels() + , activePrtModelHash(256, 256) + , inactivePrtModelHash(256, 256) + , fxStaticModels() + , activeStaticModelHash(64, 64) + , inactiveStaticModelHash(64, 64) { +} + +idFXModelRecycler::~idFXModelRecycler() { + Shutdown(); +} + +void idFXModelRecycler::Init() { + Shutdown(); + activePrtModelHash.Clear(); + inactivePrtModelHash.Clear(); + activeStaticModelHash.Clear(); + inactiveStaticModelHash.Clear(); +} + +void idFXModelRecycler::Shutdown() { + for (int index = 0; index < fxPrtModels.Num(); ++index) { + if (fxPrtModels[index].pmodel != nullptr) { + GameLib_FreeParticleFxModel(fxPrtModels[index].pmodel); + } + } + for (int index = 0; index < fxStaticModels.Num(); ++index) { + if (fxStaticModels[index].rmodel != nullptr) { + GameLib_FreeStaticFxModel(fxStaticModels[index].rmodel); + } + } + fxPrtModels.Clear(); + fxStaticModels.Clear(); + activePrtModelHash.Clear(); + inactivePrtModelHash.Clear(); + activeStaticModelHash.Clear(); + inactiveStaticModelHash.Clear(); +} + +idRenderModelParticle* idFXModelRecycler::GetParticleFxModel( + const idDeclParticle* const particleDecl, + idRenderWorld* const renderWorld) { + if (particleDecl == nullptr) { + return nullptr; + } + const int key = PointerKey(particleDecl); + for (int index = inactivePrtModelHash.First(key); index >= 0; + index = inactivePrtModelHash.Next(index)) { + if (index < fxPrtModels.Num() && + fxPrtModels[index].pDecl == particleDecl) { + inactivePrtModelHash.Remove(key, index); + activePrtModelHash.Add(key, index); + return fxPrtModels[index].pmodel; + } + } + if (fxPrtModels.Num() >= fxPrtModels.Max()) { + return nullptr; + } + fxPrtModel_t item{}; + item.pDecl = particleDecl; + item.pmodel = GameLib_AllocParticleFxModel(renderWorld, particleDecl); + if (item.pmodel == nullptr) { + return nullptr; + } + const int index = fxPrtModels.Append(item); + activePrtModelHash.Add(key, index); + return item.pmodel; +} + +void idFXModelRecycler::RecycleParticleFxModel( + const idDeclParticle* const particleDecl, + idRenderModelParticle* const model) { + if (particleDecl == nullptr || model == nullptr) { + return; + } + const int key = PointerKey(particleDecl); + for (int index = activePrtModelHash.First(key); index >= 0; + index = activePrtModelHash.Next(index)) { + if (index < fxPrtModels.Num() && + fxPrtModels[index].pmodel == model) { + activePrtModelHash.Remove(key, index); + inactivePrtModelHash.Add(key, index); + return; + } + } +} + +idRenderModel* idFXModelRecycler::GetStaticFxModel( + const idAtomicString& modelName, idRenderWorld* const renderWorld) { + if (modelName.IsEmpty()) { + return nullptr; + } + const int key = inactiveStaticModelHash.GenerateKeyForString( + modelName.c_str(), false); + for (int index = inactiveStaticModelHash.First(key); index >= 0; + index = inactiveStaticModelHash.Next(index)) { + if (index < fxStaticModels.Num() && + fxStaticModels[index].modelName == modelName) { + inactiveStaticModelHash.Remove(key, index); + activeStaticModelHash.Add(key, index); + return fxStaticModels[index].rmodel; + } + } + if (fxStaticModels.Num() >= fxStaticModels.Max()) { + return nullptr; + } + fxStaticModel_t item{}; + item.modelName = modelName; + item.rmodel = GameLib_AllocStaticFxModel(renderWorld, modelName.c_str()); + if (item.rmodel == nullptr) { + return nullptr; + } + const int index = fxStaticModels.Append(item); + activeStaticModelHash.Add(key, index); + return item.rmodel; +} + +void idFXModelRecycler::RecycleStaticFxModel( + const idAtomicString& modelName, idRenderModel* const model) { + if (modelName.IsEmpty() || model == nullptr) { + return; + } + const int key = activeStaticModelHash.GenerateKeyForString( + modelName.c_str(), false); + for (int index = activeStaticModelHash.First(key); index >= 0; + index = activeStaticModelHash.Next(index)) { + if (index < fxStaticModels.Num() && + fxStaticModels[index].rmodel == model) { + activeStaticModelHash.Remove(key, index); + inactiveStaticModelHash.Add(key, index); + return; + } + } +} + +idFXAction::idFXAction() + : tagIndex(0) + , startOrg() + , startAxis(1.0f) + , rLight(nullptr) + , rModel(nullptr) + , rParticle(nullptr) + , screenPrtHandle(-1) + , flareManager() + , ribbonManager() + , tagData(4) + , lastParticleDropPos() + , renderParmStartValue(0.0f, 0.0f, 0.0f, 0.0f) + , randomAngles(0.0f, 0.0f, 0.0f) + , viewCallbacksID(-1) { + startOrg.Zero(); + lastParticleDropPos.Zero(); +} + +idFXManager::idFXManager() + : initialized(false) + , fxDecl(nullptr) + , gameLibEffects(nullptr) + , ta(nullptr) + , rw(nullptr) + , systemColor(1.0f, 1.0f, 1.0f, 1.0f) + , random(0) + , soundInfo{nullptr, SND_CHANNEL_ANY} + , actions(4) + , actionState(4) + , remote(false) + , allowSurfaceOnlyInViewID(0) + , suppressSurfaceInViewID(0) + , viewCallbacksID(-1) + , declChangeId(-1) + , externalRotation(1.0f) + , externalPosition() + , hasExternalPositionAndRotation(false) + , actionBuffer{} + , actionBufferPos(0) + , serializeActionCount(0) { + externalPosition.Zero(); +} + +idFXManager::~idFXManager() { + Shutdown(); +} + +idVec4 idFXManager::GetVector(const idDeclRenderParm* const parameter) const { + return parameter != nullptr ? GameLib_GetFXRenderParm(parameter) + : idVec4(0.0f, 0.0f, 0.0f, 0.0f); +} + +idVec4 idFXManager::GetTableColor(const idFXSingleAction& action, + const float fraction) const { + return GameLib_GetFXTableColor(action, fraction); +} + +bool idFXManager::GetWorldSpaceTagVelocity(const tagData_t& tag, + const int gameMsPerFrame, idVec3& velocity) { + velocity.Zero(); + return ta != nullptr && gameMsPerFrame > 0 && + GameLib_GetFXTagVelocity(ta, tag, gameMsPerFrame, velocity); +} + +int idFXManager::StartSound(const soundChannel_t channel, + const idSoundShader* const shader) { + if (soundInfo.emitter == nullptr || shader == nullptr) { + return 0; + } + const soundChannel_t resolved = channel == SND_CHANNEL_ANY + ? soundInfo.channel : channel; + return GameLib_StartFXSound(soundInfo.emitter, resolved, shader); +} + +idList& idFXManager::GetViewCallbacks() { + idList* callbacks = GameLib_GetFXViewCallbacks(); + if (callbacks != nullptr) { + return *callbacks; + } + static idList empty; + return empty; +} + +const char* idFXManager::GetName() const { + return fxDecl != nullptr ? GameLib_GetFXDeclName(fxDecl) : ""; +} + +bool idFXManager::IsStopped(const int time) const { + for (int index = 0; index < actionState.Num(); ++index) { + if (time < actionState[index].stopTime) { + return false; + } + } + return true; +} + +void idFXManager::Init(const idDeclFX* const declFX, + idRenderWorld* const renderWorld, + const fxEmitterSound_t* const emitterSound, + idGameLibEffects* const effects, const float diversity, + idTreeAnimator* const treeAnimator) { + Shutdown(); + if (declFX == nullptr) { + return; + } + fxDecl = declFX; + gameLibEffects = effects; + ta = treeAnimator; + rw = renderWorld; + random.SetSeed(static_cast(diversity * 65535.0f)); + if (emitterSound != nullptr) { + soundInfo = *emitterSound; + } + const int count = (std::max)(0, GameLib_GetFXDeclActionCount(fxDecl)); + actions.SetNum(count); + actionState.SetNum(count); + declChangeId = GameLib_GetFXDeclChangeId(fxDecl); + for (int index = 0; index < count; ++index) { + CreateAction(index, rw); + } + EnumerateTags(); + initialized = true; +} + +void idFXManager::CreateAction(const int index, + idRenderWorld* const renderWorld) { + if (index < 0 || index >= actions.Num()) { + return; + } + ResetState(actionState[index]); + idFXActionParameters parameters{}; + if (GameLib_GetFXActionParameters(fxDecl, index, parameters)) { + GameLib_CreateFXActionResource(gameLibEffects, renderWorld, + parameters, actions[index]); + } +} + +void idFXManager::EnumerateTags() { + if (ta == nullptr) { + return; + } + for (int index = 0; index < actions.Num(); ++index) { + actions[index].tagData.Clear(); + GameLib_EnumerateFXActionTags(ta, fxDecl, index, + actions[index].tagData); + } +} + +void idFXManager::StartAction(const int index, const idVec3& origin, + const idMat3& axis, const int time, const int explicitTagIndex) { + if (index < 0 || index >= actions.Num()) { + return; + } + idFXActionParameters parameters{}; + if (!GameLib_GetFXActionParameters(fxDecl, index, parameters)) { + return; + } + idFXAction& action = actions[index]; + fxActionState_t& state = actionState[index]; + action.startOrg = origin; + action.startAxis = axis; + action.tagIndex = explicitTagIndex >= 0 ? explicitTagIndex : 0; + if (explicitTagIndex < 0 && action.tagData.Num() > 1) { + action.tagIndex = random.RandomInt(action.tagData.Num()); + } + action.randomAngles = idAngles( + RandomRange(random, parameters.randomRotationX), + RandomRange(random, parameters.randomRotationY), + RandomRange(random, parameters.randomRotationZ)); + state.startDelay = static_cast(RandomRange(random, + parameters.delay) * 1000.0f); + state.startTime = time; + state.stopTime = time + state.startDelay + + static_cast(parameters.duration * 1000.0f); + state.fadeInStartTime = time + state.startDelay; + state.fadeInEndTime = state.fadeInStartTime + + static_cast(parameters.fadeInTime * 1000.0f); + state.fadeOutStartTime = state.stopTime - + static_cast(parameters.fadeOutTime * 1000.0f); + state.hidden = false; + state.started = false; + state.shouldTrigger = true; + state.forceStop = false; +} + +int idFXManager::StartActions(const idVec3& origin, const idMat3& axis, + const int time, const fxCondition_t startCondition, + const fxExtraCondition_t extraCondition, const int explicitTagIndex) { + int startedCount = 0; + for (int index = 0; index < actions.Num(); ++index) { + idFXActionParameters parameters{}; + if (!GameLib_GetFXActionParameters(fxDecl, index, parameters)) { + continue; + } + if (parameters.startCondition != startCondition) { + continue; + } + if (parameters.extraCondition != FX_EXTRA_COND_NONE && + (static_cast(parameters.extraCondition) & + static_cast(extraCondition)) == 0) { + continue; + } + StartAction(index, origin, axis, time, explicitTagIndex); + ++startedCount; + } + return startedCount; +} + +int idFXManager::InternalStartFX(const fxActionCall_t& actionCall) { + if (!initialized) { + return 0; + } + const int result = StartActions(actionCall.org, actionCall.axis, + actionCall.time, actionCall.condition, actionCall.extraCondition, + actionCall.tagIdx); + ++actionBufferPos; + return result; +} + +int idFXManager::StartFX(const idVec3& origin, const idMat3& axis, + const int time, const fxCondition_t startCondition, + const int explicitTagIndex, const int delay) { + if (!initialized) { + return 0; + } + fxActionCall_t& call = actionBuffer[actionBufferPos % 8]; + call.org = origin; + call.axis = axis; + call.condition = startCondition; + call.time = time + delay; + call.extraCondition = FX_EXTRA_COND_NONE; + call.tagIdx = explicitTagIndex; + call.actionType = FXACTION_START; + call.immediate = false; + call.viewCallbacksID = viewCallbacksID; + return InternalStartFX(call); +} + +int idFXManager::StartFX(const idVec3& origin, const idMat3& axis, + const int time, const fxCondition_t startCondition, + const fxExtraCondition_t extraCondition) { + if (!initialized) { + return 0; + } + fxActionCall_t& call = actionBuffer[actionBufferPos % 8]; + call.org = origin; + call.axis = axis; + call.condition = startCondition; + call.time = time; + call.extraCondition = extraCondition; + call.tagIdx = -1; + call.actionType = FXACTION_START; + call.immediate = false; + call.viewCallbacksID = viewCallbacksID; + return InternalStartFX(call); +} + +int idFXManager::StartFX(const idVec3& origin, const idMat3& axis, + const int time, const fxCondition_t startCondition) { + return StartFX(origin, axis, time, startCondition, -1, 0); +} + +void idFXManager::LocalStartFX(const idVec3& origin, const idMat3& axis, + const int time, const fxCondition_t startCondition) { + if (initialized) { + StartActions(origin, axis, time, startCondition, + FX_EXTRA_COND_NONE, -1); + } +} + +void idFXManager::ApplyFade(const idFXActionParameters& parameters, + idFXAction& action, fxActionState_t& state, const int time, + const float fraction) { + float fade = 1.0f; + if (parameters.fadeInTime > 0.0f && time < state.fadeInEndTime) { + const int duration = state.fadeInEndTime - state.fadeInStartTime; + fade *= duration > 0 ? Clamp01(static_cast( + time - state.fadeInStartTime) / duration) : 1.0f; + } + if (parameters.fadeOutTime > 0.0f && time >= state.fadeOutStartTime) { + const int duration = state.stopTime - state.fadeOutStartTime; + fade *= duration > 0 ? Clamp01(static_cast( + state.stopTime - time) / duration) : 0.0f; + } + action.renderParmStartValue = idVec4( + systemColor.x * fade, systemColor.y * fade, + systemColor.z * fade, systemColor.w * fade * Clamp01(fraction)); +} + +void idFXManager::RestartAction(const int index, const int time) { + if (index < 0 || index >= actions.Num()) { + return; + } + const idVec3 origin = actions[index].startOrg; + const idMat3 axis = actions[index].startAxis; + StopAction(index, time, true, false); + StartAction(index, origin, axis, time, actions[index].tagIndex); +} + +void idFXManager::StopAction(const int index, const int time, + const bool immediate, const bool recycleResources) { + if (index < 0 || index >= actions.Num()) { + return; + } + idFXActionParameters parameters{}; + if (!GameLib_GetFXActionParameters(fxDecl, index, parameters)) { + return; + } + fxActionState_t& state = actionState[index]; + if (state.startTime < 0) { + return; + } + if (!immediate && !state.forceStop && parameters.fadeOutTime > 0.0f && + parameters.type != FX_SOUND) { + state.fadeOutStartTime = time; + state.stopTime = time + static_cast( + parameters.fadeOutTime * 1000.0f); + state.forceStop = true; + return; + } + if (parameters.type == FX_FLARE) { + actions[index].flareManager.StopFlare(); + } else if (parameters.type == FX_RIBBON) { + actions[index].ribbonManager.StopRibbon(); + } + GameLib_StopFXActionResource(gameLibEffects, parameters, actions[index], + time, immediate, recycleResources); + const bool keepHidden = parameters.triggered; + ResetState(state); + state.hidden = keepHidden; + actions[index].lastParticleDropPos.Zero(); + actions[index].randomAngles = idAngles(0.0f, 0.0f, 0.0f); + actions[index].startAxis = idMat3(1.0f); + actions[index].startOrg.Zero(); +} + +void idFXManager::StopActions(const int time, + const fxCondition_t stopCondition, + const fxExtraCondition_t extraCondition, const bool stopAll, + const bool immediate) { + for (int index = 0; index < actions.Num(); ++index) { + idFXActionParameters parameters{}; + if (!GameLib_GetFXActionParameters(fxDecl, index, parameters)) { + continue; + } + if (!stopAll && parameters.stopCondition != stopCondition) { + continue; + } + if (!stopAll && parameters.extraCondition != FX_EXTRA_COND_NONE && + (static_cast(parameters.extraCondition) & + static_cast(extraCondition)) == 0) { + continue; + } + StopAction(index, time, immediate, true); + } +} + +void idFXManager::InternalStopFX(const fxActionCall_t& actionCall) { + if (!initialized) { + return; + } + StopActions(actionCall.time, actionCall.condition, + actionCall.extraCondition, + actionCall.actionType == FXACTION_STOP_ALL, actionCall.immediate); + ++actionBufferPos; +} + +void idFXManager::StopFX(const int time, + const fxCondition_t stopCondition, const bool immediate) { + StopFX(time, stopCondition, FX_EXTRA_COND_NONE, immediate); +} + +void idFXManager::StopFX(const int time, + const fxCondition_t stopCondition, + const fxExtraCondition_t extraCondition, const bool immediate) { + if (!initialized) { + return; + } + fxActionCall_t& call = actionBuffer[actionBufferPos % 8]; + call.condition = stopCondition; + call.time = time; + call.extraCondition = extraCondition; + call.actionType = FXACTION_STOP; + call.immediate = immediate; + call.viewCallbacksID = viewCallbacksID; + InternalStopFX(call); +} + +void idFXManager::LocalStopFX(const int time, + const fxCondition_t stopCondition, const bool immediate) { + if (initialized) { + StopActions(time, stopCondition, FX_EXTRA_COND_NONE, false, + immediate); + } +} + +void idFXManager::StopAllFX(const int time, const bool immediate) { + if (!initialized) { + return; + } + fxActionCall_t& call = actionBuffer[actionBufferPos % 8]; + call.condition = FX_NONE; + call.time = time; + call.extraCondition = FX_EXTRA_COND_NONE; + call.actionType = FXACTION_STOP_ALL; + call.immediate = immediate; + call.viewCallbacksID = viewCallbacksID; + InternalStopFX(call); +} + +void idFXManager::LocalStopAllFX(const int time, const bool immediate) { + if (initialized) { + StopActions(time, FX_NONE, FX_EXTRA_COND_NONE, true, immediate); + } +} + +void idFXManager::UpdateActions(const idVec3& origin, const idMat3& axis, + const idVec3& velocity, const int time, const int gameMsPerFrame, + const int frameNumber, const float fovScale, const float depthHack) { + (void)gameMsPerFrame; + for (int index = 0; index < actions.Num(); ++index) { + idFXActionParameters parameters{}; + if (!GameLib_GetFXActionParameters(fxDecl, index, parameters)) { + continue; + } + fxActionState_t& state = actionState[index]; + idFXAction& action = actions[index]; + if (state.startTime < 0 || state.hidden) { + continue; + } + const int actualStart = state.startTime + state.startDelay; + if (time < actualStart) { + continue; + } + if (!state.started) { + GameLib_StartFXActionResource(gameLibEffects, parameters, action, + action.startOrg, action.startAxis, actualStart); + if (parameters.type == FX_SOUND) { + StartSound(parameters.soundChannel, parameters.sound); + } + state.started = true; + } + if (time >= state.stopTime) { + if (parameters.looping && !state.forceStop) { + RestartAction(index, time + static_cast( + parameters.restart * 1000.0f)); + } else { + StopAction(index, time, true, true); + } + continue; + } + const int duration = (std::max)(1, state.stopTime - actualStart); + const float fraction = Clamp01(static_cast(time - actualStart) / + static_cast(duration)); + ApplyFade(parameters, action, state, time, fraction); + const idVec3 updateOrigin = parameters.trackOrigin ? origin + : action.startOrg; + const idMat3 updateAxis = parameters.trackOrigin ? axis + : action.startAxis; + GameLib_UpdateFXActionResource(gameLibEffects, parameters, action, + updateOrigin, updateAxis, velocity, action.renderParmStartValue, + time, frameNumber, fovScale, depthHack); + } +} + +void idFXManager::Update(const idVec3& parentOrigin, + const idMat3& parentAxis, const idVec3& parentVelocity, const int time, + const int gameMsPerFrame, const int frameNumber, const float fovScale, + const float depthHack) { + if (!initialized) { + return; + } + while (serializeActionCount > 0) { + --serializeActionCount; + fxActionCall_t& call = actionBuffer[actionBufferPos % 8]; + call.time = time; + viewCallbacksID = call.viewCallbacksID; + if (call.actionType == FXACTION_START) { + InternalStartFX(call); + } else { + InternalStopFX(call); + } + } + UpdateActions(parentOrigin, parentAxis, parentVelocity, time, + gameMsPerFrame, frameNumber, fovScale, depthHack); +} + +void idFXManager::ResetTreeAnimator(idTreeAnimator* const treeAnimator) { + ta = treeAnimator; + EnumerateTags(); +} + +void idFXManager::Serialize(idSerializer& serializer) { + GameLib_SerializeFXManager(serializer, *this); +} + +void idFXManager::FreeActions() { + for (int index = 0; index < actions.Num(); ++index) { + idFXActionParameters parameters{}; + if (GameLib_GetFXActionParameters(fxDecl, index, parameters)) { + GameLib_FreeFXActionResource(gameLibEffects, parameters, + actions[index]); + } + actions[index].tagData.Clear(); + } + actions.Clear(); + actionState.Clear(); +} + +void idFXManager::Shutdown() { + if (fxDecl != nullptr) { + StopActions(0, FX_NONE, FX_EXTRA_COND_NONE, true, true); + FreeActions(); + } + initialized = false; + fxDecl = nullptr; + gameLibEffects = nullptr; + ta = nullptr; + rw = nullptr; + soundInfo.emitter = nullptr; + soundInfo.channel = SND_CHANNEL_ANY; + declChangeId = -1; + actionBufferPos = 0; + serializeActionCount = 0; +} diff --git a/source/engine/gamelib/effects/fxmanager.h b/source/engine/gamelib/effects/fxmanager.h index e9bfe42..f69ad09 100644 --- a/source/engine/gamelib/effects/fxmanager.h +++ b/source/engine/gamelib/effects/fxmanager.h @@ -1,66 +1,273 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effects\fxmanager.h -// Recovered logical types: 4 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/effects/lensflaremanager.h" +#include "gamelib/effects/ribbonmanager.h" +#include "idlib/containers/hashindex.h" +#include "idlib/containers/list.h" +#include "idlib/containers/staticlist.h" +#include "idlib/math/random.h" +#include "idlib/math/vector.h" +#include "idlib/text/atomicstring.h" +#include -// IDA Local Type ordinal 2791; PDB kind: enum. -enum idFXManager::fxActionCallType_t : __int32 -{ - FXACTION_START = 0x0, - FXACTION_STOP = 0x1, - FXACTION_STOP_ALL = 0x2, - FXACTION_MAX = 0x3, +class idDeclFX; +class idDeclParticle; +class idDeclRenderParm; +class idDeclTable; +class idFXSingleAction; +class idGameLibEffects; +class idMaterial; +class idRenderLight; +class idRenderModel; +class idRenderModelParticle; +class idRenderWorld; +class idSerializer; +class idSoundEmitter; +class idSoundShader; +class idTreeAnimator; +class idViewCallbacks; + +enum fxCondition_t : int { + FX_NONE = 0 }; -// IDA Local Type ordinal 14217; PDB kind: struct. -struct idFXManager::fxActionCall_t -{ - idVec3 org; - idMat3 axis; - fxCondition_t condition; - int time; - fxExtraCondition_t extraCondition; - int tagIdx; - idFXManager::fxActionCallType_t actionType; - bool immediate; - int viewCallbacksID; +enum fxExtraCondition_t : int { + FX_EXTRA_COND_NONE = 0, + FX_EXTRA_COND_MAX = 0x10000 }; -// IDA Local Type ordinal 14218; PDB kind: class. -class idFXManager -{ +enum soundChannel_t : int { + SND_CHANNEL_ANY = 0 +}; + +enum fxActionType_t : int { + FX_LIGHT = 0, + FX_PARTICLE, + FX_DECAL2, + FX_MODEL, + FX_SOUND, + FX_SCREEN_SHAKE, + FX_CONTROLLER_SHAKE, + FX_ENV_OVERRIDE, + FX_ENV_CHANGE, + FX_FLARE, + FX_RIBBON, + FX_OTHER +}; + +struct tagData_t { + idVec3 trans; + idQuat rot; + std::uint16_t parentJoint; +}; + +struct fxEmitterSound_t { + idSoundEmitter* emitter; + soundChannel_t channel; +}; + +// Stable PC-side view of the fields the recovered state machine reads from +// each idFXSingleAction. The declaration system fills this view. +struct idFXActionParameters { + fxActionType_t type; + fxCondition_t startCondition; + fxCondition_t stopCondition; + fxExtraCondition_t extraCondition; + idVec2 delay; + float duration; + float fadeInTime; + float fadeOutTime; + float restart; + bool looping; + bool triggered; + bool trackOrigin; + bool bindOrigin; + bool bindAxis; + idVec2 randomRotationX; + idVec2 randomRotationY; + idVec2 randomRotationZ; + const idDeclParticle* particleDecl; + idAtomicString modelName; + const idSoundShader* sound; + soundChannel_t soundChannel; +}; + +class idFXModelRecycler { public: - bool initialized; - const idDeclFX *fxDecl; - idGameLibEffects *gameLibEffects; - idTreeAnimator *ta; - idRenderWorld *rw; - idVec4 systemColor; - idRandom2 random; - fxEmitterSound_t soundInfo; - idList actions; - idList actionState; - bool remote; - int allowSurfaceOnlyInViewID; - int suppressSurfaceInViewID; - int viewCallbacksID; - int declChangeId; - idMat3 externalRotation; - idVec3 externalPosition; - bool hasExternalPositionAndRotation; - idFXManager::fxActionCall_t actionBuffer[8]; - int actionBufferPos; - int serializeActionCount; + struct fxPrtModel_t { + idRenderModelParticle* pmodel; + const idDeclParticle* pDecl; + }; + struct fxStaticModel_t { + idRenderModel* rmodel; + idAtomicString modelName; + }; + + idFXModelRecycler(); + ~idFXModelRecycler(); + void Init(); + void Shutdown(); + idRenderModelParticle* GetParticleFxModel( + const idDeclParticle* particleDecl, idRenderWorld* renderWorld); + void RecycleParticleFxModel(const idDeclParticle* particleDecl, + idRenderModelParticle* model); + idRenderModel* GetStaticFxModel(const idAtomicString& modelName, + idRenderWorld* renderWorld); + void RecycleStaticFxModel(const idAtomicString& modelName, + idRenderModel* model); + + idStaticList fxPrtModels; + idHashIndex activePrtModelHash; + idHashIndex inactivePrtModelHash; + idStaticList fxStaticModels; + idHashIndex activeStaticModelHash; + idHashIndex inactiveStaticModelHash; }; -// IDA Local Type ordinal 23462; PDB kind: struct. -struct __declspec(align(4)) idFXManager::UpdateActions::__l79::viewcallbackInfo_t -{ - idVec3 viewOrg; - idMat3 viewAxis; - float distSQ; - bool inRange; +struct idFXAction { + idFXAction(); + + int tagIndex; + idVec3 startOrg; + idMat3 startAxis; + idRenderLight* rLight; + idRenderModel* rModel; + idRenderModelParticle* rParticle; + int screenPrtHandle; + idLensFlareManager flareManager; + idRibbonManager ribbonManager; + idList tagData; + idVec3 lastParticleDropPos; + idVec4 renderParmStartValue; + idAngles randomAngles; + int viewCallbacksID; }; + +struct fxActionState_t { + int startDelay; + int startTime; + int stopTime; + bool hidden; + bool started; + bool shouldTrigger; + bool forceStop; + int fadeInStartTime; + int fadeInEndTime; + int fadeOutStartTime; +}; + +class idFXManager { +public: + enum fxActionCallType_t : int { + FXACTION_START = 0, + FXACTION_STOP = 1, + FXACTION_STOP_ALL = 2, + FXACTION_MAX = 3 + }; + + struct fxActionCall_t { + idVec3 org; + idMat3 axis; + fxCondition_t condition; + int time; + fxExtraCondition_t extraCondition; + int tagIdx; + fxActionCallType_t actionType; + bool immediate; + int viewCallbacksID; + }; + + idFXManager(); + ~idFXManager(); + + void Init(const idDeclFX* declFX, idRenderWorld* renderWorld, + const fxEmitterSound_t* soundInfo, idGameLibEffects* gameLibEffects, + float diversity, idTreeAnimator* treeAnimator); + void Shutdown(); + void Update(const idVec3& parentOrigin, const idMat3& parentAxis, + const idVec3& parentVelocity, int time, int gameMsPerFrame, + int frameNumber, float fovScale, float depthHack); + int StartFX(const idVec3& origin, const idMat3& axis, int time, + fxCondition_t startCondition, int explicitTagIndex, int delay); + int StartFX(const idVec3& origin, const idMat3& axis, int time, + fxCondition_t startCondition, fxExtraCondition_t extraCondition); + int StartFX(const idVec3& origin, const idMat3& axis, int time, + fxCondition_t startCondition); + void LocalStartFX(const idVec3& origin, const idMat3& axis, int time, + fxCondition_t startCondition); + void StopFX(int time, fxCondition_t stopCondition, bool immediate); + void StopFX(int time, fxCondition_t stopCondition, + fxExtraCondition_t extraCondition, bool immediate); + void LocalStopFX(int time, fxCondition_t stopCondition, bool immediate); + void StopAllFX(int time, bool immediate); + void LocalStopAllFX(int time, bool immediate); + bool IsStopped(int time) const; + const char* GetName() const; + void ResetTreeAnimator(idTreeAnimator* treeAnimator); + void Serialize(idSerializer& serializer); + + bool initialized; + const idDeclFX* fxDecl; + idGameLibEffects* gameLibEffects; + idTreeAnimator* ta; + idRenderWorld* rw; + idVec4 systemColor; + idRandom2 random; + fxEmitterSound_t soundInfo; + idList actions; + idList actionState; + bool remote; + int allowSurfaceOnlyInViewID; + int suppressSurfaceInViewID; + int viewCallbacksID; + int declChangeId; + idMat3 externalRotation; + idVec3 externalPosition; + bool hasExternalPositionAndRotation; + fxActionCall_t actionBuffer[8]; + int actionBufferPos; + int serializeActionCount; + +private: + idVec4 GetVector(const idDeclRenderParm* parameter) const; + idVec4 GetTableColor(const idFXSingleAction& action, float fraction) const; + bool GetWorldSpaceTagVelocity(const tagData_t& tag, + int gameMsPerFrame, idVec3& velocity); + int StartSound(soundChannel_t channel, const idSoundShader* shader); + idList& GetViewCallbacks(); + void ApplyFade(const idFXActionParameters& parameters, + idFXAction& action, fxActionState_t& state, int time, float fraction); + void StopAction(int index, int time, bool immediate, + bool recycleResources); + void RestartAction(int index, int time); + void StopActions(int time, fxCondition_t stopCondition, + fxExtraCondition_t extraCondition, bool stopAll, bool immediate); + void InternalStopFX(const fxActionCall_t& actionCall); + void StartAction(int index, const idVec3& origin, const idMat3& axis, + int time, int explicitTagIndex); + void EnumerateTags(); + void UpdateActions(const idVec3& origin, const idMat3& axis, + const idVec3& velocity, int time, int gameMsPerFrame, + int frameNumber, float fovScale, float depthHack); + int StartActions(const idVec3& origin, const idMat3& axis, int time, + fxCondition_t startCondition, fxExtraCondition_t extraCondition, + int explicitTagIndex); + void CreateAction(int index, idRenderWorld* renderWorld); + void FreeActions(); + int InternalStartFX(const fxActionCall_t& actionCall); +}; + +static_assert(sizeof(tagData_t) == 32, "Recovered tagData_t ABI changed"); +static_assert(sizeof(fxEmitterSound_t) == 8, + "Recovered FX emitter sound ABI changed"); +static_assert(sizeof(fxActionState_t) == 28, + "Recovered FX action-state ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idFXAction) == 184, + "Recovered idFXAction ABI changed"); +static_assert(sizeof(idFXManager::fxActionCall_t) == 76, + "Recovered FX action-call ABI changed"); +static_assert(sizeof(idFXManager) == 768, + "Recovered idFXManager ABI changed"); +#endif diff --git a/source/engine/gamelib/effects/gamelibeffects.cpp b/source/engine/gamelib/effects/gamelibeffects.cpp new file mode 100644 index 0000000..ad27250 --- /dev/null +++ b/source/engine/gamelib/effects/gamelibeffects.cpp @@ -0,0 +1,109 @@ +#include "gamelib/effects/gamelibeffects.h" + +idRenderModelEffects* GameLib_AllocEffectsModel(idRenderWorld* renderWorld); +idRenderModelDecal* GameLib_GetDecalModel(idRenderWorld* renderWorld); +idRenderModelBeam* GameLib_AllocBeamModel(idRenderWorld* renderWorld); +void GameLib_CommitEffectsModel(idRenderModelEffects* model); +void GameLib_CommitDecalModel(idRenderModelDecal* model); +void GameLib_CommitBeamModel(idRenderModelBeam* model); +void GameLib_FreeEffectsModel(idRenderModelEffects* model); +void GameLib_FreeBeamModel(idRenderModelBeam* model); +void GameLib_UpdateEffectsModel(idRenderModelEffects* model, + int currentTime, int gameMsPerFrame); +void GameLib_UpdateBeamModel(idRenderModelBeam* model, int currentTime); + +idGameLibEffects::idGameLibEffects() + : effectsModel(nullptr) + , decalModel(nullptr) + , beamModel(nullptr) + , effectsModelManager() + , deferredDecalManager() + , ribbonModelManager() + , weaponTraceManager() + , fxModelRecycler() + , weaponImpactManager() + , impactManager() + , initialized(false) { +} + +idGameLibEffects::~idGameLibEffects() { + Shutdown(); +} + +void idGameLibEffects::Init(idRenderWorld* const renderWorld, + idClip* const clip, const float diversity, + const int localPlayerIndex) { + if (initialized) { + Shutdown(); + } + if (renderWorld == nullptr) { + return; + } + + effectsModel = GameLib_AllocEffectsModel(renderWorld); + decalModel = GameLib_GetDecalModel(renderWorld); + beamModel = GameLib_AllocBeamModel(renderWorld); + if (effectsModel != nullptr) { + GameLib_CommitEffectsModel(effectsModel); + } + if (decalModel != nullptr) { + GameLib_CommitDecalModel(decalModel); + } + if (beamModel != nullptr) { + GameLib_CommitBeamModel(beamModel); + } + + effectsModelManager.Init(); + deferredDecalManager.Init(clip, decalModel); + ribbonModelManager.Shutdown(); + weaponTraceManager.Init(); + fxModelRecycler.Init(); + weaponImpactManager.Init(&effectsModelManager, diversity, 64, + localPlayerIndex); + impactManager.Init(&effectsModelManager, diversity, 32, + localPlayerIndex); + initialized = true; +} + +void idGameLibEffects::Shutdown() { + initialized = false; + effectsModelManager.Shutdown(); + deferredDecalManager.Shutdown(); + ribbonModelManager.Shutdown(); + weaponTraceManager.Init(); + fxModelRecycler.Shutdown(); + weaponImpactManager.Shutdown(); + impactManager.Shutdown(); + + if (effectsModel != nullptr) { + GameLib_FreeEffectsModel(effectsModel); + effectsModel = nullptr; + } + if (beamModel != nullptr) { + GameLib_FreeBeamModel(beamModel); + beamModel = nullptr; + } + // The decal model is owned by idRenderWorld and is not freed here in the + // recovered shutdown path. + decalModel = nullptr; +} + +bool idGameLibEffects::Update(const int currentTime, + const int gameMsPerFrame, const int serverCurrentTime) { + if (!initialized) { + return false; + } + deferredDecalManager.Update(currentTime); + if (effectsModel != nullptr) { + GameLib_UpdateEffectsModel(effectsModel, currentTime, gameMsPerFrame); + } + if (beamModel != nullptr) { + GameLib_UpdateBeamModel(beamModel, currentTime); + } + weaponTraceManager.Update(); + const idVec3 origin(0.0f, 0.0f, 0.0f); + const idMat3 axis(1.0f); + weaponImpactManager.Update(origin, axis, currentTime, serverCurrentTime); + impactManager.Update(origin, axis, currentTime, serverCurrentTime); + return true; +} diff --git a/source/engine/gamelib/effects/gamelibeffects.h b/source/engine/gamelib/effects/gamelibeffects.h index 3c815a7..61d5fa5 100644 --- a/source/engine/gamelib/effects/gamelibeffects.h +++ b/source/engine/gamelib/effects/gamelibeffects.h @@ -1,24 +1,44 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effects\gamelibeffects.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/effects/deferreddecalmanager.h" +#include "gamelib/effects/effectsmodelmanager.h" +#include "gamelib/effects/fxmanager.h" +#include "gamelib/effects/impactmanager.h" +#include "gamelib/effects/ribbonmanager.h" +#include "gamelib/effects/weapontracemanager.h" +class idClip; +class idRenderModelBeam; +class idRenderModelDecal; +class idRenderModelEffects; +class idRenderWorld; -// IDA Local Type ordinal 14163; PDB kind: class. -class __declspec(align(4)) idGameLibEffects -{ +class alignas(8) idGameLibEffects { public: - idRenderModelEffects *effectsModel; - idRenderModelDecal *decalModel; - idRenderModelBeam *beamModel; - idEffectsModelManager effectsModelManager; - idDeferredDecalManager deferredDecalManager; - idRibbonModelManager ribbonModelManager; - idWeaponTraceManager weaponTraceManager; - idFXModelRecycler fxModelRecycler; - idImpactManager weaponImpactManager; - idImpactManager impactManager; - bool initialized; + idGameLibEffects(); + ~idGameLibEffects(); + + void Init(idRenderWorld* renderWorld, idClip* clip, float diversity, + int localPlayerIndex); + void Shutdown(); + bool Update(int currentTime, int gameMsPerFrame, int serverCurrentTime); + + idRenderModelEffects* effectsModel; + idRenderModelDecal* decalModel; + idRenderModelBeam* beamModel; + idEffectsModelManager effectsModelManager; + idDeferredDecalManager deferredDecalManager; + idRibbonModelManager ribbonModelManager; + idWeaponTraceManager weaponTraceManager; + idFXModelRecycler fxModelRecycler; + idImpactManager weaponImpactManager; + idImpactManager impactManager; + bool initialized; }; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idFXModelRecycler) == 2720, + "Recovered idFXModelRecycler ABI changed"); +static_assert(sizeof(idGameLibEffects) == 10432, + "Recovered idGameLibEffects ABI changed"); +#endif diff --git a/source/engine/gamelib/effects/impactmanager.cpp b/source/engine/gamelib/effects/impactmanager.cpp new file mode 100644 index 0000000..cccdb20 --- /dev/null +++ b/source/engine/gamelib/effects/impactmanager.cpp @@ -0,0 +1,189 @@ +#include "gamelib/effects/impactmanager.h" + +#include + +const idDeclParticle* GameLib_GetParticleModelDecl( + const idRenderModelParticle* model); +int GameLib_GetParticleEffectDuration(const idDeclParticle* particle, + int numCycles); +void GameLib_ConfigureImpactParticleModel(idRenderModelParticle* model, + const idDeclParticle* particle, const idVec3& origin, + const idMat3& axis, int startTime, float diversity, + float distanceSqr, const idColor& color); +void GameLib_UpdateImpactParticleTransform(idRenderModelParticle* model, + const idVec3& origin, const idMat3& axis); +void GameLib_HideImpactParticleModel(idRenderModelParticle* model); +void GameLib_SerializeImpactBuffer(idSerializer& serializer, + idImpactManager::impactBufferItem_t* buffer, int count, + int& bufferPosition); + +idImpactManager::idImpactManager() + : impacts(0) + , impactBinds(0) + , next(0) + , random(0) + , initialized(false) + , effectsModelManager(nullptr) + , impactBuffer{} + , impactBufferPos(0) + , serializeImpactCount(0) + , localPlayerIndex(0) { +} + +idImpactManager::~idImpactManager() { + Shutdown(); +} + +void idImpactManager::Init( + idEffectsModelManager* const effectsModelManager_, + const float diversity, const int maxImpacts, + const int localPlayerIndex_) { + if (initialized) { + return; + } + effectsModelManager = effectsModelManager_; + localPlayerIndex = localPlayerIndex_; + next = 0; + random.SetSeed(static_cast(diversity * 65535.0f)); + impacts.SetNum((std::max)(0, maxImpacts)); + impactBinds.SetNum(impacts.Num()); + for (int index = 0; index < impacts.Num(); ++index) { + impacts[index] = impactState_t{nullptr, 0, 0}; + impactBinds[index].bindToParent = false; + impactBinds[index].relOrg.Zero(); + impactBinds[index].relRot = idMat3(1.0f); + } + initialized = true; +} + +void idImpactManager::StopImpacts() { + for (int index = 0; index < impacts.Num(); ++index) { + impactState_t& impact = impacts[index]; + if (impact.pmodel != nullptr) { + GameLib_HideImpactParticleModel(impact.pmodel); + if (effectsModelManager != nullptr) { + effectsModelManager->RecycleParticleFxModel( + GameLib_GetParticleModelDecl(impact.pmodel), + impact.pmodel); + } + impact.pmodel = nullptr; + } + impact.startTime = 0; + impact.endTime = 0; + } +} + +void idImpactManager::Shutdown() { + StopImpacts(); + impacts.Clear(); + impactBinds.Clear(); + effectsModelManager = nullptr; + impactBufferPos = 0; + serializeImpactCount = 0; + next = 0; + initialized = false; + localPlayerIndex = 0; +} + +void idImpactManager::UseImpact(const idVec3& origin, + const idMat3& axis, const idDeclParticle* const particle, + const int startTime, const bool bindToParent, + const idVec3& parentOrigin, const idMat3& parentAxis, + const float distanceSqr, const int playerIndex, + const idColor& color, const int numCycles) { + if (!initialized || particle == nullptr || impacts.IsEmpty() || + effectsModelManager == nullptr) { + return; + } + impactState_t& impact = impacts[next]; + impactBindState_t& bind = impactBinds[next]; + if (impact.pmodel != nullptr) { + GameLib_HideImpactParticleModel(impact.pmodel); + effectsModelManager->RecycleParticleFxModel( + GameLib_GetParticleModelDecl(impact.pmodel), impact.pmodel); + } + impact.pmodel = + effectsModelManager->GetNextParticleEffectModel(particle); + if (impact.pmodel == nullptr) { + return; + } + impact.startTime = startTime; + impact.endTime = startTime + + GameLib_GetParticleEffectDuration(particle, numCycles) + 1000; + bind.bindToParent = bindToParent; + if (bindToParent) { + const idMat3 inverseParent = parentAxis.Transpose(); + bind.relOrg = inverseParent * (origin - parentOrigin); + bind.relRot = axis * inverseParent; + } else { + bind.relOrg.Zero(); + bind.relRot = idMat3(1.0f); + } + + impactBufferItem_t& item = impactBuffer[impactBufferPos]; + item.pos = bindToParent ? bind.relOrg : origin; + item.axis = bindToParent ? bind.relRot : axis; + item.prt = particle; + item.bindToParent = bindToParent; + item.playerIndex = playerIndex; + item.serverTime = startTime; + impactBufferPos = (impactBufferPos + 1) % 16; + + GameLib_ConfigureImpactParticleModel(impact.pmodel, particle, + origin, axis, startTime, random.RandomFloat(), distanceSqr, color); + next = (next + 1) % impacts.Num(); +} + +void idImpactManager::Update(const idVec3& parentOrigin, + const idMat3& parentAxis, const int localTime, const int serverTime) { + while (serializeImpactCount > 0) { + const impactBufferItem_t& item = impactBuffer[impactBufferPos]; + if (serverTime != 0 && item.serverTime > serverTime) { + break; + } + impactBufferPos = (impactBufferPos + 1) % 16; + --serializeImpactCount; + if (item.prt == nullptr || item.playerIndex == localPlayerIndex) { + continue; + } + const idVec3 origin = item.bindToParent + ? parentOrigin + parentAxis * item.pos : item.pos; + const idMat3 axis = item.bindToParent + ? parentAxis * item.axis : item.axis; + UseImpact(origin, axis, item.prt, localTime, item.bindToParent, + parentOrigin, parentAxis, 0.0f, 0, + idColor(1.0f, 1.0f, 1.0f, 1.0f), 1); + } + + for (int index = 0; index < impacts.Num(); ++index) { + impactState_t& impact = impacts[index]; + if (impact.pmodel == nullptr) { + continue; + } + if (impact.endTime != 0 && localTime < impact.endTime) { + const impactBindState_t& bind = impactBinds[index]; + if (bind.bindToParent) { + GameLib_UpdateImpactParticleTransform(impact.pmodel, + parentOrigin + parentAxis * bind.relOrg, + bind.relRot * parentAxis); + } + } else { + GameLib_HideImpactParticleModel(impact.pmodel); + if (effectsModelManager != nullptr) { + effectsModelManager->RecycleParticleFxModel( + GameLib_GetParticleModelDecl(impact.pmodel), + impact.pmodel); + } + impact.pmodel = nullptr; + } + } +} + +void idImpactManager::Serialize(idSerializer& serializer) { + const int oldPosition = impactBufferPos; + GameLib_SerializeImpactBuffer(serializer, impactBuffer, 16, + impactBufferPos); + serializeImpactCount = impactBufferPos - oldPosition; + if (serializeImpactCount < 0) serializeImpactCount += 16; + serializeImpactCount = (std::min)(16, serializeImpactCount); +} diff --git a/source/engine/gamelib/effects/impactmanager.h b/source/engine/gamelib/effects/impactmanager.h index 4df3190..63836c5 100644 --- a/source/engine/gamelib/effects/impactmanager.h +++ b/source/engine/gamelib/effects/impactmanager.h @@ -1,50 +1,72 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effects\impactmanager.h -// Recovered logical types: 4 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/effects/effectsmodelmanager.h" +#include "idlib/color.h" +#include "idlib/containers/list.h" +#include "idlib/math/random.h" +class idDeclParticle; +class idRenderModelParticle; +class idSerializer; -// IDA Local Type ordinal 14157; PDB kind: struct. -struct idImpactManager::impactState_t -{ - idRenderModelParticle *pmodel; - int startTime; - int endTime; -}; - -// IDA Local Type ordinal 14159; PDB kind: struct. -struct idImpactManager::impactBindState_t -{ - bool bindToParent; - idVec3 relOrg; - idMat3 relRot; -}; - -// IDA Local Type ordinal 14161; PDB kind: struct. -struct idImpactManager::impactBufferItem_t -{ - idVec3 pos; - idMat3 axis; - const idDeclParticle *prt; - bool bindToParent; - int playerIndex; - int serverTime; -}; - -// IDA Local Type ordinal 14162; PDB kind: class. -class idImpactManager -{ +class idImpactManager { public: - idList impacts; - idList impactBinds; - int next; - idRandom2 random; - bool initialized; - idEffectsModelManager *effectsModelManager; - idImpactManager::impactBufferItem_t impactBuffer[16]; - int impactBufferPos; - int serializeImpactCount; - int localPlayerIndex; + struct impactState_t { + idRenderModelParticle* pmodel; + int startTime; + int endTime; + }; + + struct impactBindState_t { + bool bindToParent; + idVec3 relOrg; + idMat3 relRot; + }; + + struct impactBufferItem_t { + idVec3 pos; + idMat3 axis; + const idDeclParticle* prt; + bool bindToParent; + int playerIndex; + int serverTime; + }; + + idImpactManager(); + ~idImpactManager(); + + void Init(idEffectsModelManager* effectsModelManager, float diversity, + int maxImpacts, int localPlayerIndex); + void Shutdown(); + void StopImpacts(); + void UseImpact(const idVec3& origin, const idMat3& axis, + const idDeclParticle* particle, int startTime, bool bindToParent, + const idVec3& parentOrigin, const idMat3& parentAxis, + float distanceSqr, int playerIndex, const idColor& color, + int numCycles); + void Update(const idVec3& parentOrigin, const idMat3& parentAxis, + int localTime, int serverTime); + void Serialize(idSerializer& serializer); + + idList impacts; + idList impactBinds; + int next; + idRandom2 random; + bool initialized; + idEffectsModelManager* effectsModelManager; + impactBufferItem_t impactBuffer[16]; + int impactBufferPos; + int serializeImpactCount; + int localPlayerIndex; }; + +static_assert(sizeof(idImpactManager::impactState_t) == 12, + "Recovered impact state ABI changed"); +static_assert(sizeof(idImpactManager::impactBindState_t) == 52, + "Recovered impact bind ABI changed"); +static_assert(sizeof(idImpactManager::impactBufferItem_t) == 64, + "Recovered impact buffer ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idImpactManager) == 1084, + "Recovered idImpactManager ABI changed"); +#endif diff --git a/source/engine/gamelib/effects/lasersight.cpp b/source/engine/gamelib/effects/lasersight.cpp new file mode 100644 index 0000000..090f542 --- /dev/null +++ b/source/engine/gamelib/effects/lasersight.cpp @@ -0,0 +1,110 @@ +#include "gamelib/effects/lasersight.h" + +#include + +void GameLib_DrawLaserBeam(idRenderModelBeam* beamModel, + const idMaterial* material, const idVec3& start, + const idVec3& end, float halfWidth, unsigned char alpha); +int GameLib_CreateLaserDecal(idRenderModelDecal* decalModel, + const idMaterial* material, int startTime, float size, float depth, + bool quad); +void GameLib_RemoveLaserDecal(idRenderModelDecal* decalModel, int handle); +void GameLib_UpdateLaserDecal(idRenderModelDecal* decalModel, int handle, + const idVec3& position, const idMat3& axis, float sizeScale); + +idLaserBeam::idLaserBeam() + : material(nullptr) + , height(1.0f) { +} + +void idLaserBeam::Update(idRenderModelBeam* const beamEffects, + const idVec3& startPos, const idVec3& endPos, const float fade) { + if (beamEffects == nullptr) { + return; + } + const int alpha = static_cast(fade * 255.9f); + GameLib_DrawLaserBeam(beamEffects, material, startPos, endPos, + height * 0.5f, static_cast( + (std::max)(0, (std::min)(255, alpha)))); +} + +idLaserSight::idLaserSight() + : initialized(false) + , hidden(false) + , laserBeam() + , laserEndPoint{nullptr, 1.0f, 8.0f, -1, false} + , beamEffects(nullptr) + , decalEffects(nullptr) + , fadeInDuration(1.0f) + , fadeOutDuration(1.0f) { +} + +idLaserSight::~idLaserSight() { + Shutdown(); + beamEffects = nullptr; + decalEffects = nullptr; +} + +void idLaserSight::Init(idRenderModelBeam* const beamEffects_, + idRenderModelDecal* const decalEffects_, + const idMaterial* const laserBeamMtr, const float laserBeamHeight, + const idMaterial* const laserPointMtr, const float laserPointSize, + const float laserPointDepth, const float fadeIn, const float fadeOut, + const bool isQuad) { + beamEffects = beamEffects_; + decalEffects = decalEffects_; + laserBeam.material = laserBeamMtr; + laserBeam.height = laserBeamHeight; + laserEndPoint.material = laserPointMtr; + laserEndPoint.size = laserPointSize; + laserEndPoint.depth = laserPointDepth; + laserEndPoint.quad = isQuad; + laserEndPoint.handle = -1; + fadeInDuration = fadeIn; + fadeOutDuration = fadeOut; + initialized = true; + hidden = false; +} + +void idLaserSight::Shutdown() { + initialized = false; + if (decalEffects != nullptr && laserEndPoint.handle != -1) { + GameLib_RemoveLaserDecal(decalEffects, laserEndPoint.handle); + laserEndPoint.handle = -1; + } +} + +void idLaserSight::Show(const int startTime) { + if (!initialized || !hidden) { + return; + } + hidden = false; + if (decalEffects != nullptr && laserEndPoint.material != nullptr) { + laserEndPoint.handle = GameLib_CreateLaserDecal(decalEffects, + laserEndPoint.material, startTime, laserEndPoint.size, + laserEndPoint.depth, laserEndPoint.quad); + } +} + +void idLaserSight::Hide() { + if (!initialized || hidden) { + return; + } + hidden = true; + if (decalEffects != nullptr && laserEndPoint.handle != -1) { + GameLib_RemoveLaserDecal(decalEffects, laserEndPoint.handle); + laserEndPoint.handle = -1; + } +} + +void idLaserSight::Update(const idVec3& startPos, const idVec3& endPos, + const idMat3& axis, const float sizeScale, const float fade) { + if (hidden) { + return; + } + laserBeam.Update(beamEffects, startPos, endPos, fade); + if (decalEffects != nullptr && laserEndPoint.handle != -1) { + GameLib_UpdateLaserDecal(decalEffects, laserEndPoint.handle, + endPos, axis, sizeScale * fade); + } +} diff --git a/source/engine/gamelib/effects/lasersight.h b/source/engine/gamelib/effects/lasersight.h index 31ef23f..7b73738 100644 --- a/source/engine/gamelib/effects/lasersight.h +++ b/source/engine/gamelib/effects/lasersight.h @@ -1,36 +1,76 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effects\lasersight.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/math/vector.h" +class idMaterial; +class idRenderModelBeam; +class idRenderModelDecal; -// IDA Local Type ordinal 13817; PDB kind: struct. -struct laserSightInfo_t -{ - const idMaterial *laserBeamMtr; - const idMaterial *laserDotMtr; - float laserBeamHeight; - float laserDotSize; - float laserDotDepth; - float laserDotMinSizeScale; - float laserDotMaxSizeScale; - bool laserDotQuad; - float fadeIn; - float fadeOut; +struct laserSightInfo_t { + const idMaterial* laserBeamMtr; + const idMaterial* laserDotMtr; + float laserBeamHeight; + float laserDotSize; + float laserDotDepth; + float laserDotMinSizeScale; + float laserDotMaxSizeScale; + bool laserDotQuad; + float fadeIn; + float fadeOut; }; -// IDA Local Type ordinal 14739; PDB kind: class. -class idLaserSight -{ +class idLaserBeam { public: - bool initialized; - bool hidden; - idLaserBeam laserBeam; - idLaserEndPoint laserEndPoint; - idRenderModelBeam *beamEffects; - idRenderModelDecal *decalEffects; - float fadeInDuration; - float fadeOutDuration; + idLaserBeam(); + void Update(idRenderModelBeam* beamEffects, const idVec3& startPos, + const idVec3& endPos, float fade); + + const idMaterial* material; + float height; }; + +class alignas(4) idLaserEndPoint { +public: + const idMaterial* material; + float size; + float depth; + int handle; + bool quad; +}; + +class idLaserSight { +public: + idLaserSight(); + ~idLaserSight(); + + void Init(idRenderModelBeam* beamEffects, + idRenderModelDecal* decalEffects, const idMaterial* laserBeamMtr, + float laserBeamHeight, const idMaterial* laserPointMtr, + float laserPointSize, float laserPointDepth, float fadeIn, + float fadeOut, bool isQuad); + void Shutdown(); + void Show(int startTime); + void Hide(); + void Update(const idVec3& startPos, const idVec3& endPos, + const idMat3& axis, float sizeScale, float fade); + + bool initialized; + bool hidden; + idLaserBeam laserBeam; + idLaserEndPoint laserEndPoint; + idRenderModelBeam* beamEffects; + idRenderModelDecal* decalEffects; + float fadeInDuration; + float fadeOutDuration; +}; + +static_assert(sizeof(laserSightInfo_t) == 40, + "Recovered laserSightInfo_t ABI changed"); +static_assert(sizeof(idLaserBeam) == 8, + "Recovered idLaserBeam ABI changed"); +static_assert(sizeof(idLaserEndPoint) == 20, + "Recovered idLaserEndPoint ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idLaserSight) == 48, + "Recovered idLaserSight ABI changed"); +#endif diff --git a/source/engine/gamelib/effects/lensflaremanager.cpp b/source/engine/gamelib/effects/lensflaremanager.cpp new file mode 100644 index 0000000..5a51a50 --- /dev/null +++ b/source/engine/gamelib/effects/lensflaremanager.cpp @@ -0,0 +1,119 @@ +#include "gamelib/effects/lensflaremanager.h" + +bool GameLib_CreateFlareModels(idRenderWorld* renderWorld, + const idDeclFlare* flareDecl, idRenderModelFlare*& flareModel, + idRenderModelFlareOcclusionQuad*& occlusionModel); +void GameLib_DeleteFlareModel(idRenderModelFlare* flareModel, + idRenderModelFlareOcclusionQuad* occlusionModel); +void GameLib_ConfigureFlareModels(idRenderModelFlare* flareModel, + idRenderModelFlareOcclusionQuad* occlusionModel, float quadScale, + bool autosprited, bool sunFlare); +void GameLib_UpdateFlareModels(idRenderModelFlare* flareModel, + idRenderModelFlareOcclusionQuad* occlusionModel, + const idVec3& flareOrigin, const idVec3& occlusionOrigin, + const idMat3& axis, float fadeStartRange, float fadeEndRange, + const idVec4& color, float coverage, bool hidden); +void GameLib_ApplyFlareCoverage(idRenderModelFlare* flareModel, + float coverage); +void GameLib_ApplyFlareColor(idRenderModelFlare* flareModel, + const idVec4& color); + +idLensFlareManager::idLensFlareManager() + : flareRenderModel(nullptr) + , flareOcclusionQuadModel(nullptr) + , sunDirection() + , occlusionOffset() + , isAutosprited(false) + , isInitialized(false) { + sunDirection.Zero(); + occlusionOffset.Zero(); +} + +idLensFlareManager::~idLensFlareManager() { + if (flareRenderModel != nullptr || flareOcclusionQuadModel != nullptr) { + GameLib_DeleteFlareModel(flareRenderModel, + flareOcclusionQuadModel); + } + flareRenderModel = nullptr; + flareOcclusionQuadModel = nullptr; + isInitialized = false; +} + +void idLensFlareManager::Init(idRenderWorld* const renderWorld, + const idDeclFlare* const flareDecl, const float quadSize, + const idVec3& sunDirection_, const idVec3& occlusionOffset_, + const bool isAutosprited_) { + if (flareDecl == nullptr) { + return; + } + sunDirection = sunDirection_; + occlusionOffset = occlusionOffset_; + isAutosprited = isAutosprited_; + if (!GameLib_CreateFlareModels(renderWorld, flareDecl, + flareRenderModel, flareOcclusionQuadModel)) { + flareRenderModel = nullptr; + flareOcclusionQuadModel = nullptr; + return; + } + const bool sunFlare = sunDirection.LengthSqr() > 0.0f; + const float quadScale = quadSize < 8.0f ? 8.0f - quadSize : 0.0f; + GameLib_ConfigureFlareModels(flareRenderModel, + flareOcclusionQuadModel, quadScale, isAutosprited, sunFlare); + isInitialized = true; +} + +void idLensFlareManager::ApplyFade(const float fade) { + if (flareRenderModel != nullptr) { + GameLib_ApplyFlareCoverage(flareRenderModel, fade); + } +} + +void idLensFlareManager::ApplyColor(const idVec4& color) { + if (flareRenderModel != nullptr) { + GameLib_ApplyFlareColor(flareRenderModel, color); + } +} + +void idLensFlareManager::StartFlare(const idVec3& origin, + const idMat3& axis, const idVec4& color, const float fadeStartRange, + const float fadeEndRange) { + if (!isInitialized || flareRenderModel == nullptr || + flareOcclusionQuadModel == nullptr) { + return; + } + const bool sunFlare = sunDirection.LengthSqr() > 0.0f; + const idVec3 flareOrigin = sunFlare ? sunDirection : origin; + const idVec3 quadOrigin = sunFlare + ? sunDirection : origin + occlusionOffset; + GameLib_UpdateFlareModels(flareRenderModel, flareOcclusionQuadModel, + flareOrigin, quadOrigin, axis, fadeStartRange, fadeEndRange, + color, 1.0f, false); +} + +void idLensFlareManager::StopFlare() { + if (flareRenderModel == nullptr || flareOcclusionQuadModel == nullptr) { + return; + } + GameLib_UpdateFlareModels(flareRenderModel, flareOcclusionQuadModel, + idVec3(0.0f, 0.0f, 0.0f), idVec3(0.0f, 0.0f, 0.0f), + idMat3(1.0f), 0.0f, 0.0f, + idVec4(1.0f, 1.0f, 1.0f, 1.0f), 0.0f, true); +} + +void idLensFlareManager::Update(const idVec3& origin, + const idMat3& axis, const int time, const bool bindOrigin, + const bool bindAxis) { + (void)time; + if (!isInitialized || flareRenderModel == nullptr || + flareOcclusionQuadModel == nullptr) { + return; + } + if (sunDirection.LengthSqr() == 0.0f && (bindOrigin || bindAxis)) { + const idVec3 flareOrigin = bindOrigin ? origin : sunDirection; + const idVec3 quadOrigin = flareOrigin + occlusionOffset; + GameLib_UpdateFlareModels(flareRenderModel, + flareOcclusionQuadModel, flareOrigin, quadOrigin, + bindAxis ? axis : idMat3(1.0f), 0.0f, 0.0f, + idVec4(1.0f, 1.0f, 1.0f, 1.0f), 1.0f, false); + } +} diff --git a/source/engine/gamelib/effects/lensflaremanager.h b/source/engine/gamelib/effects/lensflaremanager.h index 6744439..765a195 100644 --- a/source/engine/gamelib/effects/lensflaremanager.h +++ b/source/engine/gamelib/effects/lensflaremanager.h @@ -1,204 +1,37 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\effects\lensflaremanager.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/math/vector.h" +class idDeclFlare; +class idRenderModelFlare; +class idRenderModelFlareOcclusionQuad; +class idRenderWorld; -// IDA Local Type ordinal 14210; PDB kind: class. -class __declspec(align(4)) idLensFlareManager -{ +class alignas(4) idLensFlareManager { public: - idRenderModelFlare *flareRenderModel; - idRenderModelFlareOcclusionQuad *flareOcclusionQuadModel; - idVec3 sunDirection; - idVec3 occlusionOffset; - bool isAutosprited; - bool isInitialized; + idLensFlareManager(); + ~idLensFlareManager(); + + void Init(idRenderWorld* renderWorld, const idDeclFlare* flareDecl, + float quadSize, const idVec3& sunDirection, + const idVec3& occlusionOffset, bool isAutosprited); + void StartFlare(const idVec3& origin, const idMat3& axis, + const idVec4& color, float fadeStartRange, float fadeEndRange); + void StopFlare(); + void Update(const idVec3& origin, const idMat3& axis, int time, + bool bindOrigin, bool bindAxis); + void ApplyFade(float fade); + void ApplyColor(const idVec4& color); + + idRenderModelFlare* flareRenderModel; + idRenderModelFlareOcclusionQuad* flareOcclusionQuadModel; + idVec3 sunDirection; + idVec3 occlusionOffset; + bool isAutosprited; + bool isInitialized; }; -// IDA Local Type ordinal 19744; PDB kind: class. -class __declspec(align(8)) idLensFlare : public idDynamicEntity -{ -public: - // Recovered virtual interface; IDA vtable ordinal 19745. - virtual idTypeInfo *GetType(); - virtual ~idLensFlare(); - virtual idEventArg *CallEvent(idEventArg *result, const idEventDef *, const idEventArg *); - virtual bool RespondsTo(const idEventDef *); - virtual idEventArg *InternalCallEvent(idEventArg *result, const idEventDef *, const idEventArg *); - virtual bool InternalRespondsTo(const idEventDef *); - virtual void PostSpawn(); - virtual void Remove(); - virtual void DeleteSubEntities(); - virtual bool Draw(idPlayer *); - virtual void JobSync(); - virtual void Think(); - virtual void PauseThink(); - virtual bool ShouldEnterDormancy(); - virtual bool ShouldLeaveDormancy(); - virtual void DormantBegin(); - virtual void DormantEnd(const int); - virtual idRenderModelInfo *GetRenderModelInfo(); - virtual const idRenderModelInfo *GetRenderModelInfo_2(); - virtual void GetScale(idVec3 *); - virtual void SetScale(const idVec3 *); - virtual void SetModelByName(const char *); - virtual void SetModel(idRenderModel *); - virtual const idMaterial *GetCustomMaterial(); - virtual void SetColor(const idVec4 *); - virtual void SetColor_2(const idColor *); - virtual void SetColor_3(const idVec3 *); - virtual void SetColor_4(float, float, float); - virtual void SetColor_5(float, float, float, float); - virtual void GetColor(idVec4 *); - virtual void GetColor_2(idColor *); - virtual void GetColor_3(idVec3 *); - virtual void Hide(bool); - virtual void Hide_2(); - virtual void Show(); - virtual void GetModelTransform(idVec3 *, idMat3 *); - virtual void GetSoundTransform(idVec3 *, idMat3 *); - virtual void UpdateModelTransform(); - virtual void UpdateFX(); - virtual void ProjectOverlay(const idVec3 *, const idVec3 *, float, const char *); - virtual idPresentable *AllocPresentable(idRenderModel *); - virtual const idComponentTimeLine *GetComponentTimeLine(); - virtual idComponentTimeLine *GetComponentTimeLine_2(); - virtual bool UpdateAnimationControllers(); - virtual void UpdateAttachments(); - virtual const idAnimStack *GetAnimStack(); - virtual idAnimStack *GetAnimStack_2(); - virtual idIndex *GetJointIndexFromTrace(idIndex *result, trace_t); - virtual awPathResult_t ChangeAnimWebState(const char *, const char *); - virtual awPathResult_t ChangeAnimWebState_2(const char *); - virtual awPathResult_t ForceAnimWebState(const char *); - virtual awPathResult_t ChangeAnimWebStateVia(const char *, const char *, const char *, const char *); - virtual awPathResult_t ChangeAnimWebStateVia_2(const char *, const char *); - virtual idAnimWebCmdCtx *GetAnimWebCmdCtx(); - virtual const idAnimWebCmdCtx *GetAnimWebCmdCtx_2(); - virtual const idAnimator_AF *GetAF(); - virtual idAnimator_AF *GetAF_2(); - virtual void PreBind(); - virtual void PostBind(); - virtual void PreUnbind(); - virtual void PostUnbind(); - virtual const splineLocation_t *GetSplineLocation(); - virtual void SetAxis(const idMat3 *); - virtual bool CanDisablePhysics(const idEntity *); - virtual collide_t Collide(const int, trace_t *, const idVec3 *); - virtual collide_t Contact(const int, contactInfo_t *); - virtual void ApplyImpulse(const int, const int, const idVec3 *, const idVec3 *); - virtual void ApplyImpulseFromEntity(const idEntity *, const int, const idVec3 *, const idVec3 *); - virtual void ApplyForce(const int, const int, const idVec3 *, const idVec3 *); - virtual bool Crush(const int); - virtual void ApplyDamage(const int, const int, const idDeclDamage *); - virtual void ActivatePhysics(const int); - virtual void DeactivatePhysics(const int); - virtual void ApplyWaterEffects(const int, const int); - virtual void ApplyWaterSplashEffects(const int, const int, surfTypes_t, idPhysicsCallbacks::splashState_t); - virtual bool TakesDamage(); - virtual void DamageFeedback(idEntity *, idEntity *, const idDeclDamage *, float *); - virtual void KilledNotification(const idEntity *, const idEntity *, const idDeclDamage *, const float); - virtual float Damage(idEntity *, idEntity *, const idDeclDamage *, const float, const idVec3 *, trace_t *); - virtual bool CalcDamageImpulse(const idEntity *, const idEntity *, const idDeclDamage *, const float, const idVec3 *, const trace_t *, idVec3 *, idVec3 *); - virtual bool IsTargetLockable(const idDeclAmmo *); - virtual void AddProjectileLock(); - virtual void RemoveProjectileLock(); - virtual const idScriptObject *GetScriptObject(); - virtual idScriptObject *GetScriptObject_2(); - virtual bool ShouldConstructScriptObjectAtSpawn(); - virtual idThread *GetStateThread(); - virtual int AddThread(const idHandle); - virtual void RemoveThread(const idHandle); - virtual idHandle *GetThread(idHandle *result, const int); - virtual int NumThreads(); - virtual int MaxThreads(); - virtual void ExecuteThread(idThread *); - virtual void ResetFSMWaitThreadIfPossible(idThread *); - virtual bool HandleGuiEvent(const sysEvent_t *); - virtual void ActivateTargets(idEntity *); - virtual bool GetRcCarCanTarget(); - virtual const idBaseHealth *GetHealthComponent(); - virtual idBaseHealth *GetHealthComponent_2(); - virtual const idSmartLootComponent *GetSmartLootComponent(); - virtual idSmartLootComponent *GetSmartLootComponent_2(); - virtual void Teleport(const idVec3 *, const idAngles *); - virtual bool IsPusher(); - virtual const idList,5> *GetTriggerTouchList(); - virtual idList,5> *GetTriggerTouchList_2(); - virtual void TestFunctionality(); - virtual float GetUsableDistance(); - virtual float GetCrosshairIconDistance(); - virtual usableState_t GetUsableState(const idEntity *, const idFocusTrace *); - virtual bool ModifyCrosshairInfo(const idEntity *, const idFocusTrace *, const usableState_t, idCrosshairInfo *); - virtual bool IsCrosshairDisabled(const idEntity *, const idFocusTrace *, const usableState_t); - virtual bool IsCrosshairSubdued(const idEntity *, const idFocusTrace *, const usableState_t); - virtual bool IsEverUsable(const idEntity *); - virtual bool IsCurrentlyUsable(const idEntity *); - virtual bool Use(idEntity *, const usableState_t); - virtual void Dropped(idEntity *, const idDeclInventory *); - virtual const idInventoryCollection *GetInventory(); - virtual idInventoryCollection *GetInventory_2(); - virtual void InventoryAdded(idInventoryItem *, int); - virtual void InventoryRemoved(idInventoryItem *); - virtual const idAttachmentCollection *GetAttachments(); - virtual idAttachmentCollection *GetAttachments_2(); - virtual void EnableAIEventResponse(const idAIEvent::aiEventClass_t); - virtual void DisableAIEventResponse(const idAIEvent::aiEventClass_t); - virtual bool CanReceiveAIEvents(const int); - virtual bool RespondsToAIEvent(const idAIEvent *); - virtual void OnAIEvent(const idAIEvent *); - virtual bool IsDead(); - virtual bool IsDying(); - virtual idFaction *GetFaction(); - virtual const idFaction *GetFaction_2(); - virtual idEntityAuditor *GetAuditor(); - virtual void GetVisibilityPoint(const visPoint_t, idVec3 *); - virtual void GetAimPoint(const aimPoint_t, idVec3 *); - virtual void GetEyePos(idVec3 *); - virtual bool IsVisible(); - virtual idDynamicCoverMgr *GetDynamicCoverMgr(); - virtual const idDynamicCoverMgr *GetDynamicCoverMgr_2(); - virtual const idAAS2 *GetAAS(); - virtual void GetViewStateFOV(idVec3 *, unsigned __int8 *, unsigned __int8 *); - virtual void GetViewStateFOV_2(idVec3 *, unsigned __int8 *, unsigned __int8 *); - virtual int GetNumRepairBotTetherPoints(); - virtual bool GetRepairBotTetherPoint(const int, const int, idVec3 *); - virtual idEntityInterface *CreateEntityInterface(idGame *); - virtual void ShowEditingDialog(); - virtual void UpdateEditingDialog(); - virtual void UpdateModifiedProperties(); - virtual inputSettings_t *GetInputSettings(inputSettings_t *result, idPlayer *); - virtual bool EvaluateControls(usercmd_t *, usercmd_t *); - virtual void CheckForErrors(idList *); - virtual void DebugDrawEntity(const idColor *, int); - virtual void ClientThink(); - virtual void Serialize(idSerializer *); - virtual void PostSerializeRead(bool); - virtual void OnActivate(idEntity *); - virtual void OnMakeActivatable(const bool); - virtual void OnNotifyProgressionOwner(); - - bool startOff; - bool cycleTrigger; - const idDeclFlare *declFlare; - float quadSize; - float fadeStart; - float fadeEnd; - idVec3 sunDirection; - const idSoundShader *sunGlareSnd; - const idDeclTable *sunGlareSndVolumeTbl; - const idDeclTable *sunGlareSndPitchTbl; - const idDeclEnv *sunGlareEnv; - int sunGlareEnvFadeOutTime; - float sunGlareEnvDotThreshold; - const idDeclTable *colorModulateTbl; - float modulateTimeScale; - idVec3 occlusionQuadOffset; - bool autospriteOcclusionQuad; - float maxSunGlareScale; - bool sunGlareEnvActive; - idLensFlareManager flareManager; -}; +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idLensFlareManager) == 36, + "Recovered idLensFlareManager ABI changed"); +#endif diff --git a/source/engine/gamelib/effects/ribbonmanager.cpp b/source/engine/gamelib/effects/ribbonmanager.cpp new file mode 100644 index 0000000..30f3884 --- /dev/null +++ b/source/engine/gamelib/effects/ribbonmanager.cpp @@ -0,0 +1,130 @@ +#include "gamelib/effects/ribbonmanager.h" + +idRibbonModelManager::idRibbonModelManager() + : ribbonEffects() { +} + +idRibbonModelManager::~idRibbonModelManager() { + Shutdown(); +} + +int idRibbonModelManager::FindRibbonEffect( + const idDeclRibbon* const ribbonDecl) const { + for (int index = 0; index < ribbonEffects.Num(); ++index) { + const ribbonEffects_t& effect = ribbonEffects[index]; + if (!effect.ribbons.IsEmpty() && effect.ribbons[0] != nullptr && + effect.ribbons[0]->ribbonDecl == ribbonDecl) { + return index; + } + } + return -1; +} + +void idRibbonModelManager::CreateRibbonEffectModelType( + const idDeclRibbon* const ribbonDecl) { + if (ribbonDecl == nullptr || FindRibbonEffect(ribbonDecl) >= 0 || + ribbonEffects.Num() >= ribbonEffects.Max()) { + return; + } + ribbonEffects_t* const effect = ribbonEffects.Alloc(); + if (effect == nullptr) { + return; + } + effect->next = 0; + for (int index = 0; index < effect->ribbons.Max(); ++index) { + effect->ribbons.Append(new idRibbon(ribbonDecl)); + } +} + +idRibbon* idRibbonModelManager::GetNextRibbonEffectModel( + const idDeclRibbon* const ribbonDecl) { + const int index = FindRibbonEffect(ribbonDecl); + if (index < 0) { + return nullptr; + } + ribbonEffects_t& effect = ribbonEffects[index]; + if (effect.ribbons.IsEmpty()) { + return nullptr; + } + idRibbon* const result = effect.ribbons[effect.next]; + effect.next = (effect.next + 1) % effect.ribbons.Num(); + return result; +} + +void idRibbonModelManager::Shutdown() { + for (int effectIndex = 0; effectIndex < ribbonEffects.Num(); + ++effectIndex) { + ribbonEffects_t& effect = ribbonEffects[effectIndex]; + for (int ribbonIndex = 0; ribbonIndex < effect.ribbons.Num(); + ++ribbonIndex) { + delete effect.ribbons[ribbonIndex]; + } + effect.ribbons.Clear(); + effect.next = 0; + } + ribbonEffects.Clear(); +} + +idRibbonManager::idRibbonManager() + : ribbon(nullptr) + , ribbonDecl(nullptr) + , beamModel(nullptr) + , modelManager(nullptr) + , isInitialized(false) { +} + +idRibbonManager::~idRibbonManager() { + Shutdown(); + ribbonDecl = nullptr; + beamModel = nullptr; + modelManager = nullptr; +} + +void idRibbonManager::Init(const idDeclRibbon* const ribbonDecl_, + idRenderModelBeam* const beamModel_, + idRibbonModelManager* const modelManager_) { + if (ribbonDecl_ == nullptr || beamModel_ == nullptr || + modelManager_ == nullptr) { + return; + } + ribbonDecl = ribbonDecl_; + beamModel = beamModel_; + modelManager = modelManager_; + modelManager->CreateRibbonEffectModelType(ribbonDecl); + isInitialized = true; +} + +void idRibbonManager::Shutdown() { + StopRibbon(); + ribbon = nullptr; + isInitialized = false; +} + +void idRibbonManager::StartRibbon(const int spawnTime, + const idVec3& spawnOrigin) { + if (ribbonDecl == nullptr || modelManager == nullptr) { + return; + } + ribbon = modelManager->GetNextRibbonEffectModel(ribbonDecl); + if (ribbon != nullptr) { + ribbon->StartRibbon(spawnTime, spawnOrigin); + } +} + +void idRibbonManager::StopRibbon() { + if (ribbon != nullptr) { + ribbon->StopRibbon(); + } +} + +bool idRibbonManager::UpdateRibbon(const int time, const idVec3& origin, + const idMat3& axis, const idVec3& velocity, const idVec4& color, + const idVec3& translate) { + if (ribbon == nullptr) { + return false; + } + ribbon->RemoveOldNodes(time); + ribbon->UpdatePosition(time, origin, axis, velocity, color); + ribbon->UpdateGeometry(time, beamModel, axis, translate); + return ribbon->ribbonState != RIBBON_INACTIVE; +} diff --git a/source/engine/gamelib/physics/afbody.cpp b/source/engine/gamelib/physics/afbody.cpp new file mode 100644 index 0000000..62f16e3 --- /dev/null +++ b/source/engine/gamelib/physics/afbody.cpp @@ -0,0 +1,204 @@ +#include "gamelib/physics/afbody.h" + +#include "gamelib/physics/clipmodel.h" +#include "idlib/lib_print.h" + +#include +#include + +void GameLib_SerializeAFBody(idSerializer* serializer, idAFBody& body); + +namespace { + +void ZeroState(AFBodyPState_t& state) { + state.worldOrigin.Zero(); + state.worldAxis = idMat3(1.0f); + state.atRestOrigin.Zero(); + state.atRestAxis = idMat3(1.0f); + std::memset(state.spatialVelocity.p, 0, sizeof(state.spatialVelocity.p)); + std::memset(state.externalForce.p, 0, sizeof(state.externalForce.p)); +} + +} // namespace + +idAFBody::idAFBody() + : name() + , clipModel(nullptr) + , children(4) + , constraints(4) + , motionQuery{0} + , inverseWorldSpatialInertia() + , I() + , invI() + , J() + , s() + , totalForce() + , auxForce() + , acceleration() { + Init(); +} + +idAFBody::idAFBody(const char* const bodyName, + idClipModel* const model, const float density) + : idAFBody() { + name.Set(bodyName != nullptr ? bodyName : "noname"); + SetClipModel(model); + SetDensity(density, idMat3(1.0f)); +} + +idAFBody::~idAFBody() { + if (clipModel != nullptr) { + clipModel->Delete(); + clipModel = nullptr; + } + children.Clear(); + constraints.Clear(); + response = nullptr; + responseIndex = nullptr; +} + +void idAFBody::Init() { + name.Set("noname"); + clipModel = nullptr; + clipMask = 0; + linearFrictionWater = -1.0f; + angularFrictionWater = -1.0f; + linearFriction = -1.0f; + angularFriction = -1.0f; + contactFriction = -1.0f; + bouncyness = -1.0f; + frictionDir.Zero(); + contactMotorDir.Zero(); + contactMotorVelocity = 0.0f; + contactMotorForce = 0.0f; + mass = 1.0f; + invMass = 1.0f; + centerOfMass.Zero(); + inertiaTensor = idMat3(1.0f); + inverseInertiaTensor = idMat3(1.0f); + parent = nullptr; + primaryConstraint = nullptr; + tree = nullptr; + std::memset(&fl, 0, sizeof(fl)); + fl.isZero = 1; + fl.selfCollision = 1; + ZeroState(current); + saved = current; + inverseWorldSpatialInertia.Zero(6, 6); + I.Zero(6, 6); + invI.Zero(6, 6); + J.Zero(6, 6); + s.Zero(); + totalForce.Zero(); + auxForce.Zero(); + acceleration.Zero(); + response = nullptr; + responseIndex = nullptr; + numResponses = 0; + maxAuxiliaryIndex = 0; + maxSubTreeAuxiliaryIndex = 0; +} + +void idAFBody::SetClipModel(idClipModel* const model) { + if (clipModel != nullptr && clipModel != model) { + clipModel->Delete(); + } + clipModel = model; +} + +void idAFBody::SetBouncyness(const float bounce) { + if (bounce < 0.0f || bounce > 1.0f) { + idLibPrint::Warning("idAFBody::SetBouncyness: %.1f out of range", + bounce); + return; + } + bouncyness = bounce; +} + +void idAFBody::SetFriction(const float linear, const float angular, + const float contact) { + if (linear < 0.0f || linear > 1.0f || angular < 0.0f || + angular > 1.0f || contact < 0.0f) { + idLibPrint::Warning("idAFBody::SetFriction: invalid coefficients"); + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; + linearFrictionWater = (std::min)(1.0f, + (std::max)(0.0f, linear * 1.2f)); + angularFrictionWater = (std::min)(1.0f, + (std::max)(0.0f, angular * 1.2f)); +} + +void idAFBody::SetDensity(const float density, + const idMat3& inertiaScale) { + if (clipModel == nullptr) return; + clipModel->GetMassProperties(density, mass, centerOfMass, + inertiaTensor); + if (mass <= 0.0f) { + idLibPrint::Warning("idAFBody::SetDensity: invalid mass for '%s'", + name.c_str()); + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor = idMat3(1.0f); + } + invMass = 1.0f / mass; + inertiaTensor *= inertiaScale; + inverseInertiaTensor = inertiaTensor; + if (!inverseInertiaTensor.InverseSelf()) { + inverseInertiaTensor = idMat3(1.0f); + } +} + +void idAFBody::SetFrictionDirection(const idVec3& direction) { + frictionDir = clipModel != nullptr + ? clipModel->GetAxis().Transpose() * direction : direction; + fl.useFrictionDir = 1; +} + +bool idAFBody::GetFrictionDirection(idVec3& direction) const { + if (!fl.useFrictionDir) return false; + direction = clipModel != nullptr + ? clipModel->GetAxis() * frictionDir : frictionDir; + return true; +} + +void idAFBody::SetContactMotorDirection(const idVec3& direction) { + contactMotorDir = clipModel != nullptr + ? clipModel->GetAxis().Transpose() * direction : direction; + fl.useContactMotorDir = 1; +} + +bool idAFBody::GetContactMotorDirection(idVec3& direction) const { + if (!fl.useContactMotorDir) return false; + direction = clipModel != nullptr + ? clipModel->GetAxis() * contactMotorDir : contactMotorDir; + return true; +} + +idVec3 idAFBody::GetPointVelocity(const idVec3& point) const { + const idVec3 linear(current.spatialVelocity[0], + current.spatialVelocity[1], current.spatialVelocity[2]); + const idVec3 angular(current.spatialVelocity[3], + current.spatialVelocity[4], current.spatialVelocity[5]); + const idVec3 origin = clipModel != nullptr + ? clipModel->GetOrigin() : current.worldOrigin; + return linear + angular.Cross(point - origin); +} + +void idAFBody::AddForce(const idVec3& point, const idVec3& force) { + current.externalForce[0] += force.x; + current.externalForce[1] += force.y; + current.externalForce[2] += force.z; + const idVec3 origin = clipModel != nullptr + ? clipModel->GetOrigin() : current.worldOrigin; + const idVec3 torque = (point - origin).Cross(force); + current.externalForce[3] += torque.x; + current.externalForce[4] += torque.y; + current.externalForce[5] += torque.z; +} + +void idAFBody::Serialize(idSerializer* const serializer) { + if (serializer != nullptr) GameLib_SerializeAFBody(serializer, *this); +} diff --git a/source/engine/gamelib/physics/afbody.h b/source/engine/gamelib/physics/afbody.h index 776ce5d..ba7aa7f 100644 --- a/source/engine/gamelib/physics/afbody.h +++ b/source/engine/gamelib/physics/afbody.h @@ -1,70 +1,106 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\physics\afbody.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/jobs/collisionquery.h" +#include "idlib/containers/list.h" +#include "idlib/math/spatialmat.h" +#include "idlib/text/atomicstring.h" +#ifndef ID_CLIP_QUERY_DEFINED +#define ID_CLIP_QUERY_DEFINED +struct idClipQuery { std::uint64_t index; }; +#endif -// IDA Local Type ordinal 14602; PDB kind: class. -class idAFBody -{ +class idAFConstraint; +class idAFTree; +class idClipModel; +class idSerializer; + +struct AFBodyPState_t { + idVec3 worldOrigin; + idMat3 worldAxis; + idVec3 atRestOrigin; + idMat3 atRestAxis; + idVec6 spatialVelocity; + idVec6 externalForce; +}; + +class alignas(16) idAFBody { public: - // Recovered virtual interface; IDA vtable ordinal 14603. - virtual ~idAFBody(); - virtual void Serialize(idSerializer *); + struct afBodyFlags_t { + std::uint8_t clearClipMaskInSolid : 1; + std::uint8_t noSyncCollide : 1; + std::uint8_t isZero : 1; + std::uint8_t useContactMotorDir : 1; + std::uint8_t useFrictionDir : 1; + std::uint8_t spatialInertiaSparse : 1; + std::uint8_t selfCollision : 1; + std::uint8_t clipMaskSet : 1; + }; - __declspec(align(16)) idAtomicString name; - idClipModel *clipModel; - int clipMask; - float linearFrictionWater; - float angularFrictionWater; - float linearFriction; - float angularFriction; - float contactFriction; - float bouncyness; - idVec3 frictionDir; - idVec3 contactMotorDir; - float contactMotorVelocity; - float contactMotorForce; - float mass; - float invMass; - idVec3 centerOfMass; - idMat3 inertiaTensor; - idMat3 inverseInertiaTensor; - idAFBody *parent; - idList children; - idAFConstraint *primaryConstraint; - idList constraints; - idAFTree *tree; - idAFBody::afBodyFlags_t fl; - AFBodyPState_t current; - AFBodyPState_t saved; - idClipQuery motionQuery; - idSpatialMat inverseWorldSpatialInertia; - idSpatialMat I; - idSpatialMat invI; - idSpatialMat J; - idStaticSpatialVec s; - idStaticSpatialVec totalForce; - idStaticSpatialVec auxForce; - idStaticSpatialVec acceleration; - float *response; - int *responseIndex; - int numResponses; - int maxAuxiliaryIndex; - int maxSubTreeAuxiliaryIndex; + idAFBody(); + idAFBody(const char* name, idClipModel* clipModel, float density); + virtual ~idAFBody(); + virtual void Serialize(idSerializer* serializer); + + void Init(); + void SetClipModel(idClipModel* model); + void SetBouncyness(float bounce); + void SetFriction(float linear, float angular, float contact); + void SetDensity(float density, const idMat3& inertiaScale); + void SetFrictionDirection(const idVec3& direction); + bool GetFrictionDirection(idVec3& direction) const; + void SetContactMotorDirection(const idVec3& direction); + bool GetContactMotorDirection(idVec3& direction) const; + idVec3 GetPointVelocity(const idVec3& point) const; + void AddForce(const idVec3& point, const idVec3& force); + + alignas(16) idAtomicString name; + idClipModel* clipModel; + int clipMask; + float linearFrictionWater; + float angularFrictionWater; + float linearFriction; + float angularFriction; + float contactFriction; + float bouncyness; + idVec3 frictionDir; + idVec3 contactMotorDir; + float contactMotorVelocity; + float contactMotorForce; + float mass; + float invMass; + idVec3 centerOfMass; + idMat3 inertiaTensor; + idMat3 inverseInertiaTensor; + idAFBody* parent; + idList children; + idAFConstraint* primaryConstraint; + idList constraints; + idAFTree* tree; + afBodyFlags_t fl; + AFBodyPState_t current; + AFBodyPState_t saved; + idClipQuery motionQuery; + idSpatialMat inverseWorldSpatialInertia; + idSpatialMat I; + idSpatialMat invI; + idSpatialMat J; + idStaticSpatialVec s; + idStaticSpatialVec totalForce; + idStaticSpatialVec auxForce; + idStaticSpatialVec acceleration; + float* response; + int* responseIndex; + int numResponses; + int maxAuxiliaryIndex; + int maxSubTreeAuxiliaryIndex; }; -// IDA Local Type ordinal 14607; PDB kind: struct. -struct idAFBody::afBodyFlags_t -{ - __int8 clearClipMaskInSolid : 1; - __int8 noSyncCollide : 1; - __int8 isZero : 1; - __int8 useContactMotorDir : 1; - __int8 useFrictionDir : 1; - __int8 spatialInertiaSparse : 1; - __int8 selfCollision : 1; - __int8 clipMaskSet : 1; -}; +static_assert(sizeof(AFBodyPState_t) == 144, + "Recovered articulated-body state ABI changed"); +static_assert(sizeof(idAFBody::afBodyFlags_t) == 1, + "Recovered articulated-body flags ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idAFBody) == 816, + "Recovered idAFBody ABI changed"); +#endif diff --git a/source/engine/gamelib/physics/aftree.cpp b/source/engine/gamelib/physics/aftree.cpp new file mode 100644 index 0000000..8299780 --- /dev/null +++ b/source/engine/gamelib/physics/aftree.cpp @@ -0,0 +1,123 @@ +#include "gamelib/physics/aftree.h" + +#include "gamelib/physics/afbody.h" +#include "gamelib/physics/clipmodel.h" + +#include + +void GameLib_FactorAFConstraintTree(const idAFTree& tree); +void GameLib_SolveAFConstraintTree(const idAFTree& tree, + int auxiliaryIndex); +void GameLib_CalculateAFConstraintResponse(const idAFTree& tree, + const idAFConstraint* constraint, int row, int auxiliaryIndex); +void GameLib_AccumulateAFConstraintForces(const idAFTree& tree, + float timeStep); +void GameLib_DrawAFBodyLink(const idVec3& parentOrigin, + const idVec3& childOrigin, const idVec4& color); + +idAFTree::idAFTree() + : sortedBodies(4) { +} + +void idAFTree::SortBodies_r(idList& sortedList, + idAFBody* const body) { + if (body == nullptr) return; + sortedList.Append(body); + body->tree = this; + for (int index = 0; index < body->children.Num(); ++index) { + idAFBody* const child = body->children[index]; + if (child != nullptr) { + child->parent = body; + SortBodies_r(sortedList, child); + } + } +} + +void idAFTree::SortBodies() { + idList original = sortedBodies; + idList ordered(4); + for (int index = 0; index < original.Num(); ++index) { + idAFBody* const body = original[index]; + if (body != nullptr && body->parent == nullptr) { + SortBodies_r(ordered, body); + } + } + // A malformed or partially deserialized graph may have no root. Preserve + // every body once instead of silently dropping it. + for (int index = 0; index < original.Num(); ++index) { + idAFBody* const body = original[index]; + bool found = false; + for (int sorted = 0; sorted < ordered.Num(); ++sorted) + if (ordered[sorted] == body) { found = true; break; } + if (!found && body != nullptr) ordered.Append(body); + } + sortedBodies = ordered; +} + +void idAFTree::SetMaxSubTreeAuxiliaryIndex() { + for (int index = sortedBodies.Num() - 1; index >= 0; --index) { + idAFBody* const body = sortedBodies[index]; + if (body == nullptr) continue; + body->maxSubTreeAuxiliaryIndex = body->maxAuxiliaryIndex; + for (int childIndex = 0; childIndex < body->children.Num(); + ++childIndex) { + const idAFBody* const child = body->children[childIndex]; + if (child != nullptr) { + body->maxSubTreeAuxiliaryIndex = (std::max)( + body->maxSubTreeAuxiliaryIndex, + child->maxSubTreeAuxiliaryIndex); + } + } + } +} + +void idAFTree::Factor() const { + GameLib_FactorAFConstraintTree(*this); +} + +void idAFTree::Solve(const int auxiliaryIndex) const { + GameLib_SolveAFConstraintTree(*this, auxiliaryIndex); +} + +void idAFTree::Response(const idAFConstraint* const constraint, + const int row, const int auxiliaryIndex) const { + if (constraint != nullptr) { + GameLib_CalculateAFConstraintResponse(*this, constraint, row, + auxiliaryIndex); + } +} + +void idAFTree::CalculateForces(const float timeStep) const { + if (timeStep <= 0.0f) return; + for (int index = 0; index < sortedBodies.Num(); ++index) { + idAFBody* const body = sortedBodies[index]; + if (body == nullptr) continue; + for (int component = 0; component < 6; ++component) { + body->totalForce[component] = + body->current.externalForce[component] + + body->auxForce[component]; + } + body->inverseWorldSpatialInertia.Multiply(body->acceleration, + body->totalForce); + const float inverseTimeStep = 1.0f / timeStep; + for (int component = 0; component < 6; ++component) { + body->acceleration[component] += + body->current.spatialVelocity[component] * inverseTimeStep; + body->s[component] = 0.0f; + } + body->fl.isZero = 1; + } + GameLib_AccumulateAFConstraintForces(*this, timeStep); +} + +void idAFTree::DebugDraw(const idVec4& color) const { + for (int index = 1; index < sortedBodies.Num(); ++index) { + const idAFBody* const body = sortedBodies[index]; + if (body == nullptr || body->parent == nullptr || + body->clipModel == nullptr || body->parent->clipModel == nullptr) { + continue; + } + GameLib_DrawAFBodyLink(body->parent->clipModel->GetOrigin(), + body->clipModel->GetOrigin(), color); + } +} diff --git a/source/engine/gamelib/physics/aftree.h b/source/engine/gamelib/physics/aftree.h index bcfb9c4..0ed345f 100644 --- a/source/engine/gamelib/physics/aftree.h +++ b/source/engine/gamelib/physics/aftree.h @@ -1,17 +1,32 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\physics\aftree.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/containers/list.h" +#include "idlib/math/vector.h" +class idAFBody; +class idAFConstraint; -// IDA Local Type ordinal 14606; PDB kind: class. -class idAFTree -{ +class idAFTree { public: - idList sortedBodies; + idAFTree(); + + void SortBodies(); + void SetMaxSubTreeAuxiliaryIndex(); + void Factor() const; + void Solve(int auxiliaryIndex) const; + void Response(const idAFConstraint* constraint, int row, + int auxiliaryIndex) const; + void CalculateForces(float timeStep) const; + void DebugDraw(const idVec4& color) const; + + idList sortedBodies; + +private: + void SortBodies_r(idList& sortedList, + idAFBody* body); }; -// IDA Local Type ordinal 33695; PDB kind: typedef. -typedef void (__fastcall *Free_t)(void *); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idAFTree) == 16, + "Recovered idAFTree ABI changed"); +#endif diff --git a/source/engine/gamelib/physics/buoyancy.cpp b/source/engine/gamelib/physics/buoyancy.cpp new file mode 100644 index 0000000..e2363e4 --- /dev/null +++ b/source/engine/gamelib/physics/buoyancy.cpp @@ -0,0 +1,116 @@ +#include "gamelib/physics/buoyancy.h" + +#include "gamelib/physics/physics.h" + +#include +#include + +struct idBuoyancySample { + idVec3 position; + float weight; +}; + +int GameLib_GetBuoyancySamples(const idClipModel* clipModel, + idBuoyancySample* samples, int maxSamples); +void GameLib_TransformBuoyancySample(const idClipModel* clipModel, + const idVec3& localPosition, idVec3& worldPosition); +int GameLib_EvaluateBuoyantBodies(idClip* clip, idPhysics* physics, + int clipMask, surfTypes_t surfaceOverride, idBuoyancyResult* results, + int maxResults); +void GameLib_ApplyWaterDamage(const idDeclDamage* damage, + idPhysics* physics, int bodyId, float waterLevel); + +idBuoyancy::idBuoyancy() + : buoyantClipModels() { +} + +void idBuoyancy::CalculateBuoyancyWeights( + const int* const polytopeNumPlanes, + const idPlane* const polytopePlanes, const int numPolytopes, + const idClipModel* const clipModel, float* const weights) { + if (weights == nullptr || numPolytopes <= 0) { + return; + } + for (int polytope = 0; polytope < numPolytopes; ++polytope) { + weights[polytope] = 0.0f; + } + if (polytopeNumPlanes == nullptr || polytopePlanes == nullptr || + clipModel == nullptr) { + return; + } + + idBuoyancySample samples[64]{}; + const int sampleCount = (std::max)(0, (std::min)(64, + GameLib_GetBuoyancySamples(clipModel, samples, 64))); + int firstPlane = 0; + for (int polytope = 0; polytope < numPolytopes; ++polytope) { + const int planeCount = (std::max)(0, + polytopeNumPlanes[polytope]); + for (int sampleIndex = 0; sampleIndex < sampleCount; + ++sampleIndex) { + idVec3 worldPosition; + GameLib_TransformBuoyancySample(clipModel, + samples[sampleIndex].position, worldPosition); + bool inside = true; + for (int planeIndex = 0; planeIndex < planeCount; + ++planeIndex) { + const idPlane& plane = polytopePlanes[ + firstPlane + planeIndex]; + if (plane.Distance(worldPosition) > 0.0f) { + inside = false; + break; + } + } + if (inside) { + weights[polytope] += samples[sampleIndex].weight; + } + } + firstPlane += planeCount; + } +} + +void idBuoyancy::ApplyBuoyancy(idClip* const clip, + idPhysics* const physics, const float timeStep, + const idDeclDamage* const waterDamage, const idVec3& waterCurrent, + const float waterDensity, const float waterViscosity, + const int clipMask, const surfTypes_t surfaceOverride) { + buoyantClipModels.Clear(); + if (clip == nullptr || physics == nullptr || timeStep <= 0.0f) { + return; + } + + idBuoyancyResult results[128]{}; + const int resultCount = (std::max)(0, (std::min)(128, + GameLib_EvaluateBuoyantBodies(clip, physics, clipMask, + surfaceOverride, results, 128))); + float maximumWaterLevel = 0.0f; + for (int index = 0; index < resultCount; ++index) { + const idBuoyancyResult& result = results[index]; + if (result.displacedVolume <= 0.0f) { + continue; + } + buoyantClipModel_t body{}; + body.physicsId = physics->GetPhysicsId(); + body.bodyId = result.bodyId; + buoyantClipModels.Append(body); + + const idVec3 gravity = *physics->GetGravity(); + const idVec3 buoyancyForce = gravity * + (-waterDensity * result.displacedVolume); + const idVec3 relativeVelocity = waterCurrent - + result.linearVelocity; + const idVec3 dragForce = relativeVelocity * + (waterViscosity * result.displacedVolume); + const idVec3 totalForce = buoyancyForce + dragForce; + physics->ApplyForce(result.bodyId, &result.centerOfBuoyancy, + &totalForce); + maximumWaterLevel = (std::max)(maximumWaterLevel, + result.waterLevel); + if (waterDamage != nullptr && result.waterLevel > 0.0f) { + GameLib_ApplyWaterDamage(waterDamage, physics, result.bodyId, + result.waterLevel); + } + } + physics->SetWaterLevel(maximumWaterLevel, -1); + physics->SetWaterViscosity(waterViscosity, -1); +} diff --git a/source/engine/gamelib/physics/buoyancy.h b/source/engine/gamelib/physics/buoyancy.h index 7191fa5..b3f1227 100644 --- a/source/engine/gamelib/physics/buoyancy.h +++ b/source/engine/gamelib/physics/buoyancy.h @@ -1,14 +1,46 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\physics\buoyancy.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "idlib/containers/staticlist.h" +#include "idlib/math/plane.h" +#include "idlib/math/vector.h" +class idClip; +class idClipModel; +class idDeclDamage; +class idPhysics; +enum surfTypes_t : int; -// IDA Local Type ordinal 18845; PDB kind: class. -class idBuoyancy -{ -public: - idStaticList buoyantClipModels; +struct buoyantClipModel_t { + int physicsId; + int bodyId; }; + +struct idBuoyancyResult { + int bodyId; + idVec3 centerOfBuoyancy; + idVec3 linearVelocity; + float displacedVolume; + float waterLevel; +}; + +class idBuoyancy { +public: + idBuoyancy(); + + void CalculateBuoyancyWeights(const int* polytopeNumPlanes, + const idPlane* polytopePlanes, int numPolytopes, + const idClipModel* clipModel, float* weights); + void ApplyBuoyancy(idClip* clip, idPhysics* physics, float timeStep, + const idDeclDamage* waterDamage, const idVec3& waterCurrent, + float waterDensity, float waterViscosity, int clipMask, + surfTypes_t surfaceOverride); + + idStaticList buoyantClipModels; +}; + +static_assert(sizeof(buoyantClipModel_t) == 8, + "Recovered buoyant clip-model ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idBuoyancy) == 1040, + "Recovered idBuoyancy ABI changed"); +#endif diff --git a/source/engine/gamelib/physics/physics.h b/source/engine/gamelib/physics/physics.h index 4749e91..08607e6 100644 --- a/source/engine/gamelib/physics/physics.h +++ b/source/engine/gamelib/physics/physics.h @@ -159,6 +159,8 @@ private: static idPhysics* hash[1024]; static int currentPhysicsId; +protected: + alignas(8) physicsType_t type; int physicsId; int entityNumber; diff --git a/source/engine/gamelib/physics/physics_actor.cpp b/source/engine/gamelib/physics/physics_actor.cpp new file mode 100644 index 0000000..4f62ce0 --- /dev/null +++ b/source/engine/gamelib/physics/physics_actor.cpp @@ -0,0 +1,244 @@ +#include "gamelib/physics/physics_actor.h" + +#include "gamelib/physics/clip.h" +#include "gamelib/physics/clipmodel.h" + +#include +#include +#include + +int GameLib_GetPhysicsCurrentTime(const idPhysics* physics); + +namespace { + +const idBounds kActorZeroBounds{{idVec3(0.0f, 0.0f, 0.0f), + idVec3(0.0f, 0.0f, 0.0f)}}; +const idVec3 kActorZeroOrigin(0.0f, 0.0f, 0.0f); +const idMat3 kActorIdentityAxis(1.0f); + +int ClampClipIndex(const int id) { + return id >= 0 && id < idPhysics_Actor::ACTORCLIP_MAX ? id : 0; +} + +} // namespace + +idPhysics_Actor::idPhysics_Actor() + : idPhysics_DynamicBase() + , clipModels{nullptr, nullptr} + , clipMasks{0, 0} + , clipModelAxis(1.0f) + , mass(100.0f) + , invMass(0.01f) + , hasMaster(false) + , masterYaw(0.0f) + , masterDeltaYaw(0.0f) + , wasHistoryInitialized(false) + , lastHistorySaveTime(0) + , originHistory{} + , yawHistory{} { +} + +idPhysics_Actor::~idPhysics_Actor() { + UnlinkClip(); + clipModels[0] = nullptr; + clipModels[1] = nullptr; +} + +void idPhysics_Actor::SetClipModel(idClipModel* const model, + const float density, const int id, const bool freeOld) { + const int index = ClampClipIndex(id); + if (clipModels[index] != nullptr && clipModels[index] != model && + freeOld) { + clipModels[index]->Delete(); + } + clipModels[index] = model; + if (model != nullptr) { + if (density > 0.0f && index == ACTORCLIP_DEFAULT) { + float modelMass = 0.0f; + idVec3 center; + idMat3 inertia; + model->GetMassProperties(density, modelMass, center, inertia); + if (modelMass > 0.0f) SetMass(modelMass, index); + } + model->Link(GetEntityNumber(), GetEntityNumber(), index, + model->GetOrigin(), clipModelAxis); + } +} + +idClipModel* idPhysics_Actor::GetClipModel(const int id) { + return clipModels[ClampClipIndex(id)]; +} + +int idPhysics_Actor::GetNumClipModels() { + return (clipModels[0] != nullptr ? 1 : 0) + + (clipModels[1] != nullptr ? 1 : 0); +} + +void idPhysics_Actor::SetMass(const float newMass, const int id) { + (void)id; + mass = (std::max)(1.0e-6f, newMass); + invMass = 1.0f / mass; +} +float idPhysics_Actor::GetMass(int) { return mass; } + +void idPhysics_Actor::SetContents(const int contents, const int id) { + idClipModel* const model = clipModels[ClampClipIndex(id)]; + if (model != nullptr) model->SetContents(contents); +} +int idPhysics_Actor::GetContents(const int id) { + idClipModel* const model = clipModels[ClampClipIndex(id)]; + return model != nullptr ? model->GetContents() : 0; +} +void idPhysics_Actor::SetClipMask(const int mask, const int id) { + clipMasks[ClampClipIndex(id)] = mask; + if (id <= 0) idPhysics_DynamicBase::SetClipMask(mask, id); +} +int idPhysics_Actor::GetClipMask(const int id) { + return clipMasks[ClampClipIndex(id)]; +} +const idBounds* idPhysics_Actor::GetBounds(const int id) { + idClipModel* const model = clipModels[ClampClipIndex(id)]; + return model != nullptr ? &model->GetBounds() : &kActorZeroBounds; +} +const idBounds* idPhysics_Actor::GetAbsBounds(const int id) { + idClipModel* const model = clipModels[ClampClipIndex(id)]; + return model != nullptr ? &model->GetAbsBounds() : &kActorZeroBounds; +} +const idVec3* idPhysics_Actor::GetOrigin(const int id) { + idClipModel* const model = clipModels[ClampClipIndex(id)]; + return model != nullptr ? &model->GetOrigin() : &kActorZeroOrigin; +} +const idMat3* idPhysics_Actor::GetAxis(const int id) { + idClipModel* const model = clipModels[ClampClipIndex(id)]; + return model != nullptr ? &model->GetAxis() : &kActorIdentityAxis; +} + +void idPhysics_Actor::SetGravity(const idVec3* const gravity) { + idPhysics_DynamicBase::SetGravity(gravity); + SetClipModelAxis(); +} + +bool idPhysics_Actor::IsPushable(int) { return false; } + +void idPhysics_Actor::DisableClip() { + DisableClip(ACTORCLIP_DEFAULT); + DisableClip(ACTORCLIP_PLAYER); +} +void idPhysics_Actor::EnableClip() { + EnableClip(ACTORCLIP_DEFAULT); + EnableClip(ACTORCLIP_PLAYER); +} +void idPhysics_Actor::DisableClip(const actorClipModel_t type_) { + if (clipModels[type_] != nullptr) clipModels[type_]->Disable(); +} +void idPhysics_Actor::EnableClip(const actorClipModel_t type_) { + if (clipModels[type_] != nullptr) clipModels[type_]->Enable(); +} +void idPhysics_Actor::UnlinkClip() { + for (idClipModel* model : clipModels) + if (model != nullptr) model->Unlink(); +} +void idPhysics_Actor::LinkClip() { + LinkClip(*GetOrigin(0), clipModelAxis); +} +void idPhysics_Actor::LinkClip(const idVec3& origin, + const idMat3& axis) { + for (int index = 0; index < ACTORCLIP_MAX; ++index) { + if (clipModels[index] != nullptr) { + clipModels[index]->Link(GetEntityNumber(), GetEntityNumber(), + index, origin, axis); + } + } +} + +void idPhysics_Actor::SetClipModelAxis() { + idVec3 down = gravityNormal; + if (down.NormalizeFast() == 0.0f) { + clipModelAxis = idMat3(1.0f); + return; + } + idVec3 right = std::fabs(down.z) < 0.7f + ? down.Cross(idVec3(0.0f, 0.0f, 1.0f)) + : down.Cross(idVec3(0.0f, 1.0f, 0.0f)); + right.NormalizeFast(); + idVec3 forward = right.Cross(down); + forward.NormalizeFast(); + clipModelAxis[0] = forward; + clipModelAxis[1] = right; + clipModelAxis[2] = -down; +} + +bool idPhysics_Actor::EvaluateContacts() { + ClearContacts(); + for (idClipModel* model : clipModels) + if (model != nullptr) AddGroundContacts(model, 12 - contacts.Num()); + return contacts.Num() > 0; +} + +void idPhysics_Actor::ClipTranslation(trace_t* const results, + const idVec3* const translation, const idClipModel* const model) { + idPhysics_DynamicBase::ClipTranslation(results, translation, + model != nullptr ? model : clipModels[0]); +} + +void idPhysics_Actor::ClipRotation(trace_t* const results, + const idRotation* const rotation, const idClipModel* const model) { + if (results == nullptr) return; + const idClipModel* moving = model != nullptr ? model : clipModels[0]; + if (clip == nullptr || moving == nullptr || rotation == nullptr) { + std::memset(results, 0, sizeof(*results)); + results->fraction = 1.0f; + return; + } + clip->Rotation(results, moving->GetOrigin(), *rotation, moving, + moving->GetAxis(), clipMasks[0], GetEntityNumber(), false, + "idPhysics_Actor::ClipRotation"); +} + +int idPhysics_Actor::ClipContents(const idClipModel* const model, + const int id) { + idClipModel* const self = clipModels[ClampClipIndex(id)]; + if (clip == nullptr || model == nullptr || self == nullptr) return 0; + trace_t result{}; + clip->ContentsModel(result, self->GetOrigin(), self, self->GetAxis(), + clipMasks[ClampClipIndex(id)], model->GetOrigin(), model, + model->GetAxis()); + return result.c.contentFlags; +} + +float idPhysics_Actor::GetMasterDeltaYaw() const { return masterDeltaYaw; } + +void idPhysics_Actor::RememberHistorySample() { + const int time = GameLib_GetPhysicsCurrentTime(this); + const idVec3 currentOrigin = *const_cast(this)->GetOrigin(0); + if (!wasHistoryInitialized) { + for (int i = 0; i < 16; ++i) { + originHistory[i] = currentOrigin; + yawHistory[i] = masterYaw; + } + wasHistoryInitialized = true; + } else { + for (int i = 15; i > 0; --i) { + originHistory[i] = originHistory[i - 1]; + yawHistory[i] = yawHistory[i - 1]; + } + originHistory[0] = currentOrigin; + yawHistory[0] = masterYaw; + } + lastHistorySaveTime = time; +} + +idVec3 idPhysics_Actor::GetOriginHistory(const int millisecondsAgo) const { + if (!wasHistoryInitialized) return kActorZeroOrigin; + const int sample = (std::max)(0, (std::min)(15, + (millisecondsAgo + 7) / 16)); + return originHistory[sample]; +} + +idVec3 idPhysics_Actor::GetVelocityHistory(const int millisecondsAgo) const { + if (!wasHistoryInitialized) return kActorZeroOrigin; + const int sample = (std::max)(1, (std::min)(15, + (millisecondsAgo + 7) / 16)); + return (originHistory[0] - originHistory[sample]) * + (1000.0f / static_cast(sample * 16)); +} diff --git a/source/engine/gamelib/physics/physics_actor.h b/source/engine/gamelib/physics/physics_actor.h index 1e24aa7..adbdaa8 100644 --- a/source/engine/gamelib/physics/physics_actor.h +++ b/source/engine/gamelib/physics/physics_actor.h @@ -1,115 +1,69 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\physics\physics_actor.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/physics/physics_dynamicbase.h" +#include "idlib/math/rotation.h" - -// IDA Local Type ordinal 2021; PDB kind: enum. -enum idPhysics_Actor::actorClipModel_t : __int32 -{ - ACTORCLIP_DEFAULT = 0x0, - ACTORCLIP_PLAYER = 0x1, - ACTORCLIP_MAX = 0x2, -}; - -// IDA Local Type ordinal 15362; PDB kind: class. -class idPhysics_Actor : public idPhysics_DynamicBase -{ +class idPhysics_Actor : public idPhysics_DynamicBase { public: - // Recovered virtual interface; IDA vtable ordinal 15363. - virtual ~idPhysics_Actor(); - virtual void SetClipModel(idClipModel *, float, int, bool); - virtual idClipModel *GetClipModel(int); - virtual int GetNumClipModels(); - virtual void SetMass(float, int); - virtual float GetMass(int); - virtual void SetContents(int, int); - virtual int GetContents(int); - virtual void SetClipMask(int, int); - virtual int GetClipMask(int); - virtual const idBounds *GetBounds(int); - virtual const idBounds *GetAbsBounds(int); - virtual void SetOrigin(const idVec3 *, int); - virtual void SetAxis(const idMat3 *, int); - virtual void Translate(const idVec3 *, int); - virtual void Rotate(const idRotation *, int); - virtual const idVec3 *GetOrigin(int); - virtual const idMat3 *GetAxis(int); - virtual const idVec3 *GetLocalOrigin(int); - virtual const idMat3 *GetLocalAxis(int); - virtual void SetLinearVelocity(const idVec3 *, int); - virtual void SetAngularVelocity(const idVec3 *, int); - virtual idVec3 *GetLinearVelocity(idVec3 *result, int); - virtual idVec3 *GetAngularVelocity(idVec3 *result, int); - virtual void SetGravity(const idVec3 *); - virtual const idVec3 *GetGravity(); - virtual const idVec3 *GetGravityNormal(); - virtual void SetWaterLevel(float, int); - virtual float GetWaterLevel(int); - virtual void SetWaterViscosity(float, int); - virtual float GetWaterViscosity(int); - virtual void SetWaterEntNum(int); - virtual int GetWaterEntNum(); - virtual void SetWaterSurfaceWrldHeight(float); - virtual float GetWaterSurfaceWrldHeight(); - virtual void GetImpactInfo(const int, const idVec3 *, impactInfo_t *); - virtual void ApplyImpulse(const int, const idVec3 *, const idVec3 *); - virtual void ApplyForce(const int, const idVec3 *, const idVec3 *); - virtual void Activate(); - virtual void PutToRest(); - virtual bool IsAtRest(); - virtual bool IsPushable(int); - virtual void SaveState(); - virtual void RestoreState(); - virtual bool Evaluate(int, int); - virtual void UpdateTime(int); - virtual void ClipTranslation(trace_t *, const idVec3 *, const idClipModel *); - virtual void ClipRotation(trace_t *, const idRotation *, const idClipModel *); - virtual int ClipContents(const idClipModel *, int); - virtual void DisableClip(); - virtual void EnableClip(); - virtual void UnlinkClip(); - virtual void LinkClip(); - virtual bool EvaluateContacts(); - virtual int GetNumContacts(); - virtual const contactInfo_t *GetContact(int); - virtual void ClearContacts(); - virtual void AddContactPhysics(idPhysics *); - virtual void RemoveContactPhysics(idPhysics *); - virtual int GetNumContactPhysics(); - virtual idPhysics *GetContactPhysics(int); - virtual void ActivateContactPhysics(); - virtual bool HasGroundContacts(); - virtual bool IsGroundEntity(int); - virtual bool IsGroundClipModel(int, int); - virtual void SetPushed(int); - virtual idVec3 *GetPushedLinearVelocity(idVec3 *result, const int); - virtual idVec3 *GetPushedAngularVelocity(idVec3 *result, const int); - virtual void SetMaster(bool, const idVec3 *, const idMat3 *, const bindFlags_t); - virtual void SetLocalOrigin(const idVec3 *, int); - virtual void SetLocalAxis(const idMat3 *, int); - virtual int GetBlockingEntityNum(); - virtual int GetLinearEndTime(); - virtual int GetAngularEndTime(); - virtual bool IsInNonResidentCollisionArea(bool); - virtual bool IsOutsideWorld(); - virtual const idMat3 *GetGravityAxis(); - virtual void DisableClip_2(const idPhysics_Actor::actorClipModel_t); - virtual void EnableClip_2(const idPhysics_Actor::actorClipModel_t); - virtual void LinkClip_2(const idVec3 *, const idMat3 *); + enum actorClipModel_t : int { + ACTORCLIP_DEFAULT = 0, + ACTORCLIP_PLAYER = 1, + ACTORCLIP_MAX = 2 + }; - idClipModel *clipModels[2]; - int clipMasks[2]; - idMat3 clipModelAxis; - float mass; - float invMass; - bool hasMaster; - float masterYaw; - float masterDeltaYaw; - bool wasHistoryInitialized; - int lastHistorySaveTime; - idVec3 originHistory[16]; - float yawHistory[16]; + idPhysics_Actor(); + ~idPhysics_Actor() override; + + void SetClipModel(idClipModel*, float, int, bool) override; + idClipModel* GetClipModel(int) override; + int GetNumClipModels() override; + void SetMass(float, int) override; + float GetMass(int) override; + void SetContents(int, int) override; + int GetContents(int) override; + void SetClipMask(int, int) override; + int GetClipMask(int) override; + const idBounds* GetBounds(int) override; + const idBounds* GetAbsBounds(int) override; + const idVec3* GetOrigin(int) override; + const idMat3* GetAxis(int) override; + void SetGravity(const idVec3*) override; + bool IsPushable(int) override; + void ClipTranslation(trace_t*, const idVec3*, + const idClipModel*) override; + void ClipRotation(trace_t*, const idRotation*, + const idClipModel*) override; + int ClipContents(const idClipModel*, int) override; + void DisableClip() override; + void EnableClip() override; + void UnlinkClip() override; + void LinkClip() override; + bool EvaluateContacts() override; + + void DisableClip(actorClipModel_t clipType); + void EnableClip(actorClipModel_t clipType); + void LinkClip(const idVec3& origin, const idMat3& axis); + void SetClipModelAxis(); + float GetMasterDeltaYaw() const; + void RememberHistorySample(); + idVec3 GetOriginHistory(int millisecondsAgo) const; + idVec3 GetVelocityHistory(int millisecondsAgo) const; + + idClipModel* clipModels[ACTORCLIP_MAX]; + int clipMasks[ACTORCLIP_MAX]; + idMat3 clipModelAxis; + float mass; + float invMass; + bool hasMaster; + float masterYaw; + float masterDeltaYaw; + bool wasHistoryInitialized; + int lastHistorySaveTime; + idVec3 originHistory[16]; + float yawHistory[16]; }; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idPhysics_Actor) == 456, + "Recovered idPhysics_Actor ABI changed"); +#endif diff --git a/source/engine/gamelib/physics/physics_dynamicbase.cpp b/source/engine/gamelib/physics/physics_dynamicbase.cpp new file mode 100644 index 0000000..43b7358 --- /dev/null +++ b/source/engine/gamelib/physics/physics_dynamicbase.cpp @@ -0,0 +1,238 @@ +#include "gamelib/physics/physics_dynamicbase.h" + +#include "gamelib/physics/clip.h" +#include "gamelib/physics/clipmodel.h" + +#include +#include + +void GameLib_ActivateContactPhysics(idPhysics* physics); +void GameLib_DrawPhysicsVelocity(const idVec3& origin, + const idVec3& linearVelocity, const idVec3& angularVelocity, + float angularScale); + +idPhysics_DynamicBase::idPhysics_DynamicBase() + : idPhysics() + , clipMask(0) + , gravityVector(0.0f, 0.0f, -1066.0f) + , gravityNormal(0.0f, 0.0f, -1.0f) + , contacts(4) + , contactPhysicsIds(4) + , waterLevel(0.0f) + , waterViscosity(0.0f) { +} + +idPhysics_DynamicBase::~idPhysics_DynamicBase() { + contacts.Clear(); + contactPhysicsIds.Clear(); +} + +void idPhysics_DynamicBase::SetClipMask(const int mask, const int id) { + (void)id; + clipMask = mask; +} + +const idBounds* idPhysics_DynamicBase::GetAbsBounds(const int id) { + const idClipModel* const model = GetClipModel(id); + if (model != nullptr) { + return &model->GetAbsBounds(); + } + static const idBounds empty{{idVec3(0.0f, 0.0f, 0.0f), + idVec3(0.0f, 0.0f, 0.0f)}}; + return ∅ +} + +void idPhysics_DynamicBase::ClipTranslation(trace_t* const results, + const idVec3* const translation, const idClipModel* const model) { + if (results == nullptr) { + return; + } + const idClipModel* const clipModel = model != nullptr + ? model : GetClipModel(0); + if (clip == nullptr || clipModel == nullptr || translation == nullptr) { + std::memset(results, 0, sizeof(*results)); + results->fraction = 1.0f; + return; + } + const idVec3& start = clipModel->GetOrigin(); + clip->Translation(results, start, start + *translation, clipModel, + clipModel->GetAxis(), clipMask, GetEntityNumber(), false, + "idPhysics_DynamicBase::ClipTranslation"); +} + +void idPhysics_DynamicBase::SetGravity(const idVec3* const gravity) { + if (gravity == nullptr) { + return; + } + gravityVector = *gravity; + gravityNormal = gravityVector; + if (gravityNormal.NormalizeFast() == 0.0f) { + gravityNormal.Zero(); + } +} + +void idPhysics_DynamicBase::SetWaterLevel(const float level, const int id) { + (void)id; + waterLevel = (std::max)(0.0f, (std::min)(1.0f, level)); +} + +float idPhysics_DynamicBase::GetWaterLevel(const int id) { + (void)id; + return waterLevel; +} + +void idPhysics_DynamicBase::SetWaterViscosity(const float viscosity, + const int id) { + (void)id; + waterViscosity = (std::max)(0.0f, viscosity); +} + +float idPhysics_DynamicBase::GetWaterViscosity(const int id) { + (void)id; + return waterViscosity; +} + +const contactInfo_t* idPhysics_DynamicBase::GetContact(const int index) { + return index >= 0 && index < contacts.Num() ? &contacts[index] : nullptr; +} + +void idPhysics_DynamicBase::ClearContacts() { + for (int index = 0; index < contacts.Num(); ++index) { + idPhysics* const other = idPhysics::GetPhysicsForId( + contacts[index].physicsId); + if (other != nullptr && other != this) { + other->RemoveContactPhysics(this); + } + } + contacts.Clear(); +} + +void idPhysics_DynamicBase::AddContactPhysics(idPhysics* const physics) { + if (physics == nullptr || physics == this) { + return; + } + const int id = physics->GetPhysicsId(); + for (int index = 0; index < contactPhysicsIds.Num(); ++index) { + if (contactPhysicsIds[index] == id) { + return; + } + } + contactPhysicsIds.Append(id); +} + +void idPhysics_DynamicBase::RemoveContactPhysics(idPhysics* const physics) { + if (physics == nullptr) { + return; + } + const int id = physics->GetPhysicsId(); + for (int index = contactPhysicsIds.Num() - 1; index >= 0; --index) { + if (contactPhysicsIds[index] == id) { + contactPhysicsIds.RemoveIndexFast(index); + } + } +} + +idPhysics* idPhysics_DynamicBase::GetContactPhysics(const int index) { + if (index < 0 || index >= contactPhysicsIds.Num()) { + return nullptr; + } + return idPhysics::GetPhysicsForId(contactPhysicsIds[index]); +} + +void idPhysics_DynamicBase::ActivateContactPhysics() { + for (int index = contactPhysicsIds.Num() - 1; index >= 0; --index) { + idPhysics* const physics = idPhysics::GetPhysicsForId( + contactPhysicsIds[index]); + if (physics == nullptr) { + contactPhysicsIds.RemoveIndexFast(index); + continue; + } + GameLib_ActivateContactPhysics(physics); + } +} + +bool idPhysics_DynamicBase::HasGroundContacts() { + for (int index = 0; index < contacts.Num(); ++index) { + if (contacts[index].normal.Dot(-gravityNormal) > 0.0f) { + return true; + } + } + return false; +} + +bool idPhysics_DynamicBase::IsGroundEntity(const int entityNumber_) { + for (int index = 0; index < contacts.Num(); ++index) { + if (contacts[index].entityNum == entityNumber_ && + contacts[index].normal.Dot(gravityNormal) < -0.1f) { + return true; + } + } + return false; +} + +bool idPhysics_DynamicBase::IsGroundClipModel(const int entityNumber_, + const int bodyId) { + for (int index = 0; index < contacts.Num(); ++index) { + if (contacts[index].entityNum == entityNumber_ && + contacts[index].bodyId == bodyId && + contacts[index].normal.Dot(gravityNormal) < -0.98480773f) { + return true; + } + } + return false; +} + +void idPhysics_DynamicBase::AddContactPhysicsForContacts() { + for (int index = 0; index < contacts.Num(); ++index) { + idPhysics* const physics = idPhysics::GetPhysicsForId( + contacts[index].physicsId); + if (physics != nullptr && physics != this) { + physics->AddContactPhysics(this); + } + } +} + +void idPhysics_DynamicBase::AddGroundContacts(const idClipModel* const model, + const int maxContacts) { + if (clip == nullptr || model == nullptr || maxContacts <= 0) { + return; + } + contactsResult_t results{}; + clip->Contacts(&results, model->GetOrigin(), gravityNormal, 0.25f, + model, model->GetAxis(), clipMask, GetEntityNumber(), + "idPhysics_DynamicBase::AddGroundContacts"); + const int count = (std::min)(results.numContacts, maxContacts); + for (int index = 0; index < count; ++index) { + contacts.Append(results.contacts[index]); + UpdateCollisionResidency(results.contacts[index]); + } + AddContactPhysicsForContacts(); +} + +bool idPhysics_DynamicBase::IsOutsideWorld() { + if (clip == nullptr) { + return false; + } + const idBounds& world = clip->GetWorldBounds(); + const idBounds* const object = GetAbsBounds(-1); + return (*object)[1].x < world[0].x - 1024.0f || + (*object)[1].y < world[0].y - 1024.0f || + (*object)[1].z < world[0].z - 1024.0f || + (*object)[0].x > world[1].x + 1024.0f || + (*object)[0].y > world[1].y + 1024.0f || + (*object)[0].z > world[1].z + 1024.0f; +} + +void idPhysics_DynamicBase::DrawVelocity(const int id, + const float linearScale, const float angularScale) const { + idVec3 linear; + idVec3 angular; + const_cast(this)->GetLinearVelocity(&linear, id); + const_cast(this)->GetAngularVelocity(&angular, id); + linear = linear * linearScale; + const idVec3* const origin = + const_cast(this)->GetOrigin(id); + if (origin != nullptr) { + GameLib_DrawPhysicsVelocity(*origin, linear, angular, angularScale); + } +} diff --git a/source/engine/gamelib/physics/physics_dynamicbase.h b/source/engine/gamelib/physics/physics_dynamicbase.h index 71c6b59..da712f1 100644 --- a/source/engine/gamelib/physics/physics_dynamicbase.h +++ b/source/engine/gamelib/physics/physics_dynamicbase.h @@ -1,98 +1,54 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\physics\physics_dynamicbase.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/physics/physics.h" +#include "idlib/bv/bounds.h" +#include "idlib/containers/list.h" - -// IDA Local Type ordinal 14609; PDB kind: class. -class __declspec(align(8)) idPhysics_DynamicBase : public idPhysics -{ +class alignas(8) idPhysics_DynamicBase : public idPhysics { public: - // Recovered virtual interface; IDA vtable ordinal 14612. - virtual ~idPhysics_DynamicBase(); - virtual void SetClipModel(idClipModel *, float, int, bool); - virtual idClipModel *GetClipModel(int); - virtual int GetNumClipModels(); - virtual void SetMass(float, int); - virtual float GetMass(int); - virtual void SetContents(int, int); - virtual int GetContents(int); - virtual void SetClipMask(int, int); - virtual int GetClipMask(int); - virtual const idBounds *GetBounds(int); - virtual const idBounds *GetAbsBounds(int); - virtual void SetOrigin(const idVec3 *, int); - virtual void SetAxis(const idMat3 *, int); - virtual void Translate(const idVec3 *, int); - virtual void Rotate(const idRotation *, int); - virtual const idVec3 *GetOrigin(int); - virtual const idMat3 *GetAxis(int); - virtual const idVec3 *GetLocalOrigin(int); - virtual const idMat3 *GetLocalAxis(int); - virtual void SetLinearVelocity(const idVec3 *, int); - virtual void SetAngularVelocity(const idVec3 *, int); - virtual idVec3 *GetLinearVelocity(idVec3 *result, int); - virtual idVec3 *GetAngularVelocity(idVec3 *result, int); - virtual void SetGravity(const idVec3 *); - virtual const idVec3 *GetGravity(); - virtual const idVec3 *GetGravityNormal(); - virtual void SetWaterLevel(float, int); - virtual float GetWaterLevel(int); - virtual void SetWaterViscosity(float, int); - virtual float GetWaterViscosity(int); - virtual void SetWaterEntNum(int); - virtual int GetWaterEntNum(); - virtual void SetWaterSurfaceWrldHeight(float); - virtual float GetWaterSurfaceWrldHeight(); - virtual void GetImpactInfo(const int, const idVec3 *, impactInfo_t *); - virtual void ApplyImpulse(const int, const idVec3 *, const idVec3 *); - virtual void ApplyForce(const int, const idVec3 *, const idVec3 *); - virtual void Activate(); - virtual void PutToRest(); - virtual bool IsAtRest(); - virtual bool IsPushable(int); - virtual void SaveState(); - virtual void RestoreState(); - virtual bool Evaluate(int, int); - virtual void UpdateTime(int); - virtual void ClipTranslation(trace_t *, const idVec3 *, const idClipModel *); - virtual void ClipRotation(trace_t *, const idRotation *, const idClipModel *); - virtual int ClipContents(const idClipModel *, int); - virtual void DisableClip(); - virtual void EnableClip(); - virtual void UnlinkClip(); - virtual void LinkClip(); - virtual bool EvaluateContacts(); - virtual int GetNumContacts(); - virtual const contactInfo_t *GetContact(int); - virtual void ClearContacts(); - virtual void AddContactPhysics(idPhysics *); - virtual void RemoveContactPhysics(idPhysics *); - virtual int GetNumContactPhysics(); - virtual idPhysics *GetContactPhysics(int); - virtual void ActivateContactPhysics(); - virtual bool HasGroundContacts(); - virtual bool IsGroundEntity(int); - virtual bool IsGroundClipModel(int, int); - virtual void SetPushed(int); - virtual idVec3 *GetPushedLinearVelocity(idVec3 *result, const int); - virtual idVec3 *GetPushedAngularVelocity(idVec3 *result, const int); - virtual void SetMaster(bool, const idVec3 *, const idMat3 *, const bindFlags_t); - virtual void SetLocalOrigin(const idVec3 *, int); - virtual void SetLocalAxis(const idMat3 *, int); - virtual int GetBlockingEntityNum(); - virtual int GetLinearEndTime(); - virtual int GetAngularEndTime(); - virtual bool IsInNonResidentCollisionArea(bool); - virtual bool IsOutsideWorld(); + idPhysics_DynamicBase(); + ~idPhysics_DynamicBase() override; - int clipMask; - idVec3 gravityVector; - idVec3 gravityNormal; - idList contacts; - idList contactPhysicsIds; - float waterLevel; - float waterViscosity; + void SetClipMask(int mask, int id) override; + int GetClipMask(int id) override { (void)id; return clipMask; } + const idBounds* GetAbsBounds(int id) override; + void ClipTranslation(trace_t* results, const idVec3* translation, + const idClipModel* model) override; + void SetGravity(const idVec3* gravity) override; + const idVec3* GetGravity() override { return &gravityVector; } + const idVec3* GetGravityNormal() override { return &gravityNormal; } + void SetWaterLevel(float level, int id) override; + float GetWaterLevel(int id) override; + void SetWaterViscosity(float viscosity, int id) override; + float GetWaterViscosity(int id) override; + int GetNumContacts() override { return contacts.Num(); } + const contactInfo_t* GetContact(int index) override; + void ClearContacts() override; + void AddContactPhysics(idPhysics* physics) override; + void RemoveContactPhysics(idPhysics* physics) override; + int GetNumContactPhysics() override { return contactPhysicsIds.Num(); } + idPhysics* GetContactPhysics(int index) override; + void ActivateContactPhysics() override; + bool HasGroundContacts() override; + bool IsGroundEntity(int entityNumber) override; + bool IsGroundClipModel(int entityNumber, int bodyId) override; + bool IsOutsideWorld(); + +protected: + void AddContactPhysicsForContacts(); + void AddGroundContacts(const idClipModel* model, int maxContacts); + void DrawVelocity(int id, float linearScale, float angularScale) const; + + int clipMask; + idVec3 gravityVector; + idVec3 gravityNormal; + idList contacts; + idList contactPhysicsIds; + float waterLevel; + float waterViscosity; }; + +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idPhysics_DynamicBase) == 120, + "Recovered idPhysics_DynamicBase ABI changed"); +#endif diff --git a/source/engine/gamelib/physics/physics_static.cpp b/source/engine/gamelib/physics/physics_static.cpp new file mode 100644 index 0000000..fb5e6b2 --- /dev/null +++ b/source/engine/gamelib/physics/physics_static.cpp @@ -0,0 +1,314 @@ +#include "gamelib/physics/physics_static.h" + +#include "gamelib/physics/clip.h" +#include "gamelib/physics/clipmodel.h" +#include "idlib/math/rotation.h" + +#include + +bool GameLib_GetMasterPhysicsTransform(idPhysicsCallbacks* callbacks, + idVec3& origin, idMat3& axis); +void GameLib_ActivateContactPhysics(idPhysics* physics); + +namespace { + +const idVec3 kZeroVector(0.0f, 0.0f, 0.0f); +const idVec3 kDownVector(0.0f, 0.0f, -1.0f); +const idMat3 kIdentityAxis(1.0f); +const idBounds kZeroBounds{{idVec3(0.0f, 0.0f, 0.0f), + idVec3(0.0f, 0.0f, 0.0f)}}; + +} // namespace + +idPhysics_Static::idPhysics_Static() + : idPhysics() + , clipModel(nullptr) + , contactPhysicsIds(4) + , isOrientated(0) + , hasMaster(0) + , reservedFlags(0) + , current{} { + type = PHYSICS_STATIC; + current.worldOrigin.Zero(); + current.worldAxis = idMat3(1.0f); + current.localOrigin.Zero(); + current.localAxis = idMat3(1.0f); +} + +idPhysics_Static::~idPhysics_Static() { + if (clipModel != nullptr) { + clipModel->Unlink(); + } + clipModel = nullptr; +} + +void idPhysics_Static::SetClipModel(idClipModel* const model, + const float density, const int id, const bool freeOld) { + (void)density; + if (clipModel != nullptr && clipModel != model && freeOld) { + clipModel->Delete(); + } + clipModel = model; + if (clipModel != nullptr) { + clipModel->Link(GetEntityNumber(), GetEntityNumber(), id, + current.worldOrigin, current.worldAxis); + } +} + +idClipModel* idPhysics_Static::GetClipModel(const int id) { + (void)id; + return clipModel; +} + +int idPhysics_Static::GetNumClipModels() { return clipModel != nullptr; } +void idPhysics_Static::SetMass(float, int) {} +float idPhysics_Static::GetMass(int) { return 0.0f; } + +void idPhysics_Static::SetContents(const int contents, const int id) { + (void)id; + if (clipModel != nullptr) clipModel->SetContents(contents); +} +int idPhysics_Static::GetContents(const int id) { + (void)id; + return clipModel != nullptr ? clipModel->GetContents() : 0; +} +void idPhysics_Static::SetClipMask(int, int) {} +int idPhysics_Static::GetClipMask(int) { return 0; } + +const idBounds* idPhysics_Static::GetBounds(const int id) { + (void)id; + return clipModel != nullptr ? &clipModel->GetBounds() : &kZeroBounds; +} + +const idBounds* idPhysics_Static::GetAbsBounds(const int id) { + (void)id; + if (clipModel != nullptr) return &clipModel->GetAbsBounds(); + static idBounds pointBounds; + pointBounds[0] = current.worldOrigin; + pointBounds[1] = current.worldOrigin; + return &pointBounds; +} + +void idPhysics_Static::SetOrigin(const idVec3* const origin, const int id) { + (void)id; + if (origin == nullptr) return; + current.worldOrigin = *origin; + if (!hasMaster) current.localOrigin = *origin; + if (clipModel != nullptr) { + clipModel->Link(GetEntityNumber(), GetEntityNumber(), + clipModel->GetBodyId(), current.worldOrigin, current.worldAxis); + } +} + +void idPhysics_Static::SetAxis(const idMat3* const axis, const int id) { + (void)id; + if (axis == nullptr) return; + current.worldAxis = *axis; + if (!hasMaster || !isOrientated) current.localAxis = *axis; + if (clipModel != nullptr) { + clipModel->Link(GetEntityNumber(), GetEntityNumber(), + clipModel->GetBodyId(), current.worldOrigin, current.worldAxis); + } +} + +void idPhysics_Static::Translate(const idVec3* const translation, + const int id) { + (void)id; + if (translation == nullptr) return; + current.worldOrigin = current.worldOrigin + *translation; + current.localOrigin = current.localOrigin + *translation; + if (clipModel != nullptr) { + clipModel->Link(GetEntityNumber(), GetEntityNumber(), + clipModel->GetBodyId(), current.worldOrigin, current.worldAxis); + } +} + +void idPhysics_Static::Rotate(const idRotation* const rotation, + const int id) { + (void)id; + if (rotation == nullptr) return; + current.worldOrigin = *rotation * current.worldOrigin; + current.worldAxis *= rotation->ToMat3(); + current.localAxis *= rotation->ToMat3(); + if (clipModel != nullptr) { + clipModel->Link(GetEntityNumber(), GetEntityNumber(), + clipModel->GetBodyId(), current.worldOrigin, current.worldAxis); + } +} + +const idVec3* idPhysics_Static::GetOrigin(int) { return ¤t.worldOrigin; } +const idMat3* idPhysics_Static::GetAxis(int) { return ¤t.worldAxis; } +const idVec3* idPhysics_Static::GetLocalOrigin(int) { return ¤t.localOrigin; } +const idMat3* idPhysics_Static::GetLocalAxis(int) { return ¤t.localAxis; } +void idPhysics_Static::SetLinearVelocity(const idVec3*, int) {} +void idPhysics_Static::SetAngularVelocity(const idVec3*, int) {} +idVec3* idPhysics_Static::GetLinearVelocity(idVec3* result, int) { + if (result != nullptr) result->Zero(); + return result; +} +idVec3* idPhysics_Static::GetAngularVelocity(idVec3* result, int) { + if (result != nullptr) result->Zero(); + return result; +} +void idPhysics_Static::SetGravity(const idVec3*) {} +const idVec3* idPhysics_Static::GetGravity() { return &kZeroVector; } +const idVec3* idPhysics_Static::GetGravityNormal() { return &kDownVector; } +void idPhysics_Static::SetWaterLevel(float, int) {} +float idPhysics_Static::GetWaterLevel(int) { return 0.0f; } +void idPhysics_Static::SetWaterViscosity(float, int) {} +float idPhysics_Static::GetWaterViscosity(int) { return 0.0f; } +void idPhysics_Static::SetWaterEntNum(int) {} +int idPhysics_Static::GetWaterEntNum() { return -1; } +void idPhysics_Static::SetWaterSurfaceWrldHeight(float) {} +float idPhysics_Static::GetWaterSurfaceWrldHeight() { return 0.0f; } + +void idPhysics_Static::GetImpactInfo(int, const idVec3*, + impactInfo_t* const info) { + if (info != nullptr) info->Zero(); +} +void idPhysics_Static::ApplyImpulse(int, const idVec3*, const idVec3*) {} +void idPhysics_Static::ApplyForce(int, const idVec3*, const idVec3*) {} +void idPhysics_Static::Activate() {} +void idPhysics_Static::PutToRest() {} +bool idPhysics_Static::IsAtRest() { return true; } +bool idPhysics_Static::IsPushable(int) { return false; } +void idPhysics_Static::SaveState() {} +void idPhysics_Static::RestoreState() {} + +bool idPhysics_Static::Evaluate(int, int) { + if (!hasMaster || callbacks == nullptr) return false; + idVec3 masterOrigin; + idMat3 masterAxis; + if (!GameLib_GetMasterPhysicsTransform(callbacks, masterOrigin, + masterAxis)) return false; + const idVec3 oldOrigin = current.worldOrigin; + const idMat3 oldAxis = current.worldAxis; + current.worldOrigin = masterOrigin + masterAxis * current.localOrigin; + current.worldAxis = isOrientated + ? current.localAxis * masterAxis : current.localAxis; + if (clipModel != nullptr) { + clipModel->Link(GetEntityNumber(), GetEntityNumber(), + clipModel->GetBodyId(), current.worldOrigin, current.worldAxis); + } + return (current.worldOrigin - oldOrigin).LengthSqr() > 0.0f || + (current.worldAxis[0] - oldAxis[0]).LengthSqr() > 0.0f || + (current.worldAxis[1] - oldAxis[1]).LengthSqr() > 0.0f || + (current.worldAxis[2] - oldAxis[2]).LengthSqr() > 0.0f; +} + +void idPhysics_Static::UpdateTime(int) {} + +void idPhysics_Static::ClipTranslation(trace_t* const results, + const idVec3* const translation, const idClipModel* const model) { + if (results == nullptr) return; + const idClipModel* moving = model != nullptr ? model : clipModel; + if (clip == nullptr || moving == nullptr || translation == nullptr) { + std::memset(results, 0, sizeof(*results)); + results->fraction = 1.0f; + return; + } + clip->Translation(results, current.worldOrigin, + current.worldOrigin + *translation, moving, current.worldAxis, 0, + GetEntityNumber(), false, "idPhysics_Static::ClipTranslation"); +} + +void idPhysics_Static::ClipRotation(trace_t* const results, + const idRotation* const rotation, const idClipModel* const model) { + if (results == nullptr) return; + const idClipModel* moving = model != nullptr ? model : clipModel; + if (clip == nullptr || moving == nullptr || rotation == nullptr) { + std::memset(results, 0, sizeof(*results)); + results->fraction = 1.0f; + return; + } + clip->Rotation(results, current.worldOrigin, *rotation, moving, + current.worldAxis, 0, GetEntityNumber(), false, + "idPhysics_Static::ClipRotation"); +} + +int idPhysics_Static::ClipContents(const idClipModel* const model, int) { + if (clip == nullptr || model == nullptr) return 0; + trace_t result{}; + clip->Contents(&result, current.worldOrigin, model, current.worldAxis, + 0, GetEntityNumber(), "idPhysics_Static::ClipContents"); + return result.c.contentFlags; +} + +void idPhysics_Static::DisableClip() { if (clipModel) clipModel->Disable(); } +void idPhysics_Static::EnableClip() { if (clipModel) clipModel->Enable(); } +void idPhysics_Static::UnlinkClip() { if (clipModel) clipModel->Unlink(); } +void idPhysics_Static::LinkClip() { + if (clipModel) clipModel->Link(GetEntityNumber(), GetEntityNumber(), + clipModel->GetBodyId(), current.worldOrigin, current.worldAxis); +} +bool idPhysics_Static::EvaluateContacts() { return false; } +int idPhysics_Static::GetNumContacts() { return 0; } +const contactInfo_t* idPhysics_Static::GetContact(int) { return nullptr; } +void idPhysics_Static::ClearContacts() {} + +void idPhysics_Static::AddContactPhysics(idPhysics* const physics) { + if (physics == nullptr || physics == this) return; + const int physicsId = physics->GetPhysicsId(); + for (int i = 0; i < contactPhysicsIds.Num(); ++i) + if (contactPhysicsIds[i] == physicsId) return; + contactPhysicsIds.Append(physicsId); +} +void idPhysics_Static::RemoveContactPhysics(idPhysics* const physics) { + if (physics == nullptr) return; + for (int i = contactPhysicsIds.Num() - 1; i >= 0; --i) + if (contactPhysicsIds[i] == physics->GetPhysicsId()) + contactPhysicsIds.RemoveIndexFast(i); +} +int idPhysics_Static::GetNumContactPhysics() { return contactPhysicsIds.Num(); } +idPhysics* idPhysics_Static::GetContactPhysics(const int index) { + return index >= 0 && index < contactPhysicsIds.Num() + ? idPhysics::GetPhysicsForId(contactPhysicsIds[index]) : nullptr; +} +void idPhysics_Static::ActivateContactPhysics() { + for (int i = contactPhysicsIds.Num() - 1; i >= 0; --i) { + idPhysics* physics = idPhysics::GetPhysicsForId(contactPhysicsIds[i]); + if (physics) GameLib_ActivateContactPhysics(physics); + else contactPhysicsIds.RemoveIndexFast(i); + } +} +bool idPhysics_Static::HasGroundContacts() { return false; } +bool idPhysics_Static::IsGroundEntity(int) { return false; } +bool idPhysics_Static::IsGroundClipModel(int, int) { return false; } +void idPhysics_Static::SetPushed(int) {} +idVec3* idPhysics_Static::GetPushedLinearVelocity(idVec3* result, int) { + if (result) result->Zero(); return result; +} +idVec3* idPhysics_Static::GetPushedAngularVelocity(idVec3* result, int) { + if (result) result->Zero(); return result; +} + +void idPhysics_Static::SetMaster(const bool orientated, + const idVec3* const masterOrigin, const idMat3* const masterAxis, + const bindFlags_t flags) { + (void)flags; + if (masterOrigin != nullptr && masterAxis != nullptr) { + const idMat3 inverse = masterAxis->Transpose(); + current.localOrigin = inverse * + (current.worldOrigin - *masterOrigin); + current.localAxis = orientated + ? current.worldAxis * inverse : current.worldAxis; + hasMaster = 1; + isOrientated = orientated ? 1 : 0; + } else { + current.localOrigin = current.worldOrigin; + current.localAxis = current.worldAxis; + hasMaster = 0; + isOrientated = 0; + } +} +void idPhysics_Static::SetLocalOrigin(const idVec3* origin, int id) { + if (origin) { current.localOrigin = *origin; Evaluate(0, 0); } + (void)id; +} +void idPhysics_Static::SetLocalAxis(const idMat3* axis, int id) { + if (axis) { current.localAxis = *axis; Evaluate(0, 0); } + (void)id; +} +int idPhysics_Static::GetBlockingEntityNum() { return 0x1FFF; } +int idPhysics_Static::GetLinearEndTime() { return 0; } +int idPhysics_Static::GetAngularEndTime() { return 0; } diff --git a/source/engine/gamelib/physics/physics_static.h b/source/engine/gamelib/physics/physics_static.h index b2fcea2..98296be 100644 --- a/source/engine/gamelib/physics/physics_static.h +++ b/source/engine/gamelib/physics/physics_static.h @@ -1,96 +1,108 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\physics\physics_static.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/physics/physics.h" +#include "idlib/bv/bounds.h" +#include "idlib/containers/list.h" - -// IDA Local Type ordinal 15178; PDB kind: class. -class idPhysics_Static : public idPhysics -{ -public: - // Recovered virtual interface; IDA vtable ordinal 15179. - virtual ~idPhysics_Static(); - virtual void SetClipModel(idClipModel *, float, int, bool); - virtual idClipModel *GetClipModel(int); - virtual int GetNumClipModels(); - virtual void SetMass(float, int); - virtual float GetMass(int); - virtual void SetContents(int, int); - virtual int GetContents(int); - virtual void SetClipMask(int, int); - virtual int GetClipMask(int); - virtual const idBounds *GetBounds(int); - virtual const idBounds *GetAbsBounds(int); - virtual void SetOrigin(const idVec3 *, int); - virtual void SetAxis(const idMat3 *, int); - virtual void Translate(const idVec3 *, int); - virtual void Rotate(const idRotation *, int); - virtual const idVec3 *GetOrigin(int); - virtual const idMat3 *GetAxis(int); - virtual const idVec3 *GetLocalOrigin(int); - virtual const idMat3 *GetLocalAxis(int); - virtual void SetLinearVelocity(const idVec3 *, int); - virtual void SetAngularVelocity(const idVec3 *, int); - virtual idVec3 *GetLinearVelocity(idVec3 *result, int); - virtual idVec3 *GetAngularVelocity(idVec3 *result, int); - virtual void SetGravity(const idVec3 *); - virtual const idVec3 *GetGravity(); - virtual const idVec3 *GetGravityNormal(); - virtual void SetWaterLevel(float, int); - virtual float GetWaterLevel(int); - virtual void SetWaterViscosity(float, int); - virtual float GetWaterViscosity(int); - virtual void SetWaterEntNum(int); - virtual int GetWaterEntNum(); - virtual void SetWaterSurfaceWrldHeight(float); - virtual float GetWaterSurfaceWrldHeight(); - virtual void GetImpactInfo(const int, const idVec3 *, impactInfo_t *); - virtual void ApplyImpulse(const int, const idVec3 *, const idVec3 *); - virtual void ApplyForce(const int, const idVec3 *, const idVec3 *); - virtual void Activate(); - virtual void PutToRest(); - virtual bool IsAtRest(); - virtual bool IsPushable(int); - virtual void SaveState(); - virtual void RestoreState(); - virtual bool Evaluate(int, int); - virtual void UpdateTime(int); - virtual void ClipTranslation(trace_t *, const idVec3 *, const idClipModel *); - virtual void ClipRotation(trace_t *, const idRotation *, const idClipModel *); - virtual int ClipContents(const idClipModel *, int); - virtual void DisableClip(); - virtual void EnableClip(); - virtual void UnlinkClip(); - virtual void LinkClip(); - virtual bool EvaluateContacts(); - virtual int GetNumContacts(); - virtual const contactInfo_t *GetContact(int); - virtual void ClearContacts(); - virtual void AddContactPhysics(idPhysics *); - virtual void RemoveContactPhysics(idPhysics *); - virtual int GetNumContactPhysics(); - virtual idPhysics *GetContactPhysics(int); - virtual void ActivateContactPhysics(); - virtual bool HasGroundContacts(); - virtual bool IsGroundEntity(int); - virtual bool IsGroundClipModel(int, int); - virtual void SetPushed(int); - virtual idVec3 *GetPushedLinearVelocity(idVec3 *result, const int); - virtual idVec3 *GetPushedAngularVelocity(idVec3 *result, const int); - virtual void SetMaster(bool, const idVec3 *, const idMat3 *, const bindFlags_t); - virtual void SetLocalOrigin(const idVec3 *, int); - virtual void SetLocalAxis(const idMat3 *, int); - virtual int GetBlockingEntityNum(); - virtual int GetLinearEndTime(); - virtual int GetAngularEndTime(); - virtual bool IsInNonResidentCollisionArea(bool); - - idClipModel *clipModel; - idList contactPhysicsIds; - unsigned __int8 : 6; - __int8 isOrientated : 1; - __int8 hasMaster : 1; - staticPState_t current; +struct staticPState_t { + idVec3 worldOrigin; + idMat3 worldAxis; + idVec3 localOrigin; + idMat3 localAxis; }; + +class idPhysics_Static : public idPhysics { +public: + idPhysics_Static(); + ~idPhysics_Static() override; + + void SetClipModel(idClipModel*, float, int, bool) override; + idClipModel* GetClipModel(int) override; + int GetNumClipModels() override; + void SetMass(float, int) override; + float GetMass(int) override; + void SetContents(int, int) override; + int GetContents(int) override; + void SetClipMask(int, int) override; + int GetClipMask(int) override; + const idBounds* GetBounds(int) override; + const idBounds* GetAbsBounds(int) override; + void SetOrigin(const idVec3*, int) override; + void SetAxis(const idMat3*, int) override; + void Translate(const idVec3*, int) override; + void Rotate(const idRotation*, int) override; + const idVec3* GetOrigin(int) override; + const idMat3* GetAxis(int) override; + const idVec3* GetLocalOrigin(int) override; + const idMat3* GetLocalAxis(int) override; + void SetLinearVelocity(const idVec3*, int) override; + void SetAngularVelocity(const idVec3*, int) override; + idVec3* GetLinearVelocity(idVec3*, int) override; + idVec3* GetAngularVelocity(idVec3*, int) override; + void SetGravity(const idVec3*) override; + const idVec3* GetGravity() override; + const idVec3* GetGravityNormal() override; + void SetWaterLevel(float, int) override; + float GetWaterLevel(int) override; + void SetWaterViscosity(float, int) override; + float GetWaterViscosity(int) override; + void SetWaterEntNum(int) override; + int GetWaterEntNum() override; + void SetWaterSurfaceWrldHeight(float) override; + float GetWaterSurfaceWrldHeight() override; + void GetImpactInfo(int, const idVec3*, impactInfo_t*) override; + void ApplyImpulse(int, const idVec3*, const idVec3*) override; + void ApplyForce(int, const idVec3*, const idVec3*) override; + void Activate() override; + void PutToRest() override; + bool IsAtRest() override; + bool IsPushable(int) override; + void SaveState() override; + void RestoreState() override; + bool Evaluate(int, int) override; + void UpdateTime(int) override; + void ClipTranslation(trace_t*, const idVec3*, + const idClipModel*) override; + void ClipRotation(trace_t*, const idRotation*, + const idClipModel*) override; + int ClipContents(const idClipModel*, int) override; + void DisableClip() override; + void EnableClip() override; + void UnlinkClip() override; + void LinkClip() override; + bool EvaluateContacts() override; + int GetNumContacts() override; + const contactInfo_t* GetContact(int) override; + void ClearContacts() override; + void AddContactPhysics(idPhysics*) override; + void RemoveContactPhysics(idPhysics*) override; + int GetNumContactPhysics() override; + idPhysics* GetContactPhysics(int) override; + void ActivateContactPhysics() override; + bool HasGroundContacts() override; + bool IsGroundEntity(int) override; + bool IsGroundClipModel(int, int) override; + void SetPushed(int) override; + idVec3* GetPushedLinearVelocity(idVec3*, int) override; + idVec3* GetPushedAngularVelocity(idVec3*, int) override; + void SetMaster(bool, const idVec3*, const idMat3*, bindFlags_t) override; + void SetLocalOrigin(const idVec3*, int) override; + void SetLocalAxis(const idMat3*, int) override; + int GetBlockingEntityNum() override; + int GetLinearEndTime() override; + int GetAngularEndTime() override; + + idClipModel* clipModel; + idList contactPhysicsIds; + std::uint8_t isOrientated : 1; + std::uint8_t hasMaster : 1; + std::uint8_t reservedFlags : 6; + staticPState_t current; +}; + +static_assert(sizeof(staticPState_t) == 96, + "Recovered static physics-state ABI changed"); +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idPhysics_Static) == 168, + "Recovered idPhysics_Static ABI changed"); +#endif diff --git a/source/engine/gamelib/physics/push.cpp b/source/engine/gamelib/physics/push.cpp new file mode 100644 index 0000000..733d73a --- /dev/null +++ b/source/engine/gamelib/physics/push.cpp @@ -0,0 +1,263 @@ +#include "gamelib/physics/push.h" + +#include "gamelib/physics/clipmodel.h" + +#include +#include + +int GameLib_GetPushPhysicsObjects(idClip* clip, const idBounds& bounds, + idPhysics** objects, int maxObjects); +bool GameLib_GetPushSweepBounds(idPhysics* pusher, + const idVec3& oldOrigin, const idVec3& newOrigin, + const idMat3& oldAxis, const idMat3& newAxis, idBounds& bounds); +idRotation GameLib_GetPushRotation(const idVec3& origin, + const idMat3& oldAxis, const idMat3& newAxis); +bool GameLib_RotatePushObjectToAxial(idPhysics* physics, + const idVec3& rotationPoint, const idClipModel* clipModel); + +namespace { + +void ClearTrace(trace_t& trace, const idVec3& end, + const idMat3& endAxis) { + std::memset(&trace, 0, sizeof(trace)); + trace.fraction = 1.0f; + trace.endpos = end; + trace.endAxis = endAxis; + trace.c.entityNum = 0x1FFF; + trace.c.physicsId = -1; +} + +} // namespace + +idPush::idPush(idClip* const clip_) + : clip(clip_) + , pushed(4) { +} + +void idPush::Init(idClip* const clip_) { clip = clip_; } + +void idPush::InitSavingPushedPhysicsObjectState() { pushed.Clear(); } + +void idPush::SavePhysicsObjectState(idPhysics* const physics) { + if (physics == nullptr) return; + for (int index = 0; index < pushed.Num(); ++index) + if (pushed[index] == physics) return; + physics->SaveState(); + pushed.Append(physics); +} + +void idPush::RestorePushedPhysicsObjectState() { + for (int index = 0; index < pushed.Num(); ++index) + if (pushed[index] != nullptr) pushed[index]->RestoreState(); +} + +void idPush::SetPushedOnPushedPhysicsObjects(const int deltaTime) { + for (int index = 0; index < pushed.Num(); ++index) + if (pushed[index] != nullptr) pushed[index]->SetPushed(deltaTime); +} + +void idPush::ClipPhysicsObjectRotation(trace_t& trace, + idPhysics* const physics, const idClipModel* const clipModel, + idClipModel* const skip, const idRotation& rotation) { + if (skip != nullptr) skip->Disable(); + physics->ClipRotation(&trace, &rotation, clipModel); + if (skip != nullptr) skip->Enable(); +} + +void idPush::ClipPhysicsObjectTranslation(trace_t& trace, + idPhysics* const physics, const idClipModel* const clipModel, + idClipModel* const skip, const idVec3& translation) { + if (skip != nullptr) skip->Disable(); + physics->ClipTranslation(&trace, &translation, clipModel); + if (skip != nullptr) skip->Enable(); +} + +bool idPush::CanPushPhysicsObject(idPhysics* const physics, + const int flags, idPhysics* const pusher) { + if (physics == nullptr || pusher == nullptr || physics == pusher) { + return false; + } + const int pusherContents = pusher->GetContents(-1); + if (!physics->IsPushable(pusherContents) || + (physics->GetClipMask(-1) & pusherContents) == 0) { + return false; + } + if ((flags & 4) != 0 && physics->GetType() != PHYSICS_RIGIDBODY) { + return false; + } + if ((flags & 8) != 0 && + pusher->IsGroundEntity(physics->GetEntityNumber())) { + return false; + } + return true; +} + +int idPush::GetPhysicsObjectsTouchingBounds(idPhysics** const objects, + const idBounds& bounds, const int flags, idPhysics* const pusher) { + if (clip == nullptr || objects == nullptr) return 0; + idPhysics* candidates[256]{}; + const int count = (std::max)(0, (std::min)(256, + GameLib_GetPushPhysicsObjects(clip, bounds, candidates, 256))); + int accepted = 0; + for (int index = 0; index < count; ++index) { + if (!CanPushPhysicsObject(candidates[index], flags, pusher)) continue; + bool duplicate = false; + for (int check = 0; check < accepted; ++check) + if (objects[check] == candidates[index]) duplicate = true; + if (!duplicate) objects[accepted++] = candidates[index]; + } + return accepted; +} + +bool idPush::RotatePhysicsObjectToAxial(idPhysics* const physics, + const idVec3& rotationPoint, const idClipModel* const clipModel) { + return physics != nullptr && GameLib_RotatePushObjectToAxial( + physics, rotationPoint, clipModel); +} + +idPush::pushResult_t idPush::RecursiveTryTranslatePushPhysicsObject( + trace_t& results, trace_t& trace, idPhysics* const pusher, + const int flags, const idVec3& translation, float& mass) { + idPhysics* const blocking = idPhysics::GetPhysicsForId(trace.c.physicsId); + if (!CanPushPhysicsObject(blocking, flags & ~8, pusher)) { + results = trace; + return PUSH_BLOCKED; + } + const pushResult_t result = TryTranslatePushPhysicsObject(results, + blocking, pusher->GetClipModel(0), flags, *blocking->GetOrigin(0), + translation, mass); + if (result == PUSH_OK) { + pusher->ClipTranslation(&trace, &translation, + pusher->GetClipModel(0)); + } + return result; +} + +idPush::pushResult_t idPush::TryTranslatePushPhysicsObject( + trace_t& results, idPhysics* const check, + idClipModel* const pusherModel, const int flags, + const idVec3& oldOrigin, const idVec3& translation, float& mass) { + if (check == nullptr) return PUSH_BLOCKED; + trace_t trace{}; + ClipPhysicsObjectTranslation(trace, check, pusherModel, + pusherModel, translation); + if (trace.fraction < 1.0f) { + const pushResult_t recursive = RecursiveTryTranslatePushPhysicsObject( + results, trace, check, flags, translation, mass); + if (recursive != PUSH_OK) return recursive; + } + SavePhysicsObjectState(check); + check->Translate(&translation, -1); + mass += check->GetMass(-1); + ClearTrace(results, oldOrigin + translation, *check->GetAxis(0)); + return PUSH_OK; +} + +idPush::pushResult_t idPush::RecursiveTryRotatePushPhysicsObject( + trace_t& results, trace_t& trace, idPhysics* const pusher, + const int flags, const idRotation& rotation, float& mass) { + idPhysics* const blocking = idPhysics::GetPhysicsForId(trace.c.physicsId); + if (!CanPushPhysicsObject(blocking, flags & ~8, pusher)) { + results = trace; + return PUSH_BLOCKED; + } + return TryRotatePushPhysicsObject(results, blocking, + pusher->GetClipModel(0), flags, rotation.GetOrigin(), + *blocking->GetAxis(0), rotation, mass); +} + +idPush::pushResult_t idPush::TryRotatePushPhysicsObject(trace_t& results, + idPhysics* const check, idClipModel* const pusherModel, + const int flags, const idVec3& rotationPoint, const idMat3& oldAxis, + const idRotation& rotation, float& mass) { + if (check == nullptr) return PUSH_BLOCKED; + trace_t trace{}; + ClipPhysicsObjectRotation(trace, check, pusherModel, pusherModel, + rotation); + if (trace.fraction < 1.0f) { + const pushResult_t recursive = RecursiveTryRotatePushPhysicsObject( + results, trace, check, flags, rotation, mass); + if (recursive != PUSH_OK) return recursive; + } + SavePhysicsObjectState(check); + check->Rotate(&rotation, -1); + if ((flags & 2) != 0 && !RotatePhysicsObjectToAxial(check, + rotationPoint, pusherModel)) { + check->RestoreState(); + return PUSH_BLOCKED; + } + mass += check->GetMass(-1); + ClearTrace(results, *check->GetOrigin(0), oldAxis * rotation.ToMat3()); + return PUSH_OK; +} + +float idPush::ClipTranslationalPush(trace_t& results, + idPhysics* const pusher, const int flags, const idVec3& newOrigin, + const idVec3& translation) { + idBounds sweep; + if (!GameLib_GetPushSweepBounds(pusher, + newOrigin - translation, newOrigin, *pusher->GetAxis(0), + *pusher->GetAxis(0), sweep)) return 0.0f; + idPhysics* objects[256]{}; + const int count = GetPhysicsObjectsTouchingBounds(objects, sweep, + flags, pusher); + float mass = 0.0f; + for (int index = 0; index < count; ++index) { + if (TryTranslatePushPhysicsObject(results, objects[index], + pusher->GetClipModel(0), flags, *objects[index]->GetOrigin(0), + translation, mass) != PUSH_OK) return mass; + } + return mass; +} + +float idPush::ClipRotationalPush(trace_t& results, + idPhysics* const pusher, const int flags, const idMat3& newAxis, + const idRotation& rotation) { + idBounds sweep; + const idVec3 origin = *pusher->GetOrigin(0); + if (!GameLib_GetPushSweepBounds(pusher, origin, origin, + *pusher->GetAxis(0), newAxis, sweep)) return 0.0f; + idPhysics* objects[256]{}; + const int count = GetPhysicsObjectsTouchingBounds(objects, sweep, + flags, pusher); + float mass = 0.0f; + for (int index = 0; index < count; ++index) { + if (TryRotatePushPhysicsObject(results, objects[index], + pusher->GetClipModel(0), flags, origin, + *objects[index]->GetAxis(0), rotation, mass) != PUSH_OK) + return mass; + } + return mass; +} + +float idPush::ClipPush(trace_t& results, idPhysics* const pusher, + const int flags, const idVec3& oldOrigin, const idMat3& oldAxis, + idVec3& newOrigin, idMat3& newAxis) { + ClearTrace(results, newOrigin, newAxis); + if (pusher == nullptr) return 0.0f; + InitSavingPushedPhysicsObjectState(); + float totalMass = 0.0f; + const idVec3 translation = newOrigin - oldOrigin; + if (translation.LengthSqr() > 0.0f) { + totalMass += ClipTranslationalPush(results, pusher, flags, + newOrigin, translation); + if (results.fraction < 1.0f) { + RestorePushedPhysicsObjectState(); + newOrigin = oldOrigin; + newAxis = oldAxis; + return totalMass; + } + } + const idRotation rotation = GameLib_GetPushRotation(newOrigin, + oldAxis, newAxis); + if (rotation.GetAngle() != 0.0f) { + totalMass += ClipRotationalPush(results, pusher, flags, + newAxis, rotation); + if (results.fraction < 1.0f) { + RestorePushedPhysicsObjectState(); + newOrigin = oldOrigin; + newAxis = oldAxis; + } + } + return totalMass; +} diff --git a/source/engine/gamelib/physics/push.h b/source/engine/gamelib/physics/push.h index b2761ac..1f80d1c 100644 --- a/source/engine/gamelib/physics/push.h +++ b/source/engine/gamelib/physics/push.h @@ -1,18 +1,63 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\gamelib\physics\push.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "gamelib/physics/physics.h" +#include "idlib/bv/bounds.h" +#include "idlib/containers/list.h" +#include "idlib/math/rotation.h" - -// IDA Local Type ordinal 14173; PDB kind: class. -class idPush -{ +class idPush { public: - idClip *clip; - idList pushed; + enum pushResult_t : int { + PUSH_OK = 0, + PUSH_BLOCKED = 1, + PUSH_CRUSHED = 2 + }; + + explicit idPush(idClip* clip = nullptr); + void Init(idClip* clip); + void InitSavingPushedPhysicsObjectState(); + void SavePhysicsObjectState(idPhysics* physics); + void RestorePushedPhysicsObjectState(); + void SetPushedOnPushedPhysicsObjects(int deltaTime); + float ClipPush(trace_t& results, idPhysics* pusher, int flags, + const idVec3& oldOrigin, const idMat3& oldAxis, + idVec3& newOrigin, idMat3& newAxis); + + idClip* clip; + idList pushed; + +private: + void ClipPhysicsObjectRotation(trace_t& trace, idPhysics* physics, + const idClipModel* clipModel, idClipModel* skip, + const idRotation& rotation); + void ClipPhysicsObjectTranslation(trace_t& trace, idPhysics* physics, + const idClipModel* clipModel, idClipModel* skip, + const idVec3& translation); + bool CanPushPhysicsObject(idPhysics* physics, int flags, + idPhysics* pusher); + int GetPhysicsObjectsTouchingBounds(idPhysics** physicsObjects, + const idBounds& bounds, int flags, idPhysics* pusher); + bool RotatePhysicsObjectToAxial(idPhysics* physics, + const idVec3& rotationPoint, const idClipModel* clipModel); + pushResult_t RecursiveTryTranslatePushPhysicsObject(trace_t& results, + trace_t& trace, idPhysics* pusher, int flags, + const idVec3& translation, float& mass); + pushResult_t RecursiveTryRotatePushPhysicsObject(trace_t& results, + trace_t& trace, idPhysics* pusher, int flags, + const idRotation& rotation, float& mass); + pushResult_t TryTranslatePushPhysicsObject(trace_t& results, + idPhysics* check, idClipModel* pusherModel, int flags, + const idVec3& oldOrigin, const idVec3& translation, float& mass); + pushResult_t TryRotatePushPhysicsObject(trace_t& results, + idPhysics* check, idClipModel* pusherModel, int flags, + const idVec3& rotationPoint, const idMat3& oldAxis, + const idRotation& rotation, float& mass); + float ClipTranslationalPush(trace_t& results, idPhysics* pusher, + int flags, const idVec3& newOrigin, const idVec3& translation); + float ClipRotationalPush(trace_t& results, idPhysics* pusher, + int flags, const idMat3& newAxis, const idRotation& rotation); }; -// IDA Local Type ordinal 29474; PDB kind: typedef. -typedef unsigned __int16 ush; +#if defined(_WIN32) && !defined(_WIN64) +static_assert(sizeof(idPush) == 20, "Recovered idPush ABI changed"); +#endif diff --git a/source/shared/idlib/math/spatialvec.h b/source/shared/idlib/math/spatialvec.h index 9fde3a2..4b96db1 100644 --- a/source/shared/idlib/math/spatialvec.h +++ b/source/shared/idlib/math/spatialvec.h @@ -148,6 +148,35 @@ private: float* p; }; +// Fixed six-component spatial vector embedded by articulated-figure bodies +// and constraints. Tungsten keeps an eight-float, 16-byte-aligned backing +// slab so VMX loads can safely read both halves of the six-vector. +class alignas(16) idStaticSpatialVec : public idSpatialVec { +public: + idStaticSpatialVec() + : idSpatialVec() + , data{} { + SetData(6, data); + } + + idStaticSpatialVec(const idStaticSpatialVec& other) + : idStaticSpatialVec() { + for (int index = 0; index < 6; ++index) data[index] = other.data[index]; + } + + idStaticSpatialVec& operator=(const idStaticSpatialVec& other) { + if (this != &other) { + for (int index = 0; index < 6; ++index) + data[index] = other.data[index]; + } + return *this; + } + + alignas(16) float data[8]; +}; + #if INTPTR_MAX == INT32_MAX static_assert(sizeof(idSpatialVec) == 8, "Recovered idSpatialVec ABI changed"); +static_assert(sizeof(idStaticSpatialVec) == 48, + "Recovered idStaticSpatialVec ABI changed"); #endif