Eliminacion de LinearAxisMotor por solo velocity y fricciones normales en los cuerpos

This commit is contained in:
Miguel 2025-07-05 15:24:54 +02:00
parent 3eee0e3d9b
commit de0e3ce5f0
5 changed files with 263 additions and 705 deletions

View File

@ -0,0 +1,10 @@
### 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

@ -114,10 +114,11 @@ namespace CtrEditor.Simulacion
// ✅ DESCARTES como sensores puros // ✅ DESCARTES como sensores puros
if (descarteA != null || descarteB != null) if (descarteA != null || descarteB != null)
{ {
var descarte = descarteA ?? descarteB;
if (botella != null) if (botella != null)
{ {
_simulationManager.RegisterDescarteContact(descarte, botella); // ✅ CORREGIDO: Marcar la botella para eliminar directamente, en lugar de registrar el contacto.
// Esto es más directo y evita el error de compilación.
botella.Descartar = true;
} }
return false; // NO generar contacto físico return false; // NO generar contacto físico
} }
@ -128,48 +129,41 @@ namespace CtrEditor.Simulacion
var curveA = GetCurveFromCollidable(pair.A); var curveA = GetCurveFromCollidable(pair.A);
var curveB = GetCurveFromCollidable(pair.B); var curveB = GetCurveFromCollidable(pair.B);
// ✅ CONTACTO BOTELLA-TRANSPORTE: Crear o actualizar motor inmediatamente // ✅ CONTACTO BOTELLA-TRANSPORTE: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA
if (botella != null && (transportA != null || transportB != null)) if (botella != null && (transportA != null || transportB != null))
{ {
var transport = transportA ?? transportB; // La velocidad del cuerpo cinemático del transporte se establece directamente
// en la clase simTransporte. El motor de físicas se encarga del resto.
// ✅ CREAR O ACTUALIZAR MOTOR DINÁMICO INMEDIATAMENTE // ✅ Fricción ajustada para un arrastre firme pero no excesivo.
transport.UpdateCachedProperties(); pairMaterial.FrictionCoefficient = 1.5f;
var direction = transport.DirectionVector; pairMaterial.MaximumRecoveryVelocity = 2.0f;
var speed = transport.Speed; pairMaterial.SpringSettings = new SpringSettings(30, 1);
botella.CreateOrUpdateMotor(transport, direction, speed);
//Fricción alta para transportes
pairMaterial.FrictionCoefficient = 0.01f;
pairMaterial.MaximumRecoveryVelocity = 1f;
pairMaterial.SpringSettings = new SpringSettings(80, 6);
} }
// ✅ CONTACTO BOTELLA-CURVA: Crear o actualizar motor inmediatamente // ✅ CONTACTO BOTELLA-CURVA: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA
else if (botella != null && (curveA != null || curveB != null)) else if (botella != null && (curveA != null || curveB != null))
{ {
var curve = curveA ?? curveB; // El motor de físicas usará la velocidad angular del cuerpo cinemático de la curva
// para calcular las fuerzas de fricción y arrastrar la botella.
// ✅ CREAR O ACTUALIZAR MOTOR DINÁMICO INMEDIATAMENTE // ✅ Fricción ajustada para un arrastre firme pero no excesivo.
var direction = _simulationManager.CalculateCurveDirectionFromBottlePosition(curve, botella); pairMaterial.FrictionCoefficient = 1.5f;
botella.CreateOrUpdateMotor(curve, direction, curve.Speed); pairMaterial.MaximumRecoveryVelocity = 2.0f;
pairMaterial.SpringSettings = new SpringSettings(30, 1);
// Fricción alta para curvas
pairMaterial.FrictionCoefficient = 0.01f;
pairMaterial.MaximumRecoveryVelocity = 1f;
pairMaterial.SpringSettings = new SpringSettings(80, 6);
} }
// ✅ CONTACTO BOTELLA-GUÍA: Configuración específica de fricción // ✅ CONTACTO BOTELLA-GUÍA: Configuración específica de fricción
else if (botella != null && (GetGuiaFromCollidable(pair.A) != null || GetGuiaFromCollidable(pair.B) != null)) else if (botella != null && (GetGuiaFromCollidable(pair.A) != null || GetGuiaFromCollidable(pair.B) != null))
{ {
// Configuración específica para guías usando propiedades configurables // Fricción baja para las guías, deben deslizar, no arrastrar.
pairMaterial.FrictionCoefficient = 0.01f; pairMaterial.FrictionCoefficient = 0.1f;
pairMaterial.MaximumRecoveryVelocity = 1f; pairMaterial.MaximumRecoveryVelocity = 1f;
pairMaterial.SpringSettings = new SpringSettings(80, 6); pairMaterial.SpringSettings = new SpringSettings(80, 6);
} }
// Ajustes básicos para otras botellas // Ajustes básicos para otras botellas
else if (botella != null) else if (botella != null)
{ {
pairMaterial.FrictionCoefficient = 0.01f; // Fricción moderada para colisiones entre botellas.
pairMaterial.FrictionCoefficient = 0.3f;
pairMaterial.MaximumRecoveryVelocity = 1f; pairMaterial.MaximumRecoveryVelocity = 1f;
pairMaterial.SpringSettings = new SpringSettings(80, 6); pairMaterial.SpringSettings = new SpringSettings(80, 6);
} }
@ -305,30 +299,54 @@ namespace CtrEditor.Simulacion
{ {
} }
// ✅ REESCRITO COMPLETAMENTE: para corregir errores de compilación y aplicar la lógica de amortiguamiento de forma segura.
public void IntegrateVelocity(Vector<int> bodyIndices, Vector3Wide position, QuaternionWide orientation, BodyInertiaWide localInertia, Vector<int> integrationMask, int workerIndex, Vector<float> dt, ref BodyVelocityWide velocity) public void IntegrateVelocity(Vector<int> bodyIndices, Vector3Wide position, QuaternionWide orientation, BodyInertiaWide localInertia, Vector<int> integrationMask, int workerIndex, Vector<float> dt, ref BodyVelocityWide velocity)
{ {
// Aplicar gravedad // 1. APLICAR GRAVEDAD Y AMORTIGUAMIENTO ESTÁNDAR (VECTORIZADO)
var gravityWide = Vector3Wide.Broadcast(Gravity); var gravityWide = Vector3Wide.Broadcast(Gravity);
velocity.Linear += gravityWide * dt; velocity.Linear.X += gravityWide.X * dt;
velocity.Linear.Y += gravityWide.Y * dt;
velocity.Linear.Z += gravityWide.Z * dt;
// ✅ ELIMINADO COMPLETAMENTE - ya no se necesita lógica especial de frenado var linearDampingFactor = new Vector<float>(MathF.Pow(MathHelper.Clamp(1 - LinearDamping, 0, 1), dt[0]));
// El sistema LinearAxisMotor maneja automáticamente todas las fuerzas var angularDampingFactor = new Vector<float>(MathF.Pow(MathHelper.Clamp(1 - AngularDamping, 0, 1), dt[0]));
// Aplicar amortiguamiento lineal y angular para simular resistencia del aire velocity.Linear *= linearDampingFactor;
// Esto es crucial para que los cilindros se detengan de forma realista velocity.Angular *= angularDampingFactor;
var linearDampingWide = Vector<float>.One * LinearDamping;
var angularDampingWide = Vector<float>.One * AngularDamping;
velocity.Linear *= linearDampingWide; // 2. LÓGICA PERSONALIZADA PARA BOTELLAS (ESCALAR)
velocity.Angular *= angularDampingWide; // ¡Esto es clave para detener rotaciones infinitas! const float bottleExtraZAngularDamping = 0.8f;
var bottleZAngularDampingFactor = MathF.Pow(MathHelper.Clamp(1 - bottleExtraZAngularDamping, 0, 1), dt[0]);
// Recorremos cada "carril" del lote de simulación.
for (int i = 0; i < Vector<int>.Count; ++i)
{
if (integrationMask[i] != 0)
{
var bodyIndex = bodyIndices[i];
// Asegurarse de que el índice del cuerpo es válido para el conjunto activo.
if (bodyIndex < _simulationManager.simulation.Bodies.ActiveSet.Count)
{
var handle = _simulationManager.simulation.Bodies.ActiveSet.IndexToHandle[bodyIndex];
if (_simulationManager.CollidableData.TryGetValue(handle.Value, out simBase baseObj) && baseObj is simBotella)
{
// B. DAMPING ANGULAR EXTRA EN EJE Z
// Para modificar un solo carril de un Vector<T>, la forma más segura es extraer
// los valores a un array, modificar el índice y crear un nuevo Vector<T>.
var zVel = new float[Vector<float>.Count];
velocity.Angular.Z.CopyTo(zVel, 0);
zVel[i] *= bottleZAngularDampingFactor;
velocity.Angular.Z = new Vector<float>(zVel);
}
}
}
}
} }
} }
public class SimulationManagerBEPU public class SimulationManagerBEPU : IDisposable
{ {
public Simulation simulation; public Simulation simulation;
public List<simBase> Cuerpos; public List<simBase> Cuerpos;
@ -368,6 +386,10 @@ namespace CtrEditor.Simulacion
private object _contactsLock = new object(); private object _contactsLock = new object();
// ✅ NUEVO: Diccionario para mapear BodyHandle a simBase para acceso O(1).
public Dictionary<int, simBase> CollidableData = new Dictionary<int, simBase>();
public float GlobalTime { get; private set; }
/// <summary> /// <summary>
/// Obtiene el objeto simBase correspondiente a un BodyHandle /// Obtiene el objeto simBase correspondiente a un BodyHandle
/// </summary> /// </summary>
@ -386,37 +408,8 @@ namespace CtrEditor.Simulacion
return simBase as simCurve; return simBase as simCurve;
} }
/// <summary> // ✅ ELIMINADO: GetBottlesConnectedToElement ya no es necesario.
/// ✅ NUEVO: Obtiene todas las botellas que están conectadas a un elemento específico // La conexión ahora es implícita a través de la física de contacto.
/// </summary>
/// <param name="element">Elemento (simTransporte o simCurve) del cual obtener las botellas conectadas</param>
/// <returns>Lista de botellas conectadas al elemento</returns>
public List<simBotella> GetBottlesConnectedToElement(simBase element)
{
var connectedBottles = new List<simBotella>();
try
{
if (element == null) return connectedBottles;
// ✅ BUSCAR BOTELLAS QUE TENGAN ESTE ELEMENTO COMO TARGET
foreach (var bottle in Cuerpos.OfType<simBotella>())
{
if (bottle != null && bottle.HasMotor && bottle.CurrentMotorTarget == element)
{
connectedBottles.Add(bottle);
}
}
System.Diagnostics.Debug.WriteLine($"[GetBottlesConnectedToElement] Encontradas {connectedBottles.Count} botellas conectadas a {element.GetType().Name}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[GetBottlesConnectedToElement] ❌ ERROR: {ex.Message}");
}
return connectedBottles;
}
// ✅ NUEVOS - gestión automática de clasificación // ✅ NUEVOS - gestión automática de clasificación
private void RegisterObjectHandle(simBase obj) private void RegisterObjectHandle(simBase obj)
@ -543,7 +536,55 @@ namespace CtrEditor.Simulacion
simulation = Simulation.Create(bufferPool, narrowPhaseCallbacks, poseIntegratorCallbacks, solveDescription); simulation = Simulation.Create(bufferPool, narrowPhaseCallbacks, poseIntegratorCallbacks, solveDescription);
// ✅ NUEVO: Suscribirse a los eventos del solver para controlar la velocidad de los cuerpos cinemáticos.
simulation.Solver.SubstepStarted += OnSubstepStarted;
simulation.Solver.SubstepEnded += OnSubstepEnded;
}
/// <summary>
/// ✅ NUEVO: Se ejecuta al inicio de cada sub-paso de la simulación.
/// Restaura las velocidades de los cuerpos cinemáticos para que el solver pueda usarlas para calcular la fricción.
/// </summary>
private void OnSubstepStarted(int substepIndex)
{
foreach (var transport in Cuerpos.OfType<simTransporte>())
{
transport.ActualizarVelocidadCinematica();
}
foreach (var curve in Cuerpos.OfType<simCurve>())
{
curve.ActualizarVelocidadCinematica();
}
}
/// <summary>
/// ✅ NUEVO: Se ejecuta al final de cada sub-paso de la simulación, pero antes de la integración de la pose.
/// Pone a cero las velocidades de los cuerpos cinemáticos para evitar que el PoseIntegrator mueva su posición.
/// </summary>
private void OnSubstepEnded(int substepIndex)
{
foreach (var transport in Cuerpos.OfType<simTransporte>())
{
if (transport.BodyHandle.Value >= 0 && simulation.Bodies.BodyExists(transport.BodyHandle))
{
var body = simulation.Bodies.GetBodyReference(transport.BodyHandle);
if (body.Kinematic)
{
body.Velocity.Linear = Vector3.Zero;
}
}
}
foreach (var curve in Cuerpos.OfType<simCurve>())
{
if (curve.BodyHandle.Value >= 0 && simulation.Bodies.BodyExists(curve.BodyHandle))
{
var body = simulation.Bodies.GetBodyReference(curve.BodyHandle);
if (body.Kinematic)
{
body.Velocity.Angular = Vector3.Zero;
}
}
}
} }
public void Clear() public void Clear()
@ -614,6 +655,10 @@ namespace CtrEditor.Simulacion
// Limpiar visualización 3D // Limpiar visualización 3D
Visualization3DManager?.Clear(); Visualization3DManager?.Clear();
// ✅ NUEVO: Limpiar también el nuevo diccionario.
CollidableData.Clear();
GlobalTime = 0f;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -625,7 +670,7 @@ namespace CtrEditor.Simulacion
{ {
try try
{ {
// ✅ INICIALIZAR PROPIEDADES CACHEADAS // ✅ INICIALIZAR PROPIEDADES CACHEADAS Y VELOCIDADES CINEMÁTICAS
foreach (var transport in Cuerpos.OfType<simTransporte>()) foreach (var transport in Cuerpos.OfType<simTransporte>())
{ {
transport.UpdateCachedProperties(); transport.UpdateCachedProperties();
@ -633,8 +678,7 @@ namespace CtrEditor.Simulacion
foreach (var curve in Cuerpos.OfType<simCurve>()) foreach (var curve in Cuerpos.OfType<simCurve>())
{ {
// ✅ CORREGIDO: Usar método público para actualizar velocidad curve.SetSpeed(curve.Speed);
curve.SetSpeed(curve.Speed); // ✅ SIMPLIFICADO: Reinicializar velocidad
} }
stopwatch.Start(); stopwatch.Start();
@ -727,13 +771,6 @@ namespace CtrEditor.Simulacion
} }
} }
// ✅ NUEVO: Detener motores de botellas que no están en contacto con elementos
// Esto se ejecuta cada 10 frames para eficiencia
if (_frameCount % 10 == 0)
{
//StopMotorsForBottlesNotInContact();
}
// ✅ CONSERVAR - sistemas que funcionan bien // ✅ CONSERVAR - sistemas que funcionan bien
ProcessBarreraContacts(); ProcessBarreraContacts();
ProcessDescarteContacts(); ProcessDescarteContacts();
@ -960,223 +997,6 @@ namespace CtrEditor.Simulacion
return new BarreraData(false, false); return new BarreraData(false, false);
} }
/// <summary>
/// ✅ NUEVO: Detiene motores de botellas que no están en contacto con elementos
/// </summary>
private void StopMotorsForBottlesNotInContact()
{
try
{
var allBottles = Cuerpos.OfType<simBotella>().ToList();
foreach (var bottle in allBottles)
{
if (bottle != null && bottle.HasMotor && bottle.IsOnElement)
{
// ✅ VERIFICAR SI LA BOTELLA ESTÁ REALMENTE EN CONTACTO CON ALGÚN ELEMENTO
bool isInContact = false;
// Verificar contacto con transportes
foreach (var transport in Cuerpos.OfType<simTransporte>())
{
if (IsBottleInContactWithTransport(bottle, transport))
{
isInContact = true;
break;
}
}
// Verificar contacto con curvas
if (!isInContact)
{
foreach (var curve in Cuerpos.OfType<simCurve>())
{
if (IsBottleInContactWithCurve(bottle, curve))
{
isInContact = true;
break;
}
}
}
// ✅ DETENER MOTOR SI NO ESTÁ EN CONTACTO
if (!isInContact)
{
bottle.StopMotor();
System.Diagnostics.Debug.WriteLine($"[StopMotorsForBottlesNotInContact] ⏹️ Detenido: {bottle.BodyHandle}");
}
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[StopMotorsForBottlesNotInContact] ❌ ERROR: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Verifica si una botella está en contacto con un transporte
/// </summary>
private bool IsBottleInContactWithTransport(simBotella bottle, simTransporte transport)
{
try
{
var bottlePos = bottle.GetPosition();
var transportPos = transport.GetPosition();
var distance = Vector2.Distance(
new Vector2(bottlePos.X, bottlePos.Y),
new Vector2(transportPos.X, transportPos.Y)
);
// Verificar si está dentro del área del transporte + tolerancia
var maxDistance = Math.Max(transport.Width, transport.Height) / 2f + bottle.Radius + 0.1f;
return distance <= maxDistance;
}
catch
{
return false;
}
}
/// <summary>
/// ✅ NUEVO: Verifica si una botella está en contacto con una curva
/// </summary>
private bool IsBottleInContactWithCurve(simBotella bottle, simCurve curve)
{
try
{
var bottlePos = bottle.GetPosition();
var curveCenter = curve.CurveCenter; // ✅ NUEVO: Usar centro real
var distance = Vector2.Distance(
new Vector2(bottlePos.X, bottlePos.Y),
new Vector2(curveCenter.X, curveCenter.Y)
);
// Verificar si está dentro del área de la curva + tolerancia
var maxDistance = curve.OuterRadius + bottle.Radius + 0.1f;
return distance <= maxDistance;
}
catch
{
return false;
}
}
/// <summary>
/// ✅ NUEVO: Calcula la dirección tangencial específica basada en la posición de la botella
/// </summary>
public Vector3 CalculateCurveDirectionFromBottlePosition(simCurve curve, simBotella bottle)
{
try
{
// ✅ NUEVO: Usar el centro real almacenado de la curva
var curveCenter = curve.CurveCenter;
var bottlePosition = bottle.GetPosition();
// Calcular el vector desde el centro de la curva hasta la botella (en el plano XY)
var radiusVector = new Vector3(bottlePosition.X - curveCenter.X, bottlePosition.Y - curveCenter.Y, 0f);
var radius = radiusVector.Length();
if (radius < 0.001f)
{
System.Diagnostics.Debug.WriteLine($"[CalculateCurveDirectionFromBottlePosition] ⚠️ Botella muy cerca del centro de la curva");
return Vector3.UnitX; // Fallback
}
// Normalizar el vector radial
var normalizedRadius = radiusVector / radius;
// Calcular la dirección tangencial (perpendicular al radio)
// En coordenadas 2D: si r = (x, y), entonces t = (-y, x) es tangente
var tangentDirection = new Vector3(-normalizedRadius.Y, normalizedRadius.X, 0f);
// Verificar que la dirección tangencial apunte en el sentido correcto según la velocidad de la curva
if (curve.Speed < 0)
{
//tangentDirection = -tangentDirection;
}
//System.Diagnostics.Debug.WriteLine($"[CalculateCurveDirectionFromBottlePosition] 📐 Dirección calculada:");
//System.Diagnostics.Debug.WriteLine($" Centro curva: {curveCenter}");
//System.Diagnostics.Debug.WriteLine($" Posición botella: {bottlePosition}");
//System.Diagnostics.Debug.WriteLine($" Radio: {radius:F3}");
//System.Diagnostics.Debug.WriteLine($" Vector radial: {normalizedRadius}");
//System.Diagnostics.Debug.WriteLine($" Dirección tangencial: {tangentDirection} (Longitud: {tangentDirection.Length():F3})");
return -tangentDirection;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[CalculateCurveDirectionFromBottlePosition] ❌ ERROR: {ex.Message}");
return Vector3.UnitX; // Fallback
}
}
public void Dispose()
{
Clear();
simulation?.Dispose();
bufferPool?.Clear();
}
/// <summary>
/// Registra un contacto entre una barrera y una botella para detección de paso
/// </summary>
/// <param name="barrera">Barrera que detecta el paso</param>
/// <param name="botella">Botella que está pasando</param>
internal void RegisterBarreraContact(simBarrera barrera, simBotella botella)
{
if (barrera != null && botella != null)
{
lock (_contactsLock)
{
if (!_barreraContacts.ContainsKey(barrera))
{
_barreraContacts[barrera] = new List<simBotella>();
}
if (!_barreraContacts[barrera].Contains(botella))
{
_barreraContacts[barrera].Add(botella);
}
}
}
}
/// <summary>
/// Registra un contacto entre un descarte y una botella para marcar eliminación
/// </summary>
/// <param name="descarte">Descarte que detecta la botella</param>
/// <param name="botella">Botella que debe ser eliminada</param>
public void RegisterDescarteContact(simDescarte descarte, simBotella botella)
{
if (descarte != null && botella != null)
{
lock (_contactsLock)
{
// Marcar la botella para eliminación
_botellasParaEliminar.Add(botella);
botella.Descartar = true;
// También registrar el contacto para la lista del descarte
if (!_descarteContacts.ContainsKey(descarte))
{
_descarteContacts[descarte] = new List<simBotella>();
}
if (!_descarteContacts[descarte].Contains(botella))
{
_descarteContacts[descarte].Add(botella);
}
}
}
}
/// <summary> /// <summary>
/// ✅ NUEVO: Procesa todas las barreras usando RayCast de BEPU /// ✅ NUEVO: Procesa todas las barreras usando RayCast de BEPU
/// Elimina la dependencia de contactos físicos y la necesidad de mantener botellas despiertas /// Elimina la dependencia de contactos físicos y la necesidad de mantener botellas despiertas
@ -1258,12 +1078,6 @@ namespace CtrEditor.Simulacion
// Eliminar botellas marcadas para eliminación (después de procesamiento) // Eliminar botellas marcadas para eliminación (después de procesamiento)
RemoveMarkedBottles(); RemoveMarkedBottles();
// Limpiar contactos ya que no los usamos más
lock (_contactsLock)
{
_descarteContacts.Clear();
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1361,6 +1175,15 @@ namespace CtrEditor.Simulacion
List<simBotella> botellasAEliminar; List<simBotella> botellasAEliminar;
lock (_contactsLock) lock (_contactsLock)
{ {
// ✅ NUEVO: Añadir botellas marcadas como "Descartar" desde los callbacks
foreach (var botella in Cuerpos.OfType<simBotella>())
{
if (botella.Descartar)
{
_botellasParaEliminar.Add(botella);
}
}
botellasAEliminar = new List<simBotella>(_botellasParaEliminar); botellasAEliminar = new List<simBotella>(_botellasParaEliminar);
_botellasParaEliminar.Clear(); _botellasParaEliminar.Clear();
} }
@ -1459,5 +1282,36 @@ namespace CtrEditor.Simulacion
return lineStart + lineDirection * projectionLength; return lineStart + lineDirection * projectionLength;
} }
public void Dispose()
{
Clear();
simulation?.Dispose();
bufferPool?.Clear();
}
/// <summary>
/// Registra un contacto entre una barrera y una botella para detección de paso
/// </summary>
/// <param name="barrera">Barrera que detecta el paso</param>
/// <param name="botella">Botella que está pasando</param>
internal void RegisterBarreraContact(simBarrera barrera, simBotella botella)
{
if (barrera != null && botella != null)
{
lock (_contactsLock)
{
if (!_barreraContacts.ContainsKey(barrera))
{
_barreraContacts[barrera] = new List<simBotella>();
}
if (!_barreraContacts[barrera].Contains(botella))
{
_barreraContacts[barrera].Add(botella);
}
}
}
}
} }
} }

View File

@ -8,6 +8,8 @@ using System.Numerics;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using DocumentFormat.OpenXml.Spreadsheet; using DocumentFormat.OpenXml.Spreadsheet;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;
namespace CtrEditor.Simulacion namespace CtrEditor.Simulacion
{ {
@ -29,17 +31,9 @@ namespace CtrEditor.Simulacion
public bool isOnBrakeTransport; // Nueva propiedad para marcar si está en un transporte con freno public bool isOnBrakeTransport; // Nueva propiedad para marcar si está en un transporte con freno
public simTransporte CurrentBrakeTransport { get; set; } // ✅ NUEVA: Referencia al transporte de freno actual public simTransporte CurrentBrakeTransport { get; set; } // ✅ NUEVA: Referencia al transporte de freno actual
// ✅ NUEVO SISTEMA SIMPLIFICADO: Motor dinámico que se crea solo cuando es necesario // ✅ NUEVO: Propiedades para la física personalizada
public ConstraintHandle CurrentMotor { get; private set; } = default; public float BaseInverseMass { get; private set; }
public ConstraintHandle CurrentDistanceLimit { get; private set; } = default; // ✅ NUEVO: Para curvas public Vector3 InitialPosition3D { get; private set; }
public simBase CurrentMotorTarget { get; private set; } = null; // Transporte o curva actual
public bool _hasMotor = false; // ✅ BANDERA PÚBLICA para acceso desde callbacks
public bool HasMotor => _hasMotor;
// ✅ NUEVAS PROPIEDADES para el motor dinámico
public Vector3 CurrentDirection { get; private set; } = Vector3.UnitX; // Dirección por defecto (1,0,0)
public float CurrentSpeed { get; private set; } = 0f; // Velocidad por defecto
public bool IsOnElement { get; private set; } = false; // Si está sobre un elemento activo
private List<Action> _deferredActions; private List<Action> _deferredActions;
@ -56,8 +50,6 @@ namespace CtrEditor.Simulacion
// ✅ USAR COORDINATECONVERTER para conversión centralizada // ✅ USAR COORDINATECONVERTER para conversión centralizada
var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), Radius + zPos_Transporte + zAltura_Transporte); var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), Radius + zPos_Transporte + zAltura_Transporte);
Create(position3D); Create(position3D);
// ✅ ELIMINADO: No crear motor permanente - se creará cuando sea necesario
} }
public float CenterX public float CenterX
@ -154,303 +146,17 @@ namespace CtrEditor.Simulacion
BodyHandle = _simulation.Bodies.Add(bodyDescription); BodyHandle = _simulation.Bodies.Add(bodyDescription);
_bodyCreated = true; // Marcar que hemos creado un cuerpo _bodyCreated = true; // Marcar que hemos creado un cuerpo
}
/// <summary> // ✅ NUEVO: Almacenar la masa inversa base después de crear el cuerpo
/// ✅ NUEVO: Crea o actualiza el motor conectándolo al transporte/curva actual // Esto nos sirve como referencia para la lógica de masa dinámica.
/// </summary> var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
public void CreateOrUpdateMotor(simBase target, Vector3 direction, float speed) this.BaseInverseMass = bodyReference.LocalInertia.InverseMass;
{
try
{
// ✅ VALIDAR DIRECCIÓN
if (direction.Length() < 0.001f)
{
return;
}
// ✅ VALIDAR QUE EL TARGET Y LA SIMULACIÓN EXISTAN // ✅ CORREGIDO: Usar el diccionario del SimulationManager y la clave .Value del handle.
if (target == null || _simulation == null || !_simulation.Bodies.BodyExists(BodyHandle)) if (_simulationManager != null)
{ _simulationManager.CollidableData[BodyHandle.Value] = this;
return;
} }
// ✅ VERIFICAR SI NECESITAMOS CREAR O ACTUALIZAR EL MOTOR
bool needsNewMotor = false;
if (!HasMotor)
{
// ✅ PRIMERA VEZ: Crear motor nuevo
needsNewMotor = true;
//System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] 🆕 Creando motor nuevo para {target?.GetType().Name}");
}
else if (CurrentMotorTarget != target)
{
// ✅ CAMBIO DE OBJETO: Eliminar motor anterior y crear uno nuevo
RemoveCurrentMotor();
needsNewMotor = true;
//System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] 🔄 Cambiando motor de {CurrentMotorTarget?.GetType().Name} a {target?.GetType().Name}");
}
else
{
// ✅ MISMO OBJETO: Solo actualizar velocidad
UpdateMotorSpeed(direction, speed / simBase.SpeedConversionFactor);
return;
}
// ✅ CREAR NUEVO MOTOR SI ES NECESARIO
if (needsNewMotor && target != null)
{
CreateMotorForTarget(target, direction, speed / simBase.SpeedConversionFactor);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] ❌ ERROR: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Crea un motor específico para un transporte o curva
/// </summary>
private void CreateMotorForTarget(simBase target, Vector3 direction, float speed)
{
try
{
if (_simulation == null || _simulation.Solver == null || !_simulation.Bodies.BodyExists(BodyHandle) || target == null)
{
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ Simulación, solver, body o target no disponible");
return;
}
// ✅ VERIFICAR QUE EL TARGET TENGA UN BODY VÁLIDO
if (!_simulation.Bodies.BodyExists(target.BodyHandle))
{
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ Target body no existe: {target.BodyHandle}");
return;
}
// ✅ VERIFICAR QUE NO TENGA YA UN MOTOR VÁLIDO
if (HasMotor && CurrentMotor.Value != 0)
{
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ⚠️ Ya existe un motor válido: {CurrentMotor}");
return;
}
// ✅ NORMALIZAR LA DIRECCIÓN TANGENCIAL
var tangentDir = Vector3.Normalize(direction);
// ✅ CREAR MOTOR CONECTADO AL TARGET
var motor = new LinearAxisMotor()
{
LocalOffsetA = Vector3.Zero, // Target
LocalOffsetB = Vector3.Zero, // Botella
LocalAxis = tangentDir, // ✅ CORREGIDO: Usar la dirección tangencial calculada
TargetVelocity = speed, // ✅ CORREGIDO: Usar la velocidad directamente
Settings = new MotorSettings(Math.Max(_mass * 20f, 8f), 0f)
};
// ✅ CONECTAR BOTELLA CON EL TARGET (transporte o curva)
CurrentMotor = _simulation.Solver.Add(target.BodyHandle, BodyHandle, motor);
CurrentMotorTarget = target;
_hasMotor = true; // ✅ ESTABLECER BANDERA
//if (target is simCurve curva) {
// // Calcular el vector desde el centro de la curva hasta la botella (en el plano XY)
// var curveCenter = curva.CurveCenter;
// var bottlePosition = GetPosition();
// var radiusVector = new Vector3(bottlePosition.X - curveCenter.X, bottlePosition.Y - curveCenter.Y, 0f);
// var radius = radiusVector.Length();
// if (radius > 1e-3f)
// {
// // Calcular offsets locales
// var localOffsetA = curveCenter; // Desde el centro de la curva hasta el punto de anclaje
// var localOffsetB = Vector3.Zero; // ✅ SIMPLIFICADO: Conectar al centro de la botella
// var distanceLimit = new DistanceLimit()
// {
// LocalOffsetA = Vector3.Zero,
// LocalOffsetB = Vector3.Zero,
// MinimumDistance = radius- 4*Radius, // Distancia mínima = radio actual
// MaximumDistance = radius+ 4*Radius, // Distancia máxima = radio actual (mantener distancia fija)
// SpringSettings = new SpringSettings(30f, 0f)
// };
// //CurrentDistanceLimit = _simulation.Solver.Add(target.BodyHandle, BodyHandle, distanceLimit);
// //System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget-Curve] 📏 DistanceLimit creado:");
// //System.Diagnostics.Debug.WriteLine($" Radio actual: {radius:F3}");
// //System.Diagnostics.Debug.WriteLine($" Punto de anclaje: {anchorPoint}");
// //System.Diagnostics.Debug.WriteLine($" LocalOffsetA (curva): {localOffsetA}");
// //System.Diagnostics.Debug.WriteLine($" LocalOffsetB (botella): {localOffsetB} (centro)");
// //System.Diagnostics.Debug.WriteLine($" Distancia objetivo: {radius:F3}");
// //System.Diagnostics.Debug.WriteLine($" DistanceLimit Handle: {CurrentDistanceLimit}");
// }
//}
// ✅ ACTUALIZAR PROPIEDADES INTERNAS
CurrentDirection = direction;
CurrentSpeed = speed;
IsOnElement = Math.Abs(speed) > 0.001f;
//System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ✅ Motor creado:");
//System.Diagnostics.Debug.WriteLine($" Botella: {BodyHandle}");
//System.Diagnostics.Debug.WriteLine($" Target: {target.BodyHandle} ({target.GetType().Name})");
//System.Diagnostics.Debug.WriteLine($" Motor Handle: {CurrentMotor} (Value: {CurrentMotor.Value})");
//System.Diagnostics.Debug.WriteLine($" Dirección: {direction}");
//System.Diagnostics.Debug.WriteLine($" Velocidad efectiva: {effectiveSpeed:F3}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ ERROR: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Actualiza solo la velocidad del motor existente
/// </summary>
private void UpdateMotorSpeed(Vector3 direction, float speed)
{
try
{
if (!HasMotor || _simulation == null)
{
return;
}
// ✅ NORMALIZAR LA DIRECCIÓN TANGENCIAL
var tangentDir = Vector3.Normalize(direction);
// ✅ OBTENER LA DESCRIPCIÓN ACTUAL DEL MOTOR
_simulation.Solver.GetDescription(CurrentMotor, out LinearAxisMotor motor);
// ✅ ACTUALIZAR DIRECCIÓN Y VELOCIDAD
motor.LocalAxis = tangentDir;
motor.TargetVelocity = speed;
// ✅ ACTUALIZAR EL MOTOR EN EL SOLVER
_simulation.Solver.ApplyDescription(CurrentMotor, motor);
// ✅ ACTUALIZAR PROPIEDADES INTERNAS
CurrentDirection = direction;
CurrentSpeed = speed;
IsOnElement = Math.Abs(speed) > 0.001f;
//System.Diagnostics.Debug.WriteLine($"[UpdateMotorSpeed] 🔄 Velocidad actualizada: {effectiveSpeed:F3}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[UpdateMotorSpeed] ❌ ERROR: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Elimina el motor actual
/// </summary>
public void RemoveCurrentMotor()
{
try
{
if (HasMotor && _simulation != null && _simulation.Solver != null)
{
// ✅ VERIFICAR QUE EL MOTOR EXISTE ANTES DE ELIMINARLO
if (CurrentMotor.Value != 0)
{
try
{
_simulation.Solver.Remove(CurrentMotor);
//System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] 🗑️ Motor eliminado: {CurrentMotor}");
}
catch (Exception removeEx)
{
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ⚠️ Error eliminando motor {CurrentMotor}: {removeEx.Message}");
// Continuar con la limpieza incluso si falla la eliminación
}
}
else
{
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ⚠️ Motor ya eliminado o inválido: {CurrentMotor}");
}
}
// ✅ NUEVO: Eliminar DistanceLimit si existe
if (CurrentDistanceLimit.Value != 0)
{
try
{
_simulation.Solver.Remove(CurrentDistanceLimit);
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] 🗑️ DistanceLimit eliminado: {CurrentDistanceLimit}");
}
catch (Exception removeEx)
{
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ⚠️ Error eliminando DistanceLimit {CurrentDistanceLimit}: {removeEx.Message}");
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ❌ ERROR: {ex.Message}");
}
finally
{
// ✅ LIMPIAR REFERENCIAS SIEMPRE
CurrentMotor = default;
CurrentDistanceLimit = default; // ✅ NUEVO: Limpiar DistanceLimit
CurrentMotorTarget = null;
_hasMotor = false; // ✅ LIMPIAR BANDERA
CurrentDirection = Vector3.UnitX;
CurrentSpeed = 0f;
IsOnElement = false;
}
}
/// <summary>
/// ✅ NUEVO: Detiene el motor (velocidad 0) pero mantiene la conexión
/// </summary>
public void StopMotor()
{
UpdateMotorSpeed(CurrentDirection, 0f);
}
/// <summary>
/// ✅ NUEVO: Limpia las restricciones asociadas a un elemento específico
/// Solo limpia los datos internos de simBotella, no elimina las restricciones de BEPU
/// </summary>
/// <param name="target">Elemento (simTransporte o simCurve) del cual limpiar las restricciones</param>
public void CleanRestrictions(simBase target)
{
try
{
// ✅ VERIFICAR SI EL TARGET COINCIDE CON EL ACTUAL
if (CurrentMotorTarget == target)
{
// ✅ LIMPIAR SOLO LOS DATOS INTERNOS (no eliminar restricciones de BEPU)
CurrentMotorTarget = null;
_hasMotor = false; // ✅ CRÍTICO: Limpiar el flag del motor
CurrentDirection = Vector3.UnitX;
CurrentSpeed = 0f;
IsOnElement = false;
// ✅ NO LIMPIAR CurrentMotor ni CurrentDistanceLimit - BEPU los maneja automáticamente
System.Diagnostics.Debug.WriteLine($"[CleanRestrictions] ✅ Restricciones limpiadas para {target?.GetType().Name}");
}
else
{
System.Diagnostics.Debug.WriteLine($"[CleanRestrictions] ⚠️ Target no coincide: actual={CurrentMotorTarget?.GetType().Name}, solicitado={target?.GetType().Name}");
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[CleanRestrictions] ❌ ERROR: {ex.Message}");
}
}
public void SetDiameter(float diameter) public void SetDiameter(float diameter)
{ {
Radius = diameter / 2f; Radius = diameter / 2f;
@ -499,8 +205,8 @@ namespace CtrEditor.Simulacion
// Convertir a ángulo en Z solamente // Convertir a ángulo en Z solamente
var rotationZ = GetRotationZ(); var rotationZ = GetRotationZ();
// Crear nueva orientación solo con rotación en Z (botella siempre "de pie") // ✅ CORREGIDO: Calificar explícitamente para evitar ambigüedad de tipos.
var correctedOrientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, rotationZ); var correctedOrientation = System.Numerics.Quaternion.CreateFromAxisAngle(Vector3.UnitZ, rotationZ);
// Aplicar la orientación corregida // Aplicar la orientación corregida
bodyReference.Pose.Orientation = correctedOrientation; bodyReference.Pose.Orientation = correctedOrientation;

View File

@ -32,9 +32,6 @@ namespace CtrEditor.Simulacion
// ✅ SIMPLIFICADO: Propiedades esenciales únicamente // ✅ SIMPLIFICADO: Propiedades esenciales únicamente
public List<simBotella> BottlesOnCurve { get; private set; } = new List<simBotella>(); public List<simBotella> BottlesOnCurve { get; private set; } = new List<simBotella>();
// ✅ EVENTO para actualización de motores
public event Action<simCurve> OnSpeedChanged;
// ✅ NUEVO: Almacenar triángulos creados para acceso directo // ✅ NUEVO: Almacenar triángulos creados para acceso directo
private Triangle[] _storedTriangles; private Triangle[] _storedTriangles;
@ -66,11 +63,37 @@ namespace CtrEditor.Simulacion
Create(innerRadius, outerRadius, startAngle, endAngle, topLeft, 0); Create(innerRadius, outerRadius, startAngle, endAngle, topLeft, 0);
} }
// ✅ SIMPLIFICADO: Configurar velocidad angular para AngularAxisMotor // ✅ MODIFICADO: Actualizar la velocidad y luego la del cuerpo cinemático
public void SetSpeed(float speed) public void SetSpeed(float speed)
{ {
Speed = speed; // Velocidad angular directa (sin inversión) Speed = speed;
OnSpeedChanged?.Invoke(this); ActualizarVelocidadCinematica();
}
/// <summary>
/// ✅ NUEVO: Aplica la velocidad angular al cuerpo cinemático de BEPU.
/// </summary>
public void ActualizarVelocidadCinematica()
{
if (_simulation != null && this.BodyHandle.Value >= 0 && _simulation.Bodies.BodyExists(this.BodyHandle))
{
var body = _simulation.Bodies.GetBodyReference(BodyHandle);
float effectiveRadius = (_innerRadius + _outerRadius) / 2.0f;
if (effectiveRadius > 0.001f)
{
// La velocidad tangencial (Speed) se convierte a velocidad angular (ω = v / r)
// ✅ CORREGIDO: Convertir velocidad de m/min a m/s para la simulación
float angularSpeed = (Speed / SpeedConversionFactor) / effectiveRadius;
// La rotación es alrededor del eje Z en el sistema de coordenadas de BEPU
body.Velocity.Angular = new Vector3(0, 0, angularSpeed);
}
else
{
body.Velocity.Angular = Vector3.Zero;
}
}
} }
/// <summary> /// <summary>
@ -220,19 +243,8 @@ namespace CtrEditor.Simulacion
} }
} }
public new void RemoverBody() public new void RemoverBody()
{ {
// ✅ NUEVO: Limpiar restricciones de botellas conectadas antes de eliminar el cuerpo
if (_simulationManager != null)
{
var connectedBottles = _simulationManager.GetBottlesConnectedToElement(this);
foreach (var bottle in connectedBottles)
{
bottle.CleanRestrictions(this);
}
}
// ✅ NUEVO: Limpiar triángulos almacenados // ✅ NUEVO: Limpiar triángulos almacenados
_storedTriangles = null; _storedTriangles = null;
@ -242,7 +254,7 @@ namespace CtrEditor.Simulacion
public void Create(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 wpfTopLeft, float unused = 0) public void Create(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 wpfTopLeft, float unused = 0)
{ {
// ✅ CORRIGIDO: Como Aether - startAngle y endAngle definen directamente el sector // ✅ CORREGIDO: Como Aether - startAngle y endAngle definen directamente el sector
// No hay rotación separada del objeto // No hay rotación separada del objeto
// Actualizar parámetros internos // Actualizar parámetros internos
@ -253,53 +265,45 @@ namespace CtrEditor.Simulacion
_endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle); _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
// ✅ NUEVO: Actualizar el centro real de la curva // ✅ NUEVO: Actualizar el centro real de la curva
// Para curvas, el "tamaño" es el diámetro del radio exterior
var curveSize = outerRadius * 2f; var curveSize = outerRadius * 2f;
var zPosition = zPos_Curve; var zPosition = zPos_Curve;
// Calcular nueva posición del centro (sin rotación - el sector ya está definido por los ángulos)
_curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition); _curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition);
Create(_curveCenter); // Sin rotación adicional Create(_curveCenter); // Llamar a la sobrecarga privada
} }
private void Create(Vector3 position) private void Create(Vector3 position)
{ {
RemoverBody(); RemoverBody();
// ✅ SIMPLIFICADO: Crear superficie usando Triangle de BEPU directamente
var triangles = CreateSimpleArcTriangles(_innerRadius, _outerRadius, _startAngle, _endAngle); var triangles = CreateSimpleArcTriangles(_innerRadius, _outerRadius, _startAngle, _endAngle);
if (triangles.Length == 0)
// ✅ ALMACENAR triángulos para acceso directo {
System.Diagnostics.Debug.WriteLine($"[simCurve] No se crearon triángulos, no se creará el cuerpo.");
return;
}
_storedTriangles = triangles; _storedTriangles = triangles;
// ✅ SIMPLIFICADO: Crear un solo cuerpo con múltiples Triangle shapes usando Mesh // ✅ CORREGIDO: Convertir el array de triángulos a un Buffer<Triangle> de BEPU.
if (triangles.Length > 0) _simulation.BufferPool.Take(triangles.Length, out BepuUtilities.Memory.Buffer<Triangle> triangleBuffer);
{
// ✅ CREAR MESH CON LA API CORRECTA DE BEPU
var triangleBuffer = new BepuUtilities.Memory.Buffer<Triangle>(triangles.Length, _simulation.BufferPool);
for (int i = 0; i < triangles.Length; i++) for (int i = 0; i < triangles.Length; i++)
{ {
triangleBuffer[i] = triangles[i]; triangleBuffer[i] = triangles[i];
} }
var mesh = new BepuPhysics.Collidables.Mesh(triangleBuffer, Vector3.One, _simulation.BufferPool); var mesh = new Mesh(triangleBuffer, Vector3.One, _simulation.BufferPool);
var shapeIndex = _simulation.Shapes.Add(mesh); var shapeIndex = _simulation.Shapes.Add(mesh);
var bodyDescription = BodyDescription.CreateKinematic( var bodyDescription = BodyDescription.CreateKinematic(
new RigidPose(position), new RigidPose(position, Quaternion.Identity), // La rotación se maneja con la velocidad angular, no con la pose inicial
new CollidableDescription(shapeIndex, 0.001f), // ✅ SpeculativeMargin bajo para detección precisa new CollidableDescription(shapeIndex, 0.1f),
new BodyActivityDescription(0.01f) new BodyActivityDescription(-1f) // ✅ CORREGIDO: -1f para que el cuerpo cinemático NUNCA duerma y no se mueva por su cuenta.
); );
BodyHandle = _simulation.Bodies.Add(bodyDescription); BodyHandle = _simulation.Bodies.Add(bodyDescription);
_bodyCreated = true; _bodyCreated = true;
System.Diagnostics.Debug.WriteLine($"[simCurve] Creado con {triangles.Length} triángulos reales almacenados"); // ✅ NUEVO: Establecer la velocidad cinemática inicial al crear el cuerpo.
ActualizarVelocidadCinematica();
} }
}
// ✅ MÉTODOS ELIMINADOS: CreateMainBody y CreateTriangleBody ya no son necesarios
// La curva ahora se crea como un solo Mesh en el método Create simplificado
/// <summary> /// <summary>
/// ✅ SIMPLIFICADO: Crea triángulos usando Triangle de BEPU directamente /// ✅ SIMPLIFICADO: Crea triángulos usando Triangle de BEPU directamente
@ -340,11 +344,5 @@ namespace CtrEditor.Simulacion
System.Diagnostics.Debug.WriteLine($"[CreateSimpleArcTriangles] Creados {triangles.Count} triángulos en coordenadas locales"); System.Diagnostics.Debug.WriteLine($"[CreateSimpleArcTriangles] Creados {triangles.Count} triángulos en coordenadas locales");
return triangles.ToArray(); return triangles.ToArray();
} }
// ✅ MÉTODOS ELIMINADOS: Los cálculos tangenciales complejos ya no son necesarios
// AngularAxisMotor maneja automáticamente la rotación en curvas
} }
} }

View File

@ -26,11 +26,6 @@ namespace CtrEditor.Simulacion
public Vector3 DirectionVector { get; private set; } public Vector3 DirectionVector { get; private set; }
public List<simBotella> BottlesOnTransport { get; private set; } = new List<simBotella>(); public List<simBotella> BottlesOnTransport { get; private set; } = new List<simBotella>();
// ✅ NUEVO EVENTO - para actualización de motores
public event Action<simTransporte> OnSpeedChanged;
public simTransporte(Simulation simulation, List<Action> deferredActions, float width, float height, Vector2 topLeft, float angle = 0, SimulationManagerBEPU simulationManager = null) public simTransporte(Simulation simulation, List<Action> deferredActions, float width, float height, Vector2 topLeft, float angle = 0, SimulationManagerBEPU simulationManager = null)
{ {
_simulation = simulation; _simulation = simulation;
@ -66,11 +61,8 @@ namespace CtrEditor.Simulacion
{ {
base.SetRotation(wpfAngle); base.SetRotation(wpfAngle);
// ✅ CRÍTICO: Actualizar propiedades cacheadas después del cambio de rotación // ✅ CRÍTICO: Actualizar propiedades cacheadas y velocidad después del cambio de rotación
UpdateCachedProperties(); UpdateCachedProperties();
// ✅ CRÍTICO: Disparar evento para actualizar motores activos con nueva dirección
OnSpeedChanged?.Invoke(this);
} }
public new void SetPosition(float x, float y, float z = 0) public new void SetPosition(float x, float y, float z = 0)
@ -108,14 +100,11 @@ namespace CtrEditor.Simulacion
var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, Width, Height, wpfAngle, zPosition); var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, Width, Height, wpfAngle, zPosition);
CoordinateConverter.UpdateBepuBodyPose(_simulation, BodyHandle, bepuCenter, wpfAngle); CoordinateConverter.UpdateBepuBodyPose(_simulation, BodyHandle, bepuCenter, wpfAngle);
// ✅ CRÍTICO: Actualizar propiedades cacheadas después del cambio de orientación // ✅ CRÍTICO: Actualizar propiedades cacheadas y velocidad después del cambio de orientación
UpdateCachedProperties(); UpdateCachedProperties();
// ✅ CRÍTICO: Disparar evento para actualizar motores activos con nueva dirección
OnSpeedChanged?.Invoke(this);
} }
// ✅ NUEVO MÉTODO - actualizar propiedades cacheadas // ✅ NUEVO MÉTODO - actualizar propiedades cacheadas y velocidad cinemática
internal void UpdateCachedProperties() internal void UpdateCachedProperties()
{ {
// ✅ CORREGIDO: La dirección siempre es UnitX rotado por el ángulo del transporte // ✅ CORREGIDO: La dirección siempre es UnitX rotado por el ángulo del transporte
@ -148,15 +137,29 @@ namespace CtrEditor.Simulacion
// 🔍 DEBUG: Agregar información detallada // 🔍 DEBUG: Agregar información detallada
System.Diagnostics.Debug.WriteLine($"[UpdateCached-Fallback] WPF Angle: {wpfAngle}°, WPF Vector: ({wpfX:F3}, {wpfY:F3}), BEPU DirectionVector: {DirectionVector}, Length: {DirectionVector.Length()}"); System.Diagnostics.Debug.WriteLine($"[UpdateCached-Fallback] WPF Angle: {wpfAngle}°, WPF Vector: ({wpfX:F3}, {wpfY:F3}), BEPU DirectionVector: {DirectionVector}, Length: {DirectionVector.Length()}");
} }
// ✅ NUEVO: Actualizar la velocidad del cuerpo cinemático siempre que cambien las propiedades
ActualizarVelocidadCinematica();
} }
// ✅ MODIFICAR MÉTODO EXISTENTE - disparar evento // ✅ MODIFICADO: Actualizar la velocidad y luego la del cuerpo cinemático
public void SetSpeed(float speed) public void SetSpeed(float speed)
{ {
Speed = speed; Speed = speed;
UpdateCachedProperties(); ActualizarVelocidadCinematica();
// Disparar evento para actualizar motores activos }
OnSpeedChanged?.Invoke(this);
/// <summary>
/// ✅ NUEVO: Aplica la velocidad al cuerpo cinemático de BEPU.
/// </summary>
public void ActualizarVelocidadCinematica()
{
if (_simulation != null && this.BodyHandle.Value >= 0 && _simulation.Bodies.BodyExists(this.BodyHandle))
{
var body = _simulation.Bodies.GetBodyReference(BodyHandle);
// ✅ CORREGIDO: Convertir velocidad de m/min a m/s para la simulación
body.Velocity.Linear = this.DirectionVector * (this.Speed / SpeedConversionFactor);
}
} }
/// <summary> /// <summary>
@ -190,9 +193,6 @@ namespace CtrEditor.Simulacion
// ✅ CRÍTICO: Actualizar propiedades cacheadas después del cambio de dimensiones // ✅ CRÍTICO: Actualizar propiedades cacheadas después del cambio de dimensiones
UpdateCachedProperties(); UpdateCachedProperties();
// ✅ CRÍTICO: Disparar evento para actualizar motores activos con nueva dirección
OnSpeedChanged?.Invoke(this);
} }
public void Create(float width, float height, Vector2 wpfTopLeft, float wpfAngle = 0) public void Create(float width, float height, Vector2 wpfTopLeft, float wpfAngle = 0)
@ -205,20 +205,10 @@ namespace CtrEditor.Simulacion
} }
/// <summary> /// <summary>
/// ✅ SIMPLIFICADO: RemoverBody que limpia restricciones y elimina el body /// ✅ SIMPLIFICADO: RemoverBody ya no necesita limpiar restricciones de botellas.
/// </summary> /// </summary>
public new void RemoverBody() public new void RemoverBody()
{ {
// ✅ NUEVO: Limpiar restricciones de botellas conectadas antes de eliminar el cuerpo
if (_simulationManager != null)
{
var connectedBottles = _simulationManager.GetBottlesConnectedToElement(this);
foreach (var bottle in connectedBottles)
{
bottle.CleanRestrictions(this);
}
}
base.RemoverBody(); base.RemoverBody();
} }
@ -233,7 +223,7 @@ namespace CtrEditor.Simulacion
var bodyDescription = BodyDescription.CreateKinematic( var bodyDescription = BodyDescription.CreateKinematic(
new RigidPose(bepuPosition, CoordinateConverter.CreateBepuQuaternionFromWpfAngle(wpfAngle)), new RigidPose(bepuPosition, CoordinateConverter.CreateBepuQuaternionFromWpfAngle(wpfAngle)),
new CollidableDescription(shapeIndex, 0.1f), new CollidableDescription(shapeIndex, 0.1f),
new BodyActivityDescription(0.01f) new BodyActivityDescription(-1f)
); );
BodyHandle = _simulation.Bodies.Add(bodyDescription); BodyHandle = _simulation.Bodies.Add(bodyDescription);