@inventure71/paddockjs 1.0.0 → 2.0.1
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 +58 -0
- package/INSTALL_AND_UPDATE.md +4 -3
- package/README.md +219 -12
- package/docs/architecture.md +126 -45
- package/docs/concepts.md +9 -5
- package/docs/custom_model_controller.md +329 -0
- package/docs/data_contract.md +317 -22
- package/docs/data_model.md +1 -1
- package/docs/index.md +10 -0
- package/docs/learnings.md +22 -0
- package/docs/rules.md +71 -14
- package/docs/sense_contract.md +59 -0
- package/docs/system_specs.md +75 -13
- package/docs/training.md +280 -32
- 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 +6 -33
- package/src/app/BrowserExpertAdapter.js +76 -9
- package/src/app/BrowserExpertExternalRenderer.js +303 -0
- package/src/app/F1SimulatorApp.js +291 -1630
- 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/config/restartOptions.js +34 -0
- package/src/environment/actions.js +13 -5
- package/src/environment/controllerLoop.js +261 -0
- package/src/environment/episode.js +98 -2
- package/src/environment/evaluation.js +54 -32
- package/src/environment/index.d.ts +376 -10
- package/src/environment/index.js +1 -0
- package/src/environment/metrics.js +159 -0
- package/src/environment/observationOptions.js +22 -1
- package/src/environment/observationVector.js +200 -0
- package/src/environment/observations.js +199 -141
- package/src/environment/options.js +37 -5
- package/src/environment/recorder.js +13 -6
- package/src/environment/rewards.js +13 -2
- package/src/environment/runtime.js +272 -23
- package/src/environment/scenarios.js +57 -9
- 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 +75 -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 +66 -0
- package/src/environment/sensors/surfaceRays.js +360 -0
- package/src/environment/sensors/trackRays.js +424 -0
- package/src/environment/sensors.js +1 -367
- package/src/environment/specs.js +159 -26
- package/src/environment/workerProtocol.js +13 -1
- package/src/index.d.ts +171 -6
- package/src/index.js +6 -32
- 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 +239 -0
- package/src/simulation/pit/pitIntent.js +92 -0
- package/src/simulation/pit/pitKinematics.js +24 -0
- package/src/simulation/pit/pitOccupancy.js +47 -0
- package/src/simulation/pit/pitPenaltyService.js +71 -0
- package/src/simulation/pit/pitQueue.js +60 -0
- package/src/simulation/pit/pitRouteMovement.js +167 -0
- package/src/simulation/pit/pitRouting.js +319 -0
- package/src/simulation/pit/pitService.js +43 -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 +94 -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 +67 -0
- package/src/simulation/race/raceOrder.js +113 -0
- package/src/simulation/race/raceProgress.js +129 -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 +92 -0
- package/src/simulation/race/stalledDnf.js +77 -0
- package/src/simulation/raceSimulation.js +231 -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 +144 -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 +193 -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/trackQueryGrid.js +94 -0
- package/src/simulation/track/trackQueryIndex.js +659 -0
- package/src/simulation/track/trackQueryPitIndex.js +140 -0
- package/src/simulation/track/trackQueryScratch.js +46 -0
- package/src/simulation/track/trackQueryStats.js +28 -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 +181 -0
- package/src/simulation/vehicle/mainTrackWheelSurface.js +82 -0
- package/src/simulation/vehicle/pitWheelSurface.js +69 -0
- package/src/simulation/vehicle/runoffResponse.js +87 -0
- package/src/simulation/vehicle/surfacePriority.js +28 -0
- package/src/simulation/vehicle/vehicleGeometry.js +247 -0
- package/src/simulation/vehicle/vehicleKinematics.js +37 -0
- package/src/simulation/vehicle/vehiclePhysics.js +364 -0
- package/src/simulation/vehicle/vehicleSnapshots.js +327 -0
- package/src/simulation/vehicle/vehicleState.js +220 -0
- package/src/simulation/vehicle/wheelSurface.js +152 -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
|
@@ -1,415 +1,13 @@
|
|
|
1
|
-
import { Container
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { Container } from 'pixi.js';
|
|
2
|
+
import { WORLD } from '../simulation/track/trackModel.js';
|
|
3
|
+
import { destroyDisplayChildren } from './track/trackRenderGeometry.js';
|
|
4
|
+
import { addAsphalt, addBarriers, addBorders, addBoundaryUnderlay, addGrass, addGravelRunoff, addKerbs } from './track/trackMaterialRenderer.js';
|
|
5
|
+
import { addPitLane, addPitLaneRunoff } from './track/pitLaneRenderer.js';
|
|
6
|
+
import { addStartingGrid } from './track/gridRenderer.js';
|
|
7
|
+
import { addFinishLine } from './track/finishLineRenderer.js';
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
};
|
|
8
|
-
const WORLD_BACKGROUND_PADDING_MULTIPLIER = 8;
|
|
9
|
-
const GRASS_COLOR = 0x2e7d32;
|
|
10
|
-
const GRAVEL_COLOR = 0xb49a68;
|
|
11
|
-
const ASPHALT_COLOR = 0x4a4d52;
|
|
12
|
-
const PIT_ASPHALT_COLOR = ASPHALT_COLOR;
|
|
13
|
-
const PIT_BOX_COLOR = 0x242831;
|
|
14
|
-
const PIT_LINE_COLOR = 0xf8fafc;
|
|
15
|
-
const PIT_SPEED_LINE_COLOR = 0xffd166;
|
|
16
|
-
const PIT_CONNECTOR_WIDTH = metersToSimUnits(12);
|
|
17
|
-
const PIT_EDGE_WIDTH = metersToSimUnits(0.35);
|
|
18
|
-
const EDGE_REVEAL_OFFSET = metersToSimUnits(0.35);
|
|
19
|
-
const EDGE_REVEAL_WIDTH = metersToSimUnits(0.5);
|
|
20
|
-
const OUTER_BOUNDARY_OFFSET = metersToSimUnits(1.1);
|
|
21
|
-
const OUTER_BOUNDARY_WIDTH = metersToSimUnits(0.75);
|
|
22
|
-
const KERB_OFFSET = metersToSimUnits(0.45);
|
|
23
|
-
const KERB_WIDTH = metersToSimUnits(1.25);
|
|
24
|
-
const FINISH_LINE_DEPTH = metersToSimUnits(8);
|
|
25
|
-
const FINISH_LINE_COLUMNS = 10;
|
|
26
|
-
const START_GRID_SLOT_COUNT = 20;
|
|
27
|
-
const START_GRID_SLOT_SPACING = metersToSimUnits(8);
|
|
28
|
-
const START_GRID_FIRST_DISTANCE = metersToSimUnits(-6);
|
|
29
|
-
const START_GRID_LATERAL_OFFSET = metersToSimUnits(3.2);
|
|
30
|
-
const START_GRID_BOX_LENGTH = metersToSimUnits(7);
|
|
31
|
-
const START_GRID_BOX_WIDTH = metersToSimUnits(3.2);
|
|
32
|
-
const SEGMENTED_STROKE_STEP = 2;
|
|
33
|
-
const KERB_STEP = 4;
|
|
34
|
-
const KERB_CURVATURE_THRESHOLD = 0.00038;
|
|
35
|
-
const OFFSET_SEGMENT_SAMPLE_COUNT = 7;
|
|
36
|
-
const NON_LOCAL_SAMPLE_STEP = 8;
|
|
37
|
-
const OFFSET_GAP_SAMPLE_COUNT = 4;
|
|
38
|
-
|
|
39
|
-
function colorToNumber(color, fallback = PIT_LINE_COLOR) {
|
|
40
|
-
if (typeof color === 'number' && Number.isFinite(color)) return color;
|
|
41
|
-
if (typeof color !== 'string') return fallback;
|
|
42
|
-
const normalized = color.trim().replace(/^#/, '');
|
|
43
|
-
if (!/^[0-9a-f]{6}$/i.test(normalized)) return fallback;
|
|
44
|
-
return Number.parseInt(normalized, 16);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function makeTrackPath(track, offset = 0) {
|
|
48
|
-
const path = new Graphics();
|
|
49
|
-
const samples = track.samples.slice(0, -1);
|
|
50
|
-
const first = offset === 0 ? samples[0] : offsetTrackPoint(samples[0], offset);
|
|
51
|
-
path.moveTo(first.x, first.y);
|
|
52
|
-
|
|
53
|
-
samples.slice(1).forEach((sample) => {
|
|
54
|
-
const point = offset === 0 ? sample : offsetTrackPoint(sample, offset);
|
|
55
|
-
path.lineTo(point.x, point.y);
|
|
56
|
-
});
|
|
57
|
-
path.closePath();
|
|
58
|
-
|
|
59
|
-
return path;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function textureOrWhite(texture) {
|
|
63
|
-
return texture ?? Texture.WHITE;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function destroyDisplayChildren(container) {
|
|
67
|
-
container.removeChildren().forEach((child) => {
|
|
68
|
-
child.destroy?.({ children: true, texture: false, textureSource: false });
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function arcDistance(track, first, second) {
|
|
73
|
-
const delta = Math.abs(first - second);
|
|
74
|
-
return Math.min(delta, track.length - delta);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function offsetPointIsLocal(track, source, point, offset) {
|
|
78
|
-
const state = nearestTrackState(track, point);
|
|
79
|
-
const localTolerance = Math.max(metersToSimUnits(105), Math.abs(offset) * 2.1);
|
|
80
|
-
const minimumEdgeDistance = Math.min(track.width / 2 - metersToSimUnits(4), Math.abs(offset) * 0.72);
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
arcDistance(track, state.distance, source.distance) <= localTolerance &&
|
|
84
|
-
state.crossTrackError >= minimumEdgeDistance
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function offsetPointOverlapsNonLocalRoad(track, source, point, offset) {
|
|
89
|
-
const localTolerance = Math.max(metersToSimUnits(105), Math.abs(offset) * 2.1);
|
|
90
|
-
const roadBand = track.width / 2 + (track.kerbWidth ?? 0) + metersToSimUnits(4);
|
|
91
|
-
const roadBandSquared = (roadBand + metersToSimUnits(20)) ** 2;
|
|
92
|
-
|
|
93
|
-
for (let index = 0; index < track.samples.length - 1; index += NON_LOCAL_SAMPLE_STEP) {
|
|
94
|
-
const sample = track.samples[index];
|
|
95
|
-
if (arcDistance(track, sample.distance, source.distance) <= localTolerance) continue;
|
|
96
|
-
|
|
97
|
-
const dx = point.x - sample.x;
|
|
98
|
-
const dy = point.y - sample.y;
|
|
99
|
-
if (dx * dx + dy * dy > roadBandSquared) continue;
|
|
100
|
-
|
|
101
|
-
const signedOffset = dx * sample.normalX + dy * sample.normalY;
|
|
102
|
-
if (Math.abs(signedOffset) <= roadBand) return true;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function interpolatedSegmentPoint(start, end, amount) {
|
|
109
|
-
return {
|
|
110
|
-
x: start.x + (end.x - start.x) * amount,
|
|
111
|
-
y: start.y + (end.y - start.y) * amount,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function pointDistance(first, second) {
|
|
116
|
-
return Math.hypot(second.x - first.x, second.y - first.y);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function drawPolyline(graphics, points, {
|
|
120
|
-
width,
|
|
121
|
-
color,
|
|
122
|
-
alpha = 1,
|
|
123
|
-
cap = 'round',
|
|
124
|
-
join = 'round',
|
|
125
|
-
}) {
|
|
126
|
-
if (!Array.isArray(points) || points.length < 2) return;
|
|
127
|
-
graphics.moveTo(points[0].x, points[0].y);
|
|
128
|
-
points.slice(1).forEach((point) => {
|
|
129
|
-
graphics.lineTo(point.x, point.y);
|
|
130
|
-
});
|
|
131
|
-
graphics.stroke({
|
|
132
|
-
width,
|
|
133
|
-
color,
|
|
134
|
-
alpha,
|
|
135
|
-
cap,
|
|
136
|
-
join,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function offsetVectorPoint(point, vector, amount) {
|
|
141
|
-
return {
|
|
142
|
-
x: point.x + vector.x * amount,
|
|
143
|
-
y: point.y + vector.y * amount,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function drawMainPitLaneOffsetLine(graphics, pitLane, offset, {
|
|
148
|
-
width,
|
|
149
|
-
color,
|
|
150
|
-
alpha = 1,
|
|
151
|
-
}) {
|
|
152
|
-
const start = offsetVectorPoint(pitLane.mainLane.start, pitLane.serviceNormal, offset);
|
|
153
|
-
const end = offsetVectorPoint(pitLane.mainLane.end, pitLane.serviceNormal, offset);
|
|
154
|
-
drawPolyline(graphics, [start, end], {
|
|
155
|
-
width,
|
|
156
|
-
color,
|
|
157
|
-
alpha,
|
|
158
|
-
cap: 'butt',
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function drawPitRoad(graphics, points, {
|
|
163
|
-
roadWidth,
|
|
164
|
-
asphaltColor = ASPHALT_COLOR,
|
|
165
|
-
edgeAlpha = 0.54,
|
|
166
|
-
cap = 'butt',
|
|
167
|
-
}) {
|
|
168
|
-
drawPolyline(graphics, points, {
|
|
169
|
-
width: roadWidth + 8,
|
|
170
|
-
color: 0x080a0f,
|
|
171
|
-
alpha: edgeAlpha,
|
|
172
|
-
cap,
|
|
173
|
-
});
|
|
174
|
-
drawPolyline(graphics, points, {
|
|
175
|
-
width: roadWidth,
|
|
176
|
-
color: asphaltColor,
|
|
177
|
-
alpha: 1,
|
|
178
|
-
cap,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function drawPitRunoff(graphics, points, roadWidth) {
|
|
183
|
-
drawPolyline(graphics, points, {
|
|
184
|
-
width: roadWidth + 92,
|
|
185
|
-
color: GRAVEL_COLOR,
|
|
186
|
-
alpha: 1,
|
|
187
|
-
cap: 'butt',
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function getPitLaneRoadCenterlines(pitLane) {
|
|
192
|
-
return {
|
|
193
|
-
entryRoad: pitLane.entry.roadCenterline ?? [pitLane.entry.edgePoint, pitLane.mainLane.start],
|
|
194
|
-
exitRoad: pitLane.exit.roadCenterline ?? [pitLane.mainLane.end, pitLane.exit.edgePoint],
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function drawCurvedPitRoadEdges(graphics, points, roadWidth) {
|
|
199
|
-
if (!Array.isArray(points) || points.length < 2) return;
|
|
200
|
-
[-1, 1].forEach((side) => {
|
|
201
|
-
const edgePoints = points.map((point, index) => {
|
|
202
|
-
const before = points[Math.max(0, index - 1)];
|
|
203
|
-
const after = points[Math.min(points.length - 1, index + 1)];
|
|
204
|
-
const dx = after.x - before.x;
|
|
205
|
-
const dy = after.y - before.y;
|
|
206
|
-
const len = Math.hypot(dx, dy) || 1;
|
|
207
|
-
return {
|
|
208
|
-
x: point.x + (-dy / len) * side * roadWidth / 2,
|
|
209
|
-
y: point.y + (dx / len) * side * roadWidth / 2,
|
|
210
|
-
};
|
|
211
|
-
});
|
|
212
|
-
drawPolyline(graphics, edgePoints, {
|
|
213
|
-
width: 2,
|
|
214
|
-
color: PIT_LINE_COLOR,
|
|
215
|
-
alpha: 0.7,
|
|
216
|
-
cap: 'butt',
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function getPitRoadEdgePoints(track, points) {
|
|
222
|
-
if (!Array.isArray(points) || points.length < 2) return points;
|
|
223
|
-
const trackEdge = track.width / 2;
|
|
224
|
-
const firstVisible = points.findIndex((point) => nearestTrackState(track, point).crossTrackError >= trackEdge - 4);
|
|
225
|
-
const lastVisibleReverse = [...points]
|
|
226
|
-
.reverse()
|
|
227
|
-
.findIndex((point) => nearestTrackState(track, point).crossTrackError >= trackEdge - 4);
|
|
228
|
-
|
|
229
|
-
if (firstVisible < 0 || lastVisibleReverse < 0) return points;
|
|
230
|
-
const lastVisible = points.length - 1 - lastVisibleReverse;
|
|
231
|
-
return points.slice(firstVisible, lastVisible + 1);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function createTeamPitGroupCorners(boxes) {
|
|
235
|
-
const ordered = [...boxes].sort((left, right) => left.index - right.index);
|
|
236
|
-
const first = ordered[0];
|
|
237
|
-
const last = ordered.at(-1);
|
|
238
|
-
if (!first || !last) return [];
|
|
239
|
-
|
|
240
|
-
return [
|
|
241
|
-
first.corners[0],
|
|
242
|
-
last.corners[1],
|
|
243
|
-
last.corners[2],
|
|
244
|
-
first.corners[3],
|
|
245
|
-
];
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export function offsetSegmentIsSafe(track, current, next, start, end, offset) {
|
|
249
|
-
const centerDistance = Math.hypot(next.x - current.x, next.y - current.y);
|
|
250
|
-
const edgeDistance = Math.hypot(end.x - start.x, end.y - start.y);
|
|
251
|
-
const maxEdgeDistance = Math.max(centerDistance * 2.6, track.width * 0.58);
|
|
252
|
-
if (edgeDistance > maxEdgeDistance || Math.abs(offset) > track.width + track.gravelWidth + track.runoffWidth) {
|
|
253
|
-
return false;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const forwardDistance = ((next.distance - current.distance + track.length) % track.length) || centerDistance;
|
|
257
|
-
|
|
258
|
-
for (let sample = 0; sample <= OFFSET_SEGMENT_SAMPLE_COUNT; sample += 1) {
|
|
259
|
-
const amount = sample / OFFSET_SEGMENT_SAMPLE_COUNT;
|
|
260
|
-
const source = {
|
|
261
|
-
distance: (current.distance + forwardDistance * amount) % track.length,
|
|
262
|
-
};
|
|
263
|
-
const point = interpolatedSegmentPoint(start, end, amount);
|
|
264
|
-
if (!offsetPointIsLocal(track, source, point, offset)) return false;
|
|
265
|
-
if (offsetPointOverlapsNonLocalRoad(track, source, point, offset)) return false;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return true;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export function offsetGapBridgeIsSafe(track, start, end, width) {
|
|
272
|
-
const bridgeDistance = pointDistance(start, end);
|
|
273
|
-
const maximumBridgeDistance = Math.max(track.width * 0.08, width * 3.4);
|
|
274
|
-
if (bridgeDistance > maximumBridgeDistance) return false;
|
|
275
|
-
|
|
276
|
-
const minimumEdgeDistance = track.width / 2 - Math.max(width * 2.5, metersToSimUnits(4.75));
|
|
277
|
-
for (let sample = 1; sample < OFFSET_GAP_SAMPLE_COUNT; sample += 1) {
|
|
278
|
-
const amount = sample / OFFSET_GAP_SAMPLE_COUNT;
|
|
279
|
-
const point = interpolatedSegmentPoint(start, end, amount);
|
|
280
|
-
const state = nearestTrackState(track, point);
|
|
281
|
-
if (state.crossTrackError < minimumEdgeDistance) return false;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return true;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
export function getOffsetStrokeSegments(track, {
|
|
288
|
-
side,
|
|
289
|
-
offset,
|
|
290
|
-
step = SEGMENTED_STROKE_STEP,
|
|
291
|
-
}) {
|
|
292
|
-
const samples = track.samples.slice(0, -1);
|
|
293
|
-
const signedOffset = side * offset;
|
|
294
|
-
|
|
295
|
-
return samples.map((current, index) => {
|
|
296
|
-
if (index % step !== 0) return null;
|
|
297
|
-
|
|
298
|
-
const next = samples[(index + step) % samples.length];
|
|
299
|
-
const start = offsetTrackPoint(current, signedOffset);
|
|
300
|
-
const end = offsetTrackPoint(next, signedOffset);
|
|
301
|
-
|
|
302
|
-
return {
|
|
303
|
-
current,
|
|
304
|
-
next,
|
|
305
|
-
start,
|
|
306
|
-
end,
|
|
307
|
-
safe: offsetSegmentIsSafe(track, current, next, start, end, signedOffset),
|
|
308
|
-
};
|
|
309
|
-
}).filter(Boolean);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
export function getOffsetGapBridges(track, segments, width) {
|
|
313
|
-
if (segments.length < 2) return [];
|
|
314
|
-
|
|
315
|
-
return segments.flatMap((segment, index) => {
|
|
316
|
-
if (!segment.safe) return [];
|
|
317
|
-
|
|
318
|
-
const previousIndex = (index - 1 + segments.length) % segments.length;
|
|
319
|
-
if (segments[previousIndex].safe) return [];
|
|
320
|
-
|
|
321
|
-
let previousSafe = null;
|
|
322
|
-
for (let lookup = 2; lookup <= segments.length; lookup += 1) {
|
|
323
|
-
const candidate = segments[(index - lookup + segments.length) % segments.length];
|
|
324
|
-
if (!candidate.safe) continue;
|
|
325
|
-
previousSafe = candidate;
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (!previousSafe || previousSafe === segment) return [];
|
|
330
|
-
if (!offsetGapBridgeIsSafe(track, previousSafe.end, segment.start, width)) return [];
|
|
331
|
-
|
|
332
|
-
return [{
|
|
333
|
-
start: previousSafe.end,
|
|
334
|
-
end: segment.start,
|
|
335
|
-
}];
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function drawOffsetSegment(graphics, start, end, {
|
|
340
|
-
width,
|
|
341
|
-
color,
|
|
342
|
-
alpha,
|
|
343
|
-
cap,
|
|
344
|
-
}) {
|
|
345
|
-
graphics.moveTo(start.x, start.y);
|
|
346
|
-
graphics.lineTo(end.x, end.y);
|
|
347
|
-
graphics.stroke({
|
|
348
|
-
width,
|
|
349
|
-
color,
|
|
350
|
-
alpha,
|
|
351
|
-
join: 'round',
|
|
352
|
-
cap,
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function drawContinuousOffsetStroke(graphics, track, {
|
|
357
|
-
side,
|
|
358
|
-
offset,
|
|
359
|
-
width,
|
|
360
|
-
color,
|
|
361
|
-
alpha = 1,
|
|
362
|
-
step = SEGMENTED_STROKE_STEP,
|
|
363
|
-
}) {
|
|
364
|
-
const samples = track.samples.slice(0, -1).filter((_, index) => index % step === 0);
|
|
365
|
-
if (samples.length < 2) return;
|
|
366
|
-
|
|
367
|
-
const first = offsetTrackPoint(samples[0], side * offset);
|
|
368
|
-
graphics.moveTo(first.x, first.y);
|
|
369
|
-
samples.slice(1).forEach((sample) => {
|
|
370
|
-
const point = offsetTrackPoint(sample, side * offset);
|
|
371
|
-
graphics.lineTo(point.x, point.y);
|
|
372
|
-
});
|
|
373
|
-
graphics.closePath();
|
|
374
|
-
graphics.stroke({
|
|
375
|
-
width,
|
|
376
|
-
color,
|
|
377
|
-
alpha,
|
|
378
|
-
join: 'round',
|
|
379
|
-
cap: 'round',
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function drawSegmentedOffsetStroke(graphics, track, {
|
|
384
|
-
side,
|
|
385
|
-
offset,
|
|
386
|
-
width,
|
|
387
|
-
color,
|
|
388
|
-
alpha = 1,
|
|
389
|
-
step = SEGMENTED_STROKE_STEP,
|
|
390
|
-
cap = 'round',
|
|
391
|
-
}) {
|
|
392
|
-
const segments = getOffsetStrokeSegments(track, { side, offset, step });
|
|
393
|
-
|
|
394
|
-
segments.forEach((segment) => {
|
|
395
|
-
if (!segment.safe) return;
|
|
396
|
-
drawOffsetSegment(graphics, segment.start, segment.end, {
|
|
397
|
-
width,
|
|
398
|
-
color,
|
|
399
|
-
alpha,
|
|
400
|
-
cap,
|
|
401
|
-
});
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
getOffsetGapBridges(track, segments, width).forEach((bridge) => {
|
|
405
|
-
drawOffsetSegment(graphics, bridge.start, bridge.end, {
|
|
406
|
-
width,
|
|
407
|
-
color,
|
|
408
|
-
alpha,
|
|
409
|
-
cap: 'round',
|
|
410
|
-
});
|
|
411
|
-
});
|
|
412
|
-
}
|
|
9
|
+
export { offsetGapBridgeIsSafe, offsetSegmentIsSafe, getOffsetGapBridges, getOffsetStrokeSegments } from './track/offsetStrokeSafety.js';
|
|
10
|
+
export { getTrackMaterialBands } from './track/trackMaterialRenderer.js';
|
|
413
11
|
|
|
414
12
|
export class ProceduralTrackAsset {
|
|
415
13
|
constructor({ textures = {}, world = WORLD } = {}) {
|
|
@@ -420,417 +18,16 @@ export class ProceduralTrackAsset {
|
|
|
420
18
|
|
|
421
19
|
render(track) {
|
|
422
20
|
destroyDisplayChildren(this.container);
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
addGrass() {
|
|
436
|
-
const grass = new Graphics();
|
|
437
|
-
const padding = Math.max(this.world.width, this.world.height) * WORLD_BACKGROUND_PADDING_MULTIPLIER;
|
|
438
|
-
const bounds = {
|
|
439
|
-
x: -padding,
|
|
440
|
-
y: -padding,
|
|
441
|
-
width: this.world.width + padding * 2,
|
|
442
|
-
height: this.world.height + padding * 2,
|
|
443
|
-
};
|
|
444
|
-
grass.label = 'world-grass';
|
|
445
|
-
grass.worldGrassBounds = bounds;
|
|
446
|
-
grass.rect(
|
|
447
|
-
bounds.x,
|
|
448
|
-
bounds.y,
|
|
449
|
-
bounds.width,
|
|
450
|
-
bounds.height,
|
|
451
|
-
).fill(GRASS_COLOR);
|
|
452
|
-
this.container.addChild(grass);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
addGravelRunoff(track) {
|
|
456
|
-
const gravel = new Graphics();
|
|
457
|
-
const innerOffset = track.width / 2 + 8;
|
|
458
|
-
const outerOffset = track.width / 2 + track.gravelWidth;
|
|
459
|
-
const centerOffset = (innerOffset + outerOffset) / 2;
|
|
460
|
-
const width = outerOffset - innerOffset;
|
|
461
|
-
|
|
462
|
-
[-1, 1].forEach((side) => {
|
|
463
|
-
drawContinuousOffsetStroke(gravel, track, {
|
|
464
|
-
side,
|
|
465
|
-
offset: centerOffset,
|
|
466
|
-
width,
|
|
467
|
-
color: GRAVEL_COLOR,
|
|
468
|
-
step: 4,
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
this.container.addChild(gravel);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
addBoundaryUnderlay(track) {
|
|
475
|
-
const boundary = new Graphics();
|
|
476
|
-
[-1, 1].forEach((side) => {
|
|
477
|
-
drawContinuousOffsetStroke(boundary, track, {
|
|
478
|
-
side,
|
|
479
|
-
offset: track.width / 2 + OUTER_BOUNDARY_OFFSET,
|
|
480
|
-
width: OUTER_BOUNDARY_WIDTH,
|
|
481
|
-
color: 0x090a0d,
|
|
482
|
-
alpha: 0.64,
|
|
483
|
-
step: 2,
|
|
484
|
-
});
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
this.drawKerbStripes(boundary, track, { clipUnsafeSegments: false });
|
|
488
|
-
|
|
489
|
-
[-1, 1].forEach((side) => {
|
|
490
|
-
drawContinuousOffsetStroke(boundary, track, {
|
|
491
|
-
side,
|
|
492
|
-
offset: track.width / 2 + EDGE_REVEAL_OFFSET,
|
|
493
|
-
width: EDGE_REVEAL_WIDTH,
|
|
494
|
-
color: 0xf8fafc,
|
|
495
|
-
alpha: 0.94,
|
|
496
|
-
step: 2,
|
|
497
|
-
});
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
this.container.addChild(boundary);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
drawKerbStripes(kerbs, track, { clipUnsafeSegments }) {
|
|
504
|
-
const samples = track.samples.slice(0, -1);
|
|
505
|
-
|
|
506
|
-
for (let index = 0; index < samples.length; index += KERB_STEP) {
|
|
507
|
-
const sample = samples[index];
|
|
508
|
-
const next = samples[(index + KERB_STEP) % samples.length];
|
|
509
|
-
const curvature = Math.max(sample.curvature, next.curvature);
|
|
510
|
-
if (curvature < KERB_CURVATURE_THRESHOLD) continue;
|
|
511
|
-
|
|
512
|
-
const color = Math.floor(index / KERB_STEP) % 2 === 0 ? 0xe10600 : 0xf8fafc;
|
|
513
|
-
[-1, 1].forEach((side) => {
|
|
514
|
-
const offset = side * (track.width / 2 + KERB_OFFSET);
|
|
515
|
-
const start = offsetTrackPoint(sample, offset);
|
|
516
|
-
const end = offsetTrackPoint(next, offset);
|
|
517
|
-
if (clipUnsafeSegments && !offsetSegmentIsSafe(track, sample, next, start, end, offset)) return;
|
|
518
|
-
kerbs.moveTo(start.x, start.y);
|
|
519
|
-
kerbs.lineTo(end.x, end.y);
|
|
520
|
-
kerbs.stroke({
|
|
521
|
-
width: KERB_WIDTH,
|
|
522
|
-
color,
|
|
523
|
-
alpha: 1,
|
|
524
|
-
join: 'round',
|
|
525
|
-
cap: 'butt',
|
|
526
|
-
});
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
addMaskedMaterial({ track, texture, strokeWidth, alpha, tileScale }) {
|
|
532
|
-
const sprite = new TilingSprite({
|
|
533
|
-
texture: textureOrWhite(texture),
|
|
534
|
-
width: this.world.width,
|
|
535
|
-
height: this.world.height,
|
|
536
|
-
tileScale,
|
|
537
|
-
});
|
|
538
|
-
sprite.alpha = alpha;
|
|
539
|
-
|
|
540
|
-
const mask = makeTrackPath(track);
|
|
541
|
-
mask.stroke({
|
|
542
|
-
width: strokeWidth,
|
|
543
|
-
color: 0xffffff,
|
|
544
|
-
alpha: 1,
|
|
545
|
-
join: 'round',
|
|
546
|
-
cap: 'butt',
|
|
547
|
-
});
|
|
548
|
-
mask.renderable = false;
|
|
549
|
-
sprite.mask = mask;
|
|
550
|
-
this.container.addChild(sprite, mask);
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
addAsphalt(track) {
|
|
554
|
-
const asphaltBase = makeTrackPath(track);
|
|
555
|
-
asphaltBase.label = 'track-asphalt';
|
|
556
|
-
asphaltBase.stroke({
|
|
557
|
-
width: track.width,
|
|
558
|
-
color: ASPHALT_COLOR,
|
|
559
|
-
alpha: 1,
|
|
560
|
-
join: 'round',
|
|
561
|
-
cap: 'butt',
|
|
562
|
-
});
|
|
563
|
-
this.container.addChild(asphaltBase);
|
|
564
|
-
|
|
565
|
-
this.addMaskedMaterial({
|
|
566
|
-
track,
|
|
567
|
-
texture: this.textures.asphalt,
|
|
568
|
-
strokeWidth: track.width,
|
|
569
|
-
alpha: 0.18,
|
|
570
|
-
tileScale: MATERIAL_TILE_SCALE.asphalt,
|
|
571
|
-
});
|
|
572
|
-
|
|
21
|
+
addGrass(this);
|
|
22
|
+
addGravelRunoff(this, track);
|
|
23
|
+
addBoundaryUnderlay(this, track);
|
|
24
|
+
addPitLaneRunoff(this, track);
|
|
25
|
+
addPitLane(this, track);
|
|
26
|
+
addAsphalt(this, track);
|
|
27
|
+
addKerbs(this, track);
|
|
28
|
+
addBorders(this, track);
|
|
29
|
+
addStartingGrid(this, track);
|
|
30
|
+
addFinishLine(this, track);
|
|
31
|
+
addBarriers(this, track);
|
|
573
32
|
}
|
|
574
|
-
|
|
575
|
-
addPitLaneRunoff(track) {
|
|
576
|
-
if (!track.pitLane?.enabled) return;
|
|
577
|
-
const pitLane = track.pitLane;
|
|
578
|
-
const pitRunoff = new Graphics();
|
|
579
|
-
pitRunoff.label = 'pit-lane-runoff';
|
|
580
|
-
|
|
581
|
-
// Only the main pit lane gets a gravel shoulder; connectors run through the track's
|
|
582
|
-
// own gravel/runoff zone so they do not need a separate runoff stroke (which would
|
|
583
|
-
// bleed an undesirable gravel band into the track asphalt at the entry/exit endpoints).
|
|
584
|
-
const workingLaneExtent = pitLane.workingLane
|
|
585
|
-
? pitLane.workingLane.offset + pitLane.workingLane.width / 2
|
|
586
|
-
: pitLane.width / 2;
|
|
587
|
-
drawPitRunoff(pitRunoff, pitLane.mainLane.points, Math.max(pitLane.width, workingLaneExtent * 2));
|
|
588
|
-
|
|
589
|
-
this.container.addChild(pitRunoff);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
addPitLane(track) {
|
|
593
|
-
if (!track.pitLane?.enabled) return;
|
|
594
|
-
const pitLane = track.pitLane;
|
|
595
|
-
const pit = new Graphics();
|
|
596
|
-
pit.label = 'pit-lane';
|
|
597
|
-
const { entryRoad, exitRoad } = getPitLaneRoadCenterlines(pitLane);
|
|
598
|
-
|
|
599
|
-
drawPitRoad(pit, entryRoad, {
|
|
600
|
-
roadWidth: PIT_CONNECTOR_WIDTH,
|
|
601
|
-
asphaltColor: ASPHALT_COLOR,
|
|
602
|
-
edgeAlpha: 0.18,
|
|
603
|
-
cap: 'round',
|
|
604
|
-
});
|
|
605
|
-
drawPitRoad(pit, exitRoad, {
|
|
606
|
-
roadWidth: PIT_CONNECTOR_WIDTH,
|
|
607
|
-
asphaltColor: ASPHALT_COLOR,
|
|
608
|
-
edgeAlpha: 0.18,
|
|
609
|
-
cap: 'round',
|
|
610
|
-
});
|
|
611
|
-
drawPitRoad(pit, pitLane.mainLane.points, {
|
|
612
|
-
roadWidth: pitLane.width,
|
|
613
|
-
asphaltColor: PIT_ASPHALT_COLOR,
|
|
614
|
-
edgeAlpha: 0.62,
|
|
615
|
-
});
|
|
616
|
-
if (pitLane.workingLane?.points?.length >= 2) {
|
|
617
|
-
drawPitRoad(pit, pitLane.workingLane.points, {
|
|
618
|
-
roadWidth: pitLane.workingLane.width,
|
|
619
|
-
asphaltColor: PIT_ASPHALT_COLOR,
|
|
620
|
-
edgeAlpha: 0.52,
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
drawMainPitLaneOffsetLine(pit, pitLane, -pitLane.width / 2, {
|
|
625
|
-
width: 3,
|
|
626
|
-
color: PIT_SPEED_LINE_COLOR,
|
|
627
|
-
alpha: 0.9,
|
|
628
|
-
});
|
|
629
|
-
drawMainPitLaneOffsetLine(pit, pitLane, pitLane.width / 2, {
|
|
630
|
-
width: 3,
|
|
631
|
-
color: PIT_LINE_COLOR,
|
|
632
|
-
alpha: 0.84,
|
|
633
|
-
});
|
|
634
|
-
if (pitLane.workingLane) {
|
|
635
|
-
drawMainPitLaneOffsetLine(pit, pitLane, pitLane.workingLane.offset - pitLane.workingLane.width / 2, {
|
|
636
|
-
width: 2,
|
|
637
|
-
color: PIT_LINE_COLOR,
|
|
638
|
-
alpha: 0.48,
|
|
639
|
-
});
|
|
640
|
-
drawMainPitLaneOffsetLine(pit, pitLane, pitLane.workingLane.offset + pitLane.workingLane.width / 2, {
|
|
641
|
-
width: 3,
|
|
642
|
-
color: PIT_LINE_COLOR,
|
|
643
|
-
alpha: 0.78,
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
const heading = pitLane.mainLane.heading;
|
|
648
|
-
const laneNormal = { x: -Math.sin(heading), y: Math.cos(heading) };
|
|
649
|
-
[pitLane.mainLane.start, pitLane.mainLane.end].forEach((point) => {
|
|
650
|
-
const a = offsetVectorPoint(point, laneNormal, -pitLane.width / 2);
|
|
651
|
-
const b = offsetVectorPoint(point, laneNormal, pitLane.width / 2);
|
|
652
|
-
drawPolyline(pit, [a, b], {
|
|
653
|
-
width: 4,
|
|
654
|
-
color: PIT_SPEED_LINE_COLOR,
|
|
655
|
-
alpha: 0.95,
|
|
656
|
-
cap: 'butt',
|
|
657
|
-
});
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
[entryRoad, exitRoad].forEach((road) => {
|
|
661
|
-
drawCurvedPitRoadEdges(pit, getPitRoadEdgePoints(track, road), PIT_CONNECTOR_WIDTH);
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
(pitLane.teams ?? []).forEach((team) => {
|
|
665
|
-
const boxes = pitLane.boxes.filter((box) => team.boxIds?.includes(box.id));
|
|
666
|
-
const corners = createTeamPitGroupCorners(boxes);
|
|
667
|
-
if (corners.length < 4) return;
|
|
668
|
-
const color = colorToNumber(team.color);
|
|
669
|
-
pit.poly(corners.flatMap((corner) => [corner.x, corner.y])).fill({
|
|
670
|
-
color,
|
|
671
|
-
alpha: 0.24,
|
|
672
|
-
});
|
|
673
|
-
pit.poly(corners.flatMap((corner) => [corner.x, corner.y])).stroke({
|
|
674
|
-
width: 3,
|
|
675
|
-
color,
|
|
676
|
-
alpha: 0.72,
|
|
677
|
-
join: 'round',
|
|
678
|
-
});
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
(pitLane.serviceAreas ?? []).forEach((serviceArea) => {
|
|
682
|
-
const teamColor = colorToNumber(serviceArea.teamColor, PIT_SPEED_LINE_COLOR);
|
|
683
|
-
if (serviceArea.queueCorners?.length >= 4) {
|
|
684
|
-
pit.poly(serviceArea.queueCorners.flatMap((corner) => [corner.x, corner.y])).fill({
|
|
685
|
-
color: teamColor,
|
|
686
|
-
alpha: 0.13,
|
|
687
|
-
});
|
|
688
|
-
pit.poly(serviceArea.queueCorners.flatMap((corner) => [corner.x, corner.y])).stroke({
|
|
689
|
-
width: 2,
|
|
690
|
-
color: teamColor,
|
|
691
|
-
alpha: 0.44,
|
|
692
|
-
join: 'round',
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
pit.poly(serviceArea.corners.flatMap((corner) => [corner.x, corner.y])).fill({
|
|
696
|
-
color: teamColor,
|
|
697
|
-
alpha: 0.2,
|
|
698
|
-
});
|
|
699
|
-
pit.poly(serviceArea.corners.flatMap((corner) => [corner.x, corner.y])).stroke({
|
|
700
|
-
width: 3,
|
|
701
|
-
color: teamColor,
|
|
702
|
-
alpha: 0.92,
|
|
703
|
-
join: 'round',
|
|
704
|
-
});
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
pitLane.boxes.forEach((box) => {
|
|
708
|
-
const teamColor = colorToNumber(box.teamColor, box.teamBoxIndex === 0 ? PIT_LINE_COLOR : PIT_SPEED_LINE_COLOR);
|
|
709
|
-
pit.poly(box.corners.flatMap((corner) => [corner.x, corner.y])).fill({
|
|
710
|
-
color: PIT_BOX_COLOR,
|
|
711
|
-
alpha: 0.96,
|
|
712
|
-
});
|
|
713
|
-
pit.poly(box.corners.flatMap((corner) => [corner.x, corner.y])).stroke({
|
|
714
|
-
width: 2,
|
|
715
|
-
color: teamColor,
|
|
716
|
-
alpha: 0.9,
|
|
717
|
-
join: 'round',
|
|
718
|
-
});
|
|
719
|
-
drawPolyline(pit, [box.laneTarget, box.center], {
|
|
720
|
-
width: 2,
|
|
721
|
-
color: teamColor,
|
|
722
|
-
alpha: 0.28,
|
|
723
|
-
cap: 'butt',
|
|
724
|
-
});
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
this.container.addChild(pit);
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
addBorders(track) {
|
|
731
|
-
const borders = new Graphics();
|
|
732
|
-
borders.label = 'track-borders';
|
|
733
|
-
|
|
734
|
-
[-1, 1].forEach((side) => {
|
|
735
|
-
drawSegmentedOffsetStroke(borders, track, {
|
|
736
|
-
side,
|
|
737
|
-
offset: track.width / 2,
|
|
738
|
-
width: 5,
|
|
739
|
-
color: 0xf8fafc,
|
|
740
|
-
alpha: 0.86,
|
|
741
|
-
step: 4,
|
|
742
|
-
});
|
|
743
|
-
|
|
744
|
-
drawSegmentedOffsetStroke(borders, track, {
|
|
745
|
-
side,
|
|
746
|
-
offset: track.width / 2 + 15,
|
|
747
|
-
width: 7,
|
|
748
|
-
color: 0x090a0d,
|
|
749
|
-
alpha: 0.56,
|
|
750
|
-
step: 4,
|
|
751
|
-
});
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
this.container.addChild(borders);
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
addKerbs(track) {
|
|
758
|
-
const kerbs = new Graphics();
|
|
759
|
-
kerbs.label = 'track-kerbs';
|
|
760
|
-
this.drawKerbStripes(kerbs, track, { clipUnsafeSegments: true });
|
|
761
|
-
this.container.addChild(kerbs);
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
addStartingGrid(track) {
|
|
765
|
-
const grid = new Graphics();
|
|
766
|
-
|
|
767
|
-
for (let index = 0; index < START_GRID_SLOT_COUNT; index += 1) {
|
|
768
|
-
const base = pointAt(track, START_GRID_FIRST_DISTANCE - index * START_GRID_SLOT_SPACING);
|
|
769
|
-
const lateralOffset = index % 2 === 0 ? -START_GRID_LATERAL_OFFSET : START_GRID_LATERAL_OFFSET;
|
|
770
|
-
const center = offsetTrackPoint(base, lateralOffset);
|
|
771
|
-
const forwardX = Math.cos(base.heading);
|
|
772
|
-
const forwardY = Math.sin(base.heading);
|
|
773
|
-
const normalX = base.normalX;
|
|
774
|
-
const normalY = base.normalY;
|
|
775
|
-
const halfLength = START_GRID_BOX_LENGTH / 2;
|
|
776
|
-
const halfWidth = START_GRID_BOX_WIDTH / 2;
|
|
777
|
-
const corners = [
|
|
778
|
-
{
|
|
779
|
-
x: center.x + forwardX * halfLength + normalX * halfWidth,
|
|
780
|
-
y: center.y + forwardY * halfLength + normalY * halfWidth,
|
|
781
|
-
},
|
|
782
|
-
{
|
|
783
|
-
x: center.x + forwardX * halfLength - normalX * halfWidth,
|
|
784
|
-
y: center.y + forwardY * halfLength - normalY * halfWidth,
|
|
785
|
-
},
|
|
786
|
-
{
|
|
787
|
-
x: center.x - forwardX * halfLength - normalX * halfWidth,
|
|
788
|
-
y: center.y - forwardY * halfLength - normalY * halfWidth,
|
|
789
|
-
},
|
|
790
|
-
{
|
|
791
|
-
x: center.x - forwardX * halfLength + normalX * halfWidth,
|
|
792
|
-
y: center.y - forwardY * halfLength + normalY * halfWidth,
|
|
793
|
-
},
|
|
794
|
-
];
|
|
795
|
-
|
|
796
|
-
grid.poly(corners.flatMap((corner) => [corner.x, corner.y])).stroke({
|
|
797
|
-
width: 3,
|
|
798
|
-
color: 0xf8fafc,
|
|
799
|
-
alpha: 0.8,
|
|
800
|
-
join: 'round',
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
this.container.addChild(grid);
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
addFinishLine(track) {
|
|
808
|
-
const finishLine = new Graphics();
|
|
809
|
-
const halfDepth = FINISH_LINE_DEPTH / 2;
|
|
810
|
-
this.addFinishLineHalf(finishLine, track, -halfDepth, 0, 0);
|
|
811
|
-
this.addFinishLineHalf(finishLine, track, 0, halfDepth, 1);
|
|
812
|
-
this.container.addChild(finishLine);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
addFinishLineHalf(graphics, track, startDistance, endDistance, rowOffset) {
|
|
816
|
-
const start = pointAt(track, startDistance);
|
|
817
|
-
const end = pointAt(track, endDistance);
|
|
818
|
-
const roadPadding = 10;
|
|
819
|
-
const width = track.width - roadPadding * 2;
|
|
820
|
-
const leftEdge = -width / 2;
|
|
821
|
-
const cellWidth = width / FINISH_LINE_COLUMNS;
|
|
822
|
-
|
|
823
|
-
for (let column = 0; column < FINISH_LINE_COLUMNS; column += 1) {
|
|
824
|
-
const innerOffset = leftEdge + column * cellWidth;
|
|
825
|
-
const outerOffset = innerOffset + cellWidth;
|
|
826
|
-
const color = (column + rowOffset) % 2 === 0 ? 0xf8fafc : 0x0b0d12;
|
|
827
|
-
const a = offsetTrackPoint(start, innerOffset);
|
|
828
|
-
const b = offsetTrackPoint(start, outerOffset);
|
|
829
|
-
const c = offsetTrackPoint(end, outerOffset);
|
|
830
|
-
const d = offsetTrackPoint(end, innerOffset);
|
|
831
|
-
|
|
832
|
-
graphics.poly([a.x, a.y, b.x, b.y, c.x, c.y, d.x, d.y]).fill(color);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
|
|
836
33
|
}
|