@react-three/drei
Version:
useful add-ons for react-three-fiber
96 lines (91 loc) • 3.71 kB
JavaScript
import _extends from '@babel/runtime/helpers/esm/extends';
import * as THREE from 'three';
import * as React from 'react';
import { useThree, useFrame } from '@react-three/fiber';
import { RenderCubeTexture } from './RenderCubeTexture.js';
function Fisheye({
renderPriority = 1,
zoom = 0,
segments = 64,
children,
resolution = 896,
...props
}) {
const sphere = React.useRef(null);
const cubeApi = React.useRef(null);
// This isn't more than a simple sphere and a fixed orthographc camera
// pointing at it. A virtual scene is portalled into the environment map
// of its material. The cube-camera filming that scene is being synced to
// the portals default camera with the <UpdateCubeCamera> component.
const {
width,
height
} = useThree(state => state.size);
const [orthoC] = React.useState(() => new THREE.OrthographicCamera());
React.useLayoutEffect(() => {
orthoC.position.set(0, 0, 100);
orthoC.zoom = 100;
orthoC.left = width / -2;
orthoC.right = width / 2;
orthoC.top = height / 2;
orthoC.bottom = height / -2;
orthoC.updateProjectionMatrix();
}, [width, height]);
const radius = Math.sqrt(width * width + height * height) / 100 * (0.5 + zoom / 2);
const normal = new THREE.Vector3();
const sph = new THREE.Sphere(new THREE.Vector3(), radius);
const normalMatrix = new THREE.Matrix3();
const compute = React.useCallback((event, state, prev) => {
// Raycast from the render camera to the sphere and get the surface normal
// of the point hit in world space of the sphere scene
// We have to set the raycaster using the orthocam and pointer
// to perform sphere interscetions.
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
state.raycaster.setFromCamera(state.pointer, orthoC);
if (!state.raycaster.ray.intersectSphere(sph, normal)) return;else normal.normalize();
// Get the matrix for transforming normals into world space
normalMatrix.getNormalMatrix(cubeApi.current.camera.matrixWorld);
// Get the ray
cubeApi.current.camera.getWorldPosition(state.raycaster.ray.origin);
state.raycaster.ray.direction.set(0, 0, 1).reflect(normal);
state.raycaster.ray.direction.x *= -1; // flip across X to accommodate the "flip" of the env map
state.raycaster.ray.direction.applyNormalMatrix(normalMatrix).multiplyScalar(-1);
return undefined;
}, []);
useFrame(state => {
// Take over rendering
if (renderPriority) state.gl.render(sphere.current, orthoC);
}, renderPriority);
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("mesh", _extends({
ref: sphere
}, props, {
scale: radius
}), /*#__PURE__*/React.createElement("sphereGeometry", {
args: [1, segments, segments]
}), /*#__PURE__*/React.createElement("meshBasicMaterial", null, /*#__PURE__*/React.createElement(RenderCubeTexture, {
compute: compute,
attach: "envMap",
flip: true,
resolution: resolution,
ref: cubeApi
}, children, /*#__PURE__*/React.createElement(UpdateCubeCamera, {
api: cubeApi
})))));
}
function UpdateCubeCamera({
api
}) {
const t = new THREE.Vector3();
const r = new THREE.Quaternion();
const s = new THREE.Vector3();
const e = new THREE.Euler(0, Math.PI, 0);
useFrame(state => {
// Read out the cameras whereabouts, state.camera is the one *within* the portal
state.camera.matrixWorld.decompose(t, r, s);
// Apply its position and rotation, flip the Y axis
api.current.camera.position.copy(t);
api.current.camera.quaternion.setFromEuler(e).premultiply(r);
});
return null;
}
export { Fisheye };