@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.
Files changed (136) hide show
  1. package/CHANGELOG.md +176 -1
  2. package/INSTALL_AND_UPDATE.md +24 -4
  3. package/README.md +84 -512
  4. package/docs/architecture.md +38 -24
  5. package/docs/component_inventory.md +72 -0
  6. package/docs/composable-layouts.md +77 -0
  7. package/docs/concepts.md +2 -2
  8. package/docs/custom_model_controller.md +6 -6
  9. package/docs/data-helpers.md +50 -0
  10. package/docs/data_contract.md +104 -27
  11. package/docs/getting-started.md +75 -0
  12. package/docs/headless-environment.md +68 -0
  13. package/docs/index.md +18 -4
  14. package/docs/learnings.md +6 -6
  15. package/docs/rules.md +12 -8
  16. package/docs/sense_contract.md +1 -1
  17. package/docs/system_specs.md +71 -21
  18. package/docs/theming.md +64 -0
  19. package/docs/training.md +26 -19
  20. package/docs/troubleshooting.md +76 -0
  21. package/docs/upgrading-hosts-to-3.md +42 -0
  22. package/examples/python/README.md +6 -0
  23. package/examples/python/base_policy_server.py +19 -0
  24. package/package.json +10 -3
  25. package/src/api/PaddockSimulatorController.js +150 -24
  26. package/src/api/runtimeTheme.js +59 -0
  27. package/src/app/F1SimulatorApp.js +213 -29
  28. package/src/app/banners/raceDataBannerController.js +71 -36
  29. package/src/app/banners/stewardMessageController.js +5 -4
  30. package/src/app/camera/cameraConstants.js +2 -0
  31. package/src/app/camera/cameraController.js +86 -29
  32. package/src/app/camera/cameraGeometry.js +12 -1
  33. package/src/app/domBindings.js +89 -9
  34. package/src/app/layoutScheduler.js +27 -0
  35. package/src/app/layoutSupport.js +165 -0
  36. package/src/app/raceOverlayClearanceSupport.js +158 -0
  37. package/src/app/readouts/carOverviewRenderer.js +16 -8
  38. package/src/app/readouts/controlStateRenderer.js +1 -1
  39. package/src/app/readouts/raceStatusRenderer.js +30 -21
  40. package/src/app/readouts/readoutFormatters.js +2 -0
  41. package/src/app/readouts/telemetryRenderer.js +17 -8
  42. package/src/app/readouts/timingTowerRenderer.js +262 -49
  43. package/src/app/runtime/frameLoop.js +5 -1
  44. package/src/app/runtime/runtimeVisibility.js +66 -5
  45. package/src/config/cssValues.js +16 -0
  46. package/src/config/defaultOptions.js +58 -38
  47. package/src/config/themeOptions.js +830 -0
  48. package/src/config/timingGapMode.js +13 -0
  49. package/src/config/urlOptions.js +25 -0
  50. package/src/data/championship.js +14 -9
  51. package/src/data/index.d.ts +212 -0
  52. package/src/data/index.js +14 -0
  53. package/src/environment/controllerLoop.js +17 -3
  54. package/src/environment/episode.js +11 -1
  55. package/src/environment/index.d.ts +1 -2
  56. package/src/environment/metrics.js +59 -16
  57. package/src/environment/observationVector.js +50 -33
  58. package/src/environment/observations.js +89 -20
  59. package/src/environment/options.js +1 -1
  60. package/src/environment/runtime.js +44 -11
  61. package/src/environment/scenarios.js +1 -1
  62. package/src/environment/sensors/carRays.js +48 -37
  63. package/src/environment/sensors/index.js +501 -97
  64. package/src/environment/sensors/indexedRayBands.js +134 -46
  65. package/src/environment/sensors/rayBandTrace.js +1171 -0
  66. package/src/environment/sensors/rayChannels.js +108 -0
  67. package/src/environment/sensors/rayConfig.js +71 -25
  68. package/src/environment/sensors/rayGeometry.js +23 -17
  69. package/src/environment/sensors/sensorTargets.js +67 -1
  70. package/src/environment/sensors/surfaceRays.js +167 -118
  71. package/src/environment/sensors/trackRays.js +157 -48
  72. package/src/index.d.ts +153 -9
  73. package/src/index.js +52 -3
  74. package/src/simulation/collisionGeometry.js +260 -96
  75. package/src/simulation/driver/driverController.js +1 -1
  76. package/src/simulation/driver/edgeRecovery.js +1 -1
  77. package/src/simulation/driver/racingControls.js +2 -2
  78. package/src/simulation/driver/rejoinControls.js +2 -2
  79. package/src/simulation/pit/pitFlow.js +21 -9
  80. package/src/simulation/pit/pitKinematics.js +2 -2
  81. package/src/simulation/pit/pitQueue.js +5 -3
  82. package/src/simulation/pit/pitRouteMovement.js +9 -4
  83. package/src/simulation/pit/pitRouting.js +79 -46
  84. package/src/simulation/pit/pitState.js +1 -0
  85. package/src/simulation/pit/pitTireService.js +7 -3
  86. package/src/simulation/race/raceFinish.js +25 -8
  87. package/src/simulation/race/raceOrder.js +189 -32
  88. package/src/simulation/race/raceProgress.js +129 -37
  89. package/src/simulation/race/raceSetup.js +4 -6
  90. package/src/simulation/race/raceStep.js +26 -14
  91. package/src/simulation/race/safetyCar.js +1 -1
  92. package/src/simulation/race/stalledDnf.js +1 -1
  93. package/src/simulation/raceSimulation.js +20 -17
  94. package/src/simulation/rules/collisionSteward.js +40 -23
  95. package/src/simulation/rules/rulesReview.js +77 -56
  96. package/src/simulation/snapshots/raceSnapshots.js +37 -0
  97. package/src/simulation/timing/drsTiming.js +143 -30
  98. package/src/simulation/timing/gapEstimation.js +25 -3
  99. package/src/simulation/timing/lapTelemetry.js +138 -40
  100. package/src/simulation/timing/raceTiming.js +5 -1
  101. package/src/simulation/timing/sectorPerformance.js +104 -26
  102. package/src/simulation/timing/timingHistory.js +229 -14
  103. package/src/simulation/timing/timingLines.js +65 -7
  104. package/src/simulation/track/pitLaneLayout.js +30 -3
  105. package/src/simulation/track/pitLaneState.js +218 -19
  106. package/src/simulation/track/spatialQueries.js +66 -33
  107. package/src/simulation/track/trackMath.js +91 -24
  108. package/src/simulation/track/trackModel.js +2 -0
  109. package/src/simulation/track/trackQueryIndex.js +1468 -189
  110. package/src/simulation/track/trackQueryPitIndex.js +138 -14
  111. package/src/simulation/track/trackQueryScratch.js +7 -0
  112. package/src/simulation/track/trackQueryStats.js +25 -0
  113. package/src/simulation/track/trackSnapshotJson.js +139 -0
  114. package/src/simulation/track/trackStatePolicy.js +249 -2
  115. package/src/simulation/vehicle/contactResolution.js +99 -62
  116. package/src/simulation/vehicle/mainTrackWheelSurface.js +73 -34
  117. package/src/simulation/vehicle/pitWheelSurface.js +81 -28
  118. package/src/simulation/vehicle/runoffResponse.js +54 -29
  119. package/src/simulation/vehicle/vehicleGeometry.js +245 -51
  120. package/src/simulation/vehicle/vehicleKinematics.js +2 -2
  121. package/src/simulation/vehicle/vehiclePhysics.js +45 -30
  122. package/src/simulation/vehicle/vehicleSnapshots.js +35 -1
  123. package/src/simulation/vehicle/vehicleState.js +7 -1
  124. package/src/simulation/vehicle/wheelSurface.js +807 -92
  125. package/src/simulation/warmup/runtimeWarmup.js +0 -1
  126. package/src/styles.css +1568 -519
  127. package/src/ui/bannerTemplates.js +38 -28
  128. package/src/ui/cameraControlsTemplate.js +16 -5
  129. package/src/ui/carOverviewTemplate.js +12 -5
  130. package/src/ui/raceCanvasTemplate.js +31 -8
  131. package/src/ui/raceControlsTemplate.js +27 -10
  132. package/src/ui/shellTemplate.js +3 -1
  133. package/src/ui/telemetryDrawerTemplate.js +23 -10
  134. package/src/ui/telemetryTemplates.js +58 -32
  135. package/src/ui/templateUtils.js +48 -0
  136. 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 { buildNearbyCars, buildRaySensors, createRayBatchContext, normalizeRayOptions } from './sensors.js';
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({ snapshot, options, events = [], controlledDrivers = options.controlledDrivers }) {
15
- const carsById = new Map(snapshot.cars.map((entry) => [entry.id, entry]));
16
- const eventsByDriver = groupEventsByDriver(events, controlledDrivers);
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
- const getRayBatchContext = () => {
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 groupEventsByDriver(events, controlledDrivers) {
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(controlledDrivers.map((driverId) => [driverId, []]));
72
- const controlledSet = new Set(controlledDrivers);
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
- const driverIds = new Set([
75
- event.driverId,
76
- event.carId,
77
- event.otherCarId,
78
- ...(event.driverIds ?? []),
79
- ].filter(Boolean));
80
- driverIds.forEach((driverId) => {
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 rays = sensors.rays.enabled ? buildRaySensors(sensorCar, snapshot, sensors.rays, rayBatchContextForSensors(sensors, getRayBatchContext)) : [];
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 = 10000;
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: simulationOptions,
42
+ options,
47
43
  surface: 'environment',
48
44
  execute: ({ warmup }) => {
49
- const warmupSimulation = createRaceSimulation(disableWarmupOptions(simulationOptions, warmup.surface));
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(simulationOptions, 'environment'));
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 === 'simulator') {
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 { dot, getCarRayOrigin, getCarRayVector, intersectAxisAlignedBoxRay } from './rayGeometry.js';
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 closest = null;
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
- const distanceMeters = simUnitsToMeters(hitDistance);
22
- if (!closest || hitDistance < closest.distanceSimUnits) {
23
- closest = {
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
- if (!closest) return createCarRayMiss(lengthMeters);
35
- const { distanceSimUnits, ...publicHit } = closest;
36
- return publicHit;
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
- hit: false,
42
- distanceMeters: lengthMeters,
43
- driverId: null,
44
- targetId: null,
45
- targetType: null,
46
- relativeSpeedKph: 0,
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 forward = { x: Math.cos(other.heading), y: Math.sin(other.heading) };
62
- const right = { x: -Math.sin(other.heading), y: Math.cos(other.heading) };
63
- const delta = { x: origin.x - other.x, y: origin.y - other.y };
64
- const localOrigin = {
65
- x: dot(delta, forward),
66
- y: dot(delta, right),
67
- };
68
- const localRay = {
69
- x: dot(ray, forward),
70
- y: dot(ray, right),
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 intersectAxisAlignedBoxRay(localOrigin, localRay, halfLength, halfWidth);
85
+ return intersectAxisAlignedBoxRayScalars(localOriginX, localOriginY, localRayX, localRayY, halfLength, halfWidth);
75
86
  }