angular-three 4.0.0-next.104 → 4.0.0-next.105

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, ViewContainerRef, TemplateRef, Injector, effect, DestroyRef, Directive, input, linkedSignal, computed, InjectionToken, untracked, isSignal, signal, ElementRef, booleanAttribute, resource, Pipe, numberAttribute, contentChild, SkipSelf, ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA, Component, Injectable, output, model, Renderer2 } from '@angular/core';
2
+ import { inject, ViewContainerRef, TemplateRef, Injector, effect, DestroyRef, Directive, input, linkedSignal, computed, InjectionToken, untracked, isSignal, signal, Injectable, ElementRef, booleanAttribute, resource, Pipe, numberAttribute, contentChild, SkipSelf, ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA, Component, output, model, Renderer2 } from '@angular/core';
3
3
  import { DOCUMENT } from '@angular/common';
4
4
  import { Subject, filter } from 'rxjs';
5
5
  import * as THREE from 'three';
@@ -295,59 +295,6 @@ function injectLoop() {
295
295
  return inject(NGT_LOOP);
296
296
  }
297
297
 
298
- const idCache = {};
299
- function makeId(event) {
300
- if (event) {
301
- return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
302
- }
303
- const newId = THREE.MathUtils.generateUUID();
304
- // ensure not already used
305
- if (!idCache[newId]) {
306
- idCache[newId] = true;
307
- return newId;
308
- }
309
- return makeId();
310
- }
311
- function makeDpr(dpr, window) {
312
- // Err on the side of progress by assuming 2x dpr if we can't detect it
313
- // This will happen in workers where window is defined but dpr isn't.
314
- const target = typeof window !== 'undefined' ? (window.devicePixelRatio ?? 2) : 1;
315
- return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
316
- }
317
- function makeRendererInstance(glOptions, canvas) {
318
- const defaultOptions = {
319
- powerPreference: 'high-performance',
320
- canvas,
321
- antialias: true,
322
- alpha: true,
323
- };
324
- const customRenderer = (typeof glOptions === 'function' ? glOptions(defaultOptions) : glOptions);
325
- if (is.renderer(customRenderer))
326
- return customRenderer;
327
- return new THREE.WebGLRenderer({ ...defaultOptions, ...glOptions });
328
- }
329
- function makeCameraInstance(isOrthographic, size) {
330
- if (isOrthographic)
331
- return new THREE.OrthographicCamera(0, 0, 0, 0, 0.1, 1000);
332
- return new THREE.PerspectiveCamera(75, size.width / size.height, 0.1, 1000);
333
- }
334
- function makeObjectGraph(object) {
335
- const data = { nodes: {}, materials: {}, meshes: {} };
336
- if (object) {
337
- object.traverse((child) => {
338
- if (child.name)
339
- data.nodes[child.name] = child;
340
- if ('material' in child && !data.materials[child.material.name]) {
341
- data.materials[child.material.name] = child
342
- .material;
343
- }
344
- if (is.three(child, 'isMesh') && !data.meshes[child.name])
345
- data.meshes[child.name] = child;
346
- });
347
- }
348
- return data;
349
- }
350
-
351
298
  /** ported from ngrx/signals */
352
299
  const STATE_SOURCE = Symbol('STATE_SOURCE');
353
300
  function patchState(stateSource, ...updaters) {
@@ -444,349 +391,137 @@ function updateCamera(camera, size) {
444
391
  }
445
392
  }
446
393
 
447
- function storeFactory() {
448
- const { invalidate, advance } = injectLoop();
449
- const document = inject(DOCUMENT);
450
- const window = document.defaultView || undefined;
451
- // NOTE: using Subject because we do not care about late-subscribers
452
- const pointerMissed$ = new Subject();
453
- const position = new THREE.Vector3();
454
- const defaultTarget = new THREE.Vector3();
455
- const tempTarget = new THREE.Vector3();
456
- let performanceTimeout = undefined;
457
- const pointer = new THREE.Vector2();
458
- const store = signalState({
459
- id: makeId(),
460
- pointerMissed$: pointerMissed$.asObservable(),
461
- events: { priority: 1, enabled: true, connected: false },
462
- // Mock objects that have to be configured
463
- gl: null,
464
- camera: null,
465
- raycaster: null,
466
- scene: null,
467
- xr: null,
468
- invalidate: (frames = 1) => invalidate(store, frames),
469
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, store),
470
- legacy: false,
471
- linear: false,
472
- flat: false,
473
- controls: null,
474
- clock: new THREE.Clock(),
475
- pointer,
476
- frameloop: 'always',
477
- performance: {
478
- current: 1,
479
- min: 0.5,
480
- max: 1,
481
- debounce: 200,
482
- regress: () => {
483
- const state = store.snapshot;
484
- // Clear timeout
485
- if (performanceTimeout)
486
- clearTimeout(performanceTimeout);
487
- // Set lower bound performance
488
- if (state.performance.current !== state.performance.min)
489
- store.update((state) => ({
490
- performance: { ...state.performance, current: state.performance.min },
491
- }));
492
- // Go back to upper bound performance after a while unless something regresses meanwhile
493
- performanceTimeout = setTimeout(() => store.update((state) => ({
494
- performance: { ...state.performance, current: store.snapshot.performance.max },
495
- })), state.performance.debounce);
394
+ /**
395
+ * @deprecated: use `getInstanceState` instead. Will be removed in 5.0.0
396
+ */
397
+ function getLocalState(obj) {
398
+ return getInstanceState(obj);
399
+ }
400
+ function getInstanceState(obj) {
401
+ if (!obj)
402
+ return undefined;
403
+ return obj.__ngt__ || undefined;
404
+ }
405
+ function invalidateInstance(instance) {
406
+ let store = getInstanceState(instance)?.store;
407
+ if (store) {
408
+ while (store.snapshot.previousRoot) {
409
+ store = store.snapshot.previousRoot;
410
+ }
411
+ if (store.snapshot.internal.frames === 0) {
412
+ store.snapshot.invalidate();
413
+ }
414
+ }
415
+ checkUpdate(instance);
416
+ }
417
+ function prepare(object, type, instanceState) {
418
+ const instance = object;
419
+ if (instanceState?.type === 'ngt-primitive' || !instance.__ngt__) {
420
+ const { hierarchyStore = signalState({
421
+ parent: null,
422
+ objects: [],
423
+ nonObjects: [],
424
+ geometryStamp: Date.now(),
425
+ }), store = null, ...rest } = instanceState || {};
426
+ const nonObjects = hierarchyStore.nonObjects;
427
+ const geometryStamp = hierarchyStore.geometryStamp;
428
+ const nonObjectsChanged = computed(() => {
429
+ const [_nonObjects] = [nonObjects(), geometryStamp()];
430
+ return _nonObjects;
431
+ });
432
+ instance.__ngt_id__ = crypto.randomUUID();
433
+ instance.__ngt__ = {
434
+ previousAttach: null,
435
+ type,
436
+ eventCount: 0,
437
+ handlers: {},
438
+ hierarchyStore,
439
+ object: instance,
440
+ parent: hierarchyStore.parent,
441
+ objects: hierarchyStore.objects,
442
+ nonObjects: nonObjectsChanged,
443
+ add(object, type) {
444
+ const current = instance.__ngt__.hierarchyStore.snapshot[type];
445
+ const foundIndex = current.findIndex((node) => object === node || (!!object['uuid'] && !!node['uuid'] && object['uuid'] === node['uuid']));
446
+ if (foundIndex > -1) {
447
+ current.splice(foundIndex, 1, object);
448
+ instance.__ngt__.hierarchyStore.update({ [type]: current });
449
+ }
450
+ else {
451
+ instance.__ngt__.hierarchyStore.update((prev) => ({ [type]: [...prev[type], object] }));
452
+ }
453
+ notifyAncestors(instance.__ngt__.hierarchyStore.snapshot.parent, type);
454
+ },
455
+ remove(object, type) {
456
+ instance.__ngt__.hierarchyStore.update((prev) => ({
457
+ [type]: prev[type].filter((node) => node !== object),
458
+ }));
459
+ notifyAncestors(instance.__ngt__.hierarchyStore.snapshot.parent, type);
460
+ },
461
+ setParent(parent) {
462
+ instance.__ngt__.hierarchyStore.update({ parent });
463
+ },
464
+ updateGeometryStamp() {
465
+ instance.__ngt__.hierarchyStore.update({ geometryStamp: Date.now() });
496
466
  },
467
+ store,
468
+ ...rest,
469
+ };
470
+ }
471
+ Object.defineProperty(instance.__ngt__, 'setPointerEvent', {
472
+ value: (eventName, callback) => {
473
+ const iS = getInstanceState(instance);
474
+ if (!iS.handlers)
475
+ iS.handlers = {};
476
+ // try to get the previous handler. compound might have one, the THREE object might also have one with the same name
477
+ const previousHandler = iS.handlers[eventName];
478
+ // readjust the callback
479
+ const updatedCallback = (event) => {
480
+ if (previousHandler)
481
+ previousHandler(event);
482
+ callback(event);
483
+ };
484
+ Object.assign(iS.handlers, { [eventName]: updatedCallback });
485
+ // increment the count everytime
486
+ iS.eventCount += 1;
487
+ // clean up the event listener by removing the target from the interaction array
488
+ return () => {
489
+ const iS = getInstanceState(instance);
490
+ if (iS) {
491
+ iS.handlers && delete iS.handlers[eventName];
492
+ iS.eventCount -= 1;
493
+ }
494
+ };
497
495
  },
498
- size: { width: 0, height: 0, top: 0, left: 0 },
499
- viewport: {
500
- initialDpr: window?.devicePixelRatio || 1,
501
- dpr: window?.devicePixelRatio || 1,
502
- width: 0,
503
- height: 0,
504
- top: 0,
505
- left: 0,
506
- aspect: 0,
507
- distance: 0,
508
- factor: 0,
509
- getCurrentViewport(camera = store.snapshot.camera, target = defaultTarget, size = store.snapshot.size) {
510
- const { width, height, top, left } = size;
511
- const aspect = width / height;
512
- if (target.isVector3)
513
- tempTarget.copy(target);
514
- else
515
- tempTarget.set(...target);
516
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
517
- if (is.three(camera, 'isOrthographicCamera')) {
518
- return {
519
- width: width / camera.zoom,
520
- height: height / camera.zoom,
521
- top,
522
- left,
523
- factor: 1,
524
- distance,
525
- aspect,
526
- };
496
+ configurable: true,
497
+ });
498
+ Object.defineProperties(instance.__ngt__, {
499
+ addInteraction: {
500
+ value: (store) => {
501
+ if (!store)
502
+ return;
503
+ const iS = getInstanceState(instance);
504
+ if (iS.eventCount < 1 || !('raycast' in instance) || !instance['raycast'])
505
+ return;
506
+ let root = store;
507
+ while (root.snapshot.previousRoot) {
508
+ root = root.snapshot.previousRoot;
509
+ }
510
+ if (root.snapshot.internal) {
511
+ const interactions = root.snapshot.internal.interaction;
512
+ const index = interactions.findIndex((obj) => obj.uuid === instance.uuid);
513
+ // if already exists, do not add to interactions
514
+ if (index < 0) {
515
+ root.snapshot.internal.interaction.push(instance);
516
+ }
527
517
  }
528
- const fov = (camera.fov * Math.PI) / 180; // convert vertical fov to radians
529
- const h = 2 * Math.tan(fov / 2) * distance; // visible height
530
- const w = h * (width / height);
531
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
532
518
  },
519
+ configurable: true,
533
520
  },
534
- setEvents: (events) => store.update((state) => ({ events: { ...state.events, ...events } })),
535
- setSize: (width, height, top, left) => {
536
- const camera = store.snapshot.camera;
537
- const size = { width, height, top: top ?? 0, left: left ?? 0 };
538
- store.update((state) => ({
539
- size,
540
- viewport: {
541
- ...state.viewport,
542
- ...state.viewport.getCurrentViewport(camera, defaultTarget, size),
543
- },
544
- }));
545
- },
546
- setDpr: (dpr) => {
547
- const resolved = makeDpr(dpr, window);
548
- store.update((state) => ({
549
- viewport: { ...state.viewport, dpr: resolved, initialDpr: state.viewport.initialDpr || resolved },
550
- }));
551
- },
552
- setFrameloop: (frameloop) => {
553
- const clock = store.snapshot.clock;
554
- // if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
555
- clock.stop();
556
- clock.elapsedTime = 0;
557
- if (frameloop !== 'never') {
558
- clock.start();
559
- clock.elapsedTime = 0;
560
- }
561
- store.update(() => ({ frameloop }));
562
- },
563
- previousRoot: null,
564
- internal: {
565
- active: false,
566
- priority: 0,
567
- frames: 0,
568
- lastEvent: new ElementRef(null),
569
- interaction: [],
570
- hovered: new Map(),
571
- capturedMap: new Map(),
572
- initialClick: [0, 0],
573
- initialHits: [],
574
- subscribers: [],
575
- subscribe: (callback, priority = 0, _store = store) => {
576
- const internal = _store.snapshot.internal;
577
- // If this subscription was given a priority, it takes rendering into its own hands
578
- // For that reason we switch off automatic rendering and increase the manual flag
579
- // As long as this flag is positive there can be no internal rendering at all
580
- // because there could be multiple render subscriptions
581
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
582
- internal.subscribers.push({ callback, priority, store: _store });
583
- // Register subscriber and sort layers from lowest to highest, meaning,
584
- // highest priority renders last (on top of the other frames)
585
- internal.subscribers = internal.subscribers.sort((a, b) => (a.priority || 0) - (b.priority || 0));
586
- return () => {
587
- const internal = _store.snapshot.internal;
588
- if (internal?.subscribers) {
589
- // Decrease manual flag if this subscription had a priority
590
- internal.priority = internal.priority - (priority > 0 ? 1 : 0);
591
- // Remove subscriber from list
592
- internal.subscribers = internal.subscribers.filter((s) => s.callback !== callback);
593
- }
594
- };
595
- },
596
- },
597
- });
598
- Object.defineProperty(store, '__pointerMissed$', { get: () => pointerMissed$ });
599
- let { size: oldSize, viewport: { dpr: oldDpr }, camera: oldCamera, } = store.snapshot;
600
- effect(() => {
601
- const [newCamera, newSize, newDpr, gl] = [store.camera(), store.size(), store.viewport.dpr(), store.gl()];
602
- // Resize camera and renderer on changes to size and pixel-ratio
603
- if (newSize !== oldSize || newDpr !== oldDpr) {
604
- oldSize = newSize;
605
- oldDpr = newDpr;
606
- // Update camera & renderer
607
- updateCamera(newCamera, newSize);
608
- gl.setPixelRatio(newDpr);
609
- const updateStyle = typeof HTMLCanvasElement !== 'undefined' && gl.domElement instanceof HTMLCanvasElement;
610
- gl.setSize(newSize.width, newSize.height, updateStyle);
611
- }
612
- // Update viewport once the camera changes
613
- if (newCamera !== oldCamera) {
614
- oldCamera = newCamera;
615
- updateCamera(newCamera, newSize);
616
- // Update viewport
617
- store.update((state) => ({
618
- viewport: { ...state.viewport, ...state.viewport.getCurrentViewport(newCamera) },
619
- }));
620
- }
621
- });
622
- return store;
623
- }
624
- const NGT_STORE = new InjectionToken('NgtStore Token');
625
- function injectStore(options) {
626
- return inject(NGT_STORE, options);
627
- }
628
-
629
- function resolveRef(ref) {
630
- if (is.ref(ref)) {
631
- return ref.nativeElement;
632
- }
633
- return ref;
634
- }
635
-
636
- class NgtParent extends NgtCommonDirective {
637
- constructor() {
638
- super();
639
- this.parent = input.required();
640
- this.store = injectStore();
641
- this._parent = computed(() => {
642
- const parent = this.parent();
643
- const rawParent = typeof parent === 'function' ? parent() : parent;
644
- if (!rawParent)
645
- return null;
646
- const scene = this.store.scene();
647
- if (typeof rawParent === 'string') {
648
- return scene.getObjectByName(rawParent);
649
- }
650
- return resolveRef(rawParent);
651
- });
652
- this.linkedValue = linkedSignal(this._parent);
653
- this.shouldSkipRender = computed(() => !this._parent());
654
- const commentNode = this.commentNode;
655
- commentNode.data = NGT_PARENT_FLAG;
656
- commentNode[NGT_PARENT_FLAG] = true;
657
- if (commentNode[NGT_INTERNAL_ADD_COMMENT_FLAG]) {
658
- commentNode[NGT_INTERNAL_ADD_COMMENT_FLAG]('parent', this.injector);
659
- delete commentNode[NGT_INTERNAL_ADD_COMMENT_FLAG];
660
- }
661
- }
662
- validate() {
663
- return !this.injected && !!this.injectedValue;
664
- }
665
- beforeCreateView() {
666
- const commentNode = this.commentNode;
667
- if (commentNode[NGT_INTERNAL_SET_PARENT_COMMENT_FLAG]) {
668
- commentNode[NGT_INTERNAL_SET_PARENT_COMMENT_FLAG](this.injectedValue);
669
- }
670
- }
671
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtParent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
672
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgtParent, isStandalone: true, selector: "ng-template[parent]", inputs: { parent: { classPropertyName: "parent", publicName: "parent", isSignal: true, isRequired: true, transformFunction: null } }, usesInheritance: true, ngImport: i0 }); }
673
- }
674
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtParent, decorators: [{
675
- type: Directive,
676
- args: [{ selector: 'ng-template[parent]' }]
677
- }], ctorParameters: () => [] });
678
-
679
- /**
680
- * @deprecated: use `getInstanceState` instead. Will be removed in 5.0.0
681
- */
682
- function getLocalState(obj) {
683
- return getInstanceState(obj);
684
- }
685
- function getInstanceState(obj) {
686
- if (!obj)
687
- return undefined;
688
- return obj.__ngt__ || undefined;
689
- }
690
- function invalidateInstance(instance) {
691
- let store = getInstanceState(instance)?.store;
692
- if (store) {
693
- while (store.snapshot.previousRoot) {
694
- store = store.snapshot.previousRoot;
695
- }
696
- if (store.snapshot.internal.frames === 0) {
697
- store.snapshot.invalidate();
698
- }
699
- }
700
- checkUpdate(instance);
701
- }
702
- function prepare(object, type, instanceState) {
703
- const instance = object;
704
- if (instanceState?.type === 'ngt-primitive' || !instance.__ngt__) {
705
- const { hierarchyStore = signalState({
706
- parent: null,
707
- objects: [],
708
- nonObjects: [],
709
- geometryStamp: Date.now(),
710
- }), store = null, ...rest } = instanceState || {};
711
- const nonObjects = hierarchyStore.nonObjects;
712
- const geometryStamp = hierarchyStore.geometryStamp;
713
- const nonObjectsChanged = computed(() => {
714
- const [_nonObjects] = [nonObjects(), geometryStamp()];
715
- return _nonObjects;
716
- });
717
- instance.__ngt__ = {
718
- previousAttach: null,
719
- type,
720
- eventCount: 0,
721
- handlers: {},
722
- hierarchyStore,
723
- object: instance,
724
- parent: hierarchyStore.parent,
725
- objects: hierarchyStore.objects,
726
- nonObjects: nonObjectsChanged,
727
- add(object, type) {
728
- const current = instance.__ngt__.hierarchyStore.snapshot[type];
729
- const foundIndex = current.findIndex((node) => object === node || (!!object['uuid'] && !!node['uuid'] && object['uuid'] === node['uuid']));
730
- if (foundIndex > -1) {
731
- current.splice(foundIndex, 1, object);
732
- instance.__ngt__.hierarchyStore.update({ [type]: current });
733
- }
734
- else {
735
- instance.__ngt__.hierarchyStore.update((prev) => ({ [type]: [...prev[type], object] }));
736
- }
737
- notifyAncestors(instance.__ngt__.hierarchyStore.snapshot.parent, type);
738
- },
739
- remove(object, type) {
740
- instance.__ngt__.hierarchyStore.update((prev) => ({
741
- [type]: prev[type].filter((node) => node !== object),
742
- }));
743
- notifyAncestors(instance.__ngt__.hierarchyStore.snapshot.parent, type);
744
- },
745
- setParent(parent) {
746
- instance.__ngt__.hierarchyStore.update({ parent });
747
- },
748
- updateGeometryStamp() {
749
- instance.__ngt__.hierarchyStore.update({ geometryStamp: Date.now() });
750
- },
751
- store,
752
- ...rest,
753
- };
754
- }
755
- Object.defineProperty(instance.__ngt__, 'setPointerEvent', {
756
- value: (eventName, callback) => {
757
- const iS = getInstanceState(instance);
758
- if (!iS.handlers)
759
- iS.handlers = {};
760
- // try to get the previous handler. compound might have one, the THREE object might also have one with the same name
761
- const previousHandler = iS.handlers[eventName];
762
- // readjust the callback
763
- const updatedCallback = (event) => {
764
- if (previousHandler)
765
- previousHandler(event);
766
- callback(event);
767
- };
768
- Object.assign(iS.handlers, { [eventName]: updatedCallback });
769
- // increment the count everytime
770
- iS.eventCount += 1;
771
- // clean up the event listener by removing the target from the interaction array
772
- return () => {
773
- const iS = getInstanceState(instance);
774
- if (iS) {
775
- iS.handlers && delete iS.handlers[eventName];
776
- iS.eventCount -= 1;
777
- }
778
- };
779
- },
780
- configurable: true,
781
- });
782
- Object.defineProperties(instance.__ngt__, {
783
- addInteraction: {
521
+ removeInteraction: {
784
522
  value: (store) => {
785
523
  if (!store)
786
524
  return;
787
- const iS = getInstanceState(instance);
788
- if (iS.eventCount < 1 || !('raycast' in instance) || !instance['raycast'])
789
- return;
790
525
  let root = store;
791
526
  while (root.snapshot.previousRoot) {
792
527
  root = root.snapshot.previousRoot;
@@ -794,27 +529,8 @@ function prepare(object, type, instanceState) {
794
529
  if (root.snapshot.internal) {
795
530
  const interactions = root.snapshot.internal.interaction;
796
531
  const index = interactions.findIndex((obj) => obj.uuid === instance.uuid);
797
- // if already exists, do not add to interactions
798
- if (index < 0) {
799
- root.snapshot.internal.interaction.push(instance);
800
- }
801
- }
802
- },
803
- configurable: true,
804
- },
805
- removeInteraction: {
806
- value: (store) => {
807
- if (!store)
808
- return;
809
- let root = store;
810
- while (root.snapshot.previousRoot) {
811
- root = root.snapshot.previousRoot;
812
- }
813
- if (root.snapshot.internal) {
814
- const interactions = root.snapshot.internal.interaction;
815
- const index = interactions.findIndex((obj) => obj.uuid === instance.uuid);
816
- if (index >= 0)
817
- interactions.splice(index, 1);
532
+ if (index >= 0)
533
+ interactions.splice(index, 1);
818
534
  }
819
535
  },
820
536
  configurable: true,
@@ -822,175 +538,398 @@ function prepare(object, type, instanceState) {
822
538
  });
823
539
  return instance;
824
540
  }
541
+ const notificationCache = new Map();
542
+ /**
543
+ * Notify ancestors about changes to a THREE.js objects' children
544
+ *
545
+ * For example: `NgtsCenter` might have a child that asynchronously loads a 3D model
546
+ * in which case the model matrices will be settled later. `NgtsCenter` needs to know about this
547
+ * matrices change to re-center everything inside of it.
548
+ *
549
+ * The implementation here uses a naive approach to reduce the number of notifications; we cache
550
+ * the notifications by the instance ID and the type of the notification.
551
+ *
552
+ * 1. If there's no cache or
553
+ * 2. If the type is different for the same instance or
554
+ * 3. We've skipped the notifications for this instance more than a certain amount
555
+ *
556
+ * then we'll proceed with notification
557
+ */
825
558
  function notifyAncestors(instance, type) {
826
559
  if (!instance)
827
560
  return;
828
561
  const localState = getInstanceState(instance);
829
562
  if (!localState)
830
563
  return;
831
- const { parent } = localState.hierarchyStore.snapshot;
832
- localState.hierarchyStore.update({ [type]: (localState.hierarchyStore.snapshot[type] || []).slice() });
833
- notifyAncestors(parent, type);
834
- }
835
-
836
- class NgtSelectionApi {
837
- constructor() {
838
- this.enabled = input(true, { alias: 'selection', transform: booleanAttribute });
839
- this.source = signal([]);
840
- this.selected = this.source.asReadonly();
841
- }
842
- update(...args) {
843
- if (!this.enabled())
844
- return;
845
- this.source.update(...args);
564
+ const id = instance.__ngt_id__ || instance['uuid'];
565
+ if (!id)
566
+ return;
567
+ const maxNotificationSkipCount = localState.store?.snapshot.maxNotificationSkipCount || 5;
568
+ const cached = notificationCache.get(id);
569
+ if (!cached || cached.lastType !== type || cached.skipCount > maxNotificationSkipCount) {
570
+ notificationCache.set(id, { skipCount: 0, lastType: type });
571
+ if (notificationCache.size === 1) {
572
+ queueMicrotask(() => notificationCache.clear());
573
+ }
574
+ const { parent } = localState.hierarchyStore.snapshot;
575
+ localState.hierarchyStore.update({ [type]: (localState.hierarchyStore.snapshot[type] || []).slice() });
576
+ notifyAncestors(parent, type);
577
+ return;
846
578
  }
847
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtSelectionApi, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
848
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgtSelectionApi, isStandalone: true, selector: "[selection]", inputs: { enabled: { classPropertyName: "enabled", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
579
+ notificationCache.set(id, { ...cached, skipCount: cached.skipCount + 1 });
849
580
  }
850
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtSelectionApi, decorators: [{
851
- type: Directive,
852
- args: [{ selector: '[selection]' }]
853
- }] });
854
- class NgtSelect {
855
- constructor() {
856
- this.enabled = input(false, { transform: booleanAttribute, alias: 'select' });
857
- const elementRef = inject(ElementRef);
858
- const selectionApi = inject(NgtSelectionApi);
859
- effect((onCleanup) => {
860
- const selectionEnabled = selectionApi.enabled();
861
- if (!selectionEnabled)
862
- return;
863
- const enabled = this.enabled();
864
- if (!enabled)
865
- return;
866
- const host = elementRef.nativeElement;
867
- const localState = getInstanceState(host);
868
- if (!localState)
869
- return;
870
- // ngt-mesh[select]
871
- if (host.type === 'Mesh') {
872
- selectionApi.update((prev) => [...prev, host]);
873
- onCleanup(() => selectionApi.update((prev) => prev.filter((el) => el !== host)));
874
- return;
581
+
582
+ // This function prepares a set of changes to be applied to the instance
583
+ function diffProps(instance, props) {
584
+ const changes = [];
585
+ for (const propKey in props) {
586
+ const propValue = props[propKey];
587
+ let key = propKey;
588
+ if (is.colorSpaceExist(instance)) {
589
+ if (propKey === 'encoding') {
590
+ key = 'colorSpace';
875
591
  }
876
- const [collection] = [untracked(selectionApi.selected), localState.objects()];
877
- let changed = false;
878
- const current = [];
879
- host.traverse((child) => {
880
- child.type === 'Mesh' && current.push(child);
881
- if (collection.indexOf(child) === -1)
882
- changed = true;
883
- });
884
- if (!changed)
885
- return;
886
- selectionApi.update((prev) => [...prev, ...current]);
887
- onCleanup(() => {
888
- selectionApi.update((prev) => prev.filter((el) => !current.includes(el)));
889
- });
890
- });
592
+ else if (propKey === 'outputEncoding') {
593
+ key = 'outputColorSpace';
594
+ }
595
+ }
596
+ if (is.equ(propValue, instance[key]))
597
+ continue;
598
+ changes.push([propKey, propValue]);
891
599
  }
892
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtSelect, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
893
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgtSelect, isStandalone: true, selector: "ngt-group[select], ngt-mesh[select]", inputs: { enabled: { classPropertyName: "enabled", publicName: "select", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
600
+ return changes;
894
601
  }
895
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtSelect, decorators: [{
896
- type: Directive,
897
- args: [{ selector: 'ngt-group[select], ngt-mesh[select]' }]
898
- }], ctorParameters: () => [] });
899
- const NgtSelection = [NgtSelectionApi, NgtSelect];
900
-
901
- /**
902
- * Release pointer captures.
903
- * This is called by releasePointerCapture in the API, and when an object is removed.
904
- */
905
- function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
906
- const captureData = captures.get(obj);
907
- if (captureData) {
908
- captures.delete(obj);
909
- // If this was the last capturing object for this pointer
910
- if (captures.size === 0) {
911
- capturedMap.delete(pointerId);
912
- captureData.target.releasePointerCapture(pointerId);
913
- }
602
+ // NOTE: this is a workaround to give the instance a chance to have the store from the parent.
603
+ // we clear this property after the applyProps is done
604
+ const NGT_APPLY_PROPS = '__ngt_apply_props__';
605
+ // https://github.com/mrdoob/three.js/pull/27042
606
+ // https://github.com/mrdoob/three.js/pull/22748
607
+ const colorMaps = ['map', 'emissiveMap', 'sheenColorMap', 'specularColorMap', 'envMap'];
608
+ function resolveInstanceKey(instance, key) {
609
+ let targetProp = instance[key];
610
+ if (!key.includes('.'))
611
+ return { root: instance, targetKey: key, targetProp };
612
+ // Resolve pierced target
613
+ targetProp = instance;
614
+ for (const part of key.split('.')) {
615
+ key = part;
616
+ instance = targetProp;
617
+ targetProp = targetProp[key];
914
618
  }
619
+ // const chain = key.split('.');
620
+ // targetProp = chain.reduce((acc, part) => acc[part], instance);
621
+ // const targetKey = chain.pop()!;
622
+ //
623
+ // // Switch root if atomic
624
+ // if (!targetProp?.set) instance = chain.reduce((acc, part) => acc[part], instance);
625
+ return { root: instance, targetKey: key, targetProp };
915
626
  }
916
- function removeInteractivity(store, object) {
917
- const { internal } = store.snapshot;
918
- // Removes every trace of an object from the data store
919
- internal.interaction = internal.interaction.filter((o) => o !== object);
920
- internal.initialHits = internal.initialHits.filter((o) => o !== object);
921
- internal.hovered.forEach((value, key) => {
922
- if (value.eventObject === object || value.object === object) {
923
- // Clear out intersects, they are outdated by now
924
- internal.hovered.delete(key);
627
+ // This function applies a set of changes to the instance
628
+ function applyProps(instance, props) {
629
+ // if props is empty
630
+ if (!Object.keys(props).length)
631
+ return instance;
632
+ const localState = getInstanceState(instance);
633
+ const rootState = localState?.store?.snapshot ?? instance[NGT_APPLY_PROPS]?.snapshot ?? {};
634
+ const changes = diffProps(instance, props);
635
+ for (let i = 0; i < changes.length; i++) {
636
+ let [key, value] = changes[i];
637
+ // Ignore setting undefined props
638
+ // https://github.com/pmndrs/react-three-fiber/issues/274
639
+ if (value === undefined)
640
+ continue;
641
+ // Alias (output)encoding => (output)colorSpace (since r152)
642
+ // https://github.com/pmndrs/react-three-fiber/pull/2829
643
+ // if (is.colorSpaceExist(instance)) {
644
+ // const sRGBEncoding = 3001;
645
+ // const SRGBColorSpace = 'srgb';
646
+ // const LinearSRGBColorSpace = 'srgb-linear';
647
+ //
648
+ // if (key === 'encoding') {
649
+ // key = 'colorSpace';
650
+ // value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
651
+ // } else if (key === 'outputEncoding') {
652
+ // key = 'outputColorSpace';
653
+ // value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
654
+ // }
655
+ // }
656
+ const { root, targetKey, targetProp } = resolveInstanceKey(instance, key);
657
+ // we have switched due to pierced props
658
+ if (root !== instance) {
659
+ return applyProps(root, { [targetKey]: value });
925
660
  }
926
- });
927
- internal.capturedMap.forEach((captures, pointerId) => {
928
- releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
929
- });
930
- }
931
- function createEvents(store) {
932
- /** Calculates delta */
933
- function calculateDistance(event) {
934
- const internal = store.snapshot.internal;
935
- const dx = event.offsetX - internal.initialClick[0];
936
- const dy = event.offsetY - internal.initialClick[1];
937
- return Math.round(Math.sqrt(dx * dx + dy * dy));
938
- }
939
- /** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
940
- function filterPointerEvents(objects) {
941
- return objects.filter((obj) => ['move', 'over', 'enter', 'out', 'leave'].some((name) => {
942
- const eventName = `pointer${name}`;
943
- return getInstanceState(obj)?.handlers?.[eventName];
944
- }));
945
- }
946
- function intersect(event, filter) {
947
- const state = store.snapshot;
948
- const duplicates = new Set();
949
- const intersections = [];
950
- // Allow callers to eliminate event objects
951
- const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
952
- // Reset all raycaster cameras to undefined
953
- for (let i = 0; i < eventsObjects.length; i++) {
954
- const objectRootState = getInstanceState(eventsObjects[i])?.store?.snapshot;
955
- if (objectRootState) {
956
- objectRootState.raycaster.camera = undefined;
957
- }
661
+ // Layers have no copy function, we must therefore copy the mask property
662
+ if (targetProp instanceof THREE.Layers && value instanceof THREE.Layers) {
663
+ targetProp.mask = value.mask;
958
664
  }
959
- if (!state.previousRoot) {
960
- // Make sure root-level pointer and ray are set up
961
- state.events.compute?.(event, store, null);
665
+ else if (is.three(targetProp, 'isColor') && is.colorRepresentation(value)) {
666
+ targetProp.set(value);
962
667
  }
963
- function handleRaycast(obj) {
964
- const objStore = getInstanceState(obj)?.store;
965
- const objState = objStore?.snapshot;
966
- // Skip event handling when noEvents is set, or when the raycasters camera is null
967
- if (!objState || !objState.events.enabled || objState.raycaster.camera === null)
968
- return [];
969
- // When the camera is undefined we have to call the event layers update function
970
- if (objState.raycaster.camera === undefined) {
971
- objState.events.compute?.(event, objStore, objState.previousRoot);
972
- // If the camera is still undefined we have to skip this layer entirely
973
- if (objState.raycaster.camera === undefined)
974
- objState.raycaster.camera = null;
668
+ // Copy if properties match signatures
669
+ else if (targetProp &&
670
+ typeof targetProp.set === 'function' &&
671
+ typeof targetProp.copy === 'function' &&
672
+ value?.constructor &&
673
+ targetProp.constructor === value.constructor) {
674
+ // If both are geometries, we should assign the value directly instead of copying
675
+ if (is.three(targetProp, 'isBufferGeometry') &&
676
+ is.three(value, 'isBufferGeometry')) {
677
+ Object.assign(root, { [targetKey]: value });
678
+ }
679
+ else {
680
+ targetProp.copy(value);
975
681
  }
976
- // Intersect object by object
977
- return objState.raycaster.camera ? objState.raycaster.intersectObject(obj, true) : [];
978
682
  }
979
- // Collect events
980
- let hits = eventsObjects
981
- // Intersect objects
982
- .flatMap(handleRaycast)
983
- // Sort by event priority and distance
984
- .sort((a, b) => {
985
- const aState = getInstanceState(a.object)?.store?.snapshot;
986
- const bState = getInstanceState(b.object)?.store?.snapshot;
987
- if (!aState || !bState)
988
- return a.distance - b.distance;
989
- return bState.events.priority - aState.events.priority || a.distance - b.distance;
990
- })
991
- // Filter out duplicates
992
- .filter((item) => {
993
- const id = makeId(item);
683
+ // Set array types
684
+ else if (targetProp && typeof targetProp.set === 'function' && Array.isArray(value)) {
685
+ if (typeof targetProp.fromArray === 'function')
686
+ targetProp.fromArray(value);
687
+ else
688
+ targetProp.set(...value);
689
+ }
690
+ // Set literal types
691
+ else if (targetProp && typeof targetProp.set === 'function' && typeof value !== 'object') {
692
+ const isColor = is.three(targetProp, 'isColor');
693
+ // Allow setting array scalars
694
+ if (!isColor && typeof targetProp.setScalar === 'function' && typeof value === 'number')
695
+ targetProp.setScalar(value);
696
+ // Otherwise just set single value
697
+ else
698
+ targetProp.set(value);
699
+ }
700
+ // Else, just overwrite the value
701
+ else {
702
+ Object.assign(root, { [targetKey]: value });
703
+ // Auto-convert sRGB texture parameters for built-in materials
704
+ // https://github.com/pmndrs/react-three-fiber/issues/344
705
+ // https://github.com/mrdoob/three.js/pull/25857
706
+ if (rootState &&
707
+ !rootState.linear &&
708
+ colorMaps.includes(targetKey) &&
709
+ root[targetKey]?.isTexture &&
710
+ // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
711
+ root[targetKey].format === THREE.RGBAFormat &&
712
+ root[targetKey].type === THREE.UnsignedByteType) {
713
+ // NOTE: this cannot be set from the renderer (e.g. sRGB source textures rendered to P3)
714
+ root[targetKey].colorSpace = THREE.SRGBColorSpace;
715
+ }
716
+ }
717
+ checkUpdate(root[targetKey]);
718
+ checkUpdate(targetProp);
719
+ invalidateInstance(instance);
720
+ }
721
+ const instanceHandlersCount = localState?.eventCount;
722
+ const parent = localState?.hierarchyStore?.snapshot.parent;
723
+ if (parent && rootState.internal && instance['raycast'] && instanceHandlersCount !== localState?.eventCount) {
724
+ // Pre-emptively remove the instance from the interaction manager
725
+ const index = rootState.internal.interaction.indexOf(instance);
726
+ if (index > -1)
727
+ rootState.internal.interaction.splice(index, 1);
728
+ // Add the instance to the interaction manager only when it has handlers
729
+ if (localState?.eventCount)
730
+ rootState.internal.interaction.push(instance);
731
+ }
732
+ if (parent && localState?.onUpdate && changes.length) {
733
+ localState.onUpdate(instance);
734
+ }
735
+ // clearing the intermediate store from the instance
736
+ if (instance[NGT_APPLY_PROPS])
737
+ delete instance[NGT_APPLY_PROPS];
738
+ return instance;
739
+ }
740
+
741
+ const catalogue = {};
742
+ function extend(objects) {
743
+ const keys = Object.keys(objects);
744
+ Object.assign(catalogue, objects);
745
+ return () => {
746
+ remove(...keys);
747
+ };
748
+ }
749
+ function remove(...keys) {
750
+ for (const key of keys) {
751
+ delete catalogue[key];
752
+ }
753
+ }
754
+ const NGT_CATALOGUE = new InjectionToken('NGT_CATALOGUE', { factory: () => catalogue });
755
+ function injectCatalogue() {
756
+ return inject(NGT_CATALOGUE);
757
+ }
758
+
759
+ function isRendererNode(node) {
760
+ return !!node && typeof node === 'object' && NGT_RENDERER_NODE_FLAG in node;
761
+ }
762
+ function createRendererNode(type, node, document) {
763
+ const state = [type, false, undefined, undefined, undefined, undefined, []];
764
+ const rendererNode = Object.assign(node, { [NGT_RENDERER_NODE_FLAG]: state });
765
+ // NOTE: assign ownerDocument to node so we can use HostListener in Component
766
+ if (!rendererNode['ownerDocument'])
767
+ rendererNode['ownerDocument'] = document;
768
+ // NOTE: Angular SSR calls `node.getAttribute()` to retrieve hydration info on a node
769
+ if (!('getAttribute' in rendererNode) || typeof rendererNode['getAttribute'] !== 'function') {
770
+ const getNodeAttribute = (name) => rendererNode[name];
771
+ getNodeAttribute[NGT_GET_NODE_ATTRIBUTE_FLAG] = true;
772
+ Object.defineProperty(rendererNode, 'getAttribute', { value: getNodeAttribute, configurable: true });
773
+ }
774
+ return rendererNode;
775
+ }
776
+ function setRendererParentNode(node, parent) {
777
+ if (!node.__ngt_renderer__[5 /* NgtRendererClassId.parent */]) {
778
+ node.__ngt_renderer__[5 /* NgtRendererClassId.parent */] = parent;
779
+ }
780
+ }
781
+ function addRendererChildNode(node, child) {
782
+ if (!node.__ngt_renderer__[6 /* NgtRendererClassId.children */].includes(child)) {
783
+ node.__ngt_renderer__[6 /* NgtRendererClassId.children */].push(child);
784
+ }
785
+ }
786
+
787
+ const idCache = {};
788
+ function makeId(event) {
789
+ if (event) {
790
+ return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
791
+ }
792
+ const newId = THREE.MathUtils.generateUUID();
793
+ // ensure not already used
794
+ if (!idCache[newId]) {
795
+ idCache[newId] = true;
796
+ return newId;
797
+ }
798
+ return makeId();
799
+ }
800
+ function makeDpr(dpr, window) {
801
+ // Err on the side of progress by assuming 2x dpr if we can't detect it
802
+ // This will happen in workers where window is defined but dpr isn't.
803
+ const target = typeof window !== 'undefined' ? (window.devicePixelRatio ?? 2) : 1;
804
+ return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
805
+ }
806
+ function makeRendererInstance(glOptions, canvas) {
807
+ const defaultOptions = {
808
+ powerPreference: 'high-performance',
809
+ canvas,
810
+ antialias: true,
811
+ alpha: true,
812
+ };
813
+ const customRenderer = (typeof glOptions === 'function' ? glOptions(defaultOptions) : glOptions);
814
+ if (is.renderer(customRenderer))
815
+ return customRenderer;
816
+ return new THREE.WebGLRenderer({ ...defaultOptions, ...glOptions });
817
+ }
818
+ function makeCameraInstance(isOrthographic, size) {
819
+ if (isOrthographic)
820
+ return new THREE.OrthographicCamera(0, 0, 0, 0, 0.1, 1000);
821
+ return new THREE.PerspectiveCamera(75, size.width / size.height, 0.1, 1000);
822
+ }
823
+ function makeObjectGraph(object) {
824
+ const data = { nodes: {}, materials: {}, meshes: {} };
825
+ if (object) {
826
+ object.traverse((child) => {
827
+ if (child.name)
828
+ data.nodes[child.name] = child;
829
+ if ('material' in child && !data.materials[child.material.name]) {
830
+ data.materials[child.material.name] = child
831
+ .material;
832
+ }
833
+ if (is.three(child, 'isMesh') && !data.meshes[child.name])
834
+ data.meshes[child.name] = child;
835
+ });
836
+ }
837
+ return data;
838
+ }
839
+
840
+ /**
841
+ * Release pointer captures.
842
+ * This is called by releasePointerCapture in the API, and when an object is removed.
843
+ */
844
+ function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
845
+ const captureData = captures.get(obj);
846
+ if (captureData) {
847
+ captures.delete(obj);
848
+ // If this was the last capturing object for this pointer
849
+ if (captures.size === 0) {
850
+ capturedMap.delete(pointerId);
851
+ captureData.target.releasePointerCapture(pointerId);
852
+ }
853
+ }
854
+ }
855
+ function removeInteractivity(store, object) {
856
+ const { internal } = store.snapshot;
857
+ // Removes every trace of an object from the data store
858
+ internal.interaction = internal.interaction.filter((o) => o !== object);
859
+ internal.initialHits = internal.initialHits.filter((o) => o !== object);
860
+ internal.hovered.forEach((value, key) => {
861
+ if (value.eventObject === object || value.object === object) {
862
+ // Clear out intersects, they are outdated by now
863
+ internal.hovered.delete(key);
864
+ }
865
+ });
866
+ internal.capturedMap.forEach((captures, pointerId) => {
867
+ releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
868
+ });
869
+ }
870
+ function createEvents(store) {
871
+ /** Calculates delta */
872
+ function calculateDistance(event) {
873
+ const internal = store.snapshot.internal;
874
+ const dx = event.offsetX - internal.initialClick[0];
875
+ const dy = event.offsetY - internal.initialClick[1];
876
+ return Math.round(Math.sqrt(dx * dx + dy * dy));
877
+ }
878
+ /** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
879
+ function filterPointerEvents(objects) {
880
+ return objects.filter((obj) => ['move', 'over', 'enter', 'out', 'leave'].some((name) => {
881
+ const eventName = `pointer${name}`;
882
+ return getInstanceState(obj)?.handlers?.[eventName];
883
+ }));
884
+ }
885
+ function intersect(event, filter) {
886
+ const state = store.snapshot;
887
+ const duplicates = new Set();
888
+ const intersections = [];
889
+ // Allow callers to eliminate event objects
890
+ const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
891
+ // Reset all raycaster cameras to undefined
892
+ for (let i = 0; i < eventsObjects.length; i++) {
893
+ const objectRootState = getInstanceState(eventsObjects[i])?.store?.snapshot;
894
+ if (objectRootState) {
895
+ objectRootState.raycaster.camera = undefined;
896
+ }
897
+ }
898
+ if (!state.previousRoot) {
899
+ // Make sure root-level pointer and ray are set up
900
+ state.events.compute?.(event, store, null);
901
+ }
902
+ function handleRaycast(obj) {
903
+ const objStore = getInstanceState(obj)?.store;
904
+ const objState = objStore?.snapshot;
905
+ // Skip event handling when noEvents is set, or when the raycasters camera is null
906
+ if (!objState || !objState.events.enabled || objState.raycaster.camera === null)
907
+ return [];
908
+ // When the camera is undefined we have to call the event layers update function
909
+ if (objState.raycaster.camera === undefined) {
910
+ objState.events.compute?.(event, objStore, objState.previousRoot);
911
+ // If the camera is still undefined we have to skip this layer entirely
912
+ if (objState.raycaster.camera === undefined)
913
+ objState.raycaster.camera = null;
914
+ }
915
+ // Intersect object by object
916
+ return objState.raycaster.camera ? objState.raycaster.intersectObject(obj, true) : [];
917
+ }
918
+ // Collect events
919
+ let hits = eventsObjects
920
+ // Intersect objects
921
+ .flatMap(handleRaycast)
922
+ // Sort by event priority and distance
923
+ .sort((a, b) => {
924
+ const aState = getInstanceState(a.object)?.store?.snapshot;
925
+ const bState = getInstanceState(b.object)?.store?.snapshot;
926
+ if (!aState || !bState)
927
+ return a.distance - b.distance;
928
+ return bState.events.priority - aState.events.priority || a.distance - b.distance;
929
+ })
930
+ // Filter out duplicates
931
+ .filter((item) => {
932
+ const id = makeId(item);
994
933
  if (duplicates.has(id))
995
934
  return false;
996
935
  duplicates.add(id);
@@ -1172,6 +1111,9 @@ function createEvents(store) {
1172
1111
  }
1173
1112
  };
1174
1113
  }
1114
+ const isPointerMove = name === 'pointermove';
1115
+ const isClickEvent = name === 'click' || name === 'contextmenu' || name === 'dblclick';
1116
+ const filter = isPointerMove ? filterPointerEvents : undefined;
1175
1117
  // Any other pointer goes here ...
1176
1118
  return function handleEvent(event) {
1177
1119
  // NOTE: __pointerMissed$ on NgtStore is private subject since we only expose the Observable
@@ -1180,9 +1122,6 @@ function createEvents(store) {
1180
1122
  // prepareRay(event)
1181
1123
  internal.lastEvent.nativeElement = event;
1182
1124
  // Get fresh intersects
1183
- const isPointerMove = name === 'pointermove';
1184
- const isClickEvent = name === 'click' || name === 'contextmenu' || name === 'dblclick';
1185
- const filter = isPointerMove ? filterPointerEvents : undefined;
1186
1125
  const hits = intersect(event, filter);
1187
1126
  const delta = isClickEvent ? calculateDistance(event) : 0;
1188
1127
  // Save initial coordinates on pointer-down
@@ -1272,1323 +1211,532 @@ function createEvents(store) {
1272
1211
  return { handlePointer };
1273
1212
  }
1274
1213
 
1275
- var _a;
1276
- const NGT_HTML_DOM_ELEMENT = new InjectionToken('NGT_HTML_DOM_ELEMENT');
1277
- function provideHTMLDomElement(...args) {
1278
- if (args.length === 0) {
1279
- return { provide: NGT_HTML_DOM_ELEMENT, useFactory: () => 'gl' };
1280
- }
1281
- if (args.length === 1) {
1282
- return { provide: NGT_HTML_DOM_ELEMENT, useFactory: args[0] };
1214
+ function attach(object, value, paths = [], useApplyProps = false) {
1215
+ const [base, ...remaining] = paths;
1216
+ if (!base)
1217
+ return;
1218
+ if (remaining.length === 0) {
1219
+ if (useApplyProps)
1220
+ applyProps(object, { [base]: value });
1221
+ else
1222
+ object[base] = value;
1283
1223
  }
1284
- return { provide: NGT_HTML_DOM_ELEMENT, useFactory: args.pop(), deps: args };
1285
- }
1286
- class NgtHTML {
1287
- static { _a = NGT_HTML_FLAG; }
1288
- static { this[_a] = true; }
1289
- constructor() {
1290
- this.domElement = inject(NGT_HTML_DOM_ELEMENT, { self: true, optional: true });
1291
- const host = inject(ElementRef);
1292
- const store = injectStore();
1293
- if (this.domElement === 'gl') {
1294
- Object.assign(host.nativeElement, {
1295
- [NGT_DOM_PARENT_FLAG]: store.snapshot.gl.domElement.parentElement,
1296
- });
1297
- }
1298
- else if (this.domElement) {
1299
- Object.assign(host.nativeElement, { [NGT_DOM_PARENT_FLAG]: this.domElement });
1300
- }
1301
- inject(DestroyRef).onDestroy(() => {
1302
- host.nativeElement[NGT_DOM_PARENT_FLAG] = null;
1303
- delete host.nativeElement[NGT_DOM_PARENT_FLAG];
1304
- });
1224
+ else {
1225
+ assignEmpty(object, base, useApplyProps);
1226
+ attach(object[base], value, remaining, useApplyProps);
1305
1227
  }
1306
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtHTML, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1307
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgtHTML, isStandalone: true, ngImport: i0 }); }
1308
1228
  }
1309
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtHTML, decorators: [{
1310
- type: Directive
1311
- }], ctorParameters: () => [] });
1312
-
1313
- const cached$1 = new Map();
1314
- const memoizedLoaders$1 = new WeakMap();
1315
- function normalizeInputs$1(input) {
1316
- let urls = [];
1317
- if (Array.isArray(input)) {
1318
- urls = input;
1229
+ function detach(parent, child, attachProp) {
1230
+ const childInstanceState = getInstanceState(child);
1231
+ if (childInstanceState) {
1232
+ if (Array.isArray(attachProp))
1233
+ attach(parent, childInstanceState.previousAttach, attachProp, childInstanceState.type === 'ngt-value');
1234
+ else
1235
+ childInstanceState.previousAttach?.();
1319
1236
  }
1320
- else if (typeof input === 'string') {
1321
- urls = [input];
1237
+ }
1238
+ function assignEmpty(obj, base, shouldAssignStoreForApplyProps = false) {
1239
+ if ((!Object.hasOwn(obj, base) && Reflect && !!Reflect.has && !Reflect.has(obj, base)) || obj[base] === undefined) {
1240
+ obj[base] = {};
1322
1241
  }
1323
- else {
1324
- urls = Object.values(input);
1242
+ if (shouldAssignStoreForApplyProps) {
1243
+ const instanceState = getInstanceState(obj[base]);
1244
+ // if we already have instance state, bail out
1245
+ if (instanceState)
1246
+ return;
1247
+ const parentInstanceState = getInstanceState(obj);
1248
+ // if parent doesn't have instance state, bail out
1249
+ if (!parentInstanceState)
1250
+ return;
1251
+ Object.assign(obj[base], { [NGT_APPLY_PROPS]: parentInstanceState.store });
1325
1252
  }
1326
- return urls.map((url) => (url.includes('undefined') || url.includes('null') || !url ? '' : url));
1327
- }
1328
- function load(loaderConstructorFactory, inputs, { extensions, onLoad, onProgress, } = {}) {
1329
- return () => {
1330
- const urls = normalizeInputs$1(inputs());
1331
- let loader = memoizedLoaders$1.get(loaderConstructorFactory(urls));
1332
- if (!loader) {
1333
- loader = new (loaderConstructorFactory(urls))();
1334
- memoizedLoaders$1.set(loaderConstructorFactory(urls), loader);
1335
- }
1336
- if (extensions)
1337
- extensions(loader);
1338
- return urls.map((url) => {
1339
- if (url === '')
1340
- return Promise.resolve(null);
1341
- if (!cached$1.has(url)) {
1342
- cached$1.set(url, new Promise((resolve, reject) => {
1343
- loader.load(url, (data) => {
1344
- if ('scene' in data) {
1345
- Object.assign(data, makeObjectGraph(data['scene']));
1346
- }
1347
- if (onLoad) {
1348
- onLoad(data);
1349
- }
1350
- resolve(data);
1351
- }, onProgress, (error) => reject(new Error(`[NGT] Could not load ${url}: ${error?.message}`)));
1352
- }));
1353
- }
1354
- return cached$1.get(url);
1355
- });
1356
- };
1357
1253
  }
1358
- /**
1359
- * @deprecated Use loaderResource instead. Will be removed in v5.0.0
1360
- * @since v4.0.0~
1361
- */
1362
- function _injectLoader(loaderConstructorFactory, inputs, { extensions, onProgress, onLoad, injector, } = {}) {
1363
- return assertInjector(_injectLoader, injector, () => {
1364
- const response = signal(null);
1365
- const cachedResultPromisesEffect = load(loaderConstructorFactory, inputs, {
1366
- extensions,
1367
- onProgress,
1368
- onLoad: onLoad,
1369
- });
1370
- effect(() => {
1371
- const originalUrls = inputs();
1372
- const cachedResultPromises = cachedResultPromisesEffect();
1373
- Promise.all(cachedResultPromises).then((results) => {
1374
- response.update(() => {
1375
- if (Array.isArray(originalUrls))
1376
- return results;
1377
- if (typeof originalUrls === 'string')
1378
- return results[0];
1379
- const keys = Object.keys(originalUrls);
1380
- return keys.reduce((result, key) => {
1381
- // @ts-ignore
1382
- result[key] = results[keys.indexOf(key)];
1383
- return result;
1384
- }, {});
1385
- });
1386
- });
1387
- });
1388
- return response.asReadonly();
1389
- });
1254
+ function createAttachFunction(cb) {
1255
+ return (parent, child, store) => cb({ parent, child, store });
1390
1256
  }
1391
- _injectLoader.preload = (loaderConstructorFactory, inputs, extensions, onLoad) => {
1392
- const effects = load(loaderConstructorFactory, inputs, { extensions, onLoad })();
1393
- if (effects) {
1394
- void Promise.all(effects);
1395
- }
1396
- };
1397
- _injectLoader.destroy = () => {
1398
- cached$1.clear();
1399
- };
1400
- _injectLoader.clear = (urls) => {
1401
- const urlToClear = Array.isArray(urls) ? urls : [urls];
1402
- urlToClear.forEach((url) => {
1403
- cached$1.delete(url);
1404
- });
1405
- };
1406
- /**
1407
- * @deprecated Use loaderResource instead. Will be removed in v5.0.0
1408
- * @since v4.0.0~
1409
- */
1410
- const injectLoader = _injectLoader;
1411
1257
 
1412
- function normalizeInputs(input) {
1413
- let urls = [];
1414
- if (Array.isArray(input)) {
1415
- urls = input;
1416
- }
1417
- else if (typeof input === 'string') {
1418
- urls = [input];
1419
- }
1420
- else {
1421
- urls = Object.values(input);
1258
+ function kebabToPascal(str) {
1259
+ if (!str)
1260
+ return str; // Handle empty input
1261
+ let pascalStr = '';
1262
+ let capitalizeNext = true; // Flag to track capitalization
1263
+ for (let i = 0; i < str.length; i++) {
1264
+ const char = str[i];
1265
+ if (char === '-') {
1266
+ capitalizeNext = true;
1267
+ continue;
1268
+ }
1269
+ pascalStr += capitalizeNext ? char.toUpperCase() : char;
1270
+ capitalizeNext = false;
1422
1271
  }
1423
- return urls.map((url) => (url.includes('undefined') || url.includes('null') || !url ? '' : url));
1272
+ return pascalStr;
1424
1273
  }
1425
- const cached = new Map();
1426
- const memoizedLoaders = new WeakMap();
1427
- function getLoaderRequestParams(input, loaderConstructorFactory, extensions) {
1428
- const urls = input();
1429
- const LoaderConstructor = loaderConstructorFactory(urls);
1430
- const normalizedUrls = normalizeInputs(urls);
1431
- let loader = memoizedLoaders.get(LoaderConstructor);
1432
- if (!loader) {
1433
- loader = new LoaderConstructor();
1434
- memoizedLoaders.set(LoaderConstructor, loader);
1274
+ function propagateStoreRecursively(node, parentNode) {
1275
+ const iS = getInstanceState(node);
1276
+ const pIS = getInstanceState(parentNode);
1277
+ if (!iS || !pIS)
1278
+ return;
1279
+ // assign store on child if not already exist
1280
+ // or child store is not the same as parent store
1281
+ // or child store is the parent of parent store
1282
+ if (!iS.store || iS.store !== pIS.store || iS.store === pIS.store.snapshot.previousRoot) {
1283
+ iS.store = pIS.store;
1284
+ // Call addInteraction if it exists
1285
+ iS.addInteraction?.(pIS.store);
1286
+ // Collect all children (objects and nonObjects)
1287
+ const children = [
1288
+ ...(iS.objects ? untracked(iS.objects) : []),
1289
+ ...(iS.nonObjects ? untracked(iS.nonObjects) : []),
1290
+ ];
1291
+ // Recursively reassign the store for each child
1292
+ for (const child of children) {
1293
+ propagateStoreRecursively(child, node);
1294
+ }
1435
1295
  }
1436
- if (extensions)
1437
- extensions(loader);
1438
- return { urls, normalizedUrls, loader };
1439
1296
  }
1440
- function getLoaderPromises(request, onProgress) {
1441
- return request.normalizedUrls.map((url) => {
1442
- if (url === '')
1443
- return Promise.resolve(null);
1444
- const cachedPromise = cached.get(url);
1445
- if (cachedPromise)
1446
- return cachedPromise;
1447
- const promise = new Promise((res, rej) => {
1448
- request.loader.load(url, (data) => {
1449
- if ('scene' in data) {
1450
- Object.assign(data, makeObjectGraph(data['scene']));
1297
+ function attachThreeNodes(parent, child) {
1298
+ const pIS = getInstanceState(parent);
1299
+ const cIS = getInstanceState(child);
1300
+ if (!pIS || !cIS) {
1301
+ throw new Error(`[NGT] THREE instances need to be prepared with local state.`);
1302
+ }
1303
+ // whether the child is added to the parent with parent.add()
1304
+ let added = false;
1305
+ // propagate store recursively
1306
+ propagateStoreRecursively(child, parent);
1307
+ if (cIS.attach) {
1308
+ const attachProp = cIS.attach;
1309
+ if (typeof attachProp === 'function') {
1310
+ let attachCleanUp = undefined;
1311
+ if (cIS.type === 'ngt-value') {
1312
+ if (cIS.hierarchyStore.snapshot.parent !== parent) {
1313
+ cIS.setParent(parent);
1451
1314
  }
1452
- res(data);
1453
- }, onProgress, (error) => rej(new Error(`[NGT] Could not load ${url}: ${error?.message}`)));
1454
- });
1455
- cached.set(url, promise);
1456
- return promise;
1457
- });
1458
- }
1459
- function loaderResource(loaderConstructorFactory, input, { extensions, onLoad, onProgress, injector, } = {}) {
1460
- return assertInjector(loaderResource, injector, () => {
1461
- return resource({
1462
- request: () => getLoaderRequestParams(input, loaderConstructorFactory, extensions),
1463
- loader: async ({ request }) => {
1464
- // TODO: use the abortSignal when THREE.Loader supports it
1465
- const loadedResults = await Promise.all(getLoaderPromises(request, onProgress));
1466
- let results;
1467
- if (Array.isArray(request.urls)) {
1468
- results = loadedResults;
1469
- }
1470
- else if (typeof request.urls === 'string') {
1471
- results = loadedResults[0];
1472
- }
1473
- else {
1474
- const keys = Object.keys(request.urls);
1475
- results = keys.reduce((result, key) => {
1476
- // @ts-ignore
1477
- result[key] = loadedResults[keys.indexOf(key)];
1478
- return result;
1479
- }, {});
1480
- }
1481
- if (onLoad)
1482
- onLoad(results);
1483
- return results;
1484
- },
1485
- });
1486
- });
1487
- }
1488
- loaderResource.preload = (loaderConstructor, inputs, extensions) => {
1489
- const params = getLoaderRequestParams(() => inputs, () => loaderConstructor, extensions);
1490
- void Promise.all(getLoaderPromises(params));
1491
- };
1492
- loaderResource.destroy = () => {
1493
- cached.clear();
1494
- };
1495
- loaderResource.clear = (urls) => {
1496
- const urlToClear = Array.isArray(urls) ? urls : [urls];
1497
- urlToClear.forEach((url) => {
1498
- cached.delete(url);
1499
- });
1500
- };
1501
-
1502
- const RGBA_REGEX = /rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*(\d*\.?\d+)?\)/;
1503
- const DEFAULT_COLOR = 0x000000;
1504
- class NgtHexify {
1505
- constructor() {
1506
- this.document = inject(DOCUMENT, { optional: true });
1507
- this.cache = {};
1508
- }
1509
- /**
1510
- * transforms a:
1511
- * - hex string to a hex number
1512
- * - rgb string to a hex number
1513
- * - rgba string to a hex number
1514
- * - css color string to a hex number
1515
- *
1516
- * always default to black if failed
1517
- * @param value
1518
- */
1519
- transform(value) {
1520
- if (value == null)
1521
- return DEFAULT_COLOR;
1522
- if (value.startsWith('#')) {
1523
- if (!this.cache[value]) {
1524
- this.cache[value] = this.hexStringToNumber(value);
1315
+ // at this point we don't have rawValue yet, so we bail and wait until the Renderer recalls attach
1316
+ if (child.__ngt_renderer__[2 /* NgtRendererClassId.rawValue */] === undefined)
1317
+ return;
1318
+ attachCleanUp = attachProp(parent, child.__ngt_renderer__[2 /* NgtRendererClassId.rawValue */], cIS.store);
1525
1319
  }
1526
- return this.cache[value];
1527
- }
1528
- if (!this.ctx) {
1529
- this.ctx = this.document?.createElement('canvas').getContext('2d');
1530
- }
1531
- if (!this.ctx) {
1532
- console.warn('[NGT] hexify: canvas context is not available');
1533
- return DEFAULT_COLOR;
1534
- }
1535
- this.ctx.fillStyle = value;
1536
- const computedValue = this.ctx.fillStyle;
1537
- if (computedValue.startsWith('#')) {
1538
- if (!this.cache[computedValue]) {
1539
- this.cache[computedValue] = this.hexStringToNumber(computedValue);
1320
+ else {
1321
+ attachCleanUp = attachProp(parent, child, cIS.store);
1540
1322
  }
1541
- return this.cache[computedValue];
1542
- }
1543
- if (!computedValue.startsWith('rgba')) {
1544
- console.warn(`[NGT] hexify: invalid color format. Expected rgba or hex, receive: ${computedValue}`);
1545
- return DEFAULT_COLOR;
1546
- }
1547
- const match = computedValue.match(RGBA_REGEX);
1548
- if (!match) {
1549
- console.warn(`[NGT] hexify: invalid color format. Expected rgba or hex, receive: ${computedValue}`);
1550
- return DEFAULT_COLOR;
1323
+ if (attachCleanUp)
1324
+ cIS.previousAttach = attachCleanUp;
1551
1325
  }
1552
- const r = parseInt(match[1], 10);
1553
- const g = parseInt(match[2], 10);
1554
- const b = parseInt(match[3], 10);
1555
- const a = match[4] ? parseFloat(match[4]) : 1.0;
1556
- const cacheKey = `${r}:${g}:${b}:${a}`;
1557
- // check result from cache
1558
- if (!this.cache[cacheKey]) {
1559
- // Convert the components to hex strings
1560
- const hexR = this.componentToHex(r);
1561
- const hexG = this.componentToHex(g);
1562
- const hexB = this.componentToHex(b);
1563
- const hexA = this.componentToHex(Math.round(a * 255));
1564
- // Combine the hex components into a single hex string
1565
- const hex = `#${hexR}${hexG}${hexB}${hexA}`;
1566
- this.cache[cacheKey] = this.hexStringToNumber(hex);
1326
+ else {
1327
+ // we skip attach none if set explicitly
1328
+ if (attachProp[0] === 'none') {
1329
+ invalidateInstance(child);
1330
+ return;
1331
+ }
1332
+ // handle material array
1333
+ if (attachProp[0] === 'material' &&
1334
+ attachProp[1] &&
1335
+ typeof Number(attachProp[1]) === 'number' &&
1336
+ is.three(child, 'isMaterial') &&
1337
+ !Array.isArray(parent['material'])) {
1338
+ parent['material'] = [];
1339
+ }
1340
+ if (cIS.type === 'ngt-value') {
1341
+ if (cIS.hierarchyStore.snapshot.parent !== parent) {
1342
+ cIS.setParent(parent);
1343
+ }
1344
+ // at this point we don't have rawValue yet, so we bail and wait until the Renderer recalls attach
1345
+ if (child.__ngt_renderer__[2 /* NgtRendererClassId.rawValue */] === undefined)
1346
+ return;
1347
+ // save prev value
1348
+ cIS.previousAttach = attachProp.reduce((value, key) => value[key], parent);
1349
+ attach(parent, child.__ngt_renderer__[2 /* NgtRendererClassId.rawValue */], attachProp, true);
1350
+ }
1351
+ else {
1352
+ // save prev value
1353
+ cIS.previousAttach = attachProp.reduce((value, key) => value[key], parent);
1354
+ attach(parent, child, attachProp);
1355
+ }
1567
1356
  }
1568
- return this.cache[cacheKey];
1569
1357
  }
1570
- hexStringToNumber(hexString) {
1571
- return parseInt(hexString.replace('#', ''), 16);
1358
+ else if (is.three(parent, 'isObject3D') && is.three(child, 'isObject3D')) {
1359
+ parent.add(child);
1360
+ added = true;
1361
+ cIS.addInteraction?.(cIS.store || pIS.store);
1572
1362
  }
1573
- componentToHex(component) {
1574
- const hex = component.toString(16);
1575
- return hex.length === 1 ? '0' + hex : hex;
1363
+ if (pIS.add) {
1364
+ pIS.add(child, added ? 'objects' : 'nonObjects');
1576
1365
  }
1577
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtHexify, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
1578
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.11", ngImport: i0, type: NgtHexify, isStandalone: true, name: "hexify" }); }
1579
- }
1580
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtHexify, decorators: [{
1581
- type: Pipe,
1582
- args: [{ name: 'hexify', pure: true }]
1583
- }] });
1584
-
1585
- const catalogue = {};
1586
- function extend(objects) {
1587
- const keys = Object.keys(objects);
1588
- Object.assign(catalogue, objects);
1589
- return () => {
1590
- remove(...keys);
1591
- };
1592
- }
1593
- function remove(...keys) {
1594
- for (const key of keys) {
1595
- delete catalogue[key];
1366
+ if (cIS.parent && untracked(cIS.parent) !== parent) {
1367
+ cIS.setParent(parent);
1596
1368
  }
1369
+ // NOTE: this does not mean that the child is actually attached to the parent on the scenegraph.
1370
+ // a child on the Angular template can also emit onAttach
1371
+ if (cIS.onAttach)
1372
+ cIS.onAttach({ parent, node: child });
1373
+ invalidateInstance(child);
1374
+ invalidateInstance(parent);
1597
1375
  }
1598
- const NGT_CATALOGUE = new InjectionToken('NGT_CATALOGUE', { factory: () => catalogue });
1599
- function injectCatalogue() {
1600
- return inject(NGT_CATALOGUE);
1601
- }
1602
-
1603
- function omit(objFn, keysToOmit, equal = (a, b) => is.equ(a, b, { objects: 'shallow', arrays: 'shallow' })) {
1604
- return computed(() => {
1605
- const obj = objFn();
1606
- const result = {};
1607
- for (const key of Object.keys(obj)) {
1608
- if (keysToOmit.includes(key))
1609
- continue;
1610
- Object.assign(result, { [key]: obj[key] });
1611
- }
1612
- return result;
1613
- }, { equal });
1614
- }
1615
- function pick(objFn, keyOrKeys, equal) {
1616
- if (Array.isArray(keyOrKeys)) {
1617
- if (!equal) {
1618
- equal = (a, b) => is.equ(a, b, { objects: 'shallow', arrays: 'shallow' });
1619
- }
1620
- return computed(() => {
1621
- const obj = objFn();
1622
- const result = {};
1623
- for (const key of keyOrKeys) {
1624
- if (!(key in obj))
1625
- continue;
1626
- Object.assign(result, { [key]: obj[key] });
1627
- }
1628
- return result;
1629
- }, { equal });
1376
+ function removeThreeChild(child, parent, dispose) {
1377
+ const pIS = getInstanceState(parent);
1378
+ const cIS = getInstanceState(child);
1379
+ // clear parent ref
1380
+ cIS?.setParent(null);
1381
+ // remove child from parent
1382
+ pIS?.remove?.(child, 'objects');
1383
+ pIS?.remove?.(child, 'nonObjects');
1384
+ if (cIS?.attach) {
1385
+ detach(parent, child, cIS.attach);
1630
1386
  }
1631
- return computed(() => objFn()[keyOrKeys], { equal });
1632
- }
1633
- function merge(objFn, toMerge, mode = 'override', equal = (a, b) => is.equ(a, b, { objects: 'shallow', arrays: 'shallow' })) {
1634
- if (mode === 'override')
1635
- return computed(() => ({ ...objFn(), ...toMerge }), { equal });
1636
- return computed(() => ({ ...toMerge, ...objFn() }), { equal });
1387
+ else if (is.three(parent, 'isObject3D') && is.three(child, 'isObject3D')) {
1388
+ parent.remove(child);
1389
+ const store = cIS?.store || pIS?.store;
1390
+ cIS?.removeInteraction?.(store);
1391
+ if (store)
1392
+ removeInteractivity(store, child);
1393
+ }
1394
+ // dispose
1395
+ const isPrimitive = cIS?.type && cIS.type !== 'ngt-primitive';
1396
+ if (!isPrimitive && child['dispose'] && !is.three(child, 'isScene')) {
1397
+ queueMicrotask(() => child['dispose']());
1398
+ }
1399
+ invalidateInstance(parent);
1637
1400
  }
1638
- function createVectorComputed(vectorCtor) {
1639
- return ((inputOrOptions, keyOrKeepUndefined, keepUndefined) => {
1640
- if (typeof keyOrKeepUndefined === 'undefined' || typeof keyOrKeepUndefined === 'boolean') {
1641
- keepUndefined = !!keyOrKeepUndefined;
1642
- const input = inputOrOptions;
1643
- return computed(() => {
1644
- const value = input();
1645
- if (keepUndefined && value == undefined)
1646
- return undefined;
1647
- if (typeof value === 'number')
1648
- return new vectorCtor().setScalar(value);
1649
- else if (Array.isArray(value))
1650
- return new vectorCtor(...value);
1651
- else if (value)
1652
- return value;
1653
- else
1654
- return new vectorCtor();
1655
- }, { equal: (a, b) => !!a && !!b && a.equals(b) });
1401
+ function internalDestroyNode(node, removeChild) {
1402
+ const rS = node.__ngt_renderer__;
1403
+ if (!rS || rS[1 /* NgtRendererClassId.destroyed */])
1404
+ return;
1405
+ for (const child of rS[6 /* NgtRendererClassId.children */].slice()) {
1406
+ removeChild?.(node, child);
1407
+ internalDestroyNode(child, removeChild);
1408
+ }
1409
+ // clear out parent if haven't
1410
+ rS[5 /* NgtRendererClassId.parent */] = undefined;
1411
+ // clear out children
1412
+ rS[6 /* NgtRendererClassId.children */].length = 0;
1413
+ // clear out NgtInstanceState
1414
+ const iS = getInstanceState(node);
1415
+ if (iS) {
1416
+ const temp = iS;
1417
+ iS.removeInteraction?.(iS.store);
1418
+ delete temp['onAttach'];
1419
+ delete temp['onUpdate'];
1420
+ delete temp['object'];
1421
+ delete temp['objects'];
1422
+ delete temp['nonObjects'];
1423
+ delete temp['parent'];
1424
+ delete temp['add'];
1425
+ delete temp['remove'];
1426
+ delete temp['updateGeometryStamp'];
1427
+ delete temp['setParent'];
1428
+ delete temp['store'];
1429
+ delete temp['handlers'];
1430
+ delete temp['hierarchyStore'];
1431
+ delete temp['previousAttach'];
1432
+ delete temp['setPointerEvent'];
1433
+ delete temp['addInteraction'];
1434
+ delete temp['removeInteraction'];
1435
+ if (iS.type !== 'ngt-primitive') {
1436
+ delete node['__ngt__'];
1656
1437
  }
1657
- const options = inputOrOptions;
1658
- const key = keyOrKeepUndefined;
1659
- return computed(() => {
1660
- const value = options()[key];
1661
- if (keepUndefined && value == undefined)
1662
- return undefined;
1663
- if (typeof value === 'number')
1664
- return new vectorCtor().setScalar(value);
1665
- else if (Array.isArray(value))
1666
- return new vectorCtor(...value);
1667
- else if (value)
1668
- return value;
1669
- else
1670
- return new vectorCtor();
1671
- }, { equal: (a, b) => !!a && !!b && a.equals(b) });
1672
- });
1673
- }
1674
- const vector2 = createVectorComputed(THREE.Vector2);
1675
- const vector3 = createVectorComputed(THREE.Vector3);
1676
- const vector4 = createVectorComputed(THREE.Vector4);
1677
-
1678
- class NgtPortalAutoRender {
1679
- constructor() {
1680
- // TODO: (chau) investigate if this is still needed
1681
- // effect(() => {
1682
- // this.portalStore.update((state) => ({ events: { ...state.events, priority: this.renderPriority() + 1 } }));
1683
- // });
1684
- this.portalStore = injectStore({ host: true });
1685
- this.parentStore = injectStore({ skipSelf: true });
1686
- this.portal = inject(NgtPortalImpl, { host: true });
1687
- this.renderPriority = input(1, { alias: 'autoRender', transform: (value) => numberAttribute(value, 1) });
1688
- effect((onCleanup) => {
1689
- const portalRendered = this.portal.portalRendered();
1690
- if (!portalRendered)
1691
- return;
1692
- // track state
1693
- const [renderPriority, { internal }] = [this.renderPriority(), this.portalStore()];
1694
- let oldClean;
1695
- const cleanup = internal.subscribe(({ gl, scene, camera }) => {
1696
- const [parentScene, parentCamera] = [
1697
- this.parentStore.snapshot.scene,
1698
- this.parentStore.snapshot.camera,
1699
- ];
1700
- oldClean = gl.autoClear;
1701
- if (renderPriority === 1) {
1702
- // clear scene and render with default
1703
- gl.autoClear = true;
1704
- gl.render(parentScene, parentCamera);
1705
- }
1706
- // disable cleaning
1707
- gl.autoClear = false;
1708
- gl.clearDepth();
1709
- gl.render(scene, camera);
1710
- // restore
1711
- gl.autoClear = oldClean;
1712
- }, renderPriority, this.portalStore);
1713
- onCleanup(() => cleanup());
1714
- });
1715
1438
  }
1716
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtPortalAutoRender, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1717
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgtPortalAutoRender, isStandalone: true, selector: "ngt-portal[autoRender]", inputs: { renderPriority: { classPropertyName: "renderPriority", publicName: "autoRender", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
1718
- }
1719
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtPortalAutoRender, decorators: [{
1720
- type: Directive,
1721
- args: [{ selector: 'ngt-portal[autoRender]' }]
1722
- }], ctorParameters: () => [] });
1723
- class NgtPortalContent {
1724
- static ngTemplateContextGuard(_, ctx) {
1725
- return true;
1439
+ // clear our debugNode
1440
+ rS[4 /* NgtRendererClassId.injector */] = undefined;
1441
+ if (rS[0 /* NgtRendererClassId.type */] === 'comment') {
1442
+ delete node[NGT_INTERNAL_ADD_COMMENT_FLAG];
1443
+ delete node[NGT_INTERNAL_SET_PARENT_COMMENT_FLAG];
1444
+ delete node[NGT_CANVAS_CONTENT_FLAG];
1445
+ delete node[NGT_PORTAL_CONTENT_FLAG];
1446
+ delete node[NGT_DOM_PARENT_FLAG];
1726
1447
  }
1727
- constructor() {
1728
- const host = inject(ElementRef, { skipSelf: true });
1729
- const { element } = inject(ViewContainerRef);
1730
- const injector = inject(Injector);
1731
- const commentNode = element.nativeElement;
1732
- const store = injectStore();
1733
- commentNode.data = NGT_PORTAL_CONTENT_FLAG;
1734
- commentNode[NGT_PORTAL_CONTENT_FLAG] = store;
1735
- commentNode[NGT_DOM_PARENT_FLAG] = host.nativeElement;
1448
+ // clear getAttribute if exist
1449
+ if ('getAttribute' in node &&
1450
+ typeof node['getAttribute'] === 'function' &&
1451
+ node['getAttribute'][NGT_GET_NODE_ATTRIBUTE_FLAG]) {
1452
+ delete node['getAttribute'];
1736
1453
  }
1737
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtPortalContent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1738
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgtPortalContent, isStandalone: true, selector: "ng-template[portalContent]", ngImport: i0 }); }
1454
+ // mark node as destroyed
1455
+ rS[1 /* NgtRendererClassId.destroyed */] = true;
1739
1456
  }
1740
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtPortalContent, decorators: [{
1741
- type: Directive,
1742
- args: [{ selector: 'ng-template[portalContent]' }]
1743
- }], ctorParameters: () => [] });
1744
- function mergeState(previousRoot, store, container, pointer, raycaster, events, size) {
1745
- // we never want to spread the id
1746
- const { id: _, ...previousState } = previousRoot.snapshot;
1747
- const state = store.snapshot;
1748
- let viewport = undefined;
1749
- if (state.camera && size) {
1750
- const camera = state.camera;
1751
- // calculate the override viewport, if present
1752
- viewport = previousState.viewport.getCurrentViewport(camera, new THREE.Vector3(), size);
1753
- // update the portal camera, if it differs from the previous layer
1754
- if (camera !== previousState.camera)
1755
- updateCamera(camera, size);
1457
+
1458
+ const NGT_RENDERER_OPTIONS = new InjectionToken('NGT_RENDERER_OPTIONS');
1459
+ class NgtRendererFactory2 {
1460
+ /**
1461
+ * NOTE: We use `useFactory` to instantiate `NgtRendererFactory2`
1462
+ */
1463
+ constructor(delegateRendererFactory) {
1464
+ this.delegateRendererFactory = delegateRendererFactory;
1465
+ this.catalogue = injectCatalogue();
1466
+ this.document = inject(DOCUMENT);
1467
+ this.options = inject(NGT_RENDERER_OPTIONS, { optional: true }) || {};
1468
+ this.rendererMap = new Map();
1756
1469
  }
1757
- return {
1758
- // the intersect consists of the previous root state
1759
- ...previousState,
1760
- ...state,
1761
- // portals have their own scene, which forms the root, a raycaster and a pointer
1762
- scene: container,
1763
- pointer,
1764
- raycaster,
1765
- // their previous root is the layer before it
1766
- previousRoot,
1767
- events: { ...previousState.events, ...state.events, ...events },
1768
- size: { ...previousState.size, ...size },
1769
- viewport: { ...previousState.viewport, ...viewport },
1770
- // layers are allowed to override events
1771
- setEvents: (events) => store.update((state) => ({ ...state, events: { ...state.events, ...events } })),
1772
- };
1773
- }
1774
- class NgtPortalImpl {
1775
- constructor() {
1776
- this.container = input.required();
1777
- this.state = input({});
1778
- this.contentRef = contentChild.required(NgtPortalContent, { read: TemplateRef });
1779
- this.anchorRef = contentChild.required(NgtPortalContent, { read: ViewContainerRef });
1780
- this.previousStore = injectStore({ skipSelf: true });
1781
- this.portalStore = injectStore();
1782
- this.injector = inject(Injector);
1783
- this.size = pick(this.state, 'size');
1784
- this.events = pick(this.state, 'events');
1785
- this.restState = omit(this.state, ['size', 'events']);
1786
- this.portalContentRendered = signal(false);
1787
- this.portalRendered = this.portalContentRendered.asReadonly();
1788
- extend({ Group });
1789
- effect(() => {
1790
- let [container, anchor, content] = [
1791
- this.container(),
1792
- this.anchorRef(),
1793
- this.contentRef(),
1794
- this.previousStore(),
1795
- ];
1796
- const [size, events, restState] = [untracked(this.size), untracked(this.events), untracked(this.restState)];
1797
- if (!is.instance(container)) {
1798
- container = prepare(container, 'ngt-portal', { store: this.portalStore });
1799
- }
1800
- const instanceState = getInstanceState(container);
1801
- if (instanceState && instanceState.store !== this.portalStore) {
1802
- instanceState.store = this.portalStore;
1470
+ createRenderer(hostElement, type) {
1471
+ const delegateRenderer = this.delegateRendererFactory.createRenderer(hostElement, type);
1472
+ if (!type)
1473
+ return delegateRenderer;
1474
+ let renderer = this.rendererMap.get(type.id);
1475
+ if (renderer) {
1476
+ if (renderer instanceof NgtRenderer2) {
1477
+ renderer.count += 1;
1478
+ if (renderer.delegateRenderer !== delegateRenderer) {
1479
+ renderer.delegateRenderer = delegateRenderer;
1480
+ }
1803
1481
  }
1804
- this.portalStore.update(restState, mergeState(this.previousStore, this.portalStore, container, this.portalStore.snapshot.pointer, this.portalStore.snapshot.raycaster, events, size));
1805
- if (this.portalViewRef) {
1806
- this.portalViewRef.detectChanges();
1807
- return;
1482
+ return renderer;
1483
+ }
1484
+ if (hostElement && !isRendererNode(hostElement)) {
1485
+ createRendererNode('platform', hostElement, this.document);
1486
+ }
1487
+ if (Reflect.get(type, 'type')?.[NGT_HTML_FLAG]) {
1488
+ this.rendererMap.set(type.id, delegateRenderer);
1489
+ // patch delegate destroyNode so we can destroy this HTML node
1490
+ // TODO: make sure we really need to do this
1491
+ const originalDestroyNode = delegateRenderer.destroyNode?.bind(delegateRenderer);
1492
+ if (!originalDestroyNode || !(NGT_DELEGATE_RENDERER_DESTROY_NODE_PATCHED_FLAG in originalDestroyNode)) {
1493
+ delegateRenderer.destroyNode = (node) => {
1494
+ originalDestroyNode?.(node);
1495
+ if (node !== hostElement)
1496
+ return;
1497
+ internalDestroyNode(node, null);
1498
+ };
1499
+ Object.assign(delegateRenderer.destroyNode, {
1500
+ [NGT_DELEGATE_RENDERER_DESTROY_NODE_PATCHED_FLAG]: true,
1501
+ });
1808
1502
  }
1809
- this.portalViewRef = anchor.createEmbeddedView(content, { injector: this.injector }, { injector: this.injector });
1810
- this.portalViewRef.detectChanges();
1811
- this.portalContentRendered.set(true);
1812
- });
1503
+ return delegateRenderer;
1504
+ }
1505
+ this.rendererMap.set(type.id, (renderer = new NgtRenderer2(delegateRenderer, this.catalogue, this.document, this.options)));
1506
+ return renderer;
1813
1507
  }
1814
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtPortalImpl, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1815
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.11", type: NgtPortalImpl, isStandalone: true, selector: "ngt-portal", inputs: { container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: true, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
1816
- {
1817
- provide: NGT_STORE,
1818
- useFactory: (previousStore) => {
1819
- const pointer = new THREE.Vector2();
1820
- const raycaster = new THREE.Raycaster();
1821
- const { id: _skipId, ...previousState } = previousStore.snapshot;
1822
- const store = signalState({
1823
- id: makeId(),
1824
- ...previousState,
1825
- scene: null,
1826
- previousRoot: previousStore,
1827
- pointer,
1828
- raycaster,
1829
- });
1830
- store.update(mergeState(previousStore, store, null, pointer, raycaster));
1831
- return store;
1832
- },
1833
- deps: [[new SkipSelf(), NGT_STORE]],
1834
- },
1835
- ], queries: [{ propertyName: "contentRef", first: true, predicate: NgtPortalContent, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "anchorRef", first: true, predicate: NgtPortalContent, descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: `
1836
- @if (portalRendered()) {
1837
- <!-- Without an element that receives pointer events state.pointer will always be 0/0 -->
1838
- <ngt-group (pointerover)="(undefined)" attach="none" />
1839
- }
1840
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1508
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtRendererFactory2, deps: [{ token: i0.RendererFactory2 }], target: i0.ɵɵFactoryTarget.Injectable }); }
1509
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtRendererFactory2 }); }
1841
1510
  }
1842
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtPortalImpl, decorators: [{
1843
- type: Component,
1844
- args: [{
1845
- selector: 'ngt-portal',
1846
- template: `
1847
- @if (portalRendered()) {
1848
- <!-- Without an element that receives pointer events state.pointer will always be 0/0 -->
1849
- <ngt-group (pointerover)="(undefined)" attach="none" />
1850
- }
1851
- `,
1852
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
1853
- changeDetection: ChangeDetectionStrategy.OnPush,
1854
- providers: [
1855
- {
1856
- provide: NGT_STORE,
1857
- useFactory: (previousStore) => {
1858
- const pointer = new THREE.Vector2();
1859
- const raycaster = new THREE.Raycaster();
1860
- const { id: _skipId, ...previousState } = previousStore.snapshot;
1861
- const store = signalState({
1862
- id: makeId(),
1863
- ...previousState,
1864
- scene: null,
1865
- previousRoot: previousStore,
1866
- pointer,
1867
- raycaster,
1868
- });
1869
- store.update(mergeState(previousStore, store, null, pointer, raycaster));
1870
- return store;
1871
- },
1872
- deps: [[new SkipSelf(), NGT_STORE]],
1873
- },
1874
- ],
1875
- }]
1876
- }], ctorParameters: () => [] });
1877
- const NgtPortal = [NgtPortalImpl, NgtPortalContent];
1878
-
1879
- // This function prepares a set of changes to be applied to the instance
1880
- function diffProps(instance, props) {
1881
- const changes = [];
1882
- for (const propKey in props) {
1883
- const propValue = props[propKey];
1884
- let key = propKey;
1885
- if (is.colorSpaceExist(instance)) {
1886
- if (propKey === 'encoding') {
1887
- key = 'colorSpace';
1888
- }
1889
- else if (propKey === 'outputEncoding') {
1890
- key = 'outputColorSpace';
1891
- }
1511
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtRendererFactory2, decorators: [{
1512
+ type: Injectable
1513
+ }], ctorParameters: () => [{ type: i0.RendererFactory2 }] });
1514
+ class NgtRenderer2 {
1515
+ constructor(delegateRenderer, catalogue, document, options, count = 1) {
1516
+ this.delegateRenderer = delegateRenderer;
1517
+ this.catalogue = catalogue;
1518
+ this.document = document;
1519
+ this.options = options;
1520
+ this.count = count;
1521
+ this.argsInjectors = [];
1522
+ this.parentInjectors = [];
1523
+ this.destroyNode = (node) => {
1524
+ internalDestroyNode(node, this.removeChild.bind(this));
1525
+ };
1526
+ this.addClass = this.delegateRenderer.addClass.bind(this.delegateRenderer);
1527
+ this.removeClass = this.delegateRenderer.removeClass.bind(this.delegateRenderer);
1528
+ this.setStyle = this.delegateRenderer.setStyle.bind(this.delegateRenderer);
1529
+ this.removeStyle = this.delegateRenderer.removeStyle.bind(this.delegateRenderer);
1530
+ this.selectRootElement = this.delegateRenderer.selectRootElement.bind(this.delegateRenderer);
1531
+ this.nextSibling = this.delegateRenderer.nextSibling.bind(this.delegateRenderer);
1532
+ this.setValue = this.delegateRenderer.setValue.bind(this.delegateRenderer);
1533
+ if (!this.options.verbose) {
1534
+ this.options.verbose = false;
1892
1535
  }
1893
- if (is.equ(propValue, instance[key]))
1894
- continue;
1895
- changes.push([propKey, propValue]);
1896
1536
  }
1897
- return changes;
1898
- }
1899
- // NOTE: this is a workaround to give the instance a chance to have the store from the parent.
1900
- // we clear this property after the applyProps is done
1901
- const NGT_APPLY_PROPS = '__ngt_apply_props__';
1902
- // https://github.com/mrdoob/three.js/pull/27042
1903
- // https://github.com/mrdoob/three.js/pull/22748
1904
- const colorMaps = ['map', 'emissiveMap', 'sheenColorMap', 'specularColorMap', 'envMap'];
1905
- function resolveInstanceKey(instance, key) {
1906
- let targetProp = instance[key];
1907
- if (!key.includes('.'))
1908
- return { root: instance, targetKey: key, targetProp };
1909
- // Resolve pierced target
1910
- targetProp = instance;
1911
- for (const part of key.split('.')) {
1912
- key = part;
1913
- instance = targetProp;
1914
- targetProp = targetProp[key];
1537
+ get data() {
1538
+ return { ...this.delegateRenderer.data, __ngt_renderer__: true };
1915
1539
  }
1916
- // const chain = key.split('.');
1917
- // targetProp = chain.reduce((acc, part) => acc[part], instance);
1918
- // const targetKey = chain.pop()!;
1919
- //
1920
- // // Switch root if atomic
1921
- // if (!targetProp?.set) instance = chain.reduce((acc, part) => acc[part], instance);
1922
- return { root: instance, targetKey: key, targetProp };
1923
- }
1924
- // This function applies a set of changes to the instance
1925
- function applyProps(instance, props) {
1926
- // if props is empty
1927
- if (!Object.keys(props).length)
1928
- return instance;
1929
- const localState = getInstanceState(instance);
1930
- const rootState = localState?.store?.snapshot ?? instance[NGT_APPLY_PROPS]?.snapshot ?? {};
1931
- const changes = diffProps(instance, props);
1932
- for (let i = 0; i < changes.length; i++) {
1933
- let [key, value] = changes[i];
1934
- // Ignore setting undefined props
1935
- // https://github.com/pmndrs/react-three-fiber/issues/274
1936
- if (value === undefined)
1937
- continue;
1938
- // Alias (output)encoding => (output)colorSpace (since r152)
1939
- // https://github.com/pmndrs/react-three-fiber/pull/2829
1940
- // if (is.colorSpaceExist(instance)) {
1941
- // const sRGBEncoding = 3001;
1942
- // const SRGBColorSpace = 'srgb';
1943
- // const LinearSRGBColorSpace = 'srgb-linear';
1944
- //
1945
- // if (key === 'encoding') {
1946
- // key = 'colorSpace';
1947
- // value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
1948
- // } else if (key === 'outputEncoding') {
1949
- // key = 'outputColorSpace';
1950
- // value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
1951
- // }
1952
- // }
1953
- const { root, targetKey, targetProp } = resolveInstanceKey(instance, key);
1954
- // we have switched due to pierced props
1955
- if (root !== instance) {
1956
- return applyProps(root, { [targetKey]: value });
1540
+ destroy() {
1541
+ if (this.count > 1) {
1542
+ this.count -= 1;
1543
+ return;
1957
1544
  }
1958
- // Layers have no copy function, we must therefore copy the mask property
1959
- if (targetProp instanceof THREE.Layers && value instanceof THREE.Layers) {
1960
- targetProp.mask = value.mask;
1545
+ // this is the last instance of the same NgtRenderer2
1546
+ this.count = 0;
1547
+ this.argsInjectors = [];
1548
+ this.parentInjectors = [];
1549
+ }
1550
+ createElement(name, namespace) {
1551
+ const platformElement = this.delegateRenderer.createElement(name, namespace);
1552
+ if (name === 'ngt-portal') {
1553
+ return createRendererNode('portal', platformElement, this.document);
1961
1554
  }
1962
- else if (is.three(targetProp, 'isColor') && is.colorRepresentation(value)) {
1963
- targetProp.set(value);
1555
+ if (name === 'ngt-value') {
1556
+ return createRendererNode('three', prepare(platformElement, 'ngt-value'), this.document);
1964
1557
  }
1965
- // Copy if properties match signatures
1966
- else if (targetProp &&
1967
- typeof targetProp.set === 'function' &&
1968
- typeof targetProp.copy === 'function' &&
1969
- value?.constructor &&
1970
- targetProp.constructor === value.constructor) {
1971
- // If both are geometries, we should assign the value directly instead of copying
1972
- if (is.three(targetProp, 'isBufferGeometry') &&
1973
- is.three(value, 'isBufferGeometry')) {
1974
- Object.assign(root, { [targetKey]: value });
1558
+ const [injectedArgs, injectedParent] = [
1559
+ this.getNgtDirective(NgtArgs, this.argsInjectors)?.value || [],
1560
+ this.getNgtDirective(NgtParent, this.parentInjectors)?.value,
1561
+ ];
1562
+ if (name === 'ngt-primitive') {
1563
+ if (!injectedArgs[0])
1564
+ throw new Error(`[NGT] ngt-primitive without args is invalid`);
1565
+ const object = injectedArgs[0];
1566
+ let instanceState = getInstanceState(object);
1567
+ if (!instanceState || instanceState.type !== 'ngt-primitive') {
1568
+ // if an object isn't already "prepared", we'll prepare it
1569
+ prepare(object, 'ngt-primitive', instanceState);
1975
1570
  }
1976
- else {
1977
- targetProp.copy(value);
1571
+ const primitiveRendererNode = createRendererNode('three', object, this.document);
1572
+ if (injectedParent) {
1573
+ primitiveRendererNode.__ngt_renderer__[5 /* NgtRendererClassId.parent */] =
1574
+ injectedParent;
1978
1575
  }
1576
+ return primitiveRendererNode;
1979
1577
  }
1980
- // Set array types
1981
- else if (targetProp && typeof targetProp.set === 'function' && Array.isArray(value)) {
1982
- if (typeof targetProp.fromArray === 'function')
1983
- targetProp.fromArray(value);
1984
- else
1985
- targetProp.set(...value);
1986
- }
1987
- // Set literal types
1988
- else if (targetProp && typeof targetProp.set === 'function' && typeof value !== 'object') {
1989
- const isColor = is.three(targetProp, 'isColor');
1990
- // Allow setting array scalars
1991
- if (!isColor && typeof targetProp.setScalar === 'function' && typeof value === 'number')
1992
- targetProp.setScalar(value);
1993
- // Otherwise just set single value
1994
- else
1995
- targetProp.set(value);
1578
+ if (!name.startsWith('ngt-')) {
1579
+ return createRendererNode('platform', platformElement, this.document);
1996
1580
  }
1997
- // Else, just overwrite the value
1998
- else {
1999
- Object.assign(root, { [targetKey]: value });
2000
- // Auto-convert sRGB texture parameters for built-in materials
2001
- // https://github.com/pmndrs/react-three-fiber/issues/344
2002
- // https://github.com/mrdoob/three.js/pull/25857
2003
- if (rootState &&
2004
- !rootState.linear &&
2005
- colorMaps.includes(targetKey) &&
2006
- root[targetKey]?.isTexture &&
2007
- // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
2008
- root[targetKey].format === THREE.RGBAFormat &&
2009
- root[targetKey].type === THREE.UnsignedByteType) {
2010
- // NOTE: this cannot be set from the renderer (e.g. sRGB source textures rendered to P3)
2011
- root[targetKey].colorSpace = THREE.SRGBColorSpace;
1581
+ const threeName = kebabToPascal(name.startsWith('ngt-') ? name.slice(4) : name);
1582
+ let threeTarget = this.catalogue[threeName];
1583
+ if (!threeTarget && threeName in THREE) {
1584
+ const threeSymbol = THREE[threeName];
1585
+ if (typeof threeSymbol === 'function') {
1586
+ // we will attempt to prefill the catalogue with symbols from THREE
1587
+ threeTarget = this.catalogue[threeName] = threeSymbol;
2012
1588
  }
2013
1589
  }
2014
- checkUpdate(root[targetKey]);
2015
- checkUpdate(targetProp);
2016
- invalidateInstance(instance);
2017
- }
2018
- const instanceHandlersCount = localState?.eventCount;
2019
- const parent = localState?.hierarchyStore?.snapshot.parent;
2020
- if (parent && rootState.internal && instance['raycast'] && instanceHandlersCount !== localState?.eventCount) {
2021
- // Pre-emptively remove the instance from the interaction manager
2022
- const index = rootState.internal.interaction.indexOf(instance);
2023
- if (index > -1)
2024
- rootState.internal.interaction.splice(index, 1);
2025
- // Add the instance to the interaction manager only when it has handlers
2026
- if (localState?.eventCount)
2027
- rootState.internal.interaction.push(instance);
2028
- }
2029
- if (parent && localState?.onUpdate && changes.length) {
2030
- localState.onUpdate(instance);
2031
- }
2032
- // clearing the intermediate store from the instance
2033
- if (instance[NGT_APPLY_PROPS])
2034
- delete instance[NGT_APPLY_PROPS];
2035
- return instance;
2036
- }
2037
-
2038
- function isRendererNode(node) {
2039
- return !!node && typeof node === 'object' && NGT_RENDERER_NODE_FLAG in node;
2040
- }
2041
- function createRendererNode(type, node, document) {
2042
- const state = [type, false, undefined, undefined, undefined, undefined, []];
2043
- const rendererNode = Object.assign(node, { [NGT_RENDERER_NODE_FLAG]: state });
2044
- // NOTE: assign ownerDocument to node so we can use HostListener in Component
2045
- if (!rendererNode['ownerDocument'])
2046
- rendererNode['ownerDocument'] = document;
2047
- // NOTE: Angular SSR calls `node.getAttribute()` to retrieve hydration info on a node
2048
- if (!('getAttribute' in rendererNode) || typeof rendererNode['getAttribute'] !== 'function') {
2049
- const getNodeAttribute = (name) => rendererNode[name];
2050
- getNodeAttribute[NGT_GET_NODE_ATTRIBUTE_FLAG] = true;
2051
- Object.defineProperty(rendererNode, 'getAttribute', { value: getNodeAttribute, configurable: true });
2052
- }
2053
- return rendererNode;
2054
- }
2055
- function setRendererParentNode(node, parent) {
2056
- if (!node.__ngt_renderer__[5 /* NgtRendererClassId.parent */]) {
2057
- node.__ngt_renderer__[5 /* NgtRendererClassId.parent */] = parent;
2058
- }
2059
- }
2060
- function addRendererChildNode(node, child) {
2061
- if (!node.__ngt_renderer__[6 /* NgtRendererClassId.children */].includes(child)) {
2062
- node.__ngt_renderer__[6 /* NgtRendererClassId.children */].push(child);
2063
- }
2064
- }
2065
-
2066
- function attach(object, value, paths = [], useApplyProps = false) {
2067
- const [base, ...remaining] = paths;
2068
- if (!base)
2069
- return;
2070
- if (remaining.length === 0) {
2071
- if (useApplyProps)
2072
- applyProps(object, { [base]: value });
2073
- else
2074
- object[base] = value;
2075
- }
2076
- else {
2077
- assignEmpty(object, base, useApplyProps);
2078
- attach(object[base], value, remaining, useApplyProps);
1590
+ if (threeTarget) {
1591
+ const threeInstance = prepare(new threeTarget(...injectedArgs), name);
1592
+ const rendererNode = createRendererNode('three', threeInstance, this.document);
1593
+ // assert type here because it is just created so we don't have to null check it
1594
+ const instanceState = getInstanceState(threeInstance);
1595
+ // auto-attach for geometry and material
1596
+ if (is.three(threeInstance, 'isBufferGeometry')) {
1597
+ instanceState.attach = ['geometry'];
1598
+ }
1599
+ else if (is.three(threeInstance, 'isMaterial')) {
1600
+ instanceState.attach = ['material'];
1601
+ }
1602
+ if (injectedParent) {
1603
+ rendererNode.__ngt_renderer__[5 /* NgtRendererClassId.parent */] =
1604
+ injectedParent;
1605
+ }
1606
+ return rendererNode;
1607
+ }
1608
+ return createRendererNode('platform', platformElement, this.document);
2079
1609
  }
2080
- }
2081
- function detach(parent, child, attachProp) {
2082
- const childInstanceState = getInstanceState(child);
2083
- if (childInstanceState) {
2084
- if (Array.isArray(attachProp))
2085
- attach(parent, childInstanceState.previousAttach, attachProp, childInstanceState.type === 'ngt-value');
2086
- else
2087
- childInstanceState.previousAttach?.();
1610
+ createComment(value) {
1611
+ const commentNode = this.delegateRenderer.createComment(value);
1612
+ const commentRendererNode = createRendererNode('comment', commentNode, this.document);
1613
+ // NOTE: we attach an arrow function to the Comment node
1614
+ // In our directives, we can call this function to then start tracking the RendererNode
1615
+ // this is done to limit the amount of Nodes we need to process for getCreationState
1616
+ Object.assign(commentRendererNode, {
1617
+ [NGT_INTERNAL_ADD_COMMENT_FLAG]: (type, injector) => {
1618
+ if (type === 'args') {
1619
+ this.argsInjectors.push(injector);
1620
+ }
1621
+ else if (type === 'parent') {
1622
+ Object.assign(commentRendererNode, {
1623
+ [NGT_INTERNAL_SET_PARENT_COMMENT_FLAG]: (ngtParent) => {
1624
+ commentRendererNode.__ngt_renderer__[5 /* NgtRendererClassId.parent */] = ngtParent;
1625
+ },
1626
+ });
1627
+ this.parentInjectors.push(injector);
1628
+ }
1629
+ commentRendererNode.__ngt_renderer__[4 /* NgtRendererClassId.injector */] = injector;
1630
+ },
1631
+ });
1632
+ return commentRendererNode;
2088
1633
  }
2089
- }
2090
- function assignEmpty(obj, base, shouldAssignStoreForApplyProps = false) {
2091
- if ((!Object.hasOwn(obj, base) && Reflect && !!Reflect.has && !Reflect.has(obj, base)) || obj[base] === undefined) {
2092
- obj[base] = {};
1634
+ createText(value) {
1635
+ const textNode = this.delegateRenderer.createText(value);
1636
+ return createRendererNode('text', textNode, this.document);
2093
1637
  }
2094
- if (shouldAssignStoreForApplyProps) {
2095
- const instanceState = getInstanceState(obj[base]);
2096
- // if we already have instance state, bail out
2097
- if (instanceState)
2098
- return;
2099
- const parentInstanceState = getInstanceState(obj);
2100
- // if parent doesn't have instance state, bail out
2101
- if (!parentInstanceState)
1638
+ appendChild(parent, newChild, refChild, isMove) {
1639
+ const delegatedFn = refChild
1640
+ ? this.delegateRenderer.insertBefore.bind(this.delegateRenderer, parent, newChild, refChild, isMove)
1641
+ : this.delegateRenderer.appendChild.bind(this.delegateRenderer, parent, newChild);
1642
+ const pRS = parent.__ngt_renderer__;
1643
+ const cRS = newChild.__ngt_renderer__;
1644
+ if (!pRS || !cRS) {
1645
+ this.options.verbose &&
1646
+ console.warn('[NGT dev mode] One of parent or child is not a renderer node.', { parent, newChild });
1647
+ return delegatedFn();
1648
+ }
1649
+ if (cRS[0 /* NgtRendererClassId.type */] === 'comment') {
1650
+ // if child is a comment, we'll set the parent then bail.
1651
+ // comment usually means it's part of a templateRef ViewContainerRef or structural directive
1652
+ setRendererParentNode(newChild, parent);
1653
+ // if parent is not three, we'll delegate to the renderer
1654
+ if (pRS[0 /* NgtRendererClassId.type */] !== 'three') {
1655
+ delegatedFn();
1656
+ }
2102
1657
  return;
2103
- Object.assign(obj[base], { [NGT_APPLY_PROPS]: parentInstanceState.store });
2104
- }
2105
- }
2106
- function createAttachFunction(cb) {
2107
- return (parent, child, store) => cb({ parent, child, store });
2108
- }
2109
-
2110
- function kebabToPascal(str) {
2111
- if (!str)
2112
- return str; // Handle empty input
2113
- let pascalStr = '';
2114
- let capitalizeNext = true; // Flag to track capitalization
2115
- for (let i = 0; i < str.length; i++) {
2116
- const char = str[i];
2117
- if (char === '-') {
2118
- capitalizeNext = true;
2119
- continue;
2120
1658
  }
2121
- pascalStr += capitalizeNext ? char.toUpperCase() : char;
2122
- capitalizeNext = false;
2123
- }
2124
- return pascalStr;
2125
- }
2126
- function propagateStoreRecursively(node, parentNode) {
2127
- const iS = getInstanceState(node);
2128
- const pIS = getInstanceState(parentNode);
2129
- if (!iS || !pIS)
2130
- return;
2131
- // assign store on child if not already exist
2132
- // or child store is not the same as parent store
2133
- // or child store is the parent of parent store
2134
- if (!iS.store || iS.store !== pIS.store || iS.store === pIS.store.snapshot.previousRoot) {
2135
- iS.store = pIS.store;
2136
- // Call addInteraction if it exists
2137
- iS.addInteraction?.(pIS.store);
2138
- // Collect all children (objects and nonObjects)
2139
- const children = [
2140
- ...(iS.objects ? untracked(iS.objects) : []),
2141
- ...(iS.nonObjects ? untracked(iS.nonObjects) : []),
2142
- ];
2143
- // Recursively reassign the store for each child
2144
- for (const child of children) {
2145
- propagateStoreRecursively(child, node);
1659
+ if (pRS[0 /* NgtRendererClassId.type */] === 'platform' && cRS[0 /* NgtRendererClassId.type */] === 'platform') {
1660
+ if (newChild[NGT_DOM_PARENT_FLAG] && newChild[NGT_DOM_PARENT_FLAG] instanceof HTMLElement) {
1661
+ return this.delegateRenderer.appendChild(newChild[NGT_DOM_PARENT_FLAG], newChild);
1662
+ }
1663
+ if (pRS[5 /* NgtRendererClassId.parent */] && !cRS[5 /* NgtRendererClassId.parent */]) {
1664
+ return this.appendChild(pRS[5 /* NgtRendererClassId.parent */], newChild);
1665
+ }
1666
+ return delegatedFn();
2146
1667
  }
2147
- }
2148
- }
2149
- function attachThreeNodes(parent, child) {
2150
- const pIS = getInstanceState(parent);
2151
- const cIS = getInstanceState(child);
2152
- if (!pIS || !cIS) {
2153
- throw new Error(`[NGT] THREE instances need to be prepared with local state.`);
2154
- }
2155
- // whether the child is added to the parent with parent.add()
2156
- let added = false;
2157
- // propagate store recursively
2158
- propagateStoreRecursively(child, parent);
2159
- if (cIS.attach) {
2160
- const attachProp = cIS.attach;
2161
- if (typeof attachProp === 'function') {
2162
- let attachCleanUp = undefined;
2163
- if (cIS.type === 'ngt-value') {
2164
- if (cIS.hierarchyStore.snapshot.parent !== parent) {
2165
- cIS.setParent(parent);
2166
- }
2167
- // at this point we don't have rawValue yet, so we bail and wait until the Renderer recalls attach
2168
- if (child.__ngt_renderer__[2 /* NgtRendererClassId.rawValue */] === undefined)
2169
- return;
2170
- attachCleanUp = attachProp(parent, child.__ngt_renderer__[2 /* NgtRendererClassId.rawValue */], cIS.store);
1668
+ if (pRS[0 /* NgtRendererClassId.type */] === 'three' && cRS[0 /* NgtRendererClassId.type */] === 'three') {
1669
+ return this.appendThreeRendererNodes(parent, newChild);
1670
+ }
1671
+ if (pRS[0 /* NgtRendererClassId.type */] === 'platform' && cRS[0 /* NgtRendererClassId.type */] === 'three') {
1672
+ // if platform has parent, delegate to that parent
1673
+ if (pRS[5 /* NgtRendererClassId.parent */]) {
1674
+ // but track the child for this parent as well
1675
+ addRendererChildNode(parent, newChild);
1676
+ return this.appendChild(pRS[5 /* NgtRendererClassId.parent */], newChild);
2171
1677
  }
2172
- else {
2173
- attachCleanUp = attachProp(parent, child, cIS.store);
1678
+ // platform can also have normal parentNode
1679
+ const platformParentNode = this.delegateRenderer.parentNode(parent);
1680
+ if (platformParentNode) {
1681
+ return this.appendChild(platformParentNode, newChild);
2174
1682
  }
2175
- if (attachCleanUp)
2176
- cIS.previousAttach = attachCleanUp;
1683
+ // if not, set up parent and child relationship for this pair then bail
1684
+ this.setNodeRelationship(parent, newChild);
1685
+ return;
2177
1686
  }
2178
- else {
2179
- // we skip attach none if set explicitly
2180
- if (attachProp[0] === 'none') {
2181
- invalidateInstance(child);
2182
- return;
1687
+ if (pRS[0 /* NgtRendererClassId.type */] === 'three' && cRS[0 /* NgtRendererClassId.type */] === 'platform') {
1688
+ if (!cRS[5 /* NgtRendererClassId.parent */]) {
1689
+ setRendererParentNode(newChild, parent);
2183
1690
  }
2184
- // handle material array
2185
- if (attachProp[0] === 'material' &&
2186
- attachProp[1] &&
2187
- typeof Number(attachProp[1]) === 'number' &&
2188
- is.three(child, 'isMaterial') &&
2189
- !Array.isArray(parent['material'])) {
2190
- parent['material'] = [];
1691
+ for (const child of cRS[6 /* NgtRendererClassId.children */]) {
1692
+ this.appendChild(parent, child);
2191
1693
  }
2192
- if (cIS.type === 'ngt-value') {
2193
- if (cIS.hierarchyStore.snapshot.parent !== parent) {
2194
- cIS.setParent(parent);
2195
- }
2196
- // at this point we don't have rawValue yet, so we bail and wait until the Renderer recalls attach
2197
- if (child.__ngt_renderer__[2 /* NgtRendererClassId.rawValue */] === undefined)
2198
- return;
2199
- // save prev value
2200
- cIS.previousAttach = attachProp.reduce((value, key) => value[key], parent);
2201
- attach(parent, child.__ngt_renderer__[2 /* NgtRendererClassId.rawValue */], attachProp, true);
2202
- }
2203
- else {
2204
- // save prev value
2205
- cIS.previousAttach = attachProp.reduce((value, key) => value[key], parent);
2206
- attach(parent, child, attachProp);
1694
+ for (const platformChildNode of newChild['childNodes'] || []) {
1695
+ if (!isRendererNode(platformChildNode) ||
1696
+ platformChildNode.__ngt_renderer__[0 /* NgtRendererClassId.type */] !== 'platform')
1697
+ continue;
1698
+ this.appendChild(parent, platformChildNode);
2207
1699
  }
1700
+ return;
2208
1701
  }
2209
- }
2210
- else if (is.three(parent, 'isObject3D') && is.three(child, 'isObject3D')) {
2211
- parent.add(child);
2212
- added = true;
2213
- cIS.addInteraction?.(cIS.store || pIS.store);
2214
- }
2215
- if (pIS.add) {
2216
- pIS.add(child, added ? 'objects' : 'nonObjects');
2217
- }
2218
- if (cIS.parent && untracked(cIS.parent) !== parent) {
2219
- cIS.setParent(parent);
2220
- }
2221
- // NOTE: this does not mean that the child is actually attached to the parent on the scenegraph.
2222
- // a child on the Angular template can also emit onAttach
2223
- if (cIS.onAttach)
2224
- cIS.onAttach({ parent, node: child });
2225
- invalidateInstance(child);
2226
- invalidateInstance(parent);
2227
- }
2228
- function removeThreeChild(child, parent, dispose) {
2229
- const pIS = getInstanceState(parent);
2230
- const cIS = getInstanceState(child);
2231
- // clear parent ref
2232
- cIS?.setParent(null);
2233
- // remove child from parent
2234
- pIS?.remove?.(child, 'objects');
2235
- pIS?.remove?.(child, 'nonObjects');
2236
- if (cIS?.attach) {
2237
- detach(parent, child, cIS.attach);
2238
- }
2239
- else if (is.three(parent, 'isObject3D') && is.three(child, 'isObject3D')) {
2240
- parent.remove(child);
2241
- const store = cIS?.store || pIS?.store;
2242
- cIS?.removeInteraction?.(store);
2243
- if (store)
2244
- removeInteractivity(store, child);
2245
- }
2246
- // dispose
2247
- const isPrimitive = cIS?.type && cIS.type !== 'ngt-primitive';
2248
- if (!isPrimitive && child['dispose'] && !is.three(child, 'isScene')) {
2249
- queueMicrotask(() => child['dispose']());
2250
- }
2251
- invalidateInstance(parent);
2252
- }
2253
- function internalDestroyNode(node, removeChild) {
2254
- const rS = node.__ngt_renderer__;
2255
- if (!rS || rS[1 /* NgtRendererClassId.destroyed */])
2256
- return;
2257
- for (const child of rS[6 /* NgtRendererClassId.children */].slice()) {
2258
- removeChild?.(node, child);
2259
- internalDestroyNode(child, removeChild);
2260
- }
2261
- // clear out parent if haven't
2262
- rS[5 /* NgtRendererClassId.parent */] = undefined;
2263
- // clear out children
2264
- rS[6 /* NgtRendererClassId.children */].length = 0;
2265
- // clear out NgtInstanceState
2266
- const iS = getInstanceState(node);
2267
- if (iS) {
2268
- const temp = iS;
2269
- iS.removeInteraction?.(iS.store);
2270
- delete temp['onAttach'];
2271
- delete temp['onUpdate'];
2272
- delete temp['object'];
2273
- delete temp['objects'];
2274
- delete temp['nonObjects'];
2275
- delete temp['parent'];
2276
- delete temp['add'];
2277
- delete temp['remove'];
2278
- delete temp['updateGeometryStamp'];
2279
- delete temp['setParent'];
2280
- delete temp['store'];
2281
- delete temp['handlers'];
2282
- delete temp['hierarchyStore'];
2283
- delete temp['previousAttach'];
2284
- delete temp['setPointerEvent'];
2285
- delete temp['addInteraction'];
2286
- delete temp['removeInteraction'];
2287
- if (iS.type !== 'ngt-primitive') {
2288
- delete node['__ngt__'];
2289
- }
2290
- }
2291
- // clear our debugNode
2292
- rS[4 /* NgtRendererClassId.injector */] = undefined;
2293
- if (rS[0 /* NgtRendererClassId.type */] === 'comment') {
2294
- delete node[NGT_INTERNAL_ADD_COMMENT_FLAG];
2295
- delete node[NGT_INTERNAL_SET_PARENT_COMMENT_FLAG];
2296
- delete node[NGT_CANVAS_CONTENT_FLAG];
2297
- delete node[NGT_PORTAL_CONTENT_FLAG];
2298
- delete node[NGT_DOM_PARENT_FLAG];
2299
- }
2300
- // clear getAttribute if exist
2301
- if ('getAttribute' in node &&
2302
- typeof node['getAttribute'] === 'function' &&
2303
- node['getAttribute'][NGT_GET_NODE_ATTRIBUTE_FLAG]) {
2304
- delete node['getAttribute'];
2305
- }
2306
- // mark node as destroyed
2307
- rS[1 /* NgtRendererClassId.destroyed */] = true;
2308
- }
2309
-
2310
- const NGT_RENDERER_OPTIONS = new InjectionToken('NGT_RENDERER_OPTIONS');
2311
- class NgtRendererFactory2 {
2312
- /**
2313
- * NOTE: We use `useFactory` to instantiate `NgtRendererFactory2`
2314
- */
2315
- constructor(delegateRendererFactory) {
2316
- this.delegateRendererFactory = delegateRendererFactory;
2317
- this.catalogue = injectCatalogue();
2318
- this.document = inject(DOCUMENT);
2319
- this.options = inject(NGT_RENDERER_OPTIONS, { optional: true }) || {};
2320
- this.rendererMap = new Map();
2321
- }
2322
- createRenderer(hostElement, type) {
2323
- const delegateRenderer = this.delegateRendererFactory.createRenderer(hostElement, type);
2324
- if (!type)
2325
- return delegateRenderer;
2326
- let renderer = this.rendererMap.get(type.id);
2327
- if (renderer) {
2328
- if (renderer instanceof NgtRenderer2) {
2329
- renderer.count += 1;
2330
- if (renderer.delegateRenderer !== delegateRenderer) {
2331
- renderer.delegateRenderer = delegateRenderer;
2332
- }
1702
+ if (pRS[0 /* NgtRendererClassId.type */] === 'portal' && cRS[0 /* NgtRendererClassId.type */] === 'three') {
1703
+ if (!cRS[5 /* NgtRendererClassId.parent */] && pRS[3 /* NgtRendererClassId.portalContainer */]) {
1704
+ return this.appendChild(pRS[3 /* NgtRendererClassId.portalContainer */], newChild);
2333
1705
  }
2334
- return renderer;
2335
- }
2336
- if (hostElement && !isRendererNode(hostElement)) {
2337
- createRendererNode('platform', hostElement, this.document);
1706
+ return;
2338
1707
  }
2339
- if (Reflect.get(type, 'type')?.[NGT_HTML_FLAG]) {
2340
- this.rendererMap.set(type.id, delegateRenderer);
2341
- // patch delegate destroyNode so we can destroy this HTML node
2342
- // TODO: make sure we really need to do this
2343
- const originalDestroyNode = delegateRenderer.destroyNode?.bind(delegateRenderer);
2344
- if (!originalDestroyNode || !(NGT_DELEGATE_RENDERER_DESTROY_NODE_PATCHED_FLAG in originalDestroyNode)) {
2345
- delegateRenderer.destroyNode = (node) => {
2346
- originalDestroyNode?.(node);
2347
- if (node !== hostElement)
2348
- return;
2349
- internalDestroyNode(node, null);
2350
- };
2351
- Object.assign(delegateRenderer.destroyNode, {
2352
- [NGT_DELEGATE_RENDERER_DESTROY_NODE_PATCHED_FLAG]: true,
2353
- });
2354
- }
2355
- return delegateRenderer;
1708
+ if (pRS[0 /* NgtRendererClassId.type */] === 'platform' && cRS[0 /* NgtRendererClassId.type */] === 'portal') {
1709
+ return this.delegateRenderer.appendChild(parent, newChild);
2356
1710
  }
2357
- this.rendererMap.set(type.id, (renderer = new NgtRenderer2(delegateRenderer, this.catalogue, this.document, this.options)));
2358
- return renderer;
1711
+ return delegatedFn();
2359
1712
  }
2360
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtRendererFactory2, deps: [{ token: i0.RendererFactory2 }], target: i0.ɵɵFactoryTarget.Injectable }); }
2361
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtRendererFactory2 }); }
2362
- }
2363
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtRendererFactory2, decorators: [{
2364
- type: Injectable
2365
- }], ctorParameters: () => [{ type: i0.RendererFactory2 }] });
2366
- class NgtRenderer2 {
2367
- constructor(delegateRenderer, catalogue, document, options, count = 1) {
2368
- this.delegateRenderer = delegateRenderer;
2369
- this.catalogue = catalogue;
2370
- this.document = document;
2371
- this.options = options;
2372
- this.count = count;
2373
- this.argsInjectors = [];
2374
- this.parentInjectors = [];
2375
- this.destroyNode = (node) => {
2376
- internalDestroyNode(node, this.removeChild.bind(this));
2377
- };
2378
- this.addClass = this.delegateRenderer.addClass.bind(this.delegateRenderer);
2379
- this.removeClass = this.delegateRenderer.removeClass.bind(this.delegateRenderer);
2380
- this.setStyle = this.delegateRenderer.setStyle.bind(this.delegateRenderer);
2381
- this.removeStyle = this.delegateRenderer.removeStyle.bind(this.delegateRenderer);
2382
- this.selectRootElement = this.delegateRenderer.selectRootElement.bind(this.delegateRenderer);
2383
- this.nextSibling = this.delegateRenderer.nextSibling.bind(this.delegateRenderer);
2384
- this.setValue = this.delegateRenderer.setValue.bind(this.delegateRenderer);
2385
- if (!this.options.verbose) {
2386
- this.options.verbose = false;
1713
+ insertBefore(parent, newChild, refChild, isMove) {
1714
+ // if both are comments and the reference child is NgtCanvasContent, we'll assign the same flag to the newChild
1715
+ // this means that the NgtCanvas component is embedding. This flag allows the Renderer to get the root scene
1716
+ // when it tries to attach the template under `ng-template[canvasContent]`
1717
+ if (refChild &&
1718
+ refChild[NGT_CANVAS_CONTENT_FLAG] &&
1719
+ refChild instanceof Comment &&
1720
+ newChild instanceof Comment) {
1721
+ Object.assign(newChild, { [NGT_CANVAS_CONTENT_FLAG]: refChild[NGT_CANVAS_CONTENT_FLAG] });
2387
1722
  }
2388
- }
2389
- get data() {
2390
- return { ...this.delegateRenderer.data, __ngt_renderer__: true };
2391
- }
2392
- destroy() {
2393
- if (this.count > 1) {
2394
- this.count -= 1;
2395
- return;
1723
+ // if there is no parent, we delegate
1724
+ if (!parent) {
1725
+ return this.delegateRenderer.insertBefore(parent, newChild, refChild, isMove);
2396
1726
  }
2397
- // this is the last instance of the same NgtRenderer2
2398
- this.count = 0;
2399
- this.argsInjectors = [];
2400
- this.parentInjectors = [];
1727
+ return this.appendChild(parent, newChild, refChild, isMove);
2401
1728
  }
2402
- createElement(name, namespace) {
2403
- const platformElement = this.delegateRenderer.createElement(name, namespace);
2404
- if (name === 'ngt-portal') {
2405
- return createRendererNode('portal', platformElement, this.document);
2406
- }
2407
- if (name === 'ngt-value') {
2408
- return createRendererNode('three', prepare(platformElement, 'ngt-value'), this.document);
1729
+ removeChild(parent, oldChild, isHostElement) {
1730
+ if (parent === null) {
1731
+ parent = this.parentNode(oldChild);
2409
1732
  }
2410
- const [injectedArgs, injectedParent] = [
2411
- this.getNgtDirective(NgtArgs, this.argsInjectors)?.value || [],
2412
- this.getNgtDirective(NgtParent, this.parentInjectors)?.value,
2413
- ];
2414
- if (name === 'ngt-primitive') {
2415
- if (!injectedArgs[0])
2416
- throw new Error(`[NGT] ngt-primitive without args is invalid`);
2417
- const object = injectedArgs[0];
2418
- let instanceState = getInstanceState(object);
2419
- if (!instanceState || instanceState.type !== 'ngt-primitive') {
2420
- // if an object isn't already "prepared", we'll prepare it
2421
- prepare(object, 'ngt-primitive', instanceState);
1733
+ const cRS = oldChild.__ngt_renderer__;
1734
+ if (!cRS) {
1735
+ try {
1736
+ return this.delegateRenderer.removeChild(parent, oldChild, isHostElement);
2422
1737
  }
2423
- const primitiveRendererNode = createRendererNode('three', object, this.document);
2424
- if (injectedParent) {
2425
- primitiveRendererNode.__ngt_renderer__[5 /* NgtRendererClassId.parent */] =
2426
- injectedParent;
2427
- }
2428
- return primitiveRendererNode;
2429
- }
2430
- if (!name.startsWith('ngt-')) {
2431
- return createRendererNode('platform', platformElement, this.document);
2432
- }
2433
- const threeName = kebabToPascal(name.startsWith('ngt-') ? name.slice(4) : name);
2434
- let threeTarget = this.catalogue[threeName];
2435
- if (!threeTarget && threeName in THREE) {
2436
- const threeSymbol = THREE[threeName];
2437
- if (typeof threeSymbol === 'function') {
2438
- // we will attempt to prefill the catalogue with symbols from THREE
2439
- threeTarget = this.catalogue[threeName] = threeSymbol;
2440
- }
2441
- }
2442
- if (threeTarget) {
2443
- const threeInstance = prepare(new threeTarget(...injectedArgs), name);
2444
- const rendererNode = createRendererNode('three', threeInstance, this.document);
2445
- // assert type here because it is just created so we don't have to null check it
2446
- const instanceState = getInstanceState(threeInstance);
2447
- // auto-attach for geometry and material
2448
- if (is.three(threeInstance, 'isBufferGeometry')) {
2449
- instanceState.attach = ['geometry'];
2450
- }
2451
- else if (is.three(threeInstance, 'isMaterial')) {
2452
- instanceState.attach = ['material'];
2453
- }
2454
- if (injectedParent) {
2455
- rendererNode.__ngt_renderer__[5 /* NgtRendererClassId.parent */] =
2456
- injectedParent;
2457
- }
2458
- return rendererNode;
2459
- }
2460
- return createRendererNode('platform', platformElement, this.document);
2461
- }
2462
- createComment(value) {
2463
- const commentNode = this.delegateRenderer.createComment(value);
2464
- const commentRendererNode = createRendererNode('comment', commentNode, this.document);
2465
- // NOTE: we attach an arrow function to the Comment node
2466
- // In our directives, we can call this function to then start tracking the RendererNode
2467
- // this is done to limit the amount of Nodes we need to process for getCreationState
2468
- Object.assign(commentRendererNode, {
2469
- [NGT_INTERNAL_ADD_COMMENT_FLAG]: (type, injector) => {
2470
- if (type === 'args') {
2471
- this.argsInjectors.push(injector);
2472
- }
2473
- else if (type === 'parent') {
2474
- Object.assign(commentRendererNode, {
2475
- [NGT_INTERNAL_SET_PARENT_COMMENT_FLAG]: (ngtParent) => {
2476
- commentRendererNode.__ngt_renderer__[5 /* NgtRendererClassId.parent */] = ngtParent;
2477
- },
2478
- });
2479
- this.parentInjectors.push(injector);
2480
- }
2481
- commentRendererNode.__ngt_renderer__[4 /* NgtRendererClassId.injector */] = injector;
2482
- },
2483
- });
2484
- return commentRendererNode;
2485
- }
2486
- createText(value) {
2487
- const textNode = this.delegateRenderer.createText(value);
2488
- return createRendererNode('text', textNode, this.document);
2489
- }
2490
- appendChild(parent, newChild, refChild, isMove) {
2491
- const delegatedFn = refChild
2492
- ? this.delegateRenderer.insertBefore.bind(this.delegateRenderer, parent, newChild, refChild, isMove)
2493
- : this.delegateRenderer.appendChild.bind(this.delegateRenderer, parent, newChild);
2494
- const pRS = parent.__ngt_renderer__;
2495
- const cRS = newChild.__ngt_renderer__;
2496
- if (!pRS || !cRS) {
2497
- this.options.verbose &&
2498
- console.warn('[NGT dev mode] One of parent or child is not a renderer node.', { parent, newChild });
2499
- return delegatedFn();
2500
- }
2501
- if (cRS[0 /* NgtRendererClassId.type */] === 'comment') {
2502
- // if child is a comment, we'll set the parent then bail.
2503
- // comment usually means it's part of a templateRef ViewContainerRef or structural directive
2504
- setRendererParentNode(newChild, parent);
2505
- // if parent is not three, we'll delegate to the renderer
2506
- if (pRS[0 /* NgtRendererClassId.type */] !== 'three') {
2507
- delegatedFn();
2508
- }
2509
- return;
2510
- }
2511
- if (pRS[0 /* NgtRendererClassId.type */] === 'platform' && cRS[0 /* NgtRendererClassId.type */] === 'platform') {
2512
- if (newChild[NGT_DOM_PARENT_FLAG] && newChild[NGT_DOM_PARENT_FLAG] instanceof HTMLElement) {
2513
- return this.delegateRenderer.appendChild(newChild[NGT_DOM_PARENT_FLAG], newChild);
2514
- }
2515
- if (pRS[5 /* NgtRendererClassId.parent */] && !cRS[5 /* NgtRendererClassId.parent */]) {
2516
- return this.appendChild(pRS[5 /* NgtRendererClassId.parent */], newChild);
2517
- }
2518
- return delegatedFn();
2519
- }
2520
- if (pRS[0 /* NgtRendererClassId.type */] === 'three' && cRS[0 /* NgtRendererClassId.type */] === 'three') {
2521
- return this.appendThreeRendererNodes(parent, newChild);
2522
- }
2523
- if (pRS[0 /* NgtRendererClassId.type */] === 'platform' && cRS[0 /* NgtRendererClassId.type */] === 'three') {
2524
- // if platform has parent, delegate to that parent
2525
- if (pRS[5 /* NgtRendererClassId.parent */]) {
2526
- // but track the child for this parent as well
2527
- addRendererChildNode(parent, newChild);
2528
- return this.appendChild(pRS[5 /* NgtRendererClassId.parent */], newChild);
2529
- }
2530
- // platform can also have normal parentNode
2531
- const platformParentNode = this.delegateRenderer.parentNode(parent);
2532
- if (platformParentNode) {
2533
- return this.appendChild(platformParentNode, newChild);
2534
- }
2535
- // if not, set up parent and child relationship for this pair then bail
2536
- this.setNodeRelationship(parent, newChild);
2537
- return;
2538
- }
2539
- if (pRS[0 /* NgtRendererClassId.type */] === 'three' && cRS[0 /* NgtRendererClassId.type */] === 'platform') {
2540
- if (!cRS[5 /* NgtRendererClassId.parent */]) {
2541
- setRendererParentNode(newChild, parent);
2542
- }
2543
- for (const child of cRS[6 /* NgtRendererClassId.children */]) {
2544
- this.appendChild(parent, child);
2545
- }
2546
- for (const platformChildNode of newChild['childNodes'] || []) {
2547
- if (!isRendererNode(platformChildNode) ||
2548
- platformChildNode.__ngt_renderer__[0 /* NgtRendererClassId.type */] !== 'platform')
2549
- continue;
2550
- this.appendChild(parent, platformChildNode);
2551
- }
2552
- return;
2553
- }
2554
- if (pRS[0 /* NgtRendererClassId.type */] === 'portal' && cRS[0 /* NgtRendererClassId.type */] === 'three') {
2555
- if (!cRS[5 /* NgtRendererClassId.parent */] && pRS[3 /* NgtRendererClassId.portalContainer */]) {
2556
- return this.appendChild(pRS[3 /* NgtRendererClassId.portalContainer */], newChild);
2557
- }
2558
- return;
2559
- }
2560
- if (pRS[0 /* NgtRendererClassId.type */] === 'platform' && cRS[0 /* NgtRendererClassId.type */] === 'portal') {
2561
- return this.delegateRenderer.appendChild(parent, newChild);
2562
- }
2563
- return delegatedFn();
2564
- }
2565
- insertBefore(parent, newChild, refChild, isMove) {
2566
- // if both are comments and the reference child is NgtCanvasContent, we'll assign the same flag to the newChild
2567
- // this means that the NgtCanvas component is embedding. This flag allows the Renderer to get the root scene
2568
- // when it tries to attach the template under `ng-template[canvasContent]`
2569
- if (refChild &&
2570
- refChild[NGT_CANVAS_CONTENT_FLAG] &&
2571
- refChild instanceof Comment &&
2572
- newChild instanceof Comment) {
2573
- Object.assign(newChild, { [NGT_CANVAS_CONTENT_FLAG]: refChild[NGT_CANVAS_CONTENT_FLAG] });
2574
- }
2575
- // if there is no parent, we delegate
2576
- if (!parent) {
2577
- return this.delegateRenderer.insertBefore(parent, newChild, refChild, isMove);
2578
- }
2579
- return this.appendChild(parent, newChild, refChild, isMove);
2580
- }
2581
- removeChild(parent, oldChild, isHostElement) {
2582
- if (parent === null) {
2583
- parent = this.parentNode(oldChild);
2584
- }
2585
- const cRS = oldChild.__ngt_renderer__;
2586
- if (!cRS) {
2587
- try {
2588
- return this.delegateRenderer.removeChild(parent, oldChild, isHostElement);
2589
- }
2590
- catch {
2591
- return;
1738
+ catch {
1739
+ return;
2592
1740
  }
2593
1741
  }
2594
1742
  // disassociate things from oldChild
@@ -2882,6 +2030,907 @@ class NgtRenderer2 {
2882
2030
  }
2883
2031
  }
2884
2032
 
2033
+ function storeFactory() {
2034
+ const { invalidate, advance } = injectLoop();
2035
+ const rendererOptions = inject(NGT_RENDERER_OPTIONS, { optional: true }) || {};
2036
+ const document = inject(DOCUMENT);
2037
+ const window = document.defaultView || undefined;
2038
+ // NOTE: using Subject because we do not care about late-subscribers
2039
+ const pointerMissed$ = new Subject();
2040
+ const position = new THREE.Vector3();
2041
+ const defaultTarget = new THREE.Vector3();
2042
+ const tempTarget = new THREE.Vector3();
2043
+ let performanceTimeout = undefined;
2044
+ const pointer = new THREE.Vector2();
2045
+ // getCurrentViewport will mutate this instead of creating a new object everytime
2046
+ const tempViewport = {
2047
+ width: 0,
2048
+ height: 0,
2049
+ top: 0,
2050
+ left: 0,
2051
+ factor: 1,
2052
+ distance: 0,
2053
+ aspect: 0,
2054
+ };
2055
+ const store = signalState({
2056
+ id: makeId(),
2057
+ maxNotificationSkipCount: rendererOptions.maxNotificationSkipCount || 5,
2058
+ pointerMissed$: pointerMissed$.asObservable(),
2059
+ events: { priority: 1, enabled: true, connected: false },
2060
+ // Mock objects that have to be configured
2061
+ gl: null,
2062
+ camera: null,
2063
+ raycaster: null,
2064
+ scene: null,
2065
+ xr: null,
2066
+ invalidate: (frames = 1) => invalidate(store, frames),
2067
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, store),
2068
+ legacy: false,
2069
+ linear: false,
2070
+ flat: false,
2071
+ controls: null,
2072
+ clock: new THREE.Clock(),
2073
+ pointer,
2074
+ frameloop: 'always',
2075
+ performance: {
2076
+ current: 1,
2077
+ min: 0.5,
2078
+ max: 1,
2079
+ debounce: 200,
2080
+ regress: () => {
2081
+ const state = store.snapshot;
2082
+ // Clear timeout
2083
+ if (performanceTimeout)
2084
+ clearTimeout(performanceTimeout);
2085
+ // Set lower bound performance
2086
+ if (state.performance.current !== state.performance.min)
2087
+ store.update((state) => ({
2088
+ performance: { ...state.performance, current: state.performance.min },
2089
+ }));
2090
+ // Go back to upper bound performance after a while unless something regresses meanwhile
2091
+ performanceTimeout = setTimeout(() => store.update((state) => ({
2092
+ performance: { ...state.performance, current: store.snapshot.performance.max },
2093
+ })), state.performance.debounce);
2094
+ },
2095
+ },
2096
+ size: { width: 0, height: 0, top: 0, left: 0 },
2097
+ viewport: {
2098
+ initialDpr: window?.devicePixelRatio || 1,
2099
+ dpr: window?.devicePixelRatio || 1,
2100
+ width: 0,
2101
+ height: 0,
2102
+ top: 0,
2103
+ left: 0,
2104
+ aspect: 0,
2105
+ distance: 0,
2106
+ factor: 0,
2107
+ getCurrentViewport(camera = store.snapshot.camera, target = defaultTarget, size = store.snapshot.size) {
2108
+ const { width, height, top, left } = size;
2109
+ const aspect = width / height;
2110
+ if (target.isVector3)
2111
+ tempTarget.copy(target);
2112
+ else
2113
+ tempTarget.set(...target);
2114
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
2115
+ // Update the pre-allocated viewport object
2116
+ tempViewport.top = top;
2117
+ tempViewport.left = left;
2118
+ tempViewport.aspect = aspect;
2119
+ tempViewport.distance = distance;
2120
+ if (is.three(camera, 'isOrthographicCamera')) {
2121
+ // For orthographic cameras
2122
+ tempViewport.width = width / camera.zoom;
2123
+ tempViewport.height = height / camera.zoom;
2124
+ tempViewport.factor = 1;
2125
+ }
2126
+ else {
2127
+ // For perspective cameras
2128
+ const fov = (camera.fov * Math.PI) / 180; // convert vertical fov to radians
2129
+ const h = 2 * Math.tan(fov / 2) * distance; // visible height
2130
+ const w = h * aspect; // visible width
2131
+ tempViewport.width = w;
2132
+ tempViewport.height = h;
2133
+ tempViewport.factor = width / w;
2134
+ }
2135
+ return tempViewport;
2136
+ },
2137
+ },
2138
+ setEvents: (events) => store.update((state) => ({ events: { ...state.events, ...events } })),
2139
+ setSize: (width, height, top, left) => {
2140
+ const camera = store.snapshot.camera;
2141
+ const size = { width, height, top: top ?? 0, left: left ?? 0 };
2142
+ store.update((state) => ({
2143
+ size,
2144
+ viewport: {
2145
+ ...state.viewport,
2146
+ ...state.viewport.getCurrentViewport(camera, defaultTarget, size),
2147
+ },
2148
+ }));
2149
+ },
2150
+ setDpr: (dpr) => {
2151
+ const resolved = makeDpr(dpr, window);
2152
+ store.update((state) => ({
2153
+ viewport: { ...state.viewport, dpr: resolved, initialDpr: state.viewport.initialDpr || resolved },
2154
+ }));
2155
+ },
2156
+ setFrameloop: (frameloop) => {
2157
+ const clock = store.snapshot.clock;
2158
+ // if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
2159
+ clock.stop();
2160
+ clock.elapsedTime = 0;
2161
+ if (frameloop !== 'never') {
2162
+ clock.start();
2163
+ clock.elapsedTime = 0;
2164
+ }
2165
+ store.update(() => ({ frameloop }));
2166
+ },
2167
+ previousRoot: null,
2168
+ internal: {
2169
+ active: false,
2170
+ priority: 0,
2171
+ frames: 0,
2172
+ lastEvent: new ElementRef(null),
2173
+ interaction: [],
2174
+ hovered: new Map(),
2175
+ capturedMap: new Map(),
2176
+ initialClick: [0, 0],
2177
+ initialHits: [],
2178
+ subscribers: [],
2179
+ subscribe: (callback, priority = 0, _store = store) => {
2180
+ const internal = _store.snapshot.internal;
2181
+ // If this subscription was given a priority, it takes rendering into its own hands
2182
+ // For that reason we switch off automatic rendering and increase the manual flag
2183
+ // As long as this flag is positive there can be no internal rendering at all
2184
+ // because there could be multiple render subscriptions
2185
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
2186
+ internal.subscribers.push({ callback, priority, store: _store });
2187
+ // Register subscriber and sort layers from lowest to highest, meaning,
2188
+ // highest priority renders last (on top of the other frames)
2189
+ internal.subscribers = internal.subscribers.sort((a, b) => (a.priority || 0) - (b.priority || 0));
2190
+ return () => {
2191
+ const internal = _store.snapshot.internal;
2192
+ if (internal?.subscribers) {
2193
+ // Decrease manual flag if this subscription had a priority
2194
+ internal.priority = internal.priority - (priority > 0 ? 1 : 0);
2195
+ // Remove subscriber from list
2196
+ internal.subscribers = internal.subscribers.filter((s) => s.callback !== callback);
2197
+ }
2198
+ };
2199
+ },
2200
+ },
2201
+ });
2202
+ Object.defineProperty(store, '__pointerMissed$', { get: () => pointerMissed$ });
2203
+ let { size: oldSize, viewport: { dpr: oldDpr }, camera: oldCamera, } = store.snapshot;
2204
+ effect(() => {
2205
+ const [newCamera, newSize, newDpr, gl] = [store.camera(), store.size(), store.viewport.dpr(), store.gl()];
2206
+ // Resize camera and renderer on changes to size and pixel-ratio
2207
+ if (newSize !== oldSize || newDpr !== oldDpr) {
2208
+ oldSize = newSize;
2209
+ oldDpr = newDpr;
2210
+ // Update camera & renderer
2211
+ updateCamera(newCamera, newSize);
2212
+ gl.setPixelRatio(newDpr);
2213
+ const updateStyle = typeof HTMLCanvasElement !== 'undefined' && gl.domElement instanceof HTMLCanvasElement;
2214
+ gl.setSize(newSize.width, newSize.height, updateStyle);
2215
+ }
2216
+ // Update viewport once the camera changes
2217
+ if (newCamera !== oldCamera) {
2218
+ oldCamera = newCamera;
2219
+ updateCamera(newCamera, newSize);
2220
+ // Update viewport
2221
+ store.update((state) => ({
2222
+ viewport: { ...state.viewport, ...state.viewport.getCurrentViewport(newCamera) },
2223
+ }));
2224
+ }
2225
+ });
2226
+ return store;
2227
+ }
2228
+ const NGT_STORE = new InjectionToken('NgtStore Token');
2229
+ function injectStore(options) {
2230
+ return inject(NGT_STORE, options);
2231
+ }
2232
+
2233
+ function resolveRef(ref) {
2234
+ if (is.ref(ref)) {
2235
+ return ref.nativeElement;
2236
+ }
2237
+ return ref;
2238
+ }
2239
+
2240
+ class NgtParent extends NgtCommonDirective {
2241
+ constructor() {
2242
+ super();
2243
+ this.parent = input.required();
2244
+ this.store = injectStore();
2245
+ this._parent = computed(() => {
2246
+ const parent = this.parent();
2247
+ const rawParent = typeof parent === 'function' ? parent() : parent;
2248
+ if (!rawParent)
2249
+ return null;
2250
+ const scene = this.store.scene();
2251
+ if (typeof rawParent === 'string') {
2252
+ return scene.getObjectByName(rawParent);
2253
+ }
2254
+ return resolveRef(rawParent);
2255
+ });
2256
+ this.linkedValue = linkedSignal(this._parent);
2257
+ this.shouldSkipRender = computed(() => !this._parent());
2258
+ const commentNode = this.commentNode;
2259
+ commentNode.data = NGT_PARENT_FLAG;
2260
+ commentNode[NGT_PARENT_FLAG] = true;
2261
+ if (commentNode[NGT_INTERNAL_ADD_COMMENT_FLAG]) {
2262
+ commentNode[NGT_INTERNAL_ADD_COMMENT_FLAG]('parent', this.injector);
2263
+ delete commentNode[NGT_INTERNAL_ADD_COMMENT_FLAG];
2264
+ }
2265
+ }
2266
+ validate() {
2267
+ return !this.injected && !!this.injectedValue;
2268
+ }
2269
+ beforeCreateView() {
2270
+ const commentNode = this.commentNode;
2271
+ if (commentNode[NGT_INTERNAL_SET_PARENT_COMMENT_FLAG]) {
2272
+ commentNode[NGT_INTERNAL_SET_PARENT_COMMENT_FLAG](this.injectedValue);
2273
+ }
2274
+ }
2275
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtParent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2276
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgtParent, isStandalone: true, selector: "ng-template[parent]", inputs: { parent: { classPropertyName: "parent", publicName: "parent", isSignal: true, isRequired: true, transformFunction: null } }, usesInheritance: true, ngImport: i0 }); }
2277
+ }
2278
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtParent, decorators: [{
2279
+ type: Directive,
2280
+ args: [{ selector: 'ng-template[parent]' }]
2281
+ }], ctorParameters: () => [] });
2282
+
2283
+ class NgtSelectionApi {
2284
+ constructor() {
2285
+ this.enabled = input(true, { alias: 'selection', transform: booleanAttribute });
2286
+ this.source = signal([]);
2287
+ this.selected = this.source.asReadonly();
2288
+ }
2289
+ update(...args) {
2290
+ if (!this.enabled())
2291
+ return;
2292
+ this.source.update(...args);
2293
+ }
2294
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtSelectionApi, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2295
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgtSelectionApi, isStandalone: true, selector: "[selection]", inputs: { enabled: { classPropertyName: "enabled", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
2296
+ }
2297
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtSelectionApi, decorators: [{
2298
+ type: Directive,
2299
+ args: [{ selector: '[selection]' }]
2300
+ }] });
2301
+ class NgtSelect {
2302
+ constructor() {
2303
+ this.enabled = input(false, { transform: booleanAttribute, alias: 'select' });
2304
+ const elementRef = inject(ElementRef);
2305
+ const selectionApi = inject(NgtSelectionApi);
2306
+ effect((onCleanup) => {
2307
+ const selectionEnabled = selectionApi.enabled();
2308
+ if (!selectionEnabled)
2309
+ return;
2310
+ const enabled = this.enabled();
2311
+ if (!enabled)
2312
+ return;
2313
+ const host = elementRef.nativeElement;
2314
+ const localState = getInstanceState(host);
2315
+ if (!localState)
2316
+ return;
2317
+ // ngt-mesh[select]
2318
+ if (host.type === 'Mesh') {
2319
+ selectionApi.update((prev) => [...prev, host]);
2320
+ onCleanup(() => selectionApi.update((prev) => prev.filter((el) => el !== host)));
2321
+ return;
2322
+ }
2323
+ const [collection] = [untracked(selectionApi.selected), localState.objects()];
2324
+ let changed = false;
2325
+ const current = [];
2326
+ host.traverse((child) => {
2327
+ child.type === 'Mesh' && current.push(child);
2328
+ if (collection.indexOf(child) === -1)
2329
+ changed = true;
2330
+ });
2331
+ if (!changed)
2332
+ return;
2333
+ selectionApi.update((prev) => [...prev, ...current]);
2334
+ onCleanup(() => {
2335
+ selectionApi.update((prev) => prev.filter((el) => !current.includes(el)));
2336
+ });
2337
+ });
2338
+ }
2339
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtSelect, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2340
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgtSelect, isStandalone: true, selector: "ngt-group[select], ngt-mesh[select]", inputs: { enabled: { classPropertyName: "enabled", publicName: "select", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
2341
+ }
2342
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtSelect, decorators: [{
2343
+ type: Directive,
2344
+ args: [{ selector: 'ngt-group[select], ngt-mesh[select]' }]
2345
+ }], ctorParameters: () => [] });
2346
+ const NgtSelection = [NgtSelectionApi, NgtSelect];
2347
+
2348
+ var _a;
2349
+ const NGT_HTML_DOM_ELEMENT = new InjectionToken('NGT_HTML_DOM_ELEMENT');
2350
+ function provideHTMLDomElement(...args) {
2351
+ if (args.length === 0) {
2352
+ return { provide: NGT_HTML_DOM_ELEMENT, useFactory: () => 'gl' };
2353
+ }
2354
+ if (args.length === 1) {
2355
+ return { provide: NGT_HTML_DOM_ELEMENT, useFactory: args[0] };
2356
+ }
2357
+ return { provide: NGT_HTML_DOM_ELEMENT, useFactory: args.pop(), deps: args };
2358
+ }
2359
+ class NgtHTML {
2360
+ static { _a = NGT_HTML_FLAG; }
2361
+ static { this[_a] = true; }
2362
+ constructor() {
2363
+ this.domElement = inject(NGT_HTML_DOM_ELEMENT, { self: true, optional: true });
2364
+ const host = inject(ElementRef);
2365
+ const store = injectStore();
2366
+ if (this.domElement === 'gl') {
2367
+ Object.assign(host.nativeElement, {
2368
+ [NGT_DOM_PARENT_FLAG]: store.snapshot.gl.domElement.parentElement,
2369
+ });
2370
+ }
2371
+ else if (this.domElement) {
2372
+ Object.assign(host.nativeElement, { [NGT_DOM_PARENT_FLAG]: this.domElement });
2373
+ }
2374
+ inject(DestroyRef).onDestroy(() => {
2375
+ host.nativeElement[NGT_DOM_PARENT_FLAG] = null;
2376
+ delete host.nativeElement[NGT_DOM_PARENT_FLAG];
2377
+ });
2378
+ }
2379
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtHTML, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2380
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgtHTML, isStandalone: true, ngImport: i0 }); }
2381
+ }
2382
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtHTML, decorators: [{
2383
+ type: Directive
2384
+ }], ctorParameters: () => [] });
2385
+
2386
+ const cached$1 = new Map();
2387
+ const memoizedLoaders$1 = new WeakMap();
2388
+ function normalizeInputs$1(input) {
2389
+ let urls = [];
2390
+ if (Array.isArray(input)) {
2391
+ urls = input;
2392
+ }
2393
+ else if (typeof input === 'string') {
2394
+ urls = [input];
2395
+ }
2396
+ else {
2397
+ urls = Object.values(input);
2398
+ }
2399
+ return urls.map((url) => (url.includes('undefined') || url.includes('null') || !url ? '' : url));
2400
+ }
2401
+ function load(loaderConstructorFactory, inputs, { extensions, onLoad, onProgress, } = {}) {
2402
+ return () => {
2403
+ const urls = normalizeInputs$1(inputs());
2404
+ let loader = memoizedLoaders$1.get(loaderConstructorFactory(urls));
2405
+ if (!loader) {
2406
+ loader = new (loaderConstructorFactory(urls))();
2407
+ memoizedLoaders$1.set(loaderConstructorFactory(urls), loader);
2408
+ }
2409
+ if (extensions)
2410
+ extensions(loader);
2411
+ return urls.map((url) => {
2412
+ if (url === '')
2413
+ return Promise.resolve(null);
2414
+ if (!cached$1.has(url)) {
2415
+ cached$1.set(url, new Promise((resolve, reject) => {
2416
+ loader.load(url, (data) => {
2417
+ if ('scene' in data) {
2418
+ Object.assign(data, makeObjectGraph(data['scene']));
2419
+ }
2420
+ if (onLoad) {
2421
+ onLoad(data);
2422
+ }
2423
+ resolve(data);
2424
+ }, onProgress, (error) => reject(new Error(`[NGT] Could not load ${url}: ${error?.message}`)));
2425
+ }));
2426
+ }
2427
+ return cached$1.get(url);
2428
+ });
2429
+ };
2430
+ }
2431
+ /**
2432
+ * @deprecated Use loaderResource instead. Will be removed in v5.0.0
2433
+ * @since v4.0.0~
2434
+ */
2435
+ function _injectLoader(loaderConstructorFactory, inputs, { extensions, onProgress, onLoad, injector, } = {}) {
2436
+ return assertInjector(_injectLoader, injector, () => {
2437
+ const response = signal(null);
2438
+ const cachedResultPromisesEffect = load(loaderConstructorFactory, inputs, {
2439
+ extensions,
2440
+ onProgress,
2441
+ onLoad: onLoad,
2442
+ });
2443
+ effect(() => {
2444
+ const originalUrls = inputs();
2445
+ const cachedResultPromises = cachedResultPromisesEffect();
2446
+ Promise.all(cachedResultPromises).then((results) => {
2447
+ response.update(() => {
2448
+ if (Array.isArray(originalUrls))
2449
+ return results;
2450
+ if (typeof originalUrls === 'string')
2451
+ return results[0];
2452
+ const keys = Object.keys(originalUrls);
2453
+ return keys.reduce((result, key) => {
2454
+ // @ts-ignore
2455
+ result[key] = results[keys.indexOf(key)];
2456
+ return result;
2457
+ }, {});
2458
+ });
2459
+ });
2460
+ });
2461
+ return response.asReadonly();
2462
+ });
2463
+ }
2464
+ _injectLoader.preload = (loaderConstructorFactory, inputs, extensions, onLoad) => {
2465
+ const effects = load(loaderConstructorFactory, inputs, { extensions, onLoad })();
2466
+ if (effects) {
2467
+ void Promise.all(effects);
2468
+ }
2469
+ };
2470
+ _injectLoader.destroy = () => {
2471
+ cached$1.clear();
2472
+ };
2473
+ _injectLoader.clear = (urls) => {
2474
+ const urlToClear = Array.isArray(urls) ? urls : [urls];
2475
+ urlToClear.forEach((url) => {
2476
+ cached$1.delete(url);
2477
+ });
2478
+ };
2479
+ /**
2480
+ * @deprecated Use loaderResource instead. Will be removed in v5.0.0
2481
+ * @since v4.0.0~
2482
+ */
2483
+ const injectLoader = _injectLoader;
2484
+
2485
+ function normalizeInputs(input) {
2486
+ let urls = [];
2487
+ if (Array.isArray(input)) {
2488
+ urls = input;
2489
+ }
2490
+ else if (typeof input === 'string') {
2491
+ urls = [input];
2492
+ }
2493
+ else {
2494
+ urls = Object.values(input);
2495
+ }
2496
+ return urls.map((url) => (url.includes('undefined') || url.includes('null') || !url ? '' : url));
2497
+ }
2498
+ const cached = new Map();
2499
+ const memoizedLoaders = new WeakMap();
2500
+ function getLoaderRequestParams(input, loaderConstructorFactory, extensions) {
2501
+ const urls = input();
2502
+ const LoaderConstructor = loaderConstructorFactory(urls);
2503
+ const normalizedUrls = normalizeInputs(urls);
2504
+ let loader = memoizedLoaders.get(LoaderConstructor);
2505
+ if (!loader) {
2506
+ loader = new LoaderConstructor();
2507
+ memoizedLoaders.set(LoaderConstructor, loader);
2508
+ }
2509
+ if (extensions)
2510
+ extensions(loader);
2511
+ return { urls, normalizedUrls, loader };
2512
+ }
2513
+ function getLoaderPromises(request, onProgress) {
2514
+ return request.normalizedUrls.map((url) => {
2515
+ if (url === '')
2516
+ return Promise.resolve(null);
2517
+ const cachedPromise = cached.get(url);
2518
+ if (cachedPromise)
2519
+ return cachedPromise;
2520
+ const promise = new Promise((res, rej) => {
2521
+ request.loader.load(url, (data) => {
2522
+ if ('scene' in data) {
2523
+ Object.assign(data, makeObjectGraph(data['scene']));
2524
+ }
2525
+ res(data);
2526
+ }, onProgress, (error) => rej(new Error(`[NGT] Could not load ${url}: ${error?.message}`)));
2527
+ });
2528
+ cached.set(url, promise);
2529
+ return promise;
2530
+ });
2531
+ }
2532
+ function loaderResource(loaderConstructorFactory, input, { extensions, onLoad, onProgress, injector, } = {}) {
2533
+ return assertInjector(loaderResource, injector, () => {
2534
+ return resource({
2535
+ request: () => getLoaderRequestParams(input, loaderConstructorFactory, extensions),
2536
+ loader: async ({ request }) => {
2537
+ // TODO: use the abortSignal when THREE.Loader supports it
2538
+ const loadedResults = await Promise.all(getLoaderPromises(request, onProgress));
2539
+ let results;
2540
+ if (Array.isArray(request.urls)) {
2541
+ results = loadedResults;
2542
+ }
2543
+ else if (typeof request.urls === 'string') {
2544
+ results = loadedResults[0];
2545
+ }
2546
+ else {
2547
+ const keys = Object.keys(request.urls);
2548
+ results = keys.reduce((result, key) => {
2549
+ // @ts-ignore
2550
+ result[key] = loadedResults[keys.indexOf(key)];
2551
+ return result;
2552
+ }, {});
2553
+ }
2554
+ if (onLoad)
2555
+ onLoad(results);
2556
+ return results;
2557
+ },
2558
+ });
2559
+ });
2560
+ }
2561
+ loaderResource.preload = (loaderConstructor, inputs, extensions) => {
2562
+ const params = getLoaderRequestParams(() => inputs, () => loaderConstructor, extensions);
2563
+ void Promise.all(getLoaderPromises(params));
2564
+ };
2565
+ loaderResource.destroy = () => {
2566
+ cached.clear();
2567
+ };
2568
+ loaderResource.clear = (urls) => {
2569
+ const urlToClear = Array.isArray(urls) ? urls : [urls];
2570
+ urlToClear.forEach((url) => {
2571
+ cached.delete(url);
2572
+ });
2573
+ };
2574
+
2575
+ const RGBA_REGEX = /rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*(\d*\.?\d+)?\)/;
2576
+ const DEFAULT_COLOR = 0x000000;
2577
+ class NgtHexify {
2578
+ constructor() {
2579
+ this.document = inject(DOCUMENT, { optional: true });
2580
+ this.cache = {};
2581
+ }
2582
+ /**
2583
+ * transforms a:
2584
+ * - hex string to a hex number
2585
+ * - rgb string to a hex number
2586
+ * - rgba string to a hex number
2587
+ * - css color string to a hex number
2588
+ *
2589
+ * always default to black if failed
2590
+ * @param value
2591
+ */
2592
+ transform(value) {
2593
+ if (value == null)
2594
+ return DEFAULT_COLOR;
2595
+ if (value.startsWith('#')) {
2596
+ if (!this.cache[value]) {
2597
+ this.cache[value] = this.hexStringToNumber(value);
2598
+ }
2599
+ return this.cache[value];
2600
+ }
2601
+ if (!this.ctx) {
2602
+ this.ctx = this.document?.createElement('canvas').getContext('2d');
2603
+ }
2604
+ if (!this.ctx) {
2605
+ console.warn('[NGT] hexify: canvas context is not available');
2606
+ return DEFAULT_COLOR;
2607
+ }
2608
+ this.ctx.fillStyle = value;
2609
+ const computedValue = this.ctx.fillStyle;
2610
+ if (computedValue.startsWith('#')) {
2611
+ if (!this.cache[computedValue]) {
2612
+ this.cache[computedValue] = this.hexStringToNumber(computedValue);
2613
+ }
2614
+ return this.cache[computedValue];
2615
+ }
2616
+ if (!computedValue.startsWith('rgba')) {
2617
+ console.warn(`[NGT] hexify: invalid color format. Expected rgba or hex, receive: ${computedValue}`);
2618
+ return DEFAULT_COLOR;
2619
+ }
2620
+ const match = computedValue.match(RGBA_REGEX);
2621
+ if (!match) {
2622
+ console.warn(`[NGT] hexify: invalid color format. Expected rgba or hex, receive: ${computedValue}`);
2623
+ return DEFAULT_COLOR;
2624
+ }
2625
+ const r = parseInt(match[1], 10);
2626
+ const g = parseInt(match[2], 10);
2627
+ const b = parseInt(match[3], 10);
2628
+ const a = match[4] ? parseFloat(match[4]) : 1.0;
2629
+ const cacheKey = `${r}:${g}:${b}:${a}`;
2630
+ // check result from cache
2631
+ if (!this.cache[cacheKey]) {
2632
+ // Convert the components to hex strings
2633
+ const hexR = this.componentToHex(r);
2634
+ const hexG = this.componentToHex(g);
2635
+ const hexB = this.componentToHex(b);
2636
+ const hexA = this.componentToHex(Math.round(a * 255));
2637
+ // Combine the hex components into a single hex string
2638
+ const hex = `#${hexR}${hexG}${hexB}${hexA}`;
2639
+ this.cache[cacheKey] = this.hexStringToNumber(hex);
2640
+ }
2641
+ return this.cache[cacheKey];
2642
+ }
2643
+ hexStringToNumber(hexString) {
2644
+ return parseInt(hexString.replace('#', ''), 16);
2645
+ }
2646
+ componentToHex(component) {
2647
+ const hex = component.toString(16);
2648
+ return hex.length === 1 ? '0' + hex : hex;
2649
+ }
2650
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtHexify, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
2651
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.11", ngImport: i0, type: NgtHexify, isStandalone: true, name: "hexify" }); }
2652
+ }
2653
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtHexify, decorators: [{
2654
+ type: Pipe,
2655
+ args: [{ name: 'hexify', pure: true }]
2656
+ }] });
2657
+
2658
+ function omit(objFn, keysToOmit, equal = (a, b) => is.equ(a, b, { objects: 'shallow', arrays: 'shallow' })) {
2659
+ return computed(() => {
2660
+ const obj = objFn();
2661
+ const result = {};
2662
+ for (const key of Object.keys(obj)) {
2663
+ if (keysToOmit.includes(key))
2664
+ continue;
2665
+ Object.assign(result, { [key]: obj[key] });
2666
+ }
2667
+ return result;
2668
+ }, { equal });
2669
+ }
2670
+ function pick(objFn, keyOrKeys, equal) {
2671
+ if (Array.isArray(keyOrKeys)) {
2672
+ if (!equal) {
2673
+ equal = (a, b) => is.equ(a, b, { objects: 'shallow', arrays: 'shallow' });
2674
+ }
2675
+ return computed(() => {
2676
+ const obj = objFn();
2677
+ const result = {};
2678
+ for (const key of keyOrKeys) {
2679
+ if (!(key in obj))
2680
+ continue;
2681
+ Object.assign(result, { [key]: obj[key] });
2682
+ }
2683
+ return result;
2684
+ }, { equal });
2685
+ }
2686
+ return computed(() => objFn()[keyOrKeys], { equal });
2687
+ }
2688
+ function merge(objFn, toMerge, mode = 'override', equal = (a, b) => is.equ(a, b, { objects: 'shallow', arrays: 'shallow' })) {
2689
+ if (mode === 'override')
2690
+ return computed(() => ({ ...objFn(), ...toMerge }), { equal });
2691
+ return computed(() => ({ ...toMerge, ...objFn() }), { equal });
2692
+ }
2693
+ function createVectorComputed(vectorCtor) {
2694
+ return ((inputOrOptions, keyOrKeepUndefined, keepUndefined) => {
2695
+ if (typeof keyOrKeepUndefined === 'undefined' || typeof keyOrKeepUndefined === 'boolean') {
2696
+ keepUndefined = !!keyOrKeepUndefined;
2697
+ const input = inputOrOptions;
2698
+ return computed(() => {
2699
+ const value = input();
2700
+ if (keepUndefined && value == undefined)
2701
+ return undefined;
2702
+ if (typeof value === 'number')
2703
+ return new vectorCtor().setScalar(value);
2704
+ else if (Array.isArray(value))
2705
+ return new vectorCtor(...value);
2706
+ else if (value)
2707
+ return value;
2708
+ else
2709
+ return new vectorCtor();
2710
+ }, { equal: (a, b) => !!a && !!b && a.equals(b) });
2711
+ }
2712
+ const options = inputOrOptions;
2713
+ const key = keyOrKeepUndefined;
2714
+ return computed(() => {
2715
+ const value = options()[key];
2716
+ if (keepUndefined && value == undefined)
2717
+ return undefined;
2718
+ if (typeof value === 'number')
2719
+ return new vectorCtor().setScalar(value);
2720
+ else if (Array.isArray(value))
2721
+ return new vectorCtor(...value);
2722
+ else if (value)
2723
+ return value;
2724
+ else
2725
+ return new vectorCtor();
2726
+ }, { equal: (a, b) => !!a && !!b && a.equals(b) });
2727
+ });
2728
+ }
2729
+ const vector2 = createVectorComputed(THREE.Vector2);
2730
+ const vector3 = createVectorComputed(THREE.Vector3);
2731
+ const vector4 = createVectorComputed(THREE.Vector4);
2732
+
2733
+ class NgtPortalAutoRender {
2734
+ constructor() {
2735
+ // TODO: (chau) investigate if this is still needed
2736
+ // effect(() => {
2737
+ // this.portalStore.update((state) => ({ events: { ...state.events, priority: this.renderPriority() + 1 } }));
2738
+ // });
2739
+ this.portalStore = injectStore({ host: true });
2740
+ this.parentStore = injectStore({ skipSelf: true });
2741
+ this.portal = inject(NgtPortalImpl, { host: true });
2742
+ this.renderPriority = input(1, { alias: 'autoRender', transform: (value) => numberAttribute(value, 1) });
2743
+ effect((onCleanup) => {
2744
+ const portalRendered = this.portal.portalRendered();
2745
+ if (!portalRendered)
2746
+ return;
2747
+ // track state
2748
+ const [renderPriority, { internal }] = [this.renderPriority(), this.portalStore()];
2749
+ let oldClean;
2750
+ const cleanup = internal.subscribe(({ gl, scene, camera }) => {
2751
+ const [parentScene, parentCamera] = [
2752
+ this.parentStore.snapshot.scene,
2753
+ this.parentStore.snapshot.camera,
2754
+ ];
2755
+ oldClean = gl.autoClear;
2756
+ if (renderPriority === 1) {
2757
+ // clear scene and render with default
2758
+ gl.autoClear = true;
2759
+ gl.render(parentScene, parentCamera);
2760
+ }
2761
+ // disable cleaning
2762
+ gl.autoClear = false;
2763
+ gl.clearDepth();
2764
+ gl.render(scene, camera);
2765
+ // restore
2766
+ gl.autoClear = oldClean;
2767
+ }, renderPriority, this.portalStore);
2768
+ onCleanup(() => cleanup());
2769
+ });
2770
+ }
2771
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtPortalAutoRender, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2772
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgtPortalAutoRender, isStandalone: true, selector: "ngt-portal[autoRender]", inputs: { renderPriority: { classPropertyName: "renderPriority", publicName: "autoRender", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
2773
+ }
2774
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtPortalAutoRender, decorators: [{
2775
+ type: Directive,
2776
+ args: [{ selector: 'ngt-portal[autoRender]' }]
2777
+ }], ctorParameters: () => [] });
2778
+ class NgtPortalContent {
2779
+ static ngTemplateContextGuard(_, ctx) {
2780
+ return true;
2781
+ }
2782
+ constructor() {
2783
+ const host = inject(ElementRef, { skipSelf: true });
2784
+ const { element } = inject(ViewContainerRef);
2785
+ const injector = inject(Injector);
2786
+ const commentNode = element.nativeElement;
2787
+ const store = injectStore();
2788
+ commentNode.data = NGT_PORTAL_CONTENT_FLAG;
2789
+ commentNode[NGT_PORTAL_CONTENT_FLAG] = store;
2790
+ commentNode[NGT_DOM_PARENT_FLAG] = host.nativeElement;
2791
+ }
2792
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtPortalContent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2793
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgtPortalContent, isStandalone: true, selector: "ng-template[portalContent]", ngImport: i0 }); }
2794
+ }
2795
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtPortalContent, decorators: [{
2796
+ type: Directive,
2797
+ args: [{ selector: 'ng-template[portalContent]' }]
2798
+ }], ctorParameters: () => [] });
2799
+ function mergeState(previousRoot, store, container, pointer, raycaster, events, size) {
2800
+ // we never want to spread the id
2801
+ const { id: _, ...previousState } = previousRoot.snapshot;
2802
+ const state = store.snapshot;
2803
+ let viewport = undefined;
2804
+ if (state.camera && size) {
2805
+ const camera = state.camera;
2806
+ // calculate the override viewport, if present
2807
+ viewport = previousState.viewport.getCurrentViewport(camera, new THREE.Vector3(), size);
2808
+ // update the portal camera, if it differs from the previous layer
2809
+ if (camera !== previousState.camera)
2810
+ updateCamera(camera, size);
2811
+ }
2812
+ return {
2813
+ // the intersect consists of the previous root state
2814
+ ...previousState,
2815
+ ...state,
2816
+ // portals have their own scene, which forms the root, a raycaster and a pointer
2817
+ scene: container,
2818
+ pointer,
2819
+ raycaster,
2820
+ // their previous root is the layer before it
2821
+ previousRoot,
2822
+ events: { ...previousState.events, ...state.events, ...events },
2823
+ size: { ...previousState.size, ...size },
2824
+ viewport: { ...previousState.viewport, ...viewport },
2825
+ // layers are allowed to override events
2826
+ setEvents: (events) => store.update((state) => ({ ...state, events: { ...state.events, ...events } })),
2827
+ };
2828
+ }
2829
+ class NgtPortalImpl {
2830
+ constructor() {
2831
+ this.container = input.required();
2832
+ this.state = input({});
2833
+ this.contentRef = contentChild.required(NgtPortalContent, { read: TemplateRef });
2834
+ this.anchorRef = contentChild.required(NgtPortalContent, { read: ViewContainerRef });
2835
+ this.previousStore = injectStore({ skipSelf: true });
2836
+ this.portalStore = injectStore();
2837
+ this.injector = inject(Injector);
2838
+ this.size = pick(this.state, 'size');
2839
+ this.events = pick(this.state, 'events');
2840
+ this.restState = omit(this.state, ['size', 'events']);
2841
+ this.portalContentRendered = signal(false);
2842
+ this.portalRendered = this.portalContentRendered.asReadonly();
2843
+ extend({ Group });
2844
+ effect(() => {
2845
+ let [container, anchor, content] = [
2846
+ this.container(),
2847
+ this.anchorRef(),
2848
+ this.contentRef(),
2849
+ this.previousStore(),
2850
+ ];
2851
+ const [size, events, restState] = [untracked(this.size), untracked(this.events), untracked(this.restState)];
2852
+ if (!is.instance(container)) {
2853
+ container = prepare(container, 'ngt-portal', { store: this.portalStore });
2854
+ }
2855
+ const instanceState = getInstanceState(container);
2856
+ if (instanceState && instanceState.store !== this.portalStore) {
2857
+ instanceState.store = this.portalStore;
2858
+ }
2859
+ this.portalStore.update(restState, mergeState(this.previousStore, this.portalStore, container, this.portalStore.snapshot.pointer, this.portalStore.snapshot.raycaster, events, size));
2860
+ if (this.portalViewRef) {
2861
+ this.portalViewRef.detectChanges();
2862
+ return;
2863
+ }
2864
+ this.portalViewRef = anchor.createEmbeddedView(content, { injector: this.injector }, { injector: this.injector });
2865
+ this.portalViewRef.detectChanges();
2866
+ this.portalContentRendered.set(true);
2867
+ });
2868
+ }
2869
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtPortalImpl, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2870
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.11", type: NgtPortalImpl, isStandalone: true, selector: "ngt-portal", inputs: { container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: true, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
2871
+ {
2872
+ provide: NGT_STORE,
2873
+ useFactory: (previousStore) => {
2874
+ const pointer = new THREE.Vector2();
2875
+ const raycaster = new THREE.Raycaster();
2876
+ const { id: _skipId, ...previousState } = previousStore.snapshot;
2877
+ const store = signalState({
2878
+ id: makeId(),
2879
+ ...previousState,
2880
+ scene: null,
2881
+ previousRoot: previousStore,
2882
+ pointer,
2883
+ raycaster,
2884
+ });
2885
+ store.update(mergeState(previousStore, store, null, pointer, raycaster));
2886
+ return store;
2887
+ },
2888
+ deps: [[new SkipSelf(), NGT_STORE]],
2889
+ },
2890
+ ], queries: [{ propertyName: "contentRef", first: true, predicate: NgtPortalContent, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "anchorRef", first: true, predicate: NgtPortalContent, descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: `
2891
+ @if (portalRendered()) {
2892
+ <!-- Without an element that receives pointer events state.pointer will always be 0/0 -->
2893
+ <ngt-group (pointerover)="(undefined)" attach="none" />
2894
+ }
2895
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2896
+ }
2897
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgtPortalImpl, decorators: [{
2898
+ type: Component,
2899
+ args: [{
2900
+ selector: 'ngt-portal',
2901
+ template: `
2902
+ @if (portalRendered()) {
2903
+ <!-- Without an element that receives pointer events state.pointer will always be 0/0 -->
2904
+ <ngt-group (pointerover)="(undefined)" attach="none" />
2905
+ }
2906
+ `,
2907
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
2908
+ changeDetection: ChangeDetectionStrategy.OnPush,
2909
+ providers: [
2910
+ {
2911
+ provide: NGT_STORE,
2912
+ useFactory: (previousStore) => {
2913
+ const pointer = new THREE.Vector2();
2914
+ const raycaster = new THREE.Raycaster();
2915
+ const { id: _skipId, ...previousState } = previousStore.snapshot;
2916
+ const store = signalState({
2917
+ id: makeId(),
2918
+ ...previousState,
2919
+ scene: null,
2920
+ previousRoot: previousStore,
2921
+ pointer,
2922
+ raycaster,
2923
+ });
2924
+ store.update(mergeState(previousStore, store, null, pointer, raycaster));
2925
+ return store;
2926
+ },
2927
+ deps: [[new SkipSelf(), NGT_STORE]],
2928
+ },
2929
+ ],
2930
+ }]
2931
+ }], ctorParameters: () => [] });
2932
+ const NgtPortal = [NgtPortalImpl, NgtPortalContent];
2933
+
2885
2934
  const shallowLoose = { objects: 'shallow', strict: false };
2886
2935
  function canvasRootInitializer(injector) {
2887
2936
  return assertInjector(canvasRootInitializer, injector, () => {