@inventure71/paddockjs 3.0.0 → 4.1.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 +152 -1
- package/INSTALL_AND_UPDATE.md +21 -4
- package/README.md +103 -551
- package/docs/architecture.md +28 -18
- package/docs/component_inventory.md +5 -4
- package/docs/composable-layouts.md +79 -0
- package/docs/custom_model_controller.md +4 -4
- package/docs/data-helpers.md +50 -0
- package/docs/data_contract.md +13 -7
- package/docs/getting-started.md +105 -0
- package/docs/headless-environment.md +68 -0
- package/docs/index.md +17 -4
- package/docs/rules.md +6 -2
- package/docs/system_specs.md +60 -7
- package/docs/theming.md +64 -0
- package/docs/training.md +20 -14
- 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 +21 -5
- package/src/api/PaddockSimulatorController.js +78 -22
- package/src/api/runtimeTheme.js +59 -0
- package/src/app/F1SimulatorApp.js +24 -3
- package/src/app/readouts/timingTowerRenderer.js +203 -42
- package/src/app/runtime/frameLoop.js +5 -1
- package/src/config/defaultOptions.js +8 -1
- package/src/config/themeOptions.js +5 -0
- package/src/data/index.d.ts +212 -0
- package/src/data/index.js +14 -0
- package/src/environment/episode.js +11 -1
- package/src/environment/metrics.js +59 -16
- package/src/environment/observationVector.js +50 -33
- package/src/environment/observations.js +89 -20
- package/src/environment/runtime.js +41 -0
- 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 +130 -89
- package/src/environment/sensors/trackRays.js +115 -1
- package/src/index.d.ts +43 -0
- package/src/index.js +27 -2
- package/src/placeholder/index.d.ts +11 -0
- package/src/placeholder/index.js +61 -0
- package/src/placeholder.css +72 -0
- package/src/simulation/collisionGeometry.js +260 -96
- package/src/simulation/pit/pitFlow.js +21 -9
- package/src/simulation/pit/pitQueue.js +5 -3
- package/src/simulation/pit/pitRouteMovement.js +9 -4
- package/src/simulation/pit/pitRouting.js +77 -42
- 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 +2 -0
- package/src/simulation/race/raceStep.js +25 -13
- 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 +115 -36
- 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 +23 -0
- package/src/simulation/track/pitLaneState.js +218 -19
- package/src/simulation/track/spatialQueries.js +67 -11
- package/src/simulation/track/trackMath.js +91 -24
- package/src/simulation/track/trackModel.js +2 -0
- package/src/simulation/track/trackQueryIndex.js +1334 -100
- 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 -1
- package/src/simulation/vehicle/contactResolution.js +96 -59
- 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/vehiclePhysics.js +42 -27
- package/src/simulation/vehicle/vehicleSnapshots.js +23 -0
- package/src/simulation/vehicle/vehicleState.js +6 -0
- package/src/simulation/vehicle/wheelSurface.js +807 -92
- package/src/styles.css +497 -497
- package/src/ui/raceControlsTemplate.js +1 -0
- package/src/ui/telemetryDrawerTemplate.js +1 -0
- package/src/ui/telemetryTemplates.js +1 -0
|
@@ -91,40 +91,36 @@ export function buildObservationVector(source, sensors, { includeSchema = true,
|
|
|
91
91
|
);
|
|
92
92
|
vector.push(sample.curvature ?? 0, (sample.headingDeltaRadians ?? 0) / Math.PI);
|
|
93
93
|
});
|
|
94
|
-
source.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
{ name: `rays[${index}].car.distanceRatio`, scale: '0..1' },
|
|
101
|
-
{ name: `rays[${index}].car.hit`, scale: 'boolean' },
|
|
102
|
-
{ name: `rays[${index}].car.relativeSpeedKph`, unit: 'kph', scale: 'fixed:200' },
|
|
103
|
-
{ name: `rays[${index}].car.targetTypeReplayGhost`, scale: 'boolean' },
|
|
104
|
-
);
|
|
105
|
-
vector.push(
|
|
106
|
-
ratio(ray.track.distanceMeters, ray.lengthMeters),
|
|
107
|
-
ray.track.hit ? 1 : 0,
|
|
108
|
-
ray.track.kind === 'exit' ? 1 : 0,
|
|
109
|
-
ray.track.kind === 'entry' ? 1 : 0,
|
|
110
|
-
ratio(ray.car.distanceMeters, ray.lengthMeters),
|
|
111
|
-
ray.car.hit ? 1 : 0,
|
|
112
|
-
ray.car.relativeSpeedKph / 200,
|
|
113
|
-
ray.car.targetType === 'replayGhost' ? 1 : 0,
|
|
114
|
-
);
|
|
115
|
-
MODEL_RAY_SURFACE_CHANNELS.forEach((channel) => {
|
|
116
|
-
if (sensors.rays.channels?.includes?.(channel)) {
|
|
117
|
-
pushSchema(schema,
|
|
118
|
-
{ name: `rays[${index}].${channel}.distanceRatio`, scale: '0..1' },
|
|
119
|
-
{ name: `rays[${index}].${channel}.hit`, scale: 'boolean' },
|
|
120
|
-
);
|
|
121
|
-
vector.push(
|
|
122
|
-
ratio(ray[channel]?.distanceMeters ?? ray.lengthMeters, ray.lengthMeters),
|
|
123
|
-
ray[channel]?.hit ? 1 : 0,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
94
|
+
if (typeof source.appendRayVectorValues === 'function') {
|
|
95
|
+
source.appendRayVectorValues(vector);
|
|
96
|
+
} else if (Array.isArray(source.rayVectorValues)) {
|
|
97
|
+
source.rayVectorValues.forEach((values, index) => {
|
|
98
|
+
pushRaySchema(schema, index, sensors);
|
|
99
|
+
vector.push(...values);
|
|
126
100
|
});
|
|
127
|
-
}
|
|
101
|
+
} else {
|
|
102
|
+
source.rays.forEach((ray, index) => {
|
|
103
|
+
pushRaySchema(schema, index, sensors);
|
|
104
|
+
vector.push(
|
|
105
|
+
ratio(ray.track.distanceMeters, ray.lengthMeters),
|
|
106
|
+
ray.track.hit ? 1 : 0,
|
|
107
|
+
ray.track.kind === 'exit' ? 1 : 0,
|
|
108
|
+
ray.track.kind === 'entry' ? 1 : 0,
|
|
109
|
+
ratio(ray.car.distanceMeters, ray.lengthMeters),
|
|
110
|
+
ray.car.hit ? 1 : 0,
|
|
111
|
+
ray.car.relativeSpeedKph / 200,
|
|
112
|
+
ray.car.targetType === 'replayGhost' ? 1 : 0,
|
|
113
|
+
);
|
|
114
|
+
MODEL_RAY_SURFACE_CHANNELS.forEach((channel) => {
|
|
115
|
+
if (sensors.rays.channels?.includes?.(channel)) {
|
|
116
|
+
vector.push(
|
|
117
|
+
ratio(ray[channel]?.distanceMeters ?? ray.lengthMeters, ray.lengthMeters),
|
|
118
|
+
ray[channel]?.hit ? 1 : 0,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
128
124
|
const nearbyLimit = sensors.nearbyCars.enabled ? (sensors.nearbyCars.maxCars ?? source.nearbyCars.length) : 0;
|
|
129
125
|
const nearbyRadius = sensors.nearbyCars.radiusMeters ?? 150;
|
|
130
126
|
for (let index = 0; index < nearbyLimit; index += 1) {
|
|
@@ -179,6 +175,27 @@ function pushSchema(schema, ...entries) {
|
|
|
179
175
|
if (schema) schema.push(...entries);
|
|
180
176
|
}
|
|
181
177
|
|
|
178
|
+
function pushRaySchema(schema, index, sensors) {
|
|
179
|
+
pushSchema(schema,
|
|
180
|
+
{ name: `rays[${index}].track.distanceRatio`, scale: '0..1' },
|
|
181
|
+
{ name: `rays[${index}].track.hit`, scale: 'boolean' },
|
|
182
|
+
{ name: `rays[${index}].track.kindExit`, scale: 'boolean' },
|
|
183
|
+
{ name: `rays[${index}].track.kindEntry`, scale: 'boolean' },
|
|
184
|
+
{ name: `rays[${index}].car.distanceRatio`, scale: '0..1' },
|
|
185
|
+
{ name: `rays[${index}].car.hit`, scale: 'boolean' },
|
|
186
|
+
{ name: `rays[${index}].car.relativeSpeedKph`, unit: 'kph', scale: 'fixed:200' },
|
|
187
|
+
{ name: `rays[${index}].car.targetTypeReplayGhost`, scale: 'boolean' },
|
|
188
|
+
);
|
|
189
|
+
MODEL_RAY_SURFACE_CHANNELS.forEach((channel) => {
|
|
190
|
+
if (sensors.rays.channels?.includes?.(channel)) {
|
|
191
|
+
pushSchema(schema,
|
|
192
|
+
{ name: `rays[${index}].${channel}.distanceRatio`, scale: '0..1' },
|
|
193
|
+
{ name: `rays[${index}].${channel}.hit`, scale: 'boolean' },
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
182
199
|
function normalizeLapProgress(source) {
|
|
183
200
|
return ratio(source.self.lapProgressMeters, source.track.lengthMeters || 1);
|
|
184
201
|
}
|
|
@@ -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) {
|
|
@@ -195,6 +195,9 @@ export function createEnvironmentRuntime(host) {
|
|
|
195
195
|
episodeState.lastObservationSnapshot = null;
|
|
196
196
|
episodeState.lastRewardSnapshot = null;
|
|
197
197
|
episodeState.drivers?.clear?.();
|
|
198
|
+
episodeState.destroyedCarsById?.clear?.();
|
|
199
|
+
clearObservationScratch(episodeState.observationScratch);
|
|
200
|
+
clearMetricScratch(episodeState.metricScratch);
|
|
198
201
|
}
|
|
199
202
|
|
|
200
203
|
return { reset, step, resetDrivers, getObservation, getState, getActionSpec, getObservationSpec, destroy };
|
|
@@ -292,6 +295,7 @@ function buildResult({
|
|
|
292
295
|
options,
|
|
293
296
|
events,
|
|
294
297
|
controlledDrivers: resultDrivers,
|
|
298
|
+
scratch: episodeState.observationScratch,
|
|
295
299
|
});
|
|
296
300
|
const episode = evaluateEpisode(observationSnapshot, options, episodeState, resultDrivers);
|
|
297
301
|
const metrics = buildDriverMetrics({
|
|
@@ -299,6 +303,7 @@ function buildResult({
|
|
|
299
303
|
previousSnapshot: episodeState.previousSnapshot,
|
|
300
304
|
options: { ...options, controlledDrivers: resultDrivers },
|
|
301
305
|
events,
|
|
306
|
+
scratch: episodeState.metricScratch,
|
|
302
307
|
});
|
|
303
308
|
const rewardEpisodeInfo = buildDriverEpisodeInfo(episodeState, {
|
|
304
309
|
...options,
|
|
@@ -345,6 +350,42 @@ function buildResult({
|
|
|
345
350
|
};
|
|
346
351
|
}
|
|
347
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
|
+
|
|
348
389
|
function applyControlledRunoffResponse(sim, options) {
|
|
349
390
|
options?.controlledDrivers?.forEach?.((driverId) => {
|
|
350
391
|
const car = sim?.cars?.find?.((item) => item.id === driverId);
|
|
@@ -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
|
}
|