tegumentar 3d
This commit is contained in:
1 parent
805eb560a0
commit
15aff84de5
1 file changed
+128
-108
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo, type ReactNode } from "react";
|
import { useMemo, type ReactNode } from "react";
|
||||||
import { Canvas } from "@react-three/fiber";
|
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 * as THREE from "three";
|
||||||
import { useAtlasStore } from "../store/useAtlasStore.js";
|
import { useAtlasStore } from "../store/useAtlasStore.js";
|
||||||
import type { Structure } from "../lib/api.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,
|
// 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
|
// então virou diagrama 2D com hotspots. A pedido do usuário, tentamos de
|
||||||
// novo seguindo o MESMO princípio que funcionou pra citologia
|
// novo seguindo o MESMO princípio que funcionou pra citologia
|
||||||
// (CellDiagram3D.tsx): em vez de tentar esculpir uma "pele" realista, é um
|
// (CellDiagram3D.tsx): um bloco de tecido em corte, como a ilustração
|
||||||
// bloco de tecido em corte — como a ilustração clássica de livro-texto —
|
// clássica de livro-texto, usando as MESMAS 20 estruturas (nomes/
|
||||||
// com cada camada/anexo como uma forma geométrica simples e reconhecível,
|
// descrições reais) que já existiam no diagrama 2D. Ilustrativo, não
|
||||||
// clicável, usando as MESMAS 20 estruturas (nomes/descrições reais) que já
|
// derivado de dado real.
|
||||||
// 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 HIGHLIGHT = new THREE.Color("#22d3ee");
|
||||||
const BLACK = new THREE.Color(0x000000);
|
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
|
// Eixo Y é a profundidade da pele (0 = superfície, negativo = mais fundo).
|
||||||
// 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)
|
|
||||||
const EPIDERME_TOP = 0;
|
const EPIDERME_TOP = 0;
|
||||||
|
const ESTRATO_CORNEO_BOT = -0.02;
|
||||||
|
const CAMADA_PIGMENTAR_BOT = -0.08;
|
||||||
const EPIDERME_BOT = -0.12;
|
const EPIDERME_BOT = -0.12;
|
||||||
const DERME_BOT = -0.55;
|
const DERME_BOT = -0.55;
|
||||||
const HIPODERME_BOT = -1.0;
|
const HIPODERME_BOT = -1.0;
|
||||||
|
|
||||||
// Vasos/nervos como tubos ao longo de uma curva — evita usar cylinderGeometry
|
// Uma camada é um "L": bloco de trás (toda a largura, z de -1 a 0) + bloco
|
||||||
// reto demais pra estruturas que na pele são ramificadas/sinuosas.
|
// 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 (
|
||||||
|
<>
|
||||||
|
<RoundedBox args={[2, thickness, 1]} radius={radius} smoothness={2} position={[0, centerY, -0.5]}>
|
||||||
|
<meshStandardMaterial {...materialProps(color, sel)} />
|
||||||
|
</RoundedBox>
|
||||||
|
<RoundedBox args={[1, thickness, 1]} radius={radius} smoothness={2} position={[-0.5, centerY, 0.5]}>
|
||||||
|
<meshStandardMaterial {...materialProps(color, sel)} />
|
||||||
|
</RoundedBox>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
function tube(points: [number, number, number][], radius: number) {
|
||||||
const curve = new THREE.CatmullRomCurve3(points.map(([x, y, z]) => new THREE.Vector3(x, y, z)));
|
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);
|
return new THREE.TubeGeometry(curve, 24, radius, 8, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Posições fixas (não aleatórias) dos glóbulos de gordura na hipoderme.
|
// Pequenos "caroços" de gordura saltando das paredes de corte da hipoderme
|
||||||
const FAT_CELLS: [number, number, number, number][] = [
|
// pro vão aberto — dão textura à camada sem precisar de transparência
|
||||||
[-0.5, -0.72, -0.4, 0.09], [-0.2, -0.85, -0.55, 0.11], [0.15, -0.7, -0.35, 0.08],
|
// (a camada em si agora é opaca, já que a oclusão foi resolvida pela
|
||||||
[0.45, -0.88, -0.5, 0.1], [-0.55, -0.9, -0.15, 0.08], [0.1, -0.95, -0.6, 0.09],
|
// geometria em L, não por transparência).
|
||||||
[-0.15, -0.65, -0.6, 0.07], [0.5, -0.65, -0.25, 0.08],
|
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<string, Structure> }) {
|
export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map<string, Structure> }) {
|
||||||
@@ -88,10 +121,13 @@ export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map<string, Struc
|
|||||||
[bySlugAll],
|
[bySlugAll],
|
||||||
);
|
);
|
||||||
|
|
||||||
const vesselTube = useMemo(() => 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), []);
|
// Tudo dentro do vão aberto (x>0.05, z>0.05) fica em ar livre — nenhuma
|
||||||
const arteryTube = useMemo(() => tube([[0.85, -1.0, -0.85], [0.7, -0.7, -0.6], [0.55, -0.5, -0.3]], 0.035), []);
|
// camada sólida entre a câmera (posicionada em x,z positivos) e essas
|
||||||
const veinTube = useMemo(() => tube([[0.9, -1.0, -0.6], [0.75, -0.68, -0.35], [0.55, -0.48, -0.15]], 0.035), []);
|
// estruturas, então o clique sempre alcança a estrutura certa.
|
||||||
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), []);
|
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;
|
if (bySlug.size === 0) return null;
|
||||||
|
|
||||||
@@ -112,39 +148,52 @@ export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map<string, Struc
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full aspect-square rounded-lg border border-white/10 overflow-hidden bg-[#0b1220]">
|
<div className="w-full aspect-square rounded-lg border border-white/10 overflow-hidden bg-[#0b1220]">
|
||||||
<Canvas dpr={[1, 1.5]} camera={{ position: [2.6, 1.7, 2.6], fov: 42 }}>
|
<Canvas dpr={[1, 1.5]} camera={{ position: [2.3, 1.5, 2.3], fov: 42 }}>
|
||||||
<color attach="background" args={["#0b1220"]} />
|
<color attach="background" args={["#0b1220"]} />
|
||||||
<ambientLight intensity={0.75} />
|
<ambientLight intensity={0.75} />
|
||||||
<directionalLight position={[3, 4, 3]} intensity={1} />
|
<directionalLight position={[3, 4, 3]} intensity={1} />
|
||||||
<directionalLight position={[-3, -1, -2]} intensity={0.3} />
|
<directionalLight position={[-3, -1, -2]} intensity={0.3} />
|
||||||
|
|
||||||
{/* 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. */}
|
||||||
<Part slug="hipoderme" bySlug={bySlug} onSelect={selectStructure}>
|
<Part slug="hipoderme" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
{(sel) => (
|
{(sel) => (
|
||||||
<mesh position={[0, (DERME_BOT + HIPODERME_BOT) / 2, 0]}>
|
<>
|
||||||
<boxGeometry args={[BLOCK * 2, DERME_BOT - HIPODERME_BOT, BLOCK * 2]} />
|
<Slab yTop={DERME_BOT} yBot={HIPODERME_BOT} color="#eab676" sel={sel} radius={0.04} />
|
||||||
<meshStandardMaterial {...materialProps("#fde68a", sel, 0.9)} />
|
{/* Piso do vão — fecha o buraco no fundo da cunha; sem ele o
|
||||||
</mesh>
|
corte pareceria vazado e tudo perto do fundo pareceria
|
||||||
|
flutuando no vazio. */}
|
||||||
|
<mesh position={[0.5, HIPODERME_BOT, 0.5]} rotation={[-Math.PI / 2, 0, 0]}>
|
||||||
|
<planeGeometry args={[1, 1]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#eab676", sel)} side={THREE.DoubleSide} />
|
||||||
|
</mesh>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Part>
|
</Part>
|
||||||
|
|
||||||
{/* Derme — camada intermediária */}
|
|
||||||
<Part slug="derme" bySlug={bySlug} onSelect={selectStructure}>
|
<Part slug="derme" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
{(sel) => (
|
{(sel) => <Slab yTop={EPIDERME_BOT} yBot={DERME_BOT} color="#e08a72" sel={sel} radius={0.04} />}
|
||||||
<mesh position={[0, (EPIDERME_BOT + DERME_BOT) / 2, 0]}>
|
</Part>
|
||||||
<boxGeometry args={[BLOCK * 2, EPIDERME_BOT - DERME_BOT, BLOCK * 2]} />
|
<Part slug="estrato-germinativo" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
<meshStandardMaterial {...materialProps("#f4a896", sel, 0.92)} />
|
{(sel) => <Slab yTop={CAMADA_PIGMENTAR_BOT} yBot={EPIDERME_BOT} color="#c98a5e" sel={sel} radius={0.015} />}
|
||||||
</mesh>
|
</Part>
|
||||||
)}
|
<Part slug="camada-pigmentar" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
|
{(sel) => <Slab yTop={ESTRATO_CORNEO_BOT} yBot={CAMADA_PIGMENTAR_BOT} color="#a8703f" sel={sel} radius={0.02} />}
|
||||||
|
</Part>
|
||||||
|
<Part slug="estrato-corneo" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
|
{(sel) => <Slab yTop={EPIDERME_TOP} yBot={ESTRATO_CORNEO_BOT} color="#f2d9b8" sel={sel} radius={0.006} />}
|
||||||
</Part>
|
</Part>
|
||||||
|
|
||||||
{/* 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. */}
|
||||||
<Part slug="papila-dermica" bySlug={bySlug} onSelect={selectStructure}>
|
<Part slug="papila-dermica" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
{(sel) => (
|
{(sel) => (
|
||||||
<>
|
<>
|
||||||
{[-0.6, -0.2, 0.2, 0.6].map((x, i) => (
|
{[
|
||||||
<mesh key={i} position={[x, EPIDERME_BOT + 0.02, 0.5]}>
|
[0.15, 0.02], [0.4, 0.02], [0.65, 0.02],
|
||||||
<coneGeometry args={[0.06, 0.08, 12]} />
|
[0.02, 0.2], [0.02, 0.45], [0.02, 0.7],
|
||||||
|
].map(([x, z], i) => (
|
||||||
|
<mesh key={i} position={[x, EPIDERME_BOT + 0.02, z]}>
|
||||||
|
<sphereGeometry args={[0.045, 12, 12]} />
|
||||||
<meshStandardMaterial {...materialProps("#e0806a", sel)} />
|
<meshStandardMaterial {...materialProps("#e0806a", sel)} />
|
||||||
</mesh>
|
</mesh>
|
||||||
))}
|
))}
|
||||||
@@ -152,131 +201,102 @@ export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map<string, Struc
|
|||||||
)}
|
)}
|
||||||
</Part>
|
</Part>
|
||||||
|
|
||||||
{/* Epiderme — camada mais fina, no topo, dividida em 3 sub-camadas visíveis */}
|
|
||||||
<Part slug="estrato-germinativo" bySlug={bySlug} onSelect={selectStructure}>
|
|
||||||
{(sel) => (
|
|
||||||
<mesh position={[0, EPIDERME_BOT + 0.02, 0]}>
|
|
||||||
<boxGeometry args={[BLOCK * 2, 0.04, BLOCK * 2]} />
|
|
||||||
<meshStandardMaterial {...materialProps("#c98a5e", sel)} />
|
|
||||||
</mesh>
|
|
||||||
)}
|
|
||||||
</Part>
|
|
||||||
<Part slug="camada-pigmentar" bySlug={bySlug} onSelect={selectStructure}>
|
|
||||||
{(sel) => (
|
|
||||||
<mesh position={[0, EPIDERME_BOT + 0.07, 0]}>
|
|
||||||
<boxGeometry args={[BLOCK * 2, 0.06, BLOCK * 2]} />
|
|
||||||
<meshStandardMaterial {...materialProps("#a8703f", sel)} />
|
|
||||||
</mesh>
|
|
||||||
)}
|
|
||||||
</Part>
|
|
||||||
<Part slug="estrato-corneo" bySlug={bySlug} onSelect={selectStructure}>
|
|
||||||
{(sel) => (
|
|
||||||
<mesh position={[0, EPIDERME_TOP - 0.01, 0]}>
|
|
||||||
<boxGeometry args={[BLOCK * 2, 0.02, BLOCK * 2]} />
|
|
||||||
<meshStandardMaterial {...materialProps("#f2d9b8", sel)} />
|
|
||||||
</mesh>
|
|
||||||
)}
|
|
||||||
</Part>
|
|
||||||
|
|
||||||
{/* Complexo pilossebáceo — pelo + folículo + papila + eretor + sebácea,
|
{/* 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). */}
|
||||||
<Part slug="foliculo-piloso" bySlug={bySlug} onSelect={selectStructure}>
|
<Part slug="foliculo-piloso" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
{(sel) => (
|
{(sel) => (
|
||||||
<mesh position={[-0.35, (EPIDERME_TOP + HIPODERME_BOT + 0.2) / 2, 0.45]} rotation={[0, 0, 0.12]}>
|
<mesh position={[0.28, -0.41, 0.14]}>
|
||||||
<cylinderGeometry args={[0.07, 0.05, 1.05, 16, 1, true]} />
|
<cylinderGeometry args={[0.06, 0.042, 0.82, 16, 1, true]} />
|
||||||
<meshStandardMaterial {...materialProps("#d8b48c", sel, 0.85)} side={THREE.DoubleSide} />
|
<meshStandardMaterial {...materialProps("#d8b48c", sel, 0.85)} side={THREE.DoubleSide} />
|
||||||
</mesh>
|
</mesh>
|
||||||
)}
|
)}
|
||||||
</Part>
|
</Part>
|
||||||
<Part slug="papila-do-pelo" bySlug={bySlug} onSelect={selectStructure}>
|
<Part slug="papila-do-pelo" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
{(sel) => (
|
{(sel) => (
|
||||||
<mesh position={[-0.41, HIPODERME_BOT + 0.22, 0.45]}>
|
<mesh position={[0.28, -0.8, 0.14]}>
|
||||||
<sphereGeometry args={[0.06, 16, 16]} />
|
<sphereGeometry args={[0.05, 16, 16]} />
|
||||||
<meshStandardMaterial {...materialProps("#dc2626", sel)} />
|
<meshStandardMaterial {...materialProps("#dc2626", sel)} />
|
||||||
</mesh>
|
</mesh>
|
||||||
)}
|
)}
|
||||||
</Part>
|
</Part>
|
||||||
<Part slug="pelo" bySlug={bySlug} onSelect={selectStructure}>
|
<Part slug="pelo" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
{(sel) => (
|
{(sel) => (
|
||||||
<mesh position={[-0.3, EPIDERME_TOP + 0.4, 0.45]} rotation={[0, 0, 0.12]}>
|
<mesh position={[0.28, 0.17, 0.14]}>
|
||||||
<cylinderGeometry args={[0.012, 0.022, 0.75, 8]} />
|
<cylinderGeometry args={[0.012, 0.02, 0.34, 8]} />
|
||||||
<meshStandardMaterial {...materialProps("#3f2a1d", sel)} />
|
<meshStandardMaterial {...materialProps("#3f2a1d", sel)} />
|
||||||
</mesh>
|
</mesh>
|
||||||
)}
|
)}
|
||||||
</Part>
|
</Part>
|
||||||
<Part slug="musculo-eretor-do-pelo" bySlug={bySlug} onSelect={selectStructure}>
|
<Part slug="musculo-eretor-do-pelo" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
{(sel) => (
|
{(sel) => (
|
||||||
<mesh position={[-0.2, EPIDERME_BOT - 0.15, 0.3]} rotation={[0, 0, -0.55]}>
|
<mesh position={[0.44, -0.3, 0.18]} rotation={[0, 0, -0.6]}>
|
||||||
<boxGeometry args={[0.025, 0.32, 0.05]} />
|
<capsuleGeometry args={[0.013, 0.2, 4, 8]} />
|
||||||
<meshStandardMaterial {...materialProps("#b91c1c", sel)} />
|
<meshStandardMaterial {...materialProps("#b91c1c", sel)} />
|
||||||
</mesh>
|
</mesh>
|
||||||
)}
|
)}
|
||||||
</Part>
|
</Part>
|
||||||
<Part slug="glandula-sebacea" bySlug={bySlug} onSelect={selectStructure}>
|
<Part slug="glandula-sebacea" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
{(sel) => (
|
{(sel) => (
|
||||||
<mesh position={[-0.14, EPIDERME_BOT - 0.08, 0.38]}>
|
<mesh position={[0.5, -0.22, 0.16]} scale={[1, 1.3, 1]}>
|
||||||
<sphereGeometry args={[0.08, 16, 16]} />
|
<sphereGeometry args={[0.06, 16, 16]} />
|
||||||
<meshStandardMaterial {...materialProps("#fbbf24", sel, 0.95)} />
|
<meshStandardMaterial {...materialProps("#fbbf24", sel, 0.95)} />
|
||||||
</mesh>
|
</mesh>
|
||||||
)}
|
)}
|
||||||
</Part>
|
</Part>
|
||||||
|
|
||||||
{/* Glândula sudorípara — tubo enovelado (coil) na derme profunda/hipoderme,
|
{/* Glândula sudorípara — tubo enovelado (coil) na hipoderme, com
|
||||||
com ducto reto até a superfície e poro no topo. */}
|
ducto reto até a superfície e poro no topo. */}
|
||||||
<Part slug="glandula-sudoripara" bySlug={bySlug} onSelect={selectStructure}>
|
<Part slug="glandula-sudoripara" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
{(sel) => (
|
{(sel) => (
|
||||||
<group position={[0.4, HIPODERME_BOT + 0.3, -0.1]}>
|
<mesh position={[0.14, -0.8, 0.32]}>
|
||||||
<mesh>
|
<torusKnotGeometry args={[0.075, 0.02, 64, 8, 2, 3]} />
|
||||||
<torusKnotGeometry args={[0.11, 0.03, 64, 8, 2, 3]} />
|
<meshStandardMaterial {...materialProps("#38bdf8", sel, 0.95)} />
|
||||||
<meshStandardMaterial {...materialProps("#38bdf8", sel, 0.95)} />
|
</mesh>
|
||||||
</mesh>
|
|
||||||
</group>
|
|
||||||
)}
|
)}
|
||||||
</Part>
|
</Part>
|
||||||
<Part slug="poro-sudoriparo" bySlug={bySlug} onSelect={selectStructure}>
|
<Part slug="poro-sudoriparo" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
{(sel) => (
|
{(sel) => (
|
||||||
<mesh position={[0.4, EPIDERME_TOP + 0.005, -0.1]} rotation={[-Math.PI / 2, 0, 0]}>
|
<mesh position={[0.14, EPIDERME_TOP + 0.005, 0.32]} rotation={[-Math.PI / 2, 0, 0]}>
|
||||||
<ringGeometry args={[0.02, 0.035, 16]} />
|
<ringGeometry args={[0.018, 0.03, 16]} />
|
||||||
<meshStandardMaterial {...materialProps("#38bdf8", sel)} side={THREE.DoubleSide} />
|
<meshStandardMaterial {...materialProps("#38bdf8", sel)} side={THREE.DoubleSide} />
|
||||||
</mesh>
|
</mesh>
|
||||||
)}
|
)}
|
||||||
</Part>
|
</Part>
|
||||||
|
{/* Ducto reto ligando a glândula sudorípara ao poro — parte visual
|
||||||
{/* Ducto reto ligando a glândula sudorípara ao poro — parte visual da
|
da glândula, sem estrutura própria no banco. */}
|
||||||
glândula, sem estrutura própria no banco (usa o mesmo slug). */}
|
<mesh position={[0.14, (-0.8 + EPIDERME_TOP) / 2, 0.32]} raycast={() => null}>
|
||||||
<mesh position={[0.4, (HIPODERME_BOT + 0.3 + EPIDERME_TOP) / 2, -0.1]} raycast={() => null}>
|
<cylinderGeometry args={[0.018, 0.018, EPIDERME_TOP - -0.8, 10]} />
|
||||||
<cylinderGeometry args={[0.025, 0.025, EPIDERME_TOP - (HIPODERME_BOT + 0.3), 10]} />
|
<meshStandardMaterial color="#38bdf8" />
|
||||||
<meshStandardMaterial color="#38bdf8" transparent opacity={0.5} />
|
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
{/* 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. */}
|
||||||
<Part slug="corpusculo-de-pacini" bySlug={bySlug} onSelect={selectStructure}>
|
<Part slug="corpusculo-de-pacini" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
{(sel) => (
|
{(sel) => (
|
||||||
<group position={[0.15, HIPODERME_BOT + 0.18, 0.35]} rotation={[0.3, 0.4, 0]}>
|
<group position={[0.18, -0.85, 0.62]} rotation={[0.3, 0.4, 0]}>
|
||||||
{[0.16, 0.12, 0.08, 0.04].map((r, i) => (
|
{[0.13, 0.1, 0.065, 0.035].map((r, i) => (
|
||||||
<mesh key={i} scale={[1, 0.55, 1]}>
|
<mesh key={i} scale={[1, 0.55, 1]}>
|
||||||
<sphereGeometry args={[r, 16, 12]} />
|
<sphereGeometry args={[r, 16, 12]} />
|
||||||
<meshStandardMaterial
|
<meshStandardMaterial {...materialProps(i % 2 === 0 ? "#fef3c7" : "#fde68a", sel, 0.85)} />
|
||||||
{...materialProps("#fef3c7", sel, 0.5)}
|
|
||||||
wireframe={i % 2 === 0}
|
|
||||||
/>
|
|
||||||
</mesh>
|
</mesh>
|
||||||
))}
|
))}
|
||||||
</group>
|
</group>
|
||||||
)}
|
)}
|
||||||
</Part>
|
</Part>
|
||||||
|
|
||||||
{/* 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 */}
|
||||||
<Part slug="terminacao-nervosa-tato" bySlug={bySlug} onSelect={selectStructure}>
|
<Part slug="terminacao-nervosa-tato" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
{(sel) => (
|
{(sel) => (
|
||||||
<mesh position={[-0.55, EPIDERME_BOT - 0.02, -0.2]} rotation={[Math.PI / 2, 0, 0]}>
|
<mesh position={[0.2, EPIDERME_BOT - 0.03, 0.7]} rotation={[Math.PI / 2, 0, 0]}>
|
||||||
<capsuleGeometry args={[0.045, 0.06, 4, 8]} />
|
<capsuleGeometry args={[0.04, 0.05, 4, 8]} />
|
||||||
<meshStandardMaterial {...materialProps("#fef08a", sel)} />
|
<meshStandardMaterial {...materialProps("#fef08a", sel)} />
|
||||||
</mesh>
|
</mesh>
|
||||||
)}
|
)}
|
||||||
</Part>
|
</Part>
|
||||||
|
|
||||||
{/* Fibra nervosa — filamento sinuoso subindo da hipoderme até a derme */}
|
{/* Fibra nervosa — filamento sinuoso subindo da hipoderme até a
|
||||||
|
terminação de tato */}
|
||||||
<Part slug="fibra-nervosa" bySlug={bySlug} onSelect={selectStructure}>
|
<Part slug="fibra-nervosa" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
{(sel) => (
|
{(sel) => (
|
||||||
<mesh geometry={nerveFiberTube}>
|
<mesh geometry={nerveFiberTube}>
|
||||||
@@ -285,7 +305,7 @@ export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map<string, Struc
|
|||||||
)}
|
)}
|
||||||
</Part>
|
</Part>
|
||||||
|
|
||||||
{/* Vascularização — rede de vasos + ramo arterial + ramo venoso, tubos sinuosos */}
|
{/* Vascularização — rede de vasos + ramo arterial + ramo venoso */}
|
||||||
<Part slug="vasos-sanguineos-linfaticos" bySlug={bySlug} onSelect={selectStructure}>
|
<Part slug="vasos-sanguineos-linfaticos" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
{(sel) => (
|
{(sel) => (
|
||||||
<mesh geometry={vesselTube}>
|
<mesh geometry={vesselTube}>
|
||||||
@@ -308,12 +328,12 @@ export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map<string, Struc
|
|||||||
)}
|
)}
|
||||||
</Part>
|
</Part>
|
||||||
|
|
||||||
{/* Glóbulos de gordura na hipoderme — só decorativos (compõem a textura
|
{/* Caroços de gordura saltando das paredes de corte da hipoderme —
|
||||||
da camada, sem estrutura própria no banco). */}
|
só textura decorativa, sem estrutura própria no banco. */}
|
||||||
{FAT_CELLS.map(([x, y, z, r], i) => (
|
{FAT_BUMPS.map(([x, y, z, r], i) => (
|
||||||
<mesh key={i} position={[x, y, z]} raycast={() => null}>
|
<mesh key={i} position={[x, y, z]} raycast={() => null}>
|
||||||
<sphereGeometry args={[r, 12, 12]} />
|
<sphereGeometry args={[r, 12, 12]} />
|
||||||
<meshStandardMaterial color="#fef9c3" transparent opacity={0.5} />
|
<meshStandardMaterial color="#fde9b8" />
|
||||||
</mesh>
|
</mesh>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
@@ -322,7 +342,7 @@ export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map<string, Struc
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-[11px] text-slate-500 mt-2">
|
<p className="text-[11px] text-slate-500 mt-2">
|
||||||
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.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user