94 lines
3.3 KiB
JavaScript
94 lines
3.3 KiB
JavaScript
import * as React from 'react';
|
|
import { useThree } from '@react-three/fiber';
|
|
|
|
function CycleRaycast({
|
|
onChanged,
|
|
portal,
|
|
preventDefault = true,
|
|
scroll = true,
|
|
keyCode = 9
|
|
}) {
|
|
const cycle = React.useRef(0);
|
|
const setEvents = useThree(state => state.setEvents);
|
|
const get = useThree(state => state.get);
|
|
const gl = useThree(state => state.gl);
|
|
React.useEffect(() => {
|
|
var _portal$current;
|
|
let hits = [];
|
|
let lastEvent = undefined;
|
|
const prev = get().events.filter;
|
|
const target = (_portal$current = portal == null ? void 0 : portal.current) !== null && _portal$current !== void 0 ? _portal$current : gl.domElement.parentNode;
|
|
|
|
// Render custom status
|
|
const renderStatus = () => target && onChanged && onChanged(hits, Math.round(cycle.current) % hits.length);
|
|
|
|
// Overwrite the raycasters custom filter (this only exists in r3f)
|
|
setEvents({
|
|
filter: (intersections, state) => {
|
|
// Reset cycle when the intersections change
|
|
let clone = [...intersections];
|
|
if (clone.length !== hits.length || !hits.every(hit => clone.map(e => e.object.uuid).includes(hit.object.uuid))) {
|
|
cycle.current = 0;
|
|
hits = clone;
|
|
renderStatus();
|
|
}
|
|
// Run custom filter if there is one
|
|
if (prev) clone = prev(clone, state);
|
|
// Cycle through the actual raycast intersects
|
|
for (let i = 0; i < Math.round(cycle.current) % clone.length; i++) {
|
|
const first = clone.shift();
|
|
clone = [...clone, first];
|
|
}
|
|
return clone;
|
|
}
|
|
});
|
|
|
|
// Cycle, refresh events and render status
|
|
const refresh = fn => {
|
|
var _get$events$handlers, _get$events$handlers2;
|
|
cycle.current = fn(cycle.current);
|
|
// Cancel hovered elements and fake a pointer-move
|
|
(_get$events$handlers = get().events.handlers) == null || _get$events$handlers.onPointerCancel(undefined);
|
|
(_get$events$handlers2 = get().events.handlers) == null || _get$events$handlers2.onPointerMove(lastEvent);
|
|
renderStatus();
|
|
};
|
|
|
|
// Key events
|
|
const tabEvent = event => {
|
|
if ((event.keyCode || event.which) === keyCode) {
|
|
if (preventDefault) event.preventDefault();
|
|
if (hits.length > 1) refresh(current => current + 1);
|
|
}
|
|
};
|
|
|
|
// Wheel events
|
|
const wheelEvent = event => {
|
|
if (preventDefault) event.preventDefault();
|
|
let delta = 0;
|
|
if (!event) event = window.event;
|
|
if (event.wheelDelta) delta = event.wheelDelta / 120;else if (event.detail) delta = -event.detail / 3;
|
|
if (hits.length > 1) refresh(current => Math.abs(current - delta));
|
|
};
|
|
|
|
// Catch last move event and position custom status
|
|
const moveEvent = event => lastEvent = event;
|
|
document.addEventListener('pointermove', moveEvent, {
|
|
passive: true
|
|
});
|
|
if (scroll) document.addEventListener('wheel', wheelEvent);
|
|
if (keyCode !== undefined) document.addEventListener('keydown', tabEvent);
|
|
return () => {
|
|
// Clean up
|
|
setEvents({
|
|
filter: prev
|
|
});
|
|
if (keyCode !== undefined) document.removeEventListener('keydown', tabEvent);
|
|
if (scroll) document.removeEventListener('wheel', wheelEvent);
|
|
document.removeEventListener('pointermove', moveEvent);
|
|
};
|
|
}, [gl, get, setEvents, preventDefault, scroll, keyCode]);
|
|
return null;
|
|
}
|
|
|
|
export { CycleRaycast };
|