diff --git a/Documentation/MemoriadeEvolucion.md b/Documentation/MemoriadeEvolucion.md index 10d6d6e..29ea79b 100644 --- a/Documentation/MemoriadeEvolucion.md +++ b/Documentation/MemoriadeEvolucion.md @@ -24,3 +24,7 @@ simTransporte : Es un box por donde las botellas pueden desplazarse usando un tr * Cuando una botella (`simBotella`) entra en contacto con un transporte de frenado (`simTransporte` con `isBrake = true`), su posición se ajusta automáticamente para centrarla en el eje longitudinal del transporte. Esto se realiza una única vez, en el primer contacto, para asegurar un acoplamiento suave y predecible. La posición de la botella se proyecta sobre la línea central del transporte y su velocidad lateral se anula, evitando que la botella se desvíe mientras frena y se alinea con el flujo de salida. +* Se ha implementado un indicador visual simple para mostrar cuando los transportes (`simTransporte`) están en movimiento. El sistema detecta automáticamente si un transporte tiene velocidad (`Speed > 0.001`) y cambia su color a verde brillante. Los transportes detenidos se muestran en verde normal. Esta funcionalidad se integra en el sistema de materiales del `BEPUVisualization3DManager` y se actualiza automáticamente cuando cambia el estado del transporte, proporcionando una retroalimentación visual inmediata del estado de operación. + +* Se ha implementado un sistema de animaciones automáticas usando StoryBoard de WPF para los transportes en movimiento. Los transportes activos muestran una animación continua que combina: (1) rotación sutil muy lenta alrededor del eje Z (20 segundos por vuelta completa) y (2) pulsación cíclica del color del material (1.5 segundos por ciclo). Las animaciones se crean y destruyen automáticamente según el estado del transporte, sin necesidad de actualización manual en cada frame. El sistema gestiona las animaciones activas en un diccionario y las limpia correctamente cuando se eliminan objetos. Se resolvió el problema de `InvalidOperationException` al animar brushes inmutables creando una función `CreateAnimatableMaterial` que genera materiales específicamente diseñados para ser animados sin estar "frozen", proporcionando una experiencia visual fluida y eficiente. + diff --git a/ObjetosSim/Dinamicos/ucBotella.xaml.cs b/ObjetosSim/Dinamicos/ucBotella.xaml.cs index fb0a115..1b65d0e 100644 --- a/ObjetosSim/Dinamicos/ucBotella.xaml.cs +++ b/ObjetosSim/Dinamicos/ucBotella.xaml.cs @@ -172,14 +172,12 @@ namespace CtrEditor.ObjetosSim SetCentro(SimGeometria.Center); // Sistema de colores jerarquizado para diferentes estados - if (SimGeometria.isRestricted) - ColorButton_oculto = Brushes.Yellow; // Estado restringido (prioridad alta) - else if (SimGeometria.isOnBrakeTransport) - ColorButton_oculto = Brushes.Blue; // En transporte con freno - NUEVO ESTADO - else if (SimGeometria.IsOnAnyTransport()) - ColorButton_oculto = Brushes.Red; // En transporte normal + if (SimGeometria.isOnBrakeTransport) + ColorButton_oculto = Brushes.Blue; // En transporte con freno (prioridad sobre presión) + else if (!SimGeometria.isOnBrakeTransport && SimGeometria.PressureBuildup>1) + ColorButton_oculto = Brushes.Red; // La botella tiene mucha presion else - ColorButton_oculto = Brushes.Gray; // Estado libre + ColorButton_oculto = Brushes.Gray; // 5. Estado libre // Ha sido marcada para remover if (SimGeometria.Descartar) diff --git a/Simulacion/BEPU.cs b/Simulacion/BEPU.cs index a9f3b4a..75de895 100644 --- a/Simulacion/BEPU.cs +++ b/Simulacion/BEPU.cs @@ -25,6 +25,7 @@ namespace CtrEditor.Simulacion public struct NarrowPhaseCallbacks : INarrowPhaseCallbacks { private SimulationManagerBEPU _simulationManager; + private const float minPressureToLostFriction = 1; public NarrowPhaseCallbacks(SimulationManagerBEPU simulationManager) { @@ -135,14 +136,15 @@ namespace CtrEditor.Simulacion botella.LastTransportCollisionZ = body.Pose.Position.Z; } // Decrementamos la presión acumulada al tocar un transporte. Se reduce más rápido de lo que aumenta. - botella.PressureBuildup = Math.Max(0, botella.PressureBuildup - 5); + botella.PressureBuildup = Math.Max(0, botella.PressureBuildup - 0.1f); // ✅ Fricción ajustada para un arrastre firme pero no excesivo. var transport = transportA ?? transportB; if (transport.isBrake) { + botella.PressureBuildup = 0; // ✅ NUEVO: Centrar la botella en el transporte de frenado la primera vez que entra. - // if (!botella.isOnBrakeTransport) + // if (!botella.isOnBrakeTransport) { if (botella.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(botella.BodyHandle) && transport.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(transport.BodyHandle)) @@ -176,10 +178,20 @@ namespace CtrEditor.Simulacion } else { - botella.isOnBrakeTransport = false; - pairMaterial.FrictionCoefficient = 1.2f; - pairMaterial.MaximumRecoveryVelocity = 1.0f; - pairMaterial.SpringSettings = new SpringSettings(80, 1); + if (botella.PressureBuildup > minPressureToLostFriction) + { + botella.isOnBrakeTransport = false; + pairMaterial.FrictionCoefficient = 0.1f; + pairMaterial.MaximumRecoveryVelocity = 1.0f; + pairMaterial.SpringSettings = new SpringSettings(80, 1); + } + else + { + botella.isOnBrakeTransport = false; + pairMaterial.FrictionCoefficient = 1.2f; + pairMaterial.MaximumRecoveryVelocity = 1.0f; + pairMaterial.SpringSettings = new SpringSettings(80, 1); + } } } @@ -191,6 +203,7 @@ namespace CtrEditor.Simulacion // ✅ NUEVO: Lógica de control de presión/flotación botella.IsTouchingTransport = true; + botella.isOnBrakeTransport = false; var botellaCollidable = GetBotellaFromCollidable(pair.A) != null ? pair.A : pair.B; if (botellaCollidable.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(botellaCollidable.BodyHandle)) { @@ -198,19 +211,27 @@ namespace CtrEditor.Simulacion botella.LastTransportCollisionZ = body.Pose.Position.Z; } // Decrementamos la presión acumulada al tocar una curva. - botella.PressureBuildup = Math.Max(0, botella.PressureBuildup - 5); + botella.PressureBuildup = Math.Max(0, botella.PressureBuildup - 0.1f); // ✅ Fricción ajustada para un arrastre firme pero no excesivo. - pairMaterial.FrictionCoefficient = 1f; - pairMaterial.MaximumRecoveryVelocity = 1.0f; - pairMaterial.SpringSettings = new SpringSettings(80, 1); + if (botella.PressureBuildup > minPressureToLostFriction) + { + pairMaterial.FrictionCoefficient = 0.1f; + pairMaterial.MaximumRecoveryVelocity = 1.0f; + pairMaterial.SpringSettings = new SpringSettings(80, 1); + }else + { + pairMaterial.FrictionCoefficient = 1f; + pairMaterial.MaximumRecoveryVelocity = 1.0f; + pairMaterial.SpringSettings = new SpringSettings(80, 1); + } } // ✅ CONTACTO BOTELLA-GUÍA: Configuración específica de fricción else if (botella != null && (GetGuiaFromCollidable(pair.A) != null || GetGuiaFromCollidable(pair.B) != null)) { // Fricción baja para las guías, deben deslizar, no arrastrar. - pairMaterial.FrictionCoefficient = 0.1f; - pairMaterial.MaximumRecoveryVelocity = 1f; + pairMaterial.FrictionCoefficient = 0.01f; + pairMaterial.MaximumRecoveryVelocity = 2f; pairMaterial.SpringSettings = new SpringSettings(20, 1); } // ✅ NUEVO: CONTACTO BOTELLA-BOTELLA: Configuración más suave para evitar el comportamiento "pegajoso". @@ -228,6 +249,12 @@ namespace CtrEditor.Simulacion pairMaterial.MaximumRecoveryVelocity = 1.5f; // Menos rebote. pairMaterial.SpringSettings = new SpringSettings(20, 0.5f); // Muelle MUCHO más suave y críticamente amortiguado. } + // Si una botella tiene presion esta presion es transmitia a el resto para que el resto pierda el coeficiente de friccion sobre los transportes + if (botellaA.PressureBuildup > 0 && botellaB.PressureBuildup == 0) + botellaB.PressureBuildup = botellaA.PressureBuildup; + if (botellaB.PressureBuildup > 0 && botellaA.PressureBuildup == 0) + botellaA.PressureBuildup = botellaB.PressureBuildup; + } // Ajustes básicos para otras botellas else if (botella != null) diff --git a/Simulacion/BEPUVisualization3D.cs b/Simulacion/BEPUVisualization3D.cs index 26a5bbb..1c34c0d 100644 --- a/Simulacion/BEPUVisualization3D.cs +++ b/Simulacion/BEPUVisualization3D.cs @@ -8,6 +8,8 @@ using BepuPhysics.Collidables; using HelixToolkit.Wpf; using System.Linq; using System.Windows.Input; +using System.Windows.Media.Animation; +using System.Windows; namespace CtrEditor.Simulacion { @@ -27,6 +29,9 @@ namespace CtrEditor.Simulacion public float OuterRadius { get; set; } public float StartAngle { get; set; } public float EndAngle { get; set; } + + // ✅ NUEVO: Estado de movimiento para transportes + public bool IsMoving { get; set; } public bool Equals(ShapeDimensions other) { @@ -38,7 +43,8 @@ namespace CtrEditor.Simulacion Math.Abs(OuterRadius - other.OuterRadius) < 0.001f && Math.Abs(StartAngle - other.StartAngle) < 0.001f && Math.Abs(EndAngle - other.EndAngle) < 0.001f && - ShapeType == other.ShapeType; + ShapeType == other.ShapeType && + IsMoving == other.IsMoving; } } @@ -70,6 +76,9 @@ namespace CtrEditor.Simulacion private SimulationManagerBEPU simulationManager; private Dictionary simBaseToModelMap; private Dictionary lastKnownDimensions; + + // ✅ NUEVO: Diccionario para gestionar animaciones activas + private Dictionary activeAnimations; // ✅ CORREGIDO: Flag de debug para mostrar triángulos individuales de curvas (true temporalmente para verificar) public static bool DebugShowIndividualTriangles { get; set; } = false; @@ -92,6 +101,7 @@ namespace CtrEditor.Simulacion simulationManager = simManager; simBaseToModelMap = new Dictionary(); lastKnownDimensions = new Dictionary(); + activeAnimations = new Dictionary(); InitializeViewport(); } @@ -243,6 +253,12 @@ namespace CtrEditor.Simulacion foreach (var simObj in objectsToRemove) { + // ✅ NUEVO: Limpiar animaciones activas para objetos eliminados + if (simObj is simTransporte transporteToRemove && activeAnimations.ContainsKey(transporteToRemove)) + { + StopTransportAnimation(transporteToRemove); + } + simBaseToModelMap.Remove(simObj); lastKnownDimensions.Remove(simObj); } @@ -626,6 +642,12 @@ namespace CtrEditor.Simulacion // System.Diagnostics.Debug.WriteLine($"[3D Update] No geometry changes needed for {simObj.GetType().Name}"); } + // ✅ NUEVO: Gestionar animaciones para transportes + if (simObj is simTransporte transporte) + { + ManageTransportAnimation(transporte, visual); + } + // Actualizar transformación del modelo 3D var transform = new Transform3DGroup(); @@ -689,6 +711,12 @@ namespace CtrEditor.Simulacion return dimensions; } + // ✅ NUEVO: Para simTransporte, detectar si está en movimiento + if (simObj is simTransporte transporte) + { + dimensions.IsMoving = Math.Abs(transporte.Speed) > 0.001f; // Consideramos movimiento si la velocidad es mayor que 0.001 + } + // Para otros objetos, usar la forma real de BEPU if (shapeIndex.Type == BepuPhysics.Collidables.Sphere.Id) { @@ -806,6 +834,13 @@ namespace CtrEditor.Simulacion public void Clear() { + // ✅ NUEVO: Detener todas las animaciones activas antes de limpiar + foreach (var kvp in activeAnimations) + { + kvp.Value.Stop(); + } + activeAnimations.Clear(); + foreach (var model in simBaseToModelMap.Values) { viewport3D.Children.Remove(model); @@ -833,6 +868,40 @@ namespace CtrEditor.Simulacion ); } + /// + /// ✅ NUEVO: Crea un material animable (no frozen) para poder animar sus propiedades + /// + /// Color del material + /// Potencia especular + /// Nivel de luz ambiente + /// Material que puede ser animado + private Material CreateAnimatableMaterial(Color color, double specularPower = 100, byte ambient = 190) + { + // Crear brush animable (no frozen) + var diffuseBrush = new SolidColorBrush(color); + + // Crear material difuso + var diffuseMaterial = new DiffuseMaterial(diffuseBrush); + + // Crear material especular - ✅ CORREGIDO: Usar un gris claro en lugar de blanco puro para reducir el brillo excesivo + var specularBrush = new SolidColorBrush(Color.FromRgb(128, 128, 128)); + var specularMaterial = new SpecularMaterial(specularBrush, specularPower); + + // Crear material "ambiente" (usando Emissive) - ✅ CORREGIDO: Usar un color mucho más oscuro para no sobreexponer + var ambientColor = Color.FromRgb((byte)(ambient / 4), (byte)(ambient / 4), (byte)(ambient / 4)); + var ambientBrush = new SolidColorBrush(ambientColor); + var ambientMaterial = new EmissiveMaterial(ambientBrush); + + // Combinar todos los materiales + var materialGroup = new MaterialGroup(); + materialGroup.Children.Add(diffuseMaterial); + materialGroup.Children.Add(specularMaterial); + materialGroup.Children.Add(ambientMaterial); + + // NO hacer freeze para mantener animable + return materialGroup; + } + /// /// Función centralizada para obtener el material apropiado según el tipo de simBase /// @@ -840,14 +909,24 @@ namespace CtrEditor.Simulacion /// Material configurado para el tipo de objeto private Material GetMaterialForSimBase(simBase simObj) { - if (simObj is simTransporte) + if (simObj is simTransporte transporte) { - // Material plástico verde para transportes - return MaterialHelper.CreateMaterial( - new SolidColorBrush(Color.FromRgb(80, 180, 80)), - specularPower: 140, - ambient: 200 - ); + // ✅ NUEVO: Cambiar color según si está en movimiento + bool isMoving = Math.Abs(transporte.Speed) > 0.001f; + if (isMoving) + { + // ✅ CORREGIDO: Usar material animable para transportes en movimiento + return CreateAnimatableMaterial(Color.FromRgb(40, 255, 40), 180, 240); + } + else + { + // Material plástico verde normal para transportes detenidos + return MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(80, 180, 80)), + specularPower: 140, + ambient: 200 + ); + } } else if (simObj is simGuia) { @@ -940,6 +1019,150 @@ namespace CtrEditor.Simulacion } } + /// + /// ✅ NUEVO: Crea una animación de movimiento para transportes usando StoryBoard + /// Combina rotación sutil + pulsación de brillo para simular movimiento + /// + /// Transporte al cual aplicar la animación + /// Visual3D del transporte + private void CreateTransportMovementAnimation(simTransporte transporte, ModelVisual3D visual) + { + try + { + // Detener animación anterior si existe + StopTransportAnimation(transporte); + + // Crear nuevo StoryBoard + var storyboard = new Storyboard(); + storyboard.RepeatBehavior = RepeatBehavior.Forever; + + // ✅ ANIMACIÓN 1: Rotación sutil continua alrededor del eje Z (muy lenta) + var rotationAnimation = new DoubleAnimation + { + From = 0, + To = 360, + Duration = TimeSpan.FromSeconds(20), // 20 segundos por vuelta completa (muy lento) + RepeatBehavior = RepeatBehavior.Forever + }; + + // Crear transform group si no existe + if (visual.Transform is not Transform3DGroup transformGroup) + { + transformGroup = new Transform3DGroup(); + if (visual.Transform != null) + transformGroup.Children.Add(visual.Transform); + visual.Transform = transformGroup; + } + + // Agregar rotación de animación + var animationRotation = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 0, 1), 0)); + transformGroup.Children.Add(animationRotation); + + // Configurar target para la rotación + Storyboard.SetTarget(rotationAnimation, animationRotation); + Storyboard.SetTargetProperty(rotationAnimation, new PropertyPath("Rotation.Angle")); + storyboard.Children.Add(rotationAnimation); + + // ✅ ANIMACIÓN 2: Pulsación de brillo del material + if (visual.Content is GeometryModel3D geometryModel && + geometryModel.Material is MaterialGroup materialGroup) + { + // Buscar el material difuso para animar (debe ser el primero en mi CreateAnimatableMaterial) + var diffuseMaterial = materialGroup.Children.OfType().FirstOrDefault(); + if (diffuseMaterial?.Brush is SolidColorBrush diffuseBrush) + { + // ✅ CORREGIDO: El brush ya debe ser animable si se creó con CreateAnimatableMaterial + var colorAnimation = new ColorAnimation + { + From = Color.FromRgb(40, 255, 40), // Verde brillante + To = Color.FromRgb(80, 255, 80), // Verde más brillante + Duration = TimeSpan.FromSeconds(1.5), // Pulsación cada 1.5 segundos + AutoReverse = true, + RepeatBehavior = RepeatBehavior.Forever + }; + + Storyboard.SetTarget(colorAnimation, diffuseBrush); + Storyboard.SetTargetProperty(colorAnimation, new PropertyPath("Color")); + storyboard.Children.Add(colorAnimation); + } + } + + // Iniciar animación + storyboard.Begin(); + + // Almacenar referencia para poder detenerla después + activeAnimations[transporte] = storyboard; + + System.Diagnostics.Debug.WriteLine($"[3D Animation] ✅ Animación de movimiento creada para transporte"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Animation] ❌ Error creando animación: {ex.Message}"); + } + } + + /// + /// ✅ NUEVO: Detiene la animación de movimiento de un transporte + /// + /// Transporte cuya animación se desea detener + private void StopTransportAnimation(simTransporte transporte) + { + try + { + if (activeAnimations.TryGetValue(transporte, out var storyboard)) + { + storyboard.Stop(); + activeAnimations.Remove(transporte); + + // Limpiar transforms de animación del visual + if (simBaseToModelMap.TryGetValue(transporte, out var visual) && + visual.Transform is Transform3DGroup transformGroup) + { + // Remover solo la rotación de animación (la última agregada) + if (transformGroup.Children.Count > 1) + { + transformGroup.Children.RemoveAt(transformGroup.Children.Count - 1); + } + } + + System.Diagnostics.Debug.WriteLine($"[3D Animation] ✅ Animación de transporte detenida"); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Animation] ❌ Error deteniendo animación: {ex.Message}"); + } + } + + /// + /// ✅ NUEVO: Gestiona automáticamente las animaciones de transportes según su estado + /// + /// Transporte a gestionar + /// Visual3D del transporte + private void ManageTransportAnimation(simTransporte transporte, ModelVisual3D visual) + { + try + { + bool isMoving = Math.Abs(transporte.Speed) > 0.001f; + bool hasActiveAnimation = activeAnimations.ContainsKey(transporte); + + if (isMoving && !hasActiveAnimation) + { + // Crear animación si el transporte está en movimiento y no tiene animación + CreateTransportMovementAnimation(transporte, visual); + } + else if (!isMoving && hasActiveAnimation) + { + // Detener animación si el transporte está detenido y tiene animación + StopTransportAnimation(transporte); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Animation] ❌ Error gestionando animación: {ex.Message}"); + } + } + /// /// ✅ MODO DEBUG DE TRIÁNGULOS DE CURVAS: /// @@ -1310,7 +1533,8 @@ namespace CtrEditor.Simulacion InnerRadius = 0, OuterRadius = 0, StartAngle = 0, - EndAngle = 0 + EndAngle = 0, + IsMoving = false // Las barreras no se mueven }; } diff --git a/Simulacion/simBotella.cs b/Simulacion/simBotella.cs index 5de6494..3667b71 100644 --- a/Simulacion/simBotella.cs +++ b/Simulacion/simBotella.cs @@ -33,7 +33,7 @@ namespace CtrEditor.Simulacion // ✅ NUEVO: Propiedades para control de flotación/presión public float? LastTransportCollisionZ { get; set; } - public int PressureBuildup { get; set; } = 0; + public float PressureBuildup { get; set; } = 0; public bool IsTouchingTransport { get; set; } = false; // ✅ NUEVO: Propiedades para la física personalizada