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
if (descarteA != null || descarteB != null)
{
var descarte = descarteA ?? descarteB;
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
}
@ -128,48 +129,41 @@ namespace CtrEditor.Simulacion
var curveA = GetCurveFromCollidable(pair.A);
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))
{
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
transport.UpdateCachedProperties();
var direction = transport.DirectionVector;
var speed = transport.Speed;
botella.CreateOrUpdateMotor(transport, direction, speed);
//Fricción alta para transportes
pairMaterial.FrictionCoefficient = 0.01f;
pairMaterial.MaximumRecoveryVelocity = 1f;
pairMaterial.SpringSettings = new SpringSettings(80, 6);
// ✅ Fricción ajustada para un arrastre firme pero no excesivo.
pairMaterial.FrictionCoefficient = 1.5f;
pairMaterial.MaximumRecoveryVelocity = 2.0f;
pairMaterial.SpringSettings = new SpringSettings(30, 1);
}
// ✅ 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))
{
var curve = curveA ?? curveB;
// ✅ CREAR O ACTUALIZAR MOTOR DINÁMICO INMEDIATAMENTE
var direction = _simulationManager.CalculateCurveDirectionFromBottlePosition(curve, botella);
botella.CreateOrUpdateMotor(curve, direction, curve.Speed);
// Fricción alta para curvas
pairMaterial.FrictionCoefficient = 0.01f;
pairMaterial.MaximumRecoveryVelocity = 1f;
pairMaterial.SpringSettings = new SpringSettings(80, 6);
// 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.
// ✅ Fricción ajustada para un arrastre firme pero no excesivo.
pairMaterial.FrictionCoefficient = 1.5f;
pairMaterial.MaximumRecoveryVelocity = 2.0f;
pairMaterial.SpringSettings = new SpringSettings(30, 1);
}
// ✅ CONTACTO BOTELLA-GUÍA: Configuración específica de fricción
else if (botella != null && (GetGuiaFromCollidable(pair.A) != null || GetGuiaFromCollidable(pair.B) != null))
{
// Configuración específica para guías usando propiedades configurables
pairMaterial.FrictionCoefficient = 0.01f;
// Fricción baja para las guías, deben deslizar, no arrastrar.
pairMaterial.FrictionCoefficient = 0.1f;
pairMaterial.MaximumRecoveryVelocity = 1f;
pairMaterial.SpringSettings = new SpringSettings(80, 6);
}
// Ajustes básicos para otras botellas
else if (botella != null)
{
pairMaterial.FrictionCoefficient = 0.01f;
// Fricción moderada para colisiones entre botellas.
pairMaterial.FrictionCoefficient = 0.3f;
pairMaterial.MaximumRecoveryVelocity = 1f;
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)
{
// Aplicar gravedad
// 1. APLICAR GRAVEDAD Y AMORTIGUAMIENTO ESTÁNDAR (VECTORIZADO)
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
// El sistema LinearAxisMotor maneja automáticamente todas las fuerzas
var linearDampingFactor = new Vector<float>(MathF.Pow(MathHelper.Clamp(1 - LinearDamping, 0, 1), dt[0]));
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
// Esto es crucial para que los cilindros se detengan de forma realista
var linearDampingWide = Vector<float>.One * LinearDamping;
var angularDampingWide = Vector<float>.One * AngularDamping;
velocity.Linear *= linearDampingFactor;
velocity.Angular *= angularDampingFactor;
velocity.Linear *= linearDampingWide;
velocity.Angular *= angularDampingWide; // ¡Esto es clave para detener rotaciones infinitas!
// 2. LÓGICA PERSONALIZADA PARA BOTELLAS (ESCALAR)
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 List<simBase> Cuerpos;
@ -368,6 +386,10 @@ namespace CtrEditor.Simulacion
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>
/// Obtiene el objeto simBase correspondiente a un BodyHandle
/// </summary>
@ -386,37 +408,8 @@ namespace CtrEditor.Simulacion
return simBase as simCurve;
}
/// <summary>
/// ✅ NUEVO: Obtiene todas las botellas que están conectadas a un elemento específico
/// </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;
}
// ✅ ELIMINADO: GetBottlesConnectedToElement ya no es necesario.
// La conexión ahora es implícita a través de la física de contacto.
// ✅ NUEVOS - gestión automática de clasificación
private void RegisterObjectHandle(simBase obj)
@ -543,7 +536,55 @@ namespace CtrEditor.Simulacion
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()
@ -614,6 +655,10 @@ namespace CtrEditor.Simulacion
// Limpiar visualización 3D
Visualization3DManager?.Clear();
// ✅ NUEVO: Limpiar también el nuevo diccionario.
CollidableData.Clear();
GlobalTime = 0f;
}
catch (Exception ex)
{
@ -625,7 +670,7 @@ namespace CtrEditor.Simulacion
{
try
{
// ✅ INICIALIZAR PROPIEDADES CACHEADAS
// ✅ INICIALIZAR PROPIEDADES CACHEADAS Y VELOCIDADES CINEMÁTICAS
foreach (var transport in Cuerpos.OfType<simTransporte>())
{
transport.UpdateCachedProperties();
@ -633,8 +678,7 @@ namespace CtrEditor.Simulacion
foreach (var curve in Cuerpos.OfType<simCurve>())
{
// ✅ CORREGIDO: Usar método público para actualizar velocidad
curve.SetSpeed(curve.Speed); // ✅ SIMPLIFICADO: Reinicializar velocidad
curve.SetSpeed(curve.Speed);
}
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
ProcessBarreraContacts();
ProcessDescarteContacts();
@ -960,223 +997,6 @@ namespace CtrEditor.Simulacion
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>
/// ✅ NUEVO: Procesa todas las barreras usando RayCast de BEPU
/// 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)
RemoveMarkedBottles();
// Limpiar contactos ya que no los usamos más
lock (_contactsLock)
{
_descarteContacts.Clear();
}
}
catch (Exception ex)
{
@ -1361,6 +1175,15 @@ namespace CtrEditor.Simulacion
List<simBotella> botellasAEliminar;
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);
_botellasParaEliminar.Clear();
}
@ -1459,5 +1282,36 @@ namespace CtrEditor.Simulacion
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.Threading.Tasks;
using DocumentFormat.OpenXml.Spreadsheet;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;
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 simTransporte CurrentBrakeTransport { get; set; } // ✅ NUEVA: Referencia al transporte de freno actual
// ✅ NUEVO SISTEMA SIMPLIFICADO: Motor dinámico que se crea solo cuando es necesario
public ConstraintHandle CurrentMotor { get; private set; } = default;
public ConstraintHandle CurrentDistanceLimit { get; private set; } = default; // ✅ NUEVO: Para curvas
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
// ✅ NUEVO: Propiedades para la física personalizada
public float BaseInverseMass { get; private set; }
public Vector3 InitialPosition3D { get; private set; }
private List<Action> _deferredActions;
@ -56,8 +50,6 @@ namespace CtrEditor.Simulacion
// ✅ USAR COORDINATECONVERTER para conversión centralizada
var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), Radius + zPos_Transporte + zAltura_Transporte);
Create(position3D);
// ✅ ELIMINADO: No crear motor permanente - se creará cuando sea necesario
}
public float CenterX
@ -154,303 +146,17 @@ namespace CtrEditor.Simulacion
BodyHandle = _simulation.Bodies.Add(bodyDescription);
_bodyCreated = true; // Marcar que hemos creado un cuerpo
// ✅ 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.
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
this.BaseInverseMass = bodyReference.LocalInertia.InverseMass;
// ✅ CORREGIDO: Usar el diccionario del SimulationManager y la clave .Value del handle.
if (_simulationManager != null)
_simulationManager.CollidableData[BodyHandle.Value] = this;
}
/// <summary>
/// ✅ NUEVO: Crea o actualiza el motor conectándolo al transporte/curva actual
/// </summary>
public void CreateOrUpdateMotor(simBase target, Vector3 direction, float speed)
{
try
{
// ✅ VALIDAR DIRECCIÓN
if (direction.Length() < 0.001f)
{
return;
}
// ✅ VALIDAR QUE EL TARGET Y LA SIMULACIÓN EXISTAN
if (target == null || _simulation == null || !_simulation.Bodies.BodyExists(BodyHandle))
{
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)
{
Radius = diameter / 2f;
@ -499,8 +205,8 @@ namespace CtrEditor.Simulacion
// Convertir a ángulo en Z solamente
var rotationZ = GetRotationZ();
// Crear nueva orientación solo con rotación en Z (botella siempre "de pie")
var correctedOrientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, rotationZ);
// ✅ CORREGIDO: Calificar explícitamente para evitar ambigüedad de tipos.
var correctedOrientation = System.Numerics.Quaternion.CreateFromAxisAngle(Vector3.UnitZ, rotationZ);
// Aplicar la orientación corregida
bodyReference.Pose.Orientation = correctedOrientation;

View File

@ -32,9 +32,6 @@ namespace CtrEditor.Simulacion
// ✅ SIMPLIFICADO: Propiedades esenciales únicamente
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
private Triangle[] _storedTriangles;
@ -66,11 +63,37 @@ namespace CtrEditor.Simulacion
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)
{
Speed = speed; // Velocidad angular directa (sin inversión)
OnSpeedChanged?.Invoke(this);
Speed = speed;
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>
@ -220,19 +243,8 @@ namespace CtrEditor.Simulacion
}
}
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
_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)
{
// ✅ 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
// Actualizar parámetros internos
@ -253,53 +265,45 @@ namespace CtrEditor.Simulacion
_endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
// ✅ 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 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);
Create(_curveCenter); // Sin rotación adicional
Create(_curveCenter); // Llamar a la sobrecarga privada
}
private void Create(Vector3 position)
{
RemoverBody();
// ✅ SIMPLIFICADO: Crear superficie usando Triangle de BEPU directamente
var triangles = CreateSimpleArcTriangles(_innerRadius, _outerRadius, _startAngle, _endAngle);
// ✅ ALMACENAR triángulos para acceso directo
_storedTriangles = triangles;
// ✅ SIMPLIFICADO: Crear un solo cuerpo con múltiples Triangle shapes usando Mesh
if (triangles.Length > 0)
if (triangles.Length == 0)
{
// ✅ 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++)
{
triangleBuffer[i] = triangles[i];
}
var mesh = new BepuPhysics.Collidables.Mesh(triangleBuffer, Vector3.One, _simulation.BufferPool);
var shapeIndex = _simulation.Shapes.Add(mesh);
var bodyDescription = BodyDescription.CreateKinematic(
new RigidPose(position),
new CollidableDescription(shapeIndex, 0.001f), // ✅ SpeculativeMargin bajo para detección precisa
new BodyActivityDescription(0.01f)
);
BodyHandle = _simulation.Bodies.Add(bodyDescription);
_bodyCreated = true;
System.Diagnostics.Debug.WriteLine($"[simCurve] Creado con {triangles.Length} triángulos reales almacenados");
System.Diagnostics.Debug.WriteLine($"[simCurve] No se crearon triángulos, no se creará el cuerpo.");
return;
}
}
_storedTriangles = triangles;
// ✅ 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
// ✅ CORREGIDO: Convertir el array de triángulos a un Buffer<Triangle> de BEPU.
_simulation.BufferPool.Take(triangles.Length, out BepuUtilities.Memory.Buffer<Triangle> triangleBuffer);
for (int i = 0; i < triangles.Length; i++)
{
triangleBuffer[i] = triangles[i];
}
var mesh = new Mesh(triangleBuffer, Vector3.One, _simulation.BufferPool);
var shapeIndex = _simulation.Shapes.Add(mesh);
var bodyDescription = BodyDescription.CreateKinematic(
new RigidPose(position, Quaternion.Identity), // La rotación se maneja con la velocidad angular, no con la pose inicial
new CollidableDescription(shapeIndex, 0.1f),
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);
_bodyCreated = true;
// ✅ NUEVO: Establecer la velocidad cinemática inicial al crear el cuerpo.
ActualizarVelocidadCinematica();
}
/// <summary>
/// ✅ 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");
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 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)
{
_simulation = simulation;
@ -66,11 +61,8 @@ namespace CtrEditor.Simulacion
{
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();
// ✅ 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)
@ -108,14 +100,11 @@ namespace CtrEditor.Simulacion
var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, Width, Height, wpfAngle, zPosition);
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();
// ✅ 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()
{
// ✅ 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
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)
{
Speed = speed;
UpdateCachedProperties();
// Disparar evento para actualizar motores activos
OnSpeedChanged?.Invoke(this);
ActualizarVelocidadCinematica();
}
/// <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>
@ -190,9 +193,6 @@ namespace CtrEditor.Simulacion
// ✅ CRÍTICO: Actualizar propiedades cacheadas después del cambio de dimensiones
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)
@ -205,20 +205,10 @@ namespace CtrEditor.Simulacion
}
/// <summary>
/// ✅ SIMPLIFICADO: RemoverBody que limpia restricciones y elimina el body
/// ✅ SIMPLIFICADO: RemoverBody ya no necesita limpiar restricciones de botellas.
/// </summary>
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();
}
@ -233,7 +223,7 @@ namespace CtrEditor.Simulacion
var bodyDescription = BodyDescription.CreateKinematic(
new RigidPose(bepuPosition, CoordinateConverter.CreateBepuQuaternionFromWpfAngle(wpfAngle)),
new CollidableDescription(shapeIndex, 0.1f),
new BodyActivityDescription(0.01f)
new BodyActivityDescription(-1f)
);
BodyHandle = _simulation.Bodies.Add(bodyDescription);