From 4db0d934113ae494150dcbc0895b30285b722396 Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Tue, 14 Jul 2026 12:14:25 +0100 Subject: [PATCH 01/12] Fix crash with SOF objects having a mesh, but no geom res defined in editor --- trinity/Eve/SpaceObject/EveSpaceObject2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp index c580579b1..70e783351 100644 --- a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp +++ b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp @@ -1214,7 +1214,7 @@ void EveSpaceObject2::GetBatchesFromOverlayVector( ITriRenderBatchAccumulator* b } TriGeometryRes* geomRes = mesh->GetGeometryResource(); - if( !geomRes->IsGood() ) + if( !geomRes || !geomRes->IsGood() ) { return; } From 21a97ff01a63634707853496365aa74f9f73c396 Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Thu, 16 Jul 2026 11:52:21 +0100 Subject: [PATCH 02/12] Added support for child meshes to inherit overlay effects --- .../Eve/SpaceObject/Children/EveChildMesh.cpp | 112 +++++++++++++++++- .../Eve/SpaceObject/Children/EveChildMesh.h | 6 + trinity/Eve/SpaceObject/EveSpaceObject2.h | 1 + 3 files changed, 118 insertions(+), 1 deletion(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp b/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp index a9efa903e..42e37ebd9 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp @@ -630,7 +630,21 @@ bool EveChildMesh::HasTransparentBatches() { if( m_display && m_mesh ) { - return !( m_mesh->GetAreas( TRIBATCHTYPE_TRANSPARENT )->empty() ); + if( !( m_mesh->GetAreas( TRIBATCHTYPE_TRANSPARENT )->empty() ) ) + { + return true; + } + + if( m_parentOverlayEffects != nullptr ) + { + for( auto it = m_parentOverlayEffects->begin(); it != m_parentOverlayEffects->end(); ++it ) + { + if( ( *it )->HasTransparentArea() ) + { + return true; + } + } + } } return false; @@ -665,6 +679,91 @@ void EveChildMesh::GetBatches( ITriRenderBatchAccumulator* batches, TriBatchType ( *it )->GetBatches( batches, batchType, perObjectData, reason ); } } + + GetBatchesFromOverlayVector( batches, perObjectData, batchType ); + } +} + +void EveChildMesh::RebuildOverlayAreaBlocks() +{ + for( int i = 0; i < EveMeshOverlayEffect::TYPE_COUNT; ++i ) + { + m_overlayMeshAreaBlocks[i].clear(); + } + + if( m_mesh ) + { + m_mesh->CollectAreaBlocks( m_overlayMeshAreaBlocks[EveMeshOverlayEffect::TYPE_ALL], TRIBATCHTYPE_OPAQUE ); + m_mesh->CollectAreaBlocks( m_overlayMeshAreaBlocks[EveMeshOverlayEffect::TYPE_ALL], TRIBATCHTYPE_TRANSPARENT ); + m_mesh->CollectAreaBlocks( m_overlayMeshAreaBlocks[EveMeshOverlayEffect::TYPE_ALL], TRIBATCHTYPE_DECAL ); + m_mesh->CollectAreaBlocks( m_overlayMeshAreaBlocks[EveMeshOverlayEffect::TYPE_OPAQUEONLY], TRIBATCHTYPE_OPAQUE ); + for( int i = 0; i < EveMeshOverlayEffect::TYPE_COUNT; ++i ) + { + TriRenderBatchAreaBlock::Optimize( m_overlayMeshAreaBlocks[i] ); + } + } + + m_overlayAreaBlocksBuilt = true; +} + +void EveChildMesh::GetBatchesFromOverlayVector( ITriRenderBatchAccumulator* batches, const Tr2PerObjectData* perObjectData, TriBatchType batchType ) +{ + if( !m_mesh || m_parentOverlayEffects == nullptr || m_parentOverlayEffects->empty() ) + { + return; + } + + TriGeometryRes* geomRes = m_mesh->GetGeometryResource(); + if( !geomRes || !geomRes->IsGood() ) + { + return; + } + + if( !m_overlayAreaBlocksBuilt ) + { + RebuildOverlayAreaBlocks(); + } + + const float screenSize = min( m_currentInstanceScreenSize, m_currentScreenSize ); + auto lod = geomRes->GetMeshLod( m_mesh->GetMeshIndex(), screenSize ); + if( !lod || !lod->m_allocationsValid ) + { + return; + } + + for( auto it = m_parentOverlayEffects->begin(); it != m_parentOverlayEffects->end(); ++it ) + { + EveMeshOverlayEffectPtr overlay = *it; + bool success = false; + const PTr2EffectVector& effects = overlay->GetEffects( batchType, success ); + if( !success ) + { + continue; + } + + EveMeshOverlayEffect::OverlayType overlayType = overlay->GetType( batchType ); + for( auto eff = effects.begin(); eff != effects.end(); ++eff ) + { + Tr2EffectPtr effect = *eff; + + for( auto& areaBlock : m_overlayMeshAreaBlocks[overlayType] ) + { + if( auto primCount = GetPrimitiveCount( *lod, areaBlock.m_startIndex, areaBlock.m_count ) ) + { + Tr2RenderBatch batch; + batch.SetMaterial( effect ); + batch.SetGeometry( lod->m_mesh->m_vertexDeclarationHandle, lod->m_vertexAllocation, lod->m_indexAllocation ); + batch.SetPerObjectData( perObjectData ); + batch.SetDrawIndexedInstanced( + primCount * 3, + 1, + lod->m_indexAllocation.GetStartIndex() + lod->m_areas[areaBlock.m_startIndex].m_firstIndex, + lod->m_vertexAllocation.GetOffset() / lod->m_vertexAllocation.GetStride(), + 0 ); + batches->Commit( batch ); + } + } + } } } @@ -928,11 +1027,16 @@ void EveChildMesh::UpdateAsyncronous( const EveUpdateContext& updateContext, con } // need to update the data we get from the parent to be relevant to us! + m_parentOverlayEffects = nullptr; if( nullptr != params.spaceObjectParent ) { params.spaceObjectParent->GetPerObjectStructs( m_vsData, m_psData ); params.spaceObjectParent->GetParentData( &m_parentData ); + // Borrow the parent's overlay effects so we can draw them over our own geometry. + // spaceObjectParent is always an EveSpaceObject2 (set to `this` in the parent's update). + m_parentOverlayEffects = &static_cast( params.spaceObjectParent )->GetOverlayEffects(); + // need to move the clipdata inversely of the translation of the childmesh m_vsData.clipData = Vector4( m_vsData.clipData.GetXYZ() - m_translation, m_vsData.clipData.w ); m_psData.clipSphereCenter = m_psData.clipSphereCenter - m_translation; @@ -1060,6 +1164,12 @@ void EveChildMesh::SetMesh( Tr2MeshBase* mesh ) m_mesh = mesh; m_instancedMesh = BlueCastPtr( m_mesh ); + + m_overlayAreaBlocksBuilt = false; + for( int i = 0; i < EveMeshOverlayEffect::TYPE_COUNT; ++i ) + { + m_overlayMeshAreaBlocks[i].clear(); + } } void EveChildMesh::RegisterAudioGeometry() diff --git a/trinity/Eve/SpaceObject/Children/EveChildMesh.h b/trinity/Eve/SpaceObject/Children/EveChildMesh.h index 54d5e1b52..2e84bf964 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildMesh.h +++ b/trinity/Eve/SpaceObject/Children/EveChildMesh.h @@ -110,6 +110,7 @@ BLUE_CLASS( EveChildMesh ) : // ITr2Renderable virtual bool HasTransparentBatches(); virtual void GetBatches( ITriRenderBatchAccumulator * batches, TriBatchType batchType, const Tr2PerObjectData* perObjectData, Tr2RenderReason reason = TR2RENDERREASON_NORMAL ); + void GetBatchesFromOverlayVector( ITriRenderBatchAccumulator * batches, const Tr2PerObjectData* perObjectData, TriBatchType batchType ); virtual float GetSortValue(); virtual Tr2PerObjectData* GetPerObjectData( ITriRenderBatchAccumulator * accumulator ); virtual bool IsVisible( const EveUpdateContext& updateContext ) const override; @@ -213,6 +214,11 @@ BLUE_CLASS( EveChildMesh ) : Tr2InstancedMeshPtr m_instancedMesh; // Cached downcast of m_mesh IEveSpaceObject2::ParentData m_parentData; + const PEveMeshOverlayEffectVector* m_parentOverlayEffects = nullptr; + std::vector m_overlayMeshAreaBlocks[EveMeshOverlayEffect::TYPE_COUNT]; + bool m_overlayAreaBlocksBuilt = false; + void RebuildOverlayAreaBlocks(); + PIEveChildTransformModifierVector m_transformModifiers; Tr2GrannyAnimationPtr m_animationUpdater; std::unique_ptr m_meshBinding; diff --git a/trinity/Eve/SpaceObject/EveSpaceObject2.h b/trinity/Eve/SpaceObject/EveSpaceObject2.h index 19ded272d..2b091aa3a 100644 --- a/trinity/Eve/SpaceObject/EveSpaceObject2.h +++ b/trinity/Eve/SpaceObject/EveSpaceObject2.h @@ -448,6 +448,7 @@ BLUE_CLASS( EveSpaceObject2 ) : void AddOverlayEffect( EveMeshOverlayEffectPtr newOverlayEffect ); void RemoveOverlayEffect( EveMeshOverlayEffectPtr newOverlayEffect ); EveMeshOverlayEffectPtr GetOverlayEffectByName( const char* name ) const; + const PEveMeshOverlayEffectVector& GetOverlayEffects() const { return m_overlayEffects; } void AddLocatorSet( const char* name, const Locator* locators, size_t locatorCount ); Vector3 GetDamageLocator( uint32_t index ) const; Vector3 GetDamageLocatorDirectionLocal( uint32_t index ) const; From 781a4e3a53e961d18d2a1f3ef267d271b0b7128b Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Thu, 16 Jul 2026 15:22:10 +0100 Subject: [PATCH 03/12] Refactored the code to reduce duplicated code with child effect inheritance --- .../Attachments/EveMeshOverlayEffect.cpp | 71 +++++++++++++++++++ .../Attachments/EveMeshOverlayEffect.h | 16 +++++ .../Eve/SpaceObject/Children/EveChildMesh.cpp | 60 ++-------------- trinity/Eve/SpaceObject/EveSpaceObject2.cpp | 44 +----------- 4 files changed, 95 insertions(+), 96 deletions(-) diff --git a/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.cpp b/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.cpp index fcf2502c2..28b20c565 100644 --- a/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.cpp +++ b/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.cpp @@ -5,6 +5,8 @@ #include "Shader/Tr2Effect.h" #include "Curves/TriCurveSet.h" #include "Controllers/ITr2Controller.h" +#include "Tr2MeshBase.h" +#include "Resources/TriGeometryRes.h" // -------------------------------------------------------------------------------------- @@ -286,3 +288,72 @@ void EveMeshOverlayEffect::Update( Be::Time realTime, Be::Time simTime ) ( *it )->Update( 0.5f ); } } + +void CollectOverlayAreaBlocks( Tr2MeshBase* mesh, std::vector ( &outAreaBlocks )[EveMeshOverlayEffect::TYPE_COUNT] ) +{ + for( int i = 0; i < EveMeshOverlayEffect::TYPE_COUNT; ++i ) + { + outAreaBlocks[i].clear(); + } + + if( !mesh ) + { + return; + } + + mesh->CollectAreaBlocks( outAreaBlocks[EveMeshOverlayEffect::TYPE_ALL], TRIBATCHTYPE_OPAQUE ); + mesh->CollectAreaBlocks( outAreaBlocks[EveMeshOverlayEffect::TYPE_ALL], TRIBATCHTYPE_TRANSPARENT ); + mesh->CollectAreaBlocks( outAreaBlocks[EveMeshOverlayEffect::TYPE_ALL], TRIBATCHTYPE_DECAL ); + mesh->CollectAreaBlocks( outAreaBlocks[EveMeshOverlayEffect::TYPE_OPAQUEONLY], TRIBATCHTYPE_OPAQUE ); + + // this list is too long, will hold one element for each mesharea at least... Optimize! + for( int i = 0; i < EveMeshOverlayEffect::TYPE_COUNT; ++i ) + { + TriRenderBatchAreaBlock::Optimize( outAreaBlocks[i] ); + } +} + +void EmitOverlayBatches( + ITriRenderBatchAccumulator* batches, + const Tr2PerObjectData* perObjectData, + TriBatchType batchType, + const PEveMeshOverlayEffectVector& overlayEffects, + const std::vector ( &areaBlocks )[EveMeshOverlayEffect::TYPE_COUNT], + const TriGeometryResLodData& lod ) +{ + for( auto it = overlayEffects.begin(); it != overlayEffects.end(); ++it ) + { + EveMeshOverlayEffectPtr overlay = *it; + bool success = false; + const PTr2EffectVector& effects = overlay->GetEffects( batchType, success ); + if( !success ) + { + continue; + } + + EveMeshOverlayEffect::OverlayType overlayType = overlay->GetType( batchType ); + for( auto eff = effects.begin(); eff != effects.end(); ++eff ) + { + Tr2EffectPtr effect = *eff; + + // add all mesh area blocks + for( auto& areaBlock : areaBlocks[overlayType] ) + { + if( auto primCount = GetPrimitiveCount( lod, areaBlock.m_startIndex, areaBlock.m_count ) ) + { + Tr2RenderBatch batch; + batch.SetMaterial( effect ); + batch.SetGeometry( lod.m_mesh->m_vertexDeclarationHandle, lod.m_vertexAllocation, lod.m_indexAllocation ); + batch.SetPerObjectData( perObjectData ); + batch.SetDrawIndexedInstanced( + primCount * 3, + 1, + lod.m_indexAllocation.GetStartIndex() + lod.m_areas[areaBlock.m_startIndex].m_firstIndex, + lod.m_vertexAllocation.GetOffset() / lod.m_vertexAllocation.GetStride(), + 0 ); + batches->Commit( batch ); + } + } + } + } +} diff --git a/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.h b/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.h index e62f95026..1073c682f 100644 --- a/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.h +++ b/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.h @@ -7,15 +7,21 @@ #include "Tr2MeshArea.h" #include "ITr2Renderable.h" +#include "TriRenderBatch.h" #include "Controllers/ITr2ControllerOwner.h" #include "ITr2CurveSetOwner.h" BLUE_DECLARE( Tr2Effect ); BLUE_DECLARE( TriCurveSet ); +BLUE_DECLARE( Tr2MeshBase ); BLUE_DECLARE_VECTOR( Tr2Effect ); BLUE_DECLARE_INTERFACE( ITr2Controller ); BLUE_DECLARE_IVECTOR( ITr2Controller ); +struct TriGeometryResLodData; +class ITriRenderBatchAccumulator; +class Tr2PerObjectData; + // -------------------------------------------------------------------------------- // Description: // This class holds curveSets and Tr2Effects used for overlay effects for SpaceObjects. @@ -98,4 +104,14 @@ BLUE_CLASS( EveMeshOverlayEffect ) : TYPEDEF_BLUECLASS( EveMeshOverlayEffect ); BLUE_DECLARE_VECTOR( EveMeshOverlayEffect ); +void CollectOverlayAreaBlocks( Tr2MeshBase* mesh, std::vector ( &outAreaBlocks )[EveMeshOverlayEffect::TYPE_COUNT] ); + +void EmitOverlayBatches( + ITriRenderBatchAccumulator* batches, + const Tr2PerObjectData* perObjectData, + TriBatchType batchType, + const PEveMeshOverlayEffectVector& overlayEffects, + const std::vector ( &areaBlocks )[EveMeshOverlayEffect::TYPE_COUNT], + const TriGeometryResLodData& lod ); + #endif // EveMeshOverlayEffect_H diff --git a/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp b/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp index 42e37ebd9..07e378fa7 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp @@ -686,23 +686,7 @@ void EveChildMesh::GetBatches( ITriRenderBatchAccumulator* batches, TriBatchType void EveChildMesh::RebuildOverlayAreaBlocks() { - for( int i = 0; i < EveMeshOverlayEffect::TYPE_COUNT; ++i ) - { - m_overlayMeshAreaBlocks[i].clear(); - } - - if( m_mesh ) - { - m_mesh->CollectAreaBlocks( m_overlayMeshAreaBlocks[EveMeshOverlayEffect::TYPE_ALL], TRIBATCHTYPE_OPAQUE ); - m_mesh->CollectAreaBlocks( m_overlayMeshAreaBlocks[EveMeshOverlayEffect::TYPE_ALL], TRIBATCHTYPE_TRANSPARENT ); - m_mesh->CollectAreaBlocks( m_overlayMeshAreaBlocks[EveMeshOverlayEffect::TYPE_ALL], TRIBATCHTYPE_DECAL ); - m_mesh->CollectAreaBlocks( m_overlayMeshAreaBlocks[EveMeshOverlayEffect::TYPE_OPAQUEONLY], TRIBATCHTYPE_OPAQUE ); - for( int i = 0; i < EveMeshOverlayEffect::TYPE_COUNT; ++i ) - { - TriRenderBatchAreaBlock::Optimize( m_overlayMeshAreaBlocks[i] ); - } - } - + CollectOverlayAreaBlocks( m_mesh, m_overlayMeshAreaBlocks ); m_overlayAreaBlocksBuilt = true; } @@ -731,40 +715,7 @@ void EveChildMesh::GetBatchesFromOverlayVector( ITriRenderBatchAccumulator* batc return; } - for( auto it = m_parentOverlayEffects->begin(); it != m_parentOverlayEffects->end(); ++it ) - { - EveMeshOverlayEffectPtr overlay = *it; - bool success = false; - const PTr2EffectVector& effects = overlay->GetEffects( batchType, success ); - if( !success ) - { - continue; - } - - EveMeshOverlayEffect::OverlayType overlayType = overlay->GetType( batchType ); - for( auto eff = effects.begin(); eff != effects.end(); ++eff ) - { - Tr2EffectPtr effect = *eff; - - for( auto& areaBlock : m_overlayMeshAreaBlocks[overlayType] ) - { - if( auto primCount = GetPrimitiveCount( *lod, areaBlock.m_startIndex, areaBlock.m_count ) ) - { - Tr2RenderBatch batch; - batch.SetMaterial( effect ); - batch.SetGeometry( lod->m_mesh->m_vertexDeclarationHandle, lod->m_vertexAllocation, lod->m_indexAllocation ); - batch.SetPerObjectData( perObjectData ); - batch.SetDrawIndexedInstanced( - primCount * 3, - 1, - lod->m_indexAllocation.GetStartIndex() + lod->m_areas[areaBlock.m_startIndex].m_firstIndex, - lod->m_vertexAllocation.GetOffset() / lod->m_vertexAllocation.GetStride(), - 0 ); - batches->Commit( batch ); - } - } - } - } + EmitOverlayBatches( batches, perObjectData, batchType, *m_parentOverlayEffects, m_overlayMeshAreaBlocks, *lod ); } void EveChildMesh::GetShadowBatches( ITriRenderBatchAccumulator* batches, const Tr2PerObjectData* perObjectData, float shadowPixelSize ) @@ -1033,9 +984,10 @@ void EveChildMesh::UpdateAsyncronous( const EveUpdateContext& updateContext, con params.spaceObjectParent->GetPerObjectStructs( m_vsData, m_psData ); params.spaceObjectParent->GetParentData( &m_parentData ); - // Borrow the parent's overlay effects so we can draw them over our own geometry. - // spaceObjectParent is always an EveSpaceObject2 (set to `this` in the parent's update). - m_parentOverlayEffects = &static_cast( params.spaceObjectParent )->GetOverlayEffects(); + if( EveSpaceObject2Ptr spaceObject2Parent = BlueCastPtr( params.spaceObjectParent ) ) + { + m_parentOverlayEffects = &spaceObject2Parent->GetOverlayEffects(); + } // need to move the clipdata inversely of the translation of the childmesh m_vsData.clipData = Vector4( m_vsData.clipData.GetXYZ() - m_translation, m_vsData.clipData.w ); diff --git a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp index 70e783351..3a9535e43 100644 --- a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp +++ b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp @@ -1249,39 +1249,7 @@ void EveSpaceObject2::GetBatchesFromOverlayVector( ITriRenderBatchAccumulator* b } // second the effects - for( auto it = m_overlayEffects.begin(); it != m_overlayEffects.end(); ++it ) - { - EveMeshOverlayEffectPtr overlay = *it; - bool success = false; - const PTr2EffectVector& effects = overlay->GetEffects( batchType, success ); - if( success ) - { - EveMeshOverlayEffect::OverlayType overlayType = overlay->GetType( batchType ); - for( auto eff = effects.begin(); eff != effects.end(); ++eff ) - { - Tr2EffectPtr effect = *eff; - - // add all mesh area blocks - for( auto& areaBlock : m_overlayMeshAreaBlocks[overlayType] ) - { - if( auto primCount = GetPrimitiveCount( *lod, areaBlock.m_startIndex, areaBlock.m_count ) ) - { - Tr2RenderBatch batch; - batch.SetMaterial( effect ); - batch.SetGeometry( lod->m_mesh->m_vertexDeclarationHandle, lod->m_vertexAllocation, lod->m_indexAllocation ); - batch.SetPerObjectData( perObjectData ); - batch.SetDrawIndexedInstanced( - primCount * 3, - 1, - lod->m_indexAllocation.GetStartIndex() + lod->m_areas[areaBlock.m_startIndex].m_firstIndex, - lod->m_vertexAllocation.GetOffset() / lod->m_vertexAllocation.GetStride(), - 0 ); - batches->Commit( batch ); - } - } - } - } - } + EmitOverlayBatches( batches, perObjectData, batchType, m_overlayEffects, m_overlayMeshAreaBlocks, *lod ); } const Matrix* EveSpaceObject2::GetLocatorTransform( LocatorType lt, unsigned int lix ) @@ -2079,15 +2047,7 @@ void EveSpaceObject2::RebuildCachedData( BlueAsyncRes* p ) // build list of block areas we need to render for overlay effects if( m_mesh ) { - m_mesh->CollectAreaBlocks( m_overlayMeshAreaBlocks[EveMeshOverlayEffect::TYPE_ALL], TRIBATCHTYPE_OPAQUE ); - m_mesh->CollectAreaBlocks( m_overlayMeshAreaBlocks[EveMeshOverlayEffect::TYPE_ALL], TRIBATCHTYPE_TRANSPARENT ); - m_mesh->CollectAreaBlocks( m_overlayMeshAreaBlocks[EveMeshOverlayEffect::TYPE_ALL], TRIBATCHTYPE_DECAL ); - m_mesh->CollectAreaBlocks( m_overlayMeshAreaBlocks[EveMeshOverlayEffect::TYPE_OPAQUEONLY], TRIBATCHTYPE_OPAQUE ); - // this list is too long will hold one element for each mesharea at least... Optimize! - for( int i = 0; i < EveMeshOverlayEffect::TYPE_COUNT; ++i ) - { - TriRenderBatchAreaBlock::Optimize( m_overlayMeshAreaBlocks[i] ); - } + CollectOverlayAreaBlocks( m_mesh, m_overlayMeshAreaBlocks ); m_mesh->CollectAreaBlocksWithSharedMaterials( m_shadowMeshOpaqueAreas, TRIBATCHTYPE_OPAQUE ); for( auto& collector : m_shadowMeshOpaqueAreas ) From 88214a642923f29ac9c80eb2b9b024f9241a5151 Mon Sep 17 00:00:00 2001 From: JohnGreenFC <152402736+JohnGreenFC@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:41:51 +0100 Subject: [PATCH 04/12] Apply suggestion from @github-actions[bot] Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- trinity/Eve/SpaceObject/EveSpaceObject2.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/trinity/Eve/SpaceObject/EveSpaceObject2.h b/trinity/Eve/SpaceObject/EveSpaceObject2.h index 2b091aa3a..4e0a9b521 100644 --- a/trinity/Eve/SpaceObject/EveSpaceObject2.h +++ b/trinity/Eve/SpaceObject/EveSpaceObject2.h @@ -448,7 +448,10 @@ BLUE_CLASS( EveSpaceObject2 ) : void AddOverlayEffect( EveMeshOverlayEffectPtr newOverlayEffect ); void RemoveOverlayEffect( EveMeshOverlayEffectPtr newOverlayEffect ); EveMeshOverlayEffectPtr GetOverlayEffectByName( const char* name ) const; - const PEveMeshOverlayEffectVector& GetOverlayEffects() const { return m_overlayEffects; } + const PEveMeshOverlayEffectVector& GetOverlayEffects() const + { + return m_overlayEffects; + } void AddLocatorSet( const char* name, const Locator* locators, size_t locatorCount ); Vector3 GetDamageLocator( uint32_t index ) const; Vector3 GetDamageLocatorDirectionLocal( uint32_t index ) const; From 65e1bb642e4093a17b476a0ca5744b93b75c5b50 Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Tue, 21 Jul 2026 16:33:51 +0100 Subject: [PATCH 05/12] Added overlay support to child meshes --- .../Eve/SpaceObject/Children/EveChildMesh.cpp | 98 ++++++++++++++++++- .../Eve/SpaceObject/Children/EveChildMesh.h | 10 ++ .../Children/EveChildMesh_Blue.cpp | 2 + 3 files changed, 106 insertions(+), 4 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp b/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp index 07e378fa7..e5cf33284 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp @@ -30,7 +30,9 @@ EveChildMesh::EveChildMesh( IRoot* lockobj ) : PARENTLOCK( m_decals ), PARENTLOCK( m_attachments ), PARENTLOCK( m_lights ), + PARENTLOCK( m_overlayEffects ), m_display( true ), + m_inheritOverlayEffects( true ), m_isVisible( false ), m_instancesVisible( false ), m_castShadow( false ), @@ -369,6 +371,7 @@ void EveChildMesh::UpdateVisibility( const EveUpdateContext& updateContext, cons m_currentScreenSize = -1; m_instancesVisible = false; m_currentInstanceScreenSize = -1.0f; + m_overlayUpdateLod = parentLod; if( !m_hasUpdated ) { @@ -635,6 +638,14 @@ bool EveChildMesh::HasTransparentBatches() return true; } + for( auto it = m_overlayEffects.begin(); it != m_overlayEffects.end(); ++it ) + { + if( ( *it )->HasTransparentArea() ) + { + return true; + } + } + if( m_parentOverlayEffects != nullptr ) { for( auto it = m_parentOverlayEffects->begin(); it != m_parentOverlayEffects->end(); ++it ) @@ -692,7 +703,8 @@ void EveChildMesh::RebuildOverlayAreaBlocks() void EveChildMesh::GetBatchesFromOverlayVector( ITriRenderBatchAccumulator* batches, const Tr2PerObjectData* perObjectData, TriBatchType batchType ) { - if( !m_mesh || m_parentOverlayEffects == nullptr || m_parentOverlayEffects->empty() ) + const bool hasParentOverlays = m_parentOverlayEffects != nullptr && !m_parentOverlayEffects->empty(); + if( !m_mesh || ( m_overlayEffects.empty() && !hasParentOverlays ) ) { return; } @@ -715,7 +727,17 @@ void EveChildMesh::GetBatchesFromOverlayVector( ITriRenderBatchAccumulator* batc return; } - EmitOverlayBatches( batches, perObjectData, batchType, *m_parentOverlayEffects, m_overlayMeshAreaBlocks, *lod ); + // own effects are emitted before the inherited ones so the parent's overlays (e.g. cloak) + // draw on top of this child's own overlays (e.g. battle damage) + if( !m_overlayEffects.empty() ) + { + EmitOverlayBatches( batches, perObjectData, batchType, m_overlayEffects, m_overlayMeshAreaBlocks, *lod ); + } + + if( hasParentOverlays ) + { + EmitOverlayBatches( batches, perObjectData, batchType, *m_parentOverlayEffects, m_overlayMeshAreaBlocks, *lod ); + } } void EveChildMesh::GetShadowBatches( ITriRenderBatchAccumulator* batches, const Tr2PerObjectData* perObjectData, float shadowPixelSize ) @@ -984,9 +1006,26 @@ void EveChildMesh::UpdateAsyncronous( const EveUpdateContext& updateContext, con params.spaceObjectParent->GetPerObjectStructs( m_vsData, m_psData ); params.spaceObjectParent->GetParentData( &m_parentData ); - if( EveSpaceObject2Ptr spaceObject2Parent = BlueCastPtr( params.spaceObjectParent ) ) + if( m_inheritOverlayEffects ) { - m_parentOverlayEffects = &spaceObject2Parent->GetOverlayEffects(); + if( EveSpaceObject2Ptr spaceObject2Parent = BlueCastPtr( params.spaceObjectParent ) ) + { + m_parentOverlayEffects = &spaceObject2Parent->GetOverlayEffects(); + } + } + else + { + // Opted out of the parent's overlay: also neutralize the inherited clip sphere, + // otherwise the part is dissolved with the rest of the ship instead of staying visible. + m_vsData.clipData.w = 0.f; + m_psData.clipRadiusSq = 0.f; + m_psData.clipRadius2Sq = 0.f; + m_psData.clipSphereFactor = 0.f; + m_psData.clipSphereFactor2 = 0.f; + m_parentData.clipRadiusSq = 0.f; + m_parentData.clipRadius2Sq = 0.f; + m_parentData.clipFactor = 0.f; + m_parentData.clipFactor2 = 0.f; } // need to move the clipdata inversely of the translation of the childmesh @@ -1055,6 +1094,21 @@ void EveChildMesh::UpdateAsyncronous( const EveUpdateContext& updateContext, con void EveChildMesh::UpdateSyncronous( const EveUpdateContext& updateContext, const EveChildUpdateParams& params ) { + if( !m_overlayEffects.empty() ) + { + Be::Time time = updateContext.GetTime(); + if( EveLODHelper::ShouldUpdate( m_overlayUpdateLod, float( TimeAsDouble( time - m_lastOverlayUpdateTime ) ) ) ) + { + // overlay effect curves need to be updated on the game thread because they may have references + // to attributes that are not thread safe, particularly the parent's clipSphereFactor + m_lastOverlayUpdateTime = time; + for( auto it = m_overlayEffects.begin(); it != m_overlayEffects.end(); ++it ) + { + ( *it )->Update( time, time ); + } + } + } + bool allowAudioGeometry = !params.spaceObjectParent || params.spaceObjectParent->IsAudioOccluder(); if( !allowAudioGeometry && m_audioGeometryRegistered ) @@ -1124,6 +1178,37 @@ void EveChildMesh::SetMesh( Tr2MeshBase* mesh ) } } +void EveChildMesh::AddOverlayEffect( EveMeshOverlayEffectPtr newOverlayEffect ) +{ + m_overlayEffects.Append( newOverlayEffect->GetRawRoot() ); +} + +void EveChildMesh::RemoveOverlayEffect( EveMeshOverlayEffectPtr overlayEffectToRemove ) +{ + ssize_t index = m_overlayEffects.FindKey( overlayEffectToRemove->GetRawRoot() ); + if( index >= 0 ) + { + m_overlayEffects.Remove( index ); + } +} + +EveMeshOverlayEffectPtr EveChildMesh::GetOverlayEffectByName( const char* name ) const +{ + if( name == nullptr ) + { + return nullptr; + } + + for( auto overlay : m_overlayEffects ) + { + if( strcmp( overlay->m_name.c_str(), name ) == 0 ) + { + return overlay; + } + } + return nullptr; +} + void EveChildMesh::RegisterAudioGeometry() { if( !m_allowAudioGeometry ) @@ -1217,6 +1302,11 @@ void EveChildMesh::SetShaderOption( const BlueSharedString& name, const BlueShar m_mesh->SetShaderOption( name, value ); } + for( auto it = m_overlayEffects.begin(); it != m_overlayEffects.end(); ++it ) + { + ( *it )->SetShaderOption( name, value ); + } + for( EveSpaceObjectDecalVector::iterator it = m_decals.begin(); it != m_decals.end(); ++it ) { ( *it )->SetShaderOption( name, value ); diff --git a/trinity/Eve/SpaceObject/Children/EveChildMesh.h b/trinity/Eve/SpaceObject/Children/EveChildMesh.h index 2e84bf964..8a0b77ebb 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildMesh.h +++ b/trinity/Eve/SpaceObject/Children/EveChildMesh.h @@ -168,6 +168,11 @@ BLUE_CLASS( EveChildMesh ) : void SetCastShadow( bool castShadow ); void SetMinScreenSize( float minScreenSize ); + void AddOverlayEffect( EveMeshOverlayEffectPtr newOverlayEffect ); + void RemoveOverlayEffect( EveMeshOverlayEffectPtr overlayEffectToRemove ); + EveMeshOverlayEffectPtr GetOverlayEffectByName( const char* name ) const; + const PEveMeshOverlayEffectVector& GetOverlayEffects() const { return m_overlayEffects; } + Tr2GrannyAnimation* GetAnimationController() const override; void SetAnimationController( Tr2GrannyAnimation * animation ); @@ -215,6 +220,10 @@ BLUE_CLASS( EveChildMesh ) : IEveSpaceObject2::ParentData m_parentData; const PEveMeshOverlayEffectVector* m_parentOverlayEffects = nullptr; + // overlay effects owned by this child mesh, rendered underneath any inherited parent overlays + PEveMeshOverlayEffectVector m_overlayEffects; + Be::Time m_lastOverlayUpdateTime = 0; + Tr2Lod m_overlayUpdateLod = TR2_LOD_UNSPECIFIED; std::vector m_overlayMeshAreaBlocks[EveMeshOverlayEffect::TYPE_COUNT]; bool m_overlayAreaBlocksBuilt = false; void RebuildOverlayAreaBlocks(); @@ -244,6 +253,7 @@ BLUE_CLASS( EveChildMesh ) : CcpMath::Sphere m_worldBoundingSphere; // bounding sphere in world space bool m_display; + bool m_inheritOverlayEffects; bool m_isVisible; bool m_instancesVisible; bool m_castShadow; diff --git a/trinity/Eve/SpaceObject/Children/EveChildMesh_Blue.cpp b/trinity/Eve/SpaceObject/Children/EveChildMesh_Blue.cpp index de99e6f6a..1e397a76b 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildMesh_Blue.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildMesh_Blue.cpp @@ -23,6 +23,8 @@ const Be::ClassInfo* EveChildMesh::ExposeToBlue() MAP_ATTRIBUTE( "name", m_name, "", Be::READWRITE | Be::PERSIST ) MAP_ATTRIBUTE( "display", m_display, "", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "inheritOverlayEffects", m_inheritOverlayEffects, "If true, the parent space object's overlay effects (e.g. cloak) also render over this child mesh.", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "overlayEffects", m_overlayEffects, "Overlay effects owned by this child mesh. Rendered over the child's mesh areas, underneath any inherited parent overlay effects.", Be::READ | Be::PERSIST ) MAP_ATTRIBUTE( "castShadow", m_castShadow, "", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) MAP_ATTRIBUTE( "updateAnimation", m_updateAnimation, "Should the object update its animation updater every frame", Be::READWRITE | Be::PERSIST ) MAP_ATTRIBUTE( "mesh", m_mesh, "", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) From 567cf3deda5d891a6e6627b52839c1efec7d8ae8 Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Wed, 22 Jul 2026 14:57:43 +0100 Subject: [PATCH 06/12] Fixing child mesh not rendering instanced overlays correctly and just being clipped --- .../Attachments/EveMeshOverlayEffect.cpp | 27 +- .../Attachments/EveMeshOverlayEffect.h | 8 + .../Children/EveChildInstancedMeshes.cpp | 380 +++++++++++++++++- .../Children/EveChildInstancedMeshes.h | 46 +++ .../Children/EveChildInstancedMeshes_Blue.cpp | 42 ++ 5 files changed, 500 insertions(+), 3 deletions(-) diff --git a/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.cpp b/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.cpp index 28b20c565..f0e0dcb7f 100644 --- a/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.cpp +++ b/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.cpp @@ -313,11 +313,12 @@ void CollectOverlayAreaBlocks( Tr2MeshBase* mesh, std::vector +static void EmitOverlayBatchesImpl( ITriRenderBatchAccumulator* batches, const Tr2PerObjectData* perObjectData, TriBatchType batchType, - const PEveMeshOverlayEffectVector& overlayEffects, + const OverlayEffectContainer& overlayEffects, const std::vector ( &areaBlocks )[EveMeshOverlayEffect::TYPE_COUNT], const TriGeometryResLodData& lod ) { @@ -357,3 +358,25 @@ void EmitOverlayBatches( } } } + +void EmitOverlayBatches( + ITriRenderBatchAccumulator* batches, + const Tr2PerObjectData* perObjectData, + TriBatchType batchType, + const PEveMeshOverlayEffectVector& overlayEffects, + const std::vector ( &areaBlocks )[EveMeshOverlayEffect::TYPE_COUNT], + const TriGeometryResLodData& lod ) +{ + EmitOverlayBatchesImpl( batches, perObjectData, batchType, overlayEffects, areaBlocks, lod ); +} + +void EmitOverlayBatches( + ITriRenderBatchAccumulator* batches, + const Tr2PerObjectData* perObjectData, + TriBatchType batchType, + const std::vector& overlayEffects, + const std::vector ( &areaBlocks )[EveMeshOverlayEffect::TYPE_COUNT], + const TriGeometryResLodData& lod ) +{ + EmitOverlayBatchesImpl( batches, perObjectData, batchType, overlayEffects, areaBlocks, lod ); +} diff --git a/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.h b/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.h index 1073c682f..2e501f5a5 100644 --- a/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.h +++ b/trinity/Eve/SpaceObject/Attachments/EveMeshOverlayEffect.h @@ -114,4 +114,12 @@ void EmitOverlayBatches( const std::vector ( &areaBlocks )[EveMeshOverlayEffect::TYPE_COUNT], const TriGeometryResLodData& lod ); +void EmitOverlayBatches( + ITriRenderBatchAccumulator* batches, + const Tr2PerObjectData* perObjectData, + TriBatchType batchType, + const std::vector& overlayEffects, + const std::vector ( &areaBlocks )[EveMeshOverlayEffect::TYPE_COUNT], + const TriGeometryResLodData& lod ); + #endif // EveMeshOverlayEffect_H diff --git a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp index 4bc9ddbef..fdb37450e 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp @@ -3,6 +3,7 @@ #include "StdAfx.h" #include "EveChildInstancedMeshes.h" #include "./Tr2RingBuffer.h" +#include "../EveSpaceObject2.h" EveChildInstancedMeshes::EveChildInstancedMeshes( IRoot* lockobj ) @@ -67,6 +68,10 @@ void EveChildInstancedMeshes::UnregisterFromMeshManager() { m_perObjectDataHandle.owner->RemovePerObjectData( m_perObjectDataHandle ); } + if( m_perObjectDataNoClipHandle ) + { + m_perObjectDataNoClipHandle.owner->RemovePerObjectData( m_perObjectDataNoClipHandle ); + } m_allRegistered = false; } @@ -187,6 +192,11 @@ void EveChildInstancedMeshes::UpdateVisibility( const EveUpdateContext& updateCo void EveChildInstancedMeshes::GetRenderables( std::vector& renderables ) { + // only overlay draws render here; the base hull goes through EveInstancedMeshManager + if( m_hasUpdated && ( ( m_parentOverlayEffects != nullptr && AnyMeshInheritsOverlayEffects() ) || HasAnyOwnOverlayEffects() ) ) + { + renderables.push_back( this ); + } } bool EveChildInstancedMeshes::GetBoundingSphere( Vector4& sphere, BoundingSphereQuery query ) const @@ -197,6 +207,16 @@ bool EveChildInstancedMeshes::GetBoundingSphere( Vector4& sphere, BoundingSphere void EveChildInstancedMeshes::UpdateSyncronous( const EveUpdateContext& updateContext, const EveChildUpdateParams& params ) { m_worldTransform = params.localToWorldTransform; + + // overlay effect curves must update on the game thread; they may reference attributes that are not thread safe + Be::Time time = updateContext.GetTime(); + for( Mesh& mesh : m_meshes ) + { + for( EveMeshOverlayEffectPtr& overlay : mesh.ownOverlayEffects ) + { + overlay->Update( time, time ); + } + } } void EveChildInstancedMeshes::UpdateAsyncronous( const EveUpdateContext& updateContext, const EveChildUpdateParams& params ) @@ -237,6 +257,12 @@ void EveChildInstancedMeshes::UpdateAsyncronous( const EveUpdateContext& updateC m_perObjectData.customData = vsData.customData; std::copy( std::begin( psData.shLightingCoefficients ), std::end( psData.shLightingCoefficients ), std::begin( m_perObjectData.shLighting ) ); + m_perObjectDataNoClip = m_perObjectData; + m_perObjectDataNoClip.clipRadiusSq = 0.f; + m_perObjectDataNoClip.clipRadius2Sq = 0.f; + m_perObjectDataNoClip.clipSphereFactor = 0.f; + m_perObjectDataNoClip.clipSphereFactor2 = 0.f; + float worldScale = std::sqrtf( std::max( { LengthSq( Vector3( m_worldTransform._11, m_worldTransform._12, m_worldTransform._13 ) ), LengthSq( Vector3( m_worldTransform._21, m_worldTransform._22, m_worldTransform._23 ) ), LengthSq( Vector3( m_worldTransform._31, m_worldTransform._32, m_worldTransform._33 ) ) } ) ); @@ -278,6 +304,23 @@ void EveChildInstancedMeshes::UpdateAsyncronous( const EveUpdateContext& updateC } } + m_parentOverlayEffects = nullptr; + if( params.spaceObjectParent ) + { + if( EveSpaceObject2Ptr spaceObject2Parent = BlueCastPtr( params.spaceObjectParent ) ) + { + const PEveMeshOverlayEffectVector& parentOverlays = spaceObject2Parent->GetOverlayEffects(); + if( !parentOverlays.empty() ) + { + m_parentOverlayEffects = &parentOverlays; + } + } + } + if( ( m_parentOverlayEffects != nullptr && AnyMeshInheritsOverlayEffects() ) || HasAnyOwnOverlayEffects() ) + { + UpdateOverlayInstanceData( vsData, psData ); + } + m_hasUpdated = true; USE_MAIN_THREAD_RENDER_CONTEXT(); @@ -517,6 +560,11 @@ void EveChildInstancedMeshes::AddMeshesToManager( EveInstancedMeshManager& manag continue; } + if( !mesh.inheritOverlayEffects && !m_perObjectDataNoClipHandle ) + { + manager.AddPerObjectData( m_perObjectDataNoClipHandle, &m_perObjectDataNoClip ); + } + if( !mesh.sphereHandle ) { manager.AddBoundingSphereGroup( mesh.sphereHandle, mesh.worldBoundingSphere, mesh.flags, mesh.instanceSpheres.data(), static_cast( mesh.instanceSpheres.size() ) ); @@ -541,7 +589,7 @@ void EveChildInstancedMeshes::AddMeshesToManager( EveInstancedMeshManager& manag area.areaCount, area.effect, area.effectHash, - m_perObjectDataHandle, + mesh.inheritOverlayEffects ? m_perObjectDataHandle : m_perObjectDataNoClipHandle, mesh.sphereHandle, mesh.instances.data(), uint32_t( mesh.instances.size() ), @@ -690,6 +738,46 @@ BluePy EveChildInstancedMeshes::SetMeshDisplay( uint32_t meshId, bool display ) return BluePy( Py_None, true ); } +BluePy EveChildInstancedMeshes::GetMeshInheritOverlayEffects( uint32_t meshId ) const +{ + if( meshId >= m_meshes.size() ) + { + PyErr_SetString( PyExc_IndexError, "Mesh index out of range" ); + return {}; + } + if( m_meshes[meshId].inheritOverlayEffects ) + { + return BluePy( Py_True, true ); + } + else + { + return BluePy( Py_False, true ); + } +} + +BluePy EveChildInstancedMeshes::SetMeshInheritOverlayEffects( uint32_t meshId, bool inherit ) +{ + if( meshId >= m_meshes.size() ) + { + PyErr_SetString( PyExc_IndexError, "Mesh index out of range" ); + return {}; + } + auto& mesh = m_meshes[meshId]; + if( mesh.inheritOverlayEffects != inherit ) + { + mesh.inheritOverlayEffects = inherit; + m_allRegistered = false; + for( auto& area : mesh.areas ) + { + if( area.meshGroupHandle ) + { + area.meshGroupHandle.owner->RemoveMeshGroup( area.meshGroupHandle ); + } + } + } + return BluePy( Py_None, true ); +} + void EveChildInstancedMeshes::ReleaseResources( TriStorage s ) { if( ( s & TRISTORAGE_MANAGEDMEMORY ) != 0 ) @@ -702,3 +790,293 @@ bool EveChildInstancedMeshes::OnPrepareResources() { return true; } + +void EveChildInstancedMeshes::AddMeshOverlayEffect( uint32_t meshId, EveMeshOverlayEffect* overlayEffect ) +{ + if( meshId >= m_meshes.size() || overlayEffect == nullptr ) + { + return; + } + m_meshes[meshId].ownOverlayEffects.push_back( overlayEffect ); +} + +void EveChildInstancedMeshes::RemoveMeshOverlayEffect( uint32_t meshId, EveMeshOverlayEffect* overlayEffect ) +{ + if( meshId >= m_meshes.size() ) + { + return; + } + auto& overlays = m_meshes[meshId].ownOverlayEffects; + overlays.erase( std::remove( overlays.begin(), overlays.end(), EveMeshOverlayEffectPtr( overlayEffect ) ), overlays.end() ); +} + +void EveChildInstancedMeshes::ClearMeshOverlayEffects( uint32_t meshId ) +{ + if( meshId >= m_meshes.size() ) + { + return; + } + m_meshes[meshId].ownOverlayEffects.clear(); +} + +uint32_t EveChildInstancedMeshes::GetMeshOverlayEffectCount( uint32_t meshId ) const +{ + if( meshId >= m_meshes.size() ) + { + return 0; + } + return uint32_t( m_meshes[meshId].ownOverlayEffects.size() ); +} + +bool EveChildInstancedMeshes::HasAnyOwnOverlayEffects() const +{ + for( const Mesh& mesh : m_meshes ) + { + if( !mesh.ownOverlayEffects.empty() ) + { + return true; + } + } + return false; +} + +bool EveChildInstancedMeshes::AnyMeshInheritsOverlayEffects() const +{ + for( const Mesh& mesh : m_meshes ) + { + if( mesh.inheritOverlayEffects ) + { + return true; + } + } + return false; +} + +uint32_t EveChildInstancedMeshes::OverlayInstancePod::GetPerObjectDataSize( Tr2RenderContextEnum::ShaderType shaderType ) const +{ + if( shaderType == Tr2RenderContextEnum::PIXEL_SHADER ) + { + return sizeof( psData ); + } + return sizeof( vsData ); +} + +void EveChildInstancedMeshes::OverlayInstancePod::UpdatePerObjectBuffer( Tr2RenderContextEnum::ShaderType shaderType, uint32_t size, void* data ) +{ + if( shaderType == Tr2RenderContextEnum::PIXEL_SHADER ) + { + memcpy( data, &psData, sizeof( psData ) ); + } + else + { + memcpy( data, &vsData, sizeof( vsData ) ); + } +} + +void EveChildInstancedMeshes::UpdateOverlayInstanceData( const EveSpaceObjectVSData& parentVsData, const EveSpaceObjectPSData& parentPsData ) +{ + // worldTransformLast still holds the previous frame's transposed world transform here + Matrix prevWorldTransform = Transpose( m_perObjectData.worldTransformLast ); + + for( Mesh& mesh : m_meshes ) + { + if( mesh.instances.empty() ) + { + continue; + } + if( !mesh.overlayPods ) + { + // sized once; the pods own device resources so they must stay at stable addresses + mesh.overlayPods = std::make_unique>( mesh.instances.size() ); + } + + for( size_t i = 0; i < mesh.instances.size(); ++i ) + { + const auto& wt = mesh.instances[i].worldTransform; + OverlayInstancePod& pod = ( *mesh.overlayPods )[i]; + + Matrix local = IdentityMatrix(); + local._11 = wt[0].x; local._12 = wt[1].x; local._13 = wt[2].x; + local._21 = wt[0].y; local._22 = wt[1].y; local._23 = wt[2].y; + local._31 = wt[0].z; local._32 = wt[1].z; local._33 = wt[2].z; + local._41 = wt[0].w; local._42 = wt[1].w; local._43 = wt[2].w; + + Vector3 translation( wt[0].w, wt[1].w, wt[2].w ); + + Matrix worldTransform = Transpose( local * m_worldTransform ); + Matrix worldTransformLast = Transpose( local * prevWorldTransform ); + Matrix invWorldTransform = Inverse( worldTransform ); + + pod.vsData = parentVsData; + pod.vsData.worldTransform = worldTransform; + pod.vsData.worldTransformLast = worldTransformLast; + pod.vsData.invWorldTransform = invWorldTransform; + // need to move the clipdata inversely of the translation of the instance + pod.vsData.clipData = Vector4( pod.vsData.clipData.GetXYZ() - translation, pod.vsData.clipData.w ); + + pod.psData = parentPsData; + pod.psData.worldTransform = worldTransform; + pod.psData.worldTransformLast = worldTransformLast; + pod.psData.invWorldTransform = invWorldTransform; + pod.psData.clipSphereCenter = pod.psData.clipSphereCenter - translation; + + if( !mesh.inheritOverlayEffects ) + { + // opted out of the parent's overlay: also neutralize the inherited clip sphere + pod.vsData.clipData.w = 0.f; + pod.psData.clipRadiusSq = 0.f; + pod.psData.clipRadius2Sq = 0.f; + pod.psData.clipSphereFactor = 0.f; + pod.psData.clipSphereFactor2 = 0.f; + } + + pod.vsBuffer.InvalidateBufferData(); + pod.psBuffer.InvalidateBufferData(); + } + } +} + +void EveChildInstancedMeshes::RebuildOverlayAreaBlocks( Mesh& mesh ) +{ + for( int i = 0; i < EveMeshOverlayEffect::TYPE_COUNT; ++i ) + { + mesh.overlayAreaBlocks[i].clear(); + } + + for( const MeshArea& area : mesh.areas ) + { + if( area.batchType == TRIBATCHTYPE_OPAQUE || area.batchType == TRIBATCHTYPE_TRANSPARENT || area.batchType == TRIBATCHTYPE_DECAL ) + { + mesh.overlayAreaBlocks[EveMeshOverlayEffect::TYPE_ALL].emplace_back( area.areaIndex, area.areaCount ); + } + if( area.batchType == TRIBATCHTYPE_OPAQUE ) + { + mesh.overlayAreaBlocks[EveMeshOverlayEffect::TYPE_OPAQUEONLY].emplace_back( area.areaIndex, area.areaCount ); + } + } + + for( int i = 0; i < EveMeshOverlayEffect::TYPE_COUNT; ++i ) + { + TriRenderBatchAreaBlock::Optimize( mesh.overlayAreaBlocks[i] ); + } + mesh.overlayAreaBlocksBuilt = true; +} + +bool EveChildInstancedMeshes::HasTransparentBatches() +{ + if( m_parentOverlayEffects != nullptr && AnyMeshInheritsOverlayEffects() ) + { + for( auto it = m_parentOverlayEffects->begin(); it != m_parentOverlayEffects->end(); ++it ) + { + if( ( *it )->HasTransparentArea() ) + { + return true; + } + } + } + for( const Mesh& mesh : m_meshes ) + { + for( const EveMeshOverlayEffectPtr& overlay : mesh.ownOverlayEffects ) + { + if( overlay->HasTransparentArea() ) + { + return true; + } + } + } + return false; +} + +float EveChildInstancedMeshes::GetSortValue() +{ + Vector3 d = Tr2Renderer::GetViewPosition() - m_worldTransform.GetTranslation(); + return Length( d ); +} + +Tr2PerObjectData* EveChildInstancedMeshes::GetPerObjectData( ITriRenderBatchAccumulator* accumulator ) +{ + if( ( m_parentOverlayEffects == nullptr || !AnyMeshInheritsOverlayEffects() ) && !HasAnyOwnOverlayEffects() ) + { + return nullptr; + } + + Tr2PerObjectData* firstPod = nullptr; + for( Mesh& mesh : m_meshes ) + { + if( !mesh.overlayPods ) + { + continue; + } + for( OverlayInstancePod& pod : *mesh.overlayPods ) + { + pod.framePod = nullptr; + auto* perObjectData = accumulator->Allocate>(); + if( perObjectData == nullptr ) + { + return firstPod; + } + perObjectData->Initialize( &pod, &pod.vsBuffer, &pod.psBuffer ); + pod.framePod = perObjectData; + if( firstPod == nullptr ) + { + firstPod = perObjectData; + } + } + } + return firstPod; +} + +void EveChildInstancedMeshes::GetBatches( ITriRenderBatchAccumulator* batches, TriBatchType batchType, const Tr2PerObjectData* perObjectData, Tr2RenderReason reason ) +{ + if( !m_hasUpdated ) + { + return; + } + + for( Mesh& mesh : m_meshes ) + { + const bool hasOwnOverlays = !mesh.ownOverlayEffects.empty(); + const bool hasInheritedOverlays = m_parentOverlayEffects != nullptr && mesh.inheritOverlayEffects; + if( !hasInheritedOverlays && !hasOwnOverlays ) + { + continue; + } + if( !mesh.display || !mesh.overlayPods ) + { + continue; + } + if( !mesh.geometry || !mesh.geometry->IsGood() ) + { + continue; + } + if( !mesh.overlayAreaBlocksBuilt ) + { + RebuildOverlayAreaBlocks( mesh ); + } + + float screenSize = m_lastCameraFrustum.GetPixelSizeAccross( mesh.worldBoundingSphere ); + auto lod = mesh.geometry->GetMeshLod( mesh.meshIndex, screenSize ); + if( !lod || !lod->m_allocationsValid ) + { + continue; + } + + for( OverlayInstancePod& pod : *mesh.overlayPods ) + { + if( pod.framePod == nullptr ) + { + continue; + } + // own effects are emitted before the inherited ones so the parent's overlays + // (e.g. cloak) draw on top of this mesh's own overlays + if( hasOwnOverlays ) + { + EmitOverlayBatches( batches, pod.framePod, batchType, mesh.ownOverlayEffects, mesh.overlayAreaBlocks, *lod ); + } + if( hasInheritedOverlays ) + { + EmitOverlayBatches( batches, pod.framePod, batchType, *m_parentOverlayEffects, mesh.overlayAreaBlocks, *lod ); + } + } + } +} diff --git a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h index 5a25e441d..709ca4794 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h +++ b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h @@ -4,6 +4,9 @@ #include "IEveSpaceObjectChild.h" #include "../../EveInstancedMeshManager.h" +#include "ITr2Renderable.h" +#include "Tr2PersistentPerObjectData.h" +#include "../Attachments/EveMeshOverlayEffect.h" BLUE_DECLARE( TriGeometryRes ); BLUE_DECLARE( Tr2Effect ); @@ -16,6 +19,7 @@ BLUE_CLASS( EveChildInstancedMeshes ) : public IEveShadowCaster, public IEveInstanceMeshProvider, public ITr2DebugRenderable, + public ITr2Renderable, public Tr2DeviceResource { public: @@ -62,6 +66,13 @@ BLUE_CLASS( EveChildInstancedMeshes ) : void GetDebugOptions( Tr2DebugRendererOptions & options ) override; void RenderDebugInfo( ITr2DebugRenderer2 & renderer ) override; + ////////////////////////////////////////////////////////////////////////////////////// + // ITr2Renderable + void GetBatches( ITriRenderBatchAccumulator * batches, TriBatchType batchType, const Tr2PerObjectData* perObjectData, Tr2RenderReason reason = TR2RENDERREASON_NORMAL ) override; + bool HasTransparentBatches() override; + float GetSortValue() override; + Tr2PerObjectData* GetPerObjectData( ITriRenderBatchAccumulator * accumulator ) override; + struct MeshArea { Tr2EffectPtr effect = nullptr; @@ -90,8 +101,28 @@ BLUE_CLASS( EveChildInstancedMeshes ) : BluePy GetAreaInfo( uint32_t meshId, uint32_t areaId ) const; BluePy GetMeshDisplay( uint32_t meshId ) const; BluePy SetMeshDisplay( uint32_t meshId, bool display ); + BluePy GetMeshInheritOverlayEffects( uint32_t meshId ) const; + BluePy SetMeshInheritOverlayEffects( uint32_t meshId, bool inherit ); + + void AddMeshOverlayEffect( uint32_t meshId, EveMeshOverlayEffect* overlayEffect ); + void RemoveMeshOverlayEffect( uint32_t meshId, EveMeshOverlayEffect* overlayEffect ); + void ClearMeshOverlayEffects( uint32_t meshId ); + uint32_t GetMeshOverlayEffectCount( uint32_t meshId ) const; private: + // per-instance constant buffers for overlay draws + struct OverlayInstancePod + { + EveSpaceObjectVSData vsData = {}; + EveSpaceObjectPSData psData = {}; + Tr2PersistentPerObjectData vsBuffer; + Tr2PersistentPerObjectData psBuffer; + Tr2PerObjectData* framePod = nullptr; // valid only within the current frame's batch gathering + + uint32_t GetPerObjectDataSize( Tr2RenderContextEnum::ShaderType shaderType ) const; + void UpdatePerObjectBuffer( Tr2RenderContextEnum::ShaderType shaderType, uint32_t size, void* data ); + }; + struct Mesh { std::string geometryPath; @@ -127,20 +158,35 @@ BLUE_CLASS( EveChildInstancedMeshes ) : }; std::vector rtMeshes; + std::unique_ptr> overlayPods; + std::vector overlayAreaBlocks[EveMeshOverlayEffect::TYPE_COUNT]; + bool overlayAreaBlocksBuilt = false; + std::vector ownOverlayEffects; + bool display = true; + bool inheritOverlayEffects = true; }; void ReleaseCachedData( BlueAsyncRes * p ) override; void RebuildCachedData( BlueAsyncRes * p ) override; void UnregisterFromMeshManager(); + void UpdateOverlayInstanceData( const EveSpaceObjectVSData& parentVsData, const EveSpaceObjectPSData& parentPsData ); + static void RebuildOverlayAreaBlocks( Mesh& mesh ); + bool HasAnyOwnOverlayEffects() const; + bool AnyMeshInheritsOverlayEffects() const; + void ReleaseResources( TriStorage s ) override; bool OnPrepareResources() override; BlueSharedString m_name; Matrix m_worldTransform = IdentityMatrix(); EveSpacePerObjectData m_perObjectData; + // m_perObjectData with the clip sphere neutralized, for meshes that opt out of the parent's overlays + EveSpacePerObjectData m_perObjectDataNoClip; + const PEveMeshOverlayEffectVector* m_parentOverlayEffects = nullptr; EveInstancedMeshManager::PerObjectDataHandle m_perObjectDataHandle; + EveInstancedMeshManager::PerObjectDataHandle m_perObjectDataNoClipHandle; std::vector m_meshes; TriFrustum m_lastCameraFrustum; mutable Tr2ConstantBufferAL m_rtPerObjectData; diff --git a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes_Blue.cpp b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes_Blue.cpp index 59c4d83b7..cf88feb78 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes_Blue.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes_Blue.cpp @@ -14,6 +14,7 @@ const Be::ClassInfo* EveChildInstancedMeshes::ExposeToBlue() MAP_INTERFACE( IEveSpaceObjectChild ) MAP_INTERFACE( EveEntity ) MAP_INTERFACE( IEveShadowCaster ) + MAP_INTERFACE( ITr2Renderable ) MAP_ATTRIBUTE( "name", m_name, "", Be::READWRITE | Be::PERSIST ) @@ -58,6 +59,47 @@ const Be::ClassInfo* EveChildInstancedMeshes::ExposeToBlue() ":param meshId: Index of the mesh to modify\n" ":param display: True to render the mesh, False to hide it\n" ":rtype: None" ) + MAP_METHOD_AND_WRAP( + "GetMeshInheritOverlayEffects", + GetMeshInheritOverlayEffects, + "Returns True if the parent space object's overlay effects (e.g. cloak) also render over the given mesh\n\n" + ":param meshId: Index of the mesh to query\n" + ":rtype: bool" ) + MAP_METHOD_AND_WRAP( + "SetMeshInheritOverlayEffects", + SetMeshInheritOverlayEffects, + "Sets whether the parent space object's overlay effects (e.g. cloak) also render over the given mesh.\n" + "When False the mesh also ignores the parent's clip sphere so it stays visible while the rest of the ship dissolves.\n\n" + ":param meshId: Index of the mesh to modify\n" + ":param inherit: True to inherit the parent's overlay effects, False to opt out\n" + ":rtype: None" ) + MAP_METHOD_AND_WRAP( + "AddMeshOverlayEffect", + AddMeshOverlayEffect, + "Adds an overlay effect owned by the given mesh. Rendered over the mesh's areas,\n" + "underneath any overlay effects inherited from the parent space object.\n\n" + ":param meshId: Index of the mesh to modify\n" + ":param overlayEffect: The EveMeshOverlayEffect to add\n" + ":rtype: None" ) + MAP_METHOD_AND_WRAP( + "RemoveMeshOverlayEffect", + RemoveMeshOverlayEffect, + "Removes a previously added overlay effect from the given mesh.\n\n" + ":param meshId: Index of the mesh to modify\n" + ":param overlayEffect: The EveMeshOverlayEffect to remove\n" + ":rtype: None" ) + MAP_METHOD_AND_WRAP( + "ClearMeshOverlayEffects", + ClearMeshOverlayEffects, + "Removes all overlay effects owned by the given mesh.\n\n" + ":param meshId: Index of the mesh to modify\n" + ":rtype: None" ) + MAP_METHOD_AND_WRAP( + "GetMeshOverlayEffectCount", + GetMeshOverlayEffectCount, + "Returns the number of overlay effects owned by the given mesh.\n\n" + ":param meshId: Index of the mesh to query\n" + ":rtype: int" ) EXPOSURE_END() } \ No newline at end of file From cdb9fe21c145df00f5697adb1f128d8f9c2d57f0 Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Wed, 22 Jul 2026 15:13:42 +0100 Subject: [PATCH 07/12] Added missing LOD considerations for overlays in EveChildInstancedMeshes Noticed in EveInstancedMeshManager::PerformFrustumCulling we were doing a lod check to cull objects that we was being missed in the EveChildInstancedMeshes::GetRenderables path --- .../Children/EveChildInstancedMeshes.cpp | 25 +++++++++++++------ .../Children/EveChildInstancedMeshes.h | 1 + 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp index fdb37450e..cc5430b45 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp @@ -188,6 +188,7 @@ void EveChildInstancedMeshes::SetName( const char* name ) void EveChildInstancedMeshes::UpdateVisibility( const EveUpdateContext& updateContext, const Matrix& parentTransform, Tr2Lod parentLod ) { m_lastCameraFrustum = updateContext.GetFrustum(); + m_lastInvLodFactor = updateContext.GetInvLodFactor(); } void EveChildInstancedMeshes::GetRenderables( std::vector& renderables ) @@ -1054,19 +1055,27 @@ void EveChildInstancedMeshes::GetBatches( ITriRenderBatchAccumulator* batches, T RebuildOverlayAreaBlocks( mesh ); } - float screenSize = m_lastCameraFrustum.GetPixelSizeAccross( mesh.worldBoundingSphere ); - auto lod = mesh.geometry->GetMeshLod( mesh.meshIndex, screenSize ); - if( !lod || !lod->m_allocationsValid ) - { - continue; - } - - for( OverlayInstancePod& pod : *mesh.overlayPods ) + for( size_t i = 0; i < mesh.overlayPods->size(); ++i ) { + OverlayInstancePod& pod = ( *mesh.overlayPods )[i]; if( pod.framePod == nullptr ) { continue; } + + // per-instance LOD selection matching the base hull in EveInstancedMeshManager + const CcpMath::Sphere& sphere = mesh.instanceSpheres[i]; + if( !m_lastCameraFrustum.IsSphereVisible( sphere.center, sphere.radius ) ) + { + continue; + } + float screenSize = m_lastCameraFrustum.GetPixelSizeAccrossEst( sphere.center, sphere.radius ) * m_lastInvLodFactor; + auto lod = mesh.geometry->GetMeshLod( mesh.meshIndex, screenSize ); + if( !lod || !lod->m_allocationsValid ) + { + continue; + } + // own effects are emitted before the inherited ones so the parent's overlays // (e.g. cloak) draw on top of this mesh's own overlays if( hasOwnOverlays ) diff --git a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h index 709ca4794..abcde286c 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h +++ b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h @@ -189,6 +189,7 @@ BLUE_CLASS( EveChildInstancedMeshes ) : EveInstancedMeshManager::PerObjectDataHandle m_perObjectDataNoClipHandle; std::vector m_meshes; TriFrustum m_lastCameraFrustum; + float m_lastInvLodFactor = 1.0f; mutable Tr2ConstantBufferAL m_rtPerObjectData; bool m_allRegistered = false; From 181579a360a14afae63a26ac3c1721c915a6e54a Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Wed, 22 Jul 2026 15:34:54 +0100 Subject: [PATCH 08/12] Stopped regeneration of instance data when switching overlay on child mesh --- .../Children/EveChildInstancedMeshes.cpp | 15 ++++++++++++++- .../Children/EveChildInstancedMeshes.h | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp index cc5430b45..3937e49e4 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp @@ -853,6 +853,11 @@ bool EveChildInstancedMeshes::AnyMeshInheritsOverlayEffects() const return false; } +bool EveChildInstancedMeshes::MeshHasActiveOverlayEffects( const Mesh& mesh ) const +{ + return !mesh.ownOverlayEffects.empty() || ( m_parentOverlayEffects != nullptr && mesh.inheritOverlayEffects ); +} + uint32_t EveChildInstancedMeshes::OverlayInstancePod::GetPerObjectDataSize( Tr2RenderContextEnum::ShaderType shaderType ) const { if( shaderType == Tr2RenderContextEnum::PIXEL_SHADER ) @@ -881,7 +886,7 @@ void EveChildInstancedMeshes::UpdateOverlayInstanceData( const EveSpaceObjectVSD for( Mesh& mesh : m_meshes ) { - if( mesh.instances.empty() ) + if( mesh.instances.empty() || !mesh.display || !MeshHasActiveOverlayEffects( mesh ) ) { continue; } @@ -1008,6 +1013,14 @@ Tr2PerObjectData* EveChildInstancedMeshes::GetPerObjectData( ITriRenderBatchAccu { continue; } + if( !mesh.display || !MeshHasActiveOverlayEffects( mesh ) ) + { + for( OverlayInstancePod& pod : *mesh.overlayPods ) + { + pod.framePod = nullptr; + } + continue; + } for( OverlayInstancePod& pod : *mesh.overlayPods ) { pod.framePod = nullptr; diff --git a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h index abcde286c..95d0fe7a6 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h +++ b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h @@ -175,6 +175,7 @@ BLUE_CLASS( EveChildInstancedMeshes ) : static void RebuildOverlayAreaBlocks( Mesh& mesh ); bool HasAnyOwnOverlayEffects() const; bool AnyMeshInheritsOverlayEffects() const; + bool MeshHasActiveOverlayEffects( const Mesh& mesh ) const; void ReleaseResources( TriStorage s ) override; bool OnPrepareResources() override; From 4c442b2a9d91494c5d963e4220b0d1ff3521e57c Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Thu, 23 Jul 2026 11:24:23 +0100 Subject: [PATCH 09/12] Fixed clip sphere clipping objects not supporting instanced rotation correctly --- .../Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp index 3937e49e4..e5a52971b 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp @@ -907,24 +907,23 @@ void EveChildInstancedMeshes::UpdateOverlayInstanceData( const EveSpaceObjectVSD local._31 = wt[0].z; local._32 = wt[1].z; local._33 = wt[2].z; local._41 = wt[0].w; local._42 = wt[1].w; local._43 = wt[2].w; - Vector3 translation( wt[0].w, wt[1].w, wt[2].w ); - Matrix worldTransform = Transpose( local * m_worldTransform ); Matrix worldTransformLast = Transpose( local * prevWorldTransform ); Matrix invWorldTransform = Inverse( worldTransform ); + Matrix invLocal = Inverse( local ); pod.vsData = parentVsData; pod.vsData.worldTransform = worldTransform; pod.vsData.worldTransformLast = worldTransformLast; pod.vsData.invWorldTransform = invWorldTransform; - // need to move the clipdata inversely of the translation of the instance - pod.vsData.clipData = Vector4( pod.vsData.clipData.GetXYZ() - translation, pod.vsData.clipData.w ); + // need to move the clipdata inversely of the transform of the instance + pod.vsData.clipData = Vector4( TransformCoord( pod.vsData.clipData.GetXYZ(), invLocal ), pod.vsData.clipData.w ); pod.psData = parentPsData; pod.psData.worldTransform = worldTransform; pod.psData.worldTransformLast = worldTransformLast; pod.psData.invWorldTransform = invWorldTransform; - pod.psData.clipSphereCenter = pod.psData.clipSphereCenter - translation; + pod.psData.clipSphereCenter = TransformCoord( pod.psData.clipSphereCenter, invLocal ); if( !mesh.inheritOverlayEffects ) { From 2167b9f8bf3cff81bceb4dc9b1266099f5d2072c Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Fri, 24 Jul 2026 11:01:49 +0100 Subject: [PATCH 10/12] Changed new child functions to return correct blue return types --- .../Children/EveChildInstancedMeshes.cpp | 42 ++++++++++++++----- .../Children/EveChildInstancedMeshes.h | 8 ++-- .../Children/EveChildInstancedMeshes_Blue.cpp | 4 +- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp index e5a52971b..a2555be5a 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp @@ -792,41 +792,57 @@ bool EveChildInstancedMeshes::OnPrepareResources() return true; } -void EveChildInstancedMeshes::AddMeshOverlayEffect( uint32_t meshId, EveMeshOverlayEffect* overlayEffect ) +BluePy EveChildInstancedMeshes::AddMeshOverlayEffect( uint32_t meshId, EveMeshOverlayEffect* overlayEffect ) { - if( meshId >= m_meshes.size() || overlayEffect == nullptr ) + if( meshId >= m_meshes.size() ) { - return; + PyErr_SetString( PyExc_IndexError, "Mesh index out of range" ); + return {}; + } + if( overlayEffect == nullptr ) + { + PyErr_SetString( PyExc_TypeError, "overlayEffect must not be None" ); + return {}; } m_meshes[meshId].ownOverlayEffects.push_back( overlayEffect ); + return BluePy( Py_None, true ); } -void EveChildInstancedMeshes::RemoveMeshOverlayEffect( uint32_t meshId, EveMeshOverlayEffect* overlayEffect ) +BluePy EveChildInstancedMeshes::RemoveMeshOverlayEffect( uint32_t meshId, EveMeshOverlayEffect* overlayEffect ) { if( meshId >= m_meshes.size() ) { - return; + PyErr_SetString( PyExc_IndexError, "Mesh index out of range" ); + return {}; } auto& overlays = m_meshes[meshId].ownOverlayEffects; - overlays.erase( std::remove( overlays.begin(), overlays.end(), EveMeshOverlayEffectPtr( overlayEffect ) ), overlays.end() ); + auto it = std::find( overlays.begin(), overlays.end(), EveMeshOverlayEffectPtr( overlayEffect ) ); + if( it != overlays.end() ) + { + overlays.erase( it ); + } + return BluePy( Py_None, true ); } -void EveChildInstancedMeshes::ClearMeshOverlayEffects( uint32_t meshId ) +BluePy EveChildInstancedMeshes::ClearMeshOverlayEffects( uint32_t meshId ) { if( meshId >= m_meshes.size() ) { - return; + PyErr_SetString( PyExc_IndexError, "Mesh index out of range" ); + return {}; } m_meshes[meshId].ownOverlayEffects.clear(); + return BluePy( Py_None, true ); } -uint32_t EveChildInstancedMeshes::GetMeshOverlayEffectCount( uint32_t meshId ) const +BluePy EveChildInstancedMeshes::GetMeshOverlayEffectCount( uint32_t meshId ) const { if( meshId >= m_meshes.size() ) { - return 0; + PyErr_SetString( PyExc_IndexError, "Mesh index out of range" ); + return {}; } - return uint32_t( m_meshes[meshId].ownOverlayEffects.size() ); + return BluePy( ToPython( uint32_t( m_meshes[meshId].ownOverlayEffects.size() ) ) ); } bool EveChildInstancedMeshes::HasAnyOwnOverlayEffects() const @@ -1058,6 +1074,10 @@ void EveChildInstancedMeshes::GetBatches( ITriRenderBatchAccumulator* batches, T { continue; } + if( reason == TR2RENDERREASON_REFLECTION && !EntityComponents::ShouldReflect( mesh.reflectionMode ) ) + { + continue; + } if( !mesh.geometry || !mesh.geometry->IsGood() ) { continue; diff --git a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h index 95d0fe7a6..07a0190ca 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h +++ b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h @@ -104,10 +104,10 @@ BLUE_CLASS( EveChildInstancedMeshes ) : BluePy GetMeshInheritOverlayEffects( uint32_t meshId ) const; BluePy SetMeshInheritOverlayEffects( uint32_t meshId, bool inherit ); - void AddMeshOverlayEffect( uint32_t meshId, EveMeshOverlayEffect* overlayEffect ); - void RemoveMeshOverlayEffect( uint32_t meshId, EveMeshOverlayEffect* overlayEffect ); - void ClearMeshOverlayEffects( uint32_t meshId ); - uint32_t GetMeshOverlayEffectCount( uint32_t meshId ) const; + BluePy AddMeshOverlayEffect( uint32_t meshId, EveMeshOverlayEffect* overlayEffect ); + BluePy RemoveMeshOverlayEffect( uint32_t meshId, EveMeshOverlayEffect* overlayEffect ); + BluePy ClearMeshOverlayEffects( uint32_t meshId ); + BluePy GetMeshOverlayEffectCount( uint32_t meshId ) const; private: // per-instance constant buffers for overlay draws diff --git a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes_Blue.cpp b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes_Blue.cpp index cf88feb78..5ecc391e8 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes_Blue.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes_Blue.cpp @@ -84,7 +84,7 @@ const Be::ClassInfo* EveChildInstancedMeshes::ExposeToBlue() MAP_METHOD_AND_WRAP( "RemoveMeshOverlayEffect", RemoveMeshOverlayEffect, - "Removes a previously added overlay effect from the given mesh.\n\n" + "Removes the first matching overlay effect previously added to the given mesh.\n\n" ":param meshId: Index of the mesh to modify\n" ":param overlayEffect: The EveMeshOverlayEffect to remove\n" ":rtype: None" ) @@ -102,4 +102,4 @@ const Be::ClassInfo* EveChildInstancedMeshes::ExposeToBlue() ":rtype: int" ) EXPOSURE_END() -} \ No newline at end of file +} From 80b4e12abf46eae74e08f97a1aeb09502acbed35 Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Fri, 24 Jul 2026 15:34:31 +0100 Subject: [PATCH 11/12] Fixing skinned shader on child mesh with no bones defined --- .../Eve/SpaceObject/Children/EveChildMesh.cpp | 37 ++++++++++++++++--- .../Eve/SpaceObject/Children/EveChildMesh.h | 3 ++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp b/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp index e5cf33284..f9efc3379 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp @@ -894,11 +894,11 @@ Tr2PerObjectData* EveChildMesh::GetPerObjectData( ITriRenderBatchAccumulator* ac } } } - - auto [bones, boneCount] = GetBoneTransforms(); - m_vsData.boneOffsets[2] = uint32_t( boneCount ); - m_boneOffsets.UploadTransforms( Tr2RingBuffer::GetInstance(), reinterpret_cast( bones ), uint32_t( boneCount ) ); } + + auto [bones, boneCount] = GetBoneTransforms(); + m_vsData.boneOffsets[2] = uint32_t( boneCount ); + m_boneOffsets.UploadTransforms( Tr2RingBuffer::GetInstance(), reinterpret_cast( bones ), uint32_t( boneCount ) ); m_vsData.boneOffsets[0] = m_boneOffsets.GetCurrentFrameOffset(); m_vsData.boneOffsets[1] = m_boneOffsets.GetPreviousFrameOffset(); @@ -1439,7 +1439,7 @@ std::pair EveChildMesh::GetBoneTransforms() const if( !m_animationUpdater || !m_animationUpdater->IsInitialized() ) { - return std::make_pair( nullptr, 0 ); + return GetRestPoseBoneTransforms(); } auto accumulatedTransforms = m_animationUpdater->GetAnimationTransforms(); @@ -1453,7 +1453,32 @@ std::pair EveChildMesh::GetBoneTransforms() const { return m_meshBinding->GetBoneTransforms(); } - return std::make_pair( nullptr, 0 ); + return GetRestPoseBoneTransforms(); +} + +std::pair EveChildMesh::GetRestPoseBoneTransforms() const +{ + if( !m_mesh || !m_mesh->GetGeometryResource() ) + { + return std::make_pair( nullptr, 0 ); + } + + // Skinned shaders without an animation get rest-pose (identity) skin matrices, sized to the + // geometry's bone bindings. Unskinned geometry drawn with a skinned shader (e.g. a SOF hull + // flagged isSkinned) has no bindings and no blend indices in the vertex stream, so every + // vertex reads bone 0 - a single identity matrix covers it. + size_t boneCount = 1; + auto cmfData = m_mesh->GetGeometryResource()->GetCMFData(); + auto meshIndex = m_mesh->GetMeshIndex(); + if( cmfData && meshIndex < cmfData->meshes.size() ) + { + boneCount = std::max( cmfData->meshes[meshIndex].boneBindings.size(), 1 ); + } + if( m_restPoseBoneTransforms.size() != boneCount ) + { + m_restPoseBoneTransforms.assign( boneCount, Float4x3( Matrix() ) ); + } + return std::make_pair( m_restPoseBoneTransforms.data(), boneCount ); } std::pair EveChildMesh::GetMorphTargets( MorphTargetAnimationFilter filter ) diff --git a/trinity/Eve/SpaceObject/Children/EveChildMesh.h b/trinity/Eve/SpaceObject/Children/EveChildMesh.h index 8a0b77ebb..f15ead806 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildMesh.h +++ b/trinity/Eve/SpaceObject/Children/EveChildMesh.h @@ -207,6 +207,7 @@ BLUE_CLASS( EveChildMesh ) : bool PrepareMorphBuffers( Tr2RenderContext & renderContext ); std::pair GetBoneTransforms() const; + std::pair GetRestPoseBoneTransforms() const; const std::pair GetMeshBindingIndices() const; std::pair GetMorphTargets( MorphTargetAnimationFilter filter ); void UpdateMorphAnimationBuffer(); @@ -231,6 +232,8 @@ BLUE_CLASS( EveChildMesh ) : PIEveChildTransformModifierVector m_transformModifiers; Tr2GrannyAnimationPtr m_animationUpdater; std::unique_ptr m_meshBinding; + // identity skin matrices for skinned meshes with no animation (rest pose) + mutable std::vector m_restPoseBoneTransforms; Tr2Lod m_lowestLodVisible; From 14d8205bc334d08e050d090d009ead56b9bc83ce Mon Sep 17 00:00:00 2001 From: JohnGreenFC <152402736+JohnGreenFC@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:40:59 +0100 Subject: [PATCH 12/12] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../Children/EveChildInstancedMeshes.cpp | 16 ++++++++++++---- .../Children/EveChildInstancedMeshes.h | 2 +- trinity/Eve/SpaceObject/Children/EveChildMesh.h | 5 ++++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp index a2555be5a..3b7bba56c 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.cpp @@ -918,10 +918,18 @@ void EveChildInstancedMeshes::UpdateOverlayInstanceData( const EveSpaceObjectVSD OverlayInstancePod& pod = ( *mesh.overlayPods )[i]; Matrix local = IdentityMatrix(); - local._11 = wt[0].x; local._12 = wt[1].x; local._13 = wt[2].x; - local._21 = wt[0].y; local._22 = wt[1].y; local._23 = wt[2].y; - local._31 = wt[0].z; local._32 = wt[1].z; local._33 = wt[2].z; - local._41 = wt[0].w; local._42 = wt[1].w; local._43 = wt[2].w; + local._11 = wt[0].x; + local._12 = wt[1].x; + local._13 = wt[2].x; + local._21 = wt[0].y; + local._22 = wt[1].y; + local._23 = wt[2].y; + local._31 = wt[0].z; + local._32 = wt[1].z; + local._33 = wt[2].z; + local._41 = wt[0].w; + local._42 = wt[1].w; + local._43 = wt[2].w; Matrix worldTransform = Transpose( local * m_worldTransform ); Matrix worldTransformLast = Transpose( local * prevWorldTransform ); diff --git a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h index 07a0190ca..e652c567f 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h +++ b/trinity/Eve/SpaceObject/Children/EveChildInstancedMeshes.h @@ -172,7 +172,7 @@ BLUE_CLASS( EveChildInstancedMeshes ) : void UnregisterFromMeshManager(); void UpdateOverlayInstanceData( const EveSpaceObjectVSData& parentVsData, const EveSpaceObjectPSData& parentPsData ); - static void RebuildOverlayAreaBlocks( Mesh& mesh ); + static void RebuildOverlayAreaBlocks( Mesh & mesh ); bool HasAnyOwnOverlayEffects() const; bool AnyMeshInheritsOverlayEffects() const; bool MeshHasActiveOverlayEffects( const Mesh& mesh ) const; diff --git a/trinity/Eve/SpaceObject/Children/EveChildMesh.h b/trinity/Eve/SpaceObject/Children/EveChildMesh.h index f15ead806..c235ff7f5 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildMesh.h +++ b/trinity/Eve/SpaceObject/Children/EveChildMesh.h @@ -171,7 +171,10 @@ BLUE_CLASS( EveChildMesh ) : void AddOverlayEffect( EveMeshOverlayEffectPtr newOverlayEffect ); void RemoveOverlayEffect( EveMeshOverlayEffectPtr overlayEffectToRemove ); EveMeshOverlayEffectPtr GetOverlayEffectByName( const char* name ) const; - const PEveMeshOverlayEffectVector& GetOverlayEffects() const { return m_overlayEffects; } + const PEveMeshOverlayEffectVector& GetOverlayEffects() const + { + return m_overlayEffects; + } Tr2GrannyAnimation* GetAnimationController() const override; void SetAnimationController( Tr2GrannyAnimation * animation );