Spaces:
Build error
Build error
File size: 4,167 Bytes
8a01471 | 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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | import { useCallback, useEffect } from 'react';
import { useThree } from '@react-three/fiber';
import * as THREE from 'three';
import { useVisualizerStore } from '@/core/store';
/**
* Hook for raycasting and object picking
*/
export function useRaycast() {
const { camera, scene, gl } = useThree();
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
const getIntersections = useCallback((event: MouseEvent | PointerEvent) => {
const rect = gl.domElement.getBoundingClientRect();
pointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
return raycaster.intersectObjects(scene.children, true);
}, [camera, scene, gl]);
return { getIntersections };
}
/**
* Keyboard shortcuts handler
*/
export function useKeyboardShortcuts() {
const resetCamera = useVisualizerStore(state => state.resetCamera);
const selectNode = useVisualizerStore(state => state.selectNode);
const selection = useVisualizerStore(state => state.selection);
const computedNodes = useVisualizerStore(state => state.computedNodes);
const updateConfig = useVisualizerStore(state => state.updateConfig);
const config = useVisualizerStore(state => state.config);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
switch (event.key) {
case 'Escape':
// Deselect current selection
selectNode(null);
break;
case 'r':
case 'R':
// Reset camera
if (!event.ctrlKey && !event.metaKey) {
resetCamera();
}
break;
case 'l':
case 'L':
// Toggle labels
updateConfig({ showLabels: !config.showLabels });
break;
case 'e':
case 'E':
// Toggle edges
updateConfig({ showEdges: !config.showEdges });
break;
case 'ArrowUp':
case 'ArrowDown': {
// Navigate between nodes
if (selection.selectedNodeId) {
const nodeIds = Array.from(computedNodes.keys());
const currentIndex = nodeIds.indexOf(selection.selectedNodeId);
const nextIndex = event.key === 'ArrowDown'
? Math.min(currentIndex + 1, nodeIds.length - 1)
: Math.max(currentIndex - 1, 0);
selectNode(nodeIds[nextIndex]);
}
break;
}
default:
break;
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [resetCamera, selectNode, selection, computedNodes, updateConfig, config]);
}
/**
* Touch gesture handler for mobile
*/
export function useTouchGestures() {
// Placeholder for touch gesture handling
// Can be expanded for pinch-to-zoom, two-finger rotate, etc.
}
/**
* LOD (Level of Detail) manager based on camera distance
*/
export function useLODManager() {
const { camera } = useThree();
const computedNodes = useVisualizerStore(state => state.computedNodes);
const updateNodeLOD = useVisualizerStore(state => state.updateNodeLOD);
// LOD thresholds
const LOD_DISTANCES = {
HIGH: 20, // LOD 0 (full detail) when closer than this
MEDIUM: 40, // LOD 1 (medium detail)
LOW: 80, // LOD 2 (low detail)
};
const updateLOD = useCallback(() => {
const lodMap = new Map<string, number>();
const cameraPos = camera.position;
computedNodes.forEach((node, id) => {
const nodePos = new THREE.Vector3(
node.computedPosition.x,
node.computedPosition.y,
node.computedPosition.z
);
const distance = cameraPos.distanceTo(nodePos);
let lod = 0;
if (distance > LOD_DISTANCES.LOW) {
lod = 2;
} else if (distance > LOD_DISTANCES.MEDIUM) {
lod = 1;
}
lodMap.set(id, lod);
});
updateNodeLOD(lodMap);
}, [camera, computedNodes, updateNodeLOD]);
return { updateLOD };
}
|