From 4c28f0b1ca7e76b069aa699aa388d572b3dcb9ba Mon Sep 17 00:00:00 2001 From: ccp-intern <180460643+ccp-intern@users.noreply.github.com> Date: Mon, 26 Oct 2026 16:05:50 +0100 Subject: [PATCH 1/4] Plat 12257 procedural meshes (#132) * wip procedural meshes * wip procedural mesh * wip procedural mesh * wip procedural mesh * wip procedural mesh * fix procedural meshes * fix procedural meshes * cleanup * fix rest pose fix... * cleanup * cleanup * cleanup * cleanup * cleanup * sierpinski * sierpinski normals * sierpinski winding fix * fix race condition --- trinity/CMakeLists.txt | 17 + .../Attachments/EveSpaceObjectDecal.cpp | 6 +- .../Attachments/EveSpaceObjectDecal.h | 8 +- .../Eve/SpaceObject/Children/EveChildMesh.cpp | 38 +- .../SpaceObject/Children/EveChildTurret.cpp | 4 +- .../Eve/SpaceObject/Children/EveChildTurret.h | 6 +- .../Children/EveSpaceObjectChild.h | 2 +- trinity/Eve/SpaceObject/EveSpaceObject2.cpp | 20 +- trinity/Eve/SpaceObject/EveSpaceObject2.h | 5 +- trinity/Eve/SpaceObject/EveSwarm.cpp | 6 +- trinity/Interior/Tr2IntSkinnedObject.cpp | 2 +- trinity/Raytracing/Tr2RaytracingGeometry.cpp | 2 +- trinity/Raytracing/Tr2RaytracingGeometry.h | 6 +- trinity/Resources/Tr2CmfContent.cpp | 34 +- trinity/Resources/Tr2CmfContent.h | 10 + trinity/Resources/Tr2GeometryProvider.cpp | 1624 ++++++++++++++++ trinity/Resources/Tr2GeometryProvider.h | 341 ++++ .../Resources/Tr2GeometryProvider_Blue.cpp | 195 ++ trinity/Resources/Tr2ProceduralCube.cpp | 139 ++ trinity/Resources/Tr2ProceduralCube.h | 36 + trinity/Resources/Tr2ProceduralCube_Blue.cpp | 21 + .../Tr2ProceduralGeometryProvider.cpp | 205 ++ .../Resources/Tr2ProceduralGeometryProvider.h | 61 + .../Tr2ProceduralGeometryProvider_Blue.cpp | 29 + trinity/Resources/Tr2ProceduralSierpinski.cpp | 242 +++ trinity/Resources/Tr2ProceduralSierpinski.h | 40 + .../Tr2ProceduralSierpinski_Blue.cpp | 22 + trinity/Resources/TriGeometryRes.cpp | 1668 +---------------- trinity/Resources/TriGeometryRes.h | 375 +--- trinity/Resources/TriGeometryRes_Blue.cpp | 184 +- trinity/Tr2BackgroundWorker.cpp | 30 + trinity/Tr2BackgroundWorker.h | 21 + trinity/Tr2InstancedMesh.cpp | 2 +- trinity/Tr2Mesh.cpp | 7 +- trinity/Tr2Mesh.h | 4 +- trinity/Tr2MeshBase.cpp | 39 +- trinity/Tr2MeshBase.h | 22 +- trinity/Tr2MeshBase_Blue.cpp | 48 + trinity/Tr2MeshProcedural.cpp | 73 + trinity/Tr2MeshProcedural.h | 38 + trinity/Tr2MeshProcedural_Blue.cpp | 23 + trinity/Tr2Mesh_Blue.cpp | 44 - trinity/Tr2Model.cpp | 2 +- trinity/Tr2SkinnedModel.cpp | 2 +- 44 files changed, 3456 insertions(+), 2247 deletions(-) create mode 100644 trinity/Resources/Tr2GeometryProvider.cpp create mode 100644 trinity/Resources/Tr2GeometryProvider.h create mode 100644 trinity/Resources/Tr2GeometryProvider_Blue.cpp create mode 100644 trinity/Resources/Tr2ProceduralCube.cpp create mode 100644 trinity/Resources/Tr2ProceduralCube.h create mode 100644 trinity/Resources/Tr2ProceduralCube_Blue.cpp create mode 100644 trinity/Resources/Tr2ProceduralGeometryProvider.cpp create mode 100644 trinity/Resources/Tr2ProceduralGeometryProvider.h create mode 100644 trinity/Resources/Tr2ProceduralGeometryProvider_Blue.cpp create mode 100644 trinity/Resources/Tr2ProceduralSierpinski.cpp create mode 100644 trinity/Resources/Tr2ProceduralSierpinski.h create mode 100644 trinity/Resources/Tr2ProceduralSierpinski_Blue.cpp create mode 100644 trinity/Tr2BackgroundWorker.cpp create mode 100644 trinity/Tr2BackgroundWorker.h create mode 100644 trinity/Tr2MeshProcedural.cpp create mode 100644 trinity/Tr2MeshProcedural.h create mode 100644 trinity/Tr2MeshProcedural_Blue.cpp diff --git a/trinity/CMakeLists.txt b/trinity/CMakeLists.txt index ba40ebe53..6000c8957 100644 --- a/trinity/CMakeLists.txt +++ b/trinity/CMakeLists.txt @@ -1247,6 +1247,9 @@ set(_SOURCES Resources/Tr2EffectRes.cpp Resources/Tr2EffectRes.h Resources/Tr2EffectRes_Blue.cpp + Resources/Tr2GeometryProvider.cpp + Resources/Tr2GeometryProvider.h + Resources/Tr2GeometryProvider_Blue.cpp Resources/Tr2GrannyStateRes.cpp Resources/Tr2GrannyStateRes.h Resources/Tr2GrannyStateRes_Blue.cpp @@ -1262,6 +1265,15 @@ set(_SOURCES Resources/Tr2MaterialRes.cpp Resources/Tr2MaterialRes.h Resources/Tr2MaterialRes_Blue.cpp + Resources/Tr2ProceduralCube.cpp + Resources/Tr2ProceduralCube.h + Resources/Tr2ProceduralCube_Blue.cpp + Resources/Tr2ProceduralGeometryProvider.cpp + Resources/Tr2ProceduralGeometryProvider.h + Resources/Tr2ProceduralGeometryProvider_Blue.cpp + Resources/Tr2ProceduralSierpinski.cpp + Resources/Tr2ProceduralSierpinski.h + Resources/Tr2ProceduralSierpinski_Blue.cpp Resources/Tr2TextureLodManager.cpp Resources/Tr2TextureLodManager.h Resources/Tr2TextureLodManager_Blue.cpp @@ -1481,6 +1493,8 @@ set(_SOURCES Tr2AtlasTexture.cpp Tr2AtlasTexture.h Tr2AtlasTexture_Blue.cpp + Tr2BackgroundWorker.cpp + Tr2BackgroundWorker.h Tr2BindingVector3.h Tr2BindingVector3_Blue.cpp Tr2Blitter.cpp @@ -1591,6 +1605,9 @@ set(_SOURCES Tr2MeshBase.cpp Tr2MeshBase.h Tr2MeshBase_Blue.cpp + Tr2MeshProcedural.cpp + Tr2MeshProcedural.h + Tr2MeshProcedural_Blue.cpp Tr2Model.cpp Tr2Model.h Tr2Model_Blue.cpp diff --git a/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.cpp b/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.cpp index 52e288c8d..94c490056 100644 --- a/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.cpp +++ b/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.cpp @@ -179,7 +179,7 @@ void EveSpaceObjectDecal::UpdateVisibility( const EveUpdateContext& updateContex } // ------------------------------------------------------------------------------------------------------ -void EveSpaceObjectDecal::GetRenderables( std::vector& renderables, DecalMeshCache& meshCache, TriGeometryRes* geomRes, float screensize ) +void EveSpaceObjectDecal::GetRenderables( std::vector& renderables, DecalMeshCache& meshCache, Tr2GeometryProvider* geomRes, float screensize ) { if( m_isVisible <= 0 || !geomRes ) { @@ -589,7 +589,7 @@ void EveSpaceObjectDecal::SetBoneIndex( int idx ) } // ------------------------------------------------------------------------------------------------------ -void EveSpaceObjectDecal::CreateDecalIndexBuffers( TriGeometryResPtr geomRes, DecalMeshCache& meshCache ) +void EveSpaceObjectDecal::CreateDecalIndexBuffers( Tr2GeometryProviderPtr geomRes, DecalMeshCache& meshCache ) { TRINITY_STATS_ZONE( __FUNCTION__ ); @@ -824,7 +824,7 @@ void EveSpaceObjectDecal::SetMinScreenSize( float minScreenSize ) m_minScreenSize = minScreenSize; } -void EveSpaceObjectDecal::CreateStaticIndexBuffers( TriGeometryResPtr geomRes ) +void EveSpaceObjectDecal::CreateStaticIndexBuffers( Tr2GeometryProviderPtr geomRes ) { TRINITY_STATS_ZONE( __FUNCTION__ ); diff --git a/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.h b/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.h index 6de349978..adb8ace34 100644 --- a/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.h +++ b/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.h @@ -128,7 +128,7 @@ BLUE_CLASS( EveSpaceObjectDecal ) : // access void UpdateVisibility( const EveUpdateContext& updateContext, const IEveSpaceObject2::ParentData* parentData ); - void GetRenderables( std::vector & renderables, DecalMeshCache & meshCache, TriGeometryRes * geomRes, float screensize ); + void GetRenderables( std::vector & renderables, DecalMeshCache & meshCache, Tr2GeometryProvider * geomRes, float screensize ); void GetInstancedRenderables( std::vector & renderables, DecalMeshCache & meshCache, const Tr2InstancedMesh* instancedMesh, float instanceScreenSize = std::numeric_limits::max() ); // access position etc. @@ -160,10 +160,10 @@ BLUE_CLASS( EveSpaceObjectDecal ) : private: // create - void CreateDecalIndexBuffers( TriGeometryResPtr geomRes, DecalMeshCache & meshCache ); + void CreateDecalIndexBuffers( Tr2GeometryProviderPtr geomRes, DecalMeshCache & meshCache ); // update void UpdateDecalMatrix(); - void CreateStaticIndexBuffers( TriGeometryResPtr geomRes ); + void CreateStaticIndexBuffers( Tr2GeometryProviderPtr geomRes ); bool HasStaticIndexBuffers() const; std::vector> GetStaticIndexBuffers() const; @@ -195,7 +195,7 @@ BLUE_CLASS( EveSpaceObjectDecal ) : Matrix m_inverseDecalMatrix; // base mesh geometry - TriGeometryResPtr m_baseGeometryResource; + Tr2GeometryProviderPtr m_baseGeometryResource; int m_geometryLodIndex; std::shared_ptr m_decalGeometry; diff --git a/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp b/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp index 2085ad14a..a4508b079 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp @@ -225,7 +225,7 @@ void EveChildMesh::InitializeAnimation() if( auto geometry = m_mesh->GetGeometryResource() ) { m_animationUpdater->SetUseMeshBinding( true ); - m_animationUpdater->SetSharedGeometryRes( geometry ); + m_animationUpdater->SetSharedGeometryRes( BlueCastPtr( geometry ) ); return; } } @@ -711,7 +711,7 @@ void EveChildMesh::GetBatchesFromOverlayVector( ITriRenderBatchAccumulator* batc return; } - TriGeometryRes* geomRes = m_mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = m_mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return; @@ -1167,6 +1167,7 @@ void EveChildMesh::UpdateSyncronous( const EveUpdateContext& updateContext, cons UnregisterAudioGeometry(); } m_allowAudioGeometry = allowAudioGeometry; + if( m_animationUpdater ) { if( m_mesh && m_animationUpdater->GetResPath().empty() ) @@ -1185,9 +1186,16 @@ void EveChildMesh::UpdateSyncronous( const EveUpdateContext& updateContext, cons if( m_mesh && !m_animationUpdater->HasMeshBinding() ) { - if( !m_meshBinding || m_meshBinding->GetAnimation() != m_animationUpdater || m_meshBinding->GetGeometryRes() != m_mesh->GetGeometryResource() || m_meshBinding->GetMeshIndex() != m_mesh->GetMeshIndex() ) + auto geometryRes = dynamic_cast( m_mesh->GetGeometryResource() ); + + if( m_meshBinding && ( m_meshBinding->GetAnimation() != m_animationUpdater || m_meshBinding->GetGeometryRes() != geometryRes || m_meshBinding->GetMeshIndex() != m_mesh->GetMeshIndex() ) ) + { + m_meshBinding = {}; + } + + if( !m_meshBinding && geometryRes ) { - m_meshBinding = std::make_unique( m_animationUpdater, m_mesh->GetGeometryResource(), m_mesh->GetMeshIndex() ); + m_meshBinding = std::make_unique( m_animationUpdater, geometryRes, m_mesh->GetMeshIndex() ); } } else @@ -1428,10 +1436,14 @@ void EveChildMesh::RenderDebugInfo( ITr2DebugRenderer2& renderer ) m_mesh->RenderDebugInfo( m_worldTransform, renderer, nullptr ); } - if( bvhOptionEnabled && !m_bvhVisualizationGeometry && m_mesh->GetGeometryResource() && m_mesh->GetGeometryResource()->IsGood() ) + if( bvhOptionEnabled && !m_bvhVisualizationGeometry ) { - m_bvhVisualizationGeometry = m_mesh->GetGeometryResource(); - m_bvhVisualizationGeometry->PrepareRayCaster(); + TriGeometryResPtr geometryRes = BlueCastPtr( m_mesh->GetGeometryResource() ); + if( geometryRes && geometryRes->IsGood() ) + { + m_bvhVisualizationGeometry = geometryRes; + m_bvhVisualizationGeometry->PrepareRayCaster(); + } } if( m_bvhVisualizationGeometry && m_bvhVisualizationGeometry->IsRayCasterReady() ) { @@ -1543,16 +1555,12 @@ std::pair EveChildMesh::GetRestPoseBoneTransforms() con 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. + // Fixes skinned shader used with uninitialized animationUpdater (no skeleton), or used with no boneBindings. Otherwise shader would read garbage. size_t boneCount = 1; - auto cmfData = m_mesh->GetGeometryResource()->GetCMFData(); - auto meshIndex = m_mesh->GetMeshIndex(); - if( cmfData && meshIndex < cmfData->meshes.size() ) + auto meshData = m_mesh->GetGeometryResource()->GetMeshData( m_mesh->GetMeshIndex() ); + if( meshData ) { - boneCount = std::max( cmfData->meshes[meshIndex].boneBindings.size(), 1 ); + boneCount = std::max( meshData->m_jointBindings.size(), 1 ); } if( m_restPoseBoneTransforms.size() != boneCount ) { diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp index 306269b33..1de214c04 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp @@ -313,7 +313,7 @@ void EveChildTurret::UpdateCachedGeometryData() m_cachedGeometryRes = geometryRes; } } -void EveChildTurret::BuildCachedGeometryData( TriGeometryRes& geometryRes ) +void EveChildTurret::BuildCachedGeometryData( Tr2GeometryProvider& geometryRes ) { if( geometryRes.GetSkeletonCount() ) { @@ -771,7 +771,7 @@ Matrix EveChildTurret::GetTurretBoneTransform( uint32_t boneID ) const return matrix; } -TriGeometryRes* EveChildTurret::GetGeometryRes() const +Tr2GeometryProvider* EveChildTurret::GetGeometryRes() const { return m_mesh ? m_mesh->GetGeometryResource() : nullptr; } diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.h b/trinity/Eve/SpaceObject/Children/EveChildTurret.h index 2e76a2804..58c3bf808 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.h +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.h @@ -39,7 +39,7 @@ BLUE_CLASS( EveChildTurret ) : void AddQuadsToQuadRenderer( const TriFrustum& frustum, Tr2QuadRenderer& quadRenderer ) const override; void UpdateCachedGeometryData(); - void BuildCachedGeometryData( TriGeometryRes & geometryRes ); + void BuildCachedGeometryData( Tr2GeometryProvider & geometryRes ); void ReleaseCachedGeometryData(); // action @@ -81,7 +81,7 @@ BLUE_CLASS( EveChildTurret ) : Matrix GetTurretBoneTransform( uint32_t boneID ) const; - TriGeometryRes* GetGeometryRes() const; + Tr2GeometryProvider* GetGeometryRes() const; // animation float PlayAnimation( const std::string& animName, const std::string& animNameIdle, float delay = 0.f ); @@ -141,7 +141,7 @@ BLUE_CLASS( EveChildTurret ) : std::wstring m_idleToTargetingMovementAudioEvent; std::wstring m_targetingToIdleMovementAudioEvent; - TriGeometryResPtr m_cachedGeometryRes; + Tr2GeometryProviderPtr m_cachedGeometryRes; }; TYPEDEF_BLUECLASS( EveChildTurret ); diff --git a/trinity/Eve/SpaceObject/Children/EveSpaceObjectChild.h b/trinity/Eve/SpaceObject/Children/EveSpaceObjectChild.h index 202c910a9..0d321480c 100644 --- a/trinity/Eve/SpaceObject/Children/EveSpaceObjectChild.h +++ b/trinity/Eve/SpaceObject/Children/EveSpaceObjectChild.h @@ -43,7 +43,7 @@ struct EveChildGeometryArea */ struct EveChildGeometry { - class TriGeometryRes* geometry = nullptr; + class Tr2GeometryProvider* geometry = nullptr; Matrix childToObject = IdentityMatrix(); uint32_t areaStart = 0; uint32_t areaCount = 0; diff --git a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp index c70b71e4b..588d24bcb 100644 --- a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp +++ b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp @@ -66,7 +66,7 @@ void GetSortedBatchesFromMeshAreaVector( const Tr2MeshAreaVector* areas, float screenSize, const Matrix* worldTransform ) { - TriGeometryRes* geomRes = mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { @@ -1181,7 +1181,7 @@ void EveSpaceObject2::GetShadowBatches( ITriRenderBatchAccumulator* batches, con return; } - TriGeometryRes* geomRes = m_mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = m_mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return; @@ -1251,7 +1251,7 @@ void EveSpaceObject2::GetBatchesFromOverlayVector( ITriRenderBatchAccumulator* b { return; } - TriGeometryRes* geomRes = mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return; @@ -1489,7 +1489,8 @@ Vector4 EveSpaceObject2::CalculateSkinnedBoundingSphere() #if WITH_GRANNY else { - return m_animationUpdater->CalculateSkinnedBoundingSphere( m_mesh->GetGeometryResource()->GetGrannyInfo() ); + auto geometry = dynamic_cast( m_mesh->GetGeometryResource() ); + return m_animationUpdater->CalculateSkinnedBoundingSphere( geometry ? geometry->GetGrannyInfo() : nullptr ); } #endif } @@ -1524,7 +1525,8 @@ std::pair EveSpaceObject2::CalculateSkinnedBoundingBoxFromTran #if WITH_GRANNY else { - m_animationUpdater->CalculateSkinnedBoundingBoxFromTransform( transform, bbMin, bbMax, m_geometryResFromMesh->GetGrannyInfo() ); + TriGeometryResPtr geometry = BlueCastPtr( m_geometryResFromMesh ); + m_animationUpdater->CalculateSkinnedBoundingBoxFromTransform( transform, bbMin, bbMax, geometry ? geometry->GetGrannyInfo() : nullptr ); } #endif } @@ -1577,7 +1579,7 @@ void EveSpaceObject2::PushChildrenAndDecalRenderables( std::vectorGetGeometryResource(); + Tr2GeometryProviderPtr geometryRes = m_mesh->GetGeometryResource(); if( geometryRes ) { DecalMeshCache meshCache; @@ -2381,7 +2383,7 @@ bool EveSpaceObject2::RebuildBoundingSphereInformation() return false; } - TriGeometryRes* geomRes = mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return false; @@ -3199,7 +3201,7 @@ void EveSpaceObject2::PrepareForAnimation() m_geometryResFromMesh = geometryRes; m_animationUpdater->SetUseMeshBinding( true ); - m_animationUpdater->SetSharedGeometryRes( m_geometryResFromMesh ); + m_animationUpdater->SetSharedGeometryRes( BlueCastPtr( m_geometryResFromMesh ) ); m_geometryResFromMesh->AddNotifyTarget( this ); } @@ -4220,7 +4222,7 @@ bool EveSpaceObject2::IsBoundingBoxReady() const return false; } - TriGeometryRes* geomRes = mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return false; diff --git a/trinity/Eve/SpaceObject/EveSpaceObject2.h b/trinity/Eve/SpaceObject/EveSpaceObject2.h index a1ce39d09..6668d2ead 100644 --- a/trinity/Eve/SpaceObject/EveSpaceObject2.h +++ b/trinity/Eve/SpaceObject/EveSpaceObject2.h @@ -55,6 +55,7 @@ BLUE_DECLARE_INTERFACE( IEveSpaceObject2 ); BLUE_DECLARE( EveSpaceObject2 ); BLUE_DECLARE_VECTOR( EveSpaceObject2 ); BLUE_DECLARE( TriGeometryRes ); +BLUE_DECLARE( Tr2GeometryProvider ); BLUE_DECLARE( Tr2MeshBase ); BLUE_DECLARE( Tr2MeshArea ); BLUE_DECLARE_VECTOR( Tr2MeshArea ); @@ -182,7 +183,7 @@ struct LocatorSourceRange struct DamageFilterOccluder { - TriGeometryResPtr geometry; + Tr2GeometryProviderPtr geometry; Matrix fromObject; uint32_t areaStart = 0; uint32_t areaCount = 0; @@ -631,7 +632,7 @@ BLUE_CLASS( EveSpaceObject2 ) : Tr2Lod m_lodLevel; Tr2Lod m_lodLevelWithChildren; - TriGeometryResPtr m_geometryResFromMesh; + Tr2GeometryProviderPtr m_geometryResFromMesh; ///////////////////////////////////////////////////////////////////////////////////// // dissolve diff --git a/trinity/Eve/SpaceObject/EveSwarm.cpp b/trinity/Eve/SpaceObject/EveSwarm.cpp index 63d440542..7630987da 100644 --- a/trinity/Eve/SpaceObject/EveSwarm.cpp +++ b/trinity/Eve/SpaceObject/EveSwarm.cpp @@ -165,7 +165,7 @@ void EveSwarmRenderable::InitDecals( const PEveSpaceObjectDecalVector& decals ) void EveSwarmRenderable::PushDecals( std::vector& renderables, float screensize ) { - TriGeometryResPtr geometryRes = m_mesh->GetGeometryResource(); + Tr2GeometryProviderPtr geometryRes = m_mesh->GetGeometryResource(); if( geometryRes ) { @@ -181,7 +181,7 @@ void EveSwarmRenderable::PushDecals( std::vector& renderables, void EveSwarmRenderable::UpdateDecalVisibility( const EveUpdateContext& updateContext, IEveSpaceObject2::ParentData& pd, Tr2GrannyAnimation* animationUpdater ) { - TriGeometryResPtr geometryRes = m_mesh->GetGeometryResource(); + Tr2GeometryProviderPtr geometryRes = m_mesh->GetGeometryResource(); if( geometryRes ) { @@ -273,7 +273,7 @@ void EveSwarmRenderable::GetShadowBatches( ITriRenderBatchAccumulator* batches, return; } - TriGeometryRes* geomRes = m_mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = m_mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return; diff --git a/trinity/Interior/Tr2IntSkinnedObject.cpp b/trinity/Interior/Tr2IntSkinnedObject.cpp index 00e47bc83..9271be2c4 100644 --- a/trinity/Interior/Tr2IntSkinnedObject.cpp +++ b/trinity/Interior/Tr2IntSkinnedObject.cpp @@ -239,7 +239,7 @@ void Tr2IntSkinnedObject::GetBatches( ITriRenderBatchAccumulator* batches, continue; } - TriGeometryRes* geomRes = mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { continue; diff --git a/trinity/Raytracing/Tr2RaytracingGeometry.cpp b/trinity/Raytracing/Tr2RaytracingGeometry.cpp index 85f8272b5..825d2a207 100644 --- a/trinity/Raytracing/Tr2RaytracingGeometry.cpp +++ b/trinity/Raytracing/Tr2RaytracingGeometry.cpp @@ -169,7 +169,7 @@ Tr2RaytracingMesh::Tr2RaytracingMesh() : { } -void Tr2RaytracingMesh::UpdateRtMesh( TriGeometryRes* geometry, uint32_t meshIndex, float screenSize ) +void Tr2RaytracingMesh::UpdateRtMesh( Tr2GeometryProvider* geometry, uint32_t meshIndex, float screenSize ) { if( m_geometry != geometry || m_meshIndex != meshIndex ) { diff --git a/trinity/Raytracing/Tr2RaytracingGeometry.h b/trinity/Raytracing/Tr2RaytracingGeometry.h index 44aeefcbf..7ea1121e3 100644 --- a/trinity/Raytracing/Tr2RaytracingGeometry.h +++ b/trinity/Raytracing/Tr2RaytracingGeometry.h @@ -11,7 +11,7 @@ BLUE_DECLARE( Tr2MeshArea ); BLUE_DECLARE_VECTOR( Tr2MeshArea ); BLUE_DECLARE( Tr2Effect ); -BLUE_DECLARE( TriGeometryRes ); +BLUE_DECLARE( Tr2GeometryProvider ); BLUE_DECLARE( Tr2RaytracingGeometry ); struct TriGeometryResLodData; @@ -46,7 +46,7 @@ class Tr2RaytracingMesh public: Tr2RaytracingMesh(); - void UpdateRtMesh( TriGeometryRes* geometry, uint32_t meshIndex, float screenSize ); + void UpdateRtMesh( Tr2GeometryProvider* geometry, uint32_t meshIndex, float screenSize ); bool SetBoneTransforms( size_t count, const Float4x3* transforms, uint32_t offset ); bool SetMorphAnimations( size_t count, const Tr2MorphTargetAnimationData* morphTargets, uint32_t morphTargetAnimationDataOffset ); @@ -67,7 +67,7 @@ class Tr2RaytracingMesh private: const Tr2BufferAL* m_skinnedVertices; - TriGeometryResPtr m_geometry; + Tr2GeometryProviderPtr m_geometry; uint32_t m_meshIndex; std::vector m_transforms; uint32_t m_boneOffset; diff --git a/trinity/Resources/Tr2CmfContent.cpp b/trinity/Resources/Tr2CmfContent.cpp index 5daa8f48b..2860954b1 100644 --- a/trinity/Resources/Tr2CmfContent.cpp +++ b/trinity/Resources/Tr2CmfContent.cpp @@ -7,22 +7,16 @@ #include -Tr2CmfContents::Tr2CmfContents( IBlueStream& stream, const char* filename ) +void Tr2CmfContents::Initialize( const void* data, size_t size, const char* name, cmf::ValidationOptions options ) { - void* data; - if( !stream.LockData( &data, 0 ) ) - { - CCP_LOGERR( "Failed to read CMF file %s", filename ); - return; - } - auto size = stream.GetSize(); - auto validated = cmf::ValidateFile( data, size, { true, true } ); + auto validated = cmf::ValidateFile( data, size, options ); if( !validated ) { - CCP_LOGERR( "Invalid CMF file %s: %s", filename, validated.error.c_str() ); + CCP_LOGERR( "Invalid CMF data %s: %s", name, validated.error.c_str() ); return; } - auto header = static_cast( data ); + + auto header = static_cast( data ); m_sections.reserve( header->sections.size() ); for( const auto& section : header->sections ) { @@ -32,9 +26,27 @@ Tr2CmfContents::Tr2CmfContents( IBlueStream& stream, const char* filename ) dest.data = std::make_unique( section.compressedSize ); memcpy( dest.data.get(), (uint8_t*)data + section.offset, section.compressedSize ); } + cmf::OffsetsToPointers( *reinterpret_cast( m_sections[0].data.get() ) ); } +Tr2CmfContents::Tr2CmfContents( IBlueStream& stream, const char* filename ) +{ + void* data; + if( !stream.LockData( &data, 0 ) ) + { + CCP_LOGERR( "Failed to read CMF file %s", filename ); + return; + } + auto size = stream.GetSize(); + Initialize( data, size, filename, { true, true } ); +} + +Tr2CmfContents::Tr2CmfContents( const void* data, size_t size, const char* name ) +{ + Initialize( data, size, name, { false, true } ); +} + Tr2CmfContents::operator bool() const { return !m_sections.empty(); diff --git a/trinity/Resources/Tr2CmfContent.h b/trinity/Resources/Tr2CmfContent.h index 5e0f908b2..e7c77954a 100644 --- a/trinity/Resources/Tr2CmfContent.h +++ b/trinity/Resources/Tr2CmfContent.h @@ -16,6 +16,14 @@ class Tr2CmfContents */ explicit Tr2CmfContents( IBlueStream& stream, const char* filename ); + /** + * @brief Creates the cmf contents in memory. + * @param data The cmf. + * @param size The size of the cmf. + * @param name The name for the cmf. + */ + Tr2CmfContents( const void* data, size_t size, const char* name ); + /** * @brief Returns true if the .cmf file was successfully read and parsed, false otherwise. */ @@ -54,4 +62,6 @@ class Tr2CmfContents std::unique_ptr data; }; std::vector
m_sections; + + void Initialize( const void* data, size_t size, const char* name, cmf::ValidationOptions options ); }; diff --git a/trinity/Resources/Tr2GeometryProvider.cpp b/trinity/Resources/Tr2GeometryProvider.cpp new file mode 100644 index 000000000..2a04066c3 --- /dev/null +++ b/trinity/Resources/Tr2GeometryProvider.cpp @@ -0,0 +1,1624 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Include/TriMath.h" +#include "Tr2GeometryProvider.h" +#include "Tr2PerObjectData.h" +#include "Tr2VertexDefinitionUtilities.h" +#include "TriDevice.h" + +#include "TriSettingsRegistrar.h" + +#include "Utilities/GeometryUtils.h" +#include "Utilities/BoundingSphere.h" +#include "Utilities/BoundingBox.h" + +#include +#include "TbbStub.h" + +using namespace Tr2RenderContextEnum; + +std::atomic AudioGeometryResData::s_nextId{ 1 }; + +bool g_eveIsAudioOcclusionGeometryEnabled = false; +TRI_REGISTER_SETTING( "eveIsAudioOcclusionGeometryEnabled", g_eveIsAudioOcclusionGeometryEnabled ); + +CCP_STATS_DECLARE( geometryResBytes, "Trinity/geometryResBytes", false, CST_MEMORY, "Size of memory occupied by geometry resources." ); + +#if TRINITY_PLATFORM != TRINITY_DIRECTX11 +const auto gpuUsage = Tr2GpuUsage::VERTEX_BUFFER | Tr2GpuUsage::INDEX_BUFFER | Tr2GpuUsage::SHADER_RESOURCE; +#else +const auto gpuUsage = Tr2GpuUsage::VERTEX_BUFFER | Tr2GpuUsage::INDEX_BUFFER; +#endif + +Tr2SuballocatedBuffer g_sharedBuffer( "TriGeometryRes shared vertex/index buffer", gpuUsage, SHARED_BUFFER_BLOCK_SIZE, SHARED_BUFFER_MAX_SIZE ); + +TriMorphTargetGeometryConstants CreateMorphGeometryConstants( const Tr2VertexDefinition& vertexDefinition, uint32_t stride, uint32_t vertexCount, Tr2RenderContext& renderContext ) +{ + TriMorphTargetGeometryConstants data = TriMorphTargetGeometryConstants{}; + for( const auto& item : vertexDefinition.m_items ) + { + if( item.m_usage == Tr2VertexDefinition::POSITION && item.m_usageIndex == 0 && item.m_stream == 0 ) + { + data.positionOffset = item.m_offset; + data.positionType = item.m_dataType; + + uint32_t type = data.positionType; + CCP_ASSERT_M( + type == Tr2VertexDefinition::DataType::FLOAT32_3, + "position type has to be FLOAT32_3!" ); + } + + if( item.m_usage == Tr2VertexDefinition::TANGENT && item.m_usageIndex == 0 && item.m_stream == 0 ) + { + data.tangentOffset = item.m_offset; + data.tangentType = item.m_dataType; + + uint32_t type = data.tangentType; + CCP_ASSERT_M( + type == Tr2VertexDefinition::DataType::FLOAT16_3 || + type == Tr2VertexDefinition::DataType::FLOAT32_3 || + type == Tr2VertexDefinition::DataType::UBYTE_4_NORM || + type == Tr2VertexDefinition::DataType::USHORT_4_NORM || + type == Tr2VertexDefinition::DataType::SHORT_4_NORM, + "tangent type has to be FLOAT16_3 or FLOAT32_3 or UBYTE_4_NORM or USHORT_4_NORM or SHORT_4_NORM!" ); + } + } + + data.vertexBufferStride = stride; + data.vertexCount = vertexCount; + return data; +} + +uint32_t GetPrimitiveCount( const TriGeometryResLodData& lod, uint32_t index, uint32_t count ) +{ + if( index >= lod.m_areas.size() ) + { + return 0; + } + + if( index + count > lod.m_areas.size() ) + { + count = uint32_t( lod.m_areas.size() - index ); + } + + auto& meshArea = lod.m_areas[index]; + + uint32_t primCount = uint32_t( meshArea.m_primitiveCount ); + for( uint32_t i = 1; i < count; ++i ) + { + primCount += lod.m_areas[index + i].m_primitiveCount; + } + return primCount; +} + +TriGeometryAreaIndexRange GetAreaIndexRange( const TriGeometryResLodData& lod, uint32_t areaIndex, uint32_t primCount, bool reversed ) +{ + TriGeometryAreaIndexRange range; + if( primCount == 0 || areaIndex >= lod.m_areas.size() ) + { + return range; + } + if( reversed && !lod.m_reversedIndicesValid ) + { + return range; + } + range.indices = reversed ? &lod.m_reversedIndexAllocation : &lod.m_indexAllocation; + range.startIndex = reversed ? + range.indices->GetStartIndex() + lod.m_primitiveCount * 3 - lod.m_areas[areaIndex].m_firstIndex - primCount * 3 : + range.indices->GetStartIndex() + lod.m_areas[areaIndex].m_firstIndex; + range.valid = true; + return range; +} + + +Tr2GeometryProvider::Tr2GeometryProvider( IRoot* lockobj ) : + m_memoryUse( 0 ), + m_meshes( "Tr2GeometryProvider/m_meshes" ), + m_skeletons( "Tr2GeometryProvider/m_skeletons" ) +{ +} + +unsigned int Tr2GeometryProvider::GetMeshCount() const +{ + return unsigned( m_meshes.size() ); +} + +TriGeometryResMeshData* Tr2GeometryProvider::GetMeshData( unsigned int meshIx ) const +{ + if( !m_isGood || meshIx >= m_meshes.size() ) + { + return nullptr; + } + + return m_meshes[meshIx].get(); +} + +TriGeometryResLodData* Tr2GeometryProvider::GetMeshLod( unsigned int meshIx, float screenSize ) const +{ + auto mesh = GetMeshData( meshIx ); + + if( !mesh ) + { + return nullptr; + } + + int lodIndex = GetLodIndexForScreenSize( meshIx, screenSize ); + return mesh->m_lods[lodIndex].get(); +} + +TriGeometryResLodData* Tr2GeometryProvider::GetMeshLod( unsigned int meshIx, int lodIndex ) const +{ + + auto mesh = GetMeshData( meshIx ); + + if( !mesh || lodIndex < 0 || lodIndex >= mesh->m_lods.size() ) + { + return nullptr; + } + + return mesh->m_lods[lodIndex].get(); +} + +int Tr2GeometryProvider::GetLodIndexForScreenSize( unsigned int meshIx, float screenSize ) const +{ + auto mesh = GetMeshData( meshIx ); + + if( !mesh ) + { + return -1; + } + + int32_t lastLod = static_cast( mesh->m_lods.size() ) - 1; + if( m_forceLod && m_forcedLodIndex >= 0 ) + { + return int( std::min( m_forcedLodIndex, lastLod ) ); + } + + for( int32_t i = lastLod; i >= 0; i-- ) + { + if( mesh->m_lods[i]->m_maxScreenSize >= screenSize ) + { + return i; + } + } + //If we end up here, it's because we requested a LOD size larger than the best one we have. + //Just return the highest quality LOD in this case. + return 0; +} + +const AudioGeometryResData* Tr2GeometryProvider::GetAudioGeometry( unsigned int meshIx ) const +{ + if( !g_eveIsAudioOcclusionGeometryEnabled ) + return nullptr; + + auto mesh = GetMeshData( meshIx ); + if( !mesh || !mesh->m_audioGeometry ) + { + return nullptr; + } + + return mesh->m_audioGeometry.get(); +} + +bool Tr2GeometryProvider::GetBoundingBox( unsigned int meshIx, Vector3& min, Vector3& max ) const +{ + TriGeometryResMeshData* mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return false; + } + + min = mesh->m_minBounds; + max = mesh->m_maxBounds; + + return true; +} + +Be::Result Tr2GeometryProvider::GetBoundingBoxFromScript( unsigned int meshIx, std::pair& bounds ) const +{ + if( meshIx >= m_meshes.size() ) + { + return Be::Result( "Mesh index out of bounds" ); + } + TriGeometryResMeshData* mesh = m_meshes[meshIx].get(); + if( !mesh ) + { + return Be::Result( "Invalid mesh" ); + } + + bounds = std::make_pair( mesh->m_minBounds, mesh->m_maxBounds ); + + return Be::Result(); +} + +bool Tr2GeometryProvider::GetAreaBoundingBox( unsigned int meshIx, unsigned int areaIx, Vector3& min, Vector3& max ) const +{ + auto mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return false; + } + + //Get the first LOD. + auto& lod = mesh->m_lods[0]; + + // Bail out if the area index is out of range + if( areaIx >= lod->m_areas.size() ) + { + return false; + } + + // Finally, get the min and max bounds for the mesh area + min = lod->m_areas[areaIx].m_minBounds; + max = lod->m_areas[areaIx].m_maxBounds; + + return true; +} + + +bool Tr2GeometryProvider::GetBoundingSphere( unsigned int meshIx, Vector4& sphere ) const +{ + TriGeometryResMeshData* mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return false; + } + + sphere = mesh->m_boundingSphere; + + return true; +} + +unsigned int Tr2GeometryProvider::GetAreaCount( unsigned int meshIx ) const +{ + auto mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return 0; + } + + //Get the first LOD. + auto& lod = mesh->m_lods[0]; + + return (unsigned int)lod->m_areas.size(); +} + +TriGeometryResAreaData* Tr2GeometryProvider::GetAreaData( unsigned int meshIx, unsigned int areaIx ) const +{ + auto mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return NULL; + } + + //Get the first LOD. + auto& lod = mesh->m_lods[0]; + + if( areaIx < 0 || areaIx >= lod->m_areas.size() ) + { + return NULL; + } + + return &lod->m_areas[areaIx]; +} + +#if TRINITYDEV +void Tr2GeometryProvider::GetDescription( std::string& desc ) +{ + desc = "Tr2GeometryProvider: '"; + desc += CW2A( GetPath() ); + desc += "'"; +} +#endif + +//This entire function is sus. We should be able to compute this when we upload the mesh to the GPU, as it can never change unless the vertex data changes. +void Tr2GeometryProvider::RecalculateBoundingSphere() +{ + TRINITY_STATS_ZONE( __FUNCTION__ ); + USE_MAIN_THREAD_RENDER_CONTEXT(); + + for( auto& mesh : m_meshes ) + { + //Try to get the vertex declaration elements + Tr2VertexDefinition decl; + if( mesh == nullptr || !Tr2EffectStateManager::GetVertexDeclarationElements( mesh->m_vertexDeclarationHandle, decl ) ) + { + continue; + } + + uint32_t vertSize = mesh->m_bytesPerVertex; + + //For now, we just compute the bounding sphere of the first LOD and ignore the lower LODs, assuming that they should fit in the highest quality LOD's bounding sphere. + auto& lod = mesh->m_lods[0]; + + + //Get the vertex data of the + + const uint8_t* pVertices; + if( !lod->m_allocationsValid || FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) + { + continue; + } + auto position = decl.Find( decl.POSITION, 0 ); + + // build a list of pointers to the positions + std::vector points( lod->m_vertexCount ); + for( uint32_t p = 0; p < lod->m_vertexCount; ++p ) + { + points[p] = (const Vector3*)&pVertices[p * vertSize + position->m_offset]; + } + + // all is done in this recursive function + BoundingSphereFromPoints( mesh->m_boundingSphere, &points[0], points.size() ); + lod->m_vertexAllocation.UnmapForReading( renderContext ); + } +} + +struct CalcBoundingBoxContext +{ + Vector3 min; + Vector3 max; + Matrix transform; +}; + +void CalcBoundingBox( void* context, const Vector3& p1, const Vector3& p2, const Vector3& p3 ) +{ + CalcBoundingBoxContext* ctx = static_cast( context ); + + Vector4 v[3]; + v[0] = Vector4( p1.x, p1.y, p1.z, 1.0f ); + v[1] = Vector4( p2.x, p2.y, p2.z, 1.0f ); + v[2] = Vector4( p3.x, p3.y, p3.z, 1.0f ); + + for( int i = 0; i < 3; ++i ) + { + v[i] = Transform( v[i], ctx->transform ); + v[i] /= v[i].w; + + ctx->max.x = std::max( ctx->max.x, v[i].x ); + ctx->max.y = std::max( ctx->max.y, v[i].y ); + ctx->max.z = std::max( ctx->max.z, v[i].z ); + + ctx->min.x = std::min( ctx->min.x, v[i].x ); + ctx->min.y = std::min( ctx->min.y, v[i].y ); + ctx->min.z = std::min( ctx->min.z, v[i].z ); + } +} + +static void ConvertTriangleData( Tr2VertexDefinition::DataType elementType, unsigned int vertexByteSize, const uint8_t* vertexBase, unsigned int index1, unsigned int index2, unsigned int index3, Vector3* v1, Vector3* v2, Vector3* v3 ) +{ + + ConvertDataToVector3( elementType, vertexBase + index1 * vertexByteSize, v1 ); + ConvertDataToVector3( elementType, vertexBase + index2 * vertexByteSize, v2 ); + ConvertDataToVector3( elementType, vertexBase + index3 * vertexByteSize, v3 ); +} + +void Tr2GeometryProvider::ProcessMeshTriangles( int meshIx, PerTriangleCallback cb, void* cbContext ) +{ + TRINITY_STATS_ZONE( __FUNCTION__ ); + USE_MAIN_THREAD_RENDER_CONTEXT(); + + int i = meshIx; + + if( m_meshes[i] == NULL ) + { + return; + } + + auto& mesh = m_meshes[i]; + + auto& lod = mesh->m_lods[0]; + + if( !lod->m_allocationsValid ) + { + return; + } + + const uint8_t* pVertices; + const uint8_t* pIndices; + + int vertSize = mesh->m_bytesPerVertex; + + if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) + { + return; + } + ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); + if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) + { + return; + } + ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); + + const uint16_t* pShortIndices = (uint16_t*)pIndices; + const uint32_t* pLongIndices = (uint32_t*)pIndices; + + + Tr2VertexDefinition decl; + if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh->m_vertexDeclarationHandle, decl ) ) + { + return; + } + + auto foundPosition = decl.Find( Tr2VertexDefinition::POSITION, 0 ); + if( !foundPosition ) + { + return; + } + unsigned int positionByteOffset = foundPosition->m_offset; + Tr2VertexDefinition::DataType declType = foundPosition->m_dataType; + + int numPrim = lod->m_primitiveCount; + for( int j = 0; j < numPrim; j++ ) + { + unsigned int index1 = 0; + unsigned int index2 = 0; + unsigned int index3 = 0; + Vector3 p1; + Vector3 p2; + Vector3 p3; + if( lod->m_indexAllocation.GetStride() == 2 ) + { + index1 = pShortIndices[j * 3]; + index2 = pShortIndices[( j * 3 ) + 1]; + index3 = pShortIndices[( j * 3 ) + 2]; + } + else + { + index1 = pLongIndices[j * 3]; + index2 = pLongIndices[( j * 3 ) + 1]; + index3 = pLongIndices[( j * 3 ) + 2]; + } + + ConvertTriangleData( declType, vertSize, pVertices + positionByteOffset, index1, index2, index3, &p1, &p2, &p3 ); + + ( *cb )( cbContext, p1, p2, p3 ); + } +} + +std::pair>> Tr2GeometryProvider::GetIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir ) +{ + RayCastResult hitInfo; + bool hit = GetIntersectionPoints( pos, dir, hitInfo ); + Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); + return std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, normal ) ) ); +} + +Be::Result Tr2GeometryProvider::GetAreaIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>& result ) +{ + if( areaIx < -1 ) + { + // -1 is a special case handled to maintain legacy behavior. + return Be::Result( "Invalid area index" ); + } + + // In Python 3, passing a signed value to an unsigned C exposed attribute is an error. + // In order to maintain backwards compatible behavior with Python 2 + // this method got changed to accept signed integers and cast them to unsigned. + auto unsignedAreaIndex = static_cast( areaIx ); + + RayCastResult hitInfo; + bool hit = GetIntersectionPoints( pos, dir, hitInfo, unsignedAreaIndex ); + Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); + result = std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, normal ) ) ); + return Be::Result(); +} + +std::pair>>>> Tr2GeometryProvider::GetIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir ) +{ + RayCastResult hitInfo; + bool hit = GetIntersectionPoints( pos, dir, hitInfo ); + Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); + return std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, std::make_pair( normal, std::make_pair( hitInfo.firstVertexColor, hitInfo.interpolatedVertexColor ) ) ) ) ); +} + +Be::Result Tr2GeometryProvider::GetAreaIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>>>& result ) +{ + if( areaIx < -1 ) + { + // -1 is a special case handled to maintain legacy behavior. + return Be::Result( "Invalid area index" ); + } + + // In Python 3, passing a signed value to an unsigned C exposed attribute is an error. + // In order to maintain backwards compatible behavior with Python 2 + // this method got changed to accept signed integers and cast them to unsigned. + auto unsignedAreaIndex = static_cast( areaIx ); + + RayCastResult hitInfo; + bool hit = GetIntersectionPoints( pos, dir, hitInfo, unsignedAreaIndex ); + Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); + result = std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, std::make_pair( normal, std::make_pair( hitInfo.firstVertexColor, hitInfo.interpolatedVertexColor ) ) ) ) ); + return Be::Result(); +} + +void Tr2GeometryProvider::PrepareRayCaster() +{ +} + +void Tr2GeometryProvider::DestroyRayCaster() +{ +} + +void Tr2GeometryProvider::ResetRayCaster() +{ +} + +bool Tr2GeometryProvider::IsRayCasterReady() const +{ + return false; +} + +bool Tr2GeometryProvider::HasRayCasterPreparationFailed() const +{ + return true; +} + +bool Tr2GeometryProvider::GetIntersectionPoints( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx, float rayLength ) +{ + if( BeResMan->IsOnMainThread() ) + { + return GetIntersectionPointsLegacy( pos, dir, result, areaIx, rayLength ); + } + CCP_ASSERT_M( false, "Raycast fallback is not available in a multithreaded environment! You need to wait for IsRayCasterReady before raycasting!" ); + return false; +} + +// Careful: This function is slow and only works on main thread! +bool Tr2GeometryProvider::GetIntersectionPointsLegacy( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx, float rayLength ) +{ + USE_MAIN_THREAD_RENDER_CONTEXT(); + + bool hit = false; + + for( size_t i = 0; i < m_meshes.size(); i++ ) + { + if( m_meshes[i] == NULL ) + { + continue; + } + + //Get the first LOD. + auto& lod = m_meshes[i]->m_lods[0]; + + const uint8_t* pVertices; + const uint8_t* pIndices; + + int vertSize = m_meshes[i]->m_bytesPerVertex; + if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) + { + return false; + } + ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); + if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) + { + return false; + } + ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); + + const uint16_t* pShortIndices = (uint16_t*)pIndices; + const uint32_t* pLongIndices = (uint32_t*)pIndices; + + Tr2VertexDefinition decl; + if( !Tr2EffectStateManager::GetVertexDeclarationElements( m_meshes[i]->m_vertexDeclarationHandle, decl ) ) + { + return false; + } + const Tr2VertexDefinition::Item* const position = decl.Find( decl.POSITION ); + if( !position ) + { + return false; + } + + const Tr2VertexDefinition::Item* const blendIndices = decl.Find( decl.BLENDINDICES ); + const Tr2VertexDefinition::Item* const colors = decl.Find( decl.COLOR ); + + int numPrim = lod->m_primitiveCount; + auto currentIndex = 0; + if( areaIx != -1 ) + { + if( areaIx >= lod->m_areas.size() ) + { + continue; + } + currentIndex = lod->m_areas[areaIx].m_firstIndex; + numPrim = lod->m_areas[areaIx].m_primitiveCount; + } + + for( int j = 0; j < numPrim; j++ ) + { + unsigned int index1 = 0; + unsigned int index2 = 0; + unsigned int index3 = 0; + Vector3 p1; + Vector3 p2; + Vector3 p3; + float pu, pv, dist; + if( lod->m_indexAllocation.GetStride() == 2 ) + { + index1 = pShortIndices[currentIndex++]; + index2 = pShortIndices[currentIndex++]; + index3 = pShortIndices[currentIndex++]; + } + else + { + index1 = pLongIndices[currentIndex++]; + index2 = pLongIndices[currentIndex++]; + index3 = pLongIndices[currentIndex++]; + } + + ConvertTriangleData( position->m_dataType, vertSize, pVertices + position->m_offset, index1, index2, index3, &p1, &p2, &p3 ); + + if( IntersectTri( p1, p2, p3, pos, dir, pu, pv, dist ) ) + { + if( rayLength > dist ) + { + float v1 = 1.0f - ( pu + pv ); + + result.position = p1 * v1 + p2 * pu + p3 * pv; + + Vector3 avec = p2 - p1; + Vector3 bvec = p3 - p1; + result.unnormalizedNormal = Cross( avec, bvec ); + + result.distance = rayLength = dist; + + int boneIndex = 0; + if( blendIndices && GetBoneIndex( blendIndices->m_dataType, pVertices + index1 * vertSize + blendIndices->m_offset, boneIndex ) ) + { + result.boneIndex = boneIndex; + } + + Color color1; + Color color2; + Color color3; + if( colors && + GetColor( colors->m_dataType, pVertices + index1 * vertSize + colors->m_offset, color1 ) && + GetColor( colors->m_dataType, pVertices + index2 * vertSize + colors->m_offset, color2 ) && + GetColor( colors->m_dataType, pVertices + index3 * vertSize + colors->m_offset, color3 ) ) + { + result.firstVertexColor = color1; + result.interpolatedVertexColor = color1 * v1 + color2 * pu + color3 * pv; + } + + hit = true; + } + } + } + } + + return hit; +} + +unsigned int Tr2GeometryProvider::GetSkeletonCount() const +{ + return (unsigned int)m_skeletons.size(); +} + +TriGeometryResSkeletonData* Tr2GeometryProvider::GetSkeletonData( unsigned int skelIx ) const +{ + CCP_ASSERT( skelIx < m_skeletons.size() ); + + return m_skeletons[skelIx].get(); +} + +bool Tr2GeometryProvider::IsMemoryUsageKnown() +{ + return !IsLoading(); +} + +size_t Tr2GeometryProvider::GetMemoryUsage() +{ + return m_memoryUse; +} + +void Tr2GeometryProvider::ReleaseResourcesHelper() +{ + m_meshes.clear(); + // We don't clear m_skeletons here, because TriGeometryRes::ReleaseResources does not notify Tr2SkinnedModel. SetupSkeletons takes care of m_skeletons anyway. + + CCP_STATS_ADD( geometryResBytes, -(int)m_memoryUse ); + m_memoryUse = 0; +} + +bool Tr2GeometryProvider::IsUsingCMF() const +{ + return true; +} + +unsigned int TriGeometryResSkeletonData::FindJoint( const char* name ) const +{ + unsigned int jointCount = (unsigned int)m_joints.size(); + for( unsigned int ix = 0; ix < jointCount; ++ix ) + { + if( strcmp( name, m_joints[ix].m_name.c_str() ) == 0 ) + { + return ix; + } + } + + return 0xffffffff; +} + +TriGeometryResSkeletonData::TriGeometryResSkeletonData() : + m_joints( "TriGeometryResSkeletonData/m_joints" ) +{ +} + +TriGeometryResAreaData::TriGeometryResAreaData() : + m_firstIndex( 0 ), + m_primitiveCount( 0 ), + m_jointBindings( "TriGeometryResAreaData/m_jointBindings" ), + m_isSkinned( false ), + m_isMorphed( false ) +{ +} + +TriGeometryResLodData::TriGeometryResLodData() : + m_vertexCount( 0 ), + m_primitiveCount( 0 ), + m_areas( "TriGeometryResMeshData/m_areas" ), + m_morphVertexDeclaration( Tr2EffectStateManager::UNINITIALIZED_DECLARATION ), + m_allocationsValid( false ), + m_reversedIndicesValid( false ), + m_uvDensities(), + m_bytesPerMorphTargetVertex( 0 ) +{ +} + + +TriGeometryResMeshData::TriGeometryResMeshData() : + + m_vertexDeclarationHandle( Tr2EffectStateManager::UNINITIALIZED_DECLARATION ), + m_bytesPerVertex( -1 ), + m_jointBindings( "TriGeometryResMeshData/m_jointBindings" ), + m_lods( "TriGeometryResModelData/m_lods" ) +{ +} + +bool Tr2GeometryProvider::RenderAreas( unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed ) +{ + return RenderAreas( std::numeric_limits::max(), meshIx, areaIx, areaCount, renderContext, reversed ); +} + +bool Tr2GeometryProvider::RenderAreas( float screenSize, unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed ) +{ + if( !m_isGood ) + { + return false; + } + + if( meshIx >= m_meshes.size() ) + { + return false; + } + + TriGeometryResMeshData* mesh = m_meshes[meshIx].get(); + if( !mesh ) + { + return false; + } + + TriGeometryResLodData* lod = GetMeshLod( meshIx, screenSize ); + + unsigned int primCount = GetPrimitiveCount( *lod, areaIx, areaCount ); + + if( primCount ) + { + auto indexRange = GetAreaIndexRange( *lod, areaIx, primCount, reversed ); + if( !indexRange.valid ) + { + return false; + } + + renderContext.m_esm.ApplyVertexDeclaration( mesh->m_vertexDeclarationHandle ); + renderContext.m_esm.ApplyStreamSource( 0, lod->m_vertexAllocation ); + renderContext.m_esm.ApplyIndexBuffer( *indexRange.indices ); + + renderContext.SetTopology( TOP_TRIANGLES ); + renderContext.DrawIndexedPrimitive( lod->m_vertexCount, indexRange.startIndex, primCount ); + } + + return true; +} + +Be::Result Tr2GeometryProvider::SaveMesh( const char* filename, uint32_t meshIndex ) const +{ + if( !IsGood() ) + { + return Be::Result( "Geometry is not prepared" ); + } + auto meshData = GetMeshData( meshIndex ); + if( !meshData ) + { + return Be::Result( "Invalid mesh index" ); + } + + if( !SaveMeshToCMFFile( *meshData, filename ) ) + { + return Be::Result( "Failed to save the mesh" ); + } + + return Be::Result(); +} + +bool Tr2GeometryProvider::SaveMeshToCMFFile( TriGeometryResMeshData& mesh, const char* filename ) +{ + USE_MAIN_THREAD_RENDER_CONTEXT(); + + if( mesh.m_vertexDeclarationHandle == Tr2EffectStateManager::UNINITIALIZED_DECLARATION ) + { + return false; + } + + auto& lod = mesh.m_lods[0]; + + if( !lod->m_allocationsValid ) + { + return false; + } + + Tr2VertexDefinition decl; + if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh.m_vertexDeclarationHandle, decl ) ) + { + return false; + } + + cmf::MemoryAllocator allocator; + cmf::BufferManager bufferAllocator( allocator ); + cmf::Span cmfVertexDecl = allocator.AllocateSpan( decl.m_items.size() ); + + if( !ConvertVertexDeclToCMF( decl, cmfVertexDecl, (uint32_t)cmfVertexDecl.size() ) ) + { + return false; + } + + uint32_t vertexBufferSize = lod->m_vertexAllocation.GetSize(); + uint32_t vertexBufferStride = lod->m_vertexAllocation.GetStride(); + const void* pVertices; + if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) + { + return false; + } + ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); + + uint32_t indexBufferSize = lod->m_indexAllocation.GetSize(); + uint32_t indexBufferStride = lod->m_indexAllocation.GetStride(); + const void* pIndices; + if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) + { + return false; + } + ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); + + + cmf::Data cmfData; + cmfData.meshes = allocator.AllocateSpan( 1 ); + + cmf::Mesh& cmfMesh = cmfData.meshes[0] = {}; + cmfMesh.name = allocator.AllocateString( mesh.m_name ); + cmfMesh.decl = cmfVertexDecl; + cmfMesh.lods = allocator.AllocateSpan( 1 ); + cmfMesh.areas = allocator.AllocateSpan( lod->m_areas.size() ); + cmfMesh.boneBindings = allocator.AllocateSpan( mesh.m_jointBindings.size() ); + cmfMesh.uvDensities = allocator.AllocateSpan( lod->m_uvDensities.size() ); + cmfMesh.bounds = AxisAlignedBoundingBox( mesh.m_minBounds, mesh.m_maxBounds ); + + memcpy( cmfMesh.uvDensities.data(), lod->m_uvDensities.data(), lod->m_uvDensities.size() * sizeof( float ) ); + + cmf::MeshLod& cmfMeshLod = cmfMesh.lods[0]; + cmfMeshLod.vb = bufferAllocator.AddBuffer( (void*)pVertices, vertexBufferSize, vertexBufferStride ); + cmfMeshLod.ib = bufferAllocator.AddBuffer( (void*)pIndices, indexBufferSize, indexBufferStride ); + cmfMeshLod.areas = allocator.AllocateSpan( lod->m_areas.size() ); + cmfMeshLod.morphTargets = cmf::Span(); + cmfMeshLod.threshold = (uint32_t)lod->m_maxScreenSize; + + for( int32_t i = 0; i < cmfMesh.areas.size(); i++ ) + { + cmf::MeshArea& cmfMeshArea = cmfMesh.areas[i]; + cmfMeshArea.name = allocator.AllocateString( lod->m_areas[i].m_name ); + cmfMeshArea.bounds = AxisAlignedBoundingBox( lod->m_areas[i].m_minBounds, lod->m_areas[i].m_maxBounds ); + cmfMeshArea.bones = allocator.AllocateSpan( lod->m_areas[i].m_jointBindings.size() ); + cmfMeshArea.affectedByBones = lod->m_areas[i].m_isSkinned; + cmfMeshArea.affectedByMorphTargets = false; + + for( int32_t j = 0; j < lod->m_areas[i].m_jointBindings.size(); j++ ) + { + cmfMeshArea.bones[j] = (uint16_t)lod->m_areas[i].m_jointBindings[j]; + } + + cmf::LodMeshArea& lodMeshArea = cmfMeshLod.areas[i]; + lodMeshArea.firstElement = lod->m_areas[i].m_firstIndex / 3; + lodMeshArea.elementCount = lod->m_areas[i].m_primitiveCount; + } + + for( int32_t i = 0; i < cmfMesh.boneBindings.size(); i++ ) + { + cmf::BoneBinding& cmfBoneBinding = cmfMesh.boneBindings[i]; + cmfBoneBinding.name = allocator.AllocateString( mesh.m_jointBindings[i].m_name ); + cmfBoneBinding.bounds = AxisAlignedBoundingBox( mesh.m_jointBindings[i].m_obbMin, mesh.m_jointBindings[i].m_obbMax ); + } + + auto file = cmf::BuildFile( cmfData, bufferAllocator ); + + std::wstring filenameW = (const wchar_t*)CA2W( filename ); + std::wstring fullPath = BePaths->ResolvePathForWritingW( filenameW ); + + FILE* outFile; + auto err = fopen_s( &outFile, CW2A( fullPath.c_str() ), "wb" ); + + if( err != 0 ) + { + CCP_LOGERR( "Failed to open output file: %ls\n", fullPath.c_str() ); + return false; + } + if( fwrite( file.data(), 1, file.size(), outFile ) != file.size() ) + { + CCP_LOGERR( "Failed to write output file: %ls\n", fullPath.c_str() ); + fclose( outFile ); + return false; + } + fclose( outFile ); + + return true; +} + +#if WITH_GRANNY +bool Tr2GeometryProvider::SaveMeshToGrannyFile( TriGeometryResMeshData& mesh, const char* filename ) +{ + USE_MAIN_THREAD_RENDER_CONTEXT(); + + if( mesh.m_vertexDeclarationHandle == Tr2EffectStateManager::UNINITIALIZED_DECLARATION ) + { + return false; + } + + auto& lod = mesh.m_lods[0]; + + if( !lod->m_allocationsValid ) + { + return false; + } + + granny_data_type_definition grannyVertexDecl[16]; + + Tr2VertexDefinition decl; + if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh.m_vertexDeclarationHandle, decl ) ) + { + return false; + } + + if( !ConvertVertexDeclToGranny( decl, grannyVertexDecl, sizeof( grannyVertexDecl ) / sizeof( grannyVertexDecl[0] ) ) ) + { + return false; + } + + const void* pVertices; + if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) + { + return false; + } + ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); + + const void* pIndices; + if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) + { + return false; + } + ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); + + + granny_vertex_data myVertexData; + memset( &myVertexData, 0, sizeof( myVertexData ) ); + myVertexData.Vertices = (granny_uint8*)pVertices; + myVertexData.VertexCount = lod->m_vertexCount; + myVertexData.VertexType = grannyVertexDecl; + + granny_tri_topology myTopology; + memset( &myTopology, 0, sizeof( myTopology ) ); + + if( lod->m_vertexCount <= 65535 ) + { + myTopology.Indices16 = (granny_uint16*)pIndices; + myTopology.Index16Count = lod->m_primitiveCount * 3; + } + else + { + myTopology.Indices = (granny_int32*)pIndices; + myTopology.IndexCount = lod->m_primitiveCount * 3; + } + + myTopology.GroupCount = (granny_int32)lod->m_areas.size(); + myTopology.Groups = CCP_NEW( "myTopology.Groups" ) granny_tri_material_group[myTopology.GroupCount]; + if( myTopology.Groups ) + { + for( int i = 0; i < myTopology.GroupCount; ++i ) + { + myTopology.Groups[i].MaterialIndex = i; + myTopology.Groups[i].TriFirst = lod->m_areas[i].m_firstIndex / 3; + myTopology.Groups[i].TriCount = lod->m_areas[i].m_primitiveCount; + } + } + else + { + myTopology.GroupCount = 0; + } + + granny_mesh grannyMesh; + memset( &grannyMesh, 0, sizeof( grannyMesh ) ); + + grannyMesh.PrimaryVertexData = &myVertexData; + grannyMesh.PrimaryTopology = &myTopology; + + granny_file_info fileInfo; + memset( &fileInfo, 0, sizeof( fileInfo ) ); + + granny_mesh* meshes[] = { &grannyMesh }; + granny_tri_topology* topologies[] = { grannyMesh.PrimaryTopology }; + granny_vertex_data* vertexDatas[] = { grannyMesh.PrimaryVertexData }; + + fileInfo.ModelCount = 0; + fileInfo.Models = 0; + fileInfo.MeshCount = 1; + fileInfo.Meshes = meshes; + fileInfo.VertexDataCount = 1; + fileInfo.VertexDatas = vertexDatas; + fileInfo.TriTopologyCount = 1; + fileInfo.TriTopologies = topologies; + + fileInfo.MaterialCount = (granny_int32)lod->m_areas.size(); + fileInfo.Materials = CCP_NEW( "fileInfo.Materials" ) granny_material * [fileInfo.MaterialCount]; + if( fileInfo.Materials == nullptr ) + { + fileInfo.MaterialCount = 0; + } + + grannyMesh.MaterialBindingCount = fileInfo.MaterialCount; + grannyMesh.MaterialBindings = CCP_NEW( "grannyMesh.MaterialBindings" ) granny_material_binding[fileInfo.MaterialCount]; + if( grannyMesh.MaterialBindings == nullptr ) + { + grannyMesh.MaterialBindingCount = 0; + } + + granny_material* pMaterials = nullptr; + if( fileInfo.MaterialCount ) + { + pMaterials = CCP_NEW( "pMaterials" ) granny_material[fileInfo.MaterialCount]; + if( pMaterials ) + { + memset( pMaterials, 0, sizeof( granny_material ) * fileInfo.MaterialCount ); + + for( int i = 0; i < fileInfo.MaterialCount; ++i ) + { + fileInfo.Materials[i] = &pMaterials[i]; + fileInfo.Materials[i]->Name = lod->m_areas[i].m_name.c_str(); + grannyMesh.MaterialBindings[i].Material = fileInfo.Materials[i]; + } + } + } + + // Bones + grannyMesh.BoneBindingCount = (granny_int32)mesh.m_jointBindings.size(); + grannyMesh.BoneBindings = CCP_NEW( "grannyMesh.BoneBindings" ) granny_bone_binding[grannyMesh.BoneBindingCount]; + if( grannyMesh.BoneBindings == nullptr ) + { + grannyMesh.BoneBindingCount = 0; + } + if( grannyMesh.BoneBindingCount > 0 ) + { + memset( grannyMesh.BoneBindings, 0, sizeof( granny_bone_binding ) * grannyMesh.BoneBindingCount ); + for( int i = 0; i < grannyMesh.BoneBindingCount; ++i ) + { + grannyMesh.BoneBindings[i].BoneName = mesh.m_jointBindings[i].m_name.c_str(); + grannyMesh.BoneBindings[i].OBBMin[0] = mesh.m_jointBindings[i].m_obbMin.x; + grannyMesh.BoneBindings[i].OBBMin[1] = mesh.m_jointBindings[i].m_obbMin.y; + grannyMesh.BoneBindings[i].OBBMin[2] = mesh.m_jointBindings[i].m_obbMin.z; + grannyMesh.BoneBindings[i].OBBMax[0] = mesh.m_jointBindings[i].m_obbMax.x; + grannyMesh.BoneBindings[i].OBBMax[1] = mesh.m_jointBindings[i].m_obbMax.y; + grannyMesh.BoneBindings[i].OBBMax[2] = mesh.m_jointBindings[i].m_obbMax.z; + } + } + + + granny_file_builder* Builder = GrannyBeginFile( 1, GrannyCurrentGRNStandardTag, GrannyGRNFileMV_32Bit_LittleEndian, GrannyGetTemporaryDirectory(), "GRNFileTemp" ); + granny_file_data_tree_writer* Writer = + GrannyBeginFileDataTreeWriting( GrannyFileInfoType, &fileInfo, 0, 0 ); + GrannyWriteDataTreeToFileBuilder( Writer, Builder ); + GrannyEndFileDataTreeWriting( Writer ); + + std::wstring filenameW = (const wchar_t*)CA2W( filename ); + std::wstring fullPath = BePaths->ResolvePathForWritingW( filenameW ); + GrannyEndFile( Builder, CW2A( fullPath.c_str() ) ); + + CCP_DELETE[] pMaterials; + CCP_DELETE[] grannyMesh.MaterialBindings; + CCP_DELETE[] grannyMesh.BoneBindings; + CCP_DELETE[] fileInfo.Materials; + + return true; +} +#endif + +// -------------------------------------------------------------------------------------- +// Description: +// Implements ITr2InstanceData interface. Query if the instance data is ready to be +// used, i.e. if the resource is good. +// Return Value: +// true if data is ready to be used +// false otherwise +// -------------------------------------------------------------------------------------- +bool Tr2GeometryProvider::IsInstanceDataReady() const +{ + return IsGood(); +} + +ITr2InstanceData::InstanceData Tr2GeometryProvider::GetInstanceData( unsigned int bufferIndex, float screenSize ) const +{ + auto mesh = GetMeshData( bufferIndex ); + + if( !mesh ) + { + static Tr2BufferAL nullVB; + return { nullVB }; + } + + auto lod = GetMeshLod( bufferIndex, screenSize ); + return { + lod->m_vertexAllocation.GetBuffer(), lod->m_vertexAllocation.GetOffset(), mesh->m_bytesPerVertex, lod->m_vertexCount + }; +} + +// -------------------------------------------------------------------------------------- +// Description: +// Implements ITr2InstanceData interface. Returns returns vertex declaration handle +// for the given instance buffer index. +// Arguments: +// bufferIndex - instance buffer index +// Return Value: +// vertex declaration handle +// -------------------------------------------------------------------------------------- +unsigned int Tr2GeometryProvider::GetInstanceBufferVertexDeclaration( unsigned int bufferIndex ) const +{ + if( bufferIndex < m_meshes.size() && m_meshes[bufferIndex] ) + { + return m_meshes[bufferIndex]->m_vertexDeclarationHandle; + } + return Tr2EffectStateManager::UNINITIALIZED_DECLARATION; +} + +CcpMath::AxisAlignedBox Tr2GeometryProvider::GetInstanceBufferBoundingBox( unsigned int bufferIndex ) const +{ + auto mesh = GetMeshData( bufferIndex ); + if( !mesh ) + { + return CcpMath::AxisAlignedBox(); + } + return CcpMath::AxisAlignedBox( mesh->m_minBounds, mesh->m_maxBounds ); +} + +Be::Result Tr2GeometryProvider::GetMeshName( unsigned int meshIx, std::string& name ) const +{ + TriGeometryResMeshData* mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return Be::Result( "Mesh index out of range, or invalid mesh" ); + } + + name = mesh->m_name; + + return Be::Result(); +} + +Be::Result Tr2GeometryProvider::GetMeshAreaCount( unsigned int meshIx, int& count ) const +{ + TriGeometryResMeshData* mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return Be::Result( "Mesh index out of range, or invalid mesh" ); + } + + //Get the first LOD. + auto& lod = mesh->m_lods[0]; + + count = (int)lod->m_areas.size(); + + return Be::Result(); +} + +Be::Result Tr2GeometryProvider::GetMeshAreaName( unsigned int meshIx, unsigned int areaIx, std::string& name ) const +{ + TriGeometryResMeshData* mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return Be::Result( "Mesh index out of range, or invalid mesh" ); + } + + //Get the first LOD. + auto& lod = mesh->m_lods[0]; + + if( areaIx < 0 || areaIx >= lod->m_areas.size() ) + { + return Be::Result( "Area index out of range" ); + } + + name = lod->m_areas[areaIx].m_name; + + return Be::Result(); +} + +Be::Result Tr2GeometryProvider::GetAreaBoundingBoxFromScript( unsigned int meshIx, unsigned int areaIx, std::pair& bounds ) +{ + TriGeometryResMeshData* mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return Be::Result( "Mesh index out of range, or invalid mesh" ); + } + + //Get the first LOD. + auto& lod = mesh->m_lods[0]; + + if( areaIx < 0 || areaIx >= lod->m_areas.size() ) + { + return Be::Result( "Area index out of range" ); + } + + bounds = std::make_pair( lod->m_areas[areaIx].m_minBounds, lod->m_areas[areaIx].m_maxBounds ); + + return Be::Result(); +} + +Be::Result Tr2GeometryProvider::GetBoundingSphereFromScript( unsigned int meshIx, std::pair& bounds ) const +{ + if( meshIx >= m_meshes.size() ) + { + return Be::Result( "Mesh index out of range" ); + } + auto& mesh = m_meshes[meshIx]; + if( !mesh ) + { + return Be::Result( "Invalid mesh" ); + } + + bounds = std::make_pair( Vector3( mesh->m_boundingSphere.x, mesh->m_boundingSphere.y, mesh->m_boundingSphere.z ), mesh->m_boundingSphere.w ); + return Be::Result(); +} + +Be::Result Tr2GeometryProvider::CalculateBoundingBoxFromTransform( unsigned int meshIx, const Matrix& transform, std::pair& bounds ) +{ + if( meshIx >= m_meshes.size() ) + { + return Be::Result( "Mesh index out of range" ); + } + auto& mesh = m_meshes[meshIx]; + if( !mesh ) + { + return Be::Result( "Invalid mesh" ); + } + + CalcBoundingBoxContext ctx; + ctx.min = Vector3( FLT_MAX, FLT_MAX, FLT_MAX ); + ctx.max = Vector3( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + ctx.transform = transform; + + if( meshIx < m_meshes.size() ) + { + ProcessMeshTriangles( meshIx, &CalcBoundingBox, &ctx ); + } + + bounds = std::make_pair( ctx.min, ctx.max ); + return Be::Result(); +} + + +BlueStdResult Tr2GeometryProvider::GetMeshVertexElements( size_t meshIndex, std::vector>& elements ) const +{ + elements.clear(); + + if( !IsGood() ) + { + return BlueStdResult( BLUE_STD_RESULT_RUNTIME_ERROR, "TriGeometryRes is not prepared" ); + } + if( meshIndex >= m_meshes.size() || !m_meshes[meshIndex] ) + { + return BlueStdResult( BLUE_STD_RESULT_INDEX_ERROR, "mesh index out of range" ); + } + + auto& mesh = m_meshes[meshIndex]; + Tr2VertexDefinition decl; + if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh->m_vertexDeclarationHandle, decl ) ) + { + return BlueStdResult( BLUE_STD_RESULT_RUNTIME_ERROR, "could not retrieve vertex declaration" ); + } + + for( auto it = std::begin( decl.m_items ); it != std::end( decl.m_items ); ++it ) + { + elements.push_back( std::make_pair( it->m_usage, it->m_usageIndex ) ); + } + return BLUE_STD_RESULT_OK; +} + +bool Tr2GeometryProvider::SetupMeshesFromCMF( const cmf::Data& cmfData ) +{ + TRINITY_STATS_ZONE( __FUNCTION__ ); + + int minimumLOD = gTriDev->GetMinimumModelLOD(); + + for( const cmf::Mesh& cmfMesh : cmfData.meshes ) + { + Tr2VertexDefinition vertexDefinition = BuildFromCMFVertexDecl( cmfMesh.decl ); + const uint32_t vertexDeclarationHandle = Tr2EffectStateManager::GetVertexDeclarationHandle( vertexDefinition ); + unsigned int bytesPerVertex = cmfMesh.lods[0].vb.stride; + + TriGeometryResMeshData* mesh = new TriGeometryResMeshData(); + + mesh->m_name = cmf::ToStdString( cmfMesh.name ); + + mesh->m_minBounds = cmfMesh.bounds.m_min; + mesh->m_maxBounds = cmfMesh.bounds.m_max; + + CcpMath::Sphere sphere( cmfMesh.bounds ); + mesh->m_boundingSphere = Vector4( sphere.center, sphere.radius ); + + mesh->m_vertexDeclarationHandle = vertexDeclarationHandle; + mesh->m_bytesPerVertex = bytesPerVertex; + + for( const auto& joint : cmfMesh.boneBindings ) + { + TriJointBinding binding; + binding.m_name = cmf::ToStdString( joint.name ); + binding.m_obbMin = joint.bounds.m_min; + binding.m_obbMax = joint.bounds.m_max; + mesh->m_jointBindings.push_back( binding ); + } + + int32_t firstLodIndex = ClampInt( minimumLOD, 0, int32_t( cmfMesh.lods.size() ) - 1 ); + + for( int32_t i = firstLodIndex; i < cmfMesh.lods.size(); i++ ) + { + const cmf::MeshLod& cmfLod = cmfMesh.lods[i]; + + //Setup a new LOD, which will be added as an LOD of the mesh. + std::unique_ptr lod( new TriGeometryResLodData() ); + + lod->m_mesh = mesh; + + // TODO: intern, delete field once granny has been removed. + lod->m_grannyMeshIndex = 0xFFFFFFFF; + + lod->m_name = cmf::ToStdString( cmfMesh.name ); + + lod->m_maxScreenSize = float( cmfLod.threshold ); + + lod->m_vertexCount = cmf::GetStreamElementCount( cmfLod.vb ); + + lod->m_primitiveCount = cmf::GetStreamElementCount( cmfLod.ib ) / 3; + + // Get the UV densities, which are used for texture streaming. + lod->m_uvDensities = std::vector( cmfMesh.uvDensities.begin(), cmfMesh.uvDensities.end() ); + + lod->m_areas.resize( cmfLod.areas.size() ); + + for( int groupIndex = 0; groupIndex < cmfLod.areas.size(); ++groupIndex ) + { + const cmf::LodMeshArea& cmfLodMeshArea = cmfLod.areas[groupIndex]; + const cmf::MeshArea& cmfMeshArea = cmfMesh.areas[groupIndex]; + + TriGeometryResAreaData& area = lod->m_areas[groupIndex]; + area.m_name = cmf::ToStdString( cmfMeshArea.name ); + + area.m_firstIndex = cmfLodMeshArea.firstElement * 3; + area.m_primitiveCount = cmfLodMeshArea.elementCount; + + area.m_isSkinned = cmfMeshArea.affectedByBones; + area.m_isMorphed = cmfMeshArea.affectedByMorphTargets; + + area.m_minBounds = cmfMeshArea.bounds.m_min; + area.m_maxBounds = cmfMeshArea.bounds.m_max; + } + + lod->m_originalLodIndex = i; + + mesh->m_lods.push_back( std::move( lod ) ); + } + + uint32_t lodMask = 0u; + for( const auto& lod : mesh->m_lods ) + { + lodMask |= 1u << lod->m_originalLodIndex; + } + mesh->m_lodMask = lodMask; + + m_meshes.push_back( std::unique_ptr( mesh ) ); + } + + return true; +} + +void Tr2GeometryProvider::SetupSkeletonsFromCMF( const cmf::Data& cmfData ) +{ + TRINITY_STATS_ZONE( __FUNCTION__ ); + + m_skeletons.resize( cmfData.skeletons.size() ); + for( int skelIx = 0; skelIx < cmfData.skeletons.size(); ++skelIx ) + { + const cmf::Skeleton& skel = cmfData.skeletons[skelIx]; + std::unique_ptr pSkelData( new TriGeometryResSkeletonData() ); + + pSkelData->m_name = cmf::ToStdString( skel.name ); + pSkelData->m_joints.resize( skel.bones.size() ); + for( int jointIx = 0; jointIx < skel.bones.size(); ++jointIx ) + { + TriGeometryResJointData& joint = pSkelData->m_joints[jointIx]; + joint.m_name = cmf::ToStdString( skel.bones[jointIx] ); + joint.m_parentJoint = skel.parents[jointIx]; + joint.m_inverseWorldTransform = *(Matrix*)&skel.invBindTransforms[jointIx]; + } + + std::swap( m_skeletons[skelIx], pSkelData ); + } +} + +bool Tr2GeometryProvider::CreateMeshesFromCMFFile( Tr2CmfContents& cmfContents, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ) +{ + TRINITY_STATS_ZONE( __FUNCTION__ ); + + if( !cmfContents.GetData() ) + { + return false; + } + + for( int32_t i = 0; i < m_meshes.size(); i++ ) + { + auto& mesh = m_meshes[i]; + + for( const auto& lod : mesh->m_lods ) + { + CreateLodFromCMFMesh( cmfContents, cmfContents.GetData()->meshes[i], cmfContents.GetData()->meshes[i].lods[lod->m_originalLodIndex], lod.get(), cpuUsage, renderContext ); + } + + if( g_eveIsAudioOcclusionGeometryEnabled ) + { + const auto& audioMesh = cmfContents.GetData()->meshes[i].audioOcclusionMesh; + if( !audioMesh.vertices.empty() && !audioMesh.indices.empty() ) + { + auto audioGeometry = std::make_unique(); + audioGeometry->m_indices.insert( audioGeometry->m_indices.end(), audioMesh.indices.begin(), audioMesh.indices.end() ); + audioGeometry->m_vertices.insert( audioGeometry->m_vertices.end(), audioMesh.vertices.begin(), audioMesh.vertices.end() ); + audioGeometry->m_minBounds = audioMesh.bounds.m_min; + audioGeometry->m_maxBounds = audioMesh.bounds.m_max; + + mesh->m_audioGeometry = std::move( audioGeometry ); + } + } + } + + CCP_STATS_ADD( geometryResBytes, m_memoryUse ); + + return true; +} + +bool Tr2GeometryProvider::CreateLodFromCMFMesh( Tr2CmfContents& cmfContents, const cmf::Mesh& cmfMesh, const cmf::MeshLod& cmfMeshLod, TriGeometryResLodData* lod, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ) +{ + TRINITY_STATS_ZONE( __FUNCTION__ ); + + if( !Tr2Renderer::IsResourceCreationAllowed() ) + { + return false; + } + + Tr2VertexDefinition vertexDefinition = BuildFromCMFVertexDecl( cmfMesh.decl ); + const unsigned bytesPerVertex = cmfMeshLod.vb.stride; + const unsigned bytesPerIndex = cmfMeshLod.ib.stride; + + int vertexCount = cmf::GetStreamElementCount( cmfMeshLod.vb ); + int vbSize = cmfMeshLod.vb.size; + + CCP_ASSERT_M( cmfContents, "TriGeometryRes::CreateLodFromCMFMesh: cmfContents must be valid!" ); + + const void* pSrcVB = cmfContents.GetViewData( cmfMeshLod.vb ); + + int indexCount = cmf::GetStreamElementCount( cmfMeshLod.ib ); + + CR_RETURN_VAL( g_sharedBuffer.Allocate( bytesPerVertex, vertexCount, const_cast( pSrcVB ), renderContext, lod->m_vertexAllocation ), false ); + + // create d3d index buffer, this one is shared, either for dynamic or static geometry + int ibSize = indexCount * bytesPerIndex; + + const void* pSrcIB = cmfContents.GetViewData( cmfMeshLod.ib ); + + if( indexCount > 0 ) + { + ALResult hr = g_sharedBuffer.Allocate( bytesPerIndex, indexCount, const_cast( pSrcIB ), renderContext, lod->m_indexAllocation ); + if( FAILED( hr ) ) + { + g_sharedBuffer.Free( lod->m_vertexAllocation ); + return false; + } + + std::vector tempBuffer( indexCount * bytesPerIndex ); + memcpy( tempBuffer.data(), pSrcIB, tempBuffer.size() ); + if( bytesPerIndex == 2 ) + { + std::reverse( reinterpret_cast( tempBuffer.data() ), reinterpret_cast( tempBuffer.data() + cmfMeshLod.ib.size ) ); + } + else + { + std::reverse( reinterpret_cast( tempBuffer.data() ), reinterpret_cast( tempBuffer.data() + cmfMeshLod.ib.size ) ); + } + + if( FAILED( g_sharedBuffer.Allocate( bytesPerIndex, indexCount, tempBuffer.data(), renderContext, lod->m_reversedIndexAllocation ) ) ) + { + g_sharedBuffer.Free( lod->m_vertexAllocation ); + g_sharedBuffer.Free( lod->m_indexAllocation ); + return false; + } + lod->m_reversedIndicesValid = true; + } + + lod->m_morphVertexDeclaration = -1; + { // Morph Target + if( cmfMesh.morphTargets.targets.size() > 0 ) + { + // Allocate the morph target array based on the size of the first morph target + Tr2VertexDefinition morphTargetVertexDefinition = BuildFromCMFVertexDecl( cmfMesh.morphTargets.decl ); + lod->m_bytesPerMorphTargetVertex = cmfMeshLod.morphTargets[0].vb.stride; + uint32_t morphDataSize = vertexCount * lod->m_bytesPerMorphTargetVertex; + uint32_t morphTargetBufferSize = (uint32_t)( morphDataSize * cmfMesh.morphTargets.targets.size() + sizeof( TriMorphTargetGeometryConstants ) ); + + CR_RETURN_VAL( g_sharedBuffer.Allocate( + 4, + morphTargetBufferSize >> 2, + nullptr, + renderContext, + lod->m_morphTargetAllocation ), + false ); + + TriMorphTargetGeometryConstants morphTargetConstants = CreateMorphGeometryConstants( + morphTargetVertexDefinition, + lod->m_bytesPerMorphTargetVertex, + vertexCount, + renderContext ); + + lod->m_morphTargetAllocation.Update( &morphTargetConstants, 0, sizeof( TriMorphTargetGeometryConstants ), renderContext ); + + for( int i = 0; i < cmfMesh.morphTargets.targets.size(); ++i ) + { + const cmf::MorphTarget& morphTarget = cmfMesh.morphTargets.targets[i]; + + size_t nameLength = morphTarget.name.size(); + // TODO: intern, this "Shape" suffix will likely be gone later + // By convention (due to the exporter), the morph target name ends with "Shape". Assert that it does, and also that it is not an empty string! + std::string morphTargetName = cmf::ToStdString( morphTarget.name ); + if( nameLength > 5 && strcmp( morphTargetName.c_str() + nameLength - 5, "Shape" ) == 0 ) + { + morphTargetName = std::string( morphTarget.name.data(), nameLength - 5 ); + } + + lod->m_morphTargetNames.push_back( morphTargetName ); + + lod->m_isBakedMorphTarget.push_back( false ); + + const void* pMorphSrc = cmfContents.GetViewData( cmfMeshLod.morphTargets[i].vb ); + lod->m_morphTargetAllocation.Update( + const_cast( pMorphSrc ), + morphDataSize * i + sizeof( TriMorphTargetGeometryConstants ), + morphDataSize, + renderContext ); + + lod->m_morphTargetDeformationAmounts.push_back( morphTarget.maxDisplacement ); + } + + lod->m_morphVertexDeclaration = Tr2EffectStateManager::GetVertexDeclarationHandle( morphTargetVertexDefinition ); + } + } + + lod->m_allocationsValid = true; + + m_memoryUse += vbSize + ibSize; // Memory use is only approximate as a hint for the resource cache + + // Append reversed index buffer size also + m_memoryUse += ibSize; + + return true; +} diff --git a/trinity/Resources/Tr2GeometryProvider.h b/trinity/Resources/Tr2GeometryProvider.h new file mode 100644 index 000000000..f426bb155 --- /dev/null +++ b/trinity/Resources/Tr2GeometryProvider.h @@ -0,0 +1,341 @@ +// Copyright © 2026 CCP ehf. + +#pragma once +#ifndef Tr2GeometryProvider_h +#define Tr2GeometryProvider_h + +#include "Tr2DeviceResource.h" +#include "include/ITr2InstanceData.h" +#include "Tr2CmfContent.h" +#include "Tr2SuballocatedBuffer.h" + +constexpr uint32_t SHARED_BUFFER_BLOCK_SIZE = 32u * 1024u * 1024u; +constexpr uint32_t SHARED_BUFFER_MAX_SIZE = 2048u * 1024u * 1024u; +extern Tr2SuballocatedBuffer g_sharedBuffer; + +class Tr2RenderContext; + + +struct TriRtGeometryConstants +{ + uint32_t indexBufferId; + uint32_t indexBufferStride; + + uint32_t indexOffset; + + uint32_t vertexBufferId; + uint32_t vertexBufferStride; + + uint32_t positionOffset; + uint32_t positionType; + + uint32_t normalOffset; + uint32_t normalType; + + uint32_t tangentOffset; + uint32_t tangentType; + + uint32_t bitangentOffset; + uint32_t bitangentType; + + uint32_t texCoord0Offset; + uint32_t texCoord0Type; + + uint32_t texCoord1Offset; + uint32_t texCoord1Type; + + uint32_t texCoord2Offset; + uint32_t texCoord2Type; + + uint32_t padding; +}; + +struct TriGeometryResAreaData +{ + TriGeometryResAreaData(); + + std::string m_name; + int m_firstIndex; + int m_primitiveCount; + Vector3 m_minBounds; + Vector3 m_maxBounds; + TrackableStdVector m_jointBindings; + + Tr2RtBottomLevelAccelerationStructureAL m_staticBlas; + bool m_isSkinned; + bool m_isMorphed; + Tr2ConstantBufferAL m_rtGeometryConstants; +}; + + +struct TriJointBinding +{ + std::string m_name; + Vector3 m_obbMin; + Vector3 m_obbMax; +}; + +struct MeshDecalLodData +{ + uint32_t m_startIndex = 0; + uint32_t m_primitiveCount = 0; +}; + +struct MeshDecalData +{ + Matrix m_inverseDecalMatrix = IdentityMatrix(); // used as a key + Tr2SuballocatedBuffer::Allocation m_indexBuffer; + uint32_t m_lodMask; //A bit mask of which LODs have been loaded. + std::vector m_lods; +}; + +struct TriGeometryResMeshData; + +extern bool g_eveIsAudioOcclusionGeometryEnabled; + +struct AudioGeometryResData +{ + uint64_t m_id; + + std::vector m_vertices; + std::vector m_indices; + + Vector3 m_minBounds; + Vector3 m_maxBounds; + + AudioGeometryResData() : + m_id( s_nextId++ ) + { + } + +private: + static std::atomic s_nextId; +}; + +struct TriGeometryResLodData +{ + TriGeometryResLodData(); + + TriGeometryResMeshData* m_mesh; + + int32_t m_grannyMeshIndex; + + std::string m_name; + + int32_t m_originalLodIndex; + float m_maxScreenSize; + + unsigned int m_vertexCount; + unsigned int m_primitiveCount; + + std::vector m_uvDensities; + + TrackableStdVector m_areas; + + bool m_allocationsValid; + Tr2SuballocatedBuffer::Allocation m_vertexAllocation; + Tr2SuballocatedBuffer::Allocation m_indexAllocation; + + Tr2SuballocatedBuffer::Allocation m_morphTargetAllocation; + + std::vector m_morphTargetNames; + std::vector m_morphTargetDeformationAmounts; + std::vector m_isBakedMorphTarget; + + unsigned int m_morphVertexDeclaration; + unsigned int m_bytesPerMorphTargetVertex; + + // Index buffer with indexes in reversed order (used by hair/clothing) + bool m_reversedIndicesValid; + Tr2SuballocatedBuffer::Allocation m_reversedIndexAllocation; +}; + +struct TriGeometryResMeshData +{ + TriGeometryResMeshData(); + + std::string m_name; + + unsigned int m_vertexDeclarationHandle; + unsigned int m_bytesPerVertex; + + Vector3 m_minBounds; + Vector3 m_maxBounds; + Vector4 m_boundingSphere; + + TrackableStdVector m_jointBindings; + + std::unique_ptr m_audioGeometry; + std::vector> m_decals; + + uint32_t m_lodMask; //A bit mask of which LODs have been loaded. + TrackableStdVector> m_lods; +}; + +uint32_t GetPrimitiveCount( const TriGeometryResLodData& lod, uint32_t index, uint32_t count ); + +struct TriGeometryAreaIndexRange +{ + const Tr2SuballocatedBuffer::Allocation* indices = nullptr; + uint32_t startIndex = 0; + bool valid = false; +}; +TriGeometryAreaIndexRange GetAreaIndexRange( const TriGeometryResLodData& lod, uint32_t areaIndex, uint32_t primCount, bool reversed ); + +struct TriGeometryResJointData +{ + std::string m_name; + unsigned int m_parentJoint; + Matrix m_inverseWorldTransform; +}; + +struct TriGeometryResSkeletonData +{ + TriGeometryResSkeletonData(); + + unsigned int FindJoint( const char* name ) const; + + std::string m_name; + TrackableStdVector m_joints; +}; + +struct RayCastResult +{ + float distance = std::numeric_limits::infinity(); + Vector3 position; + Vector3 unnormalizedNormal; + int boneIndex = -1; + Color firstVertexColor; + Color interpolatedVertexColor; +}; + +struct TriMorphTargetGeometryConstants +{ + uint32_t vertexBufferStride; + + uint32_t positionOffset; + uint32_t positionType; + + uint32_t tangentOffset; + uint32_t tangentType; + + uint32_t vertexCount; +}; + +TriMorphTargetGeometryConstants CreateMorphGeometryConstants( const Tr2VertexDefinition& vertexDefinition, uint32_t stride, uint32_t vertexCount, Tr2RenderContext& renderContext ); + + +BLUE_CLASS_IMPL( Tr2GeometryProvider ) +class Tr2GeometryProvider : public BlueAsyncRes, + public ICacheable, + public Tr2DeviceResource, + public ITr2InstanceData +{ +public: + EXPOSE_TO_BLUE(); + + void RecalculateBoundingSphere(); + Be::Result CalculateBoundingBoxFromTransform( unsigned int meshIx, const Matrix& transform, std::pair& bounds ); + + unsigned int GetMeshCount() const; + Be::Result GetMeshName( unsigned int meshIx, std::string& name ) const; + Be::Result GetMeshAreaCount( unsigned int meshIx, int& count ) const; + Be::Result GetMeshAreaName( unsigned int meshIx, unsigned int areaIx, std::string& name ) const; + TriGeometryResMeshData* GetMeshData( unsigned int meshIx ) const; + TriGeometryResLodData* GetMeshLod( unsigned int meshIx, float screenSize ) const; + TriGeometryResLodData* GetMeshLod( unsigned int meshIx, int lodIndex ) const; + int GetLodIndexForScreenSize( unsigned int meshIx, float screenSize ) const; + + const AudioGeometryResData* GetAudioGeometry( unsigned int meshIx ) const; + + unsigned int GetSkeletonCount() const; + TriGeometryResSkeletonData* GetSkeletonData( unsigned int skelIx ) const; + + unsigned int GetAreaCount( unsigned int meshIx ) const; + Be::Result GetAreaBoundingBoxFromScript( unsigned int meshIx, unsigned int areaIx, std::pair& bounds ); + TriGeometryResAreaData* GetAreaData( unsigned int meshIx, unsigned int areaIx ) const; + + // Render multiple consecutive areas, starting at 'areaIx' + bool RenderAreas( unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed = false ); + bool RenderAreas( float screenSize, unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed = false ); + + virtual bool GetIntersectionPoints( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx = -1, float rayLength = std::numeric_limits::infinity() ); + bool GetIntersectionPointsLegacy( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx = -1, float rayLength = std::numeric_limits::infinity() ); + + virtual void PrepareRayCaster(); + virtual void ResetRayCaster(); + virtual bool IsRayCasterReady() const; + virtual bool HasRayCasterPreparationFailed() const; + virtual void DestroyRayCaster(); + + std::pair>> GetIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir ); + Be::Result GetAreaIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>& result ); + + std::pair>>>> GetIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir ); + Be::Result GetAreaIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>>>& result ); + + bool GetBoundingBox( unsigned int meshIx, Vector3& min, Vector3& max ) const; + Be::Result GetBoundingBoxFromScript( unsigned int meshIx, std::pair& bounds ) const; + bool GetAreaBoundingBox( unsigned int meshIx, unsigned int areaIx, Vector3& min, Vector3& max ) const; + bool GetBoundingSphere( unsigned int meshIx, Vector4& sphere ) const; + Be::Result GetBoundingSphereFromScript( unsigned int meshIx, std::pair& bounds ) const; + + BlueStdResult GetMeshVertexElements( size_t meshIndex, std::vector>& elements ) const; + + ///////////////////////////////////////////////////////////////////////////////////// + // ITr2InstanceData + bool IsInstanceDataReady() const override; + InstanceData GetInstanceData( unsigned int bufferIndex, float screenSize ) const override; + unsigned int GetInstanceBufferVertexDeclaration( unsigned int bufferIndex ) const override; + CcpMath::AxisAlignedBox GetInstanceBufferBoundingBox( unsigned int bufferIndex ) const override; + + ///////////////////////////////////////////////////////////////////////////////////// + // ICacheable + bool IsMemoryUsageKnown(); + size_t GetMemoryUsage(); + + ///////////////////////////////////////////////////////////////////////////////////// + // ITriDeviceResource + + void ReleaseResourcesHelper(); + + virtual bool IsUsingCMF() const; + +#if TRINITYDEV + virtual void GetDescription( std::string& desc ); +#endif + +#if WITH_GRANNY + static bool SaveMeshToGrannyFile( TriGeometryResMeshData& mesh, const char* filename ); +#endif + static bool SaveMeshToCMFFile( TriGeometryResMeshData& mesh, const char* filename ); + + // Iterator functions for processing mesh data + typedef void ( *PerTriangleCallback )( void* context, const Vector3& p1, const Vector3& p2, const Vector3& p3 ); + void ProcessMeshTriangles( int meshIx, PerTriangleCallback cb, void* cbContext ); + + // name for logging/debugging + std::string m_name; + + virtual Be::Result SaveMesh( const char* filename, uint32_t meshIndex ) const; + + +protected: + Tr2GeometryProvider( IRoot* lockobj = NULL ); + + bool SetupMeshesFromCMF( const cmf::Data& cmfData ); + void SetupSkeletonsFromCMF( const cmf::Data& cmfData ); + bool CreateMeshesFromCMFFile( Tr2CmfContents& cmfContents, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ); + bool CreateLodFromCMFMesh( Tr2CmfContents& cmfContents, const cmf::Mesh& cmfMesh, const cmf::MeshLod& cmfMeshLod, TriGeometryResLodData* pMesh, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ); + + unsigned int m_memoryUse; + TrackableStdVector> m_meshes; + TrackableStdVector> m_skeletons; + + int32_t m_forcedLodIndex = -1; + bool m_forceLod = false; +}; + +TYPEDEF_BLUECLASS_WR_SHUTDOWN( Tr2GeometryProvider ); + +#endif diff --git a/trinity/Resources/Tr2GeometryProvider_Blue.cpp b/trinity/Resources/Tr2GeometryProvider_Blue.cpp new file mode 100644 index 000000000..d328f69ac --- /dev/null +++ b/trinity/Resources/Tr2GeometryProvider_Blue.cpp @@ -0,0 +1,195 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2GeometryProvider.h" +#include "Utilities/BoundingSphere.h" + +BLUE_DEFINE_ABSTRACT( Tr2GeometryProvider ); + + +const Be::ClassInfo* Tr2GeometryProvider::ExposeToBlue() +{ + EXPOSURE_BEGIN( Tr2GeometryProvider, "" ) + MAP_INTERFACE( Tr2GeometryProvider ) + MAP_INTERFACE( IBlueResource ) + MAP_INTERFACE( ICacheable ) + MAP_INTERFACE( ITr2InstanceData ) + MAP_ICACHEABLE_METHODS() + + MAP_PROPERTY_READONLY( + "meshCount", + GetMeshCount, + "Gets the count of meshes in this geometry resource" ) + MAP_ATTRIBUTE( "name", m_name, "Name for debugging/logging", Be::READWRITE ) + + MAP_ATTRIBUTE( "forceLod", m_forceLod, "Force geometry to use a specific LOD\n:jessica-group: Debug", Be::READWRITE ) + MAP_ATTRIBUTE( "forcedLodIndex", m_forcedLodIndex, "LOD index to use when forceLod is on\n:jessica-group: Debug", Be::READWRITE ) + + MAP_METHOD_AND_WRAP( + "GetMeshCount", + GetMeshCount, + "Gets the count of meshes" ) + + MAP_METHOD_AND_WRAP( + "GetMeshName", + GetMeshName, + "Gets the name of the indexed mesh\n" + ":param idx: mesh index" ) + + MAP_METHOD_AND_WRAP( + "GetMeshAreaCount", + GetMeshAreaCount, + "Gets the count of areas within the indexed mesh\n" + ":param idx: mesh index" ) + + MAP_METHOD_AND_WRAP( + "GetMeshAreaName", + GetMeshAreaName, + "Gets the name of the indexed area within the indexed mesh\n" + ":param meshIdx: mesh index\n" + ":param areaIdx: area index\n" ) + + MAP_METHOD_AND_WRAP( + "GetAreaBoundingBox", + GetAreaBoundingBoxFromScript, + "Get the bounding box of the specified area within the mesh\n" + ":param meshIdx: mesh index\n" + ":param areaIdx: area index\n" ) + + MAP_METHOD_AND_WRAP( + "GetBoundingBox", + GetBoundingBoxFromScript, + "Get the bounding box of the specified mesh\n" + ":param meshIdx: mesh index\n" ) + MAP_METHOD_AND_WRAP( + "GetBoundingSphere", + GetBoundingSphereFromScript, + "Get the bounding sphere of the specified area\n" + ":param meshIdx: mesh index\n" ) + MAP_METHOD_AND_WRAP( + "CalculateBoundingBoxFromTransform", + CalculateBoundingBoxFromTransform, + "Get the mesh's bounding box with all points transformed using transform.\n" + ":param meshIdx: mesh index\n" + ":param transform: transform matrix\n" ) + MAP_METHOD_AND_WRAP( + "RecalculateBoundingSphere", + RecalculateBoundingSphere, + "Rebuild bounding sphere info for these meshes" ) + + MAP_METHOD_AND_WRAP( + "PrepareRayCaster", + PrepareRayCaster, + "To speed up raycasts, you can call this to load the asset asynchronously, and then poll IsRayCasterReady and HasRayCasterPreparationFailed, before attempting to call GetIntersection. Must be matched with exactly one call to ResetRayCaster!" ) + MAP_METHOD_AND_WRAP( + "ResetRayCaster", + ResetRayCaster, + "Call this after being done with GetIntersection! It will unload the asset. Must be matched with exactly one call to PrepareRayCaster!" ) + MAP_METHOD_AND_WRAP( + "IsRayCasterReady", + IsRayCasterReady, + "Call this after PrepareRayCaster to see if the asset has been loaded and is ready yet for raycasting." ) + MAP_METHOD_AND_WRAP( + "HasRayCasterPreparationFailed", + HasRayCasterPreparationFailed, + "Call this after PrepareRayCaster to see if the loading asset has failed." ) + + MAP_METHOD_AND_WRAP( + "GetIntersectionPointNormalBone", + GetIntersectionPointNormalBoneFromScript, + "( pos, dir ) ->( near )\nGet the near intersection (position, normal, bone index) between a ray and the geometry.\n" + ":param pos: ray origin\n" + ":param direction: ray direction\n" ) + + MAP_METHOD_AND_WRAP( + "GetAreaIntersectionPointNormalBone", + GetAreaIntersectionPointNormalBoneFromScript, + "( pos, dir, areaIx ) ->( near )\nGet the near intersection (position, normal, bone index) between a ray and the geometry.\n" + ":param pos: ray origin\n" + ":param direction: ray direction\n" + ":param areaIx: the mesh area index\n" ) + + MAP_METHOD_AND_WRAP( + "GetIntersectionPointNormalBoneColor", + GetIntersectionPointNormalBoneColorFromScript, + "( pos, dir ) ->( near )\nGet the near intersection (position, normal, bone index, color) between a ray and the geometry.\n" + ":param pos: ray origin\n" + ":param direction: ray direction\n" ) + + MAP_METHOD_AND_WRAP( + "GetAreaIntersectionPointNormalBoneColor", + GetAreaIntersectionPointNormalBoneColorFromScript, + "( pos, dir, areaIx ) ->( near )\nGet the near intersection (position, normal, bone index, color) between a ray and the geometry.\n" + ":param pos: ray origin\n" + ":param direction: ray direction\n" + ":param areaIx: the mesh area index\n" ) + + MAP_METHOD_AND_WRAP( + "GetMeshVertexElements", + GetMeshVertexElements, + "Returns a list of (usage, usage index) tuples for vertex layout of the specified mesh.\n" + ":param idx: mesh index" ) + + MAP_METHOD_AND_WRAP( + "SaveMesh", + SaveMesh, + "Save mesh data to a granny file\n" + ":param path: desctination file path\n" + ":param mesh: index of the mesh to save" ) + EXPOSURE_CHAINTO( BlueAsyncRes ) +} + + +namespace +{ +std::pair BoundingSphereFromPointsScript( const std::vector& points ) +{ + Vector4 sphere( 0, 0, 0, 0 ); + if( points.empty() ) + { + ::BoundingSphereFromPoints( sphere, nullptr, 0 ); + } + else + { + std::vector pointers; + pointers.reserve( points.size() ); + for( const auto& p : points ) + { + pointers.push_back( &p ); + } + ::BoundingSphereFromPoints( sphere, &pointers[0], points.size() ); + } + return std::make_pair( Vector3( sphere.x, sphere.y, sphere.z ), sphere.w ); +} +} + +MAP_FUNCTION_AND_WRAP( + "BoundingSphereFromPoints", + BoundingSphereFromPointsScript, + "Generates bounding sphere around a given points.\n" + ":param points: list of 3D points in space" ); + +#if BLUE_WITH_PYTHON +static PyObject* PySetAudioOcclusionGeometryEnabled( PyObject* self, PyObject* args ) +{ + bool enabled = false; + + if( !PyArg_ParseTuple( args, "b", &enabled ) ) + { + return NULL; + } + + g_eveIsAudioOcclusionGeometryEnabled = enabled; + + Py_RETURN_NONE; +} + +MAP_FUNCTION( + "SetAudioOcclusionGeometryEnabled", + PySetAudioOcclusionGeometryEnabled, + "Enable or disable extraction of audio occlusion geometry.\n" + "Disabled by default.\n" + ":param enabled: enable/disable audio occlusion geometry\n" + ":type enabled: bool\n" + ":rtype: None" ); +#endif diff --git a/trinity/Resources/Tr2ProceduralCube.cpp b/trinity/Resources/Tr2ProceduralCube.cpp new file mode 100644 index 000000000..219087e5a --- /dev/null +++ b/trinity/Resources/Tr2ProceduralCube.cpp @@ -0,0 +1,139 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2ProceduralCube.h" + +// based on cmf unit test found in github.com\mesh\src\cmf\test\writer.cpp +bool Tr2ProceduralCubeBuilder::BuildCmf( cmf::MemoryAllocator& allocator, cmf::BufferManager& buffers, const std::atomic& cancel, cmf::Data& data ) +{ + const std::array cubeVertices = { { + { -m_extent, -m_extent, -m_extent }, + { m_extent, -m_extent, -m_extent }, + { m_extent, m_extent, -m_extent }, + { -m_extent, m_extent, -m_extent }, + { -m_extent, -m_extent, m_extent }, + { m_extent, -m_extent, m_extent }, + { m_extent, m_extent, m_extent }, + { -m_extent, m_extent, m_extent }, + } }; + + static const std::array s_cubeIndices = { { + // Front face + 0, + 1, + 2, + 0, + 2, + 3, + // Back face + 4, + 6, + 5, + 4, + 7, + 6, + // Left face + 0, + 3, + 7, + 0, + 7, + 4, + // Right face + 1, + 5, + 6, + 1, + 6, + 2, + // Top face + 3, + 2, + 6, + 3, + 6, + 7, + // Bottom face + 0, + 4, + 5, + 0, + 5, + 1, + } }; + + // Allocate vertex buffer + const uint32_t vertexStride = sizeof( Vector3 ); + cmf::BufferView vbView = buffers.AllocateBuffer( + cubeVertices.data(), + uint32_t( cubeVertices.size() * vertexStride ), + vertexStride ); + + // Allocate index buffer + const uint32_t indexStride = sizeof( uint32_t ); + cmf::BufferView ibView = buffers.AllocateBuffer( + s_cubeIndices.data(), + uint32_t( s_cubeIndices.size() * indexStride ), + indexStride ); + + // Vertex declaration: position (float32 x 3) + cmf::VertexElement posElement = {}; + posElement.usage = cmf::Usage::Position; + posElement.usageIndex = 0; + posElement.type = cmf::ElementType::Float32; + posElement.elementCount = 3; + posElement.offset = 0; + + // LodMeshArea: one area covering all 36 indices + cmf::LodMeshArea lodArea = {}; + lodArea.firstElement = 0; + lodArea.elementCount = uint32_t( s_cubeIndices.size() ) / 3; + + // MeshLod + cmf::MeshLod lod = {}; + lod.vb = vbView; + lod.ib = ibView; + cmf::Modify( lod.areas, allocator ).emplace_back( lodArea ); + + // MeshArea + cmf::MeshArea area = {}; + area.name = allocator.AllocateString( "CubeMaterial" ); + area.bounds = CcpMath::AxisAlignedBox( + Vector3( -m_extent, -m_extent, -m_extent ), + Vector3( m_extent, m_extent, m_extent ) ); + + // Mesh + cmf::Mesh mesh = {}; + mesh.name = allocator.AllocateString( "UnitCube" ); + cmf::Modify( mesh.decl, allocator ).emplace_back( posElement ); + cmf::Modify( mesh.lods, allocator ).emplace_back( lod ); + cmf::Modify( mesh.areas, allocator ).emplace_back( area ); + mesh.topology = cmf::MeshTopology::TriangleList; + mesh.bounds = CcpMath::AxisAlignedBox( + Vector3( -m_extent, -m_extent, -m_extent ), + Vector3( m_extent, m_extent, m_extent ) ); + + data = {}; + cmf::Modify( data.meshes, allocator ).emplace_back( mesh ); + + return true; +} + +std::unique_ptr Tr2ProceduralCube::CreateBuilder() const +{ + auto builder = std::make_unique(); + builder->m_name = "Tr2ProceduralCube"; + builder->m_extent = m_extent; + return builder; +} + +float Tr2ProceduralCube::GetExtent() +{ + return m_extent; +} + +void Tr2ProceduralCube::SetExtent( float extent ) +{ + m_extent = extent; + Rebuild(); +} diff --git a/trinity/Resources/Tr2ProceduralCube.h b/trinity/Resources/Tr2ProceduralCube.h new file mode 100644 index 000000000..591a764f0 --- /dev/null +++ b/trinity/Resources/Tr2ProceduralCube.h @@ -0,0 +1,36 @@ +// Copyright © 2026 CCP ehf. + +#pragma once +#ifndef Tr2ProceduralCube_h +#define Tr2ProceduralCube_h + +#include "Tr2ProceduralGeometryProvider.h" + + +struct Tr2ProceduralCubeBuilder : public Tr2ProceduralGeometryBuilder +{ + bool BuildCmf( cmf::MemoryAllocator& allocator, cmf::BufferManager& buffers, const std::atomic& cancel, cmf::Data& data ) override; + + float m_extent = .5f; +}; + + +BLUE_CLASS_IMPL( Tr2ProceduralCube ) +class Tr2ProceduralCube : public Tr2ProceduralGeometryProvider +{ +public: + EXPOSE_TO_BLUE(); + + float GetExtent(); + void SetExtent( float extent ); + +protected: + std::unique_ptr CreateBuilder() const override; + +private: + float m_extent = .5f; +}; + +TYPEDEF_BLUECLASS_WR_SHUTDOWN( Tr2ProceduralCube ); + +#endif diff --git a/trinity/Resources/Tr2ProceduralCube_Blue.cpp b/trinity/Resources/Tr2ProceduralCube_Blue.cpp new file mode 100644 index 000000000..de900a0fa --- /dev/null +++ b/trinity/Resources/Tr2ProceduralCube_Blue.cpp @@ -0,0 +1,21 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2ProceduralCube.h" + +BLUE_DEFINE( Tr2ProceduralCube ); + + +const Be::ClassInfo* Tr2ProceduralCube::ExposeToBlue() +{ + EXPOSURE_BEGIN( Tr2ProceduralCube, "" ) + MAP_INTERFACE( Tr2ProceduralCube ) + MAP_INTERFACE( IBlueResource ) + MAP_INTERFACE( ICacheable ) + MAP_INTERFACE( ITr2InstanceData ) + MAP_ICACHEABLE_METHODS() + + MAP_PROPERTY( "extent", GetExtent, SetExtent, "Extent of the cube" ) + + EXPOSURE_CHAINTO( Tr2ProceduralGeometryProvider ) +} diff --git a/trinity/Resources/Tr2ProceduralGeometryProvider.cpp b/trinity/Resources/Tr2ProceduralGeometryProvider.cpp new file mode 100644 index 000000000..af2aa04ee --- /dev/null +++ b/trinity/Resources/Tr2ProceduralGeometryProvider.cpp @@ -0,0 +1,205 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2ProceduralGeometryProvider.h" +#include "Tr2Renderer.h" +#include "Tr2BackgroundWorker.h" + +void Tr2ProceduralGeometryProvider::Rebuild() +{ + CancelBuild(); + + m_isLoading = true; + + if( m_synchronous ) + { + m_build = nullptr; + + Build build; + build.builder = CreateBuilder(); + build.owner = this; + if( StaticRunBuild( build ) ) + { + Prepare( build ); + } + else + { + FailBuild(); + } + return; + } + + // we need a fresh piece of memory, as in-flight requests might be modifying the build. (could maybe use a pool or something) + // and we need to hold onto the pointer to be able to cancel requests. + m_build = new Build(); + // we need a builder object with a copy of parameters, as this object might be destroyed on main thread while the background worker is trying to access the parameters + m_build->builder = CreateBuilder(); + // once the builder is done, we continue on the main thread, and we need to call methods on this object + m_build->owner = this; + Tr2BackgroundWorker::Instance().Add( &StaticBuild, m_build, IBlueCallbackMan::BCBF_NONE, nullptr ); +} + +void Tr2ProceduralGeometryProvider::SetSynchronous( bool synchronous ) +{ + m_synchronous = synchronous; +} + +Tr2ProceduralGeometryProvider::Tr2ProceduralGeometryProvider( IRoot* lockobj ) : + m_build( nullptr ), + m_synchronous( false ) +{ +} + +Tr2ProceduralGeometryProvider::~Tr2ProceduralGeometryProvider() +{ + CancelBuild(); + ReleaseResources( TRISTORAGE_ALL ); +} + +bool Tr2ProceduralGeometryProvider::OnPrepareResources() +{ + if( !IsPrepared() && !IsLoading() ) + { + Rebuild(); + } + return true; +} + +void Tr2ProceduralGeometryProvider::ReleaseResources( TriStorage s ) +{ + if( s & TRISTORAGE_MANAGEDMEMORY ) + { + CancelBuild(); + ReleaseResourcesHelper(); + SetPrepared( false ); + SetGood( false ); + NotifyReleaseCachedData(); + } +} + +#if TRINITYDEV +void Tr2ProceduralGeometryProvider::GetDescription( std::string& desc ) +{ + desc = "Tr2ProceduralGeometryProvider: '"; + desc += m_name; + desc += "'"; +} +#endif + +// this function runs on worker thread +void Tr2ProceduralGeometryProvider::StaticBuild( void* context ) +{ + auto build = static_cast( context ); + if( build->cancel ) + { + // owner has nulled m_build in CancelBuild, so we don't need to do that here (and we possibly can't over here, as owner also might have been destroyed) + delete build; + return; + } + + bool success = StaticRunBuild( *build ); + + if( build->cancel ) + { + // owner has nulled m_build in CancelBuild, so we don't need to do that here (and we possibly can't over here, as owner also might have been destroyed) + delete build; + return; + } + + BeResMan->AddToQueue( BRMQ_MAIN, success ? &StaticPrepare : &StaticFail, build, IBlueCallbackMan::BCBF_NONE, nullptr ); +} + +void Tr2ProceduralGeometryProvider::StaticPrepare( void* context ) +{ + auto build = static_cast( context ); + if( !build->cancel ) + { + build->owner->m_build = nullptr; + build->owner->Prepare( *build ); + } + delete build; +} + +void Tr2ProceduralGeometryProvider::StaticFail( void* context ) +{ + auto build = static_cast( context ); + if( !build->cancel ) + { + build->owner->m_build = nullptr; + build->owner->FailBuild(); + } + delete build; +} + +// this function may run on main thread or worker thread +bool Tr2ProceduralGeometryProvider::StaticRunBuild( Build& build ) +{ + cmf::MemoryAllocator allocator; + cmf::BufferManager buffers( allocator ); + cmf::Data data; + + if( !build.builder->BuildCmf( allocator, buffers, build.cancel, data ) ) + { + CCP_LOGERR( "Tr2ProceduralGeometryProvider %s: BuildCmf failed", build.builder->m_name.c_str() ); + return false; + } + + build.bytes = cmf::BuildFile( data, buffers ); + return !build.bytes.empty(); +} + +void Tr2ProceduralGeometryProvider::Prepare( Build& build ) +{ + USE_MAIN_THREAD_RENDER_CONTEXT(); + + if( !Tr2Renderer::IsResourceCreationAllowed() ) + { + m_isLoading = false; + SetPrepared( false ); + return; + } + + Tr2CmfContents contents( build.bytes.data(), build.bytes.size(), m_name.c_str() ); + if( !contents ) + { + FailBuild(); + return; + } + + NotifyReleaseCachedData(); + ReleaseResourcesHelper(); + + if( !SetupMeshesFromCMF( *contents.GetData() ) ) + { + FailBuild(); + return; + } + + SetupSkeletonsFromCMF( *contents.GetData() ); + + SetGood( CreateMeshesFromCMFFile( contents, Tr2CpuUsage::READ, renderContext ) ); + + SetPrepared( true ); + m_isLoading = false; + NotifyRebuildCachedData(); +} + +void Tr2ProceduralGeometryProvider::FailBuild() +{ + SetPrepared( true ); + m_isLoading = false; + SetGood( false ); + NotifyReleaseCachedData(); + NotifyRebuildCachedData(); +} + +void Tr2ProceduralGeometryProvider::CancelBuild() +{ + if( !m_build ) + { + return; + } + m_build->cancel = true; + m_build = nullptr; + m_isLoading = false; +} diff --git a/trinity/Resources/Tr2ProceduralGeometryProvider.h b/trinity/Resources/Tr2ProceduralGeometryProvider.h new file mode 100644 index 000000000..05889dcab --- /dev/null +++ b/trinity/Resources/Tr2ProceduralGeometryProvider.h @@ -0,0 +1,61 @@ +// Copyright © 2026 CCP ehf. + +#pragma once +#ifndef Tr2ProceduralGeometryProvider_h +#define Tr2ProceduralGeometryProvider_h + +#include "Tr2GeometryProvider.h" + +struct Tr2ProceduralGeometryBuilder +{ + virtual ~Tr2ProceduralGeometryBuilder() = default; + virtual bool BuildCmf( cmf::MemoryAllocator& allocator, cmf::BufferManager& buffers, const std::atomic& cancel, cmf::Data& data ) = 0; + std::string m_name; +}; + +BLUE_CLASS_IMPL( Tr2ProceduralGeometryProvider ) +class Tr2ProceduralGeometryProvider : public Tr2GeometryProvider +{ +public: + EXPOSE_TO_BLUE(); + + void Rebuild(); + void SetSynchronous( bool synchronous ); + +protected: + Tr2ProceduralGeometryProvider( IRoot* lockobj = nullptr ); + virtual ~Tr2ProceduralGeometryProvider(); + + virtual std::unique_ptr CreateBuilder() const = 0; + + bool OnPrepareResources() override; + void ReleaseResources( TriStorage s ) override; + +#if TRINITYDEV + void GetDescription( std::string& desc ) override; +#endif + +private: + struct Build + { + std::atomic cancel{ false }; + std::vector bytes; + std::unique_ptr builder; + Tr2ProceduralGeometryProvider* owner = nullptr; + }; + + static void StaticBuild( void* context ); + static void StaticPrepare( void* context ); + static void StaticFail( void* context ); + static bool StaticRunBuild( Build& build ); + void Prepare( Build& build ); + void FailBuild(); + void CancelBuild(); + + Build* m_build; + bool m_synchronous; +}; + +TYPEDEF_BLUECLASS_WR_SHUTDOWN( Tr2ProceduralGeometryProvider ); + +#endif diff --git a/trinity/Resources/Tr2ProceduralGeometryProvider_Blue.cpp b/trinity/Resources/Tr2ProceduralGeometryProvider_Blue.cpp new file mode 100644 index 000000000..84fd6f374 --- /dev/null +++ b/trinity/Resources/Tr2ProceduralGeometryProvider_Blue.cpp @@ -0,0 +1,29 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2ProceduralGeometryProvider.h" + +BLUE_DEFINE_ABSTRACT( Tr2ProceduralGeometryProvider ); + + +const Be::ClassInfo* Tr2ProceduralGeometryProvider::ExposeToBlue() +{ + EXPOSURE_BEGIN( Tr2ProceduralGeometryProvider, "" ) + MAP_INTERFACE( Tr2ProceduralGeometryProvider ) + MAP_INTERFACE( IBlueResource ) + MAP_INTERFACE( ICacheable ) + MAP_INTERFACE( ITr2InstanceData ) + MAP_ICACHEABLE_METHODS() + + MAP_METHOD_AND_WRAP( + "SetSynchronous", + SetSynchronous, + "Build on main thread instead of worker thread." ) + + MAP_METHOD_AND_WRAP( + "Rebuild", + Rebuild, + "Rebuild geometry from current parameters" ) + + EXPOSURE_CHAINTO( Tr2GeometryProvider ) +} diff --git a/trinity/Resources/Tr2ProceduralSierpinski.cpp b/trinity/Resources/Tr2ProceduralSierpinski.cpp new file mode 100644 index 000000000..c41a9447d --- /dev/null +++ b/trinity/Resources/Tr2ProceduralSierpinski.cpp @@ -0,0 +1,242 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2ProceduralSierpinski.h" +#include "Include/TriMath.h" + +namespace +{ + +struct Vertex +{ + Vector3 position; + std::array packedTangent; + + bool operator==( const Vertex& other ) const + { + return position == other.position && packedTangent == other.packedTangent; + } +}; + +struct VertexHash +{ + size_t operator()( const Vertex& v ) const + { + return CcpHashFNV1( &v, sizeof( Vertex ) ); + } +}; + +uint32_t AddVertex( const Vector3& vertex, + const std::array& packedTangent, + std::unordered_map& indicesMap, + std::vector& vertices ) +{ + auto [iter, inserted] = indicesMap.try_emplace( { vertex, packedTangent }, uint32_t( vertices.size() ) ); + if( inserted ) + { + vertices.push_back( { vertex, packedTangent } ); + } + return iter->second; +} + +void AddTriangle( const Vector3& vert0, + const Vector3& vert1, + const Vector3& vert2, + const std::array& packedTangent, + std::unordered_map& indicesMap, + std::vector& indices, + std::vector& vertices ) +{ + uint32_t index0 = AddVertex( vert0, packedTangent, indicesMap, vertices ); + uint32_t index1 = AddVertex( vert1, packedTangent, indicesMap, vertices ); + uint32_t index2 = AddVertex( vert2, packedTangent, indicesMap, vertices ); + indices.insert( indices.end(), { index0, index1, index2 } ); +} + +void Sierpinski( const Vector3& up, + const Vector3& left, + const Vector3& right, + const Vector3& back, + int depth, + const std::array, 4>& packedTangents, + const std::atomic& cancel, + std::unordered_map& indicesMap, + std::vector& indices, + std::vector& vertices ) +{ + if( cancel ) + { + return; + } + + if( depth == 0 ) + { + AddTriangle( up, right, left, packedTangents[0], indicesMap, indices, vertices ); + AddTriangle( up, back, right, packedTangents[1], indicesMap, indices, vertices ); + AddTriangle( up, left, back, packedTangents[2], indicesMap, indices, vertices ); + AddTriangle( left, right, back, packedTangents[3], indicesMap, indices, vertices ); + return; + } + + Vector3 upLeft = ( up + left ) * .5f; + Vector3 upRight = ( up + right ) * .5f; + Vector3 upBack = ( up + back ) * .5f; + Vector3 leftRight = ( left + right ) * .5f; + Vector3 leftBack = ( left + back ) * .5f; + Vector3 rightBack = ( right + back ) * .5f; + + Sierpinski( up, upLeft, upRight, upBack, depth - 1, packedTangents, cancel, indicesMap, indices, vertices ); + Sierpinski( upLeft, left, leftRight, leftBack, depth - 1, packedTangents, cancel, indicesMap, indices, vertices ); + Sierpinski( upRight, leftRight, right, rightBack, depth - 1, packedTangents, cancel, indicesMap, indices, vertices ); + Sierpinski( upBack, leftBack, rightBack, back, depth - 1, packedTangents, cancel, indicesMap, indices, vertices ); +} + +} + +bool Tr2ProceduralSierpinskiBuilder::BuildCmf( cmf::MemoryAllocator& allocator, cmf::BufferManager& buffers, const std::atomic& cancel, cmf::Data& data ) +{ + std::unordered_map indicesMap; + std::vector indices; + std::vector vertices; + + float r = sqrtf( 6.f ) / 4.f; + float yb = -r / 3.f; + float cb = 1.f / sqrtf( 3.f ); + float b = cb * .5f; + + auto up = Vector3( 0.f, r, 0.f ) * m_size; + auto left = Vector3( -.5f, yb, -b ) * m_size; + auto right = Vector3( .5f, yb, -b ) * m_size; + auto back = Vector3( 0.f, yb, cb ) * m_size; + + std::array edges = { + Normalize( left - up ), + Normalize( right - up ), + Normalize( left - back ), + Normalize( right - back ) + }; + std::array normals = { + Normalize( Cross( edges[1], edges[0] ) ), + Normalize( Cross( edges[1], edges[3] ) ), + Normalize( Cross( edges[2], edges[0] ) ), + Normalize( Cross( edges[2], edges[3] ) ) + }; + std::array packedTangents = { + cmf::PackTangents( cmf::TangentCompression::PackedTangent, normals[0], edges[0], Cross( normals[0], edges[0] ) ), + cmf::PackTangents( cmf::TangentCompression::PackedTangent, normals[1], edges[3], Cross( normals[1], edges[3] ) ), + cmf::PackTangents( cmf::TangentCompression::PackedTangent, normals[2], edges[0], Cross( normals[2], edges[0] ) ), + cmf::PackTangents( cmf::TangentCompression::PackedTangent, normals[3], edges[3], Cross( normals[3], edges[3] ) ), + }; + + auto convert = cmf::GetScalarConversionFunction( cmf::ElementType::Int16Norm ).first; + std::array, 4> convertedTangents; + for( int32_t i = 0; i < 4; i++ ) + { + for( int32_t j = 0; j < 4; j++ ) + { + convert.from( &convertedTangents[i][j], packedTangents[i][j] ); + } + } + + Sierpinski( up, left, right, back, ClampInt( m_depth, 0, 5 ), convertedTangents, cancel, indicesMap, indices, vertices ); + + if( cancel ) + { + return false; + } + + // Allocate vertex buffer + const uint32_t vertexStride = sizeof( Vertex ); + cmf::BufferView vbView = buffers.AllocateBuffer( + vertices.data(), + uint32_t( vertices.size() * vertexStride ), + vertexStride ); + + // Allocate index buffer + const uint32_t indexStride = sizeof( uint32_t ); + cmf::BufferView ibView = buffers.AllocateBuffer( + indices.data(), + uint32_t( indices.size() * indexStride ), + indexStride ); + + // Vertex declaration: position (float32 x 3) + cmf::VertexElement posElement = {}; + posElement.usage = cmf::Usage::Position; + posElement.usageIndex = 0; + posElement.type = cmf::ElementType::Float32; + posElement.elementCount = 3; + posElement.offset = 0; + // Vertex declaration: packed tangent (int16norm x 4) + cmf::VertexElement packedTangentElement = {}; + packedTangentElement.usage = cmf::Usage::PackedTangent; + packedTangentElement.usageIndex = 0; + packedTangentElement.type = cmf::ElementType::Int16Norm; + packedTangentElement.elementCount = 4; + packedTangentElement.offset = offsetof( Vertex, packedTangent ); + + // LodMeshArea: one area covering all indices + cmf::LodMeshArea lodArea = {}; + lodArea.firstElement = 0; + lodArea.elementCount = uint32_t( indices.size() ) / 3; + + // MeshLod + cmf::MeshLod lod = {}; + lod.vb = vbView; + lod.ib = ibView; + cmf::Modify( lod.areas, allocator ).emplace_back( lodArea ); + + // MeshArea + cmf::MeshArea area = {}; + area.name = allocator.AllocateString( "SierpinskiMaterial" ); + area.bounds = CcpMath::AxisAlignedBox( + Vector3( -.5f, yb, -b ) * m_size, + Vector3( .5f, r, cb ) * m_size ); + + // Mesh + cmf::Mesh mesh = {}; + mesh.name = allocator.AllocateString( "Sierpinski" ); + cmf::Modify( mesh.decl, allocator ).emplace_back( posElement ); + cmf::Modify( mesh.decl, allocator ).emplace_back( packedTangentElement ); + cmf::Modify( mesh.lods, allocator ).emplace_back( lod ); + cmf::Modify( mesh.areas, allocator ).emplace_back( area ); + mesh.topology = cmf::MeshTopology::TriangleList; + mesh.bounds = CcpMath::AxisAlignedBox( + Vector3( -.5f, yb, -b ) * m_size, + Vector3( .5f, r, cb ) * m_size ); + + data = {}; + cmf::Modify( data.meshes, allocator ).emplace_back( mesh ); + + return true; +} + +std::unique_ptr Tr2ProceduralSierpinski::CreateBuilder() const +{ + auto builder = std::make_unique(); + builder->m_name = "Tr2ProceduralSierpinski"; + builder->m_size = m_size; + builder->m_depth = m_depth; + return builder; +} + +float Tr2ProceduralSierpinski::GetSize() +{ + return m_size; +} + +void Tr2ProceduralSierpinski::SetSize( float size ) +{ + m_size = size; + Rebuild(); +} + +int32_t Tr2ProceduralSierpinski::GetDepth() +{ + return m_depth; +} + +void Tr2ProceduralSierpinski::SetDepth( int32_t depth ) +{ + m_depth = depth; + Rebuild(); +} diff --git a/trinity/Resources/Tr2ProceduralSierpinski.h b/trinity/Resources/Tr2ProceduralSierpinski.h new file mode 100644 index 000000000..770a35bb2 --- /dev/null +++ b/trinity/Resources/Tr2ProceduralSierpinski.h @@ -0,0 +1,40 @@ +// Copyright © 2026 CCP ehf. + +#pragma once +#ifndef Tr2ProceduralSierpinski_h +#define Tr2ProceduralSierpinski_h + +#include "Tr2ProceduralGeometryProvider.h" + + +struct Tr2ProceduralSierpinskiBuilder : public Tr2ProceduralGeometryBuilder +{ + bool BuildCmf( cmf::MemoryAllocator& allocator, cmf::BufferManager& buffers, const std::atomic& cancel, cmf::Data& data ) override; + + float m_size = 1.f; + int32_t m_depth = 1; +}; + + +BLUE_CLASS_IMPL( Tr2ProceduralSierpinski ) +class Tr2ProceduralSierpinski : public Tr2ProceduralGeometryProvider +{ +public: + EXPOSE_TO_BLUE(); + + float GetSize(); + void SetSize( float size ); + int32_t GetDepth(); + void SetDepth( int32_t depth ); + +protected: + std::unique_ptr CreateBuilder() const override; + +private: + float m_size = 1.f; + int32_t m_depth = 1; +}; + +TYPEDEF_BLUECLASS_WR_SHUTDOWN( Tr2ProceduralSierpinski ); + +#endif diff --git a/trinity/Resources/Tr2ProceduralSierpinski_Blue.cpp b/trinity/Resources/Tr2ProceduralSierpinski_Blue.cpp new file mode 100644 index 000000000..3ada3fc5c --- /dev/null +++ b/trinity/Resources/Tr2ProceduralSierpinski_Blue.cpp @@ -0,0 +1,22 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2ProceduralSierpinski.h" + +BLUE_DEFINE( Tr2ProceduralSierpinski ); + + +const Be::ClassInfo* Tr2ProceduralSierpinski::ExposeToBlue() +{ + EXPOSURE_BEGIN( Tr2ProceduralSierpinski, "" ) + MAP_INTERFACE( Tr2ProceduralSierpinski ) + MAP_INTERFACE( IBlueResource ) + MAP_INTERFACE( ICacheable ) + MAP_INTERFACE( ITr2InstanceData ) + MAP_ICACHEABLE_METHODS() + + MAP_PROPERTY( "size", GetSize, SetSize, "Size of the tetrahedron." ) + MAP_PROPERTY( "depth", GetDepth, SetDepth, "Iteration depth of fractal.\n:jessica-numeric-range: (0, 5)" ) + + EXPOSURE_CHAINTO( Tr2ProceduralGeometryProvider ) +} diff --git a/trinity/Resources/TriGeometryRes.cpp b/trinity/Resources/TriGeometryRes.cpp index dea8a2236..e6c96b529 100644 --- a/trinity/Resources/TriGeometryRes.cpp +++ b/trinity/Resources/TriGeometryRes.cpp @@ -19,24 +19,11 @@ using namespace Tr2RenderContextEnum; -std::atomic AudioGeometryResData::s_nextId{ 1 }; - -bool g_eveIsAudioOcclusionGeometryEnabled = false; -TRI_REGISTER_SETTING( "eveIsAudioOcclusionGeometryEnabled", g_eveIsAudioOcclusionGeometryEnabled ); +CCP_STATS_DECLARED_ELSEWHERE( geometryResBytes ); int g_grannyDeprecationLevel = (int)GrannyDeprecationLevel::LOG_ERROR_AND_ASSERT; TRI_REGISTER_SETTING( "grannyDeprecationLevel", g_grannyDeprecationLevel ); -CCP_STATS_DECLARE( geometryResBytes, "Trinity/geometryResBytes", false, CST_MEMORY, "Size of memory occupied by geometry resources." ); - -#if TRINITY_PLATFORM != TRINITY_DIRECTX11 -const auto gpuUsage = Tr2GpuUsage::VERTEX_BUFFER | Tr2GpuUsage::INDEX_BUFFER | Tr2GpuUsage::SHADER_RESOURCE; -#else -const auto gpuUsage = Tr2GpuUsage::VERTEX_BUFFER | Tr2GpuUsage::INDEX_BUFFER; -#endif - -Tr2SuballocatedBuffer g_sharedBuffer( "TriGeometryRes shared vertex/index buffer", gpuUsage, SHARED_BUFFER_BLOCK_SIZE, SHARED_BUFFER_MAX_SIZE ); - void HandleGrannyDeprecation( const std::wstring& path ) { @@ -60,7 +47,6 @@ void HandleGrannyDeprecation( const std::wstring& path ) } } - static void CopyGrannyName( std::string& dest, const char* src ) { // check if valid pointer, THEN assign to std::string @@ -74,7 +60,6 @@ static void CopyGrannyName( std::string& dest, const char* src ) } } - void Tr2RaycastGeometryRes::SetLodIndices( std::vector&& lodIndices ) { m_lodIndices = std::move( lodIndices ); @@ -177,55 +162,10 @@ float SquaredDistMorphToBase( int index, bool dataIsDeltas, uint8_t* pVertices, } -uint32_t GetPrimitiveCount( const TriGeometryResLodData& lod, uint32_t index, uint32_t count ) -{ - if( index >= lod.m_areas.size() ) - { - return 0; - } - - if( index + count > lod.m_areas.size() ) - { - count = uint32_t( lod.m_areas.size() - index ); - } - - auto& meshArea = lod.m_areas[index]; - - uint32_t primCount = uint32_t( meshArea.m_primitiveCount ); - for( uint32_t i = 1; i < count; ++i ) - { - primCount += lod.m_areas[index + i].m_primitiveCount; - } - return primCount; -} - -TriGeometryAreaIndexRange GetAreaIndexRange( const TriGeometryResLodData& lod, uint32_t areaIndex, uint32_t primCount, bool reversed ) -{ - TriGeometryAreaIndexRange range; - if( primCount == 0 || areaIndex >= lod.m_areas.size() ) - { - return range; - } - if( reversed && !lod.m_reversedIndicesValid ) - { - return range; - } - range.indices = reversed ? &lod.m_reversedIndexAllocation : &lod.m_indexAllocation; - range.startIndex = reversed ? - range.indices->GetStartIndex() + lod.m_primitiveCount * 3 - lod.m_areas[areaIndex].m_firstIndex - primCount * 3 : - range.indices->GetStartIndex() + lod.m_areas[areaIndex].m_firstIndex; - range.valid = true; - return range; -} - - TriGeometryRes::TriGeometryRes( IRoot* lockobj ) : #if WITH_GRANNY m_pGrannyFile( NULL ), #endif - m_memoryUse( 0 ), - m_meshes( "TriGeometryRes/m_meshes" ), - m_skeletons( "TriGeometryRes/m_skeletons" ), m_useCMF( true ) { } @@ -292,88 +232,6 @@ unsigned int TriGeometryRes::GetAnimationCount() const return 0; } -unsigned int TriGeometryRes::GetMeshCount() const -{ - return unsigned( m_meshes.size() ); -} - -TriGeometryResMeshData* TriGeometryRes::GetMeshData( unsigned int meshIx ) const -{ - if( !m_isGood || meshIx >= m_meshes.size() ) - { - return nullptr; - } - - return m_meshes[meshIx].get(); -} - -TriGeometryResLodData* TriGeometryRes::GetMeshLod( unsigned int meshIx, float screenSize ) const -{ - auto mesh = GetMeshData( meshIx ); - - if( !mesh ) - { - return nullptr; - } - - int lodIndex = GetLodIndexForScreenSize( meshIx, screenSize ); - return mesh->m_lods[lodIndex].get(); -} - -TriGeometryResLodData* TriGeometryRes::GetMeshLod( unsigned int meshIx, int lodIndex ) const -{ - - auto mesh = GetMeshData( meshIx ); - - if( !mesh || lodIndex < 0 || lodIndex >= mesh->m_lods.size() ) - { - return nullptr; - } - - return mesh->m_lods[lodIndex].get(); -} - -int TriGeometryRes::GetLodIndexForScreenSize( unsigned int meshIx, float screenSize ) const -{ - auto mesh = GetMeshData( meshIx ); - - if( !mesh ) - { - return -1; - } - - int32_t lastLod = static_cast( mesh->m_lods.size() ) - 1; - if( m_forceLod && m_forcedLodIndex >= 0 ) - { - return int( std::min( m_forcedLodIndex, lastLod ) ); - } - - for( int32_t i = lastLod; i >= 0; i-- ) - { - if( mesh->m_lods[i]->m_maxScreenSize >= screenSize ) - { - return i; - } - } - //If we end up here, it's because we requested a LOD size larger than the best one we have. - //Just return the highest quality LOD in this case. - return 0; -} - -const AudioGeometryResData* TriGeometryRes::GetAudioGeometry( unsigned int meshIx ) const -{ - if( !g_eveIsAudioOcclusionGeometryEnabled ) - return nullptr; - - auto mesh = GetMeshData( meshIx ); - if( !mesh || !mesh->m_audioGeometry ) - { - return nullptr; - } - - return mesh->m_audioGeometry.get(); -} - #if WITH_GRANNY int TriGeometryRes::GetVertexComponentOffset( const granny_mesh* grannyMesh, const char* componentName ) const { @@ -399,108 +257,6 @@ int TriGeometryRes::GetVertexComponentOffset( const granny_mesh* grannyMesh, con } #endif -bool TriGeometryRes::GetBoundingBox( unsigned int meshIx, Vector3& min, Vector3& max ) const -{ - TriGeometryResMeshData* mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return false; - } - - min = mesh->m_minBounds; - max = mesh->m_maxBounds; - - return true; -} - -Be::Result TriGeometryRes::GetBoundingBoxFromScript( unsigned int meshIx, std::pair& bounds ) const -{ - if( meshIx >= m_meshes.size() ) - { - return Be::Result( "Mesh index out of bounds" ); - } - TriGeometryResMeshData* mesh = m_meshes[meshIx].get(); - if( !mesh ) - { - return Be::Result( "Invalid mesh" ); - } - - bounds = std::make_pair( mesh->m_minBounds, mesh->m_maxBounds ); - - return Be::Result(); -} - -bool TriGeometryRes::GetAreaBoundingBox( unsigned int meshIx, unsigned int areaIx, Vector3& min, Vector3& max ) const -{ - auto mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return false; - } - - //Get the first LOD. - auto& lod = mesh->m_lods[0]; - - // Bail out if the area index is out of range - if( areaIx >= lod->m_areas.size() ) - { - return false; - } - - // Finally, get the min and max bounds for the mesh area - min = lod->m_areas[areaIx].m_minBounds; - max = lod->m_areas[areaIx].m_maxBounds; - - return true; -} - - -bool TriGeometryRes::GetBoundingSphere( unsigned int meshIx, Vector4& sphere ) const -{ - TriGeometryResMeshData* mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return false; - } - - sphere = mesh->m_boundingSphere; - - return true; -} - -unsigned int TriGeometryRes::GetAreaCount( unsigned int meshIx ) const -{ - auto mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return 0; - } - - //Get the first LOD. - auto& lod = mesh->m_lods[0]; - - return (unsigned int)lod->m_areas.size(); -} - -TriGeometryResAreaData* TriGeometryRes::GetAreaData( unsigned int meshIx, unsigned int areaIx ) const -{ - auto mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return NULL; - } - - //Get the first LOD. - auto& lod = mesh->m_lods[0]; - - if( areaIx < 0 || areaIx >= lod->m_areas.size() ) - { - return NULL; - } - - return &lod->m_areas[areaIx]; -} - bool TriGeometryRes::OnPrepareResources() { if( IsPrepared() || IsLoading() ) @@ -543,7 +299,7 @@ BlueAsyncRes::LoadingResult TriGeometryRes::DoLoad() if( m_cmfContents ) { - if( !m_cmfContents.GetData() || !SetupMeshes( *m_cmfContents.GetData() ) ) + if( !m_cmfContents.GetData() || !SetupMeshesFromCMF( *m_cmfContents.GetData() ) ) { return LR_FAILED; }; @@ -554,7 +310,7 @@ BlueAsyncRes::LoadingResult TriGeometryRes::DoLoad() { // The granny file already exists so we only have to recreate // the meshes. - SetupMeshes( GrannyGetFileInfo( m_pGrannyFile ) ); + SetupMeshesFromGranny( GrannyGetFileInfo( m_pGrannyFile ) ); return LR_SUCCESS; } #endif @@ -852,109 +608,12 @@ bool TriGeometryRes::IsAreaMorphed( TriGeometryResAreaData& area, granny_mesh* m } #endif -bool TriGeometryRes::SetupMeshes( const cmf::Data& cmfData ) -{ - TRINITY_STATS_ZONE( __FUNCTION__ ); - - int minimumLOD = gTriDev->GetMinimumModelLOD(); - - for( const cmf::Mesh& cmfMesh : cmfData.meshes ) - { - Tr2VertexDefinition vertexDefinition = BuildFromCMFVertexDecl( cmfMesh.decl ); - const uint32_t vertexDeclarationHandle = Tr2EffectStateManager::GetVertexDeclarationHandle( vertexDefinition ); - unsigned int bytesPerVertex = cmfMesh.lods[0].vb.stride; - - TriGeometryResMeshData* mesh = new TriGeometryResMeshData(); - - mesh->m_name = cmf::ToStdString( cmfMesh.name ); - - mesh->m_minBounds = cmfMesh.bounds.m_min; - mesh->m_maxBounds = cmfMesh.bounds.m_max; - - CcpMath::Sphere sphere( cmfMesh.bounds ); - mesh->m_boundingSphere = Vector4( sphere.center, sphere.radius ); - - mesh->m_vertexDeclarationHandle = vertexDeclarationHandle; - mesh->m_bytesPerVertex = bytesPerVertex; - - for( const auto& joint : cmfMesh.boneBindings ) - { - TriJointBinding binding; - binding.m_name = cmf::ToStdString( joint.name ); - binding.m_obbMin = joint.bounds.m_min; - binding.m_obbMax = joint.bounds.m_max; - mesh->m_jointBindings.push_back( binding ); - } - - int32_t firstLodIndex = ClampInt( minimumLOD, 0, int32_t( cmfMesh.lods.size() ) - 1 ); - - for( int32_t i = firstLodIndex; i < cmfMesh.lods.size(); i++ ) - { - const cmf::MeshLod& cmfLod = cmfMesh.lods[i]; - - //Setup a new LOD, which will be added as an LOD of the mesh. - std::unique_ptr lod( new TriGeometryResLodData() ); - - lod->m_mesh = mesh; - - // TODO: intern, delete field once granny has been removed. - lod->m_grannyMeshIndex = 0xFFFFFFFF; - - lod->m_name = cmf::ToStdString( cmfMesh.name ); - - lod->m_maxScreenSize = float( cmfLod.threshold ); - - lod->m_vertexCount = cmf::GetStreamElementCount( cmfLod.vb ); - - lod->m_primitiveCount = cmf::GetStreamElementCount( cmfLod.ib ) / 3; - - // Get the UV densities, which are used for texture streaming. - lod->m_uvDensities = std::vector( cmfMesh.uvDensities.begin(), cmfMesh.uvDensities.end() ); - - lod->m_areas.resize( cmfLod.areas.size() ); - - for( int groupIndex = 0; groupIndex < cmfLod.areas.size(); ++groupIndex ) - { - const cmf::LodMeshArea& cmfLodMeshArea = cmfLod.areas[groupIndex]; - const cmf::MeshArea& cmfMeshArea = cmfMesh.areas[groupIndex]; - - TriGeometryResAreaData& area = lod->m_areas[groupIndex]; - area.m_name = cmf::ToStdString( cmfMeshArea.name ); - - area.m_firstIndex = cmfLodMeshArea.firstElement * 3; - area.m_primitiveCount = cmfLodMeshArea.elementCount; - - area.m_isSkinned = cmfMeshArea.affectedByBones; - area.m_isMorphed = cmfMeshArea.affectedByMorphTargets; - - area.m_minBounds = cmfMeshArea.bounds.m_min; - area.m_maxBounds = cmfMeshArea.bounds.m_max; - } - - lod->m_originalLodIndex = i; - - mesh->m_lods.push_back( std::move( lod ) ); - } - - uint32_t lodMask = 0u; - for( const auto& lod : mesh->m_lods ) - { - lodMask |= 1u << lod->m_originalLodIndex; - } - mesh->m_lodMask = lodMask; - - m_meshes.push_back( std::unique_ptr( mesh ) ); - } - - return true; -} - #if WITH_GRANNY -bool TriGeometryRes::SetupMeshes( granny_file_info* gi ) +bool TriGeometryRes::SetupMeshesFromGranny( granny_file_info* gi ) { TRINITY_STATS_ZONE( __FUNCTION__ ); - CCP_ASSERT_M( !m_useCMF, "TriGeometryRes::SetupMeshes: intern messed up cmf loading" ); + CCP_ASSERT_M( !m_useCMF, "TriGeometryRes::SetupMeshesFromGranny: intern messed up cmf loading" ); //Used by LOD meshes to find the model they belong to. std::vector grannyMeshIndexToMeshMap; @@ -1184,36 +843,12 @@ bool TriGeometryRes::SetupMeshes( granny_file_info* gi ) } #endif -void TriGeometryRes::SetupSkeletons( const cmf::Data& cmfData ) -{ - TRINITY_STATS_ZONE( __FUNCTION__ ); - - m_skeletons.resize( cmfData.skeletons.size() ); - for( int skelIx = 0; skelIx < cmfData.skeletons.size(); ++skelIx ) - { - const cmf::Skeleton& skel = cmfData.skeletons[skelIx]; - std::unique_ptr pSkelData( new TriGeometryResSkeletonData() ); - - pSkelData->m_name = cmf::ToStdString( skel.name ); - pSkelData->m_joints.resize( skel.bones.size() ); - for( int jointIx = 0; jointIx < skel.bones.size(); ++jointIx ) - { - TriGeometryResJointData& joint = pSkelData->m_joints[jointIx]; - joint.m_name = cmf::ToStdString( skel.bones[jointIx] ); - joint.m_parentJoint = skel.parents[jointIx]; - joint.m_inverseWorldTransform = *(Matrix*)&skel.invBindTransforms[jointIx]; - } - - std::swap( m_skeletons[skelIx], pSkelData ); - } -} - #if WITH_GRANNY -void TriGeometryRes::SetupSkeletons( granny_file_info* gi ) +void TriGeometryRes::SetupSkeletonsFromGranny( granny_file_info* gi ) { TRINITY_STATS_ZONE( __FUNCTION__ ); - CCP_ASSERT_M( !m_useCMF, "TriGeometryRes::SetupSkeletons: intern messed up cmf loading" ); + CCP_ASSERT_M( !m_useCMF, "TriGeometryRes::SetupSkeletonsFromGranny: intern messed up cmf loading" ); m_skeletons.resize( gi->SkeletonCount ); for( int skelIx = 0; skelIx < gi->SkeletonCount; ++skelIx ) @@ -1222,7 +857,7 @@ void TriGeometryRes::SetupSkeletons( granny_file_info* gi ) std::unique_ptr pSkelData( new TriGeometryResSkeletonData() ); if( pSkelData == nullptr ) { - CCP_LOGWARN( "Out of memory in TriGeometryRes::SetupSkeletons" ); + CCP_LOGWARN( "Out of memory in TriGeometryRes::SetupSkeletonsFromGranny" ); return; } CopyGrannyName( pSkelData->m_name, skel->Name ); @@ -1251,12 +886,12 @@ bool TriGeometryRes::ReadCMFFile() { return false; } - if( !SetupMeshes( *m_cmfContents.GetData() ) ) + if( !SetupMeshesFromCMF( *m_cmfContents.GetData() ) ) { return false; } - SetupSkeletons( *m_cmfContents.GetData() ); + SetupSkeletonsFromCMF( *m_cmfContents.GetData() ); return true; } @@ -1288,12 +923,12 @@ bool TriGeometryRes::ReadGrannyFile() // m_pGrannyFile is freed in CreateD3DMeshFromGranny once the D3D // resources have been created on the main thread. - if( !SetupMeshes( gi ) ) + if( !SetupMeshesFromGranny( gi ) ) { return false; } - SetupSkeletons( gi ); + SetupSkeletonsFromGranny( gi ); return true; } @@ -1315,8 +950,8 @@ void TriGeometryRes::PrepareFromGrannyRes( TriGrannyRes* g ) m_useCMF = true; - SetupMeshes( *cmfData ); - SetupSkeletons( *cmfData ); + SetupMeshesFromCMF( *cmfData ); + SetupSkeletonsFromCMF( *cmfData ); CreateMeshesFromCMFFile( g->GetCMFContents(), Tr2CpuUsage::READ | Tr2CpuUsage::WRITE, renderContext ); } #if WITH_GRANNY @@ -1331,8 +966,8 @@ void TriGeometryRes::PrepareFromGrannyRes( TriGrannyRes* g ) m_useCMF = false; - SetupMeshes( gi ); - SetupSkeletons( gi ); + SetupMeshesFromGranny( gi ); + SetupSkeletonsFromGranny( gi ); CreateMeshesFromGrannyFile( gi, Tr2CpuUsage::READ | Tr2CpuUsage::WRITE, renderContext ); } #endif @@ -1343,279 +978,58 @@ void TriGeometryRes::PrepareFromGrannyRes( TriGrannyRes* g ) SetGood( true ); } -//This entire function is sus. We should be able to compute this when we upload the mesh to the GPU, as it can never change unless the vertex data changes. -void TriGeometryRes::RecalculateBoundingSphere() +void TriGeometryRes::PrepareRayCaster() { - TRINITY_STATS_ZONE( __FUNCTION__ ); - USE_MAIN_THREAD_RENDER_CONTEXT(); + m_bvh.sessions++; - for( auto& mesh : m_meshes ) + if( m_bvh.geometry || !m_useCMF ) { - //Try to get the vertex declaration elements - Tr2VertexDefinition decl; - if( mesh == nullptr || !Tr2EffectStateManager::GetVertexDeclarationElements( mesh->m_vertexDeclarationHandle, decl ) ) - { - continue; - } - - uint32_t vertSize = mesh->m_bytesPerVertex; - - //For now, we just compute the bounding sphere of the first LOD and ignore the lower LODs, assuming that they should fit in the highest quality LOD's bounding sphere. - auto& lod = mesh->m_lods[0]; - - - //Get the vertex data of the - - const uint8_t* pVertices; - if( !lod->m_allocationsValid || FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) - { - continue; - } - auto position = decl.Find( decl.POSITION, 0 ); - - // build a list of pointers to the positions - std::vector points( lod->m_vertexCount ); - for( uint32_t p = 0; p < lod->m_vertexCount; ++p ) - { - points[p] = (const Vector3*)&pVertices[p * vertSize + position->m_offset]; - } + return; + } - // all is done in this recursive function - BoundingSphereFromPoints( mesh->m_boundingSphere, &points[0], points.size() ); - lod->m_vertexAllocation.UnmapForReading( renderContext ); + std::vector lodIndices; + lodIndices.resize( m_meshes.size() ); + for( size_t i = 0; i < m_meshes.size(); i++ ) + { + lodIndices[i] = m_meshes[i] ? m_meshes[i]->m_lods[0]->m_originalLodIndex : 0; } + + m_bvh.geometry.CreateInstance(); + m_bvh.geometry->SetLodIndices( std::move( lodIndices ) ); + m_bvh.geometry->Initialize( GetFilePath().c_str(), GetExt() ); } -struct CalcBoundingBoxContext +void TriGeometryRes::DestroyRayCaster() { - Vector3 min; - Vector3 max; - Matrix transform; -}; + if( m_bvh.geometry ) + { + m_bvh.geometry->CancelPendingLoad(); + m_bvh.geometry.Unlock(); + } +} -void CalcBoundingBox( void* context, const Vector3& p1, const Vector3& p2, const Vector3& p3 ) +void TriGeometryRes::ResetRayCaster() { - CalcBoundingBoxContext* ctx = static_cast( context ); + m_bvh.sessions--; - Vector4 v[3]; - v[0] = Vector4( p1.x, p1.y, p1.z, 1.0f ); - v[1] = Vector4( p2.x, p2.y, p2.z, 1.0f ); - v[2] = Vector4( p3.x, p3.y, p3.z, 1.0f ); + CCP_ASSERT( m_bvh.sessions >= 0 ); - for( int i = 0; i < 3; ++i ) + if( m_bvh.sessions > 0 ) { - v[i] = Transform( v[i], ctx->transform ); - v[i] /= v[i].w; - - ctx->max.x = std::max( ctx->max.x, v[i].x ); - ctx->max.y = std::max( ctx->max.y, v[i].y ); - ctx->max.z = std::max( ctx->max.z, v[i].z ); - - ctx->min.x = std::min( ctx->min.x, v[i].x ); - ctx->min.y = std::min( ctx->min.y, v[i].y ); - ctx->min.z = std::min( ctx->min.z, v[i].z ); + return; } + + m_bvh.geometry.Unlock(); } -static void ConvertTriangleData( Tr2VertexDefinition::DataType elementType, unsigned int vertexByteSize, const uint8_t* vertexBase, unsigned int index1, unsigned int index2, unsigned int index3, Vector3* v1, Vector3* v2, Vector3* v3 ) +bool TriGeometryRes::IsRayCasterReady() const { - - ConvertDataToVector3( elementType, vertexBase + index1 * vertexByteSize, v1 ); - ConvertDataToVector3( elementType, vertexBase + index2 * vertexByteSize, v2 ); - ConvertDataToVector3( elementType, vertexBase + index3 * vertexByteSize, v3 ); + return m_bvh.geometry && m_bvh.geometry->IsGood(); } -void TriGeometryRes::ProcessMeshTriangles( int meshIx, PerTriangleCallback cb, void* cbContext ) +bool TriGeometryRes::HasRayCasterPreparationFailed() const { - TRINITY_STATS_ZONE( __FUNCTION__ ); - USE_MAIN_THREAD_RENDER_CONTEXT(); - - int i = meshIx; - - if( m_meshes[i] == NULL ) - { - return; - } - - auto& mesh = m_meshes[i]; - - auto& lod = mesh->m_lods[0]; - - if( !lod->m_allocationsValid ) - { - return; - } - - const uint8_t* pVertices; - const uint8_t* pIndices; - - int vertSize = mesh->m_bytesPerVertex; - - if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) - { - return; - } - ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); - if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) - { - return; - } - ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); - - const uint16_t* pShortIndices = (uint16_t*)pIndices; - const uint32_t* pLongIndices = (uint32_t*)pIndices; - - - Tr2VertexDefinition decl; - if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh->m_vertexDeclarationHandle, decl ) ) - { - return; - } - - auto foundPosition = decl.Find( Tr2VertexDefinition::POSITION, 0 ); - if( !foundPosition ) - { - return; - } - unsigned int positionByteOffset = foundPosition->m_offset; - Tr2VertexDefinition::DataType declType = foundPosition->m_dataType; - - int numPrim = lod->m_primitiveCount; - for( int j = 0; j < numPrim; j++ ) - { - unsigned int index1 = 0; - unsigned int index2 = 0; - unsigned int index3 = 0; - Vector3 p1; - Vector3 p2; - Vector3 p3; - if( lod->m_indexAllocation.GetStride() == 2 ) - { - index1 = pShortIndices[j * 3]; - index2 = pShortIndices[( j * 3 ) + 1]; - index3 = pShortIndices[( j * 3 ) + 2]; - } - else - { - index1 = pLongIndices[j * 3]; - index2 = pLongIndices[( j * 3 ) + 1]; - index3 = pLongIndices[( j * 3 ) + 2]; - } - - ConvertTriangleData( declType, vertSize, pVertices + positionByteOffset, index1, index2, index3, &p1, &p2, &p3 ); - - ( *cb )( cbContext, p1, p2, p3 ); - } -} - -std::pair>> TriGeometryRes::GetIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir ) -{ - RayCastResult hitInfo; - bool hit = GetIntersectionPoints( pos, dir, hitInfo ); - Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); - return std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, normal ) ) ); -} - -Be::Result TriGeometryRes::GetAreaIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>& result ) -{ - if( areaIx < -1 ) - { - // -1 is a special case handled to maintain legacy behavior. - return Be::Result( "Invalid area index" ); - } - - // In Python 3, passing a signed value to an unsigned C exposed attribute is an error. - // In order to maintain backwards compatible behavior with Python 2 - // this method got changed to accept signed integers and cast them to unsigned. - auto unsignedAreaIndex = static_cast( areaIx ); - - RayCastResult hitInfo; - bool hit = GetIntersectionPoints( pos, dir, hitInfo, unsignedAreaIndex ); - Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); - result = std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, normal ) ) ); - return Be::Result(); -} - -std::pair>>>> TriGeometryRes::GetIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir ) -{ - RayCastResult hitInfo; - bool hit = GetIntersectionPoints( pos, dir, hitInfo ); - Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); - return std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, std::make_pair( normal, std::make_pair( hitInfo.firstVertexColor, hitInfo.interpolatedVertexColor ) ) ) ) ); -} - -Be::Result TriGeometryRes::GetAreaIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>>>& result ) -{ - if( areaIx < -1 ) - { - // -1 is a special case handled to maintain legacy behavior. - return Be::Result( "Invalid area index" ); - } - - // In Python 3, passing a signed value to an unsigned C exposed attribute is an error. - // In order to maintain backwards compatible behavior with Python 2 - // this method got changed to accept signed integers and cast them to unsigned. - auto unsignedAreaIndex = static_cast( areaIx ); - - RayCastResult hitInfo; - bool hit = GetIntersectionPoints( pos, dir, hitInfo, unsignedAreaIndex ); - Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); - result = std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, std::make_pair( normal, std::make_pair( hitInfo.firstVertexColor, hitInfo.interpolatedVertexColor ) ) ) ) ); - return Be::Result(); -} - -void TriGeometryRes::PrepareRayCaster() -{ - m_bvh.sessions++; - - if( m_bvh.geometry || !m_useCMF ) - { - return; - } - - std::vector lodIndices; - lodIndices.resize( m_meshes.size() ); - for( size_t i = 0; i < m_meshes.size(); i++ ) - { - lodIndices[i] = m_meshes[i] ? m_meshes[i]->m_lods[0]->m_originalLodIndex : 0; - } - - m_bvh.geometry.CreateInstance(); - m_bvh.geometry->SetLodIndices( std::move( lodIndices ) ); - m_bvh.geometry->Initialize( GetFilePath().c_str(), GetExt() ); -} - -void TriGeometryRes::DestroyRayCaster() -{ - if( m_bvh.geometry ) - { - m_bvh.geometry->CancelPendingLoad(); - m_bvh.geometry.Unlock(); - } -} - -void TriGeometryRes::ResetRayCaster() -{ - m_bvh.sessions--; - - CCP_ASSERT( m_bvh.sessions >= 0 ); - - if( m_bvh.sessions > 0 ) - { - return; - } - - m_bvh.geometry.Unlock(); -} - -bool TriGeometryRes::IsRayCasterReady() const -{ - return m_bvh.geometry && m_bvh.geometry->IsGood(); -} - -bool TriGeometryRes::HasRayCasterPreparationFailed() const -{ - if( m_bvh.sessions > 0 && !m_bvh.geometry ) // ReleaseResources destroyed our bvh :( + if( m_bvh.sessions > 0 && !m_bvh.geometry ) // ReleaseResources destroyed our bvh :( { return true; } @@ -1694,183 +1108,12 @@ bool TriGeometryRes::GetIntersectionPoints( const Vector3& pos, const Vector3& d return true; } -// Careful: This function is slow and only works on main thread! -bool TriGeometryRes::GetIntersectionPointsLegacy( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx, float rayLength ) -{ - USE_MAIN_THREAD_RENDER_CONTEXT(); - - bool hit = false; - - for( size_t i = 0; i < m_meshes.size(); i++ ) - { - if( m_meshes[i] == NULL ) - { - continue; - } - - //Get the first LOD. - auto& lod = m_meshes[i]->m_lods[0]; - - const uint8_t* pVertices; - const uint8_t* pIndices; - - int vertSize = m_meshes[i]->m_bytesPerVertex; - if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) - { - return false; - } - ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); - if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) - { - return false; - } - ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); - - const uint16_t* pShortIndices = (uint16_t*)pIndices; - const uint32_t* pLongIndices = (uint32_t*)pIndices; - - Tr2VertexDefinition decl; - if( !Tr2EffectStateManager::GetVertexDeclarationElements( m_meshes[i]->m_vertexDeclarationHandle, decl ) ) - { - return false; - } - const Tr2VertexDefinition::Item* const position = decl.Find( decl.POSITION ); - if( !position ) - { - return false; - } - - const Tr2VertexDefinition::Item* const blendIndices = decl.Find( decl.BLENDINDICES ); - const Tr2VertexDefinition::Item* const colors = decl.Find( decl.COLOR ); - - int numPrim = lod->m_primitiveCount; - auto currentIndex = 0; - if( areaIx != -1 ) - { - if( areaIx >= lod->m_areas.size() ) - { - continue; - } - currentIndex = lod->m_areas[areaIx].m_firstIndex; - numPrim = lod->m_areas[areaIx].m_primitiveCount; - } - - for( int j = 0; j < numPrim; j++ ) - { - unsigned int index1 = 0; - unsigned int index2 = 0; - unsigned int index3 = 0; - Vector3 p1; - Vector3 p2; - Vector3 p3; - float pu, pv, dist; - if( lod->m_indexAllocation.GetStride() == 2 ) - { - index1 = pShortIndices[currentIndex++]; - index2 = pShortIndices[currentIndex++]; - index3 = pShortIndices[currentIndex++]; - } - else - { - index1 = pLongIndices[currentIndex++]; - index2 = pLongIndices[currentIndex++]; - index3 = pLongIndices[currentIndex++]; - } - - ConvertTriangleData( position->m_dataType, vertSize, pVertices + position->m_offset, index1, index2, index3, &p1, &p2, &p3 ); - - if( IntersectTri( p1, p2, p3, pos, dir, pu, pv, dist ) ) - { - if( rayLength > dist ) - { - float v1 = 1.0f - ( pu + pv ); - - result.position = p1 * v1 + p2 * pu + p3 * pv; - - Vector3 avec = p2 - p1; - Vector3 bvec = p3 - p1; - result.unnormalizedNormal = Cross( avec, bvec ); - - result.distance = rayLength = dist; - - int boneIndex = 0; - if( blendIndices && GetBoneIndex( blendIndices->m_dataType, pVertices + index1 * vertSize + blendIndices->m_offset, boneIndex ) ) - { - result.boneIndex = boneIndex; - } - - Color color1; - Color color2; - Color color3; - if( colors && - GetColor( colors->m_dataType, pVertices + index1 * vertSize + colors->m_offset, color1 ) && - GetColor( colors->m_dataType, pVertices + index2 * vertSize + colors->m_offset, color2 ) && - GetColor( colors->m_dataType, pVertices + index3 * vertSize + colors->m_offset, color3 ) ) - { - result.firstVertexColor = color1; - result.interpolatedVertexColor = color1 * v1 + color2 * pu + color3 * pv; - } - - hit = true; - } - } - } - } - - return hit; -} - -unsigned int TriGeometryRes::GetSkeletonCount() const -{ - return (unsigned int)m_skeletons.size(); -} - -TriGeometryResSkeletonData* TriGeometryRes::GetSkeletonData( unsigned int skelIx ) const -{ - CCP_ASSERT( skelIx < m_skeletons.size() ); - - return m_skeletons[skelIx].get(); -} - -bool TriGeometryRes::IsMemoryUsageKnown() -{ - return !IsLoading(); -} - -size_t TriGeometryRes::GetMemoryUsage() -{ - return m_memoryUse; -} - void TriGeometryRes::Initialize( const wchar_t* name, const wchar_t* ext ) { ReleaseResources( TRISTORAGE_ALL ); BlueAsyncRes::Initialize( name, ext ); } -void TriGeometryRes::ReleaseResourcesHelper() -{ - m_meshes.clear(); - //TODO: Do we need to clear skeletons here too? - - CCP_STATS_ADD( geometryResBytes, -(int)m_memoryUse ); - m_memoryUse = 0; -} - -unsigned int TriGeometryResSkeletonData::FindJoint( const char* name ) const -{ - unsigned int jointCount = (unsigned int)m_joints.size(); - for( unsigned int ix = 0; ix < jointCount; ++ix ) - { - if( strcmp( name, m_joints[ix].m_name.c_str() ) == 0 ) - { - return ix; - } - } - - return 0xffffffff; -} - void TriGeometryRes::Reload() { CancelPendingLoad(); @@ -1882,128 +1125,6 @@ void TriGeometryRes::Reload() BlueAsyncRes::Reload(); } -TriGeometryResSkeletonData::TriGeometryResSkeletonData() : - m_joints( "TriGeometryResSkeletonData/m_joints" ) -{ -} - -TriGeometryResAreaData::TriGeometryResAreaData() : - m_firstIndex( 0 ), - m_primitiveCount( 0 ), - m_jointBindings( "TriGeometryResAreaData/m_jointBindings" ), - m_isSkinned( false ), - m_isMorphed( false ) -{ -} - -TriGeometryResLodData::TriGeometryResLodData() : - m_vertexCount( 0 ), - m_primitiveCount( 0 ), - m_areas( "TriGeometryResMeshData/m_areas" ), - m_morphVertexDeclaration( Tr2EffectStateManager::UNINITIALIZED_DECLARATION ), - m_allocationsValid( false ), - m_reversedIndicesValid( false ), - m_uvDensities(), - m_bytesPerMorphTargetVertex( 0 ) -{ -} - - -TriGeometryResMeshData::TriGeometryResMeshData() : - - m_vertexDeclarationHandle( Tr2EffectStateManager::UNINITIALIZED_DECLARATION ), - m_bytesPerVertex( -1 ), - m_jointBindings( "TriGeometryResMeshData/m_jointBindings" ), - m_lods( "TriGeometryResModelData/m_lods" ) -{ -} - -bool TriGeometryRes::RenderAreas( unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed ) -{ - return RenderAreas( std::numeric_limits::max(), meshIx, areaIx, areaCount, renderContext, reversed ); -} - -bool TriGeometryRes::RenderAreas( float screenSize, unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed ) -{ - if( !m_isGood ) - { - return false; - } - - if( meshIx >= m_meshes.size() ) - { - return false; - } - - TriGeometryResMeshData* mesh = m_meshes[meshIx].get(); - if( !mesh ) - { - return false; - } - - TriGeometryResLodData* lod = GetMeshLod( meshIx, screenSize ); - - unsigned int primCount = GetPrimitiveCount( *lod, areaIx, areaCount ); - - if( primCount ) - { - auto indexRange = GetAreaIndexRange( *lod, areaIx, primCount, reversed ); - if( !indexRange.valid ) - { - return false; - } - - renderContext.m_esm.ApplyVertexDeclaration( mesh->m_vertexDeclarationHandle ); - renderContext.m_esm.ApplyStreamSource( 0, lod->m_vertexAllocation ); - renderContext.m_esm.ApplyIndexBuffer( *indexRange.indices ); - - renderContext.SetTopology( TOP_TRIANGLES ); - renderContext.DrawIndexedPrimitive( lod->m_vertexCount, indexRange.startIndex, primCount ); - } - - return true; -} - -namespace -{ -TriMorphTargetGeometryConstants CreateMorphGeometryConstants( const Tr2VertexDefinition& vertexDefinition, uint32_t stride, uint32_t vertexCount, Tr2RenderContext& renderContext ) -{ - TriMorphTargetGeometryConstants data = TriMorphTargetGeometryConstants{}; - for( const auto& item : vertexDefinition.m_items ) - { - if( item.m_usage == Tr2VertexDefinition::POSITION && item.m_usageIndex == 0 && item.m_stream == 0 ) - { - data.positionOffset = item.m_offset; - data.positionType = item.m_dataType; - - uint32_t type = data.positionType; - CCP_ASSERT_M( - type == Tr2VertexDefinition::DataType::FLOAT32_3, - "position type has to be FLOAT32_3!" ); - } - - if( item.m_usage == Tr2VertexDefinition::TANGENT && item.m_usageIndex == 0 && item.m_stream == 0 ) - { - data.tangentOffset = item.m_offset; - data.tangentType = item.m_dataType; - - uint32_t type = data.tangentType; - CCP_ASSERT_M( - type == Tr2VertexDefinition::DataType::FLOAT16_3 || - type == Tr2VertexDefinition::DataType::FLOAT32_3 || - type == Tr2VertexDefinition::DataType::UBYTE_4_NORM || - type == Tr2VertexDefinition::DataType::USHORT_4_NORM || - type == Tr2VertexDefinition::DataType::SHORT_4_NORM, - "tangent type has to be FLOAT16_3 or FLOAT32_3 or UBYTE_4_NORM or USHORT_4_NORM or SHORT_4_NORM!" ); - } - } - - data.vertexBufferStride = stride; - data.vertexCount = vertexCount; - return data; -} -} - #if WITH_GRANNY float CalculateMorphDeformationAmount( bool dataIsDeltas, int32_t vertexCount, uint8_t* pMorphSrc, Tr2VertexDefinition morphDecl, uint8_t* pVertices, Tr2VertexDefinition decl ) { @@ -2023,131 +1144,6 @@ float CalculateMorphDeformationAmount( bool dataIsDeltas, int32_t vertexCount, u } #endif -bool TriGeometryRes::CreateLodFromCMFMesh( Tr2CmfContents& cmfContents, const cmf::Mesh& cmfMesh, const cmf::MeshLod& cmfMeshLod, TriGeometryResLodData* lod, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ) -{ - TRINITY_STATS_ZONE( __FUNCTION__ ); - - if( !Tr2Renderer::IsResourceCreationAllowed() ) - { - return false; - } - - Tr2VertexDefinition vertexDefinition = BuildFromCMFVertexDecl( cmfMesh.decl ); - const unsigned bytesPerVertex = cmfMeshLod.vb.stride; - const unsigned bytesPerIndex = cmfMeshLod.ib.stride; - - int vertexCount = cmf::GetStreamElementCount( cmfMeshLod.vb ); - int vbSize = cmfMeshLod.vb.size; - - CCP_ASSERT_M( cmfContents, "TriGeometryRes::CreateLodFromCMFMesh: cmfContents must be valid!" ); - - const void* pSrcVB = cmfContents.GetViewData( cmfMeshLod.vb ); - - int indexCount = cmf::GetStreamElementCount( cmfMeshLod.ib ); - - CR_RETURN_VAL( g_sharedBuffer.Allocate( bytesPerVertex, vertexCount, const_cast( pSrcVB ), renderContext, lod->m_vertexAllocation ), false ); - - // create d3d index buffer, this one is shared, either for dynamic or static geometry - int ibSize = indexCount * bytesPerIndex; - - const void* pSrcIB = cmfContents.GetViewData( cmfMeshLod.ib ); - - if( indexCount > 0 ) - { - ALResult hr = g_sharedBuffer.Allocate( bytesPerIndex, indexCount, const_cast( pSrcIB ), renderContext, lod->m_indexAllocation ); - if( FAILED( hr ) ) - { - g_sharedBuffer.Free( lod->m_vertexAllocation ); - return false; - } - - std::vector tempBuffer( indexCount * bytesPerIndex ); - memcpy( tempBuffer.data(), pSrcIB, tempBuffer.size() ); - if( bytesPerIndex == 2 ) - { - std::reverse( reinterpret_cast( tempBuffer.data() ), reinterpret_cast( tempBuffer.data() + cmfMeshLod.ib.size ) ); - } - else - { - std::reverse( reinterpret_cast( tempBuffer.data() ), reinterpret_cast( tempBuffer.data() + cmfMeshLod.ib.size ) ); - } - - if( FAILED( g_sharedBuffer.Allocate( bytesPerIndex, indexCount, tempBuffer.data(), renderContext, lod->m_reversedIndexAllocation ) ) ) - { - g_sharedBuffer.Free( lod->m_vertexAllocation ); - g_sharedBuffer.Free( lod->m_indexAllocation ); - return false; - } - lod->m_reversedIndicesValid = true; - } - - lod->m_morphVertexDeclaration = -1; - { // Morph Target - if( cmfMesh.morphTargets.targets.size() > 0 ) - { - // Allocate the morph target array based on the size of the first morph target - Tr2VertexDefinition morphTargetVertexDefinition = BuildFromCMFVertexDecl( cmfMesh.morphTargets.decl ); - lod->m_bytesPerMorphTargetVertex = cmfMeshLod.morphTargets[0].vb.stride; - uint32_t morphDataSize = vertexCount * lod->m_bytesPerMorphTargetVertex; - uint32_t morphTargetBufferSize = (uint32_t)( morphDataSize * cmfMesh.morphTargets.targets.size() + sizeof( TriMorphTargetGeometryConstants ) ); - - CR_RETURN_VAL( g_sharedBuffer.Allocate( - 4, - morphTargetBufferSize >> 2, - nullptr, - renderContext, - lod->m_morphTargetAllocation ), - false ); - - TriMorphTargetGeometryConstants morphTargetConstants = CreateMorphGeometryConstants( - morphTargetVertexDefinition, - lod->m_bytesPerMorphTargetVertex, - vertexCount, - renderContext ); - - lod->m_morphTargetAllocation.Update( &morphTargetConstants, 0, sizeof( TriMorphTargetGeometryConstants ), renderContext ); - - for( int i = 0; i < cmfMesh.morphTargets.targets.size(); ++i ) - { - const cmf::MorphTarget& morphTarget = cmfMesh.morphTargets.targets[i]; - - size_t nameLength = morphTarget.name.size(); - // TODO: intern, this "Shape" suffix will likely be gone later - // By convention (due to the exporter), the morph target name ends with "Shape". Assert that it does, and also that it is not an empty string! - std::string morphTargetName = cmf::ToStdString( morphTarget.name ); - if( nameLength > 5 && strcmp( morphTargetName.c_str() + nameLength - 5, "Shape" ) == 0 ) - { - morphTargetName = std::string( morphTarget.name.data(), nameLength - 5 ); - } - - lod->m_morphTargetNames.push_back( morphTargetName ); - - lod->m_isBakedMorphTarget.push_back( false ); - - const void* pMorphSrc = cmfContents.GetViewData( cmfMeshLod.morphTargets[i].vb ); - lod->m_morphTargetAllocation.Update( - const_cast( pMorphSrc ), - morphDataSize * i + sizeof( TriMorphTargetGeometryConstants ), - morphDataSize, - renderContext ); - - lod->m_morphTargetDeformationAmounts.push_back( morphTarget.maxDisplacement ); - } - - lod->m_morphVertexDeclaration = Tr2EffectStateManager::GetVertexDeclarationHandle( morphTargetVertexDefinition ); - } - } - - lod->m_allocationsValid = true; - - m_memoryUse += vbSize + ibSize; // Memory use is only approximate as a hint for the resource cache - - // Append reversed index buffer size also - m_memoryUse += ibSize; - - return true; -} - #if WITH_GRANNY bool TriGeometryRes::CreateLodFromGrannyMesh( granny_mesh* grannyMesh, TriGeometryResLodData* lod, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext, void* pVBOverride ) { @@ -2282,70 +1278,31 @@ bool TriGeometryRes::CreateLodFromGrannyMesh( granny_mesh* grannyMesh, TriGeomet lod->m_isBakedMorphTarget.push_back( false ); - void* pMorphSrc = GrannyGetMeshMorphVertices( grannyMesh, i ); - lod->m_morphTargetAllocation.Update( - pMorphSrc, - morphDataSize * i + sizeof( TriMorphTargetGeometryConstants ), - morphDataSize, - renderContext ); - - float deformationAmount = CalculateMorphDeformationAmount( morphTarget.DataIsDeltas, vertexCount, (uint8_t*)pMorphSrc, morphTargetVertexDefinition, (uint8_t*)pSrc, vertexDefinition ); - lod->m_morphTargetDeformationAmounts.push_back( deformationAmount ); - } - - lod->m_morphVertexDeclaration = Tr2EffectStateManager::GetVertexDeclarationHandle( firstMorphTargetVertexDefinition ); - } - } - - lod->m_allocationsValid = true; - - m_memoryUse += vbSize + ibSize; // Memory use is only approximate as a hint for the resource cache - - // Append reversed index buffer size also - m_memoryUse += ibSize; - - return true; -} -#endif - -bool TriGeometryRes::CreateMeshesFromCMFFile( Tr2CmfContents& cmfContents, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ) -{ - TRINITY_STATS_ZONE( __FUNCTION__ ); - - if( !cmfContents.GetData() ) - { - return false; - } - - for( int32_t i = 0; i < m_meshes.size(); i++ ) - { - auto& mesh = m_meshes[i]; - - for( const auto& lod : mesh->m_lods ) - { - CreateLodFromCMFMesh( cmfContents, cmfContents.GetData()->meshes[i], cmfContents.GetData()->meshes[i].lods[lod->m_originalLodIndex], lod.get(), cpuUsage, renderContext ); - } - - if( g_eveIsAudioOcclusionGeometryEnabled ) - { - const auto& audioMesh = cmfContents.GetData()->meshes[i].audioOcclusionMesh; - if( !audioMesh.vertices.empty() && !audioMesh.indices.empty() ) - { - auto audioGeometry = std::make_unique(); - audioGeometry->m_indices.insert( audioGeometry->m_indices.end(), audioMesh.indices.begin(), audioMesh.indices.end() ); - audioGeometry->m_vertices.insert( audioGeometry->m_vertices.end(), audioMesh.vertices.begin(), audioMesh.vertices.end() ); - audioGeometry->m_minBounds = audioMesh.bounds.m_min; - audioGeometry->m_maxBounds = audioMesh.bounds.m_max; + void* pMorphSrc = GrannyGetMeshMorphVertices( grannyMesh, i ); + lod->m_morphTargetAllocation.Update( + pMorphSrc, + morphDataSize * i + sizeof( TriMorphTargetGeometryConstants ), + morphDataSize, + renderContext ); - mesh->m_audioGeometry = std::move( audioGeometry ); + float deformationAmount = CalculateMorphDeformationAmount( morphTarget.DataIsDeltas, vertexCount, (uint8_t*)pMorphSrc, morphTargetVertexDefinition, (uint8_t*)pSrc, vertexDefinition ); + lod->m_morphTargetDeformationAmounts.push_back( deformationAmount ); } + + lod->m_morphVertexDeclaration = Tr2EffectStateManager::GetVertexDeclarationHandle( firstMorphTargetVertexDefinition ); } } - CCP_STATS_ADD( geometryResBytes, m_memoryUse ); + lod->m_allocationsValid = true; + + m_memoryUse += vbSize + ibSize; // Memory use is only approximate as a hint for the resource cache + + // Append reversed index buffer size also + m_memoryUse += ibSize; return true; } +#endif #if WITH_GRANNY static granny_mesh* FindOcclusionMesh( granny_file_info* gi, const std::string& meshName ) @@ -2468,501 +1425,6 @@ Be::Result TriGeometryRes::SaveMesh( const char* filename, uint32_t return Be::Result(); } -bool TriGeometryRes::SaveMeshToCMFFile( TriGeometryResMeshData& mesh, const char* filename ) -{ - USE_MAIN_THREAD_RENDER_CONTEXT(); - - if( mesh.m_vertexDeclarationHandle == Tr2EffectStateManager::UNINITIALIZED_DECLARATION ) - { - return false; - } - - auto& lod = mesh.m_lods[0]; - - if( !lod->m_allocationsValid ) - { - return false; - } - - Tr2VertexDefinition decl; - if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh.m_vertexDeclarationHandle, decl ) ) - { - return false; - } - - cmf::MemoryAllocator allocator; - cmf::BufferManager bufferAllocator( allocator ); - cmf::Span cmfVertexDecl = allocator.AllocateSpan( decl.m_items.size() ); - - if( !ConvertVertexDeclToCMF( decl, cmfVertexDecl, (uint32_t)cmfVertexDecl.size() ) ) - { - return false; - } - - uint32_t vertexBufferSize = lod->m_vertexAllocation.GetSize(); - uint32_t vertexBufferStride = lod->m_vertexAllocation.GetStride(); - const void* pVertices; - if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) - { - return false; - } - ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); - - uint32_t indexBufferSize = lod->m_indexAllocation.GetSize(); - uint32_t indexBufferStride = lod->m_indexAllocation.GetStride(); - const void* pIndices; - if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) - { - return false; - } - ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); - - - cmf::Data cmfData; - cmfData.meshes = allocator.AllocateSpan( 1 ); - - cmf::Mesh& cmfMesh = cmfData.meshes[0] = {}; - cmfMesh.name = allocator.AllocateString( mesh.m_name ); - cmfMesh.decl = cmfVertexDecl; - cmfMesh.lods = allocator.AllocateSpan( 1 ); - cmfMesh.areas = allocator.AllocateSpan( lod->m_areas.size() ); - cmfMesh.boneBindings = allocator.AllocateSpan( mesh.m_jointBindings.size() ); - cmfMesh.uvDensities = allocator.AllocateSpan( lod->m_uvDensities.size() ); - cmfMesh.bounds = AxisAlignedBoundingBox( mesh.m_minBounds, mesh.m_maxBounds ); - - memcpy( cmfMesh.uvDensities.data(), lod->m_uvDensities.data(), lod->m_uvDensities.size() * sizeof( float ) ); - - cmf::MeshLod& cmfMeshLod = cmfMesh.lods[0]; - cmfMeshLod.vb = bufferAllocator.AddBuffer( (void*)pVertices, vertexBufferSize, vertexBufferStride ); - cmfMeshLod.ib = bufferAllocator.AddBuffer( (void*)pIndices, indexBufferSize, indexBufferStride ); - cmfMeshLod.areas = allocator.AllocateSpan( lod->m_areas.size() ); - cmfMeshLod.morphTargets = cmf::Span(); - cmfMeshLod.threshold = (uint32_t)lod->m_maxScreenSize; - - for( int32_t i = 0; i < cmfMesh.areas.size(); i++ ) - { - cmf::MeshArea& cmfMeshArea = cmfMesh.areas[i]; - cmfMeshArea.name = allocator.AllocateString( lod->m_areas[i].m_name ); - cmfMeshArea.bounds = AxisAlignedBoundingBox( lod->m_areas[i].m_minBounds, lod->m_areas[i].m_maxBounds ); - cmfMeshArea.bones = allocator.AllocateSpan( lod->m_areas[i].m_jointBindings.size() ); - cmfMeshArea.affectedByBones = lod->m_areas[i].m_isSkinned; - cmfMeshArea.affectedByMorphTargets = false; - - for( int32_t j = 0; j < lod->m_areas[i].m_jointBindings.size(); j++ ) - { - cmfMeshArea.bones[j] = (uint16_t)lod->m_areas[i].m_jointBindings[j]; - } - - cmf::LodMeshArea& lodMeshArea = cmfMeshLod.areas[i]; - lodMeshArea.firstElement = lod->m_areas[i].m_firstIndex / 3; - lodMeshArea.elementCount = lod->m_areas[i].m_primitiveCount; - } - - for( int32_t i = 0; i < cmfMesh.boneBindings.size(); i++ ) - { - cmf::BoneBinding& cmfBoneBinding = cmfMesh.boneBindings[i]; - cmfBoneBinding.name = allocator.AllocateString( mesh.m_jointBindings[i].m_name ); - cmfBoneBinding.bounds = AxisAlignedBoundingBox( mesh.m_jointBindings[i].m_obbMin, mesh.m_jointBindings[i].m_obbMax ); - } - - auto file = cmf::BuildFile( cmfData, bufferAllocator ); - - std::wstring filenameW = (const wchar_t*)CA2W( filename ); - std::wstring fullPath = BePaths->ResolvePathForWritingW( filenameW ); - - FILE* outFile; - auto err = fopen_s( &outFile, CW2A( fullPath.c_str() ), "wb" ); - - if( err != 0 ) - { - CCP_LOGERR( "Failed to open output file: %ls\n", fullPath.c_str() ); - return false; - } - if( fwrite( file.data(), 1, file.size(), outFile ) != file.size() ) - { - CCP_LOGERR( "Failed to write output file: %ls\n", fullPath.c_str() ); - fclose( outFile ); - return false; - } - fclose( outFile ); - - return true; -} - -#if WITH_GRANNY -bool TriGeometryRes::SaveMeshToGrannyFile( TriGeometryResMeshData& mesh, const char* filename ) -{ - USE_MAIN_THREAD_RENDER_CONTEXT(); - - if( mesh.m_vertexDeclarationHandle == Tr2EffectStateManager::UNINITIALIZED_DECLARATION ) - { - return false; - } - - auto& lod = mesh.m_lods[0]; - - if( !lod->m_allocationsValid ) - { - return false; - } - - granny_data_type_definition grannyVertexDecl[16]; - - Tr2VertexDefinition decl; - if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh.m_vertexDeclarationHandle, decl ) ) - { - return false; - } - - if( !ConvertVertexDeclToGranny( decl, grannyVertexDecl, sizeof( grannyVertexDecl ) / sizeof( grannyVertexDecl[0] ) ) ) - { - return false; - } - - const void* pVertices; - if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) - { - return false; - } - ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); - - const void* pIndices; - if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) - { - return false; - } - ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); - - - granny_vertex_data myVertexData; - memset( &myVertexData, 0, sizeof( myVertexData ) ); - myVertexData.Vertices = (granny_uint8*)pVertices; - myVertexData.VertexCount = lod->m_vertexCount; - myVertexData.VertexType = grannyVertexDecl; - - granny_tri_topology myTopology; - memset( &myTopology, 0, sizeof( myTopology ) ); - - if( lod->m_vertexCount <= 65535 ) - { - myTopology.Indices16 = (granny_uint16*)pIndices; - myTopology.Index16Count = lod->m_primitiveCount * 3; - } - else - { - myTopology.Indices = (granny_int32*)pIndices; - myTopology.IndexCount = lod->m_primitiveCount * 3; - } - - myTopology.GroupCount = (granny_int32)lod->m_areas.size(); - myTopology.Groups = CCP_NEW( "myTopology.Groups" ) granny_tri_material_group[myTopology.GroupCount]; - if( myTopology.Groups ) - { - for( int i = 0; i < myTopology.GroupCount; ++i ) - { - myTopology.Groups[i].MaterialIndex = i; - myTopology.Groups[i].TriFirst = lod->m_areas[i].m_firstIndex / 3; - myTopology.Groups[i].TriCount = lod->m_areas[i].m_primitiveCount; - } - } - else - { - myTopology.GroupCount = 0; - } - - granny_mesh grannyMesh; - memset( &grannyMesh, 0, sizeof( grannyMesh ) ); - - grannyMesh.PrimaryVertexData = &myVertexData; - grannyMesh.PrimaryTopology = &myTopology; - - granny_file_info fileInfo; - memset( &fileInfo, 0, sizeof( fileInfo ) ); - - granny_mesh* meshes[] = { &grannyMesh }; - granny_tri_topology* topologies[] = { grannyMesh.PrimaryTopology }; - granny_vertex_data* vertexDatas[] = { grannyMesh.PrimaryVertexData }; - - fileInfo.ModelCount = 0; - fileInfo.Models = 0; - fileInfo.MeshCount = 1; - fileInfo.Meshes = meshes; - fileInfo.VertexDataCount = 1; - fileInfo.VertexDatas = vertexDatas; - fileInfo.TriTopologyCount = 1; - fileInfo.TriTopologies = topologies; - - fileInfo.MaterialCount = (granny_int32)lod->m_areas.size(); - fileInfo.Materials = CCP_NEW( "fileInfo.Materials" ) granny_material * [fileInfo.MaterialCount]; - if( fileInfo.Materials == nullptr ) - { - fileInfo.MaterialCount = 0; - } - - grannyMesh.MaterialBindingCount = fileInfo.MaterialCount; - grannyMesh.MaterialBindings = CCP_NEW( "grannyMesh.MaterialBindings" ) granny_material_binding[fileInfo.MaterialCount]; - if( grannyMesh.MaterialBindings == nullptr ) - { - grannyMesh.MaterialBindingCount = 0; - } - - granny_material* pMaterials = nullptr; - if( fileInfo.MaterialCount ) - { - pMaterials = CCP_NEW( "pMaterials" ) granny_material[fileInfo.MaterialCount]; - if( pMaterials ) - { - memset( pMaterials, 0, sizeof( granny_material ) * fileInfo.MaterialCount ); - - for( int i = 0; i < fileInfo.MaterialCount; ++i ) - { - fileInfo.Materials[i] = &pMaterials[i]; - fileInfo.Materials[i]->Name = lod->m_areas[i].m_name.c_str(); - grannyMesh.MaterialBindings[i].Material = fileInfo.Materials[i]; - } - } - } - - // Bones - grannyMesh.BoneBindingCount = (granny_int32)mesh.m_jointBindings.size(); - grannyMesh.BoneBindings = CCP_NEW( "grannyMesh.BoneBindings" ) granny_bone_binding[grannyMesh.BoneBindingCount]; - if( grannyMesh.BoneBindings == nullptr ) - { - grannyMesh.BoneBindingCount = 0; - } - if( grannyMesh.BoneBindingCount > 0 ) - { - memset( grannyMesh.BoneBindings, 0, sizeof( granny_bone_binding ) * grannyMesh.BoneBindingCount ); - for( int i = 0; i < grannyMesh.BoneBindingCount; ++i ) - { - grannyMesh.BoneBindings[i].BoneName = mesh.m_jointBindings[i].m_name.c_str(); - grannyMesh.BoneBindings[i].OBBMin[0] = mesh.m_jointBindings[i].m_obbMin.x; - grannyMesh.BoneBindings[i].OBBMin[1] = mesh.m_jointBindings[i].m_obbMin.y; - grannyMesh.BoneBindings[i].OBBMin[2] = mesh.m_jointBindings[i].m_obbMin.z; - grannyMesh.BoneBindings[i].OBBMax[0] = mesh.m_jointBindings[i].m_obbMax.x; - grannyMesh.BoneBindings[i].OBBMax[1] = mesh.m_jointBindings[i].m_obbMax.y; - grannyMesh.BoneBindings[i].OBBMax[2] = mesh.m_jointBindings[i].m_obbMax.z; - } - } - - - granny_file_builder* Builder = GrannyBeginFile( 1, GrannyCurrentGRNStandardTag, GrannyGRNFileMV_32Bit_LittleEndian, GrannyGetTemporaryDirectory(), "GRNFileTemp" ); - granny_file_data_tree_writer* Writer = - GrannyBeginFileDataTreeWriting( GrannyFileInfoType, &fileInfo, 0, 0 ); - GrannyWriteDataTreeToFileBuilder( Writer, Builder ); - GrannyEndFileDataTreeWriting( Writer ); - - std::wstring filenameW = (const wchar_t*)CA2W( filename ); - std::wstring fullPath = BePaths->ResolvePathForWritingW( filenameW ); - GrannyEndFile( Builder, CW2A( fullPath.c_str() ) ); - - CCP_DELETE[] pMaterials; - CCP_DELETE[] grannyMesh.MaterialBindings; - CCP_DELETE[] grannyMesh.BoneBindings; - CCP_DELETE[] fileInfo.Materials; - - return true; -} -#endif - -// -------------------------------------------------------------------------------------- -// Description: -// Implements ITr2InstanceData interface. Query if the instance data is ready to be -// used, i.e. if the resource is good. -// Return Value: -// true if data is ready to be used -// false otherwise -// -------------------------------------------------------------------------------------- -bool TriGeometryRes::IsInstanceDataReady() const -{ - return IsGood(); -} - -ITr2InstanceData::InstanceData TriGeometryRes::GetInstanceData( unsigned int bufferIndex, float screenSize ) const -{ - auto mesh = GetMeshData( bufferIndex ); - - if( !mesh ) - { - static Tr2BufferAL nullVB; - return { nullVB }; - } - - auto lod = GetMeshLod( bufferIndex, screenSize ); - return { - lod->m_vertexAllocation.GetBuffer(), lod->m_vertexAllocation.GetOffset(), mesh->m_bytesPerVertex, lod->m_vertexCount - }; -} - -// -------------------------------------------------------------------------------------- -// Description: -// Implements ITr2InstanceData interface. Returns returns vertex declaration handle -// for the given instance buffer index. -// Arguments: -// bufferIndex - instance buffer index -// Return Value: -// vertex declaration handle -// -------------------------------------------------------------------------------------- -unsigned int TriGeometryRes::GetInstanceBufferVertexDeclaration( unsigned int bufferIndex ) const -{ - if( bufferIndex < m_meshes.size() && m_meshes[bufferIndex] ) - { - return m_meshes[bufferIndex]->m_vertexDeclarationHandle; - } - return Tr2EffectStateManager::UNINITIALIZED_DECLARATION; -} - -CcpMath::AxisAlignedBox TriGeometryRes::GetInstanceBufferBoundingBox( unsigned int bufferIndex ) const -{ - auto mesh = GetMeshData( bufferIndex ); - if( !mesh ) - { - return CcpMath::AxisAlignedBox(); - } - return CcpMath::AxisAlignedBox( mesh->m_minBounds, mesh->m_maxBounds ); -} - -Be::Result TriGeometryRes::GetMeshName( unsigned int meshIx, std::string& name ) const -{ - TriGeometryResMeshData* mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return Be::Result( "Mesh index out of range, or invalid mesh" ); - } - - name = mesh->m_name; - - return Be::Result(); -} - -Be::Result TriGeometryRes::GetMeshAreaCount( unsigned int meshIx, int& count ) const -{ - TriGeometryResMeshData* mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return Be::Result( "Mesh index out of range, or invalid mesh" ); - } - - //Get the first LOD. - auto& lod = mesh->m_lods[0]; - - count = (int)lod->m_areas.size(); - - return Be::Result(); -} - -Be::Result TriGeometryRes::GetMeshAreaName( unsigned int meshIx, unsigned int areaIx, std::string& name ) const -{ - TriGeometryResMeshData* mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return Be::Result( "Mesh index out of range, or invalid mesh" ); - } - - //Get the first LOD. - auto& lod = mesh->m_lods[0]; - - if( areaIx < 0 || areaIx >= lod->m_areas.size() ) - { - return Be::Result( "Area index out of range" ); - } - - name = lod->m_areas[areaIx].m_name; - - return Be::Result(); -} - -Be::Result TriGeometryRes::GetAreaBoundingBoxFromScript( unsigned int meshIx, unsigned int areaIx, std::pair& bounds ) -{ - TriGeometryResMeshData* mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return Be::Result( "Mesh index out of range, or invalid mesh" ); - } - - //Get the first LOD. - auto& lod = mesh->m_lods[0]; - - if( areaIx < 0 || areaIx >= lod->m_areas.size() ) - { - return Be::Result( "Area index out of range" ); - } - - bounds = std::make_pair( lod->m_areas[areaIx].m_minBounds, lod->m_areas[areaIx].m_maxBounds ); - - return Be::Result(); -} - -Be::Result TriGeometryRes::GetBoundingSphereFromScript( unsigned int meshIx, std::pair& bounds ) const -{ - if( meshIx >= m_meshes.size() ) - { - return Be::Result( "Mesh index out of range" ); - } - auto& mesh = m_meshes[meshIx]; - if( !mesh ) - { - return Be::Result( "Invalid mesh" ); - } - - bounds = std::make_pair( Vector3( mesh->m_boundingSphere.x, mesh->m_boundingSphere.y, mesh->m_boundingSphere.z ), mesh->m_boundingSphere.w ); - return Be::Result(); -} - -Be::Result TriGeometryRes::CalculateBoundingBoxFromTransform( unsigned int meshIx, const Matrix& transform, std::pair& bounds ) -{ - if( meshIx >= m_meshes.size() ) - { - return Be::Result( "Mesh index out of range" ); - } - auto& mesh = m_meshes[meshIx]; - if( !mesh ) - { - return Be::Result( "Invalid mesh" ); - } - - CalcBoundingBoxContext ctx; - ctx.min = Vector3( FLT_MAX, FLT_MAX, FLT_MAX ); - ctx.max = Vector3( -FLT_MAX, -FLT_MAX, -FLT_MAX ); - ctx.transform = transform; - - if( meshIx < m_meshes.size() ) - { - ProcessMeshTriangles( meshIx, &CalcBoundingBox, &ctx ); - } - - bounds = std::make_pair( ctx.min, ctx.max ); - return Be::Result(); -} - - -BlueStdResult TriGeometryRes::GetMeshVertexElements( size_t meshIndex, std::vector>& elements ) const -{ - elements.clear(); - - if( !IsGood() ) - { - return BlueStdResult( BLUE_STD_RESULT_RUNTIME_ERROR, "TriGeometryRes is not prepared" ); - } - if( meshIndex >= m_meshes.size() || !m_meshes[meshIndex] ) - { - return BlueStdResult( BLUE_STD_RESULT_INDEX_ERROR, "mesh index out of range" ); - } - - auto& mesh = m_meshes[meshIndex]; - Tr2VertexDefinition decl; - if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh->m_vertexDeclarationHandle, decl ) ) - { - return BlueStdResult( BLUE_STD_RESULT_RUNTIME_ERROR, "could not retrieve vertex declaration" ); - } - - for( auto it = std::begin( decl.m_items ); it != std::end( decl.m_items ); ++it ) - { - elements.push_back( std::make_pair( it->m_usage, it->m_usageIndex ) ); - } - return BLUE_STD_RESULT_OK; -} - bool TriGeometryRes::IsUsingCMF() const { #if WITH_GRANNY != 1 diff --git a/trinity/Resources/TriGeometryRes.h b/trinity/Resources/TriGeometryRes.h index 99a465cd9..92a1fa5a7 100644 --- a/trinity/Resources/TriGeometryRes.h +++ b/trinity/Resources/TriGeometryRes.h @@ -3,24 +3,21 @@ #ifndef _TriGeometryRes_H_ #define _TriGeometryRes_H_ -#include "Tr2DeviceResource.h" - -#include "include/ITr2InstanceData.h" -#include "include/ITr2GpuBuffer.h" - #include "Tr2CmfContent.h" -#include "Tr2SuballocatedBuffer.h" +#include "Tr2GeometryProvider.h" #include "../Utilities/BVH.h" -constexpr uint32_t SHARED_BUFFER_BLOCK_SIZE = 32u * 1024u * 1024u; -constexpr uint32_t SHARED_BUFFER_MAX_SIZE = 2048u * 1024u * 1024u; -extern Tr2SuballocatedBuffer g_sharedBuffer; - - - BLUE_DECLARE( TriGrannyRes ); class Tr2RenderContext; +enum class GrannyDeprecationLevel +{ + DO_NOTHING, + LOG_ERROR, + LOG_ERROR_AND_ASSERT +}; + +void HandleGrannyDeprecation( const std::wstring& path ); BLUE_CLASS( Tr2RaycastGeometryRes ) : public BlueAsyncRes @@ -43,226 +40,8 @@ BLUE_CLASS( Tr2RaycastGeometryRes ) : TYPEDEF_BLUECLASS_WR_SHUTDOWN( Tr2RaycastGeometryRes ); -enum class GrannyDeprecationLevel -{ - DO_NOTHING, - LOG_ERROR, - LOG_ERROR_AND_ASSERT -}; - -void HandleGrannyDeprecation( const std::wstring& path ); - -struct TriRtGeometryConstants -{ - uint32_t indexBufferId; - uint32_t indexBufferStride; - - uint32_t indexOffset; - - uint32_t vertexBufferId; - uint32_t vertexBufferStride; - - uint32_t positionOffset; - uint32_t positionType; - - uint32_t normalOffset; - uint32_t normalType; - - uint32_t tangentOffset; - uint32_t tangentType; - - uint32_t bitangentOffset; - uint32_t bitangentType; - - uint32_t texCoord0Offset; - uint32_t texCoord0Type; - - uint32_t texCoord1Offset; - uint32_t texCoord1Type; - - uint32_t texCoord2Offset; - uint32_t texCoord2Type; - - uint32_t padding; -}; - -struct TriMorphTargetGeometryConstants -{ - uint32_t vertexBufferStride; - - uint32_t positionOffset; - uint32_t positionType; - - uint32_t tangentOffset; - uint32_t tangentType; - - uint32_t vertexCount; -}; - -struct TriGeometryResAreaData -{ - TriGeometryResAreaData(); - - std::string m_name; - int m_firstIndex; - int m_primitiveCount; - Vector3 m_minBounds; - Vector3 m_maxBounds; - TrackableStdVector m_jointBindings; - - Tr2RtBottomLevelAccelerationStructureAL m_staticBlas; - bool m_isSkinned; - bool m_isMorphed; - Tr2ConstantBufferAL m_rtGeometryConstants; -}; - - -struct TriJointBinding -{ - std::string m_name; - Vector3 m_obbMin; - Vector3 m_obbMax; -}; - -struct MeshDecalLodData -{ - uint32_t m_startIndex = 0; - uint32_t m_primitiveCount = 0; -}; - -struct MeshDecalData -{ - Matrix m_inverseDecalMatrix = IdentityMatrix(); // used as a key - Tr2SuballocatedBuffer::Allocation m_indexBuffer; - uint32_t m_lodMask; //A bit mask of which LODs have been loaded. - std::vector m_lods; -}; - -struct TriGeometryResMeshData; - -extern bool g_eveIsAudioOcclusionGeometryEnabled; - -struct AudioGeometryResData -{ - uint64_t m_id; - - std::vector m_vertices; - std::vector m_indices; - - Vector3 m_minBounds; - Vector3 m_maxBounds; - - AudioGeometryResData() : - m_id( s_nextId++ ) - { - } - -private: - static std::atomic s_nextId; -}; - -struct TriGeometryResLodData -{ - TriGeometryResLodData(); - - TriGeometryResMeshData* m_mesh; - - int32_t m_grannyMeshIndex; - - std::string m_name; - - int32_t m_originalLodIndex; - float m_maxScreenSize; - - unsigned int m_vertexCount; - unsigned int m_primitiveCount; - - std::vector m_uvDensities; - - TrackableStdVector m_areas; - - bool m_allocationsValid; - Tr2SuballocatedBuffer::Allocation m_vertexAllocation; - Tr2SuballocatedBuffer::Allocation m_indexAllocation; - - Tr2SuballocatedBuffer::Allocation m_morphTargetAllocation; - - std::vector m_morphTargetNames; - std::vector m_morphTargetDeformationAmounts; - std::vector m_isBakedMorphTarget; - - unsigned int m_morphVertexDeclaration; - unsigned int m_bytesPerMorphTargetVertex; - - // Index buffer with indexes in reversed order (used by hair/clothing) - bool m_reversedIndicesValid; - Tr2SuballocatedBuffer::Allocation m_reversedIndexAllocation; -}; - -struct TriGeometryResMeshData -{ - TriGeometryResMeshData(); - - std::string m_name; - - unsigned int m_vertexDeclarationHandle; - unsigned int m_bytesPerVertex; - - Vector3 m_minBounds; - Vector3 m_maxBounds; - Vector4 m_boundingSphere; - - TrackableStdVector m_jointBindings; - - std::unique_ptr m_audioGeometry; - std::vector> m_decals; - - uint32_t m_lodMask; //A bit mask of which LODs have been loaded. - TrackableStdVector> m_lods; -}; - -uint32_t GetPrimitiveCount( const TriGeometryResLodData& lod, uint32_t index, uint32_t count ); - -struct TriGeometryAreaIndexRange -{ - const Tr2SuballocatedBuffer::Allocation* indices = nullptr; - uint32_t startIndex = 0; - bool valid = false; -}; -TriGeometryAreaIndexRange GetAreaIndexRange( const TriGeometryResLodData& lod, uint32_t areaIndex, uint32_t primCount, bool reversed ); - -struct TriGeometryResJointData -{ - std::string m_name; - unsigned int m_parentJoint; - Matrix m_inverseWorldTransform; -}; - -struct TriGeometryResSkeletonData -{ - TriGeometryResSkeletonData(); - - unsigned int FindJoint( const char* name ) const; - - std::string m_name; - TrackableStdVector m_joints; -}; - -struct RayCastResult -{ - float distance = std::numeric_limits::infinity(); - Vector3 position; - Vector3 unnormalizedNormal; - int boneIndex = -1; - Color firstVertexColor; - Color interpolatedVertexColor; -}; - -BLUE_CLASS( TriGeometryRes ) : - public BlueAsyncRes, - public ICacheable, - public Tr2DeviceResource, - public ITr2InstanceData +BLUE_CLASS_IMPL( TriGeometryRes ) +class TriGeometryRes : public Tr2GeometryProvider { public: EXPOSE_TO_BLUE(); @@ -270,29 +49,6 @@ BLUE_CLASS( TriGeometryRes ) : TriGeometryRes( IRoot* lockobj = NULL ); virtual ~TriGeometryRes(); - void RecalculateBoundingBox(); - void RecalculateBoundingSphere(); - Be::Result CalculateBoundingBoxFromTransform( unsigned int meshIx, const Matrix& transform, std::pair& bounds ); - - unsigned int GetMeshCount() const; - Be::Result GetMeshName( unsigned int meshIx, std::string& name ) const; - Be::Result GetMeshAreaCount( unsigned int meshIx, int& count ) const; - Be::Result GetMeshAreaName( unsigned int meshIx, unsigned int areaIx, std::string& name ) const; - TriGeometryResMeshData* GetMeshData( unsigned int meshIx ) const; - TriGeometryResLodData* GetMeshLod( unsigned int meshIx, float screenSize ) const; - TriGeometryResLodData* GetMeshLod( unsigned int meshIx, int lodIndex ) const; - int GetLodIndexForScreenSize( unsigned int meshIx, float screenSize ) const; - - - const AudioGeometryResData* GetAudioGeometry( unsigned int meshIx ) const; - - unsigned int GetSkeletonCount() const; - TriGeometryResSkeletonData* GetSkeletonData( unsigned int skelIx ) const; - - unsigned int GetAreaCount( unsigned int meshIx ) const; - Be::Result GetAreaBoundingBoxFromScript( unsigned int meshIx, unsigned int areaIx, std::pair& bounds ); - TriGeometryResAreaData* GetAreaData( unsigned int meshIx, unsigned int areaIx ) const; - unsigned int GetAnimationCount() const; #if WITH_GRANNY @@ -300,106 +56,51 @@ BLUE_CLASS( TriGeometryRes ) : int GetVertexComponentOffset( const granny_mesh* myMesh, const char* componentName ) const; #endif - // Render multiple consecutive areas, starting at 'areaIx' - bool RenderAreas( unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed = false ); - bool RenderAreas( float screenSize, unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed = false ); - - void RebuildCachedData(); - - bool GetIntersectionPoints( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx = -1, float rayLength = std::numeric_limits::infinity() ); - - bool GetIntersectionPointsLegacy( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx = -1, float rayLength = std::numeric_limits::infinity() ); - - void PrepareRayCaster(); - void ResetRayCaster(); - bool IsRayCasterReady() const; - bool HasRayCasterPreparationFailed() const; - void DestroyRayCaster(); - - std::pair>> GetIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir ); - Be::Result GetAreaIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>& result ); - - std::pair>>>> GetIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir ); - Be::Result GetAreaIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>>>& result ); - - bool GetBoundingBox( unsigned int meshIx, Vector3& min, Vector3& max ) const; - Be::Result GetBoundingBoxFromScript( unsigned int meshIx, std::pair& bounds ) const; - bool GetAreaBoundingBox( unsigned int meshIx, unsigned int areaIx, Vector3& min, Vector3& max ) const; - bool GetBoundingSphere( unsigned int meshIx, Vector4& sphere ) const; - Be::Result GetBoundingSphereFromScript( unsigned int meshIx, std::pair& bounds ) const; + bool GetIntersectionPoints( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx = -1, float rayLength = std::numeric_limits::infinity() ) override; - void PrepareFromGrannyRes( TriGrannyRes * g ); + void PrepareRayCaster() override; + void ResetRayCaster() override; + bool IsRayCasterReady() const override; + bool HasRayCasterPreparationFailed() const override; + void DestroyRayCaster() override; - BlueStdResult GetMeshVertexElements( size_t meshIndex, std::vector>& elements ) const; + void PrepareFromGrannyRes( TriGrannyRes* g ); ////////////////////////////////////////////////////////////////////////// // IBlueResource // // Initialize is implemented by the BlueAsyncRes base class, but we // need to intercept it to reset data structures on reload - void Initialize( const wchar_t* name, const wchar_t* ext ); - - ///////////////////////////////////////////////////////////////////////////////////// - // ITr2InstanceData - bool IsInstanceDataReady() const override; - InstanceData GetInstanceData( unsigned int bufferIndex, float screenSize ) const override; - unsigned int GetInstanceBufferVertexDeclaration( unsigned int bufferIndex ) const override; - CcpMath::AxisAlignedBox GetInstanceBufferBoundingBox( unsigned int bufferIndex ) const override; - - ///////////////////////////////////////////////////////////////////////////////////// - // ITr2GpuBuffer - Tr2BufferAL* GetGpuBuffer( unsigned index ); - - ///////////////////////////////////////////////////////////////////////////////////// - // ICacheable - bool IsMemoryUsageKnown(); - size_t GetMemoryUsage(); + void Initialize( const wchar_t* name, const wchar_t* ext ) override; ///////////////////////////////////////////////////////////////////////////////////// // ITriDeviceResource private: - bool OnPrepareResources(); + bool OnPrepareResources() override; public: - void ReleaseResources( TriStorage s ); + void ReleaseResources( TriStorage s ) override; - void ReleaseResourcesHelper(); #if TRINITYDEV - virtual void GetDescription( std::string & desc ); + void GetDescription( std::string& desc ) override; #endif #if WITH_GRANNY granny_file_info* GetGrannyInfo() const; #endif -#if WITH_GRANNY - static bool SaveMeshToGrannyFile( TriGeometryResMeshData & mesh, const char* filename ); -#endif - static bool SaveMeshToCMFFile( TriGeometryResMeshData & mesh, const char* filename ); - - // Iterator functions for processing mesh data - typedef void ( *PerTriangleCallback )( void* context, const Vector3& p1, const Vector3& p2, const Vector3& p3 ); - void ProcessMeshTriangles( int meshIx, PerTriangleCallback cb, void* cbContext ); - void Reload(); - // name for logging/debugging - std::string m_name; - TriGrannyResPtr m_sourceGranny; - Be::Result SaveMesh( const char* filename, uint32_t meshIndex ) const; + Be::Result SaveMesh( const char* filename, uint32_t meshIndex ) const override; - bool IsUsingCMF() const; + bool IsUsingCMF() const override; const cmf::Data* GetCMFData() const; void VisualizeBVH( Tr2DebugObjectReference owner, const Matrix& transform, ITr2DebugRenderer2& renderer ) const; private: - unsigned int m_memoryUse; - TrackableStdVector> m_meshes; - TrackableStdVector> m_skeletons; - #if WITH_GRANNY granny_file* m_pGrannyFile; #endif @@ -407,9 +108,6 @@ BLUE_CLASS( TriGeometryRes ) : Tr2CmfContents m_cmfContents; bool m_useCMF; - int32_t m_forcedLodIndex = -1; - bool m_forceLod = false; - struct { Tr2RaycastGeometryResPtr geometry = nullptr; @@ -419,35 +117,30 @@ BLUE_CLASS( TriGeometryRes ) : private: // Provide the functions that do the actual work of loading and preparing. // The async management itself is done in TriAsyncLoadedResource. - virtual LoadingResult DoLoad(); - virtual bool DoPrepare(); + LoadingResult DoLoad() override; + bool DoPrepare() override; #if WITH_GRANNY // Read granny file, keep data in m_pGrannyFile bool ReadGrannyFile(); void ClearGrannyData(); - bool SetupMeshes( granny_file_info * gi ); - void SetupSkeletons( granny_file_info * gi ); - void DetermineAreaBoundsAndVertCount( TriGeometryResAreaData & area, granny_mesh * grannyMesh, int bytesPerVertex ); - bool IsAreaSkinned( TriGeometryResAreaData & area, granny_mesh * grannyMesh, granny_file_info * gi, int bytesPerVertex ); - bool IsAreaMorphed( TriGeometryResAreaData & area, granny_mesh * myMesh, granny_file_info * gi ); + bool SetupMeshesFromGranny( granny_file_info* gi ); + void SetupSkeletonsFromGranny( granny_file_info* gi ); + void DetermineAreaBoundsAndVertCount( TriGeometryResAreaData& area, granny_mesh* grannyMesh, int bytesPerVertex ); + bool IsAreaSkinned( TriGeometryResAreaData& area, granny_mesh* grannyMesh, granny_file_info* gi, int bytesPerVertex ); + bool IsAreaMorphed( TriGeometryResAreaData& area, granny_mesh* myMesh, granny_file_info* gi ); // Create D3D mesh from data in m_pGrannyFile - bool CreateMeshesFromGrannyFile( granny_file_info * gi, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext & renderContext ); - bool CreateLodFromGrannyMesh( granny_mesh * myMesh, TriGeometryResLodData * pMesh, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext & renderContext, void* pVBOverride = NULL ); + bool CreateMeshesFromGrannyFile( granny_file_info* gi, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ); + bool CreateLodFromGrannyMesh( granny_mesh* myMesh, TriGeometryResLodData* pMesh, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext, void* pVBOverride = NULL ); // Extract audio geometry from lowest LOD for Wwise spatial audio - void ExtractAudioGeometry( TriGeometryResMeshData * mesh, granny_mesh * grannyMesh ); + void ExtractAudioGeometry( TriGeometryResMeshData* mesh, granny_mesh* grannyMesh ); #endif bool ReadCMFFile(); void ClearCMFData(); - - bool SetupMeshes( const cmf::Data& cmfData ); - void SetupSkeletons( const cmf::Data& cmfData ); - bool CreateMeshesFromCMFFile( Tr2CmfContents & cmfContents, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext & renderContext ); - bool CreateLodFromCMFMesh( Tr2CmfContents & cmfContents, const cmf::Mesh& cmfMesh, const cmf::MeshLod& cmfMeshLod, TriGeometryResLodData* pMesh, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ); }; TYPEDEF_BLUECLASS_WR_SHUTDOWN( TriGeometryRes ); diff --git a/trinity/Resources/TriGeometryRes_Blue.cpp b/trinity/Resources/TriGeometryRes_Blue.cpp index 47e117678..1428f9354 100644 --- a/trinity/Resources/TriGeometryRes_Blue.cpp +++ b/trinity/Resources/TriGeometryRes_Blue.cpp @@ -43,189 +43,15 @@ const Be::ClassInfo* TriGeometryRes::ExposeToBlue() MAP_INTERFACE( ITr2InstanceData ) MAP_ICACHEABLE_METHODS() - MAP_PROPERTY_READONLY( - "meshCount", - GetMeshCount, - "Gets the count of meshes in this geometry resource" ) - MAP_PROPERTY_READONLY( - "animationCount", - GetAnimationCount, - "Gets the count of animations in this geometry resource" ) - MAP_ATTRIBUTE( "name", m_name, "Name for debugging/logging", Be::READWRITE ) - - MAP_ATTRIBUTE( "forceLod", m_forceLod, "Force geometry to use a specific LOD\n:jessica-group: Debug", Be::READWRITE ) - MAP_ATTRIBUTE( "forcedLodIndex", m_forcedLodIndex, "LOD index to use when forceLod is on\n:jessica-group: Debug", Be::READWRITE ) - - MAP_METHOD_AND_WRAP( - "GetMeshCount", - GetMeshCount, - "Gets the count of meshes" ) - - MAP_METHOD_AND_WRAP( - "GetMeshName", - GetMeshName, - "Gets the name of the indexed mesh\n" - ":param idx: mesh index" ) - - MAP_METHOD_AND_WRAP( - "GetMeshAreaCount", - GetMeshAreaCount, - "Gets the count of areas within the indexed mesh\n" - ":param idx: mesh index" ) - - MAP_METHOD_AND_WRAP( - "GetMeshAreaName", - GetMeshAreaName, - "Gets the name of the indexed area within the indexed mesh\n" - ":param meshIdx: mesh index\n" - ":param areaIdx: area index\n" ) - - MAP_METHOD_AND_WRAP( - "GetAreaBoundingBox", - GetAreaBoundingBoxFromScript, - "Get the bounding box of the specified area within the mesh\n" - ":param meshIdx: mesh index\n" - ":param areaIdx: area index\n" ) - - MAP_METHOD_AND_WRAP( - "GetBoundingBox", - GetBoundingBoxFromScript, - "Get the bounding box of the specified mesh\n" - ":param meshIdx: mesh index\n" ) - MAP_METHOD_AND_WRAP( - "GetBoundingSphere", - GetBoundingSphereFromScript, - "Get the bounding sphere of the specified area\n" - ":param meshIdx: mesh index\n" ) - MAP_METHOD_AND_WRAP( - "CalculateBoundingBoxFromTransform", - CalculateBoundingBoxFromTransform, - "Get the mesh's bounding box with all points transformed using transform.\n" - ":param meshIdx: mesh index\n" - ":param transform: transform matrix\n" ) - MAP_METHOD_AND_WRAP( - "RecalculateBoundingSphere", - RecalculateBoundingSphere, - "Rebuild bounding sphere info for these meshes" ) - MAP_METHOD_AND_WRAP( "Reload", Reload, "Forces a reload from disk" ) - MAP_METHOD_AND_WRAP( - "PrepareRayCaster", - PrepareRayCaster, - "To speed up raycasts, you can call this to load the asset asynchronously, and then poll IsRayCasterReady and HasRayCasterPreparationFailed, before attempting to call GetIntersection. Must be matched with exactly one call to ResetRayCaster!" ) - MAP_METHOD_AND_WRAP( - "ResetRayCaster", - ResetRayCaster, - "Call this after being done with GetIntersection! It will unload the asset. Must be matched with exactly one call to PrepareRayCaster!" ) - MAP_METHOD_AND_WRAP( - "IsRayCasterReady", - IsRayCasterReady, - "Call this after PrepareRayCaster to see if the asset has been loaded and is ready yet for raycasting." ) - MAP_METHOD_AND_WRAP( - "HasRayCasterPreparationFailed", - HasRayCasterPreparationFailed, - "Call this after PrepareRayCaster to see if the loading asset has failed." ) - - MAP_METHOD_AND_WRAP( - "GetIntersectionPointNormalBone", - GetIntersectionPointNormalBoneFromScript, - "( pos, dir ) ->( near )\nGet the near intersection (position, normal, bone index) between a ray and the geometry.\n" - ":param pos: ray origin\n" - ":param direction: ray direction\n" ) - - MAP_METHOD_AND_WRAP( - "GetAreaIntersectionPointNormalBone", - GetAreaIntersectionPointNormalBoneFromScript, - "( pos, dir, areaIx ) ->( near )\nGet the near intersection (position, normal, bone index) between a ray and the geometry.\n" - ":param pos: ray origin\n" - ":param direction: ray direction\n" - ":param areaIx: the mesh area index\n" ) - - MAP_METHOD_AND_WRAP( - "GetIntersectionPointNormalBoneColor", - GetIntersectionPointNormalBoneColorFromScript, - "( pos, dir ) ->( near )\nGet the near intersection (position, normal, bone index, color) between a ray and the geometry.\n" - ":param pos: ray origin\n" - ":param direction: ray direction\n" ) - - MAP_METHOD_AND_WRAP( - "GetAreaIntersectionPointNormalBoneColor", - GetAreaIntersectionPointNormalBoneColorFromScript, - "( pos, dir, areaIx ) ->( near )\nGet the near intersection (position, normal, bone index, color) between a ray and the geometry.\n" - ":param pos: ray origin\n" - ":param direction: ray direction\n" - ":param areaIx: the mesh area index\n" ) - - MAP_METHOD_AND_WRAP( - "GetMeshVertexElements", - GetMeshVertexElements, - "Returns a list of (usage, usage index) tuples for vertex layout of the specified mesh.\n" - ":param idx: mesh index" ) - - MAP_METHOD_AND_WRAP( - "SaveMesh", - SaveMesh, - "Save mesh data to a granny file\n" - ":param path: desctination file path\n" - ":param mesh: index of the mesh to save" ) - EXPOSURE_CHAINTO( BlueAsyncRes ) -} - - -namespace -{ -std::pair BoundingSphereFromPointsScript( const std::vector& points ) -{ - Vector4 sphere( 0, 0, 0, 0 ); - if( points.empty() ) - { - ::BoundingSphereFromPoints( sphere, nullptr, 0 ); - } - else - { - std::vector pointers; - pointers.reserve( points.size() ); - for( const auto& p : points ) - { - pointers.push_back( &p ); - } - ::BoundingSphereFromPoints( sphere, &pointers[0], points.size() ); - } - return std::make_pair( Vector3( sphere.x, sphere.y, sphere.z ), sphere.w ); -} -} - -MAP_FUNCTION_AND_WRAP( - "BoundingSphereFromPoints", - BoundingSphereFromPointsScript, - "Generates bounding sphere around a given points.\n" - ":param points: list of 3D points in space" ); - -#if BLUE_WITH_PYTHON -static PyObject* PySetAudioOcclusionGeometryEnabled( PyObject* self, PyObject* args ) -{ - bool enabled = false; - - if( !PyArg_ParseTuple( args, "b", &enabled ) ) - { - return NULL; - } - - g_eveIsAudioOcclusionGeometryEnabled = enabled; + MAP_PROPERTY_READONLY( + "animationCount", + GetAnimationCount, + "Gets the count of animations in this geometry resource" ) - Py_RETURN_NONE; + EXPOSURE_CHAINTO( Tr2GeometryProvider ) } - -MAP_FUNCTION( - "SetAudioOcclusionGeometryEnabled", - PySetAudioOcclusionGeometryEnabled, - "Enable or disable extraction of audio occlusion geometry.\n" - "Disabled by default.\n" - ":param enabled: enable/disable audio occlusion geometry\n" - ":type enabled: bool\n" - ":rtype: None" ); -#endif \ No newline at end of file diff --git a/trinity/Tr2BackgroundWorker.cpp b/trinity/Tr2BackgroundWorker.cpp new file mode 100644 index 000000000..cccaf30a8 --- /dev/null +++ b/trinity/Tr2BackgroundWorker.cpp @@ -0,0 +1,30 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" + +#include "Tr2BackgroundWorker.h" + + +namespace +{ +IBlueCallbackManPtr s_instance; +} + +IBlueCallbackMan& Tr2BackgroundWorker::Instance() +{ + if( !s_instance ) + { + Be::Clsid clsid; + if( BeClasses->FindClsid( clsid, "blue", "BlueCallbackMan" ) ) + { + BeClasses->CreateInstance( clsid, BlueInterfaceIID(), (void**)&s_instance ); + } + CCP_ASSERT( s_instance ); + + s_instance->SetName( "Tr2BackgroundWorker" ); + s_instance->SetThreadCount( 1 ); + s_instance->SetPriority( -1 ); + s_instance->Run(); + } + return *s_instance; +} diff --git a/trinity/Tr2BackgroundWorker.h b/trinity/Tr2BackgroundWorker.h new file mode 100644 index 000000000..6950e3448 --- /dev/null +++ b/trinity/Tr2BackgroundWorker.h @@ -0,0 +1,21 @@ +// Copyright © 2026 CCP ehf. + +#pragma once +#ifndef Tr2BackgroundWorker_h +#define Tr2BackgroundWorker_h + +#include + +// -------------------------------------------------------------------------------------- +// Description: +// Tr2BackgroundWorker is a small class to offer asynchronous execution of tasks. +// See Also: +// Tr2ProceduralGeometryProvider +// -------------------------------------------------------------------------------------- +class Tr2BackgroundWorker +{ +public: + static IBlueCallbackMan& Instance(); +}; + +#endif // Tr2BackgroundWorker_h diff --git a/trinity/Tr2InstancedMesh.cpp b/trinity/Tr2InstancedMesh.cpp index 3016a014f..3ec8b1e20 100644 --- a/trinity/Tr2InstancedMesh.cpp +++ b/trinity/Tr2InstancedMesh.cpp @@ -424,7 +424,7 @@ bool Tr2InstancedMesh::IsLoading() const // true On success // false Otherwise // -------------------------------------------------------------------------------------- -static bool GetMeshVertexDeclaration( TriGeometryRes* geometryRes, int meshIndex, Tr2VertexDefinition& vd ) +static bool GetMeshVertexDeclaration( Tr2GeometryProvider* geometryRes, int meshIndex, Tr2VertexDefinition& vd ) { if( geometryRes == nullptr || !geometryRes->IsGood() ) { diff --git a/trinity/Tr2Mesh.cpp b/trinity/Tr2Mesh.cpp index b203bc0f5..cbbc41c3a 100644 --- a/trinity/Tr2Mesh.cpp +++ b/trinity/Tr2Mesh.cpp @@ -205,12 +205,7 @@ void Tr2Mesh::PySetGeometryRes( TriGeometryRes* geometryRes ) SetGeometryRes( geometryRes ); } -int Tr2Mesh::GetAreasCount() const -{ - return TRIBATCHTYPE_COUNT_OF_BATCH_TYPES; -} - -TriGeometryRes* Tr2Mesh::GetGeometryResource() const +Tr2GeometryProvider* Tr2Mesh::GetGeometryResource() const { if( m_geometryResource && m_geometryResource->IsGood() ) { diff --git a/trinity/Tr2Mesh.h b/trinity/Tr2Mesh.h index c23e8a00f..b486d740f 100644 --- a/trinity/Tr2Mesh.h +++ b/trinity/Tr2Mesh.h @@ -8,6 +8,7 @@ #include "Resources/Tr2LoadPrepareFence.h" +BLUE_DECLARE( TriGeometryRes ); BLUE_DECLARE( Tr2SerializedMorphAnimation ); BLUE_CLASS( Tr2SerializedMorphAnimation ) : @@ -52,7 +53,7 @@ BLUE_CLASS( Tr2Mesh ) : const char* GetLowResMeshResPath() const; void SetLowResMeshResPath( const char* path ); - TriGeometryRes* GetGeometryResource() const override; + Tr2GeometryProvider* GetGeometryResource() const override; void SetGeometryRes( TriGeometryRes * res ); bool IsLoading() const override; @@ -85,7 +86,6 @@ BLUE_CLASS( Tr2Mesh ) : void InitializeMorphTargets(); void PySetGeometryRes( TriGeometryRes * geometryRes ); - int GetAreasCount() const; void SetLowResGeometryRes( TriGeometryRes * res ); private: diff --git a/trinity/Tr2MeshBase.cpp b/trinity/Tr2MeshBase.cpp index a76db3aa8..138919076 100644 --- a/trinity/Tr2MeshBase.cpp +++ b/trinity/Tr2MeshBase.cpp @@ -338,6 +338,38 @@ bool Tr2MeshBase::GetDisplay() const return m_display; } +std::vector* Tr2MeshBase::GetMorphTargetNames() const +{ + return nullptr; +} +bool Tr2MeshBase::IsBakedMorph( int index ) const +{ + return false; +} +void Tr2MeshBase::SetMorphTargetWeight( const char* name, float weight ) +{ +} +float Tr2MeshBase::GetMorphTargetWeight( const char* name ) +{ + return 0.f; +} +std::vector* Tr2MeshBase::GetAllBakedMorphTargetStates() const +{ + return nullptr; +} +void Tr2MeshBase::SetBakedMorphTarget( const char* name, bool isBaked ) +{ +} +bool Tr2MeshBase::GetBakedMorphTarget( const char* name ) +{ + return false; +} +const std::unordered_map& Tr2MeshBase::GetMorphAnimations() const +{ + static const std::unordered_map s_empty; + return s_empty; +} + Tr2RenderBatch CreateGeometryBatch( TriGeometryResLodData* lod, Tr2MeshArea* area, const Tr2PerObjectData* data, bool reverseWinding ) { Tr2RenderBatch batch; @@ -424,7 +456,7 @@ void Tr2MeshBase::GetBatches( ITriRenderBatchAccumulator* batches, // ------------------------------------------------------------- const wchar_t* Tr2MeshBase::GetGeometryResPath() const { - const TriGeometryRes* currentRes = GetGeometryResource(); + const Tr2GeometryProvider* currentRes = GetGeometryResource(); if( !currentRes ) { return L""; @@ -611,3 +643,8 @@ Tr2RaytracingMesh* Tr2MeshBase::GetRtMesh() const { return m_rtMesh.get(); } + +int Tr2MeshBase::GetAreasCount() const +{ + return TRIBATCHTYPE_COUNT_OF_BATCH_TYPES; +} diff --git a/trinity/Tr2MeshBase.h b/trinity/Tr2MeshBase.h index e665c62bf..28db985ef 100644 --- a/trinity/Tr2MeshBase.h +++ b/trinity/Tr2MeshBase.h @@ -12,7 +12,7 @@ #include "Tr2RingBuffer.h" -BLUE_DECLARE( TriGeometryRes ); +BLUE_DECLARE( Tr2GeometryProvider ); struct TriGeometryResSkeletonData; class ITriRenderBatchAccumulator; @@ -66,16 +66,16 @@ BLUE_CLASS( Tr2MeshBase ) : bool GetDisplay() const; - virtual TriGeometryRes* GetGeometryResource() const = 0; + virtual Tr2GeometryProvider* GetGeometryResource() const = 0; - virtual std::vector* GetMorphTargetNames() const = 0; - virtual bool IsBakedMorph( int index ) const = 0; - virtual void SetMorphTargetWeight( const char* name, float weight ) = 0; - virtual float GetMorphTargetWeight( const char* name ) = 0; - virtual std::vector* GetAllBakedMorphTargetStates() const = 0; - virtual void SetBakedMorphTarget( const char* name, bool isBaked ) = 0; - virtual bool GetBakedMorphTarget( const char* name ) = 0; - virtual const std::unordered_map& GetMorphAnimations() const = 0; + virtual std::vector* GetMorphTargetNames() const; + virtual bool IsBakedMorph( int index ) const; + virtual void SetMorphTargetWeight( const char* name, float weight ); + virtual float GetMorphTargetWeight( const char* name ); + virtual std::vector* GetAllBakedMorphTargetStates() const; + virtual void SetBakedMorphTarget( const char* name, bool isBaked ); + virtual bool GetBakedMorphTarget( const char* name ); + virtual const std::unordered_map& GetMorphAnimations() const; ///////////////////////////////////////////////////////////////////////////////////// // IListNotify @@ -100,6 +100,8 @@ BLUE_CLASS( Tr2MeshBase ) : void UseWithScreenSize( float screenSize, float worldRadius ) const; + int GetAreasCount() const; + protected: unsigned int FindJoint( const std::string* boneList, const int numBones, const char* name ) const; void CacheBounds(); diff --git a/trinity/Tr2MeshBase_Blue.cpp b/trinity/Tr2MeshBase_Blue.cpp index 2f4f9e1f1..8a31cf6ca 100644 --- a/trinity/Tr2MeshBase_Blue.cpp +++ b/trinity/Tr2MeshBase_Blue.cpp @@ -6,6 +6,39 @@ BLUE_DEFINE_ABSTRACT( Tr2MeshBase ); +#if BLUE_WITH_PYTHON + +static PyObject* PyGetAreas( PyObject* self, PyObject* args ) +{ + Tr2MeshBase* pThis = BluePythonCast( self ); + if( !pThis ) + { + return NULL; + } + + int areaIndex; + + if( !PyArg_ParseTuple( args, "i", &areaIndex ) ) + { + PyErr_SetString( PyExc_TypeError, "Arguments must be (i)." ); + return 0; + } + + TriBatchType type( static_cast( areaIndex ) ); + + Tr2MeshAreaVector* areas( pThis->GetAreas( type ) ); + + if( !areas ) + { + Py_RETURN_NONE; + } + + return PyOS->WrapBlueObject( areas->GetRawRoot() ); +} + +#endif + + const Be::ClassInfo* Tr2MeshBase::ExposeToBlue() { EXPOSURE_BEGIN( Tr2MeshBase, "" ) @@ -40,5 +73,20 @@ const Be::ClassInfo* Tr2MeshBase::ExposeToBlue() "Assigns material bounds adjustment parameters. Normally set by the publisher\n" ":param adjustment: adjustment values extracted from shaders" ) + MAP_PROPERTY_READONLY( + "isLoading", + IsLoading, + "If set, mesh resources might still be loading" ) + + MAP_METHOD( + "GetAreas", + PyGetAreas, + "Returns mesh area list\n" + ":param areaType: area type\n" + ":type areaType: int\n" + ":rtype: None | List" ); + + MAP_METHOD_AND_WRAP( "GetAreasCount", GetAreasCount, "" ); + EXPOSURE_END() } diff --git a/trinity/Tr2MeshProcedural.cpp b/trinity/Tr2MeshProcedural.cpp new file mode 100644 index 000000000..19b1d538b --- /dev/null +++ b/trinity/Tr2MeshProcedural.cpp @@ -0,0 +1,73 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2MeshProcedural.h" +#include "Resources/Tr2GeometryProvider.h" + + +Tr2MeshProcedural::Tr2MeshProcedural( IRoot* lockobj ) +{ +} + +Tr2MeshProcedural::~Tr2MeshProcedural() +{ + if( m_geometryResource ) + { + m_geometryResource->RemoveNotifyTarget( this ); + } +} + +Tr2ProceduralGeometryProvider* Tr2MeshProcedural::GetGeometry() +{ + return m_geometryResource; +} + +void Tr2MeshProcedural::SetGeometryResource( Tr2ProceduralGeometryProvider* res ) +{ + if( m_geometryResource ) + { + m_geometryResource->RemoveNotifyTarget( this ); + } + + m_geometryResource = res; + + if( m_geometryResource ) + { + m_geometryResource->AddNotifyTarget( this ); + } + + if( m_geometryResource && !m_geometryResource->IsLoading() && !m_geometryResource->IsPrepared() ) + { + m_geometryResource->Rebuild(); + } +} + +Tr2GeometryProvider* Tr2MeshProcedural::GetGeometryResource() const +{ + if( m_geometryResource && m_geometryResource->IsGood() ) + { + return m_geometryResource; + } + return nullptr; +} + +bool Tr2MeshProcedural::IsLoading() const +{ + return m_geometryResource && m_geometryResource->IsLoading(); +} + +void Tr2MeshProcedural::ReleaseCachedData( BlueAsyncRes* p ) +{ + if( p == m_geometryResource ) + { + CacheBounds(); + } +} + +void Tr2MeshProcedural::RebuildCachedData( BlueAsyncRes* p ) +{ + if( p == m_geometryResource ) + { + CacheBounds(); + } +} diff --git a/trinity/Tr2MeshProcedural.h b/trinity/Tr2MeshProcedural.h new file mode 100644 index 000000000..903e990eb --- /dev/null +++ b/trinity/Tr2MeshProcedural.h @@ -0,0 +1,38 @@ +// Copyright © 2026 CCP ehf. + +#pragma once +#ifndef Tr2MeshProcedural_h +#define Tr2MeshProcedural_h + +#include "Tr2MeshBase.h" +#include "Resources/Tr2ProceduralGeometryProvider.h" + +BLUE_CLASS_IMPL( Tr2MeshProcedural ) +class Tr2MeshProcedural : public Tr2MeshBase, + public IBlueAsyncResNotifyTarget +{ +public: + EXPOSE_TO_BLUE(); + + Tr2MeshProcedural( IRoot* lockobj = NULL ); + ~Tr2MeshProcedural(); + + Tr2GeometryProvider* GetGeometryResource() const override; + Tr2ProceduralGeometryProvider* GetGeometry(); + void SetGeometryResource( Tr2ProceduralGeometryProvider* res ); + + bool IsLoading() const override; + + ///////////////////////////////////////////////////////////////////////////////////// + // IBlueAsyncResNotifyTarget + void ReleaseCachedData( BlueAsyncRes* p ) override; + void RebuildCachedData( BlueAsyncRes* p ) override; + +private: + Tr2ProceduralGeometryProviderPtr m_geometryResource; +}; + +TYPEDEF_BLUECLASS( Tr2MeshProcedural ); +BLUE_DECLARE_VECTOR( Tr2MeshProcedural ); + +#endif diff --git a/trinity/Tr2MeshProcedural_Blue.cpp b/trinity/Tr2MeshProcedural_Blue.cpp new file mode 100644 index 000000000..bb532e4d3 --- /dev/null +++ b/trinity/Tr2MeshProcedural_Blue.cpp @@ -0,0 +1,23 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" + +#include "Tr2MeshProcedural.h" + +#include "Resources/Tr2GeometryProvider.h" +#include "TriConstants.h" + + +BLUE_DEFINE( Tr2MeshProcedural ); + +const Be::ClassInfo* Tr2MeshProcedural::ExposeToBlue() +{ + EXPOSURE_BEGIN( Tr2MeshProcedural, "" ) + + MAP_INTERFACE( Tr2MeshProcedural ) + + MAP_ATTRIBUTE( "geometry", m_geometryResource, "", Be::HIDDEN ) // Needed to make Graphite able to detect bindings inside of the geometry + MAP_PROPERTY( "geometry", GetGeometry, SetGeometryResource, "Procedural geometry" ) + + EXPOSURE_CHAINTO( Tr2MeshBase ) +} diff --git a/trinity/Tr2Mesh_Blue.cpp b/trinity/Tr2Mesh_Blue.cpp index b7505ddef..ebde2a8e4 100644 --- a/trinity/Tr2Mesh_Blue.cpp +++ b/trinity/Tr2Mesh_Blue.cpp @@ -8,7 +8,6 @@ #include "TriConstants.h" - BLUE_DEFINE( Tr2SerializedMorphAnimation ); const Be::ClassInfo* Tr2SerializedMorphAnimation::ExposeToBlue(){ EXPOSURE_BEGIN( Tr2SerializedMorphAnimation, "" ) @@ -21,36 +20,6 @@ const Be::ClassInfo* Tr2SerializedMorphAnimation::ExposeToBlue(){ BLUE_DEFINE( Tr2Mesh ); -#if BLUE_WITH_PYTHON - -static PyObject* PyGetAreas( PyObject* self, PyObject* args ) -{ - Tr2Mesh* pThis = BluePythonCast( self ); - if( !pThis ) - { - return NULL; - } - - int areaIndex; - - if( !PyArg_ParseTuple( args, "i", &areaIndex ) ) - { - PyErr_SetString( PyExc_TypeError, "Arguments must be (i)." ); - return 0; - } - - TriBatchType type( static_cast( areaIndex ) ); - - Tr2MeshAreaVector* areas( pThis->GetAreas( type ) ); - - if( !areas ) - { - Py_RETURN_NONE; - } - - return PyOS->WrapBlueObject( areas->GetRawRoot() ); -} -#endif const Be::ClassInfo* Tr2Mesh::ExposeToBlue() { @@ -68,10 +37,6 @@ const Be::ClassInfo* Tr2Mesh::ExposeToBlue() Be::READWRITE | Be::NOTIFY | Be::PERSIST, TriGR2Chooser ) MAP_ATTRIBUTE( "deferGeometryLoad", m_deferGeometryLoad, "Defers geometry load when loading mesh objects", Be::READWRITE | Be::NOTIFY | Be::PERSIST ) - MAP_PROPERTY_READONLY( - "isLoading", - IsLoading, - "If set, mesh resources might still be loading" ) MAP_METHOD_AND_WRAP( "SetGeometryRes", @@ -80,15 +45,6 @@ const Be::ClassInfo* Tr2Mesh::ExposeToBlue() "This is used for geometry resources that require special handling, such as pre-baked blendshapes.\n" ":param geometry: new geometry resource" ) - MAP_METHOD( - "GetAreas", - PyGetAreas, - "Returns mesh area list\n" - ":param areaType: area type\n" - ":type areaType: int\n" - ":rtype: None | List" ); - MAP_METHOD_AND_WRAP( "GetAreasCount", GetAreasCount, "" ); - MAP_ATTRIBUTE( "serializedMorphAnimations", m_serializedMorphAnimations, "", Be::PERSISTONLY ) EXPOSURE_CHAINTO( Tr2MeshBase ) diff --git a/trinity/Tr2Model.cpp b/trinity/Tr2Model.cpp index 68f53a7f3..399d6c9aa 100644 --- a/trinity/Tr2Model.cpp +++ b/trinity/Tr2Model.cpp @@ -167,7 +167,7 @@ void Tr2Model::GetBatchesFromMesh( Tr2Mesh* mesh, return; } - TriGeometryRes* geomRes = mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return; diff --git a/trinity/Tr2SkinnedModel.cpp b/trinity/Tr2SkinnedModel.cpp index 0decae824..215c656bf 100644 --- a/trinity/Tr2SkinnedModel.cpp +++ b/trinity/Tr2SkinnedModel.cpp @@ -36,7 +36,7 @@ void Tr2SkinnedModel::GetBatchesForArea( Tr2MeshAreaVector* areas, Tr2Mesh* mesh return; } - TriGeometryRes* geomRes = mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return; From 2d3b59f5896ddd1735d7a80f676b8e3d1350dfd2 Mon Sep 17 00:00:00 2001 From: ccp-intern <180460643+ccp-intern@users.noreply.github.com> Date: Mon, 26 Oct 2026 16:05:50 +0100 Subject: [PATCH 2/4] Plat 12257 procedural meshes (#132) * wip procedural meshes * wip procedural mesh * wip procedural mesh * wip procedural mesh * wip procedural mesh * fix procedural meshes * fix procedural meshes * cleanup * fix rest pose fix... * cleanup * cleanup * cleanup * cleanup * cleanup * sierpinski * sierpinski normals * sierpinski winding fix * fix race condition --- trinity/CMakeLists.txt | 17 + .../Attachments/EveSpaceObjectDecal.cpp | 6 +- .../Attachments/EveSpaceObjectDecal.h | 8 +- .../Eve/SpaceObject/Children/EveChildMesh.cpp | 38 +- .../SpaceObject/Children/EveChildTurret.cpp | 4 +- .../Eve/SpaceObject/Children/EveChildTurret.h | 6 +- .../Children/EveSpaceObjectChild.h | 2 +- trinity/Eve/SpaceObject/EveSpaceObject2.cpp | 20 +- trinity/Eve/SpaceObject/EveSpaceObject2.h | 5 +- trinity/Eve/SpaceObject/EveSwarm.cpp | 6 +- trinity/Interior/Tr2IntSkinnedObject.cpp | 2 +- trinity/Raytracing/Tr2RaytracingGeometry.cpp | 2 +- trinity/Raytracing/Tr2RaytracingGeometry.h | 6 +- trinity/Resources/Tr2CmfContent.cpp | 34 +- trinity/Resources/Tr2CmfContent.h | 10 + trinity/Resources/Tr2GeometryProvider.cpp | 1624 ++++++++++++++++ trinity/Resources/Tr2GeometryProvider.h | 341 ++++ .../Resources/Tr2GeometryProvider_Blue.cpp | 195 ++ trinity/Resources/Tr2ProceduralCube.cpp | 139 ++ trinity/Resources/Tr2ProceduralCube.h | 36 + trinity/Resources/Tr2ProceduralCube_Blue.cpp | 21 + .../Tr2ProceduralGeometryProvider.cpp | 205 ++ .../Resources/Tr2ProceduralGeometryProvider.h | 61 + .../Tr2ProceduralGeometryProvider_Blue.cpp | 29 + trinity/Resources/Tr2ProceduralSierpinski.cpp | 242 +++ trinity/Resources/Tr2ProceduralSierpinski.h | 40 + .../Tr2ProceduralSierpinski_Blue.cpp | 22 + trinity/Resources/TriGeometryRes.cpp | 1668 +---------------- trinity/Resources/TriGeometryRes.h | 375 +--- trinity/Resources/TriGeometryRes_Blue.cpp | 184 +- trinity/Tr2BackgroundWorker.cpp | 30 + trinity/Tr2BackgroundWorker.h | 21 + trinity/Tr2InstancedMesh.cpp | 2 +- trinity/Tr2Mesh.cpp | 7 +- trinity/Tr2Mesh.h | 4 +- trinity/Tr2MeshBase.cpp | 39 +- trinity/Tr2MeshBase.h | 22 +- trinity/Tr2MeshBase_Blue.cpp | 48 + trinity/Tr2MeshProcedural.cpp | 73 + trinity/Tr2MeshProcedural.h | 38 + trinity/Tr2MeshProcedural_Blue.cpp | 23 + trinity/Tr2Mesh_Blue.cpp | 44 - trinity/Tr2Model.cpp | 2 +- trinity/Tr2SkinnedModel.cpp | 2 +- 44 files changed, 3456 insertions(+), 2247 deletions(-) create mode 100644 trinity/Resources/Tr2GeometryProvider.cpp create mode 100644 trinity/Resources/Tr2GeometryProvider.h create mode 100644 trinity/Resources/Tr2GeometryProvider_Blue.cpp create mode 100644 trinity/Resources/Tr2ProceduralCube.cpp create mode 100644 trinity/Resources/Tr2ProceduralCube.h create mode 100644 trinity/Resources/Tr2ProceduralCube_Blue.cpp create mode 100644 trinity/Resources/Tr2ProceduralGeometryProvider.cpp create mode 100644 trinity/Resources/Tr2ProceduralGeometryProvider.h create mode 100644 trinity/Resources/Tr2ProceduralGeometryProvider_Blue.cpp create mode 100644 trinity/Resources/Tr2ProceduralSierpinski.cpp create mode 100644 trinity/Resources/Tr2ProceduralSierpinski.h create mode 100644 trinity/Resources/Tr2ProceduralSierpinski_Blue.cpp create mode 100644 trinity/Tr2BackgroundWorker.cpp create mode 100644 trinity/Tr2BackgroundWorker.h create mode 100644 trinity/Tr2MeshProcedural.cpp create mode 100644 trinity/Tr2MeshProcedural.h create mode 100644 trinity/Tr2MeshProcedural_Blue.cpp diff --git a/trinity/CMakeLists.txt b/trinity/CMakeLists.txt index cbf221aef..99e9d638d 100644 --- a/trinity/CMakeLists.txt +++ b/trinity/CMakeLists.txt @@ -1247,6 +1247,9 @@ set(_SOURCES Resources/Tr2EffectRes.cpp Resources/Tr2EffectRes.h Resources/Tr2EffectRes_Blue.cpp + Resources/Tr2GeometryProvider.cpp + Resources/Tr2GeometryProvider.h + Resources/Tr2GeometryProvider_Blue.cpp Resources/Tr2GrannyStateRes.cpp Resources/Tr2GrannyStateRes.h Resources/Tr2GrannyStateRes_Blue.cpp @@ -1262,6 +1265,15 @@ set(_SOURCES Resources/Tr2MaterialRes.cpp Resources/Tr2MaterialRes.h Resources/Tr2MaterialRes_Blue.cpp + Resources/Tr2ProceduralCube.cpp + Resources/Tr2ProceduralCube.h + Resources/Tr2ProceduralCube_Blue.cpp + Resources/Tr2ProceduralGeometryProvider.cpp + Resources/Tr2ProceduralGeometryProvider.h + Resources/Tr2ProceduralGeometryProvider_Blue.cpp + Resources/Tr2ProceduralSierpinski.cpp + Resources/Tr2ProceduralSierpinski.h + Resources/Tr2ProceduralSierpinski_Blue.cpp Resources/Tr2TextureLodManager.cpp Resources/Tr2TextureLodManager.h Resources/Tr2TextureLodManager_Blue.cpp @@ -1481,6 +1493,8 @@ set(_SOURCES Tr2AtlasTexture.cpp Tr2AtlasTexture.h Tr2AtlasTexture_Blue.cpp + Tr2BackgroundWorker.cpp + Tr2BackgroundWorker.h Tr2BindingVector3.h Tr2BindingVector3_Blue.cpp Tr2Blitter.cpp @@ -1591,6 +1605,9 @@ set(_SOURCES Tr2MeshBase.cpp Tr2MeshBase.h Tr2MeshBase_Blue.cpp + Tr2MeshProcedural.cpp + Tr2MeshProcedural.h + Tr2MeshProcedural_Blue.cpp Tr2Model.cpp Tr2Model.h Tr2Model_Blue.cpp diff --git a/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.cpp b/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.cpp index 52e288c8d..94c490056 100644 --- a/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.cpp +++ b/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.cpp @@ -179,7 +179,7 @@ void EveSpaceObjectDecal::UpdateVisibility( const EveUpdateContext& updateContex } // ------------------------------------------------------------------------------------------------------ -void EveSpaceObjectDecal::GetRenderables( std::vector& renderables, DecalMeshCache& meshCache, TriGeometryRes* geomRes, float screensize ) +void EveSpaceObjectDecal::GetRenderables( std::vector& renderables, DecalMeshCache& meshCache, Tr2GeometryProvider* geomRes, float screensize ) { if( m_isVisible <= 0 || !geomRes ) { @@ -589,7 +589,7 @@ void EveSpaceObjectDecal::SetBoneIndex( int idx ) } // ------------------------------------------------------------------------------------------------------ -void EveSpaceObjectDecal::CreateDecalIndexBuffers( TriGeometryResPtr geomRes, DecalMeshCache& meshCache ) +void EveSpaceObjectDecal::CreateDecalIndexBuffers( Tr2GeometryProviderPtr geomRes, DecalMeshCache& meshCache ) { TRINITY_STATS_ZONE( __FUNCTION__ ); @@ -824,7 +824,7 @@ void EveSpaceObjectDecal::SetMinScreenSize( float minScreenSize ) m_minScreenSize = minScreenSize; } -void EveSpaceObjectDecal::CreateStaticIndexBuffers( TriGeometryResPtr geomRes ) +void EveSpaceObjectDecal::CreateStaticIndexBuffers( Tr2GeometryProviderPtr geomRes ) { TRINITY_STATS_ZONE( __FUNCTION__ ); diff --git a/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.h b/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.h index 6de349978..adb8ace34 100644 --- a/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.h +++ b/trinity/Eve/SpaceObject/Attachments/EveSpaceObjectDecal.h @@ -128,7 +128,7 @@ BLUE_CLASS( EveSpaceObjectDecal ) : // access void UpdateVisibility( const EveUpdateContext& updateContext, const IEveSpaceObject2::ParentData* parentData ); - void GetRenderables( std::vector & renderables, DecalMeshCache & meshCache, TriGeometryRes * geomRes, float screensize ); + void GetRenderables( std::vector & renderables, DecalMeshCache & meshCache, Tr2GeometryProvider * geomRes, float screensize ); void GetInstancedRenderables( std::vector & renderables, DecalMeshCache & meshCache, const Tr2InstancedMesh* instancedMesh, float instanceScreenSize = std::numeric_limits::max() ); // access position etc. @@ -160,10 +160,10 @@ BLUE_CLASS( EveSpaceObjectDecal ) : private: // create - void CreateDecalIndexBuffers( TriGeometryResPtr geomRes, DecalMeshCache & meshCache ); + void CreateDecalIndexBuffers( Tr2GeometryProviderPtr geomRes, DecalMeshCache & meshCache ); // update void UpdateDecalMatrix(); - void CreateStaticIndexBuffers( TriGeometryResPtr geomRes ); + void CreateStaticIndexBuffers( Tr2GeometryProviderPtr geomRes ); bool HasStaticIndexBuffers() const; std::vector> GetStaticIndexBuffers() const; @@ -195,7 +195,7 @@ BLUE_CLASS( EveSpaceObjectDecal ) : Matrix m_inverseDecalMatrix; // base mesh geometry - TriGeometryResPtr m_baseGeometryResource; + Tr2GeometryProviderPtr m_baseGeometryResource; int m_geometryLodIndex; std::shared_ptr m_decalGeometry; diff --git a/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp b/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp index 2085ad14a..a4508b079 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp @@ -225,7 +225,7 @@ void EveChildMesh::InitializeAnimation() if( auto geometry = m_mesh->GetGeometryResource() ) { m_animationUpdater->SetUseMeshBinding( true ); - m_animationUpdater->SetSharedGeometryRes( geometry ); + m_animationUpdater->SetSharedGeometryRes( BlueCastPtr( geometry ) ); return; } } @@ -711,7 +711,7 @@ void EveChildMesh::GetBatchesFromOverlayVector( ITriRenderBatchAccumulator* batc return; } - TriGeometryRes* geomRes = m_mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = m_mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return; @@ -1167,6 +1167,7 @@ void EveChildMesh::UpdateSyncronous( const EveUpdateContext& updateContext, cons UnregisterAudioGeometry(); } m_allowAudioGeometry = allowAudioGeometry; + if( m_animationUpdater ) { if( m_mesh && m_animationUpdater->GetResPath().empty() ) @@ -1185,9 +1186,16 @@ void EveChildMesh::UpdateSyncronous( const EveUpdateContext& updateContext, cons if( m_mesh && !m_animationUpdater->HasMeshBinding() ) { - if( !m_meshBinding || m_meshBinding->GetAnimation() != m_animationUpdater || m_meshBinding->GetGeometryRes() != m_mesh->GetGeometryResource() || m_meshBinding->GetMeshIndex() != m_mesh->GetMeshIndex() ) + auto geometryRes = dynamic_cast( m_mesh->GetGeometryResource() ); + + if( m_meshBinding && ( m_meshBinding->GetAnimation() != m_animationUpdater || m_meshBinding->GetGeometryRes() != geometryRes || m_meshBinding->GetMeshIndex() != m_mesh->GetMeshIndex() ) ) + { + m_meshBinding = {}; + } + + if( !m_meshBinding && geometryRes ) { - m_meshBinding = std::make_unique( m_animationUpdater, m_mesh->GetGeometryResource(), m_mesh->GetMeshIndex() ); + m_meshBinding = std::make_unique( m_animationUpdater, geometryRes, m_mesh->GetMeshIndex() ); } } else @@ -1428,10 +1436,14 @@ void EveChildMesh::RenderDebugInfo( ITr2DebugRenderer2& renderer ) m_mesh->RenderDebugInfo( m_worldTransform, renderer, nullptr ); } - if( bvhOptionEnabled && !m_bvhVisualizationGeometry && m_mesh->GetGeometryResource() && m_mesh->GetGeometryResource()->IsGood() ) + if( bvhOptionEnabled && !m_bvhVisualizationGeometry ) { - m_bvhVisualizationGeometry = m_mesh->GetGeometryResource(); - m_bvhVisualizationGeometry->PrepareRayCaster(); + TriGeometryResPtr geometryRes = BlueCastPtr( m_mesh->GetGeometryResource() ); + if( geometryRes && geometryRes->IsGood() ) + { + m_bvhVisualizationGeometry = geometryRes; + m_bvhVisualizationGeometry->PrepareRayCaster(); + } } if( m_bvhVisualizationGeometry && m_bvhVisualizationGeometry->IsRayCasterReady() ) { @@ -1543,16 +1555,12 @@ std::pair EveChildMesh::GetRestPoseBoneTransforms() con 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. + // Fixes skinned shader used with uninitialized animationUpdater (no skeleton), or used with no boneBindings. Otherwise shader would read garbage. size_t boneCount = 1; - auto cmfData = m_mesh->GetGeometryResource()->GetCMFData(); - auto meshIndex = m_mesh->GetMeshIndex(); - if( cmfData && meshIndex < cmfData->meshes.size() ) + auto meshData = m_mesh->GetGeometryResource()->GetMeshData( m_mesh->GetMeshIndex() ); + if( meshData ) { - boneCount = std::max( cmfData->meshes[meshIndex].boneBindings.size(), 1 ); + boneCount = std::max( meshData->m_jointBindings.size(), 1 ); } if( m_restPoseBoneTransforms.size() != boneCount ) { diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp index 306269b33..1de214c04 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.cpp @@ -313,7 +313,7 @@ void EveChildTurret::UpdateCachedGeometryData() m_cachedGeometryRes = geometryRes; } } -void EveChildTurret::BuildCachedGeometryData( TriGeometryRes& geometryRes ) +void EveChildTurret::BuildCachedGeometryData( Tr2GeometryProvider& geometryRes ) { if( geometryRes.GetSkeletonCount() ) { @@ -771,7 +771,7 @@ Matrix EveChildTurret::GetTurretBoneTransform( uint32_t boneID ) const return matrix; } -TriGeometryRes* EveChildTurret::GetGeometryRes() const +Tr2GeometryProvider* EveChildTurret::GetGeometryRes() const { return m_mesh ? m_mesh->GetGeometryResource() : nullptr; } diff --git a/trinity/Eve/SpaceObject/Children/EveChildTurret.h b/trinity/Eve/SpaceObject/Children/EveChildTurret.h index 5d51db4b3..3d22fce95 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildTurret.h +++ b/trinity/Eve/SpaceObject/Children/EveChildTurret.h @@ -39,7 +39,7 @@ BLUE_CLASS( EveChildTurret ) : void AddQuadsToQuadRenderer( const TriFrustum& frustum, Tr2QuadRenderer& quadRenderer ) const override; void UpdateCachedGeometryData(); - void BuildCachedGeometryData( TriGeometryRes & geometryRes ); + void BuildCachedGeometryData( Tr2GeometryProvider & geometryRes ); void ReleaseCachedGeometryData(); // action @@ -81,7 +81,7 @@ BLUE_CLASS( EveChildTurret ) : Matrix GetTurretBoneTransform( uint32_t boneID ) const; - TriGeometryRes* GetGeometryRes() const; + Tr2GeometryProvider* GetGeometryRes() const; // animation float PlayAnimation( const std::string& animName, const std::string& animNameIdle, float delay = 0.f ); @@ -141,7 +141,7 @@ BLUE_CLASS( EveChildTurret ) : std::wstring m_idleToTargetingMovementAudioEvent; std::wstring m_targetingToIdleMovementAudioEvent; - TriGeometryResPtr m_cachedGeometryRes; + Tr2GeometryProviderPtr m_cachedGeometryRes; }; TYPEDEF_BLUECLASS( EveChildTurret ); diff --git a/trinity/Eve/SpaceObject/Children/EveSpaceObjectChild.h b/trinity/Eve/SpaceObject/Children/EveSpaceObjectChild.h index 202c910a9..0d321480c 100644 --- a/trinity/Eve/SpaceObject/Children/EveSpaceObjectChild.h +++ b/trinity/Eve/SpaceObject/Children/EveSpaceObjectChild.h @@ -43,7 +43,7 @@ struct EveChildGeometryArea */ struct EveChildGeometry { - class TriGeometryRes* geometry = nullptr; + class Tr2GeometryProvider* geometry = nullptr; Matrix childToObject = IdentityMatrix(); uint32_t areaStart = 0; uint32_t areaCount = 0; diff --git a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp index 0dac34cba..3590b822b 100644 --- a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp +++ b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp @@ -66,7 +66,7 @@ void GetSortedBatchesFromMeshAreaVector( const Tr2MeshAreaVector* areas, float screenSize, const Matrix* worldTransform ) { - TriGeometryRes* geomRes = mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { @@ -1181,7 +1181,7 @@ void EveSpaceObject2::GetShadowBatches( ITriRenderBatchAccumulator* batches, con return; } - TriGeometryRes* geomRes = m_mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = m_mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return; @@ -1251,7 +1251,7 @@ void EveSpaceObject2::GetBatchesFromOverlayVector( ITriRenderBatchAccumulator* b { return; } - TriGeometryRes* geomRes = mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return; @@ -1489,7 +1489,8 @@ Vector4 EveSpaceObject2::CalculateSkinnedBoundingSphere() #if WITH_GRANNY else { - return m_animationUpdater->CalculateSkinnedBoundingSphere( m_mesh->GetGeometryResource()->GetGrannyInfo() ); + auto geometry = dynamic_cast( m_mesh->GetGeometryResource() ); + return m_animationUpdater->CalculateSkinnedBoundingSphere( geometry ? geometry->GetGrannyInfo() : nullptr ); } #endif } @@ -1524,7 +1525,8 @@ std::pair EveSpaceObject2::CalculateSkinnedBoundingBoxFromTran #if WITH_GRANNY else { - m_animationUpdater->CalculateSkinnedBoundingBoxFromTransform( transform, bbMin, bbMax, m_geometryResFromMesh->GetGrannyInfo() ); + TriGeometryResPtr geometry = BlueCastPtr( m_geometryResFromMesh ); + m_animationUpdater->CalculateSkinnedBoundingBoxFromTransform( transform, bbMin, bbMax, geometry ? geometry->GetGrannyInfo() : nullptr ); } #endif } @@ -1577,7 +1579,7 @@ void EveSpaceObject2::PushChildrenAndDecalRenderables( std::vectorGetGeometryResource(); + Tr2GeometryProviderPtr geometryRes = m_mesh->GetGeometryResource(); if( geometryRes ) { DecalMeshCache meshCache; @@ -2381,7 +2383,7 @@ bool EveSpaceObject2::RebuildBoundingSphereInformation() return false; } - TriGeometryRes* geomRes = mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return false; @@ -3199,7 +3201,7 @@ void EveSpaceObject2::PrepareForAnimation() m_geometryResFromMesh = geometryRes; m_animationUpdater->SetUseMeshBinding( true ); - m_animationUpdater->SetSharedGeometryRes( m_geometryResFromMesh ); + m_animationUpdater->SetSharedGeometryRes( BlueCastPtr( m_geometryResFromMesh ) ); m_geometryResFromMesh->AddNotifyTarget( this ); } @@ -4220,7 +4222,7 @@ bool EveSpaceObject2::IsBoundingBoxReady() const return false; } - TriGeometryRes* geomRes = mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return false; diff --git a/trinity/Eve/SpaceObject/EveSpaceObject2.h b/trinity/Eve/SpaceObject/EveSpaceObject2.h index a1ce39d09..6668d2ead 100644 --- a/trinity/Eve/SpaceObject/EveSpaceObject2.h +++ b/trinity/Eve/SpaceObject/EveSpaceObject2.h @@ -55,6 +55,7 @@ BLUE_DECLARE_INTERFACE( IEveSpaceObject2 ); BLUE_DECLARE( EveSpaceObject2 ); BLUE_DECLARE_VECTOR( EveSpaceObject2 ); BLUE_DECLARE( TriGeometryRes ); +BLUE_DECLARE( Tr2GeometryProvider ); BLUE_DECLARE( Tr2MeshBase ); BLUE_DECLARE( Tr2MeshArea ); BLUE_DECLARE_VECTOR( Tr2MeshArea ); @@ -182,7 +183,7 @@ struct LocatorSourceRange struct DamageFilterOccluder { - TriGeometryResPtr geometry; + Tr2GeometryProviderPtr geometry; Matrix fromObject; uint32_t areaStart = 0; uint32_t areaCount = 0; @@ -631,7 +632,7 @@ BLUE_CLASS( EveSpaceObject2 ) : Tr2Lod m_lodLevel; Tr2Lod m_lodLevelWithChildren; - TriGeometryResPtr m_geometryResFromMesh; + Tr2GeometryProviderPtr m_geometryResFromMesh; ///////////////////////////////////////////////////////////////////////////////////// // dissolve diff --git a/trinity/Eve/SpaceObject/EveSwarm.cpp b/trinity/Eve/SpaceObject/EveSwarm.cpp index 63d440542..7630987da 100644 --- a/trinity/Eve/SpaceObject/EveSwarm.cpp +++ b/trinity/Eve/SpaceObject/EveSwarm.cpp @@ -165,7 +165,7 @@ void EveSwarmRenderable::InitDecals( const PEveSpaceObjectDecalVector& decals ) void EveSwarmRenderable::PushDecals( std::vector& renderables, float screensize ) { - TriGeometryResPtr geometryRes = m_mesh->GetGeometryResource(); + Tr2GeometryProviderPtr geometryRes = m_mesh->GetGeometryResource(); if( geometryRes ) { @@ -181,7 +181,7 @@ void EveSwarmRenderable::PushDecals( std::vector& renderables, void EveSwarmRenderable::UpdateDecalVisibility( const EveUpdateContext& updateContext, IEveSpaceObject2::ParentData& pd, Tr2GrannyAnimation* animationUpdater ) { - TriGeometryResPtr geometryRes = m_mesh->GetGeometryResource(); + Tr2GeometryProviderPtr geometryRes = m_mesh->GetGeometryResource(); if( geometryRes ) { @@ -273,7 +273,7 @@ void EveSwarmRenderable::GetShadowBatches( ITriRenderBatchAccumulator* batches, return; } - TriGeometryRes* geomRes = m_mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = m_mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return; diff --git a/trinity/Interior/Tr2IntSkinnedObject.cpp b/trinity/Interior/Tr2IntSkinnedObject.cpp index 00e47bc83..9271be2c4 100644 --- a/trinity/Interior/Tr2IntSkinnedObject.cpp +++ b/trinity/Interior/Tr2IntSkinnedObject.cpp @@ -239,7 +239,7 @@ void Tr2IntSkinnedObject::GetBatches( ITriRenderBatchAccumulator* batches, continue; } - TriGeometryRes* geomRes = mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { continue; diff --git a/trinity/Raytracing/Tr2RaytracingGeometry.cpp b/trinity/Raytracing/Tr2RaytracingGeometry.cpp index 85f8272b5..825d2a207 100644 --- a/trinity/Raytracing/Tr2RaytracingGeometry.cpp +++ b/trinity/Raytracing/Tr2RaytracingGeometry.cpp @@ -169,7 +169,7 @@ Tr2RaytracingMesh::Tr2RaytracingMesh() : { } -void Tr2RaytracingMesh::UpdateRtMesh( TriGeometryRes* geometry, uint32_t meshIndex, float screenSize ) +void Tr2RaytracingMesh::UpdateRtMesh( Tr2GeometryProvider* geometry, uint32_t meshIndex, float screenSize ) { if( m_geometry != geometry || m_meshIndex != meshIndex ) { diff --git a/trinity/Raytracing/Tr2RaytracingGeometry.h b/trinity/Raytracing/Tr2RaytracingGeometry.h index 44aeefcbf..7ea1121e3 100644 --- a/trinity/Raytracing/Tr2RaytracingGeometry.h +++ b/trinity/Raytracing/Tr2RaytracingGeometry.h @@ -11,7 +11,7 @@ BLUE_DECLARE( Tr2MeshArea ); BLUE_DECLARE_VECTOR( Tr2MeshArea ); BLUE_DECLARE( Tr2Effect ); -BLUE_DECLARE( TriGeometryRes ); +BLUE_DECLARE( Tr2GeometryProvider ); BLUE_DECLARE( Tr2RaytracingGeometry ); struct TriGeometryResLodData; @@ -46,7 +46,7 @@ class Tr2RaytracingMesh public: Tr2RaytracingMesh(); - void UpdateRtMesh( TriGeometryRes* geometry, uint32_t meshIndex, float screenSize ); + void UpdateRtMesh( Tr2GeometryProvider* geometry, uint32_t meshIndex, float screenSize ); bool SetBoneTransforms( size_t count, const Float4x3* transforms, uint32_t offset ); bool SetMorphAnimations( size_t count, const Tr2MorphTargetAnimationData* morphTargets, uint32_t morphTargetAnimationDataOffset ); @@ -67,7 +67,7 @@ class Tr2RaytracingMesh private: const Tr2BufferAL* m_skinnedVertices; - TriGeometryResPtr m_geometry; + Tr2GeometryProviderPtr m_geometry; uint32_t m_meshIndex; std::vector m_transforms; uint32_t m_boneOffset; diff --git a/trinity/Resources/Tr2CmfContent.cpp b/trinity/Resources/Tr2CmfContent.cpp index 5daa8f48b..2860954b1 100644 --- a/trinity/Resources/Tr2CmfContent.cpp +++ b/trinity/Resources/Tr2CmfContent.cpp @@ -7,22 +7,16 @@ #include -Tr2CmfContents::Tr2CmfContents( IBlueStream& stream, const char* filename ) +void Tr2CmfContents::Initialize( const void* data, size_t size, const char* name, cmf::ValidationOptions options ) { - void* data; - if( !stream.LockData( &data, 0 ) ) - { - CCP_LOGERR( "Failed to read CMF file %s", filename ); - return; - } - auto size = stream.GetSize(); - auto validated = cmf::ValidateFile( data, size, { true, true } ); + auto validated = cmf::ValidateFile( data, size, options ); if( !validated ) { - CCP_LOGERR( "Invalid CMF file %s: %s", filename, validated.error.c_str() ); + CCP_LOGERR( "Invalid CMF data %s: %s", name, validated.error.c_str() ); return; } - auto header = static_cast( data ); + + auto header = static_cast( data ); m_sections.reserve( header->sections.size() ); for( const auto& section : header->sections ) { @@ -32,9 +26,27 @@ Tr2CmfContents::Tr2CmfContents( IBlueStream& stream, const char* filename ) dest.data = std::make_unique( section.compressedSize ); memcpy( dest.data.get(), (uint8_t*)data + section.offset, section.compressedSize ); } + cmf::OffsetsToPointers( *reinterpret_cast( m_sections[0].data.get() ) ); } +Tr2CmfContents::Tr2CmfContents( IBlueStream& stream, const char* filename ) +{ + void* data; + if( !stream.LockData( &data, 0 ) ) + { + CCP_LOGERR( "Failed to read CMF file %s", filename ); + return; + } + auto size = stream.GetSize(); + Initialize( data, size, filename, { true, true } ); +} + +Tr2CmfContents::Tr2CmfContents( const void* data, size_t size, const char* name ) +{ + Initialize( data, size, name, { false, true } ); +} + Tr2CmfContents::operator bool() const { return !m_sections.empty(); diff --git a/trinity/Resources/Tr2CmfContent.h b/trinity/Resources/Tr2CmfContent.h index 5e0f908b2..e7c77954a 100644 --- a/trinity/Resources/Tr2CmfContent.h +++ b/trinity/Resources/Tr2CmfContent.h @@ -16,6 +16,14 @@ class Tr2CmfContents */ explicit Tr2CmfContents( IBlueStream& stream, const char* filename ); + /** + * @brief Creates the cmf contents in memory. + * @param data The cmf. + * @param size The size of the cmf. + * @param name The name for the cmf. + */ + Tr2CmfContents( const void* data, size_t size, const char* name ); + /** * @brief Returns true if the .cmf file was successfully read and parsed, false otherwise. */ @@ -54,4 +62,6 @@ class Tr2CmfContents std::unique_ptr data; }; std::vector
m_sections; + + void Initialize( const void* data, size_t size, const char* name, cmf::ValidationOptions options ); }; diff --git a/trinity/Resources/Tr2GeometryProvider.cpp b/trinity/Resources/Tr2GeometryProvider.cpp new file mode 100644 index 000000000..2a04066c3 --- /dev/null +++ b/trinity/Resources/Tr2GeometryProvider.cpp @@ -0,0 +1,1624 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Include/TriMath.h" +#include "Tr2GeometryProvider.h" +#include "Tr2PerObjectData.h" +#include "Tr2VertexDefinitionUtilities.h" +#include "TriDevice.h" + +#include "TriSettingsRegistrar.h" + +#include "Utilities/GeometryUtils.h" +#include "Utilities/BoundingSphere.h" +#include "Utilities/BoundingBox.h" + +#include +#include "TbbStub.h" + +using namespace Tr2RenderContextEnum; + +std::atomic AudioGeometryResData::s_nextId{ 1 }; + +bool g_eveIsAudioOcclusionGeometryEnabled = false; +TRI_REGISTER_SETTING( "eveIsAudioOcclusionGeometryEnabled", g_eveIsAudioOcclusionGeometryEnabled ); + +CCP_STATS_DECLARE( geometryResBytes, "Trinity/geometryResBytes", false, CST_MEMORY, "Size of memory occupied by geometry resources." ); + +#if TRINITY_PLATFORM != TRINITY_DIRECTX11 +const auto gpuUsage = Tr2GpuUsage::VERTEX_BUFFER | Tr2GpuUsage::INDEX_BUFFER | Tr2GpuUsage::SHADER_RESOURCE; +#else +const auto gpuUsage = Tr2GpuUsage::VERTEX_BUFFER | Tr2GpuUsage::INDEX_BUFFER; +#endif + +Tr2SuballocatedBuffer g_sharedBuffer( "TriGeometryRes shared vertex/index buffer", gpuUsage, SHARED_BUFFER_BLOCK_SIZE, SHARED_BUFFER_MAX_SIZE ); + +TriMorphTargetGeometryConstants CreateMorphGeometryConstants( const Tr2VertexDefinition& vertexDefinition, uint32_t stride, uint32_t vertexCount, Tr2RenderContext& renderContext ) +{ + TriMorphTargetGeometryConstants data = TriMorphTargetGeometryConstants{}; + for( const auto& item : vertexDefinition.m_items ) + { + if( item.m_usage == Tr2VertexDefinition::POSITION && item.m_usageIndex == 0 && item.m_stream == 0 ) + { + data.positionOffset = item.m_offset; + data.positionType = item.m_dataType; + + uint32_t type = data.positionType; + CCP_ASSERT_M( + type == Tr2VertexDefinition::DataType::FLOAT32_3, + "position type has to be FLOAT32_3!" ); + } + + if( item.m_usage == Tr2VertexDefinition::TANGENT && item.m_usageIndex == 0 && item.m_stream == 0 ) + { + data.tangentOffset = item.m_offset; + data.tangentType = item.m_dataType; + + uint32_t type = data.tangentType; + CCP_ASSERT_M( + type == Tr2VertexDefinition::DataType::FLOAT16_3 || + type == Tr2VertexDefinition::DataType::FLOAT32_3 || + type == Tr2VertexDefinition::DataType::UBYTE_4_NORM || + type == Tr2VertexDefinition::DataType::USHORT_4_NORM || + type == Tr2VertexDefinition::DataType::SHORT_4_NORM, + "tangent type has to be FLOAT16_3 or FLOAT32_3 or UBYTE_4_NORM or USHORT_4_NORM or SHORT_4_NORM!" ); + } + } + + data.vertexBufferStride = stride; + data.vertexCount = vertexCount; + return data; +} + +uint32_t GetPrimitiveCount( const TriGeometryResLodData& lod, uint32_t index, uint32_t count ) +{ + if( index >= lod.m_areas.size() ) + { + return 0; + } + + if( index + count > lod.m_areas.size() ) + { + count = uint32_t( lod.m_areas.size() - index ); + } + + auto& meshArea = lod.m_areas[index]; + + uint32_t primCount = uint32_t( meshArea.m_primitiveCount ); + for( uint32_t i = 1; i < count; ++i ) + { + primCount += lod.m_areas[index + i].m_primitiveCount; + } + return primCount; +} + +TriGeometryAreaIndexRange GetAreaIndexRange( const TriGeometryResLodData& lod, uint32_t areaIndex, uint32_t primCount, bool reversed ) +{ + TriGeometryAreaIndexRange range; + if( primCount == 0 || areaIndex >= lod.m_areas.size() ) + { + return range; + } + if( reversed && !lod.m_reversedIndicesValid ) + { + return range; + } + range.indices = reversed ? &lod.m_reversedIndexAllocation : &lod.m_indexAllocation; + range.startIndex = reversed ? + range.indices->GetStartIndex() + lod.m_primitiveCount * 3 - lod.m_areas[areaIndex].m_firstIndex - primCount * 3 : + range.indices->GetStartIndex() + lod.m_areas[areaIndex].m_firstIndex; + range.valid = true; + return range; +} + + +Tr2GeometryProvider::Tr2GeometryProvider( IRoot* lockobj ) : + m_memoryUse( 0 ), + m_meshes( "Tr2GeometryProvider/m_meshes" ), + m_skeletons( "Tr2GeometryProvider/m_skeletons" ) +{ +} + +unsigned int Tr2GeometryProvider::GetMeshCount() const +{ + return unsigned( m_meshes.size() ); +} + +TriGeometryResMeshData* Tr2GeometryProvider::GetMeshData( unsigned int meshIx ) const +{ + if( !m_isGood || meshIx >= m_meshes.size() ) + { + return nullptr; + } + + return m_meshes[meshIx].get(); +} + +TriGeometryResLodData* Tr2GeometryProvider::GetMeshLod( unsigned int meshIx, float screenSize ) const +{ + auto mesh = GetMeshData( meshIx ); + + if( !mesh ) + { + return nullptr; + } + + int lodIndex = GetLodIndexForScreenSize( meshIx, screenSize ); + return mesh->m_lods[lodIndex].get(); +} + +TriGeometryResLodData* Tr2GeometryProvider::GetMeshLod( unsigned int meshIx, int lodIndex ) const +{ + + auto mesh = GetMeshData( meshIx ); + + if( !mesh || lodIndex < 0 || lodIndex >= mesh->m_lods.size() ) + { + return nullptr; + } + + return mesh->m_lods[lodIndex].get(); +} + +int Tr2GeometryProvider::GetLodIndexForScreenSize( unsigned int meshIx, float screenSize ) const +{ + auto mesh = GetMeshData( meshIx ); + + if( !mesh ) + { + return -1; + } + + int32_t lastLod = static_cast( mesh->m_lods.size() ) - 1; + if( m_forceLod && m_forcedLodIndex >= 0 ) + { + return int( std::min( m_forcedLodIndex, lastLod ) ); + } + + for( int32_t i = lastLod; i >= 0; i-- ) + { + if( mesh->m_lods[i]->m_maxScreenSize >= screenSize ) + { + return i; + } + } + //If we end up here, it's because we requested a LOD size larger than the best one we have. + //Just return the highest quality LOD in this case. + return 0; +} + +const AudioGeometryResData* Tr2GeometryProvider::GetAudioGeometry( unsigned int meshIx ) const +{ + if( !g_eveIsAudioOcclusionGeometryEnabled ) + return nullptr; + + auto mesh = GetMeshData( meshIx ); + if( !mesh || !mesh->m_audioGeometry ) + { + return nullptr; + } + + return mesh->m_audioGeometry.get(); +} + +bool Tr2GeometryProvider::GetBoundingBox( unsigned int meshIx, Vector3& min, Vector3& max ) const +{ + TriGeometryResMeshData* mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return false; + } + + min = mesh->m_minBounds; + max = mesh->m_maxBounds; + + return true; +} + +Be::Result Tr2GeometryProvider::GetBoundingBoxFromScript( unsigned int meshIx, std::pair& bounds ) const +{ + if( meshIx >= m_meshes.size() ) + { + return Be::Result( "Mesh index out of bounds" ); + } + TriGeometryResMeshData* mesh = m_meshes[meshIx].get(); + if( !mesh ) + { + return Be::Result( "Invalid mesh" ); + } + + bounds = std::make_pair( mesh->m_minBounds, mesh->m_maxBounds ); + + return Be::Result(); +} + +bool Tr2GeometryProvider::GetAreaBoundingBox( unsigned int meshIx, unsigned int areaIx, Vector3& min, Vector3& max ) const +{ + auto mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return false; + } + + //Get the first LOD. + auto& lod = mesh->m_lods[0]; + + // Bail out if the area index is out of range + if( areaIx >= lod->m_areas.size() ) + { + return false; + } + + // Finally, get the min and max bounds for the mesh area + min = lod->m_areas[areaIx].m_minBounds; + max = lod->m_areas[areaIx].m_maxBounds; + + return true; +} + + +bool Tr2GeometryProvider::GetBoundingSphere( unsigned int meshIx, Vector4& sphere ) const +{ + TriGeometryResMeshData* mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return false; + } + + sphere = mesh->m_boundingSphere; + + return true; +} + +unsigned int Tr2GeometryProvider::GetAreaCount( unsigned int meshIx ) const +{ + auto mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return 0; + } + + //Get the first LOD. + auto& lod = mesh->m_lods[0]; + + return (unsigned int)lod->m_areas.size(); +} + +TriGeometryResAreaData* Tr2GeometryProvider::GetAreaData( unsigned int meshIx, unsigned int areaIx ) const +{ + auto mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return NULL; + } + + //Get the first LOD. + auto& lod = mesh->m_lods[0]; + + if( areaIx < 0 || areaIx >= lod->m_areas.size() ) + { + return NULL; + } + + return &lod->m_areas[areaIx]; +} + +#if TRINITYDEV +void Tr2GeometryProvider::GetDescription( std::string& desc ) +{ + desc = "Tr2GeometryProvider: '"; + desc += CW2A( GetPath() ); + desc += "'"; +} +#endif + +//This entire function is sus. We should be able to compute this when we upload the mesh to the GPU, as it can never change unless the vertex data changes. +void Tr2GeometryProvider::RecalculateBoundingSphere() +{ + TRINITY_STATS_ZONE( __FUNCTION__ ); + USE_MAIN_THREAD_RENDER_CONTEXT(); + + for( auto& mesh : m_meshes ) + { + //Try to get the vertex declaration elements + Tr2VertexDefinition decl; + if( mesh == nullptr || !Tr2EffectStateManager::GetVertexDeclarationElements( mesh->m_vertexDeclarationHandle, decl ) ) + { + continue; + } + + uint32_t vertSize = mesh->m_bytesPerVertex; + + //For now, we just compute the bounding sphere of the first LOD and ignore the lower LODs, assuming that they should fit in the highest quality LOD's bounding sphere. + auto& lod = mesh->m_lods[0]; + + + //Get the vertex data of the + + const uint8_t* pVertices; + if( !lod->m_allocationsValid || FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) + { + continue; + } + auto position = decl.Find( decl.POSITION, 0 ); + + // build a list of pointers to the positions + std::vector points( lod->m_vertexCount ); + for( uint32_t p = 0; p < lod->m_vertexCount; ++p ) + { + points[p] = (const Vector3*)&pVertices[p * vertSize + position->m_offset]; + } + + // all is done in this recursive function + BoundingSphereFromPoints( mesh->m_boundingSphere, &points[0], points.size() ); + lod->m_vertexAllocation.UnmapForReading( renderContext ); + } +} + +struct CalcBoundingBoxContext +{ + Vector3 min; + Vector3 max; + Matrix transform; +}; + +void CalcBoundingBox( void* context, const Vector3& p1, const Vector3& p2, const Vector3& p3 ) +{ + CalcBoundingBoxContext* ctx = static_cast( context ); + + Vector4 v[3]; + v[0] = Vector4( p1.x, p1.y, p1.z, 1.0f ); + v[1] = Vector4( p2.x, p2.y, p2.z, 1.0f ); + v[2] = Vector4( p3.x, p3.y, p3.z, 1.0f ); + + for( int i = 0; i < 3; ++i ) + { + v[i] = Transform( v[i], ctx->transform ); + v[i] /= v[i].w; + + ctx->max.x = std::max( ctx->max.x, v[i].x ); + ctx->max.y = std::max( ctx->max.y, v[i].y ); + ctx->max.z = std::max( ctx->max.z, v[i].z ); + + ctx->min.x = std::min( ctx->min.x, v[i].x ); + ctx->min.y = std::min( ctx->min.y, v[i].y ); + ctx->min.z = std::min( ctx->min.z, v[i].z ); + } +} + +static void ConvertTriangleData( Tr2VertexDefinition::DataType elementType, unsigned int vertexByteSize, const uint8_t* vertexBase, unsigned int index1, unsigned int index2, unsigned int index3, Vector3* v1, Vector3* v2, Vector3* v3 ) +{ + + ConvertDataToVector3( elementType, vertexBase + index1 * vertexByteSize, v1 ); + ConvertDataToVector3( elementType, vertexBase + index2 * vertexByteSize, v2 ); + ConvertDataToVector3( elementType, vertexBase + index3 * vertexByteSize, v3 ); +} + +void Tr2GeometryProvider::ProcessMeshTriangles( int meshIx, PerTriangleCallback cb, void* cbContext ) +{ + TRINITY_STATS_ZONE( __FUNCTION__ ); + USE_MAIN_THREAD_RENDER_CONTEXT(); + + int i = meshIx; + + if( m_meshes[i] == NULL ) + { + return; + } + + auto& mesh = m_meshes[i]; + + auto& lod = mesh->m_lods[0]; + + if( !lod->m_allocationsValid ) + { + return; + } + + const uint8_t* pVertices; + const uint8_t* pIndices; + + int vertSize = mesh->m_bytesPerVertex; + + if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) + { + return; + } + ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); + if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) + { + return; + } + ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); + + const uint16_t* pShortIndices = (uint16_t*)pIndices; + const uint32_t* pLongIndices = (uint32_t*)pIndices; + + + Tr2VertexDefinition decl; + if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh->m_vertexDeclarationHandle, decl ) ) + { + return; + } + + auto foundPosition = decl.Find( Tr2VertexDefinition::POSITION, 0 ); + if( !foundPosition ) + { + return; + } + unsigned int positionByteOffset = foundPosition->m_offset; + Tr2VertexDefinition::DataType declType = foundPosition->m_dataType; + + int numPrim = lod->m_primitiveCount; + for( int j = 0; j < numPrim; j++ ) + { + unsigned int index1 = 0; + unsigned int index2 = 0; + unsigned int index3 = 0; + Vector3 p1; + Vector3 p2; + Vector3 p3; + if( lod->m_indexAllocation.GetStride() == 2 ) + { + index1 = pShortIndices[j * 3]; + index2 = pShortIndices[( j * 3 ) + 1]; + index3 = pShortIndices[( j * 3 ) + 2]; + } + else + { + index1 = pLongIndices[j * 3]; + index2 = pLongIndices[( j * 3 ) + 1]; + index3 = pLongIndices[( j * 3 ) + 2]; + } + + ConvertTriangleData( declType, vertSize, pVertices + positionByteOffset, index1, index2, index3, &p1, &p2, &p3 ); + + ( *cb )( cbContext, p1, p2, p3 ); + } +} + +std::pair>> Tr2GeometryProvider::GetIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir ) +{ + RayCastResult hitInfo; + bool hit = GetIntersectionPoints( pos, dir, hitInfo ); + Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); + return std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, normal ) ) ); +} + +Be::Result Tr2GeometryProvider::GetAreaIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>& result ) +{ + if( areaIx < -1 ) + { + // -1 is a special case handled to maintain legacy behavior. + return Be::Result( "Invalid area index" ); + } + + // In Python 3, passing a signed value to an unsigned C exposed attribute is an error. + // In order to maintain backwards compatible behavior with Python 2 + // this method got changed to accept signed integers and cast them to unsigned. + auto unsignedAreaIndex = static_cast( areaIx ); + + RayCastResult hitInfo; + bool hit = GetIntersectionPoints( pos, dir, hitInfo, unsignedAreaIndex ); + Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); + result = std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, normal ) ) ); + return Be::Result(); +} + +std::pair>>>> Tr2GeometryProvider::GetIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir ) +{ + RayCastResult hitInfo; + bool hit = GetIntersectionPoints( pos, dir, hitInfo ); + Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); + return std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, std::make_pair( normal, std::make_pair( hitInfo.firstVertexColor, hitInfo.interpolatedVertexColor ) ) ) ) ); +} + +Be::Result Tr2GeometryProvider::GetAreaIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>>>& result ) +{ + if( areaIx < -1 ) + { + // -1 is a special case handled to maintain legacy behavior. + return Be::Result( "Invalid area index" ); + } + + // In Python 3, passing a signed value to an unsigned C exposed attribute is an error. + // In order to maintain backwards compatible behavior with Python 2 + // this method got changed to accept signed integers and cast them to unsigned. + auto unsignedAreaIndex = static_cast( areaIx ); + + RayCastResult hitInfo; + bool hit = GetIntersectionPoints( pos, dir, hitInfo, unsignedAreaIndex ); + Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); + result = std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, std::make_pair( normal, std::make_pair( hitInfo.firstVertexColor, hitInfo.interpolatedVertexColor ) ) ) ) ); + return Be::Result(); +} + +void Tr2GeometryProvider::PrepareRayCaster() +{ +} + +void Tr2GeometryProvider::DestroyRayCaster() +{ +} + +void Tr2GeometryProvider::ResetRayCaster() +{ +} + +bool Tr2GeometryProvider::IsRayCasterReady() const +{ + return false; +} + +bool Tr2GeometryProvider::HasRayCasterPreparationFailed() const +{ + return true; +} + +bool Tr2GeometryProvider::GetIntersectionPoints( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx, float rayLength ) +{ + if( BeResMan->IsOnMainThread() ) + { + return GetIntersectionPointsLegacy( pos, dir, result, areaIx, rayLength ); + } + CCP_ASSERT_M( false, "Raycast fallback is not available in a multithreaded environment! You need to wait for IsRayCasterReady before raycasting!" ); + return false; +} + +// Careful: This function is slow and only works on main thread! +bool Tr2GeometryProvider::GetIntersectionPointsLegacy( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx, float rayLength ) +{ + USE_MAIN_THREAD_RENDER_CONTEXT(); + + bool hit = false; + + for( size_t i = 0; i < m_meshes.size(); i++ ) + { + if( m_meshes[i] == NULL ) + { + continue; + } + + //Get the first LOD. + auto& lod = m_meshes[i]->m_lods[0]; + + const uint8_t* pVertices; + const uint8_t* pIndices; + + int vertSize = m_meshes[i]->m_bytesPerVertex; + if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) + { + return false; + } + ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); + if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) + { + return false; + } + ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); + + const uint16_t* pShortIndices = (uint16_t*)pIndices; + const uint32_t* pLongIndices = (uint32_t*)pIndices; + + Tr2VertexDefinition decl; + if( !Tr2EffectStateManager::GetVertexDeclarationElements( m_meshes[i]->m_vertexDeclarationHandle, decl ) ) + { + return false; + } + const Tr2VertexDefinition::Item* const position = decl.Find( decl.POSITION ); + if( !position ) + { + return false; + } + + const Tr2VertexDefinition::Item* const blendIndices = decl.Find( decl.BLENDINDICES ); + const Tr2VertexDefinition::Item* const colors = decl.Find( decl.COLOR ); + + int numPrim = lod->m_primitiveCount; + auto currentIndex = 0; + if( areaIx != -1 ) + { + if( areaIx >= lod->m_areas.size() ) + { + continue; + } + currentIndex = lod->m_areas[areaIx].m_firstIndex; + numPrim = lod->m_areas[areaIx].m_primitiveCount; + } + + for( int j = 0; j < numPrim; j++ ) + { + unsigned int index1 = 0; + unsigned int index2 = 0; + unsigned int index3 = 0; + Vector3 p1; + Vector3 p2; + Vector3 p3; + float pu, pv, dist; + if( lod->m_indexAllocation.GetStride() == 2 ) + { + index1 = pShortIndices[currentIndex++]; + index2 = pShortIndices[currentIndex++]; + index3 = pShortIndices[currentIndex++]; + } + else + { + index1 = pLongIndices[currentIndex++]; + index2 = pLongIndices[currentIndex++]; + index3 = pLongIndices[currentIndex++]; + } + + ConvertTriangleData( position->m_dataType, vertSize, pVertices + position->m_offset, index1, index2, index3, &p1, &p2, &p3 ); + + if( IntersectTri( p1, p2, p3, pos, dir, pu, pv, dist ) ) + { + if( rayLength > dist ) + { + float v1 = 1.0f - ( pu + pv ); + + result.position = p1 * v1 + p2 * pu + p3 * pv; + + Vector3 avec = p2 - p1; + Vector3 bvec = p3 - p1; + result.unnormalizedNormal = Cross( avec, bvec ); + + result.distance = rayLength = dist; + + int boneIndex = 0; + if( blendIndices && GetBoneIndex( blendIndices->m_dataType, pVertices + index1 * vertSize + blendIndices->m_offset, boneIndex ) ) + { + result.boneIndex = boneIndex; + } + + Color color1; + Color color2; + Color color3; + if( colors && + GetColor( colors->m_dataType, pVertices + index1 * vertSize + colors->m_offset, color1 ) && + GetColor( colors->m_dataType, pVertices + index2 * vertSize + colors->m_offset, color2 ) && + GetColor( colors->m_dataType, pVertices + index3 * vertSize + colors->m_offset, color3 ) ) + { + result.firstVertexColor = color1; + result.interpolatedVertexColor = color1 * v1 + color2 * pu + color3 * pv; + } + + hit = true; + } + } + } + } + + return hit; +} + +unsigned int Tr2GeometryProvider::GetSkeletonCount() const +{ + return (unsigned int)m_skeletons.size(); +} + +TriGeometryResSkeletonData* Tr2GeometryProvider::GetSkeletonData( unsigned int skelIx ) const +{ + CCP_ASSERT( skelIx < m_skeletons.size() ); + + return m_skeletons[skelIx].get(); +} + +bool Tr2GeometryProvider::IsMemoryUsageKnown() +{ + return !IsLoading(); +} + +size_t Tr2GeometryProvider::GetMemoryUsage() +{ + return m_memoryUse; +} + +void Tr2GeometryProvider::ReleaseResourcesHelper() +{ + m_meshes.clear(); + // We don't clear m_skeletons here, because TriGeometryRes::ReleaseResources does not notify Tr2SkinnedModel. SetupSkeletons takes care of m_skeletons anyway. + + CCP_STATS_ADD( geometryResBytes, -(int)m_memoryUse ); + m_memoryUse = 0; +} + +bool Tr2GeometryProvider::IsUsingCMF() const +{ + return true; +} + +unsigned int TriGeometryResSkeletonData::FindJoint( const char* name ) const +{ + unsigned int jointCount = (unsigned int)m_joints.size(); + for( unsigned int ix = 0; ix < jointCount; ++ix ) + { + if( strcmp( name, m_joints[ix].m_name.c_str() ) == 0 ) + { + return ix; + } + } + + return 0xffffffff; +} + +TriGeometryResSkeletonData::TriGeometryResSkeletonData() : + m_joints( "TriGeometryResSkeletonData/m_joints" ) +{ +} + +TriGeometryResAreaData::TriGeometryResAreaData() : + m_firstIndex( 0 ), + m_primitiveCount( 0 ), + m_jointBindings( "TriGeometryResAreaData/m_jointBindings" ), + m_isSkinned( false ), + m_isMorphed( false ) +{ +} + +TriGeometryResLodData::TriGeometryResLodData() : + m_vertexCount( 0 ), + m_primitiveCount( 0 ), + m_areas( "TriGeometryResMeshData/m_areas" ), + m_morphVertexDeclaration( Tr2EffectStateManager::UNINITIALIZED_DECLARATION ), + m_allocationsValid( false ), + m_reversedIndicesValid( false ), + m_uvDensities(), + m_bytesPerMorphTargetVertex( 0 ) +{ +} + + +TriGeometryResMeshData::TriGeometryResMeshData() : + + m_vertexDeclarationHandle( Tr2EffectStateManager::UNINITIALIZED_DECLARATION ), + m_bytesPerVertex( -1 ), + m_jointBindings( "TriGeometryResMeshData/m_jointBindings" ), + m_lods( "TriGeometryResModelData/m_lods" ) +{ +} + +bool Tr2GeometryProvider::RenderAreas( unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed ) +{ + return RenderAreas( std::numeric_limits::max(), meshIx, areaIx, areaCount, renderContext, reversed ); +} + +bool Tr2GeometryProvider::RenderAreas( float screenSize, unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed ) +{ + if( !m_isGood ) + { + return false; + } + + if( meshIx >= m_meshes.size() ) + { + return false; + } + + TriGeometryResMeshData* mesh = m_meshes[meshIx].get(); + if( !mesh ) + { + return false; + } + + TriGeometryResLodData* lod = GetMeshLod( meshIx, screenSize ); + + unsigned int primCount = GetPrimitiveCount( *lod, areaIx, areaCount ); + + if( primCount ) + { + auto indexRange = GetAreaIndexRange( *lod, areaIx, primCount, reversed ); + if( !indexRange.valid ) + { + return false; + } + + renderContext.m_esm.ApplyVertexDeclaration( mesh->m_vertexDeclarationHandle ); + renderContext.m_esm.ApplyStreamSource( 0, lod->m_vertexAllocation ); + renderContext.m_esm.ApplyIndexBuffer( *indexRange.indices ); + + renderContext.SetTopology( TOP_TRIANGLES ); + renderContext.DrawIndexedPrimitive( lod->m_vertexCount, indexRange.startIndex, primCount ); + } + + return true; +} + +Be::Result Tr2GeometryProvider::SaveMesh( const char* filename, uint32_t meshIndex ) const +{ + if( !IsGood() ) + { + return Be::Result( "Geometry is not prepared" ); + } + auto meshData = GetMeshData( meshIndex ); + if( !meshData ) + { + return Be::Result( "Invalid mesh index" ); + } + + if( !SaveMeshToCMFFile( *meshData, filename ) ) + { + return Be::Result( "Failed to save the mesh" ); + } + + return Be::Result(); +} + +bool Tr2GeometryProvider::SaveMeshToCMFFile( TriGeometryResMeshData& mesh, const char* filename ) +{ + USE_MAIN_THREAD_RENDER_CONTEXT(); + + if( mesh.m_vertexDeclarationHandle == Tr2EffectStateManager::UNINITIALIZED_DECLARATION ) + { + return false; + } + + auto& lod = mesh.m_lods[0]; + + if( !lod->m_allocationsValid ) + { + return false; + } + + Tr2VertexDefinition decl; + if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh.m_vertexDeclarationHandle, decl ) ) + { + return false; + } + + cmf::MemoryAllocator allocator; + cmf::BufferManager bufferAllocator( allocator ); + cmf::Span cmfVertexDecl = allocator.AllocateSpan( decl.m_items.size() ); + + if( !ConvertVertexDeclToCMF( decl, cmfVertexDecl, (uint32_t)cmfVertexDecl.size() ) ) + { + return false; + } + + uint32_t vertexBufferSize = lod->m_vertexAllocation.GetSize(); + uint32_t vertexBufferStride = lod->m_vertexAllocation.GetStride(); + const void* pVertices; + if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) + { + return false; + } + ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); + + uint32_t indexBufferSize = lod->m_indexAllocation.GetSize(); + uint32_t indexBufferStride = lod->m_indexAllocation.GetStride(); + const void* pIndices; + if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) + { + return false; + } + ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); + + + cmf::Data cmfData; + cmfData.meshes = allocator.AllocateSpan( 1 ); + + cmf::Mesh& cmfMesh = cmfData.meshes[0] = {}; + cmfMesh.name = allocator.AllocateString( mesh.m_name ); + cmfMesh.decl = cmfVertexDecl; + cmfMesh.lods = allocator.AllocateSpan( 1 ); + cmfMesh.areas = allocator.AllocateSpan( lod->m_areas.size() ); + cmfMesh.boneBindings = allocator.AllocateSpan( mesh.m_jointBindings.size() ); + cmfMesh.uvDensities = allocator.AllocateSpan( lod->m_uvDensities.size() ); + cmfMesh.bounds = AxisAlignedBoundingBox( mesh.m_minBounds, mesh.m_maxBounds ); + + memcpy( cmfMesh.uvDensities.data(), lod->m_uvDensities.data(), lod->m_uvDensities.size() * sizeof( float ) ); + + cmf::MeshLod& cmfMeshLod = cmfMesh.lods[0]; + cmfMeshLod.vb = bufferAllocator.AddBuffer( (void*)pVertices, vertexBufferSize, vertexBufferStride ); + cmfMeshLod.ib = bufferAllocator.AddBuffer( (void*)pIndices, indexBufferSize, indexBufferStride ); + cmfMeshLod.areas = allocator.AllocateSpan( lod->m_areas.size() ); + cmfMeshLod.morphTargets = cmf::Span(); + cmfMeshLod.threshold = (uint32_t)lod->m_maxScreenSize; + + for( int32_t i = 0; i < cmfMesh.areas.size(); i++ ) + { + cmf::MeshArea& cmfMeshArea = cmfMesh.areas[i]; + cmfMeshArea.name = allocator.AllocateString( lod->m_areas[i].m_name ); + cmfMeshArea.bounds = AxisAlignedBoundingBox( lod->m_areas[i].m_minBounds, lod->m_areas[i].m_maxBounds ); + cmfMeshArea.bones = allocator.AllocateSpan( lod->m_areas[i].m_jointBindings.size() ); + cmfMeshArea.affectedByBones = lod->m_areas[i].m_isSkinned; + cmfMeshArea.affectedByMorphTargets = false; + + for( int32_t j = 0; j < lod->m_areas[i].m_jointBindings.size(); j++ ) + { + cmfMeshArea.bones[j] = (uint16_t)lod->m_areas[i].m_jointBindings[j]; + } + + cmf::LodMeshArea& lodMeshArea = cmfMeshLod.areas[i]; + lodMeshArea.firstElement = lod->m_areas[i].m_firstIndex / 3; + lodMeshArea.elementCount = lod->m_areas[i].m_primitiveCount; + } + + for( int32_t i = 0; i < cmfMesh.boneBindings.size(); i++ ) + { + cmf::BoneBinding& cmfBoneBinding = cmfMesh.boneBindings[i]; + cmfBoneBinding.name = allocator.AllocateString( mesh.m_jointBindings[i].m_name ); + cmfBoneBinding.bounds = AxisAlignedBoundingBox( mesh.m_jointBindings[i].m_obbMin, mesh.m_jointBindings[i].m_obbMax ); + } + + auto file = cmf::BuildFile( cmfData, bufferAllocator ); + + std::wstring filenameW = (const wchar_t*)CA2W( filename ); + std::wstring fullPath = BePaths->ResolvePathForWritingW( filenameW ); + + FILE* outFile; + auto err = fopen_s( &outFile, CW2A( fullPath.c_str() ), "wb" ); + + if( err != 0 ) + { + CCP_LOGERR( "Failed to open output file: %ls\n", fullPath.c_str() ); + return false; + } + if( fwrite( file.data(), 1, file.size(), outFile ) != file.size() ) + { + CCP_LOGERR( "Failed to write output file: %ls\n", fullPath.c_str() ); + fclose( outFile ); + return false; + } + fclose( outFile ); + + return true; +} + +#if WITH_GRANNY +bool Tr2GeometryProvider::SaveMeshToGrannyFile( TriGeometryResMeshData& mesh, const char* filename ) +{ + USE_MAIN_THREAD_RENDER_CONTEXT(); + + if( mesh.m_vertexDeclarationHandle == Tr2EffectStateManager::UNINITIALIZED_DECLARATION ) + { + return false; + } + + auto& lod = mesh.m_lods[0]; + + if( !lod->m_allocationsValid ) + { + return false; + } + + granny_data_type_definition grannyVertexDecl[16]; + + Tr2VertexDefinition decl; + if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh.m_vertexDeclarationHandle, decl ) ) + { + return false; + } + + if( !ConvertVertexDeclToGranny( decl, grannyVertexDecl, sizeof( grannyVertexDecl ) / sizeof( grannyVertexDecl[0] ) ) ) + { + return false; + } + + const void* pVertices; + if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) + { + return false; + } + ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); + + const void* pIndices; + if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) + { + return false; + } + ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); + + + granny_vertex_data myVertexData; + memset( &myVertexData, 0, sizeof( myVertexData ) ); + myVertexData.Vertices = (granny_uint8*)pVertices; + myVertexData.VertexCount = lod->m_vertexCount; + myVertexData.VertexType = grannyVertexDecl; + + granny_tri_topology myTopology; + memset( &myTopology, 0, sizeof( myTopology ) ); + + if( lod->m_vertexCount <= 65535 ) + { + myTopology.Indices16 = (granny_uint16*)pIndices; + myTopology.Index16Count = lod->m_primitiveCount * 3; + } + else + { + myTopology.Indices = (granny_int32*)pIndices; + myTopology.IndexCount = lod->m_primitiveCount * 3; + } + + myTopology.GroupCount = (granny_int32)lod->m_areas.size(); + myTopology.Groups = CCP_NEW( "myTopology.Groups" ) granny_tri_material_group[myTopology.GroupCount]; + if( myTopology.Groups ) + { + for( int i = 0; i < myTopology.GroupCount; ++i ) + { + myTopology.Groups[i].MaterialIndex = i; + myTopology.Groups[i].TriFirst = lod->m_areas[i].m_firstIndex / 3; + myTopology.Groups[i].TriCount = lod->m_areas[i].m_primitiveCount; + } + } + else + { + myTopology.GroupCount = 0; + } + + granny_mesh grannyMesh; + memset( &grannyMesh, 0, sizeof( grannyMesh ) ); + + grannyMesh.PrimaryVertexData = &myVertexData; + grannyMesh.PrimaryTopology = &myTopology; + + granny_file_info fileInfo; + memset( &fileInfo, 0, sizeof( fileInfo ) ); + + granny_mesh* meshes[] = { &grannyMesh }; + granny_tri_topology* topologies[] = { grannyMesh.PrimaryTopology }; + granny_vertex_data* vertexDatas[] = { grannyMesh.PrimaryVertexData }; + + fileInfo.ModelCount = 0; + fileInfo.Models = 0; + fileInfo.MeshCount = 1; + fileInfo.Meshes = meshes; + fileInfo.VertexDataCount = 1; + fileInfo.VertexDatas = vertexDatas; + fileInfo.TriTopologyCount = 1; + fileInfo.TriTopologies = topologies; + + fileInfo.MaterialCount = (granny_int32)lod->m_areas.size(); + fileInfo.Materials = CCP_NEW( "fileInfo.Materials" ) granny_material * [fileInfo.MaterialCount]; + if( fileInfo.Materials == nullptr ) + { + fileInfo.MaterialCount = 0; + } + + grannyMesh.MaterialBindingCount = fileInfo.MaterialCount; + grannyMesh.MaterialBindings = CCP_NEW( "grannyMesh.MaterialBindings" ) granny_material_binding[fileInfo.MaterialCount]; + if( grannyMesh.MaterialBindings == nullptr ) + { + grannyMesh.MaterialBindingCount = 0; + } + + granny_material* pMaterials = nullptr; + if( fileInfo.MaterialCount ) + { + pMaterials = CCP_NEW( "pMaterials" ) granny_material[fileInfo.MaterialCount]; + if( pMaterials ) + { + memset( pMaterials, 0, sizeof( granny_material ) * fileInfo.MaterialCount ); + + for( int i = 0; i < fileInfo.MaterialCount; ++i ) + { + fileInfo.Materials[i] = &pMaterials[i]; + fileInfo.Materials[i]->Name = lod->m_areas[i].m_name.c_str(); + grannyMesh.MaterialBindings[i].Material = fileInfo.Materials[i]; + } + } + } + + // Bones + grannyMesh.BoneBindingCount = (granny_int32)mesh.m_jointBindings.size(); + grannyMesh.BoneBindings = CCP_NEW( "grannyMesh.BoneBindings" ) granny_bone_binding[grannyMesh.BoneBindingCount]; + if( grannyMesh.BoneBindings == nullptr ) + { + grannyMesh.BoneBindingCount = 0; + } + if( grannyMesh.BoneBindingCount > 0 ) + { + memset( grannyMesh.BoneBindings, 0, sizeof( granny_bone_binding ) * grannyMesh.BoneBindingCount ); + for( int i = 0; i < grannyMesh.BoneBindingCount; ++i ) + { + grannyMesh.BoneBindings[i].BoneName = mesh.m_jointBindings[i].m_name.c_str(); + grannyMesh.BoneBindings[i].OBBMin[0] = mesh.m_jointBindings[i].m_obbMin.x; + grannyMesh.BoneBindings[i].OBBMin[1] = mesh.m_jointBindings[i].m_obbMin.y; + grannyMesh.BoneBindings[i].OBBMin[2] = mesh.m_jointBindings[i].m_obbMin.z; + grannyMesh.BoneBindings[i].OBBMax[0] = mesh.m_jointBindings[i].m_obbMax.x; + grannyMesh.BoneBindings[i].OBBMax[1] = mesh.m_jointBindings[i].m_obbMax.y; + grannyMesh.BoneBindings[i].OBBMax[2] = mesh.m_jointBindings[i].m_obbMax.z; + } + } + + + granny_file_builder* Builder = GrannyBeginFile( 1, GrannyCurrentGRNStandardTag, GrannyGRNFileMV_32Bit_LittleEndian, GrannyGetTemporaryDirectory(), "GRNFileTemp" ); + granny_file_data_tree_writer* Writer = + GrannyBeginFileDataTreeWriting( GrannyFileInfoType, &fileInfo, 0, 0 ); + GrannyWriteDataTreeToFileBuilder( Writer, Builder ); + GrannyEndFileDataTreeWriting( Writer ); + + std::wstring filenameW = (const wchar_t*)CA2W( filename ); + std::wstring fullPath = BePaths->ResolvePathForWritingW( filenameW ); + GrannyEndFile( Builder, CW2A( fullPath.c_str() ) ); + + CCP_DELETE[] pMaterials; + CCP_DELETE[] grannyMesh.MaterialBindings; + CCP_DELETE[] grannyMesh.BoneBindings; + CCP_DELETE[] fileInfo.Materials; + + return true; +} +#endif + +// -------------------------------------------------------------------------------------- +// Description: +// Implements ITr2InstanceData interface. Query if the instance data is ready to be +// used, i.e. if the resource is good. +// Return Value: +// true if data is ready to be used +// false otherwise +// -------------------------------------------------------------------------------------- +bool Tr2GeometryProvider::IsInstanceDataReady() const +{ + return IsGood(); +} + +ITr2InstanceData::InstanceData Tr2GeometryProvider::GetInstanceData( unsigned int bufferIndex, float screenSize ) const +{ + auto mesh = GetMeshData( bufferIndex ); + + if( !mesh ) + { + static Tr2BufferAL nullVB; + return { nullVB }; + } + + auto lod = GetMeshLod( bufferIndex, screenSize ); + return { + lod->m_vertexAllocation.GetBuffer(), lod->m_vertexAllocation.GetOffset(), mesh->m_bytesPerVertex, lod->m_vertexCount + }; +} + +// -------------------------------------------------------------------------------------- +// Description: +// Implements ITr2InstanceData interface. Returns returns vertex declaration handle +// for the given instance buffer index. +// Arguments: +// bufferIndex - instance buffer index +// Return Value: +// vertex declaration handle +// -------------------------------------------------------------------------------------- +unsigned int Tr2GeometryProvider::GetInstanceBufferVertexDeclaration( unsigned int bufferIndex ) const +{ + if( bufferIndex < m_meshes.size() && m_meshes[bufferIndex] ) + { + return m_meshes[bufferIndex]->m_vertexDeclarationHandle; + } + return Tr2EffectStateManager::UNINITIALIZED_DECLARATION; +} + +CcpMath::AxisAlignedBox Tr2GeometryProvider::GetInstanceBufferBoundingBox( unsigned int bufferIndex ) const +{ + auto mesh = GetMeshData( bufferIndex ); + if( !mesh ) + { + return CcpMath::AxisAlignedBox(); + } + return CcpMath::AxisAlignedBox( mesh->m_minBounds, mesh->m_maxBounds ); +} + +Be::Result Tr2GeometryProvider::GetMeshName( unsigned int meshIx, std::string& name ) const +{ + TriGeometryResMeshData* mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return Be::Result( "Mesh index out of range, or invalid mesh" ); + } + + name = mesh->m_name; + + return Be::Result(); +} + +Be::Result Tr2GeometryProvider::GetMeshAreaCount( unsigned int meshIx, int& count ) const +{ + TriGeometryResMeshData* mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return Be::Result( "Mesh index out of range, or invalid mesh" ); + } + + //Get the first LOD. + auto& lod = mesh->m_lods[0]; + + count = (int)lod->m_areas.size(); + + return Be::Result(); +} + +Be::Result Tr2GeometryProvider::GetMeshAreaName( unsigned int meshIx, unsigned int areaIx, std::string& name ) const +{ + TriGeometryResMeshData* mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return Be::Result( "Mesh index out of range, or invalid mesh" ); + } + + //Get the first LOD. + auto& lod = mesh->m_lods[0]; + + if( areaIx < 0 || areaIx >= lod->m_areas.size() ) + { + return Be::Result( "Area index out of range" ); + } + + name = lod->m_areas[areaIx].m_name; + + return Be::Result(); +} + +Be::Result Tr2GeometryProvider::GetAreaBoundingBoxFromScript( unsigned int meshIx, unsigned int areaIx, std::pair& bounds ) +{ + TriGeometryResMeshData* mesh = GetMeshData( meshIx ); + if( !mesh ) + { + return Be::Result( "Mesh index out of range, or invalid mesh" ); + } + + //Get the first LOD. + auto& lod = mesh->m_lods[0]; + + if( areaIx < 0 || areaIx >= lod->m_areas.size() ) + { + return Be::Result( "Area index out of range" ); + } + + bounds = std::make_pair( lod->m_areas[areaIx].m_minBounds, lod->m_areas[areaIx].m_maxBounds ); + + return Be::Result(); +} + +Be::Result Tr2GeometryProvider::GetBoundingSphereFromScript( unsigned int meshIx, std::pair& bounds ) const +{ + if( meshIx >= m_meshes.size() ) + { + return Be::Result( "Mesh index out of range" ); + } + auto& mesh = m_meshes[meshIx]; + if( !mesh ) + { + return Be::Result( "Invalid mesh" ); + } + + bounds = std::make_pair( Vector3( mesh->m_boundingSphere.x, mesh->m_boundingSphere.y, mesh->m_boundingSphere.z ), mesh->m_boundingSphere.w ); + return Be::Result(); +} + +Be::Result Tr2GeometryProvider::CalculateBoundingBoxFromTransform( unsigned int meshIx, const Matrix& transform, std::pair& bounds ) +{ + if( meshIx >= m_meshes.size() ) + { + return Be::Result( "Mesh index out of range" ); + } + auto& mesh = m_meshes[meshIx]; + if( !mesh ) + { + return Be::Result( "Invalid mesh" ); + } + + CalcBoundingBoxContext ctx; + ctx.min = Vector3( FLT_MAX, FLT_MAX, FLT_MAX ); + ctx.max = Vector3( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + ctx.transform = transform; + + if( meshIx < m_meshes.size() ) + { + ProcessMeshTriangles( meshIx, &CalcBoundingBox, &ctx ); + } + + bounds = std::make_pair( ctx.min, ctx.max ); + return Be::Result(); +} + + +BlueStdResult Tr2GeometryProvider::GetMeshVertexElements( size_t meshIndex, std::vector>& elements ) const +{ + elements.clear(); + + if( !IsGood() ) + { + return BlueStdResult( BLUE_STD_RESULT_RUNTIME_ERROR, "TriGeometryRes is not prepared" ); + } + if( meshIndex >= m_meshes.size() || !m_meshes[meshIndex] ) + { + return BlueStdResult( BLUE_STD_RESULT_INDEX_ERROR, "mesh index out of range" ); + } + + auto& mesh = m_meshes[meshIndex]; + Tr2VertexDefinition decl; + if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh->m_vertexDeclarationHandle, decl ) ) + { + return BlueStdResult( BLUE_STD_RESULT_RUNTIME_ERROR, "could not retrieve vertex declaration" ); + } + + for( auto it = std::begin( decl.m_items ); it != std::end( decl.m_items ); ++it ) + { + elements.push_back( std::make_pair( it->m_usage, it->m_usageIndex ) ); + } + return BLUE_STD_RESULT_OK; +} + +bool Tr2GeometryProvider::SetupMeshesFromCMF( const cmf::Data& cmfData ) +{ + TRINITY_STATS_ZONE( __FUNCTION__ ); + + int minimumLOD = gTriDev->GetMinimumModelLOD(); + + for( const cmf::Mesh& cmfMesh : cmfData.meshes ) + { + Tr2VertexDefinition vertexDefinition = BuildFromCMFVertexDecl( cmfMesh.decl ); + const uint32_t vertexDeclarationHandle = Tr2EffectStateManager::GetVertexDeclarationHandle( vertexDefinition ); + unsigned int bytesPerVertex = cmfMesh.lods[0].vb.stride; + + TriGeometryResMeshData* mesh = new TriGeometryResMeshData(); + + mesh->m_name = cmf::ToStdString( cmfMesh.name ); + + mesh->m_minBounds = cmfMesh.bounds.m_min; + mesh->m_maxBounds = cmfMesh.bounds.m_max; + + CcpMath::Sphere sphere( cmfMesh.bounds ); + mesh->m_boundingSphere = Vector4( sphere.center, sphere.radius ); + + mesh->m_vertexDeclarationHandle = vertexDeclarationHandle; + mesh->m_bytesPerVertex = bytesPerVertex; + + for( const auto& joint : cmfMesh.boneBindings ) + { + TriJointBinding binding; + binding.m_name = cmf::ToStdString( joint.name ); + binding.m_obbMin = joint.bounds.m_min; + binding.m_obbMax = joint.bounds.m_max; + mesh->m_jointBindings.push_back( binding ); + } + + int32_t firstLodIndex = ClampInt( minimumLOD, 0, int32_t( cmfMesh.lods.size() ) - 1 ); + + for( int32_t i = firstLodIndex; i < cmfMesh.lods.size(); i++ ) + { + const cmf::MeshLod& cmfLod = cmfMesh.lods[i]; + + //Setup a new LOD, which will be added as an LOD of the mesh. + std::unique_ptr lod( new TriGeometryResLodData() ); + + lod->m_mesh = mesh; + + // TODO: intern, delete field once granny has been removed. + lod->m_grannyMeshIndex = 0xFFFFFFFF; + + lod->m_name = cmf::ToStdString( cmfMesh.name ); + + lod->m_maxScreenSize = float( cmfLod.threshold ); + + lod->m_vertexCount = cmf::GetStreamElementCount( cmfLod.vb ); + + lod->m_primitiveCount = cmf::GetStreamElementCount( cmfLod.ib ) / 3; + + // Get the UV densities, which are used for texture streaming. + lod->m_uvDensities = std::vector( cmfMesh.uvDensities.begin(), cmfMesh.uvDensities.end() ); + + lod->m_areas.resize( cmfLod.areas.size() ); + + for( int groupIndex = 0; groupIndex < cmfLod.areas.size(); ++groupIndex ) + { + const cmf::LodMeshArea& cmfLodMeshArea = cmfLod.areas[groupIndex]; + const cmf::MeshArea& cmfMeshArea = cmfMesh.areas[groupIndex]; + + TriGeometryResAreaData& area = lod->m_areas[groupIndex]; + area.m_name = cmf::ToStdString( cmfMeshArea.name ); + + area.m_firstIndex = cmfLodMeshArea.firstElement * 3; + area.m_primitiveCount = cmfLodMeshArea.elementCount; + + area.m_isSkinned = cmfMeshArea.affectedByBones; + area.m_isMorphed = cmfMeshArea.affectedByMorphTargets; + + area.m_minBounds = cmfMeshArea.bounds.m_min; + area.m_maxBounds = cmfMeshArea.bounds.m_max; + } + + lod->m_originalLodIndex = i; + + mesh->m_lods.push_back( std::move( lod ) ); + } + + uint32_t lodMask = 0u; + for( const auto& lod : mesh->m_lods ) + { + lodMask |= 1u << lod->m_originalLodIndex; + } + mesh->m_lodMask = lodMask; + + m_meshes.push_back( std::unique_ptr( mesh ) ); + } + + return true; +} + +void Tr2GeometryProvider::SetupSkeletonsFromCMF( const cmf::Data& cmfData ) +{ + TRINITY_STATS_ZONE( __FUNCTION__ ); + + m_skeletons.resize( cmfData.skeletons.size() ); + for( int skelIx = 0; skelIx < cmfData.skeletons.size(); ++skelIx ) + { + const cmf::Skeleton& skel = cmfData.skeletons[skelIx]; + std::unique_ptr pSkelData( new TriGeometryResSkeletonData() ); + + pSkelData->m_name = cmf::ToStdString( skel.name ); + pSkelData->m_joints.resize( skel.bones.size() ); + for( int jointIx = 0; jointIx < skel.bones.size(); ++jointIx ) + { + TriGeometryResJointData& joint = pSkelData->m_joints[jointIx]; + joint.m_name = cmf::ToStdString( skel.bones[jointIx] ); + joint.m_parentJoint = skel.parents[jointIx]; + joint.m_inverseWorldTransform = *(Matrix*)&skel.invBindTransforms[jointIx]; + } + + std::swap( m_skeletons[skelIx], pSkelData ); + } +} + +bool Tr2GeometryProvider::CreateMeshesFromCMFFile( Tr2CmfContents& cmfContents, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ) +{ + TRINITY_STATS_ZONE( __FUNCTION__ ); + + if( !cmfContents.GetData() ) + { + return false; + } + + for( int32_t i = 0; i < m_meshes.size(); i++ ) + { + auto& mesh = m_meshes[i]; + + for( const auto& lod : mesh->m_lods ) + { + CreateLodFromCMFMesh( cmfContents, cmfContents.GetData()->meshes[i], cmfContents.GetData()->meshes[i].lods[lod->m_originalLodIndex], lod.get(), cpuUsage, renderContext ); + } + + if( g_eveIsAudioOcclusionGeometryEnabled ) + { + const auto& audioMesh = cmfContents.GetData()->meshes[i].audioOcclusionMesh; + if( !audioMesh.vertices.empty() && !audioMesh.indices.empty() ) + { + auto audioGeometry = std::make_unique(); + audioGeometry->m_indices.insert( audioGeometry->m_indices.end(), audioMesh.indices.begin(), audioMesh.indices.end() ); + audioGeometry->m_vertices.insert( audioGeometry->m_vertices.end(), audioMesh.vertices.begin(), audioMesh.vertices.end() ); + audioGeometry->m_minBounds = audioMesh.bounds.m_min; + audioGeometry->m_maxBounds = audioMesh.bounds.m_max; + + mesh->m_audioGeometry = std::move( audioGeometry ); + } + } + } + + CCP_STATS_ADD( geometryResBytes, m_memoryUse ); + + return true; +} + +bool Tr2GeometryProvider::CreateLodFromCMFMesh( Tr2CmfContents& cmfContents, const cmf::Mesh& cmfMesh, const cmf::MeshLod& cmfMeshLod, TriGeometryResLodData* lod, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ) +{ + TRINITY_STATS_ZONE( __FUNCTION__ ); + + if( !Tr2Renderer::IsResourceCreationAllowed() ) + { + return false; + } + + Tr2VertexDefinition vertexDefinition = BuildFromCMFVertexDecl( cmfMesh.decl ); + const unsigned bytesPerVertex = cmfMeshLod.vb.stride; + const unsigned bytesPerIndex = cmfMeshLod.ib.stride; + + int vertexCount = cmf::GetStreamElementCount( cmfMeshLod.vb ); + int vbSize = cmfMeshLod.vb.size; + + CCP_ASSERT_M( cmfContents, "TriGeometryRes::CreateLodFromCMFMesh: cmfContents must be valid!" ); + + const void* pSrcVB = cmfContents.GetViewData( cmfMeshLod.vb ); + + int indexCount = cmf::GetStreamElementCount( cmfMeshLod.ib ); + + CR_RETURN_VAL( g_sharedBuffer.Allocate( bytesPerVertex, vertexCount, const_cast( pSrcVB ), renderContext, lod->m_vertexAllocation ), false ); + + // create d3d index buffer, this one is shared, either for dynamic or static geometry + int ibSize = indexCount * bytesPerIndex; + + const void* pSrcIB = cmfContents.GetViewData( cmfMeshLod.ib ); + + if( indexCount > 0 ) + { + ALResult hr = g_sharedBuffer.Allocate( bytesPerIndex, indexCount, const_cast( pSrcIB ), renderContext, lod->m_indexAllocation ); + if( FAILED( hr ) ) + { + g_sharedBuffer.Free( lod->m_vertexAllocation ); + return false; + } + + std::vector tempBuffer( indexCount * bytesPerIndex ); + memcpy( tempBuffer.data(), pSrcIB, tempBuffer.size() ); + if( bytesPerIndex == 2 ) + { + std::reverse( reinterpret_cast( tempBuffer.data() ), reinterpret_cast( tempBuffer.data() + cmfMeshLod.ib.size ) ); + } + else + { + std::reverse( reinterpret_cast( tempBuffer.data() ), reinterpret_cast( tempBuffer.data() + cmfMeshLod.ib.size ) ); + } + + if( FAILED( g_sharedBuffer.Allocate( bytesPerIndex, indexCount, tempBuffer.data(), renderContext, lod->m_reversedIndexAllocation ) ) ) + { + g_sharedBuffer.Free( lod->m_vertexAllocation ); + g_sharedBuffer.Free( lod->m_indexAllocation ); + return false; + } + lod->m_reversedIndicesValid = true; + } + + lod->m_morphVertexDeclaration = -1; + { // Morph Target + if( cmfMesh.morphTargets.targets.size() > 0 ) + { + // Allocate the morph target array based on the size of the first morph target + Tr2VertexDefinition morphTargetVertexDefinition = BuildFromCMFVertexDecl( cmfMesh.morphTargets.decl ); + lod->m_bytesPerMorphTargetVertex = cmfMeshLod.morphTargets[0].vb.stride; + uint32_t morphDataSize = vertexCount * lod->m_bytesPerMorphTargetVertex; + uint32_t morphTargetBufferSize = (uint32_t)( morphDataSize * cmfMesh.morphTargets.targets.size() + sizeof( TriMorphTargetGeometryConstants ) ); + + CR_RETURN_VAL( g_sharedBuffer.Allocate( + 4, + morphTargetBufferSize >> 2, + nullptr, + renderContext, + lod->m_morphTargetAllocation ), + false ); + + TriMorphTargetGeometryConstants morphTargetConstants = CreateMorphGeometryConstants( + morphTargetVertexDefinition, + lod->m_bytesPerMorphTargetVertex, + vertexCount, + renderContext ); + + lod->m_morphTargetAllocation.Update( &morphTargetConstants, 0, sizeof( TriMorphTargetGeometryConstants ), renderContext ); + + for( int i = 0; i < cmfMesh.morphTargets.targets.size(); ++i ) + { + const cmf::MorphTarget& morphTarget = cmfMesh.morphTargets.targets[i]; + + size_t nameLength = morphTarget.name.size(); + // TODO: intern, this "Shape" suffix will likely be gone later + // By convention (due to the exporter), the morph target name ends with "Shape". Assert that it does, and also that it is not an empty string! + std::string morphTargetName = cmf::ToStdString( morphTarget.name ); + if( nameLength > 5 && strcmp( morphTargetName.c_str() + nameLength - 5, "Shape" ) == 0 ) + { + morphTargetName = std::string( morphTarget.name.data(), nameLength - 5 ); + } + + lod->m_morphTargetNames.push_back( morphTargetName ); + + lod->m_isBakedMorphTarget.push_back( false ); + + const void* pMorphSrc = cmfContents.GetViewData( cmfMeshLod.morphTargets[i].vb ); + lod->m_morphTargetAllocation.Update( + const_cast( pMorphSrc ), + morphDataSize * i + sizeof( TriMorphTargetGeometryConstants ), + morphDataSize, + renderContext ); + + lod->m_morphTargetDeformationAmounts.push_back( morphTarget.maxDisplacement ); + } + + lod->m_morphVertexDeclaration = Tr2EffectStateManager::GetVertexDeclarationHandle( morphTargetVertexDefinition ); + } + } + + lod->m_allocationsValid = true; + + m_memoryUse += vbSize + ibSize; // Memory use is only approximate as a hint for the resource cache + + // Append reversed index buffer size also + m_memoryUse += ibSize; + + return true; +} diff --git a/trinity/Resources/Tr2GeometryProvider.h b/trinity/Resources/Tr2GeometryProvider.h new file mode 100644 index 000000000..f426bb155 --- /dev/null +++ b/trinity/Resources/Tr2GeometryProvider.h @@ -0,0 +1,341 @@ +// Copyright © 2026 CCP ehf. + +#pragma once +#ifndef Tr2GeometryProvider_h +#define Tr2GeometryProvider_h + +#include "Tr2DeviceResource.h" +#include "include/ITr2InstanceData.h" +#include "Tr2CmfContent.h" +#include "Tr2SuballocatedBuffer.h" + +constexpr uint32_t SHARED_BUFFER_BLOCK_SIZE = 32u * 1024u * 1024u; +constexpr uint32_t SHARED_BUFFER_MAX_SIZE = 2048u * 1024u * 1024u; +extern Tr2SuballocatedBuffer g_sharedBuffer; + +class Tr2RenderContext; + + +struct TriRtGeometryConstants +{ + uint32_t indexBufferId; + uint32_t indexBufferStride; + + uint32_t indexOffset; + + uint32_t vertexBufferId; + uint32_t vertexBufferStride; + + uint32_t positionOffset; + uint32_t positionType; + + uint32_t normalOffset; + uint32_t normalType; + + uint32_t tangentOffset; + uint32_t tangentType; + + uint32_t bitangentOffset; + uint32_t bitangentType; + + uint32_t texCoord0Offset; + uint32_t texCoord0Type; + + uint32_t texCoord1Offset; + uint32_t texCoord1Type; + + uint32_t texCoord2Offset; + uint32_t texCoord2Type; + + uint32_t padding; +}; + +struct TriGeometryResAreaData +{ + TriGeometryResAreaData(); + + std::string m_name; + int m_firstIndex; + int m_primitiveCount; + Vector3 m_minBounds; + Vector3 m_maxBounds; + TrackableStdVector m_jointBindings; + + Tr2RtBottomLevelAccelerationStructureAL m_staticBlas; + bool m_isSkinned; + bool m_isMorphed; + Tr2ConstantBufferAL m_rtGeometryConstants; +}; + + +struct TriJointBinding +{ + std::string m_name; + Vector3 m_obbMin; + Vector3 m_obbMax; +}; + +struct MeshDecalLodData +{ + uint32_t m_startIndex = 0; + uint32_t m_primitiveCount = 0; +}; + +struct MeshDecalData +{ + Matrix m_inverseDecalMatrix = IdentityMatrix(); // used as a key + Tr2SuballocatedBuffer::Allocation m_indexBuffer; + uint32_t m_lodMask; //A bit mask of which LODs have been loaded. + std::vector m_lods; +}; + +struct TriGeometryResMeshData; + +extern bool g_eveIsAudioOcclusionGeometryEnabled; + +struct AudioGeometryResData +{ + uint64_t m_id; + + std::vector m_vertices; + std::vector m_indices; + + Vector3 m_minBounds; + Vector3 m_maxBounds; + + AudioGeometryResData() : + m_id( s_nextId++ ) + { + } + +private: + static std::atomic s_nextId; +}; + +struct TriGeometryResLodData +{ + TriGeometryResLodData(); + + TriGeometryResMeshData* m_mesh; + + int32_t m_grannyMeshIndex; + + std::string m_name; + + int32_t m_originalLodIndex; + float m_maxScreenSize; + + unsigned int m_vertexCount; + unsigned int m_primitiveCount; + + std::vector m_uvDensities; + + TrackableStdVector m_areas; + + bool m_allocationsValid; + Tr2SuballocatedBuffer::Allocation m_vertexAllocation; + Tr2SuballocatedBuffer::Allocation m_indexAllocation; + + Tr2SuballocatedBuffer::Allocation m_morphTargetAllocation; + + std::vector m_morphTargetNames; + std::vector m_morphTargetDeformationAmounts; + std::vector m_isBakedMorphTarget; + + unsigned int m_morphVertexDeclaration; + unsigned int m_bytesPerMorphTargetVertex; + + // Index buffer with indexes in reversed order (used by hair/clothing) + bool m_reversedIndicesValid; + Tr2SuballocatedBuffer::Allocation m_reversedIndexAllocation; +}; + +struct TriGeometryResMeshData +{ + TriGeometryResMeshData(); + + std::string m_name; + + unsigned int m_vertexDeclarationHandle; + unsigned int m_bytesPerVertex; + + Vector3 m_minBounds; + Vector3 m_maxBounds; + Vector4 m_boundingSphere; + + TrackableStdVector m_jointBindings; + + std::unique_ptr m_audioGeometry; + std::vector> m_decals; + + uint32_t m_lodMask; //A bit mask of which LODs have been loaded. + TrackableStdVector> m_lods; +}; + +uint32_t GetPrimitiveCount( const TriGeometryResLodData& lod, uint32_t index, uint32_t count ); + +struct TriGeometryAreaIndexRange +{ + const Tr2SuballocatedBuffer::Allocation* indices = nullptr; + uint32_t startIndex = 0; + bool valid = false; +}; +TriGeometryAreaIndexRange GetAreaIndexRange( const TriGeometryResLodData& lod, uint32_t areaIndex, uint32_t primCount, bool reversed ); + +struct TriGeometryResJointData +{ + std::string m_name; + unsigned int m_parentJoint; + Matrix m_inverseWorldTransform; +}; + +struct TriGeometryResSkeletonData +{ + TriGeometryResSkeletonData(); + + unsigned int FindJoint( const char* name ) const; + + std::string m_name; + TrackableStdVector m_joints; +}; + +struct RayCastResult +{ + float distance = std::numeric_limits::infinity(); + Vector3 position; + Vector3 unnormalizedNormal; + int boneIndex = -1; + Color firstVertexColor; + Color interpolatedVertexColor; +}; + +struct TriMorphTargetGeometryConstants +{ + uint32_t vertexBufferStride; + + uint32_t positionOffset; + uint32_t positionType; + + uint32_t tangentOffset; + uint32_t tangentType; + + uint32_t vertexCount; +}; + +TriMorphTargetGeometryConstants CreateMorphGeometryConstants( const Tr2VertexDefinition& vertexDefinition, uint32_t stride, uint32_t vertexCount, Tr2RenderContext& renderContext ); + + +BLUE_CLASS_IMPL( Tr2GeometryProvider ) +class Tr2GeometryProvider : public BlueAsyncRes, + public ICacheable, + public Tr2DeviceResource, + public ITr2InstanceData +{ +public: + EXPOSE_TO_BLUE(); + + void RecalculateBoundingSphere(); + Be::Result CalculateBoundingBoxFromTransform( unsigned int meshIx, const Matrix& transform, std::pair& bounds ); + + unsigned int GetMeshCount() const; + Be::Result GetMeshName( unsigned int meshIx, std::string& name ) const; + Be::Result GetMeshAreaCount( unsigned int meshIx, int& count ) const; + Be::Result GetMeshAreaName( unsigned int meshIx, unsigned int areaIx, std::string& name ) const; + TriGeometryResMeshData* GetMeshData( unsigned int meshIx ) const; + TriGeometryResLodData* GetMeshLod( unsigned int meshIx, float screenSize ) const; + TriGeometryResLodData* GetMeshLod( unsigned int meshIx, int lodIndex ) const; + int GetLodIndexForScreenSize( unsigned int meshIx, float screenSize ) const; + + const AudioGeometryResData* GetAudioGeometry( unsigned int meshIx ) const; + + unsigned int GetSkeletonCount() const; + TriGeometryResSkeletonData* GetSkeletonData( unsigned int skelIx ) const; + + unsigned int GetAreaCount( unsigned int meshIx ) const; + Be::Result GetAreaBoundingBoxFromScript( unsigned int meshIx, unsigned int areaIx, std::pair& bounds ); + TriGeometryResAreaData* GetAreaData( unsigned int meshIx, unsigned int areaIx ) const; + + // Render multiple consecutive areas, starting at 'areaIx' + bool RenderAreas( unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed = false ); + bool RenderAreas( float screenSize, unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed = false ); + + virtual bool GetIntersectionPoints( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx = -1, float rayLength = std::numeric_limits::infinity() ); + bool GetIntersectionPointsLegacy( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx = -1, float rayLength = std::numeric_limits::infinity() ); + + virtual void PrepareRayCaster(); + virtual void ResetRayCaster(); + virtual bool IsRayCasterReady() const; + virtual bool HasRayCasterPreparationFailed() const; + virtual void DestroyRayCaster(); + + std::pair>> GetIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir ); + Be::Result GetAreaIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>& result ); + + std::pair>>>> GetIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir ); + Be::Result GetAreaIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>>>& result ); + + bool GetBoundingBox( unsigned int meshIx, Vector3& min, Vector3& max ) const; + Be::Result GetBoundingBoxFromScript( unsigned int meshIx, std::pair& bounds ) const; + bool GetAreaBoundingBox( unsigned int meshIx, unsigned int areaIx, Vector3& min, Vector3& max ) const; + bool GetBoundingSphere( unsigned int meshIx, Vector4& sphere ) const; + Be::Result GetBoundingSphereFromScript( unsigned int meshIx, std::pair& bounds ) const; + + BlueStdResult GetMeshVertexElements( size_t meshIndex, std::vector>& elements ) const; + + ///////////////////////////////////////////////////////////////////////////////////// + // ITr2InstanceData + bool IsInstanceDataReady() const override; + InstanceData GetInstanceData( unsigned int bufferIndex, float screenSize ) const override; + unsigned int GetInstanceBufferVertexDeclaration( unsigned int bufferIndex ) const override; + CcpMath::AxisAlignedBox GetInstanceBufferBoundingBox( unsigned int bufferIndex ) const override; + + ///////////////////////////////////////////////////////////////////////////////////// + // ICacheable + bool IsMemoryUsageKnown(); + size_t GetMemoryUsage(); + + ///////////////////////////////////////////////////////////////////////////////////// + // ITriDeviceResource + + void ReleaseResourcesHelper(); + + virtual bool IsUsingCMF() const; + +#if TRINITYDEV + virtual void GetDescription( std::string& desc ); +#endif + +#if WITH_GRANNY + static bool SaveMeshToGrannyFile( TriGeometryResMeshData& mesh, const char* filename ); +#endif + static bool SaveMeshToCMFFile( TriGeometryResMeshData& mesh, const char* filename ); + + // Iterator functions for processing mesh data + typedef void ( *PerTriangleCallback )( void* context, const Vector3& p1, const Vector3& p2, const Vector3& p3 ); + void ProcessMeshTriangles( int meshIx, PerTriangleCallback cb, void* cbContext ); + + // name for logging/debugging + std::string m_name; + + virtual Be::Result SaveMesh( const char* filename, uint32_t meshIndex ) const; + + +protected: + Tr2GeometryProvider( IRoot* lockobj = NULL ); + + bool SetupMeshesFromCMF( const cmf::Data& cmfData ); + void SetupSkeletonsFromCMF( const cmf::Data& cmfData ); + bool CreateMeshesFromCMFFile( Tr2CmfContents& cmfContents, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ); + bool CreateLodFromCMFMesh( Tr2CmfContents& cmfContents, const cmf::Mesh& cmfMesh, const cmf::MeshLod& cmfMeshLod, TriGeometryResLodData* pMesh, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ); + + unsigned int m_memoryUse; + TrackableStdVector> m_meshes; + TrackableStdVector> m_skeletons; + + int32_t m_forcedLodIndex = -1; + bool m_forceLod = false; +}; + +TYPEDEF_BLUECLASS_WR_SHUTDOWN( Tr2GeometryProvider ); + +#endif diff --git a/trinity/Resources/Tr2GeometryProvider_Blue.cpp b/trinity/Resources/Tr2GeometryProvider_Blue.cpp new file mode 100644 index 000000000..d328f69ac --- /dev/null +++ b/trinity/Resources/Tr2GeometryProvider_Blue.cpp @@ -0,0 +1,195 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2GeometryProvider.h" +#include "Utilities/BoundingSphere.h" + +BLUE_DEFINE_ABSTRACT( Tr2GeometryProvider ); + + +const Be::ClassInfo* Tr2GeometryProvider::ExposeToBlue() +{ + EXPOSURE_BEGIN( Tr2GeometryProvider, "" ) + MAP_INTERFACE( Tr2GeometryProvider ) + MAP_INTERFACE( IBlueResource ) + MAP_INTERFACE( ICacheable ) + MAP_INTERFACE( ITr2InstanceData ) + MAP_ICACHEABLE_METHODS() + + MAP_PROPERTY_READONLY( + "meshCount", + GetMeshCount, + "Gets the count of meshes in this geometry resource" ) + MAP_ATTRIBUTE( "name", m_name, "Name for debugging/logging", Be::READWRITE ) + + MAP_ATTRIBUTE( "forceLod", m_forceLod, "Force geometry to use a specific LOD\n:jessica-group: Debug", Be::READWRITE ) + MAP_ATTRIBUTE( "forcedLodIndex", m_forcedLodIndex, "LOD index to use when forceLod is on\n:jessica-group: Debug", Be::READWRITE ) + + MAP_METHOD_AND_WRAP( + "GetMeshCount", + GetMeshCount, + "Gets the count of meshes" ) + + MAP_METHOD_AND_WRAP( + "GetMeshName", + GetMeshName, + "Gets the name of the indexed mesh\n" + ":param idx: mesh index" ) + + MAP_METHOD_AND_WRAP( + "GetMeshAreaCount", + GetMeshAreaCount, + "Gets the count of areas within the indexed mesh\n" + ":param idx: mesh index" ) + + MAP_METHOD_AND_WRAP( + "GetMeshAreaName", + GetMeshAreaName, + "Gets the name of the indexed area within the indexed mesh\n" + ":param meshIdx: mesh index\n" + ":param areaIdx: area index\n" ) + + MAP_METHOD_AND_WRAP( + "GetAreaBoundingBox", + GetAreaBoundingBoxFromScript, + "Get the bounding box of the specified area within the mesh\n" + ":param meshIdx: mesh index\n" + ":param areaIdx: area index\n" ) + + MAP_METHOD_AND_WRAP( + "GetBoundingBox", + GetBoundingBoxFromScript, + "Get the bounding box of the specified mesh\n" + ":param meshIdx: mesh index\n" ) + MAP_METHOD_AND_WRAP( + "GetBoundingSphere", + GetBoundingSphereFromScript, + "Get the bounding sphere of the specified area\n" + ":param meshIdx: mesh index\n" ) + MAP_METHOD_AND_WRAP( + "CalculateBoundingBoxFromTransform", + CalculateBoundingBoxFromTransform, + "Get the mesh's bounding box with all points transformed using transform.\n" + ":param meshIdx: mesh index\n" + ":param transform: transform matrix\n" ) + MAP_METHOD_AND_WRAP( + "RecalculateBoundingSphere", + RecalculateBoundingSphere, + "Rebuild bounding sphere info for these meshes" ) + + MAP_METHOD_AND_WRAP( + "PrepareRayCaster", + PrepareRayCaster, + "To speed up raycasts, you can call this to load the asset asynchronously, and then poll IsRayCasterReady and HasRayCasterPreparationFailed, before attempting to call GetIntersection. Must be matched with exactly one call to ResetRayCaster!" ) + MAP_METHOD_AND_WRAP( + "ResetRayCaster", + ResetRayCaster, + "Call this after being done with GetIntersection! It will unload the asset. Must be matched with exactly one call to PrepareRayCaster!" ) + MAP_METHOD_AND_WRAP( + "IsRayCasterReady", + IsRayCasterReady, + "Call this after PrepareRayCaster to see if the asset has been loaded and is ready yet for raycasting." ) + MAP_METHOD_AND_WRAP( + "HasRayCasterPreparationFailed", + HasRayCasterPreparationFailed, + "Call this after PrepareRayCaster to see if the loading asset has failed." ) + + MAP_METHOD_AND_WRAP( + "GetIntersectionPointNormalBone", + GetIntersectionPointNormalBoneFromScript, + "( pos, dir ) ->( near )\nGet the near intersection (position, normal, bone index) between a ray and the geometry.\n" + ":param pos: ray origin\n" + ":param direction: ray direction\n" ) + + MAP_METHOD_AND_WRAP( + "GetAreaIntersectionPointNormalBone", + GetAreaIntersectionPointNormalBoneFromScript, + "( pos, dir, areaIx ) ->( near )\nGet the near intersection (position, normal, bone index) between a ray and the geometry.\n" + ":param pos: ray origin\n" + ":param direction: ray direction\n" + ":param areaIx: the mesh area index\n" ) + + MAP_METHOD_AND_WRAP( + "GetIntersectionPointNormalBoneColor", + GetIntersectionPointNormalBoneColorFromScript, + "( pos, dir ) ->( near )\nGet the near intersection (position, normal, bone index, color) between a ray and the geometry.\n" + ":param pos: ray origin\n" + ":param direction: ray direction\n" ) + + MAP_METHOD_AND_WRAP( + "GetAreaIntersectionPointNormalBoneColor", + GetAreaIntersectionPointNormalBoneColorFromScript, + "( pos, dir, areaIx ) ->( near )\nGet the near intersection (position, normal, bone index, color) between a ray and the geometry.\n" + ":param pos: ray origin\n" + ":param direction: ray direction\n" + ":param areaIx: the mesh area index\n" ) + + MAP_METHOD_AND_WRAP( + "GetMeshVertexElements", + GetMeshVertexElements, + "Returns a list of (usage, usage index) tuples for vertex layout of the specified mesh.\n" + ":param idx: mesh index" ) + + MAP_METHOD_AND_WRAP( + "SaveMesh", + SaveMesh, + "Save mesh data to a granny file\n" + ":param path: desctination file path\n" + ":param mesh: index of the mesh to save" ) + EXPOSURE_CHAINTO( BlueAsyncRes ) +} + + +namespace +{ +std::pair BoundingSphereFromPointsScript( const std::vector& points ) +{ + Vector4 sphere( 0, 0, 0, 0 ); + if( points.empty() ) + { + ::BoundingSphereFromPoints( sphere, nullptr, 0 ); + } + else + { + std::vector pointers; + pointers.reserve( points.size() ); + for( const auto& p : points ) + { + pointers.push_back( &p ); + } + ::BoundingSphereFromPoints( sphere, &pointers[0], points.size() ); + } + return std::make_pair( Vector3( sphere.x, sphere.y, sphere.z ), sphere.w ); +} +} + +MAP_FUNCTION_AND_WRAP( + "BoundingSphereFromPoints", + BoundingSphereFromPointsScript, + "Generates bounding sphere around a given points.\n" + ":param points: list of 3D points in space" ); + +#if BLUE_WITH_PYTHON +static PyObject* PySetAudioOcclusionGeometryEnabled( PyObject* self, PyObject* args ) +{ + bool enabled = false; + + if( !PyArg_ParseTuple( args, "b", &enabled ) ) + { + return NULL; + } + + g_eveIsAudioOcclusionGeometryEnabled = enabled; + + Py_RETURN_NONE; +} + +MAP_FUNCTION( + "SetAudioOcclusionGeometryEnabled", + PySetAudioOcclusionGeometryEnabled, + "Enable or disable extraction of audio occlusion geometry.\n" + "Disabled by default.\n" + ":param enabled: enable/disable audio occlusion geometry\n" + ":type enabled: bool\n" + ":rtype: None" ); +#endif diff --git a/trinity/Resources/Tr2ProceduralCube.cpp b/trinity/Resources/Tr2ProceduralCube.cpp new file mode 100644 index 000000000..219087e5a --- /dev/null +++ b/trinity/Resources/Tr2ProceduralCube.cpp @@ -0,0 +1,139 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2ProceduralCube.h" + +// based on cmf unit test found in github.com\mesh\src\cmf\test\writer.cpp +bool Tr2ProceduralCubeBuilder::BuildCmf( cmf::MemoryAllocator& allocator, cmf::BufferManager& buffers, const std::atomic& cancel, cmf::Data& data ) +{ + const std::array cubeVertices = { { + { -m_extent, -m_extent, -m_extent }, + { m_extent, -m_extent, -m_extent }, + { m_extent, m_extent, -m_extent }, + { -m_extent, m_extent, -m_extent }, + { -m_extent, -m_extent, m_extent }, + { m_extent, -m_extent, m_extent }, + { m_extent, m_extent, m_extent }, + { -m_extent, m_extent, m_extent }, + } }; + + static const std::array s_cubeIndices = { { + // Front face + 0, + 1, + 2, + 0, + 2, + 3, + // Back face + 4, + 6, + 5, + 4, + 7, + 6, + // Left face + 0, + 3, + 7, + 0, + 7, + 4, + // Right face + 1, + 5, + 6, + 1, + 6, + 2, + // Top face + 3, + 2, + 6, + 3, + 6, + 7, + // Bottom face + 0, + 4, + 5, + 0, + 5, + 1, + } }; + + // Allocate vertex buffer + const uint32_t vertexStride = sizeof( Vector3 ); + cmf::BufferView vbView = buffers.AllocateBuffer( + cubeVertices.data(), + uint32_t( cubeVertices.size() * vertexStride ), + vertexStride ); + + // Allocate index buffer + const uint32_t indexStride = sizeof( uint32_t ); + cmf::BufferView ibView = buffers.AllocateBuffer( + s_cubeIndices.data(), + uint32_t( s_cubeIndices.size() * indexStride ), + indexStride ); + + // Vertex declaration: position (float32 x 3) + cmf::VertexElement posElement = {}; + posElement.usage = cmf::Usage::Position; + posElement.usageIndex = 0; + posElement.type = cmf::ElementType::Float32; + posElement.elementCount = 3; + posElement.offset = 0; + + // LodMeshArea: one area covering all 36 indices + cmf::LodMeshArea lodArea = {}; + lodArea.firstElement = 0; + lodArea.elementCount = uint32_t( s_cubeIndices.size() ) / 3; + + // MeshLod + cmf::MeshLod lod = {}; + lod.vb = vbView; + lod.ib = ibView; + cmf::Modify( lod.areas, allocator ).emplace_back( lodArea ); + + // MeshArea + cmf::MeshArea area = {}; + area.name = allocator.AllocateString( "CubeMaterial" ); + area.bounds = CcpMath::AxisAlignedBox( + Vector3( -m_extent, -m_extent, -m_extent ), + Vector3( m_extent, m_extent, m_extent ) ); + + // Mesh + cmf::Mesh mesh = {}; + mesh.name = allocator.AllocateString( "UnitCube" ); + cmf::Modify( mesh.decl, allocator ).emplace_back( posElement ); + cmf::Modify( mesh.lods, allocator ).emplace_back( lod ); + cmf::Modify( mesh.areas, allocator ).emplace_back( area ); + mesh.topology = cmf::MeshTopology::TriangleList; + mesh.bounds = CcpMath::AxisAlignedBox( + Vector3( -m_extent, -m_extent, -m_extent ), + Vector3( m_extent, m_extent, m_extent ) ); + + data = {}; + cmf::Modify( data.meshes, allocator ).emplace_back( mesh ); + + return true; +} + +std::unique_ptr Tr2ProceduralCube::CreateBuilder() const +{ + auto builder = std::make_unique(); + builder->m_name = "Tr2ProceduralCube"; + builder->m_extent = m_extent; + return builder; +} + +float Tr2ProceduralCube::GetExtent() +{ + return m_extent; +} + +void Tr2ProceduralCube::SetExtent( float extent ) +{ + m_extent = extent; + Rebuild(); +} diff --git a/trinity/Resources/Tr2ProceduralCube.h b/trinity/Resources/Tr2ProceduralCube.h new file mode 100644 index 000000000..591a764f0 --- /dev/null +++ b/trinity/Resources/Tr2ProceduralCube.h @@ -0,0 +1,36 @@ +// Copyright © 2026 CCP ehf. + +#pragma once +#ifndef Tr2ProceduralCube_h +#define Tr2ProceduralCube_h + +#include "Tr2ProceduralGeometryProvider.h" + + +struct Tr2ProceduralCubeBuilder : public Tr2ProceduralGeometryBuilder +{ + bool BuildCmf( cmf::MemoryAllocator& allocator, cmf::BufferManager& buffers, const std::atomic& cancel, cmf::Data& data ) override; + + float m_extent = .5f; +}; + + +BLUE_CLASS_IMPL( Tr2ProceduralCube ) +class Tr2ProceduralCube : public Tr2ProceduralGeometryProvider +{ +public: + EXPOSE_TO_BLUE(); + + float GetExtent(); + void SetExtent( float extent ); + +protected: + std::unique_ptr CreateBuilder() const override; + +private: + float m_extent = .5f; +}; + +TYPEDEF_BLUECLASS_WR_SHUTDOWN( Tr2ProceduralCube ); + +#endif diff --git a/trinity/Resources/Tr2ProceduralCube_Blue.cpp b/trinity/Resources/Tr2ProceduralCube_Blue.cpp new file mode 100644 index 000000000..de900a0fa --- /dev/null +++ b/trinity/Resources/Tr2ProceduralCube_Blue.cpp @@ -0,0 +1,21 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2ProceduralCube.h" + +BLUE_DEFINE( Tr2ProceduralCube ); + + +const Be::ClassInfo* Tr2ProceduralCube::ExposeToBlue() +{ + EXPOSURE_BEGIN( Tr2ProceduralCube, "" ) + MAP_INTERFACE( Tr2ProceduralCube ) + MAP_INTERFACE( IBlueResource ) + MAP_INTERFACE( ICacheable ) + MAP_INTERFACE( ITr2InstanceData ) + MAP_ICACHEABLE_METHODS() + + MAP_PROPERTY( "extent", GetExtent, SetExtent, "Extent of the cube" ) + + EXPOSURE_CHAINTO( Tr2ProceduralGeometryProvider ) +} diff --git a/trinity/Resources/Tr2ProceduralGeometryProvider.cpp b/trinity/Resources/Tr2ProceduralGeometryProvider.cpp new file mode 100644 index 000000000..af2aa04ee --- /dev/null +++ b/trinity/Resources/Tr2ProceduralGeometryProvider.cpp @@ -0,0 +1,205 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2ProceduralGeometryProvider.h" +#include "Tr2Renderer.h" +#include "Tr2BackgroundWorker.h" + +void Tr2ProceduralGeometryProvider::Rebuild() +{ + CancelBuild(); + + m_isLoading = true; + + if( m_synchronous ) + { + m_build = nullptr; + + Build build; + build.builder = CreateBuilder(); + build.owner = this; + if( StaticRunBuild( build ) ) + { + Prepare( build ); + } + else + { + FailBuild(); + } + return; + } + + // we need a fresh piece of memory, as in-flight requests might be modifying the build. (could maybe use a pool or something) + // and we need to hold onto the pointer to be able to cancel requests. + m_build = new Build(); + // we need a builder object with a copy of parameters, as this object might be destroyed on main thread while the background worker is trying to access the parameters + m_build->builder = CreateBuilder(); + // once the builder is done, we continue on the main thread, and we need to call methods on this object + m_build->owner = this; + Tr2BackgroundWorker::Instance().Add( &StaticBuild, m_build, IBlueCallbackMan::BCBF_NONE, nullptr ); +} + +void Tr2ProceduralGeometryProvider::SetSynchronous( bool synchronous ) +{ + m_synchronous = synchronous; +} + +Tr2ProceduralGeometryProvider::Tr2ProceduralGeometryProvider( IRoot* lockobj ) : + m_build( nullptr ), + m_synchronous( false ) +{ +} + +Tr2ProceduralGeometryProvider::~Tr2ProceduralGeometryProvider() +{ + CancelBuild(); + ReleaseResources( TRISTORAGE_ALL ); +} + +bool Tr2ProceduralGeometryProvider::OnPrepareResources() +{ + if( !IsPrepared() && !IsLoading() ) + { + Rebuild(); + } + return true; +} + +void Tr2ProceduralGeometryProvider::ReleaseResources( TriStorage s ) +{ + if( s & TRISTORAGE_MANAGEDMEMORY ) + { + CancelBuild(); + ReleaseResourcesHelper(); + SetPrepared( false ); + SetGood( false ); + NotifyReleaseCachedData(); + } +} + +#if TRINITYDEV +void Tr2ProceduralGeometryProvider::GetDescription( std::string& desc ) +{ + desc = "Tr2ProceduralGeometryProvider: '"; + desc += m_name; + desc += "'"; +} +#endif + +// this function runs on worker thread +void Tr2ProceduralGeometryProvider::StaticBuild( void* context ) +{ + auto build = static_cast( context ); + if( build->cancel ) + { + // owner has nulled m_build in CancelBuild, so we don't need to do that here (and we possibly can't over here, as owner also might have been destroyed) + delete build; + return; + } + + bool success = StaticRunBuild( *build ); + + if( build->cancel ) + { + // owner has nulled m_build in CancelBuild, so we don't need to do that here (and we possibly can't over here, as owner also might have been destroyed) + delete build; + return; + } + + BeResMan->AddToQueue( BRMQ_MAIN, success ? &StaticPrepare : &StaticFail, build, IBlueCallbackMan::BCBF_NONE, nullptr ); +} + +void Tr2ProceduralGeometryProvider::StaticPrepare( void* context ) +{ + auto build = static_cast( context ); + if( !build->cancel ) + { + build->owner->m_build = nullptr; + build->owner->Prepare( *build ); + } + delete build; +} + +void Tr2ProceduralGeometryProvider::StaticFail( void* context ) +{ + auto build = static_cast( context ); + if( !build->cancel ) + { + build->owner->m_build = nullptr; + build->owner->FailBuild(); + } + delete build; +} + +// this function may run on main thread or worker thread +bool Tr2ProceduralGeometryProvider::StaticRunBuild( Build& build ) +{ + cmf::MemoryAllocator allocator; + cmf::BufferManager buffers( allocator ); + cmf::Data data; + + if( !build.builder->BuildCmf( allocator, buffers, build.cancel, data ) ) + { + CCP_LOGERR( "Tr2ProceduralGeometryProvider %s: BuildCmf failed", build.builder->m_name.c_str() ); + return false; + } + + build.bytes = cmf::BuildFile( data, buffers ); + return !build.bytes.empty(); +} + +void Tr2ProceduralGeometryProvider::Prepare( Build& build ) +{ + USE_MAIN_THREAD_RENDER_CONTEXT(); + + if( !Tr2Renderer::IsResourceCreationAllowed() ) + { + m_isLoading = false; + SetPrepared( false ); + return; + } + + Tr2CmfContents contents( build.bytes.data(), build.bytes.size(), m_name.c_str() ); + if( !contents ) + { + FailBuild(); + return; + } + + NotifyReleaseCachedData(); + ReleaseResourcesHelper(); + + if( !SetupMeshesFromCMF( *contents.GetData() ) ) + { + FailBuild(); + return; + } + + SetupSkeletonsFromCMF( *contents.GetData() ); + + SetGood( CreateMeshesFromCMFFile( contents, Tr2CpuUsage::READ, renderContext ) ); + + SetPrepared( true ); + m_isLoading = false; + NotifyRebuildCachedData(); +} + +void Tr2ProceduralGeometryProvider::FailBuild() +{ + SetPrepared( true ); + m_isLoading = false; + SetGood( false ); + NotifyReleaseCachedData(); + NotifyRebuildCachedData(); +} + +void Tr2ProceduralGeometryProvider::CancelBuild() +{ + if( !m_build ) + { + return; + } + m_build->cancel = true; + m_build = nullptr; + m_isLoading = false; +} diff --git a/trinity/Resources/Tr2ProceduralGeometryProvider.h b/trinity/Resources/Tr2ProceduralGeometryProvider.h new file mode 100644 index 000000000..05889dcab --- /dev/null +++ b/trinity/Resources/Tr2ProceduralGeometryProvider.h @@ -0,0 +1,61 @@ +// Copyright © 2026 CCP ehf. + +#pragma once +#ifndef Tr2ProceduralGeometryProvider_h +#define Tr2ProceduralGeometryProvider_h + +#include "Tr2GeometryProvider.h" + +struct Tr2ProceduralGeometryBuilder +{ + virtual ~Tr2ProceduralGeometryBuilder() = default; + virtual bool BuildCmf( cmf::MemoryAllocator& allocator, cmf::BufferManager& buffers, const std::atomic& cancel, cmf::Data& data ) = 0; + std::string m_name; +}; + +BLUE_CLASS_IMPL( Tr2ProceduralGeometryProvider ) +class Tr2ProceduralGeometryProvider : public Tr2GeometryProvider +{ +public: + EXPOSE_TO_BLUE(); + + void Rebuild(); + void SetSynchronous( bool synchronous ); + +protected: + Tr2ProceduralGeometryProvider( IRoot* lockobj = nullptr ); + virtual ~Tr2ProceduralGeometryProvider(); + + virtual std::unique_ptr CreateBuilder() const = 0; + + bool OnPrepareResources() override; + void ReleaseResources( TriStorage s ) override; + +#if TRINITYDEV + void GetDescription( std::string& desc ) override; +#endif + +private: + struct Build + { + std::atomic cancel{ false }; + std::vector bytes; + std::unique_ptr builder; + Tr2ProceduralGeometryProvider* owner = nullptr; + }; + + static void StaticBuild( void* context ); + static void StaticPrepare( void* context ); + static void StaticFail( void* context ); + static bool StaticRunBuild( Build& build ); + void Prepare( Build& build ); + void FailBuild(); + void CancelBuild(); + + Build* m_build; + bool m_synchronous; +}; + +TYPEDEF_BLUECLASS_WR_SHUTDOWN( Tr2ProceduralGeometryProvider ); + +#endif diff --git a/trinity/Resources/Tr2ProceduralGeometryProvider_Blue.cpp b/trinity/Resources/Tr2ProceduralGeometryProvider_Blue.cpp new file mode 100644 index 000000000..84fd6f374 --- /dev/null +++ b/trinity/Resources/Tr2ProceduralGeometryProvider_Blue.cpp @@ -0,0 +1,29 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2ProceduralGeometryProvider.h" + +BLUE_DEFINE_ABSTRACT( Tr2ProceduralGeometryProvider ); + + +const Be::ClassInfo* Tr2ProceduralGeometryProvider::ExposeToBlue() +{ + EXPOSURE_BEGIN( Tr2ProceduralGeometryProvider, "" ) + MAP_INTERFACE( Tr2ProceduralGeometryProvider ) + MAP_INTERFACE( IBlueResource ) + MAP_INTERFACE( ICacheable ) + MAP_INTERFACE( ITr2InstanceData ) + MAP_ICACHEABLE_METHODS() + + MAP_METHOD_AND_WRAP( + "SetSynchronous", + SetSynchronous, + "Build on main thread instead of worker thread." ) + + MAP_METHOD_AND_WRAP( + "Rebuild", + Rebuild, + "Rebuild geometry from current parameters" ) + + EXPOSURE_CHAINTO( Tr2GeometryProvider ) +} diff --git a/trinity/Resources/Tr2ProceduralSierpinski.cpp b/trinity/Resources/Tr2ProceduralSierpinski.cpp new file mode 100644 index 000000000..c41a9447d --- /dev/null +++ b/trinity/Resources/Tr2ProceduralSierpinski.cpp @@ -0,0 +1,242 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2ProceduralSierpinski.h" +#include "Include/TriMath.h" + +namespace +{ + +struct Vertex +{ + Vector3 position; + std::array packedTangent; + + bool operator==( const Vertex& other ) const + { + return position == other.position && packedTangent == other.packedTangent; + } +}; + +struct VertexHash +{ + size_t operator()( const Vertex& v ) const + { + return CcpHashFNV1( &v, sizeof( Vertex ) ); + } +}; + +uint32_t AddVertex( const Vector3& vertex, + const std::array& packedTangent, + std::unordered_map& indicesMap, + std::vector& vertices ) +{ + auto [iter, inserted] = indicesMap.try_emplace( { vertex, packedTangent }, uint32_t( vertices.size() ) ); + if( inserted ) + { + vertices.push_back( { vertex, packedTangent } ); + } + return iter->second; +} + +void AddTriangle( const Vector3& vert0, + const Vector3& vert1, + const Vector3& vert2, + const std::array& packedTangent, + std::unordered_map& indicesMap, + std::vector& indices, + std::vector& vertices ) +{ + uint32_t index0 = AddVertex( vert0, packedTangent, indicesMap, vertices ); + uint32_t index1 = AddVertex( vert1, packedTangent, indicesMap, vertices ); + uint32_t index2 = AddVertex( vert2, packedTangent, indicesMap, vertices ); + indices.insert( indices.end(), { index0, index1, index2 } ); +} + +void Sierpinski( const Vector3& up, + const Vector3& left, + const Vector3& right, + const Vector3& back, + int depth, + const std::array, 4>& packedTangents, + const std::atomic& cancel, + std::unordered_map& indicesMap, + std::vector& indices, + std::vector& vertices ) +{ + if( cancel ) + { + return; + } + + if( depth == 0 ) + { + AddTriangle( up, right, left, packedTangents[0], indicesMap, indices, vertices ); + AddTriangle( up, back, right, packedTangents[1], indicesMap, indices, vertices ); + AddTriangle( up, left, back, packedTangents[2], indicesMap, indices, vertices ); + AddTriangle( left, right, back, packedTangents[3], indicesMap, indices, vertices ); + return; + } + + Vector3 upLeft = ( up + left ) * .5f; + Vector3 upRight = ( up + right ) * .5f; + Vector3 upBack = ( up + back ) * .5f; + Vector3 leftRight = ( left + right ) * .5f; + Vector3 leftBack = ( left + back ) * .5f; + Vector3 rightBack = ( right + back ) * .5f; + + Sierpinski( up, upLeft, upRight, upBack, depth - 1, packedTangents, cancel, indicesMap, indices, vertices ); + Sierpinski( upLeft, left, leftRight, leftBack, depth - 1, packedTangents, cancel, indicesMap, indices, vertices ); + Sierpinski( upRight, leftRight, right, rightBack, depth - 1, packedTangents, cancel, indicesMap, indices, vertices ); + Sierpinski( upBack, leftBack, rightBack, back, depth - 1, packedTangents, cancel, indicesMap, indices, vertices ); +} + +} + +bool Tr2ProceduralSierpinskiBuilder::BuildCmf( cmf::MemoryAllocator& allocator, cmf::BufferManager& buffers, const std::atomic& cancel, cmf::Data& data ) +{ + std::unordered_map indicesMap; + std::vector indices; + std::vector vertices; + + float r = sqrtf( 6.f ) / 4.f; + float yb = -r / 3.f; + float cb = 1.f / sqrtf( 3.f ); + float b = cb * .5f; + + auto up = Vector3( 0.f, r, 0.f ) * m_size; + auto left = Vector3( -.5f, yb, -b ) * m_size; + auto right = Vector3( .5f, yb, -b ) * m_size; + auto back = Vector3( 0.f, yb, cb ) * m_size; + + std::array edges = { + Normalize( left - up ), + Normalize( right - up ), + Normalize( left - back ), + Normalize( right - back ) + }; + std::array normals = { + Normalize( Cross( edges[1], edges[0] ) ), + Normalize( Cross( edges[1], edges[3] ) ), + Normalize( Cross( edges[2], edges[0] ) ), + Normalize( Cross( edges[2], edges[3] ) ) + }; + std::array packedTangents = { + cmf::PackTangents( cmf::TangentCompression::PackedTangent, normals[0], edges[0], Cross( normals[0], edges[0] ) ), + cmf::PackTangents( cmf::TangentCompression::PackedTangent, normals[1], edges[3], Cross( normals[1], edges[3] ) ), + cmf::PackTangents( cmf::TangentCompression::PackedTangent, normals[2], edges[0], Cross( normals[2], edges[0] ) ), + cmf::PackTangents( cmf::TangentCompression::PackedTangent, normals[3], edges[3], Cross( normals[3], edges[3] ) ), + }; + + auto convert = cmf::GetScalarConversionFunction( cmf::ElementType::Int16Norm ).first; + std::array, 4> convertedTangents; + for( int32_t i = 0; i < 4; i++ ) + { + for( int32_t j = 0; j < 4; j++ ) + { + convert.from( &convertedTangents[i][j], packedTangents[i][j] ); + } + } + + Sierpinski( up, left, right, back, ClampInt( m_depth, 0, 5 ), convertedTangents, cancel, indicesMap, indices, vertices ); + + if( cancel ) + { + return false; + } + + // Allocate vertex buffer + const uint32_t vertexStride = sizeof( Vertex ); + cmf::BufferView vbView = buffers.AllocateBuffer( + vertices.data(), + uint32_t( vertices.size() * vertexStride ), + vertexStride ); + + // Allocate index buffer + const uint32_t indexStride = sizeof( uint32_t ); + cmf::BufferView ibView = buffers.AllocateBuffer( + indices.data(), + uint32_t( indices.size() * indexStride ), + indexStride ); + + // Vertex declaration: position (float32 x 3) + cmf::VertexElement posElement = {}; + posElement.usage = cmf::Usage::Position; + posElement.usageIndex = 0; + posElement.type = cmf::ElementType::Float32; + posElement.elementCount = 3; + posElement.offset = 0; + // Vertex declaration: packed tangent (int16norm x 4) + cmf::VertexElement packedTangentElement = {}; + packedTangentElement.usage = cmf::Usage::PackedTangent; + packedTangentElement.usageIndex = 0; + packedTangentElement.type = cmf::ElementType::Int16Norm; + packedTangentElement.elementCount = 4; + packedTangentElement.offset = offsetof( Vertex, packedTangent ); + + // LodMeshArea: one area covering all indices + cmf::LodMeshArea lodArea = {}; + lodArea.firstElement = 0; + lodArea.elementCount = uint32_t( indices.size() ) / 3; + + // MeshLod + cmf::MeshLod lod = {}; + lod.vb = vbView; + lod.ib = ibView; + cmf::Modify( lod.areas, allocator ).emplace_back( lodArea ); + + // MeshArea + cmf::MeshArea area = {}; + area.name = allocator.AllocateString( "SierpinskiMaterial" ); + area.bounds = CcpMath::AxisAlignedBox( + Vector3( -.5f, yb, -b ) * m_size, + Vector3( .5f, r, cb ) * m_size ); + + // Mesh + cmf::Mesh mesh = {}; + mesh.name = allocator.AllocateString( "Sierpinski" ); + cmf::Modify( mesh.decl, allocator ).emplace_back( posElement ); + cmf::Modify( mesh.decl, allocator ).emplace_back( packedTangentElement ); + cmf::Modify( mesh.lods, allocator ).emplace_back( lod ); + cmf::Modify( mesh.areas, allocator ).emplace_back( area ); + mesh.topology = cmf::MeshTopology::TriangleList; + mesh.bounds = CcpMath::AxisAlignedBox( + Vector3( -.5f, yb, -b ) * m_size, + Vector3( .5f, r, cb ) * m_size ); + + data = {}; + cmf::Modify( data.meshes, allocator ).emplace_back( mesh ); + + return true; +} + +std::unique_ptr Tr2ProceduralSierpinski::CreateBuilder() const +{ + auto builder = std::make_unique(); + builder->m_name = "Tr2ProceduralSierpinski"; + builder->m_size = m_size; + builder->m_depth = m_depth; + return builder; +} + +float Tr2ProceduralSierpinski::GetSize() +{ + return m_size; +} + +void Tr2ProceduralSierpinski::SetSize( float size ) +{ + m_size = size; + Rebuild(); +} + +int32_t Tr2ProceduralSierpinski::GetDepth() +{ + return m_depth; +} + +void Tr2ProceduralSierpinski::SetDepth( int32_t depth ) +{ + m_depth = depth; + Rebuild(); +} diff --git a/trinity/Resources/Tr2ProceduralSierpinski.h b/trinity/Resources/Tr2ProceduralSierpinski.h new file mode 100644 index 000000000..770a35bb2 --- /dev/null +++ b/trinity/Resources/Tr2ProceduralSierpinski.h @@ -0,0 +1,40 @@ +// Copyright © 2026 CCP ehf. + +#pragma once +#ifndef Tr2ProceduralSierpinski_h +#define Tr2ProceduralSierpinski_h + +#include "Tr2ProceduralGeometryProvider.h" + + +struct Tr2ProceduralSierpinskiBuilder : public Tr2ProceduralGeometryBuilder +{ + bool BuildCmf( cmf::MemoryAllocator& allocator, cmf::BufferManager& buffers, const std::atomic& cancel, cmf::Data& data ) override; + + float m_size = 1.f; + int32_t m_depth = 1; +}; + + +BLUE_CLASS_IMPL( Tr2ProceduralSierpinski ) +class Tr2ProceduralSierpinski : public Tr2ProceduralGeometryProvider +{ +public: + EXPOSE_TO_BLUE(); + + float GetSize(); + void SetSize( float size ); + int32_t GetDepth(); + void SetDepth( int32_t depth ); + +protected: + std::unique_ptr CreateBuilder() const override; + +private: + float m_size = 1.f; + int32_t m_depth = 1; +}; + +TYPEDEF_BLUECLASS_WR_SHUTDOWN( Tr2ProceduralSierpinski ); + +#endif diff --git a/trinity/Resources/Tr2ProceduralSierpinski_Blue.cpp b/trinity/Resources/Tr2ProceduralSierpinski_Blue.cpp new file mode 100644 index 000000000..3ada3fc5c --- /dev/null +++ b/trinity/Resources/Tr2ProceduralSierpinski_Blue.cpp @@ -0,0 +1,22 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2ProceduralSierpinski.h" + +BLUE_DEFINE( Tr2ProceduralSierpinski ); + + +const Be::ClassInfo* Tr2ProceduralSierpinski::ExposeToBlue() +{ + EXPOSURE_BEGIN( Tr2ProceduralSierpinski, "" ) + MAP_INTERFACE( Tr2ProceduralSierpinski ) + MAP_INTERFACE( IBlueResource ) + MAP_INTERFACE( ICacheable ) + MAP_INTERFACE( ITr2InstanceData ) + MAP_ICACHEABLE_METHODS() + + MAP_PROPERTY( "size", GetSize, SetSize, "Size of the tetrahedron." ) + MAP_PROPERTY( "depth", GetDepth, SetDepth, "Iteration depth of fractal.\n:jessica-numeric-range: (0, 5)" ) + + EXPOSURE_CHAINTO( Tr2ProceduralGeometryProvider ) +} diff --git a/trinity/Resources/TriGeometryRes.cpp b/trinity/Resources/TriGeometryRes.cpp index 9d177de82..ab60465d1 100644 --- a/trinity/Resources/TriGeometryRes.cpp +++ b/trinity/Resources/TriGeometryRes.cpp @@ -19,24 +19,11 @@ using namespace Tr2RenderContextEnum; -std::atomic AudioGeometryResData::s_nextId{ 1 }; - -bool g_eveIsAudioOcclusionGeometryEnabled = false; -TRI_REGISTER_SETTING( "eveIsAudioOcclusionGeometryEnabled", g_eveIsAudioOcclusionGeometryEnabled ); +CCP_STATS_DECLARED_ELSEWHERE( geometryResBytes ); int g_grannyDeprecationLevel = (int)GrannyDeprecationLevel::LOG_ERROR_AND_ASSERT; TRI_REGISTER_SETTING( "grannyDeprecationLevel", g_grannyDeprecationLevel ); -CCP_STATS_DECLARE( geometryResBytes, "Trinity/geometryResBytes", false, CST_MEMORY, "Size of memory occupied by geometry resources." ); - -#if TRINITY_PLATFORM != TRINITY_DIRECTX11 -const auto gpuUsage = Tr2GpuUsage::VERTEX_BUFFER | Tr2GpuUsage::INDEX_BUFFER | Tr2GpuUsage::SHADER_RESOURCE; -#else -const auto gpuUsage = Tr2GpuUsage::VERTEX_BUFFER | Tr2GpuUsage::INDEX_BUFFER; -#endif - -Tr2SuballocatedBuffer g_sharedBuffer( "TriGeometryRes shared vertex/index buffer", gpuUsage, SHARED_BUFFER_BLOCK_SIZE, SHARED_BUFFER_MAX_SIZE ); - void HandleGrannyDeprecation( const std::wstring& path ) { @@ -60,7 +47,6 @@ void HandleGrannyDeprecation( const std::wstring& path ) } } - static void CopyGrannyName( std::string& dest, const char* src ) { // check if valid pointer, THEN assign to std::string @@ -74,7 +60,6 @@ static void CopyGrannyName( std::string& dest, const char* src ) } } - void Tr2RaycastGeometryRes::SetLodIndices( std::vector&& lodIndices ) { m_lodIndices = std::move( lodIndices ); @@ -177,55 +162,10 @@ float SquaredDistMorphToBase( int index, bool dataIsDeltas, uint8_t* pVertices, } -uint32_t GetPrimitiveCount( const TriGeometryResLodData& lod, uint32_t index, uint32_t count ) -{ - if( index >= lod.m_areas.size() ) - { - return 0; - } - - if( index + count > lod.m_areas.size() ) - { - count = uint32_t( lod.m_areas.size() - index ); - } - - auto& meshArea = lod.m_areas[index]; - - uint32_t primCount = uint32_t( meshArea.m_primitiveCount ); - for( uint32_t i = 1; i < count; ++i ) - { - primCount += lod.m_areas[index + i].m_primitiveCount; - } - return primCount; -} - -TriGeometryAreaIndexRange GetAreaIndexRange( const TriGeometryResLodData& lod, uint32_t areaIndex, uint32_t primCount, bool reversed ) -{ - TriGeometryAreaIndexRange range; - if( primCount == 0 || areaIndex >= lod.m_areas.size() ) - { - return range; - } - if( reversed && !lod.m_reversedIndicesValid ) - { - return range; - } - range.indices = reversed ? &lod.m_reversedIndexAllocation : &lod.m_indexAllocation; - range.startIndex = reversed ? - range.indices->GetStartIndex() + lod.m_primitiveCount * 3 - lod.m_areas[areaIndex].m_firstIndex - primCount * 3 : - range.indices->GetStartIndex() + lod.m_areas[areaIndex].m_firstIndex; - range.valid = true; - return range; -} - - TriGeometryRes::TriGeometryRes( IRoot* lockobj ) : #if WITH_GRANNY m_pGrannyFile( NULL ), #endif - m_memoryUse( 0 ), - m_meshes( "TriGeometryRes/m_meshes" ), - m_skeletons( "TriGeometryRes/m_skeletons" ), m_useCMF( true ) { } @@ -292,88 +232,6 @@ unsigned int TriGeometryRes::GetAnimationCount() const return 0; } -unsigned int TriGeometryRes::GetMeshCount() const -{ - return unsigned( m_meshes.size() ); -} - -TriGeometryResMeshData* TriGeometryRes::GetMeshData( unsigned int meshIx ) const -{ - if( !m_isGood || meshIx >= m_meshes.size() ) - { - return nullptr; - } - - return m_meshes[meshIx].get(); -} - -TriGeometryResLodData* TriGeometryRes::GetMeshLod( unsigned int meshIx, float screenSize ) const -{ - auto mesh = GetMeshData( meshIx ); - - if( !mesh ) - { - return nullptr; - } - - int lodIndex = GetLodIndexForScreenSize( meshIx, screenSize ); - return mesh->m_lods[lodIndex].get(); -} - -TriGeometryResLodData* TriGeometryRes::GetMeshLod( unsigned int meshIx, int lodIndex ) const -{ - - auto mesh = GetMeshData( meshIx ); - - if( !mesh || lodIndex < 0 || lodIndex >= mesh->m_lods.size() ) - { - return nullptr; - } - - return mesh->m_lods[lodIndex].get(); -} - -int TriGeometryRes::GetLodIndexForScreenSize( unsigned int meshIx, float screenSize ) const -{ - auto mesh = GetMeshData( meshIx ); - - if( !mesh ) - { - return -1; - } - - int32_t lastLod = static_cast( mesh->m_lods.size() ) - 1; - if( m_forceLod && m_forcedLodIndex >= 0 ) - { - return int( std::min( m_forcedLodIndex, lastLod ) ); - } - - for( int32_t i = lastLod; i >= 0; i-- ) - { - if( mesh->m_lods[i]->m_maxScreenSize >= screenSize ) - { - return i; - } - } - //If we end up here, it's because we requested a LOD size larger than the best one we have. - //Just return the highest quality LOD in this case. - return 0; -} - -const AudioGeometryResData* TriGeometryRes::GetAudioGeometry( unsigned int meshIx ) const -{ - if( !g_eveIsAudioOcclusionGeometryEnabled ) - return nullptr; - - auto mesh = GetMeshData( meshIx ); - if( !mesh || !mesh->m_audioGeometry ) - { - return nullptr; - } - - return mesh->m_audioGeometry.get(); -} - #if WITH_GRANNY int TriGeometryRes::GetVertexComponentOffset( const granny_mesh* grannyMesh, const char* componentName ) const { @@ -399,108 +257,6 @@ int TriGeometryRes::GetVertexComponentOffset( const granny_mesh* grannyMesh, con } #endif -bool TriGeometryRes::GetBoundingBox( unsigned int meshIx, Vector3& min, Vector3& max ) const -{ - TriGeometryResMeshData* mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return false; - } - - min = mesh->m_minBounds; - max = mesh->m_maxBounds; - - return true; -} - -Be::Result TriGeometryRes::GetBoundingBoxFromScript( unsigned int meshIx, std::pair& bounds ) const -{ - if( meshIx >= m_meshes.size() ) - { - return Be::Result( "Mesh index out of bounds" ); - } - TriGeometryResMeshData* mesh = m_meshes[meshIx].get(); - if( !mesh ) - { - return Be::Result( "Invalid mesh" ); - } - - bounds = std::make_pair( mesh->m_minBounds, mesh->m_maxBounds ); - - return Be::Result(); -} - -bool TriGeometryRes::GetAreaBoundingBox( unsigned int meshIx, unsigned int areaIx, Vector3& min, Vector3& max ) const -{ - auto mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return false; - } - - //Get the first LOD. - auto& lod = mesh->m_lods[0]; - - // Bail out if the area index is out of range - if( areaIx >= lod->m_areas.size() ) - { - return false; - } - - // Finally, get the min and max bounds for the mesh area - min = lod->m_areas[areaIx].m_minBounds; - max = lod->m_areas[areaIx].m_maxBounds; - - return true; -} - - -bool TriGeometryRes::GetBoundingSphere( unsigned int meshIx, Vector4& sphere ) const -{ - TriGeometryResMeshData* mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return false; - } - - sphere = mesh->m_boundingSphere; - - return true; -} - -unsigned int TriGeometryRes::GetAreaCount( unsigned int meshIx ) const -{ - auto mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return 0; - } - - //Get the first LOD. - auto& lod = mesh->m_lods[0]; - - return (unsigned int)lod->m_areas.size(); -} - -TriGeometryResAreaData* TriGeometryRes::GetAreaData( unsigned int meshIx, unsigned int areaIx ) const -{ - auto mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return NULL; - } - - //Get the first LOD. - auto& lod = mesh->m_lods[0]; - - if( areaIx < 0 || areaIx >= lod->m_areas.size() ) - { - return NULL; - } - - return &lod->m_areas[areaIx]; -} - bool TriGeometryRes::OnPrepareResources() { if( IsPrepared() || IsLoading() ) @@ -543,7 +299,7 @@ BlueAsyncRes::LoadingResult TriGeometryRes::DoLoad() if( m_cmfContents ) { - if( !m_cmfContents.GetData() || !SetupMeshes( *m_cmfContents.GetData() ) ) + if( !m_cmfContents.GetData() || !SetupMeshesFromCMF( *m_cmfContents.GetData() ) ) { return LR_FAILED; }; @@ -554,7 +310,7 @@ BlueAsyncRes::LoadingResult TriGeometryRes::DoLoad() { // The granny file already exists so we only have to recreate // the meshes. - SetupMeshes( GrannyGetFileInfo( m_pGrannyFile ) ); + SetupMeshesFromGranny( GrannyGetFileInfo( m_pGrannyFile ) ); return LR_SUCCESS; } #endif @@ -852,109 +608,12 @@ bool TriGeometryRes::IsAreaMorphed( TriGeometryResAreaData& area, granny_mesh* m } #endif -bool TriGeometryRes::SetupMeshes( const cmf::Data& cmfData ) -{ - TRINITY_STATS_ZONE( __FUNCTION__ ); - - int minimumLOD = gTriDev->GetMinimumModelLOD(); - - for( const cmf::Mesh& cmfMesh : cmfData.meshes ) - { - Tr2VertexDefinition vertexDefinition = BuildFromCMFVertexDecl( cmfMesh.decl ); - const uint32_t vertexDeclarationHandle = Tr2EffectStateManager::GetVertexDeclarationHandle( vertexDefinition ); - unsigned int bytesPerVertex = cmfMesh.lods[0].vb.stride; - - TriGeometryResMeshData* mesh = new TriGeometryResMeshData(); - - mesh->m_name = cmf::ToStdString( cmfMesh.name ); - - mesh->m_minBounds = cmfMesh.bounds.m_min; - mesh->m_maxBounds = cmfMesh.bounds.m_max; - - CcpMath::Sphere sphere( cmfMesh.bounds ); - mesh->m_boundingSphere = Vector4( sphere.center, sphere.radius ); - - mesh->m_vertexDeclarationHandle = vertexDeclarationHandle; - mesh->m_bytesPerVertex = bytesPerVertex; - - for( const auto& joint : cmfMesh.boneBindings ) - { - TriJointBinding binding; - binding.m_name = cmf::ToStdString( joint.name ); - binding.m_obbMin = joint.bounds.m_min; - binding.m_obbMax = joint.bounds.m_max; - mesh->m_jointBindings.push_back( binding ); - } - - int32_t firstLodIndex = ClampInt( minimumLOD, 0, int32_t( cmfMesh.lods.size() ) - 1 ); - - for( int32_t i = firstLodIndex; i < cmfMesh.lods.size(); i++ ) - { - const cmf::MeshLod& cmfLod = cmfMesh.lods[i]; - - //Setup a new LOD, which will be added as an LOD of the mesh. - std::unique_ptr lod( new TriGeometryResLodData() ); - - lod->m_mesh = mesh; - - // TODO: intern, delete field once granny has been removed. - lod->m_grannyMeshIndex = 0xFFFFFFFF; - - lod->m_name = cmf::ToStdString( cmfMesh.name ); - - lod->m_maxScreenSize = float( cmfLod.threshold ); - - lod->m_vertexCount = cmf::GetStreamElementCount( cmfLod.vb ); - - lod->m_primitiveCount = cmf::GetStreamElementCount( cmfLod.ib ) / 3; - - // Get the UV densities, which are used for texture streaming. - lod->m_uvDensities = std::vector( cmfMesh.uvDensities.begin(), cmfMesh.uvDensities.end() ); - - lod->m_areas.resize( cmfLod.areas.size() ); - - for( int groupIndex = 0; groupIndex < cmfLod.areas.size(); ++groupIndex ) - { - const cmf::LodMeshArea& cmfLodMeshArea = cmfLod.areas[groupIndex]; - const cmf::MeshArea& cmfMeshArea = cmfMesh.areas[groupIndex]; - - TriGeometryResAreaData& area = lod->m_areas[groupIndex]; - area.m_name = cmf::ToStdString( cmfMeshArea.name ); - - area.m_firstIndex = cmfLodMeshArea.firstElement * 3; - area.m_primitiveCount = cmfLodMeshArea.elementCount; - - area.m_isSkinned = cmfMeshArea.affectedByBones; - area.m_isMorphed = cmfMeshArea.affectedByMorphTargets; - - area.m_minBounds = cmfMeshArea.bounds.m_min; - area.m_maxBounds = cmfMeshArea.bounds.m_max; - } - - lod->m_originalLodIndex = i; - - mesh->m_lods.push_back( std::move( lod ) ); - } - - uint32_t lodMask = 0u; - for( const auto& lod : mesh->m_lods ) - { - lodMask |= 1u << lod->m_originalLodIndex; - } - mesh->m_lodMask = lodMask; - - m_meshes.push_back( std::unique_ptr( mesh ) ); - } - - return true; -} - #if WITH_GRANNY -bool TriGeometryRes::SetupMeshes( granny_file_info* gi ) +bool TriGeometryRes::SetupMeshesFromGranny( granny_file_info* gi ) { TRINITY_STATS_ZONE( __FUNCTION__ ); - CCP_ASSERT_M( !m_useCMF, "TriGeometryRes::SetupMeshes: intern messed up cmf loading" ); + CCP_ASSERT_M( !m_useCMF, "TriGeometryRes::SetupMeshesFromGranny: intern messed up cmf loading" ); //Used by LOD meshes to find the model they belong to. std::vector grannyMeshIndexToMeshMap; @@ -1184,36 +843,12 @@ bool TriGeometryRes::SetupMeshes( granny_file_info* gi ) } #endif -void TriGeometryRes::SetupSkeletons( const cmf::Data& cmfData ) -{ - TRINITY_STATS_ZONE( __FUNCTION__ ); - - m_skeletons.resize( cmfData.skeletons.size() ); - for( int skelIx = 0; skelIx < cmfData.skeletons.size(); ++skelIx ) - { - const cmf::Skeleton& skel = cmfData.skeletons[skelIx]; - std::unique_ptr pSkelData( new TriGeometryResSkeletonData() ); - - pSkelData->m_name = cmf::ToStdString( skel.name ); - pSkelData->m_joints.resize( skel.bones.size() ); - for( int jointIx = 0; jointIx < skel.bones.size(); ++jointIx ) - { - TriGeometryResJointData& joint = pSkelData->m_joints[jointIx]; - joint.m_name = cmf::ToStdString( skel.bones[jointIx] ); - joint.m_parentJoint = skel.parents[jointIx]; - joint.m_inverseWorldTransform = *(Matrix*)&skel.invBindTransforms[jointIx]; - } - - std::swap( m_skeletons[skelIx], pSkelData ); - } -} - #if WITH_GRANNY -void TriGeometryRes::SetupSkeletons( granny_file_info* gi ) +void TriGeometryRes::SetupSkeletonsFromGranny( granny_file_info* gi ) { TRINITY_STATS_ZONE( __FUNCTION__ ); - CCP_ASSERT_M( !m_useCMF, "TriGeometryRes::SetupSkeletons: intern messed up cmf loading" ); + CCP_ASSERT_M( !m_useCMF, "TriGeometryRes::SetupSkeletonsFromGranny: intern messed up cmf loading" ); m_skeletons.resize( gi->SkeletonCount ); for( int skelIx = 0; skelIx < gi->SkeletonCount; ++skelIx ) @@ -1222,7 +857,7 @@ void TriGeometryRes::SetupSkeletons( granny_file_info* gi ) std::unique_ptr pSkelData( new TriGeometryResSkeletonData() ); if( pSkelData == nullptr ) { - CCP_LOGWARN( "Out of memory in TriGeometryRes::SetupSkeletons" ); + CCP_LOGWARN( "Out of memory in TriGeometryRes::SetupSkeletonsFromGranny" ); return; } CopyGrannyName( pSkelData->m_name, skel->Name ); @@ -1251,12 +886,12 @@ bool TriGeometryRes::ReadCMFFile() { return false; } - if( !SetupMeshes( *m_cmfContents.GetData() ) ) + if( !SetupMeshesFromCMF( *m_cmfContents.GetData() ) ) { return false; } - SetupSkeletons( *m_cmfContents.GetData() ); + SetupSkeletonsFromCMF( *m_cmfContents.GetData() ); return true; } @@ -1288,12 +923,12 @@ bool TriGeometryRes::ReadGrannyFile() // m_pGrannyFile is freed in CreateD3DMeshFromGranny once the D3D // resources have been created on the main thread. - if( !SetupMeshes( gi ) ) + if( !SetupMeshesFromGranny( gi ) ) { return false; } - SetupSkeletons( gi ); + SetupSkeletonsFromGranny( gi ); return true; } @@ -1315,8 +950,8 @@ void TriGeometryRes::PrepareFromGrannyRes( TriGrannyRes* g ) m_useCMF = true; - SetupMeshes( *cmfData ); - SetupSkeletons( *cmfData ); + SetupMeshesFromCMF( *cmfData ); + SetupSkeletonsFromCMF( *cmfData ); CreateMeshesFromCMFFile( g->GetCMFContents(), Tr2CpuUsage::READ | Tr2CpuUsage::WRITE, renderContext ); } #if WITH_GRANNY @@ -1331,8 +966,8 @@ void TriGeometryRes::PrepareFromGrannyRes( TriGrannyRes* g ) m_useCMF = false; - SetupMeshes( gi ); - SetupSkeletons( gi ); + SetupMeshesFromGranny( gi ); + SetupSkeletonsFromGranny( gi ); CreateMeshesFromGrannyFile( gi, Tr2CpuUsage::READ | Tr2CpuUsage::WRITE, renderContext ); } #endif @@ -1343,279 +978,58 @@ void TriGeometryRes::PrepareFromGrannyRes( TriGrannyRes* g ) SetGood( true ); } -//This entire function is sus. We should be able to compute this when we upload the mesh to the GPU, as it can never change unless the vertex data changes. -void TriGeometryRes::RecalculateBoundingSphere() +void TriGeometryRes::PrepareRayCaster() { - TRINITY_STATS_ZONE( __FUNCTION__ ); - USE_MAIN_THREAD_RENDER_CONTEXT(); + m_bvh.sessions++; - for( auto& mesh : m_meshes ) + if( m_bvh.geometry || !m_useCMF ) { - //Try to get the vertex declaration elements - Tr2VertexDefinition decl; - if( mesh == nullptr || !Tr2EffectStateManager::GetVertexDeclarationElements( mesh->m_vertexDeclarationHandle, decl ) ) - { - continue; - } - - uint32_t vertSize = mesh->m_bytesPerVertex; - - //For now, we just compute the bounding sphere of the first LOD and ignore the lower LODs, assuming that they should fit in the highest quality LOD's bounding sphere. - auto& lod = mesh->m_lods[0]; - - - //Get the vertex data of the - - const uint8_t* pVertices; - if( !lod->m_allocationsValid || FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) - { - continue; - } - auto position = decl.Find( decl.POSITION, 0 ); - - // build a list of pointers to the positions - std::vector points( lod->m_vertexCount ); - for( uint32_t p = 0; p < lod->m_vertexCount; ++p ) - { - points[p] = (const Vector3*)&pVertices[p * vertSize + position->m_offset]; - } + return; + } - // all is done in this recursive function - BoundingSphereFromPoints( mesh->m_boundingSphere, &points[0], points.size() ); - lod->m_vertexAllocation.UnmapForReading( renderContext ); + std::vector lodIndices; + lodIndices.resize( m_meshes.size() ); + for( size_t i = 0; i < m_meshes.size(); i++ ) + { + lodIndices[i] = m_meshes[i] ? m_meshes[i]->m_lods[0]->m_originalLodIndex : 0; } + + m_bvh.geometry.CreateInstance(); + m_bvh.geometry->SetLodIndices( std::move( lodIndices ) ); + m_bvh.geometry->Initialize( GetFilePath().c_str(), GetExt() ); } -struct CalcBoundingBoxContext +void TriGeometryRes::DestroyRayCaster() { - Vector3 min; - Vector3 max; - Matrix transform; -}; + if( m_bvh.geometry ) + { + m_bvh.geometry->CancelPendingLoad(); + m_bvh.geometry.Unlock(); + } +} -void CalcBoundingBox( void* context, const Vector3& p1, const Vector3& p2, const Vector3& p3 ) +void TriGeometryRes::ResetRayCaster() { - CalcBoundingBoxContext* ctx = static_cast( context ); + m_bvh.sessions--; - Vector4 v[3]; - v[0] = Vector4( p1.x, p1.y, p1.z, 1.0f ); - v[1] = Vector4( p2.x, p2.y, p2.z, 1.0f ); - v[2] = Vector4( p3.x, p3.y, p3.z, 1.0f ); + CCP_ASSERT( m_bvh.sessions >= 0 ); - for( int i = 0; i < 3; ++i ) + if( m_bvh.sessions > 0 ) { - v[i] = Transform( v[i], ctx->transform ); - v[i] /= v[i].w; - - ctx->max.x = std::max( ctx->max.x, v[i].x ); - ctx->max.y = std::max( ctx->max.y, v[i].y ); - ctx->max.z = std::max( ctx->max.z, v[i].z ); - - ctx->min.x = std::min( ctx->min.x, v[i].x ); - ctx->min.y = std::min( ctx->min.y, v[i].y ); - ctx->min.z = std::min( ctx->min.z, v[i].z ); + return; } + + m_bvh.geometry.Unlock(); } -static void ConvertTriangleData( Tr2VertexDefinition::DataType elementType, unsigned int vertexByteSize, const uint8_t* vertexBase, unsigned int index1, unsigned int index2, unsigned int index3, Vector3* v1, Vector3* v2, Vector3* v3 ) +bool TriGeometryRes::IsRayCasterReady() const { - - ConvertDataToVector3( elementType, vertexBase + index1 * vertexByteSize, v1 ); - ConvertDataToVector3( elementType, vertexBase + index2 * vertexByteSize, v2 ); - ConvertDataToVector3( elementType, vertexBase + index3 * vertexByteSize, v3 ); + return m_bvh.geometry && m_bvh.geometry->IsGood(); } -void TriGeometryRes::ProcessMeshTriangles( int meshIx, PerTriangleCallback cb, void* cbContext ) +bool TriGeometryRes::HasRayCasterPreparationFailed() const { - TRINITY_STATS_ZONE( __FUNCTION__ ); - USE_MAIN_THREAD_RENDER_CONTEXT(); - - int i = meshIx; - - if( m_meshes[i] == NULL ) - { - return; - } - - auto& mesh = m_meshes[i]; - - auto& lod = mesh->m_lods[0]; - - if( !lod->m_allocationsValid ) - { - return; - } - - const uint8_t* pVertices; - const uint8_t* pIndices; - - int vertSize = mesh->m_bytesPerVertex; - - if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) - { - return; - } - ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); - if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) - { - return; - } - ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); - - const uint16_t* pShortIndices = (uint16_t*)pIndices; - const uint32_t* pLongIndices = (uint32_t*)pIndices; - - - Tr2VertexDefinition decl; - if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh->m_vertexDeclarationHandle, decl ) ) - { - return; - } - - auto foundPosition = decl.Find( Tr2VertexDefinition::POSITION, 0 ); - if( !foundPosition ) - { - return; - } - unsigned int positionByteOffset = foundPosition->m_offset; - Tr2VertexDefinition::DataType declType = foundPosition->m_dataType; - - int numPrim = lod->m_primitiveCount; - for( int j = 0; j < numPrim; j++ ) - { - unsigned int index1 = 0; - unsigned int index2 = 0; - unsigned int index3 = 0; - Vector3 p1; - Vector3 p2; - Vector3 p3; - if( lod->m_indexAllocation.GetStride() == 2 ) - { - index1 = pShortIndices[j * 3]; - index2 = pShortIndices[( j * 3 ) + 1]; - index3 = pShortIndices[( j * 3 ) + 2]; - } - else - { - index1 = pLongIndices[j * 3]; - index2 = pLongIndices[( j * 3 ) + 1]; - index3 = pLongIndices[( j * 3 ) + 2]; - } - - ConvertTriangleData( declType, vertSize, pVertices + positionByteOffset, index1, index2, index3, &p1, &p2, &p3 ); - - ( *cb )( cbContext, p1, p2, p3 ); - } -} - -std::pair>> TriGeometryRes::GetIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir ) -{ - RayCastResult hitInfo; - bool hit = GetIntersectionPoints( pos, dir, hitInfo ); - Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); - return std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, normal ) ) ); -} - -Be::Result TriGeometryRes::GetAreaIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>& result ) -{ - if( areaIx < -1 ) - { - // -1 is a special case handled to maintain legacy behavior. - return Be::Result( "Invalid area index" ); - } - - // In Python 3, passing a signed value to an unsigned C exposed attribute is an error. - // In order to maintain backwards compatible behavior with Python 2 - // this method got changed to accept signed integers and cast them to unsigned. - auto unsignedAreaIndex = static_cast( areaIx ); - - RayCastResult hitInfo; - bool hit = GetIntersectionPoints( pos, dir, hitInfo, unsignedAreaIndex ); - Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); - result = std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, normal ) ) ); - return Be::Result(); -} - -std::pair>>>> TriGeometryRes::GetIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir ) -{ - RayCastResult hitInfo; - bool hit = GetIntersectionPoints( pos, dir, hitInfo ); - Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); - return std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, std::make_pair( normal, std::make_pair( hitInfo.firstVertexColor, hitInfo.interpolatedVertexColor ) ) ) ) ); -} - -Be::Result TriGeometryRes::GetAreaIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>>>& result ) -{ - if( areaIx < -1 ) - { - // -1 is a special case handled to maintain legacy behavior. - return Be::Result( "Invalid area index" ); - } - - // In Python 3, passing a signed value to an unsigned C exposed attribute is an error. - // In order to maintain backwards compatible behavior with Python 2 - // this method got changed to accept signed integers and cast them to unsigned. - auto unsignedAreaIndex = static_cast( areaIx ); - - RayCastResult hitInfo; - bool hit = GetIntersectionPoints( pos, dir, hitInfo, unsignedAreaIndex ); - Vector3 normal = Normalize( hitInfo.unnormalizedNormal ); - result = std::make_pair( hit, std::make_pair( hitInfo.boneIndex, std::make_pair( hitInfo.position, std::make_pair( normal, std::make_pair( hitInfo.firstVertexColor, hitInfo.interpolatedVertexColor ) ) ) ) ); - return Be::Result(); -} - -void TriGeometryRes::PrepareRayCaster() -{ - m_bvh.sessions++; - - if( m_bvh.geometry || !m_useCMF ) - { - return; - } - - std::vector lodIndices; - lodIndices.resize( m_meshes.size() ); - for( size_t i = 0; i < m_meshes.size(); i++ ) - { - lodIndices[i] = m_meshes[i] ? m_meshes[i]->m_lods[0]->m_originalLodIndex : 0; - } - - m_bvh.geometry.CreateInstance(); - m_bvh.geometry->SetLodIndices( std::move( lodIndices ) ); - m_bvh.geometry->Initialize( GetFilePath().c_str(), GetExt() ); -} - -void TriGeometryRes::DestroyRayCaster() -{ - if( m_bvh.geometry ) - { - m_bvh.geometry->CancelPendingLoad(); - m_bvh.geometry.Unlock(); - } -} - -void TriGeometryRes::ResetRayCaster() -{ - m_bvh.sessions--; - - CCP_ASSERT( m_bvh.sessions >= 0 ); - - if( m_bvh.sessions > 0 ) - { - return; - } - - m_bvh.geometry.Unlock(); -} - -bool TriGeometryRes::IsRayCasterReady() const -{ - return m_bvh.geometry && m_bvh.geometry->IsGood(); -} - -bool TriGeometryRes::HasRayCasterPreparationFailed() const -{ - if( m_bvh.sessions > 0 && !m_bvh.geometry ) // ReleaseResources destroyed our bvh :( + if( m_bvh.sessions > 0 && !m_bvh.geometry ) // ReleaseResources destroyed our bvh :( { return true; } @@ -1694,183 +1108,12 @@ bool TriGeometryRes::GetIntersectionPoints( const Vector3& pos, const Vector3& d return true; } -// Careful: This function is slow and only works on main thread! -bool TriGeometryRes::GetIntersectionPointsLegacy( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx, float rayLength ) -{ - USE_MAIN_THREAD_RENDER_CONTEXT(); - - bool hit = false; - - for( size_t i = 0; i < m_meshes.size(); i++ ) - { - if( m_meshes[i] == NULL ) - { - continue; - } - - //Get the first LOD. - auto& lod = m_meshes[i]->m_lods[0]; - - const uint8_t* pVertices; - const uint8_t* pIndices; - - int vertSize = m_meshes[i]->m_bytesPerVertex; - if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) - { - return false; - } - ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); - if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) - { - return false; - } - ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); - - const uint16_t* pShortIndices = (uint16_t*)pIndices; - const uint32_t* pLongIndices = (uint32_t*)pIndices; - - Tr2VertexDefinition decl; - if( !Tr2EffectStateManager::GetVertexDeclarationElements( m_meshes[i]->m_vertexDeclarationHandle, decl ) ) - { - return false; - } - const Tr2VertexDefinition::Item* const position = decl.Find( decl.POSITION ); - if( !position ) - { - return false; - } - - const Tr2VertexDefinition::Item* const blendIndices = decl.Find( decl.BLENDINDICES ); - const Tr2VertexDefinition::Item* const colors = decl.Find( decl.COLOR ); - - int numPrim = lod->m_primitiveCount; - auto currentIndex = 0; - if( areaIx != -1 ) - { - if( areaIx >= lod->m_areas.size() ) - { - continue; - } - currentIndex = lod->m_areas[areaIx].m_firstIndex; - numPrim = lod->m_areas[areaIx].m_primitiveCount; - } - - for( int j = 0; j < numPrim; j++ ) - { - unsigned int index1 = 0; - unsigned int index2 = 0; - unsigned int index3 = 0; - Vector3 p1; - Vector3 p2; - Vector3 p3; - float pu, pv, dist; - if( lod->m_indexAllocation.GetStride() == 2 ) - { - index1 = pShortIndices[currentIndex++]; - index2 = pShortIndices[currentIndex++]; - index3 = pShortIndices[currentIndex++]; - } - else - { - index1 = pLongIndices[currentIndex++]; - index2 = pLongIndices[currentIndex++]; - index3 = pLongIndices[currentIndex++]; - } - - ConvertTriangleData( position->m_dataType, vertSize, pVertices + position->m_offset, index1, index2, index3, &p1, &p2, &p3 ); - - if( IntersectTri( p1, p2, p3, pos, dir, pu, pv, dist ) ) - { - if( rayLength > dist ) - { - float v1 = 1.0f - ( pu + pv ); - - result.position = p1 * v1 + p2 * pu + p3 * pv; - - Vector3 avec = p2 - p1; - Vector3 bvec = p3 - p1; - result.unnormalizedNormal = Cross( avec, bvec ); - - result.distance = rayLength = dist; - - int boneIndex = 0; - if( blendIndices && GetBoneIndex( blendIndices->m_dataType, pVertices + index1 * vertSize + blendIndices->m_offset, boneIndex ) ) - { - result.boneIndex = boneIndex; - } - - Color color1; - Color color2; - Color color3; - if( colors && - GetColor( colors->m_dataType, pVertices + index1 * vertSize + colors->m_offset, color1 ) && - GetColor( colors->m_dataType, pVertices + index2 * vertSize + colors->m_offset, color2 ) && - GetColor( colors->m_dataType, pVertices + index3 * vertSize + colors->m_offset, color3 ) ) - { - result.firstVertexColor = color1; - result.interpolatedVertexColor = color1 * v1 + color2 * pu + color3 * pv; - } - - hit = true; - } - } - } - } - - return hit; -} - -unsigned int TriGeometryRes::GetSkeletonCount() const -{ - return (unsigned int)m_skeletons.size(); -} - -TriGeometryResSkeletonData* TriGeometryRes::GetSkeletonData( unsigned int skelIx ) const -{ - CCP_ASSERT( skelIx < m_skeletons.size() ); - - return m_skeletons[skelIx].get(); -} - -bool TriGeometryRes::IsMemoryUsageKnown() -{ - return !IsLoading(); -} - -size_t TriGeometryRes::GetMemoryUsage() -{ - return m_memoryUse; -} - void TriGeometryRes::Initialize( const wchar_t* name, const wchar_t* ext ) { ReleaseResources( TRISTORAGE_ALL ); BlueAsyncRes::Initialize( name, ext ); } -void TriGeometryRes::ReleaseResourcesHelper() -{ - m_meshes.clear(); - //TODO: Do we need to clear skeletons here too? - - CCP_STATS_ADD( geometryResBytes, -(int)m_memoryUse ); - m_memoryUse = 0; -} - -unsigned int TriGeometryResSkeletonData::FindJoint( const char* name ) const -{ - unsigned int jointCount = (unsigned int)m_joints.size(); - for( unsigned int ix = 0; ix < jointCount; ++ix ) - { - if( strcmp( name, m_joints[ix].m_name.c_str() ) == 0 ) - { - return ix; - } - } - - return 0xffffffff; -} - void TriGeometryRes::Reload() { CancelPendingLoad(); @@ -1882,128 +1125,6 @@ void TriGeometryRes::Reload() BlueAsyncRes::Reload(); } -TriGeometryResSkeletonData::TriGeometryResSkeletonData() : - m_joints( "TriGeometryResSkeletonData/m_joints" ) -{ -} - -TriGeometryResAreaData::TriGeometryResAreaData() : - m_firstIndex( 0 ), - m_primitiveCount( 0 ), - m_jointBindings( "TriGeometryResAreaData/m_jointBindings" ), - m_isSkinned( false ), - m_isMorphed( false ) -{ -} - -TriGeometryResLodData::TriGeometryResLodData() : - m_vertexCount( 0 ), - m_primitiveCount( 0 ), - m_areas( "TriGeometryResMeshData/m_areas" ), - m_morphVertexDeclaration( Tr2EffectStateManager::UNINITIALIZED_DECLARATION ), - m_allocationsValid( false ), - m_reversedIndicesValid( false ), - m_uvDensities(), - m_bytesPerMorphTargetVertex( 0 ) -{ -} - - -TriGeometryResMeshData::TriGeometryResMeshData() : - - m_vertexDeclarationHandle( Tr2EffectStateManager::UNINITIALIZED_DECLARATION ), - m_bytesPerVertex( -1 ), - m_jointBindings( "TriGeometryResMeshData/m_jointBindings" ), - m_lods( "TriGeometryResModelData/m_lods" ) -{ -} - -bool TriGeometryRes::RenderAreas( unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed ) -{ - return RenderAreas( std::numeric_limits::max(), meshIx, areaIx, areaCount, renderContext, reversed ); -} - -bool TriGeometryRes::RenderAreas( float screenSize, unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed ) -{ - if( !m_isGood ) - { - return false; - } - - if( meshIx >= m_meshes.size() ) - { - return false; - } - - TriGeometryResMeshData* mesh = m_meshes[meshIx].get(); - if( !mesh ) - { - return false; - } - - TriGeometryResLodData* lod = GetMeshLod( meshIx, screenSize ); - - unsigned int primCount = GetPrimitiveCount( *lod, areaIx, areaCount ); - - if( primCount ) - { - auto indexRange = GetAreaIndexRange( *lod, areaIx, primCount, reversed ); - if( !indexRange.valid ) - { - return false; - } - - renderContext.m_esm.ApplyVertexDeclaration( mesh->m_vertexDeclarationHandle ); - renderContext.m_esm.ApplyStreamSource( 0, lod->m_vertexAllocation ); - renderContext.m_esm.ApplyIndexBuffer( *indexRange.indices ); - - renderContext.SetTopology( TOP_TRIANGLES ); - renderContext.DrawIndexedPrimitive( lod->m_vertexCount, indexRange.startIndex, primCount ); - } - - return true; -} - -namespace -{ -TriMorphTargetGeometryConstants CreateMorphGeometryConstants( const Tr2VertexDefinition& vertexDefinition, uint32_t stride, uint32_t vertexCount, Tr2RenderContext& renderContext ) -{ - TriMorphTargetGeometryConstants data = TriMorphTargetGeometryConstants{}; - for( const auto& item : vertexDefinition.m_items ) - { - if( item.m_usage == Tr2VertexDefinition::POSITION && item.m_usageIndex == 0 && item.m_stream == 0 ) - { - data.positionOffset = item.m_offset; - data.positionType = item.m_dataType; - - uint32_t type = data.positionType; - CCP_ASSERT_M( - type == Tr2VertexDefinition::DataType::FLOAT32_3, - "position type has to be FLOAT32_3!" ); - } - - if( item.m_usage == Tr2VertexDefinition::TANGENT && item.m_usageIndex == 0 && item.m_stream == 0 ) - { - data.tangentOffset = item.m_offset; - data.tangentType = item.m_dataType; - - uint32_t type = data.tangentType; - CCP_ASSERT_M( - type == Tr2VertexDefinition::DataType::FLOAT16_3 || - type == Tr2VertexDefinition::DataType::FLOAT32_3 || - type == Tr2VertexDefinition::DataType::UBYTE_4_NORM || - type == Tr2VertexDefinition::DataType::USHORT_4_NORM || - type == Tr2VertexDefinition::DataType::SHORT_4_NORM, - "tangent type has to be FLOAT16_3 or FLOAT32_3 or UBYTE_4_NORM or USHORT_4_NORM or SHORT_4_NORM!" ); - } - } - - data.vertexBufferStride = stride; - data.vertexCount = vertexCount; - return data; -} -} - #if WITH_GRANNY float CalculateMorphDeformationAmount( bool dataIsDeltas, int32_t vertexCount, uint8_t* pMorphSrc, Tr2VertexDefinition morphDecl, uint8_t* pVertices, Tr2VertexDefinition decl ) { @@ -2023,131 +1144,6 @@ float CalculateMorphDeformationAmount( bool dataIsDeltas, int32_t vertexCount, u } #endif -bool TriGeometryRes::CreateLodFromCMFMesh( Tr2CmfContents& cmfContents, const cmf::Mesh& cmfMesh, const cmf::MeshLod& cmfMeshLod, TriGeometryResLodData* lod, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ) -{ - TRINITY_STATS_ZONE( __FUNCTION__ ); - - if( !Tr2Renderer::IsResourceCreationAllowed() ) - { - return false; - } - - Tr2VertexDefinition vertexDefinition = BuildFromCMFVertexDecl( cmfMesh.decl ); - const unsigned bytesPerVertex = cmfMeshLod.vb.stride; - const unsigned bytesPerIndex = cmfMeshLod.ib.stride; - - int vertexCount = cmf::GetStreamElementCount( cmfMeshLod.vb ); - int vbSize = cmfMeshLod.vb.size; - - CCP_ASSERT_M( cmfContents, "TriGeometryRes::CreateLodFromCMFMesh: cmfContents must be valid!" ); - - const void* pSrcVB = cmfContents.GetViewData( cmfMeshLod.vb ); - - int indexCount = cmf::GetStreamElementCount( cmfMeshLod.ib ); - - CR_RETURN_VAL( g_sharedBuffer.Allocate( bytesPerVertex, vertexCount, const_cast( pSrcVB ), renderContext, lod->m_vertexAllocation ), false ); - - // create d3d index buffer, this one is shared, either for dynamic or static geometry - int ibSize = indexCount * bytesPerIndex; - - const void* pSrcIB = cmfContents.GetViewData( cmfMeshLod.ib ); - - if( indexCount > 0 ) - { - ALResult hr = g_sharedBuffer.Allocate( bytesPerIndex, indexCount, const_cast( pSrcIB ), renderContext, lod->m_indexAllocation ); - if( FAILED( hr ) ) - { - g_sharedBuffer.Free( lod->m_vertexAllocation ); - return false; - } - - std::vector tempBuffer( indexCount * bytesPerIndex ); - memcpy( tempBuffer.data(), pSrcIB, tempBuffer.size() ); - if( bytesPerIndex == 2 ) - { - std::reverse( reinterpret_cast( tempBuffer.data() ), reinterpret_cast( tempBuffer.data() + cmfMeshLod.ib.size ) ); - } - else - { - std::reverse( reinterpret_cast( tempBuffer.data() ), reinterpret_cast( tempBuffer.data() + cmfMeshLod.ib.size ) ); - } - - if( FAILED( g_sharedBuffer.Allocate( bytesPerIndex, indexCount, tempBuffer.data(), renderContext, lod->m_reversedIndexAllocation ) ) ) - { - g_sharedBuffer.Free( lod->m_vertexAllocation ); - g_sharedBuffer.Free( lod->m_indexAllocation ); - return false; - } - lod->m_reversedIndicesValid = true; - } - - lod->m_morphVertexDeclaration = -1; - { // Morph Target - if( cmfMesh.morphTargets.targets.size() > 0 ) - { - // Allocate the morph target array based on the size of the first morph target - Tr2VertexDefinition morphTargetVertexDefinition = BuildFromCMFVertexDecl( cmfMesh.morphTargets.decl ); - lod->m_bytesPerMorphTargetVertex = cmfMeshLod.morphTargets[0].vb.stride; - uint32_t morphDataSize = vertexCount * lod->m_bytesPerMorphTargetVertex; - uint32_t morphTargetBufferSize = (uint32_t)( morphDataSize * cmfMesh.morphTargets.targets.size() + sizeof( TriMorphTargetGeometryConstants ) ); - - CR_RETURN_VAL( g_sharedBuffer.Allocate( - 4, - morphTargetBufferSize >> 2, - nullptr, - renderContext, - lod->m_morphTargetAllocation ), - false ); - - TriMorphTargetGeometryConstants morphTargetConstants = CreateMorphGeometryConstants( - morphTargetVertexDefinition, - lod->m_bytesPerMorphTargetVertex, - vertexCount, - renderContext ); - - lod->m_morphTargetAllocation.Update( &morphTargetConstants, 0, sizeof( TriMorphTargetGeometryConstants ), renderContext ); - - for( int i = 0; i < cmfMesh.morphTargets.targets.size(); ++i ) - { - const cmf::MorphTarget& morphTarget = cmfMesh.morphTargets.targets[i]; - - size_t nameLength = morphTarget.name.size(); - // TODO: intern, this "Shape" suffix will likely be gone later - // By convention (due to the exporter), the morph target name ends with "Shape". Assert that it does, and also that it is not an empty string! - std::string morphTargetName = cmf::ToStdString( morphTarget.name ); - if( nameLength > 5 && strcmp( morphTargetName.c_str() + nameLength - 5, "Shape" ) == 0 ) - { - morphTargetName = std::string( morphTarget.name.data(), nameLength - 5 ); - } - - lod->m_morphTargetNames.push_back( morphTargetName ); - - lod->m_isBakedMorphTarget.push_back( false ); - - const void* pMorphSrc = cmfContents.GetViewData( cmfMeshLod.morphTargets[i].vb ); - lod->m_morphTargetAllocation.Update( - const_cast( pMorphSrc ), - morphDataSize * i + sizeof( TriMorphTargetGeometryConstants ), - morphDataSize, - renderContext ); - - lod->m_morphTargetDeformationAmounts.push_back( morphTarget.maxDisplacement ); - } - - lod->m_morphVertexDeclaration = Tr2EffectStateManager::GetVertexDeclarationHandle( morphTargetVertexDefinition ); - } - } - - lod->m_allocationsValid = true; - - m_memoryUse += vbSize + ibSize; // Memory use is only approximate as a hint for the resource cache - - // Append reversed index buffer size also - m_memoryUse += ibSize; - - return true; -} - #if WITH_GRANNY bool TriGeometryRes::CreateLodFromGrannyMesh( granny_mesh* grannyMesh, TriGeometryResLodData* lod, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext, void* pVBOverride ) { @@ -2282,70 +1278,31 @@ bool TriGeometryRes::CreateLodFromGrannyMesh( granny_mesh* grannyMesh, TriGeomet lod->m_isBakedMorphTarget.push_back( false ); - void* pMorphSrc = GrannyGetMeshMorphVertices( grannyMesh, i ); - lod->m_morphTargetAllocation.Update( - pMorphSrc, - morphDataSize * i + sizeof( TriMorphTargetGeometryConstants ), - morphDataSize, - renderContext ); - - float deformationAmount = CalculateMorphDeformationAmount( morphTarget.DataIsDeltas, vertexCount, (uint8_t*)pMorphSrc, morphTargetVertexDefinition, (uint8_t*)pSrc, vertexDefinition ); - lod->m_morphTargetDeformationAmounts.push_back( deformationAmount ); - } - - lod->m_morphVertexDeclaration = Tr2EffectStateManager::GetVertexDeclarationHandle( firstMorphTargetVertexDefinition ); - } - } - - lod->m_allocationsValid = true; - - m_memoryUse += vbSize + ibSize; // Memory use is only approximate as a hint for the resource cache - - // Append reversed index buffer size also - m_memoryUse += ibSize; - - return true; -} -#endif - -bool TriGeometryRes::CreateMeshesFromCMFFile( Tr2CmfContents& cmfContents, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ) -{ - TRINITY_STATS_ZONE( __FUNCTION__ ); - - if( !cmfContents.GetData() ) - { - return false; - } - - for( int32_t i = 0; i < m_meshes.size(); i++ ) - { - auto& mesh = m_meshes[i]; - - for( const auto& lod : mesh->m_lods ) - { - CreateLodFromCMFMesh( cmfContents, cmfContents.GetData()->meshes[i], cmfContents.GetData()->meshes[i].lods[lod->m_originalLodIndex], lod.get(), cpuUsage, renderContext ); - } - - if( g_eveIsAudioOcclusionGeometryEnabled ) - { - const auto& audioMesh = cmfContents.GetData()->meshes[i].audioOcclusionMesh; - if( !audioMesh.vertices.empty() && !audioMesh.indices.empty() ) - { - auto audioGeometry = std::make_unique(); - audioGeometry->m_indices.insert( audioGeometry->m_indices.end(), audioMesh.indices.begin(), audioMesh.indices.end() ); - audioGeometry->m_vertices.insert( audioGeometry->m_vertices.end(), audioMesh.vertices.begin(), audioMesh.vertices.end() ); - audioGeometry->m_minBounds = audioMesh.bounds.m_min; - audioGeometry->m_maxBounds = audioMesh.bounds.m_max; + void* pMorphSrc = GrannyGetMeshMorphVertices( grannyMesh, i ); + lod->m_morphTargetAllocation.Update( + pMorphSrc, + morphDataSize * i + sizeof( TriMorphTargetGeometryConstants ), + morphDataSize, + renderContext ); - mesh->m_audioGeometry = std::move( audioGeometry ); + float deformationAmount = CalculateMorphDeformationAmount( morphTarget.DataIsDeltas, vertexCount, (uint8_t*)pMorphSrc, morphTargetVertexDefinition, (uint8_t*)pSrc, vertexDefinition ); + lod->m_morphTargetDeformationAmounts.push_back( deformationAmount ); } + + lod->m_morphVertexDeclaration = Tr2EffectStateManager::GetVertexDeclarationHandle( firstMorphTargetVertexDefinition ); } } - CCP_STATS_ADD( geometryResBytes, m_memoryUse ); + lod->m_allocationsValid = true; + + m_memoryUse += vbSize + ibSize; // Memory use is only approximate as a hint for the resource cache + + // Append reversed index buffer size also + m_memoryUse += ibSize; return true; } +#endif #if WITH_GRANNY static granny_mesh* FindOcclusionMesh( granny_file_info* gi, const std::string& meshName ) @@ -2468,501 +1425,6 @@ Be::Result TriGeometryRes::SaveMesh( const char* filename, uint32_t return Be::Result(); } -bool TriGeometryRes::SaveMeshToCMFFile( TriGeometryResMeshData& mesh, const char* filename ) -{ - USE_MAIN_THREAD_RENDER_CONTEXT(); - - if( mesh.m_vertexDeclarationHandle == Tr2EffectStateManager::UNINITIALIZED_DECLARATION ) - { - return false; - } - - auto& lod = mesh.m_lods[0]; - - if( !lod->m_allocationsValid ) - { - return false; - } - - Tr2VertexDefinition decl; - if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh.m_vertexDeclarationHandle, decl ) ) - { - return false; - } - - cmf::MemoryAllocator allocator; - cmf::BufferManager bufferAllocator( allocator ); - cmf::Span cmfVertexDecl = allocator.AllocateSpan( decl.m_items.size() ); - - if( !ConvertVertexDeclToCMF( decl, cmfVertexDecl, (uint32_t)cmfVertexDecl.size() ) ) - { - return false; - } - - uint32_t vertexBufferSize = lod->m_vertexAllocation.GetSize(); - uint32_t vertexBufferStride = lod->m_vertexAllocation.GetStride(); - const void* pVertices; - if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) - { - return false; - } - ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); - - uint32_t indexBufferSize = lod->m_indexAllocation.GetSize(); - uint32_t indexBufferStride = lod->m_indexAllocation.GetStride(); - const void* pIndices; - if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) - { - return false; - } - ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); - - - cmf::Data cmfData; - cmfData.meshes = allocator.AllocateSpan( 1 ); - - cmf::Mesh& cmfMesh = cmfData.meshes[0] = {}; - cmfMesh.name = allocator.AllocateString( mesh.m_name ); - cmfMesh.decl = cmfVertexDecl; - cmfMesh.lods = allocator.AllocateSpan( 1 ); - cmfMesh.areas = allocator.AllocateSpan( lod->m_areas.size() ); - cmfMesh.boneBindings = allocator.AllocateSpan( mesh.m_jointBindings.size() ); - cmfMesh.uvDensities = allocator.AllocateSpan( lod->m_uvDensities.size() ); - cmfMesh.bounds = AxisAlignedBoundingBox( mesh.m_minBounds, mesh.m_maxBounds ); - - memcpy( cmfMesh.uvDensities.data(), lod->m_uvDensities.data(), lod->m_uvDensities.size() * sizeof( float ) ); - - cmf::MeshLod& cmfMeshLod = cmfMesh.lods[0]; - cmfMeshLod.vb = bufferAllocator.AddBuffer( (void*)pVertices, vertexBufferSize, vertexBufferStride ); - cmfMeshLod.ib = bufferAllocator.AddBuffer( (void*)pIndices, indexBufferSize, indexBufferStride ); - cmfMeshLod.areas = allocator.AllocateSpan( lod->m_areas.size() ); - cmfMeshLod.morphTargets = cmf::Span(); - cmfMeshLod.threshold = (uint32_t)lod->m_maxScreenSize; - - for( int32_t i = 0; i < cmfMesh.areas.size(); i++ ) - { - cmf::MeshArea& cmfMeshArea = cmfMesh.areas[i]; - cmfMeshArea.name = allocator.AllocateString( lod->m_areas[i].m_name ); - cmfMeshArea.bounds = AxisAlignedBoundingBox( lod->m_areas[i].m_minBounds, lod->m_areas[i].m_maxBounds ); - cmfMeshArea.bones = allocator.AllocateSpan( lod->m_areas[i].m_jointBindings.size() ); - cmfMeshArea.affectedByBones = lod->m_areas[i].m_isSkinned; - cmfMeshArea.affectedByMorphTargets = false; - - for( int32_t j = 0; j < lod->m_areas[i].m_jointBindings.size(); j++ ) - { - cmfMeshArea.bones[j] = (uint16_t)lod->m_areas[i].m_jointBindings[j]; - } - - cmf::LodMeshArea& lodMeshArea = cmfMeshLod.areas[i]; - lodMeshArea.firstElement = lod->m_areas[i].m_firstIndex / 3; - lodMeshArea.elementCount = lod->m_areas[i].m_primitiveCount; - } - - for( int32_t i = 0; i < cmfMesh.boneBindings.size(); i++ ) - { - cmf::BoneBinding& cmfBoneBinding = cmfMesh.boneBindings[i]; - cmfBoneBinding.name = allocator.AllocateString( mesh.m_jointBindings[i].m_name ); - cmfBoneBinding.bounds = AxisAlignedBoundingBox( mesh.m_jointBindings[i].m_obbMin, mesh.m_jointBindings[i].m_obbMax ); - } - - auto file = cmf::BuildFile( cmfData, bufferAllocator ); - - std::wstring filenameW = (const wchar_t*)CA2W( filename ); - std::wstring fullPath = BePaths->ResolvePathForWritingW( filenameW ); - - FILE* outFile; - auto err = fopen_s( &outFile, CW2A( fullPath.c_str() ), "wb" ); - - if( err != 0 ) - { - CCP_LOGERR( "Failed to open output file: %ls\n", fullPath.c_str() ); - return false; - } - if( fwrite( file.data(), 1, file.size(), outFile ) != file.size() ) - { - CCP_LOGERR( "Failed to write output file: %ls\n", fullPath.c_str() ); - fclose( outFile ); - return false; - } - fclose( outFile ); - - return true; -} - -#if WITH_GRANNY -bool TriGeometryRes::SaveMeshToGrannyFile( TriGeometryResMeshData& mesh, const char* filename ) -{ - USE_MAIN_THREAD_RENDER_CONTEXT(); - - if( mesh.m_vertexDeclarationHandle == Tr2EffectStateManager::UNINITIALIZED_DECLARATION ) - { - return false; - } - - auto& lod = mesh.m_lods[0]; - - if( !lod->m_allocationsValid ) - { - return false; - } - - granny_data_type_definition grannyVertexDecl[16]; - - Tr2VertexDefinition decl; - if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh.m_vertexDeclarationHandle, decl ) ) - { - return false; - } - - if( !ConvertVertexDeclToGranny( decl, grannyVertexDecl, sizeof( grannyVertexDecl ) / sizeof( grannyVertexDecl[0] ) ) ) - { - return false; - } - - const void* pVertices; - if( FAILED( lod->m_vertexAllocation.MapForReading( pVertices, renderContext ) ) ) - { - return false; - } - ON_BLOCK_EXIT( [&] { lod->m_vertexAllocation.UnmapForReading( renderContext ); } ); - - const void* pIndices; - if( FAILED( lod->m_indexAllocation.MapForReading( pIndices, renderContext ) ) ) - { - return false; - } - ON_BLOCK_EXIT( [&] { lod->m_indexAllocation.UnmapForReading( renderContext ); } ); - - - granny_vertex_data myVertexData; - memset( &myVertexData, 0, sizeof( myVertexData ) ); - myVertexData.Vertices = (granny_uint8*)pVertices; - myVertexData.VertexCount = lod->m_vertexCount; - myVertexData.VertexType = grannyVertexDecl; - - granny_tri_topology myTopology; - memset( &myTopology, 0, sizeof( myTopology ) ); - - if( lod->m_vertexCount <= 65535 ) - { - myTopology.Indices16 = (granny_uint16*)pIndices; - myTopology.Index16Count = lod->m_primitiveCount * 3; - } - else - { - myTopology.Indices = (granny_int32*)pIndices; - myTopology.IndexCount = lod->m_primitiveCount * 3; - } - - myTopology.GroupCount = (granny_int32)lod->m_areas.size(); - myTopology.Groups = CCP_NEW( "myTopology.Groups" ) granny_tri_material_group[myTopology.GroupCount]; - if( myTopology.Groups ) - { - for( int i = 0; i < myTopology.GroupCount; ++i ) - { - myTopology.Groups[i].MaterialIndex = i; - myTopology.Groups[i].TriFirst = lod->m_areas[i].m_firstIndex / 3; - myTopology.Groups[i].TriCount = lod->m_areas[i].m_primitiveCount; - } - } - else - { - myTopology.GroupCount = 0; - } - - granny_mesh grannyMesh; - memset( &grannyMesh, 0, sizeof( grannyMesh ) ); - - grannyMesh.PrimaryVertexData = &myVertexData; - grannyMesh.PrimaryTopology = &myTopology; - - granny_file_info fileInfo; - memset( &fileInfo, 0, sizeof( fileInfo ) ); - - granny_mesh* meshes[] = { &grannyMesh }; - granny_tri_topology* topologies[] = { grannyMesh.PrimaryTopology }; - granny_vertex_data* vertexDatas[] = { grannyMesh.PrimaryVertexData }; - - fileInfo.ModelCount = 0; - fileInfo.Models = 0; - fileInfo.MeshCount = 1; - fileInfo.Meshes = meshes; - fileInfo.VertexDataCount = 1; - fileInfo.VertexDatas = vertexDatas; - fileInfo.TriTopologyCount = 1; - fileInfo.TriTopologies = topologies; - - fileInfo.MaterialCount = (granny_int32)lod->m_areas.size(); - fileInfo.Materials = CCP_NEW( "fileInfo.Materials" ) granny_material * [fileInfo.MaterialCount]; - if( fileInfo.Materials == nullptr ) - { - fileInfo.MaterialCount = 0; - } - - grannyMesh.MaterialBindingCount = fileInfo.MaterialCount; - grannyMesh.MaterialBindings = CCP_NEW( "grannyMesh.MaterialBindings" ) granny_material_binding[fileInfo.MaterialCount]; - if( grannyMesh.MaterialBindings == nullptr ) - { - grannyMesh.MaterialBindingCount = 0; - } - - granny_material* pMaterials = nullptr; - if( fileInfo.MaterialCount ) - { - pMaterials = CCP_NEW( "pMaterials" ) granny_material[fileInfo.MaterialCount]; - if( pMaterials ) - { - memset( pMaterials, 0, sizeof( granny_material ) * fileInfo.MaterialCount ); - - for( int i = 0; i < fileInfo.MaterialCount; ++i ) - { - fileInfo.Materials[i] = &pMaterials[i]; - fileInfo.Materials[i]->Name = lod->m_areas[i].m_name.c_str(); - grannyMesh.MaterialBindings[i].Material = fileInfo.Materials[i]; - } - } - } - - // Bones - grannyMesh.BoneBindingCount = (granny_int32)mesh.m_jointBindings.size(); - grannyMesh.BoneBindings = CCP_NEW( "grannyMesh.BoneBindings" ) granny_bone_binding[grannyMesh.BoneBindingCount]; - if( grannyMesh.BoneBindings == nullptr ) - { - grannyMesh.BoneBindingCount = 0; - } - if( grannyMesh.BoneBindingCount > 0 ) - { - memset( grannyMesh.BoneBindings, 0, sizeof( granny_bone_binding ) * grannyMesh.BoneBindingCount ); - for( int i = 0; i < grannyMesh.BoneBindingCount; ++i ) - { - grannyMesh.BoneBindings[i].BoneName = mesh.m_jointBindings[i].m_name.c_str(); - grannyMesh.BoneBindings[i].OBBMin[0] = mesh.m_jointBindings[i].m_obbMin.x; - grannyMesh.BoneBindings[i].OBBMin[1] = mesh.m_jointBindings[i].m_obbMin.y; - grannyMesh.BoneBindings[i].OBBMin[2] = mesh.m_jointBindings[i].m_obbMin.z; - grannyMesh.BoneBindings[i].OBBMax[0] = mesh.m_jointBindings[i].m_obbMax.x; - grannyMesh.BoneBindings[i].OBBMax[1] = mesh.m_jointBindings[i].m_obbMax.y; - grannyMesh.BoneBindings[i].OBBMax[2] = mesh.m_jointBindings[i].m_obbMax.z; - } - } - - - granny_file_builder* Builder = GrannyBeginFile( 1, GrannyCurrentGRNStandardTag, GrannyGRNFileMV_32Bit_LittleEndian, GrannyGetTemporaryDirectory(), "GRNFileTemp" ); - granny_file_data_tree_writer* Writer = - GrannyBeginFileDataTreeWriting( GrannyFileInfoType, &fileInfo, 0, 0 ); - GrannyWriteDataTreeToFileBuilder( Writer, Builder ); - GrannyEndFileDataTreeWriting( Writer ); - - std::wstring filenameW = (const wchar_t*)CA2W( filename ); - std::wstring fullPath = BePaths->ResolvePathForWritingW( filenameW ); - GrannyEndFile( Builder, CW2A( fullPath.c_str() ) ); - - CCP_DELETE[] pMaterials; - CCP_DELETE[] grannyMesh.MaterialBindings; - CCP_DELETE[] grannyMesh.BoneBindings; - CCP_DELETE[] fileInfo.Materials; - - return true; -} -#endif - -// -------------------------------------------------------------------------------------- -// Description: -// Implements ITr2InstanceData interface. Query if the instance data is ready to be -// used, i.e. if the resource is good. -// Return Value: -// true if data is ready to be used -// false otherwise -// -------------------------------------------------------------------------------------- -bool TriGeometryRes::IsInstanceDataReady() const -{ - return IsGood(); -} - -ITr2InstanceData::InstanceData TriGeometryRes::GetInstanceData( unsigned int bufferIndex, float screenSize ) const -{ - auto mesh = GetMeshData( bufferIndex ); - - if( !mesh ) - { - static Tr2BufferAL nullVB; - return { nullVB }; - } - - auto lod = GetMeshLod( bufferIndex, screenSize ); - return { - lod->m_vertexAllocation.GetBuffer(), lod->m_vertexAllocation.GetOffset(), mesh->m_bytesPerVertex, lod->m_vertexCount - }; -} - -// -------------------------------------------------------------------------------------- -// Description: -// Implements ITr2InstanceData interface. Returns returns vertex declaration handle -// for the given instance buffer index. -// Arguments: -// bufferIndex - instance buffer index -// Return Value: -// vertex declaration handle -// -------------------------------------------------------------------------------------- -unsigned int TriGeometryRes::GetInstanceBufferVertexDeclaration( unsigned int bufferIndex ) const -{ - if( bufferIndex < m_meshes.size() && m_meshes[bufferIndex] ) - { - return m_meshes[bufferIndex]->m_vertexDeclarationHandle; - } - return Tr2EffectStateManager::UNINITIALIZED_DECLARATION; -} - -CcpMath::AxisAlignedBox TriGeometryRes::GetInstanceBufferBoundingBox( unsigned int bufferIndex ) const -{ - auto mesh = GetMeshData( bufferIndex ); - if( !mesh ) - { - return CcpMath::AxisAlignedBox(); - } - return CcpMath::AxisAlignedBox( mesh->m_minBounds, mesh->m_maxBounds ); -} - -Be::Result TriGeometryRes::GetMeshName( unsigned int meshIx, std::string& name ) const -{ - TriGeometryResMeshData* mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return Be::Result( "Mesh index out of range, or invalid mesh" ); - } - - name = mesh->m_name; - - return Be::Result(); -} - -Be::Result TriGeometryRes::GetMeshAreaCount( unsigned int meshIx, int& count ) const -{ - TriGeometryResMeshData* mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return Be::Result( "Mesh index out of range, or invalid mesh" ); - } - - //Get the first LOD. - auto& lod = mesh->m_lods[0]; - - count = (int)lod->m_areas.size(); - - return Be::Result(); -} - -Be::Result TriGeometryRes::GetMeshAreaName( unsigned int meshIx, unsigned int areaIx, std::string& name ) const -{ - TriGeometryResMeshData* mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return Be::Result( "Mesh index out of range, or invalid mesh" ); - } - - //Get the first LOD. - auto& lod = mesh->m_lods[0]; - - if( areaIx < 0 || areaIx >= lod->m_areas.size() ) - { - return Be::Result( "Area index out of range" ); - } - - name = lod->m_areas[areaIx].m_name; - - return Be::Result(); -} - -Be::Result TriGeometryRes::GetAreaBoundingBoxFromScript( unsigned int meshIx, unsigned int areaIx, std::pair& bounds ) -{ - TriGeometryResMeshData* mesh = GetMeshData( meshIx ); - if( !mesh ) - { - return Be::Result( "Mesh index out of range, or invalid mesh" ); - } - - //Get the first LOD. - auto& lod = mesh->m_lods[0]; - - if( areaIx < 0 || areaIx >= lod->m_areas.size() ) - { - return Be::Result( "Area index out of range" ); - } - - bounds = std::make_pair( lod->m_areas[areaIx].m_minBounds, lod->m_areas[areaIx].m_maxBounds ); - - return Be::Result(); -} - -Be::Result TriGeometryRes::GetBoundingSphereFromScript( unsigned int meshIx, std::pair& bounds ) const -{ - if( meshIx >= m_meshes.size() ) - { - return Be::Result( "Mesh index out of range" ); - } - auto& mesh = m_meshes[meshIx]; - if( !mesh ) - { - return Be::Result( "Invalid mesh" ); - } - - bounds = std::make_pair( Vector3( mesh->m_boundingSphere.x, mesh->m_boundingSphere.y, mesh->m_boundingSphere.z ), mesh->m_boundingSphere.w ); - return Be::Result(); -} - -Be::Result TriGeometryRes::CalculateBoundingBoxFromTransform( unsigned int meshIx, const Matrix& transform, std::pair& bounds ) -{ - if( meshIx >= m_meshes.size() ) - { - return Be::Result( "Mesh index out of range" ); - } - auto& mesh = m_meshes[meshIx]; - if( !mesh ) - { - return Be::Result( "Invalid mesh" ); - } - - CalcBoundingBoxContext ctx; - ctx.min = Vector3( FLT_MAX, FLT_MAX, FLT_MAX ); - ctx.max = Vector3( -FLT_MAX, -FLT_MAX, -FLT_MAX ); - ctx.transform = transform; - - if( meshIx < m_meshes.size() ) - { - ProcessMeshTriangles( meshIx, &CalcBoundingBox, &ctx ); - } - - bounds = std::make_pair( ctx.min, ctx.max ); - return Be::Result(); -} - - -BlueStdResult TriGeometryRes::GetMeshVertexElements( size_t meshIndex, std::vector>& elements ) const -{ - elements.clear(); - - if( !IsGood() ) - { - return BlueStdResult( BLUE_STD_RESULT_RUNTIME_ERROR, "TriGeometryRes is not prepared" ); - } - if( meshIndex >= m_meshes.size() || !m_meshes[meshIndex] ) - { - return BlueStdResult( BLUE_STD_RESULT_INDEX_ERROR, "mesh index out of range" ); - } - - auto& mesh = m_meshes[meshIndex]; - Tr2VertexDefinition decl; - if( !Tr2EffectStateManager::GetVertexDeclarationElements( mesh->m_vertexDeclarationHandle, decl ) ) - { - return BlueStdResult( BLUE_STD_RESULT_RUNTIME_ERROR, "could not retrieve vertex declaration" ); - } - - for( auto it = std::begin( decl.m_items ); it != std::end( decl.m_items ); ++it ) - { - elements.push_back( std::make_pair( it->m_usage, it->m_usageIndex ) ); - } - return BLUE_STD_RESULT_OK; -} - bool TriGeometryRes::IsUsingCMF() const { #if WITH_GRANNY != 1 diff --git a/trinity/Resources/TriGeometryRes.h b/trinity/Resources/TriGeometryRes.h index 99a465cd9..92a1fa5a7 100644 --- a/trinity/Resources/TriGeometryRes.h +++ b/trinity/Resources/TriGeometryRes.h @@ -3,24 +3,21 @@ #ifndef _TriGeometryRes_H_ #define _TriGeometryRes_H_ -#include "Tr2DeviceResource.h" - -#include "include/ITr2InstanceData.h" -#include "include/ITr2GpuBuffer.h" - #include "Tr2CmfContent.h" -#include "Tr2SuballocatedBuffer.h" +#include "Tr2GeometryProvider.h" #include "../Utilities/BVH.h" -constexpr uint32_t SHARED_BUFFER_BLOCK_SIZE = 32u * 1024u * 1024u; -constexpr uint32_t SHARED_BUFFER_MAX_SIZE = 2048u * 1024u * 1024u; -extern Tr2SuballocatedBuffer g_sharedBuffer; - - - BLUE_DECLARE( TriGrannyRes ); class Tr2RenderContext; +enum class GrannyDeprecationLevel +{ + DO_NOTHING, + LOG_ERROR, + LOG_ERROR_AND_ASSERT +}; + +void HandleGrannyDeprecation( const std::wstring& path ); BLUE_CLASS( Tr2RaycastGeometryRes ) : public BlueAsyncRes @@ -43,226 +40,8 @@ BLUE_CLASS( Tr2RaycastGeometryRes ) : TYPEDEF_BLUECLASS_WR_SHUTDOWN( Tr2RaycastGeometryRes ); -enum class GrannyDeprecationLevel -{ - DO_NOTHING, - LOG_ERROR, - LOG_ERROR_AND_ASSERT -}; - -void HandleGrannyDeprecation( const std::wstring& path ); - -struct TriRtGeometryConstants -{ - uint32_t indexBufferId; - uint32_t indexBufferStride; - - uint32_t indexOffset; - - uint32_t vertexBufferId; - uint32_t vertexBufferStride; - - uint32_t positionOffset; - uint32_t positionType; - - uint32_t normalOffset; - uint32_t normalType; - - uint32_t tangentOffset; - uint32_t tangentType; - - uint32_t bitangentOffset; - uint32_t bitangentType; - - uint32_t texCoord0Offset; - uint32_t texCoord0Type; - - uint32_t texCoord1Offset; - uint32_t texCoord1Type; - - uint32_t texCoord2Offset; - uint32_t texCoord2Type; - - uint32_t padding; -}; - -struct TriMorphTargetGeometryConstants -{ - uint32_t vertexBufferStride; - - uint32_t positionOffset; - uint32_t positionType; - - uint32_t tangentOffset; - uint32_t tangentType; - - uint32_t vertexCount; -}; - -struct TriGeometryResAreaData -{ - TriGeometryResAreaData(); - - std::string m_name; - int m_firstIndex; - int m_primitiveCount; - Vector3 m_minBounds; - Vector3 m_maxBounds; - TrackableStdVector m_jointBindings; - - Tr2RtBottomLevelAccelerationStructureAL m_staticBlas; - bool m_isSkinned; - bool m_isMorphed; - Tr2ConstantBufferAL m_rtGeometryConstants; -}; - - -struct TriJointBinding -{ - std::string m_name; - Vector3 m_obbMin; - Vector3 m_obbMax; -}; - -struct MeshDecalLodData -{ - uint32_t m_startIndex = 0; - uint32_t m_primitiveCount = 0; -}; - -struct MeshDecalData -{ - Matrix m_inverseDecalMatrix = IdentityMatrix(); // used as a key - Tr2SuballocatedBuffer::Allocation m_indexBuffer; - uint32_t m_lodMask; //A bit mask of which LODs have been loaded. - std::vector m_lods; -}; - -struct TriGeometryResMeshData; - -extern bool g_eveIsAudioOcclusionGeometryEnabled; - -struct AudioGeometryResData -{ - uint64_t m_id; - - std::vector m_vertices; - std::vector m_indices; - - Vector3 m_minBounds; - Vector3 m_maxBounds; - - AudioGeometryResData() : - m_id( s_nextId++ ) - { - } - -private: - static std::atomic s_nextId; -}; - -struct TriGeometryResLodData -{ - TriGeometryResLodData(); - - TriGeometryResMeshData* m_mesh; - - int32_t m_grannyMeshIndex; - - std::string m_name; - - int32_t m_originalLodIndex; - float m_maxScreenSize; - - unsigned int m_vertexCount; - unsigned int m_primitiveCount; - - std::vector m_uvDensities; - - TrackableStdVector m_areas; - - bool m_allocationsValid; - Tr2SuballocatedBuffer::Allocation m_vertexAllocation; - Tr2SuballocatedBuffer::Allocation m_indexAllocation; - - Tr2SuballocatedBuffer::Allocation m_morphTargetAllocation; - - std::vector m_morphTargetNames; - std::vector m_morphTargetDeformationAmounts; - std::vector m_isBakedMorphTarget; - - unsigned int m_morphVertexDeclaration; - unsigned int m_bytesPerMorphTargetVertex; - - // Index buffer with indexes in reversed order (used by hair/clothing) - bool m_reversedIndicesValid; - Tr2SuballocatedBuffer::Allocation m_reversedIndexAllocation; -}; - -struct TriGeometryResMeshData -{ - TriGeometryResMeshData(); - - std::string m_name; - - unsigned int m_vertexDeclarationHandle; - unsigned int m_bytesPerVertex; - - Vector3 m_minBounds; - Vector3 m_maxBounds; - Vector4 m_boundingSphere; - - TrackableStdVector m_jointBindings; - - std::unique_ptr m_audioGeometry; - std::vector> m_decals; - - uint32_t m_lodMask; //A bit mask of which LODs have been loaded. - TrackableStdVector> m_lods; -}; - -uint32_t GetPrimitiveCount( const TriGeometryResLodData& lod, uint32_t index, uint32_t count ); - -struct TriGeometryAreaIndexRange -{ - const Tr2SuballocatedBuffer::Allocation* indices = nullptr; - uint32_t startIndex = 0; - bool valid = false; -}; -TriGeometryAreaIndexRange GetAreaIndexRange( const TriGeometryResLodData& lod, uint32_t areaIndex, uint32_t primCount, bool reversed ); - -struct TriGeometryResJointData -{ - std::string m_name; - unsigned int m_parentJoint; - Matrix m_inverseWorldTransform; -}; - -struct TriGeometryResSkeletonData -{ - TriGeometryResSkeletonData(); - - unsigned int FindJoint( const char* name ) const; - - std::string m_name; - TrackableStdVector m_joints; -}; - -struct RayCastResult -{ - float distance = std::numeric_limits::infinity(); - Vector3 position; - Vector3 unnormalizedNormal; - int boneIndex = -1; - Color firstVertexColor; - Color interpolatedVertexColor; -}; - -BLUE_CLASS( TriGeometryRes ) : - public BlueAsyncRes, - public ICacheable, - public Tr2DeviceResource, - public ITr2InstanceData +BLUE_CLASS_IMPL( TriGeometryRes ) +class TriGeometryRes : public Tr2GeometryProvider { public: EXPOSE_TO_BLUE(); @@ -270,29 +49,6 @@ BLUE_CLASS( TriGeometryRes ) : TriGeometryRes( IRoot* lockobj = NULL ); virtual ~TriGeometryRes(); - void RecalculateBoundingBox(); - void RecalculateBoundingSphere(); - Be::Result CalculateBoundingBoxFromTransform( unsigned int meshIx, const Matrix& transform, std::pair& bounds ); - - unsigned int GetMeshCount() const; - Be::Result GetMeshName( unsigned int meshIx, std::string& name ) const; - Be::Result GetMeshAreaCount( unsigned int meshIx, int& count ) const; - Be::Result GetMeshAreaName( unsigned int meshIx, unsigned int areaIx, std::string& name ) const; - TriGeometryResMeshData* GetMeshData( unsigned int meshIx ) const; - TriGeometryResLodData* GetMeshLod( unsigned int meshIx, float screenSize ) const; - TriGeometryResLodData* GetMeshLod( unsigned int meshIx, int lodIndex ) const; - int GetLodIndexForScreenSize( unsigned int meshIx, float screenSize ) const; - - - const AudioGeometryResData* GetAudioGeometry( unsigned int meshIx ) const; - - unsigned int GetSkeletonCount() const; - TriGeometryResSkeletonData* GetSkeletonData( unsigned int skelIx ) const; - - unsigned int GetAreaCount( unsigned int meshIx ) const; - Be::Result GetAreaBoundingBoxFromScript( unsigned int meshIx, unsigned int areaIx, std::pair& bounds ); - TriGeometryResAreaData* GetAreaData( unsigned int meshIx, unsigned int areaIx ) const; - unsigned int GetAnimationCount() const; #if WITH_GRANNY @@ -300,106 +56,51 @@ BLUE_CLASS( TriGeometryRes ) : int GetVertexComponentOffset( const granny_mesh* myMesh, const char* componentName ) const; #endif - // Render multiple consecutive areas, starting at 'areaIx' - bool RenderAreas( unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed = false ); - bool RenderAreas( float screenSize, unsigned int meshIx, unsigned int areaIx, unsigned int areaCount, Tr2RenderContext& renderContext, bool reversed = false ); - - void RebuildCachedData(); - - bool GetIntersectionPoints( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx = -1, float rayLength = std::numeric_limits::infinity() ); - - bool GetIntersectionPointsLegacy( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx = -1, float rayLength = std::numeric_limits::infinity() ); - - void PrepareRayCaster(); - void ResetRayCaster(); - bool IsRayCasterReady() const; - bool HasRayCasterPreparationFailed() const; - void DestroyRayCaster(); - - std::pair>> GetIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir ); - Be::Result GetAreaIntersectionPointNormalBoneFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>& result ); - - std::pair>>>> GetIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir ); - Be::Result GetAreaIntersectionPointNormalBoneColorFromScript( const Vector3& pos, const Vector3& dir, int areaIx, std::pair>>>>& result ); - - bool GetBoundingBox( unsigned int meshIx, Vector3& min, Vector3& max ) const; - Be::Result GetBoundingBoxFromScript( unsigned int meshIx, std::pair& bounds ) const; - bool GetAreaBoundingBox( unsigned int meshIx, unsigned int areaIx, Vector3& min, Vector3& max ) const; - bool GetBoundingSphere( unsigned int meshIx, Vector4& sphere ) const; - Be::Result GetBoundingSphereFromScript( unsigned int meshIx, std::pair& bounds ) const; + bool GetIntersectionPoints( const Vector3& pos, const Vector3& dir, RayCastResult& result, uint32_t areaIx = -1, float rayLength = std::numeric_limits::infinity() ) override; - void PrepareFromGrannyRes( TriGrannyRes * g ); + void PrepareRayCaster() override; + void ResetRayCaster() override; + bool IsRayCasterReady() const override; + bool HasRayCasterPreparationFailed() const override; + void DestroyRayCaster() override; - BlueStdResult GetMeshVertexElements( size_t meshIndex, std::vector>& elements ) const; + void PrepareFromGrannyRes( TriGrannyRes* g ); ////////////////////////////////////////////////////////////////////////// // IBlueResource // // Initialize is implemented by the BlueAsyncRes base class, but we // need to intercept it to reset data structures on reload - void Initialize( const wchar_t* name, const wchar_t* ext ); - - ///////////////////////////////////////////////////////////////////////////////////// - // ITr2InstanceData - bool IsInstanceDataReady() const override; - InstanceData GetInstanceData( unsigned int bufferIndex, float screenSize ) const override; - unsigned int GetInstanceBufferVertexDeclaration( unsigned int bufferIndex ) const override; - CcpMath::AxisAlignedBox GetInstanceBufferBoundingBox( unsigned int bufferIndex ) const override; - - ///////////////////////////////////////////////////////////////////////////////////// - // ITr2GpuBuffer - Tr2BufferAL* GetGpuBuffer( unsigned index ); - - ///////////////////////////////////////////////////////////////////////////////////// - // ICacheable - bool IsMemoryUsageKnown(); - size_t GetMemoryUsage(); + void Initialize( const wchar_t* name, const wchar_t* ext ) override; ///////////////////////////////////////////////////////////////////////////////////// // ITriDeviceResource private: - bool OnPrepareResources(); + bool OnPrepareResources() override; public: - void ReleaseResources( TriStorage s ); + void ReleaseResources( TriStorage s ) override; - void ReleaseResourcesHelper(); #if TRINITYDEV - virtual void GetDescription( std::string & desc ); + void GetDescription( std::string& desc ) override; #endif #if WITH_GRANNY granny_file_info* GetGrannyInfo() const; #endif -#if WITH_GRANNY - static bool SaveMeshToGrannyFile( TriGeometryResMeshData & mesh, const char* filename ); -#endif - static bool SaveMeshToCMFFile( TriGeometryResMeshData & mesh, const char* filename ); - - // Iterator functions for processing mesh data - typedef void ( *PerTriangleCallback )( void* context, const Vector3& p1, const Vector3& p2, const Vector3& p3 ); - void ProcessMeshTriangles( int meshIx, PerTriangleCallback cb, void* cbContext ); - void Reload(); - // name for logging/debugging - std::string m_name; - TriGrannyResPtr m_sourceGranny; - Be::Result SaveMesh( const char* filename, uint32_t meshIndex ) const; + Be::Result SaveMesh( const char* filename, uint32_t meshIndex ) const override; - bool IsUsingCMF() const; + bool IsUsingCMF() const override; const cmf::Data* GetCMFData() const; void VisualizeBVH( Tr2DebugObjectReference owner, const Matrix& transform, ITr2DebugRenderer2& renderer ) const; private: - unsigned int m_memoryUse; - TrackableStdVector> m_meshes; - TrackableStdVector> m_skeletons; - #if WITH_GRANNY granny_file* m_pGrannyFile; #endif @@ -407,9 +108,6 @@ BLUE_CLASS( TriGeometryRes ) : Tr2CmfContents m_cmfContents; bool m_useCMF; - int32_t m_forcedLodIndex = -1; - bool m_forceLod = false; - struct { Tr2RaycastGeometryResPtr geometry = nullptr; @@ -419,35 +117,30 @@ BLUE_CLASS( TriGeometryRes ) : private: // Provide the functions that do the actual work of loading and preparing. // The async management itself is done in TriAsyncLoadedResource. - virtual LoadingResult DoLoad(); - virtual bool DoPrepare(); + LoadingResult DoLoad() override; + bool DoPrepare() override; #if WITH_GRANNY // Read granny file, keep data in m_pGrannyFile bool ReadGrannyFile(); void ClearGrannyData(); - bool SetupMeshes( granny_file_info * gi ); - void SetupSkeletons( granny_file_info * gi ); - void DetermineAreaBoundsAndVertCount( TriGeometryResAreaData & area, granny_mesh * grannyMesh, int bytesPerVertex ); - bool IsAreaSkinned( TriGeometryResAreaData & area, granny_mesh * grannyMesh, granny_file_info * gi, int bytesPerVertex ); - bool IsAreaMorphed( TriGeometryResAreaData & area, granny_mesh * myMesh, granny_file_info * gi ); + bool SetupMeshesFromGranny( granny_file_info* gi ); + void SetupSkeletonsFromGranny( granny_file_info* gi ); + void DetermineAreaBoundsAndVertCount( TriGeometryResAreaData& area, granny_mesh* grannyMesh, int bytesPerVertex ); + bool IsAreaSkinned( TriGeometryResAreaData& area, granny_mesh* grannyMesh, granny_file_info* gi, int bytesPerVertex ); + bool IsAreaMorphed( TriGeometryResAreaData& area, granny_mesh* myMesh, granny_file_info* gi ); // Create D3D mesh from data in m_pGrannyFile - bool CreateMeshesFromGrannyFile( granny_file_info * gi, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext & renderContext ); - bool CreateLodFromGrannyMesh( granny_mesh * myMesh, TriGeometryResLodData * pMesh, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext & renderContext, void* pVBOverride = NULL ); + bool CreateMeshesFromGrannyFile( granny_file_info* gi, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ); + bool CreateLodFromGrannyMesh( granny_mesh* myMesh, TriGeometryResLodData* pMesh, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext, void* pVBOverride = NULL ); // Extract audio geometry from lowest LOD for Wwise spatial audio - void ExtractAudioGeometry( TriGeometryResMeshData * mesh, granny_mesh * grannyMesh ); + void ExtractAudioGeometry( TriGeometryResMeshData* mesh, granny_mesh* grannyMesh ); #endif bool ReadCMFFile(); void ClearCMFData(); - - bool SetupMeshes( const cmf::Data& cmfData ); - void SetupSkeletons( const cmf::Data& cmfData ); - bool CreateMeshesFromCMFFile( Tr2CmfContents & cmfContents, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext & renderContext ); - bool CreateLodFromCMFMesh( Tr2CmfContents & cmfContents, const cmf::Mesh& cmfMesh, const cmf::MeshLod& cmfMeshLod, TriGeometryResLodData* pMesh, Tr2CpuUsage::Type cpuUsage, Tr2PrimaryRenderContext& renderContext ); }; TYPEDEF_BLUECLASS_WR_SHUTDOWN( TriGeometryRes ); diff --git a/trinity/Resources/TriGeometryRes_Blue.cpp b/trinity/Resources/TriGeometryRes_Blue.cpp index 47e117678..1428f9354 100644 --- a/trinity/Resources/TriGeometryRes_Blue.cpp +++ b/trinity/Resources/TriGeometryRes_Blue.cpp @@ -43,189 +43,15 @@ const Be::ClassInfo* TriGeometryRes::ExposeToBlue() MAP_INTERFACE( ITr2InstanceData ) MAP_ICACHEABLE_METHODS() - MAP_PROPERTY_READONLY( - "meshCount", - GetMeshCount, - "Gets the count of meshes in this geometry resource" ) - MAP_PROPERTY_READONLY( - "animationCount", - GetAnimationCount, - "Gets the count of animations in this geometry resource" ) - MAP_ATTRIBUTE( "name", m_name, "Name for debugging/logging", Be::READWRITE ) - - MAP_ATTRIBUTE( "forceLod", m_forceLod, "Force geometry to use a specific LOD\n:jessica-group: Debug", Be::READWRITE ) - MAP_ATTRIBUTE( "forcedLodIndex", m_forcedLodIndex, "LOD index to use when forceLod is on\n:jessica-group: Debug", Be::READWRITE ) - - MAP_METHOD_AND_WRAP( - "GetMeshCount", - GetMeshCount, - "Gets the count of meshes" ) - - MAP_METHOD_AND_WRAP( - "GetMeshName", - GetMeshName, - "Gets the name of the indexed mesh\n" - ":param idx: mesh index" ) - - MAP_METHOD_AND_WRAP( - "GetMeshAreaCount", - GetMeshAreaCount, - "Gets the count of areas within the indexed mesh\n" - ":param idx: mesh index" ) - - MAP_METHOD_AND_WRAP( - "GetMeshAreaName", - GetMeshAreaName, - "Gets the name of the indexed area within the indexed mesh\n" - ":param meshIdx: mesh index\n" - ":param areaIdx: area index\n" ) - - MAP_METHOD_AND_WRAP( - "GetAreaBoundingBox", - GetAreaBoundingBoxFromScript, - "Get the bounding box of the specified area within the mesh\n" - ":param meshIdx: mesh index\n" - ":param areaIdx: area index\n" ) - - MAP_METHOD_AND_WRAP( - "GetBoundingBox", - GetBoundingBoxFromScript, - "Get the bounding box of the specified mesh\n" - ":param meshIdx: mesh index\n" ) - MAP_METHOD_AND_WRAP( - "GetBoundingSphere", - GetBoundingSphereFromScript, - "Get the bounding sphere of the specified area\n" - ":param meshIdx: mesh index\n" ) - MAP_METHOD_AND_WRAP( - "CalculateBoundingBoxFromTransform", - CalculateBoundingBoxFromTransform, - "Get the mesh's bounding box with all points transformed using transform.\n" - ":param meshIdx: mesh index\n" - ":param transform: transform matrix\n" ) - MAP_METHOD_AND_WRAP( - "RecalculateBoundingSphere", - RecalculateBoundingSphere, - "Rebuild bounding sphere info for these meshes" ) - MAP_METHOD_AND_WRAP( "Reload", Reload, "Forces a reload from disk" ) - MAP_METHOD_AND_WRAP( - "PrepareRayCaster", - PrepareRayCaster, - "To speed up raycasts, you can call this to load the asset asynchronously, and then poll IsRayCasterReady and HasRayCasterPreparationFailed, before attempting to call GetIntersection. Must be matched with exactly one call to ResetRayCaster!" ) - MAP_METHOD_AND_WRAP( - "ResetRayCaster", - ResetRayCaster, - "Call this after being done with GetIntersection! It will unload the asset. Must be matched with exactly one call to PrepareRayCaster!" ) - MAP_METHOD_AND_WRAP( - "IsRayCasterReady", - IsRayCasterReady, - "Call this after PrepareRayCaster to see if the asset has been loaded and is ready yet for raycasting." ) - MAP_METHOD_AND_WRAP( - "HasRayCasterPreparationFailed", - HasRayCasterPreparationFailed, - "Call this after PrepareRayCaster to see if the loading asset has failed." ) - - MAP_METHOD_AND_WRAP( - "GetIntersectionPointNormalBone", - GetIntersectionPointNormalBoneFromScript, - "( pos, dir ) ->( near )\nGet the near intersection (position, normal, bone index) between a ray and the geometry.\n" - ":param pos: ray origin\n" - ":param direction: ray direction\n" ) - - MAP_METHOD_AND_WRAP( - "GetAreaIntersectionPointNormalBone", - GetAreaIntersectionPointNormalBoneFromScript, - "( pos, dir, areaIx ) ->( near )\nGet the near intersection (position, normal, bone index) between a ray and the geometry.\n" - ":param pos: ray origin\n" - ":param direction: ray direction\n" - ":param areaIx: the mesh area index\n" ) - - MAP_METHOD_AND_WRAP( - "GetIntersectionPointNormalBoneColor", - GetIntersectionPointNormalBoneColorFromScript, - "( pos, dir ) ->( near )\nGet the near intersection (position, normal, bone index, color) between a ray and the geometry.\n" - ":param pos: ray origin\n" - ":param direction: ray direction\n" ) - - MAP_METHOD_AND_WRAP( - "GetAreaIntersectionPointNormalBoneColor", - GetAreaIntersectionPointNormalBoneColorFromScript, - "( pos, dir, areaIx ) ->( near )\nGet the near intersection (position, normal, bone index, color) between a ray and the geometry.\n" - ":param pos: ray origin\n" - ":param direction: ray direction\n" - ":param areaIx: the mesh area index\n" ) - - MAP_METHOD_AND_WRAP( - "GetMeshVertexElements", - GetMeshVertexElements, - "Returns a list of (usage, usage index) tuples for vertex layout of the specified mesh.\n" - ":param idx: mesh index" ) - - MAP_METHOD_AND_WRAP( - "SaveMesh", - SaveMesh, - "Save mesh data to a granny file\n" - ":param path: desctination file path\n" - ":param mesh: index of the mesh to save" ) - EXPOSURE_CHAINTO( BlueAsyncRes ) -} - - -namespace -{ -std::pair BoundingSphereFromPointsScript( const std::vector& points ) -{ - Vector4 sphere( 0, 0, 0, 0 ); - if( points.empty() ) - { - ::BoundingSphereFromPoints( sphere, nullptr, 0 ); - } - else - { - std::vector pointers; - pointers.reserve( points.size() ); - for( const auto& p : points ) - { - pointers.push_back( &p ); - } - ::BoundingSphereFromPoints( sphere, &pointers[0], points.size() ); - } - return std::make_pair( Vector3( sphere.x, sphere.y, sphere.z ), sphere.w ); -} -} - -MAP_FUNCTION_AND_WRAP( - "BoundingSphereFromPoints", - BoundingSphereFromPointsScript, - "Generates bounding sphere around a given points.\n" - ":param points: list of 3D points in space" ); - -#if BLUE_WITH_PYTHON -static PyObject* PySetAudioOcclusionGeometryEnabled( PyObject* self, PyObject* args ) -{ - bool enabled = false; - - if( !PyArg_ParseTuple( args, "b", &enabled ) ) - { - return NULL; - } - - g_eveIsAudioOcclusionGeometryEnabled = enabled; + MAP_PROPERTY_READONLY( + "animationCount", + GetAnimationCount, + "Gets the count of animations in this geometry resource" ) - Py_RETURN_NONE; + EXPOSURE_CHAINTO( Tr2GeometryProvider ) } - -MAP_FUNCTION( - "SetAudioOcclusionGeometryEnabled", - PySetAudioOcclusionGeometryEnabled, - "Enable or disable extraction of audio occlusion geometry.\n" - "Disabled by default.\n" - ":param enabled: enable/disable audio occlusion geometry\n" - ":type enabled: bool\n" - ":rtype: None" ); -#endif \ No newline at end of file diff --git a/trinity/Tr2BackgroundWorker.cpp b/trinity/Tr2BackgroundWorker.cpp new file mode 100644 index 000000000..cccaf30a8 --- /dev/null +++ b/trinity/Tr2BackgroundWorker.cpp @@ -0,0 +1,30 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" + +#include "Tr2BackgroundWorker.h" + + +namespace +{ +IBlueCallbackManPtr s_instance; +} + +IBlueCallbackMan& Tr2BackgroundWorker::Instance() +{ + if( !s_instance ) + { + Be::Clsid clsid; + if( BeClasses->FindClsid( clsid, "blue", "BlueCallbackMan" ) ) + { + BeClasses->CreateInstance( clsid, BlueInterfaceIID(), (void**)&s_instance ); + } + CCP_ASSERT( s_instance ); + + s_instance->SetName( "Tr2BackgroundWorker" ); + s_instance->SetThreadCount( 1 ); + s_instance->SetPriority( -1 ); + s_instance->Run(); + } + return *s_instance; +} diff --git a/trinity/Tr2BackgroundWorker.h b/trinity/Tr2BackgroundWorker.h new file mode 100644 index 000000000..6950e3448 --- /dev/null +++ b/trinity/Tr2BackgroundWorker.h @@ -0,0 +1,21 @@ +// Copyright © 2026 CCP ehf. + +#pragma once +#ifndef Tr2BackgroundWorker_h +#define Tr2BackgroundWorker_h + +#include + +// -------------------------------------------------------------------------------------- +// Description: +// Tr2BackgroundWorker is a small class to offer asynchronous execution of tasks. +// See Also: +// Tr2ProceduralGeometryProvider +// -------------------------------------------------------------------------------------- +class Tr2BackgroundWorker +{ +public: + static IBlueCallbackMan& Instance(); +}; + +#endif // Tr2BackgroundWorker_h diff --git a/trinity/Tr2InstancedMesh.cpp b/trinity/Tr2InstancedMesh.cpp index 3016a014f..3ec8b1e20 100644 --- a/trinity/Tr2InstancedMesh.cpp +++ b/trinity/Tr2InstancedMesh.cpp @@ -424,7 +424,7 @@ bool Tr2InstancedMesh::IsLoading() const // true On success // false Otherwise // -------------------------------------------------------------------------------------- -static bool GetMeshVertexDeclaration( TriGeometryRes* geometryRes, int meshIndex, Tr2VertexDefinition& vd ) +static bool GetMeshVertexDeclaration( Tr2GeometryProvider* geometryRes, int meshIndex, Tr2VertexDefinition& vd ) { if( geometryRes == nullptr || !geometryRes->IsGood() ) { diff --git a/trinity/Tr2Mesh.cpp b/trinity/Tr2Mesh.cpp index b203bc0f5..cbbc41c3a 100644 --- a/trinity/Tr2Mesh.cpp +++ b/trinity/Tr2Mesh.cpp @@ -205,12 +205,7 @@ void Tr2Mesh::PySetGeometryRes( TriGeometryRes* geometryRes ) SetGeometryRes( geometryRes ); } -int Tr2Mesh::GetAreasCount() const -{ - return TRIBATCHTYPE_COUNT_OF_BATCH_TYPES; -} - -TriGeometryRes* Tr2Mesh::GetGeometryResource() const +Tr2GeometryProvider* Tr2Mesh::GetGeometryResource() const { if( m_geometryResource && m_geometryResource->IsGood() ) { diff --git a/trinity/Tr2Mesh.h b/trinity/Tr2Mesh.h index c23e8a00f..b486d740f 100644 --- a/trinity/Tr2Mesh.h +++ b/trinity/Tr2Mesh.h @@ -8,6 +8,7 @@ #include "Resources/Tr2LoadPrepareFence.h" +BLUE_DECLARE( TriGeometryRes ); BLUE_DECLARE( Tr2SerializedMorphAnimation ); BLUE_CLASS( Tr2SerializedMorphAnimation ) : @@ -52,7 +53,7 @@ BLUE_CLASS( Tr2Mesh ) : const char* GetLowResMeshResPath() const; void SetLowResMeshResPath( const char* path ); - TriGeometryRes* GetGeometryResource() const override; + Tr2GeometryProvider* GetGeometryResource() const override; void SetGeometryRes( TriGeometryRes * res ); bool IsLoading() const override; @@ -85,7 +86,6 @@ BLUE_CLASS( Tr2Mesh ) : void InitializeMorphTargets(); void PySetGeometryRes( TriGeometryRes * geometryRes ); - int GetAreasCount() const; void SetLowResGeometryRes( TriGeometryRes * res ); private: diff --git a/trinity/Tr2MeshBase.cpp b/trinity/Tr2MeshBase.cpp index a76db3aa8..138919076 100644 --- a/trinity/Tr2MeshBase.cpp +++ b/trinity/Tr2MeshBase.cpp @@ -338,6 +338,38 @@ bool Tr2MeshBase::GetDisplay() const return m_display; } +std::vector* Tr2MeshBase::GetMorphTargetNames() const +{ + return nullptr; +} +bool Tr2MeshBase::IsBakedMorph( int index ) const +{ + return false; +} +void Tr2MeshBase::SetMorphTargetWeight( const char* name, float weight ) +{ +} +float Tr2MeshBase::GetMorphTargetWeight( const char* name ) +{ + return 0.f; +} +std::vector* Tr2MeshBase::GetAllBakedMorphTargetStates() const +{ + return nullptr; +} +void Tr2MeshBase::SetBakedMorphTarget( const char* name, bool isBaked ) +{ +} +bool Tr2MeshBase::GetBakedMorphTarget( const char* name ) +{ + return false; +} +const std::unordered_map& Tr2MeshBase::GetMorphAnimations() const +{ + static const std::unordered_map s_empty; + return s_empty; +} + Tr2RenderBatch CreateGeometryBatch( TriGeometryResLodData* lod, Tr2MeshArea* area, const Tr2PerObjectData* data, bool reverseWinding ) { Tr2RenderBatch batch; @@ -424,7 +456,7 @@ void Tr2MeshBase::GetBatches( ITriRenderBatchAccumulator* batches, // ------------------------------------------------------------- const wchar_t* Tr2MeshBase::GetGeometryResPath() const { - const TriGeometryRes* currentRes = GetGeometryResource(); + const Tr2GeometryProvider* currentRes = GetGeometryResource(); if( !currentRes ) { return L""; @@ -611,3 +643,8 @@ Tr2RaytracingMesh* Tr2MeshBase::GetRtMesh() const { return m_rtMesh.get(); } + +int Tr2MeshBase::GetAreasCount() const +{ + return TRIBATCHTYPE_COUNT_OF_BATCH_TYPES; +} diff --git a/trinity/Tr2MeshBase.h b/trinity/Tr2MeshBase.h index e665c62bf..28db985ef 100644 --- a/trinity/Tr2MeshBase.h +++ b/trinity/Tr2MeshBase.h @@ -12,7 +12,7 @@ #include "Tr2RingBuffer.h" -BLUE_DECLARE( TriGeometryRes ); +BLUE_DECLARE( Tr2GeometryProvider ); struct TriGeometryResSkeletonData; class ITriRenderBatchAccumulator; @@ -66,16 +66,16 @@ BLUE_CLASS( Tr2MeshBase ) : bool GetDisplay() const; - virtual TriGeometryRes* GetGeometryResource() const = 0; + virtual Tr2GeometryProvider* GetGeometryResource() const = 0; - virtual std::vector* GetMorphTargetNames() const = 0; - virtual bool IsBakedMorph( int index ) const = 0; - virtual void SetMorphTargetWeight( const char* name, float weight ) = 0; - virtual float GetMorphTargetWeight( const char* name ) = 0; - virtual std::vector* GetAllBakedMorphTargetStates() const = 0; - virtual void SetBakedMorphTarget( const char* name, bool isBaked ) = 0; - virtual bool GetBakedMorphTarget( const char* name ) = 0; - virtual const std::unordered_map& GetMorphAnimations() const = 0; + virtual std::vector* GetMorphTargetNames() const; + virtual bool IsBakedMorph( int index ) const; + virtual void SetMorphTargetWeight( const char* name, float weight ); + virtual float GetMorphTargetWeight( const char* name ); + virtual std::vector* GetAllBakedMorphTargetStates() const; + virtual void SetBakedMorphTarget( const char* name, bool isBaked ); + virtual bool GetBakedMorphTarget( const char* name ); + virtual const std::unordered_map& GetMorphAnimations() const; ///////////////////////////////////////////////////////////////////////////////////// // IListNotify @@ -100,6 +100,8 @@ BLUE_CLASS( Tr2MeshBase ) : void UseWithScreenSize( float screenSize, float worldRadius ) const; + int GetAreasCount() const; + protected: unsigned int FindJoint( const std::string* boneList, const int numBones, const char* name ) const; void CacheBounds(); diff --git a/trinity/Tr2MeshBase_Blue.cpp b/trinity/Tr2MeshBase_Blue.cpp index 2f4f9e1f1..8a31cf6ca 100644 --- a/trinity/Tr2MeshBase_Blue.cpp +++ b/trinity/Tr2MeshBase_Blue.cpp @@ -6,6 +6,39 @@ BLUE_DEFINE_ABSTRACT( Tr2MeshBase ); +#if BLUE_WITH_PYTHON + +static PyObject* PyGetAreas( PyObject* self, PyObject* args ) +{ + Tr2MeshBase* pThis = BluePythonCast( self ); + if( !pThis ) + { + return NULL; + } + + int areaIndex; + + if( !PyArg_ParseTuple( args, "i", &areaIndex ) ) + { + PyErr_SetString( PyExc_TypeError, "Arguments must be (i)." ); + return 0; + } + + TriBatchType type( static_cast( areaIndex ) ); + + Tr2MeshAreaVector* areas( pThis->GetAreas( type ) ); + + if( !areas ) + { + Py_RETURN_NONE; + } + + return PyOS->WrapBlueObject( areas->GetRawRoot() ); +} + +#endif + + const Be::ClassInfo* Tr2MeshBase::ExposeToBlue() { EXPOSURE_BEGIN( Tr2MeshBase, "" ) @@ -40,5 +73,20 @@ const Be::ClassInfo* Tr2MeshBase::ExposeToBlue() "Assigns material bounds adjustment parameters. Normally set by the publisher\n" ":param adjustment: adjustment values extracted from shaders" ) + MAP_PROPERTY_READONLY( + "isLoading", + IsLoading, + "If set, mesh resources might still be loading" ) + + MAP_METHOD( + "GetAreas", + PyGetAreas, + "Returns mesh area list\n" + ":param areaType: area type\n" + ":type areaType: int\n" + ":rtype: None | List" ); + + MAP_METHOD_AND_WRAP( "GetAreasCount", GetAreasCount, "" ); + EXPOSURE_END() } diff --git a/trinity/Tr2MeshProcedural.cpp b/trinity/Tr2MeshProcedural.cpp new file mode 100644 index 000000000..19b1d538b --- /dev/null +++ b/trinity/Tr2MeshProcedural.cpp @@ -0,0 +1,73 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2MeshProcedural.h" +#include "Resources/Tr2GeometryProvider.h" + + +Tr2MeshProcedural::Tr2MeshProcedural( IRoot* lockobj ) +{ +} + +Tr2MeshProcedural::~Tr2MeshProcedural() +{ + if( m_geometryResource ) + { + m_geometryResource->RemoveNotifyTarget( this ); + } +} + +Tr2ProceduralGeometryProvider* Tr2MeshProcedural::GetGeometry() +{ + return m_geometryResource; +} + +void Tr2MeshProcedural::SetGeometryResource( Tr2ProceduralGeometryProvider* res ) +{ + if( m_geometryResource ) + { + m_geometryResource->RemoveNotifyTarget( this ); + } + + m_geometryResource = res; + + if( m_geometryResource ) + { + m_geometryResource->AddNotifyTarget( this ); + } + + if( m_geometryResource && !m_geometryResource->IsLoading() && !m_geometryResource->IsPrepared() ) + { + m_geometryResource->Rebuild(); + } +} + +Tr2GeometryProvider* Tr2MeshProcedural::GetGeometryResource() const +{ + if( m_geometryResource && m_geometryResource->IsGood() ) + { + return m_geometryResource; + } + return nullptr; +} + +bool Tr2MeshProcedural::IsLoading() const +{ + return m_geometryResource && m_geometryResource->IsLoading(); +} + +void Tr2MeshProcedural::ReleaseCachedData( BlueAsyncRes* p ) +{ + if( p == m_geometryResource ) + { + CacheBounds(); + } +} + +void Tr2MeshProcedural::RebuildCachedData( BlueAsyncRes* p ) +{ + if( p == m_geometryResource ) + { + CacheBounds(); + } +} diff --git a/trinity/Tr2MeshProcedural.h b/trinity/Tr2MeshProcedural.h new file mode 100644 index 000000000..903e990eb --- /dev/null +++ b/trinity/Tr2MeshProcedural.h @@ -0,0 +1,38 @@ +// Copyright © 2026 CCP ehf. + +#pragma once +#ifndef Tr2MeshProcedural_h +#define Tr2MeshProcedural_h + +#include "Tr2MeshBase.h" +#include "Resources/Tr2ProceduralGeometryProvider.h" + +BLUE_CLASS_IMPL( Tr2MeshProcedural ) +class Tr2MeshProcedural : public Tr2MeshBase, + public IBlueAsyncResNotifyTarget +{ +public: + EXPOSE_TO_BLUE(); + + Tr2MeshProcedural( IRoot* lockobj = NULL ); + ~Tr2MeshProcedural(); + + Tr2GeometryProvider* GetGeometryResource() const override; + Tr2ProceduralGeometryProvider* GetGeometry(); + void SetGeometryResource( Tr2ProceduralGeometryProvider* res ); + + bool IsLoading() const override; + + ///////////////////////////////////////////////////////////////////////////////////// + // IBlueAsyncResNotifyTarget + void ReleaseCachedData( BlueAsyncRes* p ) override; + void RebuildCachedData( BlueAsyncRes* p ) override; + +private: + Tr2ProceduralGeometryProviderPtr m_geometryResource; +}; + +TYPEDEF_BLUECLASS( Tr2MeshProcedural ); +BLUE_DECLARE_VECTOR( Tr2MeshProcedural ); + +#endif diff --git a/trinity/Tr2MeshProcedural_Blue.cpp b/trinity/Tr2MeshProcedural_Blue.cpp new file mode 100644 index 000000000..bb532e4d3 --- /dev/null +++ b/trinity/Tr2MeshProcedural_Blue.cpp @@ -0,0 +1,23 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" + +#include "Tr2MeshProcedural.h" + +#include "Resources/Tr2GeometryProvider.h" +#include "TriConstants.h" + + +BLUE_DEFINE( Tr2MeshProcedural ); + +const Be::ClassInfo* Tr2MeshProcedural::ExposeToBlue() +{ + EXPOSURE_BEGIN( Tr2MeshProcedural, "" ) + + MAP_INTERFACE( Tr2MeshProcedural ) + + MAP_ATTRIBUTE( "geometry", m_geometryResource, "", Be::HIDDEN ) // Needed to make Graphite able to detect bindings inside of the geometry + MAP_PROPERTY( "geometry", GetGeometry, SetGeometryResource, "Procedural geometry" ) + + EXPOSURE_CHAINTO( Tr2MeshBase ) +} diff --git a/trinity/Tr2Mesh_Blue.cpp b/trinity/Tr2Mesh_Blue.cpp index b7505ddef..ebde2a8e4 100644 --- a/trinity/Tr2Mesh_Blue.cpp +++ b/trinity/Tr2Mesh_Blue.cpp @@ -8,7 +8,6 @@ #include "TriConstants.h" - BLUE_DEFINE( Tr2SerializedMorphAnimation ); const Be::ClassInfo* Tr2SerializedMorphAnimation::ExposeToBlue(){ EXPOSURE_BEGIN( Tr2SerializedMorphAnimation, "" ) @@ -21,36 +20,6 @@ const Be::ClassInfo* Tr2SerializedMorphAnimation::ExposeToBlue(){ BLUE_DEFINE( Tr2Mesh ); -#if BLUE_WITH_PYTHON - -static PyObject* PyGetAreas( PyObject* self, PyObject* args ) -{ - Tr2Mesh* pThis = BluePythonCast( self ); - if( !pThis ) - { - return NULL; - } - - int areaIndex; - - if( !PyArg_ParseTuple( args, "i", &areaIndex ) ) - { - PyErr_SetString( PyExc_TypeError, "Arguments must be (i)." ); - return 0; - } - - TriBatchType type( static_cast( areaIndex ) ); - - Tr2MeshAreaVector* areas( pThis->GetAreas( type ) ); - - if( !areas ) - { - Py_RETURN_NONE; - } - - return PyOS->WrapBlueObject( areas->GetRawRoot() ); -} -#endif const Be::ClassInfo* Tr2Mesh::ExposeToBlue() { @@ -68,10 +37,6 @@ const Be::ClassInfo* Tr2Mesh::ExposeToBlue() Be::READWRITE | Be::NOTIFY | Be::PERSIST, TriGR2Chooser ) MAP_ATTRIBUTE( "deferGeometryLoad", m_deferGeometryLoad, "Defers geometry load when loading mesh objects", Be::READWRITE | Be::NOTIFY | Be::PERSIST ) - MAP_PROPERTY_READONLY( - "isLoading", - IsLoading, - "If set, mesh resources might still be loading" ) MAP_METHOD_AND_WRAP( "SetGeometryRes", @@ -80,15 +45,6 @@ const Be::ClassInfo* Tr2Mesh::ExposeToBlue() "This is used for geometry resources that require special handling, such as pre-baked blendshapes.\n" ":param geometry: new geometry resource" ) - MAP_METHOD( - "GetAreas", - PyGetAreas, - "Returns mesh area list\n" - ":param areaType: area type\n" - ":type areaType: int\n" - ":rtype: None | List" ); - MAP_METHOD_AND_WRAP( "GetAreasCount", GetAreasCount, "" ); - MAP_ATTRIBUTE( "serializedMorphAnimations", m_serializedMorphAnimations, "", Be::PERSISTONLY ) EXPOSURE_CHAINTO( Tr2MeshBase ) diff --git a/trinity/Tr2Model.cpp b/trinity/Tr2Model.cpp index 68f53a7f3..399d6c9aa 100644 --- a/trinity/Tr2Model.cpp +++ b/trinity/Tr2Model.cpp @@ -167,7 +167,7 @@ void Tr2Model::GetBatchesFromMesh( Tr2Mesh* mesh, return; } - TriGeometryRes* geomRes = mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return; diff --git a/trinity/Tr2SkinnedModel.cpp b/trinity/Tr2SkinnedModel.cpp index 0decae824..215c656bf 100644 --- a/trinity/Tr2SkinnedModel.cpp +++ b/trinity/Tr2SkinnedModel.cpp @@ -36,7 +36,7 @@ void Tr2SkinnedModel::GetBatchesForArea( Tr2MeshAreaVector* areas, Tr2Mesh* mesh return; } - TriGeometryRes* geomRes = mesh->GetGeometryResource(); + Tr2GeometryProvider* geomRes = mesh->GetGeometryResource(); if( !geomRes || !geomRes->IsGood() ) { return; From c32ae01d7b111addcc6f7abe36601c72f5743cc4 Mon Sep 17 00:00:00 2001 From: filipppavlov Date: Tue, 27 Oct 2026 10:29:41 +0000 Subject: [PATCH 3/4] Add a voxel data container (#133) * Tr2VoxelData wip * Simplify interface * Add persistence * Async read support * Add some comments * Add "on data changed" event; address PR comments --- trinity/CMakeLists.txt | 3 + trinity/Tr2VoxelData.cpp | 752 +++++++++++++++++++ trinity/Tr2VoxelData.h | 215 ++++++ trinity/Tr2VoxelData_Blue.cpp | 90 +++ trinity/tests/Python/tests/test_voxeldata.py | 111 +++ 5 files changed, 1171 insertions(+) create mode 100644 trinity/Tr2VoxelData.cpp create mode 100644 trinity/Tr2VoxelData.h create mode 100644 trinity/Tr2VoxelData_Blue.cpp create mode 100644 trinity/tests/Python/tests/test_voxeldata.py diff --git a/trinity/CMakeLists.txt b/trinity/CMakeLists.txt index 99e9d638d..eed7176d6 100644 --- a/trinity/CMakeLists.txt +++ b/trinity/CMakeLists.txt @@ -1757,6 +1757,9 @@ set(_SOURCES Tr2VolumetricsRenderer.cpp Tr2VolumetricsRenderer.h Tr2VolumetricsRenderer_Blue.cpp + Tr2VoxelData.cpp + Tr2VoxelData.h + Tr2VoxelData_Blue.cpp TriColor.cpp TriColor_Blue.cpp TriConstants.cpp diff --git a/trinity/Tr2VoxelData.cpp b/trinity/Tr2VoxelData.cpp new file mode 100644 index 000000000..fa930995f --- /dev/null +++ b/trinity/Tr2VoxelData.cpp @@ -0,0 +1,752 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2VoxelData.h" +#include + + +namespace +{ + +template +struct PythonStorageType +{ + using type = T; +}; + +template <> +struct PythonStorageType +{ + using type = float; +}; + +/** + * @brief Extracts a single payload element from a Python object. The payload is either a scalar (if elementCount is 1) or a tuple of scalars. + * @tparam T Destination type of the payload element. + * @param pp The Python object containing a payload element representation. + * @param elementCount The number of elements in the payload. + * @param payload The array to store the extracted payload elements. + * @return True if the extraction was successful, false otherwise. + */ +template +bool ExtractPayloadElement( PyObject* pp, uint8_t elementCount, std::array& payload ) +{ + if( elementCount > 1 && ( !PyTuple_Check( pp ) || PyTuple_Size( pp ) != elementCount ) ) + { + PyErr_Format( PyExc_TypeError, "Payload must be a %u-tuple", elementCount ); + return false; + } + for( size_t j = 0; j < elementCount; ++j ) + { + // BlueExtractArgument does not compile for int16_t and int8_t, so we need to extract as int32_t and cast + if constexpr( std::is_same_v || std::is_same_v ) + { + int32_t tmp = 0; + if( !BlueExtractArgument( elementCount == 1 ? pp : PyTuple_GetItem( pp, Py_ssize_t( j ) ), tmp, static_cast( j ) ) ) + { + PyErr_Format( PyExc_TypeError, "Payload must be a tuple of %u unsigned ints", elementCount ); + return false; + } + payload[j] = static_cast( tmp ); + } + else + { + if( !BlueExtractArgument( elementCount == 1 ? pp : PyTuple_GetItem( pp, Py_ssize_t( j ) ), payload[j], static_cast( j ) ) ) + { + PyErr_Format( PyExc_TypeError, "Payload must be a tuple of %u unsigned ints", elementCount ); + return false; + } + } + } + return true; +} + +template +float NormalizedValue( T value ) +{ + if constexpr( std::is_signed_v ) + { + return float( std::max( float( value ) / float( std::numeric_limits::max() ), -1.0f ) ); + } + else + { + return float( value ) / float( std::numeric_limits::max() ); + } +} + +template +T DenormalizedValue( float value ) +{ + if constexpr( std::is_signed_v ) + { + return T( std::round( std::clamp( float( value ), -1.0f, 1.0f ) * float( std::numeric_limits::max() ) ) ); + } + else + { + return T( std::round( std::clamp( float( value ), 0.0f, 1.0f ) * float( std::numeric_limits::max() ) ) ); + } +} + +/** + * @brief Converts a payload element from the internal storage format to a Python object. The payload is either a scalar (if elementCount is 1) or a tuple of scalars. + * @tparam T Underlying storage type of the payload element. + * @param dimensions The dimensions of the voxel data. + * @param payloadType The type of the payload. + * @param payloadData The raw payload data. + * @param coord The coordinates of the voxel. + * @return A Python object representing the payload element. + */ +template +BluePy WrapPayloadElement( const Tr2VoxelData::Dimensions& dimensions, const Tr2VoxelData::PayloadType& payloadType, const uint8_t* payloadData, const Tr2VoxelData::Coord& coord ) +{ + std::array::type, 4> p = {}; + size_t index = coord.x + dimensions.width * ( coord.y + dimensions.height * coord.z ); + auto bytesPerPixel = payloadType.GetBytesPerValue(); + for( size_t j = 0; j < payloadType.elementCount; ++j ) + { + if constexpr( std::is_same_v ) + { + auto byte = reinterpret_cast( payloadData + ( index * payloadType.elementCount + j ) / 8 ); + p[j] = ( *byte & ( 1 << ( ( index * payloadType.elementCount + j ) % 8 ) ) ) != 0; + } + else + { + p[j] = reinterpret_cast( payloadData + index * bytesPerPixel )[j]; + } + } + + if( payloadType.normalized ) + { + if( payloadType.elementCount == 1 ) + { + // 1-element payloads are returned as scalars, not tuples + return BluePy( BlueWrapReturnValue( {}, NormalizedValue( p[0] ) ) ); + } + PyObject* tuple = PyTuple_New( payloadType.elementCount ); + for( size_t j = 0; j < payloadType.elementCount; ++j ) + { + PyTuple_SetItem( tuple, Py_ssize_t( j ), BlueWrapReturnValue( {}, NormalizedValue( p[j] ) ) ); + } + return BluePy( tuple ); + } + if( payloadType.elementCount == 1 ) + { + // 1-element payloads are returned as scalars, not tuples + return BluePy( BlueWrapReturnValue( {}, p[0] ) ); + } + PyObject* tuple = PyTuple_New( payloadType.elementCount ); + for( size_t j = 0; j < payloadType.elementCount; ++j ) + { + PyTuple_SetItem( tuple, j, BlueWrapReturnValue( {}, p[j] ) ); + } + return BluePy( tuple ); +} + +} + + +bool Tr2VoxelData::PayloadType::IsValid() const +{ + if( elementCount == 0 || elementCount > 4 ) + { + return false; + } + if( normalized && ( type == Tr2VoxelDataType::Float || type == Tr2VoxelDataType::Float16 || type == Tr2VoxelDataType::Boolean ) ) + { + return false; + } + return true; +} + +size_t Tr2VoxelData::PayloadType::GetBytesPerValue() const +{ + switch( type ) + { + case Tr2VoxelDataType::Boolean: + case Tr2VoxelDataType::UInt8: + case Tr2VoxelDataType::Int8: + return elementCount; + case Tr2VoxelDataType::UInt16: + case Tr2VoxelDataType::Int16: + case Tr2VoxelDataType::Float16: + return 2 * size_t( elementCount ); + case Tr2VoxelDataType::UInt32: + case Tr2VoxelDataType::Int32: + case Tr2VoxelDataType::Float: + return 4 * size_t( elementCount ); + default: + return 0; + } +} + +size_t Tr2VoxelData::PayloadType::GetStoreSize( const Dimensions& dimensions ) const +{ + if( type == Tr2VoxelDataType::Boolean ) + { + return ( dimensions.width * dimensions.height * dimensions.depth * elementCount + 7 ) / 8; + } + return dimensions.width * dimensions.height * dimensions.depth * GetBytesPerValue(); +} + + +Tr2VoxelData::PayloadSnapshot::PayloadSnapshot( PayloadSnapshot&& other ) noexcept : + m_voxelData( other.m_voxelData ), + m_type( other.m_type ), + m_channel( other.m_channel ), + m_data( other.m_data ) +{ + other.m_voxelData = nullptr; + other.m_type = {}; + other.m_channel = 0; + other.m_data = nullptr; +} + +Tr2VoxelData::PayloadSnapshot::~PayloadSnapshot() +{ + if( m_voxelData ) + { + std::lock_guard guard( m_voxelData->m_payloadMutex ); + + auto& payload = m_voxelData->m_payload[m_channel]; + if( payload.current.data.data() == m_data ) + { + CCP_ASSERT( payload.current.readerCount > 0 ); + --payload.current.readerCount; + return; + } + auto found = std::find_if( payload.old.begin(), payload.old.end(), [this]( const Snapshot& s ) { return s.data.data() == m_data; } ); + if( found != payload.old.end() ) + { + CCP_ASSERT( found->readerCount > 0 ); + --found->readerCount; + if( found->readerCount == 0 ) + { + payload.old.erase( found ); + } + return; + } + CCP_ASSERT_M( false, "PayloadSnapshot destructor called with invalid data pointer" ); + } +} + +Tr2VoxelData::PayloadSnapshot& Tr2VoxelData::PayloadSnapshot::operator=( PayloadSnapshot&& other ) noexcept +{ + if( this != &other ) + { + m_voxelData = other.m_voxelData; + m_type = other.m_type; + m_channel = other.m_channel; + m_data = other.m_data; + + other.m_voxelData = nullptr; + other.m_type = {}; + other.m_channel = 0; + other.m_data = nullptr; + } + return *this; +} + + +bool Tr2VoxelData::ModifiedChannels::IsChannelModified( size_t channelIndex ) const +{ + return ( m_modifiedChannelsBitmask & ( 1ull << channelIndex ) ) != 0; +} + +void Tr2VoxelData::ModifiedChannels::MarkChannelModified( size_t channelIndex ) +{ + m_modifiedChannelsBitmask |= ( 1ull << channelIndex ); +} + + + +Tr2VoxelData::Tr2VoxelData( IRoot* ) +{ +} + +BlueStdResult Tr2VoxelData::Create( const Dimensions& dimensions, std::vector payload ) +{ + if( m_dimensions.width != 0 || m_dimensions.height != 0 || m_dimensions.depth != 0 ) + { + return BlueStdResult( BLUE_STD_RESULT_VALUE_ERROR, "Voxel data has already been created" ); + } + if( dimensions.width == 0 || dimensions.height == 0 || dimensions.depth == 0 ) + { + return BlueStdResult( BLUE_STD_RESULT_VALUE_ERROR, "Invalid dimensions" ); + } + if( payload.empty() ) + { + return BlueStdResult( BLUE_STD_RESULT_VALUE_ERROR, "Payload cannot be empty" ); + } + for( auto& format : payload ) + { + if( !format.IsValid() ) + { + return BlueStdResult( BLUE_STD_RESULT_VALUE_ERROR, "Invalid payload definition" ); + } + } + + m_dimensions = dimensions; + m_payload.clear(); + for( auto& format : payload ) + { + m_payload.emplace_back( Payload{ format, { std::vector( format.GetStoreSize( dimensions ) ) } } ); + } + return {}; +} + +bool Tr2VoxelData::IsWithinBounds( const Coord& coord ) const +{ + return coord.x >= 0 && coord.x < CoordType( m_dimensions.width ) && coord.y >= 0 && coord.y < CoordType( m_dimensions.height ) && coord.z >= 0 && coord.z < CoordType( m_dimensions.depth ); +} + + +Tr2VoxelData::Dimensions Tr2VoxelData::GetDimensions() const +{ + return m_dimensions; +} + +size_t Tr2VoxelData::GetPayloadChannelCount() const +{ + return m_payload.size(); +} + +const Tr2VoxelData::PayloadType& Tr2VoxelData::GetPayloadType( size_t index ) const +{ + return m_payload[index].type; +} + + +Tr2VoxelData::PayloadSnapshot Tr2VoxelData::GetPayloadSnapshot( size_t index ) +{ + std::lock_guard guard( m_payloadMutex ); + PayloadSnapshot snapshot; + if( index < m_payload.size() ) + { + snapshot.m_voxelData = this; + snapshot.m_type = m_payload[index].type; + snapshot.m_channel = index; + snapshot.m_data = m_payload[index].current.data.data(); + ++m_payload[index].current.readerCount; + } + return snapshot; +} + + +BluePy Tr2VoxelData::SetVoxels( PyObject* set ) +{ + auto setIter = BluePy( PyObject_GetIter( set ), true ); + if( !setIter ) + { + PyErr_SetString( PyExc_TypeError, "SetVoxels the first argument must be a sequence of tuples of the form ((x, y, z), p0, p1,...)" ); + return {}; + } + + { + std::lock_guard guard( m_payloadMutex ); + + for( auto& payload : m_payload ) + { + if( payload.current.readerCount ) + { + auto& snapshot = payload.old.emplace_back(); + snapshot.readerCount = payload.current.readerCount; + std::swap( snapshot.data, payload.current.data ); + payload.current.data.insert( payload.current.data.begin(), snapshot.data.begin(), snapshot.data.end() ); + payload.current.readerCount = 0; + } + } + } + + ModifiedChannels modifiedChannels; + + while( BluePy element = BluePy( PyIter_Next( setIter ) ) ) + { + if( !element || !PyTuple_Check( element ) || PyTuple_Size( element ) != 1 + m_payload.size() ) + { + PyErr_SetString( PyExc_TypeError, "SetVoxels the first argument must be a sequence of tuples of the form ((x, y, z), p0, p1,...)" ); + return {}; + } + Coord coord = {}; + if( !BlueExtractArgument( PyTuple_GET_ITEM( element.o, 0 ), coord, 1 ) ) + { + PyErr_SetString( PyExc_TypeError, "SetVoxels the first argument must be a sequence of tuples of the form ((x, y, z), p0, p1,...)" ); + return {}; + } + if( !IsWithinBounds( coord ) ) + { + PyErr_SetString( PyExc_ValueError, "SetVoxels coordinates out of bounds" ); + return {}; + } + if( !ExtractPayload( element, coord, modifiedChannels ) ) + { + return {}; + } + } + + m_onDataChanged.Broadcast( modifiedChannels ); + + return BluePy( Py_None, true ); +} + + +BluePy Tr2VoxelData::PyGetPayloadData( size_t index, const Coord& coord ) +{ + if( index >= m_payload.size() ) + { + PyErr_SetString( PyExc_IndexError, "Payload index out of range" ); + return {}; + } + if( !IsWithinBounds( coord ) ) + { + PyErr_SetString( PyExc_ValueError, "Coordinates out of bounds" ); + return {}; + } + + const auto& payload = m_payload[index]; + switch( payload.type.type ) + { + case Tr2VoxelDataType::Boolean: + return WrapPayloadElement( m_dimensions, payload.type, payload.current.data.data(), coord ); + case Tr2VoxelDataType::Float: + return WrapPayloadElement( m_dimensions, payload.type, payload.current.data.data(), coord ); + case Tr2VoxelDataType::Float16: + return WrapPayloadElement( m_dimensions, payload.type, payload.current.data.data(), coord ); + case Tr2VoxelDataType::UInt8: + return WrapPayloadElement( m_dimensions, payload.type, payload.current.data.data(), coord ); + case Tr2VoxelDataType::UInt16: + return WrapPayloadElement( m_dimensions, payload.type, payload.current.data.data(), coord ); + case Tr2VoxelDataType::UInt32: + return WrapPayloadElement( m_dimensions, payload.type, payload.current.data.data(), coord ); + case Tr2VoxelDataType::Int8: + return WrapPayloadElement( m_dimensions, payload.type, payload.current.data.data(), coord ); + case Tr2VoxelDataType::Int16: + return WrapPayloadElement( m_dimensions, payload.type, payload.current.data.data(), coord ); + case Tr2VoxelDataType::Int32: + return WrapPayloadElement( m_dimensions, payload.type, payload.current.data.data(), coord ); + default: + return {}; + } +} + +template +bool Tr2VoxelData::ExtractPayloadElement( PyObject* element, size_t index, const PayloadType& payloadType, const Coord& coord, ModifiedChannels& modifiedChannels ) +{ + if( payloadType.normalized ) + { + return ExtractNormalizedPayloadElement( element, index, payloadType.elementCount, coord, modifiedChannels ); + } + std::array::type, 4> p; + // The 1+ here is because the first element of the tuple is the coordinate, and the payloads start at index 1 + if( !::ExtractPayloadElement( PyTuple_GetItem( element, Py_ssize_t( 1 + index ) ), payloadType.elementCount, p ) ) + { + return false; + } + size_t coordIndex = coord.x + m_dimensions.width * ( coord.y + m_dimensions.height * coord.z ); + auto& dest = m_payload[index]; + auto bytesPerPixel = dest.type.GetBytesPerValue(); + for( size_t j = 0; j < payloadType.elementCount; ++j ) + { + if constexpr( std::is_same_v ) + { + auto& byte = *reinterpret_cast( dest.current.data.data() + ( coordIndex * payloadType.elementCount + j ) / 8 ); + auto oldByte = byte; + if( p[j] ) + { + byte |= 1 << ( ( coordIndex * payloadType.elementCount + j ) % 8 ); + } + else + { + byte &= ~( 1 << ( ( coordIndex * payloadType.elementCount + j ) % 8 ) ); + } + if( oldByte != byte ) + { + modifiedChannels.MarkChannelModified( index ); + } + } + else + { + auto& destElement = reinterpret_cast( dest.current.data.data() + coordIndex * bytesPerPixel )[j]; + if( destElement != static_cast( p[j] ) ) + { + modifiedChannels.MarkChannelModified( index ); + } + destElement = static_cast( p[j] ); + } + } + return true; +} + +template +bool Tr2VoxelData::ExtractNormalizedPayloadElement( PyObject* element, size_t index, uint8_t elementCount, const Coord& coord, ModifiedChannels& modifiedChannels ) +{ + std::array p; + // The 1+ here is because the first element of the tuple is the coordinate, and the payloads start at index 1 + if( !::ExtractPayloadElement( PyTuple_GetItem( element, Py_ssize_t( 1 + index ) ), elementCount, p ) ) + { + return false; + } + auto coordIndex = coord.x + m_dimensions.width * ( coord.y + m_dimensions.height * coord.z ); + auto& dest = m_payload[index]; + auto bytesPerPixel = dest.type.GetBytesPerValue(); + for( size_t j = 0; j < elementCount; ++j ) + { + auto newValue = DenormalizedValue( p[j] ); + auto& dstElement = reinterpret_cast( dest.current.data.data() + coordIndex * bytesPerPixel )[j]; + if( dstElement != newValue ) + { + modifiedChannels.MarkChannelModified( index ); + } + dstElement = newValue; + } + return true; +} + +bool Tr2VoxelData::ExtractPayload( PyObject* element, const Coord& coord, ModifiedChannels& modifiedChannels ) +{ + for( size_t i = 0; i < m_payload.size(); ++i ) + { + switch( m_payload[i].type.type ) + { + case Tr2VoxelDataType::Boolean: + if( !ExtractPayloadElement( element, i, m_payload[i].type, coord, modifiedChannels ) ) + { + return false; + } + break; + case Tr2VoxelDataType::Float: + if( !ExtractPayloadElement( element, i, m_payload[i].type, coord, modifiedChannels ) ) + { + return false; + } + break; + case Tr2VoxelDataType::Float16: + if( !ExtractPayloadElement( element, i, m_payload[i].type, coord, modifiedChannels ) ) + { + return false; + } + break; + case Tr2VoxelDataType::UInt8: + if( !ExtractPayloadElement( element, i, m_payload[i].type, coord, modifiedChannels ) ) + { + return false; + } + break; + case Tr2VoxelDataType::UInt16: + if( !ExtractPayloadElement( element, i, m_payload[i].type, coord, modifiedChannels ) ) + { + return false; + } + break; + case Tr2VoxelDataType::UInt32: + if( !ExtractPayloadElement( element, i, m_payload[i].type, coord, modifiedChannels ) ) + { + return false; + } + break; + case Tr2VoxelDataType::Int8: + if( !ExtractPayloadElement( element, i, m_payload[i].type, coord, modifiedChannels ) ) + { + return false; + } + break; + case Tr2VoxelDataType::Int16: + if( !ExtractPayloadElement( element, i, m_payload[i].type, coord, modifiedChannels ) ) + { + return false; + } + break; + case Tr2VoxelDataType::Int32: + if( !ExtractPayloadElement( element, i, m_payload[i].type, coord, modifiedChannels ) ) + { + return false; + } + break; + default: + return false; + } + } + return true; +} + +void Tr2VoxelData::GetWriteBufferAndSize( const char*, unsigned char** buffer, size_t* bufferSize ) +{ + *bufferSize = std::accumulate( m_payload.begin(), m_payload.end(), sizeof( PayloadType ) * m_payload.size(), []( size_t sum, const Payload& p ) { return sum + p.current.data.size(); } ); + uint8_t* buf = new uint8_t[*bufferSize]; + if( !buf ) + { + *bufferSize = 0; + return; + } + *buffer = buf; + for( const auto& p : m_payload ) + { + new( buf ) PayloadType( p.type ); + buf += sizeof( PayloadType ); + memcpy( buf, p.current.data.data(), p.current.data.size() ); + buf += p.current.data.size(); + } +} + +void Tr2VoxelData::ReleaseWriteBuffer( unsigned char* buffer ) +{ + delete[] buffer; +} + +unsigned char* Tr2VoxelData::AllocateReadBuffer( const char*, size_t bufferSize ) +{ + return new uint8_t[bufferSize]; +} + +void Tr2VoxelData::SetBufferAndSize( const char*, unsigned char* buffer, size_t bufferSize ) +{ + auto initialBuffer = buffer; + + while( true ) + { + if( bufferSize < sizeof( PayloadType ) ) + { + break; + } + PayloadType payloadType = *reinterpret_cast( buffer ); + buffer += sizeof( PayloadType ); + bufferSize -= sizeof( PayloadType ); + size_t payloadSize = payloadType.GetStoreSize( m_dimensions ); + if( bufferSize < payloadSize ) + { + break; + } + m_payload.emplace_back( Payload{ payloadType, { std::vector( buffer, buffer + payloadSize ) } } ); + buffer += payloadSize; + bufferSize -= payloadSize; + } + delete[] initialBuffer; +} + + +bool BlueExtractArgument( BlueScriptValue argument, Tr2VoxelData::Dimensions& resultRef, unsigned int argID ) +{ + if( !PyTuple_Check( argument ) ) + { + PyErr_Format( PyExc_TypeError, "Could not match argument %i to expected type: tuple expected.", argID ); + return false; + } + if( PyTuple_Size( argument ) != 3 ) + { + PyErr_Format( PyExc_TypeError, "Could not match argument %i to expected type: tuple with three elements expected.", argID ); + return false; + } + + uint32_t x; + if( !BlueExtractArgument( PyTuple_GET_ITEM( argument, 0 ), x, 0 ) ) + { + return false; + } + uint32_t y; + if( !BlueExtractArgument( PyTuple_GET_ITEM( argument, 1 ), y, 1 ) ) + { + return false; + } + uint32_t z; + if( !BlueExtractArgument( PyTuple_GET_ITEM( argument, 2 ), z, 2 ) ) + { + return false; + } + + resultRef = Tr2VoxelData::Dimensions{ x, y, z }; + return true; +} + +bool BlueExtractArgument( BlueScriptValue argument, Tr2VoxelData::Coord& resultRef, unsigned int argID ) +{ + if( !PyTuple_Check( argument ) ) + { + PyErr_Format( PyExc_TypeError, "Could not match argument %i to expected type: tuple expected.", argID ); + return false; + } + if( PyTuple_Size( argument ) != 3 ) + { + PyErr_Format( PyExc_TypeError, "Could not match argument %i to expected type: tuple with three elements expected.", argID ); + return false; + } + + Tr2VoxelData::CoordType x; + if( !BlueExtractArgument( PyTuple_GET_ITEM( argument, 0 ), x, 0 ) ) + { + return false; + } + Tr2VoxelData::CoordType y; + if( !BlueExtractArgument( PyTuple_GET_ITEM( argument, 1 ), y, 1 ) ) + { + return false; + } + Tr2VoxelData::CoordType z; + if( !BlueExtractArgument( PyTuple_GET_ITEM( argument, 2 ), z, 2 ) ) + { + return false; + } + + resultRef = Tr2VoxelData::Coord{ x, y, z }; + return true; +} + +bool BlueExtractArgument( BlueScriptValue argument, Tr2VoxelData::PayloadType& resultRef, unsigned int argID ) +{ + if( !argument || !PyDict_Check( argument ) ) + { + PyErr_Format( PyExc_TypeError, "Could not match argument %i to expected type: payload definition expected.", argID ); + return false; + } + Tr2VoxelData::PayloadType pt; + if( !BlueExtractArgument( BluePy( PyDict_GetItemString( argument, "type" ) ), pt.type, 0 ) || + !BlueExtractArgument( BluePy( PyDict_GetItemString( argument, "elementCount" ) ), pt.elementCount, 1 ) || + !BlueExtractArgument( BluePy( PyDict_GetItemString( argument, "normalized" ) ), pt.normalized, 2 ) ) + { + PyErr_Format( PyExc_TypeError, "Could not match argument %i to expected type: payload definition expected.", argID ); + return false; + } + resultRef = pt; + return true; +} + +BlueStdResult Tr2VoxelDataTestAccessor::Create( Tr2VoxelData* voxelData, size_t channelIndex ) +{ + m_snapshot = voxelData->GetPayloadSnapshot( channelIndex ); + if( m_snapshot.m_data == nullptr ) + { + return BlueStdResult( BLUE_STD_RESULT_VALUE_ERROR, "Invalid channel index" ); + } + return {}; +} + +BluePy Tr2VoxelDataTestAccessor::PyGetPayloadData( const Tr2VoxelData::Coord& coord ) const +{ + if( !m_snapshot.m_voxelData->IsWithinBounds( coord ) ) + { + PyErr_SetString( PyExc_ValueError, "Coordinates out of bounds" ); + return {}; + } + + switch( m_snapshot.m_type.type ) + { + case Tr2VoxelDataType::Boolean: + return WrapPayloadElement( m_snapshot.m_voxelData->GetDimensions(), m_snapshot.m_type, m_snapshot.m_data, coord ); + case Tr2VoxelDataType::Float: + return WrapPayloadElement( m_snapshot.m_voxelData->GetDimensions(), m_snapshot.m_type, m_snapshot.m_data, coord ); + case Tr2VoxelDataType::Float16: + return WrapPayloadElement( m_snapshot.m_voxelData->GetDimensions(), m_snapshot.m_type, m_snapshot.m_data, coord ); + case Tr2VoxelDataType::UInt8: + return WrapPayloadElement( m_snapshot.m_voxelData->GetDimensions(), m_snapshot.m_type, m_snapshot.m_data, coord ); + case Tr2VoxelDataType::UInt16: + return WrapPayloadElement( m_snapshot.m_voxelData->GetDimensions(), m_snapshot.m_type, m_snapshot.m_data, coord ); + case Tr2VoxelDataType::UInt32: + return WrapPayloadElement( m_snapshot.m_voxelData->GetDimensions(), m_snapshot.m_type, m_snapshot.m_data, coord ); + case Tr2VoxelDataType::Int8: + return WrapPayloadElement( m_snapshot.m_voxelData->GetDimensions(), m_snapshot.m_type, m_snapshot.m_data, coord ); + case Tr2VoxelDataType::Int16: + return WrapPayloadElement( m_snapshot.m_voxelData->GetDimensions(), m_snapshot.m_type, m_snapshot.m_data, coord ); + case Tr2VoxelDataType::Int32: + return WrapPayloadElement( m_snapshot.m_voxelData->GetDimensions(), m_snapshot.m_type, m_snapshot.m_data, coord ); + default: + return {}; + } +} diff --git a/trinity/Tr2VoxelData.h b/trinity/Tr2VoxelData.h new file mode 100644 index 000000000..f75a6ce1a --- /dev/null +++ b/trinity/Tr2VoxelData.h @@ -0,0 +1,215 @@ +// Copyright © 2026 CCP ehf. + +#pragma once + +#include "Tr2Event.h" + +/** + * @brief Types allowed for payload channels in Tr2VoxelData. These types are used to define the type of data stored in each channel of the voxel data. + * Note that these types mostly correspond to CMF or texture formats, with the exception of Boolean. + */ +enum class Tr2VoxelDataType : uint8_t +{ + Boolean, + Float, + Float16, + UInt8, + UInt16, + UInt32, + Int8, + Int16, + Int32 +}; + + +BLUE_CLASS_IMPL( Tr2VoxelData ); + +/** + * @brief A container for 3D voxel data. Allows storing multiple data points per voxel. Each voxel can have multiple channels of data, each with its own type and number of elements. + */ +class Tr2VoxelData : public ICustomPersist +{ +public: + EXPOSE_TO_BLUE(); + + /** + * @brief Dimensions of the voxel data. + */ + struct Dimensions + { + uint32_t width = 0; + uint32_t height = 0; + uint32_t depth = 0; + }; + + using CoordType = int32_t; + + /** + * @brief Coordinates of a voxel in the 3D space. Used to access or modify the data of a specific voxel. + */ + struct Coord + { + CoordType x = 0; + CoordType y = 0; + CoordType z = 0; + }; + + /** + * @brief Definition of a payload channel. Each channel has a type, number of elements, and whether it is normalized. + * Normalized channels are expected to have values in the range [0, 1] for unsigned types and [-1, 1] for signed types. + * When accessing normalized data, in Python, the values will be returned as floats in the range [0, 1] or [-1, 1], regardless of the underlying storage type. + */ + struct PayloadType + { + Tr2VoxelDataType type = Tr2VoxelDataType::Float; + uint8_t elementCount = 1; + bool normalized = false; + + bool IsValid() const; + size_t GetBytesPerValue() const; + size_t GetStoreSize( const Dimensions& dimensions ) const; + }; + + /** + * @brief Snapshot of a payload channel. This is used to access the data of a specific channel in a read-only manner, + * specifically in asynchronous contexts where the underlying voxel data may be modified concurrently. The snapshot holds a + * reference to the voxel data, the type of the channel, the index of the channel, and a pointer to the data. + * If the data changes while a snapshot is held, the snapshot will still point to the old data until it is released. This allows for safe concurrent access to voxel data. + */ + class PayloadSnapshot + { + public: + PayloadSnapshot() = default; + PayloadSnapshot( PayloadSnapshot&& other ) noexcept; + ~PayloadSnapshot(); + PayloadSnapshot& operator=( PayloadSnapshot&& other ) noexcept; + + Tr2VoxelDataPtr m_voxelData; + PayloadType m_type; + size_t m_channel = 0; + const uint8_t* m_data = nullptr; + }; + + /** + * @brief Helper class to track which channels have been modified. This is used in conjunction with the OnDataChanged event to notify listeners of changes to specific channels. + */ + class ModifiedChannels + { + public: + bool IsChannelModified( size_t channelIndex ) const; + void MarkChannelModified( size_t channelIndex ); + + private: + uint64_t m_modifiedChannelsBitmask = 0; + }; + + Tr2VoxelData( IRoot* lockobj = nullptr ); + + /** + * @brief Second part of the two-phase construction for Tr2VoxelData. Initializes the voxel data with the given dimensions and payload definition. + * @param dimensions The dimensions of the voxel data (width, height, depth). + * @param payload A vector of payload channel definitions. + * @return A BlueStdResult indicating success or failure. + */ + BlueStdResult Create( const Dimensions& dimensions, std::vector payload ); + + /** + * @brief Checks if the given coordinates are within the bounds of the voxel data dimensions. + * @param coord The coordinates of the voxel to check. + * @return True if the coordinates are within bounds, false otherwise. + */ + bool IsWithinBounds( const Coord& coord ) const; + + /** + * @brief Gets the dimensions of the voxel data. + * @return The dimensions of the voxel data. + */ + Dimensions GetDimensions() const; + + /** + * @brief Gets the number of payload channels in the voxel data. + * @return The number of payload channels. + */ + size_t GetPayloadChannelCount() const; + + /** + * @brief Gets the payload type of the specified channel. + * @param index The index of the payload channel. + * @return The payload type of the specified channel. + */ + const PayloadType& GetPayloadType( size_t index ) const; + + /** + * @brief Gets a snapshot of the specified payload channel. + * @param index The index of the payload channel. + * @return A snapshot of the specified payload channel. + */ + PayloadSnapshot GetPayloadSnapshot( size_t index ); + + // ICustomPersist interface for persisting m_payload as a custom binary block + void GetWriteBufferAndSize( const char* memberName, unsigned char** buffer, size_t* bufferSize ) override; + void ReleaseWriteBuffer( unsigned char* buffer ) override; + unsigned char* AllocateReadBuffer( const char* memberName, size_t bufferSize ) override; + void SetBufferAndSize( const char* memberName, unsigned char* buffer, size_t bufferSize ) override; + + using OnDataChanged = Tr2Event; + + /** + * @brief Event that is broadcast when the voxel data is modified. This allows listeners to be notified of changes to the voxel data, enabling them to react accordingly. + */ + OnDataChanged m_onDataChanged; + +private: + struct Snapshot + { + std::vector data; + size_t readerCount = 0; + }; + + struct Payload + { + PayloadType type; + Snapshot current; + std::vector old; + }; + + BluePy SetVoxels( PyObject* set ); + BluePy PyGetPayloadData( size_t index, const Coord& coord ); + + bool ExtractPayload( PyObject* element, const Coord& coord, ModifiedChannels& modifiedChannels ); + + template + bool ExtractPayloadElement( PyObject* element, size_t index, const PayloadType& payloadType, const Coord& coord, ModifiedChannels& modifiedChannels ); + template + bool ExtractNormalizedPayloadElement( PyObject* element, size_t index, uint8_t elementCount, const Coord& coord, ModifiedChannels& modifiedChannels ); + + Dimensions m_dimensions; + std::vector m_payload; + std::mutex m_payloadMutex; +}; + +TYPEDEF_BLUECLASS( Tr2VoxelData ); + + +bool BlueExtractArgument( BlueScriptValue argument, Tr2VoxelData::Dimensions& resultRef, unsigned int argID ); +bool BlueExtractArgument( BlueScriptValue argument, Tr2VoxelData::Coord& resultRef, unsigned int argID ); +bool BlueExtractArgument( BlueScriptValue argument, Tr2VoxelData::PayloadType& resultRef, unsigned int argID ); + + +BLUE_CLASS_IMPL( Tr2VoxelDataTestAccessor ); + +/** + * @brief Testing helper class for Tr2VoxelData. Provides access to the payload data of a specific channel for testing and debugging purposes. + */ +class Tr2VoxelDataTestAccessor : public IRoot +{ +public: + EXPOSE_TO_BLUE(); + + BlueStdResult Create( Tr2VoxelData* voxelData, size_t channelIndex ); + BluePy PyGetPayloadData( const Tr2VoxelData::Coord& coord ) const; + + Tr2VoxelData::PayloadSnapshot m_snapshot; +}; + +TYPEDEF_BLUECLASS( Tr2VoxelDataTestAccessor ); diff --git a/trinity/Tr2VoxelData_Blue.cpp b/trinity/Tr2VoxelData_Blue.cpp new file mode 100644 index 000000000..c64cafe1a --- /dev/null +++ b/trinity/Tr2VoxelData_Blue.cpp @@ -0,0 +1,90 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2VoxelData.h" + +namespace +{ +Be::VarChooser Tr2VoxelDataTypeChooser[] = { + { "Boolean", BeCast( Tr2VoxelDataType::Boolean ), "" }, + { "Float", BeCast( Tr2VoxelDataType::Float ), "" }, + { "Float16", BeCast( Tr2VoxelDataType::Float16 ), "" }, + { "UInt8", BeCast( Tr2VoxelDataType::UInt8 ), "" }, + { "UInt16", BeCast( Tr2VoxelDataType::UInt16 ), "" }, + { "UInt32", BeCast( Tr2VoxelDataType::UInt32 ), "" }, + { "Int8", BeCast( Tr2VoxelDataType::Int8 ), "" }, + { "Int16", BeCast( Tr2VoxelDataType::Int16 ), "" }, + { "Int32", BeCast( Tr2VoxelDataType::Int32 ), "" }, + { 0 } +}; +} + +BLUE_REGISTER_ENUM_EX( + "Tr2VoxelDataType", + Tr2VoxelDataType, + Tr2VoxelDataTypeChooser, + ENUM_REG_ENUM_OBJECT_ON_MODULE ); + + +BLUE_DEFINE( Tr2VoxelData ); + +const Be::ClassInfo* Tr2VoxelData::ExposeToBlue() +{ + EXPOSURE_BEGIN( Tr2VoxelData, "Container for 3D voxel data. Allows storing multiple data points per voxel" ) + MAP_INTERFACE( ICustomPersist ) + + MAP_ATTRIBUTE( "width", m_dimensions.width, "The width of the voxel data", Be::READ | Be::PERSIST ); + MAP_ATTRIBUTE( "height", m_dimensions.height, "The height of the voxel data", Be::READ | Be::PERSIST ); + MAP_ATTRIBUTE( "depth", m_dimensions.depth, "The depth of the voxel data", Be::READ | Be::PERSIST ); + + MAP_PROPERTY_READONLY( "payloadChannelCount", GetPayloadChannelCount, "Number of channels in the payload" ) + + MAP_ATTRIBUTE_AS_CUSTOM_BINARY_BLOCK( "payload" ) + + MAP_METHOD_AND_WRAP( + "__init__", + Create, + "Initialize the voxel data with the given dimensions and payload definition\n\n" + ":param dimensions: The dimensions of the voxel data (tuple of width, height, depth)\n" + ":param payload: The payload definition for the voxel data: a list of payload definitions dictionaries. Dictionary entries are:\n" + " type: The type of the payload channel (see trinity.Tr2VoxelDataType)\n" + " elementCount: The number of elements in the payload channel\n" + " normalized: Whether the payload channel is normalized\n" ); + MAP_METHOD_AND_WRAP( + "SetVoxels", + SetVoxels, + "Populate the voxel data with the given generator\n\n" + ":param mod: A generator/sequence of voxel data in a form of tuples ((x, y, z), p0, p1, ...) where p# are payload elements\n" + ":type mod: Sequence[Tuple]\n" ); + MAP_METHOD_AND_WRAP( + "GetPayloadData", + PyGetPayloadData, + "Returns data for the specified payload channel at the given voxel coordinates\n\n" + ":param index: The index of the payload channel\n" + ":param coord: The coordinates of the voxel (tuple of x, y, z)\n" + ":return: The payload data for the specified channel\n" ) + EXPOSURE_END() +} + + + +BLUE_DEFINE( Tr2VoxelDataTestAccessor ); + +const Be::ClassInfo* Tr2VoxelDataTestAccessor::ExposeToBlue() +{ + EXPOSURE_BEGIN( Tr2VoxelDataTestAccessor, "Test accessor for Tr2VoxelData payload channels (for tests and debugging)" ) + + MAP_METHOD_AND_WRAP( + "__init__", + Create, + "Initialize test accessor with the data from a payload channel of the voxel data\n\n" + ":param data: The voxel data to create the accessor for\n" + ":param channel: The payload channel to create the accessor for\n" ); + MAP_METHOD_AND_WRAP( + "GetData", + PyGetPayloadData, + "Returns data for the payload channel at the given voxel coordinates\n\n" + ":param coord: The coordinates of the voxel (tuple of x, y, z)\n" + ":return: The payload data for the specified channel\n" ) + EXPOSURE_END() +} diff --git a/trinity/tests/Python/tests/test_voxeldata.py b/trinity/tests/Python/tests/test_voxeldata.py new file mode 100644 index 000000000..c942f8a5a --- /dev/null +++ b/trinity/tests/Python/tests/test_voxeldata.py @@ -0,0 +1,111 @@ +import unittest + +import blue +import trinity + +from ._util import TempRes + + +class TestVoxelData(unittest.TestCase): + def assertAlmostEqual(self, first, second, places = None, msg = None, delta = None): + if isinstance(first, tuple) and isinstance(second, tuple): + for f, s in zip(first, second): + super().assertAlmostEqual(f, s, places, msg, delta) + return + return super().assertAlmostEqual(first, second, places, msg, delta) + + def test_cannotCreateVoxelDataWithNoPayload(self): + with self.assertRaises(ValueError): + data = trinity.Tr2VoxelData((2, 2, 2), []) + + def test_canCreateVoxelDataWithPayload(self): + data = trinity.Tr2VoxelData((2, 2, 2), [{"type": trinity.Tr2VoxelDataType.UInt8, "elementCount": 1, "normalized": False}, {"type": trinity.Tr2VoxelDataType.Float16, "elementCount": 2, "normalized": False}]) + self.assertEqual(data.payloadChannelCount, 2) + + def test_cannotCreateWithInvalidTypes(self): + with self.assertRaises(ValueError): + trinity.Tr2VoxelData((2, 2, 2), [{"type": trinity.Tr2VoxelDataType.UInt8, "elementCount": 5, "normalized": False}]) + with self.assertRaises(ValueError): + trinity.Tr2VoxelData((2, 2, 2), [{"type": trinity.Tr2VoxelDataType.Boolean, "elementCount": 1, "normalized": True}]) + + def test_unpopulatedDataIsZeroInitialized(self): + data = trinity.Tr2VoxelData((2, 2, 2), [{"type": trinity.Tr2VoxelDataType.UInt8, "elementCount": 1, "normalized": False}, {"type": trinity.Tr2VoxelDataType.Float16, "elementCount": 2, "normalized": False}]) + self.assertEqual(data.GetPayloadData(0, (0, 0, 0)), 0) + self.assertEqual(data.GetPayloadData(1, (0, 0, 0)), (0.0, 0.0)) + + def test_canPopulateData(self): + data = trinity.Tr2VoxelData((2, 2, 2), [{"type": trinity.Tr2VoxelDataType.UInt8, "elementCount": 1, "normalized": False}, {"type": trinity.Tr2VoxelDataType.Float16, "elementCount": 2, "normalized": False}]) + data.SetVoxels([((0, 0, 0), 7, (0.5, 0.2)), ((1, 0, 1), 8, (0.3, 0.7))]) + self.assertEqual(data.GetPayloadData(0, (0, 0, 0)), 7) + + self.assertAlmostEqual(data.GetPayloadData(1, (0, 0, 0)), (0.5, 0.2), places=2) + self.assertEqual(data.GetPayloadData(0, (1, 0, 1)), 8) + self.assertAlmostEqual(data.GetPayloadData(1, (1, 0, 1)), (0.3, 0.7), places=2) + self.assertEqual(data.GetPayloadData(0, (1, 0, 0)), 0) + self.assertEqual(data.GetPayloadData(1, (1, 0, 0)), (0.0, 0.0)) + + def test_canUseBooleans(self): + data = trinity.Tr2VoxelData((2, 2, 2), [{"type": trinity.Tr2VoxelDataType.Boolean, "elementCount": 1, "normalized": False}]) + data.SetVoxels([((0, 0, 0), False), ((1, 0, 1), True)]) + self.assertEqual(data.GetPayloadData(0, (0, 0, 0)), False) + self.assertEqual(data.GetPayloadData(0, (1, 0, 1)), True) + self.assertEqual(data.GetPayloadData(0, (1, 0, 0)), False) + + def test_canUseBooleans2(self): + data = trinity.Tr2VoxelData((2, 2, 2), [{"type": trinity.Tr2VoxelDataType.Boolean, "elementCount": 2, "normalized": False}]) + data.SetVoxels([((0, 0, 0), (False, True)), ((1, 0, 1), (True, False))]) + self.assertEqual(data.GetPayloadData(0, (0, 0, 0)), (False, True)) + self.assertEqual(data.GetPayloadData(0, (1, 0, 1)), (True, False)) + self.assertEqual(data.GetPayloadData(0, (1, 0, 0)), (False, False)) + + def test_canUseNormalizedInts(self): + data = trinity.Tr2VoxelData((2, 2, 2), [{"type": trinity.Tr2VoxelDataType.UInt8, "elementCount": 1, "normalized": True}]) + data.SetVoxels([((0, 0, 0), 0.5), ((1, 0, 1), 1.0)]) + self.assertAlmostEqual(data.GetPayloadData(0, (0, 0, 0)), 0.5, places=2) + self.assertAlmostEqual(data.GetPayloadData(0, (1, 0, 1)), 1.0, places=2) + self.assertAlmostEqual(data.GetPayloadData(0, (1, 0, 0)), 0.0, places=2) + + def test_populateOutsideBoundsFails(self): + data = trinity.Tr2VoxelData((2, 2, 2), [{"type": trinity.Tr2VoxelDataType.UInt8, "elementCount": 1, "normalized": False}]) + with self.assertRaises(ValueError): + data.SetVoxels([((0, 0, 0), 1), ((3, 0, 1), 2)]) + + def test_canAddVoxel(self): + data = trinity.Tr2VoxelData((2, 2, 2), [{"type": trinity.Tr2VoxelDataType.UInt8, "elementCount": 1, "normalized": False}, {"type": trinity.Tr2VoxelDataType.Float16, "elementCount": 2, "normalized": False}]) + data.SetVoxels([((1, 1, 1), 7, (0.5, 0.2))]) + data.SetVoxels([((1, 0, 0), 3, (0.3, 0.7))]) + self.assertEqual(data.GetPayloadData(0, (1, 1, 1)), 7) + self.assertEqual(data.GetPayloadData(0, (1, 0, 0)), 3) + self.assertEqual(data.GetPayloadData(0, (0, 0, 0)), 0) + + def test_canChangeVoxel(self): + data = trinity.Tr2VoxelData((2, 2, 2), [{"type": trinity.Tr2VoxelDataType.UInt8, "elementCount": 1, "normalized": False}, {"type": trinity.Tr2VoxelDataType.Float16, "elementCount": 2, "normalized": False}]) + data.SetVoxels([((1, 1, 1), 7, (0.5, 0.2))]) + data.SetVoxels([((1, 0, 0), 3, (0.3, 0.7)), ((1, 1, 1), 8, (0.6, 0.4))]) + self.assertEqual(data.GetPayloadData(0, (1, 1, 1)), 8) + + def test_canPersistData(self): + with TempRes(): + data = trinity.Tr2VoxelData((2, 2, 2), [{"type": trinity.Tr2VoxelDataType.UInt8, "elementCount": 1, "normalized": False}, {"type": trinity.Tr2VoxelDataType.Float16, "elementCount": 2, "normalized": False}]) + data.SetVoxels([((1, 1, 1), 7, (0.5, 0.2))]) + + self.assertTrue(blue.resMan.SaveObject(data, "res:/test_voxeldata.red")) + + data1 = blue.resMan.LoadObject("res:/test_voxeldata.red") + self.assertEqual(data.payloadChannelCount, data1.payloadChannelCount) + self.assertEqual(data.width, data1.width) + self.assertEqual(data.height, data1.height) + self.assertEqual(data.depth, data1.depth) + self.assertEqual(data.GetPayloadData(0, (1, 1, 1)), 7) + self.assertAlmostEqual(data.GetPayloadData(1, (1, 1, 1)), (0.5, 0.2), places=2) + + def test_asyncAccess(self): + data = trinity.Tr2VoxelData((2, 2, 2), [{"type": trinity.Tr2VoxelDataType.UInt8, "elementCount": 1, "normalized": False}]) + data.SetVoxels([((1, 1, 1), 7)]) + accessor = trinity.Tr2VoxelDataTestAccessor(data, 0) + # The accessor should have a snapshot of the data at the time of creation, so changing the data afterwards should not affect the accessor. + self.assertEqual(accessor.GetData((1, 1, 1)), 7) + data.SetVoxels([((1, 1, 1), 8)]) + self.assertEqual(accessor.GetData((1, 1, 1)), 7) + accessor = trinity.Tr2VoxelDataTestAccessor(data, 0) + self.assertEqual(accessor.GetData((1, 1, 1)), 8) From 50b1afede2a1469346409adbc64cd57a9a17bd75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20=C3=9E=C3=B3r=20Gunnarsson?= Date: Tue, 3 Nov 2026 15:21:26 +0000 Subject: [PATCH 4/4] Tr2VoxelTexture Implementation of a texture that converts a voxeldata channel to a 3d texture. It implements ITr2TextureProvider so it can be used in Tr2RuntimeTextureProvider shader resource. --- trinity/CMakeLists.txt | 3 + trinity/Tr2VoxelData.cpp | 38 +++-- trinity/Tr2VoxelData.h | 49 ++++++ trinity/Tr2VoxelTexture.cpp | 271 +++++++++++++++++++++++++++++++ trinity/Tr2VoxelTexture.h | 110 +++++++++++++ trinity/Tr2VoxelTexture_Blue.cpp | 25 +++ 6 files changed, 483 insertions(+), 13 deletions(-) create mode 100644 trinity/Tr2VoxelTexture.cpp create mode 100644 trinity/Tr2VoxelTexture.h create mode 100644 trinity/Tr2VoxelTexture_Blue.cpp diff --git a/trinity/CMakeLists.txt b/trinity/CMakeLists.txt index eed7176d6..afbdf930d 100644 --- a/trinity/CMakeLists.txt +++ b/trinity/CMakeLists.txt @@ -1760,6 +1760,9 @@ set(_SOURCES Tr2VoxelData.cpp Tr2VoxelData.h Tr2VoxelData_Blue.cpp + Tr2VoxelTexture.cpp + Tr2VoxelTexture.h + Tr2VoxelTexture_Blue.cpp TriColor.cpp TriColor_Blue.cpp TriConstants.cpp diff --git a/trinity/Tr2VoxelData.cpp b/trinity/Tr2VoxelData.cpp index fa930995f..f0a30fcf9 100644 --- a/trinity/Tr2VoxelData.cpp +++ b/trinity/Tr2VoxelData.cpp @@ -106,8 +106,7 @@ BluePy WrapPayloadElement( const Tr2VoxelData::Dimensions& dimensions, const Tr2 { if constexpr( std::is_same_v ) { - auto byte = reinterpret_cast( payloadData + ( index * payloadType.elementCount + j ) / 8 ); - p[j] = ( *byte & ( 1 << ( ( index * payloadType.elementCount + j ) % 8 ) ) ) != 0; + p[j] = Tr2VoxelData::GetPackedBit( payloadData, index * payloadType.elementCount + j ); } else { @@ -332,6 +331,29 @@ Tr2VoxelData::PayloadSnapshot Tr2VoxelData::GetPayloadSnapshot( size_t index ) } +bool Tr2VoxelData::GetPackedBit( const uint8_t* data, size_t bitIndex ) +{ + return ( data[bitIndex / 8] >> ( bitIndex % 8 ) ) & 1; +} + + +bool Tr2VoxelData::SetPackedBit( uint8_t* data, size_t bitIndex, bool value ) +{ + uint8_t& byte = data[bitIndex / 8]; + uint8_t oldByte = byte; + uint8_t mask = uint8_t( 1 << ( bitIndex % 8 ) ); + if( value ) + { + byte |= mask; + } + else + { + byte &= ~mask; + } + return byte != oldByte; +} + + BluePy Tr2VoxelData::SetVoxels( PyObject* set ) { auto setIter = BluePy( PyObject_GetIter( set ), true ); @@ -448,17 +470,7 @@ bool Tr2VoxelData::ExtractPayloadElement( PyObject* element, size_t index, const { if constexpr( std::is_same_v ) { - auto& byte = *reinterpret_cast( dest.current.data.data() + ( coordIndex * payloadType.elementCount + j ) / 8 ); - auto oldByte = byte; - if( p[j] ) - { - byte |= 1 << ( ( coordIndex * payloadType.elementCount + j ) % 8 ); - } - else - { - byte &= ~( 1 << ( ( coordIndex * payloadType.elementCount + j ) % 8 ) ); - } - if( oldByte != byte ) + if( SetPackedBit( dest.current.data.data(), coordIndex * payloadType.elementCount + j, p[j] ) ) { modifiedChannels.MarkChannelModified( index ); } diff --git a/trinity/Tr2VoxelData.h b/trinity/Tr2VoxelData.h index f75a6ce1a..83bc127c6 100644 --- a/trinity/Tr2VoxelData.h +++ b/trinity/Tr2VoxelData.h @@ -21,6 +21,37 @@ enum class Tr2VoxelDataType : uint8_t Int32 }; +/** + * @brief Returns the name of the given Tr2VoxelDataType as a string. + * Useful for debugging and logging purposes. + */ +constexpr const char* Tr2VoxelDataTypeName( Tr2VoxelDataType type ) +{ + switch( type ) + { + case Tr2VoxelDataType::Boolean: + return "Boolean"; + case Tr2VoxelDataType::Float: + return "Float"; + case Tr2VoxelDataType::Float16: + return "Float16"; + case Tr2VoxelDataType::UInt8: + return "UInt8"; + case Tr2VoxelDataType::UInt16: + return "UInt16"; + case Tr2VoxelDataType::UInt32: + return "UInt32"; + case Tr2VoxelDataType::Int8: + return "Int8"; + case Tr2VoxelDataType::Int16: + return "Int16"; + case Tr2VoxelDataType::Int32: + return "Int32"; + default: + return "Unknown"; + } +} + BLUE_CLASS_IMPL( Tr2VoxelData ); @@ -146,6 +177,24 @@ class Tr2VoxelData : public ICustomPersist */ PayloadSnapshot GetPayloadSnapshot( size_t index ); + /** + * @brief Reads one element of a Boolean payload. Boolean payloads store one bit per element, least significant bit first: + * element j of voxel i is bit ( i * elementCount + j ) of the payload data. + * @param data The payload data. + * @param bitIndex The index of the element's bit. + * @return The value of the element. + */ + static bool GetPackedBit( const uint8_t* data, size_t bitIndex ); + + /** + * @brief Writes one element of a Boolean payload (see GetPackedBit for the layout). + * @param data The payload data. + * @param bitIndex The index of the element's bit. + * @param value The value to write. + * @return True if the stored value changed. + */ + static bool SetPackedBit( uint8_t* data, size_t bitIndex, bool value ); + // ICustomPersist interface for persisting m_payload as a custom binary block void GetWriteBufferAndSize( const char* memberName, unsigned char** buffer, size_t* bufferSize ) override; void ReleaseWriteBuffer( unsigned char* buffer ) override; diff --git a/trinity/Tr2VoxelTexture.cpp b/trinity/Tr2VoxelTexture.cpp new file mode 100644 index 000000000..6d026f02c --- /dev/null +++ b/trinity/Tr2VoxelTexture.cpp @@ -0,0 +1,271 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2VoxelTexture.h" + +namespace +{ + +/** + * @brief Returns the pixel formats corresponding to the given Tr2VoxelDataType. The returned array contains pixel formats for 1, 2 and 4 elements respectively. + * @param type The Tr2VoxelDataType to get pixel formats for. + * @param normalized Whether the pixel formats should be normalized (for integer types). + * @return An array of pixel formats corresponding to the given Tr2VoxelDataType. + */ +std::array GetDataTypePixelFormats( Tr2VoxelDataType type, bool normalized ) +{ + using namespace Tr2RenderContextEnum; + constexpr std::array invalidFormat = { PIXEL_FORMAT_UNKNOWN, PIXEL_FORMAT_UNKNOWN, PIXEL_FORMAT_UNKNOWN }; + switch( type ) + { + case Tr2VoxelDataType::Boolean: + return { PIXEL_FORMAT_R8_UINT, PIXEL_FORMAT_R8G8_UINT, PIXEL_FORMAT_R8G8B8A8_UINT }; + case Tr2VoxelDataType::Float: + return { PIXEL_FORMAT_R32_FLOAT, PIXEL_FORMAT_R32G32_FLOAT, PIXEL_FORMAT_R32G32B32A32_FLOAT }; + case Tr2VoxelDataType::Float16: + return { PIXEL_FORMAT_R16_FLOAT, PIXEL_FORMAT_R16G16_FLOAT, PIXEL_FORMAT_R16G16B16A16_FLOAT }; + case Tr2VoxelDataType::Int8: + if( normalized ) + { + return { PIXEL_FORMAT_R8_SNORM, PIXEL_FORMAT_R8G8_SNORM, PIXEL_FORMAT_R8G8B8A8_SNORM }; + } + return { PIXEL_FORMAT_R8_SINT, PIXEL_FORMAT_R8G8_SINT, PIXEL_FORMAT_R8G8B8A8_SINT }; + case Tr2VoxelDataType::UInt8: + if( normalized ) + { + return { PIXEL_FORMAT_R8_UNORM, PIXEL_FORMAT_R8G8_UNORM, PIXEL_FORMAT_R8G8B8A8_UNORM }; + } + return { PIXEL_FORMAT_R8_UINT, PIXEL_FORMAT_R8G8_UINT, PIXEL_FORMAT_R8G8B8A8_UINT }; + case Tr2VoxelDataType::Int16: + if( normalized ) + { + return { PIXEL_FORMAT_R16_SNORM, PIXEL_FORMAT_R16G16_SNORM, PIXEL_FORMAT_R16G16B16A16_SNORM }; + } + return { PIXEL_FORMAT_R16_SINT, PIXEL_FORMAT_R16G16_SINT, PIXEL_FORMAT_R16G16B16A16_SINT }; + case Tr2VoxelDataType::UInt16: + if( normalized ) + { + return { PIXEL_FORMAT_R16_UNORM, PIXEL_FORMAT_R16G16_UNORM, PIXEL_FORMAT_R16G16B16A16_UNORM }; + } + return { PIXEL_FORMAT_R16_UINT, PIXEL_FORMAT_R16G16_UINT, PIXEL_FORMAT_R16G16B16A16_UINT }; + case Tr2VoxelDataType::Int32: + if( normalized ) + { + return invalidFormat; + } + return { PIXEL_FORMAT_R32_SINT, PIXEL_FORMAT_R32G32_SINT, PIXEL_FORMAT_R32G32B32A32_SINT }; + case Tr2VoxelDataType::UInt32: + if( normalized ) + { + return invalidFormat; + } + return { PIXEL_FORMAT_R32_UINT, PIXEL_FORMAT_R32G32_UINT, PIXEL_FORMAT_R32G32B32A32_UINT }; + } + return invalidFormat; +} + +Tr2RenderContextEnum::PixelFormat GetPixelFormatForVoxelDataType( const Tr2VoxelData::PayloadType& payloadType ) +{ + auto pixelFormats = GetDataTypePixelFormats( payloadType.type, payloadType.normalized ); + switch( payloadType.elementCount ) + { + case 1: + return pixelFormats[0]; + case 2: + return pixelFormats[1]; + case 4: + return pixelFormats[2]; + default: + return Tr2RenderContextEnum::PixelFormat::PIXEL_FORMAT_UNKNOWN; + } +} +} + +Tr2VoxelTexture::Tr2VoxelTexture( IRoot* ) +{ +} + +bool Tr2VoxelTexture::OnModified( Be::Var* value ) +{ + if( IsMatch( value, m_channel ) ) + { + RebuildTexture(); + } + return true; +} + +Tr2TextureAL* Tr2VoxelTexture::GetTexture() +{ + if( m_texture.IsValid() ) + { + return &m_texture; + } + return nullptr; +} + +Tr2VoxelTexture::OnTextureChangeEvent& Tr2VoxelTexture::OnTextureChange() +{ + return m_onTextureChange; +} + +void Tr2VoxelTexture::SetVoxelData( Tr2VoxelData* voxelData ) +{ + if( m_voxelData == voxelData ) + { + return; + } + if( m_voxelData ) + { + m_voxelData->m_onDataChanged.UnregisterListener( this ); + } + m_voxelData = voxelData; + + if( m_voxelData ) + { + m_voxelData->m_onDataChanged.RegisterListener( this ); + } + + RebuildTexture(); +} + +Tr2VoxelData* Tr2VoxelTexture::GetVoxelData() const +{ + return m_voxelData; +} + +void Tr2VoxelTexture::OnVoxelDataChanged( Tr2VoxelData::ModifiedChannels modifiedChannels ) +{ + if( m_texture.IsValid() && modifiedChannels.IsChannelModified( m_channel ) ) + { + PopulateTextureData(); + } +} + +void Tr2VoxelTexture::RebuildTexture() +{ + // Empty when there is no voxel data or the channel can't be a texture + m_texture = CreateTexture(); + if( m_texture.IsValid() ) + { + PopulateTextureData(); + } + m_onTextureChange.Broadcast(); +} + +Tr2TextureAL Tr2VoxelTexture::CreateTexture() +{ + auto texture = Tr2TextureAL(); + + if( !m_voxelData ) + { + return texture; + } + + if( m_channel >= m_voxelData->GetPayloadChannelCount() ) + { + CCP_LOGERR( "Invalid channel index %zu for voxel data with %zu channels", m_channel, m_voxelData->GetPayloadChannelCount() ); + return texture; + } + + auto voxelDims = m_voxelData->GetDimensions(); + auto payloadType = m_voxelData->GetPayloadType( m_channel ); + auto pixelFormat = GetPixelFormatForVoxelDataType( payloadType ); + + if( pixelFormat == Tr2RenderContextEnum::PixelFormat::PIXEL_FORMAT_UNKNOWN ) + { + CCP_LOGERR( "Unsupported voxel data type (%s x%d%s) for texture creation", + Tr2VoxelDataTypeName( payloadType.type ), + payloadType.elementCount, + payloadType.normalized ? ", normalized" : "" ); + return texture; + } + + auto dims = Tr2BitmapDimensions( + Tr2RenderContextEnum::TextureType::TEX_TYPE_3D, + pixelFormat, + voxelDims.width, + voxelDims.height, + voxelDims.depth, + 1 ); + + USE_MAIN_THREAD_RENDER_CONTEXT(); + + auto result = texture.Create( dims, Tr2GpuUsage::SHADER_RESOURCE, Tr2CpuUsage::WRITE, renderContext ); + + if( FAILED( result ) ) + { + CCP_LOGERR( "Failed to create texture" ); + } + return texture; +} + +void Tr2VoxelTexture::PopulateTextureData() +{ + auto snapshot = m_voxelData->GetPayloadSnapshot( m_channel ); + if( snapshot.m_data == nullptr ) + { + CCP_LOGERR( "Failed to get payload snapshot for channel %zu", m_channel ); + return; + } + + auto voxelDims = m_voxelData->GetDimensions(); + const auto& payloadType = snapshot.m_type; + + // The source pitches are passed explicitly, so the backend can use its own row alignment + uint32_t pitch = voxelDims.width * Tr2RenderContextEnum::GetBytesPerPixel( m_texture.GetFormat() ); + uint32_t slicePitch = pitch * voxelDims.height; + + const uint8_t* source = snapshot.m_data; + std::vector unpacked; + if( payloadType.type == Tr2VoxelDataType::Boolean ) + { + // Byte n of the texture data is element n of the payload + unpacked.resize( size_t( slicePitch ) * voxelDims.depth ); + for( size_t n = 0; n < unpacked.size(); ++n ) + { + unpacked[n] = Tr2VoxelData::GetPackedBit( snapshot.m_data, n ); + } + source = unpacked.data(); + } + + USE_MAIN_THREAD_RENDER_CONTEXT(); + if( FAILED( m_texture.UpdateSubresource( Tr2TextureSubresource( 0 ), source, pitch, slicePitch, renderContext ) ) ) + { + CCP_LOGERR( "Failed to update texture data" ); + } +} + +bool Tr2VoxelTexture::IsValid() const +{ + return m_texture.IsValid(); +} + +uint32_t Tr2VoxelTexture::GetWidth() const +{ + return m_texture.GetWidth(); +} + +uint32_t Tr2VoxelTexture::GetHeight() const +{ + return m_texture.GetHeight(); +} + +uint32_t Tr2VoxelTexture::GetDepth() const +{ + return m_texture.GetDepth(); +} + +uint32_t Tr2VoxelTexture::GetMipCount() const +{ + return m_texture.GetMipCount(); +} + +Tr2RenderContextEnum::PixelFormat Tr2VoxelTexture::GetFormat() const +{ + return m_texture.GetFormat(); +} + +Tr2RenderContextEnum::TextureType Tr2VoxelTexture::GetType() const +{ + return m_texture.GetType(); +} diff --git a/trinity/Tr2VoxelTexture.h b/trinity/Tr2VoxelTexture.h new file mode 100644 index 000000000..85b199f56 --- /dev/null +++ b/trinity/Tr2VoxelTexture.h @@ -0,0 +1,110 @@ +// Copyright © 2026 CCP ehf. + +#pragma once +#include "ITr2TextureProvider.h" +#include "Tr2VoxelData.h" + +/** +* @brief Texture composed from voxel data. Reacts to changes in data and updates the texture accordingly. +*/ +class Tr2VoxelTexture : public ITr2TextureProvider, + public INotify +{ +public: + EXPOSE_TO_BLUE(); + Tr2VoxelTexture( IRoot* lockobj = nullptr ); + bool OnModified( Be::Var* value ) override; + + /** + * @brief Gets the texture. + */ + Tr2TextureAL* GetTexture() override; + + /** + * @brief Gets the texture change event. + */ + OnTextureChangeEvent& OnTextureChange() override; + + /** + * @brief Sets the voxel data associated with this texture. + */ + void SetVoxelData( Tr2VoxelData* voxelData ); + + /** + * @brief Gets the voxel data associated with this texture. + */ + Tr2VoxelData* GetVoxelData() const; + + /** + * @brief Returns whether the texture is valid. + */ + bool IsValid() const; + + /** + * @brief Gets the width of the texture. + */ + uint32_t GetWidth() const; + + /** + * @brief Gets the height of the texture. + */ + uint32_t GetHeight() const; + + /** + * @brief Gets the depth of the texture. + */ + uint32_t GetDepth() const; + + /** + * @brief Gets the mip count of the texture. + */ + uint32_t GetMipCount() const; + + /** + * @brief Gets the format of the texture. + */ + Tr2RenderContextEnum::PixelFormat GetFormat() const; + + /** + * @brief Gets the type of the texture. + */ + Tr2RenderContextEnum::TextureType GetType() const; + +private: + /** + * @brief Rebuilds the texture from the voxel data. This function is called when the voxel data or the channel is set. + */ + void RebuildTexture(); + + /** + * @brief Creates a texture from the voxel data. + * It creates a new texture when the voxel data and selected channel are valid. Otherwise, it returns an empty invalid texture. + */ + Tr2TextureAL CreateTexture(); + + /** + * @brief Populates the texture with the voxel data. + * When the voxel data is modified, this function is called to update the texture with the new data. + */ + void PopulateTextureData(); + + /** + * @brief Called when the voxel data is modified. + * @param modifiedChannels The channels that were modified. + */ + void OnVoxelDataChanged( Tr2VoxelData::ModifiedChannels modifiedChannels ); + + // The voxel data associated with this texture. + Tr2VoxelDataPtr m_voxelData; + + // The texture associated with this voxel data. + Tr2TextureAL m_texture; + + // The texture change callback event, which is broadcast when the texture is modified. + OnTextureChangeEvent m_onTextureChange; + + // The voxel data channel to use for the texture. + size_t m_channel = 0; +}; + +TYPEDEF_BLUECLASS( Tr2VoxelTexture ); \ No newline at end of file diff --git a/trinity/Tr2VoxelTexture_Blue.cpp b/trinity/Tr2VoxelTexture_Blue.cpp new file mode 100644 index 000000000..7f63da66e --- /dev/null +++ b/trinity/Tr2VoxelTexture_Blue.cpp @@ -0,0 +1,25 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "Tr2VoxelTexture.h" + + +BLUE_DEFINE( Tr2VoxelTexture ); + +const Be::ClassInfo* Tr2VoxelTexture::ExposeToBlue() +{ + EXPOSURE_BEGIN( Tr2VoxelTexture, "Texture composed from voxel data" ) + MAP_INTERFACE( INotify ) + MAP_INTERFACE( ITr2TextureProvider ) + MAP_PROPERTY( "voxelData", GetVoxelData, SetVoxelData, "The voxel data associated with this texture" ); + MAP_ATTRIBUTE( "channel", m_channel, "The channel of the voxel data to use", Be::READWRITE | Be::NOTIFY ); + + MAP_PROPERTY_READONLY( "width", GetWidth, "The width of the texture" ); + MAP_PROPERTY_READONLY( "height", GetHeight, "The height of the texture" ); + MAP_PROPERTY_READONLY( "depth", GetDepth, "The depth of the texture" ); + MAP_PROPERTY_READONLY( "mipCount", GetMipCount, "The number of mip levels of the texture" ); + MAP_PROPERTY_READONLY( "format", GetFormat, "The pixel format of the texture" ); + MAP_PROPERTY_READONLY( "type", GetType, "The type of the texture" ); + MAP_PROPERTY_READONLY( "valid", IsValid, "Whether the texture generated from the voxel data is valid" ); + EXPOSURE_END(); +} \ No newline at end of file