UNPKG

@react-three/drei

Version:

useful add-ons for react-three-fiber

224 lines (220 loc) 7.38 kB
import _extends from '@babel/runtime/helpers/esm/extends'; import * as React from 'react'; import { Vector3, CylinderGeometry, Matrix4, WebGLRenderTarget, RGBAFormat, ShaderMaterial, DoubleSide, RepeatWrapping } from 'three'; import { useThree, useFrame } from '@react-three/fiber'; import { FullScreenQuad } from 'three-stdlib'; import { SpotLightMaterial } from '../materials/SpotLightMaterial.js'; import SpotlightShadowShader from '../helpers/glsl/DefaultSpotlightShadowShadows.glsl.js'; const isSpotLight = child => { return child == null ? void 0 : child.isSpotLight; }; function VolumetricMesh({ opacity = 1, radiusTop, radiusBottom, depthBuffer, color = 'white', distance = 5, angle = 0.15, attenuation = 5, anglePower = 5 }) { const mesh = React.useRef(null); const size = useThree(state => state.size); const camera = useThree(state => state.camera); const dpr = useThree(state => state.viewport.dpr); const [material] = React.useState(() => new SpotLightMaterial()); const [vec] = React.useState(() => new Vector3()); radiusTop = radiusTop === undefined ? 0.1 : radiusTop; radiusBottom = radiusBottom === undefined ? angle * 7 : radiusBottom; useFrame(() => { material.uniforms.spotPosition.value.copy(mesh.current.getWorldPosition(vec)); mesh.current.lookAt(mesh.current.parent.target.getWorldPosition(vec)); }); const geom = React.useMemo(() => { const geometry = new CylinderGeometry(radiusTop, radiusBottom, distance, 128, 64, true); geometry.applyMatrix4(new Matrix4().makeTranslation(0, -distance / 2, 0)); geometry.applyMatrix4(new Matrix4().makeRotationX(-Math.PI / 2)); return geometry; }, [distance, radiusTop, radiusBottom]); return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("mesh", { ref: mesh, geometry: geom, raycast: () => null }, /*#__PURE__*/React.createElement("primitive", { object: material, attach: "material", "uniforms-opacity-value": opacity, "uniforms-lightColor-value": color, "uniforms-attenuation-value": attenuation, "uniforms-anglePower-value": anglePower, "uniforms-depth-value": depthBuffer, "uniforms-cameraNear-value": camera.near, "uniforms-cameraFar-value": camera.far, "uniforms-resolution-value": depthBuffer ? [size.width * dpr, size.height * dpr] : [0, 0] }))); } function useCommon(spotlight, mesh, width, height, distance) { const [[pos, dir]] = React.useState(() => [new Vector3(), new Vector3()]); React.useLayoutEffect(() => { if (isSpotLight(spotlight.current)) { spotlight.current.shadow.mapSize.set(width, height); spotlight.current.shadow.needsUpdate = true; } else { throw new Error('SpotlightShadow must be a child of a SpotLight'); } }, [spotlight, width, height]); useFrame(() => { if (!spotlight.current) return; const A = spotlight.current.position; const B = spotlight.current.target.position; dir.copy(B).sub(A); var len = dir.length(); dir.normalize().multiplyScalar(len * distance); pos.copy(A).add(dir); mesh.current.position.copy(pos); mesh.current.lookAt(spotlight.current.target.position); }); } function SpotlightShadowWithShader({ distance = 0.4, alphaTest = 0.5, map, shader = SpotlightShadowShader, width = 512, height = 512, scale = 1, children, ...rest }) { const mesh = React.useRef(null); const spotlight = rest.spotlightRef; const debug = rest.debug; useCommon(spotlight, mesh, width, height, distance); const renderTarget = React.useMemo(() => new WebGLRenderTarget(width, height, { format: RGBAFormat, stencilBuffer: false // depthTexture: null! }), [width, height]); const uniforms = React.useRef({ uShadowMap: { value: map }, uTime: { value: 0 } }); React.useEffect(() => void (uniforms.current.uShadowMap.value = map), [map]); const fsQuad = React.useMemo(() => new FullScreenQuad(new ShaderMaterial({ uniforms: uniforms.current, vertexShader: /* glsl */` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: shader })), [shader]); React.useEffect(() => () => { fsQuad.material.dispose(); fsQuad.dispose(); }, [fsQuad]); React.useEffect(() => () => renderTarget.dispose(), [renderTarget]); useFrame(({ gl }, dt) => { uniforms.current.uTime.value += dt; gl.setRenderTarget(renderTarget); fsQuad.render(gl); gl.setRenderTarget(null); }); return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("mesh", { ref: mesh, scale: scale, castShadow: true }, /*#__PURE__*/React.createElement("planeGeometry", null), /*#__PURE__*/React.createElement("meshBasicMaterial", { transparent: true, side: DoubleSide, alphaTest: alphaTest, alphaMap: renderTarget.texture, "alphaMap-wrapS": RepeatWrapping, "alphaMap-wrapT": RepeatWrapping, opacity: debug ? 1 : 0 }, children))); } function SpotlightShadowWithoutShader({ distance = 0.4, alphaTest = 0.5, map, width = 512, height = 512, scale, children, ...rest }) { const mesh = React.useRef(null); const spotlight = rest.spotlightRef; const debug = rest.debug; useCommon(spotlight, mesh, width, height, distance); return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("mesh", { ref: mesh, scale: scale, castShadow: true }, /*#__PURE__*/React.createElement("planeGeometry", null), /*#__PURE__*/React.createElement("meshBasicMaterial", { transparent: true, side: DoubleSide, alphaTest: alphaTest, alphaMap: map, "alphaMap-wrapS": RepeatWrapping, "alphaMap-wrapT": RepeatWrapping, opacity: debug ? 1 : 0 }, children))); } function SpotLightShadow(props) { if (props.shader) return /*#__PURE__*/React.createElement(SpotlightShadowWithShader, props); return /*#__PURE__*/React.createElement(SpotlightShadowWithoutShader, props); } const SpotLight = /*#__PURE__*/React.forwardRef(({ // Volumetric opacity = 1, radiusTop, radiusBottom, depthBuffer, color = 'white', distance = 5, angle = 0.15, attenuation = 5, anglePower = 5, volumetric = true, debug = false, children, ...props }, ref) => { const spotlight = React.useRef(null); React.useImperativeHandle(ref, () => spotlight.current, []); return /*#__PURE__*/React.createElement("group", null, debug && spotlight.current && /*#__PURE__*/React.createElement("spotLightHelper", { args: [spotlight.current] }), /*#__PURE__*/React.createElement("spotLight", _extends({ ref: spotlight, angle: angle, color: color, distance: distance, castShadow: true }, props), volumetric && /*#__PURE__*/React.createElement(VolumetricMesh, { debug: debug, opacity: opacity, radiusTop: radiusTop, radiusBottom: radiusBottom, depthBuffer: depthBuffer, color: color, distance: distance, angle: angle, attenuation: attenuation, anglePower: anglePower })), children && /*#__PURE__*/React.cloneElement(children, { spotlightRef: spotlight, debug: debug })); }); export { SpotLight, SpotLightShadow };