diff --git a/frontend/src/components/viewer/Mannequin.tsx b/frontend/src/components/viewer/Mannequin.tsx index 482621d..012b84d 100644 --- a/frontend/src/components/viewer/Mannequin.tsx +++ b/frontend/src/components/viewer/Mannequin.tsx @@ -1,38 +1,134 @@ -// Boneco esquemático simples (mesmas proporções/posições usadas em -// PlanesPage) — extraído pra um componente próprio pra ser reaproveitado -// também pelo módulo de Exame Físico, que precisa das MESMAS coordenadas -// de tórax/abdome/membros pra posicionar os pontos de exame corretamente. -export function Mannequin() { - const skin = "#8fa3c8"; +import { useMemo } from "react"; +import * as THREE from "three"; + +const SKIN = "#8fa3c8"; + +// Perfil do tronco (raio, altura) de baixo pra cima — vira uma superfície +// lisa e arredondada ao ser revolvido (LatheGeometry), bem mais parecido +// com um corpo de verdade do que uma caixa. Não é uma malha esculpida (não +// dá forma às costelas, escápulas etc.) — é só um contorno correto o +// bastante pra indicar plano/direção sobre uma região reconhecível, e por +// ser 100% procedural (nenhum arquivo importado) continua leve e sem +// nenhuma questão de licença, ao contrário de baixar um basemesh pronto. +const TORSO_PROFILE: [radius: number, y: number][] = [ + [0.13, 0.55], // base da pelve + [0.165, 0.63], // quadril (mais largo) + [0.145, 0.75], + [0.12, 0.9], // cintura (mais estreito) + [0.135, 1.0], + [0.16, 1.12], + [0.19, 1.25], // tórax/ombros (mais largo) + [0.175, 1.36], + [0.11, 1.46], // base do pescoço + [0.065, 1.52], +]; + +function useTorsoGeometry() { + return useMemo(() => { + const points = TORSO_PROFILE.map(([r, y]) => new THREE.Vector2(r, y)); + const geo = new THREE.LatheGeometry(points, 20); + // O tronco humano é mais largo (ombro a ombro) do que profundo + // (peito a costas) — achatando o eixo Z do sólido de revolução (que + // sai circular) a gente sai de "cilindro" pra uma seção elíptica bem + // mais realista, sem precisar de um perfil 2D por eixo. + geo.scale(1, 1, 0.62); + return geo; + }, []); +} + +function Limb({ + x, + topY, + midY, + bottomY, + topRadius, + bottomRadius, + footZ, +}: { + x: number; + topY: number; + midY: number; + bottomY: number; + topRadius: number; + bottomRadius: number; + footZ?: number; +}) { + const upperLength = topY - midY; + const lowerLength = midY - bottomY; return ( + {/* segmento superior (braço/coxa) */} + + + + + {/* segmento inferior (antebraço/perna), mais fino */} + + + + + {/* mão/pé — só uma cápsula achatada na ponta, o bastante pra não + "sumir" o membro no ar */} + + + + + + ); +} + +// Boneco esquemático usado só pela página de Planos Anatômicos, pra ter +// uma silhueta humana reconhecível (tronco arredondado + membros afunilados) +// sobre a qual desenhar planos de corte e setas de direção — não precisa +// (nem deveria, pelo tamanho) da fidelidade do dataset real do Z-Anatomy, +// que é usado no Atlas e no Exame Físico via . +export function Mannequin() { + const torsoGeometry = useTorsoGeometry(); + + return ( + + {/* cabeça */} - - + + - - - + {/* pescoço */} + + + - - - + + {/* tronco (revolução do perfil acima) */} + + + {[1, -1].map((side) => ( - - - - - - + ))} + {[1, -1].map((side) => ( - - - - - - + ))} ); diff --git a/frontend/src/routes/PlanesPage.tsx b/frontend/src/routes/PlanesPage.tsx index 48073bf..76a9868 100644 --- a/frontend/src/routes/PlanesPage.tsx +++ b/frontend/src/routes/PlanesPage.tsx @@ -51,6 +51,15 @@ const AXIS_GROUPS: AxisGroup[] = [ { label: "Esquerda (do paciente)", from: [0, 0.9, 0], to: [0.7, 0.9, 0], color: "#a78bfa" }, ], }, + { + key: "cranial-caudal", + label: "Cranial / Caudal (na coluna)", + hint: "Mesmo sentido de Superior/Inferior, mas usado sobretudo pra estruturas ao longo do eixo da cabeça à cauda (coluna, tronco, SNC)", + axes: [ + { label: "Cranial", from: [0, 0.9, -0.12], to: [0, 1.65, -0.12], color: "#c084fc" }, + { label: "Caudal", from: [0, 0.9, -0.12], to: [0, 0.05, -0.12], color: "#c084fc" }, + ], + }, { key: "medial-lateral", label: "Medial / Lateral (no braço)", @@ -134,6 +143,7 @@ export default function PlanesPage() { }); const [visibleAxisGroups, setVisibleAxisGroups] = useState>({ principal: true, + "cranial-caudal": false, "medial-lateral": false, "proximal-distal": false, "superficial-profundo": false,