import { useMemo, type ReactNode } from "react"; import { Canvas } from "@react-three/fiber"; 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"; // Histórico (ver data-pipeline/seed/build-manifest.mjs): tentamos duas vezes // 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): 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); // Função comum (NÃO hook) — chamada dentro de .map()/render-prop, onde um // hook violaria as Regras de Hooks do React (ver mesmo padrão em // CellDiagram3D.tsx). function materialProps(baseColor: string, selected: boolean, opacity = 1) { return { color: baseColor, transparent: opacity < 1, opacity, emissive: selected ? HIGHLIGHT : BLACK, emissiveIntensity: selected ? 0.7 : 0, }; } function Part({ slug, bySlug, onSelect, children, }: { slug: string; bySlug: Map; onSelect: (id: string) => void; children: (isSelected: boolean) => ReactNode; }) { const selectedStructureId = useAtlasStore((s) => s.selectedStructureId); const structure = bySlug.get(slug); const isSelected = !!structure && structure.id === selectedStructureId; return ( { e.stopPropagation(); if (structure) onSelect(structure.id); }} > {children(isSelected)} ); } // 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; // 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); } // 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 }) { const toggleSystem = useAtlasStore((s) => s.toggleSystem); const selectStructure = useAtlasStore((s) => s.selectStructure); const bySlug = useMemo( () => new Map([...bySlugAll.values()].filter((s) => s.system.slug === "tegumentar").map((s) => [s.slug, s])), [bySlugAll], ); // 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; return (

Sistema Tegumentar (Pele) — modelo ilustrativo, gire e clique nas estruturas

{/* 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. */} )} {(sel) => } {(sel) => } {(sel) => } {(sel) => } {/* 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.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) => ( ))} )} {/* Complexo pilossebáceo — pelo + folículo + papila + eretor + sebácea, 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 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. */} null}> {/* Corpúsculo de Pacini — receptor em camadas concêntricas ("cebola"), fundo da hipoderme, junto à parede de corte. */} {(sel) => ( {[0.13, 0.1, 0.065, 0.035].map((r, i) => ( ))} )} {/* Terminação nervosa de tato — pequena cápsula na junção derme-epiderme */} {(sel) => ( )} {/* Fibra nervosa — filamento sinuoso subindo da hipoderme até a terminação de tato */} {(sel) => ( )} {/* Vascularização — rede de vasos + ramo arterial + ramo venoso */} {(sel) => ( )} {(sel) => ( )} {(sel) => ( )} {/* 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}> ))}

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.

); }