use corpo real exame fisico
This commit is contained in:
1 parent
939293ba60
commit
7e8963135b
4 files changed
+166
-108
No files matched your search
@@ -0,0 +1,58 @@
|
||||
import { Html } from "@react-three/drei";
|
||||
import * as THREE from "three";
|
||||
import { useAtlasStore } from "../../store/useAtlasStore.js";
|
||||
import type { ExamPoint, ExamTechnique } from "../../data/physicalExam.js";
|
||||
import { TECHNIQUES } from "../../data/physicalExam.js";
|
||||
|
||||
// Roda DENTRO do mesmo <Canvas> do Scene (recebido via prop "children" —
|
||||
// ver Scene.tsx) pra poder usar <Html> do drei e o bounding box real do
|
||||
// corpo carregado (contentBox no store, o mesmo usado pelo plano de corte e
|
||||
// pela navegação rápida por região). Cada ponto de exame é dado como
|
||||
// FRAÇÃO do corpo (ver data/physicalExam.ts), convertida aqui pra
|
||||
// coordenada absoluta — assim os pontos acompanham o corpo real não
|
||||
// importa a escala/origem carregada.
|
||||
export function ExamMarkers({
|
||||
points,
|
||||
selectedId,
|
||||
onSelect,
|
||||
}: {
|
||||
points: ExamPoint[];
|
||||
selectedId: string | null;
|
||||
onSelect: (id: string) => void;
|
||||
}) {
|
||||
const contentBox = useAtlasStore((s) => s.contentBox);
|
||||
if (!contentBox) return null;
|
||||
|
||||
const min = new THREE.Vector3(...contentBox.min);
|
||||
const max = new THREE.Vector3(...contentBox.max);
|
||||
const techniqueColor = (t: ExamTechnique) => TECHNIQUES.find((x) => x.key === t)?.color ?? "#fff";
|
||||
|
||||
return (
|
||||
<>
|
||||
{points.map((p) => {
|
||||
const [fx, fy, fz] = p.fraction;
|
||||
const position: [number, number, number] = [
|
||||
THREE.MathUtils.lerp(min.x, max.x, fx),
|
||||
THREE.MathUtils.lerp(min.y, max.y, fy),
|
||||
THREE.MathUtils.lerp(min.z, max.z, fz),
|
||||
];
|
||||
const isSelected = p.id === selectedId;
|
||||
const color = techniqueColor(p.technique);
|
||||
return (
|
||||
<Html key={p.id} position={position} center distanceFactor={4} zIndexRange={[10, 0]}>
|
||||
<button
|
||||
onClick={() => onSelect(p.id)}
|
||||
title={p.name}
|
||||
className="w-4 h-4 rounded-full border-2 border-white/80 transition-transform hover:scale-125"
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
boxShadow: isSelected ? `0 0 0 5px ${color}55` : undefined,
|
||||
transform: isSelected ? "scale(1.4)" : undefined,
|
||||
}}
|
||||
/>
|
||||
</Html>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Suspense, useEffect, useMemo, useRef } from "react";
|
||||
import { Suspense, useEffect, useMemo, useRef, type ReactNode } from "react";
|
||||
import { Canvas, useThree } from "@react-three/fiber";
|
||||
import { CameraControls, Grid } from "@react-three/drei";
|
||||
import * as THREE from "three";
|
||||
@@ -173,7 +173,17 @@ function CameraController({ modelFiles }: { modelFiles: string[] }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function Scene({ bySlug }: { bySlug: Map<string, Structure> }) {
|
||||
export function Scene({
|
||||
bySlug,
|
||||
children,
|
||||
}: {
|
||||
bySlug: Map<string, Structure>;
|
||||
// Ponto de extensão pra conteúdo 3D extra dentro do MESMO Canvas (e do
|
||||
// mesmo contexto do react-three-fiber) — usado pelo módulo de Exame
|
||||
// Físico pra desenhar os pontos de exame em cima do corpo real, sem
|
||||
// duplicar toda a lógica de carregamento de malhas/câmera daqui.
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
const visibleSystems = useAtlasStore((s) => s.visibleSystems);
|
||||
const selectedStructureId = useAtlasStore((s) => s.selectedStructureId);
|
||||
const selectStructure = useAtlasStore((s) => s.selectStructure);
|
||||
@@ -245,6 +255,7 @@ export function Scene({ bySlug }: { bySlug: Map<string, Structure> }) {
|
||||
infiniteGrid
|
||||
/>
|
||||
<CameraController modelFiles={modelFiles} />
|
||||
{children}
|
||||
</Canvas>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user