novo corpo

This commit is contained in:
Rodrigo Bertoldo de Sousa Martins committed 2026-09-14 22:42:46 -03:00
1 parent 270cdbb965
commit 12342f442d
5 files changed
+230 -125

No files matched your search

+32 -125
View File
@@ -1,135 +1,42 @@
import { useMemo } from "react";
import { useGLTF } from "@react-three/drei";
import * as THREE from "three";
const SKIN = "#8fa3c8";
const MODEL_URL = "/mannequin/body-male-realistic.glb";
// Altura alvo em metros, decidida pra bater com as âncoras de seta/plano já
// calibradas em PlanesPage.tsx (ex: ombro em y≈1.4, quadril em y≈0.9).
const TARGET_HEIGHT = 1.78;
// 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],
];
useGLTF.preload(MODEL_URL);
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 (
<group>
{/* segmento superior (braço/coxa) */}
<mesh position={[x, midY + upperLength / 2, 0]}>
<capsuleGeometry args={[topRadius, Math.max(upperLength - topRadius * 1.6, 0.02), 4, 8]} />
<meshStandardMaterial color={SKIN} />
</mesh>
{/* segmento inferior (antebraço/perna), mais fino */}
<mesh position={[x, bottomY + lowerLength / 2, 0]}>
<capsuleGeometry args={[bottomRadius, Math.max(lowerLength - bottomRadius * 1.6, 0.02), 4, 8]} />
<meshStandardMaterial color={SKIN} />
</mesh>
{/* mão/pé — só uma cápsula achatada na ponta, o bastante pra não
"sumir" o membro no ar */}
<mesh
position={[x, bottomY - 0.03, footZ ?? 0]}
rotation={footZ ? [Math.PI / 2, 0, 0] : [0, 0, 0]}
>
<capsuleGeometry args={[bottomRadius * 0.85, footZ ? 0.09 : 0.05, 3, 6]} />
<meshStandardMaterial color={SKIN} />
</mesh>
</group>
);
}
// 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 <Scene>.
// Corpo humano esculpido de verdade (não caixas/cilindros) em posição
// anatômica, usado só pela página de Planos Anatômicos — ver
// data-pipeline/scripts/mannequin-asset/README.md pra origem (Blender
// Studio "Human Base Meshes", CC0), licença e como essa malha foi
// reposicionada da pose original (braços afastados) pra anatômica (braços
// ao lado do corpo). Não tem nenhuma relação com o dataset Z-Anatomy usado
// no Atlas — é só uma silhueta externa, sem estruturas internas.
export function Mannequin() {
const torsoGeometry = useTorsoGeometry();
const { scene } = useGLTF(MODEL_URL);
return (
<group>
{/* cabeça */}
<mesh position={[0, 1.65, 0]}>
<sphereGeometry args={[0.115, 20, 16]} />
<meshStandardMaterial color={SKIN} />
</mesh>
{/* pescoço */}
<mesh position={[0, 1.555, 0]}>
<capsuleGeometry args={[0.055, 0.03, 3, 8]} />
<meshStandardMaterial color={SKIN} />
</mesh>
const normalized = useMemo(() => {
const clone = scene.clone(true);
const box = new THREE.Box3().setFromObject(clone);
const size = new THREE.Vector3();
box.getSize(size);
const scale = size.y > 0 ? TARGET_HEIGHT / size.y : 1;
const center = new THREE.Vector3();
box.getCenter(center);
{/* tronco (revolução do perfil acima) */}
<mesh geometry={torsoGeometry}>
<meshStandardMaterial color={SKIN} />
</mesh>
const group = new THREE.Group();
group.add(clone);
// Centraliza em X/Z e põe o pé no chão (y=0) ANTES de escalar, depois
// aplica a escala no grupo pai — assim o pivô da escala é a origem já
// corrigida, em vez do pivô torto que a malha trouxe do Blender.
clone.position.set(-center.x, -box.min.y, -center.z);
group.scale.setScalar(scale);
return group;
}, [scene]);
{[1, -1].map((side) => (
<Limb
key={`arm-${side}`}
x={side * 0.235}
topY={1.42}
midY={1.08}
bottomY={0.83}
topRadius={0.052}
bottomRadius={0.042}
/>
))}
{[1, -1].map((side) => (
<Limb
key={`leg-${side}`}
x={side * 0.09}
topY={0.58}
midY={0.3}
bottomY={0.02}
topRadius={0.075}
bottomRadius={0.055}
footZ={0.09}
/>
))}
</group>
);
return <primitive object={normalized} />;
}