lecodes-sdk 0.20.2 → 1.1.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 +35 -9
- package/dist/types/animate/tween/Animation.d.ts +69 -0
- package/dist/types/animate/tween/Timeline.d.ts +55 -0
- package/dist/types/animate/tween/animateValue.d.ts +27 -0
- package/dist/types/animate/tween/easing.d.ts +29 -0
- package/dist/types/animate/tween/spec.d.ts +178 -0
- package/dist/types/canvas/Canvas.d.ts +2 -0
- package/dist/types/g2/Node2D.d.ts +16 -0
- package/dist/types/g2/Sprite.d.ts +11 -1
- package/dist/types/gl/Camera.d.ts +15 -1
- package/dist/types/gl/DecalSet.d.ts +48 -3
- package/dist/types/gl/Foliage.d.ts +47 -0
- package/dist/types/gl/Geometry.d.ts +36 -0
- package/dist/types/gl/Light.d.ts +25 -7
- package/dist/types/gl/Lightmap.d.ts +90 -51
- package/dist/types/gl/Material.d.ts +32 -20
- package/dist/types/gl/Mesh.d.ts +7 -1
- package/dist/types/gl/Model.d.ts +41 -5
- package/dist/types/gl/Node.d.ts +18 -0
- package/dist/types/gl/Particles.d.ts +53 -1
- package/dist/types/gl/Scene.d.ts +21 -1
- package/dist/types/gl/animation/AnimationClip.d.ts +19 -0
- package/dist/types/gl/animation/Animator.d.ts +27 -0
- package/dist/types/gl/animation/DynamicBone.d.ts +184 -0
- package/dist/types/gl/animation/IK.d.ts +109 -0
- package/dist/types/gl/{Locomotion.d.ts → animation/Locomotion.d.ts} +6 -6
- package/dist/types/gl/animation/Warp.d.ts +2 -1
- package/dist/types/gl/animation/core.d.ts +35 -4
- 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/physics/Ragdoll.d.ts +161 -0
- 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} +10 -8
- 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 +35 -27
- package/dist/types/runtime/files.d.ts +24 -1
- package/dist/types/scene/defineScene.d.ts +50 -31
- package/dist/types/ui/UIButton.d.ts +3 -1
- package/dist/types/ui/UIInput.d.ts +5 -1
- package/dist/types/ui/UINode.d.ts +24 -24
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/prompts/README.md +142 -142
- package/prompts/core-design.md +27 -4
- package/prompts/core.md +35 -6
- package/prompts/select.ts +19 -4
- package/src/animate/tween/Animation.ts +378 -0
- package/src/animate/tween/Timeline.ts +175 -0
- package/src/animate/tween/animateValue.ts +100 -0
- package/src/animate/tween/easing.ts +172 -0
- package/src/animate/tween/spec.ts +479 -0
- package/src/bridges.d.ts +1760 -1481
- package/src/canvas/Canvas.ts +21 -0
- package/src/compile/__tests__/assetMacro.test.ts +26 -0
- package/src/compile/__tests__/compile.test.ts +11 -0
- package/src/compile/__tests__/detectEntry.test.ts +19 -0
- package/src/compile/__tests__/serverSplit.test.ts +27 -0
- package/src/compile/bundler.ts +34 -4
- package/src/compile/compileProject.ts +31 -1
- package/src/compile/detectEntry.ts +8 -3
- package/src/compile/header.ts +6 -3
- package/src/compile/index.ts +3 -0
- package/src/compile/sceneEditor.ts +42 -1
- package/src/compile/serverSplit.ts +9 -3
- package/src/g2/Node2D.ts +38 -0
- package/src/g2/Sprite.ts +20 -1
- package/src/gl/Camera.ts +34 -1
- package/src/gl/DecalSet.ts +132 -5
- package/src/gl/Foliage.ts +102 -0
- package/src/gl/Geometry.ts +109 -0
- package/src/gl/Light.ts +46 -16
- package/src/gl/Lightmap.ts +440 -249
- package/src/gl/Material.ts +69 -36
- package/src/gl/Mesh.ts +120 -102
- package/src/gl/Model.ts +167 -124
- package/src/gl/Node.ts +40 -1
- package/src/gl/Particles.ts +82 -5
- package/src/gl/Scene.ts +35 -2
- package/src/gl/animation/AnimationClip.ts +52 -0
- package/src/gl/animation/Animator.ts +42 -2
- package/src/gl/animation/DynamicBone.ts +482 -0
- package/src/gl/animation/IK.ts +214 -0
- package/src/gl/{Locomotion.ts → animation/Locomotion.ts} +7 -7
- package/src/gl/animation/Playback.ts +5 -4
- package/src/gl/animation/Warp.ts +5 -2
- package/src/gl/animation/core.ts +65 -4
- 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/physics/Ragdoll.ts +451 -0
- 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} +14 -12
- 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 +236 -224
- package/src/runtime/files.ts +32 -2
- package/src/scene/defineScene.ts +92 -66
- package/src/scene/level.ts +2 -2
- package/src/ui/UIButton.ts +2 -2
- package/src/ui/UIInput.ts +3 -3
- package/src/ui/UINode.ts +61 -36
- package/dist/types/animate/animate.d.ts +0 -20
- package/dist/types/gl/Gearbox.d.ts +0 -86
- package/dist/types/gl/IK.d.ts +0 -53
- package/dist/types/gl/Ragdoll.d.ts +0 -86
- package/dist/types/gl/Vehicle.d.ts +0 -191
- package/dist/types/gl/Wheel.d.ts +0 -95
- package/src/animate/animate.ts +0 -238
- package/src/gl/Gearbox.ts +0 -212
- package/src/gl/IK.ts +0 -193
- package/src/gl/Ragdoll.ts +0 -270
- 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
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// IK — chains the ENGINE solves in its late pass: after every aspect's update(dt) ran, before the
|
|
2
|
+
// dynamic bones and the skin flush. Attach to the END bone of the chain (the chain is its parent and
|
|
3
|
+
// grandparent); nothing runs in JS per frame — the aspect only describes the chain, the host reads the
|
|
4
|
+
// target nodes itself each frame and creator-anim (anchors.cpp) solves.
|
|
5
|
+
//
|
|
6
|
+
// const foot = hero.bone('LeftFoot')!
|
|
7
|
+
// foot.aspect(IK.TwoBone, { target: footTarget, pole: kneeHint }) // upLeg → leg → foot
|
|
8
|
+
// foot.ik.weight = grounded ? 1 : 0 // blend in/out
|
|
9
|
+
// hero.bone('Head')!.aspect(IK.LookAt, { target: camera, limit: 70 }) // head tracks the camera
|
|
10
|
+
//
|
|
11
|
+
// ANCHORED chains — hands on a weapon's grips (the gun-master rig): the chain rides a SOCKET of the
|
|
12
|
+
// rig (`anim.sockets(...)`: named empties on a bone, a weapon's grip / magazine / bolt), keeping the
|
|
13
|
+
// animated hand's pose RELATIVE to that socket on the gun the take was authored on and replaying it
|
|
14
|
+
// relative to the socket on the gun in the hands — shifted and turned by the sockets' difference,
|
|
15
|
+
// never re-authored. A clip's anchor spans (`clip.anchors(...)`) move the hand between sockets while
|
|
16
|
+
// it plays (grip → magazine → free → bolt → grip through a reload):
|
|
17
|
+
//
|
|
18
|
+
// arms.anim.sockets('ik_hand_gun', { grip_r: weapon.gripR, grip_l: weapon.gripL, mag: weapon.mag })
|
|
19
|
+
// arms.anim.sockets('ik_hand_gun', { grip_r: {...}, grip_l: {...}, mag: {...} }, { set: 'tr15' }) // the donor's
|
|
20
|
+
// arms.bone('hand_l')!.aspect(IK.TwoBone, { anchor: 'grip_l' })
|
|
21
|
+
// arms.anim.clip('R_ReloadEmpty')!.anchors('hand_l', { donor: 'tr15', spans: [[0.9, 1.4, 'mag'], [1.4, 2.0, ''], [2.0, 2.4, 'mag']] })
|
|
22
|
+
//
|
|
23
|
+
// Since the engine solves after the late phase, a target the game moves in its own update(dt) — the
|
|
24
|
+
// gun bone placed for aiming and recoil — is reached the SAME frame.
|
|
25
|
+
|
|
26
|
+
import { Aspect } from "../../core/Aspect"
|
|
27
|
+
import { Vec3, type Vec3Like } from "../../math/vec"
|
|
28
|
+
import { Quat } from "../../math/quat"
|
|
29
|
+
import type { Node } from "../Node"
|
|
30
|
+
import { Animator } from "./Animator"
|
|
31
|
+
import type { Core } from "./core"
|
|
32
|
+
|
|
33
|
+
type Target = Node | Vec3Like
|
|
34
|
+
|
|
35
|
+
const isNode = (t: unknown): t is Node => typeof t === "object" && t !== null && typeof (t as Node).id === "number" && typeof (t as { worldMatrix?: unknown }).worldMatrix === "object"
|
|
36
|
+
|
|
37
|
+
// CANIM_IK_* (creator-anim.h): the fixed-order parameter array of a chain.
|
|
38
|
+
const P = { TARGET: 0, POLE: 3, HAS_POLE: 6, WEIGHT: 7, ROT: 8, ROT_WEIGHT: 12, AXIS: 13, LIMIT: 16, ENABLED: 17, ANCHOR_TIME: 18, ANCHOR_PIN: 19, COUNT: 20 }
|
|
39
|
+
const stateBuf = new Float32Array(4)
|
|
40
|
+
|
|
41
|
+
/** The rig a bone belongs to: the nearest ancestor carrying an Animator (every Model has one); a bare
|
|
42
|
+
* node tree gets one on its topmost node. */
|
|
43
|
+
const rigOf = (bone: Node): Core => {
|
|
44
|
+
let top: Node = bone
|
|
45
|
+
for (let n: Node | null = bone; n; n = n.parent) {
|
|
46
|
+
const anim = n.get(Animator)
|
|
47
|
+
if (anim) return anim._core
|
|
48
|
+
top = n
|
|
49
|
+
}
|
|
50
|
+
top.aspect(Animator)
|
|
51
|
+
return top.get(Animator)!._core
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** What both chains share: the native handle, the parameter push, the readback. */
|
|
55
|
+
abstract class Chain<Name extends string> extends Aspect<Name, Node> {
|
|
56
|
+
protected _rig?: Core
|
|
57
|
+
protected _ik = 0
|
|
58
|
+
protected _weight = 1
|
|
59
|
+
protected _enabled = true
|
|
60
|
+
protected abstract readonly kind: 0 | 1
|
|
61
|
+
|
|
62
|
+
/** 0–1 contribution (blend in/out, e.g. foot planting only while grounded). */
|
|
63
|
+
get weight(): number { return this._weight }
|
|
64
|
+
set weight(v: number) { this._weight = v; this.push() }
|
|
65
|
+
/** Solve every frame (default). */
|
|
66
|
+
get enabled(): boolean { return this._enabled }
|
|
67
|
+
set enabled(v: boolean) { this._enabled = v; this.push() }
|
|
68
|
+
/** After the last solve: how far the end bone still is from its target, metres (0 = reached). */
|
|
69
|
+
get error(): number { return this._rig && this._ik && this._rig.ikState(this._ik, stateBuf) ? stateBuf[0]! : 0 }
|
|
70
|
+
|
|
71
|
+
onAttach(): void {
|
|
72
|
+
this._rig = rigOf(this.node)
|
|
73
|
+
this._ik = this._rig.ikCreate(this.kind, this.node.id)
|
|
74
|
+
if (!this._ik) console.warn(`IK: ${this.node.name || "the bone"} is no joint of its rig (or this host has no IK)`)
|
|
75
|
+
this.push()
|
|
76
|
+
}
|
|
77
|
+
onDetach(): void {
|
|
78
|
+
if (this._rig && this._ik) this._rig.ikDestroy(this._ik)
|
|
79
|
+
this._ik = 0
|
|
80
|
+
this._rig = undefined
|
|
81
|
+
}
|
|
82
|
+
onReconfigure(): void { this.push() }
|
|
83
|
+
|
|
84
|
+
protected params(): Float32Array {
|
|
85
|
+
const p = new Float32Array(P.COUNT)
|
|
86
|
+
p[P.WEIGHT] = this._weight
|
|
87
|
+
p[P.ENABLED] = this._enabled ? 1 : 0
|
|
88
|
+
p[P.LIMIT] = 80
|
|
89
|
+
p[P.AXIS + 2] = -1
|
|
90
|
+
p[P.ROT + 3] = 1
|
|
91
|
+
p[P.ANCHOR_TIME] = 0.05
|
|
92
|
+
return p
|
|
93
|
+
}
|
|
94
|
+
/** Push the description to the engine (a no-op until attached). */
|
|
95
|
+
protected abstract push(): void
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Two-bone analytic IK (limbs). Attach to the END bone: `foot.aspect(IK.TwoBone, { target })` solves
|
|
99
|
+
* upper (grandparent) + mid (parent) so the end reaches `target`; `pole` steers the bend (knee /
|
|
100
|
+
* elbow) — a Node or world point; `anchor` makes it ride a socket of the rig instead (see the file
|
|
101
|
+
* header). */
|
|
102
|
+
class TwoBone extends Chain<"ik"> {
|
|
103
|
+
static readonly aspect = "ik"
|
|
104
|
+
protected readonly kind = 0 as const
|
|
105
|
+
private _target?: Target
|
|
106
|
+
private _pole?: Target
|
|
107
|
+
private _rotation?: Node | Quat
|
|
108
|
+
private _rotationWeight = 1
|
|
109
|
+
private _anchor = ""
|
|
110
|
+
private _anchorTime = 0.05
|
|
111
|
+
private _anchorPin = 0
|
|
112
|
+
|
|
113
|
+
/** Where the end bone should be (a Node — read by the engine every frame — or a world position). */
|
|
114
|
+
get target(): Target | undefined { return this._target }
|
|
115
|
+
set target(v: Target | undefined) { this._target = v; this.push() }
|
|
116
|
+
/** Bend hint — the mid joint is pulled toward it (a Node or a world point). */
|
|
117
|
+
get pole(): Target | undefined { return this._pole }
|
|
118
|
+
set pole(v: Target | undefined) { this._pole = v; this.push() }
|
|
119
|
+
/** World rotation the END bone takes after the solve — a Node (its world rotation) or a Quat. Unset =
|
|
120
|
+
* the end bone keeps its animated rotation (a hand on a grip, a foot on a slope want it set). Blended
|
|
121
|
+
* by `weight · rotationWeight`. An anchored chain takes its rotation from the socket instead. */
|
|
122
|
+
get rotation(): Node | Quat | undefined { return this._rotation }
|
|
123
|
+
set rotation(v: Node | Quat | undefined) { this._rotation = v; this.push() }
|
|
124
|
+
/** 0–1 contribution of `rotation` (on top of `weight`); for an anchored chain, of the socket's turn. */
|
|
125
|
+
get rotationWeight(): number { return this._rotationWeight }
|
|
126
|
+
set rotationWeight(v: number) { this._rotationWeight = v; this.push() }
|
|
127
|
+
/** ANCHORED: the name of the LIVE socket the end bone rides (`anim.sockets(...)`) — its target and
|
|
128
|
+
* rotation come from the socket and the playing clips' anchor spans; `target` / `pole` / `rotation`
|
|
129
|
+
* are ignored. '' = a plain chain. */
|
|
130
|
+
get anchor(): string { return this._anchor }
|
|
131
|
+
set anchor(v: string) { this._anchor = v ?? ""; this.push() }
|
|
132
|
+
/** Anchored: how fast the hand moves between sockets — the halflife (s) of the spring the applied
|
|
133
|
+
* delta follows the wanted one with (default 0.05). */
|
|
134
|
+
get anchorTime(): number { return this._anchorTime }
|
|
135
|
+
set anchorTime(v: number) { this._anchorTime = v; this.push() }
|
|
136
|
+
/** Anchored: 0..1 how hard the end bone is PINNED on its socket while the playing clips leave it there
|
|
137
|
+
* (a GAP in their spans — the hand is holding the gun). 1 = it sits exactly on the socket, so nothing
|
|
138
|
+
* in the pose below can slide it along the gun; 0 (default) = it keeps the take's own relation to the
|
|
139
|
+
* socket, moved by the delta. A clip's SPANS carry their own pin and default to 0, because a hand on
|
|
140
|
+
* its way to a magazine must keep the take's motion — so this knob is about holding, not reaching. */
|
|
141
|
+
get anchorPin(): number { return this._anchorPin }
|
|
142
|
+
set anchorPin(v: number) { this._anchorPin = v; this.push() }
|
|
143
|
+
/** After the last solve: the anchor delta applied — how far (m) and how much (rad) the live socket
|
|
144
|
+
* moved the hand off the take's own pose. 0 for a plain chain. */
|
|
145
|
+
get anchorDelta(): { distance: number, angle: number } {
|
|
146
|
+
if (!this._rig || !this._ik || !this._rig.ikState(this._ik, stateBuf)) return { distance: 0, angle: 0 }
|
|
147
|
+
return { distance: stateBuf[1]!, angle: stateBuf[2]! }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
protected push(): void {
|
|
151
|
+
const rig = this._rig
|
|
152
|
+
if (!rig || !this._ik) return
|
|
153
|
+
const p = this.params()
|
|
154
|
+
p[P.ANCHOR_TIME] = this._anchorTime
|
|
155
|
+
p[P.ANCHOR_PIN] = this._anchorPin
|
|
156
|
+
const anchored = this._anchor !== ""
|
|
157
|
+
p[P.ROT_WEIGHT] = anchored || this._rotation !== undefined ? this._rotationWeight : 0
|
|
158
|
+
let targetNode = 0, poleNode = 0, rotNode = 0
|
|
159
|
+
if (!anchored) {
|
|
160
|
+
const t = this._target, pl = this._pole, r = this._rotation
|
|
161
|
+
if (isNode(t)) targetNode = t.id
|
|
162
|
+
else if (t !== undefined) { const v = new Vec3(t); p[P.TARGET] = v.x; p[P.TARGET + 1] = v.y; p[P.TARGET + 2] = v.z }
|
|
163
|
+
if (isNode(pl)) poleNode = pl.id
|
|
164
|
+
else if (pl !== undefined) { const v = new Vec3(pl); p[P.POLE] = v.x; p[P.POLE + 1] = v.y; p[P.POLE + 2] = v.z; p[P.HAS_POLE] = 1 }
|
|
165
|
+
if (isNode(r)) rotNode = r.id
|
|
166
|
+
else if (r !== undefined) { p[P.ROT] = r.x; p[P.ROT + 1] = r.y; p[P.ROT + 2] = r.z; p[P.ROT + 3] = r.w }
|
|
167
|
+
}
|
|
168
|
+
rig.ikSet(this._ik, p)
|
|
169
|
+
rig.ikNodes(this._ik, targetNode, poleNode, rotNode)
|
|
170
|
+
rig.ikAnchor(this._ik, this._anchor)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Aim a bone at a target (head / eyes / turret). Attach to the bone itself:
|
|
175
|
+
* `head.aspect(IK.LookAt, { target: camera, limit: 70 })`. `axis` is the bone's LOCAL forward
|
|
176
|
+
* (the direction that should point at the target) — rigs differ; default −Z, Mixamo heads look
|
|
177
|
+
* along +Z of the head bone in most exports, so pass `axis: [0, 0, 1]` there if it faces backwards. */
|
|
178
|
+
class LookAt extends Chain<"lookAt"> {
|
|
179
|
+
static readonly aspect = "lookAt"
|
|
180
|
+
protected readonly kind = 1 as const
|
|
181
|
+
private _target?: Target
|
|
182
|
+
private _axis: Vec3Like = [ 0, 0, -1 ]
|
|
183
|
+
private _limit = 80
|
|
184
|
+
|
|
185
|
+
/** What to look at (a Node — read by the engine every frame — or a world position). */
|
|
186
|
+
get target(): Target | undefined { return this._target }
|
|
187
|
+
set target(v: Target | undefined) { this._target = v; this.push() }
|
|
188
|
+
/** The bone's local axis that should point at the target. */
|
|
189
|
+
get axis(): Vec3Like { return this._axis }
|
|
190
|
+
set axis(v: Vec3Like) { this._axis = v; this.push() }
|
|
191
|
+
/** Max deflection from the animated direction, in degrees (default 80). */
|
|
192
|
+
get limit(): number { return this._limit }
|
|
193
|
+
set limit(v: number) { this._limit = v; this.push() }
|
|
194
|
+
|
|
195
|
+
protected push(): void {
|
|
196
|
+
const rig = this._rig
|
|
197
|
+
if (!rig || !this._ik) return
|
|
198
|
+
const p = this.params()
|
|
199
|
+
const a = new Vec3(this._axis)
|
|
200
|
+
p[P.AXIS] = a.x; p[P.AXIS + 1] = a.y; p[P.AXIS + 2] = a.z
|
|
201
|
+
p[P.LIMIT] = this._limit
|
|
202
|
+
let targetNode = 0
|
|
203
|
+
const t = this._target
|
|
204
|
+
if (isNode(t)) targetNode = t.id
|
|
205
|
+
else if (t !== undefined) { const v = new Vec3(t); p[P.TARGET] = v.x; p[P.TARGET + 1] = v.y; p[P.TARGET + 2] = v.z }
|
|
206
|
+
rig.ikSet(this._ik, p)
|
|
207
|
+
rig.ikNodes(this._ik, targetNode, 0, 0)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Inverse kinematics aspects — attach to bones (see file header). */
|
|
212
|
+
export const IK = { TwoBone, LookAt }
|
|
213
|
+
export type IKTwoBone = TwoBone
|
|
214
|
+
export type IKLookAt = LookAt
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
// how fast the gait moves, where its foot contacts fall), so nothing here is a hand-measured table.
|
|
28
28
|
// Give `angle` / `speed` only to override a measurement.
|
|
29
29
|
|
|
30
|
-
import { Aspect } from "
|
|
31
|
-
import { Vec3, cx, cz, type Vec3Like } from "
|
|
32
|
-
import { Animator } from "./
|
|
33
|
-
import type { FeetOptions } from "./
|
|
34
|
-
import type { WarpOptions } from "./
|
|
35
|
-
import { CharacterController } from "
|
|
36
|
-
import type { Node } from "
|
|
30
|
+
import { Aspect } from "../../core/Aspect"
|
|
31
|
+
import { Vec3, cx, cz, type Vec3Like } from "../../math/vec"
|
|
32
|
+
import { Animator } from "./Animator"
|
|
33
|
+
import type { FeetOptions } from "./Feet"
|
|
34
|
+
import type { WarpOptions } from "./Warp"
|
|
35
|
+
import { CharacterController } from "../physics/CharacterController"
|
|
36
|
+
import type { Node } from "../Node"
|
|
37
37
|
|
|
38
38
|
/** How fast the character wants to go: the gait picks its clips and its speed. */
|
|
39
39
|
export type Gait = "walk" | "run" | "sprint"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// Playback — the handle `play()` returns (docs/animator-plan.md). Thenable: `await anim.play('slash')`
|
|
2
|
-
// resolves at the clip's HAND-OVER —
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// resolves at the clip's HAND-OVER — its end (the `end` of its window) — with `true`; `false` when it
|
|
3
|
+
// was cut short (replaced / stopped). What you call right after the await is what its last pose
|
|
4
|
+
// transitions into: `anim.play(next)` (a chained one-shot), `anim.playLoop(next)` — nothing = back to
|
|
5
|
+
// the loop over `fadeOut` (or hold, on a layer without one). A cancel window is game logic (a clip
|
|
6
|
+
// event, `progress`, `busy`), not a fade: play the next clip whenever the game says so.
|
|
6
7
|
// Returning one from an `async` function flattens it into `Promise<boolean>` — return `p.done` (or
|
|
7
8
|
// make the function sync) when the caller needs the handle.
|
|
8
9
|
|
package/src/gl/animation/Warp.ts
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// before the feet (docs/animation-v2-plan.md §2.7). `stride` scales each leg's step to the speed the body
|
|
3
3
|
// really travels at (a walk played at 1.9 m/s stops skating); `orientation` turns the lower body toward
|
|
4
4
|
// where the body really goes while the spine counter-turns (arcs and strafing without a clip per angle).
|
|
5
|
-
// Both need the body's motion
|
|
5
|
+
// Both need the body's motion — a `Locomotion` feeds it every frame, or `anim.motion = velocity` when the
|
|
6
|
+
// gait is driven by hand; without either leave it off — and the CLIP's motion: its root motion, or for an
|
|
7
|
+
// in-place loop the pace its pack declared (`lecodes.velocity` in the GLB animation's extras). Off by
|
|
6
8
|
// default. `step` is the STEP WARP: stride / lift / pitch / slope dials solved in the knee hinge plane.
|
|
7
9
|
//
|
|
8
10
|
// model.anim.warp.set(true) // both, with the defaults
|
|
@@ -16,7 +18,8 @@ import type { Core } from "./core"
|
|
|
16
18
|
export type WarpOptions = {
|
|
17
19
|
/** Fit the stride to the speed the body actually travels at. `[min, max]` clamps the scale (default
|
|
18
20
|
* 0.85…1.2). A CORRECTION: a pack whose takes already read right at the speeds it is played at wants
|
|
19
|
-
* none of this; open the range for a pack that must cover speeds it was never recorded at.
|
|
21
|
+
* none of this; open the range for a pack that must cover speeds it was never recorded at. The clip's
|
|
22
|
+
* own speed is its root motion — an in-place loop takes part only with a declared pace (see above). */
|
|
20
23
|
stride?: boolean | [number, number]
|
|
21
24
|
/** Turn the lower body toward where the body really travels; a number caps the turn in degrees
|
|
22
25
|
* (default 20). The whole twist lives in one joint: a few degrees read as a lean, a lot as a broken
|
package/src/gl/animation/core.ts
CHANGED
|
@@ -25,15 +25,24 @@ export type PlayOptions = {
|
|
|
25
25
|
fade?: number
|
|
26
26
|
/** Transition seconds for the way in only (overrides `fade`). */
|
|
27
27
|
fadeIn?: number
|
|
28
|
-
/** One-shots: the transition BACK to the loop
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
/** One-shots: the transition BACK to the loop after the end (overrides `fade`; `0` = cut at the end).
|
|
29
|
+
* The clip plays to its last frame; the loop then takes over and the difference between the two poses
|
|
30
|
+
* (and their velocities) decays over this time, so no part of the clip is cut — to play less of it,
|
|
31
|
+
* give it an `end`. On a layer with no loop the clip HOLDS its last frame (a rock that broke stays
|
|
32
|
+
* broken) — `anim.stop({ fade })` is the way back to rest. */
|
|
32
33
|
fadeOut?: number
|
|
33
34
|
/** Playback rate for this clip (default 1). */
|
|
34
35
|
speed?: number
|
|
35
36
|
/** Rewind even if the clip is already playing (default: rewind only if it isn't). */
|
|
36
37
|
restart?: boolean
|
|
38
|
+
/** Play a WINDOW of the clip, seconds of the clip: enter at `start` (default 0; only when the play
|
|
39
|
+
* rewinds — see `restart`) and treat `end` (default the clip's end) as the end — the hand-over fires
|
|
40
|
+
* there, the last frame held there, events beyond it never fire. Only the swing of a longer take, the
|
|
41
|
+
* wind-up without the recovery. A window that lives in the clip table belongs in
|
|
42
|
+
* `AnimationClip.from(clip, { from, to })` instead. Not combined with `phase`. Hosts without the
|
|
43
|
+
* window play the whole clip. */
|
|
44
|
+
start?: number
|
|
45
|
+
end?: number
|
|
37
46
|
/** Enter the clip at a point in the GAIT CYCLE instead of at its start: `'match'` = the phase the
|
|
38
47
|
* layer shows right now (the walk's left foot is down → the turn clip starts where its left foot
|
|
39
48
|
* is down too, so nothing slides), or a number 0–1. Clips without a gait cycle ignore it. */
|
|
@@ -293,6 +302,12 @@ export class Core {
|
|
|
293
302
|
ensure(): boolean {
|
|
294
303
|
if (this.id) return true
|
|
295
304
|
if (this.embedded().length === 0 && this.table.size === 0) { console.warn(`Animator: no clips on ${this.node.name || "the node"} — nothing to play`); return false }
|
|
305
|
+
return this.ensureRig()
|
|
306
|
+
}
|
|
307
|
+
/** The native animator for the RIG alone — an IK chain or a socket on a model that has no clips
|
|
308
|
+
* still needs the joint tree described to the engine. False = no transform hierarchy (warned). */
|
|
309
|
+
ensureRig(): boolean {
|
|
310
|
+
if (this.id) return true
|
|
296
311
|
ensureEvents()
|
|
297
312
|
this.id = _creator.animatorCreate(this.node.id)
|
|
298
313
|
if (!this.id) { console.warn("Animator: node has no transform hierarchy"); return false }
|
|
@@ -303,6 +318,7 @@ export class Core {
|
|
|
303
318
|
if (this._feetParams && _creator.animatorSetFeetParams) _creator.animatorSetFeetParams(this.id, this._feetParams)
|
|
304
319
|
if (this.speed !== 1) _creator.animatorSetGlobal(this.id, this.speed, false)
|
|
305
320
|
for (const L of this.layers) if (L.index > 0) this.pushLayer(L)
|
|
321
|
+
for (const s of this._sockets.values()) this.pushSocket(s)
|
|
306
322
|
return true
|
|
307
323
|
}
|
|
308
324
|
destroy(): void {
|
|
@@ -378,6 +394,11 @@ export class Core {
|
|
|
378
394
|
s.playback = rec
|
|
379
395
|
_creator.animatorPlay(this.id, s.slot, false, o.speed ?? 1, fadeIn, fadeOut, o.restart ?? false)
|
|
380
396
|
if (o.turn !== undefined) _creator.animatorSlotSetTurn?.(this.id, s.slot, o.turn * Math.PI / 180, true)
|
|
397
|
+
if (o.start !== undefined || o.end !== undefined) {
|
|
398
|
+
const st = Math.max(0, o.start ?? 0), en = o.end === undefined ? -1 : Math.min(o.end, clip.duration)
|
|
399
|
+
if (en >= 0 && en <= st) console.warn(`Animator: '${name}' window ${st}–${o.end} is empty (${clip.duration.toFixed(2)}s clip)`)
|
|
400
|
+
_creator.animatorSlotSetWindow?.(this.id, s.slot, st, en)
|
|
401
|
+
}
|
|
381
402
|
if (phase !== undefined) _creator.animatorSeekPhase(this.id, s.slot, phase)
|
|
382
403
|
return new Playback(rec, this)
|
|
383
404
|
}
|
|
@@ -671,6 +692,11 @@ export class Core {
|
|
|
671
692
|
this._warp = params
|
|
672
693
|
if (this.id) _creator.animatorSetWarpParams(this.id, params)
|
|
673
694
|
}
|
|
695
|
+
/** The body's world velocity this frame (m/s) — the game side of the stride / orientation fit. Nothing
|
|
696
|
+
* to feed before the animator exists; hosts without the warp stage have no method. */
|
|
697
|
+
setMotion(vx: number, vy: number, vz: number): void {
|
|
698
|
+
if (this.id && _creator.animatorSetMotion) _creator.animatorSetMotion(this.id, vx, vy, vz)
|
|
699
|
+
}
|
|
674
700
|
/** The feet stage (lock / ground IK), as the engine's fixed-order parameter array. */
|
|
675
701
|
private _feetParams?: Float32Array
|
|
676
702
|
setFeetParams(params: Float32Array): void {
|
|
@@ -682,6 +708,41 @@ export class Core {
|
|
|
682
708
|
footState(side: 0 | 1, out: Float32Array): boolean {
|
|
683
709
|
return this.id !== 0 && _creator.animatorFootState !== undefined && _creator.animatorFootState(this.id, side, out)
|
|
684
710
|
}
|
|
711
|
+
// ---- IK chains + sockets (creator-anim anchors.cpp; the aspects in IK.ts) ----
|
|
712
|
+
// Hosts without the chains (an engine predating them) get a warning once and inert chains.
|
|
713
|
+
private static _warnedIk = false
|
|
714
|
+
private hasIk(): boolean {
|
|
715
|
+
if (_creator.animatorIkCreate) return true
|
|
716
|
+
if (!Core._warnedIk) { Core._warnedIk = true; console.warn("IK: this host has no IK chains (engine predates them) — chains and sockets are inert") }
|
|
717
|
+
return false
|
|
718
|
+
}
|
|
719
|
+
/** A chain ending in `endEntity` (kind 0 two-bone: the bone, its parent, its grandparent; 1 look-at). 0 = none. */
|
|
720
|
+
ikCreate(kind: 0 | 1, endEntity: number): number {
|
|
721
|
+
if (!this.ensureRig() || !this.hasIk()) return 0
|
|
722
|
+
return _creator.animatorIkCreate!(this.id, kind, endEntity)
|
|
723
|
+
}
|
|
724
|
+
ikDestroy(ik: number): void { if (this.id && _creator.animatorIkDestroy) _creator.animatorIkDestroy(this.id, ik) }
|
|
725
|
+
ikSet(ik: number, params: Float32Array): void { if (this.id && _creator.animatorIkSet) _creator.animatorIkSet(this.id, ik, params) }
|
|
726
|
+
ikNodes(ik: number, target: number, pole: number, rotation: number): void { if (this.id && _creator.animatorIkNodes) _creator.animatorIkNodes(this.id, ik, target, pole, rotation) }
|
|
727
|
+
ikAnchor(ik: number, socket: string): void { if (this.id && _creator.animatorIkAnchor) _creator.animatorIkAnchor(this.id, ik, socket) }
|
|
728
|
+
ikState(ik: number, out: Float32Array): boolean { return this.id !== 0 && _creator.animatorIkState !== undefined && _creator.animatorIkState(this.id, ik, out) }
|
|
729
|
+
/** Sockets by "set/name", replayed when the animator is (re)created. */
|
|
730
|
+
private readonly _sockets = new Map<string, { set: string, name: string, joint: number, node: number, trs: Float32Array | null }>()
|
|
731
|
+
/** Define a socket on joint `joint` (a bone's entity): a NODE socket (read by the engine every frame) or a
|
|
732
|
+
* fixed TRS in the joint's space. set "" = the live set, any other name = a donor's. */
|
|
733
|
+
setSocket(set: string, name: string, joint: number, node: number, trs: Float32Array | null): void {
|
|
734
|
+
const rec = { set, name, joint, node, trs }
|
|
735
|
+
this._sockets.set(`${set}/${name}`, rec)
|
|
736
|
+
if (this.ensureRig()) this.pushSocket(rec)
|
|
737
|
+
}
|
|
738
|
+
removeSocket(set: string, name: string): void {
|
|
739
|
+
this._sockets.delete(`${set}/${name}`)
|
|
740
|
+
if (this.id && _creator.animatorSetSocket) _creator.animatorSetSocket(this.id, set, name, 0, 0, null)
|
|
741
|
+
}
|
|
742
|
+
private pushSocket(s: { set: string, name: string, joint: number, node: number, trs: Float32Array | null }): void {
|
|
743
|
+
if (this.hasIk() && _creator.animatorSetSocket) _creator.animatorSetSocket(this.id, s.set, s.name, s.joint, s.node, s.trs)
|
|
744
|
+
}
|
|
745
|
+
|
|
685
746
|
private readonly _steps = new Set<StepHandler>()
|
|
686
747
|
onStepHandler(cb: StepHandler): void { this._steps.add(cb) }
|
|
687
748
|
offStepHandler(cb: StepHandler): void { this._steps.delete(cb) }
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
//
|
|
12
12
|
// Without host audio the aspect is inert: `play` returns an inert Voice, nothing throws.
|
|
13
13
|
|
|
14
|
-
import { Aspect } from "
|
|
15
|
-
import type { FieldMeta } from "
|
|
16
|
-
import { audio, sourceParams, type PlaySoundOptions, type Rolloff } from "
|
|
17
|
-
import type { Sound } from "
|
|
18
|
-
import { audioSupported } from "
|
|
19
|
-
import type { Voice } from "
|
|
20
|
-
import type { Node } from "
|
|
14
|
+
import { Aspect } from "../../core/Aspect"
|
|
15
|
+
import type { FieldMeta } from "../../core/fields"
|
|
16
|
+
import { audio, sourceParams, type PlaySoundOptions, type Rolloff } from "../../audio/audio"
|
|
17
|
+
import type { Sound } from "../../audio/Sound"
|
|
18
|
+
import { audioSupported } from "../../audio/support"
|
|
19
|
+
import type { Voice } from "../../audio/Voice"
|
|
20
|
+
import type { Node } from "../Node"
|
|
21
21
|
|
|
22
22
|
export type AudioCone = {
|
|
23
23
|
/** Degrees of full volume around the node's −Z. */
|
|
@@ -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"
|