fix clique

This commit is contained in:
Rodrigo Bertoldo de Sousa Martins committed 2026-09-11 11:30:46 -03:00
1 parent 3710c18825
commit 4542e8fda0
2 files changed
+64 -14

No files matched your search

+59 -4
View File
@@ -218,6 +218,58 @@ function CameraController({ modelFiles }: { modelFiles: string[] }) {
); );
} }
// Ossos finos (costela vista de perfil, falange, etc.) são fáceis de
// "errar" clicando — o raio único do clique padrão do three.js precisa
// acertar exatamente um triângulo da malha. Em vez de um único raio no
// pixel exato do clique, lançamos vários raios num pequeno raio ao redor
// dele (como um "dedo gordo") e ficamos com o resultado válido mais
// próximo da câmera — bem mais tolerante sem precisar mexer na geometria.
const CLICK_SAMPLE_OFFSETS: [number, number][] = [
[0, 0],
[5, 0], [-5, 0], [0, 5], [0, -5],
[4, 4], [-4, 4], [4, -4], [-4, -4],
];
function ClickSelector({
bySlug,
onSelect,
}: {
bySlug: Map<string, Structure>;
onSelect: (slug: string) => void;
}) {
const { camera, scene, gl, raycaster } = useThree();
useEffect(() => {
const canvas = gl.domElement;
const ndc = new THREE.Vector2();
function handleClick(event: PointerEvent) {
const rect = canvas.getBoundingClientRect();
let best: { distance: number; slug: string } | null = null;
for (const [dx, dy] of CLICK_SAMPLE_OFFSETS) {
const x = ((event.clientX + dx - rect.left) / rect.width) * 2 - 1;
const y = -((event.clientY + dy - rect.top) / rect.height) * 2 + 1;
if (x < -1 || x > 1 || y < -1 || y > 1) continue;
ndc.set(x, y);
raycaster.setFromCamera(ndc, camera);
const hits = raycaster.intersectObjects(scene.children, true);
const hit = hits.find((h) => h.object.visible && h.object.name && bySlug.has(h.object.name));
if (hit && (!best || hit.distance < best.distance)) {
best = { distance: hit.distance, slug: hit.object.name };
}
}
if (best) onSelect(best.slug);
}
canvas.addEventListener("click", handleClick);
return () => canvas.removeEventListener("click", handleClick);
}, [camera, scene, gl, raycaster, bySlug, onSelect]);
return null;
}
export function Scene({ export function Scene({
bySlug, bySlug,
children, children,
@@ -296,13 +348,16 @@ export function Scene({
visibleSystems={visibleSystems} visibleSystems={visibleSystems}
selectedSlug={selectedSlug} selectedSlug={selectedSlug}
clippingPlanes={clippingPlanes} clippingPlanes={clippingPlanes}
onSelect={(slug) => {
const structure = bySlug.get(slug);
if (structure) selectStructure(structure.id);
}}
/> />
))} ))}
</Suspense> </Suspense>
<ClickSelector
bySlug={bySlug}
onSelect={(slug) => {
const structure = bySlug.get(slug);
if (structure) selectStructure(structure.id);
}}
/>
<Grid <Grid
position={[0, 0, 0]} position={[0, 0, 0]}
args={[4, 4]} args={[4, 4]}
+5 -10
View File
@@ -1,7 +1,6 @@
import { useEffect, useMemo, useRef } from "react"; import { useEffect, useMemo, useRef } from "react";
import { useGLTF } from "@react-three/drei"; import { useGLTF } from "@react-three/drei";
import * as THREE from "three"; import * as THREE from "three";
import type { ThreeEvent } from "@react-three/fiber";
import { modelUrl, type Structure } from "../../lib/api.js"; import { modelUrl, type Structure } from "../../lib/api.js";
interface Props { interface Props {
@@ -12,7 +11,6 @@ interface Props {
structuresInFile: Structure[]; structuresInFile: Structure[];
visibleSystems: Set<string>; visibleSystems: Set<string>;
selectedSlug: string | null; selectedSlug: string | null;
onSelect: (slug: string) => void;
clippingPlanes: THREE.Plane[]; clippingPlanes: THREE.Plane[];
} }
@@ -24,7 +22,6 @@ export function SystemModel({
structuresInFile, structuresInFile,
visibleSystems, visibleSystems,
selectedSlug, selectedSlug,
onSelect,
clippingPlanes, clippingPlanes,
}: Props) { }: Props) {
const { scene } = useGLTF(modelUrl(modelFile)); const { scene } = useGLTF(modelUrl(modelFile));
@@ -127,11 +124,9 @@ export function SystemModel({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [root, bySlugInFile, byMeshNameInFile, visibleSystems, selectedSlug, clippingPlanes]); }, [root, bySlugInFile, byMeshNameInFile, visibleSystems, selectedSlug, clippingPlanes]);
const handleClick = (e: ThreeEvent<MouseEvent>) => { // Seleção por clique agora é feita centralizadamente em Scene.tsx
e.stopPropagation(); // (ClickSelector) — um "clique gordo" com vários raios em vez de só o
const name = e.object.name; // pixel exato, bem mais tolerante com ossos finos (costela de perfil,
if (name) onSelect(name); // falange etc.) do que o onClick padrão por malha.
}; return <primitive object={root} />;
return <primitive object={root} onClick={handleClick} />;
} }