Files
orni-attack/source/core/physics/rigid_body.hpp
T
JailDesigner 0fd9360029 Fase 5: infraestructura del sistema de fisica vectorial
Crea los componentes base del nuevo motor de fisica sin alterar
todavia el comportamiento del juego. La migracion de Ship/Enemy/
Bullet al nuevo sistema queda para Fase 6.

Nuevos archivos:
- core/physics/rigid_body.hpp - struct POD con:
  * Vec2 position, velocity (cartesianas, NO polares)
  * float angle, angular_velocity
  * mass, inverse_mass (cacheado; 0 = estatico)
  * restitution (elasticidad 0..1)
  * linear_damping, angular_damping (s-1, exponencial)
  * radius (circulo de colision)
  * applyForce / applyImpulse / clearAccumulators
  * setStatic() para paredes/obstaculos
- core/physics/physics_world.hpp/.cpp - mundo fisico:
  * Almacena RigidBody* (no-owning, ownership en entidades)
  * setBounds(SDL_FRect) para paredes implicitas (PLAYAREA)
  * update(dt) = integrate + resolveBoundsCollisions + resolveBodyCollisions
  * Integrador semi-implicito de Euler + damping exponencial
  * Resolucion circulo-circulo con correccion posicional e impulsos elasticos
  * Formula del impulso: j = -(1+e)*(v_rel . n) / (1/m_a + 1/m_b)
  * Broadphase trivial O(n^2): suficiente para ~25 cuerpos del juego

Decisiones de diseno:
- Velocidad en cartesianas (Vec2) en lugar de la representacion polar
  actual (escalar velocidad + cos/sin del angulo cada frame). Adios al
  acoplamiento entre orientacion y direccion de movimiento.
- Composicion sobre herencia: RigidBody es un struct independiente que
  las entidades incrustaran como member en Fase 6, no una clase base.
- El integrador semi-implicito es la version estandar para juegos
  arcade (mas estable que Euler explicito sin coste extra).
- Damping exponencial (exp(-damping*dt)) en lugar de lineal: mantiene
  el feeling consistente independientemente del framerate.
- Sin gravedad: el juego es top-down, no necesita campo de fuerzas
  global. Las entidades aplican sus propias fuerzas (thrust).

Pendiente Fase 6:
- Anadir RigidBody body_ a Entity (member, no pointer)
- Migrar Ship: thrust como applyForce, en lugar de velocity_ escalar
- Migrar Enemy: cambios de direccion via applyImpulse, rebotes los
  hace PhysicsWorld
- Migrar Bullet: lineal sin damping, restitution=0 (no rebotan)
- Anadir PhysicsWorld a GameScene, registrar bodies, llamar update()

Compila y enlaza. Smoke test xvfb OK: el juego arranca igual que antes
(la nueva infraestructura aun no se invoca).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 13:12:06 +02:00

76 lines
2.9 KiB
C++

// rigid_body.hpp - Cuerpo rígido 2D para el sistema de física
// © 2025 Orni Attack
//
// Estructura POD-like que encapsula el estado físico de una entidad:
// posición, velocidad lineal/angular, masa, restitución y damping.
// El integrador es semi-implícito de Euler (estable para juegos arcade).
//
// Convenciones:
// - position: coordenadas lógicas (px), donde la entidad está en el mundo
// - angle: radianes; 0 apunta hacia arriba (eje Y negativo en SDL)
// - velocity: px/s en cartesianas (NO polares — adiós a cos/sin por entidad)
// - mass = 0 (inverse_mass = 0) representa un cuerpo estático (masa infinita)
// - restitution 0 = inelástico, 1 = elástico perfecto
// - linear_damping en s⁻¹ (fricción exponencial: v *= exp(-damping * dt))
#pragma once
#include "core/types.hpp"
namespace Physics {
struct RigidBody {
// --- Estado cinemático ---
Vec2 position{}; // Posición del centro (px)
Vec2 velocity{}; // Velocidad lineal (px/s)
float angle{0.0F}; // Orientación (rad)
float angular_velocity{0.0F}; // Velocidad angular (rad/s)
// --- Propiedades físicas ---
float mass{1.0F}; // Masa (kg, escala libre)
float inverse_mass{1.0F}; // 1/mass cacheado (0 = estático)
float restitution{0.5F}; // Elasticidad (0..1)
float linear_damping{0.0F}; // Fricción lineal (s⁻¹)
float angular_damping{0.0F}; // Fricción angular (s⁻¹)
float radius{0.0F}; // Radio de colisión (círculo)
// --- Fuerzas acumuladas (reseteadas tras cada integrate) ---
Vec2 force_accumulator{};
float torque_accumulator{0.0F};
// Configura la masa y precalcula inverse_mass.
// mass <= 0 marca el cuerpo como estático (inmovible por impulsos).
void setMass(float new_mass) {
mass = new_mass;
inverse_mass = (new_mass > 0.0F) ? 1.0F / new_mass : 0.0F;
}
// Marca el cuerpo como estático (paredes, obstáculos fijos).
void setStatic() {
mass = 0.0F;
inverse_mass = 0.0F;
velocity = Vec2{};
angular_velocity = 0.0F;
}
[[nodiscard]] auto isStatic() const -> bool { return inverse_mass == 0.0F; }
// Aplica una fuerza instantánea (acumulada para el siguiente integrate).
void applyForce(const Vec2& force) { force_accumulator += force; }
// Aplica un impulso (cambio inmediato de velocidad: Δv = J / m).
void applyImpulse(const Vec2& impulse) {
if (!isStatic()) {
velocity += impulse * inverse_mass;
}
}
// Resetea los acumuladores tras la integración.
void clearAccumulators() {
force_accumulator = Vec2{};
torque_accumulator = 0.0F;
}
};
} // namespace Physics