UNPKG

react-three-fiber

Version:

A React renderer for Three.js (web and react-native)

1,379 lines (1,165 loc) 59.7 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _extends = require('@babel/runtime/helpers/extends'); var _objectWithoutPropertiesLoose = require('@babel/runtime/helpers/objectWithoutPropertiesLoose'); var _construct = require('@babel/runtime/helpers/construct'); var THREE = require('three'); var Reconciler = require('react-reconciler'); var scheduler = require('scheduler'); var React = require('react'); var tinyEmitter = require('tiny-emitter'); var useAsset = require('use-asset'); var CSS2DRenderer = require('three/examples/jsm/renderers/CSS2DRenderer'); var useMeasure = require('react-use-measure'); var mergeRefs = require('react-merge-refs'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n['default'] = e; return Object.freeze(n); } var _extends__default = /*#__PURE__*/_interopDefaultLegacy(_extends); var _objectWithoutPropertiesLoose__default = /*#__PURE__*/_interopDefaultLegacy(_objectWithoutPropertiesLoose); var _construct__default = /*#__PURE__*/_interopDefaultLegacy(_construct); var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE); var Reconciler__default = /*#__PURE__*/_interopDefaultLegacy(Reconciler); var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var useMeasure__default = /*#__PURE__*/_interopDefaultLegacy(useMeasure); var mergeRefs__default = /*#__PURE__*/_interopDefaultLegacy(mergeRefs); var name = "react-three-fiber"; var version = "5.3.14"; var roots = new Map(); var emptyObject = {}; var is = { obj: function obj(a) { return a === Object(a) && !is.arr(a); }, fun: function fun(a) { return typeof a === 'function'; }, str: function str(a) { return typeof a === 'string'; }, num: function num(a) { return typeof a === 'number'; }, und: function und(a) { return a === void 0; }, arr: function arr(a) { return Array.isArray(a); }, equ: function equ(a, b) { // Wrong type or one of the two undefined, doesn't match if (typeof a !== typeof b || !!a !== !!b) return false; // Atomic, just compare a against b if (is.str(a) || is.num(a) || is.obj(a)) return a === b; // Array, shallow compare first to see if it's a match if (is.arr(a) && a == b) return true; // Last resort, go through keys var i; for (i in a) { if (!(i in b)) return false; } for (i in b) { if (a[i] !== b[i]) return false; } return is.und(i) ? a === b : true; } }; function createSubs(callback, subs) { var index = subs.length; subs.push(callback); return function () { return void subs.splice(index, 1); }; } var globalEffects = []; var globalAfterEffects = []; var globalTailEffects = []; var addEffect = function addEffect(callback) { return createSubs(callback, globalEffects); }; var addAfterEffect = function addAfterEffect(callback) { return createSubs(callback, globalAfterEffects); }; var addTail = function addTail(callback) { return createSubs(callback, globalTailEffects); }; function renderGl(state, timestamp, repeat, runGlobalEffects) { if (repeat === void 0) { repeat = 0; } if (runGlobalEffects === void 0) { runGlobalEffects = false; } var i; // Run global effects if (runGlobalEffects) { for (i = 0; i < globalEffects.length; i++) { globalEffects[i](timestamp); repeat++; } } // Run local effects var delta = state.current.clock.getDelta(); for (i = 0; i < state.current.subscribers.length; i++) { state.current.subscribers[i].ref.current(state.current, delta); } // Decrease frame count state.current.frames = Math.max(0, state.current.frames - 1); repeat += !state.current.invalidateFrameloop ? 1 : state.current.frames; // Render content if (!state.current.manual && state.current.gl.render) state.current.gl.render(state.current.scene, state.current.camera); // Run global after-effects if (runGlobalEffects) { for (i = 0; i < globalAfterEffects.length; i++) { globalAfterEffects[i](timestamp); } } return repeat; } var running = false; function renderLoop(timestamp) { running = true; var repeat = 0; var i; // Run global effects for (i = 0; i < globalEffects.length; i++) { globalEffects[i](timestamp); repeat++; } roots.forEach(function (root) { var state = root.containerInfo.__state; // If the frameloop is invalidated, do not run another frame if (state.current.active && state.current.ready && (!state.current.invalidateFrameloop || state.current.frames > 0)) { repeat = renderGl(state, timestamp, repeat); } else { repeat = 0; } }); // Run global after-effects for (i = 0; i < globalAfterEffects.length; i++) { globalAfterEffects[i](timestamp); } if (repeat !== 0) { return requestAnimationFrame(renderLoop); } else { // Tail call effects, they are called when rendering stops for (i = 0; i < globalTailEffects.length; i++) { globalTailEffects[i](timestamp); } } // Flag end of operation running = false; } function invalidate(state, frames) { if (state === void 0) { state = true; } if (frames === void 0) { frames = 1; } if (state === true) { roots.forEach(function (root) { var state = root.containerInfo.__state; state.current.frames = state.current.ready ? state.current.frames + frames : frames; }); } else if (state && state.current) { if (state.current.vr) return; state.current.frames = state.current.ready ? state.current.frames + frames : frames; } if (!running) { running = true; requestAnimationFrame(renderLoop); } } function forceResize() { roots.forEach(function (root) { return root.containerInfo.__state.current.forceResize(); }); } var catalogue = {}; var extend = function extend(objects) { return void (catalogue = _extends__default['default']({}, catalogue, objects)); }; function applyProps(instance, newProps, oldProps, accumulative) { if (oldProps === void 0) { oldProps = {}; } if (accumulative === void 0) { accumulative = false; } // Filter equals, events and reserved props var container = instance.__container; var sameProps = []; var handlers = []; var i; var keys = Object.keys(newProps); for (i = 0; i < keys.length; i++) { if (is.equ(newProps[keys[i]], oldProps[keys[i]])) { sameProps.push(keys[i]); } // Event-handlers ... // are functions, that // start with "on", and // contain the name "Pointer", "Click", "ContextMenu", or "Wheel" if (is.fun(newProps[keys[i]]) && keys[i].startsWith('on')) { if (keys[i].includes('Pointer') || keys[i].includes('Click') || keys[i].includes('ContextMenu') || keys[i].includes('Wheel')) { handlers.push(keys[i]); } } } var leftOvers = []; keys = Object.keys(oldProps); if (accumulative) { for (i = 0; i < keys.length; i++) { if (newProps[keys[i]] === void 0) { leftOvers.push(keys[i]); } } } var toFilter = [].concat(sameProps, ['children', 'key', 'ref']); // Instances use "object" as a reserved identifier if (instance.__instance) toFilter.push('object'); var filteredProps = _extends__default['default']({}, newProps); // Removes sameProps and reserved props from newProps keys = Object.keys(filteredProps); for (i = 0; i < keys.length; i++) { if (toFilter.indexOf(keys[i]) > -1) { delete filteredProps[keys[i]]; } } // Add left-overs as undefined props so they can be removed keys = Object.keys(leftOvers); for (i = 0; i < keys.length; i++) { if (keys[i] !== 'children') { filteredProps[keys[i]] = undefined; } } var filteredPropsEntries = Object.entries(filteredProps); if (filteredPropsEntries.length > 0) { filteredPropsEntries.forEach(function (_ref) { var key = _ref[0], value = _ref[1]; if (!handlers.includes(key)) { var _instance$__container, _instance$__container2; var root = instance; var target = root[key]; if (key.includes('-')) { var entries = key.split('-'); target = entries.reduce(function (acc, key) { return acc[key]; }, instance); // If the target is atomic, it forces us to switch the root if (!(target && target.set)) { var _entries$reverse = entries.reverse(), _name = _entries$reverse[0], reverseEntries = _entries$reverse.slice(1); root = reverseEntries.reverse().reduce(function (acc, key) { return acc[key]; }, instance); key = _name; } } // Special treatment for objects with support for set/copy var isColorManagement = (_instance$__container = instance.__container) == null ? void 0 : (_instance$__container2 = _instance$__container.__state) == null ? void 0 : _instance$__container2.current.colorManagement; if (target && target.set && (target.copy || target instanceof THREE.Layers)) { // If value is an array it has got to be the set function if (Array.isArray(value)) { var _target; (_target = target).set.apply(_target, value); } // Test again target.copy(class) next ... else if (target.copy && value && value.constructor && target.constructor.name === value.constructor.name) { target.copy(value); } // If nothing else fits, just set the single value, ignore undefined // https://github.com/react-spring/react-three-fiber/issues/274 else if (value !== undefined) { target.set(value); // Auto-convert sRGB colors, for now ... // https://github.com/react-spring/react-three-fiber/issues/344 if (isColorManagement && target instanceof THREE.Color) { target.convertSRGBToLinear(); } } // Else, just overwrite the value } else { root[key] = value; // Auto-convert sRGB textures, for now ... // https://github.com/react-spring/react-three-fiber/issues/344 if (isColorManagement && root[key] instanceof THREE.Texture) { root[key].encoding = THREE.sRGBEncoding; } } invalidateInstance(instance); } }); // Preemptively delete the instance from the containers interaction if (accumulative && container && instance.raycast && instance.__handlers) { instance.__handlers = undefined; var index = container.__interaction.indexOf(instance); if (index > -1) container.__interaction.splice(index, 1); } // Prep interaction handlers if (handlers.length) { if (accumulative && container && instance.raycast) container.__interaction.push(instance); // Add handlers to the instances handler-map instance.__handlers = handlers.reduce(function (acc, key) { acc[key.charAt(2).toLowerCase() + key.substr(3)] = newProps[key]; return acc; }, {}); } // Call the update lifecycle when it is being updated, but only when it is part of the scene if (instance.parent) updateInstance(instance); } } function invalidateInstance(instance) { if (instance.__container && instance.__container.__state) invalidate(instance.__container.__state); } function updateInstance(instance) { if (instance.onUpdate) instance.onUpdate(instance); } function createInstance(type, _ref2, container, hostContext, internalInstanceHandle) { var _ref2$args = _ref2.args, args = _ref2$args === void 0 ? [] : _ref2$args, props = _objectWithoutPropertiesLoose__default['default'](_ref2, ["args"]); var name = "" + type[0].toUpperCase() + type.slice(1); var instance; if (type === 'primitive') { // Switch off dispose for primitive objects props = _extends__default['default']({ dispose: null }, props); instance = props.object; instance.__instance = true; instance.__dispose = instance.dispose; } else { var target = catalogue[name] || THREE__namespace[name]; if (!target) { throw "\"" + name + "\" is not part of the THREE namespace! Did you forget to extend it? See: https://github.com/pmndrs/react-three-fiber/blob/master/markdown/api.md#using-3rd-party-objects-declaratively"; } instance = is.arr(args) ? _construct__default['default'](target, args) : new target(args); } // Bind to the root container in case portals are being used // This is perhaps better for event management as we can keep them on a single instance while (container.__container) { container = container.__container; } // TODO: https://github.com/facebook/react/issues/17147 // If it's still not there it means the portal was created on a virtual node outside of react if (!roots.has(container)) { var fn = function fn(node) { if (!node["return"]) return node.stateNode && node.stateNode.containerInfo;else return fn(node["return"]); }; container = fn(internalInstanceHandle); } // Apply initial props instance.__objects = []; instance.__container = container; // Auto-attach geometries and materials if (name.endsWith('Geometry')) { props = _extends__default['default']({ attach: 'geometry' }, props); } else if (name.endsWith('Material')) { props = _extends__default['default']({ attach: 'material' }, props); } // It should NOT call onUpdate on object instanciation, because it hasn't been added to the // view yet. If the callback relies on references for instance, they won't be ready yet, this is // why it passes "true" here applyProps(instance, props, {}); return instance; } function appendChild(parentInstance, child) { if (child) { if (child.isObject3D) { parentInstance.add(child); } else { parentInstance.__objects.push(child); child.parent = parentInstance; // The attach attribute implies that the object attaches itself on the parent if (child.attachArray) { if (!is.arr(parentInstance[child.attachArray])) parentInstance[child.attachArray] = []; parentInstance[child.attachArray].push(child); } else if (child.attachObject) { if (!is.obj(parentInstance[child.attachObject[0]])) parentInstance[child.attachObject[0]] = {}; parentInstance[child.attachObject[0]][child.attachObject[1]] = child; } else if (child.attach) { parentInstance[child.attach] = child; } } updateInstance(child); invalidateInstance(child); } } function insertBefore(parentInstance, child, beforeChild) { if (child) { if (child.isObject3D) { child.parent = parentInstance; child.dispatchEvent({ type: 'added' }); var restSiblings = parentInstance.children.filter(function (sibling) { return sibling !== child; }); // TODO: the order is out of whack if data objects are present, has to be recalculated var index = restSiblings.indexOf(beforeChild); parentInstance.children = [].concat(restSiblings.slice(0, index), [child], restSiblings.slice(index)); updateInstance(child); } else { appendChild(parentInstance, child); } // TODO: order!!! invalidateInstance(child); } } function removeRecursive(array, parent, clone) { if (clone === void 0) { clone = false; } if (array) { // Three uses splice op's internally we may have to shallow-clone the array in order to safely remove items var target = clone ? [].concat(array) : array; target.forEach(function (child) { return removeChild(parent, child); }); } } function removeChild(parentInstance, child) { if (child) { if (child.isObject3D) { parentInstance.remove(child); } else { child.parent = null; if (parentInstance.__objects) parentInstance.__objects = parentInstance.__objects.filter(function (x) { return x !== child; }); // Remove attachment if (child.attachArray) { parentInstance[child.attachArray] = parentInstance[child.attachArray].filter(function (x) { return x !== child; }); } else if (child.attachObject) { delete parentInstance[child.attachObject[0]][child.attachObject[1]]; } else if (child.attach) { parentInstance[child.attach] = null; } } // Remove interactivity if (child.__container) child.__container.__interaction = child.__container.__interaction.filter(function (x) { return x !== child; }); invalidateInstance(child); // Allow objects to bail out of recursive dispose alltogether by passing dispose={null} if (child.dispose !== null) { scheduler.unstable_runWithPriority(scheduler.unstable_IdlePriority, function () { // Remove nested child objects removeRecursive(child.__objects, child); removeRecursive(child.children, child, true); // Dispose item if (child.dispose && child.type !== 'Scene') child.dispose();else if (child.__dispose) child.__dispose(); // Remove references delete child.__container; delete child.__objects; }); } } } function switchInstance(instance, type, newProps, fiber) { var parent = instance.parent; var newInstance = createInstance(type, newProps, instance.__container, null, fiber); removeChild(parent, instance); appendChild(parent, newInstance) // This evil hack switches the react-internal fiber node // https://github.com/facebook/react/issues/14983 // https://github.com/facebook/react/pull/15021 ; [fiber, fiber.alternate].forEach(function (fiber) { if (fiber !== null) { fiber.stateNode = newInstance; if (fiber.ref) { if (is.fun(fiber.ref)) fiber.ref(newInstance);else fiber.ref.current = newInstance; } } }); } var Renderer = Reconciler__default['default']({ now: scheduler.unstable_now, createInstance: createInstance, removeChild: removeChild, appendChild: appendChild, insertBefore: insertBefore, warnsIfNotActing: true, supportsMutation: true, isPrimaryRenderer: false, scheduleTimeout: is.fun(setTimeout) ? setTimeout : undefined, cancelTimeout: is.fun(clearTimeout) ? clearTimeout : undefined, // @ts-ignore setTimeout: is.fun(setTimeout) ? setTimeout : undefined, // @ts-ignore clearTimeout: is.fun(clearTimeout) ? clearTimeout : undefined, noTimeout: -1, appendInitialChild: appendChild, appendChildToContainer: appendChild, removeChildFromContainer: removeChild, insertInContainerBefore: insertBefore, commitUpdate: function commitUpdate(instance, updatePayload, type, oldProps, newProps, fiber) { if (instance.__instance && newProps.object && newProps.object !== instance) { // <instance object={...} /> where the object reference has changed switchInstance(instance, type, newProps, fiber); } else { // This is a data object, let's extract critical information about it var _newProps$args = newProps.args, argsNew = _newProps$args === void 0 ? [] : _newProps$args, restNew = _objectWithoutPropertiesLoose__default['default'](newProps, ["args"]); var _oldProps$args = oldProps.args, argsOld = _oldProps$args === void 0 ? [] : _oldProps$args, restOld = _objectWithoutPropertiesLoose__default['default'](oldProps, ["args"]); // If it has new props or arguments, then it needs to be re-instanciated var hasNewArgs = argsNew.some(function (value, index) { return is.obj(value) ? Object.entries(value).some(function (_ref3) { var key = _ref3[0], val = _ref3[1]; return val !== argsOld[index][key]; }) : value !== argsOld[index]; }); if (hasNewArgs) { // Next we create a new instance and append it again switchInstance(instance, type, newProps, fiber); } else { // Otherwise just overwrite props applyProps(instance, restNew, restOld, true); } } }, hideInstance: function hideInstance(instance) { if (instance.isObject3D) { instance.visible = false; invalidateInstance(instance); } }, unhideInstance: function unhideInstance(instance, props) { if (instance.isObject3D && props.visible == null || props.visible) { instance.visible = true; invalidateInstance(instance); } }, hideTextInstance: function hideTextInstance() { throw new Error('Text is not allowed in the react-three-fibre tree. You may have extraneous whitespace between components.'); }, getPublicInstance: function getPublicInstance(instance) { return instance; }, getRootHostContext: function getRootHostContext() { return emptyObject; }, getChildHostContext: function getChildHostContext() { return emptyObject; }, createTextInstance: function createTextInstance() {}, finalizeInitialChildren: function finalizeInitialChildren(instance) { // https://github.com/facebook/react/issues/20271 // Returning true will trigger commitMount return instance.__handlers; }, commitMount: function commitMount(instance) /*, type, props*/ { // https://github.com/facebook/react/issues/20271 // This will make sure events are only added once to the central container var container = instance.__container; if (container && instance.raycast && instance.__handlers) container.__interaction.push(instance); }, prepareUpdate: function prepareUpdate() { return emptyObject; }, shouldDeprioritizeSubtree: function shouldDeprioritizeSubtree() { return false; }, prepareForCommit: function prepareForCommit() { return null; }, preparePortalMount: function preparePortalMount() { return null; }, resetAfterCommit: function resetAfterCommit() {}, shouldSetTextContent: function shouldSetTextContent() { return false; }, clearContainer: function clearContainer() { return false; } }); var hasSymbol = is.fun(Symbol) && Symbol["for"]; var REACT_PORTAL_TYPE = hasSymbol ? Symbol["for"]('react.portal') : 0xeaca; function render(element, container, state) { var root = roots.get(container); if (!root) { container.__state = state; // @ts-ignore var newRoot = root = Renderer.createContainer(container, state !== undefined && state.current.concurrent ? 2 : 0, false, // @ts-ignore null); roots.set(container, newRoot); } Renderer.updateContainer(element, root, null, function () { return undefined; }); return Renderer.getPublicRootInstance(root); } function unmountComponentAtNode(container, callback) { var root = roots.get(container); if (root) { Renderer.updateContainer(null, root, null, function () { roots["delete"](container); if (callback) callback(container); }); } } function createPortal(children, containerInfo, implementation, key) { if (key === void 0) { key = null; } if (!containerInfo.__objects) containerInfo.__objects = []; return { $$typeof: REACT_PORTAL_TYPE, key: key == null ? null : '' + key, children: children, containerInfo: containerInfo, implementation: implementation }; } Renderer.injectIntoDevTools({ bundleType: process.env.NODE_ENV === 'production' ? 0 : 1, //@ts-ignore findHostInstanceByFiber: function findHostInstanceByFiber() { return null; }, version: version, rendererPackageName: name }); var threeTypes = /*#__PURE__*/Object.freeze({ __proto__: null }); function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } it = o[Symbol.iterator](); return it.next.bind(it); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function isOrthographicCamera(def) { return def.isOrthographicCamera; } function makeId(event) { return (event.eventObject || event.object).uuid + '/' + event.index; } var stateContext = /*#__PURE__*/React.createContext({}); var useCanvas = function useCanvas(props) { var children = props.children, gl = props.gl, camera = props.camera, orthographic = props.orthographic, raycaster = props.raycaster, size = props.size, pixelRatio = props.pixelRatio, _props$vr = props.vr, vr = _props$vr === void 0 ? false : _props$vr, _props$concurrent = props.concurrent, concurrent = _props$concurrent === void 0 ? false : _props$concurrent, _props$shadowMap = props.shadowMap, shadowMap = _props$shadowMap === void 0 ? false : _props$shadowMap, _props$colorManagemen = props.colorManagement, colorManagement = _props$colorManagemen === void 0 ? true : _props$colorManagemen, _props$invalidateFram = props.invalidateFrameloop, invalidateFrameloop = _props$invalidateFram === void 0 ? false : _props$invalidateFram, _props$updateDefaultC = props.updateDefaultCamera, updateDefaultCamera = _props$updateDefaultC === void 0 ? true : _props$updateDefaultC, _props$noEvents = props.noEvents, noEvents = _props$noEvents === void 0 ? false : _props$noEvents, onCreated = props.onCreated, onPointerMissed = props.onPointerMissed, forceResize = props.forceResize; // Local, reactive state var _React$useState = React.useState(false), ready = _React$useState[0], setReady = _React$useState[1]; var _React$useState2 = React.useState(function () { return new THREE.Vector2(); }), mouse = _React$useState2[0]; var _React$useState3 = React.useState(function () { var ray = new THREE.Raycaster(); if (raycaster) { var filter = raycaster.filter, computeOffsets = raycaster.computeOffsets, raycasterProps = _objectWithoutPropertiesLoose__default['default'](raycaster, ["filter", "computeOffsets"]); applyProps(ray, raycasterProps, {}); } return ray; }), defaultRaycaster = _React$useState3[0]; var _React$useState4 = React.useState(function () { var scene = new THREE.Scene(); scene.__interaction = []; scene.__objects = []; return scene; }), defaultScene = _React$useState4[0]; var _React$useState5 = React.useState(function () { var cam = orthographic ? new THREE.OrthographicCamera(0, 0, 0, 0, 0.1, 1000) : new THREE.PerspectiveCamera(75, 0, 0.1, 1000); cam.position.z = 5; if (camera) applyProps(cam, camera, {}); // Always look at [0, 0, 0] cam.lookAt(0, 0, 0); return cam; }), defaultCam = _React$useState5[0], _setDefaultCamera = _React$useState5[1]; var _React$useState6 = React.useState(function () { return new THREE.Clock(); }), clock = _React$useState6[0]; // Public state var state = React.useRef({ ready: false, active: true, manual: 0, colorManagement: colorManagement, vr: vr, concurrent: concurrent, noEvents: noEvents, invalidateFrameloop: false, frames: 0, aspect: 0, subscribers: [], camera: defaultCam, scene: defaultScene, raycaster: defaultRaycaster, mouse: mouse, clock: clock, gl: gl, size: size, viewport: null, initialClick: [0, 0], initialHits: [], pointer: new tinyEmitter.TinyEmitter(), captured: undefined, events: undefined, subscribe: function subscribe(ref, priority) { if (priority === void 0) { priority = 0; } // If this subscription was given a priority, it takes rendering into its own hands // For that reason we switch off automatic rendering and increase the manual flag // As long as this flag is positive (there could be multiple render subscription) // ..there can be no internal rendering at all if (priority) state.current.manual++; state.current.subscribers.push({ ref: ref, priority: priority }); // Sort layers from lowest to highest, meaning, highest priority renders last (on top of the other frames) state.current.subscribers = state.current.subscribers.sort(function (a, b) { return a.priority - b.priority; }); return function () { var _state$current; if ((_state$current = state.current) == null ? void 0 : _state$current.subscribers) { // Decrease manual flag if this subscription had a priority if (priority) state.current.manual--; state.current.subscribers = state.current.subscribers.filter(function (s) { return s.ref !== ref; }); } }; }, setDefaultCamera: function setDefaultCamera(camera) { return _setDefaultCamera(camera); }, invalidate: function invalidate$1() { return invalidate(state); }, intersect: function intersect(event, prepare) { if (event === void 0) { event = {}; } if (prepare === void 0) { prepare = true; } return handlePointerMove(event, prepare); }, forceResize: forceResize }); var position = new THREE.Vector3(); var getCurrentViewport = React.useCallback(function (camera, target) { if (camera === void 0) { camera = state.current.camera; } if (target === void 0) { target = new THREE.Vector3(0, 0, 0); } var _state$current$size = state.current.size, width = _state$current$size.width, height = _state$current$size.height; var distance = camera.getWorldPosition(position).distanceTo(target); if (isOrthographicCamera(camera)) { return { width: width / camera.zoom, height: height / camera.zoom, factor: 1, distance: distance }; } else { var fov = camera.fov * Math.PI / 180; // convert vertical fov to radians var h = 2 * Math.tan(fov / 2) * distance; // visible height var w = h * (width / height); return { width: w, height: h, factor: width / w, distance: distance }; } }, []); // Writes locals into public state for distribution among subscribers, context, etc React.useMemo(function () { state.current.ready = ready; state.current.size = size; state.current.camera = defaultCam; state.current.invalidateFrameloop = invalidateFrameloop; state.current.vr = vr; state.current.gl = gl; state.current.concurrent = concurrent; state.current.noEvents = noEvents; // Make viewport backwards compatible state.current.viewport = getCurrentViewport; // eslint-disable-next-line react-hooks/exhaustive-deps }, [invalidateFrameloop, vr, concurrent, noEvents, ready, size, defaultCam, gl]); // Adjusts default camera React.useMemo(function () { state.current.aspect = size.width / size.height; // Assign viewport props to the function Object.assign(state.current.viewport, getCurrentViewport()); // https://github.com/drcmda/react-three-fiber/issues/92 // Sometimes automatic default camera adjustment isn't wanted behaviour if (updateDefaultCamera) { if (isOrthographicCamera(defaultCam)) { defaultCam.left = size.width / -2; defaultCam.right = size.width / 2; defaultCam.top = size.height / 2; defaultCam.bottom = size.height / -2; } else { defaultCam.aspect = state.current.aspect; } defaultCam.updateProjectionMatrix(); // https://github.com/react-spring/react-three-fiber/issues/178 // Update matrix world since the renderer is a frame late defaultCam.updateMatrixWorld(); } gl.setSize(size.width, size.height); if (ready) invalidate(state); // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultCam, gl, size, updateDefaultCamera, ready]); // Only trigger the context provider when necessary var sharedState = React.useRef(null); React.useMemo(function () { var _state$current2 = state.current, ready = _state$current2.ready, manual = _state$current2.manual, vr = _state$current2.vr, noEvents = _state$current2.noEvents, invalidateFrameloop = _state$current2.invalidateFrameloop, frames = _state$current2.frames, subscribers = _state$current2.subscribers, captured = _state$current2.captured, initialClick = _state$current2.initialClick, initialHits = _state$current2.initialHits, props = _objectWithoutPropertiesLoose__default['default'](_state$current2, ["ready", "manual", "vr", "noEvents", "invalidateFrameloop", "frames", "subscribers", "captured", "initialClick", "initialHits"]); sharedState.current = props; // eslint-disable-next-line react-hooks/exhaustive-deps }, [size, defaultCam]); // Update pixel ratio React.useLayoutEffect(function () { if (pixelRatio) { if (Array.isArray(pixelRatio)) gl.setPixelRatio(Math.max(Math.min(pixelRatio[0], window.devicePixelRatio), pixelRatio[1]));else gl.setPixelRatio(pixelRatio); } }, [gl, pixelRatio]); // Update shadow map React.useLayoutEffect(function () { if (shadowMap) { gl.shadowMap.enabled = true; if (typeof shadowMap === 'object') Object.assign(gl.shadowMap, shadowMap);else gl.shadowMap.type = THREE.PCFSoftShadowMap; } if (colorManagement) { gl.toneMapping = THREE.ACESFilmicToneMapping; gl.outputEncoding = THREE.sRGBEncoding; } // eslint-disable-next-line react-hooks/exhaustive-deps }, [shadowMap, colorManagement]); /** Events ------------------------------------------------------------------------------------------------ */ var hovered = React.useMemo(function () { return new Map(); }, []); var temp = new THREE.Vector3(); /** Sets up defaultRaycaster */ var prepareRay = React.useCallback(function (event) { // https://github.com/pmndrs/react-three-fiber/pull/782 // Events trigger outside of canvas when moved var offsets = (raycaster == null ? void 0 : raycaster.computeOffsets == null ? void 0 : raycaster.computeOffsets(event, sharedState.current)) || event.nativeEvent; if (offsets) { var offsetX = offsets.offsetX, offsetY = offsets.offsetY; var _state$current$size2 = state.current.size, width = _state$current$size2.width, height = _state$current$size2.height; mouse.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1); defaultRaycaster.setFromCamera(mouse, state.current.camera); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); /** Intersects interaction objects using the event input */ var intersect = React.useCallback(function (filter) { // Skip event handling when noEvents is set if (state.current.noEvents) return []; var seen = new Set(); var hits = []; // Allow callers to eliminate event objects var eventsObjects = filter ? filter(state.current.scene.__interaction) : state.current.scene.__interaction; // Intersect known handler objects and filter against duplicates var intersects = defaultRaycaster.intersectObjects(eventsObjects, true).filter(function (item) { var id = makeId(item); if (seen.has(id)) return false; seen.add(id); return true; }); // https://github.com/mrdoob/three.js/issues/16031 // Allow custom userland intersect sort order if (raycaster && raycaster.filter && sharedState.current) { intersects = raycaster.filter(intersects, sharedState.current); } for (var _iterator = _createForOfIteratorHelperLoose(intersects), _step; !(_step = _iterator()).done;) { var _intersect = _step.value; var eventObject = _intersect.object; // Bubble event up while (eventObject) { var handlers = eventObject.__handlers; if (handlers) hits.push(_extends__default['default']({}, _intersect, { eventObject: eventObject })); eventObject = eventObject.parent; } } return hits; }, // eslint-disable-next-line react-hooks/exhaustive-deps []); /** Calculates click deltas */ var calculateDistance = React.useCallback(function (event) { var dx = event.nativeEvent.offsetX - state.current.initialClick[0]; var dy = event.nativeEvent.offsetY - state.current.initialClick[1]; return Math.round(Math.sqrt(dx * dx + dy * dy)); }, []); var handlePointerCancel = React.useCallback(function (event, hits, prepare) { if (prepare === void 0) { prepare = true; } state.current.pointer.emit('pointerCancel', event); if (prepare) prepareRay(event); Array.from(hovered.values()).forEach(function (hoveredObj) { // When no objects were hit or the the hovered object wasn't found underneath the cursor // we call onPointerOut and delete the object from the hovered-elements map if (hits && (!hits.length || !hits.find(function (hit) { return hit.object === hoveredObj.object && hit.index === hoveredObj.index; }))) { var eventObject = hoveredObj.eventObject; var handlers = eventObject.__handlers; hovered["delete"](makeId(hoveredObj)); if (handlers) { // Clear out intersects, they are outdated by now var data = _extends__default['default']({}, hoveredObj, { intersections: hits || [] }); if (handlers.pointerOut) handlers.pointerOut(_extends__default['default']({}, data, { type: 'pointerout' })); if (handlers.pointerLeave) handlers.pointerLeave(_extends__default['default']({}, data, { type: 'pointerleave' })); } } }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); /** Creates filtered intersects and returns an array of positive hits */ var getIntersects = React.useCallback(function (event, filter) { // Get fresh intersects var intersections = intersect(filter); // If the interaction is captured take that into account, the captured event has to be part of the intersects if (state.current.captured && event.type !== 'click' && event.type !== 'wheel') { state.current.captured.forEach(function (captured) { if (!intersections.find(function (hit) { return hit.eventObject === captured.eventObject; })) intersections.push(captured); }); } return intersections; }, // eslint-disable-next-line react-hooks/exhaustive-deps []); /** Handles intersections by forwarding them to handlers */ var handleIntersects = React.useCallback(function (intersections, event, fn) { // If anything has been found, forward it to the event listeners if (intersections.length) { (function () { var unprojectedPoint = temp.set(mouse.x, mouse.y, 0).unproject(state.current.camera); var delta = event.type === 'click' ? calculateDistance(event) : 0; var releasePointerCapture = function releasePointerCapture(id) { return event.target.releasePointerCapture(id); }; var localState = { stopped: false, captured: false }; var _loop = function _loop() { var hit = _step2.value; var setPointerCapture = function setPointerCapture(id) { // If the hit is going to be captured flag that we're in captured state if (!localState.captured) { localState.captured = true; // The captured hit array is reset to collect hits state.current.captured = []; } // Push hits to the array if (state.current.captured) { state.current.captured.push(hit); } // Call the original event now event.target.setPointerCapture(id); }; var raycastEvent = _extends__default['default']({}, event, hit, { intersections: intersections, stopped: localState.stopped, delta: delta, unprojectedPoint: unprojectedPoint, ray: defaultRaycaster.ray, camera: state.current.camera, // Hijack stopPropagation, which just sets a flag stopPropagation: function stopPropagation() { // https://github.com/react-spring/react-three-fiber/issues/596 // Events are not allowed to stop propagation if the pointer has been captured var cap = state.current.captured; if (!cap || cap.find(function (h) { return h.eventObject.id === hit.eventObject.id; })) { raycastEvent.stopped = localState.stopped = true; // Propagation is stopped, remove all other hover records // An event handler is only allowed to flush other handlers if it is hovered itself if (hovered.size && Array.from(hovered.values()).find(function (i) { return i.eventObject === hit.eventObject; })) { // Objects cannot flush out higher up objects that have already caught the event var higher = intersections.slice(0, intersections.indexOf(hit)); handlePointerCancel(raycastEvent, [].concat(higher, [hit])); } } }, target: _extends__default['default']({}, event.target, { setPointerCapture: setPointerCapture, releasePointerCapture: releasePointerCapture }), currentTarget: _extends__default['default']({}, event.currentTarget, { setPointerCapture: setPointerCapture, releasePointerCapture: releasePointerCapture }), sourceEvent: event }); fn(raycastEvent); // Event bubbling may be interrupted by stopPropagation if (localState.stopped === true) return "break"; }; for (var _iterator2 = _createForOfIteratorHelperLoose(intersections), _step2; !(_step2 = _iterator2()).done;) { var _ret = _loop(); if (_ret === "break") break; } })(); } return intersections; }, // eslint-disable-next-line react-hooks/exhaustive-deps []); var handlePointerMove = React.useCallback(function (event, prepare) { if (prepare === void 0) { prepare = true; } state.current.pointer.emit('pointerMove', event); if (prepare) prepareRay(event); var hits = getIntersects(event, // This is onPointerMove, we're only interested in events that exhibit this particular event function (objects) { return objects.filter(function (obj) { return ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(function (name) { return obj.__handlers['pointer' + name]; }); }); }); // Take care of unhover handlePointerCancel(event, hits); handleIntersects(hits, event, function (data) { var eventObject = data.eventObject; var handlers = eventObject.__handlers; // Check presence of handlers if (!handlers) return; // Check if mouse enter or out is present if (handlers.pointerOver || handlers.pointerEnter || handlers.pointerOut || handlers.pointerLeave) { var id = makeId(data); var hoveredItem = hovered.get(id); if (!hoveredItem) { // If the object wasn't previously hovered, book it and call its handler hovered.set(id, data); if (handlers.pointerOver) handlers.pointerOver(_extends__default['default']({}, data, { type: 'pointerover' })); if (handlers.pointerEnter) handlers.pointerEnter(_extends__default['default']({}, data, { type: 'pointerenter' })); } else if (hoveredItem.stopped) { // If the object was previously hovered and stopped, we shouldn't allow other items to proceed data.stopPropagation(); } } // Call mouse move if (handlers.pointerMove) handlers.pointerMove(data); }); return hits; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); var handlePointer = React.useCallback(function (name) { return function (event, prepare) { if (prepare === void 0) { prepare = true; } state.current.pointer.emit(name, event); if (prepare) prepareRay(event); var hits = getIntersects(event); handleIntersects(hits, event, function (data) { var eventObject = data.eventObject; var handlers = eventObject.__handlers; if (handlers && handlers[name]) { // Forward all events back to their respective handlers with the exception of click events, // which must use the initial target if (name !== 'click' && name !== 'contextMenu' && name !== 'doubleClick' || state.current.initialHits.includes(eventObject)) { handlers[name](data); pointerMissed(event, defaultScene.__interaction, function (object) { return object !== eventObject; }); } } }); // If a click yields no results, pass it back to the user as a miss if (name === 'pointerDown') { state.current.initialClick = [event.nativeEvent.offsetX, event.nativeEvent.offsetY]; state.current.initialHits = hits.map(function (hit) { return hit.eventObject; }); } if ((name === 'click' || name === 'contextMenu' || name === 'doubleClick') && !hits.length) { if (calculateDistance(event) <= 2) { pointerMissed(event, defaultScene.__interaction); if (onPointerMissed) onPointerMissed(); } } }; }, // eslint-disable-next-line react-hooks/exhaustive-deps [onPointerMissed, calculateDistance, getIntersects, handleIntersects, prepareRay]); React.useMemo(function () { state.current.events = { onClick: handlePointer('click'), onContextMenu: handlePointer('contextMenu'), onDoubleClick: handlePointer('doubleClick'), onWheel: handlePointer('wheel'), onPointerDown: handlePointer('pointerDown'), onPointerUp: handlePointer('pointerUp'), onPointerLeave: function onPointerLeave(e) { return handlePointerCancel(e, []); }, onPointerMove: handlePointerMove, // onGotPointerCapture is not needed any longer because the behaviour is hacked into // the event itself (see handleIntersects). But in order for non-web targets to simulate // it we keep the legacy event, which simply flags all current intersects as captured onGotPointerCaptureLegacy: function onGotPointerCaptureLegacy(e) { return state.current.captured = intersect(e); }, onLostPointerCapture: function onLostPointerCapture(e) { return state.current.captured = undefined, handlePointerCancel(e); } }; }, [handlePointer, intersect, handlePointerCancel, handlePointerMove]); /** Events ------------------------------------------------------------------------------------------------- */ // This component is a bridge into the three render context, when it gets rendered // we know we are ready to compile shaders, call subscribers, etc var Canvas = React.useCallback(function Canvas(props) { var activate = function activate() { return setReady(true); }; React.useEffect(function () { var result = onCreated && onCreated(state.current); if (result && result.then) result.then(activate);else activate(); }, []); return props.children; }, // The Canvas component has to be static, it should not be re-created ever // eslint-disable-next-line react-hooks/exhaustive-deps []); // Render v-dom into scene React.useLayoutEffect(function () { render( /*#__PURE__*/React.createElement(Canvas, null, /*#__PURE__*/React.createElement(stateContext.Provider, { value: sharedState.current }, typeof children === 'function' ? children(state.current) : children)), defaultScene, state); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ready, children, sharedState.current]); React.useLayoutEffect(function () { if (ready) { // Start render-loop, either via RAF or setAnimationLoop for VR if (!state.current.vr) { if (state.current.frames === 0) invalidate(state); } else if ((gl.xr || gl.vr) && gl.setAnimationLoop) { (gl.xr || gl.vr).enabled = true; gl.setAnimationLoop(function (t) { return renderGl(state, t, 0, true); }); } else { console.warn('the gl instance does not support VR!'); } } }, [gl, ready, invalidateFrameloop]); // Dispose renderer on unmount React.useEffect(function () { return