Needle Engine

Changes between version 3.13.0-beta and 3.14.0-beta
Files changed (9) hide show
  1. src/engine/engine_context.ts +157 -40
  2. src/engine/engine_element.ts +36 -70
  3. src/engine/engine_physics_rapier.ts +1 -1
  4. src/engine/engine_serialization_builtin_serializer.ts +9 -5
  5. src/engine/engine_types.ts +9 -8
  6. src/engine-components/ui/EventSystem.ts +0 -1
  7. src/engine-components/ui/Graphic.ts +12 -4
  8. src/engine/extensions/NEEDLE_techniques_webgl.ts +23 -8
  9. src/needle-engine.ts +7 -2
src/engine/engine_context.ts CHANGED
@@ -30,7 +30,9 @@
30
30
  import { ContextEvent, ContextRegistry } from './engine_context_registry.js';
31
31
  import { delay, getParam } from './engine_utils.js';
32
32
  import { VERSION } from './engine_constants.js';
33
- import { isDevEnvironment, LogType, showBalloonMessage } from './debug/index.js';
33
+ import { isDevEnvironment, LogType, showBalloonMessage, showBalloonWarning } from './debug/index.js';
34
+ import { getLoader } from './engine_gltf.js';
35
+ import { isLocalNetwork } from './engine_networking_utils.js';
34
36
 
35
37
 
36
38
  const debug = utils.getParam("debugcontext");
@@ -43,26 +45,45 @@
43
45
  // those will be accessed from our custom html element to load them into their context
44
46
  export const build_scene_functions: { [name: string]: (context: Context) => Promise<void> } = {};
45
47
 
48
+
46
49
  export declare class LoadingProgressArgs {
50
+ /** the name or URL of the loaded file */
47
51
  name: string;
52
+ /** the loading progress event from the loader */
48
53
  progress: ProgressEvent;
54
+ /** the index of the loaded file */
49
55
  index: number;
56
+ /** the total number of files to load */
50
57
  count: number;
51
58
  }
52
- export declare class LoadingOptions {
53
- progress: (args: LoadingProgressArgs) => void;
59
+ export declare class ContextCreateArgs {
60
+ /** list of glTF or GLB files to load */
61
+ files: Array<string>;
62
+ /** called when loading a provided glTF file started */
63
+ onLoadingStart?: (index: number, file: string) => void;
64
+ /** called on update for each loaded glTF file */
65
+ onLoadingProgress?: (args: LoadingProgressArgs) => void;
66
+ /** Called after a gLTF file has finished loading */
67
+ onLoadingFinished?: (index: number, file: string, glTF: GLTF | null) => void;
54
68
  }
55
69
 
56
70
  export class ContextArgs {
57
71
  name?: string;
72
+ /** for debugging only */
58
73
  alias?: string;
59
- domElement: HTMLElement | null;
60
- renderer?: WebGLRenderer = undefined;
74
+ /** the hash is used as a seed when initially loading the scene files */
61
75
  hash?: string;
62
76
 
63
- constructor(domElement: HTMLElement | null) {
64
- this.domElement = domElement ?? document.body;
65
- }
77
+ /** when true the context will not check if it's visible in the viewport and always update and render */
78
+ runInBackground?: boolean;
79
+ /** the DOM element the context belongs to or is inside of (this does not have to be the canvas. use renderer.domElement if you want to access the dom canvas) */
80
+ domElement?: HTMLElement | null;
81
+ /** externally owned renderer */
82
+ renderer?: WebGLRenderer;
83
+ /** externally owned camera */
84
+ camera?: Camera;
85
+ /** externally owned scene */
86
+ scene?: Scene;
66
87
  }
67
88
 
68
89
  export enum FrameEvent {
@@ -81,6 +102,7 @@
81
102
  ImmersiveAR = "immersive-ar",
82
103
  }
83
104
 
105
+ /** threejs callback event signature */
84
106
  export declare type OnRenderCallback = (renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group) => void
85
107
 
86
108
 
@@ -202,7 +224,7 @@
202
224
  get isInAR() { return this.xrSessionMode === XRSessionMode.ImmersiveAR; }
203
225
  get xrSession() { return this.renderer.xr?.getSession(); }
204
226
  get xrFrame() { return this._xrFrame }
205
- get xrCamera() : WebXRArrayCamera | undefined { return this.renderer.xr?.getCamera(); }
227
+ get xrCamera(): WebXRArrayCamera | undefined { return this.renderer.xr?.getCamera(); }
206
228
  private _xrFrame: XRFrame | null = null;
207
229
  get arOverlayElement(): HTMLElement {
208
230
  const el = this.domElement as any;
@@ -232,7 +254,13 @@
232
254
  scripts_WithCorroutines: IComponent[] = [];
233
255
  coroutines: { [FrameEvent: number]: Array<CoroutineData> } = {}
234
256
 
257
+ mainCameraComponent: ICamera | undefined;
258
+
259
+ private _camera: Camera | null = null;
235
260
  get mainCamera(): Camera | null {
261
+ if (this._camera) {
262
+ return this._camera;
263
+ }
236
264
  if (this.mainCameraComponent) {
237
265
  const cam = this.mainCameraComponent as ICamera;
238
266
  if (!cam.cam)
@@ -241,7 +269,9 @@
241
269
  }
242
270
  return null;
243
271
  }
244
- mainCameraComponent: ICamera | undefined;
272
+ set mainCamera(cam: Camera | null) {
273
+ this._camera = cam;
274
+ }
245
275
 
246
276
  post_setup_callbacks: Function[] = [];
247
277
  pre_update_callbacks: Function[] = [];
@@ -284,6 +314,7 @@
284
314
  this.alias = args?.alias;
285
315
  this.domElement = args?.domElement || document.body;
286
316
  this.hash = args?.hash;
317
+
287
318
  if (args?.renderer) {
288
319
  this.renderer = args.renderer;
289
320
  this.isManagedExternally = true;
@@ -292,10 +323,11 @@
292
323
  this.createRenderer();
293
324
  }
294
325
 
295
- this.scene = new Scene();
326
+ if (args?.runInBackground !== undefined) this.runInBackground = args.runInBackground;
327
+ if (args?.scene) this.scene = args.scene;
328
+ else this.scene = new Scene();
329
+ if (args?.camera) this._camera = args.camera;
296
330
 
297
- ContextRegistry.register(this);
298
-
299
331
  this.application = new Application(this);
300
332
  this.time = new Time();
301
333
  this.input = new Input(this);
@@ -307,6 +339,7 @@
307
339
  this.lightmaps = new LightDataRegistry(this);
308
340
  this.players = new PlayerViewManager(this);
309
341
 
342
+
310
343
  const resizeCallback = () => this._sizeChanged = true;
311
344
  window.addEventListener('resize', resizeCallback);
312
345
  this._disposeCallbacks.push(() => window.removeEventListener('resize', resizeCallback));
@@ -319,6 +352,8 @@
319
352
  this._isVisible = entries[0].isIntersecting;
320
353
  });
321
354
  this._disposeCallbacks.push(() => this._intersectionObserver?.disconnect());
355
+
356
+ ContextRegistry.register(this);
322
357
  }
323
358
 
324
359
  private createRenderer() {
@@ -402,10 +437,14 @@
402
437
  camera.updateProjectionMatrix();
403
438
  }
404
439
 
405
- async onCreate(buildScene?: (context: Context, loadingOptions?: LoadingOptions) => Promise<GLTF[] | null>, opts?: LoadingOptions) {
440
+ /** @deprecated use create. This method will be removed in a future version */
441
+ async onCreate(opts?: ContextCreateArgs) {
442
+ return this.create(opts);
443
+ }
444
+ async create(opts?: ContextCreateArgs) {
406
445
  try {
407
446
  this._isCreating = true;
408
- const res = await this.internalOnCreate(buildScene, opts);
447
+ const res = await this.internalOnCreate(opts);
409
448
  return res;
410
449
  }
411
450
  finally {
@@ -610,12 +649,15 @@
610
649
  }
611
650
 
612
651
 
613
- private async internalOnCreate(buildScene?: (context: Context, opts?: LoadingOptions) => Promise<GLTF[] | null>, opts?: LoadingOptions) {
652
+ private _createId: number = 0;
653
+ private async internalOnCreate(opts?: ContextCreateArgs) {
654
+ const createId = ++this._createId;
614
655
 
615
656
  this.clear();
616
657
  // stop the animation loop if its running during creation
617
658
  // since we do not want to start enabling scripts etc before they are deserialized
618
- this.renderer?.setAnimationLoop(null);
659
+ if (this.isManagedExternally === false)
660
+ this.renderer?.setAnimationLoop(null);
619
661
 
620
662
  await delay(1);
621
663
 
@@ -624,26 +666,25 @@
624
666
 
625
667
  // load and create scene
626
668
  let prepare_succeeded = true;
627
- let loadedFiles: GLTF[] | undefined | null = undefined;
669
+ let loadedFiles!: Array<GLTF | null>;
628
670
  try {
629
671
  Context.Current = this;
630
- if (buildScene) {
631
- loadedFiles = await buildScene(this, opts);
632
- // if the files are null the loading was cancelled
633
- // this happens when the src attribute changes during loading and we want to load other files
634
- if(loadedFiles === null) return false;
672
+ if (opts) {
673
+ loadedFiles = await this.internalLoadInitialContent(createId, opts);
635
674
  }
675
+ else loadedFiles = [];
636
676
  }
637
677
  catch (err) {
638
678
  console.error(err);
639
679
  prepare_succeeded = false;
640
680
  }
641
681
  if (!prepare_succeeded) return false;
682
+ if (createId !== this._createId) return false;
642
683
 
643
684
  this.internalOnUpdateVisible();
644
685
 
645
686
  if (!this.renderer) {
646
- if(debug) console.warn("Context has no renderer (perhaps it was disconnected?", this.domElement.isConnected);
687
+ if (debug) console.warn("Context has no renderer (perhaps it was disconnected?", this.domElement.isConnected);
647
688
  return false;
648
689
  }
649
690
 
@@ -746,18 +787,87 @@
746
787
  // the _defaultTargetFramerate is intentionally an object so it can be changed at any time if not explictly set by the user
747
788
  this.targetFrameRate = Context._defaultTargetFramerate;
748
789
  }
749
- else if(debug) console.log("Target framerate set to", this.targetFrameRate);
790
+ else if (debug) console.log("Target framerate set to", this.targetFrameRate);
750
791
 
751
792
  this._isCreating = false;
752
- this.restartRenderLoop();
793
+ if (!this.isManagedExternally)
794
+ this.restartRenderLoop();
753
795
  this._dispatchReadyAfterFrame = true;
754
796
  return ContextRegistry.dispatchCallback(ContextEvent.ContextCreated, this, { files: loadedFiles });
755
797
  }
756
798
 
757
- private _accumulatedTime = 0;
758
- private _framerateClock = new Clock();
759
- private _dispatchReadyAfterFrame = false;
799
+ private async internalLoadInitialContent(createId: number, args: ContextCreateArgs): Promise<Array<GLTF>> {
800
+ const results = new Array<GLTF>();
801
+ // early out if we dont have any files to load
802
+ if (args.files.length === 0) return results;
760
803
 
804
+
805
+ const files = [...args.files];
806
+ const progressArg: LoadingProgressArgs = {
807
+ name: "",
808
+ progress: null!,
809
+ index: 0,
810
+ count: files.length
811
+ }
812
+
813
+ const loader = getLoader();
814
+ let loadingHash = 0;
815
+ if (this.hash) loadingHash = Number.parseInt(this.hash) ?? 0;
816
+ for (let i = 0; i < files.length; i++) {
817
+ // abort loading if the create id has changed
818
+ if (createId !== this._createId) {
819
+ if (debug) console.log("Aborting loading because create id changed", createId, this._createId);
820
+ break;
821
+ }
822
+ const file = files[i];
823
+
824
+ if (!file.includes(".glb") && !file.includes(".gltf")) {
825
+ const warning = `Needle Engine: found suspicious src "${file}"`;
826
+ console.warn(warning);
827
+ if (isLocalNetwork()) showBalloonWarning(warning);
828
+ }
829
+
830
+ args?.onLoadingStart?.call(this, i, file);
831
+ const res = await loader.loadSync(this, file, file, loadingHash, prog => {
832
+ progressArg.name = file;
833
+ progressArg.progress = prog;
834
+ progressArg.index = i;
835
+ progressArg.count = files.length;
836
+ args.onLoadingProgress?.call(this, progressArg);
837
+ });
838
+ args?.onLoadingFinished?.call(this, i, file, res ?? null);
839
+ if (res) {
840
+ results.push(res);
841
+ }
842
+ else {
843
+ // a file could not be loaded
844
+ console.warn("Could not load file: " + file);
845
+ }
846
+ }
847
+
848
+ // if the id was changed while still loading
849
+ // then we want to cleanup/destroy previously loaded files
850
+ if (createId !== this._createId) {
851
+ for (const res of results) {
852
+ if (res) {
853
+ for (const scene of res.scenes)
854
+ destroy(scene, true, true);
855
+ }
856
+ }
857
+ }
858
+ // otherwise we want to add the loaded files to the current scene
859
+ else {
860
+ for (const res of results) {
861
+ if (res) {
862
+ this.scene.add(res.scene);
863
+ }
864
+ }
865
+ }
866
+
867
+ return results;
868
+ }
869
+
870
+
761
871
  /** Sets the animation loop.
762
872
  * Can not be done while creating the context or when disposed
763
873
  **/
@@ -770,15 +880,15 @@
770
880
  console.warn("Can not start render loop while creating context");
771
881
  return false;
772
882
  }
773
- const renderMethod = this.render.bind(this);
774
- this.renderer.setAnimationLoop(renderMethod);
883
+ this.renderer.setAnimationLoop((timestamp, frame: XRFrame | null) => this.update(timestamp, frame));
775
884
  return true;
776
885
  }
777
886
 
778
- private render(_, frame: XRFrame | null) {
887
+ public update(timestamp: DOMHighResTimeStamp, frame?: XRFrame | null) {
888
+ if (frame === undefined) frame = null;
779
889
  if (isDevEnvironment() || debug || looputils.hasNewScripts()) {
780
890
  try {
781
- this.internalRender(_, frame);
891
+ this.internalRender(timestamp, frame);
782
892
  }
783
893
  catch (err) {
784
894
  if ((isDevEnvironment() || debug) && err instanceof Error)
@@ -790,19 +900,22 @@
790
900
  }
791
901
  }
792
902
  else {
793
- this.internalRender(_, frame);
903
+ this.internalRender(timestamp, frame);
794
904
  }
795
905
  }
796
906
 
797
907
  private _lastTimestamp = 0;
798
- private internalRender(timestamp : DOMHighResTimeStamp, frame: XRFrame | null) {
908
+ private _accumulatedTime = 0;
909
+ private _dispatchReadyAfterFrame = false;
910
+
911
+ private internalRender(timestamp: DOMHighResTimeStamp, frame: XRFrame | null) {
799
912
  this._xrFrame = frame;
800
913
 
801
914
  this._currentFrameEvent = FrameEvent.Undefined;
802
-
915
+
803
916
  if (this.isInXR === false && this.targetFrameRate !== undefined) {
804
- if(this._lastTimestamp === 0) this._lastTimestamp = timestamp;
805
- this._accumulatedTime += (timestamp - this._lastTimestamp) /1000;
917
+ if (this._lastTimestamp === 0) this._lastTimestamp = timestamp;
918
+ this._accumulatedTime += (timestamp - this._lastTimestamp) / 1000;
806
919
  this._lastTimestamp = timestamp;
807
920
  let targetFrameRate = this.targetFrameRate;
808
921
  if (typeof targetFrameRate === "object") targetFrameRate = targetFrameRate.value!;
@@ -896,7 +1009,7 @@
896
1009
 
897
1010
  if (this.onHandlePaused()) return;
898
1011
 
899
- if (this.isVisibleToUser) {
1012
+ if (this.isVisibleToUser || this.runInBackground) {
900
1013
 
901
1014
  this._currentFrameEvent = FrameEvent.OnBeforeRender;
902
1015
 
@@ -953,7 +1066,11 @@
953
1066
 
954
1067
  this.connection.sendBufferedMessagesNow();
955
1068
 
956
- this._stats?.end();
1069
+ if (this._stats) {
1070
+ this._stats.end();
1071
+ if (this.time.frameCount % 150 === 0)
1072
+ console.log({ ...this.renderer.info.memory }, { ...this.renderer.info.render });
1073
+ }
957
1074
 
958
1075
  if (this._dispatchReadyAfterFrame) {
959
1076
  this._dispatchReadyAfterFrame = false;
src/engine/engine_element.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Context, LoadingProgressArgs } from "./engine_setup.js";
1
+ import { Context, ContextCreateArgs, LoadingProgressArgs } from "./engine_setup.js";
2
2
  import { AROverlayHandler, arContainerClassName } from "./engine_element_overlay.js";
3
3
  import { GameObject } from "../engine-components/Component.js";
4
4
  import { calculateProgress01, EngineLoadingView, ILoadingViewHandler } from "./engine_element_loading.js";
@@ -118,7 +118,10 @@
118
118
  :host canvas {
119
119
  position: absolute;
120
120
  user-select: none;
121
- touch-action: none;
121
+ /** allow touch panning but no pinch zoom **/
122
+ /** but this doesnt work yet:
123
+ * touch-action: pan-x, pan-y;
124
+ **/
122
125
  }
123
126
  :host .content {
124
127
  position: absolute;
@@ -148,7 +151,7 @@
148
151
  <slot class="overlay-content"></slot>
149
152
  </div>
150
153
  `;
151
-
154
+
152
155
  if (this.shadowRoot)
153
156
  this.shadowRoot.appendChild(template.content.cloneNode(true));
154
157
 
@@ -306,82 +309,45 @@
306
309
  if (debug) console.warn("--------------", loadId, "Needle Engine: Begin loading", alias ?? "", filesToLoad);
307
310
  this.onBeforeBeginLoading();
308
311
 
309
- type LoadFunctionResult = (ctx: Context) => Promise<LoadedGLTF[] | null>;
310
- let loadFunction: null | LoadFunctionResult = null;
311
312
  const loadedFiles: Array<LoadedGLTF> = [];
312
-
313
- if (filesToLoad?.length > 0) {
314
- loadFunction = async (ctx: Context) => {
315
- let hash = 0;
316
- if (ctx.hash) hash = Number.parseInt(ctx.hash) ?? 0;
317
- const loader = getLoader();
318
- for (let i = 0; i < filesToLoad.length; i++) {
319
- if (this._loadId !== loadId) return loadedFiles;
320
- const url = filesToLoad[i];
321
- // Check if the url contains glb or gltf, this is mainly to handle legacy projects/codegen files were the src attribute was a function name
322
- if (!url.includes(".glb") && !url.includes(".gltf")) {
323
- const warning = `Needle Engine: found suspicious src "${url}"`;
324
- console.warn(warning);
325
- if (isLocalNetwork()) showBalloonWarning(warning);
326
- }
327
- const fileName = getNameFromUrl(url);
328
- const progress: LoadingProgressArgs = {
329
- name: fileName,
330
- progress: null!,
331
- index: i,
332
- count: filesToLoad.length
333
- };
334
- const progressEventArgs = {
335
- detail: {
336
- context: ctx,
337
- name: fileName,
338
- progress: progress.progress,
339
- index: i,
340
- count: filesToLoad.length,
341
- totalProgress01: this._loadingProgress01
342
- }
343
- }
344
- const res = await loader.loadSync(ctx, url, url, hash, prog => {
345
- if(this._loadId !== loadId) return;
346
- // Calc progress
347
- progress.progress = prog;
348
- this._loadingProgress01 = calculateProgress01(progress);
349
- // Update overlay if we use it
350
- if (useDefaultLoading) this._loadingView?.onLoadingUpdate(progress);
351
- // Dispatch event
352
- progressEventArgs.detail.progress = prog;
353
- progressEventArgs.detail.totalProgress01 = this._loadingProgress01;
354
- this.dispatchEvent(new CustomEvent("progress", progressEventArgs));
313
+ const progressEventDetail = {
314
+ context: this._context,
315
+ name: "",
316
+ progress: {} as ProgressEvent,
317
+ index: 0,
318
+ count: filesToLoad.length,
319
+ totalProgress01: this._loadingProgress01
320
+ };
321
+ const progressEvent = new CustomEvent("progress", { detail: progressEventDetail });
322
+ const args: ContextCreateArgs = {
323
+ files: filesToLoad,
324
+ onLoadingProgress: evt => {
325
+ evt.name = getNameFromUrl(evt.name);
326
+ console.log({...evt})
327
+ if (useDefaultLoading) this._loadingView?.onLoadingUpdate(evt);
328
+ progressEventDetail.name = evt.name;
329
+ progressEventDetail.progress = evt.progress;
330
+ this._loadingProgress01 = calculateProgress01(evt);
331
+ progressEventDetail.totalProgress01 = this._loadingProgress01;
332
+ this.dispatchEvent(progressEvent);
333
+ },
334
+ onLoadingFinished: (_index, file, glTF) => {
335
+ if (glTF) {
336
+ loadedFiles.push({
337
+ src: file,
338
+ file: glTF
355
339
  });
356
- if (res) {
357
- loadedFiles.push({
358
- src: url,
359
- file: res as GLTF
360
- });
361
- const obj = res.scene;
362
- if (obj) {
363
- // if the loading ID changed but the scene has already been loaded we want to dispose it
364
- if (this._loadId !== loadId) {
365
- destroy(obj, true, true)
366
- return null;
367
- }
368
- GameObject.add(obj, ctx.scene, ctx);
369
- }
370
- }
371
340
  }
372
- if (this._loadId !== loadId) return null;
373
- return loadedFiles;
374
- };
341
+ }
375
342
  }
376
343
 
377
344
  const currentHash = this.getAttribute("hash");
378
345
  if (currentHash !== null && currentHash !== undefined)
379
346
  this._context.hash = currentHash;
380
347
  this._context.alias = alias;
381
- await this._context.onCreate(loadFunction as any);
348
+ await this._context.create(args);
382
349
  if (this._loadId !== loadId) return;
383
- if (debug)
384
- console.warn("--------------", loadId, "Needle Engine: finished loading", alias ?? "", filesToLoad);
350
+ if (debug) console.warn("--------------", loadId, "Needle Engine: finished loading", alias ?? "", filesToLoad);
385
351
  this._loadingProgress01 = 1;
386
352
  if (useDefaultLoading) {
387
353
  this._loadingView?.onLoadingUpdate(1, "creating scene");
@@ -505,7 +471,7 @@
505
471
  this.classList.add(arSessionActiveClassName);
506
472
  this.classList.remove(desktopSessionActiveClassName);
507
473
  const arContainer = this.getAROverlayContainer();
508
- if(debug) console.warn("onSetupAR:", arContainer)
474
+ if (debug) console.warn("onSetupAR:", arContainer)
509
475
  if (arContainer) {
510
476
  arContainer.classList.add(arSessionActiveClassName);
511
477
  arContainer.classList.remove(desktopSessionActiveClassName);
src/engine/engine_physics_rapier.ts CHANGED
@@ -566,7 +566,7 @@
566
566
  positions = this._meshCache.get(key)!;
567
567
  }
568
568
  else {
569
- console.warn("Your model is using scaled mesh colliders which is not optimal for performance", mesh.name, Object.assign({}, scale), mesh);
569
+ console.warn("Your model is using scaled mesh colliders which is not optimal for performance", mesh.name, Object.assign({}, scale));
570
570
  // showBalloonWarning("Your model is using scaled mesh colliders which is not optimal for performance: " + mesh.name + ", consider using unscaled objects");
571
571
  const scaledPositions = new Float32Array(positions.length);
572
572
  for (let i = 0; i < positions.length; i += 3) {
src/engine/engine_serialization_builtin_serializer.ts CHANGED
@@ -347,8 +347,16 @@
347
347
  onDeserialize(data: any, context: SerializationContext) {
348
348
  if (data instanceof Texture && context.type === RenderTexture) {
349
349
  const tex = data as Texture;
350
- const rt = new RenderTexture(tex.image.width, tex.image.height);
350
+ const rt = new RenderTexture(tex.image.width, tex.image.height, {
351
+ colorSpace: THREE.LinearSRGBColorSpace,
352
+ });
351
353
  rt.texture = tex;
354
+
355
+ tex.isRenderTargetTexture = true;
356
+ tex.flipY = true;
357
+ tex.offset.y = 1;
358
+ tex.repeat.y = -1;
359
+ tex.needsUpdate = true;
352
360
 
353
361
  if (tex instanceof CompressedTexture) {
354
362
  //@ts-ignore
@@ -357,10 +365,6 @@
357
365
  tex.format = THREE.RGBAFormat;
358
366
  }
359
367
 
360
- rt.texture.flipY = true;
361
- rt.texture.offset.y = 1;
362
- rt.texture.repeat.y = -1;
363
-
364
368
  return rt;
365
369
  }
366
370
  return undefined;
src/engine/engine_types.ts CHANGED
@@ -3,15 +3,16 @@
3
3
  import { RGBAColor } from "../engine-components/js-extensions/RGBAColor.js";
4
4
  import { CollisionDetectionMode, PhysicsMaterial, RigidbodyConstraints } from "./engine_physics.types.js";
5
5
  import { CircularBuffer } from "./engine_utils.js";
6
+ import { GLTF as GLTF3 } from "three/examples/jsm/loaders/GLTFLoader.js";
6
7
 
7
- export type GLTF = {
8
- asset: { generator: string, version: string }
9
- animations: AnimationClip[];
10
- cameras: Camera[];
11
- scene: Object3D;
12
- scenes: Object3D[];
13
- userData: object;
14
- parser: any;
8
+ export type GLTF = GLTF3 & {
9
+ // asset: { generator: string, version: string }
10
+ // animations: AnimationClip[];
11
+ // cameras: Camera[];
12
+ // scene: Object3D;
13
+ // scenes: Object3D[];
14
+ // userData: object;
15
+ // parser: any;
15
16
  }
16
17
 
17
18
  export type LoadedGLTF = {
src/engine-components/ui/EventSystem.ts CHANGED
@@ -263,7 +263,6 @@
263
263
  return
264
264
  }
265
265
 
266
- if(debug) console.log("EventSystem: raycast");
267
266
  const hits = this.performRaycast(null);
268
267
  if (!hits) return;
269
268
  this.lastPointerEvent = args;
src/engine-components/ui/Graphic.ts CHANGED
@@ -195,10 +195,18 @@
195
195
  if (Graphic.textureCache.has(tex)) {
196
196
  tex = Graphic.textureCache.get(tex)!;
197
197
  } else {
198
- const clone = tex.clone();
199
- clone.colorSpace = LinearSRGBColorSpace;
200
- Graphic.textureCache.set(tex, clone);
201
- tex = clone;
198
+ if (tex.isRenderTargetTexture) {
199
+ // we can not clone the texture if it's a render target
200
+ // otherwise it won't be updated anymore in the UI
201
+ // TODO: below maskable graphic is flipped but settings a rendertexture results in the texture being upside down.
202
+ // we should remove the flip below (scale.y *= -1) but this needs to be tested with all UI components
203
+ }
204
+ else {
205
+ const clone = tex.clone();
206
+ clone.colorSpace = LinearSRGBColorSpace;
207
+ Graphic.textureCache.set(tex, clone);
208
+ tex = clone;
209
+ }
202
210
  }
203
211
  }
204
212
  this.setOptions({ backgroundImage: tex, borderRadius: 0, backgroundOpacity: this.color.alpha, backgroundSize: "stretch" });
src/engine/extensions/NEEDLE_techniques_webgl.ts CHANGED
@@ -588,16 +588,31 @@
588
588
  // this way all properties of custom shaders are also accessible via material._MyProperty
589
589
  function createUniformProperties(material: CustomShader) {
590
590
  if (material.uniforms) {
591
+ if (debug)
592
+ console.log("Uniforms:", material.uniforms);
591
593
  for (const key in material.uniforms) {
592
- if (!Object.getOwnPropertyDescriptor(material, key)) {
593
- Object.defineProperty(material, key, {
594
- get: () => material.uniforms[key].value,
595
- set: (value) => {
596
- material.uniforms[key].value = value
597
- material.needsUpdate = true;
598
- }
599
- });
594
+ defineProperty(key, key);
595
+
596
+ // see NE-3396
597
+ switch (key) {
598
+ case "_Color":
599
+ defineProperty("color", key);
600
+ break;
601
+ // case "_Metallic":
602
+ // defineProperty("metalness", key);
603
+ // break;
600
604
  }
601
605
  }
602
606
  }
607
+ function defineProperty(key: string, uniformsKey: string) {
608
+ if (!Object.getOwnPropertyDescriptor(material, key)) {
609
+ Object.defineProperty(material, key, {
610
+ get: () => material.uniforms[uniformsKey].value,
611
+ set: (value) => {
612
+ material.uniforms[uniformsKey].value = value
613
+ material.needsUpdate = true;
614
+ }
615
+ });
616
+ }
617
+ }
603
618
  }
src/needle-engine.ts CHANGED
@@ -9,8 +9,13 @@
9
9
  export * from "./engine-schemes/api.js";
10
10
 
11
11
  // make accessible for external javascript
12
- import { Context } from "./engine/engine_setup.js";
13
- const Needle = { Context: Context };
12
+ import { Context, loadSync } from "./engine/api.js";
13
+ const Needle = {
14
+ Context: Context,
15
+ glTF: {
16
+ loadFromURL: loadSync,
17
+ }
18
+ };
14
19
  if (globalThis["Needle"] !== undefined) {
15
20
  console.warn("Needle Engine is already imported");
16
21
  }