@inventure71/paddockjs 1.0.0 → 2.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 +52 -0
- package/INSTALL_AND_UPDATE.md +4 -3
- package/README.md +194 -12
- package/docs/architecture.md +119 -44
- package/docs/concepts.md +4 -2
- package/docs/custom_model_controller.md +329 -0
- package/docs/data_contract.md +299 -17
- package/docs/index.md +1 -0
- package/docs/learnings.md +22 -0
- package/docs/rules.md +37 -10
- package/docs/system_specs.md +23 -10
- package/docs/training.md +272 -30
- package/examples/python/README.md +62 -0
- package/examples/python/base_policy_server.py +191 -0
- package/examples/python/requirements.txt +2 -0
- package/package.json +12 -4
- package/src/api/PaddockSimulatorController.js +4 -0
- package/src/app/BrowserExpertAdapter.js +367 -9
- package/src/app/F1SimulatorApp.js +305 -1603
- package/src/app/banners/bannerFormatters.js +55 -0
- package/src/app/banners/raceDataBannerController.js +141 -0
- package/src/app/banners/stewardMessageController.js +99 -0
- package/src/app/camera/cameraConstants.js +21 -0
- package/src/app/camera/cameraController.js +265 -0
- package/src/app/camera/cameraGeometry.js +176 -0
- package/src/app/domBindings.js +4 -0
- package/src/app/readouts/carOverviewRenderer.js +109 -0
- package/src/app/readouts/controlStateRenderer.js +37 -0
- package/src/app/readouts/raceStatusRenderer.js +133 -0
- package/src/app/readouts/readoutFormatters.js +58 -0
- package/src/app/readouts/telemetryRenderer.js +113 -0
- package/src/app/readouts/timingTowerRenderer.js +177 -0
- package/src/app/rendering/appAssets.js +38 -0
- package/src/app/rendering/carRenderer.js +286 -0
- package/src/app/rendering/displayUtils.js +22 -0
- package/src/app/rendering/drsTrailRenderer.js +90 -0
- package/src/app/rendering/expertSensorRenderer.js +126 -0
- package/src/app/rendering/pitLaneStatusRenderer.js +50 -0
- package/src/app/rendering/replayGhostRenderer.js +124 -0
- package/src/app/rendering/trackRenderer.js +25 -0
- package/src/app/runtime/frameLoop.js +66 -0
- package/src/app/runtime/runtimeTiming.js +34 -0
- package/src/app/runtime/runtimeVisibility.js +45 -0
- package/src/config/defaultOptions.js +20 -0
- package/src/environment/actions.js +13 -5
- package/src/environment/controllerLoop.js +246 -0
- package/src/environment/episode.js +86 -2
- package/src/environment/evaluation.js +54 -32
- package/src/environment/index.d.ts +361 -9
- package/src/environment/index.js +1 -0
- package/src/environment/metrics.js +159 -0
- package/src/environment/observationOptions.js +22 -1
- package/src/environment/observations.js +325 -40
- package/src/environment/options.js +37 -5
- package/src/environment/runtime.js +222 -21
- package/src/environment/scenarios.js +37 -8
- package/src/environment/sensors/bodySenses.js +23 -0
- package/src/environment/sensors/boundarySenses.js +17 -0
- package/src/environment/sensors/carRays.js +75 -0
- package/src/environment/sensors/contactSenses.js +32 -0
- package/src/environment/sensors/index.js +157 -0
- package/src/environment/sensors/indexedRayBands.js +103 -0
- package/src/environment/sensors/nearbyCars.js +52 -0
- package/src/environment/sensors/opponentRadar.js +65 -0
- package/src/environment/sensors/rayConfig.js +110 -0
- package/src/environment/sensors/rayDefaults.js +6 -0
- package/src/environment/sensors/rayGeometry.js +60 -0
- package/src/environment/sensors/rayGuards.js +19 -0
- package/src/environment/sensors/sensorTargets.js +60 -0
- package/src/environment/sensors/surfaceRays.js +296 -0
- package/src/environment/sensors/trackRays.js +261 -0
- package/src/environment/sensors.js +1 -367
- package/src/environment/specs.js +100 -5
- package/src/environment/workerProtocol.js +13 -1
- package/src/index.d.ts +169 -6
- package/src/index.js +5 -0
- package/src/rendering/proceduralTrackAsset.js +20 -823
- package/src/rendering/renderSnapshot.js +38 -12
- package/src/rendering/track/finishLineRenderer.js +34 -0
- package/src/rendering/track/gridRenderer.js +47 -0
- package/src/rendering/track/offsetStrokeSafety.js +136 -0
- package/src/rendering/track/pitLaneRenderer.js +248 -0
- package/src/rendering/track/trackMaterialRenderer.js +326 -0
- package/src/rendering/track/trackRenderConstants.js +38 -0
- package/src/rendering/track/trackRenderGeometry.js +94 -0
- package/src/simulation/collisionGeometry.js +1 -1
- package/src/simulation/driver/driverControlConstants.js +28 -0
- package/src/simulation/driver/driverController.js +47 -0
- package/src/simulation/driver/driverInput.js +28 -0
- package/src/simulation/driver/driverMath.js +6 -0
- package/src/simulation/driver/driverPersonality.js +25 -0
- package/src/simulation/driver/edgeRecovery.js +48 -0
- package/src/simulation/driver/racingControls.js +399 -0
- package/src/simulation/driver/racingLinePlan.js +166 -0
- package/src/simulation/driver/recoveryDynamics.js +38 -0
- package/src/simulation/driver/rejoinControls.js +213 -0
- package/src/simulation/driver/safetyCarControls.js +35 -0
- package/src/simulation/driver/trafficScan.js +51 -0
- package/src/simulation/driverController.js +4 -619
- package/src/simulation/participants/participantInteractions.js +120 -0
- package/src/simulation/pit/pitFlow.js +234 -0
- package/src/simulation/pit/pitIntent.js +92 -0
- package/src/simulation/pit/pitOccupancy.js +47 -0
- package/src/simulation/pit/pitPenaltyService.js +71 -0
- package/src/simulation/pit/pitQueue.js +55 -0
- package/src/simulation/pit/pitRouteMovement.js +160 -0
- package/src/simulation/pit/pitRouting.js +319 -0
- package/src/simulation/pit/pitService.js +41 -0
- package/src/simulation/pit/pitServiceConstants.js +21 -0
- package/src/simulation/pit/pitServiceProfile.js +45 -0
- package/src/simulation/pit/pitSnapshots.js +61 -0
- package/src/simulation/pit/pitState.js +113 -0
- package/src/simulation/pit/pitTireService.js +92 -0
- package/src/simulation/race/classification.js +104 -0
- package/src/simulation/race/gridPenalties.js +37 -0
- package/src/simulation/race/raceControlState.js +38 -0
- package/src/simulation/race/raceDistance.js +32 -0
- package/src/simulation/race/raceFinish.js +65 -0
- package/src/simulation/race/raceLifecycle.js +71 -0
- package/src/simulation/race/raceOrder.js +113 -0
- package/src/simulation/race/raceProgress.js +124 -0
- package/src/simulation/race/raceSetup.js +93 -0
- package/src/simulation/race/raceStep.js +80 -0
- package/src/simulation/race/redFlag.js +15 -0
- package/src/simulation/race/retirements.js +44 -0
- package/src/simulation/race/safetyCar.js +86 -0
- package/src/simulation/race/stalledDnf.js +86 -0
- package/src/simulation/raceSimulation.js +230 -2817
- package/src/simulation/replay/replayGhosts.js +128 -0
- package/src/simulation/rules/moduleConfig.js +72 -0
- package/src/simulation/rules/penaltyConfig.js +99 -0
- package/src/simulation/rules/penaltyEvents.js +18 -0
- package/src/simulation/rules/penaltyStats.js +26 -0
- package/src/simulation/rules/ruleConfigMerge.js +41 -0
- package/src/simulation/rules/ruleDefaults.js +85 -0
- package/src/simulation/rules/rulesReview.js +141 -0
- package/src/simulation/rules/rulesetPresets.js +47 -0
- package/src/simulation/rules/trackLimitsSteward.js +1 -1
- package/src/simulation/rulesConfig.js +5 -323
- package/src/simulation/snapshots/raceSnapshots.js +192 -0
- package/src/simulation/timing/drsTiming.js +87 -0
- package/src/simulation/timing/gapEstimation.js +38 -0
- package/src/simulation/timing/lapTelemetry.js +182 -0
- package/src/simulation/timing/raceTiming.js +15 -0
- package/src/simulation/timing/sectorPerformance.js +60 -0
- package/src/simulation/timing/timingConstants.js +5 -0
- package/src/simulation/timing/timingHistory.js +46 -0
- package/src/simulation/timing/timingLines.js +79 -0
- package/src/simulation/track/centerlineSpline.js +59 -0
- package/src/simulation/track/drsZones.js +67 -0
- package/src/simulation/track/pitLaneAccess.js +103 -0
- package/src/simulation/track/pitLaneLayout.js +395 -0
- package/src/simulation/track/pitLaneState.js +160 -0
- package/src/simulation/track/proceduralCenterline.js +237 -0
- package/src/simulation/track/sampleGeometry.js +49 -0
- package/src/simulation/track/spatialQueries.js +154 -0
- package/src/simulation/track/startStraight.js +141 -0
- package/src/simulation/track/trackConstants.js +109 -0
- package/src/simulation/track/trackGenerationOptions.js +283 -0
- package/src/simulation/track/trackMath.js +241 -0
- package/src/simulation/track/trackModel.js +240 -0
- package/src/simulation/track/trackQueryIndex.js +945 -0
- package/src/simulation/track/trackSectors.js +19 -0
- package/src/simulation/track/trackStatePolicy.js +24 -0
- package/src/simulation/track/trackValidation.js +98 -0
- package/src/simulation/trackModel.js +1 -1706
- package/src/simulation/vehicle/contactResolution.js +116 -0
- package/src/simulation/vehicle/mainTrackWheelSurface.js +82 -0
- package/src/simulation/vehicle/pitWheelSurface.js +69 -0
- package/src/simulation/vehicle/runoffResponse.js +88 -0
- package/src/simulation/vehicle/surfacePriority.js +28 -0
- package/src/simulation/vehicle/vehicleGeometry.js +247 -0
- package/src/simulation/vehicle/vehiclePhysics.js +363 -0
- package/src/simulation/vehicle/vehicleSnapshots.js +316 -0
- package/src/simulation/vehicle/vehicleState.js +186 -0
- package/src/simulation/vehicle/wheelSurface.js +150 -0
- package/src/simulation/vehicleGeometry.js +1 -247
- package/src/simulation/vehiclePhysics.js +1 -136
- package/src/simulation/warmup/runtimeWarmup.js +224 -0
- package/src/simulation/wheelSurface.js +1 -338
- package/src/styles.css +42 -0
- package/src/ui/cameraControlsTemplate.js +20 -0
- package/src/ui/carOverviewTemplate.js +33 -0
- package/src/ui/componentTemplates.js +14 -360
- package/src/ui/raceCanvasTemplate.js +69 -0
- package/src/ui/raceControlsTemplate.js +36 -0
- package/src/ui/shellTemplate.js +5 -4
- package/src/ui/telemetryDrawerTemplate.js +55 -0
- package/src/ui/telemetryTemplates.js +136 -0
- package/src/ui/timingTowerTemplate.js +34 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { clamp, createMulberry32, seededRange } from '../simMath.js';
|
|
2
|
+
import { CENTERLINE_CONTROLS, TRACK_BOUNDARY_PADDING, WORLD, MIN_TRACK_SHAPE_VARIATION } from './trackConstants.js';
|
|
3
|
+
import { distance } from './trackMath.js';
|
|
4
|
+
|
|
5
|
+
export function normalizeSeed(seed) {
|
|
6
|
+
if (Number.isFinite(seed)) return seed >>> 0;
|
|
7
|
+
let hash = 2166136261;
|
|
8
|
+
String(seed ?? 'f1-track').split('').forEach((character) => {
|
|
9
|
+
hash ^= character.charCodeAt(0);
|
|
10
|
+
hash = Math.imul(hash, 16777619);
|
|
11
|
+
});
|
|
12
|
+
return hash >>> 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function rotateControls(controls, random) {
|
|
16
|
+
const rotation = Math.floor(seededRange(random, 0, controls.length));
|
|
17
|
+
return [...controls.slice(rotation), ...controls.slice(0, rotation)];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function cellKey(x, y) {
|
|
21
|
+
return `${x},${y}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function randomInteger(random, min, max) {
|
|
25
|
+
return Math.floor(seededRange(random, min, max + 1));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function clampInteger(value, min, max) {
|
|
29
|
+
return Math.round(clamp(value, min, max));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createRandomRegionTemplate(seed, options = {}) {
|
|
33
|
+
const random = createMulberry32(seed);
|
|
34
|
+
const cornerDensity = Number.isFinite(options.cornerDensity) ? Math.max(0.5, options.cornerDensity) : 1;
|
|
35
|
+
const extraColumns = Math.round((cornerDensity - 1) * 4);
|
|
36
|
+
const extraFeatures = Math.round((cornerDensity - 1) * 3);
|
|
37
|
+
const columns = randomInteger(random, options.minColumns ?? 10, options.maxColumns ?? (14 + extraColumns));
|
|
38
|
+
const rows = randomInteger(random, options.minRows ?? 7, options.maxRows ?? (8 + Math.max(0, Math.round(extraColumns / 2))));
|
|
39
|
+
const top = new Array(columns).fill(1);
|
|
40
|
+
const bottom = new Array(columns).fill(rows - 2);
|
|
41
|
+
let topCursor = randomInteger(random, 1, 2);
|
|
42
|
+
let bottomCursor = rows - randomInteger(random, 2, 3);
|
|
43
|
+
|
|
44
|
+
for (let column = 1; column < columns - 1; column += 1) {
|
|
45
|
+
topCursor += randomInteger(random, -1, 1);
|
|
46
|
+
bottomCursor += randomInteger(random, -1, 1);
|
|
47
|
+
topCursor = clampInteger(topCursor, 1, rows - 4);
|
|
48
|
+
bottomCursor = clampInteger(bottomCursor, topCursor + 2, rows - 2);
|
|
49
|
+
top[column] = topCursor;
|
|
50
|
+
bottom[column] = bottomCursor;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const featureCount = randomInteger(random, options.minFeatures ?? 4, options.maxFeatures ?? (6 + extraFeatures));
|
|
54
|
+
for (let feature = 0; feature < featureCount; feature += 1) {
|
|
55
|
+
const column = randomInteger(random, 2, columns - 3);
|
|
56
|
+
const width = randomInteger(random, 1, 2);
|
|
57
|
+
const carvesTop = random() < 0.5;
|
|
58
|
+
const amount = random() < 0.76 ? 1 : 2;
|
|
59
|
+
|
|
60
|
+
for (let offset = 0; offset < width; offset += 1) {
|
|
61
|
+
const target = clampInteger(column + offset, 1, columns - 2);
|
|
62
|
+
if (carvesTop) {
|
|
63
|
+
top[target] = clampInteger(top[target] + amount, 1, bottom[target] - 2);
|
|
64
|
+
top[target - 1] = clampInteger(top[target - 1] - 1, 1, bottom[target - 1] - 2);
|
|
65
|
+
} else {
|
|
66
|
+
bottom[target] = clampInteger(bottom[target] - amount, top[target] + 2, rows - 2);
|
|
67
|
+
bottom[target - 1] = clampInteger(bottom[target - 1] + 1, top[target - 1] + 2, rows - 2);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const cells = [];
|
|
73
|
+
for (let column = 1; column < columns - 1; column += 1) {
|
|
74
|
+
for (let row = top[column]; row <= bottom[column]; row += 1) {
|
|
75
|
+
cells.push([column, row]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { columns, rows, cells };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function traceRegionBoundary(template) {
|
|
83
|
+
const selected = new Set(template.cells.map(([x, y]) => cellKey(x, y)));
|
|
84
|
+
const edges = [];
|
|
85
|
+
|
|
86
|
+
template.cells.forEach(([x, y]) => {
|
|
87
|
+
if (!selected.has(cellKey(x, y - 1))) edges.push([[x, y], [x + 1, y]]);
|
|
88
|
+
if (!selected.has(cellKey(x + 1, y))) edges.push([[x + 1, y], [x + 1, y + 1]]);
|
|
89
|
+
if (!selected.has(cellKey(x, y + 1))) edges.push([[x + 1, y + 1], [x, y + 1]]);
|
|
90
|
+
if (!selected.has(cellKey(x - 1, y))) edges.push([[x, y + 1], [x, y]]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const outgoing = new Map();
|
|
94
|
+
edges.forEach(([start, end]) => {
|
|
95
|
+
const key = cellKey(start[0], start[1]);
|
|
96
|
+
if (!outgoing.has(key)) outgoing.set(key, []);
|
|
97
|
+
outgoing.get(key).push(end);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const start = edges[0][0];
|
|
101
|
+
let current = start;
|
|
102
|
+
const boundary = [start];
|
|
103
|
+
|
|
104
|
+
for (let index = 0; index < edges.length; index += 1) {
|
|
105
|
+
const next = outgoing.get(cellKey(current[0], current[1]))?.shift();
|
|
106
|
+
if (!next) break;
|
|
107
|
+
current = next;
|
|
108
|
+
if (current[0] === start[0] && current[1] === start[1]) return boundary;
|
|
109
|
+
boundary.push(current);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return boundary;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function smoothRegionBoundary(points, amount = 0.34, rounds = 2) {
|
|
116
|
+
let smoothed = points;
|
|
117
|
+
for (let round = 0; round < rounds; round += 1) {
|
|
118
|
+
const next = [];
|
|
119
|
+
smoothed.forEach((point, index) => {
|
|
120
|
+
const following = smoothed[(index + 1) % smoothed.length];
|
|
121
|
+
next.push({
|
|
122
|
+
x: point.x + (following.x - point.x) * amount,
|
|
123
|
+
y: point.y + (following.y - point.y) * amount,
|
|
124
|
+
});
|
|
125
|
+
next.push({
|
|
126
|
+
x: point.x + (following.x - point.x) * (1 - amount),
|
|
127
|
+
y: point.y + (following.y - point.y) * (1 - amount),
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
smoothed = next;
|
|
131
|
+
}
|
|
132
|
+
return smoothed;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function createRegionTransform(random, options = {}) {
|
|
136
|
+
const scale = Number.isFinite(options.scale) ? Math.max(0.05, options.scale) : 1;
|
|
137
|
+
return {
|
|
138
|
+
centerX: 0.5 + seededRange(random, -0.015, 0.015),
|
|
139
|
+
centerY: 0.5 + seededRange(random, -0.015, 0.015),
|
|
140
|
+
scaleX: seededRange(random, 0.9, 0.98) * scale,
|
|
141
|
+
scaleY: seededRange(random, 0.9, 0.98) * scale,
|
|
142
|
+
phaseX: seededRange(random, 0, Math.PI * 2),
|
|
143
|
+
phaseY: seededRange(random, 0, Math.PI * 2),
|
|
144
|
+
warpX: seededRange(random, 0.018, 0.044),
|
|
145
|
+
warpY: seededRange(random, 0.018, 0.052),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function regionPointToWorld([x, y], template, transform) {
|
|
150
|
+
const usableWidth = WORLD.width - TRACK_BOUNDARY_PADDING * 2;
|
|
151
|
+
const usableHeight = WORLD.height - TRACK_BOUNDARY_PADDING * 2;
|
|
152
|
+
const normalizedX = x / template.columns;
|
|
153
|
+
const normalizedY = y / template.rows;
|
|
154
|
+
const radialX = normalizedX - 0.5;
|
|
155
|
+
const radialY = normalizedY - 0.5;
|
|
156
|
+
const edgeInfluence = clamp((Math.abs(radialX) + Math.abs(radialY) - 0.22) / 0.36, 0, 1);
|
|
157
|
+
const warpX = Math.sin(normalizedY * Math.PI * 4.4 + transform.phaseX) * transform.warpX * edgeInfluence;
|
|
158
|
+
const warpY = Math.sin(normalizedX * Math.PI * 4.2 + transform.phaseY) * transform.warpY * edgeInfluence;
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
x: TRACK_BOUNDARY_PADDING + clamp(
|
|
162
|
+
transform.centerX + radialX * transform.scaleX + warpX,
|
|
163
|
+
0.04,
|
|
164
|
+
0.96,
|
|
165
|
+
) * usableWidth,
|
|
166
|
+
y: TRACK_BOUNDARY_PADDING + clamp(
|
|
167
|
+
transform.centerY + radialY * transform.scaleY + warpY,
|
|
168
|
+
0.06,
|
|
169
|
+
0.94,
|
|
170
|
+
) * usableHeight,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function generateRegionCenterlineControls(seed, options = {}) {
|
|
175
|
+
const normalizedSeed = normalizeSeed(seed);
|
|
176
|
+
const random = createMulberry32(normalizedSeed);
|
|
177
|
+
const template = options.fallback
|
|
178
|
+
? createRandomRegionTemplate(normalizedSeed, {
|
|
179
|
+
minColumns: 9,
|
|
180
|
+
maxColumns: 13,
|
|
181
|
+
minRows: 6,
|
|
182
|
+
maxRows: 8,
|
|
183
|
+
minFeatures: 3,
|
|
184
|
+
maxFeatures: 5,
|
|
185
|
+
cornerDensity: options.cornerDensity,
|
|
186
|
+
})
|
|
187
|
+
: createRandomRegionTemplate(normalizedSeed, { cornerDensity: options.cornerDensity });
|
|
188
|
+
const transform = createRegionTransform(random, options);
|
|
189
|
+
const boundary = traceRegionBoundary(template).map((point) => regionPointToWorld(point, template, transform));
|
|
190
|
+
return rotateControls(smoothRegionBoundary(boundary), random);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function strengthenShapeVariation(controls, minimumVariation = MIN_TRACK_SHAPE_VARIATION) {
|
|
194
|
+
const center = { x: WORLD.width / 2, y: WORLD.height / 2 };
|
|
195
|
+
const radii = controls.map((point) => distance(point, center));
|
|
196
|
+
const mean = radii.reduce((total, radius) => total + radius, 0) / Math.max(1, radii.length);
|
|
197
|
+
const variation = mean > 0
|
|
198
|
+
? Math.sqrt(radii.reduce((total, radius) => total + (radius - mean) ** 2, 0) / Math.max(1, radii.length)) / mean
|
|
199
|
+
: 0;
|
|
200
|
+
if (variation > minimumVariation + 0.015) return controls;
|
|
201
|
+
|
|
202
|
+
return controls.map((point) => {
|
|
203
|
+
const radius = distance(point, center);
|
|
204
|
+
if (radius <= 0) return point;
|
|
205
|
+
const amount = mean + (radius - mean) * 1.18;
|
|
206
|
+
const scale = amount / radius;
|
|
207
|
+
return {
|
|
208
|
+
x: clamp(center.x + (point.x - center.x) * scale, TRACK_BOUNDARY_PADDING, WORLD.width - TRACK_BOUNDARY_PADDING),
|
|
209
|
+
y: clamp(center.y + (point.y - center.y) * scale, TRACK_BOUNDARY_PADDING, WORLD.height - TRACK_BOUNDARY_PADDING),
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function generateCenterlineControls(seed, options = {}) {
|
|
215
|
+
return strengthenShapeVariation(generateRegionCenterlineControls(seed, options), options.variation);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function generateFallbackCenterlineControls(seed, options = {}) {
|
|
219
|
+
return strengthenShapeVariation(
|
|
220
|
+
generateRegionCenterlineControls(seed ^ 0xa5a5a5a5, { ...options, fallback: true }),
|
|
221
|
+
options.variation,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function generateSafeFallbackCenterlineControls(seed, options = {}) {
|
|
226
|
+
const center = { x: WORLD.width / 2, y: WORLD.height / 2 };
|
|
227
|
+
const scale = (0.78 + ((seed >>> 3) % 4) * 0.02) *
|
|
228
|
+
(Number.isFinite(options.scale) ? Math.max(0.05, options.scale) : 1);
|
|
229
|
+
const mirrorX = (seed & 1) === 1 ? -1 : 1;
|
|
230
|
+
const mirrorY = (seed & 2) === 2 ? -1 : 1;
|
|
231
|
+
const controls = CENTERLINE_CONTROLS.map((point) => ({
|
|
232
|
+
x: center.x + (point.x - center.x) * scale * mirrorX,
|
|
233
|
+
y: center.y + (point.y - center.y) * scale * mirrorY,
|
|
234
|
+
}));
|
|
235
|
+
|
|
236
|
+
return rotateControls(controls, createMulberry32(seed ^ 0x9e3779b9));
|
|
237
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { normalizeAngle } from '../simMath.js';
|
|
2
|
+
import { distance } from './trackMath.js';
|
|
3
|
+
|
|
4
|
+
export function recalculateSampleGeometry(samples) {
|
|
5
|
+
const usableCount = samples.length - 1;
|
|
6
|
+
for (let index = 0; index < usableCount; index += 1) {
|
|
7
|
+
const sample = samples[index];
|
|
8
|
+
const previous = samples[(index - 1 + usableCount) % usableCount];
|
|
9
|
+
const next = samples[(index + 1) % usableCount];
|
|
10
|
+
const nextNext = samples[(index + 2) % usableCount];
|
|
11
|
+
const heading = Math.atan2(next.y - previous.y, next.x - previous.x);
|
|
12
|
+
const nextHeading = Math.atan2(nextNext.y - sample.y, nextNext.x - sample.x);
|
|
13
|
+
sample.heading = heading;
|
|
14
|
+
sample.normalX = -Math.sin(heading);
|
|
15
|
+
sample.normalY = Math.cos(heading);
|
|
16
|
+
sample.curvature = Math.abs(normalizeAngle(nextHeading - heading)) / 28;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
samples[usableCount] = {
|
|
20
|
+
...samples[0],
|
|
21
|
+
distance: samples[usableCount].distance,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return samples;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function rebuildSampleDistances(samples) {
|
|
28
|
+
const usable = samples.slice(0, -1).map((sample) => ({ ...sample }));
|
|
29
|
+
let rebuiltLength = 0;
|
|
30
|
+
|
|
31
|
+
usable.forEach((sample, index) => {
|
|
32
|
+
if (index > 0) rebuiltLength += distance(usable[index - 1], sample);
|
|
33
|
+
sample.distance = rebuiltLength;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
rebuiltLength += distance(usable.at(-1), usable[0]);
|
|
37
|
+
const rebuilt = [
|
|
38
|
+
...usable,
|
|
39
|
+
{
|
|
40
|
+
...usable[0],
|
|
41
|
+
distance: rebuiltLength,
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
samples: recalculateSampleGeometry(rebuilt),
|
|
47
|
+
totalLength: rebuiltLength,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { clamp, normalizeAngle, wrapDistance } from '../simMath.js';
|
|
2
|
+
import { NEAREST_HINT_WINDOW_SAMPLES, PIT_LANE_WIDTH } from './trackConstants.js';
|
|
3
|
+
import { nearestPitLaneState } from './pitLaneState.js';
|
|
4
|
+
import { queryNearestTrackProjection } from './trackQueryIndex.js';
|
|
5
|
+
|
|
6
|
+
export function pointAt(track, distanceAlong) {
|
|
7
|
+
const wrapped = wrapDistance(distanceAlong, track.length);
|
|
8
|
+
const low = sampleIndexAtDistance(track, wrapped);
|
|
9
|
+
|
|
10
|
+
const next = track.samples[low] ?? track.samples[0];
|
|
11
|
+
const previous = track.samples[Math.max(0, low - 1)] ?? next;
|
|
12
|
+
const span = Math.max(1, next.distance - previous.distance);
|
|
13
|
+
const amount = clamp((wrapped - previous.distance) / span, 0, 1);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
x: previous.x + (next.x - previous.x) * amount,
|
|
17
|
+
y: previous.y + (next.y - previous.y) * amount,
|
|
18
|
+
heading: previous.heading + normalizeAngle(next.heading - previous.heading) * amount,
|
|
19
|
+
normalX: previous.normalX + (next.normalX - previous.normalX) * amount,
|
|
20
|
+
normalY: previous.normalY + (next.normalY - previous.normalY) * amount,
|
|
21
|
+
curvature: previous.curvature + (next.curvature - previous.curvature) * amount,
|
|
22
|
+
distance: wrapped,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function sampleIndexAtDistance(track, distanceAlong) {
|
|
27
|
+
const wrapped = wrapDistance(distanceAlong, track.length);
|
|
28
|
+
let low = 0;
|
|
29
|
+
let high = track.samples.length - 1;
|
|
30
|
+
|
|
31
|
+
while (low < high) {
|
|
32
|
+
const mid = Math.floor((low + high) / 2);
|
|
33
|
+
if (track.samples[mid].distance < wrapped) low = mid + 1;
|
|
34
|
+
else high = mid;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return low;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function nearestSampleInRange(track, position, startIndex, endIndex) {
|
|
41
|
+
const sampleCount = track.samples.length - 1;
|
|
42
|
+
let best = null;
|
|
43
|
+
let bestDistance = Infinity;
|
|
44
|
+
|
|
45
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
46
|
+
const wrappedIndex = ((index % sampleCount) + sampleCount) % sampleCount;
|
|
47
|
+
const sample = track.samples[wrappedIndex];
|
|
48
|
+
const dx = position.x - sample.x;
|
|
49
|
+
const dy = position.y - sample.y;
|
|
50
|
+
const squared = dx * dx + dy * dy;
|
|
51
|
+
if (squared < bestDistance) {
|
|
52
|
+
bestDistance = squared;
|
|
53
|
+
best = sample;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { best, bestDistance };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function nearestSampleGlobal(track, position) {
|
|
61
|
+
return nearestSampleInRange(track, position, 0, track.samples.length - 2);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function createTrackState(track, position, best) {
|
|
65
|
+
const dx = position.x - best.x;
|
|
66
|
+
const dy = position.y - best.y;
|
|
67
|
+
const signedOffset = dx * best.normalX + dy * best.normalY;
|
|
68
|
+
const crossTrackError = Math.abs(signedOffset);
|
|
69
|
+
const trackEdge = track.width / 2;
|
|
70
|
+
const kerbEdge = trackEdge + (track.kerbWidth ?? 0);
|
|
71
|
+
const gravelEdge = kerbEdge + track.gravelWidth;
|
|
72
|
+
const runoffEdge = gravelEdge + track.runoffWidth;
|
|
73
|
+
const surface = crossTrackError <= trackEdge
|
|
74
|
+
? 'track'
|
|
75
|
+
: crossTrackError <= kerbEdge
|
|
76
|
+
? 'kerb'
|
|
77
|
+
: crossTrackError <= gravelEdge
|
|
78
|
+
? 'gravel'
|
|
79
|
+
: crossTrackError <= runoffEdge
|
|
80
|
+
? 'grass'
|
|
81
|
+
: 'barrier';
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
...best,
|
|
85
|
+
signedOffset,
|
|
86
|
+
crossTrackError,
|
|
87
|
+
surface,
|
|
88
|
+
onTrack: surface === 'track' || surface === 'kerb',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function nearestTrackState(track, position, progressHint = null, options = {}) {
|
|
93
|
+
const allowPitOverride = options.allowPitOverride !== false;
|
|
94
|
+
const best = options.indexMode === 'legacy'
|
|
95
|
+
? nearestTrackSampleLegacy(track, position, progressHint)
|
|
96
|
+
: queryNearestTrackProjection(track, position, progressHint, {
|
|
97
|
+
indexMode: options.indexMode,
|
|
98
|
+
hintMaxDistance: options.hintMaxDistance,
|
|
99
|
+
}) ??
|
|
100
|
+
nearestTrackSampleLegacy(track, position, progressHint);
|
|
101
|
+
const trackState = createTrackState(track, position, best);
|
|
102
|
+
if (!allowPitOverride) return trackState;
|
|
103
|
+
const pitState = nearestPitLaneState(track, position);
|
|
104
|
+
if (!pitState) return trackState;
|
|
105
|
+
|
|
106
|
+
const mainRoadEdge = track.width / 2 + (track.kerbWidth ?? 0);
|
|
107
|
+
if (trackState.crossTrackError <= mainRoadEdge && pitState.surface !== 'pit-box') return trackState;
|
|
108
|
+
|
|
109
|
+
const mergeBuffer = PIT_LANE_WIDTH * 0.16;
|
|
110
|
+
const isMainTrackMerge =
|
|
111
|
+
trackState.surface === 'track' &&
|
|
112
|
+
(
|
|
113
|
+
(pitState.surface === 'pit-entry' && pitState.pitLaneDistanceAlong <= mergeBuffer) ||
|
|
114
|
+
(pitState.surface === 'pit-exit' && (pitState.pitLaneTotalLength - pitState.pitLaneDistanceAlong) <= mergeBuffer)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (isMainTrackMerge) return trackState;
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
...pitState,
|
|
121
|
+
mainTrackSignedOffset: trackState.signedOffset,
|
|
122
|
+
mainTrackCrossTrackError: trackState.crossTrackError,
|
|
123
|
+
signedOffset: trackState.signedOffset,
|
|
124
|
+
crossTrackError: trackState.crossTrackError,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function nearestTrackSampleLegacy(track, position, progressHint = null) {
|
|
129
|
+
let nearest = null;
|
|
130
|
+
|
|
131
|
+
if (Number.isFinite(progressHint)) {
|
|
132
|
+
const centerIndex = sampleIndexAtDistance(track, progressHint);
|
|
133
|
+
nearest = nearestSampleInRange(
|
|
134
|
+
track,
|
|
135
|
+
position,
|
|
136
|
+
centerIndex - NEAREST_HINT_WINDOW_SAMPLES,
|
|
137
|
+
centerIndex + NEAREST_HINT_WINDOW_SAMPLES,
|
|
138
|
+
);
|
|
139
|
+
const fallbackDistance = track.width / 2 + (track.kerbWidth ?? 0) + track.gravelWidth + track.runoffWidth + 180;
|
|
140
|
+
if (!nearest.best || nearest.bestDistance > fallbackDistance * fallbackDistance) {
|
|
141
|
+
nearest = null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return nearest?.best ?? nearestSampleGlobal(track, position).best;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function offsetTrackPoint(point, offset) {
|
|
149
|
+
return {
|
|
150
|
+
x: point.x + point.normalX * offset,
|
|
151
|
+
y: point.y + point.normalY * offset,
|
|
152
|
+
heading: point.heading,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { normalizeAngle } from '../simMath.js';
|
|
2
|
+
import { START_STRAIGHT_BLEND_LENGTH, START_STRAIGHT_EXIT_LENGTH, START_STRAIGHT_GRID_LENGTH, START_STRAIGHT_LOCK_EXTRA } from './trackConstants.js';
|
|
3
|
+
import { blendPoint, distanceForwardAlongTrack, pointWorldClearance, smoothstep } from './trackMath.js';
|
|
4
|
+
import { rebuildSampleDistances } from './sampleGeometry.js';
|
|
5
|
+
|
|
6
|
+
function resolveStartStraightOptions(options = {}) {
|
|
7
|
+
return {
|
|
8
|
+
grid: options.grid ?? START_STRAIGHT_GRID_LENGTH,
|
|
9
|
+
exit: options.exit ?? START_STRAIGHT_EXIT_LENGTH,
|
|
10
|
+
blend: options.blend ?? START_STRAIGHT_BLEND_LENGTH,
|
|
11
|
+
lockExtra: options.lockExtra ?? START_STRAIGHT_LOCK_EXTRA,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function straightenStandingStartSamples(samples, totalLength, options = {}) {
|
|
16
|
+
const startStraight = resolveStartStraightOptions(options);
|
|
17
|
+
const start = samples[0];
|
|
18
|
+
const forward = {
|
|
19
|
+
x: Math.cos(start.heading),
|
|
20
|
+
y: Math.sin(start.heading),
|
|
21
|
+
};
|
|
22
|
+
const gridLockLength = startStraight.grid + startStraight.lockExtra;
|
|
23
|
+
const exitLockLength = startStraight.exit + startStraight.lockExtra;
|
|
24
|
+
|
|
25
|
+
const straightened = samples.slice(0, -1).map((sample) => {
|
|
26
|
+
const distanceFromStart = sample.distance;
|
|
27
|
+
const distanceToStart = totalLength - sample.distance;
|
|
28
|
+
let lineDistance = null;
|
|
29
|
+
let blendAmount = 0;
|
|
30
|
+
|
|
31
|
+
if (distanceFromStart <= exitLockLength + startStraight.blend) {
|
|
32
|
+
lineDistance = distanceFromStart;
|
|
33
|
+
blendAmount = distanceFromStart <= exitLockLength
|
|
34
|
+
? 1
|
|
35
|
+
: 1 - smoothstep((distanceFromStart - exitLockLength) / startStraight.blend);
|
|
36
|
+
} else if (distanceToStart <= gridLockLength + startStraight.blend) {
|
|
37
|
+
lineDistance = -distanceToStart;
|
|
38
|
+
blendAmount = distanceToStart <= gridLockLength
|
|
39
|
+
? 1
|
|
40
|
+
: 1 - smoothstep((distanceToStart - gridLockLength) / startStraight.blend);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (lineDistance == null || blendAmount <= 0) return { ...sample };
|
|
44
|
+
|
|
45
|
+
return blendPoint(sample, {
|
|
46
|
+
...sample,
|
|
47
|
+
x: start.x + forward.x * lineDistance,
|
|
48
|
+
y: start.y + forward.y * lineDistance,
|
|
49
|
+
}, blendAmount);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return rebuildSampleDistances([
|
|
53
|
+
...straightened,
|
|
54
|
+
{
|
|
55
|
+
...straightened[0],
|
|
56
|
+
distance: totalLength,
|
|
57
|
+
},
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function chooseStandingStartIndex(samples, totalLength, options = {}) {
|
|
62
|
+
const startStraight = resolveStartStraightOptions(options);
|
|
63
|
+
const usableSamples = samples.slice(0, -1);
|
|
64
|
+
let best = { index: 0, score: Infinity };
|
|
65
|
+
|
|
66
|
+
for (let index = 0; index < usableSamples.length; index += 1) {
|
|
67
|
+
const candidate = usableSamples[index];
|
|
68
|
+
const candidateForward = {
|
|
69
|
+
x: Math.cos(candidate.heading),
|
|
70
|
+
y: Math.sin(candidate.heading),
|
|
71
|
+
};
|
|
72
|
+
const projectedGridEnd = {
|
|
73
|
+
x: candidate.x - candidateForward.x * startStraight.grid,
|
|
74
|
+
y: candidate.y - candidateForward.y * startStraight.grid,
|
|
75
|
+
};
|
|
76
|
+
const projectedExitEnd = {
|
|
77
|
+
x: candidate.x + candidateForward.x * startStraight.exit,
|
|
78
|
+
y: candidate.y + candidateForward.y * startStraight.exit,
|
|
79
|
+
};
|
|
80
|
+
const projectedClearance = Math.min(pointWorldClearance(projectedGridEnd), pointWorldClearance(projectedExitEnd));
|
|
81
|
+
if (projectedClearance < 0) continue;
|
|
82
|
+
|
|
83
|
+
let gridTurn = 0;
|
|
84
|
+
let gridCurvature = 0;
|
|
85
|
+
let gridCount = 0;
|
|
86
|
+
let exitTurn = 0;
|
|
87
|
+
let exitCurvature = 0;
|
|
88
|
+
let exitCount = 0;
|
|
89
|
+
|
|
90
|
+
for (let offset = 1; offset < usableSamples.length; offset += 1) {
|
|
91
|
+
const sample = usableSamples[(index - offset + usableSamples.length) % usableSamples.length];
|
|
92
|
+
const distanceToLine = distanceForwardAlongTrack(sample.distance, candidate.distance, totalLength);
|
|
93
|
+
if (distanceToLine > startStraight.grid) break;
|
|
94
|
+
gridTurn = Math.max(gridTurn, Math.abs(normalizeAngle(candidate.heading - sample.heading)));
|
|
95
|
+
gridCurvature += sample.curvature;
|
|
96
|
+
gridCount += 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (let offset = 1; offset < usableSamples.length; offset += 1) {
|
|
100
|
+
const sample = usableSamples[(index + offset) % usableSamples.length];
|
|
101
|
+
const distanceFromLine = distanceForwardAlongTrack(candidate.distance, sample.distance, totalLength);
|
|
102
|
+
if (distanceFromLine > startStraight.exit) break;
|
|
103
|
+
exitTurn = Math.max(exitTurn, Math.abs(normalizeAngle(sample.heading - candidate.heading)));
|
|
104
|
+
exitCurvature += sample.curvature;
|
|
105
|
+
exitCount += 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const score =
|
|
109
|
+
gridTurn * 5 +
|
|
110
|
+
exitTurn * 1.2 +
|
|
111
|
+
(gridCurvature / Math.max(1, gridCount)) * 2600 +
|
|
112
|
+
(exitCurvature / Math.max(1, exitCount)) * 650 -
|
|
113
|
+
projectedClearance * 0.002;
|
|
114
|
+
|
|
115
|
+
if (score < best.score) best = { index, score };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return best.index;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function rotateSamplesToStandingStart(samples, totalLength, options = {}) {
|
|
122
|
+
const usableSamples = samples.slice(0, -1);
|
|
123
|
+
const startIndex = chooseStandingStartIndex(samples, totalLength, options);
|
|
124
|
+
const startDistance = usableSamples[startIndex].distance;
|
|
125
|
+
|
|
126
|
+
const rotated = [
|
|
127
|
+
...usableSamples.slice(startIndex),
|
|
128
|
+
...usableSamples.slice(0, startIndex),
|
|
129
|
+
].map((sample) => ({
|
|
130
|
+
...sample,
|
|
131
|
+
distance: distanceForwardAlongTrack(startDistance, sample.distance, totalLength),
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
return [
|
|
135
|
+
...rotated,
|
|
136
|
+
{
|
|
137
|
+
...rotated[0],
|
|
138
|
+
distance: totalLength,
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { metersToSimUnits } from '../units.js';
|
|
2
|
+
|
|
3
|
+
export const WORLD = {
|
|
4
|
+
width: metersToSimUnits(3550),
|
|
5
|
+
height: metersToSimUnits(2148),
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const TRACK = {
|
|
9
|
+
name: 'Apex Harbor GP',
|
|
10
|
+
width: metersToSimUnits(15),
|
|
11
|
+
kerbWidth: metersToSimUnits(1.5),
|
|
12
|
+
gravelWidth: metersToSimUnits(12),
|
|
13
|
+
runoffWidth: metersToSimUnits(20),
|
|
14
|
+
barrierWidth: metersToSimUnits(0.55),
|
|
15
|
+
sampleCount: 3600,
|
|
16
|
+
drsZones: [
|
|
17
|
+
{ id: 'main-straight', startRatio: 0.02, endRatio: 0.18 },
|
|
18
|
+
{ id: 'back-straight', startRatio: 0.43, endRatio: 0.60 },
|
|
19
|
+
{ id: 'harbor-straight', startRatio: 0.82, endRatio: 0.96 },
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const GENERATED_TRACK_MIN_LENGTH = metersToSimUnits(3600);
|
|
24
|
+
export const GENERATED_TRACK_MAX_LENGTH = metersToSimUnits(9000);
|
|
25
|
+
export const GENERATED_TRACK_ATTEMPTS = 24;
|
|
26
|
+
export const GENERATED_FALLBACK_ATTEMPTS = 8;
|
|
27
|
+
export const TRACK_BOUNDARY_PADDING = metersToSimUnits(248);
|
|
28
|
+
export const MIN_NON_ADJACENT_ARC_DISTANCE = metersToSimUnits(700);
|
|
29
|
+
export const MIN_TRACK_CLEARANCE_MULTIPLIER = 1.55;
|
|
30
|
+
export const MIN_TRACK_SHAPE_VARIATION = 0.28;
|
|
31
|
+
export const MAX_LOCAL_TURN_RADIANS = 1.42;
|
|
32
|
+
export const MAX_SAMPLE_HEADING_DELTA_RADIANS = 0.08;
|
|
33
|
+
export const START_STRAIGHT_GRID_LENGTH = metersToSimUnits(496);
|
|
34
|
+
export const START_STRAIGHT_EXIT_LENGTH = metersToSimUnits(500);
|
|
35
|
+
export const START_STRAIGHT_LOCK_EXTRA = metersToSimUnits(43);
|
|
36
|
+
export const START_STRAIGHT_BLEND_LENGTH = metersToSimUnits(320);
|
|
37
|
+
export const NEAREST_HINT_WINDOW_SAMPLES = 240;
|
|
38
|
+
export const PIT_LANE_WIDTH = metersToSimUnits(12);
|
|
39
|
+
export const PIT_LANE_EDGE_GAP = metersToSimUnits(16);
|
|
40
|
+
export const PIT_ACCESS_MIN_LENGTH = metersToSimUnits(70);
|
|
41
|
+
export const PIT_ACCESS_MAX_LENGTH = metersToSimUnits(150);
|
|
42
|
+
export const PIT_ACCESS_TANGENT_RATIO = 0.72;
|
|
43
|
+
export const PIT_ACCESS_SAMPLE_STEPS = 24;
|
|
44
|
+
export const PIT_ACCESS_TRACK_OVERLAP = PIT_LANE_WIDTH * 0.52;
|
|
45
|
+
export const PIT_ACCESS_SEARCH_STEP = metersToSimUnits(8);
|
|
46
|
+
export const PIT_ENTRY_SEARCH_BEFORE = metersToSimUnits(230);
|
|
47
|
+
export const PIT_ENTRY_SEARCH_AFTER = metersToSimUnits(250);
|
|
48
|
+
export const PIT_EXIT_SEARCH_BEFORE = metersToSimUnits(60);
|
|
49
|
+
export const PIT_EXIT_SEARCH_AFTER = metersToSimUnits(560);
|
|
50
|
+
export const PIT_TEAM_COUNT = 10;
|
|
51
|
+
export const PIT_BOXES_PER_TEAM = 2;
|
|
52
|
+
export const PIT_BOX_COUNT = PIT_TEAM_COUNT * PIT_BOXES_PER_TEAM;
|
|
53
|
+
export const PIT_BOX_LENGTH = metersToSimUnits(9);
|
|
54
|
+
export const PIT_BOX_DEPTH = metersToSimUnits(4.5);
|
|
55
|
+
export const PIT_BOX_PAIR_GAP = metersToSimUnits(4);
|
|
56
|
+
export const PIT_TEAM_GAP = metersToSimUnits(14);
|
|
57
|
+
export const PIT_BOX_TO_LANE_GAP = metersToSimUnits(1.5);
|
|
58
|
+
export const PIT_WORKING_LANE_GAP = metersToSimUnits(1.5);
|
|
59
|
+
export const PIT_WORKING_LANE_WIDTH = metersToSimUnits(8);
|
|
60
|
+
export const PIT_SERVICE_AREA_LENGTH = metersToSimUnits(11);
|
|
61
|
+
export const PIT_SERVICE_AREA_DEPTH = metersToSimUnits(5);
|
|
62
|
+
export const PIT_SERVICE_QUEUE_GAP = metersToSimUnits(12);
|
|
63
|
+
export const PIT_LANE_ENTRY_BUFFER = metersToSimUnits(44);
|
|
64
|
+
export const PIT_LANE_EXIT_BUFFER = metersToSimUnits(44);
|
|
65
|
+
export const PIT_LANE_FINISH_RATIO = 0.64;
|
|
66
|
+
export const PIT_WORLD_PADDING = metersToSimUnits(24);
|
|
67
|
+
export const PIT_TRACK_CLEARANCE_MARGIN = metersToSimUnits(5);
|
|
68
|
+
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
|
+
export const CENTERLINE_CONTROLS = [
|
|
91
|
+
{ x: WORLD.width * 0.05, y: WORLD.height * 0.56 },
|
|
92
|
+
{ x: WORLD.width * 0.10, y: WORLD.height * 0.81 },
|
|
93
|
+
{ x: WORLD.width * 0.23, y: WORLD.height * 0.91 },
|
|
94
|
+
{ x: WORLD.width * 0.35, y: WORLD.height * 0.80 },
|
|
95
|
+
{ x: WORLD.width * 0.48, y: WORLD.height * 0.90 },
|
|
96
|
+
{ x: WORLD.width * 0.59, y: WORLD.height * 0.75 },
|
|
97
|
+
{ x: WORLD.width * 0.71, y: WORLD.height * 0.87 },
|
|
98
|
+
{ x: WORLD.width * 0.82, y: WORLD.height * 0.72 },
|
|
99
|
+
{ x: WORLD.width * 0.94, y: WORLD.height * 0.66 },
|
|
100
|
+
{ x: WORLD.width * 0.96, y: WORLD.height * 0.47 },
|
|
101
|
+
{ x: WORLD.width * 0.88, y: WORLD.height * 0.33 },
|
|
102
|
+
{ x: WORLD.width * 0.74, y: WORLD.height * 0.31 },
|
|
103
|
+
{ x: WORLD.width * 0.64, y: WORLD.height * 0.18 },
|
|
104
|
+
{ x: WORLD.width * 0.54, y: WORLD.height * 0.31 },
|
|
105
|
+
{ x: WORLD.width * 0.43, y: WORLD.height * 0.13 },
|
|
106
|
+
{ x: WORLD.width * 0.29, y: WORLD.height * 0.18 },
|
|
107
|
+
{ x: WORLD.width * 0.17, y: WORLD.height * 0.31 },
|
|
108
|
+
{ x: WORLD.width * 0.08, y: WORLD.height * 0.43 },
|
|
109
|
+
];
|