From 64a8574bca3e670b2226f49f1d021779d424e9ab Mon Sep 17 00:00:00 2001 From: breki Date: Fri, 24 Jul 2026 16:19:52 +0000 Subject: [PATCH 01/14] first iteration of filling in evechildturret and fix some typoes in eveturretset and update clang format to never wrap --- .clang-format | 5 +- trinity/CMakeLists.txt | 3 + .../Eve/SpaceObject/Children/EveChildMesh.h | 6 +- .../SpaceObject/Children/EveChildTurret.cpp | 1108 +++++++++++++++++ .../Eve/SpaceObject/Children/EveChildTurret.h | 194 +++ .../Children/EveChildTurret_Blue.cpp | 13 + trinity/Eve/Turret/EveTurretSet.cpp | 18 +- 7 files changed, 1334 insertions(+), 13 deletions(-) create mode 100644 trinity/Eve/SpaceObject/Children/EveChildTurret.cpp create mode 100644 trinity/Eve/SpaceObject/Children/EveChildTurret.h create mode 100644 trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp diff --git a/.clang-format b/.clang-format index 039810cca..db9ae9ed3 100644 --- a/.clang-format +++ b/.clang-format @@ -113,7 +113,10 @@ BreakBeforeBraces: Custom BreakBeforeTernaryOperators: false BreakConstructorInitializers: AfterColon BreakStringLiterals: false -ColumnLimit: 120 +# Kept at 0 to match the Cpp section: some tools (e.g. CLion's ClangFormat +# integration) misclassify C++ .h files as ObjC and would otherwise re-wrap +# lines at the column limit. +ColumnLimit: 0 CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 diff --git a/trinity/CMakeLists.txt b/trinity/CMakeLists.txt index dc0e9c6e0..fe753d463 100644 --- a/trinity/CMakeLists.txt +++ b/trinity/CMakeLists.txt @@ -569,6 +569,9 @@ set(_SOURCES Eve/SpaceObject/Children/EveChildSpherePin_Blue.cpp Eve/SpaceObject/Children/EveChildTransform.cpp Eve/SpaceObject/Children/EveChildTransform.cpp + Eve/SpaceObject/Children/EveChildTurret.cpp + Eve/SpaceObject/Children/EveChildTurret.h + Eve/SpaceObject/Children/EveChildTurret_Blue.cpp Eve/SpaceObject/Children/EveCloudEditableVolume.cpp Eve/SpaceObject/Children/EveCloudEditableVolume.h Eve/SpaceObject/Children/EveCloudEditableVolume_Blue.cpp diff --git a/trinity/Eve/SpaceObject/Children/EveChildMesh.h b/trinity/Eve/SpaceObject/Children/EveChildMesh.h index 54d5e1b52..70c7e738c 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildMesh.h +++ b/trinity/Eve/SpaceObject/Children/EveChildMesh.h @@ -84,8 +84,8 @@ BLUE_CLASS( EveChildMesh ) : void UpdateVisibility( const EveUpdateContext& updateContext, const Matrix& parentTransform, Tr2Lod parentLod ); void GetRenderables( std::vector & renderables ); bool GetBoundingSphere( Vector4 & sphere, BoundingSphereQuery query = EVE_BOUNDS_NORMAL ) const; - void UpdateSyncronous( const EveUpdateContext& updateContext, const EveChildUpdateParams& params ); - void UpdateAsyncronous( const EveUpdateContext& updateContext, const EveChildUpdateParams& params ); + void UpdateSyncronous( const EveUpdateContext& updateContext, const EveChildUpdateParams& params ) override; + void UpdateAsyncronous( const EveUpdateContext& updateContext, const EveChildUpdateParams& params ) override; void GetLocalToWorldTransform( Matrix & transform ) const; void ChangeLOD( Tr2Lod lod ) override; virtual void Setup( const Vector3* scale, const Quaternion* rotation, const Vector3* translation, Tr2Lod lowestLodVisible ); @@ -193,7 +193,7 @@ BLUE_CLASS( EveChildMesh ) : virtual void ReleaseResources( TriStorage s ); virtual bool OnPrepareResources(); - void InitializeAnimation(); + virtual void InitializeAnimation(); bool ShouldReflect() const; bool DisplayDecals() const; diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp new file mode 100644 index 000000000..004ed80c5 --- /dev/null +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp @@ -0,0 +1,1108 @@ +// Copyright © 2026 Fenris Creations ehf. + +#include "StdAfx.h" +#include "EveChildTurret.h" +#include "Eve/Turret/EveTurretFiringFX.h" +#include "Tr2MeshBase.h" +#include "TriMath.h" +#include "TriObserverLocal.h" + +// names of system bones like they are in the granny file +static std::string s_systemBoneSkeletonNames[] = { + "invalid", // SYSBONE_INVALID + "Sys_Rotation_Arm", // SYSBONE_ROTATION + "Sys_Rotation_Arm01", // SYSBONE_ROTATION1 + "Sys_Rotation_Arm02", // SYSBONE_ROTATION2 + "Sys_CounterRotation", // SYSBONE_COUNTER_ROTATION + "Sys_Pitch_Barrel", // SYSBONE_PITCH + "Sys_Pitch_Barrel1", // SYSBONE_PITCH1 + "Sys_Pitch_Barrel2", // SYSBONE_PITCH2 + "Sys_Height", // SYSBONE_SCALED_HEIGHT + "Sys_Pitch_Arm01", // SYSBONE_SCALED_PITCH01 + "Sys_Pitch_Arm02", // SYSBONE_SCALED_PITCH02 + "Sys_Pitch_Arm03", // SYSBONE_SCALED_PITCH03 + "Sys_Pitch_Arm04", // SYSBONE_SCALED_PITCH04 + "Sys_Pitch_Arm05", // SYSBONE_SCALED_PITCH05 + "Sys_Pitch_Arm06", // SYSBONE_SCALED_PITCH06 +}; + +// invalids +const unsigned int INVALID_BONE_INDEX = 0xffffffff; +const unsigned int INVALID_TURRET_INDEX = 0xffffffff; + +// some very static timings, no need to confuse artists by exposing them +const float TRACKING_FADE_TIME = 1.f; + +EveChildTurret::EveChildTurret( IRoot* lockobj ) : + EveChildMesh( lockobj ) +// TODO: init rest of variables +{ + m_target.CreateInstance(); + + PrepareResources(); +} + +EveChildTurret::~EveChildTurret() +{ + if( m_firingEffect ) + { + m_firingEffect->CleanUp(); + } + + m_skeleton = nullptr; + m_skeletonBoneIndices.clear(); + m_boneBounds.clear(); + + ReleaseResources( TRISTORAGE_ALL ); +} +bool EveChildTurret::Initialize() +{ + // pass down some user-defined data into sub-modules we don't save out + // TODO: keep or discard + // m_target->SetBehaviour( m_laserMissBehaviour, m_projectileMissBehaviour, m_impactSize, m_impactBehaviour ); + + return EveChildMesh::Initialize(); +} +bool EveChildTurret::OnModified( Be::Var* value ) +{ + /* + if( IsMatch( value, m_laserMissBehaviour ) || IsMatch( value, m_projectileMissBehaviour ) || IsMatch( value, m_impactSize ) || IsMatch( value, m_impactBehaviour ) ) + { + // TODO: keep or discard + m_target->SetBehaviour( m_laserMissBehaviour, m_projectileMissBehaviour, m_impactSize, m_impactBehaviour ); + } + */ + return EveChildMesh::OnModified( value ); +} +void EveChildTurret::RegisterComponents() +{ + EveChildMesh::RegisterComponents(); + const auto registry = GetComponentRegistry(); + if( registry && m_display ) + { + if( EveEntityPtr entity = BlueCastPtr( m_firingEffect ) ) + { + entity->Register( registry ); + } + } +} +void EveChildTurret::UnRegisterComponents() +{ + EveChildMesh::UnRegisterComponents(); + const auto registry = this->GetComponentRegistry(); + if( registry ) + { + if( EveEntityPtr entity = BlueCastPtr( m_firingEffect ) ) + { + entity->UnRegister( registry ); + } + } +} +void EveChildTurret::GetDebugOptions( Tr2DebugRendererOptions& options ) +{ + EveChildMesh::GetDebugOptions( options ); + + if( m_firingEffect ) + { + m_firingEffect->GetDebugOptions( options ); + } + + if( m_turretMovementObserver ) + { + m_turretMovementObserver->GetDebugOptions( options ); + } +} +void EveChildTurret::RenderDebugInfo( ITr2DebugRenderer2& renderer ) +{ + EveChildMesh::RenderDebugInfo( renderer ); + + if( m_firingEffect ) + { + m_firingEffect->RenderDebugInfo( renderer ); + } + + if( m_turretMovementObserver && m_playMovementSound ) + { + m_turretMovementObserver->RenderDebugInfo( renderer ); + } +} + +void EveChildTurret::UpdateSyncronous( const EveUpdateContext& updateContext, const EveChildUpdateParams& params ) +{ + float deltaT = updateContext.GetDeltaT(); + + /* + // TODO: LODs needed? + // LODing + if( UpdateLOD( updateContext ) ) + { + // LOD change, so just call ::InitializeGeometryResource(), takes care of everything + InitializeGeometryResource(); + + // LOD change: toggle source dest effect of the attached firingFX + if( m_firingEffect ) + { + switch( m_lodLevel ) + { + case LOD_DISABLED: + case LOD_HIGHEST: + m_firingEffect->SetDisplaySourceObject( true ); + break; + default: + m_firingEffect->SetDisplaySourceObject( false ); + break; + } + } + } + */ + + if( m_sequencer ) + { + m_sequencer->RemoveFinishedAnimations( Tr2Renderer::GetAnimationTime() ); + } + + // setup and update attached firing effect + if( m_firingEffect ) + { + // if the attached firing effect is looping, then we must recheck if active turret is still the best, + if( m_firingEffect->IsLooping() ) + { + if( m_state == STATE_FIRING ) + { + // don't do it every frame, cause this will result in popping + m_recheckTimeLeft -= deltaT; + if( m_recheckTimeLeft < 0.f ) + { + Vector3 source = m_worldTransform.GetTranslation(); + Vector3 position = source; + if( int closestLocator = m_target->FindClosestLocator( &source, &position ) ) + { + if( closestLocator != m_target->GetLocator() ) + { + // Set up the firing states correctly + SetupFiringState(); + } + } + // recheck every 2 seconds + m_recheckTimeLeft = 2.f; + } + } + } + m_firingEffect->UpdateSynchronous( updateContext ); + } + + // update the target locator position + // TODO: probably wrong + Vector3 position = m_parentData.transform.GetTranslation(); + if( m_firingEffect ) + { + m_firingEffect->GetStartPosition( position ); + } + + m_target->Update( deltaT, &position ); + + if( m_mesh && m_turretMovementObserver != nullptr ) + { + // TODO: new turret movementObserver prob needed + // m_turretMovementObserver->Update( m_singleTurrets[0].worldMatrix ); + } + EveChildMesh::UpdateSyncronous( updateContext, params ); +} + +void EveChildTurret::UpdateAsyncronous( const EveUpdateContext& updateContext, const EveChildUpdateParams& params ) +{ + + // TODO: prob some freakyness in here + float deltaT = updateContext.GetDeltaT(); + // handle fading of turret tracking + if( m_trackingInfluenceDelta != 0.f ) + { + m_trackingInfluence += m_trackingInfluenceDelta * deltaT; + if( m_trackingInfluence > m_maxTrackingTime ) + { + m_trackingInfluence = m_maxTrackingTime; + m_trackingInfluenceDelta = 0.f; + } + else if( m_trackingInfluence < 0.f ) + { + m_trackingInfluence = 0.f; + m_trackingInfluenceDelta = 0.f; + } + } + + if( m_delayToFadeOutTracking > 0.f ) + { + m_delayToFadeOutTracking -= deltaT; + if( m_delayToFadeOutTracking <= 0.f ) + { + m_delayToFadeOutTracking = 0.f; + m_trackingInfluenceDelta = -1.f / TRACKING_FADE_TIME; + } + } + + if( m_delayToFadeInTracking > 0.f ) + { + m_delayToFadeInTracking -= deltaT; + if( m_delayToFadeInTracking <= 0.f ) + { + m_delayToFadeInTracking = 0.f; + m_trackingInfluenceDelta = 1.f / TRACKING_FADE_TIME; + } + } + + // TODO: does this have to happen after the timing stuff over there ^ + // Should handle all the mesh data and transforms + EveChildMesh::UpdateAsyncronous( updateContext, params ); + + // setup and update attached firing effect + if( m_firingEffect ) + { + // TODO: is this a valid replacement of if( m_activeTurret != INVALID_TURRET_INDEX ) + if( m_mesh ) + { + // update all muzzle points in the firing effect + for( unsigned int i = 0; i < m_firingEffect->GetPerMuzzleEffectCount(); ++i ) + { + // get world transform of this muzzle bone + Matrix matrix = GetFiringBoneWorldTransform( i ); + // and set it to the muzzle + m_firingEffect->SetMuzzleTransform( i, &matrix ); + } + m_firingEffectMuzzlePosSet = true; + } + + m_firingEffect->SetEndPosition( m_target->GetTargetPosition() ); + + // time update (return value tells us if effect is ready to fire!) + if( m_firingEffect->UpdateAsynchronous( updateContext ) ) + { + // if we haven't initialised muzzle positions, do it now + // this can happen, and if we don't do this all effects originate from + // the player ship until turret geometry is loaded and muzzle positions + // properly set + if( !m_firingEffectMuzzlePosSet ) + { + for( unsigned int i = 0; i < m_firingEffect->GetPerMuzzleEffectCount(); ++i ) + { + // use something relatively sensible, even absent geometry + m_firingEffect->SetMuzzleTransform( i, &m_parentData.transform ); + } + + m_firingEffectMuzzlePosSet = true; + } + m_firingEffect->SetDisplayDestObject( m_target->ShowDestObject() ); + } + } +} + +void EveChildTurret::ReleaseCachedData( BlueAsyncRes* resource ) +{ + // TODO: for now duplicates destructor + m_skeleton = nullptr; + m_skeletonBoneIndices.clear(); + m_boneBounds.clear(); +} + +void EveChildTurret::RebuildCachedData( BlueAsyncRes* resource ) +{ + const auto geometryResource = m_mesh->GetGeometryResource(); + if( resource == geometryResource ) + { + // finished loading the turret geometry resource, so grab vertex decl and bounding sphere + if( geometryResource->GetMeshCount() ) + { + if( const TriGeometryResMeshData* meshData = geometryResource->GetMeshData( 0 ) ) + { + + // get a bounding box for visibility detection, if this is not already set in the redfile + // TODO: might not be needed + if( m_worldBoundingSphere.radius == 0.f ) + { + geometryResource->RecalculateBoundingSphere(); + Vector4 boundingSphere; + geometryResource->GetBoundingSphere( 0, boundingSphere ); + m_worldBoundingSphere = CcpMath::Sphere( boundingSphere ); + } + } + } + + if( geometryResource->GetSkeletonCount() ) + { + if( TriGeometryResSkeletonData* skeletonData = geometryResource->GetSkeletonData( 0 ) ) + { + for( int i = 0; i < SYSBONE_MAX; ++i ) + { + // in case we don't find system bone, ::FindJoint() returns 0xffffffff + m_systemBoneID[i] = skeletonData->FindJoint( s_systemBoneSkeletonNames[i].c_str() ); + } + + InitializeFiringEffect(); + } + } + + InitializeAnimation(); + + if( !m_animationQueue.empty() ) + { + std::vector pending; + pending.swap( m_animationQueue ); + for( const auto& [animName, animNameIdle] : pending ) + { + PlayAnimation( animName, animNameIdle ); + } + } + else + { + // force an anim based on a state + ForceIdleAnimation(); + } + } +} + +void EveChildTurret::EnterStateDeactive() +{ + switch( m_state ) + { + case STATE_DEACTIVE: + // do nothing if we are already in this state + break; + case STATE_IDLE: + case STATE_RELOADING: + // no fadeout of tracking, just play deactive anim and then the deactive loop + m_trackingInfluence = 0.f; + PlayAnimation( "Pack", "Inactive" ); + m_delayToFadeOutTracking = 0.f; + break; + case STATE_FIRING: + // stop shooting + if( m_firingEffect ) + { + m_firingEffect->StopFiring(); + } + // DON'T break, just continue with stopping things: + case STATE_TARGETING: + // fadeout the tracking, play deactive anim and then the deactive loop + m_delayToFadeOutTracking = 0.0001f; + m_target->StopFireAtLocator(); + + PlayAnimation( "Pack", "Inactive", TRACKING_FADE_TIME ); + break; + + default: + break; + } + m_state = STATE_DEACTIVE; +} + +void EveChildTurret::EnterStateIdle() +{ + // TODO: might want to remove this state + // if( !m_isOnline ) + // { + // return; + // } + + switch( m_state ) + { + case STATE_INVALID: + case STATE_RELOADING: + // just play active loop + PlayAnimation( "", "Active" ); + break; + case STATE_DEACTIVE: + // start unpack animation, disable tracking and then into active loop + PlayAnimation( "Deploy", "Active" ); + m_trackingInfluence = 0.f; + break; + case STATE_IDLE: + // do nothing here + break; + case STATE_TARGETING: + case STATE_FIRING: + // stop shooting, fadeout tracking, then into active loop + m_delayToFadeOutTracking = 0.0001f; + m_target->StopFireAtLocator(); + if( m_firingEffect ) + { + m_firingEffect->StopFiring(); + } + PlayAnimation( "", "Active", TRACKING_FADE_TIME ); + + if( m_playMovementSound && !m_targetingToIdleMovementAudioEvent.empty() ) + { + SendEventToAudEmitter( m_turretMovementObserver, m_targetingToIdleMovementAudioEvent ); + } + break; + } + m_state = STATE_IDLE; +} + +void EveChildTurret::EnterStateTargeting() +{ + float animLength = 0.f; + // TODO: might want to remove this state + // if( !m_isOnline ) + // { + // return; + // } + + // what state are we in? + switch( m_state ) + { + case STATE_DEACTIVE: + // play deploy anim, then active loop and fade in tracking + animLength = PlayAnimation( "Deploy", "Active", TRACKING_FADE_TIME ); + // fade in tracking + m_delayToFadeInTracking = animLength + 0.0001f; + break; + case STATE_IDLE: + case STATE_RELOADING: + // fadein tracking, play active loop + m_delayToFadeInTracking = 0.0001f; + PlayAnimation( "", "Active", TRACKING_FADE_TIME ); + break; + case STATE_TARGETING: + break; + case STATE_FIRING: + // stop shooting, then into active loop + m_target->StopFireAtLocator(); + if( m_firingEffect ) + { + m_firingEffect->StopFiring(); + } + PlayAnimation( "", "Active", 0.f ); + break; + + default: + break; + } + m_state = STATE_TARGETING; +} + +void EveChildTurret::EnterStateFiring() +{ + if( !SetupFiringState() ) + { + return; + } + + // only if we are in firing mode, call ::StopFiring() on the effect right before + // we call ::PrepareFiring(), it'll clean things up in the effect + if( m_firingEffect && m_state == STATE_FIRING ) + { + if( m_firingEffect->IsLooping() ) + { + // We don't want to start and stop the curves when the turret is looping and firing + m_firingEffect->PrepareFiringEffectMoveObjects(); + return; + } + m_firingEffect->StopFiring(); + } + + // We're starting a firing sequence, we need to set up our firing effect time-delays + if( m_firingEffect ) + { + // TODO: random firing delay yay or nay + //if( m_maxCyclingFirePos > 1 ) + //{ + // m_firingEffect->PrepareFiring( m_randomFiringDelay, m_currentCyclingFiresPos, m_cyclingFireGroupCount ); + //} + //else + //{ + float randomFiringDelay = 0.f; + m_firingEffect->PrepareFiring( randomFiringDelay ); + //} + + if( m_target != nullptr ) + { + m_firingEffect->SetImpactConfiguration( m_target->GetImpactConfiguration() ); + } + } + + // finally, we can set state + m_state = STATE_FIRING; +} + +bool EveChildTurret::SetupFiringState() +{ + if( m_state == STATE_DEACTIVE ) + { + // this state change is forbidden! + CCP_LOGERR( "EveTurretSet %s wants to fire but is in deactive state.", m_name.c_str() ); + return false; + } + int closestLocator = -1; + { + Vector3 source = m_worldTransform.GetTranslation(); + Vector3 position = source; + closestLocator = m_target->FindClosestLocator( &source, &position ); + } + + // TODO: remove or keep? + // if this turret is set to cycle through the muzzles for firing, do it here + /* + if( m_maxCyclingFirePos > 1 ) + { + m_currentCyclingFiresPos += m_cyclingFireGroupCount; + if( m_currentCyclingFiresPos >= m_maxCyclingFirePos * m_cyclingFireGroupCount ) + { + m_currentCyclingFiresPos = 0; + } + } + */ + + // timing: apply a randomized fire delay + // TODO: remove? + // CalcRandomDelay(); + float m_randomFiringDelay = 0.f; // TODO: temp value + + // timing: is the length of the firing effect known? + float effectTotalTime = m_firingEffect ? m_firingEffect->GetFiringDuration() : 0.f; + float effectPeakTime = m_firingEffect ? m_firingEffect->GetFiringPeakTime() : 0.f; + + Vector3 source = m_parentData.transform.GetTranslation(); + + // what state are we in? + switch( m_state ) + { + case STATE_IDLE: + case STATE_RELOADING: + // and delay the effect until we are facing target + m_randomFiringDelay += m_maxTrackingTime; + // fadein tracking, play fire anim (only one the firing turret!) and then the active anim + m_delayToFadeInTracking = 0.0001f; + + PlayAnimation( GetFireAnimationName(), "Active", m_randomFiringDelay ); + // assign locator and turret + m_target->StartFireAtLocator( closestLocator, m_randomFiringDelay + effectPeakTime, effectTotalTime - effectPeakTime, &source ); + break; + case STATE_FIRING: + case STATE_TARGETING: + PlayAnimation( GetFireAnimationName(), "Active", m_randomFiringDelay ); + m_target->StartFireAtLocator( closestLocator, m_randomFiringDelay + effectPeakTime, effectTotalTime - effectPeakTime, &source ); + break; + + default: + break; + } + + return true; +} + +void EveChildTurret::EnterStateReloading() +{ + // what state are we in? + switch( m_state ) + { + case STATE_DEACTIVE: + // ignore this state change: when the turret is inactive, no reload state can be shown! + break; + case STATE_INVALID: + case STATE_IDLE: + case STATE_RELOADING: + // just play reloading anim and then loop + PlayAnimation( "Reload", "Active", 0.f ); + break; + case STATE_TARGETING: + case STATE_FIRING: + // stop shooting, fadeout tracking, then into active loop + m_delayToFadeOutTracking = 0.0001f; + m_target->StopFireAtLocator(); + if( m_firingEffect ) + { + m_firingEffect->StopFiring(); + } + + PlayAnimation( "Reload", "Active", TRACKING_FADE_TIME ); + break; + + default: + break; + } + m_state = STATE_RELOADING; +} + +void EveChildTurret::ForceStateDeactive() +{ + // turn it all off + m_trackingInfluence = 0.f; + m_delayToFadeOutTracking = 0.f; + m_target->StopFireAtLocator(); + if( m_firingEffect ) + { + m_firingEffect->StopFiring(); + } + // finally, we can set state + m_state = STATE_DEACTIVE; + + // now force-play the deactive anim for this state + ForceIdleAnimation(); +} + +void EveChildTurret::ForceIdleAnimation() +{ + std::string idleAnimName = ""; + // what state? + switch( m_state ) + { + case STATE_DEACTIVE: + idleAnimName = "Inactive"; + break; + case STATE_IDLE: + case STATE_TARGETING: + case STATE_FIRING: + idleAnimName = "Active"; + break; + + default: + break; + } + + // set it to all turrets in this set + if( idleAnimName.length() > 0 ) + { + PlayAnimation( "", idleAnimName, 0.f ); + } +} + +void EveChildTurret::ForceStateTargeting() +{ + m_trackingInfluence = m_maxTrackingTime; + m_trackingInfluenceDelta = 0.f; + + m_state = STATE_TARGETING; + + // now force-play the deactive anim for this state + PlayAnimation( "", "Active", 0.f ); +} + +Matrix EveChildTurret::GetFiringBoneWorldTransform( unsigned int muzzle ) const +{ + if( !m_mesh ) + { + return m_parentData.transform; + } + + Matrix matrix = m_worldTransform; + + // get the boneID for that muzzle from firing effect + if( !m_firingEffect ) + { + return matrix; + } + unsigned int boneID = m_firingEffect->GetPerMuzzleBoneID( muzzle ); + return GetTurretBoneTransform( boneID ); +} + +void EveChildTurret::InitializeFiringEffect() +{ + if( !m_firingEffect ) + { + return; + } + m_firingEffect->RegisterWithQuadRenderer( *Tr2QuadRenderer::Instance() ); + + auto geometryResource = m_mesh->GetGeometryResource(); + if( geometryResource && geometryResource->GetSkeletonCount() ) + { + if( TriGeometryResSkeletonData* skeletonData = geometryResource->GetSkeletonData( 0 ) ) + { + const auto muzzleCount = m_firingEffect->GetPerMuzzleEffectCount(); + if( muzzleCount > EveTurretFiringFX::MUZZLECOUNT_MAX ) + { + CCP_LOGERR( "Upper limit of firing bones is %d, this turret has %d", EveTurretFiringFX::MUZZLECOUNT_MAX, muzzleCount ); + } + + const unsigned int boneCount = std::min( muzzleCount, static_cast( EveTurretFiringFX::MUZZLECOUNT_MAX ) ); + // firing bones should always be on the format Pos_FireXX where XX can range form 01 to 99 + for( unsigned int i = 0; i < boneCount; ++i ) + { + char boneName[32]; + int boneNameIndex = i + 1; + snprintf( boneName, sizeof boneName, "%s%02u", m_firingEffect->GetFiringBoneName(), boneNameIndex ); + + // in case we don't find positional bone, ::FindJoint() returns 0xffffffff + m_firingEffect->SetMuzzleBoneID( i, skeletonData->FindJoint( boneName ) ); + } + } + } +} + +void EveChildTurret::InitializeAnimation() +{ + EveChildMesh::InitializeAnimation(); + if( const auto geometryResource = m_mesh->GetGeometryResource() ) + { + // get a model, a meshbinding and animation stuff from the resource + const cmf::Data* cmfData = geometryResource->GetCMFData(); + if( cmfData && cmfData->skeletons.size() ) + { + const auto mesh = std::find_if( cmfData->meshes.begin(), cmfData->meshes.end(), []( const cmf::Mesh& m ) { + return m.skeleton == 0; + } ); + + if( mesh != cmfData->meshes.end() && mesh->boneBindings.size() ) + { + if( m_skeletonBoneIndices.empty() ) + { + m_skeleton = &cmfData->skeletons[0]; + + + if( !m_sequencer ) + { + m_sequencer = std::make_unique( *m_skeleton ); + cmf::RestPose( m_pose, *m_skeleton ); + } + + m_skeletonBoneIndices = Tr2GrannyAnimationUtils::CreateMapping( *m_skeleton, mesh->boneBindings, static_cast( mesh->boneBindings.size() ) ); + } + } + } + } +} + +void EveChildTurret::ModifySystemBoneTransform( SystemBones bone, const Vector3* target, const Matrix* localTransform, Vector3& position, Quaternion& rotation ) const +{ + switch( bone ) + { + case SYSBONE_INVALID: + break; + case SYSBONE_ROTATION: + case SYSBONE_ROTATION01: + case SYSBONE_ROTATION02: { + // rotation of turret 360 degrees, alpha is between -pi and pi + float alpha = atan2( target->x, target->z ); + // never forget do apply influence! + alpha *= m_trackingInfluence; + // 1st: make quaternion + Quaternion quat = RotationQuaternion( alpha, 0.f, 0.f ); + // 2nd: apply this quat after the original one + quat = rotation * quat; + // TODO: cmf_transform ? + // 3rd: make granny_transform from quat + rotation = quat; + } + break; + case SYSBONE_COUNTER_ROTATION: { + // inverse(!!) rotation of turret 360 degress, alpha is between -pi and pi + float alpha = -atan2( target->x, target->z ); + // never forget do apply influence! + alpha *= m_trackingInfluence; + // 1st: make quaternion + Quaternion quat = RotationQuaternion( alpha, 0.f, 0.f ); + // 2nd: apply this quat after the original one + quat = rotation * quat; + // 3rd: make granny_transform from quat + rotation = quat; + } + break; + case SYSBONE_PITCH: + case SYSBONE_PITCH1: + case SYSBONE_PITCH2: { + CalcTransformForPitchBone( target, XMConvertToRadians( m_sysBonePitchMin ), XMConvertToRadians( m_sysBonePitchMax ), bone, localTransform, rotation ); + } + break; + case SYSBONE_SCALED_HEIGHT: { + // pitch of barrel 90 degrees + Vector3 directionNormal = Normalize( *target ); + float height = TriClamp( directionNormal.y, 0.f, 1.f ); + // never forget do apply influence! + height *= m_trackingInfluence; + // it's a pos extension with a scale + Vector3 pos = Vector3( 0.f, height * m_sysBoneHeight, 0.f ) + position; + position = pos; + } + break; + case SYSBONE_SCALED_PITCH01: + case SYSBONE_SCALED_PITCH02: + case SYSBONE_SCALED_PITCH03: + case SYSBONE_SCALED_PITCH04: + case SYSBONE_SCALED_PITCH05: + case SYSBONE_SCALED_PITCH06: { + CalcTransformForPitchBone( target, 0.f, XMConvertToRadians( m_sysBonePitchMax ), bone, nullptr, rotation ); + } + break; + default: + break; + } +} + +void EveChildTurret::CalcTransformForPitchBone( const Vector3* target, float minPitch, float maxPitch, unsigned int boneIndex, const Matrix* localTransform, Quaternion& rotation ) const +{ + float pitchOffset = GetBonePitchOffset( boneIndex ); + float pitchFactor = GetBonePitchFactor( boneIndex ); + // pitch of barrel 90 degrees + Vector3 bone_position( 0.f, 0.f, 0.f ); + + if( localTransform ) + { + bone_position = localTransform->GetTranslation(); + } + + Vector3 relTarget = *target - bone_position; + Vector3 dirNrm = Normalize( relTarget ); + float radians = asinf( dirNrm.y ); + + if( localTransform ) + { + Vector3 bone_direction = Normalize( bone_position ); + float d = Dot( bone_direction, *target ); + if( d < Length( bone_position ) ) + { + // Assuming up is enough for now to avoid cross products + radians = TriFloatSign( relTarget.y ) * XM_PI - radians; + } + } + + float alpha = TriClamp( radians, minPitch, maxPitch ); + // modify! + alpha = pitchFactor * alpha + XMConvertToRadians( pitchOffset ); + // never forget do apply influence! + alpha *= m_trackingInfluence; + // 1st: make quaternion + Quaternion quat = RotationQuaternion( 0.f, -alpha, 0.f ); + // 2nd: apply this quat after the original one + quat = rotation * quat; + // 2nd: make granny_transform from quat + rotation = quat; +} + +float EveChildTurret::GetBonePitchFactor( unsigned int boneIndex ) const +{ + switch( boneIndex ) + { + case SYSBONE_PITCH: + case SYSBONE_PITCH1: + case SYSBONE_PITCH2: + return m_sysBonePitchFactor; + case SYSBONE_SCALED_PITCH01: + return m_sysBonePitch01Factor; + case SYSBONE_SCALED_PITCH02: + return m_sysBonePitch02Factor; + case SYSBONE_SCALED_PITCH03: + return m_sysBonePitch03Factor; + default: + return 1.0f; + } +} + +float EveChildTurret::GetBonePitchOffset( unsigned int boneIndex ) const +{ + switch( boneIndex ) + { + case SYSBONE_PITCH: + case SYSBONE_PITCH1: + case SYSBONE_PITCH2: + return m_sysBonePitchOffset; + case SYSBONE_SCALED_PITCH01: + return m_sysBonePitch01Offset; + case SYSBONE_SCALED_PITCH02: + return m_sysBonePitch02Offset; + case SYSBONE_SCALED_PITCH03: + return m_sysBonePitch03Offset; + default: + return 0.0f; + } +} +Matrix EveChildTurret::GetTurretBoneTransform( uint32_t boneID ) const +{ + Matrix matrix = m_worldTransform; + + if( boneID == INVALID_BONE_INDEX ) + { + // TODO: should support lowLodTransform? prob yes + // return lowLodTransform * matrix; + return matrix; + } + if( m_animationUpdater ) + { + const auto& worldTransforms = m_animationUpdater->GetWorldTransforms(); + if( !worldTransforms.empty() ) + { + return worldTransforms[boneID] * matrix; + } + } + // TODO: port rest of function? + return matrix; +} + +float EveChildTurret::PlayAnimation( const std::string& animName, const std::string& animNameIdle, float delay ) +{ + if( !m_animationUpdater ) + { + return 0.f; + } + float animLength = 0.f; + + auto cmfData = m_mesh->GetGeometryResource()->GetCMFData(); + if( cmfData ) + { + // there can be more animations in one res, so find right one + size_t animIx = cmfData->animations.size(); + if( !animName.empty() ) + { + auto animation = std::find_if( cmfData->animations.begin(), cmfData->animations.end(), [&animName]( const cmf::Animation& anim ) { + return cmf::ToStdStringView( anim.name ) == animName; + } ); + if( animation == cmfData->animations.end() ) + { + return 0.f; + } + animIx = std::distance( cmfData->animations.begin(), animation ); + } + + size_t idleIx = cmfData->animations.size(); + if( !animNameIdle.empty() ) + { + auto animation = std::find_if( cmfData->animations.begin(), cmfData->animations.end(), [&animNameIdle]( const cmf::Animation& anim ) { + return cmf::ToStdStringView( anim.name ) == animNameIdle; + } ); + if( animation == cmfData->animations.end() ) + { + return 0.f; + } + idleIx = std::distance( cmfData->animations.begin(), animation ); + } + + // stop all animation + StopAnimation( delay ); + + if( m_animationUpdater ) + { + // granny, play first anim once, if provided & found + if( animIx != cmfData->animations.size() ) + { + auto& animation = cmfData->animations[animIx]; + animLength = animation.duration; + + // ( const char* animName, bool replace, int loopCount, float delay, float speed, bool clearWhenDone ) + std::string animationName = cmf::ToStdString( animation.name ); + bool replace = false; // TODO: correct? + int loopCount = 1; + float speed = 1.f; + m_animationUpdater->PlayAnimation( animationName.c_str(), replace, loopCount, delay, speed ); + // TODO: set start/stop time seems different from ^ + // player->SetStartTime( delay + Tr2Renderer::GetAnimationTime() ); + // player->SetStopTime( animLength + delay + Tr2Renderer::GetAnimationTime() ); + } + + // then play idle anim on loop (after delay), if provided & found + if( idleIx != cmfData->animations.size() ) + { + auto& animation = cmfData->animations[idleIx]; + std::string animationName = cmf::ToStdString( animation.name ); + bool replace = false; // TODO: correct? + int loopCount = 0; + float speed = 1.f; + + m_animationUpdater->PlayAnimation( animationName.c_str(), replace, loopCount, delay, speed ); + // TODO: set start time seems different from ^ + // player->SetStartTime( animLength + delay + Tr2Renderer::GetAnimationTime() ); + } + } + } + + return animLength; +} + +void EveChildTurret::StopAnimation( float delay ) +{ + // TODO: prob not needed and can just use animation class function instead of this probably useless middleman + auto geometryResource = m_mesh->GetGeometryResource(); + // if we don't have a geometry, animation is useless and probably unwanted + if( !geometryResource ) + { + return; + } + + // empty queue, so no more buffered requests + m_animationQueue.clear(); + + // stop + if( m_animationUpdater ) + { + /* + // TODO: find correct functions + m_animationUpdater->EnumerateAnimations( [&]( const std::shared_ptr& player ) { + player->SetStopTime( delay + Tr2Renderer::GetAnimationTime() ); + } ); + + m_animationUpdater->RemoveFinishedAnimations( Tr2Renderer::GetAnimationTime() ); + */ + } +} + +std::string EveChildTurret::GetFireAnimationName() const +{ + /* + // TODO: idk if this is useful skipping for now + // if m_currentCyclingFiresPos is 0, it's just "Fire" + std::string res = "Fire"; + if( m_currentCyclingFiresPos > 0 ) + { + res.push_back( '0' ); + res.push_back( '0' + m_currentCyclingFiresPos / m_cyclingFireGroupCount ); + } + + return res; + */ + return ""; +} + +EveTurretFiringFXPtr EveChildTurret::GetFiringEffect() +{ + return m_firingEffect; +} + +void EveChildTurret::SetFiringEffect( const EveTurretFiringFXPtr& firingEffect ) +{ + auto registry = GetComponentRegistry(); + if( EveEntityPtr entity = BlueCastPtr( m_firingEffect ) ) + { + entity->UnRegister( registry ); + } + m_firingEffect = firingEffect; + if( EveEntityPtr entity = BlueCastPtr( m_firingEffect ) ) + { + entity->Register( registry ); + } + InitializeFiringEffect(); +} + +void EveChildTurret::SetTargetObject( IRoot* target ) +{ + if( !target ) + { + return; + } + ITriTargetablePtr oldTargetPtr = m_target->GetTargetable(); + + // attach to target + m_target->SetTargetable( target ); + + if( m_playMovementSound && !m_idleToTargetingMovementAudioEvent.empty() ) + { + // Always trigger movement sounds if coming from IDLE state, otherwise trigger it only if you're targeting a new object. + if( m_state == STATE_IDLE || !oldTargetPtr.IsEqualObject( m_target->GetTargetable() ) ) + { + SendEventToAudEmitter( m_turretMovementObserver, m_idleToTargetingMovementAudioEvent ); + } + } + + // update the firing effect we have one + SetTargetScale(); +} + +ITriTargetablePtr EveChildTurret::GetTargetObject() +{ + return m_target->GetTargetable(); +} + +void EveChildTurret::SetTargetScale() +{ + if( m_firingEffect ) + { + float radius = m_target->GetRadius(); + m_firingEffect->SetScaleByRadius( radius ); + } +} diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.h b/trinity/Eve/SpaceObject/Children/EveChildTurret.h new file mode 100644 index 000000000..d1520a4e1 --- /dev/null +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.h @@ -0,0 +1,194 @@ +// Copyright © 2026 Fenris Creations ehf. + +#pragma once +#ifndef EveChildTurret_H +#define EveChildTurret_H + +#include "EveChildMesh.h" +#include "Eve/Turret/EveTurretTarget.h" + +BLUE_DECLARE( EveTurretFiringFX ); +BLUE_DECLARE( EveTurretTarget ); +BLUE_DECLARE( EveTurretTarget ); +BLUE_DECLARE( EveChildInstanceContainer ); + +BLUE_CLASS( EveChildTurret ) : public EveChildMesh, public IBlueAsyncResNotifyTarget +{ +public: + EXPOSE_TO_BLUE(); + + EveChildTurret( IRoot* lockobj = nullptr ); + ~EveChildTurret(); + + bool Initialize() override; + bool OnModified( Be::Var * value ) override; + void RegisterComponents() override; + void UnRegisterComponents() override; + + void GetDebugOptions( Tr2DebugRendererOptions & options ) override; + void RenderDebugInfo( ITr2DebugRenderer2 & renderer ) override; + + // EveSpaceObjectChild + void UpdateSyncronous( const EveUpdateContext& updateContext, const EveChildUpdateParams& params ) override; + void UpdateAsyncronous( const EveUpdateContext& updateContext, const EveChildUpdateParams& params ) override; + + // IBlueAsyncResNotifyTarget + void ReleaseCachedData( BlueAsyncRes * resource ) override; + void RebuildCachedData( BlueAsyncRes * resource ) override; + + // action + void EnterStateDeactive(); + void EnterStateIdle(); + void EnterStateTargeting(); + void EnterStateFiring(); + bool SetupFiringState(); + void EnterStateReloading(); + + void ForceStateDeactive(); + void ForceIdleAnimation(); + void ForceStateTargeting(); + + Matrix GetFiringBoneWorldTransform( unsigned int muzzle ) const; + + // turret set states + enum State + { + STATE_INVALID = 0, + STATE_DEACTIVE, + STATE_IDLE, + STATE_TARGETING, + STATE_FIRING, + STATE_RELOADING, + }; + +protected: + // system-controlled bones + // TODO: needed? + enum SystemBones + { + SYSBONE_INVALID = 0, + SYSBONE_ROTATION, + SYSBONE_ROTATION01, + SYSBONE_ROTATION02, + SYSBONE_COUNTER_ROTATION, + SYSBONE_PITCH, + SYSBONE_PITCH1, + SYSBONE_PITCH2, + SYSBONE_SCALED_HEIGHT, + SYSBONE_SCALED_PITCH01, + SYSBONE_SCALED_PITCH02, + SYSBONE_SCALED_PITCH03, + SYSBONE_SCALED_PITCH04, + SYSBONE_SCALED_PITCH05, + SYSBONE_SCALED_PITCH06, + SYSBONE_MAX, + }; + + // setup the attached firing effect + void InitializeFiringEffect(); + + void InitializeAnimation() override; + + // TODO: Need update LOD ? + + // set transform for tracking + void ModifySystemBoneTransform( + SystemBones bone, const Vector3* target, const Matrix* localTransform, Vector3& position, Quaternion& rotation ) + const; + + // Calculates the pitch for a bone based on the parameters + void CalcTransformForPitchBone( const Vector3* target, + float minPitch, + float maxPitch, + unsigned int boneIndex, + const Matrix* localTransform, + Quaternion& rotation ) const; + + // Returns the correct pitch factor for a specific bone index + float GetBonePitchFactor( unsigned int boneIndex ) const; + // Returns the correct pitch offset for a specific bone index + float GetBonePitchOffset( unsigned int boneIndex ) const; + + Matrix GetTurretBoneTransform( uint32_t boneID ) const; + + // animation + float PlayAnimation( const std::string& animName, const std::string& animNameIdle, float delay = 0.f ); + void StopAnimation( float delay = 0.f ); + std::string GetFireAnimationName() const; + + EveTurretFiringFXPtr GetFiringEffect(); + void SetFiringEffect( const EveTurretFiringFXPtr& firingEffect ); + + // TODO: rename GrannyBoneBindingBounds? + std::vector m_boneBounds; + + // Assign the target object + void SetTargetObject( IRoot * target ); + ITriTargetablePtr GetTargetObject(); + void SetTargetScale(); + + // target (object we are tracking) + EveTurretTargetPtr m_target; + + // impacts + float m_impactSize; + ImpactBehaviour::Type m_impactBehaviour; + + // tracking + float m_trackingInfluence; + float m_trackingInfluenceDelta; + float m_delayToFadeOutTracking; + float m_delayToFadeInTracking; + float m_maxTrackingTime; + + // animation + // TODO: needed? + struct AnimationRequest + { + std::string animName; + std::string animNameIdle; + }; + std::vector m_animationQueue; + const cmf::Skeleton* m_skeleton; + std::vector m_skeletonBoneIndices; + std::unique_ptr m_sequencer; + cmf::SkeletonPose m_pose; + + // system bones + unsigned int m_systemBoneID[SYSBONE_MAX]; + // specific system bone values + float m_sysBoneHeight; + float m_sysBonePitchOffset; + float m_sysBonePitchFactor; + float m_sysBonePitchMin; + float m_sysBonePitchMax; + float m_sysBonePitch01Offset; + float m_sysBonePitch01Factor; + float m_sysBonePitch02Offset; + float m_sysBonePitch02Factor; + float m_sysBonePitch03Offset; + float m_sysBonePitch03Factor; + + // state of turret set + State m_state; + + float m_recheckTimeLeft; + + // firing effect redfile path + std::string m_firingEffectResPath; + + // TODO: move firing effect into its own class? + // firing effect + EveTurretFiringFXPtr m_firingEffect; + bool m_firingEffectMuzzlePosSet; + + // Audio specific attributes + bool m_playMovementSound; + TriObserverLocalPtr m_turretMovementObserver; + std::wstring m_idleToTargetingMovementAudioEvent; + std::wstring m_targetingToIdleMovementAudioEvent; +}; + +TYPEDEF_BLUECLASS( EveChildTurret ); + +#endif diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp new file mode 100644 index 000000000..c05c72697 --- /dev/null +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp @@ -0,0 +1,13 @@ +// Copyright © 2026 Fenris Creations ehf. + +#include "StdAfx.h" +#include "EveChildTurret.h" + +BLUE_DEFINE( EveChildTurret ); + +const Be::ClassInfo* EveChildTurret::ExposeToBlue() +{ + EXPOSURE_BEGIN( EveChildTurret, "" ) + MAP_INTERFACE( EveChildTurret ) + EXPOSURE_CHAINTO( EveChildMesh ) +} diff --git a/trinity/Eve/Turret/EveTurretSet.cpp b/trinity/Eve/Turret/EveTurretSet.cpp index 87efc0812..151c7f0ff 100644 --- a/trinity/Eve/Turret/EveTurretSet.cpp +++ b/trinity/Eve/Turret/EveTurretSet.cpp @@ -67,9 +67,9 @@ bool IsUsingCMF( TriGeometryRes* geometryResource ) // -------------------------------------------------------------------------------- // Description: -// Initialize data members, set everything to inlavid/empty and call -// ::PrepareResouce(), which will create a vertex decleration and a -// special istance buffer for the instance rendering. Also load the +// Initialize data members, set everything to invalid/empty and call +// ::PrepareResource(), which will create a vertex declaration and a +// special instance buffer for the instance rendering. Also load the // shader for shadow generation // -------------------------------------------------------------------------------- EveTurretSet::EveTurretSet( IRoot* lockobj ) : @@ -1419,7 +1419,7 @@ void EveTurretSet::UpdateSingleTurrets() // First thing to do is to keep the world-matrices of all turrets up to date, // cause most likely the ship has moved. Then sample the granny animation // for each single turret of this set, cause they are animated independently. -// Just before collapsing the skeleton matrices, sneek in a bone modification +// Just before collapsing the skeleton matrices, sneak in a bone modification // for the auto tracking. // Also smoothly do some fading between states and tracking positions. // SeeAlso: @@ -1564,7 +1564,7 @@ void EveTurretSet::UpdateTurretTransforms( const Matrix* turretTransformMatrix ) // -------------------------------------------------------------------------------- Matrix EveTurretSet::GetFiringBoneWorldTransform( unsigned int muzzle ) const { - // there MUST be an avtive turret aka a "firing turret"! + // there MUST be an active turret aka a "firing turret"! unsigned int closestTurret = m_activeTurret; // so if we don't have one, calc one temporarily if( closestTurret == INVALID_TURRET_INDEX ) @@ -1735,7 +1735,7 @@ void EveTurretSet::ModifySystemBoneTransform( SystemBones bone, const Vector3* t case SYSBONE_ROTATION: case SYSBONE_ROTATION01: case SYSBONE_ROTATION02: { - // rotation of turret 360 degress, alpha is between -pi and pi + // rotation of turret 360 degrees, alpha is between -pi and pi float alpha = atan2( target->x, target->z ); // never forget do apply influence! alpha *= m_trackingInfluence; @@ -2918,7 +2918,7 @@ void EveTurretSet::EnterStateIdle() break; case STATE_TARGETING: case STATE_FIRING: - // stop shooting, fadout tracking, then into active loop + // stop shooting, fadeout tracking, then into active loop m_delayToFadeOutTracking = 0.0001f; m_activeTurret = INVALID_TURRET_INDEX; m_target->StopFireAtLocator(); @@ -3178,7 +3178,7 @@ void EveTurretSet::EnterStateReloading() switch( m_state ) { case STATE_DEACTIVE: - // ingnore this state change: when the turret is inactive, no reload state can be shown! + // ignore this state change: when the turret is inactive, no reload state can be shown! break; case STATE_INVALID: case STATE_IDLE: @@ -3191,7 +3191,7 @@ void EveTurretSet::EnterStateReloading() break; case STATE_TARGETING: case STATE_FIRING: - // stop shooting, fadout tracking, then into active loop + // stop shooting, fadeout tracking, then into active loop m_delayToFadeOutTracking = 0.0001f; m_activeTurret = INVALID_TURRET_INDEX; m_target->StopFireAtLocator(); From 3565268d60052be7cd98f5eef967763af3a12f78 Mon Sep 17 00:00:00 2001 From: breki Date: Fri, 24 Jul 2026 16:24:27 +0000 Subject: [PATCH 02/14] fix formatting --- trinity/Eve/SpaceObject/Children/EveChildTurret.h | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.h b/trinity/Eve/SpaceObject/Children/EveChildTurret.h index d1520a4e1..db4a64380 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.h +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.h @@ -12,7 +12,8 @@ BLUE_DECLARE( EveTurretTarget ); BLUE_DECLARE( EveTurretTarget ); BLUE_DECLARE( EveChildInstanceContainer ); -BLUE_CLASS( EveChildTurret ) : public EveChildMesh, public IBlueAsyncResNotifyTarget +BLUE_CLASS( EveChildTurret ) : + public EveChildMesh, public IBlueAsyncResNotifyTarget { public: EXPOSE_TO_BLUE(); @@ -92,17 +93,10 @@ BLUE_CLASS( EveChildTurret ) : public EveChildMesh, public IBlueAsyncResNotifyTa // TODO: Need update LOD ? // set transform for tracking - void ModifySystemBoneTransform( - SystemBones bone, const Vector3* target, const Matrix* localTransform, Vector3& position, Quaternion& rotation ) - const; + void ModifySystemBoneTransform( SystemBones bone, const Vector3* target, const Matrix* localTransform, Vector3& position, Quaternion& rotation ) const; // Calculates the pitch for a bone based on the parameters - void CalcTransformForPitchBone( const Vector3* target, - float minPitch, - float maxPitch, - unsigned int boneIndex, - const Matrix* localTransform, - Quaternion& rotation ) const; + void CalcTransformForPitchBone( const Vector3* target, float minPitch, float maxPitch, unsigned int boneIndex, const Matrix* localTransform, Quaternion& rotation ) const; // Returns the correct pitch factor for a specific bone index float GetBonePitchFactor( unsigned int boneIndex ) const; From add33a3828fc9e2eaf2f26c6e818dc8d5fddc005 Mon Sep 17 00:00:00 2001 From: breki Date: Mon, 27 Jul 2026 14:16:18 +0000 Subject: [PATCH 03/14] initalize turret variable and setup _blue file and fix compile error --- .../SpaceObject/Children/EveChildTurret.cpp | 10 ++- .../Eve/SpaceObject/Children/EveChildTurret.h | 52 ++++++------- .../Children/EveChildTurret_Blue.cpp | 76 +++++++++++++++++++ 3 files changed, 108 insertions(+), 30 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp index 004ed80c5..22fcde315 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp @@ -35,8 +35,12 @@ const float TRACKING_FADE_TIME = 1.f; EveChildTurret::EveChildTurret( IRoot* lockobj ) : EveChildMesh( lockobj ) -// TODO: init rest of variables { + for( unsigned int i = 0; i < SYSBONE_MAX; ++i ) + { + m_systemBoneID[i] = INVALID_BONE_INDEX; + } + m_target.CreateInstance(); PrepareResources(); @@ -1049,12 +1053,12 @@ std::string EveChildTurret::GetFireAnimationName() const return ""; } -EveTurretFiringFXPtr EveChildTurret::GetFiringEffect() +EveTurretFiringFX* EveChildTurret::GetFiringEffect() { return m_firingEffect; } -void EveChildTurret::SetFiringEffect( const EveTurretFiringFXPtr& firingEffect ) +void EveChildTurret::SetFiringEffect( EveTurretFiringFX* firingEffect ) { auto registry = GetComponentRegistry(); if( EveEntityPtr entity = BlueCastPtr( m_firingEffect ) ) diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.h b/trinity/Eve/SpaceObject/Children/EveChildTurret.h index db4a64380..cc85f8ec2 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.h +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.h @@ -9,8 +9,6 @@ BLUE_DECLARE( EveTurretFiringFX ); BLUE_DECLARE( EveTurretTarget ); -BLUE_DECLARE( EveTurretTarget ); -BLUE_DECLARE( EveChildInstanceContainer ); BLUE_CLASS( EveChildTurret ) : public EveChildMesh, public IBlueAsyncResNotifyTarget @@ -110,8 +108,8 @@ BLUE_CLASS( EveChildTurret ) : void StopAnimation( float delay = 0.f ); std::string GetFireAnimationName() const; - EveTurretFiringFXPtr GetFiringEffect(); - void SetFiringEffect( const EveTurretFiringFXPtr& firingEffect ); + EveTurretFiringFX* GetFiringEffect(); + void SetFiringEffect( EveTurretFiringFX* firingEffect ); // TODO: rename GrannyBoneBindingBounds? std::vector m_boneBounds; @@ -125,15 +123,15 @@ BLUE_CLASS( EveChildTurret ) : EveTurretTargetPtr m_target; // impacts - float m_impactSize; - ImpactBehaviour::Type m_impactBehaviour; + float m_impactSize = 0.f; + ImpactBehaviour::Type m_impactBehaviour = ImpactBehaviour::DAMAGE_LOCATOR; // tracking - float m_trackingInfluence; - float m_trackingInfluenceDelta; - float m_delayToFadeOutTracking; - float m_delayToFadeInTracking; - float m_maxTrackingTime; + float m_trackingInfluence = 0.f; + float m_trackingInfluenceDelta = 0.f; + float m_delayToFadeOutTracking = 0.f; + float m_delayToFadeInTracking = 0.f; + float m_maxTrackingTime = 1.f; // animation // TODO: needed? @@ -143,7 +141,7 @@ BLUE_CLASS( EveChildTurret ) : std::string animNameIdle; }; std::vector m_animationQueue; - const cmf::Skeleton* m_skeleton; + const cmf::Skeleton* m_skeleton = nullptr; std::vector m_skeletonBoneIndices; std::unique_ptr m_sequencer; cmf::SkeletonPose m_pose; @@ -151,22 +149,22 @@ BLUE_CLASS( EveChildTurret ) : // system bones unsigned int m_systemBoneID[SYSBONE_MAX]; // specific system bone values - float m_sysBoneHeight; - float m_sysBonePitchOffset; - float m_sysBonePitchFactor; - float m_sysBonePitchMin; - float m_sysBonePitchMax; - float m_sysBonePitch01Offset; - float m_sysBonePitch01Factor; - float m_sysBonePitch02Offset; - float m_sysBonePitch02Factor; - float m_sysBonePitch03Offset; - float m_sysBonePitch03Factor; + float m_sysBoneHeight = 1.f; + float m_sysBonePitchOffset = 0.f; + float m_sysBonePitchFactor = 1.f; + float m_sysBonePitchMin = 0.f; + float m_sysBonePitchMax = 90.f; + float m_sysBonePitch01Offset = 0.f; + float m_sysBonePitch01Factor = 1.f; + float m_sysBonePitch02Offset = 0.f; + float m_sysBonePitch02Factor = 1.f; + float m_sysBonePitch03Offset = 0.f; + float m_sysBonePitch03Factor = 1.f; // state of turret set - State m_state; + State m_state = STATE_IDLE; - float m_recheckTimeLeft; + float m_recheckTimeLeft = -1.f; // firing effect redfile path std::string m_firingEffectResPath; @@ -174,10 +172,10 @@ BLUE_CLASS( EveChildTurret ) : // TODO: move firing effect into its own class? // firing effect EveTurretFiringFXPtr m_firingEffect; - bool m_firingEffectMuzzlePosSet; + bool m_firingEffectMuzzlePosSet = false; // Audio specific attributes - bool m_playMovementSound; + bool m_playMovementSound = true; TriObserverLocalPtr m_turretMovementObserver; std::wstring m_idleToTargetingMovementAudioEvent; std::wstring m_targetingToIdleMovementAudioEvent; diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp index c05c72697..3b3bb0251 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp @@ -2,12 +2,88 @@ #include "StdAfx.h" #include "EveChildTurret.h" +#include "Eve/Turret/EveTurretFiringFX.h" BLUE_DEFINE( EveChildTurret ); +extern Be::VarChooser ImpactBehaviourChooser[]; + const Be::ClassInfo* EveChildTurret::ExposeToBlue() { EXPOSURE_BEGIN( EveChildTurret, "" ) MAP_INTERFACE( EveChildTurret ) + MAP_ATTRIBUTE( "trackingInfluence", m_trackingInfluence, "How much tracking is allowed?", Be::READ ) + MAP_ATTRIBUTE( "maxTrackingTime", m_maxTrackingTime, "How long does tracking take?", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "state", m_state, "State of the turret", Be::READ | Be::PERSIST ) + + MAP_PROPERTY( "targetObject", GetTargetObject, SetTargetObject, "object this turret will track" ) + MAP_ATTRIBUTE( "target", m_target, "Info on the target", Be::READ ) + + MAP_ATTRIBUTE( "sysBoneHeight", m_sysBoneHeight, "System bone HEIGHT extension factor", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "sysBonePitchFactor", m_sysBonePitchFactor, "main pitch factor", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "sysBonePitchOffset", m_sysBonePitchOffset, "main pitch offset (in degrees!)", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "sysBonePitchMin", m_sysBonePitchMin, "main pitch minimum clamp value, prevents the turret from targeting down too much (in degrees!)", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "sysBonePitchMax", m_sysBonePitchMax, "main pitch maximum clamp value, prevents the turret from targeting down too much (in degrees!)", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "sysBonePitch01Factor", m_sysBonePitch01Factor, "pitch 01 factor", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "sysBonePitch01Offset", m_sysBonePitch01Offset, "pitch 01 offset (in degrees!)", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "sysBonePitch02Factor", m_sysBonePitch02Factor, "pitch 02 factor", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "sysBonePitch02Offset", m_sysBonePitch02Offset, "pitch 02 offset (in degrees!)", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "sysBonePitch03Factor", m_sysBonePitch03Factor, "pitch 03 factor", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "sysBonePitch03Offset", m_sysBonePitch03Offset, "pitch 03 offset (in degrees!)", Be::READWRITE | Be::PERSIST ) + + MAP_ATTRIBUTE( "firingEffect", m_firingEffect, "", Be::HIDDEN ) // Needed to make Graphite able to detect bindings inside of the firing effect. + MAP_PROPERTY( "firingEffect", GetFiringEffect, SetFiringEffect, "The module for the firing effect of this turret" ) + MAP_ATTRIBUTE( "firingEffectResPath", m_firingEffectResPath, "A res path to the redfile containing the primary firing effect", Be::READWRITE | Be::PERSIST ) + + MAP_ATTRIBUTE( "impactSize", m_impactSize, "Size of impacts. No impact if size is 0 or less", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE_WITH_CHOOSER( "impactBehaviour", m_impactBehaviour, "What do we want to hit? ", Be::READWRITE | Be::NOTIFY | Be::PERSIST | Be::ENUM, ImpactBehaviourChooser ) + + MAP_ATTRIBUTE( "turretMovementObserver", m_turretMovementObserver, "The observer for turret movement sounds. Note: the positioning of this observer is automatic.", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "playMovementSound", m_playMovementSound, "If true this turret set will play its mechanical movement sounds if movement audio events are defined.", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "idleToTargetingMovementAudioEvent", m_idleToTargetingMovementAudioEvent, "The event to send to the audio engine for mechanical noise when a turret moves from idle to targeting.", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "targetingToIdleMovementAudioEvent", m_targetingToIdleMovementAudioEvent, "The event to send to the audio engine for mechanical noise when a turret moves from targeting to idle.", Be::READWRITE | Be::PERSIST ) + + MAP_METHOD_AND_WRAP( + "EnterStateDeactive", + EnterStateDeactive, + "Go into state deactive: play deactive anim and stay inside ship. \n:jessica-placement: TOOLBAR\n:jessica-icon: fa-bed\n" ) + + MAP_METHOD_AND_WRAP( + "EnterStateIdle", + EnterStateIdle, + "Go into state idle: play idle anim and face cannons forward. \n:jessica-placement: TOOLBAR\n:jessica-icon: fa-male\n" ) + + MAP_METHOD_AND_WRAP( + "EnterStateTargeting", + EnterStateTargeting, + "Go into state targeting: face cannons towards enemy. \n:jessica-placement: TOOLBAR\n:jessica-icon: fa-crosshairs\n" ) + + MAP_METHOD_AND_WRAP( + "EnterStateFiring", + EnterStateFiring, + "Go into state fire: play fire anim and face cannons towards enemy.\n:jessica-placement: TOOLBAR\n:jessica-icon: fa-fire-alt\n" ) + + MAP_METHOD_AND_WRAP( + "EnterStateReloading", + EnterStateReloading, + "Go into state reloading: play reload anim and then idle. \n:jessica-placement: TOOLBAR\n:jessica-icon: fa-sync\n" ) + + MAP_METHOD_AND_WRAP( + "ForceStateDeactive", + ForceStateDeactive, + "Force into state deactive: no anim, no transition, just flip." ) + + MAP_METHOD_AND_WRAP( + "ForceStateTargeting", + ForceStateTargeting, + "Force into state targeting: no anim, no transition, just flip." ) + + MAP_METHOD_AND_WRAP( + "GetFiringBoneWorldTransform", + GetFiringBoneWorldTransform, + "Returns the world transform matrix of the specfified firing bone in the currently firing turret." + "\n:param idx: index of the firing bone in the current model." + "\n:returns: The world transform matrix." ) + EXPOSURE_CHAINTO( EveChildMesh ) } From 2462b9abc8b9db8bb65198183b6c00a861c48d44 Mon Sep 17 00:00:00 2001 From: breki Date: Mon, 27 Jul 2026 14:38:07 +0000 Subject: [PATCH 04/14] add validation guards --- .../SpaceObject/Children/EveChildTurret.cpp | 115 +++++++++--------- .../Eve/SpaceObject/Children/EveChildTurret.h | 2 + 2 files changed, 58 insertions(+), 59 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp index 22fcde315..ec00ede56 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp @@ -8,7 +8,7 @@ #include "TriObserverLocal.h" // names of system bones like they are in the granny file -static std::string s_systemBoneSkeletonNames[] = { +constexpr const char* s_systemBoneSkeletonNames[] = { "invalid", // SYSBONE_INVALID "Sys_Rotation_Arm", // SYSBONE_ROTATION "Sys_Rotation_Arm01", // SYSBONE_ROTATION1 @@ -27,11 +27,11 @@ static std::string s_systemBoneSkeletonNames[] = { }; // invalids -const unsigned int INVALID_BONE_INDEX = 0xffffffff; -const unsigned int INVALID_TURRET_INDEX = 0xffffffff; +constexpr unsigned int INVALID_BONE_INDEX = 0xffffffff; +constexpr unsigned int INVALID_TURRET_INDEX = 0xffffffff; // some very static timings, no need to confuse artists by exposing them -const float TRACKING_FADE_TIME = 1.f; +constexpr float TRACKING_FADE_TIME = 1.f; EveChildTurret::EveChildTurret( IRoot* lockobj ) : EveChildMesh( lockobj ) @@ -309,15 +309,14 @@ void EveChildTurret::ReleaseCachedData( BlueAsyncRes* resource ) void EveChildTurret::RebuildCachedData( BlueAsyncRes* resource ) { - const auto geometryResource = m_mesh->GetGeometryResource(); + const auto geometryResource = GetGeometryRes(); if( resource == geometryResource ) { // finished loading the turret geometry resource, so grab vertex decl and bounding sphere if( geometryResource->GetMeshCount() ) { - if( const TriGeometryResMeshData* meshData = geometryResource->GetMeshData( 0 ) ) + if( geometryResource->GetMeshData( 0 ) ) { - // get a bounding box for visibility detection, if this is not already set in the redfile // TODO: might not be needed if( m_worldBoundingSphere.radius == 0.f ) @@ -337,7 +336,7 @@ void EveChildTurret::RebuildCachedData( BlueAsyncRes* resource ) for( int i = 0; i < SYSBONE_MAX; ++i ) { // in case we don't find system bone, ::FindJoint() returns 0xffffffff - m_systemBoneID[i] = skeletonData->FindJoint( s_systemBoneSkeletonNames[i].c_str() ); + m_systemBoneID[i] = skeletonData->FindJoint( s_systemBoneSkeletonNames[i] ); } InitializeFiringEffect(); @@ -532,7 +531,7 @@ bool EveChildTurret::SetupFiringState() if( m_state == STATE_DEACTIVE ) { // this state change is forbidden! - CCP_LOGERR( "EveTurretSet %s wants to fire but is in deactive state.", m_name.c_str() ); + CCP_LOGERR( "EveChildTurret %s wants to fire but is in deactive state.", m_name.c_str() ); return false; } int closestLocator = -1; @@ -558,7 +557,7 @@ bool EveChildTurret::SetupFiringState() // timing: apply a randomized fire delay // TODO: remove? // CalcRandomDelay(); - float m_randomFiringDelay = 0.f; // TODO: temp value + float randomFiringDelay = 0.f; // TODO: temp value // timing: is the length of the firing effect known? float effectTotalTime = m_firingEffect ? m_firingEffect->GetFiringDuration() : 0.f; @@ -572,18 +571,18 @@ bool EveChildTurret::SetupFiringState() case STATE_IDLE: case STATE_RELOADING: // and delay the effect until we are facing target - m_randomFiringDelay += m_maxTrackingTime; + randomFiringDelay += m_maxTrackingTime; // fadein tracking, play fire anim (only one the firing turret!) and then the active anim m_delayToFadeInTracking = 0.0001f; - PlayAnimation( GetFireAnimationName(), "Active", m_randomFiringDelay ); + PlayAnimation( GetFireAnimationName(), "Active", randomFiringDelay ); // assign locator and turret - m_target->StartFireAtLocator( closestLocator, m_randomFiringDelay + effectPeakTime, effectTotalTime - effectPeakTime, &source ); + m_target->StartFireAtLocator( closestLocator, randomFiringDelay + effectPeakTime, effectTotalTime - effectPeakTime, &source ); break; case STATE_FIRING: case STATE_TARGETING: - PlayAnimation( GetFireAnimationName(), "Active", m_randomFiringDelay ); - m_target->StartFireAtLocator( closestLocator, m_randomFiringDelay + effectPeakTime, effectTotalTime - effectPeakTime, &source ); + PlayAnimation( GetFireAnimationName(), "Active", randomFiringDelay ); + m_target->StartFireAtLocator( closestLocator, randomFiringDelay + effectPeakTime, effectTotalTime - effectPeakTime, &source ); break; default: @@ -706,7 +705,7 @@ void EveChildTurret::InitializeFiringEffect() } m_firingEffect->RegisterWithQuadRenderer( *Tr2QuadRenderer::Instance() ); - auto geometryResource = m_mesh->GetGeometryResource(); + auto geometryResource = GetGeometryRes(); if( geometryResource && geometryResource->GetSkeletonCount() ) { if( TriGeometryResSkeletonData* skeletonData = geometryResource->GetSkeletonData( 0 ) ) @@ -735,7 +734,7 @@ void EveChildTurret::InitializeFiringEffect() void EveChildTurret::InitializeAnimation() { EveChildMesh::InitializeAnimation(); - if( const auto geometryResource = m_mesh->GetGeometryResource() ) + if( const auto geometryResource = GetGeometryRes() ) { // get a model, a meshbinding and animation stuff from the resource const cmf::Data* cmfData = geometryResource->GetCMFData(); @@ -912,16 +911,13 @@ Matrix EveChildTurret::GetTurretBoneTransform( uint32_t boneID ) const { Matrix matrix = m_worldTransform; - if( boneID == INVALID_BONE_INDEX ) - { - // TODO: should support lowLodTransform? prob yes - // return lowLodTransform * matrix; - return matrix; - } + + // TODO: should support lowLodTransform? prob yes + // return lowLodTransform * matrix; if( m_animationUpdater ) { const auto& worldTransforms = m_animationUpdater->GetWorldTransforms(); - if( !worldTransforms.empty() ) + if( boneID < worldTransforms.size() ) { return worldTransforms[boneID] * matrix; } @@ -930,16 +926,21 @@ Matrix EveChildTurret::GetTurretBoneTransform( uint32_t boneID ) const return matrix; } +TriGeometryRes* EveChildTurret::GetGeometryRes() const +{ + return m_mesh ? m_mesh->GetGeometryResource() : nullptr; +} + float EveChildTurret::PlayAnimation( const std::string& animName, const std::string& animNameIdle, float delay ) { - if( !m_animationUpdater ) + auto geometryRes = GetGeometryRes(); + if( !m_animationUpdater || !geometryRes ) { return 0.f; } float animLength = 0.f; - auto cmfData = m_mesh->GetGeometryResource()->GetCMFData(); - if( cmfData ) + if( auto cmfData = geometryRes->GetCMFData() ) { // there can be more animations in one res, so find right one size_t animIx = cmfData->animations.size(); @@ -971,38 +972,35 @@ float EveChildTurret::PlayAnimation( const std::string& animName, const std::str // stop all animation StopAnimation( delay ); - if( m_animationUpdater ) + // granny, play first anim once, if provided & found + if( animIx != cmfData->animations.size() ) { - // granny, play first anim once, if provided & found - if( animIx != cmfData->animations.size() ) - { - auto& animation = cmfData->animations[animIx]; - animLength = animation.duration; - - // ( const char* animName, bool replace, int loopCount, float delay, float speed, bool clearWhenDone ) - std::string animationName = cmf::ToStdString( animation.name ); - bool replace = false; // TODO: correct? - int loopCount = 1; - float speed = 1.f; - m_animationUpdater->PlayAnimation( animationName.c_str(), replace, loopCount, delay, speed ); - // TODO: set start/stop time seems different from ^ - // player->SetStartTime( delay + Tr2Renderer::GetAnimationTime() ); - // player->SetStopTime( animLength + delay + Tr2Renderer::GetAnimationTime() ); - } + auto& animation = cmfData->animations[animIx]; + animLength = animation.duration; + + // ( const char* animName, bool replace, int loopCount, float delay, float speed, bool clearWhenDone ) + std::string animationName = cmf::ToStdString( animation.name ); + bool replace = false; // TODO: correct? + int loopCount = 1; + float speed = 1.f; + m_animationUpdater->PlayAnimation( animationName.c_str(), replace, loopCount, delay, speed ); + // TODO: set start/stop time seems different from ^ + // player->SetStartTime( delay + Tr2Renderer::GetAnimationTime() ); + // player->SetStopTime( animLength + delay + Tr2Renderer::GetAnimationTime() ); + } - // then play idle anim on loop (after delay), if provided & found - if( idleIx != cmfData->animations.size() ) - { - auto& animation = cmfData->animations[idleIx]; - std::string animationName = cmf::ToStdString( animation.name ); - bool replace = false; // TODO: correct? - int loopCount = 0; - float speed = 1.f; - - m_animationUpdater->PlayAnimation( animationName.c_str(), replace, loopCount, delay, speed ); - // TODO: set start time seems different from ^ - // player->SetStartTime( animLength + delay + Tr2Renderer::GetAnimationTime() ); - } + // then play idle anim on loop (after delay), if provided & found + if( idleIx != cmfData->animations.size() ) + { + auto& animation = cmfData->animations[idleIx]; + std::string animationName = cmf::ToStdString( animation.name ); + bool replace = false; // TODO: correct? + int loopCount = 0; + float speed = 1.f; + + m_animationUpdater->PlayAnimation( animationName.c_str(), replace, loopCount, delay, speed ); + // TODO: set start time seems different from ^ + // player->SetStartTime( animLength + delay + Tr2Renderer::GetAnimationTime() ); } } @@ -1012,9 +1010,8 @@ float EveChildTurret::PlayAnimation( const std::string& animName, const std::str void EveChildTurret::StopAnimation( float delay ) { // TODO: prob not needed and can just use animation class function instead of this probably useless middleman - auto geometryResource = m_mesh->GetGeometryResource(); // if we don't have a geometry, animation is useless and probably unwanted - if( !geometryResource ) + if( !GetGeometryRes() ) { return; } diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.h b/trinity/Eve/SpaceObject/Children/EveChildTurret.h index cc85f8ec2..e2eb3ecda 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.h +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.h @@ -103,6 +103,8 @@ BLUE_CLASS( EveChildTurret ) : Matrix GetTurretBoneTransform( uint32_t boneID ) const; + TriGeometryRes* GetGeometryRes() const; + // animation float PlayAnimation( const std::string& animName, const std::string& animNameIdle, float delay = 0.f ); void StopAnimation( float delay = 0.f ); From f82b14d1de3ec332c52282ef11be047e371bc918 Mon Sep 17 00:00:00 2001 From: breki Date: Mon, 27 Jul 2026 15:49:48 +0000 Subject: [PATCH 05/14] fix async loading --- .../SpaceObject/Children/EveChildTurret.cpp | 105 +++++++++--------- .../Eve/SpaceObject/Children/EveChildTurret.h | 19 ++-- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp index ec00ede56..0faab14d1 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp @@ -53,6 +53,7 @@ EveChildTurret::~EveChildTurret() m_firingEffect->CleanUp(); } + m_cachedGeometryRes = nullptr; m_skeleton = nullptr; m_skeletonBoneIndices.clear(); m_boneBounds.clear(); @@ -160,6 +161,8 @@ void EveChildTurret::UpdateSyncronous( const EveUpdateContext& updateContext, co } */ + UpdateCachedGeometryData(); + if( m_sequencer ) { m_sequencer->RemoveFinishedAnimations( Tr2Renderer::GetAnimationTime() ); @@ -298,68 +301,66 @@ void EveChildTurret::UpdateAsyncronous( const EveUpdateContext& updateContext, c } } } - -void EveChildTurret::ReleaseCachedData( BlueAsyncRes* resource ) +void EveChildTurret::UpdateCachedGeometryData() { - // TODO: for now duplicates destructor - m_skeleton = nullptr; - m_skeletonBoneIndices.clear(); - m_boneBounds.clear(); + auto* geometryRes = GetGeometryRes(); + if( geometryRes == m_cachedGeometryRes ) + { + return; + } + ReleaseCachedGeometryData(); + if( geometryRes && geometryRes->IsGood() ) + { + BuildCachedGeometryData( *geometryRes ); + m_cachedGeometryRes = geometryRes; + } } - -void EveChildTurret::RebuildCachedData( BlueAsyncRes* resource ) +void EveChildTurret::BuildCachedGeometryData( TriGeometryRes& geometryRes ) { - const auto geometryResource = GetGeometryRes(); - if( resource == geometryResource ) + // finished loading the turret geometry resource, so grab vertex decl and bounding sphere + if( geometryRes.GetMeshCount() ) { - // finished loading the turret geometry resource, so grab vertex decl and bounding sphere - if( geometryResource->GetMeshCount() ) + if( geometryRes.GetMeshData( 0 ) ) { - if( geometryResource->GetMeshData( 0 ) ) + // get a bounding box for visibility detection, if this is not already set in the redfile + // TODO: might not be needed + if( m_worldBoundingSphere.radius == 0.f ) { - // get a bounding box for visibility detection, if this is not already set in the redfile - // TODO: might not be needed - if( m_worldBoundingSphere.radius == 0.f ) - { - geometryResource->RecalculateBoundingSphere(); - Vector4 boundingSphere; - geometryResource->GetBoundingSphere( 0, boundingSphere ); - m_worldBoundingSphere = CcpMath::Sphere( boundingSphere ); - } + geometryRes.RecalculateBoundingSphere(); + Vector4 boundingSphere; + geometryRes.GetBoundingSphere( 0, boundingSphere ); + m_worldBoundingSphere = CcpMath::Sphere( boundingSphere ); } } + } - if( geometryResource->GetSkeletonCount() ) + if( geometryRes.GetSkeletonCount() ) + { + if( TriGeometryResSkeletonData* skeletonData = geometryRes.GetSkeletonData( 0 ) ) { - if( TriGeometryResSkeletonData* skeletonData = geometryResource->GetSkeletonData( 0 ) ) + for( int i = 0; i < SYSBONE_MAX; ++i ) { - for( int i = 0; i < SYSBONE_MAX; ++i ) - { - // in case we don't find system bone, ::FindJoint() returns 0xffffffff - m_systemBoneID[i] = skeletonData->FindJoint( s_systemBoneSkeletonNames[i] ); - } - - InitializeFiringEffect(); + // in case we don't find system bone, ::FindJoint() returns 0xffffffff + m_systemBoneID[i] = skeletonData->FindJoint( s_systemBoneSkeletonNames[i] ); } - } - - InitializeAnimation(); - if( !m_animationQueue.empty() ) - { - std::vector pending; - pending.swap( m_animationQueue ); - for( const auto& [animName, animNameIdle] : pending ) - { - PlayAnimation( animName, animNameIdle ); - } - } - else - { - // force an anim based on a state - ForceIdleAnimation(); + InitializeFiringEffect(); } } + + InitializeAnimation(); + + // TODO: forceXAnimation based on m_state? + ForceIdleAnimation(); +} + +void EveChildTurret::ReleaseCachedGeometryData() +{ + // TODO: for now duplicates destructor + m_cachedGeometryRes = nullptr; + m_skeleton = nullptr; + m_skeletonBoneIndices.clear(); + m_boneBounds.clear(); } void EveChildTurret::EnterStateDeactive() @@ -931,6 +932,13 @@ TriGeometryRes* EveChildTurret::GetGeometryRes() const return m_mesh ? m_mesh->GetGeometryResource() : nullptr; } +// TODO: heavily refactor once animation ownership is decided (m_animationUpdater vs m_sequencer). +// Known defects for the rewrite: +// - a missing anim name aborts the whole request instead of playing what was found +// - the idle anim starts at `delay` instead of after the one-shot finishes +// (original sequenced via player SetStartTime/SetStopTime, no equivalent wired here) +// - StopAnimation is a stub, so the "stop all animation" call below does nothing +// - duplicated find_if lookups float EveChildTurret::PlayAnimation( const std::string& animName, const std::string& animNameIdle, float delay ) { auto geometryRes = GetGeometryRes(); @@ -1016,9 +1024,6 @@ void EveChildTurret::StopAnimation( float delay ) return; } - // empty queue, so no more buffered requests - m_animationQueue.clear(); - // stop if( m_animationUpdater ) { diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.h b/trinity/Eve/SpaceObject/Children/EveChildTurret.h index e2eb3ecda..7b71a120b 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.h +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.h @@ -11,7 +11,7 @@ BLUE_DECLARE( EveTurretFiringFX ); BLUE_DECLARE( EveTurretTarget ); BLUE_CLASS( EveChildTurret ) : - public EveChildMesh, public IBlueAsyncResNotifyTarget + public EveChildMesh { public: EXPOSE_TO_BLUE(); @@ -31,9 +31,9 @@ BLUE_CLASS( EveChildTurret ) : void UpdateSyncronous( const EveUpdateContext& updateContext, const EveChildUpdateParams& params ) override; void UpdateAsyncronous( const EveUpdateContext& updateContext, const EveChildUpdateParams& params ) override; - // IBlueAsyncResNotifyTarget - void ReleaseCachedData( BlueAsyncRes * resource ) override; - void RebuildCachedData( BlueAsyncRes * resource ) override; + void UpdateCachedGeometryData(); + void BuildCachedGeometryData( TriGeometryRes & geometryRes ); + void ReleaseCachedGeometryData(); // action void EnterStateDeactive(); @@ -111,7 +111,7 @@ BLUE_CLASS( EveChildTurret ) : std::string GetFireAnimationName() const; EveTurretFiringFX* GetFiringEffect(); - void SetFiringEffect( EveTurretFiringFX* firingEffect ); + void SetFiringEffect( EveTurretFiringFX * firingEffect ); // TODO: rename GrannyBoneBindingBounds? std::vector m_boneBounds; @@ -136,13 +136,6 @@ BLUE_CLASS( EveChildTurret ) : float m_maxTrackingTime = 1.f; // animation - // TODO: needed? - struct AnimationRequest - { - std::string animName; - std::string animNameIdle; - }; - std::vector m_animationQueue; const cmf::Skeleton* m_skeleton = nullptr; std::vector m_skeletonBoneIndices; std::unique_ptr m_sequencer; @@ -181,6 +174,8 @@ BLUE_CLASS( EveChildTurret ) : TriObserverLocalPtr m_turretMovementObserver; std::wstring m_idleToTargetingMovementAudioEvent; std::wstring m_targetingToIdleMovementAudioEvent; + + TriGeometryResPtr m_cachedGeometryRes; }; TYPEDEF_BLUECLASS( EveChildTurret ); From e58b0bf3cedd4f828df9843a24c49b12210a81b3 Mon Sep 17 00:00:00 2001 From: breki Date: Mon, 27 Jul 2026 16:09:59 +0000 Subject: [PATCH 06/14] cleanup more todos --- .../SpaceObject/Children/EveChildTurret.cpp | 35 ++++--------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp index 0faab14d1..1822365c7 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp @@ -53,10 +53,7 @@ EveChildTurret::~EveChildTurret() m_firingEffect->CleanUp(); } - m_cachedGeometryRes = nullptr; - m_skeleton = nullptr; - m_skeletonBoneIndices.clear(); - m_boneBounds.clear(); + ReleaseCachedGeometryData(); ReleaseResources( TRISTORAGE_ALL ); } @@ -264,7 +261,6 @@ void EveChildTurret::UpdateAsyncronous( const EveUpdateContext& updateContext, c // setup and update attached firing effect if( m_firingEffect ) { - // TODO: is this a valid replacement of if( m_activeTurret != INVALID_TURRET_INDEX ) if( m_mesh ) { // update all muzzle points in the firing effect @@ -356,7 +352,6 @@ void EveChildTurret::BuildCachedGeometryData( TriGeometryRes& geometryRes ) void EveChildTurret::ReleaseCachedGeometryData() { - // TODO: for now duplicates destructor m_cachedGeometryRes = nullptr; m_skeleton = nullptr; m_skeletonBoneIndices.clear(); @@ -503,19 +498,9 @@ void EveChildTurret::EnterStateFiring() m_firingEffect->StopFiring(); } - // We're starting a firing sequence, we need to set up our firing effect time-delays if( m_firingEffect ) { - // TODO: random firing delay yay or nay - //if( m_maxCyclingFirePos > 1 ) - //{ - // m_firingEffect->PrepareFiring( m_randomFiringDelay, m_currentCyclingFiresPos, m_cyclingFireGroupCount ); - //} - //else - //{ - float randomFiringDelay = 0.f; - m_firingEffect->PrepareFiring( randomFiringDelay ); - //} + m_firingEffect->PrepareFiring( 0.f ); if( m_target != nullptr ) { @@ -555,11 +540,6 @@ bool EveChildTurret::SetupFiringState() } */ - // timing: apply a randomized fire delay - // TODO: remove? - // CalcRandomDelay(); - float randomFiringDelay = 0.f; // TODO: temp value - // timing: is the length of the firing effect known? float effectTotalTime = m_firingEffect ? m_firingEffect->GetFiringDuration() : 0.f; float effectPeakTime = m_firingEffect ? m_firingEffect->GetFiringPeakTime() : 0.f; @@ -571,19 +551,17 @@ bool EveChildTurret::SetupFiringState() { case STATE_IDLE: case STATE_RELOADING: - // and delay the effect until we are facing target - randomFiringDelay += m_maxTrackingTime; // fadein tracking, play fire anim (only one the firing turret!) and then the active anim m_delayToFadeInTracking = 0.0001f; - PlayAnimation( GetFireAnimationName(), "Active", randomFiringDelay ); + PlayAnimation( GetFireAnimationName(), "Active", m_maxTrackingTime ); // assign locator and turret - m_target->StartFireAtLocator( closestLocator, randomFiringDelay + effectPeakTime, effectTotalTime - effectPeakTime, &source ); + m_target->StartFireAtLocator( closestLocator, m_maxTrackingTime + effectPeakTime, effectTotalTime - effectPeakTime, &source ); break; case STATE_FIRING: case STATE_TARGETING: - PlayAnimation( GetFireAnimationName(), "Active", randomFiringDelay ); - m_target->StartFireAtLocator( closestLocator, randomFiringDelay + effectPeakTime, effectTotalTime - effectPeakTime, &source ); + PlayAnimation( GetFireAnimationName(), "Active", m_maxTrackingTime ); + m_target->StartFireAtLocator( closestLocator, m_maxTrackingTime + effectPeakTime, effectTotalTime - effectPeakTime, &source ); break; default: @@ -662,7 +640,6 @@ void EveChildTurret::ForceIdleAnimation() break; } - // set it to all turrets in this set if( idleAnimName.length() > 0 ) { PlayAnimation( "", idleAnimName, 0.f ); From 34784fee9dec7cbd64c88bc02661199d3e07bf14 Mon Sep 17 00:00:00 2001 From: breki Date: Mon, 27 Jul 2026 16:38:29 +0000 Subject: [PATCH 07/14] impact behviour support --- trinity/Eve/SpaceObject/Children/EveChildTurret.cpp | 12 ++++-------- .../Eve/SpaceObject/Children/EveChildTurret_Blue.cpp | 2 +- trinity/Eve/Turret/EveTurretTarget.cpp | 9 +++++++++ trinity/Eve/Turret/EveTurretTarget.h | 1 + 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp index 1822365c7..a4b37f7a8 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp @@ -59,21 +59,17 @@ EveChildTurret::~EveChildTurret() } bool EveChildTurret::Initialize() { - // pass down some user-defined data into sub-modules we don't save out - // TODO: keep or discard - // m_target->SetBehaviour( m_laserMissBehaviour, m_projectileMissBehaviour, m_impactSize, m_impactBehaviour ); + // pass down some user-defined data into sub-modules we don't save out. + m_target->SetImpactBehaviour( m_impactSize, m_impactBehaviour ); return EveChildMesh::Initialize(); } bool EveChildTurret::OnModified( Be::Var* value ) { - /* - if( IsMatch( value, m_laserMissBehaviour ) || IsMatch( value, m_projectileMissBehaviour ) || IsMatch( value, m_impactSize ) || IsMatch( value, m_impactBehaviour ) ) + if( IsMatch( value, m_impactSize ) || IsMatch( value, m_impactBehaviour ) ) { - // TODO: keep or discard - m_target->SetBehaviour( m_laserMissBehaviour, m_projectileMissBehaviour, m_impactSize, m_impactBehaviour ); + m_target->SetImpactBehaviour( m_impactSize, m_impactBehaviour ); } - */ return EveChildMesh::OnModified( value ); } void EveChildTurret::RegisterComponents() diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp index 3b3bb0251..9d498f21f 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp @@ -35,7 +35,7 @@ const Be::ClassInfo* EveChildTurret::ExposeToBlue() MAP_PROPERTY( "firingEffect", GetFiringEffect, SetFiringEffect, "The module for the firing effect of this turret" ) MAP_ATTRIBUTE( "firingEffectResPath", m_firingEffectResPath, "A res path to the redfile containing the primary firing effect", Be::READWRITE | Be::PERSIST ) - MAP_ATTRIBUTE( "impactSize", m_impactSize, "Size of impacts. No impact if size is 0 or less", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "impactSize", m_impactSize, "Size of impacts. No impact if size is 0 or less", Be::READWRITE | Be::NOTIFY | Be::PERSIST ) MAP_ATTRIBUTE_WITH_CHOOSER( "impactBehaviour", m_impactBehaviour, "What do we want to hit? ", Be::READWRITE | Be::NOTIFY | Be::PERSIST | Be::ENUM, ImpactBehaviourChooser ) MAP_ATTRIBUTE( "turretMovementObserver", m_turretMovementObserver, "The observer for turret movement sounds. Note: the positioning of this observer is automatic.", Be::READWRITE | Be::PERSIST ) diff --git a/trinity/Eve/Turret/EveTurretTarget.cpp b/trinity/Eve/Turret/EveTurretTarget.cpp index a4126ae26..d3298ced1 100644 --- a/trinity/Eve/Turret/EveTurretTarget.cpp +++ b/trinity/Eve/Turret/EveTurretTarget.cpp @@ -332,6 +332,15 @@ void EveTurretTarget::SetBehaviour( bool laserMiss, bool projectileMiss, float i { m_laserMissBehaviour = laserMiss; m_projectileMissBehaviour = projectileMiss; + SetImpactBehaviour( impactSize, impactBehaviour ); +} + +// -------------------------------------------------------------------------------- +// Description: +// Set the impact configuration, leaving miss behaviour disabled/untouched +// -------------------------------------------------------------------------------- +void EveTurretTarget::SetImpactBehaviour( float impactSize, ImpactBehaviour::Type impactBehaviour ) +{ m_impactSize = impactSize; m_impactBehaviour = impactBehaviour; } diff --git a/trinity/Eve/Turret/EveTurretTarget.h b/trinity/Eve/Turret/EveTurretTarget.h index d4de78f8c..5a1769c0a 100644 --- a/trinity/Eve/Turret/EveTurretTarget.h +++ b/trinity/Eve/Turret/EveTurretTarget.h @@ -48,6 +48,7 @@ BLUE_CLASS( EveTurretTarget ) : // hit/miss void SetBehaviour( bool laserMiss, bool projectileMiss, float impactSize, ImpactBehaviour::Type impactBehaviour ); + void SetImpactBehaviour( float impactSize, ImpactBehaviour::Type impactBehaviour ); bool GetShotMissed() const; void SetShotMissed( bool missed ); double GetLastShotTime() const; From 0cbdbfd1e98a469cd5d5232a7537ff8781865891 Mon Sep 17 00:00:00 2001 From: breki Date: Tue, 28 Jul 2026 11:45:29 +0000 Subject: [PATCH 08/14] resolve more todos --- .../SpaceObject/Children/EveChildTurret.cpp | 107 +++++------------- .../Eve/SpaceObject/Children/EveChildTurret.h | 6 + .../Children/EveChildTurret_Blue.cpp | 8 ++ 3 files changed, 44 insertions(+), 77 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp index a4b37f7a8..965011eea 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp @@ -7,7 +7,7 @@ #include "TriMath.h" #include "TriObserverLocal.h" -// names of system bones like they are in the granny file +// names of system bones like they are in the cmf file constexpr const char* s_systemBoneSkeletonNames[] = { "invalid", // SYSBONE_INVALID "Sys_Rotation_Arm", // SYSBONE_ROTATION @@ -59,7 +59,7 @@ EveChildTurret::~EveChildTurret() } bool EveChildTurret::Initialize() { - // pass down some user-defined data into sub-modules we don't save out. + // pass down some user-defined data into submodules we don't save out. m_target->SetImpactBehaviour( m_impactSize, m_impactBehaviour ); return EveChildMesh::Initialize(); @@ -129,30 +129,10 @@ void EveChildTurret::UpdateSyncronous( const EveUpdateContext& updateContext, co { float deltaT = updateContext.GetDeltaT(); - /* - // TODO: LODs needed? - // LODing - if( UpdateLOD( updateContext ) ) + if( m_firingEffect ) { - // LOD change, so just call ::InitializeGeometryResource(), takes care of everything - InitializeGeometryResource(); - - // LOD change: toggle source dest effect of the attached firingFX - if( m_firingEffect ) - { - switch( m_lodLevel ) - { - case LOD_DISABLED: - case LOD_HIGHEST: - m_firingEffect->SetDisplaySourceObject( true ); - break; - default: - m_firingEffect->SetDisplaySourceObject( false ); - break; - } - } + m_firingEffect->SetDisplaySourceObject( IsVisible( updateContext ) ); } - */ UpdateCachedGeometryData(); @@ -192,8 +172,7 @@ void EveChildTurret::UpdateSyncronous( const EveUpdateContext& updateContext, co } // update the target locator position - // TODO: probably wrong - Vector3 position = m_parentData.transform.GetTranslation(); + Vector3 position = m_worldTransform.GetTranslation(); if( m_firingEffect ) { m_firingEffect->GetStartPosition( position ); @@ -203,16 +182,13 @@ void EveChildTurret::UpdateSyncronous( const EveUpdateContext& updateContext, co if( m_mesh && m_turretMovementObserver != nullptr ) { - // TODO: new turret movementObserver prob needed - // m_turretMovementObserver->Update( m_singleTurrets[0].worldMatrix ); + m_turretMovementObserver->Update( m_worldTransform ); } EveChildMesh::UpdateSyncronous( updateContext, params ); } void EveChildTurret::UpdateAsyncronous( const EveUpdateContext& updateContext, const EveChildUpdateParams& params ) { - - // TODO: prob some freakyness in here float deltaT = updateContext.GetDeltaT(); // handle fading of turret tracking if( m_trackingInfluenceDelta != 0.f ) @@ -250,7 +226,6 @@ void EveChildTurret::UpdateAsyncronous( const EveUpdateContext& updateContext, c } } - // TODO: does this have to happen after the timing stuff over there ^ // Should handle all the mesh data and transforms EveChildMesh::UpdateAsyncronous( updateContext, params ); @@ -277,14 +252,13 @@ void EveChildTurret::UpdateAsyncronous( const EveUpdateContext& updateContext, c { // if we haven't initialised muzzle positions, do it now // this can happen, and if we don't do this all effects originate from - // the player ship until turret geometry is loaded and muzzle positions + // the turret root until turret geometry is loaded and muzzle positions // properly set if( !m_firingEffectMuzzlePosSet ) { for( unsigned int i = 0; i < m_firingEffect->GetPerMuzzleEffectCount(); ++i ) { - // use something relatively sensible, even absent geometry - m_firingEffect->SetMuzzleTransform( i, &m_parentData.transform ); + m_firingEffect->SetMuzzleTransform( i, &m_worldTransform ); } m_firingEffectMuzzlePosSet = true; @@ -309,23 +283,6 @@ void EveChildTurret::UpdateCachedGeometryData() } void EveChildTurret::BuildCachedGeometryData( TriGeometryRes& geometryRes ) { - // finished loading the turret geometry resource, so grab vertex decl and bounding sphere - if( geometryRes.GetMeshCount() ) - { - if( geometryRes.GetMeshData( 0 ) ) - { - // get a bounding box for visibility detection, if this is not already set in the redfile - // TODO: might not be needed - if( m_worldBoundingSphere.radius == 0.f ) - { - geometryRes.RecalculateBoundingSphere(); - Vector4 boundingSphere; - geometryRes.GetBoundingSphere( 0, boundingSphere ); - m_worldBoundingSphere = CcpMath::Sphere( boundingSphere ); - } - } - } - if( geometryRes.GetSkeletonCount() ) { if( TriGeometryResSkeletonData* skeletonData = geometryRes.GetSkeletonData( 0 ) ) @@ -342,7 +299,6 @@ void EveChildTurret::BuildCachedGeometryData( TriGeometryRes& geometryRes ) InitializeAnimation(); - // TODO: forceXAnimation based on m_state? ForceIdleAnimation(); } @@ -352,6 +308,7 @@ void EveChildTurret::ReleaseCachedGeometryData() m_skeleton = nullptr; m_skeletonBoneIndices.clear(); m_boneBounds.clear(); + m_firingEffectMuzzlePosSet = false; } void EveChildTurret::EnterStateDeactive() @@ -391,11 +348,10 @@ void EveChildTurret::EnterStateDeactive() void EveChildTurret::EnterStateIdle() { - // TODO: might want to remove this state - // if( !m_isOnline ) - // { - // return; - // } + if( !m_isOnline ) + { + return; + } switch( m_state ) { @@ -435,11 +391,10 @@ void EveChildTurret::EnterStateIdle() void EveChildTurret::EnterStateTargeting() { float animLength = 0.f; - // TODO: might want to remove this state - // if( !m_isOnline ) - // { - // return; - // } + if( !m_isOnline ) + { + return; + } // what state are we in? switch( m_state ) @@ -496,7 +451,14 @@ void EveChildTurret::EnterStateFiring() if( m_firingEffect ) { - m_firingEffect->PrepareFiring( 0.f ); + if( m_maxCyclingFirePos > 1 ) + { + m_firingEffect->PrepareFiring( 0.f, m_currentCyclingFiresPos, m_cyclingFireGroupCount ); + } + else + { + m_firingEffect->PrepareFiring( 0.f ); + } if( m_target != nullptr ) { @@ -523,9 +485,7 @@ bool EveChildTurret::SetupFiringState() closestLocator = m_target->FindClosestLocator( &source, &position ); } - // TODO: remove or keep? // if this turret is set to cycle through the muzzles for firing, do it here - /* if( m_maxCyclingFirePos > 1 ) { m_currentCyclingFiresPos += m_cyclingFireGroupCount; @@ -534,7 +494,6 @@ bool EveChildTurret::SetupFiringState() m_currentCyclingFiresPos = 0; } } - */ // timing: is the length of the firing effect known? float effectTotalTime = m_firingEffect ? m_firingEffect->GetFiringDuration() : 0.f; @@ -627,6 +586,7 @@ void EveChildTurret::ForceIdleAnimation() idleAnimName = "Inactive"; break; case STATE_IDLE: + case STATE_RELOADING: case STATE_TARGETING: case STATE_FIRING: idleAnimName = "Active"; @@ -657,7 +617,7 @@ Matrix EveChildTurret::GetFiringBoneWorldTransform( unsigned int muzzle ) const { if( !m_mesh ) { - return m_parentData.transform; + return m_worldTransform; } Matrix matrix = m_worldTransform; @@ -673,6 +633,7 @@ Matrix EveChildTurret::GetFiringBoneWorldTransform( unsigned int muzzle ) const void EveChildTurret::InitializeFiringEffect() { + m_firingEffectMuzzlePosSet = false; if( !m_firingEffect ) { return; @@ -755,8 +716,6 @@ void EveChildTurret::ModifySystemBoneTransform( SystemBones bone, const Vector3* Quaternion quat = RotationQuaternion( alpha, 0.f, 0.f ); // 2nd: apply this quat after the original one quat = rotation * quat; - // TODO: cmf_transform ? - // 3rd: make granny_transform from quat rotation = quat; } break; @@ -769,7 +728,6 @@ void EveChildTurret::ModifySystemBoneTransform( SystemBones bone, const Vector3* Quaternion quat = RotationQuaternion( alpha, 0.f, 0.f ); // 2nd: apply this quat after the original one quat = rotation * quat; - // 3rd: make granny_transform from quat rotation = quat; } break; @@ -840,7 +798,6 @@ void EveChildTurret::CalcTransformForPitchBone( const Vector3* target, float min Quaternion quat = RotationQuaternion( 0.f, -alpha, 0.f ); // 2nd: apply this quat after the original one quat = rotation * quat; - // 2nd: make granny_transform from quat rotation = quat; } @@ -884,19 +841,15 @@ float EveChildTurret::GetBonePitchOffset( unsigned int boneIndex ) const Matrix EveChildTurret::GetTurretBoneTransform( uint32_t boneID ) const { Matrix matrix = m_worldTransform; - - - // TODO: should support lowLodTransform? prob yes - // return lowLodTransform * matrix; if( m_animationUpdater ) { const auto& worldTransforms = m_animationUpdater->GetWorldTransforms(); + // covers Invalid since INVALID_BONE_INDEX is max_float if( boneID < worldTransforms.size() ) { return worldTransforms[boneID] * matrix; } } - // TODO: port rest of function? return matrix; } @@ -953,7 +906,7 @@ float EveChildTurret::PlayAnimation( const std::string& animName, const std::str // stop all animation StopAnimation( delay ); - // granny, play first anim once, if provided & found + // play first anim once, if provided & found if( animIx != cmfData->animations.size() ) { auto& animation = cmfData->animations[animIx]; diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.h b/trinity/Eve/SpaceObject/Children/EveChildTurret.h index 7b71a120b..97295bca0 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.h +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.h @@ -124,6 +124,8 @@ BLUE_CLASS( EveChildTurret ) : // target (object we are tracking) EveTurretTargetPtr m_target; + bool m_isOnline = true; + // impacts float m_impactSize = 0.f; ImpactBehaviour::Type m_impactBehaviour = ImpactBehaviour::DAMAGE_LOCATOR; @@ -141,6 +143,10 @@ BLUE_CLASS( EveChildTurret ) : std::unique_ptr m_sequencer; cmf::SkeletonPose m_pose; + uint32_t m_maxCyclingFirePos = 1; + uint32_t m_cyclingFireGroupCount = 1; + uint32_t m_currentCyclingFiresPos = 0; + // system bones unsigned int m_systemBoneID[SYSBONE_MAX]; // specific system bone values diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp index 9d498f21f..0d36c6501 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp @@ -12,6 +12,9 @@ const Be::ClassInfo* EveChildTurret::ExposeToBlue() { EXPOSURE_BEGIN( EveChildTurret, "" ) MAP_INTERFACE( EveChildTurret ) + + + MAP_ATTRIBUTE( "isOnline", m_isOnline, "Indicate if turret is active", Be::READWRITE ) MAP_ATTRIBUTE( "trackingInfluence", m_trackingInfluence, "How much tracking is allowed?", Be::READ ) MAP_ATTRIBUTE( "maxTrackingTime", m_maxTrackingTime, "How long does tracking take?", Be::READWRITE | Be::PERSIST ) MAP_ATTRIBUTE( "state", m_state, "State of the turret", Be::READ | Be::PERSIST ) @@ -31,6 +34,11 @@ const Be::ClassInfo* EveChildTurret::ExposeToBlue() MAP_ATTRIBUTE( "sysBonePitch03Factor", m_sysBonePitch03Factor, "pitch 03 factor", Be::READWRITE | Be::PERSIST ) MAP_ATTRIBUTE( "sysBonePitch03Offset", m_sysBonePitch03Offset, "pitch 03 offset (in degrees!)", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "maxCyclingFirePos", m_maxCyclingFirePos, "If greater than one we cycle through the given number of muzzles.", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "cyclingFireGroupCount", m_cyclingFireGroupCount, "The number of muzzles in one cycle group, usually only one.", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "currentCyclingFiresPos", m_currentCyclingFiresPos, "Current muzzle id due to cycling muzzles", Be::READ ) + + MAP_ATTRIBUTE( "firingEffect", m_firingEffect, "", Be::HIDDEN ) // Needed to make Graphite able to detect bindings inside of the firing effect. MAP_PROPERTY( "firingEffect", GetFiringEffect, SetFiringEffect, "The module for the firing effect of this turret" ) MAP_ATTRIBUTE( "firingEffectResPath", m_firingEffectResPath, "A res path to the redfile containing the primary firing effect", Be::READWRITE | Be::PERSIST ) From e05dd34ce4be6be4fc03a53b1ac6b392083b4c89 Mon Sep 17 00:00:00 2001 From: breki Date: Tue, 28 Jul 2026 14:52:54 +0000 Subject: [PATCH 09/14] Add posemodifer interface and remove more todos --- trinity/CMakeLists.txt | 1 + .../SpaceObject/Children/EveChildTurret.cpp | 67 +++++++++++-------- .../Eve/SpaceObject/Children/EveChildTurret.h | 20 ++---- trinity/Include/ITr2PoseModifier.h | 19 ++++++ trinity/Tr2GrannyAnimation.cpp | 20 +++++- trinity/Tr2GrannyAnimation.h | 6 ++ 6 files changed, 90 insertions(+), 43 deletions(-) create mode 100644 trinity/Include/ITr2PoseModifier.h diff --git a/trinity/CMakeLists.txt b/trinity/CMakeLists.txt index fe753d463..3aa2a7b29 100644 --- a/trinity/CMakeLists.txt +++ b/trinity/CMakeLists.txt @@ -842,6 +842,7 @@ set(_SOURCES Include/ITr2InstanceData.h Include/ITr2Interior.h Include/ITr2MultiPassScene.h + Include/ITr2PoseModifier.h Include/ITr2Scene.h Include/ITr2Updateable.h Include/ITr2ValueBinding.h diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp index 965011eea..4af33e885 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp @@ -3,6 +3,7 @@ #include "StdAfx.h" #include "EveChildTurret.h" #include "Eve/Turret/EveTurretFiringFX.h" +#include "Tr2GrannyAnimation.h" #include "Tr2MeshBase.h" #include "TriMath.h" #include "TriObserverLocal.h" @@ -48,6 +49,11 @@ EveChildTurret::EveChildTurret( IRoot* lockobj ) : EveChildTurret::~EveChildTurret() { + if( m_hookedUpdater && m_hookedUpdater->GetPoseModifier() == this ) + { + m_hookedUpdater->SetPoseModifier( nullptr ); + } + if( m_firingEffect ) { m_firingEffect->CleanUp(); @@ -136,11 +142,6 @@ void EveChildTurret::UpdateSyncronous( const EveUpdateContext& updateContext, co UpdateCachedGeometryData(); - if( m_sequencer ) - { - m_sequencer->RemoveFinishedAnimations( Tr2Renderer::GetAnimationTime() ); - } - // setup and update attached firing effect if( m_firingEffect ) { @@ -305,9 +306,6 @@ void EveChildTurret::BuildCachedGeometryData( TriGeometryRes& geometryRes ) void EveChildTurret::ReleaseCachedGeometryData() { m_cachedGeometryRes = nullptr; - m_skeleton = nullptr; - m_skeletonBoneIndices.clear(); - m_boneBounds.clear(); m_firingEffectMuzzlePosSet = false; } @@ -668,33 +666,44 @@ void EveChildTurret::InitializeFiringEffect() void EveChildTurret::InitializeAnimation() { + if( !m_animationUpdater ) + { + m_animationUpdater.CreateInstance(); + } EveChildMesh::InitializeAnimation(); - if( const auto geometryResource = GetGeometryRes() ) + + if( m_hookedUpdater != m_animationUpdater ) { - // get a model, a meshbinding and animation stuff from the resource - const cmf::Data* cmfData = geometryResource->GetCMFData(); - if( cmfData && cmfData->skeletons.size() ) + if( m_hookedUpdater && m_hookedUpdater->GetPoseModifier() == this ) { - const auto mesh = std::find_if( cmfData->meshes.begin(), cmfData->meshes.end(), []( const cmf::Mesh& m ) { - return m.skeleton == 0; - } ); - - if( mesh != cmfData->meshes.end() && mesh->boneBindings.size() ) - { - if( m_skeletonBoneIndices.empty() ) - { - m_skeleton = &cmfData->skeletons[0]; + m_hookedUpdater->SetPoseModifier( nullptr ); + } + m_animationUpdater->SetPoseModifier( this ); + m_hookedUpdater = m_animationUpdater; + } +} +void EveChildTurret::ModifyPose( const cmf::Skeleton& skeleton, cmf::SkeletonPose& pose ) +{ + if( m_trackingInfluence == 0.f ) + { + return; + } - if( !m_sequencer ) - { - m_sequencer = std::make_unique( *m_skeleton ); - cmf::RestPose( m_pose, *m_skeleton ); - } + Vector3 targetPosOS = TransformCoord( *m_target->GetTrackingPosition(), Inverse( m_worldTransform ) ); - m_skeletonBoneIndices = Tr2GrannyAnimationUtils::CreateMapping( *m_skeleton, mesh->boneBindings, static_cast( mesh->boneBindings.size() ) ); - } - } + for( unsigned int bone = 0; bone < SYSBONE_MAX; ++bone ) + { + // covers Invalid since INVALID_BONE_INDEX is max + if( m_systemBoneID[bone] < pose.boneTransforms.size() ) + { + cmf::Transform& boneTransform = pose.boneTransforms[m_systemBoneID[bone]]; + ModifySystemBoneTransform( + static_cast( bone ), + &targetPosOS, + nullptr, + boneTransform.position, + boneTransform.rotation ); } } } diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.h b/trinity/Eve/SpaceObject/Children/EveChildTurret.h index 97295bca0..033b9c925 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.h +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.h @@ -6,12 +6,14 @@ #include "EveChildMesh.h" #include "Eve/Turret/EveTurretTarget.h" +#include "Include/ITr2PoseModifier.h" BLUE_DECLARE( EveTurretFiringFX ); BLUE_DECLARE( EveTurretTarget ); BLUE_CLASS( EveChildTurret ) : - public EveChildMesh + public EveChildMesh, + public ITr2PoseModifier { public: EXPOSE_TO_BLUE(); @@ -60,9 +62,10 @@ BLUE_CLASS( EveChildTurret ) : STATE_RELOADING, }; + void ModifyPose( const cmf::Skeleton& skeleton, cmf::SkeletonPose& pose ) override; + protected: // system-controlled bones - // TODO: needed? enum SystemBones { SYSBONE_INVALID = 0, @@ -88,8 +91,6 @@ BLUE_CLASS( EveChildTurret ) : void InitializeAnimation() override; - // TODO: Need update LOD ? - // set transform for tracking void ModifySystemBoneTransform( SystemBones bone, const Vector3* target, const Matrix* localTransform, Vector3& position, Quaternion& rotation ) const; @@ -113,9 +114,6 @@ BLUE_CLASS( EveChildTurret ) : EveTurretFiringFX* GetFiringEffect(); void SetFiringEffect( EveTurretFiringFX * firingEffect ); - // TODO: rename GrannyBoneBindingBounds? - std::vector m_boneBounds; - // Assign the target object void SetTargetObject( IRoot * target ); ITriTargetablePtr GetTargetObject(); @@ -137,11 +135,8 @@ BLUE_CLASS( EveChildTurret ) : float m_delayToFadeInTracking = 0.f; float m_maxTrackingTime = 1.f; - // animation - const cmf::Skeleton* m_skeleton = nullptr; - std::vector m_skeletonBoneIndices; - std::unique_ptr m_sequencer; - cmf::SkeletonPose m_pose; + // animation: updater we last hooked our pose modifier into (to unhook on swap) + Tr2GrannyAnimationPtr m_hookedUpdater; uint32_t m_maxCyclingFirePos = 1; uint32_t m_cyclingFireGroupCount = 1; @@ -170,7 +165,6 @@ BLUE_CLASS( EveChildTurret ) : // firing effect redfile path std::string m_firingEffectResPath; - // TODO: move firing effect into its own class? // firing effect EveTurretFiringFXPtr m_firingEffect; bool m_firingEffectMuzzlePosSet = false; diff --git a/trinity/Include/ITr2PoseModifier.h b/trinity/Include/ITr2PoseModifier.h new file mode 100644 index 000000000..1d23e3284 --- /dev/null +++ b/trinity/Include/ITr2PoseModifier.h @@ -0,0 +1,19 @@ +// Copyright © 2026 Fenris Creations ehf. + +#pragma once + +#ifndef ITR2POSEMODIFIER_H +#define ITR2POSEMODIFIER_H + +#include + +class ITr2PoseModifier +{ +public: + virtual void ModifyPose( const cmf::Skeleton& skeleton, cmf::SkeletonPose& pose ) = 0; + +protected: + ~ITr2PoseModifier() = default; +}; + +#endif //ITR2POSEMODIFIER_H diff --git a/trinity/Tr2GrannyAnimation.cpp b/trinity/Tr2GrannyAnimation.cpp index 0dbe67aa5..921d2fa20 100644 --- a/trinity/Tr2GrannyAnimation.cpp +++ b/trinity/Tr2GrannyAnimation.cpp @@ -6,6 +6,7 @@ #include "Resources/TriGeometryRes.h" #include "Tr2Renderer.h" #include "include/ITr2DebugRenderer.h" +#include "Include/ITr2PoseModifier.h" #include "Utilities/BoundingBox.h" #include "Utilities/BoundingSphere.h" #include "Tr2VertexDefinitionUtilities.h" @@ -88,6 +89,7 @@ Tr2GrannyAnimation::Tr2GrannyAnimation( IRoot* lockobj ) : m_additiveMode( false ), m_aimingBone( false ), m_aimBone( "" ), + m_poseModifier( nullptr ), m_paused( false ), m_pauseTime( 0.f ), m_totalPauseOffset( 0.f ) @@ -1691,6 +1693,12 @@ void Tr2GrannyAnimation::PrePhysicsAnimation( Be::Time time, const Matrix& model UpdateAimingBone( skeleton ); + if( m_poseModifier ) + { + m_poseModifier->ModifyPose( skeleton, m_pose ); + } + + if( m_boneOffset.NeedRebind( (uint32_t)skeleton.bones.size() ) && skeleton.bones.size() ) { std::vector bones( skeleton.bones.size() ); @@ -2133,6 +2141,16 @@ void Tr2GrannyAnimation::DisableAimBone() m_aimingBone = false; } +ITr2PoseModifier* Tr2GrannyAnimation::GetPoseModifier() const +{ + return m_poseModifier; +} + +void Tr2GrannyAnimation::SetPoseModifier( ITr2PoseModifier* poseModifier ) +{ + m_poseModifier = poseModifier; +} + void Tr2GrannyAnimation::SetAdditiveBlendMode( bool additive ) { m_additiveMode = additive; @@ -2434,7 +2452,7 @@ std::pair Tr2AnimationMeshBinding::GetBoneTransforms() { GrannyColumnMatrixMultiply4x3Transpose( (granny_real32*)( (granny_matrix_3x4*)m_boneTransforms.get() )[i], - (granny_real32*)m_meshSkeleton->Bones[meshToBone[i]].InverseWorld4x4, + (granny_real32*)m_meshSkeleton -> Bones[meshToBone[i]].InverseWorld4x4, (granny_real32*)GrannyGetWorldPose4x4( m_animation->m_worldPose, animBones[i] ) ); } } diff --git a/trinity/Tr2GrannyAnimation.h b/trinity/Tr2GrannyAnimation.h index 90ade9265..95cd64b52 100644 --- a/trinity/Tr2GrannyAnimation.h +++ b/trinity/Tr2GrannyAnimation.h @@ -13,6 +13,7 @@ BLUE_DECLARE( TriGeometryRes ); BLUE_DECLARE( Tr2GrannyAnimation ); class Tr2AnimationMeshBinding; +class ITr2PoseModifier; namespace Tr2GrannyAnimationUtils { @@ -94,6 +95,9 @@ BLUE_CLASS( Tr2GrannyAnimation ) : void AimBone( const char* boneName, float target_x, float target_y, float target_z, float axis_x, float axis_y, float axis_z ); void DisableAimBone(); + ITr2PoseModifier* GetPoseModifier() const; + void SetPoseModifier( ITr2PoseModifier * poseModifier ); + void SetAdditiveBlendMode( bool additive ); bool GetAdditiveBlendMode(); @@ -219,6 +223,8 @@ BLUE_CLASS( Tr2GrannyAnimation ) : Vector3 m_aimBoneOrientation; Vector3 m_aimAxis; + ITr2PoseModifier* m_poseModifier; + bool m_useMeshBinding; bool m_animationEnabled; From b22b565f7f7b67c43f0cf576902290a5a431aa8d Mon Sep 17 00:00:00 2001 From: breki Date: Tue, 28 Jul 2026 15:32:55 +0000 Subject: [PATCH 10/14] more animation setup, play/stop working now --- .../SpaceObject/Children/EveChildTurret.cpp | 101 +++--------------- trinity/Tr2GrannyAnimation.cpp | 5 + trinity/Tr2GrannyAnimation.h | 1 + trinity/Tr2GrannyAnimationLayer.cpp | 36 +++++++ trinity/Tr2GrannyAnimationLayer.h | 1 + trinity/Tr2GrannyAnimation_Blue.cpp | 6 ++ 6 files changed, 61 insertions(+), 89 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp index 4af33e885..1dbc16981 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp @@ -867,116 +867,41 @@ TriGeometryRes* EveChildTurret::GetGeometryRes() const return m_mesh ? m_mesh->GetGeometryResource() : nullptr; } -// TODO: heavily refactor once animation ownership is decided (m_animationUpdater vs m_sequencer). -// Known defects for the rewrite: -// - a missing anim name aborts the whole request instead of playing what was found -// - the idle anim starts at `delay` instead of after the one-shot finishes -// (original sequenced via player SetStartTime/SetStopTime, no equivalent wired here) -// - StopAnimation is a stub, so the "stop all animation" call below does nothing -// - duplicated find_if lookups float EveChildTurret::PlayAnimation( const std::string& animName, const std::string& animNameIdle, float delay ) { - auto geometryRes = GetGeometryRes(); - if( !m_animationUpdater || !geometryRes ) + if( !m_animationUpdater ) { return 0.f; } - float animLength = 0.f; - - if( auto cmfData = geometryRes->GetCMFData() ) - { - // there can be more animations in one res, so find right one - size_t animIx = cmfData->animations.size(); - if( !animName.empty() ) - { - auto animation = std::find_if( cmfData->animations.begin(), cmfData->animations.end(), [&animName]( const cmf::Animation& anim ) { - return cmf::ToStdStringView( anim.name ) == animName; - } ); - if( animation == cmfData->animations.end() ) - { - return 0.f; - } - animIx = std::distance( cmfData->animations.begin(), animation ); - } - - size_t idleIx = cmfData->animations.size(); - if( !animNameIdle.empty() ) - { - auto animation = std::find_if( cmfData->animations.begin(), cmfData->animations.end(), [&animNameIdle]( const cmf::Animation& anim ) { - return cmf::ToStdStringView( anim.name ) == animNameIdle; - } ); - if( animation == cmfData->animations.end() ) - { - return 0.f; - } - idleIx = std::distance( cmfData->animations.begin(), animation ); - } - // stop all animation - StopAnimation( delay ); + m_animationUpdater->StopAnimations( delay ); - // play first anim once, if provided & found - if( animIx != cmfData->animations.size() ) - { - auto& animation = cmfData->animations[animIx]; - animLength = animation.duration; - - // ( const char* animName, bool replace, int loopCount, float delay, float speed, bool clearWhenDone ) - std::string animationName = cmf::ToStdString( animation.name ); - bool replace = false; // TODO: correct? - int loopCount = 1; - float speed = 1.f; - m_animationUpdater->PlayAnimation( animationName.c_str(), replace, loopCount, delay, speed ); - // TODO: set start/stop time seems different from ^ - // player->SetStartTime( delay + Tr2Renderer::GetAnimationTime() ); - // player->SetStopTime( animLength + delay + Tr2Renderer::GetAnimationTime() ); - } - - // then play idle anim on loop (after delay), if provided & found - if( idleIx != cmfData->animations.size() ) + float animLength = 0.f; + if( !animName.empty() ) + { + if( m_animationUpdater->PlayAnimation( animName.c_str(), false, 1, 0.f, 1.f, false ) ) { - auto& animation = cmfData->animations[idleIx]; - std::string animationName = cmf::ToStdString( animation.name ); - bool replace = false; // TODO: correct? - int loopCount = 0; - float speed = 1.f; - - m_animationUpdater->PlayAnimation( animationName.c_str(), replace, loopCount, delay, speed ); - // TODO: set start time seems different from ^ - // player->SetStartTime( animLength + delay + Tr2Renderer::GetAnimationTime() ); + animLength = m_animationUpdater->FindAnimationDurationByName( animName.c_str() ); } } + if( !animNameIdle.empty() ) + { + m_animationUpdater->PlayAnimation( animNameIdle.c_str(), false, 0, 0.f, 1.f, false ); + } return animLength; } void EveChildTurret::StopAnimation( float delay ) { - // TODO: prob not needed and can just use animation class function instead of this probably useless middleman - // if we don't have a geometry, animation is useless and probably unwanted - if( !GetGeometryRes() ) - { - return; - } - - // stop if( m_animationUpdater ) { - /* - // TODO: find correct functions - m_animationUpdater->EnumerateAnimations( [&]( const std::shared_ptr& player ) { - player->SetStopTime( delay + Tr2Renderer::GetAnimationTime() ); - } ); - - m_animationUpdater->RemoveFinishedAnimations( Tr2Renderer::GetAnimationTime() ); - */ + m_animationUpdater->StopAnimations( delay ); } } std::string EveChildTurret::GetFireAnimationName() const { - /* - // TODO: idk if this is useful skipping for now // if m_currentCyclingFiresPos is 0, it's just "Fire" std::string res = "Fire"; if( m_currentCyclingFiresPos > 0 ) @@ -986,8 +911,6 @@ std::string EveChildTurret::GetFireAnimationName() const } return res; - */ - return ""; } EveTurretFiringFX* EveChildTurret::GetFiringEffect() diff --git a/trinity/Tr2GrannyAnimation.cpp b/trinity/Tr2GrannyAnimation.cpp index 921d2fa20..275429617 100644 --- a/trinity/Tr2GrannyAnimation.cpp +++ b/trinity/Tr2GrannyAnimation.cpp @@ -1496,6 +1496,11 @@ void Tr2GrannyAnimation::EndAnimation() } +void Tr2GrannyAnimation::StopAnimations( float delay ) +{ + m_baseLayer.StopAnimations( delay ); +} + void Tr2GrannyAnimation::ClearAnimations() { m_baseLayer.ClearAnimations(); diff --git a/trinity/Tr2GrannyAnimation.h b/trinity/Tr2GrannyAnimation.h index 95cd64b52..7b100211f 100644 --- a/trinity/Tr2GrannyAnimation.h +++ b/trinity/Tr2GrannyAnimation.h @@ -76,6 +76,7 @@ BLUE_CLASS( Tr2GrannyAnimation ) : bool PlayAnimation( const char* animName, bool replace, int loopCount, float delay, float speed, bool clearWhenDone = true ); bool PlayLayerAnimationByName( const char* layer, const char* animName, bool replace, int loopCount, float delay, float speed, bool clearWhenDone ); void EndAnimation(); + void StopAnimations( float delay ); void ClearAnimations(); float GetAnimationChainCompleteTime(); diff --git a/trinity/Tr2GrannyAnimationLayer.cpp b/trinity/Tr2GrannyAnimationLayer.cpp index 3458c5d6a..ca4713280 100644 --- a/trinity/Tr2GrannyAnimationLayer.cpp +++ b/trinity/Tr2GrannyAnimationLayer.cpp @@ -457,6 +457,41 @@ void Tr2GrannyAnimationLayer::ClearMorphTracks( granny_control* control ) } #endif +void Tr2GrannyAnimationLayer::StopAnimations( float delay ) +{ + m_animationQueue.clear(); + + if( m_sequencer ) + { + const float animationTime = GetLayerAnimationTime(); + m_sequencer->EnumerateAnimations( [&]( const std::shared_ptr& player ) { + player->SetStopTime( animationTime + delay ); + if( delay <= 0.f ) + { + ClearMorphTracks( player.get() ); + } + } ); + m_sequencer->RemoveFinishedAnimations( animationTime ); + } + +#if WITH_GRANNY + if( m_modelInstance ) + { + for( granny_model_control_binding* binding = GrannyModelControlsBegin( m_modelInstance ); binding != GrannyModelControlsEnd( m_modelInstance ); ) + { + granny_control* control = GrannyGetControlFromBinding( binding ); + binding = GrannyModelControlsNext( binding ); + GrannyCompleteControlAt( control, GetLayerAnimationTime() + delay ); + if( GrannyFreeControlIfComplete( control ) ) + { + ClearTextTracks( control ); + ClearMorphTracks( control ); + } + } + } +#endif +} + void Tr2GrannyAnimationLayer::ClearAnimations() { m_animationQueue.clear(); @@ -773,6 +808,7 @@ void Tr2GrannyAnimationLayer::FreeCompletedControls() ClearMorphTracks( player.get() ); } } ); + m_sequencer->RemoveFinishedAnimations( GetLayerAnimationTime() ); } #if WITH_GRANNY diff --git a/trinity/Tr2GrannyAnimationLayer.h b/trinity/Tr2GrannyAnimationLayer.h index a4e902862..b6155f35e 100644 --- a/trinity/Tr2GrannyAnimationLayer.h +++ b/trinity/Tr2GrannyAnimationLayer.h @@ -70,6 +70,7 @@ class Tr2GrannyAnimationLayer bool PlayAnimation( const Tr2GrannyAnimation* grannyAnimation, const char* animName, bool replace, int loopCount, float delay, float speed, bool clearWhenDone ); void QueueAnimation( const char* animName, bool replace, int loopCount, float delay, float speed, bool clearWhenDone ); void EndAnimation(); + void StopAnimations( float delay ); void ClearAnimations(); float GetAnimationChainCompleteTime(); float GetAnimationRemainingTime(); diff --git a/trinity/Tr2GrannyAnimation_Blue.cpp b/trinity/Tr2GrannyAnimation_Blue.cpp index 35192d6a8..2ae7ab057 100644 --- a/trinity/Tr2GrannyAnimation_Blue.cpp +++ b/trinity/Tr2GrannyAnimation_Blue.cpp @@ -85,6 +85,12 @@ const Be::ClassInfo* Tr2GrannyAnimation::ExposeToBlue() ClearAnimations, "ClearAnimations()\n\n" "Abruptly ends all animations." ) + MAP_METHOD_AND_WRAP( + "StopAnimations", + StopAnimations, + "StopAnimations( delay )\n\n" + "Stops all animations, current and queued.\n" + ":param delay: time (in seconds) from now until playing animations stop" ) MAP_METHOD_AND_WRAP( "PlayLayerAnimation", From e338a312ec2b01dcad1a15e35881528635ac92af Mon Sep 17 00:00:00 2001 From: breki Date: Fri, 31 Jul 2026 11:08:15 +0000 Subject: [PATCH 11/14] runtime child init and firingEffectResPath consumption --- trinity/Eve/SpaceObject/Children/EveChildTurret.cpp | 10 ++++++++++ .../Eve/SpaceObject/Children/EveChildTurret_Blue.cpp | 2 +- trinity/Eve/SpaceObject/EveSpaceObject2.cpp | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp index 1dbc16981..0cf31035b 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp @@ -68,6 +68,12 @@ bool EveChildTurret::Initialize() // pass down some user-defined data into submodules we don't save out. m_target->SetImpactBehaviour( m_impactSize, m_impactBehaviour ); + // an inline-authored firingEffect wins over the res path at load time + if( !m_firingEffect && !m_firingEffectResPath.empty() ) + { + SetFiringEffect( BeResMan->LoadObject( m_firingEffectResPath.c_str() ).p ); + } + return EveChildMesh::Initialize(); } bool EveChildTurret::OnModified( Be::Var* value ) @@ -76,6 +82,10 @@ bool EveChildTurret::OnModified( Be::Var* value ) { m_target->SetImpactBehaviour( m_impactSize, m_impactBehaviour ); } + if( IsMatch( value, m_firingEffectResPath ) && !m_firingEffectResPath.empty() ) + { + SetFiringEffect( BeResMan->LoadObject( m_firingEffectResPath.c_str() ).p ); + } return EveChildMesh::OnModified( value ); } void EveChildTurret::RegisterComponents() diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp index 0d36c6501..ed17ee659 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret_Blue.cpp @@ -41,7 +41,7 @@ const Be::ClassInfo* EveChildTurret::ExposeToBlue() MAP_ATTRIBUTE( "firingEffect", m_firingEffect, "", Be::HIDDEN ) // Needed to make Graphite able to detect bindings inside of the firing effect. MAP_PROPERTY( "firingEffect", GetFiringEffect, SetFiringEffect, "The module for the firing effect of this turret" ) - MAP_ATTRIBUTE( "firingEffectResPath", m_firingEffectResPath, "A res path to the redfile containing the primary firing effect", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "firingEffectResPath", m_firingEffectResPath, "A res path to the redfile containing the primary firing effect", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) MAP_ATTRIBUTE( "impactSize", m_impactSize, "Size of impacts. No impact if size is 0 or less", Be::READWRITE | Be::NOTIFY | Be::PERSIST ) MAP_ATTRIBUTE_WITH_CHOOSER( "impactBehaviour", m_impactBehaviour, "What do we want to hit? ", Be::READWRITE | Be::NOTIFY | Be::PERSIST | Be::ENUM, ImpactBehaviourChooser ) diff --git a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp index c580579b1..0de7cd154 100644 --- a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp +++ b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp @@ -307,6 +307,11 @@ void EveSpaceObject2::OnListModified( long event, ssize_t key, ssize_t key2, IRo switch( event & BELIST_EVENTMASK ) { case BELIST_INSERTED: + // children inserted at runtime never go through the loader's initialize pass + if( IInitializePtr init = BlueCastPtr( value ) ) + { + init->Initialize(); + } if( IEveSpaceObjectChildPtr child = BlueCastPtr( value ) ) { for( auto it = begin( m_controllerVariables ); it != end( m_controllerVariables ); ++it ) From 3b3bd27158f6bdc9f845acc1fa4a40705de40ae4 Mon Sep 17 00:00:00 2001 From: breki Date: Tue, 4 Aug 2026 14:05:38 +0000 Subject: [PATCH 12/14] remove animation clearing fix --- trinity/Tr2GrannyAnimationLayer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/trinity/Tr2GrannyAnimationLayer.cpp b/trinity/Tr2GrannyAnimationLayer.cpp index ca4713280..5833eb006 100644 --- a/trinity/Tr2GrannyAnimationLayer.cpp +++ b/trinity/Tr2GrannyAnimationLayer.cpp @@ -808,7 +808,6 @@ void Tr2GrannyAnimationLayer::FreeCompletedControls() ClearMorphTracks( player.get() ); } } ); - m_sequencer->RemoveFinishedAnimations( GetLayerAnimationTime() ); } #if WITH_GRANNY From 889e172b95b3f3d5bbefd944a2a8c47ab90a513d Mon Sep 17 00:00:00 2001 From: breki Date: Tue, 4 Aug 2026 14:50:38 +0000 Subject: [PATCH 13/14] revert initalization and stopanimation --- trinity/Eve/SpaceObject/Children/EveChildTurret.cpp | 8 -------- trinity/Eve/SpaceObject/Children/EveChildTurret.h | 1 - trinity/Eve/SpaceObject/EveSpaceObject2.cpp | 5 ----- 3 files changed, 14 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp index 0cf31035b..22dfa6c94 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp @@ -902,14 +902,6 @@ float EveChildTurret::PlayAnimation( const std::string& animName, const std::str return animLength; } -void EveChildTurret::StopAnimation( float delay ) -{ - if( m_animationUpdater ) - { - m_animationUpdater->StopAnimations( delay ); - } -} - std::string EveChildTurret::GetFireAnimationName() const { // if m_currentCyclingFiresPos is 0, it's just "Fire" diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.h b/trinity/Eve/SpaceObject/Children/EveChildTurret.h index 033b9c925..9a920b5f4 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.h +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.h @@ -108,7 +108,6 @@ BLUE_CLASS( EveChildTurret ) : // animation float PlayAnimation( const std::string& animName, const std::string& animNameIdle, float delay = 0.f ); - void StopAnimation( float delay = 0.f ); std::string GetFireAnimationName() const; EveTurretFiringFX* GetFiringEffect(); diff --git a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp index 0de7cd154..c580579b1 100644 --- a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp +++ b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp @@ -307,11 +307,6 @@ void EveSpaceObject2::OnListModified( long event, ssize_t key, ssize_t key2, IRo switch( event & BELIST_EVENTMASK ) { case BELIST_INSERTED: - // children inserted at runtime never go through the loader's initialize pass - if( IInitializePtr init = BlueCastPtr( value ) ) - { - init->Initialize(); - } if( IEveSpaceObjectChildPtr child = BlueCastPtr( value ) ) { for( auto it = begin( m_controllerVariables ); it != end( m_controllerVariables ); ++it ) From 170bdddaae89e43a5e7d3340b59c938bfd89c139 Mon Sep 17 00:00:00 2001 From: breki Date: Wed, 5 Aug 2026 09:16:51 +0000 Subject: [PATCH 14/14] workaround because clang formatter is being weird --- trinity/Tr2GrannyAnimation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trinity/Tr2GrannyAnimation.cpp b/trinity/Tr2GrannyAnimation.cpp index 275429617..04adc45bb 100644 --- a/trinity/Tr2GrannyAnimation.cpp +++ b/trinity/Tr2GrannyAnimation.cpp @@ -2457,7 +2457,7 @@ std::pair Tr2AnimationMeshBinding::GetBoneTransforms() { GrannyColumnMatrixMultiply4x3Transpose( (granny_real32*)( (granny_matrix_3x4*)m_boneTransforms.get() )[i], - (granny_real32*)m_meshSkeleton -> Bones[meshToBone[i]].InverseWorld4x4, + (granny_real32*)( m_meshSkeleton->Bones[meshToBone[i]].InverseWorld4x4 ), (granny_real32*)GrannyGetWorldPose4x4( m_animation->m_worldPose, animBones[i] ) ); } }