@inventure71/paddockjs 4.1.0 → 4.3.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 +76 -0
- package/INSTALL_AND_UPDATE.md +31 -5
- package/README.md +34 -3
- package/docs/advanced_ai_pace.md +131 -0
- package/docs/advanced_physics_rebuild.md +166 -0
- package/docs/architecture.md +111 -30
- package/docs/cleanup_coverage.md +284 -0
- package/docs/component_inventory.md +32 -16
- package/docs/composable-layouts.md +2 -0
- package/docs/concepts.md +12 -1
- package/docs/custom_model_controller.md +1 -1
- package/docs/data_contract.md +33 -10
- package/docs/data_model.md +4 -2
- package/docs/efficiency_cleanup_2026-10-13.md +80 -0
- package/docs/getting-started.md +3 -0
- package/docs/grid_walk.md +80 -0
- package/docs/index.md +5 -2
- package/docs/keyboard_driving_fix.md +118 -0
- package/docs/limit_driving_2026-10-14.md +88 -0
- package/docs/loading.md +141 -0
- package/docs/maintainability_audit_2026-09-08.md +146 -0
- package/docs/racing_ai_improvements.md +145 -0
- package/docs/rules.md +43 -13
- package/docs/sense_contract.md +4 -1
- package/docs/system_specs.md +40 -12
- package/docs/testing-reliability-guide.md +67 -0
- package/docs/v11_improvements.md +108 -0
- package/package.json +23 -15
- package/src/api/MountedSimulatorComponents.js +101 -0
- package/src/api/PaddockSimulatorController.js +56 -81
- package/src/app/BrowserExpertAdapter.js +0 -6
- package/src/app/BrowserExpertExternalRenderer.js +72 -50
- package/src/app/ExternalTrackSnapshot.js +50 -0
- package/src/app/F1SimulatorApp.js +242 -297
- package/src/app/HostLifecycleEmitter.js +93 -0
- package/src/app/banners/raceDataBannerController.js +9 -4
- package/src/app/camera/cameraConstants.js +1 -1
- package/src/app/camera/cameraController.js +10 -13
- package/src/app/camera/cameraGeometry.js +1 -1
- package/src/app/controls/SimulatorViewControls.js +45 -0
- package/src/app/controls/workbenchBindings.js +64 -0
- package/src/app/controls/workbenchDisclosure.js +41 -0
- package/src/app/domBindings.js +21 -13
- package/src/app/gridWalk/GridWalkController.js +180 -0
- package/src/app/gridWalk/gridWalkCamera.js +88 -0
- package/src/app/gridWalk/gridWalkControls.js +24 -0
- package/src/app/gridWalk/gridWalkCrew.js +246 -0
- package/src/app/gridWalk/gridWalkLabels.js +104 -0
- package/src/app/gridWalk/gridWalkLayout.js +46 -0
- package/src/app/gridWalk/gridWalkNavigation.js +99 -0
- package/src/app/gridWalk/gridWalkProjectBanner.js +43 -0
- package/src/app/gridWalk/gridWalkSession.js +50 -0
- package/src/app/layoutSupport.js +2 -0
- package/src/app/raceOverlayClearanceSupport.js +21 -7
- package/src/app/readouts/carOverviewRenderer.js +9 -4
- package/src/app/readouts/readoutFormatters.js +1 -8
- package/src/app/readouts/telemetryRenderer.js +1 -3
- package/src/app/readouts/timingTowerRenderer.js +48 -50
- package/src/app/rendering/appAssets.js +8 -10
- package/src/app/rendering/carRenderer.js +27 -79
- package/src/app/rendering/displayUtils.js +0 -6
- package/src/app/rendering/expertSensorRenderer.js +1 -1
- package/src/app/rendering/replayGhostRenderer.js +4 -1
- package/src/app/rendering/trackRenderer.js +2 -2
- package/src/app/runtime/frameLoop.js +18 -17
- package/src/app/runtime/pixiStartupLifecycle.js +94 -0
- package/src/app/runtime/runtimeTiming.js +4 -1
- package/src/app/runtime/runtimeVisibility.js +7 -2
- package/src/config/cssValues.js +1 -1
- package/src/config/defaultOptions.js +12 -173
- package/src/config/restartOptions.js +1 -0
- package/src/config/runtimeSeed.js +10 -0
- package/src/config/simulatorDefaults.js +106 -0
- package/src/config/telemetryModules.js +20 -0
- package/src/config/theme/cssTokens.js +85 -0
- package/src/config/theme/themeCss.js +72 -0
- package/src/config/theme/themeInputs.js +155 -0
- package/src/config/theme/themeResolver.js +172 -0
- package/src/config/theme/themeSelectors.js +69 -0
- package/src/config/theme/themeTokens.js +257 -0
- package/src/config/theme/themeUtils.js +15 -0
- package/src/config/themeOptions.js +14 -830
- package/src/config/timingGapMode.js +0 -2
- package/src/config/uiOptions.js +53 -0
- package/src/data/championship.js +16 -158
- package/src/data/championshipDefaults.js +87 -0
- package/src/data/driverData.js +10 -20
- package/src/data/entryIdentity.js +67 -0
- package/src/data/entryPresentation.js +23 -0
- package/src/data/entryTypes.d.ts +160 -0
- package/src/data/index.d.ts +12 -195
- package/src/data/ratingScale.js +14 -0
- package/src/data/teamTypes.d.ts +23 -0
- package/src/data/trackTypes.d.ts +101 -0
- package/src/data/vehicleData.js +12 -23
- package/src/environment/actions.js +3 -2
- package/src/environment/controllerLoop.js +57 -44
- package/src/environment/controllerScheduler.js +30 -0
- package/src/environment/driverObservations.js +260 -0
- package/src/environment/driverReset.js +51 -0
- package/src/environment/episode.js +0 -1
- package/src/environment/evaluation.js +6 -90
- package/src/environment/evaluationTracker.js +100 -0
- package/src/environment/events.js +23 -0
- package/src/environment/index.d.ts +35 -91
- package/src/environment/metrics.js +3 -17
- package/src/environment/observationSchema.js +104 -0
- package/src/environment/observationVector.js +16 -115
- package/src/environment/observations.js +16 -316
- package/src/environment/options.js +5 -24
- package/src/environment/renderFrames.js +21 -0
- package/src/environment/resetOptions.js +41 -0
- package/src/environment/results.js +125 -0
- package/src/environment/rewards.js +10 -16
- package/src/environment/runtime.js +17 -300
- package/src/environment/scenarioOptions.js +90 -0
- package/src/environment/scenarioPlacements.js +70 -0
- package/src/environment/scenarioPresets.js +78 -0
- package/src/environment/scenarios.js +5 -236
- package/src/environment/scratch.js +18 -0
- package/src/environment/sensorOptions.js +20 -0
- package/src/environment/sensors/index.js +2 -556
- package/src/environment/sensors/indexedRayBands.js +0 -18
- package/src/environment/sensors/nearbyCars.js +18 -15
- package/src/environment/sensors/opponentRadar.js +3 -2
- package/src/environment/sensors/pitConnectorProximity.js +20 -0
- package/src/environment/sensors/rayBandTrace.js +1 -37
- package/src/environment/sensors/rayBatch.js +81 -0
- package/src/environment/sensors/rayConfig.js +26 -0
- package/src/environment/sensors/rayGeometry.js +0 -8
- package/src/environment/sensors/rayGuards.js +0 -6
- package/src/environment/sensors/rayOutputs.js +75 -0
- package/src/environment/sensors/rayQuery.js +110 -0
- package/src/environment/sensors/rayScratch.js +146 -0
- package/src/environment/sensors/sensorTargets.js +12 -4
- package/src/environment/sensors/surfaceRays.js +6 -61
- package/src/environment/sensors/trackRays.js +7 -80
- package/src/environment/simulationSetup.js +30 -0
- package/src/environment/specs.js +15 -109
- package/src/index.d.ts +43 -202
- package/src/index.js +28 -6
- package/src/placeholder/index.js +1 -8
- package/src/rendering/displayLifecycle.js +6 -0
- package/src/rendering/proceduralTrackAsset.js +1 -1
- package/src/rendering/renderSnapshot.js +3 -0
- package/src/rendering/track/finishLineRenderer.js +1 -1
- package/src/rendering/track/gridRenderer.js +1 -1
- package/src/rendering/track/offsetStrokeSafety.js +2 -2
- package/src/rendering/track/pitLaneRenderer.js +1 -1
- package/src/rendering/track/trackMaterialRenderer.js +4 -4
- package/src/rendering/track/trackRenderConstants.js +0 -1
- package/src/rendering/track/trackRenderGeometry.js +1 -7
- package/src/simulation/collisionGeometry.js +12 -20
- package/src/simulation/driver/advancedPathControls.js +53 -0
- package/src/simulation/driver/advancedRacePace.js +112 -0
- package/src/simulation/driver/advancedRacingControls.js +61 -0
- package/src/simulation/driver/advancedRacingEnvelope.js +255 -0
- package/src/simulation/driver/advancedRacingSteering.js +76 -0
- package/src/simulation/driver/advancedRacingTiming.js +16 -0
- package/src/simulation/driver/arcadeRacePace.js +83 -0
- package/src/simulation/driver/arcadeRacingControls.js +111 -0
- package/src/simulation/driver/arcadeRacingEnvelope.js +63 -0
- package/src/simulation/driver/driverControlConstants.js +0 -2
- package/src/simulation/driver/driverController.js +16 -5
- package/src/simulation/driver/edgeRecovery.js +2 -3
- package/src/simulation/driver/racingControls.js +10 -396
- package/src/simulation/driver/racingDriverProfile.js +48 -0
- package/src/simulation/driver/racingLinePlan.js +54 -20
- package/src/simulation/driver/rejoinControls.js +59 -112
- package/src/simulation/driver/safetyCarControls.js +12 -0
- package/src/simulation/driver/trafficScan.js +136 -27
- package/src/simulation/participants/participantInteractions.js +24 -15
- package/src/simulation/pit/pitCarPlacement.js +29 -0
- package/src/simulation/pit/pitFlow.js +8 -30
- package/src/simulation/pit/pitIntent.js +4 -3
- package/src/simulation/pit/pitOccupancy.js +2 -2
- package/src/simulation/pit/pitQueue.js +2 -18
- package/src/simulation/pit/pitRouteMovement.js +1 -36
- package/src/simulation/pit/pitRouting.js +5 -81
- package/src/simulation/pit/pitService.js +3 -32
- package/src/simulation/pit/pitServiceConstants.js +1 -0
- package/src/simulation/pit/pitServiceProgress.js +34 -0
- package/src/simulation/pit/pitSnapshots.js +8 -4
- package/src/simulation/pit/pitState.js +1 -5
- package/src/simulation/race/classification.js +1 -8
- package/src/simulation/race/raceFinish.js +3 -8
- package/src/simulation/race/raceOrder.js +2 -13
- package/src/simulation/race/raceProgress.js +6 -20
- package/src/simulation/race/raceSetup.js +1 -1
- package/src/simulation/race/raceStep.js +20 -11
- package/src/simulation/race/redFlag.js +2 -4
- package/src/simulation/race/safetyCar.js +5 -9
- package/src/simulation/race/stalledDnf.js +2 -4
- package/src/simulation/raceSimulation.js +6 -38
- package/src/simulation/replay/replayGhosts.js +44 -32
- package/src/simulation/rules/collisionSteward.js +0 -14
- package/src/simulation/rules/penaltyActions.js +37 -0
- package/src/simulation/rules/penaltyLedger.js +8 -1
- package/src/simulation/rules/pitLaneSpeedingSteward.js +1 -1
- package/src/simulation/rules/rulesReview.js +20 -16
- package/src/simulation/rules/trackLimitsSteward.js +2 -2
- package/src/simulation/simMath.js +4 -0
- package/src/simulation/snapshots/raceSnapshots.js +30 -11
- package/src/simulation/timing/drsState.js +8 -0
- package/src/simulation/timing/drsTiming.js +2 -4
- package/src/simulation/timing/lapTelemetry.js +7 -4
- package/src/simulation/timing/sectorPerformance.js +14 -6
- package/src/simulation/timing/timingLines.js +12 -6
- package/src/simulation/track/pitLaneAccess.js +2 -2
- package/src/simulation/track/pitLaneLayout.js +4 -135
- package/src/simulation/track/pitLaneModel.js +70 -0
- package/src/simulation/track/pitLaneState.js +0 -1
- package/src/simulation/track/pitServiceLayout.js +121 -0
- package/src/simulation/track/proceduralTrack.js +77 -0
- package/src/simulation/track/spatialQueries.js +76 -31
- package/src/simulation/track/trackBuilder.js +54 -0
- package/src/simulation/track/trackConstants.js +0 -22
- package/src/simulation/track/trackMath.js +4 -29
- package/src/simulation/track/trackModel.js +12 -240
- package/src/simulation/track/trackModelCache.js +105 -0
- package/src/simulation/track/trackNearestQueries.js +1218 -0
- package/src/simulation/track/trackQueryConstants.js +3 -0
- package/src/simulation/track/trackQueryConstruction.js +368 -0
- package/src/simulation/track/trackQueryIndex.js +12 -1933
- package/src/simulation/track/trackQuerySegments.js +174 -0
- package/src/simulation/track/trackQueryStats.js +12 -0
- package/src/simulation/track/trackRayQueries.js +165 -0
- package/src/simulation/vehicle/advancedContactResponse.js +89 -0
- package/src/simulation/vehicle/advancedTireForces.js +92 -0
- package/src/simulation/vehicle/advancedVehicleModel.js +65 -0
- package/src/simulation/vehicle/advancedVehiclePhysics.js +127 -0
- package/src/simulation/vehicle/contactResolution.js +23 -9
- package/src/simulation/vehicle/mainTrackWheelSurface.js +2 -13
- package/src/simulation/vehicle/pitWheelSurface.js +0 -12
- package/src/simulation/vehicle/runoffResponse.js +2 -4
- package/src/simulation/vehicle/sampledWheelSurface.js +266 -0
- package/src/simulation/vehicle/vehicleGeometry.js +85 -103
- package/src/simulation/vehicle/vehicleKinematics.js +10 -0
- package/src/simulation/vehicle/vehiclePhysics.js +42 -196
- package/src/simulation/vehicle/vehicleSnapshots.js +18 -6
- package/src/simulation/vehicle/vehicleState.js +5 -3
- package/src/simulation/vehicle/wheelSurface.js +13 -432
- package/src/simulation/vehicle/wheelSurfaceCache.js +55 -0
- package/src/simulation/vehicle/wheelSurfaceSummary.js +101 -0
- package/src/styles.css +173 -19
- package/src/ui/bannerTemplates.js +4 -2
- package/src/ui/cameraControlsTemplate.js +4 -2
- package/src/ui/gridWalkTemplate.js +29 -0
- package/src/ui/htmlEscaping.js +8 -0
- package/src/ui/raceCanvasTemplate.js +3 -2
- package/src/ui/shellTemplate.js +3 -0
- package/src/ui/telemetryDrawerTemplate.js +4 -2
- package/src/ui/telemetryTemplates.js +3 -22
- package/src/ui/templateUtils.js +2 -8
- package/src/ui/viewControlsTemplate.js +21 -0
- package/examples/train-basic-policy.mjs +0 -276
- package/examples/trainingData.mjs +0 -133
|
@@ -2,31 +2,63 @@ import { clamp, normalizeAngle, wrapDistance } from '../simMath.js';
|
|
|
2
2
|
import { PIT_LANE_WIDTH } from './trackConstants.js';
|
|
3
3
|
import { pointInsideBounds } from './trackMath.js';
|
|
4
4
|
import { nearestPitLaneState, resolveDirectConnectorPitLaneState } from './pitLaneState.js';
|
|
5
|
-
import {
|
|
5
|
+
import { queryNearestTrackProjectionInto } from './trackQueryIndex.js';
|
|
6
6
|
import { recordStat } from './trackQueryStats.js';
|
|
7
7
|
|
|
8
8
|
export function pointAt(track, distanceAlong) {
|
|
9
|
+
return pointAtInto(track, distanceAlong, {});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function pointAtInto(track, distanceAlong, target) {
|
|
9
13
|
const wrapped = wrapDistance(distanceAlong, track.length);
|
|
10
|
-
const low =
|
|
14
|
+
const low = sampleIndexAtDistanceUnwrapped(track, wrapped);
|
|
11
15
|
|
|
12
16
|
const next = track.samples[low] ?? track.samples[0];
|
|
13
17
|
const previous = track.samples[Math.max(0, low - 1)] ?? next;
|
|
14
18
|
const span = Math.max(1, next.distance - previous.distance);
|
|
15
19
|
const amount = clamp((wrapped - previous.distance) / span, 0, 1);
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
21
|
+
target.x = previous.x + (next.x - previous.x) * amount;
|
|
22
|
+
target.y = previous.y + (next.y - previous.y) * amount;
|
|
23
|
+
target.heading = previous.heading + normalizeAngle(next.heading - previous.heading) * amount;
|
|
24
|
+
target.normalX = previous.normalX + (next.normalX - previous.normalX) * amount;
|
|
25
|
+
target.normalY = previous.normalY + (next.normalY - previous.normalY) * amount;
|
|
26
|
+
target.curvature = previous.curvature + (next.curvature - previous.curvature) * amount;
|
|
27
|
+
target.distance = wrapped;
|
|
28
|
+
return target;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
export function
|
|
31
|
+
export function sampleHeadingCurvatureAtInto(track, distanceAlong, target) {
|
|
29
32
|
const wrapped = wrapDistance(distanceAlong, track.length);
|
|
33
|
+
const low = sampleIndexAtDistanceUnwrapped(track, wrapped);
|
|
34
|
+
|
|
35
|
+
const next = track.samples[low] ?? track.samples[0];
|
|
36
|
+
const previous = track.samples[Math.max(0, low - 1)] ?? next;
|
|
37
|
+
const span = Math.max(1, next.distance - previous.distance);
|
|
38
|
+
const amount = clamp((wrapped - previous.distance) / span, 0, 1);
|
|
39
|
+
|
|
40
|
+
target.heading = previous.heading + normalizeAngle(next.heading - previous.heading) * amount;
|
|
41
|
+
target.curvature = previous.curvature + (next.curvature - previous.curvature) * amount;
|
|
42
|
+
return target;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function sampleHeadingAt(track, distanceAlong) {
|
|
46
|
+
const wrapped = wrapDistance(distanceAlong, track.length);
|
|
47
|
+
const low = sampleIndexAtDistanceUnwrapped(track, wrapped);
|
|
48
|
+
|
|
49
|
+
const next = track.samples[low] ?? track.samples[0];
|
|
50
|
+
const previous = track.samples[Math.max(0, low - 1)] ?? next;
|
|
51
|
+
const span = Math.max(1, next.distance - previous.distance);
|
|
52
|
+
const amount = clamp((wrapped - previous.distance) / span, 0, 1);
|
|
53
|
+
|
|
54
|
+
return previous.heading + normalizeAngle(next.heading - previous.heading) * amount;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function sampleIndexAtDistance(track, distanceAlong) {
|
|
58
|
+
return binarySampleIndexAtDistance(track, wrapDistance(distanceAlong, track.length));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function binarySampleIndexAtDistance(track, wrapped) {
|
|
30
62
|
let low = 0;
|
|
31
63
|
let high = track.samples.length - 1;
|
|
32
64
|
|
|
@@ -39,28 +71,41 @@ export function sampleIndexAtDistance(track, distanceAlong) {
|
|
|
39
71
|
return low;
|
|
40
72
|
}
|
|
41
73
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
74
|
+
// Uniform-bin index over sample distances: table[bin] holds the smallest
|
|
75
|
+
// sample index whose distance reaches the bin start, so a lookup plus a short
|
|
76
|
+
// forward walk returns exactly what the binary search would, in O(1). Built
|
|
77
|
+
// eagerly when the track query index is attached; lookups on sample arrays
|
|
78
|
+
// without a prepared table fall back to the binary search.
|
|
79
|
+
const SAMPLE_INDEX_LUTS = new WeakMap();
|
|
80
|
+
|
|
81
|
+
export function prepareSampleIndexLut(track) {
|
|
82
|
+
const samples = track?.samples;
|
|
83
|
+
if (!Array.isArray(samples) || samples.length < 2 || !Number.isFinite(track.length) || track.length <= 0) return;
|
|
84
|
+
if (SAMPLE_INDEX_LUTS.has(samples)) return;
|
|
85
|
+
const lastIndex = samples.length - 1;
|
|
86
|
+
const bins = Math.max(1, lastIndex * 2);
|
|
87
|
+
const table = new Int32Array(bins);
|
|
88
|
+
const binSize = track.length / bins;
|
|
89
|
+
let index = 0;
|
|
90
|
+
for (let bin = 0; bin < bins; bin += 1) {
|
|
91
|
+
const start = bin * binSize;
|
|
92
|
+
while (index < lastIndex && samples[index].distance < start) index += 1;
|
|
93
|
+
table[bin] = index;
|
|
57
94
|
}
|
|
58
|
-
|
|
59
|
-
return { best, bestDistance };
|
|
95
|
+
SAMPLE_INDEX_LUTS.set(samples, { bins, binSize, table });
|
|
60
96
|
}
|
|
61
97
|
|
|
62
|
-
|
|
63
|
-
|
|
98
|
+
function sampleIndexAtDistanceUnwrapped(track, wrapped) {
|
|
99
|
+
const samples = track.samples;
|
|
100
|
+
const lut = Number.isFinite(wrapped) ? SAMPLE_INDEX_LUTS.get(samples) : null;
|
|
101
|
+
if (!lut) return binarySampleIndexAtDistance(track, wrapped);
|
|
102
|
+
const lastIndex = samples.length - 1;
|
|
103
|
+
let bin = Math.floor(wrapped / lut.binSize);
|
|
104
|
+
if (bin < 0) bin = 0;
|
|
105
|
+
else if (bin >= lut.bins) bin = lut.bins - 1;
|
|
106
|
+
let index = lut.table[bin];
|
|
107
|
+
while (index < lastIndex && samples[index].distance < wrapped) index += 1;
|
|
108
|
+
return index;
|
|
64
109
|
}
|
|
65
110
|
|
|
66
111
|
export function createTrackState(track, position, best) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { CENTERLINE_CONTROLS, TRACK } from './trackConstants.js';
|
|
2
|
+
import { distance } from './trackMath.js';
|
|
3
|
+
import { rawCenterPoint } from './centerlineSpline.js';
|
|
4
|
+
import { recalculateSampleGeometry } from './sampleGeometry.js';
|
|
5
|
+
import { rotateSamplesToStandingStart, straightenStandingStartSamples } from './startStraight.js';
|
|
6
|
+
import { prepareSampleIndexLut } from './spatialQueries.js';
|
|
7
|
+
import { createTrackSectors } from './trackSectors.js';
|
|
8
|
+
import { deriveDrsZones, normalizeDrsZone } from './drsZones.js';
|
|
9
|
+
import { createPitLaneModel } from './pitLaneLayout.js';
|
|
10
|
+
import { attachTrackQueryIndex, createTrackQueryIndex } from './trackQueryConstruction.js';
|
|
11
|
+
import { attachTrackSnapshotJsonSerializer } from './trackSnapshotJson.js';
|
|
12
|
+
import { cacheTrackModel, getCachedTrackModel } from './trackModelCache.js';
|
|
13
|
+
|
|
14
|
+
export function buildTrackModel(track = TRACK) {
|
|
15
|
+
const cached = getCachedTrackModel(track);
|
|
16
|
+
if (cached) return cached;
|
|
17
|
+
|
|
18
|
+
const controls = track.centerlineControls ?? CENTERLINE_CONTROLS;
|
|
19
|
+
const base = [];
|
|
20
|
+
for (let index = 0; index <= track.sampleCount; index += 1) {
|
|
21
|
+
base.push(rawCenterPoint(index / track.sampleCount, controls, track.curveInterpolation));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let totalLength = 0;
|
|
25
|
+
const samples = base.map((point, index) => {
|
|
26
|
+
if (index > 0) totalLength += distance(base[index - 1], point);
|
|
27
|
+
return { ...point, distance: totalLength, heading: 0, normalX: 0, normalY: 0, curvature: 0 };
|
|
28
|
+
});
|
|
29
|
+
recalculateSampleGeometry(samples);
|
|
30
|
+
|
|
31
|
+
const startStraightOptions = track.generationOptions?.startStraight;
|
|
32
|
+
const rotatedSamples = rotateSamplesToStandingStart(samples, totalLength, startStraightOptions);
|
|
33
|
+
const straightenedTrack = straightenStandingStartSamples(rotatedSamples, totalLength, startStraightOptions);
|
|
34
|
+
const normalizedSamples = straightenedTrack.samples;
|
|
35
|
+
totalLength = straightenedTrack.totalLength;
|
|
36
|
+
|
|
37
|
+
const model = {
|
|
38
|
+
...track,
|
|
39
|
+
centerlineControls: controls,
|
|
40
|
+
length: totalLength,
|
|
41
|
+
samples: normalizedSamples,
|
|
42
|
+
sectors: createTrackSectors(totalLength),
|
|
43
|
+
drsZones: (track.drsZones ?? deriveDrsZones(normalizedSamples, totalLength))
|
|
44
|
+
.map((zone) => normalizeDrsZone(zone, totalLength)),
|
|
45
|
+
};
|
|
46
|
+
model.pitLane = track.pitLane?.enabled === false || track.generationOptions?.pitLane?.enabled === false
|
|
47
|
+
? null
|
|
48
|
+
: createPitLaneModel(model);
|
|
49
|
+
attachTrackQueryIndex(model, createTrackQueryIndex(model));
|
|
50
|
+
prepareSampleIndexLut(model);
|
|
51
|
+
attachTrackSnapshotJsonSerializer(model);
|
|
52
|
+
cacheTrackModel(track, model);
|
|
53
|
+
return model;
|
|
54
|
+
}
|
|
@@ -34,7 +34,6 @@ export const START_STRAIGHT_GRID_LENGTH = metersToSimUnits(496);
|
|
|
34
34
|
export const START_STRAIGHT_EXIT_LENGTH = metersToSimUnits(500);
|
|
35
35
|
export const START_STRAIGHT_LOCK_EXTRA = metersToSimUnits(43);
|
|
36
36
|
export const START_STRAIGHT_BLEND_LENGTH = metersToSimUnits(320);
|
|
37
|
-
export const NEAREST_HINT_WINDOW_SAMPLES = 240;
|
|
38
37
|
export const PIT_LANE_WIDTH = metersToSimUnits(12);
|
|
39
38
|
export const PIT_LANE_EDGE_GAP = metersToSimUnits(16);
|
|
40
39
|
export const PIT_ACCESS_MIN_LENGTH = metersToSimUnits(70);
|
|
@@ -66,27 +65,6 @@ export const PIT_LANE_FINISH_RATIO = 0.64;
|
|
|
66
65
|
export const PIT_WORLD_PADDING = metersToSimUnits(24);
|
|
67
66
|
export const PIT_TRACK_CLEARANCE_MARGIN = metersToSimUnits(5);
|
|
68
67
|
export const PIT_LANE_OFFSET_SEARCH_STEP = metersToSimUnits(12);
|
|
69
|
-
export const PROCEDURAL_TRACK_TEMPLATES = [
|
|
70
|
-
[
|
|
71
|
-
[0.08, 0.55], [0.10, 0.80], [0.22, 0.89], [0.42, 0.84], [0.52, 0.93],
|
|
72
|
-
[0.60, 0.75], [0.74, 0.88], [0.91, 0.76], [0.94, 0.54], [0.82, 0.46],
|
|
73
|
-
[0.94, 0.28], [0.78, 0.18], [0.62, 0.31], [0.54, 0.13], [0.43, 0.30],
|
|
74
|
-
[0.33, 0.17], [0.20, 0.24], [0.12, 0.38], [0.22, 0.48], [0.13, 0.50],
|
|
75
|
-
],
|
|
76
|
-
[
|
|
77
|
-
[0.07, 0.46], [0.14, 0.72], [0.25, 0.83], [0.39, 0.73], [0.47, 0.88],
|
|
78
|
-
[0.56, 0.70], [0.67, 0.82], [0.88, 0.84], [0.95, 0.63], [0.83, 0.56],
|
|
79
|
-
[0.92, 0.43], [0.79, 0.35], [0.88, 0.20], [0.68, 0.15], [0.58, 0.29],
|
|
80
|
-
[0.47, 0.18], [0.34, 0.30], [0.21, 0.19], [0.10, 0.28], [0.17, 0.39],
|
|
81
|
-
],
|
|
82
|
-
[
|
|
83
|
-
[0.06, 0.61], [0.13, 0.86], [0.31, 0.91], [0.43, 0.79], [0.57, 0.87],
|
|
84
|
-
[0.71, 0.70], [0.92, 0.72], [0.95, 0.50], [0.84, 0.43], [0.91, 0.32],
|
|
85
|
-
[0.74, 0.24], [0.69, 0.11], [0.54, 0.18], [0.45, 0.08], [0.35, 0.22],
|
|
86
|
-
[0.23, 0.17], [0.11, 0.31], [0.24, 0.43], [0.15, 0.52], [0.28, 0.60],
|
|
87
|
-
],
|
|
88
|
-
];
|
|
89
|
-
|
|
90
68
|
export const CENTERLINE_CONTROLS = [
|
|
91
69
|
{ x: WORLD.width * 0.05, y: WORLD.height * 0.56 },
|
|
92
70
|
{ x: WORLD.width * 0.10, y: WORLD.height * 0.81 },
|
|
@@ -161,35 +161,6 @@ export function pointInsideBounds(point, bounds) {
|
|
|
161
161
|
);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
export function projectPointToSegment(position, start, end) {
|
|
165
|
-
const dx = end.x - start.x;
|
|
166
|
-
const dy = end.y - start.y;
|
|
167
|
-
const lengthSquared = dx * dx + dy * dy;
|
|
168
|
-
const amount = lengthSquared > 0
|
|
169
|
-
? clamp(((position.x - start.x) * dx + (position.y - start.y) * dy) / lengthSquared, 0, 1)
|
|
170
|
-
: 0;
|
|
171
|
-
const projected = {
|
|
172
|
-
x: start.x + dx * amount,
|
|
173
|
-
y: start.y + dy * amount,
|
|
174
|
-
};
|
|
175
|
-
const length = Math.sqrt(lengthSquared);
|
|
176
|
-
const heading = Math.atan2(dy, dx);
|
|
177
|
-
const normalX = length > 0 ? -dy / length : 0;
|
|
178
|
-
const normalY = length > 0 ? dx / length : 1;
|
|
179
|
-
const signedOffset = (position.x - projected.x) * normalX + (position.y - projected.y) * normalY;
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
point: projected,
|
|
183
|
-
amount,
|
|
184
|
-
length,
|
|
185
|
-
heading,
|
|
186
|
-
normalX,
|
|
187
|
-
normalY,
|
|
188
|
-
signedOffset,
|
|
189
|
-
crossTrackError: Math.abs(signedOffset),
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
164
|
export function nearestPointOnPolyline(points, position, segmentIndices = null) {
|
|
194
165
|
return nearestPointOnPolylineInto({}, points, position, { segmentIndices });
|
|
195
166
|
}
|
|
@@ -306,3 +277,7 @@ export function pointIsInsidePolygon(point, polygon) {
|
|
|
306
277
|
|
|
307
278
|
return inside;
|
|
308
279
|
}
|
|
280
|
+
|
|
281
|
+
export function finitePoint(point) {
|
|
282
|
+
return Number.isFinite(point?.x) && Number.isFinite(point?.y);
|
|
283
|
+
}
|
|
@@ -1,242 +1,14 @@
|
|
|
1
|
-
import { normalizeAngle } from '../simMath.js';
|
|
2
|
-
import { CENTERLINE_CONTROLS, TRACK } from './trackConstants.js';
|
|
3
|
-
import { distance } from './trackMath.js';
|
|
4
|
-
import { rawCenterPoint } from './centerlineSpline.js';
|
|
5
|
-
import { generateCenterlineControls, generateFallbackCenterlineControls, generateSafeFallbackCenterlineControls, normalizeSeed } from './proceduralCenterline.js';
|
|
6
|
-
import { isValidProceduralTrackModel } from './trackValidation.js';
|
|
7
|
-
import { recalculateSampleGeometry } from './sampleGeometry.js';
|
|
8
|
-
import { rotateSamplesToStandingStart, straightenStandingStartSamples } from './startStraight.js';
|
|
9
|
-
import { createTrackSectors } from './trackSectors.js';
|
|
10
|
-
import { deriveDrsZones, normalizeDrsZone } from './drsZones.js';
|
|
11
|
-
import { createPitLaneModel } from './pitLaneLayout.js';
|
|
12
|
-
import { attachTrackQueryIndex, createTrackQueryIndex } from './trackQueryIndex.js';
|
|
13
|
-
import { resolveProceduralTrackOptions } from './trackGenerationOptions.js';
|
|
14
|
-
import { attachTrackSnapshotJsonSerializer } from './trackSnapshotJson.js';
|
|
15
|
-
|
|
16
1
|
export { WORLD, TRACK } from './trackConstants.js';
|
|
2
|
+
export { normalizeAngle } from '../simMath.js';
|
|
17
3
|
export { isInDrsZone } from './drsZones.js';
|
|
18
|
-
export {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const base = [];
|
|
30
|
-
for (let index = 0; index <= track.sampleCount; index += 1) {
|
|
31
|
-
base.push(rawCenterPoint(index / track.sampleCount, controls, track.curveInterpolation));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
let totalLength = 0;
|
|
35
|
-
const samples = base.map((point, index) => {
|
|
36
|
-
if (index > 0) totalLength += distance(base[index - 1], point);
|
|
37
|
-
return { ...point, distance: totalLength, heading: 0, normalX: 0, normalY: 0, curvature: 0 };
|
|
38
|
-
});
|
|
39
|
-
recalculateSampleGeometry(samples);
|
|
40
|
-
|
|
41
|
-
const startStraightOptions = track.generationOptions?.startStraight;
|
|
42
|
-
const rotatedSamples = rotateSamplesToStandingStart(samples, totalLength, startStraightOptions);
|
|
43
|
-
const straightenedTrack = straightenStandingStartSamples(rotatedSamples, totalLength, startStraightOptions);
|
|
44
|
-
const normalizedSamples = straightenedTrack.samples;
|
|
45
|
-
totalLength = straightenedTrack.totalLength;
|
|
46
|
-
|
|
47
|
-
const model = {
|
|
48
|
-
...track,
|
|
49
|
-
centerlineControls: controls,
|
|
50
|
-
length: totalLength,
|
|
51
|
-
samples: normalizedSamples,
|
|
52
|
-
sectors: createTrackSectors(totalLength),
|
|
53
|
-
drsZones: (track.drsZones ?? deriveDrsZones(normalizedSamples, totalLength))
|
|
54
|
-
.map((zone) => normalizeDrsZone(zone, totalLength)),
|
|
55
|
-
};
|
|
56
|
-
model.pitLane = track.pitLane?.enabled === false || track.generationOptions?.pitLane?.enabled === false
|
|
57
|
-
? null
|
|
58
|
-
: createPitLaneModel(model);
|
|
59
|
-
attachTrackQueryIndex(model, createTrackQueryIndex(model));
|
|
60
|
-
attachTrackSnapshotJsonSerializer(model);
|
|
61
|
-
if (canReuseCachedModel) {
|
|
62
|
-
TRACK_MODEL_CACHE.set(track, freezeTrackModel(model));
|
|
63
|
-
}
|
|
64
|
-
return model;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function createProceduralTrack(seed = Date.now(), options = {}) {
|
|
68
|
-
const normalizedSeed = normalizeSeed(seed);
|
|
69
|
-
const generationOptions = resolveProceduralTrackOptions(options);
|
|
70
|
-
const cacheKey = `${normalizedSeed}:${generationOptions.cacheKey}`;
|
|
71
|
-
const cached = PROCEDURAL_TRACK_CACHE.get(cacheKey);
|
|
72
|
-
if (cached) return cached;
|
|
73
|
-
|
|
74
|
-
for (let attempt = 0; attempt < generationOptions.attempts.primary; attempt += 1) {
|
|
75
|
-
const candidateSeed = (normalizedSeed + Math.imul(attempt, 2654435761)) >>> 0;
|
|
76
|
-
const candidate = {
|
|
77
|
-
...TRACK,
|
|
78
|
-
name: `Generated GP ${candidateSeed.toString(36).toUpperCase().padStart(6, '0').slice(-6)}`,
|
|
79
|
-
seed: candidateSeed,
|
|
80
|
-
curveInterpolation: 'centripetal',
|
|
81
|
-
centerlineControls: generateCenterlineControls(candidateSeed, generationOptions.shape),
|
|
82
|
-
drsZones: null,
|
|
83
|
-
generationOptions,
|
|
84
|
-
...(generationOptions.pitLane.enabled === false ? { pitLane: { enabled: false } } : {}),
|
|
85
|
-
};
|
|
86
|
-
const model = buildTrackModel(candidate);
|
|
87
|
-
if (isValidProceduralTrackModel(model, generationOptions)) {
|
|
88
|
-
const trackDefinition = {
|
|
89
|
-
...candidate,
|
|
90
|
-
drsZones: model.drsZones.map(({ id, startRatio, endRatio }) => ({ id, startRatio, endRatio })),
|
|
91
|
-
};
|
|
92
|
-
return cacheProceduralTrackDefinition(cacheKey, trackDefinition);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let fallback = null;
|
|
97
|
-
let fallbackModel = null;
|
|
98
|
-
for (let attempt = 0; attempt < generationOptions.attempts.fallback; attempt += 1) {
|
|
99
|
-
const candidateSeed = (normalizedSeed ^ Math.imul(attempt + 1, 2246822519)) >>> 0;
|
|
100
|
-
const candidate = {
|
|
101
|
-
...TRACK,
|
|
102
|
-
name: `Generated GP ${normalizedSeed.toString(36).toUpperCase().padStart(6, '0').slice(-6)}`,
|
|
103
|
-
seed: candidateSeed,
|
|
104
|
-
curveInterpolation: 'centripetal',
|
|
105
|
-
centerlineControls: generateFallbackCenterlineControls(candidateSeed, generationOptions.shape),
|
|
106
|
-
drsZones: null,
|
|
107
|
-
generationOptions,
|
|
108
|
-
...(generationOptions.pitLane.enabled === false ? { pitLane: { enabled: false } } : {}),
|
|
109
|
-
};
|
|
110
|
-
const model = buildTrackModel(candidate);
|
|
111
|
-
if (isValidProceduralTrackModel(model, generationOptions)) {
|
|
112
|
-
fallback = candidate;
|
|
113
|
-
fallbackModel = model;
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (!fallback || !fallbackModel) {
|
|
119
|
-
fallback = {
|
|
120
|
-
...TRACK,
|
|
121
|
-
name: `Generated GP ${normalizedSeed.toString(36).toUpperCase().padStart(6, '0').slice(-6)}`,
|
|
122
|
-
seed: normalizedSeed,
|
|
123
|
-
curveInterpolation: 'centripetal',
|
|
124
|
-
centerlineControls: generateSafeFallbackCenterlineControls(normalizedSeed, generationOptions.shape),
|
|
125
|
-
drsZones: null,
|
|
126
|
-
generationOptions,
|
|
127
|
-
...(generationOptions.pitLane.enabled === false ? { pitLane: { enabled: false } } : {}),
|
|
128
|
-
};
|
|
129
|
-
fallbackModel = buildTrackModel(fallback);
|
|
130
|
-
}
|
|
131
|
-
const fallbackDefinition = {
|
|
132
|
-
...fallback,
|
|
133
|
-
drsZones: fallbackModel.drsZones.map(({ id, startRatio, endRatio }) => ({ id, startRatio, endRatio })),
|
|
134
|
-
};
|
|
135
|
-
return cacheProceduralTrackDefinition(cacheKey, fallbackDefinition);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export { normalizeAngle };
|
|
139
|
-
|
|
140
|
-
function cacheProceduralTrackDefinition(cacheKey, trackDefinition) {
|
|
141
|
-
const frozen = deepFreeze(trackDefinition);
|
|
142
|
-
PROCEDURAL_TRACK_CACHE.set(cacheKey, frozen);
|
|
143
|
-
return frozen;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function freezeTrackModel(model) {
|
|
147
|
-
freezeArrayItems(model.samples);
|
|
148
|
-
freezeArrayItems(model.centerlineControls);
|
|
149
|
-
freezeArrayItems(model.sectors);
|
|
150
|
-
freezeArrayItems(model.drsZones);
|
|
151
|
-
deepFreeze(model.pitLane);
|
|
152
|
-
freezeTrackQueryIndex(model.queryIndex);
|
|
153
|
-
return Object.freeze(model);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function freezeArrayItems(items) {
|
|
157
|
-
if (!Array.isArray(items)) return;
|
|
158
|
-
items.forEach((item) => deepFreeze(item));
|
|
159
|
-
Object.freeze(items);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function deepFreeze(value) {
|
|
163
|
-
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
164
|
-
Object.freeze(value);
|
|
165
|
-
Object.values(value).forEach((child) => {
|
|
166
|
-
deepFreeze(child);
|
|
167
|
-
});
|
|
168
|
-
return value;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function freezeTrackQueryIndex(index) {
|
|
172
|
-
if (!index || typeof index !== 'object') return index;
|
|
173
|
-
deepFreeze(index.bands);
|
|
174
|
-
freezeCenterlineSegments(index.centerline);
|
|
175
|
-
freezeSpatialGrid(index.grid);
|
|
176
|
-
freezeSpatialGrid(index.segmentGrid);
|
|
177
|
-
freezeArcBuckets(index.arcBuckets);
|
|
178
|
-
freezePitQueryIndex(index.pit);
|
|
179
|
-
return Object.freeze(index);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function freezeCenterlineSegments(centerline) {
|
|
183
|
-
if (!centerline || typeof centerline !== 'object') return centerline;
|
|
184
|
-
Object.entries(centerline).forEach(([key, value]) => {
|
|
185
|
-
if (ArrayBuffer.isView(value)) {
|
|
186
|
-
centerline[key] = Object.freeze(Array.from(value));
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
return Object.freeze(centerline);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function freezeSpatialGrid(grid) {
|
|
193
|
-
if (!grid || typeof grid !== 'object') return grid;
|
|
194
|
-
deepFreeze(grid.bounds);
|
|
195
|
-
if (grid.cells instanceof Map) {
|
|
196
|
-
for (const cell of grid.cells.values()) Object.freeze(cell);
|
|
197
|
-
grid.cells = readonlyMap(grid.cells);
|
|
198
|
-
}
|
|
199
|
-
return Object.freeze(grid);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function freezeArcBuckets(arcBuckets) {
|
|
203
|
-
if (!arcBuckets || typeof arcBuckets !== 'object') return arcBuckets;
|
|
204
|
-
if (Array.isArray(arcBuckets.buckets)) {
|
|
205
|
-
arcBuckets.buckets.forEach((bucket) => Object.freeze(bucket));
|
|
206
|
-
Object.freeze(arcBuckets.buckets);
|
|
207
|
-
}
|
|
208
|
-
return Object.freeze(arcBuckets);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function freezePitQueryIndex(pit) {
|
|
212
|
-
if (!pit || typeof pit !== 'object') return pit;
|
|
213
|
-
freezeSpatialGrid(pit.roadGrid);
|
|
214
|
-
freezeSpatialGrid(pit.boxGrid);
|
|
215
|
-
deepFreeze(pit.routes);
|
|
216
|
-
freezeArrayItems(pit.roadSegments);
|
|
217
|
-
freezeArrayItems(pit.boxCandidates);
|
|
218
|
-
return Object.freeze(pit);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function readonlyMap(map) {
|
|
222
|
-
return new Proxy(map, {
|
|
223
|
-
get(target, property) {
|
|
224
|
-
if (property === 'set' || property === 'delete' || property === 'clear') {
|
|
225
|
-
return () => {
|
|
226
|
-
throw new TypeError('Cannot mutate a cached track query index');
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
const value = Reflect.get(target, property, target);
|
|
230
|
-
return typeof value === 'function' ? value.bind(target) : value;
|
|
231
|
-
},
|
|
232
|
-
set() {
|
|
233
|
-
throw new TypeError('Cannot mutate a cached track query index');
|
|
234
|
-
},
|
|
235
|
-
deleteProperty() {
|
|
236
|
-
throw new TypeError('Cannot mutate a cached track query index');
|
|
237
|
-
},
|
|
238
|
-
defineProperty() {
|
|
239
|
-
throw new TypeError('Cannot mutate a cached track query index');
|
|
240
|
-
},
|
|
241
|
-
});
|
|
242
|
-
}
|
|
4
|
+
export { buildTrackModel } from './trackBuilder.js';
|
|
5
|
+
export { createProceduralTrack } from './proceduralTrack.js';
|
|
6
|
+
export { PROCEDURAL_TRACK_CACHE_MAX_ENTRIES } from './trackModelCache.js';
|
|
7
|
+
export {
|
|
8
|
+
nearestTrackState,
|
|
9
|
+
offsetTrackPoint,
|
|
10
|
+
pointAt,
|
|
11
|
+
pointAtInto,
|
|
12
|
+
sampleHeadingAt,
|
|
13
|
+
sampleHeadingCurvatureAtInto,
|
|
14
|
+
} from './spatialQueries.js';
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { TRACK } from './trackConstants.js';
|
|
2
|
+
|
|
3
|
+
const PROCEDURAL_TRACK_CACHE = new Map();
|
|
4
|
+
const TRACK_MODEL_CACHE = new WeakMap();
|
|
5
|
+
export const PROCEDURAL_TRACK_CACHE_MAX_ENTRIES = 8;
|
|
6
|
+
|
|
7
|
+
export function getCachedTrackModel(track) {
|
|
8
|
+
return track === TRACK || Object.isFrozen(track) ? TRACK_MODEL_CACHE.get(track) : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function cacheTrackModel(track, model) {
|
|
12
|
+
if (track === TRACK || Object.isFrozen(track)) TRACK_MODEL_CACHE.set(track, freezeTrackModel(model));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getCachedProceduralTrack(cacheKey) {
|
|
16
|
+
const cached = PROCEDURAL_TRACK_CACHE.get(cacheKey);
|
|
17
|
+
if (!cached) return null;
|
|
18
|
+
PROCEDURAL_TRACK_CACHE.delete(cacheKey);
|
|
19
|
+
PROCEDURAL_TRACK_CACHE.set(cacheKey, cached);
|
|
20
|
+
return cached;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function cacheProceduralTrackDefinition(cacheKey, trackDefinition, model) {
|
|
24
|
+
const frozen = deepFreeze(trackDefinition);
|
|
25
|
+
if (model) TRACK_MODEL_CACHE.set(frozen, freezeTrackModel(model));
|
|
26
|
+
while (PROCEDURAL_TRACK_CACHE.size >= PROCEDURAL_TRACK_CACHE_MAX_ENTRIES) {
|
|
27
|
+
const oldestKey = PROCEDURAL_TRACK_CACHE.keys().next().value;
|
|
28
|
+
if (oldestKey == null) break;
|
|
29
|
+
PROCEDURAL_TRACK_CACHE.delete(oldestKey);
|
|
30
|
+
}
|
|
31
|
+
PROCEDURAL_TRACK_CACHE.set(cacheKey, frozen);
|
|
32
|
+
return frozen;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function freezeTrackModel(model) {
|
|
36
|
+
freezeArrayItems(model.samples);
|
|
37
|
+
freezeArrayItems(model.centerlineControls);
|
|
38
|
+
freezeArrayItems(model.sectors);
|
|
39
|
+
freezeArrayItems(model.drsZones);
|
|
40
|
+
deepFreeze(model.pitLane);
|
|
41
|
+
freezeTrackQueryIndex(model.queryIndex);
|
|
42
|
+
return Object.freeze(model);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function freezeArrayItems(items) {
|
|
46
|
+
if (!Array.isArray(items)) return;
|
|
47
|
+
items.forEach((item) => deepFreeze(item));
|
|
48
|
+
Object.freeze(items);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function deepFreeze(value) {
|
|
52
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
53
|
+
Object.freeze(value);
|
|
54
|
+
Object.values(value).forEach((child) => {
|
|
55
|
+
deepFreeze(child);
|
|
56
|
+
});
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function freezeTrackQueryIndex(index) {
|
|
61
|
+
if (!index || typeof index !== 'object') return index;
|
|
62
|
+
deepFreeze(index.bands);
|
|
63
|
+
freezeCenterlineSegments(index.centerline);
|
|
64
|
+
freezeSpatialGrid(index.grid);
|
|
65
|
+
freezeSpatialGrid(index.segmentGrid);
|
|
66
|
+
freezeArcBuckets(index.arcBuckets);
|
|
67
|
+
freezePitQueryIndex(index.pit);
|
|
68
|
+
return Object.freeze(index);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function freezeCenterlineSegments(centerline) {
|
|
72
|
+
if (!centerline || typeof centerline !== 'object') return centerline;
|
|
73
|
+
Object.entries(centerline).forEach(([key, value]) => {
|
|
74
|
+
if (ArrayBuffer.isView(value)) {
|
|
75
|
+
centerline[key] = Object.freeze(Array.from(value));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return Object.freeze(centerline);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function freezeSpatialGrid(grid) {
|
|
82
|
+
if (!grid || typeof grid !== 'object') return grid;
|
|
83
|
+
deepFreeze(grid.bounds);
|
|
84
|
+
freezeArrayItems(grid.cells);
|
|
85
|
+
return Object.freeze(grid);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function freezeArcBuckets(arcBuckets) {
|
|
89
|
+
if (!arcBuckets || typeof arcBuckets !== 'object') return arcBuckets;
|
|
90
|
+
if (Array.isArray(arcBuckets.buckets)) {
|
|
91
|
+
arcBuckets.buckets.forEach((bucket) => Object.freeze(bucket));
|
|
92
|
+
Object.freeze(arcBuckets.buckets);
|
|
93
|
+
}
|
|
94
|
+
return Object.freeze(arcBuckets);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function freezePitQueryIndex(pit) {
|
|
98
|
+
if (!pit || typeof pit !== 'object') return pit;
|
|
99
|
+
freezeSpatialGrid(pit.roadGrid);
|
|
100
|
+
freezeSpatialGrid(pit.boxGrid);
|
|
101
|
+
deepFreeze(pit.routes);
|
|
102
|
+
freezeArrayItems(pit.roadSegments);
|
|
103
|
+
freezeArrayItems(pit.boxCandidates);
|
|
104
|
+
return Object.freeze(pit);
|
|
105
|
+
}
|