@needle-tools/engine 4.14.0-next.31f837e → 4.14.0-next.b2e3b1a

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 (95) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/components.needle.json +1 -1
  3. package/dist/{generateMeshBVH.worker-DFcS3P04.js → generateMeshBVH.worker-DiCnZlf3.js} +1 -1
  4. package/dist/{gltf-progressive-8voIgNp_.js → gltf-progressive-Bm_6aEi4.js} +4 -4
  5. package/dist/{gltf-progressive-BRRBj-nY.umd.cjs → gltf-progressive-BttGBXw6.umd.cjs} +3 -3
  6. package/dist/{gltf-progressive-Dkh3tG4-.min.js → gltf-progressive-T5WKTux5.min.js} +1 -1
  7. package/dist/{loader.worker-C6cXDgR1.js → loader.worker-BqODMeeW.js} +1 -1
  8. package/dist/{materialx-Dx8st96L.min.js → materialx-CJyQZtjt.min.js} +1 -1
  9. package/dist/{materialx-D66rYPqe.js → materialx-DMs1E08Z.js} +2 -2
  10. package/dist/{materialx-CxlgposR.umd.cjs → materialx-DaKKOoVk.umd.cjs} +1 -1
  11. package/dist/{needle-engine.bundle-BQXG5qbQ.umd.cjs → needle-engine.bundle-BC1QDiuv.umd.cjs} +139 -139
  12. package/dist/{needle-engine.bundle-D7w0XD7M.min.js → needle-engine.bundle-Bhgt3W8p.min.js} +134 -134
  13. package/dist/{needle-engine.bundle-Byl5i6zJ.js → needle-engine.bundle-CeQXs7Hh.js} +5966 -5880
  14. package/dist/needle-engine.d.ts +552 -31
  15. package/dist/needle-engine.js +4 -4
  16. package/dist/needle-engine.min.js +1 -1
  17. package/dist/needle-engine.umd.cjs +1 -1
  18. package/dist/{postprocessing-Ce5-UWiA.min.js → postprocessing-06AXuvdv.min.js} +2 -2
  19. package/dist/{postprocessing-BkSpxpYB.js → postprocessing-CI2x8Cln.js} +4 -4
  20. package/dist/{postprocessing-DFVElmAh.umd.cjs → postprocessing-CPDcA21P.umd.cjs} +2 -2
  21. package/dist/{three-Bad8p1pf.umd.cjs → three-BjYim-vL.umd.cjs} +47 -47
  22. package/dist/{three-DFV1-P9z.js → three-Bvk2VKbF.js} +2 -2
  23. package/dist/{three-CWn13_u1.min.js → three-IG2qPafA.min.js} +2 -2
  24. package/dist/{three-examples-CO-tx3Sp.umd.cjs → three-examples-BMmNgNCN.umd.cjs} +1 -1
  25. package/dist/{three-examples-43yqn3mL.js → three-examples-CMYCd5nH.js} +1 -1
  26. package/dist/{three-examples-DKuJVGT4.min.js → three-examples-CQl1fFZp.min.js} +1 -1
  27. package/dist/{three-mesh-ui-DyEA5HQF.min.js → three-mesh-ui-5HVE2RV-.min.js} +1 -1
  28. package/dist/{three-mesh-ui-fSAQJxdI.js → three-mesh-ui-BlakAItG.js} +1 -1
  29. package/dist/{three-mesh-ui-ChzVOraf.umd.cjs → three-mesh-ui-D828VbQp.umd.cjs} +1 -1
  30. package/dist/{vendor-pe19S9r5.min.js → vendor-BxK0WKmT.min.js} +1 -1
  31. package/dist/{vendor-B51YffMU.umd.cjs → vendor-CIDkyBaO.umd.cjs} +1 -1
  32. package/dist/{vendor-CgpZ5ivC.js → vendor-ixwD-vv2.js} +1 -1
  33. package/lib/engine/api.d.ts +1 -1
  34. package/lib/engine/api.js +1 -1
  35. package/lib/engine/api.js.map +1 -1
  36. package/lib/engine/engine_components.d.ts +1 -1
  37. package/lib/engine/engine_components.js +7 -3
  38. package/lib/engine/engine_components.js.map +1 -1
  39. package/lib/engine/engine_context.d.ts +12 -0
  40. package/lib/engine/engine_context.js +29 -0
  41. package/lib/engine/engine_context.js.map +1 -1
  42. package/lib/engine/engine_gltf_builtin_components.js +16 -1
  43. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  44. package/lib/engine/engine_materialpropertyblock.d.ts +223 -11
  45. package/lib/engine/engine_materialpropertyblock.js +303 -33
  46. package/lib/engine/engine_materialpropertyblock.js.map +1 -1
  47. package/lib/engine-components/Camera.js.map +1 -1
  48. package/lib/engine-components/DropListener.js.map +1 -1
  49. package/lib/engine-components/Duplicatable.js.map +1 -1
  50. package/lib/engine-components/GroundProjection.js.map +1 -1
  51. package/lib/engine-components/NeedleMenu.js.map +1 -1
  52. package/lib/engine-components/NestedGltf.js.map +1 -1
  53. package/lib/engine-components/ReflectionProbe.d.ts +32 -3
  54. package/lib/engine-components/ReflectionProbe.js +57 -26
  55. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  56. package/lib/engine-components/Renderer.d.ts +2 -0
  57. package/lib/engine-components/Renderer.js +30 -6
  58. package/lib/engine-components/Renderer.js.map +1 -1
  59. package/lib/engine-components/RendererLightmap.js +2 -3
  60. package/lib/engine-components/RendererLightmap.js.map +1 -1
  61. package/lib/engine-components/SeeThrough.d.ts +0 -2
  62. package/lib/engine-components/SeeThrough.js +114 -88
  63. package/lib/engine-components/SeeThrough.js.map +1 -1
  64. package/lib/engine-components/SmoothFollow.js.map +1 -1
  65. package/lib/engine-components/ui/Button.js.map +1 -1
  66. package/lib/engine-components/ui/Raycaster.js.map +1 -1
  67. package/lib/engine-components/web/ViewBox.d.ts +2 -2
  68. package/lib/engine-components/web/ViewBox.js +2 -2
  69. package/lib/engine-components/web/ViewBox.js.map +1 -1
  70. package/lib/engine-components/webxr/WebXRPlaneTracking.js.map +1 -1
  71. package/lib/engine-components/webxr/controllers/XRControllerFollow.js.map +1 -1
  72. package/lib/engine-components/webxr/controllers/XRControllerMovement.js.map +1 -1
  73. package/package.json +4 -4
  74. package/src/engine/api.ts +1 -1
  75. package/src/engine/engine_components.ts +7 -3
  76. package/src/engine/engine_context.ts +28 -2
  77. package/src/engine/engine_gltf_builtin_components.ts +17 -1
  78. package/src/engine/engine_materialpropertyblock.ts +405 -39
  79. package/src/engine-components/Camera.ts +2 -2
  80. package/src/engine-components/DropListener.ts +1 -1
  81. package/src/engine-components/Duplicatable.ts +1 -1
  82. package/src/engine-components/GroundProjection.ts +3 -0
  83. package/src/engine-components/NeedleMenu.ts +3 -0
  84. package/src/engine-components/NestedGltf.ts +1 -1
  85. package/src/engine-components/ReflectionProbe.ts +61 -30
  86. package/src/engine-components/Renderer.ts +34 -6
  87. package/src/engine-components/RendererLightmap.ts +2 -3
  88. package/src/engine-components/SeeThrough.ts +122 -107
  89. package/src/engine-components/SmoothFollow.ts +2 -2
  90. package/src/engine-components/ui/Button.ts +1 -1
  91. package/src/engine-components/ui/Raycaster.ts +1 -1
  92. package/src/engine-components/web/ViewBox.ts +9 -2
  93. package/src/engine-components/webxr/WebXRPlaneTracking.ts +3 -3
  94. package/src/engine-components/webxr/controllers/XRControllerFollow.ts +1 -1
  95. package/src/engine-components/webxr/controllers/XRControllerMovement.ts +4 -4
@@ -1,30 +1,56 @@
1
- import { Material, Object3D, Color, Texture, Vector2, Vector3, Vector4, WebGLRenderer, Scene, Camera, BufferGeometry, Group } from "three";
1
+ import { BufferGeometry, Camera, Color, Euler,Group, Material, Object3D, Scene, Texture, Vector2, Vector3, Vector4, WebGLRenderer } from "three";
2
2
 
3
- type MaterialPropertyType = number | number[] | Color | Texture | Vector2 | Vector3 | Vector4 | null;
4
3
 
4
+ // @TODO: we need to detect objects with materials both transparent and NOT transparent. These need to be updated in scene.onBeforeRender to have correct renderlists
5
+
6
+ /**
7
+ * Valid types that can be used as material property overrides
8
+ */
9
+ type MaterialPropertyType = number | number[] | Color | Texture | Vector2 | Vector3 | Vector4 | null | Euler;
10
+
11
+ /**
12
+ * Defines offset and repeat transformations for texture coordinates
13
+ */
5
14
  export interface TextureTransform {
15
+ /** UV offset applied to the texture */
6
16
  offset?: Vector2;
17
+ /** UV repeat/scale applied to the texture */
7
18
  repeat?: Vector2;
8
19
  }
9
20
 
10
- export interface PropertyBlockOverride {
21
+ /**
22
+ * Represents a single material property override with optional texture transformation
23
+ * @template T The type of the property value
24
+ */
25
+ export interface PropertyBlockOverride<T extends MaterialPropertyType = MaterialPropertyType> {
26
+ /** The name of the material property to override (e.g., "color", "map", "roughness") */
11
27
  name: string;
12
- value: MaterialPropertyType;
28
+ /** The value to set for this property */
29
+ value: T;
30
+ /** Optional texture coordinate transformation (only used when value is a Texture) */
13
31
  textureTransform?: TextureTransform;
14
32
  }
15
33
 
34
+ /**
35
+ * Utility type that extracts only non-function property names from a type
36
+ * @template T The type to extract property names from
37
+ */
16
38
  type NonFunctionPropertyNames<T> = {
17
39
  [K in keyof T]: T[K] extends Function ? never : K
18
40
  }[keyof T];
19
41
 
20
- // Centralized registry for all property block related data
42
+ /**
43
+ * Centralized registry for all property block related data.
44
+ * Uses WeakMaps to allow automatic garbage collection when objects are destroyed.
45
+ * @internal
46
+ */
21
47
  class PropertyBlockRegistry {
22
48
  // Map from object to its property block
23
49
  private objectToBlock = new WeakMap<Object3D, MaterialPropertyBlock>();
24
50
 
25
51
  // Track which materials belong to which property block (to prevent applying to wrong materials)
26
52
  // Use WeakSet for automatic cleanup when materials are garbage collected
27
- private objectToMaterials = new WeakMap<Object3D, WeakSet<Material>>();
53
+ // private objectToMaterials = new WeakMap<Object3D, WeakSet<Material>>();
28
54
 
29
55
  // Track which meshes have callbacks for which property block owners
30
56
  private meshToOwners = new WeakMap<Object3D, Set<Object3D>>();
@@ -45,21 +71,21 @@ class PropertyBlockRegistry {
45
71
 
46
72
  deleteBlock(object: Object3D): void {
47
73
  this.objectToBlock.delete(object);
48
- this.objectToMaterials.delete(object);
74
+ // this.objectToMaterials.delete(object);
49
75
  }
50
76
 
51
- addMaterial(object: Object3D, material: Material): void {
52
- let materials = this.objectToMaterials.get(object);
53
- if (!materials) {
54
- materials = new WeakSet();
55
- this.objectToMaterials.set(object, materials);
56
- }
57
- materials.add(material);
58
- }
77
+ // addMaterial(object: Object3D, material: Material): void {
78
+ // let materials = this.objectToMaterials.get(object);
79
+ // if (!materials) {
80
+ // materials = new WeakSet();
81
+ // this.objectToMaterials.set(object, materials);
82
+ // }
83
+ // materials.add(material);
84
+ // }
59
85
 
60
- hasMaterial(object: Object3D, material: Material): boolean {
61
- return this.objectToMaterials.get(object)?.has(material) ?? false;
62
- }
86
+ // hasMaterial(object: Object3D, material: Material): boolean {
87
+ // return this.objectToMaterials.get(object)?.has(material) ?? false;
88
+ // }
63
89
 
64
90
  isHooked(mesh: Object3D, owner: Object3D): boolean {
65
91
  return this.meshToOwners.get(mesh)?.has(owner) ?? false;
@@ -95,17 +121,101 @@ class PropertyBlockRegistry {
95
121
 
96
122
  const registry = new PropertyBlockRegistry();
97
123
 
124
+ /**
125
+ * MaterialPropertyBlock allows per-object material property overrides without creating new material instances.
126
+ * This is useful for rendering multiple objects with the same base material but different properties
127
+ * (e.g., different colors, textures, or shader parameters).
128
+ *
129
+ * The property block system works by:
130
+ * - Temporarily applying overrides in onBeforeRender
131
+ * - Restoring original values in onAfterRender
132
+ * - Managing shader defines and program cache keys for correct shader compilation
133
+ * - Supporting texture coordinate transforms per object
134
+ *
135
+ * Common use cases:
136
+ * - **Lightmaps**: Apply unique lightmap textures to individual objects sharing the same material
137
+ * - **Reflection Probes**: Apply different environment maps per object for localized reflections
138
+ * - **See-through effects**: Temporarily override transparency/transmission properties for X-ray effects
139
+ *
140
+ * ## Getting a MaterialPropertyBlock
141
+ *
142
+ * **Important**: Do not use the constructor directly. Instead, use the static {@link MaterialPropertyBlock.get} method:
143
+ *
144
+ * ```typescript
145
+ * const block = MaterialPropertyBlock.get(myMesh);
146
+ * ```
147
+ *
148
+ * This method will either return an existing property block or create a new one if it doesn't exist.
149
+ * It automatically:
150
+ * - Creates the property block instance
151
+ * - Registers it in the internal registry
152
+ * - Attaches the necessary render callbacks to the object
153
+ * - Handles Groups by applying overrides to all child meshes
154
+ *
155
+ * @example Basic usage
156
+ * ```typescript
157
+ * // Get or create a property block for an object
158
+ * const block = MaterialPropertyBlock.get(myMesh);
159
+ *
160
+ * // Override the color property
161
+ * block.setOverride("color", new Color(1, 0, 0));
162
+ *
163
+ * // Override a texture with custom UV transform (useful for lightmaps)
164
+ * block.setOverride("lightMap", myLightmapTexture, {
165
+ * offset: new Vector2(0.5, 0.5),
166
+ * repeat: new Vector2(2, 2)
167
+ * });
168
+ *
169
+ * // Set a shader define
170
+ * block.setDefine("USE_CUSTOM_FEATURE", 1);
171
+ * ```
172
+ *
173
+ * @example Lightmap usage
174
+ * ```typescript
175
+ * const block = MaterialPropertyBlock.get(mesh);
176
+ * block.setOverride("lightMap", lightmapTexture);
177
+ * block.setOverride("lightMapIntensity", 1.5);
178
+ * ```
179
+ *
180
+ * @example See-through effect
181
+ * ```typescript
182
+ * const block = MaterialPropertyBlock.get(mesh);
183
+ * block.setOverride("transparent", true);
184
+ * block.setOverride("opacity", 0.3);
185
+ * ```
186
+ *
187
+ * @template T The material type this property block is associated with
188
+ */
98
189
  export class MaterialPropertyBlock<T extends Material = Material> {
99
190
  private _overrides: PropertyBlockOverride[] = [];
100
191
  private _defines: Record<string, string | number | boolean> = {};
101
192
  private _object: Object3D | null = null;
102
193
 
194
+ /** The object this property block is attached to */
103
195
  get object(): Object3D | null { return this._object; }
104
196
 
105
- constructor(object: Object3D | null = null) {
197
+ /**
198
+ * Creates a new MaterialPropertyBlock
199
+ * @param object The object this property block is for (optional)
200
+ */
201
+ protected constructor(object: Object3D | null = null) {
106
202
  this._object = object;
107
203
  }
108
204
 
205
+ /**
206
+ * Gets or creates a MaterialPropertyBlock for the given object.
207
+ * This is the recommended way to obtain a property block instance.
208
+ *
209
+ * @template T The material type
210
+ * @param object The object to get/create a property block for
211
+ * @returns The MaterialPropertyBlock associated with this object
212
+ *
213
+ * @example
214
+ * ```typescript
215
+ * const block = MaterialPropertyBlock.get(myMesh);
216
+ * block.setOverride("roughness", 0.5);
217
+ * ```
218
+ */
109
219
  static get<T extends Material = Material>(object: Object3D): MaterialPropertyBlock<T> {
110
220
  let block = registry.getBlock(object);
111
221
  if (!block) {
@@ -116,6 +226,20 @@ export class MaterialPropertyBlock<T extends Material = Material> {
116
226
  return block as MaterialPropertyBlock<T>;
117
227
  }
118
228
 
229
+ /**
230
+ * Checks if an object has any property overrides
231
+ * @param object The object to check
232
+ * @returns True if the object has a property block with overrides
233
+ */
234
+ static hasOverrides(object: Object3D): boolean {
235
+ const block = registry.getBlock(object);
236
+ return block ? block.hasOverrides() : false;
237
+ }
238
+
239
+ /**
240
+ * Disposes this property block and cleans up associated resources.
241
+ * After calling dispose, this property block should not be used.
242
+ */
119
243
  dispose() {
120
244
  if (this._object) {
121
245
  registry.deleteBlock(this._object);
@@ -125,6 +249,29 @@ export class MaterialPropertyBlock<T extends Material = Material> {
125
249
  this._object = null;
126
250
  }
127
251
 
252
+ /**
253
+ * Sets or updates a material property override.
254
+ * The override will be applied to the material during rendering.
255
+ *
256
+ * @param name The name of the material property to override (e.g., "color", "map", "roughness")
257
+ * @param value The value to set
258
+ * @param textureTransform Optional UV transform (only used when value is a Texture)
259
+ *
260
+ * @example
261
+ * ```typescript
262
+ * // Override a simple property
263
+ * block.setOverride("roughness", 0.8);
264
+ *
265
+ * // Override a color
266
+ * block.setOverride("color", new Color(0xff0000));
267
+ *
268
+ * // Override a texture with UV transform
269
+ * block.setOverride("map", texture, {
270
+ * offset: new Vector2(0, 0),
271
+ * repeat: new Vector2(2, 2)
272
+ * });
273
+ * ```
274
+ */
128
275
  setOverride<K extends NonFunctionPropertyNames<T>>(name: K, value: T[K], textureTransform?: TextureTransform): void;
129
276
  setOverride(name: string, value: MaterialPropertyType, textureTransform?: TextureTransform): void;
130
277
  setOverride(name: string, value: MaterialPropertyType, textureTransform?: TextureTransform): void {
@@ -137,36 +284,87 @@ export class MaterialPropertyBlock<T extends Material = Material> {
137
284
  }
138
285
  }
139
286
 
287
+ /**
288
+ * Gets the override for a specific property with type-safe value inference
289
+ * @param name The property name to get
290
+ * @returns The PropertyBlockOverride with correctly typed value if it exists, undefined otherwise
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * const block = MaterialPropertyBlock.get<MeshStandardMaterial>(mesh);
295
+ *
296
+ * // Value is inferred as number | undefined
297
+ * const roughness = block.getOverride("roughness")?.value;
298
+ *
299
+ * // Value is inferred as Color | undefined
300
+ * const color = block.getOverride("color")?.value;
301
+ *
302
+ * // Value is inferred as Texture | null | undefined
303
+ * const map = block.getOverride("map")?.value;
304
+ *
305
+ * // Explicitly specify the type for properties not on the base material type
306
+ * const transmission = block.getOverride<number>("transmission")?.value;
307
+ *
308
+ * // Or use a more specific material type
309
+ * const physicalBlock = block as MaterialPropertyBlock<MeshPhysicalMaterial>;
310
+ * const transmissionTyped = physicalBlock.getOverride("transmission")?.value; // number
311
+ * ```
312
+ */
313
+ getOverride<K extends NonFunctionPropertyNames<T>>(name: K): PropertyBlockOverride<T[K] & MaterialPropertyType> | undefined;
314
+ getOverride<V extends MaterialPropertyType = MaterialPropertyType>(name: string): PropertyBlockOverride<V> | undefined;
140
315
  getOverride(name: string): PropertyBlockOverride | undefined {
141
316
  return this._overrides.find(o => o.name === name);
142
317
  }
143
318
 
144
- clearOverride(name: string): void {
319
+ /**
320
+ * Removes a specific property override
321
+ * @param name The property name to clear
322
+ */
323
+ removeOveride<K extends NonFunctionPropertyNames<T>>(name: K | ({} & string)): void {
145
324
  const index = this._overrides.findIndex(o => o.name === name);
146
325
  if (index >= 0) {
147
326
  this._overrides.splice(index, 1);
148
327
  }
149
328
  }
150
329
 
330
+ /**
331
+ * Removes all property overrides from this block
332
+ */
151
333
  clearAllOverrides(): void {
152
334
  this._overrides = [];
153
335
  }
154
336
 
155
- removeOverride(name: string): void {
156
- this.clearOverride(name);
157
- }
158
-
159
- get overrides(): PropertyBlockOverride[] {
337
+ /**
338
+ * Gets all property overrides as a readonly array
339
+ * @returns Array of all property overrides
340
+ */
341
+ get overrides(): readonly PropertyBlockOverride[] {
160
342
  return this._overrides;
161
343
  }
162
344
 
345
+ /**
346
+ * Checks if this property block has any overrides
347
+ * @returns True if there are any overrides set
348
+ */
163
349
  hasOverrides(): boolean {
164
350
  return this._overrides.length > 0;
165
351
  }
166
352
 
167
353
  /**
168
- * Set a shader define that will be included in the program cache key
169
- * This allows different objects sharing the same material to have different shader programs
354
+ * Set a shader define that will be included in the program cache key.
355
+ * This allows different objects sharing the same material to have different shader programs.
356
+ *
357
+ * Defines affect shader compilation and are useful for enabling/disabling features per-object.
358
+ *
359
+ * @param name The define name (e.g., "USE_LIGHTMAP", "ENABLE_REFLECTIONS")
360
+ * @param value The define value (typically a boolean, number, or string)
361
+ *
362
+ * @example
363
+ * ```typescript
364
+ * // Enable a feature for this specific object
365
+ * block.setDefine("USE_CUSTOM_SHADER", true);
366
+ * block.setDefine("QUALITY_LEVEL", 2);
367
+ * ```
170
368
  */
171
369
  setDefine(name: string, value: string | number | boolean): void {
172
370
  this._defines[name] = value;
@@ -174,6 +372,7 @@ export class MaterialPropertyBlock<T extends Material = Material> {
174
372
 
175
373
  /**
176
374
  * Remove a shader define
375
+ * @param name The define name to remove
177
376
  */
178
377
  clearDefine(name: string): void {
179
378
  this._defines[name] = undefined as any;
@@ -181,11 +380,20 @@ export class MaterialPropertyBlock<T extends Material = Material> {
181
380
 
182
381
  /**
183
382
  * Get all defines set on this property block
383
+ * @returns A readonly record of all defines
184
384
  */
185
385
  getDefines(): Readonly<Record<string, string | number | boolean>> {
186
386
  return this._defines;
187
387
  }
188
388
 
389
+ /**
390
+ * Generates a cache key based on the current overrides and defines.
391
+ * This key is used internally to ensure correct shader program selection
392
+ * when objects share materials but have different property blocks.
393
+ *
394
+ * @returns A string representing the current state of this property block
395
+ * @internal
396
+ */
189
397
  getCacheKey(): string {
190
398
  const parts: string[] = [];
191
399
 
@@ -227,13 +435,25 @@ export class MaterialPropertyBlock<T extends Material = Material> {
227
435
  }
228
436
  }
229
437
 
438
+ /**
439
+ * Symbol used to store original material values on the material object
440
+ * @internal
441
+ */
230
442
  const $originalValues = Symbol("originalValues");
231
443
 
444
+ /**
445
+ * Stores an original material property value before override
446
+ * @internal
447
+ */
232
448
  interface OriginalValue {
233
449
  name: string;
234
450
  value: unknown;
235
451
  }
236
452
 
453
+ /**
454
+ * Stores saved texture transform state for restoration
455
+ * @internal
456
+ */
237
457
  interface SavedTextureTransform {
238
458
  name: string;
239
459
  offsetX: number;
@@ -242,11 +462,22 @@ interface SavedTextureTransform {
242
462
  repeatY: number;
243
463
  }
244
464
 
465
+ /**
466
+ * Symbol used to store saved texture transforms on the material object
467
+ * @internal
468
+ */
245
469
  const $savedTextureTransforms = Symbol("savedTextureTransforms");
246
470
 
471
+ /**
472
+ * Type for Three.js object render callbacks
473
+ * @internal
474
+ */
247
475
  type ObjectRenderCallback = (this: Object3D, renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group) => void;
248
476
 
249
- // Collect all materials from an object and its children
477
+ /**
478
+ * Collect all materials from an object and its children
479
+ * @internal
480
+ */
250
481
  function collectMaterials(object: Object3D, materials: Set<Material>): void {
251
482
  const obj = object as Object3D & { material?: Material | Material[] };
252
483
  if (obj.material) {
@@ -263,8 +494,14 @@ function collectMaterials(object: Object3D, materials: Set<Material>): void {
263
494
  }
264
495
  }
265
496
 
266
- // Find property block by checking this object and parent (if parent is a Group)
267
- // Returns both the block and the owner object
497
+ /**
498
+ * Find property block by checking this object and parent (if parent is a Group).
499
+ * Returns both the block and the owner object.
500
+ *
501
+ * @param obj The object to search from
502
+ * @returns The property block and its owner object, or undefined if not found
503
+ * @internal
504
+ */
268
505
  function findPropertyBlockAndOwner(obj: Object3D): { block: MaterialPropertyBlock; owner: Object3D } | undefined {
269
506
  // First check if this object itself has a property block
270
507
  let block = registry.getBlock(obj);
@@ -279,8 +516,93 @@ function findPropertyBlockAndOwner(obj: Object3D): { block: MaterialPropertyBloc
279
516
  return undefined;
280
517
  }
281
518
 
519
+ /**
520
+ * Symbol to track which materials are currently being rendered for an object
521
+ * @internal
522
+ */
523
+ const currentlyRenderingFlag = Symbol("beforeRenderingFlag");
524
+
525
+ /**
526
+ * Tracks original transparent values that were changed during render list building
527
+ * @internal
528
+ */
529
+ const beforeRenderListTransparentChanged = new WeakMap<Object3D, boolean>();
530
+
531
+ /**
532
+ * Tracks original transmission values that were changed during render list building
533
+ * @internal
534
+ */
535
+ const beforeRenderListTransparentChangedTransmission = new WeakMap<Object3D, number>();
536
+
537
+ /**
538
+ * Callback invoked before an object is added to the render list.
539
+ * Used to temporarily override transparency/transmission for correct render list assignment.
540
+ * @internal
541
+ */
542
+ const onBeforeRenderListPush = function (this: Object3D, _object: Object3D, _geometry: BufferGeometry, material: Material, _group: Group) {
543
+ const block = registry.getBlock(_object);
544
+ if (!block) {
545
+ return;
546
+ }
547
+ if (block.hasOverrides()) {
548
+ const transmission = block.getOverride<number>("transmission")?.value;
549
+ const transparent = block.getOverride("transparent")?.value;
550
+
551
+ if (transmission !== undefined && typeof transmission === "number" && "transmission" in material && transmission !== material.transmission) {
552
+ beforeRenderListTransparentChangedTransmission.set(this, material.transmission as number);
553
+ material.transmission = transmission;
554
+ }
555
+ if (transparent !== undefined && typeof transparent === "boolean" && transparent !== material.transparent) {
556
+ beforeRenderListTransparentChanged.set(this, material.transparent);
557
+ material.transparent = transparent;
558
+ }
559
+ }
560
+ }
561
+ /**
562
+ * Callback invoked after an object is added to the render list.
563
+ * Restores the original transparency/transmission values that were overridden in onBeforeRenderListPush.
564
+ * @internal
565
+ */
566
+ const onAfterRenderListPush = function (this: Object3D, _object: Object3D, _geometry: BufferGeometry, material: Material, _group: Group) {
567
+ const prevTransparent = beforeRenderListTransparentChanged.get(_object);
568
+ if (prevTransparent !== undefined) {
569
+ beforeRenderListTransparentChanged.delete(_object);
570
+ material.transparent = prevTransparent;
571
+ }
572
+
573
+ const prevTransmission = beforeRenderListTransparentChangedTransmission.get(_object);
574
+ if (prevTransmission !== undefined) {
575
+ beforeRenderListTransparentChangedTransmission.delete(_object);
576
+ (material as any).transmission = prevTransmission;
577
+ }
578
+ }
579
+
282
580
  // #region OnBeforeRender
581
+ /**
582
+ * Main callback invoked before rendering an object.
583
+ * Applies property block overrides and defines to the material.
584
+ * @internal
585
+ */
283
586
  const onBeforeRender_MaterialBlock: ObjectRenderCallback = function (this: Object3D, _renderer: WebGLRenderer, _scene: Scene, _camera: Camera, _geometry: BufferGeometry, material: Material, _group: Group) {
587
+
588
+
589
+ // Only run if the material belongs to this object and is a "regular" material (not depth or other override material)
590
+ const materials = (this as any).material as Array<Material> | Material | undefined;
591
+ if (!materials) return;
592
+ if (Array.isArray(materials)) {
593
+ if (!materials.includes(material)) return;
594
+ }
595
+ else if (materials !== material) {
596
+ return;
597
+ }
598
+
599
+ // Keep track of which materials rendering started for so we can check in onAfterRender if it was processed
600
+ // (in case of override materials like depth material where onBeforeRender runs but we don't want to apply overrides)
601
+ if (this[currentlyRenderingFlag] === undefined) this[currentlyRenderingFlag] = new WeakSet<Material>();
602
+ this[currentlyRenderingFlag].add(material);
603
+
604
+
605
+ // Before rendering, check if this object (or its parent Group) has a property block with overrides for this material.
284
606
  const result = findPropertyBlockAndOwner(this);
285
607
  if (!result) {
286
608
  return;
@@ -289,9 +611,9 @@ const onBeforeRender_MaterialBlock: ObjectRenderCallback = function (this: Objec
289
611
  const { block: propertyBlock, owner } = result;
290
612
 
291
613
  // Only apply if this material was registered with this property block
292
- if (!registry.hasMaterial(owner, material)) {
293
- return;
294
- }
614
+ // if (!registry.hasMaterial(owner, material)) {
615
+ // return;
616
+ // }
295
617
 
296
618
  const overrides = propertyBlock.overrides;
297
619
  const mat = material as any;
@@ -373,7 +695,19 @@ const onBeforeRender_MaterialBlock: ObjectRenderCallback = function (this: Objec
373
695
  };
374
696
 
375
697
  // #region OnAfterRender
698
+ /**
699
+ * Main callback invoked after rendering an object.
700
+ * Restores the original material property values and defines.
701
+ * @internal
702
+ */
376
703
  const onAfterRender_MaterialBlock: ObjectRenderCallback = function (this: Object3D, _renderer: WebGLRenderer, _scene: Scene, _camera: Camera, _geometry: BufferGeometry, material: Material, _group: Group) {
704
+
705
+ // We don't want to run this logic if onBeforeRender didn't run for this material (e.g. due to DepthMaterial or other override material), so we check the flag set in onBeforeRender
706
+ if (this[currentlyRenderingFlag] === undefined) return;
707
+ if (!this[currentlyRenderingFlag].has(material)) return;
708
+ this[currentlyRenderingFlag].delete(material);
709
+
710
+
377
711
  const result = findPropertyBlockAndOwner(this);
378
712
  if (!result) {
379
713
  return;
@@ -382,9 +716,9 @@ const onAfterRender_MaterialBlock: ObjectRenderCallback = function (this: Object
382
716
  const { block: propertyBlock, owner } = result;
383
717
 
384
718
  // Only restore if this material was registered with this property block
385
- if (!registry.hasMaterial(owner, material)) {
386
- return;
387
- }
719
+ // if (!registry.hasMaterial(owner, material)) {
720
+ // return;
721
+ // }
388
722
 
389
723
  const overrides = propertyBlock.overrides;
390
724
 
@@ -446,11 +780,17 @@ const onAfterRender_MaterialBlock: ObjectRenderCallback = function (this: Object
446
780
 
447
781
 
448
782
  // #region Attach Callbacks
783
+ /**
784
+ * Attaches the property block render callbacks to an object and its child meshes.
785
+ * @param object The object to attach callbacks to
786
+ * @param _propertyBlock The property block being attached (unused but kept for clarity)
787
+ * @internal
788
+ */
449
789
  function attachPropertyBlockToObject(object: Object3D, _propertyBlock: MaterialPropertyBlock): void {
450
790
  // Collect and register all materials that belong to this property block
451
- const materials = new Set<Material>();
452
- collectMaterials(object, materials);
453
- materials.forEach(mat => registry.addMaterial(object, mat));
791
+ // const materials = new Set<Material>();
792
+ // collectMaterials(object, materials);
793
+ // materials.forEach(mat => registry.addMaterial(object, mat));
454
794
 
455
795
  // Attach callbacks to renderable objects (Mesh, SkinnedMesh)
456
796
  // Groups don't render themselves but we still need to handle child meshes
@@ -464,6 +804,13 @@ function attachPropertyBlockToObject(object: Object3D, _propertyBlock: MaterialP
464
804
  attachCallbacksToMesh(object, object);
465
805
  }
466
806
  }
807
+ /**
808
+ * Attaches render callbacks to a specific mesh object.
809
+ * Chains with existing callbacks if they exist.
810
+ * @param mesh The mesh to attach callbacks to
811
+ * @param propertyBlockOwner The object that owns the property block (may be the mesh itself or its parent Group)
812
+ * @internal
813
+ */
467
814
  function attachCallbacksToMesh(mesh: Object3D, propertyBlockOwner: Object3D): void {
468
815
  // Check if this specific mesh already has our callbacks attached for this property block owner
469
816
  if (registry.isHooked(mesh, propertyBlockOwner)) {
@@ -492,9 +839,28 @@ function attachCallbacksToMesh(mesh: Object3D, propertyBlockOwner: Object3D): vo
492
839
  original.call(this, renderer, scene, camera, geometry, material, group);
493
840
  };
494
841
  }
842
+
843
+ /** @ts-ignore patched in three.js */
844
+ mesh.onBeforeRenderListPush = onBeforeRenderListPush;
845
+ /** @ts-ignore patched in three.js */
846
+ mesh.onAfterRenderListPush = onAfterRenderListPush;
847
+
495
848
  }
496
849
  //#endregion
497
850
 
851
+ /**
852
+ * Checks if an object has a MaterialPropertyBlock attached to it.
853
+ *
854
+ * @param object The object to check
855
+ * @returns True if the object has a property block registered
856
+ *
857
+ * @example
858
+ * ```typescript
859
+ * if (objectHasPropertyBlock(myMesh)) {
860
+ * console.log("This mesh has property overrides");
861
+ * }
862
+ * ```
863
+ */
498
864
  export function objectHasPropertyBlock(object: Object3D): boolean {
499
865
  return registry.getBlock(object) !== undefined;
500
866
  }
@@ -132,7 +132,7 @@ export class Camera extends Behaviour implements ICamera {
132
132
  */
133
133
  get nearClipPlane(): number { return this._nearClipPlane; }
134
134
  @serializable()
135
- set nearClipPlane(val) {
135
+ set nearClipPlane(val: number) {
136
136
  const changed = this._nearClipPlane != val;
137
137
  this._nearClipPlane = val;
138
138
  if (this._cam && (changed || this._cam.near != val)) {
@@ -149,7 +149,7 @@ export class Camera extends Behaviour implements ICamera {
149
149
  */
150
150
  get farClipPlane(): number { return this._farClipPlane; }
151
151
  @serializable()
152
- set farClipPlane(val) {
152
+ set farClipPlane(val: number) {
153
153
  const changed = this._farClipPlane != val;
154
154
  this._farClipPlane = val;
155
155
  if (this._cam && (changed || this._cam.far != val)) {
@@ -181,7 +181,7 @@ export class DropListener extends Behaviour {
181
181
  * Only used when fitIntoVolume is enabled.
182
182
  */
183
183
  @serializable(Vector3)
184
- fitVolumeSize = new Vector3(1, 1, 1);
184
+ fitVolumeSize: Vector3 = new Vector3(1, 1, 1);
185
185
 
186
186
  /**
187
187
  * When enabled, dropped objects will be positioned at the point where the cursor hit the scene.
@@ -66,7 +66,7 @@ export class Duplicatable extends Behaviour implements IPointerEventHandler {
66
66
  * @default 60
67
67
  */
68
68
  @serializable()
69
- limitCount = 60;
69
+ limitCount: number = 60;
70
70
 
71
71
  private _currentCount = 0;
72
72
  private _startPosition: Vector3 | null = null;
@@ -5,7 +5,10 @@ import { Gizmos } from "../engine/engine_gizmos.js";
5
5
  import { serializable } from "../engine/engine_serialization_decorator.js";
6
6
  import { getBoundingBox, getTempVector, getWorldScale, Graphics, setVisibleInCustomShadowRendering, setWorldPosition } from "../engine/engine_three_utils.js";
7
7
  import { delayForFrames, getParam, Watch as Watch } from "../engine/engine_utils.js";
8
+ // Type-only imports for TSDoc @see links
9
+ import type { Camera } from "./Camera.js";
8
10
  import { Behaviour } from "./Component.js";
11
+ import type { ContactShadows } from "./ContactShadows.js";
9
12
 
10
13
  const debug = getParam("debuggroundprojection");
11
14