File size: 3,415 Bytes
bf237c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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 };