From c1584e8d55d459a4e48992a4e29170bcf67774b5 Mon Sep 17 00:00:00 2001 From: Miguel Date: Thu, 3 Jul 2025 11:47:09 +0200 Subject: [PATCH] =?UTF-8?q?Se=20a=C3=B1adi=C3=B3=20una=20nueva=20librer?= =?UTF-8?q?=C3=ADa=20para=20la=20simulaci=C3=B3n=20y=20se=20realizaron=20m?= =?UTF-8?q?ejoras=20en=20la=20gesti=C3=B3n=20de=20motores=20en=20simTransp?= =?UTF-8?q?orte=20y=20simCurve.=20Se=20unificaron=20m=C3=A9todos=20para=20?= =?UTF-8?q?la=20creaci=C3=B3n=20de=20motores=20lineales=20y=20angulares,?= =?UTF-8?q?=20optimizando=20la=20l=C3=B3gica=20de=20detecci=C3=B3n=20de=20?= =?UTF-8?q?colisiones=20y=20la=20visualizaci=C3=B3n=20de=20tri=C3=A1ngulos?= =?UTF-8?q?=20en=203D.=20Adem=C3=A1s,=20se=20corrigieron=20errores=20en=20?= =?UTF-8?q?la=20extracci=C3=B3n=20de=20tri=C3=A1ngulos=20de=20BEPU,=20aseg?= =?UTF-8?q?urando=20que=20se=20utilicen=20coordenadas=20locales=20para=20e?= =?UTF-8?q?vitar=20transformaciones=20duplicadas.=20Se=20implementaron=20m?= =?UTF-8?q?=C3=A9todos=20para=20activar=20y=20desactivar=20el=20modo=20de?= =?UTF-8?q?=20depuraci=C3=B3n=20de=20tri=C3=A1ngulos,=20mejorando=20la=20e?= =?UTF-8?q?xperiencia=20de=20visualizaci=C3=B3n.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CtrEditor.code-workspace | 3 + Simulacion/BEPU.cs | 766 ++++++++---------------------- Simulacion/BEPUVisualization3D.cs | 157 +++--- 3 files changed, 304 insertions(+), 622 deletions(-) diff --git a/CtrEditor.code-workspace b/CtrEditor.code-workspace index c0cd4b5..d93d214 100644 --- a/CtrEditor.code-workspace +++ b/CtrEditor.code-workspace @@ -5,6 +5,9 @@ }, { "path": "../Libraries/LibS7Adv" + }, + { + "path": "../Librerias/bepuphysics2-master" } ], "settings": {} diff --git a/Simulacion/BEPU.cs b/Simulacion/BEPU.cs index 00a6c1b..27a734a 100644 --- a/Simulacion/BEPU.cs +++ b/Simulacion/BEPU.cs @@ -860,11 +860,11 @@ namespace CtrEditor.Simulacion public simTransporte CurrentBrakeTransport { get; set; } // ✅ NUEVA: Referencia al transporte de freno actual // ✅ NUEVO SISTEMA SIMPLIFICADO: Un solo motor por botella - public enum MotorType { None, Linear, Angular } - public MotorType CurrentMotorType { get; set; } = MotorType.None; - public simTransporte CurrentTransport { get; set; } - public ConstraintHandle CurrentMotor { get; set; } + 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; private List _deferredActions; @@ -1069,51 +1069,44 @@ namespace CtrEditor.Simulacion } // ✅ SISTEMA SIMPLIFICADO: Métodos unificados de gestión de motores - public void AssignLinearMotor(simTransporte transport, ConstraintHandle motor) + public void AssignMotor(ConstraintHandle motor, BodyHandle elementHandle) { - RemoveCurrentMotor(); // Eliminar motor anterior automáticamente CurrentMotor = motor; - CurrentMotorType = MotorType.Linear; - CurrentTransport = transport; - if (transport.isBrake) - { - isOnBrakeTransport = true; - CurrentBrakeTransport = transport; - } + CurrentElementHandle = elementHandle; } - public void AssignAngularMotor(simCurve curve, ConstraintHandle motor) + public void AssignDistanceLimit(ConstraintHandle distanceLimit) { - RemoveCurrentMotor(); // Eliminar motor anterior automáticamente - CurrentMotor = motor; - CurrentMotorType = MotorType.Angular; - // Las curvas no usan CurrentTransport + CurrentDistanceLimit = distanceLimit; } - public void RemoveCurrentMotor() + public void RemoveAllConstraints() { - if (HasActiveMotor) - { - // El MotorManager se encargará de eliminar el motor del solver CurrentMotor = default; - CurrentMotorType = MotorType.None; - CurrentTransport = null; - isOnBrakeTransport = false; - CurrentBrakeTransport = null; - } + CurrentDistanceLimit = default; + CurrentElementHandle = default; } // ✅ LEGACY: Mantener compatibilidad con código existente public void AssignToTransport(simTransporte transport, ConstraintHandle motor) { - AssignLinearMotor(transport, motor); + AssignMotor(motor, transport.BodyHandle); } public void RemoveFromTransport() { - RemoveCurrentMotor(); - } + RemoveAllConstraints(); + } + public void AssignAngularMotor(simCurve curve, ConstraintHandle motor) + { + AssignMotor(motor, curve.BodyHandle); + } + + public void RemoveCurrentMotor() + { + RemoveAllConstraints(); + } } /// @@ -1228,8 +1221,8 @@ namespace CtrEditor.Simulacion } /// - /// ✅ NUEVO: Obtiene los triángulos reales creados para la curva - /// Devuelve los triángulos transformados a coordenadas mundiales usando la posición del cuerpo + /// ✅ CORREGIDO: Obtiene los triángulos reales creados para la curva + /// Devuelve los triángulos en coordenadas locales para evitar transformación duplicada /// public Triangle[] GetRealBEPUTriangles() { @@ -1241,11 +1234,10 @@ namespace CtrEditor.Simulacion return new Triangle[0]; } - // ✅ TRANSFORMAR a coordenadas mundiales - var worldTriangles = TransformTrianglesToWorldCoordinates(_storedTriangles); - - System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] Devolviendo {worldTriangles.Length} triángulos transformados a coordenadas mundiales"); - return worldTriangles; + // ✅ CORREGIDO: Devolver triángulos en coordenadas locales + // La visualización 3D aplicará la transformación una sola vez + System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] Devolviendo {_storedTriangles.Length} triángulos en coordenadas locales"); + return _storedTriangles; } catch (Exception ex) { @@ -1254,6 +1246,29 @@ namespace CtrEditor.Simulacion } } + /// + /// ✅ NUEVO: Obtiene los triángulos transformados a coordenadas mundiales (solo para debugging) + /// + public Triangle[] GetWorldBEPUTriangles() + { + try + { + if (_storedTriangles == null || _storedTriangles.Length == 0) + { + return new Triangle[0]; + } + + var worldTriangles = TransformTrianglesToWorldCoordinates(_storedTriangles); + System.Diagnostics.Debug.WriteLine($"[GetWorldBEPUTriangles] Devolviendo {worldTriangles.Length} triángulos en coordenadas mundiales"); + return worldTriangles; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[GetWorldBEPUTriangles] Error: {ex.Message}"); + return new Triangle[0]; + } + } + /// /// ✅ NUEVO: Transforma triángulos de coordenadas locales a mundiales /// @@ -1336,10 +1351,10 @@ namespace CtrEditor.Simulacion info.AppendLine($" C: ({triangle.C.X:F3}, {triangle.C.Y:F3}, {triangle.C.Z:F3})"); } - // ✅ NUEVO: Mostrar triángulos transformados a coordenadas mundiales + // ✅ CORREGIDO: Mostrar triángulos transformados a coordenadas mundiales usando nuevo método try { - var worldTriangles = TransformTrianglesToWorldCoordinates(_storedTriangles); + var worldTriangles = GetWorldBEPUTriangles(); info.AppendLine($""); info.AppendLine($"Primeros {triangleCount} triángulos MUNDIALES (transformados):"); @@ -1657,12 +1672,12 @@ namespace CtrEditor.Simulacion public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, ref TManifold manifold, out PairMaterialProperties pairMaterial) where TManifold : unmanaged, IContactManifold { - // ✅ SIMPLIFICADO - configuración básica de materiales físicos + // ✅ CONFIGURACIÓN BÁSICA de materiales físicos pairMaterial = new PairMaterialProperties { - FrictionCoefficient = 0.6f, // Fricción moderada por defecto - MaximumRecoveryVelocity = 2f, // Velocidad máxima de recuperación - SpringSettings = new SpringSettings(60, 4) // Rigidez y amortiguamiento estándar + FrictionCoefficient = 0.6f, + MaximumRecoveryVelocity = 2f, + SpringSettings = new SpringSettings(60, 4) }; if (_simulationManager != null) @@ -1675,13 +1690,13 @@ namespace CtrEditor.Simulacion var descarteB = GetDescarteFromCollidable(pair.B); var botella = botellaA ?? botellaB; - // ✅ CONSERVAR - barreras como sensores puros + // ✅ BARRERAS como sensores puros if (barreraA != null || barreraB != null) { - return false; // NO generar contacto físico para barreras + return false; // NO generar contacto físico } - // ✅ CONSERVAR - descartes como sensores puros + // ✅ DESCARTES como sensores puros if (descarteA != null || descarteB != null) { var descarte = descarteA ?? descarteB; @@ -1689,65 +1704,50 @@ namespace CtrEditor.Simulacion { _simulationManager.RegisterDescarteContact(descarte, botella); } - return false; // NO generar contacto físico para descartes + return false; // NO generar contacto físico } - // ✅ SIMPLIFICADO: Detección directa de transportes y curvas + // ✅ DETECCIÓN SIMPLIFICADA de transportes y curvas var transportA = GetTransportFromCollidable(pair.A); var transportB = GetTransportFromCollidable(pair.B); var curveA = GetCurveFromCollidable(pair.A); var curveB = GetCurveFromCollidable(pair.B); - // ✅ VERIFICAR SI YA SE CREÓ MOTOR PARA EVITAR DUPLICADOS - BodyHandle? bottleHandle = null; - BodyHandle? elementHandle = null; - + // ✅ CONTACTO BOTELLA-TRANSPORTE if (botella != null && (transportA != null || transportB != null)) { var transport = transportA ?? transportB; - bottleHandle = botella.BodyHandle; - elementHandle = transport.BodyHandle; - var pairKey = (bottleHandle.Value, elementHandle.Value); - if (!_simulationManager._motorsCreated.Contains(pairKey)) + // Verificar si ya tiene motor para este elemento específico + if (botella.CurrentElementHandle.Value == 0 || !botella.CurrentElementHandle.Equals(transport.BodyHandle)) { _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) + // Fricción alta para transportes pairMaterial.FrictionCoefficient = 0.9f; pairMaterial.MaximumRecoveryVelocity = 1f; pairMaterial.SpringSettings = new SpringSettings(80, 6); } - // ✅ SIMPLIFICADO: Si hay contacto botella-curva, crear AngularAxisMotor + // ✅ CONTACTO BOTELLA-CURVA (ahora también usa LinearAxisMotor + DistanceLimit) else if (botella != null && (curveA != null || curveB != null)) { var curve = curveA ?? curveB; - bottleHandle = botella.BodyHandle; - elementHandle = curve.BodyHandle; - - // ✅ SIMPLIFICADO: Crear motor angular usando el cuerpo principal de la curva - var pairKey = (bottleHandle.Value, elementHandle.Value); - if (!_simulationManager._motorsCreated.Contains(pairKey)) + // Verificar si ya tiene motor para este elemento específico + if (botella.CurrentElementHandle.Value == 0 || !botella.CurrentElementHandle.Equals(curve.BodyHandle)) { - _simulationManager.TryCreateCurveAngularMotor(botella, curve); - _simulationManager._motorsCreated.Add(pairKey); - System.Diagnostics.Debug.WriteLine($"[Manifold] Motor angular curva: {bottleHandle} → {elementHandle}"); + _simulationManager.TryCreateCurveLinearMotor(botella, curve); } - // Fricción alta para curvas (SIEMPRE aplicar para colisión física) + // Fricción alta para curvas pairMaterial.FrictionCoefficient = 0.9f; pairMaterial.MaximumRecoveryVelocity = 1f; pairMaterial.SpringSettings = new SpringSettings(80, 6); - } - // Solo ajustes básicos de fricción para otras botellas + // Ajustes básicos para otras botellas else if (botella != null) { - // Fricción alta para mayor estabilidad de botellas pairMaterial.FrictionCoefficient = 0.9f; pairMaterial.MaximumRecoveryVelocity = 1f; pairMaterial.SpringSettings = new SpringSettings(80, 6); @@ -1899,430 +1899,124 @@ namespace CtrEditor.Simulacion public class MotorManager { private SimulationManagerBEPU _simulationManager; - private Dictionary _activeMotors; - private Dictionary _motorToBottle; + private Dictionary _bottleConstraints; public MotorManager(SimulationManagerBEPU simulationManager) { _simulationManager = simulationManager; - _activeMotors = new Dictionary(); - _motorToBottle = new Dictionary(); + _bottleConstraints = new Dictionary(); } - - public void CreateTransportMotor(simBotella bottle, simTransporte transport) + + /// + /// ✅ ÚNICO MÉTODO: Crear motor LinearAxisMotor con dirección calculada + /// + public bool CreateLinearMotor(simBotella bottle, BodyHandle elementHandle, Vector3 direction, float speed, bool isCurve = false) { - try + // Verificar si ya tenemos motor para este elemento específico + if (_bottleConstraints.TryGetValue(bottle.BodyHandle, out var existing)) { - // ✅ VALIDACIONES CRÍTICAS - if (bottle == null || transport == null || _simulationManager?.simulation == null) - return; - - // Validar que los BodyHandle existan - if (!_simulationManager.simulation.Bodies.BodyExists(bottle.BodyHandle) || - !_simulationManager.simulation.Bodies.BodyExists(transport.BodyHandle)) - return; + if (existing.elementHandle.Equals(elementHandle)) + { + return false; // Ya tiene motor para este elemento, no recrear + } - // Asegurar que DirectionVector esté inicializado - transport.UpdateCachedProperties(); - - // Validar que DirectionVector no sea cero - if (transport.DirectionVector.Length() < 0.001f) - return; - - // ✅ CORREGIDO - LocalAxis debe estar en coordenadas locales del transporte - // Para transportes, el eje local de movimiento es siempre UnitX (eje largo) - var localAxis = Vector3.UnitX; // Siempre UnitX en coordenadas locales del transporte - - var motor = new LinearAxisMotor() + // 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 { - LocalOffsetA = Vector3.Zero, - LocalOffsetB = Vector3.Zero, - LocalAxis = localAxis, // ✅ Usar eje local, no mundial - TargetVelocity = transport.SpeedMetersPerSecond, - Settings = new MotorSettings(Math.Max(bottle.Mass * 30f, 10f), 5f) - }; + _simulationManager.simulation.Solver.Remove(constraints.motor); + } + catch { } - // 🔍 DEBUG: Comparar eje local vs mundial - System.Diagnostics.Debug.WriteLine($"[Motor] Transport angle: {transport.GetRotationZ()}°"); - System.Diagnostics.Debug.WriteLine($"[Motor] World DirectionVector: {transport.DirectionVector}"); - System.Diagnostics.Debug.WriteLine($"[Motor] Local Axis: {localAxis}"); - System.Diagnostics.Debug.WriteLine($"[Motor] Target Velocity: {transport.SpeedMetersPerSecond}"); + if (constraints.distanceLimit.HasValue) + { + try + { + _simulationManager.simulation.Solver.Remove(constraints.distanceLimit.Value); + } + catch { } + } - var motorHandle = _simulationManager.simulation.Solver.Add( - transport.BodyHandle, - bottle.BodyHandle, - motor - ); - - _activeMotors[bottle.BodyHandle] = motorHandle; - _motorToBottle[motorHandle] = bottle; - bottle.AssignToTransport(transport, motorHandle); - transport.BottlesOnTransport.Add(bottle); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"[MotorManager] Error creating transport motor: {ex.Message}"); + _bottleConstraints.Remove(bottle.BodyHandle); } + + bottle.RemoveAllConstraints(); + + System.Diagnostics.Debug.WriteLine($"[MotorManager] Constraints removidos para botella: {bottle.BodyHandle}"); } - - public void CreateCurveMotor(simBotella bottle, simCurve curve) - { - // ✅ SIMPLIFICADO: Usar AngularAxisMotor para curvas - CreateCurveAngularMotor(bottle, curve); - } - - /// - /// ✅ NUEVO: Crear motor angular para curvas (simplificado) - /// - public void CreateCurveAngularMotor(simBotella bottle, simCurve curve) - { - try - { - // ✅ VALIDACIONES CRÍTICAS - if (bottle == null || curve == null || _simulationManager?.simulation == null) - return; - - // Validar que los BodyHandle existan - if (!_simulationManager.simulation.Bodies.BodyExists(bottle.BodyHandle) || - !_simulationManager.simulation.Bodies.BodyExists(curve.BodyHandle)) - return; - - // Verificar que la curva tenga velocidad - if (Math.Abs(curve.Speed) < 0.001f) - return; - - // ✅ SIMPLIFICADO: AngularAxisMotor para rotación en el plano XY - var angularMotor = new AngularAxisMotor() - { - LocalAxisA = Vector3.UnitZ, // Eje Z para rotación en plano XY - TargetVelocity = curve.Speed, // Velocidad angular directa en rad/s - Settings = new MotorSettings(Math.Max(bottle.Mass * 10f, 5f), 3f) - }; - - var motorHandle = _simulationManager.simulation.Solver.Add( - curve.BodyHandle, - bottle.BodyHandle, - angularMotor - ); - - _activeMotors[bottle.BodyHandle] = motorHandle; - _motorToBottle[motorHandle] = bottle; - bottle.AssignAngularMotor(curve, motorHandle); - curve.BottlesOnCurve.Add(bottle); - - System.Diagnostics.Debug.WriteLine($"[MotorManager] Angular motor created: {bottle.BodyHandle} → {curve.BodyHandle}"); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"[MotorManager] Error creating angular motor: {ex.Message}"); - } - } - - public void RemoveMotor(simBotella bottle) - { - try - { - if (bottle == null || _simulationManager?.simulation == null) - return; - - if (_activeMotors.TryGetValue(bottle.BodyHandle, out var motorHandle)) - { - // Validar que el solver existe antes de remover - if (_simulationManager.simulation.Solver != null) - { - _simulationManager.simulation.Solver.Remove(motorHandle); - 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(); - } - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"[MotorManager] Error removing motor: {ex.Message}"); - } - } - - public void UpdateTransportSpeed(simTransporte transport) - { - // TODO: Implementar actualización de velocidad cuando se resuelva la API correcta - // Por ahora, recreamos los motores como alternativa - foreach (var bottle in transport.BottlesOnTransport.ToList()) - { - if (_activeMotors.ContainsKey(bottle.BodyHandle)) - { - RemoveMotor(bottle); - CreateTransportMotor(bottle, transport); - } - } - } - - public void UpdateCurveSpeed(simCurve curve) - { - // ✅ Las curvas requieren recrear motores cuando cambia la velocidad - // Esto es porque la dirección tangencial puede cambiar y es más eficiente recrear - foreach (var bottle in curve.BottlesOnCurve.ToList()) - { - if (_activeMotors.ContainsKey(bottle.BodyHandle)) - { - RemoveMotor(bottle); - CreateCurveMotor(bottle, curve); - } - } - } - - /// - /// ✅ 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) - { - try - { - 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 - 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.Speed, // ✅ SIMPLIFICADO: Usar velocidad directa - 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.Speed}"); - - _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) - { - try - { - if (_simulationManager?.simulation?.Solver == null) - return; - - // ✅ 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) - { - var bottleHandle = kvp.Key; - var motorHandle = kvp.Value; - - if (_motorToBottle.TryGetValue(motorHandle, out var bottle)) - { - bool shouldRemove = false; - - // ✅ OPTIMIZADO: Verificación directa por tipo de objeto - switch (targetObject) - { - case simTransporte transport: - shouldRemove = bottle.CurrentTransport?.BodyHandle.Equals(bodyHandle) == true; - break; - - case simCurve curve: - // Verificar si la botella está en esta curva específica - shouldRemove = curve.BottlesOnCurve.Contains(bottle); - break; - } - - if (shouldRemove) - { - motorsToRemove.Add((bottleHandle, motorHandle)); - } - } - } - - // Eliminar motors encontrados - foreach (var (bottleHandle, motorHandle) in motorsToRemove) - { - try - { - _simulationManager.simulation.Solver.Remove(motorHandle); - _activeMotors.Remove(bottleHandle); - - if (_motorToBottle.TryGetValue(motorHandle, out var bottle)) - { - bottle.RemoveFromTransport(); - _motorToBottle.Remove(motorHandle); - } - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"[MotorManager] Error removing specific motor for body {bodyHandle}: {ex.Message}"); - } - } - - // ✅ 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) - { - System.Diagnostics.Debug.WriteLine($"[MotorManager] Error in RemoveMotorsByBodyHandle: {ex.Message}"); - } - } - - public void Clear() { try { - if (_simulationManager?.simulation?.Solver != null) + foreach (var kvp in _bottleConstraints) { - foreach (var motorHandle in _activeMotors.Values) + try { _simulationManager.simulation.Solver.Remove(kvp.Value.motor); } catch { } + if (kvp.Value.distanceLimit.HasValue) { - try - { - _simulationManager.simulation.Solver.Remove(motorHandle); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"[MotorManager] Error removing motor during clear: {ex.Message}"); - } + try { _simulationManager.simulation.Solver.Remove(kvp.Value.distanceLimit.Value); } catch { } } } - _activeMotors.Clear(); - _motorToBottle.Clear(); - - // ✅ SIMPLIFICADO: Solo limpiar pares de motores creados - if (_simulationManager?.Cuerpos != null) - { - // Limpiar pares de motores creados - _simulationManager._motorsCreated?.Clear(); - } + _bottleConstraints.Clear(); } catch (Exception ex) { @@ -2373,6 +2067,8 @@ namespace CtrEditor.Simulacion private object _contactsLock = new object(); + private Dictionary _distanceLimits; // ✅ NUEVO: DistanceLimit por botella + /// /// Obtiene el objeto simBase correspondiente a un BodyHandle /// @@ -2401,15 +2097,12 @@ namespace CtrEditor.Simulacion break; case simTransporte transport: _transportHandles.Add(transport.BodyHandle); - // Suscribirse a cambios de velocidad - transport.OnSpeedChanged += _motorManager.UpdateTransportSpeed; // Calcular propiedades iniciales transport.UpdateCachedProperties(); break; case simCurve curve: _curveHandles.Add(curve.BodyHandle); // ✅ SIMPLIFICADO: Ya no hay triángulos separados, solo el cuerpo principal - curve.OnSpeedChanged += _motorManager.UpdateCurveSpeed; break; case simBarrera barrier: _barrierHandles.Add(barrier.BodyHandle); @@ -2427,18 +2120,16 @@ namespace CtrEditor.Simulacion case simBotella bottle: _bottleHandles.Remove(bottle.BodyHandle); // Eliminar motor si existe - _motorManager.RemoveMotor(bottle); + _motorManager.RemoveBottleConstraints(bottle); // Eliminar de pares de motores creados RemoveFromMotorsCreated(bottle.BodyHandle); break; case simTransporte transport: _transportHandles.Remove(transport.BodyHandle); - transport.OnSpeedChanged -= _motorManager.UpdateTransportSpeed; break; case simCurve curve: _curveHandles.Remove(curve.BodyHandle); // ✅ SIMPLIFICADO: Ya no hay triángulos separados - curve.OnSpeedChanged -= _motorManager.UpdateCurveSpeed; break; case simBarrera barrier: _barrierHandles.Remove(barrier.BodyHandle); @@ -2739,15 +2430,20 @@ namespace CtrEditor.Simulacion if (_frameCount % 10 != 0) return; var activeBottles = Cuerpos.OfType() - .Where(b => b.HasActiveMotor && b.CurrentTransport != null) + .Where(b => b.HasActiveMotor && b.CurrentElementHandle.Value != 0) .ToList(); foreach (var bottle in activeBottles) { - if (!IsBottleOnTransport(bottle, bottle.CurrentTransport)) + var element = GetSimBaseFromBodyHandle(bottle.CurrentElementHandle); + if (element is simTransporte transport) { - ProcessBottleExitsTransport(bottle); + if (!IsBottleOnTransport(bottle, transport)) + { + ProcessBottleExitsTransport(bottle); + } } + // Las curvas no necesitan verificación de salida porque usan DistanceLimit } } @@ -2768,14 +2464,14 @@ namespace CtrEditor.Simulacion private void ProcessBottleExitsTransport(simBotella bottle) { RemoveCurrentMotorPair(bottle); - _motorManager.RemoveMotor(bottle); + _motorManager.RemoveBottleConstraints(bottle); } private void RemoveCurrentMotorPair(simBotella bottle) { - if (bottle.CurrentTransport != null) + if (bottle.CurrentElementHandle.Value != 0) { - _motorsCreated.Remove((bottle.BodyHandle, bottle.CurrentTransport.BodyHandle)); + _motorsCreated.Remove((bottle.BodyHandle, bottle.CurrentElementHandle)); } } @@ -2884,7 +2580,15 @@ namespace CtrEditor.Simulacion /// public void RemoveMotorsConnectedToBody(BodyHandle bodyHandle) { - _motorManager?.RemoveMotorsByBodyHandle(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) + { + _motorManager.RemoveBottleConstraints(bottle); + } } @@ -2907,19 +2611,18 @@ namespace CtrEditor.Simulacion { try { - // Validaciones básicas if (bottle == null || transport == null || _motorManager == null) return; - // Verificar si ya tiene un motor activo - if (bottle.HasActiveMotor) - 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); + // 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}"); } @@ -2930,31 +2633,33 @@ namespace CtrEditor.Simulacion } /// - /// ✅ NUEVO: Intenta crear un motor angular para curvas si no existe uno activo + /// ✅ NUEVO: Crear motor de curva usando LinearAxisMotor + DistanceLimit /// - public void TryCreateCurveAngularMotor(simBotella bottle, simCurve curve) + public void TryCreateCurveLinearMotor(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; - - // Asegurar que la curva tenga velocidad if (Math.Abs(curve.Speed) < 0.001f) return; // No crear motor para curvas detenidas - _motorManager.CreateCurveAngularMotor(bottle, curve); + // 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); - System.Diagnostics.Debug.WriteLine($"[TryCreateCurveAngularMotor] Motor angular creado: {bottle.BodyHandle} - {curve.BodyHandle}"); + _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 TryCreateCurveAngularMotor: {ex.Message}"); + System.Diagnostics.Debug.WriteLine($"[SimulationManager] Error in TryCreateCurveLinearMotor: {ex.Message}"); } } @@ -3427,54 +3132,5 @@ namespace CtrEditor.Simulacion } - /// - /// ✅ 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 e83ee80..f3d1a0c 100644 --- a/Simulacion/BEPUVisualization3D.cs +++ b/Simulacion/BEPUVisualization3D.cs @@ -71,7 +71,7 @@ namespace CtrEditor.Simulacion private Dictionary simBaseToModelMap; private Dictionary lastKnownDimensions; - // ✅ NUEVO: Flag de debug para mostrar triángulos individuales de curvas + // ✅ CORREGIDO: Flag de debug para mostrar triángulos individuales de curvas (true temporalmente para verificar) public static bool DebugShowIndividualTriangles { get; set; } = true; public HelixViewport3D Viewport3D @@ -386,103 +386,85 @@ namespace CtrEditor.Simulacion } /// - /// ✅ NUEVO: Crea mesh directamente desde los triángulos reales de BEPU - /// Extrae la geometría exacta que está almacenada en la simulación física + /// ✅ CORREGIDO: Crea mesh desde los triángulos locales de BEPU + /// Extrae la geometría exacta en coordenadas locales para evitar transformación duplicada /// private void CreateCurveMeshFromBEPUTriangles(MeshBuilder meshBuilder, simCurve curve) { try { - // ✅ EXTRAER TRIÁNGULOS REALES DE BEPU - var realTriangles = curve.GetRealBEPUTriangles(); + // ✅ EXTRAER TRIÁNGULOS LOCALES DE BEPU (sin transformación duplicada) + var localTriangles = curve.GetRealBEPUTriangles(); - if (realTriangles.Length == 0) + if (localTriangles.Length == 0) { - System.Diagnostics.Debug.WriteLine($"[3D BEPU] WARNING: No se pudieron extraer triángulos reales, usando fallback"); + System.Diagnostics.Debug.WriteLine($"[3D BEPU] WARNING: No se pudieron extraer triángulos locales, usando fallback"); CreateCurveMeshFallback(meshBuilder, curve); return; } - System.Diagnostics.Debug.WriteLine($"[3D BEPU] Creando mesh desde {realTriangles.Length} triángulos reales de BEPU"); + System.Diagnostics.Debug.WriteLine($"[3D BEPU] Creando mesh desde {localTriangles.Length} triángulos locales de BEPU"); - // ✅ USAR TRIÁNGULOS EXACTOS DE BEPU - foreach (var triangle in realTriangles) + // ✅ USAR TRIÁNGULOS LOCALES DE BEPU (la transformación se aplicará automáticamente en UpdateVisualization) + foreach (var triangle in localTriangles) { // Convertir triángulos de BEPU a puntos 3D de Helix var pointA = new Point3D(triangle.A.X, triangle.A.Y, triangle.A.Z); var pointB = new Point3D(triangle.B.X, triangle.B.Y, triangle.B.Z); var pointC = new Point3D(triangle.C.X, triangle.C.Y, triangle.C.Z); - // Agregar triángulo exacto al mesh + // Agregar triángulo en coordenadas locales al mesh meshBuilder.AddTriangle(pointA, pointB, pointC); } - System.Diagnostics.Debug.WriteLine($"[3D BEPU] ✅ Mesh creado usando triángulos reales de BEPU"); + System.Diagnostics.Debug.WriteLine($"[3D BEPU] ✅ Mesh creado usando triángulos locales de BEPU (transformación aplicada automáticamente)"); } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"[3D BEPU] ERROR extrayendo triángulos reales: {ex.Message}"); + System.Diagnostics.Debug.WriteLine($"[3D BEPU] ERROR extrayendo triángulos locales: {ex.Message}"); System.Diagnostics.Debug.WriteLine($"[3D BEPU] Fallback a geometría recreada"); CreateCurveMeshFallback(meshBuilder, curve); } } /// - /// ✅ NUEVO: Función de debug que muestra triángulos individuales reales de BEPU - /// Cada triángulo se renderiza de manera separada con offset para poder hacer debug visual + /// ✅ CORREGIDO: Función de debug que muestra triángulos reales de BEPU sin offset artificial + /// Los triángulos se muestran exactamente como están en la física (planos, sin separación) /// private void CreateCurveDebugMeshWithIndividualTriangles(MeshBuilder meshBuilder, simCurve curve) { try { - // ✅ EXTRAER TRIÁNGULOS REALES DE BEPU PARA DEBUG - var realTriangles = curve.GetRealBEPUTriangles(); + // ✅ EXTRAER TRIÁNGULOS LOCALES DE BEPU PARA DEBUG + var localTriangles = curve.GetRealBEPUTriangles(); - if (realTriangles.Length == 0) + if (localTriangles.Length == 0) { - System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: No hay triángulos reales para debug, usando fallback"); + System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: No hay triángulos locales para debug, usando fallback"); CreateCurveMeshFallback(meshBuilder, curve); return; } - System.Diagnostics.Debug.WriteLine($"[3D Debug] Creando debug de {realTriangles.Length} triángulos reales individuales"); + System.Diagnostics.Debug.WriteLine($"[3D Debug] Creando debug de {localTriangles.Length} triángulos reales planos"); - // ✅ MOSTRAR CADA TRIÁNGULO SEPARADO CON OFFSET PARA DEBUG - float triangleSeparation = 0.02f; // Separación entre triángulos para debug visual - - for (int i = 0; i < realTriangles.Length; i++) + // ✅ MOSTRAR TRIÁNGULOS REALES SIN OFFSET ARTIFICIAL + for (int i = 0; i < localTriangles.Length; i++) { - var triangle = realTriangles[i]; + var triangle = localTriangles[i]; - // Calcular centroide del triángulo - var centroid = (triangle.A + triangle.B + triangle.C) / 3f; + // ✅ USAR TRIÁNGULOS EXACTOS SIN MODIFICACIÓN + var pointA = new Point3D(triangle.A.X, triangle.A.Y, triangle.A.Z); + var pointB = new Point3D(triangle.B.X, triangle.B.Y, triangle.B.Z); + var pointC = new Point3D(triangle.C.X, triangle.C.Y, triangle.C.Z); - // Calcular normal del triángulo - var edge1 = triangle.B - triangle.A; - var edge2 = triangle.C - triangle.A; - var normal = Vector3.Normalize(Vector3.Cross(edge1, edge2)); - - // Offset hacia arriba para separar triángulos visualmente - var offset = normal * triangleSeparation * (i + 1); - - // Aplicar offset a todos los vértices - var offsetA = triangle.A + offset; - var offsetB = triangle.B + offset; - var offsetC = triangle.C + offset; - - // Convertir a puntos 3D de Helix - var pointA = new Point3D(offsetA.X, offsetA.Y, offsetA.Z); - var pointB = new Point3D(offsetB.X, offsetB.Y, offsetB.Z); - var pointC = new Point3D(offsetC.X, offsetC.Y, offsetC.Z); - - // Agregar triángulo separado + // Agregar triángulo real sin modificaciones meshBuilder.AddTriangle(pointA, pointB, pointC); - // ✅ DEBUG: Agregar bordes para visualizar mejor cada triángulo - AddDebugTriangleEdges(meshBuilder, pointA, pointB, pointC, 0.005f); + // ✅ DEBUG: Agregar bordes delgados para visualizar límites entre triángulos + AddDebugTriangleEdges(meshBuilder, pointA, pointB, pointC, 0.002f); } - System.Diagnostics.Debug.WriteLine($"[3D Debug] ✅ Debug mesh creado con triángulos reales separados"); + System.Diagnostics.Debug.WriteLine($"[3D Debug] ✅ Debug mesh creado con triángulos reales planos (sin offset artificial)"); } catch (Exception ex) { @@ -985,16 +967,26 @@ namespace CtrEditor.Simulacion } /// - /// ✅ NUEVO: Activa o desactiva el modo debug para mostrar triángulos individuales de curvas + /// ✅ MODO DEBUG DE TRIÁNGULOS DE CURVAS: + /// + /// TRUE (Debug Mode): Muestra triángulos individuales reales de BEPU con bordes visibles + /// FALSE (Normal Mode): Muestra superficie continua suave + /// + /// Uso desde código: + /// // Activar modo debug para ver triángulos reales + /// visualizationManager.ShowRealTriangles(); + /// + /// // Desactivar para ver superficie continua + /// visualizationManager.ShowContinuousSurface(); /// - /// True para activar debug, false para modo normal + /// True para mostrar triángulos reales, false para superficie continua /// 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")}"); + System.Diagnostics.Debug.WriteLine($"[3D Debug] Modo triángulos reales: {(enableDebug ? "ACTIVADO (triángulos individuales)" : "DESACTIVADO (superficie continua)")}"); if (wasChanged && forceRefresh) { @@ -1002,6 +994,22 @@ namespace CtrEditor.Simulacion } } + /// + /// ✅ NUEVO: Método simple para activar el modo debug - muestra triángulos reales planos + /// + public void ShowRealTriangles() + { + SetDebugTrianglesMode(true); + } + + /// + /// ✅ NUEVO: Método simple para activar el modo superficie continua + /// + public void ShowContinuousSurface() + { + SetDebugTrianglesMode(false); + } + /// /// ✅ NUEVO: Fuerza la regeneración de todas las visualizaciones de curvas /// Útil cuando se cambia el modo debug @@ -1077,17 +1085,17 @@ namespace CtrEditor.Simulacion debugInfo.AppendLine(meshInfo); debugInfo.AppendLine($""); - // ✅ EXTRAER Y MOSTRAR TRIÁNGULOS REALES - var realTriangles = curve.GetRealBEPUTriangles(); - debugInfo.AppendLine($"TRIÁNGULOS REALES EXTRAÍDOS: {realTriangles.Length}"); + // ✅ 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 con detalles completos - int maxToShow = Math.Min(10, realTriangles.Length); + // 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 = realTriangles[i]; - debugInfo.AppendLine($"Triángulo {i + 1}:"); + 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})"); @@ -1101,9 +1109,24 @@ namespace CtrEditor.Simulacion debugInfo.AppendLine($""); } - if (realTriangles.Length > maxToShow) + // ✅ 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++) { - debugInfo.AppendLine($"... y {realTriangles.Length - maxToShow} triángulos más"); + 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($""); @@ -1118,18 +1141,18 @@ namespace CtrEditor.Simulacion } /// - /// ✅ NUEVO: Activa la visualización de triángulos reales de BEPU - /// Muestra los triángulos exactos extraídos de la simulación física + /// ✅ 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 /// - /// True para mostrar triángulos reales, false para superficie normal + /// True para mostrar triángulos locales, false para superficie normal public void SetRealBEPUTrianglesMode(bool enable) { SetDebugTrianglesMode(enable); - System.Diagnostics.Debug.WriteLine($"[3D BEPU] Modo triángulos reales: {(enable ? "ACTIVADO" : "DESACTIVADO")}"); + System.Diagnostics.Debug.WriteLine($"[3D BEPU] Modo triángulos locales: {(enable ? "ACTIVADO" : "DESACTIVADO")}"); } /// - /// ✅ NUEVO: Verifica si una curva tiene triángulos válidos en BEPU + /// ✅ CORREGIDO: Verifica si una curva tiene triángulos válidos en BEPU /// /// Curva a verificar /// True si tiene triángulos válidos @@ -1139,7 +1162,7 @@ namespace CtrEditor.Simulacion try { - var triangles = curve.GetRealBEPUTriangles(); + var triangles = curve.GetRealBEPUTriangles(); // Obtiene triángulos locales bool hasTriangles = triangles.Length > 0; System.Diagnostics.Debug.WriteLine($"[3D BEPU] Curva tiene {triangles.Length} triángulos válidos: {hasTriangles}");