Skip to content

EveTriggerVolume implementation - #69

Open
phevosccp wants to merge 19 commits into
mainfrom
trigger-volume-clean
Open

EveTriggerVolume implementation#69
phevosccp wants to merge 19 commits into
mainfrom
trigger-volume-clean

Conversation

@phevosccp

Copy link
Copy Markdown

Description

EVETriggerVolume simply triggers a Python callback when a tracked position crosses into or out of a volume region.
When authoring a trigger volume in Graphite you will see more options (e.g trigger a Node graph) but these don't directly affect trinity.

  • Callbacks go through gTriDev->AddPostUpdateCallback with the object Lock()ed, so
    Python is only touched at post-update on the main thread , similar to what
    TriEventCurve does.
  • Uses EveBoxVolume / EveSphereVolume / EveEllipsoidVolume through the IEveVolume
    interface.
  • Implements IEveSpaceObject2, IWorldPosition, IInitialize and ITr2DebugRenderable.

Testing

Tested for Frontier, the volume authoring code changes will be a separate PR for platformtools together with monolith changes.

In the demo volumes will trigger 3 custom callbacks:

  1. Wwise RTPC sets master volume to 0
  2. Camera shake
  3. Scene colour wash
demo.mp4

phevosccp and others added 15 commits August 21, 2026 14:08
Stop unregistered the updateable but kept the action's reference to the
emitter, so the emitter outlived the action that was driving it.
Volumes gain a persisted enabled attribute, so a placement can switch
between trigger shapes without editing the shape list, and a name
accessor so per-placement names can be read back off a volume.
Implemented for the box, sphere and ellipsoid shapes.

Only EveTriggerVolume honours the flag so far: fog volumes, post process
volumes, lighting overrides, procedural containers and distribution
placement generators still evaluate every volume they are given.
A top-level scene object whose trigger region is a list of IEveVolume
shapes placed relative to its own translation and rotation, editable in
Graphite like any other space object. Each synchronous update it
evaluates the tracked position against the enabled volumes and keeps the
inside/outside state, exposed read-only as isInside and intensity.

A local bounding sphere over the enabled volumes acts as the broad phase
for the point test and provides the bounds for picking, falling back to a
unit radius so an object with no volumes set up yet stays selectable.
SetCallback stores a Python callable that is invoked as
callback( name, entered ) whenever the tracked position crosses the
threshold. Transitions are detected during update, so the call is
deferred to the post-update point on the main thread; the object is
locked while the callback is queued and unlocked once it has run, so it
cannot be destroyed in between.
The highest intensity among the exclusion volumes is subtracted from the
trigger intensity and clamped at zero, so a region can have holes in it
without modelling the remainder as separate shapes.
Draws the trigger volumes - green while the tracked position is inside,
white otherwise - the exclusion volumes in red, and the broad-phase
bounding sphere, each under its own debug option. forceTriggered pins
the trigger into the entered state so a setup can be exercised in
Graphite with no ball attached.
External parameters expose per-placement values for dungeon asset
manipulations, and the translation/rotation curve slots let the client
attach the object's own destiny ball instead of driving the object from
the static translation and rotation attributes.

The callback name now prefers the first enabled volume's name: external
parameters in a .red file cannot reference the root object, so
per-placement names are bound to a volume, and the client overwrites the
root name attribute with the destiny ball ID when adding the object to
the scene.
- documentation fixes
void Tr2ActionBindRTPC::Stop( ITr2ActionController& controller )
{
controller.UnRegisterUpdateable( *this );
m_emitter = nullptr;

Copy link
Copy Markdown

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?

Comment thread trinity/Eve/EveTriggerVolume.cpp Outdated
translation = m_translation;
}

m_worldTransform = RotationMatrix( m_rotation ) * TranslationMatrix( translation );

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you not taking ball's rotation into account?

Comment thread trinity/Eve/EveTriggerVolume.cpp Outdated
}
else
{
translation = m_translation;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it awkward that m_translation is only used until the volume is attached to the ball?

Comment thread trinity/Eve/EveTriggerVolume.cpp Outdated
if( inside != m_isInside )
{
m_isInside = inside;
QueueCallback( inside );

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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: UpdateTriggerState is only called from UpdateSyncronous, always on the main thread. It should be safe to call Python directly in this context, or am I missing something?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely forgot to address this one after our discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants