From 15aff84de55fce207f1cef3d089711cd858b8b83 Mon Sep 17 00:00:00 2001 From: Rodrigo Bertoldo de Sousa Martins Date: Fri, 11 Sep 2026 17:11:03 -0300 Subject: [PATCH] tegumentar 3d --- frontend/src/components/SkinDiagram3D.tsx | 236 ++++++++++++---------- 1 file changed, 128 insertions(+), 108 deletions(-) diff --git a/frontend/src/components/SkinDiagram3D.tsx b/frontend/src/components/SkinDiagram3D.tsx index 3c37374..d97015e 100644 --- a/frontend/src/components/SkinDiagram3D.tsx +++ b/frontend/src/components/SkinDiagram3D.tsx @@ -1,6 +1,6 @@ import { useMemo, type ReactNode } from "react"; import { Canvas } from "@react-three/fiber"; -import { CameraControls } from "@react-three/drei"; +import { CameraControls, RoundedBox } from "@react-three/drei"; import * as THREE from "three"; import { useAtlasStore } from "../store/useAtlasStore.js"; import type { Structure } from "../lib/api.js"; @@ -9,11 +9,23 @@ import type { Structure } from "../lib/api.js"; // um bloco 3D esquemático pro tegumentar e nenhuma ficou boa o bastante, // então virou diagrama 2D com hotspots. A pedido do usuário, tentamos de // novo seguindo o MESMO princípio que funcionou pra citologia -// (CellDiagram3D.tsx): em vez de tentar esculpir uma "pele" realista, é um -// bloco de tecido em corte — como a ilustração clássica de livro-texto — -// com cada camada/anexo como uma forma geométrica simples e reconhecível, -// clicável, usando as MESMAS 20 estruturas (nomes/descrições reais) que já -// existiam no diagrama 2D. Ilustrativo, não derivado de dado real. +// (CellDiagram3D.tsx): um bloco de tecido em corte, como a ilustração +// clássica de livro-texto, usando as MESMAS 20 estruturas (nomes/ +// descrições reais) que já existiam no diagrama 2D. Ilustrativo, não +// derivado de dado real. +// +// PRIMEIRA VERSÃO (caixas cheias e translúcidas empilhadas) tinha o MESMO +// problema já visto na célula real de FIB-SEM: a derme/hipoderme, sendo +// sólidas e cobrindo tudo, sempre são o primeiro acerto de qualquer raio de +// clique, escondendo as estruturas internas — mas aqui não dá pra só +// desabilitar o raycast delas (o usuário PRECISA poder clicar em "derme" e +// "hipoderme" também, ao contrário da membrana da célula). A solução é +// geométrica, não de material: as camadas formam um "L" (falta o quadrante +// x>0 E z>0 em todas as profundidades), como o corte-em-cunha clássico dos +// livros-texto (um "pedaço retirado" do bloco). Todo o conteúdo interno +// (folículo, glândulas, vasos, nervo) fica dentro desse vão aberto, sem +// nada sólido entre a câmera e eles — resolve a oclusão sem sacrificar a +// clicabilidade das camadas. const HIGHLIGHT = new THREE.Color("#22d3ee"); const BLACK = new THREE.Color(0x000000); @@ -57,26 +69,47 @@ function Part({ ); } -// Bloco de tecido: 2 x 2 (largura x profundidade), cortado ao meio pra -// expor as camadas de lado. Eixo Y é a profundidade da pele (0 = topo). -const BLOCK = 1.0; // meia-largura/profundidade do bloco (de -BLOCK a BLOCK) +// Eixo Y é a profundidade da pele (0 = superfície, negativo = mais fundo). const EPIDERME_TOP = 0; +const ESTRATO_CORNEO_BOT = -0.02; +const CAMADA_PIGMENTAR_BOT = -0.08; const EPIDERME_BOT = -0.12; const DERME_BOT = -0.55; const HIPODERME_BOT = -1.0; -// Vasos/nervos como tubos ao longo de uma curva — evita usar cylinderGeometry -// reto demais pra estruturas que na pele são ramificadas/sinuosas. +// Uma camada é um "L": bloco de trás (toda a largura, z de -1 a 0) + bloco +// da frente-esquerda (metade da largura, z de 0 a 1). O quadrante x>0,z>0 +// (canto frente-direita, voltado pra câmera) fica de fora — é o vão aberto +// onde ficam expostas as estruturas internas. +function Slab({ yTop, yBot, color, sel, radius }: { yTop: number; yBot: number; color: string; sel: boolean; radius: number }) { + const thickness = yTop - yBot; + const centerY = (yTop + yBot) / 2; + return ( + <> + + + + + + + + ); +} + +// Vasos/nervos como tubos ao longo de uma curva — mais orgânico que um +// cilindro reto pra estruturas que na pele são ramificadas/sinuosas. function tube(points: [number, number, number][], radius: number) { const curve = new THREE.CatmullRomCurve3(points.map(([x, y, z]) => new THREE.Vector3(x, y, z))); return new THREE.TubeGeometry(curve, 24, radius, 8, false); } -// Posições fixas (não aleatórias) dos glóbulos de gordura na hipoderme. -const FAT_CELLS: [number, number, number, number][] = [ - [-0.5, -0.72, -0.4, 0.09], [-0.2, -0.85, -0.55, 0.11], [0.15, -0.7, -0.35, 0.08], - [0.45, -0.88, -0.5, 0.1], [-0.55, -0.9, -0.15, 0.08], [0.1, -0.95, -0.6, 0.09], - [-0.15, -0.65, -0.6, 0.07], [0.5, -0.65, -0.25, 0.08], +// Pequenos "caroços" de gordura saltando das paredes de corte da hipoderme +// pro vão aberto — dão textura à camada sem precisar de transparência +// (a camada em si agora é opaca, já que a oclusão foi resolvida pela +// geometria em L, não por transparência). +const FAT_BUMPS: [number, number, number, number][] = [ + [0.15, -0.65, 0.02, 0.06], [0.4, -0.78, 0.02, 0.07], [0.65, -0.68, 0.02, 0.055], + [0.02, -0.88, 0.55, 0.06], [0.02, -0.62, 0.8, 0.055], [0.02, -0.9, 0.9, 0.07], ]; export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map }) { @@ -88,10 +121,13 @@ export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map tube([[0.75, -0.35, -0.75], [0.55, -0.5, -0.3], [0.3, -0.6, 0.1], [0.05, -0.65, 0.4]], 0.03), []); - const arteryTube = useMemo(() => tube([[0.85, -1.0, -0.85], [0.7, -0.7, -0.6], [0.55, -0.5, -0.3]], 0.035), []); - const veinTube = useMemo(() => tube([[0.9, -1.0, -0.6], [0.75, -0.68, -0.35], [0.55, -0.48, -0.15]], 0.035), []); - const nerveFiberTube = useMemo(() => tube([[-0.85, -0.95, 0.6], [-0.6, -0.6, 0.3], [-0.3, -0.3, 0.1], [-0.1, -0.15, 0]], 0.02), []); + // Tudo dentro do vão aberto (x>0.05, z>0.05) fica em ar livre — nenhuma + // camada sólida entre a câmera (posicionada em x,z positivos) e essas + // estruturas, então o clique sempre alcança a estrutura certa. + const vesselTube = useMemo(() => tube([[0.35, -0.35, 0.45], [0.28, -0.45, 0.4], [0.22, -0.55, 0.35], [0.16, -0.6, 0.3]], 0.026), []); + const arteryTube = useMemo(() => tube([[0.4, -0.85, 0.2], [0.3, -0.65, 0.24], [0.22, -0.5, 0.28]], 0.03), []); + const veinTube = useMemo(() => tube([[0.4, -0.85, 0.42], [0.3, -0.65, 0.4], [0.22, -0.48, 0.36]], 0.03), []); + const nerveFiberTube = useMemo(() => tube([[0.2, -0.82, 0.62], [0.2, -0.5, 0.66], [0.2, -0.25, 0.68], [0.2, -0.14, 0.7]], 0.018), []); if (bySlug.size === 0) return null; @@ -112,39 +148,52 @@ export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map
- + - {/* Hipoderme — camada mais funda, com glóbulos de gordura */} + {/* Camadas — cada uma em formato de "L" (ver Slab), deixando o + quadrante frente-direita aberto pra expor o interior. */} {(sel) => ( - - - - + <> + + {/* Piso do vão — fecha o buraco no fundo da cunha; sem ele o + corte pareceria vazado e tudo perto do fundo pareceria + flutuando no vazio. */} + + + + + )} - - {/* Derme — camada intermediária */} - {(sel) => ( - - - - - )} + {(sel) => } + + + {(sel) => } + + + {(sel) => } + + + {(sel) => } - {/* Papilas dérmicas — pequenas ondulações no topo da derme, contra a epiderme */} + {/* Papilas dérmicas — pequenas ondulações saltando da parede de + corte da derme (na junção com a epiderme) pro vão aberto. */} {(sel) => ( <> - {[-0.6, -0.2, 0.2, 0.6].map((x, i) => ( - - + {[ + [0.15, 0.02], [0.4, 0.02], [0.65, 0.02], + [0.02, 0.2], [0.02, 0.45], [0.02, 0.7], + ].map(([x, z], i) => ( + + ))} @@ -152,131 +201,102 @@ export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map - {/* Epiderme — camada mais fina, no topo, dividida em 3 sub-camadas visíveis */} - - {(sel) => ( - - - - - )} - - - {(sel) => ( - - - - - )} - - - {(sel) => ( - - - - - )} - - {/* Complexo pilossebáceo — pelo + folículo + papila + eretor + sebácea, - cortado na metade frontal do bloco pra ficar visível por dentro. */} + todo dentro do vão aberto, perto do canto (mais visível contra + as duas paredes de corte). */} {(sel) => ( - - + + )} {(sel) => ( - - + + )} {(sel) => ( - - + + )} {(sel) => ( - - + + )} {(sel) => ( - - + + )} - {/* Glândula sudorípara — tubo enovelado (coil) na derme profunda/hipoderme, - com ducto reto até a superfície e poro no topo. */} + {/* Glândula sudorípara — tubo enovelado (coil) na hipoderme, com + ducto reto até a superfície e poro no topo. */} {(sel) => ( - - - - - - + + + + )} {(sel) => ( - - + + )} - - {/* Ducto reto ligando a glândula sudorípara ao poro — parte visual da - glândula, sem estrutura própria no banco (usa o mesmo slug). */} - null}> - - + {/* Ducto reto ligando a glândula sudorípara ao poro — parte visual + da glândula, sem estrutura própria no banco. */} + null}> + + - {/* Corpúsculo de Pacini — receptor em camadas concêntricas ("cebola"), fundo da hipoderme */} + {/* Corpúsculo de Pacini — receptor em camadas concêntricas ("cebola"), + fundo da hipoderme, junto à parede de corte. */} {(sel) => ( - - {[0.16, 0.12, 0.08, 0.04].map((r, i) => ( + + {[0.13, 0.1, 0.065, 0.035].map((r, i) => ( - + ))} )} - {/* Terminação nervosa de tato — pequeno disco na junção derme-epiderme */} + {/* Terminação nervosa de tato — pequena cápsula na junção derme-epiderme */} {(sel) => ( - - + + )} - {/* Fibra nervosa — filamento sinuoso subindo da hipoderme até a derme */} + {/* Fibra nervosa — filamento sinuoso subindo da hipoderme até a + terminação de tato */} {(sel) => ( @@ -285,7 +305,7 @@ export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map - {/* Vascularização — rede de vasos + ramo arterial + ramo venoso, tubos sinuosos */} + {/* Vascularização — rede de vasos + ramo arterial + ramo venoso */} {(sel) => ( @@ -308,12 +328,12 @@ export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map - {/* Glóbulos de gordura na hipoderme — só decorativos (compõem a textura - da camada, sem estrutura própria no banco). */} - {FAT_CELLS.map(([x, y, z, r], i) => ( + {/* Caroços de gordura saltando das paredes de corte da hipoderme — + só textura decorativa, sem estrutura própria no banco. */} + {FAT_BUMPS.map(([x, y, z, r], i) => ( null}> - + ))} @@ -322,7 +342,7 @@ export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map

- Modelo ilustrativo (não derivado de dado real) — bloco de pele em corte, proporções esquemáticas pensadas pra reconhecer cada camada e anexo. + Modelo ilustrativo (não derivado de dado real) — bloco de pele com uma cunha removida (como em livros-texto), proporções esquemáticas pensadas pra reconhecer cada camada e anexo.