lecodes-cli 0.19.0 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/dist/index.js +730 -235
  2. package/package.json +14 -13
  3. package/runtime/scene-harness.json +1 -1
  4. package/runtime/sdk-types.json +1 -1
  5. package/src/api.ts +302 -0
  6. package/src/browserAuth.ts +87 -0
  7. package/src/cmgenTool.ts +104 -0
  8. package/src/commands/app.ts +891 -0
  9. package/src/commands/appAndroid.ts +603 -0
  10. package/src/commands/appDesktop.ts +326 -0
  11. package/src/commands/appDesktopMac.ts +470 -0
  12. package/src/commands/appIcon.ts +187 -0
  13. package/src/commands/appShared.ts +448 -0
  14. package/src/commands/appTemplates.ts +883 -0
  15. package/src/commands/appTemplatesAndroid.ts +599 -0
  16. package/src/commands/appTemplatesGradlew.ts +9 -0
  17. package/src/commands/assets.ts +28 -0
  18. package/src/commands/clone.ts +60 -0
  19. package/src/commands/compile.ts +253 -0
  20. package/src/commands/create.ts +59 -0
  21. package/src/commands/design.ts +863 -0
  22. package/src/commands/designTemplates.ts +11 -0
  23. package/src/commands/desktop.ts +122 -0
  24. package/src/commands/dev.ts +214 -0
  25. package/src/commands/diff.ts +59 -0
  26. package/src/commands/init.ts +191 -0
  27. package/src/commands/install.ts +198 -0
  28. package/src/commands/lightmap.ts +290 -0
  29. package/src/commands/link.ts +147 -0
  30. package/src/commands/login.ts +24 -0
  31. package/src/commands/navmesh.ts +225 -0
  32. package/src/commands/pn.ts +305 -0
  33. package/src/commands/projectTemplates.ts +129 -0
  34. package/src/commands/pull.ts +109 -0
  35. package/src/commands/push.ts +173 -0
  36. package/src/commands/render.ts +414 -0
  37. package/src/commands/scene.ts +148 -0
  38. package/src/commands/shaders.ts +157 -0
  39. package/src/commands/status.ts +29 -0
  40. package/src/commands/test.ts +370 -0
  41. package/src/commands/thumbs.ts +178 -0
  42. package/src/commands/types.ts +87 -0
  43. package/src/commands/update.ts +190 -0
  44. package/src/compile/assetIcons.ts +214 -0
  45. package/src/compile/collect.ts +46 -0
  46. package/src/compile/collectLocal.ts +37 -0
  47. package/src/compile/designCompile.ts +104 -0
  48. package/src/compile/fonts.ts +156 -0
  49. package/src/compile/headlessBundle.ts +129 -0
  50. package/src/compile/nativeStack.ts +21 -0
  51. package/src/compile/projectCompile.ts +109 -0
  52. package/src/compile/sceneCompile.ts +113 -0
  53. package/src/compile/screenEntry.ts +127 -0
  54. package/src/compile/shaders.ts +245 -0
  55. package/src/config.ts +42 -0
  56. package/src/designMeta.ts +35 -0
  57. package/src/desktopRenderer.ts +532 -0
  58. package/src/desktopScript.ts +276 -0
  59. package/src/dev/clientTemplates.ts +160 -0
  60. package/src/dev/devServer.ts +292 -0
  61. package/src/dev/wsServer.ts +144 -0
  62. package/src/distRoot.ts +20 -0
  63. package/src/ignore.ts +163 -0
  64. package/src/index.ts +491 -0
  65. package/src/lecodes-3d-editor.d.ts +41 -0
  66. package/src/lecodes-assets.d.ts +7 -0
  67. package/src/lecodes-design.d.ts +191 -0
  68. package/src/lecodes-renderer.d.ts +131 -0
  69. package/src/localFiles.ts +144 -0
  70. package/src/manifest.ts +46 -0
  71. package/src/matcTool.ts +137 -0
  72. package/src/peers.ts +40 -0
  73. package/src/project.ts +61 -0
  74. package/src/projectEnv.ts +94 -0
  75. package/src/qrcode-terminal.d.ts +9 -0
  76. package/src/releases.ts +125 -0
  77. package/src/serverDiff.ts +78 -0
  78. package/src/textDiff.ts +103 -0
  79. package/src/types.ts +0 -0
  80. package/src/util.ts +146 -0
  81. package/runtime/sdk/animate/animate.ts +0 -238
  82. package/runtime/sdk/animate/bezier.ts +0 -138
  83. package/runtime/sdk/animate/easings.ts +0 -126
  84. package/runtime/sdk/canvas/Canvas.ts +0 -305
  85. package/runtime/sdk/compile/aspectMacro.ts +0 -86
  86. package/runtime/sdk/compile/assetIconMacro.ts +0 -384
  87. package/runtime/sdk/compile/assetMacro.ts +0 -146
  88. package/runtime/sdk/compile/assetName.ts +0 -50
  89. package/runtime/sdk/compile/bundler.ts +0 -287
  90. package/runtime/sdk/compile/compileProject.ts +0 -129
  91. package/runtime/sdk/compile/detectEntry.ts +0 -128
  92. package/runtime/sdk/compile/fontMacro.ts +0 -459
  93. package/runtime/sdk/compile/fontRegistry.ts +0 -78
  94. package/runtime/sdk/compile/header.ts +0 -67
  95. package/runtime/sdk/compile/index.ts +0 -100
  96. package/runtime/sdk/compile/libraryImports.ts +0 -52
  97. package/runtime/sdk/compile/liteMaterial.ts +0 -247
  98. package/runtime/sdk/compile/sceneEditor.ts +0 -88
  99. package/runtime/sdk/compile/serverSplit.ts +0 -233
  100. package/runtime/sdk/compile/serverTypes.ts +0 -227
  101. package/runtime/sdk/compile/sfnt.ts +0 -98
  102. package/runtime/sdk/compile/shaderSchema.ts +0 -202
  103. package/runtime/sdk/compile/shaderTargets.ts +0 -81
  104. package/runtime/sdk/compile/sourcemap.ts +0 -25
  105. package/runtime/sdk/core/Aspect.ts +0 -568
  106. package/runtime/sdk/core/InspectorUI.ts +0 -212
  107. package/runtime/sdk/core/StateMachine.ts +0 -308
  108. package/runtime/sdk/core/color.ts +0 -66
  109. package/runtime/sdk/core/compWrite.ts +0 -42
  110. package/runtime/sdk/core/events.ts +0 -38
  111. package/runtime/sdk/core/fields.ts +0 -120
  112. package/runtime/sdk/core/registry.ts +0 -23
  113. package/runtime/sdk/core/signals.ts +0 -277
  114. package/runtime/sdk/core/time.ts +0 -84
  115. package/runtime/sdk/g2/Camera2D.ts +0 -40
  116. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  117. package/runtime/sdk/g2/Node2D.ts +0 -267
  118. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  119. package/runtime/sdk/g2/Physics2D.ts +0 -346
  120. package/runtime/sdk/g2/Scene2D.ts +0 -209
  121. package/runtime/sdk/g2/Shape2D.ts +0 -259
  122. package/runtime/sdk/g2/Sprite.ts +0 -89
  123. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  124. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  125. package/runtime/sdk/g2/Texture2D.ts +0 -47
  126. package/runtime/sdk/g2/Tilemap.ts +0 -41
  127. package/runtime/sdk/g2/Tileset.ts +0 -71
  128. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  129. package/runtime/sdk/g2/autotile.ts +0 -433
  130. package/runtime/sdk/g2/cells.ts +0 -91
  131. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  132. package/runtime/sdk/g2/groups2d.ts +0 -106
  133. package/runtime/sdk/g2/loop.ts +0 -50
  134. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  135. package/runtime/sdk/g2/touch.ts +0 -83
  136. package/runtime/sdk/gl/Camera.ts +0 -160
  137. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  138. package/runtime/sdk/gl/CharacterController.ts +0 -238
  139. package/runtime/sdk/gl/Gearbox.ts +0 -212
  140. package/runtime/sdk/gl/Geometry.ts +0 -279
  141. package/runtime/sdk/gl/IK.ts +0 -193
  142. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  143. package/runtime/sdk/gl/Light.ts +0 -107
  144. package/runtime/sdk/gl/Lightmap.ts +0 -256
  145. package/runtime/sdk/gl/Material.ts +0 -260
  146. package/runtime/sdk/gl/Mesh.ts +0 -83
  147. package/runtime/sdk/gl/Model.ts +0 -124
  148. package/runtime/sdk/gl/NavAgent.ts +0 -337
  149. package/runtime/sdk/gl/NavMesh.ts +0 -397
  150. package/runtime/sdk/gl/Node.ts +0 -350
  151. package/runtime/sdk/gl/Noise.ts +0 -30
  152. package/runtime/sdk/gl/Particles.ts +0 -676
  153. package/runtime/sdk/gl/Physics.ts +0 -222
  154. package/runtime/sdk/gl/Plane.ts +0 -53
  155. package/runtime/sdk/gl/Ragdoll.ts +0 -270
  156. package/runtime/sdk/gl/Ray.ts +0 -16
  157. package/runtime/sdk/gl/Scene.ts +0 -486
  158. package/runtime/sdk/gl/Shape.ts +0 -409
  159. package/runtime/sdk/gl/Terrain.ts +0 -1092
  160. package/runtime/sdk/gl/Texture.ts +0 -63
  161. package/runtime/sdk/gl/Trigger.ts +0 -45
  162. package/runtime/sdk/gl/Vehicle.ts +0 -473
  163. package/runtime/sdk/gl/Wheel.ts +0 -240
  164. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  165. package/runtime/sdk/gl/animation/Animator.ts +0 -120
  166. package/runtime/sdk/gl/animation/Layer.ts +0 -31
  167. package/runtime/sdk/gl/animation/Loop.ts +0 -30
  168. package/runtime/sdk/gl/animation/Playback.ts +0 -44
  169. package/runtime/sdk/gl/animation/core.ts +0 -336
  170. package/runtime/sdk/gl/controls.ts +0 -95
  171. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  172. package/runtime/sdk/gl/scenarios.ts +0 -291
  173. package/runtime/sdk/gl/state.ts +0 -6
  174. package/runtime/sdk/gl/terrainMesh.ts +0 -219
  175. package/runtime/sdk/gl/touch.ts +0 -68
  176. package/runtime/sdk/inject.ts +0 -197
  177. package/runtime/sdk/math/Mathf.ts +0 -118
  178. package/runtime/sdk/math/mat4.ts +0 -278
  179. package/runtime/sdk/math/quat.ts +0 -232
  180. package/runtime/sdk/math/vec.ts +0 -255
  181. package/runtime/sdk/net/codec.ts +0 -119
  182. package/runtime/sdk/net/core.ts +0 -384
  183. package/runtime/sdk/net/index.ts +0 -181
  184. package/runtime/sdk/net/replication.ts +0 -622
  185. package/runtime/sdk/plugins/camera.ts +0 -81
  186. package/runtime/sdk/plugins/geolocation.ts +0 -123
  187. package/runtime/sdk/plugins/oauth.ts +0 -61
  188. package/runtime/sdk/plugins/permission.ts +0 -7
  189. package/runtime/sdk/plugins/push.ts +0 -132
  190. package/runtime/sdk/plugins/qr.ts +0 -73
  191. package/runtime/sdk/plugins/service.ts +0 -47
  192. package/runtime/sdk/runtime/app.ts +0 -101
  193. package/runtime/sdk/runtime/appEvents.ts +0 -54
  194. package/runtime/sdk/runtime/channel.ts +0 -61
  195. package/runtime/sdk/runtime/clipboard.ts +0 -20
  196. package/runtime/sdk/runtime/datetime.ts +0 -329
  197. package/runtime/sdk/runtime/device.ts +0 -293
  198. package/runtime/sdk/runtime/fetch.ts +0 -77
  199. package/runtime/sdk/runtime/files.ts +0 -23
  200. package/runtime/sdk/runtime/input.ts +0 -175
  201. package/runtime/sdk/runtime/media.ts +0 -111
  202. package/runtime/sdk/runtime/misc.ts +0 -16
  203. package/runtime/sdk/runtime/net.ts +0 -36
  204. package/runtime/sdk/runtime/rpc.ts +0 -218
  205. package/runtime/sdk/runtime/service.ts +0 -83
  206. package/runtime/sdk/runtime/share.ts +0 -9
  207. package/runtime/sdk/runtime/storage.ts +0 -13
  208. package/runtime/sdk/runtime/touch.ts +0 -76
  209. package/runtime/sdk/scene/defineScene.ts +0 -1353
  210. package/runtime/sdk/scene/editorPlugins.ts +0 -110
  211. package/runtime/sdk/scene/gizmos.ts +0 -148
  212. package/runtime/sdk/scene/grammar.ts +0 -120
  213. package/runtime/sdk/scene/material.ts +0 -188
  214. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  215. package/runtime/sdk/server/auth/global.ts +0 -80
  216. package/runtime/sdk/server/auth/host.ts +0 -318
  217. package/runtime/sdk/server/auth/models.ts +0 -83
  218. package/runtime/sdk/server/auth/types.ts +0 -50
  219. package/runtime/sdk/server/channel.ts +0 -56
  220. package/runtime/sdk/server/context.ts +0 -36
  221. package/runtime/sdk/server/db/defineDb.ts +0 -237
  222. package/runtime/sdk/server/db/fields.ts +0 -132
  223. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  224. package/runtime/sdk/server/db/index.ts +0 -7
  225. package/runtime/sdk/server/db/marci/query.ts +0 -412
  226. package/runtime/sdk/server/db/types.ts +0 -202
  227. package/runtime/sdk/server/errors.ts +0 -12
  228. package/runtime/sdk/server/host.ts +0 -74
  229. package/runtime/sdk/server/inject.ts +0 -13
  230. package/runtime/sdk/server/runtime.ts +0 -133
  231. package/runtime/sdk/server/validate.ts +0 -87
  232. package/runtime/sdk/ui/NativeView.ts +0 -142
  233. package/runtime/sdk/ui/UI.ts +0 -39
  234. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  235. package/runtime/sdk/ui/UIButton.ts +0 -101
  236. package/runtime/sdk/ui/UIContainer.ts +0 -60
  237. package/runtime/sdk/ui/UIImage.ts +0 -83
  238. package/runtime/sdk/ui/UIInput.ts +0 -185
  239. package/runtime/sdk/ui/UIModal.ts +0 -139
  240. package/runtime/sdk/ui/UINode.ts +0 -830
  241. package/runtime/sdk/ui/UIPager.ts +0 -362
  242. package/runtime/sdk/ui/UIPopover.ts +0 -100
  243. package/runtime/sdk/ui/UIScreen.ts +0 -123
  244. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  245. package/runtime/sdk/ui/UISpacer.ts +0 -14
  246. package/runtime/sdk/ui/UITabs.ts +0 -236
  247. package/runtime/sdk/ui/UIText.ts +0 -51
  248. package/runtime/sdk/ui/UIVideo.ts +0 -88
  249. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  250. package/runtime/sdk/ui/UIWidget.ts +0 -127
  251. package/runtime/sdk/ui/fonts.ts +0 -13
  252. package/runtime/sdk/ui/presentable.ts +0 -117
  253. package/runtime/sdk/ui/router.ts +0 -132
  254. package/runtime/sdk/ui/theme.ts +0 -84
@@ -1,240 +0,0 @@
1
- // One wheel of a `Vehicle`, as an aspect on the wheel's own node. The node's position IS the wheel's
2
- // position (where its centre rests on flat ground), and everything else about it lives here too:
3
- //
4
- // const car = new Node('car')
5
- // .add(body, wheelFL.aspect(Wheel), wheelFR.aspect(Wheel),
6
- // wheelRL.aspect(Wheel), wheelRR.aspect(Wheel))
7
- // .aspect(Shape, { box: [0.9, 0.4, 2.1] })
8
- // .aspect(Vehicle, { mass: 1200 })
9
- //
10
- // That is the whole minimum: `Vehicle` collects every `Wheel` under itself, groups them into axles by
11
- // Z (front first), and the defaults fall out — the front axle steers, the rear one drives and carries
12
- // the handbrake. Override any of it per wheel.
13
- //
14
- // This aspect is also what POSES the wheel: it runs in the LATE phase (after the physics step, before
15
- // the frame draws) and writes the node's transform from the suspension length, steer angle and spin
16
- // the engine reports. Nothing else writes that transform — the engine deliberately does not, so a
17
- // wheel model's own conventions (which way its axle points, how big it looks) are settings here
18
- // rather than constants in C++, and a part that should steer but NOT spin is simply a sibling node.
19
- //
20
- // Chassis space is forward −Z / up +Y, the SDK's own convention (`node.forward`).
21
-
22
- import { Aspect } from "../core/Aspect"
23
- import type { FieldMeta } from "../core/fields"
24
- import { Mat4 } from "../math/mat4"
25
- import { Quat } from "../math/quat"
26
- import { Vec3, cx, cy, cz, type Vec3Like } from "../math/vec"
27
- import { Gizmos } from "../scene/gizmos"
28
- import { Node } from "./Node"
29
- import { Shape } from "./Shape"
30
- import type { Vehicle } from "./Vehicle"
31
-
32
- /** Floats `vehicleGetState` writes before the per-wheel block: speed, rpm, contacts, vx, vy, vz. */
33
- export const STATE_HEADER = 6
34
- /** Floats per wheel: contact, slip, slipAngle, suspensionLength, steerDeg, spin. */
35
- export const WHEEL_STATE = 6
36
-
37
- /** How a tire behaves past its grip peak. `road` is Jolt's own curve — grip falls off after the peak,
38
- * so the car can be made to let go. `arcade` holds the peak all the way out: much harder to spin. */
39
- export type TireCurve = "road" | "arcade"
40
-
41
- export interface WheelSuspension {
42
- /** Total travel in metres, full droop to fully compressed (default 0.4). */
43
- travel?: number
44
- /** Spring frequency in Hz — bigger is stiffer (default 1.4; a race car ~2.5, a truck ~1). It also
45
- * sets the ride-height sag, and the sag is how far the wheel can still extend DOWN. */
46
- stiffness?: number
47
- /** Damping ratio 0..1 (default 0.5). Low values pogo. */
48
- damping?: number
49
- }
50
-
51
- export interface WheelTire {
52
- /** Grip multiplier on the friction curves (default 1). The grip a wheel actually gets is combined
53
- * with the ground it stands on as `sqrt(tire × groundFriction)`, and a body's friction defaults to
54
- * 0.6 — so a road a car should really corner on wants `Physics { friction: 1 }`. */
55
- grip?: number
56
- /** Which friction curve (default `road`). */
57
- curve?: TireCurve
58
- }
59
-
60
- export interface WheelVisual {
61
- /** The MODEL's axle direction in its own space (default `[1, 0, 0]`). Set it instead of parenting
62
- * the mesh under a corrective node when the art points another way. */
63
- axis?: Vec3Like
64
- /** Drawn radius, if the art is not exactly the physical `radius` — sinks a wheel into its arch or
65
- * lets a fat tire overhang, with no effect on the simulation. Default: the physical radius. */
66
- radius?: number
67
- /** Let the model spin (default true). `false` keeps the steer angle and suspension travel but not
68
- * the rotation — a brake caliper. */
69
- spin?: boolean
70
- }
71
-
72
- const DEG2RAD = Math.PI / 180
73
- const UP = new Vec3(0, 1, 0)
74
-
75
- export class Wheel extends Aspect<"wheel", Node> {
76
- static readonly aspect = "wheel"
77
-
78
- /** Wheel radius in metres. Default: measured from the node's rendered bounds, else 0.35. Too small
79
- * and the car scrapes, too large and it floats. */
80
- radius?: number
81
- /** Wheel width in metres. Default: measured, else 0.25. */
82
- width?: number
83
- /** Does this wheel steer? Default: the wheels on the front axle. */
84
- steer?: boolean
85
- /** Does the engine drive it? Default: from the vehicle's `drive` layout. */
86
- drive?: boolean
87
- /** Foot brake on this wheel. */
88
- brake = true
89
- /** Handbrake on this wheel. Default: every non-steering wheel (locking the front wheels of a moving
90
- * car is not a handbrake turn, it is a crash). */
91
- handbrake?: boolean
92
- /** Axle group, front to back. Default: derived from Z. Set it for a car whose wheels are not in neat
93
- * left/right pairs — an axle's wheels are paired for the differential and the anti-roll bar. */
94
- axle?: number
95
- /** Foot-brake torque in N·m (default 1500); the handbrake pulls 3× on the wheels that have it. */
96
- brakeTorque = 1500
97
-
98
- suspension: WheelSuspension = { travel: 0.4, stiffness: 1.4, damping: 0.5 }
99
- tire: WheelTire = { grip: 1, curve: "road" }
100
- visual: WheelVisual = {}
101
-
102
- /** Scene-editor inspector. `steer` / `drive` / `handbrake` are deliberately absent: they are
103
- * tri-state (undefined = derive it from the axle), which a switch cannot express. */
104
- static fields: FieldMeta<Wheel> = {
105
- radius: { min: 0.05, step: 0.01 },
106
- width: { min: 0.02, step: 0.01 },
107
- axle: { min: 0, step: 1 },
108
- brake: { label: "Foot brake" },
109
- brakeTorque: { label: "Brake torque", min: 0, step: 50 },
110
- suspension: { hidden: true },
111
- tire: { hidden: true },
112
- visual: { hidden: true },
113
- }
114
- static editor = { rebuild: true }
115
-
116
- /** @internal Set by `Vehicle` at attach — the car this wheel belongs to, and its index in the blob. */
117
- _vehicle: Vehicle | null = null
118
- /** @internal */
119
- _index = -1
120
- /** @internal Suspension attachment point in chassis space; the wheel hangs below it. */
121
- _attach = new Vec3()
122
- /** @internal Resting centre in chassis space (what the author placed). */
123
- _rest = new Vec3()
124
-
125
- private _angle = 0 // integrated spin, radians — the engine reports rate, not angle
126
- private _scale = new Vec3(1, 1, 1) // the node's own scale, preserved through the pose write
127
- private _local = true // is the node a direct child of the chassis?
128
-
129
- onAttach(): void {
130
- this._scale = this.node.scale
131
- }
132
-
133
- onDetach(): void {
134
- this._vehicle = null
135
- this._index = -1
136
- }
137
-
138
- /** @internal Called by Vehicle once the car exists, with the geometry it resolved. */
139
- _bind(vehicle: Vehicle, index: number, rest: Vec3, attach: Vec3): void {
140
- this._vehicle = vehicle
141
- this._index = index
142
- this._rest = rest
143
- this._attach = attach
144
- this._scale = this.node.scale
145
- this._local = this.node.parent === vehicle.node
146
- }
147
-
148
- // ---- live state ------------------------------------------------------------------------------
149
- // Every getter reads the car's ONE shared state buffer, which is refreshed once per frame.
150
-
151
- private _state(k: number): number {
152
- const v = this._vehicle
153
- if (!v || this._index < 0) return 0
154
- return v._wheelState(this._index, k)
155
- }
156
-
157
- /** Touching the ground this step. */
158
- get contact(): boolean { return this._state(0) !== 0 }
159
- /** Longitudinal slip RATIO: 0 = rolling in sync with the ground, ~1 = spinning up or locked.
160
- * Unsigned — smoke, tire marks and wheelspin sound key off this. */
161
- get slip(): number { return this._state(1) }
162
- /** Slip ANGLE in degrees: how far the tire's travel direction is off from where it points.
163
- * Unsigned, 0..90 — past the curve's peak (~3°) the tire is sliding sideways. Drift particles. */
164
- get slipAngle(): number { return this._state(2) }
165
- /** Steer angle in degrees, positive to the right. */
166
- get steerAngle(): number { return this._state(4) }
167
- /** Wheel spin in rad/s (× radius ≈ the speed the tire is laying down). */
168
- get spin(): number { return this._state(5) }
169
- /** Suspension compression, 0 = fully extended (drooping) … 1 = fully compressed. */
170
- get compression(): number {
171
- const travel = this.suspension.travel ?? 0.4
172
- if (travel <= 0) return 0
173
- const len = this._state(3)
174
- const min = travel * 0.4
175
- return Math.max(0, Math.min(1, (travel - len) / (travel - min)))
176
- }
177
-
178
- // ---- the visual ------------------------------------------------------------------------------
179
-
180
- /** LATE phase: after the step and the transform sync, right before the frame draws. */
181
- update(dt: number): void {
182
- const v = this._vehicle
183
- if (!v || this._index < 0 || !v.id) return
184
-
185
- if (this.visual.spin !== false) this._angle += this._state(5) * dt
186
-
187
- // Down the suspension from the attachment point by however far it currently hangs.
188
- const length = this._state(3)
189
- const centre = new Vec3(this._attach.x, this._attach.y - length, this._attach.z)
190
-
191
- // Steer about the chassis' up axis, then spin about the MODEL's own axle in the steered frame.
192
- // Both reports are negated, for the same reason: the engine describes them in the DRIVER's terms
193
- // and the scene is right-handed with forward on −Z. Steer is reported right-positive while a yaw
194
- // about +Y turns left. Spin is reported positive-means-forward, but a positive turn about the
195
- // right-hand axle carries the top of the wheel to +Z — which rolls the car backwards.
196
- const steer = Quat.fromAxisAngle(UP, -this._state(4) * DEG2RAD)
197
- const axis = this.visual.axis ?? [1, 0, 0]
198
- const rotation = this.visual.spin === false ? steer : steer.mul(Quat.fromAxisAngle(axis, -this._angle))
199
-
200
- // A model that draws bigger or smaller than it collides scales on top of the node's own scale.
201
- const vr = this.visual.radius
202
- const k = vr !== undefined && this.radius ? vr / this.radius : 1
203
- const scale = k === 1 ? this._scale : new Vec3(this._scale.x * k, this._scale.y * k, this._scale.z * k)
204
-
205
- const m = Mat4.compose(centre, rotation, scale)
206
- // The common case is a direct child of the chassis, where chassis space IS the local space.
207
- if (this._local) this.node.matrix = m
208
- else this.node.worldMatrix = new Mat4(v.node.worldMatrix).mul(m)
209
- }
210
-
211
- /** Edit mode: this wheel's own circle at its resting height, its travel range, and a mark when the
212
- * engine drives it — enough to place a wheel without running the game. */
213
- rebuild(): void {
214
- const travel = this.suspension.travel ?? 0.4
215
- const r = this.radius ?? this._measure().radius
216
- const world = new Mat4(this.node.worldMatrix)
217
- const P = (x: number, y: number, z: number): Vec3 => world.transformPoint([ x, y, z ])
218
- const ring: Vec3[] = []
219
- for (let i = 0; i < 24; i++) {
220
- const t = (i / 24) * Math.PI * 2
221
- ring.push(P(0, Math.sin(t) * r, Math.cos(t) * r))
222
- }
223
- Gizmos.polyline(ring, { color: this.steer === false ? "#9aa3b2" : "#5b8ef0", closed: true })
224
- Gizmos.line(P(0, travel * 0.6, 0), P(0, -travel * 0.4, 0), { color: "#59606d" })
225
- if (this.drive) Gizmos.cross(P(0, 0, 0), r * 0.35, { color: "#e0b25a" })
226
- }
227
-
228
- /** @internal Radius/width from the node's rendered bounds, so a placed model needs no numbers. */
229
- _measure(): { radius: number, width: number } {
230
- const b = Shape.boundsOf(this.node)
231
- if (!b) return { radius: 0.35, width: 0.25 }
232
- const size = new Vec3(cx(b.max) - cx(b.min), cy(b.max) - cy(b.min), cz(b.max) - cz(b.min))
233
- // A wheel is a disc: its diameter is the larger of the two axes across the axle, and the width is
234
- // the axle direction itself.
235
- const axis = this.visual.axis ?? [1, 0, 0]
236
- const along = Math.abs(cx(axis)) >= Math.abs(cz(axis)) ? size.x : size.z
237
- const across = Math.max(Math.abs(cx(axis)) >= Math.abs(cz(axis)) ? size.z : size.x, size.y)
238
- return { radius: across > 0 ? across * 0.5 : 0.35, width: along > 0 ? along : 0.25 }
239
- }
240
- }
@@ -1,204 +0,0 @@
1
- // AnimationClip — the unit of the animation system: immutable data (name, duration, tracks that
2
- // target bone NAMES, not entities). One representation for a GLB's embedded clips (model.anim.clips),
3
- // clips loaded from other files (Mixamo: one GLB per animation) and procedural clips built from
4
- // curves. An Animator binds clips to a skeleton by name. Clip EVENTS (`addEvent`) live here too —
5
- // a fact about the clip's timeline, shared wherever it plays. See docs/animator-plan.md.
6
- //
7
- // const [hero, idle, run] = await Promise.all([
8
- // Model.load(asset('./hero.glb')),
9
- // AnimationClip.load(asset('./idle.glb')), // the file's only/first clip
10
- // AnimationClip.load(asset('./run.glb')),
11
- // ])
12
- // const slash = await AnimationClip.load(asset('./attacks.glb'), 'Slash') // one out of many
13
- // const bob = AnimationClip.from({ tracks: { Hips: { position: [[0, [0,0,0]], [1, [0,0.05,0]]] } } })
14
-
15
- import { fetch, type FetchResponse } from "../../runtime/fetch"
16
- import type { Vec3Like } from "../../math/vec"
17
- import type { QuatLike } from "../../math/quat"
18
-
19
- /** A keyframe: `[time (s), value]`. */
20
- export type ClipKey<V> = [number, V]
21
- export type ClipInterpolation = "linear" | "step"
22
-
23
- /** Curves for one target node (bone name), any subset of the three channels. */
24
- export type ClipTrackDef = {
25
- position?: ClipKey<Vec3Like | [number, number, number]>[]
26
- rotation?: ClipKey<QuatLike | [number, number, number, number]>[]
27
- scale?: ClipKey<Vec3Like | [number, number, number]>[]
28
- interpolation?: ClipInterpolation
29
- }
30
-
31
- export type ClipDef = {
32
- /** Clip length in seconds — defaults to the last key time across all tracks. */
33
- duration?: number
34
- /** Target node name → its curves. Names bind to bones (or any child node) at Animator attach. */
35
- tracks: Record<string, ClipTrackDef>
36
- }
37
-
38
- export type ClipInfo = { name: string, duration: number, trackCount: number }
39
-
40
- const PATH = { position: 0, rotation: 1, scale: 2 } as const
41
- const INTERP = { linear: 0, step: 1 } as const
42
-
43
- const setCache = new Map<number, AnimationClip[]>()
44
-
45
- const readInfo = (setId: number): ClipInfo[] =>
46
- ((_creator.getClipSetInfo(setId) ?? []) as ClipInfo[]).map((c) => ({ name: c.name, duration: c.duration, trackCount: c.trackCount ?? 0 }))
47
-
48
- const readValue = (v: unknown, comps: number, out: number[]): void => {
49
- if (Array.isArray(v)) { for (let i = 0; i < comps; i++) out.push(Number(v[i] ?? (i === 3 ? 1 : 0))); return }
50
- const o = v as Record<string, number>
51
- out.push(o.x ?? 0, o.y ?? 0, o.z ?? 0)
52
- if (comps === 4) out.push(o.w ?? 1)
53
- }
54
-
55
- export class AnimationClip {
56
- /** Source name (the clip's name inside its file; `"clip"` for procedural clips). Informational —
57
- * an Animator addresses clips by the key YOU give it. */
58
- readonly name: string
59
- /** Length in seconds. */
60
- readonly duration: number
61
- /** Number of animated (node, channel) tracks. */
62
- readonly trackCount: number
63
- /** @internal native clip set + index inside it. */
64
- readonly _set: number
65
- /** @internal */
66
- readonly _index: number
67
- /** @internal events: seconds + name, sorted (the engine gets them normalized via setClipEvents). */
68
- _events: { t: number, name: string }[] = []
69
-
70
- private constructor(set: number, index: number, info: ClipInfo) {
71
- this._set = set
72
- this._index = index
73
- this.name = info.name
74
- this.duration = info.duration
75
- this.trackCount = info.trackCount
76
- }
77
-
78
- /** @internal — a placeholder for a Playback that never started (unknown clip). */
79
- static _none(): AnimationClip { return new AnimationClip(0, 0, { name: "", duration: 0, trackCount: 0 }) }
80
-
81
- /** @internal — wrap the clips of an existing native set (a GLB's embedded clips). Cached per set
82
- * so every model / clone sharing the set gets the SAME objects (events added on one apply to all —
83
- * as they do in the engine). */
84
- static _ofSet(setId: number): AnimationClip[] {
85
- if (!setId) return []
86
- let clips = setCache.get(setId)
87
- if (!clips) { clips = readInfo(setId).map((info, i) => new AnimationClip(setId, i, info)); setCache.set(setId, clips) }
88
- return clips
89
- }
90
-
91
- /** Mark a moment of the clip (SECONDS from its start) with an event name: `kick.addEvent(0.4, 'hit')`
92
- * → `anim.on('hit', (clip, layer) => …)` fires when the playhead crosses it, loops included.
93
- * Events are part of the clip: every model playing it gets them; `slice()` keeps the ones inside
94
- * the range, re-timed. Chainable. */
95
- addEvent(time: number, name: string): this {
96
- this._events.push({ t: Math.min(Math.max(0, time), this.duration), name })
97
- this._events.sort((a, b) => a.t - b.t)
98
- this._pushEvents()
99
- return this
100
- }
101
- private _pushEvents(): void {
102
- const d = this.duration
103
- _creator.setClipEvents(this._set, this._index, new Float32Array(this._events.map((e) => (d > 0 ? e.t / d : 0))))
104
- }
105
-
106
- /** Load ONE clip from a GLB: the file's only/first clip, or the one named / at the given index. */
107
- static load(source: string | FetchResponse, clip?: string | number): Promise<AnimationClip> {
108
- return AnimationClip._loadSet(source).then(([setId, infos]) => {
109
- let index = 0
110
- if (typeof clip === "number") index = clip
111
- else if (typeof clip === "string") index = infos.findIndex((c) => c.name === clip)
112
- if (index < 0 || index >= infos.length) {
113
- _creator.destroyClipSet(setId)
114
- throw new Error(`AnimationClip.load: no clip ${JSON.stringify(clip)} in ${AnimationClip._label(source)} — clips: ${infos.map((c) => c.name).join(", ") || "(none)"}`)
115
- }
116
- return new AnimationClip(setId, index, infos[index])
117
- })
118
- }
119
-
120
- /** Load EVERY clip of a GLB (library files), keyed by clip name. */
121
- static loadAll(source: string | FetchResponse): Promise<Record<string, AnimationClip>> {
122
- return AnimationClip._loadSet(source).then(([setId, infos]) => {
123
- const out: Record<string, AnimationClip> = {}
124
- infos.forEach((info, i) => { out[info.name] = new AnimationClip(setId, i, info) })
125
- return out
126
- })
127
- }
128
-
129
- /** Build a clip from curves in code — no DCC needed. Keys are `[time, value]`; a track binds to the
130
- * node of that name when the clip is used by an Animator (bones, or any child node). */
131
- static from(def: ClipDef): AnimationClip {
132
- const names: string[] = []
133
- const data: number[] = [0, 0]
134
- let trackCount = 0
135
- let duration = 0
136
- for (const [target, track] of Object.entries(def.tracks)) {
137
- const interp = INTERP[track.interpolation ?? "linear"] ?? 0
138
- const emit = (path: 0 | 1 | 2, comps: 3 | 4, keys: ClipKey<unknown>[] | undefined) => {
139
- if (!keys || keys.length === 0) return
140
- const sorted = keys.slice().sort((a, b) => a[0] - b[0])
141
- names.push(target)
142
- data.push(path, interp, comps, sorted.length)
143
- for (const k of sorted) data.push(k[0])
144
- for (const k of sorted) readValue(k[1], comps, data)
145
- duration = Math.max(duration, sorted[sorted.length - 1][0])
146
- trackCount++
147
- }
148
- emit(PATH.position, 3, track.position)
149
- emit(PATH.rotation, 4, track.rotation)
150
- emit(PATH.scale, 3, track.scale)
151
- }
152
- data[0] = trackCount
153
- if (def.duration !== undefined) duration = def.duration
154
- data[1] = duration
155
- const setId = _creator.createClipFromTracks(names.join("\n"), new Float32Array(data))
156
- if (setId === 0) throw new Error("AnimationClip.from: invalid track data")
157
- const info = readInfo(setId)[0] ?? { name: "clip", duration, trackCount }
158
- return new AnimationClip(setId, 0, info)
159
- }
160
-
161
- /** A sub-range of this clip as a NEW clip — `[start, end]` seconds of the source, re-timed to 0
162
- * (`end` defaults to the clip's end; array-slice semantics). Cut a too-long take down to the
163
- * action, or carve several sub-clips out of one packed timeline:
164
- *
165
- * clips: { Kick: kick.slice(0.2, 1.1) }
166
- *
167
- * Keys outside the range are dropped and exact boundary values are interpolated in, so the
168
- * sliced clip starts and ends precisely on the source's pose at the cut points. Everything
169
- * downstream (blend spaces, events' 0–1 times, root motion, phase sync) sees a normal clip of
170
- * the new duration. */
171
- slice(start: number, end?: number): AnimationClip {
172
- const e = Math.min(end ?? this.duration, this.duration)
173
- const st = Math.max(0, start)
174
- const setId = _creator.sliceClip(this._set, this._index, st, e)
175
- if (setId === 0) throw new Error(`AnimationClip.slice: bad range ${start}${end !== undefined ? `–${end}` : ""} of '${this.name}' (${this.duration.toFixed(2)}s)`)
176
- const info = readInfo(setId)[0] ?? { name: this.name, duration: e - st, trackCount: this.trackCount }
177
- const out = new AnimationClip(setId, 0, info)
178
- // the engine re-timed the event TIMES; carry the names the same way
179
- out._events = this._events.filter((ev) => ev.t >= st - 1e-6 && ev.t <= e + 1e-6).map((ev) => ({ t: Math.min(Math.max(0, ev.t - st), e - st), name: ev.name }))
180
- return out
181
- }
182
-
183
- private static _label(source: string | FetchResponse): string {
184
- return typeof source === "string" ? source : "response"
185
- }
186
-
187
- private static _loadSet(source: string | FetchResponse): Promise<[number, ClipInfo[]]> {
188
- if (typeof _creator.loadClips !== "function") {
189
- return Promise.reject(new Error("AnimationClip.load: this host has no animation-clip support yet"))
190
- }
191
- if (typeof source === "string") {
192
- return fetch(source, { useOnce: true }).then((resp) => {
193
- if (resp.status >= 400) throw new Error(`AnimationClip.load: failed to load ${source}. HTTP ${resp.status}`)
194
- return AnimationClip._loadSet(resp)
195
- })
196
- }
197
- return new Promise<[number, ClipInfo[]]>((resolve, reject) => {
198
- _creator.loadClips((source as unknown as { _id: number })._id, (setId: number) => {
199
- if (setId === 0) { reject(new Error("AnimationClip.load: the file has no animations (or is not a GLB)")); return }
200
- resolve([setId, readInfo(setId)])
201
- }, reject)
202
- })
203
- }
204
- }
@@ -1,120 +0,0 @@
1
- // Animator — THE animation system, reached as `node.anim`. Every Model carries one pre-attached (its
2
- // clip list = the GLB's embedded clips; nothing native exists until the first play()). The whole
3
- // per-frame loop — fades, blend spaces, one-shot fade-back, root motion — runs in the engine; the
4
- // SDK only issues calls. See docs/animator-plan.md.
5
- //
6
- // const hero = await Model.load(asset('./hero.glb'))
7
- // hero.anim.playLoop('Idle') // the layer's LOOP: what it rests on
8
- // await hero.anim.play('Jump', { fadeIn: 0.1, fadeOut: 0.2 }) // a one-shot over it, back to the loop after
9
- //
10
- // hero.anim.addClip('slash', slash) // clips from other files / procedural / sliced
11
- // const loco = hero.anim.playLoop({ Idle: 0, Walking: 2, 'Fast Run': 6 }) // a blend as the loop
12
- // loco.value = hero.controller.velocity.length // where we are on the blend axis
13
- // const kick = hero.anim.play('slash', { fadeIn: 0.1, fadeOut: 0.3 }) // resolves at its hand-over
14
- // kick.clip.addEvent(0.4, 'hit'); hero.anim.on('hit', () => …)
15
- // if (await kick) hero.anim.playLoop('Crouch', { fade: 0.3 }) // resolved at the hand-over: what you start now is what it fades into
16
- // const upper = hero.anim.addLayer({ mask: 'Spine1' })
17
- // upper.playLoop('Aim', { fade: 0.3 }) // arms aim, legs keep walking
18
- //
19
- // Any Node hierarchy works, not just Models: tracks bind to child names (a robot arm of meshes).
20
-
21
- import { Aspect } from "../../core/Aspect"
22
- import type { Node } from "../Node"
23
- import type { AnimationClip } from "./AnimationClip"
24
- import { Core, type ActiveClip, type ClipEventHandler, type LayerOptions, type LoopDef, type LoopOptions, type PlayOptions, type StopOptions } from "./core"
25
- import type { Loop } from "./Loop"
26
- import type { Layer } from "./Layer"
27
- import type { Playback } from "./Playback"
28
-
29
- /** Level of detail for a GLB instance (docs/lod-plan.md): `'auto'` = the engine's pick by screen size and
30
- * visibility, or a fixed level 0 (full) … 3 (coarsest mesh, animation every 4th frame without fingers). */
31
- export type LodMode = "auto" | 0 | 1 | 2 | 3
32
-
33
- /** @internal Push a node's LOD override to the host: the Model's mesh level and the Animator's rate
34
- * (an Animator set to 'full' wins over a model level — the pose stays exact, the mesh may still coarsen). */
35
- export const _pushLod = (node: Node): void => {
36
- if (!_creator.setLod) return
37
- const mesh = (node as { _lodMesh?: number })._lodMesh ?? -1
38
- const anim = node.get(Animator)
39
- _creator.setLod(node.id, mesh, anim?._lod === "full" ? 0 : mesh)
40
- }
41
-
42
- export class Animator extends Aspect<"anim", Node> {
43
- static readonly aspect = "anim"
44
- private _c!: Core
45
- private _rootMotion = false
46
- private _rootRotation = false
47
- /** @internal */
48
- _lod: "auto" | "full" = "auto"
49
-
50
- /** Animation level of detail. `'auto'` (default): a character small on screen or out of view is
51
- * evaluated every 2nd / 4th frame and stops sampling its finger, toe and twist joints; `'full'`:
52
- * every frame, every joint, whatever the distance — a hero seen through a scope, a cutscene actor.
53
- * Independent of `Model.lod` (the mesh level). No-op on hosts without the LOD pass. */
54
- get lod(): "auto" | "full" { return this._lod }
55
- set lod(v: "auto" | "full") { this._lod = v; _pushLod(this.node) }
56
-
57
- onAttach(): void { this._c = new Core(this.node) }
58
- onDetach(): void { this._c.destroy() }
59
-
60
- // ---- clips ----
61
- /** The clip list, in order: the GLB's embedded clips (file order), then the clips you added. An
62
- * added clip with an embedded clip's name takes its place. */
63
- get clips(): readonly AnimationClip[] { return this._c.clips() }
64
- /** One clip by name, or by index into `clips` — e.g. to `slice()` it. `undefined` if there is none. */
65
- clip(ref: string | number): AnimationClip | undefined { return this._c.resolveClip(ref)?.[1] }
66
- /** Add a clip under a name (default: its own `name`) — from another file, procedural, or sliced.
67
- * Overrides an embedded clip of the same name. Chainable. */
68
- addClip(name: string | AnimationClip, clip?: AnimationClip): this {
69
- const c = typeof name === "string" ? clip : name
70
- const n = typeof name === "string" ? name : name.name
71
- if (!c) { console.warn(`Animator.addClip('${n}'): no clip`); return this }
72
- this._c.addClip(n, c)
73
- return this
74
- }
75
-
76
- // ---- playing (the base layer) ----
77
- /** Play a one-shot on the base layer — by name, index, or the first clip with no argument.
78
- * Crossfades per the options (default: cut). Returns a Playback: `await` it — it resolves at the
79
- * clip's hand-over (`true`; `false` if cut short), and what you start right after is what the clip
80
- * fades into (another one-shot, `playLoop(next)`; nothing = back to the loop). */
81
- play(clip?: string | number, options: PlayOptions = {}): Playback { return this._c.play(this._c.layers[0], clip, options) }
82
- /** Set the base layer's LOOP — what it shows when no one-shot plays: a clip (`playLoop('Idle')`) or
83
- * a blend space (`playLoop({ Idle: 0, Run: 6 })`, drive the returned object's `value`). Crossfades
84
- * over `fade` from whatever plays — a one-shot included (it is cut short). `stop()` removes it. */
85
- playLoop(def: LoopDef, options: LoopOptions = {}): Loop | undefined { return this._c.loop(this._c.layers[0], def, options) }
86
- /** Fade everything out, on every layer (loops included) → rest pose. */
87
- stop(options: StopOptions = {}): this { this._c.stopAll(options.fade ?? 0); return this }
88
- /** The base layer's current loop (the object the last `playLoop()` returned), if any. */
89
- get loop(): Loop | undefined { return this._c.layers[0].loop?.view }
90
- /** A one-shot on the base layer hasn't handed over yet. */
91
- get busy(): boolean { return this._c.busy(this._c.layers[0]) }
92
- /** Every clip shown this frame, all layers, with its weight — the crossfades' state (a debug overlay's list). */
93
- get active(): ActiveClip[] { return this._c.layers.flatMap((L) => this._c.active(L)) }
94
-
95
- // ---- layers ----
96
- /** A new layer on top (masked override / additive). The returned object is its handle. */
97
- addLayer(options: LayerOptions = {}): Layer { return this._c.addLayer(options) }
98
-
99
- // ---- clip events (`clip.addEvent(0.4, 'hit')`) ----
100
- on(event: string, cb: ClipEventHandler): this { this._c.on(event, cb); return this }
101
- off(event: string, cb: ClipEventHandler): this { this._c.off(event, cb); return this }
102
-
103
- // ---- props ----
104
- /** Global playback rate: 0.3 = slow-mo, 0 = pause. */
105
- get speed(): number { return this._c.speed }
106
- set speed(v: number) { this._c.setSpeed(v) }
107
- /** Root motion: the root bone's horizontal travel comes OFF the pose and moves the node instead —
108
- * or its CharacterController (on this node or an ancestor) as a velocity, so it collides. For
109
- * clips whose hips actually travel (Mixamo without "In Place", lunges, rolls). */
110
- get rootMotion(): boolean { return this._rootMotion }
111
- set rootMotion(on: boolean) { this._rootMotion = on; this._c.setRootMotion(on, this._rootRotation) }
112
- /** With `rootMotion`, the root bone's TURN (its yaw about the node's up, since the clip's first
113
- * frame) is root motion too: it comes off the pose and turns the node the travel lands on — the
114
- * character (or the model) faces where a turn-start / turn-in-place / arc clip took it, and the
115
- * crossfade into the next clip keeps that heading instead of swinging back. Off by default: a
116
- * walk cycle's hip sway is a turn too, and most rigs want it in the pose; turn it on for a rig
117
- * whose root bone carries the heading (`lecodes assets retarget --root-rotation yaw`). */
118
- get rootRotation(): boolean { return this._rootRotation }
119
- set rootRotation(on: boolean) { this._rootRotation = on; if (this._rootMotion) this._c.setRootMotion(true, on) }
120
- }
@@ -1,31 +0,0 @@
1
- // Layer — one layer of an Animator (`anim.addLayer({ mask: 'Spine1' })` for the upper body,
2
- // `{ additive: true }` for deltas stacked on top). The Animator's own play/stop/playLoop ARE its base
3
- // layer; extra layers composite over it in creation order. The object is the layer's only handle.
4
-
5
- import type { ActiveClip, Core, LayerRec, LoopDef, LoopOptions, PlayOptions, StopOptions } from "./core"
6
- import type { Loop } from "./Loop"
7
- import type { Playback } from "./Playback"
8
-
9
- export class Layer {
10
- private readonly _r: LayerRec
11
- private readonly _c: Core
12
- /** @internal — created by the Animator. */
13
- constructor(rec: LayerRec, core: Core) { this._r = rec; this._c = core }
14
-
15
- /** Play a one-shot on this layer (crossfading per the options); resolves at its hand-over. */
16
- play(clip?: string | number, options: PlayOptions = {}): Playback { return this._c.play(this._r, clip, options) }
17
- /** Set / transition this layer's loop (a clip, or a blend space) — crossfades over `fade` from whatever
18
- * the layer shows, a one-shot included (it is cut short). */
19
- playLoop(def: LoopDef, options: LoopOptions = {}): Loop | undefined { return this._c.loop(this._r, def, options) }
20
- /** Fade everything on this layer out — one-shots and its loop. */
21
- stop(options: StopOptions = {}): this { this._c.stopLayer(this._r, options.fade ?? 0); return this }
22
- /** This layer's current loop (the object the last `playLoop()` returned), if any. */
23
- get loop(): Loop | undefined { return this._r.loop?.view }
24
- /** Layer contribution 0–1. */
25
- get weight(): number { return this._r.weight }
26
- set weight(v: number) { this._r.weight = v; this._c.pushLayer(this._r) }
27
- /** A one-shot on this layer hasn't handed over yet. */
28
- get busy(): boolean { return this._c.busy(this._r) }
29
- /** Every clip this layer shows this frame with its weight — the crossfades' state, for a debug overlay. */
30
- get active(): ActiveClip[] { return this._c.active(this._r) }
31
- }
@@ -1,30 +0,0 @@
1
- // Loop — a layer's LOOP: what it shows when no one-shot plays (`anim.playLoop('Idle')` — one looping clip,
2
- // or `anim.playLoop({ Idle: 0, Walking: 2, Run: 6 })` — a blend whose members loop in phase and `value`
3
- // picks the mix: 1D linear between neighbours, `[x, y]` positions = a 2D gradient band). One-shots fade
4
- // it out and return to it; `playLoop(next)` crossfades to another; `stop()` removes it. The object
5
- // returned by `playLoop()` (also `anim.loop`) is the handle; a replaced loop's object goes dead.
6
-
7
- import type { BlendPosition, Core, LoopRec } from "./core"
8
-
9
- export class Loop {
10
- private readonly _r: LoopRec
11
- private readonly _c: Core
12
- /** @internal — created by playLoop(). */
13
- constructor(rec: LoopRec, core: Core) { this._r = rec; this._c = core }
14
-
15
- /** Position on the axis (number) or plane (`[x, y]`) — units are whatever the positions use. Live
16
- * even while a one-shot plays (the loop returns at the new mix). */
17
- get value(): BlendPosition { return this._r.value }
18
- set value(v: BlendPosition) { this._c.setLoopValue(this._r, v) }
19
- /** Where the loop is in its cycle, 0–1 — one clock for every member (a blend cycles over its
20
- * weighted period). Set it to jump the cycle: `playLoop(def, { phase })` on the way in, or here
21
- * to re-sync with something else (a clip whose pose matches the cycle at that phase). */
22
- get phase(): number { return this._c.loopPhase(this._r) }
23
- set phase(v: number) { this._c.setLoopPhase(this._r, v) }
24
- /** Each member's current contribution, 0–1 (fades included). */
25
- get weights(): Record<string, number> {
26
- const out: Record<string, number> = {}
27
- for (const m of this._r.members) out[m.name] = this._c.slotWeight(m.slot)
28
- return out
29
- }
30
- }