From 4ff93a5802f3410ba5b697760a3624903226713c Mon Sep 17 00:00:00 2001 From: Miguel Date: Wed, 2 Jul 2025 23:39:22 +0200 Subject: [PATCH] Antes de cambios en simCurve --- Simulacion/BEPU.cs | 703 ++++++++++++++++++++++-------- Simulacion/BEPUVisualization3D.cs | 342 +++++++++++++-- 2 files changed, 829 insertions(+), 216 deletions(-) diff --git a/Simulacion/BEPU.cs b/Simulacion/BEPU.cs index d2218d0..b6e80f0 100644 --- a/Simulacion/BEPU.cs +++ b/Simulacion/BEPU.cs @@ -25,31 +25,31 @@ namespace CtrEditor.Simulacion public static class CoordinateConverter { /// - /// Convierte ángulo de WPF a BEPU (invierte signo) + /// Convierte ángulo de WPF a BEPU (invierte signo) - USO INTERNO /// - public static float WpfAngleToBepuAngle(float wpfAngle) + internal static float WpfAngleToBepuAngle(float wpfAngle) { return -wpfAngle; } /// - /// Convierte ángulo de BEPU a WPF (invierte signo) + /// Convierte ángulo de BEPU a WPF (invierte signo) - USO INTERNO /// - public static float BepuAngleToWpfAngle(float bepuAngle) + internal static float BepuAngleToWpfAngle(float bepuAngle) { return -bepuAngle; } /// - /// Convierte posición Y de WPF a BEPU (invierte signo) + /// Convierte posición Y de WPF a BEPU (invierte signo) - USO INTERNO /// - public static float WpfYToBepuY(float wpfY) + internal static float WpfYToBepuY(float wpfY) { return -wpfY; } /// - /// Convierte posición Y de BEPU a WPF (invierte signo) + /// Convierte posición Y de BEPU a WPF (invierte signo) - RETORNA VALOR WPF /// public static float BepuYToWpfY(float bepuY) { @@ -57,15 +57,15 @@ namespace CtrEditor.Simulacion } /// - /// Convierte Vector2 de WPF a BEPU (solo invierte Y) + /// Convierte Vector2 de WPF a BEPU (solo invierte Y) - USO INTERNO /// - public static Vector2 WpfToBepuVector2(Vector2 wpfVector) + internal static Vector2 WpfToBepuVector2(Vector2 wpfVector) { return new Vector2(wpfVector.X, -wpfVector.Y); } /// - /// Convierte Vector2 de BEPU a WPF (solo invierte Y) + /// Convierte Vector2 de BEPU a WPF (solo invierte Y) - RETORNA VALOR WPF /// public static Vector2 BepuToWpfVector2(Vector2 bepuVector) { @@ -73,7 +73,7 @@ namespace CtrEditor.Simulacion } /// - /// Convierte Vector3 de BEPU a Vector2 WPF (proyección XY con Y invertida) + /// Convierte Vector3 de BEPU a Vector2 WPF (proyección XY con Y invertida) - RETORNA VALOR WPF /// public static Vector2 BepuVector3ToWpfVector2(Vector3 bepuVector) { @@ -81,10 +81,10 @@ namespace CtrEditor.Simulacion } /// - /// Calcula la posición del centro en coordenadas BEPU desde Top-Left WPF + /// Calcula la posición del centro en coordenadas BEPU desde Top-Left WPF - USO INTERNO /// Maneja correctamente la rotación del objeto /// - public static Vector3 CalculateBepuCenterFromWpfTopLeft(Vector2 wpfTopLeft, float width, float height, float wpfAngle, float zPosition) + internal 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; @@ -108,7 +108,7 @@ namespace CtrEditor.Simulacion } /// - /// Calcula la posición Top-Left WPF desde el centro BEPU + /// Calcula la posición Top-Left WPF desde el centro BEPU - RETORNA VALOR WPF /// Maneja correctamente la rotación del objeto /// public static Vector2 CalculateWpfTopLeftFromBepuCenter(Vector3 bepuCenter, float width, float height, float wpfAngle) @@ -138,16 +138,16 @@ namespace CtrEditor.Simulacion } /// - /// Crea un Quaternion para BEPU desde un ángulo WPF + /// Crea un Quaternion para BEPU desde un ángulo WPF - USO INTERNO /// - public static Quaternion CreateBepuQuaternionFromWpfAngle(float wpfAngle) + internal 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 + /// Extrae el ángulo WPF desde un Quaternion BEPU - RETORNA VALOR WPF /// public static float ExtractWpfAngleFromBepuQuaternion(Quaternion bepuQuaternion) { @@ -162,9 +162,9 @@ namespace CtrEditor.Simulacion } /// - /// Actualiza la posición de un body BEPU manteniendo su rotación actual + /// Actualiza la posición de un body BEPU manteniendo su rotación actual - USO INTERNO /// - public static void UpdateBepuBodyPosition(Simulation simulation, BodyHandle bodyHandle, Vector3 newBepuPosition) + internal static void UpdateBepuBodyPosition(Simulation simulation, BodyHandle bodyHandle, Vector3 newBepuPosition) { if (simulation != null && simulation.Bodies.BodyExists(bodyHandle)) { @@ -174,9 +174,9 @@ namespace CtrEditor.Simulacion } /// - /// Actualiza la rotación de un body BEPU manteniendo su posición actual + /// Actualiza la rotación de un body BEPU manteniendo su posición actual - USO INTERNO /// - public static void UpdateBepuBodyRotation(Simulation simulation, BodyHandle bodyHandle, float wpfAngle) + internal static void UpdateBepuBodyRotation(Simulation simulation, BodyHandle bodyHandle, float wpfAngle) { if (simulation != null && simulation.Bodies.BodyExists(bodyHandle)) { @@ -186,9 +186,9 @@ namespace CtrEditor.Simulacion } /// - /// Actualiza posición y rotación de un body BEPU simultáneamente + /// Actualiza posición y rotación de un body BEPU simultáneamente - USO INTERNO /// - public static void UpdateBepuBodyPose(Simulation simulation, BodyHandle bodyHandle, Vector3 newBepuPosition, float wpfAngle) + internal static void UpdateBepuBodyPose(Simulation simulation, BodyHandle bodyHandle, Vector3 newBepuPosition, float wpfAngle) { if (simulation != null && simulation.Bodies.BodyExists(bodyHandle)) { @@ -199,9 +199,9 @@ namespace CtrEditor.Simulacion } /// - /// Obtiene la posición del centro en coordenadas BEPU + /// Obtiene la posición del centro en coordenadas BEPU - USO INTERNO /// - public static Vector3 GetBepuBodyPosition(Simulation simulation, BodyHandle bodyHandle) + internal static Vector3 GetBepuBodyPosition(Simulation simulation, BodyHandle bodyHandle) { if (simulation != null && simulation.Bodies.BodyExists(bodyHandle)) { @@ -212,7 +212,7 @@ namespace CtrEditor.Simulacion } /// - /// Obtiene el ángulo WPF desde un body BEPU + /// Obtiene el ángulo WPF desde un body BEPU - RETORNA VALOR WPF /// public static float GetWpfAngleFromBepuBody(Simulation simulation, BodyHandle bodyHandle) { @@ -223,6 +223,24 @@ namespace CtrEditor.Simulacion } return 0f; } + + /// + /// ✅ NUEVO: Convierte directamente de grados WPF a radianes BEPU - USO INTERNO + /// Maneja tanto la inversión de signo como la conversión a radianes en una sola operación + /// + internal static float WpfDegreesToBepuRadians(float wpfDegrees) + { + return simBase.GradosARadianes(WpfAngleToBepuAngle(wpfDegrees)); + } + + /// + /// ✅ NUEVO: Convierte directamente de radianes BEPU a grados WPF - RETORNA VALOR WPF + /// Maneja tanto la conversión a grados como la inversión de signo en una sola operación + /// + public static float BepuRadiansToWpfDegrees(float bepuRadians) + { + return BepuAngleToWpfAngle(simBase.RadianesAGrados(bepuRadians)); + } } public class simBase @@ -467,7 +485,7 @@ namespace CtrEditor.Simulacion { // ✅ CORREGIDO: Aplicar conversión de coordenadas WPF→BEPU en el vector var wpfAngle = GetRotationZ(); // Ángulo WPF en grados - var wpfAngleRadians = GradosARadianes(wpfAngle); + var wpfAngleRadians = simBase.GradosARadianes(wpfAngle); // Calcular el vector en coordenadas WPF var wpfX = (float)Math.Cos(wpfAngleRadians); @@ -480,7 +498,7 @@ namespace CtrEditor.Simulacion 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; + SpeedMetersPerSecond = Speed / simBase.SPEED_CONVERSION_FACTOR; } // ✅ MODIFICAR MÉTODO EXISTENTE - disparar evento @@ -499,7 +517,7 @@ namespace CtrEditor.Simulacion /// Velocidad en m/s (típicamente entre -5.0 y 5.0) public void SetTransportSpeed(float speedMeterPerSecond) { - SetSpeed(speedMeterPerSecond * SPEED_CONVERSION_FACTOR); + SetSpeed(speedMeterPerSecond * simBase.SPEED_CONVERSION_FACTOR); } /// @@ -951,7 +969,7 @@ namespace CtrEditor.Simulacion new RigidPose(position), new BodyVelocity(), inertia, // Inercia estándar de esfera - new CollidableDescription(shapeIndex, 0.01f), + new CollidableDescription(shapeIndex, 0.001f), activityDescription ); @@ -1070,19 +1088,26 @@ namespace CtrEditor.Simulacion } + /// + /// Representa una curva o arco en la simulación física. + /// ✅ IMPORTANTE: Los ángulos _startAngle y _endAngle se almacenan INTERNAMENTE en radianes BEPU + /// (ya convertidos desde grados WPF usando CoordinateConverter.WpfDegreesToBepuRadians) + /// Las propiedades públicas StartAngle/EndAngle devuelven grados WPF usando CoordinateConverter.BepuRadiansToWpfDegrees + /// ✅ NUEVO: Cada triángulo actúa como un mini-transporte con su propia dirección tangencial fija + /// public class simCurve : simBase { private float _innerRadius; private float _outerRadius; - private float _startAngle; - private float _endAngle; + private float _startAngle; // ✅ SIEMPRE en radianes BEPU + private float _endAngle; // ✅ SIEMPRE en radianes BEPU 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 + // ✅ SIMPLIFICADO: Solo necesitamos velocidad en m/s, no sistema complejo de direcciones public float SpeedMetersPerSecond { get; private set; } public List BottlesOnCurve { get; private set; } = new List(); @@ -1092,10 +1117,13 @@ namespace CtrEditor.Simulacion // ✅ NUEVA REFERENCIA - para limpiar motors private SimulationManagerBEPU _simulationManager; + // ✅ NUEVO: Direcciones tangenciales precalculadas para cada triángulo + internal Dictionary _triangleDirections = new Dictionary(); + 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 + public float StartAngle => CoordinateConverter.BepuRadiansToWpfDegrees(_startAngle); // Convertir de radianes BEPU internos a grados WPF + public float EndAngle => CoordinateConverter.BepuRadiansToWpfDegrees(_endAngle); // Convertir de radianes BEPU internos a grados WPF /// /// ✅ NUEVO: Expone los triángulos originales para visualización debug @@ -1109,9 +1137,9 @@ namespace CtrEditor.Simulacion _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)); + // ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes + _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle); + _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle); _triangleBodyHandles = new List(); _originalTriangles = new List>(); // ✅ NUEVO: Inicializar lista de triángulos @@ -1123,7 +1151,7 @@ namespace CtrEditor.Simulacion public void SetSpeed(float speed) { Speed = -speed; - SpeedMetersPerSecond = Math.Abs(Speed) / SPEED_CONVERSION_FACTOR; + SpeedMetersPerSecond = Math.Abs(Speed) / simBase.SPEED_CONVERSION_FACTOR; OnSpeedChanged?.Invoke(this); } @@ -1131,9 +1159,9 @@ namespace CtrEditor.Simulacion { _innerRadius = innerRadius; _outerRadius = outerRadius; - // ✅ CORREGIDO: Usar conversión WPF a BEPU consistente - _startAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(startAngle)); - _endAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(endAngle)); + // ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes + _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle); + _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle); // Recrear la curva con nuevos parámetros manteniendo posición actual var currentPosition = GetPosition(); @@ -1148,9 +1176,9 @@ namespace CtrEditor.Simulacion // 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)); + // ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes + _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle); + _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle); // ✅ USAR COORDINATECONVERTER para conversión centralizada var curveSize = outerRadius * 2f; @@ -1187,6 +1215,9 @@ namespace CtrEditor.Simulacion // ✅ CRÍTICO: Limpiar todos los motors conectados a esta curva ANTES de eliminar los bodies RemoveConnectedMotors(); + // ✅ NUEVO: Limpiar cachés de direcciones + _triangleDirections.Clear(); + // Remover todos los triángulos de la curva if (_triangleBodyHandles != null && _simulation != null) { @@ -1250,9 +1281,9 @@ namespace CtrEditor.Simulacion // 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)); + // ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes + _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle); + _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle); // ✅ USAR COORDINATECONVERTER para conversión centralizada // Para curvas, el "tamaño" es el diámetro del radio exterior @@ -1311,31 +1342,59 @@ namespace CtrEditor.Simulacion 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); + // ✅ NUEVO: Calcular dirección tangencial con compensación centrípeta + // Usamos la posición del centro del triángulo para calcular la tangente + var radiusVector = new Vector2(center.X, center.Y); + if (radiusVector.Length() > 0.001f) + { + // Vector tangente (perpendicular al radio) en el plano XY + var tangent = new Vector3(-radiusVector.Y, radiusVector.X, 0); + tangent = Vector3.Normalize(tangent); + + // ✅ COMPENSACIÓN CENTRÍPETA: Inclinar la dirección hacia el centro + var radiusVector3D = new Vector3(radiusVector.X, radiusVector.Y, 0); + var toCenter = -Vector3.Normalize(radiusVector3D); // Vector hacia el centro + + // Mezclar tangente con componente centrípeta (10% hacia el centro) + const float CENTRIPETAL_FACTOR = 0.1f; // 10% de fuerza hacia el centro + var adjustedDirection = Vector3.Normalize(tangent + toCenter * CENTRIPETAL_FACTOR); + + // Ajustar dirección según la velocidad (signo) + if (Speed < 0) + adjustedDirection = -adjustedDirection; + + // Almacenar dirección ajustada antes de crear el body + var triangleDirection = adjustedDirection; + + // 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 + 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 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); + // 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 bodyDescription = BodyDescription.CreateKinematic( + new RigidPose(worldCenter), + new CollidableDescription(shapeIndex, 0.001f), // Speculative margin normal + activityDescription + ); - var triangleHandle = _simulation.Bodies.Add(bodyDescription); - _triangleBodyHandles.Add(triangleHandle); + var triangleHandle = _simulation.Bodies.Add(bodyDescription); + _triangleBodyHandles.Add(triangleHandle); + + // ✅ NUEVO: Almacenar la dirección tangencial para este triángulo + _triangleDirections[triangleHandle] = triangleDirection; + } } catch (Exception ex) { @@ -1347,6 +1406,16 @@ namespace CtrEditor.Simulacion private const int MinSegments = 8; private const int MaxSegments = 64; + /// + /// Crea triángulos para representar una curva/arco en la simulación BEPU. + /// IMPORTANTE: Los parámetros startAngle y endAngle ya están convertidos a radianes BEPU internamente, + /// pero además se invierte la dirección del arco para mantener coherencia visual con WPF. + /// + /// Radio interno del arco + /// Radio externo del arco + /// Ángulo inicial en radianes BEPU (ya convertido desde grados WPF) + /// Ángulo final en radianes BEPU (ya convertido desde grados WPF) + /// Lista de triángulos que forman el arco private List> CreateTriangulatedCurve(float innerRadius, float outerRadius, float startAngle, float endAngle) { var triangles = new List>(); @@ -1356,9 +1425,17 @@ namespace CtrEditor.Simulacion 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 + // ✅ CRÍTICO: Inversión de dirección del arco para conversión WPF ↔ BEPU + // + // Los ángulos individuales ya están convertidos (WPF grados → BEPU radianes con signo invertido), + // pero también necesitamos invertir la DIRECCIÓN del arco: + // + // • WPF: startAngle → endAngle (sentido horario, Y+ hacia abajo) + // • BEPU: -endAngle → -startAngle (sentido antihorario, Y+ hacia arriba) + // + // Ejemplo: WPF arco de 0° a 90° → BEPU arco de -90° a 0° (equivalente visual) + float fromAngle = -endAngle; // Empezar desde el ángulo final negativo + float toAngle = -startAngle; // Terminar en el ángulo inicial negativo float angleStep = (toAngle - fromAngle) / segments; // Generar vértices para los arcos interior y exterior @@ -1404,7 +1481,9 @@ namespace CtrEditor.Simulacion /// - /// Calcula la dirección tangencial para aplicar fuerzas de curva + /// Calcula la dirección tangencial para aplicar fuerzas de curva. + /// NOTA: Este cálculo es dinámico (basado en posición actual) y no se ve afectado + /// por la inversión de dirección del arco en CreateTriangulatedCurve. /// public Vector3 GetTangentialDirection(Vector3 bottlePosition) { @@ -1444,6 +1523,14 @@ namespace CtrEditor.Simulacion var direction = GetTangentialDirection(bottlePosition); return Speed < 0 ? -direction : direction; } + + /// + /// ✅ NUEVO - Obtiene la dirección tangencial fija de un triángulo específico + /// + public Vector3 GetTriangleDirection(BodyHandle triangleHandle) + { + return _triangleDirections.GetValueOrDefault(triangleHandle, Vector3.Zero); + } } public class simDescarte : simBase @@ -1539,7 +1626,48 @@ namespace CtrEditor.Simulacion public bool AllowContactGeneration(int workerIndex, CollidableReference a, CollidableReference b, ref float speculativeMargin) { - return true; + // ✅ NUEVO FILTRADO: Evitar colisiones innecesarias entre triángulos de curvas + if (_simulationManager != null) + { + // Obtener información de los objetos involucrados + var curveA = _simulationManager.GetCurveFromTriangleBodyHandle(a.BodyHandle); + var curveB = _simulationManager.GetCurveFromTriangleBodyHandle(b.BodyHandle); + var transportA = GetTransportFromCollidable(a); + var transportB = GetTransportFromCollidable(b); + var barrierA = GetBarreraFromCollidable(a); + var barrierB = GetBarreraFromCollidable(b); + var discardA = GetDescarteFromCollidable(a); + var discardB = GetDescarteFromCollidable(b); + var botellaA = GetBotellaFromCollidable(a); + var botellaB = GetBotellaFromCollidable(b); + + // ✅ FILTRO 1: No permitir colisiones entre triángulos de curvas + if (curveA != null && curveB != null) + { + return false; // Los triángulos de curva no deben colisionar entre sí + } + + // ✅ FILTRO 2: No permitir colisiones entre transportes + if (transportA != null && transportB != null) + { + return false; // Los transportes no deben colisionar entre sí + } + + // ✅ FILTRO 3: No permitir colisiones entre elementos estáticos (transportes, curvas, barreras, descartes) + var staticA = (transportA != null || curveA != null || barrierA != null || discardA != null); + var staticB = (transportB != null || curveB != null || barrierB != null || discardB != null); + + if (staticA && staticB) + { + return false; // Los elementos estáticos no deben colisionar entre sí + } + + // ✅ ELIMINADO: No bloquear colisiones físicas aquí + // Las colisiones físicas deben permitirse siempre para mantener geometría sólida + // Solo bloqueamos la creación de motores duplicados en ConfigureContactManifold + } + + return true; // Permitir todas las demás colisiones (botella-estático, botella-botella) } public bool AllowContactGeneration(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB) @@ -1587,31 +1715,56 @@ namespace CtrEditor.Simulacion // ✅ 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); + (simCurve curveA, Vector3 directionA) = GetCurveAndDirectionFromCollidable(pair.A); + (simCurve curveB, Vector3 directionB) = GetCurveAndDirectionFromCollidable(pair.B); - // ✅ RESTAURADO - llamar métodos públicos del SimulationManager - // Si hay contacto botella-transporte, crear motor LinearAxisMotor + // ✅ VERIFICAR SI YA SE CREÓ MOTOR PARA EVITAR DUPLICADOS + BodyHandle? bottleHandle = null; + BodyHandle? elementHandle = null; + if (botella != null && (transportA != null || transportB != null)) { var transport = transportA ?? transportB; - _simulationManager.TryCreateTransportMotor(botella, transport); // ✅ Llamar método público + bottleHandle = botella.BodyHandle; + elementHandle = transport.BodyHandle; - // Fricción alta para transportes + var pairKey = (bottleHandle.Value, elementHandle.Value); + if (!_simulationManager._motorsCreated.Contains(pairKey)) + { + _simulationManager.TryCreateTransportMotor(botella, transport); + _simulationManager._motorsCreated.Add(pairKey); + System.Diagnostics.Debug.WriteLine($"[Manifold] Motor transporte: {bottleHandle} → {elementHandle}"); + } + + // Fricción alta para transportes (SIEMPRE aplicar para colisión física) pairMaterial.FrictionCoefficient = 0.9f; pairMaterial.MaximumRecoveryVelocity = 1f; pairMaterial.SpringSettings = new SpringSettings(80, 6); } - // Si hay contacto botella-curva, crear motor LinearAxisMotor + // ✅ SIMPLIFICADO: Si hay contacto botella-triángulo de curva, crear motor con dirección específica else if (botella != null && (curveA != null || curveB != null)) { var curve = curveA ?? curveB; - _simulationManager.TryCreateCurveMotor(botella, curve); // ✅ Llamar método público + var direction = curveA != null ? directionA : directionB; + var triangleHandle = curveA != null ? pair.A.BodyHandle : pair.B.BodyHandle; - // Fricción alta para curvas + bottleHandle = botella.BodyHandle; + elementHandle = triangleHandle; + + // ✅ SIMPLIFICADO: Crear motor usando sistema natural de colisiones de BEPU + var pairKey = (bottleHandle.Value, elementHandle.Value); + if (!_simulationManager._motorsCreated.Contains(pairKey)) + { + _simulationManager.TryCreateCurveMotor(botella, curve, direction, triangleHandle); + _simulationManager._motorsCreated.Add(pairKey); + System.Diagnostics.Debug.WriteLine($"[Manifold] Motor curva: {bottleHandle} → {triangleHandle}"); + } + + // Fricción alta para curvas (SIEMPRE aplicar para colisión física) 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) @@ -1696,6 +1849,30 @@ namespace CtrEditor.Simulacion } return null; } + + /// + /// ✅ NUEVO: Obtiene curva y dirección específica del triángulo + /// + private (simCurve curve, Vector3 direction) GetCurveAndDirectionFromCollidable(CollidableReference collidable) + { + if (_simulationManager?.simulation != null) + { + if (collidable.Mobility == CollidableMobility.Kinematic) + { + var bodyHandle = collidable.BodyHandle; + + // Buscar curva que contenga este triángulo + var curve = _simulationManager.GetCurveFromTriangleBodyHandle(bodyHandle); + if (curve != null) + { + // Obtener dirección específica del triángulo + var direction = curve.GetTriangleDirection(bodyHandle); + return (curve, direction); + } + } + } + return (null, Vector3.Zero); + } /// /// ✅ NUEVO MÉTODO: Aplicar fuerzas de frenado directamente en el manifold @@ -1854,12 +2031,26 @@ namespace CtrEditor.Simulacion if (direction.Length() < 0.001f) return; - // ✅ USAR CUERPO DE LA CURVA EN LUGAR DE REFERENCIA ESTÁTICA + // ✅ CONVERTIR DIRECCIÓN MUNDIAL A COORDENADAS LOCALES DE LA CURVA + Vector3 localAxisDirection; + + if (_simulationManager.simulation.Bodies.BodyExists(curve.BodyHandle)) + { + var curveBody = _simulationManager.simulation.Bodies[curve.BodyHandle]; + var inverseCurveOrientation = Quaternion.Conjugate(curveBody.Pose.Orientation); + localAxisDirection = Vector3.Transform(direction, inverseCurveOrientation); + } + else + { + // Fallback: usar dirección mundial si no se puede obtener orientación + localAxisDirection = direction; + } + var motor = new LinearAxisMotor() { LocalOffsetA = Vector3.Zero, LocalOffsetB = Vector3.Zero, - LocalAxis = direction, + LocalAxis = localAxisDirection, // ✅ Usar dirección en coordenadas locales TargetVelocity = curve.SpeedMetersPerSecond, Settings = new MotorSettings(Math.Max(bottle.Mass * 20f, 8f), 4f) }; @@ -1894,13 +2085,34 @@ namespace CtrEditor.Simulacion if (_simulationManager.simulation.Solver != null) { _simulationManager.simulation.Solver.Remove(motorHandle); + System.Diagnostics.Debug.WriteLine($"[RemoveMotor] Motor eliminado: {motorHandle}"); } - + _activeMotors.Remove(bottle.BodyHandle); _motorToBottle.Remove(motorHandle); + // ✅ NUEVO: Limpiar pares de motores creados para esta botella + var bottleHandle = bottle.BodyHandle; + var motorsToRemove = _simulationManager._motorsCreated + .Where(pair => pair.bottle == bottleHandle) + .ToList(); + + foreach (var pair in motorsToRemove) + { + _simulationManager._motorsCreated.Remove(pair); + System.Diagnostics.Debug.WriteLine($"[MotorManager] Motor pair cleaned: {pair.bottle} - {pair.element}"); + } + // Limpiar referencias de forma segura bottle.CurrentTransport?.BottlesOnTransport.Remove(bottle); + + // ✅ SIMPLIFICADO: Solo limpiar listas de botellas en curvas + var curves = _simulationManager.Cuerpos.OfType(); + foreach (var curve in curves) + { + curve.BottlesOnCurve.Remove(bottle); + } + bottle.RemoveFromTransport(); } } @@ -1938,19 +2150,113 @@ namespace CtrEditor.Simulacion } } - public void UpdateCurveMotorDirection(simBotella bottle, simCurve curve) + /// + /// ✅ NUEVO: Actualiza la dirección de un motor de curva existente para un nuevo triángulo + /// + public void UpdateCurveMotorDirection(simBotella bottle, simCurve curve, Vector3 newDirection, BodyHandle newTriangleHandle) { - // 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)) + try { - RemoveMotor(bottle); - CreateCurveMotor(bottle, curve); + if (bottle == null || curve == null || _simulationManager?.simulation == null) + return; + + if (!_activeMotors.ContainsKey(bottle.BodyHandle)) + return; // No hay motor activo + + // Validar que la dirección sea válida + if (newDirection.Length() < 0.001f) + return; + + // ✅ ESTRATEGIA: Recrear motor con nueva dirección (más eficiente que actualizar en BEPU) + var oldMotorHandle = _activeMotors[bottle.BodyHandle]; + + // Eliminar motor anterior sin limpiar pares silenciados (los conservamos) + if (_simulationManager.simulation.Solver != null) + { + _simulationManager.simulation.Solver.Remove(oldMotorHandle); + // Motor anterior eliminado y recreado con nueva dirección + } + + _activeMotors.Remove(bottle.BodyHandle); + _motorToBottle.Remove(oldMotorHandle); + + // Crear nuevo motor con nueva dirección (reutilizar CreateCurveMotorWithDirection) + CreateCurveMotorWithDirection(bottle, curve, newDirection); + + System.Diagnostics.Debug.WriteLine($"[UpdateCurveMotorDirection] Motor actualizado con nueva dirección para triángulo: {newTriangleHandle}"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[MotorManager] Error updating curve motor direction: {ex.Message}"); } } /// - /// ✅ NUEVO: Elimina todos los motors conectados a un body específico (transportes y curvas) + /// ✅ NUEVO - Crea motor de curva con dirección precalculada + /// + public void CreateCurveMotorWithDirection(simBotella bottle, simCurve curve, Vector3 direction) + { + 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; + + // Validar que la dirección no sea cero + if (direction.Length() < 0.001f) + return; + + // ✅ CONVERTIR DIRECCIÓN MUNDIAL A COORDENADAS LOCALES DE LA CURVA + // El cuerpo principal de la curva no tiene rotación, pero aún necesitamos coordenadas locales + Vector3 localAxisDirection; + + if (_simulationManager.simulation.Bodies.BodyExists(curve.BodyHandle)) + { + var curveBody = _simulationManager.simulation.Bodies[curve.BodyHandle]; + var inverseCurveOrientation = Quaternion.Conjugate(curveBody.Pose.Orientation); + localAxisDirection = Vector3.Transform(direction, inverseCurveOrientation); + } + else + { + // Fallback: usar dirección mundial si no se puede obtener orientación + localAxisDirection = direction; + } + + var motor = new LinearAxisMotor() + { + LocalOffsetA = Vector3.Zero, + LocalOffsetB = Vector3.Zero, + LocalAxis = localAxisDirection, // ✅ Usar dirección en coordenadas locales + TargetVelocity = curve.SpeedMetersPerSecond, + Settings = new MotorSettings(Math.Max(bottle.Mass * 20f, 8f), 4f) + }; + + var motorHandle = _simulationManager.simulation.Solver.Add( + curve.BodyHandle, + bottle.BodyHandle, + motor + ); + // Logging con coordenadas mundiales y locales para depuración + System.Diagnostics.Debug.WriteLine($"[CreateCurveMotorWithDirection] Bottle: {bottle.BodyHandle} World: ({direction.X:F2}, {direction.Y:F2}) Local: ({localAxisDirection.X:F2}, {localAxisDirection.Y:F2}) Speed: {curve.SpeedMetersPerSecond}"); + + _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 with direction: {ex.Message}"); + } + } + + /// + /// ✅ OPTIMIZADO: Elimina todos los motors conectados a un body específico de forma eficiente /// public void RemoveMotorsByBodyHandle(BodyHandle bodyHandle) { @@ -1959,7 +2265,26 @@ namespace CtrEditor.Simulacion if (_simulationManager?.simulation?.Solver == null) return; - // Encontrar todos los motors que involucran este body + // ✅ OPTIMIZACIÓN: Solo procesar si hay motors activos + if (_activeMotors.Count == 0) + { + return; // No hay motors, salir inmediatamente + } + + // ✅ OPTIMIZACIÓN: Identificar rápidamente el tipo de body + var targetObject = _simulationManager.GetSimBaseFromBodyHandle(bodyHandle); + if (targetObject == null) + { + // ✅ Podría ser un triángulo de curva, buscar curva que lo contenga + var curve = _simulationManager.GetCurveFromTriangleBodyHandle(bodyHandle); + if (curve == null) + { + return; // No es un objeto relevante + } + targetObject = curve; + } + + // Encontrar motors que deben eliminarse var motorsToRemove = new List<(BodyHandle bottleHandle, ConstraintHandle motorHandle)>(); foreach (var kvp in _activeMotors) @@ -1967,38 +2292,21 @@ namespace CtrEditor.Simulacion 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) + // ✅ OPTIMIZADO: Verificación directa por tipo de objeto + switch (targetObject) { - 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; - } + case simTransporte transport: + shouldRemove = bottle.CurrentTransport?.BodyHandle.Equals(bodyHandle) == true; + break; - // Verificar triángulos de la curva - if (curve._triangleBodyHandles != null && - curve._triangleBodyHandles.Contains(bodyHandle)) - { - shouldRemove = true; - break; - } - } + case simCurve curve: + // Verificar si la botella está en esta curva específica + shouldRemove = curve.BottlesOnCurve.Contains(bottle); + break; } if (shouldRemove) @@ -2008,7 +2316,7 @@ namespace CtrEditor.Simulacion } } - // Eliminar todos los motors encontrados + // Eliminar motors encontrados foreach (var (bottleHandle, motorHandle) in motorsToRemove) { try @@ -2018,7 +2326,7 @@ namespace CtrEditor.Simulacion if (_motorToBottle.TryGetValue(motorHandle, out var bottle)) { - bottle.RemoveFromTransport(); // Esto limpia tanto transporte como curva + bottle.RemoveFromTransport(); _motorToBottle.Remove(motorHandle); } } @@ -2028,7 +2336,11 @@ namespace CtrEditor.Simulacion } } - System.Diagnostics.Debug.WriteLine($"[MotorManager] Removed {motorsToRemove.Count} motors connected to body {bodyHandle}"); + // ✅ SOLO LOG SI SE ELIMINARON MOTORS + if (motorsToRemove.Count > 0) + { + System.Diagnostics.Debug.WriteLine($"[MotorManager] Removed {motorsToRemove.Count} motors connected to body {bodyHandle}"); + } } catch (Exception ex) { @@ -2058,6 +2370,19 @@ namespace CtrEditor.Simulacion } _activeMotors.Clear(); _motorToBottle.Clear(); + + // ✅ NUEVO: Limpiar cachés de direcciones de todas las curvas y pares silenciados + if (_simulationManager?.Cuerpos != null) + { + var curves = _simulationManager.Cuerpos.OfType(); + foreach (var curve in curves) + { + curve._triangleDirections?.Clear(); + } + + // También limpiar pares de motores creados + _simulationManager._motorsCreated?.Clear(); + } } catch (Exception ex) { @@ -2091,8 +2416,8 @@ namespace CtrEditor.Simulacion // ✅ NUEVO - sistema de motores private MotorManager _motorManager; - // ✅ NUEVO - tabla de exclusiones para callbacks silenciados - private HashSet<(BodyHandle bottle, BodyHandle transport)> _silencedPairs; + // ✅ SEPARADO - tabla para bloquear creación de motores (NO bloquea colisiones físicas) + internal HashSet<(BodyHandle bottle, BodyHandle element)> _motorsCreated; // ✅ NUEVO - contador de frames para optimizaciones private int _frameCount = 0; @@ -2181,8 +2506,8 @@ namespace CtrEditor.Simulacion _bottleHandles.Remove(bottle.BodyHandle); // Eliminar motor si existe _motorManager.RemoveMotor(bottle); - // Eliminar de pares silenciados - RemoveFromSilencedPairs(bottle.BodyHandle); + // Eliminar de pares de motores creados + RemoveFromMotorsCreated(bottle.BodyHandle); break; case simTransporte transport: _transportHandles.Remove(transport.BodyHandle); @@ -2208,9 +2533,9 @@ namespace CtrEditor.Simulacion } } - private void RemoveFromSilencedPairs(BodyHandle bottleHandle) + private void RemoveFromMotorsCreated(BodyHandle bottleHandle) { - _silencedPairs.RemoveWhere(pair => pair.bottle == bottleHandle); + _motorsCreated.RemoveWhere(pair => pair.bottle == bottleHandle); } // ✅ NUEVOS MÉTODOS - obtener objetos por handle @@ -2249,7 +2574,7 @@ namespace CtrEditor.Simulacion _barrierHandles = new HashSet(); _discardHandles = new HashSet(); _bottleHandles = new HashSet(); - _silencedPairs = new HashSet<(BodyHandle, BodyHandle)>(); + _motorsCreated = new HashSet<(BodyHandle, BodyHandle)>(); // ✅ CONSERVAR - resto del constructor igual bufferPool = new BufferPool(); @@ -2295,7 +2620,7 @@ namespace CtrEditor.Simulacion _barreraContacts.Clear(); _descarteContacts.Clear(); _botellasParaEliminar.Clear(); - _silencedPairs.Clear(); // ✅ NUEVO + _motorsCreated.Clear(); // ✅ NUEVO } // ✅ NUEVO - limpiar clasificaciones @@ -2445,8 +2770,8 @@ namespace CtrEditor.Simulacion // ✅ NUEVO - verificación periódica de salidas de transporte CheckBottleExitsFromTransports(); - // ✅ NUEVO - actualización periódica de direcciones de curvas - UpdateCurveMotorDirections(); + // ✅ ELIMINADO - UpdateCurveMotorDirections() ya no es necesario + // Los triángulos de curva funcionan como mini-transportes usando colisiones naturales // ✅ CONSERVAR - limitación de rotación y mantener botellas despiertas foreach (var cuerpo in Cuerpos.OfType().ToList()) @@ -2526,35 +2851,20 @@ namespace CtrEditor.Simulacion private void ProcessBottleExitsTransport(simBotella bottle) { - UnsileceCurrentPair(bottle); + RemoveCurrentMotorPair(bottle); _motorManager.RemoveMotor(bottle); } - private void UnsileceCurrentPair(simBotella bottle) + private void RemoveCurrentMotorPair(simBotella bottle) { if (bottle.CurrentTransport != null) { - _silencedPairs.Remove((bottle.BodyHandle, bottle.CurrentTransport.BodyHandle)); + _motorsCreated.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); - } - } - } + // ✅ ELIMINADO: Ya no necesitamos UpdateCurveMotorDirections + // Cada triángulo de curva funciona como un mini-transporte usando el sistema de colisiones natural public void Remove(simBase Objeto) { @@ -2661,6 +2971,8 @@ namespace CtrEditor.Simulacion _motorManager?.RemoveMotorsByBodyHandle(bodyHandle); } + + public void Dispose() { Clear(); @@ -2683,20 +2995,17 @@ namespace CtrEditor.Simulacion if (bottle == null || transport == null || _motorManager == null) return; - // Verificar si ya tiene un motor activo o está silenciado + // Verificar si ya tiene un motor activo 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); + + System.Diagnostics.Debug.WriteLine($"[TryCreateTransportMotor] Motor creado: {bottle.BodyHandle} - {transport.BodyHandle}"); } catch (Exception ex) { @@ -2704,33 +3013,7 @@ namespace CtrEditor.Simulacion } } - /// - /// ✅ 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 @@ -3198,5 +3481,55 @@ namespace CtrEditor.Simulacion return lineStart + lineDirection * projectionLength; } + + /// + /// ✅ MODIFICADO: Sistema dinámico de motores de curva - permite actualización entre triángulos + /// + public void TryCreateCurveMotor(simBotella bottle, simCurve curve, Vector3 triangleDirection, BodyHandle triangleHandle) + { + try + { + // Validaciones básicas + if (bottle == null || curve == null || _motorManager == null) + return; + + // Verificar que la curva tenga velocidad + if (Math.Abs(curve.Speed) < 0.001f) + return; // No crear motor para curvas detenidas + + // Verificar que la dirección del triángulo sea válida + if (triangleDirection.Length() < 0.001f) + return; + + // ✅ NUEVO: Si ya tiene motor de curva, actualizarlo; si no, crear uno nuevo + if (bottle.HasActiveMotor) + { + // Verificar si el motor actual es de una curva + var currentCurves = Cuerpos.OfType().Where(c => c.BottlesOnCurve.Contains(bottle)); + if (currentCurves.Any()) + { + // Actualizar motor existente con nueva dirección del triángulo + _motorManager.UpdateCurveMotorDirection(bottle, curve, triangleDirection, triangleHandle); + + System.Diagnostics.Debug.WriteLine($"[TryCreateCurveMotor] Motor actualizado: {bottle.BodyHandle} → Triangle {triangleHandle}"); + return; + } + else + { + // Tiene motor de transporte, no interferir + return; + } + } + + // ✅ Crear nuevo motor de curva + _motorManager.CreateCurveMotorWithDirection(bottle, curve, triangleDirection); + + System.Diagnostics.Debug.WriteLine($"[TryCreateCurveMotor] Nuevo motor: {bottle.BodyHandle} → Triangle {triangleHandle}"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[SimulationManager] Error in TryCreateCurveMotor with direction: {ex.Message}"); + } + } } } \ No newline at end of file diff --git a/Simulacion/BEPUVisualization3D.cs b/Simulacion/BEPUVisualization3D.cs index e98f69b..35f8aef 100644 --- a/Simulacion/BEPUVisualization3D.cs +++ b/Simulacion/BEPUVisualization3D.cs @@ -45,6 +45,24 @@ namespace CtrEditor.Simulacion /// /// Manager que sincroniza el mundo 3D de BEPU con la visualización HelixToolkit /// Usa simBase como clave única para evitar problemas de reutilización de BodyHandle + /// + /// ✅ NUEVO: Funcionalidad de Debug para Triángulos + /// Para activar la visualización de triángulos individuales de curvas: + /// + /// // Activar modo debug + /// visualizationManager.SetDebugTrianglesMode(true); + /// + /// // Desactivar modo debug (volver a superficie continua) + /// visualizationManager.SetDebugTrianglesMode(false); + /// + /// // Verificar si está activo + /// bool isDebugActive = visualizationManager.IsDebugTrianglesModeEnabled(); + /// + /// En modo debug: + /// - Cada triángulo de BEPU se muestra separado con un pequeño offset + /// - Los triángulos tienen bordes wireframe para mejor visualización + /// - Se usa un material naranja semi-transparente para distinguir del modo normal + /// - Se muestran mensajes de debug en la consola con información detallada /// public class BEPUVisualization3DManager { @@ -53,6 +71,9 @@ namespace CtrEditor.Simulacion private Dictionary simBaseToModelMap; private Dictionary lastKnownDimensions; + // ✅ NUEVO: Flag de debug para mostrar triángulos individuales de curvas + public static bool DebugShowIndividualTriangles { get; set; } = true; + public HelixViewport3D Viewport3D { get => viewport3D; @@ -221,20 +242,17 @@ namespace CtrEditor.Simulacion // Caso especial: simBotella usa esfera en BEPU pero se visualiza como cilindro if (simObj is simBotella botella) { - System.Diagnostics.Debug.WriteLine($"[3D Create] Creating CYLINDER visualization for simBotella (BEPU uses sphere)"); visual3D = CreateCylinderVisualization(botella.Radius, botella.Height, simObj); } // Caso especial: simCurve usa múltiples triángulos pero se visualiza como superficie curva else if (simObj is simCurve curve) { - System.Diagnostics.Debug.WriteLine($"[3D Create] Creating CURVE visualization for simCurve"); visual3D = CreateCurveVisualization(curve); } else { // Para otros objetos, usar la forma real de BEPU var dimensions = GetShapeDimensions(shapeIndex, simObj); - System.Diagnostics.Debug.WriteLine($"[3D Create] Shape type: {dimensions.ShapeType}, Radius: {dimensions.Radius}"); if (dimensions.ShapeType == BepuPhysics.Collidables.Sphere.Id) { @@ -269,8 +287,6 @@ namespace CtrEditor.Simulacion // Agregar a la vista 3D y asociar con simBase viewport3D.Children.Add(visual3D); simBaseToModelMap[simObj] = visual3D; - - System.Diagnostics.Debug.WriteLine($"[3D Create] Successfully created 3D visualization for {simObj.GetType().Name}"); } } catch (Exception ex) @@ -338,11 +354,25 @@ namespace CtrEditor.Simulacion { var meshBuilder = new MeshBuilder(); - // ✅ NUEVO: Usar directamente los triángulos originales de BEPU (debug real) - CreateCurveMeshFromBEPUTriangles(meshBuilder, curve); + // ✅ NUEVO: Elegir entre visualización normal o debug según el flag + if (DebugShowIndividualTriangles) + { + CreateCurveDebugMeshWithIndividualTriangles(meshBuilder, curve); + } + else + { + // ✅ Usar directamente los triángulos originales de BEPU (superficie continua) + CreateCurveMeshFromBEPUTriangles(meshBuilder, curve); + } var geometry = meshBuilder.ToMesh(); - var model = new GeometryModel3D(geometry, GetMaterialForSimBase(curve)); + + // ✅ NUEVO: Usar material específico para debug si está activo + Material material = DebugShowIndividualTriangles ? + GetDebugMaterialForCurve(curve) : + GetMaterialForSimBase(curve); + + var model = new GeometryModel3D(geometry, material); var visual = new ModelVisual3D(); visual.Content = model; @@ -365,8 +395,6 @@ namespace CtrEditor.Simulacion // Obtener los triángulos originales directamente de BEPU var originalTriangles = curve.GetOriginalTriangles(); - System.Diagnostics.Debug.WriteLine($"[3D Debug] Creating curve mesh from {originalTriangles.Count} BEPU triangles"); - if (originalTriangles.Count == 0) { System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: No triangles found in BEPU curve"); @@ -410,7 +438,6 @@ namespace CtrEditor.Simulacion meshBuilder.AddQuad(p3Bottom, p1Bottom, p1Top, p3Top); // Lado 3-1 } - System.Diagnostics.Debug.WriteLine($"[3D Debug] Successfully created mesh from BEPU triangles"); } catch (Exception ex) { @@ -422,6 +449,136 @@ namespace CtrEditor.Simulacion } } + /// + /// ✅ NUEVO: Función de debug que muestra triángulos individuales de BEPU + /// Cada triángulo se renderiza de manera separada para poder hacer debug visual + /// + private void CreateCurveDebugMeshWithIndividualTriangles(MeshBuilder meshBuilder, simCurve curve) + { + try + { + // Obtener los triángulos originales directamente de BEPU + var originalTriangles = curve.GetOriginalTriangles(); + + if (originalTriangles.Count == 0) + { + System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: No triangles found in BEPU curve"); + return; + } + + System.Diagnostics.Debug.WriteLine($"[3D Debug] Creating debug visualization for {originalTriangles.Count} triangles"); + + // Altura muy pequeña para mostrar triángulos planos (como en BEPU) + const float debugHeight = 0.02f; // Más bajo que la superficie normal para diferenciarlo + const float triangleSeparation = 0.01f; // Separación pequeña entre triángulos para distinguirlos + + // Convertir cada triángulo de BEPU a un triángulo 3D individual + for (int i = 0; i < originalTriangles.Count; i++) + { + var triangle = originalTriangles[i]; + + if (triangle.Count != 3) + { + System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: Invalid triangle {i} with {triangle.Count} vertices"); + continue; + } + + // Calcular el centro del triángulo para aplicar separación + var center = new System.Numerics.Vector3( + (triangle[0].X + triangle[1].X + triangle[2].X) / 3f, + (triangle[0].Y + triangle[1].Y + triangle[2].Y) / 3f, + 0 + ); + + // Aplicar una pequeña separación desde el centro para distinguir triángulos + var offset = center * triangleSeparation; + + // Crear triángulo superior (visible desde arriba) + var p1Top = new Point3D(triangle[0].X + offset.X, triangle[0].Y + offset.Y, debugHeight); + var p2Top = new Point3D(triangle[1].X + offset.X, triangle[1].Y + offset.Y, debugHeight); + var p3Top = new Point3D(triangle[2].X + offset.X, triangle[2].Y + offset.Y, debugHeight); + + // Crear triángulo inferior (visible desde abajo) + var p1Bottom = new Point3D(triangle[0].X + offset.X, triangle[0].Y + offset.Y, 0); + var p2Bottom = new Point3D(triangle[1].X + offset.X, triangle[1].Y + offset.Y, 0); + var p3Bottom = new Point3D(triangle[2].X + offset.X, triangle[2].Y + offset.Y, 0); + + // Agregar triángulo superior (normal hacia arriba) + meshBuilder.AddTriangle(p1Top, p2Top, p3Top); + + // Agregar triángulo inferior (normal hacia abajo) + meshBuilder.AddTriangle(p1Bottom, p3Bottom, p2Bottom); + + // ✅ OPCIONAL: Agregar bordes/wireframe para mejor visualización del debug + // Crear líneas delgadas en los bordes del triángulo para verlo mejor + const float edgeThickness = 0.005f; + + // Borde 1-2 + AddDebugEdge(meshBuilder, p1Top, p2Top, p1Bottom, p2Bottom, edgeThickness); + // Borde 2-3 + AddDebugEdge(meshBuilder, p2Top, p3Top, p2Bottom, p3Bottom, edgeThickness); + // Borde 3-1 + AddDebugEdge(meshBuilder, p3Top, p1Top, p3Bottom, p1Bottom, edgeThickness); + } + + System.Diagnostics.Debug.WriteLine($"[3D Debug] Successfully created debug mesh with {originalTriangles.Count} individual triangles"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR creating debug mesh: {ex.Message}"); + + // Fallback: mostrar mensaje de error en consola y usar geometría básica + System.Diagnostics.Debug.WriteLine($"[3D Debug] Falling back to basic debug geometry"); + CreateBasicDebugGeometry(meshBuilder, curve); + } + } + + /// + /// ✅ NUEVO: Agrega un borde delgado entre dos puntos para debug visual + /// + private void AddDebugEdge(MeshBuilder meshBuilder, Point3D p1Top, Point3D p2Top, Point3D p1Bottom, Point3D p2Bottom, float thickness) + { + try + { + // Crear un cilindro muy delgado como borde + meshBuilder.AddCylinder(p1Top, p2Top, thickness, 4, false, false); + meshBuilder.AddCylinder(p1Bottom, p2Bottom, thickness, 4, false, false); + meshBuilder.AddCylinder(p1Top, p1Bottom, thickness, 4, false, false); + meshBuilder.AddCylinder(p2Top, p2Bottom, thickness, 4, false, false); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR adding debug edge: {ex.Message}"); + } + } + + /// + /// ✅ NUEVO: Geometría básica de debug cuando fallan otras opciones + /// + private void CreateBasicDebugGeometry(MeshBuilder meshBuilder, simCurve curve) + { + try + { + // Crear una geometría muy simple que indique que hay un problema + float innerRadius = curve.InnerRadius; + float outerRadius = curve.OuterRadius; + float centerRadius = (innerRadius + outerRadius) / 2f; + + // Crear un triángulo básico como indicador de debug + var p1 = new Point3D(centerRadius, 0, 0); + var p2 = new Point3D(centerRadius * Math.Cos(Math.PI/3), centerRadius * Math.Sin(Math.PI/3), 0); + var p3 = new Point3D(centerRadius * Math.Cos(-Math.PI/3), centerRadius * Math.Sin(-Math.PI/3), 0); + + meshBuilder.AddTriangle(p1, p2, p3); + + System.Diagnostics.Debug.WriteLine($"[3D Debug] Created basic debug geometry as fallback"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR creating basic debug geometry: {ex.Message}"); + } + } + /// /// Método fallback que recrea la geometría (mantener por compatibilidad) /// @@ -519,23 +676,15 @@ namespace CtrEditor.Simulacion { dimensionsChanged = !currentDimensions.Equals(lastDimensions); - // ✅ LOGGING: Debug para curvas - if (simObj is simCurve && dimensionsChanged) - { - System.Diagnostics.Debug.WriteLine($"[3D Update] CURVE dimensions changed! Old: Inner={lastDimensions.InnerRadius}, Outer={lastDimensions.OuterRadius}, Start={lastDimensions.StartAngle}, End={lastDimensions.EndAngle}"); - System.Diagnostics.Debug.WriteLine($"[3D Update] CURVE dimensions changed! New: Inner={currentDimensions.InnerRadius}, Outer={currentDimensions.OuterRadius}, Start={currentDimensions.StartAngle}, End={currentDimensions.EndAngle}"); - } } else { dimensionsChanged = true; // Primera vez - System.Diagnostics.Debug.WriteLine($"[3D Update] First time creating geometry for {simObj.GetType().Name}"); } // Si las dimensiones cambiaron, recrear la geometría if (dimensionsChanged) { - System.Diagnostics.Debug.WriteLine($"[3D Update] Recreating geometry for {simObj.GetType().Name}"); RecreateVisualizationGeometry(simObj, visual, currentDimensions); lastKnownDimensions[simObj] = currentDimensions; } @@ -595,7 +744,6 @@ namespace CtrEditor.Simulacion dimensions.StartAngle = curve.StartAngle; dimensions.EndAngle = curve.EndAngle; dimensions.ShapeType = -1; // Tipo especial para curvas - System.Diagnostics.Debug.WriteLine($"[3D GetDimensions] Curve - Inner: {curve.InnerRadius}, Outer: {curve.OuterRadius}, Start: {curve.StartAngle}, End: {curve.EndAngle}"); return dimensions; } @@ -668,17 +816,21 @@ namespace CtrEditor.Simulacion // Caso especial: simCurve necesita recreación completa de geometría else if (simObj is simCurve curve) { - System.Diagnostics.Debug.WriteLine($"[3D Recreate] Creating CURVE mesh from BEPU triangles - Inner: {curve.InnerRadius}, Outer: {curve.OuterRadius}, Start: {curve.StartAngle}, End: {curve.EndAngle}"); - var meshBuilder = new MeshBuilder(); - // ✅ NUEVO: Usar directamente los triángulos de BEPU (debug real) - CreateCurveMeshFromBEPUTriangles(meshBuilder, curve); + // ✅ CORREGIDO: Respetar el flag de debug también en recreación + if (DebugShowIndividualTriangles) + { + CreateCurveDebugMeshWithIndividualTriangles(meshBuilder, curve); + newMaterial = GetDebugMaterialForCurve(curve); + } + else + { + CreateCurveMeshFromBEPUTriangles(meshBuilder, curve); + newMaterial = GetMaterialForSimBase(simObj); + } newGeometry = meshBuilder.ToMesh(); - newMaterial = GetMaterialForSimBase(simObj); - - System.Diagnostics.Debug.WriteLine($"[3D Recreate] CURVE mesh created successfully with {newGeometry?.Positions?.Count ?? 0} vertices"); } else if (dimensions.ShapeType == BepuPhysics.Collidables.Sphere.Id) { @@ -711,7 +863,6 @@ namespace CtrEditor.Simulacion { geometryModel.Geometry = newGeometry; geometryModel.Material = newMaterial; - System.Diagnostics.Debug.WriteLine($"[3D Recreate] Successfully recreated geometry for {simObj.GetType().Name}"); } } catch (Exception ex) @@ -730,6 +881,25 @@ namespace CtrEditor.Simulacion lastKnownDimensions.Clear(); } + /// + /// ✅ NUEVO: Material específico para debug de curvas + /// + /// Curva en modo debug + /// Material especial para visualización de debug + private Material GetDebugMaterialForCurve(simCurve curve) + { + // Material wireframe semi-transparente para debug + // Color naranja brillante para que sea muy visible + var debugBrush = new SolidColorBrush(Color.FromRgb(255, 165, 0)); // Naranja + debugBrush.Opacity = 0.7; // 70% opacidad para ver superposiciones + + return MaterialHelper.CreateMaterial( + debugBrush, + specularPower: 20, // Menos reflectante para mejor visibilidad + ambient: 250 // Más ambiente para que se vea bien en todas las condiciones + ); + } + /// /// Función centralizada para obtener el material apropiado según el tipo de simBase /// @@ -837,6 +1007,119 @@ namespace CtrEditor.Simulacion } } + /// + /// ✅ NUEVO: Activa o desactiva el modo debug para mostrar triángulos individuales de curvas + /// + /// True para activar debug, false para modo normal + /// True para forzar actualización inmediata de todas las curvas + public void SetDebugTrianglesMode(bool enableDebug, bool forceRefresh = true) + { + bool wasChanged = DebugShowIndividualTriangles != enableDebug; + DebugShowIndividualTriangles = enableDebug; + + System.Diagnostics.Debug.WriteLine($"[3D Debug] Debug triangles mode: {(enableDebug ? "ENABLED" : "DISABLED")}"); + + if (wasChanged && forceRefresh) + { + RefreshCurveVisualizations(); + } + } + + /// + /// ✅ NUEVO: Fuerza la regeneración de todas las visualizaciones de curvas + /// Útil cuando se cambia el modo debug + /// + public void RefreshCurveVisualizations() + { + if (simulationManager?.Cuerpos == null) + return; + + try + { + System.Diagnostics.Debug.WriteLine($"[3D Debug] Refreshing curve visualizations..."); + + // Encontrar todas las curvas y forzar su regeneración + var curvesToRefresh = simulationManager.Cuerpos.OfType().ToList(); + + foreach (var curve in curvesToRefresh) + { + // Remover las dimensiones conocidas para forzar recreación + lastKnownDimensions.Remove(curve); + + // Forzar actualización de la visualización + if (simBaseToModelMap.ContainsKey(curve)) + { + UpdateVisualizationFromSimBase(curve); + } + } + + System.Diagnostics.Debug.WriteLine($"[3D Debug] Refreshed {curvesToRefresh.Count} curve visualizations"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR refreshing curve visualizations: {ex.Message}"); + } + } + + /// + /// ✅ NUEVO: Obtiene el estado actual del modo debug + /// + /// True si el modo debug está activo + public bool IsDebugTrianglesModeEnabled() + { + return DebugShowIndividualTriangles; + } + + /// + /// ✅ NUEVO: Obtiene información detallada de debug sobre los triángulos de una curva + /// Útil para debug sin cambiar la visualización + /// + /// Curva para analizar + /// Información de debug como string + public string GetCurveDebugInfo(simCurve curve) + { + if (curve == null) + return "ERROR: Curva es null"; + + try + { + var triangles = curve.GetOriginalTriangles(); + var debugInfo = new System.Text.StringBuilder(); + + debugInfo.AppendLine($"=== DEBUG INFO PARA CURVA ==="); + debugInfo.AppendLine($"Parámetros de curva:"); + debugInfo.AppendLine($" - Radio Interior: {curve.InnerRadius}"); + debugInfo.AppendLine($" - Radio Exterior: {curve.OuterRadius}"); + debugInfo.AppendLine($" - Ángulo Inicio: {curve.StartAngle} rad ({curve.StartAngle * 180 / Math.PI:F1}°)"); + debugInfo.AppendLine($" - Ángulo Fin: {curve.EndAngle} rad ({curve.EndAngle * 180 / Math.PI:F1}°)"); + debugInfo.AppendLine($""); + debugInfo.AppendLine($"Triángulos en BEPU: {triangles.Count}"); + + for (int i = 0; i < Math.Min(triangles.Count, 10); i++) // Mostrar máximo 10 triángulos + { + var triangle = triangles[i]; + debugInfo.AppendLine($" Triángulo {i + 1}:"); + debugInfo.AppendLine($" P1: ({triangle[0].X:F3}, {triangle[0].Y:F3}, {triangle[0].Z:F3})"); + debugInfo.AppendLine($" P2: ({triangle[1].X:F3}, {triangle[1].Y:F3}, {triangle[1].Z:F3})"); + debugInfo.AppendLine($" P3: ({triangle[2].X:F3}, {triangle[2].Y:F3}, {triangle[2].Z:F3})"); + } + + if (triangles.Count > 10) + { + debugInfo.AppendLine($" ... y {triangles.Count - 10} triángulos más"); + } + + debugInfo.AppendLine($""); + debugInfo.AppendLine($"Modo debug actual: {(DebugShowIndividualTriangles ? "ACTIVADO" : "DESACTIVADO")}"); + + return debugInfo.ToString(); + } + catch (Exception ex) + { + return $"ERROR obteniendo info de debug: {ex.Message}"; + } + } + /// /// Carga el estado guardado de la cámara desde EstadoPersistente /// @@ -854,7 +1137,6 @@ namespace CtrEditor.Simulacion FieldOfView = cameraSettings.FieldOfView }; - System.Diagnostics.Debug.WriteLine($"[3D Camera] Loaded camera state from persistent storage"); } catch (Exception ex) { @@ -893,7 +1175,6 @@ namespace CtrEditor.Simulacion cameraSettings.FieldOfView = camera.FieldOfView; EstadoPersistente.Instance.GuardarEstado(); - System.Diagnostics.Debug.WriteLine($"[3D Camera] Saved camera state to persistent storage"); } } catch (Exception ex) @@ -916,7 +1197,6 @@ namespace CtrEditor.Simulacion viewport3D.MouseUp += (sender, e) => SaveCameraState(); viewport3D.MouseWheel += (sender, e) => SaveCameraState(); - System.Diagnostics.Debug.WriteLine($"[3D Camera] Subscribed to camera change events"); } catch (Exception ex) {