@react-three/fiber 9.0.0-alpha.2 → 9.0.0-alpha.3
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/CHANGELOG.md +962 -522
- package/dist/declarations/src/core/events.d.ts +91 -69
- package/dist/declarations/src/core/hooks.d.ts +53 -27
- package/dist/declarations/src/core/index.d.ts +15 -60
- package/dist/declarations/src/core/loop.d.ts +31 -15
- package/dist/declarations/src/core/reconciler.d.ts +43 -0
- package/dist/declarations/src/core/renderer.d.ts +85 -40
- package/dist/declarations/src/core/stages.d.ts +64 -59
- package/dist/declarations/src/core/store.d.ts +147 -109
- package/dist/declarations/src/core/utils.d.ts +128 -80
- package/dist/declarations/src/index.d.ts +6 -10
- package/dist/declarations/src/native/Canvas.d.ts +14 -8
- package/dist/declarations/src/native/events.d.ts +4 -4
- package/dist/declarations/src/native.d.ts +6 -8
- package/dist/declarations/src/three-types.d.ts +56 -47
- package/dist/declarations/src/web/Canvas.d.ts +24 -11
- package/dist/declarations/src/web/events.d.ts +4 -4
- package/dist/{index-5bd4d3cf.cjs.dev.js → loop-0698c205.cjs.dev.js} +1469 -1268
- package/dist/{index-8128f248.cjs.prod.js → loop-a0ef8208.cjs.prod.js} +1469 -1268
- package/dist/{index-47b7622a.esm.js → loop-b2aca207.esm.js} +1466 -1268
- package/dist/react-three-fiber.cjs.d.ts +1 -0
- package/dist/react-three-fiber.cjs.dev.js +126 -115
- package/dist/react-three-fiber.cjs.prod.js +126 -115
- package/dist/react-three-fiber.esm.js +92 -84
- package/native/dist/react-three-fiber-native.cjs.d.ts +1 -0
- package/native/dist/react-three-fiber-native.cjs.dev.js +278 -211
- package/native/dist/react-three-fiber-native.cjs.prod.js +278 -211
- package/native/dist/react-three-fiber-native.esm.js +242 -180
- package/native/package.json +5 -5
- package/package.json +18 -12
- package/readme.md +253 -202
- package/dist/declarations/src/native/polyfills.d.ts +0 -1
|
@@ -1,30 +1,448 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { DefaultEventPriority, ContinuousEventPriority, DiscreteEventPriority, ConcurrentRoot } from 'react-reconciler/constants';
|
|
4
|
-
import create from 'zustand';
|
|
3
|
+
import { NoEventPriority, DefaultEventPriority, ContinuousEventPriority, DiscreteEventPriority, ConcurrentRoot } from 'react-reconciler/constants';
|
|
4
|
+
import { create } from 'zustand';
|
|
5
|
+
import { useFiber, useContextBridge, traverseFiber } from 'its-fine';
|
|
6
|
+
import _extends from '@babel/runtime/helpers/esm/extends';
|
|
5
7
|
import Reconciler from 'react-reconciler';
|
|
6
8
|
import { unstable_scheduleCallback, unstable_IdlePriority } from 'scheduler';
|
|
7
9
|
import { suspend, preload, clear } from 'suspend-react';
|
|
8
10
|
|
|
11
|
+
var threeTypes = /*#__PURE__*/Object.freeze({
|
|
12
|
+
__proto__: null
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// TODO: handle constructor overloads
|
|
16
|
+
// https://github.com/pmndrs/react-three-fiber/pull/2931
|
|
17
|
+
// https://github.com/microsoft/TypeScript/issues/37079
|
|
18
|
+
|
|
19
|
+
const catalogue = {};
|
|
20
|
+
let i = 0;
|
|
21
|
+
const extend = objects => {
|
|
22
|
+
if (typeof objects === 'function') {
|
|
23
|
+
const Component = `${i++}`;
|
|
24
|
+
catalogue[Component] = objects;
|
|
25
|
+
|
|
26
|
+
// Returns a component whose name will be inferred in devtools
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
return /*#__PURE__*/React.forwardRef({
|
|
29
|
+
[objects.name]: (props, ref) => /*#__PURE__*/React.createElement(Component, _extends({}, props, {
|
|
30
|
+
ref: ref
|
|
31
|
+
}))
|
|
32
|
+
}[objects.name]);
|
|
33
|
+
} else {
|
|
34
|
+
return void Object.assign(catalogue, objects);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
function createInstance(type, props, root) {
|
|
38
|
+
// Get target from catalogue
|
|
39
|
+
const name = `${type[0].toUpperCase()}${type.slice(1)}`;
|
|
40
|
+
const target = catalogue[name];
|
|
41
|
+
|
|
42
|
+
// Validate element target
|
|
43
|
+
if (type !== 'primitive' && !target) throw new Error(`R3F: ${name} is not part of the THREE namespace! Did you forget to extend? See: https://docs.pmnd.rs/react-three-fiber/api/objects#using-3rd-party-objects-declaratively`);
|
|
44
|
+
|
|
45
|
+
// Validate primitives
|
|
46
|
+
if (type === 'primitive' && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`);
|
|
47
|
+
|
|
48
|
+
// Throw if an object or literal was passed for args
|
|
49
|
+
if (props.args !== undefined && !Array.isArray(props.args)) throw new Error('R3F: The args prop must be an array!');
|
|
50
|
+
|
|
51
|
+
// Create instance
|
|
52
|
+
const instance = prepare(props.object, root, type, props);
|
|
53
|
+
return instance;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// https://github.com/facebook/react/issues/20271
|
|
57
|
+
// This will make sure events and attach are only handled once when trees are complete
|
|
58
|
+
function handleContainerEffects(parent, child, beforeChild) {
|
|
59
|
+
// Bail if tree isn't mounted or parent is not a container.
|
|
60
|
+
// This ensures that the tree is finalized and React won't discard results to Suspense
|
|
61
|
+
const state = child.root.getState();
|
|
62
|
+
if (!parent.parent && parent.object !== state.scene) return;
|
|
63
|
+
|
|
64
|
+
// Create & link object on first run
|
|
65
|
+
if (!child.object) {
|
|
66
|
+
var _child$props$object, _child$props$args;
|
|
67
|
+
// Get target from catalogue
|
|
68
|
+
const name = `${child.type[0].toUpperCase()}${child.type.slice(1)}`;
|
|
69
|
+
const target = catalogue[name];
|
|
70
|
+
|
|
71
|
+
// Create object
|
|
72
|
+
child.object = (_child$props$object = child.props.object) != null ? _child$props$object : new target(...((_child$props$args = child.props.args) != null ? _child$props$args : []));
|
|
73
|
+
child.object.__r3f = child;
|
|
74
|
+
|
|
75
|
+
// Set initial props
|
|
76
|
+
applyProps(child.object, child.props);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Append instance
|
|
80
|
+
if (child.props.attach) {
|
|
81
|
+
attach(parent, child);
|
|
82
|
+
} else if (isObject3D(child.object) && isObject3D(parent.object)) {
|
|
83
|
+
const childIndex = parent.object.children.indexOf(beforeChild == null ? void 0 : beforeChild.object);
|
|
84
|
+
if (beforeChild && childIndex !== -1) {
|
|
85
|
+
child.object.parent = parent.object;
|
|
86
|
+
parent.object.children.splice(childIndex, 0, child.object);
|
|
87
|
+
child.object.dispatchEvent({
|
|
88
|
+
type: 'added'
|
|
89
|
+
});
|
|
90
|
+
parent.object.dispatchEvent({
|
|
91
|
+
type: 'childadded',
|
|
92
|
+
child: child.object
|
|
93
|
+
});
|
|
94
|
+
} else {
|
|
95
|
+
parent.object.add(child.object);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Link subtree
|
|
100
|
+
for (const childInstance of child.children) handleContainerEffects(child, childInstance);
|
|
101
|
+
|
|
102
|
+
// Tree was updated, request a frame
|
|
103
|
+
invalidateInstance(child);
|
|
104
|
+
}
|
|
105
|
+
function appendChild(parent, child) {
|
|
106
|
+
if (!child) return;
|
|
107
|
+
|
|
108
|
+
// Link instances
|
|
109
|
+
child.parent = parent;
|
|
110
|
+
parent.children.push(child);
|
|
111
|
+
|
|
112
|
+
// Attach tree once complete
|
|
113
|
+
handleContainerEffects(parent, child);
|
|
114
|
+
}
|
|
115
|
+
function insertBefore(parent, child, beforeChild) {
|
|
116
|
+
if (!child || !beforeChild) return;
|
|
117
|
+
|
|
118
|
+
// Link instances
|
|
119
|
+
child.parent = parent;
|
|
120
|
+
const childIndex = parent.children.indexOf(beforeChild);
|
|
121
|
+
if (childIndex !== -1) parent.children.splice(childIndex, 0, child);else parent.children.push(child);
|
|
122
|
+
|
|
123
|
+
// Attach tree once complete
|
|
124
|
+
handleContainerEffects(parent, child, beforeChild);
|
|
125
|
+
}
|
|
126
|
+
function removeChild(parent, child, dispose, recursive) {
|
|
127
|
+
if (!child) return;
|
|
128
|
+
|
|
129
|
+
// Unlink instances
|
|
130
|
+
child.parent = null;
|
|
131
|
+
if (recursive === undefined) {
|
|
132
|
+
const childIndex = parent.children.indexOf(child);
|
|
133
|
+
if (childIndex !== -1) parent.children.splice(childIndex, 1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Eagerly tear down tree
|
|
137
|
+
if (child.props.attach) {
|
|
138
|
+
detach(parent, child);
|
|
139
|
+
} else if (isObject3D(child.object) && isObject3D(parent.object)) {
|
|
140
|
+
parent.object.remove(child.object);
|
|
141
|
+
removeInteractivity(findInitialRoot(child), child.object);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Allow objects to bail out of unmount disposal with dispose={null}
|
|
145
|
+
const shouldDispose = child.props.dispose !== null && dispose !== false;
|
|
146
|
+
|
|
147
|
+
// Recursively remove instance children
|
|
148
|
+
if (recursive !== false) {
|
|
149
|
+
for (const node of child.children) removeChild(child, node, shouldDispose, true);
|
|
150
|
+
child.children.length = 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Unlink instance object
|
|
154
|
+
delete child.object.__r3f;
|
|
155
|
+
|
|
156
|
+
// Dispose object whenever the reconciler feels like it.
|
|
157
|
+
// Never dispose of primitives because their state may be kept outside of React!
|
|
158
|
+
// In order for an object to be able to dispose it
|
|
159
|
+
// - has a dispose method
|
|
160
|
+
// - cannot be a <primitive object={...} />
|
|
161
|
+
// - cannot be a THREE.Scene, because three has broken its own API
|
|
162
|
+
if (shouldDispose && child.type !== 'primitive' && child.object.type !== 'Scene') {
|
|
163
|
+
if (typeof child.object.dispose === 'function') {
|
|
164
|
+
const dispose = child.object.dispose.bind(child.object);
|
|
165
|
+
const handleDispose = () => {
|
|
166
|
+
try {
|
|
167
|
+
dispose();
|
|
168
|
+
} catch (e) {
|
|
169
|
+
// no-op
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// In a testing environment, cleanup immediately
|
|
174
|
+
if (typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined') handleDispose();
|
|
175
|
+
// Otherwise, using a real GPU so schedule cleanup to prevent stalls
|
|
176
|
+
else unstable_scheduleCallback(unstable_IdlePriority, handleDispose);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Tree was updated, request a frame for top-level instance
|
|
181
|
+
if (dispose === undefined) invalidateInstance(child);
|
|
182
|
+
}
|
|
183
|
+
function setFiberInstance(fiber, instance) {
|
|
184
|
+
if (fiber !== null) {
|
|
185
|
+
fiber.stateNode = instance;
|
|
186
|
+
if (typeof fiber.ref === 'function') fiber.ref(instance.object);else if (fiber.ref) fiber.ref.current = instance.object;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function switchInstance(oldInstance, type, props, fiber) {
|
|
190
|
+
// Create a new instance
|
|
191
|
+
const newInstance = createInstance(type, props, oldInstance.root);
|
|
192
|
+
|
|
193
|
+
// Move children to new instance
|
|
194
|
+
for (const child of oldInstance.children) {
|
|
195
|
+
removeChild(oldInstance, child, false, false);
|
|
196
|
+
appendChild(newInstance, child);
|
|
197
|
+
}
|
|
198
|
+
oldInstance.children.length = 0;
|
|
199
|
+
|
|
200
|
+
// Link up new instance
|
|
201
|
+
const parent = oldInstance.parent;
|
|
202
|
+
if (parent) {
|
|
203
|
+
// Manually handle replace https://github.com/pmndrs/react-three-fiber/pull/2680
|
|
204
|
+
|
|
205
|
+
newInstance.autoRemovedBeforeAppend = !!newInstance.parent;
|
|
206
|
+
if (!oldInstance.autoRemovedBeforeAppend) removeChild(parent, oldInstance);
|
|
207
|
+
appendChild(parent, newInstance);
|
|
208
|
+
|
|
209
|
+
// if (!oldInstance.autoRemovedBeforeAppend) {
|
|
210
|
+
// insertBefore(parent, newInstance, oldInstance)
|
|
211
|
+
// removeChild(parent, oldInstance)
|
|
212
|
+
// } else {
|
|
213
|
+
// appendChild(parent, newInstance)
|
|
214
|
+
// }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// This evil hack switches the react-internal fiber instance
|
|
218
|
+
// https://github.com/facebook/react/issues/14983
|
|
219
|
+
// TODO: investigate scheduling key prop change instead of switchInstance entirely
|
|
220
|
+
// https://github.com/facebook/react/pull/15021#issuecomment-480185369
|
|
221
|
+
setFiberInstance(fiber, newInstance);
|
|
222
|
+
setFiberInstance(fiber.alternate, newInstance);
|
|
223
|
+
|
|
224
|
+
// Tree was updated, request a frame
|
|
225
|
+
invalidateInstance(newInstance);
|
|
226
|
+
return newInstance;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Don't handle text instances, warn on undefined behavior
|
|
230
|
+
const handleTextInstance = () => console.warn('R3F: Text is not allowed in JSX! This could be stray whitespace or characters.');
|
|
231
|
+
const NO_CONTEXT = {};
|
|
232
|
+
let currentUpdatePriority = NoEventPriority;
|
|
233
|
+
|
|
234
|
+
// Effectively removed to diff in commit phase
|
|
235
|
+
// https://github.com/facebook/react/pull/27409
|
|
236
|
+
function prepareUpdate(instance, _type, oldProps, newProps) {
|
|
237
|
+
var _newProps$args, _oldProps$args, _newProps$args2;
|
|
238
|
+
// Reconstruct primitives if object prop changes
|
|
239
|
+
if (instance.type === 'primitive' && oldProps.object !== newProps.object) return [true];
|
|
240
|
+
|
|
241
|
+
// Throw if an object or literal was passed for args
|
|
242
|
+
if (newProps.args !== undefined && !Array.isArray(newProps.args)) throw new Error('R3F: The args prop must be an array!');
|
|
243
|
+
|
|
244
|
+
// Reconstruct instance if args change
|
|
245
|
+
if (((_newProps$args = newProps.args) == null ? void 0 : _newProps$args.length) !== ((_oldProps$args = oldProps.args) == null ? void 0 : _oldProps$args.length)) return [true];
|
|
246
|
+
if ((_newProps$args2 = newProps.args) != null && _newProps$args2.some((value, index) => {
|
|
247
|
+
var _oldProps$args2;
|
|
248
|
+
return value !== ((_oldProps$args2 = oldProps.args) == null ? void 0 : _oldProps$args2[index]);
|
|
249
|
+
})) return [true];
|
|
250
|
+
|
|
251
|
+
// Create a diff-set, flag if there are any changes
|
|
252
|
+
const changedProps = diffProps(instance, newProps, true);
|
|
253
|
+
if (Object.keys(changedProps).length) return [false, changedProps];
|
|
254
|
+
|
|
255
|
+
// Otherwise do not touch the instance
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const reconciler = Reconciler({
|
|
259
|
+
isPrimaryRenderer: false,
|
|
260
|
+
warnsIfNotActing: false,
|
|
261
|
+
supportsMutation: true,
|
|
262
|
+
supportsPersistence: false,
|
|
263
|
+
supportsHydration: false,
|
|
264
|
+
createInstance,
|
|
265
|
+
removeChild,
|
|
266
|
+
appendChild,
|
|
267
|
+
appendInitialChild: appendChild,
|
|
268
|
+
insertBefore,
|
|
269
|
+
appendChildToContainer(container, child) {
|
|
270
|
+
const scene = container.getState().scene.__r3f;
|
|
271
|
+
if (!child || !scene) return;
|
|
272
|
+
appendChild(scene, child);
|
|
273
|
+
},
|
|
274
|
+
removeChildFromContainer(container, child) {
|
|
275
|
+
const scene = container.getState().scene.__r3f;
|
|
276
|
+
if (!child || !scene) return;
|
|
277
|
+
removeChild(scene, child);
|
|
278
|
+
},
|
|
279
|
+
insertInContainerBefore(container, child, beforeChild) {
|
|
280
|
+
const scene = container.getState().scene.__r3f;
|
|
281
|
+
if (!child || !beforeChild || !scene) return;
|
|
282
|
+
insertBefore(scene, child, beforeChild);
|
|
283
|
+
},
|
|
284
|
+
getRootHostContext: () => NO_CONTEXT,
|
|
285
|
+
getChildHostContext: () => NO_CONTEXT,
|
|
286
|
+
// @ts-ignore prepareUpdate and updatePayload removed with React 19
|
|
287
|
+
commitUpdate(instance, type, oldProps, newProps, fiber) {
|
|
288
|
+
const diff = prepareUpdate(instance, type, oldProps, newProps);
|
|
289
|
+
if (diff === null) return;
|
|
290
|
+
const [reconstruct, changedProps] = diff;
|
|
291
|
+
|
|
292
|
+
// Reconstruct when args or <primitive object={...} have changes
|
|
293
|
+
if (reconstruct) return switchInstance(instance, type, newProps, fiber);
|
|
294
|
+
|
|
295
|
+
// Otherwise just overwrite props
|
|
296
|
+
Object.assign(instance.props, changedProps);
|
|
297
|
+
applyProps(instance.object, changedProps);
|
|
298
|
+
},
|
|
299
|
+
finalizeInitialChildren: () => false,
|
|
300
|
+
commitMount() {},
|
|
301
|
+
getPublicInstance: instance => instance == null ? void 0 : instance.object,
|
|
302
|
+
prepareForCommit: () => null,
|
|
303
|
+
preparePortalMount: container => prepare(container.getState().scene, container, '', {}),
|
|
304
|
+
resetAfterCommit: () => {},
|
|
305
|
+
shouldSetTextContent: () => false,
|
|
306
|
+
clearContainer: () => false,
|
|
307
|
+
hideInstance(instance) {
|
|
308
|
+
var _instance$parent;
|
|
309
|
+
if (instance.props.attach && (_instance$parent = instance.parent) != null && _instance$parent.object) {
|
|
310
|
+
detach(instance.parent, instance);
|
|
311
|
+
} else if (isObject3D(instance.object)) {
|
|
312
|
+
instance.object.visible = false;
|
|
313
|
+
}
|
|
314
|
+
instance.isHidden = true;
|
|
315
|
+
invalidateInstance(instance);
|
|
316
|
+
},
|
|
317
|
+
unhideInstance(instance) {
|
|
318
|
+
if (instance.isHidden) {
|
|
319
|
+
var _instance$parent2;
|
|
320
|
+
if (instance.props.attach && (_instance$parent2 = instance.parent) != null && _instance$parent2.object) {
|
|
321
|
+
attach(instance.parent, instance);
|
|
322
|
+
} else if (isObject3D(instance.object) && instance.props.visible !== false) {
|
|
323
|
+
instance.object.visible = true;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
instance.isHidden = false;
|
|
327
|
+
invalidateInstance(instance);
|
|
328
|
+
},
|
|
329
|
+
createTextInstance: handleTextInstance,
|
|
330
|
+
hideTextInstance: handleTextInstance,
|
|
331
|
+
unhideTextInstance: handleTextInstance,
|
|
332
|
+
scheduleTimeout: typeof setTimeout === 'function' ? setTimeout : undefined,
|
|
333
|
+
cancelTimeout: typeof clearTimeout === 'function' ? clearTimeout : undefined,
|
|
334
|
+
noTimeout: -1,
|
|
335
|
+
getInstanceFromNode: () => null,
|
|
336
|
+
beforeActiveInstanceBlur() {},
|
|
337
|
+
afterActiveInstanceBlur() {},
|
|
338
|
+
detachDeletedInstance() {},
|
|
339
|
+
// @ts-ignore untyped react-experimental options inspired by react-art
|
|
340
|
+
// TODO: add shell types for these and upstream to DefinitelyTyped
|
|
341
|
+
// https://github.com/facebook/react/blob/main/packages/react-art/src/ReactFiberConfigART.js
|
|
342
|
+
shouldAttemptEagerTransition() {
|
|
343
|
+
return false;
|
|
344
|
+
},
|
|
345
|
+
requestPostPaintCallback() {},
|
|
346
|
+
maySuspendCommit() {
|
|
347
|
+
return false;
|
|
348
|
+
},
|
|
349
|
+
preloadInstance() {
|
|
350
|
+
return true; // true indicates already loaded
|
|
351
|
+
},
|
|
352
|
+
startSuspendingCommit() {},
|
|
353
|
+
suspendInstance() {},
|
|
354
|
+
waitForCommitToBeReady() {
|
|
355
|
+
return null;
|
|
356
|
+
},
|
|
357
|
+
NotPendingTransition: null,
|
|
358
|
+
setCurrentUpdatePriority(newPriority) {
|
|
359
|
+
currentUpdatePriority = newPriority;
|
|
360
|
+
},
|
|
361
|
+
getCurrentUpdatePriority() {
|
|
362
|
+
return currentUpdatePriority;
|
|
363
|
+
},
|
|
364
|
+
resolveUpdatePriority() {
|
|
365
|
+
var _window$event;
|
|
366
|
+
if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
|
|
367
|
+
switch (typeof window !== 'undefined' && ((_window$event = window.event) == null ? void 0 : _window$event.type)) {
|
|
368
|
+
case 'click':
|
|
369
|
+
case 'contextmenu':
|
|
370
|
+
case 'dblclick':
|
|
371
|
+
case 'pointercancel':
|
|
372
|
+
case 'pointerdown':
|
|
373
|
+
case 'pointerup':
|
|
374
|
+
return DiscreteEventPriority;
|
|
375
|
+
case 'pointermove':
|
|
376
|
+
case 'pointerout':
|
|
377
|
+
case 'pointerover':
|
|
378
|
+
case 'pointerenter':
|
|
379
|
+
case 'pointerleave':
|
|
380
|
+
case 'wheel':
|
|
381
|
+
return ContinuousEventPriority;
|
|
382
|
+
default:
|
|
383
|
+
return DefaultEventPriority;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
9
388
|
var _window$document, _window$navigator;
|
|
389
|
+
/**
|
|
390
|
+
* Returns the instance's initial (outmost) root.
|
|
391
|
+
*/
|
|
392
|
+
function findInitialRoot(instance) {
|
|
393
|
+
let root = instance.root;
|
|
394
|
+
// TODO: this needs testing https://github.com/pmndrs/react-three-fiber/commit/a4a31ed93c48d1e6dac91329bb5f2ca6a25e5f9c
|
|
395
|
+
// while (root.getState().previousRoot) root = root.getState().previousRoot!
|
|
396
|
+
return root;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Returns `true` with correct TS type inference if an object has a configurable color space (since r152).
|
|
401
|
+
*/
|
|
402
|
+
const hasColorSpace = object => 'colorSpace' in object || 'outputColorSpace' in object;
|
|
403
|
+
/**
|
|
404
|
+
* The current THREE.ColorManagement instance, if present.
|
|
405
|
+
*/
|
|
406
|
+
const getColorManagement = () => {
|
|
407
|
+
var _ColorManagement;
|
|
408
|
+
return (_ColorManagement = catalogue.ColorManagement) != null ? _ColorManagement : null;
|
|
409
|
+
};
|
|
410
|
+
/**
|
|
411
|
+
* Safely flush async effects when testing, simulating a legacy root.
|
|
412
|
+
*/
|
|
413
|
+
const act = React.act;
|
|
10
414
|
const isOrthographicCamera = def => def && def.isOrthographicCamera;
|
|
11
415
|
const isRef = obj => obj && obj.hasOwnProperty('current');
|
|
12
|
-
/**
|
|
13
|
-
* An SSR-friendly useLayoutEffect.
|
|
14
|
-
*
|
|
15
|
-
* React currently throws a warning when using useLayoutEffect on the server.
|
|
16
|
-
* To get around it, we can conditionally useEffect on the server (no-op) and
|
|
17
|
-
* useLayoutEffect elsewhere.
|
|
18
|
-
*
|
|
19
|
-
* @see https://github.com/facebook/react/issues/14927
|
|
20
|
-
*/
|
|
21
416
|
|
|
417
|
+
/**
|
|
418
|
+
* An SSR-friendly useLayoutEffect.
|
|
419
|
+
*
|
|
420
|
+
* React currently throws a warning when using useLayoutEffect on the server.
|
|
421
|
+
* To get around it, we can conditionally useEffect on the server (no-op) and
|
|
422
|
+
* useLayoutEffect elsewhere.
|
|
423
|
+
*
|
|
424
|
+
* @see https://github.com/facebook/react/issues/14927
|
|
425
|
+
*/
|
|
22
426
|
const useIsomorphicLayoutEffect = typeof window !== 'undefined' && ((_window$document = window.document) != null && _window$document.createElement || ((_window$navigator = window.navigator) == null ? void 0 : _window$navigator.product) === 'ReactNative') ? React.useLayoutEffect : React.useEffect;
|
|
23
427
|
function useMutableCallback(fn) {
|
|
24
428
|
const ref = React.useRef(fn);
|
|
25
429
|
useIsomorphicLayoutEffect(() => void (ref.current = fn), [fn]);
|
|
26
430
|
return ref;
|
|
27
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* Bridges renderer Context and StrictMode from a primary renderer.
|
|
434
|
+
*/
|
|
435
|
+
function useBridge() {
|
|
436
|
+
const fiber = useFiber();
|
|
437
|
+
const ContextBridge = useContextBridge();
|
|
438
|
+
return React.useMemo(() => ({
|
|
439
|
+
children
|
|
440
|
+
}) => {
|
|
441
|
+
const strict = !!traverseFiber(fiber, true, node => node.type === React.StrictMode);
|
|
442
|
+
const Root = strict ? React.StrictMode : React.Fragment;
|
|
443
|
+
return /*#__PURE__*/React.createElement(Root, null, /*#__PURE__*/React.createElement(ContextBridge, null, children));
|
|
444
|
+
}, [fiber, ContextBridge]);
|
|
445
|
+
}
|
|
28
446
|
function Block({
|
|
29
447
|
set
|
|
30
448
|
}) {
|
|
@@ -41,34 +459,31 @@ class ErrorBoundary extends React.Component {
|
|
|
41
459
|
error: false
|
|
42
460
|
};
|
|
43
461
|
}
|
|
44
|
-
|
|
45
462
|
componentDidCatch(err) {
|
|
46
463
|
this.props.set(err);
|
|
47
464
|
}
|
|
48
|
-
|
|
49
465
|
render() {
|
|
50
466
|
return this.state.error ? null : this.props.children;
|
|
51
467
|
}
|
|
52
|
-
|
|
53
468
|
}
|
|
54
|
-
|
|
55
469
|
ErrorBoundary.getDerivedStateFromError = () => ({
|
|
56
470
|
error: true
|
|
57
471
|
});
|
|
58
|
-
|
|
59
472
|
function calculateDpr(dpr) {
|
|
60
|
-
|
|
473
|
+
var _window$devicePixelRa;
|
|
474
|
+
// Err on the side of progress by assuming 2x dpr if we can't detect it
|
|
475
|
+
// This will happen in workers where window is defined but dpr isn't.
|
|
476
|
+
const target = typeof window !== 'undefined' ? (_window$devicePixelRa = window.devicePixelRatio) != null ? _window$devicePixelRa : 2 : 1;
|
|
61
477
|
return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
|
|
62
478
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Returns instance root state
|
|
65
|
-
*/
|
|
66
479
|
|
|
67
|
-
|
|
480
|
+
/**
|
|
481
|
+
* Returns instance root state
|
|
482
|
+
*/
|
|
483
|
+
function getRootState(obj) {
|
|
68
484
|
var _r3f;
|
|
69
|
-
|
|
70
485
|
return (_r3f = obj.__r3f) == null ? void 0 : _r3f.root.getState();
|
|
71
|
-
}
|
|
486
|
+
}
|
|
72
487
|
// A collection of compare functions
|
|
73
488
|
const is = {
|
|
74
489
|
obj: a => a === Object(a) && !is.arr(a) && typeof a !== 'function',
|
|
@@ -78,81 +493,86 @@ const is = {
|
|
|
78
493
|
boo: a => typeof a === 'boolean',
|
|
79
494
|
und: a => a === void 0,
|
|
80
495
|
arr: a => Array.isArray(a),
|
|
81
|
-
|
|
82
496
|
equ(a, b, {
|
|
83
497
|
arrays = 'shallow',
|
|
84
498
|
objects = 'reference',
|
|
85
499
|
strict = true
|
|
86
500
|
} = {}) {
|
|
87
501
|
// Wrong type or one of the two undefined, doesn't match
|
|
88
|
-
if (typeof a !== typeof b || !!a !== !!b) return false;
|
|
89
|
-
|
|
502
|
+
if (typeof a !== typeof b || !!a !== !!b) return false;
|
|
503
|
+
// Atomic, just compare a against b
|
|
90
504
|
if (is.str(a) || is.num(a)) return a === b;
|
|
91
505
|
const isObj = is.obj(a);
|
|
92
506
|
if (isObj && objects === 'reference') return a === b;
|
|
93
507
|
const isArr = is.arr(a);
|
|
94
|
-
if (isArr && arrays === 'reference') return a === b;
|
|
95
|
-
|
|
96
|
-
if ((isArr || isObj) && a === b) return true;
|
|
97
|
-
|
|
508
|
+
if (isArr && arrays === 'reference') return a === b;
|
|
509
|
+
// Array or Object, shallow compare first to see if it's a match
|
|
510
|
+
if ((isArr || isObj) && a === b) return true;
|
|
511
|
+
// Last resort, go through keys
|
|
98
512
|
let i;
|
|
99
|
-
|
|
513
|
+
// Check if a has all the keys of b
|
|
100
514
|
for (i in a) if (!(i in b)) return false;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
515
|
+
// Check if values between keys match
|
|
516
|
+
if (isObj && arrays === 'shallow' && objects === 'shallow') {
|
|
517
|
+
for (i in strict ? b : a) if (!is.equ(a[i], b[i], {
|
|
518
|
+
strict,
|
|
519
|
+
objects: 'reference'
|
|
520
|
+
})) return false;
|
|
521
|
+
} else {
|
|
522
|
+
for (i in strict ? b : a) if (a[i] !== b[i]) return false;
|
|
523
|
+
}
|
|
524
|
+
// If i is undefined
|
|
104
525
|
if (is.und(i)) {
|
|
526
|
+
// If both arrays are empty we consider them equal
|
|
105
527
|
if (isArr && a.length === 0 && b.length === 0) return true;
|
|
528
|
+
// If both objects are empty we consider them equal
|
|
106
529
|
if (isObj && Object.keys(a).length === 0 && Object.keys(b).length === 0) return true;
|
|
530
|
+
// Otherwise match them by value
|
|
107
531
|
if (a !== b) return false;
|
|
108
532
|
}
|
|
109
|
-
|
|
110
533
|
return true;
|
|
111
534
|
}
|
|
535
|
+
};
|
|
112
536
|
|
|
113
|
-
|
|
114
|
-
|
|
537
|
+
// Collects nodes and materials from a THREE.Object3D
|
|
115
538
|
function buildGraph(object) {
|
|
116
539
|
const data = {
|
|
117
540
|
nodes: {},
|
|
118
541
|
materials: {}
|
|
119
542
|
};
|
|
120
|
-
|
|
121
543
|
if (object) {
|
|
122
544
|
object.traverse(obj => {
|
|
123
545
|
if (obj.name) data.nodes[obj.name] = obj;
|
|
124
546
|
if (obj.material && !data.materials[obj.material.name]) data.materials[obj.material.name] = obj.material;
|
|
125
547
|
});
|
|
126
548
|
}
|
|
127
|
-
|
|
128
549
|
return data;
|
|
129
550
|
}
|
|
130
551
|
// Disposes an object and all its properties
|
|
131
552
|
function dispose(obj) {
|
|
132
553
|
if (obj.type !== 'Scene') obj.dispose == null ? void 0 : obj.dispose();
|
|
133
|
-
|
|
134
554
|
for (const p in obj) {
|
|
135
555
|
const prop = obj[p];
|
|
136
556
|
if ((prop == null ? void 0 : prop.type) !== 'Scene') prop == null ? void 0 : prop.dispose == null ? void 0 : prop.dispose();
|
|
137
557
|
}
|
|
138
558
|
}
|
|
139
|
-
const REACT_INTERNAL_PROPS = ['children', 'key', 'ref'];
|
|
559
|
+
const REACT_INTERNAL_PROPS = ['children', 'key', 'ref'];
|
|
140
560
|
|
|
561
|
+
// Gets only instance props from reconciler fibers
|
|
141
562
|
function getInstanceProps(queue) {
|
|
142
563
|
const props = {};
|
|
143
|
-
|
|
144
564
|
for (const key in queue) {
|
|
145
565
|
if (!REACT_INTERNAL_PROPS.includes(key)) props[key] = queue[key];
|
|
146
566
|
}
|
|
147
|
-
|
|
148
567
|
return props;
|
|
149
|
-
}
|
|
568
|
+
}
|
|
150
569
|
|
|
570
|
+
// Each object in the scene carries a small LocalState descriptor
|
|
151
571
|
function prepare(target, root, type, props) {
|
|
152
|
-
const object = target;
|
|
153
|
-
|
|
154
|
-
let instance = object.__r3f;
|
|
572
|
+
const object = target;
|
|
155
573
|
|
|
574
|
+
// Create instance descriptor
|
|
575
|
+
let instance = object == null ? void 0 : object.__r3f;
|
|
156
576
|
if (!instance) {
|
|
157
577
|
instance = {
|
|
158
578
|
root,
|
|
@@ -165,33 +585,37 @@ function prepare(target, root, type, props) {
|
|
|
165
585
|
handlers: {},
|
|
166
586
|
isHidden: false
|
|
167
587
|
};
|
|
168
|
-
object
|
|
588
|
+
if (object) {
|
|
589
|
+
object.__r3f = instance;
|
|
590
|
+
if (type) applyProps(object, instance.props);
|
|
591
|
+
}
|
|
169
592
|
}
|
|
170
|
-
|
|
171
593
|
return instance;
|
|
172
594
|
}
|
|
173
595
|
function resolve(root, key) {
|
|
174
596
|
var _target;
|
|
175
|
-
|
|
176
597
|
let target = root[key];
|
|
177
598
|
if (!key.includes('-')) return {
|
|
178
599
|
root,
|
|
179
600
|
key,
|
|
180
601
|
target
|
|
181
|
-
};
|
|
602
|
+
};
|
|
182
603
|
|
|
604
|
+
// Resolve pierced target
|
|
183
605
|
const chain = key.split('-');
|
|
184
606
|
target = chain.reduce((acc, key) => acc[key], root);
|
|
185
|
-
key = chain.pop();
|
|
607
|
+
key = chain.pop();
|
|
186
608
|
|
|
609
|
+
// Switch root if atomic
|
|
187
610
|
if (!((_target = target) != null && _target.set)) root = chain.reduce((acc, key) => acc[key], root);
|
|
188
611
|
return {
|
|
189
612
|
root,
|
|
190
613
|
key,
|
|
191
614
|
target
|
|
192
615
|
};
|
|
193
|
-
}
|
|
616
|
+
}
|
|
194
617
|
|
|
618
|
+
// Checks if a dash-cased string ends with an integer
|
|
195
619
|
const INDEX_REGEX = /-\d+$/;
|
|
196
620
|
function attach(parent, child) {
|
|
197
621
|
if (is.str(child.props.attach)) {
|
|
@@ -204,7 +628,6 @@ function attach(parent, child) {
|
|
|
204
628
|
} = resolve(parent.object, index);
|
|
205
629
|
if (!Array.isArray(root[key])) root[key] = [];
|
|
206
630
|
}
|
|
207
|
-
|
|
208
631
|
const {
|
|
209
632
|
root,
|
|
210
633
|
key
|
|
@@ -221,483 +644,237 @@ function detach(parent, child) {
|
|
|
221
644
|
root,
|
|
222
645
|
key
|
|
223
646
|
} = resolve(parent.object, child.props.attach);
|
|
224
|
-
const previous = child.previousAttach;
|
|
225
|
-
|
|
226
|
-
if (previous === undefined) delete root[key];
|
|
647
|
+
const previous = child.previousAttach;
|
|
648
|
+
// When the previous value was undefined, it means the value was never set to begin with
|
|
649
|
+
if (previous === undefined) delete root[key];
|
|
650
|
+
// Otherwise set the previous value
|
|
227
651
|
else root[key] = previous;
|
|
228
652
|
} else {
|
|
229
653
|
child.previousAttach == null ? void 0 : child.previousAttach(parent.object, child.object);
|
|
230
654
|
}
|
|
231
|
-
|
|
232
655
|
delete child.previousAttach;
|
|
233
656
|
}
|
|
234
|
-
const RESERVED_PROPS = [...REACT_INTERNAL_PROPS,
|
|
235
|
-
|
|
236
|
-
'dispose'
|
|
237
|
-
|
|
657
|
+
const RESERVED_PROPS = [...REACT_INTERNAL_PROPS,
|
|
658
|
+
// Instance props
|
|
659
|
+
'args', 'dispose', 'attach', 'object',
|
|
660
|
+
// Behavior flags
|
|
661
|
+
'dispose'];
|
|
662
|
+
const MEMOIZED_PROTOTYPES = new Map();
|
|
663
|
+
|
|
664
|
+
// This function prepares a set of changes to be applied to the instance
|
|
238
665
|
function diffProps(instance, newProps, resetRemoved = false) {
|
|
239
|
-
const changedProps = {};
|
|
666
|
+
const changedProps = {};
|
|
240
667
|
|
|
668
|
+
// Sort through props
|
|
241
669
|
for (const prop in newProps) {
|
|
242
670
|
// Skip reserved keys
|
|
243
|
-
if (RESERVED_PROPS.includes(prop)) continue;
|
|
244
|
-
|
|
245
|
-
if (is.equ(newProps[prop], instance.props[prop])) continue;
|
|
671
|
+
if (RESERVED_PROPS.includes(prop)) continue;
|
|
672
|
+
// Skip if props match
|
|
673
|
+
if (is.equ(newProps[prop], instance.props[prop])) continue;
|
|
246
674
|
|
|
675
|
+
// Props changed, add them
|
|
247
676
|
changedProps[prop] = newProps[prop];
|
|
248
|
-
} // Reset removed props for HMR
|
|
249
677
|
|
|
678
|
+
// Reset pierced props
|
|
679
|
+
for (const other in newProps) {
|
|
680
|
+
if (other.startsWith(`${prop}-`)) changedProps[other] = newProps[other];
|
|
681
|
+
}
|
|
682
|
+
}
|
|
250
683
|
|
|
684
|
+
// Reset removed props for HMR
|
|
251
685
|
if (resetRemoved) {
|
|
252
686
|
for (const prop in instance.props) {
|
|
253
687
|
if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
|
|
254
688
|
const {
|
|
255
689
|
root,
|
|
256
690
|
key
|
|
257
|
-
} = resolve(instance.object, prop);
|
|
691
|
+
} = resolve(instance.object, prop);
|
|
692
|
+
|
|
693
|
+
// https://github.com/mrdoob/three.js/issues/21209
|
|
258
694
|
// HMR/fast-refresh relies on the ability to cancel out props, but threejs
|
|
259
695
|
// has no means to do this. Hence we curate a small collection of value-classes
|
|
260
696
|
// with their respective constructor/set arguments
|
|
261
697
|
// For removed props, try to set default values, if possible
|
|
262
|
-
|
|
263
|
-
if (root.constructor) {
|
|
264
|
-
var _root$__r3f$props$arg, _root$__r3f;
|
|
265
|
-
|
|
698
|
+
if (root.constructor && root.constructor.length === 0) {
|
|
266
699
|
// create a blank slate of the instance and copy the particular parameter.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
700
|
+
let ctor = MEMOIZED_PROTOTYPES.get(root.constructor);
|
|
701
|
+
if (!ctor) {
|
|
702
|
+
ctor = new root.constructor();
|
|
703
|
+
MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
|
|
704
|
+
}
|
|
705
|
+
changedProps[key] = ctor[key];
|
|
272
706
|
} else {
|
|
273
707
|
// instance does not have constructor, just set it to 0
|
|
274
708
|
changedProps[key] = 0;
|
|
275
709
|
}
|
|
276
710
|
}
|
|
277
711
|
}
|
|
278
|
-
|
|
279
712
|
return changedProps;
|
|
280
|
-
}
|
|
281
|
-
|
|
713
|
+
}
|
|
714
|
+
const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production';
|
|
715
|
+
|
|
716
|
+
// const LinearEncoding = 3000
|
|
717
|
+
const sRGBEncoding = 3001;
|
|
718
|
+
const SRGBColorSpace = 'srgb';
|
|
719
|
+
const LinearSRGBColorSpace = 'srgb-linear';
|
|
720
|
+
|
|
721
|
+
// https://github.com/mrdoob/three.js/pull/27042
|
|
722
|
+
// https://github.com/mrdoob/three.js/pull/22748
|
|
723
|
+
const colorMaps = ['map', 'emissiveMap', 'sheenTintMap',
|
|
724
|
+
// <r134
|
|
725
|
+
'sheenColorMap', 'specularTintMap',
|
|
726
|
+
// <r134
|
|
727
|
+
'specularColorMap', 'envMap'];
|
|
728
|
+
const EVENT_REGEX = /^on(Pointer|Click|DoubleClick|ContextMenu|Wheel)/;
|
|
729
|
+
|
|
730
|
+
// This function applies a set of changes to the instance
|
|
282
731
|
function applyProps(object, props) {
|
|
283
732
|
const instance = object.__r3f;
|
|
284
|
-
const rootState = instance
|
|
733
|
+
const rootState = instance && findInitialRoot(instance).getState();
|
|
285
734
|
const prevHandlers = instance == null ? void 0 : instance.eventCount;
|
|
286
|
-
|
|
287
735
|
for (const prop in props) {
|
|
288
|
-
let value = props[prop];
|
|
736
|
+
let value = props[prop];
|
|
289
737
|
|
|
290
|
-
|
|
738
|
+
// Don't mutate reserved keys
|
|
739
|
+
if (RESERVED_PROPS.includes(prop)) continue;
|
|
291
740
|
|
|
292
|
-
|
|
741
|
+
// Deal with pointer events, including removing them if undefined
|
|
742
|
+
if (instance && EVENT_REGEX.test(prop)) {
|
|
293
743
|
if (typeof value === 'function') instance.handlers[prop] = value;else delete instance.handlers[prop];
|
|
294
744
|
instance.eventCount = Object.keys(instance.handlers).length;
|
|
295
745
|
}
|
|
296
746
|
|
|
297
|
-
|
|
747
|
+
// Ignore setting undefined props
|
|
748
|
+
// https://github.com/pmndrs/react-three-fiber/issues/274
|
|
749
|
+
if (value === undefined) continue;
|
|
750
|
+
let {
|
|
298
751
|
root,
|
|
299
752
|
key,
|
|
300
753
|
target
|
|
301
|
-
} = resolve(object, prop);
|
|
754
|
+
} = resolve(object, prop);
|
|
755
|
+
|
|
756
|
+
// Alias (output)encoding => (output)colorSpace (since r152)
|
|
757
|
+
// https://github.com/pmndrs/react-three-fiber/pull/2829
|
|
758
|
+
if (hasColorSpace(root)) {
|
|
759
|
+
if (key === 'encoding') {
|
|
760
|
+
key = 'colorSpace';
|
|
761
|
+
value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
|
|
762
|
+
} else if (key === 'outputEncoding') {
|
|
763
|
+
key = 'outputColorSpace';
|
|
764
|
+
value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
302
767
|
|
|
303
|
-
|
|
768
|
+
// Copy if properties match signatures
|
|
769
|
+
if (target != null && target.copy && (
|
|
770
|
+
// Some environments may break strict identity checks by duplicating versions of three.js.
|
|
771
|
+
// Loosen to unminified names, ignoring descendents.
|
|
772
|
+
// https://github.com/pmndrs/react-three-fiber/issues/2856
|
|
773
|
+
// TODO: fix upstream and remove in v9
|
|
774
|
+
__DEV__ ? target.constructor.name === value.constructor.name : target.constructor === value.constructor)) {
|
|
304
775
|
target.copy(value);
|
|
305
|
-
}
|
|
776
|
+
}
|
|
777
|
+
// Layers have no copy function, we must therefore copy the mask property
|
|
306
778
|
else if (target instanceof THREE.Layers && value instanceof THREE.Layers) {
|
|
307
779
|
target.mask = value.mask;
|
|
308
|
-
}
|
|
780
|
+
}
|
|
781
|
+
// Set array types
|
|
309
782
|
else if (target != null && target.set && Array.isArray(value)) {
|
|
310
783
|
if (target.fromArray) target.fromArray(value);else target.set(...value);
|
|
311
|
-
}
|
|
312
|
-
//
|
|
784
|
+
}
|
|
785
|
+
// Set literal types
|
|
313
786
|
else if (target != null && target.set && typeof value !== 'object') {
|
|
314
|
-
const isColor = target instanceof THREE.Color;
|
|
787
|
+
const isColor = target instanceof THREE.Color;
|
|
788
|
+
// Allow setting array scalars
|
|
789
|
+
if (!isColor && target.setScalar && typeof value === 'number') target.setScalar(value);
|
|
790
|
+
// Otherwise just set single value
|
|
791
|
+
else target.set(value);
|
|
315
792
|
|
|
316
|
-
|
|
317
|
-
else if (value !== undefined) target.set(value);
|
|
318
|
-
} // Else, just overwrite the value
|
|
319
|
-
else {
|
|
320
|
-
root[key] = value; // Auto-convert sRGB textures, for now ...
|
|
793
|
+
// Emulate THREE.ColorManagement for older three.js versions
|
|
321
794
|
// https://github.com/pmndrs/react-three-fiber/issues/344
|
|
795
|
+
if (!getColorManagement() && !(rootState != null && rootState.linear) && isColor) target.convertSRGBToLinear();
|
|
796
|
+
}
|
|
797
|
+
// Else, just overwrite the value
|
|
798
|
+
else {
|
|
799
|
+
root[key] = value;
|
|
322
800
|
|
|
323
|
-
|
|
324
|
-
|
|
801
|
+
// Auto-convert sRGB texture parameters for built-in materials
|
|
802
|
+
// https://github.com/pmndrs/react-three-fiber/issues/344
|
|
803
|
+
// https://github.com/mrdoob/three.js/pull/25857
|
|
804
|
+
if (rootState && !rootState.linear && colorMaps.includes(key) && root[key] instanceof THREE.Texture &&
|
|
805
|
+
// sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
|
|
806
|
+
root[key].format === THREE.RGBAFormat && root[key].type === THREE.UnsignedByteType) {
|
|
807
|
+
// NOTE: this cannot be set from the renderer (e.g. sRGB source textures rendered to P3)
|
|
808
|
+
if (hasColorSpace(root[key])) root[key].colorSpace = 'srgb';else root[key].encoding = sRGBEncoding;
|
|
325
809
|
}
|
|
326
810
|
}
|
|
327
811
|
}
|
|
328
812
|
|
|
813
|
+
// Register event handlers
|
|
329
814
|
if (instance != null && instance.parent && rootState != null && rootState.internal && instance.object instanceof THREE.Object3D && prevHandlers !== instance.eventCount) {
|
|
330
815
|
// Pre-emptively remove the instance from the interaction manager
|
|
331
816
|
const index = rootState.internal.interaction.indexOf(instance.object);
|
|
332
|
-
if (index > -1) rootState.internal.interaction.splice(index, 1);
|
|
333
|
-
|
|
817
|
+
if (index > -1) rootState.internal.interaction.splice(index, 1);
|
|
818
|
+
// Add the instance to the interaction manager only when it has handlers
|
|
334
819
|
if (instance.eventCount && instance.object.raycast !== null && instance.object instanceof THREE.Object3D) {
|
|
335
820
|
rootState.internal.interaction.push(instance.object);
|
|
336
821
|
}
|
|
337
822
|
}
|
|
338
823
|
|
|
824
|
+
// Auto-attach geometries and materials
|
|
825
|
+
if (instance && instance.props.attach === undefined) {
|
|
826
|
+
if (instance.object instanceof THREE.BufferGeometry) instance.props.attach = 'geometry';else if (instance.object instanceof THREE.Material) instance.props.attach = 'material';
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Instance was updated, request a frame
|
|
339
830
|
if (instance) invalidateInstance(instance);
|
|
340
831
|
return object;
|
|
341
832
|
}
|
|
342
833
|
function invalidateInstance(instance) {
|
|
343
834
|
var _instance$root;
|
|
344
|
-
|
|
345
835
|
const state = (_instance$root = instance.root) == null ? void 0 : _instance$root.getState == null ? void 0 : _instance$root.getState();
|
|
346
836
|
if (state && state.internal.frames === 0) state.invalidate();
|
|
347
837
|
}
|
|
348
838
|
function updateCamera(camera, size) {
|
|
349
|
-
// https://github.com/pmndrs/react-three-fiber/issues/92
|
|
350
839
|
// Do not mess with the camera if it belongs to the user
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
camera.updateProjectionMatrix(); // https://github.com/pmndrs/react-three-fiber/issues/178
|
|
362
|
-
// Update matrix world since the renderer is a frame late
|
|
363
|
-
|
|
364
|
-
camera.updateMatrixWorld();
|
|
840
|
+
// https://github.com/pmndrs/react-three-fiber/issues/92
|
|
841
|
+
if (camera.manual) return;
|
|
842
|
+
if (isOrthographicCamera(camera)) {
|
|
843
|
+
camera.left = size.width / -2;
|
|
844
|
+
camera.right = size.width / 2;
|
|
845
|
+
camera.top = size.height / 2;
|
|
846
|
+
camera.bottom = size.height / -2;
|
|
847
|
+
} else {
|
|
848
|
+
camera.aspect = size.width / size.height;
|
|
365
849
|
}
|
|
850
|
+
camera.updateProjectionMatrix();
|
|
366
851
|
}
|
|
367
|
-
|
|
368
|
-
// Keys that shouldn't be copied between R3F stores
|
|
369
|
-
const privateKeys = ['set', 'get', 'setSize', 'setFrameloop', 'setDpr', 'events', 'invalidate', 'advance', 'size', 'viewport'];
|
|
370
|
-
const isRenderer = def => !!(def != null && def.render);
|
|
371
|
-
const context = /*#__PURE__*/React.createContext(null);
|
|
372
|
-
|
|
373
|
-
const createStore = (invalidate, advance) => {
|
|
374
|
-
const rootStore = create((set, get) => {
|
|
375
|
-
const position = new THREE.Vector3();
|
|
376
|
-
const defaultTarget = new THREE.Vector3();
|
|
377
|
-
const tempTarget = new THREE.Vector3();
|
|
378
|
-
|
|
379
|
-
function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
|
|
380
|
-
const {
|
|
381
|
-
width,
|
|
382
|
-
height,
|
|
383
|
-
top,
|
|
384
|
-
left
|
|
385
|
-
} = size;
|
|
386
|
-
const aspect = width / height;
|
|
387
|
-
if (target instanceof THREE.Vector3) tempTarget.copy(target);else tempTarget.set(...target);
|
|
388
|
-
const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
|
|
389
|
-
|
|
390
|
-
if (isOrthographicCamera(camera)) {
|
|
391
|
-
return {
|
|
392
|
-
width: width / camera.zoom,
|
|
393
|
-
height: height / camera.zoom,
|
|
394
|
-
top,
|
|
395
|
-
left,
|
|
396
|
-
factor: 1,
|
|
397
|
-
distance,
|
|
398
|
-
aspect
|
|
399
|
-
};
|
|
400
|
-
} else {
|
|
401
|
-
const fov = camera.fov * Math.PI / 180; // convert vertical fov to radians
|
|
402
|
-
|
|
403
|
-
const h = 2 * Math.tan(fov / 2) * distance; // visible height
|
|
404
|
-
|
|
405
|
-
const w = h * (width / height);
|
|
406
|
-
return {
|
|
407
|
-
width: w,
|
|
408
|
-
height: h,
|
|
409
|
-
top,
|
|
410
|
-
left,
|
|
411
|
-
factor: width / w,
|
|
412
|
-
distance,
|
|
413
|
-
aspect
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
let performanceTimeout = undefined;
|
|
419
|
-
|
|
420
|
-
const setPerformanceCurrent = current => set(state => ({
|
|
421
|
-
performance: { ...state.performance,
|
|
422
|
-
current
|
|
423
|
-
}
|
|
424
|
-
}));
|
|
425
|
-
|
|
426
|
-
const pointer = new THREE.Vector2();
|
|
427
|
-
const rootState = {
|
|
428
|
-
set,
|
|
429
|
-
get,
|
|
430
|
-
// Mock objects that have to be configured
|
|
431
|
-
gl: null,
|
|
432
|
-
camera: null,
|
|
433
|
-
raycaster: null,
|
|
434
|
-
events: {
|
|
435
|
-
priority: 1,
|
|
436
|
-
enabled: true,
|
|
437
|
-
connected: false
|
|
438
|
-
},
|
|
439
|
-
xr: null,
|
|
440
|
-
invalidate: (frames = 1) => invalidate(get(), frames),
|
|
441
|
-
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
|
|
442
|
-
legacy: false,
|
|
443
|
-
linear: false,
|
|
444
|
-
flat: false,
|
|
445
|
-
scene: new THREE.Scene(),
|
|
446
|
-
controls: null,
|
|
447
|
-
clock: new THREE.Clock(),
|
|
448
|
-
pointer,
|
|
449
|
-
mouse: pointer,
|
|
450
|
-
frameloop: 'always',
|
|
451
|
-
onPointerMissed: undefined,
|
|
452
|
-
performance: {
|
|
453
|
-
current: 1,
|
|
454
|
-
min: 0.5,
|
|
455
|
-
max: 1,
|
|
456
|
-
debounce: 200,
|
|
457
|
-
regress: () => {
|
|
458
|
-
const state = get(); // Clear timeout
|
|
459
|
-
|
|
460
|
-
if (performanceTimeout) clearTimeout(performanceTimeout); // Set lower bound performance
|
|
461
|
-
|
|
462
|
-
if (state.performance.current !== state.performance.min) setPerformanceCurrent(state.performance.min); // Go back to upper bound performance after a while unless something regresses meanwhile
|
|
463
|
-
|
|
464
|
-
performanceTimeout = setTimeout(() => setPerformanceCurrent(get().performance.max), state.performance.debounce);
|
|
465
|
-
}
|
|
466
|
-
},
|
|
467
|
-
size: {
|
|
468
|
-
width: 0,
|
|
469
|
-
height: 0,
|
|
470
|
-
top: 0,
|
|
471
|
-
left: 0
|
|
472
|
-
},
|
|
473
|
-
viewport: {
|
|
474
|
-
initialDpr: 0,
|
|
475
|
-
dpr: 0,
|
|
476
|
-
width: 0,
|
|
477
|
-
height: 0,
|
|
478
|
-
top: 0,
|
|
479
|
-
left: 0,
|
|
480
|
-
aspect: 0,
|
|
481
|
-
distance: 0,
|
|
482
|
-
factor: 0,
|
|
483
|
-
getCurrentViewport
|
|
484
|
-
},
|
|
485
|
-
setEvents: events => set(state => ({ ...state,
|
|
486
|
-
events: { ...state.events,
|
|
487
|
-
...events
|
|
488
|
-
}
|
|
489
|
-
})),
|
|
490
|
-
setSize: (width, height, top = 0, left = 0) => {
|
|
491
|
-
const camera = get().camera;
|
|
492
|
-
const size = {
|
|
493
|
-
width,
|
|
494
|
-
height,
|
|
495
|
-
top,
|
|
496
|
-
left
|
|
497
|
-
};
|
|
498
|
-
set(state => ({
|
|
499
|
-
size,
|
|
500
|
-
viewport: { ...state.viewport,
|
|
501
|
-
...getCurrentViewport(camera, defaultTarget, size)
|
|
502
|
-
}
|
|
503
|
-
}));
|
|
504
|
-
},
|
|
505
|
-
setDpr: dpr => set(state => {
|
|
506
|
-
const resolved = calculateDpr(dpr);
|
|
507
|
-
return {
|
|
508
|
-
viewport: { ...state.viewport,
|
|
509
|
-
dpr: resolved,
|
|
510
|
-
initialDpr: state.viewport.initialDpr || resolved
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
|
-
}),
|
|
514
|
-
setFrameloop: frameloop => {
|
|
515
|
-
var _frameloop$mode, _frameloop$render, _frameloop$maxDelta;
|
|
516
|
-
|
|
517
|
-
const state = get();
|
|
518
|
-
const mode = typeof frameloop === 'string' ? frameloop : (frameloop == null ? void 0 : frameloop.mode) === 'auto' ? 'always' : (_frameloop$mode = frameloop == null ? void 0 : frameloop.mode) != null ? _frameloop$mode : state.frameloop;
|
|
519
|
-
const render = typeof frameloop === 'string' ? state.internal.render : (_frameloop$render = frameloop == null ? void 0 : frameloop.render) != null ? _frameloop$render : state.internal.render;
|
|
520
|
-
const maxDelta = typeof frameloop === 'string' ? state.internal.maxDelta : (_frameloop$maxDelta = frameloop == null ? void 0 : frameloop.maxDelta) != null ? _frameloop$maxDelta : state.internal.maxDelta;
|
|
521
|
-
const clock = state.clock; // if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
|
|
522
|
-
|
|
523
|
-
clock.stop();
|
|
524
|
-
clock.elapsedTime = 0;
|
|
525
|
-
|
|
526
|
-
if (frameloop !== 'never') {
|
|
527
|
-
clock.start();
|
|
528
|
-
clock.elapsedTime = 0;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
set(() => ({
|
|
532
|
-
frameloop: mode,
|
|
533
|
-
internal: { ...state.internal,
|
|
534
|
-
render,
|
|
535
|
-
maxDelta
|
|
536
|
-
}
|
|
537
|
-
}));
|
|
538
|
-
},
|
|
539
|
-
previousRoot: undefined,
|
|
540
|
-
internal: {
|
|
541
|
-
// Events
|
|
542
|
-
interaction: [],
|
|
543
|
-
hovered: new Map(),
|
|
544
|
-
subscribers: [],
|
|
545
|
-
initialClick: [0, 0],
|
|
546
|
-
initialHits: [],
|
|
547
|
-
capturedMap: new Map(),
|
|
548
|
-
lastEvent: /*#__PURE__*/React.createRef(),
|
|
549
|
-
// Updates
|
|
550
|
-
active: false,
|
|
551
|
-
frames: 0,
|
|
552
|
-
stages: [],
|
|
553
|
-
render: 'auto',
|
|
554
|
-
maxDelta: 1 / 10,
|
|
555
|
-
priority: 0,
|
|
556
|
-
subscribe: (ref, priority, store) => {
|
|
557
|
-
const state = get();
|
|
558
|
-
const internal = state.internal; // If this subscription was given a priority, it takes rendering into its own hands
|
|
559
|
-
// For that reason we switch off automatic rendering and increase the manual flag
|
|
560
|
-
// As long as this flag is positive there can be no internal rendering at all
|
|
561
|
-
// because there could be multiple render subscriptions
|
|
562
|
-
|
|
563
|
-
internal.priority = internal.priority + (priority > 0 ? 1 : 0); // We use the render flag and deprecate priority
|
|
564
|
-
|
|
565
|
-
if (internal.priority && state.internal.render === 'auto') set(() => ({
|
|
566
|
-
internal: { ...state.internal,
|
|
567
|
-
render: 'manual'
|
|
568
|
-
}
|
|
569
|
-
}));
|
|
570
|
-
internal.subscribers.push({
|
|
571
|
-
ref,
|
|
572
|
-
priority,
|
|
573
|
-
store
|
|
574
|
-
}); // Register subscriber and sort layers from lowest to highest, meaning,
|
|
575
|
-
// highest priority renders last (on top of the other frames)
|
|
576
|
-
|
|
577
|
-
internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
|
|
578
|
-
return () => {
|
|
579
|
-
const state = get();
|
|
580
|
-
const internal = state.internal;
|
|
581
|
-
|
|
582
|
-
if (internal != null && internal.subscribers) {
|
|
583
|
-
// Decrease manual flag if this subscription had a priority
|
|
584
|
-
internal.priority = internal.priority - (priority > 0 ? 1 : 0); // We use the render flag and deprecate priority
|
|
585
|
-
|
|
586
|
-
if (!internal.priority && state.internal.render === 'manual') set(() => ({
|
|
587
|
-
internal: { ...state.internal,
|
|
588
|
-
render: 'auto'
|
|
589
|
-
}
|
|
590
|
-
})); // Remove subscriber from list
|
|
591
|
-
|
|
592
|
-
internal.subscribers = internal.subscribers.filter(s => s.ref !== ref);
|
|
593
|
-
}
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
};
|
|
598
|
-
return rootState;
|
|
599
|
-
});
|
|
600
|
-
const state = rootStore.getState();
|
|
601
|
-
prepare(state.scene, rootStore, '', {});
|
|
602
|
-
let oldSize = state.size;
|
|
603
|
-
let oldDpr = state.viewport.dpr;
|
|
604
|
-
let oldCamera = state.camera;
|
|
605
|
-
rootStore.subscribe(() => {
|
|
606
|
-
const {
|
|
607
|
-
camera,
|
|
608
|
-
size,
|
|
609
|
-
viewport,
|
|
610
|
-
gl,
|
|
611
|
-
set
|
|
612
|
-
} = rootStore.getState(); // Resize camera and renderer on changes to size and pixelratio
|
|
613
|
-
|
|
614
|
-
if (size !== oldSize || viewport.dpr !== oldDpr) {
|
|
615
|
-
oldSize = size;
|
|
616
|
-
oldDpr = viewport.dpr; // Update camera & renderer
|
|
617
|
-
|
|
618
|
-
updateCamera(camera, size);
|
|
619
|
-
gl.setPixelRatio(viewport.dpr); // Play nice with offscreen canvas contexts
|
|
620
|
-
|
|
621
|
-
const updateStyle = gl.domElement instanceof HTMLCanvasElement;
|
|
622
|
-
gl.setSize(size.width, size.height, updateStyle);
|
|
623
|
-
} // Update viewport once the camera changes
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if (camera !== oldCamera) {
|
|
627
|
-
oldCamera = camera; // Update viewport
|
|
628
|
-
|
|
629
|
-
set(state => ({
|
|
630
|
-
viewport: { ...state.viewport,
|
|
631
|
-
...state.viewport.getCurrentViewport(camera)
|
|
632
|
-
}
|
|
633
|
-
}));
|
|
634
|
-
}
|
|
635
|
-
}); // Invalidate on any change
|
|
636
|
-
|
|
637
|
-
rootStore.subscribe(state => invalidate(state)); // Return root state
|
|
638
|
-
|
|
639
|
-
return rootStore;
|
|
640
|
-
};
|
|
852
|
+
const isObject3D = object => object == null ? void 0 : object.isObject3D;
|
|
641
853
|
|
|
642
854
|
function makeId(event) {
|
|
643
855
|
return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
|
|
644
|
-
} // https://github.com/facebook/react/tree/main/packages/react-reconciler#getcurrenteventpriority
|
|
645
|
-
// Gives React a clue as to how import the current interaction is
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
function getEventPriority() {
|
|
649
|
-
var _globalScope$event;
|
|
650
|
-
|
|
651
|
-
// Get a handle to the current global scope in window and worker contexts if able
|
|
652
|
-
// https://github.com/pmndrs/react-three-fiber/pull/2493
|
|
653
|
-
const globalScope = typeof self !== 'undefined' && self || typeof window !== 'undefined' && window;
|
|
654
|
-
if (!globalScope) return DefaultEventPriority;
|
|
655
|
-
const name = (_globalScope$event = globalScope.event) == null ? void 0 : _globalScope$event.type;
|
|
656
|
-
|
|
657
|
-
switch (name) {
|
|
658
|
-
case 'click':
|
|
659
|
-
case 'contextmenu':
|
|
660
|
-
case 'dblclick':
|
|
661
|
-
case 'pointercancel':
|
|
662
|
-
case 'pointerdown':
|
|
663
|
-
case 'pointerup':
|
|
664
|
-
return DiscreteEventPriority;
|
|
665
|
-
|
|
666
|
-
case 'pointermove':
|
|
667
|
-
case 'pointerout':
|
|
668
|
-
case 'pointerover':
|
|
669
|
-
case 'pointerenter':
|
|
670
|
-
case 'pointerleave':
|
|
671
|
-
case 'wheel':
|
|
672
|
-
return ContinuousEventPriority;
|
|
673
|
-
|
|
674
|
-
default:
|
|
675
|
-
return DefaultEventPriority;
|
|
676
|
-
}
|
|
677
856
|
}
|
|
678
|
-
/**
|
|
679
|
-
* Release pointer captures.
|
|
680
|
-
* This is called by releasePointerCapture in the API, and when an object is removed.
|
|
681
|
-
*/
|
|
682
857
|
|
|
858
|
+
/**
|
|
859
|
+
* Release pointer captures.
|
|
860
|
+
* This is called by releasePointerCapture in the API, and when an object is removed.
|
|
861
|
+
*/
|
|
683
862
|
function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
|
|
684
863
|
const captureData = captures.get(obj);
|
|
685
|
-
|
|
686
864
|
if (captureData) {
|
|
687
|
-
captures.delete(obj);
|
|
688
|
-
|
|
865
|
+
captures.delete(obj);
|
|
866
|
+
// If this was the last capturing object for this pointer
|
|
689
867
|
if (captures.size === 0) {
|
|
690
868
|
capturedMap.delete(pointerId);
|
|
691
869
|
captureData.target.releasePointerCapture(pointerId);
|
|
692
870
|
}
|
|
693
871
|
}
|
|
694
872
|
}
|
|
695
|
-
|
|
696
873
|
function removeInteractivity(store, object) {
|
|
697
874
|
const {
|
|
698
875
|
internal
|
|
699
|
-
} = store.getState();
|
|
700
|
-
|
|
876
|
+
} = store.getState();
|
|
877
|
+
// Removes every trace of an object from the data store
|
|
701
878
|
internal.interaction = internal.interaction.filter(o => o !== object);
|
|
702
879
|
internal.initialHits = internal.initialHits.filter(o => o !== object);
|
|
703
880
|
internal.hovered.forEach((value, key) => {
|
|
@@ -720,106 +897,103 @@ function createEvents(store) {
|
|
|
720
897
|
const dy = event.offsetY - internal.initialClick[1];
|
|
721
898
|
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
722
899
|
}
|
|
723
|
-
/** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
|
|
724
|
-
|
|
725
900
|
|
|
901
|
+
/** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
|
|
726
902
|
function filterPointerEvents(objects) {
|
|
727
903
|
return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
|
|
728
904
|
var _r3f;
|
|
729
|
-
|
|
730
905
|
return (_r3f = obj.__r3f) == null ? void 0 : _r3f.handlers['onPointer' + name];
|
|
731
906
|
}));
|
|
732
907
|
}
|
|
733
|
-
|
|
734
908
|
function intersect(event, filter) {
|
|
735
909
|
const state = store.getState();
|
|
736
910
|
const duplicates = new Set();
|
|
737
|
-
const intersections = [];
|
|
738
|
-
|
|
739
|
-
const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
|
|
740
|
-
|
|
741
|
-
eventsObjects.
|
|
742
|
-
const state = getRootState(
|
|
743
|
-
|
|
911
|
+
const intersections = [];
|
|
912
|
+
// Allow callers to eliminate event objects
|
|
913
|
+
const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
|
|
914
|
+
// Reset all raycaster cameras to undefined
|
|
915
|
+
for (let i = 0; i < eventsObjects.length; i++) {
|
|
916
|
+
const state = getRootState(eventsObjects[i]);
|
|
744
917
|
if (state) {
|
|
745
918
|
state.raycaster.camera = undefined;
|
|
746
919
|
}
|
|
747
|
-
}
|
|
748
|
-
|
|
920
|
+
}
|
|
749
921
|
if (!state.previousRoot) {
|
|
750
922
|
// Make sure root-level pointer and ray are set up
|
|
751
923
|
state.events.compute == null ? void 0 : state.events.compute(event, state);
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
const state = getRootState(obj); // Skip event handling when noEvents is set, or when the raycasters camera is null
|
|
758
|
-
|
|
759
|
-
if (!state || !state.events.enabled || state.raycaster.camera === null) return []; // When the camera is undefined we have to call the event layers update function
|
|
924
|
+
}
|
|
925
|
+
function handleRaycast(obj) {
|
|
926
|
+
const state = getRootState(obj);
|
|
927
|
+
// Skip event handling when noEvents is set, or when the raycasters camera is null
|
|
928
|
+
if (!state || !state.events.enabled || state.raycaster.camera === null) return [];
|
|
760
929
|
|
|
930
|
+
// When the camera is undefined we have to call the event layers update function
|
|
761
931
|
if (state.raycaster.camera === undefined) {
|
|
762
932
|
var _state$previousRoot;
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
933
|
+
state.events.compute == null ? void 0 : state.events.compute(event, state, (_state$previousRoot = state.previousRoot) == null ? void 0 : _state$previousRoot.getState());
|
|
934
|
+
// If the camera is still undefined we have to skip this layer entirely
|
|
766
935
|
if (state.raycaster.camera === undefined) state.raycaster.camera = null;
|
|
767
|
-
}
|
|
768
|
-
|
|
936
|
+
}
|
|
769
937
|
|
|
938
|
+
// Intersect object by object
|
|
770
939
|
return state.raycaster.camera ? state.raycaster.intersectObject(obj, true) : [];
|
|
771
|
-
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Collect events
|
|
943
|
+
let hits = eventsObjects
|
|
944
|
+
// Intersect objects
|
|
945
|
+
.flatMap(handleRaycast)
|
|
946
|
+
// Sort by event priority and distance
|
|
772
947
|
.sort((a, b) => {
|
|
773
948
|
const aState = getRootState(a.object);
|
|
774
949
|
const bState = getRootState(b.object);
|
|
775
|
-
if (!aState || !bState) return
|
|
950
|
+
if (!aState || !bState) return a.distance - b.distance;
|
|
776
951
|
return bState.events.priority - aState.events.priority || a.distance - b.distance;
|
|
777
|
-
})
|
|
952
|
+
})
|
|
953
|
+
// Filter out duplicates
|
|
778
954
|
.filter(item => {
|
|
779
955
|
const id = makeId(item);
|
|
780
956
|
if (duplicates.has(id)) return false;
|
|
781
957
|
duplicates.add(id);
|
|
782
958
|
return true;
|
|
783
|
-
});
|
|
784
|
-
// Allow custom userland intersect sort order, this likely only makes sense on the root filter
|
|
959
|
+
});
|
|
785
960
|
|
|
786
|
-
|
|
961
|
+
// https://github.com/mrdoob/three.js/issues/16031
|
|
962
|
+
// Allow custom userland intersect sort order, this likely only makes sense on the root filter
|
|
963
|
+
if (state.events.filter) hits = state.events.filter(hits, state);
|
|
787
964
|
|
|
965
|
+
// Bubble up the events, find the event source (eventObject)
|
|
788
966
|
for (const hit of hits) {
|
|
789
|
-
let eventObject = hit.object;
|
|
790
|
-
|
|
967
|
+
let eventObject = hit.object;
|
|
968
|
+
// Bubble event up
|
|
791
969
|
while (eventObject) {
|
|
792
970
|
var _r3f2;
|
|
793
|
-
|
|
794
|
-
|
|
971
|
+
if ((_r3f2 = eventObject.__r3f) != null && _r3f2.eventCount) intersections.push({
|
|
972
|
+
...hit,
|
|
795
973
|
eventObject
|
|
796
974
|
});
|
|
797
975
|
eventObject = eventObject.parent;
|
|
798
976
|
}
|
|
799
|
-
}
|
|
800
|
-
|
|
977
|
+
}
|
|
801
978
|
|
|
979
|
+
// If the interaction is captured, make all capturing targets part of the intersect.
|
|
802
980
|
if ('pointerId' in event && state.internal.capturedMap.has(event.pointerId)) {
|
|
803
981
|
for (let captureData of state.internal.capturedMap.get(event.pointerId).values()) {
|
|
804
|
-
intersections.push(captureData.intersection);
|
|
982
|
+
if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
|
|
805
983
|
}
|
|
806
984
|
}
|
|
807
|
-
|
|
808
985
|
return intersections;
|
|
809
986
|
}
|
|
810
|
-
/** Handles intersections by forwarding them to handlers */
|
|
811
|
-
|
|
812
987
|
|
|
988
|
+
/** Handles intersections by forwarding them to handlers */
|
|
813
989
|
function handleIntersects(intersections, event, delta, callback) {
|
|
814
990
|
// If anything has been found, forward it to the event listeners
|
|
815
991
|
if (intersections.length) {
|
|
816
992
|
const localState = {
|
|
817
993
|
stopped: false
|
|
818
994
|
};
|
|
819
|
-
|
|
820
995
|
for (const hit of intersections) {
|
|
821
996
|
const state = getRootState(hit.object);
|
|
822
|
-
|
|
823
997
|
if (state) {
|
|
824
998
|
const {
|
|
825
999
|
raycaster,
|
|
@@ -828,19 +1002,15 @@ function createEvents(store) {
|
|
|
828
1002
|
internal
|
|
829
1003
|
} = state;
|
|
830
1004
|
const unprojectedPoint = new THREE.Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
831
|
-
|
|
832
1005
|
const hasPointerCapture = id => {
|
|
833
1006
|
var _internal$capturedMap, _internal$capturedMap2;
|
|
834
|
-
|
|
835
1007
|
return (_internal$capturedMap = (_internal$capturedMap2 = internal.capturedMap.get(id)) == null ? void 0 : _internal$capturedMap2.has(hit.eventObject)) != null ? _internal$capturedMap : false;
|
|
836
1008
|
};
|
|
837
|
-
|
|
838
1009
|
const setPointerCapture = id => {
|
|
839
1010
|
const captureData = {
|
|
840
1011
|
intersection: hit,
|
|
841
1012
|
target: event.target
|
|
842
1013
|
};
|
|
843
|
-
|
|
844
1014
|
if (internal.capturedMap.has(id)) {
|
|
845
1015
|
// if the pointerId was previously captured, we add the hit to the
|
|
846
1016
|
// event capturedMap.
|
|
@@ -850,29 +1020,27 @@ function createEvents(store) {
|
|
|
850
1020
|
// containing the hitObject, and the hit. hitObject is used for
|
|
851
1021
|
// faster access.
|
|
852
1022
|
internal.capturedMap.set(id, new Map([[hit.eventObject, captureData]]));
|
|
853
|
-
}
|
|
1023
|
+
}
|
|
854
1024
|
event.target.setPointerCapture(id);
|
|
855
1025
|
};
|
|
856
|
-
|
|
857
1026
|
const releasePointerCapture = id => {
|
|
858
1027
|
const captures = internal.capturedMap.get(id);
|
|
859
|
-
|
|
860
1028
|
if (captures) {
|
|
861
1029
|
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
862
1030
|
}
|
|
863
|
-
};
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
let extractEventProps = {}; // 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.
|
|
1031
|
+
};
|
|
867
1032
|
|
|
1033
|
+
// Add native event props
|
|
1034
|
+
let extractEventProps = {};
|
|
1035
|
+
// 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.
|
|
868
1036
|
for (let prop in event) {
|
|
869
|
-
let property = event[prop];
|
|
1037
|
+
let property = event[prop];
|
|
1038
|
+
// Only copy over atomics, leave functions alone as these should be
|
|
870
1039
|
// called as event.nativeEvent.fn()
|
|
871
|
-
|
|
872
1040
|
if (typeof property !== 'function') extractEventProps[prop] = property;
|
|
873
1041
|
}
|
|
874
|
-
|
|
875
|
-
|
|
1042
|
+
let raycastEvent = {
|
|
1043
|
+
...hit,
|
|
876
1044
|
...extractEventProps,
|
|
877
1045
|
pointer,
|
|
878
1046
|
intersections,
|
|
@@ -882,17 +1050,20 @@ function createEvents(store) {
|
|
|
882
1050
|
ray: raycaster.ray,
|
|
883
1051
|
camera: camera,
|
|
884
1052
|
// Hijack stopPropagation, which just sets a flag
|
|
885
|
-
stopPropagation
|
|
1053
|
+
stopPropagation() {
|
|
886
1054
|
// https://github.com/pmndrs/react-three-fiber/issues/596
|
|
887
1055
|
// Events are not allowed to stop propagation if the pointer has been captured
|
|
888
|
-
const capturesForPointer = 'pointerId' in event && internal.capturedMap.get(event.pointerId);
|
|
1056
|
+
const capturesForPointer = 'pointerId' in event && internal.capturedMap.get(event.pointerId);
|
|
889
1057
|
|
|
890
|
-
|
|
891
|
-
|
|
1058
|
+
// We only authorize stopPropagation...
|
|
1059
|
+
if (
|
|
1060
|
+
// ...if this pointer hasn't been captured
|
|
1061
|
+
!capturesForPointer ||
|
|
1062
|
+
// ... or if the hit object is capturing the pointer
|
|
892
1063
|
capturesForPointer.has(hit.eventObject)) {
|
|
893
|
-
raycastEvent.stopped = localState.stopped = true;
|
|
1064
|
+
raycastEvent.stopped = localState.stopped = true;
|
|
1065
|
+
// Propagation is stopped, remove all other hover records
|
|
894
1066
|
// An event handler is only allowed to flush other handlers if it is hovered itself
|
|
895
|
-
|
|
896
1067
|
if (internal.hovered.size && Array.from(internal.hovered.values()).find(i => i.eventObject === hit.eventObject)) {
|
|
897
1068
|
// Objects cannot flush out higher up objects that have already caught the event
|
|
898
1069
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
@@ -912,111 +1083,136 @@ function createEvents(store) {
|
|
|
912
1083
|
releasePointerCapture
|
|
913
1084
|
},
|
|
914
1085
|
nativeEvent: event
|
|
915
|
-
};
|
|
916
|
-
|
|
917
|
-
callback(raycastEvent); // Event bubbling may be interrupted by stopPropagation
|
|
1086
|
+
};
|
|
918
1087
|
|
|
1088
|
+
// Call subscribers
|
|
1089
|
+
callback(raycastEvent);
|
|
1090
|
+
// Event bubbling may be interrupted by stopPropagation
|
|
919
1091
|
if (localState.stopped === true) break;
|
|
920
1092
|
}
|
|
921
1093
|
}
|
|
922
1094
|
}
|
|
923
|
-
|
|
924
1095
|
return intersections;
|
|
925
1096
|
}
|
|
926
|
-
|
|
927
1097
|
function cancelPointer(intersections) {
|
|
928
1098
|
const {
|
|
929
1099
|
internal
|
|
930
1100
|
} = store.getState();
|
|
931
|
-
|
|
1101
|
+
for (const hoveredObj of internal.hovered.values()) {
|
|
932
1102
|
// When no objects were hit or the the hovered object wasn't found underneath the cursor
|
|
933
1103
|
// we call onPointerOut and delete the object from the hovered-elements map
|
|
934
1104
|
if (!intersections.length || !intersections.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId)) {
|
|
935
1105
|
const eventObject = hoveredObj.eventObject;
|
|
936
1106
|
const instance = eventObject.__r3f;
|
|
937
1107
|
internal.hovered.delete(makeId(hoveredObj));
|
|
938
|
-
|
|
939
1108
|
if (instance != null && instance.eventCount) {
|
|
940
|
-
const handlers = instance.handlers;
|
|
941
|
-
|
|
942
|
-
const data = {
|
|
1109
|
+
const handlers = instance.handlers;
|
|
1110
|
+
// Clear out intersects, they are outdated by now
|
|
1111
|
+
const data = {
|
|
1112
|
+
...hoveredObj,
|
|
943
1113
|
intersections
|
|
944
1114
|
};
|
|
945
1115
|
handlers.onPointerOut == null ? void 0 : handlers.onPointerOut(data);
|
|
946
1116
|
handlers.onPointerLeave == null ? void 0 : handlers.onPointerLeave(data);
|
|
947
1117
|
}
|
|
948
1118
|
}
|
|
949
|
-
}
|
|
1119
|
+
}
|
|
950
1120
|
}
|
|
951
|
-
|
|
952
|
-
|
|
1121
|
+
function pointerMissed(event, objects) {
|
|
1122
|
+
for (let i = 0; i < objects.length; i++) {
|
|
1123
|
+
const instance = objects[i].__r3f;
|
|
1124
|
+
instance == null ? void 0 : instance.handlers.onPointerMissed == null ? void 0 : instance.handlers.onPointerMissed(event);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
function handlePointer(name) {
|
|
953
1128
|
// Deal with cancelation
|
|
954
1129
|
switch (name) {
|
|
955
1130
|
case 'onPointerLeave':
|
|
956
1131
|
case 'onPointerCancel':
|
|
957
1132
|
return () => cancelPointer([]);
|
|
958
|
-
|
|
959
1133
|
case 'onLostPointerCapture':
|
|
960
1134
|
return event => {
|
|
961
1135
|
const {
|
|
962
1136
|
internal
|
|
963
1137
|
} = store.getState();
|
|
964
|
-
|
|
965
|
-
if ('pointerId' in event && !internal.capturedMap.has(event.pointerId)) {
|
|
1138
|
+
if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) {
|
|
966
1139
|
// If the object event interface had onLostPointerCapture, we'd call it here on every
|
|
967
|
-
// object that's getting removed.
|
|
968
|
-
|
|
969
|
-
|
|
1140
|
+
// object that's getting removed. We call it on the next frame because onLostPointerCapture
|
|
1141
|
+
// fires before onPointerUp. Otherwise pointerUp would never be called if the event didn't
|
|
1142
|
+
// happen in the object it originated from, leaving components in a in-between state.
|
|
1143
|
+
requestAnimationFrame(() => {
|
|
1144
|
+
// Only release if pointer-up didn't do it already
|
|
1145
|
+
if (internal.capturedMap.has(event.pointerId)) {
|
|
1146
|
+
internal.capturedMap.delete(event.pointerId);
|
|
1147
|
+
cancelPointer([]);
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
970
1150
|
}
|
|
971
1151
|
};
|
|
972
|
-
}
|
|
973
|
-
|
|
1152
|
+
}
|
|
974
1153
|
|
|
975
|
-
|
|
1154
|
+
// Any other pointer goes here ...
|
|
1155
|
+
return function handleEvent(event) {
|
|
976
1156
|
const {
|
|
977
1157
|
onPointerMissed,
|
|
978
1158
|
internal
|
|
979
|
-
} = store.getState();
|
|
1159
|
+
} = store.getState();
|
|
980
1160
|
|
|
981
|
-
|
|
1161
|
+
// prepareRay(event)
|
|
1162
|
+
internal.lastEvent.current = event;
|
|
982
1163
|
|
|
1164
|
+
// Get fresh intersects
|
|
983
1165
|
const isPointerMove = name === 'onPointerMove';
|
|
984
1166
|
const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
|
|
985
|
-
const filter = isPointerMove ? filterPointerEvents : undefined;
|
|
986
|
-
|
|
1167
|
+
const filter = isPointerMove ? filterPointerEvents : undefined;
|
|
987
1168
|
const hits = intersect(event, filter);
|
|
988
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
1169
|
+
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
989
1170
|
|
|
1171
|
+
// Save initial coordinates on pointer-down
|
|
990
1172
|
if (name === 'onPointerDown') {
|
|
991
1173
|
internal.initialClick = [event.offsetX, event.offsetY];
|
|
992
1174
|
internal.initialHits = hits.map(hit => hit.eventObject);
|
|
993
|
-
}
|
|
994
|
-
// Missed events have to come first in order to establish user-land side-effect clean up
|
|
995
|
-
|
|
1175
|
+
}
|
|
996
1176
|
|
|
1177
|
+
// If a click yields no results, pass it back to the user as a miss
|
|
1178
|
+
// Missed events have to come first in order to establish user-land side-effect clean up
|
|
997
1179
|
if (isClickEvent && !hits.length) {
|
|
998
1180
|
if (delta <= 2) {
|
|
999
1181
|
pointerMissed(event, internal.interaction);
|
|
1000
1182
|
if (onPointerMissed) onPointerMissed(event);
|
|
1001
1183
|
}
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1184
|
+
}
|
|
1185
|
+
// Take care of unhover
|
|
1005
1186
|
if (isPointerMove) cancelPointer(hits);
|
|
1006
|
-
|
|
1187
|
+
function onIntersect(data) {
|
|
1007
1188
|
const eventObject = data.eventObject;
|
|
1008
|
-
const instance = eventObject.__r3f;
|
|
1189
|
+
const instance = eventObject.__r3f;
|
|
1009
1190
|
|
|
1191
|
+
// Check presence of handlers
|
|
1010
1192
|
if (!(instance != null && instance.eventCount)) return;
|
|
1011
1193
|
const handlers = instance.handlers;
|
|
1012
1194
|
|
|
1195
|
+
/*
|
|
1196
|
+
MAYBE TODO, DELETE IF NOT:
|
|
1197
|
+
Check if the object is captured, captured events should not have intersects running in parallel
|
|
1198
|
+
But wouldn't it be better to just replace capturedMap with a single entry?
|
|
1199
|
+
Also, are we OK with straight up making picking up multiple objects impossible?
|
|
1200
|
+
|
|
1201
|
+
const pointerId = (data as ThreeEvent<PointerEvent>).pointerId
|
|
1202
|
+
if (pointerId !== undefined) {
|
|
1203
|
+
const capturedMeshSet = internal.capturedMap.get(pointerId)
|
|
1204
|
+
if (capturedMeshSet) {
|
|
1205
|
+
const captured = capturedMeshSet.get(eventObject)
|
|
1206
|
+
if (captured && captured.localState.stopped) return
|
|
1207
|
+
}
|
|
1208
|
+
}*/
|
|
1209
|
+
|
|
1013
1210
|
if (isPointerMove) {
|
|
1014
1211
|
// Move event ...
|
|
1015
1212
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1016
1213
|
// When enter or out is present take care of hover-state
|
|
1017
1214
|
const id = makeId(data);
|
|
1018
1215
|
const hoveredItem = internal.hovered.get(id);
|
|
1019
|
-
|
|
1020
1216
|
if (!hoveredItem) {
|
|
1021
1217
|
// If the object wasn't previously hovered, book it and call its handler
|
|
1022
1218
|
internal.hovered.set(id, data);
|
|
@@ -1026,21 +1222,19 @@ function createEvents(store) {
|
|
|
1026
1222
|
// If the object was previously hovered and stopped, we shouldn't allow other items to proceed
|
|
1027
1223
|
data.stopPropagation();
|
|
1028
1224
|
}
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1225
|
+
}
|
|
1226
|
+
// Call mouse move
|
|
1032
1227
|
handlers.onPointerMove == null ? void 0 : handlers.onPointerMove(data);
|
|
1033
1228
|
} else {
|
|
1034
1229
|
// All other events ...
|
|
1035
1230
|
const handler = handlers[name];
|
|
1036
|
-
|
|
1037
1231
|
if (handler) {
|
|
1038
1232
|
// Forward all events back to their respective handlers with the exception of click events,
|
|
1039
1233
|
// which must use the initial target
|
|
1040
1234
|
if (!isClickEvent || internal.initialHits.includes(eventObject)) {
|
|
1041
1235
|
// Missed events have to come first
|
|
1042
|
-
pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
|
|
1043
|
-
|
|
1236
|
+
pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
|
|
1237
|
+
// Now call the handler
|
|
1044
1238
|
handler(data);
|
|
1045
1239
|
}
|
|
1046
1240
|
} else {
|
|
@@ -1050,489 +1244,317 @@ function createEvents(store) {
|
|
|
1050
1244
|
}
|
|
1051
1245
|
}
|
|
1052
1246
|
}
|
|
1053
|
-
}
|
|
1247
|
+
}
|
|
1248
|
+
handleIntersects(hits, event, delta, onIntersect);
|
|
1054
1249
|
};
|
|
1055
|
-
};
|
|
1056
|
-
|
|
1057
|
-
function pointerMissed(event, objects) {
|
|
1058
|
-
objects.forEach(object => {
|
|
1059
|
-
var _r3f3;
|
|
1060
|
-
|
|
1061
|
-
return (_r3f3 = object.__r3f) == null ? void 0 : _r3f3.handlers.onPointerMissed == null ? void 0 : _r3f3.handlers.onPointerMissed(event);
|
|
1062
|
-
});
|
|
1063
1250
|
}
|
|
1064
|
-
|
|
1065
1251
|
return {
|
|
1066
1252
|
handlePointer
|
|
1067
1253
|
};
|
|
1068
1254
|
}
|
|
1069
1255
|
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
if (child.props.attach) attach(parent, child);
|
|
1113
|
-
|
|
1114
|
-
for (const childInstance of child.children) handleContainerEffects(child, childInstance);
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
function appendChild(parent, child) {
|
|
1118
|
-
if (!child) return; // Link instances
|
|
1119
|
-
|
|
1120
|
-
child.parent = parent;
|
|
1121
|
-
parent.children.push(child); // Add Object3Ds if able
|
|
1122
|
-
|
|
1123
|
-
if (!child.props.attach && parent.object instanceof THREE.Object3D && child.object instanceof THREE.Object3D) {
|
|
1124
|
-
parent.object.add(child.object);
|
|
1125
|
-
} // Attach tree once complete
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
handleContainerEffects(parent, child); // Tree was updated, request a frame
|
|
1129
|
-
|
|
1130
|
-
invalidateInstance(child);
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
function insertBefore(parent, child, beforeChild, replace = false) {
|
|
1134
|
-
if (!child || !beforeChild) return; // Link instances
|
|
1135
|
-
|
|
1136
|
-
child.parent = parent;
|
|
1137
|
-
const childIndex = parent.children.indexOf(beforeChild);
|
|
1138
|
-
if (childIndex !== -1) parent.children.splice(childIndex, replace ? 1 : 0, child);
|
|
1139
|
-
if (replace) beforeChild.parent = null; // Manually splice Object3Ds
|
|
1140
|
-
|
|
1141
|
-
if (!child.props.attach && parent.object instanceof THREE.Object3D && child.object instanceof THREE.Object3D && beforeChild.object instanceof THREE.Object3D) {
|
|
1142
|
-
child.object.parent = parent.object;
|
|
1143
|
-
parent.object.children.splice(parent.object.children.indexOf(beforeChild.object), 0, child.object);
|
|
1144
|
-
child.object.dispatchEvent({
|
|
1145
|
-
type: 'added'
|
|
1146
|
-
});
|
|
1147
|
-
} // Attach tree once complete
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
handleContainerEffects(parent, child); // Tree was updated, request a frame
|
|
1151
|
-
|
|
1152
|
-
invalidateInstance(child);
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
function removeChild(parent, child, dispose, recursive) {
|
|
1156
|
-
if (!child) return; // Unlink instances
|
|
1157
|
-
|
|
1158
|
-
child.parent = null;
|
|
1159
|
-
|
|
1160
|
-
if (recursive === undefined) {
|
|
1161
|
-
const childIndex = parent.children.indexOf(child);
|
|
1162
|
-
if (childIndex !== -1) parent.children.splice(childIndex, 1);
|
|
1163
|
-
} // Eagerly tear down tree
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
if (child.props.attach) {
|
|
1167
|
-
detach(parent, child);
|
|
1168
|
-
} else if (child.object instanceof THREE.Object3D && parent.object instanceof THREE.Object3D) {
|
|
1169
|
-
parent.object.remove(child.object);
|
|
1170
|
-
removeInteractivity(child.root, child.object);
|
|
1171
|
-
} // Allow objects to bail out of unmount disposal with dispose={null}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
const shouldDispose = child.props.dispose !== null && dispose !== false; // Recursively remove instance children
|
|
1175
|
-
|
|
1176
|
-
if (recursive !== false) {
|
|
1177
|
-
for (const node of child.children) removeChild(child, node, shouldDispose, true);
|
|
1178
|
-
|
|
1179
|
-
child.children = [];
|
|
1180
|
-
} // Unlink instance object
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
delete child.object.__r3f; // Dispose object whenever the reconciler feels like it.
|
|
1184
|
-
// Never dispose of primitives because their state may be kept outside of React!
|
|
1185
|
-
// In order for an object to be able to dispose it
|
|
1186
|
-
// - has a dispose method
|
|
1187
|
-
// - cannot be a <primitive object={...} />
|
|
1188
|
-
// - cannot be a THREE.Scene, because three has broken its own API
|
|
1189
|
-
|
|
1190
|
-
if (shouldDispose && child.type !== 'primitive' && child.object.type !== 'Scene') {
|
|
1191
|
-
const dispose = child.object.dispose;
|
|
1192
|
-
|
|
1193
|
-
if (typeof dispose === 'function') {
|
|
1194
|
-
unstable_scheduleCallback(unstable_IdlePriority, () => {
|
|
1195
|
-
try {
|
|
1196
|
-
dispose();
|
|
1197
|
-
} catch (e) {
|
|
1198
|
-
/* ... */
|
|
1199
|
-
}
|
|
1200
|
-
});
|
|
1201
|
-
}
|
|
1202
|
-
} // Tree was updated, request a frame for top-level instance
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (dispose === undefined) invalidateInstance(child);
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
function switchInstance(oldInstance, type, props, fiber) {
|
|
1209
|
-
// Create a new instance
|
|
1210
|
-
const newInstance = createInstance(type, props, oldInstance.root); // Move children to new instance
|
|
1211
|
-
|
|
1212
|
-
for (const child of oldInstance.children) {
|
|
1213
|
-
removeChild(oldInstance, child, false, false);
|
|
1214
|
-
appendChild(newInstance, child);
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
oldInstance.children = []; // Link up new instance
|
|
1218
|
-
|
|
1219
|
-
const parent = oldInstance.parent;
|
|
1220
|
-
|
|
1221
|
-
if (parent) {
|
|
1222
|
-
insertBefore(parent, newInstance, oldInstance, true);
|
|
1223
|
-
} // This evil hack switches the react-internal fiber node
|
|
1224
|
-
[fiber, fiber.alternate].forEach(fiber => {
|
|
1225
|
-
if (fiber !== null) {
|
|
1226
|
-
fiber.stateNode = newInstance;
|
|
1227
|
-
|
|
1228
|
-
if (fiber.ref) {
|
|
1229
|
-
if (typeof fiber.ref === 'function') fiber.ref(newInstance.object);else fiber.ref.current = newInstance.object;
|
|
1256
|
+
const isRenderer = def => !!(def != null && def.render);
|
|
1257
|
+
const context = /*#__PURE__*/React.createContext(null);
|
|
1258
|
+
const createStore = (invalidate, advance) => {
|
|
1259
|
+
const rootStore = create((set, get) => {
|
|
1260
|
+
const position = new THREE.Vector3();
|
|
1261
|
+
const defaultTarget = new THREE.Vector3();
|
|
1262
|
+
const tempTarget = new THREE.Vector3();
|
|
1263
|
+
function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
|
|
1264
|
+
const {
|
|
1265
|
+
width,
|
|
1266
|
+
height,
|
|
1267
|
+
top,
|
|
1268
|
+
left
|
|
1269
|
+
} = size;
|
|
1270
|
+
const aspect = width / height;
|
|
1271
|
+
if (target instanceof THREE.Vector3) tempTarget.copy(target);else tempTarget.set(...target);
|
|
1272
|
+
const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
|
|
1273
|
+
if (isOrthographicCamera(camera)) {
|
|
1274
|
+
return {
|
|
1275
|
+
width: width / camera.zoom,
|
|
1276
|
+
height: height / camera.zoom,
|
|
1277
|
+
top,
|
|
1278
|
+
left,
|
|
1279
|
+
factor: 1,
|
|
1280
|
+
distance,
|
|
1281
|
+
aspect
|
|
1282
|
+
};
|
|
1283
|
+
} else {
|
|
1284
|
+
const fov = camera.fov * Math.PI / 180; // convert vertical fov to radians
|
|
1285
|
+
const h = 2 * Math.tan(fov / 2) * distance; // visible height
|
|
1286
|
+
const w = h * (width / height);
|
|
1287
|
+
return {
|
|
1288
|
+
width: w,
|
|
1289
|
+
height: h,
|
|
1290
|
+
top,
|
|
1291
|
+
left,
|
|
1292
|
+
factor: width / w,
|
|
1293
|
+
distance,
|
|
1294
|
+
aspect
|
|
1295
|
+
};
|
|
1230
1296
|
}
|
|
1231
1297
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
const handleTextInstance = () => console.warn('R3F: Text is not allowed in JSX! This could be stray whitespace or characters.');
|
|
1240
|
-
|
|
1241
|
-
const reconciler = Reconciler({
|
|
1242
|
-
supportsMutation: true,
|
|
1243
|
-
isPrimaryRenderer: false,
|
|
1244
|
-
supportsPersistence: false,
|
|
1245
|
-
supportsHydration: false,
|
|
1246
|
-
noTimeout: -1,
|
|
1247
|
-
createInstance,
|
|
1248
|
-
removeChild,
|
|
1249
|
-
appendChild,
|
|
1250
|
-
appendInitialChild: appendChild,
|
|
1251
|
-
insertBefore,
|
|
1252
|
-
|
|
1253
|
-
appendChildToContainer(container, child) {
|
|
1254
|
-
const scene = container.getState().scene.__r3f;
|
|
1255
|
-
|
|
1256
|
-
if (!child || !scene) return;
|
|
1257
|
-
appendChild(scene, child);
|
|
1258
|
-
},
|
|
1259
|
-
|
|
1260
|
-
removeChildFromContainer(container, child) {
|
|
1261
|
-
const scene = container.getState().scene.__r3f;
|
|
1262
|
-
|
|
1263
|
-
if (!child || !scene) return;
|
|
1264
|
-
removeChild(scene, child);
|
|
1265
|
-
},
|
|
1266
|
-
|
|
1267
|
-
insertInContainerBefore(container, child, beforeChild) {
|
|
1268
|
-
const scene = container.getState().scene.__r3f;
|
|
1269
|
-
|
|
1270
|
-
if (!child || !beforeChild || !scene) return;
|
|
1271
|
-
insertBefore(scene, child, beforeChild);
|
|
1272
|
-
},
|
|
1273
|
-
|
|
1274
|
-
getRootHostContext: () => null,
|
|
1275
|
-
getChildHostContext: parentHostContext => parentHostContext,
|
|
1276
|
-
|
|
1277
|
-
prepareUpdate(instance, _type, oldProps, newProps) {
|
|
1278
|
-
var _newProps$args, _oldProps$args, _newProps$args2;
|
|
1279
|
-
|
|
1280
|
-
// Reconstruct primitives if object prop changes
|
|
1281
|
-
if (instance.type === 'primitive' && oldProps.object !== newProps.object) return [true]; // Throw if an object or literal was passed for args
|
|
1282
|
-
|
|
1283
|
-
if (newProps.args !== undefined && !Array.isArray(newProps.args)) throw new Error('R3F: The args prop must be an array!'); // Reconstruct instance if args change
|
|
1284
|
-
|
|
1285
|
-
if (((_newProps$args = newProps.args) == null ? void 0 : _newProps$args.length) !== ((_oldProps$args = oldProps.args) == null ? void 0 : _oldProps$args.length)) return [true];
|
|
1286
|
-
if ((_newProps$args2 = newProps.args) != null && _newProps$args2.some((value, index) => {
|
|
1287
|
-
var _oldProps$args2;
|
|
1288
|
-
|
|
1289
|
-
return value !== ((_oldProps$args2 = oldProps.args) == null ? void 0 : _oldProps$args2[index]);
|
|
1290
|
-
})) return [true]; // Create a diff-set, flag if there are any changes
|
|
1291
|
-
|
|
1292
|
-
const changedProps = diffProps(instance, newProps, true);
|
|
1293
|
-
if (Object.keys(changedProps).length) return [false, changedProps]; // Otherwise do not touch the instance
|
|
1294
|
-
|
|
1295
|
-
return null;
|
|
1296
|
-
},
|
|
1297
|
-
|
|
1298
|
-
commitUpdate(instance, diff, type, _oldProps, newProps, fiber) {
|
|
1299
|
-
const [reconstruct, changedProps] = diff; // Reconstruct when args or <primitive object={...} have changes
|
|
1300
|
-
|
|
1301
|
-
if (reconstruct) return switchInstance(instance, type, newProps, fiber); // Otherwise just overwrite props
|
|
1302
|
-
|
|
1303
|
-
Object.assign(instance.props, changedProps);
|
|
1304
|
-
applyProps(instance.object, changedProps);
|
|
1305
|
-
},
|
|
1306
|
-
|
|
1307
|
-
finalizeInitialChildren: () => false,
|
|
1308
|
-
|
|
1309
|
-
commitMount() {},
|
|
1310
|
-
|
|
1311
|
-
getPublicInstance: instance => instance == null ? void 0 : instance.object,
|
|
1312
|
-
prepareForCommit: () => null,
|
|
1313
|
-
preparePortalMount: container => prepare(container.getState().scene, container, '', {}),
|
|
1314
|
-
resetAfterCommit: () => {},
|
|
1315
|
-
shouldSetTextContent: () => false,
|
|
1316
|
-
clearContainer: () => false,
|
|
1317
|
-
|
|
1318
|
-
hideInstance(instance) {
|
|
1319
|
-
var _instance$parent;
|
|
1320
|
-
|
|
1321
|
-
if (instance.props.attach && (_instance$parent = instance.parent) != null && _instance$parent.object) {
|
|
1322
|
-
detach(instance.parent, instance);
|
|
1323
|
-
} else if (instance.object instanceof THREE.Object3D) {
|
|
1324
|
-
instance.object.visible = false;
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
instance.isHidden = true;
|
|
1328
|
-
invalidateInstance(instance);
|
|
1329
|
-
},
|
|
1330
|
-
|
|
1331
|
-
unhideInstance(instance) {
|
|
1332
|
-
if (instance.isHidden) {
|
|
1333
|
-
var _instance$parent2;
|
|
1334
|
-
|
|
1335
|
-
if (instance.props.attach && (_instance$parent2 = instance.parent) != null && _instance$parent2.object) {
|
|
1336
|
-
attach(instance.parent, instance);
|
|
1337
|
-
} else if (instance.object instanceof THREE.Object3D && instance.props.visible !== false) {
|
|
1338
|
-
instance.object.visible = true;
|
|
1298
|
+
let performanceTimeout = undefined;
|
|
1299
|
+
const setPerformanceCurrent = current => set(state => ({
|
|
1300
|
+
performance: {
|
|
1301
|
+
...state.performance,
|
|
1302
|
+
current
|
|
1339
1303
|
}
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
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
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1304
|
+
}));
|
|
1305
|
+
const pointer = new THREE.Vector2();
|
|
1306
|
+
const rootState = {
|
|
1307
|
+
set,
|
|
1308
|
+
get,
|
|
1309
|
+
// Mock objects that have to be configured
|
|
1310
|
+
gl: null,
|
|
1311
|
+
camera: null,
|
|
1312
|
+
raycaster: null,
|
|
1313
|
+
events: {
|
|
1314
|
+
priority: 1,
|
|
1315
|
+
enabled: true,
|
|
1316
|
+
connected: false
|
|
1317
|
+
},
|
|
1318
|
+
scene: null,
|
|
1319
|
+
xr: null,
|
|
1320
|
+
invalidate: (frames = 1) => invalidate(get(), frames),
|
|
1321
|
+
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
|
|
1322
|
+
legacy: false,
|
|
1323
|
+
linear: false,
|
|
1324
|
+
flat: false,
|
|
1325
|
+
controls: null,
|
|
1326
|
+
clock: new THREE.Clock(),
|
|
1327
|
+
pointer,
|
|
1328
|
+
mouse: pointer,
|
|
1329
|
+
frameloop: 'always',
|
|
1330
|
+
onPointerMissed: undefined,
|
|
1331
|
+
performance: {
|
|
1332
|
+
current: 1,
|
|
1333
|
+
min: 0.5,
|
|
1334
|
+
max: 1,
|
|
1335
|
+
debounce: 200,
|
|
1336
|
+
regress: () => {
|
|
1337
|
+
const state = get();
|
|
1338
|
+
// Clear timeout
|
|
1339
|
+
if (performanceTimeout) clearTimeout(performanceTimeout);
|
|
1340
|
+
// Set lower bound performance
|
|
1341
|
+
if (state.performance.current !== state.performance.min) setPerformanceCurrent(state.performance.min);
|
|
1342
|
+
// Go back to upper bound performance after a while unless something regresses meanwhile
|
|
1343
|
+
performanceTimeout = setTimeout(() => setPerformanceCurrent(get().performance.max), state.performance.debounce);
|
|
1344
|
+
}
|
|
1345
|
+
},
|
|
1346
|
+
size: {
|
|
1347
|
+
width: 0,
|
|
1348
|
+
height: 0,
|
|
1349
|
+
top: 0,
|
|
1350
|
+
left: 0
|
|
1351
|
+
},
|
|
1352
|
+
viewport: {
|
|
1353
|
+
initialDpr: 0,
|
|
1354
|
+
dpr: 0,
|
|
1355
|
+
width: 0,
|
|
1356
|
+
height: 0,
|
|
1357
|
+
top: 0,
|
|
1358
|
+
left: 0,
|
|
1359
|
+
aspect: 0,
|
|
1360
|
+
distance: 0,
|
|
1361
|
+
factor: 0,
|
|
1362
|
+
getCurrentViewport
|
|
1363
|
+
},
|
|
1364
|
+
setEvents: events => set(state => ({
|
|
1365
|
+
...state,
|
|
1366
|
+
events: {
|
|
1367
|
+
...state.events,
|
|
1368
|
+
...events
|
|
1369
|
+
}
|
|
1370
|
+
})),
|
|
1371
|
+
setSize: (width, height, top = 0, left = 0) => {
|
|
1372
|
+
const camera = get().camera;
|
|
1373
|
+
const size = {
|
|
1374
|
+
width,
|
|
1375
|
+
height,
|
|
1376
|
+
top,
|
|
1377
|
+
left
|
|
1378
|
+
};
|
|
1379
|
+
set(state => ({
|
|
1380
|
+
size,
|
|
1381
|
+
viewport: {
|
|
1382
|
+
...state.viewport,
|
|
1383
|
+
...getCurrentViewport(camera, defaultTarget, size)
|
|
1384
|
+
}
|
|
1385
|
+
}));
|
|
1386
|
+
},
|
|
1387
|
+
setDpr: dpr => set(state => {
|
|
1388
|
+
const resolved = calculateDpr(dpr);
|
|
1389
|
+
return {
|
|
1390
|
+
viewport: {
|
|
1391
|
+
...state.viewport,
|
|
1392
|
+
dpr: resolved,
|
|
1393
|
+
initialDpr: state.viewport.initialDpr || resolved
|
|
1394
|
+
}
|
|
1395
|
+
};
|
|
1396
|
+
}),
|
|
1397
|
+
setFrameloop: frameloop => {
|
|
1398
|
+
var _frameloop$mode, _frameloop$render, _frameloop$maxDelta;
|
|
1399
|
+
const state = get();
|
|
1400
|
+
const mode = typeof frameloop === 'string' ? frameloop : (frameloop == null ? void 0 : frameloop.mode) === 'auto' ? 'always' : (_frameloop$mode = frameloop == null ? void 0 : frameloop.mode) != null ? _frameloop$mode : state.frameloop;
|
|
1401
|
+
const render = typeof frameloop === 'string' ? state.internal.render : (_frameloop$render = frameloop == null ? void 0 : frameloop.render) != null ? _frameloop$render : state.internal.render;
|
|
1402
|
+
const maxDelta = typeof frameloop === 'string' ? state.internal.maxDelta : (_frameloop$maxDelta = frameloop == null ? void 0 : frameloop.maxDelta) != null ? _frameloop$maxDelta : state.internal.maxDelta;
|
|
1403
|
+
const clock = state.clock;
|
|
1404
|
+
// if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
|
|
1405
|
+
clock.stop();
|
|
1406
|
+
clock.elapsedTime = 0;
|
|
1407
|
+
if (frameloop !== 'never') {
|
|
1408
|
+
clock.start();
|
|
1409
|
+
clock.elapsedTime = 0;
|
|
1410
|
+
}
|
|
1411
|
+
set(() => ({
|
|
1412
|
+
frameloop: mode,
|
|
1413
|
+
internal: {
|
|
1414
|
+
...state.internal,
|
|
1415
|
+
render,
|
|
1416
|
+
maxDelta
|
|
1417
|
+
}
|
|
1418
|
+
}));
|
|
1419
|
+
},
|
|
1420
|
+
previousRoot: undefined,
|
|
1421
|
+
internal: {
|
|
1422
|
+
// Events
|
|
1423
|
+
interaction: [],
|
|
1424
|
+
hovered: new Map(),
|
|
1425
|
+
subscribers: [],
|
|
1426
|
+
initialClick: [0, 0],
|
|
1427
|
+
initialHits: [],
|
|
1428
|
+
capturedMap: new Map(),
|
|
1429
|
+
lastEvent: /*#__PURE__*/React.createRef(),
|
|
1430
|
+
// Updates
|
|
1431
|
+
active: false,
|
|
1432
|
+
frames: 0,
|
|
1433
|
+
stages: [],
|
|
1434
|
+
render: 'auto',
|
|
1435
|
+
maxDelta: 1 / 10,
|
|
1436
|
+
priority: 0,
|
|
1437
|
+
subscribe: (ref, priority, store) => {
|
|
1438
|
+
const state = get();
|
|
1439
|
+
const internal = state.internal;
|
|
1440
|
+
// If this subscription was given a priority, it takes rendering into its own hands
|
|
1441
|
+
// For that reason we switch off automatic rendering and increase the manual flag
|
|
1442
|
+
// As long as this flag is positive there can be no internal rendering at all
|
|
1443
|
+
// because there could be multiple render subscriptions
|
|
1444
|
+
internal.priority = internal.priority + (priority > 0 ? 1 : 0);
|
|
1445
|
+
// We use the render flag and deprecate priority
|
|
1446
|
+
if (internal.priority && state.internal.render === 'auto') set(() => ({
|
|
1447
|
+
internal: {
|
|
1448
|
+
...state.internal,
|
|
1449
|
+
render: 'manual'
|
|
1450
|
+
}
|
|
1451
|
+
}));
|
|
1452
|
+
internal.subscribers.push({
|
|
1453
|
+
ref,
|
|
1454
|
+
priority,
|
|
1455
|
+
store
|
|
1456
|
+
});
|
|
1457
|
+
// Register subscriber and sort layers from lowest to highest, meaning,
|
|
1458
|
+
// highest priority renders last (on top of the other frames)
|
|
1459
|
+
internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
|
|
1460
|
+
return () => {
|
|
1461
|
+
const state = get();
|
|
1462
|
+
const internal = state.internal;
|
|
1463
|
+
if (internal != null && internal.subscribers) {
|
|
1464
|
+
// Decrease manual flag if this subscription had a priority
|
|
1465
|
+
internal.priority = internal.priority - (priority > 0 ? 1 : 0);
|
|
1466
|
+
// We use the render flag and deprecate priority
|
|
1467
|
+
if (!internal.priority && state.internal.render === 'manual') set(() => ({
|
|
1468
|
+
internal: {
|
|
1469
|
+
...state.internal,
|
|
1470
|
+
render: 'auto'
|
|
1471
|
+
}
|
|
1472
|
+
}));
|
|
1473
|
+
// Remove subscriber from list
|
|
1474
|
+
internal.subscribers = internal.subscribers.filter(s => s.ref !== ref);
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1451
1478
|
}
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1479
|
+
};
|
|
1480
|
+
return rootState;
|
|
1481
|
+
});
|
|
1482
|
+
const state = rootStore.getState();
|
|
1483
|
+
let oldSize = state.size;
|
|
1484
|
+
let oldDpr = state.viewport.dpr;
|
|
1485
|
+
let oldCamera = state.camera;
|
|
1486
|
+
rootStore.subscribe(() => {
|
|
1487
|
+
const {
|
|
1488
|
+
camera,
|
|
1489
|
+
size,
|
|
1490
|
+
viewport,
|
|
1491
|
+
gl,
|
|
1492
|
+
set
|
|
1493
|
+
} = rootStore.getState();
|
|
1459
1494
|
|
|
1460
|
-
|
|
1461
|
-
|
|
1495
|
+
// Resize camera and renderer on changes to size and pixelratio
|
|
1496
|
+
if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
|
|
1497
|
+
oldSize = size;
|
|
1498
|
+
oldDpr = viewport.dpr;
|
|
1499
|
+
// Update camera & renderer
|
|
1500
|
+
updateCamera(camera, size);
|
|
1501
|
+
gl.setPixelRatio(viewport.dpr);
|
|
1502
|
+
const updateStyle = typeof HTMLCanvasElement !== 'undefined' && gl.domElement instanceof HTMLCanvasElement;
|
|
1503
|
+
gl.setSize(size.width, size.height, updateStyle);
|
|
1462
1504
|
}
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
function invalidate(state, frames = 1) {
|
|
1466
|
-
var _state$gl$xr2;
|
|
1467
|
-
|
|
1468
|
-
if (!state) return roots.forEach(root => invalidate(root.store.getState()), frames);
|
|
1469
|
-
if ((_state$gl$xr2 = state.gl.xr) != null && _state$gl$xr2.isPresenting || !state.internal.active || state.frameloop === 'never') return; // Increase frames, do not go higher than 60
|
|
1470
|
-
|
|
1471
|
-
state.internal.frames = Math.min(60, state.internal.frames + frames); // If the render-loop isn't active, start it
|
|
1472
1505
|
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1506
|
+
// Update viewport once the camera changes
|
|
1507
|
+
if (camera !== oldCamera) {
|
|
1508
|
+
oldCamera = camera;
|
|
1509
|
+
// Update viewport
|
|
1510
|
+
set(state => ({
|
|
1511
|
+
viewport: {
|
|
1512
|
+
...state.viewport,
|
|
1513
|
+
...state.viewport.getCurrentViewport(camera)
|
|
1514
|
+
}
|
|
1515
|
+
}));
|
|
1476
1516
|
}
|
|
1477
|
-
}
|
|
1517
|
+
});
|
|
1478
1518
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
if (!state) roots.forEach(root => update(timestamp, root.store.getState()));else update(timestamp, state, frame);
|
|
1482
|
-
if (runGlobalEffects) flushGlobalEffects('after', timestamp);
|
|
1483
|
-
}
|
|
1519
|
+
// Invalidate on any change
|
|
1520
|
+
rootStore.subscribe(state => invalidate(state));
|
|
1484
1521
|
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#invalidate
|
|
1491
|
-
*/
|
|
1492
|
-
invalidate,
|
|
1493
|
-
|
|
1494
|
-
/**
|
|
1495
|
-
* Advances the frameloop and runs render effects, useful for when manually rendering via `frameloop="never"`.
|
|
1496
|
-
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#advance
|
|
1497
|
-
*/
|
|
1498
|
-
advance
|
|
1499
|
-
};
|
|
1500
|
-
}
|
|
1522
|
+
// Return root state
|
|
1523
|
+
return rootStore;
|
|
1524
|
+
};
|
|
1525
|
+
|
|
1526
|
+
// TODO: Remove deprecated fields in `Subscription`
|
|
1501
1527
|
|
|
1502
|
-
/**
|
|
1503
|
-
* Class representing a stage that updates every frame.
|
|
1504
|
-
* Stages are used to build a lifecycle of effects for an app's frameloop.
|
|
1528
|
+
/**
|
|
1529
|
+
* Class representing a stage that updates every frame.
|
|
1530
|
+
* Stages are used to build a lifecycle of effects for an app's frameloop.
|
|
1505
1531
|
*/
|
|
1506
1532
|
class Stage {
|
|
1507
1533
|
constructor() {
|
|
1508
1534
|
this.subscribers = [];
|
|
1509
1535
|
this._frameTime = 0;
|
|
1510
1536
|
}
|
|
1511
|
-
/**
|
|
1512
|
-
* Executes all callback subscriptions on the stage.
|
|
1513
|
-
* @param delta - Delta time between frame calls.
|
|
1514
|
-
* @param [frame] - The XR frame if it exists.
|
|
1515
|
-
*/
|
|
1516
|
-
|
|
1517
1537
|
|
|
1538
|
+
/**
|
|
1539
|
+
* Executes all callback subscriptions on the stage.
|
|
1540
|
+
* @param delta - Delta time between frame calls.
|
|
1541
|
+
* @param [frame] - The XR frame if it exists.
|
|
1542
|
+
*/
|
|
1518
1543
|
frame(delta, frame) {
|
|
1519
1544
|
const subs = this.subscribers;
|
|
1520
1545
|
const initialTime = performance.now();
|
|
1521
|
-
|
|
1522
1546
|
for (let i = 0; i < subs.length; i++) {
|
|
1523
1547
|
subs[i].ref.current(subs[i].store.getState(), delta, frame);
|
|
1524
1548
|
}
|
|
1525
|
-
|
|
1526
1549
|
this._frameTime = performance.now() - initialTime;
|
|
1527
1550
|
}
|
|
1528
|
-
/**
|
|
1529
|
-
* Adds a callback subscriber to the stage.
|
|
1530
|
-
* @param ref - The mutable callback reference.
|
|
1531
|
-
* @param store - The store to be used with the callback execution.
|
|
1532
|
-
* @returns A function to remove the subscription.
|
|
1533
|
-
*/
|
|
1534
|
-
|
|
1535
1551
|
|
|
1552
|
+
/**
|
|
1553
|
+
* Adds a callback subscriber to the stage.
|
|
1554
|
+
* @param ref - The mutable callback reference.
|
|
1555
|
+
* @param store - The store to be used with the callback execution.
|
|
1556
|
+
* @returns A function to remove the subscription.
|
|
1557
|
+
*/
|
|
1536
1558
|
add(ref, store) {
|
|
1537
1559
|
this.subscribers.push({
|
|
1538
1560
|
ref,
|
|
@@ -1544,21 +1566,20 @@ class Stage {
|
|
|
1544
1566
|
});
|
|
1545
1567
|
};
|
|
1546
1568
|
}
|
|
1547
|
-
|
|
1548
1569
|
get frameTime() {
|
|
1549
1570
|
return this._frameTime;
|
|
1550
1571
|
}
|
|
1572
|
+
}
|
|
1551
1573
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1574
|
+
// Using Unity's fixedStep default.
|
|
1554
1575
|
const FPS_50 = 1 / 50;
|
|
1555
|
-
/**
|
|
1556
|
-
* Class representing a stage that updates every frame at a fixed rate.
|
|
1557
|
-
* @param name - Name of the stage.
|
|
1558
|
-
* @param [fixedStep] - Fixed step rate.
|
|
1559
|
-
* @param [maxSubsteps] - Maximum number of substeps.
|
|
1560
|
-
*/
|
|
1561
1576
|
|
|
1577
|
+
/**
|
|
1578
|
+
* Class representing a stage that updates every frame at a fixed rate.
|
|
1579
|
+
* @param name - Name of the stage.
|
|
1580
|
+
* @param [fixedStep] - Fixed step rate.
|
|
1581
|
+
* @param [maxSubsteps] - Maximum number of substeps.
|
|
1582
|
+
*/
|
|
1562
1583
|
class FixedStage extends Stage {
|
|
1563
1584
|
constructor(fixedStep, maxSubSteps) {
|
|
1564
1585
|
super();
|
|
@@ -1569,74 +1590,62 @@ class FixedStage extends Stage {
|
|
|
1569
1590
|
this._fixedFrameTime = 0;
|
|
1570
1591
|
this._substepTimes = [];
|
|
1571
1592
|
}
|
|
1572
|
-
/**
|
|
1573
|
-
* Executes all callback subscriptions on the stage.
|
|
1574
|
-
* @param delta - Delta time between frame calls.
|
|
1575
|
-
* @param [frame] - The XR frame if it exists.
|
|
1576
|
-
*/
|
|
1577
|
-
|
|
1578
1593
|
|
|
1594
|
+
/**
|
|
1595
|
+
* Executes all callback subscriptions on the stage.
|
|
1596
|
+
* @param delta - Delta time between frame calls.
|
|
1597
|
+
* @param [frame] - The XR frame if it exists.
|
|
1598
|
+
*/
|
|
1579
1599
|
frame(delta, frame) {
|
|
1580
1600
|
const initialTime = performance.now();
|
|
1581
1601
|
let substeps = 0;
|
|
1582
1602
|
this._substepTimes = [];
|
|
1583
1603
|
this._accumulator += delta;
|
|
1584
|
-
|
|
1585
1604
|
while (this._accumulator >= this._fixedStep && substeps < this._maxSubsteps) {
|
|
1586
1605
|
this._accumulator -= this._fixedStep;
|
|
1587
1606
|
substeps++;
|
|
1588
1607
|
super.frame(this._fixedStep, frame);
|
|
1589
|
-
|
|
1590
1608
|
this._substepTimes.push(super.frameTime);
|
|
1591
1609
|
}
|
|
1610
|
+
this._fixedFrameTime = performance.now() - initialTime;
|
|
1592
1611
|
|
|
1593
|
-
|
|
1612
|
+
// The accumulator will only be larger than the fixed step if we had to
|
|
1594
1613
|
// bail early due to hitting the max substep limit or execution time lagging.
|
|
1595
1614
|
// In that case, we want to shave off the excess so we don't fall behind next frame.
|
|
1596
|
-
|
|
1597
1615
|
this._accumulator = this._accumulator % this._fixedStep;
|
|
1598
1616
|
this._alpha = this._accumulator / this._fixedStep;
|
|
1599
1617
|
}
|
|
1600
|
-
|
|
1601
1618
|
get frameTime() {
|
|
1602
1619
|
return this._fixedFrameTime;
|
|
1603
1620
|
}
|
|
1604
|
-
|
|
1605
1621
|
get substepTimes() {
|
|
1606
1622
|
return this._substepTimes;
|
|
1607
1623
|
}
|
|
1608
|
-
|
|
1609
1624
|
get fixedStep() {
|
|
1610
1625
|
return this._fixedStep;
|
|
1611
1626
|
}
|
|
1612
|
-
|
|
1613
1627
|
set fixedStep(fixedStep) {
|
|
1614
1628
|
this._fixedStep = fixedStep;
|
|
1615
1629
|
}
|
|
1616
|
-
|
|
1617
1630
|
get maxSubsteps() {
|
|
1618
1631
|
return this._maxSubsteps;
|
|
1619
1632
|
}
|
|
1620
|
-
|
|
1621
1633
|
set maxSubsteps(maxSubsteps) {
|
|
1622
1634
|
this._maxSubsteps = maxSubsteps;
|
|
1623
1635
|
}
|
|
1624
|
-
|
|
1625
1636
|
get accumulator() {
|
|
1626
1637
|
return this._accumulator;
|
|
1627
1638
|
}
|
|
1628
|
-
|
|
1629
1639
|
get alpha() {
|
|
1630
1640
|
return this._alpha;
|
|
1631
1641
|
}
|
|
1632
|
-
|
|
1633
1642
|
}
|
|
1634
|
-
const Early = new Stage();
|
|
1635
|
-
const Fixed = new FixedStage();
|
|
1636
|
-
const Update = new Stage();
|
|
1637
|
-
const Late = new Stage();
|
|
1638
|
-
const Render = new Stage();
|
|
1639
|
-
const After = new Stage();
|
|
1643
|
+
const Early = /*#__PURE__*/new Stage();
|
|
1644
|
+
const Fixed = /*#__PURE__*/new FixedStage();
|
|
1645
|
+
const Update = /*#__PURE__*/new Stage();
|
|
1646
|
+
const Late = /*#__PURE__*/new Stage();
|
|
1647
|
+
const Render = /*#__PURE__*/new Stage();
|
|
1648
|
+
const After = /*#__PURE__*/new Stage();
|
|
1640
1649
|
const Stages = {
|
|
1641
1650
|
Early,
|
|
1642
1651
|
Fixed,
|
|
@@ -1647,11 +1656,11 @@ const Stages = {
|
|
|
1647
1656
|
};
|
|
1648
1657
|
const Lifecycle = [Early, Fixed, Update, Late, Render, After];
|
|
1649
1658
|
|
|
1650
|
-
/**
|
|
1651
|
-
* Exposes an object's {@link Instance}.
|
|
1652
|
-
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useInstanceHandle
|
|
1653
|
-
*
|
|
1654
|
-
* **Note**: this is an escape hatch to react-internal fields. Expect this to change significantly between versions.
|
|
1659
|
+
/**
|
|
1660
|
+
* Exposes an object's {@link Instance}.
|
|
1661
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useInstanceHandle
|
|
1662
|
+
*
|
|
1663
|
+
* **Note**: this is an escape hatch to react-internal fields. Expect this to change significantly between versions.
|
|
1655
1664
|
*/
|
|
1656
1665
|
function useInstanceHandle(ref) {
|
|
1657
1666
|
const instance = React.useRef(null);
|
|
@@ -1663,113 +1672,123 @@ function useStore() {
|
|
|
1663
1672
|
if (!store) throw new Error('R3F: Hooks can only be used within the Canvas component!');
|
|
1664
1673
|
return store;
|
|
1665
1674
|
}
|
|
1666
|
-
/**
|
|
1667
|
-
* Accesses R3F's internal state, containing renderer, canvas, scene, etc.
|
|
1668
|
-
* @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usethree
|
|
1669
|
-
*/
|
|
1670
1675
|
|
|
1676
|
+
/**
|
|
1677
|
+
* Accesses R3F's internal state, containing renderer, canvas, scene, etc.
|
|
1678
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usethree
|
|
1679
|
+
*/
|
|
1671
1680
|
function useThree(selector = state => state, equalityFn) {
|
|
1681
|
+
// TODO: fix this type
|
|
1672
1682
|
return useStore()(selector, equalityFn);
|
|
1673
1683
|
}
|
|
1674
|
-
/**
|
|
1675
|
-
* Executes a callback before render in a shared frame loop.
|
|
1676
|
-
* Can order effects with render priority or manually render with a positive priority.
|
|
1677
|
-
* @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe
|
|
1678
|
-
*/
|
|
1679
1684
|
|
|
1685
|
+
/**
|
|
1686
|
+
* Executes a callback before render in a shared frame loop.
|
|
1687
|
+
* Can order effects with render priority or manually render with a positive priority.
|
|
1688
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe
|
|
1689
|
+
*/
|
|
1680
1690
|
function useFrame(callback, renderPriority = 0) {
|
|
1681
1691
|
const store = useStore();
|
|
1682
|
-
const subscribe = store.getState().internal.subscribe;
|
|
1683
|
-
|
|
1684
|
-
const ref = useMutableCallback(callback);
|
|
1685
|
-
|
|
1692
|
+
const subscribe = store.getState().internal.subscribe;
|
|
1693
|
+
// Memoize ref
|
|
1694
|
+
const ref = useMutableCallback(callback);
|
|
1695
|
+
// Subscribe on mount, unsubscribe on unmount
|
|
1686
1696
|
useIsomorphicLayoutEffect(() => subscribe(ref, renderPriority, store), [renderPriority, subscribe, store]);
|
|
1687
1697
|
return null;
|
|
1688
1698
|
}
|
|
1689
|
-
/**
|
|
1690
|
-
* Executes a callback in a given update stage.
|
|
1691
|
-
* Uses the stage instance to indetify which stage to target in the lifecycle.
|
|
1692
|
-
*/
|
|
1693
1699
|
|
|
1700
|
+
/**
|
|
1701
|
+
* Executes a callback in a given update stage.
|
|
1702
|
+
* Uses the stage instance to indetify which stage to target in the lifecycle.
|
|
1703
|
+
*/
|
|
1694
1704
|
function useUpdate(callback, stage = Stages.Update) {
|
|
1695
1705
|
const store = useStore();
|
|
1696
|
-
const stages = store.getState().internal.stages;
|
|
1697
|
-
|
|
1698
|
-
const ref = useMutableCallback(callback);
|
|
1699
|
-
|
|
1700
|
-
if (!stages.includes(stage)) throw new Error(`An invoked stage does not exist in the lifecycle.`);
|
|
1701
|
-
|
|
1706
|
+
const stages = store.getState().internal.stages;
|
|
1707
|
+
// Memoize ref
|
|
1708
|
+
const ref = useMutableCallback(callback);
|
|
1709
|
+
// Throw an error if a stage does not exist in the lifecycle
|
|
1710
|
+
if (!stages.includes(stage)) throw new Error(`An invoked stage does not exist in the lifecycle.`);
|
|
1711
|
+
// Subscribe on mount, unsubscribe on unmount
|
|
1702
1712
|
useIsomorphicLayoutEffect(() => stage.add(ref, store), [stage]);
|
|
1703
1713
|
}
|
|
1704
|
-
/**
|
|
1705
|
-
* Returns a node graph of an object with named nodes & materials.
|
|
1706
|
-
* @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usegraph
|
|
1707
|
-
*/
|
|
1708
1714
|
|
|
1715
|
+
/**
|
|
1716
|
+
* Returns a node graph of an object with named nodes & materials.
|
|
1717
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usegraph
|
|
1718
|
+
*/
|
|
1709
1719
|
function useGraph(object) {
|
|
1710
1720
|
return React.useMemo(() => buildGraph(object), [object]);
|
|
1711
1721
|
}
|
|
1712
|
-
|
|
1722
|
+
const memoizedLoaders = new WeakMap();
|
|
1723
|
+
const isConstructor = value => {
|
|
1724
|
+
var _value$prototype;
|
|
1725
|
+
return typeof value === 'function' && (value == null ? void 0 : (_value$prototype = value.prototype) == null ? void 0 : _value$prototype.constructor) === value;
|
|
1726
|
+
};
|
|
1713
1727
|
function loadingFn(extensions, onProgress) {
|
|
1714
|
-
return function (Proto, ...input) {
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
if (
|
|
1721
|
-
|
|
1722
|
-
|
|
1728
|
+
return async function (Proto, ...input) {
|
|
1729
|
+
let loader;
|
|
1730
|
+
|
|
1731
|
+
// Construct and cache loader if constructor was passed
|
|
1732
|
+
if (isConstructor(Proto)) {
|
|
1733
|
+
loader = memoizedLoaders.get(Proto);
|
|
1734
|
+
if (!loader) {
|
|
1735
|
+
loader = new Proto();
|
|
1736
|
+
memoizedLoaders.set(Proto, loader);
|
|
1737
|
+
}
|
|
1738
|
+
} else {
|
|
1739
|
+
loader = Proto;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
// Apply loader extensions
|
|
1743
|
+
if (extensions) extensions(loader);
|
|
1744
|
+
|
|
1745
|
+
// Go through the urls and load them
|
|
1746
|
+
return Promise.all(input.map(input => new Promise((res, reject) => loader.load(input, data => res(isObject3D(data == null ? void 0 : data.scene) ? Object.assign(data, buildGraph(data.scene)) : data), onProgress, error => reject(new Error(`Could not load ${input}: ${error == null ? void 0 : error.message}`))))));
|
|
1723
1747
|
};
|
|
1724
1748
|
}
|
|
1725
|
-
/**
|
|
1726
|
-
* Synchronously loads and caches assets with a three loader.
|
|
1727
|
-
*
|
|
1728
|
-
* Note: this hook's caller must be wrapped with `React.Suspense`
|
|
1729
|
-
* @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useloader
|
|
1749
|
+
/**
|
|
1750
|
+
* Synchronously loads and caches assets with a three loader.
|
|
1751
|
+
*
|
|
1752
|
+
* Note: this hook's caller must be wrapped with `React.Suspense`
|
|
1753
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useloader
|
|
1730
1754
|
*/
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
function useLoader(Proto, input, extensions, onProgress) {
|
|
1755
|
+
function useLoader(loader, input, extensions, onProgress) {
|
|
1734
1756
|
// Use suspense to load async assets
|
|
1735
1757
|
const keys = Array.isArray(input) ? input : [input];
|
|
1736
|
-
const results = suspend(loadingFn(extensions, onProgress), [
|
|
1758
|
+
const results = suspend(loadingFn(extensions, onProgress), [loader, ...keys], {
|
|
1737
1759
|
equal: is.equ
|
|
1738
|
-
});
|
|
1739
|
-
|
|
1760
|
+
});
|
|
1761
|
+
// Return the object(s)
|
|
1740
1762
|
return Array.isArray(input) ? results : results[0];
|
|
1741
1763
|
}
|
|
1742
|
-
/**
|
|
1743
|
-
* Preloads an asset into cache as a side-effect.
|
|
1744
|
-
*/
|
|
1745
1764
|
|
|
1746
|
-
|
|
1765
|
+
/**
|
|
1766
|
+
* Preloads an asset into cache as a side-effect.
|
|
1767
|
+
*/
|
|
1768
|
+
useLoader.preload = function (loader, input, extensions) {
|
|
1747
1769
|
const keys = Array.isArray(input) ? input : [input];
|
|
1748
|
-
return preload(loadingFn(extensions), [
|
|
1770
|
+
return preload(loadingFn(extensions), [loader, ...keys]);
|
|
1749
1771
|
};
|
|
1750
|
-
/**
|
|
1751
|
-
* Removes a loaded asset from cache.
|
|
1752
|
-
*/
|
|
1753
|
-
|
|
1754
1772
|
|
|
1755
|
-
|
|
1773
|
+
/**
|
|
1774
|
+
* Removes a loaded asset from cache.
|
|
1775
|
+
*/
|
|
1776
|
+
useLoader.clear = function (loader, input) {
|
|
1756
1777
|
const keys = Array.isArray(input) ? input : [input];
|
|
1757
|
-
return clear([
|
|
1778
|
+
return clear([loader, ...keys]);
|
|
1758
1779
|
};
|
|
1759
1780
|
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
advance
|
|
1764
|
-
} = createLoop(roots);
|
|
1781
|
+
// TODO: fix type resolve
|
|
1782
|
+
|
|
1783
|
+
const _roots = new Map();
|
|
1765
1784
|
const shallowLoose = {
|
|
1766
1785
|
objects: 'shallow',
|
|
1767
1786
|
strict: false
|
|
1768
1787
|
};
|
|
1769
|
-
|
|
1770
1788
|
const createRendererInstance = (gl, canvas) => {
|
|
1771
1789
|
const customRenderer = typeof gl === 'function' ? gl(canvas) : gl;
|
|
1772
|
-
if (isRenderer(customRenderer)) return customRenderer;
|
|
1790
|
+
if (isRenderer(customRenderer)) return customRenderer;
|
|
1791
|
+
return new THREE.WebGLRenderer({
|
|
1773
1792
|
powerPreference: 'high-performance',
|
|
1774
1793
|
canvas: canvas,
|
|
1775
1794
|
antialias: true,
|
|
@@ -1777,44 +1796,42 @@ const createRendererInstance = (gl, canvas) => {
|
|
|
1777
1796
|
...gl
|
|
1778
1797
|
});
|
|
1779
1798
|
};
|
|
1780
|
-
|
|
1781
1799
|
const createStages = (stages, store) => {
|
|
1782
1800
|
const state = store.getState();
|
|
1783
1801
|
let subscribers;
|
|
1784
1802
|
let subscription;
|
|
1785
|
-
|
|
1786
1803
|
const _stages = stages != null ? stages : Lifecycle;
|
|
1787
|
-
|
|
1788
1804
|
if (!_stages.includes(Stages.Update)) throw 'The Stages.Update stage is required for R3F.';
|
|
1789
1805
|
if (!_stages.includes(Stages.Render)) throw 'The Stages.Render stage is required for R3F.';
|
|
1790
1806
|
state.set(({
|
|
1791
1807
|
internal
|
|
1792
1808
|
}) => ({
|
|
1793
|
-
internal: {
|
|
1809
|
+
internal: {
|
|
1810
|
+
...internal,
|
|
1794
1811
|
stages: _stages
|
|
1795
1812
|
}
|
|
1796
|
-
}));
|
|
1813
|
+
}));
|
|
1797
1814
|
|
|
1815
|
+
// Add useFrame loop to update stage
|
|
1798
1816
|
const frameCallback = {
|
|
1799
|
-
current
|
|
1817
|
+
current(state, delta, frame) {
|
|
1800
1818
|
subscribers = state.internal.subscribers;
|
|
1801
|
-
|
|
1802
1819
|
for (let i = 0; i < subscribers.length; i++) {
|
|
1803
1820
|
subscription = subscribers[i];
|
|
1804
1821
|
subscription.ref.current(subscription.store.getState(), delta, frame);
|
|
1805
1822
|
}
|
|
1806
1823
|
}
|
|
1807
1824
|
};
|
|
1808
|
-
Stages.Update.add(frameCallback, store);
|
|
1825
|
+
Stages.Update.add(frameCallback, store);
|
|
1809
1826
|
|
|
1827
|
+
// Add render callback to render stage
|
|
1810
1828
|
const renderCallback = {
|
|
1811
|
-
current
|
|
1829
|
+
current(state) {
|
|
1812
1830
|
if (state.internal.render === 'auto' && state.gl.render) state.gl.render(state.scene, state.camera);
|
|
1813
1831
|
}
|
|
1814
1832
|
};
|
|
1815
1833
|
Stages.Render.add(renderCallback, store);
|
|
1816
1834
|
};
|
|
1817
|
-
|
|
1818
1835
|
function computeInitialSize(canvas, size) {
|
|
1819
1836
|
if (!size && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
|
|
1820
1837
|
const {
|
|
@@ -1829,8 +1846,14 @@ function computeInitialSize(canvas, size) {
|
|
|
1829
1846
|
top,
|
|
1830
1847
|
left
|
|
1831
1848
|
};
|
|
1849
|
+
} else if (!size && typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
|
|
1850
|
+
return {
|
|
1851
|
+
width: canvas.width,
|
|
1852
|
+
height: canvas.height,
|
|
1853
|
+
top: 0,
|
|
1854
|
+
left: 0
|
|
1855
|
+
};
|
|
1832
1856
|
}
|
|
1833
|
-
|
|
1834
1857
|
return {
|
|
1835
1858
|
width: 0,
|
|
1836
1859
|
height: 0,
|
|
@@ -1839,36 +1862,61 @@ function computeInitialSize(canvas, size) {
|
|
|
1839
1862
|
...size
|
|
1840
1863
|
};
|
|
1841
1864
|
}
|
|
1842
|
-
|
|
1843
1865
|
function createRoot(canvas) {
|
|
1844
1866
|
// Check against mistaken use of createRoot
|
|
1845
|
-
const prevRoot =
|
|
1867
|
+
const prevRoot = _roots.get(canvas);
|
|
1846
1868
|
const prevFiber = prevRoot == null ? void 0 : prevRoot.fiber;
|
|
1847
1869
|
const prevStore = prevRoot == null ? void 0 : prevRoot.store;
|
|
1848
|
-
if (prevRoot) console.warn('R3F.createRoot should only be called once!');
|
|
1849
|
-
// https://github.com/pmndrs/react-three-fiber/pull/2261
|
|
1870
|
+
if (prevRoot) console.warn('R3F.createRoot should only be called once!');
|
|
1850
1871
|
|
|
1851
|
-
|
|
1872
|
+
// Report when an error was detected in a previous render
|
|
1873
|
+
// https://github.com/pmndrs/react-three-fiber/pull/2261
|
|
1874
|
+
const logRecoverableError = typeof reportError === 'function' ?
|
|
1875
|
+
// In modern browsers, reportError will dispatch an error event,
|
|
1852
1876
|
// emulating an uncaught JavaScript error.
|
|
1853
|
-
reportError :
|
|
1854
|
-
console.error
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
const
|
|
1859
|
-
|
|
1860
|
-
|
|
1877
|
+
reportError :
|
|
1878
|
+
// In older browsers and test environments, fallback to console.error.
|
|
1879
|
+
console.error;
|
|
1880
|
+
|
|
1881
|
+
// Create store
|
|
1882
|
+
const store = prevStore || createStore(invalidate, advance);
|
|
1883
|
+
// Create renderer
|
|
1884
|
+
const fiber = prevFiber || reconciler.createContainer(store,
|
|
1885
|
+
// container
|
|
1886
|
+
ConcurrentRoot,
|
|
1887
|
+
// tag
|
|
1888
|
+
null,
|
|
1889
|
+
// hydration callbacks
|
|
1890
|
+
false,
|
|
1891
|
+
// isStrictMode
|
|
1892
|
+
null,
|
|
1893
|
+
// concurrentUpdatesByDefaultOverride
|
|
1894
|
+
'',
|
|
1895
|
+
// identifierPrefix
|
|
1896
|
+
logRecoverableError,
|
|
1897
|
+
// onUncaughtError
|
|
1898
|
+
logRecoverableError,
|
|
1899
|
+
// onCaughtError
|
|
1900
|
+
logRecoverableError,
|
|
1901
|
+
// onRecoverableError
|
|
1902
|
+
null // transitionCallbacks
|
|
1903
|
+
);
|
|
1904
|
+
// Map it
|
|
1905
|
+
if (!prevRoot) _roots.set(canvas, {
|
|
1861
1906
|
fiber,
|
|
1862
1907
|
store
|
|
1863
|
-
});
|
|
1908
|
+
});
|
|
1864
1909
|
|
|
1910
|
+
// Locals
|
|
1865
1911
|
let onCreated;
|
|
1866
1912
|
let configured = false;
|
|
1913
|
+
let lastCamera;
|
|
1867
1914
|
return {
|
|
1868
1915
|
configure(props = {}) {
|
|
1869
1916
|
let {
|
|
1870
1917
|
gl: glConfig,
|
|
1871
1918
|
size: propsSize,
|
|
1919
|
+
scene: sceneOptions,
|
|
1872
1920
|
events,
|
|
1873
1921
|
onCreated: onCreatedCallback,
|
|
1874
1922
|
shadows = false,
|
|
@@ -1884,102 +1932,149 @@ function createRoot(canvas) {
|
|
|
1884
1932
|
onPointerMissed,
|
|
1885
1933
|
stages
|
|
1886
1934
|
} = props;
|
|
1887
|
-
let state = store.getState();
|
|
1935
|
+
let state = store.getState();
|
|
1888
1936
|
|
|
1937
|
+
// Set up renderer (one time only!)
|
|
1889
1938
|
let gl = state.gl;
|
|
1890
1939
|
if (!state.gl) state.set({
|
|
1891
1940
|
gl: gl = createRendererInstance(glConfig, canvas)
|
|
1892
|
-
});
|
|
1941
|
+
});
|
|
1893
1942
|
|
|
1943
|
+
// Set up raycaster (one time only!)
|
|
1894
1944
|
let raycaster = state.raycaster;
|
|
1895
1945
|
if (!raycaster) state.set({
|
|
1896
1946
|
raycaster: raycaster = new THREE.Raycaster()
|
|
1897
|
-
});
|
|
1947
|
+
});
|
|
1898
1948
|
|
|
1949
|
+
// Set raycaster options
|
|
1899
1950
|
const {
|
|
1900
1951
|
params,
|
|
1901
1952
|
...options
|
|
1902
1953
|
} = raycastOptions || {};
|
|
1903
|
-
if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, {
|
|
1954
|
+
if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, {
|
|
1955
|
+
...options
|
|
1904
1956
|
});
|
|
1905
1957
|
if (!is.equ(params, raycaster.params, shallowLoose)) applyProps(raycaster, {
|
|
1906
|
-
params: {
|
|
1958
|
+
params: {
|
|
1959
|
+
...raycaster.params,
|
|
1907
1960
|
...params
|
|
1908
1961
|
}
|
|
1909
|
-
});
|
|
1962
|
+
});
|
|
1910
1963
|
|
|
1911
|
-
|
|
1964
|
+
// Create default camera, don't overwrite any user-set state
|
|
1965
|
+
if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
|
|
1966
|
+
lastCamera = cameraOptions;
|
|
1912
1967
|
const isCamera = cameraOptions instanceof THREE.Camera;
|
|
1913
1968
|
const camera = isCamera ? cameraOptions : orthographic ? new THREE.OrthographicCamera(0, 0, 0, 0, 0.1, 1000) : new THREE.PerspectiveCamera(75, 0, 0.1, 1000);
|
|
1914
|
-
|
|
1915
1969
|
if (!isCamera) {
|
|
1916
1970
|
camera.position.z = 5;
|
|
1917
|
-
if (cameraOptions) applyProps(camera, cameraOptions);
|
|
1918
|
-
|
|
1919
|
-
if (!(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
|
|
1971
|
+
if (cameraOptions) applyProps(camera, cameraOptions);
|
|
1972
|
+
// Always look at center by default
|
|
1973
|
+
if (!state.camera && !(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
|
|
1920
1974
|
}
|
|
1921
|
-
|
|
1922
1975
|
state.set({
|
|
1923
1976
|
camera
|
|
1924
1977
|
});
|
|
1925
|
-
} // Set up XR (one time only!)
|
|
1926
1978
|
|
|
1979
|
+
// Configure raycaster
|
|
1980
|
+
// https://github.com/pmndrs/react-xr/issues/300
|
|
1981
|
+
raycaster.camera = camera;
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
// Set up scene (one time only!)
|
|
1985
|
+
if (!state.scene) {
|
|
1986
|
+
let scene;
|
|
1987
|
+
if (sceneOptions instanceof THREE.Scene) {
|
|
1988
|
+
scene = sceneOptions;
|
|
1989
|
+
prepare(scene, store, '', {});
|
|
1990
|
+
} else {
|
|
1991
|
+
scene = new THREE.Scene();
|
|
1992
|
+
prepare(scene, store, '', {});
|
|
1993
|
+
if (sceneOptions) applyProps(scene, sceneOptions);
|
|
1994
|
+
}
|
|
1995
|
+
state.set({
|
|
1996
|
+
scene
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1927
1999
|
|
|
2000
|
+
// Set up XR (one time only!)
|
|
1928
2001
|
if (!state.xr) {
|
|
1929
2002
|
// Handle frame behavior in WebXR
|
|
1930
2003
|
const handleXRFrame = (timestamp, frame) => {
|
|
1931
2004
|
const state = store.getState();
|
|
1932
2005
|
if (state.frameloop === 'never') return;
|
|
1933
2006
|
advance(timestamp, true, state, frame);
|
|
1934
|
-
};
|
|
1935
|
-
|
|
2007
|
+
};
|
|
1936
2008
|
|
|
2009
|
+
// Toggle render switching on session
|
|
1937
2010
|
const handleSessionChange = () => {
|
|
1938
2011
|
const state = store.getState();
|
|
1939
2012
|
state.gl.xr.enabled = state.gl.xr.isPresenting;
|
|
1940
2013
|
state.gl.xr.setAnimationLoop(state.gl.xr.isPresenting ? handleXRFrame : null);
|
|
1941
2014
|
if (!state.gl.xr.isPresenting) invalidate(state);
|
|
1942
|
-
};
|
|
1943
|
-
|
|
2015
|
+
};
|
|
1944
2016
|
|
|
2017
|
+
// WebXR session manager
|
|
1945
2018
|
const xr = {
|
|
1946
2019
|
connect() {
|
|
1947
2020
|
const gl = store.getState().gl;
|
|
1948
2021
|
gl.xr.addEventListener('sessionstart', handleSessionChange);
|
|
1949
2022
|
gl.xr.addEventListener('sessionend', handleSessionChange);
|
|
1950
2023
|
},
|
|
1951
|
-
|
|
1952
2024
|
disconnect() {
|
|
1953
2025
|
const gl = store.getState().gl;
|
|
1954
2026
|
gl.xr.removeEventListener('sessionstart', handleSessionChange);
|
|
1955
2027
|
gl.xr.removeEventListener('sessionend', handleSessionChange);
|
|
1956
2028
|
}
|
|
2029
|
+
};
|
|
1957
2030
|
|
|
1958
|
-
|
|
1959
|
-
|
|
2031
|
+
// Subscribe to WebXR session events
|
|
1960
2032
|
if (gl.xr) xr.connect();
|
|
1961
2033
|
state.set({
|
|
1962
2034
|
xr
|
|
1963
2035
|
});
|
|
1964
|
-
}
|
|
1965
|
-
|
|
2036
|
+
}
|
|
1966
2037
|
|
|
2038
|
+
// Set shadowmap
|
|
1967
2039
|
if (gl.shadowMap) {
|
|
1968
|
-
const
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
gl.shadowMap.
|
|
1973
|
-
|
|
1974
|
-
|
|
2040
|
+
const oldEnabled = gl.shadowMap.enabled;
|
|
2041
|
+
const oldType = gl.shadowMap.type;
|
|
2042
|
+
gl.shadowMap.enabled = !!shadows;
|
|
2043
|
+
if (is.boo(shadows)) {
|
|
2044
|
+
gl.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
2045
|
+
} else if (is.str(shadows)) {
|
|
2046
|
+
var _types$shadows;
|
|
2047
|
+
const types = {
|
|
2048
|
+
basic: THREE.BasicShadowMap,
|
|
2049
|
+
percentage: THREE.PCFShadowMap,
|
|
2050
|
+
soft: THREE.PCFSoftShadowMap,
|
|
2051
|
+
variance: THREE.VSMShadowMap
|
|
2052
|
+
};
|
|
2053
|
+
gl.shadowMap.type = (_types$shadows = types[shadows]) != null ? _types$shadows : THREE.PCFSoftShadowMap;
|
|
2054
|
+
} else if (is.obj(shadows)) {
|
|
2055
|
+
Object.assign(gl.shadowMap, shadows);
|
|
1975
2056
|
}
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
2057
|
+
if (oldEnabled !== gl.shadowMap.enabled || oldType !== gl.shadowMap.type) gl.shadowMap.needsUpdate = true;
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
// Safely set color management if available.
|
|
2061
|
+
// Avoid accessing THREE.ColorManagement to play nice with older versions
|
|
2062
|
+
const ColorManagement = getColorManagement();
|
|
2063
|
+
if (ColorManagement) {
|
|
2064
|
+
if ('enabled' in ColorManagement) ColorManagement.enabled = !legacy;else if ('legacyMode' in ColorManagement) ColorManagement.legacyMode = legacy;
|
|
2065
|
+
}
|
|
1982
2066
|
|
|
2067
|
+
// Set color space and tonemapping preferences
|
|
2068
|
+
if (!configured) {
|
|
2069
|
+
const LinearEncoding = 3000;
|
|
2070
|
+
const sRGBEncoding = 3001;
|
|
2071
|
+
applyProps(gl, {
|
|
2072
|
+
outputEncoding: linear ? LinearEncoding : sRGBEncoding,
|
|
2073
|
+
toneMapping: flat ? THREE.NoToneMapping : THREE.ACESFilmicToneMapping
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
// Update color management state
|
|
1983
2078
|
if (state.legacy !== legacy) state.set(() => ({
|
|
1984
2079
|
legacy
|
|
1985
2080
|
}));
|
|
@@ -1988,42 +2083,43 @@ function createRoot(canvas) {
|
|
|
1988
2083
|
}));
|
|
1989
2084
|
if (state.flat !== flat) state.set(() => ({
|
|
1990
2085
|
flat
|
|
1991
|
-
}));
|
|
1992
|
-
|
|
1993
|
-
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, gl, shallowLoose)) applyProps(gl, glConfig); // Store events internally
|
|
2086
|
+
}));
|
|
1994
2087
|
|
|
2088
|
+
// Set gl props
|
|
2089
|
+
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, gl, shallowLoose)) applyProps(gl, glConfig);
|
|
2090
|
+
// Store events internally
|
|
1995
2091
|
if (events && !state.events.handlers) state.set({
|
|
1996
2092
|
events: events(store)
|
|
1997
|
-
});
|
|
1998
|
-
|
|
1999
|
-
if (dpr && state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr); // Check size, allow it to take on container bounds initially
|
|
2000
|
-
|
|
2093
|
+
});
|
|
2094
|
+
// Check size, allow it to take on container bounds initially
|
|
2001
2095
|
const size = computeInitialSize(canvas, propsSize);
|
|
2002
|
-
|
|
2003
2096
|
if (!is.equ(size, state.size, shallowLoose)) {
|
|
2004
2097
|
state.setSize(size.width, size.height, size.top, size.left);
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2098
|
+
}
|
|
2099
|
+
// Check pixelratio
|
|
2100
|
+
if (dpr && state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr);
|
|
2101
|
+
// Check frameloop
|
|
2102
|
+
if (state.frameloop !== frameloop) state.setFrameloop(frameloop);
|
|
2103
|
+
// Check pointer missed
|
|
2010
2104
|
if (!state.onPointerMissed) state.set({
|
|
2011
2105
|
onPointerMissed
|
|
2012
|
-
});
|
|
2013
|
-
|
|
2106
|
+
});
|
|
2107
|
+
// Check performance
|
|
2014
2108
|
if (performance && !is.equ(performance, state.performance, shallowLoose)) state.set(state => ({
|
|
2015
|
-
performance: {
|
|
2109
|
+
performance: {
|
|
2110
|
+
...state.performance,
|
|
2016
2111
|
...performance
|
|
2017
2112
|
}
|
|
2018
|
-
}));
|
|
2113
|
+
}));
|
|
2019
2114
|
|
|
2020
|
-
|
|
2115
|
+
// Create update stages. Only do this once on init
|
|
2116
|
+
if (state.internal.stages.length === 0) createStages(stages, store);
|
|
2021
2117
|
|
|
2118
|
+
// Set locals
|
|
2022
2119
|
onCreated = onCreatedCallback;
|
|
2023
2120
|
configured = true;
|
|
2024
2121
|
return this;
|
|
2025
2122
|
},
|
|
2026
|
-
|
|
2027
2123
|
render(children) {
|
|
2028
2124
|
// The root has to be configured before it can be rendered
|
|
2029
2125
|
if (!configured) this.configure();
|
|
@@ -2035,21 +2131,17 @@ function createRoot(canvas) {
|
|
|
2035
2131
|
}), fiber, null, () => undefined);
|
|
2036
2132
|
return store;
|
|
2037
2133
|
},
|
|
2038
|
-
|
|
2039
2134
|
unmount() {
|
|
2040
2135
|
unmountComponentAtNode(canvas);
|
|
2041
2136
|
}
|
|
2042
|
-
|
|
2043
2137
|
};
|
|
2044
2138
|
}
|
|
2045
|
-
|
|
2046
2139
|
function render(children, canvas, config) {
|
|
2047
2140
|
console.warn('R3F.render is no longer supported in React 18. Use createRoot instead!');
|
|
2048
2141
|
const root = createRoot(canvas);
|
|
2049
2142
|
root.configure(config);
|
|
2050
2143
|
return root.render(children);
|
|
2051
2144
|
}
|
|
2052
|
-
|
|
2053
2145
|
function Provider({
|
|
2054
2146
|
store,
|
|
2055
2147
|
children,
|
|
@@ -2057,28 +2149,28 @@ function Provider({
|
|
|
2057
2149
|
rootElement
|
|
2058
2150
|
}) {
|
|
2059
2151
|
useIsomorphicLayoutEffect(() => {
|
|
2060
|
-
const state = store.getState();
|
|
2061
|
-
|
|
2152
|
+
const state = store.getState();
|
|
2153
|
+
// Flag the canvas active, rendering will now begin
|
|
2062
2154
|
state.set(state => ({
|
|
2063
|
-
internal: {
|
|
2155
|
+
internal: {
|
|
2156
|
+
...state.internal,
|
|
2064
2157
|
active: true
|
|
2065
2158
|
}
|
|
2066
|
-
}));
|
|
2067
|
-
|
|
2068
|
-
if (onCreated) onCreated(state);
|
|
2159
|
+
}));
|
|
2160
|
+
// Notifiy that init is completed, the scene graph exists, but nothing has yet rendered
|
|
2161
|
+
if (onCreated) onCreated(state);
|
|
2162
|
+
// Connect events to the targets parent, this is done to ensure events are registered on
|
|
2069
2163
|
// a shared target, and not on the canvas itself
|
|
2070
|
-
|
|
2071
|
-
|
|
2164
|
+
if (!store.getState().events.connected) state.events.connect == null ? void 0 : state.events.connect(rootElement);
|
|
2165
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2072
2166
|
}, []);
|
|
2073
2167
|
return /*#__PURE__*/React.createElement(context.Provider, {
|
|
2074
2168
|
value: store
|
|
2075
2169
|
}, children);
|
|
2076
2170
|
}
|
|
2077
|
-
|
|
2078
2171
|
function unmountComponentAtNode(canvas, callback) {
|
|
2079
|
-
const root =
|
|
2172
|
+
const root = _roots.get(canvas);
|
|
2080
2173
|
const fiber = root == null ? void 0 : root.fiber;
|
|
2081
|
-
|
|
2082
2174
|
if (fiber) {
|
|
2083
2175
|
const state = root == null ? void 0 : root.store.getState();
|
|
2084
2176
|
if (state) state.internal.active = false;
|
|
@@ -2087,13 +2179,12 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
2087
2179
|
setTimeout(() => {
|
|
2088
2180
|
try {
|
|
2089
2181
|
var _state$gl, _state$gl$renderLists, _state$gl2, _state$gl3;
|
|
2090
|
-
|
|
2091
2182
|
state.events.disconnect == null ? void 0 : state.events.disconnect();
|
|
2092
2183
|
(_state$gl = state.gl) == null ? void 0 : (_state$gl$renderLists = _state$gl.renderLists) == null ? void 0 : _state$gl$renderLists.dispose == null ? void 0 : _state$gl$renderLists.dispose();
|
|
2093
2184
|
(_state$gl2 = state.gl) == null ? void 0 : _state$gl2.forceContextLoss == null ? void 0 : _state$gl2.forceContextLoss();
|
|
2094
2185
|
if ((_state$gl3 = state.gl) != null && _state$gl3.xr) state.xr.disconnect();
|
|
2095
2186
|
dispose(state.scene);
|
|
2096
|
-
|
|
2187
|
+
_roots.delete(canvas);
|
|
2097
2188
|
if (callback) callback(canvas);
|
|
2098
2189
|
} catch (e) {
|
|
2099
2190
|
/* ... */
|
|
@@ -2103,25 +2194,22 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
2103
2194
|
});
|
|
2104
2195
|
}
|
|
2105
2196
|
}
|
|
2106
|
-
|
|
2107
2197
|
function createPortal(children, container, state) {
|
|
2108
2198
|
return /*#__PURE__*/React.createElement(Portal, {
|
|
2109
|
-
key: container.uuid,
|
|
2110
2199
|
children: children,
|
|
2111
2200
|
container: container,
|
|
2112
2201
|
state: state
|
|
2113
2202
|
});
|
|
2114
2203
|
}
|
|
2115
|
-
|
|
2116
2204
|
function Portal({
|
|
2117
2205
|
state = {},
|
|
2118
2206
|
children,
|
|
2119
2207
|
container
|
|
2120
2208
|
}) {
|
|
2121
|
-
/** This has to be a component because it would not be able to call useThree/useStore otherwise since
|
|
2122
|
-
* if this is our environment, then we are not in r3f's renderer but in react-dom, it would trigger
|
|
2123
|
-
* the "R3F hooks can only be used within the Canvas component!" warning:
|
|
2124
|
-
* <Canvas>
|
|
2209
|
+
/** This has to be a component because it would not be able to call useThree/useStore otherwise since
|
|
2210
|
+
* if this is our environment, then we are not in r3f's renderer but in react-dom, it would trigger
|
|
2211
|
+
* the "R3F hooks can only be used within the Canvas component!" warning:
|
|
2212
|
+
* <Canvas>
|
|
2125
2213
|
* {createPortal(...)} */
|
|
2126
2214
|
const {
|
|
2127
2215
|
events,
|
|
@@ -2131,32 +2219,20 @@ function Portal({
|
|
|
2131
2219
|
const previousRoot = useStore();
|
|
2132
2220
|
const [raycaster] = React.useState(() => new THREE.Raycaster());
|
|
2133
2221
|
const [pointer] = React.useState(() => new THREE.Vector2());
|
|
2134
|
-
const inject =
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
Object.keys(rootState).forEach(key => {
|
|
2142
|
-
if ( // Some props should be off-limits
|
|
2143
|
-
privateKeys.includes(key) || // Otherwise filter out the props that are different and let the inject layer take precedence
|
|
2144
|
-
rootState[key] !== injectState[key]) {
|
|
2145
|
-
delete intersect[key];
|
|
2146
|
-
}
|
|
2147
|
-
});
|
|
2148
|
-
let viewport = undefined;
|
|
2149
|
-
|
|
2150
|
-
if (injectState && size) {
|
|
2151
|
-
const camera = injectState.camera; // Calculate the override viewport, if present
|
|
2152
|
-
|
|
2153
|
-
viewport = rootState.viewport.getCurrentViewport(camera, new THREE.Vector3(), size); // Update the portal camera, if it differs from the previous layer
|
|
2154
|
-
|
|
2222
|
+
const inject = useMutableCallback((rootState, injectState) => {
|
|
2223
|
+
let viewport;
|
|
2224
|
+
if (injectState.camera && size) {
|
|
2225
|
+
const camera = injectState.camera;
|
|
2226
|
+
// Calculate the override viewport, if present
|
|
2227
|
+
viewport = rootState.viewport.getCurrentViewport(camera, new THREE.Vector3(), size);
|
|
2228
|
+
// Update the portal camera, if it differs from the previous layer
|
|
2155
2229
|
if (camera !== rootState.camera) updateCamera(camera, size);
|
|
2156
2230
|
}
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
...
|
|
2231
|
+
return {
|
|
2232
|
+
// The intersect consists of the previous root state
|
|
2233
|
+
...rootState,
|
|
2234
|
+
get: injectState.get,
|
|
2235
|
+
set: injectState.set,
|
|
2160
2236
|
// Portals have their own scene, which forms the root, a raycaster and a pointer
|
|
2161
2237
|
scene: container,
|
|
2162
2238
|
raycaster,
|
|
@@ -2165,72 +2241,194 @@ function Portal({
|
|
|
2165
2241
|
// Their previous root is the layer before it
|
|
2166
2242
|
previousRoot,
|
|
2167
2243
|
// Events, size and viewport can be overridden by the inject layer
|
|
2168
|
-
events: {
|
|
2169
|
-
...
|
|
2244
|
+
events: {
|
|
2245
|
+
...rootState.events,
|
|
2246
|
+
...injectState.events,
|
|
2170
2247
|
...events
|
|
2171
2248
|
},
|
|
2172
|
-
size: {
|
|
2249
|
+
size: {
|
|
2250
|
+
...rootState.size,
|
|
2173
2251
|
...size
|
|
2174
2252
|
},
|
|
2175
|
-
viewport: {
|
|
2253
|
+
viewport: {
|
|
2254
|
+
...rootState.viewport,
|
|
2176
2255
|
...viewport
|
|
2177
2256
|
},
|
|
2178
|
-
...rest
|
|
2179
|
-
};
|
|
2180
|
-
}, [state]);
|
|
2181
|
-
const [usePortalStore] = React.useState(() => {
|
|
2182
|
-
// Create a mirrored store, based on the previous root with a few overrides ...
|
|
2183
|
-
const previousState = previousRoot.getState();
|
|
2184
|
-
const store = create((set, get) => ({ ...previousState,
|
|
2185
|
-
scene: container,
|
|
2186
|
-
raycaster,
|
|
2187
|
-
pointer,
|
|
2188
|
-
mouse: pointer,
|
|
2189
|
-
previousRoot,
|
|
2190
|
-
events: { ...previousState.events,
|
|
2191
|
-
...events
|
|
2192
|
-
},
|
|
2193
|
-
size: { ...previousState.size,
|
|
2194
|
-
...size
|
|
2195
|
-
},
|
|
2196
|
-
...rest,
|
|
2197
|
-
// Set and get refer to this root-state
|
|
2198
|
-
set,
|
|
2199
|
-
get,
|
|
2200
2257
|
// Layers are allowed to override events
|
|
2201
|
-
setEvents: events => set(state => ({
|
|
2202
|
-
|
|
2258
|
+
setEvents: events => injectState.set(state => ({
|
|
2259
|
+
...state,
|
|
2260
|
+
events: {
|
|
2261
|
+
...state.events,
|
|
2203
2262
|
...events
|
|
2204
2263
|
}
|
|
2205
2264
|
}))
|
|
2206
|
-
}
|
|
2207
|
-
return store;
|
|
2265
|
+
};
|
|
2208
2266
|
});
|
|
2209
|
-
React.
|
|
2267
|
+
const usePortalStore = React.useMemo(() => {
|
|
2268
|
+
const store = create((set, get) => ({
|
|
2269
|
+
...rest,
|
|
2270
|
+
set,
|
|
2271
|
+
get
|
|
2272
|
+
}));
|
|
2273
|
+
|
|
2210
2274
|
// Subscribe to previous root-state and copy changes over to the mirrored portal-state
|
|
2211
|
-
const
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
}, []);
|
|
2217
|
-
React.useEffect(() => {
|
|
2218
|
-
usePortalStore.setState(injectState => inject(previousRoot.getState(), injectState));
|
|
2219
|
-
}, [inject]);
|
|
2275
|
+
const onMutate = prev => store.setState(state => inject.current(prev, state));
|
|
2276
|
+
onMutate(previousRoot.getState());
|
|
2277
|
+
previousRoot.subscribe(onMutate);
|
|
2278
|
+
return store;
|
|
2279
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2280
|
+
}, [previousRoot, container]);
|
|
2220
2281
|
return /*#__PURE__*/React.createElement(React.Fragment, null, reconciler.createPortal( /*#__PURE__*/React.createElement(context.Provider, {
|
|
2221
2282
|
value: usePortalStore
|
|
2222
2283
|
}, children), usePortalStore, null));
|
|
2223
2284
|
}
|
|
2224
|
-
|
|
2225
2285
|
reconciler.injectIntoDevTools({
|
|
2226
2286
|
bundleType: process.env.NODE_ENV === 'production' ? 0 : 1,
|
|
2227
2287
|
rendererPackageName: '@react-three/fiber',
|
|
2228
2288
|
version: React.version
|
|
2229
2289
|
});
|
|
2230
2290
|
|
|
2231
|
-
|
|
2232
|
-
|
|
2291
|
+
function createSubs(callback, subs) {
|
|
2292
|
+
const sub = {
|
|
2293
|
+
callback
|
|
2294
|
+
};
|
|
2295
|
+
subs.add(sub);
|
|
2296
|
+
return () => void subs.delete(sub);
|
|
2297
|
+
}
|
|
2298
|
+
const globalEffects = new Set();
|
|
2299
|
+
const globalAfterEffects = new Set();
|
|
2300
|
+
const globalTailEffects = new Set();
|
|
2301
|
+
|
|
2302
|
+
/**
|
|
2303
|
+
* Adds a global render callback which is called each frame.
|
|
2304
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addEffect
|
|
2305
|
+
*/
|
|
2306
|
+
const addEffect = callback => createSubs(callback, globalEffects);
|
|
2307
|
+
|
|
2308
|
+
/**
|
|
2309
|
+
* Adds a global after-render callback which is called each frame.
|
|
2310
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addAfterEffect
|
|
2311
|
+
*/
|
|
2312
|
+
const addAfterEffect = callback => createSubs(callback, globalAfterEffects);
|
|
2313
|
+
|
|
2314
|
+
/**
|
|
2315
|
+
* Adds a global callback which is called when rendering stops.
|
|
2316
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addTail
|
|
2317
|
+
*/
|
|
2318
|
+
const addTail = callback => createSubs(callback, globalTailEffects);
|
|
2319
|
+
function run(effects, timestamp) {
|
|
2320
|
+
if (!effects.size) return;
|
|
2321
|
+
for (const {
|
|
2322
|
+
callback
|
|
2323
|
+
} of effects.values()) {
|
|
2324
|
+
callback(timestamp);
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
function flushGlobalEffects(type, timestamp) {
|
|
2328
|
+
switch (type) {
|
|
2329
|
+
case 'before':
|
|
2330
|
+
return run(globalEffects, timestamp);
|
|
2331
|
+
case 'after':
|
|
2332
|
+
return run(globalAfterEffects, timestamp);
|
|
2333
|
+
case 'tail':
|
|
2334
|
+
return run(globalTailEffects, timestamp);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
function update(timestamp, state, frame) {
|
|
2338
|
+
// Run local effects
|
|
2339
|
+
let delta = state.clock.getDelta();
|
|
2340
|
+
// In frameloop='never' mode, clock times are updated using the provided timestamp
|
|
2341
|
+
if (state.frameloop === 'never' && typeof timestamp === 'number') {
|
|
2342
|
+
delta = timestamp - state.clock.elapsedTime;
|
|
2343
|
+
state.clock.oldTime = state.clock.elapsedTime;
|
|
2344
|
+
state.clock.elapsedTime = timestamp;
|
|
2345
|
+
} else {
|
|
2346
|
+
delta = Math.max(Math.min(delta, state.internal.maxDelta), 0);
|
|
2347
|
+
}
|
|
2348
|
+
// Call subscribers (useUpdate)
|
|
2349
|
+
for (const stage of state.internal.stages) {
|
|
2350
|
+
stage.frame(delta, frame);
|
|
2351
|
+
}
|
|
2352
|
+
state.internal.frames = Math.max(0, state.internal.frames - 1);
|
|
2353
|
+
return state.frameloop === 'always' ? 1 : state.internal.frames;
|
|
2354
|
+
}
|
|
2355
|
+
let running = false;
|
|
2356
|
+
let useFrameInProgress = false;
|
|
2357
|
+
let repeat;
|
|
2358
|
+
let frame;
|
|
2359
|
+
let state;
|
|
2360
|
+
function loop(timestamp) {
|
|
2361
|
+
frame = requestAnimationFrame(loop);
|
|
2362
|
+
running = true;
|
|
2363
|
+
repeat = 0;
|
|
2364
|
+
|
|
2365
|
+
// Run effects
|
|
2366
|
+
flushGlobalEffects('before', timestamp);
|
|
2367
|
+
|
|
2368
|
+
// Render all roots
|
|
2369
|
+
useFrameInProgress = true;
|
|
2370
|
+
for (const root of _roots.values()) {
|
|
2371
|
+
var _state$gl$xr;
|
|
2372
|
+
state = root.store.getState();
|
|
2373
|
+
|
|
2374
|
+
// If the frameloop is invalidated, do not run another frame
|
|
2375
|
+
if (state.internal.active && (state.frameloop === 'always' || state.internal.frames > 0) && !((_state$gl$xr = state.gl.xr) != null && _state$gl$xr.isPresenting)) {
|
|
2376
|
+
repeat += update(timestamp, state);
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
useFrameInProgress = true;
|
|
2380
|
+
|
|
2381
|
+
// Run after-effects
|
|
2382
|
+
flushGlobalEffects('after', timestamp);
|
|
2383
|
+
|
|
2384
|
+
// Stop the loop if nothing invalidates it
|
|
2385
|
+
if (repeat === 0) {
|
|
2386
|
+
// Tail call effects, they are called when rendering stops
|
|
2387
|
+
flushGlobalEffects('tail', timestamp);
|
|
2388
|
+
|
|
2389
|
+
// Flag end of operation
|
|
2390
|
+
running = false;
|
|
2391
|
+
return cancelAnimationFrame(frame);
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
/**
|
|
2396
|
+
* Invalidates the view, requesting a frame to be rendered. Will globally invalidate unless passed a root's state.
|
|
2397
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#invalidate
|
|
2398
|
+
*/
|
|
2399
|
+
function invalidate(state, frames = 1) {
|
|
2400
|
+
var _state$gl$xr2;
|
|
2401
|
+
if (!state) return _roots.forEach(root => invalidate(root.store.getState(), frames));
|
|
2402
|
+
if ((_state$gl$xr2 = state.gl.xr) != null && _state$gl$xr2.isPresenting || !state.internal.active || state.frameloop === 'never') return;
|
|
2403
|
+
if (frames > 1) {
|
|
2404
|
+
// legacy support for people using frames parameters
|
|
2405
|
+
// Increase frames, do not go higher than 60
|
|
2406
|
+
state.internal.frames = Math.min(60, state.internal.frames + frames);
|
|
2407
|
+
} else {
|
|
2408
|
+
if (useFrameInProgress) {
|
|
2409
|
+
//called from within a useFrame, it means the user wants an additional frame
|
|
2410
|
+
state.internal.frames = 2;
|
|
2411
|
+
} else {
|
|
2412
|
+
//the user need a new frame, no need to increment further than 1
|
|
2413
|
+
state.internal.frames = 1;
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
// If the render-loop isn't active, start it
|
|
2418
|
+
if (!running) {
|
|
2419
|
+
running = true;
|
|
2420
|
+
requestAnimationFrame(loop);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
/**
|
|
2425
|
+
* Advances the frameloop and runs render effects, useful for when manually rendering via `frameloop="never"`.
|
|
2426
|
+
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#advance
|
|
2233
2427
|
*/
|
|
2234
|
-
|
|
2428
|
+
function advance(timestamp, runGlobalEffects = true, state, frame) {
|
|
2429
|
+
if (runGlobalEffects) flushGlobalEffects('before', timestamp);
|
|
2430
|
+
if (!state) for (const root of _roots.values()) update(timestamp, root.store.getState());else update(timestamp, state, frame);
|
|
2431
|
+
if (runGlobalEffects) flushGlobalEffects('after', timestamp);
|
|
2432
|
+
}
|
|
2235
2433
|
|
|
2236
|
-
export {
|
|
2434
|
+
export { useStore as A, Block as B, useThree as C, useFrame as D, ErrorBoundary as E, FixedStage as F, useUpdate as G, useGraph as H, useLoader as I, Stage as S, _roots as _, useMutableCallback as a, useIsomorphicLayoutEffect as b, createEvents as c, createRoot as d, extend as e, unmountComponentAtNode as f, flushGlobalEffects as g, addEffect as h, isRef as i, addAfterEffect as j, addTail as k, invalidate as l, advance as m, render as n, createPortal as o, Stages as p, context as q, reconciler as r, applyProps as s, threeTypes as t, useBridge as u, getRootState as v, dispose as w, act as x, buildGraph as y, useInstanceHandle as z };
|