using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; using System.Windows; using System.Diagnostics; using System; using System.Collections.Generic; using System.Numerics; using System.Linq; using BepuPhysics; using BepuPhysics.Collidables; using BepuPhysics.CollisionDetection; using BepuPhysics.Constraints; using BepuUtilities; using BepuUtilities.Memory; 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 // ✅ CORREGIDO: Restaurar factor de conversión correcto public const float SPEED_CONVERSION_FACTOR = 1/2f; // Factor de conversión de velocidad interna a m/s - Para LinearAxisMotor es 0.5f // 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 public const float zPos_Guia = 0.05f; // Z de la parte baja public const float zAltura_Guia = 0.20f; // Altura de la guía sobre zPos public const float zAltura_Barrera = zAltura_Guia; // Altura de la barrera sobre zPos public const float zPos_Barrera = zPos_Guia; // Z de la parte baja public const float zPos_Descarte = 0.1f; // Z de la parte baja // Constantes para configuración public const float zPos_Curve = zPos_Transporte; // Z de la parte baja de la curva public const float zAltura_Curve = zAltura_Transporte; // Altura de la curva (triángulos planos) public void RemoverBody() { // Solo intentar remover si realmente hemos creado un cuerpo antes if (_bodyCreated && _simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) { _simulation.Bodies.Remove(BodyHandle); _bodyCreated = false; // Marcar como no creado después de remover } } /// /// ✅ 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); } public static float GradosARadianes(float grados) { return grados * (float)Math.PI / 180f; } public static float RadianesAGrados(float radianes) { return radianes * 180f / (float)Math.PI; } public void SetPosition(float x, float y, float z = 0) { CoordinateConverter.UpdateBepuBodyPosition(_simulation, BodyHandle, new Vector3(x, y, z)); } public void SetPosition(Vector2 wpfPosition) { // Mantener la coordenada Z actual para preservar la altura del objeto 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 bepuPosition) { CoordinateConverter.UpdateBepuBodyPosition(_simulation, BodyHandle, bepuPosition); } public Vector3 GetPosition() { return CoordinateConverter.GetBepuBodyPosition(_simulation, BodyHandle); } public void SetRotation(float wpfAngle) { CoordinateConverter.UpdateBepuBodyRotation(_simulation, BodyHandle, wpfAngle); } public float GetRotationZ() { return CoordinateConverter.GetWpfAngleFromBepuBody(_simulation, BodyHandle); } } public class simTransporte : simBase { public float Speed { get; set; } // Velocidad para efectos de cinta transportadora (m/s) public float Friction { get; set; } // Friccion para efectos de cinta transportadora public float DistanceGuide2Guide { get; set; } public bool isBrake { get; set; } public bool TransportWithGuides = false; private List _deferredActions; public float Width { get; set; } public float Height { get; set; } // ✅ 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 { get { return GetRotationZ(); } set { SetRotation(value); } } /// /// ✅ SOBRESCRITO: SetRotation que actualiza automáticamente las propiedades cacheadas /// public new void SetRotation(float wpfAngle) { base.SetRotation(wpfAngle); // ✅ CRÍTICO: Actualizar propiedades cacheadas 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) { base.SetPosition(x, y, z); } /// /// ✅ NUEVO: Actualiza posición desde Top-Left WPF con dimensiones y ángulo actuales /// internal 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 /// internal 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 /// internal 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); // ✅ CRÍTICO: Actualizar propiedades cacheadas 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 internal void UpdateCachedProperties() { // ✅ CORREGIDO: La dirección siempre es UnitX rotado por el ángulo del transporte // NO depende de las dimensiones (Width >= Height) sino solo de la rotación if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) { var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle); var bepuQuaternion = bodyReference.Pose.Orientation; // ✅ SIEMPRE usar UnitX y aplicar la rotación DirectionVector = Vector3.Transform(Vector3.UnitX, bepuQuaternion); // 🔍 DEBUG: Agregar información detallada var wpfAngle = GetRotationZ(); System.Diagnostics.Debug.WriteLine($"[UpdateCached] WPF Angle: {wpfAngle}°, DirectionVector: {DirectionVector}, Length: {DirectionVector.Length()}"); } else { // ✅ CORREGIDO: Aplicar conversión de coordenadas WPF→BEPU en el vector var wpfAngle = GetRotationZ(); // Ángulo WPF en grados var wpfAngleRadians = GradosARadianes(wpfAngle); // Calcular el vector en coordenadas WPF var wpfX = (float)Math.Cos(wpfAngleRadians); var wpfY = (float)Math.Sin(wpfAngleRadians); // ✅ APLICAR CONVERSIÓN Y: En WPF Y+ es abajo, en BEPU Y+ es arriba DirectionVector = new Vector3(wpfX, -wpfY, 0); // Invertir Y para conversión WPF→BEPU // 🔍 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()}"); } 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); } /// /// Configura la velocidad del transporte en metros por segundo /// Valores positivos mueven en la dirección del transporte, negativos en dirección opuesta /// /// Velocidad en m/s (típicamente entre -5.0 y 5.0) public void SetTransportSpeed(float speedMeterPerSecond) { SetSpeed(speedMeterPerSecond * SPEED_CONVERSION_FACTOR); } /// /// Detiene completamente el transporte /// public void StopTransport() { SetSpeed(0f); } /// /// Invierte la dirección del transporte manteniendo la misma velocidad /// public void ReverseTransport() { SetSpeed(-Speed); } public void SetDimensions(float width, float height) { Width = width; Height = height; // ✅ 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); ChangeBodyShape(shapeIndex); } // ✅ 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) { // ✅ 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); } /// /// ✅ 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(bepuPosition, CoordinateConverter.CreateBepuQuaternionFromWpfAngle(wpfAngle)), new CollidableDescription(shapeIndex, 0.1f), new BodyActivityDescription(0.01f) ); BodyHandle = _simulation.Bodies.Add(bodyDescription); _bodyCreated = true; // Marcar que hemos creado un cuerpo // ✅ CRÍTICO: Actualizar propiedades cacheadas después de crear el body UpdateCachedProperties(); } } public class simBarrera : simBase { public float Distancia; public bool LuzCortada; public bool LuzCortadaNeck; public bool DetectNeck; public List ListSimBotellaContact; float _height; private List _deferredActions; public float Width { get; set; } public float Height { get; set; } public simBarrera(Simulation simulation, List deferredActions, float width, float height, Vector2 topLeft, float angle = 0, bool detectectNeck = false) { _simulation = simulation; _deferredActions = deferredActions; Width = width; Height = height; _height = height; DetectNeck = detectectNeck; ListSimBotellaContact = new List(); // Usar el nuevo método Create que maneja Top-Left correctamente Create(width, height, topLeft, angle, detectectNeck); } public float Angle { get { return GetRotationZ(); } set { SetRotation(value); } } public new void SetPosition(float x, float y, float z = 0) { base.SetPosition(x, y, z); } /// /// ✅ NUEVO: Actualiza posición desde Top-Left WPF con dimensiones y ángulo actuales /// internal 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 /// internal 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 /// internal 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; // ✅ 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); ChangeBodyShape(shapeIndex); } } public void Create(float width, float height, Vector2 wpfTopLeft, float wpfAngle = 0, bool detectectNeck = false) { // ✅ 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 bepuPosition, float wpfAngle = 0, bool detectectNeck = false) { RemoverBody(); // Crear box con altura de 0.2m (desde z=0 hasta z=0.2) para funcionar como detector var box = new Box(width, height, zAltura_Barrera); // Altura de 20cm para detectar botellas completas var shapeIndex = _simulation.Shapes.Add(box); // Crear como SENSOR (Kinematic con speculative margin 0 para detección pura) // 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(bepuPosition, CoordinateConverter.CreateBepuQuaternionFromWpfAngle(wpfAngle)), new CollidableDescription(shapeIndex, 0f), // Speculative margin 0 para sensores activityDescription ); BodyHandle = _simulation.Bodies.Add(bodyDescription); _bodyCreated = true; // Marcar que hemos creado un cuerpo } } public class simGuia : simBase { private List _deferredActions; // Propiedades para acceder a las dimensiones del objeto WPF public float GuideThickness { get; set; } // Espesor de la guía public simGuia(Simulation simulation, List deferredActions, float width, float height, Vector2 topLeft, float angle) { _simulation = simulation; _deferredActions = deferredActions; GuideThickness = height; Create(width, height, topLeft, angle); } public void Create(float width, float height, Vector2 wpfTopLeft, float wpfAngle) { RemoverBody(); // ✅ 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); // ✅ USAR COORDINATECONVERTER para conversión centralizada var bodyDescription = BodyDescription.CreateKinematic( new RigidPose(bepuCenter, CoordinateConverter.CreateBepuQuaternionFromWpfAngle(wpfAngle)), new CollidableDescription(shapeIndex, 0.1f), new BodyActivityDescription(0.01f) ); BodyHandle = _simulation.Bodies.Add(bodyDescription); _bodyCreated = true; } // Método para actualizar las propiedades desde el objeto WPF public void UpdateProperties(float ancho, float altoGuia, float angulo) { // 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); } } /// /// ✅ CORREGIDO - Actualiza solo la posición sin recrear el objeto usando CoordinateConverter /// Requiere las dimensiones reales para conversión correcta Top-Left → Center /// public void SetPosition(Vector2 wpfTopLeft, float wpfAngle, float actualWidth, float actualHeight) { // ✅ USAR COORDINATECONVERTER para conversión centralizada con dimensiones reales var zPosition = zAltura_Guia / 2 + zPos_Guia; var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, actualWidth, actualHeight, wpfAngle, zPosition); // Actualizar posición y rotación simultáneamente CoordinateConverter.UpdateBepuBodyPose(_simulation, BodyHandle, bepuCenter, wpfAngle); } /// /// ✅ NUEVO: Actualiza tanto posición como rotación desde parámetros WPF (sobrecarga para compatibilidad) /// internal void UpdateFromWpfParameters(Vector2 wpfTopLeft, float wpfAngle, float width, float height) { // Usar el método SetPosition con dimensiones correctas SetPosition(wpfTopLeft, wpfAngle, width, height); // Actualizar propiedades internas GuideThickness = height; } /// /// ✅ LEGACY - Mantener compatibilidad con versión anterior (usar dimensiones almacenadas) /// public void SetPosition(Vector2 wpfTopLeft, float wpfAngle = 0) { // Fallback: usar GuideThickness como aproximación si no se proporcionan dimensiones SetPosition(wpfTopLeft, wpfAngle, GuideThickness * 10f, GuideThickness); } } public class simBotella : simBase { public float Radius; public float Height; // Altura para la visualización del cilindro en Helix private float _mass; public bool Descartar = false; public int isOnTransports; public List ListOnTransports; public bool isRestricted; public bool isNoMoreRestricted; 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) { _simulation = simulation; _deferredActions = deferredActions; Radius = diameter / 2f; Height = diameter; // Altura igual al diámetro para mantener proporciones similares _mass = mass; _neckRadius = neckRadius; ListOnTransports = new List(); // ✅ USAR COORDINATECONVERTER para conversión centralizada var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), Radius + zPos_Transporte + zAltura_Transporte); Create(position3D); } public float CenterX { get { return GetPosition().X; } set { var pos = GetPosition(); SetPosition(value, pos.Y, pos.Z); } } public float CenterY { get { // ✅ USAR COORDINATECONVERTER para conversión centralizada return CoordinateConverter.BepuYToWpfY(GetPosition().Y); } set { var pos = GetPosition(); // ✅ USAR COORDINATECONVERTER para conversión centralizada SetPosition(pos.X, CoordinateConverter.WpfYToBepuY(value), pos.Z); } } public Vector2 Center { get { var pos3D = GetPosition(); // ✅ USAR COORDINATECONVERTER para conversión centralizada return CoordinateConverter.BepuVector3ToWpfVector2(pos3D); } set { // Mantener la Z actual, solo cambiar X, Y var currentPos = GetPosition(); // ✅ USAR COORDINATECONVERTER para conversión centralizada SetPosition(value.X, CoordinateConverter.WpfYToBepuY(value.Y), currentPos.Z); } } public float Mass { get { if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) { var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle); return 1f / bodyReference.LocalInertia.InverseMass; } return _mass; } set { _mass = value; if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) { // Usar esfera simple - sin complejidad de inercia personalizada var sphere = new Sphere(Radius); var inertia = sphere.ComputeInertia(value); _simulation.Bodies.SetLocalInertia(BodyHandle, inertia); } } } private void Create(Vector3 position) { RemoverBody(); // Usar ESFERA en BEPU para simplicidad matemática y eficiencia var sphere = new Sphere(Radius); var shapeIndex = _simulation.Shapes.Add(sphere); // Inercia estándar de esfera - sin complejidad adicional var inertia = sphere.ComputeInertia(_mass); // NUNCA DORMIR - Valor negativo significa que las botellas JAMÁS entran en sleep mode // Esto es crítico para detección continua de barreras, transportes y descartes var activityDescription = new BodyActivityDescription(-1f); // -1 = NUNCA dormir var bodyDescription = BodyDescription.CreateDynamic( new RigidPose(position), new BodyVelocity(), inertia, // Inercia estándar de esfera new CollidableDescription(shapeIndex, 0.01f), activityDescription ); BodyHandle = _simulation.Bodies.Add(bodyDescription); _bodyCreated = true; // Marcar que hemos creado un cuerpo } public void SetDiameter(float diameter) { Radius = diameter / 2f; Height = diameter; // Mantener altura igual al diámetro para proporciones consistentes // ✅ 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); ChangeBodyShape(shapeIndex); } } public void SetHeight(float height) { Height = height; // ✅ 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); ChangeBodyShape(shapeIndex); } } public void SetMass(float mass) { Mass = mass; } /// /// Limita la rotación de la botella solo al plano XY (siempre "de pie") /// Esto simplifica enormemente la simulación y es más realista para botellas /// public void LimitRotationToXYPlane() { if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) { var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle); // Extraer solo la rotación en Z (plano XY) y eliminar las rotaciones en X e Y var currentOrientation = bodyReference.Pose.Orientation; // 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); // Aplicar la orientación corregida bodyReference.Pose.Orientation = correctedOrientation; // También limitar la velocidad angular a solo rotación en Z var angularVelocity = bodyReference.Velocity.Angular; bodyReference.Velocity.Angular = new Vector3(0, 0, angularVelocity.Z); } } public void ApplyLinearVelocity(Vector3 velocity) { if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) { var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle); bodyReference.Velocity.Linear = velocity; } } public Vector3 GetLinearVelocity() { if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) { var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle); return bodyReference.Velocity.Linear; } return Vector3.Zero; } public void CenterFixtureOnConveyor() { // Implementar lógica de centrado si es necesario } public bool IsOnAnyTransport() { return isOnTransports > 0; } // ✅ NUEVOS MÉTODOS - gestión de estado public void AssignToTransport(simTransporte transport, ConstraintHandle motor) { CurrentTransport = transport; CurrentMotor = motor; if (transport.isBrake) { isOnBrakeTransport = true; CurrentBrakeTransport = transport; } } public void RemoveFromTransport() { CurrentTransport = null; CurrentMotor = default; isOnBrakeTransport = false; CurrentBrakeTransport = null; } } public class simCurve : simBase { private float _innerRadius; private float _outerRadius; private float _startAngle; private float _endAngle; public float Speed { get; set; } // Velocidad para efectos de cinta transportadora (m/s) private List _deferredActions; 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 => RadianesAGrados(_startAngle); // Convertir de radianes BEPU internos a grados WPF public float EndAngle => RadianesAGrados(_endAngle); // Convertir de radianes BEPU internos a grados WPF /// /// ✅ NUEVO: Expone los triángulos originales para visualización debug /// 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, SimulationManagerBEPU simulationManager = null) { _simulation = simulation; _deferredActions = deferredActions; _simulationManager = simulationManager; // ✅ NUEVA REFERENCIA _innerRadius = innerRadius; _outerRadius = outerRadius; // ✅ CORREGIDO: Usar conversión WPF a BEPU y luego a radianes para consistencia _startAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(startAngle)); _endAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(endAngle)); _triangleBodyHandles = new List(); _originalTriangles = new List>(); // ✅ NUEVO: Inicializar lista de triángulos // Crear la curva con los ángulos que definen el sector 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) { _innerRadius = innerRadius; _outerRadius = outerRadius; // ✅ CORREGIDO: Usar conversión WPF a BEPU consistente _startAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(startAngle)); _endAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(endAngle)); // Recrear la curva con nuevos parámetros manteniendo posición actual var currentPosition = GetPosition(); Create(currentPosition); } /// /// ✅ NUEVO: Actualiza tanto posición como rotación desde parámetros WPF /// internal void UpdateFromWpfParameters(Vector2 wpfTopLeft, float wpfAngle, float innerRadius, float outerRadius, float startAngle, float endAngle) { // Actualizar parámetros de la curva _innerRadius = innerRadius; _outerRadius = outerRadius; // ✅ CORREGIDO: Usar conversión WPF a BEPU consistente _startAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(startAngle)); _endAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(endAngle)); // ✅ USAR COORDINATECONVERTER para conversión centralizada var curveSize = outerRadius * 2f; var zPosition = zAltura_Curve / 2f + zPos_Curve; var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition); Create(bepuCenter); } /// /// ✅ NUEVO: Actualiza posición desde Top-Left WPF manteniendo parámetros actuales /// internal void SetPositionFromWpfTopLeft(Vector2 wpfTopLeft) { var curveSize = _outerRadius * 2f; var zPosition = GetPosition().Z; // Mantener Z actual var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition); Create(bepuCenter); } /// /// ✅ NUEVO: Obtiene Top-Left WPF desde la posición actual /// internal Vector2 GetWpfTopLeft() { var bepuCenter = GetPosition(); var curveSize = _outerRadius * 2f; return CoordinateConverter.CalculateWpfTopLeftFromBepuCenter(bepuCenter, curveSize, curveSize, 0f); } 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) { foreach (var triangleHandle in _triangleBodyHandles) { if (_simulation.Bodies.BodyExists(triangleHandle)) { _simulation.Bodies.Remove(triangleHandle); } } _triangleBodyHandles.Clear(); } // ✅ NUEVO: Limpiar también los triángulos originales if (_originalTriangles != null) { _originalTriangles.Clear(); } // 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 wpfTopLeft, float unused = 0) { // ✅ CORRIGIDO: Como Aether - startAngle y endAngle definen directamente el sector // No hay rotación separada del objeto // Actualizar parámetros internos _innerRadius = innerRadius; _outerRadius = outerRadius; // ✅ CORREGIDO: Usar conversión WPF a BEPU consistente _startAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(startAngle)); _endAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(endAngle)); // ✅ USAR COORDINATECONVERTER para conversión centralizada // Para curvas, el "tamaño" es el diámetro del radio exterior var curveSize = outerRadius * 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 bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition); Create(bepuCenter); // Sin rotación adicional } private void Create(Vector3 position) { RemoverBody(); // Crear triángulos para la curva - los ángulos ya definen la forma correcta var triangles = CreateTriangulatedCurve(_innerRadius, _outerRadius, -_startAngle, -_endAngle); // ✅ NUEVO: Almacenar triángulos originales para visualización debug _originalTriangles = new List>(triangles); foreach (var triangle in triangles) { CreateTriangleBody(triangle, position); } // Crear un cuerpo principal invisible para referencia de posición CreateMainBody(position); } private void CreateMainBody(Vector3 position) { // Crear un cuerpo principal muy pequeño e invisible solo para referencia var smallBox = new Box(0.01f, 0.01f, 0.01f); var shapeIndex = _simulation.Shapes.Add(smallBox); var bodyDescription = BodyDescription.CreateKinematic( new RigidPose(position), // Sin rotación - la forma ya está definida por los ángulos new CollidableDescription(shapeIndex, 0f), // Sin speculative margin new BodyActivityDescription(0.01f) ); BodyHandle = _simulation.Bodies.Add(bodyDescription); _bodyCreated = true; } private void CreateTriangleBody(List triangle, Vector3 basePosition) { if (triangle.Count != 3) return; try { // Calcular centro y dimensiones del triángulo var center = (triangle[0] + triangle[1] + triangle[2]) / 3f; var worldCenter = center + basePosition; // Calcular dimensiones aproximadas del triángulo para crear una caja plana float minX = Math.Min(Math.Min(triangle[0].X, triangle[1].X), triangle[2].X); float maxX = Math.Max(Math.Max(triangle[0].X, triangle[1].X), triangle[2].X); float minY = Math.Min(Math.Min(triangle[0].Y, triangle[1].Y), triangle[2].Y); float maxY = Math.Max(Math.Max(triangle[0].Y, triangle[1].Y), triangle[2].Y); float width = Math.Max(maxX - minX, 0.01f); float height = Math.Max(maxY - minY, 0.01f); float thickness = zAltura_Curve; // Altura muy pequeña para triángulos planos // Crear una caja plana que aproxime el triángulo var box = new Box(width, height, thickness); var shapeIndex = _simulation.Shapes.Add(box); // Crear como cuerpo estático sólido (NO sensor) var activityDescription = new BodyActivityDescription(0.01f); var bodyDescription = BodyDescription.CreateKinematic( new RigidPose(worldCenter), new CollidableDescription(shapeIndex, 0.1f), // Speculative margin normal activityDescription ); var triangleHandle = _simulation.Bodies.Add(bodyDescription); _triangleBodyHandles.Add(triangleHandle); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[simCurve] Error creating triangle: {ex.Message}"); } } private const float SegmentationFactor = 32f / 3f; private const int MinSegments = 8; private const int MaxSegments = 64; private List> CreateTriangulatedCurve(float innerRadius, float outerRadius, float startAngle, float endAngle) { var triangles = new List>(); // Calcular número de segmentos basado en el tamaño del arco float arcLength = Math.Abs(endAngle - startAngle) * ((innerRadius + outerRadius) / 2f); int segments = (int)(arcLength * SegmentationFactor); segments = Math.Max(MinSegments, Math.Min(segments, MaxSegments)); // ✅ SENTIDO HORARIO: Intercambiar ángulos para que vaya en sentido horario float fromAngle = endAngle; // Empezar desde el ángulo final float toAngle = startAngle; // Terminar en el ángulo inicial float angleStep = (toAngle - fromAngle) / segments; // Generar vértices para los arcos interior y exterior var innerPoints = new Vector3[segments + 1]; var outerPoints = new Vector3[segments + 1]; for (int i = 0; i <= segments; i++) { float angle = fromAngle + i * angleStep; float cosAngle = (float)Math.Cos(angle); float sinAngle = (float)Math.Sin(angle); // Triángulos planos en el plano XY (Z = 0 relativo) innerPoints[i] = new Vector3(innerRadius * cosAngle, innerRadius * sinAngle, 0); outerPoints[i] = new Vector3(outerRadius * cosAngle, outerRadius * sinAngle, 0); } // Crear triángulos for (int i = 0; i < segments; i++) { // Primer triángulo del sector var upperTriangle = new List { innerPoints[i], outerPoints[i], outerPoints[i + 1] }; triangles.Add(upperTriangle); // Segundo triángulo del sector var lowerTriangle = new List { innerPoints[i], outerPoints[i + 1], innerPoints[i + 1] }; triangles.Add(lowerTriangle); } return triangles; } /// /// Calcula la dirección tangencial para aplicar fuerzas de curva /// public Vector3 GetTangentialDirection(Vector3 bottlePosition) { try { var curvePosition = GetPosition(); var centerToBottle = new Vector2(bottlePosition.X - curvePosition.X, bottlePosition.Y - curvePosition.Y); if (centerToBottle.Length() < 0.001f) return Vector3.Zero; // Vector tangente (perpendicular al radio) en el plano XY var tangent = new Vector3(-centerToBottle.Y, centerToBottle.X, 0); // Normalizar if (tangent.Length() > 0.001f) { tangent = Vector3.Normalize(tangent); } // Ajustar dirección según la velocidad (signo) if (Speed < 0) tangent = -tangent; return tangent; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[simCurve] Error calculating tangential direction: {ex.Message}"); 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 { private float _radius; private List _deferredActions; public List ListSimBotellaContact; public simDescarte(Simulation simulation, List deferredActions, float diameter, Vector2 position) { _simulation = simulation; _deferredActions = deferredActions; _radius = diameter / 2f; ListSimBotellaContact = new List(); // ✅ USAR COORDINATECONVERTER para conversión centralizada var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), zPos_Descarte + _radius); Create(position3D); } public float Radius { get { return _radius; } set { _radius = Math.Max(value, 0.01f); // Mínimo 1cm // Recrear el cuerpo con el nuevo radio var currentPos = GetPosition(); Create(currentPos); } } public void SetDiameter(float diameter) { Radius = diameter / 2f; } public float GetDiameter() { return _radius * 2f; } public void Create(Vector2 position) { // ✅ USAR COORDINATECONVERTER para conversión centralizada var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), zPos_Descarte + _radius); Create(position3D); } /// /// ✅ NUEVO: Actualiza posición usando coordenadas WPF apropiadas /// internal void UpdateFromWpfCenter(Vector2 wpfCenter) { var position3D = new Vector3(wpfCenter.X, CoordinateConverter.WpfYToBepuY(wpfCenter.Y), zPos_Descarte + _radius); // Actualizar solo posición manteniendo orientación CoordinateConverter.UpdateBepuBodyPosition(_simulation, BodyHandle, position3D); } private void Create(Vector3 position) { RemoverBody(); // Crear esfera sensor para detección var sphere = new BepuPhysics.Collidables.Sphere(_radius); var shapeIndex = _simulation.Shapes.Add(sphere); // Crear como SENSOR (Kinematic con speculative margin 0 para detección pura) // Configurar actividad para NUNCA dormir - los sensores deben estar siempre activos var activityDescription = new BodyActivityDescription(-1f); // -1 = nunca dormir var bodyDescription = BodyDescription.CreateKinematic( new RigidPose(position), new CollidableDescription(shapeIndex, 0f), // Speculative margin 0 para sensores activityDescription ); BodyHandle = _simulation.Bodies.Add(bodyDescription); _bodyCreated = true; // Marcar que hemos creado un cuerpo } } // Callback handlers para BEPU public struct NarrowPhaseCallbacks : INarrowPhaseCallbacks { private SimulationManagerBEPU _simulationManager; public NarrowPhaseCallbacks(SimulationManagerBEPU simulationManager) { _simulationManager = simulationManager; } public bool AllowContactGeneration(int workerIndex, CollidableReference a, CollidableReference b, ref float speculativeMargin) { return true; } public bool AllowContactGeneration(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB) { return true; } public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, ref TManifold manifold, out PairMaterialProperties pairMaterial) where TManifold : unmanaged, IContactManifold { // ✅ SIMPLIFICADO - configuración básica de materiales físicos pairMaterial = new PairMaterialProperties { FrictionCoefficient = 0.6f, // Fricción moderada por defecto MaximumRecoveryVelocity = 2f, // Velocidad máxima de recuperación SpringSettings = new SpringSettings(60, 4) // Rigidez y amortiguamiento estándar }; if (_simulationManager != null) { var botellaA = GetBotellaFromCollidable(pair.A); var botellaB = GetBotellaFromCollidable(pair.B); var barreraA = GetBarreraFromCollidable(pair.A); var barreraB = GetBarreraFromCollidable(pair.B); var descarteA = GetDescarteFromCollidable(pair.A); var descarteB = GetDescarteFromCollidable(pair.B); var botella = botellaA ?? botellaB; // ✅ CONSERVAR - barreras como sensores puros if (barreraA != null || barreraB != null) { return false; // NO generar contacto físico para barreras } // ✅ CONSERVAR - descartes como sensores puros if (descarteA != null || descarteB != null) { var descarte = descarteA ?? descarteB; if (botella != null) { _simulationManager.RegisterDescarteContact(descarte, botella); } return false; // NO generar contacto físico para descartes } // ✅ 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)) { var transport = transportA ?? transportB; _simulationManager.TryCreateTransportMotor(botella, transport); // ✅ Llamar método público // Fricción alta para transportes pairMaterial.FrictionCoefficient = 0.9f; pairMaterial.MaximumRecoveryVelocity = 1f; pairMaterial.SpringSettings = new SpringSettings(80, 6); } // Si hay contacto botella-curva, crear motor LinearAxisMotor else if (botella != null && (curveA != null || curveB != null)) { var curve = curveA ?? curveB; _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); } } return true; } private simTransporte GetTransportFromCollidable(CollidableReference collidable) { if (_simulationManager?.simulation != null) { if (collidable.Mobility == CollidableMobility.Kinematic) { var bodyHandle = collidable.BodyHandle; var simBase = _simulationManager.GetSimBaseFromBodyHandle(bodyHandle); return simBase as simTransporte; } } return null; } private simBotella GetBotellaFromCollidable(CollidableReference collidable) { if (_simulationManager?.simulation != null) { if (collidable.Mobility == CollidableMobility.Dynamic) { var bodyHandle = collidable.BodyHandle; var simBase = _simulationManager.GetSimBaseFromBodyHandle(bodyHandle); return simBase as simBotella; } } return null; } private simBarrera GetBarreraFromCollidable(CollidableReference collidable) { if (_simulationManager?.simulation != null) { if (collidable.Mobility == CollidableMobility.Kinematic) { var bodyHandle = collidable.BodyHandle; var simBase = _simulationManager.GetSimBaseFromBodyHandle(bodyHandle); return simBase as simBarrera; } } return null; } private simDescarte GetDescarteFromCollidable(CollidableReference collidable) { if (_simulationManager?.simulation != null) { if (collidable.Mobility == CollidableMobility.Kinematic) { var bodyHandle = collidable.BodyHandle; var simBase = _simulationManager.GetSimBaseFromBodyHandle(bodyHandle); return simBase as simDescarte; } } return null; } private simCurve GetCurveFromCollidable(CollidableReference collidable) { if (_simulationManager?.simulation != null) { if (collidable.Mobility == CollidableMobility.Kinematic) { var bodyHandle = collidable.BodyHandle; // ✅ NUEVO: Buscar específicamente en los triángulos de las curvas return _simulationManager.GetCurveFromTriangleBodyHandle(bodyHandle); } } return null; } /// /// ✅ NUEVO MÉTODO: Aplicar fuerzas de frenado directamente en el manifold /// public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB, ref ConvexContactManifold manifold) { return true; } public void Initialize(Simulation simulation) { } public void Dispose() { } } public struct PoseIntegratorCallbacks : IPoseIntegratorCallbacks { public Vector3 Gravity; public float LinearDamping; public float AngularDamping; private SimulationManagerBEPU _simulationManager; // ✅ NUEVA REFERENCIA public PoseIntegratorCallbacks(Vector3 gravity, float linearDamping = 0.999f, float angularDamping = 0.995f, SimulationManagerBEPU simulationManager = null) { Gravity = gravity; LinearDamping = linearDamping; AngularDamping = angularDamping; _simulationManager = simulationManager; // ✅ NUEVA INICIALIZACIÓN } public readonly AngularIntegrationMode AngularIntegrationMode => AngularIntegrationMode.Nonconserving; public readonly bool AllowSubstepsForUnconstrainedBodies => false; public readonly bool IntegrateVelocityForKinematics => false; public void Initialize(Simulation simulation) { } public void PrepareForIntegration(float dt) { } public void IntegrateVelocity(Vector bodyIndices, Vector3Wide position, QuaternionWide orientation, BodyInertiaWide localInertia, Vector integrationMask, int workerIndex, Vector dt, ref BodyVelocityWide velocity) { // Aplicar gravedad var gravityWide = Vector3Wide.Broadcast(Gravity); velocity.Linear += gravityWide * dt; // ✅ 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 var linearDampingWide = Vector.One * LinearDamping; var angularDampingWide = Vector.One * AngularDamping; velocity.Linear *= linearDampingWide; velocity.Angular *= angularDampingWide; // ¡Esto es clave para detener rotaciones infinitas! } } 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 { // ✅ 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; // ✅ CORREGIDO - LocalAxis debe estar en coordenadas locales del transporte // Para transportes, el eje local de movimiento es siempre UnitX (eje largo) var localAxis = Vector3.UnitX; // Siempre UnitX en coordenadas locales del transporte var motor = new LinearAxisMotor() { LocalOffsetA = Vector3.Zero, LocalOffsetB = Vector3.Zero, LocalAxis = localAxis, // ✅ Usar eje local, no mundial TargetVelocity = transport.SpeedMetersPerSecond, Settings = new MotorSettings(Math.Max(bottle.Mass * 30f, 10f), 5f) }; // 🔍 DEBUG: Comparar eje local vs mundial System.Diagnostics.Debug.WriteLine($"[Motor] Transport angle: {transport.GetRotationZ()}°"); System.Diagnostics.Debug.WriteLine($"[Motor] World DirectionVector: {transport.DirectionVector}"); System.Diagnostics.Debug.WriteLine($"[Motor] Local Axis: {localAxis}"); System.Diagnostics.Debug.WriteLine($"[Motor] Target Velocity: {transport.SpeedMetersPerSecond}"); 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)) { // 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) { 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}"); } } } public class SimulationManagerBEPU { public Simulation simulation; public List Cuerpos; public List _deferredActions; private BufferPool bufferPool; private Stopwatch stopwatch; private double stopwatch_last; // Referencia al manager de visualización 3D public BEPUVisualization3DManager Visualization3DManager { get; set; } // Propiedad para controlar si la actualización 3D está habilitada public bool Is3DUpdateEnabled { get; set; } = true; // ✅ 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; private Dictionary> _descarteContacts; private HashSet _botellasParaEliminar; // ✅ ELIMINAR COMPLETAMENTE // private Dictionary _transportContacts; // NO SE USA MÁS // private Dictionary _brakeTransportContacts; // NO SE USA MÁS private object _contactsLock = new object(); /// /// Obtiene el objeto simBase correspondiente a un BodyHandle /// public simBase GetSimBaseFromBodyHandle(BodyHandle bodyHandle) { return Cuerpos.FirstOrDefault(c => c.BodyHandle.Equals(bodyHandle)); } /// /// ✅ NUEVO: Obtiene una simCurve si el BodyHandle pertenece a uno de sus triángulos /// public simCurve GetCurveFromTriangleBodyHandle(BodyHandle bodyHandle) { // Buscar en todas las curvas si alguna contiene este BodyHandle en sus triángulos var curves = Cuerpos.OfType(); foreach (var curve in curves) { if (curve._triangleBodyHandles != null && curve._triangleBodyHandles.Contains(bodyHandle)) { return curve; } } // Si no se encuentra en triángulos, buscar en cuerpos principales como fallback var simBase = GetSimBaseFromBodyHandle(bodyHandle); 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(); _barreraContacts = new Dictionary>(); _descarteContacts = new Dictionary>(); _botellasParaEliminar = new HashSet(); // ✅ 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(); var narrowPhaseCallbacks = new NarrowPhaseCallbacks(this); // Configurar amortiguamiento para comportamiento realista: // - LinearDamping: 0.999f (muy leve, objetos siguen moviéndose pero eventualmente se detienen) // - AngularDamping: 0.995f (más agresivo para detener rotaciones infinitas de cilindros) // ✅ MODIFICADO: Pasar referencia de this al PoseIntegratorCallbacks var poseIntegratorCallbacks = new PoseIntegratorCallbacks( gravity: new Vector3(0, 0, -9.81f), // Gravedad en Z linearDamping: 0.999f, // Amortiguamiento lineal suave angularDamping: 0.995f, // Amortiguamiento angular más fuerte para detener rotaciones simulationManager: this // ✅ NUEVA REFERENCIA ); 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 - 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) { _barreraContacts.Clear(); _descarteContacts.Clear(); _botellasParaEliminar.Clear(); _silencedPairs.Clear(); // ✅ NUEVO } // ✅ NUEVO - limpiar clasificaciones _transportHandles.Clear(); _curveHandles.Clear(); _barrierHandles.Clear(); _discardHandles.Clear(); _bottleHandles.Clear(); // ✅ CONSERVAR - resto del método igual var cuerposToRemove = new List(Cuerpos); foreach (var cuerpo in cuerposToRemove) { try { if (cuerpo != null) { cuerpo.RemoverBody(); } } catch (Exception ex) { // Error removing body - continue with cleanup } } Cuerpos.Clear(); _deferredActions.Clear(); // ✅ NUEVO - Limpiar todas las dimensiones almacenadas CtrEditor.ObjetosSim.osBase.ClearAllStoredDimensions(); // Limpiar la simulación completamente si existe if (simulation != null) { try { // Forzar un timestep pequeño para limpiar contactos pendientes simulation.Timestep(1f / 1000f); // 1ms } catch (Exception ex) { // Warning during cleanup timestep - continue } } // Limpiar visualización 3D Visualization3DManager?.Clear(); } catch (Exception ex) { // Critical error during clear - operation failed } } public void Start() { 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; if (float.IsNaN(deltaTime) || float.IsInfinity(deltaTime) || deltaTime <= 0) { deltaTime = 1f / 60f; } const float maxDeltaTime = 0.1f; if (deltaTime > maxDeltaTime) { deltaTime = 1f / 60f; } // ✅ CONSERVAR - procesar acciones diferidas foreach (var action in _deferredActions) { action(); } _deferredActions.Clear(); // ✅ CONSERVAR - validaciones de simulación if (simulation?.Bodies == null) { return; } var invalidBodies = Cuerpos.Where(c => c != null && !simulation.Bodies.BodyExists(c.BodyHandle)).ToList(); if (invalidBodies.Count > 0) { foreach (var invalidBody in invalidBodies) { Cuerpos.Remove(invalidBody); } } // ✅ CONSERVAR - timestep var timestepValue = Math.Max(deltaTime, 1f / 120f); try { simulation.Timestep(timestepValue); } catch (AccessViolationException ex) { lock (_contactsLock) { _barreraContacts.Clear(); } throw; } // ✅ ELIMINAR COMPLETAMENTE // ApplyTransportForces(deltaTime); // NO SE USA MÁS // ProcessBrakeTransportContacts(); // NO SE USA MÁS // ✅ NUEVO - verificación periódica de salidas de transporte CheckBottleExitsFromTransports(); // ✅ 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(); simulation.Awakener.AwakenBody(cuerpo.BodyHandle); } } catch (Exception ex) { // Error limiting rotation for bottle - continue } } // ✅ CONSERVAR - sistemas que funcionan bien ProcessBarreraContacts(); ProcessDescarteContacts(); ProcessCleanupSystem(); // ✅ SIMPLIFICAR - limpiar solo contactos que usamos lock (_contactsLock) { _barreraContacts.Clear(); _descarteContacts.Clear(); } // ✅ CONSERVAR - sincronización 3D if (Is3DUpdateEnabled) { Visualization3DManager?.SynchronizeWorld(); } } catch (Exception ex) { lock (_contactsLock) { _barreraContacts.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) { // ✅ 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); if (Is3DUpdateEnabled) { Visualization3DManager?.SynchronizeWorld(); } } public simBotella AddCircle(float diameter, Vector2 position, float mass) { var botella = new simBotella(simulation, _deferredActions, diameter, position, mass); Cuerpos.Add(botella); RegisterObjectHandle(botella); // ✅ NUEVO if (Is3DUpdateEnabled) { Visualization3DManager?.SynchronizeWorld(); } return botella; } public simTransporte AddRectangle(float width, float height, Vector2 position, float angle) { var transporte = new simTransporte(simulation, _deferredActions, width, height, position, angle, this); // ✅ PASAR REFERENCIA Cuerpos.Add(transporte); RegisterObjectHandle(transporte); // ✅ NUEVO - incluye UpdateCachedProperties() if (Is3DUpdateEnabled) { Visualization3DManager?.SynchronizeWorld(); } return transporte; } public simBarrera AddBarrera(float width, float height, Vector2 position, float angle, bool detectarCuello) { var barrera = new simBarrera(simulation, _deferredActions, width, height, position, angle, detectarCuello); Cuerpos.Add(barrera); RegisterObjectHandle(barrera); // ✅ NUEVO if (Is3DUpdateEnabled) { Visualization3DManager?.SynchronizeWorld(); } return barrera; } public simGuia AddLine(float width, float height, Vector2 topLeft, float angle) { var guia = new simGuia(simulation, _deferredActions, width, height, topLeft, angle); Cuerpos.Add(guia); // ✅ NOTA: simGuia no requiere registro especial if (Is3DUpdateEnabled) { Visualization3DManager?.SynchronizeWorld(); } return guia; } public simDescarte AddDescarte(float diameter, Vector2 position) { var descarte = new simDescarte(simulation, _deferredActions, diameter, position); Cuerpos.Add(descarte); 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, this); // ✅ PASAR REFERENCIA Cuerpos.Add(curve); 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(); simulation?.Dispose(); bufferPool?.Clear(); } /// /// ✅ IMPLEMENTADO: Intenta crear un motor de transporte si no existe uno activo /// public void TryCreateTransportMotor(simBotella bottle, simTransporte transport) { try { // 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}"); } } /// /// ✅ IMPLEMENTADO: Intenta crear un motor de curva si no existe uno activo /// public void TryCreateCurveMotor(simBotella bottle, simCurve curve) { try { // 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}"); } } /// /// Registra un contacto entre una barrera y una botella para detección de paso /// /// Barrera que detecta el paso /// Botella que está pasando public void RegisterBarreraContact(simBarrera barrera, simBotella botella) { if (barrera != null && botella != null) { lock (_contactsLock) { if (!_barreraContacts.ContainsKey(barrera)) { _barreraContacts[barrera] = new List(); } if (!_barreraContacts[barrera].Contains(botella)) { _barreraContacts[barrera].Add(botella); } } } } /// /// Registra un contacto entre un descarte y una botella para marcar eliminación /// /// Descarte que detecta la botella /// Botella que debe ser eliminada 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(); } if (!_descarteContacts[descarte].Contains(botella)) { _descarteContacts[descarte].Add(botella); } } } } /// /// Procesa todas las barreras usando detección geométrica pura /// Ya no depende de contactos físicos de BEPU - usa cálculo geométrico para ambos flags /// private void ProcessBarreraContacts() { try { // Obtener todas las barreras y botellas var barreras = Cuerpos.OfType().ToList(); var botellas = Cuerpos.OfType().ToList(); foreach (var barrera in barreras) { try { // Verificar que la barrera aún existe en la simulación if (barrera == null || !simulation?.Bodies?.BodyExists(barrera.BodyHandle) == true) continue; // Resetear flags barrera.LuzCortada = false; barrera.LuzCortadaNeck = false; barrera.Distancia = float.MaxValue; // Limpiar la lista de forma segura if (barrera.ListSimBotellaContact != null) { barrera.ListSimBotellaContact.Clear(); } // Calcular detección usando geometría pura para TODAS las botellas CalculateBarreraDetectionGeometric(barrera, botellas); } catch (Exception ex) { // Error processing barrera - continue } } // Limpiar contactos ya que no los usamos más lock (_contactsLock) { _barreraContacts.Clear(); } } catch (Exception ex) { // Critical error in ProcessBarrera - continue } } /// /// Calcula detección geométrica pura para una barrera específica contra todas las botellas /// Usa el mismo sistema geométrico para ambos flags: LuzCortada (radio completo) y LuzCortadaNeck (radio/2) /// /// Barrera que está detectando /// Lista de TODAS las botellas en la simulación private void CalculateBarreraDetectionGeometric(simBarrera barrera, List todasLasBotellas) { try { // Validaciones de seguridad if (barrera == null || todasLasBotellas == null || simulation?.Bodies == null) return; if (!simulation.Bodies.BodyExists(barrera.BodyHandle)) return; var barrierBody = simulation.Bodies[barrera.BodyHandle]; var barrierPosition = barrierBody.Pose.Position; var barrierOrientation = barrierBody.Pose.Orientation; // Validar valores de posición y orientación if (float.IsNaN(barrierPosition.X) || float.IsNaN(barrierPosition.Y) || float.IsNaN(barrierPosition.Z)) { return; } // Obtener las dimensiones de la barrera de forma segura var halfWidth = Math.Max(barrera.Width / 2f, 0.01f); // Mínimo 1cm float minDistance = float.MaxValue; var botellasDetectadas = new List(); // Procesar TODAS las botellas (no solo las en contacto físico) foreach (var botella in todasLasBotellas) { try { if (botella == null || !simulation.Bodies.BodyExists(botella.BodyHandle)) continue; var botellaBody = simulation.Bodies[botella.BodyHandle]; var botellaPosition = botellaBody.Pose.Position; // Validar posición de la botella if (float.IsNaN(botellaPosition.X) || float.IsNaN(botellaPosition.Y) || float.IsNaN(botellaPosition.Z)) { continue; } // CLAVE: Crear la línea del haz en el plano XY a la altura del centro de la botella var bottleZ = botellaPosition.Z; // Altura actual del centro de la botella // Puntos de la línea del haz en el plano XY a la altura de la botella var localStart = new Vector3(-halfWidth, 0, 0); var localEnd = new Vector3(halfWidth, 0, 0); // Transformar a coordenadas mundiales pero en el plano Z de la botella var worldStartXY = barrierPosition + Vector3.Transform(localStart, barrierOrientation); var worldEndXY = barrierPosition + Vector3.Transform(localEnd, barrierOrientation); // Ajustar Z para que esté en el plano de la botella worldStartXY = new Vector3(worldStartXY.X, worldStartXY.Y, bottleZ); worldEndXY = new Vector3(worldEndXY.X, worldEndXY.Y, bottleZ); // Calcular distancia desde el centro de la botella a la línea del haz var closestPoint = ProjectPointOntoLine(botellaPosition, worldStartXY, worldEndXY); var distance = Vector3.Distance(closestPoint, botellaPosition); // Validar distancia calculada if (float.IsNaN(distance) || float.IsInfinity(distance)) { continue; } // Actualizar distancia mínima if (distance < minDistance) { minDistance = distance; } // NUEVO: Verificar LuzCortada usando radio completo if (distance <= botella.Radius) { barrera.LuzCortada = true; botellasDetectadas.Add(botella); } // Verificar detección de cuello usando radio/2 (como antes) if (barrera.DetectNeck && botella.Radius > 0) { var neckRadius = botella.Radius / 2f; if (distance <= neckRadius) { barrera.LuzCortadaNeck = true; // No hacer break aquí - queremos procesar todas las botellas para LuzCortada } } } catch (Exception ex) { // Error processing bottle - continue } } // Asignar resultados de forma segura barrera.Distancia = minDistance == float.MaxValue ? 0f : minDistance; // Actualizar lista de botellas detectadas if (barrera.ListSimBotellaContact != null) { barrera.ListSimBotellaContact.AddRange(botellasDetectadas); } } catch (Exception ex) { // En caso de error, asignar valores seguros if (barrera != null) { barrera.Distancia = float.MaxValue; barrera.LuzCortada = false; barrera.LuzCortadaNeck = false; } } } /// /// Procesa contactos de descarte para eliminar botellas marcadas usando detección geométrica /// private void ProcessDescarteContacts() { try { // Obtener todos los descartes y botellas var descartes = Cuerpos.OfType().ToList(); var botellas = Cuerpos.OfType().ToList(); foreach (var descarte in descartes) { try { // Verificar que el descarte aún existe en la simulación if (descarte == null || !simulation?.Bodies?.BodyExists(descarte.BodyHandle) == true) continue; // Limpiar la lista de forma segura if (descarte.ListSimBotellaContact != null) { descarte.ListSimBotellaContact.Clear(); } // Calcular detección usando geometría pura para TODAS las botellas CalculateDescarteDetectionGeometric(descarte, botellas); } catch (Exception ex) { // Error processing descarte - continue } } // 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) { // Critical error in ProcessDescarte - continue } } /// /// Calcula detección geométrica pura para un descarte específico contra todas las botellas /// Usa detección de esfera contra esfera para determinar si hay contacto /// private void CalculateDescarteDetectionGeometric(simDescarte descarte, List todasLasBotellas) { try { // Validaciones de seguridad if (descarte == null || todasLasBotellas == null || simulation?.Bodies == null) return; if (!simulation.Bodies.BodyExists(descarte.BodyHandle)) return; var descarteBody = simulation.Bodies[descarte.BodyHandle]; var descartePosition = descarteBody.Pose.Position; // Validar valores de posición if (float.IsNaN(descartePosition.X) || float.IsNaN(descartePosition.Y) || float.IsNaN(descartePosition.Z)) { return; } var botellasDetectadas = new List(); // Procesar TODAS las botellas para detección geométrica foreach (var botella in todasLasBotellas) { try { if (botella == null || !simulation.Bodies.BodyExists(botella.BodyHandle)) continue; var botellaBody = simulation.Bodies[botella.BodyHandle]; var botellaPosition = botellaBody.Pose.Position; // Validar posición de la botella if (float.IsNaN(botellaPosition.X) || float.IsNaN(botellaPosition.Y) || float.IsNaN(botellaPosition.Z)) { continue; } // Calcular distancia entre centros (detección esfera contra esfera) var distance = Vector3.Distance(descartePosition, botellaPosition); // Validar distancia calculada if (float.IsNaN(distance) || float.IsInfinity(distance)) { continue; } // Verificar si las esferas se superponen var totalRadius = descarte.Radius + botella.Radius; if (distance <= totalRadius) { // Marcar botella para eliminación botella.Descartar = true; _botellasParaEliminar.Add(botella); botellasDetectadas.Add(botella); } } catch (Exception ex) { // Error processing bottle - continue } } // Actualizar lista de botellas detectadas if (descarte.ListSimBotellaContact != null) { descarte.ListSimBotellaContact.AddRange(botellasDetectadas); } } catch (Exception ex) { // Critical error in CalculateDescarteGeometric - continue } } /// /// Elimina las botellas marcadas para eliminación de forma segura /// private void RemoveMarkedBottles() { try { List botellasAEliminar; lock (_contactsLock) { botellasAEliminar = new List(_botellasParaEliminar); _botellasParaEliminar.Clear(); } foreach (var botella in botellasAEliminar) { try { if (botella != null && Cuerpos.Contains(botella)) { Remove(botella); } } catch (Exception ex) { // Error removing bottle - continue } } // Botellas eliminadas: {botellasAEliminar.Count} } catch (Exception ex) { // Critical error in RemoveMarkedBottles - continue } } /// /// Sistema de limpieza que elimina botellas que estén debajo de la altura de los transportes /// Cualquier botella con Z menor al nivel superior de los transportes será eliminada /// private void ProcessCleanupSystem() { try { // Altura máxima de los transportes (nivel superior) var maxTransportHeight = simBase.zPos_Transporte + simBase.zAltura_Transporte; // Obtener todas las botellas var botellas = Cuerpos.OfType().ToList(); foreach (var botella in botellas) { try { // Validar que la botella aún existe en la simulación if (botella == null || !simulation?.Bodies?.BodyExists(botella.BodyHandle) == true) continue; var posicion = botella.GetPosition(); // Validar posición if (float.IsNaN(posicion.Z) || float.IsInfinity(posicion.Z)) continue; // Si la botella está debajo del nivel de los transportes, marcarla para eliminación if (posicion.Z < (maxTransportHeight - 2 * botella.Radius) || posicion.Z > (maxTransportHeight + 2 * botella.Radius)) { lock (_contactsLock) { botella.Descartar = true; _botellasParaEliminar.Add(botella); } } } catch (Exception ex) { // Error processing bottle in cleanup - continue } } } catch (Exception ex) { // Critical error in ProcessCleanupSystem - continue } } /// /// Proyecta un punto sobre una línea definida por dos puntos /// private Vector3 ProjectPointOntoLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd) { var lineDirection = Vector3.Normalize(lineEnd - lineStart); var pointToStart = point - lineStart; var projectionLength = Vector3.Dot(pointToStart, lineDirection); // Restringir la proyección a los límites de la línea var lineLength = Vector3.Distance(lineStart, lineEnd); projectionLength = Math.Max(0, Math.Min(projectionLength, lineLength)); return lineStart + lineDirection * projectionLength; } } }