lecodes-cli 0.19.0 → 0.20.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.
Files changed (270) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +1069 -396
  3. package/package.json +14 -13
  4. package/runtime/scene-harness.json +1 -1
  5. package/runtime/sdk-types.json +1 -1
  6. package/runtime/web/assets/Roboto-Variable-DHm9jpd-.woff2 +0 -0
  7. package/runtime/web/assets/__vite-browser-external-BIHI7g3E.js +1 -0
  8. package/runtime/web/assets/basis-C64VHDVD.js +1 -0
  9. package/runtime/web/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
  10. package/runtime/web/assets/createViewerLite-BvHobVhp.js +852 -0
  11. package/runtime/web/assets/draco-BiISTFcR.js +118 -0
  12. package/runtime/web/assets/draco_decoder-DsQ12WqX.wasm +0 -0
  13. package/runtime/web/assets/index-CfNCCBjN.js +2 -0
  14. package/runtime/web/assets/mapViewImpl-B2JcES8l.js +810 -0
  15. package/runtime/web/assets/maplibre-gl-worker-CJfwIrte.js +8 -0
  16. package/runtime/web/assets/neutral_ibl128-D-lzdkLN.ktx +0 -0
  17. package/runtime/web/assets/worker-Caf-yYEI.js +2 -0
  18. package/runtime/web/embed.html +32 -0
  19. package/runtime/web/embed.js +171 -0
  20. package/src/api.ts +302 -0
  21. package/src/browserAuth.ts +87 -0
  22. package/src/cmgenTool.ts +104 -0
  23. package/src/commands/app.ts +896 -0
  24. package/src/commands/appAndroid.ts +603 -0
  25. package/src/commands/appDesktop.ts +339 -0
  26. package/src/commands/appDesktopMac.ts +470 -0
  27. package/src/commands/appIcon.ts +187 -0
  28. package/src/commands/appShared.ts +542 -0
  29. package/src/commands/appTemplates.ts +907 -0
  30. package/src/commands/appTemplatesAndroid.ts +599 -0
  31. package/src/commands/appTemplatesGradlew.ts +9 -0
  32. package/src/commands/assets.ts +28 -0
  33. package/src/commands/clone.ts +60 -0
  34. package/src/commands/compile.ts +253 -0
  35. package/src/commands/create.ts +59 -0
  36. package/src/commands/design.ts +863 -0
  37. package/src/commands/designTemplates.ts +11 -0
  38. package/src/commands/desktop.ts +127 -0
  39. package/src/commands/dev.ts +236 -0
  40. package/src/commands/diff.ts +59 -0
  41. package/src/commands/init.ts +191 -0
  42. package/src/commands/install.ts +198 -0
  43. package/src/commands/lightmap.ts +290 -0
  44. package/src/commands/link.ts +147 -0
  45. package/src/commands/login.ts +24 -0
  46. package/src/commands/navmesh.ts +225 -0
  47. package/src/commands/pn.ts +305 -0
  48. package/src/commands/projectTemplates.ts +130 -0
  49. package/src/commands/pull.ts +109 -0
  50. package/src/commands/push.ts +173 -0
  51. package/src/commands/render.ts +416 -0
  52. package/src/commands/scene.ts +148 -0
  53. package/src/commands/shaders.ts +157 -0
  54. package/src/commands/status.ts +29 -0
  55. package/src/commands/test.ts +371 -0
  56. package/src/commands/thumbs.ts +178 -0
  57. package/src/commands/types.ts +87 -0
  58. package/src/commands/update.ts +190 -0
  59. package/src/compile/assetIcons.ts +214 -0
  60. package/src/compile/collect.ts +46 -0
  61. package/src/compile/collectLocal.ts +37 -0
  62. package/src/compile/designCompile.ts +104 -0
  63. package/src/compile/fonts.ts +156 -0
  64. package/src/compile/headlessBundle.ts +129 -0
  65. package/src/compile/nativeStack.ts +21 -0
  66. package/src/compile/projectCompile.ts +109 -0
  67. package/src/compile/sceneCompile.ts +113 -0
  68. package/src/compile/screenEntry.ts +127 -0
  69. package/src/compile/shaders.ts +245 -0
  70. package/src/config.ts +42 -0
  71. package/src/designMeta.ts +35 -0
  72. package/src/desktopRenderer.ts +545 -0
  73. package/src/desktopScript.ts +276 -0
  74. package/src/dev/clientTemplates.ts +160 -0
  75. package/src/dev/devServer.ts +333 -0
  76. package/src/dev/webRunner.ts +22 -0
  77. package/src/dev/wsServer.ts +144 -0
  78. package/src/distRoot.ts +20 -0
  79. package/src/ignore.ts +163 -0
  80. package/src/index.ts +494 -0
  81. package/src/lecodes-3d-editor.d.ts +41 -0
  82. package/src/lecodes-assets.d.ts +7 -0
  83. package/src/lecodes-design.d.ts +191 -0
  84. package/src/lecodes-renderer.d.ts +131 -0
  85. package/src/localFiles.ts +144 -0
  86. package/src/manifest.ts +46 -0
  87. package/src/matcTool.ts +137 -0
  88. package/src/peers.ts +40 -0
  89. package/src/project.ts +61 -0
  90. package/src/projectEnv.ts +94 -0
  91. package/src/qrcode-terminal.d.ts +9 -0
  92. package/src/releases.ts +125 -0
  93. package/src/serverDiff.ts +78 -0
  94. package/src/textDiff.ts +103 -0
  95. package/src/types.ts +0 -0
  96. package/src/util.ts +146 -0
  97. package/runtime/sdk/animate/animate.ts +0 -238
  98. package/runtime/sdk/animate/bezier.ts +0 -138
  99. package/runtime/sdk/animate/easings.ts +0 -126
  100. package/runtime/sdk/canvas/Canvas.ts +0 -305
  101. package/runtime/sdk/compile/aspectMacro.ts +0 -86
  102. package/runtime/sdk/compile/assetIconMacro.ts +0 -384
  103. package/runtime/sdk/compile/assetMacro.ts +0 -146
  104. package/runtime/sdk/compile/assetName.ts +0 -50
  105. package/runtime/sdk/compile/bundler.ts +0 -287
  106. package/runtime/sdk/compile/compileProject.ts +0 -129
  107. package/runtime/sdk/compile/detectEntry.ts +0 -128
  108. package/runtime/sdk/compile/fontMacro.ts +0 -459
  109. package/runtime/sdk/compile/fontRegistry.ts +0 -78
  110. package/runtime/sdk/compile/header.ts +0 -67
  111. package/runtime/sdk/compile/index.ts +0 -100
  112. package/runtime/sdk/compile/libraryImports.ts +0 -52
  113. package/runtime/sdk/compile/liteMaterial.ts +0 -247
  114. package/runtime/sdk/compile/sceneEditor.ts +0 -88
  115. package/runtime/sdk/compile/serverSplit.ts +0 -233
  116. package/runtime/sdk/compile/serverTypes.ts +0 -227
  117. package/runtime/sdk/compile/sfnt.ts +0 -98
  118. package/runtime/sdk/compile/shaderSchema.ts +0 -202
  119. package/runtime/sdk/compile/shaderTargets.ts +0 -81
  120. package/runtime/sdk/compile/sourcemap.ts +0 -25
  121. package/runtime/sdk/core/Aspect.ts +0 -568
  122. package/runtime/sdk/core/InspectorUI.ts +0 -212
  123. package/runtime/sdk/core/StateMachine.ts +0 -308
  124. package/runtime/sdk/core/color.ts +0 -66
  125. package/runtime/sdk/core/compWrite.ts +0 -42
  126. package/runtime/sdk/core/events.ts +0 -38
  127. package/runtime/sdk/core/fields.ts +0 -120
  128. package/runtime/sdk/core/registry.ts +0 -23
  129. package/runtime/sdk/core/signals.ts +0 -277
  130. package/runtime/sdk/core/time.ts +0 -84
  131. package/runtime/sdk/g2/Camera2D.ts +0 -40
  132. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  133. package/runtime/sdk/g2/Node2D.ts +0 -267
  134. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  135. package/runtime/sdk/g2/Physics2D.ts +0 -346
  136. package/runtime/sdk/g2/Scene2D.ts +0 -209
  137. package/runtime/sdk/g2/Shape2D.ts +0 -259
  138. package/runtime/sdk/g2/Sprite.ts +0 -89
  139. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  140. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  141. package/runtime/sdk/g2/Texture2D.ts +0 -47
  142. package/runtime/sdk/g2/Tilemap.ts +0 -41
  143. package/runtime/sdk/g2/Tileset.ts +0 -71
  144. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  145. package/runtime/sdk/g2/autotile.ts +0 -433
  146. package/runtime/sdk/g2/cells.ts +0 -91
  147. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  148. package/runtime/sdk/g2/groups2d.ts +0 -106
  149. package/runtime/sdk/g2/loop.ts +0 -50
  150. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  151. package/runtime/sdk/g2/touch.ts +0 -83
  152. package/runtime/sdk/gl/Camera.ts +0 -160
  153. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  154. package/runtime/sdk/gl/CharacterController.ts +0 -238
  155. package/runtime/sdk/gl/Gearbox.ts +0 -212
  156. package/runtime/sdk/gl/Geometry.ts +0 -279
  157. package/runtime/sdk/gl/IK.ts +0 -193
  158. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  159. package/runtime/sdk/gl/Light.ts +0 -107
  160. package/runtime/sdk/gl/Lightmap.ts +0 -256
  161. package/runtime/sdk/gl/Material.ts +0 -260
  162. package/runtime/sdk/gl/Mesh.ts +0 -83
  163. package/runtime/sdk/gl/Model.ts +0 -124
  164. package/runtime/sdk/gl/NavAgent.ts +0 -337
  165. package/runtime/sdk/gl/NavMesh.ts +0 -397
  166. package/runtime/sdk/gl/Node.ts +0 -350
  167. package/runtime/sdk/gl/Noise.ts +0 -30
  168. package/runtime/sdk/gl/Particles.ts +0 -676
  169. package/runtime/sdk/gl/Physics.ts +0 -222
  170. package/runtime/sdk/gl/Plane.ts +0 -53
  171. package/runtime/sdk/gl/Ragdoll.ts +0 -270
  172. package/runtime/sdk/gl/Ray.ts +0 -16
  173. package/runtime/sdk/gl/Scene.ts +0 -486
  174. package/runtime/sdk/gl/Shape.ts +0 -409
  175. package/runtime/sdk/gl/Terrain.ts +0 -1092
  176. package/runtime/sdk/gl/Texture.ts +0 -63
  177. package/runtime/sdk/gl/Trigger.ts +0 -45
  178. package/runtime/sdk/gl/Vehicle.ts +0 -473
  179. package/runtime/sdk/gl/Wheel.ts +0 -240
  180. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  181. package/runtime/sdk/gl/animation/Animator.ts +0 -120
  182. package/runtime/sdk/gl/animation/Layer.ts +0 -31
  183. package/runtime/sdk/gl/animation/Loop.ts +0 -30
  184. package/runtime/sdk/gl/animation/Playback.ts +0 -44
  185. package/runtime/sdk/gl/animation/core.ts +0 -336
  186. package/runtime/sdk/gl/controls.ts +0 -95
  187. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  188. package/runtime/sdk/gl/scenarios.ts +0 -291
  189. package/runtime/sdk/gl/state.ts +0 -6
  190. package/runtime/sdk/gl/terrainMesh.ts +0 -219
  191. package/runtime/sdk/gl/touch.ts +0 -68
  192. package/runtime/sdk/inject.ts +0 -197
  193. package/runtime/sdk/math/Mathf.ts +0 -118
  194. package/runtime/sdk/math/mat4.ts +0 -278
  195. package/runtime/sdk/math/quat.ts +0 -232
  196. package/runtime/sdk/math/vec.ts +0 -255
  197. package/runtime/sdk/net/codec.ts +0 -119
  198. package/runtime/sdk/net/core.ts +0 -384
  199. package/runtime/sdk/net/index.ts +0 -181
  200. package/runtime/sdk/net/replication.ts +0 -622
  201. package/runtime/sdk/plugins/camera.ts +0 -81
  202. package/runtime/sdk/plugins/geolocation.ts +0 -123
  203. package/runtime/sdk/plugins/oauth.ts +0 -61
  204. package/runtime/sdk/plugins/permission.ts +0 -7
  205. package/runtime/sdk/plugins/push.ts +0 -132
  206. package/runtime/sdk/plugins/qr.ts +0 -73
  207. package/runtime/sdk/plugins/service.ts +0 -47
  208. package/runtime/sdk/runtime/app.ts +0 -101
  209. package/runtime/sdk/runtime/appEvents.ts +0 -54
  210. package/runtime/sdk/runtime/channel.ts +0 -61
  211. package/runtime/sdk/runtime/clipboard.ts +0 -20
  212. package/runtime/sdk/runtime/datetime.ts +0 -329
  213. package/runtime/sdk/runtime/device.ts +0 -293
  214. package/runtime/sdk/runtime/fetch.ts +0 -77
  215. package/runtime/sdk/runtime/files.ts +0 -23
  216. package/runtime/sdk/runtime/input.ts +0 -175
  217. package/runtime/sdk/runtime/media.ts +0 -111
  218. package/runtime/sdk/runtime/misc.ts +0 -16
  219. package/runtime/sdk/runtime/net.ts +0 -36
  220. package/runtime/sdk/runtime/rpc.ts +0 -218
  221. package/runtime/sdk/runtime/service.ts +0 -83
  222. package/runtime/sdk/runtime/share.ts +0 -9
  223. package/runtime/sdk/runtime/storage.ts +0 -13
  224. package/runtime/sdk/runtime/touch.ts +0 -76
  225. package/runtime/sdk/scene/defineScene.ts +0 -1353
  226. package/runtime/sdk/scene/editorPlugins.ts +0 -110
  227. package/runtime/sdk/scene/gizmos.ts +0 -148
  228. package/runtime/sdk/scene/grammar.ts +0 -120
  229. package/runtime/sdk/scene/material.ts +0 -188
  230. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  231. package/runtime/sdk/server/auth/global.ts +0 -80
  232. package/runtime/sdk/server/auth/host.ts +0 -318
  233. package/runtime/sdk/server/auth/models.ts +0 -83
  234. package/runtime/sdk/server/auth/types.ts +0 -50
  235. package/runtime/sdk/server/channel.ts +0 -56
  236. package/runtime/sdk/server/context.ts +0 -36
  237. package/runtime/sdk/server/db/defineDb.ts +0 -237
  238. package/runtime/sdk/server/db/fields.ts +0 -132
  239. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  240. package/runtime/sdk/server/db/index.ts +0 -7
  241. package/runtime/sdk/server/db/marci/query.ts +0 -412
  242. package/runtime/sdk/server/db/types.ts +0 -202
  243. package/runtime/sdk/server/errors.ts +0 -12
  244. package/runtime/sdk/server/host.ts +0 -74
  245. package/runtime/sdk/server/inject.ts +0 -13
  246. package/runtime/sdk/server/runtime.ts +0 -133
  247. package/runtime/sdk/server/validate.ts +0 -87
  248. package/runtime/sdk/ui/NativeView.ts +0 -142
  249. package/runtime/sdk/ui/UI.ts +0 -39
  250. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  251. package/runtime/sdk/ui/UIButton.ts +0 -101
  252. package/runtime/sdk/ui/UIContainer.ts +0 -60
  253. package/runtime/sdk/ui/UIImage.ts +0 -83
  254. package/runtime/sdk/ui/UIInput.ts +0 -185
  255. package/runtime/sdk/ui/UIModal.ts +0 -139
  256. package/runtime/sdk/ui/UINode.ts +0 -830
  257. package/runtime/sdk/ui/UIPager.ts +0 -362
  258. package/runtime/sdk/ui/UIPopover.ts +0 -100
  259. package/runtime/sdk/ui/UIScreen.ts +0 -123
  260. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  261. package/runtime/sdk/ui/UISpacer.ts +0 -14
  262. package/runtime/sdk/ui/UITabs.ts +0 -236
  263. package/runtime/sdk/ui/UIText.ts +0 -51
  264. package/runtime/sdk/ui/UIVideo.ts +0 -88
  265. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  266. package/runtime/sdk/ui/UIWidget.ts +0 -127
  267. package/runtime/sdk/ui/fonts.ts +0 -13
  268. package/runtime/sdk/ui/presentable.ts +0 -117
  269. package/runtime/sdk/ui/router.ts +0 -132
  270. package/runtime/sdk/ui/theme.ts +0 -84
@@ -1,259 +0,0 @@
1
- // A collider shape, as an aspect on a Node2D. PURE GEOMETRY — attaching a Shape2D does not by itself
2
- // make the node rigid or a sensor; pair it with a Physics2D (rigid body) or a Trigger2D (sensor), both
3
- // of which require a Shape2D and turn its geometry into a Box2D fixture. The shape is also what pointer
4
- // picking hits, so a node is clickable once it has a Shape2D + Physics2D/Trigger2D (a body to query).
5
- //
6
- // wall.aspect(Shape2D, { box: [200, 16] }).aspect(Physics2D, { motion: 'static' })
7
- // goal.aspect(Shape2D, { circle: 20 }).aspect(Trigger2D)
8
- // hero.aspect(Shape2D, { capsule: { from: [0, 6], to: [0, 26], radius: 6 } }).aspect(Physics2D, { motion: 'dynamic' })
9
- //
10
- // With no geometry set, Shape2D derives a box from WHAT THE NODE DRAWS — the sprite's size scaled by
11
- // the node, positioned by its anchor. So `aspect(Shape2D, {})` on a sprite anchored at its feet gives
12
- // a collider that wraps the art instead of one centred on the pivot (half of it underground), which
13
- // is why every character used to carry a hand-computed offset.
14
- //
15
- // RE-CONFIGURING REBUILDS IT IN PLACE: `node.aspect(Shape2D, { capsule: … })` on a live node swaps the
16
- // fixture while the body keeps its id, its velocity and its place in the world. That is what makes a
17
- // crouch (and a live editor edit) possible.
18
-
19
- import { Aspect } from "../core/Aspect"
20
- import { Vec2, cx, cy, type Vec2Like } from "../math/vec"
21
- import { ALL_GROUPS } from "./groups2d"
22
- import type { Node2D } from "./Node2D"
23
-
24
- /** @internal How an owner (Physics2D / Trigger2D) wants the geometry realised. */
25
- export type ShapeFixture2D = {
26
- sensor: boolean
27
- friction: number
28
- bounce: number
29
- category?: number
30
- mask?: number
31
- /** Chains carry no mass, so Box2D only accepts them on a static body. */
32
- staticBody?: boolean
33
- }
34
-
35
- export class Shape2D extends Aspect<"shape", Node2D> {
36
- static readonly aspect = "shape"
37
-
38
- /** Box half-extents [hw, hh] (world units). */
39
- box?: Vec2Like
40
- /** Circle radius. */
41
- circle?: number
42
- /** Capsule between two local points, with a radius. */
43
- capsule?: { from: Vec2Like, to: Vec2Like, radius: number }
44
- /** Thin segment (an edge) between two local points. */
45
- segment?: { from: Vec2Like, to: Vec2Like }
46
- /** Convex polygon, up to 8 local points (a convex hull is computed — a CONCAVE outline silently
47
- * becomes its hull; use `chain` for those). */
48
- polygon?: Vec2Like[]
49
- /**
50
- * A polyline of connected segments — the one shape that expresses a long CONCAVE surface (rolling
51
- * terrain, a cave wall) in one piece, seam-free. Pass only the points that should be solid; the
52
- * tangent points Box2D needs at the ends are synthesised here.
53
- *
54
- * STATIC bodies only (a polyline has no area, so no mass), at least 2 points (4 with `loop`), and it
55
- * must not self-intersect. It is ONE-SIDED: solid on the right of the point order — walk the points
56
- * so that the solid material is on your right, or set `flip`.
57
- */
58
- chain?: Vec2Like[]
59
- /** `chain`: close the contour (an island / a room boundary). Needs at least 4 points. */
60
- loop?: boolean
61
- /** `chain`: put the solid side on the other side of the line. */
62
- flip?: boolean
63
- /** The collider's centre relative to the node, in world units. Applies to EVERY kind — it
64
- * translates a capsule's / segment's / polygon's / chain's points too, unlike the old `offset`. */
65
- origin?: Vec2Like
66
-
67
- /** @internal Geometry as of the last build — an unchanged signature means nothing to rebuild. */
68
- private _sig = ""
69
- /** @internal The last resize the CHARACTER refused (backs `controller.resizing`). */
70
- _resizeRefused = false
71
- /** @internal How the owner asked for it, so a rebuild can replay the same fixture. */
72
- private _fx: ShapeFixture2D | null = null
73
- private _handle = 0
74
-
75
- /** @internal — the character claimed this shape; record the geometry so a later re-configure is
76
- * compared against it. */
77
- _claimedByCharacter(): void { this._sig = this._signature(); this._resizeRefused = false }
78
-
79
- /** @internal — realise this geometry as a fixture on `handle` (a body from Physics2D/Trigger2D). */
80
- _addTo(handle: number, fx: ShapeFixture2D): void {
81
- this._handle = handle
82
- this._fx = fx
83
- this._sig = this._signature()
84
- this._create()
85
- }
86
-
87
- /** @internal — the owner's material changed live; keep it for the next rebuild. */
88
- _setMaterial(friction: number, bounce: number): void {
89
- if (this._fx) { this._fx.friction = friction; this._fx.bounce = bounce }
90
- }
91
-
92
- /** @internal — the owner's filter changed live. */
93
- _setFilter(category: number, mask: number): void {
94
- if (this._fx) { this._fx.category = category; this._fx.mask = mask }
95
- }
96
-
97
- onDetach(): void { this._handle = 0; this._fx = null; this._sig = ""; this._resizeRefused = false }
98
-
99
- /**
100
- * Re-configuring the aspect swaps the fixture in place — same body, same id, same velocity, same
101
- * position. Nothing happens when the geometry is unchanged, so re-applying the same options in a
102
- * loop is free.
103
- */
104
- onReconfigure(): void {
105
- const sig = this._signature()
106
- if (sig === this._sig) return
107
-
108
- // A CharacterController2D owns this shape instead of a body: resizing goes to the mover, which
109
- // VALIDATES it (you cannot stand up under a low pipe) and can refuse.
110
- if (this.node._xfKind === 1) {
111
- const cap = this._capsule()
112
- const ok = _creator2d.characterSetCapsule(this.node._xf, cap.x1, cap.y1, cap.x2, cap.y2, cap.radius)
113
- // On refusal NOTHING changed — so DON'T commit the signature. The next identical
114
- // `aspect(Shape2D, …)` then still sees a difference and retries; committing here would swallow
115
- // every retry for good, which is the whole trap in this design.
116
- this._resizeRefused = !ok
117
- if (ok) this._sig = sig
118
- return
119
- }
120
-
121
- if (!this._handle || !this._fx) return
122
- this._sig = sig
123
- _creator2d.physicsClearShapes(this._handle)
124
- this._create()
125
- }
126
-
127
- /**
128
- * @internal — this geometry as a CAPSULE, for CharacterController2D (the mover takes nothing else).
129
- * An explicit capsule is used as given; anything else is derived from the drawn rect, so
130
- * `aspect(Shape2D, {})` on a sprite gives a capsule that wraps the art.
131
- */
132
- _capsule(): { x1: number, y1: number, x2: number, y2: number, radius: number } {
133
- const ox = this.origin ? cx(this.origin) : 0
134
- const oy = this.origin ? cy(this.origin) : 0
135
- if (this.capsule) {
136
- const { from, to, radius } = this.capsule
137
- return { x1: cx(from) + ox, y1: cy(from) + oy, x2: cx(to) + ox, y2: cy(to) + oy, radius }
138
- }
139
- if (this.circle !== undefined) return { x1: ox, y1: oy, x2: ox, y2: oy, radius: this.circle }
140
- let hw = 8, hh = 8, bx = ox, by = oy
141
- if (this.box) { hw = cx(this.box); hh = cy(this.box) }
142
- else {
143
- const a = this._autoBox()
144
- hw = a.hw; hh = a.hh
145
- if (!this.origin) { bx = a.x; by = a.y }
146
- }
147
- const radius = Math.min(hw, hh)
148
- const half = Math.max(0, hh - radius)
149
- return { x1: bx, y1: by - half, x2: bx, y2: by + half, radius }
150
- }
151
-
152
- // ---- internals ---------------------------------------------------------------------------------
153
-
154
- private _create(): void {
155
- const fx = this._fx!
156
- const handle = this._handle
157
- const category = fx.category ?? ALL_GROUPS
158
- const mask = fx.mask ?? ALL_GROUPS
159
- const ox = this.origin ? cx(this.origin) : 0
160
- const oy = this.origin ? cy(this.origin) : 0
161
-
162
- if (this.chain) {
163
- this._createChain(handle, fx, category, mask, ox, oy)
164
- } else if (this.circle !== undefined) {
165
- _creator2d.physicsAddCircle(handle, this.circle, ox, oy, fx.friction, fx.bounce, fx.sensor, category, mask)
166
- } else if (this.capsule) {
167
- const { from, to, radius } = this.capsule
168
- _creator2d.physicsAddCapsule(handle, cx(from) + ox, cy(from) + oy, cx(to) + ox, cy(to) + oy, radius,
169
- fx.friction, fx.bounce, fx.sensor, category, mask)
170
- } else if (this.segment) {
171
- const { from, to } = this.segment
172
- _creator2d.physicsAddSegment(handle, cx(from) + ox, cy(from) + oy, cx(to) + ox, cy(to) + oy,
173
- fx.friction, fx.bounce, category, mask)
174
- } else if (this.polygon) {
175
- const pts = new Float32Array(this.polygon.length * 2)
176
- this.polygon.forEach((p, i) => { pts[i * 2] = cx(p) + ox; pts[i * 2 + 1] = cy(p) + oy })
177
- _creator2d.physicsAddPolygon(handle, pts, this.polygon.length, fx.friction, fx.bounce, fx.sensor, category, mask)
178
- } else {
179
- // box: explicit half-extents, or "auto" — the rect the node actually draws.
180
- let hw = 8, hh = 8, bx = ox, by = oy
181
- if (this.box) {
182
- hw = cx(this.box); hh = cy(this.box)
183
- } else {
184
- const auto = this._autoBox()
185
- hw = auto.hw; hh = auto.hh
186
- if (!this.origin) { bx = auto.x; by = auto.y } // an explicit origin still wins
187
- }
188
- _creator2d.physicsAddBox(handle, hw, hh, bx, by, fx.friction, fx.bounce, fx.sensor, category, mask)
189
- }
190
- }
191
-
192
- /**
193
- * Box2D's chain wants the two TANGENT points as extra entries and does not collide on its first or
194
- * last edge — pass it your outline verbatim and you lose both end segments (four points give ONE).
195
- * So the caller passes only the solid points and the ends are extrapolated here: `p0 + (p0 - p1)`.
196
- * A loop needs none of that (every point is interior) but does need 4 of them.
197
- */
198
- private _createChain(handle: number, fx: ShapeFixture2D, category: number, mask: number, ox: number, oy: number): void {
199
- const src = this.chain!
200
- const loop = this.loop === true
201
- if (src.length < (loop ? 4 : 2)) {
202
- throw new Error(`Shape2D chain needs at least ${loop ? 4 : 2} points (got ${src.length})${loop ? " — a closed contour of 3 is a polygon" : ""}`)
203
- }
204
- if (fx.staticBody === false) {
205
- throw new Error("Shape2D { chain } works on a STATIC body only — a polyline has no area, so no mass. Use polygon/box for a dynamic body.")
206
- }
207
- const order = this.flip === true ? [ ...src ].reverse() : src
208
- const p: number[] = []
209
- if (!loop) {
210
- // leading ghost: mirror the first point across the second
211
- p.push(2 * cx(order[0]) - cx(order[1]) + ox, 2 * cy(order[0]) - cy(order[1]) + oy)
212
- }
213
- for (const v of order) p.push(cx(v) + ox, cy(v) + oy)
214
- if (!loop) {
215
- const n = order.length
216
- p.push(2 * cx(order[n - 1]) - cx(order[n - 2]) + ox, 2 * cy(order[n - 1]) - cy(order[n - 2]) + oy)
217
- }
218
- _creator2d.physicsAddChain(handle, new Float32Array(p), p.length / 2, loop, fx.friction, fx.bounce, category, mask)
219
- }
220
-
221
- // Everything that changes the GEOMETRY, so a re-configure that only touches the material is free.
222
- private _signature(): string {
223
- const o = this.origin ? `${cx(this.origin)},${cy(this.origin)}` : ""
224
- if (this.chain) return `chain:${this.chain.map(v => `${cx(v)},${cy(v)}`).join(";")}|${this.loop}|${this.flip}|${o}`
225
- if (this.circle !== undefined) return `circle:${this.circle}|${o}`
226
- if (this.capsule) {
227
- const { from, to, radius } = this.capsule
228
- return `cap:${cx(from)},${cy(from)},${cx(to)},${cy(to)},${radius}|${o}`
229
- }
230
- if (this.segment) {
231
- const { from, to } = this.segment
232
- return `seg:${cx(from)},${cy(from)},${cx(to)},${cy(to)}|${o}`
233
- }
234
- if (this.polygon) return `poly:${this.polygon.map(v => `${cx(v)},${cy(v)}`).join(";")}|${o}`
235
- if (this.box) return `box:${cx(this.box)},${cy(this.box)}|${o}`
236
- const a = this._autoBox()
237
- return `auto:${a.hw},${a.hh},${a.x},${a.y}|${o}`
238
- }
239
-
240
- /**
241
- * @internal — half-extents and centre of the rect this node DRAWS, in the node's own frame.
242
- *
243
- * Sprite anchor space is Y-DOWN while the world is Y-up (see docs/conventions.md), so `anchor: [.5, 1]`
244
- * ("feet") puts the art ABOVE the origin: its centre sits at +h/2. A node with no sprite falls back
245
- * to 8x8 at the origin. This is a SNAPSHOT taken when the fixture is built — growing the sprite later
246
- * does not resize the collider; re-configure the aspect to re-measure.
247
- */
248
- private _autoBox(): { hw: number, hh: number, x: number, y: number } {
249
- const n = this.node as { size?: Vec2, anchor?: Vec2, scale?: Vec2 }
250
- const size = n.size
251
- if (!size || !(size.x > 0) || !(size.y > 0)) return { hw: 8, hh: 8, x: 0, y: 0 }
252
- const sx = n.scale ? Math.abs(n.scale.x) : 1
253
- const sy = n.scale ? Math.abs(n.scale.y) : 1
254
- const w = size.x * sx, h = size.y * sy
255
- const ax = n.anchor ? n.anchor.x : 0.5
256
- const ay = n.anchor ? n.anchor.y : 0.5
257
- return { hw: w * 0.5, hh: h * 0.5, x: (0.5 - ax) * w, y: (ay - 0.5) * h }
258
- }
259
- }
@@ -1,89 +0,0 @@
1
- // A textured 2D node. Cheap setters proxy straight to the native sprite; the data lives in
2
- // creator-2d. Sprite-sheet animation is no longer built in — attach the SpriteAnimation aspect:
3
- // new Sprite({ texture }).aspect(SpriteAnimation, { size: [32, 48], idle: [0,1,2,3] }).anim.play('idle')
4
-
5
- import { Color, type ColorInput } from "../core/color"
6
- import { Vec2, type Vec2Like } from "../math/vec"
7
- import { Canvas, isCanvas } from "../canvas/Canvas"
8
- import { Node2D } from "./Node2D"
9
- import type { Texture2D } from "./Texture2D"
10
-
11
- /** Anything that can back a sprite: a texture, or a Canvas (baked to a texture on assign). */
12
- export type TextureSource = Texture2D | Canvas
13
-
14
- export type SpriteOptions = {
15
- texture?: TextureSource
16
- /** Pivot, [0,0] = top-left .. [1,1] = bottom-right. Use [0.5, 1] (feet) for Y-sorted depth. */
17
- anchor?: Vec2Like
18
- /** World size; defaults to the texture's pixel dimensions. */
19
- size?: Vec2Like
20
- /** Normalized UV sub-rect [u0,v0,u1,v1]. */
21
- frame?: [number, number, number, number]
22
- /** Tint multiplied with the texture. */
23
- color?: ColorInput
24
- opacity?: number
25
- layer?: number
26
- position?: Vec2Like
27
- }
28
-
29
- export class Sprite extends Node2D {
30
- private _texture?: Texture2D
31
- private _anchor = new Vec2(0.5, 0.5)
32
- private _color: ColorInput = 0xffffff
33
- private _opacity = 1
34
- private _flipX = false
35
- private _flipY = false
36
- private _size: Vec2 | null = null
37
-
38
- constructor(options: SpriteOptions = {}) {
39
- super()
40
- if (options.texture) this.texture = options.texture
41
- if (options.size) this.size = options.size
42
- if (options.anchor) this.anchor = options.anchor
43
- if (options.frame) this.frame = options.frame
44
- if (options.color !== undefined) this.color = options.color
45
- if (options.opacity !== undefined) this.opacity = options.opacity
46
- if (options.layer !== undefined) this.layer = options.layer
47
- if (options.position) this.position = options.position
48
- }
49
-
50
- get texture(): Texture2D | undefined { return this._texture }
51
- set texture(t: TextureSource | undefined) {
52
- const tex = isCanvas(t) ? t.texture() : t // a Canvas bakes to a texture on assign
53
- this._texture = tex
54
- if (!tex) return
55
- _creator2d.setSprite(this.id, tex.id) // native resets UV + size to the full (pixel) texture
56
- // A Canvas is authored in LOGICAL units but baked at logical × pixelRatio, so default the sprite's
57
- // WORLD size to the canvas's LOGICAL size — pixelRatio then only affects crispness, not on-screen
58
- // size. (A plain texture keeps the native default: the texture's pixel dimensions.)
59
- if (!this._size && isCanvas(t)) this._size = new Vec2(t.width, t.height)
60
- if (this._size) _creator2d.setSpriteSize(this.id, this._size.x, this._size.y)
61
- }
62
-
63
- /** Current world size: explicit override if set, else the texture's pixel dimensions (a Canvas-backed
64
- * sprite defaults to the canvas's LOGICAL size, so pixelRatio is crispness only, not size). */
65
- get size(): Vec2 { return this._size ?? (this._texture ? new Vec2(this._texture.width, this._texture.height) : new Vec2(0, 0)) }
66
- set size(v: Vec2Like) { this._size = new Vec2(v); _creator2d.setSpriteSize(this.id, this._size.x, this._size.y) }
67
-
68
- get anchor(): Vec2 { return this._anchor.clone() }
69
- set anchor(v: Vec2Like) { this._anchor = new Vec2(v); _creator2d.setSpriteAnchor(this.id, this._anchor.x, this._anchor.y) }
70
-
71
- /** Set the UV sub-rect (normalized). */
72
- set frame(uv: [number, number, number, number]) { _creator2d.setSpriteFrame(this.id, uv[0], uv[1], uv[2], uv[3]) }
73
- /** Set the sub-rect in texture pixels. */
74
- setFramePx(px: number, py: number, pw: number, ph: number): this {
75
- _creator2d.setSpriteFramePx(this.id, px, py, pw, ph)
76
- return this
77
- }
78
-
79
- get color(): ColorInput { return this._color }
80
- set color(c: ColorInput) { this._color = c; const [ r, g, b ] = Color.toRgb01(c); _creator2d.setSpriteColor(this.id, r, g, b) }
81
-
82
- get opacity(): number { return this._opacity }
83
- set opacity(a: number) { this._opacity = a; _creator2d.setSpriteOpacity(this.id, a) }
84
-
85
- get flipX(): boolean { return this._flipX }
86
- set flipX(v: boolean) { this._flipX = v; _creator2d.setSpriteFlip(this.id, v, this._flipY) }
87
- get flipY(): boolean { return this._flipY }
88
- set flipY(v: boolean) { this._flipY = v; _creator2d.setSpriteFlip(this.id, this._flipX, v) }
89
- }
@@ -1,171 +0,0 @@
1
- // Sprite-sheet animation as an aspect on a Sprite. Configured as DATA (frame size + named clips) in
2
- // the attach call, then driven by name:
3
- // sprite.aspect(SpriteAnimation, { size: [32, 48], fps: 10, clips: { idle: [0,1], walk: [4,5,6,7] } })
4
- // sprite.anim.play('walk')
5
- // Once defined, clips advance entirely in native code; the aspect just translates clip ids back to
6
- // names for the node's 'loopReached' / 'completed' events.
7
- //
8
- // Directional sheets (one texture row per facing): set `directions` in row order and mark clips
9
- // `perDirection` — each expands to `name_DIR` with the frames offset by that row. Then
10
- // `play('walk', 'SE')` or `play('walk', moveVector)` picks the row (compass tokens: E/NE/N/NW/W/SW/S/SE),
11
- // and the last direction sticks, so a later `play('idle')` keeps facing.
12
- //
13
- // The native clips need the texture's dimensions, so when the Sprite has no texture yet (SpriteSheet's
14
- // deferred load), the define is postponed: play() calls queue by name and start once define() runs.
15
-
16
- import { Aspect } from "../core/Aspect"
17
- import { cx, cy, type Vec2Like } from "../math/vec"
18
- import { ensureAnimEvents } from "./loop"
19
- import type { Sprite } from "./Sprite"
20
-
21
- /** Per-clip frame list, or an object for per-clip fps/loop/direction overrides. */
22
- export type Clip = number[] | { frames: number[], fps?: number, loop?: boolean, perDirection?: boolean }
23
-
24
- // Compass angles in Y-up world degrees (E = +x, N = +y) for vector → direction resolution.
25
- const COMPASS: Record<string, number> = { E: 0, NE: 45, N: 90, NW: 135, W: 180, SW: 225, S: 270, SE: 315 }
26
-
27
- export class SpriteAnimation extends Aspect<"anim", Sprite> {
28
- static readonly aspect = "anim"
29
-
30
- /** Grid cell size in texture pixels (defaults to the full texture). */
31
- size?: [number, number]
32
- /** Grid columns override — for sheets with padding; defaults to floor(texWidth / cellWidth). */
33
- cols?: number
34
- /** Pixel origin of the grid in the texture — cells index from here (a SpriteSheet region). */
35
- origin?: [number, number]
36
- /** Default fps for clips that don't override it. */
37
- fps = 12
38
- /** Default loop for clips that don't override it. */
39
- loop = true
40
- /** Named clips; frame indices are row-major into the grid. Set via the attach opts. */
41
- clips: Record<string, Clip> = {}
42
- /** Facing names in texture-row order (row i = directions[i]) for `perDirection` clips. */
43
- directions?: string[]
44
-
45
- private _clipIds: Record<string, number> = {}
46
- private _clipNames: Record<number, string> = {}
47
- private _current: string | null = null
48
- private _defined = false
49
- private _queued: string | null = null
50
- private _dir: string | null = null
51
-
52
- onAttach(): void {
53
- // No texture yet (deferred sheet load): postpone the native define — SpriteSheet (or the user)
54
- // calls define() after assigning the texture; play() calls queue until then.
55
- if (this.node.texture) this._define()
56
- // route native animation events for this entity to named 'loopReached'/'completed' events.
57
- ;(this.node as { _dispatchAnimEvent: (clipId: number, type: number) => void })._dispatchAnimEvent =
58
- (clipId, type) => this.node._emitAnim(type === 1 ? "completed" : "loopReached", this._clipNames[clipId] ?? "")
59
- ensureAnimEvents()
60
- }
61
-
62
- /** (Re)build native clips from `this.clips`. Called on attach; call again after changing clips. */
63
- define(): this { this._define(); return this }
64
-
65
- private _define(): void {
66
- const node = this.node
67
- const tex = node.texture
68
- if (!tex) throw new Error("SpriteAnimation: set a texture on the Sprite before defining clips")
69
- const [ fw, fh ] = this.size ?? [ tex.width, tex.height ]
70
- const cols = this.cols ?? Math.max(1, Math.floor(tex.width / fw))
71
- const [ ox, oy ] = this.origin ?? [ 0, 0 ]
72
-
73
- const defineOne = (name: string, indices: number[], fps: number, loop: boolean): void => {
74
- const frames = new Float32Array(indices.length * 4)
75
- indices.forEach((idx, i) => {
76
- const px = ox + (idx % cols) * fw
77
- const py = oy + Math.floor(idx / cols) * fh
78
- frames[i * 4 + 0] = px / tex.width
79
- frames[i * 4 + 1] = py / tex.height
80
- frames[i * 4 + 2] = (px + fw) / tex.width
81
- frames[i * 4 + 3] = (py + fh) / tex.height
82
- })
83
- const clipId = _creator2d.defineAnimation(node.id, frames, fps, loop)
84
- this._clipIds[name] = clipId
85
- this._clipNames[clipId] = name
86
- }
87
-
88
- for (const name of Object.keys(this.clips)) {
89
- const value = this.clips[name]
90
- const indices = Array.isArray(value) ? value : value.frames
91
- const fps = Array.isArray(value) ? this.fps : (value.fps ?? this.fps)
92
- const loop = Array.isArray(value) ? this.loop : (value.loop ?? this.loop)
93
- const dirs = !Array.isArray(value) && value.perDirection ? this.directions : undefined
94
-
95
- if (dirs && dirs.length > 0) {
96
- // one clip per facing: row i's frames are the base (row-0) frames shifted down i rows.
97
- dirs.forEach((dir, row) => defineOne(`${name}_${dir}`, indices.map((f) => f + row * cols), fps, loop))
98
- } else {
99
- defineOne(name, indices, fps, loop)
100
- }
101
- }
102
- if (this.size) _creator2d.setSpriteSize(node.id, fw, fh)
103
- this._defined = true
104
-
105
- if (this._queued) {
106
- const queued = this._queued
107
- this._queued = null
108
- const id = this._clipIds[queued]
109
- if (id === undefined) throw new Error(`SpriteAnimation.play(): unknown clip "${queued}"`)
110
- _creator2d.playAnimation(node.id, id)
111
- }
112
- }
113
-
114
- /**
115
- * Play a clip by name. Re-playing the active clip is a no-op (keeps it running smoothly).
116
- * For a `perDirection` clip, `dir` picks the facing — a direction name or a movement vector
117
- * (nearest compass row wins); omitted, the last direction (or the first row) is kept.
118
- */
119
- play(name: string, dir?: string | Vec2Like): this {
120
- let target = name
121
- const clip = this.clips[name]
122
- if (clip && !Array.isArray(clip) && clip.perDirection && this.directions?.length) {
123
- const resolved = this._resolveDir(dir)
124
- if (resolved) { this._dir = resolved; target = `${name}_${resolved}` }
125
- }
126
- if (this._current === target) return this
127
- this._current = target
128
- if (!this._defined) { this._queued = target; return this }
129
- const id = this._clipIds[target]
130
- if (id === undefined) throw new Error(`SpriteAnimation.play(): unknown clip "${target}"`)
131
- _creator2d.playAnimation(this.node.id, id)
132
- return this
133
- }
134
-
135
- stop(): this {
136
- this._current = null
137
- this._queued = null
138
- if (this._defined) _creator2d.stopAnimation(this.node.id)
139
- return this
140
- }
141
-
142
- /** Current facing (last direction resolved by play()), or null before the first directional play. */
143
- get direction(): string | null { return this._dir }
144
-
145
- set speed(value: number) { _creator2d.setAnimationSpeed(this.node.id, value) }
146
- get current(): string | null { return this._current }
147
- get frame(): number { return _creator2d.getAnimationFrame(this.node.id) }
148
-
149
- // A direction name passes through; a vector resolves to the nearest compass row this sheet has.
150
- // A zero vector (standing still) keeps the current facing.
151
- private _resolveDir(dir?: string | Vec2Like): string | null {
152
- const dirs = this.directions!
153
- if (typeof dir === "string") return dir
154
- if (dir !== undefined) {
155
- const x = cx(dir), y = cy(dir)
156
- if (x !== 0 || y !== 0) {
157
- const angle = (Math.atan2(y, x) * 180 / Math.PI + 360) % 360
158
- let best: string | null = null
159
- let bestDist = Infinity
160
- for (const d of dirs) {
161
- const a = COMPASS[d]
162
- if (a === undefined) continue
163
- const dist = Math.min(Math.abs(a - angle), 360 - Math.abs(a - angle))
164
- if (dist < bestDist) { bestDist = dist; best = d }
165
- }
166
- if (best) return best
167
- }
168
- }
169
- return this._dir ?? dirs[0] ?? null
170
- }
171
- }