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,