From d78e3ece2a711fc166bcd2525fd8811f13738e01 Mon Sep 17 00:00:00 2001 From: breki Date: Thu, 27 Aug 2026 09:25:40 +0000 Subject: [PATCH 1/6] code review tweaks --- trinity/Eve/EvePlanet.cpp | 2 +- trinity/Tr2ProjectBoundingBoxBracket.cpp | 26 +++--------------------- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/trinity/Eve/EvePlanet.cpp b/trinity/Eve/EvePlanet.cpp index 5924a7489..2d9e41cf4 100644 --- a/trinity/Eve/EvePlanet.cpp +++ b/trinity/Eve/EvePlanet.cpp @@ -158,7 +158,7 @@ bool EvePlanet::GetWorldBoundingBox( Vector3& min, Vector3& max ) const return false; } - const float renderScale = m_renderScale > 0.0f ? m_renderScale : 1.0f; + const float renderScale = std::max( m_renderScale, 1.0f ); const Matrix scaledTransform = CalculatePlanetScaleTransform( m_worldTransform, renderScale ); sphere = Vector4( scaledTransform.GetTranslation(), m_radius / renderScale ); diff --git a/trinity/Tr2ProjectBoundingBoxBracket.cpp b/trinity/Tr2ProjectBoundingBoxBracket.cpp index c8e9e5d42..2ae697ca9 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.cpp +++ b/trinity/Tr2ProjectBoundingBoxBracket.cpp @@ -29,26 +29,6 @@ struct ProjectedBounds bool coversViewport; }; -Vector4 TransformPointToClip( const Vector3& point, const Matrix& viewProjection ) -{ - 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, - point.x * viewProjection._14 + point.y * viewProjection._24 + point.z * viewProjection._34 + viewProjection._44 - }; -} - -Vector4 Lerp( const Vector4& a, const Vector4& b, float t ) -{ - return Vector4{ - 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 - }; -} - // 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: @@ -119,7 +99,7 @@ void AddNearPlaneIntersection( const Vector4& a, const Vector4& b, std::vector= 0.0f && clipCenter.w > 0.0f && ProjectClipPoint( clipCenter, viewport, projectedCenter ) ) { From b2b20f69221f2568fcfb21b401c26dfbb9399df0 Mon Sep 17 00:00:00 2001 From: breki Date: Fri, 28 Aug 2026 14:38:20 +0000 Subject: [PATCH 2/6] replace AABB boundingbox with OBB to fix bracket not hugging boundingbox sides properly --- trinity/Eve/EveEffectRoot2.cpp | 15 +- trinity/Eve/EveEffectRoot2.h | 2 +- trinity/Eve/EvePlanet.cpp | 13 +- trinity/Eve/EvePlanet.h | 2 +- trinity/Eve/EveTransform.cpp | 6 +- trinity/Eve/EveTransform.h | 2 +- trinity/Eve/SpaceObject/EveSpaceObject2.cpp | 7 +- trinity/Eve/SpaceObject/EveSpaceObject2.h | 2 +- trinity/Include/ITr2BoundingBox.h | 4 +- trinity/Interior/Tr2IntSkinnedObject.cpp | 14 +- trinity/Interior/Tr2IntSkinnedObject.h | 3 + trinity/Interior/Tr2InteriorPlaceable.cpp | 12 ++ trinity/Interior/Tr2InteriorPlaceable.h | 4 + trinity/Tr2ProjectBoundingBoxBracket.cpp | 149 ++++++++++++++---- trinity/Tr2ProjectBoundingBoxBracket.h | 3 + trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp | 8 + 16 files changed, 190 insertions(+), 56 deletions(-) diff --git a/trinity/Eve/EveEffectRoot2.cpp b/trinity/Eve/EveEffectRoot2.cpp index 02b00254c..8f1b65fd1 100644 --- a/trinity/Eve/EveEffectRoot2.cpp +++ b/trinity/Eve/EveEffectRoot2.cpp @@ -5,6 +5,7 @@ #include "Utilities/BoundingSphere.h" #include "Utilities/BoundingBox.h" +#include "Utilities/Obb.h" #include "TriFrustum.h" #include "Lights/Tr2PointLight.h" #include "Tr2LightManager.h" @@ -431,15 +432,23 @@ void EveEffectRoot2::GetLocalToWorldTransform( Matrix& transform ) const transform = m_lastUpdateMatrix; } -bool EveEffectRoot2::GetWorldBoundingBox( Vector3& min, Vector3& max ) const +bool EveEffectRoot2::GetWorldBoundingObb( Obb& obb ) const { if( m_boundingSphere.w <= 0.0f ) { return false; } - BoundingBoxInitialize( m_boundingSphere, min, max ); - BoundingBoxTransform( min, max, m_lastUpdateMatrix ); + // A sphere's bounds must not depend on orientation, so the box is world-axis + // aligned; only the scale is taken from the transform. + const Vector3 sphereCenter( m_boundingSphere.x, m_boundingSphere.y, m_boundingSphere.z ); + const float scale = std::max( std::max( Length( m_lastUpdateMatrix.GetX() ), Length( m_lastUpdateMatrix.GetY() ) ), Length( m_lastUpdateMatrix.GetZ() ) ); + const float radius = m_boundingSphere.w * scale; + obb.x = Vector3( 1.0f, 0.0f, 0.0f ); + obb.y = Vector3( 0.0f, 1.0f, 0.0f ); + obb.z = Vector3( 0.0f, 0.0f, 1.0f ); + obb.center = TransformCoord( sphereCenter, m_lastUpdateMatrix ); + obb.sizes = Vector3( radius, radius, radius ); return true; } diff --git a/trinity/Eve/EveEffectRoot2.h b/trinity/Eve/EveEffectRoot2.h index e3a3af712..bba42d501 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 GetWorldBoundingObb( Obb & obb ) const override; bool IsBoundingBoxReady() const override; ///////////////////////////////////////////////////////////////////////////////////// diff --git a/trinity/Eve/EvePlanet.cpp b/trinity/Eve/EvePlanet.cpp index 2d9e41cf4..843c5047f 100644 --- a/trinity/Eve/EvePlanet.cpp +++ b/trinity/Eve/EvePlanet.cpp @@ -8,6 +8,7 @@ #include "EveUpdateContext.h" #include "Curves/TriCurveSet.h" #include "Utilities/BoundingBox.h" +#include "Utilities/Obb.h" const float EvePlanet::SCALE = 1000000.0f; @@ -150,9 +151,8 @@ Quaternion EvePlanet::GetWorldRotation() return m_rotation; } -bool EvePlanet::GetWorldBoundingBox( Vector3& min, Vector3& max ) const +bool EvePlanet::GetWorldBoundingObb( Obb& obb ) const { - Vector4 sphere; if( m_radius <= 0.0f ) { return false; @@ -160,9 +160,12 @@ bool EvePlanet::GetWorldBoundingBox( Vector3& min, Vector3& max ) const const float renderScale = std::max( m_renderScale, 1.0f ); const Matrix scaledTransform = CalculatePlanetScaleTransform( m_worldTransform, renderScale ); - sphere = Vector4( scaledTransform.GetTranslation(), m_radius / renderScale ); - - BoundingBoxInitialize( sphere, min, max ); + const float radius = m_radius / renderScale; + obb.x = Vector3( 1.0f, 0.0f, 0.0f ); + obb.y = Vector3( 0.0f, 1.0f, 0.0f ); + obb.z = Vector3( 0.0f, 0.0f, 1.0f ); + obb.center = scaledTransform.GetTranslation(); + obb.sizes = Vector3( radius, radius, radius ); return true; } diff --git a/trinity/Eve/EvePlanet.h b/trinity/Eve/EvePlanet.h index c79c40cb7..1ebff3862 100644 --- a/trinity/Eve/EvePlanet.h +++ b/trinity/Eve/EvePlanet.h @@ -60,7 +60,7 @@ BLUE_CLASS( EvePlanet ) : virtual Quaternion GetWorldRotation(); // ITr2BoundingBox - bool GetWorldBoundingBox( Vector3 & min, Vector3 & max ) const override; + bool GetWorldBoundingObb( Obb & obb ) const override; bool IsBoundingBoxReady() const override; // ITr2SecondaryLightSource diff --git a/trinity/Eve/EveTransform.cpp b/trinity/Eve/EveTransform.cpp index 8126a09e0..41d12a2ab 100644 --- a/trinity/Eve/EveTransform.cpp +++ b/trinity/Eve/EveTransform.cpp @@ -6,6 +6,7 @@ #include "Utilities/Vector3d.h" #include "Utilities/BoundingSphere.h" #include "Utilities/BoundingBox.h" +#include "Utilities/Obb.h" #include "TriFrustum.h" #include "Particle/Tr2ParticleSystem.h" #include "Particle/ITr2GenericEmitter.h" @@ -381,13 +382,14 @@ bool EveTransform::GetLocalBoundingBox( Vector3& min, Vector3& max ) return true; } -bool EveTransform::GetWorldBoundingBox( Vector3& min, Vector3& max ) const +bool EveTransform::GetWorldBoundingObb( Obb& obb ) const { + Vector3 min, max; if( !GetDirectLocalBounds( m_overrideBoundsMin, m_overrideBoundsMax, m_mesh, min, max ) ) { return false; } - BoundingBoxTransform( min, max, m_worldTransform ); + obb.CreateClippedWorldBoundingObb( min, max, m_worldTransform, nullptr ); return true; } diff --git a/trinity/Eve/EveTransform.h b/trinity/Eve/EveTransform.h index 57f90e6e1..75078edea 100644 --- a/trinity/Eve/EveTransform.h +++ b/trinity/Eve/EveTransform.h @@ -69,7 +69,7 @@ BLUE_CLASS( EveTransform ) : ///////////////////////////////////////////////////////////////////////////////////// // ITr2BoundingBox - bool GetWorldBoundingBox( Vector3 & min, Vector3 & max ) const override; + bool GetWorldBoundingObb( Obb & obb ) const override; bool IsBoundingBoxReady() const override; ///////////////////////////////////////////////////////////////////////////////////// diff --git a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp index 3ac83d052..f0c208368 100644 --- a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp +++ b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp @@ -5,6 +5,7 @@ #include "Utilities/BoundingBox.h" #include "Utilities/BoundingSphere.h" #include "Utilities/MatrixUtils.h" +#include "Utilities/Obb.h" #include "include/ITr2DebugRenderer.h" #include "include/IEveBallpark.h" @@ -4165,11 +4166,9 @@ void EveSpaceObject2::GetPickingBatches( ITriRenderBatchAccumulator* batches, Tr } } -bool EveSpaceObject2::GetWorldBoundingBox( Vector3& min, Vector3& max ) const +bool EveSpaceObject2::GetWorldBoundingObb( Obb& obb ) const { - min = m_localAabbMin; - max = m_localAabbMax; - BoundingBoxTransform( min, max, m_worldTransform ); + obb.CreateClippedWorldBoundingObb( m_localAabbMin, m_localAabbMax, m_worldTransform, nullptr ); return true; } diff --git a/trinity/Eve/SpaceObject/EveSpaceObject2.h b/trinity/Eve/SpaceObject/EveSpaceObject2.h index 74f6522e4..0986c219d 100644 --- a/trinity/Eve/SpaceObject/EveSpaceObject2.h +++ b/trinity/Eve/SpaceObject/EveSpaceObject2.h @@ -347,7 +347,7 @@ BLUE_CLASS( EveSpaceObject2 ) : ///////////////////////////////////////////////////////////////////////////////////// // ITr2BoundingBox - virtual bool GetWorldBoundingBox( Vector3 & min, Vector3 & max ) const; + virtual bool GetWorldBoundingObb( Obb & obb ) const; virtual bool IsBoundingBoxReady() const; ///////////////////////////////////////////////////////////////////////////////////// diff --git a/trinity/Include/ITr2BoundingBox.h b/trinity/Include/ITr2BoundingBox.h index 0a54ce568..98a143533 100644 --- a/trinity/Include/ITr2BoundingBox.h +++ b/trinity/Include/ITr2BoundingBox.h @@ -5,12 +5,12 @@ #ifndef ITr2BoundingBox_h #define ITr2BoundingBox_h -struct Vector3; +struct Obb; BLUE_INTERFACE( ITr2BoundingBox ) : IRoot { - virtual bool GetWorldBoundingBox( Vector3 & min, Vector3 & max ) const = 0; + virtual bool GetWorldBoundingObb( Obb & obb ) const = 0; virtual bool IsBoundingBoxReady( void ) const = 0; }; diff --git a/trinity/Interior/Tr2IntSkinnedObject.cpp b/trinity/Interior/Tr2IntSkinnedObject.cpp index 319551702..f918aea90 100644 --- a/trinity/Interior/Tr2IntSkinnedObject.cpp +++ b/trinity/Interior/Tr2IntSkinnedObject.cpp @@ -5,6 +5,7 @@ #include "Tr2IntSkinnedObject.h" #include "Utilities/BoundingSphere.h" +#include "Utilities/Obb.h" #include "TriSettingsRegistrar.h" #include "Tr2PerObjectData.h" #include "Resources/TriGeometryRes.h" @@ -147,10 +148,21 @@ bool Tr2IntSkinnedObject::GetWorldBoundingBox( Vector3& min, Vector3& max ) cons return true; } +bool Tr2IntSkinnedObject::GetWorldBoundingObb( Obb& obb ) const +{ + Vector3 min, max; + if( !GetLocalBoundingBox( min, max ) ) + { + return false; + } + obb.CreateClippedWorldBoundingObb( min, max, GetSkinningTransform(), nullptr ); + return true; +} + bool Tr2IntSkinnedObject::IsBoundingBoxReady( void ) const { Vector3 min, max; - return GetWorldBoundingBox( min, max ); + return GetLocalBoundingBox( min, max ); } void Tr2IntSkinnedObject::AddToApexScene( Tr2ApexScene* apexScene ) diff --git a/trinity/Interior/Tr2IntSkinnedObject.h b/trinity/Interior/Tr2IntSkinnedObject.h index 8cf33afdf..1da1fe254 100644 --- a/trinity/Interior/Tr2IntSkinnedObject.h +++ b/trinity/Interior/Tr2IntSkinnedObject.h @@ -92,6 +92,9 @@ class Tr2IntSkinnedObject : public ITr2InteriorDynamic, virtual bool GetWorldBoundingBox( Vector3& min, Vector3& max ) const; virtual bool IsBoundingBoxReady( void ) const; + // ITr2BoundingBox + virtual bool GetWorldBoundingObb( Obb& obb ) const; + // Apex void AddToApexScene( Tr2ApexScene* apexScene ); void RemoveFromApexScene( void ); diff --git a/trinity/Interior/Tr2InteriorPlaceable.cpp b/trinity/Interior/Tr2InteriorPlaceable.cpp index 498d2a98a..1caade56e 100644 --- a/trinity/Interior/Tr2InteriorPlaceable.cpp +++ b/trinity/Interior/Tr2InteriorPlaceable.cpp @@ -8,6 +8,7 @@ // Trinity headers #include "Utilities/BoundingSphere.h" +#include "Utilities/Obb.h" #include "Tr2PerObjectData.h" #include "Wod/WodPlaceableRes.h" #include "Tr2Mesh.h" @@ -135,6 +136,17 @@ bool Tr2InteriorPlaceable::GetWorldBoundingBox( Vector3& min, Vector3& max ) con return true; } +bool Tr2InteriorPlaceable::GetWorldBoundingObb( Obb& obb ) const +{ + Vector3 min, max; + if( !GetLocalBoundingBox( min, max ) ) + { + return false; + } + obb.CreateClippedWorldBoundingObb( min, max, m_transform, nullptr ); + return true; +} + bool Tr2InteriorPlaceable::IsBoundingBoxReady( void ) const { return m_isBoundingBoxModified || ( m_placeableRes && m_placeableRes->IsReady() ); diff --git a/trinity/Interior/Tr2InteriorPlaceable.h b/trinity/Interior/Tr2InteriorPlaceable.h index 7500f45ac..71e5dbeb7 100644 --- a/trinity/Interior/Tr2InteriorPlaceable.h +++ b/trinity/Interior/Tr2InteriorPlaceable.h @@ -74,6 +74,10 @@ class Tr2InteriorPlaceable : public INotify, virtual bool GetLocalBoundingBox( Vector3& min, Vector3& max ) const; virtual bool GetWorldBoundingBox( Vector3& min, Vector3& max ) const; virtual bool IsBoundingBoxReady( void ) const; + + ///////////////////////////////////////////////////////////////////////////////////// + // ITr2BoundingBox + virtual bool GetWorldBoundingObb( Obb& obb ) const; virtual void PrePhysicsUpdate( Be::Time time ); virtual void PostPhysicsUpdate( Be::Time time, Tr2ApexScene* apexScene ); diff --git a/trinity/Tr2ProjectBoundingBoxBracket.cpp b/trinity/Tr2ProjectBoundingBoxBracket.cpp index 2ae697ca9..aa58d4110 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.cpp +++ b/trinity/Tr2ProjectBoundingBoxBracket.cpp @@ -9,7 +9,7 @@ #include "Tr2Renderer.h" #include "TriViewport.h" #include "Sprite2d/Tr2Sprite2dContainer.h" -#include "Utilities/BoundingBox.h" +#include "Utilities/Obb.h" #include "include/ITr2DebugRenderer.h" extern ITr2DebugRendererPtr g_debugRenderer; @@ -18,6 +18,17 @@ namespace { const float CLIP_EPSILON = 1e-5f; +// Obb::GetPoint corner index bits: 0x1 = -X, 0x2 = -Y, 0x4 = -Z; +// each edge joins the two corners differing in exactly one axis sign. +const int OBB_EDGES[12][2] = { + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 0, 2 }, { 1, 3 }, { 4, 6 }, { 5, 7 }, + { 0, 4 }, { 1, 5 }, { 2, 6 }, { 3, 7 }, +}; + +// 8 corners plus at most one near-plane cut per edge +const int MAX_PROJECTABLE_POINTS = 20; + struct ProjectedBounds { float x; @@ -88,9 +99,17 @@ bool CanPerspectiveDivide( const Vector4& point ) return fabsf( point.w ) > CLIP_EPSILON; } +// The Transform( Vector3, Matrix ) overload computes w from the matrix's fourth +// column alone, dropping the point's components, so points must go through the +// Vector4 overload to get a usable clip-space w. +Vector4 TransformPoint( const Vector3& point, const Matrix& matrix ) +{ + return Transform( Vector4( point, 1.0f ), matrix ); +} + // 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 ) +void AddNearPlaneIntersection( const Vector4& a, const Vector4& b, Vector4* points, int& pointCount ) { float denominator = a.z - b.z; if( fabsf( denominator ) <= CLIP_EPSILON ) @@ -102,10 +121,28 @@ void AddNearPlaneIntersection( const Vector4& a, const Vector4& b, std::vector obb.sizes[i] * LengthSq( *axes[i] ) ) + { + return false; + } + } + return true; +} + bool ProjectClipPoint( const Vector4& point, const TriViewport& viewport, Vector3& projected ) { if( !CanPerspectiveDivide( point ) ) @@ -120,24 +157,14 @@ bool ProjectClipPoint( const Vector4& point, const TriViewport& viewport, Vector return true; } -bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, const Matrix& viewProjection, const TriViewport& viewport, ProjectedBounds& bounds ) +bool ProjectBoundingBoxToViewport( const Obb& obb, 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 ); - Vector4 clipCorners[8]; uint32_t outcodes[8]; uint32_t combinedOutcode = ~0u; for( int i = 0; i < 8; ++i ) { - clipCorners[i] = Transform( corners[i], viewProjection ); + clipCorners[i] = TransformPoint( obb.GetPoint( i ), viewProjection ); outcodes[i] = ClipOutcode( clipCorners[i] ); combinedOutcode &= outcodes[i]; } @@ -148,30 +175,26 @@ bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, c return false; } - std::vector projectablePoints; - projectablePoints.reserve( 20 ); + Vector4 projectablePoints[MAX_PROJECTABLE_POINTS]; + int pointCount = 0; for( int i = 0; i < 8; ++i ) { if( !( outcodes[i] & CLIP_NEAR ) && CanPerspectiveDivide( clipCorners[i] ) ) { - projectablePoints.push_back( clipCorners[i] ); + projectablePoints[pointCount++] = clipCorners[i]; } } - 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 ) { // XOR: the edge endpoints straddle the near plane - if( ( outcodes[EDGES[i][0]] ^ outcodes[EDGES[i][1]] ) & CLIP_NEAR ) + if( ( outcodes[OBB_EDGES[i][0]] ^ outcodes[OBB_EDGES[i][1]] ) & CLIP_NEAR ) { - AddNearPlaneIntersection( clipCorners[EDGES[i][0]], clipCorners[EDGES[i][1]], projectablePoints ); + AddNearPlaneIntersection( clipCorners[OBB_EDGES[i][0]], clipCorners[OBB_EDGES[i][1]], projectablePoints, pointCount ); } } - if( projectablePoints.empty() ) + if( pointCount == 0 ) { return false; } @@ -184,8 +207,9 @@ bool ProjectBoundingBoxToViewport( const Vector3& bbMin, const Vector3& bbMax, c float maxX = 0.0f; float maxY = 0.0f; - for( const Vector4& point : projectablePoints ) + for( int i = 0; i < pointCount; ++i ) { + const Vector4& point = projectablePoints[i]; if( !ProjectClipPoint( point, viewport, projected ) ) { continue; @@ -272,6 +296,50 @@ float ClampProjectedSize( float size, float minSize, float maxSize ) } return size; } + +const uint32_t DEBUG_OBB_COLOR = 0xffffff00; +const uint32_t DEBUG_RECT_COLOR = 0xffff00ff; + +void DrawDebugObb( const Obb& obb ) +{ + for( int i = 0; i < 12; ++i ) + { + g_debugRenderer->DrawLine( obb.GetPoint( OBB_EDGES[i][0] ), obb.GetPoint( OBB_EDGES[i][1] ), DEBUG_OBB_COLOR ); + } +} + +// Draws the published screen rect as world-space lines at the box center's +// depth, so it visually coincides with the bracket sprite. +void DrawDebugRect( float x, float y, float width, float height, const Vector3& center, const Matrix& viewProjection, const TriViewport& viewport ) +{ + if( viewport.width <= 0 || viewport.height <= 0 ) + { + return; + } + + const Vector4 clipCenter = TransformPoint( center, viewProjection ); + if( clipCenter.w <= CLIP_EPSILON || clipCenter.z < 0.0f ) + { + return; + } + const float ndcZ = std::min( std::max( clipCenter.z / clipCenter.w, 0.001f ), 0.999f ); + + const Matrix inverseViewProjection = Inverse( viewProjection ); + const float screenCorners[4][2] = { + { x, y }, { x + width, y }, { x + width, y + height }, { x, y + height } + }; + Vector3 worldCorners[4]; + for( int i = 0; i < 4; ++i ) + { + const float ndcX = 2.0f * ( screenCorners[i][0] - static_cast( viewport.x ) ) / static_cast( viewport.width ) - 1.0f; + const float ndcY = 1.0f - 2.0f * ( screenCorners[i][1] - static_cast( viewport.y ) ) / static_cast( viewport.height ); + worldCorners[i] = TransformCoord( Vector3( ndcX, ndcY, ndcZ ), inverseViewProjection ); + } + for( int i = 0; i < 4; ++i ) + { + g_debugRenderer->DrawLine( worldCorners[i], worldCorners[( i + 1 ) % 4], DEBUG_RECT_COLOR ); + } +} } @@ -286,6 +354,7 @@ Tr2ProjectBoundingBoxBracket::Tr2ProjectBoundingBoxBracket( IRoot* lockobj /*= N m_projectedWidth( 0.0f ), m_projectedHeight( 0.0f ), m_integerCoordinates( true ), + m_debugDraw( false ), m_screenMargin( 0.0f ), m_cameraDistance( 0 ), m_isProjectionValid( false ), @@ -298,19 +367,24 @@ Tr2ProjectBoundingBoxBracket::Tr2ProjectBoundingBoxBracket( IRoot* lockobj /*= N void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) { - Vector3 bbMin, bbMax; - if( !m_object || !m_object->IsBoundingBoxReady() || !m_object->GetWorldBoundingBox( bbMin, bbMax ) ) + Obb obb; + if( !m_object || !m_object->IsBoundingBoxReady() || !m_object->GetWorldBoundingObb( obb ) ) { SetEmptyProjection(); return; } - const Vector3 center = ( bbMax + bbMin ) * 0.5f; + const bool debugDraw = m_debugDraw && g_debugRenderer; + if( debugDraw ) + { + DrawDebugObb( obb ); + } + const Vector3 viewPosition = Tr2Renderer::GetViewPosition(); - m_cameraDistance = Length( viewPosition - center ); + m_cameraDistance = Length( viewPosition - obb.center ); const TriViewport& viewport = Tr2Renderer::GetViewport(); - if( BoundingBoxIsInside( bbMin, bbMax, viewPosition ) ) + if( ObbContainsPoint( obb, viewPosition ) ) { SetFullViewportProjection( viewport ); return; @@ -318,7 +392,7 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) Matrix viewProjection = Tr2Renderer::GetViewTransform() * Tr2Renderer::GetProjectionTransform(); ProjectedBounds projectedBounds; - if( !ProjectBoundingBoxToViewport( bbMin, bbMax, viewProjection, viewport, projectedBounds ) ) + if( !ProjectBoundingBoxToViewport( obb, viewProjection, viewport, projectedBounds ) ) { SetEmptyProjection(); return; @@ -333,8 +407,13 @@ void Tr2ProjectBoundingBoxBracket::UpdateValue( double time ) m_extendsOffscreen = projectedBounds.extendsOffscreen; m_coversViewport = projectedBounds.coversViewport; - ConstrainProjection( center, viewProjection, viewport ); + ConstrainProjection( obb.center, viewProjection, viewport ); PublishProjection( viewport ); + + if( debugDraw && m_isProjectionValid ) + { + DrawDebugRect( m_projectedX, m_projectedY, m_projectedWidth, m_projectedHeight, obb.center, viewProjection, viewport ); + } } void Tr2ProjectBoundingBoxBracket::SetEmptyProjection() @@ -388,7 +467,7 @@ void Tr2ProjectBoundingBoxBracket::ConstrainProjection( const Vector3& center, c { // Bounded brackets are anchored on the projected 3d box center, not the projected // rect center, unless the box center is behind the near plane. - Vector4 clipCenter = Transform( center, viewProjection ); + Vector4 clipCenter = TransformPoint( center, viewProjection ); Vector3 projectedCenter; if( clipCenter.z >= 0.0f && clipCenter.w > 0.0f && ProjectClipPoint( clipCenter, viewport, projectedCenter ) ) { @@ -428,7 +507,7 @@ void Tr2ProjectBoundingBoxBracket::PublishProjection( const TriViewport& viewpor m_isProjectionValid = true; UpdateBracket(); - if( g_debugRenderer ) + if( m_debugDraw && g_debugRenderer ) { int x = static_cast( m_projectedX ); int y = static_cast( m_projectedY ); diff --git a/trinity/Tr2ProjectBoundingBoxBracket.h b/trinity/Tr2ProjectBoundingBoxBracket.h index 36e62c7f5..971ddba60 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.h +++ b/trinity/Tr2ProjectBoundingBoxBracket.h @@ -39,6 +39,9 @@ class Tr2ProjectBoundingBoxBracket : public ITriFunction // Should the coordinates be rounded to the nearest integer? Defaults to true. bool m_integerCoordinates; + // Draw the consumed bounds and the published rect through the debug renderer. + bool m_debugDraw; + float m_minProjectedWidth; float m_minProjectedHeight; float m_maxProjectedWidth; diff --git a/trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp b/trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp index d9e3c7870..78a076392 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp +++ b/trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp @@ -105,6 +105,14 @@ const Be::ClassInfo* Tr2ProjectBoundingBoxBracket::ExposeToBlue() "If set, projected x, y, width and height are rounded to the nearest integer.", Be::READWRITE ) + MAP_ATTRIBUTE( + "debugDraw", + m_debugDraw, + "If set, and a debug renderer is set, draws the bounds the projection\n" + "consumed as a world-space wireframe, the published rect, and the\n" + "bracket's name and size at its projected position.", + Be::READWRITE ) + MAP_ATTRIBUTE( "screenMargin", m_screenMargin, From 44998fd75bdd3c8bd285b092a3939de903fee7a3 Mon Sep 17 00:00:00 2001 From: breki Date: Mon, 31 Aug 2026 11:17:20 +0000 Subject: [PATCH 3/6] split up obb helper function that was two functions in one, so passing in nullptr isnt needed anymore --- trinity/Eve/EveTransform.cpp | 2 +- trinity/Eve/SpaceObject/EveSpaceObject2.cpp | 2 +- trinity/Interior/Tr2IntSkinnedObject.cpp | 2 +- trinity/Interior/Tr2InteriorPlaceable.cpp | 2 +- trinity/Utilities/Obb.cpp | 37 +++++++++++++++------ trinity/Utilities/Obb.h | 1 + 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/trinity/Eve/EveTransform.cpp b/trinity/Eve/EveTransform.cpp index 41d12a2ab..977b5f4ca 100644 --- a/trinity/Eve/EveTransform.cpp +++ b/trinity/Eve/EveTransform.cpp @@ -389,7 +389,7 @@ bool EveTransform::GetWorldBoundingObb( Obb& obb ) const { return false; } - obb.CreateClippedWorldBoundingObb( min, max, m_worldTransform, nullptr ); + obb.CreateWorldBoundingObb( min, max, m_worldTransform ); return true; } diff --git a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp index f0c208368..951f92042 100644 --- a/trinity/Eve/SpaceObject/EveSpaceObject2.cpp +++ b/trinity/Eve/SpaceObject/EveSpaceObject2.cpp @@ -4168,7 +4168,7 @@ void EveSpaceObject2::GetPickingBatches( ITriRenderBatchAccumulator* batches, Tr bool EveSpaceObject2::GetWorldBoundingObb( Obb& obb ) const { - obb.CreateClippedWorldBoundingObb( m_localAabbMin, m_localAabbMax, m_worldTransform, nullptr ); + obb.CreateWorldBoundingObb( m_localAabbMin, m_localAabbMax, m_worldTransform ); return true; } diff --git a/trinity/Interior/Tr2IntSkinnedObject.cpp b/trinity/Interior/Tr2IntSkinnedObject.cpp index f918aea90..e6ca62742 100644 --- a/trinity/Interior/Tr2IntSkinnedObject.cpp +++ b/trinity/Interior/Tr2IntSkinnedObject.cpp @@ -155,7 +155,7 @@ bool Tr2IntSkinnedObject::GetWorldBoundingObb( Obb& obb ) const { return false; } - obb.CreateClippedWorldBoundingObb( min, max, GetSkinningTransform(), nullptr ); + obb.CreateWorldBoundingObb( min, max, GetSkinningTransform() ); return true; } diff --git a/trinity/Interior/Tr2InteriorPlaceable.cpp b/trinity/Interior/Tr2InteriorPlaceable.cpp index 1caade56e..d519d648a 100644 --- a/trinity/Interior/Tr2InteriorPlaceable.cpp +++ b/trinity/Interior/Tr2InteriorPlaceable.cpp @@ -143,7 +143,7 @@ bool Tr2InteriorPlaceable::GetWorldBoundingObb( Obb& obb ) const { return false; } - obb.CreateClippedWorldBoundingObb( min, max, m_transform, nullptr ); + obb.CreateWorldBoundingObb( min, max, m_transform ); return true; } diff --git a/trinity/Utilities/Obb.cpp b/trinity/Utilities/Obb.cpp index b5701d274..6c779dfc5 100644 --- a/trinity/Utilities/Obb.cpp +++ b/trinity/Utilities/Obb.cpp @@ -7,33 +7,50 @@ // ------------------------------------------------------------- // Description: -// Computes an Oriented Bounding Box in world space, which has been shrunk -// to fit the current viewing frustum as tightly as possible without visibly clipping -// into it. The frustum is derived from Tr2Renderer GetViewTransform, GetProjectionTransform, and so on. +// Computes an Oriented Bounding Box in world space. // The calculation starts out with the AABB from GetLocalBoundingBox, so the explicit bounds // are supported. // Arguments: // localMin - minimum of AABB in local coordinates // localMax - maximum of AABB in local coordinates // localToWorld - [in] transform that takes this skinned object from local to world coordinates -// frustum - frustum to shrink against. If null, no shrinking occurs and this is just a convenience setup helper (AABB->OBB) // Summary: -// Compute a world-space OBB, tightened for the current frustum. +// Compute a world-space OBB. // ------------------------------------------------------------- -void Obb::CreateClippedWorldBoundingObb( const Vector3& min, const Vector3& max, const Matrix& localToWorld, const TriFrustum* frustum ) +void Obb::CreateWorldBoundingObb( const Vector3& localMin, const Vector3& localMax, const Matrix& localToWorld ) { - // take AABB center.. - center = 0.5 * ( max + min ); + // take AABB center + center = 0.5 * ( localMax + localMin ); - // .. and move to world space. + // and move to world space. Vector4 centerWorld = Transform( center, localToWorld ); center = Vector3( centerWorld.x, centerWorld.y, centerWorld.z ); - sizes = 0.5 * ( max - min ); + sizes = 0.5 * ( localMax - localMax ); x = Vector3( localToWorld._11, localToWorld._12, localToWorld._13 ); y = Vector3( localToWorld._21, localToWorld._22, localToWorld._23 ); z = Vector3( localToWorld._31, localToWorld._32, localToWorld._33 ); +} + +// ------------------------------------------------------------- +// Description: +// Computes an Oriented Bounding Box in world space, which has been shrunk +// to fit the current viewing frustum as tightly as possible without visibly clipping +// into it. The frustum is derived from Tr2Renderer GetViewTransform, GetProjectionTransform, and so on. +// The calculation starts out with the AABB from GetLocalBoundingBox, so the explicit bounds +// are supported. +// Arguments: +// localMin - minimum of AABB in local coordinates +// localMax - maximum of AABB in local coordinates +// localToWorld - [in] transform that takes this skinned object from local to world coordinates +// frustum - frustum to shrink against. If null, no shrinking occurs and this is just a convenience setup helper (AABB->OBB) +// Summary: +// Compute a world-space OBB, tightened for the current frustum. +// ------------------------------------------------------------- +void Obb::CreateClippedWorldBoundingObb( const Vector3& localMin, const Vector3& localMax, const Matrix& localToWorld, const TriFrustum* frustum ) +{ + CreateWorldBoundingObb( localMin, localMax, localToWorld ); if( !frustum ) { diff --git a/trinity/Utilities/Obb.h b/trinity/Utilities/Obb.h index a68b1c3da..30a0455d8 100644 --- a/trinity/Utilities/Obb.h +++ b/trinity/Utilities/Obb.h @@ -31,6 +31,7 @@ struct Obb // sizes - half the size of the OBB along every axis x, y or z. Ie you get to a corner point with center + sizes[0] * x. Full width/height/depth is sizes*2. Vector3 sizes; + void CreateWorldBoundingObb( const Vector3& localMin, const Vector3& localMax, const Matrix& localToWorld ); void CreateClippedWorldBoundingObb( const Vector3& localMin, const Vector3& localMax, const Matrix& localToWorld, const TriFrustum* frustum ); Vector3 GetPoint( unsigned N ) const; From f5198c02fc45a4691e2d925939a2f7070d8c1915 Mon Sep 17 00:00:00 2001 From: breki Date: Mon, 31 Aug 2026 11:52:07 +0000 Subject: [PATCH 4/6] use ccpmath sphere for evePlanet and EveEffectRoot2 world obb and added a function to create the obb from sphere --- trinity/Eve/EveEffectRoot2.cpp | 13 +++---------- trinity/Eve/EvePlanet.cpp | 7 ++----- trinity/Utilities/Obb.cpp | 10 ++++++++++ trinity/Utilities/Obb.h | 1 + 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/trinity/Eve/EveEffectRoot2.cpp b/trinity/Eve/EveEffectRoot2.cpp index 8f1b65fd1..0acff7b76 100644 --- a/trinity/Eve/EveEffectRoot2.cpp +++ b/trinity/Eve/EveEffectRoot2.cpp @@ -439,16 +439,9 @@ bool EveEffectRoot2::GetWorldBoundingObb( Obb& obb ) const return false; } - // A sphere's bounds must not depend on orientation, so the box is world-axis - // aligned; only the scale is taken from the transform. - const Vector3 sphereCenter( m_boundingSphere.x, m_boundingSphere.y, m_boundingSphere.z ); - const float scale = std::max( std::max( Length( m_lastUpdateMatrix.GetX() ), Length( m_lastUpdateMatrix.GetY() ) ), Length( m_lastUpdateMatrix.GetZ() ) ); - const float radius = m_boundingSphere.w * scale; - obb.x = Vector3( 1.0f, 0.0f, 0.0f ); - obb.y = Vector3( 0.0f, 1.0f, 0.0f ); - obb.z = Vector3( 0.0f, 0.0f, 1.0f ); - obb.center = TransformCoord( sphereCenter, m_lastUpdateMatrix ); - obb.sizes = Vector3( radius, radius, radius ); + CcpMath::Sphere worldSphere( m_boundingSphere ); + worldSphere.Transform( m_lastUpdateMatrix ); + obb.CreateFromSphere( worldSphere ); return true; } diff --git a/trinity/Eve/EvePlanet.cpp b/trinity/Eve/EvePlanet.cpp index 843c5047f..548d56fdd 100644 --- a/trinity/Eve/EvePlanet.cpp +++ b/trinity/Eve/EvePlanet.cpp @@ -161,11 +161,8 @@ bool EvePlanet::GetWorldBoundingObb( Obb& obb ) const const float renderScale = std::max( m_renderScale, 1.0f ); const Matrix scaledTransform = CalculatePlanetScaleTransform( m_worldTransform, renderScale ); const float radius = m_radius / renderScale; - obb.x = Vector3( 1.0f, 0.0f, 0.0f ); - obb.y = Vector3( 0.0f, 1.0f, 0.0f ); - obb.z = Vector3( 0.0f, 0.0f, 1.0f ); - obb.center = scaledTransform.GetTranslation(); - obb.sizes = Vector3( radius, radius, radius ); + CcpMath::Sphere worldSphere( scaledTransform.GetTranslation(), radius ); + obb.CreateFromSphere( worldSphere ); return true; } diff --git a/trinity/Utilities/Obb.cpp b/trinity/Utilities/Obb.cpp index 6c779dfc5..1534b66fb 100644 --- a/trinity/Utilities/Obb.cpp +++ b/trinity/Utilities/Obb.cpp @@ -206,6 +206,16 @@ void Obb::CreateClippedWorldBoundingObb( const Vector3& localMin, const Vector3& } } +// A sphere's bounds do not depend on orientation, so the box is world-axis aligned. +void Obb::CreateFromSphere( const CcpMath::Sphere& worldSphere ) +{ + x = Vector3( 1.0f, 0.0f, 0.0f ); + y = Vector3( 0.0f, 1.0f, 0.0f ); + z = Vector3( 0.0f, 0.0f, 1.0f ); + center = worldSphere.center; + sizes = Vector3( worldSphere.radius, worldSphere.radius, worldSphere.radius ); +} + // ------------------------------------------------------------- // Description: // Return a corner point of the OBB. diff --git a/trinity/Utilities/Obb.h b/trinity/Utilities/Obb.h index 30a0455d8..87f59db0e 100644 --- a/trinity/Utilities/Obb.h +++ b/trinity/Utilities/Obb.h @@ -33,6 +33,7 @@ struct Obb void CreateWorldBoundingObb( const Vector3& localMin, const Vector3& localMax, const Matrix& localToWorld ); void CreateClippedWorldBoundingObb( const Vector3& localMin, const Vector3& localMax, const Matrix& localToWorld, const TriFrustum* frustum ); + void CreateFromSphere( const CcpMath::Sphere& worldSphere ); Vector3 GetPoint( unsigned N ) const; From fb06a81c72c5deb061938734c9ba242fd0788bf8 Mon Sep 17 00:00:00 2001 From: breki Date: Mon, 31 Aug 2026 11:53:35 +0000 Subject: [PATCH 5/6] dont change renderscale check in EvePlanet --- trinity/Eve/EvePlanet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trinity/Eve/EvePlanet.cpp b/trinity/Eve/EvePlanet.cpp index 548d56fdd..ea646bd8d 100644 --- a/trinity/Eve/EvePlanet.cpp +++ b/trinity/Eve/EvePlanet.cpp @@ -158,7 +158,7 @@ bool EvePlanet::GetWorldBoundingObb( Obb& obb ) const return false; } - const float renderScale = std::max( m_renderScale, 1.0f ); + const float renderScale = m_renderScale > 0.0f ? m_renderScale : 1.0f; const Matrix scaledTransform = CalculatePlanetScaleTransform( m_worldTransform, renderScale ); const float radius = m_radius / renderScale; CcpMath::Sphere worldSphere( scaledTransform.GetTranslation(), radius ); From e6a8c8824f3bf15dda017933fe8f779fe6b2f9bf Mon Sep 17 00:00:00 2001 From: Breki Ingibjargarson Date: Mon, 31 Aug 2026 14:07:37 +0000 Subject: [PATCH 6/6] auto clang formatting Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- trinity/Tr2ProjectBoundingBoxBracket.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/trinity/Tr2ProjectBoundingBoxBracket.cpp b/trinity/Tr2ProjectBoundingBoxBracket.cpp index aa58d4110..ba2daed64 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.cpp +++ b/trinity/Tr2ProjectBoundingBoxBracket.cpp @@ -21,9 +21,18 @@ const float CLIP_EPSILON = 1e-5f; // Obb::GetPoint corner index bits: 0x1 = -X, 0x2 = -Y, 0x4 = -Z; // each edge joins the two corners differing in exactly one axis sign. const int OBB_EDGES[12][2] = { - { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, - { 0, 2 }, { 1, 3 }, { 4, 6 }, { 5, 7 }, - { 0, 4 }, { 1, 5 }, { 2, 6 }, { 3, 7 }, + { 0, 1 }, + { 2, 3 }, + { 4, 5 }, + { 6, 7 }, + { 0, 2 }, + { 1, 3 }, + { 4, 6 }, + { 5, 7 }, + { 0, 4 }, + { 1, 5 }, + { 2, 6 }, + { 3, 7 }, }; // 8 corners plus at most one near-plane cut per edge