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,232 +0,0 @@
1
- // Fluent quaternion (x, y, z, w) — the rotation type for the 3D node API. Same contract as Vec: mutable
2
- // fields, PURE methods (each returns a new Quat). Accepts a `Quat` or a raw `[x, y, z, w]` everywhere.
3
- //
4
- // Convention: standard right-handed, column-major (matches gl-matrix and three.js). Euler angles are in
5
- // DEGREES. The euler order default is "YXZ" (yaw-pitch-roll, the usual choice for cameras / characters).
6
- //
7
- // NB: this fixes a transpose bug in the old `transform.ts` euler extraction, which read the matrix with
8
- // row/col swapped and so returned a NEGATED angle for a positive rotation. `eulerFromColMajor` below uses
9
- // the correct column-major element mapping, so `node.eulerAngles` now round-trips with the setter.
10
-
11
- import { Vec3, cx, cy, cz, type Vec3Like } from "./vec"
12
-
13
- export type QuatLike = Quat | readonly [number, number, number, number] | number[] | Float32Array
14
- export type EulerOrder = "XYZ" | "YXZ" | "ZXY" | "ZYX" | "YZX" | "XZY"
15
-
16
- /** Read the w / 4th component of any quaternion-like (wrapper `.w`, or array-like `[3]`). */
17
- export const cw = (q: { w?: number } | ArrayLike<number>): number => (q as { w?: number }).w ?? (q as ArrayLike<number>)[3]
18
- const DEG2RAD = Math.PI / 180
19
- const RAD2DEG = 180 / Math.PI
20
- const EPS = 1e-6
21
- const approx = (a: number, b: number, eps: number): boolean => Math.abs(a - b) <= eps * Math.max(1, Math.abs(a), Math.abs(b))
22
- const clamp1 = (v: number): number => (v < -1 ? -1 : v > 1 ? 1 : v)
23
-
24
- export class Quat {
25
- x: number
26
- y: number
27
- z: number
28
- w: number
29
-
30
- constructor(x: number | QuatLike = 0, y = 0, z = 0, w = 1) {
31
- if (typeof x === "number") { this.x = x; this.y = y; this.z = z; this.w = w }
32
- else { this.x = cx(x); this.y = cy(x); this.z = cz(x); this.w = cw(x) }
33
- }
34
-
35
- // --- mutation ---
36
- set(x: number, y: number, z: number, w: number): this { this.x = x; this.y = y; this.z = z; this.w = w; return this }
37
- copy(q: QuatLike): this { this.x = cx(q); this.y = cy(q); this.z = cz(q); this.w = cw(q); return this }
38
-
39
- // --- pure ops ---
40
- /** Hamilton product `this ⊗ b` (apply `b` first, then `this`). */
41
- mul(b: QuatLike): Quat {
42
- const ax = this.x, ay = this.y, az = this.z, aw = this.w
43
- const bx = cx(b), by = cy(b), bz = cz(b), bw = cw(b)
44
- return new Quat(
45
- ax * bw + aw * bx + ay * bz - az * by,
46
- ay * bw + aw * by + az * bx - ax * bz,
47
- az * bw + aw * bz + ax * by - ay * bx,
48
- aw * bw - ax * bx - ay * by - az * bz,
49
- )
50
- }
51
- normalize(): Quat {
52
- let l = Math.hypot(this.x, this.y, this.z, this.w)
53
- if (l === 0) return new Quat(0, 0, 0, 1)
54
- l = 1 / l
55
- return new Quat(this.x * l, this.y * l, this.z * l, this.w * l)
56
- }
57
- conjugate(): Quat { return new Quat(-this.x, -this.y, -this.z, this.w) }
58
- /** Inverse rotation. For a unit quaternion this equals `conjugate()`. */
59
- invert(): Quat {
60
- const d = this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w
61
- if (d === 0) return new Quat(0, 0, 0, 0)
62
- const i = 1 / d
63
- return new Quat(-this.x * i, -this.y * i, -this.z * i, this.w * i)
64
- }
65
- dot(b: QuatLike): number { return this.x * cx(b) + this.y * cy(b) + this.z * cz(b) + this.w * cw(b) }
66
- /** Spherical interpolation toward `b` by `t ∈ [0, 1]`. */
67
- slerp(b: QuatLike, t: number): Quat {
68
- const ax = this.x, ay = this.y, az = this.z, aw = this.w
69
- let bx = cx(b), by = cy(b), bz = cz(b), bw = cw(b)
70
- let cosom = ax * bx + ay * by + az * bz + aw * bw
71
- if (cosom < 0) { cosom = -cosom; bx = -bx; by = -by; bz = -bz; bw = -bw }
72
- let s0: number, s1: number
73
- if (1 - cosom > EPS) {
74
- const omega = Math.acos(cosom), sinom = Math.sin(omega)
75
- s0 = Math.sin((1 - t) * omega) / sinom
76
- s1 = Math.sin(t * omega) / sinom
77
- } else { s0 = 1 - t; s1 = t }
78
- return new Quat(s0 * ax + s1 * bx, s0 * ay + s1 * by, s0 * az + s1 * bz, s0 * aw + s1 * bw)
79
- }
80
- /** Angular distance (radians) between two unit quaternions. */
81
- angle(b: QuatLike): number {
82
- const d = this.dot(b)
83
- return Math.acos(clamp1(2 * d * d - 1))
84
- }
85
- /** Rotate a vector by this quaternion → a new `Vec3`. */
86
- rotateVec3(v: Vec3Like): Vec3 { return new Vec3(v).rotate(this) }
87
-
88
- /** Extract euler angles (DEGREES) in the given order. */
89
- toEuler(order: EulerOrder = "YXZ"): Vec3 {
90
- // build the (column-major) rotation matrix entries from the unit quaternion, then extract.
91
- const x = this.x, y = this.y, z = this.z, w = this.w
92
- const x2 = x + x, y2 = y + y, z2 = z + z
93
- const xx = x * x2, xy = x * y2, xz = x * z2
94
- const yy = y * y2, yz = y * z2, zz = z * z2
95
- const wx = w * x2, wy = w * y2, wz = w * z2
96
- const m = [
97
- 1 - (yy + zz), xy + wz, xz - wy, 0,
98
- xy - wz, 1 - (xx + zz), yz + wx, 0,
99
- xz + wy, yz - wx, 1 - (xx + yy), 0,
100
- ]
101
- return eulerFromColMajor(m, order)
102
- }
103
-
104
- // --- interop ---
105
- equals(b: QuatLike, eps = 1e-6): boolean {
106
- return approx(this.x, cx(b), eps) && approx(this.y, cy(b), eps) && approx(this.z, cz(b), eps) && approx(this.w, cw(b), eps)
107
- }
108
- /** True if the two unit quaternions represent the same rotation (±q are equal). */
109
- sameRotation(b: QuatLike, eps = 1e-6): boolean { return Math.abs(this.dot(b)) >= 1 - eps }
110
- clone(): Quat { return new Quat(this.x, this.y, this.z, this.w) }
111
- toArray(): [number, number, number, number] { return [ this.x, this.y, this.z, this.w ] }
112
- *[Symbol.iterator](): Iterator<number> { yield this.x; yield this.y; yield this.z; yield this.w }
113
-
114
- // --- factories ---
115
- static from(q: QuatLike): Quat { return new Quat(cx(q), cy(q), cz(q), cw(q)) }
116
- static get identity(): Quat { return new Quat(0, 0, 0, 1) }
117
-
118
- /** From euler angles in DEGREES (default order "YXZ"). Inverts `toEuler`. */
119
- static fromEuler(x: number, y: number, z: number, order: EulerOrder = "YXZ"): Quat {
120
- const c1 = Math.cos(x * DEG2RAD / 2), c2 = Math.cos(y * DEG2RAD / 2), c3 = Math.cos(z * DEG2RAD / 2)
121
- const s1 = Math.sin(x * DEG2RAD / 2), s2 = Math.sin(y * DEG2RAD / 2), s3 = Math.sin(z * DEG2RAD / 2)
122
- switch (order) {
123
- case "XYZ": return new Quat(s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3)
124
- case "YXZ": return new Quat(s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3)
125
- case "ZXY": return new Quat(s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3)
126
- case "ZYX": return new Quat(s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3)
127
- case "YZX": return new Quat(s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3)
128
- case "XZY": return new Quat(s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3)
129
- }
130
- }
131
-
132
- /** From a rotation `axis` and an angle in RADIANS. The axis is normalized for you. */
133
- static fromAxisAngle(axis: Vec3Like, rad: number): Quat {
134
- let ax = cx(axis), ay = cy(axis), az = cz(axis)
135
- const l = Math.hypot(ax, ay, az)
136
- if (l === 0) return new Quat(0, 0, 0, 1)
137
- ax /= l; ay /= l; az /= l
138
- const s = Math.sin(rad / 2)
139
- return new Quat(ax * s, ay * s, az * s, Math.cos(rad / 2))
140
- }
141
-
142
- /** The shortest rotation that takes direction `a` onto direction `b`. */
143
- static fromTo(a: Vec3Like, b: Vec3Like): Quat {
144
- const A = new Vec3(a).normalize(), B = new Vec3(b).normalize()
145
- const d = A.dot(B)
146
- if (d > 0.999999) return new Quat(0, 0, 0, 1)
147
- if (d < -0.999999) {
148
- // antiparallel: rotate 180° about any axis ⟂ A.
149
- let axis = new Vec3(1, 0, 0).cross(A)
150
- if (axis.lengthSq() < 1e-12) axis = new Vec3(0, 1, 0).cross(A)
151
- return Quat.fromAxisAngle(axis.normalize(), Math.PI)
152
- }
153
- const c = A.cross(B)
154
- return new Quat(c.x, c.y, c.z, 1 + d).normalize()
155
- }
156
-
157
- /** A rotation whose forward (−Z) points along `forward`, with `up` as the reference up axis. */
158
- static lookRotation(forward: Vec3Like, up: Vec3Like = Vec3.up): Quat {
159
- const zc = new Vec3(forward).normalize().negate() // local +Z basis (forward is −Z)
160
- let xc = new Vec3(up).cross(zc)
161
- if (xc.lengthSq() < 1e-12) xc = new Vec3(up).cross(zc.add([ 1e-4, 0, 0 ]))
162
- xc = xc.normalize()
163
- const yc = zc.cross(xc)
164
- return quatFromColMajor([ xc.x, xc.y, xc.z, 0, yc.x, yc.y, yc.z, 0, zc.x, zc.y, zc.z ])
165
- }
166
- }
167
-
168
- /** Quaternion from a column-major rotation matrix (≥3×3, indices 0,1,2,4,5,6,8,9,10). gl-matrix's
169
- * `getRotation` algorithm — the exact inverse of building a matrix from a quaternion. */
170
- export const quatFromColMajor = (m: ArrayLike<number>): Quat => {
171
- const m00 = m[0], m01 = m[1], m02 = m[2]
172
- const m10 = m[4], m11 = m[5], m12 = m[6]
173
- const m20 = m[8], m21 = m[9], m22 = m[10]
174
- const trace = m00 + m11 + m22
175
- let S: number
176
- if (trace > 0) {
177
- S = Math.sqrt(trace + 1) * 2
178
- return new Quat((m12 - m21) / S, (m20 - m02) / S, (m01 - m10) / S, 0.25 * S)
179
- } else if (m00 > m11 && m00 > m22) {
180
- S = Math.sqrt(1 + m00 - m11 - m22) * 2
181
- return new Quat(0.25 * S, (m01 + m10) / S, (m20 + m02) / S, (m12 - m21) / S)
182
- } else if (m11 > m22) {
183
- S = Math.sqrt(1 + m11 - m00 - m22) * 2
184
- return new Quat((m01 + m10) / S, 0.25 * S, (m12 + m21) / S, (m20 - m02) / S)
185
- } else {
186
- S = Math.sqrt(1 + m22 - m00 - m11) * 2
187
- return new Quat((m20 + m02) / S, (m12 + m21) / S, 0.25 * S, (m01 - m10) / S)
188
- }
189
- }
190
-
191
- /** Euler angles (DEGREES) from a column-major rotation matrix, standard three.js extraction.
192
- * Element naming follows the column-major layout: mRC = m[(C-1)*4 + (R-1)]. */
193
- export const eulerFromColMajor = (m: ArrayLike<number>, order: EulerOrder = "YXZ"): Vec3 => {
194
- const m11 = m[0], m21 = m[1], m31 = m[2]
195
- const m12 = m[4], m22 = m[5], m32 = m[6]
196
- const m13 = m[8], m23 = m[9], m33 = m[10]
197
- let x = 0, y = 0, z = 0
198
- const LIM = 0.9999999
199
- switch (order) {
200
- case "XYZ":
201
- y = Math.asin(clamp1(m13))
202
- if (Math.abs(m13) < LIM) { x = Math.atan2(-m23, m33); z = Math.atan2(-m12, m11) }
203
- else { x = Math.atan2(m32, m22); z = 0 }
204
- break
205
- case "YXZ":
206
- x = Math.asin(-clamp1(m23))
207
- if (Math.abs(m23) < LIM) { y = Math.atan2(m13, m33); z = Math.atan2(m21, m22) }
208
- else { y = Math.atan2(-m31, m11); z = 0 }
209
- break
210
- case "ZXY":
211
- x = Math.asin(clamp1(m32))
212
- if (Math.abs(m32) < LIM) { y = Math.atan2(-m31, m33); z = Math.atan2(-m12, m22) }
213
- else { y = 0; z = Math.atan2(m21, m11) }
214
- break
215
- case "ZYX":
216
- y = Math.asin(-clamp1(m31))
217
- if (Math.abs(m31) < LIM) { x = Math.atan2(m32, m33); z = Math.atan2(m21, m11) }
218
- else { x = 0; z = Math.atan2(-m12, m22) }
219
- break
220
- case "YZX":
221
- z = Math.asin(clamp1(m21))
222
- if (Math.abs(m21) < LIM) { x = Math.atan2(-m23, m22); y = Math.atan2(-m31, m11) }
223
- else { x = 0; y = Math.atan2(m13, m33) }
224
- break
225
- case "XZY":
226
- z = Math.asin(-clamp1(m12))
227
- if (Math.abs(m12) < LIM) { x = Math.atan2(m32, m22); y = Math.atan2(m13, m11) }
228
- else { x = Math.atan2(-m23, m33); y = 0 }
229
- break
230
- }
231
- return new Vec3(x * RAD2DEG, y * RAD2DEG, z * RAD2DEG)
232
- }
@@ -1,255 +0,0 @@
1
- // Fluent 2D / 3D vectors — the SDK's user-facing vector math (no gl-matrix, no out-params, no imports).
2
- //
3
- // Design (see PLAN.md): a vector is a *mutable little struct* — `v.x = 3`, `v.set(x, y, z)` mutate in
4
- // place — but every *method* is PURE: `a.add(b)` returns a NEW vector and never touches `a`. So chains
5
- // (`dir.normalize().scale(s)`) can't clobber their source, and the immutable-method style is the most
6
- // amenable to a future escape-analysis / scalar-replacement compiler pass. Backing is plain number
7
- // fields (not a Float32Array), which the optimizer can trivially scalar-replace.
8
- //
9
- // "Never wrap away the metal": vectors expose `.x/.y/.z`, are iterable (`const [x, y] = v`, `[...v]`),
10
- // and every method / constructor accepts a raw tuple just as well as a wrapper. Reads off a `*Like`
11
- // input go through the `cx/cy/cz` helpers below, which work uniformly on a wrapper (`.x`) or an
12
- // array-like (`[0]`), so callers can always pass `[0, 1, 0]` where a vector is expected.
13
-
14
- export type Vec2Like = Vec2 | readonly [number, number] | number[] | Float32Array
15
- export type Vec3Like = Vec3 | readonly [number, number, number] | number[] | Float32Array
16
-
17
- /** Read the x / first component of any vector-like (wrapper, tuple, or typed array). `?? ` is safe on
18
- * 0 and NaN — it only falls through on `undefined` (i.e. a non-wrapper that has no `.x`). */
19
- export const cx = (v: { x?: number } | ArrayLike<number>): number => (v as { x?: number }).x ?? (v as ArrayLike<number>)[0]
20
- export const cy = (v: { y?: number } | ArrayLike<number>): number => (v as { y?: number }).y ?? (v as ArrayLike<number>)[1]
21
- export const cz = (v: { z?: number } | ArrayLike<number>): number => (v as { z?: number }).z ?? (v as ArrayLike<number>)[2]
22
-
23
- /** Relative-epsilon compare (matches gl-matrix's `equals`), so the oracle tests agree to the bit. */
24
- const approx = (a: number, b: number, eps: number): boolean => Math.abs(a - b) <= eps * Math.max(1, Math.abs(a), Math.abs(b))
25
-
26
- // ---------------------------------------------------------------------------------------------------
27
- // Vec2
28
- // ---------------------------------------------------------------------------------------------------
29
-
30
- export class Vec2 {
31
- x: number
32
- y: number
33
-
34
- constructor(x: number | Vec2Like = 0, y = 0) {
35
- if (typeof x === "number") { this.x = x; this.y = y }
36
- else { this.x = cx(x); this.y = cy(x) }
37
- }
38
-
39
- // --- mutation (the only methods that change `this`) ---
40
- /** Mutate the components in place. Returns `this`. */
41
- set(x: number, y: number): this { this.x = x; this.y = y; return this }
42
- /** Copy another vector's components into this one. Returns `this`. */
43
- copy(v: Vec2Like): this { this.x = cx(v); this.y = cy(v); return this }
44
-
45
- // --- pure arithmetic (each returns a NEW Vec2) ---
46
- add(v: Vec2Like): Vec2 { return new Vec2(this.x + cx(v), this.y + cy(v)) }
47
- sub(v: Vec2Like): Vec2 { return new Vec2(this.x - cx(v), this.y - cy(v)) }
48
- mul(v: Vec2Like): Vec2 { return new Vec2(this.x * cx(v), this.y * cy(v)) }
49
- div(v: Vec2Like): Vec2 { return new Vec2(this.x / cx(v), this.y / cy(v)) }
50
- scale(s: number): Vec2 { return new Vec2(this.x * s, this.y * s) }
51
- /** `this + v * s` (fused multiply-add). */
52
- scaleAndAdd(v: Vec2Like, s: number): Vec2 { return new Vec2(this.x + cx(v) * s, this.y + cy(v) * s) }
53
- negate(): Vec2 { return new Vec2(-this.x, -this.y) }
54
-
55
- // --- products / measures ---
56
- dot(v: Vec2Like): number { return this.x * cx(v) + this.y * cy(v) }
57
- /** 2D cross product → the scalar z of the 3D cross (signed parallelogram area). */
58
- cross(v: Vec2Like): number { return this.x * cy(v) - this.y * cx(v) }
59
- length(): number { return Math.hypot(this.x, this.y) }
60
- lengthSq(): number { return this.x * this.x + this.y * this.y }
61
- distanceTo(v: Vec2Like): number { return Math.hypot(this.x - cx(v), this.y - cy(v)) }
62
- distanceSqTo(v: Vec2Like): number { const dx = this.x - cx(v), dy = this.y - cy(v); return dx * dx + dy * dy }
63
-
64
- normalize(): Vec2 {
65
- const l = Math.hypot(this.x, this.y)
66
- return l === 0 ? new Vec2(0, 0) : new Vec2(this.x / l, this.y / l)
67
- }
68
- lerp(v: Vec2Like, t: number): Vec2 { return new Vec2(this.x + (cx(v) - this.x) * t, this.y + (cy(v) - this.y) * t) }
69
- clamp(min: Vec2Like, max: Vec2Like): Vec2 {
70
- return new Vec2(clampN(this.x, cx(min), cx(max)), clampN(this.y, cy(min), cy(max)))
71
- }
72
- min(v: Vec2Like): Vec2 { return new Vec2(Math.min(this.x, cx(v)), Math.min(this.y, cy(v))) }
73
- max(v: Vec2Like): Vec2 { return new Vec2(Math.max(this.x, cx(v)), Math.max(this.y, cy(v))) }
74
- /** Left-hand perpendicular: (-y, x), a 90° CCW turn. */
75
- perp(): Vec2 { return new Vec2(-this.y, this.x) }
76
- /** Rotate by `rad` (CCW) around `origin` (default 0). */
77
- rotate(rad: number, origin: Vec2Like = ZERO2): Vec2 {
78
- const ox = cx(origin), oy = cy(origin)
79
- const c = Math.cos(rad), s = Math.sin(rad)
80
- const dx = this.x - ox, dy = this.y - oy
81
- return new Vec2(ox + dx * c - dy * s, oy + dx * s + dy * c)
82
- }
83
- /** The vector's heading in radians: `atan2(y, x)`. */
84
- heading(): number { return Math.atan2(this.y, this.x) }
85
- /** Angle (radians) between this and `v`. */
86
- angle(v: Vec2Like): number {
87
- const mag = Math.sqrt(this.lengthSq() * (cx(v) * cx(v) + cy(v) * cy(v)))
88
- return mag === 0 ? 0 : Math.acos(clampN(this.dot(v) / mag, -1, 1))
89
- }
90
-
91
- withX(x: number): Vec2 { return new Vec2(x, this.y) }
92
- withY(y: number): Vec2 { return new Vec2(this.x, y) }
93
-
94
- // --- interop ---
95
- equals(v: Vec2Like, eps = 1e-6): boolean { return approx(this.x, cx(v), eps) && approx(this.y, cy(v), eps) }
96
- clone(): Vec2 { return new Vec2(this.x, this.y) }
97
- toArray(): [number, number] { return [ this.x, this.y ] }
98
- *[Symbol.iterator](): Iterator<number> { yield this.x; yield this.y }
99
-
100
- static from(v: Vec2Like): Vec2 { return new Vec2(cx(v), cy(v)) }
101
- static get zero(): Vec2 { return new Vec2(0, 0) }
102
- static get one(): Vec2 { return new Vec2(1, 1) }
103
- static get up(): Vec2 { return new Vec2(0, 1) }
104
- static get down(): Vec2 { return new Vec2(0, -1) }
105
- static get left(): Vec2 { return new Vec2(-1, 0) }
106
- static get right(): Vec2 { return new Vec2(1, 0) }
107
- }
108
-
109
- // ---------------------------------------------------------------------------------------------------
110
- // Vec3
111
- // ---------------------------------------------------------------------------------------------------
112
-
113
- export class Vec3 {
114
- x: number
115
- y: number
116
- z: number
117
-
118
- constructor(x: number | Vec3Like = 0, y = 0, z = 0) {
119
- if (typeof x === "number") { this.x = x; this.y = y; this.z = z }
120
- else { this.x = cx(x); this.y = cy(x); this.z = cz(x) }
121
- }
122
-
123
- // --- mutation ---
124
- set(x: number, y: number, z: number): this { this.x = x; this.y = y; this.z = z; return this }
125
- copy(v: Vec3Like): this { this.x = cx(v); this.y = cy(v); this.z = cz(v); return this }
126
-
127
- // --- pure arithmetic ---
128
- add(v: Vec3Like): Vec3 { return new Vec3(this.x + cx(v), this.y + cy(v), this.z + cz(v)) }
129
- sub(v: Vec3Like): Vec3 { return new Vec3(this.x - cx(v), this.y - cy(v), this.z - cz(v)) }
130
- mul(v: Vec3Like): Vec3 { return new Vec3(this.x * cx(v), this.y * cy(v), this.z * cz(v)) }
131
- div(v: Vec3Like): Vec3 { return new Vec3(this.x / cx(v), this.y / cy(v), this.z / cz(v)) }
132
- scale(s: number): Vec3 { return new Vec3(this.x * s, this.y * s, this.z * s) }
133
- /** `this + v * s` (fused multiply-add). */
134
- scaleAndAdd(v: Vec3Like, s: number): Vec3 { return new Vec3(this.x + cx(v) * s, this.y + cy(v) * s, this.z + cz(v) * s) }
135
- negate(): Vec3 { return new Vec3(-this.x, -this.y, -this.z) }
136
-
137
- // --- products / measures ---
138
- dot(v: Vec3Like): number { return this.x * cx(v) + this.y * cy(v) + this.z * cz(v) }
139
- cross(v: Vec3Like): Vec3 {
140
- const bx = cx(v), by = cy(v), bz = cz(v)
141
- return new Vec3(this.y * bz - this.z * by, this.z * bx - this.x * bz, this.x * by - this.y * bx)
142
- }
143
- length(): number { return Math.hypot(this.x, this.y, this.z) }
144
- lengthSq(): number { return this.x * this.x + this.y * this.y + this.z * this.z }
145
- distanceTo(v: Vec3Like): number { return Math.hypot(this.x - cx(v), this.y - cy(v), this.z - cz(v)) }
146
- distanceSqTo(v: Vec3Like): number {
147
- const dx = this.x - cx(v), dy = this.y - cy(v), dz = this.z - cz(v)
148
- return dx * dx + dy * dy + dz * dz
149
- }
150
-
151
- normalize(): Vec3 {
152
- const l = Math.hypot(this.x, this.y, this.z)
153
- return l === 0 ? new Vec3(0, 0, 0) : new Vec3(this.x / l, this.y / l, this.z / l)
154
- }
155
- lerp(v: Vec3Like, t: number): Vec3 {
156
- return new Vec3(this.x + (cx(v) - this.x) * t, this.y + (cy(v) - this.y) * t, this.z + (cz(v) - this.z) * t)
157
- }
158
- clamp(min: Vec3Like, max: Vec3Like): Vec3 {
159
- return new Vec3(clampN(this.x, cx(min), cx(max)), clampN(this.y, cy(min), cy(max)), clampN(this.z, cz(min), cz(max)))
160
- }
161
- min(v: Vec3Like): Vec3 { return new Vec3(Math.min(this.x, cx(v)), Math.min(this.y, cy(v)), Math.min(this.z, cz(v))) }
162
- max(v: Vec3Like): Vec3 { return new Vec3(Math.max(this.x, cx(v)), Math.max(this.y, cy(v)), Math.max(this.z, cz(v))) }
163
- /** Reflect this vector about a (unit) `normal`: `v - 2·(v·n)·n`. */
164
- reflect(normal: Vec3Like): Vec3 {
165
- const d = 2 * this.dot(normal)
166
- return new Vec3(this.x - d * cx(normal), this.y - d * cy(normal), this.z - d * cz(normal))
167
- }
168
- /** Vector projection of this onto `onto`. */
169
- project(onto: Vec3Like): Vec3 {
170
- const ox = cx(onto), oy = cy(onto), oz = cz(onto)
171
- const denom = ox * ox + oy * oy + oz * oz
172
- if (denom === 0) return new Vec3(0, 0, 0)
173
- const k = this.dot(onto) / denom
174
- return new Vec3(ox * k, oy * k, oz * k)
175
- }
176
- /** Angle (radians) between this and `v`. */
177
- angle(v: Vec3Like): number {
178
- const bx = cx(v), by = cy(v), bz = cz(v)
179
- const mag = Math.sqrt(this.lengthSq() * (bx * bx + by * by + bz * bz))
180
- return mag === 0 ? 0 : Math.acos(clampN(this.dot(v) / mag, -1, 1))
181
- }
182
-
183
- // --- rotation ---
184
- /** Rotate this point around the X axis through `origin` (default 0) by `rad`. */
185
- rotateX(rad: number, origin: Vec3Like = ZERO3): Vec3 {
186
- const ox = cx(origin), oy = cy(origin), oz = cz(origin)
187
- const py = this.y - oy, pz = this.z - oz, c = Math.cos(rad), s = Math.sin(rad)
188
- return new Vec3(this.x, oy + py * c - pz * s, oz + py * s + pz * c)
189
- }
190
- rotateY(rad: number, origin: Vec3Like = ZERO3): Vec3 {
191
- const ox = cx(origin), oy = cy(origin), oz = cz(origin)
192
- const px = this.x - ox, pz = this.z - oz, c = Math.cos(rad), s = Math.sin(rad)
193
- return new Vec3(ox + pz * s + px * c, this.y, oz + pz * c - px * s)
194
- }
195
- rotateZ(rad: number, origin: Vec3Like = ZERO3): Vec3 {
196
- const ox = cx(origin), oy = cy(origin), oz = cz(origin)
197
- const px = this.x - ox, py = this.y - oy, c = Math.cos(rad), s = Math.sin(rad)
198
- return new Vec3(ox + px * c - py * s, oy + px * s + py * c, this.z)
199
- }
200
- /** Rotate this vector by a quaternion (x, y, z, w). Accepts a `Quat` or a raw `[x,y,z,w]`. */
201
- rotate(q: ArrayLike<number> | { x: number, y: number, z: number, w: number }): Vec3 {
202
- const qx = cx(q as Vec3Like), qy = cy(q as Vec3Like), qz = cz(q as Vec3Like)
203
- const qw = (q as { w?: number }).w ?? (q as ArrayLike<number>)[3]
204
- const vx = this.x, vy = this.y, vz = this.z
205
- // t = 2 * (q.xyz × v)
206
- let tx = qy * vz - qz * vy, ty = qz * vx - qx * vz, tz = qx * vy - qy * vx
207
- tx += tx; ty += ty; tz += tz
208
- // v + w·t + q.xyz × t
209
- return new Vec3(
210
- vx + qw * tx + qy * tz - qz * ty,
211
- vy + qw * ty + qz * tx - qx * tz,
212
- vz + qw * tz + qx * ty - qy * tx,
213
- )
214
- }
215
- /** Transform this point by a column-major mat4 (a `Mat4`, its `.m`, or a raw length-16 array),
216
- * applying translation and the perspective divide. */
217
- transform(m: ArrayLike<number> | { m: ArrayLike<number> }): Vec3 {
218
- const a = (m as { m?: ArrayLike<number> }).m ?? (m as ArrayLike<number>)
219
- const x = this.x, y = this.y, z = this.z
220
- let w = a[3] * x + a[7] * y + a[11] * z + a[15]
221
- w = w || 1
222
- return new Vec3(
223
- (a[0] * x + a[4] * y + a[8] * z + a[12]) / w,
224
- (a[1] * x + a[5] * y + a[9] * z + a[13]) / w,
225
- (a[2] * x + a[6] * y + a[10] * z + a[14]) / w,
226
- )
227
- }
228
-
229
- withX(x: number): Vec3 { return new Vec3(x, this.y, this.z) }
230
- withY(y: number): Vec3 { return new Vec3(this.x, y, this.z) }
231
- withZ(z: number): Vec3 { return new Vec3(this.x, this.y, z) }
232
-
233
- // --- interop ---
234
- equals(v: Vec3Like, eps = 1e-6): boolean {
235
- return approx(this.x, cx(v), eps) && approx(this.y, cy(v), eps) && approx(this.z, cz(v), eps)
236
- }
237
- clone(): Vec3 { return new Vec3(this.x, this.y, this.z) }
238
- toArray(): [number, number, number] { return [ this.x, this.y, this.z ] }
239
- *[Symbol.iterator](): Iterator<number> { yield this.x; yield this.y; yield this.z }
240
-
241
- static from(v: Vec3Like): Vec3 { return new Vec3(cx(v), cy(v), cz(v)) }
242
- static get zero(): Vec3 { return new Vec3(0, 0, 0) }
243
- static get one(): Vec3 { return new Vec3(1, 1, 1) }
244
- static get up(): Vec3 { return new Vec3(0, 1, 0) }
245
- static get down(): Vec3 { return new Vec3(0, -1, 0) }
246
- static get left(): Vec3 { return new Vec3(-1, 0, 0) }
247
- static get right(): Vec3 { return new Vec3(1, 0, 0) }
248
- /** Forward is −Z (the engine's cameras look down −Z; `node.forward` is −Z at identity). */
249
- static get forward(): Vec3 { return new Vec3(0, 0, -1) }
250
- static get back(): Vec3 { return new Vec3(0, 0, 1) }
251
- }
252
-
253
- const clampN = (v: number, lo: number, hi: number): number => (v < lo ? lo : v > hi ? hi : v)
254
- const ZERO2 = new Vec2(0, 0)
255
- const ZERO3 = new Vec3(0, 0, 0)
@@ -1,119 +0,0 @@
1
- // Defaults → wire layout (docs/multiplayer-plan.md §4 "input: defaults object = layout"). The hot
2
- // stream — inputs and snapshot states — is binary and its shape comes from a plain defaults object:
3
- // number → f32
4
- // boolean → 1 bit (packed after the floats)
5
- // number[] → f32 × length, fixed
6
- // Nothing else: no strings, no nesting, no optionals — snapshot fields are flat and fixed-size by
7
- // design. Both sides run the same bundle, so the layout is derived, never transmitted; the `sig`
8
- // feeds the schema hash the handshake compares.
9
-
10
- export type FieldKind = 0 | 1 | 2 // f32 · bool · f32[]
11
- export type Field = { key: string; kind: FieldKind; len: number }
12
- export type Layout = { fields: Field[]; floats: number; bools: number; bytes: number; sig: string }
13
-
14
- const describe = (v: unknown): string => Array.isArray(v) ? `array of ${v.length}` : v === null ? "null" : typeof v
15
-
16
- export const makeLayout = (defaults: Record<string, unknown>, what: string): Layout => {
17
- const fields: Field[] = []
18
- let floats = 0
19
- let bools = 0
20
- for (const key of Object.keys(defaults)) {
21
- const v = defaults[key]
22
- if (typeof v === "number") { fields.push({ key, kind: 0, len: 1 }); floats++ }
23
- else if (typeof v === "boolean") { fields.push({ key, kind: 1, len: 1 }); bools++ }
24
- else if (Array.isArray(v) && v.length > 0 && v.length <= 64 && v.every((n) => typeof n === "number")) {
25
- fields.push({ key, kind: 2, len: v.length }); floats += v.length
26
- } else {
27
- throw new Error(`${what}: field '${key}' must be a number, a boolean or a fixed-length number[] (got ${describe(v)})`)
28
- }
29
- }
30
- const sig = fields.map((f) => `${f.key}:${f.kind}${f.len > 1 ? f.len : ""}`).join(",")
31
- return { fields, floats, bools, bytes: floats * 4 + Math.ceil(bools / 8), sig }
32
- }
33
-
34
- /** A fresh state object shaped like the defaults (arrays copied, never shared). */
35
- export const cloneDefaults = <T extends object>(defaults: T): T => {
36
- const out: Record<string, unknown> = {}
37
- for (const key of Object.keys(defaults)) {
38
- const v = (defaults as Record<string, unknown>)[key]
39
- out[key] = Array.isArray(v) ? v.slice() : v
40
- }
41
- return out as T
42
- }
43
-
44
- /** Copy the layout's fields from `from` into `into` (array elements copied in place; unknown keys
45
- * ignored — a JSON spawn state or a partial `setInput`). */
46
- export const assignState = (layout: Layout, into: any, from: any): void => {
47
- if (!from) return
48
- for (const f of layout.fields) {
49
- const v = from[f.key]
50
- if (v === undefined) continue
51
- if (f.kind === 2) {
52
- const a = into[f.key] as number[]
53
- for (let i = 0; i < f.len; i++) a[i] = +(v[i] ?? 0)
54
- } else if (f.kind === 1) into[f.key] = !!v
55
- else into[f.key] = +v
56
- }
57
- }
58
-
59
- export const packInto = (view: DataView, offset: number, layout: Layout, s: any): number => {
60
- let o = offset
61
- for (const f of layout.fields) {
62
- if (f.kind === 0) { view.setFloat32(o, s[f.key], true); o += 4 }
63
- else if (f.kind === 2) {
64
- const a = s[f.key] as number[]
65
- for (let i = 0; i < f.len; i++) { view.setFloat32(o, a[i] ?? 0, true); o += 4 }
66
- }
67
- }
68
- let bits = 0
69
- let n = 0
70
- for (const f of layout.fields) {
71
- if (f.kind !== 1) continue
72
- if (s[f.key]) bits |= 1 << (n & 7)
73
- n++
74
- if ((n & 7) === 0) { view.setUint8(o++, bits); bits = 0 }
75
- }
76
- if (n & 7) view.setUint8(o++, bits)
77
- return o
78
- }
79
-
80
- export const unpackFrom = (view: DataView, offset: number, layout: Layout, into: any): number => {
81
- let o = offset
82
- for (const f of layout.fields) {
83
- if (f.kind === 0) { into[f.key] = view.getFloat32(o, true); o += 4 }
84
- else if (f.kind === 2) {
85
- const a = into[f.key] as number[]
86
- for (let i = 0; i < f.len; i++) { a[i] = view.getFloat32(o, true); o += 4 }
87
- }
88
- }
89
- let bits = 0
90
- let n = 0
91
- for (const f of layout.fields) {
92
- if (f.kind !== 1) continue
93
- if ((n & 7) === 0) bits = view.getUint8(o++)
94
- into[f.key] = (bits & (1 << (n & 7))) !== 0
95
- n++
96
- }
97
- return o
98
- }
99
-
100
- /** `out = a + (b − a) · t` for the numeric fields; booleans take `b`'s. */
101
- export const lerpState = (layout: Layout, a: any, b: any, t: number, out: any): void => {
102
- for (const f of layout.fields) {
103
- if (f.kind === 0) out[f.key] = a[f.key] + (b[f.key] - a[f.key]) * t
104
- else if (f.kind === 2) {
105
- const pa = a[f.key] as number[], pb = b[f.key] as number[], po = out[f.key] as number[]
106
- for (let i = 0; i < f.len; i++) po[i] = pa[i] + (pb[i] - pa[i]) * t
107
- } else out[f.key] = b[f.key]
108
- }
109
- }
110
-
111
- /** FNV-1a over a string → 8 hex chars. Enough to tell two bundles' schemas apart. */
112
- export const hashString = (s: string): string => {
113
- let h = 0x811c9dc5
114
- for (let i = 0; i < s.length; i++) {
115
- h ^= s.charCodeAt(i)
116
- h = Math.imul(h, 0x01000193)
117
- }
118
- return (h >>> 0).toString(16).padStart(8, "0")
119
- }