106 lines
4.0 KiB
JavaScript
106 lines
4.0 KiB
JavaScript
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 };
|