diff --git a/ObjetosSim/osBase.cs b/ObjetosSim/osBase.cs
index 644b457..3b603cf 100644
--- a/ObjetosSim/osBase.cs
+++ b/ObjetosSim/osBase.cs
@@ -61,6 +61,25 @@ namespace CtrEditor.ObjetosSim
}
+ ///
+ /// Estructura para almacenar las dimensiones de los objetos de simulación
+ ///
+ public struct SimObjectDimensions
+ {
+ public float Width { get; set; }
+ public float Height { get; set; }
+ public float Radius { get; set; }
+ public int ObjectType { get; set; } // Tipo de objeto: 1=Transport, 2=Barrier, 3=Guide, etc.
+
+ public bool Equals(SimObjectDimensions other)
+ {
+ return Math.Abs(Width - other.Width) < 0.001f &&
+ Math.Abs(Height - other.Height) < 0.001f &&
+ Math.Abs(Radius - other.Radius) < 0.001f &&
+ ObjectType == other.ObjectType;
+ }
+ }
+
public abstract partial class osBase : ObservableObject
{
public virtual string Nombre { get; set; } = "osBase";
@@ -70,6 +89,10 @@ namespace CtrEditor.ObjetosSim
[JsonIgnore]
private System.Threading.Timer timer = null;
+ // ✅ SISTEMA DE DIMENSIONES: Para evitar recreación innecesaria de objetos físicos
+ [JsonIgnore]
+ private static Dictionary _lastKnownDimensions = new Dictionary();
+
[ObservableProperty]
[property: JsonIgnore]
@@ -1531,8 +1554,24 @@ namespace CtrEditor.ObjetosSim
if (simRect != null)
{
var topLeft2D = GetRectangleTopLeft(wpfRect);
- // simRect.Create maneja internamente la conversión a 3D con pivot correcto
- simRect.Create(Ancho, Alto, topLeft2D, Angulo);
+
+ // ✅ SISTEMA INTELIGENTE: Solo recrear si las dimensiones han cambiado
+ bool dimensionsChanged = HasTransportDimensionsChanged(simRect, Ancho, Alto);
+
+ if (dimensionsChanged)
+ {
+ // Las dimensiones cambiaron, recrear el objeto físico
+ simRect.Create(Ancho, Alto, topLeft2D, Angulo);
+ simRect.SetDimensions(Ancho, Alto); // Actualizar dimensiones internas
+ }
+ else
+ {
+ // ✅ CORREGIDO: Usar UpdateFromWpfParameters para manejar correctamente Top-Left + Ángulo
+ simRect.UpdateFromWpfParameters(topLeft2D, Angulo);
+ }
+
+ // Actualizar propiedades cacheadas
+ simRect.UpdateCachedProperties();
}
}
@@ -1541,8 +1580,21 @@ namespace CtrEditor.ObjetosSim
if (simRect != null)
{
var topLeft2D = GetRectangleTopLeft(wpfRect);
- // simRect.Create maneja internamente la conversión a 3D con pivot correcto
- simRect.Create(Ancho, Alto, topLeft2D, Angulo);
+
+ // ✅ SISTEMA INTELIGENTE: Solo recrear si las dimensiones han cambiado
+ bool dimensionsChanged = HasBarrierDimensionsChanged(simRect, Ancho, Alto);
+
+ if (dimensionsChanged)
+ {
+ // Las dimensiones cambiaron, recrear el objeto físico
+ simRect.Create(Ancho, Alto, topLeft2D, Angulo);
+ simRect.SetDimensions(Ancho, Alto); // Actualizar dimensiones internas
+ }
+ else
+ {
+ // ✅ CORREGIDO: Usar UpdateFromWpfParameters para manejar correctamente Top-Left + Ángulo
+ simRect.UpdateFromWpfParameters(topLeft2D, Angulo);
+ }
}
}
@@ -1585,9 +1637,23 @@ namespace CtrEditor.ObjetosSim
// Actualizar las propiedades desde el objeto osGuia
if (this is osGuia guiaObj)
{
+ // ✅ SISTEMA INTELIGENTE: Solo recrear si las dimensiones han cambiado
+ bool dimensionsChanged = HasGuideDimensionsChanged(simGuia, guiaObj.Ancho, guiaObj.AltoGuia);
+
+ if (dimensionsChanged)
+ {
+ // Las dimensiones cambiaron, recrear el objeto físico
+ simGuia.Create(guiaObj.Ancho, guiaObj.AltoGuia, topLeft2D, guiaObj.Angulo);
+ simGuia.SetDimensions(guiaObj.Ancho, guiaObj.AltoGuia); // Actualizar dimensiones internas
+ }
+ else
+ {
+ // Solo actualizar posición y rotación
+ simGuia.SetPosition(topLeft2D, guiaObj.Angulo);
+ }
+
+ // Actualizar propiedades internas
simGuia.UpdateProperties(guiaObj.Ancho, guiaObj.AltoGuia, guiaObj.Angulo);
- // Crear usando Top-Left + dimensiones + ángulo
- simGuia.Create(guiaObj.Ancho, guiaObj.AltoGuia, topLeft2D, guiaObj.Angulo);
}
}
}
@@ -1676,6 +1742,128 @@ namespace CtrEditor.ObjetosSim
}
}
+ // ✅ MÉTODOS PARA GESTIÓN INTELIGENTE DE DIMENSIONES
+
+ ///
+ /// Verifica si las dimensiones de un transporte han cambiado
+ ///
+ private bool HasTransportDimensionsChanged(simTransporte transport, float newWidth, float newHeight)
+ {
+ if (transport == null) return true;
+
+ var newDimensions = new SimObjectDimensions
+ {
+ Width = newWidth,
+ Height = newHeight,
+ ObjectType = 1 // Transport
+ };
+
+ if (_lastKnownDimensions.TryGetValue(transport, out var lastDimensions))
+ {
+ bool changed = !newDimensions.Equals(lastDimensions);
+ if (changed)
+ {
+ _lastKnownDimensions[transport] = newDimensions;
+ System.Diagnostics.Debug.WriteLine($"[Dimensions] Transport dimensions CHANGED: {newWidth}x{newHeight}");
+ }
+ return changed;
+ }
+ else
+ {
+ // Primera vez - consideramos como cambio
+ _lastKnownDimensions[transport] = newDimensions;
+ System.Diagnostics.Debug.WriteLine($"[Dimensions] Transport first time: {newWidth}x{newHeight}");
+ return true;
+ }
+ }
+
+ ///
+ /// Verifica si las dimensiones de una barrera han cambiado
+ ///
+ private bool HasBarrierDimensionsChanged(simBarrera barrier, float newWidth, float newHeight)
+ {
+ if (barrier == null) return true;
+
+ var newDimensions = new SimObjectDimensions
+ {
+ Width = newWidth,
+ Height = newHeight,
+ ObjectType = 2 // Barrier
+ };
+
+ if (_lastKnownDimensions.TryGetValue(barrier, out var lastDimensions))
+ {
+ bool changed = !newDimensions.Equals(lastDimensions);
+ if (changed)
+ {
+ _lastKnownDimensions[barrier] = newDimensions;
+ System.Diagnostics.Debug.WriteLine($"[Dimensions] Barrier dimensions CHANGED: {newWidth}x{newHeight}");
+ }
+ return changed;
+ }
+ else
+ {
+ // Primera vez - consideramos como cambio
+ _lastKnownDimensions[barrier] = newDimensions;
+ System.Diagnostics.Debug.WriteLine($"[Dimensions] Barrier first time: {newWidth}x{newHeight}");
+ return true;
+ }
+ }
+
+ ///
+ /// Verifica si las dimensiones de una guía han cambiado
+ ///
+ private bool HasGuideDimensionsChanged(simGuia guide, float newWidth, float newHeight)
+ {
+ if (guide == null) return true;
+
+ var newDimensions = new SimObjectDimensions
+ {
+ Width = newWidth,
+ Height = newHeight,
+ ObjectType = 3 // Guide
+ };
+
+ if (_lastKnownDimensions.TryGetValue(guide, out var lastDimensions))
+ {
+ bool changed = !newDimensions.Equals(lastDimensions);
+ if (changed)
+ {
+ _lastKnownDimensions[guide] = newDimensions;
+ System.Diagnostics.Debug.WriteLine($"[Dimensions] Guide dimensions CHANGED: {newWidth}x{newHeight}");
+ }
+ return changed;
+ }
+ else
+ {
+ // Primera vez - consideramos como cambio
+ _lastKnownDimensions[guide] = newDimensions;
+ System.Diagnostics.Debug.WriteLine($"[Dimensions] Guide first time: {newWidth}x{newHeight}");
+ return true;
+ }
+ }
+
+ ///
+ /// Limpia las dimensiones almacenadas para un objeto específico
+ ///
+ public static void ClearStoredDimensions(simBase simObj)
+ {
+ if (simObj != null && _lastKnownDimensions.ContainsKey(simObj))
+ {
+ _lastKnownDimensions.Remove(simObj);
+ System.Diagnostics.Debug.WriteLine($"[Dimensions] Cleared stored dimensions for {simObj.GetType().Name}");
+ }
+ }
+
+ ///
+ /// Limpia todas las dimensiones almacenadas (útil al cerrar simulación)
+ ///
+ public static void ClearAllStoredDimensions()
+ {
+ _lastKnownDimensions.Clear();
+ System.Diagnostics.Debug.WriteLine($"[Dimensions] Cleared ALL stored dimensions");
+ }
+
}
public class UniqueId
{
diff --git a/Simulacion/BEPU.cs b/Simulacion/BEPU.cs
index 359c7ad..70bd328 100644
--- a/Simulacion/BEPU.cs
+++ b/Simulacion/BEPU.cs
@@ -17,12 +17,223 @@ using CtrEditor.FuncionesBase;
namespace CtrEditor.Simulacion
{
+ ///
+ /// Clase centralizada para manejar todas las conversiones entre coordenadas WPF y BEPU
+ /// WPF: Y hacia abajo, ángulos en sentido horario, Top-Left como referencia
+ /// BEPU: Y hacia arriba, ángulos en sentido antihorario, Center como referencia
+ ///
+ public static class CoordinateConverter
+ {
+ ///
+ /// Convierte ángulo de WPF a BEPU (invierte signo)
+ ///
+ public static float WpfAngleToBepuAngle(float wpfAngle)
+ {
+ return -wpfAngle;
+ }
+
+ ///
+ /// Convierte ángulo de BEPU a WPF (invierte signo)
+ ///
+ public static float BepuAngleToWpfAngle(float bepuAngle)
+ {
+ return -bepuAngle;
+ }
+
+ ///
+ /// Convierte posición Y de WPF a BEPU (invierte signo)
+ ///
+ public static float WpfYToBepuY(float wpfY)
+ {
+ return -wpfY;
+ }
+
+ ///
+ /// Convierte posición Y de BEPU a WPF (invierte signo)
+ ///
+ public static float BepuYToWpfY(float bepuY)
+ {
+ return -bepuY;
+ }
+
+ ///
+ /// Convierte Vector2 de WPF a BEPU (solo invierte Y)
+ ///
+ public static Vector2 WpfToBepuVector2(Vector2 wpfVector)
+ {
+ return new Vector2(wpfVector.X, -wpfVector.Y);
+ }
+
+ ///
+ /// Convierte Vector2 de BEPU a WPF (solo invierte Y)
+ ///
+ public static Vector2 BepuToWpfVector2(Vector2 bepuVector)
+ {
+ return new Vector2(bepuVector.X, -bepuVector.Y);
+ }
+
+ ///
+ /// Convierte Vector3 de BEPU a Vector2 WPF (proyección XY con Y invertida)
+ ///
+ public static Vector2 BepuVector3ToWpfVector2(Vector3 bepuVector)
+ {
+ return new Vector2(bepuVector.X, -bepuVector.Y);
+ }
+
+ ///
+ /// Calcula la posición del centro en coordenadas BEPU desde Top-Left WPF
+ /// Maneja correctamente la rotación del objeto
+ ///
+ public static Vector3 CalculateBepuCenterFromWpfTopLeft(Vector2 wpfTopLeft, float width, float height, float wpfAngle, float zPosition)
+ {
+ // Calcular el offset del centro desde Top-Left (sin rotación)
+ var offsetX = width / 2f;
+ var offsetY = height / 2f;
+
+ // Convertir ángulo WPF a radianes para cálculos trigonométricos
+ var angleRadians = simBase.GradosARadianes(wpfAngle);
+ var cos = (float)Math.Cos(angleRadians);
+ var sin = (float)Math.Sin(angleRadians);
+
+ // Rotar el offset alrededor del Top-Left usando el ángulo WPF
+ var rotatedOffsetX = offsetX * cos - offsetY * sin;
+ var rotatedOffsetY = offsetX * sin + offsetY * cos;
+
+ // Calcular nueva posición del centro manteniendo Top-Left fijo
+ var centerX = wpfTopLeft.X + rotatedOffsetX;
+ var centerY = wpfTopLeft.Y + rotatedOffsetY;
+
+ // Convertir a 3D con Y invertida para BEPU
+ return new Vector3(centerX, WpfYToBepuY(centerY), zPosition);
+ }
+
+ ///
+ /// Calcula la posición Top-Left WPF desde el centro BEPU
+ /// Maneja correctamente la rotación del objeto
+ ///
+ public static Vector2 CalculateWpfTopLeftFromBepuCenter(Vector3 bepuCenter, float width, float height, float wpfAngle)
+ {
+ // Convertir centro BEPU a WPF
+ var wpfCenterX = bepuCenter.X;
+ var wpfCenterY = BepuYToWpfY(bepuCenter.Y);
+
+ // Calcular el offset del centro al Top-Left (sin rotación)
+ var offsetX = -width / 2f;
+ var offsetY = -height / 2f;
+
+ // Convertir ángulo WPF a radianes para cálculos trigonométricos
+ var angleRadians = simBase.GradosARadianes(wpfAngle);
+ var cos = (float)Math.Cos(angleRadians);
+ var sin = (float)Math.Sin(angleRadians);
+
+ // Rotar el offset usando el ángulo WPF
+ var rotatedOffsetX = offsetX * cos - offsetY * sin;
+ var rotatedOffsetY = offsetX * sin + offsetY * cos;
+
+ // Calcular Top-Left desde el centro
+ var topLeftX = wpfCenterX + rotatedOffsetX;
+ var topLeftY = wpfCenterY + rotatedOffsetY;
+
+ return new Vector2(topLeftX, topLeftY);
+ }
+
+ ///
+ /// Crea un Quaternion para BEPU desde un ángulo WPF
+ ///
+ public static Quaternion CreateBepuQuaternionFromWpfAngle(float wpfAngle)
+ {
+ var bepuAngle = WpfAngleToBepuAngle(wpfAngle);
+ return Quaternion.CreateFromAxisAngle(Vector3.UnitZ, simBase.GradosARadianes(bepuAngle));
+ }
+
+ ///
+ /// Extrae el ángulo WPF desde un Quaternion BEPU
+ ///
+ public static float ExtractWpfAngleFromBepuQuaternion(Quaternion bepuQuaternion)
+ {
+ // Extraer ángulo Z del quaternion
+ var bepuAngleRadians = (float)Math.Atan2(
+ 2.0 * (bepuQuaternion.W * bepuQuaternion.Z + bepuQuaternion.X * bepuQuaternion.Y),
+ 1.0 - 2.0 * (bepuQuaternion.Y * bepuQuaternion.Y + bepuQuaternion.Z * bepuQuaternion.Z)
+ );
+
+ var bepuAngleDegrees = simBase.RadianesAGrados(bepuAngleRadians);
+ return BepuAngleToWpfAngle(bepuAngleDegrees);
+ }
+
+ ///
+ /// Actualiza la posición de un body BEPU manteniendo su rotación actual
+ ///
+ public static void UpdateBepuBodyPosition(Simulation simulation, BodyHandle bodyHandle, Vector3 newBepuPosition)
+ {
+ if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
+ {
+ var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
+ bodyReference.Pose.Position = newBepuPosition;
+ }
+ }
+
+ ///
+ /// Actualiza la rotación de un body BEPU manteniendo su posición actual
+ ///
+ public static void UpdateBepuBodyRotation(Simulation simulation, BodyHandle bodyHandle, float wpfAngle)
+ {
+ if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
+ {
+ var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
+ bodyReference.Pose.Orientation = CreateBepuQuaternionFromWpfAngle(wpfAngle);
+ }
+ }
+
+ ///
+ /// Actualiza posición y rotación de un body BEPU simultáneamente
+ ///
+ public static void UpdateBepuBodyPose(Simulation simulation, BodyHandle bodyHandle, Vector3 newBepuPosition, float wpfAngle)
+ {
+ if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
+ {
+ var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
+ bodyReference.Pose.Position = newBepuPosition;
+ bodyReference.Pose.Orientation = CreateBepuQuaternionFromWpfAngle(wpfAngle);
+ }
+ }
+
+ ///
+ /// Obtiene la posición del centro en coordenadas BEPU
+ ///
+ public static Vector3 GetBepuBodyPosition(Simulation simulation, BodyHandle bodyHandle)
+ {
+ if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
+ {
+ var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
+ return bodyReference.Pose.Position;
+ }
+ return Vector3.Zero;
+ }
+
+ ///
+ /// Obtiene el ángulo WPF desde un body BEPU
+ ///
+ public static float GetWpfAngleFromBepuBody(Simulation simulation, BodyHandle bodyHandle)
+ {
+ if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
+ {
+ var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
+ return ExtractWpfAngleFromBepuQuaternion(bodyReference.Pose.Orientation);
+ }
+ return 0f;
+ }
+ }
+
public class simBase
{
public BodyHandle BodyHandle { get; protected set; }
public Simulation _simulation;
protected bool _bodyCreated = false; // Bandera para saber si hemos creado un cuerpo
+ // ✅ NUEVA CONSTANTE - unificar conversión de velocidad
+ public const float SPEED_CONVERSION_FACTOR = 1f; //18.5f; // Factor de conversión de velocidad interna a m/s
+
// Constantes para las posiciones Z de los objetos 3D
public const float zPos_Transporte = 0f; // Z de la parte baja
public const float zAltura_Transporte = 0.1f; // Altura del transporte sobre zPos
@@ -48,6 +259,38 @@ namespace CtrEditor.Simulacion
}
}
+ ///
+ /// ✅ NUEVO: Cambia la forma de un body existente, limpiando la forma anterior para evitar memory leaks
+ ///
+ protected void ChangeBodyShape(TypedIndex newShapeIndex)
+ {
+ if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
+ {
+ var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
+
+ // ✅ CRÍTICO: Obtener la forma anterior para limpiarla del pool de shapes
+ var oldShapeIndex = bodyReference.Collidable.Shape;
+
+ // Cambiar a la nueva forma
+ _simulation.Bodies.SetShape(BodyHandle, newShapeIndex);
+
+ // ✅ CRÍTICO: Limpiar la forma anterior del pool para evitar memory leaks
+ // Nota: Solo limpiar si es diferente (para evitar limpiar la forma que acabamos de asignar)
+ if (oldShapeIndex.Packed != newShapeIndex.Packed)
+ {
+ try
+ {
+ _simulation.Shapes.RemoveAndDispose(oldShapeIndex, _simulation.BufferPool);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[simBase] Warning: Could not dispose old shape: {ex.Message}");
+ // Continuar - esto no es crítico para la funcionalidad
+ }
+ }
+ }
+ }
+
public static float Min(float Value, float Min = 0.01f)
{
return Math.Max(Value, Min);
@@ -65,56 +308,35 @@ namespace CtrEditor.Simulacion
public void SetPosition(float x, float y, float z = 0)
{
- if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
- {
- var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
- bodyReference.Pose.Position = new Vector3(x, y, z);
- }
+ CoordinateConverter.UpdateBepuBodyPosition(_simulation, BodyHandle, new Vector3(x, y, z));
}
- public void SetPosition(Vector2 position)
+ public void SetPosition(Vector2 wpfPosition)
{
- // Invertir Y para convertir de WPF (Y hacia abajo) a 3D (Y hacia arriba)
// Mantener la coordenada Z actual para preservar la altura del objeto
- var currentPosition = GetPosition();
- SetPosition(position.X, -position.Y, currentPosition.Z);
+ var currentBepuPosition = CoordinateConverter.GetBepuBodyPosition(_simulation, BodyHandle);
+ var newBepuPosition = new Vector3(wpfPosition.X, CoordinateConverter.WpfYToBepuY(wpfPosition.Y), currentBepuPosition.Z);
+ CoordinateConverter.UpdateBepuBodyPosition(_simulation, BodyHandle, newBepuPosition);
}
- public void SetPosition(Vector3 position)
+ public void SetPosition(Vector3 bepuPosition)
{
- SetPosition(position.X, position.Y, position.Z);
+ CoordinateConverter.UpdateBepuBodyPosition(_simulation, BodyHandle, bepuPosition);
}
public Vector3 GetPosition()
{
- if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
- {
- var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
- return bodyReference.Pose.Position;
- }
- return Vector3.Zero;
+ return CoordinateConverter.GetBepuBodyPosition(_simulation, BodyHandle);
}
- public void SetRotation(float angleZ)
+ public void SetRotation(float wpfAngle)
{
- if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
- {
- var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
- // ✅ Convertir de grados a radianes para Quaternion.CreateFromAxisAngle
- bodyReference.Pose.Orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, GradosARadianes(angleZ));
- }
+ CoordinateConverter.UpdateBepuBodyRotation(_simulation, BodyHandle, wpfAngle);
}
public float GetRotationZ()
{
- if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
- {
- var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
- // Extraer el ángulo Z del quaternion
- var q = bodyReference.Pose.Orientation;
- return (float)Math.Atan2(2.0 * (q.W * q.Z + q.X * q.Y), 1.0 - 2.0 * (q.Y * q.Y + q.Z * q.Z));
- }
- return 0f;
+ return CoordinateConverter.GetWpfAngleFromBepuBody(_simulation, BodyHandle);
}
}
@@ -130,15 +352,30 @@ namespace CtrEditor.Simulacion
public float Width { get; set; }
public float Height { get; set; }
- public simTransporte(Simulation simulation, List deferredActions, float width, float height, Vector2 topLeft, float angle = 0)
+ // ✅ NUEVAS PROPIEDADES - cachear cálculos costosos
+ public Vector3 DirectionVector { get; private set; }
+ public float SpeedMetersPerSecond { get; private set; }
+ public List BottlesOnTransport { get; private set; } = new List();
+
+ // ✅ NUEVO EVENTO - para actualización de motores
+ public event Action OnSpeedChanged;
+
+ // ✅ NUEVA REFERENCIA - para limpiar motors
+ private SimulationManagerBEPU _simulationManager;
+
+ public simTransporte(Simulation simulation, List deferredActions, float width, float height, Vector2 topLeft, float angle = 0, SimulationManagerBEPU simulationManager = null)
{
_simulation = simulation;
_deferredActions = deferredActions;
+ _simulationManager = simulationManager; // ✅ NUEVA REFERENCIA
Width = width;
Height = height;
// Usar el nuevo método Create que maneja Top-Left correctamente
Create(width, height, topLeft, angle);
+
+ // ✅ INICIALIZAR PROPIEDADES CRÍTICAS
+ UpdateCachedProperties();
}
public float Angle
@@ -157,10 +394,57 @@ namespace CtrEditor.Simulacion
{
base.SetPosition(x, y, z);
}
+
+ ///
+ /// ✅ NUEVO: Actualiza posición desde Top-Left WPF con dimensiones y ángulo actuales
+ ///
+ public void SetPositionFromWpfTopLeft(Vector2 wpfTopLeft)
+ {
+ var currentWpfAngle = GetRotationZ(); // Ya usa CoordinateConverter
+ var zPosition = GetPosition().Z; // Mantener Z actual
+ var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, Width, Height, currentWpfAngle, zPosition);
+ SetPosition(bepuCenter);
+ }
+
+ ///
+ /// ✅ NUEVO: Obtiene Top-Left WPF desde la posición actual
+ ///
+ public Vector2 GetWpfTopLeft()
+ {
+ var bepuCenter = GetPosition();
+ var wpfAngle = GetRotationZ(); // Ya usa CoordinateConverter
+ return CoordinateConverter.CalculateWpfTopLeftFromBepuCenter(bepuCenter, Width, Height, wpfAngle);
+ }
+
+ ///
+ /// ✅ NUEVO: Actualiza tanto posición como rotación desde parámetros WPF
+ ///
+ public void UpdateFromWpfParameters(Vector2 wpfTopLeft, float wpfAngle)
+ {
+ var zPosition = GetPosition().Z; // Mantener Z actual
+ var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, Width, Height, wpfAngle, zPosition);
+ CoordinateConverter.UpdateBepuBodyPose(_simulation, BodyHandle, bepuCenter, wpfAngle);
+
+ // Actualizar propiedades cacheadas después del cambio
+ UpdateCachedProperties();
+ }
+ // ✅ NUEVO MÉTODO - actualizar propiedades cacheadas
+ public void UpdateCachedProperties()
+ {
+ // Calcular dirección basada en rotación actual
+ var angle = GetRotationZ();
+ DirectionVector = new Vector3((float)Math.Cos(angle), (float)Math.Sin(angle), 0);
+ SpeedMetersPerSecond = Speed / SPEED_CONVERSION_FACTOR;
+ }
+
+ // ✅ MODIFICAR MÉTODO EXISTENTE - disparar evento
public void SetSpeed(float speed)
{
Speed = speed;
+ UpdateCachedProperties();
+ // Disparar evento para actualizar motores activos
+ OnSpeedChanged?.Invoke(this);
}
///
@@ -170,7 +454,7 @@ namespace CtrEditor.Simulacion
/// Velocidad en m/s (típicamente entre -5.0 y 5.0)
public void SetTransportSpeed(float speedMeterPerSecond)
{
- Speed = speedMeterPerSecond;
+ SetSpeed(speedMeterPerSecond * SPEED_CONVERSION_FACTOR);
}
///
@@ -178,7 +462,7 @@ namespace CtrEditor.Simulacion
///
public void StopTransport()
{
- Speed = 0f;
+ SetSpeed(0f);
}
///
@@ -186,7 +470,7 @@ namespace CtrEditor.Simulacion
///
public void ReverseTransport()
{
- Speed = -Speed;
+ SetSpeed(-Speed);
}
public void SetDimensions(float width, float height)
@@ -194,49 +478,71 @@ namespace CtrEditor.Simulacion
Width = width;
Height = height;
- // Actualizar la forma del cuerpo existente
+ // ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var box = new Box(width, height, zAltura_Transporte);
var shapeIndex = _simulation.Shapes.Add(box);
- _simulation.Bodies.SetShape(BodyHandle, shapeIndex);
+ ChangeBodyShape(shapeIndex);
}
}
- public void Create(float width, float height, Vector2 topLeft, float angle = 0)
+ public void Create(float width, float height, Vector2 wpfTopLeft, float wpfAngle = 0)
{
- // Calcular el offset del centro desde Top-Left (sin rotación)
- var offsetX = width / 2f;
- var offsetY = height / 2f;
-
- // Rotar el offset alrededor del Top-Left usando el ángulo
- // ✅ Convertir de grados a radianes antes de usar Math.Cos/Sin
- var angleRadians = GradosARadianes(angle);
- var cos = (float)Math.Cos(angleRadians);
- var sin = (float)Math.Sin(angleRadians);
-
- var rotatedOffsetX = offsetX * cos - offsetY * sin;
- var rotatedOffsetY = offsetX * sin + offsetY * cos;
-
- // Calcular nueva posición del centro manteniendo Top-Left fijo
- var centerX = topLeft.X + rotatedOffsetX;
- var centerY = topLeft.Y + rotatedOffsetY;
-
- // Convertir a 3D e invertir Y - Transportes en Z = Depth/2 para estar sobre el suelo
- var center3D = new Vector3(centerX, -centerY, zAltura_Transporte / 2f + zPos_Transporte);
-
- Create(width, height, center3D, -angle); // ✅ Invertir ángulo cuando se invierte Y
+ // ✅ USAR COORDINATECONVERTER para conversión centralizada
+ var zPosition = zAltura_Transporte / 2f + zPos_Transporte;
+ var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, width, height, wpfAngle, zPosition);
+
+ Create(width, height, bepuCenter, wpfAngle);
}
- public void Create(float width, float height, Vector3 position, float angle = 0)
+ ///
+ /// ✅ MODIFICADO: RemoverBody que limpia motors conectados antes de eliminar el body
+ ///
+ public new void RemoverBody()
+ {
+ if (_bodyCreated && _simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
+ {
+ // ✅ CRÍTICO: Limpiar todos los motors conectados a este transporte ANTES de eliminar el body
+ RemoveConnectedMotors();
+
+ _simulation.Bodies.Remove(BodyHandle);
+ _bodyCreated = false;
+ }
+ }
+
+ ///
+ /// ✅ NUEVO: Limpia todos los motors conectados a este transporte
+ ///
+ private void RemoveConnectedMotors()
+ {
+ try
+ {
+ // ✅ USAR REFERENCIA DIRECTA al SimulationManager
+ if (_simulationManager != null)
+ {
+ _simulationManager.RemoveMotorsConnectedToBody(BodyHandle);
+ }
+
+ // Limpiar la lista local de botellas
+ BottlesOnTransport.Clear();
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[simTransporte] Error removing connected motors: {ex.Message}");
+ }
+ }
+
+ public void Create(float width, float height, Vector3 bepuPosition, float wpfAngle = 0)
{
RemoverBody();
var box = new Box(width, height, zAltura_Transporte);
var shapeIndex = _simulation.Shapes.Add(box);
+ // ✅ USAR COORDINATECONVERTER para conversión centralizada
var bodyDescription = BodyDescription.CreateKinematic(
- new RigidPose(position, Quaternion.CreateFromAxisAngle(Vector3.UnitZ, GradosARadianes(angle))),
+ new RigidPose(bepuPosition, CoordinateConverter.CreateBepuQuaternionFromWpfAngle(wpfAngle)),
new CollidableDescription(shapeIndex, 0.1f),
new BodyActivityDescription(0.01f)
);
@@ -288,44 +594,60 @@ namespace CtrEditor.Simulacion
{
base.SetPosition(x, y, z);
}
+
+ ///
+ /// ✅ NUEVO: Actualiza posición desde Top-Left WPF con dimensiones y ángulo actuales
+ ///
+ public void SetPositionFromWpfTopLeft(Vector2 wpfTopLeft)
+ {
+ var currentWpfAngle = GetRotationZ(); // Ya usa CoordinateConverter
+ var zPosition = GetPosition().Z; // Mantener Z actual
+ var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, Width, Height, currentWpfAngle, zPosition);
+ SetPosition(bepuCenter);
+ }
+
+ ///
+ /// ✅ NUEVO: Obtiene Top-Left WPF desde la posición actual
+ ///
+ public Vector2 GetWpfTopLeft()
+ {
+ var bepuCenter = GetPosition();
+ var wpfAngle = GetRotationZ(); // Ya usa CoordinateConverter
+ return CoordinateConverter.CalculateWpfTopLeftFromBepuCenter(bepuCenter, Width, Height, wpfAngle);
+ }
+
+ ///
+ /// ✅ NUEVO: Actualiza tanto posición como rotación desde parámetros WPF
+ ///
+ public void UpdateFromWpfParameters(Vector2 wpfTopLeft, float wpfAngle)
+ {
+ var zPosition = GetPosition().Z; // Mantener Z actual
+ var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, Width, Height, wpfAngle, zPosition);
+ CoordinateConverter.UpdateBepuBodyPose(_simulation, BodyHandle, bepuCenter, wpfAngle);
+ }
public void SetDimensions(float width, float height)
{
_height = height;
- // Actualizar la forma del cuerpo existente
+ // ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var box = new Box(width, height, zAltura_Barrera); // Altura de 20cm para detectar botellas
var shapeIndex = _simulation.Shapes.Add(box);
- _simulation.Bodies.SetShape(BodyHandle, shapeIndex);
+ ChangeBodyShape(shapeIndex);
}
}
- public void Create(float width, float height, Vector2 topLeft, float angle = 0, bool detectectNeck = false)
+ public void Create(float width, float height, Vector2 wpfTopLeft, float wpfAngle = 0, bool detectectNeck = false)
{
- // Calcular el offset del centro desde Top-Left (sin rotación)
- var offsetX = width / 2f;
- var offsetY = height / 2f;
-
- // Rotar el offset alrededor del Top-Left usando el ángulo
- // ✅ Convertir de grados a radianes antes de usar Math.Cos/Sin
- var angleRadians = GradosARadianes(angle);
- var cos = (float)Math.Cos(angleRadians);
- var sin = (float)Math.Sin(angleRadians);
-
- var rotatedOffsetX = offsetX * cos - offsetY * sin;
- var rotatedOffsetY = offsetX * sin + offsetY * cos;
-
- // Calcular nueva posición del centro manteniendo Top-Left fijo
- var centerX = topLeft.X + rotatedOffsetX;
- var centerY = topLeft.Y + rotatedOffsetY;
-
- // Convertir a 3D e invertir Y - Sensores centrados en z=0.1 (rango 0-0.2) para detectar botellas
- var center3D = new Vector3(centerX, -centerY, zPos_Barrera + zAltura_Barrera / 2f);
- Create(width, height, center3D, -angle, detectectNeck); // ✅ Invertir ángulo cuando se invierte Y
+ // ✅ USAR COORDINATECONVERTER para conversión centralizada
+ var zPosition = zPos_Barrera + zAltura_Barrera / 2f;
+ var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, width, height, wpfAngle, zPosition);
+
+ Create(width, height, bepuCenter, wpfAngle, detectectNeck);
}
- public void Create(float width, float height, Vector3 position, float angle = 0, bool detectectNeck = false)
+ public void Create(float width, float height, Vector3 bepuPosition, float wpfAngle = 0, bool detectectNeck = false)
{
RemoverBody();
@@ -337,8 +659,9 @@ namespace CtrEditor.Simulacion
// Configurar actividad para NUNCA dormir - los sensores deben estar siempre activos
var activityDescription = new BodyActivityDescription(-1f); // -1 = nunca dormir
+ // ✅ USAR COORDINATECONVERTER para conversión centralizada
var bodyDescription = BodyDescription.CreateKinematic(
- new RigidPose(position, Quaternion.CreateFromAxisAngle(Vector3.UnitZ, GradosARadianes(angle))),
+ new RigidPose(bepuPosition, CoordinateConverter.CreateBepuQuaternionFromWpfAngle(wpfAngle)),
new CollidableDescription(shapeIndex, 0f), // Speculative margin 0 para sensores
activityDescription
);
@@ -365,39 +688,21 @@ namespace CtrEditor.Simulacion
Create(width, height, topLeft, angle);
}
- public void Create(float width, float height, Vector2 topLeft, float angle)
+ public void Create(float width, float height, Vector2 wpfTopLeft, float wpfAngle)
{
RemoverBody();
- // Calcular el offset del centro desde Top-Left (sin rotación)
- var offsetX = width / 2f;
- var offsetY = height / 2f;
-
- // Rotar el offset alrededor del Top-Left usando el ángulo
- // ✅ Convertir de grados a radianes antes de usar Math.Cos/Sin
- var angleRadians = GradosARadianes(angle);
- var cos = (float)Math.Cos(angleRadians);
- var sin = (float)Math.Sin(angleRadians);
-
- var rotatedOffsetX = offsetX * cos - offsetY * sin;
- var rotatedOffsetY = offsetX * sin + offsetY * cos;
-
- // Calcular nueva posición del centro manteniendo Top-Left fijo
- var centerX = topLeft.X + rotatedOffsetX;
- var centerY = topLeft.Y + rotatedOffsetY;
-
- // Convertir a 3D e invertir Y - Guías más altas que transportes
- var center3D = new Vector3(centerX, -centerY, zAltura_Guia / 2 + zPos_Guia);
+ // ✅ USAR COORDINATECONVERTER para conversión centralizada
+ var zPosition = zAltura_Guia / 2 + zPos_Guia;
+ var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, width, height, wpfAngle, zPosition);
// Crear el Box con las dimensiones dadas - más alto para contener botellas
var box = new Box(width, GuideThickness, zAltura_Guia);
var shapeIndex = _simulation.Shapes.Add(box);
- // ✅ Invertir ángulo cuando se invierte Y para coherencia de rotación
- var orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, GradosARadianes(-angle));
-
+ // ✅ USAR COORDINATECONVERTER para conversión centralizada
var bodyDescription = BodyDescription.CreateKinematic(
- new RigidPose(center3D, orientation),
+ new RigidPose(bepuCenter, CoordinateConverter.CreateBepuQuaternionFromWpfAngle(wpfAngle)),
new CollidableDescription(shapeIndex, 0.1f),
new BodyActivityDescription(0.01f)
);
@@ -412,6 +717,38 @@ namespace CtrEditor.Simulacion
// Nota: ancho y angulo se ignoran ya que se calculan automáticamente desde start->end
GuideThickness = altoGuia;
}
+
+ ///
+ /// ✅ NUEVO - Actualiza solo las dimensiones sin recrear el objeto
+ ///
+ public void SetDimensions(float width, float height)
+ {
+ GuideThickness = height;
+
+ // ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
+ if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
+ {
+ var box = new Box(width, GuideThickness, zAltura_Guia);
+ var shapeIndex = _simulation.Shapes.Add(box);
+ ChangeBodyShape(shapeIndex);
+ }
+ }
+
+ ///
+ /// ✅ NUEVO - Actualiza solo la posición sin recrear el objeto usando CoordinateConverter
+ ///
+ public void SetPosition(Vector2 wpfTopLeft, float wpfAngle = 0)
+ {
+ // ✅ USAR COORDINATECONVERTER para conversión centralizada
+ var zPosition = zAltura_Guia / 2 + zPos_Guia;
+ // Usar las dimensiones actuales de la guía
+ var width = GuideThickness; // Para guías, el ancho puede ser derivado del espesor
+ var height = GuideThickness;
+ var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, width, height, wpfAngle, zPosition);
+
+ // Actualizar posición y rotación simultáneamente
+ CoordinateConverter.UpdateBepuBodyPose(_simulation, BodyHandle, bepuCenter, wpfAngle);
+ }
}
public class simBotella : simBase
@@ -424,12 +761,18 @@ namespace CtrEditor.Simulacion
public List ListOnTransports;
public bool isRestricted;
public bool isNoMoreRestricted;
- public float OriginalMass;
+
public simTransporte ConveyorRestrictedTo;
public float OverlapPercentage;
public float _neckRadius;
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
+
+ // ✅ NUEVAS PROPIEDADES - gestión de motores
+ public simTransporte CurrentTransport { get; set; }
+ public ConstraintHandle CurrentMotor { get; set; }
+ public bool HasActiveMotor => CurrentMotor.Value != 0;
+
private List _deferredActions;
public simBotella(Simulation simulation, List deferredActions, float diameter, Vector2 position, float mass, float neckRadius = 0)
@@ -439,7 +782,6 @@ namespace CtrEditor.Simulacion
Radius = diameter / 2f;
Height = diameter; // Altura igual al diámetro para mantener proporciones similares
_mass = mass;
- OriginalMass = mass;
_neckRadius = neckRadius;
ListOnTransports = new List();
@@ -550,12 +892,12 @@ namespace CtrEditor.Simulacion
Radius = diameter / 2f;
Height = diameter; // Mantener altura igual al diámetro para proporciones consistentes
- // Actualizar la forma del cuerpo existente
+ // ✅ 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);
- _simulation.Bodies.SetShape(BodyHandle, shapeIndex);
+ ChangeBodyShape(shapeIndex);
}
}
@@ -563,12 +905,12 @@ namespace CtrEditor.Simulacion
{
Height = height;
- // Actualizar la forma del cuerpo existente
+ // ✅ 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);
- _simulation.Bodies.SetShape(BodyHandle, shapeIndex);
+ ChangeBodyShape(shapeIndex);
}
}
@@ -634,26 +976,26 @@ namespace CtrEditor.Simulacion
return isOnTransports > 0;
}
- ///
- /// ✅ NUEVO: Método de seguridad para restaurar la masa original si la botella tiene masa aumentada
- ///
- public void RestoreOriginalMassIfNeeded()
+ // ✅ NUEVOS MÉTODOS - gestión de estado
+ public void AssignToTransport(simTransporte transport, ConstraintHandle motor)
{
- if (OriginalMass > 0) // Solo si hay masa original guardada
+ CurrentTransport = transport;
+ CurrentMotor = motor;
+ if (transport.isBrake)
{
- try
- {
- SetMass(OriginalMass);
- OriginalMass = 0; // Limpiar para evitar confusión
+ isOnBrakeTransport = true;
+ CurrentBrakeTransport = transport;
+ }
+ }
+
+ public void RemoveFromTransport()
+ {
+ CurrentTransport = null;
+ CurrentMotor = default;
isOnBrakeTransport = false;
CurrentBrakeTransport = null;
}
- catch (Exception ex)
- {
- // Error restoring original mass - continue
- }
- }
- }
+
}
public class simCurve : simBase
@@ -668,6 +1010,16 @@ namespace CtrEditor.Simulacion
internal List _triangleBodyHandles; // Lista de todos los triángulos que componen la curva
private List> _originalTriangles; // ✅ NUEVO: Triángulos originales para visualización debug
+ // ✅ NUEVAS PROPIEDADES - optimización
+ public float SpeedMetersPerSecond { get; private set; }
+ public List BottlesOnCurve { get; private set; } = new List();
+
+ // ✅ NUEVO EVENTO - para actualización de motores
+ public event Action OnSpeedChanged;
+
+ // ✅ NUEVA REFERENCIA - para limpiar motors
+ private SimulationManagerBEPU _simulationManager;
+
public float InnerRadius => _innerRadius;
public float OuterRadius => _outerRadius;
public float StartAngle => _startAngle;
@@ -678,10 +1030,11 @@ namespace CtrEditor.Simulacion
///
public List> GetOriginalTriangles() => _originalTriangles ?? new List>();
- public simCurve(Simulation simulation, List deferredActions, float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 topLeft, float unused = 0)
+ public simCurve(Simulation simulation, List deferredActions, float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 topLeft, float unused = 0, SimulationManagerBEPU simulationManager = null)
{
_simulation = simulation;
_deferredActions = deferredActions;
+ _simulationManager = simulationManager; // ✅ NUEVA REFERENCIA
_innerRadius = innerRadius;
_outerRadius = outerRadius;
// ✅ SENTIDO HORARIO: Convertir ángulos para que vayan en sentido horario
@@ -694,9 +1047,12 @@ namespace CtrEditor.Simulacion
Create(innerRadius, outerRadius, startAngle, endAngle, topLeft, 0);
}
+ // ✅ MODIFICAR MÉTODO EXISTENTE - disparar evento
public void SetSpeed(float speed)
{
Speed = -speed;
+ SpeedMetersPerSecond = Math.Abs(Speed) / SPEED_CONVERSION_FACTOR;
+ OnSpeedChanged?.Invoke(this);
}
public void UpdateCurve(float innerRadius, float outerRadius, float startAngle, float endAngle)
@@ -713,6 +1069,9 @@ namespace CtrEditor.Simulacion
public new void RemoverBody()
{
+ // ✅ CRÍTICO: Limpiar todos los motors conectados a esta curva ANTES de eliminar los bodies
+ RemoveConnectedMotors();
+
// Remover todos los triángulos de la curva
if (_triangleBodyHandles != null && _simulation != null)
{
@@ -735,8 +1094,40 @@ namespace CtrEditor.Simulacion
// Remover el cuerpo principal (si existe)
base.RemoverBody();
}
+
+ ///
+ /// ✅ NUEVO: Limpia todos los motors conectados a esta curva
+ ///
+ private void RemoveConnectedMotors()
+ {
+ try
+ {
+ // ✅ USAR REFERENCIA DIRECTA al SimulationManager
+ if (_simulationManager != null)
+ {
+ // Limpiar motors del cuerpo principal
+ _simulationManager.RemoveMotorsConnectedToBody(BodyHandle);
+
+ // Limpiar motors de todos los triángulos
+ if (_triangleBodyHandles != null)
+ {
+ foreach (var triangleHandle in _triangleBodyHandles)
+ {
+ _simulationManager.RemoveMotorsConnectedToBody(triangleHandle);
+ }
+ }
+ }
+
+ // Limpiar la lista local de botellas
+ BottlesOnCurve.Clear();
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[simCurve] Error removing connected motors: {ex.Message}");
+ }
+ }
- public void Create(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 topLeft, 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
// No hay rotación separada del objeto
@@ -747,20 +1138,15 @@ namespace CtrEditor.Simulacion
_startAngle = GradosARadianes(startAngle);
_endAngle = GradosARadianes(endAngle);
- // Calcular el offset del centro desde Top-Left
+ // ✅ USAR COORDINATECONVERTER para conversión centralizada
// Para curvas, el "tamaño" es el diámetro del radio exterior
var curveSize = outerRadius * 2f;
- var offsetX = curveSize / 2f;
- var offsetY = curveSize / 2f;
-
+ var zPosition = zAltura_Curve / 2f + zPos_Curve;
+
// Calcular nueva posición del centro (sin rotación - el sector ya está definido por los ángulos)
- var centerX = topLeft.X + offsetX;
- var centerY = topLeft.Y + offsetY;
+ var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition);
- // Convertir a 3D e invertir Y - Curvas en el mismo nivel que transportes
- var center3D = new Vector3(centerX, -centerY, zAltura_Curve / 2f + zPos_Curve);
-
- Create(center3D); // Sin rotación adicional
+ Create(bepuCenter); // Sin rotación adicional
}
private void Create(Vector3 position)
@@ -935,6 +1321,13 @@ namespace CtrEditor.Simulacion
return Vector3.Zero;
}
}
+
+ // ✅ MÉTODO MEJORADO - versión optimizada
+ public Vector3 GetCachedTangentialDirection(Vector3 bottlePosition)
+ {
+ var direction = GetTangentialDirection(bottlePosition);
+ return Speed < 0 ? -direction : direction;
+ }
}
public class simDescarte : simBase
@@ -1031,118 +1424,77 @@ namespace CtrEditor.Simulacion
public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, ref TManifold manifold, out PairMaterialProperties pairMaterial) where TManifold : unmanaged, IContactManifold
{
- // Configuración de materiales físicos por defecto
+ // ✅ SIMPLIFICADO - configuración básica de materiales físicos
pairMaterial = new PairMaterialProperties
{
- FrictionCoefficient = 0.6f, // Fricción moderada
+ FrictionCoefficient = 0.6f, // Fricción moderada por defecto
MaximumRecoveryVelocity = 2f, // Velocidad máxima de recuperación
- SpringSettings = new SpringSettings(60, 4) // Rigidez y amortiguamiento para colisiones sólidas
+ SpringSettings = new SpringSettings(60, 4) // Rigidez y amortiguamiento estándar
};
- // Detectar contactos que involucren botellas para aplicar propiedades especiales
if (_simulationManager != null)
{
var botellaA = GetBotellaFromCollidable(pair.A);
var botellaB = GetBotellaFromCollidable(pair.B);
- var transportA = GetTransportFromCollidable(pair.A);
- var transportB = GetTransportFromCollidable(pair.B);
var barreraA = GetBarreraFromCollidable(pair.A);
var barreraB = GetBarreraFromCollidable(pair.B);
- var curveA = GetCurveFromCollidable(pair.A);
- var curveB = GetCurveFromCollidable(pair.B);
- var botella = botellaA ?? botellaB;
-
- // Detectar contactos con descarte
var descarteA = GetDescarteFromCollidable(pair.A);
var descarteB = GetDescarteFromCollidable(pair.B);
+ var botella = botellaA ?? botellaB;
- // IMPORTANTE: Si hay una barrera involucrada, NO generar contacto físico
+ // ✅ CONSERVAR - barreras como sensores puros
if (barreraA != null || barreraB != null)
{
- // Ya no necesitamos registrar contactos - usamos detección geométrica pura
- // Las barreras son sensores completamente virtuales
-
- // NO generar contacto físico para barreras - son sensores puros
- return false;
+ return false; // NO generar contacto físico para barreras
}
- // Si hay un descarte involucrado, NO generar contacto físico pero registrar para eliminación
+ // ✅ CONSERVAR - descartes como sensores puros
if (descarteA != null || descarteB != null)
{
var descarte = descarteA ?? descarteB;
-
- // Registrar la botella para eliminación si hay contacto
if (botella != null)
{
_simulationManager.RegisterDescarteContact(descarte, botella);
}
-
- // NO generar contacto físico para descartes - son sensores puros
- return false;
+ return false; // NO generar contacto físico para descartes
}
- // Si hay una botella en contacto (pero no con barrera), aplicar mayor fricción para estabilidad
- if (botella != null)
+ // ✅ PROCESAR CREACIÓN DE MOTORES para transportes y curvas
+ var transportA = GetTransportFromCollidable(pair.A);
+ var transportB = GetTransportFromCollidable(pair.B);
+ var curveA = GetCurveFromCollidable(pair.A);
+ var curveB = GetCurveFromCollidable(pair.B);
+
+ // ✅ RESTAURADO - llamar métodos públicos del SimulationManager
+ // Si hay contacto botella-transporte, crear motor LinearAxisMotor
+ if (botella != null && (transportA != null || transportB != null))
{
- // Aumentar mucho la fricción para botellas (más estables)
+ var transport = transportA ?? transportB;
+ _simulationManager.TryCreateTransportMotor(botella, transport); // ✅ Llamar método público
+
+ // Fricción alta para transportes
pairMaterial.FrictionCoefficient = 0.9f;
-
- // Reducir la velocidad de recuperación para evitar rebotes excesivos
pairMaterial.MaximumRecoveryVelocity = 1f;
-
- // Hacer las colisiones más suaves y estables
pairMaterial.SpringSettings = new SpringSettings(80, 6);
}
-
- // Si hay un transporte y una botella en contacto
- if ((transportA != null || transportB != null) && botella != null)
- {
- var transporte = transportA ?? transportB;
-
- // ✅ NUEVA LÓGICA: Detectar contactos con transporte de freno PRIMERO
- if (transporte.isBrake)
- {
- // FRICCIÓN EXTREMA para transporte con freno - simula frenos mecánicos
- pairMaterial.FrictionCoefficient = 5.0f; // Fricción extremadamente alta
-
- // Reducir velocidad de recuperación para evitar rebotes
- pairMaterial.MaximumRecoveryVelocity = 0.1f;
-
- // Hacer el contacto más rígido para mejor control
- pairMaterial.SpringSettings = new SpringSettings(200, 20);
-
- // ✅ APLICAR FUERZAS DE FRENADO DIRECTAMENTE EN EL MANIFOLD
- ApplyBrakeForces(ref manifold, botella, transporte);
-
- // Registrar contacto con transporte de freno
- _simulationManager.RegisterBrakeTransportContact(botella, transporte);
- }
- else
- {
- // ✅ NUEVA LÓGICA: Aplicar fuerzas de transporte DIRECTAMENTE en el manifold
- if (Math.Abs(transporte.Speed) > 0.001f) // Solo si hay velocidad significativa
- {
- pairMaterial.FrictionCoefficient = 1.2f; // Fricción muy alta
-
- // ✅ APLICAR FUERZAS DE TRANSPORTE DIRECTAMENTE EN EL MANIFOLD
- ApplyTransportForces(ref manifold, botella, transporte);
- }
- }
- }
-
- // Si hay una curva y una botella en contacto
- if ((curveA != null || curveB != null) && botella != null)
+ // Si hay contacto botella-curva, crear motor LinearAxisMotor
+ else if (botella != null && (curveA != null || curveB != null))
{
var curve = curveA ?? curveB;
-
- // Solo aplicar si hay velocidad significativa en la curva
- if (Math.Abs(curve.Speed) > 0.001f)
- {
- pairMaterial.FrictionCoefficient = 1.0f; // Fricción alta para curvas
-
- // Aplicar fuerzas de curva directamente en el manifold
- ApplyCurveForces(ref manifold, botella, curve);
- }
+ _simulationManager.TryCreateCurveMotor(botella, curve); // ✅ Llamar método público
+
+ // Fricción alta para curvas
+ pairMaterial.FrictionCoefficient = 0.9f;
+ pairMaterial.MaximumRecoveryVelocity = 1f;
+ pairMaterial.SpringSettings = new SpringSettings(80, 6);
+ }
+ // Solo ajustes básicos de fricción para otras botellas
+ else if (botella != null)
+ {
+ // Fricción alta para mayor estabilidad de botellas
+ pairMaterial.FrictionCoefficient = 0.9f;
+ pairMaterial.MaximumRecoveryVelocity = 1f;
+ pairMaterial.SpringSettings = new SpringSettings(80, 6);
}
}
@@ -1223,165 +1575,7 @@ namespace CtrEditor.Simulacion
///
/// ✅ NUEVO MÉTODO: Aplicar fuerzas de frenado directamente en el manifold
///
- private void ApplyBrakeForces(ref TManifold manifold, simBotella botella, simTransporte transporte)
- where TManifold : unmanaged, IContactManifold
- {
- try
- {
- if (_simulationManager?.simulation == null) return;
- var botellaBody = _simulationManager.simulation.Bodies.GetBodyReference(botella.BodyHandle);
- var transporteBody = _simulationManager.simulation.Bodies.GetBodyReference(transporte.BodyHandle);
-
- // Calcular velocidad relativa
- var relativeVelocity = botellaBody.Velocity.Linear - transporteBody.Velocity.Linear;
-
- // Si la botella se mueve más rápido que el transporte, aplicar fuerza de frenado
- var transportDirection = GetTransportDirection(transporte);
- var velocityInTransportDirection = Vector3.Dot(relativeVelocity, transportDirection);
-
- // if (Math.Abs(velocityInTransportDirection) > Math.Abs(transporte.Speed / 18.5f))
- // {
- // Calcular fuerza de frenado proporcional a la diferencia de velocidad
- var targetSpeed = transporte.Speed / 18.5f;
- var currentSpeed = Vector3.Dot(botellaBody.Velocity.Linear, transportDirection);
- var speedDifference = currentSpeed - targetSpeed;
-
- // Aplicar impulso de frenado - esto se hace DURANTE la resolución de colisiones
- var brakeImpulse = -transportDirection * speedDifference * botella.Mass * 0.5f;
- botellaBody.ApplyLinearImpulse(brakeImpulse);
- // }
- }
- catch (Exception ex)
- {
- // Error applying brake forces during collision resolution
- }
- }
-
- ///
- /// ✅ NUEVO MÉTODO: Obtener dirección del transporte
- ///
- private Vector3 GetTransportDirection(simTransporte transporte)
- {
- var angle = transporte.GetRotationZ();
- return new Vector3((float)Math.Cos(angle), (float)Math.Sin(angle), 0);
- }
-
- ///
- /// ✅ NUEVO MÉTODO: Aplicar fuerzas de transporte normal directamente en el manifold
- ///
- private void ApplyTransportForces(ref TManifold manifold, simBotella botella, simTransporte transporte)
- where TManifold : unmanaged, IContactManifold
- {
- try
- {
- if (_simulationManager?.simulation == null) return;
-
- var botellaBody = _simulationManager.simulation.Bodies.GetBodyReference(botella.BodyHandle);
- var transporteBody = _simulationManager.simulation.Bodies.GetBodyReference(transporte.BodyHandle);
-
- // Calcular la dirección del transporte
- var transportDirection = GetTransportDirection(transporte);
-
- // Calcular la fuerza proporcional a la velocidad deseada
- var targetVelocity = transportDirection * transporte.Speed / 18.5f;
- var currentVelocity = botellaBody.Velocity.Linear;
-
- // Solo aplicar fuerza en el plano horizontal (X-Y)
- var horizontalVelocity = new Vector3(currentVelocity.X, currentVelocity.Y, 0);
-
- // Para transportes con freno (si aplica), limitar la velocidad máxima de la botella
- bool isOnBrakeTransport = botella.isOnBrakeTransport;
- if (isOnBrakeTransport)
- {
- var transportSpeed = Math.Abs(transporte.Speed / 18.5f);
- var currentSpeed = horizontalVelocity.Length();
-
- // Si la botella va más rápido que el transporte, limitar su velocidad
- if (currentSpeed > transportSpeed * 1.1f) // 10% de tolerancia
- {
- var limitedVelocity = Vector3.Normalize(horizontalVelocity) * transportSpeed;
- botellaBody.Velocity.Linear = new Vector3(limitedVelocity.X, limitedVelocity.Y, currentVelocity.Z);
- horizontalVelocity = limitedVelocity;
- }
- }
-
- var velocityDiff = targetVelocity - horizontalVelocity;
-
- // Aplicar una fuerza proporcional a la diferencia de velocidad
- var forceMultiplier = isOnBrakeTransport ? 3.0f : 1.0f; // 3x más fuerte para botellas con guías
- var baseForceMagnitude = velocityDiff.Length() * botella.Mass * 10f; // Factor base
- var forceMagnitude = baseForceMagnitude * forceMultiplier; // Aplicar multiplicador
-
- if (forceMagnitude > 0.01f)
- {
- var maxForce = botella.Mass * 50f * forceMultiplier; // Límite también escalado
- var force = Vector3.Normalize(velocityDiff) * Math.Min(forceMagnitude, maxForce);
-
- // Aplicar impulso directamente durante la resolución de colisiones
- botellaBody.ApplyLinearImpulse(force * (1f / 60f)); // Simular deltaTime típico
- }
- }
- catch (Exception ex)
- {
- // Error applying transport forces during collision resolution
- }
- }
-
- ///
- /// ✅ SIMPLIFICADO: Aplicar fuerzas de curva directamente en el manifold
- /// Si hay colisión física, la botella está sobre la curva - no necesita cálculos adicionales
- ///
- private void ApplyCurveForces(ref TManifold manifold, simBotella botella, simCurve curve)
- where TManifold : unmanaged, IContactManifold
- {
- try
- {
- if (_simulationManager?.simulation == null) return;
-
- var botellaBody = _simulationManager.simulation.Bodies.GetBodyReference(botella.BodyHandle);
- var botellaPosition = botellaBody.Pose.Position;
-
- // ✅ SIMPLIFICADO: Si hay colisión física, aplicar fuerzas directamente
- // Obtener la dirección tangencial
- var tangentialDirection = curve.GetTangentialDirection(botellaPosition);
-
- if (tangentialDirection.Length() > 0.001f)
- {
- // Calcular velocidad objetivo
- var speedMetersPerSecond = Math.Abs(curve.Speed) / 18.5f; // Convertir a m/s
- var targetVelocity = tangentialDirection * speedMetersPerSecond;
-
- // Obtener velocidad actual
- var currentVelocity = botellaBody.Velocity.Linear;
- var horizontalVelocity = new Vector3(currentVelocity.X, currentVelocity.Y, 0);
-
- // Usar directamente la dirección tangencial (sin interpolación compleja)
- var newDirection = tangentialDirection;
-
- // Usar la velocidad mayor entre la actual y la de la curva para continuidad
- var currentSpeed = horizontalVelocity.Length();
- var targetSpeed = Math.Max(currentSpeed, speedMetersPerSecond);
-
- // Calcular nueva velocidad
- var newVelocity = newDirection * targetSpeed;
- var velocityDiff = newVelocity - horizontalVelocity;
-
- // Aplicar impulso directamente (sin factor de overlap - la colisión es suficiente)
- var forceMagnitude = velocityDiff.Length() * botella.Mass * 8f;
- if (forceMagnitude > 0.01f)
- {
- var maxForce = botella.Mass * 40f;
- var force = Vector3.Normalize(velocityDiff) * Math.Min(forceMagnitude, maxForce);
- botellaBody.ApplyLinearImpulse(force * (1f / 60f)); // Simular deltaTime típico
- }
- }
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine($"[simCurve] Error applying curve forces: {ex.Message}");
- }
- }
public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB, ref ConvexContactManifold manifold)
{
@@ -1432,11 +1626,8 @@ namespace CtrEditor.Simulacion
var gravityWide = Vector3Wide.Broadcast(Gravity);
velocity.Linear += gravityWide * dt;
- // ✅ NUEVA FUNCIONALIDAD: Aplicar fuerzas de frenado durante la integración
- if (_simulationManager != null)
- {
- ApplyBrakeForcesInIntegration(bodyIndices, ref velocity, dt, workerIndex);
- }
+ // ✅ ELIMINADO COMPLETAMENTE - ya no se necesita lógica especial de frenado
+ // El sistema LinearAxisMotor maneja automáticamente todas las fuerzas
// Aplicar amortiguamiento lineal y angular para simular resistencia del aire
// Esto es crucial para que los cilindros se detengan de forma realista
@@ -1447,31 +1638,296 @@ namespace CtrEditor.Simulacion
velocity.Angular *= angularDampingWide; // ¡Esto es clave para detener rotaciones infinitas!
}
- ///
- /// ✅ NUEVO MÉTODO: Aplicar fuerzas de frenado durante la integración de velocidades
- /// ✅ SIMPLIFICADO: Solo aplicar amortiguamiento extra para botellas en transportes con freno
- ///
- private void ApplyBrakeForcesInIntegration(Vector bodyIndices, ref BodyVelocityWide velocity, Vector dt, int workerIndex)
+
+ }
+
+ public class MotorManager
+ {
+ private SimulationManagerBEPU _simulationManager;
+ private Dictionary _activeMotors;
+ private Dictionary _motorToBottle;
+
+ public MotorManager(SimulationManagerBEPU simulationManager)
+ {
+ _simulationManager = simulationManager;
+ _activeMotors = new Dictionary();
+ _motorToBottle = new Dictionary();
+ }
+
+ public void CreateTransportMotor(simBotella bottle, simTransporte transport)
{
try
{
- // ✅ SIMPLIFICADO: Aplicar amortiguamiento extra para efectos de frenado
- // Esto es más compatible con la arquitectura vectorizada de BEPU
-
- // Amortiguamiento adicional para simular efecto de frenado
- var brakeDampingWide = Vector.One * 0.95f; // Más agresivo que el damping normal
-
- // Aplicar solo si tenemos referencia al simulation manager
- if (_simulationManager != null)
+ // ✅ VALIDACIONES CRÍTICAS
+ if (bottle == null || transport == null || _simulationManager?.simulation == null)
+ return;
+
+ // Validar que los BodyHandle existan
+ if (!_simulationManager.simulation.Bodies.BodyExists(bottle.BodyHandle) ||
+ !_simulationManager.simulation.Bodies.BodyExists(transport.BodyHandle))
+ return;
+
+ // Asegurar que DirectionVector esté inicializado
+ transport.UpdateCachedProperties();
+
+ // Validar que DirectionVector no sea cero
+ if (transport.DirectionVector.Length() < 0.001f)
+ return;
+
+ // ✅ SIMPLIFICAR - Usar LinearAxisMotor básico
+ var motor = new LinearAxisMotor()
+ {
+ LocalOffsetA = Vector3.Zero,
+ LocalOffsetB = Vector3.Zero,
+ LocalAxis = transport.DirectionVector,
+ TargetVelocity = transport.SpeedMetersPerSecond,
+ Settings = new MotorSettings(Math.Max(bottle.Mass * 30f, 10f), 5f)
+ };
+
+ var motorHandle = _simulationManager.simulation.Solver.Add(
+ transport.BodyHandle,
+ bottle.BodyHandle,
+ motor
+ );
+
+ _activeMotors[bottle.BodyHandle] = motorHandle;
+ _motorToBottle[motorHandle] = bottle;
+ bottle.AssignToTransport(transport, motorHandle);
+ transport.BottlesOnTransport.Add(bottle);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[MotorManager] Error creating transport motor: {ex.Message}");
+ }
+ }
+
+ public void CreateCurveMotor(simBotella bottle, simCurve curve)
+ {
+ try
+ {
+ // ✅ VALIDACIONES CRÍTICAS
+ if (bottle == null || curve == null || _simulationManager?.simulation == null)
+ return;
+
+ // Validar que los BodyHandle existan
+ if (!_simulationManager.simulation.Bodies.BodyExists(bottle.BodyHandle) ||
+ !_simulationManager.simulation.Bodies.BodyExists(curve.BodyHandle))
+ return;
+
+ var direction = curve.GetCachedTangentialDirection(bottle.GetPosition());
+
+ // Validar que la dirección no sea cero
+ if (direction.Length() < 0.001f)
+ return;
+
+ // ✅ USAR CUERPO DE LA CURVA EN LUGAR DE REFERENCIA ESTÁTICA
+ var motor = new LinearAxisMotor()
+ {
+ LocalOffsetA = Vector3.Zero,
+ LocalOffsetB = Vector3.Zero,
+ LocalAxis = direction,
+ TargetVelocity = curve.SpeedMetersPerSecond,
+ Settings = new MotorSettings(Math.Max(bottle.Mass * 20f, 8f), 4f)
+ };
+
+ var motorHandle = _simulationManager.simulation.Solver.Add(
+ curve.BodyHandle, // ✅ USAR CUERPO DE LA CURVA
+ bottle.BodyHandle,
+ motor
+ );
+
+ _activeMotors[bottle.BodyHandle] = motorHandle;
+ _motorToBottle[motorHandle] = bottle;
+ bottle.CurrentMotor = motorHandle;
+ curve.BottlesOnCurve.Add(bottle);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[MotorManager] Error creating curve motor: {ex.Message}");
+ }
+ }
+
+ public void RemoveMotor(simBotella bottle)
+ {
+ try
+ {
+ if (bottle == null || _simulationManager?.simulation == null)
+ return;
+
+ if (_activeMotors.TryGetValue(bottle.BodyHandle, out var motorHandle))
{
- // El amortiguamiento extra se aplica globalmente,
- // las fuerzas específicas se manejan en ApplyBrakeForces del NarrowPhaseCallbacks
- velocity.Linear *= brakeDampingWide;
+ // Validar que el solver existe antes de remover
+ if (_simulationManager.simulation.Solver != null)
+ {
+ _simulationManager.simulation.Solver.Remove(motorHandle);
+ }
+
+ _activeMotors.Remove(bottle.BodyHandle);
+ _motorToBottle.Remove(motorHandle);
+
+ // Limpiar referencias de forma segura
+ bottle.CurrentTransport?.BottlesOnTransport.Remove(bottle);
+ bottle.RemoveFromTransport();
}
}
catch (Exception ex)
{
- // Error during brake force integration
+ System.Diagnostics.Debug.WriteLine($"[MotorManager] Error removing motor: {ex.Message}");
+ }
+ }
+
+ public void UpdateTransportSpeed(simTransporte transport)
+ {
+ // TODO: Implementar actualización de velocidad cuando se resuelva la API correcta
+ // Por ahora, recreamos los motores como alternativa
+ foreach (var bottle in transport.BottlesOnTransport.ToList())
+ {
+ if (_activeMotors.ContainsKey(bottle.BodyHandle))
+ {
+ RemoveMotor(bottle);
+ CreateTransportMotor(bottle, transport);
+ }
+ }
+ }
+
+ public void UpdateCurveSpeed(simCurve curve)
+ {
+ // ✅ Las curvas requieren recrear motores cuando cambia la velocidad
+ // Esto es porque la dirección tangencial puede cambiar y es más eficiente recrear
+ foreach (var bottle in curve.BottlesOnCurve.ToList())
+ {
+ if (_activeMotors.ContainsKey(bottle.BodyHandle))
+ {
+ RemoveMotor(bottle);
+ CreateCurveMotor(bottle, curve);
+ }
+ }
+ }
+
+ public void UpdateCurveMotorDirection(simBotella bottle, simCurve curve)
+ {
+ // TODO: Implementar actualización de dirección cuando se resuelva la API correcta
+ // Por ahora, recreamos el motor como alternativa para curvas
+ if (_activeMotors.ContainsKey(bottle.BodyHandle))
+ {
+ RemoveMotor(bottle);
+ CreateCurveMotor(bottle, curve);
+ }
+ }
+
+ ///
+ /// ✅ NUEVO: Elimina todos los motors conectados a un body específico (transportes y curvas)
+ ///
+ public void RemoveMotorsByBodyHandle(BodyHandle bodyHandle)
+ {
+ try
+ {
+ if (_simulationManager?.simulation?.Solver == null)
+ return;
+
+ // Encontrar todos los motors que involucran este body
+ var motorsToRemove = new List<(BodyHandle bottleHandle, ConstraintHandle motorHandle)>();
+
+ foreach (var kvp in _activeMotors)
+ {
+ var bottleHandle = kvp.Key;
+ var motorHandle = kvp.Value;
+
+ // Verificar si esta botella tiene un motor conectado al body que se va a eliminar
+ if (_motorToBottle.TryGetValue(motorHandle, out var bottle))
+ {
+ bool shouldRemove = false;
+
+ // Verificar transporte
+ if (bottle.CurrentTransport?.BodyHandle.Equals(bodyHandle) == true)
+ {
+ shouldRemove = true;
+ }
+
+ // Verificar curvas (buscar en el cuerpo principal y triángulos)
+ if (!shouldRemove)
+ {
+ var curves = _simulationManager.Cuerpos.OfType();
+ foreach (var curve in curves)
+ {
+ // Verificar cuerpo principal de la curva
+ if (curve.BodyHandle.Equals(bodyHandle))
+ {
+ shouldRemove = true;
+ break;
+ }
+
+ // Verificar triángulos de la curva
+ if (curve._triangleBodyHandles != null &&
+ curve._triangleBodyHandles.Contains(bodyHandle))
+ {
+ shouldRemove = true;
+ break;
+ }
+ }
+ }
+
+ if (shouldRemove)
+ {
+ motorsToRemove.Add((bottleHandle, motorHandle));
+ }
+ }
+ }
+
+ // Eliminar todos los motors encontrados
+ foreach (var (bottleHandle, motorHandle) in motorsToRemove)
+ {
+ try
+ {
+ _simulationManager.simulation.Solver.Remove(motorHandle);
+ _activeMotors.Remove(bottleHandle);
+
+ if (_motorToBottle.TryGetValue(motorHandle, out var bottle))
+ {
+ bottle.RemoveFromTransport(); // Esto limpia tanto transporte como curva
+ _motorToBottle.Remove(motorHandle);
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[MotorManager] Error removing specific motor for body {bodyHandle}: {ex.Message}");
+ }
+ }
+
+ System.Diagnostics.Debug.WriteLine($"[MotorManager] Removed {motorsToRemove.Count} motors connected to body {bodyHandle}");
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[MotorManager] Error in RemoveMotorsByBodyHandle: {ex.Message}");
+ }
+ }
+
+
+
+ public void Clear()
+ {
+ try
+ {
+ if (_simulationManager?.simulation?.Solver != null)
+ {
+ foreach (var motorHandle in _activeMotors.Values)
+ {
+ try
+ {
+ _simulationManager.simulation.Solver.Remove(motorHandle);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[MotorManager] Error removing motor during clear: {ex.Message}");
+ }
+ }
+ }
+ _activeMotors.Clear();
+ _motorToBottle.Clear();
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[MotorManager] Error during clear: {ex.Message}");
}
}
}
@@ -1491,16 +1947,31 @@ namespace CtrEditor.Simulacion
// Propiedad para controlar si la actualización 3D está habilitada
public bool Is3DUpdateEnabled { get; set; } = true;
- // Sistema de contactos de transporte para aplicar fuerzas
- private Dictionary _transportContacts;
- // Sistema de contactos de barrera para detección de paso
+ // ✅ NUEVAS - para filtrado eficiente de callbacks
+ private HashSet _transportHandles;
+ private HashSet _curveHandles;
+ private HashSet _barrierHandles;
+ private HashSet _discardHandles;
+ private HashSet _bottleHandles;
+
+ // ✅ NUEVO - sistema de motores
+ private MotorManager _motorManager;
+
+ // ✅ NUEVO - tabla de exclusiones para callbacks silenciados
+ private HashSet<(BodyHandle bottle, BodyHandle transport)> _silencedPairs;
+
+ // ✅ NUEVO - contador de frames para optimizaciones
+ private int _frameCount = 0;
+
+ // ✅ CONSERVAR - sistemas existentes que funcionan bien
private Dictionary> _barreraContacts;
- // Sistema de contactos de descarte para marcar botellas para eliminación
private Dictionary> _descarteContacts;
- // Lista de botellas marcadas para eliminación
private HashSet _botellasParaEliminar;
- // Sistema de contactos de transporte con freno para marcar botellas
- private Dictionary _brakeTransportContacts;
+
+ // ✅ ELIMINAR COMPLETAMENTE
+ // private Dictionary _transportContacts; // NO SE USA MÁS
+ // private Dictionary _brakeTransportContacts; // NO SE USA MÁS
+
private object _contactsLock = new object();
///
@@ -1532,15 +2003,121 @@ namespace CtrEditor.Simulacion
return simBase as simCurve;
}
+ // ✅ NUEVOS - gestión automática de clasificación
+ private void RegisterObjectHandle(simBase obj)
+ {
+ switch (obj)
+ {
+ case simBotella bottle:
+ _bottleHandles.Add(bottle.BodyHandle);
+ break;
+ case simTransporte transport:
+ _transportHandles.Add(transport.BodyHandle);
+ // Suscribirse a cambios de velocidad
+ transport.OnSpeedChanged += _motorManager.UpdateTransportSpeed;
+ // Calcular propiedades iniciales
+ transport.UpdateCachedProperties();
+ break;
+ case simCurve curve:
+ _curveHandles.Add(curve.BodyHandle);
+ // También añadir todos sus triángulos
+ if (curve._triangleBodyHandles != null)
+ {
+ foreach (var triangleHandle in curve._triangleBodyHandles)
+ {
+ _curveHandles.Add(triangleHandle);
+ }
+ }
+ curve.OnSpeedChanged += _motorManager.UpdateCurveSpeed;
+ break;
+ case simBarrera barrier:
+ _barrierHandles.Add(barrier.BodyHandle);
+ break;
+ case simDescarte discard:
+ _discardHandles.Add(discard.BodyHandle);
+ break;
+ }
+ }
+
+ private void UnregisterObjectHandle(simBase obj)
+ {
+ switch (obj)
+ {
+ case simBotella bottle:
+ _bottleHandles.Remove(bottle.BodyHandle);
+ // Eliminar motor si existe
+ _motorManager.RemoveMotor(bottle);
+ // Eliminar de pares silenciados
+ RemoveFromSilencedPairs(bottle.BodyHandle);
+ break;
+ case simTransporte transport:
+ _transportHandles.Remove(transport.BodyHandle);
+ transport.OnSpeedChanged -= _motorManager.UpdateTransportSpeed;
+ break;
+ case simCurve curve:
+ _curveHandles.Remove(curve.BodyHandle);
+ if (curve._triangleBodyHandles != null)
+ {
+ foreach (var triangleHandle in curve._triangleBodyHandles)
+ {
+ _curveHandles.Remove(triangleHandle);
+ }
+ }
+ curve.OnSpeedChanged -= _motorManager.UpdateCurveSpeed;
+ break;
+ case simBarrera barrier:
+ _barrierHandles.Remove(barrier.BodyHandle);
+ break;
+ case simDescarte discard:
+ _discardHandles.Remove(discard.BodyHandle);
+ break;
+ }
+ }
+
+ private void RemoveFromSilencedPairs(BodyHandle bottleHandle)
+ {
+ _silencedPairs.RemoveWhere(pair => pair.bottle == bottleHandle);
+ }
+
+ // ✅ NUEVOS MÉTODOS - obtener objetos por handle
+ private simBotella GetBottleByHandle(BodyHandle handle)
+ {
+ return Cuerpos.OfType().FirstOrDefault(b => b.BodyHandle.Equals(handle));
+ }
+
+ private simTransporte GetTransportByHandle(BodyHandle handle)
+ {
+ return Cuerpos.OfType().FirstOrDefault(t => t.BodyHandle.Equals(handle));
+ }
+
+ private simCurve GetCurveByHandle(BodyHandle handle)
+ {
+ // Primero buscar en cuerpos principales
+ var curve = Cuerpos.OfType().FirstOrDefault(c => c.BodyHandle.Equals(handle));
+ if (curve != null) return curve;
+
+ // Luego buscar en triángulos
+ return GetCurveFromTriangleBodyHandle(handle);
+ }
+
public SimulationManagerBEPU()
{
+ // ✅ EXISTENTES - conservar
Cuerpos = new List();
_deferredActions = new List();
- _transportContacts = new Dictionary();
_barreraContacts = new Dictionary>();
_descarteContacts = new Dictionary>();
_botellasParaEliminar = new HashSet();
- _brakeTransportContacts = new Dictionary();
+
+ // ✅ NUEVOS - sistemas de filtrado y motores
+ _transportHandles = new HashSet();
+ _curveHandles = new HashSet();
+ _barrierHandles = new HashSet();
+ _discardHandles = new HashSet();
+ _bottleHandles = new HashSet();
+ _silencedPairs = new HashSet<(BodyHandle, BodyHandle)>();
+
+ // ✅ CONSERVAR - resto del constructor igual
bufferPool = new BufferPool();
stopwatch = new Stopwatch();
@@ -1560,36 +2137,41 @@ namespace CtrEditor.Simulacion
var solveDescription = new SolveDescription(8, 1);
simulation = Simulation.Create(bufferPool, narrowPhaseCallbacks, poseIntegratorCallbacks, solveDescription);
+
+ // ✅ CREAR MOTOR MANAGER DESPUÉS DE LA SIMULACIÓN
+ _motorManager = new MotorManager(this);
}
public void Clear()
{
try
{
- // ✅ NUEVO: Restaurar masas originales de todas las botellas antes de limpiar
- foreach (var cuerpo in Cuerpos.OfType())
- {
- try
- {
- cuerpo.RestoreOriginalMassIfNeeded();
- }
- catch (Exception ex)
- {
- // Error restoring mass during clear - continue
- }
- }
-
- // Limpiar contactos primero para evitar referencias colgantes
+ // ✅ NUEVO - limpiar motor manager
+ _motorManager?.Clear();
+
+ // ✅ SIMPLIFICAR - eliminar lógica de masa especial
+ // foreach (var cuerpo in Cuerpos.OfType())
+ // {
+ // cuerpo.RestoreOriginalMassIfNeeded(); // NO SE USA MÁS
+ // }
+
+ // ✅ CONSERVAR - limpiar contactos
lock (_contactsLock)
{
- _transportContacts.Clear();
_barreraContacts.Clear();
_descarteContacts.Clear();
_botellasParaEliminar.Clear();
- _brakeTransportContacts.Clear();
+ _silencedPairs.Clear(); // ✅ NUEVO
}
+
+ // ✅ NUEVO - limpiar clasificaciones
+ _transportHandles.Clear();
+ _curveHandles.Clear();
+ _barrierHandles.Clear();
+ _discardHandles.Clear();
+ _bottleHandles.Clear();
- // Remover cuerpos de forma segura
+ // ✅ CONSERVAR - resto del método igual
var cuerposToRemove = new List(Cuerpos);
foreach (var cuerpo in cuerposToRemove)
{
@@ -1608,6 +2190,9 @@ namespace CtrEditor.Simulacion
Cuerpos.Clear();
_deferredActions.Clear();
+
+ // ✅ NUEVO - Limpiar todas las dimensiones almacenadas
+ CtrEditor.ObjetosSim.osBase.ClearAllStoredDimensions();
// Limpiar la simulación completamente si existe
if (simulation != null)
@@ -1634,45 +2219,66 @@ namespace CtrEditor.Simulacion
public void Start()
{
- stopwatch.Start();
- stopwatch_last = stopwatch.Elapsed.TotalMilliseconds;
+ try
+ {
+ // ✅ INICIALIZAR PROPIEDADES CACHEADAS
+ foreach (var transport in Cuerpos.OfType())
+ {
+ transport.UpdateCachedProperties();
+ }
+
+ foreach (var curve in Cuerpos.OfType())
+ {
+ // ✅ CORREGIDO: Usar método público para actualizar velocidad
+ curve.SetSpeed(curve.Speed); // Esto actualiza automáticamente SpeedMetersPerSecond internamente
+ }
+
+ stopwatch.Start();
+ stopwatch_last = stopwatch.Elapsed.TotalMilliseconds;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[SimulationManager] Error in Start: {ex.Message}");
+ stopwatch.Start();
+ stopwatch_last = stopwatch.Elapsed.TotalMilliseconds;
+ }
}
public void Step()
{
try
{
+ _frameCount++;
+
+ // ✅ CONSERVAR - validación de deltaTime
var currentTime = stopwatch.Elapsed.TotalMilliseconds;
var deltaTime = (float)((currentTime - stopwatch_last) / 1000.0);
stopwatch_last = currentTime;
- // Validar deltaTime para evitar valores problemáticos
if (float.IsNaN(deltaTime) || float.IsInfinity(deltaTime) || deltaTime <= 0)
{
- deltaTime = 1f / 60f; // Fallback a 60 FPS
+ deltaTime = 1f / 60f;
}
- // Limitar deltaTime a 100ms máximo para evitar saltos tras pausas del debugger
- const float maxDeltaTime = 0.1f; // 100ms
+ const float maxDeltaTime = 0.1f;
if (deltaTime > maxDeltaTime)
{
- deltaTime = 1f / 60f; // Resetear a 60 FPS estándar si excede el límite
+ deltaTime = 1f / 60f;
}
- // Procesar acciones diferidas
+ // ✅ CONSERVAR - procesar acciones diferidas
foreach (var action in _deferredActions)
{
action();
}
_deferredActions.Clear();
- // Validar que la simulación esté en buen estado antes del timestep
+ // ✅ CONSERVAR - validaciones de simulación
if (simulation?.Bodies == null)
{
return;
}
- // Validar que no hay cuerpos con handles inválidos
var invalidBodies = Cuerpos.Where(c => c != null && !simulation.Bodies.BodyExists(c.BodyHandle)).ToList();
if (invalidBodies.Count > 0)
{
@@ -1682,8 +2288,8 @@ namespace CtrEditor.Simulacion
}
}
- // Paso de simulación (genera las colisiones/contactos)
- var timestepValue = Math.Max(deltaTime, 1f / 120f); // Mínimo 120 FPS para mejor estabilidad
+ // ✅ CONSERVAR - timestep
+ var timestepValue = Math.Max(deltaTime, 1f / 120f);
try
{
@@ -1691,32 +2297,31 @@ namespace CtrEditor.Simulacion
}
catch (AccessViolationException ex)
{
- // Limpiar contactos que podrían estar corruptos
lock (_contactsLock)
{
- // ✅ ELIMINADO: _transportContacts.Clear(); - Ya no se usa
_barreraContacts.Clear();
}
- throw; // Re-lanzar para debug
+ throw;
}
- // ✅ ELIMINADO: ApplyTransportForces - Ahora se aplica directamente en ConfigureContactManifold
- // ApplyTransportForces(deltaTime); // ELIMINADO - Ahora en ConfigureContactManifold
+ // ✅ ELIMINAR COMPLETAMENTE
+ // ApplyTransportForces(deltaTime); // NO SE USA MÁS
+ // ProcessBrakeTransportContacts(); // NO SE USA MÁS
- // Procesar contactos de transporte con freno para marcar/desmarcar botellas
- ProcessBrakeTransportContacts();
+ // ✅ NUEVO - verificación periódica de salidas de transporte
+ CheckBottleExitsFromTransports();
- // Limitar rotaciones de botellas solo al plano XY (siempre "de pie")
- // Y GARANTIZAR que nunca entren en sleep mode
- foreach (var cuerpo in Cuerpos.OfType().ToList()) // ToList para evitar modificación durante iteración
+ // ✅ NUEVO - actualización periódica de direcciones de curvas
+ UpdateCurveMotorDirections();
+
+ // ✅ CONSERVAR - limitación de rotación y mantener botellas despiertas
+ foreach (var cuerpo in Cuerpos.OfType().ToList())
{
try
{
if (simulation.Bodies.BodyExists(cuerpo.BodyHandle))
{
cuerpo.LimitRotationToXYPlane();
-
- // FORZAR que la botella esté siempre despierta - doble seguridad
simulation.Awakener.AwakenBody(cuerpo.BodyHandle);
}
}
@@ -1726,25 +2331,19 @@ namespace CtrEditor.Simulacion
}
}
- // Procesar contactos de barrera para detección de paso
+ // ✅ CONSERVAR - sistemas que funcionan bien
ProcessBarreraContacts();
-
- // Procesar contactos de descarte para eliminación de botellas
ProcessDescarteContacts();
-
- // Procesar sistema de limpieza para eliminar botellas debajo de transportes
ProcessCleanupSystem();
- // Limpiar contactos DESPUÉS de usarlos
+ // ✅ SIMPLIFICAR - limpiar solo contactos que usamos
lock (_contactsLock)
{
- // ✅ ELIMINADO: _transportContacts.Clear(); - Ya no se usa
_barreraContacts.Clear();
_descarteContacts.Clear();
- _brakeTransportContacts.Clear();
}
- // Sincronizar con la visualización 3D solo si está habilitado
+ // ✅ CONSERVAR - sincronización 3D
if (Is3DUpdateEnabled)
{
Visualization3DManager?.SynchronizeWorld();
@@ -1752,38 +2351,88 @@ namespace CtrEditor.Simulacion
}
catch (Exception ex)
{
- // En caso de error crítico, limpiar contactos para evitar estado corrupto
lock (_contactsLock)
{
- // ✅ ELIMINADO: _transportContacts.Clear(); - Ya no se usa
_barreraContacts.Clear();
- _brakeTransportContacts.Clear();
+ }
+ }
+ }
+
+ private void CheckBottleExitsFromTransports()
+ {
+ // ✅ Ejecutar cada 10 frames para eficiencia
+ if (_frameCount % 10 != 0) return;
+
+ var activeBottles = Cuerpos.OfType()
+ .Where(b => b.HasActiveMotor && b.CurrentTransport != null)
+ .ToList();
+
+ foreach (var bottle in activeBottles)
+ {
+ if (!IsBottleOnTransport(bottle, bottle.CurrentTransport))
+ {
+ ProcessBottleExitsTransport(bottle);
+ }
+ }
+ }
+
+ private bool IsBottleOnTransport(simBotella bottle, simTransporte transport)
+ {
+ 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;
+ }
+
+ private void ProcessBottleExitsTransport(simBotella bottle)
+ {
+ UnsileceCurrentPair(bottle);
+ _motorManager.RemoveMotor(bottle);
+ }
+
+ private void UnsileceCurrentPair(simBotella bottle)
+ {
+ if (bottle.CurrentTransport != null)
+ {
+ _silencedPairs.Remove((bottle.BodyHandle, bottle.CurrentTransport.BodyHandle));
+ }
+ }
+
+ private void UpdateCurveMotorDirections()
+ {
+ // ✅ Ejecutar cada 5 frames para eficiencia
+ if (_frameCount % 5 != 0) return;
+
+ var curvesWithBottles = Cuerpos.OfType()
+ .Where(c => c.BottlesOnCurve.Count > 0)
+ .ToList();
+
+ foreach (var curve in curvesWithBottles)
+ {
+ foreach (var bottle in curve.BottlesOnCurve.ToList())
+ {
+ _motorManager.UpdateCurveMotorDirection(bottle, curve);
}
}
}
public void Remove(simBase Objeto)
{
- // ✅ NUEVO: Si es una botella con masa aumentada, restaurar masa original antes de eliminar
- if (Objeto is simBotella botella && botella.OriginalMass > 0)
- {
- try
- {
- botella.SetMass(botella.OriginalMass);
- botella.OriginalMass = 0;
- botella.isOnBrakeTransport = false;
- botella.CurrentBrakeTransport = null;
- }
- catch (Exception ex)
- {
- // Error restoring mass during removal - continue with removal
- }
- }
-
+ // ✅ SIMPLIFICADO - eliminar lógica de masa especial
+ UnregisterObjectHandle(Objeto); // ✅ NUEVO
+
+ // ✅ NUEVO - Limpiar dimensiones almacenadas en osBase
+ CtrEditor.ObjetosSim.osBase.ClearStoredDimensions(Objeto);
+
Objeto.RemoverBody();
Cuerpos.Remove(Objeto);
- // Actualizar visualización 3D tras eliminar objeto
if (Is3DUpdateEnabled)
{
Visualization3DManager?.SynchronizeWorld();
@@ -1794,26 +2443,25 @@ namespace CtrEditor.Simulacion
{
var botella = new simBotella(simulation, _deferredActions, diameter, position, mass);
Cuerpos.Add(botella);
+ RegisterObjectHandle(botella); // ✅ NUEVO
- // Sincronizar con la visualización 3D
if (Is3DUpdateEnabled)
{
Visualization3DManager?.SynchronizeWorld();
}
-
return botella;
}
public simTransporte AddRectangle(float width, float height, Vector2 position, float angle)
{
- var transporte = new simTransporte(simulation, _deferredActions, width, height, position, angle);
+ var transporte = new simTransporte(simulation, _deferredActions, width, height, position, angle, this); // ✅ PASAR REFERENCIA
Cuerpos.Add(transporte);
- // Sincronizar con la visualización 3D
+ RegisterObjectHandle(transporte); // ✅ NUEVO - incluye UpdateCachedProperties()
+
if (Is3DUpdateEnabled)
{
Visualization3DManager?.SynchronizeWorld();
}
-
return transporte;
}
@@ -1821,12 +2469,12 @@ namespace CtrEditor.Simulacion
{
var barrera = new simBarrera(simulation, _deferredActions, width, height, position, angle, detectarCuello);
Cuerpos.Add(barrera);
- // Sincronizar con la visualización 3D
+ RegisterObjectHandle(barrera); // ✅ NUEVO
+
if (Is3DUpdateEnabled)
{
Visualization3DManager?.SynchronizeWorld();
}
-
return barrera;
}
@@ -1836,12 +2484,12 @@ namespace CtrEditor.Simulacion
{
var guia = new simGuia(simulation, _deferredActions, width, height, topLeft, angle);
Cuerpos.Add(guia);
- // Sincronizar con la visualización 3D
+ // ✅ NOTA: simGuia no requiere registro especial
+
if (Is3DUpdateEnabled)
{
Visualization3DManager?.SynchronizeWorld();
}
-
return guia;
}
@@ -1849,28 +2497,36 @@ namespace CtrEditor.Simulacion
{
var descarte = new simDescarte(simulation, _deferredActions, diameter, position);
Cuerpos.Add(descarte);
- // Sincronizar con la visualización 3D
+ RegisterObjectHandle(descarte); // ✅ NUEVO
+
if (Is3DUpdateEnabled)
{
Visualization3DManager?.SynchronizeWorld();
}
-
return descarte;
}
public simCurve AddCurve(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 topLeft, float unused = 0)
{
- var curve = new simCurve(simulation, _deferredActions, innerRadius, outerRadius, startAngle, endAngle, topLeft, unused);
+ var curve = new simCurve(simulation, _deferredActions, innerRadius, outerRadius, startAngle, endAngle, topLeft, unused, this); // ✅ PASAR REFERENCIA
Cuerpos.Add(curve);
- // Sincronizar con la visualización 3D
+ RegisterObjectHandle(curve); // ✅ NUEVO
+
if (Is3DUpdateEnabled)
{
Visualization3DManager?.SynchronizeWorld();
}
-
return curve;
}
+ ///
+ /// ✅ NUEVO: Método público para limpiar motors conectados a un body específico
+ ///
+ public void RemoveMotorsConnectedToBody(BodyHandle bodyHandle)
+ {
+ _motorManager?.RemoveMotorsByBodyHandle(bodyHandle);
+ }
+
public void Dispose()
{
Clear();
@@ -1878,52 +2534,67 @@ namespace CtrEditor.Simulacion
bufferPool?.Clear();
}
+
+
+
+
///
- /// Registra un contacto entre una botella y un transporte para aplicar fuerzas
+ /// ✅ IMPLEMENTADO: Intenta crear un motor de transporte si no existe uno activo
///
- /// Botella en contacto
- /// Transporte en contacto
- public void RegisterTransportContact(simBotella botella, simTransporte transporte)
+ public void TryCreateTransportMotor(simBotella bottle, simTransporte transport)
{
- if (botella != null && transporte != null)
+ try
{
- lock (_contactsLock)
- {
- _transportContacts[botella] = transporte;
- }
+ // Validaciones básicas
+ if (bottle == null || transport == null || _motorManager == null)
+ return;
+
+ // Verificar si ya tiene un motor activo o está silenciado
+ if (bottle.HasActiveMotor)
+ return;
+
+ var pairKey = (bottle.BodyHandle, transport.BodyHandle);
+ if (_silencedPairs.Contains(pairKey))
+ return;
+
+ // Asegurar que el transporte tenga propiedades inicializadas
+ if (Math.Abs(transport.Speed) < 0.001f)
+ return; // No crear motor para transportes detenidos
+
+ _motorManager.CreateTransportMotor(bottle, transport);
+ _silencedPairs.Add(pairKey);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[SimulationManager] Error in TryCreateTransportMotor: {ex.Message}");
}
}
///
- /// Registra un contacto entre una botella y un transporte con freno para marcar la botella
- /// ✅ NUEVO: También aumenta la masa de la botella 10x para simular conexión mecánica
+ /// ✅ IMPLEMENTADO: Intenta crear un motor de curva si no existe uno activo
///
- /// Botella en contacto
- /// Transporte con freno en contacto
- public void RegisterBrakeTransportContact(simBotella botella, simTransporte transporte)
+ public void TryCreateCurveMotor(simBotella bottle, simCurve curve)
{
- if (botella != null && transporte != null && transporte.isBrake)
+ try
{
- lock (_contactsLock)
- {
- _brakeTransportContacts[botella] = transporte;
- // ✅ NUEVO: Mantener referencia directa en la botella
- botella.CurrentBrakeTransport = transporte;
- botella.ConveyorRestrictedTo = transporte;
-
- // ✅ NUEVO: Aumentar masa 10x para simular conexión mecánica con el transporte
- if (!botella.isOnBrakeTransport) // Solo si no estaba ya en un transporte con freno
- {
- // Guardar masa original si no está guardada ya
- if (botella.OriginalMass <= 0)
- {
- botella.OriginalMass = botella.Mass;
- }
-
- // Aumentar masa 10 veces para simular "enganche" mecánico
- botella.SetMass(botella.OriginalMass * 100f);
- }
- }
+ // Validaciones básicas
+ if (bottle == null || curve == null || _motorManager == null)
+ return;
+
+ // Verificar si ya tiene un motor activo
+ if (bottle.HasActiveMotor)
+ return;
+
+ // Verificar que la curva tenga velocidad
+ if (Math.Abs(curve.Speed) < 0.001f)
+ return; // No crear motor para curvas detenidas
+
+ // ✅ Para curvas no silenciamos porque la dirección cambia constantemente
+ _motorManager.CreateCurveMotor(bottle, curve);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[SimulationManager] Error in TryCreateCurveMotor: {ex.Message}");
}
}
@@ -1980,226 +2651,7 @@ namespace CtrEditor.Simulacion
}
}
- ///
- /// Aplica fuerzas de transporte a las botellas en contacto
- ///
- /// Tiempo transcurrido desde el último paso
- private void ApplyTransportForces(float deltaTime)
- {
- try
- {
- // Validar deltaTime
- if (float.IsNaN(deltaTime) || float.IsInfinity(deltaTime) || deltaTime <= 0)
- return;
- lock (_contactsLock)
- {
- // Crear copia de contactos para evitar modificación durante iteración
- var contactsCopy = new Dictionary(_transportContacts);
-
- foreach (var contact in contactsCopy)
- {
- try
- {
- var botella = contact.Key;
- var transporte = contact.Value;
-
- // Validar objetos
- if (botella == null || transporte == null || simulation?.Bodies == null)
- continue;
-
- // Verificar que ambos objetos aún existen
- if (!simulation.Bodies.BodyExists(botella.BodyHandle) || !simulation.Bodies.BodyExists(transporte.BodyHandle))
- continue;
-
- // Solo aplicar si hay velocidad significativa
- if (Math.Abs(transporte.Speed) <= 0.001f)
- continue;
-
- // Determinar si es una botella en transporte con freno (guías laterales)
- bool isOnBrakeTransport = botella.isOnBrakeTransport;
- float forceMultiplier = isOnBrakeTransport ? 3.0f : 1.0f; // 3x más fuerte para botellas con guías
-
- // Aplicar fuerzas de movimiento (longitudinales) con control de velocidad
- ApplyLongitudinalForces(botella, transporte, deltaTime, forceMultiplier, isOnBrakeTransport);
-
- }
- catch (Exception ex)
- {
- // Error processing contact - continue
- }
- }
- }
- }
- catch (Exception ex)
- {
- // Critical error in ApplyTransport - continue
- }
- }
-
- ///
- /// Aplica fuerzas longitudinales para el movimiento del transporte con control de velocidad
- ///
- private void ApplyLongitudinalForces(simBotella botella, simTransporte transporte, float deltaTime, float forceMultiplier, bool isOnBrakeTransport)
- {
- try
- {
- // Calcular la dirección del transporte de forma segura
- var angle = transporte.GetRotationZ();
-
- // Validar ángulo
- if (float.IsNaN(angle) || float.IsInfinity(angle))
- return;
-
- var cos = (float)Math.Cos(angle);
- var sin = (float)Math.Sin(angle);
-
- // Validar valores trigonométricos
- if (float.IsNaN(cos) || float.IsNaN(sin))
- return;
-
- // Vector de dirección del transporte (en el plano X-Y)
- var transportDirection = new Vector3(cos, sin, 0);
-
- // Calcular la fuerza proporcional a la velocidad deseada
- var targetVelocity = transportDirection * transporte.Speed / 18.5f;
- var currentVelocity = botella.GetLinearVelocity();
-
- // Validar velocidades
- if (float.IsNaN(currentVelocity.X) || float.IsNaN(currentVelocity.Y) || float.IsNaN(currentVelocity.Z))
- return;
-
- // Solo aplicar fuerza en el plano horizontal (X-Y)
- var horizontalVelocity = new Vector3(currentVelocity.X, currentVelocity.Y, 0);
-
- // Para transportes con freno, limitar la velocidad máxima de la botella
- if (isOnBrakeTransport)
- {
- var transportSpeed = Math.Abs(transporte.Speed / 18.5f);
- var currentSpeed = horizontalVelocity.Length();
-
- // Si la botella va más rápido que el transporte, limitar su velocidad
- if (currentSpeed > transportSpeed * 1f) // 10% de tolerancia
- {
- var limitedVelocity = Vector3.Normalize(horizontalVelocity) * transportSpeed;
- botella.ApplyLinearVelocity(new Vector3(limitedVelocity.X, limitedVelocity.Y, currentVelocity.Z));
- horizontalVelocity = limitedVelocity;
- }
- }
-
- var velocityDiff = targetVelocity - horizontalVelocity;
-
- // Validar diferencia de velocidad
- if (float.IsNaN(velocityDiff.X) || float.IsNaN(velocityDiff.Y) || float.IsNaN(velocityDiff.Z))
- return;
-
- // Aplicar una fuerza proporcional a la diferencia de velocidad
- var baseForceMagnitude = velocityDiff.Length() * botella.Mass * 10f; // Factor base
- var forceMagnitude = baseForceMagnitude * forceMultiplier; // Aplicar multiplicador
-
- // Validar magnitud de fuerza
- if (float.IsNaN(forceMagnitude) || float.IsInfinity(forceMagnitude))
- return;
-
- if (forceMagnitude > 0.01f)
- {
- var maxForce = botella.Mass * 50f * forceMultiplier; // Límite también escalado
- var force = Vector3.Normalize(velocityDiff) * Math.Min(forceMagnitude, maxForce);
-
- // Validar fuerza final
- if (float.IsNaN(force.X) || float.IsNaN(force.Y) || float.IsNaN(force.Z))
- return;
-
- var bodyReference = simulation.Bodies.GetBodyReference(botella.BodyHandle);
- bodyReference.ApplyLinearImpulse(force * deltaTime);
- }
- }
- catch (Exception ex)
- {
- // Error in ApplyLongitudinalForces - continue
- }
- }
-
-
- ///
- /// Procesa contactos de transporte con freno para marcar/desmarcar botellas
- /// ✅ NUEVO: También gestiona el cambio de masa - restaura masa original al salir del transporte
- ///
- private void ProcessBrakeTransportContacts()
- {
- try
- {
- // Primero, recopilar botellas que estaban en transporte con freno para detectar cambios
- var todasLasBotellas = Cuerpos.OfType().ToList();
- var botellasQueEstabanEnBrakeTransport = new HashSet();
-
- foreach (var botella in todasLasBotellas)
- {
- if (botella != null && botella.isOnBrakeTransport)
- {
- botellasQueEstabanEnBrakeTransport.Add(botella);
- botella.isOnBrakeTransport = false; // Asumir que no están en contacto
- }
- }
-
- // Luego, marcar las botellas que SÍ están en contacto con transportes de freno
- lock (_contactsLock)
- {
- var contactsCopy = new Dictionary(_brakeTransportContacts);
-
- foreach (var contact in contactsCopy)
- {
- try
- {
- var botella = contact.Key;
- var transporte = contact.Value;
-
- // Validar objetos
- if (botella == null || transporte == null || simulation?.Bodies == null)
- continue;
-
- // Verificar que ambos objetos aún existen
- if (!simulation.Bodies.BodyExists(botella.BodyHandle) || !simulation.Bodies.BodyExists(transporte.BodyHandle))
- continue;
-
- // Verificar que el transporte sigue teniendo freno activado
- if (transporte.isBrake)
- {
- botella.isOnBrakeTransport = true;
- // Quitar de la lista de botellas que estaban en brake transport
- botellasQueEstabanEnBrakeTransport.Remove(botella);
- }
- }
- catch (Exception ex)
- {
- // Error processing brake transport contact - continue
- }
- }
- }
-
- // ✅ NUEVO: Restaurar masa original de botellas que salieron del transporte con freno
- foreach (var botella in botellasQueEstabanEnBrakeTransport)
- {
- try
- {
- if (botella.OriginalMass > 0) // Solo si tenemos masa original guardada
- {
- botella.SetMass(botella.OriginalMass);
- botella.OriginalMass = 0; // Limpiar para evitar confusión
- botella.CurrentBrakeTransport = null; // Limpiar referencia
- }
- }
- catch (Exception ex)
- {
- // Error restoring original mass - continue
- }
- }
- }
- catch (Exception ex)
- {
- // Critical error in ProcessBrakeTransportContacts - continue
- }
- }
///
/// Procesa todas las barreras usando detección geométrica pura