fix clique
This commit is contained in:
1 parent
3710c18825
commit
4542e8fda0
2 files changed
+64
-14
No files matched your search
@@ -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({
|
||||
bySlug,
|
||||
children,
|
||||
@@ -296,13 +348,16 @@ export function Scene({
|
||||
visibleSystems={visibleSystems}
|
||||
selectedSlug={selectedSlug}
|
||||
clippingPlanes={clippingPlanes}
|
||||
onSelect={(slug) => {
|
||||
const structure = bySlug.get(slug);
|
||||
if (structure) selectStructure(structure.id);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Suspense>
|
||||
<ClickSelector
|
||||
bySlug={bySlug}
|
||||
onSelect={(slug) => {
|
||||
const structure = bySlug.get(slug);
|
||||
if (structure) selectStructure(structure.id);
|
||||
}}
|
||||
/>
|
||||
<Grid
|
||||
position={[0, 0, 0]}
|
||||
args={[4, 4]}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { useGLTF } from "@react-three/drei";
|
||||
import * as THREE from "three";
|
||||
import type { ThreeEvent } from "@react-three/fiber";
|
||||
import { modelUrl, type Structure } from "../../lib/api.js";
|
||||
|
||||
interface Props {
|
||||
@@ -12,7 +11,6 @@ interface Props {
|
||||
structuresInFile: Structure[];
|
||||
visibleSystems: Set<string>;
|
||||
selectedSlug: string | null;
|
||||
onSelect: (slug: string) => void;
|
||||
clippingPlanes: THREE.Plane[];
|
||||
}
|
||||
|
||||
@@ -24,7 +22,6 @@ export function SystemModel({
|
||||
structuresInFile,
|
||||
visibleSystems,
|
||||
selectedSlug,
|
||||
onSelect,
|
||||
clippingPlanes,
|
||||
}: Props) {
|
||||
const { scene } = useGLTF(modelUrl(modelFile));
|
||||
@@ -127,11 +124,9 @@ export function SystemModel({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [root, bySlugInFile, byMeshNameInFile, visibleSystems, selectedSlug, clippingPlanes]);
|
||||
|
||||
const handleClick = (e: ThreeEvent<MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
const name = e.object.name;
|
||||
if (name) onSelect(name);
|
||||
};
|
||||
|
||||
return <primitive object={root} onClick={handleClick} />;
|
||||
// Seleção por clique agora é feita centralizadamente em Scene.tsx
|
||||
// (ClickSelector) — um "clique gordo" com vários raios em vez de só o
|
||||
// pixel exato, bem mais tolerante com ossos finos (costela de perfil,
|
||||
// falange etc.) do que o onClick padrão por malha.
|
||||
return <primitive object={root} />;
|
||||
}
|
||||
Reference in new issue
Block a user