lecodes-cli 0.1.5 → 0.1.7

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 (81) hide show
  1. package/dist/index.js +300 -149
  2. package/package.json +5 -3
  3. package/runtime/sdk/animate/animate.ts +238 -0
  4. package/runtime/sdk/animate/bezier.ts +138 -0
  5. package/runtime/sdk/animate/easings.ts +126 -0
  6. package/runtime/sdk/core/color.ts +66 -0
  7. package/runtime/sdk/core/events.ts +38 -0
  8. package/runtime/sdk/core/registry.ts +23 -0
  9. package/runtime/sdk/g2/Camera2D.ts +73 -0
  10. package/runtime/sdk/g2/Node2D.ts +104 -0
  11. package/runtime/sdk/g2/Scene2D.ts +73 -0
  12. package/runtime/sdk/g2/Sprite.ts +164 -0
  13. package/runtime/sdk/g2/Texture2D.ts +37 -0
  14. package/runtime/sdk/g2/Tilemap.ts +41 -0
  15. package/runtime/sdk/g2/loop.ts +22 -0
  16. package/runtime/sdk/gl/Body.ts +101 -0
  17. package/runtime/sdk/gl/Camera.ts +40 -0
  18. package/runtime/sdk/gl/Geometry.ts +218 -0
  19. package/runtime/sdk/gl/Light.ts +37 -0
  20. package/runtime/sdk/gl/Material.ts +124 -0
  21. package/runtime/sdk/gl/Mesh.ts +123 -0
  22. package/runtime/sdk/gl/Node.ts +307 -0
  23. package/runtime/sdk/gl/Noise.ts +30 -0
  24. package/runtime/sdk/gl/Particles.ts +185 -0
  25. package/runtime/sdk/gl/Plane.ts +53 -0
  26. package/runtime/sdk/gl/Ray.ts +16 -0
  27. package/runtime/sdk/gl/Scene.ts +179 -0
  28. package/runtime/sdk/gl/Texture.ts +35 -0
  29. package/runtime/sdk/gl/controls.ts +95 -0
  30. package/runtime/sdk/gl/state.ts +6 -0
  31. package/runtime/sdk/gl/touch.ts +56 -0
  32. package/runtime/sdk/inject.ts +72 -0
  33. package/runtime/sdk/math/Mathf.ts +118 -0
  34. package/runtime/sdk/math/mat4.ts +278 -0
  35. package/runtime/sdk/math/quat.ts +232 -0
  36. package/runtime/sdk/math/vec.ts +255 -0
  37. package/runtime/sdk/plugins/qr.ts +36 -0
  38. package/runtime/sdk/runtime/device.ts +33 -0
  39. package/runtime/sdk/runtime/fetch.ts +71 -0
  40. package/runtime/sdk/runtime/files.ts +23 -0
  41. package/runtime/sdk/runtime/input.ts +12 -0
  42. package/runtime/sdk/runtime/media.ts +111 -0
  43. package/runtime/sdk/runtime/misc.ts +12 -0
  44. package/runtime/sdk/runtime/net.ts +36 -0
  45. package/runtime/sdk/runtime/storage.ts +13 -0
  46. package/runtime/sdk/runtime/touch.ts +44 -0
  47. package/runtime/sdk/ui/UI.ts +19 -0
  48. package/runtime/sdk/ui/UIButton.ts +84 -0
  49. package/runtime/sdk/ui/UIContainer.ts +100 -0
  50. package/runtime/sdk/ui/UIImage.ts +68 -0
  51. package/runtime/sdk/ui/UIInput.ts +78 -0
  52. package/runtime/sdk/ui/UINode.ts +342 -0
  53. package/runtime/sdk/ui/UIScreen.ts +122 -0
  54. package/runtime/sdk/ui/UIScrollable.ts +70 -0
  55. package/runtime/sdk/ui/UISpacer.ts +14 -0
  56. package/runtime/sdk/ui/UIText.ts +40 -0
  57. package/runtime/sdk/ui/UIVideo.ts +38 -0
  58. package/runtime/sdk/ui/UIVirtualizedList.ts +222 -0
  59. package/runtime/sdk/ui/UIWidget.ts +97 -0
  60. package/runtime/sdk/ui/fonts.ts +10 -0
  61. package/runtime/sdk/ui/router.ts +73 -0
  62. package/runtime/gl-matrix/common.js +0 -54
  63. package/runtime/gl-matrix/header.js +0 -12
  64. package/runtime/gl-matrix/index.js +0 -11
  65. package/runtime/gl-matrix/mat2.js +0 -425
  66. package/runtime/gl-matrix/mat2d.js +0 -476
  67. package/runtime/gl-matrix/mat3.js +0 -765
  68. package/runtime/gl-matrix/mat4.js +0 -1947
  69. package/runtime/gl-matrix/quat.js +0 -737
  70. package/runtime/gl-matrix/quat2.js +0 -822
  71. package/runtime/gl-matrix/vec2.js +0 -620
  72. package/runtime/gl-matrix/vec3.js +0 -791
  73. package/runtime/gl-matrix/vec4.js +0 -652
  74. package/runtime/worker/animate.js +0 -304
  75. package/runtime/worker/chunk-4ZN32GYS.js +0 -34
  76. package/runtime/worker/chunk-G5ZA3LKB.js +0 -52
  77. package/runtime/worker/chunk-MI3UXE5H.js +0 -1395
  78. package/runtime/worker/creator.js +0 -930
  79. package/runtime/worker/globals.js +0 -208
  80. package/runtime/worker/ui.js +0 -763
  81. package/runtime/worker/utils.js +0 -16
@@ -0,0 +1,218 @@
1
+ // Mesh geometry: vertex / normal / index / uv buffers, plus the primitive builders. `Geometry.box()`
2
+ // etc. return a Geometry you can hand to Mesh.from(); Mesh.box() is sugar over them. Ported from
3
+ // worker/src/primitives/*.
4
+
5
+ import { cx, cy, cz, type Vec3Like } from "../math/vec"
6
+
7
+ export type MeshKind = "triangles" | "vertices" | "edges"
8
+
9
+ // local tuple helpers — geometry building works on raw Float32Array buffers, so it stays out of the
10
+ // Vec3 wrapper (no per-vertex allocation).
11
+ type Tup3 = [number, number, number]
12
+ const cross = (a: Tup3, b: Tup3): Tup3 => [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]
13
+ const normalize = (a: Tup3): Tup3 => {
14
+ const l = Math.hypot(a[0], a[1], a[2])
15
+ return l === 0 ? [ 0, 0, 0 ] : [ a[0] / l, a[1] / l, a[2] / l ]
16
+ }
17
+
18
+ export class Geometry {
19
+ vertices: Float32Array
20
+ normals: Float32Array
21
+ indices: Uint16Array
22
+ uv: Float32Array
23
+ /** @internal native mesh-type enum (0 triangles / 1 edges / 2 vertices). */
24
+ _meshKind = 0
25
+
26
+ constructor(vertices: Float32Array, normals: Float32Array, indices: Uint16Array, uv: Float32Array) {
27
+ this.vertices = vertices
28
+ this.normals = normals
29
+ this.indices = indices
30
+ this.uv = uv
31
+ }
32
+
33
+ translate(x: number, y: number, z: number): this {
34
+ for (let i = 0; i < this.vertices.length; i += 3) {
35
+ this.vertices[i] += x; this.vertices[i + 1] += y; this.vertices[i + 2] += z
36
+ }
37
+ return this
38
+ }
39
+ scale(x: number, y: number, z: number): this {
40
+ for (let i = 0; i < this.vertices.length; i += 3) {
41
+ this.vertices[i] *= x; this.vertices[i + 1] *= y; this.vertices[i + 2] *= z
42
+ }
43
+ return this
44
+ }
45
+ scaleUV(x: number, y: number): this {
46
+ for (let i = 0; i < this.uv.length; i += 2) { this.uv[i] *= x; this.uv[i + 1] *= y }
47
+ return this
48
+ }
49
+ set kind(kind: MeshKind) {
50
+ this._meshKind = kind === "edges" ? 1 : kind === "vertices" ? 2 : 0
51
+ }
52
+
53
+ static box(): Geometry { return createCube() }
54
+ static sphere(options?: SphereOptions): Geometry { return createSphere(options) }
55
+ static cylinder(options?: CylinderOptions): Geometry { return createCylinder(options) }
56
+ static plane(options?: PlaneOptions): Geometry { return createPlane(options) }
57
+ }
58
+
59
+ // --- cube ---
60
+ const createCube = (): Geometry => {
61
+ const vertices = new Float32Array(6 * 4 * 3)
62
+ const normals = new Float32Array(6 * 4 * 3)
63
+ const uv = new Float32Array(6 * 4 * 3)
64
+ const indices = new Uint16Array(6 * 6)
65
+ let cnt = 0, indicesCnt = 0
66
+ const sides = [ -0.5, 0.5 ]
67
+ for (let i = 0; i < 6; i++) {
68
+ const s = cnt
69
+ const side = i % 2 === 0 ? -0.5 : 0.5
70
+ for (const a of sides) {
71
+ for (const b of sides) {
72
+ uv.set([ a + 0.5, b + 0.5 ], cnt * 2)
73
+ if (i === 0 || i === 1) { vertices.set([ side, b, -a ], cnt * 3); normals.set([ side * 2, 0, 0 ], cnt * 3) }
74
+ if (i === 2 || i === 3) { vertices.set([ a, side, -b ], cnt * 3); normals.set([ 0, side * 2, 0 ], cnt * 3) }
75
+ if (i === 4 || i === 5) { vertices.set([ a, b, side ], cnt * 3); normals.set([ 0, 0, side * 2 ], cnt * 3) }
76
+ cnt++
77
+ }
78
+ }
79
+ const s1 = s + 1, s2 = s + 2, s3 = s + 3
80
+ if (i % 2 === 0) indices.set([ s, s1, s2, s3, s2, s1 ], indicesCnt)
81
+ else indices.set([ s2, s1, s, s1, s2, s3 ], indicesCnt)
82
+ indicesCnt += 6
83
+ }
84
+ return new Geometry(vertices, normals, indices, uv)
85
+ }
86
+
87
+ // --- sphere ---
88
+ export type SphereOptions = { radius?: number, widthSegments?: number, heightSegments?: number }
89
+ const createSphere = (options: SphereOptions = {}): Geometry => {
90
+ const { radius = 0.5, widthSegments = 32, heightSegments = 32 } = options
91
+ const verticesCount = (widthSegments + 1) * (heightSegments - 1) + widthSegments * 2
92
+ const vertices = new Float32Array(verticesCount * 3)
93
+ const normals = new Float32Array(verticesCount * 3)
94
+ const uvs = new Float32Array(verticesCount * 2)
95
+ const indices = new Uint16Array(widthSegments * (heightSegments - 2) * 6 + widthSegments * 3 * 2)
96
+ const phiLength = Math.PI * 2, thetaLength = Math.PI
97
+ let vertexCnt = 0, indicesCnt = 0
98
+ for (let iy = 1; iy < heightSegments; iy++) {
99
+ const v = iy / heightSegments
100
+ for (let ix = 0; ix <= widthSegments; ix++) {
101
+ const u = ix / widthSegments
102
+ const point: [number, number, number] = [
103
+ -Math.cos(u * phiLength) * Math.sin(v * thetaLength),
104
+ Math.cos(v * thetaLength),
105
+ Math.sin(u * phiLength) * Math.sin(v * thetaLength),
106
+ ]
107
+ vertices.set([ point[0] * radius, point[1] * radius, point[2] * radius ], vertexCnt * 3)
108
+ normals.set(point, vertexCnt * 3)
109
+ uvs.set([ u, 1 - v ], vertexCnt * 2)
110
+ if (iy > 1 && ix > 0) {
111
+ const s = vertexCnt - 1
112
+ const s1 = vertexCnt - (widthSegments + 1) - 1
113
+ indices.set([ s + 1, s1, s, s1, s + 1, s1 + 1 ], indicesCnt)
114
+ indicesCnt += 6
115
+ }
116
+ vertexCnt++
117
+ }
118
+ }
119
+ const sides = [ -radius, radius ]
120
+ const circleStart2 = vertexCnt - (widthSegments + 1)
121
+ for (const side of sides) {
122
+ const circleStart = side > 0 ? 0 : circleStart2
123
+ for (let ix = 0; ix < widthSegments; ix++) {
124
+ const u = (ix + 0.5) / widthSegments
125
+ vertices.set([ 0, side, 0 ], vertexCnt * 3)
126
+ normals.set([ 0, side < 0 ? -1 : 1, 0 ], vertexCnt * 3)
127
+ uvs.set([ u, side < 0 ? 0 : 1 ], vertexCnt * 2)
128
+ const s = circleStart + ix
129
+ if (side > 0) indices.set([ s, s + 1, vertexCnt ], indicesCnt)
130
+ else indices.set([ vertexCnt, s + 1, s ], indicesCnt)
131
+ indicesCnt += 3
132
+ vertexCnt++
133
+ }
134
+ }
135
+ return new Geometry(vertices, normals, indices, uvs)
136
+ }
137
+
138
+ // --- cylinder ---
139
+ export type CylinderOptions = { radius?: number, radiusTop?: number, radiusBottom?: number, edges?: number, smooth?: boolean }
140
+ const createCylinder = (options: CylinderOptions = {}): Geometry => {
141
+ const { edges = 32, smooth = true, radius = 0.5, radiusTop = radius, radiusBottom = radius } = options
142
+ const verticesCount = edges * 4 + (edges + 1) * 2
143
+ const vertices = new Float32Array(verticesCount * 3)
144
+ const normals = new Float32Array(verticesCount * 3)
145
+ const uv = new Float32Array(verticesCount * 2)
146
+ const indices = new Uint16Array(edges * 6 + edges * 6)
147
+ const sides = [ -0.5, 0.5 ]
148
+ let cnt = 0, indicesCnt = 0
149
+ for (let i = 0; i < edges; i++) {
150
+ const s = cnt
151
+ const normalX = Math.cos((i + 0.5) / edges * Math.PI * 2)
152
+ const normalY = Math.sin((i + 0.5) / edges * Math.PI * 2)
153
+ for (let j = 0; j < 2; j++) {
154
+ const x = Math.cos((i + j) / edges * Math.PI * 2)
155
+ const y = Math.sin((i + j) / edges * Math.PI * 2)
156
+ for (const side of sides) {
157
+ if (side > 0) vertices.set([ x * radiusTop, side, y * radiusTop ], cnt * 3)
158
+ else vertices.set([ x * radiusBottom, side, y * radiusBottom ], cnt * 3)
159
+ if (smooth) normals.set([ x, 0, y ], cnt * 3)
160
+ else normals.set([ normalX, 0, normalY ], cnt * 3)
161
+ uv.set([ (i + j) / edges, side + 0.5 ], cnt * 2)
162
+ cnt++
163
+ }
164
+ }
165
+ const s1 = s + 1, s2 = (s + 2) % (edges * 4), s3 = (s + 3) % (edges * 4)
166
+ indices.set([ s, s1, s2, s3, s2, s1 ], indicesCnt)
167
+ indicesCnt += 6
168
+ }
169
+ for (const side of sides) {
170
+ const center = cnt
171
+ vertices.set([ 0, side, 0 ], cnt * 3); normals.set([ 0, side * 2, 0 ], cnt * 3); cnt++
172
+ for (let i = 0; i < edges; i++) {
173
+ const x = Math.cos(i / edges * Math.PI * 2), y = Math.sin(i / edges * Math.PI * 2)
174
+ if (side > 0) vertices.set([ x * radiusTop, side, y * radiusTop ], cnt * 3)
175
+ else vertices.set([ x * radiusBottom, side, y * radiusBottom ], cnt * 3)
176
+ normals.set([ 0, side * 2, 0 ], cnt * 3)
177
+ if (side < 0) indices.set([ center, center + i + 1, center + 1 + ((i + 1) % edges) ], indicesCnt)
178
+ else indices.set([ center + 1 + ((i + 1) % edges), center + i + 1, center ], indicesCnt)
179
+ indicesCnt += 3
180
+ cnt++
181
+ }
182
+ }
183
+ return new Geometry(vertices, normals, indices, uv)
184
+ }
185
+
186
+ // --- plane ---
187
+ export type PlaneOptions = { normal?: Vec3Like }
188
+ const createPlane = (options: PlaneOptions = {}): Geometry => {
189
+ const normal: Tup3 = options.normal ? [ cx(options.normal), cy(options.normal), cz(options.normal) ] : [ 0, 0, 1 ]
190
+ const vertices = new Float32Array(4 * 3)
191
+ const normals = new Float32Array(4 * 3)
192
+ const uvs = new Float32Array(4 * 2)
193
+ const sides = [ -0.5, 0.5 ]
194
+ let cnt = 0
195
+ let upVector: Tup3, rightVec: Tup3
196
+ if (normal[1] === 0 && normal[2] === 0) { upVector = [ 0, 1, 0 ]; rightVec = [ 0, 0, -normal[0] ] }
197
+ else if (normal[0] === 0 && normal[2] === 0) { upVector = [ 0, 0, 1 ]; rightVec = [ -normal[1], 0, 0 ] }
198
+ else if (normal[0] === 0 && normal[1] === 0) { upVector = [ 0, 1, 0 ]; rightVec = [ normal[2], 0, 0 ] } // тут было верно
199
+ else {
200
+ const temp: Tup3 = Math.abs(normal[0]) < 0.5 ? [ 1, 0, 0 ] : [ 0, 0, 1 ]
201
+ upVector = normalize(cross(temp, normal))
202
+ rightVec = normalize(cross(upVector, normal))
203
+ }
204
+ for (const a of sides) {
205
+ for (const b of sides) {
206
+ normals.set(normal, cnt * 3)
207
+ uvs.set([ a + 0.5, b + 0.5 ], cnt * 2)
208
+ vertices.set([
209
+ rightVec[0] * a + upVector[0] * b,
210
+ rightVec[1] * a + upVector[1] * b,
211
+ rightVec[2] * a + upVector[2] * b,
212
+ ], cnt * 3)
213
+ cnt++
214
+ }
215
+ }
216
+ const indices = new Uint16Array([ 2, 1, 0, 1, 2, 3 ])
217
+ return new Geometry(vertices, normals, indices, uvs)
218
+ }
@@ -0,0 +1,37 @@
1
+ // Lights. Currently a directional sun (the engine's primary light). Add it to a scene like any node.
2
+
3
+ import { Color, type ColorInput } from "../core/color"
4
+ import type { Vec3Like } from "../math/vec"
5
+ import { Node } from "./Node"
6
+
7
+ export type SunOptions = {
8
+ /** Light direction (points where the light travels). Defaults to a typical key-light angle. */
9
+ direction?: Vec3Like
10
+ /** Luminous intensity. */
11
+ intensity?: number
12
+ color?: ColorInput
13
+ /**
14
+ * Shadow quality (default 1):
15
+ * 0 - no shadows
16
+ * 1 - standard (1024 shadow map, hard PCF edges)
17
+ * 2 - high (2048 + stable, soft contact-hardening edges)
18
+ * 3 - best (4096 + stable, PCSS soft shadows)
19
+ * Higher values cost more GPU/memory.
20
+ */
21
+ shadowsQuality?: 0 | 1 | 2 | 3
22
+ }
23
+
24
+ export class Light extends Node {
25
+ /** A directional sun light. */
26
+ static sun(options: SunOptions = {}): Light {
27
+ const light = new Light()
28
+ const [ dx, dy, dz ] = options.direction ?? [ 0.548267, -0.473983, -0.689016 ]
29
+ _creator.createSunLight(
30
+ light.id, dx, dy, dz,
31
+ options.intensity ?? 100000,
32
+ Color.toPackedRgb(options.color ?? 0xffffff),
33
+ options.shadowsQuality ?? 1,
34
+ )
35
+ return light
36
+ }
37
+ }
@@ -0,0 +1,124 @@
1
+ // A material instance over creator-gl. Uniforms are set through a Proxy that lowers each JS value to
2
+ // the right native uniform call. The common materials are static factories (Material.lit/unlit/
3
+ // video/shadow); Material.load brings in a custom compiled shader. Ported from worker/src/Material.ts,
4
+ // with the Lit/Unlit/... subclasses collapsed into factories and color accepting any ColorInput.
5
+
6
+ import { Color, type ColorInput } from "../core/color"
7
+ import { fetch, type FetchResponse } from "../runtime/fetch"
8
+ import { Texture } from "./Texture"
9
+
10
+ type UniformValue = string | Texture | number | boolean | number[] | Float32Array | null
11
+
12
+ const applyUniform = (id: number, key: string, value: UniformValue): void => {
13
+ if (typeof value === "string") {
14
+ if (value.length > 7) _creator.setUniformRgba(id, key, parseInt(value.slice(1), 16))
15
+ else _creator.setUniformRgb(id, key, parseInt(value.slice(1), 16))
16
+ } else if (typeof value === "number") {
17
+ _creator.setUniformFloat(id, key, value)
18
+ } else if (typeof value === "boolean") {
19
+ _creator.setUniformBoolean(id, key, value)
20
+ } else if (value instanceof Texture) {
21
+ _creator.setUniformTexture(id, key, value._id, value.wrapS, value.wrapT)
22
+ } else if (value instanceof Float32Array) {
23
+ _creator.setUniformArray(id, key, value)
24
+ } else if (Array.isArray(value)) {
25
+ _creator.setUniformArray(id, key, Float32Array.of(...value))
26
+ } else {
27
+ _creator.setUniformNull(id, key)
28
+ }
29
+ }
30
+
31
+ export type MaterialColorOptions = { color?: ColorInput, map?: Texture | null }
32
+
33
+ export class Material {
34
+ /** @internal native material-instance handle. */
35
+ _id: number
36
+ readonly shader: FetchResponse | "unknown"
37
+ readonly uniforms: Record<string, UniformValue>
38
+
39
+ private _colorKey: string | null = null
40
+ private _useMapFlag = true
41
+
42
+ constructor(shader: FetchResponse | number) {
43
+ if (typeof shader === "number") {
44
+ this.shader = "unknown"
45
+ this._id = shader
46
+ } else {
47
+ this.shader = shader
48
+ this._id = _creator.createMaterial((shader as any)._id)
49
+ }
50
+ const store: Record<string, UniformValue> = {}
51
+ const id = () => this._id
52
+ this.uniforms = new Proxy(store, {
53
+ get: (t, k: string) => t[k],
54
+ set: (t, k: string, v: UniformValue) => { t[k] = v; applyUniform(id(), k, v); return true },
55
+ })
56
+ }
57
+
58
+ /** Set a uniform (chainable). */
59
+ set(key: string, value: UniformValue): this {
60
+ this.uniforms[key] = value
61
+ return this
62
+ }
63
+
64
+ set color(c: ColorInput) {
65
+ if (this._colorKey) this.uniforms[this._colorKey] = Color.toHexString(c)
66
+ }
67
+
68
+ set map(value: Texture | null) {
69
+ if (value === null) {
70
+ if (this._useMapFlag) this.uniforms.useBaseColorMap = false
71
+ } else {
72
+ this.uniforms.baseColorMap = value
73
+ if (this._useMapFlag) this.uniforms.useBaseColorMap = true
74
+ }
75
+ }
76
+
77
+ // --- factories ---
78
+
79
+ /** PBR lit material. */
80
+ static lit(options: MaterialColorOptions = {}): Material {
81
+ // The fetchLocal literal must be inline so the compiler's preload header captures the shader name.
82
+ const m = new Material({ _id: _creatorUtils.fetchLocal("lit.filamat") } as any)
83
+ m._colorKey = "baseColor"
84
+ if (options.color !== undefined) m.color = options.color
85
+ if (options.map) m.map = options.map
86
+ return m
87
+ }
88
+
89
+ /** Unlit (flat) material. */
90
+ static unlit(options: MaterialColorOptions = {}): Material {
91
+ const m = new Material({ _id: _creatorUtils.fetchLocal("unlit.filamat") } as any)
92
+ m._colorKey = "baseColor"
93
+ if (options.color !== undefined) m.color = options.color
94
+ if (options.map) m.map = options.map
95
+ return m
96
+ }
97
+
98
+ /** Material that samples a VideoPlayer's texture. */
99
+ static video(map?: Texture): Material {
100
+ const m = new Material({ _id: _creatorUtils.fetchLocal("video.filamat") } as any)
101
+ m._useMapFlag = false
102
+ if (map) m.uniforms.baseColorMap = map
103
+ return m
104
+ }
105
+
106
+ /** Shadow-catcher material (transparent except where shadows fall). */
107
+ static shadow(color: ColorInput = "#000000aa"): Material {
108
+ const m = new Material({ _id: _creatorUtils.fetchLocal("shadow.filamat") } as any)
109
+ m._colorKey = "color"
110
+ m.color = color
111
+ return m
112
+ }
113
+
114
+ /** Load a custom compiled shader (.mat URL) as a material. */
115
+ static load(url: string): Promise<Material> {
116
+ return fetch(url, { useOnce: true }).then((resp) => {
117
+ if (resp.status >= 400) return Promise.reject(new Error(`Failed to fetch shader from ${url}. HTTP ${resp.status}`))
118
+ return new Material(resp)
119
+ })
120
+ }
121
+
122
+ /** @internal */
123
+ static idOf(material: Material): number { return material._id }
124
+ }
@@ -0,0 +1,123 @@
1
+ // A renderable 3D node. Static factories build the common shapes; Mesh.from wraps a custom Geometry;
2
+ // Mesh.load brings in a GLB (returned as its root Node, with baked animation clips). Ported from
3
+ // worker/src/components/Mesh.ts — folded into the node (no component).
4
+
5
+ import { cx, cy, cz, type Vec3Like } from "../math/vec"
6
+ import { fetch, type FetchResponse } from "../runtime/fetch"
7
+ import {
8
+ Geometry,
9
+ type CylinderOptions,
10
+ type PlaneOptions,
11
+ type SphereOptions,
12
+ } from "./Geometry"
13
+ import { Material } from "./Material"
14
+ import { Node } from "./Node"
15
+
16
+ /** Common transform/render options every primitive factory accepts. */
17
+ export type MeshOptions = {
18
+ material?: Material
19
+ position?: Vec3Like
20
+ eulerAngles?: Vec3Like
21
+ scale?: Vec3Like | number
22
+ name?: string
23
+ castShadows?: boolean
24
+ receiveShadows?: boolean
25
+ }
26
+
27
+ export class Mesh extends Node {
28
+ private _geometry?: Geometry
29
+ private _materials: Material[] = []
30
+
31
+ constructor(geometry?: Geometry, material?: Material) {
32
+ super()
33
+ if (geometry) {
34
+ const mat = material ?? Material.unlit()
35
+ this._geometry = geometry
36
+ this._materials = [ mat ]
37
+ _creator.setMesh(this.id, Material.idOf(mat), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind)
38
+ }
39
+ }
40
+
41
+ override get geometry(): Geometry | undefined { return this._geometry }
42
+
43
+ get material(): Material { return this._materials[0] }
44
+ set material(m: Material) { this.setMaterial(m, 0) }
45
+
46
+ setMaterial(material: Material, index = 0): this {
47
+ this._materials[index] = material
48
+ _creator.setMaterial(this.id, Material.idOf(material), index)
49
+ return this
50
+ }
51
+ getMaterial(index = 0): Material | null {
52
+ const existing = this._materials[index]
53
+ if (existing) return existing
54
+ const id = _creator.getMaterial(this.id, index)
55
+ if (id === 0) return null
56
+ const mat = new Material(id)
57
+ this._materials[index] = mat
58
+ return mat
59
+ }
60
+
61
+ set culling(v: boolean) { _creator.setCulling(this.id, v) }
62
+ set castShadows(v: boolean) { _creator.setCastShadows(this.id, v) }
63
+ set receiveShadows(v: boolean) { _creator.setReceiveShadows(this.id, v) }
64
+
65
+ // --- factories ---
66
+
67
+ static box(options: MeshOptions & { size?: Vec3Like | number } = {}): Mesh {
68
+ const geo = Geometry.box()
69
+ const [ sx, sy, sz ] = sizeToVec(options.size ?? 1)
70
+ if (sx !== 1 || sy !== 1 || sz !== 1) geo.scale(sx, sy, sz)
71
+ return apply(new Mesh(geo, options.material), options)
72
+ }
73
+
74
+ static sphere(options: MeshOptions & SphereOptions = {}): Mesh {
75
+ return apply(new Mesh(Geometry.sphere(options), options.material), options)
76
+ }
77
+
78
+ static cylinder(options: MeshOptions & CylinderOptions = {}): Mesh {
79
+ return apply(new Mesh(Geometry.cylinder(options), options.material), options)
80
+ }
81
+
82
+ static plane(options: MeshOptions & PlaneOptions = {}): Mesh {
83
+ return apply(new Mesh(Geometry.plane(options), options.material), options)
84
+ }
85
+
86
+ /** Wrap a custom Geometry. */
87
+ static from(geometry: Geometry, options: MeshOptions = {}): Mesh {
88
+ return apply(new Mesh(geometry, options.material), options)
89
+ }
90
+
91
+ /**
92
+ * Load a GLB model. Returns the model's root Node (a GLB is a node hierarchy); its animation clips
93
+ * are available via node.clips and driven with node.play(name|index).
94
+ */
95
+ static load(source: string | FetchResponse, options: { culling?: boolean, onProgress?: (p: { loaded: number, total?: number }) => void } = {}): Promise<Node> {
96
+ if (typeof source === "string") {
97
+ return fetch(source, { useOnce: true, onProgress: options.onProgress }).then((resp) => {
98
+ if (resp.status >= 400) return Promise.reject(new Error(`Failed to load GLB from ${source}. HTTP ${resp.status}`))
99
+ return Mesh.load(resp, options)
100
+ })
101
+ }
102
+ return new Promise<Node>((resolve, reject) => {
103
+ _creator.createGlb((source as any)._id, (entityId) => {
104
+ if (entityId === 0) { reject(new Error("Failed to load GLB")); return }
105
+ const node = new Node(entityId)
106
+ if (options.culling !== true) _creator.setGlbCulling(entityId, false)
107
+ resolve(node)
108
+ }, reject)
109
+ })
110
+ }
111
+ }
112
+
113
+ const sizeToVec = (s: Vec3Like | number): [number, number, number] => (typeof s === "number" ? [ s, s, s ] : [ cx(s), cy(s), cz(s) ])
114
+
115
+ const apply = (mesh: Mesh, o: MeshOptions): Mesh => {
116
+ if (o.position) mesh.position = o.position
117
+ if (o.eulerAngles) mesh.eulerAngles = o.eulerAngles
118
+ if (o.scale !== undefined) mesh.scale = typeof o.scale === "number" ? [ o.scale, o.scale, o.scale ] : o.scale
119
+ if (o.name !== undefined) mesh.name = o.name
120
+ if (o.castShadows !== undefined) mesh.castShadows = o.castShadows
121
+ if (o.receiveShadows !== undefined) mesh.receiveShadows = o.receiveShadows
122
+ return mesh
123
+ }