Spaces:
Running
Running
| import _extends from '@babel/runtime/helpers/esm/extends'; | |
| import * as React from 'react'; | |
| import * as ReactDOM from 'react-dom/client'; | |
| import { Vector3, DoubleSide, OrthographicCamera, PerspectiveCamera, Vector2 } from 'three'; | |
| import { useThree, useFrame } from '@react-three/fiber'; | |
| const v1 = /* @__PURE__ */new Vector3(); | |
| const v2 = /* @__PURE__ */new Vector3(); | |
| const v3 = /* @__PURE__ */new Vector3(); | |
| const v4 = /* @__PURE__ */new Vector2(); | |
| function defaultCalculatePosition(el, camera, size) { | |
| const objectPos = v1.setFromMatrixPosition(el.matrixWorld); | |
| objectPos.project(camera); | |
| const widthHalf = size.width / 2; | |
| const heightHalf = size.height / 2; | |
| return [objectPos.x * widthHalf + widthHalf, -(objectPos.y * heightHalf) + heightHalf]; | |
| } | |
| function isObjectBehindCamera(el, camera) { | |
| const objectPos = v1.setFromMatrixPosition(el.matrixWorld); | |
| const cameraPos = v2.setFromMatrixPosition(camera.matrixWorld); | |
| const deltaCamObj = objectPos.sub(cameraPos); | |
| const camDir = camera.getWorldDirection(v3); | |
| return deltaCamObj.angleTo(camDir) > Math.PI / 2; | |
| } | |
| function isObjectVisible(el, camera, raycaster, occlude) { | |
| const elPos = v1.setFromMatrixPosition(el.matrixWorld); | |
| const screenPos = elPos.clone(); | |
| screenPos.project(camera); | |
| v4.set(screenPos.x, screenPos.y); | |
| raycaster.setFromCamera(v4, camera); | |
| const intersects = raycaster.intersectObjects(occlude, true); | |
| if (intersects.length) { | |
| const intersectionDistance = intersects[0].distance; | |
| const pointDistance = elPos.distanceTo(raycaster.ray.origin); | |
| return pointDistance < intersectionDistance; | |
| } | |
| return true; | |
| } | |
| function objectScale(el, camera) { | |
| if (camera instanceof OrthographicCamera) { | |
| return camera.zoom; | |
| } else if (camera instanceof PerspectiveCamera) { | |
| const objectPos = v1.setFromMatrixPosition(el.matrixWorld); | |
| const cameraPos = v2.setFromMatrixPosition(camera.matrixWorld); | |
| const vFOV = camera.fov * Math.PI / 180; | |
| const dist = objectPos.distanceTo(cameraPos); | |
| const scaleFOV = 2 * Math.tan(vFOV / 2) * dist; | |
| return 1 / scaleFOV; | |
| } else { | |
| return 1; | |
| } | |
| } | |
| function objectZIndex(el, camera, zIndexRange) { | |
| if (camera instanceof PerspectiveCamera || camera instanceof OrthographicCamera) { | |
| const objectPos = v1.setFromMatrixPosition(el.matrixWorld); | |
| const cameraPos = v2.setFromMatrixPosition(camera.matrixWorld); | |
| const dist = objectPos.distanceTo(cameraPos); | |
| const A = (zIndexRange[1] - zIndexRange[0]) / (camera.far - camera.near); | |
| const B = zIndexRange[1] - A * camera.far; | |
| return Math.round(A * dist + B); | |
| } | |
| return undefined; | |
| } | |
| const epsilon = value => Math.abs(value) < 1e-10 ? 0 : value; | |
| function getCSSMatrix(matrix, multipliers, prepend = '') { | |
| let matrix3d = 'matrix3d('; | |
| for (let i = 0; i !== 16; i++) { | |
| matrix3d += epsilon(multipliers[i] * matrix.elements[i]) + (i !== 15 ? ',' : ')'); | |
| } | |
| return prepend + matrix3d; | |
| } | |
| const getCameraCSSMatrix = (multipliers => { | |
| return matrix => getCSSMatrix(matrix, multipliers); | |
| })([1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1]); | |
| const getObjectCSSMatrix = (scaleMultipliers => { | |
| return (matrix, factor) => getCSSMatrix(matrix, scaleMultipliers(factor), 'translate(-50%,-50%)'); | |
| })(f => [1 / f, 1 / f, 1 / f, 1, -1 / f, -1 / f, -1 / f, -1, 1 / f, 1 / f, 1 / f, 1, 1, 1, 1, 1]); | |
| function isRefObject(ref) { | |
| return ref && typeof ref === 'object' && 'current' in ref; | |
| } | |
| const Html = /* @__PURE__ */React.forwardRef(({ | |
| children, | |
| eps = 0.001, | |
| style, | |
| className, | |
| prepend, | |
| center, | |
| fullscreen, | |
| portal, | |
| distanceFactor, | |
| sprite = false, | |
| transform = false, | |
| occlude, | |
| onOcclude, | |
| castShadow, | |
| receiveShadow, | |
| material, | |
| geometry, | |
| zIndexRange = [16777271, 0], | |
| calculatePosition = defaultCalculatePosition, | |
| as = 'div', | |
| wrapperClass, | |
| pointerEvents = 'auto', | |
| ...props | |
| }, ref) => { | |
| const { | |
| gl, | |
| camera, | |
| scene, | |
| size, | |
| raycaster, | |
| events, | |
| viewport | |
| } = useThree(); | |
| const [el] = React.useState(() => document.createElement(as)); | |
| const root = React.useRef(); | |
| const group = React.useRef(null); | |
| const oldZoom = React.useRef(0); | |
| const oldPosition = React.useRef([0, 0]); | |
| const transformOuterRef = React.useRef(null); | |
| const transformInnerRef = React.useRef(null); | |
| // Append to the connected element, which makes HTML work with views | |
| const target = (portal == null ? void 0 : portal.current) || events.connected || gl.domElement.parentNode; | |
| const occlusionMeshRef = React.useRef(null); | |
| const isMeshSizeSet = React.useRef(false); | |
| const isRayCastOcclusion = React.useMemo(() => { | |
| return occlude && occlude !== 'blending' || Array.isArray(occlude) && occlude.length && isRefObject(occlude[0]); | |
| }, [occlude]); | |
| React.useLayoutEffect(() => { | |
| const el = gl.domElement; | |
| if (occlude && occlude === 'blending') { | |
| el.style.zIndex = `${Math.floor(zIndexRange[0] / 2)}`; | |
| el.style.position = 'absolute'; | |
| el.style.pointerEvents = 'none'; | |
| } else { | |
| el.style.zIndex = null; | |
| el.style.position = null; | |
| el.style.pointerEvents = null; | |
| } | |
| }, [occlude]); | |
| React.useLayoutEffect(() => { | |
| if (group.current) { | |
| const currentRoot = root.current = ReactDOM.createRoot(el); | |
| scene.updateMatrixWorld(); | |
| if (transform) { | |
| el.style.cssText = `position:absolute;top:0;left:0;pointer-events:none;overflow:hidden;`; | |
| } else { | |
| const vec = calculatePosition(group.current, camera, size); | |
| el.style.cssText = `position:absolute;top:0;left:0;transform:translate3d(${vec[0]}px,${vec[1]}px,0);transform-origin:0 0;`; | |
| } | |
| if (target) { | |
| if (prepend) target.prepend(el);else target.appendChild(el); | |
| } | |
| return () => { | |
| if (target) target.removeChild(el); | |
| currentRoot.unmount(); | |
| }; | |
| } | |
| }, [target, transform]); | |
| React.useLayoutEffect(() => { | |
| if (wrapperClass) el.className = wrapperClass; | |
| }, [wrapperClass]); | |
| const styles = React.useMemo(() => { | |
| if (transform) { | |
| return { | |
| position: 'absolute', | |
| top: 0, | |
| left: 0, | |
| width: size.width, | |
| height: size.height, | |
| transformStyle: 'preserve-3d', | |
| pointerEvents: 'none' | |
| }; | |
| } else { | |
| return { | |
| position: 'absolute', | |
| transform: center ? 'translate3d(-50%,-50%,0)' : 'none', | |
| ...(fullscreen && { | |
| top: -size.height / 2, | |
| left: -size.width / 2, | |
| width: size.width, | |
| height: size.height | |
| }), | |
| ...style | |
| }; | |
| } | |
| }, [style, center, fullscreen, size, transform]); | |
| const transformInnerStyles = React.useMemo(() => ({ | |
| position: 'absolute', | |
| pointerEvents | |
| }), [pointerEvents]); | |
| React.useLayoutEffect(() => { | |
| isMeshSizeSet.current = false; | |
| if (transform) { | |
| var _root$current; | |
| (_root$current = root.current) == null || _root$current.render(/*#__PURE__*/React.createElement("div", { | |
| ref: transformOuterRef, | |
| style: styles | |
| }, /*#__PURE__*/React.createElement("div", { | |
| ref: transformInnerRef, | |
| style: transformInnerStyles | |
| }, /*#__PURE__*/React.createElement("div", { | |
| ref: ref, | |
| className: className, | |
| style: style, | |
| children: children | |
| })))); | |
| } else { | |
| var _root$current2; | |
| (_root$current2 = root.current) == null || _root$current2.render(/*#__PURE__*/React.createElement("div", { | |
| ref: ref, | |
| style: styles, | |
| className: className, | |
| children: children | |
| })); | |
| } | |
| }); | |
| const visible = React.useRef(true); | |
| useFrame(gl => { | |
| if (group.current) { | |
| camera.updateMatrixWorld(); | |
| group.current.updateWorldMatrix(true, false); | |
| const vec = transform ? oldPosition.current : calculatePosition(group.current, camera, size); | |
| if (transform || Math.abs(oldZoom.current - camera.zoom) > eps || Math.abs(oldPosition.current[0] - vec[0]) > eps || Math.abs(oldPosition.current[1] - vec[1]) > eps) { | |
| const isBehindCamera = isObjectBehindCamera(group.current, camera); | |
| let raytraceTarget = false; | |
| if (isRayCastOcclusion) { | |
| if (Array.isArray(occlude)) { | |
| raytraceTarget = occlude.map(item => item.current); | |
| } else if (occlude !== 'blending') { | |
| raytraceTarget = [scene]; | |
| } | |
| } | |
| const previouslyVisible = visible.current; | |
| if (raytraceTarget) { | |
| const isvisible = isObjectVisible(group.current, camera, raycaster, raytraceTarget); | |
| visible.current = isvisible && !isBehindCamera; | |
| } else { | |
| visible.current = !isBehindCamera; | |
| } | |
| if (previouslyVisible !== visible.current) { | |
| if (onOcclude) onOcclude(!visible.current);else el.style.display = visible.current ? 'block' : 'none'; | |
| } | |
| const halfRange = Math.floor(zIndexRange[0] / 2); | |
| const zRange = occlude ? isRayCastOcclusion // | |
| ? [zIndexRange[0], halfRange] : [halfRange - 1, 0] : zIndexRange; | |
| el.style.zIndex = `${objectZIndex(group.current, camera, zRange)}`; | |
| if (transform) { | |
| const [widthHalf, heightHalf] = [size.width / 2, size.height / 2]; | |
| const fov = camera.projectionMatrix.elements[5] * heightHalf; | |
| const { | |
| isOrthographicCamera, | |
| top, | |
| left, | |
| bottom, | |
| right | |
| } = camera; | |
| const cameraMatrix = getCameraCSSMatrix(camera.matrixWorldInverse); | |
| const cameraTransform = isOrthographicCamera ? `scale(${fov})translate(${epsilon(-(right + left) / 2)}px,${epsilon((top + bottom) / 2)}px)` : `translateZ(${fov}px)`; | |
| let matrix = group.current.matrixWorld; | |
| if (sprite) { | |
| matrix = camera.matrixWorldInverse.clone().transpose().copyPosition(matrix).scale(group.current.scale); | |
| matrix.elements[3] = matrix.elements[7] = matrix.elements[11] = 0; | |
| matrix.elements[15] = 1; | |
| } | |
| el.style.width = size.width + 'px'; | |
| el.style.height = size.height + 'px'; | |
| el.style.perspective = isOrthographicCamera ? '' : `${fov}px`; | |
| if (transformOuterRef.current && transformInnerRef.current) { | |
| transformOuterRef.current.style.transform = `${cameraTransform}${cameraMatrix}translate(${widthHalf}px,${heightHalf}px)`; | |
| transformInnerRef.current.style.transform = getObjectCSSMatrix(matrix, 1 / ((distanceFactor || 10) / 400)); | |
| } | |
| } else { | |
| const scale = distanceFactor === undefined ? 1 : objectScale(group.current, camera) * distanceFactor; | |
| el.style.transform = `translate3d(${vec[0]}px,${vec[1]}px,0) scale(${scale})`; | |
| } | |
| oldPosition.current = vec; | |
| oldZoom.current = camera.zoom; | |
| } | |
| } | |
| if (!isRayCastOcclusion && occlusionMeshRef.current && !isMeshSizeSet.current) { | |
| if (transform) { | |
| if (transformOuterRef.current) { | |
| const el = transformOuterRef.current.children[0]; | |
| if (el != null && el.clientWidth && el != null && el.clientHeight) { | |
| const { | |
| isOrthographicCamera | |
| } = camera; | |
| if (isOrthographicCamera || geometry) { | |
| if (props.scale) { | |
| if (!Array.isArray(props.scale)) { | |
| occlusionMeshRef.current.scale.setScalar(1 / props.scale); | |
| } else if (props.scale instanceof Vector3) { | |
| occlusionMeshRef.current.scale.copy(props.scale.clone().divideScalar(1)); | |
| } else { | |
| occlusionMeshRef.current.scale.set(1 / props.scale[0], 1 / props.scale[1], 1 / props.scale[2]); | |
| } | |
| } | |
| } else { | |
| const ratio = (distanceFactor || 10) / 400; | |
| const w = el.clientWidth * ratio; | |
| const h = el.clientHeight * ratio; | |
| occlusionMeshRef.current.scale.set(w, h, 1); | |
| } | |
| isMeshSizeSet.current = true; | |
| } | |
| } | |
| } else { | |
| const ele = el.children[0]; | |
| if (ele != null && ele.clientWidth && ele != null && ele.clientHeight) { | |
| const ratio = 1 / viewport.factor; | |
| const w = ele.clientWidth * ratio; | |
| const h = ele.clientHeight * ratio; | |
| occlusionMeshRef.current.scale.set(w, h, 1); | |
| isMeshSizeSet.current = true; | |
| } | |
| occlusionMeshRef.current.lookAt(gl.camera.position); | |
| } | |
| } | |
| }); | |
| const shaders = React.useMemo(() => ({ | |
| vertexShader: !transform ? /* glsl */` | |
| /* | |
| This shader is from the THREE's SpriteMaterial. | |
| We need to turn the backing plane into a Sprite | |
| (make it always face the camera) if "transfrom" | |
| is false. | |
| */ | |
| #include <common> | |
| void main() { | |
| vec2 center = vec2(0., 1.); | |
| float rotation = 0.0; | |
| // This is somewhat arbitrary, but it seems to work well | |
| // Need to figure out how to derive this dynamically if it even matters | |
| float size = 0.03; | |
| vec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 ); | |
| vec2 scale; | |
| scale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) ); | |
| scale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) ); | |
| bool isPerspective = isPerspectiveMatrix( projectionMatrix ); | |
| if ( isPerspective ) scale *= - mvPosition.z; | |
| vec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale * size; | |
| vec2 rotatedPosition; | |
| rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y; | |
| rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y; | |
| mvPosition.xy += rotatedPosition; | |
| gl_Position = projectionMatrix * mvPosition; | |
| } | |
| ` : undefined, | |
| fragmentShader: /* glsl */` | |
| void main() { | |
| gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); | |
| } | |
| ` | |
| }), [transform]); | |
| return /*#__PURE__*/React.createElement("group", _extends({}, props, { | |
| ref: group | |
| }), occlude && !isRayCastOcclusion && /*#__PURE__*/React.createElement("mesh", { | |
| castShadow: castShadow, | |
| receiveShadow: receiveShadow, | |
| ref: occlusionMeshRef | |
| }, geometry || /*#__PURE__*/React.createElement("planeGeometry", null), material || /*#__PURE__*/React.createElement("shaderMaterial", { | |
| side: DoubleSide, | |
| vertexShader: shaders.vertexShader, | |
| fragmentShader: shaders.fragmentShader | |
| }))); | |
| }); | |
| export { Html }; | |