| /** | |
| * interactions/raycasting.js | |
| * ========================== | |
| * Raycasting interaction engine for hover tooltips, pointer coordinates, and 3D click selection. | |
| */ | |
| import * as THREE from 'three'; | |
| const raycaster = new THREE.Raycaster(); | |
| const mouse = new THREE.Vector2(); | |
| /** | |
| * Resolves the underlying tensor node data from a Raycaster intersection. | |
| * @param {object} intersection - Three.js intersection result | |
| * @returns {{ mesh: THREE.Mesh, data: object }|null} | |
| */ | |
| export function resolveNodeFromIntersection(intersection) { | |
| if (!intersection || !intersection.object) return null; | |
| let obj = intersection.object; | |
| while (obj && (!obj.userData || !obj.userData.id) && obj.parent) { | |
| obj = obj.parent; | |
| } | |
| if (obj && obj.userData && obj.userData.id) { | |
| return { mesh: obj, data: obj.userData }; | |
| } | |
| return null; | |
| } | |
| /** | |
| * Binds mouse hover and click events on the WebGL canvas. | |
| * @param {HTMLCanvasElement} canvas - Target canvas | |
| * @param {THREE.PerspectiveCamera} camera - Active camera | |
| * @param {Array<THREE.Mesh>} interactableMeshes - Selectable mesh pool | |
| * @param {Function} onSelectNode - Callback when node is clicked: (nodeData, isLeft, mesh) | |
| */ | |
| export function setupRaycasting(canvas, camera, interactableMeshes, onSelectNode) { | |
| const tooltip = document.getElementById('tooltip'); | |
| canvas.addEventListener('pointermove', (event) => { | |
| const rect = canvas.getBoundingClientRect(); | |
| mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; | |
| mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; | |
| raycaster.setFromCamera(mouse, camera); | |
| const intersects = raycaster.intersectObjects(interactableMeshes, false); | |
| if (intersects.length > 0) { | |
| const hit = resolveNodeFromIntersection(intersects[0]); | |
| if (hit && tooltip) { | |
| canvas.style.cursor = 'pointer'; | |
| const meta = hit.data.metadata || {}; | |
| const shapeStr = meta.shape ? `[${meta.shape.join(' × ')}]` : ''; | |
| tooltip.innerHTML = ` | |
| <div style="font-weight:700;color:#fff;">${hit.data.label || hit.data.id}</div> | |
| <div style="font-size:10px;color:#38bdf8;">${shapeStr} ${meta.params || ''}</div> | |
| <div style="font-size:9px;color:#94a3b8;">${hit.data.side === 'compressed' ? '⚡ Compressed' : 'Dense Baseline'}</div> | |
| `; | |
| tooltip.style.left = `${event.clientX + 14}px`; | |
| tooltip.style.top = `${event.clientY + 14}px`; | |
| tooltip.classList.remove('hidden'); | |
| return; | |
| } | |
| } | |
| canvas.style.cursor = 'default'; | |
| if (tooltip) tooltip.classList.add('hidden'); | |
| }); | |
| canvas.addEventListener('click', (event) => { | |
| const rect = canvas.getBoundingClientRect(); | |
| mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; | |
| mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; | |
| raycaster.setFromCamera(mouse, camera); | |
| const intersects = raycaster.intersectObjects(interactableMeshes, false); | |
| if (intersects.length > 0) { | |
| const hit = resolveNodeFromIntersection(intersects[0]); | |
| if (hit && onSelectNode) { | |
| const isLeft = (hit.data.side === 'baseline'); | |
| onSelectNode(hit.data, isLeft, hit.mesh); | |
| } | |
| } | |
| }); | |
| } | |
Xet Storage Details
- Size:
- 3.46 kB
- Xet hash:
- 07398a37042b44b6f40ef05890951930e97bfd5f2c833faadfaaa003072a48aa
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.