Eliminación del archivo "Memoria de evolución" y cambios en la creación de botellas en la simulación. Se reemplazó el método AddCircle por AddBotella en ucBotella y ucBotellaCuello, y se ajustaron propiedades en simBotella para mejorar la gestión de inercia y fricción. Se eliminaron referencias a barreras físicas en BEPU, optimizando la lógica de colisiones y ajustando parámetros de fricción para mejorar la simulación.

This commit is contained in:
Miguel 2025-07-05 17:04:39 +02:00
parent de0e3ce5f0
commit eb6ed62d5b
8 changed files with 86 additions and 85 deletions

4
.cursor/rules/reglas.mdc Normal file
View File

@ -0,0 +1,4 @@
---
alwaysApply: true
---
Quisiera que con los conocimientos importantes y desiciones importantes que hemos adquirido y utilizado los agregues a @MemoriadeEvolucion.md manteniendo el estilo que ya tenemos de texto simple sin demasiado codigo y una semantica resumida.

View File

@ -1,10 +0,0 @@
### Aqui se mantiene la memoria de evolucion de las distintas decisiones que fueron tomadas y porque
* Se usan esferas en vez de cilindros para mejorar la eficiencia. En el Debug3D si se usan cilindros
* Se usaron LinearAxisMotors originalmente sobre las botellas para crear un movimiento segun las colisicones con los transportes. Luego se busco un sistema que represente mejor la fisica y por cuestiones de eficiencia en vez de crear placas en movimiento real, se usaron cuerpos Box y Triangulos kinematic con velocidad lineal o angular per se interviene en el Integrador para que antes de integrar la velocidad se quite la velocidad y antes del proximo Step se vuelve a aplicar la velocidad. Esto permite que el integrador no mueva los objetos kinetic pero si aplique velocidad por friccion a los objetos dinamic ( simBotella )
* Originalmente se habia puesto todos los objetos en awaken para poder usar las colisiones constantemente incluso con objetos en modo sleep para que los simBarrera puedan detectar las colisiones. Ahora que se usar RayCast podemos dejar que las simBotellas se duerman
* La unica clase que se ha terminado de refactorizar respecto a el cambio de coordenadas es simBarrera y ucPhotocell. El concepto es poder separar usando metodos de SimulationManagerBEPU y estructuras como BarreraData la conversion de coordenadas de WPF a coordenadas BEPU. Para esto se usa CoordinateConverter que permite bidireccionalmente convertir las coordenadas pero esto solo se debe usar en SimulationManagerBEPU las clases derivadas de osBase solo deben manejar coordenadas WPF, mientras que las clases dervadas de simBase solo deben almacenar y usar coordenadas BEPU. La z tambien es algo que se debe transferir a SimulationManagerBEPU ya que los objetos simBase deberian recibir tambien sus coordenadas de Z desde SimulationManagerBEPU y ser SimulationManagerBEPU el que gestione las Z.

View File

@ -0,0 +1,19 @@
### Aqui se mantiene la memoria de evolucion de las distintas decisiones que fueron tomadas y porque
BEPU.cs : SimulationManagerBEPU gestor de la simulacion con el motor BEPUphysics y punto de creacion y modificacion de los objetos dentro del mundo que se derivan des simBase
simBase : Clase base que da un marco a el resto de los objetos
simBarrera : Simula una fotocelula con un espejo usando RayCast
simBotella : Simula una botella que puede transitar por los transportes simTransporte o simCurve
simCurve : Simula una curva o arco de curva de transporte
* Se usaron esferas en vez de cilindros para mejorar la eficiencia. En el Debug3D si se usan cilindros. **Revertido**: Se ha vuelto a usar `Cylinder` para las `simBotella` ya que el nuevo sistema de fricción debería prevenir la rotación indeseada que ocurría con los `LinearAxisMotor`.
* Se reemplazó el sistema de `LinearAxisMotor` que actuaba sobre las `simBotella` por un sistema basado en fricción. Los transportes (`simTransporte` y `simCurve`) ahora son cuerpos cinemáticos que no se mueven de su sitio. Para lograr que arrastren a las botellas, se les asigna una velocidad (`Velocity.Linear` o `Velocity.Angular`) justo antes de que el solver se ejecute (en `OnSubstepStarted`) y se les quita justo después (en `OnSubstepEnded`). Esto permite que los cuerpos cinemáticos transmitan su velocidad a través de la fricción durante la simulación, pero evita que el `PoseIntegrator` los desplace de su posición original, ya que su velocidad es cero cuando se integra la pose.
* Para aumentar la estabilidad de las `simBotella` y evitar que roten descontroladamente sobre su eje Z al ser arrastradas, se implementó un callback `IntegrateVelocity` personalizado. Este callback identifica las botellas durante la integración y les aplica un amortiguamiento angular (`AngularDamping`) adicional solo en el eje Z. Se descartó la idea inicial de modificar dinámicamente la masa de las botellas dentro de este callback, ya que la arquitectura de BEPUphysics no permite cambiar la masa o la inercia de un cuerpo durante la fase de integración de velocidad.
* Originalmente se habia puesto todos los objetos en awaken para poder usar las colisiones constantemente incluso con objetos en modo sleep para que los simBarrera puedan detectar las colisiones. Ahora que se usar RayCast podemos dejar que las simBotellas se duerman
* La unica clase que se ha terminado de refactorizar respecto a el cambio de coordenadas es simBarrera y ucPhotocell. El concepto es poder separar usando metodos de SimulationManagerBEPU y estructuras como BarreraData la conversion de coordenadas de WPF a coordenadas BEPU. Para esto se usa CoordinateConverter que permite bidireccionalmente convertir las coordenadas pero esto solo se debe usar en SimulationManagerBEPU las clases derivadas de osBase solo deben manejar coordenadas WPF, mientras que las clases dervadas de simBase solo deben almacenar y usar coordenadas BEPU. La z tambien es algo que se debe transferir a SimulationManagerBEPU ya que los objetos simBase deberian recibir tambien sus coordenadas de Z desde SimulationManagerBEPU y ser SimulationManagerBEPU el que gestione las Z.

View File

@ -146,7 +146,9 @@ namespace CtrEditor.ObjetosSim
// Se llama cuando inicia la simulación - crear geometría si no existe
if (SimGeometria == null)
{
SimGeometria = simulationManager.AddCircle(Diametro, GetCentro(), Mass);
SimGeometria = simulationManager.AddBotella(Diametro, GetCentro(), Mass);
SimGeometria.SimObjectType = "Botella";
SimGeometria.WpfObject = this;
}
else
{
@ -196,7 +198,7 @@ namespace CtrEditor.ObjetosSim
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
base.ucLoaded();
SimGeometria = simulationManager.AddCircle(Diametro, GetCentro(), Mass);
SimGeometria = simulationManager.AddBotella(Diametro, GetCentro(), Mass);
}
public override void ucUnLoaded()
{

View File

@ -180,7 +180,7 @@ namespace CtrEditor.ObjetosSim
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
// crear el objeto de simulacion
base.ucLoaded();
SimGeometria = simulationManager.AddCircle(Diametro, GetCentro(), Mass);
SimGeometria = simulationManager.AddBotella(Diametro, GetCentro(), Mass);
}
public override void ucUnLoaded()
{

View File

@ -98,19 +98,10 @@ namespace CtrEditor.Simulacion
{
var botellaA = GetBotellaFromCollidable(pair.A);
var botellaB = GetBotellaFromCollidable(pair.B);
// ✅ ELIMINADO: Las barreras ya no tienen body físico
// var barreraA = GetBarreraFromCollidable(pair.A);
// var barreraB = GetBarreraFromCollidable(pair.B);
var descarteA = GetDescarteFromCollidable(pair.A);
var descarteB = GetDescarteFromCollidable(pair.B);
var botella = botellaA ?? botellaB;
// ✅ ELIMINADO: Las barreras ya no participan en colisiones físicas
// if (barreraA != null || barreraB != null)
// {
// return false; // NO generar contacto físico
// }
// ✅ DESCARTES como sensores puros
if (descarteA != null || descarteB != null)
{
@ -136,9 +127,9 @@ namespace CtrEditor.Simulacion
// en la clase simTransporte. El motor de físicas se encarga del resto.
// ✅ Fricción ajustada para un arrastre firme pero no excesivo.
pairMaterial.FrictionCoefficient = 1.5f;
pairMaterial.MaximumRecoveryVelocity = 2.0f;
pairMaterial.SpringSettings = new SpringSettings(30, 1);
pairMaterial.FrictionCoefficient = 1.2f;
pairMaterial.MaximumRecoveryVelocity = 1.0f;
pairMaterial.SpringSettings = new SpringSettings(80, 1);
}
// ✅ CONTACTO BOTELLA-CURVA: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA
else if (botella != null && (curveA != null || curveB != null))
@ -147,9 +138,9 @@ namespace CtrEditor.Simulacion
// para calcular las fuerzas de fricción y arrastrar la botella.
// ✅ Fricción ajustada para un arrastre firme pero no excesivo.
pairMaterial.FrictionCoefficient = 1.5f;
pairMaterial.MaximumRecoveryVelocity = 2.0f;
pairMaterial.SpringSettings = new SpringSettings(30, 1);
pairMaterial.FrictionCoefficient = 1f;
pairMaterial.MaximumRecoveryVelocity = 1.0f;
pairMaterial.SpringSettings = new SpringSettings(80, 1);
}
// ✅ CONTACTO BOTELLA-GUÍA: Configuración específica de fricción
else if (botella != null && (GetGuiaFromCollidable(pair.A) != null || GetGuiaFromCollidable(pair.B) != null))
@ -157,7 +148,14 @@ namespace CtrEditor.Simulacion
// Fricción baja para las guías, deben deslizar, no arrastrar.
pairMaterial.FrictionCoefficient = 0.1f;
pairMaterial.MaximumRecoveryVelocity = 1f;
pairMaterial.SpringSettings = new SpringSettings(80, 6);
pairMaterial.SpringSettings = new SpringSettings(20, 1);
}
// ✅ NUEVO: CONTACTO BOTELLA-BOTELLA: Configuración más suave para evitar el comportamiento "pegajoso".
else if (botellaA != null && botellaB != null)
{
pairMaterial.FrictionCoefficient = 0.01f; // Un poco menos de fricción.
pairMaterial.MaximumRecoveryVelocity = 1.5f; // Menos rebote.
pairMaterial.SpringSettings = new SpringSettings(20, 0.5f); // Muelle MUCHO más suave y críticamente amortiguado.
}
// Ajustes básicos para otras botellas
else if (botella != null)
@ -165,7 +163,7 @@ namespace CtrEditor.Simulacion
// Fricción moderada para colisiones entre botellas.
pairMaterial.FrictionCoefficient = 0.3f;
pairMaterial.MaximumRecoveryVelocity = 1f;
pairMaterial.SpringSettings = new SpringSettings(80, 6);
pairMaterial.SpringSettings = new SpringSettings(80, 1);
}
}
@ -761,7 +759,7 @@ namespace CtrEditor.Simulacion
{
if (simulation.Bodies.BodyExists(cuerpo.BodyHandle))
{
cuerpo.LimitRotationToXYPlane();
// cuerpo.LimitRotationToXYPlane();
simulation.Awakener.AwakenBody(cuerpo.BodyHandle);
}
}
@ -805,48 +803,24 @@ namespace CtrEditor.Simulacion
/// </summary>
public void Remove(simBase Objeto)
{
if (Objeto == null) return;
try
if (Objeto != null)
{
// ✅ SIMPLIFICADO - eliminar lógica de masa especial
UnregisterObjectHandle(Objeto); // ✅ NUEVO
Objeto.RemoverBody(); // ✅ CONSERVAR - remover el cuerpo de BEPU
// ✅ NUEVO - Limpiar dimensiones almacenadas en osBase
CtrEditor.ObjetosSim.osBase.ClearStoredDimensions(Objeto);
// ✅ REMOVER de la lista inmediatamente para evitar referencias colgantes
Cuerpos.Remove(Objeto);
if (Is3DUpdateEnabled)
// Aplazar la eliminación para evitar problemas de concurrencia
_deferredActions.Add(() =>
{
Visualization3DManager?.SynchronizeWorld();
}
//System.Diagnostics.Debug.WriteLine($"[Remove] ✅ Objeto marcado para eliminación diferida: {Objeto.GetType().Name}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[Remove] ❌ Error marcando objeto {Objeto?.GetType().Name}: {ex.Message}");
// ✅ CRÍTICO: Siempre remover de la lista incluso si falló
Cuerpos.Remove(Objeto);
UnregisterObjectHandle(Objeto);
Objeto.RemoverBody();
Cuerpos.Remove(Objeto);
});
}
}
public simBotella AddCircle(float diameter, Vector2 position, float mass)
public simBotella AddBotella(float diameter, Vector2 position, float mass)
{
var botella = new simBotella(simulation, _deferredActions, diameter, position, mass);
Cuerpos.Add(botella);
RegisterObjectHandle(botella); // ✅ NUEVO
if (Is3DUpdateEnabled)
{
Visualization3DManager?.SynchronizeWorld();
}
RegisterObjectHandle(botella);
return botella;
}

View File

@ -11,6 +11,8 @@ namespace CtrEditor.Simulacion
{
public class simBase
{
public object WpfObject { get; set; }
public string SimObjectType { get; set; }
public BodyHandle BodyHandle { get; protected set; }
public Simulation _simulation;
protected bool _bodyCreated = false; // Bandera para saber si hemos creado un cuerpo

View File

@ -113,9 +113,16 @@ namespace CtrEditor.Simulacion
_mass = value;
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
// Usar esfera simple - sin complejidad de inercia personalizada
var sphere = new Sphere(Radius);
var inertia = sphere.ComputeInertia(value);
var cylinder = new Cylinder(Radius, Height);
var inertia = cylinder.ComputeInertia(_mass);
// Modificamos el tensor de inercia para evitar que la botella vuelque.
var inverseInertia = inertia.InverseInertiaTensor;
inverseInertia.XX = 0f; // Resistencia infinita a la rotación en el eje X (vuelco).
inverseInertia.YY = 0f; // Resistencia infinita a la rotación en el eje Y (vuelco).
// La resistencia en ZZ (eje Z) se mantiene, permitiendo que la botella gire sobre su base.
inertia.InverseInertiaTensor = inverseInertia;
_simulation.Bodies.SetLocalInertia(BodyHandle, inertia);
}
}
@ -125,27 +132,30 @@ namespace CtrEditor.Simulacion
{
RemoverBody();
// Usar ESFERA en BEPU para simplicidad matemática y eficiencia
var sphere = new Sphere(Radius);
var shapeIndex = _simulation.Shapes.Add(sphere);
// Inercia estándar de esfera - sin complejidad adicional
var inertia = sphere.ComputeInertia(_mass);
// NUNCA DORMIR - Valor negativo significa que las botellas JAMÁS entran en sleep mode
// Esto es crítico para detección continua de barreras, transportes y descartes
var activityDescription = new BodyActivityDescription(-1f); // -1 = NUNCA dormir
var cylinder = new Cylinder(Radius, Height);
var shapeIndex = _simulation.Shapes.Add(cylinder);
// 1. Creamos el cuerpo con una inercia por defecto.
var defaultInertia = cylinder.ComputeInertia(_mass);
var activityDescription = new BodyActivityDescription(-1f);
var bodyDescription = BodyDescription.CreateDynamic(
new RigidPose(position),
new BodyVelocity(),
inertia, // Inercia estándar de esfera
new CollidableDescription(shapeIndex, 0.001f),
defaultInertia,
new CollidableDescription(shapeIndex, 0),
activityDescription
);
BodyHandle = _simulation.Bodies.Add(bodyDescription);
_bodyCreated = true; // Marcar que hemos creado un cuerpo
_bodyCreated = true;
// 2. Inmediatamente después de crearlo, aplicamos la inercia personalizada.
// Esto asegura que SetLocalInertia se llame para todos los objetos nuevos.
var inertia = cylinder.ComputeInertia(_mass);
var inverseInertia = inertia.InverseInertiaTensor;
inverseInertia.XX = 0f;
inverseInertia.YY = 0f;
inertia.InverseInertiaTensor = inverseInertia;
_simulation.Bodies.SetLocalInertia(BodyHandle, inertia);
// ✅ NUEVO: Almacenar la masa inversa base después de crear el cuerpo
// Esto nos sirve como referencia para la lógica de masa dinámica.
@ -165,8 +175,8 @@ namespace CtrEditor.Simulacion
// ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var sphere = new Sphere(Radius);
var shapeIndex = _simulation.Shapes.Add(sphere);
var cylinder = new Cylinder(Radius, Height);
var shapeIndex = _simulation.Shapes.Add(cylinder);
ChangeBodyShape(shapeIndex);
}
}
@ -178,8 +188,8 @@ namespace CtrEditor.Simulacion
// ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var sphere = new Sphere(Radius);
var shapeIndex = _simulation.Shapes.Add(sphere);
var cylinder = new Cylinder(Radius, Height);
var shapeIndex = _simulation.Shapes.Add(cylinder);
ChangeBodyShape(shapeIndex);
}
}