@inventure71/paddockjs 4.2.0 → 4.3.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 +40 -0
- package/INSTALL_AND_UPDATE.md +24 -4
- package/README.md +31 -2
- package/docs/advanced_ai_pace.md +131 -0
- package/docs/advanced_physics_rebuild.md +166 -0
- package/docs/architecture.md +109 -30
- package/docs/cleanup_coverage.md +284 -0
- package/docs/component_inventory.md +31 -15
- package/docs/composable-layouts.md +2 -0
- package/docs/concepts.md +12 -1
- package/docs/custom_model_controller.md +1 -1
- package/docs/data_contract.md +33 -10
- package/docs/data_model.md +4 -2
- package/docs/efficiency_cleanup_2026-10-13.md +80 -0
- package/docs/grid_walk.md +84 -0
- package/docs/index.md +1 -0
- package/docs/keyboard_driving_fix.md +118 -0
- package/docs/limit_driving_2026-10-14.md +88 -0
- package/docs/maintainability_audit_2026-09-08.md +146 -0
- package/docs/racing_ai_improvements.md +145 -0
- package/docs/rules.md +43 -13
- package/docs/sense_contract.md +4 -1
- package/docs/system_specs.md +38 -11
- package/docs/testing-reliability-guide.md +5 -0
- package/docs/v11_improvements.md +108 -0
- package/package.json +24 -13
- package/src/api/MountedSimulatorComponents.js +101 -0
- package/src/api/PaddockSimulatorController.js +56 -81
- package/src/app/BrowserExpertAdapter.js +0 -6
- package/src/app/BrowserExpertExternalRenderer.js +72 -50
- package/src/app/ExternalTrackSnapshot.js +50 -0
- package/src/app/F1SimulatorApp.js +242 -297
- package/src/app/HostLifecycleEmitter.js +93 -0
- package/src/app/banners/raceDataBannerController.js +9 -4
- package/src/app/camera/cameraConstants.js +1 -1
- package/src/app/camera/cameraController.js +10 -13
- package/src/app/camera/cameraGeometry.js +1 -1
- package/src/app/controls/SimulatorViewControls.js +45 -0
- package/src/app/controls/workbenchBindings.js +64 -0
- package/src/app/controls/workbenchDisclosure.js +41 -0
- package/src/app/domBindings.js +21 -13
- package/src/app/gridWalk/GridWalkController.js +180 -0
- package/src/app/gridWalk/gridWalkCamera.js +88 -0
- package/src/app/gridWalk/gridWalkControls.js +24 -0
- package/src/app/gridWalk/gridWalkCrew.js +246 -0
- package/src/app/gridWalk/gridWalkLabels.js +104 -0
- package/src/app/gridWalk/gridWalkLayout.js +46 -0
- package/src/app/gridWalk/gridWalkNavigation.js +106 -0
- package/src/app/gridWalk/gridWalkProjectBanner.js +43 -0
- package/src/app/gridWalk/gridWalkSession.js +50 -0
- package/src/app/layoutSupport.js +2 -0
- package/src/app/raceOverlayClearanceSupport.js +21 -7
- package/src/app/readouts/carOverviewRenderer.js +9 -4
- package/src/app/readouts/readoutFormatters.js +1 -8
- package/src/app/readouts/telemetryRenderer.js +1 -3
- package/src/app/readouts/timingTowerRenderer.js +48 -50
- package/src/app/rendering/appAssets.js +8 -10
- package/src/app/rendering/carRenderer.js +27 -79
- package/src/app/rendering/displayUtils.js +0 -6
- package/src/app/rendering/expertSensorRenderer.js +1 -1
- package/src/app/rendering/replayGhostRenderer.js +4 -1
- package/src/app/rendering/trackRenderer.js +2 -2
- package/src/app/runtime/frameLoop.js +18 -17
- package/src/app/runtime/pixiStartupLifecycle.js +94 -0
- package/src/app/runtime/runtimeTiming.js +4 -1
- package/src/app/runtime/runtimeVisibility.js +7 -2
- package/src/config/cssValues.js +1 -1
- package/src/config/defaultOptions.js +12 -173
- package/src/config/restartOptions.js +1 -0
- package/src/config/runtimeSeed.js +10 -0
- package/src/config/simulatorDefaults.js +106 -0
- package/src/config/telemetryModules.js +20 -0
- package/src/config/theme/cssTokens.js +85 -0
- package/src/config/theme/themeCss.js +72 -0
- package/src/config/theme/themeInputs.js +155 -0
- package/src/config/theme/themeResolver.js +172 -0
- package/src/config/theme/themeSelectors.js +69 -0
- package/src/config/theme/themeTokens.js +257 -0
- package/src/config/theme/themeUtils.js +15 -0
- package/src/config/themeOptions.js +14 -830
- package/src/config/timingGapMode.js +0 -2
- package/src/config/uiOptions.js +53 -0
- package/src/data/championship.js +16 -158
- package/src/data/championshipDefaults.js +87 -0
- package/src/data/driverData.js +10 -20
- package/src/data/entryIdentity.js +67 -0
- package/src/data/entryPresentation.js +23 -0
- package/src/data/entryTypes.d.ts +160 -0
- package/src/data/index.d.ts +13 -259
- package/src/data/ratingScale.js +14 -0
- package/src/data/teamTypes.d.ts +23 -0
- package/src/data/trackTypes.d.ts +101 -0
- package/src/data/vehicleData.js +12 -23
- package/src/environment/actions.js +3 -2
- package/src/environment/controllerLoop.js +57 -44
- package/src/environment/controllerScheduler.js +30 -0
- package/src/environment/driverObservations.js +260 -0
- package/src/environment/driverReset.js +51 -0
- package/src/environment/episode.js +0 -1
- package/src/environment/evaluation.js +6 -90
- package/src/environment/evaluationTracker.js +100 -0
- package/src/environment/events.js +23 -0
- package/src/environment/index.d.ts +21 -140
- package/src/environment/metrics.js +3 -17
- package/src/environment/observationSchema.js +104 -0
- package/src/environment/observationVector.js +16 -115
- package/src/environment/observations.js +16 -316
- package/src/environment/options.js +5 -24
- package/src/environment/renderFrames.js +21 -0
- package/src/environment/resetOptions.js +41 -0
- package/src/environment/results.js +125 -0
- package/src/environment/rewards.js +10 -16
- package/src/environment/runtime.js +17 -300
- package/src/environment/scenarioOptions.js +90 -0
- package/src/environment/scenarioPlacements.js +70 -0
- package/src/environment/scenarioPresets.js +78 -0
- package/src/environment/scenarios.js +5 -236
- package/src/environment/scratch.js +18 -0
- package/src/environment/sensorOptions.js +20 -0
- package/src/environment/sensors/index.js +2 -556
- package/src/environment/sensors/indexedRayBands.js +0 -18
- package/src/environment/sensors/nearbyCars.js +18 -15
- package/src/environment/sensors/opponentRadar.js +3 -2
- package/src/environment/sensors/pitConnectorProximity.js +20 -0
- package/src/environment/sensors/rayBandTrace.js +1 -37
- package/src/environment/sensors/rayBatch.js +81 -0
- package/src/environment/sensors/rayConfig.js +26 -0
- package/src/environment/sensors/rayGeometry.js +0 -8
- package/src/environment/sensors/rayGuards.js +0 -6
- package/src/environment/sensors/rayOutputs.js +75 -0
- package/src/environment/sensors/rayQuery.js +110 -0
- package/src/environment/sensors/rayScratch.js +146 -0
- package/src/environment/sensors/sensorTargets.js +12 -4
- package/src/environment/sensors/surfaceRays.js +6 -61
- package/src/environment/sensors/trackRays.js +7 -80
- package/src/environment/simulationSetup.js +30 -0
- package/src/environment/specs.js +15 -109
- package/src/index.d.ts +34 -259
- package/src/index.js +28 -6
- package/src/placeholder/index.js +1 -8
- package/src/rendering/displayLifecycle.js +6 -0
- package/src/rendering/proceduralTrackAsset.js +1 -1
- package/src/rendering/renderSnapshot.js +3 -0
- package/src/rendering/track/finishLineRenderer.js +1 -1
- package/src/rendering/track/gridRenderer.js +1 -1
- package/src/rendering/track/offsetStrokeSafety.js +2 -2
- package/src/rendering/track/pitLaneRenderer.js +1 -1
- package/src/rendering/track/trackMaterialRenderer.js +4 -4
- package/src/rendering/track/trackRenderConstants.js +0 -1
- package/src/rendering/track/trackRenderGeometry.js +1 -7
- package/src/simulation/collisionGeometry.js +5 -11
- package/src/simulation/driver/advancedPathControls.js +53 -0
- package/src/simulation/driver/advancedRacePace.js +112 -0
- package/src/simulation/driver/advancedRacingControls.js +61 -0
- package/src/simulation/driver/advancedRacingEnvelope.js +255 -0
- package/src/simulation/driver/advancedRacingSteering.js +76 -0
- package/src/simulation/driver/advancedRacingTiming.js +16 -0
- package/src/simulation/driver/arcadeRacePace.js +83 -0
- package/src/simulation/driver/arcadeRacingControls.js +111 -0
- package/src/simulation/driver/arcadeRacingEnvelope.js +63 -0
- package/src/simulation/driver/driverControlConstants.js +0 -2
- package/src/simulation/driver/driverController.js +14 -3
- package/src/simulation/driver/edgeRecovery.js +0 -1
- package/src/simulation/driver/racingControls.js +10 -406
- package/src/simulation/driver/racingDriverProfile.js +48 -0
- package/src/simulation/driver/racingLinePlan.js +48 -15
- package/src/simulation/driver/rejoinControls.js +58 -111
- package/src/simulation/driver/safetyCarControls.js +12 -0
- package/src/simulation/driver/trafficScan.js +89 -6
- package/src/simulation/pit/pitCarPlacement.js +29 -0
- package/src/simulation/pit/pitFlow.js +8 -30
- package/src/simulation/pit/pitIntent.js +4 -3
- package/src/simulation/pit/pitOccupancy.js +2 -2
- package/src/simulation/pit/pitQueue.js +2 -18
- package/src/simulation/pit/pitRouteMovement.js +1 -36
- package/src/simulation/pit/pitRouting.js +5 -81
- package/src/simulation/pit/pitService.js +3 -32
- package/src/simulation/pit/pitServiceConstants.js +1 -0
- package/src/simulation/pit/pitServiceProgress.js +34 -0
- package/src/simulation/pit/pitSnapshots.js +8 -4
- package/src/simulation/pit/pitState.js +1 -5
- package/src/simulation/race/classification.js +1 -8
- package/src/simulation/race/raceFinish.js +3 -8
- package/src/simulation/race/raceOrder.js +2 -13
- package/src/simulation/race/raceProgress.js +6 -20
- package/src/simulation/race/raceSetup.js +1 -1
- package/src/simulation/race/raceStep.js +1 -1
- package/src/simulation/race/redFlag.js +2 -4
- package/src/simulation/race/safetyCar.js +3 -8
- package/src/simulation/race/stalledDnf.js +2 -4
- package/src/simulation/raceSimulation.js +6 -38
- package/src/simulation/replay/replayGhosts.js +44 -32
- package/src/simulation/rules/collisionSteward.js +0 -14
- package/src/simulation/rules/penaltyActions.js +37 -0
- package/src/simulation/rules/penaltyLedger.js +8 -1
- package/src/simulation/rules/rulesReview.js +1 -7
- package/src/simulation/simMath.js +4 -0
- package/src/simulation/snapshots/raceSnapshots.js +30 -11
- package/src/simulation/timing/drsState.js +8 -0
- package/src/simulation/timing/drsTiming.js +2 -4
- package/src/simulation/timing/lapTelemetry.js +7 -4
- package/src/simulation/timing/sectorPerformance.js +3 -3
- package/src/simulation/timing/timingLines.js +12 -6
- package/src/simulation/track/pitLaneAccess.js +2 -2
- package/src/simulation/track/pitLaneLayout.js +4 -135
- package/src/simulation/track/pitLaneModel.js +70 -0
- package/src/simulation/track/pitLaneState.js +0 -1
- package/src/simulation/track/pitServiceLayout.js +121 -0
- package/src/simulation/track/proceduralTrack.js +77 -0
- package/src/simulation/track/spatialQueries.js +1 -25
- package/src/simulation/track/trackBuilder.js +54 -0
- package/src/simulation/track/trackConstants.js +0 -22
- package/src/simulation/track/trackMath.js +4 -29
- package/src/simulation/track/trackModel.js +4 -241
- package/src/simulation/track/trackModelCache.js +105 -0
- package/src/simulation/track/trackNearestQueries.js +1218 -0
- package/src/simulation/track/trackQueryConstants.js +3 -0
- package/src/simulation/track/trackQueryConstruction.js +368 -0
- package/src/simulation/track/trackQueryIndex.js +12 -1933
- package/src/simulation/track/trackQuerySegments.js +174 -0
- package/src/simulation/track/trackQueryStats.js +12 -0
- package/src/simulation/track/trackRayQueries.js +165 -0
- package/src/simulation/vehicle/advancedContactResponse.js +89 -0
- package/src/simulation/vehicle/advancedTireForces.js +92 -0
- package/src/simulation/vehicle/advancedVehicleModel.js +65 -0
- package/src/simulation/vehicle/advancedVehiclePhysics.js +127 -0
- package/src/simulation/vehicle/contactResolution.js +20 -6
- package/src/simulation/vehicle/mainTrackWheelSurface.js +2 -13
- package/src/simulation/vehicle/pitWheelSurface.js +0 -12
- package/src/simulation/vehicle/runoffResponse.js +2 -4
- package/src/simulation/vehicle/sampledWheelSurface.js +266 -0
- package/src/simulation/vehicle/vehicleGeometry.js +0 -56
- package/src/simulation/vehicle/vehicleKinematics.js +10 -0
- package/src/simulation/vehicle/vehiclePhysics.js +35 -194
- package/src/simulation/vehicle/vehicleSnapshots.js +13 -4
- package/src/simulation/vehicle/vehicleState.js +2 -1
- package/src/simulation/vehicle/wheelSurface.js +13 -432
- package/src/simulation/vehicle/wheelSurfaceCache.js +55 -0
- package/src/simulation/vehicle/wheelSurfaceSummary.js +101 -0
- package/src/styles.css +173 -19
- package/src/ui/bannerTemplates.js +4 -2
- package/src/ui/cameraControlsTemplate.js +4 -2
- package/src/ui/gridWalkTemplate.js +29 -0
- package/src/ui/htmlEscaping.js +8 -0
- package/src/ui/shellTemplate.js +3 -0
- package/src/ui/telemetryDrawerTemplate.js +4 -2
- package/src/ui/telemetryTemplates.js +3 -22
- package/src/ui/templateUtils.js +2 -8
- package/src/ui/viewControlsTemplate.js +21 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { clamp, normalizeAngle } from '../simMath.js';
|
|
2
|
+
import { SIM_UNITS_PER_METER, TOP_SPEED_SIM_UNITS_PER_SECOND } from '../units.js';
|
|
3
|
+
import { ADVANCED_VEHICLE_MODEL as MODEL, ADVANCED_WHEELS, advancedWheelSurface, advancedDriveForce, advancedDragFactor } from './advancedVehicleModel.js';
|
|
4
|
+
import { advancedNormalLoads, advancedTireForce } from './advancedTireForces.js';
|
|
5
|
+
|
|
6
|
+
export function integrateAdvancedVehiclePhysics(car, controls, dt, { tireFactor, tireDegradationEnabled, maxSteer, steerRate }) {
|
|
7
|
+
if (!(dt > 0) || !Number.isFinite(dt)) return car;
|
|
8
|
+
if (!Number.isFinite(car.velocityX) || !Number.isFinite(car.velocityY)) {
|
|
9
|
+
car.velocityX = Math.cos(car.heading) * car.speed;
|
|
10
|
+
car.velocityY = Math.sin(car.heading) * car.speed;
|
|
11
|
+
}
|
|
12
|
+
const throttle = clamp(controls.throttle ?? 0, 0, 1);
|
|
13
|
+
const brake = clamp(controls.brake ?? 0, 0, 1);
|
|
14
|
+
const steeringTarget = clamp(controls.steering ?? 0, -maxSteer, maxSteer);
|
|
15
|
+
const steps = Math.ceil(dt / MODEL.maxIntegrationStep);
|
|
16
|
+
const h = dt / steps;
|
|
17
|
+
const initialVelocityX = car.velocityX / SIM_UNITS_PER_METER;
|
|
18
|
+
const initialVelocityY = car.velocityY / SIM_UNITS_PER_METER;
|
|
19
|
+
const initialHeading = car.heading;
|
|
20
|
+
let frontUsage = 0;
|
|
21
|
+
let rearUsage = 0;
|
|
22
|
+
let resistanceYawAcceleration = 0;
|
|
23
|
+
for (let step = 0; step < steps; step += 1) {
|
|
24
|
+
car.steeringAngle += clamp(steeringTarget - car.steeringAngle, -steerRate * h, steerRate * h);
|
|
25
|
+
const cos = Math.cos(car.heading);
|
|
26
|
+
const sin = Math.sin(car.heading);
|
|
27
|
+
const vx = car.velocityX / SIM_UNITS_PER_METER;
|
|
28
|
+
const vy = car.velocityY / SIM_UNITS_PER_METER;
|
|
29
|
+
const u = vx * cos + vy * sin;
|
|
30
|
+
const v = -vx * sin + vy * cos;
|
|
31
|
+
const speed = Math.hypot(vx, vy);
|
|
32
|
+
const yaw = car.yawRate ?? 0;
|
|
33
|
+
const loads = advancedNormalLoads(car, speed, car.longitudinalAcceleration ?? 0, car.lateralAcceleration ?? 0);
|
|
34
|
+
// The existing newton-valued power rating defines launch force. Above the
|
|
35
|
+
// crossover speed, the envelope is constant power instead of constant force.
|
|
36
|
+
const drive = throttle * advancedDriveForce(car, u);
|
|
37
|
+
let fx = 0;
|
|
38
|
+
let fy = 0;
|
|
39
|
+
let moment = 0;
|
|
40
|
+
let resistanceMoment = 0;
|
|
41
|
+
frontUsage = 0;
|
|
42
|
+
rearUsage = 0;
|
|
43
|
+
for (let index = 0; index < ADVANCED_WHEELS.length; index += 1) {
|
|
44
|
+
const wheel = ADVANCED_WHEELS[index];
|
|
45
|
+
const { x, y } = wheel;
|
|
46
|
+
const steer = wheel.front ? car.steeringAngle : 0;
|
|
47
|
+
const cs = Math.cos(steer);
|
|
48
|
+
const sn = Math.sin(steer);
|
|
49
|
+
const wheelU = (u - yaw * y) * cs + (v + yaw * x) * sn;
|
|
50
|
+
const wheelV = -(u - yaw * y) * sn + (v + yaw * x) * cs;
|
|
51
|
+
const surface = advancedWheelSurface(car, wheel.id);
|
|
52
|
+
const engine = wheel.front ? 0 : drive / 2;
|
|
53
|
+
const brakeDemand = brake * car.brakeNewtons * (wheel.front ? MODEL.frontBrakeFraction : 1 - MODEL.frontBrakeFraction) / 2;
|
|
54
|
+
// Brake/static resistance cannot manufacture a reverse launch at rest.
|
|
55
|
+
const brakingDirection = Math.sign(wheelU || engine);
|
|
56
|
+
const longitudinalLever = x * sn - y * cs;
|
|
57
|
+
const inverseContactMass = 1 / car.mass + longitudinalLever ** 2 / (car.mass * MODEL.yawInertiaPerMass);
|
|
58
|
+
// Four simultaneous contacts share the stopping impulse. Include yaw
|
|
59
|
+
// inertia and share the budget between braking and rolling resistance;
|
|
60
|
+
// independently spending it twice would create motion near rest.
|
|
61
|
+
const stoppingBudget = Math.abs(wheelU) / (4 * h * inverseContactMass);
|
|
62
|
+
const braking = brakingDirection * Math.min(brakeDemand, stoppingBudget + engine);
|
|
63
|
+
const remainingBudget = Math.max(0, stoppingBudget - Math.max(0, (braking - engine) * Math.sign(wheelU)));
|
|
64
|
+
const rolling = Math.sign(wheelU) * Math.min(surface.rolling * loads[index], remainingBudget);
|
|
65
|
+
const nominalLoad = car.mass * MODEL.gravity * (wheel.front ? MODEL.frontWeightFraction : 1 - MODEL.frontWeightFraction) / 2;
|
|
66
|
+
const tire = advancedTireForce({
|
|
67
|
+
normalLoad: loads[index], nominalLoad, grip: car.tireGrip, tireFactor, surface,
|
|
68
|
+
longitudinalDemand: engine - braking, forwardVelocity: wheelU, lateralVelocity: wheelV,
|
|
69
|
+
});
|
|
70
|
+
const wheelFx = tire.longitudinal - rolling;
|
|
71
|
+
const bodyFx = wheelFx * cs - tire.lateral * sn;
|
|
72
|
+
const bodyFy = wheelFx * sn + tire.lateral * cs;
|
|
73
|
+
fx += bodyFx;
|
|
74
|
+
fy += bodyFy;
|
|
75
|
+
moment += x * bodyFy - y * bodyFx;
|
|
76
|
+
resistanceMoment += x * (-rolling * sn) - y * (-rolling * cs);
|
|
77
|
+
if (wheel.front) frontUsage = Math.max(frontUsage, tire.usage);
|
|
78
|
+
else rearUsage = Math.max(rearUsage, tire.usage);
|
|
79
|
+
}
|
|
80
|
+
const dragFactor = advancedDragFactor(car);
|
|
81
|
+
fx -= dragFactor * speed * u;
|
|
82
|
+
fy -= dragFactor * speed * v;
|
|
83
|
+
car.longitudinalAcceleration = fx / car.mass;
|
|
84
|
+
car.lateralAcceleration = fy / car.mass;
|
|
85
|
+
const ax = (fx * cos - fy * sin) / car.mass;
|
|
86
|
+
const ay = (fx * sin + fy * cos) / car.mass;
|
|
87
|
+
car.velocityX += ax * h * SIM_UNITS_PER_METER;
|
|
88
|
+
car.velocityY += ay * h * SIM_UNITS_PER_METER;
|
|
89
|
+
car.yawRate = yaw + moment / (car.mass * MODEL.yawInertiaPerMass) * h;
|
|
90
|
+
resistanceYawAcceleration = resistanceMoment / (car.mass * MODEL.yawInertiaPerMass);
|
|
91
|
+
car.heading = normalizeAngle(car.heading + car.yawRate * h);
|
|
92
|
+
car.speed = Math.hypot(car.velocityX, car.velocityY);
|
|
93
|
+
// Retain the package's supported top-speed envelope, including externally
|
|
94
|
+
// placed cars. Engine/drag balance determines normal acceleration below it.
|
|
95
|
+
if (car.speed > TOP_SPEED_SIM_UNITS_PER_SECOND) {
|
|
96
|
+
const scale = TOP_SPEED_SIM_UNITS_PER_SECOND / car.speed;
|
|
97
|
+
car.velocityX *= scale;
|
|
98
|
+
car.velocityY *= scale;
|
|
99
|
+
car.speed = TOP_SPEED_SIM_UNITS_PER_SECOND;
|
|
100
|
+
}
|
|
101
|
+
car.x += car.velocityX * h;
|
|
102
|
+
car.y += car.velocityY * h;
|
|
103
|
+
}
|
|
104
|
+
const ax = (car.velocityX / SIM_UNITS_PER_METER - initialVelocityX) / dt;
|
|
105
|
+
const ay = (car.velocityY / SIM_UNITS_PER_METER - initialVelocityY) / dt;
|
|
106
|
+
car.longitudinalAcceleration = ax * Math.cos(initialHeading) + ay * Math.sin(initialHeading);
|
|
107
|
+
car.lateralAcceleration = -ax * Math.sin(initialHeading) + ay * Math.cos(initialHeading);
|
|
108
|
+
car.longitudinalG = car.longitudinalAcceleration / MODEL.gravity;
|
|
109
|
+
car.lateralG = car.lateralAcceleration / MODEL.gravity;
|
|
110
|
+
car.gripUsage = Math.max(frontUsage, rearUsage);
|
|
111
|
+
car.slipAngleRadians = car.speed > 0.01 ? normalizeAngle(Math.atan2(car.velocityY, car.velocityX) - car.heading) : 0;
|
|
112
|
+
car.tractionLimited = car.gripUsage > 1;
|
|
113
|
+
car.stabilityState = Math.abs(car.slipAngleRadians) > 0.35 ? 'spin-risk'
|
|
114
|
+
: rearUsage > 1 && rearUsage > frontUsage * 1.15 ? 'oversteer'
|
|
115
|
+
: frontUsage > 1 ? 'understeer' : 'stable';
|
|
116
|
+
car.wheelDragYawRate = resistanceYawAcceleration * dt;
|
|
117
|
+
car.steerSaturation = 1 / Math.max(1, frontUsage);
|
|
118
|
+
car.turnRadius = Math.abs(car.yawRate) < 0.001 ? Infinity : car.speed / Math.abs(car.yawRate);
|
|
119
|
+
car.throttle = throttle;
|
|
120
|
+
car.brake = brake;
|
|
121
|
+
if (tireDegradationEnabled !== false) {
|
|
122
|
+
const care = clamp(Number(car.tireCare) || 1, 0.45, 1.8);
|
|
123
|
+
const wear = (0.035 + Math.abs(car.lateralG) * 0.11 + brake * 0.07 + throttle * 0.025 + Math.max(0, car.gripUsage - 1) * 0.03) / care;
|
|
124
|
+
car.tireEnergy = clamp((car.tireEnergy ?? 100) - wear * dt, 1, 100);
|
|
125
|
+
}
|
|
126
|
+
return car;
|
|
127
|
+
}
|
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
import { canCollide, isCollidable } from '../participants/participantInteractions.js';
|
|
8
8
|
import { clamp, normalizeAngle } from '../simMath.js';
|
|
9
9
|
import { VEHICLE_LIMITS, isSimulatorPhysicsMode } from './vehiclePhysics.js';
|
|
10
|
-
import { shiftPreviousRenderPose } from '
|
|
10
|
+
import { shiftPreviousRenderPose } from './vehicleKinematics.js';
|
|
11
|
+
import { applyAdvancedContactResponse } from './advancedContactResponse.js';
|
|
11
12
|
|
|
12
13
|
const MAX_COLLISION_CORRECTION = 4.5;
|
|
13
14
|
|
|
@@ -113,23 +114,29 @@ export function resolveCollisionsForSimulation(sim) {
|
|
|
113
114
|
second.y += secondCorrectionY;
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
|
|
117
|
+
const advancedPhysics = isSimulatorPhysicsMode(sim.physicsMode);
|
|
118
|
+
if (advancedPhysics) {
|
|
119
|
+
applyContactVelocityResponse(sim, first, second, collision.axis, {
|
|
120
|
+
firstFixed: firstPitControlled,
|
|
121
|
+
secondFixed: secondPitControlled,
|
|
122
|
+
});
|
|
123
|
+
} else if (oneCarFixed) {
|
|
117
124
|
if (!firstPitControlled) dampPitContactVelocity(first, 0.985, isSimulatorPhysicsMode(sim.physicsMode));
|
|
118
125
|
if (!secondPitControlled) dampPitContactVelocity(second, 0.985, isSimulatorPhysicsMode(sim.physicsMode));
|
|
119
126
|
} else {
|
|
120
127
|
applyContactVelocityResponse(sim, first, second, collision.axis);
|
|
121
128
|
}
|
|
122
129
|
|
|
123
|
-
const yawNudge = clamp(collision.depth * 0.0025, 0.008, 0.035);
|
|
130
|
+
const yawNudge = advancedPhysics ? 0 : clamp(collision.depth * 0.0025, 0.008, 0.035);
|
|
124
131
|
const freshContact = first.contactCooldown <= 0 && second.contactCooldown <= 0;
|
|
125
132
|
const firstHeadingCorrection = firstPitControlled ? 0 : -collision.axis.y * yawNudge;
|
|
126
133
|
const secondHeadingCorrection = secondPitControlled ? 0 : collision.axis.y * yawNudge;
|
|
127
134
|
if (!firstPitControlled) {
|
|
128
|
-
first.heading = normalizeAngle(first.heading + firstHeadingCorrection);
|
|
135
|
+
if (!advancedPhysics) first.heading = normalizeAngle(first.heading + firstHeadingCorrection);
|
|
129
136
|
shiftPreviousRenderPose(first, firstCorrectionX, firstCorrectionY, firstHeadingCorrection);
|
|
130
137
|
}
|
|
131
138
|
if (!secondPitControlled) {
|
|
132
|
-
second.heading = normalizeAngle(second.heading + secondHeadingCorrection);
|
|
139
|
+
if (!advancedPhysics) second.heading = normalizeAngle(second.heading + secondHeadingCorrection);
|
|
133
140
|
shiftPreviousRenderPose(second, secondCorrectionX, secondCorrectionY, secondHeadingCorrection);
|
|
134
141
|
}
|
|
135
142
|
first.contactCooldown = 1;
|
|
@@ -166,8 +173,15 @@ export function resolveCollisionsForSimulation(sim) {
|
|
|
166
173
|
|
|
167
174
|
export function applyContactVelocityResponse(sim, first, second, axis, options = {}) {
|
|
168
175
|
recordContactVelocityResponse(options.stats);
|
|
176
|
+
if (isSimulatorPhysicsMode(sim.physicsMode)) {
|
|
177
|
+
applyAdvancedContactResponse(first, second, axis, {
|
|
178
|
+
restitution: sim.rules.collisionRestitution,
|
|
179
|
+
firstFixed: options.firstFixed,
|
|
180
|
+
secondFixed: options.secondFixed,
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
169
184
|
if (
|
|
170
|
-
isSimulatorPhysicsMode(sim.physicsMode) ||
|
|
171
185
|
Number.isFinite(first.velocityX) ||
|
|
172
186
|
Number.isFinite(second.velocityX)
|
|
173
187
|
) {
|
|
@@ -37,17 +37,6 @@ export function wheelOutsideFromOffsets(minimumSignedOffset, maximumSignedOffset
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export function analyticWheelState(patch, centerState, track, trackLimit) {
|
|
41
|
-
return writeAnalyticWheelStateWithProjectedHalfWidth(
|
|
42
|
-
{},
|
|
43
|
-
patch,
|
|
44
|
-
centerState,
|
|
45
|
-
track,
|
|
46
|
-
trackLimit,
|
|
47
|
-
analyticWheelProjectedHalfWidth(patch, centerState),
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
40
|
export function writeAnalyticWheelState(target, patch, centerState, track, trackLimit) {
|
|
52
41
|
return writeAnalyticWheelStateWithProjectedHalfWidth(
|
|
53
42
|
target,
|
|
@@ -87,7 +76,7 @@ export function writeAnalyticWheelStateWithProjectedHalfWidth(
|
|
|
87
76
|
const state = sampledStates.length === 1 ? (sampledStates[0] ?? {}) : {};
|
|
88
77
|
sampledStates.length = 1;
|
|
89
78
|
sampledStates[0] = state;
|
|
90
|
-
writeStateFromSignedOffset(state,
|
|
79
|
+
writeStateFromSignedOffset(state, track, signedOffset);
|
|
91
80
|
|
|
92
81
|
target.id = patch.id;
|
|
93
82
|
target.x = patch.center.x;
|
|
@@ -107,7 +96,7 @@ export function writeAnalyticWheelStateWithProjectedHalfWidth(
|
|
|
107
96
|
return target;
|
|
108
97
|
}
|
|
109
98
|
|
|
110
|
-
function writeStateFromSignedOffset(target,
|
|
99
|
+
function writeStateFromSignedOffset(target, track, signedOffset) {
|
|
111
100
|
const crossTrackError = Math.abs(signedOffset);
|
|
112
101
|
const surface = mainTrackSurface(track, crossTrackError);
|
|
113
102
|
target.signedOffset = signedOffset;
|
|
@@ -38,10 +38,6 @@ export function canSkipConnectorPitChecks(track, geometry, centerState, epsilon
|
|
|
38
38
|
return true;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export function pitPatchOffsetRange(patch, centerState) {
|
|
42
|
-
return patchPitOffsetRange(patch, centerState);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
41
|
export function pitPatchInsideRoad(patch, centerState, epsilon = 0.001) {
|
|
46
42
|
const roadWidth = Number(centerState?.pitLaneRoadWidth);
|
|
47
43
|
if (!centerState?.inPitLane || !Number.isFinite(roadWidth) || roadWidth <= 0) return false;
|
|
@@ -50,14 +46,6 @@ export function pitPatchInsideRoad(patch, centerState, epsilon = 0.001) {
|
|
|
50
46
|
return range.minimum >= -halfRoadWidth - epsilon && range.maximum <= halfRoadWidth + epsilon;
|
|
51
47
|
}
|
|
52
48
|
|
|
53
|
-
export function pitPatchOverlapsRoad(patch, centerState, epsilon = 0.001) {
|
|
54
|
-
const roadWidth = Number(centerState?.pitLaneRoadWidth);
|
|
55
|
-
if (!centerState?.inPitLane || !Number.isFinite(roadWidth) || roadWidth <= 0) return false;
|
|
56
|
-
const halfRoadWidth = roadWidth / 2;
|
|
57
|
-
const range = patchPitOffsetRange(patch, centerState);
|
|
58
|
-
return range.minimum <= halfRoadWidth + epsilon && range.maximum >= -halfRoadWidth - epsilon;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
49
|
export function writeAnalyticPitWheelState(target, patch, centerState) {
|
|
62
50
|
const range = patchPitOffsetRange(patch, centerState);
|
|
63
51
|
const signedOffset = centerState.mainTrackSignedOffset ?? centerState.signedOffset;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clearCurrentDrsState } from '../timing/drsState.js';
|
|
1
2
|
import {
|
|
2
3
|
VEHICLE_GEOMETRY,
|
|
3
4
|
vehicleAxes,
|
|
@@ -89,10 +90,7 @@ function destroyCarOnBarrier(sim, car) {
|
|
|
89
90
|
car.outOfRace = true;
|
|
90
91
|
markCarDnf(sim, car, { reason: 'barrier' });
|
|
91
92
|
car.canAttack = false;
|
|
92
|
-
car
|
|
93
|
-
car.drsEligible = false;
|
|
94
|
-
car.drsZoneId = null;
|
|
95
|
-
car.drsZoneEnabled = false;
|
|
93
|
+
clearCurrentDrsState(car);
|
|
96
94
|
freezeBarrierMotion(car, sim.physicsMode);
|
|
97
95
|
sim.events.unshift({
|
|
98
96
|
type: 'car-destroyed',
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { ensureOrientedRectCorners } from './vehicleGeometry.js';
|
|
2
|
+
import { analyticWheelProjectedHalfWidth, wheelFullyOutside, writeAnalyticWheelState } from './mainTrackWheelSurface.js';
|
|
3
|
+
import { pitPatchInsideRoad, writeAnalyticPitWheelState } from './pitWheelSurface.js';
|
|
4
|
+
import { worstState } from './surfacePriority.js';
|
|
5
|
+
import { resetWheelSummary, recordWheelSummary, finishWheelSummary } from './wheelSurfaceSummary.js';
|
|
6
|
+
import {
|
|
7
|
+
nearestTrackStateForCar,
|
|
8
|
+
queryHintedTrackStateForCar,
|
|
9
|
+
queryLocalSegmentTrackProjectionsForCar,
|
|
10
|
+
queryLocalSegmentTrackStateForCar,
|
|
11
|
+
queryLocalSegmentTrackStatesForCar,
|
|
12
|
+
} from '../track/trackStatePolicy.js';
|
|
13
|
+
|
|
14
|
+
const STRAIGHT_CONNECTOR_CURVATURE_EPSILON = 1e-8;
|
|
15
|
+
const MAIN_TRACK_PATCH_MARGIN = 0.001;
|
|
16
|
+
|
|
17
|
+
function writeFullWheelState(target, patch, sampledStates, trackLimit) {
|
|
18
|
+
const state = worstState(sampledStates);
|
|
19
|
+
let minimumSignedOffset = Infinity;
|
|
20
|
+
let maximumSignedOffset = -Infinity;
|
|
21
|
+
for (let index = 0; index < sampledStates.length; index += 1) {
|
|
22
|
+
const signedOffset = sampledStates[index].signedOffset;
|
|
23
|
+
if (signedOffset < minimumSignedOffset) minimumSignedOffset = signedOffset;
|
|
24
|
+
if (signedOffset > maximumSignedOffset) maximumSignedOffset = signedOffset;
|
|
25
|
+
}
|
|
26
|
+
const outside = wheelFullyOutside(sampledStates, trackLimit);
|
|
27
|
+
|
|
28
|
+
target.id = patch.id;
|
|
29
|
+
target.x = patch.center.x;
|
|
30
|
+
target.y = patch.center.y;
|
|
31
|
+
target.signedOffset = state.signedOffset;
|
|
32
|
+
target.crossTrackError = state.crossTrackError;
|
|
33
|
+
target.surface = state.surface;
|
|
34
|
+
target.onTrack = Boolean(state.onTrack);
|
|
35
|
+
target.inPitLane = Boolean(state.inPitLane);
|
|
36
|
+
target.pitLanePart = state.pitLanePart ?? null;
|
|
37
|
+
target.pitBoxId = state.pitBoxId ?? null;
|
|
38
|
+
target.minimumSignedOffset = minimumSignedOffset;
|
|
39
|
+
target.maximumSignedOffset = maximumSignedOffset;
|
|
40
|
+
target.fullyOutsideWhiteLine = outside.fullyOutsideWhiteLine;
|
|
41
|
+
target.outsideSide = outside.outsideSide;
|
|
42
|
+
target.sampledStates = sampledStates;
|
|
43
|
+
return target;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function queryConnectorSampleState(track, car, point, progressHint, centerState) {
|
|
47
|
+
const baseSegmentId = Number.isInteger(centerState?.segmentId) ? centerState.segmentId : null;
|
|
48
|
+
if (baseSegmentId == null) return queryHintedTrackStateForCar(track, car, point, progressHint);
|
|
49
|
+
return queryLocalSegmentTrackStateForCar(
|
|
50
|
+
track,
|
|
51
|
+
car,
|
|
52
|
+
point,
|
|
53
|
+
baseSegmentId,
|
|
54
|
+
progressHint,
|
|
55
|
+
{ skipPitOverrideInsideMainRoad: true },
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function sampleFullConnectorPatchInto(sampledStates, patch, track, car, progressHint, centerState) {
|
|
60
|
+
const corners = ensureOrientedRectCorners(patch);
|
|
61
|
+
sampledStates.length = 1 + corners.length;
|
|
62
|
+
sampledStates[0] = queryConnectorSampleState(track, car, patch.center, progressHint, centerState);
|
|
63
|
+
for (let index = 0; index < corners.length; index += 1) {
|
|
64
|
+
sampledStates[index + 1] = queryConnectorSampleState(track, car, corners[index], progressHint, centerState);
|
|
65
|
+
}
|
|
66
|
+
return sampledStates;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function sampleFullNearestPatchInto(sampledStates, patch, track, car) {
|
|
70
|
+
const corners = ensureOrientedRectCorners(patch);
|
|
71
|
+
sampledStates.length = 1 + corners.length;
|
|
72
|
+
sampledStates[0] = nearestTrackStateForCar(track, car, patch.center, car.progress);
|
|
73
|
+
for (let index = 0; index < corners.length; index += 1) {
|
|
74
|
+
sampledStates[index + 1] = nearestTrackStateForCar(track, car, corners[index], car.progress);
|
|
75
|
+
}
|
|
76
|
+
return sampledStates;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function writeConnectorWheelState(target, {
|
|
80
|
+
patch,
|
|
81
|
+
wheelCenterState,
|
|
82
|
+
centerState,
|
|
83
|
+
car,
|
|
84
|
+
track,
|
|
85
|
+
trackLimit,
|
|
86
|
+
progressHint,
|
|
87
|
+
sampledStates,
|
|
88
|
+
}) {
|
|
89
|
+
if (!wheelCenterState) {
|
|
90
|
+
return writeAnalyticWheelState(target, patch, centerState, track, trackLimit);
|
|
91
|
+
}
|
|
92
|
+
if (!wheelCenterState.inPitLane) {
|
|
93
|
+
return writeAnalyticWheelState(target, patch, wheelCenterState, track, trackLimit);
|
|
94
|
+
}
|
|
95
|
+
if (pitPatchInsideRoad(patch, wheelCenterState)) {
|
|
96
|
+
return writeAnalyticPitWheelState(target, patch, wheelCenterState);
|
|
97
|
+
}
|
|
98
|
+
sampleFullConnectorPatchInto(sampledStates, patch, track, car, progressHint, centerState);
|
|
99
|
+
return writeFullWheelState(target, patch, sampledStates, trackLimit);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function collectPatchCenters(target, patches) {
|
|
103
|
+
target.length = patches.length;
|
|
104
|
+
for (let index = 0; index < patches.length; index += 1) {
|
|
105
|
+
target[index] = patches[index].center;
|
|
106
|
+
}
|
|
107
|
+
return target;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function connectorWheelCenterStates(
|
|
111
|
+
track,
|
|
112
|
+
car,
|
|
113
|
+
patches,
|
|
114
|
+
centerState,
|
|
115
|
+
progressHint,
|
|
116
|
+
scratchStates = null,
|
|
117
|
+
scratchProjections = null,
|
|
118
|
+
scratchPositions = null,
|
|
119
|
+
) {
|
|
120
|
+
const baseSegmentId = Number.isInteger(centerState?.segmentId) ? centerState.segmentId : null;
|
|
121
|
+
const states = scratchStates ?? new Array(patches.length);
|
|
122
|
+
states.length = patches.length;
|
|
123
|
+
if (baseSegmentId == null) {
|
|
124
|
+
for (let index = 0; index < patches.length; index += 1) {
|
|
125
|
+
states[index] = queryHintedTrackStateForCar(track, car, patches[index].center, progressHint);
|
|
126
|
+
}
|
|
127
|
+
return states;
|
|
128
|
+
}
|
|
129
|
+
const positions = collectPatchCenters(scratchPositions ?? new Array(patches.length), patches);
|
|
130
|
+
return queryLocalSegmentTrackStatesForCar(
|
|
131
|
+
track,
|
|
132
|
+
car,
|
|
133
|
+
positions,
|
|
134
|
+
baseSegmentId,
|
|
135
|
+
progressHint,
|
|
136
|
+
{
|
|
137
|
+
radius: 2,
|
|
138
|
+
skipPitOverrideInsideMainRoad: true,
|
|
139
|
+
target: states,
|
|
140
|
+
projections: scratchProjections,
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function connectorWheelCenterProjections(
|
|
146
|
+
track,
|
|
147
|
+
car,
|
|
148
|
+
patches,
|
|
149
|
+
centerState,
|
|
150
|
+
progressHint,
|
|
151
|
+
scratchProjections = null,
|
|
152
|
+
scratchPositions = null,
|
|
153
|
+
) {
|
|
154
|
+
const baseSegmentId = Number.isInteger(centerState?.segmentId) ? centerState.segmentId : null;
|
|
155
|
+
if (baseSegmentId == null) return null;
|
|
156
|
+
const positions = collectPatchCenters(scratchPositions ?? new Array(patches.length), patches);
|
|
157
|
+
return queryLocalSegmentTrackProjectionsForCar(
|
|
158
|
+
track,
|
|
159
|
+
car,
|
|
160
|
+
positions,
|
|
161
|
+
baseSegmentId,
|
|
162
|
+
progressHint,
|
|
163
|
+
{
|
|
164
|
+
radius: 2,
|
|
165
|
+
projections: scratchProjections,
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function canUseStraightConnectorMainTrackAnalytic(track, geometry, centerState, trackLimit) {
|
|
171
|
+
if (
|
|
172
|
+
!track ||
|
|
173
|
+
!geometry?.contactPatches?.length ||
|
|
174
|
+
!centerState ||
|
|
175
|
+
centerState.inPitLane ||
|
|
176
|
+
!centerState.onTrack ||
|
|
177
|
+
Math.abs(centerState.curvature ?? 0) > STRAIGHT_CONNECTOR_CURVATURE_EPSILON
|
|
178
|
+
) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for (let index = 0; index < geometry.contactPatches.length; index += 1) {
|
|
183
|
+
const patch = geometry.contactPatches[index];
|
|
184
|
+
const wheelCenterOffset =
|
|
185
|
+
(patch.center.x - centerState.x) * centerState.normalX +
|
|
186
|
+
(patch.center.y - centerState.y) * centerState.normalY;
|
|
187
|
+
const projectedHalfWidth = analyticWheelProjectedHalfWidth(patch, centerState);
|
|
188
|
+
if (
|
|
189
|
+
wheelCenterOffset - projectedHalfWidth < -trackLimit - MAIN_TRACK_PATCH_MARGIN ||
|
|
190
|
+
wheelCenterOffset + projectedHalfWidth > trackLimit + MAIN_TRACK_PATCH_MARGIN
|
|
191
|
+
) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function writeConnectorWheelStatesWithSummary({
|
|
199
|
+
targetWheels,
|
|
200
|
+
patches,
|
|
201
|
+
wheelCenterStates,
|
|
202
|
+
centerState,
|
|
203
|
+
car,
|
|
204
|
+
track,
|
|
205
|
+
trackLimit,
|
|
206
|
+
progressHint,
|
|
207
|
+
sampledStatePools,
|
|
208
|
+
summaryTarget = null,
|
|
209
|
+
}) {
|
|
210
|
+
const summary = resetWheelSummary(summaryTarget ?? {});
|
|
211
|
+
for (let index = 0; index < patches.length; index += 1) {
|
|
212
|
+
const sampledStates = sampledStatePools[index] ?? [];
|
|
213
|
+
sampledStatePools[index] = sampledStates;
|
|
214
|
+
const wheel = writeConnectorWheelState(targetWheels[index], {
|
|
215
|
+
patch: patches[index],
|
|
216
|
+
wheelCenterState: wheelCenterStates[index],
|
|
217
|
+
centerState,
|
|
218
|
+
car,
|
|
219
|
+
track,
|
|
220
|
+
trackLimit,
|
|
221
|
+
progressHint,
|
|
222
|
+
sampledStates,
|
|
223
|
+
});
|
|
224
|
+
recordWheelSummary(summary, wheel);
|
|
225
|
+
}
|
|
226
|
+
finishWheelSummary(summary, track, trackLimit);
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
wheels: targetWheels,
|
|
230
|
+
effectiveSurface: summary.effectiveSurface,
|
|
231
|
+
representative: summary.representative,
|
|
232
|
+
trackLimits: summary.trackLimits,
|
|
233
|
+
allOnTrackOrPit: summary.allOnTrackOrPit,
|
|
234
|
+
allInPitLane: summary.allInPitLane,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function writeNearestSampledWheelStatesWithSummary({
|
|
239
|
+
targetWheels,
|
|
240
|
+
patches,
|
|
241
|
+
car,
|
|
242
|
+
track,
|
|
243
|
+
trackLimit,
|
|
244
|
+
sampledStatePools,
|
|
245
|
+
summaryTarget = null,
|
|
246
|
+
}) {
|
|
247
|
+
const summary = resetWheelSummary(summaryTarget ?? {});
|
|
248
|
+
for (let index = 0; index < patches.length; index += 1) {
|
|
249
|
+
const patch = patches[index];
|
|
250
|
+
const sampledStates = sampledStatePools[index] ?? [];
|
|
251
|
+
sampledStatePools[index] = sampledStates;
|
|
252
|
+
sampleFullNearestPatchInto(sampledStates, patch, track, car);
|
|
253
|
+
const wheel = writeFullWheelState(targetWheels[index], patch, sampledStates, trackLimit);
|
|
254
|
+
recordWheelSummary(summary, wheel);
|
|
255
|
+
}
|
|
256
|
+
finishWheelSummary(summary, track, trackLimit);
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
wheels: targetWheels,
|
|
260
|
+
effectiveSurface: summary.effectiveSurface,
|
|
261
|
+
representative: summary.representative,
|
|
262
|
+
trackLimits: summary.trackLimits,
|
|
263
|
+
allOnTrackOrPit: summary.allOnTrackOrPit,
|
|
264
|
+
allInPitLane: summary.allInPitLane,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
@@ -110,24 +110,6 @@ function writeOrientedRectValues(target, {
|
|
|
110
110
|
return rect;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
function writeOrientedRect(target, { id, type, center, heading, length, width }) {
|
|
114
|
-
const cos = Math.cos(heading);
|
|
115
|
-
const sin = Math.sin(heading);
|
|
116
|
-
return writeOrientedRectValues(target, {
|
|
117
|
-
id,
|
|
118
|
-
type,
|
|
119
|
-
centerX: center.x,
|
|
120
|
-
centerY: center.y,
|
|
121
|
-
heading,
|
|
122
|
-
length,
|
|
123
|
-
width,
|
|
124
|
-
forwardX: cos,
|
|
125
|
-
forwardY: sin,
|
|
126
|
-
rightX: -sin,
|
|
127
|
-
rightY: cos,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
113
|
function writeVehicleGeometry(target, pose) {
|
|
132
114
|
const geometry = target ?? {};
|
|
133
115
|
const forwardX = Math.cos(pose.heading);
|
|
@@ -238,10 +220,6 @@ function writeCurrentVehicleGeometry(target, pose) {
|
|
|
238
220
|
return geometry;
|
|
239
221
|
}
|
|
240
222
|
|
|
241
|
-
export function createOrientedRect({ id, type, center, heading, length, width }) {
|
|
242
|
-
return writeOrientedRect(null, { id, type, center, heading, length, width });
|
|
243
|
-
}
|
|
244
|
-
|
|
245
223
|
export function createVehicleGeometry(car, options = {}) {
|
|
246
224
|
const pose = vehiclePose(car, options);
|
|
247
225
|
return writeVehicleGeometry(null, pose);
|
|
@@ -323,21 +301,6 @@ function writeVehicleShapeAabb(target, shape) {
|
|
|
323
301
|
return target;
|
|
324
302
|
}
|
|
325
303
|
|
|
326
|
-
export function mergeAabbs(aabbs) {
|
|
327
|
-
let minX = Infinity;
|
|
328
|
-
let maxX = -Infinity;
|
|
329
|
-
let minY = Infinity;
|
|
330
|
-
let maxY = -Infinity;
|
|
331
|
-
for (let index = 0; index < aabbs.length; index += 1) {
|
|
332
|
-
const aabb = aabbs[index];
|
|
333
|
-
if (aabb.minX < minX) minX = aabb.minX;
|
|
334
|
-
if (aabb.maxX > maxX) maxX = aabb.maxX;
|
|
335
|
-
if (aabb.minY < minY) minY = aabb.minY;
|
|
336
|
-
if (aabb.maxY > maxY) maxY = aabb.maxY;
|
|
337
|
-
}
|
|
338
|
-
return { minX, maxX, minY, maxY };
|
|
339
|
-
}
|
|
340
|
-
|
|
341
304
|
function writeGeometryPose(target, car) {
|
|
342
305
|
const pose = target ?? {};
|
|
343
306
|
const x = finiteOr(car.x, 0);
|
|
@@ -455,25 +418,6 @@ export function ensureOrientedRectCorners(rect) {
|
|
|
455
418
|
}).corners;
|
|
456
419
|
}
|
|
457
420
|
|
|
458
|
-
export function createVehicleAabb(car, options = {}) {
|
|
459
|
-
const geometry = createVehicleGeometry(car, options);
|
|
460
|
-
return mergeAabbs(geometry.shapes.map(createVehicleShapeAabb));
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
export function interpolateVehiclePose(car, amount) {
|
|
464
|
-
const previousX = finiteOr(car.previousX, car.x);
|
|
465
|
-
const previousY = finiteOr(car.previousY, car.y);
|
|
466
|
-
const previousHeading = finiteOr(car.previousHeading, car.heading);
|
|
467
|
-
const headingDelta = normalizeAngle(finiteOr(car.heading, 0) - previousHeading);
|
|
468
|
-
|
|
469
|
-
return {
|
|
470
|
-
...car,
|
|
471
|
-
x: previousX + (finiteOr(car.x, previousX) - previousX) * amount,
|
|
472
|
-
y: previousY + (finiteOr(car.y, previousY) - previousY) * amount,
|
|
473
|
-
heading: normalizeAngle(previousHeading + headingDelta * amount),
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
|
|
477
421
|
export function getCarCorners(car) {
|
|
478
422
|
return createVehicleGeometry(car).body.corners;
|
|
479
423
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeAngle } from '../simMath.js';
|
|
2
|
+
|
|
1
3
|
export function freezeVehicleMotion(car, {
|
|
2
4
|
brake = 1,
|
|
3
5
|
clearManualControls = false,
|
|
@@ -35,3 +37,11 @@ export function syncVehicleVelocityToHeading(car) {
|
|
|
35
37
|
car.velocityX = Math.cos(heading) * speed;
|
|
36
38
|
car.velocityY = Math.sin(heading) * speed;
|
|
37
39
|
}
|
|
40
|
+
|
|
41
|
+
export function shiftPreviousRenderPose(car, dx, dy, dheading = 0) {
|
|
42
|
+
car.previousX = Number.isFinite(car.previousX) ? car.previousX + dx : car.x + dx;
|
|
43
|
+
car.previousY = Number.isFinite(car.previousY) ? car.previousY + dy : car.y + dy;
|
|
44
|
+
car.previousHeading = normalizeAngle(
|
|
45
|
+
(Number.isFinite(car.previousHeading) ? car.previousHeading : car.heading) + dheading,
|
|
46
|
+
);
|
|
47
|
+
}
|