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
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
// A reverb zone, as an aspect on a Node (docs/audio-plan.md §2): a box or sphere volume that
|
|
2
|
-
// follows the node; while the listener is inside, the zone's reverb blends onto a bus (the bus's
|
|
3
|
-
// own reverb outside it). Zones nest: the smallest one containing the listener wins. `blend` is
|
|
4
|
-
// how many metres inside the border the crossfade takes — walking through a doorway is smooth.
|
|
5
|
-
//
|
|
6
|
-
// new Node().aspect(AudioZone, { box: [6, 2.5, 6], reverb: 'hall', blend: 2 })
|
|
7
|
-
// corridor.aspect(Shape, { box: [1, 1.5, 8] }).aspect(AudioZone, { reverb: 'bathroom' }) // the Shape's box
|
|
8
|
-
//
|
|
9
|
-
// Sizes follow Shape's convention: `box` = HALF-extents, `sphere` = radius, in the node's local
|
|
10
|
-
// units (the node's scale applies). Without host audio the aspect is inert.
|
|
11
|
-
|
|
12
|
-
import { Aspect } from "
|
|
13
|
-
import { reverbBlock, type ReverbParams, type ReverbPreset } from "
|
|
14
|
-
import { audio } from "
|
|
15
|
-
import { audioSupported } from "
|
|
16
|
-
import { cx, cy, cz, type Vec3Like } from "
|
|
17
|
-
import type { Node } from "
|
|
18
|
-
import { Shape } from "
|
|
19
|
-
|
|
20
|
-
export class AudioZone extends Aspect<"audioZone", Node> {
|
|
21
|
-
static readonly aspect = "audioZone"
|
|
22
|
-
|
|
23
|
-
private _box: Vec3Like | null = null
|
|
24
|
-
private _sphere: number | null = null
|
|
25
|
-
private _reverb: ReverbPreset | ReverbParams = "room"
|
|
26
|
-
private _blend = 1
|
|
27
|
-
private _bus = "sfx"
|
|
28
|
-
/** @internal engine zone id. */
|
|
29
|
-
_id = 0
|
|
30
|
-
/** The engine's zone id — what `audio.stats.listenerZone` reports while the listener is inside. */
|
|
31
|
-
get id(): number { return this._id }
|
|
32
|
-
|
|
33
|
-
/** Half-extents [hx, hy, hz]; falls back to the node's Shape box, then a 1 m cube. */
|
|
34
|
-
get box(): Vec3Like | null { return this._box }
|
|
35
|
-
set box(v: Vec3Like | null) { this._box = v; if (v) this._sphere = null; this._push() }
|
|
36
|
-
/** Radius; falls back to the node's Shape sphere. */
|
|
37
|
-
get sphere(): number | null { return this._sphere }
|
|
38
|
-
set sphere(v: number | null) { this._sphere = v; if (v !== null) this._box = null; this._push() }
|
|
39
|
-
/** A preset name or explicit params (see Bus.reverb). */
|
|
40
|
-
get reverb(): ReverbPreset | ReverbParams { return this._reverb }
|
|
41
|
-
set reverb(v: ReverbPreset | ReverbParams) { this._reverb = v; this._push() }
|
|
42
|
-
/** Crossfade depth in metres inside the border (0 = a hard edge). Default 1. */
|
|
43
|
-
get blend(): number { return this._blend }
|
|
44
|
-
set blend(v: number) { this._blend = v; this._push() }
|
|
45
|
-
/** The bus the zone's reverb rides on. Default `sfx`. */
|
|
46
|
-
get bus(): string { return this._bus }
|
|
47
|
-
set bus(v: string) { this._bus = v; this._push() }
|
|
48
|
-
|
|
49
|
-
onAttach(): void {
|
|
50
|
-
if (!audioSupported) return
|
|
51
|
-
this._id = _creatorAudio.createZone()
|
|
52
|
-
if (!this._id) return
|
|
53
|
-
_creatorAudio.attachZone(this._id, this.node.id)
|
|
54
|
-
this._push()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
onDetach(): void {
|
|
58
|
-
if (this._id && audioSupported) _creatorAudio.destroyZone(this._id)
|
|
59
|
-
this._id = 0
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
private _push(): void {
|
|
63
|
-
if (!this._id || !audioSupported) return
|
|
64
|
-
let box = this._box, sphere = this._sphere
|
|
65
|
-
if (!box && sphere === null) {
|
|
66
|
-
const shape = this.node.get(Shape)
|
|
67
|
-
if (shape?.sphere !== undefined) sphere = shape.sphere
|
|
68
|
-
else if (shape?.box) box = shape.box
|
|
69
|
-
}
|
|
70
|
-
const dims = sphere !== null
|
|
71
|
-
? new Float32Array([ sphere * 2, sphere * 2, sphere * 2 ])
|
|
72
|
-
: new Float32Array(box ? [ cx(box) * 2, cy(box) * 2, cz(box) * 2 ] : [ 2, 2, 2 ])
|
|
73
|
-
_creatorAudio.setZone(this._id, sphere !== null ? 1 : 0, dims, this._blend, audio._busId(this._bus), reverbBlock(this._reverb))
|
|
74
|
-
}
|
|
75
|
-
}
|
|
1
|
+
// A reverb zone, as an aspect on a Node (docs/audio-plan.md §2): a box or sphere volume that
|
|
2
|
+
// follows the node; while the listener is inside, the zone's reverb blends onto a bus (the bus's
|
|
3
|
+
// own reverb outside it). Zones nest: the smallest one containing the listener wins. `blend` is
|
|
4
|
+
// how many metres inside the border the crossfade takes — walking through a doorway is smooth.
|
|
5
|
+
//
|
|
6
|
+
// new Node().aspect(AudioZone, { box: [6, 2.5, 6], reverb: 'hall', blend: 2 })
|
|
7
|
+
// corridor.aspect(Shape, { box: [1, 1.5, 8] }).aspect(AudioZone, { reverb: 'bathroom' }) // the Shape's box
|
|
8
|
+
//
|
|
9
|
+
// Sizes follow Shape's convention: `box` = HALF-extents, `sphere` = radius, in the node's local
|
|
10
|
+
// units (the node's scale applies). Without host audio the aspect is inert.
|
|
11
|
+
|
|
12
|
+
import { Aspect } from "../../core/Aspect"
|
|
13
|
+
import { reverbBlock, type ReverbParams, type ReverbPreset } from "../../audio/Bus"
|
|
14
|
+
import { audio } from "../../audio/audio"
|
|
15
|
+
import { audioSupported } from "../../audio/support"
|
|
16
|
+
import { cx, cy, cz, type Vec3Like } from "../../math/vec"
|
|
17
|
+
import type { Node } from "../Node"
|
|
18
|
+
import { Shape } from "../physics/Shape"
|
|
19
|
+
|
|
20
|
+
export class AudioZone extends Aspect<"audioZone", Node> {
|
|
21
|
+
static readonly aspect = "audioZone"
|
|
22
|
+
|
|
23
|
+
private _box: Vec3Like | null = null
|
|
24
|
+
private _sphere: number | null = null
|
|
25
|
+
private _reverb: ReverbPreset | ReverbParams = "room"
|
|
26
|
+
private _blend = 1
|
|
27
|
+
private _bus = "sfx"
|
|
28
|
+
/** @internal engine zone id. */
|
|
29
|
+
_id = 0
|
|
30
|
+
/** The engine's zone id — what `audio.stats.listenerZone` reports while the listener is inside. */
|
|
31
|
+
get id(): number { return this._id }
|
|
32
|
+
|
|
33
|
+
/** Half-extents [hx, hy, hz]; falls back to the node's Shape box, then a 1 m cube. */
|
|
34
|
+
get box(): Vec3Like | null { return this._box }
|
|
35
|
+
set box(v: Vec3Like | null) { this._box = v; if (v) this._sphere = null; this._push() }
|
|
36
|
+
/** Radius; falls back to the node's Shape sphere. */
|
|
37
|
+
get sphere(): number | null { return this._sphere }
|
|
38
|
+
set sphere(v: number | null) { this._sphere = v; if (v !== null) this._box = null; this._push() }
|
|
39
|
+
/** A preset name or explicit params (see Bus.reverb). */
|
|
40
|
+
get reverb(): ReverbPreset | ReverbParams { return this._reverb }
|
|
41
|
+
set reverb(v: ReverbPreset | ReverbParams) { this._reverb = v; this._push() }
|
|
42
|
+
/** Crossfade depth in metres inside the border (0 = a hard edge). Default 1. */
|
|
43
|
+
get blend(): number { return this._blend }
|
|
44
|
+
set blend(v: number) { this._blend = v; this._push() }
|
|
45
|
+
/** The bus the zone's reverb rides on. Default `sfx`. */
|
|
46
|
+
get bus(): string { return this._bus }
|
|
47
|
+
set bus(v: string) { this._bus = v; this._push() }
|
|
48
|
+
|
|
49
|
+
onAttach(): void {
|
|
50
|
+
if (!audioSupported) return
|
|
51
|
+
this._id = _creatorAudio.createZone()
|
|
52
|
+
if (!this._id) return
|
|
53
|
+
_creatorAudio.attachZone(this._id, this.node.id)
|
|
54
|
+
this._push()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onDetach(): void {
|
|
58
|
+
if (this._id && audioSupported) _creatorAudio.destroyZone(this._id)
|
|
59
|
+
this._id = 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private _push(): void {
|
|
63
|
+
if (!this._id || !audioSupported) return
|
|
64
|
+
let box = this._box, sphere = this._sphere
|
|
65
|
+
if (!box && sphere === null) {
|
|
66
|
+
const shape = this.node.get(Shape)
|
|
67
|
+
if (shape?.sphere !== undefined) sphere = shape.sphere
|
|
68
|
+
else if (shape?.box) box = shape.box
|
|
69
|
+
}
|
|
70
|
+
const dims = sphere !== null
|
|
71
|
+
? new Float32Array([ sphere * 2, sphere * 2, sphere * 2 ])
|
|
72
|
+
: new Float32Array(box ? [ cx(box) * 2, cy(box) * 2, cz(box) * 2 ] : [ 2, 2, 2 ])
|
|
73
|
+
_creatorAudio.setZone(this._id, sphere !== null ? 1 : 0, dims, this._blend, audio._busId(this._bus), reverbBlock(this._reverb))
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// engine reads whatever camera it renders with. Set a node to listen from somewhere else (the
|
|
4
4
|
// player's head in a third-person game, so sounds pan around the character, not the camera).
|
|
5
5
|
|
|
6
|
-
import { audioSupported } from "
|
|
7
|
-
import type { Node } from "
|
|
6
|
+
import { audioSupported } from "../../audio/support"
|
|
7
|
+
import type { Node } from "../Node"
|
|
8
8
|
|
|
9
9
|
export class SceneAudio {
|
|
10
10
|
private _listener: Node | null = null
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
// Without a loaded NavMesh the aspect is inert — `goTo` returns false, nothing throws — so a
|
|
21
21
|
// project stays runnable on a host without navigation.
|
|
22
22
|
|
|
23
|
-
import { Aspect } from "
|
|
24
|
-
import type { FieldMeta } from "
|
|
25
|
-
import { Vec3, cx, cy, cz, type Vec3Like } from "
|
|
26
|
-
import { CharacterController } from "
|
|
23
|
+
import { Aspect } from "../../core/Aspect"
|
|
24
|
+
import type { FieldMeta } from "../../core/fields"
|
|
25
|
+
import { Vec3, cx, cy, cz, type Vec3Like } from "../../math/vec"
|
|
26
|
+
import { CharacterController } from "../physics/CharacterController"
|
|
27
27
|
import { NavCrowd, NavMesh, NAV_AGENT, NAV_READ } from "./NavMesh"
|
|
28
|
-
import type { Node } from "
|
|
28
|
+
import type { Node } from "../Node"
|
|
29
29
|
|
|
30
30
|
export type NavAgentState = "idle" | "moving" | "arrived" | "blocked" | "offmesh"
|
|
31
31
|
export type NavAgentAvoidance = "off" | "low" | "medium" | "high"
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
// (creator-nav) is the algorithms and the per-step hot path: ONE crowd update + ONE flat read per
|
|
19
19
|
// fixed step for every agent in the scene (`NavCrowd`, a system the mesh attaches to its scene).
|
|
20
20
|
|
|
21
|
-
import { fetch } from "
|
|
22
|
-
import { System } from "
|
|
23
|
-
import { Vec3, cx, cy, cz, type Vec3Like } from "
|
|
24
|
-
import { Geometry } from "
|
|
25
|
-
import { Material } from "
|
|
26
|
-
import { Mesh } from "
|
|
27
|
-
import type { Node } from "
|
|
28
|
-
import type { Scene } from "
|
|
21
|
+
import { fetch } from "../../runtime/fetch"
|
|
22
|
+
import { System } from "../../core/Aspect"
|
|
23
|
+
import { Vec3, cx, cy, cz, type Vec3Like } from "../../math/vec"
|
|
24
|
+
import { Geometry } from "../Geometry"
|
|
25
|
+
import { Material } from "../Material"
|
|
26
|
+
import { Mesh } from "../Mesh"
|
|
27
|
+
import type { Node } from "../Node"
|
|
28
|
+
import type { Scene } from "../Scene"
|
|
29
29
|
import type { NavAgent } from "./NavAgent"
|
|
30
30
|
|
|
31
31
|
// Fixed-order layouts — keep in step with creator-nav/creator-nav.h (CNAV_AGENT_* / CNAV_READ_*).
|
|
@@ -38,12 +38,12 @@
|
|
|
38
38
|
// Note: it collides with solid bodies but PASSES THROUGH triggers, and (v1) is not itself detected by
|
|
39
39
|
// triggers and isn't pointer-pickable while active.
|
|
40
40
|
|
|
41
|
-
import { Aspect } from "
|
|
42
|
-
import type { FieldMeta } from "
|
|
43
|
-
import { Vec3, cx, cy, cz, type Vec2Like, type Vec3Like } from "
|
|
44
|
-
import type { CompAxis, CompWriter } from "
|
|
41
|
+
import { Aspect } from "../../core/Aspect"
|
|
42
|
+
import type { FieldMeta } from "../../core/fields"
|
|
43
|
+
import { Vec3, cx, cy, cz, type Vec2Like, type Vec3Like } from "../../math/vec"
|
|
44
|
+
import type { CompAxis, CompWriter } from "../../core/compWrite"
|
|
45
45
|
import { Shape } from "./Shape"
|
|
46
|
-
import type { Node } from "
|
|
46
|
+
import type { Node } from "../Node"
|
|
47
47
|
|
|
48
48
|
/** Where the character's feet are, as reported by the solver after the last step. */
|
|
49
49
|
export type GroundState = "ground" | "slope" | "unsupported" | "air"
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
// velocity / applyImpulse. Reading node.position is free (native already wrote it). A `position` write
|
|
12
12
|
// is a TELEPORT that reaches the body (Shape routes it), which is how a kinematic body is placed too.
|
|
13
13
|
|
|
14
|
-
import { Aspect } from "
|
|
15
|
-
import type { FieldMeta } from "
|
|
16
|
-
import type { CompAxis, CompWriter } from "
|
|
17
|
-
import { Vec3, cx, cy, cz, type Vec3Like } from "
|
|
18
|
-
import { nodeRegistry, type Node } from "
|
|
14
|
+
import { Aspect } from "../../core/Aspect"
|
|
15
|
+
import type { FieldMeta } from "../../core/fields"
|
|
16
|
+
import type { CompAxis, CompWriter } from "../../core/compWrite"
|
|
17
|
+
import { Vec3, cx, cy, cz, type Vec3Like } from "../../math/vec"
|
|
18
|
+
import { nodeRegistry, type Node } from "../Node"
|
|
19
19
|
import { Shape } from "./Shape"
|
|
20
20
|
|
|
21
21
|
export type MotionType = "static" | "dynamic" | "kinematic"
|
|
@@ -95,6 +95,7 @@ export class Physics extends Aspect<"physics", Node> implements CompWriter {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
private _bodyId = 0
|
|
98
|
+
private static _refusedWarned = false
|
|
98
99
|
|
|
99
100
|
onAttach(): void {
|
|
100
101
|
if (!_creator.physicsHasSupport || !_creator.physicsHasSupport()) return
|
|
@@ -107,6 +108,12 @@ export class Physics extends Aspect<"physics", Node> implements CompWriter {
|
|
|
107
108
|
}
|
|
108
109
|
const shapeId = shape._claim() // drop the pick-only body; reuse its shape
|
|
109
110
|
this._bodyId = _creator.physicsCreateBody(this.node.id, shapeId, MOTION[this.motion], this.mass, false, false)
|
|
111
|
+
// The engine refuses a body past its limit (4 096 unless configured) — silently, and a level that
|
|
112
|
+
// crosses it loses whatever loads LAST (the terrain, streamed in after the props): say so once.
|
|
113
|
+
if (!this._bodyId && shapeId && !Physics._refusedWarned) {
|
|
114
|
+
Physics._refusedWarned = true
|
|
115
|
+
console.warn(`Physics: the engine refused a body for "${this.node.name}" — the body limit is reached? Raise it before the level loads: Physics.configure({ maxBodies })`)
|
|
116
|
+
}
|
|
110
117
|
// A fresh body already carries DEFAULT_FRICTION host-side, so only a NAMED surface has to
|
|
111
118
|
// travel — the common case costs no bridge call at all.
|
|
112
119
|
if (this._friction !== DEFAULT_FRICTION) _creator.physicsSetFriction?.(this._bodyId, this._friction)
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
// lie: the pose holds, nothing can wake them, the solver skips them. Raycasts hit it either way, and
|
|
27
27
|
// a frozen `'all'` body is an obstacle you can stand on.
|
|
28
28
|
|
|
29
|
-
import { Aspect } from "
|
|
30
|
-
import type { FieldMeta } from "
|
|
31
|
-
import { cx, cy, cz, type Vec3Like } from "
|
|
32
|
-
import { Node } from "
|
|
29
|
+
import { Aspect } from "../../core/Aspect"
|
|
30
|
+
import type { FieldMeta } from "../../core/fields"
|
|
31
|
+
import { cx, cy, cz, type Vec3Like } from "../../math/vec"
|
|
32
|
+
import { Node } from "../Node"
|
|
33
33
|
import { DEFAULT_FRICTION } from "./Physics"
|
|
34
34
|
|
|
35
35
|
export interface RagdollHinge {
|
|
@@ -71,7 +71,8 @@ const STRIDE = 12
|
|
|
71
71
|
|
|
72
72
|
// Bone names per humanoid role, tried in order: Mixamo / MotusMan / Unity-style first, then UE-style.
|
|
73
73
|
// `node.bone()` already matches the part after a `:` / `|` prefix (`mixamorig:Hips`).
|
|
74
|
-
|
|
74
|
+
/** @internal shared with DynamicBone's `colliders: 'humanoid'` */
|
|
75
|
+
export const HUMANOID: Record<string, string[]> = {
|
|
75
76
|
hips: [ "Hips", "pelvis", "Pelvis", "hip" ],
|
|
76
77
|
spine: [ "Spine", "spine_01", "spine", "Spine1" ],
|
|
77
78
|
neck: [ "Neck", "neck_01", "neck" ],
|
|
@@ -96,7 +97,8 @@ const HUMANOID: Record<string, string[]> = {
|
|
|
96
97
|
* legs, hands and feet where the rig has them): sizes for a ~1.8 m human, mass shares from body
|
|
97
98
|
* segment tables, knees and elbows as hinges that fold the natural way for a model standing in a
|
|
98
99
|
* T- or A-pose facing +Z. */
|
|
99
|
-
|
|
100
|
+
/** @internal shared with DynamicBone's `colliders: 'humanoid'` */
|
|
101
|
+
export const humanoidParts = (find: (role: string) => string | null): RagdollPart[] => {
|
|
100
102
|
const hips = find("hips"), spine = find("spine"), neck = find("neck"), head = find("head")
|
|
101
103
|
if (!hips) throw new Error("Ragdoll 'humanoid': no hips bone (Hips / pelvis) on this model — list the parts by name")
|
|
102
104
|
const parts: RagdollPart[] = []
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
// pick body is a STATIC snapshot at attach time — for a moving pickable object add a Physics body
|
|
18
18
|
// (which follows the sim), rather than relying on the pick body.
|
|
19
19
|
|
|
20
|
-
import { Aspect } from "
|
|
21
|
-
import { cx, cy, cz, type Vec3Like } from "
|
|
22
|
-
import type { Node } from "
|
|
20
|
+
import { Aspect } from "../../core/Aspect"
|
|
21
|
+
import { cx, cy, cz, type Vec3Like } from "../../math/vec"
|
|
22
|
+
import type { Node } from "../Node"
|
|
23
23
|
|
|
24
24
|
/** Which geometry a Shape uses — the one-of the fields below encode (see `_build`'s order). */
|
|
25
25
|
export type ShapeKind = "auto" | "box" | "sphere" | "cylinder" | "capsule" | "mesh" | "convex" | "heightfield"
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
// A trigger zone, as an aspect on a 3D Node. Requires a Shape (its geometry); creates a static SENSOR
|
|
2
|
-
// body from it, so a physics body overlapping the zone fires the node's 'enter' / 'exit' events
|
|
3
|
-
// instead of colliding. A trigger does not block movement, and is still pointer-pickable.
|
|
4
|
-
//
|
|
5
|
-
// const goal = new Mesh(box(), material)
|
|
6
|
-
// .aspect(Shape, { box: [1, 2, 1] })
|
|
7
|
-
// .aspect(Trigger)
|
|
8
|
-
// goal.addEventListener('enter', other => win(other))
|
|
9
|
-
// goal.addEventListener('exit', other => …)
|
|
10
|
-
|
|
11
|
-
import { Aspect } from "
|
|
12
|
-
import { Shape } from "./Shape"
|
|
13
|
-
import { ensurePhysicsEvents } from "./physicsEvents"
|
|
14
|
-
import type { Node } from "
|
|
15
|
-
|
|
16
|
-
export class Trigger extends Aspect<"trigger", Node> {
|
|
17
|
-
static readonly aspect = "trigger"
|
|
18
|
-
|
|
19
|
-
private _bodyId = 0
|
|
20
|
-
/** Native body id (0 if no physics support). */
|
|
21
|
-
get id(): number { return this._bodyId }
|
|
22
|
-
|
|
23
|
-
onAttach(): void {
|
|
24
|
-
if (!_creator.physicsHasSupport || !_creator.physicsHasSupport()) return
|
|
25
|
-
const shape = this.node.get(Shape)
|
|
26
|
-
if (!shape) {
|
|
27
|
-
throw new Error("Trigger requires a Shape aspect — add it first: node.aspect(Shape, {…}).aspect(Trigger)")
|
|
28
|
-
}
|
|
29
|
-
const shapeId = shape._claim()
|
|
30
|
-
this._bodyId = _creator.physicsCreateBody(this.node.id, shapeId, 0 /* static */, 0, true /* sensor */, false)
|
|
31
|
-
shape._ownBody(this._bodyId)
|
|
32
|
-
ensurePhysicsEvents() // routes overlap events → the node's 'enter' / 'exit'
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
onDetach(): void {
|
|
36
|
-
if (this._bodyId) { _creator.physicsRemoveBody(this._bodyId); this._bodyId = 0 }
|
|
37
|
-
const shape = this.node.get(Shape)
|
|
38
|
-
if (shape) {
|
|
39
|
-
shape._ownBody(0) // the Shape held OUR body id — forget it, or its own detach removes it twice (native crash)
|
|
40
|
-
shape._recreatePickBody()
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// No moveTo(): `node.position = p` moves the node AND the sensor body it owns (see Node._xf).
|
|
45
|
-
}
|
|
1
|
+
// A trigger zone, as an aspect on a 3D Node. Requires a Shape (its geometry); creates a static SENSOR
|
|
2
|
+
// body from it, so a physics body overlapping the zone fires the node's 'enter' / 'exit' events
|
|
3
|
+
// instead of colliding. A trigger does not block movement, and is still pointer-pickable.
|
|
4
|
+
//
|
|
5
|
+
// const goal = new Mesh(box(), material)
|
|
6
|
+
// .aspect(Shape, { box: [1, 2, 1] })
|
|
7
|
+
// .aspect(Trigger)
|
|
8
|
+
// goal.addEventListener('enter', other => win(other))
|
|
9
|
+
// goal.addEventListener('exit', other => …)
|
|
10
|
+
|
|
11
|
+
import { Aspect } from "../../core/Aspect"
|
|
12
|
+
import { Shape } from "./Shape"
|
|
13
|
+
import { ensurePhysicsEvents } from "./physicsEvents"
|
|
14
|
+
import type { Node } from "../Node"
|
|
15
|
+
|
|
16
|
+
export class Trigger extends Aspect<"trigger", Node> {
|
|
17
|
+
static readonly aspect = "trigger"
|
|
18
|
+
|
|
19
|
+
private _bodyId = 0
|
|
20
|
+
/** Native body id (0 if no physics support). */
|
|
21
|
+
get id(): number { return this._bodyId }
|
|
22
|
+
|
|
23
|
+
onAttach(): void {
|
|
24
|
+
if (!_creator.physicsHasSupport || !_creator.physicsHasSupport()) return
|
|
25
|
+
const shape = this.node.get(Shape)
|
|
26
|
+
if (!shape) {
|
|
27
|
+
throw new Error("Trigger requires a Shape aspect — add it first: node.aspect(Shape, {…}).aspect(Trigger)")
|
|
28
|
+
}
|
|
29
|
+
const shapeId = shape._claim()
|
|
30
|
+
this._bodyId = _creator.physicsCreateBody(this.node.id, shapeId, 0 /* static */, 0, true /* sensor */, false)
|
|
31
|
+
shape._ownBody(this._bodyId)
|
|
32
|
+
ensurePhysicsEvents() // routes overlap events → the node's 'enter' / 'exit'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onDetach(): void {
|
|
36
|
+
if (this._bodyId) { _creator.physicsRemoveBody(this._bodyId); this._bodyId = 0 }
|
|
37
|
+
const shape = this.node.get(Shape)
|
|
38
|
+
if (shape) {
|
|
39
|
+
shape._ownBody(0) // the Shape held OUR body id — forget it, or its own detach removes it twice (native crash)
|
|
40
|
+
shape._recreatePickBody()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// No moveTo(): `node.position = p` moves the node AND the sensor body it owns (see Node._xf).
|
|
45
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// is delivered to BOTH nodes with the other as the argument. type: 0 = enter (contact/overlap began),
|
|
3
3
|
// 1 = exit (ended). Registered lazily on the first trigger/physics body or 'enter'/'exit' listener.
|
|
4
4
|
|
|
5
|
-
import { nodeRegistry } from "
|
|
5
|
+
import { nodeRegistry } from "../Node"
|
|
6
6
|
|
|
7
7
|
let registered = false
|
|
8
8
|
export const ensurePhysicsEvents = (): void => {
|
|
@@ -31,16 +31,16 @@
|
|
|
31
31
|
// headless, an older build) gets the same chunks as child Mesh nodes from gl/terrainMesh.ts — the
|
|
32
32
|
// project runs everywhere, normal maps just read flat there.
|
|
33
33
|
|
|
34
|
-
import { Node } from "
|
|
35
|
-
import { Mesh } from "
|
|
36
|
-
import { Model } from "
|
|
37
|
-
import { Material } from "
|
|
38
|
-
import { Texture } from "
|
|
39
|
-
import { Geometry } from "
|
|
40
|
-
import { Shape } from "
|
|
41
|
-
import { Color, type ColorInput } from "
|
|
42
|
-
import { fetch } from "
|
|
43
|
-
import { Vec3, cx, cy, cz, type Vec3Like } from "
|
|
34
|
+
import { Node } from "../Node"
|
|
35
|
+
import { Mesh } from "../Mesh"
|
|
36
|
+
import { Model } from "../Model"
|
|
37
|
+
import { Material } from "../Material"
|
|
38
|
+
import { Texture } from "../Texture"
|
|
39
|
+
import { Geometry } from "../Geometry"
|
|
40
|
+
import { Shape } from "../physics/Shape"
|
|
41
|
+
import { Color, type ColorInput } from "../../core/color"
|
|
42
|
+
import { fetch } from "../../runtime/fetch"
|
|
43
|
+
import { Vec3, cx, cy, cz, type Vec3Like } from "../../math/vec"
|
|
44
44
|
import {
|
|
45
45
|
buildTerrainChunk, chunkCounts, faceNormalAt, gridRaycast, heightAt as gridHeightAt, normalAt as gridNormalAt, type TerrainGrid,
|
|
46
46
|
} from "./terrainMesh"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// read wrong on those hosts — the accepted fallback cost.
|
|
10
10
|
// Keep the hole / split rule in step with terrain.cpp buildChunk and Jolt's HeightFieldShape.
|
|
11
11
|
|
|
12
|
-
import { Geometry } from "
|
|
12
|
+
import { Geometry } from "../Geometry"
|
|
13
13
|
|
|
14
14
|
export type TerrainGrid = {
|
|
15
15
|
sizeX: number
|