UNPKG

@react-three/drei

Version:

useful add-ons for react-three-fiber

106 lines (102 loc) 4.11 kB
import _extends from '@babel/runtime/helpers/esm/extends'; import * as THREE from 'three'; import * as React from 'react'; import { useThree, createPortal, useFrame } from '@react-three/fiber'; import { useFBO } from './Fbo.js'; const RenderTexture = /* @__PURE__ */React.forwardRef(({ children, compute, width, height, samples = 8, renderPriority = 0, eventPriority = 0, frames = Infinity, stencilBuffer = false, depthBuffer = true, generateMipmaps = false, ...props }, forwardRef) => { const { size, viewport } = useThree(); const fbo = useFBO((width || size.width) * viewport.dpr, (height || size.height) * viewport.dpr, { samples, stencilBuffer, depthBuffer, generateMipmaps }); const [vScene] = React.useState(() => new THREE.Scene()); const uvCompute = React.useCallback((event, state, previous) => { var _fbo$texture, _previous$previousRoo; // Since this is only a texture it does not have an easy way to obtain the parent, which we // need to transform event coordinates to local coordinates. We use r3f internals to find the // next Object3D. let parent = (_fbo$texture = fbo.texture) == null || (_fbo$texture = _fbo$texture.__r3f.parent) == null ? void 0 : _fbo$texture.object; while (parent && !(parent instanceof THREE.Object3D)) { var _parent$__r3f$parent; parent = (_parent$__r3f$parent = parent.__r3f.parent) == null ? void 0 : _parent$__r3f$parent.object; } if (!parent) return false; // First we call the previous state-onion-layers compute, this is what makes it possible to nest portals if (!previous.raycaster.camera) previous.events.compute(event, previous, (_previous$previousRoo = previous.previousRoot) == null ? void 0 : _previous$previousRoo.getState()); // We run a quick check against the parent, if it isn't hit there's no need to raycast at all const [intersection] = previous.raycaster.intersectObject(parent); if (!intersection) return false; // We take that hits uv coords, set up this layers raycaster, et voilà, we have raycasting on arbitrary surfaces const uv = intersection.uv; if (!uv) return false; state.raycaster.setFromCamera(state.pointer.set(uv.x * 2 - 1, uv.y * 2 - 1), state.camera); }, []); React.useImperativeHandle(forwardRef, () => fbo.texture, [fbo]); return /*#__PURE__*/React.createElement(React.Fragment, null, createPortal(/*#__PURE__*/React.createElement(Container, { renderPriority: renderPriority, frames: frames, fbo: fbo }, children, /*#__PURE__*/React.createElement("group", { onPointerOver: () => null })), vScene, { events: { compute: compute || uvCompute, priority: eventPriority } }), /*#__PURE__*/React.createElement("primitive", _extends({ object: fbo.texture }, props))); }); // The container component has to be separate, it can not be inlined because "useFrame(state" when run inside createPortal will return // the portals own state which includes user-land overrides (custom cameras etc), but if it is executed in <RenderTexture>'s render function // it would return the default state. function Container({ frames, renderPriority, children, fbo }) { let count = 0; let oldAutoClear; let oldXrEnabled; let oldRenderTarget; let oldIsPresenting; useFrame(state => { if (frames === Infinity || count < frames) { oldAutoClear = state.gl.autoClear; oldXrEnabled = state.gl.xr.enabled; oldRenderTarget = state.gl.getRenderTarget(); oldIsPresenting = state.gl.xr.isPresenting; state.gl.autoClear = true; state.gl.xr.enabled = false; state.gl.xr.isPresenting = false; state.gl.setRenderTarget(fbo); state.gl.render(state.scene, state.camera); state.gl.setRenderTarget(oldRenderTarget); state.gl.autoClear = oldAutoClear; state.gl.xr.enabled = oldXrEnabled; state.gl.xr.isPresenting = oldIsPresenting; count++; } }, renderPriority); return /*#__PURE__*/React.createElement(React.Fragment, null, children); } export { RenderTexture };