Skip to content

Commit ae08856

Browse files
Copilotkjy5
andauthored
Add unsaved ProbeWorldState slice for subscribable world-space probe positions (#840)
* Initial plan * Add ProbeWorldState slice for world-space probe positions Co-authored-by: kjy5 <[email protected]> * Add documentation and example for ProbeWorldState Co-authored-by: kjy5 <[email protected]> * Add implementation summary Co-authored-by: kjy5 <[email protected]> * Remove nuget.exe binary and update gitignore * Remove examples, meta files, and documentation per review feedback Co-authored-by: kjy5 <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: kjy5 <[email protected]>
1 parent 98d4677 commit ae08856

File tree

6 files changed

+273
-7
lines changed

6 files changed

+273
-7
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System.Linq;
2+
using Unity.AppUI.Redux;
3+
using UnityEngine;
4+
5+
namespace Models.Scene
6+
{
7+
public static class ProbeWorldReducers
8+
{
9+
/// <summary>
10+
/// Updates or adds a probe's world state
11+
/// </summary>
12+
public static ProbeWorldStateSlice UpdateProbeWorldStateReducer(
13+
ProbeWorldStateSlice state,
14+
IAction<ProbeWorldState> action
15+
)
16+
{
17+
var probeWorldStatesCopy = state.ProbeWorldStates.ToList();
18+
19+
// Find if this probe already exists in the list
20+
var index = probeWorldStatesCopy.FindIndex(p => p.Name == action.payload.Name);
21+
22+
if (index >= 0)
23+
{
24+
// Update existing probe world state
25+
probeWorldStatesCopy[index] = action.payload;
26+
}
27+
else
28+
{
29+
// Add new probe world state
30+
probeWorldStatesCopy.Add(action.payload);
31+
}
32+
33+
return state with { ProbeWorldStates = probeWorldStatesCopy };
34+
}
35+
36+
/// <summary>
37+
/// Removes a probe's world state when the probe is removed
38+
/// </summary>
39+
public static ProbeWorldStateSlice RemoveProbeWorldStateReducer(
40+
ProbeWorldStateSlice state,
41+
IAction<string> action
42+
)
43+
{
44+
var probeWorldStatesCopy = state.ProbeWorldStates.ToList();
45+
46+
// Remove the probe world state with the specified name
47+
probeWorldStatesCopy.RemoveAll(p => p.Name == action.payload);
48+
49+
return state with { ProbeWorldStates = probeWorldStatesCopy };
50+
}
51+
52+
/// <summary>
53+
/// Clears all probe world states
54+
/// </summary>
55+
public static ProbeWorldStateSlice ClearAllProbeWorldStatesReducer(
56+
ProbeWorldStateSlice state,
57+
IAction action
58+
)
59+
{
60+
return state with { ProbeWorldStates = new() };
61+
}
62+
}
63+
64+
public static class ProbeWorldActions
65+
{
66+
/// <summary>
67+
/// Action to update a probe's world state with computed values
68+
/// </summary>
69+
public static readonly ActionCreator<ProbeWorldState> UPDATE_PROBE_WORLD_STATE =
70+
$"{SliceNames.PROBE_WORLD_SLICE}/UpdateProbeWorldState";
71+
72+
/// <summary>
73+
/// Action to remove a probe's world state
74+
/// </summary>
75+
public static readonly ActionCreator<string> REMOVE_PROBE_WORLD_STATE =
76+
$"{SliceNames.PROBE_WORLD_SLICE}/RemoveProbeWorldState";
77+
78+
/// <summary>
79+
/// Action to clear all probe world states
80+
/// </summary>
81+
public static readonly ActionCreator CLEAR_ALL_PROBE_WORLD_STATES =
82+
$"{SliceNames.PROBE_WORLD_SLICE}/ClearAllProbeWorldStates";
83+
}
84+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System;
2+
using UnityEngine;
3+
4+
namespace Models.Scene
5+
{
6+
/// <summary>
7+
/// Represents the world-space computed position and orientation of a probe.
8+
/// This is an unsaved state that reflects computed values from ProbeManager and ProbeController
9+
/// after they update the CCF coordinates (APMLDV).
10+
/// </summary>
11+
[Serializable]
12+
public record ProbeWorldState
13+
{
14+
#region Core Identity
15+
16+
/// <summary>
17+
/// Name/identifier of the probe (matches ProbeState.Name)
18+
/// </summary>
19+
public string Name = string.Empty;
20+
21+
#endregion
22+
23+
#region World-Space Position and Orientation
24+
25+
/// <summary>
26+
/// World-space position of the probe tip in untransformed coordinates (WorldU)
27+
/// </summary>
28+
public Vector3 TipPositionWorldU;
29+
30+
/// <summary>
31+
/// World-space position of the probe tip in transformed coordinates (WorldT)
32+
/// </summary>
33+
public Vector3 TipPositionWorldT;
34+
35+
/// <summary>
36+
/// World-space right vector of the probe tip
37+
/// </summary>
38+
public Vector3 TipRightWorldU;
39+
40+
/// <summary>
41+
/// World-space up vector of the probe tip
42+
/// </summary>
43+
public Vector3 TipUpWorldU;
44+
45+
/// <summary>
46+
/// World-space forward vector of the probe tip
47+
/// </summary>
48+
public Vector3 TipForwardWorldU;
49+
50+
#endregion
51+
52+
#region Surface Coordinates
53+
54+
/// <summary>
55+
/// Brain surface coordinate in transformed space (WorldT)
56+
/// </summary>
57+
public Vector3 SurfaceCoordinateWorldT;
58+
59+
/// <summary>
60+
/// Brain surface coordinate in untransformed space (WorldU)
61+
/// </summary>
62+
public Vector3 SurfaceCoordinateWorldU;
63+
64+
/// <summary>
65+
/// Brain surface coordinate in atlas transformed space (CoordT)
66+
/// </summary>
67+
public Vector3 SurfaceCoordinateT;
68+
69+
/// <summary>
70+
/// Whether the probe is currently in the brain
71+
/// </summary>
72+
public bool IsProbeInBrain;
73+
74+
#endregion
75+
76+
#region Recording Region
77+
78+
/// <summary>
79+
/// Recording region base coordinate in world space (untransformed)
80+
/// </summary>
81+
public Vector3 RecRegionBaseCoordWorldU;
82+
83+
/// <summary>
84+
/// Recording region top coordinate in world space (untransformed)
85+
/// </summary>
86+
public Vector3 RecRegionTopCoordWorldU;
87+
88+
#endregion
89+
}
90+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace Models.Scene
6+
{
7+
/// <summary>
8+
/// Unsaved state slice containing world-space computed values for all probes.
9+
/// This state is updated after ProbeManager and ProbeController run their updates
10+
/// on CCF coordinates (APMLDV) and compute world-space positions and orientations.
11+
/// Other components can subscribe to this slice to react to computed value changes.
12+
/// </summary>
13+
[Serializable]
14+
public record ProbeWorldStateSlice
15+
{
16+
/// <summary>
17+
/// List of world-space states for all probes
18+
/// </summary>
19+
public List<ProbeWorldState> ProbeWorldStates = new();
20+
21+
/// <summary>
22+
/// Helper to get a specific probe's world state by name
23+
/// </summary>
24+
public ProbeWorldState GetProbeWorldState(string probeName)
25+
{
26+
return ProbeWorldStates.FirstOrDefault(state => state.Name == probeName);
27+
}
28+
}
29+
}

Assets/Scripts/Models/SliceNames.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ public static class SliceNames
77
public const string SETTINGS_SLICE = "settings";
88
public const string RIG_SLICE = "rig";
99
public const string ATLAS_SETTINGS_SLICE = "atlasSettings";
10+
public const string PROBE_WORLD_SLICE = "probeWorld";
1011
}
1112
}

Assets/Scripts/Pinpoint/Probes/ProbeManager.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,11 @@ private void OnDestroy()
367367

368368
// Unsubscribe from state.
369369
_probeStateSubscription?.Dispose();
370+
371+
#if APP_UI
372+
// Remove the probe world state when the probe is destroyed
373+
PinpointApp.StoreServiceStore.Dispatch(ProbeWorldActions.REMOVE_PROBE_WORLD_STATE, name);
374+
#endif
370375
}
371376

372377
#endregion
@@ -491,6 +496,42 @@ public void ProbeMoved()
491496
_recRegionTopCoordWorldU = BrainAtlasManager.WorldT2WorldU(endCoordWorldT, true);
492497
}
493498

499+
#if APP_UI
500+
/// <summary>
501+
/// Dispatches the computed world-space probe state to the Redux store.
502+
/// This is called after probe position/orientation updates are complete.
503+
/// </summary>
504+
private void DispatchProbeWorldState()
505+
{
506+
// Get tip world coordinates
507+
var (tipCoordWorldU, tipRightWorldU, tipUpWorldU, tipForwardWorldU) =
508+
_probeController.GetTipWorldU();
509+
510+
// Get tip position in WorldT
511+
Vector3 tipCoordWorldT = _probeController.ProbeTipT.position;
512+
513+
// Create the world state object
514+
var worldState = new ProbeWorldState
515+
{
516+
Name = name,
517+
TipPositionWorldU = tipCoordWorldU,
518+
TipPositionWorldT = tipCoordWorldT,
519+
TipRightWorldU = tipRightWorldU,
520+
TipUpWorldU = tipUpWorldU,
521+
TipForwardWorldU = tipForwardWorldU,
522+
SurfaceCoordinateWorldT = _brainSurfaceWorldT,
523+
SurfaceCoordinateWorldU = _brainSurfaceWorldU,
524+
SurfaceCoordinateT = _brainSurfaceCoordT,
525+
IsProbeInBrain = _probeInBrain,
526+
RecRegionBaseCoordWorldU = _recRegionBaseCoordWorldU,
527+
RecRegionTopCoordWorldU = _recRegionTopCoordWorldU
528+
};
529+
530+
// Dispatch to Redux store
531+
PinpointApp.StoreServiceStore.Dispatch(ProbeWorldActions.UPDATE_PROBE_WORLD_STATE, worldState);
532+
}
533+
#endif
534+
494535
#region Channel map
495536
public (
496537
float startPosmm,
@@ -1002,6 +1043,11 @@ public void UpdateSurfacePosition()
10021043
BrainAtlasManager.ActiveReferenceAtlas.World2Atlas(_brainSurfaceWorldU, true)
10031044
);
10041045
}
1046+
1047+
#if APP_UI
1048+
// Dispatch world state update after surface position is calculated
1049+
DispatchProbeWorldState();
1050+
#endif
10051051
}
10061052

10071053
// TODO: Remove useDV and always use depth.

Assets/Scripts/Services/StoreService.cs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public StoreService(LocalStorageService localStorageService)
4949
new AtlasSettingsState()
5050
);
5151

52+
// Initialize the probe world state (unsaved, transient state for computed values)
53+
var initialProbeWorldState = new ProbeWorldStateSlice();
54+
5255
// Initialize the Redux store.
5356
var mainSlice = StoreFactory.CreateSlice(
5457
SliceNames.MAIN_SLICE,
@@ -295,16 +298,29 @@ public StoreService(LocalStorageService localStorageService)
295298
);
296299
}
297300
);
298-
Store = StoreFactory.CreateStore(
299-
new ISlice<PartitionedState>[]
301+
var probeWorldSlice = StoreFactory.CreateSlice(
302+
SliceNames.PROBE_WORLD_SLICE,
303+
initialProbeWorldState,
304+
builder =>
300305
{
301-
mainSlice,
302-
sceneSlice,
303-
settingsSlice,
304-
rigSlice,
305-
atlasSettingsSlice,
306+
builder
307+
.AddCase(
308+
ProbeWorldActions.UPDATE_PROBE_WORLD_STATE,
309+
ProbeWorldReducers.UpdateProbeWorldStateReducer
310+
)
311+
.AddCase(
312+
ProbeWorldActions.REMOVE_PROBE_WORLD_STATE,
313+
ProbeWorldReducers.RemoveProbeWorldStateReducer
314+
)
315+
.AddCase(
316+
ProbeWorldActions.CLEAR_ALL_PROBE_WORLD_STATES,
317+
ProbeWorldReducers.ClearAllProbeWorldStatesReducer
318+
);
306319
}
307320
);
321+
Store = StoreFactory.CreateStore(
322+
new ISlice<PartitionedState>[] { mainSlice, sceneSlice, settingsSlice, rigSlice, atlasSettingsSlice, probeWorldSlice }
323+
);
308324
}
309325

310326
/// <summary>

0 commit comments

Comments
 (0)