UNPKG

react-three-fiber

Version:
1,317 lines (1,121 loc) 44.7 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var _construct = _interopDefault(require('@babel/runtime/helpers/construct')); var _objectWithoutPropertiesLoose = _interopDefault(require('@babel/runtime/helpers/objectWithoutPropertiesLoose')); var _extends = _interopDefault(require('@babel/runtime/helpers/extends')); var THREE = require('three'); var Reconciler = _interopDefault(require('react-reconciler')); var scheduler = require('scheduler'); var React = require('react'); var tinyEmitter = require('tiny-emitter'); var usePromise = _interopDefault(require('react-promise-suspense')); var _asyncToGenerator = _interopDefault(require('@babel/runtime/helpers/asyncToGenerator')); var expoGl = require('expo-gl'); var reactNative = require('react-native'); var expoThree = require('expo-three'); var version = "3.0.2"; function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); } function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } var roots = new Map(); var emptyObject = {}; var is = { obj: function obj(a) { return a === Object(a); }, 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, doesn't match if (typeof a !== typeof 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; } }; var globalEffects = []; function addEffect(callback) { globalEffects.push(callback); } function renderGl(state, timestamp, repeat, runGlobalEffects) { if (repeat === void 0) { repeat = 0; } if (runGlobalEffects === void 0) { runGlobalEffects = false; } // Run global effects if (runGlobalEffects) globalEffects.forEach(function (effect) { return effect(timestamp) && repeat++; }); // Run local effects var delta = state.current.clock.getDelta(); state.current.subscribers.forEach(function (sub) { return sub.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.scene, state.current.camera); return repeat; } var running = false; function renderLoop(timestamp) { running = true; var repeat = 0; // Run global effects globalEffects.forEach(function (effect) { return effect(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); }); if (repeat !== 0) return requestAnimationFrame(renderLoop); // Flag end of operation running = false; } function invalidate(state, frames) { if (state === void 0) { state = true; } if (frames === void 0) { frames = 2; } if (state === true) roots.forEach(function (root) { return root.containerInfo.__state.current.frames = frames; });else if (state && state.current) { if (state.current.vr) return; state.current.frames = frames; } if (!running) { running = true; requestAnimationFrame(renderLoop); } } var catalogue = {}; var extend = function extend(objects) { return void (catalogue = _extends({}, 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 = Object.keys(newProps).filter(function (key) { return is.equ(newProps[key], oldProps[key]); }); var handlers = Object.keys(newProps).filter(function (key) { return typeof newProps[key] === 'function' && key.startsWith('on'); }); var leftOvers = accumulative ? Object.keys(oldProps).filter(function (key) { return newProps[key] === void 0; }) : []; var filteredProps = [].concat(sameProps, ['children', 'key', 'ref']).reduce(function (acc, prop) { var _ = acc[prop], rest = _objectWithoutPropertiesLoose(acc, [prop].map(_toPropertyKey)); return rest; }, newProps); // Add left-overs as undefined props so they can be removed leftOvers.forEach(function (key) { return filteredProps[key] = undefined; }); if (Object.keys(filteredProps).length > 0) { Object.entries(filteredProps).forEach(function (_ref) { var key = _ref[0], value = _ref[1]; if (!handlers.includes(key)) { 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 if (target && target.set && (target.copy || target instanceof THREE.Layers)) { var _target; if (target.copy && target.constructor.name === value.constructor.name) target.copy(value);else if (Array.isArray(value)) (_target = target).set.apply(_target, value);else target.set(value); // Else, just overwrite the value } else root[key] = value; 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) { // Add interactive object to central container if (container && instance.raycast) { // Unless the only onUpdate is the only event present we flag the instance as interactive if (!(handlers.length === 1 && handlers[0] === 'onUpdate')) container.__interaction.push(instance); } // Add handlers to the instances handler-map instance.__handlers = handlers.reduce(function (acc, key) { var _extends2; return _extends({}, acc, (_extends2 = {}, _extends2[key.charAt(2).toLowerCase() + key.substr(3)] = newProps[key], _extends2)); }, {}); } // 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.__handlers && instance.__handlers.update) instance.__handlers.update(instance); } function createInstance(type, _ref2, container) { var _ref2$args = _ref2.args, args = _ref2$args === void 0 ? [] : _ref2$args, props = _objectWithoutPropertiesLoose(_ref2, ["args"]); var name = "" + type[0].toUpperCase() + type.slice(1); var instance; if (type === 'primitive') { instance = props.object; instance.__instance = true; } else { var target = catalogue[name] || THREE[name]; instance = is.arr(args) ? _construct(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; } // Apply initial props instance.__objects = []; instance.__container = container; // 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 "false" 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.attach) parentInstance[child.attach] = child;else 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; } } updateInstance(child); invalidateInstance(child); } } function insertBefore(parentInstance, child, beforeChild) { if (child) { if (child.isObject3D) { child.parent = parentInstance; child.dispatchEvent({ type: 'added' }); // TODO: the order is out of whack if data objects are present, has to be recalculated var index = parentInstance.children.indexOf(beforeChild); parentInstance.children = [].concat(parentInstance.children.slice(0, index), [child], parentInstance.children.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; parentInstance.__objects = parentInstance.__objects.filter(function (x) { return x !== child; }); // Remove attachment if (child.attach) parentInstance[child.attach] = null;else 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]]; } } invalidateInstance(child); scheduler.unstable_runWithPriority(scheduler.unstable_IdlePriority, function () { // Remove interactivity if (child.__container) child.__container.__interaction = child.__container.__interaction.filter(function (x) { return x !== child; }); // Remove nested child objects removeRecursive(child.__objects, child); removeRecursive(child.children, child, true); // Dispose item 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); 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 (typeof fiber.ref === 'function') fiber.ref(newInstance);else fiber.ref.current = newInstance; } } }); } var Renderer = Reconciler({ now: scheduler.unstable_now, createInstance: createInstance, removeChild: removeChild, appendChild: appendChild, insertBefore: insertBefore, supportsMutation: true, isPrimaryRenderer: false, // @ts-ignore schedulePassiveEffects: scheduler.unstable_scheduleCallback, cancelPassiveEffects: scheduler.unstable_cancelCallback, 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 if (instance.isObject3D) { // Common Threejs scene object applyProps(instance, newProps, oldProps, true); } 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(newProps, ["args"]); var _oldProps$args = oldProps.args, argsOld = _oldProps$args === void 0 ? [] : _oldProps$args, restOld = _objectWithoutPropertiesLoose(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); } }, getPublicInstance: function getPublicInstance(instance) { return instance; }, getRootHostContext: function getRootHostContext() { return emptyObject; }, getChildHostContext: function getChildHostContext() { return emptyObject; }, createTextInstance: function createTextInstance() {}, finalizeInitialChildren: function finalizeInitialChildren() { return false; }, prepareUpdate: function prepareUpdate() { return emptyObject; }, shouldDeprioritizeSubtree: function shouldDeprioritizeSubtree() { return false; }, prepareForCommit: function prepareForCommit() {}, resetAfterCommit: function resetAfterCommit() {}, shouldSetTextContent: function shouldSetTextContent() { return false; } }); function render(element, container, state) { var root = roots.get(container); if (!root) { container.__state = state; var newRoot = root = Renderer.createContainer(container, false, false); roots.set(container, newRoot); } Renderer.updateContainer(element, root, null, function () { return undefined; }); return Renderer.getPublicRootInstance(root); } function unmountComponentAtNode(container) { var root = roots.get(container); if (root) Renderer.updateContainer(null, root, null, function () { return void roots["delete"](container); }); } var hasSymbol = typeof Symbol === 'function' && Symbol["for"]; var REACT_PORTAL_TYPE = hasSymbol ? Symbol["for"]('react.portal') : 0xeaca; function createPortal(children, containerInfo, implementation, key) { if (key === void 0) { key = null; } 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, version: version, rendererPackageName: 'react-three-fiber', findHostInstanceByFiber: Renderer.findHostInstance }); function isOrthographicCamera(def) { return def.isOrthographicCamera; } function makeId(event) { return event.object.uuid + '/' + event.index; } var stateContext = 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$shadowMap = props.shadowMap, shadowMap = _props$shadowMap === void 0 ? false : _props$shadowMap, _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; // Local, reactive state var _useState = React.useState(false), ready = _useState[0], setReady = _useState[1]; var _useState2 = React.useState(function () { return new THREE.Vector2(); }), mouse = _useState2[0]; var _useState3 = React.useState(function () { var ray = new THREE.Raycaster(); if (raycaster) { var filter = raycaster.filter, raycasterProps = _objectWithoutPropertiesLoose(raycaster, ["filter"]); applyProps(ray, raycasterProps, {}); } return ray; }), defaultRaycaster = _useState3[0]; var _useState4 = React.useState(function () { var scene = new THREE.Scene(); scene.__interaction = []; scene.__objects = []; return scene; }), defaultScene = _useState4[0]; var _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, {}); return cam; }), defaultCam = _useState5[0], _setDefaultCamera = _useState5[1]; var _useState6 = React.useState(function () { return new THREE.Clock(); }), clock = _useState6[0]; // Public state var state = React.useRef({ ready: false, active: true, manual: 0, vr: vr, noEvents: noEvents, invalidateFrameloop: false, frames: 0, aspect: 0, subscribers: [], camera: defaultCam, scene: defaultScene, raycaster: defaultRaycaster, mouse: mouse, clock: clock, gl: gl, size: size, viewport: { width: 0, height: 0, factor: 0 }, initialClick: [0, 0], initialHits: [], pointer: new tinyEmitter.TinyEmitter(), captured: 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 () { // 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) { return handlePointerMove(event || {}); } }); // Writes locals into public state for distribution among subscribers, context, etc React.useLayoutEffect(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.noEvents = noEvents; }, [invalidateFrameloop, vr, noEvents, ready, size, defaultCam, gl]); // Update pixel ratio React.useLayoutEffect(function () { return void (pixelRatio && gl.setPixelRatio(pixelRatio)); }, [pixelRatio]); // Update shadowmap React.useLayoutEffect(function () { if (shadowMap) { gl.shadowMap.enabled = true; if (typeof shadowMap === 'object') Object.assign(gl, shadowMap);else gl.shadowMap.type = THREE.PCFSoftShadowMap; } }, [shadowMap]); // Adjusts default camera React.useLayoutEffect(function () { state.current.aspect = size.width / size.height; if (isOrthographicCamera(state.current.camera)) { state.current.viewport = { width: size.width, height: size.height, factor: 1 }; } else { var target = new THREE.Vector3(0, 0, 0); var distance = state.current.camera.position.distanceTo(target); var fov = THREE.Math.degToRad(state.current.camera.fov); // convert vertical fov to radians var height = 2 * Math.tan(fov / 2) * distance; // visible height var width = height * state.current.aspect; state.current.viewport = { width: width, height: height, factor: size.width / width }; } // #92 (https://github.com/drcmda/react-three-fiber/issues/92) // Sometimes automatic default camera adjustment isn't wanted behaviour if (updateDefaultCamera) { if (isOrthographicCamera(state.current.camera)) { state.current.camera.left = size.width / -2; state.current.camera.right = size.width / 2; state.current.camera.top = size.height / 2; state.current.camera.bottom = size.height / -2; } else { state.current.camera.aspect = state.current.aspect; } state.current.camera.updateProjectionMatrix(); // #178: https://github.com/react-spring/react-three-fiber/issues/178 // Update matrix world since the renderer is a frame late state.current.camera.updateMatrixWorld(); } gl.setSize(size.width, size.height); if (ready) invalidate(state); }, [size, updateDefaultCamera]); // This component is a bridge into the three render context, when it gets rendererd // we know we are ready to compile shaders, call subscribers, etc var IsReady = React.useCallback(function () { var activate = function activate() { return setReady(true); }; React.useEffect(function () { var result = onCreated && onCreated(state.current); return void (result && result.then ? result.then(activate) : activate()); }, []); return null; }, []); // Only trigger the context provider when necessary var sharedState = React.useRef(); React.useLayoutEffect(function () { var _state$current = state.current, ready = _state$current.ready, manual = _state$current.manual, vr = _state$current.vr, noEvents = _state$current.noEvents, invalidateFrameloop = _state$current.invalidateFrameloop, frames = _state$current.frames, subscribers = _state$current.subscribers, captured = _state$current.captured, initialClick = _state$current.initialClick, initialHits = _state$current.initialHits, props = _objectWithoutPropertiesLoose(_state$current, ["ready", "manual", "vr", "noEvents", "invalidateFrameloop", "frames", "subscribers", "captured", "initialClick", "initialHits"]); sharedState.current = props; }, [size, defaultCam]); // Render v-dom into scene React.useLayoutEffect(function () { render(React.createElement(stateContext.Provider, { value: sharedState.current }, typeof children === 'function' ? children(state.current) : children, React.createElement(IsReady, null)), defaultScene, state); }, [ready, children, sharedState.current]); React.useLayoutEffect(function () { if (ready) { // Start render-loop, either via RAF or setAnimationLoop for VR if (!state.current.vr) { invalidate(state); } else if (gl.vr && gl.setAnimationLoop) { gl.vr.enabled = true; gl.setAnimationLoop(function (t) { return renderGl(state, t, 0, true); }); } else console.warn('the gl instance does not support VR!'); } }, [ready]); // Dispose renderer on unmount React.useEffect(function () { return function () { if (state.current.gl) { state.current.gl.forceContextLoss(); state.current.gl.dispose(); state.current.gl = undefined; unmountComponentAtNode(state.current.scene); state.current.active = false; } }; }, []); /** Sets up defaultRaycaster */ var prepareRay = React.useCallback(function (event) { if (event.clientX !== void 0) { // #101: https://github.com/react-spring/react-three-fiber/issues/101 // The offset parent isn't taken into account by the resize observer // let x = event.clientX // let y = event.clientY // if (event.target && event.target.offsetParent) { // x -= event.target.offsetParent.offsetLeft // y -= event.target.offsetParent.offsetTop // } var _state$current$size = state.current.size, left = _state$current$size.left, right = _state$current$size.right, top = _state$current$size.top, bottom = _state$current$size.bottom; var clientX = event.clientX, clientY = event.clientY; if (typeof window !== 'undefined') { clientX += window.pageXOffset; clientY += window.pageYOffset; } mouse.set((clientX - left) / (right - left) * 2 - 1, -((clientY - top) / (bottom - top)) * 2 + 1); defaultRaycaster.setFromCamera(mouse, state.current.camera); } }, []); /** Intersects interaction objects using the event input */ var intersect = React.useCallback(function (event, prepare) { if (prepare === void 0) { prepare = true; } // Skip event handling when noEvents is set if (state.current.noEvents) return []; if (prepare) prepareRay(event); var seen = new Set(); var hits = []; // Intersect known handler objects and filter against duplicates var intersects = defaultRaycaster.intersectObjects(state.current.scene.__interaction, true).filter(function (item) { var id = makeId(item); if (seen.has(id)) return false; seen.add(id); return true; }); // #16031: (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 = intersects, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { var _ref; if (_isArray) { if (_i >= _iterator.length) break; _ref = _iterator[_i++]; } else { _i = _iterator.next(); if (_i.done) break; _ref = _i.value; } var _intersect = _ref; var eventObject = _intersect.object; // Bubble event up while (eventObject) { var handlers = eventObject.__handlers; if (handlers) hits.push(_extends({}, _intersect, { eventObject: eventObject })); eventObject = eventObject.parent; } } return hits; }, []); /** Calculates click deltas */ var calculateDistance = React.useCallback(function (event) { var dx = event.clientX - state.current.initialClick[0]; var dy = event.clientY - state.current.initialClick[1]; return Math.round(Math.sqrt(dx * dx + dy * dy)); }, []); /** Handles intersections by forwarding them to handlers */ var handleIntersects = React.useCallback(function (event, fn) { prepareRay(event); // If the interaction is captured, take the last known hit instead of raycasting again var hits = state.current.captured && event.type !== 'click' && event.type !== 'wheel' ? state.current.captured : intersect(event, false); if (hits.length) { var unprojectedPoint = new THREE.Vector3(mouse.x, mouse.y, 0).unproject(state.current.camera); var _delta = event.type === 'click' ? calculateDistance(event) : 0; var _loop = function _loop() { if (_isArray2) { if (_i2 >= _iterator2.length) return "break"; _ref2 = _iterator2[_i2++]; } else { _i2 = _iterator2.next(); if (_i2.done) return "break"; _ref2 = _i2.value; } var hit = _ref2; var stopped = { current: false }; fn(_extends({}, event, hit, { stopped: stopped, delta: _delta, unprojectedPoint: unprojectedPoint, ray: defaultRaycaster.ray, // Hijack stopPropagation, which just sets a flag stopPropagation: function stopPropagation() { return stopped.current = true; }, sourceEvent: event })); if (stopped.current === true) return "break"; }; for (var _iterator2 = hits, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { var _ref2; var _ret = _loop(); if (_ret === "break") break; } } return hits; }, []); var handlePointer = React.useCallback(function (name) { return function (event) { state.current.pointer.emit(name, event); // Collect hits var hits = handleIntersects(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, // which must must the initial target if (name !== 'click' || state.current.initialHits.includes(eventObject)) handlers[name](data); } }); // If a click yields no results, pass it back to the user as a miss if (name === 'pointerDown') { state.current.initialClick = [event.clientX, event.clientY]; state.current.initialHits = hits.map(function (hit) { return hit.eventObject; }); } if (name === 'click' && !hits.length && onPointerMissed) { if (calculateDistance(event) <= 2) onPointerMissed(); } }; }, [onPointerMissed]); var hovered = new Map(); var handlePointerMove = React.useCallback(function (event) { state.current.pointer.emit('pointerMove', event); var hits = handleIntersects(event, function (data) { var eventObject = data.eventObject; var handlers = eventObject.__handlers; // Check presence of handlers if (!handlers) return; // Call mouse move if (handlers.pointerMove) handlers.pointerMove(data); // Check if mouse enter or out is present if (handlers.pointerOver || handlers.pointerOut) { 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({}, data, { type: 'pointerover' })); } else if (hoveredItem.stopped.current) { // If the object was previously hovered and stopped, we shouldn't allow other items to proceed data.stopPropagation(); } } }); // Take care of unhover handlePointerCancel(event, hits); return hits; }, []); var handlePointerCancel = React.useCallback(function (event, hits) { state.current.pointer.emit('pointerCancel', event); if (!hits) hits = handleIntersects(event, function () { return null; }); Array.from(hovered.values()).forEach(function (data) { // 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 (i) { return i.eventObject === data.eventObject; }))) { var eventObject = data.eventObject; var handlers = eventObject.__handlers; if (handlers && handlers.pointerOut) handlers.pointerOut(_extends({}, data, { type: 'pointerout' })); hovered["delete"](makeId(data)); } }); }, []); return { onClick: handlePointer('click'), onWheel: handlePointer('wheel'), onPointerDown: handlePointer('pointerDown'), onPointerUp: handlePointer('pointerUp'), onPointerLeave: function onPointerLeave(e) { return handlePointerCancel(e, []); }, onPointerMove: handlePointerMove, onGotPointerCapture: function onGotPointerCapture(e) { return state.current.captured = intersect(e, false); }, onLostPointerCapture: function onLostPointerCapture(e) { return state.current.captured = undefined, handlePointerCancel(e); } }; }; function useFrame(callback, renderPriority) { if (renderPriority === void 0) { renderPriority = 0; } var _useContext = React.useContext(stateContext), subscribe = _useContext.subscribe; // Update ref var ref = React.useRef(callback); React.useLayoutEffect(function () { return void (ref.current = callback); }, [callback]); // Subscribe/unsub React.useEffect(function () { var unsubscribe = subscribe(ref, renderPriority); return function () { return unsubscribe(); }; }, [renderPriority]); } function useRender(callback, takeOver) { return useFrame(callback, takeOver ? 1 : 0); } function useThree() { return React.useContext(stateContext); } function useUpdate(callback, dependents, optionalRef) { var _useContext2 = React.useContext(stateContext), invalidate = _useContext2.invalidate; var localRef = React.useRef(); var ref = optionalRef ? optionalRef : localRef; React.useEffect(function () { if (ref.current) { callback(ref.current); invalidate(); } }, dependents); return ref; } function useResource(optionalRef) { var _useState = React.useState(false), _ = _useState[0], forceUpdate = _useState[1]; var localRef = React.useRef(undefined); var ref = optionalRef ? optionalRef : localRef; React.useEffect(function () { return void forceUpdate(function (i) { return !i; }); }, [ref.current]); return [ref, ref.current]; } var blackList = ['id', 'uuid', 'type', 'children', 'parent', 'matrix', 'matrixWorld', 'matrixWorldNeedsUpdate', 'modelViewMatrix', 'normalMatrix']; function prune(props) { var reducedProps = _extends({}, props); // Remove black listed props blackList.forEach(function (name) { return delete reducedProps[name]; }); // Remove functions Object.keys(reducedProps).forEach(function (name) { return typeof reducedProps[name] === 'function' && delete reducedProps[name]; }); // Prune materials and geometries if (reducedProps.material) reducedProps.material = prune(reducedProps.material); if (reducedProps.geometry) reducedProps.geometry = prune(reducedProps.geometry); // Return cleansed object return reducedProps; } function useLoader(Proto, url, extensions) { var loader = React.useMemo(function () { // Construct new loader var temp = new Proto(); // Run loader extensions if (extensions) extensions(temp); return temp; }, [Proto]); // Use suspense to load async assets var results = usePromise(function (Proto, url) { var urlArray = Array.isArray(url) ? url : [url]; return Promise.all(urlArray.map(function (url) { return new Promise(function (res) { return loader.load(url, function (data) { var objects = []; if (data.scene) data.scene.traverse(function (props) { return objects.push(prune(props)); }); data.__$ = objects; res(data); }); }); })); }, [Proto, url]); // Dispose objects on unmount React.useEffect(function () { return function () { return results.forEach(function (data) { if (data.dispose) data.dispose(); if (data.scene) data.scene.dispose(); }, []); }; }); // Temporary hack to make the new api backwards compatible for a while ... var isArray = Array.isArray(url); if (!isArray) { var _Object$assign; Object.assign(results[0], (_Object$assign = {}, _Object$assign[Symbol.iterator] = function () { console.warn('[value]=useLoader(...) is deprecated, please use value=useLoader(...) instead!'); return [results[0]][Symbol.iterator](); }, _Object$assign)); } // Return the object itself and a list of pruned props return isArray ? results : results[0]; } function clientXY(e) { e.clientX = e.nativeEvent.pageX; e.clientY = e.nativeEvent.pageY; return e; } var CLICK_DELTA = 20; var styles = { flex: 1 }; var IsReady = React.memo(function (_ref) { var gl = _ref.gl, props = _objectWithoutPropertiesLoose(_ref, ["gl"]); var events = useCanvas(_extends({}, props, { gl: gl })); var pointerDownCoords = null; var panResponder = React.useMemo(function () { return reactNative.PanResponder.create({ onStartShouldSetPanResponderCapture: function onStartShouldSetPanResponderCapture(e) { events.onGotPointerCapture(clientXY(e)); return true; }, onStartShouldSetPanResponder: function onStartShouldSetPanResponder() { return true; }, onMoveShouldSetPanResponder: function onMoveShouldSetPanResponder() { return true; }, onMoveShouldSetPanResponderCapture: function onMoveShouldSetPanResponderCapture() { return true; }, onPanResponderTerminationRequest: function onPanResponderTerminationRequest() { return true; }, onPanResponderStart: function onPanResponderStart(e) { pointerDownCoords = [e.nativeEvent.locationX, e.nativeEvent.locationY]; events.onPointerDown(clientXY(e)); }, onPanResponderMove: function onPanResponderMove(e) { return events.onPointerMove(clientXY(e)); }, onPanResponderEnd: function onPanResponderEnd(e) { events.onPointerUp(clientXY(e)); if (pointerDownCoords) { var xDelta = pointerDownCoords[0] - e.nativeEvent.locationX; var yDelta = pointerDownCoords[1] - e.nativeEvent.locationY; if (Math.sqrt(Math.pow(xDelta, 2) + Math.pow(yDelta, 2)) < CLICK_DELTA) { events.onClick(clientXY(e)); } } pointerDownCoords = null; }, onPanResponderRelease: function onPanResponderRelease(e) { return events.onPointerLeave(clientXY(e)); }, onPanResponderTerminate: function onPanResponderTerminate(e) { return events.onLostPointerCapture(clientXY(e)); }, onPanResponderReject: function onPanResponderReject(e) { return events.onLostPointerCapture(clientXY(e)); } }); }, []); return React.createElement(reactNative.View, _extends({}, panResponder.panHandlers, { style: reactNative.StyleSheet.absoluteFill })); }); var Canvas = React.memo(function (props) { var _useState = React.useState(null), size = _useState[0], setSize = _useState[1]; var _useState2 = React.useState(), renderer = _useState2[0], setRenderer = _useState2[1]; // Handle size changes var onLayout = function onLayout(e) { return setSize(e.nativeEvent.layout); }; // Fired when EXGL context is initialized var onContextCreate = /*#__PURE__*/ function () { var _ref2 = _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee(gl) { var pixelRatio, renderer, rendererRender; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: if (!props.onContextCreated) { _context.next = 3; break; } _context.next = 3; return props.onContextCreated(gl); case 3: if (props.shadowMap) { // https://github.com/expo/expo-three/issues/38 gl.createRenderbuffer = function () { return {}; }; } pixelRatio = reactNative.PixelRatio.get(); renderer = new expoThree.Renderer({ gl: gl, width: size.width / pixelRatio, height: size.height / pixelRatio, pixelRatio: pixelRatio }); // Bind previous render method to Renderer rendererRender = renderer.render.bind(renderer); renderer.render = function (scene, camera) { rendererRender(scene, camera); // End frame through the RN Bridge gl.endFrameEXP(); }; setRenderer(renderer); case 9: case "end": return _context.stop(); } } }, _callee); })); return function onContextCreate(_x) { return _ref2.apply(this, arguments); }; }(); var setNativeRef = function setNativeRef(ref) { if (props.nativeRef_EXPERIMENTAL && !props.nativeRef_EXPERIMENTAL.current) { props.nativeRef_EXPERIMENTAL.current = ref; } }; // 1. Ensure Size // 2. Ensure EXGLContext // 3. Call `useCanvas` return React.createElement(reactNative.View, { onLayout: onLayout, style: _extends({}, styles, props.style) }, size && React.createElement(expoGl.GLView, { nativeRef_EXPERIMENTAL: setNativeRef, onContextCreate: onContextCreate, style: reactNative.StyleSheet.absoluteFill }), size && renderer && React.createElement(IsReady, _extends({}, props, { size: size, gl: renderer }))); }); exports.Canvas = Canvas; exports.Renderer = Renderer; exports.addEffect = addEffect; exports.applyProps = applyProps; exports.createPortal = createPortal; exports.extend = extend; exports.invalidate = invalidate; exports.render = render; exports.renderGl = renderGl; exports.unmountComponentAtNode = unmountComponentAtNode; exports.useFrame = useFrame; exports.useLoader = useLoader; exports.useRender = useRender; exports.useResource = useResource; exports.useThree = useThree; exports.useUpdate = useUpdate;