@needle-tools/engine 2.58.0-pre → 2.58.1-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "2.58.0-pre",
3
+ "version": "2.58.1-pre",
4
4
  "description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally.",
5
5
  "main": "dist/needle-engine.js",
6
6
  "module": "src/needle-engine.ts",
@@ -2,7 +2,7 @@ import { Context } from './engine_setup';
2
2
  import * as utils from "./engine_generic_utils";
3
3
  import * as constants from "./engine_constants";
4
4
  import { getParam } from './engine_utils';
5
- import { Object3D } from 'three';
5
+ import { CubeCamera, Object3D, WebGLCubeRenderTarget } from 'three';
6
6
  import { IComponent } from './engine_types';
7
7
  import { isActiveSelf, setActive } from './engine_gameobject';
8
8
 
@@ -83,6 +83,8 @@ export function processNewScripts(context: Context) {
83
83
  updateActiveInHierarchyWithoutEventCall(script.gameObject);
84
84
  if (script.activeAndEnabled)
85
85
  utils.safeInvoke(script.__internalAwake.bind(script));
86
+
87
+ // registerPrewarmObject(script.gameObject, context);
86
88
  }
87
89
  }
88
90
  catch (err) {
@@ -320,4 +322,62 @@ function perComponent(go: THREE.Object3D, evt: (comp: IComponent) => void) {
320
322
  evt(comp);
321
323
  }
322
324
  }
325
+ }
326
+
327
+
328
+ const prewarmList: Map<Context, Object3D[]> = new Map();
329
+ const $prewarmedFlag = Symbol("prewarmFlag");
330
+ const $waitingForPrewarm = Symbol("waitingForPrewarm");
331
+ const debugPrewarm = getParam("debugprewarm");
332
+
333
+ export function registerPrewarmObject(obj: Object3D, context: Context) {
334
+ if (!obj) return;
335
+ // allow objects to be marked as prewarmed in which case we dont need to register them again
336
+ if (obj[$prewarmedFlag] === true) return;
337
+ if (obj[$waitingForPrewarm] === true) return;
338
+ if (!prewarmList.has(context)) {
339
+ prewarmList.set(context, []);
340
+ }
341
+ obj[$waitingForPrewarm] = true;
342
+ const list = prewarmList.get(context);
343
+ list!.push(obj);
344
+ if(debugPrewarm) console.debug("register prewarm", obj.name);
345
+ }
346
+
347
+ let prewarmTarget: WebGLCubeRenderTarget | null = null;
348
+ let prewarmCamera: CubeCamera | null = null;
349
+
350
+ // called by the engine to remove scroll or animation hiccup when objects are rendered/compiled for the first time
351
+ export function runPrewarm(context: Context) {
352
+ if (!context) return;
353
+ const list = prewarmList.get(context);
354
+ if (!list?.length) return;
355
+
356
+ const cam = context.mainCamera;
357
+ if (cam) {
358
+ if(debugPrewarm) console.log("prewarm", list.length, "objects", [...list]);
359
+ const renderer = context.renderer;
360
+ const scene = context.scene;
361
+ renderer.compile(scene, cam!)
362
+ prewarmTarget ??= new WebGLCubeRenderTarget(64)
363
+ prewarmCamera ??= new CubeCamera(0.001, 9999999, prewarmTarget);
364
+ prewarmCamera.update(renderer, scene);
365
+ for (const obj of list) {
366
+ obj[$prewarmedFlag] = true;
367
+ obj[$waitingForPrewarm] = false;
368
+ }
369
+ list.length = 0;
370
+ if(debugPrewarm) console.log("prewarm done");
371
+ }
372
+ }
373
+
374
+ export function clearPrewarmList(context: Context) {
375
+ const list = prewarmList.get(context);
376
+ if (list) {
377
+ for (const obj of list) {
378
+ obj[$waitingForPrewarm] = false;
379
+ }
380
+ list.length = 0;
381
+ }
382
+ prewarmList.delete(context);
323
383
  }
@@ -12,6 +12,7 @@ import { createBuiltinComponents, writeBuiltinComponentData } from "./engine_glt
12
12
  import { SerializationContext } from "./engine_serialization_core";
13
13
  import { NEEDLE_components } from "./extensions/NEEDLE_components";
14
14
  import { addNewComponent, getComponentInChildren } from "./engine_components";
15
+ import { registerPrewarmObject } from "./engine_mainloop_utils";
15
16
 
16
17
 
17
18
  export class NeedleGltfLoader implements INeedleGltfLoader {
@@ -116,6 +117,7 @@ export function parseSync(context: Context, data, path: string, seed: number | U
116
117
  invokeEvents(GltfLoadEventType.AfterLoaded, new GltfLoadEvent(context, path, loader, data));
117
118
  await handleLoadedGltf(context, path, data, seed, componentsExtension);
118
119
  invokeEvents(GltfLoadEventType.FinishedSetup, new GltfLoadEvent(context, path, loader, data));
120
+ registerPrewarmObject(data.scene, context);
119
121
  resolve(data);
120
122
 
121
123
  }, err => {
@@ -146,6 +148,7 @@ export function loadSync(context: Context, url: string, seed: number | UIDProvid
146
148
  invokeEvents(GltfLoadEventType.AfterLoaded, new GltfLoadEvent(context, url, loader, data));
147
149
  await handleLoadedGltf(context, url, data, seed, componentsExtension);
148
150
  invokeEvents(GltfLoadEventType.FinishedSetup, new GltfLoadEvent(context, url, loader, data));
151
+ registerPrewarmObject(data.scene, context);
149
152
  resolve(data);
150
153
 
151
154
  }, evt => {
@@ -701,6 +701,7 @@ export class Context {
701
701
 
702
702
 
703
703
  if (!this.isManagedExternally) {
704
+ looputils.runPrewarm(this);
704
705
  this._currentFrameEvent = -10;
705
706
  this.renderNow();
706
707
  this._currentFrameEvent = FrameEvent.OnAfterRender;
@@ -12,6 +12,8 @@ import { NEED_UPDATE_INSTANCE_KEY } from "../engine/engine_instancing";
12
12
  import { IRenderer, ISharedMaterials } from "../engine/engine_types";
13
13
  import { debug, ReflectionProbe } from "./ReflectionProbe";
14
14
  import { setCustomVisibility } from "../engine/js-extensions/Layers";
15
+ import { isLocalNetwork } from "../engine/engine_networking_utils";
16
+ import { showBalloonWarning } from "../engine/debug/debug";
15
17
 
16
18
  // for staying compatible with old code
17
19
  export { InstancingUtil } from "../engine/engine_instancing";
@@ -53,12 +55,14 @@ class SharedMaterialArray implements ISharedMaterials {
53
55
  private _renderer: Renderer;
54
56
  private _targets: THREE.Object3D[] = [];
55
57
 
58
+ private _indexMapMaxIndex?: number;
59
+ private _indexMap?: Map<number, number>;
56
60
 
57
61
  is(renderer: Renderer) {
58
62
  return this._renderer === renderer;
59
63
  }
60
64
 
61
- constructor(renderer: Renderer) {
65
+ constructor(renderer: Renderer, originalMaterials: Material[]) {
62
66
  this._renderer = renderer;
63
67
  const setMaterial = this.setMaterial.bind(this);
64
68
  const getMaterial = this.getMaterial.bind(this);
@@ -76,6 +80,38 @@ class SharedMaterialArray implements ISharedMaterials {
76
80
  }
77
81
  }
78
82
 
83
+ // this is useful to have an index map when e.g. materials are trying to be assigned by index
84
+ let hasMissingMaterials = false;
85
+ let indexMap: Map<number, number> | undefined = undefined;
86
+ let maxIndex: number = 0;
87
+ for (let i = 0; i < this._targets.length; i++) {
88
+ const target = this._targets[i] as Mesh;
89
+ if (!target) continue;
90
+ const mat = target.material as Material;
91
+ if (!mat) continue;
92
+ for (let k = 0; k < originalMaterials.length; k++) {
93
+ const orig = originalMaterials[k];
94
+ if (!orig) {
95
+ hasMissingMaterials = true;
96
+ continue;
97
+ }
98
+ if (mat.name === orig.name) {
99
+ if (indexMap === undefined) indexMap = new Map();
100
+ indexMap.set(k, i);
101
+ maxIndex = Math.max(maxIndex, k);
102
+ // console.log(`Material ${mat.name} at ${k} was found at index ${i} in renderer ${renderer.name}.`)
103
+ break;
104
+ }
105
+ }
106
+ }
107
+ if (hasMissingMaterials) {
108
+ this._indexMapMaxIndex = maxIndex;
109
+ this._indexMap = indexMap;
110
+ const warningMessage = `Renderer ${renderer.name} was initialized with missing materials - this may lead to unexpected behaviour when trying to access sharedMaterials by index.`;
111
+ console.warn(warningMessage);
112
+ if(isLocalNetwork()) showBalloonWarning("Found renderer with missing materials: please check the console for details.");
113
+ }
114
+
79
115
  // this lets us override the javascript indexer, only works in ES6 tho
80
116
  // but like that we can use sharedMaterials[index] and it will be assigned to the object directly
81
117
  return new Proxy(this, {
@@ -98,10 +134,22 @@ class SharedMaterialArray implements ISharedMaterials {
98
134
  }
99
135
 
100
136
  get length(): number {
137
+ if (this._indexMapMaxIndex !== undefined) return this._indexMapMaxIndex + 1;
101
138
  return this._targets.length;
102
139
  }
103
140
 
141
+ private resolveIndex(index: number): number {
142
+ const map = this._indexMap;
143
+ // if we have a index map it means that some materials were missing
144
+ if (map) {
145
+ if (map.has(index)) return map.get(index) as number;
146
+ // return -1;
147
+ }
148
+ return index;
149
+ }
150
+
104
151
  private setMaterial(mat: Material, index: number) {
152
+ index = this.resolveIndex(index);
105
153
  if (index < 0 || index >= this._targets.length) return;
106
154
  const target = this._targets[index];
107
155
  if (!target || target["material"] === undefined) return;
@@ -109,6 +157,7 @@ class SharedMaterialArray implements ISharedMaterials {
109
157
  }
110
158
 
111
159
  private getMaterial(index: number) {
160
+ index = this.resolveIndex(index);
112
161
  if (index < 0) return null;
113
162
  const obj = this._targets;
114
163
  if (index >= obj.length) return null;
@@ -193,10 +242,18 @@ export class Renderer extends Behaviour implements IRenderer {
193
242
  }
194
243
 
195
244
  private _sharedMaterials!: SharedMaterialArray;
245
+ private _originalMaterials: Material[] = [];
246
+
247
+ // this is just available during deserialization
248
+ private set sharedMaterials(_val: Array<Material | null>) {
249
+ // TODO: elements in the array might be missing at the moment which leads to problems if an index is serialized
250
+ this._originalMaterials = _val as Material[];
251
+ }
196
252
 
253
+ //@ts-ignore
197
254
  get sharedMaterials(): SharedMaterialArray {
198
255
  if (!this._sharedMaterials || !this._sharedMaterials.is(this))
199
- this._sharedMaterials = new SharedMaterialArray(this);
256
+ this._sharedMaterials = new SharedMaterialArray(this, this._originalMaterials);
200
257
  return this._sharedMaterials!;
201
258
  }
202
259