componentes da celula
Signed-off-by: Rodrigo Bertoldo de Sousa Martins <rodrigo@martinsevieira.adv.br>
This commit is contained in:
1 parent
84ee99aabf
commit
3195bdd724
4 files changed
+179
-20
No files matched your search
@@ -2,15 +2,19 @@ import { useMemo, useRef } from "react";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import * as THREE from "three";
|
||||
import type { Stage } from "../../data/cellDivisionStages.js";
|
||||
import type { CellComponentId } from "../../data/cellDivisionComponents.js";
|
||||
|
||||
// Cena 3D ilustrativa (mesmo princípio do CellDiagram3D.tsx e do
|
||||
// Mannequin.tsx: sem malha real disponível pra esse conteúdo, então
|
||||
// desenhamos à mão com primitivas do Three.js) — recebe só o ESTÁGIO alvo
|
||||
// atual (`stage`) e anima suavemente toda célula/cromátide até lá sempre
|
||||
// que o estágio muda, via interpolação por frame (useFrame), sem precisar
|
||||
// de nenhuma biblioteca de animação.
|
||||
// de nenhuma biblioteca de animação. Também aceita clique nos componentes
|
||||
// (cromossomo, centrossomo, fibra) — ver `onSelect`.
|
||||
|
||||
const MAX_CELLS = 4;
|
||||
const HIGHLIGHT = new THREE.Color("#22d3ee");
|
||||
const BLACK = new THREE.Color(0x000000);
|
||||
|
||||
// Fator de suavização independente de framerate: a cada frame, anda uma
|
||||
// fração `k` do caminho que falta até o alvo — converge suave em ~0.4s
|
||||
@@ -19,20 +23,31 @@ function smoothing(delta: number, halfLifeSeconds = 0.18) {
|
||||
return 1 - Math.pow(0.5, delta / halfLifeSeconds);
|
||||
}
|
||||
|
||||
function ChromatidShape({ color }: { color: string }) {
|
||||
interface SelectionProps {
|
||||
selected: CellComponentId | null;
|
||||
onSelect: (id: CellComponentId) => void;
|
||||
}
|
||||
|
||||
function ChromatidShape({ color, isSelected }: { color: string; isSelected: boolean }) {
|
||||
const matProps = {
|
||||
color,
|
||||
roughness: 0.5,
|
||||
emissive: isSelected ? HIGHLIGHT : BLACK,
|
||||
emissiveIntensity: isSelected ? 0.8 : 0,
|
||||
};
|
||||
return (
|
||||
<group>
|
||||
<mesh position={[-0.045, 0.08, 0]} rotation={[0, 0, Math.PI / 7]}>
|
||||
<capsuleGeometry args={[0.022, 0.14, 3, 6]} />
|
||||
<meshStandardMaterial color={color} roughness={0.5} />
|
||||
<meshStandardMaterial {...matProps} />
|
||||
</mesh>
|
||||
<mesh position={[0.045, 0.08, 0]} rotation={[0, 0, -Math.PI / 7]}>
|
||||
<capsuleGeometry args={[0.022, 0.14, 3, 6]} />
|
||||
<meshStandardMaterial color={color} roughness={0.5} />
|
||||
<meshStandardMaterial {...matProps} />
|
||||
</mesh>
|
||||
<mesh>
|
||||
<sphereGeometry args={[0.03, 10, 10]} />
|
||||
<meshStandardMaterial color={color} roughness={0.5} />
|
||||
<meshStandardMaterial {...matProps} />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
@@ -44,7 +59,15 @@ interface ChromatidTargetWorld {
|
||||
color: string;
|
||||
}
|
||||
|
||||
function AnimatedChromatid({ id, targets }: { id: string; targets: Map<string, ChromatidTargetWorld> }) {
|
||||
function AnimatedChromatid({
|
||||
id,
|
||||
targets,
|
||||
selected,
|
||||
onSelect,
|
||||
}: {
|
||||
id: string;
|
||||
targets: Map<string, ChromatidTargetWorld>;
|
||||
} & SelectionProps) {
|
||||
// Posição/rotação são controladas 100% via useFrame (nunca como prop
|
||||
// JSX reativa) — se passássemos `position={...}` no JSX, o
|
||||
// react-three-fiber reaplicaria (e "teleportaria") o objeto pra lá toda
|
||||
@@ -65,8 +88,14 @@ function AnimatedChromatid({ id, targets }: { id: string; targets: Map<string, C
|
||||
});
|
||||
|
||||
return (
|
||||
<group ref={ref}>
|
||||
<ChromatidShape color={initialColor} />
|
||||
<group
|
||||
ref={ref}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onSelect("cromossomo");
|
||||
}}
|
||||
>
|
||||
<ChromatidShape color={initialColor} isSelected={selected === "cromossomo"} />
|
||||
</group>
|
||||
);
|
||||
}
|
||||
@@ -83,6 +112,8 @@ interface CellSlotState {
|
||||
function AnimatedCellSlot({
|
||||
target,
|
||||
state,
|
||||
selected,
|
||||
onSelect,
|
||||
}: {
|
||||
target: {
|
||||
center: [number, number, number];
|
||||
@@ -93,7 +124,7 @@ function AnimatedCellSlot({
|
||||
elongated: number;
|
||||
} | null;
|
||||
state: CellSlotState;
|
||||
}) {
|
||||
} & SelectionProps) {
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const membraneRef = useRef<THREE.Mesh>(null);
|
||||
const membraneMatRef = useRef<THREE.MeshStandardMaterial>(null);
|
||||
@@ -139,9 +170,17 @@ function AnimatedCellSlot({
|
||||
}
|
||||
});
|
||||
|
||||
const centrosomeSelected = selected === "centrossomo";
|
||||
const fiberSelected = selected === "fibras";
|
||||
|
||||
return (
|
||||
<group ref={groupRef}>
|
||||
{/* membrana plasmática — translúcida, não recebe clique (só decorativa) */}
|
||||
{/* membrana plasmática e envelope nuclear: sem raycast (`raycast={() =>
|
||||
null}`) de propósito — sendo "cascas" que envolvem tudo o resto,
|
||||
elas sempre seriam o primeiro acerto de qualquer raio de clique,
|
||||
impedindo o clique de alcançar cromossomo/centrossomo/fibra por
|
||||
dentro. Continuam explicáveis pela lista de componentes no
|
||||
painel (CellDivisionPage.tsx), só não são clicáveis AQUI. */}
|
||||
<mesh ref={membraneRef} raycast={() => null}>
|
||||
<sphereGeometry args={[0.8, 28, 28]} />
|
||||
<meshStandardMaterial
|
||||
@@ -153,7 +192,6 @@ function AnimatedCellSlot({
|
||||
depthWrite={false}
|
||||
/>
|
||||
</mesh>
|
||||
{/* envelope nuclear — esfera um pouco menor, desfaz/refaz conforme a fase */}
|
||||
<mesh raycast={() => null}>
|
||||
<sphereGeometry args={[0.42, 24, 24]} />
|
||||
<meshStandardMaterial ref={envelopeMatRef} color="#c4b5fd" transparent opacity={0} depthWrite={false} />
|
||||
@@ -163,9 +201,20 @@ function AnimatedCellSlot({
|
||||
da prófase. */}
|
||||
<group ref={centrosomesRef}>
|
||||
{[-1, 1].map((side) => (
|
||||
<mesh key={side} position={[side * 0.65, 0, 0]}>
|
||||
<mesh
|
||||
key={side}
|
||||
position={[side * 0.65, 0, 0]}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onSelect("centrossomo");
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.035, 10, 10]} />
|
||||
<meshStandardMaterial color="#f8fafc" />
|
||||
<meshStandardMaterial
|
||||
color="#f8fafc"
|
||||
emissive={centrosomeSelected ? HIGHLIGHT : BLACK}
|
||||
emissiveIntensity={centrosomeSelected ? 0.8 : 0}
|
||||
/>
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
@@ -178,9 +227,21 @@ function AnimatedCellSlot({
|
||||
key={i}
|
||||
position={[side * 0.65 + side * -0.32, offset * 0.6, offset]}
|
||||
rotation={[0, 0, side > 0 ? Math.PI / 2 + offset * 0.3 : -Math.PI / 2 + offset * 0.3]}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onSelect("fibras");
|
||||
}}
|
||||
>
|
||||
<cylinderGeometry args={[0.004, 0.004, 0.64, 4]} />
|
||||
<meshStandardMaterial color="#f8fafc" transparent opacity={0.4} />
|
||||
{/* raio um pouco maior só pra facilitar o clique — a fibra
|
||||
visual continua fina, isso é puramente a área alvo */}
|
||||
<cylinderGeometry args={[0.02, 0.02, 0.64, 6]} />
|
||||
<meshStandardMaterial
|
||||
color="#f8fafc"
|
||||
transparent
|
||||
opacity={fiberSelected ? 0.9 : 0.4}
|
||||
emissive={fiberSelected ? HIGHLIGHT : BLACK}
|
||||
emissiveIntensity={fiberSelected ? 0.6 : 0}
|
||||
/>
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
@@ -190,7 +251,7 @@ function AnimatedCellSlot({
|
||||
);
|
||||
}
|
||||
|
||||
export function CellDivisionScene({ stage }: { stage: Stage }) {
|
||||
export function CellDivisionScene({ stage, selected, onSelect }: { stage: Stage } & SelectionProps) {
|
||||
// Estado imperativo (fora do React) por slot de célula — persiste entre
|
||||
// mudanças de estágio pra animação continuar de onde estava, sem exigir
|
||||
// que o número de células seja o mesmo do estágio anterior.
|
||||
@@ -235,11 +296,11 @@ export function CellDivisionScene({ stage }: { stage: Stage }) {
|
||||
<directionalLight position={[-2, -1, -2]} intensity={0.3} />
|
||||
|
||||
{cellStates.map((state, i) => (
|
||||
<AnimatedCellSlot key={i} target={stage.cells[i] ?? null} state={state} />
|
||||
<AnimatedCellSlot key={i} target={stage.cells[i] ?? null} state={state} selected={selected} onSelect={onSelect} />
|
||||
))}
|
||||
|
||||
{chromatidIds.map((id) => (
|
||||
<AnimatedChromatid key={id} id={id} targets={chromatidTargets} />
|
||||
<AnimatedChromatid key={id} id={id} targets={chromatidTargets} selected={selected} onSelect={onSelect} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user