lecodes-sdk 0.20.2 → 1.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/dist/global.d.ts +17 -5
- package/dist/types/canvas/Canvas.d.ts +2 -0
- package/dist/types/gl/DecalSet.d.ts +48 -3
- package/dist/types/gl/Geometry.d.ts +12 -0
- package/dist/types/gl/Lightmap.d.ts +9 -0
- package/dist/types/gl/Material.d.ts +4 -0
- package/dist/types/gl/Mesh.d.ts +7 -1
- package/dist/types/gl/Model.d.ts +34 -0
- package/dist/types/gl/Particles.d.ts +13 -0
- package/dist/types/gl/Scene.d.ts +1 -1
- package/dist/types/gl/animation/DynamicBone.d.ts +173 -0
- package/dist/types/gl/{IK.d.ts → animation/IK.d.ts} +4 -4
- package/dist/types/gl/{Locomotion.d.ts → animation/Locomotion.d.ts} +6 -6
- package/dist/types/gl/{AudioSource.d.ts → audio/AudioSource.d.ts} +6 -6
- package/dist/types/gl/{AudioZone.d.ts → audio/AudioZone.d.ts} +4 -4
- package/dist/types/gl/{SceneAudio.d.ts → audio/SceneAudio.d.ts} +1 -1
- package/dist/types/gl/{NavAgent.d.ts → nav/NavAgent.d.ts} +4 -4
- package/dist/types/gl/{NavMesh.d.ts → nav/NavMesh.d.ts} +4 -4
- package/dist/types/gl/{CharacterController.d.ts → physics/CharacterController.d.ts} +5 -5
- package/dist/types/gl/{Physics.d.ts → physics/Physics.d.ts} +6 -5
- package/dist/types/gl/{Ragdoll.d.ts → physics/Ragdoll.d.ts} +4 -4
- package/dist/types/gl/{Shape.d.ts → physics/Shape.d.ts} +3 -3
- package/dist/types/gl/{Trigger.d.ts → physics/Trigger.d.ts} +2 -2
- package/dist/types/gl/{Terrain.d.ts → terrain/Terrain.d.ts} +6 -6
- package/dist/types/gl/{terrainMesh.d.ts → terrain/terrainMesh.d.ts} +1 -1
- package/dist/types/gl/vehicle/Vehicle.d.ts +300 -0
- package/dist/types/gl/vehicle/Wheel.d.ts +147 -0
- package/dist/types/inject.d.ts +28 -26
- package/dist/types/runtime/files.d.ts +24 -1
- package/dist/types/scene/defineScene.d.ts +10 -3
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/prompts/README.md +142 -142
- package/src/bridges.d.ts +1599 -1481
- package/src/canvas/Canvas.ts +21 -0
- package/src/compile/__tests__/compile.test.ts +11 -0
- package/src/compile/header.ts +6 -3
- package/src/compile/index.ts +1 -0
- package/src/compile/sceneEditor.ts +42 -1
- package/src/gl/DecalSet.ts +132 -5
- package/src/gl/Geometry.ts +348 -284
- package/src/gl/Lightmap.ts +32 -5
- package/src/gl/Material.ts +21 -0
- package/src/gl/Mesh.ts +120 -102
- package/src/gl/Model.ts +33 -1
- package/src/gl/Node.ts +1 -1
- package/src/gl/Particles.ts +21 -3
- package/src/gl/Scene.ts +1 -1
- package/src/gl/animation/DynamicBone.ts +459 -0
- package/src/gl/{IK.ts → animation/IK.ts} +4 -4
- package/src/gl/{Locomotion.ts → animation/Locomotion.ts} +7 -7
- package/src/gl/{AudioSource.ts → audio/AudioSource.ts} +7 -7
- package/src/gl/{AudioZone.ts → audio/AudioZone.ts} +75 -75
- package/src/gl/{SceneAudio.ts → audio/SceneAudio.ts} +2 -2
- package/src/gl/{NavAgent.ts → nav/NavAgent.ts} +5 -5
- package/src/gl/{NavMesh.ts → nav/NavMesh.ts} +8 -8
- package/src/gl/{CharacterController.ts → physics/CharacterController.ts} +5 -5
- package/src/gl/{Physics.ts → physics/Physics.ts} +12 -5
- package/src/gl/{Ragdoll.ts → physics/Ragdoll.ts} +8 -6
- package/src/gl/{Shape.ts → physics/Shape.ts} +3 -3
- package/src/gl/{Trigger.ts → physics/Trigger.ts} +45 -45
- package/src/gl/{physicsEvents.ts → physics/physicsEvents.ts} +1 -1
- package/src/gl/{Terrain.ts → terrain/Terrain.ts} +10 -10
- package/src/gl/{terrainMesh.ts → terrain/terrainMesh.ts} +1 -1
- package/src/gl/vehicle/Vehicle.ts +666 -0
- package/src/gl/vehicle/Wheel.ts +290 -0
- package/src/inject.ts +226 -224
- package/src/runtime/files.ts +32 -2
- package/src/scene/defineScene.ts +26 -10
- package/src/scene/level.ts +2 -2
- package/dist/types/gl/Gearbox.d.ts +0 -86
- package/dist/types/gl/Vehicle.d.ts +0 -191
- package/dist/types/gl/Wheel.d.ts +0 -95
- package/src/gl/Gearbox.ts +0 -212
- package/src/gl/Vehicle.ts +0 -473
- package/src/gl/Wheel.ts +0 -240
- /package/dist/types/gl/{physicsEvents.d.ts → physics/physicsEvents.d.ts} +0 -0
package/src/scene/defineScene.ts
CHANGED
|
@@ -46,13 +46,14 @@ import { CameraPlace } from "../gl/CameraPlace"
|
|
|
46
46
|
import { Node } from "../gl/Node"
|
|
47
47
|
import { Mesh } from "../gl/Mesh"
|
|
48
48
|
import { Model } from "../gl/Model"
|
|
49
|
-
import { Physics } from "../gl/Physics"
|
|
49
|
+
import { Physics } from "../gl/physics/Physics"
|
|
50
|
+
import { CharacterController } from "../gl/physics/CharacterController"
|
|
50
51
|
import { Lightmap } from "../gl/Lightmap"
|
|
51
|
-
import { NavMesh } from "../gl/NavMesh"
|
|
52
|
-
import { Terrain, type TerrainLayer, type TerrainRegion, type TerrainOptions } from "../gl/Terrain"
|
|
52
|
+
import { NavMesh } from "../gl/nav/NavMesh"
|
|
53
|
+
import { Terrain, type TerrainLayer, type TerrainRegion, type TerrainOptions } from "../gl/terrain/Terrain"
|
|
53
54
|
import { Light, type SunOptions } from "../gl/Light"
|
|
54
55
|
import { assignMaterialDef, MaterialHandle, resolveMaterialDef, type MaterialDef } from "./material"
|
|
55
|
-
import type { CylinderOptions, PlaneOptions, SphereOptions } from "../gl/Geometry"
|
|
56
|
+
import type { CapsuleOptions, CylinderOptions, PlaneOptions, SphereOptions } from "../gl/Geometry"
|
|
56
57
|
import { GizmoBuffer, Gizmos, withGizmoScope, type GizmoBatch } from "./gizmos"
|
|
57
58
|
|
|
58
59
|
// ---- the literal grammar (what the visual editor reads and writes) -----------
|
|
@@ -61,6 +62,7 @@ export type MeshDef =
|
|
|
61
62
|
| { kind: "box", size?: Vec3Like | number }
|
|
62
63
|
| ({ kind: "sphere" } & SphereOptions)
|
|
63
64
|
| ({ kind: "cylinder" } & CylinderOptions)
|
|
65
|
+
| ({ kind: "capsule" } & CapsuleOptions)
|
|
64
66
|
| ({ kind: "plane" } & PlaneOptions)
|
|
65
67
|
|
|
66
68
|
export type LightDef = { kind: "sun" } & SunOptions
|
|
@@ -230,6 +232,10 @@ export type SceneLightmapDef = {
|
|
|
230
232
|
* ambient cube (2026-08-31) movers shade like the statics and the default is right; it stays as a
|
|
231
233
|
* trim. See `LightmapLoadOptions.volumeBoost`. */
|
|
232
234
|
volumeBoost?: number
|
|
235
|
+
/** Occluder-only statics (props without lightmap UVs): `"baked"` (default) = their shadow is in the
|
|
236
|
+
* atlas, they stop casting in real time and the shadow pass draws only the movers; `"realtime"` =
|
|
237
|
+
* they keep casting (their shadow on other non-receiver props). See `LightmapLoadOptions.occluderShadows`. */
|
|
238
|
+
occluderShadows?: "baked" | "realtime"
|
|
233
239
|
}
|
|
234
240
|
|
|
235
241
|
/** `env.navmesh` — the level's navigation mesh (see navmesh.md): the file `lecodes navmesh bake`
|
|
@@ -402,6 +408,7 @@ const createMesh = async (def: MeshDef, material?: MaterialDef): Promise<Mesh> =
|
|
|
402
408
|
case "box": mesh = Mesh.box({ size: def.size, material: mat }); break
|
|
403
409
|
case "sphere": { const { kind: _k, ...opts } = def; mesh = Mesh.sphere({ ...opts, material: mat }); break }
|
|
404
410
|
case "cylinder": { const { kind: _k, ...opts } = def; mesh = Mesh.cylinder({ ...opts, material: mat }); break }
|
|
411
|
+
case "capsule": { const { kind: _k, ...opts } = def; mesh = Mesh.capsule({ ...opts, material: mat }); break }
|
|
405
412
|
case "plane": { const { kind: _k, ...opts } = def; mesh = Mesh.plane({ ...opts, material: mat }); break }
|
|
406
413
|
}
|
|
407
414
|
if (material instanceof MaterialHandle) material._users.add({ node: mesh, slot: 0 })
|
|
@@ -417,11 +424,15 @@ const createMesh = async (def: MeshDef, material?: MaterialDef): Promise<Mesh> =
|
|
|
417
424
|
|
|
418
425
|
type LightmapCtx = { prefix: string, volume: boolean, dynamicOnly?: boolean }
|
|
419
426
|
|
|
427
|
+
// A CharacterController moves its node as surely as a dynamic body does (2026-09-11: the hero's
|
|
428
|
+
// capsule was read as static, so its sword baked a shadow into the terrain at the spawn point).
|
|
420
429
|
const movesByPhysics = (def: SceneNodeDef): boolean =>
|
|
421
430
|
(def.aspects ?? []).some((e) =>
|
|
422
|
-
(e.ctor as unknown) === Physics && ((e.props as { motion?: string } | undefined)?.motion ?? "dynamic") !== "static")
|
|
431
|
+
((e.ctor as unknown) === Physics && ((e.props as { motion?: string } | undefined)?.motion ?? "dynamic") !== "static")
|
|
432
|
+
|| (e.ctor as unknown) === CharacterController)
|
|
423
433
|
|
|
424
|
-
/** The static verdict for one node def (the editor's "Baked lighting" switch shows the same rule).
|
|
434
|
+
/** The static verdict for one node def (the editor's "Baked lighting" switch shows the same rule).
|
|
435
|
+
* Children of a mover are movers too — buildNodes carries that down, this is the node's own verdict. */
|
|
425
436
|
export const isLightmapStatic = (def: SceneNodeDef): boolean => def.lightmap ?? !movesByPhysics(def)
|
|
426
437
|
|
|
427
438
|
// `lightmap` undefined = Model.load's own default ('dynamic' while a level with a light volume runs —
|
|
@@ -860,7 +871,9 @@ const buildNodes = async (
|
|
|
860
871
|
): Promise<void> => {
|
|
861
872
|
const pending: { path: string, node: Node, def: SceneNodeDef }[] = []
|
|
862
873
|
|
|
863
|
-
|
|
874
|
+
// `parentMoves`: a child of a mover moves with it (the body under the hero's capsule) — it never
|
|
875
|
+
// bakes, whatever its own def says short of an explicit `lightmap: true`
|
|
876
|
+
const build = async (name: string, nd: SceneNodeDef, parentNode: Node | null, parentPath: string, parentMoves = false): Promise<void> => {
|
|
864
877
|
// names are path segments — '/' would fork the path, ':' would ambiguate editor card keys
|
|
865
878
|
if (name === "" || name.includes("/") || name.includes(":")) {
|
|
866
879
|
throw new Error(`Scene node name "${name}" is invalid — names are non-empty and contain no '/' or ':'`)
|
|
@@ -868,7 +881,7 @@ const buildNodes = async (
|
|
|
868
881
|
// the path is derived from def keys BEFORE any await, so it is deterministic even though
|
|
869
882
|
// Promise.all makes build completion (and record insertion) order nondeterministic
|
|
870
883
|
const path = parentPath === "" ? name : `${parentPath}/${name}`
|
|
871
|
-
const lmStatic = lm !== null && !lm.dynamicOnly && isLightmapStatic(nd)
|
|
884
|
+
const lmStatic = lm !== null && !lm.dynamicOnly && (nd.lightmap ?? (!parentMoves && isLightmapStatic(nd)))
|
|
872
885
|
// a mover in a level with a light volume reads the volume (models: the engine binds it; meshes: registered below)
|
|
873
886
|
const lmDynamic = lm !== null && !lmStatic && lm.volume
|
|
874
887
|
const node = await createSource(path, nd, lmStatic ? true : lmDynamic ? "dynamic" : undefined)
|
|
@@ -886,7 +899,7 @@ const buildNodes = async (
|
|
|
886
899
|
if (nd.prefab !== undefined) await attachPrefab(node, path, nd, scene, stack, lmStatic ? { prefix: `${lm!.prefix}${path}/`, volume: lm!.volume } : lm && lm.volume ? { prefix: `${lm.prefix}${path}/`, volume: true, dynamicOnly: true } : null)
|
|
887
900
|
pending.push({ path, node, def: nd })
|
|
888
901
|
nodes[path] = node
|
|
889
|
-
await Promise.all(Object.entries(nd.children ?? {}).map(([ childName, child ]) => build(childName, child, node, path)))
|
|
902
|
+
await Promise.all(Object.entries(nd.children ?? {}).map(([ childName, child ]) => build(childName, child, node, path, parentMoves || (lm !== null && !lmStatic))))
|
|
890
903
|
}
|
|
891
904
|
|
|
892
905
|
await Promise.all(Object.entries(defs).map(([ name, nd ]) => build(name, nd, parent, "")))
|
|
@@ -983,7 +996,7 @@ const instantiate = async (def: SceneDef, editorRuns: EditorRun[]): Promise<{ sc
|
|
|
983
996
|
if (lm && lightmap) {
|
|
984
997
|
await Lightmap.load(scene, { data: lightmap.data, texture: lightmap.texture, light: lightmap.light, volume: lightmap.volume },
|
|
985
998
|
{ sunStrength: lightmap.sunStrength, ambientScale: lightmap.ambientScale, aoStrength: lightmap.aoStrength,
|
|
986
|
-
lightBoost: lightmap.lightBoost, volumeBoost: lightmap.volumeBoost })
|
|
999
|
+
lightBoost: lightmap.lightBoost, volumeBoost: lightmap.volumeBoost, occluderShadows: lightmap.occluderShadows })
|
|
987
1000
|
}
|
|
988
1001
|
// navigation: load the baked mesh — or, under `lecodes navmesh bake`, dump the level's collision
|
|
989
1002
|
// geometry for it (the static bodies exist by now; edit mode never loads it — v1)
|
|
@@ -1117,6 +1130,9 @@ export class SceneHandle<D extends SceneDef = SceneDef> {
|
|
|
1117
1130
|
scene.add(node)
|
|
1118
1131
|
applyNode(node, p.slice(p.lastIndexOf("/") + 1), d)
|
|
1119
1132
|
if (isEditMode()) addEditorMarker(node, d)
|
|
1133
|
+
// a prefab wrapper instantiates its file's nodes (same path as buildNodes; no lightmap
|
|
1134
|
+
// context — a patched node is an edit-mode rebuild, baked lighting is the level's business)
|
|
1135
|
+
if (d.prefab !== undefined) await attachPrefab(node, p, d, scene, new Set([ this.def ]), null)
|
|
1120
1136
|
attachAspects(node, p, d, nodes, scene, this._editorRuns)
|
|
1121
1137
|
nodes[p] = node
|
|
1122
1138
|
await Promise.all(Object.entries(d.children ?? {}).map(([ cn, cd ]) => build(`${p}/${cn}`, cd, node)))
|
package/src/scene/level.ts
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
import { Node } from "../gl/Node"
|
|
16
16
|
import { Model } from "../gl/Model"
|
|
17
|
-
import { Shape } from "../gl/Shape"
|
|
18
|
-
import { Physics } from "../gl/Physics"
|
|
17
|
+
import { Shape } from "../gl/physics/Shape"
|
|
18
|
+
import { Physics } from "../gl/physics/Physics"
|
|
19
19
|
import { cx, cy, cz, type Vec3Like } from "../math/vec"
|
|
20
20
|
|
|
21
21
|
/** One placement handed to a `build` function (all coordinates local to the group). */
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { Aspect } from "../core/Aspect";
|
|
2
|
-
import type { FieldMeta } from "../core/fields";
|
|
3
|
-
import type { Node } from "./Node";
|
|
4
|
-
import type { Vehicle } from "./Vehicle";
|
|
5
|
-
export declare class Gearbox extends Aspect<"gearbox", Node> {
|
|
6
|
-
static readonly aspect = "gearbox";
|
|
7
|
-
/** Forward gear ratios, first gear first. Engine RPM = wheel speed × ratio × the differential. */
|
|
8
|
-
ratios: number[];
|
|
9
|
-
/** Reverse ratio — negative, because a ratio's SIGN is what reverse means to the engine. */
|
|
10
|
-
reverse: number;
|
|
11
|
-
/** Shift up when the engine passes this fraction of its redline. */
|
|
12
|
-
shiftUp: number;
|
|
13
|
-
/** Shift down when it falls below this fraction. */
|
|
14
|
-
shiftDown: number;
|
|
15
|
-
/** How long a shift takes, in seconds — the clutch is out for this long. */
|
|
16
|
-
shiftTime: number;
|
|
17
|
-
/** How long the clutch takes to re-engage afterwards. */
|
|
18
|
-
clutchTime: number;
|
|
19
|
-
/** How long to wait after a shift before another is allowed — without it a car sitting on a
|
|
20
|
-
* threshold hunts between two gears. */
|
|
21
|
-
latency: number;
|
|
22
|
-
/** How far the clutch bites the instant drive is asked for from rest, 0..1. It only has to COUPLE
|
|
23
|
-
* the shafts — how much torque that passes is the engine's business, so a feathered throttle
|
|
24
|
-
* still pulls away gently at the same bite point. */
|
|
25
|
-
bite: number;
|
|
26
|
-
static fields: FieldMeta<Gearbox>;
|
|
27
|
-
/** Current gear: −1 reverse, 0 neutral, 1… forward. Read it for a HUD. */
|
|
28
|
-
gear: number;
|
|
29
|
-
/** True while a shift is in progress — the clutch is out and the engine is not driving the wheels. */
|
|
30
|
-
get shifting(): boolean;
|
|
31
|
-
private _shiftLeft;
|
|
32
|
-
private _engageLeft;
|
|
33
|
-
private _waitLeft;
|
|
34
|
-
private _take;
|
|
35
|
-
/** EARLY phase: the pair has to be latched before this frame's step consumes it. */
|
|
36
|
-
updateBefore(dt: number): void;
|
|
37
|
-
/**
|
|
38
|
-
* The driver's left foot: where the clutch pedal is, ignoring shifts. Returns the new engagement.
|
|
39
|
-
*
|
|
40
|
-
* A driver does not compute anything. They watch the tacho: **revs sinking to idle means the
|
|
41
|
-
* wheels are about to drag the engine under, and that is when you push the clutch in.** That
|
|
42
|
-
* single test replaces every ratio and threshold — the crossover it finds is the exact speed where
|
|
43
|
-
* engine braking stops and the engine would start PUSHING instead, and it finds it per gear
|
|
44
|
-
* without being told the gear.
|
|
45
|
-
*
|
|
46
|
-
* The pedal comes back UP only for the throttle. Healthy revs are not on their own a reason to
|
|
47
|
-
* engage: a car standing still shows healthy revs too — the wheels simply are not turning to say
|
|
48
|
-
* otherwise — and a clutch dropped on that evidence lurches the car off with no throttle at all.
|
|
49
|
-
*
|
|
50
|
-
* Three things fall out of it for free:
|
|
51
|
-
*
|
|
52
|
-
* - **A braked car stops.** The engine cannot stall, so a clutch left in at 0 km/h feeds idle
|
|
53
|
-
* torque to the wheels forever and the car creeps against its own brakes. Here the revs pin at
|
|
54
|
-
* idle, so the pedal goes down and the brakes have nothing to fight.
|
|
55
|
-
* - **Reverse is not a special case.** No ratio takes part, so it needs no separate number — which
|
|
56
|
-
* is exactly why a fixed road-speed threshold got reverse wrong.
|
|
57
|
-
* - **Hill starts judder instead of stalling.** Let the pedal out, the load pulls the revs down,
|
|
58
|
-
* the rule pushes it straight back in, and the clutch sits slipping at the bite point until the
|
|
59
|
-
* car moves. So the test deliberately ignores the throttle — gating it on "no throttle" would
|
|
60
|
-
* stall the car under load. Throttle only holds the floor at `bite`.
|
|
61
|
-
*
|
|
62
|
-
* The cost is honest and small: once the pedal is down the revs sit at idle and cannot rise on
|
|
63
|
-
* their own, so **coasting downhill gives no engine braking until you touch the throttle** — which
|
|
64
|
-
* is precisely what a car with the clutch in does. Slipping it permanently to keep listening would
|
|
65
|
-
* bring the creep straight back.
|
|
66
|
-
*/
|
|
67
|
-
protected takeUp(v: Vehicle, dt: number): number;
|
|
68
|
-
/**
|
|
69
|
-
* Which gear the car should be in. The default is an ordinary automatic: pull away from neutral in
|
|
70
|
-
* the direction of the throttle, shift up past `shiftUp` of the redline and down below `shiftDown`,
|
|
71
|
-
* and refuse to shift again for `latency` seconds. Override this and nothing else.
|
|
72
|
-
*/
|
|
73
|
-
select(v: Vehicle): number;
|
|
74
|
-
/** Change gear now, with the clutch envelope. Safe to call from `select()` or from game code
|
|
75
|
-
* (a sequential manual is `if (Input.key('KeyE')) car.gearbox.shift(car.gearbox.gear + 1)`). */
|
|
76
|
-
shift(gear: number): void;
|
|
77
|
-
/** The ratio the engine should be running: 0 in neutral, negative in reverse. */
|
|
78
|
-
get ratio(): number;
|
|
79
|
-
/**
|
|
80
|
-
* The clutch scalar 0..1 the engine is given: the SHIFT envelope (out during a shift, ramping back
|
|
81
|
-
* over `clutchTime`) times the driver's pedal (`takeUp`). Two independent reasons for the clutch
|
|
82
|
-
* to be out, so they multiply: mid-shift is out however healthy the revs are, and an engine being
|
|
83
|
-
* dragged to idle is out whatever gear it just picked.
|
|
84
|
-
*/
|
|
85
|
-
get clutch(): number;
|
|
86
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { Aspect } from "../core/Aspect";
|
|
2
|
-
import type { FieldMeta } from "../core/fields";
|
|
3
|
-
import { Vec3, type Vec3Like } from "../math/vec";
|
|
4
|
-
import { type QuatLike } from "../math/quat";
|
|
5
|
-
import { Node } from "./Node";
|
|
6
|
-
import { Wheel } from "./Wheel";
|
|
7
|
-
/** Which axle the engine drives. A wheel's own `drive` overrides it. */
|
|
8
|
-
export type DriveLayout = "rear" | "front" | "all";
|
|
9
|
-
/**
|
|
10
|
-
* How an axle's two wheels share torque.
|
|
11
|
-
* - `lsd` — limited slip (Jolt's 1.4 ratio), the sane default.
|
|
12
|
-
* - `open` — fully open: a lifted or spinning wheel takes ALL the torque.
|
|
13
|
-
* - `locked` — both wheels forced to the same speed. Breaks traction predictably: the drift diff.
|
|
14
|
-
* A number is the raw max/min wheel-speed ratio (> 1; smaller is more locked).
|
|
15
|
-
*/
|
|
16
|
-
export type DifferentialMode = "lsd" | "open" | "locked";
|
|
17
|
-
export interface EngineConfig {
|
|
18
|
-
/** Peak torque in N·m (default 500). The single biggest "how fast is it" knob. */
|
|
19
|
-
torque?: number;
|
|
20
|
-
/** Redline in RPM (default 6000). */
|
|
21
|
-
maxRpm?: number;
|
|
22
|
-
/** Idle RPM (default 1000) — the engine never falls below it. */
|
|
23
|
-
idleRpm?: number;
|
|
24
|
-
/**
|
|
25
|
-
* The engine's CHARACTER: a flat list `[rpm, torque, rpm, torque, …]` in NORMALISED units — x is
|
|
26
|
-
* a fraction of the redline, y a fraction of `torque`. Left out, Jolt's own broad curve is used.
|
|
27
|
-
*
|
|
28
|
-
* `[0, 0.8, 0.66, 1, 1, 0.8]` is an ordinary petrol engine (peak two thirds up, falling to the
|
|
29
|
-
* redline); `[0, 1, 1, 1]` is flat, which is what an electric motor feels like.
|
|
30
|
-
*/
|
|
31
|
-
curve?: number[];
|
|
32
|
-
/** Moment of inertia in kg·m² (default 0.5) — how eagerly it revs when the clutch is out. */
|
|
33
|
-
inertia?: number;
|
|
34
|
-
/** Engine braking: angular damping of the engine itself (default 0.2). */
|
|
35
|
-
braking?: number;
|
|
36
|
-
}
|
|
37
|
-
export interface SteeringConfig {
|
|
38
|
-
/** Steering lock in degrees at a standstill (default 35). */
|
|
39
|
-
lock?: number;
|
|
40
|
-
/**
|
|
41
|
-
* The lock still available, in degrees, once the car reaches `speed` (default 10) — what stops a
|
|
42
|
-
* car over-driving its own front tires.
|
|
43
|
-
*
|
|
44
|
-
* A tire makes its peak grip at a few degrees of slip angle, and the steering angle a corner needs
|
|
45
|
-
* falls with the SQUARE of speed. Holding full lock at 100 km/h therefore asks the front for
|
|
46
|
-
* several times what it can give: the tires saturate, the car ploughs on, and the wheel stops
|
|
47
|
-
* answering. In between the lock eases on `(1 − v/speed)²`, which tracks the real curve closely.
|
|
48
|
-
* `0` turns the taper off — full lock at any speed.
|
|
49
|
-
*/
|
|
50
|
-
atSpeed?: number;
|
|
51
|
-
/** The speed in m/s at which the lock has fallen to `atSpeed` (default 30 ≈ 108 km/h). */
|
|
52
|
-
speed?: number;
|
|
53
|
-
/** How fast the front wheels may turn, in degrees per second (default 220). A keyboard asks for
|
|
54
|
-
* full lock in one frame, which no steering wheel can do, and that step alone snaps a tire past
|
|
55
|
-
* its peak. `0` = instantly. */
|
|
56
|
-
rate?: number;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Aerodynamics. Both numbers are quoted the same way — **a fraction of the car's own weight at
|
|
60
|
-
* 30 m/s (108 km/h)** — and both grow with the SQUARE of speed from there, so `0.5` at 30 m/s is
|
|
61
|
-
* `2.0` at 60 m/s. One mental model for both, and no drag coefficients or frontal areas to invent.
|
|
62
|
-
*/
|
|
63
|
-
export interface AeroConfig {
|
|
64
|
-
/**
|
|
65
|
-
* Downforce (default 0 — a car with no wing). The knob that decides whether a car corners better
|
|
66
|
-
* or worse the faster it goes: tire grip is a constant, but the force a corner demands grows with
|
|
67
|
-
* v², so with no downforce every car runs out of road at speed. `0.6` roughly doubles a car's
|
|
68
|
-
* cornering at 150 km/h; a road car is nearer `0.1`, a race car well past `1`.
|
|
69
|
-
*
|
|
70
|
-
* It presses along the CHASSIS' own down axis (that is what keeps a car planted over a crest) and
|
|
71
|
-
* only while a wheel is on the ground — a car on its roof is never launched by its own wing.
|
|
72
|
-
*/
|
|
73
|
-
downforce?: number;
|
|
74
|
-
/** Air resistance (default 0.03 — an ordinary car). What gives a car a top speed at all. */
|
|
75
|
-
drag?: number;
|
|
76
|
-
}
|
|
77
|
-
export declare class Vehicle extends Aspect<"vehicle", Node> {
|
|
78
|
-
static readonly aspect = "vehicle";
|
|
79
|
-
/** Chassis mass in kg (default 1200). Wheels are massless in this model. */
|
|
80
|
-
mass: number;
|
|
81
|
-
/** Centre of mass height in chassis space (metres, negative = lower). Left out, it drops to halfway
|
|
82
|
-
* down the chassis shape — a car with its mass at the shape's centre tips over. */
|
|
83
|
-
centerOfMass?: number;
|
|
84
|
-
/** Which axle the engine drives (default "rear"). A wheel's own `drive` overrides it. */
|
|
85
|
-
driveAxle: DriveLayout;
|
|
86
|
-
/** Differential lock across each driven axle. `locked` is the drift setup — both wheels turn
|
|
87
|
-
* together, so the pair lets go as a pair. Live: re-configure the aspect to switch it mid-drive. */
|
|
88
|
-
differential: DifferentialMode | number;
|
|
89
|
-
/**
|
|
90
|
-
* Anti-roll bars between each axle's wheels — what stops a car rolling onto its inside wheels in a
|
|
91
|
-
* fast corner. `true` (default) is a bar 0.01 as stiff as the wheel's own spring; a number is that
|
|
92
|
-
* ratio directly, and `0` / `false` removes the bars.
|
|
93
|
-
*
|
|
94
|
-
* **The numbers are far smaller than they look and the gain is steep** — Jolt's bar feeds
|
|
95
|
-
* `lengthDifference × stiffness × dt` as a bias into the suspension's own soft constraint, so 4%
|
|
96
|
-
* of the spring already flattens the body. Measured on a 1250 kg car: `0` → 1.3° of roll at 0.86 g,
|
|
97
|
-
* `0.01` → 0.6°, `0.15` → 0.2° (flat), `0.5` → the car HOPS.
|
|
98
|
-
*
|
|
99
|
-
* This is the knob for how much a car visibly LEANS, not for wheel lift: bars change how far the
|
|
100
|
-
* body tips, not how much load transfers. For lift, lower `centerOfMass`.
|
|
101
|
-
*/
|
|
102
|
-
antiRoll: boolean | number;
|
|
103
|
-
/** Pitch/roll limit in degrees (default 60) before the constraint rights the car. 180 turns it off. */
|
|
104
|
-
maxTilt: number;
|
|
105
|
-
/** Friction of the CHASSIS body itself — how it slides when it lands on its roof or side. Nothing
|
|
106
|
-
* to do with the tires; those are the `Wheel`'s `tire.grip` × the ground's friction. */
|
|
107
|
-
friction: number;
|
|
108
|
-
/** Strength of the clutch when fully engaged (default 10). Small values slip; large ones weld the
|
|
109
|
-
* engine to the wheels. The `Gearbox` scales it 0..1 while shifting. */
|
|
110
|
-
clutchStrength: number;
|
|
111
|
-
engine: EngineConfig;
|
|
112
|
-
steering: SteeringConfig;
|
|
113
|
-
aero: AeroConfig;
|
|
114
|
-
/** Scene-editor inspector: the flat, tunable half. The grouped objects are edited in code. */
|
|
115
|
-
static fields: FieldMeta<Vehicle>;
|
|
116
|
-
static editor: {
|
|
117
|
-
rebuild: boolean;
|
|
118
|
-
};
|
|
119
|
-
private _id;
|
|
120
|
-
private _bodyId;
|
|
121
|
-
private _builtMass;
|
|
122
|
-
private _wheels;
|
|
123
|
-
private _state;
|
|
124
|
-
private _stateFrame;
|
|
125
|
-
private _throttle;
|
|
126
|
-
private _steer;
|
|
127
|
-
private _brake;
|
|
128
|
-
private _handbrake;
|
|
129
|
-
onAttach(): void;
|
|
130
|
-
onDetach(): void;
|
|
131
|
-
/**
|
|
132
|
-
* Re-configuring an attached vehicle retunes it in place — the way a drift button switches the
|
|
133
|
-
* differential without the car so much as blinking:
|
|
134
|
-
*
|
|
135
|
-
* car.aspect(Vehicle, { differential: drift ? 'locked' : 'lsd' })
|
|
136
|
-
*
|
|
137
|
-
* Live: `differential`, `engine`, `steering`, `aero`, `maxTilt`, `friction`, `clutchStrength`, and
|
|
138
|
-
* every `Wheel`'s `tire.grip` and brake torques. Everything else describes the car's STRUCTURE —
|
|
139
|
-
* `mass`, `centerOfMass`, which wheels exist and where, their suspension, `driveAxle`, `antiRoll` — and
|
|
140
|
-
* is baked into the constraint at attach; to change one of those, re-attach the aspect.
|
|
141
|
-
*/
|
|
142
|
-
onReconfigure(): void;
|
|
143
|
-
/** Native vehicle id (0 until attached / no physics support). */
|
|
144
|
-
get id(): number;
|
|
145
|
-
/** The chassis rigid-body id — the same handle the plain body calls take. */
|
|
146
|
-
get bodyId(): number;
|
|
147
|
-
/** The wheels, in blob order (front axle first). */
|
|
148
|
-
get wheels(): readonly Wheel[];
|
|
149
|
-
/**
|
|
150
|
-
* The driver's controls, all in [-1, 1] (brakes in [0, 1]). Sticky: the last values keep applying
|
|
151
|
-
* until the next call, so a held key does not need re-sending. `forward < 0` asks for reverse; the
|
|
152
|
-
* `Gearbox` engages it once the car has stopped.
|
|
153
|
-
*/
|
|
154
|
-
drive(forward: number, steer?: number, brake?: number, handbrake?: number): this;
|
|
155
|
-
/** Teleport upright and clear all motion (velocity, engine, gearbox, wheel spin). Defaults to the
|
|
156
|
-
* chassis node's current pose — `reset()` alone un-flips a car where it lies. */
|
|
157
|
-
reset(position?: Vec3Like, rotation?: QuatLike): this;
|
|
158
|
-
/** Push the chassis (a boost pad, an explosion) — an instantaneous impulse in kg·m/s. */
|
|
159
|
-
applyImpulse(v: Vec3Like): this;
|
|
160
|
-
private _read;
|
|
161
|
-
/** Speed along the car's forward axis in m/s — negative when reversing (× 3.6 for km/h). */
|
|
162
|
-
get speed(): number;
|
|
163
|
-
/** Engine revolutions per minute. */
|
|
164
|
-
get rpm(): number;
|
|
165
|
-
/** How many wheels are touching the ground. */
|
|
166
|
-
get wheelsOnGround(): number;
|
|
167
|
-
/** Any wheel on the ground — false while airborne. */
|
|
168
|
-
get grounded(): boolean;
|
|
169
|
-
/** Chassis velocity in world units/s (fresh Vec3). */
|
|
170
|
-
get velocity(): Vec3;
|
|
171
|
-
/** The last throttle handed to `drive()` — what the gearbox reads to know which way you want to go. */
|
|
172
|
-
get throttle(): number;
|
|
173
|
-
/** The last steer input handed to `drive()`, [-1, 1]. */
|
|
174
|
-
get steerInput(): number;
|
|
175
|
-
/** The last foot-brake input handed to `drive()`, [0, 1] — the pedal, not the resulting force. */
|
|
176
|
-
get brake(): number;
|
|
177
|
-
/** The last handbrake input handed to `drive()`, [0, 1]. */
|
|
178
|
-
get handbrake(): number;
|
|
179
|
-
/** Current gear from the attached `Gearbox`: −1 reverse, 0 neutral, 1… forward. */
|
|
180
|
-
get gear(): number;
|
|
181
|
-
/** The steering lock, in degrees, the car is allowed at its current speed — the full lock at rest,
|
|
182
|
-
* falling to `steering.atSpeed`. The same curve the engine applies; useful on a HUD. */
|
|
183
|
-
get steerLock(): number;
|
|
184
|
-
/** Every Wheel under the chassis, ordered front axle first, with its axle group resolved. */
|
|
185
|
-
private _collectWheels;
|
|
186
|
-
/** Pack the whole configuration into the one blob the host ABI takes (see bridges.d.ts). */
|
|
187
|
-
private _buildSettings;
|
|
188
|
-
/** Edit mode: which way the chassis faces. Each wheel draws its own circle and travel range — see
|
|
189
|
-
* `Wheel.rebuild()`. Play mode never calls this. */
|
|
190
|
-
rebuild(): void;
|
|
191
|
-
}
|
package/dist/types/gl/Wheel.d.ts
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { Aspect } from "../core/Aspect";
|
|
2
|
-
import type { FieldMeta } from "../core/fields";
|
|
3
|
-
import { type Vec3Like } from "../math/vec";
|
|
4
|
-
import { Node } from "./Node";
|
|
5
|
-
/** Floats `vehicleGetState` writes before the per-wheel block: speed, rpm, contacts, vx, vy, vz. */
|
|
6
|
-
export declare const STATE_HEADER = 6;
|
|
7
|
-
/** Floats per wheel: contact, slip, slipAngle, suspensionLength, steerDeg, spin. */
|
|
8
|
-
export declare const WHEEL_STATE = 6;
|
|
9
|
-
/** How a tire behaves past its grip peak. `road` is Jolt's own curve — grip falls off after the peak,
|
|
10
|
-
* so the car can be made to let go. `arcade` holds the peak all the way out: much harder to spin. */
|
|
11
|
-
export type TireCurve = "road" | "arcade";
|
|
12
|
-
export interface WheelSuspension {
|
|
13
|
-
/** Total travel in metres, full droop to fully compressed (default 0.4). */
|
|
14
|
-
travel?: number;
|
|
15
|
-
/** Spring frequency in Hz — bigger is stiffer (default 1.4; a race car ~2.5, a truck ~1). It also
|
|
16
|
-
* sets the ride-height sag, and the sag is how far the wheel can still extend DOWN. */
|
|
17
|
-
stiffness?: number;
|
|
18
|
-
/** Damping ratio 0..1 (default 0.5). Low values pogo. */
|
|
19
|
-
damping?: number;
|
|
20
|
-
}
|
|
21
|
-
export interface WheelTire {
|
|
22
|
-
/** Grip multiplier on the friction curves (default 1). The grip a wheel actually gets is combined
|
|
23
|
-
* with the ground it stands on as `sqrt(tire × groundFriction)`, and a body's friction defaults to
|
|
24
|
-
* 0.6 — so a road a car should really corner on wants `Physics { friction: 1 }`. */
|
|
25
|
-
grip?: number;
|
|
26
|
-
/** Which friction curve (default `road`). */
|
|
27
|
-
curve?: TireCurve;
|
|
28
|
-
}
|
|
29
|
-
export interface WheelVisual {
|
|
30
|
-
/** The MODEL's axle direction in its own space (default `[1, 0, 0]`). Set it instead of parenting
|
|
31
|
-
* the mesh under a corrective node when the art points another way. */
|
|
32
|
-
axis?: Vec3Like;
|
|
33
|
-
/** Drawn radius, if the art is not exactly the physical `radius` — sinks a wheel into its arch or
|
|
34
|
-
* lets a fat tire overhang, with no effect on the simulation. Default: the physical radius. */
|
|
35
|
-
radius?: number;
|
|
36
|
-
/** Let the model spin (default true). `false` keeps the steer angle and suspension travel but not
|
|
37
|
-
* the rotation — a brake caliper. */
|
|
38
|
-
spin?: boolean;
|
|
39
|
-
}
|
|
40
|
-
export declare class Wheel extends Aspect<"wheel", Node> {
|
|
41
|
-
static readonly aspect = "wheel";
|
|
42
|
-
/** Wheel radius in metres. Default: measured from the node's rendered bounds, else 0.35. Too small
|
|
43
|
-
* and the car scrapes, too large and it floats. */
|
|
44
|
-
radius?: number;
|
|
45
|
-
/** Wheel width in metres. Default: measured, else 0.25. */
|
|
46
|
-
width?: number;
|
|
47
|
-
/** Does this wheel steer? Default: the wheels on the front axle. */
|
|
48
|
-
steer?: boolean;
|
|
49
|
-
/** Does the engine drive it? Default: from the vehicle's `drive` layout. */
|
|
50
|
-
drive?: boolean;
|
|
51
|
-
/** Foot brake on this wheel. */
|
|
52
|
-
brake: boolean;
|
|
53
|
-
/** Handbrake on this wheel. Default: every non-steering wheel (locking the front wheels of a moving
|
|
54
|
-
* car is not a handbrake turn, it is a crash). */
|
|
55
|
-
handbrake?: boolean;
|
|
56
|
-
/** Axle group, front to back. Default: derived from Z. Set it for a car whose wheels are not in neat
|
|
57
|
-
* left/right pairs — an axle's wheels are paired for the differential and the anti-roll bar. */
|
|
58
|
-
axle?: number;
|
|
59
|
-
/** Foot-brake torque in N·m (default 1500); the handbrake pulls 3× on the wheels that have it. */
|
|
60
|
-
brakeTorque: number;
|
|
61
|
-
suspension: WheelSuspension;
|
|
62
|
-
tire: WheelTire;
|
|
63
|
-
visual: WheelVisual;
|
|
64
|
-
/** Scene-editor inspector. `steer` / `drive` / `handbrake` are deliberately absent: they are
|
|
65
|
-
* tri-state (undefined = derive it from the axle), which a switch cannot express. */
|
|
66
|
-
static fields: FieldMeta<Wheel>;
|
|
67
|
-
static editor: {
|
|
68
|
-
rebuild: boolean;
|
|
69
|
-
};
|
|
70
|
-
private _angle;
|
|
71
|
-
private _scale;
|
|
72
|
-
private _local;
|
|
73
|
-
onAttach(): void;
|
|
74
|
-
onDetach(): void;
|
|
75
|
-
private _state;
|
|
76
|
-
/** Touching the ground this step. */
|
|
77
|
-
get contact(): boolean;
|
|
78
|
-
/** Longitudinal slip RATIO: 0 = rolling in sync with the ground, ~1 = spinning up or locked.
|
|
79
|
-
* Unsigned — smoke, tire marks and wheelspin sound key off this. */
|
|
80
|
-
get slip(): number;
|
|
81
|
-
/** Slip ANGLE in degrees: how far the tire's travel direction is off from where it points.
|
|
82
|
-
* Unsigned, 0..90 — past the curve's peak (~3°) the tire is sliding sideways. Drift particles. */
|
|
83
|
-
get slipAngle(): number;
|
|
84
|
-
/** Steer angle in degrees, positive to the right. */
|
|
85
|
-
get steerAngle(): number;
|
|
86
|
-
/** Wheel spin in rad/s (× radius ≈ the speed the tire is laying down). */
|
|
87
|
-
get spin(): number;
|
|
88
|
-
/** Suspension compression, 0 = fully extended (drooping) … 1 = fully compressed. */
|
|
89
|
-
get compression(): number;
|
|
90
|
-
/** LATE phase: after the step and the transform sync, right before the frame draws. */
|
|
91
|
-
update(dt: number): void;
|
|
92
|
-
/** Edit mode: this wheel's own circle at its resting height, its travel range, and a mark when the
|
|
93
|
-
* engine drives it — enough to place a wheel without running the game. */
|
|
94
|
-
rebuild(): void;
|
|
95
|
-
}
|