From 6b12cfe231903260d9d20b525a611ad2b439d55a Mon Sep 17 00:00:00 2001 From: Breki Ingibjargarson Date: Fri, 22 May 2026 16:47:36 +0000 Subject: [PATCH 01/10] add ITr2BoundingBox to EvePlanet, EveEffectRoot2, EveRootTransform, EveTransform --- trinity/Eve/EveEffectRoot2.cpp | 108 +++++++++++++++++- trinity/Eve/EveEffectRoot2.h | 9 +- trinity/Eve/EveEffectRoot2_Blue.cpp | 1 + trinity/Eve/EvePlanet.cpp | 25 ++++- trinity/Eve/EvePlanet.h | 4 + trinity/Eve/EvePlanet_Blue.cpp | 1 + trinity/Eve/EveRootTransform_Blue.cpp | 1 + trinity/Eve/EveTransform.cpp | 154 +++++++++++++++++++++++++- trinity/Eve/EveTransform.h | 11 +- trinity/Eve/EveTransform_Blue.cpp | 1 + 10 files changed, 307 insertions(+), 8 deletions(-) diff --git a/trinity/Eve/EveEffectRoot2.cpp b/trinity/Eve/EveEffectRoot2.cpp index 7106c15f5..bce01d9ae 100644 --- a/trinity/Eve/EveEffectRoot2.cpp +++ b/trinity/Eve/EveEffectRoot2.cpp @@ -2,6 +2,7 @@ #include "EveEffectRoot2.h" #include "Utilities/BoundingSphere.h" +#include "Utilities/BoundingBox.h" #include "TriFrustum.h" #include "Lights/Tr2PointLight.h" #include "Tr2LightManager.h" @@ -11,8 +12,60 @@ #include "Eve/SpaceObject/EveSpaceObject2.h" #include "Eve/SpaceObject/Children/EveChildContainer.h" +#include + extern float g_eveSpaceObjectResourceUnloadingTimeThreshold; +namespace +{ +bool IsFinite( const Vector3& v ) +{ + return std::isfinite( v.x ) && std::isfinite( v.y ) && std::isfinite( v.z ); +} + +bool IsValidBoundingSphere( const Vector4& sphere ) +{ + return sphere.w > 0.0f && + std::isfinite( sphere.x ) && + std::isfinite( sphere.y ) && + std::isfinite( sphere.z ) && + std::isfinite( sphere.w ); +} + +void IncludeWorldBounds( const Vector3& boundsMin, const Vector3& boundsMax, Vector3& min, Vector3& max, bool& valid ) +{ + if( !IsFinite( boundsMin ) || !IsFinite( boundsMax ) ) + { + return; + } + + if( !valid ) + { + min = boundsMin; + max = boundsMax; + valid = true; + } + else + { + BoundingBoxUpdate( min, max, boundsMin, boundsMax ); + } +} + +void IncludeWorldSphereBounds( const Vector4& sphere, Vector3& min, Vector3& max, bool& valid ) +{ + if( !IsValidBoundingSphere( sphere ) ) + { + return; + } + + Vector3 sphereMin; + Vector3 sphereMax; + BoundingBoxInitialize( sphere, sphereMin, sphereMax ); + IncludeWorldBounds( sphereMin, sphereMax, min, max, valid ); +} +} + + EveEffectRoot2::EveEffectRoot2( IRoot* lockobj ) : PARENTLOCK( m_observers ), PARENTLOCK( m_lights ), @@ -396,8 +449,13 @@ void EveEffectRoot2::GetModelCenterWorldPosition( Vector3 &position ) const bool EveEffectRoot2::GetLocalBoundingBox( Vector3 &min, Vector3 &max ) { - // If possible, return an AABB in local coordinates - return false; + if( !IsValidBoundingSphere( m_boundingSphere ) ) + { + return false; + } + + BoundingBoxInitialize( m_boundingSphere, min, max ); + return true; } void EveEffectRoot2::GetLocalToWorldTransform( Matrix &transform ) const @@ -406,6 +464,50 @@ void EveEffectRoot2::GetLocalToWorldTransform( Matrix &transform ) const transform = m_lastUpdateMatrix; } +bool EveEffectRoot2::GetWorldBoundingBox( Vector3& min, Vector3& max ) const +{ + bool valid = false; + + if( IsValidBoundingSphere( m_boundingSphere ) ) + { + Vector3 rootMin; + Vector3 rootMax; + BoundingBoxInitialize( m_boundingSphere, rootMin, rootMax ); + BoundingBoxTransform( rootMin, rootMax, m_lastUpdateMatrix ); + IncludeWorldBounds( rootMin, rootMax, min, max, valid ); + } + + for( auto it = m_effectChildren.begin(); it != m_effectChildren.end(); ++it ) + { + Vector4 childBounds; + if( ( *it )->GetBoundingSphere( childBounds, EVE_BOUNDS_WITH_CHILDREN ) ) + { + IncludeWorldSphereBounds( childBounds, min, max, valid ); + } + } + + return valid; +} + +bool EveEffectRoot2::IsBoundingBoxReady() const +{ + if( IsValidBoundingSphere( m_boundingSphere ) ) + { + return true; + } + + for( auto it = m_effectChildren.begin(); it != m_effectChildren.end(); ++it ) + { + Vector4 childBounds; + if( ( *it )->GetBoundingSphere( childBounds, EVE_BOUNDS_WITH_CHILDREN ) && IsValidBoundingSphere( childBounds ) ) + { + return true; + } + } + + return false; +} + void EveEffectRoot2::RegisterWithQuadRenderer( Tr2QuadRenderer& quadRenderer ) { for( auto it = m_effectChildren.begin(); it != m_effectChildren.end(); ++it ) @@ -992,4 +1094,4 @@ void EveEffectRoot2::SetProceduralContainerVariable( const char *name, float val auto child = *it; child->SetProceduralContainerVariable( name, value ); } -} \ No newline at end of file +} diff --git a/trinity/Eve/EveEffectRoot2.h b/trinity/Eve/EveEffectRoot2.h index bee310e19..0f78e6362 100644 --- a/trinity/Eve/EveEffectRoot2.h +++ b/trinity/Eve/EveEffectRoot2.h @@ -18,6 +18,7 @@ #include "ITr2SoundEmitterOwner.h" #include "Controllers/ITr2ControllerOwner.h" #include "Lights/ITr2LightOwner.h" +#include "ITr2BoundingBox.h" #include "EveEntity.h" BLUE_DECLARE( Tr2Light ); @@ -46,6 +47,7 @@ BLUE_CLASS( EveEffectRoot2 ): public ITr2SoundEmitterOwner, public ITr2ControllerOwner, public ITr2LightOwner, + public ITr2BoundingBox, public EveEntity { @@ -85,6 +87,11 @@ BLUE_CLASS( EveEffectRoot2 ): void AddQuadsToQuadRenderer( const TriFrustum& frustum, Tr2QuadRenderer& quadRenderer ); void SetProceduralContainerVariable( const char *name, float value ) override; + ///////////////////////////////////////////////////////////////////////////////////// + // ITr2BoundingBox + bool GetWorldBoundingBox( Vector3& min, Vector3& max ) const override; + bool IsBoundingBoxReady() const override; + ///////////////////////////////////////////////////////////////////////////////////// // ITr2LightOwner void GetLights( Tr2LightManager& lightManager ) const override; @@ -224,4 +231,4 @@ BLUE_CLASS( EveEffectRoot2 ): TYPEDEF_BLUECLASS( EveEffectRoot2 ); -#endif // EveEffectRoot2_h \ No newline at end of file +#endif // EveEffectRoot2_h diff --git a/trinity/Eve/EveEffectRoot2_Blue.cpp b/trinity/Eve/EveEffectRoot2_Blue.cpp index c656465ad..e8b285ccc 100644 --- a/trinity/Eve/EveEffectRoot2_Blue.cpp +++ b/trinity/Eve/EveEffectRoot2_Blue.cpp @@ -17,6 +17,7 @@ const Be::ClassInfo* EveEffectRoot2::ExposeToBlue() MAP_INTERFACE ( IShaderConfigurer ) MAP_INTERFACE( ITr2SoundEmitterOwner ) MAP_INTERFACE( ITr2LightOwner ) + MAP_INTERFACE( ITr2BoundingBox ) MAP_INTERFACE( IWorldPosition ) MAP_INTERFACE( EveEntity ) diff --git a/trinity/Eve/EvePlanet.cpp b/trinity/Eve/EvePlanet.cpp index 3abb973b6..dbe3723f0 100644 --- a/trinity/Eve/EvePlanet.cpp +++ b/trinity/Eve/EvePlanet.cpp @@ -5,6 +5,7 @@ #include "TriDevice.h" #include "EveUpdateContext.h" #include "Curves/TriCurveSet.h" +#include "Utilities/BoundingBox.h" const float EvePlanet::SCALE = 1000000.0f; @@ -148,6 +149,28 @@ Quaternion EvePlanet::GetWorldRotation() return m_rotation; } +bool EvePlanet::GetWorldBoundingBox( Vector3& min, Vector3& max ) const +{ + if( m_radius <= 0.0f ) + { + return false; + } + + const float renderScale = m_renderScale > 0.0f ? m_renderScale : 1.0f; + const Matrix scaledTransform = CalculatePlanetScaleTransform( m_worldTransform, renderScale ); + const Vector3 center = scaledTransform.GetTranslation(); + const float radius = m_radius / renderScale; + const Vector4 sphere( center, radius ); + + BoundingBoxInitialize( sphere, min, max ); + return true; +} + +bool EvePlanet::IsBoundingBoxReady() const +{ + return m_radius > 0.0f; +} + // -------------------------------------------------------------------------------- // Description: // Calculate the pixeldiameter of this planet sphere at a given position. Is used @@ -385,4 +408,4 @@ void EvePlanet::RenderDebugInfo( ITr2DebugRenderer2& renderer ) renderable->RenderDebugInfo( renderer ); } } -} \ No newline at end of file +} diff --git a/trinity/Eve/EvePlanet.h b/trinity/Eve/EvePlanet.h index 350811882..3c722d8e2 100644 --- a/trinity/Eve/EvePlanet.h +++ b/trinity/Eve/EvePlanet.h @@ -49,6 +49,10 @@ BLUE_CLASS( EvePlanet ): virtual Vector3 GetWorldPosition(); virtual Quaternion GetWorldRotation(); + // ITr2BoundingBox + bool GetWorldBoundingBox( Vector3& min, Vector3& max ) const override; + bool IsBoundingBoxReady() const override; + // ITr2SecondaryLightSource virtual void RegisterSecondaryLightSource( Tr2ShLightingManager& ); virtual void UnregisterSecondaryLightSource( Tr2ShLightingManager& ); diff --git a/trinity/Eve/EvePlanet_Blue.cpp b/trinity/Eve/EvePlanet_Blue.cpp index 055bc13d7..f6fc0db79 100644 --- a/trinity/Eve/EvePlanet_Blue.cpp +++ b/trinity/Eve/EvePlanet_Blue.cpp @@ -15,6 +15,7 @@ const Be::ClassInfo* EvePlanet::ExposeToBlue() MAP_INTERFACE( IShaderConfigurer ) MAP_INTERFACE( ITr2SoundEmitterOwner ) MAP_INTERFACE( IWorldPosition ) + MAP_INTERFACE( ITr2BoundingBox ) MAP_ATTRIBUTE ( "radius", diff --git a/trinity/Eve/EveRootTransform_Blue.cpp b/trinity/Eve/EveRootTransform_Blue.cpp index 609b5e1cb..a8267628e 100644 --- a/trinity/Eve/EveRootTransform_Blue.cpp +++ b/trinity/Eve/EveRootTransform_Blue.cpp @@ -11,6 +11,7 @@ const Be::ClassInfo* EveRootTransform::ExposeToBlue() MAP_INTERFACE( ITriTargetable ) MAP_INTERFACE( ITr2Pickable ) MAP_INTERFACE( IWorldPosition ) + MAP_INTERFACE( ITr2BoundingBox ) MAP_ATTRIBUTE ( diff --git a/trinity/Eve/EveTransform.cpp b/trinity/Eve/EveTransform.cpp index 6ef0eaf91..90e3ad915 100644 --- a/trinity/Eve/EveTransform.cpp +++ b/trinity/Eve/EveTransform.cpp @@ -15,6 +15,53 @@ extern float g_eveSpaceSceneLowUpdateRate; +namespace +{ +bool HasOverrideBounds( const Vector3& boundsMin, const Vector3& boundsMax ) +{ + return boundsMax.x != boundsMin.x || boundsMax.y != boundsMin.y || boundsMax.z != boundsMin.z; +} + +void IncludeBounds( const Vector3& boundsMin, const Vector3& boundsMax, Vector3& min, Vector3& max, bool& valid ) +{ + if( !valid ) + { + min = boundsMin; + max = boundsMax; + valid = true; + } + else + { + BoundingBoxUpdate( min, max, boundsMin, boundsMax ); + } +} + +void IncludeSphereBounds( const Vector4& sphere, Vector3& min, Vector3& max, bool& valid ) +{ + if( sphere.w <= 0.0f ) + { + return; + } + + Vector3 sphereMin; + Vector3 sphereMax; + BoundingBoxInitialize( sphere, sphereMin, sphereMax ); + IncludeBounds( sphereMin, sphereMax, min, max, valid ); +} + +bool GetDirectLocalBounds( const Vector3& overrideBoundsMin, const Vector3& overrideBoundsMax, Tr2MeshBase* mesh, Vector3& min, Vector3& max ) +{ + if( HasOverrideBounds( overrideBoundsMin, overrideBoundsMax ) ) + { + min = overrideBoundsMin; + max = overrideBoundsMax; + return true; + } + + return mesh && mesh->GetBoundingBox( min, max ); +} +} + EveTransform::EveTransform( IRoot* lockobj ) : Tr2Transform( lockobj ), PARENTLOCK( m_children ), @@ -349,6 +396,111 @@ void EveTransform::GetRenderables( std::vector& renderables ) GetRenderables( renderables, nullptr ); } +bool EveTransform::GetLocalBoundingBox( Vector3& min, Vector3& max ) +{ + bool valid = GetDirectLocalBounds( m_overrideBoundsMin, m_overrideBoundsMax, m_mesh, min, max ); + + Matrix inverseWorldTransform; + const bool hasInverseWorldTransform = Inverse( inverseWorldTransform, m_worldTransform ); + + if( hasInverseWorldTransform ) + { + for( auto it = m_children.begin(); it != m_children.end(); ++it ) + { + Vector3 childMin; + Vector3 childMax; + bool hasChildBounds = false; + + if( auto childBoundingBox = dynamic_cast( *it ) ) + { + hasChildBounds = childBoundingBox->GetWorldBoundingBox( childMin, childMax ); + } + + if( !hasChildBounds ) + { + Vector4 childSphere; + if( ( *it )->GetBoundingSphere( childSphere, EVE_BOUNDS_WITH_CHILDREN ) && childSphere.w > 0.0f ) + { + BoundingBoxInitialize( childSphere, childMin, childMax ); + hasChildBounds = true; + } + } + + if( hasChildBounds ) + { + BoundingBoxTransform( childMin, childMax, inverseWorldTransform ); + IncludeBounds( childMin, childMax, min, max, valid ); + } + } + } + + return valid; +} + +bool EveTransform::GetWorldBoundingBox( Vector3& min, Vector3& max ) const +{ + bool valid = false; + + Vector3 localMin; + Vector3 localMax; + if( GetDirectLocalBounds( m_overrideBoundsMin, m_overrideBoundsMax, m_mesh, localMin, localMax ) ) + { + BoundingBoxTransform( localMin, localMax, m_worldTransform ); + IncludeBounds( localMin, localMax, min, max, valid ); + } + + for( auto it = m_children.begin(); it != m_children.end(); ++it ) + { + Vector3 childMin; + Vector3 childMax; + if( auto childBoundingBox = dynamic_cast( *it ) ) + { + if( childBoundingBox->GetWorldBoundingBox( childMin, childMax ) ) + { + IncludeBounds( childMin, childMax, min, max, valid ); + continue; + } + } + + Vector4 childSphere; + if( ( *it )->GetBoundingSphere( childSphere, EVE_BOUNDS_WITH_CHILDREN ) ) + { + IncludeSphereBounds( childSphere, min, max, valid ); + } + } + + return valid; +} + +bool EveTransform::IsBoundingBoxReady() const +{ + Vector3 min; + Vector3 max; + if( GetDirectLocalBounds( m_overrideBoundsMin, m_overrideBoundsMax, m_mesh, min, max ) ) + { + return true; + } + + for( auto it = m_children.begin(); it != m_children.end(); ++it ) + { + if( auto childBoundingBox = dynamic_cast( *it ) ) + { + if( childBoundingBox->IsBoundingBoxReady() ) + { + return true; + } + } + + Vector4 childSphere; + if( ( *it )->GetBoundingSphere( childSphere, EVE_BOUNDS_WITH_CHILDREN ) && childSphere.w > 0.0f ) + { + return true; + } + } + + return false; +} + bool EveTransform::GetBoundingSphere( Vector4& sphere, BoundingSphereQuery query ) const { bool valid = false; @@ -504,4 +656,4 @@ void EveTransform::GetPickingBatches( ITriRenderBatchAccumulator* batches, Tr2Pi GetBatches( batches, TRIBATCHTYPE_TRANSPARENT, perObjectData ); GetBatches( batches, TRIBATCHTYPE_ADDITIVE, perObjectData ); } -} \ No newline at end of file +} diff --git a/trinity/Eve/EveTransform.h b/trinity/Eve/EveTransform.h index 94a7fd7f9..3d47d3665 100644 --- a/trinity/Eve/EveTransform.h +++ b/trinity/Eve/EveTransform.h @@ -14,6 +14,7 @@ #include "IEveTransform.h" #include "IWorldPosition.h" +#include "ITr2BoundingBox.h" BLUE_DECLARE( Tr2ParticleSystem ); BLUE_DECLARE_VECTOR( Tr2ParticleSystem ); @@ -35,6 +36,7 @@ BLUE_CLASS( EveTransform ): public IWorldPosition, public IInitialize, public ITr2CurveSetOwner, + public ITr2BoundingBox, public ITr2DebugRenderable { public: @@ -57,8 +59,13 @@ BLUE_CLASS( EveTransform ): bool GetBoundingSphere( Vector4& sphere, BoundingSphereQuery query=EVE_BOUNDS_NORMAL ) const override; void UpdateModelCenterWorldPosition( Vector3 &position, Be::Time t ) override; void GetModelCenterWorldPosition( Vector3 &position ) const override; - bool GetLocalBoundingBox( Vector3 &min, Vector3 &max ) override { return false; } - void GetLocalToWorldTransform( Matrix &transform ) const override { transform = IdentityMatrix(); } + bool GetLocalBoundingBox( Vector3 &min, Vector3 &max ) override; + void GetLocalToWorldTransform( Matrix &transform ) const override { transform = m_worldTransform; } + + ///////////////////////////////////////////////////////////////////////////////////// + // ITr2BoundingBox + bool GetWorldBoundingBox( Vector3& min, Vector3& max ) const override; + bool IsBoundingBoxReady() const override; ///////////////////////////////////////////////////////////////////////////////////// // ITr2Renderable - mostly implemented by Tr2Transform except for these diff --git a/trinity/Eve/EveTransform_Blue.cpp b/trinity/Eve/EveTransform_Blue.cpp index 5e56308ab..fa08b7cf7 100644 --- a/trinity/Eve/EveTransform_Blue.cpp +++ b/trinity/Eve/EveTransform_Blue.cpp @@ -14,6 +14,7 @@ const Be::ClassInfo* EveTransform::ExposeToBlue() MAP_INTERFACE( ITr2Pickable ) MAP_INTERFACE( IWorldPosition ) MAP_INTERFACE( IInitialize ) + MAP_INTERFACE( ITr2BoundingBox ) MAP_ATTRIBUTE ( From b5e325ef29bf92780cf94798600f2efe881591d9 Mon Sep 17 00:00:00 2001 From: Breki Ingibjargarson Date: Thu, 4 Jun 2026 09:35:09 +0000 Subject: [PATCH 02/10] update bounding box bracket modernize it and add features that space scene can use --- trinity/Interior/Tr2InteriorPlaceable.cpp | 4 +- trinity/Tr2ProjectBoundingBoxBracket.cpp | 448 ++++++++++++------ trinity/Tr2ProjectBoundingBoxBracket.h | 9 + trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp | 46 +- 4 files changed, 357 insertions(+), 150 deletions(-) diff --git a/trinity/Interior/Tr2InteriorPlaceable.cpp b/trinity/Interior/Tr2InteriorPlaceable.cpp index 7aa47e6b8..168909b09 100644 --- a/trinity/Interior/Tr2InteriorPlaceable.cpp +++ b/trinity/Interior/Tr2InteriorPlaceable.cpp @@ -135,7 +135,7 @@ bool Tr2InteriorPlaceable::GetWorldBoundingBox( Vector3& min, Vector3& max ) con bool Tr2InteriorPlaceable::IsBoundingBoxReady( void ) const { - return( m_placeableRes && m_placeableRes->IsReady() ); + return m_isBoundingBoxModified || ( m_placeableRes && m_placeableRes->IsReady() ); } void Tr2InteriorPlaceable::PrePhysicsUpdate( Be::Time time ) @@ -646,4 +646,4 @@ void Tr2InteriorPlaceable::GetPickingBatches( ITriRenderBatchAccumulator* batche GetBatches( batches, TRIBATCHTYPE_TRANSPARENT, perObjectData ); GetBatches( batches, TRIBATCHTYPE_ADDITIVE, perObjectData ); } -} \ No newline at end of file +} diff --git a/trinity/Tr2ProjectBoundingBoxBracket.cpp b/trinity/Tr2ProjectBoundingBoxBracket.cpp index 512c9fc26..030e512b4 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.cpp +++ b/trinity/Tr2ProjectBoundingBoxBracket.cpp @@ -6,6 +6,8 @@ #include "StdAfx.h" +#include + #include "Tr2ProjectBoundingBoxBracket.h" #include "include/ITr2BoundingBox.h" #include "Tr2Renderer.h" @@ -16,6 +18,249 @@ extern ITr2DebugRendererPtr g_debugRenderer; +namespace +{ + const float CLIP_EPSILON = 1e-5f; + + struct ClipPoint + { + float x; + float y; + float z; + float w; + }; + + struct ProjectedBounds + { + float x; + float y; + float z; + float width; + float height; + bool extendsOffscreen; + bool coversViewport; + }; + + bool IsFinite( float value ) + { + return std::isfinite( value ); + } + + bool IsFinite( const ClipPoint& point ) + { + return IsFinite( point.x ) && IsFinite( point.y ) && IsFinite( point.z ) && IsFinite( point.w ); + } + + ClipPoint TransformPointToClip( const Vector3& point, const Matrix& viewProjection ) + { + return ClipPoint + { + point.x * viewProjection._11 + point.y * viewProjection._21 + point.z * viewProjection._31 + viewProjection._41, + point.x * viewProjection._12 + point.y * viewProjection._22 + point.z * viewProjection._32 + viewProjection._42, + point.x * viewProjection._13 + point.y * viewProjection._23 + point.z * viewProjection._33 + viewProjection._43, + point.x * viewProjection._14 + point.y * viewProjection._24 + point.z * viewProjection._34 + viewProjection._44 + }; + } + + ClipPoint Lerp( const ClipPoint& a, const ClipPoint& b, float t ) + { + return ClipPoint + { + a.x + ( b.x - a.x ) * t, + a.y + ( b.y - a.y ) * t, + a.z + ( b.z - a.z ) * t, + a.w + ( b.w - a.w ) * t + }; + } + + bool AreAllOutsidePlane( const ClipPoint* points, float ( *planeDistance )( const ClipPoint& ) ) + { + for( int i = 0; i < 8; ++i ) + { + if( planeDistance( points[i] ) >= 0.0f ) + { + return false; + } + } + return true; + } + + float DistanceToLeftPlane( const ClipPoint& point ) { return point.x + point.w; } + float DistanceToRightPlane( const ClipPoint& point ) { return point.w - point.x; } + float DistanceToBottomPlane( const ClipPoint& point ) { return point.y + point.w; } + float DistanceToTopPlane( const ClipPoint& point ) { return point.w - point.y; } + float DistanceToNearPlane( const ClipPoint& point ) { return point.z; } + float DistanceToFarPlane( const ClipPoint& point ) { return point.w - point.z; } + + bool IsTriviallyOutsideFrustum( const ClipPoint* points ) + { + return AreAllOutsidePlane( points, DistanceToLeftPlane ) || + AreAllOutsidePlane( points, DistanceToRightPlane ) || + AreAllOutsidePlane( points, DistanceToBottomPlane ) || + AreAllOutsidePlane( points, DistanceToTopPlane ) || + AreAllOutsidePlane( points, DistanceToNearPlane ) || + AreAllOutsidePlane( points, DistanceToFarPlane ); + } + + bool CanPerspectiveDivide( const ClipPoint& point ) + { + return IsFinite( point ) && fabsf( point.w ) > CLIP_EPSILON; + } + + void AddIfProjectable( const ClipPoint& point, std::vector& points ) + { + if( point.z >= 0.0f && CanPerspectiveDivide( point ) ) + { + points.push_back( point ); + } + } + + void AddNearPlaneIntersection( const ClipPoint& a, const ClipPoint& b, std::vector& points ) + { + if( ( a.z < 0.0f ) == ( b.z < 0.0f ) ) + { + return; + } + + float denominator = a.z - b.z; + if( fabsf( denominator ) <= CLIP_EPSILON ) + { + return; + } + + float t = a.z / denominator; + ClipPoint point = Lerp( a, b, t ); + if( CanPerspectiveDivide( point ) ) + { + points.push_back( point ); + } + } + + bool ProjectClipPoint( const ClipPoint& point, const TriViewport& viewport, Vector3& projected ) + { + if( !CanPerspectiveDivide( point ) ) + { + return false; + } + + float reciprocalW = 1.0f / point.w; + projected.x = viewport.x + ( 1.0f + point.x * reciprocalW ) * 0.5f * viewport.width; + projected.y = viewport.y + ( 1.0f - point.y * reciprocalW ) * 0.5f * viewport.height; + projected.z = viewport.minZ + point.z * reciprocalW * ( viewport.maxZ - viewport.minZ ); + return IsFinite( projected.x ) && IsFinite( projected.y ) && IsFinite( projected.z ); + } + + bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, const Matrix& viewProjection, const TriViewport& viewport, ProjectedBounds& bounds ) + { + Vector3 corners[8]; + corners[0] = bbMin; + corners[1] = Vector3( bbMin.x, bbMin.y, bbMax.z ); + corners[2] = Vector3( bbMax.x, bbMin.y, bbMin.z ); + corners[3] = Vector3( bbMax.x, bbMin.y, bbMax.z ); + corners[4] = bbMax; + corners[5] = Vector3( bbMax.x, bbMax.y, bbMin.z ); + corners[6] = Vector3( bbMin.x, bbMax.y, bbMax.z ); + corners[7] = Vector3( bbMin.x, bbMax.y, bbMin.z ); + + ClipPoint clipCorners[8]; + for( int i = 0; i < 8; ++i ) + { + clipCorners[i] = TransformPointToClip( corners[i], viewProjection ); + if( !IsFinite( clipCorners[i] ) ) + { + return false; + } + } + + if( IsTriviallyOutsideFrustum( clipCorners ) ) + { + return false; + } + + std::vector projectablePoints; + projectablePoints.reserve( 20 ); + for( int i = 0; i < 8; ++i ) + { + AddIfProjectable( clipCorners[i], projectablePoints ); + } + + static const int EDGES[12][2] = + { + { 0, 1 }, { 1, 3 }, { 3, 2 }, { 2, 0 }, + { 7, 6 }, { 6, 4 }, { 4, 5 }, { 5, 7 }, + { 0, 7 }, { 1, 6 }, { 2, 5 }, { 3, 4 } + }; + + for( int i = 0; i < 12; ++i ) + { + AddNearPlaneIntersection( clipCorners[EDGES[i][0]], clipCorners[EDGES[i][1]], projectablePoints ); + } + + if( projectablePoints.empty() ) + { + return false; + } + + Vector3 projected; + bool hasProjectedPoint = false; + float minX = 0.0f; + float minY = 0.0f; + float minZ = 0.0f; + float maxX = 0.0f; + float maxY = 0.0f; + + for( const ClipPoint& point : projectablePoints ) + { + if( !ProjectClipPoint( point, viewport, projected ) ) + { + continue; + } + + if( !hasProjectedPoint ) + { + minX = maxX = projected.x; + minY = maxY = projected.y; + minZ = projected.z; + hasProjectedPoint = true; + } + else + { + minX = std::min( minX, projected.x ); + maxX = std::max( maxX, projected.x ); + minY = std::min( minY, projected.y ); + maxY = std::max( maxY, projected.y ); + minZ = std::min( minZ, projected.z ); + } + } + + if( !hasProjectedPoint ) + { + return false; + } + + float width = maxX - minX; + float height = maxY - minY; + if( !IsFinite( width ) || !IsFinite( height ) || width <= 0.0f || height <= 0.0f ) + { + return false; + } + + float viewportLeft = static_cast( viewport.x ); + float viewportTop = static_cast( viewport.y ); + float viewportRight = viewportLeft + static_cast( viewport.width ); + float viewportBottom = viewportTop + static_cast( viewport.height ); + + bounds.x = minX; + bounds.y = minY; + bounds.z = minZ; + bounds.width = width; + bounds.height = height; + bounds.extendsOffscreen = minX < viewportLeft || minY < viewportTop || maxX > viewportRight || maxY > viewportBottom; + bounds.coversViewport = minX <= viewportLeft && minY <= viewportTop && maxX >= viewportRight && maxY >= viewportBottom; + return true; + } +} + Tr2ProjectBoundingBoxBracket::Tr2ProjectBoundingBoxBracket( IRoot* lockobj /*= NULL */ ) : m_minProjectedWidth( 0.0f ), @@ -29,7 +274,11 @@ Tr2ProjectBoundingBoxBracket::Tr2ProjectBoundingBoxBracket( IRoot* lockobj /*= N m_projectedHeight( 0.0f ), m_integerCoordinates( true ), m_screenMargin( 32.0f ), - m_cameraDistance( 0 ) + m_cameraDistance( 0 ), + m_isProjectionValid( false ), + m_containsCamera( false ), + m_extendsOffscreen( false ), + m_coversViewport( false ) { } @@ -38,130 +287,80 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) { if( !m_object ) { + SetEmptyProjection(); return; } if( !m_object->IsBoundingBoxReady() ) { + SetEmptyProjection(); return; } Vector3 bbMin, bbMax; if( !m_object->GetWorldBoundingBox( bbMin, bbMax ) ) { - return; - } - - Vector3 expansion( 0.5f, 0.5f, 0.5f ); - Vector3 expandedMin = bbMin - expansion; - Vector3 expandedMax = bbMax + expansion; - if( BoundingBoxIsInside( expandedMin, expandedMax, Tr2Renderer::GetViewPosition() ) ) - { - // Camera is inside bounding box - can't do any sensible projection SetEmptyProjection(); return; } Vector3 center = (bbMax + bbMin) * 0.5f; - Vector3 projectedCenter; - Matrix viewProj; - projectedCenter = TransformCoord( center, Tr2Renderer::GetViewTransform() ); - projectedCenter = TransformCoord( projectedCenter, Tr2Renderer::GetProjectionTransform() ); - Vec3TransformByViewport( projectedCenter, Tr2Renderer::GetViewport() ); - if( projectedCenter.z <= 0.0f || projectedCenter.z >= 1.0f ) - { - SetEmptyProjection(); - return; - } - Vector3 d = Tr2Renderer::GetViewPosition() - center; m_cameraDistance = Length( d ); - BoundingBoxProject( - bbMin, - bbMax, - Tr2Renderer::GetViewTransform(), - Tr2Renderer::GetProjectionTransform(), - Tr2Renderer::GetViewport() - ); - - if( bbMin.z <= 0.0f || bbMax.z >= 1.0f ) + const TriViewport& viewport = Tr2Renderer::GetViewport(); + if( BoundingBoxIsInside( bbMin, bbMax, Tr2Renderer::GetViewPosition() ) ) { - SetEmptyProjection(); + m_projectedX = static_cast( viewport.x ); + m_projectedY = static_cast( viewport.y ); + m_projectedZ = viewport.minZ; + m_projectedWidth = static_cast( viewport.width ); + m_projectedHeight = static_cast( viewport.height ); + m_isProjectionValid = true; + m_containsCamera = true; + m_extendsOffscreen = true; + m_coversViewport = true; + UpdateBracket(); return; } - m_projectedZ = std::min(bbMin.z, bbMax.z); - - unsigned int screenWidth; - unsigned int screenHeight; - Tr2Renderer::GetBackBufferDimensions( screenWidth, screenHeight ); - - if( (bbMin.x > screenWidth) || (bbMax.x < 0.0f) || (bbMin.y > screenHeight) || (bbMax.y < 0.0f) ) + Matrix viewProjection = Tr2Renderer::GetViewTransform() * Tr2Renderer::GetProjectionTransform(); + ProjectedBounds projectedBounds; + if( !ProjectBoundingBoxToViewport( bbMin, bbMax, viewProjection, viewport, projectedBounds ) ) { SetEmptyProjection(); return; } - bbMin.x = std::max( bbMin.x, m_screenMargin ); - bbMax.x = std::min( bbMax.x, (float)screenWidth - m_screenMargin ); - - bbMin.y = std::max( bbMin.y, m_screenMargin ); - bbMax.y = std::min( bbMax.y, (float)screenHeight - m_screenMargin ); - - m_projectedWidth = bbMax.x - bbMin.x; - m_projectedHeight = bbMax.y - bbMin.y; - - bool useCenter3d = false; - if( m_maxProjectedWidth > 0.0f || m_maxProjectedHeight > 0.0f ) - { - useCenter3d = true; - } - - float maxWidth = m_maxProjectedWidth; - if( maxWidth == 0.0f ) - { - maxWidth = 1e6f; - } - - if( m_projectedWidth < m_minProjectedWidth ) + m_projectedX = projectedBounds.x; + m_projectedY = projectedBounds.y; + m_projectedZ = projectedBounds.z; + m_projectedWidth = projectedBounds.width; + m_projectedHeight = projectedBounds.height; + m_containsCamera = false; + m_extendsOffscreen = projectedBounds.extendsOffscreen; + m_coversViewport = projectedBounds.coversViewport; + + float centerX = m_projectedX + m_projectedWidth * 0.5f; + float centerY = m_projectedY + m_projectedHeight * 0.5f; + if( m_minProjectedWidth > 0.0f && m_projectedWidth < m_minProjectedWidth ) { m_projectedWidth = m_minProjectedWidth; } - else if( m_projectedWidth > maxWidth ) - { - m_projectedWidth = maxWidth; - } - - float maxHeight = m_maxProjectedHeight; - if( maxHeight == 0.0f ) + else if( m_maxProjectedWidth > 0.0f && m_projectedWidth > m_maxProjectedWidth ) { - maxHeight = 1e6f; + m_projectedWidth = m_maxProjectedWidth; } - if( m_projectedHeight < m_minProjectedHeight ) + if( m_minProjectedHeight > 0.0f && m_projectedHeight < m_minProjectedHeight ) { m_projectedHeight = m_minProjectedHeight; } - else if( m_projectedHeight > maxHeight ) + else if( m_maxProjectedHeight > 0.0f && m_projectedHeight > m_maxProjectedHeight ) { - m_projectedHeight = maxHeight; + m_projectedHeight = m_maxProjectedHeight; } - float centerX; - float centerY; - if( useCenter3d ) - { - // Bounded brackets are centered around the center of the 3d bounding box - centerX = projectedCenter.x; - centerY = projectedCenter.y; - } - else - { - // Unbounded brackets are centered around the center of the projected bounding box - centerX = (bbMin.x + bbMax.x) * 0.5f; - centerY = (bbMin.y + bbMax.y) * 0.5f; - } m_projectedX = centerX - m_projectedWidth * 0.5f; m_projectedY = centerY - m_projectedHeight * 0.5f; @@ -173,62 +372,14 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) m_projectedHeight = floor( m_projectedHeight + 0.5f ); } - if( m_projectedX < m_screenMargin ) + if( !IsFinite( m_projectedX ) || !IsFinite( m_projectedY ) || !IsFinite( m_projectedWidth ) || !IsFinite( m_projectedHeight ) || m_projectedWidth <= 0.0f || m_projectedHeight <= 0.0f ) { - float d = m_screenMargin - m_projectedX; - if( d < m_projectedWidth - m_screenMargin ) - { - m_projectedX = m_screenMargin; - } - else - { - SetEmptyProjection(); - return; - } - } - - if( m_projectedY < m_screenMargin ) - { - float d = m_screenMargin - m_projectedY; - if( d < m_projectedHeight - m_screenMargin ) - { - m_projectedY = m_screenMargin; - } - else - { - SetEmptyProjection(); - return; - } - m_projectedY = m_screenMargin; - } - - if( m_projectedX + m_projectedWidth > screenWidth - m_screenMargin ) - { - m_projectedWidth = screenWidth - m_screenMargin - m_projectedX; - if( m_projectedWidth < m_screenMargin ) - { - SetEmptyProjection(); - return; - } - } - - if( m_projectedY + m_projectedHeight > screenHeight - m_screenMargin ) - { - m_projectedHeight = screenHeight - m_screenMargin - m_projectedY; - if( m_projectedHeight < m_screenMargin ) - { - SetEmptyProjection(); - return; - } + SetEmptyProjection(); + return; } - if( m_bracket ) - { - m_bracket->SetDisplayX( m_projectedX ); - m_bracket->SetDisplayY( m_projectedY ); - m_bracket->SetDisplayWidth( m_projectedWidth ); - m_bracket->SetDisplayHeight( m_projectedHeight ); - } + m_isProjectionValid = true; + UpdateBracket(); if( g_debugRenderer ) { @@ -236,13 +387,6 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) int y = (int)m_projectedY; g_debugRenderer->Printf( x, y, 0xffffffff, "%S", m_name.c_str() ); y += 16; - - g_debugRenderer->Printf( x, y, 0xffffffff, "(%5.2f, %5.2f, %5.2f)", bbMin.x, bbMin.y, bbMin.z ); - y += 16; - - g_debugRenderer->Printf( x, y, 0xffffffff, "(%5.2f, %5.2f, %5.2f)", bbMax.x, bbMax.y, bbMax.z ); - y += 16; - g_debugRenderer->Printf( x, y, 0xffffffff, "(%5.2f, %5.2f)", m_projectedWidth, m_projectedHeight ); } } @@ -254,7 +398,16 @@ void Tr2ProjectBoundingBoxBracket::SetEmptyProjection() m_projectedZ = 0.0f; m_projectedWidth = 0.0f; m_projectedHeight = 0.0f; + m_isProjectionValid = false; + m_containsCamera = false; + m_extendsOffscreen = false; + m_coversViewport = false; + + UpdateBracket(); +} +void Tr2ProjectBoundingBoxBracket::UpdateBracket() +{ if( m_bracket ) { m_bracket->SetDisplayX( m_projectedX ); @@ -262,4 +415,9 @@ void Tr2ProjectBoundingBoxBracket::SetEmptyProjection() m_bracket->SetDisplayWidth( m_projectedWidth ); m_bracket->SetDisplayHeight( m_projectedHeight ); } + + if( m_bracketUpdateCallback ) + { + m_bracketUpdateCallback.CallVoid( this ); + } } diff --git a/trinity/Tr2ProjectBoundingBoxBracket.h b/trinity/Tr2ProjectBoundingBoxBracket.h index e6406ad64..1af92e6ee 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.h +++ b/trinity/Tr2ProjectBoundingBoxBracket.h @@ -27,6 +27,7 @@ class Tr2ProjectBoundingBoxBracket : void UpdateValue( double time ); void SetEmptyProjection(); + void UpdateBracket(); protected: std::wstring m_name; @@ -58,6 +59,14 @@ class Tr2ProjectBoundingBoxBracket : float m_projectedHeight; float m_cameraDistance; float m_screenMargin; + + bool m_isProjectionValid; + bool m_containsCamera; + bool m_extendsOffscreen; + bool m_coversViewport; + + // An optional callback to call when projectBracket is updated. + BlueScriptCallback m_bracketUpdateCallback; }; TYPEDEF_BLUECLASS( Tr2ProjectBoundingBoxBracket ); diff --git a/trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp b/trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp index eceb55325..096894758 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp +++ b/trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp @@ -12,7 +12,7 @@ BLUE_DEFINE( Tr2ProjectBoundingBoxBracket ); const Be::ClassInfo* Tr2ProjectBoundingBoxBracket::ExposeToBlue() { - EXPOSURE_BEGIN( Tr2ProjectBoundingBoxBracket, "Projects a 3D bounding box to 2D for brackets \n:jessica-deprecated: True" ) + EXPOSURE_BEGIN( Tr2ProjectBoundingBoxBracket, "Projects a 3D bounding box to 2D for brackets" ) MAP_INTERFACE( ITriFunction ) MAP_INTERFACE( Tr2ProjectBoundingBoxBracket ) @@ -136,8 +136,48 @@ const Be::ClassInfo* Tr2ProjectBoundingBoxBracket::ExposeToBlue() ( "screenMargin", m_screenMargin, - "Brackets are never projected outside the screen - this controls the margin from" - "\nthe edges of the screen.", + "Deprecated compatibility attribute. Bounding-box brackets are no longer" + "\nclamped to a screen margin.", + Be::READWRITE + ) + + MAP_ATTRIBUTE + ( + "isProjectionValid", + m_isProjectionValid, + "True when the bounding box produced a valid projected rect.", + Be::READ + ) + + MAP_ATTRIBUTE + ( + "containsCamera", + m_containsCamera, + "True when the camera is inside the tracked bounding box.", + Be::READ + ) + + MAP_ATTRIBUTE + ( + "extendsOffscreen", + m_extendsOffscreen, + "True when the projected rect extends beyond the current viewport.", + Be::READ + ) + + MAP_ATTRIBUTE + ( + "coversViewport", + m_coversViewport, + "True when the projected rect covers the current viewport.", + Be::READ + ) + + MAP_ATTRIBUTE + ( + "bracketUpdateCallback", + m_bracketUpdateCallback, + "An optional callback that is called whenever the bracket projection is updated.", Be::READWRITE ) From c68a5902ddaebd863142ff86192bd319be0be8d9 Mon Sep 17 00:00:00 2001 From: breki Date: Fri, 17 Jul 2026 13:24:59 +0000 Subject: [PATCH 03/10] update boundingBoxBracket after feedback and review --- trinity/CMakeLists.txt | 1 + trinity/Eve/EveChildBounds.h | 37 +++ trinity/Eve/EveEffectRoot2.cpp | 80 ++--- trinity/Eve/EvePlanet.cpp | 18 +- trinity/Eve/EvePlanet.h | 3 +- trinity/Eve/EveTransform.cpp | 100 ++---- trinity/Include/ITr2BoundingBox.h | 8 + trinity/Tr2ProjectBoundingBoxBracket.cpp | 291 +++++++++++++++++- trinity/Tr2ProjectBoundingBoxBracket.h | 3 - trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp | 39 ++- trinity/Utilities/BoundingBox.cpp | 21 ++ trinity/Utilities/BoundingBox.h | 5 + trinity/Utilities/BoundingSphere.cpp | 6 + trinity/Utilities/BoundingSphere.h | 1 + 14 files changed, 447 insertions(+), 166 deletions(-) create mode 100644 trinity/Eve/EveChildBounds.h diff --git a/trinity/CMakeLists.txt b/trinity/CMakeLists.txt index 606b971e0..356e97898 100644 --- a/trinity/CMakeLists.txt +++ b/trinity/CMakeLists.txt @@ -754,6 +754,7 @@ set(_SOURCES Eve/EveCamera.cpp Eve/EveCamera.h Eve/EveCamera_Blue.cpp + Eve/EveChildBounds.h Eve/EveComponentRegistry.cpp Eve/EveComponentRegistry.h Eve/EveComponentRegistry_Blue.cpp diff --git a/trinity/Eve/EveChildBounds.h b/trinity/Eve/EveChildBounds.h new file mode 100644 index 000000000..bcc50a731 --- /dev/null +++ b/trinity/Eve/EveChildBounds.h @@ -0,0 +1,37 @@ +// Copyright © 2026 Fenris Creations + +#pragma once +#ifndef EveChildBounds_H +#define EveChildBounds_H + +#include "ITr2BoundingBox.h" +#include "Utilities/BoundingBox.h" +#include "Utilities/BoundingSphere.h" + +// Best-effort world-space bounds for a scene-graph child: prefers the child's +// axis-aligned box when it implements ITr2BoundingBox, otherwise falls back to +// its bounding sphere. ChildT only needs GetBoundingSphere( Vector4&, +// BoundingSphereQuery ); there is no common bounds interface across the Eve +// child types (IEveTransform, IEveSpaceObjectChild, ...). +template +bool GetChildWorldBounds( const ChildT* child, Vector3& min, Vector3& max ) +{ + if( auto childBoundingBox = dynamic_cast( child ) ) + { + if( childBoundingBox->GetWorldBoundingBox( min, max ) ) + { + return true; + } + } + + Vector4 childSphere; + if( child->GetBoundingSphere( childSphere, EVE_BOUNDS_WITH_CHILDREN ) && BoundingSphereIsValid( childSphere ) ) + { + BoundingBoxInitialize( childSphere, min, max ); + return true; + } + + return false; +} + +#endif // EveChildBounds_H diff --git a/trinity/Eve/EveEffectRoot2.cpp b/trinity/Eve/EveEffectRoot2.cpp index ac00a03f1..1cb450396 100644 --- a/trinity/Eve/EveEffectRoot2.cpp +++ b/trinity/Eve/EveEffectRoot2.cpp @@ -10,6 +10,7 @@ #include "Tr2LightManager.h" #include "Controllers/ITr2Controller.h" #include "Curves/TriCurveSet.h" +#include "Eve/EveChildBounds.h" #include "Eve/EveUpdateContext.h" #include "Eve/SpaceObject/EveSpaceObject2.h" #include "Eve/SpaceObject/Children/EveChildContainer.h" @@ -18,56 +19,6 @@ extern float g_eveSpaceObjectResourceUnloadingTimeThreshold; -namespace -{ -bool IsFinite( const Vector3& v ) -{ - return std::isfinite( v.x ) && std::isfinite( v.y ) && std::isfinite( v.z ); -} - -bool IsValidBoundingSphere( const Vector4& sphere ) -{ - return sphere.w > 0.0f && - std::isfinite( sphere.x ) && - std::isfinite( sphere.y ) && - std::isfinite( sphere.z ) && - std::isfinite( sphere.w ); -} - -void IncludeWorldBounds( const Vector3& boundsMin, const Vector3& boundsMax, Vector3& min, Vector3& max, bool& valid ) -{ - if( !IsFinite( boundsMin ) || !IsFinite( boundsMax ) ) - { - return; - } - - if( !valid ) - { - min = boundsMin; - max = boundsMax; - valid = true; - } - else - { - BoundingBoxUpdate( min, max, boundsMin, boundsMax ); - } -} - -void IncludeWorldSphereBounds( const Vector4& sphere, Vector3& min, Vector3& max, bool& valid ) -{ - if( !IsValidBoundingSphere( sphere ) ) - { - return; - } - - Vector3 sphereMin; - Vector3 sphereMax; - BoundingBoxInitialize( sphere, sphereMin, sphereMax ); - IncludeWorldBounds( sphereMin, sphereMax, min, max, valid ); -} -} - - EveEffectRoot2::EveEffectRoot2( IRoot* lockobj ) : PARENTLOCK( m_observers ), PARENTLOCK( m_lights ), @@ -451,7 +402,7 @@ void EveEffectRoot2::GetModelCenterWorldPosition( Vector3& position ) const bool EveEffectRoot2::GetLocalBoundingBox( Vector3& min, Vector3& max ) { - if( !IsValidBoundingSphere( m_boundingSphere ) ) + if( !BoundingSphereIsValid( m_boundingSphere ) ) { return false; } @@ -468,32 +419,41 @@ void EveEffectRoot2::GetLocalToWorldTransform( Matrix& transform ) const bool EveEffectRoot2::GetWorldBoundingBox( Vector3& min, Vector3& max ) const { - bool valid = false; + CcpMath::AxisAlignedBox bounds; - if( IsValidBoundingSphere( m_boundingSphere ) ) + if( BoundingSphereIsValid( m_boundingSphere ) ) { Vector3 rootMin; Vector3 rootMax; BoundingBoxInitialize( m_boundingSphere, rootMin, rootMax ); BoundingBoxTransform( rootMin, rootMax, m_lastUpdateMatrix ); - IncludeWorldBounds( rootMin, rootMax, min, max, valid ); + BoundingBoxInclude( bounds, rootMin, rootMax ); } for( auto it = m_effectChildren.begin(); it != m_effectChildren.end(); ++it ) { - Vector4 childBounds; - if( ( *it )->GetBoundingSphere( childBounds, EVE_BOUNDS_WITH_CHILDREN ) ) + const IEveSpaceObjectChild* child = *it; + Vector3 childMin; + Vector3 childMax; + if( GetChildWorldBounds( child, childMin, childMax ) ) { - IncludeWorldSphereBounds( childBounds, min, max, valid ); + BoundingBoxInclude( bounds, childMin, childMax ); } } - return valid; + if( !bounds.IsInitialized() ) + { + return false; + } + + min = bounds.m_min; + max = bounds.m_max; + return true; } bool EveEffectRoot2::IsBoundingBoxReady() const { - if( IsValidBoundingSphere( m_boundingSphere ) ) + if( BoundingSphereIsValid( m_boundingSphere ) ) { return true; } @@ -501,7 +461,7 @@ bool EveEffectRoot2::IsBoundingBoxReady() const for( auto it = m_effectChildren.begin(); it != m_effectChildren.end(); ++it ) { Vector4 childBounds; - if( ( *it )->GetBoundingSphere( childBounds, EVE_BOUNDS_WITH_CHILDREN ) && IsValidBoundingSphere( childBounds ) ) + if( ( *it )->GetBoundingSphere( childBounds, EVE_BOUNDS_WITH_CHILDREN ) && BoundingSphereIsValid( childBounds ) ) { return true; } diff --git a/trinity/Eve/EvePlanet.cpp b/trinity/Eve/EvePlanet.cpp index f5b664801..01a29667b 100644 --- a/trinity/Eve/EvePlanet.cpp +++ b/trinity/Eve/EvePlanet.cpp @@ -151,6 +151,18 @@ Quaternion EvePlanet::GetWorldRotation() } bool EvePlanet::GetWorldBoundingBox( Vector3& min, Vector3& max ) const +{ + Vector4 sphere; + if( !GetWorldBoundingSphere( sphere ) ) + { + return false; + } + + BoundingBoxInitialize( sphere, min, max ); + return true; +} + +bool EvePlanet::GetWorldBoundingSphere( Vector4& sphere ) const { if( m_radius <= 0.0f ) { @@ -159,11 +171,7 @@ bool EvePlanet::GetWorldBoundingBox( Vector3& min, Vector3& max ) const const float renderScale = m_renderScale > 0.0f ? m_renderScale : 1.0f; const Matrix scaledTransform = CalculatePlanetScaleTransform( m_worldTransform, renderScale ); - const Vector3 center = scaledTransform.GetTranslation(); - const float radius = m_radius / renderScale; - const Vector4 sphere( center, radius ); - - BoundingBoxInitialize( sphere, min, max ); + sphere = Vector4( scaledTransform.GetTranslation(), m_radius / renderScale ); return true; } diff --git a/trinity/Eve/EvePlanet.h b/trinity/Eve/EvePlanet.h index 83227d683..3f0e00445 100644 --- a/trinity/Eve/EvePlanet.h +++ b/trinity/Eve/EvePlanet.h @@ -60,7 +60,8 @@ BLUE_CLASS( EvePlanet ) : virtual Quaternion GetWorldRotation(); // ITr2BoundingBox - bool GetWorldBoundingBox( Vector3& min, Vector3& max ) const override; + bool GetWorldBoundingBox( Vector3 & min, Vector3 & max ) const override; + bool GetWorldBoundingSphere( Vector4 & sphere ) const override; bool IsBoundingBoxReady() const override; // ITr2SecondaryLightSource diff --git a/trinity/Eve/EveTransform.cpp b/trinity/Eve/EveTransform.cpp index da37b510c..8e9842b50 100644 --- a/trinity/Eve/EveTransform.cpp +++ b/trinity/Eve/EveTransform.cpp @@ -10,6 +10,7 @@ #include "Particle/Tr2ParticleSystem.h" #include "Particle/ITr2GenericEmitter.h" #include "EveUpdateContext.h" +#include "EveChildBounds.h" #include "EveLODHelper.h" #include "Curves/TriCurveSet.h" #include "Tr2Mesh.h" @@ -24,33 +25,6 @@ bool HasOverrideBounds( const Vector3& boundsMin, const Vector3& boundsMax ) return boundsMax.x != boundsMin.x || boundsMax.y != boundsMin.y || boundsMax.z != boundsMin.z; } -void IncludeBounds( const Vector3& boundsMin, const Vector3& boundsMax, Vector3& min, Vector3& max, bool& valid ) -{ - if( !valid ) - { - min = boundsMin; - max = boundsMax; - valid = true; - } - else - { - BoundingBoxUpdate( min, max, boundsMin, boundsMax ); - } -} - -void IncludeSphereBounds( const Vector4& sphere, Vector3& min, Vector3& max, bool& valid ) -{ - if( sphere.w <= 0.0f ) - { - return; - } - - Vector3 sphereMin; - Vector3 sphereMax; - BoundingBoxInitialize( sphere, sphereMin, sphereMax ); - IncludeBounds( sphereMin, sphereMax, min, max, valid ); -} - bool GetDirectLocalBounds( const Vector3& overrideBoundsMin, const Vector3& overrideBoundsMax, Tr2MeshBase* mesh, Vector3& min, Vector3& max ) { if( HasOverrideBounds( overrideBoundsMin, overrideBoundsMax ) ) @@ -400,78 +374,72 @@ void EveTransform::GetRenderables( std::vector& renderables ) bool EveTransform::GetLocalBoundingBox( Vector3& min, Vector3& max ) { - bool valid = GetDirectLocalBounds( m_overrideBoundsMin, m_overrideBoundsMax, m_mesh, min, max ); + CcpMath::AxisAlignedBox bounds; - Matrix inverseWorldTransform; - const bool hasInverseWorldTransform = Inverse( inverseWorldTransform, m_worldTransform ); + Vector3 directMin; + Vector3 directMax; + if( GetDirectLocalBounds( m_overrideBoundsMin, m_overrideBoundsMax, m_mesh, directMin, directMax ) ) + { + BoundingBoxInclude( bounds, directMin, directMax ); + } - if( hasInverseWorldTransform ) + Matrix inverseWorldTransform; + if( Inverse( inverseWorldTransform, m_worldTransform ) ) { for( auto it = m_children.begin(); it != m_children.end(); ++it ) { + const IEveTransform* child = *it; Vector3 childMin; Vector3 childMax; - bool hasChildBounds = false; - - if( auto childBoundingBox = dynamic_cast( *it ) ) - { - hasChildBounds = childBoundingBox->GetWorldBoundingBox( childMin, childMax ); - } - - if( !hasChildBounds ) - { - Vector4 childSphere; - if( ( *it )->GetBoundingSphere( childSphere, EVE_BOUNDS_WITH_CHILDREN ) && childSphere.w > 0.0f ) - { - BoundingBoxInitialize( childSphere, childMin, childMax ); - hasChildBounds = true; - } - } - - if( hasChildBounds ) + if( GetChildWorldBounds( child, childMin, childMax ) ) { BoundingBoxTransform( childMin, childMax, inverseWorldTransform ); - IncludeBounds( childMin, childMax, min, max, valid ); + BoundingBoxInclude( bounds, childMin, childMax ); } } } - return valid; + if( !bounds.IsInitialized() ) + { + return false; + } + + min = bounds.m_min; + max = bounds.m_max; + return true; } bool EveTransform::GetWorldBoundingBox( Vector3& min, Vector3& max ) const { - bool valid = false; + CcpMath::AxisAlignedBox bounds; Vector3 localMin; Vector3 localMax; if( GetDirectLocalBounds( m_overrideBoundsMin, m_overrideBoundsMax, m_mesh, localMin, localMax ) ) { BoundingBoxTransform( localMin, localMax, m_worldTransform ); - IncludeBounds( localMin, localMax, min, max, valid ); + BoundingBoxInclude( bounds, localMin, localMax ); } for( auto it = m_children.begin(); it != m_children.end(); ++it ) { + const IEveTransform* child = *it; Vector3 childMin; Vector3 childMax; - if( auto childBoundingBox = dynamic_cast( *it ) ) + if( GetChildWorldBounds( child, childMin, childMax ) ) { - if( childBoundingBox->GetWorldBoundingBox( childMin, childMax ) ) - { - IncludeBounds( childMin, childMax, min, max, valid ); - continue; - } + BoundingBoxInclude( bounds, childMin, childMax ); } + } - Vector4 childSphere; - if( ( *it )->GetBoundingSphere( childSphere, EVE_BOUNDS_WITH_CHILDREN ) ) - { - IncludeSphereBounds( childSphere, min, max, valid ); - } + if( !bounds.IsInitialized() ) + { + return false; } - return valid; + min = bounds.m_min; + max = bounds.m_max; + return true; } bool EveTransform::IsBoundingBoxReady() const @@ -494,7 +462,7 @@ bool EveTransform::IsBoundingBoxReady() const } Vector4 childSphere; - if( ( *it )->GetBoundingSphere( childSphere, EVE_BOUNDS_WITH_CHILDREN ) && childSphere.w > 0.0f ) + if( ( *it )->GetBoundingSphere( childSphere, EVE_BOUNDS_WITH_CHILDREN ) && BoundingSphereIsValid( childSphere ) ) { return true; } diff --git a/trinity/Include/ITr2BoundingBox.h b/trinity/Include/ITr2BoundingBox.h index 0a54ce568..7003fa123 100644 --- a/trinity/Include/ITr2BoundingBox.h +++ b/trinity/Include/ITr2BoundingBox.h @@ -6,12 +6,20 @@ #define ITr2BoundingBox_h struct Vector3; +struct Vector4; BLUE_INTERFACE( ITr2BoundingBox ) : IRoot { virtual bool GetWorldBoundingBox( Vector3 & min, Vector3 & max ) const = 0; virtual bool IsBoundingBoxReady( void ) const = 0; + + // Optional tighter bound for genuinely spherical objects, as (center.xyz, radius.w). + // Consumers that can exploit it (bracket projection) prefer it over the box. + virtual bool GetWorldBoundingSphere( Vector4& /*sphere*/ ) const + { + return false; + } }; #endif \ No newline at end of file diff --git a/trinity/Tr2ProjectBoundingBoxBracket.cpp b/trinity/Tr2ProjectBoundingBoxBracket.cpp index e2781bf8e..43acdbdc5 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.cpp +++ b/trinity/Tr2ProjectBoundingBoxBracket.cpp @@ -10,6 +10,7 @@ #include "TriViewport.h" #include "Sprite2d/Tr2Sprite2dContainer.h" #include "Utilities/BoundingBox.h" +#include "Utilities/BoundingSphere.h" #include "include/ITr2DebugRenderer.h" extern ITr2DebugRendererPtr g_debugRenderer; @@ -268,6 +269,207 @@ bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, c bounds.coversViewport = minX <= viewportLeft && minY <= viewportTop && maxX >= viewportRight && maxY >= viewportBottom; return true; } + +enum class SphereProjection +{ + PROJECTED, + EMPTY, + UNSUPPORTED +}; + +struct AxisBounds +{ + float minSlope; + float maxSlope; + bool hasBounds; +}; + +// Exact near-clipped silhouette bounds of a sphere in one screen axis, as view-space +// slopes (axis / forward depth). The per-axis 2D tangent construction is exact for the +// 3D silhouette: the cross-axis coordinate drops out of the tangency condition at the +// axis extremes. +AxisBounds ComputeSphereAxisBounds( float a, float b, float r, float nearDepth ) +{ + AxisBounds result{ 0.0f, 0.0f, false }; + + float candidates[4]; + int candidateCount = 0; + + const float m2 = a * a + b * b - r * r; + const float denominator = b * b - r * r; + if( m2 > 0.0f && fabsf( denominator ) > CLIP_EPSILON ) + { + const float m = sqrtf( m2 ); + const float tangents[2] = { ( a * b - r * m ) / denominator, ( a * b + r * m ) / denominator }; + for( float u : tangents ) + { + // A tangent whose touch point lies behind the near plane does not bound + // the visible silhouette; the near-plane circle takes over there. + const float tangentDepth = ( a * u + b ) / ( 1.0f + u * u ); + if( IsFinite( u ) && tangentDepth >= nearDepth ) + { + candidates[candidateCount++] = u; + } + } + } + + const float nearOffset = nearDepth - b; + const float nearCircleSq = r * r - nearOffset * nearOffset; + if( nearCircleSq > 0.0f ) + { + const float nearCircleRadius = sqrtf( nearCircleSq ); + candidates[candidateCount++] = ( a - nearCircleRadius ) / nearDepth; + candidates[candidateCount++] = ( a + nearCircleRadius ) / nearDepth; + } + + if( candidateCount == 0 ) + { + return result; + } + + result.minSlope = result.maxSlope = candidates[0]; + for( int i = 1; i < candidateCount; ++i ) + { + result.minSlope = std::min( result.minSlope, candidates[i] ); + result.maxSlope = std::max( result.maxSlope, candidates[i] ); + } + result.hasBounds = true; + return result; +} + +// Requires a rigid view transform and a symmetric, non-oblique perspective projection; +// reports UNSUPPORTED otherwise so the caller can fall back to the box path. +SphereProjection ProjectBoundingSphereToViewport( const Vector4& sphere, const Matrix& view, const Matrix& projection, const TriViewport& viewport, ProjectedBounds& bounds ) +{ + const float radius = sphere.w; + if( radius <= 0.0f ) + { + return SphereProjection::UNSUPPORTED; + } + + const float handedness = projection._34; + if( fabsf( fabsf( handedness ) - 1.0f ) > CLIP_EPSILON || + projection._11 <= 0.0f || projection._22 <= 0.0f || projection._33 == 0.0f || + projection._12 != 0.0f || projection._13 != 0.0f || projection._14 != 0.0f || + projection._21 != 0.0f || projection._23 != 0.0f || projection._24 != 0.0f || + projection._31 != 0.0f || projection._32 != 0.0f || + projection._41 != 0.0f || projection._42 != 0.0f || projection._44 != 0.0f ) + { + return SphereProjection::UNSUPPORTED; + } + + const float nearDepth = -handedness * projection._43 / projection._33; + if( !IsFinite( nearDepth ) || nearDepth <= 0.0f ) + { + return SphereProjection::UNSUPPORTED; + } + + const Vector3 centerView = TransformCoord( BoundingSphereGetCenter( sphere ), view ); + const float forward = handedness * centerView.z; + if( !IsFinite( centerView.x ) || !IsFinite( centerView.y ) || !IsFinite( forward ) ) + { + return SphereProjection::UNSUPPORTED; + } + + if( forward + radius < nearDepth ) + { + return SphereProjection::EMPTY; + } + + const float farDenominator = projection._33 - handedness; + if( fabsf( farDenominator ) > CLIP_EPSILON ) + { + const float farDepth = -handedness * projection._43 / farDenominator; + if( IsFinite( farDepth ) && farDepth > nearDepth && forward - radius > farDepth ) + { + return SphereProjection::EMPTY; + } + } + + const AxisBounds xBounds = ComputeSphereAxisBounds( centerView.x, forward, radius, nearDepth ); + const AxisBounds yBounds = ComputeSphereAxisBounds( centerView.y, forward, radius, nearDepth ); + if( !xBounds.hasBounds || !yBounds.hasBounds ) + { + return SphereProjection::EMPTY; + } + + const float minNdcX = projection._11 * xBounds.minSlope; + const float maxNdcX = projection._11 * xBounds.maxSlope; + const float minNdcY = projection._22 * yBounds.minSlope; + const float maxNdcY = projection._22 * yBounds.maxSlope; + if( !IsFinite( minNdcX ) || !IsFinite( maxNdcX ) || !IsFinite( minNdcY ) || !IsFinite( maxNdcY ) ) + { + return SphereProjection::UNSUPPORTED; + } + + if( maxNdcX < -1.0f || minNdcX > 1.0f || maxNdcY < -1.0f || minNdcY > 1.0f ) + { + return SphereProjection::EMPTY; + } + + const float viewportLeft = static_cast( viewport.x ); + const float viewportTop = static_cast( viewport.y ); + const float viewportWidth = static_cast( viewport.width ); + const float viewportHeight = static_cast( viewport.height ); + + const float minX = viewportLeft + ( 1.0f + minNdcX ) * 0.5f * viewportWidth; + const float maxX = viewportLeft + ( 1.0f + maxNdcX ) * 0.5f * viewportWidth; + const float minY = viewportTop + ( 1.0f - maxNdcY ) * 0.5f * viewportHeight; + const float maxY = viewportTop + ( 1.0f - minNdcY ) * 0.5f * viewportHeight; + + const float width = maxX - minX; + const float height = maxY - minY; + if( !IsFinite( width ) || !IsFinite( height ) || width <= 0.0f || height <= 0.0f ) + { + return SphereProjection::EMPTY; + } + + const float centerDistance = Length( centerView ); + const float nearestDepth = std::max( nearDepth, centerDistance - radius ); + const float ndcZ = ( handedness * nearestDepth * projection._33 + projection._43 ) / nearestDepth; + const float clampedNdcZ = std::min( 1.0f, std::max( 0.0f, ndcZ ) ); + + const float viewportRight = viewportLeft + viewportWidth; + const float viewportBottom = viewportTop + viewportHeight; + + bounds.x = minX; + bounds.y = minY; + bounds.z = viewport.minZ + clampedNdcZ * ( viewport.maxZ - viewport.minZ ); + bounds.width = width; + bounds.height = height; + bounds.extendsOffscreen = minX < viewportLeft || minY < viewportTop || maxX > viewportRight || maxY > viewportBottom; + bounds.coversViewport = minX <= viewportLeft && minY <= viewportTop && maxX >= viewportRight && maxY >= viewportBottom; + return SphereProjection::PROJECTED; +} + +bool ClampToScreenMargin( const TriViewport& viewport, float margin, float& x, float& y, float& width, float& height ) +{ + if( margin <= 0.0f ) + { + return true; + } + + float left = static_cast( viewport.x ) + margin; + float top = static_cast( viewport.y ) + margin; + float right = static_cast( viewport.x ) + static_cast( viewport.width ) - margin; + float bottom = static_cast( viewport.y ) + static_cast( viewport.height ) - margin; + + float minX = std::max( x, left ); + float minY = std::max( y, top ); + float maxX = std::min( x + width, right ); + float maxY = std::min( y + height, bottom ); + + if( maxX <= minX || maxY <= minY ) + { + return false; + } + + x = minX; + y = minY; + width = maxX - minX; + height = maxY - minY; + return true; +} } @@ -282,7 +484,7 @@ Tr2ProjectBoundingBoxBracket::Tr2ProjectBoundingBoxBracket( IRoot* lockobj /*= N m_projectedWidth( 0.0f ), m_projectedHeight( 0.0f ), m_integerCoordinates( true ), - m_screenMargin( 32.0f ), + m_screenMargin( 0.0f ), m_cameraDistance( 0 ), m_isProjectionValid( false ), m_containsCamera( false ), @@ -306,25 +508,44 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) return; } + Vector4 sphere( 0.0f, 0.0f, 0.0f, 0.0f ); + const bool hasSphere = m_object->GetWorldBoundingSphere( sphere ) && sphere.w > 0.0f; + Vector3 bbMin, bbMax; - if( !m_object->GetWorldBoundingBox( bbMin, bbMax ) ) + bool hasBox = false; + Vector3 center; + if( hasSphere ) { - SetEmptyProjection(); - return; + center = BoundingSphereGetCenter( sphere ); + } + else + { + if( !m_object->GetWorldBoundingBox( bbMin, bbMax ) ) + { + SetEmptyProjection(); + return; + } + hasBox = true; + center = ( bbMax + bbMin ) * 0.5f; } - Vector3 center = ( bbMax + bbMin ) * 0.5f; Vector3 d = Tr2Renderer::GetViewPosition() - center; m_cameraDistance = Length( d ); const TriViewport& viewport = Tr2Renderer::GetViewport(); - if( BoundingBoxIsInside( bbMin, bbMax, Tr2Renderer::GetViewPosition() ) ) + const bool cameraInside = hasSphere ? m_cameraDistance <= sphere.w : BoundingBoxIsInside( bbMin, bbMax, Tr2Renderer::GetViewPosition() ); + if( cameraInside ) { m_projectedX = static_cast( viewport.x ); m_projectedY = static_cast( viewport.y ); m_projectedZ = viewport.minZ; m_projectedWidth = static_cast( viewport.width ); m_projectedHeight = static_cast( viewport.height ); + if( !ClampToScreenMargin( viewport, m_screenMargin, m_projectedX, m_projectedY, m_projectedWidth, m_projectedHeight ) ) + { + SetEmptyProjection(); + return; + } m_isProjectionValid = true; m_containsCamera = true; m_extendsOffscreen = true; @@ -335,10 +556,34 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) Matrix viewProjection = Tr2Renderer::GetViewTransform() * Tr2Renderer::GetProjectionTransform(); ProjectedBounds projectedBounds; - if( !ProjectBoundingBoxToViewport( bbMin, bbMax, viewProjection, viewport, projectedBounds ) ) + bool hasProjection = false; + if( hasSphere ) { - SetEmptyProjection(); - return; + switch( ProjectBoundingSphereToViewport( sphere, Tr2Renderer::GetViewTransform(), Tr2Renderer::GetProjectionTransform(), viewport, projectedBounds ) ) + { + case SphereProjection::PROJECTED: + hasProjection = true; + break; + case SphereProjection::EMPTY: + SetEmptyProjection(); + return; + case SphereProjection::UNSUPPORTED: + break; + } + } + + if( !hasProjection ) + { + if( !hasBox && !m_object->GetWorldBoundingBox( bbMin, bbMax ) ) + { + SetEmptyProjection(); + return; + } + if( !ProjectBoundingBoxToViewport( bbMin, bbMax, viewProjection, viewport, projectedBounds ) ) + { + SetEmptyProjection(); + return; + } } m_projectedX = projectedBounds.x; @@ -352,6 +597,19 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) float centerX = m_projectedX + m_projectedWidth * 0.5f; float centerY = m_projectedY + m_projectedHeight * 0.5f; + if( m_maxProjectedWidth > 0.0f || m_maxProjectedHeight > 0.0f ) + { + // Bounded brackets are anchored on the projected 3d box center, not the projected + // rect center, unless the box center is behind the near plane. + ClipPoint clipCenter = TransformPointToClip( center, viewProjection ); + Vector3 projectedCenter; + if( clipCenter.z >= 0.0f && clipCenter.w > 0.0f && ProjectClipPoint( clipCenter, viewport, projectedCenter ) ) + { + centerX = projectedCenter.x; + centerY = projectedCenter.y; + } + } + if( m_minProjectedWidth > 0.0f && m_projectedWidth < m_minProjectedWidth ) { m_projectedWidth = m_minProjectedWidth; @@ -387,13 +645,19 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) return; } + if( !ClampToScreenMargin( viewport, m_screenMargin, m_projectedX, m_projectedY, m_projectedWidth, m_projectedHeight ) ) + { + SetEmptyProjection(); + return; + } + m_isProjectionValid = true; UpdateBracket(); if( g_debugRenderer ) { - int x = (int)m_projectedX; - int y = (int)m_projectedY; + int x = static_cast( m_projectedX ); + int y = static_cast( m_projectedY ); g_debugRenderer->Printf( x, y, 0xffffffff, "%S", m_name.c_str() ); y += 16; g_debugRenderer->Printf( x, y, 0xffffffff, "(%5.2f, %5.2f)", m_projectedWidth, m_projectedHeight ); @@ -424,9 +688,4 @@ void Tr2ProjectBoundingBoxBracket::UpdateBracket() m_bracket->SetDisplayWidth( m_projectedWidth ); m_bracket->SetDisplayHeight( m_projectedHeight ); } - - if( m_bracketUpdateCallback ) - { - m_bracketUpdateCallback.CallVoid( this ); - } } diff --git a/trinity/Tr2ProjectBoundingBoxBracket.h b/trinity/Tr2ProjectBoundingBoxBracket.h index da82a3c62..4aa387a1f 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.h +++ b/trinity/Tr2ProjectBoundingBoxBracket.h @@ -60,9 +60,6 @@ class Tr2ProjectBoundingBoxBracket : public ITriFunction bool m_containsCamera; bool m_extendsOffscreen; bool m_coversViewport; - - // An optional callback to call when projectBracket is updated. - BlueScriptCallback m_bracketUpdateCallback; }; TYPEDEF_BLUECLASS( Tr2ProjectBoundingBoxBracket ); diff --git a/trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp b/trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp index 96ef6f626..d9e3c7870 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp +++ b/trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp @@ -40,25 +40,29 @@ const Be::ClassInfo* Tr2ProjectBoundingBoxBracket::ExposeToBlue() MAP_ATTRIBUTE( "minProjectedWidth", m_minProjectedWidth, - "Minimum width after projection", + "Minimum width after projection. Not applied while containsCamera\n" + "is true: the rect is the full viewport inset by screenMargin.", Be::READWRITE ) MAP_ATTRIBUTE( "minProjectedHeight", m_minProjectedHeight, - "Minimum height after projection", + "Minimum height after projection. Not applied while containsCamera\n" + "is true: the rect is the full viewport inset by screenMargin.", Be::READWRITE ) MAP_ATTRIBUTE( "maxProjectedWidth", m_maxProjectedWidth, - "Maximum width after projection", + "Maximum width after projection. Not applied while containsCamera\n" + "is true: the rect is the full viewport inset by screenMargin.", Be::READWRITE ) MAP_ATTRIBUTE( "maxProjectedHeight", m_maxProjectedHeight, - "Maximum height after projection", + "Maximum height after projection. Not applied while containsCamera\n" + "is true: the rect is the full viewport inset by screenMargin.", Be::READWRITE ) MAP_ATTRIBUTE( @@ -90,7 +94,9 @@ const Be::ClassInfo* Tr2ProjectBoundingBoxBracket::ExposeToBlue() "cameraDistance", m_cameraDistance, "Distance of the object from the camera, as determined by the\n" - "center of the bounding box.", + "center of the bounding box. Measured in the space the object is\n" + "rendered in: render-scaled objects (e.g. planets) report the\n" + "distance to their scaled proxy, not the true world distance.", Be::READ ) MAP_ATTRIBUTE( @@ -102,8 +108,9 @@ const Be::ClassInfo* Tr2ProjectBoundingBoxBracket::ExposeToBlue() MAP_ATTRIBUTE( "screenMargin", m_screenMargin, - "Deprecated compatibility attribute. Bounding-box brackets are no longer" - "\nclamped to a screen margin.", + "If greater than zero, the projected rect is clamped inside the viewport" + "\ninset by this many pixels, and the projection becomes empty when nothing" + "\nremains inside that safe frame. Zero (default) disables clamping.", Be::READWRITE ) MAP_ATTRIBUTE( @@ -121,20 +128,22 @@ const Be::ClassInfo* Tr2ProjectBoundingBoxBracket::ExposeToBlue() MAP_ATTRIBUTE( "extendsOffscreen", m_extendsOffscreen, - "True when the projected rect extends beyond the current viewport.", + "True when the raw projection of the bounding box extends beyond the\n" + "current viewport. Describes the projection itself, not the output rect:\n" + "min/max sizing, recentering and screenMargin clamping do not affect\n" + "this flag. Compare projectedX/Y/Width/Height against the viewport if\n" + "you need the output rect's overlap instead. Always true when\n" + "containsCamera is true.", Be::READ ) MAP_ATTRIBUTE( "coversViewport", m_coversViewport, - "True when the projected rect covers the current viewport.", + "True when the raw projection of the bounding box covers the entire\n" + "viewport. Describes the projection itself, not the output rect:\n" + "min/max sizing, recentering and screenMargin clamping do not affect\n" + "this flag. Always true when containsCamera is true.", Be::READ ) - MAP_ATTRIBUTE( - "bracketUpdateCallback", - m_bracketUpdateCallback, - "An optional callback that is called whenever the bracket projection is updated.", - Be::READWRITE ) - EXPOSURE_END() } diff --git a/trinity/Utilities/BoundingBox.cpp b/trinity/Utilities/BoundingBox.cpp index 209ca0bd1..488c2a196 100644 --- a/trinity/Utilities/BoundingBox.cpp +++ b/trinity/Utilities/BoundingBox.cpp @@ -1,6 +1,7 @@ // Copyright © 2023 CCP ehf. #include "StdAfx.h" +#include "Include/TriMath.h" #include "BoundingBox.h" #include "BoundingSphere.h" #include "MatrixUtils.h" @@ -208,6 +209,26 @@ void BoundingBoxUpdate( Vector3& min, Vector3& max, const Vector4& sphere ) BoundingBoxUpdate( min, max, Vector3( sphere.x + sphere.w, sphere.y + sphere.w, sphere.z + sphere.w ) ); } +void BoundingBoxInclude( CcpMath::AxisAlignedBox& box, const Vector3& min, const Vector3& max ) +{ + if( !IsFinite( min ) || !IsFinite( max ) ) + { + return; + } + + box.Include( CcpMath::AxisAlignedBox( min, max ) ); +} + +void BoundingBoxInclude( CcpMath::AxisAlignedBox& box, const Vector4& sphere ) +{ + if( !BoundingSphereIsValid( sphere ) ) + { + return; + } + + box.Include( CcpMath::AxisAlignedBox( sphere ) ); +} + void BoundingBoxTransform( Vector3& min, Vector3& max, const Matrix& tf ) { Vector3 corners[8]; diff --git a/trinity/Utilities/BoundingBox.h b/trinity/Utilities/BoundingBox.h index 6b057dd2b..10d2b70fa 100644 --- a/trinity/Utilities/BoundingBox.h +++ b/trinity/Utilities/BoundingBox.h @@ -24,6 +24,11 @@ void BoundingBoxUpdate( Vector3& min, Vector3& max, const Vector3& pos ); void BoundingBoxUpdate( Vector3& min, Vector3& max, const Vector3& otherMin, const Vector3& otherMax ); void BoundingBoxUpdate( Vector3& min, Vector3& max, const Vector4& sphere ); +// Guarded accumulation: non-finite boxes and invalid spheres are ignored, since +// CcpMath::AxisAlignedBox::Include would absorb NaN into the accumulated bounds. +void BoundingBoxInclude( CcpMath::AxisAlignedBox& box, const Vector3& min, const Vector3& max ); +void BoundingBoxInclude( CcpMath::AxisAlignedBox& box, const Vector4& sphere ); + // Transforms an axis aligned bounding box by the given transform, returns // the new axis aligned bounding box void BoundingBoxTransform( Vector3& min, Vector3& max, const Matrix& tf ); diff --git a/trinity/Utilities/BoundingSphere.cpp b/trinity/Utilities/BoundingSphere.cpp index deb7ac2dd..a2df5e0c2 100644 --- a/trinity/Utilities/BoundingSphere.cpp +++ b/trinity/Utilities/BoundingSphere.cpp @@ -11,6 +11,12 @@ void BoundingSphereInitialize( Vector4& sphere ) sphere = Vector4( 0.f, 0.f, 0.f, 0.f ); } +bool BoundingSphereIsValid( const Vector4& sphere ) +{ + // w > 0 rejects a NaN radius, but a NaN center needs the explicit checks. + return sphere.w > 0.0f && IsFinite( sphere.x ) && IsFinite( sphere.y ) && IsFinite( sphere.z ) && IsFinite( sphere.w ); +} + bool BoundingSphereIsInside( const Vector4& sphere, const Vector3& pos ) { const float radiusEpsilon = 1e-4f; diff --git a/trinity/Utilities/BoundingSphere.h b/trinity/Utilities/BoundingSphere.h index 0e36aa684..5b31571a1 100644 --- a/trinity/Utilities/BoundingSphere.h +++ b/trinity/Utilities/BoundingSphere.h @@ -10,6 +10,7 @@ inline const Vector3& BoundingSphereGetCenter( const Vector4& sphere ) return (const Vector3&)sphere; } void BoundingSphereInitialize( Vector4& sphere ); +bool BoundingSphereIsValid( const Vector4& sphere ); bool BoundingSphereIsInside( const Vector4& sphere, const Vector3& pos ); bool BoundingSphereIsSphereInside( const Vector4& parentSphere, const Vector4& testSphere ); void BoundingSphereUpdate( const Vector3& pos, Vector4& sphere ); From 90bd80f2b617657f6c76f964c6fe8a61bfcde8cb Mon Sep 17 00:00:00 2001 From: breki Date: Fri, 17 Jul 2026 13:40:18 +0000 Subject: [PATCH 04/10] remove unused helper function --- trinity/Utilities/BoundingBox.cpp | 62 ------------------------------- trinity/Utilities/BoundingBox.h | 4 -- 2 files changed, 66 deletions(-) diff --git a/trinity/Utilities/BoundingBox.cpp b/trinity/Utilities/BoundingBox.cpp index 488c2a196..3a45cecaf 100644 --- a/trinity/Utilities/BoundingBox.cpp +++ b/trinity/Utilities/BoundingBox.cpp @@ -286,68 +286,6 @@ void BoundingBoxTransform( Vector3& min, Vector3& max, const Matrix& tf ) } } -void BoundingBoxProject( Vector3& min, Vector3& max, const Matrix& view, const Matrix& proj, const TriViewport& vp ) -{ - Vector3 corners[8]; - - corners[0] = min; - - corners[1].x = min.x; - corners[1].y = min.y; - corners[1].z = max.z; - - corners[2].x = max.x; - corners[2].y = min.y; - corners[2].z = min.z; - - corners[3].x = max.x; - corners[3].y = min.y; - corners[3].z = max.z; - - corners[4] = max; - - corners[5].x = max.x; - corners[5].y = max.y; - corners[5].z = min.z; - - corners[6].x = min.x; - corners[6].y = max.y; - corners[6].z = max.z; - - corners[7].x = min.x; - corners[7].y = max.y; - corners[7].z = min.z; - - Matrix viewProj = view * proj; - for( int i = 0; i < 8; ++i ) - { - corners[i] = TransformCoord( corners[i], viewProj ); - Vec3TransformByViewport( corners[i], vp ); - } - - min = corners[0]; - max = corners[0]; - float* pMin = (float*)&min; - float* pMax = (float*)&max; - - for( int i = 1; i < 8; ++i ) - { - float* pCorner = (float*)&corners[i]; - - for( int component = 0; component < 3; ++component ) - { - if( pCorner[component] < pMin[component] ) - { - pMin[component] = pCorner[component]; - } - if( pCorner[component] > pMax[component] ) - { - pMax[component] = pCorner[component]; - } - } - } -} - bool IntersectAxisAlignedBoxAxisAlignedBox( const Vector3& minBoundsA, const Vector3& maxBoundsA, const Vector3& minBoundsB, const Vector3& maxBoundsB ) { XMVECTOR MinA = XMVectorSet( minBoundsA.x, minBoundsA.y, minBoundsA.z, 0.0f ); diff --git a/trinity/Utilities/BoundingBox.h b/trinity/Utilities/BoundingBox.h index 10d2b70fa..f47bb929f 100644 --- a/trinity/Utilities/BoundingBox.h +++ b/trinity/Utilities/BoundingBox.h @@ -33,10 +33,6 @@ void BoundingBoxInclude( CcpMath::AxisAlignedBox& box, const Vector4& sphere ); // the new axis aligned bounding box void BoundingBoxTransform( Vector3& min, Vector3& max, const Matrix& tf ); -// Projects an axis aligned bounding box into screen space with the given view -// and projection matrices, along with a viewport. -void BoundingBoxProject( Vector3& min, Vector3& max, const Matrix& proj, const Matrix& view, const TriViewport& vp ); - bool IntersectAxisAlignedBoxAxisAlignedBox( const Vector3& minBoundsA, const Vector3& maxBoundsA, const Vector3& minBoundsB, const Vector3& maxBoundsB ); bool IntersectOrientedBoxAxisAlignedBox( const Vector3& centerA, const Vector3& extentsA, const Quaternion& orientationA, const Vector3& minBounds, const Vector3& maxBounds ); bool IntersectOrientedBoxOrientedBox( const Vector3& centerA, const Vector3& extentsA, const Quaternion& orientationA, const Vector3& centerB, const Vector3& extentsB, const Quaternion& orientationB ); From 310c82fbe04371470513a16d7689ca61c0e774f6 Mon Sep 17 00:00:00 2001 From: breki Date: Tue, 21 Jul 2026 10:00:09 +0000 Subject: [PATCH 05/10] Remove child bounding sphere logic and remove boundingsphere interface function in ITr2BoundingBox --- trinity/CMakeLists.txt | 1 - trinity/Eve/EveChildBounds.h | 37 ---- trinity/Eve/EveEffectRoot2.cpp | 34 +--- trinity/Eve/EvePlanet.cpp | 13 +- trinity/Eve/EvePlanet.h | 1 - trinity/Eve/EveTransform.cpp | 52 +----- trinity/Include/ITr2BoundingBox.h | 8 - trinity/Tr2ProjectBoundingBoxBracket.cpp | 223 +---------------------- trinity/Utilities/BoundingBox.cpp | 10 - trinity/Utilities/BoundingBox.h | 1 - trinity/Utilities/BoundingSphere.cpp | 6 - trinity/Utilities/BoundingSphere.h | 1 - 12 files changed, 14 insertions(+), 373 deletions(-) delete mode 100644 trinity/Eve/EveChildBounds.h diff --git a/trinity/CMakeLists.txt b/trinity/CMakeLists.txt index 356e97898..606b971e0 100644 --- a/trinity/CMakeLists.txt +++ b/trinity/CMakeLists.txt @@ -754,7 +754,6 @@ set(_SOURCES Eve/EveCamera.cpp Eve/EveCamera.h Eve/EveCamera_Blue.cpp - Eve/EveChildBounds.h Eve/EveComponentRegistry.cpp Eve/EveComponentRegistry.h Eve/EveComponentRegistry_Blue.cpp diff --git a/trinity/Eve/EveChildBounds.h b/trinity/Eve/EveChildBounds.h deleted file mode 100644 index bcc50a731..000000000 --- a/trinity/Eve/EveChildBounds.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2026 Fenris Creations - -#pragma once -#ifndef EveChildBounds_H -#define EveChildBounds_H - -#include "ITr2BoundingBox.h" -#include "Utilities/BoundingBox.h" -#include "Utilities/BoundingSphere.h" - -// Best-effort world-space bounds for a scene-graph child: prefers the child's -// axis-aligned box when it implements ITr2BoundingBox, otherwise falls back to -// its bounding sphere. ChildT only needs GetBoundingSphere( Vector4&, -// BoundingSphereQuery ); there is no common bounds interface across the Eve -// child types (IEveTransform, IEveSpaceObjectChild, ...). -template -bool GetChildWorldBounds( const ChildT* child, Vector3& min, Vector3& max ) -{ - if( auto childBoundingBox = dynamic_cast( child ) ) - { - if( childBoundingBox->GetWorldBoundingBox( min, max ) ) - { - return true; - } - } - - Vector4 childSphere; - if( child->GetBoundingSphere( childSphere, EVE_BOUNDS_WITH_CHILDREN ) && BoundingSphereIsValid( childSphere ) ) - { - BoundingBoxInitialize( childSphere, min, max ); - return true; - } - - return false; -} - -#endif // EveChildBounds_H diff --git a/trinity/Eve/EveEffectRoot2.cpp b/trinity/Eve/EveEffectRoot2.cpp index 1cb450396..e175f539c 100644 --- a/trinity/Eve/EveEffectRoot2.cpp +++ b/trinity/Eve/EveEffectRoot2.cpp @@ -10,13 +10,10 @@ #include "Tr2LightManager.h" #include "Controllers/ITr2Controller.h" #include "Curves/TriCurveSet.h" -#include "Eve/EveChildBounds.h" #include "Eve/EveUpdateContext.h" #include "Eve/SpaceObject/EveSpaceObject2.h" #include "Eve/SpaceObject/Children/EveChildContainer.h" -#include - extern float g_eveSpaceObjectResourceUnloadingTimeThreshold; EveEffectRoot2::EveEffectRoot2( IRoot* lockobj ) : @@ -402,7 +399,7 @@ void EveEffectRoot2::GetModelCenterWorldPosition( Vector3& position ) const bool EveEffectRoot2::GetLocalBoundingBox( Vector3& min, Vector3& max ) { - if( !BoundingSphereIsValid( m_boundingSphere ) ) + if( m_boundingSphere.w <= 0.0f ) { return false; } @@ -421,7 +418,7 @@ bool EveEffectRoot2::GetWorldBoundingBox( Vector3& min, Vector3& max ) const { CcpMath::AxisAlignedBox bounds; - if( BoundingSphereIsValid( m_boundingSphere ) ) + if( m_boundingSphere.w > 0.0f ) { Vector3 rootMin; Vector3 rootMax; @@ -430,17 +427,6 @@ bool EveEffectRoot2::GetWorldBoundingBox( Vector3& min, Vector3& max ) const BoundingBoxInclude( bounds, rootMin, rootMax ); } - for( auto it = m_effectChildren.begin(); it != m_effectChildren.end(); ++it ) - { - const IEveSpaceObjectChild* child = *it; - Vector3 childMin; - Vector3 childMax; - if( GetChildWorldBounds( child, childMin, childMax ) ) - { - BoundingBoxInclude( bounds, childMin, childMax ); - } - } - if( !bounds.IsInitialized() ) { return false; @@ -453,21 +439,7 @@ bool EveEffectRoot2::GetWorldBoundingBox( Vector3& min, Vector3& max ) const bool EveEffectRoot2::IsBoundingBoxReady() const { - if( BoundingSphereIsValid( m_boundingSphere ) ) - { - return true; - } - - for( auto it = m_effectChildren.begin(); it != m_effectChildren.end(); ++it ) - { - Vector4 childBounds; - if( ( *it )->GetBoundingSphere( childBounds, EVE_BOUNDS_WITH_CHILDREN ) && BoundingSphereIsValid( childBounds ) ) - { - return true; - } - } - - return false; + return m_boundingSphere.w > 0.0f; } void EveEffectRoot2::RegisterWithQuadRenderer( Tr2QuadRenderer& quadRenderer ) diff --git a/trinity/Eve/EvePlanet.cpp b/trinity/Eve/EvePlanet.cpp index 01a29667b..5924a7489 100644 --- a/trinity/Eve/EvePlanet.cpp +++ b/trinity/Eve/EvePlanet.cpp @@ -153,17 +153,6 @@ Quaternion EvePlanet::GetWorldRotation() bool EvePlanet::GetWorldBoundingBox( Vector3& min, Vector3& max ) const { Vector4 sphere; - if( !GetWorldBoundingSphere( sphere ) ) - { - return false; - } - - BoundingBoxInitialize( sphere, min, max ); - return true; -} - -bool EvePlanet::GetWorldBoundingSphere( Vector4& sphere ) const -{ if( m_radius <= 0.0f ) { return false; @@ -172,6 +161,8 @@ bool EvePlanet::GetWorldBoundingSphere( Vector4& sphere ) const const float renderScale = m_renderScale > 0.0f ? m_renderScale : 1.0f; const Matrix scaledTransform = CalculatePlanetScaleTransform( m_worldTransform, renderScale ); sphere = Vector4( scaledTransform.GetTranslation(), m_radius / renderScale ); + + BoundingBoxInitialize( sphere, min, max ); return true; } diff --git a/trinity/Eve/EvePlanet.h b/trinity/Eve/EvePlanet.h index 3f0e00445..c79c40cb7 100644 --- a/trinity/Eve/EvePlanet.h +++ b/trinity/Eve/EvePlanet.h @@ -61,7 +61,6 @@ BLUE_CLASS( EvePlanet ) : // ITr2BoundingBox bool GetWorldBoundingBox( Vector3 & min, Vector3 & max ) const override; - bool GetWorldBoundingSphere( Vector4 & sphere ) const override; bool IsBoundingBoxReady() const override; // ITr2SecondaryLightSource diff --git a/trinity/Eve/EveTransform.cpp b/trinity/Eve/EveTransform.cpp index 8e9842b50..1e70f503d 100644 --- a/trinity/Eve/EveTransform.cpp +++ b/trinity/Eve/EveTransform.cpp @@ -10,7 +10,6 @@ #include "Particle/Tr2ParticleSystem.h" #include "Particle/ITr2GenericEmitter.h" #include "EveUpdateContext.h" -#include "EveChildBounds.h" #include "EveLODHelper.h" #include "Curves/TriCurveSet.h" #include "Tr2Mesh.h" @@ -383,22 +382,6 @@ bool EveTransform::GetLocalBoundingBox( Vector3& min, Vector3& max ) BoundingBoxInclude( bounds, directMin, directMax ); } - Matrix inverseWorldTransform; - if( Inverse( inverseWorldTransform, m_worldTransform ) ) - { - for( auto it = m_children.begin(); it != m_children.end(); ++it ) - { - const IEveTransform* child = *it; - Vector3 childMin; - Vector3 childMax; - if( GetChildWorldBounds( child, childMin, childMax ) ) - { - BoundingBoxTransform( childMin, childMax, inverseWorldTransform ); - BoundingBoxInclude( bounds, childMin, childMax ); - } - } - } - if( !bounds.IsInitialized() ) { return false; @@ -421,17 +404,6 @@ bool EveTransform::GetWorldBoundingBox( Vector3& min, Vector3& max ) const BoundingBoxInclude( bounds, localMin, localMax ); } - for( auto it = m_children.begin(); it != m_children.end(); ++it ) - { - const IEveTransform* child = *it; - Vector3 childMin; - Vector3 childMax; - if( GetChildWorldBounds( child, childMin, childMax ) ) - { - BoundingBoxInclude( bounds, childMin, childMax ); - } - } - if( !bounds.IsInitialized() ) { return false; @@ -446,29 +418,7 @@ bool EveTransform::IsBoundingBoxReady() const { Vector3 min; Vector3 max; - if( GetDirectLocalBounds( m_overrideBoundsMin, m_overrideBoundsMax, m_mesh, min, max ) ) - { - return true; - } - - for( auto it = m_children.begin(); it != m_children.end(); ++it ) - { - if( auto childBoundingBox = dynamic_cast( *it ) ) - { - if( childBoundingBox->IsBoundingBoxReady() ) - { - return true; - } - } - - Vector4 childSphere; - if( ( *it )->GetBoundingSphere( childSphere, EVE_BOUNDS_WITH_CHILDREN ) && BoundingSphereIsValid( childSphere ) ) - { - return true; - } - } - - return false; + return GetDirectLocalBounds( m_overrideBoundsMin, m_overrideBoundsMax, m_mesh, min, max ); } bool EveTransform::GetBoundingSphere( Vector4& sphere, BoundingSphereQuery query ) const diff --git a/trinity/Include/ITr2BoundingBox.h b/trinity/Include/ITr2BoundingBox.h index 7003fa123..0a54ce568 100644 --- a/trinity/Include/ITr2BoundingBox.h +++ b/trinity/Include/ITr2BoundingBox.h @@ -6,20 +6,12 @@ #define ITr2BoundingBox_h struct Vector3; -struct Vector4; BLUE_INTERFACE( ITr2BoundingBox ) : IRoot { virtual bool GetWorldBoundingBox( Vector3 & min, Vector3 & max ) const = 0; virtual bool IsBoundingBoxReady( void ) const = 0; - - // Optional tighter bound for genuinely spherical objects, as (center.xyz, radius.w). - // Consumers that can exploit it (bracket projection) prefer it over the box. - virtual bool GetWorldBoundingSphere( Vector4& /*sphere*/ ) const - { - return false; - } }; #endif \ No newline at end of file diff --git a/trinity/Tr2ProjectBoundingBoxBracket.cpp b/trinity/Tr2ProjectBoundingBoxBracket.cpp index 43acdbdc5..424c8984e 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.cpp +++ b/trinity/Tr2ProjectBoundingBoxBracket.cpp @@ -270,178 +270,6 @@ bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, c return true; } -enum class SphereProjection -{ - PROJECTED, - EMPTY, - UNSUPPORTED -}; - -struct AxisBounds -{ - float minSlope; - float maxSlope; - bool hasBounds; -}; - -// Exact near-clipped silhouette bounds of a sphere in one screen axis, as view-space -// slopes (axis / forward depth). The per-axis 2D tangent construction is exact for the -// 3D silhouette: the cross-axis coordinate drops out of the tangency condition at the -// axis extremes. -AxisBounds ComputeSphereAxisBounds( float a, float b, float r, float nearDepth ) -{ - AxisBounds result{ 0.0f, 0.0f, false }; - - float candidates[4]; - int candidateCount = 0; - - const float m2 = a * a + b * b - r * r; - const float denominator = b * b - r * r; - if( m2 > 0.0f && fabsf( denominator ) > CLIP_EPSILON ) - { - const float m = sqrtf( m2 ); - const float tangents[2] = { ( a * b - r * m ) / denominator, ( a * b + r * m ) / denominator }; - for( float u : tangents ) - { - // A tangent whose touch point lies behind the near plane does not bound - // the visible silhouette; the near-plane circle takes over there. - const float tangentDepth = ( a * u + b ) / ( 1.0f + u * u ); - if( IsFinite( u ) && tangentDepth >= nearDepth ) - { - candidates[candidateCount++] = u; - } - } - } - - const float nearOffset = nearDepth - b; - const float nearCircleSq = r * r - nearOffset * nearOffset; - if( nearCircleSq > 0.0f ) - { - const float nearCircleRadius = sqrtf( nearCircleSq ); - candidates[candidateCount++] = ( a - nearCircleRadius ) / nearDepth; - candidates[candidateCount++] = ( a + nearCircleRadius ) / nearDepth; - } - - if( candidateCount == 0 ) - { - return result; - } - - result.minSlope = result.maxSlope = candidates[0]; - for( int i = 1; i < candidateCount; ++i ) - { - result.minSlope = std::min( result.minSlope, candidates[i] ); - result.maxSlope = std::max( result.maxSlope, candidates[i] ); - } - result.hasBounds = true; - return result; -} - -// Requires a rigid view transform and a symmetric, non-oblique perspective projection; -// reports UNSUPPORTED otherwise so the caller can fall back to the box path. -SphereProjection ProjectBoundingSphereToViewport( const Vector4& sphere, const Matrix& view, const Matrix& projection, const TriViewport& viewport, ProjectedBounds& bounds ) -{ - const float radius = sphere.w; - if( radius <= 0.0f ) - { - return SphereProjection::UNSUPPORTED; - } - - const float handedness = projection._34; - if( fabsf( fabsf( handedness ) - 1.0f ) > CLIP_EPSILON || - projection._11 <= 0.0f || projection._22 <= 0.0f || projection._33 == 0.0f || - projection._12 != 0.0f || projection._13 != 0.0f || projection._14 != 0.0f || - projection._21 != 0.0f || projection._23 != 0.0f || projection._24 != 0.0f || - projection._31 != 0.0f || projection._32 != 0.0f || - projection._41 != 0.0f || projection._42 != 0.0f || projection._44 != 0.0f ) - { - return SphereProjection::UNSUPPORTED; - } - - const float nearDepth = -handedness * projection._43 / projection._33; - if( !IsFinite( nearDepth ) || nearDepth <= 0.0f ) - { - return SphereProjection::UNSUPPORTED; - } - - const Vector3 centerView = TransformCoord( BoundingSphereGetCenter( sphere ), view ); - const float forward = handedness * centerView.z; - if( !IsFinite( centerView.x ) || !IsFinite( centerView.y ) || !IsFinite( forward ) ) - { - return SphereProjection::UNSUPPORTED; - } - - if( forward + radius < nearDepth ) - { - return SphereProjection::EMPTY; - } - - const float farDenominator = projection._33 - handedness; - if( fabsf( farDenominator ) > CLIP_EPSILON ) - { - const float farDepth = -handedness * projection._43 / farDenominator; - if( IsFinite( farDepth ) && farDepth > nearDepth && forward - radius > farDepth ) - { - return SphereProjection::EMPTY; - } - } - - const AxisBounds xBounds = ComputeSphereAxisBounds( centerView.x, forward, radius, nearDepth ); - const AxisBounds yBounds = ComputeSphereAxisBounds( centerView.y, forward, radius, nearDepth ); - if( !xBounds.hasBounds || !yBounds.hasBounds ) - { - return SphereProjection::EMPTY; - } - - const float minNdcX = projection._11 * xBounds.minSlope; - const float maxNdcX = projection._11 * xBounds.maxSlope; - const float minNdcY = projection._22 * yBounds.minSlope; - const float maxNdcY = projection._22 * yBounds.maxSlope; - if( !IsFinite( minNdcX ) || !IsFinite( maxNdcX ) || !IsFinite( minNdcY ) || !IsFinite( maxNdcY ) ) - { - return SphereProjection::UNSUPPORTED; - } - - if( maxNdcX < -1.0f || minNdcX > 1.0f || maxNdcY < -1.0f || minNdcY > 1.0f ) - { - return SphereProjection::EMPTY; - } - - const float viewportLeft = static_cast( viewport.x ); - const float viewportTop = static_cast( viewport.y ); - const float viewportWidth = static_cast( viewport.width ); - const float viewportHeight = static_cast( viewport.height ); - - const float minX = viewportLeft + ( 1.0f + minNdcX ) * 0.5f * viewportWidth; - const float maxX = viewportLeft + ( 1.0f + maxNdcX ) * 0.5f * viewportWidth; - const float minY = viewportTop + ( 1.0f - maxNdcY ) * 0.5f * viewportHeight; - const float maxY = viewportTop + ( 1.0f - minNdcY ) * 0.5f * viewportHeight; - - const float width = maxX - minX; - const float height = maxY - minY; - if( !IsFinite( width ) || !IsFinite( height ) || width <= 0.0f || height <= 0.0f ) - { - return SphereProjection::EMPTY; - } - - const float centerDistance = Length( centerView ); - const float nearestDepth = std::max( nearDepth, centerDistance - radius ); - const float ndcZ = ( handedness * nearestDepth * projection._33 + projection._43 ) / nearestDepth; - const float clampedNdcZ = std::min( 1.0f, std::max( 0.0f, ndcZ ) ); - - const float viewportRight = viewportLeft + viewportWidth; - const float viewportBottom = viewportTop + viewportHeight; - - bounds.x = minX; - bounds.y = minY; - bounds.z = viewport.minZ + clampedNdcZ * ( viewport.maxZ - viewport.minZ ); - bounds.width = width; - bounds.height = height; - bounds.extendsOffscreen = minX < viewportLeft || minY < viewportTop || maxX > viewportRight || maxY > viewportBottom; - bounds.coversViewport = minX <= viewportLeft && minY <= viewportTop && maxX >= viewportRight && maxY >= viewportBottom; - return SphereProjection::PROJECTED; -} - bool ClampToScreenMargin( const TriViewport& viewport, float margin, float& x, float& y, float& width, float& height ) { if( margin <= 0.0f ) @@ -508,32 +336,20 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) return; } - Vector4 sphere( 0.0f, 0.0f, 0.0f, 0.0f ); - const bool hasSphere = m_object->GetWorldBoundingSphere( sphere ) && sphere.w > 0.0f; - Vector3 bbMin, bbMax; - bool hasBox = false; Vector3 center; - if( hasSphere ) + if( !m_object->GetWorldBoundingBox( bbMin, bbMax ) ) { - center = BoundingSphereGetCenter( sphere ); - } - else - { - if( !m_object->GetWorldBoundingBox( bbMin, bbMax ) ) - { - SetEmptyProjection(); - return; - } - hasBox = true; - center = ( bbMax + bbMin ) * 0.5f; + SetEmptyProjection(); + return; } + center = ( bbMax + bbMin ) * 0.5f; Vector3 d = Tr2Renderer::GetViewPosition() - center; m_cameraDistance = Length( d ); const TriViewport& viewport = Tr2Renderer::GetViewport(); - const bool cameraInside = hasSphere ? m_cameraDistance <= sphere.w : BoundingBoxIsInside( bbMin, bbMax, Tr2Renderer::GetViewPosition() ); + const bool cameraInside = BoundingBoxIsInside( bbMin, bbMax, Tr2Renderer::GetViewPosition() ); if( cameraInside ) { m_projectedX = static_cast( viewport.x ); @@ -556,34 +372,11 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) Matrix viewProjection = Tr2Renderer::GetViewTransform() * Tr2Renderer::GetProjectionTransform(); ProjectedBounds projectedBounds; - bool hasProjection = false; - if( hasSphere ) - { - switch( ProjectBoundingSphereToViewport( sphere, Tr2Renderer::GetViewTransform(), Tr2Renderer::GetProjectionTransform(), viewport, projectedBounds ) ) - { - case SphereProjection::PROJECTED: - hasProjection = true; - break; - case SphereProjection::EMPTY: - SetEmptyProjection(); - return; - case SphereProjection::UNSUPPORTED: - break; - } - } - if( !hasProjection ) + if( !ProjectBoundingBoxToViewport( bbMin, bbMax, viewProjection, viewport, projectedBounds ) ) { - if( !hasBox && !m_object->GetWorldBoundingBox( bbMin, bbMax ) ) - { - SetEmptyProjection(); - return; - } - if( !ProjectBoundingBoxToViewport( bbMin, bbMax, viewProjection, viewport, projectedBounds ) ) - { - SetEmptyProjection(); - return; - } + SetEmptyProjection(); + return; } m_projectedX = projectedBounds.x; diff --git a/trinity/Utilities/BoundingBox.cpp b/trinity/Utilities/BoundingBox.cpp index 3a45cecaf..02e9df238 100644 --- a/trinity/Utilities/BoundingBox.cpp +++ b/trinity/Utilities/BoundingBox.cpp @@ -219,16 +219,6 @@ void BoundingBoxInclude( CcpMath::AxisAlignedBox& box, const Vector3& min, const box.Include( CcpMath::AxisAlignedBox( min, max ) ); } -void BoundingBoxInclude( CcpMath::AxisAlignedBox& box, const Vector4& sphere ) -{ - if( !BoundingSphereIsValid( sphere ) ) - { - return; - } - - box.Include( CcpMath::AxisAlignedBox( sphere ) ); -} - void BoundingBoxTransform( Vector3& min, Vector3& max, const Matrix& tf ) { Vector3 corners[8]; diff --git a/trinity/Utilities/BoundingBox.h b/trinity/Utilities/BoundingBox.h index f47bb929f..5e657a049 100644 --- a/trinity/Utilities/BoundingBox.h +++ b/trinity/Utilities/BoundingBox.h @@ -27,7 +27,6 @@ void BoundingBoxUpdate( Vector3& min, Vector3& max, const Vector4& sphere ); // Guarded accumulation: non-finite boxes and invalid spheres are ignored, since // CcpMath::AxisAlignedBox::Include would absorb NaN into the accumulated bounds. void BoundingBoxInclude( CcpMath::AxisAlignedBox& box, const Vector3& min, const Vector3& max ); -void BoundingBoxInclude( CcpMath::AxisAlignedBox& box, const Vector4& sphere ); // Transforms an axis aligned bounding box by the given transform, returns // the new axis aligned bounding box diff --git a/trinity/Utilities/BoundingSphere.cpp b/trinity/Utilities/BoundingSphere.cpp index a2df5e0c2..deb7ac2dd 100644 --- a/trinity/Utilities/BoundingSphere.cpp +++ b/trinity/Utilities/BoundingSphere.cpp @@ -11,12 +11,6 @@ void BoundingSphereInitialize( Vector4& sphere ) sphere = Vector4( 0.f, 0.f, 0.f, 0.f ); } -bool BoundingSphereIsValid( const Vector4& sphere ) -{ - // w > 0 rejects a NaN radius, but a NaN center needs the explicit checks. - return sphere.w > 0.0f && IsFinite( sphere.x ) && IsFinite( sphere.y ) && IsFinite( sphere.z ) && IsFinite( sphere.w ); -} - bool BoundingSphereIsInside( const Vector4& sphere, const Vector3& pos ) { const float radiusEpsilon = 1e-4f; diff --git a/trinity/Utilities/BoundingSphere.h b/trinity/Utilities/BoundingSphere.h index 5b31571a1..0e36aa684 100644 --- a/trinity/Utilities/BoundingSphere.h +++ b/trinity/Utilities/BoundingSphere.h @@ -10,7 +10,6 @@ inline const Vector3& BoundingSphereGetCenter( const Vector4& sphere ) return (const Vector3&)sphere; } void BoundingSphereInitialize( Vector4& sphere ); -bool BoundingSphereIsValid( const Vector4& sphere ); bool BoundingSphereIsInside( const Vector4& sphere, const Vector3& pos ); bool BoundingSphereIsSphereInside( const Vector4& parentSphere, const Vector4& testSphere ); void BoundingSphereUpdate( const Vector3& pos, Vector4& sphere ); From c9d4dd74b29c75d72b7cad0804f954e922e00a57 Mon Sep 17 00:00:00 2001 From: Breki Ingibjargarson Date: Tue, 21 Jul 2026 10:42:47 +0000 Subject: [PATCH 06/10] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- trinity/Eve/EveEffectRoot2.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trinity/Eve/EveEffectRoot2.h b/trinity/Eve/EveEffectRoot2.h index d9c600348..ff0cd8275 100644 --- a/trinity/Eve/EveEffectRoot2.h +++ b/trinity/Eve/EveEffectRoot2.h @@ -92,7 +92,7 @@ BLUE_CLASS( EveEffectRoot2 ) : ///////////////////////////////////////////////////////////////////////////////////// // ITr2BoundingBox - bool GetWorldBoundingBox( Vector3& min, Vector3& max ) const override; + bool GetWorldBoundingBox( Vector3 & min, Vector3 & max ) const override; bool IsBoundingBoxReady() const override; ///////////////////////////////////////////////////////////////////////////////////// From 19668f3777b328c4ef205b45f913a7ed55e5e13a Mon Sep 17 00:00:00 2001 From: breki Date: Tue, 21 Jul 2026 13:12:24 +0000 Subject: [PATCH 07/10] simplify changes by removing isfinite checks and applying other codereview feedback --- trinity/Eve/EveEffectRoot2.cpp | 18 +- trinity/Eve/EveTransform.cpp | 30 +-- trinity/Include/TriMath.h | 1 + trinity/Tr2ProjectBoundingBoxBracket.cpp | 224 ++++++++++------------- trinity/Tr2ProjectBoundingBoxBracket.h | 4 + trinity/TriMath.cpp | 6 + trinity/Utilities/BoundingBox.cpp | 10 - trinity/Utilities/BoundingBox.h | 4 - 8 files changed, 117 insertions(+), 180 deletions(-) diff --git a/trinity/Eve/EveEffectRoot2.cpp b/trinity/Eve/EveEffectRoot2.cpp index e175f539c..4b395817f 100644 --- a/trinity/Eve/EveEffectRoot2.cpp +++ b/trinity/Eve/EveEffectRoot2.cpp @@ -8,6 +8,7 @@ #include "TriFrustum.h" #include "Lights/Tr2PointLight.h" #include "Tr2LightManager.h" +#include "TriMath.h" #include "Controllers/ITr2Controller.h" #include "Curves/TriCurveSet.h" #include "Eve/EveUpdateContext.h" @@ -416,24 +417,13 @@ void EveEffectRoot2::GetLocalToWorldTransform( Matrix& transform ) const bool EveEffectRoot2::GetWorldBoundingBox( Vector3& min, Vector3& max ) const { - CcpMath::AxisAlignedBox bounds; - - if( m_boundingSphere.w > 0.0f ) - { - Vector3 rootMin; - Vector3 rootMax; - BoundingBoxInitialize( m_boundingSphere, rootMin, rootMax ); - BoundingBoxTransform( rootMin, rootMax, m_lastUpdateMatrix ); - BoundingBoxInclude( bounds, rootMin, rootMax ); - } - - if( !bounds.IsInitialized() ) + if( m_boundingSphere.w <= 0.0f ) { return false; } - min = bounds.m_min; - max = bounds.m_max; + BoundingBoxInitialize( m_boundingSphere, min, max ); + BoundingBoxTransform( min, max, m_lastUpdateMatrix ); return true; } diff --git a/trinity/Eve/EveTransform.cpp b/trinity/Eve/EveTransform.cpp index 1e70f503d..cc3bf5c6c 100644 --- a/trinity/Eve/EveTransform.cpp +++ b/trinity/Eve/EveTransform.cpp @@ -14,6 +14,7 @@ #include "Curves/TriCurveSet.h" #include "Tr2Mesh.h" #include "Tr2MeshBase.h" +#include "TriMath.h" extern float g_eveSpaceSceneLowUpdateRate; @@ -373,44 +374,21 @@ void EveTransform::GetRenderables( std::vector& renderables ) bool EveTransform::GetLocalBoundingBox( Vector3& min, Vector3& max ) { - CcpMath::AxisAlignedBox bounds; - - Vector3 directMin; - Vector3 directMax; - if( GetDirectLocalBounds( m_overrideBoundsMin, m_overrideBoundsMax, m_mesh, directMin, directMax ) ) - { - BoundingBoxInclude( bounds, directMin, directMax ); - } - - if( !bounds.IsInitialized() ) + if( !GetDirectLocalBounds( m_overrideBoundsMin, m_overrideBoundsMax, m_mesh, min, max ) ) { return false; } - min = bounds.m_min; - max = bounds.m_max; return true; } bool EveTransform::GetWorldBoundingBox( Vector3& min, Vector3& max ) const { - CcpMath::AxisAlignedBox bounds; - - Vector3 localMin; - Vector3 localMax; - if( GetDirectLocalBounds( m_overrideBoundsMin, m_overrideBoundsMax, m_mesh, localMin, localMax ) ) - { - BoundingBoxTransform( localMin, localMax, m_worldTransform ); - BoundingBoxInclude( bounds, localMin, localMax ); - } - - if( !bounds.IsInitialized() ) + if( !GetDirectLocalBounds( m_overrideBoundsMin, m_overrideBoundsMax, m_mesh, min, max ) ) { return false; } - - min = bounds.m_min; - max = bounds.m_max; + BoundingBoxTransform( min, max, m_worldTransform ); return true; } diff --git a/trinity/Include/TriMath.h b/trinity/Include/TriMath.h index e33d03576..5a8f799fd 100644 --- a/trinity/Include/TriMath.h +++ b/trinity/Include/TriMath.h @@ -261,6 +261,7 @@ double* Matrix4dFromMatrix( double* result, const Matrix& mat ); bool IsFinite( float value ); bool IsFinite( const Vector3& vec ); +bool IsFinite( const Vector4& vec ); float TriClamp( float f, float min, float max ); int32_t ClampInt( int32_t f, int32_t min, int32_t max ); diff --git a/trinity/Tr2ProjectBoundingBoxBracket.cpp b/trinity/Tr2ProjectBoundingBoxBracket.cpp index 424c8984e..8b9ee07f0 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.cpp +++ b/trinity/Tr2ProjectBoundingBoxBracket.cpp @@ -19,14 +19,6 @@ namespace { const float CLIP_EPSILON = 1e-5f; -struct ClipPoint -{ - float x; - float y; - float z; - float w; -}; - struct ProjectedBounds { float x; @@ -38,19 +30,9 @@ struct ProjectedBounds bool coversViewport; }; -bool IsFinite( float value ) -{ - return std::isfinite( value ); -} - -bool IsFinite( const ClipPoint& point ) +Vector4 TransformPointToClip( const Vector3& point, const Matrix& viewProjection ) { - return IsFinite( point.x ) && IsFinite( point.y ) && IsFinite( point.z ) && IsFinite( point.w ); -} - -ClipPoint TransformPointToClip( const Vector3& point, const Matrix& viewProjection ) -{ - return ClipPoint{ + return Vector4{ point.x * viewProjection._11 + point.y * viewProjection._21 + point.z * viewProjection._31 + viewProjection._41, point.x * viewProjection._12 + point.y * viewProjection._22 + point.z * viewProjection._32 + viewProjection._42, point.x * viewProjection._13 + point.y * viewProjection._23 + point.z * viewProjection._33 + viewProjection._43, @@ -58,9 +40,9 @@ ClipPoint TransformPointToClip( const Vector3& point, const Matrix& viewProjecti }; } -ClipPoint Lerp( const ClipPoint& a, const ClipPoint& b, float t ) +Vector4 Lerp( const Vector4& a, const Vector4& b, float t ) { - return ClipPoint{ + return Vector4{ a.x + ( b.x - a.x ) * t, a.y + ( b.y - a.y ) * t, a.z + ( b.z - a.z ) * t, @@ -68,7 +50,7 @@ ClipPoint Lerp( const ClipPoint& a, const ClipPoint& b, float t ) }; } -bool AreAllOutsidePlane( const ClipPoint* points, float ( *planeDistance )( const ClipPoint& ) ) +bool AreAllOutsidePlane( const Vector4* points, float ( *planeDistance )( const Vector4& ) ) { for( int i = 0; i < 8; ++i ) { @@ -80,32 +62,32 @@ bool AreAllOutsidePlane( const ClipPoint* points, float ( *planeDistance )( cons return true; } -float DistanceToLeftPlane( const ClipPoint& point ) +float DistanceToLeftPlane( const Vector4& point ) { return point.x + point.w; } -float DistanceToRightPlane( const ClipPoint& point ) +float DistanceToRightPlane( const Vector4& point ) { return point.w - point.x; } -float DistanceToBottomPlane( const ClipPoint& point ) +float DistanceToBottomPlane( const Vector4& point ) { return point.y + point.w; } -float DistanceToTopPlane( const ClipPoint& point ) +float DistanceToTopPlane( const Vector4& point ) { return point.w - point.y; } -float DistanceToNearPlane( const ClipPoint& point ) +float DistanceToNearPlane( const Vector4& point ) { return point.z; } -float DistanceToFarPlane( const ClipPoint& point ) +float DistanceToFarPlane( const Vector4& point ) { return point.w - point.z; } -bool IsTriviallyOutsideFrustum( const ClipPoint* points ) +bool IsTriviallyOutsideFrustum( const Vector4* points ) { return AreAllOutsidePlane( points, DistanceToLeftPlane ) || AreAllOutsidePlane( points, DistanceToRightPlane ) || @@ -115,12 +97,12 @@ bool IsTriviallyOutsideFrustum( const ClipPoint* points ) AreAllOutsidePlane( points, DistanceToFarPlane ); } -bool CanPerspectiveDivide( const ClipPoint& point ) +bool CanPerspectiveDivide( const Vector4& point ) { - return IsFinite( point ) && fabsf( point.w ) > CLIP_EPSILON; + return fabsf( point.w ) > CLIP_EPSILON; } -void AddIfProjectable( const ClipPoint& point, std::vector& points ) +void AddIfProjectable( const Vector4& point, std::vector& points ) { if( point.z >= 0.0f && CanPerspectiveDivide( point ) ) { @@ -128,7 +110,7 @@ void AddIfProjectable( const ClipPoint& point, std::vector& points ) } } -void AddNearPlaneIntersection( const ClipPoint& a, const ClipPoint& b, std::vector& points ) +void AddNearPlaneIntersection( const Vector4& a, const Vector4& b, std::vector& points ) { if( ( a.z < 0.0f ) == ( b.z < 0.0f ) ) { @@ -142,14 +124,14 @@ void AddNearPlaneIntersection( const ClipPoint& a, const ClipPoint& b, std::vect } float t = a.z / denominator; - ClipPoint point = Lerp( a, b, t ); + Vector4 point = Lerp( a, b, t ); if( CanPerspectiveDivide( point ) ) { points.push_back( point ); } } -bool ProjectClipPoint( const ClipPoint& point, const TriViewport& viewport, Vector3& projected ) +bool ProjectClipPoint( const Vector4& point, const TriViewport& viewport, Vector3& projected ) { if( !CanPerspectiveDivide( point ) ) { @@ -160,7 +142,7 @@ bool ProjectClipPoint( const ClipPoint& point, const TriViewport& viewport, Vect projected.x = viewport.x + ( 1.0f + point.x * reciprocalW ) * 0.5f * viewport.width; projected.y = viewport.y + ( 1.0f - point.y * reciprocalW ) * 0.5f * viewport.height; projected.z = viewport.minZ + point.z * reciprocalW * ( viewport.maxZ - viewport.minZ ); - return IsFinite( projected.x ) && IsFinite( projected.y ) && IsFinite( projected.z ); + return true; } bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, const Matrix& viewProjection, const TriViewport& viewport, ProjectedBounds& bounds ) @@ -175,14 +157,10 @@ bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, c corners[6] = Vector3( bbMin.x, bbMax.y, bbMax.z ); corners[7] = Vector3( bbMin.x, bbMax.y, bbMin.z ); - ClipPoint clipCorners[8]; + Vector4 clipCorners[8]; for( int i = 0; i < 8; ++i ) { clipCorners[i] = TransformPointToClip( corners[i], viewProjection ); - if( !IsFinite( clipCorners[i] ) ) - { - return false; - } } if( IsTriviallyOutsideFrustum( clipCorners ) ) @@ -190,7 +168,7 @@ bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, c return false; } - std::vector projectablePoints; + std::vector projectablePoints; projectablePoints.reserve( 20 ); for( int i = 0; i < 8; ++i ) { @@ -219,7 +197,7 @@ bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, c float maxX = 0.0f; float maxY = 0.0f; - for( const ClipPoint& point : projectablePoints ) + for( const Vector4& point : projectablePoints ) { if( !ProjectClipPoint( point, viewport, projected ) ) { @@ -250,7 +228,7 @@ bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, c float width = maxX - minX; float height = maxY - minY; - if( !IsFinite( width ) || !IsFinite( height ) || width <= 0.0f || height <= 0.0f ) + if( width <= 0.0f || height <= 0.0f ) { return false; } @@ -298,6 +276,19 @@ bool ClampToScreenMargin( const TriViewport& viewport, float margin, float& x, f height = maxY - minY; return true; } + +float ClampProjectedSize( float size, float minSize, float maxSize ) +{ + if( minSize > 0.0f && size < minSize ) + { + return minSize; + } + if( maxSize > 0.0f && size > maxSize ) + { + return maxSize; + } + return size; +} } @@ -324,55 +315,26 @@ Tr2ProjectBoundingBoxBracket::Tr2ProjectBoundingBoxBracket( IRoot* lockobj /*= N void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) { - if( !m_object ) - { - SetEmptyProjection(); - return; - } - - if( !m_object->IsBoundingBoxReady() ) - { - SetEmptyProjection(); - return; - } - Vector3 bbMin, bbMax; - Vector3 center; - if( !m_object->GetWorldBoundingBox( bbMin, bbMax ) ) + if( !m_object || !m_object->IsBoundingBoxReady() || !m_object->GetWorldBoundingBox( bbMin, bbMax ) ) { SetEmptyProjection(); return; } - center = ( bbMax + bbMin ) * 0.5f; - Vector3 d = Tr2Renderer::GetViewPosition() - center; - m_cameraDistance = Length( d ); + const Vector3 center = ( bbMax + bbMin ) * 0.5f; + const Vector3 viewPosition = Tr2Renderer::GetViewPosition(); + m_cameraDistance = Length( viewPosition - center ); const TriViewport& viewport = Tr2Renderer::GetViewport(); - const bool cameraInside = BoundingBoxIsInside( bbMin, bbMax, Tr2Renderer::GetViewPosition() ); - if( cameraInside ) + if( BoundingBoxIsInside( bbMin, bbMax, viewPosition ) ) { - m_projectedX = static_cast( viewport.x ); - m_projectedY = static_cast( viewport.y ); - m_projectedZ = viewport.minZ; - m_projectedWidth = static_cast( viewport.width ); - m_projectedHeight = static_cast( viewport.height ); - if( !ClampToScreenMargin( viewport, m_screenMargin, m_projectedX, m_projectedY, m_projectedWidth, m_projectedHeight ) ) - { - SetEmptyProjection(); - return; - } - m_isProjectionValid = true; - m_containsCamera = true; - m_extendsOffscreen = true; - m_coversViewport = true; - UpdateBracket(); + SetFullViewportProjection( viewport ); return; } Matrix viewProjection = Tr2Renderer::GetViewTransform() * Tr2Renderer::GetProjectionTransform(); ProjectedBounds projectedBounds; - if( !ProjectBoundingBoxToViewport( bbMin, bbMax, viewProjection, viewport, projectedBounds ) ) { SetEmptyProjection(); @@ -388,13 +350,62 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) m_extendsOffscreen = projectedBounds.extendsOffscreen; m_coversViewport = projectedBounds.coversViewport; + ConstrainProjection( center, viewProjection, viewport ); + PublishProjection( viewport ); +} + +void Tr2ProjectBoundingBoxBracket::SetEmptyProjection() +{ + m_projectedX = 0.0f; + m_projectedY = 0.0f; + m_projectedZ = 0.0f; + m_projectedWidth = 0.0f; + m_projectedHeight = 0.0f; + m_isProjectionValid = false; + m_containsCamera = false; + m_extendsOffscreen = false; + m_coversViewport = false; + + UpdateBracket(); +} + +void Tr2ProjectBoundingBoxBracket::UpdateBracket() +{ + if( m_bracket ) + { + m_bracket->SetDisplayX( m_projectedX ); + m_bracket->SetDisplayY( m_projectedY ); + m_bracket->SetDisplayWidth( m_projectedWidth ); + m_bracket->SetDisplayHeight( m_projectedHeight ); + } +} +void Tr2ProjectBoundingBoxBracket::SetFullViewportProjection( const TriViewport& viewport ) +{ + m_projectedX = static_cast( viewport.x ); + m_projectedY = static_cast( viewport.y ); + m_projectedZ = viewport.minZ; + m_projectedWidth = static_cast( viewport.width ); + m_projectedHeight = static_cast( viewport.height ); + if( !ClampToScreenMargin( viewport, m_screenMargin, m_projectedX, m_projectedY, m_projectedWidth, m_projectedHeight ) ) + { + SetEmptyProjection(); + return; + } + m_isProjectionValid = true; + m_containsCamera = true; + m_extendsOffscreen = true; + m_coversViewport = true; + UpdateBracket(); +} +void Tr2ProjectBoundingBoxBracket::ConstrainProjection( const Vector3& center, const Matrix& viewProjection, const TriViewport& viewport ) +{ float centerX = m_projectedX + m_projectedWidth * 0.5f; float centerY = m_projectedY + m_projectedHeight * 0.5f; if( m_maxProjectedWidth > 0.0f || m_maxProjectedHeight > 0.0f ) { // Bounded brackets are anchored on the projected 3d box center, not the projected // rect center, unless the box center is behind the near plane. - ClipPoint clipCenter = TransformPointToClip( center, viewProjection ); + Vector4 clipCenter = TransformPointToClip( center, viewProjection ); Vector3 projectedCenter; if( clipCenter.z >= 0.0f && clipCenter.w > 0.0f && ProjectClipPoint( clipCenter, viewport, projectedCenter ) ) { @@ -403,23 +414,8 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) } } - if( m_minProjectedWidth > 0.0f && m_projectedWidth < m_minProjectedWidth ) - { - m_projectedWidth = m_minProjectedWidth; - } - else if( m_maxProjectedWidth > 0.0f && m_projectedWidth > m_maxProjectedWidth ) - { - m_projectedWidth = m_maxProjectedWidth; - } - - if( m_minProjectedHeight > 0.0f && m_projectedHeight < m_minProjectedHeight ) - { - m_projectedHeight = m_minProjectedHeight; - } - else if( m_maxProjectedHeight > 0.0f && m_projectedHeight > m_maxProjectedHeight ) - { - m_projectedHeight = m_maxProjectedHeight; - } + m_projectedWidth = ClampProjectedSize( m_projectedWidth, m_minProjectedWidth, m_maxProjectedWidth ); + m_projectedHeight = ClampProjectedSize( m_projectedHeight, m_minProjectedHeight, m_maxProjectedHeight ); m_projectedX = centerX - m_projectedWidth * 0.5f; m_projectedY = centerY - m_projectedHeight * 0.5f; @@ -431,8 +427,10 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) m_projectedWidth = floor( m_projectedWidth + 0.5f ); m_projectedHeight = floor( m_projectedHeight + 0.5f ); } - - if( !IsFinite( m_projectedX ) || !IsFinite( m_projectedY ) || !IsFinite( m_projectedWidth ) || !IsFinite( m_projectedHeight ) || m_projectedWidth <= 0.0f || m_projectedHeight <= 0.0f ) +} +void Tr2ProjectBoundingBoxBracket::PublishProjection( const TriViewport& viewport ) +{ + if( m_projectedWidth <= 0.0f || m_projectedHeight <= 0.0f ) { SetEmptyProjection(); return; @@ -456,29 +454,3 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) g_debugRenderer->Printf( x, y, 0xffffffff, "(%5.2f, %5.2f)", m_projectedWidth, m_projectedHeight ); } } - -void Tr2ProjectBoundingBoxBracket::SetEmptyProjection() -{ - m_projectedX = 0.0f; - m_projectedY = 0.0f; - m_projectedZ = 0.0f; - m_projectedWidth = 0.0f; - m_projectedHeight = 0.0f; - m_isProjectionValid = false; - m_containsCamera = false; - m_extendsOffscreen = false; - m_coversViewport = false; - - UpdateBracket(); -} - -void Tr2ProjectBoundingBoxBracket::UpdateBracket() -{ - if( m_bracket ) - { - m_bracket->SetDisplayX( m_projectedX ); - m_bracket->SetDisplayY( m_projectedY ); - m_bracket->SetDisplayWidth( m_projectedWidth ); - m_bracket->SetDisplayHeight( m_projectedHeight ); - } -} diff --git a/trinity/Tr2ProjectBoundingBoxBracket.h b/trinity/Tr2ProjectBoundingBoxBracket.h index 4aa387a1f..36e62c7f5 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.h +++ b/trinity/Tr2ProjectBoundingBoxBracket.h @@ -26,6 +26,10 @@ class Tr2ProjectBoundingBoxBracket : public ITriFunction void UpdateBracket(); protected: + void SetFullViewportProjection( const TriViewport& viewport ); + void ConstrainProjection( const Vector3& center, const Matrix& viewProjection, const TriViewport& viewport ); + void PublishProjection( const TriViewport& viewport ); + std::wstring m_name; ////////////////////////////////////////////////////////////////////////// diff --git a/trinity/TriMath.cpp b/trinity/TriMath.cpp index 424827e69..5fe9a3102 100644 --- a/trinity/TriMath.cpp +++ b/trinity/TriMath.cpp @@ -893,6 +893,12 @@ bool IsFinite( const Vector3& vec ) return IsFinite( vec.x ) && IsFinite( vec.y ) && IsFinite( vec.z ); } +bool IsFinite( const Vector4& vec ) +{ + return IsFinite( vec.x ) && IsFinite( vec.y ) + && IsFinite( vec.z ) && IsFinite( vec.w ); +} + float TriClamp( float f, float min, diff --git a/trinity/Utilities/BoundingBox.cpp b/trinity/Utilities/BoundingBox.cpp index 02e9df238..3c0fbcb87 100644 --- a/trinity/Utilities/BoundingBox.cpp +++ b/trinity/Utilities/BoundingBox.cpp @@ -209,16 +209,6 @@ void BoundingBoxUpdate( Vector3& min, Vector3& max, const Vector4& sphere ) BoundingBoxUpdate( min, max, Vector3( sphere.x + sphere.w, sphere.y + sphere.w, sphere.z + sphere.w ) ); } -void BoundingBoxInclude( CcpMath::AxisAlignedBox& box, const Vector3& min, const Vector3& max ) -{ - if( !IsFinite( min ) || !IsFinite( max ) ) - { - return; - } - - box.Include( CcpMath::AxisAlignedBox( min, max ) ); -} - void BoundingBoxTransform( Vector3& min, Vector3& max, const Matrix& tf ) { Vector3 corners[8]; diff --git a/trinity/Utilities/BoundingBox.h b/trinity/Utilities/BoundingBox.h index 5e657a049..ce193e241 100644 --- a/trinity/Utilities/BoundingBox.h +++ b/trinity/Utilities/BoundingBox.h @@ -24,10 +24,6 @@ void BoundingBoxUpdate( Vector3& min, Vector3& max, const Vector3& pos ); void BoundingBoxUpdate( Vector3& min, Vector3& max, const Vector3& otherMin, const Vector3& otherMax ); void BoundingBoxUpdate( Vector3& min, Vector3& max, const Vector4& sphere ); -// Guarded accumulation: non-finite boxes and invalid spheres are ignored, since -// CcpMath::AxisAlignedBox::Include would absorb NaN into the accumulated bounds. -void BoundingBoxInclude( CcpMath::AxisAlignedBox& box, const Vector3& min, const Vector3& max ); - // Transforms an axis aligned bounding box by the given transform, returns // the new axis aligned bounding box void BoundingBoxTransform( Vector3& min, Vector3& max, const Matrix& tf ); From d0efbaf5e9e1d1bb8dba5c9d564d3d2b84fd1d50 Mon Sep 17 00:00:00 2001 From: Breki Ingibjargarson Date: Tue, 21 Jul 2026 13:13:37 +0000 Subject: [PATCH 08/10] Update trinity/TriMath.cpp Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- trinity/TriMath.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/trinity/TriMath.cpp b/trinity/TriMath.cpp index 5fe9a3102..058f9ae67 100644 --- a/trinity/TriMath.cpp +++ b/trinity/TriMath.cpp @@ -895,8 +895,7 @@ bool IsFinite( const Vector3& vec ) bool IsFinite( const Vector4& vec ) { - return IsFinite( vec.x ) && IsFinite( vec.y ) - && IsFinite( vec.z ) && IsFinite( vec.w ); + return IsFinite( vec.x ) && IsFinite( vec.y ) && IsFinite( vec.z ) && IsFinite( vec.w ); } float TriClamp( From 76ad6c2a021a31305fa3504ccbec2132366339ac Mon Sep 17 00:00:00 2001 From: breki Date: Tue, 21 Jul 2026 13:48:15 +0000 Subject: [PATCH 09/10] remove unused includes and functions caused by older commits --- trinity/Eve/EveEffectRoot2.cpp | 1 - trinity/Eve/EveTransform.cpp | 1 - trinity/Include/TriMath.h | 147 ++++++++--------------- trinity/Tr2ProjectBoundingBoxBracket.cpp | 1 - trinity/TriMath.cpp | 5 - trinity/Utilities/BoundingBox.cpp | 1 - 6 files changed, 50 insertions(+), 106 deletions(-) diff --git a/trinity/Eve/EveEffectRoot2.cpp b/trinity/Eve/EveEffectRoot2.cpp index 4b395817f..c7e088f3d 100644 --- a/trinity/Eve/EveEffectRoot2.cpp +++ b/trinity/Eve/EveEffectRoot2.cpp @@ -8,7 +8,6 @@ #include "TriFrustum.h" #include "Lights/Tr2PointLight.h" #include "Tr2LightManager.h" -#include "TriMath.h" #include "Controllers/ITr2Controller.h" #include "Curves/TriCurveSet.h" #include "Eve/EveUpdateContext.h" diff --git a/trinity/Eve/EveTransform.cpp b/trinity/Eve/EveTransform.cpp index cc3bf5c6c..8126a09e0 100644 --- a/trinity/Eve/EveTransform.cpp +++ b/trinity/Eve/EveTransform.cpp @@ -14,7 +14,6 @@ #include "Curves/TriCurveSet.h" #include "Tr2Mesh.h" #include "Tr2MeshBase.h" -#include "TriMath.h" extern float g_eveSpaceSceneLowUpdateRate; diff --git a/trinity/Include/TriMath.h b/trinity/Include/TriMath.h index 5a8f799fd..af3066168 100644 --- a/trinity/Include/TriMath.h +++ b/trinity/Include/TriMath.h @@ -40,16 +40,10 @@ struct Vector3d; bool TriVectorIsIdentical( const Vector3* v1, const Vector3* v2, float epsilon = 1e-5f ); //Rotate a vector by the quaternion -Vector3* TriVectorRotateQuaternion( - Vector3* out, - const Vector3* v, - const Quaternion* q ); +Vector3* TriVectorRotateQuaternion( Vector3* out, const Vector3* v, const Quaternion* q ); //Rotate a vector by the rotation part of a matrix (ignores translation) -Vector3* TriVectorRotateMatrix( - Vector3* out, - const Vector3* v, - const Matrix* m ); +Vector3* TriVectorRotateMatrix( Vector3* out, const Vector3* v, const Matrix* m ); //The next two functions are the most common application //of the previous two functions and can be optimized greatly @@ -57,59 +51,41 @@ Vector3* TriVectorRotateMatrix( //Rotate a unit vector aligned to one of the axes //(defined by xyz) by the quaternion -Vector3* TriVectorRotatedBasisQuaternion( - Vector3* out, - const TRITRANSFORMAXIS xyz, - const Quaternion* q ); +Vector3* TriVectorRotatedBasisQuaternion( Vector3* out, const TRITRANSFORMAXIS xyz, const Quaternion* q ); //Rotate a unit vector aligned to one of the axes //(defined by xyz) by the matrix -Vector3* TriVectorRotatedBasisMatrix( - Vector3* out, - const TRITRANSFORMAXIS xyz, - const Matrix* m ); +Vector3* TriVectorRotatedBasisMatrix( Vector3* out, const TRITRANSFORMAXIS xyz, const Matrix* m ); // -Vector3* TriVectorSpherical( - Vector3* v, - float phi, - float theta, - float rad ); - -Vector3* TriVectorExponentialDecayInteger( - Vector3* pos, - const Vector3* pos0, - const Vector3* vel0, - const Vector3* acc0, - const float mass, - const float drag, - float time ); - -Vector3* TriVectorExponentialDecay( - Vector3* vel, - const Vector3* vel0, - const Vector3* acc0, - const float mass, - const float drag, - float time ); - -Vector3* TriVectorExponentialDecayInteger( - Vector3* pos, - const Vector3* x, - const Vector3* v, - const Vector3* a, - const float m, - const float k, - const float t, - const float pow ); - -Vector3* TriVectorExponentialDecay( - Vector3* vel, - const Vector3* v, - const Vector3* a, - const float k, - const float pow ); +Vector3* TriVectorSpherical( Vector3* v, float phi, float theta, float rad ); + +Vector3* TriVectorExponentialDecayInteger( Vector3* pos, + const Vector3* pos0, + const Vector3* vel0, + const Vector3* acc0, + const float mass, + const float drag, + float time ); + +Vector3* TriVectorExponentialDecay( Vector3* vel, + const Vector3* vel0, + const Vector3* acc0, + const float mass, + const float drag, + float time ); + +Vector3* TriVectorExponentialDecayInteger( Vector3* pos, + const Vector3* x, + const Vector3* v, + const Vector3* a, + const float m, + const float k, + const float t, + const float pow ); + +Vector3* TriVectorExponentialDecay( Vector3* vel, const Vector3* v, const Vector3* a, const float k, const float pow ); // Projects a point onto a plane Vector3 TriVectorProjectOnPlane( const Vector3& point, const Vector3& p0, const Vector3& n ); @@ -140,10 +116,7 @@ float TriFloatRandomGauss( float mu, float deviation ); ///////////////////////////////////////////////////////////////////////////////////////// // Turns a normalized vector into a color. Used to change directions into color. -Color* TriColorFromVector( - Color* c, - const Vector3* v, - float height = 0.0f ); +Color* TriColorFromVector( Color* c, const Vector3* v, float height = 0.0f ); ///////////////////////////////////////////////////////////////////////////////////////// @@ -159,46 +132,27 @@ Color* TriColorFromVector( // Ergo, the sqrt is half as much rotation around the *same* axis as the quaternion. // This is very useful, since a quaternion rotates by theta when w = cos(theta/2) // See TriQuaternionRotationArc for an example of use. -Quaternion* TriQuaternionSqrt( - Quaternion* out, - const Quaternion* q ); +Quaternion* TriQuaternionSqrt( Quaternion* out, const Quaternion* q ); // Builds a quaternion that is the rotation between two vectors about the origin -Quaternion* TriQuaternionRotationArc( - Quaternion* out, - const Vector3* v1, - const Vector3* v2 ); +Quaternion* TriQuaternionRotationArc( Quaternion* out, const Vector3* v1, const Vector3* v2 ); -Quaternion* TriQuaternionArcFromForward( - Quaternion* out, - const Vector3* v ); +Quaternion* TriQuaternionArcFromForward( Quaternion* out, const Vector3* v ); // does something nice, Eggert, please specify.... -Quaternion* TriQuaternionAxisHeading( - Quaternion* out, - const Quaternion* q, - const Vector3* v ); +Quaternion* TriQuaternionAxisHeading( Quaternion* out, const Quaternion* q, const Vector3* v ); //Takes in a vector, and returns a pure unit quaternion with the same rotation axis as the vectors heading -Quaternion* TriQuaternionDirVector( - Quaternion* out, - const Vector3* v ); +Quaternion* TriQuaternionDirVector( Quaternion* out, const Vector3* v ); //out = in*length -Quaternion* TriQuaternionScale( - Quaternion* out, - const Quaternion* in, - float length ); +Quaternion* TriQuaternionScale( Quaternion* out, const Quaternion* in, float length ); //Pre: q is a unit quaternion //Post: yaw in [0;2*pi[ , pitch in [-pi/2;pi/2]; roll in [-pi; pi] // if you yaw and then pitch and then roll you get the same result as using the quaternion -void TriQuaternionToYawPitchRoll( - float* yaw, - float* pitch, - float* roll, - const Quaternion* q ); +void TriQuaternionToYawPitchRoll( float* yaw, float* pitch, float* roll, const Quaternion* q ); ///////////////////////////////////////////////////////////////////////////////////////// // Matrix extensions @@ -233,14 +187,9 @@ Matrix* TriMatrixRemoveScaling( Matrix* out, const Matrix* in ); Matrix* TriMatrixRemoveTranslation( Matrix* out, const Matrix* in ); Matrix* TriMatrixOverwriteTranslation( Matrix* out, const Matrix* in, const Vector3* t ); -Matrix* TriMatrixRotationArc( - Matrix* out, - const Vector3* v1, - const Vector3* v2 ); +Matrix* TriMatrixRotationArc( Matrix* out, const Vector3* v1, const Vector3* v2 ); -Matrix* TriMatrixArcFromForward( - Matrix* out, - const Vector3* v ); +Matrix* TriMatrixArcFromForward( Matrix* out, const Vector3* v ); ///////////////////////////////////////////////////////////////////////////////////////// @@ -261,7 +210,6 @@ double* Matrix4dFromMatrix( double* result, const Matrix& mat ); bool IsFinite( float value ); bool IsFinite( const Vector3& vec ); -bool IsFinite( const Vector4& vec ); float TriClamp( float f, float min, float max ); int32_t ClampInt( int32_t f, int32_t min, int32_t max ); @@ -272,8 +220,7 @@ float TriLinearize( float min, float max, float v ); //pre: f in [0.0 ; 1.0] //post: returnvalue in [0.0 ; 1.0] -float SinSmooth( - float f ); +float SinSmooth( float f ); float CubicInterpolate( float f0, float f1, float f2, float f3, float s ); @@ -335,7 +282,8 @@ class TriPerlinNoise * @param frequencyScale The factor by which to scale the frequency of each octave. Typically greater than 1 (e.g., 2) to ensure that higher octaves have higher frequency. * @return The fractal sum of Perlin noise octaves at the given coordinate. */ - [[nodiscard]] double FractalSum( double x, size_t octaves, double amplitudeScale = 0.5, double frequencyScale = 2 ) const; + [[nodiscard]] double + FractalSum( double x, size_t octaves, double amplitudeScale = 0.5, double frequencyScale = 2 ) const; private: static constexpr int32_t TABLE_SIZE = 256; @@ -351,7 +299,12 @@ double PerlinNoise1D( double x, double invAmplitude, double frequency, int octav // // Coordinate system conversion methods // -bool ConvertProjectionCoordToWorldPickRay( float x, float y, const Matrix* projMat, const Matrix* viewMat, Vector3* rayStart, Vector3* rayDir ); +bool ConvertProjectionCoordToWorldPickRay( float x, + float y, + const Matrix* projMat, + const Matrix* viewMat, + Vector3* rayStart, + Vector3* rayDir ); #endif \ No newline at end of file diff --git a/trinity/Tr2ProjectBoundingBoxBracket.cpp b/trinity/Tr2ProjectBoundingBoxBracket.cpp index 8b9ee07f0..e1599e04c 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.cpp +++ b/trinity/Tr2ProjectBoundingBoxBracket.cpp @@ -10,7 +10,6 @@ #include "TriViewport.h" #include "Sprite2d/Tr2Sprite2dContainer.h" #include "Utilities/BoundingBox.h" -#include "Utilities/BoundingSphere.h" #include "include/ITr2DebugRenderer.h" extern ITr2DebugRendererPtr g_debugRenderer; diff --git a/trinity/TriMath.cpp b/trinity/TriMath.cpp index 058f9ae67..424827e69 100644 --- a/trinity/TriMath.cpp +++ b/trinity/TriMath.cpp @@ -893,11 +893,6 @@ bool IsFinite( const Vector3& vec ) return IsFinite( vec.x ) && IsFinite( vec.y ) && IsFinite( vec.z ); } -bool IsFinite( const Vector4& vec ) -{ - return IsFinite( vec.x ) && IsFinite( vec.y ) && IsFinite( vec.z ) && IsFinite( vec.w ); -} - float TriClamp( float f, float min, diff --git a/trinity/Utilities/BoundingBox.cpp b/trinity/Utilities/BoundingBox.cpp index 3c0fbcb87..3627049f9 100644 --- a/trinity/Utilities/BoundingBox.cpp +++ b/trinity/Utilities/BoundingBox.cpp @@ -1,7 +1,6 @@ // Copyright © 2023 CCP ehf. #include "StdAfx.h" -#include "Include/TriMath.h" #include "BoundingBox.h" #include "BoundingSphere.h" #include "MatrixUtils.h" From e377e7c6d144e47f24cea82075032b971556ff15 Mon Sep 17 00:00:00 2001 From: breki Date: Tue, 21 Jul 2026 15:12:30 +0000 Subject: [PATCH 10/10] better is trivially inside function --- trinity/Tr2ProjectBoundingBoxBracket.cpp | 128 ++++++++++++----------- 1 file changed, 66 insertions(+), 62 deletions(-) diff --git a/trinity/Tr2ProjectBoundingBoxBracket.cpp b/trinity/Tr2ProjectBoundingBoxBracket.cpp index e1599e04c..c8e9e5d42 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.cpp +++ b/trinity/Tr2ProjectBoundingBoxBracket.cpp @@ -49,51 +49,58 @@ Vector4 Lerp( const Vector4& a, const Vector4& b, float t ) }; } -bool AreAllOutsidePlane( const Vector4* points, float ( *planeDistance )( const Vector4& ) ) +// Cohen-Sutherland style outcodes: one bit per plane of the D3D clip volume +// (-w <= x <= w, -w <= y <= w, 0 <= z <= w); a set bit means the point is +// outside that plane. In 2D (4 bits) the zones look like: +// +// | | +// 1001 | 1000 | 1010 +// ------+--------+------ +// 0001 | 0000 | 0010 <- 0000 = inside +// ------+--------+------ +// 0101 | 0100 | 0110 +// +// AND of all corner codes != 0 => every corner shares an outside plane +// => the box is fully off-frustum. +// XOR of two corner codes & CLIP_NEAR => the edge crosses the near plane. +enum ClipPlaneBits : uint32_t { - for( int i = 0; i < 8; ++i ) - { - if( planeDistance( points[i] ) >= 0.0f ) - { - return false; - } - } - return true; -} - -float DistanceToLeftPlane( const Vector4& point ) -{ - return point.x + point.w; -} -float DistanceToRightPlane( const Vector4& point ) -{ - return point.w - point.x; -} -float DistanceToBottomPlane( const Vector4& point ) -{ - return point.y + point.w; -} -float DistanceToTopPlane( const Vector4& point ) -{ - return point.w - point.y; -} -float DistanceToNearPlane( const Vector4& point ) -{ - return point.z; -} -float DistanceToFarPlane( const Vector4& point ) -{ - return point.w - point.z; -} + CLIP_LEFT = 1 << 0, + CLIP_RIGHT = 1 << 1, + CLIP_BOTTOM = 1 << 2, + CLIP_TOP = 1 << 3, + CLIP_NEAR = 1 << 4, + CLIP_FAR = 1 << 5, +}; -bool IsTriviallyOutsideFrustum( const Vector4* points ) +uint32_t ClipOutcode( const Vector4& point ) { - return AreAllOutsidePlane( points, DistanceToLeftPlane ) || - AreAllOutsidePlane( points, DistanceToRightPlane ) || - AreAllOutsidePlane( points, DistanceToBottomPlane ) || - AreAllOutsidePlane( points, DistanceToTopPlane ) || - AreAllOutsidePlane( points, DistanceToNearPlane ) || - AreAllOutsidePlane( points, DistanceToFarPlane ); + uint32_t code = 0; + if( point.x + point.w < 0.0f ) + { + code |= CLIP_LEFT; + } + if( point.w - point.x < 0.0f ) + { + code |= CLIP_RIGHT; + } + if( point.y + point.w < 0.0f ) + { + code |= CLIP_BOTTOM; + } + if( point.w - point.y < 0.0f ) + { + code |= CLIP_TOP; + } + if( point.z < 0.0f ) + { + code |= CLIP_NEAR; + } + if( point.w - point.z < 0.0f ) + { + code |= CLIP_FAR; + } + return code; } bool CanPerspectiveDivide( const Vector4& point ) @@ -101,21 +108,10 @@ bool CanPerspectiveDivide( const Vector4& point ) return fabsf( point.w ) > CLIP_EPSILON; } -void AddIfProjectable( const Vector4& point, std::vector& points ) -{ - if( point.z >= 0.0f && CanPerspectiveDivide( point ) ) - { - points.push_back( point ); - } -} - +// Both endpoints must be on opposite sides of the near plane; the caller +// guarantees this via the outcode test. void AddNearPlaneIntersection( const Vector4& a, const Vector4& b, std::vector& points ) { - if( ( a.z < 0.0f ) == ( b.z < 0.0f ) ) - { - return; - } - float denominator = a.z - b.z; if( fabsf( denominator ) <= CLIP_EPSILON ) { @@ -157,12 +153,17 @@ bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, c corners[7] = Vector3( bbMin.x, bbMax.y, bbMin.z ); Vector4 clipCorners[8]; + uint32_t outcodes[8]; + uint32_t combinedOutcode = ~0u; for( int i = 0; i < 8; ++i ) { clipCorners[i] = TransformPointToClip( corners[i], viewProjection ); + outcodes[i] = ClipOutcode( clipCorners[i] ); + combinedOutcode &= outcodes[i]; } - if( IsTriviallyOutsideFrustum( clipCorners ) ) + // A surviving bit means every corner is outside the same plane + if( combinedOutcode != 0 ) { return false; } @@ -171,7 +172,10 @@ bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, c projectablePoints.reserve( 20 ); for( int i = 0; i < 8; ++i ) { - AddIfProjectable( clipCorners[i], projectablePoints ); + if( !( outcodes[i] & CLIP_NEAR ) && CanPerspectiveDivide( clipCorners[i] ) ) + { + projectablePoints.push_back( clipCorners[i] ); + } } static const int EDGES[12][2] = { @@ -180,7 +184,11 @@ bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, c for( int i = 0; i < 12; ++i ) { - AddNearPlaneIntersection( clipCorners[EDGES[i][0]], clipCorners[EDGES[i][1]], projectablePoints ); + // XOR: the edge endpoints straddle the near plane + if( ( outcodes[EDGES[i][0]] ^ outcodes[EDGES[i][1]] ) & CLIP_NEAR ) + { + AddNearPlaneIntersection( clipCorners[EDGES[i][0]], clipCorners[EDGES[i][1]], projectablePoints ); + } } if( projectablePoints.empty() ) @@ -227,10 +235,6 @@ bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, c float width = maxX - minX; float height = maxY - minY; - if( width <= 0.0f || height <= 0.0f ) - { - return false; - } float viewportLeft = static_cast( viewport.x ); float viewportTop = static_cast( viewport.y );