@react-three/fiber 8.0.0-beta-04 → 8.0.0-beta.1

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.
@@ -41,29 +41,38 @@ const pick = (obj, keys) => filterKeys(obj, false, ...keys);
41
41
  * Clones an object and prunes or omits keys.
42
42
  */
43
43
 
44
- const omit = (obj, keys) => filterKeys(obj, true, ...keys); // A collection of compare functions
45
-
44
+ const omit = (obj, keys) => filterKeys(obj, true, ...keys);
45
+ // A collection of compare functions
46
46
  const is = {
47
47
  obj: a => a === Object(a) && !is.arr(a) && typeof a !== 'function',
48
48
  fun: a => typeof a === 'function',
49
49
  str: a => typeof a === 'string',
50
50
  num: a => typeof a === 'number',
51
+ boo: a => typeof a === 'boolean',
51
52
  und: a => a === void 0,
52
53
  arr: a => Array.isArray(a),
53
54
 
54
- equ(a, b) {
55
+ equ(a, b, {
56
+ arrays = 'shallow',
57
+ objects = 'reference',
58
+ strict = true
59
+ } = {}) {
55
60
  // Wrong type or one of the two undefined, doesn't match
56
61
  if (typeof a !== typeof b || !!a !== !!b) return false; // Atomic, just compare a against b
57
62
 
58
- if (is.str(a) || is.num(a) || is.obj(a)) return a === b; // Array, shallow compare first to see if it's a match
63
+ if (is.str(a) || is.num(a)) return a === b;
64
+ const isObj = is.obj(a);
65
+ if (isObj && objects === 'reference') return a === b;
66
+ const isArr = is.arr(a);
67
+ if (isArr && arrays === 'reference') return a === b; // Array or Object, shallow compare first to see if it's a match
59
68
 
60
- if (is.arr(a) && a == b) return true; // Last resort, go through keys
69
+ if ((isArr || isObj) && a == b) return true; // Last resort, go through keys
61
70
 
62
71
  let i;
63
72
 
64
73
  for (i in a) if (!(i in b)) return false;
65
74
 
66
- for (i in b) if (a[i] !== b[i]) return false;
75
+ for (i in strict ? b : a) if (a[i] !== b[i]) return false;
67
76
 
68
77
  return is.und(i) ? a === b : true;
69
78
  }
@@ -154,15 +163,8 @@ function detach(parent, child, type) {
154
163
  const [, detach] = type;
155
164
  if (is.str(detach)) parent[detach](child);else if (is.fun(detach)) detach(parent, child);
156
165
  }
157
- } // Shallow check arrays, but check objects atomically
158
-
159
- function checkShallow(a, b) {
160
- if (is.arr(a) && is.equ(a, b)) return true;
161
- if (a === b) return true;
162
- return false;
163
166
  } // This function prepares a set of changes to be applied to the instance
164
167
 
165
-
166
168
  function diffProps(instance, {
167
169
  children: cN,
168
170
  key: kN,
@@ -194,7 +196,7 @@ function diffProps(instance, {
194
196
  // Bail out on primitive object
195
197
  if ((_instance$__r3f2 = instance.__r3f) != null && _instance$__r3f2.primitive && key === 'object') return; // When props match bail out
196
198
 
197
- if (checkShallow(value, previous[key])) return; // Collect handlers and bail out
199
+ if (is.equ(value, previous[key])) return; // Collect handlers and bail out
198
200
 
199
201
  if (/^on(Pointer|Click|DoubleClick|ContextMenu|Wheel)/.test(key)) return changes.push([key, value, true, []]); // Split dashed props
200
202
 
@@ -391,7 +393,7 @@ function createEvents(store) {
391
393
  /** Sets up defaultRaycaster */
392
394
 
393
395
  function prepareRay(event) {
394
- var _raycaster$computeOff;
396
+ var _customOffsets$offset, _customOffsets$offset2, _customOffsets$width, _customOffsets$height;
395
397
 
396
398
  const state = store.getState();
397
399
  const {
@@ -402,14 +404,11 @@ function createEvents(store) {
402
404
  } = state; // https://github.com/pmndrs/react-three-fiber/pull/782
403
405
  // Events trigger outside of canvas when moved
404
406
 
405
- const {
406
- offsetX,
407
- offsetY
408
- } = (_raycaster$computeOff = raycaster.computeOffsets == null ? void 0 : raycaster.computeOffsets(event, state)) != null ? _raycaster$computeOff : event;
409
- const {
410
- width,
411
- height
412
- } = size;
407
+ const customOffsets = raycaster.computeOffsets == null ? void 0 : raycaster.computeOffsets(event, state);
408
+ const offsetX = (_customOffsets$offset = customOffsets == null ? void 0 : customOffsets.offsetX) != null ? _customOffsets$offset : event.offsetX;
409
+ const offsetY = (_customOffsets$offset2 = customOffsets == null ? void 0 : customOffsets.offsetY) != null ? _customOffsets$offset2 : event.offsetY;
410
+ const width = (_customOffsets$width = customOffsets == null ? void 0 : customOffsets.width) != null ? _customOffsets$width : size.width;
411
+ const height = (_customOffsets$height = customOffsets == null ? void 0 : customOffsets.height) != null ? _customOffsets$height : size.height;
413
412
  mouse.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
414
413
  raycaster.setFromCamera(mouse, camera);
415
414
  }
@@ -968,10 +967,13 @@ function createRenderer(roots, getEventPriority) {
968
967
 
969
968
  instance.__r3f.objects = [];
970
969
  removeChild(parent, instance);
971
- appendChild(parent, newInstance) // This evil hack switches the react-internal fiber node
972
- // https://github.com/facebook/react/issues/14983
973
- // https://github.com/facebook/react/pull/15021
974
- ;
970
+ appendChild(parent, newInstance); // Re-bind event handlers
971
+
972
+ if (newInstance.raycast && newInstance.__r3f.eventCount) {
973
+ const rootState = newInstance.__r3f.root.getState();
974
+
975
+ rootState.internal.interaction.push(newInstance);
976
+ } // This evil hack switches the react-internal fiber node
975
977
  [fiber, fiber.alternate].forEach(fiber => {
976
978
  if (fiber !== null) {
977
979
  fiber.stateNode = newInstance;
@@ -1065,7 +1067,7 @@ function createRenderer(roots, getEventPriority) {
1065
1067
  isPrimaryRenderer: false,
1066
1068
  getCurrentEventPriority: () => getEventPriority ? getEventPriority() : DefaultEventPriority,
1067
1069
  // @ts-ignore
1068
- now: is.fun(performance.now) ? performance.now : is.fun(Date.now) ? Date.now : undefined,
1070
+ now: typeof performance !== 'undefined' && is.fun(performance.now) ? performance.now : is.fun(Date.now) ? Date.now : undefined,
1069
1071
  // @ts-ignore
1070
1072
  scheduleTimeout: is.fun(setTimeout) ? setTimeout : undefined,
1071
1073
  // @ts-ignore
@@ -1123,63 +1125,8 @@ const isRenderer = def => !!(def != null && def.render);
1123
1125
  const isOrthographicCamera = def => def && def.isOrthographicCamera;
1124
1126
  const context = /*#__PURE__*/React.createContext(null);
1125
1127
 
1126
- const createStore = (applyProps, invalidate, advance, props) => {
1127
- const {
1128
- gl,
1129
- size,
1130
- shadows = false,
1131
- linear = false,
1132
- flat = false,
1133
- orthographic = false,
1134
- frameloop = 'always',
1135
- dpr = [1, 2],
1136
- performance,
1137
- clock = new THREE.Clock(),
1138
- raycaster: raycastOptions,
1139
- camera: cameraOptions,
1140
- onPointerMissed
1141
- } = props; // Set shadowmap
1142
-
1143
- if (shadows) {
1144
- gl.shadowMap.enabled = true;
1145
- if (typeof shadows === 'object') Object.assign(gl.shadowMap, shadows);else gl.shadowMap.type = THREE.PCFSoftShadowMap;
1146
- } // Set color preferences
1147
-
1148
-
1149
- if (linear) gl.outputEncoding = THREE.LinearEncoding;
1150
- if (flat) gl.toneMapping = THREE.NoToneMapping; // clock.elapsedTime is updated using advance(timestamp)
1151
-
1152
- if (frameloop === 'never') {
1153
- clock.stop();
1154
- clock.elapsedTime = 0;
1155
- }
1156
-
1128
+ const createStore = (invalidate, advance) => {
1157
1129
  const rootState = create((set, get) => {
1158
- // Create custom raycaster
1159
- const raycaster = new THREE.Raycaster();
1160
- const {
1161
- params,
1162
- ...options
1163
- } = raycastOptions || {};
1164
- applyProps(raycaster, {
1165
- enabled: true,
1166
- ...options,
1167
- params: { ...raycaster.params,
1168
- ...params
1169
- }
1170
- }); // Create default camera
1171
-
1172
- const isCamera = cameraOptions instanceof THREE.Camera;
1173
- const camera = isCamera ? cameraOptions : orthographic ? new THREE.OrthographicCamera(0, 0, 0, 0, 0.1, 1000) : new THREE.PerspectiveCamera(75, 0, 0.1, 1000);
1174
-
1175
- if (!isCamera) {
1176
- camera.position.z = 5;
1177
- if (cameraOptions) applyProps(camera, cameraOptions); // Always look at center by default
1178
-
1179
- if (!(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
1180
- }
1181
-
1182
- const initialDpr = calculateDpr(dpr);
1183
1130
  const position = new THREE.Vector3();
1184
1131
  const defaultTarget = new THREE.Vector3();
1185
1132
  const tempTarget = new THREE.Vector3();
@@ -1223,60 +1170,34 @@ const createStore = (applyProps, invalidate, advance, props) => {
1223
1170
  performance: { ...state.performance,
1224
1171
  current
1225
1172
  }
1226
- })); // Handle frame behavior in WebXR
1227
-
1228
-
1229
- const handleXRFrame = timestamp => {
1230
- const state = get();
1231
- if (state.frameloop === 'never') return;
1232
- advance(timestamp, true);
1233
- }; // Toggle render switching on session
1234
-
1235
-
1236
- const handleSessionChange = () => {
1237
- gl.xr.enabled = gl.xr.isPresenting;
1238
- gl.setAnimationLoop(gl.xr.isPresenting ? handleXRFrame : null); // If exiting session, request frame
1239
-
1240
- if (!gl.xr.isPresenting) invalidate(get());
1241
- }; // WebXR session manager
1242
-
1243
-
1244
- const xr = {
1245
- connect() {
1246
- gl.xr.addEventListener('sessionstart', handleSessionChange);
1247
- gl.xr.addEventListener('sessionend', handleSessionChange);
1248
- },
1249
-
1250
- disconnect() {
1251
- gl.xr.removeEventListener('sessionstart', handleSessionChange);
1252
- gl.xr.removeEventListener('sessionend', handleSessionChange);
1253
- }
1254
-
1255
- }; // Subscribe to WebXR session events
1173
+ }));
1256
1174
 
1257
- if (gl.xr) xr.connect();
1258
1175
  return {
1259
- gl,
1176
+ // Mock objects that have to be configured
1177
+ gl: null,
1178
+ camera: null,
1179
+ raycaster: null,
1180
+ events: {
1181
+ connected: false
1182
+ },
1183
+ xr: null,
1260
1184
  set,
1261
1185
  get,
1262
1186
  invalidate: () => invalidate(get()),
1263
1187
  advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
1264
- linear,
1265
- flat,
1188
+ linear: false,
1189
+ flat: false,
1266
1190
  scene: prepare(new THREE.Scene()),
1267
- camera,
1268
1191
  controls: null,
1269
- raycaster,
1270
- clock,
1192
+ clock: new THREE.Clock(),
1271
1193
  mouse: new THREE.Vector2(),
1272
- frameloop,
1273
- onPointerMissed,
1194
+ frameloop: 'always',
1195
+ onPointerMissed: undefined,
1274
1196
  performance: {
1275
1197
  current: 1,
1276
1198
  min: 0.5,
1277
1199
  max: 1,
1278
1200
  debounce: 200,
1279
- ...performance,
1280
1201
  regress: () => {
1281
1202
  const state = get(); // Clear timeout
1282
1203
 
@@ -1292,8 +1213,8 @@ const createStore = (applyProps, invalidate, advance, props) => {
1292
1213
  height: 0
1293
1214
  },
1294
1215
  viewport: {
1295
- initialDpr,
1296
- dpr: initialDpr,
1216
+ initialDpr: 0,
1217
+ dpr: 0,
1297
1218
  width: 0,
1298
1219
  height: 0,
1299
1220
  aspect: 0,
@@ -1302,6 +1223,7 @@ const createStore = (applyProps, invalidate, advance, props) => {
1302
1223
  getCurrentViewport
1303
1224
  },
1304
1225
  setSize: (width, height) => {
1226
+ const camera = get().camera;
1305
1227
  const size = {
1306
1228
  width,
1307
1229
  height
@@ -1313,22 +1235,34 @@ const createStore = (applyProps, invalidate, advance, props) => {
1313
1235
  }
1314
1236
  }));
1315
1237
  },
1316
- setDpr: dpr => set(state => ({
1317
- viewport: { ...state.viewport,
1318
- dpr: calculateDpr(dpr)
1238
+ setDpr: dpr => set(state => {
1239
+ const resolved = calculateDpr(dpr);
1240
+ return {
1241
+ viewport: { ...state.viewport,
1242
+ dpr: resolved,
1243
+ initialDpr: state.viewport.initialDpr || resolved
1244
+ }
1245
+ };
1246
+ }),
1247
+ setFrameloop: (frameloop = 'always') => {
1248
+ const clock = get().clock; // if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
1249
+
1250
+ clock.stop();
1251
+ clock.elapsedTime = 0;
1252
+
1253
+ if (frameloop !== 'never') {
1254
+ clock.start();
1255
+ clock.elapsedTime = 0;
1319
1256
  }
1320
- })),
1321
- setFrameloop: (frameloop = 'always') => set(() => ({
1322
- frameloop
1323
- })),
1324
- events: {
1325
- connected: false
1257
+
1258
+ set(() => ({
1259
+ frameloop
1260
+ }));
1326
1261
  },
1327
1262
  internal: {
1328
1263
  active: false,
1329
1264
  priority: 0,
1330
1265
  frames: 0,
1331
- lastProps: props,
1332
1266
  lastEvent: /*#__PURE__*/React.createRef(),
1333
1267
  interaction: [],
1334
1268
  hovered: new Map(),
@@ -1336,7 +1270,6 @@ const createStore = (applyProps, invalidate, advance, props) => {
1336
1270
  initialClick: [0, 0],
1337
1271
  initialHits: [],
1338
1272
  capturedMap: new Map(),
1339
- xr,
1340
1273
  subscribe: (ref, priority = 0) => {
1341
1274
  set(({
1342
1275
  internal
@@ -1380,13 +1313,14 @@ const createStore = (applyProps, invalidate, advance, props) => {
1380
1313
  camera,
1381
1314
  size,
1382
1315
  viewport,
1383
- internal
1316
+ internal,
1317
+ gl
1384
1318
  } = rootState.getState();
1385
1319
 
1386
1320
  if (size !== oldSize || viewport.dpr !== oldDpr) {
1387
1321
  // https://github.com/pmndrs/react-three-fiber/issues/92
1388
1322
  // Do not mess with the camera if it belongs to the user
1389
- if (!camera.manual && !(internal.lastProps.camera instanceof THREE.Camera)) {
1323
+ if (!camera.manual) {
1390
1324
  if (isOrthographicCamera(camera)) {
1391
1325
  camera.left = size.width / -2;
1392
1326
  camera.right = size.width / 2;
@@ -1408,69 +1342,13 @@ const createStore = (applyProps, invalidate, advance, props) => {
1408
1342
  oldSize = size;
1409
1343
  oldDpr = viewport.dpr;
1410
1344
  }
1411
- }); // Update size
1412
-
1413
- if (size) state.setSize(size.width, size.height); // Invalidate on any change
1345
+ }); // Invalidate on any change
1414
1346
 
1415
1347
  rootState.subscribe(state => invalidate(state)); // Return root state
1416
1348
 
1417
1349
  return rootState;
1418
1350
  };
1419
1351
 
1420
- function useStore() {
1421
- const store = React.useContext(context);
1422
- if (!store) throw `R3F hooks can only be used within the Canvas component!`;
1423
- return store;
1424
- }
1425
- function useThree(selector = state => state, equalityFn) {
1426
- return useStore()(selector, equalityFn);
1427
- }
1428
- function useFrame(callback, renderPriority = 0) {
1429
- const subscribe = useStore().getState().internal.subscribe; // Update ref
1430
-
1431
- const ref = React.useRef(callback);
1432
- React.useLayoutEffect(() => void (ref.current = callback), [callback]); // Subscribe on mount, unsubscribe on unmount
1433
-
1434
- React.useLayoutEffect(() => subscribe(ref, renderPriority), [renderPriority, subscribe]);
1435
- return null;
1436
- }
1437
- function useGraph(object) {
1438
- return React.useMemo(() => buildGraph(object), [object]);
1439
- }
1440
-
1441
- function loadingFn(extensions, onProgress) {
1442
- return function (Proto, ...input) {
1443
- // Construct new loader and run extensions
1444
- const loader = new Proto();
1445
- if (extensions) extensions(loader); // Go through the urls and load them
1446
-
1447
- return Promise.all(input.map(input => new Promise((res, reject) => loader.load(input, data => {
1448
- if (data.scene) Object.assign(data, buildGraph(data.scene));
1449
- res(data);
1450
- }, onProgress, error => reject(`Could not load ${input}: ${error.message}`)))));
1451
- };
1452
- }
1453
-
1454
- function useLoader(Proto, input, extensions, onProgress) {
1455
- // Use suspense to load async assets
1456
- const keys = Array.isArray(input) ? input : [input];
1457
- const results = suspend(loadingFn(extensions, onProgress), [Proto, ...keys], {
1458
- equal: is.equ
1459
- }); // Return the object/s
1460
-
1461
- return Array.isArray(input) ? results : results[0];
1462
- }
1463
-
1464
- useLoader.preload = function (Proto, input, extensions) {
1465
- const keys = Array.isArray(input) ? input : [input];
1466
- return preload(loadingFn(extensions), [Proto, ...keys]);
1467
- };
1468
-
1469
- useLoader.clear = function (Proto, input) {
1470
- const keys = Array.isArray(input) ? input : [input];
1471
- return clear([Proto, ...keys]);
1472
- };
1473
-
1474
1352
  function createSubs(callback, subs) {
1475
1353
  const index = subs.length;
1476
1354
  subs.push(callback);
@@ -1489,7 +1367,9 @@ function run(effects, timestamp) {
1489
1367
  for (i = 0; i < effects.length; i++) effects[i](timestamp);
1490
1368
  }
1491
1369
 
1492
- function render$1(timestamp, state) {
1370
+ let subscribers;
1371
+
1372
+ function render$1(timestamp, state, frame) {
1493
1373
  // Run local effects
1494
1374
  let delta = state.clock.getDelta(); // In frameloop='never' mode, clock times are updated using the provided timestamp
1495
1375
 
@@ -1500,7 +1380,9 @@ function render$1(timestamp, state) {
1500
1380
  } // Call subscribers (useFrame)
1501
1381
 
1502
1382
 
1503
- for (i = 0; i < state.internal.subscribers.length; i++) state.internal.subscribers[i].ref.current(state, delta); // Render content
1383
+ subscribers = state.internal.subscribers;
1384
+
1385
+ for (i = 0; i < subscribers.length; i++) subscribers[i].ref.current(state, delta, frame); // Render content
1504
1386
 
1505
1387
 
1506
1388
  if (!state.internal.priority && state.gl.render) state.gl.render(state.scene, state.camera); // Decrease frame count
@@ -1512,29 +1394,35 @@ function render$1(timestamp, state) {
1512
1394
  function createLoop(roots) {
1513
1395
  let running = false;
1514
1396
  let repeat;
1397
+ let frame;
1398
+ let state;
1515
1399
 
1516
1400
  function loop(timestamp) {
1401
+ frame = requestAnimationFrame(loop);
1517
1402
  running = true;
1518
1403
  repeat = 0; // Run effects
1519
1404
 
1520
- run(globalEffects, timestamp); // Render all roots
1405
+ if (globalEffects.length) run(globalEffects, timestamp); // Render all roots
1521
1406
 
1522
1407
  roots.forEach(root => {
1523
1408
  var _state$gl$xr;
1524
1409
 
1525
- const state = root.store.getState(); // If the frameloop is invalidated, do not run another frame
1410
+ state = root.store.getState(); // If the frameloop is invalidated, do not run another frame
1526
1411
 
1527
1412
  if (state.internal.active && (state.frameloop === 'always' || state.internal.frames > 0) && !((_state$gl$xr = state.gl.xr) != null && _state$gl$xr.isPresenting)) {
1528
1413
  repeat += render$1(timestamp, state);
1529
1414
  }
1530
1415
  }); // Run after-effects
1531
1416
 
1532
- run(globalAfterEffects, timestamp); // Keep on looping if anything invalidates the frameloop
1417
+ if (globalAfterEffects.length) run(globalAfterEffects, timestamp); // Stop the loop if nothing invalidates it
1533
1418
 
1534
- if (repeat > 0) return requestAnimationFrame(loop); // Tail call effects, they are called when rendering stops
1535
- else run(globalTailEffects, timestamp); // Flag end of operation
1419
+ if (repeat === 0) {
1420
+ // Tail call effects, they are called when rendering stops
1421
+ if (globalTailEffects.length) run(globalTailEffects, timestamp); // Flag end of operation
1536
1422
 
1537
- running = false;
1423
+ running = false;
1424
+ return cancelAnimationFrame(frame);
1425
+ }
1538
1426
  }
1539
1427
 
1540
1428
  function invalidate(state) {
@@ -1551,9 +1439,9 @@ function createLoop(roots) {
1551
1439
  }
1552
1440
  }
1553
1441
 
1554
- function advance(timestamp, runGlobalEffects = true, state) {
1442
+ function advance(timestamp, runGlobalEffects = true, state, frame) {
1555
1443
  if (runGlobalEffects) run(globalEffects, timestamp);
1556
- if (!state) roots.forEach(root => render$1(timestamp, root.store.getState()));else render$1(timestamp, state);
1444
+ if (!state) roots.forEach(root => render$1(timestamp, root.store.getState()));else render$1(timestamp, state, frame);
1557
1445
  if (runGlobalEffects) run(globalAfterEffects, timestamp);
1558
1446
  }
1559
1447
 
@@ -1564,6 +1452,60 @@ function createLoop(roots) {
1564
1452
  };
1565
1453
  }
1566
1454
 
1455
+ function useStore() {
1456
+ const store = React.useContext(context);
1457
+ if (!store) throw `R3F hooks can only be used within the Canvas component!`;
1458
+ return store;
1459
+ }
1460
+ function useThree(selector = state => state, equalityFn) {
1461
+ return useStore()(selector, equalityFn);
1462
+ }
1463
+ function useFrame(callback, renderPriority = 0) {
1464
+ const subscribe = useStore().getState().internal.subscribe; // Update ref
1465
+
1466
+ const ref = React.useRef(callback);
1467
+ React.useLayoutEffect(() => void (ref.current = callback), [callback]); // Subscribe on mount, unsubscribe on unmount
1468
+
1469
+ React.useLayoutEffect(() => subscribe(ref, renderPriority), [renderPriority, subscribe]);
1470
+ return null;
1471
+ }
1472
+ function useGraph(object) {
1473
+ return React.useMemo(() => buildGraph(object), [object]);
1474
+ }
1475
+
1476
+ function loadingFn(extensions, onProgress) {
1477
+ return function (Proto, ...input) {
1478
+ // Construct new loader and run extensions
1479
+ const loader = new Proto();
1480
+ if (extensions) extensions(loader); // Go through the urls and load them
1481
+
1482
+ return Promise.all(input.map(input => new Promise((res, reject) => loader.load(input, data => {
1483
+ if (data.scene) Object.assign(data, buildGraph(data.scene));
1484
+ res(data);
1485
+ }, onProgress, error => reject(`Could not load ${input}: ${error.message}`)))));
1486
+ };
1487
+ }
1488
+
1489
+ function useLoader(Proto, input, extensions, onProgress) {
1490
+ // Use suspense to load async assets
1491
+ const keys = Array.isArray(input) ? input : [input];
1492
+ const results = suspend(loadingFn(extensions, onProgress), [Proto, ...keys], {
1493
+ equal: is.equ
1494
+ }); // Return the object/s
1495
+
1496
+ return Array.isArray(input) ? results : results[0];
1497
+ }
1498
+
1499
+ useLoader.preload = function (Proto, input, extensions) {
1500
+ const keys = Array.isArray(input) ? input : [input];
1501
+ return preload(loadingFn(extensions), [Proto, ...keys]);
1502
+ };
1503
+
1504
+ useLoader.clear = function (Proto, input) {
1505
+ const keys = Array.isArray(input) ? input : [input];
1506
+ return clear([Proto, ...keys]);
1507
+ };
1508
+
1567
1509
  const roots = new Map();
1568
1510
  const {
1569
1511
  invalidate,
@@ -1573,113 +1515,216 @@ const {
1573
1515
  reconciler,
1574
1516
  applyProps
1575
1517
  } = createRenderer(roots, getEventPriority);
1518
+ const shallowLoose = {
1519
+ objects: 'shallow',
1520
+ strict: false
1521
+ };
1576
1522
 
1577
1523
  const createRendererInstance = (gl, canvas) => {
1578
1524
  const customRenderer = typeof gl === 'function' ? gl(canvas) : gl;
1579
- if (isRenderer(customRenderer)) return customRenderer;
1580
- const renderer = new THREE.WebGLRenderer({
1525
+ if (isRenderer(customRenderer)) return customRenderer;else return new THREE.WebGLRenderer({
1581
1526
  powerPreference: 'high-performance',
1582
1527
  canvas: canvas,
1583
1528
  antialias: true,
1584
1529
  alpha: true,
1585
1530
  ...gl
1586
- }); // Set color management
1531
+ });
1532
+ };
1587
1533
 
1588
- renderer.outputEncoding = THREE.sRGBEncoding;
1589
- renderer.toneMapping = THREE.ACESFilmicToneMapping; // Set gl props
1534
+ function createRoot(canvas) {
1535
+ // Check against mistaken use of createRoot
1536
+ let prevRoot = roots.get(canvas);
1537
+ let prevFiber = prevRoot == null ? void 0 : prevRoot.fiber;
1538
+ let prevStore = prevRoot == null ? void 0 : prevRoot.store;
1539
+ if (prevRoot) console.warn('R3F.createRoot should only be called once!'); // Create store
1590
1540
 
1591
- if (gl) applyProps(renderer, gl);
1592
- return renderer;
1593
- };
1541
+ const store = prevStore || createStore(invalidate, advance); // Create renderer
1594
1542
 
1595
- function createRoot(canvas, config) {
1543
+ const fiber = prevFiber || reconciler.createContainer(store, ConcurrentRoot, false, null); // Map it
1544
+
1545
+ if (!prevRoot) roots.set(canvas, {
1546
+ fiber,
1547
+ store
1548
+ }); // Locals
1549
+
1550
+ let onCreated;
1551
+ let configured = false;
1596
1552
  return {
1597
- render: element => {
1598
- var _store;
1553
+ configure(props = {}) {
1554
+ var _canvas$parentElement, _canvas$parentElement2, _canvas$parentElement3, _canvas$parentElement4;
1599
1555
 
1600
1556
  let {
1601
- gl,
1557
+ gl: glConfig,
1602
1558
  size,
1603
1559
  events,
1604
- onCreated,
1605
- ...props
1606
- } = config || {}; // Allow size to take on container bounds initially
1560
+ onCreated: onCreatedCallback,
1561
+ shadows = false,
1562
+ linear = false,
1563
+ flat = false,
1564
+ orthographic = false,
1565
+ frameloop = 'always',
1566
+ dpr = [1, 2],
1567
+ performance,
1568
+ raycaster: raycastOptions,
1569
+ camera: cameraOptions,
1570
+ onPointerMissed
1571
+ } = props;
1572
+ let state = store.getState(); // Set up renderer (one time only!)
1573
+
1574
+ let gl = state.gl;
1575
+ if (!state.gl) state.set({
1576
+ gl: gl = createRendererInstance(glConfig, canvas)
1577
+ }); // Set up raycaster (one time only!)
1578
+
1579
+ let raycaster = state.raycaster;
1580
+ if (!raycaster) state.set({
1581
+ raycaster: raycaster = new THREE.Raycaster()
1582
+ }); // Set raycaster options
1607
1583
 
1608
- if (!size) {
1609
- var _canvas$parentElement, _canvas$parentElement2, _canvas$parentElement3, _canvas$parentElement4;
1584
+ const {
1585
+ params,
1586
+ ...options
1587
+ } = raycastOptions || {};
1588
+ if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, {
1589
+ enabled: true,
1590
+ ...options
1591
+ });
1592
+ if (!is.equ(params, raycaster.params, shallowLoose)) applyProps(raycaster, {
1593
+ params: { ...raycaster.params,
1594
+ ...params
1595
+ }
1596
+ }); // Create default camera (one time only!)
1610
1597
 
1611
- size = {
1612
- width: (_canvas$parentElement = (_canvas$parentElement2 = canvas.parentElement) == null ? void 0 : _canvas$parentElement2.clientWidth) != null ? _canvas$parentElement : 0,
1613
- height: (_canvas$parentElement3 = (_canvas$parentElement4 = canvas.parentElement) == null ? void 0 : _canvas$parentElement4.clientHeight) != null ? _canvas$parentElement3 : 0
1614
- };
1615
- }
1598
+ if (!state.camera) {
1599
+ const isCamera = cameraOptions instanceof THREE.Camera;
1600
+ const camera = isCamera ? cameraOptions : orthographic ? new THREE.OrthographicCamera(0, 0, 0, 0, 0.1, 1000) : new THREE.PerspectiveCamera(75, 0, 0.1, 1000);
1601
+
1602
+ if (!isCamera) {
1603
+ camera.position.z = 5;
1604
+ if (cameraOptions) applyProps(camera, cameraOptions); // Always look at center by default
1605
+
1606
+ if (!(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
1607
+ }
1616
1608
 
1617
- let root = roots.get(canvas);
1618
- let fiber = root == null ? void 0 : root.fiber;
1619
- let store = root == null ? void 0 : root.store;
1620
- let state = (_store = store) == null ? void 0 : _store.getState();
1609
+ state.set({
1610
+ camera
1611
+ });
1612
+ } // Set up XR (one time only!)
1621
1613
 
1622
- if (fiber && state) {
1623
- // When a root was found, see if any fundamental props must be changed or exchanged
1624
- // Check pixelratio
1625
- if (props.dpr !== undefined && state.viewport.dpr !== calculateDpr(props.dpr)) state.setDpr(props.dpr); // Check size
1626
1614
 
1627
- if (state.size.width !== size.width || state.size.height !== size.height) state.setSize(size.width, size.height); // Check frameloop
1615
+ if (!state.xr) {
1616
+ // Handle frame behavior in WebXR
1617
+ const handleXRFrame = (timestamp, frame) => {
1618
+ const state = store.getState();
1619
+ if (state.frameloop === 'never') return;
1620
+ advance(timestamp, true, state, frame);
1621
+ }; // Toggle render switching on session
1628
1622
 
1629
- if (state.frameloop !== props.frameloop) state.setFrameloop(props.frameloop); // For some props we want to reset the entire root
1630
- // Changes to the color-space
1631
1623
 
1632
- const linearChanged = props.linear !== state.internal.lastProps.linear;
1624
+ const handleSessionChange = () => {
1625
+ const gl = store.getState().gl;
1626
+ gl.xr.enabled = gl.xr.isPresenting; // @ts-expect-error
1627
+ // WebXRManager's signature is incorrect.
1628
+ // See: https://github.com/pmndrs/react-three-fiber/pull/2017#discussion_r790134505
1633
1629
 
1634
- if (linearChanged) {
1635
- unmountComponentAtNode(canvas);
1636
- fiber = undefined;
1637
- }
1638
- }
1630
+ gl.xr.setAnimationLoop(gl.xr.isPresenting ? handleXRFrame : null);
1631
+ }; // WebXR session manager
1639
1632
 
1640
- if (!fiber) {
1641
- // If no root has been found, make one
1642
- // Create gl
1643
- const glRenderer = createRendererInstance(gl, canvas); // Create store
1644
1633
 
1645
- store = createStore(applyProps, invalidate, advance, {
1646
- gl: glRenderer,
1647
- size,
1648
- ...props
1649
- });
1650
- const state = store.getState(); // Create renderer
1634
+ const xr = {
1635
+ connect() {
1636
+ const gl = store.getState().gl;
1637
+ gl.xr.addEventListener('sessionstart', handleSessionChange);
1638
+ gl.xr.addEventListener('sessionend', handleSessionChange);
1639
+ },
1651
1640
 
1652
- fiber = reconciler.createContainer(store, ConcurrentRoot, false, null); // Map it
1641
+ disconnect() {
1642
+ const gl = store.getState().gl;
1643
+ gl.xr.removeEventListener('sessionstart', handleSessionChange);
1644
+ gl.xr.removeEventListener('sessionend', handleSessionChange);
1645
+ }
1653
1646
 
1654
- roots.set(canvas, {
1655
- fiber,
1656
- store
1657
- }); // Store events internally
1647
+ }; // Subscribe to WebXR session events
1658
1648
 
1659
- if (events) state.set({
1660
- events: events(store)
1649
+ if (gl.xr) xr.connect();
1650
+ state.set({
1651
+ xr
1661
1652
  });
1662
- }
1653
+ } // Set shadowmap
1663
1654
 
1664
- if (store && fiber) {
1665
- reconciler.updateContainer( /*#__PURE__*/React.createElement(Provider, {
1666
- store: store,
1667
- element: element,
1668
- onCreated: onCreated,
1669
- target: canvas
1670
- }), fiber, null, () => undefined);
1671
- return store;
1672
- } else {
1673
- throw 'Error creating root!';
1674
- }
1655
+
1656
+ if (gl.shadowMap) {
1657
+ const isBoolean = is.boo(shadows);
1658
+
1659
+ if (isBoolean && gl.shadowMap.enabled !== shadows || !is.equ(shadows, gl.shadowMap, shallowLoose)) {
1660
+ const old = gl.shadowMap.enabled;
1661
+ gl.shadowMap.enabled = !!shadows;
1662
+ if (!isBoolean) Object.assign(gl.shadowMap, shadows);else gl.shadowMap.type = THREE.PCFSoftShadowMap;
1663
+ if (old !== gl.shadowMap.enabled) gl.shadowMap.needsUpdate = true;
1664
+ }
1665
+ } // Set color management
1666
+
1667
+
1668
+ const outputEncoding = linear ? THREE.LinearEncoding : THREE.sRGBEncoding;
1669
+ const toneMapping = flat ? THREE.NoToneMapping : THREE.ACESFilmicToneMapping;
1670
+ if (gl.outputEncoding !== outputEncoding) gl.outputEncoding = outputEncoding;
1671
+ if (gl.toneMapping !== toneMapping) gl.toneMapping = toneMapping; // Set gl props
1672
+
1673
+ if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, gl, shallowLoose)) applyProps(gl, glConfig); // Store events internally
1674
+
1675
+ if (events && !state.events.handlers) state.set({
1676
+ events: events(store)
1677
+ }); // Check pixelratio
1678
+
1679
+ if (dpr && state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr); // Check size, allow it to take on container bounds initially
1680
+
1681
+ size = size || {
1682
+ width: (_canvas$parentElement = (_canvas$parentElement2 = canvas.parentElement) == null ? void 0 : _canvas$parentElement2.clientWidth) != null ? _canvas$parentElement : 0,
1683
+ height: (_canvas$parentElement3 = (_canvas$parentElement4 = canvas.parentElement) == null ? void 0 : _canvas$parentElement4.clientHeight) != null ? _canvas$parentElement3 : 0
1684
+ };
1685
+ if (!is.equ(size, state.size, shallowLoose)) state.setSize(size.width, size.height); // Check frameloop
1686
+
1687
+ if (state.frameloop !== frameloop) state.setFrameloop(frameloop); // Check pointer missed
1688
+
1689
+ if (!state.onPointerMissed) state.set({
1690
+ onPointerMissed
1691
+ }); // Check performance
1692
+
1693
+ if (performance && !is.equ(performance, state.performance, shallowLoose)) state.set(state => ({
1694
+ performance: { ...state.performance,
1695
+ ...performance
1696
+ }
1697
+ })); // Set locals
1698
+
1699
+ onCreated = onCreatedCallback;
1700
+ configured = true;
1701
+ return this;
1702
+ },
1703
+
1704
+ render(element) {
1705
+ // The root has to be configured before it can be rendered
1706
+ if (!configured) this.configure();
1707
+ reconciler.updateContainer( /*#__PURE__*/React.createElement(Provider, {
1708
+ store: store,
1709
+ element: element,
1710
+ onCreated: onCreated,
1711
+ target: canvas
1712
+ }), fiber, null, () => undefined);
1713
+ return store;
1675
1714
  },
1676
- unmount: () => unmountComponentAtNode(canvas)
1715
+
1716
+ unmount() {
1717
+ unmountComponentAtNode(canvas);
1718
+ }
1719
+
1677
1720
  };
1678
1721
  }
1679
1722
 
1680
1723
  function render(element, canvas, config = {}) {
1681
1724
  console.warn('R3F.render is no longer supported in React 18. Use createRoot instead!');
1682
- return createRoot(canvas, config).render(element);
1725
+ const root = createRoot(canvas);
1726
+ root.configure(config);
1727
+ return root.render(element);
1683
1728
  }
1684
1729
 
1685
1730
  function Provider({
@@ -1722,7 +1767,7 @@ function unmountComponentAtNode(canvas, callback) {
1722
1767
  state.events.disconnect == null ? void 0 : state.events.disconnect();
1723
1768
  (_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();
1724
1769
  (_state$gl2 = state.gl) == null ? void 0 : _state$gl2.forceContextLoss == null ? void 0 : _state$gl2.forceContextLoss();
1725
- if ((_state$gl3 = state.gl) != null && _state$gl3.xr) state.internal.xr.disconnect();
1770
+ if ((_state$gl3 = state.gl) != null && _state$gl3.xr) state.xr.disconnect();
1726
1771
  dispose(state);
1727
1772
  roots.delete(canvas);
1728
1773
  if (callback) callback(canvas);
@@ -1747,4 +1792,4 @@ reconciler.injectIntoDevTools({
1747
1792
  version: '18.0.0'
1748
1793
  });
1749
1794
 
1750
- export { is as A, createRoot as a, useStore as b, createEvents as c, useThree as d, extend as e, useFrame as f, useGraph as g, useLoader as h, context as i, createPortal as j, reconciler as k, applyProps as l, dispose as m, invalidate as n, omit as o, pick as p, advance as q, render as r, addEffect as s, threeTypes as t, unmountComponentAtNode as u, addAfterEffect as v, addTail as w, act as x, roots as y, buildGraph as z };
1795
+ export { createRoot as a, context as b, createEvents as c, createPortal as d, extend as e, reconciler as f, applyProps as g, dispose as h, invalidate as i, advance as j, addEffect as k, addAfterEffect as l, addTail as m, act as n, omit as o, pick as p, roots as q, render as r, useStore as s, threeTypes as t, unmountComponentAtNode as u, useThree as v, useFrame as w, useGraph as x, useLoader as y };