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

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 (142) 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-D7w0XD7M.min.js → needle-engine.bundle-BwfaInTa.min.js} +156 -148
  12. package/dist/{needle-engine.bundle-BQXG5qbQ.umd.cjs → needle-engine.bundle-DJE-Bjpa.umd.cjs} +141 -133
  13. package/dist/{needle-engine.bundle-Byl5i6zJ.js → needle-engine.bundle-TmE5-_na.js} +6741 -6449
  14. package/dist/needle-engine.d.ts +896 -51
  15. package/dist/needle-engine.js +571 -570
  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 +203 -18
  34. package/lib/engine/api.js +271 -18
  35. package/lib/engine/api.js.map +1 -1
  36. package/lib/engine/engine_accessibility.d.ts +58 -0
  37. package/lib/engine/engine_accessibility.js +143 -0
  38. package/lib/engine/engine_accessibility.js.map +1 -0
  39. package/lib/engine/engine_context.d.ts +2 -0
  40. package/lib/engine/engine_context.js +7 -0
  41. package/lib/engine/engine_context.js.map +1 -1
  42. package/lib/engine/engine_materialpropertyblock.d.ts +309 -11
  43. package/lib/engine/engine_materialpropertyblock.js +396 -36
  44. package/lib/engine/engine_materialpropertyblock.js.map +1 -1
  45. package/lib/engine/engine_math.d.ts +34 -1
  46. package/lib/engine/engine_math.js +34 -1
  47. package/lib/engine/engine_math.js.map +1 -1
  48. package/lib/engine/engine_networking.js +1 -1
  49. package/lib/engine/engine_networking.js.map +1 -1
  50. package/lib/engine/engine_types.d.ts +2 -0
  51. package/lib/engine/engine_types.js +2 -0
  52. package/lib/engine/engine_types.js.map +1 -1
  53. package/lib/engine/webcomponents/icons.js +3 -0
  54. package/lib/engine/webcomponents/icons.js.map +1 -1
  55. package/lib/engine/webcomponents/logo-element.d.ts +1 -0
  56. package/lib/engine/webcomponents/logo-element.js +3 -1
  57. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  58. package/lib/engine/webcomponents/needle-button.d.ts +37 -11
  59. package/lib/engine/webcomponents/needle-button.js +42 -11
  60. package/lib/engine/webcomponents/needle-button.js.map +1 -1
  61. package/lib/engine/webcomponents/needle-engine.d.ts +10 -2
  62. package/lib/engine/webcomponents/needle-engine.js +13 -3
  63. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  64. package/lib/engine-components/Camera.js.map +1 -1
  65. package/lib/engine-components/Component.d.ts +1 -2
  66. package/lib/engine-components/Component.js +1 -2
  67. package/lib/engine-components/Component.js.map +1 -1
  68. package/lib/engine-components/DragControls.d.ts +1 -0
  69. package/lib/engine-components/DragControls.js +21 -0
  70. package/lib/engine-components/DragControls.js.map +1 -1
  71. package/lib/engine-components/DropListener.js.map +1 -1
  72. package/lib/engine-components/Duplicatable.js.map +1 -1
  73. package/lib/engine-components/GroundProjection.js.map +1 -1
  74. package/lib/engine-components/NeedleMenu.d.ts +2 -0
  75. package/lib/engine-components/NeedleMenu.js +2 -0
  76. package/lib/engine-components/NeedleMenu.js.map +1 -1
  77. package/lib/engine-components/NestedGltf.js.map +1 -1
  78. package/lib/engine-components/Networking.d.ts +28 -3
  79. package/lib/engine-components/Networking.js +28 -3
  80. package/lib/engine-components/Networking.js.map +1 -1
  81. package/lib/engine-components/ReflectionProbe.d.ts +33 -3
  82. package/lib/engine-components/ReflectionProbe.js +76 -27
  83. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  84. package/lib/engine-components/Renderer.d.ts +2 -0
  85. package/lib/engine-components/Renderer.js +30 -6
  86. package/lib/engine-components/Renderer.js.map +1 -1
  87. package/lib/engine-components/RendererLightmap.js +2 -3
  88. package/lib/engine-components/RendererLightmap.js.map +1 -1
  89. package/lib/engine-components/SeeThrough.d.ts +0 -2
  90. package/lib/engine-components/SeeThrough.js +114 -88
  91. package/lib/engine-components/SeeThrough.js.map +1 -1
  92. package/lib/engine-components/SmoothFollow.js.map +1 -1
  93. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +107 -13
  94. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +167 -30
  95. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
  96. package/lib/engine-components/ui/Button.d.ts +1 -0
  97. package/lib/engine-components/ui/Button.js +11 -0
  98. package/lib/engine-components/ui/Button.js.map +1 -1
  99. package/lib/engine-components/ui/Raycaster.js.map +1 -1
  100. package/lib/engine-components/ui/Text.d.ts +1 -0
  101. package/lib/engine-components/ui/Text.js +11 -0
  102. package/lib/engine-components/ui/Text.js.map +1 -1
  103. package/lib/engine-components/web/ViewBox.d.ts +2 -2
  104. package/lib/engine-components/web/ViewBox.js +2 -2
  105. package/lib/engine-components/web/ViewBox.js.map +1 -1
  106. package/lib/engine-components/webxr/WebXRPlaneTracking.js.map +1 -1
  107. package/lib/engine-components/webxr/controllers/XRControllerFollow.js.map +1 -1
  108. package/lib/engine-components/webxr/controllers/XRControllerMovement.js.map +1 -1
  109. package/package.json +4 -4
  110. package/src/engine/api.ts +371 -19
  111. package/src/engine/engine_accessibility.ts +178 -0
  112. package/src/engine/engine_context.ts +9 -0
  113. package/src/engine/engine_materialpropertyblock.ts +499 -42
  114. package/src/engine/engine_math.ts +34 -1
  115. package/src/engine/engine_networking.ts +1 -1
  116. package/src/engine/engine_types.ts +5 -0
  117. package/src/engine/webcomponents/icons.ts +3 -0
  118. package/src/engine/webcomponents/logo-element.ts +4 -1
  119. package/src/engine/webcomponents/needle-button.ts +44 -13
  120. package/src/engine/webcomponents/needle-engine.ts +18 -7
  121. package/src/engine-components/Camera.ts +2 -2
  122. package/src/engine-components/Component.ts +1 -3
  123. package/src/engine-components/DragControls.ts +29 -4
  124. package/src/engine-components/DropListener.ts +1 -1
  125. package/src/engine-components/Duplicatable.ts +1 -1
  126. package/src/engine-components/GroundProjection.ts +3 -0
  127. package/src/engine-components/NeedleMenu.ts +8 -3
  128. package/src/engine-components/NestedGltf.ts +1 -1
  129. package/src/engine-components/Networking.ts +29 -4
  130. package/src/engine-components/ReflectionProbe.ts +81 -31
  131. package/src/engine-components/Renderer.ts +34 -6
  132. package/src/engine-components/RendererLightmap.ts +2 -3
  133. package/src/engine-components/SeeThrough.ts +122 -107
  134. package/src/engine-components/SmoothFollow.ts +2 -2
  135. package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +198 -65
  136. package/src/engine-components/ui/Button.ts +13 -1
  137. package/src/engine-components/ui/Raycaster.ts +1 -1
  138. package/src/engine-components/ui/Text.ts +13 -0
  139. package/src/engine-components/web/ViewBox.ts +9 -2
  140. package/src/engine-components/webxr/WebXRPlaneTracking.ts +3 -3
  141. package/src/engine-components/webxr/controllers/XRControllerFollow.ts +1 -1
  142. 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,130 @@ 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
+ * ## How Property Blocks Work
130
+ *
131
+ * **Important**: Overrides are registered on the **Object3D**, not on the material.
132
+ * This means:
133
+ * - If you change the object's material, the overrides will still be applied to the new material
134
+ * - Multiple objects can share the same material but have different property overrides
135
+ * - If you don't want overrides applied after changing a material, you must remove them using {@link removeOveride}, {@link clearAllOverrides}, or {@link dispose}
136
+ *
137
+ * The property block system works by:
138
+ * - Temporarily applying overrides in onBeforeRender
139
+ * - Restoring original values in onAfterRender
140
+ * - Managing shader defines and program cache keys for correct shader compilation
141
+ * - Supporting texture coordinate transforms per object
142
+ *
143
+ * ## Common Use Cases
144
+ *
145
+ * - **Lightmaps**: Apply unique lightmap textures to individual objects sharing the same material
146
+ * - **Reflection Probes**: Apply different environment maps per object for localized reflections
147
+ * - **See-through effects**: Temporarily override transparency/transmission properties for X-ray effects
148
+ *
149
+ * ## Getting a MaterialPropertyBlock
150
+ *
151
+ * **Important**: Do not use the constructor directly. Instead, use the static {@link MaterialPropertyBlock.get} method:
152
+ *
153
+ * ```typescript
154
+ * const block = MaterialPropertyBlock.get(myMesh);
155
+ * ```
156
+ *
157
+ * This method will either return an existing property block or create a new one if it doesn't exist.
158
+ * It automatically:
159
+ * - Creates the property block instance
160
+ * - Registers it in the internal registry
161
+ * - Attaches the necessary render callbacks to the object
162
+ * - Handles Groups by applying overrides to all child meshes
163
+ *
164
+ * @example Basic usage
165
+ * ```typescript
166
+ * // Get or create a property block for an object
167
+ * const block = MaterialPropertyBlock.get(myMesh);
168
+ *
169
+ * // Override the color property
170
+ * block.setOverride("color", new Color(1, 0, 0));
171
+ *
172
+ * // Override a texture with custom UV transform (useful for lightmaps)
173
+ * block.setOverride("lightMap", myLightmapTexture, {
174
+ * offset: new Vector2(0.5, 0.5),
175
+ * repeat: new Vector2(2, 2)
176
+ * });
177
+ *
178
+ * // Set a shader define
179
+ * block.setDefine("USE_CUSTOM_FEATURE", 1);
180
+ * ```
181
+ *
182
+ * @example Material swapping behavior
183
+ * ```typescript
184
+ * const mesh = new Mesh(geometry, materialA);
185
+ * const block = MaterialPropertyBlock.get(mesh);
186
+ * block.setOverride("color", new Color(1, 0, 0));
187
+ *
188
+ * // The color override is red for materialA
189
+ *
190
+ * // Swap the material - overrides persist and apply to the new material!
191
+ * mesh.material = materialB;
192
+ * // The color override is now red for materialB too
193
+ *
194
+ * // If you don't want overrides on the new material, remove them:
195
+ * block.clearAllOverrides(); // Remove all overrides
196
+ * // or
197
+ * block.removeOveride("color"); // Remove specific override
198
+ * // or
199
+ * block.dispose(); // Remove the entire property block
200
+ * ```
201
+ *
202
+ * @example Lightmap usage
203
+ * ```typescript
204
+ * const block = MaterialPropertyBlock.get(mesh);
205
+ * block.setOverride("lightMap", lightmapTexture);
206
+ * block.setOverride("lightMapIntensity", 1.5);
207
+ * ```
208
+ *
209
+ * @example See-through effect
210
+ * ```typescript
211
+ * const block = MaterialPropertyBlock.get(mesh);
212
+ * block.setOverride("transparent", true);
213
+ * block.setOverride("opacity", 0.3);
214
+ * ```
215
+ *
216
+ * @template T The material type this property block is associated with
217
+ */
98
218
  export class MaterialPropertyBlock<T extends Material = Material> {
99
219
  private _overrides: PropertyBlockOverride[] = [];
100
220
  private _defines: Record<string, string | number | boolean> = {};
101
221
  private _object: Object3D | null = null;
102
222
 
223
+ /** The object this property block is attached to */
103
224
  get object(): Object3D | null { return this._object; }
104
225
 
105
- constructor(object: Object3D | null = null) {
226
+ /**
227
+ * Creates a new MaterialPropertyBlock
228
+ * @param object The object this property block is for (optional)
229
+ */
230
+ protected constructor(object: Object3D | null = null) {
106
231
  this._object = object;
107
232
  }
108
233
 
234
+ /**
235
+ * Gets or creates a MaterialPropertyBlock for the given object.
236
+ * This is the recommended way to obtain a property block instance.
237
+ *
238
+ * @template T The material type
239
+ * @param object The object to get/create a property block for
240
+ * @returns The MaterialPropertyBlock associated with this object
241
+ *
242
+ * @example
243
+ * ```typescript
244
+ * const block = MaterialPropertyBlock.get(myMesh);
245
+ * block.setOverride("roughness", 0.5);
246
+ * ```
247
+ */
109
248
  static get<T extends Material = Material>(object: Object3D): MaterialPropertyBlock<T> {
110
249
  let block = registry.getBlock(object);
111
250
  if (!block) {
@@ -116,6 +255,20 @@ export class MaterialPropertyBlock<T extends Material = Material> {
116
255
  return block as MaterialPropertyBlock<T>;
117
256
  }
118
257
 
258
+ /**
259
+ * Checks if an object has any property overrides
260
+ * @param object The object to check
261
+ * @returns True if the object has a property block with overrides
262
+ */
263
+ static hasOverrides(object: Object3D): boolean {
264
+ const block = registry.getBlock(object);
265
+ return block ? block.hasOverrides() : false;
266
+ }
267
+
268
+ /**
269
+ * Disposes this property block and cleans up associated resources.
270
+ * After calling dispose, this property block should not be used.
271
+ */
119
272
  dispose() {
120
273
  if (this._object) {
121
274
  registry.deleteBlock(this._object);
@@ -125,6 +278,29 @@ export class MaterialPropertyBlock<T extends Material = Material> {
125
278
  this._object = null;
126
279
  }
127
280
 
281
+ /**
282
+ * Sets or updates a material property override.
283
+ * The override will be applied to the material during rendering.
284
+ *
285
+ * @param name The name of the material property to override (e.g., "color", "map", "roughness")
286
+ * @param value The value to set
287
+ * @param textureTransform Optional UV transform (only used when value is a Texture)
288
+ *
289
+ * @example
290
+ * ```typescript
291
+ * // Override a simple property
292
+ * block.setOverride("roughness", 0.8);
293
+ *
294
+ * // Override a color
295
+ * block.setOverride("color", new Color(0xff0000));
296
+ *
297
+ * // Override a texture with UV transform
298
+ * block.setOverride("map", texture, {
299
+ * offset: new Vector2(0, 0),
300
+ * repeat: new Vector2(2, 2)
301
+ * });
302
+ * ```
303
+ */
128
304
  setOverride<K extends NonFunctionPropertyNames<T>>(name: K, value: T[K], textureTransform?: TextureTransform): void;
129
305
  setOverride(name: string, value: MaterialPropertyType, textureTransform?: TextureTransform): void;
130
306
  setOverride(name: string, value: MaterialPropertyType, textureTransform?: TextureTransform): void {
@@ -137,36 +313,144 @@ export class MaterialPropertyBlock<T extends Material = Material> {
137
313
  }
138
314
  }
139
315
 
316
+ /**
317
+ * Gets the override for a specific property with type-safe value inference
318
+ * @param name The property name to get
319
+ * @returns The PropertyBlockOverride with correctly typed value if it exists, undefined otherwise
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * const block = MaterialPropertyBlock.get<MeshStandardMaterial>(mesh);
324
+ *
325
+ * // Value is inferred as number | undefined
326
+ * const roughness = block.getOverride("roughness")?.value;
327
+ *
328
+ * // Value is inferred as Color | undefined
329
+ * const color = block.getOverride("color")?.value;
330
+ *
331
+ * // Value is inferred as Texture | null | undefined
332
+ * const map = block.getOverride("map")?.value;
333
+ *
334
+ * // Explicitly specify the type for properties not on the base material type
335
+ * const transmission = block.getOverride<number>("transmission")?.value;
336
+ *
337
+ * // Or use a more specific material type
338
+ * const physicalBlock = block as MaterialPropertyBlock<MeshPhysicalMaterial>;
339
+ * const transmissionTyped = physicalBlock.getOverride("transmission")?.value; // number
340
+ * ```
341
+ */
342
+ getOverride<K extends NonFunctionPropertyNames<T>>(name: K): PropertyBlockOverride<T[K] & MaterialPropertyType> | undefined;
343
+ getOverride<V extends MaterialPropertyType = MaterialPropertyType>(name: string): PropertyBlockOverride<V> | undefined;
140
344
  getOverride(name: string): PropertyBlockOverride | undefined {
141
345
  return this._overrides.find(o => o.name === name);
142
346
  }
143
347
 
144
- clearOverride(name: string): void {
348
+ /**
349
+ * Removes a specific property override.
350
+ * After removal, the material will use its original property value for this property.
351
+ *
352
+ * @param name The property name to remove the override for
353
+ *
354
+ * @example
355
+ * ```typescript
356
+ * const block = MaterialPropertyBlock.get(mesh);
357
+ *
358
+ * // Set some overrides
359
+ * block.setOverride("color", new Color(1, 0, 0));
360
+ * block.setOverride("roughness", 0.5);
361
+ * block.setOverride("lightMap", lightmapTexture);
362
+ *
363
+ * // Remove a specific override - the material will now use its original color
364
+ * block.removeOveride("color");
365
+ *
366
+ * // Other overrides (roughness, lightMap) remain active
367
+ * ```
368
+ */
369
+ removeOveride<K extends NonFunctionPropertyNames<T>>(name: K | ({} & string)): void {
145
370
  const index = this._overrides.findIndex(o => o.name === name);
146
371
  if (index >= 0) {
147
372
  this._overrides.splice(index, 1);
148
373
  }
149
374
  }
150
375
 
376
+ /**
377
+ * Removes all property overrides from this block.
378
+ * After calling this, the material will use its original values for all properties.
379
+ *
380
+ * **Note**: This does NOT remove shader defines. Use {@link clearDefine} or {@link dispose} for that.
381
+ *
382
+ * @example Remove all overrides but keep the property block
383
+ * ```typescript
384
+ * const block = MaterialPropertyBlock.get(mesh);
385
+ *
386
+ * // Set multiple overrides
387
+ * block.setOverride("color", new Color(1, 0, 0));
388
+ * block.setOverride("roughness", 0.5);
389
+ * block.setOverride("lightMap", lightmapTexture);
390
+ *
391
+ * // Later, remove all overrides at once
392
+ * block.clearAllOverrides();
393
+ *
394
+ * // The material now uses its original values
395
+ * // The property block still exists and can be reused with new overrides
396
+ * ```
397
+ *
398
+ * @example Temporarily disable all overrides
399
+ * ```typescript
400
+ * const block = MaterialPropertyBlock.get(mesh);
401
+ *
402
+ * // Save current overrides if you want to restore them later
403
+ * const savedOverrides = [...block.overrides];
404
+ *
405
+ * // Clear all overrides temporarily
406
+ * block.clearAllOverrides();
407
+ *
408
+ * // Do some rendering without overrides...
409
+ *
410
+ * // Restore overrides
411
+ * savedOverrides.forEach(override => {
412
+ * block.setOverride(override.name, override.value, override.textureTransform);
413
+ * });
414
+ * ```
415
+ *
416
+ * @see {@link removeOveride} - To remove a single override
417
+ * @see {@link dispose} - To completely remove the property block and clean up resources
418
+ */
151
419
  clearAllOverrides(): void {
152
420
  this._overrides = [];
153
421
  }
154
422
 
155
- removeOverride(name: string): void {
156
- this.clearOverride(name);
157
- }
158
-
159
- get overrides(): PropertyBlockOverride[] {
423
+ /**
424
+ * Gets all property overrides as a readonly array
425
+ * @returns Array of all property overrides
426
+ */
427
+ get overrides(): readonly PropertyBlockOverride[] {
160
428
  return this._overrides;
161
429
  }
162
430
 
431
+ /**
432
+ * Checks if this property block has any overrides
433
+ * @returns True if there are any overrides set
434
+ */
163
435
  hasOverrides(): boolean {
164
436
  return this._overrides.length > 0;
165
437
  }
166
438
 
167
439
  /**
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
440
+ * Set a shader define that will be included in the program cache key.
441
+ * This allows different objects sharing the same material to have different shader programs.
442
+ *
443
+ * Defines affect shader compilation and are useful for enabling/disabling features per-object.
444
+ *
445
+ * @param name The define name (e.g., "USE_LIGHTMAP", "ENABLE_REFLECTIONS")
446
+ * @param value The define value (typically a boolean, number, or string)
447
+ *
448
+ * @example
449
+ * ```typescript
450
+ * // Enable a feature for this specific object
451
+ * block.setDefine("USE_CUSTOM_SHADER", true);
452
+ * block.setDefine("QUALITY_LEVEL", 2);
453
+ * ```
170
454
  */
171
455
  setDefine(name: string, value: string | number | boolean): void {
172
456
  this._defines[name] = value;
@@ -174,6 +458,7 @@ export class MaterialPropertyBlock<T extends Material = Material> {
174
458
 
175
459
  /**
176
460
  * Remove a shader define
461
+ * @param name The define name to remove
177
462
  */
178
463
  clearDefine(name: string): void {
179
464
  this._defines[name] = undefined as any;
@@ -181,11 +466,20 @@ export class MaterialPropertyBlock<T extends Material = Material> {
181
466
 
182
467
  /**
183
468
  * Get all defines set on this property block
469
+ * @returns A readonly record of all defines
184
470
  */
185
471
  getDefines(): Readonly<Record<string, string | number | boolean>> {
186
472
  return this._defines;
187
473
  }
188
474
 
475
+ /**
476
+ * Generates a cache key based on the current overrides and defines.
477
+ * This key is used internally to ensure correct shader program selection
478
+ * when objects share materials but have different property blocks.
479
+ *
480
+ * @returns A string representing the current state of this property block
481
+ * @internal
482
+ */
189
483
  getCacheKey(): string {
190
484
  const parts: string[] = [];
191
485
 
@@ -227,13 +521,25 @@ export class MaterialPropertyBlock<T extends Material = Material> {
227
521
  }
228
522
  }
229
523
 
524
+ /**
525
+ * Symbol used to store original material values on the material object
526
+ * @internal
527
+ */
230
528
  const $originalValues = Symbol("originalValues");
231
529
 
530
+ /**
531
+ * Stores an original material property value before override
532
+ * @internal
533
+ */
232
534
  interface OriginalValue {
233
535
  name: string;
234
536
  value: unknown;
235
537
  }
236
538
 
539
+ /**
540
+ * Stores saved texture transform state for restoration
541
+ * @internal
542
+ */
237
543
  interface SavedTextureTransform {
238
544
  name: string;
239
545
  offsetX: number;
@@ -242,11 +548,22 @@ interface SavedTextureTransform {
242
548
  repeatY: number;
243
549
  }
244
550
 
551
+ /**
552
+ * Symbol used to store saved texture transforms on the material object
553
+ * @internal
554
+ */
245
555
  const $savedTextureTransforms = Symbol("savedTextureTransforms");
246
556
 
557
+ /**
558
+ * Type for Three.js object render callbacks
559
+ * @internal
560
+ */
247
561
  type ObjectRenderCallback = (this: Object3D, renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group) => void;
248
562
 
249
- // Collect all materials from an object and its children
563
+ /**
564
+ * Collect all materials from an object and its children
565
+ * @internal
566
+ */
250
567
  function collectMaterials(object: Object3D, materials: Set<Material>): void {
251
568
  const obj = object as Object3D & { material?: Material | Material[] };
252
569
  if (obj.material) {
@@ -263,8 +580,14 @@ function collectMaterials(object: Object3D, materials: Set<Material>): void {
263
580
  }
264
581
  }
265
582
 
266
- // Find property block by checking this object and parent (if parent is a Group)
267
- // Returns both the block and the owner object
583
+ /**
584
+ * Find property block by checking this object and parent (if parent is a Group).
585
+ * Returns both the block and the owner object.
586
+ *
587
+ * @param obj The object to search from
588
+ * @returns The property block and its owner object, or undefined if not found
589
+ * @internal
590
+ */
268
591
  function findPropertyBlockAndOwner(obj: Object3D): { block: MaterialPropertyBlock; owner: Object3D } | undefined {
269
592
  // First check if this object itself has a property block
270
593
  let block = registry.getBlock(obj);
@@ -279,8 +602,93 @@ function findPropertyBlockAndOwner(obj: Object3D): { block: MaterialPropertyBloc
279
602
  return undefined;
280
603
  }
281
604
 
605
+ /**
606
+ * Symbol to track which materials are currently being rendered for an object
607
+ * @internal
608
+ */
609
+ const currentlyRenderingFlag = Symbol("beforeRenderingFlag");
610
+
611
+ /**
612
+ * Tracks original transparent values that were changed during render list building
613
+ * @internal
614
+ */
615
+ const beforeRenderListTransparentChanged = new WeakMap<Object3D, boolean>();
616
+
617
+ /**
618
+ * Tracks original transmission values that were changed during render list building
619
+ * @internal
620
+ */
621
+ const beforeRenderListTransparentChangedTransmission = new WeakMap<Object3D, number>();
622
+
623
+ /**
624
+ * Callback invoked before an object is added to the render list.
625
+ * Used to temporarily override transparency/transmission for correct render list assignment.
626
+ * @internal
627
+ */
628
+ const onBeforeRenderListPush = function (this: Object3D, _object: Object3D, _geometry: BufferGeometry, material: Material, _group: Group) {
629
+ const block = registry.getBlock(_object);
630
+ if (!block) {
631
+ return;
632
+ }
633
+ if (block.hasOverrides()) {
634
+ const transmission = block.getOverride<number>("transmission")?.value;
635
+ const transparent = block.getOverride("transparent")?.value;
636
+
637
+ if (transmission !== undefined && typeof transmission === "number" && "transmission" in material && transmission !== material.transmission) {
638
+ beforeRenderListTransparentChangedTransmission.set(this, material.transmission as number);
639
+ material.transmission = transmission;
640
+ }
641
+ if (transparent !== undefined && typeof transparent === "boolean" && transparent !== material.transparent) {
642
+ beforeRenderListTransparentChanged.set(this, material.transparent);
643
+ material.transparent = transparent;
644
+ }
645
+ }
646
+ }
647
+ /**
648
+ * Callback invoked after an object is added to the render list.
649
+ * Restores the original transparency/transmission values that were overridden in onBeforeRenderListPush.
650
+ * @internal
651
+ */
652
+ const onAfterRenderListPush = function (this: Object3D, _object: Object3D, _geometry: BufferGeometry, material: Material, _group: Group) {
653
+ const prevTransparent = beforeRenderListTransparentChanged.get(_object);
654
+ if (prevTransparent !== undefined) {
655
+ beforeRenderListTransparentChanged.delete(_object);
656
+ material.transparent = prevTransparent;
657
+ }
658
+
659
+ const prevTransmission = beforeRenderListTransparentChangedTransmission.get(_object);
660
+ if (prevTransmission !== undefined) {
661
+ beforeRenderListTransparentChangedTransmission.delete(_object);
662
+ (material as any).transmission = prevTransmission;
663
+ }
664
+ }
665
+
282
666
  // #region OnBeforeRender
667
+ /**
668
+ * Main callback invoked before rendering an object.
669
+ * Applies property block overrides and defines to the material.
670
+ * @internal
671
+ */
283
672
  const onBeforeRender_MaterialBlock: ObjectRenderCallback = function (this: Object3D, _renderer: WebGLRenderer, _scene: Scene, _camera: Camera, _geometry: BufferGeometry, material: Material, _group: Group) {
673
+
674
+
675
+ // Only run if the material belongs to this object and is a "regular" material (not depth or other override material)
676
+ const materials = (this as any).material as Array<Material> | Material | undefined;
677
+ if (!materials) return;
678
+ if (Array.isArray(materials)) {
679
+ if (!materials.includes(material)) return;
680
+ }
681
+ else if (materials !== material) {
682
+ return;
683
+ }
684
+
685
+ // Keep track of which materials rendering started for so we can check in onAfterRender if it was processed
686
+ // (in case of override materials like depth material where onBeforeRender runs but we don't want to apply overrides)
687
+ if (this[currentlyRenderingFlag] === undefined) this[currentlyRenderingFlag] = new WeakSet<Material>();
688
+ this[currentlyRenderingFlag].add(material);
689
+
690
+
691
+ // Before rendering, check if this object (or its parent Group) has a property block with overrides for this material.
284
692
  const result = findPropertyBlockAndOwner(this);
285
693
  if (!result) {
286
694
  return;
@@ -289,9 +697,9 @@ const onBeforeRender_MaterialBlock: ObjectRenderCallback = function (this: Objec
289
697
  const { block: propertyBlock, owner } = result;
290
698
 
291
699
  // Only apply if this material was registered with this property block
292
- if (!registry.hasMaterial(owner, material)) {
293
- return;
294
- }
700
+ // if (!registry.hasMaterial(owner, material)) {
701
+ // return;
702
+ // }
295
703
 
296
704
  const overrides = propertyBlock.overrides;
297
705
  const mat = material as any;
@@ -373,7 +781,19 @@ const onBeforeRender_MaterialBlock: ObjectRenderCallback = function (this: Objec
373
781
  };
374
782
 
375
783
  // #region OnAfterRender
784
+ /**
785
+ * Main callback invoked after rendering an object.
786
+ * Restores the original material property values and defines.
787
+ * @internal
788
+ */
376
789
  const onAfterRender_MaterialBlock: ObjectRenderCallback = function (this: Object3D, _renderer: WebGLRenderer, _scene: Scene, _camera: Camera, _geometry: BufferGeometry, material: Material, _group: Group) {
790
+
791
+ // 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
792
+ if (this[currentlyRenderingFlag] === undefined) return;
793
+ if (!this[currentlyRenderingFlag].has(material)) return;
794
+ this[currentlyRenderingFlag].delete(material);
795
+
796
+
377
797
  const result = findPropertyBlockAndOwner(this);
378
798
  if (!result) {
379
799
  return;
@@ -382,9 +802,9 @@ const onAfterRender_MaterialBlock: ObjectRenderCallback = function (this: Object
382
802
  const { block: propertyBlock, owner } = result;
383
803
 
384
804
  // Only restore if this material was registered with this property block
385
- if (!registry.hasMaterial(owner, material)) {
386
- return;
387
- }
805
+ // if (!registry.hasMaterial(owner, material)) {
806
+ // return;
807
+ // }
388
808
 
389
809
  const overrides = propertyBlock.overrides;
390
810
 
@@ -446,25 +866,38 @@ const onAfterRender_MaterialBlock: ObjectRenderCallback = function (this: Object
446
866
 
447
867
 
448
868
  // #region Attach Callbacks
869
+ /**
870
+ * Attaches the property block render callbacks to an object and its child meshes.
871
+ * @param object The object to attach callbacks to
872
+ * @param _propertyBlock The property block being attached (unused but kept for clarity)
873
+ * @internal
874
+ */
449
875
  function attachPropertyBlockToObject(object: Object3D, _propertyBlock: MaterialPropertyBlock): void {
450
876
  // 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));
877
+ // const materials = new Set<Material>();
878
+ // collectMaterials(object, materials);
879
+ // materials.forEach(mat => registry.addMaterial(object, mat));
454
880
 
455
881
  // Attach callbacks to renderable objects (Mesh, SkinnedMesh)
456
882
  // Groups don't render themselves but we still need to handle child meshes
457
883
  if (object.type === "Group") {
458
884
  object.children.forEach(child => {
459
885
  if (child.type === "Mesh" || child.type === "SkinnedMesh") {
460
- attachCallbacksToMesh(child, object);
886
+ attachCallbacksToMesh(child, object, _propertyBlock);
461
887
  }
462
888
  });
463
889
  } else if (object.type === "Mesh" || object.type === "SkinnedMesh") {
464
- attachCallbacksToMesh(object, object);
890
+ attachCallbacksToMesh(object, object, _propertyBlock);
465
891
  }
466
892
  }
467
- function attachCallbacksToMesh(mesh: Object3D, propertyBlockOwner: Object3D): void {
893
+ /**
894
+ * Attaches render callbacks to a specific mesh object.
895
+ * Chains with existing callbacks if they exist.
896
+ * @param mesh The mesh to attach callbacks to
897
+ * @param propertyBlockOwner The object that owns the property block (may be the mesh itself or its parent Group)
898
+ * @internal
899
+ */
900
+ function attachCallbacksToMesh(mesh: Object3D, propertyBlockOwner: Object3D, _propertyBlock: MaterialPropertyBlock): void {
468
901
  // Check if this specific mesh already has our callbacks attached for this property block owner
469
902
  if (registry.isHooked(mesh, propertyBlockOwner)) {
470
903
  // Already hooked for this property block owner
@@ -473,6 +906,11 @@ function attachCallbacksToMesh(mesh: Object3D, propertyBlockOwner: Object3D): vo
473
906
 
474
907
  registry.addHook(mesh, propertyBlockOwner);
475
908
 
909
+ /**
910
+ * Expose the property block for e.g. Needle Inspector
911
+ */
912
+ mesh["needle:materialPropertyBlock"] = _propertyBlock;
913
+
476
914
  if (!mesh.onBeforeRender) {
477
915
  mesh.onBeforeRender = onBeforeRender_MaterialBlock;
478
916
  } else {
@@ -492,9 +930,28 @@ function attachCallbacksToMesh(mesh: Object3D, propertyBlockOwner: Object3D): vo
492
930
  original.call(this, renderer, scene, camera, geometry, material, group);
493
931
  };
494
932
  }
933
+
934
+ /** @ts-ignore patched in three.js */
935
+ mesh.onBeforeRenderListPush = onBeforeRenderListPush;
936
+ /** @ts-ignore patched in three.js */
937
+ mesh.onAfterRenderListPush = onAfterRenderListPush;
938
+
495
939
  }
496
940
  //#endregion
497
941
 
942
+ /**
943
+ * Checks if an object has a MaterialPropertyBlock attached to it.
944
+ *
945
+ * @param object The object to check
946
+ * @returns True if the object has a property block registered
947
+ *
948
+ * @example
949
+ * ```typescript
950
+ * if (objectHasPropertyBlock(myMesh)) {
951
+ * console.log("This mesh has property overrides");
952
+ * }
953
+ * ```
954
+ */
498
955
  export function objectHasPropertyBlock(object: Object3D): boolean {
499
956
  return registry.getBlock(object) !== undefined;
500
957
  }