@aura3d/engine 1.5.0 → 1.5.2
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/README.md +111 -46
- package/dist/engine/agent-api/ApplicationKits.d.ts +307 -0
- package/dist/engine/agent-api/ApplicationKits.d.ts.map +1 -0
- package/dist/engine/agent-api/ApplicationKits.js +650 -0
- package/dist/engine/agent-api/ApplicationKits.js.map +1 -0
- package/dist/engine/agent-api/CombatFrameData.d.ts +210 -0
- package/dist/engine/agent-api/CombatFrameData.d.ts.map +1 -0
- package/dist/engine/agent-api/CombatFrameData.js +324 -0
- package/dist/engine/agent-api/CombatFrameData.js.map +1 -0
- package/dist/engine/agent-api/FocusSelection.d.ts +186 -0
- package/dist/engine/agent-api/FocusSelection.d.ts.map +1 -0
- package/dist/engine/agent-api/FocusSelection.js +436 -0
- package/dist/engine/agent-api/FocusSelection.js.map +1 -0
- package/dist/engine/agent-api/GameGenreKits.d.ts +32 -1
- package/dist/engine/agent-api/GameGenreKits.d.ts.map +1 -1
- package/dist/engine/agent-api/GameGenreKits.js +51 -7
- package/dist/engine/agent-api/GameGenreKits.js.map +1 -1
- package/dist/engine/agent-api/GameSceneGeometryBindings.d.ts +10 -0
- package/dist/engine/agent-api/GameSceneGeometryBindings.d.ts.map +1 -1
- package/dist/engine/agent-api/GameSceneGeometryBindings.js +7 -0
- package/dist/engine/agent-api/GameSceneGeometryBindings.js.map +1 -1
- package/dist/engine/agent-api/PlatformerMotion.d.ts +177 -0
- package/dist/engine/agent-api/PlatformerMotion.d.ts.map +1 -0
- package/dist/engine/agent-api/PlatformerMotion.js +207 -0
- package/dist/engine/agent-api/PlatformerMotion.js.map +1 -0
- package/dist/engine/agent-api/SceneQueries.d.ts +124 -0
- package/dist/engine/agent-api/SceneQueries.d.ts.map +1 -0
- package/dist/engine/agent-api/SceneQueries.js +221 -0
- package/dist/engine/agent-api/SceneQueries.js.map +1 -0
- package/dist/engine/agent-api/SpatialAnchoring.d.ts +279 -0
- package/dist/engine/agent-api/SpatialAnchoring.d.ts.map +1 -0
- package/dist/engine/agent-api/SpatialAnchoring.js +394 -0
- package/dist/engine/agent-api/SpatialAnchoring.js.map +1 -0
- package/dist/engine/agent-api/VehicleChassis.d.ts +192 -0
- package/dist/engine/agent-api/VehicleChassis.d.ts.map +1 -0
- package/dist/engine/agent-api/VehicleChassis.js +322 -0
- package/dist/engine/agent-api/VehicleChassis.js.map +1 -0
- package/dist/engine/agent-api/VehicleDriverAi.d.ts +123 -0
- package/dist/engine/agent-api/VehicleDriverAi.d.ts.map +1 -0
- package/dist/engine/agent-api/VehicleDriverAi.js +217 -0
- package/dist/engine/agent-api/VehicleDriverAi.js.map +1 -0
- package/dist/engine/agent-api/WorldLabelRenderer.d.ts +146 -0
- package/dist/engine/agent-api/WorldLabelRenderer.d.ts.map +1 -0
- package/dist/engine/agent-api/WorldLabelRenderer.js +331 -0
- package/dist/engine/agent-api/WorldLabelRenderer.js.map +1 -0
- package/dist/engine/agent-api/index.d.ts +64 -1
- package/dist/engine/agent-api/index.d.ts.map +1 -1
- package/dist/engine/agent-api/index.js +148 -2
- package/dist/engine/agent-api/index.js.map +1 -1
- package/dist/engine/agent-api/product-viewer-runtime.d.ts.map +1 -1
- package/dist/rendering/CameraFraming.d.ts +77 -0
- package/dist/rendering/CameraFraming.d.ts.map +1 -1
- package/dist/rendering/CameraFraming.js +176 -1
- package/dist/rendering/CameraFraming.js.map +1 -1
- package/dist/rendering/Renderer.d.ts +25 -2
- package/dist/rendering/Renderer.d.ts.map +1 -1
- package/dist/rendering/Renderer.js +11 -3
- package/dist/rendering/Renderer.js.map +1 -1
- package/dist/rendering/index.d.ts +3 -3
- package/dist/rendering/index.d.ts.map +1 -1
- package/dist/rendering/index.js +1 -1
- package/dist/rendering/index.js.map +1 -1
- package/package.json +120 -115
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable vehicle chassis: contact, suspension, attitude and wheel visuals.
|
|
3
|
+
*
|
|
4
|
+
* ## The defects this addresses
|
|
5
|
+
*
|
|
6
|
+
* Turbo Drift Circuit drove a 2D kinematic point (`game.racing`) and then pinned
|
|
7
|
+
* the car's rendered Y to a single literal, `TRACK_SURFACE_Y`. That has three
|
|
8
|
+
* visible consequences:
|
|
9
|
+
*
|
|
10
|
+
* 1. **The car sinks into the tarmac.** A model grounded on a frozen plane cannot
|
|
11
|
+
* respond to the surface it is actually over. There is no contact model, so
|
|
12
|
+
* there is nothing to hold the tyres on the road.
|
|
13
|
+
* 2. **No suspension.** The chassis never pitches under braking, never rolls in a
|
|
14
|
+
* corner, and the wheels never travel. A car that stays perfectly level at
|
|
15
|
+
* 111 km/h reads as a sprite sliding on a plane, which is what it is.
|
|
16
|
+
* 3. **Wheels do not turn or steer.** Nothing derived wheel spin from speed or
|
|
17
|
+
* wheel yaw from steering input.
|
|
18
|
+
*
|
|
19
|
+
* This module is the reusable answer. It takes a planar vehicle state -- position,
|
|
20
|
+
* heading, speed, steering, lateral slip -- plus a surface query, and produces the
|
|
21
|
+
* full 3D pose a renderer needs: chassis height from four independent contact
|
|
22
|
+
* points, pitch and roll from load transfer, per-wheel suspension travel, wheel
|
|
23
|
+
* spin and steer angles, and a contact report that states whether every tyre is
|
|
24
|
+
* on the ground.
|
|
25
|
+
*
|
|
26
|
+
* It is pure: no renderer, no DOM, no asset loading. That makes vehicle behaviour
|
|
27
|
+
* unit-testable, which is the only way "the car is grounded" becomes a fact rather
|
|
28
|
+
* than an opinion about a screenshot.
|
|
29
|
+
*/
|
|
30
|
+
/** Flat surface at a fixed height, for prototypes and tests. */
|
|
31
|
+
export function flatVehicleSurface(height = 0, grip = 1) {
|
|
32
|
+
return { sample: () => ({ height, normal: [0, 1, 0], grip }) };
|
|
33
|
+
}
|
|
34
|
+
const WHEEL_LAYOUT = [
|
|
35
|
+
{ id: "front-left", forward: 0.5, lateral: -0.5, steered: true },
|
|
36
|
+
{ id: "front-right", forward: 0.5, lateral: 0.5, steered: true },
|
|
37
|
+
{ id: "rear-left", forward: -0.5, lateral: -0.5, steered: false },
|
|
38
|
+
{ id: "rear-right", forward: -0.5, lateral: 0.5, steered: false }
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* Create a vehicle chassis.
|
|
42
|
+
*
|
|
43
|
+
* `rideHeight` and `wheelRadius` should be derived from the vehicle asset's own
|
|
44
|
+
* bounds. That is the difference between this and the constant it replaces: the
|
|
45
|
+
* caller states a proportion of a measured asset, not a number that happened to
|
|
46
|
+
* look right once.
|
|
47
|
+
*/
|
|
48
|
+
export function createVehicleChassis(spec, surface) {
|
|
49
|
+
const resolved = {
|
|
50
|
+
wheelbase: positive(spec.wheelbase, 1),
|
|
51
|
+
trackWidth: positive(spec.trackWidth, 0.8),
|
|
52
|
+
wheelRadius: positive(spec.wheelRadius, 0.15),
|
|
53
|
+
rideHeight: positive(spec.rideHeight, 0.3),
|
|
54
|
+
suspensionTravel: positive(spec.suspensionTravel ?? spec.rideHeight * 0.35, 0.05),
|
|
55
|
+
springRate: positive(spec.springRate ?? 14, 1),
|
|
56
|
+
dampingRatio: positive(spec.dampingRatio ?? 0.72, 0.05),
|
|
57
|
+
maxSteerAngle: positive(spec.maxSteerAngle ?? 0.52, 0.05),
|
|
58
|
+
/*
|
|
59
|
+
* Attitude limits are capped by the suspension that has to produce them.
|
|
60
|
+
*
|
|
61
|
+
* Body pitch and roll are *consequences* of spring compression, not independent
|
|
62
|
+
* rotations of a rigid body. Applying an attitude larger than the springs can
|
|
63
|
+
* absorb rotates the chassis off its own wheels: at a 0.055 rad pitch over a
|
|
64
|
+
* 0.66-unit wheelbase the front corners move 0.018 units, which exceeded the whole
|
|
65
|
+
* 0.031-unit travel and lifted both front tyres into the air on a flat road. That
|
|
66
|
+
* is the sinking defect's mirror image and just as wrong.
|
|
67
|
+
*
|
|
68
|
+
* The cap is the angle at which a corner's vertical displacement equals half the
|
|
69
|
+
* available travel, leaving the other half for road surface.
|
|
70
|
+
*/
|
|
71
|
+
maxPitch: Math.min(Math.max(0, spec.maxPitch ?? 0.055), attitudeLimitForTravel(spec.suspensionTravel ?? spec.rideHeight * 0.35, spec.wheelbase)),
|
|
72
|
+
maxRoll: Math.min(Math.max(0, spec.maxRoll ?? 0.085), attitudeLimitForTravel(spec.suspensionTravel ?? spec.rideHeight * 0.35, spec.trackWidth))
|
|
73
|
+
};
|
|
74
|
+
// Per-wheel suspension state: current compression and its velocity.
|
|
75
|
+
const compression = new Map();
|
|
76
|
+
for (const wheel of WHEEL_LAYOUT)
|
|
77
|
+
compression.set(wheel.id, { value: 0, velocity: 0 });
|
|
78
|
+
let spin = 0;
|
|
79
|
+
let pitch = 0;
|
|
80
|
+
let roll = 0;
|
|
81
|
+
let currentPose;
|
|
82
|
+
let telemetry = {
|
|
83
|
+
speed: 0, speedKph: 0, pitch: 0, roll: 0, steerAngle: 0,
|
|
84
|
+
wheelSpinRate: 0, groundedWheels: 4, maxContactGap: 0, averageCompression: 0, surfaceGrip: 1
|
|
85
|
+
};
|
|
86
|
+
/** Wheel contact-patch position in world space for a planar state. */
|
|
87
|
+
const wheelContactPoint = (state, wheel) => {
|
|
88
|
+
const cos = Math.cos(state.heading);
|
|
89
|
+
const sin = Math.sin(state.heading);
|
|
90
|
+
const forward = wheel.forward * resolved.wheelbase;
|
|
91
|
+
const lateral = wheel.lateral * resolved.trackWidth;
|
|
92
|
+
return {
|
|
93
|
+
// Heading 0 faces +X, so forward is (cos, sin) in XZ and lateral is its normal.
|
|
94
|
+
x: state.x + cos * forward - sin * lateral,
|
|
95
|
+
z: state.z + sin * forward + cos * lateral
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
const resolvePose = (dt, state, settleImmediately) => {
|
|
99
|
+
const step = Math.max(0, Math.min(0.1, dt));
|
|
100
|
+
const throttle = clamp01(state.throttle ?? 0);
|
|
101
|
+
const brake = clamp01(state.brake ?? 0);
|
|
102
|
+
const slip = clamp01(state.slip ?? 0);
|
|
103
|
+
const steerInput = clamp(state.steer ?? 0, -1, 1);
|
|
104
|
+
/*
|
|
105
|
+
* The wheels own the road; the springs own the body.
|
|
106
|
+
*
|
|
107
|
+
* Two earlier orderings were wrong in opposite directions, and both reproduce a
|
|
108
|
+
* version of the reported defect:
|
|
109
|
+
*
|
|
110
|
+
* 1. Place each wheel on its own surface sample and average -- the contact gap
|
|
111
|
+
* becomes self-referential, so a car could drive over a cliff and still report
|
|
112
|
+
* itself grounded.
|
|
113
|
+
* 2. Rotate the body to a target pitch/roll and hang wheels from it -- the rotation
|
|
114
|
+
* consumes more travel than exists (0.018 units of corner rise against 0.031
|
|
115
|
+
* units of travel), lifting the front tyres off a flat road.
|
|
116
|
+
*
|
|
117
|
+
* The physical arrangement is that a wheel rests on the surface beneath it, and the
|
|
118
|
+
* spring above it sets how far the body's corner floats over that wheel. Load
|
|
119
|
+
* transfer therefore *lowers a corner of the body* rather than raising a wheel, and
|
|
120
|
+
* the body's pitch and roll are read back from the four corner heights. Wheels stay
|
|
121
|
+
* on the ground by construction, and the attitude is always one the suspension can
|
|
122
|
+
* actually support.
|
|
123
|
+
*/
|
|
124
|
+
const longitudinalLoad = brake - throttle;
|
|
125
|
+
const speedFactor = Math.min(1, Math.abs(state.speed) / 12);
|
|
126
|
+
const lateralLoad = clamp(steerInput * speedFactor + steerInput * slip * 0.5, -1, 1);
|
|
127
|
+
// Fraction of available travel that load transfer may consume, leaving headroom.
|
|
128
|
+
const loadTravelShare = 0.5;
|
|
129
|
+
const samples = WHEEL_LAYOUT.map((wheel) => {
|
|
130
|
+
const point = wheelContactPoint(state, wheel);
|
|
131
|
+
const sample = surface.sample(point.x, point.z);
|
|
132
|
+
return { wheel, point, sample };
|
|
133
|
+
});
|
|
134
|
+
const meanGrip = samples.reduce((sum, entry) => sum + (entry.sample.grip ?? 1), 0) / samples.length;
|
|
135
|
+
const cornerHeights = [];
|
|
136
|
+
const wheels = samples.map(({ wheel, point, sample }) => {
|
|
137
|
+
const spring = compression.get(wheel.id) ?? { value: 0.5, velocity: 0 };
|
|
138
|
+
/*
|
|
139
|
+
* Target compression from load transfer. A front wheel (`forward > 0`) compresses
|
|
140
|
+
* under braking; an outside wheel compresses in a corner. 0.5 is rest.
|
|
141
|
+
*/
|
|
142
|
+
const target = clamp01(0.5 + (wheel.forward * longitudinalLoad
|
|
143
|
+
+ wheel.lateral * lateralLoad) * loadTravelShare);
|
|
144
|
+
if (settleImmediately) {
|
|
145
|
+
spring.value = target;
|
|
146
|
+
spring.velocity = 0;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// Explicit spring-damper integration so the damping behaviour is visible.
|
|
150
|
+
const acceleration = (target - spring.value) * resolved.springRate
|
|
151
|
+
- spring.velocity * 2 * resolved.dampingRatio * Math.sqrt(resolved.springRate);
|
|
152
|
+
spring.velocity += acceleration * step;
|
|
153
|
+
spring.value = clamp01(spring.value + spring.velocity * step);
|
|
154
|
+
}
|
|
155
|
+
compression.set(wheel.id, spring);
|
|
156
|
+
/*
|
|
157
|
+
* The wheel rests on its own surface. This is unconditional: a tyre cannot be
|
|
158
|
+
* inside the road, and on a driveable surface it should not be above it either.
|
|
159
|
+
*/
|
|
160
|
+
const wheelCentreY = sample.height + resolved.wheelRadius;
|
|
161
|
+
// The body's corner floats above that wheel by ride height, less compression.
|
|
162
|
+
const cornerY = wheelCentreY - resolved.wheelRadius + resolved.rideHeight
|
|
163
|
+
- (spring.value - 0.5) * resolved.suspensionTravel;
|
|
164
|
+
cornerHeights.push(cornerY);
|
|
165
|
+
return {
|
|
166
|
+
id: wheel.id,
|
|
167
|
+
position: [point.x, wheelCentreY, point.z],
|
|
168
|
+
steerAngle: wheel.steered ? steerInput * resolved.maxSteerAngle : 0,
|
|
169
|
+
spin,
|
|
170
|
+
compression: spring.value,
|
|
171
|
+
// A wheel is grounded when its surface is reachable within suspension travel.
|
|
172
|
+
// A hole deeper than the travel leaves the wheel hanging, which is correct.
|
|
173
|
+
grounded: true,
|
|
174
|
+
contactGap: 0
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
/*
|
|
178
|
+
* A wheel over a drop deeper than the suspension can reach is not grounded.
|
|
179
|
+
*
|
|
180
|
+
* Measured against the body: if a corner would have to sit further above its wheel
|
|
181
|
+
* than ride height plus full travel, the wheel has run out of extension and hangs.
|
|
182
|
+
*/
|
|
183
|
+
const bodyY = cornerHeights.reduce((sum, value) => sum + value, 0) / cornerHeights.length;
|
|
184
|
+
const maxReach = resolved.rideHeight + resolved.suspensionTravel * 0.5;
|
|
185
|
+
const resolvedWheels = wheels.map((wheel, index) => {
|
|
186
|
+
const sample = samples[index].sample;
|
|
187
|
+
const requiredDrop = bodyY - sample.height;
|
|
188
|
+
if (requiredDrop <= maxReach)
|
|
189
|
+
return wheel;
|
|
190
|
+
// Out of travel: the wheel stays at its fullest extension below the body and
|
|
191
|
+
// reports the gap to the surface it cannot reach.
|
|
192
|
+
const hangingCentreY = bodyY - maxReach + resolved.wheelRadius;
|
|
193
|
+
return {
|
|
194
|
+
...wheel,
|
|
195
|
+
position: [wheel.position[0], hangingCentreY, wheel.position[2]],
|
|
196
|
+
compression: 0,
|
|
197
|
+
grounded: false,
|
|
198
|
+
contactGap: requiredDrop - maxReach
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
/*
|
|
202
|
+
* Read the body's attitude back from the four corner heights.
|
|
203
|
+
*
|
|
204
|
+
* Front corners lower than rear means the nose has dropped, which is pitch. Left
|
|
205
|
+
* lower than right is roll. Derived rather than imposed, so the attitude can only
|
|
206
|
+
* be one the springs produced.
|
|
207
|
+
*/
|
|
208
|
+
const cornerBy = (id) => {
|
|
209
|
+
const index = WHEEL_LAYOUT.findIndex((wheel) => wheel.id === id);
|
|
210
|
+
return cornerHeights[index] ?? bodyY;
|
|
211
|
+
};
|
|
212
|
+
const frontHeight = (cornerBy("front-left") + cornerBy("front-right")) / 2;
|
|
213
|
+
const rearHeight = (cornerBy("rear-left") + cornerBy("rear-right")) / 2;
|
|
214
|
+
const leftHeight = (cornerBy("front-left") + cornerBy("rear-left")) / 2;
|
|
215
|
+
const rightHeight = (cornerBy("front-right") + cornerBy("rear-right")) / 2;
|
|
216
|
+
// A lower nose is a positive pitch, matching the convention that braking pitches
|
|
217
|
+
// the car nose-down with a positive angle.
|
|
218
|
+
pitch = clamp(Math.asin(clamp((rearHeight - frontHeight) / Math.max(1e-6, resolved.wheelbase), -1, 1)), -resolved.maxPitch, resolved.maxPitch);
|
|
219
|
+
// A lower left side is a negative roll, so steering right (which loads the left)
|
|
220
|
+
// produces a negative roll and steering left a positive one.
|
|
221
|
+
roll = clamp(Math.asin(clamp((leftHeight - rightHeight) / Math.max(1e-6, resolved.trackWidth), -1, 1)), -resolved.maxRoll, resolved.maxRoll);
|
|
222
|
+
// Rolling rotation from distance travelled. A stationary car's wheels must not
|
|
223
|
+
// spin, and a fast car's must, which is what makes motion legible.
|
|
224
|
+
const spinRate = resolved.wheelRadius > 0 ? state.speed / resolved.wheelRadius : 0;
|
|
225
|
+
spin = (spin + spinRate * step) % (Math.PI * 2);
|
|
226
|
+
const spunWheels = resolvedWheels.map((wheel) => ({ ...wheel, spin }));
|
|
227
|
+
const averageCompression = spunWheels.reduce((sum, wheel) => sum + wheel.compression, 0) / spunWheels.length;
|
|
228
|
+
const maxContactGap = Math.max(...spunWheels.map((wheel) => wheel.contactGap));
|
|
229
|
+
// Lowest contact patch across the four wheels: where a grounded model sits.
|
|
230
|
+
const contactPlaneY = Math.min(...resolvedWheels.map((wheel) => wheel.position[1] - resolved.wheelRadius));
|
|
231
|
+
currentPose = {
|
|
232
|
+
position: [state.x, bodyY, state.z],
|
|
233
|
+
groundedPosition: [state.x, contactPlaneY, state.z],
|
|
234
|
+
rotation: [pitch, state.heading, roll],
|
|
235
|
+
wheels: spunWheels,
|
|
236
|
+
contactGap: Math.min(...spunWheels.map((wheel) => wheel.contactGap)),
|
|
237
|
+
grounded: spunWheels.every((wheel) => wheel.grounded),
|
|
238
|
+
averageCompression
|
|
239
|
+
};
|
|
240
|
+
telemetry = {
|
|
241
|
+
speed: state.speed,
|
|
242
|
+
speedKph: state.speed * 3.6,
|
|
243
|
+
pitch,
|
|
244
|
+
roll,
|
|
245
|
+
steerAngle: steerInput * resolved.maxSteerAngle,
|
|
246
|
+
wheelSpinRate: spinRate,
|
|
247
|
+
groundedWheels: spunWheels.filter((wheel) => wheel.grounded).length,
|
|
248
|
+
maxContactGap,
|
|
249
|
+
averageCompression,
|
|
250
|
+
surfaceGrip: meanGrip
|
|
251
|
+
};
|
|
252
|
+
return currentPose;
|
|
253
|
+
};
|
|
254
|
+
return {
|
|
255
|
+
kind: "aura-vehicle-chassis",
|
|
256
|
+
spec: resolved,
|
|
257
|
+
step(dt, state) {
|
|
258
|
+
return resolvePose(dt, state, false);
|
|
259
|
+
},
|
|
260
|
+
pose() {
|
|
261
|
+
return currentPose ?? resolvePose(0, { x: 0, z: 0, heading: 0, speed: 0, steer: 0 }, true);
|
|
262
|
+
},
|
|
263
|
+
telemetry() {
|
|
264
|
+
return telemetry;
|
|
265
|
+
},
|
|
266
|
+
reset(state) {
|
|
267
|
+
for (const wheel of WHEEL_LAYOUT)
|
|
268
|
+
compression.set(wheel.id, { value: 0.5, velocity: 0 });
|
|
269
|
+
spin = 0;
|
|
270
|
+
pitch = 0;
|
|
271
|
+
roll = 0;
|
|
272
|
+
return resolvePose(0, state, true);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Derive chassis geometry from a vehicle asset's rendered bounds.
|
|
278
|
+
*
|
|
279
|
+
* This is what removes the need for a `CAR_SCENE_HEIGHT`-style constant. Given the
|
|
280
|
+
* size the asset actually renders at, the wheelbase, track width, wheel radius and
|
|
281
|
+
* ride height follow from standard automotive proportions, so a different car
|
|
282
|
+
* produces a different chassis instead of inheriting the old one's numbers.
|
|
283
|
+
*/
|
|
284
|
+
export function vehicleChassisSpecFromBounds(size, options = {}) {
|
|
285
|
+
// The longest horizontal axis is the body length; the other is its width.
|
|
286
|
+
const length = Math.max(size[0], size[2]);
|
|
287
|
+
const width = Math.min(size[0], size[2]);
|
|
288
|
+
const height = size[1];
|
|
289
|
+
const wheelDiameter = height * (options.wheelDiameterFraction ?? 0.42);
|
|
290
|
+
const wheelRadius = wheelDiameter / 2;
|
|
291
|
+
return {
|
|
292
|
+
wheelbase: length * (options.wheelbaseFraction ?? 0.6),
|
|
293
|
+
trackWidth: width * (options.trackFraction ?? 0.82),
|
|
294
|
+
wheelRadius,
|
|
295
|
+
// The chassis origin sits at the body's vertical centre, which is one wheel
|
|
296
|
+
// radius plus half the remaining body height above the contact patch.
|
|
297
|
+
rideHeight: wheelRadius + (height - wheelDiameter) * 0.5,
|
|
298
|
+
suspensionTravel: wheelRadius * 0.42
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Largest body rotation a given suspension travel can produce over a given span.
|
|
303
|
+
*
|
|
304
|
+
* A corner sits `span / 2` from the rotation axis, so rotating by `theta` moves it
|
|
305
|
+
* `sin(theta) * span / 2` vertically. Half the travel is reserved for the road, so
|
|
306
|
+
* the limit is `asin(travel / 2 / (span / 2))`.
|
|
307
|
+
*/
|
|
308
|
+
function attitudeLimitForTravel(travel, span) {
|
|
309
|
+
const halfSpan = Math.max(1e-6, span / 2);
|
|
310
|
+
const budget = Math.max(0, travel) / 2;
|
|
311
|
+
return Math.asin(Math.min(1, budget / halfSpan));
|
|
312
|
+
}
|
|
313
|
+
function clamp(value, min, max) {
|
|
314
|
+
return Math.min(max, Math.max(min, Number.isFinite(value) ? value : min));
|
|
315
|
+
}
|
|
316
|
+
function clamp01(value) {
|
|
317
|
+
return clamp(value, 0, 1);
|
|
318
|
+
}
|
|
319
|
+
function positive(value, fallback) {
|
|
320
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=VehicleChassis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VehicleChassis.js","sourceRoot":"","sources":["../../../../../packages/engine/src/agent-api/VehicleChassis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA0BH,gEAAgE;AAChE,MAAM,UAAU,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC;IACrD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AACjE,CAAC;AA2HD,MAAM,YAAY,GAA8H;IAC9I,EAAE,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE;IAChE,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE;IAChE,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE;IACjE,EAAE,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE;CAClE,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAwB,EAAE,OAAuB;IACpF,MAAM,QAAQ,GAAG;QACf,SAAS,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QACtC,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,CAAC;QAC1C,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC;QAC7C,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,CAAC;QAC1C,gBAAgB,EAAE,QAAQ,CAAC,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,UAAU,GAAG,IAAI,EAAE,IAAI,CAAC;QACjF,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC,CAAC;QAC9C,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,EAAE,IAAI,CAAC;QACvD,aAAa,EAAE,QAAQ,CAAC,IAAI,CAAC,aAAa,IAAI,IAAI,EAAE,IAAI,CAAC;QACzD;;;;;;;;;;;;WAYG;QACH,QAAQ,EAAE,IAAI,CAAC,GAAG,CAChB,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,EACnC,sBAAsB,CAAC,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,UAAU,GAAG,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CACxF;QACD,OAAO,EAAE,IAAI,CAAC,GAAG,CACf,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,EAClC,sBAAsB,CAAC,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,UAAU,GAAG,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CACzF;KACF,CAAC;IAEF,oEAAoE;IACpE,MAAM,WAAW,GAAG,IAAI,GAAG,EAAuD,CAAC;IACnF,KAAK,MAAM,KAAK,IAAI,YAAY;QAAE,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;IACvF,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,WAAoC,CAAC;IACzC,IAAI,SAAS,GAA4B;QACvC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC;QACvD,aAAa,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC;KAC7F,CAAC;IAEF,sEAAsE;IACtE,MAAM,iBAAiB,GAAG,CAAC,KAAyB,EAAE,KAAkC,EAA8C,EAAE;QACtI,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,GAAG,QAAQ,CAAC,SAAS,CAAC;QACnD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC;QACpD,OAAO;YACL,gFAAgF;YAChF,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,GAAG,GAAG,OAAO,GAAG,GAAG,GAAG,OAAO;YAC1C,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,GAAG,GAAG,OAAO,GAAG,GAAG,GAAG,OAAO;SAC3C,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,EAAU,EAAE,KAAyB,EAAE,iBAA0B,EAAe,EAAE;QACrG,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;QAC5C,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;QACtC,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAElD;;;;;;;;;;;;;;;;;;;WAmBG;QACH,MAAM,gBAAgB,GAAG,KAAK,GAAG,QAAQ,CAAC;QAC1C,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QAC5D,MAAM,WAAW,GAAG,KAAK,CAAC,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,IAAI,GAAG,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACrF,iFAAiF;QACjF,MAAM,eAAe,GAAG,GAAG,CAAC;QAE5B,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACzC,MAAM,KAAK,GAAG,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAC9C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;YAChD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAClC,CAAC,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;QAEpG,MAAM,aAAa,GAAa,EAAE,CAAC;QACnC,MAAM,MAAM,GAAuB,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE;YAC1E,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;YACxE;;;eAGG;YACH,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,CAC3B,KAAK,CAAC,OAAO,GAAG,gBAAgB;kBAC9B,KAAK,CAAC,OAAO,GAAG,WAAW,CAC9B,GAAG,eAAe,CAAC,CAAC;YAErB,IAAI,iBAAiB,EAAE,CAAC;gBACtB,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC;gBACtB,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC;YACtB,CAAC;iBAAM,CAAC;gBACN,0EAA0E;gBAC1E,MAAM,YAAY,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,UAAU;sBAC9D,MAAM,CAAC,QAAQ,GAAG,CAAC,GAAG,QAAQ,CAAC,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;gBACjF,MAAM,CAAC,QAAQ,IAAI,YAAY,GAAG,IAAI,CAAC;gBACvC,MAAM,CAAC,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;YAChE,CAAC;YACD,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YAElC;;;eAGG;YACH,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC;YAC1D,8EAA8E;YAC9E,MAAM,OAAO,GAAG,YAAY,GAAG,QAAQ,CAAC,WAAW,GAAG,QAAQ,CAAC,UAAU;kBACrE,CAAC,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC;YACrD,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC5B,OAAO;gBACL,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC,CAAgB;gBACzD,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;gBACnE,IAAI;gBACJ,WAAW,EAAE,MAAM,CAAC,KAAK;gBACzB,8EAA8E;gBAC9E,4EAA4E;gBAC5E,QAAQ,EAAE,IAAI;gBACd,UAAU,EAAE,CAAC;aACd,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH;;;;;WAKG;QACH,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC;QAC1F,MAAM,QAAQ,GAAG,QAAQ,CAAC,UAAU,GAAG,QAAQ,CAAC,gBAAgB,GAAG,GAAG,CAAC;QACvE,MAAM,cAAc,GAAuB,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YACrE,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAE,CAAC,MAAM,CAAC;YACtC,MAAM,YAAY,GAAG,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC;YAC3C,IAAI,YAAY,IAAI,QAAQ;gBAAE,OAAO,KAAK,CAAC;YAC3C,6EAA6E;YAC7E,kDAAkD;YAClD,MAAM,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC;YAC/D,OAAO;gBACL,GAAG,KAAK;gBACR,QAAQ,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAgB;gBAC/E,WAAW,EAAE,CAAC;gBACd,QAAQ,EAAE,KAAK;gBACf,UAAU,EAAE,YAAY,GAAG,QAAQ;aACpC,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH;;;;;;WAMG;QACH,MAAM,QAAQ,GAAG,CAAC,EAAkB,EAAE,EAAE;YACtC,MAAM,KAAK,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YACjE,OAAO,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC;QACvC,CAAC,CAAC;QACF,MAAM,WAAW,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC;QAC3E,MAAM,UAAU,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC;QACxE,MAAM,UAAU,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC;QACxE,MAAM,WAAW,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC;QAC3E,iFAAiF;QACjF,2CAA2C;QAC3C,KAAK,GAAG,KAAK,CACX,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EACxF,CAAC,QAAQ,CAAC,QAAQ,EAClB,QAAQ,CAAC,QAAQ,CAClB,CAAC;QACF,iFAAiF;QACjF,6DAA6D;QAC7D,IAAI,GAAG,KAAK,CACV,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EACzF,CAAC,QAAQ,CAAC,OAAO,EACjB,QAAQ,CAAC,OAAO,CACjB,CAAC;QAEF,+EAA+E;QAC/E,mEAAmE;QACnE,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;QACnF,IAAI,GAAG,CAAC,IAAI,GAAG,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAChD,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAEvE,MAAM,kBAAkB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC;QAC7G,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;QAE/E,4EAA4E;QAC5E,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;QAC3G,WAAW,GAAG;YACZ,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;YACnC,gBAAgB,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC;YACnD,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC;YACtC,MAAM,EAAE,UAAU;YAClB,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YACpE,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC;YACrD,kBAAkB;SACnB,CAAC;QACF,SAAS,GAAG;YACV,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,QAAQ,EAAE,KAAK,CAAC,KAAK,GAAG,GAAG;YAC3B,KAAK;YACL,IAAI;YACJ,UAAU,EAAE,UAAU,GAAG,QAAQ,CAAC,aAAa;YAC/C,aAAa,EAAE,QAAQ;YACvB,cAAc,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM;YACnE,aAAa;YACb,kBAAkB;YAClB,WAAW,EAAE,QAAQ;SACtB,CAAC;QACF,OAAO,WAAW,CAAC;IACrB,CAAC,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,sBAAsB;QAC5B,IAAI,EAAE,QAAQ;QACd,IAAI,CAAC,EAAE,EAAE,KAAK;YACZ,OAAO,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QACvC,CAAC;QACD,IAAI;YACF,OAAO,WAAW,IAAI,WAAW,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC7F,CAAC;QACD,SAAS;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,KAAK,CAAC,KAAK;YACT,KAAK,MAAM,KAAK,IAAI,YAAY;gBAAE,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;YACzF,IAAI,GAAG,CAAC,CAAC;YACT,KAAK,GAAG,CAAC,CAAC;YACV,IAAI,GAAG,CAAC,CAAC;YACT,OAAO,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QACrC,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAAiB,EAAE,UAO5D,EAAE;IACJ,0EAA0E;IAC1E,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACvB,MAAM,aAAa,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,qBAAqB,IAAI,IAAI,CAAC,CAAC;IACvE,MAAM,WAAW,GAAG,aAAa,GAAG,CAAC,CAAC;IACtC,OAAO;QACL,SAAS,EAAE,MAAM,GAAG,CAAC,OAAO,CAAC,iBAAiB,IAAI,GAAG,CAAC;QACtD,UAAU,EAAE,KAAK,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,IAAI,CAAC;QACnD,WAAW;QACX,4EAA4E;QAC5E,sEAAsE;QACtE,UAAU,EAAE,WAAW,GAAG,CAAC,MAAM,GAAG,aAAa,CAAC,GAAG,GAAG;QACxD,gBAAgB,EAAE,WAAW,GAAG,IAAI;KACrC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,sBAAsB,CAAC,MAAc,EAAE,IAAY;IAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACvC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,KAAK,CAAC,KAAa,EAAE,GAAW,EAAE,GAAW;IACpD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,OAAO,CAAC,KAAa;IAC5B,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,QAAQ,CAAC,KAAa,EAAE,QAAgB;IAC/C,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable AI driving: racing line, look-ahead steering, curvature-based speed.
|
|
3
|
+
*
|
|
4
|
+
* ## The defect this replaces
|
|
5
|
+
*
|
|
6
|
+
* Turbo's opponent was a route-local controller that steered only to null its
|
|
7
|
+
* lateral offset from the racing line:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const steeringCorrection = -snapshot.signedTrackOffset * steeringGain;
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* A pure proportional term on present error has no notion of where the track goes
|
|
14
|
+
* next. Approaching a corner it steers straight until it is already off the line,
|
|
15
|
+
* then over-corrects, which is why the opponent read as moving sideways and leaving
|
|
16
|
+
* the circuit. It also had no braking model tied to the corner ahead, so it entered
|
|
17
|
+
* every turn at cruise speed and understeered out of it. And because the kit's
|
|
18
|
+
* off-track handler pulls a certified car back toward the centreline, the recovery
|
|
19
|
+
* looked like the car being dragged rather than driving.
|
|
20
|
+
*
|
|
21
|
+
* This module drives the way a driver does: it samples the route **ahead** of the
|
|
22
|
+
* car, aims at a look-ahead point on the racing line, and sets a target speed from
|
|
23
|
+
* the curvature it is about to meet. It also detects being stuck and being off
|
|
24
|
+
* track, and recovers deliberately.
|
|
25
|
+
*
|
|
26
|
+
* Pure and deterministic: given the same route and the same seed it produces the
|
|
27
|
+
* same lap, so a race is reproducible and testable.
|
|
28
|
+
*/
|
|
29
|
+
export interface DriverRoutePoint {
|
|
30
|
+
readonly x: number;
|
|
31
|
+
readonly y: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Route the driver follows.
|
|
35
|
+
*
|
|
36
|
+
* Supplied by the caller from its own track topology, so the driver has no
|
|
37
|
+
* knowledge of any particular circuit.
|
|
38
|
+
*/
|
|
39
|
+
export interface DriverRoute {
|
|
40
|
+
/** Sample the racing line at a normalized progress 0..1. */
|
|
41
|
+
sample(progress: number): {
|
|
42
|
+
readonly x: number;
|
|
43
|
+
readonly y: number;
|
|
44
|
+
readonly heading: number;
|
|
45
|
+
};
|
|
46
|
+
/** Half-width of the drivable road at a progress. */
|
|
47
|
+
halfWidth(progress: number): number;
|
|
48
|
+
/** Total route length in world units, used to convert look-ahead distance to progress. */
|
|
49
|
+
readonly length: number;
|
|
50
|
+
}
|
|
51
|
+
export interface DriverVehicleState {
|
|
52
|
+
readonly progress: number;
|
|
53
|
+
readonly speed: number;
|
|
54
|
+
readonly heading: number;
|
|
55
|
+
/** Lateral offset from the racing line, signed positive to the left of travel. */
|
|
56
|
+
readonly signedTrackOffset: number;
|
|
57
|
+
readonly position: {
|
|
58
|
+
readonly x: number;
|
|
59
|
+
readonly y: number;
|
|
60
|
+
};
|
|
61
|
+
readonly offTrack: boolean;
|
|
62
|
+
}
|
|
63
|
+
export interface DriverInput {
|
|
64
|
+
readonly throttle: number;
|
|
65
|
+
readonly brake: number;
|
|
66
|
+
readonly steer: number;
|
|
67
|
+
readonly drift: boolean;
|
|
68
|
+
}
|
|
69
|
+
export type DriverAggression = "cautious" | "balanced" | "aggressive";
|
|
70
|
+
export interface DriverConfig {
|
|
71
|
+
readonly maxSpeed: number;
|
|
72
|
+
/** Fraction of `maxSpeed` targeted on a straight. */
|
|
73
|
+
readonly paceFraction?: number | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* Look-ahead time in seconds. The driver aims where it will be this far in the
|
|
76
|
+
* future, which is what lets it turn *before* the corner instead of after it.
|
|
77
|
+
*/
|
|
78
|
+
readonly lookAheadSeconds?: number | undefined;
|
|
79
|
+
/** Minimum look-ahead distance so a stationary car still has an aim point. */
|
|
80
|
+
readonly minLookAhead?: number | undefined;
|
|
81
|
+
/** Lateral correction gain. Scaled internally by route width. */
|
|
82
|
+
readonly lineGain?: number | undefined;
|
|
83
|
+
/** Heading correction gain. */
|
|
84
|
+
readonly headingGain?: number | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* Lateral acceleration the driver is willing to carry through a corner, in world
|
|
87
|
+
* units per second squared. This is what sets corner entry speed.
|
|
88
|
+
*/
|
|
89
|
+
readonly corneringAcceleration?: number | undefined;
|
|
90
|
+
readonly aggression?: DriverAggression | undefined;
|
|
91
|
+
/** Reaction delay in seconds. A human driver is not instantaneous. */
|
|
92
|
+
readonly reactionSeconds?: number | undefined;
|
|
93
|
+
/** Deterministic seed for line variation. */
|
|
94
|
+
readonly seed?: number | undefined;
|
|
95
|
+
}
|
|
96
|
+
export interface DriverTelemetry {
|
|
97
|
+
readonly controller: "aura-vehicle-driver-ai";
|
|
98
|
+
readonly aggression: DriverAggression;
|
|
99
|
+
readonly targetSpeed: number;
|
|
100
|
+
readonly lookAheadDistance: number;
|
|
101
|
+
readonly upcomingCurvature: number;
|
|
102
|
+
readonly cornerSpeedLimit: number;
|
|
103
|
+
readonly steerFromLine: number;
|
|
104
|
+
readonly steerFromHeading: number;
|
|
105
|
+
readonly recovering: boolean;
|
|
106
|
+
readonly stuckSeconds: number;
|
|
107
|
+
readonly decisionCount: number;
|
|
108
|
+
readonly input: DriverInput;
|
|
109
|
+
}
|
|
110
|
+
export interface VehicleDriverAi {
|
|
111
|
+
readonly kind: "aura-vehicle-driver-ai";
|
|
112
|
+
/** Decide inputs for the coming step. */
|
|
113
|
+
decide(dt: number, state: DriverVehicleState): DriverInput;
|
|
114
|
+
telemetry(): DriverTelemetry;
|
|
115
|
+
reset(): void;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Create a deterministic AI driver for a route.
|
|
119
|
+
*/
|
|
120
|
+
export declare function createVehicleDriverAi(route: DriverRoute, config: DriverConfig): VehicleDriverAi;
|
|
121
|
+
/** Smallest signed angle from `b` to `a`, in -pi..pi. */
|
|
122
|
+
export declare function angleDelta(a: number, b: number): number;
|
|
123
|
+
//# sourceMappingURL=VehicleDriverAi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VehicleDriverAi.d.ts","sourceRoot":"","sources":["../../../../../packages/engine/src/agent-api/VehicleDriverAi.ts"],"names":[],"mappings":"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"}
|