@inventure71/paddockjs 2.0.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +176 -1
- package/INSTALL_AND_UPDATE.md +24 -4
- package/README.md +84 -512
- package/docs/architecture.md +38 -24
- package/docs/component_inventory.md +72 -0
- package/docs/composable-layouts.md +77 -0
- package/docs/concepts.md +2 -2
- package/docs/custom_model_controller.md +6 -6
- package/docs/data-helpers.md +50 -0
- package/docs/data_contract.md +104 -27
- package/docs/getting-started.md +75 -0
- package/docs/headless-environment.md +68 -0
- package/docs/index.md +18 -4
- package/docs/learnings.md +6 -6
- package/docs/rules.md +12 -8
- package/docs/sense_contract.md +1 -1
- package/docs/system_specs.md +71 -21
- package/docs/theming.md +64 -0
- package/docs/training.md +26 -19
- package/docs/troubleshooting.md +76 -0
- package/docs/upgrading-hosts-to-3.md +42 -0
- package/examples/python/README.md +6 -0
- package/examples/python/base_policy_server.py +19 -0
- package/package.json +10 -3
- package/src/api/PaddockSimulatorController.js +150 -24
- package/src/api/runtimeTheme.js +59 -0
- package/src/app/F1SimulatorApp.js +213 -29
- package/src/app/banners/raceDataBannerController.js +71 -36
- package/src/app/banners/stewardMessageController.js +5 -4
- package/src/app/camera/cameraConstants.js +2 -0
- package/src/app/camera/cameraController.js +86 -29
- package/src/app/camera/cameraGeometry.js +12 -1
- package/src/app/domBindings.js +89 -9
- package/src/app/layoutScheduler.js +27 -0
- package/src/app/layoutSupport.js +165 -0
- package/src/app/raceOverlayClearanceSupport.js +158 -0
- package/src/app/readouts/carOverviewRenderer.js +16 -8
- package/src/app/readouts/controlStateRenderer.js +1 -1
- package/src/app/readouts/raceStatusRenderer.js +30 -21
- package/src/app/readouts/readoutFormatters.js +2 -0
- package/src/app/readouts/telemetryRenderer.js +17 -8
- package/src/app/readouts/timingTowerRenderer.js +262 -49
- package/src/app/runtime/frameLoop.js +5 -1
- package/src/app/runtime/runtimeVisibility.js +66 -5
- package/src/config/cssValues.js +16 -0
- package/src/config/defaultOptions.js +58 -38
- package/src/config/themeOptions.js +830 -0
- package/src/config/timingGapMode.js +13 -0
- package/src/config/urlOptions.js +25 -0
- package/src/data/championship.js +14 -9
- package/src/data/index.d.ts +212 -0
- package/src/data/index.js +14 -0
- package/src/environment/controllerLoop.js +17 -3
- package/src/environment/episode.js +11 -1
- package/src/environment/index.d.ts +1 -2
- package/src/environment/metrics.js +59 -16
- package/src/environment/observationVector.js +50 -33
- package/src/environment/observations.js +89 -20
- package/src/environment/options.js +1 -1
- package/src/environment/runtime.js +44 -11
- package/src/environment/scenarios.js +1 -1
- package/src/environment/sensors/carRays.js +48 -37
- package/src/environment/sensors/index.js +501 -97
- package/src/environment/sensors/indexedRayBands.js +134 -46
- package/src/environment/sensors/rayBandTrace.js +1171 -0
- package/src/environment/sensors/rayChannels.js +108 -0
- package/src/environment/sensors/rayConfig.js +71 -25
- package/src/environment/sensors/rayGeometry.js +23 -17
- package/src/environment/sensors/sensorTargets.js +67 -1
- package/src/environment/sensors/surfaceRays.js +167 -118
- package/src/environment/sensors/trackRays.js +157 -48
- package/src/index.d.ts +153 -9
- package/src/index.js +52 -3
- package/src/simulation/collisionGeometry.js +260 -96
- package/src/simulation/driver/driverController.js +1 -1
- package/src/simulation/driver/edgeRecovery.js +1 -1
- package/src/simulation/driver/racingControls.js +2 -2
- package/src/simulation/driver/rejoinControls.js +2 -2
- package/src/simulation/pit/pitFlow.js +21 -9
- package/src/simulation/pit/pitKinematics.js +2 -2
- package/src/simulation/pit/pitQueue.js +5 -3
- package/src/simulation/pit/pitRouteMovement.js +9 -4
- package/src/simulation/pit/pitRouting.js +79 -46
- package/src/simulation/pit/pitState.js +1 -0
- package/src/simulation/pit/pitTireService.js +7 -3
- package/src/simulation/race/raceFinish.js +25 -8
- package/src/simulation/race/raceOrder.js +189 -32
- package/src/simulation/race/raceProgress.js +129 -37
- package/src/simulation/race/raceSetup.js +4 -6
- package/src/simulation/race/raceStep.js +26 -14
- package/src/simulation/race/safetyCar.js +1 -1
- package/src/simulation/race/stalledDnf.js +1 -1
- package/src/simulation/raceSimulation.js +20 -17
- package/src/simulation/rules/collisionSteward.js +40 -23
- package/src/simulation/rules/rulesReview.js +77 -56
- package/src/simulation/snapshots/raceSnapshots.js +37 -0
- package/src/simulation/timing/drsTiming.js +143 -30
- package/src/simulation/timing/gapEstimation.js +25 -3
- package/src/simulation/timing/lapTelemetry.js +138 -40
- package/src/simulation/timing/raceTiming.js +5 -1
- package/src/simulation/timing/sectorPerformance.js +104 -26
- package/src/simulation/timing/timingHistory.js +229 -14
- package/src/simulation/timing/timingLines.js +65 -7
- package/src/simulation/track/pitLaneLayout.js +30 -3
- package/src/simulation/track/pitLaneState.js +218 -19
- package/src/simulation/track/spatialQueries.js +66 -33
- package/src/simulation/track/trackMath.js +91 -24
- package/src/simulation/track/trackModel.js +2 -0
- package/src/simulation/track/trackQueryIndex.js +1468 -189
- package/src/simulation/track/trackQueryPitIndex.js +138 -14
- package/src/simulation/track/trackQueryScratch.js +7 -0
- package/src/simulation/track/trackQueryStats.js +25 -0
- package/src/simulation/track/trackSnapshotJson.js +139 -0
- package/src/simulation/track/trackStatePolicy.js +249 -2
- package/src/simulation/vehicle/contactResolution.js +99 -62
- package/src/simulation/vehicle/mainTrackWheelSurface.js +73 -34
- package/src/simulation/vehicle/pitWheelSurface.js +81 -28
- package/src/simulation/vehicle/runoffResponse.js +54 -29
- package/src/simulation/vehicle/vehicleGeometry.js +245 -51
- package/src/simulation/vehicle/vehicleKinematics.js +2 -2
- package/src/simulation/vehicle/vehiclePhysics.js +45 -30
- package/src/simulation/vehicle/vehicleSnapshots.js +35 -1
- package/src/simulation/vehicle/vehicleState.js +7 -1
- package/src/simulation/vehicle/wheelSurface.js +807 -92
- package/src/simulation/warmup/runtimeWarmup.js +0 -1
- package/src/styles.css +1568 -519
- package/src/ui/bannerTemplates.js +38 -28
- package/src/ui/cameraControlsTemplate.js +16 -5
- package/src/ui/carOverviewTemplate.js +12 -5
- package/src/ui/raceCanvasTemplate.js +31 -8
- package/src/ui/raceControlsTemplate.js +27 -10
- package/src/ui/shellTemplate.js +3 -1
- package/src/ui/telemetryDrawerTemplate.js +23 -10
- package/src/ui/telemetryTemplates.js +58 -32
- package/src/ui/templateUtils.js +48 -0
- package/src/ui/timingTowerTemplate.js +29 -12
|
@@ -6,18 +6,37 @@ import { buildBodySenses } from './sensors/bodySenses.js';
|
|
|
6
6
|
import { buildBoundarySenses } from './sensors/boundarySenses.js';
|
|
7
7
|
import { buildContactPatchSenses } from './sensors/contactSenses.js';
|
|
8
8
|
import { enrichOpponentRadar } from './sensors/opponentRadar.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
buildNearbyCars,
|
|
11
|
+
appendRaySensorVectorValues,
|
|
12
|
+
buildRaySensors,
|
|
13
|
+
createRayBatchContext,
|
|
14
|
+
normalizeRayOptions,
|
|
15
|
+
} from './sensors.js';
|
|
10
16
|
import { buildObservationVector } from './observationVector.js';
|
|
11
17
|
|
|
12
18
|
const EMPTY_EVENTS = Object.freeze([]);
|
|
13
19
|
|
|
14
|
-
export function buildEnvironmentObservation({
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
export function buildEnvironmentObservation({
|
|
21
|
+
snapshot,
|
|
22
|
+
options,
|
|
23
|
+
events = [],
|
|
24
|
+
controlledDrivers = options.controlledDrivers,
|
|
25
|
+
scratch = null,
|
|
26
|
+
}) {
|
|
27
|
+
const carsById = carsByIdForObservation(snapshot, scratch);
|
|
28
|
+
const eventsByDriver = groupEventsByDriver(events, controlledDrivers, scratch);
|
|
17
29
|
const defaultSensors = defaultSensorOptions(options);
|
|
18
30
|
const hasSensorOverrides = Object.keys(options.sensorsByDriver ?? {}).length > 0;
|
|
19
31
|
let rayBatchContext = null;
|
|
20
|
-
|
|
32
|
+
let rayScratchBatchContext = null;
|
|
33
|
+
const getRayBatchContext = ({ scratch: useScratch = false } = {}) => {
|
|
34
|
+
if (useScratch) {
|
|
35
|
+
rayScratchBatchContext ??= createRayBatchContext(snapshot, {
|
|
36
|
+
scratch: rayBatchContextScratchForObservation(scratch),
|
|
37
|
+
});
|
|
38
|
+
return rayScratchBatchContext;
|
|
39
|
+
}
|
|
21
40
|
rayBatchContext ??= createRayBatchContext(snapshot);
|
|
22
41
|
return rayBatchContext;
|
|
23
42
|
};
|
|
@@ -66,24 +85,65 @@ function formatObservation(observation, options = {}) {
|
|
|
66
85
|
return formatted;
|
|
67
86
|
}
|
|
68
87
|
|
|
69
|
-
function
|
|
88
|
+
function carsByIdForObservation(snapshot, scratch) {
|
|
89
|
+
if (!scratch) return new Map(snapshot.cars.map((entry) => [entry.id, entry]));
|
|
90
|
+
const carsById = scratch.carsById ?? new Map();
|
|
91
|
+
scratch.carsById = carsById;
|
|
92
|
+
carsById.clear();
|
|
93
|
+
for (let index = 0; index < snapshot.cars.length; index += 1) {
|
|
94
|
+
const entry = snapshot.cars[index];
|
|
95
|
+
carsById.set(entry.id, entry);
|
|
96
|
+
}
|
|
97
|
+
return carsById;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function groupEventsByDriver(events, controlledDrivers, scratch = null) {
|
|
70
101
|
if (!events.length) return null;
|
|
71
|
-
const byDriver = new Map(
|
|
72
|
-
const controlledSet = new Set(
|
|
102
|
+
const byDriver = scratch ? (scratch.eventsByDriver ?? new Map()) : new Map();
|
|
103
|
+
const controlledSet = scratch ? (scratch.controlledEventDrivers ?? new Set()) : new Set();
|
|
104
|
+
const eventDriverIds = scratch ? (scratch.eventDriverIds ?? []) : [];
|
|
105
|
+
if (scratch) {
|
|
106
|
+
scratch.eventsByDriver = byDriver;
|
|
107
|
+
scratch.controlledEventDrivers = controlledSet;
|
|
108
|
+
scratch.eventDriverIds = eventDriverIds;
|
|
109
|
+
}
|
|
110
|
+
byDriver.clear();
|
|
111
|
+
controlledSet.clear();
|
|
112
|
+
for (let index = 0; index < controlledDrivers.length; index += 1) {
|
|
113
|
+
const driverId = controlledDrivers[index];
|
|
114
|
+
byDriver.set(driverId, []);
|
|
115
|
+
controlledSet.add(driverId);
|
|
116
|
+
}
|
|
73
117
|
events.forEach((event) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
118
|
+
eventDriverIds.length = 0;
|
|
119
|
+
pushEventDriverId(eventDriverIds, event.driverId);
|
|
120
|
+
pushEventDriverId(eventDriverIds, event.carId);
|
|
121
|
+
pushEventDriverId(eventDriverIds, event.otherCarId);
|
|
122
|
+
const extraDriverIds = event.driverIds ?? [];
|
|
123
|
+
for (let index = 0; index < extraDriverIds.length; index += 1) {
|
|
124
|
+
pushEventDriverId(eventDriverIds, extraDriverIds[index]);
|
|
125
|
+
}
|
|
126
|
+
for (let index = 0; index < eventDriverIds.length; index += 1) {
|
|
127
|
+
const driverId = eventDriverIds[index];
|
|
81
128
|
if (controlledSet.has(driverId)) byDriver.get(driverId)?.push(event);
|
|
82
|
-
}
|
|
129
|
+
}
|
|
83
130
|
});
|
|
131
|
+
eventDriverIds.length = 0;
|
|
84
132
|
return byDriver;
|
|
85
133
|
}
|
|
86
134
|
|
|
135
|
+
function pushEventDriverId(target, driverId) {
|
|
136
|
+
if (!driverId || target.includes(driverId)) return;
|
|
137
|
+
target.push(driverId);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function rayBatchContextScratchForObservation(scratch) {
|
|
141
|
+
if (!scratch) return true;
|
|
142
|
+
const rayScratch = scratch.rayBatchContextScratch ?? {};
|
|
143
|
+
scratch.rayBatchContextScratch = rayScratch;
|
|
144
|
+
return rayScratch;
|
|
145
|
+
}
|
|
146
|
+
|
|
87
147
|
function buildDriverObservationObject(car, snapshot, options, events, sensors, getRayBatchContext = null) {
|
|
88
148
|
const sensorCar = withEnvironmentControlFlag(car, options);
|
|
89
149
|
const onTrack = isCarLegallyOnTrack(car);
|
|
@@ -186,7 +246,15 @@ function buildDriverVectorDirect(car, snapshot, options, events, sensors, getRay
|
|
|
186
246
|
headingErrorRadians: trackHeadingError,
|
|
187
247
|
};
|
|
188
248
|
const contactPatches = includePhysicalDriverSenses ? buildContactPatchSenses(car) : [];
|
|
189
|
-
const
|
|
249
|
+
const appendRayVectorValues = sensors.rays.enabled
|
|
250
|
+
? (vector) => appendRaySensorVectorValues(
|
|
251
|
+
vector,
|
|
252
|
+
sensorCar,
|
|
253
|
+
snapshot,
|
|
254
|
+
sensors.rays,
|
|
255
|
+
rayBatchContextForSensors(sensors, getRayBatchContext, { scratch: true }),
|
|
256
|
+
)
|
|
257
|
+
: null;
|
|
190
258
|
const nearbyCars = sensors.nearbyCars.enabled
|
|
191
259
|
? enrichOpponentRadar(car, buildNearbyCars(car, snapshot, sensors.nearbyCars), snapshot)
|
|
192
260
|
: [];
|
|
@@ -219,7 +287,8 @@ function buildDriverVectorDirect(car, snapshot, options, events, sensors, getRay
|
|
|
219
287
|
curvature: trackCurvature,
|
|
220
288
|
lookahead: buildTrackLookahead(car, snapshot, options),
|
|
221
289
|
},
|
|
222
|
-
rays,
|
|
290
|
+
rays: [],
|
|
291
|
+
appendRayVectorValues,
|
|
223
292
|
nearbyCars,
|
|
224
293
|
};
|
|
225
294
|
return buildObservationVector(source, sensors, {
|
|
@@ -228,9 +297,9 @@ function buildDriverVectorDirect(car, snapshot, options, events, sensors, getRay
|
|
|
228
297
|
});
|
|
229
298
|
}
|
|
230
299
|
|
|
231
|
-
function rayBatchContextForSensors(sensors, getRayBatchContext) {
|
|
300
|
+
function rayBatchContextForSensors(sensors, getRayBatchContext, { scratch = false } = {}) {
|
|
232
301
|
if (!getRayBatchContext || !sensors.rays?.channels?.includes('car')) return null;
|
|
233
|
-
return getRayBatchContext();
|
|
302
|
+
return getRayBatchContext({ scratch });
|
|
234
303
|
}
|
|
235
304
|
|
|
236
305
|
export function defaultSensorOptions(options) {
|
|
@@ -8,7 +8,7 @@ import { normalizeRayOptions } from './sensors.js';
|
|
|
8
8
|
|
|
9
9
|
const DEFAULT_SEED = 1971;
|
|
10
10
|
const DEFAULT_FRAME_SKIP = 1;
|
|
11
|
-
const DEFAULT_MAX_STEPS =
|
|
11
|
+
const DEFAULT_MAX_STEPS = 1_000_000;
|
|
12
12
|
const RESULT_STATE_OUTPUTS = new Set(['full', 'minimal', 'none']);
|
|
13
13
|
const RESET_OBSERVATION_SCOPES = new Set(['all', 'reset']);
|
|
14
14
|
|
|
@@ -38,15 +38,11 @@ export function createPaddockEnvironment(options = {}) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function createSimulationWithEnvironmentScenario(options) {
|
|
41
|
-
const simulationOptions = {
|
|
42
|
-
...options,
|
|
43
|
-
trackQueryIndex: shouldUseTrackQueryIndex(options),
|
|
44
|
-
};
|
|
45
41
|
runWarmupWithGuard({
|
|
46
|
-
options
|
|
42
|
+
options,
|
|
47
43
|
surface: 'environment',
|
|
48
44
|
execute: ({ warmup }) => {
|
|
49
|
-
const warmupSimulation = createRaceSimulation(disableWarmupOptions(
|
|
45
|
+
const warmupSimulation = createRaceSimulation(disableWarmupOptions(options, warmup.surface));
|
|
50
46
|
initializeControlledPitIntentForSimulation(warmupSimulation, options);
|
|
51
47
|
applyEnvironmentScenario(warmupSimulation, options);
|
|
52
48
|
applyControlledRunoffResponse(warmupSimulation, options);
|
|
@@ -59,17 +55,13 @@ function createSimulationWithEnvironmentScenario(options) {
|
|
|
59
55
|
});
|
|
60
56
|
},
|
|
61
57
|
});
|
|
62
|
-
const sim = createRaceSimulation(disableWarmupOptions(
|
|
58
|
+
const sim = createRaceSimulation(disableWarmupOptions(options, 'environment'));
|
|
63
59
|
initializeControlledPitIntentForSimulation(sim, options);
|
|
64
60
|
applyEnvironmentScenario(sim, options);
|
|
65
61
|
applyControlledRunoffResponse(sim, options);
|
|
66
62
|
return sim;
|
|
67
63
|
}
|
|
68
64
|
|
|
69
|
-
function shouldUseTrackQueryIndex(options) {
|
|
70
|
-
return options.trackQueryIndex !== false;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
65
|
export function createEnvironmentRuntime(host) {
|
|
74
66
|
const episodeState = createEpisodeState();
|
|
75
67
|
|
|
@@ -203,6 +195,9 @@ export function createEnvironmentRuntime(host) {
|
|
|
203
195
|
episodeState.lastObservationSnapshot = null;
|
|
204
196
|
episodeState.lastRewardSnapshot = null;
|
|
205
197
|
episodeState.drivers?.clear?.();
|
|
198
|
+
episodeState.destroyedCarsById?.clear?.();
|
|
199
|
+
clearObservationScratch(episodeState.observationScratch);
|
|
200
|
+
clearMetricScratch(episodeState.metricScratch);
|
|
206
201
|
}
|
|
207
202
|
|
|
208
203
|
return { reset, step, resetDrivers, getObservation, getState, getActionSpec, getObservationSpec, destroy };
|
|
@@ -300,6 +295,7 @@ function buildResult({
|
|
|
300
295
|
options,
|
|
301
296
|
events,
|
|
302
297
|
controlledDrivers: resultDrivers,
|
|
298
|
+
scratch: episodeState.observationScratch,
|
|
303
299
|
});
|
|
304
300
|
const episode = evaluateEpisode(observationSnapshot, options, episodeState, resultDrivers);
|
|
305
301
|
const metrics = buildDriverMetrics({
|
|
@@ -307,6 +303,7 @@ function buildResult({
|
|
|
307
303
|
previousSnapshot: episodeState.previousSnapshot,
|
|
308
304
|
options: { ...options, controlledDrivers: resultDrivers },
|
|
309
305
|
events,
|
|
306
|
+
scratch: episodeState.metricScratch,
|
|
310
307
|
});
|
|
311
308
|
const rewardEpisodeInfo = buildDriverEpisodeInfo(episodeState, {
|
|
312
309
|
...options,
|
|
@@ -353,6 +350,42 @@ function buildResult({
|
|
|
353
350
|
};
|
|
354
351
|
}
|
|
355
352
|
|
|
353
|
+
function clearObservationScratch(scratch) {
|
|
354
|
+
if (!scratch) return;
|
|
355
|
+
scratch.carsById?.clear?.();
|
|
356
|
+
scratch.eventsByDriver?.clear?.();
|
|
357
|
+
scratch.controlledEventDrivers?.clear?.();
|
|
358
|
+
clearRayBatchContextScratch(scratch.rayBatchContextScratch);
|
|
359
|
+
if (Array.isArray(scratch.eventDriverIds)) scratch.eventDriverIds.length = 0;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function clearRayBatchContextScratch(scratch) {
|
|
363
|
+
if (!scratch) return;
|
|
364
|
+
const arrayKeys = [
|
|
365
|
+
'rayTargets',
|
|
366
|
+
'rayTargetPool',
|
|
367
|
+
'carTargets',
|
|
368
|
+
'rayVectors',
|
|
369
|
+
'sharedRayQueries',
|
|
370
|
+
'rays',
|
|
371
|
+
'rayPool',
|
|
372
|
+
'rayVectorValues',
|
|
373
|
+
'rayVectorValuePool',
|
|
374
|
+
];
|
|
375
|
+
for (let index = 0; index < arrayKeys.length; index += 1) {
|
|
376
|
+
const value = scratch[arrayKeys[index]];
|
|
377
|
+
if (Array.isArray(value)) value.length = 0;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function clearMetricScratch(scratch) {
|
|
382
|
+
if (!scratch) return;
|
|
383
|
+
scratch.previousCarsById?.clear?.();
|
|
384
|
+
scratch.currentCarsById?.clear?.();
|
|
385
|
+
scratch.contactCounts?.clear?.();
|
|
386
|
+
if (Array.isArray(scratch.eventDriverIds)) scratch.eventDriverIds.length = 0;
|
|
387
|
+
}
|
|
388
|
+
|
|
356
389
|
function applyControlledRunoffResponse(sim, options) {
|
|
357
390
|
options?.controlledDrivers?.forEach?.((driverId) => {
|
|
358
391
|
const car = sim?.cars?.find?.((item) => item.id === driverId);
|
|
@@ -233,7 +233,7 @@ function placementToCarState(track, currentCar, placement, { physicsMode = 'arca
|
|
|
233
233
|
};
|
|
234
234
|
if (speed != null) {
|
|
235
235
|
partial.speed = speed;
|
|
236
|
-
if (physicsMode === '
|
|
236
|
+
if (physicsMode === 'advanced') {
|
|
237
237
|
partial.velocityX = Math.cos(heading) * speed;
|
|
238
238
|
partial.velocityY = Math.sin(heading) * speed;
|
|
239
239
|
} else {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { metersToSimUnits, simUnitsToMeters } from '../../simulation/units.js';
|
|
2
2
|
import { VEHICLE_GEOMETRY } from '../../simulation/vehicle/vehicleGeometry.js';
|
|
3
|
-
import {
|
|
3
|
+
import { getCarRayOrigin, getCarRayVector, intersectAxisAlignedBoxRayScalars } from './rayGeometry.js';
|
|
4
4
|
import { rayDetectableTargets } from './sensorTargets.js';
|
|
5
5
|
|
|
6
6
|
export function estimateCarHit(
|
|
@@ -10,41 +10,53 @@ export function estimateCarHit(
|
|
|
10
10
|
lengthMeters,
|
|
11
11
|
origin = getCarRayOrigin(car),
|
|
12
12
|
targets = rayDetectableTargets(car, snapshot),
|
|
13
|
+
options = {},
|
|
13
14
|
) {
|
|
14
|
-
const ray = getCarRayVector(car, angleDegrees);
|
|
15
|
+
const ray = options.rayVector ?? getCarRayVector(car, angleDegrees);
|
|
16
|
+
if (options.stats) {
|
|
17
|
+
if (options.rayVector) options.stats.callerVectorCount += 1;
|
|
18
|
+
else options.stats.computedVectorCount += 1;
|
|
19
|
+
}
|
|
15
20
|
const maxDistance = metersToSimUnits(lengthMeters);
|
|
16
|
-
let
|
|
21
|
+
let closestTarget = null;
|
|
22
|
+
let closestDistance = Infinity;
|
|
17
23
|
targets.forEach((other) => {
|
|
18
24
|
if (!carRayBroadphaseHit(origin, ray, maxDistance, other)) return;
|
|
19
25
|
const hitDistance = intersectCarFootprint(origin, ray, other);
|
|
20
26
|
if (hitDistance == null || hitDistance > maxDistance) return;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
hit: true,
|
|
25
|
-
distanceSimUnits: hitDistance,
|
|
26
|
-
distanceMeters,
|
|
27
|
-
driverId: other.id,
|
|
28
|
-
targetId: other.id,
|
|
29
|
-
targetType: other.entityType,
|
|
30
|
-
relativeSpeedKph: other.speedKph - car.speedKph,
|
|
31
|
-
};
|
|
27
|
+
if (hitDistance < closestDistance) {
|
|
28
|
+
closestDistance = hitDistance;
|
|
29
|
+
closestTarget = other;
|
|
32
30
|
}
|
|
33
31
|
});
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return
|
|
32
|
+
const resultTarget = options.resultTarget ?? null;
|
|
33
|
+
if (resultTarget && options.stats) options.stats.resultTargetCount += 1;
|
|
34
|
+
if (!closestTarget) return writeCarRayMiss(resultTarget ?? {}, lengthMeters);
|
|
35
|
+
return writeCarRayResult(resultTarget ?? {}, car, closestTarget, closestDistance);
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
export function createCarRayMiss(lengthMeters) {
|
|
40
|
-
return {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
return writeCarRayMiss({}, lengthMeters);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function writeCarRayMiss(target, lengthMeters) {
|
|
43
|
+
target.hit = false;
|
|
44
|
+
target.distanceMeters = lengthMeters;
|
|
45
|
+
target.driverId = null;
|
|
46
|
+
target.targetId = null;
|
|
47
|
+
target.targetType = null;
|
|
48
|
+
target.relativeSpeedKph = 0;
|
|
49
|
+
return target;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function writeCarRayResult(target, car, other, distanceSimUnits) {
|
|
53
|
+
target.hit = true;
|
|
54
|
+
target.distanceMeters = simUnitsToMeters(distanceSimUnits);
|
|
55
|
+
target.driverId = other.id;
|
|
56
|
+
target.targetId = other.id;
|
|
57
|
+
target.targetType = other.entityType;
|
|
58
|
+
target.relativeSpeedKph = other.speedKph - car.speedKph;
|
|
59
|
+
return target;
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
function carRayBroadphaseHit(origin, ray, maxDistance, other) {
|
|
@@ -58,18 +70,17 @@ function carRayBroadphaseHit(origin, ray, maxDistance, other) {
|
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
function intersectCarFootprint(origin, ray, other) {
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
73
|
+
const forwardX = Math.cos(other.heading);
|
|
74
|
+
const forwardY = Math.sin(other.heading);
|
|
75
|
+
const rightX = -forwardY;
|
|
76
|
+
const rightY = forwardX;
|
|
77
|
+
const deltaX = origin.x - other.x;
|
|
78
|
+
const deltaY = origin.y - other.y;
|
|
79
|
+
const localOriginX = deltaX * forwardX + deltaY * forwardY;
|
|
80
|
+
const localOriginY = deltaX * rightX + deltaY * rightY;
|
|
81
|
+
const localRayX = ray.x * forwardX + ray.y * forwardY;
|
|
82
|
+
const localRayY = ray.x * rightX + ray.y * rightY;
|
|
72
83
|
const halfLength = VEHICLE_GEOMETRY.bodyLength / 2;
|
|
73
84
|
const halfWidth = VEHICLE_GEOMETRY.bodyWidth / 2;
|
|
74
|
-
return
|
|
85
|
+
return intersectAxisAlignedBoxRayScalars(localOriginX, localOriginY, localRayX, localRayY, halfLength, halfWidth);
|
|
75
86
|
}
|