@@ -68,30 +68,113 @@ class ScenarioSimulationMotionSystem final : public engine::EngineSystem {
6868 advanceRoutesForWaypointProgress (query, 0.0 , entities, layoutCache);
6969 replanBlockedExitRoutes (query, entities, layoutCache, clock.elapsedSeconds , layoutRevision);
7070 replanBlockedRouteSegments (query, entities, layoutCache, clock.elapsedSeconds , layoutRevision);
71-
72- for (const auto entity : entities) {
73- auto & position = query.get <Position>(entity);
74- const auto & agent = query.get <Agent>(entity);
75- auto & velocity = query.get <Velocity>(entity);
76- auto & route = query.get <EvacuationRoute>(entity);
77- auto & status = query.get <EvacuationStatus>(entity);
71+
72+ if (!resources.contains <ScenarioTimingKeyframesResource>()) {
73+ resources.set (ScenarioTimingKeyframesResource{});
74+ }
75+ auto & timingKeyframes = resources.get <ScenarioTimingKeyframesResource>();
76+ const auto totalAgentCount = entities.size ();
77+ const auto t90TargetCount = static_cast <std::size_t >(std::ceil (static_cast <double >(totalAgentCount) * 0.90 ));
78+ const auto t95TargetCount = static_cast <std::size_t >(std::ceil (static_cast <double >(totalAgentCount) * 0.95 ));
79+
80+ std::size_t evacuatedAtStartCount = 0 ;
81+ std::size_t newlyEvacuatedCount = 0 ;
82+ for (const auto entity : entities) {
83+ auto & position = query.get <Position>(entity);
84+ auto & velocity = query.get <Velocity>(entity);
85+ auto & route = query.get <EvacuationRoute>(entity);
86+ auto & status = query.get <EvacuationStatus>(entity);
7887 if (status.evacuated ) {
88+ ++evacuatedAtStartCount;
89+ continue ;
90+ }
91+ if (route.destinationZoneId .empty ()) {
7992 continue ;
8093 }
8194
8295 const auto & floorLayout = cachedLayoutForFloor (layoutCache, route.currentFloorId );
8396 const auto * destinationZone = findZone (floorLayout, route.destinationZoneId );
8497 if (destinationZone != nullptr && pointInRing (destinationZone->area .outline , position.value )) {
8598 status.evacuated = true ;
86- status.completionTimeSeconds = clock.elapsedSeconds ;
87- velocity.value = {};
88- continue ;
89- }
90-
91- if (route.nextWaypointIndex >= route.waypoints .size ()) {
92- velocity.value = {};
93- continue ;
94- }
99+ status.completionTimeSeconds = clock.elapsedSeconds ;
100+ velocity.value = {};
101+ ++newlyEvacuatedCount;
102+ }
103+ }
104+
105+ const auto evacuatedAfterCount = evacuatedAtStartCount + newlyEvacuatedCount;
106+ const bool shouldCaptureT90 = t90TargetCount > 0
107+ && !timingKeyframes.t90Frame .has_value ()
108+ && evacuatedAtStartCount < t90TargetCount
109+ && evacuatedAfterCount >= t90TargetCount;
110+ const bool shouldCaptureT95 = t95TargetCount > 0
111+ && !timingKeyframes.t95Frame .has_value ()
112+ && evacuatedAtStartCount < t95TargetCount
113+ && evacuatedAfterCount >= t95TargetCount;
114+ if (shouldCaptureT90 || shouldCaptureT95) {
115+ SimulationFrame keyframe;
116+ keyframe.elapsedSeconds = clock.elapsedSeconds ;
117+ keyframe.totalAgentCount = totalAgentCount;
118+ keyframe.evacuatedAgentCount = evacuatedAfterCount;
119+ keyframe.complete = totalAgentCount > 0 && evacuatedAfterCount >= totalAgentCount;
120+
121+ const auto view = query.view <Position, Agent, Velocity, EvacuationStatus>();
122+ keyframe.agents .reserve (view.size ());
123+ for (const auto entity : view) {
124+ const auto & status = query.get <EvacuationStatus>(entity);
125+ if (status.evacuated ) {
126+ continue ;
127+ }
128+ const auto & position = query.get <Position>(entity);
129+ const auto & velocity = query.get <Velocity>(entity);
130+ const auto & agent = query.get <Agent>(entity);
131+ const auto * route = query.contains <EvacuationRoute>(entity) ? &query.get <EvacuationRoute>(entity) : nullptr ;
132+ keyframe.agents .push_back ({
133+ .id = entity.index ,
134+ .position = position.value ,
135+ .velocity = velocity.value ,
136+ .radius = agent.radius ,
137+ .floorId = route != nullptr
138+ ? (!route->displayFloorId .empty ()
139+ ? route->displayFloorId
140+ : route->currentFloorId )
141+ : std::string{},
142+ .stalled = route != nullptr
143+ && scenarioAgentStalled (simulation_internal::lengthOf (velocity.value ), route->stalledSeconds ),
144+ });
145+ }
146+
147+ if (shouldCaptureT90) {
148+ timingKeyframes.t90Frame = keyframe;
149+ }
150+ if (shouldCaptureT95) {
151+ timingKeyframes.t95Frame = keyframe;
152+ }
153+ if (resources.contains <ScenarioResultArtifactsResource>()) {
154+ auto & result = resources.get <ScenarioResultArtifactsResource>();
155+ if (shouldCaptureT90 && !result.artifacts .timingSummary .t90Frame .has_value ()) {
156+ result.artifacts .timingSummary .t90Frame = timingKeyframes.t90Frame ;
157+ }
158+ if (shouldCaptureT95 && !result.artifacts .timingSummary .t95Frame .has_value ()) {
159+ result.artifacts .timingSummary .t95Frame = timingKeyframes.t95Frame ;
160+ }
161+ }
162+ }
163+
164+ for (const auto entity : entities) {
165+ auto & position = query.get <Position>(entity);
166+ const auto & agent = query.get <Agent>(entity);
167+ auto & velocity = query.get <Velocity>(entity);
168+ auto & route = query.get <EvacuationRoute>(entity);
169+ auto & status = query.get <EvacuationStatus>(entity);
170+ if (status.evacuated ) {
171+ continue ;
172+ }
173+
174+ if (route.nextWaypointIndex >= route.waypoints .size ()) {
175+ velocity.value = {};
176+ continue ;
177+ }
95178
96179 const auto target = routeWaypointTarget (route, position.value );
97180 const auto distance = distanceBetween (position.value , target);
@@ -105,11 +188,12 @@ class ScenarioSimulationMotionSystem final : public engine::EngineSystem {
105188 velocity.value = {};
106189 continue ;
107190 }
108-
191+
192+ const auto & floorLayout = cachedLayoutForFloor (layoutCache, route.currentFloorId );
109193 const auto routeDirection = (target - position.value ) * (1.0 / distance);
110194 const auto maxSpeed = effectiveMaxSpeed (layoutCache, agent, route, position.value );
111195 const auto desiredVelocity = routeDirection * maxSpeed;
112- double speedScale = 1.0 ;
196+ double speedScale = 1.0 ;
113197 const auto neighborRadius = std::max (
114198 static_cast <double >(agent.radius ) + kDefaultAgentRadius + kPersonalSpaceBuffer ,
115199 kHeadOnLookAheadDistance );
0 commit comments