From ba073a9e80a379732e53df76a5fe2bbdded988f1 Mon Sep 17 00:00:00 2001 From: Miguel Date: Thu, 3 Jul 2025 17:47:21 +0200 Subject: [PATCH] =?UTF-8?q?Se=20simplific=C3=B3=20el=20m=C3=A9todo=20Remov?= =?UTF-8?q?erBody=20en=20simTransporte=20y=20simCurve,=20confiando=20en=20?= =?UTF-8?q?BEPU=20para=20la=20limpieza=20de=20constraints.=20Se=20implemen?= =?UTF-8?q?taron=20nuevos=20m=C3=A9todos=20para=20la=20creaci=C3=B3n=20y?= =?UTF-8?q?=20actualizaci=C3=B3n=20de=20motores,=20mejorando=20la=20gesti?= =?UTF-8?q?=C3=B3n=20de=20motores=20din=C3=A1micos.=20Adem=C3=A1s,=20se=20?= =?UTF-8?q?optimiz=C3=B3=20la=20verificaci=C3=B3n=20de=20contacto=20entre?= =?UTF-8?q?=20botellas=20y=20transportes/curvas,=20y=20se=20introdujo=20un?= =?UTF-8?q?=20sistema=20de=20eliminaci=C3=B3n=20diferida=20para=20objetos,?= =?UTF-8?q?=20mejorando=20la=20seguridad=20y=20eficiencia=20del=20c=C3=B3d?= =?UTF-8?q?igo.=20Se=20eliminaron=20m=C3=A9todos=20obsoletos=20relacionado?= =?UTF-8?q?s=20con=20la=20depuraci=C3=B3n=20de=20tri=C3=A1ngulos=20en=20BE?= =?UTF-8?q?PU.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Simulacion/BEPU.cs | 1073 +++++++++++++++-------------- Simulacion/BEPUVisualization3D.cs | 85 --- 2 files changed, 555 insertions(+), 603 deletions(-) diff --git a/Simulacion/BEPU.cs b/Simulacion/BEPU.cs index 27a734a..b1a1025 100644 --- a/Simulacion/BEPU.cs +++ b/Simulacion/BEPU.cs @@ -269,11 +269,24 @@ namespace CtrEditor.Simulacion public void RemoverBody() { - // Solo intentar remover si realmente hemos creado un cuerpo antes - if (_bodyCreated && _simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + try { - _simulation.Bodies.Remove(BodyHandle); - _bodyCreated = false; // Marcar como no creado después de remover + // Solo intentar remover si realmente hemos creado un cuerpo antes + if (_bodyCreated && _simulation != null && _simulation.Bodies != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + _simulation.Bodies.Remove(BodyHandle); + _bodyCreated = false; // Marcar como no creado después de remover + System.Diagnostics.Debug.WriteLine($"[simBase.RemoverBody] ✅ Body eliminado: {BodyHandle}"); + } + else + { + System.Diagnostics.Debug.WriteLine($"[simBase.RemoverBody] ⚠️ Body no existe o no creado: {BodyHandle}"); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[simBase.RemoverBody] ❌ ERROR: {ex.Message}"); + _bodyCreated = false; // Marcar como no creado en caso de error } } @@ -378,14 +391,13 @@ namespace CtrEditor.Simulacion // ✅ 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) + + public simTransporte(Simulation simulation, List deferredActions, float width, float height, Vector2 topLeft, float angle = 0) { _simulation = simulation; _deferredActions = deferredActions; - _simulationManager = simulationManager; // ✅ NUEVA REFERENCIA + Width = width; Height = height; @@ -566,40 +578,11 @@ namespace CtrEditor.Simulacion } /// - /// ✅ MODIFICADO: RemoverBody que limpia motors conectados antes de eliminar el body + /// ✅ SIMPLIFICADO: RemoverBody que solo elimina 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}"); - } + base.RemoverBody(); } public void Create(float width, float height, Vector3 bepuPosition, float wpfAngle = 0) @@ -859,12 +842,16 @@ namespace CtrEditor.Simulacion public bool isOnBrakeTransport; // Nueva propiedad para marcar si está en un transporte con freno public simTransporte CurrentBrakeTransport { get; set; } // ✅ NUEVA: Referencia al transporte de freno actual - // ✅ NUEVO SISTEMA SIMPLIFICADO: Un solo motor por botella - public ConstraintHandle CurrentMotor { get; set; } = default; - public ConstraintHandle CurrentDistanceLimit { get; set; } = default; - public BodyHandle CurrentElementHandle { get; set; } = default; // Body del elemento actual (transporte o curva) - public bool HasActiveMotor => CurrentMotor.Value != 0; - public bool HasDistanceLimit => CurrentDistanceLimit.Value != 0; + // ✅ NUEVO SISTEMA SIMPLIFICADO: Motor dinámico que se crea solo cuando es necesario + public ConstraintHandle CurrentMotor { get; private set; } = default; + public simBase CurrentMotorTarget { get; private set; } = null; // Transporte o curva actual + public bool _hasMotor = false; // ✅ BANDERA PÚBLICA para acceso desde callbacks + public bool HasMotor => _hasMotor; + + // ✅ NUEVAS PROPIEDADES para el motor dinámico + public Vector3 CurrentDirection { get; private set; } = Vector3.UnitX; // Dirección por defecto (1,0,0) + public float CurrentSpeed { get; private set; } = 0f; // Velocidad por defecto + public bool IsOnElement { get; private set; } = false; // Si está sobre un elemento activo private List _deferredActions; @@ -881,6 +868,8 @@ namespace CtrEditor.Simulacion // ✅ USAR COORDINATECONVERTER para conversión centralizada var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), Radius + zPos_Transporte + zAltura_Transporte); Create(position3D); + + // ✅ ELIMINADO: No crear motor permanente - se creará cuando sea necesario } public float CenterX @@ -979,6 +968,247 @@ namespace CtrEditor.Simulacion _bodyCreated = true; // Marcar que hemos creado un cuerpo } + /// + /// ✅ NUEVO: Crea o actualiza el motor conectándolo al transporte/curva actual + /// + public void CreateOrUpdateMotor(simBase target, Vector3 direction, float speed) + { + try + { + // ✅ VALIDAR DIRECCIÓN + if (direction.Length() < 0.001f) + { + return; + } + + // ✅ VALIDAR QUE EL TARGET Y LA SIMULACIÓN EXISTAN + if (target == null || _simulation == null || !_simulation.Bodies.BodyExists(BodyHandle)) + { + return; + } + + // ✅ VERIFICAR SI NECESITAMOS CREAR O ACTUALIZAR EL MOTOR + bool needsNewMotor = false; + + if (!HasMotor) + { + // ✅ PRIMERA VEZ: Crear motor nuevo + needsNewMotor = true; + System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] 🆕 Creando motor nuevo para {target?.GetType().Name}"); + } + else if (CurrentMotorTarget != target) + { + // ✅ CAMBIO DE OBJETO: Eliminar motor anterior y crear uno nuevo + RemoveCurrentMotor(); + needsNewMotor = true; + System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] 🔄 Cambiando motor de {CurrentMotorTarget?.GetType().Name} a {target?.GetType().Name}"); + } + else + { + // ✅ MISMO OBJETO: Solo actualizar velocidad + UpdateMotorSpeed(direction, speed); + return; + } + + // ✅ CREAR NUEVO MOTOR SI ES NECESARIO + if (needsNewMotor && target != null) + { + CreateMotorForTarget(target, direction, speed); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] ❌ ERROR: {ex.Message}"); + } + } + + /// + /// ✅ NUEVO: Crea un motor específico para un transporte o curva + /// + private void CreateMotorForTarget(simBase target, Vector3 direction, float speed) + { + try + { + if (_simulation == null || _simulation.Solver == null || !_simulation.Bodies.BodyExists(BodyHandle) || target == null) + { + System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ Simulación, solver, body o target no disponible"); + return; + } + + // ✅ VERIFICAR QUE EL TARGET TENGA UN BODY VÁLIDO + if (!_simulation.Bodies.BodyExists(target.BodyHandle)) + { + System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ Target body no existe: {target.BodyHandle}"); + return; + } + + // ✅ VERIFICAR QUE NO TENGA YA UN MOTOR VÁLIDO + if (HasMotor && CurrentMotor.Value != 0) + { + System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ⚠️ Ya existe un motor válido: {CurrentMotor}"); + return; + } + + // ✅ CALCULAR VELOCIDAD EFECTIVA + var effectiveSpeed = CalculateEffectiveSpeed(direction, speed); + + // ✅ CREAR MOTOR CONECTADO AL TARGET + var motor = new LinearAxisMotor() + { + LocalOffsetA = Vector3.Zero, // Botella + LocalOffsetB = Vector3.Zero, // Target + LocalAxis = Vector3.UnitX, // Dirección fija del motor + TargetVelocity = effectiveSpeed, + Settings = new MotorSettings(Math.Max(_mass * 20f, 8f), 4f) + }; + + // ✅ CONECTAR BOTELLA CON EL TARGET (transporte o curva) + CurrentMotor = _simulation.Solver.Add(BodyHandle, BodyHandle, motor); + + // ✅ VERIFICAR QUE EL MOTOR SE CREÓ CORRECTAMENTE + if (CurrentMotor.Value == 0) + { + System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ Error: Motor no se creó correctamente"); + return; + } + + CurrentMotorTarget = target; + _hasMotor = true; // ✅ ESTABLECER BANDERA + + // ✅ ACTUALIZAR PROPIEDADES INTERNAS + CurrentDirection = direction; + CurrentSpeed = speed; + IsOnElement = Math.Abs(speed) > 0.001f; + + System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ✅ Motor creado:"); + System.Diagnostics.Debug.WriteLine($" Botella: {BodyHandle}"); + System.Diagnostics.Debug.WriteLine($" Target: {target.BodyHandle} ({target.GetType().Name})"); + System.Diagnostics.Debug.WriteLine($" Motor Handle: {CurrentMotor} (Value: {CurrentMotor.Value})"); + System.Diagnostics.Debug.WriteLine($" Dirección: {direction}"); + System.Diagnostics.Debug.WriteLine($" Velocidad efectiva: {effectiveSpeed:F3}"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ ERROR: {ex.Message}"); + } + } + + /// + /// ✅ NUEVO: Actualiza solo la velocidad del motor existente + /// + private void UpdateMotorSpeed(Vector3 direction, float speed) + { + try + { + if (!HasMotor || _simulation == null) + { + return; + } + + // ✅ OBTENER LA DESCRIPCIÓN ACTUAL DEL MOTOR + _simulation.Solver.GetDescription(CurrentMotor, out LinearAxisMotor motor); + + // ✅ CALCULAR VELOCIDAD EFECTIVA + var effectiveSpeed = CalculateEffectiveSpeed(direction, speed); + + // ✅ ACTUALIZAR SOLO LA VELOCIDAD + motor.TargetVelocity = effectiveSpeed; + + // ✅ ACTUALIZAR EL MOTOR EN EL SOLVER + _simulation.Solver.ApplyDescription(CurrentMotor, motor); + + // ✅ ACTUALIZAR PROPIEDADES INTERNAS + CurrentDirection = direction; + CurrentSpeed = speed; + IsOnElement = Math.Abs(speed) > 0.001f; + + System.Diagnostics.Debug.WriteLine($"[UpdateMotorSpeed] 🔄 Velocidad actualizada: {effectiveSpeed:F3}"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[UpdateMotorSpeed] ❌ ERROR: {ex.Message}"); + } + } + + /// + /// ✅ NUEVO: Elimina el motor actual + /// + public void RemoveCurrentMotor() + { + try + { + if (HasMotor && _simulation != null && _simulation.Solver != null) + { + // ✅ VERIFICAR QUE EL MOTOR EXISTE ANTES DE ELIMINARLO + if (CurrentMotor.Value != 0) + { + try + { + _simulation.Solver.Remove(CurrentMotor); + System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] 🗑️ Motor eliminado: {CurrentMotor}"); + } + catch (Exception removeEx) + { + System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ⚠️ Error eliminando motor {CurrentMotor}: {removeEx.Message}"); + // Continuar con la limpieza incluso si falla la eliminación + } + } + else + { + System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ⚠️ Motor ya eliminado o inválido: {CurrentMotor}"); + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ❌ ERROR: {ex.Message}"); + } + finally + { + // ✅ LIMPIAR REFERENCIAS SIEMPRE + CurrentMotor = default; + CurrentMotorTarget = null; + _hasMotor = false; // ✅ LIMPIAR BANDERA + CurrentDirection = Vector3.UnitX; + CurrentSpeed = 0f; + IsOnElement = false; + } + } + + /// + /// ✅ NUEVO: Detiene el motor (velocidad 0) pero mantiene la conexión + /// + public void StopMotor() + { + UpdateMotorSpeed(CurrentDirection, 0f); + } + + /// + /// ✅ NUEVO MÉTODO: Calcular velocidad efectiva para dirección deseada + /// + private float CalculateEffectiveSpeed(Vector3 desiredDirection, float desiredSpeed) + { + // ✅ NORMALIZAR LA DIRECCIÓN DESEADA + var normalizedDirection = Vector3.Normalize(desiredDirection); + + // ✅ CALCULAR LA PROYECCIÓN EN EL EJE X (dirección del motor fijo) + // Como el motor está fijo en UnitX, solo podemos controlar el movimiento en X + var projectionX = normalizedDirection.X; + + // ✅ CALCULAR VELOCIDAD EFECTIVA + // Si la dirección es principalmente en X, usar la velocidad completa + // Si la dirección es perpendicular a X, la velocidad será 0 + var effectiveSpeed = projectionX * desiredSpeed; + + System.Diagnostics.Debug.WriteLine($"[CalculateEffectiveSpeed] 📐 Cálculo:"); + System.Diagnostics.Debug.WriteLine($" Dirección deseada: {desiredDirection} (Normalizada: {normalizedDirection})"); + System.Diagnostics.Debug.WriteLine($" Proyección en X: {projectionX:F3}"); + System.Diagnostics.Debug.WriteLine($" Velocidad deseada: {desiredSpeed:F3}"); + System.Diagnostics.Debug.WriteLine($" Velocidad efectiva: {effectiveSpeed:F3}"); + + return effectiveSpeed; + } + public void SetDiameter(float diameter) { Radius = diameter / 2f; @@ -1068,45 +1298,14 @@ namespace CtrEditor.Simulacion return isOnTransports > 0; } - // ✅ SISTEMA SIMPLIFICADO: Métodos unificados de gestión de motores - public void AssignMotor(ConstraintHandle motor, BodyHandle elementHandle) + /// + /// ✅ SIMPLIFICADO: RemoverBody que confía en BEPU para limpiar constraints + /// + public new void RemoverBody() { - CurrentMotor = motor; - CurrentElementHandle = elementHandle; - } - - public void AssignDistanceLimit(ConstraintHandle distanceLimit) - { - CurrentDistanceLimit = distanceLimit; - } - - public void RemoveAllConstraints() - { - CurrentMotor = default; - CurrentDistanceLimit = default; - CurrentElementHandle = default; - } - - // ✅ LEGACY: Mantener compatibilidad con código existente - public void AssignToTransport(simTransporte transport, ConstraintHandle motor) - { - AssignMotor(motor, transport.BodyHandle); - } - - public void RemoveFromTransport() - { - RemoveAllConstraints(); + base.RemoverBody(); } - public void AssignAngularMotor(simCurve curve, ConstraintHandle motor) - { - AssignMotor(motor, curve.BodyHandle); - } - - public void RemoveCurrentMotor() - { - RemoveAllConstraints(); - } } /// @@ -1126,14 +1325,16 @@ namespace CtrEditor.Simulacion private List _deferredActions; + // ✅ NUEVO: Almacenar el centro real de la curva + private Vector3 _curveCenter; + // ✅ SIMPLIFICADO: Propiedades esenciales únicamente public List BottlesOnCurve { get; private set; } = new List(); // ✅ EVENTO para actualización de motores public event Action OnSpeedChanged; - // ✅ REFERENCIA para limpiar motors - private SimulationManagerBEPU _simulationManager; + // ✅ NUEVO: Almacenar triángulos creados para acceso directo private Triangle[] _storedTriangles; @@ -1143,17 +1344,24 @@ namespace CtrEditor.Simulacion 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 - public simCurve(Simulation simulation, List deferredActions, float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 topLeft, float unused = 0, SimulationManagerBEPU simulationManager = null) + // ✅ NUEVO: Propiedad para acceder al centro real de la curva + public Vector3 CurveCenter => _curveCenter; + + public simCurve(Simulation simulation, List deferredActions, float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 topLeft, float unused = 0) { _simulation = simulation; _deferredActions = deferredActions; - _simulationManager = simulationManager; _innerRadius = innerRadius; _outerRadius = outerRadius; // ✅ SIMPLIFICADO: Usar conversión directa WPF grados → BEPU radianes _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle); _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle); + // ✅ NUEVO: Calcular y almacenar el centro real de la curva + var curveSize = outerRadius * 2f; + var zPosition = zAltura_Curve / 2f + zPos_Curve; + _curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(topLeft, curveSize, curveSize, 0f, zPosition); + // ✅ SIMPLIFICADO: Crear la curva directamente Create(innerRadius, outerRadius, startAngle, endAngle, topLeft, 0); } @@ -1165,19 +1373,6 @@ namespace CtrEditor.Simulacion OnSpeedChanged?.Invoke(this); } - public void UpdateCurve(float innerRadius, float outerRadius, float startAngle, float endAngle) - { - _innerRadius = innerRadius; - _outerRadius = outerRadius; - // ✅ 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(); - Create(currentPosition); - } - /// /// ✅ NUEVO: Actualiza tanto posición como rotación desde parámetros WPF /// @@ -1190,12 +1385,12 @@ namespace CtrEditor.Simulacion _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle); _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle); - // ✅ USAR COORDINATECONVERTER para conversión centralizada + // ✅ NUEVO: Actualizar el centro real de la curva var curveSize = outerRadius * 2f; var zPosition = zAltura_Curve / 2f + zPos_Curve; - var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition); + _curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition); - Create(bepuCenter); + Create(_curveCenter); } /// @@ -1205,9 +1400,9 @@ namespace CtrEditor.Simulacion { var curveSize = _outerRadius * 2f; var zPosition = GetPosition().Z; // Mantener Z actual - var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition); + _curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition); - Create(bepuCenter); + Create(_curveCenter); } /// @@ -1215,9 +1410,8 @@ namespace CtrEditor.Simulacion /// internal Vector2 GetWpfTopLeft() { - var bepuCenter = GetPosition(); var curveSize = _outerRadius * 2f; - return CoordinateConverter.CalculateWpfTopLeftFromBepuCenter(bepuCenter, curveSize, curveSize, 0f); + return CoordinateConverter.CalculateWpfTopLeftFromBepuCenter(_curveCenter, curveSize, curveSize, 0f); } /// @@ -1310,116 +1504,15 @@ namespace CtrEditor.Simulacion } } - /// - /// ✅ NUEVO: Información de debug sobre el Mesh real de BEPU - /// - public string GetBEPUMeshDebugInfo() - { - try - { - if (_simulation == null || !_simulation.Bodies.BodyExists(BodyHandle)) - return "ERROR: Body no existe en simulación"; - - var body = _simulation.Bodies[BodyHandle]; - var collidable = body.Collidable; - var shapeIndex = collidable.Shape; - - var info = new System.Text.StringBuilder(); - info.AppendLine("=== DEBUG INFO MESH REAL DE BEPU ==="); - info.AppendLine($"Position: {body.Pose.Position}"); - info.AppendLine($"Orientation: {body.Pose.Orientation}"); - info.AppendLine($"Shape Index: {shapeIndex.Index}"); - info.AppendLine($"Shape Type: {shapeIndex.Type} (Mesh.Id = {BepuPhysics.Collidables.Mesh.Id})"); - info.AppendLine($"Shape exists: {shapeIndex.Exists}"); - - if (_storedTriangles != null) - { - info.AppendLine($"Triángulos almacenados (locales): {_storedTriangles.Length}"); - - // Mostrar algunos triángulos locales de ejemplo - int triangleCount = Math.Min(3, _storedTriangles.Length); - info.AppendLine($""); - info.AppendLine($"Primeros {triangleCount} triángulos LOCALES:"); - - for (int i = 0; i < triangleCount; i++) - { - var triangle = _storedTriangles[i]; - - info.AppendLine($" Triángulo LOCAL {i}:"); - info.AppendLine($" A: ({triangle.A.X:F3}, {triangle.A.Y:F3}, {triangle.A.Z:F3})"); - info.AppendLine($" B: ({triangle.B.X:F3}, {triangle.B.Y:F3}, {triangle.B.Z:F3})"); - info.AppendLine($" C: ({triangle.C.X:F3}, {triangle.C.Y:F3}, {triangle.C.Z:F3})"); - } - - // ✅ CORREGIDO: Mostrar triángulos transformados a coordenadas mundiales usando nuevo método - try - { - var worldTriangles = GetWorldBEPUTriangles(); - info.AppendLine($""); - info.AppendLine($"Primeros {triangleCount} triángulos MUNDIALES (transformados):"); - - for (int i = 0; i < Math.Min(triangleCount, worldTriangles.Length); i++) - { - var triangle = worldTriangles[i]; - - info.AppendLine($" Triángulo MUNDIAL {i}:"); - info.AppendLine($" A: ({triangle.A.X:F3}, {triangle.A.Y:F3}, {triangle.A.Z:F3})"); - info.AppendLine($" B: ({triangle.B.X:F3}, {triangle.B.Y:F3}, {triangle.B.Z:F3})"); - info.AppendLine($" C: ({triangle.C.X:F3}, {triangle.C.Y:F3}, {triangle.C.Z:F3})"); - } - } - catch (Exception ex) - { - info.AppendLine($"ERROR obteniendo triángulos mundiales: {ex.Message}"); - } - } - else - { - info.AppendLine($"ERROR: No hay triángulos almacenados"); - } - - return info.ToString(); - } - catch (Exception ex) - { - return $"ERROR obteniendo debug info: {ex.Message}"; - } - } public new void RemoverBody() { - // ✅ SIMPLIFICADO: Limpiar motors conectados - RemoveConnectedMotors(); - // ✅ NUEVO: Limpiar triángulos almacenados _storedTriangles = null; // ✅ SIMPLIFICADO: Solo remover el cuerpo principal (Mesh único) base.RemoverBody(); } - - /// - /// ✅ NUEVO: Limpia todos los motors conectados a esta curva - /// - private void RemoveConnectedMotors() - { - try - { - // ✅ USAR REFERENCIA DIRECTA al SimulationManager - if (_simulationManager != null) - { - // ✅ SIMPLIFICADO: Solo limpiar motors del cuerpo principal - _simulationManager.RemoveMotorsConnectedToBody(BodyHandle); - } - - // 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) { @@ -1433,15 +1526,15 @@ namespace CtrEditor.Simulacion _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle); _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle); - // ✅ USAR COORDINATECONVERTER para conversión centralizada + // ✅ NUEVO: Actualizar el centro real de la curva // Para curvas, el "tamaño" es el diámetro del radio exterior var curveSize = outerRadius * 2f; var zPosition = 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); + _curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition); - Create(bepuCenter); // Sin rotación adicional + Create(_curveCenter); // Sin rotación adicional } private void Create(Vector3 position) @@ -1707,38 +1800,37 @@ namespace CtrEditor.Simulacion return false; // NO generar contacto físico } - // ✅ DETECCIÓN SIMPLIFICADA de transportes y curvas + // ✅ NUEVO SISTEMA SIMPLIFICADO: Solo registrar contactos para actualización en Step var transportA = GetTransportFromCollidable(pair.A); var transportB = GetTransportFromCollidable(pair.B); var curveA = GetCurveFromCollidable(pair.A); var curveB = GetCurveFromCollidable(pair.B); - // ✅ CONTACTO BOTELLA-TRANSPORTE + // ✅ CONTACTO BOTELLA-TRANSPORTE: Crear o actualizar motor inmediatamente if (botella != null && (transportA != null || transportB != null)) { var transport = transportA ?? transportB; - // Verificar si ya tiene motor para este elemento específico - if (botella.CurrentElementHandle.Value == 0 || !botella.CurrentElementHandle.Equals(transport.BodyHandle)) - { - _simulationManager.TryCreateTransportMotor(botella, transport); - } + // ✅ CREAR O ACTUALIZAR MOTOR DINÁMICO INMEDIATAMENTE + transport.UpdateCachedProperties(); + var direction = transport.DirectionVector; + var speed = transport.SpeedMetersPerSecond; + botella.CreateOrUpdateMotor(transport, direction, speed); // Fricción alta para transportes pairMaterial.FrictionCoefficient = 0.9f; pairMaterial.MaximumRecoveryVelocity = 1f; pairMaterial.SpringSettings = new SpringSettings(80, 6); } - // ✅ CONTACTO BOTELLA-CURVA (ahora también usa LinearAxisMotor + DistanceLimit) + // ✅ CONTACTO BOTELLA-CURVA: Crear o actualizar motor inmediatamente else if (botella != null && (curveA != null || curveB != null)) { var curve = curveA ?? curveB; - // Verificar si ya tiene motor para este elemento específico - if (botella.CurrentElementHandle.Value == 0 || !botella.CurrentElementHandle.Equals(curve.BodyHandle)) - { - _simulationManager.TryCreateCurveLinearMotor(botella, curve); - } + // ✅ CREAR O ACTUALIZAR MOTOR DINÁMICO INMEDIATAMENTE + var direction = _simulationManager.CalculateCurveDirectionFromBottlePosition(curve, botella); + var speed = curve.Speed; + botella.CreateOrUpdateMotor(curve, direction, speed); // Fricción alta para curvas pairMaterial.FrictionCoefficient = 0.9f; @@ -1757,6 +1849,8 @@ namespace CtrEditor.Simulacion return true; } + + private simTransporte GetTransportFromCollidable(CollidableReference collidable) { if (_simulationManager?.simulation != null) @@ -1827,6 +1921,10 @@ namespace CtrEditor.Simulacion return null; } + + + + /// /// ✅ NUEVO MÉTODO: Aplicar fuerzas de frenado directamente en el manifold /// @@ -1896,134 +1994,7 @@ namespace CtrEditor.Simulacion } - public class MotorManager - { - private SimulationManagerBEPU _simulationManager; - private Dictionary _bottleConstraints; - - public MotorManager(SimulationManagerBEPU simulationManager) - { - _simulationManager = simulationManager; - _bottleConstraints = new Dictionary(); - } - /// - /// ✅ ÚNICO MÉTODO: Crear motor LinearAxisMotor con dirección calculada - /// - public bool CreateLinearMotor(simBotella bottle, BodyHandle elementHandle, Vector3 direction, float speed, bool isCurve = false) - { - // Verificar si ya tenemos motor para este elemento específico - if (_bottleConstraints.TryGetValue(bottle.BodyHandle, out var existing)) - { - if (existing.elementHandle.Equals(elementHandle)) - { - return false; // Ya tiene motor para este elemento, no recrear - } - - // Tiene motor para otro elemento, eliminarlo primero - RemoveBottleConstraints(bottle); - } - - // Validaciones básicas - if (direction.Length() < 0.001f) - return false; - - // ✅ SOLO LinearAxisMotor - sin AngularAxisMotor - var motor = new LinearAxisMotor() - { - LocalOffsetA = Vector3.Zero, - LocalOffsetB = Vector3.Zero, - LocalAxis = Vector3.Normalize(direction), - TargetVelocity = speed, - Settings = new MotorSettings(Math.Max(bottle.Mass * 20f, 8f), 4f) - }; - - var motorHandle = _simulationManager.simulation.Solver.Add(elementHandle, bottle.BodyHandle, motor); - - ConstraintHandle? distanceHandle = null; - - // Si es curva, crear también DistanceLimit - if (isCurve) - { - var element = _simulationManager.GetSimBaseFromBodyHandle(elementHandle); - if (element is simCurve curve) - { - var radius = (bottle.GetPosition() - curve.GetPosition()).Length(); - if (radius > 1e-3f) - { - var distanceLimit = new DistanceLimit() - { - LocalOffsetA = Vector3.Zero, - LocalOffsetB = Vector3.Zero, - MinimumDistance = 4*radius, - MaximumDistance = 4*radius, - SpringSettings = new SpringSettings(30f, 1f) - }; - - distanceHandle = _simulationManager.simulation.Solver.Add(elementHandle, bottle.BodyHandle, distanceLimit); - bottle.AssignDistanceLimit(distanceHandle.Value); - } - } - } - - // Registrar constraints - _bottleConstraints[bottle.BodyHandle] = (motorHandle, distanceHandle, elementHandle); - bottle.AssignMotor(motorHandle, elementHandle); - - System.Diagnostics.Debug.WriteLine($"[MotorManager] Motor creado: {bottle.BodyHandle} → {elementHandle} (Curve: {isCurve})"); - return true; - } - - /// - /// ✅ ÚNICO MÉTODO DE ELIMINACIÓN: Eliminar todos los constraints de una botella - /// - public void RemoveBottleConstraints(simBotella bottle) - { - if (_bottleConstraints.TryGetValue(bottle.BodyHandle, out var constraints)) - { - try - { - _simulationManager.simulation.Solver.Remove(constraints.motor); - } - catch { } - - if (constraints.distanceLimit.HasValue) - { - try - { - _simulationManager.simulation.Solver.Remove(constraints.distanceLimit.Value); - } - catch { } - } - - _bottleConstraints.Remove(bottle.BodyHandle); - } - - bottle.RemoveAllConstraints(); - - System.Diagnostics.Debug.WriteLine($"[MotorManager] Constraints removidos para botella: {bottle.BodyHandle}"); - } - - public void Clear() - { - try - { - foreach (var kvp in _bottleConstraints) - { - try { _simulationManager.simulation.Solver.Remove(kvp.Value.motor); } catch { } - if (kvp.Value.distanceLimit.HasValue) - { - try { _simulationManager.simulation.Solver.Remove(kvp.Value.distanceLimit.Value); } catch { } - } - } - _bottleConstraints.Clear(); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"[MotorManager] Error during clear: {ex.Message}"); - } - } - } public class SimulationManagerBEPU { @@ -2047,11 +2018,9 @@ namespace CtrEditor.Simulacion private HashSet _discardHandles; private HashSet _bottleHandles; - // ✅ NUEVO - sistema de motores - private MotorManager _motorManager; - // ✅ 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; @@ -2061,14 +2030,14 @@ namespace CtrEditor.Simulacion private Dictionary> _descarteContacts; private HashSet _botellasParaEliminar; - // ✅ ELIMINAR COMPLETAMENTE - // private Dictionary _transportContacts; // NO SE USA MÁS - // private Dictionary _brakeTransportContacts; // NO SE USA MÁS + // ✅ NUEVO - sistema de eliminación diferida para evitar problemas de sincronización + private Queue _pendingRemovals; + private object _removalLock = new object(); + + private object _contactsLock = new object(); - private Dictionary _distanceLimits; // ✅ NUEVO: DistanceLimit por botella - /// /// Obtiene el objeto simBase correspondiente a un BodyHandle /// @@ -2119,10 +2088,7 @@ namespace CtrEditor.Simulacion { case simBotella bottle: _bottleHandles.Remove(bottle.BodyHandle); - // Eliminar motor si existe - _motorManager.RemoveBottleConstraints(bottle); - // Eliminar de pares de motores creados - RemoveFromMotorsCreated(bottle.BodyHandle); + break; case simTransporte transport: _transportHandles.Remove(transport.BodyHandle); @@ -2140,10 +2106,7 @@ namespace CtrEditor.Simulacion } } - private void RemoveFromMotorsCreated(BodyHandle bottleHandle) - { - _motorsCreated.RemoveWhere(pair => pair.bottle == bottleHandle); - } + // ✅ NUEVOS MÉTODOS - obtener objetos por handle private simBotella GetBottleByHandle(BodyHandle handle) @@ -2175,13 +2138,16 @@ namespace CtrEditor.Simulacion _descarteContacts = new Dictionary>(); _botellasParaEliminar = new HashSet(); - // ✅ NUEVOS - sistemas de filtrado y motores + // ✅ NUEVOS - sistemas de filtrado y contactos _transportHandles = new HashSet(); _curveHandles = new HashSet(); _barrierHandles = new HashSet(); _discardHandles = new HashSet(); _bottleHandles = new HashSet(); - _motorsCreated = new HashSet<(BodyHandle, BodyHandle)>(); + + // ✅ NUEVO - inicializar sistema de eliminación diferida + _pendingRemovals = new Queue(); + // ✅ CONSERVAR - resto del constructor igual bufferPool = new BufferPool(); @@ -2204,16 +2170,14 @@ namespace CtrEditor.Simulacion 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()) @@ -2227,7 +2191,7 @@ namespace CtrEditor.Simulacion _barreraContacts.Clear(); _descarteContacts.Clear(); _botellasParaEliminar.Clear(); - _motorsCreated.Clear(); // ✅ NUEVO + } // ✅ NUEVO - limpiar clasificaciones @@ -2236,6 +2200,12 @@ namespace CtrEditor.Simulacion _barrierHandles.Clear(); _discardHandles.Clear(); _bottleHandles.Clear(); + + // ✅ NUEVO - limpiar eliminaciones pendientes + lock (_removalLock) + { + _pendingRemovals.Clear(); + } // ✅ CONSERVAR - resto del método igual var cuerposToRemove = new List(Cuerpos); @@ -2338,7 +2308,7 @@ namespace CtrEditor.Simulacion action(); } _deferredActions.Clear(); - + // ✅ CONSERVAR - validaciones de simulación if (simulation?.Bodies == null) { @@ -2370,15 +2340,7 @@ namespace CtrEditor.Simulacion 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(); - - // ✅ 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()) @@ -2397,6 +2359,13 @@ namespace CtrEditor.Simulacion } } + // ✅ NUEVO: Detener motores de botellas que no están en contacto con elementos + // Esto se ejecuta cada 10 frames para eficiencia + if (_frameCount % 10 == 0) + { + StopMotorsForBottlesNotInContact(); + } + // ✅ CONSERVAR - sistemas que funcionan bien ProcessBarreraContacts(); ProcessDescarteContacts(); @@ -2424,76 +2393,48 @@ namespace CtrEditor.Simulacion } } - private void CheckBottleExitsFromTransports() - { - // ✅ Ejecutar cada 10 frames para eficiencia - if (_frameCount % 10 != 0) return; - - var activeBottles = Cuerpos.OfType() - .Where(b => b.HasActiveMotor && b.CurrentElementHandle.Value != 0) - .ToList(); - - foreach (var bottle in activeBottles) - { - var element = GetSimBaseFromBodyHandle(bottle.CurrentElementHandle); - if (element is simTransporte transport) - { - if (!IsBottleOnTransport(bottle, transport)) - { - ProcessBottleExitsTransport(bottle); - } - } - // Las curvas no necesitan verificación de salida porque usan DistanceLimit - } - } - 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) - { - RemoveCurrentMotorPair(bottle); - _motorManager.RemoveBottleConstraints(bottle); - } - - private void RemoveCurrentMotorPair(simBotella bottle) - { - if (bottle.CurrentElementHandle.Value != 0) - { - _motorsCreated.Remove((bottle.BodyHandle, bottle.CurrentElementHandle)); - } - } - - // ✅ ELIMINADO: Ya no necesitamos UpdateCurveMotorDirections - // Cada triángulo de curva funciona como un mini-transporte usando el sistema de colisiones natural + /// + /// ✅ NUEVO: Marca un objeto para eliminación diferida (más seguro) + /// public void Remove(simBase Objeto) { - // ✅ SIMPLIFICADO - eliminar lógica de masa especial - UnregisterObjectHandle(Objeto); // ✅ NUEVO + if (Objeto == null) return; - // ✅ NUEVO - Limpiar dimensiones almacenadas en osBase - CtrEditor.ObjetosSim.osBase.ClearStoredDimensions(Objeto); - - Objeto.RemoverBody(); - Cuerpos.Remove(Objeto); - - if (Is3DUpdateEnabled) + try { - Visualization3DManager?.SynchronizeWorld(); + // ✅ SIMPLIFICADO - eliminar lógica de masa especial + UnregisterObjectHandle(Objeto); // ✅ NUEVO + + // ✅ NUEVO - Limpiar dimensiones almacenadas en osBase + CtrEditor.ObjetosSim.osBase.ClearStoredDimensions(Objeto); + + // ✅ NUEVO - Agregar a cola de eliminación diferida + lock (_removalLock) + { + _pendingRemovals.Enqueue(Objeto); + } + + // ✅ REMOVER de la lista inmediatamente para evitar referencias colgantes + Cuerpos.Remove(Objeto); + + if (Is3DUpdateEnabled) + { + Visualization3DManager?.SynchronizeWorld(); + } + + System.Diagnostics.Debug.WriteLine($"[Remove] ✅ Objeto marcado para eliminación diferida: {Objeto.GetType().Name}"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[Remove] ❌ Error marcando objeto {Objeto?.GetType().Name}: {ex.Message}"); + + // ✅ CRÍTICO: Siempre remover de la lista incluso si falló + Cuerpos.Remove(Objeto); } } + public simBotella AddCircle(float diameter, Vector2 position, float mass) { @@ -2510,7 +2451,7 @@ namespace CtrEditor.Simulacion public simTransporte AddRectangle(float width, float height, Vector2 position, float angle) { - var transporte = new simTransporte(simulation, _deferredActions, width, height, position, angle, this); // ✅ PASAR REFERENCIA + var transporte = new simTransporte(simulation, _deferredActions, width, height, position, angle); Cuerpos.Add(transporte); RegisterObjectHandle(transporte); // ✅ NUEVO - incluye UpdateCachedProperties() @@ -2564,7 +2505,7 @@ namespace CtrEditor.Simulacion 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 + var curve = new simCurve(simulation, _deferredActions, innerRadius, outerRadius, startAngle, endAngle, topLeft, unused); Cuerpos.Add(curve); RegisterObjectHandle(curve); // ✅ NUEVO @@ -2575,22 +2516,165 @@ namespace CtrEditor.Simulacion return curve; } - /// - /// ✅ NUEVO: Método público para limpiar motors conectados a un body específico - /// - public void RemoveMotorsConnectedToBody(BodyHandle bodyHandle) - { - // Encontrar todas las botellas conectadas a este body y eliminar sus constraints - var bottlesToRemoveConstraints = Cuerpos.OfType() - .Where(b => b.HasActiveMotor && b.CurrentElementHandle.Equals(bodyHandle)) - .ToList(); - foreach (var bottle in bottlesToRemoveConstraints) + + + + /// + /// ✅ NUEVO: Detiene motores de botellas que no están en contacto con elementos + /// + private void StopMotorsForBottlesNotInContact() + { + try { - _motorManager.RemoveBottleConstraints(bottle); + var allBottles = Cuerpos.OfType().ToList(); + + foreach (var bottle in allBottles) + { + if (bottle != null && bottle.HasMotor && bottle.IsOnElement) + { + // ✅ VERIFICAR SI LA BOTELLA ESTÁ REALMENTE EN CONTACTO CON ALGÚN ELEMENTO + bool isInContact = false; + + // Verificar contacto con transportes + foreach (var transport in Cuerpos.OfType()) + { + if (IsBottleInContactWithTransport(bottle, transport)) + { + isInContact = true; + break; + } + } + + // Verificar contacto con curvas + if (!isInContact) + { + foreach (var curve in Cuerpos.OfType()) + { + if (IsBottleInContactWithCurve(bottle, curve)) + { + isInContact = true; + break; + } + } + } + + // ✅ DETENER MOTOR SI NO ESTÁ EN CONTACTO + if (!isInContact) + { + bottle.StopMotor(); + System.Diagnostics.Debug.WriteLine($"[StopMotorsForBottlesNotInContact] ⏹️ Detenido: {bottle.BodyHandle}"); + } + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[StopMotorsForBottlesNotInContact] ❌ ERROR: {ex.Message}"); } } + /// + /// ✅ NUEVO: Verifica si una botella está en contacto con un transporte + /// + private bool IsBottleInContactWithTransport(simBotella bottle, simTransporte transport) + { + try + { + var bottlePos = bottle.GetPosition(); + var transportPos = transport.GetPosition(); + var distance = Vector2.Distance( + new Vector2(bottlePos.X, bottlePos.Y), + new Vector2(transportPos.X, transportPos.Y) + ); + + // Verificar si está dentro del área del transporte + tolerancia + var maxDistance = Math.Max(transport.Width, transport.Height) / 2f + bottle.Radius + 0.1f; + return distance <= maxDistance; + } + catch + { + return false; + } + } + + /// + /// ✅ NUEVO: Verifica si una botella está en contacto con una curva + /// + private bool IsBottleInContactWithCurve(simBotella bottle, simCurve curve) + { + try + { + var bottlePos = bottle.GetPosition(); + var curveCenter = curve.CurveCenter; // ✅ NUEVO: Usar centro real + var distance = Vector2.Distance( + new Vector2(bottlePos.X, bottlePos.Y), + new Vector2(curveCenter.X, curveCenter.Y) + ); + + // Verificar si está dentro del área de la curva + tolerancia + var maxDistance = curve.OuterRadius + bottle.Radius + 0.1f; + return distance <= maxDistance; + } + catch + { + return false; + } + } + + + /// + /// ✅ NUEVO: Calcula la dirección tangencial específica basada en la posición de la botella + /// + public Vector3 CalculateCurveDirectionFromBottlePosition(simCurve curve, simBotella bottle) + { + try + { + // ✅ NUEVO: Usar el centro real almacenado de la curva + var curveCenter = curve.CurveCenter; + var bottlePosition = bottle.GetPosition(); + + // Calcular el vector desde el centro de la curva hasta la botella (en el plano XY) + var radiusVector = new Vector3(bottlePosition.X - curveCenter.X, bottlePosition.Y - curveCenter.Y, 0f); + var radius = radiusVector.Length(); + + if (radius < 0.001f) + { + System.Diagnostics.Debug.WriteLine($"[CalculateCurveDirectionFromBottlePosition] ⚠️ Botella muy cerca del centro de la curva"); + return Vector3.UnitX; // Fallback + } + + // Normalizar el vector radial + var normalizedRadius = radiusVector / radius; + + // Calcular la dirección tangencial (perpendicular al radio) + // En coordenadas 2D: si r = (x, y), entonces t = (-y, x) es tangente + var tangentDirection = new Vector3(-normalizedRadius.Y, normalizedRadius.X, 0f); + + // Verificar que la dirección tangencial apunte en el sentido correcto según la velocidad de la curva + if (curve.Speed < 0) + { + tangentDirection = -tangentDirection; + } + + System.Diagnostics.Debug.WriteLine($"[CalculateCurveDirectionFromBottlePosition] 📐 Dirección calculada:"); + System.Diagnostics.Debug.WriteLine($" Centro curva: {curveCenter}"); + System.Diagnostics.Debug.WriteLine($" Posición botella: {bottlePosition}"); + System.Diagnostics.Debug.WriteLine($" Radio: {radius:F3}"); + System.Diagnostics.Debug.WriteLine($" Vector radial: {normalizedRadius}"); + System.Diagnostics.Debug.WriteLine($" Dirección tangencial: {tangentDirection} (Longitud: {tangentDirection.Length():F3})"); + + return tangentDirection; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[CalculateCurveDirectionFromBottlePosition] ❌ ERROR: {ex.Message}"); + return Vector3.UnitX; // Fallback + } + } + + + public void Dispose() @@ -2604,64 +2688,9 @@ namespace CtrEditor.Simulacion - /// - /// ✅ IMPLEMENTADO: Intenta crear un motor de transporte si no existe uno activo - /// - public void TryCreateTransportMotor(simBotella bottle, simTransporte transport) - { - try - { - if (bottle == null || transport == null || _motorManager == null) - return; - - if (Math.Abs(transport.Speed) < 0.001f) - return; // No crear motor para transportes detenidos - - // Calcular dirección del transporte - transport.UpdateCachedProperties(); - var direction = transport.DirectionVector; - var speed = transport.SpeedMetersPerSecond; - - _motorManager.CreateLinearMotor(bottle, transport.BodyHandle, direction, speed, false); - - System.Diagnostics.Debug.WriteLine($"[TryCreateTransportMotor] Motor creado: {bottle.BodyHandle} - {transport.BodyHandle}"); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"[SimulationManager] Error in TryCreateTransportMotor: {ex.Message}"); - } - } - /// - /// ✅ NUEVO: Crear motor de curva usando LinearAxisMotor + DistanceLimit - /// - public void TryCreateCurveLinearMotor(simBotella bottle, simCurve curve) - { - try - { - if (bottle == null || curve == null || _motorManager == null) - return; - - if (Math.Abs(curve.Speed) < 0.001f) - return; // No crear motor para curvas detenidas - - // Calcular dirección tangencial de la curva - var bottlePos = bottle.GetPosition(); - var curvePos = curve.GetPosition(); - var radial = bottlePos - curvePos; - var normalizedRadial = Vector3.Normalize(radial); - // Para una curva en el plano XY, la tangente se obtiene rotando el vector radial 90 grados - var tangentDirection = new Vector3(-normalizedRadial.Y, normalizedRadial.X, 0f); - - _motorManager.CreateLinearMotor(bottle, curve.BodyHandle, tangentDirection, curve.Speed, true); - - System.Diagnostics.Debug.WriteLine($"[TryCreateCurveLinearMotor] Motor creado: {bottle.BodyHandle} - {curve.BodyHandle}"); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"[SimulationManager] Error in TryCreateCurveLinearMotor: {ex.Message}"); - } - } + + @@ -3048,20 +3077,26 @@ namespace CtrEditor.Simulacion { if (botella != null && Cuerpos.Contains(botella)) { + System.Diagnostics.Debug.WriteLine($"[RemoveMarkedBottles] 🗑️ Marcando botella para eliminación: {botella.BodyHandle}"); + + // ✅ USAR ELIMINACIÓN DIFERIDA (más seguro) Remove(botella); } } catch (Exception ex) { - // Error removing bottle - continue + System.Diagnostics.Debug.WriteLine($"[RemoveMarkedBottles] ❌ Error marcando botella {botella?.BodyHandle}: {ex.Message}"); + // ✅ INTENTAR remover de la lista incluso si Remove falló + if (botella != null) + { + Cuerpos.Remove(botella); + } } } - - // Botellas eliminadas: {botellasAEliminar.Count} } catch (Exception ex) { - // Critical error in RemoveMarkedBottles - continue + System.Diagnostics.Debug.WriteLine($"[RemoveMarkedBottles] ❌ ERROR CRÍTICO: {ex.Message}"); } } @@ -3132,5 +3167,7 @@ namespace CtrEditor.Simulacion } + + } } \ No newline at end of file diff --git a/Simulacion/BEPUVisualization3D.cs b/Simulacion/BEPUVisualization3D.cs index f3d1a0c..20ff637 100644 --- a/Simulacion/BEPUVisualization3D.cs +++ b/Simulacion/BEPUVisualization3D.cs @@ -1055,91 +1055,6 @@ namespace CtrEditor.Simulacion return DebugShowIndividualTriangles; } - /// - /// ✅ NUEVO: Obtiene información detallada de debug sobre los triángulos reales de BEPU - /// Extrae y muestra los triángulos exactos almacenados en la simulación física - /// - /// Curva para analizar - /// Información de debug como string - public string GetCurveDebugInfo(simCurve curve) - { - if (curve == null) - return "ERROR: Curva es null"; - - try - { - var debugInfo = new System.Text.StringBuilder(); - - debugInfo.AppendLine($"=== DEBUG INFO TRIÁNGULOS REALES DE BEPU ==="); - debugInfo.AppendLine($"Parámetros de curva:"); - debugInfo.AppendLine($" - Radio Interior: {curve.InnerRadius}"); - debugInfo.AppendLine($" - Radio Exterior: {curve.OuterRadius}"); - debugInfo.AppendLine($" - Ángulo Inicio: {curve.StartAngle}° ({curve.StartAngle * Math.PI / 180:F3} rad)"); - debugInfo.AppendLine($" - Ángulo Fin: {curve.EndAngle}° ({curve.EndAngle * Math.PI / 180:F3} rad)"); - debugInfo.AppendLine($" - Velocidad: {curve.Speed} rad/s"); - debugInfo.AppendLine($""); - - // ✅ OBTENER INFORMACIÓN REAL DEL MESH DE BEPU - var meshInfo = curve.GetBEPUMeshDebugInfo(); - debugInfo.AppendLine("INFORMACIÓN DEL MESH EN BEPU:"); - debugInfo.AppendLine(meshInfo); - debugInfo.AppendLine($""); - - // ✅ EXTRAER Y MOSTRAR TRIÁNGULOS REALES (LOCALES) - var localTriangles = curve.GetRealBEPUTriangles(); - debugInfo.AppendLine($"TRIÁNGULOS REALES EXTRAÍDOS (LOCALES): {localTriangles.Length}"); - debugInfo.AppendLine($""); - - // Mostrar los primeros triángulos locales con detalles completos - int maxToShow = Math.Min(5, localTriangles.Length); - for (int i = 0; i < maxToShow; i++) - { - var triangle = localTriangles[i]; - debugInfo.AppendLine($"Triángulo LOCAL {i + 1}:"); - debugInfo.AppendLine($" A: ({triangle.A.X:F4}, {triangle.A.Y:F4}, {triangle.A.Z:F4})"); - debugInfo.AppendLine($" B: ({triangle.B.X:F4}, {triangle.B.Y:F4}, {triangle.B.Z:F4})"); - debugInfo.AppendLine($" C: ({triangle.C.X:F4}, {triangle.C.Y:F4}, {triangle.C.Z:F4})"); - - // Calcular área del triángulo - var edge1 = triangle.B - triangle.A; - var edge2 = triangle.C - triangle.A; - var cross = Vector3.Cross(edge1, edge2); - var area = cross.Length() / 2f; - debugInfo.AppendLine($" Área: {area:F6}"); - debugInfo.AppendLine($""); - } - - // ✅ NUEVO: Mostrar también algunos triángulos en coordenadas mundiales - var worldTriangles = curve.GetWorldBEPUTriangles(); - debugInfo.AppendLine($"TRIÁNGULOS REALES EXTRAÍDOS (MUNDIALES): {worldTriangles.Length}"); - debugInfo.AppendLine($""); - - for (int i = 0; i < Math.Min(3, worldTriangles.Length); i++) - { - var triangle = worldTriangles[i]; - debugInfo.AppendLine($"Triángulo MUNDIAL {i + 1}:"); - debugInfo.AppendLine($" A: ({triangle.A.X:F4}, {triangle.A.Y:F4}, {triangle.A.Z:F4})"); - debugInfo.AppendLine($" B: ({triangle.B.X:F4}, {triangle.B.Y:F4}, {triangle.B.Z:F4})"); - debugInfo.AppendLine($" C: ({triangle.C.X:F4}, {triangle.C.Y:F4}, {triangle.C.Z:F4})"); - debugInfo.AppendLine($""); - } - - if (localTriangles.Length > maxToShow) - { - debugInfo.AppendLine($"... y {localTriangles.Length - maxToShow} triángulos locales más"); - } - - debugInfo.AppendLine($""); - debugInfo.AppendLine($"Modo debug visual: {(DebugShowIndividualTriangles ? "ACTIVADO (triángulos separados)" : "DESACTIVADO (superficie continua)")}"); - - return debugInfo.ToString(); - } - catch (Exception ex) - { - return $"ERROR obteniendo info de debug real: {ex.Message}"; - } - } - /// /// ✅ CORREGIDO: Activa la visualización de triángulos locales de BEPU /// Muestra los triángulos exactos extraídos de la simulación física en coordenadas locales