-
Notifications
You must be signed in to change notification settings - Fork 72
EveTriggerVolume implementation #69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f5e9b36
aa133e6
6940c42
1c8b356
064ac92
e29e84d
2c5450c
7d9718a
feffadd
df7dad7
c6768cd
8bfa77a
3611e35
041aeb9
161d4ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,292 @@ | ||
| // Copyright © 2026 CCP ehf. | ||
|
|
||
| #include "StdAfx.h" | ||
| #include "EveTriggerVolume.h" | ||
| #include "TriDevice.h" | ||
|
|
||
| namespace | ||
| { | ||
| void InvokeTriggerCallback( void* context, bool entered ) | ||
| { | ||
| EveTriggerVolume* triggerVolume = reinterpret_cast<EveTriggerVolume*>( context ); | ||
|
|
||
| triggerVolume->InvokeCallback( entered ); | ||
| triggerVolume->GetRawRoot()->Unlock(); | ||
| } | ||
|
|
||
| void TriggerEnterCallback( void* context ) | ||
| { | ||
| InvokeTriggerCallback( context, true ); | ||
| } | ||
|
|
||
| void TriggerExitCallback( void* context ) | ||
| { | ||
| InvokeTriggerCallback( context, false ); | ||
| } | ||
| } | ||
|
|
||
| EveTriggerVolume::EveTriggerVolume( IRoot* lockobj ) : | ||
| PARENTLOCK( m_volumes ), | ||
| PARENTLOCK( m_exclusionVolumes ), | ||
| PARENTLOCK( m_externalParameters ), | ||
| m_rotation( 0.0f, 0.0f, 0.0f, 1.0f ), | ||
| m_translation( 0.0f, 0.0f, 0.0f ), | ||
| m_worldTransform( IdentityMatrix() ), | ||
| m_enterThreshold( 0.5f ), | ||
| m_isInside( false ), | ||
| m_currentIntensity( 0.0f ) | ||
| { | ||
| } | ||
|
|
||
| EveTriggerVolume::~EveTriggerVolume() | ||
| { | ||
| } | ||
|
|
||
| void EveTriggerVolume::RebuildBoundingSphere() | ||
| { | ||
| CCP_STATS_ZONE( __FUNCTION__ ); | ||
|
|
||
| m_boundingSphere = CcpMath::Sphere(); | ||
|
|
||
| for( const auto& volume : m_volumes ) | ||
| { | ||
| auto volumeSphere = volume->GetBoundingSphere(); | ||
|
|
||
| if( !volumeSphere.IsInitialized() ) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| if( !m_boundingSphere.IsInitialized() || volumeSphere.IsSphereInside( m_boundingSphere ) ) | ||
| { | ||
| m_boundingSphere = volumeSphere; | ||
| continue; | ||
| } | ||
|
|
||
| if( m_boundingSphere.IsSphereInside( volumeSphere ) ) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| Vector3 delta = volumeSphere.center - m_boundingSphere.center; | ||
| float deltaLen = Length( delta ); | ||
|
|
||
| m_boundingSphere.center += 0.5f * ( 1.f + ( volumeSphere.radius - m_boundingSphere.radius ) / deltaLen ) * delta; | ||
| m_boundingSphere.radius = 0.5f * ( m_boundingSphere.radius + volumeSphere.radius + deltaLen ); | ||
| } | ||
| } | ||
|
|
||
| void EveTriggerVolume::SetCallback( const BlueScriptCallback& callback ) | ||
| { | ||
| m_callback = callback; | ||
| } | ||
|
|
||
| void EveTriggerVolume::InvokeCallback( bool entered ) | ||
| { | ||
| BlueScriptCallback callback = m_callback; | ||
| if( !callback ) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| callback.CallVoid( m_name.c_str(), entered ).ReportException(); | ||
| } | ||
|
|
||
| void EveTriggerVolume::QueueCallback( bool entered ) | ||
| { | ||
| if( !m_callback ) | ||
| { | ||
| return; | ||
| } | ||
| // Keeps the object alive until the queued callback runs. | ||
| GetRawRoot()->Lock(); | ||
|
|
||
| if( entered ) | ||
| { | ||
| gTriDev->AddPostUpdateCallback( TriggerEnterCallback, reinterpret_cast<void*>( this ) ); | ||
| } | ||
| else | ||
| { | ||
| gTriDev->AddPostUpdateCallback( TriggerExitCallback, reinterpret_cast<void*>( this ) ); | ||
| } | ||
| } | ||
|
|
||
| void EveTriggerVolume::UpdateWorldTransform( Be::Time time ) | ||
| { | ||
| Vector3 translation; | ||
|
|
||
| if( m_ballPosition ) | ||
| { | ||
| m_ballPosition->Update( &translation, time ); | ||
| } | ||
| else | ||
| { | ||
| translation = m_translation; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't it awkward that |
||
| } | ||
|
|
||
| m_worldTransform = RotationMatrix( m_rotation ) * TranslationMatrix( translation ); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are you not taking ball's rotation into account? |
||
| } | ||
|
|
||
| // IEveSpaceObject2 | ||
| void EveTriggerVolume::UpdateSyncronous( const EveUpdateContext& updateContext ) | ||
| { | ||
| CCP_STATS_ZONE( __FUNCTION__ ); | ||
|
|
||
| UpdateWorldTransform( updateContext.GetTime() ); | ||
|
|
||
| RebuildBoundingSphere(); | ||
|
|
||
| UpdateTriggerState( updateContext ); | ||
| } | ||
|
|
||
| float EveTriggerVolume::GetMaxIntensity( const PIEveVolumeVector& volumes, const Vector3& position ) | ||
| { | ||
| float intensity = 0.0f; | ||
| for( const auto& volume : volumes ) | ||
| { | ||
| intensity = std::max( intensity, volume->GetIntensity( position ) ); | ||
| if( intensity == 1.0f ) | ||
| { | ||
| // early exit | ||
| break; | ||
| } | ||
| } | ||
| return intensity; | ||
| } | ||
|
|
||
| void EveTriggerVolume::UpdateTriggerState( const EveUpdateContext& updateContext ) | ||
| { | ||
| m_currentIntensity = 0.0f; | ||
|
|
||
| bool inside = false; | ||
| if( m_trackedPosition && !m_volumes.empty() ) | ||
| { | ||
| Vector3 trackedPosition; | ||
| m_trackedPosition->Update( &trackedPosition, updateContext.GetTime() ); | ||
|
|
||
| Matrix inverseWorldTransform = Inverse( m_worldTransform ); | ||
| Vector3 positionInObjectSpace = Transform( trackedPosition, inverseWorldTransform ).GetXYZ(); | ||
|
|
||
| // check first if the tracked position is within the bounding sphere | ||
| if( m_boundingSphere.IsPointInside( positionInObjectSpace ) ) | ||
| { | ||
| m_currentIntensity = GetMaxIntensity( m_volumes, positionInObjectSpace ); | ||
|
|
||
| if( m_currentIntensity != 0.0f ) | ||
| { | ||
| // check if the tracked position is within an exclusion volume | ||
| float negativeIntensity = GetMaxIntensity( m_exclusionVolumes, positionInObjectSpace ); | ||
| m_currentIntensity = std::max( 0.0f, m_currentIntensity - negativeIntensity ); | ||
| } | ||
| } | ||
|
|
||
| inside = m_currentIntensity >= m_enterThreshold; | ||
| } | ||
|
|
||
| if( inside != m_isInside ) | ||
| { | ||
| m_isInside = inside; | ||
| QueueCallback( inside ); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As we discussed before, I don't see the reason to queue these callbacks:
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I completely forgot to address this one after our discussion. |
||
| } | ||
| } | ||
|
|
||
| void EveTriggerVolume::UpdateAsyncronous( const EveUpdateContext& updateContext ) | ||
| { | ||
| } | ||
|
|
||
| void EveTriggerVolume::UpdateVisibility( const EveUpdateContext& updateContext, const Matrix& parentTransform ) | ||
| { | ||
| } | ||
|
|
||
| void EveTriggerVolume::GetRenderables( std::vector<ITr2Renderable*>& renderables, Tr2ImpostorManager* impostors ) | ||
| { | ||
| } | ||
|
|
||
| bool EveTriggerVolume::GetBoundingSphere( Vector4& sphere, BoundingSphereQuery query ) const | ||
| { | ||
| Vector3 worldCenter = Transform( m_boundingSphere.center, m_worldTransform ).GetXYZ(); | ||
| sphere = Vector4( worldCenter.x, worldCenter.y, worldCenter.z, std::max( m_boundingSphere.radius, 1.0f ) ); | ||
| return true; | ||
| } | ||
|
|
||
| void EveTriggerVolume::UpdateModelCenterWorldPosition( Vector3& position, Be::Time t ) | ||
| { | ||
| UpdateWorldTransform( t ); | ||
| GetModelCenterWorldPosition( position ); | ||
| } | ||
|
|
||
| void EveTriggerVolume::GetModelCenterWorldPosition( Vector3& position ) const | ||
| { | ||
| position = Transform( m_boundingSphere.center, m_worldTransform ).GetXYZ(); | ||
| } | ||
|
|
||
| bool EveTriggerVolume::GetLocalBoundingBox( Vector3& min, Vector3& max ) | ||
| { | ||
| // Fall back to a unit box when no volumes are set up yet, so the object stays pickable in Graphite. | ||
| float radius = std::max( m_boundingSphere.radius, 1.0f ); | ||
| Vector3 extent( radius, radius, radius ); | ||
|
|
||
| min = m_boundingSphere.center - extent; | ||
| max = m_boundingSphere.center + extent; | ||
| return true; | ||
| } | ||
|
|
||
| void EveTriggerVolume::GetLocalToWorldTransform( Matrix& transform ) const | ||
| { | ||
| transform = m_worldTransform; | ||
| } | ||
|
|
||
| Vector3 EveTriggerVolume::GetWorldPosition() | ||
| { | ||
| return m_worldTransform.GetTranslation(); | ||
| } | ||
|
|
||
| Quaternion EveTriggerVolume::GetWorldRotation() | ||
| { | ||
| return Normalize( RotationQuaternion( m_worldTransform ) ); | ||
| } | ||
|
|
||
| bool EveTriggerVolume::Initialize() | ||
| { | ||
| UpdateWorldTransform( Be::Time( 0.0 ) ); | ||
| RebuildBoundingSphere(); | ||
| return true; | ||
| } | ||
|
|
||
| void EveTriggerVolume::GetDebugOptions( Tr2DebugRendererOptions& options ) | ||
| { | ||
| options.insert( "Trigger Volumes" ); | ||
| options.insert( "Trigger Exclusion Volumes" ); | ||
| options.insert( "Trigger Bounding Sphere" ); | ||
| } | ||
|
|
||
| void EveTriggerVolume::RenderDebugInfo( ITr2DebugRenderer2& renderer ) | ||
| { | ||
| if( renderer.HasOption( GetRawRoot(), "Trigger Volumes" ) ) | ||
| { | ||
| // green when the tracked position is inside, white otherwise | ||
| Color color = 0xFFFFFFFF; | ||
| if( m_isInside ) | ||
| { | ||
| color = 0xFF33FF33; | ||
| } | ||
|
|
||
| for( const auto& volume : m_volumes ) | ||
| { | ||
| volume->RenderDebugInfo( renderer, m_worldTransform, color ); | ||
| } | ||
| } | ||
|
|
||
| if( renderer.HasOption( GetRawRoot(), "Trigger Exclusion Volumes" ) ) | ||
| { | ||
| for( const auto& volume : m_exclusionVolumes ) | ||
| { | ||
| volume->RenderDebugInfo( renderer, m_worldTransform, 0xFFFF3333 ); | ||
| } | ||
| } | ||
|
|
||
| if( renderer.HasOption( GetRawRoot(), "Trigger Bounding Sphere" ) ) | ||
| { | ||
| renderer.DrawSphere( this, TranslationMatrix( m_boundingSphere.center ) * m_worldTransform, m_boundingSphere.radius, 10, Tr2DebugRenderer::Wireframe, 0xff333333 ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is this related to the PR? I vaguely remember this change as fixing a memory leak. Should it be in a separate PR?