lecodes-sdk 0.20.2 → 1.1.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 (129) hide show
  1. package/dist/global.d.ts +35 -9
  2. package/dist/types/animate/tween/Animation.d.ts +69 -0
  3. package/dist/types/animate/tween/Timeline.d.ts +55 -0
  4. package/dist/types/animate/tween/animateValue.d.ts +27 -0
  5. package/dist/types/animate/tween/easing.d.ts +29 -0
  6. package/dist/types/animate/tween/spec.d.ts +178 -0
  7. package/dist/types/canvas/Canvas.d.ts +2 -0
  8. package/dist/types/g2/Node2D.d.ts +16 -0
  9. package/dist/types/g2/Sprite.d.ts +11 -1
  10. package/dist/types/gl/Camera.d.ts +15 -1
  11. package/dist/types/gl/DecalSet.d.ts +48 -3
  12. package/dist/types/gl/Foliage.d.ts +47 -0
  13. package/dist/types/gl/Geometry.d.ts +36 -0
  14. package/dist/types/gl/Light.d.ts +25 -7
  15. package/dist/types/gl/Lightmap.d.ts +90 -51
  16. package/dist/types/gl/Material.d.ts +32 -20
  17. package/dist/types/gl/Mesh.d.ts +7 -1
  18. package/dist/types/gl/Model.d.ts +41 -5
  19. package/dist/types/gl/Node.d.ts +18 -0
  20. package/dist/types/gl/Particles.d.ts +53 -1
  21. package/dist/types/gl/Scene.d.ts +21 -1
  22. package/dist/types/gl/animation/AnimationClip.d.ts +19 -0
  23. package/dist/types/gl/animation/Animator.d.ts +27 -0
  24. package/dist/types/gl/animation/DynamicBone.d.ts +184 -0
  25. package/dist/types/gl/animation/IK.d.ts +109 -0
  26. package/dist/types/gl/{Locomotion.d.ts → animation/Locomotion.d.ts} +6 -6
  27. package/dist/types/gl/animation/Warp.d.ts +2 -1
  28. package/dist/types/gl/animation/core.d.ts +35 -4
  29. package/dist/types/gl/{AudioSource.d.ts → audio/AudioSource.d.ts} +6 -6
  30. package/dist/types/gl/{AudioZone.d.ts → audio/AudioZone.d.ts} +4 -4
  31. package/dist/types/gl/{SceneAudio.d.ts → audio/SceneAudio.d.ts} +1 -1
  32. package/dist/types/gl/{NavAgent.d.ts → nav/NavAgent.d.ts} +4 -4
  33. package/dist/types/gl/{NavMesh.d.ts → nav/NavMesh.d.ts} +4 -4
  34. package/dist/types/gl/{CharacterController.d.ts → physics/CharacterController.d.ts} +5 -5
  35. package/dist/types/gl/{Physics.d.ts → physics/Physics.d.ts} +6 -5
  36. package/dist/types/gl/physics/Ragdoll.d.ts +161 -0
  37. package/dist/types/gl/{Shape.d.ts → physics/Shape.d.ts} +3 -3
  38. package/dist/types/gl/{Trigger.d.ts → physics/Trigger.d.ts} +2 -2
  39. package/dist/types/gl/{Terrain.d.ts → terrain/Terrain.d.ts} +10 -8
  40. package/dist/types/gl/{terrainMesh.d.ts → terrain/terrainMesh.d.ts} +1 -1
  41. package/dist/types/gl/vehicle/Vehicle.d.ts +300 -0
  42. package/dist/types/gl/vehicle/Wheel.d.ts +147 -0
  43. package/dist/types/inject.d.ts +35 -27
  44. package/dist/types/runtime/files.d.ts +24 -1
  45. package/dist/types/scene/defineScene.d.ts +50 -31
  46. package/dist/types/ui/UIButton.d.ts +3 -1
  47. package/dist/types/ui/UIInput.d.ts +5 -1
  48. package/dist/types/ui/UINode.d.ts +24 -24
  49. package/dist/types.json +1 -1
  50. package/package.json +1 -1
  51. package/prompts/README.md +142 -142
  52. package/prompts/core-design.md +27 -4
  53. package/prompts/core.md +35 -6
  54. package/prompts/select.ts +19 -4
  55. package/src/animate/tween/Animation.ts +378 -0
  56. package/src/animate/tween/Timeline.ts +175 -0
  57. package/src/animate/tween/animateValue.ts +100 -0
  58. package/src/animate/tween/easing.ts +172 -0
  59. package/src/animate/tween/spec.ts +479 -0
  60. package/src/bridges.d.ts +1760 -1481
  61. package/src/canvas/Canvas.ts +21 -0
  62. package/src/compile/__tests__/assetMacro.test.ts +26 -0
  63. package/src/compile/__tests__/compile.test.ts +11 -0
  64. package/src/compile/__tests__/detectEntry.test.ts +19 -0
  65. package/src/compile/__tests__/serverSplit.test.ts +27 -0
  66. package/src/compile/bundler.ts +34 -4
  67. package/src/compile/compileProject.ts +31 -1
  68. package/src/compile/detectEntry.ts +8 -3
  69. package/src/compile/header.ts +6 -3
  70. package/src/compile/index.ts +3 -0
  71. package/src/compile/sceneEditor.ts +42 -1
  72. package/src/compile/serverSplit.ts +9 -3
  73. package/src/g2/Node2D.ts +38 -0
  74. package/src/g2/Sprite.ts +20 -1
  75. package/src/gl/Camera.ts +34 -1
  76. package/src/gl/DecalSet.ts +132 -5
  77. package/src/gl/Foliage.ts +102 -0
  78. package/src/gl/Geometry.ts +109 -0
  79. package/src/gl/Light.ts +46 -16
  80. package/src/gl/Lightmap.ts +440 -249
  81. package/src/gl/Material.ts +69 -36
  82. package/src/gl/Mesh.ts +120 -102
  83. package/src/gl/Model.ts +167 -124
  84. package/src/gl/Node.ts +40 -1
  85. package/src/gl/Particles.ts +82 -5
  86. package/src/gl/Scene.ts +35 -2
  87. package/src/gl/animation/AnimationClip.ts +52 -0
  88. package/src/gl/animation/Animator.ts +42 -2
  89. package/src/gl/animation/DynamicBone.ts +482 -0
  90. package/src/gl/animation/IK.ts +214 -0
  91. package/src/gl/{Locomotion.ts → animation/Locomotion.ts} +7 -7
  92. package/src/gl/animation/Playback.ts +5 -4
  93. package/src/gl/animation/Warp.ts +5 -2
  94. package/src/gl/animation/core.ts +65 -4
  95. package/src/gl/{AudioSource.ts → audio/AudioSource.ts} +7 -7
  96. package/src/gl/{AudioZone.ts → audio/AudioZone.ts} +75 -75
  97. package/src/gl/{SceneAudio.ts → audio/SceneAudio.ts} +2 -2
  98. package/src/gl/{NavAgent.ts → nav/NavAgent.ts} +5 -5
  99. package/src/gl/{NavMesh.ts → nav/NavMesh.ts} +8 -8
  100. package/src/gl/{CharacterController.ts → physics/CharacterController.ts} +5 -5
  101. package/src/gl/{Physics.ts → physics/Physics.ts} +12 -5
  102. package/src/gl/physics/Ragdoll.ts +451 -0
  103. package/src/gl/{Shape.ts → physics/Shape.ts} +3 -3
  104. package/src/gl/{Trigger.ts → physics/Trigger.ts} +45 -45
  105. package/src/gl/{physicsEvents.ts → physics/physicsEvents.ts} +1 -1
  106. package/src/gl/{Terrain.ts → terrain/Terrain.ts} +14 -12
  107. package/src/gl/{terrainMesh.ts → terrain/terrainMesh.ts} +1 -1
  108. package/src/gl/vehicle/Vehicle.ts +666 -0
  109. package/src/gl/vehicle/Wheel.ts +290 -0
  110. package/src/inject.ts +236 -224
  111. package/src/runtime/files.ts +32 -2
  112. package/src/scene/defineScene.ts +92 -66
  113. package/src/scene/level.ts +2 -2
  114. package/src/ui/UIButton.ts +2 -2
  115. package/src/ui/UIInput.ts +3 -3
  116. package/src/ui/UINode.ts +61 -36
  117. package/dist/types/animate/animate.d.ts +0 -20
  118. package/dist/types/gl/Gearbox.d.ts +0 -86
  119. package/dist/types/gl/IK.d.ts +0 -53
  120. package/dist/types/gl/Ragdoll.d.ts +0 -86
  121. package/dist/types/gl/Vehicle.d.ts +0 -191
  122. package/dist/types/gl/Wheel.d.ts +0 -95
  123. package/src/animate/animate.ts +0 -238
  124. package/src/gl/Gearbox.ts +0 -212
  125. package/src/gl/IK.ts +0 -193
  126. package/src/gl/Ragdoll.ts +0 -270
  127. package/src/gl/Vehicle.ts +0 -473
  128. package/src/gl/Wheel.ts +0 -240
  129. /package/dist/types/gl/{physicsEvents.d.ts → physics/physicsEvents.d.ts} +0 -0
@@ -161,8 +161,6 @@ export type LitMaterialOptions = MaterialColorOptions & MaterialStateOptions & {
161
161
  metallic?: number
162
162
  }
163
163
 
164
- /** `Material.lightmapShading` tiers — see the setter. */
165
- export type LightmapShading = "full" | "baked" | "baked-lite"
166
164
 
167
165
  export class Material {
168
166
  /** @internal native material-instance handle. */
@@ -328,38 +326,50 @@ export class Material {
328
326
  return m
329
327
  }
330
328
 
331
- /** The lightmap material (docs/lightmap-plan.md): PBR base colour × a baked shadow/AO atlas on UV1.
332
- * Models take it through `Model.load(…, { lightmap: true })`; `Lightmap.load` builds one per static
333
- * Mesh. Parameters use gltfio's names (`baseColorFactor`, `baseColorMap`, `roughnessFactor`,
334
- * `metallicFactor`) plus `lightmap`, `lightmapST`, `ambientScale`, `sunStrength` (the sun / IBL terms come from
335
- * filament's per-frame uniforms inside the shader). */
329
+ /** The lightmap material (packages/creator-bake): a lit PBR material whose whole DIFFUSE light is the
330
+ * baked irradiance atlas on UV1 (`lightmapLight`, lux) — the real-time sun, lamps and diffuse IBL do not
331
+ * touch it; the specular IBL stays, occluded by the baked sky visibility. Models take it through
332
+ * `Model.load(…, { lightmap: true })`; a static Mesh gets one from here. Parameters use gltfio's names
333
+ * (`baseColorFactor`, `baseColorMap`, `roughnessFactor`, `metallicFactor`) plus `lightmapLight`, `lightmapAux`,
334
+ * `lightmapST`, `lightScale` (the level-wide value `Lightmap.load` sets) and the debug view knobs. */
336
335
  static lightmap(): Material {
337
- // Inline literals: the compiler's preload header captures the shader names from them.
338
- const id = Material.lightmapShading === "baked"
339
- ? _creatorUtils.fetchLocal("lightmap-baked.filamat")
340
- : Material.lightmapShading === "baked-lite"
341
- ? _creatorUtils.fetchLocal("lightmap-baked-lite.filamat")
342
- : _creatorUtils.fetchLocal("lightmap.filamat")
343
- const m = new Material({ _id: id } as any)
344
- m.set("baseColorFactor", "#ffffffff").set("roughnessFactor", 1).set("metallicFactor", 0)
345
- m.set("lightmapST", [ 1, 1, 0, 0 ]).set("ambientScale", 1).set("sunStrength", 1).set("hasNormalMap", 0)
346
- // lightScale 0 = no baked point lights until Lightmap.load binds a light atlas / volume
347
- m.set("bakedAo", 1).set("lightScale", 0)
348
- m.set("probeMin", [ 0, 0, 0, 0 ]).set("probeInvSize", [ 0, 0, 0, 0 ])
336
+ // Inline literal: the compiler's preload header captures the shader name from it.
337
+ const m = new Material({ _id: _creatorUtils.fetchLocal("lightmap.filamat") } as any)
338
+ Material._lightmapDefaults(m)
339
+ return m
340
+ }
341
+
342
+ /** `Material.lightmap()`'s masked twin (`blending: masked`, same shader and parameters): what the engine
343
+ * gives a lightmapped model's alpha-MASK materials. The cutoff comes from the glTF material. */
344
+ static lightmapMasked(): Material {
345
+ const m = new Material({ _id: _creatorUtils.fetchLocal("lightmap-masked.filamat") } as any)
346
+ Material._lightmapDefaults(m)
349
347
  return m
350
348
  }
351
349
 
352
350
  /** The terrain splat material (docs/terrain-plan.md §1.5): four albedo (+ normal-map) layers blended by
353
351
  * a control map on the terrain's own grid, per-layer `tiling` (metres per repeat) / `roughness` /
354
- * `normalScale` / `triplanar`, lightmap-aware (the same `lightmap` / `lightmapST` block as
355
- * `Material.lightmap`). `Terrain` builds and owns one per terrain; the uniforms are primed here so an
356
- * unset layer is white and an unbaked terrain is fully lit. */
352
+ * `normalScale` / `triplanar`. `Terrain` builds and owns one per terrain; the uniforms are primed here so an
353
+ * unset layer is white. Lit real-time; a BAKED terrain takes `Material.terrainLightmap()`. */
357
354
  static terrain(): Material {
358
355
  // Inline literal: the compiler's preload header captures the shader name from it.
359
356
  const m = new Material({ _id: _creatorUtils.fetchLocal("terrain.filamat") } as any)
360
357
  m.set("tiling", [ 8, 8, 8, 8 ]).set("roughness", [ 1, 1, 1, 1 ]).set("normalScale", [ 0, 0, 0, 0 ]).set("triplanar", [ 0, 0, 0, 0 ])
361
358
  m.set("gridSize", [ 2, 2 ]).set("cellSize", 1).set("tint", "#ffffff")
362
- m.set("lightmapST", [ 1, 1, 0, 0 ]).set("ambientScale", 1).set("sunStrength", 1).set("bakedAo", 1).set("lightScale", 0)
359
+ return m
360
+ }
361
+
362
+ /** `Material.terrain()` for a BAKED terrain (terrain-lightmap.mat): the same layers and uniforms, shaded like
363
+ * `Material.lightmap()` — the whole diffuse light is the baked atlas on the terrain's UV1 (one rect), which
364
+ * `Lightmap.load` binds. A scene file's `terrain:` node that is a lightmap static is built with it; without a
365
+ * bound bake it draws black, like any lightmapped static. No reflection probes (the sampler budget): it reflects the
366
+ * sky through the baked sky visibility. */
367
+ static terrainLightmap(): Material {
368
+ // Inline literal: the compiler's preload header captures the shader name from it.
369
+ const m = new Material({ _id: _creatorUtils.fetchLocal("terrain-lightmap.filamat") } as any)
370
+ m.set("tiling", [ 8, 8, 8, 8 ]).set("roughness", [ 1, 1, 1, 1 ]).set("normalScale", [ 0, 0, 0, 0 ]).set("triplanar", [ 0, 0, 0, 0 ])
371
+ m.set("gridSize", [ 2, 2 ]).set("cellSize", 1).set("tint", "#ffffff")
372
+ m.set("lightmapST", [ 1, 1, 0, 0 ]).set("lightScale", 1).set("debugMode", 0).set("debugParams", [ 0.001, 0, 5, 0 ])
363
373
  return m
364
374
  }
365
375
 
@@ -367,22 +377,45 @@ export class Material {
367
377
  * the GLB provider instantiates; the template itself never draws). */
368
378
  static _lightmapTemplate(): Material { return (Material._lmTemplate ??= Material.lightmap()) }
369
379
  private static _lmTemplate: Material | undefined
380
+ /** @internal The tier's MASKED twin, handed to the engine next to the template: a model's glTF MASK
381
+ * materials (foliage cards) take it instead of falling back to the ubershader's full lit path. */
382
+ static _lightmapMaskedTemplate(): Material { return (Material._lmMaskedTemplate ??= Material.lightmapMasked()) }
383
+ private static _lmMaskedTemplate: Material | undefined
384
+ /** The FOLIAGE tier (`Model.load(…, { foliage: true })`, see `Foliage`): an unlit shader with its own cheap
385
+ * lighting plus a vertex shader that sways in the wind, bends away from benders and thins out with distance,
386
+ * and a per-copy tint. glTF parameters like `Material.lightmap()` plus `wind` / `sway` / `fade` / `benders`,
387
+ * which the engine writes itself. Not baked by the rewritten bake yet. */
388
+ static foliage(): Material {
389
+ const m = new Material({ _id: _creatorUtils.fetchLocal("foliage.filamat") } as any)
390
+ Material._glbDefaults(m)
391
+ return m
392
+ }
370
393
 
371
- static _lightmapShading: LightmapShading = "full"
372
- /** Which shader lightmapped models take — a graphics-quality tier, engine-wide. `"full"` is filament's
373
- * lit path over the baked atlas (IBL specular, real-time point lights such as a muzzle flash, sun
374
- * shadows on dynamic objects). `"baked"` keeps the baked light and an approximated ambient but drops
375
- * the lit path: ~40 % cheaper per pixel on a fill-bound GPU. `"baked-lite"` is that minus the normal,
376
- * metallic/roughness and occlusion map reads (the factors stand in, the atlas keeps the baked AO;
377
- * emissive still glows): +20 % more at 1080p on the same GPU. Read when a lightmapped model LOADS, so set
378
- * it before the level (a settings menu applies it on the next level load), like `Texture.maxSize`. */
379
- static get lightmapShading(): LightmapShading { return Material._lightmapShading }
380
- static set lightmapShading(mode: LightmapShading) {
381
- if (mode === Material._lightmapShading) return
382
- Material._lightmapShading = mode
383
- Material._lmTemplate = undefined // the next load builds a template on the new shader
394
+ /** `Material.foliage()`'s masked twin (`blending: masked`): what the engine gives a foliage model's alpha-MASK
395
+ * materials — the cards. */
396
+ static foliageMasked(): Material {
397
+ const m = new Material({ _id: _creatorUtils.fetchLocal("foliage-masked.filamat") } as any)
398
+ Material._glbDefaults(m)
399
+ return m
384
400
  }
385
401
 
402
+ /** the glTF-side identity values every provider-handed material starts from */
403
+ private static _glbDefaults(m: Material): void {
404
+ m.set("baseColorFactor", "#ffffffff").set("roughnessFactor", 1).set("metallicFactor", 0).set("hasNormalMap", 0)
405
+ }
406
+
407
+ private static _lightmapDefaults(m: Material): void {
408
+ Material._glbDefaults(m)
409
+ m.set("lightmapST", [ 1, 1, 0, 0 ]).set("lightScale", 1).set("debugMode", 0).set("debugParams", [ 0.001, 0, 5, 0 ])
410
+ }
411
+
412
+ /** @internal the foliage template pair, like the lightmap one (a host that lacks the tier returns id 0 from
413
+ * fetchLocal — `Model.load` then falls back to the lightmap tier). */
414
+ static _foliageTemplate(): Material { return (Material._folTemplate ??= Material.foliage()) }
415
+ private static _folTemplate: Material | undefined
416
+ static _foliageMaskedTemplate(): Material { return (Material._folMaskedTemplate ??= Material.foliageMasked()) }
417
+ private static _folMaskedTemplate: Material | undefined
418
+
386
419
  /** Shadow-catcher material (transparent except where shadows fall). */
387
420
  static shadow(color: ColorInput = "#000000aa"): Material {
388
421
  const m = new Material({ _id: _creatorUtils.fetchLocal("shadow.filamat") } as any)
package/src/gl/Mesh.ts CHANGED
@@ -1,102 +1,120 @@
1
- // A renderable 3D node from raw geometry — the primitive shapes (box/sphere/cylinder/plane) and
2
- // Mesh.from for a custom Geometry. A GLB import is a different kind: see Model.load.
3
-
4
- import { cx, cy, cz, type Vec3Like } from "../math/vec"
5
- import {
6
- Geometry,
7
- type CylinderOptions,
8
- type PlaneOptions,
9
- type SphereOptions,
10
- } from "./Geometry"
11
- import { Material } from "./Material"
12
- import { Node } from "./Node"
13
-
14
- /** Common transform/render options every primitive factory accepts. */
15
- export type MeshOptions = {
16
- material?: Material
17
- position?: Vec3Like
18
- eulerAngles?: Vec3Like
19
- scale?: Vec3Like | number
20
- name?: string
21
- castShadows?: boolean
22
- receiveShadows?: boolean
23
- /** Coarse draw order, 0 (first) … 7 (last); default 4. See `Mesh.renderPriority`. */
24
- renderPriority?: number
25
- }
26
-
27
- export class Mesh extends Node {
28
- private _geometry?: Geometry
29
-
30
- constructor(geometry?: Geometry, material?: Material) {
31
- super()
32
- if (geometry) {
33
- const mat = material ?? Material.unlit()
34
- this._geometry = geometry
35
- this._materials = [ mat ]
36
- _creator.setMesh(this.id, Material.idOf(mat), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1, geometry.colors)
37
- }
38
- }
39
-
40
- override get geometry(): Geometry | undefined { return this._geometry }
41
- /** Replace the geometry in place — the node, its transform and its material stay, the vertex and
42
- * index buffers are rebuilt. What an editor overlay or a debug drawer redraws with. */
43
- setGeometry(geometry: Geometry): this {
44
- this._geometry = geometry
45
- _creator.setMesh(this.id, Material.idOf(this._materials![0]!), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1, geometry.colors)
46
- return this
47
- }
48
-
49
- /** A Mesh always carries a material (slot 0) — see Node.setMaterial for the slot API. */
50
- override get material(): Material { return this._materials![0]! }
51
- override set material(m: Material) { this.setMaterial(m, 0) }
52
-
53
- set culling(v: boolean) { _creator.setCulling(this.id, v) }
54
- /** Coarse draw order within the frame: 0 draws first, 7 last, 4 is the default (Filament's
55
- * renderable priority; within one priority opaque draws sort front-to-back, blended back-to-front).
56
- * Something drawn over the scene with `Material.depthTest = false` goes in 7, so nothing drawn
57
- * after it can cover it. Write-only; a host that predates the call ignores it. */
58
- set renderPriority(v: number) {
59
- if (_creator.setRenderPriority) _creator.setRenderPriority(this.id, Math.max(0, Math.min(7, Math.round(v))))
60
- else if (!Mesh._warnedPriority) { Mesh._warnedPriority = true; console.warn("[mesh] this host has no setRenderPriority — renderPriority ignored") }
61
- }
62
- private static _warnedPriority = false
63
- set castShadows(v: boolean) { _creator.setCastShadows(this.id, v) }
64
- set receiveShadows(v: boolean) { _creator.setReceiveShadows(this.id, v) }
65
-
66
- // --- factories ---
67
-
68
- static box(options: MeshOptions & { size?: Vec3Like | number } = {}): Mesh {
69
- // the size goes into the geometry (not a scale after): the lightmap chart is laid out per face area
70
- return apply(new Mesh(Geometry.box(options.size ?? 1), options.material), options)
71
- }
72
-
73
- static sphere(options: MeshOptions & SphereOptions = {}): Mesh {
74
- return apply(new Mesh(Geometry.sphere(options), options.material), options)
75
- }
76
-
77
- static cylinder(options: MeshOptions & CylinderOptions = {}): Mesh {
78
- return apply(new Mesh(Geometry.cylinder(options), options.material), options)
79
- }
80
-
81
- static plane(options: MeshOptions & PlaneOptions = {}): Mesh {
82
- return apply(new Mesh(Geometry.plane(options), options.material), options)
83
- }
84
-
85
- /** Wrap a custom Geometry. */
86
- static from(geometry: Geometry, options: MeshOptions = {}): Mesh {
87
- return apply(new Mesh(geometry, options.material), options)
88
- }
89
- }
90
-
91
- const sizeToVec = (s: Vec3Like | number): [number, number, number] => (typeof s === "number" ? [ s, s, s ] : [ cx(s), cy(s), cz(s) ])
92
-
93
- const apply = (mesh: Mesh, o: MeshOptions): Mesh => {
94
- if (o.position) mesh.position = o.position
95
- if (o.eulerAngles) mesh.eulerAngles = o.eulerAngles
96
- if (o.scale !== undefined) mesh.scale = typeof o.scale === "number" ? [ o.scale, o.scale, o.scale ] : o.scale
97
- if (o.name !== undefined) mesh.name = o.name
98
- if (o.castShadows !== undefined) mesh.castShadows = o.castShadows
99
- if (o.receiveShadows !== undefined) mesh.receiveShadows = o.receiveShadows
100
- if (o.renderPriority !== undefined) mesh.renderPriority = o.renderPriority
101
- return mesh
102
- }
1
+ // A renderable 3D node from raw geometry — the primitive shapes (box/sphere/cylinder/capsule/plane) and
2
+ // Mesh.from for a custom Geometry. A GLB import is a different kind: see Model.load.
3
+
4
+ import { cx, cy, cz, type Vec3Like } from "../math/vec"
5
+ import {
6
+ Geometry,
7
+ type CapsuleOptions,
8
+ type CylinderOptions,
9
+ type PlaneOptions,
10
+ type SphereOptions,
11
+ } from "./Geometry"
12
+ import { Material } from "./Material"
13
+ import { Node } from "./Node"
14
+
15
+ /** Common transform/render options every primitive factory accepts. */
16
+ export type MeshOptions = {
17
+ material?: Material
18
+ position?: Vec3Like
19
+ eulerAngles?: Vec3Like
20
+ scale?: Vec3Like | number
21
+ name?: string
22
+ castShadows?: boolean
23
+ receiveShadows?: boolean
24
+ /** Coarse draw order, 0 (first) … 7 (last); default 4. See `Mesh.renderPriority`. */
25
+ renderPriority?: number
26
+ }
27
+
28
+ export class Mesh extends Node {
29
+ private _geometry?: Geometry
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, geometry.uv1, geometry.colors)
38
+ }
39
+ }
40
+
41
+ override get geometry(): Geometry | undefined { return this._geometry }
42
+ /** Replace the geometry in place — the node, its transform and its material stay, the vertex and
43
+ * index buffers are rebuilt. What an editor overlay or a debug drawer redraws with. */
44
+ setGeometry(geometry: Geometry): this {
45
+ this._geometry = geometry
46
+ _creator.setMesh(this.id, Material.idOf(this._materials![0]!), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1, geometry.colors)
47
+ // the host rebuilds the renderable for a new geometry (creator-gl createMesh destroys + builds), which
48
+ // resets its priority / culling / shadow flags to the defaults — an overlay redrawn every frame lost its
49
+ // renderPriority 7 after one frame and hid inside the model. What was set on this mesh is set again.
50
+ if (this._renderPriority !== undefined) this.renderPriority = this._renderPriority
51
+ if (this._culling !== undefined) this.culling = this._culling
52
+ if (this._castShadows !== undefined) this.castShadows = this._castShadows
53
+ if (this._receiveShadows !== undefined) this.receiveShadows = this._receiveShadows
54
+ return this
55
+ }
56
+ private _renderPriority?: number
57
+ private _culling?: boolean
58
+ private _castShadows?: boolean
59
+ private _receiveShadows?: boolean
60
+
61
+ /** A Mesh always carries a material (slot 0) — see Node.setMaterial for the slot API. */
62
+ override get material(): Material { return this._materials![0]! }
63
+ override set material(m: Material) { this.setMaterial(m, 0) }
64
+
65
+ set culling(v: boolean) { this._culling = v; _creator.setCulling(this.id, v) }
66
+ /** Coarse draw order within the frame: 0 draws first, 7 last, 4 is the default (Filament's
67
+ * renderable priority; within one priority opaque draws sort front-to-back, blended back-to-front).
68
+ * Something drawn over the scene with `Material.depthTest = false` goes in 7, so nothing drawn
69
+ * after it can cover it. Write-only; a host that predates the call ignores it. */
70
+ set renderPriority(v: number) {
71
+ this._renderPriority = v
72
+ if (_creator.setRenderPriority) _creator.setRenderPriority(this.id, Math.max(0, Math.min(7, Math.round(v))))
73
+ else if (!Mesh._warnedPriority) { Mesh._warnedPriority = true; console.warn("[mesh] this host has no setRenderPriority — renderPriority ignored") }
74
+ }
75
+ private static _warnedPriority = false
76
+ set castShadows(v: boolean) { this._castShadows = v; _creator.setCastShadows(this.id, v) }
77
+ set receiveShadows(v: boolean) { this._receiveShadows = v; _creator.setReceiveShadows(this.id, v) }
78
+
79
+ // --- factories ---
80
+
81
+ static box(options: MeshOptions & { size?: Vec3Like | number } = {}): Mesh {
82
+ // the size goes into the geometry (not a scale after): the lightmap chart is laid out per face area
83
+ return apply(new Mesh(Geometry.box(options.size ?? 1), options.material), options)
84
+ }
85
+
86
+ static sphere(options: MeshOptions & SphereOptions = {}): Mesh {
87
+ return apply(new Mesh(Geometry.sphere(options), options.material), options)
88
+ }
89
+
90
+ static cylinder(options: MeshOptions & CylinderOptions = {}): Mesh {
91
+ return apply(new Mesh(Geometry.cylinder(options), options.material), options)
92
+ }
93
+
94
+ /** A capsule along Y (`radius`, `length` between the cap centres) — a character's or a collider's shape in one mesh. */
95
+ static capsule(options: MeshOptions & CapsuleOptions = {}): Mesh {
96
+ return apply(new Mesh(Geometry.capsule(options), options.material), options)
97
+ }
98
+
99
+ static plane(options: MeshOptions & PlaneOptions = {}): Mesh {
100
+ return apply(new Mesh(Geometry.plane(options), options.material), options)
101
+ }
102
+
103
+ /** Wrap a custom Geometry. */
104
+ static from(geometry: Geometry, options: MeshOptions = {}): Mesh {
105
+ return apply(new Mesh(geometry, options.material), options)
106
+ }
107
+ }
108
+
109
+ const sizeToVec = (s: Vec3Like | number): [number, number, number] => (typeof s === "number" ? [ s, s, s ] : [ cx(s), cy(s), cz(s) ])
110
+
111
+ const apply = (mesh: Mesh, o: MeshOptions): Mesh => {
112
+ if (o.position) mesh.position = o.position
113
+ if (o.eulerAngles) mesh.eulerAngles = o.eulerAngles
114
+ if (o.scale !== undefined) mesh.scale = typeof o.scale === "number" ? [ o.scale, o.scale, o.scale ] : o.scale
115
+ if (o.name !== undefined) mesh.name = o.name
116
+ if (o.castShadows !== undefined) mesh.castShadows = o.castShadows
117
+ if (o.receiveShadows !== undefined) mesh.receiveShadows = o.receiveShadows
118
+ if (o.renderPriority !== undefined) mesh.renderPriority = o.renderPriority
119
+ return mesh
120
+ }