@needle-tools/engine 2.35.4-pre → 2.35.5-pre

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 (49) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/needle-engine.d.ts +53 -34
  3. package/dist/needle-engine.js +394 -343
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +96 -45
  6. package/dist/needle-engine.min.js.map +4 -4
  7. package/lib/engine/api.d.ts +3 -0
  8. package/lib/engine/api.js +3 -0
  9. package/lib/engine/api.js.map +1 -1
  10. package/lib/engine/debug/debug.d.ts +3 -0
  11. package/lib/engine/debug/debug.js +8 -0
  12. package/lib/engine/debug/debug.js.map +1 -0
  13. package/lib/engine/debug/debug_overlay.d.ts +7 -0
  14. package/lib/engine/debug/debug_overlay.js +202 -0
  15. package/lib/engine/debug/debug_overlay.js.map +1 -0
  16. package/lib/engine/engine.js +1 -1
  17. package/lib/engine/engine.js.map +1 -1
  18. package/lib/engine/engine_serialization_core.js +35 -0
  19. package/lib/engine/engine_serialization_core.js.map +1 -1
  20. package/lib/engine/engine_types.d.ts +5 -1
  21. package/lib/engine/engine_types.js.map +1 -1
  22. package/lib/engine/extensions/NEEDLE_render_objects.d.ts +2 -2
  23. package/lib/engine/extensions/NEEDLE_render_objects.js +2 -2
  24. package/lib/engine/extensions/NEEDLE_render_objects.js.map +1 -1
  25. package/lib/engine-components/AudioSource.d.ts +1 -1
  26. package/lib/engine-components/AudioSource.js +2 -2
  27. package/lib/engine-components/AudioSource.js.map +1 -1
  28. package/lib/engine-components/Renderer.d.ts +3 -2
  29. package/lib/engine-components/Renderer.js.map +1 -1
  30. package/lib/engine-components/WebXRController.js +6 -2
  31. package/lib/engine-components/WebXRController.js.map +1 -1
  32. package/lib/engine-components/js-extensions/ExtensionUtils.js +1 -1
  33. package/lib/engine-components/js-extensions/ExtensionUtils.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/engine/api.ts +3 -0
  36. package/src/engine/debug/debug.ts +9 -0
  37. package/src/engine/debug/debug_overlay.ts +217 -0
  38. package/src/engine/engine.ts +1 -1
  39. package/src/engine/engine_serialization_core.ts +34 -0
  40. package/src/engine/engine_types.ts +6 -1
  41. package/src/engine/extensions/NEEDLE_render_objects.ts +4 -4
  42. package/src/engine-components/AudioSource.ts +3 -3
  43. package/src/engine-components/Renderer.ts +737 -734
  44. package/src/engine-components/WebXRController.ts +6 -2
  45. package/src/engine-components/js-extensions/ExtensionUtils.ts +1 -1
  46. package/lib/engine/debug/error_overlay.d.ts +0 -1
  47. package/lib/engine/debug/error_overlay.js +0 -114
  48. package/lib/engine/debug/error_overlay.js.map +0 -1
  49. package/src/engine/debug/error_overlay.ts +0 -126
@@ -1,735 +1,738 @@
1
- import { Behaviour, GameObject } from "./Component";
2
- import * as THREE from "three";
3
- // import { RendererCustomShader } from "./RendererCustomShader";
4
- import { RendererLightmap } from "./RendererLightmap";
5
- import { Context } from "../engine/engine_setup";
6
- import { getParam } from "../engine/engine_utils";
7
- import { serializeable } from "../engine/engine_serialization_decorator";
8
- import { Material, Mesh, Texture, Vector4 } from "three";
9
- import { NEEDLE_render_objects } from "../engine/extensions/NEEDLE_render_objects";
10
- import { NEEDLE_deferred_texture } from "../engine/extensions/NEEDLE_deferred_texture";
11
- import { NEED_UPDATE_INSTANCE_KEY } from "../engine/engine_instancing";
12
- import { IRenderer } from "../engine/engine_types";
13
-
14
- // for staying compatible with old code
15
- export { InstancingUtil } from "../engine/engine_instancing";
16
-
17
- const suppressInstancing = getParam("noInstancing");
18
- const debugLightmap = getParam("debuglightmaps") ? true : false;
19
- const debugInstancing = getParam("debuginstancing");
20
- const debugProgressiveLoading = getParam("debugprogressive");
21
- const suppressProgressiveLoading = getParam("noprogressive");
22
-
23
- export class FieldWithDefault {
24
- public path: string | null = null;
25
- public asset: object | null = null;
26
- public default: any;
27
- }
28
-
29
- export enum RenderState {
30
- Both = 0,
31
- Back = 1,
32
- Front = 2,
33
- }
34
-
35
-
36
- // support sharedMaterials[index] assigning materials directly to the objects
37
- class SharedMaterialArray {
38
-
39
- private _renderer: Renderer;
40
- private _targets: THREE.Object3D[] = [];
41
-
42
- is(renderer: Renderer) {
43
- return this._renderer === renderer;
44
- }
45
-
46
- constructor(renderer: Renderer) {
47
- this._renderer = renderer;
48
- const setMaterial = this.setMaterial.bind(this);
49
- const getMaterial = this.getMaterial.bind(this);
50
- const go = renderer.gameObject;
51
- this._targets = [];
52
- if (go) {
53
- switch (go.type) {
54
- case "Group":
55
- this._targets = [...go.children];
56
- break;
57
- case "Mesh":
58
- this._targets.push(go);
59
- break;
60
- }
61
- }
62
-
63
- // this lets us override the javascript indexer, only works in ES6 tho
64
- // but like that we can use sharedMaterials[index] and it will be assigned to the object directly
65
- return new Proxy(this, {
66
- get(target, key) {
67
- if (typeof key === "string") {
68
- const index = parseInt(key);
69
- if (!isNaN(index)) {
70
- return getMaterial(index);
71
- }
72
- }
73
- return target[key];
74
- },
75
- set(target, key, value) {
76
- if (typeof key === "string")
77
- setMaterial(value, Number.parseInt(key));
78
- // console.log(target, key, value);
79
- return Reflect.set(target, key, value);
80
- }
81
- });
82
- }
83
-
84
- get length(): number {
85
- return this._targets.length;
86
- }
87
-
88
- private setMaterial(mat: Material, index: number) {
89
- if (index < 0 || index >= this._targets.length) return;
90
- const target = this._targets[index];
91
- if (!target) return;
92
- target["material"] = mat;
93
- }
94
-
95
- private getMaterial(index: number) {
96
- if (index < 0) return null;
97
- const obj = this._targets;
98
- if (index >= obj.length) return null;
99
- const target = obj[index];
100
- if (!target) return null;
101
- return target["material"];
102
- }
103
-
104
- }
105
-
106
- export class Renderer extends Behaviour implements IRenderer {
107
-
108
- @serializeable()
109
- receiveShadows: boolean = false;
110
- @serializeable()
111
- shadowCastingMode: ShadowCastingMode = ShadowCastingMode.Off;
112
- @serializeable()
113
- lightmapIndex: number = -1;
114
- @serializeable(Vector4)
115
- lightmapScaleOffset: THREE.Vector4 = new THREE.Vector4(1, 1, 0, 0);
116
- @serializeable()
117
- enableInstancing: boolean[] | undefined = undefined;
118
- @serializeable()
119
- renderOrder: number[] | undefined = undefined;
120
- @serializeable()
121
- allowOcclusionWhenDynamic: boolean = true;
122
-
123
- // custom shader
124
- // get materialProperties(): Array<MaterialProperties> | undefined {
125
- // return this._materialProperties;
126
- // }
127
- // set materialProperties(value: Array<MaterialProperties> | undefined) {
128
- // this._materialProperties = value;
129
- // }
130
-
131
- // private customShaderHandler: RendererCustomShader | undefined = undefined;
132
-
133
- // private _materialProperties: Array<MaterialProperties> | undefined = undefined;
134
- private _lightmaps?: RendererLightmap[];
135
-
136
- get sharedMesh(): Mesh | undefined {
137
- if (this.gameObject.type === "Mesh") {
138
- return this.gameObject as unknown as Mesh
139
- }
140
- else if (this.gameObject.type === "Group") {
141
- return this.gameObject.children[0] as unknown as Mesh;
142
- }
143
- return undefined;
144
- }
145
-
146
- get sharedMaterial(): THREE.Material {
147
- return this.sharedMaterials[0];
148
- }
149
-
150
- set sharedMaterial(mat: THREE.Material) {
151
- this.sharedMaterials[0] = mat;
152
- }
153
-
154
- /**@deprecated please use sharedMaterial */
155
- get material(): THREE.Material {
156
- return this.sharedMaterials[0];
157
- }
158
-
159
- /**@deprecated please use sharedMaterial */
160
- set material(mat: THREE.Material) {
161
- this.sharedMaterials[0] = mat;
162
- }
163
-
164
- private _sharedMaterials!: SharedMaterialArray;
165
-
166
- get sharedMaterials(): SharedMaterialArray {
167
- if (!this._sharedMaterials || !this._sharedMaterials.is(this))
168
- this._sharedMaterials = new SharedMaterialArray(this);
169
- return this._sharedMaterials!;
170
- }
171
-
172
- public static get shouldSuppressInstancing() {
173
- return suppressInstancing;
174
- }
175
-
176
- private _lightmapTextureOverride: Texture | null | undefined = undefined;
177
- public get lightmap(): Texture | null {
178
- if (this._lightmaps?.length) {
179
- return this._lightmaps[0].lightmap;
180
- }
181
- return null;
182
- }
183
- public set lightmap(tex: Texture | null | undefined) {
184
- this._lightmapTextureOverride = tex;
185
- // set undefined to return to default
186
- if (tex === undefined) {
187
- tex = this.context.lightmaps.tryGetLightmap(this.sourceId, this.lightmapIndex);
188
- }
189
- if (this._lightmaps?.length) {
190
- for (const lm of this._lightmaps) {
191
- lm.lightmap = tex;
192
- }
193
- }
194
- }
195
- get hasLightmap(): boolean {
196
- const lm = this.lightmap;
197
- return lm !== null && lm !== undefined;
198
- }
199
-
200
- awake() {
201
- this.clearInstancingState();
202
-
203
- const type = this.gameObject.type;
204
- if (type === "Group") {
205
- for (const child of this.gameObject.children) {
206
- this.context.addBeforeRenderListener(child, this.onBeforeRenderThree.bind(this));
207
- child.layers.mask = this.gameObject.layers.mask;
208
- }
209
-
210
- if (this.renderOrder !== undefined) {
211
- // Objects can have nested renderers (e.g. contain 2 meshes and then again another group)
212
- // or perhaps just regular child objects that have their own renderer component (?)
213
- let index = 0;
214
- for (let i = 0; i < this.gameObject.children.length; i++) {
215
- const ch = this.gameObject.children[i];
216
- // ignore nested groups or objects that have their own renderer (aka their own render order settings)
217
- if (ch.type !== "Mesh" || GameObject.getComponent(ch, Renderer)) continue;
218
- if (this.renderOrder.length <= index) {
219
- console.error("Incorrect element count", this);
220
- break;
221
- }
222
- ch.renderOrder = this.renderOrder[index];
223
- index += 1;
224
- }
225
- }
226
- }
227
- // TODO: custom shader with sub materials
228
- else if (type === "Mesh" || type === "SkinnedMesh") {
229
-
230
- this.context.addBeforeRenderListener(this.gameObject, this.onBeforeRenderThree.bind(this));
231
-
232
- if (this.renderOrder !== undefined && this.renderOrder.length > 0)
233
- this.gameObject.renderOrder = this.renderOrder[0];
234
- }
235
-
236
- if (this.lightmapIndex >= 0) {
237
- // use the override lightmap if its not undefined
238
- const tex = this._lightmapTextureOverride !== undefined
239
- ? this._lightmapTextureOverride
240
- : this.context.lightmaps.tryGetLightmap(this.sourceId, this.lightmapIndex);
241
- if (tex) {
242
- // tex.encoding = THREE.LinearEncoding;
243
- this._lightmaps = [];
244
-
245
- if (type === "Mesh") {
246
- if (!this.gameObject["material"]?.isMeshBasicMaterial === true) {
247
- const rm = new RendererLightmap(this.gameObject, this.context);// GameObject.addNewComponent(this.gameObject, RendererLightmap);
248
- this._lightmaps.push(rm);
249
- rm.init(this.lightmapIndex, this.lightmapScaleOffset, tex, debugLightmap);
250
- }
251
- }
252
- // for multi materials we need to loop through children
253
- // and then we add a lightmap renderer component to each of them
254
- else if (type === "Group") {
255
- for (const child of this.gameObject.children) {
256
- if (!child["material"]?.isMeshBasicMaterial) {
257
- const rm = new RendererLightmap(child as GameObject, this.context);
258
- this._lightmaps.push(rm);
259
- rm.init(this.lightmapIndex, this.lightmapScaleOffset, tex, debugLightmap);
260
- // onBeforeRender is not called when the renderer is on a group
261
- // this is an issue we probably also need to handle for custom shaders
262
- // and need a better solution, but for now this fixes lightmaps for multimaterial objects
263
- rm.bindOnBeforeRender();
264
- }
265
- }
266
- }
267
- }
268
- }
269
-
270
- }
271
-
272
- private _isInstancingEnabled: boolean = false;
273
- private handles: InstanceHandle[] | null | undefined = undefined;
274
- private prevLayers: number[] | null | undefined = undefined;
275
-
276
- private clearInstancingState() {
277
- this._isInstancingEnabled = false;
278
- this.handles = undefined;
279
- this.prevLayers = undefined;
280
- }
281
- setInstancingEnabled(enabled: boolean): boolean {
282
- if (this._isInstancingEnabled === enabled) return enabled && (this.handles === undefined || this.handles != null && this.handles.length > 0);
283
- this._isInstancingEnabled = enabled;
284
- if (enabled) {
285
- // if handles is undefined we
286
- if (this.handles === undefined) {
287
- this.handles = instancing.setup(this.gameObject, this.context, null, { rend: this, foundMeshes: 0 });
288
- if (this.handles) {
289
- // const disableSelf = this.gameObject.type === "Mesh" || this.gameObject.children?.length === this.handles.length;
290
- // this.gameObject.visible = !disableSelf;
291
- // this.gameObject.type = "Object3D";
292
- // this.gameObject.material = null;
293
- // console.log("Using instancing", this.gameObject.visible);
294
- // this.gameObject.onBeforeRender = () => console.log("SHOULD NOT BE CALLED");
295
- GameObject.markAsInstancedRendered(this.gameObject, true);
296
- return true;
297
- }
298
- }
299
- else if (this.handles !== null) {
300
- for (const handler of this.handles) {
301
- handler.updateInstanceMatrix(true);
302
- handler.add();
303
- }
304
- // this.gameObject.type = "Object3D";
305
- // this.gameObject.visible = false;
306
- GameObject.markAsInstancedRendered(this.gameObject, true);
307
- return true;
308
- }
309
- }
310
- else {
311
- if (this.handles) {
312
- for (const handler of this.handles) {
313
- handler.remove();
314
- }
315
- }
316
- // this.gameObject.visible = true;
317
- return true;
318
- }
319
-
320
- return false;
321
- }
322
-
323
- start() {
324
- if (this.enableInstancing && !suppressInstancing) {
325
- this.setInstancingEnabled(true);
326
- }
327
- this.gameObject.frustumCulled = this.allowOcclusionWhenDynamic;
328
- if (this.gameObject.type === "Group") {
329
- for (let i = 0; i < this.gameObject.children.length; i++) {
330
- const ch = this.gameObject.children[i];
331
- ch.frustumCulled = this.allowOcclusionWhenDynamic;
332
- }
333
- }
334
- }
335
-
336
- onEnable() {
337
- // this.customShaderHandler?.onEnable();
338
- // if (this.didEnable && this.enabled && (!this.handles || this.handles?.length <= 0))
339
- // this.gameObject.visible = true;
340
- if (this._isInstancingEnabled) {
341
- this.setInstancingEnabled(true);
342
- }
343
- else if (this.enabled) {
344
- this.gameObject.visible = true;
345
- this.applyStencil();
346
- }
347
- }
348
-
349
- onDisable() {
350
- // this.customShaderHandler?.onDisable();
351
- // if (!this.enabled) {
352
- // this.gameObject.visible = false;
353
- // }
354
- if (this.handles && this.handles.length > 0) {
355
- this.setInstancingEnabled(false);
356
- // this.gameObject.visible = false;
357
- }
358
- else if (!this.enabled) {
359
- this.gameObject.visible = false;
360
- }
361
- }
362
-
363
- onDestroy(): void {
364
- this.handles = null;
365
- }
366
-
367
- applyStencil() {
368
- NEEDLE_render_objects.applyStencil(this);
369
- }
370
-
371
- onBeforeRenderThree(_renderer, _scene, _camera, _geometry, material, _group) {
372
-
373
- // progressive load before rendering so we only load textures for visible materials
374
- if (!suppressProgressiveLoading && material._didRequestTextureLOD === undefined) {
375
- material._didRequestTextureLOD = 0;
376
- if (debugProgressiveLoading) {
377
- console.log("Load material LOD (with delay)", material.name);
378
- setTimeout(() => {
379
- NEEDLE_deferred_texture.assignTextureLOD(this.context, this.sourceId, material);
380
- }, 2000);
381
- }
382
- else {
383
- NEEDLE_deferred_texture.assignTextureLOD(this.context, this.sourceId, material);
384
- }
385
- }
386
-
387
- if (material.envMapIntensity !== undefined) {
388
- const factor = this.hasLightmap ? Math.PI : 1;
389
- material.envMapIntensity = Math.max(0, this.context.rendererData.environmentIntensity / factor);
390
- }
391
-
392
- // if (!camera) {
393
- // let isXRCamera = false;
394
- // if (this.context.isInXR) {
395
- // // @ts-ignore
396
- // const arr = this.context.renderer.xr.getCamera() as ArrayCamera;
397
- // if (arr.cameras?.length > 0) {
398
- // camera = arr;
399
- // isXRCamera = true;
400
- // }
401
- // }
402
- // }
403
-
404
- // if (this.customShaderHandler) {
405
- // this.customShaderHandler.onBeforeRender(renderer, scene, camera, geometry, material, group);
406
- // }
407
- // else if (this.rawShaderHandler) {
408
- // for (const h of this.rawShaderHandler) {
409
- // h.onBeforeRender(this.gameObject, camera);
410
- // }
411
- // }
412
-
413
- if (this._lightmaps) {
414
- for (const lm of this._lightmaps) {
415
- lm.onBeforeRenderThree(material);
416
- }
417
- }
418
- }
419
-
420
- onBeforeRender() {
421
- if (!this.gameObject) {
422
- return;
423
- }
424
-
425
- const needsUpdate: boolean = this.gameObject[NEED_UPDATE_INSTANCE_KEY] === true || this.gameObject.matrixWorldNeedsUpdate;
426
-
427
- if (this.gameObject.type === "Group" && this.gameObject.children?.length > 0) {
428
- for (const ch of this.gameObject.children) {
429
- this.applySettings(ch);
430
- }
431
- }
432
- else {
433
- this.applySettings(this.gameObject);
434
- }
435
-
436
- if (needsUpdate) {
437
- delete this.gameObject[NEED_UPDATE_INSTANCE_KEY];
438
- if (this.handles) {
439
- const remove = false;// Math.random() < .01;
440
- for (let i = this.handles.length - 1; i >= 0; i--) {
441
- const h = this.handles[i];
442
- if (remove) {
443
- h.remove();
444
- this.handles.splice(i, 1);
445
- }
446
- else
447
- h.updateInstanceMatrix();
448
- }
449
- this.gameObject.matrixWorldNeedsUpdate = false;
450
- }
451
- }
452
-
453
- if (this.handles && this.handles.length <= 0) {
454
- GameObject.markAsInstancedRendered(this.gameObject, false);
455
- }
456
-
457
- if (this._isInstancingEnabled && this.handles) {
458
- for (let i = 0; i < this.handles.length; i++) {
459
- const handle = this.handles[i];
460
- if (!this.prevLayers) this.prevLayers = [];
461
- const layer = handle.object.layers.mask;
462
- if (i >= this.prevLayers.length) this.prevLayers.push(layer);
463
- else this.prevLayers[i] = layer;
464
- handle.object.layers.disableAll();
465
- }
466
- }
467
- }
468
-
469
- onAfterRender() {
470
- if (this._isInstancingEnabled && this.handles && this.prevLayers && this.prevLayers.length >= this.handles.length) {
471
- for (let i = 0; i < this.handles.length; i++) {
472
- const handle = this.handles[i];
473
- handle.object.layers.mask = this.prevLayers[i];
474
- }
475
- }
476
- }
477
-
478
- private applySettings(go: THREE.Object3D) {
479
- go.receiveShadow = this.receiveShadows;
480
- if (this.shadowCastingMode == ShadowCastingMode.On) {
481
- go.castShadow = true;
482
- }
483
- else go.castShadow = false;
484
- }
485
-
486
- }
487
-
488
- export class MeshRenderer extends Renderer {
489
-
490
- }
491
-
492
- export class SkinnedMeshRenderer extends MeshRenderer {
493
- awake() {
494
- super.awake();
495
- // disable skinned mesh occlusion because of https://github.com/mrdoob/three.js/issues/14499
496
- this.allowOcclusionWhenDynamic = false;
497
- }
498
- }
499
-
500
- export enum ShadowCastingMode {
501
- /// <summary>
502
- /// <para>No shadows are cast from this object.</para>
503
- /// </summary>
504
- Off,
505
- /// <summary>
506
- /// <para>Shadows are cast from this object.</para>
507
- /// </summary>
508
- On,
509
- /// <summary>
510
- /// <para>Shadows are cast from this object, treating it as two-sided.</para>
511
- /// </summary>
512
- TwoSided,
513
- /// <summary>
514
- /// <para>Object casts shadows, but is otherwise invisible in the Scene.</para>
515
- /// </summary>
516
- ShadowsOnly,
517
- }
518
-
519
-
520
-
521
- declare class InstancingSetupArgs { rend: Renderer; foundMeshes: number };
522
-
523
- class InstancingHandler {
524
-
525
- public objs: InstancedMeshRenderer[] = [];
526
-
527
- public setup(obj: THREE.Object3D, context: Context, handlesArray: InstanceHandle[] | null, args: InstancingSetupArgs, level: number = 0)
528
- : InstanceHandle[] | null {
529
-
530
- const res = this.tryCreateOrAddInstance(obj, context, args);
531
- if (res) {
532
- if (handlesArray === null) handlesArray = [];
533
- handlesArray.push(res);
534
- return handlesArray;
535
- }
536
-
537
- if (level <= 0 && obj.type !== "Mesh") {
538
- const nextLevel = level + 1;
539
- for (const ch of obj.children) {
540
- handlesArray = this.setup(ch, context, handlesArray, args, nextLevel);
541
- }
542
- }
543
- return handlesArray;
544
- }
545
-
546
- private tryCreateOrAddInstance(obj: THREE.Object3D, context: Context, args: InstancingSetupArgs): InstanceHandle | null {
547
- if (obj.type === "Mesh") {
548
- const index = args.foundMeshes;
549
- args.foundMeshes += 1;
550
- if (!args.rend.enableInstancing) return null;
551
- if (index >= args.rend.enableInstancing.length) {
552
- // console.error("Something is wrong with instance setup", obj, args.rend.enableInstancing, index);
553
- return null;
554
- }
555
- if (!args.rend.enableInstancing[index]) {
556
- // instancing is disabled
557
- // console.log("Instancing is disabled", obj);
558
- return null;
559
- }
560
- // instancing is enabled:
561
- const mesh = obj as THREE.Mesh;
562
- const geo = mesh.geometry as THREE.BufferGeometry;
563
- const mat = mesh.material as THREE.Material;
564
-
565
- for (const i of this.objs) {
566
- if (i.isFull()) continue;
567
- if (i.geo === geo && i.material === mat) {
568
- return i.addInstance(mesh);
569
- }
570
- }
571
- // console.log("Add new instance mesh renderer", obj);
572
- const i = new InstancedMeshRenderer(obj.name, geo, mat, 200, context);
573
- this.objs.push(i);
574
- return i.addInstance(mesh);
575
- }
576
- return null;
577
- }
578
- }
579
- const instancing: InstancingHandler = new InstancingHandler();
580
-
581
- class InstanceHandle {
582
-
583
- get name(): string {
584
- return this.object.name;
585
- }
586
-
587
- instanceIndex: number = -1;
588
- object: THREE.Mesh;
589
- instancer: InstancedMeshRenderer;
590
-
591
- constructor(instanceIndex: number, originalObject: THREE.Mesh, instancer: InstancedMeshRenderer) {
592
- this.instanceIndex = instanceIndex;
593
- this.object = originalObject;
594
- this.instancer = instancer;
595
- GameObject.markAsInstancedRendered(originalObject, true);
596
- // this.object.visible = false;
597
- }
598
-
599
- updateInstanceMatrix(updateChildren: boolean = false) {
600
- if (this.instanceIndex < 0) return;
601
- this.object.updateWorldMatrix(true, updateChildren);
602
- this.instancer.updateInstance(this.object.matrixWorld, this.instanceIndex);
603
- }
604
-
605
- add() {
606
- if (this.instanceIndex >= 0) return;
607
- this.instancer.add(this);
608
- }
609
-
610
- remove() {
611
- if (this.instanceIndex < 0) return;
612
- this.instancer.remove(this);
613
- }
614
- }
615
-
616
- class InstancedMeshRenderer {
617
-
618
- public name: string = "";
619
- public geo: THREE.BufferGeometry;
620
- public material: THREE.Material;
621
- get currentCount(): number { return this.inst.count; }
622
-
623
- private context: Context;
624
- private inst: THREE.InstancedMesh;
625
- private handles: (InstanceHandle | null)[] = [];
626
- private maxCount: number;
627
-
628
- private static nullMatrix: THREE.Matrix4 = new THREE.Matrix4();
629
-
630
- isFull(): boolean {
631
- return this.currentCount >= this.maxCount;
632
- }
633
-
634
- constructor(name: string, geo: THREE.BufferGeometry, material: THREE.Material, count: number, context: Context) {
635
- this.name = name;
636
- this.geo = geo;
637
- this.material = material;
638
- this.context = context;
639
- this.maxCount = count;
640
- if(debugInstancing){
641
- material = new THREE.MeshBasicMaterial({ color: this.randomColor() });
642
- }
643
- this.inst = new THREE.InstancedMesh(geo, material, count);
644
- this.inst.count = 0;
645
- this.inst.layers.set(2);
646
- this.inst.visible = true;
647
- // this.inst.castShadow = true;
648
- // this.inst.receiveShadow = true;
649
- this.context.scene.add(this.inst);
650
- // console.log(this.inst);
651
- // this.context.pre_render_callbacks.push(this.onPreRender.bind(this));
652
-
653
- // setInterval(() => {
654
- // this.inst.visible = !this.inst.visible;
655
- // }, 500);
656
- }
657
-
658
- private randomColor(){
659
- return new THREE.Color(Math.random(), Math.random(), Math.random());
660
- }
661
-
662
- addInstance(obj: THREE.Mesh): InstanceHandle | null {
663
- if (this.currentCount >= this.maxCount) {
664
- console.error("TOO MANY INSTANCES - resize is not yet implemented!", this.inst.count); // todo: make it resize
665
- return null;
666
- }
667
- const handle = new InstanceHandle(-1, obj, this);
668
- this.add(handle);
669
- return handle;
670
- }
671
-
672
-
673
- add(handle: InstanceHandle) {
674
- if (handle.instanceIndex < 0) {
675
- handle.instanceIndex = this.currentCount;
676
- // console.log(handle.instanceIndex, this.currentCount);
677
- if (handle.instanceIndex >= this.handles.length)
678
- this.handles.push(handle);
679
- else this.handles[handle.instanceIndex] = handle;
680
- }
681
- // console.log("Handle instance");
682
- handle.object.updateWorldMatrix(true, true);
683
- this.inst.setMatrixAt(handle.instanceIndex, handle.object.matrixWorld);
684
- this.inst.instanceMatrix.needsUpdate = true;
685
- this.inst.count += 1;
686
-
687
- if (this.inst.count > 0)
688
- this.inst.visible = true;
689
-
690
- // console.log("Added", this.name, this.inst.count, this.handles);
691
- }
692
-
693
- remove(handle: InstanceHandle) {
694
- if (!handle) return;
695
- if (handle.instanceIndex < 0 || handle.instanceIndex >= this.handles.length || this.inst.count <= 0) {
696
- return;
697
- }
698
- if (this.handles[handle.instanceIndex] !== handle) {
699
- console.error("instance handle is not part of renderer, was it removed before?", handle.instanceIndex, this.name);
700
- const index = this.handles.indexOf(handle);
701
- if (index < 0)
702
- return;
703
- handle.instanceIndex = index;
704
- }
705
- this.handles[handle.instanceIndex] = null;
706
- this.inst.setMatrixAt(handle.instanceIndex, InstancedMeshRenderer.nullMatrix);
707
- const removedLastElement = handle.instanceIndex >= this.currentCount - 1;
708
- // console.log(removedLastElement, this.currentCount, handle.instanceIndex, this.handles);
709
- if (!removedLastElement && this.currentCount > 0) {
710
- const lastElement = this.handles[this.currentCount - 1];
711
- if (lastElement) {
712
- lastElement.instanceIndex = handle.instanceIndex;
713
- lastElement.updateInstanceMatrix();
714
- this.handles[handle.instanceIndex] = lastElement;
715
- this.handles[this.currentCount - 1] = null;
716
- // this.inst.setMatrixAt(handle.instanceIndex, lastElement.object.matrixWorld);
717
- // this.inst.setMatrixAt(this.currentCount - 1, InstancedMeshRenderer.nullMatrix);
718
- }
719
- }
720
-
721
- if (this.inst.count > 0)
722
- this.inst.count -= 1;
723
- handle.instanceIndex = -1;
724
-
725
- if (this.inst.count <= 0)
726
- this.inst.visible = false;
727
-
728
- this.inst.instanceMatrix.needsUpdate = true;
729
- }
730
-
731
- updateInstance(mat: THREE.Matrix4, index: number) {
732
- this.inst.setMatrixAt(index, mat);
733
- this.inst.instanceMatrix.needsUpdate = true;
734
- }
1
+ import { Behaviour, GameObject } from "./Component";
2
+ import * as THREE from "three";
3
+ // import { RendererCustomShader } from "./RendererCustomShader";
4
+ import { RendererLightmap } from "./RendererLightmap";
5
+ import { Context } from "../engine/engine_setup";
6
+ import { getParam } from "../engine/engine_utils";
7
+ import { serializeable } from "../engine/engine_serialization_decorator";
8
+ import { Material, Mesh, Texture, Vector4 } from "three";
9
+ import { NEEDLE_render_objects } from "../engine/extensions/NEEDLE_render_objects";
10
+ import { NEEDLE_deferred_texture } from "../engine/extensions/NEEDLE_deferred_texture";
11
+ import { NEED_UPDATE_INSTANCE_KEY } from "../engine/engine_instancing";
12
+ import { IRenderer, ISharedMaterials } from "../engine/engine_types";
13
+
14
+ // for staying compatible with old code
15
+ export { InstancingUtil } from "../engine/engine_instancing";
16
+
17
+ const suppressInstancing = getParam("noInstancing");
18
+ const debugLightmap = getParam("debuglightmaps") ? true : false;
19
+ const debugInstancing = getParam("debuginstancing");
20
+ const debugProgressiveLoading = getParam("debugprogressive");
21
+ const suppressProgressiveLoading = getParam("noprogressive");
22
+
23
+ export class FieldWithDefault {
24
+ public path: string | null = null;
25
+ public asset: object | null = null;
26
+ public default: any;
27
+ }
28
+
29
+ export enum RenderState {
30
+ Both = 0,
31
+ Back = 1,
32
+ Front = 2,
33
+ }
34
+
35
+
36
+ // support sharedMaterials[index] assigning materials directly to the objects
37
+ class SharedMaterialArray implements ISharedMaterials {
38
+
39
+ [num: number]: THREE.Material;
40
+
41
+ private _renderer: Renderer;
42
+ private _targets: THREE.Object3D[] = [];
43
+
44
+
45
+ is(renderer: Renderer) {
46
+ return this._renderer === renderer;
47
+ }
48
+
49
+ constructor(renderer: Renderer) {
50
+ this._renderer = renderer;
51
+ const setMaterial = this.setMaterial.bind(this);
52
+ const getMaterial = this.getMaterial.bind(this);
53
+ const go = renderer.gameObject;
54
+ this._targets = [];
55
+ if (go) {
56
+ switch (go.type) {
57
+ case "Group":
58
+ this._targets = [...go.children];
59
+ break;
60
+ case "Mesh":
61
+ this._targets.push(go);
62
+ break;
63
+ }
64
+ }
65
+
66
+ // this lets us override the javascript indexer, only works in ES6 tho
67
+ // but like that we can use sharedMaterials[index] and it will be assigned to the object directly
68
+ return new Proxy(this, {
69
+ get(target, key) {
70
+ if (typeof key === "string") {
71
+ const index = parseInt(key);
72
+ if (!isNaN(index)) {
73
+ return getMaterial(index);
74
+ }
75
+ }
76
+ return target[key];
77
+ },
78
+ set(target, key, value) {
79
+ if (typeof key === "string")
80
+ setMaterial(value, Number.parseInt(key));
81
+ // console.log(target, key, value);
82
+ return Reflect.set(target, key, value);
83
+ }
84
+ });
85
+ }
86
+
87
+ get length(): number {
88
+ return this._targets.length;
89
+ }
90
+
91
+ private setMaterial(mat: Material, index: number) {
92
+ if (index < 0 || index >= this._targets.length) return;
93
+ const target = this._targets[index];
94
+ if (!target) return;
95
+ target["material"] = mat;
96
+ }
97
+
98
+ private getMaterial(index: number) {
99
+ if (index < 0) return null;
100
+ const obj = this._targets;
101
+ if (index >= obj.length) return null;
102
+ const target = obj[index];
103
+ if (!target) return null;
104
+ return target["material"];
105
+ }
106
+
107
+ }
108
+
109
+ export class Renderer extends Behaviour implements IRenderer {
110
+
111
+ @serializeable()
112
+ receiveShadows: boolean = false;
113
+ @serializeable()
114
+ shadowCastingMode: ShadowCastingMode = ShadowCastingMode.Off;
115
+ @serializeable()
116
+ lightmapIndex: number = -1;
117
+ @serializeable(Vector4)
118
+ lightmapScaleOffset: THREE.Vector4 = new THREE.Vector4(1, 1, 0, 0);
119
+ @serializeable()
120
+ enableInstancing: boolean[] | undefined = undefined;
121
+ @serializeable()
122
+ renderOrder: number[] | undefined = undefined;
123
+ @serializeable()
124
+ allowOcclusionWhenDynamic: boolean = true;
125
+
126
+ // custom shader
127
+ // get materialProperties(): Array<MaterialProperties> | undefined {
128
+ // return this._materialProperties;
129
+ // }
130
+ // set materialProperties(value: Array<MaterialProperties> | undefined) {
131
+ // this._materialProperties = value;
132
+ // }
133
+
134
+ // private customShaderHandler: RendererCustomShader | undefined = undefined;
135
+
136
+ // private _materialProperties: Array<MaterialProperties> | undefined = undefined;
137
+ private _lightmaps?: RendererLightmap[];
138
+
139
+ get sharedMesh(): Mesh | undefined {
140
+ if (this.gameObject.type === "Mesh") {
141
+ return this.gameObject as unknown as Mesh
142
+ }
143
+ else if (this.gameObject.type === "Group") {
144
+ return this.gameObject.children[0] as unknown as Mesh;
145
+ }
146
+ return undefined;
147
+ }
148
+
149
+ get sharedMaterial(): THREE.Material {
150
+ return this.sharedMaterials[0];
151
+ }
152
+
153
+ set sharedMaterial(mat: THREE.Material) {
154
+ this.sharedMaterials[0] = mat;
155
+ }
156
+
157
+ /**@deprecated please use sharedMaterial */
158
+ get material(): THREE.Material {
159
+ return this.sharedMaterials[0];
160
+ }
161
+
162
+ /**@deprecated please use sharedMaterial */
163
+ set material(mat: THREE.Material) {
164
+ this.sharedMaterials[0] = mat;
165
+ }
166
+
167
+ private _sharedMaterials!: SharedMaterialArray;
168
+
169
+ get sharedMaterials(): SharedMaterialArray {
170
+ if (!this._sharedMaterials || !this._sharedMaterials.is(this))
171
+ this._sharedMaterials = new SharedMaterialArray(this);
172
+ return this._sharedMaterials!;
173
+ }
174
+
175
+ public static get shouldSuppressInstancing() {
176
+ return suppressInstancing;
177
+ }
178
+
179
+ private _lightmapTextureOverride: Texture | null | undefined = undefined;
180
+ public get lightmap(): Texture | null {
181
+ if (this._lightmaps?.length) {
182
+ return this._lightmaps[0].lightmap;
183
+ }
184
+ return null;
185
+ }
186
+ public set lightmap(tex: Texture | null | undefined) {
187
+ this._lightmapTextureOverride = tex;
188
+ // set undefined to return to default
189
+ if (tex === undefined) {
190
+ tex = this.context.lightmaps.tryGetLightmap(this.sourceId, this.lightmapIndex);
191
+ }
192
+ if (this._lightmaps?.length) {
193
+ for (const lm of this._lightmaps) {
194
+ lm.lightmap = tex;
195
+ }
196
+ }
197
+ }
198
+ get hasLightmap(): boolean {
199
+ const lm = this.lightmap;
200
+ return lm !== null && lm !== undefined;
201
+ }
202
+
203
+ awake() {
204
+ this.clearInstancingState();
205
+
206
+ const type = this.gameObject.type;
207
+ if (type === "Group") {
208
+ for (const child of this.gameObject.children) {
209
+ this.context.addBeforeRenderListener(child, this.onBeforeRenderThree.bind(this));
210
+ child.layers.mask = this.gameObject.layers.mask;
211
+ }
212
+
213
+ if (this.renderOrder !== undefined) {
214
+ // Objects can have nested renderers (e.g. contain 2 meshes and then again another group)
215
+ // or perhaps just regular child objects that have their own renderer component (?)
216
+ let index = 0;
217
+ for (let i = 0; i < this.gameObject.children.length; i++) {
218
+ const ch = this.gameObject.children[i];
219
+ // ignore nested groups or objects that have their own renderer (aka their own render order settings)
220
+ if (ch.type !== "Mesh" || GameObject.getComponent(ch, Renderer)) continue;
221
+ if (this.renderOrder.length <= index) {
222
+ console.error("Incorrect element count", this);
223
+ break;
224
+ }
225
+ ch.renderOrder = this.renderOrder[index];
226
+ index += 1;
227
+ }
228
+ }
229
+ }
230
+ // TODO: custom shader with sub materials
231
+ else if (type === "Mesh" || type === "SkinnedMesh") {
232
+
233
+ this.context.addBeforeRenderListener(this.gameObject, this.onBeforeRenderThree.bind(this));
234
+
235
+ if (this.renderOrder !== undefined && this.renderOrder.length > 0)
236
+ this.gameObject.renderOrder = this.renderOrder[0];
237
+ }
238
+
239
+ if (this.lightmapIndex >= 0) {
240
+ // use the override lightmap if its not undefined
241
+ const tex = this._lightmapTextureOverride !== undefined
242
+ ? this._lightmapTextureOverride
243
+ : this.context.lightmaps.tryGetLightmap(this.sourceId, this.lightmapIndex);
244
+ if (tex) {
245
+ // tex.encoding = THREE.LinearEncoding;
246
+ this._lightmaps = [];
247
+
248
+ if (type === "Mesh") {
249
+ if (!this.gameObject["material"]?.isMeshBasicMaterial === true) {
250
+ const rm = new RendererLightmap(this.gameObject, this.context);// GameObject.addNewComponent(this.gameObject, RendererLightmap);
251
+ this._lightmaps.push(rm);
252
+ rm.init(this.lightmapIndex, this.lightmapScaleOffset, tex, debugLightmap);
253
+ }
254
+ }
255
+ // for multi materials we need to loop through children
256
+ // and then we add a lightmap renderer component to each of them
257
+ else if (type === "Group") {
258
+ for (const child of this.gameObject.children) {
259
+ if (!child["material"]?.isMeshBasicMaterial) {
260
+ const rm = new RendererLightmap(child as GameObject, this.context);
261
+ this._lightmaps.push(rm);
262
+ rm.init(this.lightmapIndex, this.lightmapScaleOffset, tex, debugLightmap);
263
+ // onBeforeRender is not called when the renderer is on a group
264
+ // this is an issue we probably also need to handle for custom shaders
265
+ // and need a better solution, but for now this fixes lightmaps for multimaterial objects
266
+ rm.bindOnBeforeRender();
267
+ }
268
+ }
269
+ }
270
+ }
271
+ }
272
+
273
+ }
274
+
275
+ private _isInstancingEnabled: boolean = false;
276
+ private handles: InstanceHandle[] | null | undefined = undefined;
277
+ private prevLayers: number[] | null | undefined = undefined;
278
+
279
+ private clearInstancingState() {
280
+ this._isInstancingEnabled = false;
281
+ this.handles = undefined;
282
+ this.prevLayers = undefined;
283
+ }
284
+ setInstancingEnabled(enabled: boolean): boolean {
285
+ if (this._isInstancingEnabled === enabled) return enabled && (this.handles === undefined || this.handles != null && this.handles.length > 0);
286
+ this._isInstancingEnabled = enabled;
287
+ if (enabled) {
288
+ // if handles is undefined we
289
+ if (this.handles === undefined) {
290
+ this.handles = instancing.setup(this.gameObject, this.context, null, { rend: this, foundMeshes: 0 });
291
+ if (this.handles) {
292
+ // const disableSelf = this.gameObject.type === "Mesh" || this.gameObject.children?.length === this.handles.length;
293
+ // this.gameObject.visible = !disableSelf;
294
+ // this.gameObject.type = "Object3D";
295
+ // this.gameObject.material = null;
296
+ // console.log("Using instancing", this.gameObject.visible);
297
+ // this.gameObject.onBeforeRender = () => console.log("SHOULD NOT BE CALLED");
298
+ GameObject.markAsInstancedRendered(this.gameObject, true);
299
+ return true;
300
+ }
301
+ }
302
+ else if (this.handles !== null) {
303
+ for (const handler of this.handles) {
304
+ handler.updateInstanceMatrix(true);
305
+ handler.add();
306
+ }
307
+ // this.gameObject.type = "Object3D";
308
+ // this.gameObject.visible = false;
309
+ GameObject.markAsInstancedRendered(this.gameObject, true);
310
+ return true;
311
+ }
312
+ }
313
+ else {
314
+ if (this.handles) {
315
+ for (const handler of this.handles) {
316
+ handler.remove();
317
+ }
318
+ }
319
+ // this.gameObject.visible = true;
320
+ return true;
321
+ }
322
+
323
+ return false;
324
+ }
325
+
326
+ start() {
327
+ if (this.enableInstancing && !suppressInstancing) {
328
+ this.setInstancingEnabled(true);
329
+ }
330
+ this.gameObject.frustumCulled = this.allowOcclusionWhenDynamic;
331
+ if (this.gameObject.type === "Group") {
332
+ for (let i = 0; i < this.gameObject.children.length; i++) {
333
+ const ch = this.gameObject.children[i];
334
+ ch.frustumCulled = this.allowOcclusionWhenDynamic;
335
+ }
336
+ }
337
+ }
338
+
339
+ onEnable() {
340
+ // this.customShaderHandler?.onEnable();
341
+ // if (this.didEnable && this.enabled && (!this.handles || this.handles?.length <= 0))
342
+ // this.gameObject.visible = true;
343
+ if (this._isInstancingEnabled) {
344
+ this.setInstancingEnabled(true);
345
+ }
346
+ else if (this.enabled) {
347
+ this.gameObject.visible = true;
348
+ this.applyStencil();
349
+ }
350
+ }
351
+
352
+ onDisable() {
353
+ // this.customShaderHandler?.onDisable();
354
+ // if (!this.enabled) {
355
+ // this.gameObject.visible = false;
356
+ // }
357
+ if (this.handles && this.handles.length > 0) {
358
+ this.setInstancingEnabled(false);
359
+ // this.gameObject.visible = false;
360
+ }
361
+ else if (!this.enabled) {
362
+ this.gameObject.visible = false;
363
+ }
364
+ }
365
+
366
+ onDestroy(): void {
367
+ this.handles = null;
368
+ }
369
+
370
+ applyStencil() {
371
+ NEEDLE_render_objects.applyStencil(this);
372
+ }
373
+
374
+ onBeforeRenderThree(_renderer, _scene, _camera, _geometry, material, _group) {
375
+
376
+ // progressive load before rendering so we only load textures for visible materials
377
+ if (!suppressProgressiveLoading && material._didRequestTextureLOD === undefined) {
378
+ material._didRequestTextureLOD = 0;
379
+ if (debugProgressiveLoading) {
380
+ console.log("Load material LOD (with delay)", material.name);
381
+ setTimeout(() => {
382
+ NEEDLE_deferred_texture.assignTextureLOD(this.context, this.sourceId, material);
383
+ }, 2000);
384
+ }
385
+ else {
386
+ NEEDLE_deferred_texture.assignTextureLOD(this.context, this.sourceId, material);
387
+ }
388
+ }
389
+
390
+ if (material.envMapIntensity !== undefined) {
391
+ const factor = this.hasLightmap ? Math.PI : 1;
392
+ material.envMapIntensity = Math.max(0, this.context.rendererData.environmentIntensity / factor);
393
+ }
394
+
395
+ // if (!camera) {
396
+ // let isXRCamera = false;
397
+ // if (this.context.isInXR) {
398
+ // // @ts-ignore
399
+ // const arr = this.context.renderer.xr.getCamera() as ArrayCamera;
400
+ // if (arr.cameras?.length > 0) {
401
+ // camera = arr;
402
+ // isXRCamera = true;
403
+ // }
404
+ // }
405
+ // }
406
+
407
+ // if (this.customShaderHandler) {
408
+ // this.customShaderHandler.onBeforeRender(renderer, scene, camera, geometry, material, group);
409
+ // }
410
+ // else if (this.rawShaderHandler) {
411
+ // for (const h of this.rawShaderHandler) {
412
+ // h.onBeforeRender(this.gameObject, camera);
413
+ // }
414
+ // }
415
+
416
+ if (this._lightmaps) {
417
+ for (const lm of this._lightmaps) {
418
+ lm.onBeforeRenderThree(material);
419
+ }
420
+ }
421
+ }
422
+
423
+ onBeforeRender() {
424
+ if (!this.gameObject) {
425
+ return;
426
+ }
427
+
428
+ const needsUpdate: boolean = this.gameObject[NEED_UPDATE_INSTANCE_KEY] === true || this.gameObject.matrixWorldNeedsUpdate;
429
+
430
+ if (this.gameObject.type === "Group" && this.gameObject.children?.length > 0) {
431
+ for (const ch of this.gameObject.children) {
432
+ this.applySettings(ch);
433
+ }
434
+ }
435
+ else {
436
+ this.applySettings(this.gameObject);
437
+ }
438
+
439
+ if (needsUpdate) {
440
+ delete this.gameObject[NEED_UPDATE_INSTANCE_KEY];
441
+ if (this.handles) {
442
+ const remove = false;// Math.random() < .01;
443
+ for (let i = this.handles.length - 1; i >= 0; i--) {
444
+ const h = this.handles[i];
445
+ if (remove) {
446
+ h.remove();
447
+ this.handles.splice(i, 1);
448
+ }
449
+ else
450
+ h.updateInstanceMatrix();
451
+ }
452
+ this.gameObject.matrixWorldNeedsUpdate = false;
453
+ }
454
+ }
455
+
456
+ if (this.handles && this.handles.length <= 0) {
457
+ GameObject.markAsInstancedRendered(this.gameObject, false);
458
+ }
459
+
460
+ if (this._isInstancingEnabled && this.handles) {
461
+ for (let i = 0; i < this.handles.length; i++) {
462
+ const handle = this.handles[i];
463
+ if (!this.prevLayers) this.prevLayers = [];
464
+ const layer = handle.object.layers.mask;
465
+ if (i >= this.prevLayers.length) this.prevLayers.push(layer);
466
+ else this.prevLayers[i] = layer;
467
+ handle.object.layers.disableAll();
468
+ }
469
+ }
470
+ }
471
+
472
+ onAfterRender() {
473
+ if (this._isInstancingEnabled && this.handles && this.prevLayers && this.prevLayers.length >= this.handles.length) {
474
+ for (let i = 0; i < this.handles.length; i++) {
475
+ const handle = this.handles[i];
476
+ handle.object.layers.mask = this.prevLayers[i];
477
+ }
478
+ }
479
+ }
480
+
481
+ private applySettings(go: THREE.Object3D) {
482
+ go.receiveShadow = this.receiveShadows;
483
+ if (this.shadowCastingMode == ShadowCastingMode.On) {
484
+ go.castShadow = true;
485
+ }
486
+ else go.castShadow = false;
487
+ }
488
+
489
+ }
490
+
491
+ export class MeshRenderer extends Renderer {
492
+
493
+ }
494
+
495
+ export class SkinnedMeshRenderer extends MeshRenderer {
496
+ awake() {
497
+ super.awake();
498
+ // disable skinned mesh occlusion because of https://github.com/mrdoob/three.js/issues/14499
499
+ this.allowOcclusionWhenDynamic = false;
500
+ }
501
+ }
502
+
503
+ export enum ShadowCastingMode {
504
+ /// <summary>
505
+ /// <para>No shadows are cast from this object.</para>
506
+ /// </summary>
507
+ Off,
508
+ /// <summary>
509
+ /// <para>Shadows are cast from this object.</para>
510
+ /// </summary>
511
+ On,
512
+ /// <summary>
513
+ /// <para>Shadows are cast from this object, treating it as two-sided.</para>
514
+ /// </summary>
515
+ TwoSided,
516
+ /// <summary>
517
+ /// <para>Object casts shadows, but is otherwise invisible in the Scene.</para>
518
+ /// </summary>
519
+ ShadowsOnly,
520
+ }
521
+
522
+
523
+
524
+ declare class InstancingSetupArgs { rend: Renderer; foundMeshes: number };
525
+
526
+ class InstancingHandler {
527
+
528
+ public objs: InstancedMeshRenderer[] = [];
529
+
530
+ public setup(obj: THREE.Object3D, context: Context, handlesArray: InstanceHandle[] | null, args: InstancingSetupArgs, level: number = 0)
531
+ : InstanceHandle[] | null {
532
+
533
+ const res = this.tryCreateOrAddInstance(obj, context, args);
534
+ if (res) {
535
+ if (handlesArray === null) handlesArray = [];
536
+ handlesArray.push(res);
537
+ return handlesArray;
538
+ }
539
+
540
+ if (level <= 0 && obj.type !== "Mesh") {
541
+ const nextLevel = level + 1;
542
+ for (const ch of obj.children) {
543
+ handlesArray = this.setup(ch, context, handlesArray, args, nextLevel);
544
+ }
545
+ }
546
+ return handlesArray;
547
+ }
548
+
549
+ private tryCreateOrAddInstance(obj: THREE.Object3D, context: Context, args: InstancingSetupArgs): InstanceHandle | null {
550
+ if (obj.type === "Mesh") {
551
+ const index = args.foundMeshes;
552
+ args.foundMeshes += 1;
553
+ if (!args.rend.enableInstancing) return null;
554
+ if (index >= args.rend.enableInstancing.length) {
555
+ // console.error("Something is wrong with instance setup", obj, args.rend.enableInstancing, index);
556
+ return null;
557
+ }
558
+ if (!args.rend.enableInstancing[index]) {
559
+ // instancing is disabled
560
+ // console.log("Instancing is disabled", obj);
561
+ return null;
562
+ }
563
+ // instancing is enabled:
564
+ const mesh = obj as THREE.Mesh;
565
+ const geo = mesh.geometry as THREE.BufferGeometry;
566
+ const mat = mesh.material as THREE.Material;
567
+
568
+ for (const i of this.objs) {
569
+ if (i.isFull()) continue;
570
+ if (i.geo === geo && i.material === mat) {
571
+ return i.addInstance(mesh);
572
+ }
573
+ }
574
+ // console.log("Add new instance mesh renderer", obj);
575
+ const i = new InstancedMeshRenderer(obj.name, geo, mat, 200, context);
576
+ this.objs.push(i);
577
+ return i.addInstance(mesh);
578
+ }
579
+ return null;
580
+ }
581
+ }
582
+ const instancing: InstancingHandler = new InstancingHandler();
583
+
584
+ class InstanceHandle {
585
+
586
+ get name(): string {
587
+ return this.object.name;
588
+ }
589
+
590
+ instanceIndex: number = -1;
591
+ object: THREE.Mesh;
592
+ instancer: InstancedMeshRenderer;
593
+
594
+ constructor(instanceIndex: number, originalObject: THREE.Mesh, instancer: InstancedMeshRenderer) {
595
+ this.instanceIndex = instanceIndex;
596
+ this.object = originalObject;
597
+ this.instancer = instancer;
598
+ GameObject.markAsInstancedRendered(originalObject, true);
599
+ // this.object.visible = false;
600
+ }
601
+
602
+ updateInstanceMatrix(updateChildren: boolean = false) {
603
+ if (this.instanceIndex < 0) return;
604
+ this.object.updateWorldMatrix(true, updateChildren);
605
+ this.instancer.updateInstance(this.object.matrixWorld, this.instanceIndex);
606
+ }
607
+
608
+ add() {
609
+ if (this.instanceIndex >= 0) return;
610
+ this.instancer.add(this);
611
+ }
612
+
613
+ remove() {
614
+ if (this.instanceIndex < 0) return;
615
+ this.instancer.remove(this);
616
+ }
617
+ }
618
+
619
+ class InstancedMeshRenderer {
620
+
621
+ public name: string = "";
622
+ public geo: THREE.BufferGeometry;
623
+ public material: THREE.Material;
624
+ get currentCount(): number { return this.inst.count; }
625
+
626
+ private context: Context;
627
+ private inst: THREE.InstancedMesh;
628
+ private handles: (InstanceHandle | null)[] = [];
629
+ private maxCount: number;
630
+
631
+ private static nullMatrix: THREE.Matrix4 = new THREE.Matrix4();
632
+
633
+ isFull(): boolean {
634
+ return this.currentCount >= this.maxCount;
635
+ }
636
+
637
+ constructor(name: string, geo: THREE.BufferGeometry, material: THREE.Material, count: number, context: Context) {
638
+ this.name = name;
639
+ this.geo = geo;
640
+ this.material = material;
641
+ this.context = context;
642
+ this.maxCount = count;
643
+ if(debugInstancing){
644
+ material = new THREE.MeshBasicMaterial({ color: this.randomColor() });
645
+ }
646
+ this.inst = new THREE.InstancedMesh(geo, material, count);
647
+ this.inst.count = 0;
648
+ this.inst.layers.set(2);
649
+ this.inst.visible = true;
650
+ // this.inst.castShadow = true;
651
+ // this.inst.receiveShadow = true;
652
+ this.context.scene.add(this.inst);
653
+ // console.log(this.inst);
654
+ // this.context.pre_render_callbacks.push(this.onPreRender.bind(this));
655
+
656
+ // setInterval(() => {
657
+ // this.inst.visible = !this.inst.visible;
658
+ // }, 500);
659
+ }
660
+
661
+ private randomColor(){
662
+ return new THREE.Color(Math.random(), Math.random(), Math.random());
663
+ }
664
+
665
+ addInstance(obj: THREE.Mesh): InstanceHandle | null {
666
+ if (this.currentCount >= this.maxCount) {
667
+ console.error("TOO MANY INSTANCES - resize is not yet implemented!", this.inst.count); // todo: make it resize
668
+ return null;
669
+ }
670
+ const handle = new InstanceHandle(-1, obj, this);
671
+ this.add(handle);
672
+ return handle;
673
+ }
674
+
675
+
676
+ add(handle: InstanceHandle) {
677
+ if (handle.instanceIndex < 0) {
678
+ handle.instanceIndex = this.currentCount;
679
+ // console.log(handle.instanceIndex, this.currentCount);
680
+ if (handle.instanceIndex >= this.handles.length)
681
+ this.handles.push(handle);
682
+ else this.handles[handle.instanceIndex] = handle;
683
+ }
684
+ // console.log("Handle instance");
685
+ handle.object.updateWorldMatrix(true, true);
686
+ this.inst.setMatrixAt(handle.instanceIndex, handle.object.matrixWorld);
687
+ this.inst.instanceMatrix.needsUpdate = true;
688
+ this.inst.count += 1;
689
+
690
+ if (this.inst.count > 0)
691
+ this.inst.visible = true;
692
+
693
+ // console.log("Added", this.name, this.inst.count, this.handles);
694
+ }
695
+
696
+ remove(handle: InstanceHandle) {
697
+ if (!handle) return;
698
+ if (handle.instanceIndex < 0 || handle.instanceIndex >= this.handles.length || this.inst.count <= 0) {
699
+ return;
700
+ }
701
+ if (this.handles[handle.instanceIndex] !== handle) {
702
+ console.error("instance handle is not part of renderer, was it removed before?", handle.instanceIndex, this.name);
703
+ const index = this.handles.indexOf(handle);
704
+ if (index < 0)
705
+ return;
706
+ handle.instanceIndex = index;
707
+ }
708
+ this.handles[handle.instanceIndex] = null;
709
+ this.inst.setMatrixAt(handle.instanceIndex, InstancedMeshRenderer.nullMatrix);
710
+ const removedLastElement = handle.instanceIndex >= this.currentCount - 1;
711
+ // console.log(removedLastElement, this.currentCount, handle.instanceIndex, this.handles);
712
+ if (!removedLastElement && this.currentCount > 0) {
713
+ const lastElement = this.handles[this.currentCount - 1];
714
+ if (lastElement) {
715
+ lastElement.instanceIndex = handle.instanceIndex;
716
+ lastElement.updateInstanceMatrix();
717
+ this.handles[handle.instanceIndex] = lastElement;
718
+ this.handles[this.currentCount - 1] = null;
719
+ // this.inst.setMatrixAt(handle.instanceIndex, lastElement.object.matrixWorld);
720
+ // this.inst.setMatrixAt(this.currentCount - 1, InstancedMeshRenderer.nullMatrix);
721
+ }
722
+ }
723
+
724
+ if (this.inst.count > 0)
725
+ this.inst.count -= 1;
726
+ handle.instanceIndex = -1;
727
+
728
+ if (this.inst.count <= 0)
729
+ this.inst.visible = false;
730
+
731
+ this.inst.instanceMatrix.needsUpdate = true;
732
+ }
733
+
734
+ updateInstance(mat: THREE.Matrix4, index: number) {
735
+ this.inst.setMatrixAt(index, mat);
736
+ this.inst.instanceMatrix.needsUpdate = true;
737
+ }
735
738
  }