@inventure71/paddockjs 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +146 -1
- package/INSTALL_AND_UPDATE.md +15 -4
- package/README.md +84 -562
- package/docs/architecture.md +28 -18
- package/docs/composable-layouts.md +77 -0
- package/docs/custom_model_controller.md +4 -4
- package/docs/data-helpers.md +50 -0
- package/docs/data_contract.md +9 -7
- package/docs/getting-started.md +75 -0
- package/docs/headless-environment.md +68 -0
- package/docs/index.md +17 -4
- package/docs/rules.md +6 -2
- package/docs/system_specs.md +49 -6
- package/docs/theming.md +64 -0
- package/docs/training.md +20 -14
- package/docs/troubleshooting.md +76 -0
- package/docs/upgrading-hosts-to-3.md +42 -0
- package/examples/python/README.md +6 -0
- package/examples/python/base_policy_server.py +19 -0
- package/package.json +10 -3
- package/src/api/PaddockSimulatorController.js +78 -22
- package/src/api/runtimeTheme.js +59 -0
- package/src/app/F1SimulatorApp.js +24 -3
- package/src/app/readouts/timingTowerRenderer.js +203 -42
- package/src/app/runtime/frameLoop.js +5 -1
- package/src/config/defaultOptions.js +8 -1
- package/src/config/themeOptions.js +5 -0
- package/src/data/index.d.ts +212 -0
- package/src/data/index.js +14 -0
- package/src/environment/episode.js +11 -1
- package/src/environment/metrics.js +59 -16
- package/src/environment/observationVector.js +50 -33
- package/src/environment/observations.js +89 -20
- package/src/environment/runtime.js +41 -0
- package/src/environment/sensors/carRays.js +48 -37
- package/src/environment/sensors/index.js +501 -97
- package/src/environment/sensors/indexedRayBands.js +134 -46
- package/src/environment/sensors/rayBandTrace.js +1171 -0
- package/src/environment/sensors/rayChannels.js +108 -0
- package/src/environment/sensors/rayConfig.js +71 -25
- package/src/environment/sensors/rayGeometry.js +23 -17
- package/src/environment/sensors/sensorTargets.js +67 -1
- package/src/environment/sensors/surfaceRays.js +130 -89
- package/src/environment/sensors/trackRays.js +115 -1
- package/src/index.d.ts +43 -0
- package/src/index.js +27 -2
- package/src/simulation/collisionGeometry.js +260 -96
- package/src/simulation/pit/pitFlow.js +21 -9
- package/src/simulation/pit/pitQueue.js +5 -3
- package/src/simulation/pit/pitRouteMovement.js +9 -4
- package/src/simulation/pit/pitRouting.js +77 -42
- package/src/simulation/pit/pitState.js +1 -0
- package/src/simulation/pit/pitTireService.js +7 -3
- package/src/simulation/race/raceFinish.js +25 -8
- package/src/simulation/race/raceOrder.js +189 -32
- package/src/simulation/race/raceProgress.js +129 -37
- package/src/simulation/race/raceSetup.js +2 -0
- package/src/simulation/race/raceStep.js +25 -13
- package/src/simulation/raceSimulation.js +20 -17
- package/src/simulation/rules/collisionSteward.js +40 -23
- package/src/simulation/rules/rulesReview.js +77 -56
- package/src/simulation/snapshots/raceSnapshots.js +37 -0
- package/src/simulation/timing/drsTiming.js +143 -30
- package/src/simulation/timing/gapEstimation.js +25 -3
- package/src/simulation/timing/lapTelemetry.js +115 -36
- package/src/simulation/timing/raceTiming.js +5 -1
- package/src/simulation/timing/sectorPerformance.js +104 -26
- package/src/simulation/timing/timingHistory.js +229 -14
- package/src/simulation/timing/timingLines.js +65 -7
- package/src/simulation/track/pitLaneLayout.js +23 -0
- package/src/simulation/track/pitLaneState.js +218 -19
- package/src/simulation/track/spatialQueries.js +67 -11
- package/src/simulation/track/trackMath.js +91 -24
- package/src/simulation/track/trackModel.js +2 -0
- package/src/simulation/track/trackQueryIndex.js +1334 -100
- package/src/simulation/track/trackQueryPitIndex.js +138 -14
- package/src/simulation/track/trackQueryScratch.js +7 -0
- package/src/simulation/track/trackQueryStats.js +25 -0
- package/src/simulation/track/trackSnapshotJson.js +139 -0
- package/src/simulation/track/trackStatePolicy.js +249 -1
- package/src/simulation/vehicle/contactResolution.js +96 -59
- package/src/simulation/vehicle/mainTrackWheelSurface.js +73 -34
- package/src/simulation/vehicle/pitWheelSurface.js +81 -28
- package/src/simulation/vehicle/runoffResponse.js +54 -29
- package/src/simulation/vehicle/vehicleGeometry.js +245 -51
- package/src/simulation/vehicle/vehiclePhysics.js +42 -27
- package/src/simulation/vehicle/vehicleSnapshots.js +23 -0
- package/src/simulation/vehicle/vehicleState.js +6 -0
- package/src/simulation/vehicle/wheelSurface.js +807 -92
- package/src/styles.css +497 -497
|
@@ -3,48 +3,222 @@ import { normalizeRayOptions } from './rayConfig.js';
|
|
|
3
3
|
import { degreesToRadians, getCarRayOrigin } from './rayGeometry.js';
|
|
4
4
|
import { isSelfCarTarget, rayDetectableTargetsForSnapshot } from './sensorTargets.js';
|
|
5
5
|
import { createTrackMiss, createTrackRayContext, estimateTrackHit } from './trackRays.js';
|
|
6
|
-
import { createSurfaceMiss, estimateSurfaceHits
|
|
6
|
+
import { createSurfaceMiss, estimateSurfaceHits } from './surfaceRays.js';
|
|
7
7
|
import { canUseBatchTrainingRayApproximation } from './rayGuards.js';
|
|
8
|
+
import { traceIndexedRayBands } from './rayBandTrace.js';
|
|
9
|
+
import { createRayChannelFlags, writeRayChannelFlags } from './rayChannels.js';
|
|
10
|
+
import { pitOverrideAllowedForCar } from '../../simulation/track/trackStatePolicy.js';
|
|
8
11
|
|
|
9
12
|
export { buildNearbyCars } from './nearbyCars.js';
|
|
10
13
|
export { DEFAULT_RAY_ANGLES_DEGREES } from './rayDefaults.js';
|
|
11
|
-
export {
|
|
14
|
+
export { RAY_CHANNELS } from './rayChannels.js';
|
|
15
|
+
export { normalizeRayOptions, RAY_LAYOUT_PRESETS } from './rayConfig.js';
|
|
12
16
|
export { getCarRayOrigin, getCarRayVector } from './rayGeometry.js';
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
const CHANNEL_CONTEXT = Symbol('rayChannelContext');
|
|
19
|
+
|
|
20
|
+
export function createRayBatchContext(snapshot, { scratch = false } = {}) {
|
|
21
|
+
const contextScratch = scratch === true
|
|
22
|
+
? {}
|
|
23
|
+
: scratch && typeof scratch === 'object'
|
|
24
|
+
? scratch
|
|
25
|
+
: null;
|
|
26
|
+
const context = {
|
|
27
|
+
rayTargets: rayDetectableTargetsForSnapshot(snapshot, contextScratch),
|
|
17
28
|
};
|
|
29
|
+
if (contextScratch) context.scratch = contextScratch;
|
|
30
|
+
return context;
|
|
18
31
|
}
|
|
19
32
|
|
|
20
|
-
|
|
33
|
+
function getRayScratch(batchContext) {
|
|
34
|
+
return batchContext?.scratch ?? null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getNormalizedRayOptions(rayOptions, scratch) {
|
|
38
|
+
if (!scratch || !rayOptions || typeof rayOptions !== 'object') return normalizeRayOptions(rayOptions);
|
|
39
|
+
if (rayOptions?.rays && rayOptions?.channels && rayOptions?.anglesDegrees) {
|
|
40
|
+
return normalizeRayOptions(rayOptions);
|
|
41
|
+
}
|
|
42
|
+
const cache = scratch.normalizedRayOptionsBySource ?? new WeakMap();
|
|
43
|
+
scratch.normalizedRayOptionsBySource = cache;
|
|
44
|
+
const cached = cache.get(rayOptions);
|
|
45
|
+
if (cached) return cached;
|
|
21
46
|
const normalized = normalizeRayOptions(rayOptions);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
47
|
+
cache.set(rayOptions, normalized);
|
|
48
|
+
return normalized;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getRayOrigin(car, scratch) {
|
|
52
|
+
if (!scratch) return getCarRayOrigin(car);
|
|
53
|
+
const origin = scratch.origin ?? {};
|
|
54
|
+
scratch.origin = origin;
|
|
55
|
+
origin.x = car.x;
|
|
56
|
+
origin.y = car.y;
|
|
57
|
+
return origin;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getFilteredCarTargets(car, snapshot, batchContext, scratch) {
|
|
61
|
+
const targets = batchContext?.rayTargets ?? rayDetectableTargetsForSnapshot(snapshot);
|
|
62
|
+
if (!scratch) return targets.filter((target) => !isSelfCarTarget(car, target));
|
|
63
|
+
const filtered = scratch.carTargets ?? [];
|
|
64
|
+
scratch.carTargets = filtered;
|
|
65
|
+
let count = 0;
|
|
66
|
+
for (let index = 0; index < targets.length; index += 1) {
|
|
67
|
+
const target = targets[index];
|
|
68
|
+
if (isSelfCarTarget(car, target)) continue;
|
|
69
|
+
filtered[count] = target;
|
|
70
|
+
count += 1;
|
|
25
71
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const trackContext = !usesTrackContext
|
|
30
|
-
? null
|
|
31
|
-
: createTrackRayContext(car, snapshot, origin, normalized.precision);
|
|
32
|
-
const carTargets = normalized.channels.includes('car')
|
|
33
|
-
? (batchContext?.rayTargets ?? rayDetectableTargetsForSnapshot(snapshot)).filter((target) => !isSelfCarTarget(car, target))
|
|
34
|
-
: [];
|
|
72
|
+
filtered.length = count;
|
|
73
|
+
return filtered;
|
|
74
|
+
}
|
|
35
75
|
|
|
36
|
-
|
|
37
|
-
|
|
76
|
+
function prepareScratchArray(scratch, arrayKey, poolKey, count, createItem) {
|
|
77
|
+
if (!scratch) return null;
|
|
78
|
+
const values = scratch[arrayKey] ?? [];
|
|
79
|
+
const pool = scratch[poolKey] ?? [];
|
|
80
|
+
scratch[arrayKey] = values;
|
|
81
|
+
scratch[poolKey] = pool;
|
|
82
|
+
values.length = count;
|
|
83
|
+
for (let index = 0; index < count; index += 1) {
|
|
84
|
+
const item = pool[index] ?? createItem();
|
|
85
|
+
pool[index] = item;
|
|
86
|
+
values[index] = item;
|
|
38
87
|
}
|
|
88
|
+
return values;
|
|
89
|
+
}
|
|
39
90
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
x: Math.cos((car.heading ?? 0) +
|
|
44
|
-
y: Math.sin((car.heading ?? 0) +
|
|
91
|
+
function getScratchRayVector(scratch, index, car, angleRadians) {
|
|
92
|
+
if (!scratch) {
|
|
93
|
+
return {
|
|
94
|
+
x: Math.cos((car.heading ?? 0) + angleRadians),
|
|
95
|
+
y: Math.sin((car.heading ?? 0) + angleRadians),
|
|
45
96
|
};
|
|
46
|
-
|
|
47
|
-
|
|
97
|
+
}
|
|
98
|
+
const vectors = scratch.rayVectors ?? [];
|
|
99
|
+
scratch.rayVectors = vectors;
|
|
100
|
+
const vector = vectors[index] ?? {};
|
|
101
|
+
vectors[index] = vector;
|
|
102
|
+
vector.x = Math.cos((car.heading ?? 0) + angleRadians);
|
|
103
|
+
vector.y = Math.sin((car.heading ?? 0) + angleRadians);
|
|
104
|
+
return vector;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getSharedRayQuery(scratch, index) {
|
|
108
|
+
if (!scratch) return {};
|
|
109
|
+
const queries = scratch.sharedRayQueries ?? [];
|
|
110
|
+
scratch.sharedRayQueries = queries;
|
|
111
|
+
const query = queries[index] ?? {};
|
|
112
|
+
queries[index] = query;
|
|
113
|
+
query.carHitResult = query.carHitResult ?? {};
|
|
114
|
+
query.surfaceBoundaries = null;
|
|
115
|
+
query.trackBandBoundaries = null;
|
|
116
|
+
query.boundaryStateCache = null;
|
|
117
|
+
query.sampleStateCache = null;
|
|
118
|
+
return query;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getCarRayStats(scratch) {
|
|
122
|
+
if (!scratch) return null;
|
|
123
|
+
const stats = scratch.carRayStats ?? {
|
|
124
|
+
callerVectorCount: 0,
|
|
125
|
+
computedVectorCount: 0,
|
|
126
|
+
resultTargetCount: 0,
|
|
127
|
+
};
|
|
128
|
+
scratch.carRayStats = stats;
|
|
129
|
+
return stats;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function estimateCarHitForRay(car, snapshot, ray, origin, vector, carTargets, sharedRayQuery, scratch) {
|
|
133
|
+
return estimateCarHit(car, snapshot, ray.angleDegrees, ray.lengthMeters, origin, carTargets, {
|
|
134
|
+
rayVector: vector,
|
|
135
|
+
resultTarget: sharedRayQuery?.carHitResult,
|
|
136
|
+
stats: getCarRayStats(scratch),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getRayBandTraceStats(scratch) {
|
|
141
|
+
if (!scratch) return null;
|
|
142
|
+
const stats = scratch.rayBandTraceStats ?? {
|
|
143
|
+
directPathCount: 0,
|
|
144
|
+
sampledPathCount: 0,
|
|
145
|
+
fallbackCount: 0,
|
|
146
|
+
channelSetAllocations: 0,
|
|
147
|
+
};
|
|
148
|
+
scratch.rayBandTraceStats = stats;
|
|
149
|
+
return stats;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function createRayChannelContext(normalized) {
|
|
153
|
+
if (normalized?.[CHANNEL_CONTEXT]) return normalized[CHANNEL_CONTEXT];
|
|
154
|
+
const channels = writeRayChannelFlags(createRayChannelFlags(), normalized.channels);
|
|
155
|
+
const context = {
|
|
156
|
+
channels,
|
|
157
|
+
surfaceChannels: channels.surfaceChannels,
|
|
158
|
+
hasRoadEdge: channels.hasRoadEdge,
|
|
159
|
+
hasCar: channels.hasCar,
|
|
160
|
+
hasRoadOrSurface: channels.hasRoadEdge || channels.surfaceChannels.length > 0,
|
|
161
|
+
};
|
|
162
|
+
if (normalized) {
|
|
163
|
+
Object.defineProperty(normalized, CHANNEL_CONTEXT, {
|
|
164
|
+
value: context,
|
|
165
|
+
enumerable: false,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return context;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function estimateRayBands({
|
|
172
|
+
car,
|
|
173
|
+
snapshot,
|
|
174
|
+
ray,
|
|
175
|
+
angleDegrees,
|
|
176
|
+
origin,
|
|
177
|
+
vector,
|
|
178
|
+
normalized,
|
|
179
|
+
channelContext,
|
|
180
|
+
trackContext,
|
|
181
|
+
sharedRayQuery,
|
|
182
|
+
scratch,
|
|
183
|
+
}) {
|
|
184
|
+
const stats = getRayBandTraceStats(scratch);
|
|
185
|
+
const canUseRayBandTrace = channelContext.hasRoadOrSurface &&
|
|
186
|
+
trackContext?.originState;
|
|
187
|
+
if (canUseRayBandTrace) {
|
|
188
|
+
const direct = traceIndexedRayBands({
|
|
189
|
+
track: snapshot.track,
|
|
190
|
+
origin,
|
|
191
|
+
vector,
|
|
192
|
+
lengthMeters: ray.lengthMeters,
|
|
193
|
+
originState: trackContext.originState,
|
|
194
|
+
channels: channelContext.channels,
|
|
195
|
+
precision: normalized.precision,
|
|
196
|
+
sharedRayQuery,
|
|
197
|
+
stats,
|
|
198
|
+
validateBoundaries: car?.interaction?.profile !== 'batch-training',
|
|
199
|
+
allowPitOverride: car?.interaction?.profile === 'batch-training'
|
|
200
|
+
? false
|
|
201
|
+
: pitOverrideAllowedForCar(car),
|
|
202
|
+
mainTrackOnly: car?.interaction?.profile === 'batch-training',
|
|
203
|
+
});
|
|
204
|
+
if (direct.available) {
|
|
205
|
+
if (stats) {
|
|
206
|
+
if (direct.path === 'sampled') stats.sampledPathCount += 1;
|
|
207
|
+
else stats.directPathCount += 1;
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
roadEdge: direct.roadEdge,
|
|
211
|
+
surfaceHits: {
|
|
212
|
+
kerb: direct.kerb,
|
|
213
|
+
illegalSurface: direct.illegalSurface,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
if (stats) stats.fallbackCount += 1;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
roadEdge: channelContext.hasRoadEdge
|
|
48
222
|
? estimateTrackHit(
|
|
49
223
|
car,
|
|
50
224
|
snapshot,
|
|
@@ -53,47 +227,297 @@ export function buildRaySensors(car, snapshot, rayOptions = {}, batchContext = n
|
|
|
53
227
|
trackContext,
|
|
54
228
|
{ precision: normalized.precision, sharedRayQuery },
|
|
55
229
|
)
|
|
56
|
-
: createTrackMiss(ray.lengthMeters)
|
|
57
|
-
|
|
58
|
-
|
|
230
|
+
: createTrackMiss(ray.lengthMeters),
|
|
231
|
+
surfaceHits: estimateSurfaceHits(
|
|
232
|
+
car,
|
|
233
|
+
snapshot,
|
|
234
|
+
ray,
|
|
235
|
+
origin,
|
|
236
|
+
vector,
|
|
237
|
+
channelContext.surfaceChannels,
|
|
238
|
+
trackContext,
|
|
239
|
+
{ precision: normalized.precision, sharedRayQuery },
|
|
240
|
+
),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function writeRichRay(target, ray, angleRadians, roadEdge, carHit, surfaceHits, channels) {
|
|
245
|
+
target.id = ray.id;
|
|
246
|
+
target.angleDegrees = ray.angleDegrees;
|
|
247
|
+
target.angleRadians = angleRadians;
|
|
248
|
+
target.lengthMeters = ray.lengthMeters;
|
|
249
|
+
target.roadEdge = writeTrackRayHit(target.roadEdge ?? {}, roadEdge);
|
|
250
|
+
target.track = target.roadEdge;
|
|
251
|
+
target.kerb = channels.hasKerb
|
|
252
|
+
? writeSurfaceRayHit(target.kerb ?? {}, surfaceHits.kerb ?? createSurfaceMiss(ray.lengthMeters))
|
|
253
|
+
: writeSurfaceRayHit(target.kerb ?? {}, createSurfaceMiss(ray.lengthMeters));
|
|
254
|
+
target.illegalSurface = channels.hasIllegalSurface
|
|
255
|
+
? writeSurfaceRayHit(target.illegalSurface ?? {}, surfaceHits.illegalSurface ?? createSurfaceMiss(ray.lengthMeters))
|
|
256
|
+
: writeSurfaceRayHit(target.illegalSurface ?? {}, createSurfaceMiss(ray.lengthMeters));
|
|
257
|
+
target.car = writeCarRayHit(target.car ?? {}, carHit);
|
|
258
|
+
return target;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function writeTrackRayHit(target, source) {
|
|
262
|
+
target.hit = Boolean(source?.hit);
|
|
263
|
+
target.distanceMeters = source?.distanceMeters;
|
|
264
|
+
target.kind = source?.kind ?? null;
|
|
265
|
+
return target;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function writeSurfaceRayHit(target, source) {
|
|
269
|
+
target.hit = Boolean(source?.hit);
|
|
270
|
+
target.distanceMeters = source?.distanceMeters;
|
|
271
|
+
target.surface = source?.surface ?? null;
|
|
272
|
+
return target;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function writeCarRayHit(target, source) {
|
|
276
|
+
target.hit = Boolean(source?.hit);
|
|
277
|
+
target.distanceMeters = source?.distanceMeters;
|
|
278
|
+
target.driverId = source?.driverId ?? null;
|
|
279
|
+
target.targetId = source?.targetId ?? null;
|
|
280
|
+
target.targetType = source?.targetType ?? null;
|
|
281
|
+
target.relativeSpeedKph = source?.relativeSpeedKph ?? 0;
|
|
282
|
+
return target;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function buildRaySensors(car, snapshot, rayOptions = {}, batchContext = null) {
|
|
286
|
+
const scratch = getRayScratch(batchContext);
|
|
287
|
+
const normalized = getNormalizedRayOptions(rayOptions, scratch);
|
|
288
|
+
if (!normalized.enabled) return [];
|
|
289
|
+
const channelContext = createRayChannelContext(normalized);
|
|
290
|
+
if (car?.destroyed || car?.outOfRace) {
|
|
291
|
+
return buildInactiveCarRays(normalized, scratch);
|
|
292
|
+
}
|
|
293
|
+
const origin = getRayOrigin(car, scratch);
|
|
294
|
+
const usesTrackContext = channelContext.hasRoadOrSurface;
|
|
295
|
+
const trackContext = !usesTrackContext
|
|
296
|
+
? null
|
|
297
|
+
: createTrackRayContext(car, snapshot, origin, normalized.precision);
|
|
298
|
+
const carTargets = channelContext.hasCar
|
|
299
|
+
? getFilteredCarTargets(car, snapshot, batchContext, scratch)
|
|
300
|
+
: [];
|
|
301
|
+
|
|
302
|
+
if (canUseFastBatchTrainingRays(car, snapshot, trackContext)) {
|
|
303
|
+
return buildFastBatchTrainingRays(car, snapshot, normalized, channelContext, origin, carTargets, trackContext, scratch);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const scratchRays = prepareScratchArray(scratch, 'rays', 'rayPool', normalized.rays.length, () => ({}));
|
|
307
|
+
const rays = scratchRays ?? normalized.rays.map(() => ({}));
|
|
308
|
+
for (let index = 0; index < normalized.rays.length; index += 1) {
|
|
309
|
+
const ray = normalized.rays[index];
|
|
310
|
+
const angleDegrees = ray.angleDegrees;
|
|
311
|
+
const angleRadians = degreesToRadians(angleDegrees);
|
|
312
|
+
const vector = getScratchRayVector(scratch, index, car, angleRadians);
|
|
313
|
+
const sharedRayQuery = getSharedRayQuery(scratch, index);
|
|
314
|
+
const { roadEdge, surfaceHits } = estimateRayBands({
|
|
315
|
+
car,
|
|
316
|
+
snapshot,
|
|
317
|
+
ray,
|
|
318
|
+
angleDegrees,
|
|
319
|
+
origin,
|
|
320
|
+
vector,
|
|
321
|
+
normalized,
|
|
322
|
+
channelContext,
|
|
323
|
+
trackContext,
|
|
324
|
+
sharedRayQuery,
|
|
325
|
+
scratch,
|
|
326
|
+
});
|
|
327
|
+
const carHit = channelContext.hasCar
|
|
328
|
+
? estimateCarHitForRay(car, snapshot, ray, origin, vector, carTargets, sharedRayQuery, scratch)
|
|
59
329
|
: createCarRayMiss(ray.lengthMeters);
|
|
60
|
-
|
|
330
|
+
|
|
331
|
+
writeRichRay(rays[index], ray, angleRadians, roadEdge, carHit, surfaceHits, channelContext.channels);
|
|
332
|
+
}
|
|
333
|
+
return rays;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function buildRaySensorVectorValues(car, snapshot, rayOptions = {}, batchContext = null) {
|
|
337
|
+
const scratch = getRayScratch(batchContext);
|
|
338
|
+
const normalized = getNormalizedRayOptions(rayOptions, scratch);
|
|
339
|
+
if (!normalized.enabled) return [];
|
|
340
|
+
const channelContext = createRayChannelContext(normalized);
|
|
341
|
+
const surfaceChannels = channelContext.surfaceChannels;
|
|
342
|
+
if (car?.destroyed || car?.outOfRace) {
|
|
343
|
+
return buildInactiveRayVectorValues(normalized, surfaceChannels, scratch);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const origin = getRayOrigin(car, scratch);
|
|
347
|
+
const usesTrackContext = channelContext.hasRoadOrSurface;
|
|
348
|
+
const trackContext = !usesTrackContext
|
|
349
|
+
? null
|
|
350
|
+
: createTrackRayContext(car, snapshot, origin, normalized.precision);
|
|
351
|
+
const carTargets = channelContext.hasCar
|
|
352
|
+
? getFilteredCarTargets(car, snapshot, batchContext, scratch)
|
|
353
|
+
: [];
|
|
354
|
+
|
|
355
|
+
const vectorValues = prepareScratchArray(scratch, 'rayVectorValues', 'rayVectorValuePool', normalized.rays.length, () => []);
|
|
356
|
+
const values = vectorValues ?? normalized.rays.map(() => []);
|
|
357
|
+
for (let index = 0; index < normalized.rays.length; index += 1) {
|
|
358
|
+
const ray = normalized.rays[index];
|
|
359
|
+
const angleRadians = degreesToRadians(ray.angleDegrees);
|
|
360
|
+
const vector = getScratchRayVector(scratch, index, car, angleRadians);
|
|
361
|
+
const sharedRayQuery = getSharedRayQuery(scratch, index);
|
|
362
|
+
const { roadEdge, surfaceHits } = estimateRayBands({
|
|
61
363
|
car,
|
|
62
364
|
snapshot,
|
|
63
365
|
ray,
|
|
366
|
+
angleDegrees: ray.angleDegrees,
|
|
64
367
|
origin,
|
|
65
368
|
vector,
|
|
66
|
-
normalized
|
|
369
|
+
normalized,
|
|
370
|
+
channelContext,
|
|
67
371
|
trackContext,
|
|
68
|
-
|
|
69
|
-
|
|
372
|
+
sharedRayQuery,
|
|
373
|
+
scratch,
|
|
374
|
+
});
|
|
375
|
+
const carHit = channelContext.hasCar
|
|
376
|
+
? estimateCarHitForRay(car, snapshot, ray, origin, vector, carTargets, sharedRayQuery, scratch)
|
|
377
|
+
: createCarRayMiss(ray.lengthMeters);
|
|
378
|
+
writeRayVectorValues(values[index], ray.lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels);
|
|
379
|
+
}
|
|
380
|
+
return values;
|
|
381
|
+
}
|
|
70
382
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
383
|
+
export function appendRaySensorVectorValues(target, car, snapshot, rayOptions = {}, batchContext = null) {
|
|
384
|
+
const scratch = getRayScratch(batchContext);
|
|
385
|
+
const normalized = getNormalizedRayOptions(rayOptions, scratch);
|
|
386
|
+
if (!normalized.enabled) return target;
|
|
387
|
+
const channelContext = createRayChannelContext(normalized);
|
|
388
|
+
const surfaceChannels = channelContext.surfaceChannels;
|
|
389
|
+
if (car?.destroyed || car?.outOfRace) {
|
|
390
|
+
for (let index = 0; index < normalized.rays.length; index += 1) {
|
|
391
|
+
const ray = normalized.rays[index];
|
|
392
|
+
appendRayVectorValues(
|
|
393
|
+
target,
|
|
394
|
+
ray.lengthMeters,
|
|
395
|
+
createTrackMiss(ray.lengthMeters),
|
|
396
|
+
createCarRayMiss(ray.lengthMeters),
|
|
397
|
+
{},
|
|
398
|
+
surfaceChannels,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return target;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const origin = getRayOrigin(car, scratch);
|
|
405
|
+
const usesTrackContext = channelContext.hasRoadOrSurface;
|
|
406
|
+
const trackContext = !usesTrackContext
|
|
407
|
+
? null
|
|
408
|
+
: createTrackRayContext(car, snapshot, origin, normalized.precision);
|
|
409
|
+
const carTargets = channelContext.hasCar
|
|
410
|
+
? getFilteredCarTargets(car, snapshot, batchContext, scratch)
|
|
411
|
+
: [];
|
|
412
|
+
|
|
413
|
+
for (let index = 0; index < normalized.rays.length; index += 1) {
|
|
414
|
+
const ray = normalized.rays[index];
|
|
415
|
+
const angleRadians = degreesToRadians(ray.angleDegrees);
|
|
416
|
+
const vector = getScratchRayVector(scratch, index, car, angleRadians);
|
|
417
|
+
const sharedRayQuery = getSharedRayQuery(scratch, index);
|
|
418
|
+
const { roadEdge, surfaceHits } = estimateRayBands({
|
|
419
|
+
car,
|
|
420
|
+
snapshot,
|
|
421
|
+
ray,
|
|
422
|
+
angleDegrees: ray.angleDegrees,
|
|
423
|
+
origin,
|
|
424
|
+
vector,
|
|
425
|
+
normalized,
|
|
426
|
+
channelContext,
|
|
427
|
+
trackContext,
|
|
428
|
+
sharedRayQuery,
|
|
429
|
+
scratch,
|
|
430
|
+
});
|
|
431
|
+
const carHit = channelContext.hasCar
|
|
432
|
+
? estimateCarHitForRay(car, snapshot, ray, origin, vector, carTargets, sharedRayQuery, scratch)
|
|
433
|
+
: createCarRayMiss(ray.lengthMeters);
|
|
434
|
+
appendRayVectorValues(target, ray.lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels);
|
|
435
|
+
}
|
|
436
|
+
return target;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function buildInactiveCarRays(normalized, scratch = null) {
|
|
440
|
+
const channelContext = createRayChannelContext(normalized);
|
|
441
|
+
const rays = prepareScratchArray(scratch, 'rays', 'rayPool', normalized.rays.length, () => ({})) ??
|
|
442
|
+
normalized.rays.map(() => ({}));
|
|
443
|
+
for (let index = 0; index < normalized.rays.length; index += 1) {
|
|
444
|
+
const ray = normalized.rays[index];
|
|
445
|
+
const roadEdge = createTrackMiss(ray.lengthMeters);
|
|
446
|
+
writeRichRay(
|
|
447
|
+
rays[index],
|
|
448
|
+
ray,
|
|
449
|
+
degreesToRadians(ray.angleDegrees),
|
|
76
450
|
roadEdge,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
451
|
+
createCarRayMiss(ray.lengthMeters),
|
|
452
|
+
{},
|
|
453
|
+
channelContext.channels,
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
return rays;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function createInactiveRayVectorValues(ray, surfaceChannels) {
|
|
460
|
+
return rayVectorValues(
|
|
461
|
+
ray.lengthMeters,
|
|
462
|
+
createTrackMiss(ray.lengthMeters),
|
|
463
|
+
createCarRayMiss(ray.lengthMeters),
|
|
464
|
+
{},
|
|
465
|
+
surfaceChannels,
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function buildInactiveRayVectorValues(normalized, surfaceChannels, scratch = null) {
|
|
470
|
+
const values = prepareScratchArray(scratch, 'rayVectorValues', 'rayVectorValuePool', normalized.rays.length, () => []) ??
|
|
471
|
+
normalized.rays.map(() => []);
|
|
472
|
+
for (let index = 0; index < normalized.rays.length; index += 1) {
|
|
473
|
+
const ray = normalized.rays[index];
|
|
474
|
+
writeRayVectorValues(
|
|
475
|
+
values[index],
|
|
476
|
+
ray.lengthMeters,
|
|
477
|
+
createTrackMiss(ray.lengthMeters),
|
|
478
|
+
createCarRayMiss(ray.lengthMeters),
|
|
479
|
+
{},
|
|
480
|
+
surfaceChannels,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
return values;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function rayVectorValues(lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels) {
|
|
487
|
+
return writeRayVectorValues([], lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function writeRayVectorValues(values, lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels) {
|
|
491
|
+
values.length = 0;
|
|
492
|
+
appendRayVectorValues(values, lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels);
|
|
493
|
+
return values;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function appendRayVectorValues(values, lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels) {
|
|
497
|
+
values.push(
|
|
498
|
+
ratio(roadEdge.distanceMeters, lengthMeters),
|
|
499
|
+
roadEdge.hit ? 1 : 0,
|
|
500
|
+
roadEdge.kind === 'exit' ? 1 : 0,
|
|
501
|
+
roadEdge.kind === 'entry' ? 1 : 0,
|
|
502
|
+
ratio(carHit.distanceMeters, lengthMeters),
|
|
503
|
+
carHit.hit ? 1 : 0,
|
|
504
|
+
carHit.relativeSpeedKph / 200,
|
|
505
|
+
carHit.targetType === 'replayGhost' ? 1 : 0,
|
|
506
|
+
);
|
|
507
|
+
for (let index = 0; index < surfaceChannels.length; index += 1) {
|
|
508
|
+
const channel = surfaceChannels[index];
|
|
509
|
+
const hit = surfaceHits[channel] ?? createSurfaceMiss(lengthMeters);
|
|
510
|
+
values.push(
|
|
511
|
+
ratio(hit.distanceMeters, lengthMeters),
|
|
512
|
+
hit.hit ? 1 : 0,
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
return values;
|
|
83
516
|
}
|
|
84
517
|
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
angleDegrees: ray.angleDegrees,
|
|
89
|
-
angleRadians: degreesToRadians(ray.angleDegrees),
|
|
90
|
-
lengthMeters: ray.lengthMeters,
|
|
91
|
-
roadEdge: createTrackMiss(ray.lengthMeters),
|
|
92
|
-
track: createTrackMiss(ray.lengthMeters),
|
|
93
|
-
kerb: createSurfaceMiss(ray.lengthMeters),
|
|
94
|
-
illegalSurface: createSurfaceMiss(ray.lengthMeters),
|
|
95
|
-
car: createCarRayMiss(ray.lengthMeters),
|
|
96
|
-
}));
|
|
518
|
+
function ratio(value, max) {
|
|
519
|
+
const finite = Number.isFinite(value) ? value : max;
|
|
520
|
+
return Math.max(0, Math.min(1, finite / Math.max(1e-9, max)));
|
|
97
521
|
}
|
|
98
522
|
|
|
99
523
|
function canUseFastBatchTrainingRays(car, snapshot, trackContext) {
|
|
@@ -106,52 +530,32 @@ function canUseFastBatchTrainingRays(car, snapshot, trackContext) {
|
|
|
106
530
|
canUseBatchTrainingRayApproximation(snapshot.track, trackContext.originState);
|
|
107
531
|
}
|
|
108
532
|
|
|
109
|
-
function buildFastBatchTrainingRays(car, snapshot, normalized, origin, carTargets, trackContext) {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
533
|
+
function buildFastBatchTrainingRays(car, snapshot, normalized, channelContext, origin, carTargets, trackContext, scratch = null) {
|
|
534
|
+
const rays = prepareScratchArray(scratch, 'rays', 'rayPool', normalized.rays.length, () => ({})) ??
|
|
535
|
+
normalized.rays.map(() => ({}));
|
|
536
|
+
for (let index = 0; index < normalized.rays.length; index += 1) {
|
|
537
|
+
const ray = normalized.rays[index];
|
|
113
538
|
const angleRadians = degreesToRadians(ray.angleDegrees);
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const roadEdge = channels.has('roadEdge')
|
|
118
|
-
? estimateTrackHit(
|
|
119
|
-
car,
|
|
120
|
-
snapshot,
|
|
121
|
-
ray.angleDegrees,
|
|
122
|
-
ray.lengthMeters,
|
|
123
|
-
trackContext,
|
|
124
|
-
{ precision: normalized.precision, sharedRayQuery },
|
|
125
|
-
)
|
|
126
|
-
: createTrackMiss(ray.lengthMeters);
|
|
127
|
-
const carHit = channels.has('car') && carTargets.length > 0
|
|
128
|
-
? estimateCarHit(car, snapshot, ray.angleDegrees, ray.lengthMeters, origin, carTargets)
|
|
129
|
-
: createCarRayMiss(ray.lengthMeters);
|
|
130
|
-
const surfaceHits = estimateSurfaceHits(
|
|
539
|
+
const vector = getScratchRayVector(scratch, index, car, angleRadians);
|
|
540
|
+
const sharedRayQuery = getSharedRayQuery(scratch, index);
|
|
541
|
+
const { roadEdge, surfaceHits } = estimateRayBands({
|
|
131
542
|
car,
|
|
132
543
|
snapshot,
|
|
133
544
|
ray,
|
|
545
|
+
angleDegrees: ray.angleDegrees,
|
|
134
546
|
origin,
|
|
135
547
|
vector,
|
|
136
|
-
normalized
|
|
548
|
+
normalized,
|
|
549
|
+
channelContext,
|
|
137
550
|
trackContext,
|
|
138
|
-
|
|
139
|
-
|
|
551
|
+
sharedRayQuery,
|
|
552
|
+
scratch,
|
|
553
|
+
});
|
|
554
|
+
const carHit = channelContext.hasCar && carTargets.length > 0
|
|
555
|
+
? estimateCarHitForRay(car, snapshot, ray, origin, vector, carTargets, sharedRayQuery, scratch)
|
|
556
|
+
: createCarRayMiss(ray.lengthMeters);
|
|
140
557
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
angleRadians,
|
|
145
|
-
lengthMeters: ray.lengthMeters,
|
|
146
|
-
roadEdge,
|
|
147
|
-
track: roadEdge,
|
|
148
|
-
kerb: channels.has('kerb')
|
|
149
|
-
? (surfaceHits.kerb ?? createSurfaceMiss(ray.lengthMeters))
|
|
150
|
-
: createSurfaceMiss(ray.lengthMeters),
|
|
151
|
-
illegalSurface: channels.has('illegalSurface')
|
|
152
|
-
? (surfaceHits.illegalSurface ?? createSurfaceMiss(ray.lengthMeters))
|
|
153
|
-
: createSurfaceMiss(ray.lengthMeters),
|
|
154
|
-
car: carHit,
|
|
155
|
-
};
|
|
156
|
-
});
|
|
558
|
+
writeRichRay(rays[index], ray, angleRadians, roadEdge, carHit, surfaceHits, channelContext.channels);
|
|
559
|
+
}
|
|
560
|
+
return rays;
|
|
157
561
|
}
|