@needle-tools/engine 4.13.1-next.9fc3e64 → 4.14.0-next.31f837e

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 (52) hide show
  1. package/dist/generateMeshBVH.worker-DFcS3P04.js +21 -0
  2. package/dist/{gltf-progressive-Dbi_Tfhb.js → gltf-progressive-8voIgNp_.js} +4 -4
  3. package/dist/{gltf-progressive-CaUGGjVL.umd.cjs → gltf-progressive-BRRBj-nY.umd.cjs} +1 -1
  4. package/dist/{gltf-progressive-DuAR0MQR.min.js → gltf-progressive-Dkh3tG4-.min.js} +1 -1
  5. package/dist/loader.worker-C6cXDgR1.js +23 -0
  6. package/dist/{materialx-BF23AVE8.umd.cjs → materialx-CxlgposR.umd.cjs} +1 -1
  7. package/dist/{materialx-fkoFuRh3.js → materialx-D66rYPqe.js} +2 -2
  8. package/dist/{materialx-B9ddsHcF.min.js → materialx-Dx8st96L.min.js} +1 -1
  9. package/dist/{needle-engine.bundle-Dqrh7aWw.umd.cjs → needle-engine.bundle-BQXG5qbQ.umd.cjs} +136 -143
  10. package/dist/{needle-engine.bundle-BZRE5G6O.js → needle-engine.bundle-Byl5i6zJ.js} +6120 -5904
  11. package/dist/{needle-engine.bundle-DwybonUg.min.js → needle-engine.bundle-D7w0XD7M.min.js} +147 -154
  12. package/dist/needle-engine.d.ts +61 -9
  13. package/dist/needle-engine.js +416 -415
  14. package/dist/needle-engine.min.js +1 -1
  15. package/dist/needle-engine.umd.cjs +1 -1
  16. package/dist/{postprocessing-DdM-tz1j.js → postprocessing-BkSpxpYB.js} +2 -2
  17. package/dist/{postprocessing-BVNrgYZK.min.js → postprocessing-Ce5-UWiA.min.js} +1 -1
  18. package/dist/{postprocessing-CI2TjWpu.umd.cjs → postprocessing-DFVElmAh.umd.cjs} +1 -1
  19. package/dist/{three-BW2s1Yl-.umd.cjs → three-Bad8p1pf.umd.cjs} +46 -46
  20. package/dist/{three-I__hSXzr.min.js → three-CWn13_u1.min.js} +33 -33
  21. package/dist/{three-VvRoMeIN.js → three-DFV1-P9z.js} +4209 -4209
  22. package/dist/{three-examples-BhfOE7NG.js → three-examples-43yqn3mL.js} +1 -1
  23. package/dist/{three-examples-Bpfu6ke_.umd.cjs → three-examples-CO-tx3Sp.umd.cjs} +1 -1
  24. package/dist/{three-examples-D8zAE_7t.min.js → three-examples-DKuJVGT4.min.js} +1 -1
  25. package/dist/{three-mesh-ui-BU55xDxJ.umd.cjs → three-mesh-ui-ChzVOraf.umd.cjs} +1 -1
  26. package/dist/{three-mesh-ui-C3QbemOV.min.js → three-mesh-ui-DyEA5HQF.min.js} +1 -1
  27. package/dist/{three-mesh-ui-CcMp-FQm.js → three-mesh-ui-fSAQJxdI.js} +1 -1
  28. package/dist/{vendor-COVQl0b8.umd.cjs → vendor-B51YffMU.umd.cjs} +1 -1
  29. package/dist/{vendor-BiyIZ61v.js → vendor-CgpZ5ivC.js} +1 -1
  30. package/dist/{vendor-DW7zqjuT.min.js → vendor-pe19S9r5.min.js} +1 -1
  31. package/lib/engine/api.d.ts +1 -0
  32. package/lib/engine/api.js +1 -0
  33. package/lib/engine/api.js.map +1 -1
  34. package/lib/engine/engine_lightdata.js +8 -6
  35. package/lib/engine/engine_lightdata.js.map +1 -1
  36. package/lib/engine/engine_materialpropertyblock.d.ts +47 -0
  37. package/lib/engine/engine_materialpropertyblock.js +412 -0
  38. package/lib/engine/engine_materialpropertyblock.js.map +1 -0
  39. package/lib/engine-components/ReflectionProbe.d.ts +0 -1
  40. package/lib/engine-components/ReflectionProbe.js +15 -76
  41. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  42. package/lib/engine-components/RendererLightmap.d.ts +13 -9
  43. package/lib/engine-components/RendererLightmap.js +68 -81
  44. package/lib/engine-components/RendererLightmap.js.map +1 -1
  45. package/package.json +2 -2
  46. package/src/engine/api.ts +1 -0
  47. package/src/engine/engine_lightdata.ts +8 -6
  48. package/src/engine/engine_materialpropertyblock.ts +500 -0
  49. package/src/engine-components/ReflectionProbe.ts +17 -89
  50. package/src/engine-components/RendererLightmap.ts +76 -87
  51. package/dist/generateMeshBVH.worker-iyfPIK6R.js +0 -21
  52. package/dist/loader.worker-C1GG9A7C.js +0 -23
@@ -0,0 +1,500 @@
1
+ import { Material, Object3D, Color, Texture, Vector2, Vector3, Vector4, WebGLRenderer, Scene, Camera, BufferGeometry, Group } from "three";
2
+
3
+ type MaterialPropertyType = number | number[] | Color | Texture | Vector2 | Vector3 | Vector4 | null;
4
+
5
+ export interface TextureTransform {
6
+ offset?: Vector2;
7
+ repeat?: Vector2;
8
+ }
9
+
10
+ export interface PropertyBlockOverride {
11
+ name: string;
12
+ value: MaterialPropertyType;
13
+ textureTransform?: TextureTransform;
14
+ }
15
+
16
+ type NonFunctionPropertyNames<T> = {
17
+ [K in keyof T]: T[K] extends Function ? never : K
18
+ }[keyof T];
19
+
20
+ // Centralized registry for all property block related data
21
+ class PropertyBlockRegistry {
22
+ // Map from object to its property block
23
+ private objectToBlock = new WeakMap<Object3D, MaterialPropertyBlock>();
24
+
25
+ // Track which materials belong to which property block (to prevent applying to wrong materials)
26
+ // Use WeakSet for automatic cleanup when materials are garbage collected
27
+ private objectToMaterials = new WeakMap<Object3D, WeakSet<Material>>();
28
+
29
+ // Track which meshes have callbacks for which property block owners
30
+ private meshToOwners = new WeakMap<Object3D, Set<Object3D>>();
31
+
32
+ // Track original callback functions for cleanup (reserved for future use)
33
+ private meshToOriginalCallbacks = new WeakMap<Object3D, {
34
+ onBeforeRender?: ObjectRenderCallback;
35
+ onAfterRender?: ObjectRenderCallback;
36
+ }>();
37
+
38
+ getBlock(object: Object3D): MaterialPropertyBlock | undefined {
39
+ return this.objectToBlock.get(object);
40
+ }
41
+
42
+ setBlock(object: Object3D, block: MaterialPropertyBlock): void {
43
+ this.objectToBlock.set(object, block);
44
+ }
45
+
46
+ deleteBlock(object: Object3D): void {
47
+ this.objectToBlock.delete(object);
48
+ this.objectToMaterials.delete(object);
49
+ }
50
+
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
+ }
59
+
60
+ hasMaterial(object: Object3D, material: Material): boolean {
61
+ return this.objectToMaterials.get(object)?.has(material) ?? false;
62
+ }
63
+
64
+ isHooked(mesh: Object3D, owner: Object3D): boolean {
65
+ return this.meshToOwners.get(mesh)?.has(owner) ?? false;
66
+ }
67
+
68
+ addHook(mesh: Object3D, owner: Object3D): void {
69
+ let owners = this.meshToOwners.get(mesh);
70
+ if (!owners) {
71
+ owners = new Set();
72
+ this.meshToOwners.set(mesh, owners);
73
+ }
74
+ owners.add(owner);
75
+ }
76
+
77
+ removeHook(mesh: Object3D, owner: Object3D): void {
78
+ const owners = this.meshToOwners.get(mesh);
79
+ if (owners) {
80
+ owners.delete(owner);
81
+ if (owners.size === 0) {
82
+ this.meshToOwners.delete(mesh);
83
+ }
84
+ }
85
+ }
86
+
87
+ getOriginalCallbacks(mesh: Object3D): { onBeforeRender?: ObjectRenderCallback; onAfterRender?: ObjectRenderCallback } | undefined {
88
+ return this.meshToOriginalCallbacks.get(mesh);
89
+ }
90
+
91
+ setOriginalCallbacks(mesh: Object3D, callbacks: { onBeforeRender?: ObjectRenderCallback; onAfterRender?: ObjectRenderCallback }): void {
92
+ this.meshToOriginalCallbacks.set(mesh, callbacks);
93
+ }
94
+ }
95
+
96
+ const registry = new PropertyBlockRegistry();
97
+
98
+ export class MaterialPropertyBlock<T extends Material = Material> {
99
+ private _overrides: PropertyBlockOverride[] = [];
100
+ private _defines: Record<string, string | number | boolean> = {};
101
+ private _object: Object3D | null = null;
102
+
103
+ get object(): Object3D | null { return this._object; }
104
+
105
+ constructor(object: Object3D | null = null) {
106
+ this._object = object;
107
+ }
108
+
109
+ static get<T extends Material = Material>(object: Object3D): MaterialPropertyBlock<T> {
110
+ let block = registry.getBlock(object);
111
+ if (!block) {
112
+ block = new MaterialPropertyBlock(object);
113
+ registry.setBlock(object, block);
114
+ attachPropertyBlockToObject(object, block);
115
+ }
116
+ return block as MaterialPropertyBlock<T>;
117
+ }
118
+
119
+ dispose() {
120
+ if (this._object) {
121
+ registry.deleteBlock(this._object);
122
+ // TODO: Add cleanup for hooked meshes
123
+ }
124
+ this._overrides = [];
125
+ this._object = null;
126
+ }
127
+
128
+ setOverride<K extends NonFunctionPropertyNames<T>>(name: K, value: T[K], textureTransform?: TextureTransform): void;
129
+ setOverride(name: string, value: MaterialPropertyType, textureTransform?: TextureTransform): void;
130
+ setOverride(name: string, value: MaterialPropertyType, textureTransform?: TextureTransform): void {
131
+ const existing = this._overrides.find(o => o.name === name);
132
+ if (existing) {
133
+ existing.value = value;
134
+ existing.textureTransform = textureTransform;
135
+ } else {
136
+ this._overrides.push({ name, value, textureTransform });
137
+ }
138
+ }
139
+
140
+ getOverride(name: string): PropertyBlockOverride | undefined {
141
+ return this._overrides.find(o => o.name === name);
142
+ }
143
+
144
+ clearOverride(name: string): void {
145
+ const index = this._overrides.findIndex(o => o.name === name);
146
+ if (index >= 0) {
147
+ this._overrides.splice(index, 1);
148
+ }
149
+ }
150
+
151
+ clearAllOverrides(): void {
152
+ this._overrides = [];
153
+ }
154
+
155
+ removeOverride(name: string): void {
156
+ this.clearOverride(name);
157
+ }
158
+
159
+ get overrides(): PropertyBlockOverride[] {
160
+ return this._overrides;
161
+ }
162
+
163
+ hasOverrides(): boolean {
164
+ return this._overrides.length > 0;
165
+ }
166
+
167
+ /**
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
170
+ */
171
+ setDefine(name: string, value: string | number | boolean): void {
172
+ this._defines[name] = value;
173
+ }
174
+
175
+ /**
176
+ * Remove a shader define
177
+ */
178
+ clearDefine(name: string): void {
179
+ this._defines[name] = undefined as any;
180
+ }
181
+
182
+ /**
183
+ * Get all defines set on this property block
184
+ */
185
+ getDefines(): Readonly<Record<string, string | number | boolean>> {
186
+ return this._defines;
187
+ }
188
+
189
+ getCacheKey(): string {
190
+ const parts: string[] = [];
191
+
192
+ // Add defines to cache key
193
+ const defineKeys = Object.keys(this._defines).sort();
194
+ for (const key of defineKeys) {
195
+ const value = this._defines[key];
196
+ if (value !== undefined) {
197
+ parts.push(`d:${key}=${value}`);
198
+ }
199
+ }
200
+
201
+ // Add overrides to cache key
202
+ for (const o of this._overrides) {
203
+ if (o.value === null) continue;
204
+ let val = "";
205
+ if (o.value instanceof Texture) {
206
+ val = (o.value as any).uuid || "texture";
207
+ if (o.textureTransform) {
208
+ const t = o.textureTransform;
209
+ if (t.offset) val += `;to:${t.offset.x},${t.offset.y}`;
210
+ if (t.repeat) val += `;tr:${t.repeat.x},${t.repeat.y}`;
211
+ }
212
+ } else if (Array.isArray(o.value)) {
213
+ val = o.value.join(",");
214
+ } else if (o.value && typeof o.value === "object" && "r" in o.value) {
215
+ const c = o.value as any;
216
+ val = `${c.r},${c.g},${c.b},${c.a !== undefined ? c.a : ""}`;
217
+ } else if (o.value && typeof o.value === "object" && "x" in o.value) {
218
+ // Vector2, Vector3, Vector4
219
+ const v = o.value as any;
220
+ val = `${v.x},${v.y}${v.z !== undefined ? `,${v.z}` : ""}${v.w !== undefined ? `,${v.w}` : ""}`;
221
+ } else {
222
+ val = String(o.value);
223
+ }
224
+ parts.push(`${o.name}=${val}`);
225
+ }
226
+ return parts.join(";");
227
+ }
228
+ }
229
+
230
+ const $originalValues = Symbol("originalValues");
231
+
232
+ interface OriginalValue {
233
+ name: string;
234
+ value: unknown;
235
+ }
236
+
237
+ interface SavedTextureTransform {
238
+ name: string;
239
+ offsetX: number;
240
+ offsetY: number;
241
+ repeatX: number;
242
+ repeatY: number;
243
+ }
244
+
245
+ const $savedTextureTransforms = Symbol("savedTextureTransforms");
246
+
247
+ type ObjectRenderCallback = (this: Object3D, renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group) => void;
248
+
249
+ // Collect all materials from an object and its children
250
+ function collectMaterials(object: Object3D, materials: Set<Material>): void {
251
+ const obj = object as Object3D & { material?: Material | Material[] };
252
+ if (obj.material) {
253
+ if (Array.isArray(obj.material)) {
254
+ obj.material.forEach(mat => materials.add(mat));
255
+ } else {
256
+ materials.add(obj.material);
257
+ }
258
+ }
259
+
260
+ // For Groups, collect materials from children too
261
+ if (object.type === "Group") {
262
+ object.children.forEach(child => collectMaterials(child, materials));
263
+ }
264
+ }
265
+
266
+ // Find property block by checking this object and parent (if parent is a Group)
267
+ // Returns both the block and the owner object
268
+ function findPropertyBlockAndOwner(obj: Object3D): { block: MaterialPropertyBlock; owner: Object3D } | undefined {
269
+ // First check if this object itself has a property block
270
+ let block = registry.getBlock(obj);
271
+ if (block) return { block, owner: obj };
272
+
273
+ // If not, check if parent is a Group and has a property block
274
+ if (obj.parent && obj.parent.type === "Group") {
275
+ block = registry.getBlock(obj.parent);
276
+ if (block) return { block, owner: obj.parent };
277
+ }
278
+
279
+ return undefined;
280
+ }
281
+
282
+ // #region OnBeforeRender
283
+ const onBeforeRender_MaterialBlock: ObjectRenderCallback = function (this: Object3D, _renderer: WebGLRenderer, _scene: Scene, _camera: Camera, _geometry: BufferGeometry, material: Material, _group: Group) {
284
+ const result = findPropertyBlockAndOwner(this);
285
+ if (!result) {
286
+ return;
287
+ }
288
+
289
+ const { block: propertyBlock, owner } = result;
290
+
291
+ // Only apply if this material was registered with this property block
292
+ if (!registry.hasMaterial(owner, material)) {
293
+ return;
294
+ }
295
+
296
+ const overrides = propertyBlock.overrides;
297
+ const mat = material as any;
298
+
299
+ // Apply defines to material - this affects shader compilation
300
+ const defines = propertyBlock.getDefines();
301
+ const defineKeys = Object.keys(defines);
302
+ if (defineKeys.length > 0) {
303
+ if (!mat.defines) mat.defines = {};
304
+ for (const key of defineKeys) {
305
+ const value = defines[key];
306
+ if (value !== undefined) {
307
+ mat.defines[key] = value;
308
+ }
309
+ }
310
+ }
311
+
312
+ // Still set up cache key even if no overrides (defines affect it)
313
+ if (overrides.length === 0 && defineKeys.length === 0) {
314
+ return;
315
+ }
316
+
317
+ // Defines always affect shader compilation → need program change
318
+ let needsProgramChange = defineKeys.length > 0;
319
+
320
+ if (!mat[$originalValues]) {
321
+ mat[$originalValues] = [];
322
+ }
323
+ const originalValues = mat[$originalValues] as OriginalValue[];
324
+
325
+ for (const override of overrides) {
326
+ if (override.value === null) continue;
327
+
328
+ const currentValue = mat[override.name];
329
+
330
+ const existingOriginal = originalValues.find((o: OriginalValue) => o.name === override.name);
331
+ if (existingOriginal) {
332
+ // Update to current value each frame so animations/external changes are preserved
333
+ existingOriginal.value = currentValue;
334
+ } else {
335
+ originalValues.push({ name: override.name, value: currentValue });
336
+ }
337
+
338
+ // Check if this override changes shader features (truthiness change).
339
+ // E.g. null → Texture enables USE_LIGHTMAP, Texture → null disables it.
340
+ // Pure uniform changes (red → blue, textureA → textureB) don't need program switch.
341
+ if (!needsProgramChange && !!currentValue !== !!override.value) {
342
+ needsProgramChange = true;
343
+ }
344
+
345
+ // Set all material properties including lightMap -
346
+ // three.js reads material.lightMap to determine shader parameters and upload uniforms
347
+ mat[override.name] = override.value;
348
+
349
+ // Apply per-object texture transform (offset/repeat) if specified
350
+ if (override.textureTransform && override.value instanceof Texture) {
351
+ const tex = override.value;
352
+ if (!mat[$savedTextureTransforms]) mat[$savedTextureTransforms] = [];
353
+ (mat[$savedTextureTransforms] as SavedTextureTransform[]).push({
354
+ name: override.name,
355
+ offsetX: tex.offset.x, offsetY: tex.offset.y,
356
+ repeatX: tex.repeat.x, repeatY: tex.repeat.y
357
+ });
358
+ const t = override.textureTransform;
359
+ if (t.offset) tex.offset.copy(t.offset);
360
+ if (t.repeat) tex.repeat.copy(t.repeat);
361
+ }
362
+ }
363
+
364
+ // Only set needsUpdate when overrides change shader features (truthiness changes
365
+ // like null↔texture, or defines added). This triggers getProgram() for program switches.
366
+ // Pure uniform overrides (color, roughness) skip this — no version increment needed.
367
+ if (needsProgramChange) {
368
+ mat.needsUpdate = true;
369
+ }
370
+ // _forceRefresh triggers uniform re-upload for consecutive objects sharing
371
+ // the same program and material (without it three.js skips the upload).
372
+ mat._forceRefresh = true;
373
+ };
374
+
375
+ // #region OnAfterRender
376
+ const onAfterRender_MaterialBlock: ObjectRenderCallback = function (this: Object3D, _renderer: WebGLRenderer, _scene: Scene, _camera: Camera, _geometry: BufferGeometry, material: Material, _group: Group) {
377
+ const result = findPropertyBlockAndOwner(this);
378
+ if (!result) {
379
+ return;
380
+ }
381
+
382
+ const { block: propertyBlock, owner } = result;
383
+
384
+ // Only restore if this material was registered with this property block
385
+ if (!registry.hasMaterial(owner, material)) {
386
+ return;
387
+ }
388
+
389
+ const overrides = propertyBlock.overrides;
390
+
391
+ const mat = material as any;
392
+ const originalValues = mat[$originalValues] as OriginalValue[] | undefined;
393
+
394
+ // Clean up defines — this affects shader compilation
395
+ const defines = propertyBlock.getDefines();
396
+ const defineKeys = Object.keys(defines);
397
+ let needsProgramChange = false;
398
+ if (defineKeys.length > 0 && mat.defines) {
399
+ for (const key of defineKeys) {
400
+ delete mat.defines[key];
401
+ }
402
+ needsProgramChange = true;
403
+ }
404
+
405
+ if (overrides.length === 0) {
406
+ if (needsProgramChange) {
407
+ mat.needsUpdate = true;
408
+ mat._forceRefresh = true;
409
+ }
410
+ return;
411
+ }
412
+
413
+ if (!originalValues) return;
414
+
415
+ // Restore texture transforms before restoring material properties
416
+ const savedTransforms = mat[$savedTextureTransforms] as SavedTextureTransform[] | undefined;
417
+ if (savedTransforms && savedTransforms.length > 0) {
418
+ for (const saved of savedTransforms) {
419
+ const override = overrides.find(o => o.name === saved.name);
420
+ if (override?.value instanceof Texture) {
421
+ override.value.offset.set(saved.offsetX, saved.offsetY);
422
+ override.value.repeat.set(saved.repeatX, saved.repeatY);
423
+ }
424
+ }
425
+ savedTransforms.length = 0;
426
+ }
427
+
428
+ for (const override of overrides) {
429
+ const original = originalValues.find(o => o.name === override.name);
430
+ if (original) {
431
+ // Check if restoring changes shader features (truthiness change)
432
+ if (!needsProgramChange && !!override.value !== !!original.value) {
433
+ needsProgramChange = true;
434
+ }
435
+ mat[override.name] = original.value;
436
+ }
437
+ }
438
+
439
+ // Only set needsUpdate when restoring affects shader features
440
+ if (needsProgramChange) {
441
+ mat.needsUpdate = true;
442
+ }
443
+ // Always force uniform refresh so the next object gets correct values
444
+ mat._forceRefresh = true;
445
+ };
446
+
447
+
448
+ // #region Attach Callbacks
449
+ function attachPropertyBlockToObject(object: Object3D, _propertyBlock: MaterialPropertyBlock): void {
450
+ // 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));
454
+
455
+ // Attach callbacks to renderable objects (Mesh, SkinnedMesh)
456
+ // Groups don't render themselves but we still need to handle child meshes
457
+ if (object.type === "Group") {
458
+ object.children.forEach(child => {
459
+ if (child.type === "Mesh" || child.type === "SkinnedMesh") {
460
+ attachCallbacksToMesh(child, object);
461
+ }
462
+ });
463
+ } else if (object.type === "Mesh" || object.type === "SkinnedMesh") {
464
+ attachCallbacksToMesh(object, object);
465
+ }
466
+ }
467
+ function attachCallbacksToMesh(mesh: Object3D, propertyBlockOwner: Object3D): void {
468
+ // Check if this specific mesh already has our callbacks attached for this property block owner
469
+ if (registry.isHooked(mesh, propertyBlockOwner)) {
470
+ // Already hooked for this property block owner
471
+ return;
472
+ }
473
+
474
+ registry.addHook(mesh, propertyBlockOwner);
475
+
476
+ if (!mesh.onBeforeRender) {
477
+ mesh.onBeforeRender = onBeforeRender_MaterialBlock;
478
+ } else {
479
+ const original = mesh.onBeforeRender;
480
+ mesh.onBeforeRender = function (renderer, scene, camera, geometry, material, group) {
481
+ original.call(this, renderer, scene, camera, geometry, material, group);
482
+ onBeforeRender_MaterialBlock.call(this, renderer, scene, camera, geometry, material, group);
483
+ };
484
+ }
485
+
486
+ if (!mesh.onAfterRender) {
487
+ mesh.onAfterRender = onAfterRender_MaterialBlock;
488
+ } else {
489
+ const original = mesh.onAfterRender;
490
+ mesh.onAfterRender = function (renderer, scene, camera, geometry, material, group) {
491
+ onAfterRender_MaterialBlock.call(this, renderer, scene, camera, geometry, material, group);
492
+ original.call(this, renderer, scene, camera, geometry, material, group);
493
+ };
494
+ }
495
+ }
496
+ //#endregion
497
+
498
+ export function objectHasPropertyBlock(object: Object3D): boolean {
499
+ return registry.getBlock(object) !== undefined;
500
+ }
@@ -1,4 +1,4 @@
1
- import { CubeReflectionMapping, CubeTexture, EquirectangularReflectionMapping, LinearSRGBColorSpace, Material, MeshBasicMaterial, Object3D, SRGBColorSpace, Texture, Vector3 } from "three";
1
+ import { Color, CubeReflectionMapping, CubeTexture, EquirectangularReflectionMapping, LinearSRGBColorSpace, Material, MeshBasicMaterial, MeshStandardMaterial, Object3D, SRGBColorSpace, Texture, Vector3 } from "three";
2
2
 
3
3
  import { isDevEnvironment, showBalloonWarning } from "../engine/debug/index.js";
4
4
  import { serializable } from "../engine/engine_serialization.js";
@@ -7,13 +7,12 @@ import type { IRenderer } from "../engine/engine_types.js";
7
7
  import { getParam } from "../engine/engine_utils.js";
8
8
  import { BoxHelperComponent } from "./BoxHelperComponent.js";
9
9
  import { Behaviour } from "./Component.js";
10
+ import { MaterialPropertyBlock } from "../engine/engine_materialpropertyblock.js";
10
11
 
11
12
  export const debug = getParam("debugreflectionprobe");
12
13
  const disable = getParam("noreflectionprobe");
13
14
 
14
15
  const $reflectionProbeKey = Symbol("reflectionProbeKey");
15
- const $originalMaterial = Symbol("original material");
16
-
17
16
  /**
18
17
  * The [ReflectionProbe](https://engine.needle.tools/docs/api/ReflectionProbe) provides environment reflection data to materials within its defined area.
19
18
  * Use for chrome-like materials that need accurate environment reflections.
@@ -38,12 +37,13 @@ const $originalMaterial = Symbol("original material");
38
37
  * @see {@link Renderer} for material and rendering control
39
38
  * @see {@link Light} for scene lighting
40
39
  */
40
+
41
41
  export class ReflectionProbe extends Behaviour {
42
42
 
43
43
  private static _probes: Map<Context, ReflectionProbe[]> = new Map();
44
44
 
45
45
  static isUsingReflectionProbe(material: Material) {
46
- return !!(material[$reflectionProbeKey] || material[$originalMaterial]?.[$reflectionProbeKey]);
46
+ return !!(material as any)[$reflectionProbeKey];
47
47
  }
48
48
 
49
49
  public static get(object: Object3D | null | undefined, context: Context, isAnchor: boolean, anchor?: Object3D): ReflectionProbe | null {
@@ -55,7 +55,6 @@ export class ReflectionProbe extends Behaviour {
55
55
  if (!probe.__didAwake) probe.__internalAwake();
56
56
  if (probe.activeAndEnabled) {
57
57
  if (anchor) {
58
- // test if anchor is reflection probe object
59
58
  if (probe.gameObject === anchor) {
60
59
  return probe;
61
60
  }
@@ -72,13 +71,9 @@ export class ReflectionProbe extends Behaviour {
72
71
  return null;
73
72
  }
74
73
 
75
-
76
-
77
74
  private _texture!: Texture;
78
75
 
79
- // @serializable(Texture)
80
76
  set texture(tex: Texture) {
81
-
82
77
  if (this._texture === tex) return;
83
78
  this._texture = tex;
84
79
 
@@ -86,7 +81,6 @@ export class ReflectionProbe extends Behaviour {
86
81
 
87
82
  if (tex) {
88
83
  if (tex instanceof CubeTexture) {
89
- // cube textures use CubeReflectionMapping by default
90
84
  }
91
85
  else if (tex.mapping !== EquirectangularReflectionMapping) {
92
86
  tex.mapping = EquirectangularReflectionMapping;
@@ -146,29 +140,14 @@ export class ReflectionProbe extends Behaviour {
146
140
  }
147
141
  }
148
142
 
149
-
150
- // when objects are rendered and they share material
151
- // and some need reflection probe and some don't
152
- // we need to make sure we don't override the material but use a copy
153
-
154
- private static _rendererMaterialsCache: Map<IRenderer, Array<{ material: Material, copy: Material, originalVersion: number }>> = new Map();
155
-
156
143
  onSet(_rend: IRenderer) {
157
144
  if (disable) return;
158
145
  if (!this.enabled) return;
159
146
  if (_rend.sharedMaterials?.length <= 0) return;
160
147
  if (!this.texture) return;
161
148
 
162
- let rendererCache = ReflectionProbe._rendererMaterialsCache.get(_rend);
163
- if (!rendererCache) {
164
- rendererCache = [];
165
- ReflectionProbe._rendererMaterialsCache.set(_rend, rendererCache);
166
- }
167
-
168
- // TODO: dont clone material for every renderer that uses reflection probes, we can do it once per material when they use the same reflection texture
149
+ const object = _rend.gameObject as unknown as Object3D;
169
150
 
170
- // need to make sure materials are not shared when using reflection probes
171
- // otherwise some renderers outside of the probe will be affected or vice versa
172
151
  for (let i = 0; i < _rend.sharedMaterials.length; i++) {
173
152
  const material = _rend.sharedMaterials[i];
174
153
  if (!material) {
@@ -178,75 +157,24 @@ export class ReflectionProbe extends Behaviour {
178
157
  continue;
179
158
  }
180
159
 
181
- if (material instanceof MeshBasicMaterial) {
182
- continue;
183
- }
184
-
185
- let cached = rendererCache[i];
186
-
187
- // make sure we have the currently assigned material cached (and an up to date clone of that)
188
- // Compare against the stored original version, not the clone's version (which gets modified by needsUpdate)
189
- // This prevents cloning materials every frame when onUnset restores the original material
190
- const isCachedInstance = material === cached?.copy;
191
- const hasChanged = !cached || cached.material.uuid !== material.uuid || cached.originalVersion !== material.version;
192
- if (!isCachedInstance && hasChanged) {
193
- if (debug) {
194
- let reason = "";
195
- if (!cached) reason = "not cached";
196
- else if (cached.material !== material) reason = "reference changed; cached instance?: " + isCachedInstance;
197
- else if (cached.originalVersion !== material.version) reason = "version changed";
198
- console.warn("Cloning material", material.name, material.version, "Reason:", reason, "\n", material.uuid, "\n", cached?.copy.uuid, "\n", _rend.name);
199
- }
200
-
201
- const clone = material.clone();
202
- clone.version = material.version;
203
-
204
- if (cached) {
205
- cached.copy = clone;
206
- cached.material = material;
207
- cached.originalVersion = material.version;
208
- }
209
- else {
210
- cached = {
211
- material: material,
212
- copy: clone,
213
- originalVersion: material.version
214
- };
215
- rendererCache.push(cached);
216
- }
217
-
218
- clone[$reflectionProbeKey] = this;
219
- clone[$originalMaterial] = material;
220
-
221
- if (debug) console.log("Set reflection", _rend.name, _rend.guid);
222
- }
223
-
224
- // See NE-4771 and NE-4856
225
- if (cached && cached.copy) {
226
- cached.copy.onBeforeCompile = material.onBeforeCompile;
227
- }
228
-
229
- /** this is the material that we copied and that has the reflection probe */
230
- const copy = cached?.copy;
160
+ if (debug) console.log("Setting reflection probe on material", material.name, "for renderer", _rend.name);
231
161
 
232
- if ("envMap" in copy) {
233
- if (copy.envMap !== this.texture) { // Only update if changed
234
- copy.envMap = this.texture;
235
- copy.needsUpdate = true;
236
- }
237
- }
162
+ const propertyBlock = MaterialPropertyBlock.get(object);
163
+ propertyBlock.setOverride("envMap", this.texture);
164
+ // propertyBlock.setOverride("color", new Color(0xff0000));
238
165
 
239
- _rend.sharedMaterials[i] = copy;
166
+ (material as any)[$reflectionProbeKey] = true;
240
167
  }
241
168
  }
242
169
 
243
170
  onUnset(_rend: IRenderer) {
244
- const rendererCache = ReflectionProbe._rendererMaterialsCache.get(_rend);
245
- if (rendererCache) {
246
- for (let i = 0; i < rendererCache.length; i++) {
247
- const cached = rendererCache[i];
248
- _rend.sharedMaterials[i] = cached.material;
171
+ // const object = _rend.gameObject as unknown as Object3D;
172
+
173
+ for (let i = 0; i < _rend.sharedMaterials.length; i++) {
174
+ const material = _rend.sharedMaterials[i];
175
+ if (material) {
176
+ delete (material as any)[$reflectionProbeKey];
249
177
  }
250
178
  }
251
179
  }
252
- }
180
+ }