lecodes-sdk 0.20.0 → 0.20.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 (64) hide show
  1. package/dist/global.d.ts +31 -0
  2. package/dist/inject.js +361 -260
  3. package/dist/types/audio/Bus.d.ts +45 -0
  4. package/dist/types/audio/Sound.d.ts +28 -0
  5. package/dist/types/audio/Voice.d.ts +27 -0
  6. package/dist/types/audio/audio.d.ts +83 -0
  7. package/dist/types/audio/support.d.ts +1 -0
  8. package/dist/types/gl/AudioSource.d.ts +60 -0
  9. package/dist/types/gl/AudioZone.d.ts +32 -0
  10. package/dist/types/gl/DecalSet.d.ts +103 -0
  11. package/dist/types/gl/Geometry.d.ts +5 -0
  12. package/dist/types/gl/Light.d.ts +7 -0
  13. package/dist/types/gl/Material.d.ts +86 -2
  14. package/dist/types/gl/Mesh.d.ts +11 -0
  15. package/dist/types/gl/Scene.d.ts +23 -0
  16. package/dist/types/gl/SceneAudio.d.ts +11 -0
  17. package/dist/types/gl/Texture.d.ts +29 -1
  18. package/dist/types/gl/animation/AnimationClip.d.ts +25 -12
  19. package/dist/types/gl/animation/core.d.ts +15 -10
  20. package/dist/types/gl/state.d.ts +0 -1
  21. package/dist/types/inject.d.ts +10 -0
  22. package/dist/types/runtime/input.d.ts +11 -0
  23. package/dist/types/ui/UIImage.d.ts +15 -5
  24. package/dist/types.json +1 -1
  25. package/package.json +1 -1
  26. package/prompts/dist/2d-game.md +408 -197
  27. package/prompts/dist/3d-app.md +491 -166
  28. package/prompts/dist/ar-app.md +373 -163
  29. package/prompts/dist/design.md +83 -87
  30. package/prompts/dist/ui-app.md +325 -136
  31. package/src/audio/Bus.ts +102 -0
  32. package/src/audio/Sound.ts +96 -0
  33. package/src/audio/Voice.ts +102 -0
  34. package/src/audio/audio.ts +161 -0
  35. package/src/audio/support.ts +6 -0
  36. package/src/bridges.d.ts +1481 -1352
  37. package/src/compile/compileProject.ts +30 -15
  38. package/src/compile/index.ts +3 -0
  39. package/src/core/Aspect.ts +33 -8
  40. package/src/g2/Scene2D.ts +7 -0
  41. package/src/gl/AudioSource.ts +113 -0
  42. package/src/gl/AudioZone.ts +75 -0
  43. package/src/gl/CameraPlace.ts +52 -52
  44. package/src/gl/DecalSet.ts +233 -0
  45. package/src/gl/Geometry.ts +5 -0
  46. package/src/gl/Light.ts +16 -0
  47. package/src/gl/Lightmap.ts +3 -2
  48. package/src/gl/Material.ts +152 -4
  49. package/src/gl/Mesh.ts +20 -1
  50. package/src/gl/Ragdoll.ts +270 -270
  51. package/src/gl/Scene.ts +41 -7
  52. package/src/gl/SceneAudio.ts +26 -0
  53. package/src/gl/Texture.ts +43 -3
  54. package/src/gl/Trigger.ts +45 -45
  55. package/src/gl/Vehicle.ts +5 -5
  56. package/src/gl/animation/AnimationClip.ts +43 -20
  57. package/src/gl/animation/Animator.ts +4 -3
  58. package/src/gl/animation/core.ts +20 -15
  59. package/src/gl/scenarios.ts +291 -291
  60. package/src/gl/state.ts +1 -1
  61. package/src/inject.ts +12 -0
  62. package/src/runtime/input.ts +6 -1
  63. package/src/scene/gizmos.ts +148 -148
  64. package/src/ui/UIImage.ts +21 -7
@@ -1,148 +1,148 @@
1
- // Editor gizmos: immediate-mode line drawing for code that runs while a scene is EDITED — a
2
- // generator aspect's `rebuild()`, a `make()` factory, an editor tool's hooks. Nothing here touches
3
- // the scene: calls accumulate WORLD-space line segments into the buffer of the run that is
4
- // currently executing (an ambient scope the scene loader opens around each synchronous call), and
5
- // the scene editor pushes those buffers to the lite engine's overlay layer, which draws them over
6
- // the picture — never outlined, never picked, never rendered by Filament, absent in play mode.
7
- //
8
- // rebuild() {
9
- // Gizmos.polyline(points, { color: "#5b8ef0", closed: true })
10
- // for (const p of points) Gizmos.cross(p, 0.07)
11
- // }
12
- //
13
- // Every run starts with an EMPTY buffer — what a call draws is the whole picture (like
14
- // `generated.clear()`, only implicit). Outside any scope (play mode, hand-attached aspects, a call
15
- // after an `await` inside rebuild) the calls are no-ops: rebuild is synchronous by contract.
16
- //
17
- // ANCHORED gizmos (`{ node }` in the style): points are in that node's LOCAL frame and the engine
18
- // follows the node live — through a gizmo drag, not only after it — with the node's scale
19
- // stripped (an empty scaled ×10 for its children keeps a normal-sized cross). An anchored picture
20
- // is the node's own: clicking it in the viewport selects the node, and it draws in the selection
21
- // accent while selected. World-space calls (path lines) are never pickable.
22
-
23
- import { Mat4, type Mat4Like } from "../math/mat4"
24
- import type { Vec3Like } from "../math/vec"
25
-
26
- /** The anchor of a gizmo call: any object with an engine entity id (a `Node`). */
27
- export type GizmoAnchor = { readonly id: number }
28
-
29
- export type GizmoStyle = {
30
- /** CSS hex color (`#rgb` / `#rrggbb`); default a neutral light gray. */
31
- color?: string
32
- /** 0..1, default 1. */
33
- alpha?: number
34
- /** Anchor: points are in this node's LOCAL frame (scale ignored); the lines follow the node
35
- * live, are pickable (a click selects the node) and turn the selection accent when it is
36
- * selected. Omit for world-space lines (not pickable). */
37
- node?: GizmoAnchor | null
38
- }
39
-
40
- /** One color batch of LINES (flat `[x,y,z, x,y,z]` per segment) — what a host hands the engine.
41
- * With `entityId` the segments are in that entity's local frame (see GizmoStyle.node). */
42
- export type GizmoBatch = { color: string, alpha: number, segments: number[], entityId?: number }
43
-
44
- const DEFAULT_COLOR = "#cfd4dd"
45
-
46
- /** The per-run collector (owned by the scene loader's EditorRun). Batches are keyed by style so a
47
- * hundred same-colored segments cost one draw. */
48
- export class GizmoBuffer {
49
- batches: GizmoBatch[] = []
50
- private _byStyle = new Map<string, GizmoBatch>()
51
-
52
- clear(): void {
53
- this.batches = []
54
- this._byStyle.clear()
55
- }
56
-
57
- segments(style: GizmoStyle | undefined): number[] {
58
- const color = style?.color ?? DEFAULT_COLOR
59
- const alpha = Math.max(0, Math.min(1, style?.alpha ?? 1))
60
- const entityId = style?.node?.id
61
- const key = `${color}@${alpha}@${entityId ?? ""}`
62
- let b = this._byStyle.get(key)
63
- if (!b) {
64
- b = entityId ? { color, alpha, segments: [], entityId } : { color, alpha, segments: [] }
65
- this._byStyle.set(key, b)
66
- this.batches.push(b)
67
- }
68
- return b.segments
69
- }
70
- }
71
-
72
- let current: GizmoBuffer | null = null
73
- let warnedOutOfScope = false
74
-
75
- const xyz = (p: Vec3Like): [number, number, number] =>
76
- Array.isArray(p) ? [ p[0] ?? 0, p[1] ?? 0, p[2] ?? 0 ] : [ (p as { x: number }).x, (p as { y: number }).y, (p as { z: number }).z ]
77
-
78
- const target = (style: GizmoStyle | undefined): number[] | null => {
79
- if (current) return current.segments(style)
80
- if (!warnedOutOfScope && (globalThis as { __lecodesSceneEdit?: boolean }).__lecodesSceneEdit) {
81
- warnedOutOfScope = true
82
- console.warn("[Gizmos] draw call outside an editor run — gizmos must be drawn synchronously inside rebuild() / a make() factory / a tool hook")
83
- }
84
- return null
85
- }
86
-
87
- /** @internal Run `fn` with `buffer` as the ambient gizmo target (cleared first). Nested scopes
88
- * restore the outer one. */
89
- export const withGizmoScope = <T>(buffer: GizmoBuffer, fn: () => T): T => {
90
- const prev = current
91
- buffer.clear()
92
- current = buffer
93
- try { return fn() } finally { current = prev }
94
- }
95
-
96
- /**
97
- * Editor-only line drawing, available inside generator `rebuild()`, `make()` factories and editor
98
- * tool hooks. Points are WORLD space. Drawn by the scene editor's viewport overlay (depth-test
99
- * off — a path through a wall is still a path); invisible everywhere else.
100
- */
101
- export const Gizmos = {
102
- /** One segment from `a` to `b`. */
103
- line(a: Vec3Like, b: Vec3Like, style?: GizmoStyle): void {
104
- const out = target(style)
105
- if (!out) return
106
- out.push(...xyz(a), ...xyz(b))
107
- },
108
-
109
- /** Consecutive segments through `points`; `closed` joins the last point back to the first. */
110
- polyline(points: readonly Vec3Like[], style?: GizmoStyle & { closed?: boolean }): void {
111
- if (points.length < 2) return
112
- const out = target(style)
113
- if (!out) return
114
- const n = points.length
115
- const last = style?.closed ? n : n - 1
116
- for (let i = 0; i < last; i++) out.push(...xyz(points[i]!), ...xyz(points[(i + 1) % n]!))
117
- },
118
-
119
- /** A wireframe camera frustum looking down −Z: the four edges from the origin to a rect
120
- * `length` metres ahead sized by `fov` (vertical, degrees) × `aspect` (default 16:9), plus an
121
- * "up" fin above the rect. Anchor it (`{ node }`) for a camera node's marker — the frustum
122
- * then follows the node's pose; `matrix` places an unanchored one in world space. */
123
- frustum(fov: number, style?: GizmoStyle & { aspect?: number, length?: number, matrix?: Mat4Like }): void {
124
- const out = target(style)
125
- if (!out) return
126
- const aspect = style?.aspect ?? 16 / 9, length = style?.length ?? 0.6
127
- const m = style?.matrix ? new Mat4(style.matrix) : null
128
- const h = Math.tan((fov * Math.PI) / 360) * length, w = h * aspect, z = -length
129
- const P = (x: number, y: number, zz: number): [number, number, number] => {
130
- if (!m) return [ x, y, zz ]
131
- const v = m.transformPoint([ x, y, zz ])
132
- return [ v.x, v.y, v.z ]
133
- }
134
- const o = P(0, 0, 0)
135
- const c = [ P(-w, -h, z), P(w, -h, z), P(w, h, z), P(-w, h, z) ]
136
- for (const q of c) out.push(...o, ...q)
137
- for (let i = 0; i < 4; i++) out.push(...c[i]!, ...c[(i + 1) % 4]!)
138
- out.push(...P(-w * 0.4, h, z), ...P(0, h * 1.6, z), ...P(0, h * 1.6, z), ...P(w * 0.4, h, z))
139
- },
140
-
141
- /** A three-axis cross centred on `p` (a point marker); `size` = half extent, default 0.1. */
142
- cross(p: Vec3Like, size = 0.1, style?: GizmoStyle): void {
143
- const out = target(style)
144
- if (!out) return
145
- const [ x, y, z ] = xyz(p)
146
- out.push(x - size, y, z, x + size, y, z, x, y - size, z, x, y + size, z, x, y, z - size, x, y, z + size)
147
- },
148
- }
1
+ // Editor gizmos: immediate-mode line drawing for code that runs while a scene is EDITED — a
2
+ // generator aspect's `rebuild()`, a `make()` factory, an editor tool's hooks. Nothing here touches
3
+ // the scene: calls accumulate WORLD-space line segments into the buffer of the run that is
4
+ // currently executing (an ambient scope the scene loader opens around each synchronous call), and
5
+ // the scene editor pushes those buffers to the lite engine's overlay layer, which draws them over
6
+ // the picture — never outlined, never picked, never rendered by Filament, absent in play mode.
7
+ //
8
+ // rebuild() {
9
+ // Gizmos.polyline(points, { color: "#5b8ef0", closed: true })
10
+ // for (const p of points) Gizmos.cross(p, 0.07)
11
+ // }
12
+ //
13
+ // Every run starts with an EMPTY buffer — what a call draws is the whole picture (like
14
+ // `generated.clear()`, only implicit). Outside any scope (play mode, hand-attached aspects, a call
15
+ // after an `await` inside rebuild) the calls are no-ops: rebuild is synchronous by contract.
16
+ //
17
+ // ANCHORED gizmos (`{ node }` in the style): points are in that node's LOCAL frame and the engine
18
+ // follows the node live — through a gizmo drag, not only after it — with the node's scale
19
+ // stripped (an empty scaled ×10 for its children keeps a normal-sized cross). An anchored picture
20
+ // is the node's own: clicking it in the viewport selects the node, and it draws in the selection
21
+ // accent while selected. World-space calls (path lines) are never pickable.
22
+
23
+ import { Mat4, type Mat4Like } from "../math/mat4"
24
+ import type { Vec3Like } from "../math/vec"
25
+
26
+ /** The anchor of a gizmo call: any object with an engine entity id (a `Node`). */
27
+ export type GizmoAnchor = { readonly id: number }
28
+
29
+ export type GizmoStyle = {
30
+ /** CSS hex color (`#rgb` / `#rrggbb`); default a neutral light gray. */
31
+ color?: string
32
+ /** 0..1, default 1. */
33
+ alpha?: number
34
+ /** Anchor: points are in this node's LOCAL frame (scale ignored); the lines follow the node
35
+ * live, are pickable (a click selects the node) and turn the selection accent when it is
36
+ * selected. Omit for world-space lines (not pickable). */
37
+ node?: GizmoAnchor | null
38
+ }
39
+
40
+ /** One color batch of LINES (flat `[x,y,z, x,y,z]` per segment) — what a host hands the engine.
41
+ * With `entityId` the segments are in that entity's local frame (see GizmoStyle.node). */
42
+ export type GizmoBatch = { color: string, alpha: number, segments: number[], entityId?: number }
43
+
44
+ const DEFAULT_COLOR = "#cfd4dd"
45
+
46
+ /** The per-run collector (owned by the scene loader's EditorRun). Batches are keyed by style so a
47
+ * hundred same-colored segments cost one draw. */
48
+ export class GizmoBuffer {
49
+ batches: GizmoBatch[] = []
50
+ private _byStyle = new Map<string, GizmoBatch>()
51
+
52
+ clear(): void {
53
+ this.batches = []
54
+ this._byStyle.clear()
55
+ }
56
+
57
+ segments(style: GizmoStyle | undefined): number[] {
58
+ const color = style?.color ?? DEFAULT_COLOR
59
+ const alpha = Math.max(0, Math.min(1, style?.alpha ?? 1))
60
+ const entityId = style?.node?.id
61
+ const key = `${color}@${alpha}@${entityId ?? ""}`
62
+ let b = this._byStyle.get(key)
63
+ if (!b) {
64
+ b = entityId ? { color, alpha, segments: [], entityId } : { color, alpha, segments: [] }
65
+ this._byStyle.set(key, b)
66
+ this.batches.push(b)
67
+ }
68
+ return b.segments
69
+ }
70
+ }
71
+
72
+ let current: GizmoBuffer | null = null
73
+ let warnedOutOfScope = false
74
+
75
+ const xyz = (p: Vec3Like): [number, number, number] =>
76
+ Array.isArray(p) ? [ p[0] ?? 0, p[1] ?? 0, p[2] ?? 0 ] : [ (p as { x: number }).x, (p as { y: number }).y, (p as { z: number }).z ]
77
+
78
+ const target = (style: GizmoStyle | undefined): number[] | null => {
79
+ if (current) return current.segments(style)
80
+ if (!warnedOutOfScope && (globalThis as { __lecodesSceneEdit?: boolean }).__lecodesSceneEdit) {
81
+ warnedOutOfScope = true
82
+ console.warn("[Gizmos] draw call outside an editor run — gizmos must be drawn synchronously inside rebuild() / a make() factory / a tool hook")
83
+ }
84
+ return null
85
+ }
86
+
87
+ /** @internal Run `fn` with `buffer` as the ambient gizmo target (cleared first). Nested scopes
88
+ * restore the outer one. */
89
+ export const withGizmoScope = <T>(buffer: GizmoBuffer, fn: () => T): T => {
90
+ const prev = current
91
+ buffer.clear()
92
+ current = buffer
93
+ try { return fn() } finally { current = prev }
94
+ }
95
+
96
+ /**
97
+ * Editor-only line drawing, available inside generator `rebuild()`, `make()` factories and editor
98
+ * tool hooks. Points are WORLD space. Drawn by the scene editor's viewport overlay (depth-test
99
+ * off — a path through a wall is still a path); invisible everywhere else.
100
+ */
101
+ export const Gizmos = {
102
+ /** One segment from `a` to `b`. */
103
+ line(a: Vec3Like, b: Vec3Like, style?: GizmoStyle): void {
104
+ const out = target(style)
105
+ if (!out) return
106
+ out.push(...xyz(a), ...xyz(b))
107
+ },
108
+
109
+ /** Consecutive segments through `points`; `closed` joins the last point back to the first. */
110
+ polyline(points: readonly Vec3Like[], style?: GizmoStyle & { closed?: boolean }): void {
111
+ if (points.length < 2) return
112
+ const out = target(style)
113
+ if (!out) return
114
+ const n = points.length
115
+ const last = style?.closed ? n : n - 1
116
+ for (let i = 0; i < last; i++) out.push(...xyz(points[i]!), ...xyz(points[(i + 1) % n]!))
117
+ },
118
+
119
+ /** A wireframe camera frustum looking down −Z: the four edges from the origin to a rect
120
+ * `length` metres ahead sized by `fov` (vertical, degrees) × `aspect` (default 16:9), plus an
121
+ * "up" fin above the rect. Anchor it (`{ node }`) for a camera node's marker — the frustum
122
+ * then follows the node's pose; `matrix` places an unanchored one in world space. */
123
+ frustum(fov: number, style?: GizmoStyle & { aspect?: number, length?: number, matrix?: Mat4Like }): void {
124
+ const out = target(style)
125
+ if (!out) return
126
+ const aspect = style?.aspect ?? 16 / 9, length = style?.length ?? 0.6
127
+ const m = style?.matrix ? new Mat4(style.matrix) : null
128
+ const h = Math.tan((fov * Math.PI) / 360) * length, w = h * aspect, z = -length
129
+ const P = (x: number, y: number, zz: number): [number, number, number] => {
130
+ if (!m) return [ x, y, zz ]
131
+ const v = m.transformPoint([ x, y, zz ])
132
+ return [ v.x, v.y, v.z ]
133
+ }
134
+ const o = P(0, 0, 0)
135
+ const c = [ P(-w, -h, z), P(w, -h, z), P(w, h, z), P(-w, h, z) ]
136
+ for (const q of c) out.push(...o, ...q)
137
+ for (let i = 0; i < 4; i++) out.push(...c[i]!, ...c[(i + 1) % 4]!)
138
+ out.push(...P(-w * 0.4, h, z), ...P(0, h * 1.6, z), ...P(0, h * 1.6, z), ...P(w * 0.4, h, z))
139
+ },
140
+
141
+ /** A three-axis cross centred on `p` (a point marker); `size` = half extent, default 0.1. */
142
+ cross(p: Vec3Like, size = 0.1, style?: GizmoStyle): void {
143
+ const out = target(style)
144
+ if (!out) return
145
+ const [ x, y, z ] = xyz(p)
146
+ out.push(x - size, y, z, x + size, y, z, x, y - size, z, x, y + size, z, x, y, z - size, x, y, z + size)
147
+ },
148
+ }
package/src/ui/UIImage.ts CHANGED
@@ -2,6 +2,7 @@ import type { FetchResponse } from "../runtime/fetch"
2
2
  import type { SvgSourceValue as SvgSource } from "../runtime/misc"
3
3
  import type { File } from "../runtime/fetch"
4
4
  import { Canvas, isCanvas } from "../canvas/Canvas"
5
+ import type { Scene2D } from "../g2/Scene2D"
5
6
  import { Element, type Color, type ElementStyle, type UIElementBase } from "./UINode"
6
7
 
7
8
  export type UIImageStyle = ElementStyle & { borderRadius?: number, objectFit?: "cover" | "contain" | "fill", tintColor?: Color }
@@ -10,19 +11,32 @@ export type UIImageStyle = ElementStyle & { borderRadius?: number, objectFit?: "
10
11
  * docs/canvas-contract.md ("Pending"). */
11
12
  export type CanvasImageSource = { canvasSurface: number }
12
13
 
13
- /** Accepted image sources. A Canvas is baked to a surface marker on assign. */
14
- export type ImageSource = string | FetchResponse | File | SvgSource | Canvas
14
+ /** A live 2D scene as an image source: the host draws the scene into the node's box every frame
15
+ * (the scene's camera, projected into the box). No intrinsic size — give the node a width and
16
+ * height. */
17
+ export type Scene2DImageSource = { scene2d: number }
15
18
 
16
- const toSrc = (src: ImageSource): string | FetchResponse | File | SvgSource | CanvasImageSource =>
19
+ /** Accepted image sources. A Canvas is baked to a surface marker on assign; a Scene2D becomes a
20
+ * live-scene marker. */
21
+ export type ImageSource = string | FetchResponse | File | SvgSource | Canvas | Scene2D
22
+
23
+ // An engine object that knows how to present itself as an image source (Scene2D._imageSrc). Duck-typed
24
+ // so the UI layer never imports an engine class — a UI-only bundle carries no engine bridge.
25
+ const hasImageSrc = (v: unknown): v is { _imageSrc(): Scene2DImageSource } =>
26
+ typeof v === "object" && v !== null && typeof (v as { _imageSrc?: unknown })._imageSrc === "function"
27
+
28
+ const toSrc = (src: ImageSource): ResolvedImageSource =>
17
29
  // _bakeSurfaceForUI (not _bakeSurface) also flags the canvas UI-bound, so canvas.update() later
18
30
  // refreshes this image node in place — that's what makes an animated UIImage(canvas) update.
19
- isCanvas(src) ? { canvasSurface: src._bakeSurfaceForUI() } : src
31
+ isCanvas(src) ? { canvasSurface: src._bakeSurfaceForUI() } : hasImageSrc(src) ? src._imageSrc() : src
20
32
 
21
- /** The stored/resolved image source (a Canvas is resolved to a surface marker). */
22
- export type ResolvedImageSource = string | FetchResponse | File | SvgSource | CanvasImageSource
33
+ /** The stored/resolved image source (a Canvas is resolved to a surface marker, a Scene2D to a
34
+ * live-scene marker). */
35
+ export type ResolvedImageSource = string | FetchResponse | File | SvgSource | CanvasImageSource | Scene2DImageSource
23
36
 
24
37
  /** An image element. Sources: URL / `asset(...)`, fetch response / File, an SVG source
25
- * (`assetIcon`), or a live Canvas (`canvas.update()` refreshes the node in place). */
38
+ * (`assetIcon`), a live Canvas (`canvas.update()` refreshes the node in place), or a live Scene2D
39
+ * (drawn into the box every frame — a minimap). */
26
40
  export interface UIImage extends UIElementBase<UIImageStyle> {
27
41
  readonly type: "image",
28
42
  /** The image source — writable to swap the picture in place. */