@react-three/fiber 10.0.0-canary.b0fafc8 → 10.0.0-canary.c3fa45d

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/dist/index.cjs CHANGED
@@ -160,7 +160,7 @@ function useEnvironment({
160
160
  fiber.useLoader.clear(loader, multiFile ? [files] : files);
161
161
  }
162
162
  renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
163
- }, [files, renderer.domElement]);
163
+ }, [extension, files, loader, multiFile, renderer.domElement]);
164
164
  const loaderResult = fiber.useLoader(
165
165
  loader,
166
166
  multiFile ? [files] : files,
@@ -360,7 +360,22 @@ function EnvironmentPortal({
360
360
  environmentIntensity,
361
361
  environmentRotation
362
362
  });
363
- }, [children, virtualScene, fbo.texture, scene, defaultScene, background, frames, gl]);
363
+ }, [
364
+ children,
365
+ virtualScene,
366
+ fbo.texture,
367
+ scene,
368
+ defaultScene,
369
+ background,
370
+ frames,
371
+ gl,
372
+ blur,
373
+ backgroundBlurriness,
374
+ backgroundIntensity,
375
+ backgroundRotation,
376
+ environmentIntensity,
377
+ environmentRotation
378
+ ]);
364
379
  let count = 1;
365
380
  fiber.useFrame(() => {
366
381
  if (frames === Infinity || count < frames) {
@@ -996,6 +1011,9 @@ function applyProps(object, props) {
996
1011
  else target.set(value);
997
1012
  } else {
998
1013
  root[key] = value;
1014
+ if (key.endsWith("Node") && root.isMaterial) {
1015
+ root.needsUpdate = true;
1016
+ }
999
1017
  if (rootState && rootState.renderer?.outputColorSpace === webgpu.SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
1000
1018
  root[key].format === webgpu.RGBAFormat && root[key].type === webgpu.UnsignedByteType) {
1001
1019
  root[key].colorSpace = rootState.textureColorSpace;
@@ -1029,38 +1047,60 @@ function applyProps(object, props) {
1029
1047
  return object;
1030
1048
  }
1031
1049
 
1050
+ const DEFAULT_POINTER_ID = 0;
1051
+ const XR_POINTER_ID_START = 1e3;
1052
+ function getPointerState(internal, pointerId) {
1053
+ let state = internal.pointerMap.get(pointerId);
1054
+ if (!state) {
1055
+ state = {
1056
+ hovered: /* @__PURE__ */ new Map(),
1057
+ captured: /* @__PURE__ */ new Map(),
1058
+ initialClick: [0, 0],
1059
+ initialHits: []
1060
+ };
1061
+ internal.pointerMap.set(pointerId, state);
1062
+ }
1063
+ return state;
1064
+ }
1065
+ function getPointerId(event) {
1066
+ return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
1067
+ }
1032
1068
  function makeId(event) {
1033
1069
  return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
1034
1070
  }
1035
- function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
1036
- const captureData = captures.get(obj);
1071
+ function releaseInternalPointerCapture(internal, obj, pointerId) {
1072
+ const pointerState = internal.pointerMap.get(pointerId);
1073
+ if (!pointerState) return;
1074
+ const captureData = pointerState.captured.get(obj);
1037
1075
  if (captureData) {
1038
- captures.delete(obj);
1039
- if (captures.size === 0) {
1040
- capturedMap.delete(pointerId);
1041
- captureData.target.releasePointerCapture(pointerId);
1042
- }
1076
+ pointerState.captured.delete(obj);
1077
+ captureData.target.releasePointerCapture(pointerId);
1043
1078
  }
1044
1079
  }
1045
1080
  function removeInteractivity(store, object) {
1046
1081
  const { internal } = store.getState();
1047
1082
  internal.interaction = internal.interaction.filter((o) => o !== object);
1048
- internal.initialHits = internal.initialHits.filter((o) => o !== object);
1049
- internal.hovered.forEach((value, key) => {
1050
- if (value.eventObject === object || value.object === object) {
1051
- internal.hovered.delete(key);
1083
+ for (const [pointerId, pointerState] of internal.pointerMap) {
1084
+ pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
1085
+ pointerState.hovered.forEach((value, key) => {
1086
+ if (value.eventObject === object || value.object === object) {
1087
+ pointerState.hovered.delete(key);
1088
+ }
1089
+ });
1090
+ if (pointerState.captured.has(object)) {
1091
+ releaseInternalPointerCapture(internal, object, pointerId);
1052
1092
  }
1053
- });
1054
- internal.capturedMap.forEach((captures, pointerId) => {
1055
- releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
1056
- });
1093
+ }
1057
1094
  unregisterVisibility(store, object);
1058
1095
  }
1059
1096
  function createEvents(store) {
1060
- function calculateDistance(event) {
1097
+ function calculateDistance(event, pointerId) {
1061
1098
  const { internal } = store.getState();
1062
- const dx = event.offsetX - internal.initialClick[0];
1063
- const dy = event.offsetY - internal.initialClick[1];
1099
+ const pointerState = internal.pointerMap.get(pointerId);
1100
+ if (!pointerState) return 0;
1101
+ const [initialX, initialY] = pointerState.initialClick;
1102
+ const dx = event.offsetX - initialX;
1103
+ const dy = event.offsetY - initialY;
1064
1104
  return Math.round(Math.sqrt(dx * dx + dy * dy));
1065
1105
  }
1066
1106
  function filterPointerEvents(objects) {
@@ -1096,6 +1136,15 @@ function createEvents(store) {
1096
1136
  return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
1097
1137
  }
1098
1138
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
1139
+ const aInteractivePriority = a.object.userData?.interactivePriority;
1140
+ const bInteractivePriority = b.object.userData?.interactivePriority;
1141
+ if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
1142
+ if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
1143
+ if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
1144
+ if (aInteractivePriority !== bInteractivePriority) {
1145
+ return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
1146
+ }
1147
+ }
1099
1148
  const aState = getRootState(a.object);
1100
1149
  const bState = getRootState(b.object);
1101
1150
  const aPriority = aState?.events?.priority ?? 1;
@@ -1117,9 +1166,13 @@ function createEvents(store) {
1117
1166
  eventObject = eventObject.parent;
1118
1167
  }
1119
1168
  }
1120
- if ("pointerId" in event && state.internal.capturedMap.has(event.pointerId)) {
1121
- for (const captureData of state.internal.capturedMap.get(event.pointerId).values()) {
1122
- if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1169
+ if ("pointerId" in event) {
1170
+ const pointerId = event.pointerId;
1171
+ const pointerState = state.internal.pointerMap.get(pointerId);
1172
+ if (pointerState?.captured.size) {
1173
+ for (const captureData of pointerState.captured.values()) {
1174
+ if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1175
+ }
1123
1176
  }
1124
1177
  }
1125
1178
  return intersections;
@@ -1132,27 +1185,25 @@ function createEvents(store) {
1132
1185
  if (state) {
1133
1186
  const { raycaster, pointer, camera, internal } = state;
1134
1187
  const unprojectedPoint = new webgpu.Vector3(pointer.x, pointer.y, 0).unproject(camera);
1135
- const hasPointerCapture = (id) => internal.capturedMap.get(id)?.has(hit.eventObject) ?? false;
1188
+ const hasPointerCapture = (id) => {
1189
+ const pointerState = internal.pointerMap.get(id);
1190
+ return pointerState?.captured.has(hit.eventObject) ?? false;
1191
+ };
1136
1192
  const setPointerCapture = (id) => {
1137
1193
  const captureData = { intersection: hit, target: event.target };
1138
- if (internal.capturedMap.has(id)) {
1139
- internal.capturedMap.get(id).set(hit.eventObject, captureData);
1140
- } else {
1141
- internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
1142
- }
1194
+ const pointerState = getPointerState(internal, id);
1195
+ pointerState.captured.set(hit.eventObject, captureData);
1143
1196
  event.target.setPointerCapture(id);
1144
1197
  };
1145
1198
  const releasePointerCapture = (id) => {
1146
- const captures = internal.capturedMap.get(id);
1147
- if (captures) {
1148
- releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
1149
- }
1199
+ releaseInternalPointerCapture(internal, hit.eventObject, id);
1150
1200
  };
1151
1201
  const extractEventProps = {};
1152
1202
  for (const prop in event) {
1153
1203
  const property = event[prop];
1154
1204
  if (typeof property !== "function") extractEventProps[prop] = property;
1155
1205
  }
1206
+ const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
1156
1207
  const raycastEvent = {
1157
1208
  ...hit,
1158
1209
  ...extractEventProps,
@@ -1163,18 +1214,19 @@ function createEvents(store) {
1163
1214
  unprojectedPoint,
1164
1215
  ray: raycaster.ray,
1165
1216
  camera,
1217
+ pointerId: eventPointerId,
1166
1218
  // Hijack stopPropagation, which just sets a flag
1167
1219
  stopPropagation() {
1168
- const capturesForPointer = "pointerId" in event && internal.capturedMap.get(event.pointerId);
1220
+ const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
1169
1221
  if (
1170
1222
  // ...if this pointer hasn't been captured
1171
- !capturesForPointer || // ... or if the hit object is capturing the pointer
1172
- capturesForPointer.has(hit.eventObject)
1223
+ !pointerState?.captured.size || // ... or if the hit object is capturing the pointer
1224
+ pointerState.captured.has(hit.eventObject)
1173
1225
  ) {
1174
1226
  raycastEvent.stopped = localState.stopped = true;
1175
- if (internal.hovered.size && Array.from(internal.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
1227
+ if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
1176
1228
  const higher = intersections.slice(0, intersections.indexOf(hit));
1177
- cancelPointer([...higher, hit]);
1229
+ cancelPointer([...higher, hit], eventPointerId);
1178
1230
  }
1179
1231
  }
1180
1232
  },
@@ -1190,15 +1242,18 @@ function createEvents(store) {
1190
1242
  }
1191
1243
  return intersections;
1192
1244
  }
1193
- function cancelPointer(intersections) {
1245
+ function cancelPointer(intersections, pointerId) {
1194
1246
  const { internal } = store.getState();
1195
- for (const hoveredObj of internal.hovered.values()) {
1247
+ const pid = pointerId ?? DEFAULT_POINTER_ID;
1248
+ const pointerState = internal.pointerMap.get(pid);
1249
+ if (!pointerState) return;
1250
+ for (const [hoveredId, hoveredObj] of pointerState.hovered) {
1196
1251
  if (!intersections.length || !intersections.find(
1197
1252
  (hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
1198
1253
  )) {
1199
1254
  const eventObject = hoveredObj.eventObject;
1200
1255
  const instance = eventObject.__r3f;
1201
- internal.hovered.delete(makeId(hoveredObj));
1256
+ pointerState.hovered.delete(hoveredId);
1202
1257
  if (instance?.eventCount) {
1203
1258
  const handlers = instance.handlers;
1204
1259
  const data = { ...hoveredObj, intersections };
@@ -1227,41 +1282,118 @@ function createEvents(store) {
1227
1282
  instance?.handlers.onDropMissed?.(event);
1228
1283
  }
1229
1284
  }
1285
+ function cleanupPointer(pointerId) {
1286
+ const { internal } = store.getState();
1287
+ const pointerState = internal.pointerMap.get(pointerId);
1288
+ if (pointerState) {
1289
+ for (const [, hoveredObj] of pointerState.hovered) {
1290
+ const eventObject = hoveredObj.eventObject;
1291
+ const instance = eventObject.__r3f;
1292
+ if (instance?.eventCount) {
1293
+ const handlers = instance.handlers;
1294
+ const data = { ...hoveredObj, intersections: [] };
1295
+ handlers.onPointerOut?.(data);
1296
+ handlers.onPointerLeave?.(data);
1297
+ }
1298
+ }
1299
+ internal.pointerMap.delete(pointerId);
1300
+ }
1301
+ internal.pointerDirty.delete(pointerId);
1302
+ }
1303
+ function processDeferredPointer(event, pointerId) {
1304
+ const state = store.getState();
1305
+ const { internal } = state;
1306
+ if (!state.events.enabled) return;
1307
+ const filter = filterPointerEvents;
1308
+ const hits = intersect(event, filter);
1309
+ cancelPointer(hits, pointerId);
1310
+ function onIntersect(data) {
1311
+ const eventObject = data.eventObject;
1312
+ const instance = eventObject.__r3f;
1313
+ if (!instance?.eventCount) return;
1314
+ const handlers = instance.handlers;
1315
+ if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1316
+ const id = makeId(data);
1317
+ const pointerState = getPointerState(internal, pointerId);
1318
+ const hoveredItem = pointerState.hovered.get(id);
1319
+ if (!hoveredItem) {
1320
+ pointerState.hovered.set(id, data);
1321
+ handlers.onPointerOver?.(data);
1322
+ handlers.onPointerEnter?.(data);
1323
+ } else if (hoveredItem.stopped) {
1324
+ data.stopPropagation();
1325
+ }
1326
+ }
1327
+ handlers.onPointerMove?.(data);
1328
+ }
1329
+ handleIntersects(hits, event, 0, onIntersect);
1330
+ }
1230
1331
  function handlePointer(name) {
1231
1332
  switch (name) {
1232
1333
  case "onPointerLeave":
1233
- case "onPointerCancel":
1234
1334
  case "onDragLeave":
1235
1335
  return () => cancelPointer([]);
1336
+ // Global cancel of these events
1337
+ case "onPointerCancel":
1338
+ return (event) => {
1339
+ const pointerId = getPointerId(event);
1340
+ cleanupPointer(pointerId);
1341
+ };
1236
1342
  case "onLostPointerCapture":
1237
1343
  return (event) => {
1238
1344
  const { internal } = store.getState();
1239
- if ("pointerId" in event && internal.capturedMap.has(event.pointerId)) {
1345
+ const pointerId = getPointerId(event);
1346
+ const pointerState = internal.pointerMap.get(pointerId);
1347
+ if (pointerState?.captured.size) {
1240
1348
  requestAnimationFrame(() => {
1241
- if (internal.capturedMap.has(event.pointerId)) {
1242
- internal.capturedMap.delete(event.pointerId);
1243
- cancelPointer([]);
1349
+ const pointerState2 = internal.pointerMap.get(pointerId);
1350
+ if (pointerState2?.captured.size) {
1351
+ pointerState2.captured.clear();
1244
1352
  }
1353
+ cancelPointer([], pointerId);
1245
1354
  });
1246
1355
  }
1247
1356
  };
1248
1357
  }
1249
1358
  return function handleEvent(event) {
1250
1359
  const state = store.getState();
1251
- const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
1360
+ const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
1361
+ const pointerId = getPointerId(event);
1252
1362
  internal.lastEvent.current = event;
1253
- if (!state.events.enabled) return;
1363
+ if (!events.enabled) return;
1254
1364
  const isPointerMove = name === "onPointerMove";
1255
1365
  const isDragOver = name === "onDragOver";
1256
1366
  const isDrop = name === "onDrop";
1257
1367
  const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
1368
+ const isPointerDown = name === "onPointerDown";
1369
+ const isPointerUp = name === "onPointerUp";
1370
+ const isWheel = name === "onWheel";
1371
+ const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
1372
+ if (isPointerMove && canDeferRaycasts) {
1373
+ events.compute?.(event, state);
1374
+ internal.pointerDirty.set(pointerId, event);
1375
+ return;
1376
+ }
1377
+ if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
1378
+ events.compute?.(event, state);
1379
+ internal.pointerDirty.set(pointerId, event);
1380
+ return;
1381
+ }
1382
+ if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
1383
+ const deferredEvent = internal.pointerDirty.get(pointerId);
1384
+ internal.pointerDirty.delete(pointerId);
1385
+ processDeferredPointer(deferredEvent, pointerId);
1386
+ }
1258
1387
  const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
1259
1388
  const hits = intersect(event, filter);
1260
- const delta = isClickEvent ? calculateDistance(event) : 0;
1261
- if (name === "onPointerDown") {
1262
- internal.initialClick = [event.offsetX, event.offsetY];
1263
- internal.initialHits = hits.map((hit) => hit.eventObject);
1264
- }
1389
+ const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
1390
+ if (isPointerDown) {
1391
+ const pointerState2 = getPointerState(internal, pointerId);
1392
+ pointerState2.initialClick = [event.offsetX, event.offsetY];
1393
+ pointerState2.initialHits = hits.map((hit) => hit.eventObject);
1394
+ }
1395
+ const pointerState = internal.pointerMap.get(pointerId);
1396
+ const initialHits = pointerState?.initialHits ?? [];
1265
1397
  if (isClickEvent && !hits.length) {
1266
1398
  if (delta <= 2) {
1267
1399
  pointerMissed(event, internal.interaction);
@@ -1276,7 +1408,9 @@ function createEvents(store) {
1276
1408
  dropMissed(event, internal.interaction);
1277
1409
  if (onDropMissed) onDropMissed(event);
1278
1410
  }
1279
- if (isPointerMove || isDragOver) cancelPointer(hits);
1411
+ if (isPointerMove || isDragOver) {
1412
+ cancelPointer(hits, pointerId);
1413
+ }
1280
1414
  function onIntersect(data) {
1281
1415
  const eventObject = data.eventObject;
1282
1416
  const instance = eventObject.__r3f;
@@ -1285,9 +1419,10 @@ function createEvents(store) {
1285
1419
  if (isPointerMove) {
1286
1420
  if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1287
1421
  const id = makeId(data);
1288
- const hoveredItem = internal.hovered.get(id);
1422
+ const pointerState2 = getPointerState(internal, pointerId);
1423
+ const hoveredItem = pointerState2.hovered.get(id);
1289
1424
  if (!hoveredItem) {
1290
- internal.hovered.set(id, data);
1425
+ pointerState2.hovered.set(id, data);
1291
1426
  handlers.onPointerOver?.(data);
1292
1427
  handlers.onPointerEnter?.(data);
1293
1428
  } else if (hoveredItem.stopped) {
@@ -1297,9 +1432,10 @@ function createEvents(store) {
1297
1432
  handlers.onPointerMove?.(data);
1298
1433
  } else if (isDragOver) {
1299
1434
  const id = makeId(data);
1300
- const hoveredItem = internal.hovered.get(id);
1435
+ const pointerState2 = getPointerState(internal, pointerId);
1436
+ const hoveredItem = pointerState2.hovered.get(id);
1301
1437
  if (!hoveredItem) {
1302
- internal.hovered.set(id, data);
1438
+ pointerState2.hovered.set(id, data);
1303
1439
  handlers.onDragOverEnter?.(data);
1304
1440
  } else if (hoveredItem.stopped) {
1305
1441
  data.stopPropagation();
@@ -1310,18 +1446,18 @@ function createEvents(store) {
1310
1446
  } else {
1311
1447
  const handler = handlers[name];
1312
1448
  if (handler) {
1313
- if (!isClickEvent || internal.initialHits.includes(eventObject)) {
1449
+ if (!isClickEvent || initialHits.includes(eventObject)) {
1314
1450
  pointerMissed(
1315
1451
  event,
1316
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1452
+ internal.interaction.filter((object) => !initialHits.includes(object))
1317
1453
  );
1318
1454
  handler(data);
1319
1455
  }
1320
1456
  } else {
1321
- if (isClickEvent && internal.initialHits.includes(eventObject)) {
1457
+ if (isClickEvent && initialHits.includes(eventObject)) {
1322
1458
  pointerMissed(
1323
1459
  event,
1324
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1460
+ internal.interaction.filter((object) => !initialHits.includes(object))
1325
1461
  );
1326
1462
  }
1327
1463
  }
@@ -1330,7 +1466,15 @@ function createEvents(store) {
1330
1466
  handleIntersects(hits, event, delta, onIntersect);
1331
1467
  };
1332
1468
  }
1333
- return { handlePointer };
1469
+ function flushDeferredPointers() {
1470
+ const { internal, events } = store.getState();
1471
+ if (!events.frameTimedRaycasts) return;
1472
+ for (const [pointerId, event] of internal.pointerDirty) {
1473
+ processDeferredPointer(event, pointerId);
1474
+ }
1475
+ internal.pointerDirty.clear();
1476
+ }
1477
+ return { handlePointer, flushDeferredPointers, processDeferredPointer };
1334
1478
  }
1335
1479
  const DOM_EVENTS = {
1336
1480
  onClick: ["click", false],
@@ -1349,10 +1493,15 @@ const DOM_EVENTS = {
1349
1493
  onLostPointerCapture: ["lostpointercapture", true]
1350
1494
  };
1351
1495
  function createPointerEvents(store) {
1352
- const { handlePointer } = createEvents(store);
1496
+ const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
1497
+ let nextXRPointerId = XR_POINTER_ID_START;
1498
+ const xrPointers = /* @__PURE__ */ new Map();
1353
1499
  return {
1354
1500
  priority: 1,
1355
1501
  enabled: true,
1502
+ frameTimedRaycasts: true,
1503
+ alwaysFireOnScroll: true,
1504
+ updateOnFrame: false,
1356
1505
  compute(event, state) {
1357
1506
  state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
1358
1507
  state.raycaster.setFromCamera(state.pointer, state.camera);
@@ -1362,11 +1511,33 @@ function createPointerEvents(store) {
1362
1511
  (acc, key) => ({ ...acc, [key]: handlePointer(key) }),
1363
1512
  {}
1364
1513
  ),
1365
- update: () => {
1514
+ update: (pointerId) => {
1515
+ const { events, internal } = store.getState();
1516
+ if (!events.handlers) return;
1517
+ if (pointerId !== void 0) {
1518
+ const event = internal.pointerDirty.get(pointerId);
1519
+ if (event) {
1520
+ internal.pointerDirty.delete(pointerId);
1521
+ processDeferredPointer(event, pointerId);
1522
+ } else if (internal.lastEvent?.current) {
1523
+ processDeferredPointer(internal.lastEvent.current, pointerId);
1524
+ }
1525
+ } else {
1526
+ flushDeferredPointers();
1527
+ if (internal.lastEvent?.current) {
1528
+ events.handlers.onPointerMove(internal.lastEvent.current);
1529
+ }
1530
+ }
1531
+ },
1532
+ flush: () => {
1366
1533
  const { events, internal } = store.getState();
1367
- if (internal.lastEvent?.current && events.handlers) events.handlers.onPointerMove(internal.lastEvent.current);
1534
+ flushDeferredPointers();
1535
+ if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
1536
+ events.handlers.onPointerMove(internal.lastEvent.current);
1537
+ }
1368
1538
  },
1369
1539
  connect: (target) => {
1540
+ if (!target) return;
1370
1541
  const { set, events } = store.getState();
1371
1542
  events.disconnect?.();
1372
1543
  set((state) => ({ events: { ...state.events, connected: target } }));
@@ -1390,6 +1561,32 @@ function createPointerEvents(store) {
1390
1561
  }
1391
1562
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
1392
1563
  }
1564
+ },
1565
+ registerPointer: (config) => {
1566
+ const pointerId = nextXRPointerId++;
1567
+ xrPointers.set(pointerId, config);
1568
+ const { internal } = store.getState();
1569
+ getPointerState(internal, pointerId);
1570
+ return pointerId;
1571
+ },
1572
+ unregisterPointer: (pointerId) => {
1573
+ xrPointers.delete(pointerId);
1574
+ const { internal } = store.getState();
1575
+ const pointerState = internal.pointerMap.get(pointerId);
1576
+ if (pointerState) {
1577
+ for (const [, hoveredObj] of pointerState.hovered) {
1578
+ const eventObject = hoveredObj.eventObject;
1579
+ const instance = eventObject.__r3f;
1580
+ if (instance?.eventCount) {
1581
+ const handlers = instance.handlers;
1582
+ const data = { ...hoveredObj, intersections: [] };
1583
+ handlers.onPointerOut?.(data);
1584
+ handlers.onPointerLeave?.(data);
1585
+ }
1586
+ }
1587
+ internal.pointerMap.delete(pointerId);
1588
+ }
1589
+ internal.pointerDirty.delete(pointerId);
1393
1590
  }
1394
1591
  };
1395
1592
  }
@@ -1703,7 +1900,7 @@ function shouldRun(job, now) {
1703
1900
  const minInterval = 1e3 / job.fps;
1704
1901
  const lastRun = job.lastRun ?? 0;
1705
1902
  const elapsed = now - lastRun;
1706
- if (elapsed < minInterval) return false;
1903
+ if (elapsed < minInterval - 1) return false;
1707
1904
  if (job.drop) {
1708
1905
  job.lastRun = now;
1709
1906
  } else {
@@ -2520,7 +2717,14 @@ const createStore = (invalidate, advance) => {
2520
2717
  frustum: new webgpu.Frustum(),
2521
2718
  autoUpdateFrustum: true,
2522
2719
  raycaster: null,
2523
- events: { priority: 1, enabled: true, connected: false },
2720
+ events: {
2721
+ priority: 1,
2722
+ enabled: true,
2723
+ connected: false,
2724
+ frameTimedRaycasts: true,
2725
+ alwaysFireOnScroll: true,
2726
+ updateOnFrame: false
2727
+ },
2524
2728
  scene: null,
2525
2729
  rootScene: null,
2526
2730
  xr: null,
@@ -2611,11 +2815,13 @@ const createStore = (invalidate, advance) => {
2611
2815
  },
2612
2816
  setError: (error) => set(() => ({ error })),
2613
2817
  error: null,
2614
- //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
2818
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
2615
2819
  uniforms: {},
2616
2820
  nodes: {},
2821
+ buffers: {},
2822
+ gpuStorage: {},
2617
2823
  textures: /* @__PURE__ */ new Map(),
2618
- postProcessing: null,
2824
+ renderPipeline: null,
2619
2825
  passes: {},
2620
2826
  _hmrVersion: 0,
2621
2827
  _sizeImperative: false,
@@ -2624,12 +2830,16 @@ const createStore = (invalidate, advance) => {
2624
2830
  internal: {
2625
2831
  // Events
2626
2832
  interaction: [],
2627
- hovered: /* @__PURE__ */ new Map(),
2628
2833
  subscribers: [],
2834
+ // Per-pointer state (new unified structure)
2835
+ pointerMap: /* @__PURE__ */ new Map(),
2836
+ pointerDirty: /* @__PURE__ */ new Map(),
2837
+ lastEvent: React__namespace.createRef(),
2838
+ // Deprecated but kept for backwards compatibility
2839
+ hovered: /* @__PURE__ */ new Map(),
2629
2840
  initialClick: [0, 0],
2630
2841
  initialHits: [],
2631
2842
  capturedMap: /* @__PURE__ */ new Map(),
2632
- lastEvent: React__namespace.createRef(),
2633
2843
  // Visibility tracking (onFramed, onOccluded, onVisible)
2634
2844
  visibilityRegistry: /* @__PURE__ */ new Map(),
2635
2845
  // Occlusion system (WebGPU only)
@@ -2717,14 +2927,16 @@ const createStore = (invalidate, advance) => {
2717
2927
  oldSize = size;
2718
2928
  oldDpr = viewport.dpr;
2719
2929
  updateCamera(camera, size);
2720
- if (canvasTarget) {
2930
+ if (internal.isSecondary && canvasTarget) {
2721
2931
  if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
2722
- const updateStyle = typeof HTMLCanvasElement !== "undefined" && canvasTarget.domElement instanceof HTMLCanvasElement;
2723
- canvasTarget.setSize(size.width, size.height, updateStyle);
2932
+ canvasTarget.setSize(size.width, size.height, false);
2724
2933
  } else {
2725
2934
  if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
2726
- const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
2727
- actualRenderer.setSize(size.width, size.height, updateStyle);
2935
+ actualRenderer.setSize(size.width, size.height, false);
2936
+ if (canvasTarget) {
2937
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
2938
+ canvasTarget.setSize(size.width, size.height, false);
2939
+ }
2728
2940
  }
2729
2941
  }
2730
2942
  if (camera !== oldCamera) {
@@ -15011,7 +15223,6 @@ function createRoot(canvas) {
15011
15223
  events,
15012
15224
  onCreated: onCreatedCallback,
15013
15225
  shadows = false,
15014
- textureColorSpace = webgpu.SRGBColorSpace,
15015
15226
  orthographic = false,
15016
15227
  frameloop = "always",
15017
15228
  dpr = [1, 2],
@@ -15026,6 +15237,7 @@ function createRoot(canvas) {
15026
15237
  _sizeProps,
15027
15238
  forceEven
15028
15239
  } = props;
15240
+ const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || webgpu.SRGBColorSpace;
15029
15241
  const state = store.getState();
15030
15242
  const defaultGLProps = {
15031
15243
  canvas,
@@ -15081,6 +15293,12 @@ function createRoot(canvas) {
15081
15293
  } else if (!wantsGL && !state.internal.actualRenderer) {
15082
15294
  renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
15083
15295
  if (!renderer.hasInitialized?.()) {
15296
+ const size2 = computeInitialSize(canvas, propsSize);
15297
+ if (size2.width > 0 && size2.height > 0) {
15298
+ const pixelRatio = calculateDpr(dpr);
15299
+ canvas.width = size2.width * pixelRatio;
15300
+ canvas.height = size2.height * pixelRatio;
15301
+ }
15084
15302
  await renderer.init();
15085
15303
  }
15086
15304
  const backend = renderer.backend;
@@ -15190,7 +15408,7 @@ function createRoot(canvas) {
15190
15408
  lastConfiguredProps.performance = performance;
15191
15409
  }
15192
15410
  if (!state.xr) {
15193
- const handleXRFrame = (timestamp, frame) => {
15411
+ const handleXRFrame = (timestamp, _frame) => {
15194
15412
  const state2 = store.getState();
15195
15413
  if (state2.frameloop === "never") return;
15196
15414
  advance(timestamp);
@@ -15226,15 +15444,22 @@ function createRoot(canvas) {
15226
15444
  const oldType = renderer.shadowMap.type;
15227
15445
  renderer.shadowMap.enabled = !!shadows;
15228
15446
  if (is.boo(shadows)) {
15229
- renderer.shadowMap.type = webgpu.PCFSoftShadowMap;
15447
+ renderer.shadowMap.type = webgpu.PCFShadowMap;
15230
15448
  } else if (is.str(shadows)) {
15449
+ if (shadows === "soft") {
15450
+ notifyDepreciated({
15451
+ heading: 'shadows="soft" is deprecated',
15452
+ body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
15453
+ link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
15454
+ });
15455
+ }
15231
15456
  const types = {
15232
15457
  basic: webgpu.BasicShadowMap,
15233
15458
  percentage: webgpu.PCFShadowMap,
15234
- soft: webgpu.PCFSoftShadowMap,
15459
+ soft: webgpu.PCFShadowMap,
15235
15460
  variance: webgpu.VSMShadowMap
15236
15461
  };
15237
- renderer.shadowMap.type = types[shadows] ?? webgpu.PCFSoftShadowMap;
15462
+ renderer.shadowMap.type = types[shadows] ?? webgpu.PCFShadowMap;
15238
15463
  } else if (is.obj(shadows)) {
15239
15464
  Object.assign(renderer.shadowMap, shadows);
15240
15465
  }
@@ -15250,13 +15475,24 @@ function createRoot(canvas) {
15250
15475
  if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
15251
15476
  lastConfiguredProps.textureColorSpace = textureColorSpace;
15252
15477
  }
15478
+ const r3fProps = ["textureColorSpace"];
15479
+ const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
15480
+ const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
15253
15481
  if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
15254
- applyProps(renderer, glConfig);
15482
+ const glProps = {};
15483
+ for (const key in glConfig) {
15484
+ if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
15485
+ }
15486
+ applyProps(renderer, glProps);
15255
15487
  }
15256
15488
  if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
15257
15489
  const currentRenderer = state.renderer;
15258
15490
  if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
15259
- applyProps(currentRenderer, rendererConfig);
15491
+ const rendererProps = {};
15492
+ for (const key in rendererConfig) {
15493
+ if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
15494
+ }
15495
+ applyProps(currentRenderer, rendererProps);
15260
15496
  }
15261
15497
  }
15262
15498
  const scheduler = getScheduler();
@@ -15282,6 +15518,18 @@ function createRoot(canvas) {
15282
15518
  system: true
15283
15519
  }
15284
15520
  );
15521
+ const unregisterEventsFlush = scheduler.register(
15522
+ () => {
15523
+ const state2 = store.getState();
15524
+ state2.events.flush?.();
15525
+ },
15526
+ {
15527
+ id: `${newRootId}_events`,
15528
+ rootId: newRootId,
15529
+ phase: "input",
15530
+ system: true
15531
+ }
15532
+ );
15285
15533
  const unregisterFrustum = scheduler.register(
15286
15534
  () => {
15287
15535
  const state2 = store.getState();
@@ -15316,7 +15564,7 @@ function createRoot(canvas) {
15316
15564
  const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
15317
15565
  if (userHandlesRender || state2.internal.priority) return;
15318
15566
  try {
15319
- if (state2.postProcessing?.render) state2.postProcessing.render();
15567
+ if (state2.renderPipeline?.render) state2.renderPipeline.render();
15320
15568
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
15321
15569
  } catch (error) {
15322
15570
  state2.setError(error instanceof Error ? error : new Error(String(error)));
@@ -15341,6 +15589,7 @@ function createRoot(canvas) {
15341
15589
  unregisterRoot: () => {
15342
15590
  unregisterRoot();
15343
15591
  unregisterCanvasTarget();
15592
+ unregisterEventsFlush();
15344
15593
  unregisterFrustum();
15345
15594
  unregisterVisibility();
15346
15595
  unregisterRender();
@@ -15506,9 +15755,13 @@ function PortalInner({ state = {}, children, container }) {
15506
15755
  const store = traditional.createWithEqualityFn((set, get) => ({ ...rest, set, get }));
15507
15756
  const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
15508
15757
  onMutate(previousRoot.getState());
15509
- previousRoot.subscribe(onMutate);
15510
15758
  return store;
15511
15759
  }, [previousRoot, container]);
15760
+ useIsomorphicLayoutEffect(() => {
15761
+ const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
15762
+ const unsubscribe = previousRoot.subscribe(onMutate);
15763
+ return unsubscribe;
15764
+ }, [previousRoot, usePortalStore]);
15512
15765
  return (
15513
15766
  // @ts-ignore, reconciler types are not maintained
15514
15767
  /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reconciler.createPortal(
@@ -15553,8 +15806,18 @@ function CanvasImpl({
15553
15806
  forceEven,
15554
15807
  ...props
15555
15808
  }) {
15556
- const { primaryCanvas, scheduler, ...rendererConfig } = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp) ? rendererProp : { primaryCanvas: void 0, scheduler: void 0 };
15557
- const renderer = Object.keys(rendererConfig).length > 0 ? rendererConfig : rendererProp;
15809
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
15810
+ let primaryCanvas;
15811
+ let scheduler;
15812
+ let renderer;
15813
+ if (isRendererConfig) {
15814
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
15815
+ primaryCanvas = pc;
15816
+ scheduler = sc;
15817
+ renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
15818
+ } else {
15819
+ renderer = rendererProp;
15820
+ }
15558
15821
  React__namespace.useMemo(() => extend(THREE), []);
15559
15822
  const Bridge = useBridge();
15560
15823
  const backgroundProps = React__namespace.useMemo(() => {
@@ -15729,6 +15992,7 @@ function CanvasImpl({
15729
15992
  queueMicrotask(() => {
15730
15993
  const rootEntry = _roots.get(canvas);
15731
15994
  if (rootEntry?.store) {
15995
+ console.log("[R3F] HMR detected \u2014 rebuilding nodes/uniforms");
15732
15996
  rootEntry.store.setState((state) => ({
15733
15997
  nodes: {},
15734
15998
  uniforms: {},
@@ -15740,8 +16004,7 @@ function CanvasImpl({
15740
16004
  if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && undefined) {
15741
16005
  const hot = undefined;
15742
16006
  hot.on("vite:afterUpdate", handleHMR);
15743
- return () => hot.dispose?.(() => {
15744
- });
16007
+ return () => hot.off?.("vite:afterUpdate", handleHMR);
15745
16008
  }
15746
16009
  if (typeof module !== "undefined" && module.hot) {
15747
16010
  const hot = module.hot;
@@ -15764,7 +16027,16 @@ function CanvasImpl({
15764
16027
  ...style
15765
16028
  },
15766
16029
  ...props,
15767
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx("canvas", { ref: canvasRef, id, className: "r3f-canvas", style: { display: "block" }, children: fallback }) })
16030
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
16031
+ "canvas",
16032
+ {
16033
+ ref: canvasRef,
16034
+ id,
16035
+ className: "r3f-canvas",
16036
+ style: { display: "block", width: "100%", height: "100%" },
16037
+ children: fallback
16038
+ }
16039
+ ) })
15768
16040
  }
15769
16041
  );
15770
16042
  }