angular-three 2.0.0-beta.21 → 2.0.0-beta.223
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/index.mjs +4 -11
- package/esm2022/lib/canvas.mjs +81 -156
- package/esm2022/lib/directives/args.mjs +6 -6
- package/esm2022/lib/directives/common.mjs +15 -14
- package/esm2022/lib/directives/parent.mjs +6 -6
- package/esm2022/lib/dom/events.mjs +2 -2
- package/esm2022/lib/events.mjs +28 -25
- package/esm2022/lib/instance.mjs +39 -34
- package/esm2022/lib/loader.mjs +12 -14
- package/esm2022/lib/loop.mjs +9 -10
- package/esm2022/lib/portal.mjs +122 -135
- package/esm2022/lib/ref.mjs +18 -20
- package/esm2022/lib/renderer/catalogue.mjs +2 -2
- package/esm2022/lib/renderer/constants.mjs +2 -2
- package/esm2022/lib/renderer/index.mjs +58 -62
- package/esm2022/lib/renderer/store.mjs +129 -120
- package/esm2022/lib/renderer/utils.mjs +35 -42
- package/esm2022/lib/roots.mjs +41 -38
- package/esm2022/lib/routed-scene.mjs +6 -7
- package/esm2022/lib/store.mjs +163 -189
- package/esm2022/lib/utils/apply-props.mjs +12 -17
- package/esm2022/lib/utils/attach.mjs +6 -6
- package/esm2022/lib/utils/before-render.mjs +12 -0
- package/esm2022/lib/utils/cd-aware-signal.mjs +24 -0
- package/esm2022/lib/utils/create-api-token.mjs +13 -0
- package/esm2022/lib/utils/is.mjs +6 -5
- package/esm2022/lib/utils/make.mjs +15 -12
- package/esm2022/lib/utils/signal-store.mjs +67 -57
- package/esm2022/lib/utils/update.mjs +3 -2
- package/fesm2022/angular-three.mjs +1621 -1795
- package/fesm2022/angular-three.mjs.map +1 -1
- package/index.d.ts +6 -10
- package/lib/canvas.d.ts +24 -37
- package/lib/directives/common.d.ts +12 -1
- package/lib/events.d.ts +2 -2
- package/lib/instance.d.ts +19 -10
- package/lib/loader.d.ts +13 -4
- package/lib/loop.d.ts +6 -29
- package/lib/portal.d.ts +18 -26
- package/lib/ref.d.ts +0 -1
- package/lib/renderer/catalogue.d.ts +5 -1
- package/lib/renderer/constants.d.ts +1 -1
- package/lib/renderer/index.d.ts +55 -4
- package/lib/renderer/store.d.ts +18 -21
- package/lib/renderer/utils.d.ts +2 -3
- package/lib/roots.d.ts +4 -3
- package/lib/store.d.ts +9 -11
- package/lib/utils/apply-props.d.ts +0 -1
- package/lib/{before-render.d.ts → utils/before-render.d.ts} +1 -1
- package/lib/utils/cd-aware-signal.d.ts +4 -0
- package/lib/utils/create-api-token.d.ts +23 -0
- package/lib/utils/is.d.ts +11 -12
- package/lib/utils/make.d.ts +3 -2
- package/lib/utils/signal-store.d.ts +16 -3
- package/metadata.json +1 -1
- package/package.json +30 -11
- package/plugin/generators.json +0 -32
- package/plugin/src/generators/init/compat.d.ts +1 -3
- package/plugin/src/generators/init/files/experience/{experience.component.ts.__tmpl__ → experience.component.ts__tmpl__} +1 -0
- package/plugin/src/generators/init/generator.d.ts +2 -5
- package/plugin/src/generators/init/generator.js +94 -95
- package/plugin/src/generators/init/generator.js.map +1 -1
- package/plugin/src/generators/init/schema.json +1 -12
- package/plugin/src/generators/utils.js.map +1 -1
- package/plugin/src/generators/{versions.d.ts → version.d.ts} +5 -3
- package/plugin/src/generators/version.js +18 -0
- package/plugin/src/generators/version.js.map +1 -0
- package/plugin/src/index.d.ts +0 -3
- package/plugin/src/index.js +0 -9
- package/plugin/src/index.js.map +1 -1
- package/web-types.json +1 -1
- package/esm2022/lib/before-render.mjs +0 -13
- package/esm2022/lib/directives/key.mjs +0 -29
- package/esm2022/lib/directives/repeat.mjs +0 -17
- package/esm2022/lib/three-types.mjs +0 -2
- package/esm2022/lib/utils/assert-injection-context.mjs +0 -14
- package/esm2022/lib/utils/create-injection-token.mjs +0 -47
- package/esm2022/lib/utils/safe-detect-changes.mjs +0 -17
- package/lib/directives/key.d.ts +0 -10
- package/lib/directives/repeat.d.ts +0 -7
- package/lib/three-types.d.ts +0 -306
- package/lib/utils/assert-injection-context.d.ts +0 -2
- package/lib/utils/create-injection-token.d.ts +0 -27
- package/lib/utils/safe-detect-changes.d.ts +0 -2
- package/plugin/package.json +0 -6
- package/plugin/src/generators/init-cannon/compat.d.ts +0 -2
- package/plugin/src/generators/init-cannon/compat.js +0 -6
- package/plugin/src/generators/init-cannon/compat.js.map +0 -1
- package/plugin/src/generators/init-cannon/generator.d.ts +0 -2
- package/plugin/src/generators/init-cannon/generator.js +0 -22
- package/plugin/src/generators/init-cannon/generator.js.map +0 -1
- package/plugin/src/generators/init-cannon/schema.json +0 -6
- package/plugin/src/generators/init-postprocessing/compat.d.ts +0 -2
- package/plugin/src/generators/init-postprocessing/compat.js +0 -6
- package/plugin/src/generators/init-postprocessing/compat.js.map +0 -1
- package/plugin/src/generators/init-postprocessing/generator.d.ts +0 -2
- package/plugin/src/generators/init-postprocessing/generator.js +0 -20
- package/plugin/src/generators/init-postprocessing/generator.js.map +0 -1
- package/plugin/src/generators/init-postprocessing/schema.json +0 -6
- package/plugin/src/generators/init-soba/compat.d.ts +0 -2
- package/plugin/src/generators/init-soba/compat.js +0 -6
- package/plugin/src/generators/init-soba/compat.js.map +0 -1
- package/plugin/src/generators/init-soba/generator.d.ts +0 -2
- package/plugin/src/generators/init-soba/generator.js +0 -26
- package/plugin/src/generators/init-soba/generator.js.map +0 -1
- package/plugin/src/generators/init-soba/schema.json +0 -6
- package/plugin/src/generators/versions.js +0 -16
- package/plugin/src/generators/versions.js.map +0 -1
- /package/plugin/src/generators/init/files/experience/{experience.component.html.__tmpl__ → experience.component.html__tmpl__} +0 -0
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { untracked, computed, signal, ElementRef, inject,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { untracked, computed, signal, ElementRef, inject, effect, InjectionToken, Optional, SkipSelf, ViewContainerRef, NgZone, TemplateRef, DestroyRef, Directive, Input, EventEmitter, getDebugNode, RendererFactory2, Injectable, Injector, provideZoneChangeDetection, makeEnvironmentProviders, EnvironmentInjector, afterNextRender, createEnvironmentInjector, Component, ChangeDetectionStrategy, Output, ViewChild, CUSTOM_ELEMENTS_SCHEMA, ContentChild, ChangeDetectorRef, forwardRef } from '@angular/core';
|
|
3
|
+
import { injectAutoEffect } from 'ngxtension/auto-effect';
|
|
4
|
+
import { provideResizeOptions, NgxResize } from 'ngxtension/resize';
|
|
5
5
|
import * as THREE from 'three';
|
|
6
|
-
import {
|
|
6
|
+
import { DOCUMENT } from '@angular/common';
|
|
7
|
+
import { createInjectionToken } from 'ngxtension/create-injection-token';
|
|
8
|
+
import { Subject, filter } from 'rxjs';
|
|
9
|
+
import { assertInjector } from 'ngxtension/assert-injector';
|
|
7
10
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
8
11
|
import * as i1 from '@angular/router';
|
|
9
12
|
import { ActivationEnd, RouterOutlet } from '@angular/router';
|
|
10
13
|
|
|
11
14
|
const STORE_COMPUTED_KEY = '__ngt_signal_store_computed__';
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
function reducer(state) {
|
|
16
|
+
return (previous) => {
|
|
14
17
|
const partial = typeof state === 'function' ? state(previous) : state;
|
|
15
18
|
Object.keys(partial).forEach((key) => {
|
|
16
19
|
const typedKey = key;
|
|
@@ -20,83 +23,93 @@ const setter = (_source) => (state) => {
|
|
|
20
23
|
});
|
|
21
24
|
return partial;
|
|
22
25
|
};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Object.keys(state).forEach((key) => {
|
|
30
|
-
const typedKey = key;
|
|
31
|
-
if (state[typedKey] === undefined && previous[typedKey] != null) {
|
|
32
|
-
state[typedKey] = previous[typedKey];
|
|
33
|
-
}
|
|
26
|
+
}
|
|
27
|
+
function updater(_source) {
|
|
28
|
+
return (state) => {
|
|
29
|
+
const updater = reducer(state);
|
|
30
|
+
untracked(() => {
|
|
31
|
+
_source.update((previous) => ({ ...previous, ...updater(previous) }));
|
|
34
32
|
});
|
|
35
|
-
return state;
|
|
36
33
|
};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
34
|
+
}
|
|
35
|
+
function patcher(_source) {
|
|
36
|
+
return (state) => {
|
|
37
|
+
const updater = reducer(state);
|
|
38
|
+
untracked(() => {
|
|
39
|
+
_source.update((previous) => ({ ...updater(previous), ...previous }));
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function getter(_source) {
|
|
44
|
+
return (...keys) => {
|
|
45
|
+
const root = untracked(_source);
|
|
46
|
+
if (keys.length === 0)
|
|
47
|
+
return root;
|
|
48
|
+
return keys.reduce((value, key) => value[key], root);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function selector(_state, computedCache) {
|
|
52
|
+
return (...keysAndOptions) => {
|
|
53
|
+
if (keysAndOptions.length === 0)
|
|
54
|
+
return _state;
|
|
55
|
+
if (keysAndOptions.length === 1 && typeof keysAndOptions[0] === 'object') {
|
|
56
|
+
const cachedKey = STORE_COMPUTED_KEY.concat(JSON.stringify(keysAndOptions[0]));
|
|
57
|
+
if (!computedCache.has(cachedKey)) {
|
|
58
|
+
computedCache.set(cachedKey, computed(_state, keysAndOptions));
|
|
59
|
+
}
|
|
60
|
+
return computedCache.get(cachedKey);
|
|
61
|
+
}
|
|
62
|
+
const [keys, options] = parseStoreOptions(keysAndOptions);
|
|
63
|
+
const joinedKeys = keys.join('-');
|
|
64
|
+
const cachedKeys = joinedKeys.concat(JSON.stringify(options));
|
|
65
|
+
if (!computedCache.has(cachedKeys)) {
|
|
66
|
+
computedCache.set(cachedKeys, computed(() => keys.reduce((value, key) => value[key], _state()), options));
|
|
67
|
+
}
|
|
68
|
+
return computedCache.get(cachedKeys);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function parseStoreOptions(keysAndOptions) {
|
|
72
|
+
if (typeof keysAndOptions.at(-1) === 'object') {
|
|
73
|
+
return [keysAndOptions.slice(0, -1), keysAndOptions.at(-1)];
|
|
74
|
+
}
|
|
75
|
+
return [keysAndOptions, { equal: Object.is }];
|
|
76
|
+
}
|
|
65
77
|
function signalStore(initialState = {}, options) {
|
|
66
78
|
let source;
|
|
67
|
-
let
|
|
79
|
+
let update;
|
|
68
80
|
let get;
|
|
69
81
|
let patch;
|
|
82
|
+
let select;
|
|
83
|
+
let state;
|
|
84
|
+
const computedCache = new Map();
|
|
85
|
+
if (!options) {
|
|
86
|
+
options = { equal: Object.is };
|
|
87
|
+
}
|
|
70
88
|
if (typeof initialState === 'function') {
|
|
71
89
|
source = signal({}, options);
|
|
90
|
+
state = source.asReadonly();
|
|
72
91
|
get = getter(source);
|
|
73
|
-
|
|
92
|
+
update = updater(source);
|
|
74
93
|
patch = patcher(source);
|
|
75
|
-
|
|
94
|
+
select = selector(state, computedCache);
|
|
95
|
+
source.set(initialState({ update, get, patch, select }));
|
|
76
96
|
}
|
|
77
97
|
else {
|
|
78
98
|
source = signal(initialState, options);
|
|
99
|
+
state = source.asReadonly();
|
|
79
100
|
get = getter(source);
|
|
80
|
-
|
|
101
|
+
update = updater(source);
|
|
81
102
|
patch = patcher(source);
|
|
103
|
+
select = selector(state, computedCache);
|
|
82
104
|
}
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// NOTE: internal _snapshot to debug current state
|
|
87
|
-
Object.defineProperty(store, '_snapshot', {
|
|
88
|
-
get: state,
|
|
105
|
+
const store = { select, get, update, patch, state };
|
|
106
|
+
Object.defineProperty(store, 'snapshot', {
|
|
107
|
+
get: untracked.bind({}, state),
|
|
89
108
|
configurable: false,
|
|
90
109
|
enumerable: false,
|
|
91
110
|
});
|
|
92
111
|
return store;
|
|
93
112
|
}
|
|
94
|
-
function parseStoreOptions(keysAndOptions) {
|
|
95
|
-
if (typeof keysAndOptions.at(-1) === 'object') {
|
|
96
|
-
return [keysAndOptions.slice(0, -1), keysAndOptions.at(-1)];
|
|
97
|
-
}
|
|
98
|
-
return [keysAndOptions];
|
|
99
|
-
}
|
|
100
113
|
|
|
101
114
|
const is = {
|
|
102
115
|
obj: (a) => a === Object(a) && !Array.isArray(a) && typeof a !== 'function',
|
|
@@ -105,11 +118,13 @@ const is = {
|
|
|
105
118
|
orthographicCamera: (a) => !!a && a.isOrthographicCamera,
|
|
106
119
|
perspectiveCamera: (a) => !!a && a.isPerspectiveCamera,
|
|
107
120
|
camera: (a) => !!a && a.isCamera,
|
|
108
|
-
renderer: (a) => !!a && a
|
|
121
|
+
renderer: (a) => !!a && typeof a === 'object' && 'render' in a && typeof a['render'] === 'function',
|
|
109
122
|
scene: (a) => !!a && a.isScene,
|
|
110
|
-
object3D: (a) => !!a && a.isObject3D,
|
|
111
|
-
instance: (a) => !!a && !!a['__ngt__'],
|
|
112
123
|
ref: (a) => a instanceof ElementRef,
|
|
124
|
+
instance: (a) => !!a && !!a['__ngt__'],
|
|
125
|
+
object3D: (a) => !!a && a.isObject3D,
|
|
126
|
+
// instance: (a: unknown): a is NgtInstanceNode => !!a && !!(a as NgtAnyRecord)['__ngt__'],
|
|
127
|
+
// ref: (a: unknown): a is ElementRef => a instanceof ElementRef,
|
|
113
128
|
colorSpaceExist: (object) => 'colorSpace' in object || 'outputColorSpace' in object,
|
|
114
129
|
equ(a, b, { arrays = 'shallow', objects = 'reference', strict = true } = {}) {
|
|
115
130
|
// Wrong type or one of the two undefined, doesn't match
|
|
@@ -172,8 +187,9 @@ function updateCamera(camera, size) {
|
|
|
172
187
|
camera.top = size.height / 2;
|
|
173
188
|
camera.bottom = size.height / -2;
|
|
174
189
|
}
|
|
175
|
-
else
|
|
190
|
+
else {
|
|
176
191
|
camera.aspect = size.width / size.height;
|
|
192
|
+
}
|
|
177
193
|
camera.updateProjectionMatrix();
|
|
178
194
|
camera.updateMatrixWorld();
|
|
179
195
|
}
|
|
@@ -181,11 +197,11 @@ function updateCamera(camera, size) {
|
|
|
181
197
|
|
|
182
198
|
function getLocalState(obj) {
|
|
183
199
|
if (!obj)
|
|
184
|
-
return
|
|
185
|
-
return obj['__ngt__']
|
|
200
|
+
return undefined;
|
|
201
|
+
return obj['__ngt__'];
|
|
186
202
|
}
|
|
187
203
|
function invalidateInstance(instance) {
|
|
188
|
-
const state = getLocalState(instance).
|
|
204
|
+
const state = getLocalState(instance)?.store.snapshot;
|
|
189
205
|
if (state && state.internal.frames === 0)
|
|
190
206
|
state.invalidate();
|
|
191
207
|
checkUpdate(instance);
|
|
@@ -193,37 +209,44 @@ function invalidateInstance(instance) {
|
|
|
193
209
|
function prepare(object, localState) {
|
|
194
210
|
const instance = object;
|
|
195
211
|
if (localState?.primitive || !instance.__ngt__) {
|
|
196
|
-
const {
|
|
212
|
+
const { instanceStore = signalStore({
|
|
213
|
+
nativeProps: {},
|
|
214
|
+
parent: null,
|
|
215
|
+
objects: [],
|
|
216
|
+
nonObjects: [],
|
|
217
|
+
}), ...rest } = localState || {};
|
|
197
218
|
instance.__ngt__ = {
|
|
198
219
|
previousAttach: null,
|
|
199
220
|
store: null,
|
|
200
|
-
parent: signal(null),
|
|
201
221
|
memoized: {},
|
|
202
222
|
eventCount: 0,
|
|
203
223
|
handlers: {},
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
});
|
|
224
|
+
instanceStore,
|
|
225
|
+
parent: instanceStore.select('parent'),
|
|
226
|
+
objects: instanceStore.select('objects'),
|
|
227
|
+
nonObjects: instanceStore.select('nonObjects'),
|
|
228
|
+
nativeProps: instanceStore.select('nativeProps'),
|
|
229
|
+
add(object, type) {
|
|
230
|
+
const current = instance.__ngt__.instanceStore.get(type);
|
|
231
|
+
const foundIndex = current.indexOf((node) => object === node);
|
|
232
|
+
if (foundIndex > -1) {
|
|
233
|
+
current.splice(foundIndex, 1, object);
|
|
234
|
+
instance.__ngt__.instanceStore.update({ [type]: current });
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
instance.__ngt__.instanceStore.update((prev) => ({ [type]: [...prev[type], object] }));
|
|
238
|
+
}
|
|
239
|
+
notifyAncestors(instance.__ngt__.instanceStore.get('parent'));
|
|
221
240
|
},
|
|
222
|
-
remove
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
241
|
+
remove(object, type) {
|
|
242
|
+
instance.__ngt__.instanceStore.update((prev) => ({ [type]: prev[type].filter((node) => node !== object) }));
|
|
243
|
+
notifyAncestors(instance.__ngt__.instanceStore.get('parent'));
|
|
244
|
+
},
|
|
245
|
+
setNativeProps(key, value) {
|
|
246
|
+
instance.__ngt__.instanceStore.update((prev) => ({ nativeProps: { ...prev.nativeProps, [key]: value } }));
|
|
247
|
+
},
|
|
248
|
+
setParent(parent) {
|
|
249
|
+
instance.__ngt__.instanceStore.update({ parent });
|
|
227
250
|
},
|
|
228
251
|
...rest,
|
|
229
252
|
};
|
|
@@ -234,146 +257,10 @@ function notifyAncestors(instance) {
|
|
|
234
257
|
if (!instance)
|
|
235
258
|
return;
|
|
236
259
|
const localState = getLocalState(instance);
|
|
237
|
-
if (localState
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
notifyAncestors(localState.parent());
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// This function prepares a set of changes to be applied to the instance
|
|
245
|
-
function diffProps(instance, props) {
|
|
246
|
-
const propsEntries = Object.entries(props);
|
|
247
|
-
const changes = [];
|
|
248
|
-
for (const [propKey, propValue] of propsEntries) {
|
|
249
|
-
let key = propKey;
|
|
250
|
-
if (is.colorSpaceExist(instance)) {
|
|
251
|
-
if (propKey === 'encoding') {
|
|
252
|
-
key = 'colorSpace';
|
|
253
|
-
}
|
|
254
|
-
else if (propKey === 'outputEncoding') {
|
|
255
|
-
key = 'outputColorSpace';
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
if (is.equ(propValue, instance[key]))
|
|
259
|
-
continue;
|
|
260
|
-
changes.push([propKey, propValue]);
|
|
261
|
-
}
|
|
262
|
-
return changes;
|
|
263
|
-
}
|
|
264
|
-
// This function applies a set of changes to the instance
|
|
265
|
-
function applyProps(instance, props) {
|
|
266
|
-
// if props is empty
|
|
267
|
-
if (!Object.keys(props).length)
|
|
268
|
-
return instance;
|
|
269
|
-
// Filter equals, events and reserved props
|
|
270
|
-
// filter equals, events , and reserved props
|
|
271
|
-
const localState = getLocalState(instance);
|
|
272
|
-
const rootState = localState.store?.get();
|
|
273
|
-
const changes = diffProps(instance, props);
|
|
274
|
-
for (let i = 0; i < changes.length; i++) {
|
|
275
|
-
let [key, value] = changes[i];
|
|
276
|
-
// Alias (output)encoding => (output)colorSpace (since r152)
|
|
277
|
-
// https://github.com/pmndrs/react-three-fiber/pull/2829
|
|
278
|
-
if (is.colorSpaceExist(instance)) {
|
|
279
|
-
const sRGBEncoding = 3001;
|
|
280
|
-
const SRGBColorSpace = 'srgb';
|
|
281
|
-
const LinearSRGBColorSpace = 'srgb-linear';
|
|
282
|
-
if (key === 'encoding') {
|
|
283
|
-
key = 'colorSpace';
|
|
284
|
-
value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
|
|
285
|
-
}
|
|
286
|
-
else if (key === 'outputEncoding') {
|
|
287
|
-
key = 'outputColorSpace';
|
|
288
|
-
value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
const currentInstance = instance;
|
|
292
|
-
const targetProp = currentInstance[key];
|
|
293
|
-
// special treatmen for objects with support for set/copy, and layers
|
|
294
|
-
if (targetProp && targetProp['set'] && (targetProp['copy'] || targetProp instanceof THREE.Layers)) {
|
|
295
|
-
const isColor = targetProp instanceof THREE.Color;
|
|
296
|
-
// if value is an array
|
|
297
|
-
if (Array.isArray(value)) {
|
|
298
|
-
if (targetProp['fromArray'])
|
|
299
|
-
targetProp['fromArray'](value);
|
|
300
|
-
else
|
|
301
|
-
targetProp['set'](...value);
|
|
302
|
-
}
|
|
303
|
-
// test again target.copy
|
|
304
|
-
else if (targetProp['copy'] &&
|
|
305
|
-
value &&
|
|
306
|
-
value.constructor &&
|
|
307
|
-
targetProp.constructor.name === value.constructor.name) {
|
|
308
|
-
targetProp['copy'](value);
|
|
309
|
-
if (!THREE.ColorManagement && !rootState.linear && isColor)
|
|
310
|
-
targetProp['convertSRGBToLinear']();
|
|
311
|
-
}
|
|
312
|
-
// if nothing else fits, just set the single value, ignore undefined
|
|
313
|
-
else if (value !== undefined) {
|
|
314
|
-
const isColor = targetProp instanceof THREE.Color;
|
|
315
|
-
// allow setting array scalars
|
|
316
|
-
if (!isColor && targetProp['setScalar'])
|
|
317
|
-
targetProp['setScalar'](value);
|
|
318
|
-
// layers have no copy function, copy the mask
|
|
319
|
-
else if (targetProp instanceof THREE.Layers && value instanceof THREE.Layers)
|
|
320
|
-
targetProp.mask = value.mask;
|
|
321
|
-
// otherwise just set ...
|
|
322
|
-
else
|
|
323
|
-
targetProp['set'](value);
|
|
324
|
-
// auto-convert srgb
|
|
325
|
-
if (!THREE.ColorManagement && !rootState?.linear && isColor)
|
|
326
|
-
targetProp.convertSRGBToLinear();
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
// else just overwrite the value
|
|
330
|
-
else {
|
|
331
|
-
currentInstance[key] = value;
|
|
332
|
-
// auto-convert srgb textures
|
|
333
|
-
if (currentInstance[key] instanceof THREE.Texture &&
|
|
334
|
-
currentInstance[key].format === THREE.RGBAFormat &&
|
|
335
|
-
currentInstance[key].type === THREE.UnsignedByteType) {
|
|
336
|
-
const texture = currentInstance[key];
|
|
337
|
-
if (rootState?.gl) {
|
|
338
|
-
if (is.colorSpaceExist(texture) && is.colorSpaceExist(rootState.gl))
|
|
339
|
-
texture.colorSpace = rootState.gl.outputColorSpace;
|
|
340
|
-
else
|
|
341
|
-
texture.encoding = rootState.gl.outputEncoding;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
checkUpdate(currentInstance[key]);
|
|
346
|
-
checkUpdate(targetProp);
|
|
347
|
-
invalidateInstance(instance);
|
|
348
|
-
}
|
|
349
|
-
const instanceHandlers = localState.eventCount;
|
|
350
|
-
const parent = localState.parent ? untracked(localState.parent) : null;
|
|
351
|
-
if (parent && rootState.internal && instance['raycast'] && instanceHandlers !== localState.eventCount) {
|
|
352
|
-
// Pre-emptively remove the instance from the interaction manager
|
|
353
|
-
const index = rootState.internal.interaction.indexOf(instance);
|
|
354
|
-
if (index > -1)
|
|
355
|
-
rootState.internal.interaction.splice(index, 1);
|
|
356
|
-
// Add the instance to the interaction manager only when it has handlers
|
|
357
|
-
if (localState.eventCount)
|
|
358
|
-
rootState.internal.interaction.push(instance);
|
|
359
|
-
}
|
|
360
|
-
if (parent && localState.afterUpdate && localState.afterUpdate.observed && changes.length) {
|
|
361
|
-
localState.afterUpdate.emit(instance);
|
|
362
|
-
}
|
|
363
|
-
return instance;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function assertInjectionContext(fn, injector) {
|
|
367
|
-
try {
|
|
368
|
-
if (!injector) {
|
|
369
|
-
return inject(Injector);
|
|
370
|
-
}
|
|
371
|
-
return injector;
|
|
372
|
-
}
|
|
373
|
-
catch {
|
|
374
|
-
!injector && assertInInjectionContext(fn);
|
|
375
|
-
return null;
|
|
376
|
-
}
|
|
260
|
+
if (!localState)
|
|
261
|
+
return;
|
|
262
|
+
localState.instanceStore.update((prev) => ({ objects: prev.objects, nonObjects: prev.nonObjects }));
|
|
263
|
+
notifyAncestors(localState.instanceStore.get('parent'));
|
|
377
264
|
}
|
|
378
265
|
|
|
379
266
|
const idCache = {};
|
|
@@ -390,26 +277,28 @@ function makeId(event) {
|
|
|
390
277
|
return makeId();
|
|
391
278
|
}
|
|
392
279
|
function makeDpr(dpr, window) {
|
|
393
|
-
|
|
280
|
+
// Err on the side of progress by assuming 2x dpr if we can't detect it
|
|
281
|
+
// This will happen in workers where window is defined but dpr isn't.
|
|
282
|
+
const target = typeof window !== 'undefined' ? window.devicePixelRatio ?? 2 : 1;
|
|
394
283
|
return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
|
|
395
284
|
}
|
|
396
|
-
function
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
return new THREE.PerspectiveCamera(75, size.width / size.height, 0.1, 1000);
|
|
400
|
-
}
|
|
401
|
-
function makeDefaultRenderer(glOptions, canvasElement) {
|
|
402
|
-
const customRenderer = (typeof glOptions === 'function' ? glOptions(canvasElement) : glOptions);
|
|
403
|
-
if (customRenderer?.render != null)
|
|
285
|
+
function makeRendererInstance(glOptions, canvas) {
|
|
286
|
+
const customRenderer = (typeof glOptions === 'function' ? glOptions(canvas) : glOptions);
|
|
287
|
+
if (is.renderer(customRenderer))
|
|
404
288
|
return customRenderer;
|
|
405
289
|
return new THREE.WebGLRenderer({
|
|
406
290
|
powerPreference: 'high-performance',
|
|
407
|
-
canvas:
|
|
291
|
+
canvas: canvas,
|
|
408
292
|
antialias: true,
|
|
409
293
|
alpha: true,
|
|
410
|
-
...
|
|
294
|
+
...glOptions,
|
|
411
295
|
});
|
|
412
296
|
}
|
|
297
|
+
function makeCameraInstance(isOrthographic, size) {
|
|
298
|
+
if (isOrthographic)
|
|
299
|
+
return new THREE.OrthographicCamera(0, 0, 0, 0, 0.1, 1000);
|
|
300
|
+
return new THREE.PerspectiveCamera(75, size.width / size.height, 0.1, 1000);
|
|
301
|
+
}
|
|
413
302
|
function makeObjectGraph(object) {
|
|
414
303
|
const data = { nodes: {}, materials: {} };
|
|
415
304
|
if (object) {
|
|
@@ -425,706 +314,62 @@ function makeObjectGraph(object) {
|
|
|
425
314
|
return data;
|
|
426
315
|
}
|
|
427
316
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
let raycaster = state.raycaster;
|
|
485
|
-
if (!raycaster)
|
|
486
|
-
stateToUpdate.raycaster = raycaster = new THREE.Raycaster();
|
|
487
|
-
// set raycaster options
|
|
488
|
-
const { params, ...options } = raycasterOptions || {};
|
|
489
|
-
if (!is.equ(options, raycaster, shallowLoose))
|
|
490
|
-
applyProps(raycaster, { ...options });
|
|
491
|
-
if (!is.equ(params, raycaster.params, shallowLoose)) {
|
|
492
|
-
applyProps(raycaster, { params: { ...raycaster.params, ...(params || {}) } });
|
|
493
|
-
}
|
|
494
|
-
// create default camera
|
|
495
|
-
if (!state.camera) {
|
|
496
|
-
const isCamera = is.camera(cameraOptions);
|
|
497
|
-
let camera = isCamera ? cameraOptions : makeDefaultCamera(orthographic || false, state.size);
|
|
498
|
-
if (!isCamera) {
|
|
499
|
-
if (cameraOptions)
|
|
500
|
-
applyProps(camera, cameraOptions);
|
|
501
|
-
// set position.z
|
|
502
|
-
if (!cameraOptions?.position)
|
|
503
|
-
camera.position.z = 5;
|
|
504
|
-
// always look at center or passed-in lookAt by default
|
|
505
|
-
if (!cameraOptions?.rotation && !cameraOptions?.quaternion) {
|
|
506
|
-
if (Array.isArray(lookAt))
|
|
507
|
-
camera.lookAt(lookAt[0], lookAt[1], lookAt[2]);
|
|
508
|
-
else if (lookAt instanceof THREE.Vector3)
|
|
509
|
-
camera.lookAt(lookAt);
|
|
510
|
-
else
|
|
511
|
-
camera.lookAt(0, 0, 0);
|
|
512
|
-
}
|
|
513
|
-
// update projection matrix after applyprops
|
|
514
|
-
camera.updateProjectionMatrix?.();
|
|
515
|
-
}
|
|
516
|
-
if (!is.instance(camera))
|
|
517
|
-
camera = prepare(camera, { store });
|
|
518
|
-
stateToUpdate.camera = camera;
|
|
519
|
-
}
|
|
520
|
-
// Set up scene (one time only!)
|
|
521
|
-
if (!state.scene) {
|
|
522
|
-
let scene;
|
|
523
|
-
if (sceneOptions instanceof THREE.Scene) {
|
|
524
|
-
scene = prepare(sceneOptions, { store });
|
|
525
|
-
}
|
|
526
|
-
else {
|
|
527
|
-
scene = prepare(new THREE.Scene(), { store });
|
|
528
|
-
if (sceneOptions)
|
|
529
|
-
applyProps(scene, sceneOptions);
|
|
530
|
-
}
|
|
531
|
-
stateToUpdate.scene = scene;
|
|
532
|
-
}
|
|
533
|
-
// Set up XR (one time only!)
|
|
534
|
-
if (!state.xr) {
|
|
535
|
-
// Handle frame behavior in WebXR
|
|
536
|
-
const handleXRFrame = (timestamp, frame) => {
|
|
537
|
-
const state = store.get();
|
|
538
|
-
if (state.frameloop === 'never')
|
|
539
|
-
return;
|
|
540
|
-
loop.advance(timestamp, true, store, frame);
|
|
541
|
-
};
|
|
542
|
-
// Toggle render switching on session
|
|
543
|
-
const handleSessionChange = () => {
|
|
544
|
-
const state = store.get();
|
|
545
|
-
state.gl.xr.enabled = state.gl.xr.isPresenting;
|
|
546
|
-
state.gl.xr.setAnimationLoop(state.gl.xr.isPresenting ? handleXRFrame : null);
|
|
547
|
-
if (!state.gl.xr.isPresenting)
|
|
548
|
-
loop.invalidate(store);
|
|
549
|
-
};
|
|
550
|
-
// WebXR session manager
|
|
551
|
-
const xr = {
|
|
552
|
-
connect: () => {
|
|
553
|
-
gl.xr.addEventListener('sessionstart', handleSessionChange);
|
|
554
|
-
gl.xr.addEventListener('sessionend', handleSessionChange);
|
|
555
|
-
},
|
|
556
|
-
disconnect: () => {
|
|
557
|
-
gl.xr.removeEventListener('sessionstart', handleSessionChange);
|
|
558
|
-
gl.xr.removeEventListener('sessionend', handleSessionChange);
|
|
559
|
-
},
|
|
560
|
-
};
|
|
561
|
-
// Subscribe to WebXR session events
|
|
562
|
-
if (gl.xr && typeof gl.xr.addEventListener === 'function')
|
|
563
|
-
xr.connect();
|
|
564
|
-
stateToUpdate.xr = xr;
|
|
565
|
-
}
|
|
566
|
-
// Set shadowmap
|
|
567
|
-
if (gl.shadowMap) {
|
|
568
|
-
const oldEnabled = gl.shadowMap.enabled;
|
|
569
|
-
const oldType = gl.shadowMap.type;
|
|
570
|
-
gl.shadowMap.enabled = !!shadows;
|
|
571
|
-
if (typeof shadows === 'boolean') {
|
|
572
|
-
gl.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
573
|
-
}
|
|
574
|
-
else if (typeof shadows === 'string') {
|
|
575
|
-
const types = {
|
|
576
|
-
basic: THREE.BasicShadowMap,
|
|
577
|
-
percentage: THREE.PCFShadowMap,
|
|
578
|
-
soft: THREE.PCFSoftShadowMap,
|
|
579
|
-
variance: THREE.VSMShadowMap,
|
|
580
|
-
};
|
|
581
|
-
gl.shadowMap.type = types[shadows] ?? THREE.PCFSoftShadowMap;
|
|
582
|
-
}
|
|
583
|
-
else if (is.obj(shadows)) {
|
|
584
|
-
Object.assign(gl.shadowMap, shadows);
|
|
585
|
-
}
|
|
586
|
-
if (oldEnabled !== gl.shadowMap.enabled || oldType !== gl.shadowMap.type)
|
|
587
|
-
checkNeedsUpdate(gl.shadowMap);
|
|
588
|
-
}
|
|
589
|
-
// Safely set color management if available.
|
|
590
|
-
// Avoid accessing THREE.ColorManagement to play nice with older versions
|
|
591
|
-
if (THREE.ColorManagement) {
|
|
592
|
-
const ColorManagement = THREE.ColorManagement;
|
|
593
|
-
if ('enabled' in ColorManagement)
|
|
594
|
-
ColorManagement['enabled'] = !legacy ?? false;
|
|
595
|
-
else if ('legacyMode' in ColorManagement)
|
|
596
|
-
ColorManagement['legacyMode'] = legacy ?? true;
|
|
597
|
-
}
|
|
598
|
-
// set color space and tonemapping preferences
|
|
599
|
-
const LinearEncoding = 3000;
|
|
600
|
-
const sRGBEncoding = 3001;
|
|
601
|
-
applyProps(gl, {
|
|
602
|
-
outputEncoding: linear ? LinearEncoding : sRGBEncoding,
|
|
603
|
-
toneMapping: flat ? THREE.NoToneMapping : THREE.ACESFilmicToneMapping,
|
|
604
|
-
});
|
|
605
|
-
// Update color management state
|
|
606
|
-
if (state.legacy !== legacy)
|
|
607
|
-
stateToUpdate.legacy = legacy;
|
|
608
|
-
if (state.linear !== linear)
|
|
609
|
-
stateToUpdate.linear = linear;
|
|
610
|
-
if (state.flat !== flat)
|
|
611
|
-
stateToUpdate.flat = flat;
|
|
612
|
-
// Set gl props
|
|
613
|
-
gl.setClearAlpha(0);
|
|
614
|
-
gl.setPixelRatio(makeDpr(state.viewport.dpr));
|
|
615
|
-
gl.setSize(state.size.width, state.size.height);
|
|
616
|
-
if (is.obj(glOptions) &&
|
|
617
|
-
!(typeof glOptions === 'function') &&
|
|
618
|
-
!is.renderer(glOptions) &&
|
|
619
|
-
!is.equ(glOptions, gl, shallowLoose)) {
|
|
620
|
-
applyProps(gl, glOptions);
|
|
621
|
-
}
|
|
622
|
-
// Store events internally
|
|
623
|
-
if (events && !state.events.handlers)
|
|
624
|
-
stateToUpdate.events = events(store);
|
|
625
|
-
// Check performance
|
|
626
|
-
if (performance && !is.equ(performance, state.performance, shallowLoose)) {
|
|
627
|
-
stateToUpdate.performance = { ...state.performance, ...performance };
|
|
628
|
-
}
|
|
629
|
-
store.set(stateToUpdate);
|
|
630
|
-
// Check size, allow it to take on container bounds initially
|
|
631
|
-
const size = computeInitialSize(canvas, sizeOptions);
|
|
632
|
-
if (!is.equ(size, state.size, shallowLoose)) {
|
|
633
|
-
state.setSize(size.width, size.height, size.top, size.left);
|
|
634
|
-
}
|
|
635
|
-
// Check pixelratio
|
|
636
|
-
if (dpr && state.viewport.dpr !== makeDpr(dpr))
|
|
637
|
-
state.setDpr(dpr);
|
|
638
|
-
// Check frameloop
|
|
639
|
-
if (state.frameloop !== frameloop)
|
|
640
|
-
state.setFrameloop(frameloop);
|
|
641
|
-
isConfigured = true;
|
|
642
|
-
invalidateRef?.destroy();
|
|
643
|
-
invalidateRef = effect(() => void store.state().invalidate(), { manualCleanup: true, injector });
|
|
644
|
-
},
|
|
645
|
-
};
|
|
646
|
-
};
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
function computeInitialSize(canvas, defaultSize) {
|
|
650
|
-
if (defaultSize)
|
|
651
|
-
return defaultSize;
|
|
652
|
-
if (canvas instanceof HTMLCanvasElement && canvas.parentElement) {
|
|
653
|
-
return canvas.parentElement.getBoundingClientRect();
|
|
654
|
-
}
|
|
655
|
-
return { width: 0, height: 0, top: 0, left: 0 };
|
|
656
|
-
}
|
|
657
|
-
// Disposes an object and all its properties
|
|
658
|
-
function dispose(obj) {
|
|
659
|
-
if (obj.dispose && obj.type !== 'Scene')
|
|
660
|
-
obj.dispose();
|
|
661
|
-
for (const p in obj) {
|
|
662
|
-
p.dispose?.();
|
|
663
|
-
delete obj[p];
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
function createInjectFn(token) {
|
|
668
|
-
return (injectOptions) => inject(token, injectOptions);
|
|
669
|
-
}
|
|
670
|
-
function createProvideFn(token, factory, deps, extraProviders) {
|
|
671
|
-
return (value) => {
|
|
672
|
-
let provider;
|
|
673
|
-
if (value) {
|
|
674
|
-
provider = { provide: token, useValue: value };
|
|
675
|
-
}
|
|
676
|
-
else {
|
|
677
|
-
provider = { provide: token, useFactory: factory, deps: (deps ?? []) };
|
|
678
|
-
}
|
|
679
|
-
return extraProviders ? [extraProviders, provider] : provider;
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
function createInjectionToken(factory, options) {
|
|
683
|
-
const opts = options ?? { isRoot: true };
|
|
684
|
-
opts.isRoot ??= true;
|
|
685
|
-
if (opts.isRoot) {
|
|
686
|
-
if (opts.token) {
|
|
687
|
-
throw new Error(`\
|
|
688
|
-
createInjectionToken is creating a root InjectionToken but an external token is passed in.
|
|
689
|
-
`);
|
|
690
|
-
}
|
|
691
|
-
const token = new InjectionToken(`Token for ${factory.name}`, {
|
|
692
|
-
factory: () => {
|
|
693
|
-
if (opts.deps && Array.isArray(opts.deps)) {
|
|
694
|
-
return factory(...opts.deps.map((dep) => inject(dep)));
|
|
695
|
-
}
|
|
696
|
-
return factory();
|
|
697
|
-
},
|
|
698
|
-
});
|
|
699
|
-
return [
|
|
700
|
-
createInjectFn(token),
|
|
701
|
-
createProvideFn(token, factory, opts.deps),
|
|
702
|
-
token,
|
|
703
|
-
];
|
|
704
|
-
}
|
|
705
|
-
const token = opts.token || new InjectionToken(`Token for ${factory.name}`);
|
|
706
|
-
return [
|
|
707
|
-
createInjectFn(token),
|
|
708
|
-
createProvideFn(token, factory, opts.deps, opts.extraProviders),
|
|
709
|
-
token,
|
|
710
|
-
];
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
function createSubs(callback, subs) {
|
|
714
|
-
const sub = { callback };
|
|
715
|
-
subs.add(sub);
|
|
716
|
-
return () => void subs.delete(sub);
|
|
717
|
-
}
|
|
718
|
-
const globalEffects = new Set();
|
|
719
|
-
const globalAfterEffects = new Set();
|
|
720
|
-
const globalTailEffects = new Set();
|
|
721
|
-
/**
|
|
722
|
-
* Adds a global render callback which is called each frame.
|
|
723
|
-
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addEffect
|
|
724
|
-
*/
|
|
725
|
-
const addEffect = (callback) => createSubs(callback, globalEffects);
|
|
726
|
-
/**
|
|
727
|
-
* Adds a global after-render callback which is called each frame.
|
|
728
|
-
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addAfterEffect
|
|
729
|
-
*/
|
|
730
|
-
const addAfterEffect = (callback) => createSubs(callback, globalAfterEffects);
|
|
731
|
-
/**
|
|
732
|
-
* Adds a global callback which is called when rendering stops.
|
|
733
|
-
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addTail
|
|
734
|
-
*/
|
|
735
|
-
const addTail = (callback) => createSubs(callback, globalTailEffects);
|
|
736
|
-
function run(effects, timestamp) {
|
|
737
|
-
if (!effects.size)
|
|
738
|
-
return;
|
|
739
|
-
for (const { callback } of effects.values()) {
|
|
740
|
-
callback(timestamp);
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
function flushGlobalEffects(type, timestamp) {
|
|
744
|
-
switch (type) {
|
|
745
|
-
case 'before':
|
|
746
|
-
return run(globalEffects, timestamp);
|
|
747
|
-
case 'after':
|
|
748
|
-
return run(globalAfterEffects, timestamp);
|
|
749
|
-
case 'tail':
|
|
750
|
-
return run(globalTailEffects, timestamp);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
function render(timestamp, store, frame) {
|
|
754
|
-
const state = store.get();
|
|
755
|
-
// Run local effects
|
|
756
|
-
let delta = state.clock.getDelta();
|
|
757
|
-
// In frameloop='never' mode, clock times are updated using the provided timestamp
|
|
758
|
-
if (state.frameloop === 'never' && typeof timestamp === 'number') {
|
|
759
|
-
delta = timestamp - state.clock.elapsedTime;
|
|
760
|
-
state.clock.oldTime = state.clock.elapsedTime;
|
|
761
|
-
state.clock.elapsedTime = timestamp;
|
|
762
|
-
}
|
|
763
|
-
// Call subscribers (useFrame)
|
|
764
|
-
const subscribers = state.internal.subscribers;
|
|
765
|
-
for (let i = 0; i < subscribers.length; i++) {
|
|
766
|
-
const subscription = subscribers[i];
|
|
767
|
-
subscription.callback({ ...subscription.store.get(), delta, frame });
|
|
768
|
-
}
|
|
769
|
-
// Render content
|
|
770
|
-
if (!state.internal.priority && state.gl.render)
|
|
771
|
-
state.gl.render(state.scene, state.camera);
|
|
772
|
-
// Decrease frame count
|
|
773
|
-
state.internal.frames = Math.max(0, state.internal.frames - 1);
|
|
774
|
-
return state.frameloop === 'always' ? 1 : state.internal.frames;
|
|
775
|
-
}
|
|
776
|
-
function createLoop(roots) {
|
|
777
|
-
let running = false;
|
|
778
|
-
let repeat;
|
|
779
|
-
let frame;
|
|
780
|
-
function loop(timestamp) {
|
|
781
|
-
frame = requestAnimationFrame(loop);
|
|
782
|
-
running = true;
|
|
783
|
-
repeat = 0;
|
|
784
|
-
// Run effects
|
|
785
|
-
flushGlobalEffects('before', timestamp);
|
|
786
|
-
// Render all roots
|
|
787
|
-
for (const root of roots.values()) {
|
|
788
|
-
const state = root.get();
|
|
789
|
-
// If the frameloop is invalidated, do not run another frame
|
|
790
|
-
if (state.internal.active &&
|
|
791
|
-
(state.frameloop === 'always' || state.internal.frames > 0) &&
|
|
792
|
-
!state.gl.xr?.isPresenting) {
|
|
793
|
-
repeat += render(timestamp, root);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
// Run after-effects
|
|
797
|
-
flushGlobalEffects('after', timestamp);
|
|
798
|
-
// Stop the loop if nothing invalidates it
|
|
799
|
-
if (repeat === 0) {
|
|
800
|
-
// Tail call effects, they are called when rendering stops
|
|
801
|
-
flushGlobalEffects('tail', timestamp);
|
|
802
|
-
// Flag end of operation
|
|
803
|
-
running = false;
|
|
804
|
-
return cancelAnimationFrame(frame);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
function invalidate(store, frames = 1) {
|
|
808
|
-
const state = store?.get();
|
|
809
|
-
if (!state)
|
|
810
|
-
return roots.forEach((root) => invalidate(root, frames));
|
|
811
|
-
if (state.gl.xr?.isPresenting || !state.internal.active || state.frameloop === 'never')
|
|
812
|
-
return;
|
|
813
|
-
// Increase frames, do not go higher than 60
|
|
814
|
-
state.internal.frames = Math.min(60, state.internal.frames + frames);
|
|
815
|
-
// If the render-loop isn't active, start it
|
|
816
|
-
if (!running) {
|
|
817
|
-
running = true;
|
|
818
|
-
requestAnimationFrame(loop);
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
function advance(timestamp, runGlobalEffects = true, store, frame) {
|
|
822
|
-
if (runGlobalEffects)
|
|
823
|
-
flushGlobalEffects('before', timestamp);
|
|
824
|
-
const state = store?.get();
|
|
825
|
-
if (!state)
|
|
826
|
-
for (const root of roots.values())
|
|
827
|
-
render(timestamp, root);
|
|
828
|
-
else
|
|
829
|
-
render(timestamp, store, frame);
|
|
830
|
-
if (runGlobalEffects)
|
|
831
|
-
flushGlobalEffects('after', timestamp);
|
|
832
|
-
}
|
|
833
|
-
return {
|
|
834
|
-
loop,
|
|
835
|
-
/**
|
|
836
|
-
* Invalidates the view, requesting a frame to be rendered. Will globally invalidate unless passed a root's state.
|
|
837
|
-
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#invalidate
|
|
838
|
-
*/
|
|
839
|
-
invalidate,
|
|
840
|
-
/**
|
|
841
|
-
* Advances the frameloop and runs render effects, useful for when manually rendering via `frameloop="never"`.
|
|
842
|
-
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#advance
|
|
843
|
-
*/
|
|
844
|
-
advance,
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
const [injectNgtLoop, , NGT_LOOP] = createInjectionToken(() => createLoop(roots));
|
|
848
|
-
|
|
849
|
-
function safeDetectChanges(...cdrs) {
|
|
850
|
-
cdrs.forEach((cdr) => {
|
|
851
|
-
if (!cdr)
|
|
852
|
-
return;
|
|
853
|
-
try {
|
|
854
|
-
// dynamic created component with ViewContainerRef#createComponent does not have Context
|
|
855
|
-
// but it has _attachedToViewContainer
|
|
856
|
-
if (cdr['_attachedToViewContainer'] || !!cdr['context']) {
|
|
857
|
-
cdr.detectChanges();
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
catch (e) {
|
|
861
|
-
cdr.markForCheck();
|
|
862
|
-
}
|
|
863
|
-
});
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
function storeFactory(loop, document, injector, parent) {
|
|
867
|
-
return runInInjectionContext(injector, () => {
|
|
868
|
-
const window = document.defaultView;
|
|
869
|
-
if (!window) {
|
|
870
|
-
// TODO: revisit this when we need to support multiple platforms
|
|
871
|
-
throw new Error(`[NGT] Window is not available.`);
|
|
872
|
-
}
|
|
873
|
-
const cdr = inject(ChangeDetectorRef);
|
|
874
|
-
// NOTE: using Subject because we do not care about late-subscribers
|
|
875
|
-
const pointerMissed$ = new Subject();
|
|
876
|
-
const store = signalStore(({ get, set }) => {
|
|
877
|
-
const { invalidate, advance } = loop;
|
|
878
|
-
const position = new THREE.Vector3();
|
|
879
|
-
const defaultTarget = new THREE.Vector3();
|
|
880
|
-
const tempTarget = new THREE.Vector3();
|
|
881
|
-
function getCurrentViewport(camera = get('camera'), target = defaultTarget, size = get('size')) {
|
|
882
|
-
const { width, height, top, left } = size;
|
|
883
|
-
const aspect = width / height;
|
|
884
|
-
if (target instanceof THREE.Vector3)
|
|
885
|
-
tempTarget.copy(target);
|
|
886
|
-
else
|
|
887
|
-
tempTarget.set(...target);
|
|
888
|
-
const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
|
|
889
|
-
if (is.orthographicCamera(camera)) {
|
|
890
|
-
return {
|
|
891
|
-
width: width / camera.zoom,
|
|
892
|
-
height: height / camera.zoom,
|
|
893
|
-
top,
|
|
894
|
-
left,
|
|
895
|
-
factor: 1,
|
|
896
|
-
distance,
|
|
897
|
-
aspect,
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
else {
|
|
901
|
-
const fov = (camera.fov * Math.PI) / 180; // convert vertical fov to radians
|
|
902
|
-
const h = 2 * Math.tan(fov / 2) * distance; // visible height
|
|
903
|
-
const w = h * (width / height);
|
|
904
|
-
return { width: w, height: h, top, left, factor: width / w, distance, aspect };
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
let performanceTimeout = undefined;
|
|
908
|
-
const setPerformanceCurrent = (current) => set((state) => ({ performance: { ...state.performance, current } }));
|
|
909
|
-
const pointer = new THREE.Vector2();
|
|
910
|
-
return {
|
|
911
|
-
pointerMissed$: pointerMissed$.asObservable(),
|
|
912
|
-
events: { priority: 1, enabled: true, connected: false },
|
|
913
|
-
invalidate: (frames = 1) => invalidate(store, frames),
|
|
914
|
-
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, store),
|
|
915
|
-
legacy: false,
|
|
916
|
-
linear: false,
|
|
917
|
-
flat: false,
|
|
918
|
-
controls: null,
|
|
919
|
-
clock: new THREE.Clock(),
|
|
920
|
-
pointer,
|
|
921
|
-
frameloop: 'always',
|
|
922
|
-
performance: {
|
|
923
|
-
current: 1,
|
|
924
|
-
min: 0.5,
|
|
925
|
-
max: 1,
|
|
926
|
-
debounce: 200,
|
|
927
|
-
regress: () => {
|
|
928
|
-
const state = get();
|
|
929
|
-
// Clear timeout
|
|
930
|
-
if (performanceTimeout)
|
|
931
|
-
clearTimeout(performanceTimeout);
|
|
932
|
-
// Set lower bound performance
|
|
933
|
-
if (state.performance.current !== state.performance.min)
|
|
934
|
-
setPerformanceCurrent(state.performance.min);
|
|
935
|
-
// Go back to upper bound performance after a while unless something regresses meanwhile
|
|
936
|
-
performanceTimeout = setTimeout(() => {
|
|
937
|
-
setPerformanceCurrent(get('performance', 'max'));
|
|
938
|
-
safeDetectChanges(cdr);
|
|
939
|
-
}, state.performance.debounce);
|
|
940
|
-
},
|
|
941
|
-
},
|
|
942
|
-
size: { width: 0, height: 0, top: 0, left: 0, updateStyle: false },
|
|
943
|
-
viewport: {
|
|
944
|
-
initialDpr: 0,
|
|
945
|
-
dpr: 0,
|
|
946
|
-
width: 0,
|
|
947
|
-
height: 0,
|
|
948
|
-
top: 0,
|
|
949
|
-
left: 0,
|
|
950
|
-
aspect: 0,
|
|
951
|
-
distance: 0,
|
|
952
|
-
factor: 0,
|
|
953
|
-
getCurrentViewport,
|
|
954
|
-
},
|
|
955
|
-
setEvents: (events) => set((state) => ({ ...state, events: { ...state.events, ...events } })),
|
|
956
|
-
setSize: (width, height, top, left) => {
|
|
957
|
-
const camera = get('camera');
|
|
958
|
-
const size = { width, height, top: top || 0, left: left || 0 };
|
|
959
|
-
set((state) => ({
|
|
960
|
-
size,
|
|
961
|
-
viewport: { ...state.viewport, ...getCurrentViewport(camera, defaultTarget, size) },
|
|
962
|
-
}));
|
|
963
|
-
},
|
|
964
|
-
setDpr: (dpr) => set((state) => {
|
|
965
|
-
const resolved = makeDpr(dpr, window);
|
|
966
|
-
return {
|
|
967
|
-
viewport: {
|
|
968
|
-
...state.viewport,
|
|
969
|
-
dpr: resolved,
|
|
970
|
-
initialDpr: state.viewport.initialDpr || resolved,
|
|
971
|
-
},
|
|
972
|
-
};
|
|
973
|
-
}),
|
|
974
|
-
setFrameloop: (frameloop = 'always') => {
|
|
975
|
-
const clock = get('clock');
|
|
976
|
-
// if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
|
|
977
|
-
clock.stop();
|
|
978
|
-
clock.elapsedTime = 0;
|
|
979
|
-
if (frameloop !== 'never') {
|
|
980
|
-
clock.start();
|
|
981
|
-
clock.elapsedTime = 0;
|
|
982
|
-
}
|
|
983
|
-
set(() => ({ frameloop }));
|
|
984
|
-
},
|
|
985
|
-
previousRoot: parent,
|
|
986
|
-
internal: {
|
|
987
|
-
active: false,
|
|
988
|
-
priority: 0,
|
|
989
|
-
frames: 0,
|
|
990
|
-
lastEvent: new ElementRef(null),
|
|
991
|
-
interaction: [],
|
|
992
|
-
hovered: new Map(),
|
|
993
|
-
subscribers: [],
|
|
994
|
-
initialClick: [0, 0],
|
|
995
|
-
initialHits: [],
|
|
996
|
-
capturedMap: new Map(),
|
|
997
|
-
subscribe: (callback, priority = 0, _store = store) => {
|
|
998
|
-
const internal = get('internal');
|
|
999
|
-
// If this subscription was given a priority, it takes rendering into its own hands
|
|
1000
|
-
// For that reason we switch off automatic rendering and increase the manual flag
|
|
1001
|
-
// As long as this flag is positive there can be no internal rendering at all
|
|
1002
|
-
// because there could be multiple render subscriptions
|
|
1003
|
-
internal.priority = internal.priority + (priority > 0 ? 1 : 0);
|
|
1004
|
-
internal.subscribers.push({ callback, priority, store });
|
|
1005
|
-
// Register subscriber and sort layers from lowest to highest, meaning,
|
|
1006
|
-
// highest priority renders last (on top of the other frames)
|
|
1007
|
-
internal.subscribers = internal.subscribers.sort((a, b) => (a.priority || 0) - (b.priority || 0));
|
|
1008
|
-
return () => {
|
|
1009
|
-
const internal = get('internal');
|
|
1010
|
-
if (internal?.subscribers) {
|
|
1011
|
-
// Decrease manual flag if this subscription had a priority
|
|
1012
|
-
internal.priority = internal.priority - (priority > 0 ? 1 : 0);
|
|
1013
|
-
// Remove subscriber from list
|
|
1014
|
-
internal.subscribers = internal.subscribers.filter((s) => s.callback !== callback);
|
|
1015
|
-
}
|
|
1016
|
-
};
|
|
1017
|
-
},
|
|
1018
|
-
},
|
|
1019
|
-
};
|
|
1020
|
-
});
|
|
1021
|
-
// NOTE: assign pointerMissed$ so we can use it in events
|
|
1022
|
-
Object.defineProperty(store, 'pointerMissed$', { get: () => pointerMissed$ });
|
|
1023
|
-
const state = store.get();
|
|
1024
|
-
let oldSize = state.size;
|
|
1025
|
-
let oldDpr = state.viewport.dpr;
|
|
1026
|
-
let oldCamera = state.camera;
|
|
1027
|
-
const _camera = store.select('camera');
|
|
1028
|
-
const _size = store.select('size');
|
|
1029
|
-
const _viewport = store.select('viewport');
|
|
1030
|
-
effect(() => {
|
|
1031
|
-
const [camera, size, viewport, gl] = [_camera(), _size(), _viewport(), store.get('gl')];
|
|
1032
|
-
// Resize camera and renderer on changes to size and pixelratio
|
|
1033
|
-
if (size !== oldSize || viewport.dpr !== oldDpr) {
|
|
1034
|
-
oldSize = size;
|
|
1035
|
-
oldDpr = viewport.dpr;
|
|
1036
|
-
// Update camera & renderer
|
|
1037
|
-
updateCamera(camera, size);
|
|
1038
|
-
gl.setPixelRatio(viewport.dpr);
|
|
1039
|
-
const updateStyle = typeof HTMLCanvasElement !== 'undefined' && gl.domElement instanceof HTMLCanvasElement;
|
|
1040
|
-
gl.setSize(size.width, size.height, updateStyle);
|
|
1041
|
-
}
|
|
1042
|
-
// Update viewport once the camera changes
|
|
1043
|
-
if (camera !== oldCamera) {
|
|
1044
|
-
oldCamera = camera;
|
|
1045
|
-
updateCamera(camera, size);
|
|
1046
|
-
// Update viewport
|
|
1047
|
-
store.set((state) => ({
|
|
1048
|
-
viewport: { ...state.viewport, ...state.viewport.getCurrentViewport(camera) },
|
|
1049
|
-
}));
|
|
1050
|
-
}
|
|
1051
|
-
});
|
|
1052
|
-
return store;
|
|
1053
|
-
});
|
|
1054
|
-
}
|
|
1055
|
-
const NGT_STORE = new InjectionToken('NgtStore token');
|
|
1056
|
-
const [injectNgtStore, provideNgtStore] = createInjectionToken(storeFactory, {
|
|
1057
|
-
isRoot: false,
|
|
1058
|
-
deps: [NGT_LOOP, DOCUMENT, Injector, [new Optional(), new SkipSelf(), NGT_STORE]],
|
|
1059
|
-
token: NGT_STORE,
|
|
1060
|
-
});
|
|
1061
|
-
|
|
1062
|
-
function injectBeforeRender(cb, { priority = 0, injector } = {}) {
|
|
1063
|
-
injector = assertInjectionContext(injectBeforeRender, injector);
|
|
1064
|
-
return runInInjectionContext(injector, () => {
|
|
1065
|
-
const store = injectNgtStore();
|
|
1066
|
-
const sub = store.get('internal').subscribe(cb, priority, store);
|
|
1067
|
-
inject(DestroyRef).onDestroy(() => void sub());
|
|
1068
|
-
return sub;
|
|
1069
|
-
});
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
/**
|
|
1073
|
-
* Release pointer captures.
|
|
1074
|
-
* This is called by releasePointerCapture in the API, and when an object is removed.
|
|
1075
|
-
*/
|
|
1076
|
-
function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
|
|
1077
|
-
const captureData = captures.get(obj);
|
|
1078
|
-
if (captureData) {
|
|
1079
|
-
captures.delete(obj);
|
|
1080
|
-
// If this was the last capturing object for this pointer
|
|
1081
|
-
if (captures.size === 0) {
|
|
1082
|
-
capturedMap.delete(pointerId);
|
|
1083
|
-
captureData.target.releasePointerCapture(pointerId);
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
function removeInteractivity(store, object) {
|
|
1088
|
-
const { internal } = store.get();
|
|
1089
|
-
// Removes every trace of an object from the data store
|
|
1090
|
-
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
1091
|
-
internal.initialHits = internal.initialHits.filter((o) => o !== object);
|
|
1092
|
-
internal.hovered.forEach((value, key) => {
|
|
1093
|
-
if (value.eventObject === object || value.object === object) {
|
|
1094
|
-
// Clear out intersects, they are outdated by now
|
|
1095
|
-
internal.hovered.delete(key);
|
|
1096
|
-
}
|
|
1097
|
-
});
|
|
1098
|
-
internal.capturedMap.forEach((captures, pointerId) => {
|
|
1099
|
-
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
function createEvents(store) {
|
|
1103
|
-
/** Calculates delta */
|
|
1104
|
-
function calculateDistance(event) {
|
|
1105
|
-
const internal = store.get('internal');
|
|
1106
|
-
const dx = event.offsetX - internal.initialClick[0];
|
|
1107
|
-
const dy = event.offsetY - internal.initialClick[1];
|
|
1108
|
-
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
1109
|
-
}
|
|
1110
|
-
/** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
|
|
1111
|
-
function filterPointerEvents(objects) {
|
|
1112
|
-
return objects.filter((obj) => ['move', 'over', 'enter', 'out', 'leave'].some((name) => {
|
|
1113
|
-
const eventName = `pointer${name}`;
|
|
1114
|
-
return getLocalState(obj).handlers?.[eventName];
|
|
1115
|
-
}));
|
|
1116
|
-
}
|
|
1117
|
-
function intersect(event, filter) {
|
|
1118
|
-
const state = store.get();
|
|
1119
|
-
const duplicates = new Set();
|
|
1120
|
-
const intersections = [];
|
|
1121
|
-
// Allow callers to eliminate event objects
|
|
1122
|
-
const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
|
|
1123
|
-
// Reset all raycaster cameras to undefined
|
|
1124
|
-
for (let i = 0; i < eventsObjects.length; i++) {
|
|
1125
|
-
const state = getLocalState(eventsObjects[i]).store.get();
|
|
1126
|
-
if (state) {
|
|
1127
|
-
state.raycaster.camera = undefined;
|
|
317
|
+
/**
|
|
318
|
+
* Release pointer captures.
|
|
319
|
+
* This is called by releasePointerCapture in the API, and when an object is removed.
|
|
320
|
+
*/
|
|
321
|
+
function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
|
|
322
|
+
const captureData = captures.get(obj);
|
|
323
|
+
if (captureData) {
|
|
324
|
+
captures.delete(obj);
|
|
325
|
+
// If this was the last capturing object for this pointer
|
|
326
|
+
if (captures.size === 0) {
|
|
327
|
+
capturedMap.delete(pointerId);
|
|
328
|
+
captureData.target.releasePointerCapture(pointerId);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function removeInteractivity(store, object) {
|
|
333
|
+
const { internal } = store.snapshot;
|
|
334
|
+
// Removes every trace of an object from the data store
|
|
335
|
+
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
336
|
+
internal.initialHits = internal.initialHits.filter((o) => o !== object);
|
|
337
|
+
internal.hovered.forEach((value, key) => {
|
|
338
|
+
if (value.eventObject === object || value.object === object) {
|
|
339
|
+
// Clear out intersects, they are outdated by now
|
|
340
|
+
internal.hovered.delete(key);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
internal.capturedMap.forEach((captures, pointerId) => {
|
|
344
|
+
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
function createEvents(store) {
|
|
348
|
+
/** Calculates delta */
|
|
349
|
+
function calculateDistance(event) {
|
|
350
|
+
const internal = store.get('internal');
|
|
351
|
+
const dx = event.offsetX - internal.initialClick[0];
|
|
352
|
+
const dy = event.offsetY - internal.initialClick[1];
|
|
353
|
+
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
354
|
+
}
|
|
355
|
+
/** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
|
|
356
|
+
function filterPointerEvents(objects) {
|
|
357
|
+
return objects.filter((obj) => ['move', 'over', 'enter', 'out', 'leave'].some((name) => {
|
|
358
|
+
const eventName = `pointer${name}`;
|
|
359
|
+
return getLocalState(obj)?.handlers?.[eventName];
|
|
360
|
+
}));
|
|
361
|
+
}
|
|
362
|
+
function intersect(event, filter) {
|
|
363
|
+
const state = store.get();
|
|
364
|
+
const duplicates = new Set();
|
|
365
|
+
const intersections = [];
|
|
366
|
+
// Allow callers to eliminate event objects
|
|
367
|
+
const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
|
|
368
|
+
// Reset all raycaster cameras to undefined
|
|
369
|
+
for (let i = 0; i < eventsObjects.length; i++) {
|
|
370
|
+
const state = getLocalState(eventsObjects[i])?.store.snapshot;
|
|
371
|
+
if (state) {
|
|
372
|
+
state.raycaster.camera = undefined;
|
|
1128
373
|
}
|
|
1129
374
|
}
|
|
1130
375
|
if (!state.previousRoot) {
|
|
@@ -1133,8 +378,8 @@ function createEvents(store) {
|
|
|
1133
378
|
}
|
|
1134
379
|
function handleRaycast(obj) {
|
|
1135
380
|
const objLocalState = getLocalState(obj);
|
|
1136
|
-
const objStore = objLocalState
|
|
1137
|
-
const objState = objStore?.
|
|
381
|
+
const objStore = objLocalState?.store;
|
|
382
|
+
const objState = objStore?.snapshot;
|
|
1138
383
|
// Skip event handling when noEvents is set, or when the raycasters camera is null
|
|
1139
384
|
if (!objState || !objState.events.enabled || objState.raycaster.camera === null)
|
|
1140
385
|
return [];
|
|
@@ -1154,8 +399,8 @@ function createEvents(store) {
|
|
|
1154
399
|
.flatMap(handleRaycast)
|
|
1155
400
|
// Sort by event priority and distance
|
|
1156
401
|
.sort((a, b) => {
|
|
1157
|
-
const aState = getLocalState(a.object)
|
|
1158
|
-
const bState = getLocalState(b.object)
|
|
402
|
+
const aState = getLocalState(a.object)?.store?.snapshot;
|
|
403
|
+
const bState = getLocalState(b.object)?.store?.snapshot;
|
|
1159
404
|
if (!aState || !bState)
|
|
1160
405
|
return a.distance - b.distance;
|
|
1161
406
|
return bState.events.priority - aState.events.priority || a.distance - b.distance;
|
|
@@ -1177,7 +422,7 @@ function createEvents(store) {
|
|
|
1177
422
|
let eventObject = hit.object;
|
|
1178
423
|
// bubble event up
|
|
1179
424
|
while (eventObject) {
|
|
1180
|
-
if (getLocalState(eventObject)
|
|
425
|
+
if (getLocalState(eventObject)?.eventCount) {
|
|
1181
426
|
intersections.push({ ...hit, eventObject });
|
|
1182
427
|
}
|
|
1183
428
|
eventObject = eventObject.parent;
|
|
@@ -1185,7 +430,7 @@ function createEvents(store) {
|
|
|
1185
430
|
}
|
|
1186
431
|
// If the interaction is captured, make all capturing targets part of the intersect.
|
|
1187
432
|
if ('pointerId' in event && state.internal.capturedMap.has(event.pointerId)) {
|
|
1188
|
-
for (
|
|
433
|
+
for (const captureData of state.internal.capturedMap.get(event.pointerId).values()) {
|
|
1189
434
|
if (!duplicates.has(makeId(captureData.intersection)))
|
|
1190
435
|
intersections.push(captureData.intersection);
|
|
1191
436
|
}
|
|
@@ -1194,13 +439,12 @@ function createEvents(store) {
|
|
|
1194
439
|
}
|
|
1195
440
|
/** Handles intersections by forwarding them to handlers */
|
|
1196
441
|
function handleIntersects(intersections, event, delta, callback) {
|
|
1197
|
-
const rootState = store.
|
|
442
|
+
const rootState = store.snapshot;
|
|
1198
443
|
// If anything has been found, forward it to the event listeners
|
|
1199
444
|
if (intersections.length) {
|
|
1200
445
|
const localState = { stopped: false };
|
|
1201
446
|
for (const hit of intersections) {
|
|
1202
|
-
const
|
|
1203
|
-
const { raycaster, pointer, camera, internal } = state;
|
|
447
|
+
const { raycaster, pointer, camera, internal } = getLocalState(hit.object)?.store?.snapshot || rootState;
|
|
1204
448
|
const unprojectedPoint = new THREE.Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
1205
449
|
const hasPointerCapture = (id) => internal.capturedMap.get(id)?.has(hit.eventObject) ?? false;
|
|
1206
450
|
const setPointerCapture = (id) => {
|
|
@@ -1228,8 +472,8 @@ function createEvents(store) {
|
|
|
1228
472
|
// Add native event props
|
|
1229
473
|
const extractEventProps = {};
|
|
1230
474
|
// This iterates over the event's properties including the inherited ones. Native PointerEvents have most of their props as getters which are inherited, but polyfilled PointerEvents have them all as their own properties (i.e. not inherited). We can't use Object.keys() or Object.entries() as they only return "own" properties; nor Object.getPrototypeOf(event) as that *doesn't* return "own" properties, only inherited ones.
|
|
1231
|
-
for (
|
|
1232
|
-
|
|
475
|
+
for (const prop in event) {
|
|
476
|
+
const property = event[prop];
|
|
1233
477
|
// Only copy over atomics, leave functions alone as these should be
|
|
1234
478
|
// called as event.nativeEvent.fn()
|
|
1235
479
|
if (typeof property !== 'function')
|
|
@@ -1275,289 +519,897 @@ function createEvents(store) {
|
|
|
1275
519
|
// Call subscribers
|
|
1276
520
|
callback(raycastEvent);
|
|
1277
521
|
// Event bubbling may be interrupted by stopPropagation
|
|
1278
|
-
if (localState.stopped
|
|
522
|
+
if (localState.stopped)
|
|
1279
523
|
break;
|
|
1280
524
|
}
|
|
1281
525
|
}
|
|
1282
|
-
return intersections;
|
|
1283
|
-
}
|
|
1284
|
-
function cancelPointer(intersections) {
|
|
1285
|
-
const internal = store.get('internal');
|
|
1286
|
-
for (const hoveredObj of internal.hovered.values()) {
|
|
1287
|
-
// When no objects were hit or the the hovered object wasn't found underneath the cursor
|
|
1288
|
-
// we call onPointerOut and delete the object from the hovered-elements map
|
|
1289
|
-
if (!intersections.length ||
|
|
1290
|
-
!intersections.find((hit) => hit.object === hoveredObj.object &&
|
|
1291
|
-
hit.index === hoveredObj.index &&
|
|
1292
|
-
hit.instanceId === hoveredObj.instanceId)) {
|
|
1293
|
-
const eventObject = hoveredObj.eventObject;
|
|
1294
|
-
const instance = getLocalState(eventObject);
|
|
1295
|
-
const handlers = instance?.handlers;
|
|
1296
|
-
internal.hovered.delete(makeId(hoveredObj));
|
|
1297
|
-
if (instance?.eventCount) {
|
|
1298
|
-
// Clear out intersects, they are outdated by now
|
|
1299
|
-
const data = { ...hoveredObj, intersections };
|
|
1300
|
-
handlers?.pointerout?.(data);
|
|
1301
|
-
handlers?.pointerleave?.(data);
|
|
526
|
+
return intersections;
|
|
527
|
+
}
|
|
528
|
+
function cancelPointer(intersections) {
|
|
529
|
+
const internal = store.get('internal');
|
|
530
|
+
for (const hoveredObj of internal.hovered.values()) {
|
|
531
|
+
// When no objects were hit or the the hovered object wasn't found underneath the cursor
|
|
532
|
+
// we call onPointerOut and delete the object from the hovered-elements map
|
|
533
|
+
if (!intersections.length ||
|
|
534
|
+
!intersections.find((hit) => hit.object === hoveredObj.object &&
|
|
535
|
+
hit.index === hoveredObj.index &&
|
|
536
|
+
hit.instanceId === hoveredObj.instanceId)) {
|
|
537
|
+
const eventObject = hoveredObj.eventObject;
|
|
538
|
+
const instance = getLocalState(eventObject);
|
|
539
|
+
const handlers = instance?.handlers;
|
|
540
|
+
internal.hovered.delete(makeId(hoveredObj));
|
|
541
|
+
if (instance?.eventCount) {
|
|
542
|
+
// Clear out intersects, they are outdated by now
|
|
543
|
+
const data = { ...hoveredObj, intersections };
|
|
544
|
+
handlers?.pointerout?.(data);
|
|
545
|
+
handlers?.pointerleave?.(data);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
function pointerMissed(event, objects) {
|
|
551
|
+
for (let i = 0; i < objects.length; i++) {
|
|
552
|
+
const instance = getLocalState(objects[i]);
|
|
553
|
+
instance?.handlers.pointermissed?.(event);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function handlePointer(name) {
|
|
557
|
+
// Deal with cancelation
|
|
558
|
+
switch (name) {
|
|
559
|
+
case 'pointerleave':
|
|
560
|
+
case 'pointercancel':
|
|
561
|
+
return () => cancelPointer([]);
|
|
562
|
+
case 'lostpointercapture':
|
|
563
|
+
return (event) => {
|
|
564
|
+
const { internal } = store.snapshot;
|
|
565
|
+
if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) {
|
|
566
|
+
// If the object event interface had lostpointercapture, we'd call it here on every
|
|
567
|
+
// object that's getting removed. We call it on the next frame because lostpointercapture
|
|
568
|
+
// fires before pointerup. Otherwise pointerUp would never be called if the event didn't
|
|
569
|
+
// happen in the object it originated from, leaving components in a in-between state.
|
|
570
|
+
requestAnimationFrame(() => {
|
|
571
|
+
// Only release if pointer-up didn't do it already
|
|
572
|
+
if (internal.capturedMap.has(event.pointerId)) {
|
|
573
|
+
internal.capturedMap.delete(event.pointerId);
|
|
574
|
+
cancelPointer([]);
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
// Any other pointer goes here ...
|
|
581
|
+
return function handleEvent(event) {
|
|
582
|
+
const pointerMissed$ = store['pointerMissed$'];
|
|
583
|
+
const internal = store.get('internal');
|
|
584
|
+
// prepareRay(event)
|
|
585
|
+
internal.lastEvent.nativeElement = event;
|
|
586
|
+
// Get fresh intersects
|
|
587
|
+
const isPointerMove = name === 'pointermove';
|
|
588
|
+
const isClickEvent = name === 'click' || name === 'contextmenu' || name === 'dblclick';
|
|
589
|
+
const filter = isPointerMove ? filterPointerEvents : undefined;
|
|
590
|
+
const hits = intersect(event, filter);
|
|
591
|
+
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
592
|
+
// Save initial coordinates on pointer-down
|
|
593
|
+
if (name === 'pointerdown') {
|
|
594
|
+
internal.initialClick = [event.offsetX, event.offsetY];
|
|
595
|
+
internal.initialHits = hits.map((hit) => hit.eventObject);
|
|
596
|
+
}
|
|
597
|
+
// If a click yields no results, pass it back to the user as a miss
|
|
598
|
+
// Missed events have to come first in order to establish user-land side-effect clean up
|
|
599
|
+
if (isClickEvent && !hits.length) {
|
|
600
|
+
if (delta <= 2) {
|
|
601
|
+
pointerMissed(event, internal.interaction);
|
|
602
|
+
pointerMissed$.next(event);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
// Take care of unhover
|
|
606
|
+
if (isPointerMove)
|
|
607
|
+
cancelPointer(hits);
|
|
608
|
+
function onIntersect(data) {
|
|
609
|
+
const eventObject = data.eventObject;
|
|
610
|
+
const instance = getLocalState(eventObject);
|
|
611
|
+
const handlers = instance?.handlers;
|
|
612
|
+
// Check presence of handlers
|
|
613
|
+
if (!instance?.eventCount)
|
|
614
|
+
return;
|
|
615
|
+
/*
|
|
616
|
+
MAYBE TODO, DELETE IF NOT:
|
|
617
|
+
Check if the object is captured, captured events should not have intersects running in parallel
|
|
618
|
+
But wouldn't it be better to just replace capturedMap with a single entry?
|
|
619
|
+
Also, are we OK with straight up making picking up multiple objects impossible?
|
|
620
|
+
|
|
621
|
+
const pointerId = (data as ThreeEvent<PointerEvent>).pointerId
|
|
622
|
+
if (pointerId !== undefined) {
|
|
623
|
+
const capturedMeshSet = internal.capturedMap.get(pointerId)
|
|
624
|
+
if (capturedMeshSet) {
|
|
625
|
+
const captured = capturedMeshSet.get(eventObject)
|
|
626
|
+
if (captured && captured.localState.stopped) return
|
|
627
|
+
}
|
|
628
|
+
}*/
|
|
629
|
+
if (isPointerMove) {
|
|
630
|
+
// Move event ...
|
|
631
|
+
if (handlers?.pointerover || handlers?.pointerenter || handlers?.pointerout || handlers?.pointerleave) {
|
|
632
|
+
// When enter or out is present take care of hover-state
|
|
633
|
+
const id = makeId(data);
|
|
634
|
+
const hoveredItem = internal.hovered.get(id);
|
|
635
|
+
if (!hoveredItem) {
|
|
636
|
+
// If the object wasn't previously hovered, book it and call its handler
|
|
637
|
+
internal.hovered.set(id, data);
|
|
638
|
+
handlers.pointerover?.(data);
|
|
639
|
+
handlers.pointerenter?.(data);
|
|
640
|
+
}
|
|
641
|
+
else if (hoveredItem.stopped) {
|
|
642
|
+
// If the object was previously hovered and stopped, we shouldn't allow other items to proceed
|
|
643
|
+
data.stopPropagation();
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
// Call mouse move
|
|
647
|
+
handlers?.pointermove?.(data);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
// All other events ...
|
|
651
|
+
const handler = handlers?.[name];
|
|
652
|
+
if (handler) {
|
|
653
|
+
// Forward all events back to their respective handlers with the exception of click events,
|
|
654
|
+
// which must use the initial target
|
|
655
|
+
if (!isClickEvent || internal.initialHits.includes(eventObject)) {
|
|
656
|
+
// Missed events have to come first
|
|
657
|
+
pointerMissed(event, internal.interaction.filter((object) => !internal.initialHits.includes(object)));
|
|
658
|
+
// Now call the handler
|
|
659
|
+
handler(data);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
// Trigger onPointerMissed on all elements that have pointer over/out handlers, but not click and weren't hit
|
|
664
|
+
if (isClickEvent && internal.initialHits.includes(eventObject)) {
|
|
665
|
+
pointerMissed(event, internal.interaction.filter((object) => !internal.initialHits.includes(object)));
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
handleIntersects(hits, event, delta, onIntersect);
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
return { handlePointer };
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const DOM_EVENTS = {
|
|
677
|
+
click: false,
|
|
678
|
+
contextmenu: false,
|
|
679
|
+
dblclick: false,
|
|
680
|
+
wheel: false, // passive wheel errors with OrbitControls
|
|
681
|
+
pointerdown: true,
|
|
682
|
+
pointerup: true,
|
|
683
|
+
pointerleave: true,
|
|
684
|
+
pointermove: true,
|
|
685
|
+
pointercancel: true,
|
|
686
|
+
lostpointercapture: true,
|
|
687
|
+
};
|
|
688
|
+
const supportedEvents = [
|
|
689
|
+
'click',
|
|
690
|
+
'contextmenu',
|
|
691
|
+
'dblclick',
|
|
692
|
+
'pointerup',
|
|
693
|
+
'pointerdown',
|
|
694
|
+
'pointerover',
|
|
695
|
+
'pointerout',
|
|
696
|
+
'pointerenter',
|
|
697
|
+
'pointerleave',
|
|
698
|
+
'pointermove',
|
|
699
|
+
'pointermissed',
|
|
700
|
+
'pointercancel',
|
|
701
|
+
'wheel',
|
|
702
|
+
];
|
|
703
|
+
function createPointerEvents(store) {
|
|
704
|
+
const { handlePointer } = createEvents(store);
|
|
705
|
+
return {
|
|
706
|
+
priority: 1,
|
|
707
|
+
enabled: true,
|
|
708
|
+
compute: (event, root) => {
|
|
709
|
+
const state = root.get();
|
|
710
|
+
// https://github.com/pmndrs/react-three-fiber/pull/782
|
|
711
|
+
// Events trigger outside of canvas when moved, use offsetX/Y by default and allow overrides
|
|
712
|
+
state.pointer.set((event.offsetX / state.size.width) * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
713
|
+
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
714
|
+
},
|
|
715
|
+
connected: undefined,
|
|
716
|
+
handlers: Object.keys(DOM_EVENTS).reduce((handlers, supportedEventName) => {
|
|
717
|
+
handlers[supportedEventName] = handlePointer(supportedEventName);
|
|
718
|
+
return handlers;
|
|
719
|
+
}, {}),
|
|
720
|
+
update: () => {
|
|
721
|
+
const { events, internal } = store.get();
|
|
722
|
+
if (internal.lastEvent?.nativeElement && events.handlers)
|
|
723
|
+
events.handlers.pointermove(internal.lastEvent.nativeElement);
|
|
724
|
+
},
|
|
725
|
+
connect: (target) => {
|
|
726
|
+
const state = store.get();
|
|
727
|
+
state.events.disconnect?.();
|
|
728
|
+
state.setEvents({ connected: target });
|
|
729
|
+
Object.entries(state.events.handlers ?? {}).forEach(([eventName, eventHandler]) => {
|
|
730
|
+
const passive = DOM_EVENTS[eventName];
|
|
731
|
+
target.addEventListener(eventName, eventHandler, { passive });
|
|
732
|
+
});
|
|
733
|
+
},
|
|
734
|
+
disconnect: () => {
|
|
735
|
+
const { events, setEvents } = store.get();
|
|
736
|
+
if (events.connected) {
|
|
737
|
+
Object.entries(events.handlers ?? {}).forEach(([eventName, eventHandler]) => {
|
|
738
|
+
if (events.connected instanceof HTMLElement) {
|
|
739
|
+
events.connected.removeEventListener(eventName, eventHandler);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
setEvents({ connected: undefined });
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// This function prepares a set of changes to be applied to the instance
|
|
749
|
+
function diffProps(instance, props) {
|
|
750
|
+
const propsEntries = Object.entries(props);
|
|
751
|
+
const changes = [];
|
|
752
|
+
for (const [propKey, propValue] of propsEntries) {
|
|
753
|
+
let key = propKey;
|
|
754
|
+
if (is.colorSpaceExist(instance)) {
|
|
755
|
+
if (propKey === 'encoding') {
|
|
756
|
+
key = 'colorSpace';
|
|
757
|
+
}
|
|
758
|
+
else if (propKey === 'outputEncoding') {
|
|
759
|
+
key = 'outputColorSpace';
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (is.equ(propValue, instance[key]))
|
|
763
|
+
continue;
|
|
764
|
+
changes.push([propKey, propValue]);
|
|
765
|
+
}
|
|
766
|
+
return changes;
|
|
767
|
+
}
|
|
768
|
+
// This function applies a set of changes to the instance
|
|
769
|
+
function applyProps(instance, props) {
|
|
770
|
+
// if props is empty
|
|
771
|
+
if (!Object.keys(props).length)
|
|
772
|
+
return instance;
|
|
773
|
+
// filter equals, and reserved props
|
|
774
|
+
const localState = getLocalState(instance);
|
|
775
|
+
const rootState = localState?.store?.snapshot ?? {};
|
|
776
|
+
const changes = diffProps(instance, props);
|
|
777
|
+
for (let i = 0; i < changes.length; i++) {
|
|
778
|
+
let [key, value] = changes[i];
|
|
779
|
+
// Alias (output)encoding => (output)colorSpace (since r152)
|
|
780
|
+
// https://github.com/pmndrs/react-three-fiber/pull/2829
|
|
781
|
+
if (is.colorSpaceExist(instance)) {
|
|
782
|
+
const sRGBEncoding = 3001;
|
|
783
|
+
const SRGBColorSpace = 'srgb';
|
|
784
|
+
const LinearSRGBColorSpace = 'srgb-linear';
|
|
785
|
+
if (key === 'encoding') {
|
|
786
|
+
key = 'colorSpace';
|
|
787
|
+
value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
|
|
788
|
+
}
|
|
789
|
+
else if (key === 'outputEncoding') {
|
|
790
|
+
key = 'outputColorSpace';
|
|
791
|
+
value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
const currentInstance = instance;
|
|
795
|
+
const targetProp = currentInstance[key];
|
|
796
|
+
// special treatmen for objects with support for set/copy, and layers
|
|
797
|
+
if (targetProp && targetProp['set'] && (targetProp['copy'] || targetProp instanceof THREE.Layers)) {
|
|
798
|
+
const isColor = targetProp instanceof THREE.Color;
|
|
799
|
+
// if value is an array
|
|
800
|
+
if (Array.isArray(value)) {
|
|
801
|
+
if (targetProp['fromArray'])
|
|
802
|
+
targetProp['fromArray'](value);
|
|
803
|
+
else
|
|
804
|
+
targetProp['set'](...value);
|
|
805
|
+
} // test again target.copy
|
|
806
|
+
else if (targetProp['copy'] &&
|
|
807
|
+
value &&
|
|
808
|
+
value.constructor &&
|
|
809
|
+
targetProp.constructor.name === value.constructor.name) {
|
|
810
|
+
targetProp['copy'](value);
|
|
811
|
+
if (!THREE.ColorManagement && !rootState.linear && isColor)
|
|
812
|
+
targetProp['convertSRGBToLinear']();
|
|
813
|
+
} // if nothing else fits, just set the single value, ignore undefined
|
|
814
|
+
else if (value !== undefined) {
|
|
815
|
+
const isColor = targetProp instanceof THREE.Color;
|
|
816
|
+
// allow setting array scalars
|
|
817
|
+
if (!isColor && targetProp['setScalar'])
|
|
818
|
+
targetProp['setScalar'](value);
|
|
819
|
+
// layers have no copy function, copy the mask
|
|
820
|
+
else if (targetProp instanceof THREE.Layers && value instanceof THREE.Layers)
|
|
821
|
+
targetProp.mask = value.mask;
|
|
822
|
+
// otherwise just set ...
|
|
823
|
+
else
|
|
824
|
+
targetProp['set'](value);
|
|
825
|
+
// auto-convert srgb
|
|
826
|
+
if (!THREE.ColorManagement && !rootState?.linear && isColor)
|
|
827
|
+
targetProp.convertSRGBToLinear();
|
|
828
|
+
}
|
|
829
|
+
} // else just overwrite the value
|
|
830
|
+
else {
|
|
831
|
+
currentInstance[key] = value;
|
|
832
|
+
// auto-convert srgb textures
|
|
833
|
+
if (currentInstance[key] instanceof THREE.Texture &&
|
|
834
|
+
currentInstance[key].format === THREE.RGBAFormat &&
|
|
835
|
+
currentInstance[key].type === THREE.UnsignedByteType) {
|
|
836
|
+
const texture = currentInstance[key];
|
|
837
|
+
if (rootState?.gl) {
|
|
838
|
+
if (is.colorSpaceExist(texture) && is.colorSpaceExist(rootState.gl))
|
|
839
|
+
texture.colorSpace = rootState.gl.outputColorSpace;
|
|
840
|
+
else
|
|
841
|
+
texture.encoding = rootState.gl.outputEncoding;
|
|
1302
842
|
}
|
|
1303
843
|
}
|
|
1304
844
|
}
|
|
845
|
+
checkUpdate(currentInstance[key]);
|
|
846
|
+
checkUpdate(targetProp);
|
|
847
|
+
invalidateInstance(instance);
|
|
1305
848
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
849
|
+
const instanceHandlersCount = localState?.eventCount;
|
|
850
|
+
const parent = localState?.instanceStore?.get('parent');
|
|
851
|
+
if (parent && rootState.internal && instance['raycast'] && instanceHandlersCount !== localState?.eventCount) {
|
|
852
|
+
// Pre-emptively remove the instance from the interaction manager
|
|
853
|
+
const index = rootState.internal.interaction.indexOf(instance);
|
|
854
|
+
if (index > -1)
|
|
855
|
+
rootState.internal.interaction.splice(index, 1);
|
|
856
|
+
// Add the instance to the interaction manager only when it has handlers
|
|
857
|
+
if (localState?.eventCount)
|
|
858
|
+
rootState.internal.interaction.push(instance);
|
|
1311
859
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
// Any other pointer goes here ...
|
|
1330
|
-
return function handleEvent(event) {
|
|
1331
|
-
const pointerMissed$ = store['pointerMissed$'];
|
|
1332
|
-
const internal = store.get('internal');
|
|
1333
|
-
// prepareRay(event)
|
|
1334
|
-
internal.lastEvent.nativeElement = event;
|
|
1335
|
-
// Get fresh intersects
|
|
1336
|
-
const isPointerMove = name === 'pointermove';
|
|
1337
|
-
const isClickEvent = name === 'click' || name === 'contextmenu' || name === 'dblclick';
|
|
1338
|
-
const filter = isPointerMove ? filterPointerEvents : undefined;
|
|
1339
|
-
const hits = intersect(event, filter);
|
|
1340
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
1341
|
-
// Save initial coordinates on pointer-down
|
|
1342
|
-
if (name === 'pointerdown') {
|
|
1343
|
-
internal.initialClick = [event.offsetX, event.offsetY];
|
|
1344
|
-
internal.initialHits = hits.map((hit) => hit.eventObject);
|
|
860
|
+
if (parent && localState?.afterUpdate && localState.afterUpdate.observed && changes.length) {
|
|
861
|
+
localState.afterUpdate.emit(instance);
|
|
862
|
+
}
|
|
863
|
+
return instance;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const shallowLoose = { objects: 'shallow', strict: false };
|
|
867
|
+
const roots = new Map();
|
|
868
|
+
function injectCanvasRootInitializer(injector) {
|
|
869
|
+
return assertInjector(injectCanvasRootInitializer, injector, () => {
|
|
870
|
+
const injectedStore = injectNgtStore();
|
|
871
|
+
const loop = injectNgtLoop();
|
|
872
|
+
return (canvas) => {
|
|
873
|
+
const exist = roots.has(canvas);
|
|
874
|
+
let store = roots.get(canvas);
|
|
875
|
+
if (store) {
|
|
876
|
+
console.warn('[NGT] Same canvas root is being created twice');
|
|
1345
877
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
if (delta <= 2) {
|
|
1350
|
-
pointerMissed(event, internal.interaction);
|
|
1351
|
-
pointerMissed$.next(event);
|
|
1352
|
-
}
|
|
878
|
+
store ||= injectedStore;
|
|
879
|
+
if (!store) {
|
|
880
|
+
throw new Error('[NGT] No store initialized');
|
|
1353
881
|
}
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
882
|
+
if (!exist) {
|
|
883
|
+
roots.set(canvas, store);
|
|
884
|
+
}
|
|
885
|
+
let isConfigured = false;
|
|
886
|
+
let lastCamera;
|
|
887
|
+
return {
|
|
888
|
+
isConfigured,
|
|
889
|
+
destroy: (timeout = 500) => {
|
|
890
|
+
const root = roots.get(canvas);
|
|
891
|
+
if (root) {
|
|
892
|
+
root.update((state) => ({ internal: { ...state.internal, active: false } }));
|
|
893
|
+
setTimeout(() => {
|
|
894
|
+
try {
|
|
895
|
+
const state = root.get();
|
|
896
|
+
state.events.disconnect?.();
|
|
897
|
+
state.gl?.renderLists?.dispose?.();
|
|
898
|
+
state.gl?.forceContextLoss?.();
|
|
899
|
+
if (state.gl?.xr)
|
|
900
|
+
state.xr.disconnect();
|
|
901
|
+
dispose(state);
|
|
902
|
+
roots.delete(canvas);
|
|
903
|
+
}
|
|
904
|
+
catch (e) {
|
|
905
|
+
console.error('[NGT] Unexpected error while destroying Canvas Root', e);
|
|
906
|
+
}
|
|
907
|
+
}, timeout);
|
|
908
|
+
}
|
|
909
|
+
},
|
|
910
|
+
configure: (inputs) => {
|
|
911
|
+
const { shadows = false, linear = false, flat = false, legacy = false, orthographic = false, frameloop = 'always', dpr = [1, 2], gl: glOptions, size: sizeOptions, camera: cameraOptions, raycaster: raycasterOptions, scene: sceneOptions, events, lookAt, performance, } = inputs;
|
|
912
|
+
const state = store.snapshot;
|
|
913
|
+
const stateToUpdate = {};
|
|
914
|
+
// setup renderer
|
|
915
|
+
let gl = state.gl;
|
|
916
|
+
if (!state.gl)
|
|
917
|
+
stateToUpdate.gl = gl = makeRendererInstance(glOptions, canvas);
|
|
918
|
+
// setup raycaster
|
|
919
|
+
let raycaster = state.raycaster;
|
|
920
|
+
if (!raycaster)
|
|
921
|
+
stateToUpdate.raycaster = raycaster = new THREE.Raycaster();
|
|
922
|
+
// set raycaster options
|
|
923
|
+
const { params, ...options } = raycasterOptions || {};
|
|
924
|
+
if (!is.equ(options, raycaster, shallowLoose))
|
|
925
|
+
applyProps(raycaster, options);
|
|
926
|
+
if (!is.equ(params, raycaster.params, shallowLoose)) {
|
|
927
|
+
applyProps(raycaster, { params: { ...raycaster.params, ...(params || {}) } });
|
|
928
|
+
}
|
|
929
|
+
// Create default camera, don't overwrite any user-set state
|
|
930
|
+
if (!state.camera || (state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose))) {
|
|
931
|
+
lastCamera = cameraOptions;
|
|
932
|
+
const isCamera = is.camera(cameraOptions);
|
|
933
|
+
let camera = isCamera ? cameraOptions : makeCameraInstance(orthographic, state.size);
|
|
934
|
+
if (!isCamera) {
|
|
935
|
+
camera.position.z = 5;
|
|
936
|
+
if (cameraOptions)
|
|
937
|
+
applyProps(camera, cameraOptions);
|
|
938
|
+
// always look at center or passed-in lookAt by default
|
|
939
|
+
if (!state.camera && !cameraOptions?.rotation && !cameraOptions?.quaternion) {
|
|
940
|
+
if (Array.isArray(lookAt))
|
|
941
|
+
camera.lookAt(lookAt[0], lookAt[1], lookAt[2]);
|
|
942
|
+
else if (lookAt instanceof THREE.Vector3)
|
|
943
|
+
camera.lookAt(lookAt);
|
|
944
|
+
else
|
|
945
|
+
camera.lookAt(0, 0, 0);
|
|
946
|
+
}
|
|
947
|
+
// update projection matrix after applyprops
|
|
948
|
+
camera.updateProjectionMatrix?.();
|
|
949
|
+
}
|
|
950
|
+
if (!is.instance(camera))
|
|
951
|
+
camera = prepare(camera, { store });
|
|
952
|
+
stateToUpdate.camera = camera;
|
|
953
|
+
// Configure raycaster
|
|
954
|
+
// https://github.com/pmndrs/react-xr/issues/300
|
|
955
|
+
raycaster.camera = camera;
|
|
956
|
+
}
|
|
957
|
+
// Set up scene (one time only!)
|
|
958
|
+
if (!state.scene) {
|
|
959
|
+
let scene;
|
|
960
|
+
if (sceneOptions instanceof THREE.Scene) {
|
|
961
|
+
scene = sceneOptions;
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
scene = new THREE.Scene();
|
|
965
|
+
if (sceneOptions)
|
|
966
|
+
applyProps(scene, sceneOptions);
|
|
967
|
+
}
|
|
968
|
+
stateToUpdate.scene = prepare(scene, { store });
|
|
969
|
+
}
|
|
970
|
+
// Set up XR (one time only!)
|
|
971
|
+
if (!state.xr) {
|
|
972
|
+
// Handle frame behavior in WebXR
|
|
973
|
+
const handleXRFrame = (timestamp, frame) => {
|
|
974
|
+
const state = store.snapshot;
|
|
975
|
+
if (state.frameloop === 'never')
|
|
976
|
+
return;
|
|
977
|
+
loop.advance(timestamp, true, store, frame);
|
|
978
|
+
};
|
|
979
|
+
// Toggle render switching on session
|
|
980
|
+
const handleSessionChange = () => {
|
|
981
|
+
const state = store.snapshot;
|
|
982
|
+
state.gl.xr.enabled = state.gl.xr.isPresenting;
|
|
983
|
+
state.gl.xr.setAnimationLoop(state.gl.xr.isPresenting ? handleXRFrame : null);
|
|
984
|
+
if (!state.gl.xr.isPresenting)
|
|
985
|
+
loop.invalidate(store);
|
|
986
|
+
};
|
|
987
|
+
// WebXR session manager
|
|
988
|
+
const xr = {
|
|
989
|
+
connect: () => {
|
|
990
|
+
gl.xr.addEventListener('sessionstart', handleSessionChange);
|
|
991
|
+
gl.xr.addEventListener('sessionend', handleSessionChange);
|
|
992
|
+
},
|
|
993
|
+
disconnect: () => {
|
|
994
|
+
gl.xr.removeEventListener('sessionstart', handleSessionChange);
|
|
995
|
+
gl.xr.removeEventListener('sessionend', handleSessionChange);
|
|
996
|
+
},
|
|
997
|
+
};
|
|
998
|
+
// Subscribe to WebXR session events
|
|
999
|
+
if (gl.xr && typeof gl.xr.addEventListener === 'function')
|
|
1000
|
+
xr.connect();
|
|
1001
|
+
stateToUpdate.xr = xr;
|
|
1002
|
+
}
|
|
1003
|
+
// Set shadowmap
|
|
1004
|
+
if (gl.shadowMap) {
|
|
1005
|
+
const oldEnabled = gl.shadowMap.enabled;
|
|
1006
|
+
const oldType = gl.shadowMap.type;
|
|
1007
|
+
gl.shadowMap.enabled = !!shadows;
|
|
1008
|
+
if (typeof shadows === 'boolean') {
|
|
1009
|
+
gl.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
1392
1010
|
}
|
|
1393
|
-
else if (
|
|
1394
|
-
|
|
1395
|
-
|
|
1011
|
+
else if (typeof shadows === 'string') {
|
|
1012
|
+
const types = {
|
|
1013
|
+
basic: THREE.BasicShadowMap,
|
|
1014
|
+
percentage: THREE.PCFShadowMap,
|
|
1015
|
+
soft: THREE.PCFSoftShadowMap,
|
|
1016
|
+
variance: THREE.VSMShadowMap,
|
|
1017
|
+
};
|
|
1018
|
+
gl.shadowMap.type = types[shadows] ?? THREE.PCFSoftShadowMap;
|
|
1396
1019
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
handlers?.pointermove?.(data);
|
|
1400
|
-
}
|
|
1401
|
-
else {
|
|
1402
|
-
// All other events ...
|
|
1403
|
-
const handler = handlers?.[name];
|
|
1404
|
-
if (handler) {
|
|
1405
|
-
// Forward all events back to their respective handlers with the exception of click events,
|
|
1406
|
-
// which must use the initial target
|
|
1407
|
-
if (!isClickEvent || internal.initialHits.includes(eventObject)) {
|
|
1408
|
-
// Missed events have to come first
|
|
1409
|
-
pointerMissed(event, internal.interaction.filter((object) => !internal.initialHits.includes(object)));
|
|
1410
|
-
// Now call the handler
|
|
1411
|
-
handler(data);
|
|
1020
|
+
else if (is.obj(shadows)) {
|
|
1021
|
+
Object.assign(gl.shadowMap, shadows);
|
|
1412
1022
|
}
|
|
1023
|
+
if (oldEnabled !== gl.shadowMap.enabled || oldType !== gl.shadowMap.type)
|
|
1024
|
+
checkNeedsUpdate(gl.shadowMap);
|
|
1413
1025
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1026
|
+
// Safely set color management if available.
|
|
1027
|
+
// Avoid accessing THREE.ColorManagement to play nice with older versions
|
|
1028
|
+
if (THREE.ColorManagement) {
|
|
1029
|
+
const ColorManagement = THREE.ColorManagement;
|
|
1030
|
+
if ('enabled' in ColorManagement)
|
|
1031
|
+
ColorManagement['enabled'] = !legacy ?? false;
|
|
1032
|
+
else if ('legacyMode' in ColorManagement)
|
|
1033
|
+
ColorManagement['legacyMode'] = legacy ?? true;
|
|
1419
1034
|
}
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1035
|
+
if (!isConfigured) {
|
|
1036
|
+
// set color space and tonemapping preferences once
|
|
1037
|
+
const LinearEncoding = 3000;
|
|
1038
|
+
const sRGBEncoding = 3001;
|
|
1039
|
+
applyProps(gl, {
|
|
1040
|
+
outputEncoding: linear ? LinearEncoding : sRGBEncoding,
|
|
1041
|
+
toneMapping: flat ? THREE.NoToneMapping : THREE.ACESFilmicToneMapping,
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
// Update color management state
|
|
1045
|
+
if (state.legacy !== legacy)
|
|
1046
|
+
stateToUpdate.legacy = legacy;
|
|
1047
|
+
if (state.linear !== linear)
|
|
1048
|
+
stateToUpdate.linear = linear;
|
|
1049
|
+
if (state.flat !== flat)
|
|
1050
|
+
stateToUpdate.flat = flat;
|
|
1051
|
+
// Set gl props
|
|
1052
|
+
gl.setClearAlpha(0);
|
|
1053
|
+
gl.setPixelRatio(makeDpr(state.viewport.dpr));
|
|
1054
|
+
gl.setSize(state.size.width, state.size.height);
|
|
1055
|
+
if (is.obj(glOptions) &&
|
|
1056
|
+
!(typeof glOptions === 'function') &&
|
|
1057
|
+
!is.renderer(glOptions) &&
|
|
1058
|
+
!is.equ(glOptions, gl, shallowLoose)) {
|
|
1059
|
+
applyProps(gl, glOptions);
|
|
1060
|
+
}
|
|
1061
|
+
// Store events internally
|
|
1062
|
+
if (events && !state.events.handlers)
|
|
1063
|
+
stateToUpdate.events = events(store);
|
|
1064
|
+
// Check performance
|
|
1065
|
+
if (performance && !is.equ(performance, state.performance, shallowLoose)) {
|
|
1066
|
+
stateToUpdate.performance = { ...state.performance, ...performance };
|
|
1067
|
+
}
|
|
1068
|
+
store.update(stateToUpdate);
|
|
1069
|
+
// Check size, allow it to take on container bounds initially
|
|
1070
|
+
const size = computeInitialSize(canvas, sizeOptions);
|
|
1071
|
+
if (!is.equ(size, state.size, shallowLoose)) {
|
|
1072
|
+
state.setSize(size.width, size.height, size.top, size.left);
|
|
1073
|
+
}
|
|
1074
|
+
// Check pixelratio
|
|
1075
|
+
if (dpr && state.viewport.dpr !== makeDpr(dpr))
|
|
1076
|
+
state.setDpr(dpr);
|
|
1077
|
+
// Check frameloop
|
|
1078
|
+
if (state.frameloop !== frameloop)
|
|
1079
|
+
state.setFrameloop(frameloop);
|
|
1080
|
+
isConfigured = true;
|
|
1081
|
+
},
|
|
1082
|
+
};
|
|
1423
1083
|
};
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
function computeInitialSize(canvas, defaultSize) {
|
|
1087
|
+
if (defaultSize) {
|
|
1088
|
+
return defaultSize;
|
|
1089
|
+
}
|
|
1090
|
+
if (typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
|
|
1091
|
+
return canvas.parentElement.getBoundingClientRect();
|
|
1092
|
+
}
|
|
1093
|
+
if (typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
|
|
1094
|
+
return { width: canvas.width, height: canvas.height, top: 0, left: 0 };
|
|
1095
|
+
}
|
|
1096
|
+
return { width: 0, height: 0, top: 0, left: 0 };
|
|
1097
|
+
}
|
|
1098
|
+
// Disposes an object and all its properties
|
|
1099
|
+
function dispose(obj) {
|
|
1100
|
+
if (obj.dispose && obj.type !== 'Scene')
|
|
1101
|
+
obj.dispose();
|
|
1102
|
+
for (const p in obj) {
|
|
1103
|
+
p.dispose?.();
|
|
1104
|
+
delete obj[p];
|
|
1424
1105
|
}
|
|
1425
|
-
return { handlePointer };
|
|
1426
1106
|
}
|
|
1427
1107
|
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
const
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1108
|
+
function createSubs(callback, subs) {
|
|
1109
|
+
const sub = { callback };
|
|
1110
|
+
subs.add(sub);
|
|
1111
|
+
return () => void subs.delete(sub);
|
|
1112
|
+
}
|
|
1113
|
+
const globalEffects = new Set();
|
|
1114
|
+
const globalAfterEffects = new Set();
|
|
1115
|
+
const globalTailEffects = new Set();
|
|
1116
|
+
/**
|
|
1117
|
+
* Adds a global render callback which is called each frame.
|
|
1118
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addEffect
|
|
1119
|
+
*/
|
|
1120
|
+
const addEffect = (callback) => createSubs(callback, globalEffects);
|
|
1121
|
+
/**
|
|
1122
|
+
* Adds a global after-render callback which is called each frame.
|
|
1123
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addAfterEffect
|
|
1124
|
+
*/
|
|
1125
|
+
const addAfterEffect = (callback) => createSubs(callback, globalAfterEffects);
|
|
1126
|
+
/**
|
|
1127
|
+
* Adds a global callback which is called when rendering stops.
|
|
1128
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addTail
|
|
1129
|
+
*/
|
|
1130
|
+
const addTail = (callback) => createSubs(callback, globalTailEffects);
|
|
1131
|
+
function run(effects, timestamp) {
|
|
1132
|
+
if (!effects.size)
|
|
1133
|
+
return;
|
|
1134
|
+
for (const { callback } of effects.values()) {
|
|
1135
|
+
callback(timestamp);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
function flushGlobalEffects(type, timestamp) {
|
|
1139
|
+
switch (type) {
|
|
1140
|
+
case 'before':
|
|
1141
|
+
return run(globalEffects, timestamp);
|
|
1142
|
+
case 'after':
|
|
1143
|
+
return run(globalAfterEffects, timestamp);
|
|
1144
|
+
case 'tail':
|
|
1145
|
+
return run(globalTailEffects, timestamp);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
function render(timestamp, store, frame) {
|
|
1149
|
+
const state = store.snapshot;
|
|
1150
|
+
// Run local effects
|
|
1151
|
+
let delta = state.clock.getDelta();
|
|
1152
|
+
// In frameloop='never' mode, clock times are updated using the provided timestamp
|
|
1153
|
+
if (state.frameloop === 'never' && typeof timestamp === 'number') {
|
|
1154
|
+
delta = timestamp - state.clock.elapsedTime;
|
|
1155
|
+
state.clock.oldTime = state.clock.elapsedTime;
|
|
1156
|
+
state.clock.elapsedTime = timestamp;
|
|
1157
|
+
}
|
|
1158
|
+
// Call subscribers (beforeRender)
|
|
1159
|
+
const subscribers = state.internal.subscribers;
|
|
1160
|
+
for (let i = 0; i < subscribers.length; i++) {
|
|
1161
|
+
const subscription = subscribers[i];
|
|
1162
|
+
subscription.callback({ ...subscription.store.snapshot, delta, frame });
|
|
1163
|
+
}
|
|
1164
|
+
// Render content
|
|
1165
|
+
if (!state.internal.priority && state.gl.render)
|
|
1166
|
+
state.gl.render(state.scene, state.camera);
|
|
1167
|
+
// Decrease frame count
|
|
1168
|
+
state.internal.frames = Math.max(0, state.internal.frames - 1);
|
|
1169
|
+
return state.frameloop === 'always' ? 1 : state.internal.frames;
|
|
1170
|
+
}
|
|
1171
|
+
function createLoop(roots) {
|
|
1172
|
+
let running = false;
|
|
1173
|
+
let repeat;
|
|
1174
|
+
let frame;
|
|
1175
|
+
function loop(timestamp) {
|
|
1176
|
+
frame = requestAnimationFrame(loop);
|
|
1177
|
+
running = true;
|
|
1178
|
+
repeat = 0;
|
|
1179
|
+
// Run effects
|
|
1180
|
+
flushGlobalEffects('before', timestamp);
|
|
1181
|
+
// Render all roots
|
|
1182
|
+
for (const root of roots.values()) {
|
|
1183
|
+
const state = root.snapshot;
|
|
1184
|
+
// If the frameloop is invalidated, do not run another frame
|
|
1185
|
+
if (state.internal.active &&
|
|
1186
|
+
(state.frameloop === 'always' || state.internal.frames > 0) &&
|
|
1187
|
+
!state.gl.xr?.isPresenting) {
|
|
1188
|
+
repeat += render(timestamp, root);
|
|
1495
1189
|
}
|
|
1496
|
-
}
|
|
1190
|
+
}
|
|
1191
|
+
// Run after-effects
|
|
1192
|
+
flushGlobalEffects('after', timestamp);
|
|
1193
|
+
// Stop the loop if nothing invalidates it
|
|
1194
|
+
if (repeat === 0) {
|
|
1195
|
+
// Tail call effects, they are called when rendering stops
|
|
1196
|
+
flushGlobalEffects('tail', timestamp);
|
|
1197
|
+
// Flag end of operation
|
|
1198
|
+
running = false;
|
|
1199
|
+
return cancelAnimationFrame(frame);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
function invalidate(store, frames = 1) {
|
|
1203
|
+
const state = store?.snapshot;
|
|
1204
|
+
if (!state)
|
|
1205
|
+
return roots.forEach((root) => invalidate(root, frames));
|
|
1206
|
+
if (state.gl.xr?.isPresenting || !state.internal.active || state.frameloop === 'never')
|
|
1207
|
+
return;
|
|
1208
|
+
// Increase frames, do not go higher than 60
|
|
1209
|
+
state.internal.frames = Math.min(60, state.internal.frames + frames);
|
|
1210
|
+
// If the render-loop isn't active, start it
|
|
1211
|
+
if (!running) {
|
|
1212
|
+
running = true;
|
|
1213
|
+
requestAnimationFrame(loop);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
function advance(timestamp, runGlobalEffects = true, store, frame) {
|
|
1217
|
+
if (runGlobalEffects)
|
|
1218
|
+
flushGlobalEffects('before', timestamp);
|
|
1219
|
+
if (!store)
|
|
1220
|
+
for (const root of roots.values())
|
|
1221
|
+
render(timestamp, root);
|
|
1222
|
+
else
|
|
1223
|
+
render(timestamp, store, frame);
|
|
1224
|
+
if (runGlobalEffects)
|
|
1225
|
+
flushGlobalEffects('after', timestamp);
|
|
1226
|
+
}
|
|
1227
|
+
return {
|
|
1228
|
+
loop,
|
|
1229
|
+
/**
|
|
1230
|
+
* Invalidates the view, requesting a frame to be rendered. Will globally invalidate unless passed a root's state.
|
|
1231
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#invalidate
|
|
1232
|
+
*/
|
|
1233
|
+
invalidate,
|
|
1234
|
+
/**
|
|
1235
|
+
* Advances the frameloop and runs render effects, useful for when manually rendering via `frameloop="never"`.
|
|
1236
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#advance
|
|
1237
|
+
*/
|
|
1238
|
+
advance,
|
|
1497
1239
|
};
|
|
1498
1240
|
}
|
|
1241
|
+
const [injectNgtLoop] = createInjectionToken(() => createLoop(roots));
|
|
1499
1242
|
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
const
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1243
|
+
function storeFactory(previousStore) {
|
|
1244
|
+
const document = inject(DOCUMENT);
|
|
1245
|
+
const window = document.defaultView;
|
|
1246
|
+
if (!window) {
|
|
1247
|
+
// TODO: revisit this when we need to support multiple platforms
|
|
1248
|
+
throw new Error(`[NGT] Window is not available.`);
|
|
1249
|
+
}
|
|
1250
|
+
const loop = injectNgtLoop();
|
|
1251
|
+
// NOTE: using Subject because we do not care about late-subscribers
|
|
1252
|
+
const pointerMissed$ = new Subject();
|
|
1253
|
+
const store = signalStore(({ get, update }) => {
|
|
1254
|
+
const { invalidate, advance } = loop;
|
|
1255
|
+
const position = new THREE.Vector3();
|
|
1256
|
+
const defaultTarget = new THREE.Vector3();
|
|
1257
|
+
const tempTarget = new THREE.Vector3();
|
|
1258
|
+
function getCurrentViewport(camera = get('camera'), target = defaultTarget, size = get('size')) {
|
|
1259
|
+
const { width, height, top, left } = size;
|
|
1260
|
+
const aspect = width / height;
|
|
1261
|
+
if (target instanceof THREE.Vector3)
|
|
1262
|
+
tempTarget.copy(target);
|
|
1263
|
+
else
|
|
1264
|
+
tempTarget.set(...target);
|
|
1265
|
+
const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
|
|
1266
|
+
if (is.orthographicCamera(camera)) {
|
|
1267
|
+
return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
|
|
1268
|
+
}
|
|
1269
|
+
const fov = (camera.fov * Math.PI) / 180; // convert vertical fov to radians
|
|
1270
|
+
const h = 2 * Math.tan(fov / 2) * distance; // visible height
|
|
1271
|
+
const w = h * (width / height);
|
|
1272
|
+
return { width: w, height: h, top, left, factor: width / w, distance, aspect };
|
|
1273
|
+
}
|
|
1274
|
+
let performanceTimeout = undefined;
|
|
1275
|
+
const setPerformanceCurrent = (current) => update((state) => ({ performance: { ...state.performance, current } }));
|
|
1276
|
+
const pointer = new THREE.Vector2();
|
|
1277
|
+
return {
|
|
1278
|
+
pointerMissed$: pointerMissed$.asObservable(),
|
|
1279
|
+
events: { priority: 1, enabled: true, connected: false },
|
|
1280
|
+
invalidate: (frames = 1) => invalidate(store, frames),
|
|
1281
|
+
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, store),
|
|
1282
|
+
legacy: false,
|
|
1283
|
+
linear: false,
|
|
1284
|
+
flat: false,
|
|
1285
|
+
controls: null,
|
|
1286
|
+
clock: new THREE.Clock(),
|
|
1287
|
+
pointer,
|
|
1288
|
+
frameloop: 'always',
|
|
1289
|
+
performance: {
|
|
1290
|
+
current: 1,
|
|
1291
|
+
min: 0.5,
|
|
1292
|
+
max: 1,
|
|
1293
|
+
debounce: 200,
|
|
1294
|
+
regress: () => {
|
|
1295
|
+
const state = get();
|
|
1296
|
+
// Clear timeout
|
|
1297
|
+
if (performanceTimeout)
|
|
1298
|
+
clearTimeout(performanceTimeout);
|
|
1299
|
+
// Set lower bound performance
|
|
1300
|
+
if (state.performance.current !== state.performance.min)
|
|
1301
|
+
setPerformanceCurrent(state.performance.min);
|
|
1302
|
+
// Go back to upper bound performance after a while unless something regresses meanwhile
|
|
1303
|
+
performanceTimeout = setTimeout(() => {
|
|
1304
|
+
setPerformanceCurrent(get('performance', 'max'));
|
|
1305
|
+
// safeDetectChanges(cdr);
|
|
1306
|
+
}, state.performance.debounce);
|
|
1307
|
+
},
|
|
1308
|
+
},
|
|
1309
|
+
size: { width: 0, height: 0, top: 0, left: 0, updateStyle: false },
|
|
1310
|
+
viewport: {
|
|
1311
|
+
initialDpr: 0,
|
|
1312
|
+
dpr: 0,
|
|
1313
|
+
width: 0,
|
|
1314
|
+
height: 0,
|
|
1315
|
+
top: 0,
|
|
1316
|
+
left: 0,
|
|
1317
|
+
aspect: 0,
|
|
1318
|
+
distance: 0,
|
|
1319
|
+
factor: 0,
|
|
1320
|
+
getCurrentViewport,
|
|
1321
|
+
},
|
|
1322
|
+
setEvents: (events) => update((state) => ({ ...state, events: { ...state.events, ...events } })),
|
|
1323
|
+
setSize: (width, height, top, left) => {
|
|
1324
|
+
const camera = get('camera');
|
|
1325
|
+
const size = { width, height, top: top || 0, left: left || 0 };
|
|
1326
|
+
update((state) => ({
|
|
1327
|
+
size,
|
|
1328
|
+
viewport: { ...state.viewport, ...getCurrentViewport(camera, defaultTarget, size) },
|
|
1523
1329
|
}));
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1330
|
+
},
|
|
1331
|
+
setDpr: (dpr) => update((state) => {
|
|
1332
|
+
const resolved = makeDpr(dpr, window);
|
|
1333
|
+
return { viewport: { ...state.viewport, dpr: resolved, initialDpr: state.viewport.initialDpr || resolved } };
|
|
1334
|
+
}),
|
|
1335
|
+
setFrameloop: (frameloop = 'always') => {
|
|
1336
|
+
const clock = get('clock');
|
|
1337
|
+
// if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
|
|
1338
|
+
clock.stop();
|
|
1339
|
+
clock.elapsedTime = 0;
|
|
1340
|
+
if (frameloop !== 'never') {
|
|
1341
|
+
clock.start();
|
|
1342
|
+
clock.elapsedTime = 0;
|
|
1343
|
+
}
|
|
1344
|
+
update(() => ({ frameloop }));
|
|
1345
|
+
},
|
|
1346
|
+
previousRoot: previousStore,
|
|
1347
|
+
internal: {
|
|
1348
|
+
active: false,
|
|
1349
|
+
priority: 0,
|
|
1350
|
+
frames: 0,
|
|
1351
|
+
lastEvent: new ElementRef(null),
|
|
1352
|
+
interaction: [],
|
|
1353
|
+
hovered: new Map(),
|
|
1354
|
+
subscribers: [],
|
|
1355
|
+
initialClick: [0, 0],
|
|
1356
|
+
initialHits: [],
|
|
1357
|
+
capturedMap: new Map(),
|
|
1358
|
+
subscribe: (callback, priority = 0, _store = store) => {
|
|
1359
|
+
const internal = get('internal');
|
|
1360
|
+
// If this subscription was given a priority, it takes rendering into its own hands
|
|
1361
|
+
// For that reason we switch off automatic rendering and increase the manual flag
|
|
1362
|
+
// As long as this flag is positive there can be no internal rendering at all
|
|
1363
|
+
// because there could be multiple render subscriptions
|
|
1364
|
+
internal.priority = internal.priority + (priority > 0 ? 1 : 0);
|
|
1365
|
+
internal.subscribers.push({ callback, priority, store: _store });
|
|
1366
|
+
// Register subscriber and sort layers from lowest to highest, meaning,
|
|
1367
|
+
// highest priority renders last (on top of the other frames)
|
|
1368
|
+
internal.subscribers = internal.subscribers.sort((a, b) => (a.priority || 0) - (b.priority || 0));
|
|
1369
|
+
return () => {
|
|
1370
|
+
const internal = get('internal');
|
|
1371
|
+
if (internal?.subscribers) {
|
|
1372
|
+
// Decrease manual flag if this subscription had a priority
|
|
1373
|
+
internal.priority = internal.priority - (priority > 0 ? 1 : 0);
|
|
1374
|
+
// Remove subscriber from list
|
|
1375
|
+
internal.subscribers = internal.subscribers.filter((s) => s.callback !== callback);
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
},
|
|
1379
|
+
},
|
|
1380
|
+
};
|
|
1381
|
+
});
|
|
1382
|
+
Object.defineProperty(store, 'pointerMissed$', { get: () => pointerMissed$ });
|
|
1383
|
+
let { size: oldSize, viewport: { dpr: oldDpr }, camera: oldCamera, } = store.snapshot;
|
|
1384
|
+
const [camera, size, viewportDpr] = [store.select('camera'), store.select('size'), store.select('viewport', 'dpr')];
|
|
1385
|
+
effect(() => {
|
|
1386
|
+
const [newCamera, newSize, newDpr, gl] = [camera(), size(), viewportDpr(), store.snapshot.gl];
|
|
1387
|
+
// Resize camera and renderer on changes to size and pixel-ratio
|
|
1388
|
+
if (newSize !== oldSize || newDpr !== oldDpr) {
|
|
1389
|
+
oldSize = newSize;
|
|
1390
|
+
oldDpr = newDpr;
|
|
1391
|
+
// Update camera & renderer
|
|
1392
|
+
updateCamera(newCamera, newSize);
|
|
1393
|
+
gl.setPixelRatio(newDpr);
|
|
1394
|
+
const updateStyle = typeof HTMLCanvasElement !== 'undefined' && gl.domElement instanceof HTMLCanvasElement;
|
|
1395
|
+
gl.setSize(newSize.width, newSize.height, updateStyle);
|
|
1396
|
+
}
|
|
1397
|
+
// Update viewport once the camera changes
|
|
1398
|
+
if (newCamera !== oldCamera) {
|
|
1399
|
+
oldCamera = newCamera;
|
|
1400
|
+
updateCamera(newCamera, newSize);
|
|
1401
|
+
// Update viewport
|
|
1402
|
+
store.update((state) => ({ viewport: { ...state.viewport, ...state.viewport.getCurrentViewport(newCamera) } }));
|
|
1403
|
+
}
|
|
1553
1404
|
});
|
|
1405
|
+
return store;
|
|
1554
1406
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
};
|
|
1407
|
+
const NGT_STORE = new InjectionToken('NgtStore Token');
|
|
1408
|
+
const [injectNgtStore, provideNgtStore] = createInjectionToken(storeFactory, {
|
|
1409
|
+
isRoot: false,
|
|
1410
|
+
deps: [[new Optional(), new SkipSelf(), NGT_STORE]],
|
|
1411
|
+
token: NGT_STORE,
|
|
1412
|
+
});
|
|
1561
1413
|
|
|
1562
1414
|
const catalogue = {};
|
|
1563
1415
|
function extend(objects) {
|
|
@@ -1577,7 +1429,7 @@ const SPECIAL_PROPERTIES = {
|
|
|
1577
1429
|
COMPOUND: 'ngtCompound',
|
|
1578
1430
|
RENDER_PRIORITY: 'priority',
|
|
1579
1431
|
ATTACH: 'attach',
|
|
1580
|
-
|
|
1432
|
+
RAW_VALUE: 'rawValue',
|
|
1581
1433
|
REF: 'ref',
|
|
1582
1434
|
};
|
|
1583
1435
|
const SPECIAL_EVENTS = {
|
|
@@ -1586,20 +1438,21 @@ const SPECIAL_EVENTS = {
|
|
|
1586
1438
|
AFTER_ATTACH: 'afterAttach',
|
|
1587
1439
|
};
|
|
1588
1440
|
|
|
1441
|
+
const [injectNodeType, provideNodeType] = createInjectionToken(() => '', {
|
|
1442
|
+
isRoot: false,
|
|
1443
|
+
});
|
|
1589
1444
|
class NgtCommonDirective {
|
|
1590
|
-
static { this.processComment = true; }
|
|
1591
1445
|
constructor() {
|
|
1592
1446
|
this.vcr = inject(ViewContainerRef);
|
|
1593
1447
|
this.zone = inject(NgZone);
|
|
1594
1448
|
this.template = inject(TemplateRef);
|
|
1449
|
+
this.nodeType = injectNodeType();
|
|
1595
1450
|
this.injected = false;
|
|
1596
1451
|
this.shouldCreateView = true;
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
delete commentNode[SPECIAL_INTERNAL_ADD_COMMENT];
|
|
1602
|
-
}
|
|
1452
|
+
const commentNode = this.vcr.element.nativeElement;
|
|
1453
|
+
if (commentNode[SPECIAL_INTERNAL_ADD_COMMENT]) {
|
|
1454
|
+
commentNode[SPECIAL_INTERNAL_ADD_COMMENT](this.nodeType);
|
|
1455
|
+
delete commentNode[SPECIAL_INTERNAL_ADD_COMMENT];
|
|
1603
1456
|
}
|
|
1604
1457
|
inject(DestroyRef).onDestroy(() => {
|
|
1605
1458
|
this.view?.destroy();
|
|
@@ -1612,16 +1465,16 @@ class NgtCommonDirective {
|
|
|
1612
1465
|
this.view.destroy();
|
|
1613
1466
|
}
|
|
1614
1467
|
this.view = this.vcr.createEmbeddedView(this.template);
|
|
1615
|
-
|
|
1468
|
+
this.view.detectChanges();
|
|
1616
1469
|
}
|
|
1617
1470
|
});
|
|
1618
1471
|
}
|
|
1619
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1620
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "
|
|
1472
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtCommonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1473
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.1", type: NgtCommonDirective, ngImport: i0 }); }
|
|
1621
1474
|
}
|
|
1622
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1475
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtCommonDirective, decorators: [{
|
|
1623
1476
|
type: Directive
|
|
1624
|
-
}], ctorParameters:
|
|
1477
|
+
}], ctorParameters: () => [] });
|
|
1625
1478
|
|
|
1626
1479
|
class NgtArgs extends NgtCommonDirective {
|
|
1627
1480
|
constructor() {
|
|
@@ -1645,12 +1498,12 @@ class NgtArgs extends NgtCommonDirective {
|
|
|
1645
1498
|
validate() {
|
|
1646
1499
|
return !this.injected && !!this.injectedArgs.length;
|
|
1647
1500
|
}
|
|
1648
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1649
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "
|
|
1501
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtArgs, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1502
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.1", type: NgtArgs, isStandalone: true, selector: "ng-template[args]", inputs: { args: "args" }, providers: [provideNodeType('args')], usesInheritance: true, ngImport: i0 }); }
|
|
1650
1503
|
}
|
|
1651
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1504
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtArgs, decorators: [{
|
|
1652
1505
|
type: Directive,
|
|
1653
|
-
args: [{ selector: 'ng-template[args]', standalone: true }]
|
|
1506
|
+
args: [{ selector: 'ng-template[args]', standalone: true, providers: [provideNodeType('args')] }]
|
|
1654
1507
|
}], propDecorators: { args: [{
|
|
1655
1508
|
type: Input
|
|
1656
1509
|
}] } });
|
|
@@ -1677,12 +1530,12 @@ class NgtParent extends NgtCommonDirective {
|
|
|
1677
1530
|
validate() {
|
|
1678
1531
|
return !this.injected && !!this.injectedParent;
|
|
1679
1532
|
}
|
|
1680
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1681
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "
|
|
1533
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtParent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1534
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.1", type: NgtParent, isStandalone: true, selector: "ng-template[parent]", inputs: { parent: "parent" }, providers: [provideNodeType('parent')], usesInheritance: true, ngImport: i0 }); }
|
|
1682
1535
|
}
|
|
1683
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1536
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtParent, decorators: [{
|
|
1684
1537
|
type: Directive,
|
|
1685
|
-
args: [{ selector: 'ng-template[parent]', standalone: true }]
|
|
1538
|
+
args: [{ selector: 'ng-template[parent]', standalone: true, providers: [provideNodeType('parent')] }]
|
|
1686
1539
|
}], propDecorators: { parent: [{
|
|
1687
1540
|
type: Input
|
|
1688
1541
|
}] } });
|
|
@@ -1701,11 +1554,11 @@ function attach(object, value, paths = []) {
|
|
|
1701
1554
|
}
|
|
1702
1555
|
function detach(parent, child, attachProp) {
|
|
1703
1556
|
const childLocalState = getLocalState(child);
|
|
1704
|
-
if (
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1557
|
+
if (childLocalState) {
|
|
1558
|
+
if (Array.isArray(attachProp))
|
|
1559
|
+
attach(parent, childLocalState.previousAttach, attachProp);
|
|
1560
|
+
else
|
|
1561
|
+
childLocalState.previousAttach();
|
|
1709
1562
|
}
|
|
1710
1563
|
}
|
|
1711
1564
|
function assignEmpty(obj, base) {
|
|
@@ -1749,6 +1602,14 @@ var NgtQueueOpClassId;
|
|
|
1749
1602
|
NgtQueueOpClassId[NgtQueueOpClassId["op"] = 1] = "op";
|
|
1750
1603
|
NgtQueueOpClassId[NgtQueueOpClassId["done"] = 2] = "done";
|
|
1751
1604
|
})(NgtQueueOpClassId || (NgtQueueOpClassId = {}));
|
|
1605
|
+
function kebabToPascal(str) {
|
|
1606
|
+
// split the string at each hyphen
|
|
1607
|
+
const parts = str.split('-');
|
|
1608
|
+
// map over the parts, capitalizing the first letter of each part
|
|
1609
|
+
const pascalParts = parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1));
|
|
1610
|
+
// join the parts together to create the final PascalCase string
|
|
1611
|
+
return pascalParts.join('');
|
|
1612
|
+
}
|
|
1752
1613
|
function attachThreeChild(parent, child) {
|
|
1753
1614
|
const pLS = getLocalState(parent);
|
|
1754
1615
|
const cLS = getLocalState(child);
|
|
@@ -1785,10 +1646,8 @@ function attachThreeChild(parent, child) {
|
|
|
1785
1646
|
}
|
|
1786
1647
|
// attach
|
|
1787
1648
|
if (cLS.isRaw) {
|
|
1788
|
-
if (cLS.
|
|
1789
|
-
|
|
1790
|
-
cLS.parent.set(parent);
|
|
1791
|
-
});
|
|
1649
|
+
if (cLS.instanceStore.get('parent') !== parent) {
|
|
1650
|
+
cLS.setParent(parent);
|
|
1792
1651
|
}
|
|
1793
1652
|
// at this point we don't have rawValue yet, so we bail and wait until the Renderer recalls attach
|
|
1794
1653
|
if (child.__ngt_renderer__[NgtRendererClassId.rawValue] === undefined)
|
|
@@ -1807,10 +1666,8 @@ function attachThreeChild(parent, child) {
|
|
|
1807
1666
|
added = true;
|
|
1808
1667
|
}
|
|
1809
1668
|
pLS.add(child, added ? 'objects' : 'nonObjects');
|
|
1810
|
-
if (cLS.
|
|
1811
|
-
|
|
1812
|
-
cLS.parent.set(parent);
|
|
1813
|
-
});
|
|
1669
|
+
if (cLS.instanceStore.get('parent') !== parent) {
|
|
1670
|
+
cLS.setParent(parent);
|
|
1814
1671
|
}
|
|
1815
1672
|
if (cLS.afterAttach)
|
|
1816
1673
|
cLS.afterAttach.emit({ parent, node: child });
|
|
@@ -1821,24 +1678,22 @@ function removeThreeChild(parent, child, dispose) {
|
|
|
1821
1678
|
const pLS = getLocalState(parent);
|
|
1822
1679
|
const cLS = getLocalState(child);
|
|
1823
1680
|
// clear parent ref
|
|
1824
|
-
|
|
1825
|
-
cLS.parent?.set(null);
|
|
1826
|
-
});
|
|
1681
|
+
cLS?.setParent(null);
|
|
1827
1682
|
// remove child from parent
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
if (
|
|
1831
|
-
pLS.remove(child, 'nonObjects');
|
|
1832
|
-
if (cLS.attach) {
|
|
1683
|
+
pLS?.remove(child, 'objects');
|
|
1684
|
+
pLS?.remove(child, 'nonObjects');
|
|
1685
|
+
if (cLS?.attach) {
|
|
1833
1686
|
detach(parent, child, cLS.attach);
|
|
1834
1687
|
}
|
|
1835
1688
|
else if (is.object3D(parent) && is.object3D(child)) {
|
|
1836
1689
|
parent.remove(child);
|
|
1837
|
-
|
|
1690
|
+
const store = cLS?.store || pLS?.store;
|
|
1691
|
+
if (store)
|
|
1692
|
+
removeInteractivity(store, child);
|
|
1838
1693
|
}
|
|
1839
|
-
const isPrimitive = cLS
|
|
1694
|
+
const isPrimitive = cLS?.primitive;
|
|
1840
1695
|
if (!isPrimitive) {
|
|
1841
|
-
removeThreeRecursive(cLS.
|
|
1696
|
+
removeThreeRecursive(cLS?.instanceStore.get('objects') || [], child, !!dispose);
|
|
1842
1697
|
removeThreeRecursive(child.children, child, !!dispose);
|
|
1843
1698
|
}
|
|
1844
1699
|
// dispose
|
|
@@ -1851,16 +1706,12 @@ function removeThreeRecursive(array, parent, dispose) {
|
|
|
1851
1706
|
if (array)
|
|
1852
1707
|
[...array].forEach((child) => removeThreeChild(parent, child, dispose));
|
|
1853
1708
|
}
|
|
1854
|
-
function
|
|
1855
|
-
// split the string at each hyphen
|
|
1856
|
-
const parts = str.split('-');
|
|
1857
|
-
// map over the parts, capitalizing the first letter of each part
|
|
1858
|
-
const pascalParts = parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1));
|
|
1859
|
-
// join the parts together to create the final PascalCase string
|
|
1860
|
-
return pascalParts.join('');
|
|
1861
|
-
}
|
|
1862
|
-
function processThreeEvent(instance, priority, eventName, callback, zone, rootCdr, targetCdr) {
|
|
1709
|
+
function processThreeEvent(instance, priority, eventName, callback) {
|
|
1863
1710
|
const lS = getLocalState(instance);
|
|
1711
|
+
if (!lS) {
|
|
1712
|
+
console.warn('[NGT] instance has not been prepared yet.');
|
|
1713
|
+
return () => { };
|
|
1714
|
+
}
|
|
1864
1715
|
if (eventName === SPECIAL_EVENTS.BEFORE_RENDER) {
|
|
1865
1716
|
return lS.store
|
|
1866
1717
|
.get('internal')
|
|
@@ -1881,10 +1732,7 @@ function processThreeEvent(instance, priority, eventName, callback, zone, rootCd
|
|
|
1881
1732
|
const updatedCallback = (event) => {
|
|
1882
1733
|
if (previousHandler)
|
|
1883
1734
|
previousHandler(event);
|
|
1884
|
-
|
|
1885
|
-
callback(event);
|
|
1886
|
-
safeDetectChanges(targetCdr, rootCdr);
|
|
1887
|
-
});
|
|
1735
|
+
callback(event);
|
|
1888
1736
|
};
|
|
1889
1737
|
Object.assign(lS.handlers, { [eventName]: updatedCallback });
|
|
1890
1738
|
// increment the count everytime
|
|
@@ -1895,23 +1743,24 @@ function processThreeEvent(instance, priority, eventName, callback, zone, rootCd
|
|
|
1895
1743
|
lS.store.get('internal', 'interaction').push(instance);
|
|
1896
1744
|
// clean up the event listener by removing the target from the interaction array
|
|
1897
1745
|
return () => {
|
|
1898
|
-
const
|
|
1899
|
-
if (
|
|
1900
|
-
|
|
1901
|
-
const
|
|
1902
|
-
|
|
1903
|
-
.findIndex((obj) => obj.uuid === instance.uuid);
|
|
1746
|
+
const lS = getLocalState(instance);
|
|
1747
|
+
if (lS) {
|
|
1748
|
+
lS.eventCount -= 1;
|
|
1749
|
+
const interactions = lS.store.get('internal', 'interaction') || [];
|
|
1750
|
+
const index = interactions.findIndex((obj) => obj.uuid === instance.uuid);
|
|
1904
1751
|
if (index >= 0)
|
|
1905
|
-
|
|
1752
|
+
interactions.splice(index, 1);
|
|
1906
1753
|
}
|
|
1907
1754
|
};
|
|
1908
1755
|
}
|
|
1909
1756
|
|
|
1910
1757
|
const NGT_COMPOUND_PREFIXES = new InjectionToken('NgtCompoundPrefixes');
|
|
1911
1758
|
class NgtRendererStore {
|
|
1912
|
-
constructor(
|
|
1913
|
-
this.
|
|
1914
|
-
this.
|
|
1759
|
+
constructor(rootState) {
|
|
1760
|
+
this.rootState = rootState;
|
|
1761
|
+
this.argsCommentNodes = [];
|
|
1762
|
+
this.parentCommentNodes = [];
|
|
1763
|
+
this.portalCommentsNodes = [];
|
|
1915
1764
|
}
|
|
1916
1765
|
createNode(type, node) {
|
|
1917
1766
|
const state = [
|
|
@@ -1934,34 +1783,100 @@ class NgtRendererStore {
|
|
|
1934
1783
|
const rendererNode = Object.assign(node, { __ngt_renderer__: state });
|
|
1935
1784
|
// NOTE: assign ownerDocument to node so we can use HostListener in Component
|
|
1936
1785
|
if (!rendererNode['ownerDocument'])
|
|
1937
|
-
rendererNode['ownerDocument'] = this.
|
|
1786
|
+
rendererNode['ownerDocument'] = this.rootState.document;
|
|
1938
1787
|
// NOTE: assign injectorFactory on non-three type since
|
|
1939
1788
|
// rendererNode is an instance of DOM Node
|
|
1940
1789
|
if (state[NgtRendererClassId.type] !== 'three') {
|
|
1941
|
-
state[NgtRendererClassId.injectorFactory] = () => getDebugNode(rendererNode)
|
|
1790
|
+
state[NgtRendererClassId.injectorFactory] = () => getDebugNode(rendererNode)?.injector;
|
|
1942
1791
|
}
|
|
1943
1792
|
if (state[NgtRendererClassId.type] === 'comment') {
|
|
1944
1793
|
// NOTE: we attach an arrow function to the Comment node
|
|
1945
1794
|
// In our directives, we can call this function to then start tracking the RendererNode
|
|
1946
1795
|
// this is done to limit the amount of Nodes we need to process for getCreationState
|
|
1947
1796
|
rendererNode[SPECIAL_INTERNAL_ADD_COMMENT] = (node) => {
|
|
1948
|
-
if (node
|
|
1949
|
-
this.
|
|
1797
|
+
if (node === 'args') {
|
|
1798
|
+
this.argsCommentNodes.push(rendererNode);
|
|
1950
1799
|
}
|
|
1951
|
-
else {
|
|
1952
|
-
this.
|
|
1800
|
+
else if (node === 'parent') {
|
|
1801
|
+
this.parentCommentNodes.push(rendererNode);
|
|
1802
|
+
}
|
|
1803
|
+
else if (typeof node === 'object') {
|
|
1804
|
+
this.portalCommentsNodes.push(node);
|
|
1953
1805
|
}
|
|
1954
1806
|
};
|
|
1955
1807
|
return rendererNode;
|
|
1956
1808
|
}
|
|
1957
1809
|
if (state[NgtRendererClassId.type] === 'compound') {
|
|
1958
1810
|
state[NgtRendererClassId.queueOps] = new Set();
|
|
1959
|
-
state[NgtRendererClassId.attributes] = {};
|
|
1960
|
-
state[NgtRendererClassId.properties] = {};
|
|
1811
|
+
state[NgtRendererClassId.attributes] = state[NgtRendererClassId.properties] = {};
|
|
1961
1812
|
return rendererNode;
|
|
1962
1813
|
}
|
|
1963
1814
|
return rendererNode;
|
|
1964
1815
|
}
|
|
1816
|
+
isCompound(name) {
|
|
1817
|
+
return this.rootState.compoundPrefixes.some((prefix) => name.startsWith(prefix));
|
|
1818
|
+
}
|
|
1819
|
+
isDOM(node) {
|
|
1820
|
+
const rS = node['__ngt_renderer__'];
|
|
1821
|
+
return (!rS ||
|
|
1822
|
+
(rS[NgtRendererClassId.type] !== 'compound' &&
|
|
1823
|
+
(node instanceof Element || node instanceof Document || node instanceof Window)));
|
|
1824
|
+
}
|
|
1825
|
+
getClosestParentWithInstance(node) {
|
|
1826
|
+
let parent = node.__ngt_renderer__[NgtRendererClassId.parent];
|
|
1827
|
+
while (parent && parent.__ngt_renderer__[NgtRendererClassId.type] !== 'three') {
|
|
1828
|
+
parent = parent.__ngt_renderer__[NgtRendererClassId.portalContainer]
|
|
1829
|
+
? parent.__ngt_renderer__[NgtRendererClassId.portalContainer]
|
|
1830
|
+
: parent.__ngt_renderer__[NgtRendererClassId.parent];
|
|
1831
|
+
}
|
|
1832
|
+
return parent;
|
|
1833
|
+
}
|
|
1834
|
+
getClosestParentWithCompound(node) {
|
|
1835
|
+
if (node.__ngt_renderer__[NgtRendererClassId.compoundParent]) {
|
|
1836
|
+
return node.__ngt_renderer__[NgtRendererClassId.compoundParent];
|
|
1837
|
+
}
|
|
1838
|
+
let parent = node.__ngt_renderer__[NgtRendererClassId.parent];
|
|
1839
|
+
if (parent &&
|
|
1840
|
+
parent.__ngt_renderer__[NgtRendererClassId.type] === 'compound' &&
|
|
1841
|
+
!parent.__ngt_renderer__[NgtRendererClassId.compounded]) {
|
|
1842
|
+
return parent;
|
|
1843
|
+
}
|
|
1844
|
+
while (parent &&
|
|
1845
|
+
(parent.__ngt_renderer__[NgtRendererClassId.type] === 'three' ||
|
|
1846
|
+
!parent.__ngt_renderer__[NgtRendererClassId.compoundParent] ||
|
|
1847
|
+
parent.__ngt_renderer__[NgtRendererClassId.type] !== 'compound')) {
|
|
1848
|
+
parent = parent.__ngt_renderer__[NgtRendererClassId.parent];
|
|
1849
|
+
}
|
|
1850
|
+
if (!parent)
|
|
1851
|
+
return null;
|
|
1852
|
+
if (parent.__ngt_renderer__[NgtRendererClassId.type] === 'three' &&
|
|
1853
|
+
parent.__ngt_renderer__[NgtRendererClassId.compoundParent]) {
|
|
1854
|
+
return parent.__ngt_renderer__[NgtRendererClassId.compoundParent];
|
|
1855
|
+
}
|
|
1856
|
+
if (!parent.__ngt_renderer__[NgtRendererClassId.compounded]) {
|
|
1857
|
+
return parent;
|
|
1858
|
+
}
|
|
1859
|
+
return null;
|
|
1860
|
+
}
|
|
1861
|
+
processPortalContainer(portal) {
|
|
1862
|
+
const injector = portal.__ngt_renderer__[NgtRendererClassId.injectorFactory]?.();
|
|
1863
|
+
if (!injector)
|
|
1864
|
+
return;
|
|
1865
|
+
const portalStore = injector.get(NGT_STORE, null);
|
|
1866
|
+
if (!portalStore)
|
|
1867
|
+
return;
|
|
1868
|
+
const portalContainer = portalStore.get('scene');
|
|
1869
|
+
if (!portalContainer)
|
|
1870
|
+
return;
|
|
1871
|
+
portal.__ngt_renderer__[NgtRendererClassId.portalContainer] = this.createNode('three', portalContainer);
|
|
1872
|
+
}
|
|
1873
|
+
getCreationState() {
|
|
1874
|
+
return [
|
|
1875
|
+
this.firstNonInjectedDirective('argsCommentNodes', NgtArgs)?.args || [],
|
|
1876
|
+
this.firstNonInjectedDirective('parentCommentNodes', NgtParent)?.parent || null,
|
|
1877
|
+
this.tryGetPortalStore(),
|
|
1878
|
+
];
|
|
1879
|
+
}
|
|
1965
1880
|
setParent(node, parent) {
|
|
1966
1881
|
if (!node.__ngt_renderer__[NgtRendererClassId.parent]) {
|
|
1967
1882
|
node.__ngt_renderer__[NgtRendererClassId.parent] = parent;
|
|
@@ -1983,19 +1898,16 @@ class NgtRendererStore {
|
|
|
1983
1898
|
if (instanceRS && instanceRS[NgtRendererClassId.parent]) {
|
|
1984
1899
|
const parentRS = instanceRS[NgtRendererClassId.parent].__ngt_renderer__;
|
|
1985
1900
|
// NOTE: if instance is already compounded by its parent. skip
|
|
1986
|
-
if (parentRS[NgtRendererClassId.type] === 'compound' &&
|
|
1987
|
-
parentRS[NgtRendererClassId.compounded] === instance) {
|
|
1901
|
+
if (parentRS[NgtRendererClassId.type] === 'compound' && parentRS[NgtRendererClassId.compounded] === instance) {
|
|
1988
1902
|
return;
|
|
1989
1903
|
}
|
|
1990
1904
|
}
|
|
1991
1905
|
const rS = compound.__ngt_renderer__;
|
|
1992
1906
|
rS[NgtRendererClassId.compounded] = instance;
|
|
1993
|
-
const
|
|
1994
|
-
const properties = Object.keys(rS[NgtRendererClassId.properties]);
|
|
1995
|
-
for (const key of attributes) {
|
|
1907
|
+
for (const key of Object.keys(rS[NgtRendererClassId.attributes])) {
|
|
1996
1908
|
this.applyAttribute(instance, key, rS[NgtRendererClassId.attributes][key]);
|
|
1997
1909
|
}
|
|
1998
|
-
for (const key of properties) {
|
|
1910
|
+
for (const key of Object.keys(rS[NgtRendererClassId.properties])) {
|
|
1999
1911
|
this.applyProperty(instance, key, rS[NgtRendererClassId.properties][key]);
|
|
2000
1912
|
}
|
|
2001
1913
|
this.executeOperation(compound);
|
|
@@ -2005,6 +1917,7 @@ class NgtRendererStore {
|
|
|
2005
1917
|
}
|
|
2006
1918
|
executeOperation(node, type = 'op') {
|
|
2007
1919
|
const rS = node.__ngt_renderer__;
|
|
1920
|
+
// TODO: maybe an array with pop() would work better
|
|
2008
1921
|
if (rS[NgtRendererClassId.queueOps]?.size) {
|
|
2009
1922
|
rS[NgtRendererClassId.queueOps].forEach((op) => {
|
|
2010
1923
|
if (op[NgtQueueOpClassId.type] === type) {
|
|
@@ -2014,19 +1927,6 @@ class NgtRendererStore {
|
|
|
2014
1927
|
});
|
|
2015
1928
|
}
|
|
2016
1929
|
}
|
|
2017
|
-
processPortalContainer(portal) {
|
|
2018
|
-
const injectorFactory = portal.__ngt_renderer__[NgtRendererClassId.injectorFactory];
|
|
2019
|
-
const injector = injectorFactory?.();
|
|
2020
|
-
if (!injector)
|
|
2021
|
-
return;
|
|
2022
|
-
const portalStore = injector.get(NGT_STORE, null);
|
|
2023
|
-
if (!portalStore)
|
|
2024
|
-
return;
|
|
2025
|
-
const portalContainer = portalStore.get('scene');
|
|
2026
|
-
if (!portalContainer)
|
|
2027
|
-
return;
|
|
2028
|
-
portal.__ngt_renderer__[NgtRendererClassId.portalContainer] = this.createNode('three', portalContainer);
|
|
2029
|
-
}
|
|
2030
1930
|
applyAttribute(node, name, value) {
|
|
2031
1931
|
const rS = node.__ngt_renderer__;
|
|
2032
1932
|
if (rS[NgtRendererClassId.destroyed])
|
|
@@ -2039,7 +1939,10 @@ class NgtRendererStore {
|
|
|
2039
1939
|
priority = 0;
|
|
2040
1940
|
console.warn(`[NGT] "priority" is an invalid number, default to 0`);
|
|
2041
1941
|
}
|
|
2042
|
-
getLocalState(node)
|
|
1942
|
+
const localState = getLocalState(node);
|
|
1943
|
+
if (localState) {
|
|
1944
|
+
localState.priority = priority;
|
|
1945
|
+
}
|
|
2043
1946
|
}
|
|
2044
1947
|
if (name === SPECIAL_PROPERTIES.COMPOUND) {
|
|
2045
1948
|
// NOTE: we set the compound property on instance node now so we know that this instance is being compounded
|
|
@@ -2049,11 +1952,15 @@ class NgtRendererStore {
|
|
|
2049
1952
|
if (name === SPECIAL_PROPERTIES.ATTACH) {
|
|
2050
1953
|
// NOTE: handle attach as tring
|
|
2051
1954
|
const paths = value.split('.');
|
|
2052
|
-
if (paths.length)
|
|
2053
|
-
getLocalState(node)
|
|
1955
|
+
if (paths.length) {
|
|
1956
|
+
const localState = getLocalState(node);
|
|
1957
|
+
if (localState) {
|
|
1958
|
+
localState.attach = paths;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
2054
1961
|
return;
|
|
2055
1962
|
}
|
|
2056
|
-
if (name === SPECIAL_PROPERTIES.
|
|
1963
|
+
if (name === SPECIAL_PROPERTIES.RAW_VALUE) {
|
|
2057
1964
|
// NOTE: coercion
|
|
2058
1965
|
let maybeCoerced = value;
|
|
2059
1966
|
if (maybeCoerced === '' || maybeCoerced === 'true' || maybeCoerced === 'false') {
|
|
@@ -2078,9 +1985,10 @@ class NgtRendererStore {
|
|
|
2078
1985
|
value.nativeElement = node;
|
|
2079
1986
|
return;
|
|
2080
1987
|
}
|
|
2081
|
-
const
|
|
1988
|
+
const localState = getLocalState(node);
|
|
1989
|
+
const parent = localState?.instanceStore.get('parent') || rS[NgtRendererClassId.parent];
|
|
2082
1990
|
// [rawValue]
|
|
2083
|
-
if (
|
|
1991
|
+
if (localState?.isRaw && name === SPECIAL_PROPERTIES.RAW_VALUE) {
|
|
2084
1992
|
rS[NgtRendererClassId.rawValue] = value;
|
|
2085
1993
|
if (parent)
|
|
2086
1994
|
attachThreeChild(parent, node);
|
|
@@ -2088,7 +1996,8 @@ class NgtRendererStore {
|
|
|
2088
1996
|
}
|
|
2089
1997
|
// [attach]
|
|
2090
1998
|
if (name === SPECIAL_PROPERTIES.ATTACH) {
|
|
2091
|
-
|
|
1999
|
+
if (localState)
|
|
2000
|
+
localState.attach = Array.isArray(value) ? value.map((v) => v.toString()) : value;
|
|
2092
2001
|
if (parent)
|
|
2093
2002
|
attachThreeChild(parent, node);
|
|
2094
2003
|
return;
|
|
@@ -2102,62 +2011,8 @@ class NgtRendererStore {
|
|
|
2102
2011
|
applyProps(node, { [name]: value });
|
|
2103
2012
|
this.updateNativeProps(node, name, value);
|
|
2104
2013
|
}
|
|
2105
|
-
isCompound(name) {
|
|
2106
|
-
return this.root.compoundPrefixes.some((prefix) => name.startsWith(prefix));
|
|
2107
|
-
}
|
|
2108
|
-
isDOM(node) {
|
|
2109
|
-
const rS = node['__ngt_renderer__'];
|
|
2110
|
-
return (!rS ||
|
|
2111
|
-
(rS[NgtRendererClassId.type] !== 'compound' &&
|
|
2112
|
-
(node instanceof Element || node instanceof Document || node instanceof Window)));
|
|
2113
|
-
}
|
|
2114
2014
|
get rootScene() {
|
|
2115
|
-
return this.
|
|
2116
|
-
}
|
|
2117
|
-
get portals() {
|
|
2118
|
-
return this.root.portals;
|
|
2119
|
-
}
|
|
2120
|
-
getClosestParentWithInstance(node) {
|
|
2121
|
-
let parent = node.__ngt_renderer__[NgtRendererClassId.parent];
|
|
2122
|
-
while (parent && parent.__ngt_renderer__[NgtRendererClassId.type] !== 'three') {
|
|
2123
|
-
parent = parent.__ngt_renderer__[NgtRendererClassId.portalContainer]
|
|
2124
|
-
? parent.__ngt_renderer__[NgtRendererClassId.portalContainer]
|
|
2125
|
-
: parent.__ngt_renderer__[NgtRendererClassId.parent];
|
|
2126
|
-
}
|
|
2127
|
-
return parent;
|
|
2128
|
-
}
|
|
2129
|
-
getClosestParentWithCompound(node) {
|
|
2130
|
-
if (node.__ngt_renderer__[NgtRendererClassId.compoundParent]) {
|
|
2131
|
-
return node.__ngt_renderer__[NgtRendererClassId.compoundParent];
|
|
2132
|
-
}
|
|
2133
|
-
let parent = node.__ngt_renderer__[NgtRendererClassId.parent];
|
|
2134
|
-
if (parent &&
|
|
2135
|
-
parent.__ngt_renderer__[NgtRendererClassId.type] === 'compound' &&
|
|
2136
|
-
!parent.__ngt_renderer__[NgtRendererClassId.compounded]) {
|
|
2137
|
-
return parent;
|
|
2138
|
-
}
|
|
2139
|
-
while (parent &&
|
|
2140
|
-
(parent.__ngt_renderer__[NgtRendererClassId.type] === 'three' ||
|
|
2141
|
-
!parent.__ngt_renderer__[NgtRendererClassId.compoundParent] ||
|
|
2142
|
-
parent.__ngt_renderer__[NgtRendererClassId.type] !== 'compound')) {
|
|
2143
|
-
parent = parent.__ngt_renderer__[NgtRendererClassId.parent];
|
|
2144
|
-
}
|
|
2145
|
-
if (!parent)
|
|
2146
|
-
return;
|
|
2147
|
-
if (parent.__ngt_renderer__[NgtRendererClassId.type] === 'three' &&
|
|
2148
|
-
parent.__ngt_renderer__[NgtRendererClassId.compoundParent]) {
|
|
2149
|
-
return parent.__ngt_renderer__[NgtRendererClassId.compoundParent];
|
|
2150
|
-
}
|
|
2151
|
-
if (!parent.__ngt_renderer__[NgtRendererClassId.compounded]) {
|
|
2152
|
-
return parent;
|
|
2153
|
-
}
|
|
2154
|
-
return null;
|
|
2155
|
-
}
|
|
2156
|
-
getCreationState() {
|
|
2157
|
-
const injectedArgs = this.firstNonInjectedDirective(NgtArgs)?.args || [];
|
|
2158
|
-
const injectedParent = this.firstNonInjectedDirective(NgtParent)?.parent || null;
|
|
2159
|
-
const store = this.tryGetPortalStore();
|
|
2160
|
-
return { injectedArgs, injectedParent, store };
|
|
2015
|
+
return this.rootState.store.get('scene');
|
|
2161
2016
|
}
|
|
2162
2017
|
destroy(node, parent) {
|
|
2163
2018
|
const rS = node.__ngt_renderer__;
|
|
@@ -2167,18 +2022,17 @@ class NgtRendererStore {
|
|
|
2167
2022
|
rS[NgtRendererClassId.compound] = undefined;
|
|
2168
2023
|
rS[NgtRendererClassId.compoundParent] = undefined;
|
|
2169
2024
|
const localState = getLocalState(node);
|
|
2170
|
-
if (localState
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
if (localState.nonObjects) {
|
|
2174
|
-
untracked(localState.nonObjects).forEach((obj) => this.destroy(obj, parent));
|
|
2025
|
+
if (localState?.instanceStore) {
|
|
2026
|
+
localState.instanceStore.get('objects').forEach((obj) => this.destroy(obj, parent));
|
|
2027
|
+
localState.instanceStore.get('nonObjects').forEach((obj) => this.destroy(obj, parent));
|
|
2175
2028
|
}
|
|
2176
|
-
if (localState
|
|
2029
|
+
if (localState?.afterUpdate)
|
|
2177
2030
|
localState.afterUpdate.complete();
|
|
2178
|
-
if (localState
|
|
2031
|
+
if (localState?.afterAttach)
|
|
2179
2032
|
localState.afterAttach.complete();
|
|
2180
2033
|
delete localState['objects'];
|
|
2181
2034
|
delete localState['nonObjects'];
|
|
2035
|
+
delete localState['parent'];
|
|
2182
2036
|
delete localState['nativeProps'];
|
|
2183
2037
|
delete localState['add'];
|
|
2184
2038
|
delete localState['remove'];
|
|
@@ -2186,24 +2040,20 @@ class NgtRendererStore {
|
|
|
2186
2040
|
delete localState['afterAttach'];
|
|
2187
2041
|
delete localState['store'];
|
|
2188
2042
|
delete localState['handlers'];
|
|
2189
|
-
if (!localState
|
|
2043
|
+
if (!localState?.primitive) {
|
|
2190
2044
|
delete node['__ngt__'];
|
|
2191
2045
|
}
|
|
2192
2046
|
}
|
|
2193
2047
|
if (rS[NgtRendererClassId.type] === 'comment') {
|
|
2194
2048
|
rS[NgtRendererClassId.injectorFactory] = null;
|
|
2195
2049
|
delete node[SPECIAL_INTERNAL_ADD_COMMENT];
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
this.comments.splice(index, 1);
|
|
2050
|
+
if (!this.removeCommentNode(node, this.argsCommentNodes)) {
|
|
2051
|
+
this.removeCommentNode(node, this.parentCommentNodes);
|
|
2199
2052
|
}
|
|
2200
2053
|
}
|
|
2201
2054
|
if (rS[NgtRendererClassId.type] === 'portal') {
|
|
2202
2055
|
rS[NgtRendererClassId.injectorFactory] = null;
|
|
2203
|
-
|
|
2204
|
-
if (index > -1) {
|
|
2205
|
-
this.portals.splice(index, 1);
|
|
2206
|
-
}
|
|
2056
|
+
this.removeCommentNode(node, this.portalCommentsNodes);
|
|
2207
2057
|
}
|
|
2208
2058
|
if (rS[NgtRendererClassId.type] === 'compound') {
|
|
2209
2059
|
rS[NgtRendererClassId.compounded] = undefined;
|
|
@@ -2239,18 +2089,24 @@ class NgtRendererStore {
|
|
|
2239
2089
|
this.removeChild(parent, node);
|
|
2240
2090
|
}
|
|
2241
2091
|
}
|
|
2092
|
+
removeCommentNode(node, nodes) {
|
|
2093
|
+
const index = nodes.findIndex((comment) => comment === node);
|
|
2094
|
+
if (index > -1) {
|
|
2095
|
+
nodes.splice(index, 1);
|
|
2096
|
+
return true;
|
|
2097
|
+
}
|
|
2098
|
+
return false;
|
|
2099
|
+
}
|
|
2242
2100
|
updateNativeProps(node, key, value) {
|
|
2243
2101
|
const localState = getLocalState(node);
|
|
2244
|
-
|
|
2245
|
-
return;
|
|
2246
|
-
localState.nativeProps.set({ [key]: value });
|
|
2102
|
+
localState?.setNativeProps(key, value);
|
|
2247
2103
|
}
|
|
2248
|
-
firstNonInjectedDirective(dir) {
|
|
2104
|
+
firstNonInjectedDirective(listProperty, dir) {
|
|
2249
2105
|
let directive;
|
|
2250
2106
|
const destroyed = [];
|
|
2251
|
-
let i = this.
|
|
2107
|
+
let i = this[listProperty].length - 1;
|
|
2252
2108
|
while (i >= 0) {
|
|
2253
|
-
const comment = this
|
|
2109
|
+
const comment = this[listProperty][i];
|
|
2254
2110
|
if (comment.__ngt_renderer__[NgtRendererClassId.destroyed]) {
|
|
2255
2111
|
destroyed.push(i);
|
|
2256
2112
|
i--;
|
|
@@ -2269,7 +2125,7 @@ class NgtRendererStore {
|
|
|
2269
2125
|
i--;
|
|
2270
2126
|
}
|
|
2271
2127
|
destroyed.forEach((index) => {
|
|
2272
|
-
this.
|
|
2128
|
+
this[listProperty].splice(index, 1);
|
|
2273
2129
|
});
|
|
2274
2130
|
return directive;
|
|
2275
2131
|
}
|
|
@@ -2277,10 +2133,10 @@ class NgtRendererStore {
|
|
|
2277
2133
|
let store;
|
|
2278
2134
|
const destroyed = [];
|
|
2279
2135
|
// we only care about the portal states because NgtStore only differs per Portal
|
|
2280
|
-
let i = this.
|
|
2136
|
+
let i = this.portalCommentsNodes.length - 1;
|
|
2281
2137
|
while (i >= 0) {
|
|
2282
2138
|
// loop through the portal state backwards to find the closest NgtStore
|
|
2283
|
-
const portal = this.
|
|
2139
|
+
const portal = this.portalCommentsNodes[i];
|
|
2284
2140
|
if (portal.__ngt_renderer__[NgtRendererClassId.destroyed]) {
|
|
2285
2141
|
destroyed.push(i);
|
|
2286
2142
|
i--;
|
|
@@ -2300,72 +2156,62 @@ class NgtRendererStore {
|
|
|
2300
2156
|
i--;
|
|
2301
2157
|
}
|
|
2302
2158
|
destroyed.forEach((index) => {
|
|
2303
|
-
this.
|
|
2159
|
+
this.portalCommentsNodes.splice(index, 1);
|
|
2304
2160
|
});
|
|
2305
|
-
return store || this.
|
|
2161
|
+
return store || this.rootState.store;
|
|
2306
2162
|
}
|
|
2307
2163
|
}
|
|
2308
2164
|
|
|
2309
2165
|
class NgtRendererFactory {
|
|
2310
2166
|
constructor() {
|
|
2311
2167
|
this.delegateRendererFactory = inject(RendererFactory2, { skipSelf: true });
|
|
2312
|
-
this.zone = inject(NgZone);
|
|
2313
2168
|
this.catalogue = injectNgtCatalogue();
|
|
2314
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
2315
2169
|
this.rendererMap = new Map();
|
|
2316
2170
|
this.routedSet = new Set();
|
|
2317
|
-
// all Renderer instances share the same Store
|
|
2171
|
+
// NOTE: all Renderer instances under the same NgtCanvas share the same Store
|
|
2318
2172
|
this.rendererStore = new NgtRendererStore({
|
|
2319
|
-
portals: [],
|
|
2320
2173
|
store: injectNgtStore(),
|
|
2321
2174
|
compoundPrefixes: inject(NGT_COMPOUND_PREFIXES),
|
|
2322
2175
|
document: inject(DOCUMENT),
|
|
2323
2176
|
});
|
|
2324
2177
|
}
|
|
2325
2178
|
createRenderer(hostElement, type) {
|
|
2326
|
-
const
|
|
2179
|
+
const delegateRenderer = this.delegateRendererFactory.createRenderer(hostElement, type);
|
|
2327
2180
|
if (!type)
|
|
2328
|
-
return
|
|
2329
|
-
//
|
|
2181
|
+
return delegateRenderer;
|
|
2182
|
+
// NOTE: might need to revisit this
|
|
2330
2183
|
if (type['type'][HTML]) {
|
|
2331
|
-
this.rendererMap.set(type.id,
|
|
2332
|
-
return
|
|
2184
|
+
this.rendererMap.set(type.id, delegateRenderer);
|
|
2185
|
+
return delegateRenderer;
|
|
2333
2186
|
}
|
|
2334
2187
|
if (type['type'][ROUTED_SCENE]) {
|
|
2335
2188
|
this.routedSet.add(type.id);
|
|
2336
2189
|
}
|
|
2337
2190
|
let renderer = this.rendererMap.get(type.id);
|
|
2338
2191
|
if (!renderer) {
|
|
2339
|
-
renderer = new NgtRenderer(
|
|
2192
|
+
this.rendererMap.set(type.id, (renderer = new NgtRenderer(delegateRenderer, this.rendererStore, this.catalogue,
|
|
2340
2193
|
// setting root scene if there's no routed scene OR this component is the routed Scene
|
|
2341
|
-
!hostElement && (this.routedSet.size === 0 || this.routedSet.has(type.id)));
|
|
2342
|
-
this.rendererMap.set(type.id, renderer);
|
|
2194
|
+
!hostElement && (this.routedSet.size === 0 || this.routedSet.has(type.id)))));
|
|
2343
2195
|
}
|
|
2344
2196
|
return renderer;
|
|
2345
2197
|
}
|
|
2346
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2347
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
2198
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtRendererFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2199
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtRendererFactory }); }
|
|
2348
2200
|
}
|
|
2349
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2201
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtRendererFactory, decorators: [{
|
|
2350
2202
|
type: Injectable
|
|
2351
2203
|
}] });
|
|
2352
|
-
/**
|
|
2353
|
-
* Anything abbreviated with rS/RS stands for RendererState
|
|
2354
|
-
*/
|
|
2355
2204
|
class NgtRenderer {
|
|
2356
|
-
constructor(delegate, store, catalogue,
|
|
2205
|
+
constructor(delegate, store, catalogue, isRoot = true) {
|
|
2357
2206
|
this.delegate = delegate;
|
|
2358
2207
|
this.store = store;
|
|
2359
2208
|
this.catalogue = catalogue;
|
|
2360
|
-
this.
|
|
2361
|
-
this.cdr = cdr;
|
|
2362
|
-
this.root = root;
|
|
2209
|
+
this.isRoot = isRoot;
|
|
2363
2210
|
this.createText = this.delegate.createText.bind(this.delegate);
|
|
2364
2211
|
this.destroy = this.delegate.destroy.bind(this.delegate);
|
|
2365
2212
|
this.destroyNode = null;
|
|
2366
2213
|
this.selectRootElement = this.delegate.selectRootElement.bind(this.delegate);
|
|
2367
2214
|
this.nextSibling = this.delegate.nextSibling.bind(this.delegate);
|
|
2368
|
-
this.removeAttribute = this.delegate.removeAttribute.bind(this.delegate);
|
|
2369
2215
|
this.addClass = this.delegate.addClass.bind(this.delegate);
|
|
2370
2216
|
this.removeClass = this.delegate.removeClass.bind(this.delegate);
|
|
2371
2217
|
this.setStyle = this.delegate.setStyle.bind(this.delegate);
|
|
@@ -2375,41 +2221,37 @@ class NgtRenderer {
|
|
|
2375
2221
|
createElement(name, namespace) {
|
|
2376
2222
|
const element = this.delegate.createElement(name, namespace);
|
|
2377
2223
|
// on first pass, we return the Root Scene as the root node
|
|
2378
|
-
if (this.
|
|
2379
|
-
this.
|
|
2224
|
+
if (this.isRoot) {
|
|
2225
|
+
this.isRoot = false;
|
|
2380
2226
|
const node = this.store.createNode('three', this.store.rootScene);
|
|
2381
|
-
node.__ngt_renderer__[NgtRendererClassId.injectorFactory] = () => getDebugNode(element)
|
|
2227
|
+
node.__ngt_renderer__[NgtRendererClassId.injectorFactory] = () => getDebugNode(element)?.injector;
|
|
2382
2228
|
return node;
|
|
2383
2229
|
}
|
|
2384
|
-
// handle compound
|
|
2385
2230
|
if (this.store.isCompound(name)) {
|
|
2386
2231
|
return this.store.createNode('compound', element);
|
|
2387
2232
|
}
|
|
2388
|
-
// handle portal
|
|
2389
2233
|
if (name === SPECIAL_DOM_TAG.NGT_PORTAL) {
|
|
2390
2234
|
return this.store.createNode('portal', element);
|
|
2391
2235
|
}
|
|
2392
|
-
// handle raw value
|
|
2393
2236
|
if (name === SPECIAL_DOM_TAG.NGT_VALUE) {
|
|
2394
2237
|
return this.store.createNode('three', Object.assign({ __ngt_renderer__: { rawValue: undefined } },
|
|
2395
2238
|
// NOTE: we assign this manually to a raw value node
|
|
2396
2239
|
// because we say it is a 'three' node but we're not using prepare()
|
|
2397
|
-
{ __ngt__: { isRaw: true, parent:
|
|
2240
|
+
{ __ngt__: { isRaw: true, parent: () => null } }));
|
|
2398
2241
|
}
|
|
2399
|
-
const
|
|
2242
|
+
const [injectedArgs, injectedParent, store] = this.store.getCreationState();
|
|
2400
2243
|
let parent = injectedParent;
|
|
2401
2244
|
if (typeof injectedParent === 'string') {
|
|
2402
2245
|
parent = store
|
|
2403
2246
|
.get('scene')
|
|
2404
2247
|
.getObjectByName(injectedParent);
|
|
2405
2248
|
}
|
|
2406
|
-
// handle primitive
|
|
2407
2249
|
if (name === SPECIAL_DOM_TAG.NGT_PRIMITIVE) {
|
|
2408
2250
|
if (!injectedArgs[0])
|
|
2409
2251
|
throw new Error(`[NGT] ngt-primitive without args is invalid`);
|
|
2410
2252
|
const object = injectedArgs[0];
|
|
2411
2253
|
let localState = getLocalState(object);
|
|
2412
|
-
if (!
|
|
2254
|
+
if (!localState) {
|
|
2413
2255
|
// NOTE: if an object isn't already "prepared", we prepare it
|
|
2414
2256
|
localState = getLocalState(prepare(object, { store, args: injectedArgs, primitive: true }));
|
|
2415
2257
|
}
|
|
@@ -2421,8 +2263,7 @@ class NgtRenderer {
|
|
|
2421
2263
|
}
|
|
2422
2264
|
return node;
|
|
2423
2265
|
}
|
|
2424
|
-
const
|
|
2425
|
-
const threeName = kebabToPascal(threeTag);
|
|
2266
|
+
const threeName = kebabToPascal(name.startsWith('ngt') ? name.slice(4) : name);
|
|
2426
2267
|
const threeTarget = this.catalogue[threeName];
|
|
2427
2268
|
// we have the THREE constructor here, handle it
|
|
2428
2269
|
if (threeTarget) {
|
|
@@ -2470,13 +2311,14 @@ class NgtRenderer {
|
|
|
2470
2311
|
}
|
|
2471
2312
|
if (cRS?.[NgtRendererClassId.injectedParent]) {
|
|
2472
2313
|
if (is.ref(cRS[NgtRendererClassId.injectedParent])) {
|
|
2473
|
-
const injector = cRS[NgtRendererClassId.injectorFactory]()
|
|
2314
|
+
const injector = cRS[NgtRendererClassId.injectorFactory]()?.get(Injector, null);
|
|
2474
2315
|
if (!injector) {
|
|
2475
2316
|
console.warn(`[NGT] NgtRenderer is attempting to start an effect for injectedParent but no Injector is found.`);
|
|
2476
2317
|
return;
|
|
2477
2318
|
}
|
|
2478
2319
|
const watcher = effect(() => {
|
|
2479
|
-
const injectedParent = cRS[NgtRendererClassId.injectedParent]
|
|
2320
|
+
const injectedParent = cRS[NgtRendererClassId.injectedParent]
|
|
2321
|
+
.nativeElement;
|
|
2480
2322
|
if (injectedParent && injectedParent !== parent) {
|
|
2481
2323
|
this.appendChild(injectedParent, newChild);
|
|
2482
2324
|
// only run this effect once
|
|
@@ -2511,8 +2353,9 @@ class NgtRenderer {
|
|
|
2511
2353
|
}
|
|
2512
2354
|
// if both are three instances, straightforward case
|
|
2513
2355
|
if (pRS[NgtRendererClassId.type] === 'three' && cRS?.[NgtRendererClassId.type] === 'three') {
|
|
2356
|
+
const cLS = getLocalState(newChild);
|
|
2514
2357
|
// if child already attached to a parent, skip
|
|
2515
|
-
if (
|
|
2358
|
+
if (cLS?.instanceStore?.get('parent'))
|
|
2516
2359
|
return;
|
|
2517
2360
|
// attach THREE child
|
|
2518
2361
|
attachThreeChild(parent, newChild);
|
|
@@ -2527,7 +2370,7 @@ class NgtRenderer {
|
|
|
2527
2370
|
}
|
|
2528
2371
|
// if only the parent is the THREE instance
|
|
2529
2372
|
if (pRS[NgtRendererClassId.type] === 'three') {
|
|
2530
|
-
for (const renderChild of cRS?.[NgtRendererClassId.children]) {
|
|
2373
|
+
for (const renderChild of cRS?.[NgtRendererClassId.children] || []) {
|
|
2531
2374
|
this.appendChild(parent, renderChild);
|
|
2532
2375
|
}
|
|
2533
2376
|
}
|
|
@@ -2602,23 +2445,34 @@ class NgtRenderer {
|
|
|
2602
2445
|
return rS[NgtRendererClassId.parent];
|
|
2603
2446
|
return this.delegate.parentNode(node);
|
|
2604
2447
|
}
|
|
2605
|
-
|
|
2448
|
+
setAttributeInternal(el, name, value, namespace) {
|
|
2606
2449
|
const rS = el.__ngt_renderer__;
|
|
2607
2450
|
if (rS[NgtRendererClassId.type] === 'compound') {
|
|
2608
2451
|
// we don't have the compound instance yet
|
|
2609
2452
|
rS[NgtRendererClassId.attributes][name] = value;
|
|
2610
2453
|
if (!rS[NgtRendererClassId.compounded]) {
|
|
2611
2454
|
this.store.queueOperation(el, ['op', () => this.setAttribute(el, name, value, namespace)]);
|
|
2612
|
-
return;
|
|
2455
|
+
return false;
|
|
2613
2456
|
}
|
|
2614
|
-
this.
|
|
2615
|
-
return;
|
|
2457
|
+
return this.setAttributeInternal(rS[NgtRendererClassId.compounded], name, value, namespace);
|
|
2616
2458
|
}
|
|
2617
2459
|
if (rS[NgtRendererClassId.type] === 'three') {
|
|
2618
2460
|
this.store.applyAttribute(el, name, value);
|
|
2619
|
-
return;
|
|
2461
|
+
return false;
|
|
2462
|
+
}
|
|
2463
|
+
return true;
|
|
2464
|
+
}
|
|
2465
|
+
setAttribute(el, name, value, namespace) {
|
|
2466
|
+
const useDelegate = this.setAttributeInternal(el, name, value, namespace);
|
|
2467
|
+
if (useDelegate) {
|
|
2468
|
+
this.delegate.setAttribute(el, name, value);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
removeAttribute(el, name, namespace) {
|
|
2472
|
+
const useDelegate = this.setAttributeInternal(el, name, undefined, namespace);
|
|
2473
|
+
if (useDelegate) {
|
|
2474
|
+
this.delegate.removeAttribute(el, name, namespace);
|
|
2620
2475
|
}
|
|
2621
|
-
return this.delegate.setAttribute(el, name, value);
|
|
2622
2476
|
}
|
|
2623
2477
|
setProperty(el, name, value) {
|
|
2624
2478
|
// TODO: should we support ref value
|
|
@@ -2645,17 +2499,16 @@ class NgtRenderer {
|
|
|
2645
2499
|
listen(target, eventName, callback) {
|
|
2646
2500
|
const rS = target.__ngt_renderer__;
|
|
2647
2501
|
// if the target doesn't have __ngt_renderer__, we delegate
|
|
2648
|
-
// if target is DOM node,
|
|
2502
|
+
// if target is DOM node, we delegate
|
|
2649
2503
|
if (!rS || this.store.isDOM(target)) {
|
|
2650
2504
|
return this.delegate.listen(target, eventName, callback);
|
|
2651
2505
|
}
|
|
2652
2506
|
if (rS[NgtRendererClassId.type] === 'three' ||
|
|
2653
2507
|
(rS[NgtRendererClassId.type] === 'compound' && rS[NgtRendererClassId.compounded])) {
|
|
2654
2508
|
const instance = rS[NgtRendererClassId.compounded] || target;
|
|
2655
|
-
const
|
|
2656
|
-
const
|
|
2657
|
-
|
|
2658
|
-
return processThreeEvent(instance, priority || 0, eventName, callback, this.zone, this.cdr, targetCdr);
|
|
2509
|
+
const localState = getLocalState(instance);
|
|
2510
|
+
const priority = localState?.priority ?? 0;
|
|
2511
|
+
return processThreeEvent(instance, priority || 0, eventName, callback);
|
|
2659
2512
|
}
|
|
2660
2513
|
if (rS[NgtRendererClassId.type] === 'compound' && !rS[NgtRendererClassId.compounded]) {
|
|
2661
2514
|
this.store.queueOperation(target, [
|
|
@@ -2683,48 +2536,54 @@ class NgtRenderer {
|
|
|
2683
2536
|
const cType = cRS[NgtRendererClassId.type];
|
|
2684
2537
|
const isParentCompounded = pRS[NgtRendererClassId.compounded];
|
|
2685
2538
|
const isChildCompounded = cRS[NgtRendererClassId.compounded];
|
|
2539
|
+
const cLS = getLocalState(child);
|
|
2686
2540
|
// if child is three but haven't been attached to a parent yet
|
|
2687
|
-
const isDanglingThreeChild = cType === 'three' && !
|
|
2541
|
+
const isDanglingThreeChild = cType === 'three' && !cLS?.instanceStore?.get('parent');
|
|
2688
2542
|
// or both parent and child are DOM elements
|
|
2689
2543
|
// or they are compound AND haven't had a THREE instance yet
|
|
2690
2544
|
const isParentStillDOM = pType === 'dom' || (pType === 'compound' && !isParentCompounded);
|
|
2691
2545
|
const isChildStillDOM = cType === 'dom' || (cType === 'compound' && !isChildCompounded);
|
|
2692
2546
|
// and the child is a compounded compound
|
|
2693
2547
|
const isCompoundChildCompounded = cType === 'compound' && !!isChildCompounded;
|
|
2694
|
-
return (isDanglingThreeChild ||
|
|
2695
|
-
(isParentStillDOM && isChildStillDOM) ||
|
|
2696
|
-
(isParentStillDOM && isCompoundChildCompounded));
|
|
2548
|
+
return (isDanglingThreeChild || (isParentStillDOM && isChildStillDOM) || (isParentStillDOM && isCompoundChildCompounded));
|
|
2697
2549
|
}
|
|
2698
2550
|
get data() {
|
|
2699
2551
|
return this.delegate.data;
|
|
2700
2552
|
}
|
|
2701
2553
|
}
|
|
2702
|
-
function provideNgtRenderer(store, compoundPrefixes
|
|
2554
|
+
function provideNgtRenderer(store, compoundPrefixes) {
|
|
2703
2555
|
if (!compoundPrefixes.includes('ngts'))
|
|
2704
2556
|
compoundPrefixes.push('ngts');
|
|
2705
2557
|
if (!compoundPrefixes.includes('ngtp'))
|
|
2706
2558
|
compoundPrefixes.push('ngtp');
|
|
2707
2559
|
return makeEnvironmentProviders([
|
|
2708
|
-
|
|
2560
|
+
NgtRendererFactory,
|
|
2561
|
+
{ provide: RendererFactory2, useExisting: NgtRendererFactory },
|
|
2709
2562
|
{ provide: NGT_COMPOUND_PREFIXES, useValue: compoundPrefixes },
|
|
2710
|
-
{ provide: ChangeDetectorRef, useValue: cdr },
|
|
2711
2563
|
provideNgtStore(store),
|
|
2712
2564
|
provideZoneChangeDetection({ runCoalescing: true, eventCoalescing: true }),
|
|
2713
2565
|
]);
|
|
2714
2566
|
}
|
|
2715
2567
|
|
|
2716
2568
|
class NgtCanvas {
|
|
2569
|
+
set _sceneGraphInputs(value) {
|
|
2570
|
+
this.sceneGraphInputs.set(value);
|
|
2571
|
+
}
|
|
2572
|
+
set _canvasInputs(value) {
|
|
2573
|
+
this.canvasInputs.update(value);
|
|
2574
|
+
}
|
|
2717
2575
|
constructor() {
|
|
2718
2576
|
this.store = injectNgtStore();
|
|
2719
2577
|
this.initRoot = injectCanvasRootInitializer();
|
|
2578
|
+
this.autoEffect = injectAutoEffect();
|
|
2720
2579
|
this.host = inject(ElementRef);
|
|
2721
2580
|
this.viewContainerRef = inject(ViewContainerRef);
|
|
2722
|
-
this.injector = inject(Injector);
|
|
2723
|
-
this.environmentInjector = inject(EnvironmentInjector);
|
|
2724
2581
|
this.zone = inject(NgZone);
|
|
2725
|
-
this.
|
|
2726
|
-
this.
|
|
2727
|
-
this.
|
|
2582
|
+
this.environmentInjector = inject(EnvironmentInjector);
|
|
2583
|
+
this.injector = inject(Injector);
|
|
2584
|
+
this.compoundPrefixes = [];
|
|
2585
|
+
this.sceneGraphInputs = signal({}, { equal: Object.is });
|
|
2586
|
+
this.canvasInputs = signalStore({
|
|
2728
2587
|
shadows: false,
|
|
2729
2588
|
linear: false,
|
|
2730
2589
|
flat: false,
|
|
@@ -2734,102 +2593,49 @@ class NgtCanvas {
|
|
|
2734
2593
|
dpr: [1, 2],
|
|
2735
2594
|
events: createPointerEvents,
|
|
2736
2595
|
});
|
|
2737
|
-
this.sceneGraphInputs = {};
|
|
2738
|
-
this.compoundPrefixes = [];
|
|
2739
2596
|
this.created = new EventEmitter();
|
|
2740
|
-
this
|
|
2741
|
-
this.
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
set orthographic(orthographic) {
|
|
2753
|
-
this.inputs.set({ orthographic });
|
|
2754
|
-
}
|
|
2755
|
-
set frameloop(frameloop) {
|
|
2756
|
-
this.inputs.set({ frameloop });
|
|
2757
|
-
}
|
|
2758
|
-
set dpr(dpr) {
|
|
2759
|
-
this.inputs.set({ dpr });
|
|
2760
|
-
}
|
|
2761
|
-
set raycaster(raycaster) {
|
|
2762
|
-
this.inputs.set({ raycaster });
|
|
2763
|
-
}
|
|
2764
|
-
set shadows(shadows) {
|
|
2765
|
-
this.inputs.set({ shadows });
|
|
2766
|
-
}
|
|
2767
|
-
set camera(camera) {
|
|
2768
|
-
this.inputs.set({ camera });
|
|
2769
|
-
}
|
|
2770
|
-
set scene(scene) {
|
|
2771
|
-
this.inputs.set({ scene });
|
|
2772
|
-
}
|
|
2773
|
-
set gl(gl) {
|
|
2774
|
-
this.inputs.set({ gl });
|
|
2775
|
-
}
|
|
2776
|
-
set eventSource(eventSource) {
|
|
2777
|
-
this.inputs.set({ eventSource });
|
|
2778
|
-
}
|
|
2779
|
-
set eventPrefix(eventPrefix) {
|
|
2780
|
-
this.inputs.set({ eventPrefix });
|
|
2781
|
-
}
|
|
2782
|
-
set lookAt(lookAt) {
|
|
2783
|
-
this.inputs.set({ lookAt });
|
|
2784
|
-
}
|
|
2785
|
-
set performance(performance) {
|
|
2786
|
-
this.inputs.set({ performance });
|
|
2787
|
-
}
|
|
2788
|
-
ngOnChanges(changes) {
|
|
2789
|
-
if (changes['sceneGraphInputs'] && !changes['sceneGraphInputs'].firstChange && this.glRef) {
|
|
2790
|
-
this.setSceneGraphInputs();
|
|
2791
|
-
}
|
|
2792
|
-
}
|
|
2793
|
-
ngOnInit() {
|
|
2794
|
-
// NOTE: we resolve glCanvas at this point, setup the configurator
|
|
2795
|
-
this.configurator = this.initRoot(this.glCanvas.nativeElement);
|
|
2796
|
-
this.destroyRef.onDestroy(() => {
|
|
2797
|
-
this.glEnvironmentInjector?.destroy();
|
|
2597
|
+
// NOTE: this signal is updated outside of Zone
|
|
2598
|
+
this.resizeResult = signal({}, { equal: Object.is });
|
|
2599
|
+
this.eventSource = this.canvasInputs.select('eventSource');
|
|
2600
|
+
this.hbPointerEvents = computed(() => (this.eventSource() ? 'none' : 'auto'));
|
|
2601
|
+
afterNextRender(() => {
|
|
2602
|
+
this.zone.runOutsideAngular(() => {
|
|
2603
|
+
this.configurator = this.initRoot(this.glCanvas.nativeElement);
|
|
2604
|
+
this.noZoneResizeEffect();
|
|
2605
|
+
this.noZoneSceneGraphInputsEffect();
|
|
2606
|
+
});
|
|
2607
|
+
});
|
|
2608
|
+
inject(DestroyRef).onDestroy(() => {
|
|
2798
2609
|
this.glRef?.destroy();
|
|
2799
|
-
this.
|
|
2800
|
-
injectNgtLoader.destroy();
|
|
2610
|
+
this.glEnvironmentInjector?.destroy();
|
|
2801
2611
|
this.configurator?.destroy();
|
|
2802
2612
|
});
|
|
2803
2613
|
}
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
this.zone.runOutsideAngular(() => {
|
|
2813
|
-
if (!this.configurator)
|
|
2814
|
-
this.configurator = this.initRoot(this.glCanvas.nativeElement);
|
|
2815
|
-
this.configurator.configure({ ...inputs(), size: result });
|
|
2614
|
+
noZoneResizeEffect() {
|
|
2615
|
+
this.autoEffect(() => {
|
|
2616
|
+
const resizeResult = this.resizeResult();
|
|
2617
|
+
if (resizeResult.width > 0 && resizeResult.height > 0) {
|
|
2618
|
+
if (!this.configurator)
|
|
2619
|
+
this.configurator = this.initRoot(this.glCanvas.nativeElement);
|
|
2620
|
+
this.configurator.configure({ ...this.canvasInputs.state(), size: resizeResult });
|
|
2621
|
+
untracked(() => {
|
|
2816
2622
|
if (this.glRef) {
|
|
2817
|
-
this.
|
|
2623
|
+
this.glRef.changeDetectorRef.detectChanges();
|
|
2818
2624
|
}
|
|
2819
2625
|
else {
|
|
2820
|
-
this.
|
|
2626
|
+
this.noZoneRender();
|
|
2821
2627
|
}
|
|
2822
2628
|
});
|
|
2823
|
-
}
|
|
2824
|
-
}
|
|
2629
|
+
}
|
|
2630
|
+
});
|
|
2825
2631
|
}
|
|
2826
|
-
|
|
2632
|
+
noZoneRender() {
|
|
2633
|
+
// NOTE: destroy previous instances if existed
|
|
2827
2634
|
this.glEnvironmentInjector?.destroy();
|
|
2828
2635
|
this.glRef?.destroy();
|
|
2829
|
-
// Flag the canvas active, rendering will now begin
|
|
2830
|
-
this.store.
|
|
2831
|
-
const inputs = this.
|
|
2832
|
-
const state = this.store.get();
|
|
2636
|
+
// NOTE: Flag the canvas active, rendering will now begin
|
|
2637
|
+
this.store.update((state) => ({ internal: { ...state.internal, active: true } }));
|
|
2638
|
+
const [inputs, state] = [this.canvasInputs.snapshot, this.store.snapshot];
|
|
2833
2639
|
// connect to event source
|
|
2834
2640
|
state.events.connect?.(inputs.eventSource
|
|
2835
2641
|
? is.ref(inputs.eventSource)
|
|
@@ -2840,111 +2646,83 @@ class NgtCanvas {
|
|
|
2840
2646
|
if (inputs.eventPrefix) {
|
|
2841
2647
|
state.setEvents({
|
|
2842
2648
|
compute: (event, store) => {
|
|
2843
|
-
const
|
|
2649
|
+
const { pointer, raycaster, camera, size } = store.snapshot;
|
|
2844
2650
|
const x = event[(inputs.eventPrefix + 'X')];
|
|
2845
2651
|
const y = event[(inputs.eventPrefix + 'Y')];
|
|
2846
|
-
|
|
2847
|
-
|
|
2652
|
+
pointer.set((x / size.width) * 2 - 1, -(y / size.height) * 2 + 1);
|
|
2653
|
+
raycaster.setFromCamera(pointer, camera);
|
|
2848
2654
|
},
|
|
2849
2655
|
});
|
|
2850
2656
|
}
|
|
2851
2657
|
// emit created event if observed
|
|
2852
2658
|
if (this.created.observed) {
|
|
2853
|
-
|
|
2854
|
-
this.zone.run(() => {
|
|
2855
|
-
this.created.emit(this.store.get());
|
|
2856
|
-
});
|
|
2659
|
+
this.created.emit(this.store.snapshot);
|
|
2857
2660
|
}
|
|
2858
2661
|
if (!this.store.get('events', 'connected')) {
|
|
2859
2662
|
this.store.get('events').connect?.(this.glCanvas.nativeElement);
|
|
2860
2663
|
}
|
|
2861
|
-
this.glEnvironmentInjector = createEnvironmentInjector([provideNgtRenderer(this.store, this.compoundPrefixes
|
|
2664
|
+
this.glEnvironmentInjector = createEnvironmentInjector([provideNgtRenderer(this.store, this.compoundPrefixes)], this.environmentInjector);
|
|
2862
2665
|
this.glRef = this.viewContainerRef.createComponent(this.sceneGraph, {
|
|
2863
2666
|
environmentInjector: this.glEnvironmentInjector,
|
|
2864
2667
|
injector: this.injector,
|
|
2865
2668
|
});
|
|
2866
|
-
this.
|
|
2867
|
-
this.setSceneGraphInputs();
|
|
2868
|
-
}
|
|
2869
|
-
overrideChangeDetectorRef() {
|
|
2870
|
-
const originalDetectChanges = this.cdr.detectChanges.bind(this.cdr);
|
|
2871
|
-
this.cdr.detectChanges = () => {
|
|
2872
|
-
originalDetectChanges();
|
|
2873
|
-
safeDetectChanges(this.glRef?.changeDetectorRef);
|
|
2874
|
-
};
|
|
2669
|
+
this.glRef.changeDetectorRef.detectChanges();
|
|
2670
|
+
this.setSceneGraphInputs(untracked(this.sceneGraphInputs));
|
|
2875
2671
|
}
|
|
2876
|
-
|
|
2877
|
-
this.
|
|
2878
|
-
|
|
2879
|
-
for (const [key, value] of Object.entries(this.sceneGraphInputs)) {
|
|
2880
|
-
this.glRef.setInput(key, value);
|
|
2881
|
-
}
|
|
2882
|
-
this.glRef.changeDetectorRef.detectChanges();
|
|
2883
|
-
}
|
|
2672
|
+
noZoneSceneGraphInputsEffect() {
|
|
2673
|
+
this.autoEffect(() => {
|
|
2674
|
+
this.setSceneGraphInputs(this.sceneGraphInputs());
|
|
2884
2675
|
});
|
|
2885
2676
|
}
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2677
|
+
setSceneGraphInputs(sceneGraphInputs) {
|
|
2678
|
+
if (this.glRef) {
|
|
2679
|
+
for (const [key, value] of Object.entries(sceneGraphInputs)) {
|
|
2680
|
+
this.glRef.setInput(key, value);
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtCanvas, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2685
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.2.1", type: NgtCanvas, isStandalone: true, selector: "ngt-canvas", inputs: { sceneGraph: "sceneGraph", compoundPrefixes: "compoundPrefixes", _sceneGraphInputs: ["sceneGraphInputs", "_sceneGraphInputs"], _canvasInputs: ["options", "_canvasInputs"] }, outputs: { created: "created" }, host: { properties: { "style.pointerEvents": "hbPointerEvents()" }, styleAttribute: "display: block;position: relative;width: 100%;height: 100%;overflow: hidden;" }, providers: [
|
|
2686
|
+
provideResizeOptions({ emitInZone: false, emitInitialResult: true, debounce: 250 }),
|
|
2687
|
+
provideNgtStore(),
|
|
2688
|
+
], viewQueries: [{ propertyName: "glCanvas", first: true, predicate: ["glCanvas"], descendants: true, static: true }], ngImport: i0, template: `
|
|
2689
|
+
<div (ngxResize)="resizeResult.set($event)" style="height: 100%; width: 100%;">
|
|
2889
2690
|
<canvas #glCanvas style="display: block;"></canvas>
|
|
2890
2691
|
</div>
|
|
2891
2692
|
`, isInline: true, dependencies: [{ kind: "directive", type: NgxResize, selector: "[ngxResize]", inputs: ["ngxResizeOptions"], outputs: ["ngxResize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2892
2693
|
}
|
|
2893
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2694
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtCanvas, decorators: [{
|
|
2894
2695
|
type: Component,
|
|
2895
2696
|
args: [{
|
|
2896
2697
|
selector: 'ngt-canvas',
|
|
2897
2698
|
standalone: true,
|
|
2898
2699
|
template: `
|
|
2899
|
-
<div (ngxResize)="
|
|
2700
|
+
<div (ngxResize)="resizeResult.set($event)" style="height: 100%; width: 100%;">
|
|
2900
2701
|
<canvas #glCanvas style="display: block;"></canvas>
|
|
2901
2702
|
</div>
|
|
2902
2703
|
`,
|
|
2903
2704
|
imports: [NgxResize],
|
|
2904
|
-
providers: [
|
|
2705
|
+
providers: [
|
|
2706
|
+
provideResizeOptions({ emitInZone: false, emitInitialResult: true, debounce: 250 }),
|
|
2707
|
+
provideNgtStore(),
|
|
2708
|
+
],
|
|
2905
2709
|
host: {
|
|
2906
2710
|
style: 'display: block;position: relative;width: 100%;height: 100%;overflow: hidden;',
|
|
2907
2711
|
'[style.pointerEvents]': 'hbPointerEvents()',
|
|
2908
2712
|
},
|
|
2909
2713
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2910
2714
|
}]
|
|
2911
|
-
}], propDecorators: { sceneGraph: [{
|
|
2715
|
+
}], ctorParameters: () => [], propDecorators: { sceneGraph: [{
|
|
2912
2716
|
type: Input,
|
|
2913
2717
|
args: [{ required: true }]
|
|
2914
|
-
}], sceneGraphInputs: [{
|
|
2915
|
-
type: Input
|
|
2916
2718
|
}], compoundPrefixes: [{
|
|
2917
2719
|
type: Input
|
|
2918
|
-
}],
|
|
2919
|
-
type: Input
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
}], orthographic: [{
|
|
2925
|
-
type: Input
|
|
2926
|
-
}], frameloop: [{
|
|
2927
|
-
type: Input
|
|
2928
|
-
}], dpr: [{
|
|
2929
|
-
type: Input
|
|
2930
|
-
}], raycaster: [{
|
|
2931
|
-
type: Input
|
|
2932
|
-
}], shadows: [{
|
|
2933
|
-
type: Input
|
|
2934
|
-
}], camera: [{
|
|
2935
|
-
type: Input
|
|
2936
|
-
}], scene: [{
|
|
2937
|
-
type: Input
|
|
2938
|
-
}], gl: [{
|
|
2939
|
-
type: Input
|
|
2940
|
-
}], eventSource: [{
|
|
2941
|
-
type: Input
|
|
2942
|
-
}], eventPrefix: [{
|
|
2943
|
-
type: Input
|
|
2944
|
-
}], lookAt: [{
|
|
2945
|
-
type: Input
|
|
2946
|
-
}], performance: [{
|
|
2947
|
-
type: Input
|
|
2720
|
+
}], _sceneGraphInputs: [{
|
|
2721
|
+
type: Input,
|
|
2722
|
+
args: [{ alias: 'sceneGraphInputs' }]
|
|
2723
|
+
}], _canvasInputs: [{
|
|
2724
|
+
type: Input,
|
|
2725
|
+
args: [{ alias: 'options' }]
|
|
2948
2726
|
}], created: [{
|
|
2949
2727
|
type: Output
|
|
2950
2728
|
}], glCanvas: [{
|
|
@@ -2952,62 +2730,82 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImpor
|
|
|
2952
2730
|
args: ['glCanvas', { static: true }]
|
|
2953
2731
|
}] } });
|
|
2954
2732
|
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
return false;
|
|
2963
|
-
}
|
|
2964
|
-
set key(key) {
|
|
2965
|
-
const normalizedKey = JSON.stringify(key);
|
|
2966
|
-
if (this.lastKey !== normalizedKey) {
|
|
2967
|
-
this.lastKey = normalizedKey;
|
|
2968
|
-
this.createView();
|
|
2969
|
-
}
|
|
2970
|
-
}
|
|
2971
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: NgtKey, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2972
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.2", type: NgtKey, isStandalone: true, selector: "ng-template[key]", inputs: { key: "key" }, usesInheritance: true, ngImport: i0 }); }
|
|
2733
|
+
const cached = new Map();
|
|
2734
|
+
function normalizeInputs(input) {
|
|
2735
|
+
if (Array.isArray(input))
|
|
2736
|
+
return input;
|
|
2737
|
+
if (typeof input === 'string')
|
|
2738
|
+
return [input];
|
|
2739
|
+
return Object.values(input);
|
|
2973
2740
|
}
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2741
|
+
function load(loaderConstructorFactory, inputs, { extensions, onProgress, } = {}) {
|
|
2742
|
+
return () => {
|
|
2743
|
+
const urls = normalizeInputs(inputs());
|
|
2744
|
+
const loader = new (loaderConstructorFactory(urls))();
|
|
2745
|
+
if (extensions)
|
|
2746
|
+
extensions(loader);
|
|
2747
|
+
// TODO: reevaluate this
|
|
2748
|
+
return urls.map((url) => {
|
|
2749
|
+
if (!cached.has(url)) {
|
|
2750
|
+
cached.set(url, new Promise((resolve, reject) => {
|
|
2751
|
+
loader.load(url, (data) => {
|
|
2752
|
+
if ('scene' in data) {
|
|
2753
|
+
Object.assign(data, makeObjectGraph(data['scene']));
|
|
2754
|
+
}
|
|
2755
|
+
resolve(data);
|
|
2756
|
+
}, onProgress, (error) => reject(new Error(`[NGT] Could not load ${url}: ${error}`)));
|
|
2757
|
+
}));
|
|
2758
|
+
}
|
|
2759
|
+
return cached.get(url);
|
|
2760
|
+
});
|
|
2761
|
+
};
|
|
2987
2762
|
}
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2763
|
+
function _injectNgtLoader(loaderConstructorFactory, inputs, { extensions, onProgress, injector, } = {}) {
|
|
2764
|
+
return assertInjector(_injectNgtLoader, injector, () => {
|
|
2765
|
+
const response = signal(null);
|
|
2766
|
+
const effector = load(loaderConstructorFactory, inputs, { extensions, onProgress });
|
|
2767
|
+
effect(() => {
|
|
2768
|
+
const originalUrls = inputs();
|
|
2769
|
+
Promise.all(effector()).then((results) => {
|
|
2770
|
+
response.update(() => {
|
|
2771
|
+
if (Array.isArray(originalUrls))
|
|
2772
|
+
return results;
|
|
2773
|
+
if (typeof originalUrls === 'string')
|
|
2774
|
+
return results[0];
|
|
2775
|
+
const keys = Object.keys(originalUrls);
|
|
2776
|
+
return keys.reduce((result, key) => {
|
|
2777
|
+
result[key] = results[keys.indexOf(key)];
|
|
2778
|
+
return result;
|
|
2779
|
+
}, {});
|
|
2780
|
+
});
|
|
2781
|
+
});
|
|
2782
|
+
});
|
|
2783
|
+
return response.asReadonly();
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
_injectNgtLoader.preload = (loaderConstructorFactory, inputs, extensions) => {
|
|
2787
|
+
void Promise.all(load(loaderConstructorFactory, inputs, { extensions })());
|
|
2788
|
+
};
|
|
2789
|
+
_injectNgtLoader.destroy = () => {
|
|
2790
|
+
cached.clear();
|
|
2791
|
+
};
|
|
2792
|
+
const injectNgtLoader = _injectNgtLoader;
|
|
2994
2793
|
|
|
2995
2794
|
function injectNgtRef(initial = null, injector) {
|
|
2996
|
-
injector
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
inject(DestroyRef).onDestroy(() => void cached.clear());
|
|
2795
|
+
return assertInjector(injectNgtRef, injector, () => {
|
|
2796
|
+
const ref = is.ref(initial) ? initial : new ElementRef(initial);
|
|
2797
|
+
const refSignal = signal(ref.nativeElement, { equal: Object.is });
|
|
2798
|
+
const readonlyRef = refSignal.asReadonly();
|
|
2799
|
+
const computedCached = new Map();
|
|
2800
|
+
inject(DestroyRef).onDestroy(() => void computedCached.clear());
|
|
3003
2801
|
const children = (type = 'objects') => {
|
|
3004
|
-
if (!
|
|
3005
|
-
|
|
3006
|
-
const instance =
|
|
2802
|
+
if (!computedCached.has(type)) {
|
|
2803
|
+
computedCached.set(type, computed(() => {
|
|
2804
|
+
const instance = readonlyRef();
|
|
3007
2805
|
if (!instance)
|
|
3008
2806
|
return [];
|
|
3009
2807
|
const localState = getLocalState(instance);
|
|
3010
|
-
if (!localState
|
|
2808
|
+
if (!localState?.instanceStore)
|
|
3011
2809
|
return [];
|
|
3012
2810
|
if (type === 'objects')
|
|
3013
2811
|
return localState.objects();
|
|
@@ -3016,26 +2814,34 @@ function injectNgtRef(initial = null, injector) {
|
|
|
3016
2814
|
return [...localState.objects(), ...localState.nonObjects()];
|
|
3017
2815
|
}));
|
|
3018
2816
|
}
|
|
3019
|
-
return
|
|
2817
|
+
return computedCached.get(type);
|
|
3020
2818
|
};
|
|
3021
2819
|
Object.defineProperties(ref, {
|
|
3022
2820
|
nativeElement: {
|
|
3023
2821
|
set: (newElement) => {
|
|
3024
2822
|
untracked(() => {
|
|
3025
|
-
if (newElement !==
|
|
3026
|
-
|
|
2823
|
+
if (newElement !== readonlyRef()) {
|
|
2824
|
+
refSignal.set(newElement);
|
|
3027
2825
|
}
|
|
3028
2826
|
});
|
|
3029
2827
|
},
|
|
3030
|
-
get:
|
|
2828
|
+
get: readonlyRef,
|
|
3031
2829
|
},
|
|
3032
|
-
|
|
3033
|
-
children: { get: () => children },
|
|
2830
|
+
children: { value: children },
|
|
3034
2831
|
});
|
|
3035
2832
|
return ref;
|
|
3036
2833
|
});
|
|
3037
2834
|
}
|
|
3038
2835
|
|
|
2836
|
+
function injectBeforeRender(cb, { priority = 0, injector } = {}) {
|
|
2837
|
+
return assertInjector(injectBeforeRender, injector, () => {
|
|
2838
|
+
const store = injectNgtStore();
|
|
2839
|
+
const sub = store.get('internal').subscribe(cb, priority, store);
|
|
2840
|
+
inject(DestroyRef).onDestroy(() => void sub());
|
|
2841
|
+
return sub;
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2844
|
+
|
|
3039
2845
|
const privateKeys = [
|
|
3040
2846
|
'get',
|
|
3041
2847
|
'set',
|
|
@@ -3049,39 +2855,66 @@ const privateKeys = [
|
|
|
3049
2855
|
'size',
|
|
3050
2856
|
'viewport',
|
|
3051
2857
|
];
|
|
2858
|
+
const [, providePortalStore] = createInjectionToken((parentStore) => {
|
|
2859
|
+
const parentState = parentStore.snapshot;
|
|
2860
|
+
const pointer = new THREE.Vector2();
|
|
2861
|
+
const raycaster = new THREE.Raycaster();
|
|
2862
|
+
return signalStore(({ update }) => {
|
|
2863
|
+
return {
|
|
2864
|
+
...parentState,
|
|
2865
|
+
pointer,
|
|
2866
|
+
raycaster,
|
|
2867
|
+
previousRoot: parentStore,
|
|
2868
|
+
// Layers are allowed to override events
|
|
2869
|
+
setEvents: (events) => update((state) => ({ ...state, events: { ...state.events, ...events } })),
|
|
2870
|
+
};
|
|
2871
|
+
});
|
|
2872
|
+
}, { isRoot: false, token: NGT_STORE, deps: [[new SkipSelf(), NGT_STORE]] });
|
|
3052
2873
|
class NgtPortalBeforeRender {
|
|
3053
2874
|
constructor() {
|
|
3054
2875
|
this.portalStore = injectNgtStore();
|
|
3055
2876
|
this.injector = inject(Injector);
|
|
3056
2877
|
this.renderPriority = 1;
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
gl.
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
static { this.ɵ
|
|
3080
|
-
}
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
2878
|
+
afterNextRender(() => {
|
|
2879
|
+
let oldClear;
|
|
2880
|
+
injectBeforeRender(() => {
|
|
2881
|
+
const { gl, scene, camera } = this.portalStore.get();
|
|
2882
|
+
oldClear = gl.autoClear;
|
|
2883
|
+
if (this.renderPriority === 1) {
|
|
2884
|
+
// clear scene and render with default
|
|
2885
|
+
gl.autoClear = true;
|
|
2886
|
+
gl.render(this.parentScene, this.parentCamera);
|
|
2887
|
+
}
|
|
2888
|
+
// disable cleaning
|
|
2889
|
+
gl.autoClear = false;
|
|
2890
|
+
gl.clearDepth();
|
|
2891
|
+
gl.render(scene, camera);
|
|
2892
|
+
// restore
|
|
2893
|
+
gl.autoClear = oldClear;
|
|
2894
|
+
}, { priority: this.renderPriority, injector: this.injector });
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
onPointerOver() {
|
|
2898
|
+
/* noop */
|
|
2899
|
+
}
|
|
2900
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtPortalBeforeRender, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2901
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.2.1", type: NgtPortalBeforeRender, isStandalone: true, selector: "ngt-portal-before-render", inputs: { renderPriority: "renderPriority", parentScene: "parentScene", parentCamera: "parentCamera" }, ngImport: i0, template: `
|
|
2902
|
+
<!-- Without an element that receives pointer events state.pointer will always be 0/0 -->
|
|
2903
|
+
<ngt-group (pointerover)="onPointerOver()" attach="none" />
|
|
2904
|
+
`, isInline: true }); }
|
|
2905
|
+
}
|
|
2906
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtPortalBeforeRender, decorators: [{
|
|
2907
|
+
type: Component,
|
|
2908
|
+
args: [{
|
|
2909
|
+
selector: 'ngt-portal-before-render',
|
|
2910
|
+
standalone: true,
|
|
2911
|
+
template: `
|
|
2912
|
+
<!-- Without an element that receives pointer events state.pointer will always be 0/0 -->
|
|
2913
|
+
<ngt-group (pointerover)="onPointerOver()" attach="none" />
|
|
2914
|
+
`,
|
|
2915
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
2916
|
+
}]
|
|
2917
|
+
}], ctorParameters: () => [], propDecorators: { renderPriority: [{
|
|
3085
2918
|
type: Input
|
|
3086
2919
|
}], parentScene: [{
|
|
3087
2920
|
type: Input,
|
|
@@ -3089,8 +2922,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImpor
|
|
|
3089
2922
|
}], parentCamera: [{
|
|
3090
2923
|
type: Input,
|
|
3091
2924
|
args: [{ required: true }]
|
|
3092
|
-
}], beforeRender: [{
|
|
3093
|
-
type: Output
|
|
3094
2925
|
}] } });
|
|
3095
2926
|
class NgtPortalContent {
|
|
3096
2927
|
constructor(vcr, parentVcr) {
|
|
@@ -3100,87 +2931,55 @@ class NgtPortalContent {
|
|
|
3100
2931
|
delete commentNode[SPECIAL_INTERNAL_ADD_COMMENT];
|
|
3101
2932
|
}
|
|
3102
2933
|
}
|
|
3103
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3104
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "
|
|
2934
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtPortalContent, deps: [{ token: i0.ViewContainerRef }, { token: i0.ViewContainerRef, skipSelf: true }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2935
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.1", type: NgtPortalContent, isStandalone: true, selector: "ng-template[ngtPortalContent]", ngImport: i0 }); }
|
|
3105
2936
|
}
|
|
3106
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2937
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtPortalContent, decorators: [{
|
|
3107
2938
|
type: Directive,
|
|
3108
2939
|
args: [{ selector: 'ng-template[ngtPortalContent]', standalone: true }]
|
|
3109
|
-
}], ctorParameters:
|
|
2940
|
+
}], ctorParameters: () => [{ type: i0.ViewContainerRef }, { type: i0.ViewContainerRef, decorators: [{
|
|
3110
2941
|
type: SkipSelf
|
|
3111
|
-
}] }]
|
|
2942
|
+
}] }] });
|
|
3112
2943
|
class NgtPortal {
|
|
3113
|
-
set
|
|
3114
|
-
this.
|
|
2944
|
+
set _portalInputs(value) {
|
|
2945
|
+
this.portalInputs.update(value);
|
|
3115
2946
|
}
|
|
3116
|
-
set
|
|
3117
|
-
this.
|
|
2947
|
+
set _autoRender(value) {
|
|
2948
|
+
this.autoRender.set(value);
|
|
3118
2949
|
}
|
|
3119
2950
|
constructor() {
|
|
3120
|
-
this.
|
|
3121
|
-
|
|
2951
|
+
this.portalInputs = signalStore({
|
|
2952
|
+
container: injectNgtRef(prepare(new THREE.Scene())),
|
|
2953
|
+
});
|
|
2954
|
+
this.autoRender = signal(false);
|
|
3122
2955
|
this.autoRenderPriority = 1;
|
|
3123
|
-
this.
|
|
2956
|
+
this.destroyRef = inject(DestroyRef);
|
|
2957
|
+
this.autoEffect = injectAutoEffect();
|
|
3124
2958
|
this.parentStore = injectNgtStore({ skipSelf: true });
|
|
2959
|
+
this.portalStore = injectNgtStore({ self: true });
|
|
2960
|
+
this.portalRendered = signal(false);
|
|
2961
|
+
this.renderAutoBeforeRender = computed(() => this.portalRendered() && this.autoRender());
|
|
3125
2962
|
this.parentScene = this.parentStore.get('scene');
|
|
3126
2963
|
this.parentCamera = this.parentStore.get('camera');
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
this.portalContentView.destroy();
|
|
3136
|
-
}
|
|
3137
|
-
});
|
|
3138
|
-
}
|
|
3139
|
-
ngOnInit() {
|
|
3140
|
-
const previousState = this.parentStore.get();
|
|
3141
|
-
const inputsState = this.inputs.get();
|
|
3142
|
-
if (!inputsState.state && this.autoRender) {
|
|
3143
|
-
inputsState.state = { events: { priority: this.autoRenderPriority + 1 } };
|
|
3144
|
-
}
|
|
3145
|
-
const { events, size, ...restInputsState } = inputsState.state || {};
|
|
3146
|
-
const containerState = inputsState.container;
|
|
3147
|
-
let container = is.ref(containerState) ? containerState.nativeElement : containerState;
|
|
3148
|
-
if (!is.instance(container)) {
|
|
3149
|
-
container = prepare(container);
|
|
3150
|
-
}
|
|
3151
|
-
const localState = getLocalState(container);
|
|
3152
|
-
if (!localState.store) {
|
|
3153
|
-
localState.store = this.portalStore;
|
|
3154
|
-
}
|
|
3155
|
-
this.portalStore.set({
|
|
3156
|
-
...previousState,
|
|
3157
|
-
scene: container,
|
|
3158
|
-
raycaster: this.raycaster,
|
|
3159
|
-
pointer: this.pointer,
|
|
3160
|
-
previousRoot: this.parentStore,
|
|
3161
|
-
events: { ...previousState.events, ...(events || {}) },
|
|
3162
|
-
size: { ...previousState.size, ...(size || {}) },
|
|
3163
|
-
...restInputsState,
|
|
3164
|
-
setEvents: (events) => this.portalStore.set((state) => ({ ...state, events: { ...state.events, ...events } })),
|
|
3165
|
-
});
|
|
3166
|
-
const parentState = this.parentStore.select();
|
|
3167
|
-
effect(() => {
|
|
3168
|
-
const previous = parentState();
|
|
3169
|
-
this.zone.runOutsideAngular(() => {
|
|
3170
|
-
this.portalStore.set((state) => this.inject(previous, state));
|
|
2964
|
+
afterNextRender(() => {
|
|
2965
|
+
const parentState = this.parentStore.snapshot;
|
|
2966
|
+
const { container, state: { events = {}, size = {}, ...rest }, } = this.portalInputs.snapshot;
|
|
2967
|
+
this.portalStore.update({
|
|
2968
|
+
scene: (is.ref(container) ? container.nativeElement : container),
|
|
2969
|
+
events: { ...parentState.events, ...events },
|
|
2970
|
+
size: { ...parentState.size, ...size },
|
|
2971
|
+
...rest,
|
|
3171
2972
|
});
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
root: { ...this.parentStore.get(), delta: portal.delta, frame: portal.frame },
|
|
3183
|
-
portal,
|
|
2973
|
+
this.autoEffect(() => {
|
|
2974
|
+
const previous = this.parentStore.state();
|
|
2975
|
+
this.portalStore.update((state) => this.inject(previous, state));
|
|
2976
|
+
});
|
|
2977
|
+
untracked(() => {
|
|
2978
|
+
const portalView = this.portalContentAnchor.createEmbeddedView(this.portalContentTemplate);
|
|
2979
|
+
portalView.detectChanges();
|
|
2980
|
+
this.destroyRef.onDestroy(portalView.destroy.bind(portalView));
|
|
2981
|
+
});
|
|
2982
|
+
this.portalRendered.set(true);
|
|
3184
2983
|
});
|
|
3185
2984
|
}
|
|
3186
2985
|
inject(rootState, injectState) {
|
|
@@ -3191,7 +2990,7 @@ class NgtPortal {
|
|
|
3191
2990
|
delete intersect[key];
|
|
3192
2991
|
}
|
|
3193
2992
|
});
|
|
3194
|
-
const inputs = this.
|
|
2993
|
+
const inputs = this.portalInputs.snapshot;
|
|
3195
2994
|
const { size, events, ...restInputsState } = inputs.state || {};
|
|
3196
2995
|
let viewport = undefined;
|
|
3197
2996
|
if (injectState && size) {
|
|
@@ -3203,8 +3002,6 @@ class NgtPortal {
|
|
|
3203
3002
|
return {
|
|
3204
3003
|
...intersect,
|
|
3205
3004
|
scene: is.ref(inputs.container) ? inputs.container.nativeElement : inputs.container,
|
|
3206
|
-
raycaster: this.raycaster,
|
|
3207
|
-
pointer: this.pointer,
|
|
3208
3005
|
previousRoot: this.parentStore,
|
|
3209
3006
|
events: { ...rootState.events, ...(injectState?.events || {}), ...events },
|
|
3210
3007
|
size: { ...rootState.size, ...size },
|
|
@@ -3212,51 +3009,47 @@ class NgtPortal {
|
|
|
3212
3009
|
...restInputsState,
|
|
3213
3010
|
};
|
|
3214
3011
|
}
|
|
3215
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3216
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
3012
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtPortal, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3013
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.2.1", type: NgtPortal, isStandalone: true, selector: "ngt-portal", inputs: { _portalInputs: ["options", "_portalInputs"], _autoRender: ["autoRender", "_autoRender"], autoRenderPriority: "autoRenderPriority" }, providers: [providePortalStore()], queries: [{ propertyName: "portalContentTemplate", first: true, predicate: NgtPortalContent, descendants: true, read: TemplateRef, static: true }], viewQueries: [{ propertyName: "portalContentAnchor", first: true, predicate: ["portalContentAnchor"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: `
|
|
3217
3014
|
<ng-container #portalContentAnchor>
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
/>
|
|
3015
|
+
@if (renderAutoBeforeRender()) {
|
|
3016
|
+
<ngt-portal-before-render
|
|
3017
|
+
[renderPriority]="autoRenderPriority"
|
|
3018
|
+
[parentScene]="parentScene"
|
|
3019
|
+
[parentCamera]="parentCamera"
|
|
3020
|
+
/>
|
|
3021
|
+
}
|
|
3226
3022
|
</ng-container>
|
|
3227
|
-
`, isInline: true, dependencies: [{ kind: "
|
|
3023
|
+
`, isInline: true, dependencies: [{ kind: "component", type: NgtPortalBeforeRender, selector: "ngt-portal-before-render", inputs: ["renderPriority", "parentScene", "parentCamera"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3228
3024
|
}
|
|
3229
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3025
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtPortal, decorators: [{
|
|
3230
3026
|
type: Component,
|
|
3231
3027
|
args: [{
|
|
3232
3028
|
selector: 'ngt-portal',
|
|
3233
3029
|
standalone: true,
|
|
3234
3030
|
template: `
|
|
3235
3031
|
<ng-container #portalContentAnchor>
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
/>
|
|
3032
|
+
@if (renderAutoBeforeRender()) {
|
|
3033
|
+
<ngt-portal-before-render
|
|
3034
|
+
[renderPriority]="autoRenderPriority"
|
|
3035
|
+
[parentScene]="parentScene"
|
|
3036
|
+
[parentCamera]="parentCamera"
|
|
3037
|
+
/>
|
|
3038
|
+
}
|
|
3244
3039
|
</ng-container>
|
|
3245
3040
|
`,
|
|
3246
|
-
imports: [
|
|
3247
|
-
providers: [
|
|
3041
|
+
imports: [NgtPortalBeforeRender],
|
|
3042
|
+
providers: [providePortalStore()],
|
|
3043
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3248
3044
|
}]
|
|
3249
|
-
}], ctorParameters:
|
|
3250
|
-
type: Input
|
|
3251
|
-
}], portalState: [{
|
|
3045
|
+
}], ctorParameters: () => [], propDecorators: { _portalInputs: [{
|
|
3252
3046
|
type: Input,
|
|
3253
|
-
args: ['
|
|
3254
|
-
}],
|
|
3255
|
-
type: Input
|
|
3047
|
+
args: [{ alias: 'options' }]
|
|
3048
|
+
}], _autoRender: [{
|
|
3049
|
+
type: Input,
|
|
3050
|
+
args: [{ alias: 'autoRender' }]
|
|
3256
3051
|
}], autoRenderPriority: [{
|
|
3257
3052
|
type: Input
|
|
3258
|
-
}], beforeRender: [{
|
|
3259
|
-
type: Output
|
|
3260
3053
|
}], portalContentTemplate: [{
|
|
3261
3054
|
type: ContentChild,
|
|
3262
3055
|
args: [NgtPortalContent, { read: TemplateRef, static: true }]
|
|
@@ -3272,14 +3065,14 @@ class NgtRoutedScene {
|
|
|
3272
3065
|
constructor(router, cdr) {
|
|
3273
3066
|
router.events
|
|
3274
3067
|
.pipe(filter((event) => event instanceof ActivationEnd), takeUntilDestroyed())
|
|
3275
|
-
.subscribe((
|
|
3068
|
+
.subscribe(cdr.detectChanges.bind(cdr));
|
|
3276
3069
|
}
|
|
3277
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3278
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
3070
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtRoutedScene, deps: [{ token: i1.Router }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3071
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.2.1", type: NgtRoutedScene, isStandalone: true, selector: "ngt-routed-scene", ngImport: i0, template: `
|
|
3279
3072
|
<router-outlet />
|
|
3280
3073
|
`, isInline: true, dependencies: [{ kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }] }); }
|
|
3281
3074
|
}
|
|
3282
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3075
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.1", ngImport: i0, type: NgtRoutedScene, decorators: [{
|
|
3283
3076
|
type: Component,
|
|
3284
3077
|
args: [{
|
|
3285
3078
|
standalone: true,
|
|
@@ -3289,11 +3082,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImpor
|
|
|
3289
3082
|
`,
|
|
3290
3083
|
imports: [RouterOutlet],
|
|
3291
3084
|
}]
|
|
3292
|
-
}], ctorParameters:
|
|
3085
|
+
}], ctorParameters: () => [{ type: i1.Router }, { type: i0.ChangeDetectorRef }] });
|
|
3086
|
+
|
|
3087
|
+
// TODO: use scheduler instead of force CD
|
|
3088
|
+
function cdAwareSignal(initialValue, { injector, ...options } = {}) {
|
|
3089
|
+
return assertInjector(cdAwareSignal, injector, () => {
|
|
3090
|
+
if (!options.equal) {
|
|
3091
|
+
options.equal = Object.is;
|
|
3092
|
+
}
|
|
3093
|
+
const cdr = inject(ChangeDetectorRef);
|
|
3094
|
+
const source = signal(initialValue, options);
|
|
3095
|
+
const originalSet = source.set.bind(source);
|
|
3096
|
+
const originalUpdate = source.update.bind(source);
|
|
3097
|
+
source.set = (...args) => {
|
|
3098
|
+
originalSet(...args);
|
|
3099
|
+
cdr.detectChanges();
|
|
3100
|
+
};
|
|
3101
|
+
source.update = (...args) => {
|
|
3102
|
+
originalUpdate(...args);
|
|
3103
|
+
cdr.detectChanges();
|
|
3104
|
+
};
|
|
3105
|
+
return source;
|
|
3106
|
+
});
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
function apiFactory(obj) {
|
|
3110
|
+
return obj.api;
|
|
3111
|
+
}
|
|
3112
|
+
function createApiToken(forwardedObject) {
|
|
3113
|
+
const [injectFn, provideFn] = createInjectionToken((apiFactory), {
|
|
3114
|
+
isRoot: false,
|
|
3115
|
+
deps: [forwardRef(forwardedObject)],
|
|
3116
|
+
});
|
|
3117
|
+
return [injectFn, () => provideFn()];
|
|
3118
|
+
}
|
|
3293
3119
|
|
|
3294
3120
|
/**
|
|
3295
3121
|
* Generated bundle index. Do not edit.
|
|
3296
3122
|
*/
|
|
3297
3123
|
|
|
3298
|
-
export { HTML, NGT_STORE, NgtArgs, NgtCanvas,
|
|
3124
|
+
export { HTML, NGT_STORE, NgtArgs, NgtCanvas, NgtPortal, NgtPortalContent, NgtRenderer, NgtRendererFactory, NgtRoutedScene, ROUTED_SCENE, addAfterEffect, addEffect, addTail, applyProps, cdAwareSignal, checkNeedsUpdate, checkUpdate, createApiToken, createAttachFunction, extend, getLocalState, injectBeforeRender, injectNgtLoader, injectNgtRef, injectNgtStore, invalidateInstance, is, makeCameraInstance, makeDpr, makeId, makeObjectGraph, makeRendererInstance, prepare, provideNgtRenderer, provideNgtStore, signalStore, updateCamera };
|
|
3299
3125
|
//# sourceMappingURL=angular-three.mjs.map
|