fibras do fuso
Signed-off-by: Rodrigo Bertoldo de Sousa Martins <rodrigo@martinsevieira.adv.br>
This commit is contained in:
1 parent
3195bdd724
commit
2dc5a167ed
1 file changed
+53
-25
@@ -23,6 +23,38 @@ function smoothing(delta: number, halfLifeSeconds = 0.18) {
|
||||
return 1 - Math.pow(0.5, delta / halfLifeSeconds);
|
||||
}
|
||||
|
||||
// Geometria das fibras do fuso: cada cilindro vai de A até B de verdade
|
||||
// (não só "perto" do centrossomo com um ângulo aproximado) — calculamos
|
||||
// a posição (ponto médio), o comprimento e a rotação (quaternion) direto
|
||||
// dos dois pontos, então a fibra sempre nasce exatamente na esfera do
|
||||
// centrossomo e se afunila em leque até perto do centro da célula, como
|
||||
// o fuso mitótico de verdade.
|
||||
const CENTROSOME_X = 0.65;
|
||||
const FIBER_FAN = [-0.26, -0.12, 0.12, 0.26];
|
||||
|
||||
function cylinderBetween(from: THREE.Vector3, to: THREE.Vector3) {
|
||||
const direction = new THREE.Vector3().subVectors(to, from);
|
||||
const length = direction.length();
|
||||
const position = new THREE.Vector3().addVectors(from, to).multiplyScalar(0.5);
|
||||
const quaternion = new THREE.Quaternion().setFromUnitVectors(
|
||||
new THREE.Vector3(0, 1, 0), // eixo padrão do CylinderGeometry
|
||||
direction.clone().normalize(),
|
||||
);
|
||||
return { position, quaternion, length };
|
||||
}
|
||||
|
||||
// Um leque de fibras por polo, todas nascendo EXATAMENTE no centrossomo
|
||||
// (0, ±CENTROSOME_X ao longo de X) e convergindo pra perto do centro da
|
||||
// célula — computado uma única vez (é geometria fixa, não depende de
|
||||
// estado/props) em vez de recalcular a cada render.
|
||||
const FIBER_DEFS = ([-1, 1] as const).flatMap((side) => {
|
||||
const from = new THREE.Vector3(side * CENTROSOME_X, 0, 0);
|
||||
return FIBER_FAN.map((offset) => {
|
||||
const to = new THREE.Vector3(side * 0.12, offset * 0.55, offset * 0.55);
|
||||
return { side, ...cylinderBetween(from, to) };
|
||||
});
|
||||
});
|
||||
|
||||
interface SelectionProps {
|
||||
selected: CellComponentId | null;
|
||||
onSelect: (id: CellComponentId) => void;
|
||||
@@ -220,31 +252,27 @@ function AnimatedCellSlot({
|
||||
</group>
|
||||
{/* fibras do fuso — só a partir da prófase (quando `spindle: true`) */}
|
||||
<group ref={fibersRef}>
|
||||
{[-1, 1].map((side) => (
|
||||
<group key={side}>
|
||||
{[-0.25, -0.1, 0.1, 0.25].map((offset, i) => (
|
||||
<mesh
|
||||
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");
|
||||
}}
|
||||
>
|
||||
{/* 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>
|
||||
{FIBER_DEFS.map((fiber, i) => (
|
||||
<mesh
|
||||
key={i}
|
||||
position={fiber.position}
|
||||
quaternion={fiber.quaternion}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onSelect("fibras");
|
||||
}}
|
||||
>
|
||||
{/* raio um pouco maior só pra facilitar o clique — a fibra
|
||||
visual continua fina, isso é puramente a área alvo */}
|
||||
<cylinderGeometry args={[0.018, 0.018, fiber.length, 6]} />
|
||||
<meshStandardMaterial
|
||||
color="#f8fafc"
|
||||
transparent
|
||||
opacity={fiberSelected ? 0.9 : 0.4}
|
||||
emissive={fiberSelected ? HIGHLIGHT : BLACK}
|
||||
emissiveIntensity={fiberSelected ? 0.6 : 0}
|
||||
/>
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
</group>
|
||||
|
||||
Reference in new issue
Block a user