|
|
import _extends from '@babel/runtime/helpers/esm/extends'; |
|
|
import * as React from 'react'; |
|
|
import * as THREE from 'three'; |
|
|
import { context, useThree, createPortal, useFrame } from '@react-three/fiber'; |
|
|
import tunnel from 'tunnel-rat'; |
|
|
|
|
|
const isOrthographicCamera = def => def && def.isOrthographicCamera; |
|
|
const col = new THREE.Color(); |
|
|
const tracked = tunnel(); |
|
|
function computeContainerPosition(canvasSize, trackRect) { |
|
|
const { |
|
|
right, |
|
|
top, |
|
|
left: trackLeft, |
|
|
bottom: trackBottom, |
|
|
width, |
|
|
height |
|
|
} = trackRect; |
|
|
const isOffscreen = trackRect.bottom < 0 || top > canvasSize.height || right < 0 || trackRect.left > canvasSize.width; |
|
|
const canvasBottom = canvasSize.top + canvasSize.height; |
|
|
const bottom = canvasBottom - trackBottom; |
|
|
const left = trackLeft - canvasSize.left; |
|
|
return { |
|
|
position: { |
|
|
width, |
|
|
height, |
|
|
left, |
|
|
top, |
|
|
bottom, |
|
|
right |
|
|
}, |
|
|
isOffscreen |
|
|
}; |
|
|
} |
|
|
function prepareSkissor(state, { |
|
|
left, |
|
|
bottom, |
|
|
width, |
|
|
height |
|
|
}) { |
|
|
let autoClear; |
|
|
const aspect = width / height; |
|
|
if (isOrthographicCamera(state.camera)) { |
|
|
if (!state.camera.manual) { |
|
|
if (state.camera.left !== width / -2 || state.camera.right !== width / 2 || state.camera.top !== height / 2 || state.camera.bottom !== height / -2) { |
|
|
Object.assign(state.camera, { |
|
|
left: width / -2, |
|
|
right: width / 2, |
|
|
top: height / 2, |
|
|
bottom: height / -2 |
|
|
}); |
|
|
state.camera.updateProjectionMatrix(); |
|
|
} |
|
|
} else { |
|
|
state.camera.updateProjectionMatrix(); |
|
|
} |
|
|
} else if (state.camera.aspect !== aspect) { |
|
|
state.camera.aspect = aspect; |
|
|
state.camera.updateProjectionMatrix(); |
|
|
} |
|
|
autoClear = state.gl.autoClear; |
|
|
state.gl.autoClear = false; |
|
|
state.gl.setViewport(left, bottom, width, height); |
|
|
state.gl.setScissor(left, bottom, width, height); |
|
|
state.gl.setScissorTest(true); |
|
|
return autoClear; |
|
|
} |
|
|
function finishSkissor(state, autoClear) { |
|
|
|
|
|
state.gl.setScissorTest(false); |
|
|
state.gl.autoClear = autoClear; |
|
|
} |
|
|
function clear(state) { |
|
|
state.gl.getClearColor(col); |
|
|
state.gl.setClearColor(col, state.gl.getClearAlpha()); |
|
|
state.gl.clear(true, true); |
|
|
} |
|
|
function Container({ |
|
|
visible = true, |
|
|
canvasSize, |
|
|
scene, |
|
|
index, |
|
|
children, |
|
|
frames, |
|
|
rect, |
|
|
track |
|
|
}) { |
|
|
const rootState = useThree(); |
|
|
const [isOffscreen, setOffscreen] = React.useState(false); |
|
|
let frameCount = 0; |
|
|
useFrame(state => { |
|
|
if (frames === Infinity || frameCount <= frames) { |
|
|
var _track$current; |
|
|
if (track) rect.current = (_track$current = track.current) == null ? void 0 : _track$current.getBoundingClientRect(); |
|
|
frameCount++; |
|
|
} |
|
|
if (rect.current) { |
|
|
const { |
|
|
position, |
|
|
isOffscreen: _isOffscreen |
|
|
} = computeContainerPosition(canvasSize, rect.current); |
|
|
if (isOffscreen !== _isOffscreen) setOffscreen(_isOffscreen); |
|
|
if (visible && !isOffscreen && rect.current) { |
|
|
const autoClear = prepareSkissor(state, position); |
|
|
|
|
|
state.gl.render(children ? state.scene : scene, state.camera); |
|
|
finishSkissor(state, autoClear); |
|
|
} |
|
|
} |
|
|
}, index); |
|
|
React.useLayoutEffect(() => { |
|
|
const curRect = rect.current; |
|
|
if (curRect && (!visible || !isOffscreen)) { |
|
|
|
|
|
const { |
|
|
position |
|
|
} = computeContainerPosition(canvasSize, curRect); |
|
|
const autoClear = prepareSkissor(rootState, position); |
|
|
clear(rootState); |
|
|
finishSkissor(rootState, autoClear); |
|
|
} |
|
|
}, [visible, isOffscreen]); |
|
|
React.useEffect(() => { |
|
|
if (!track) return; |
|
|
const curRect = rect.current; |
|
|
|
|
|
const old = rootState.get().events.connected; |
|
|
rootState.setEvents({ |
|
|
connected: track.current |
|
|
}); |
|
|
return () => { |
|
|
if (curRect) { |
|
|
const { |
|
|
position |
|
|
} = computeContainerPosition(canvasSize, curRect); |
|
|
const autoClear = prepareSkissor(rootState, position); |
|
|
clear(rootState); |
|
|
finishSkissor(rootState, autoClear); |
|
|
} |
|
|
rootState.setEvents({ |
|
|
connected: old |
|
|
}); |
|
|
}; |
|
|
}, [track]); |
|
|
return React.createElement(React.Fragment, null, children, React.createElement("group", { |
|
|
onPointerOver: () => null |
|
|
})); |
|
|
} |
|
|
const CanvasView = React.forwardRef(({ |
|
|
track, |
|
|
visible = true, |
|
|
index = 1, |
|
|
id, |
|
|
style, |
|
|
className, |
|
|
frames = Infinity, |
|
|
children, |
|
|
...props |
|
|
}, fref) => { |
|
|
var _rect$current, _rect$current2, _rect$current3, _rect$current4; |
|
|
const rect = React.useRef(null); |
|
|
const { |
|
|
size, |
|
|
scene |
|
|
} = useThree(); |
|
|
const [virtualScene] = React.useState(() => new THREE.Scene()); |
|
|
const [ready, toggle] = React.useReducer(() => true, false); |
|
|
const compute = React.useCallback((event, state) => { |
|
|
if (rect.current && track && track.current && event.target === track.current) { |
|
|
const { |
|
|
width, |
|
|
height, |
|
|
left, |
|
|
top |
|
|
} = rect.current; |
|
|
const x = event.clientX - left; |
|
|
const y = event.clientY - top; |
|
|
state.pointer.set(x / width * 2 - 1, -(y / height) * 2 + 1); |
|
|
state.raycaster.setFromCamera(state.pointer, state.camera); |
|
|
} |
|
|
}, [rect, track]); |
|
|
React.useEffect(() => { |
|
|
var _track$current2; |
|
|
|
|
|
if (track) rect.current = (_track$current2 = track.current) == null ? void 0 : _track$current2.getBoundingClientRect(); |
|
|
|
|
|
toggle(); |
|
|
}, [track]); |
|
|
return React.createElement("group", _extends({ |
|
|
ref: fref |
|
|
}, props), ready && createPortal(React.createElement(Container, { |
|
|
visible: visible, |
|
|
canvasSize: size, |
|
|
frames: frames, |
|
|
scene: scene, |
|
|
track: track, |
|
|
rect: rect, |
|
|
index: index |
|
|
}, children), virtualScene, { |
|
|
events: { |
|
|
compute, |
|
|
priority: index |
|
|
}, |
|
|
size: { |
|
|
width: (_rect$current = rect.current) == null ? void 0 : _rect$current.width, |
|
|
height: (_rect$current2 = rect.current) == null ? void 0 : _rect$current2.height, |
|
|
|
|
|
top: (_rect$current3 = rect.current) == null ? void 0 : _rect$current3.top, |
|
|
|
|
|
left: (_rect$current4 = rect.current) == null ? void 0 : _rect$current4.left |
|
|
} |
|
|
})); |
|
|
}); |
|
|
const HtmlView = React.forwardRef(({ |
|
|
as: El = 'div', |
|
|
id, |
|
|
visible, |
|
|
className, |
|
|
style, |
|
|
index = 1, |
|
|
track, |
|
|
frames = Infinity, |
|
|
children, |
|
|
...props |
|
|
}, fref) => { |
|
|
const uuid = React.useId(); |
|
|
const ref = React.useRef(null); |
|
|
React.useImperativeHandle(fref, () => ref.current); |
|
|
return React.createElement(React.Fragment, null, React.createElement(El, _extends({ |
|
|
ref: ref, |
|
|
id: id, |
|
|
className: className, |
|
|
style: style |
|
|
}, props)), React.createElement(tracked.In, null, React.createElement(CanvasView, { |
|
|
visible: visible, |
|
|
key: uuid, |
|
|
track: ref, |
|
|
frames: frames, |
|
|
index: index |
|
|
}, children))); |
|
|
}); |
|
|
const View = (() => { |
|
|
const _View = React.forwardRef((props, fref) => { |
|
|
|
|
|
const store = React.useContext(context); |
|
|
|
|
|
if (!store) return React.createElement(HtmlView, _extends({ |
|
|
ref: fref |
|
|
}, props)); |
|
|
|
|
|
else return React.createElement(CanvasView, _extends({ |
|
|
ref: fref |
|
|
}, props)); |
|
|
}); |
|
|
_View.Port = () => React.createElement(tracked.Out, null); |
|
|
return _View; |
|
|
})(); |
|
|
|
|
|
export { View }; |
|
|
|