diff --git a/trinity/Eve/EveEffectRoot2.cpp b/trinity/Eve/EveEffectRoot2.cpp index 1d02b8dc0..4e9122b36 100644 --- a/trinity/Eve/EveEffectRoot2.cpp +++ b/trinity/Eve/EveEffectRoot2.cpp @@ -4,6 +4,7 @@ #include "EveEffectRoot2.h" #include "Utilities/BoundingSphere.h" +#include "Utilities/BoundingBox.h" #include "TriFrustum.h" #include "Lights/Tr2PointLight.h" #include "Tr2LightManager.h" @@ -13,8 +14,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 ), @@ -398,8 +451,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 @@ -408,6 +466,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 ) @@ -994,4 +1096,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 97c0a5d76..d2bb8abfd 100644 --- a/trinity/Eve/EveEffectRoot2.h +++ b/trinity/Eve/EveEffectRoot2.h @@ -19,6 +19,7 @@ #include "ITr2SoundEmitterOwner.h" #include "Controllers/ITr2ControllerOwner.h" #include "Lights/ITr2LightOwner.h" +#include "ITr2BoundingBox.h" #include "EveEntity.h" #include @@ -49,6 +50,7 @@ BLUE_CLASS( EveEffectRoot2 ): public ITr2SoundEmitterOwner, public ITr2ControllerOwner, public ITr2LightOwner, + public ITr2BoundingBox, public EveEntity { @@ -88,6 +90,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; @@ -227,4 +234,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 31605e349..c71bfa9c2 100644 --- a/trinity/Eve/EveEffectRoot2_Blue.cpp +++ b/trinity/Eve/EveEffectRoot2_Blue.cpp @@ -19,6 +19,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 63f6be926..2e149d8f0 100644 --- a/trinity/Eve/EvePlanet.cpp +++ b/trinity/Eve/EvePlanet.cpp @@ -7,6 +7,7 @@ #include "TriDevice.h" #include "EveUpdateContext.h" #include "Curves/TriCurveSet.h" +#include "Utilities/BoundingBox.h" const float EvePlanet::SCALE = 1000000.0f; @@ -150,6 +151,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 @@ -387,4 +410,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 57856d9a9..b6d806979 100644 --- a/trinity/Eve/EvePlanet.h +++ b/trinity/Eve/EvePlanet.h @@ -59,6 +59,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 80194272d..2f3a72165 100644 --- a/trinity/Eve/EvePlanet_Blue.cpp +++ b/trinity/Eve/EvePlanet_Blue.cpp @@ -17,6 +17,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 8cdd4276a..be7092414 100644 --- a/trinity/Eve/EveRootTransform_Blue.cpp +++ b/trinity/Eve/EveRootTransform_Blue.cpp @@ -13,6 +13,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 cf28d005d..de7350bda 100644 --- a/trinity/Eve/EveTransform.cpp +++ b/trinity/Eve/EveTransform.cpp @@ -17,6 +17,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 ), @@ -351,6 +398,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; @@ -506,4 +658,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 b4928eabe..ea4e933dc 100644 --- a/trinity/Eve/EveTransform.h +++ b/trinity/Eve/EveTransform.h @@ -16,6 +16,7 @@ #include "IEveTransform.h" #include "IWorldPosition.h" +#include "ITr2BoundingBox.h" BLUE_DECLARE( Tr2ParticleSystem ); BLUE_DECLARE_VECTOR( Tr2ParticleSystem ); @@ -37,6 +38,7 @@ BLUE_CLASS( EveTransform ): public IWorldPosition, public IInitialize, public ITr2CurveSetOwner, + public ITr2BoundingBox, public ITr2DebugRenderable { public: @@ -59,8 +61,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 d7ec1f75d..311f42e40 100644 --- a/trinity/Eve/EveTransform_Blue.cpp +++ b/trinity/Eve/EveTransform_Blue.cpp @@ -16,6 +16,7 @@ const Be::ClassInfo* EveTransform::ExposeToBlue() MAP_INTERFACE( ITr2Pickable ) MAP_INTERFACE( IWorldPosition ) MAP_INTERFACE( IInitialize ) + MAP_INTERFACE( ITr2BoundingBox ) MAP_ATTRIBUTE ( diff --git a/trinity/Interior/Tr2InteriorPlaceable.cpp b/trinity/Interior/Tr2InteriorPlaceable.cpp index 473115c37..dbeed5c1d 100644 --- a/trinity/Interior/Tr2InteriorPlaceable.cpp +++ b/trinity/Interior/Tr2InteriorPlaceable.cpp @@ -137,7 +137,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 ) @@ -648,4 +648,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 aaba9777f..d13234d90 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.cpp +++ b/trinity/Tr2ProjectBoundingBoxBracket.cpp @@ -2,6 +2,8 @@ #include "StdAfx.h" +#include + #include "Tr2ProjectBoundingBoxBracket.h" #include "include/ITr2BoundingBox.h" #include "Tr2Renderer.h" @@ -12,6 +14,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 ), @@ -25,7 +270,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 ) { } @@ -34,130 +283,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; @@ -169,62 +368,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 ) { @@ -232,13 +383,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 ); } } @@ -250,7 +394,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 ); @@ -258,4 +411,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 17d14f428..abd37f5af 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket.h +++ b/trinity/Tr2ProjectBoundingBoxBracket.h @@ -24,6 +24,7 @@ class Tr2ProjectBoundingBoxBracket : void UpdateValue( double time ); void SetEmptyProjection(); + void UpdateBracket(); protected: std::wstring m_name; @@ -55,6 +56,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 2a544c15c..71ecce120 100644 --- a/trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp +++ b/trinity/Tr2ProjectBoundingBoxBracket_Blue.cpp @@ -9,7 +9,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 ) @@ -133,8 +133,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 )