diff --git a/CtrEditor.csproj b/CtrEditor.csproj index bbfe580..07894e4 100644 --- a/CtrEditor.csproj +++ b/CtrEditor.csproj @@ -27,12 +27,16 @@ true + + + + + + + - - - @@ -76,26 +80,23 @@ - - - - - - + + + @@ -103,8 +104,6 @@ - - @@ -187,7 +186,6 @@ - diff --git a/MainViewModel.cs b/MainViewModel.cs index 65442ba..e2ee2e2 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -44,7 +44,7 @@ namespace CtrEditor private float TiempoDesdeStartSimulacion; private bool Debug_SimulacionCreado = false; - public SimulationManagerFP simulationManager = new SimulationManagerFP(); + public SimulationManagerBEPU simulationManager = new SimulationManagerBEPU(); private readonly DispatcherTimer _timerSimulacion; private readonly DispatcherTimer _timerPLCUpdate; @@ -52,6 +52,9 @@ namespace CtrEditor public Canvas MainCanvas; + // Manager para la visualización 3D + public BEPUVisualization3DManager Visualization3DManager { get; set; } + [ObservableProperty] private bool isConnected; diff --git a/MainWindow.xaml b/MainWindow.xaml index 78cd097..530febf 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -122,7 +122,9 @@ - + + + @@ -223,6 +225,35 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ObjetosSim/Dinamicos/ucBotella.xaml.cs b/ObjetosSim/Dinamicos/ucBotella.xaml.cs index af510d6..ca9433b 100644 --- a/ObjetosSim/Dinamicos/ucBotella.xaml.cs +++ b/ObjetosSim/Dinamicos/ucBotella.xaml.cs @@ -1,11 +1,9 @@ using System.Windows; using System.Windows.Controls; -//using using Microsoft.Xna.Framework; - using LibS7Adv; using CtrEditor.Simulacion; using CommunityToolkit.Mvvm.ComponentModel; -using nkast.Aether.Physics2D.Common; +using System.Numerics; using System.Windows.Media; using CtrEditor.FuncionesBase; using System.ComponentModel; diff --git a/ObjetosSim/Dinamicos/ucBotellaCuello.xaml.cs b/ObjetosSim/Dinamicos/ucBotellaCuello.xaml.cs index dbec795..751a152 100644 --- a/ObjetosSim/Dinamicos/ucBotellaCuello.xaml.cs +++ b/ObjetosSim/Dinamicos/ucBotellaCuello.xaml.cs @@ -5,7 +5,7 @@ using System.Windows.Controls; using LibS7Adv; using CtrEditor.Simulacion; using CommunityToolkit.Mvvm.ComponentModel; -using nkast.Aether.Physics2D.Common; +using System.Numerics; using System.Windows.Media; using CtrEditor.FuncionesBase; using System.ComponentModel; diff --git a/ObjetosSim/Estaticos/ucDescarte.xaml.cs b/ObjetosSim/Estaticos/ucDescarte.xaml.cs index 089be67..d9f2fb0 100644 --- a/ObjetosSim/Estaticos/ucDescarte.xaml.cs +++ b/ObjetosSim/Estaticos/ucDescarte.xaml.cs @@ -3,7 +3,7 @@ using LibS7Adv; using CtrEditor.Simulacion; using System.Windows; using System.Windows.Controls; -using nkast.Aether.Physics2D.Common; +using System.Numerics; using System.Windows.Media.Animation; using CommunityToolkit.Mvvm.ComponentModel; using CtrEditor.FuncionesBase; diff --git a/ObjetosSim/osBase.cs b/ObjetosSim/osBase.cs index a8c3730..3089a9b 100644 --- a/ObjetosSim/osBase.cs +++ b/ObjetosSim/osBase.cs @@ -4,7 +4,7 @@ using CtrEditor.Serialization; using CtrEditor.Services; using CtrEditor.Simulacion; using LibS7Adv; -using nkast.Aether.Physics2D.Common; +using System.Numerics; using PaddleOCRSharp; using Siemens.Simatic.Simulation.Runtime; using System.ComponentModel; // Para poder usar [property: Category ... diff --git a/ObjetosSim/osSistemaFluidos.cs b/ObjetosSim/osSistemaFluidos.cs deleted file mode 100644 index 56f83f6..0000000 --- a/ObjetosSim/osSistemaFluidos.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Windows.Media; -using CommunityToolkit.Mvvm.ComponentModel; -using CtrEditor.FuncionesBase; -using CtrEditor.Simulacion.Fluids; -using CtrEditor.Simulacion.Fluids.Components; -using nkast.Aether.Physics2D.Common; -using Color = System.Windows.Media.Color; -using Siemens.Simatic.Simulation.Runtime; -using LibS7Adv; - -namespace CtrEditor.ObjetosSim -{ - /// - /// ViewModel para el sistema de fluidos - /// - public partial class osSistemaFluidos : osBase, IosBase - { - // Referencia a la simulación de fluidos - public SimulacionFluidos _simFluidos; - - // Tamaño del área de simulación - [ObservableProperty] - [property: Category("Configuración")] - [property: Description("Ancho del área de simulación en metros")] - [property: Name("Ancho Simulación")] - private float anchoSimulacion = 10.0f; - - [ObservableProperty] - [property: Category("Configuración")] - [property: Description("Alto del área de simulación en metros")] - [property: Name("Alto Simulación")] - private float altoSimulacion = 10.0f; - - // Propiedades del fluido - [ObservableProperty] - [property: Category("Apariencia")] - [property: Description("Tamaño visual de las partículas")] - [property: Name("Tamaño Partícula")] - private float tamañoParticula = 0.01f; - - [ObservableProperty] - [property: Category("Apariencia")] - [property: Description("Color del fluido")] - [property: Name("Color Fluido")] - private Color colorFluido = Colors.CornflowerBlue; - - [ObservableProperty] - [property: Category("Apariencia")] - [property: Description("Opacidad de las partículas")] - [property: Name("Opacidad Partículas")] - private double opacidadParticulas = 0.7; - - // Propiedades de gravedad - [ObservableProperty] - [property: Category("Simulación")] - [property: Description("Gravedad en X (m/s²)")] - [property: Name("Gravedad X")] - private float gravedadX = 0.0f; - - [ObservableProperty] - [property: Category("Simulación")] - [property: Description("Gravedad en Y (m/s²)")] - [property: Name("Gravedad Y")] - private float gravedadY = 9.8f; - - partial void OnGravedadXChanged(float value) - { - ActualizarGravedad(); - } - - partial void OnGravedadYChanged(float value) - { - ActualizarGravedad(); - } - - // Estadísticas de la simulación - [ObservableProperty] - [property: Category("Información")] - [property: Description("Número de partículas")] - [property: Name("Número Partículas")] - private int numeroParticulas; - - [ObservableProperty] - [property: Category("Información")] - [property: Description("Rendimiento en FPS")] - [property: Name("FPS")] - private double fps; - - // Referencia a componentes (solo para la función Add) - private List _contenedores = new List(); - - // Nombre de la clase para identificación - public static string NombreClase() - { - return "Sistema de Fluidos"; - } - - private string nombre = NombreClase(); - - [property: Category("Identificación")] - [property: Description("Nombre identificativo del objeto")] - [property: Name("Nombre")] - public override string Nombre - { - get => nombre; - set => SetProperty(ref nombre, value); - } - - // Métodos para interactuar con la simulación - - /// - /// Agrega partículas en un punto específico - /// - public void AgregarParticula(Vector2 posicion) - { - _simFluidos?.AgregarParticula(posicion); - } - - /// - /// Agrega múltiples partículas en un área - /// - public void AgregarParticulasEnArea(Vector2 centro, float ancho, float alto, int cantidad) - { - _simFluidos?.AgregarParticulasEnArea(centro, ancho, alto, cantidad); - } - - /// - /// Crea un nuevo tanque y lo agrega a la simulación - /// - public Tanque CrearTanque(Vector2 posicion, float ancho, float alto) - { - if (_simFluidos == null) return null; - - Tanque tanque = new Tanque(posicion, ancho, alto, (int)(AnchoSimulacion * 100)); - _simFluidos.AgregarContenedor(tanque); - _contenedores.Add(tanque); - return tanque; - } - - /// - /// Crea una nueva tubería - /// - public Tuberia CrearTuberia(float diametro) - { - if (_simFluidos == null) return null; - - Tuberia tuberia = new Tuberia(diametro, (int)(AnchoSimulacion * 100)); - _simFluidos.AgregarContenedor(tuberia); - _contenedores.Add(tuberia); - return tuberia; - } - - /// - /// Crea una nueva válvula - /// - public Valvula CrearValvula(Vector2 posicion, float diametro, float apertura = 1.0f) - { - if (_simFluidos == null) return null; - - Valvula valvula = new Valvula(posicion, diametro, apertura, (int)(AnchoSimulacion * 100)); - _simFluidos.AgregarContenedor(valvula); - _contenedores.Add(valvula); - return valvula; - } - - /// - /// Elimina un componente de la simulación - /// - public void EliminarComponente(IContenedorFluido componente) - { - if (_simFluidos == null || componente == null) return; - - _simFluidos.RemoverContenedor(componente); - _contenedores.Remove(componente); - } - - /// - /// Limpia todas las partículas de la simulación - /// - public void LimpiarParticulas() - { - _simFluidos?.LimpiarParticulas(); - } - - /// - /// Actualiza el vector de gravedad según las propiedades - /// - private void ActualizarGravedad() - { - if (_simFluidos != null) - { - _simFluidos.AjustarGravedad(new Vector2(GravedadX, GravedadY)); - } - } - - /// - /// Constructor de la clase - /// - public osSistemaFluidos() - { - // Inicializar propiedades básicas - Ancho = 1.0f; - Alto = 1.0f; - } - - // Métodos sobrescritos de osBase - - public override void UpdateGeometryStart() - { - // Crear la simulación de fluidos si es necesario - if (_simFluidos == null) - { - _simFluidos = new SimulacionFluidos( - AnchoSimulacion, - AltoSimulacion, - 10000, // Máximo de partículas - new Vector2(GravedadX, GravedadY) - ); - } - } - - public override void UpdateGeometryStep() - { - // No es necesario actualizar en cada paso - } - - public override void UpdateControl(int elapsedMilliseconds) - { - // Actualizar estadísticas - if (_simFluidos != null) - { - NumeroParticulas = _simFluidos.ParticlesCount; - } - } - - /// - /// Llamado cuando se inicia la simulación de fluidos - /// - public void OnFluidSimulationStart() - { - // Crear la simulación de fluidos si es necesario - UpdateGeometryStart(); - } - - /// - /// Llamado cuando se detiene la simulación de fluidos - /// - public void OnFluidSimulationStop() - { - // Detener recursos si es necesario - SimulationStop(); - } - - /// - /// Actualiza la simulación de fluidos - /// - public void UpdateFluidSimulation(float deltaTime) - { - // Actualizar la simulación con el delta time - if (_simFluidos != null) - { - _simFluidos.Actualizar(deltaTime); - - // Actualizar el control visual - UpdateControl((int)(deltaTime * 1000)); - } - } - - public override void SimulationStop() - { - // Limpiar recursos si es necesario cuando se detiene la simulación - } - - public override void ucLoaded() - { - base.ucLoaded(); - - // Inicializar la simulación de fluidos si es necesario - UpdateGeometryStart(); - } - - public override void ucUnLoaded() - { - // Limpiar recursos - _simFluidos = null; - _contenedores.Clear(); - } - - // Implementación para las conexiones con PLC - - [ObservableProperty] - [property: Description("Tag de lectura/escritura del nivel del Tanque 1")] - [property: Category("PLC:")] - private string tagNivelTanque1; - - [ObservableProperty] - [property: Description("Tag de lectura/escritura de la apertura de la Válvula 1")] - [property: Category("PLC:")] - private string tagAperturaValvula1; - - // Referencia a componentes típicos para integración con PLC - private Tanque _tanque1; - private Valvula _valvula1; - - public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds) - { - // Ejemplo de integración con PLC para la válvula - if (_valvula1 != null && !string.IsNullOrEmpty(TagAperturaValvula1)) - { - float aperturaValvula = LeerWordTagScaled(TagAperturaValvula1) / 100.0f; - _valvula1.Apertura = Math.Clamp(aperturaValvula, 0, 1); - } - - // Ejemplo de escritura del nivel del tanque al PLC - if (_tanque1 != null && !string.IsNullOrEmpty(TagNivelTanque1)) - { - float nivelTanque = 0; // Implementar cálculo real del nivel - EscribirWordTagScaled(TagNivelTanque1, nivelTanque * 100, 0, 100, 0, 27648); - } - } - } -} diff --git a/ObjetosSim/ucBasicExample.xaml b/ObjetosSim/ucBasicExample.xaml deleted file mode 100644 index 5c43d60..0000000 --- a/ObjetosSim/ucBasicExample.xaml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ObjetosSim/ucBasicExample.xaml.cs b/ObjetosSim/ucBasicExample.xaml.cs deleted file mode 100644 index af3cc05..0000000 --- a/ObjetosSim/ucBasicExample.xaml.cs +++ /dev/null @@ -1,245 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media.Animation; -using System.Windows.Media; -using CommunityToolkit.Mvvm.ComponentModel; - -using LibS7Adv; -using CtrEditor.Simulacion; -using System.Windows.Input; -using System.ComponentModel; - - -namespace CtrEditor.ObjetosSim -{ - /// - /// Interaction logic for ucBasicExample.xaml - /// - /// - - public partial class osBasicExample : osBase, IosBase - { - private osBase _osMotor = null; - - private simTransporte SimGeometria; - - public static string NombreClase() - { - return "Ejemplo Básico"; - } - private string nombre = NombreClase(); - - [property: Category("Identificación")] - [property: Description("Nombre identificativo del objeto")] - [property: Name("Nombre")] - public override string Nombre - { - get => nombre; - set => SetProperty(ref nombre, value); - } - - [ObservableProperty] - [property: Category("Simulación")] - [property: Description("Velocidad actual del transporte")] - [property: Name("Velocidad Actual")] - public float velocidadActual; - - partial void OnVelocidadActualChanged(float value) - { - SetSpeed(); - } - - [ObservableProperty] - [property: Category("Configuración")] - [property: Description("Invierte el sentido de movimiento")] - [property: Name("Invertir Dirección")] - bool invertirDireccion; - - partial void OnInvertirDireccionChanged(bool value) - { - SetSpeed(); - if (_visualRepresentation is ucBasicExample uc) - { - CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion); - ActualizarAnimacionStoryBoardTransporte(VelocidadActual); - } - } - - void SetSpeed() - { - if (InvertirDireccion) - SimGeometria?.SetSpeed(-VelocidadActual); - else - SimGeometria?.SetSpeed(VelocidadActual); - ActualizarAnimacionStoryBoardTransporte(VelocidadActual); - } - - - [ObservableProperty] - [property: Category("Enlace PLC")] - [property: Description("Motor enlazado al transporte")] - [property: Name("Motor")] - public string motor; - - partial void OnMotorChanged(string value) - { - _osMotor = ObtenerLink(Motor, typeof(osVMmotorSim)); - } - - [ObservableProperty] - [property: Category("Configuración")] - [property: Description("Ancho del transporte")] - [property: Name("Ancho")] - public float ancho; - - [ObservableProperty] - [property: Category("Configuración")] - [property: Description("Alto del transporte")] - [property: Name("Alto")] - public float alto; - - [ObservableProperty] - [property: Category("Configuración")] - [property: Description("Ángulo de rotación")] - [property: Name("Ángulo")] - public float angulo; - - [ObservableProperty] - [property: Category("Configuración")] - [property: Description("Coeficiente de fricción")] - [property: Name("Coeficiente Fricción")] - public float frictionCoefficient; - - [ObservableProperty] - [property: Category("Configuración")] - [property: Description("Velocidad máxima a 50Hz")] - [property: Name("Velocidad Max 50Hz")] - public float velMax50hz; - - [ObservableProperty] - [property: Category("Configuración")] - [property: Description("Tiempo de rampa")] - [property: Name("Tiempo Rampa")] - public float tiempoRampa; - - [ObservableProperty] - [property: Category("Información")] - [property: Description("Estado de marcha")] - [property: Name("En Marcha")] - public bool esMarcha; - - - private void ActualizarGeometrias() - { - if (_visualRepresentation is ucBasicExample uc) - { - UpdateRectangle(SimGeometria, uc.Transporte, Alto, Ancho, Angulo); - SetSpeed(); - } - ActualizarAnimacionStoryBoardTransporte(VelocidadActual); - } - - public osBasicExample() - { - Ancho = 1; - Alto = 0.10f; - } - - public override void SimulationStop() - { - // Se llama al detener la simulacion - ActualizarAnimacionStoryBoardTransporte(VelocidadActual); - } - public override void UpdateGeometryStart() - { - // Se llama antes de la simulacion - ActualizarGeometrias(); - } - - public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds) - { - if (_osMotor != null) - { - if (_osMotor is osVMmotorSim motor) - VelocidadActual = motor.Velocidad; - } - else if (Motor.Length > 0) - _osMotor = ObtenerLink(Motor, typeof(osVMmotorSim)); - } - - public override void ucLoaded() - { - // El UserControl ya se ha cargado y podemos obtener las coordenadas para - // crear el objeto de simulacion - base.ucLoaded(); - - if (_visualRepresentation is ucBasicExample uc) - { - SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo); - CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion); - } - } - public override void ucUnLoaded() - { - // El UserControl se esta eliminando - // eliminar el objeto de simulacion - simulationManager.Remove(SimGeometria); - } - - } - - public partial class ucBasicExample : UserControl, IDataContainer - { - public osBase? Datos { get; set; } - - public ucBasicExample() - { - InitializeComponent(); - this.Loaded += OnLoaded; - this.Unloaded += OnUnloaded; - } - private void OnLoaded(object sender, RoutedEventArgs e) - { - Datos?.ucLoaded(); - } - private void OnUnloaded(object sender, RoutedEventArgs e) - { - Datos?.ucUnLoaded(); - } - public void Resize(float width, float height) - { - if (Datos is osBasicExample datos) - datos.Ancho += PixelToMeter.Instance.calc.PixelsToMeters(width); - } - public void Move(float LeftPixels, float TopPixels) - { - if (Datos != null) - { - Datos.Left = PixelToMeter.Instance.calc.PixelsToMeters(LeftPixels); - Datos.Top = PixelToMeter.Instance.calc.PixelsToMeters(TopPixels); - } - } - public float Angle() - { - if (Datos != null) - if (Datos is osBasicExample datos) - return datos.Angulo; - return 0f; - } - public void Rotate(float Angle) - { - if (Datos != null) - if (Datos is osBasicExample datos) - datos.Angulo += Angle; - } - public void Highlight(bool State) { } - public int ZIndex() - { - return 1; - } - - } -} - - - diff --git a/ObjetosSim/ucSistemaFluidos.xaml b/ObjetosSim/ucSistemaFluidos.xaml deleted file mode 100644 index feb4e42..0000000 --- a/ObjetosSim/ucSistemaFluidos.xaml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/ObjetosSim/ucSistemaFluidos.xaml.cs b/ObjetosSim/ucSistemaFluidos.xaml.cs deleted file mode 100644 index 7a2c27f..0000000 --- a/ObjetosSim/ucSistemaFluidos.xaml.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; -using CtrEditor.FuncionesBase; -using CtrEditor.Simulacion.Fluids; -using tainicom.Aether.Physics2D.Fluids; - -namespace CtrEditor.ObjetosSim -{ - /// - /// Lógica para ucSistemaFluidos.xaml - /// - public partial class ucSistemaFluidos : UserControl, IDataContainer - { - public osBase? Datos { get; set; } - public int zIndex_fromFrames { get; set; } - - // Host para el sistema visual de partículas - private ParticulasFluidoHost _hostVisual; - - // Timer para medir FPS - private DateTime _ultimaActualizacion = DateTime.Now; - private int _contadorFrames = 0; - private double _fps = 0; - - public ucSistemaFluidos() - { - InitializeComponent(); - this.Loaded += OnLoaded; - this.Unloaded += OnUnloaded; - - // Inicializar el host visual y agregarlo al contenedor - _hostVisual = new ParticulasFluidoHost(); - ContenedorVisual.Children.Add(_hostVisual); - - // Configurar actualizaciones periódicas para FPS - CompositionTarget.Rendering += OnRendering; - } - - private void OnRendering(object sender, EventArgs e) - { - _contadorFrames++; - - // Calcular FPS cada segundo - TimeSpan elapsed = DateTime.Now - _ultimaActualizacion; - if (elapsed.TotalSeconds >= 1) - { - _fps = _contadorFrames / elapsed.TotalSeconds; - _contadorFrames = 0; - _ultimaActualizacion = DateTime.Now; - - if (Datos is osSistemaFluidos vm) - { - vm.Fps = _fps; - } - } - - // Actualizar la visualización de partículas - ActualizarVisualizacion(); - } - - private void OnLoaded(object sender, RoutedEventArgs e) - { - Datos?.ucLoaded(); - } - - private void OnUnloaded(object sender, RoutedEventArgs e) - { - Datos?.ucUnLoaded(); - CompositionTarget.Rendering -= OnRendering; - } - - public void Highlight(bool State) { } - - public ZIndexEnum ZIndex_Base() - { - return ZIndexEnum.Dinamicos; - } - - /// - /// Actualiza la visualización de partículas - /// - private void ActualizarVisualizacion() - { - if (Datos is osSistemaFluidos viewModel) - { - // Obtener la simulación activa desde el ViewModel - var simulacion = viewModel._simFluidos?.SistemaFluido; - if (simulacion != null) - { - // Configurar propiedades visuales - float tamañoParticula = viewModel.TamañoParticula; - Color colorFluido = viewModel.ColorFluido; - - // Actualizar la visualización - _hostVisual.ActualizarParticulasFluido(simulacion.Particles, tamañoParticula, colorFluido, viewModel.OpacidadParticulas); - } - } - } - } - - /// - /// Clase que maneja el rendering optimizado de partículas usando DrawingVisual - /// - public class ParticulasFluidoHost : FrameworkElement - { - private readonly DrawingVisual _visual = new DrawingVisual(); - private readonly MeterToPixelConverter _converter = new MeterToPixelConverter(); - - public ParticulasFluidoHost() - { - AddVisualChild(_visual); - } - - protected override int VisualChildrenCount => 1; - - protected override Visual GetVisualChild(int index) - { - if (index != 0) - throw new ArgumentOutOfRangeException(); - - return _visual; - } - - /// - /// Actualiza la visualización de las partículas de fluido - /// - /// Lista de partículas a visualizar - /// Tamaño de las partículas en metros - /// Color base del fluido - /// Opacidad base de las partículas - public void ActualizarParticulasFluido(IEnumerable particulas, - float tamañoParticula, - Color colorFluido, - double opacidadBase) - { - using (DrawingContext dc = _visual.RenderOpen()) - { - foreach (var particula in particulas) - { - // Ignorar partículas inactivas u ocultas - if (particula == null) - continue; - - // Convertir posición a píxeles - float x = (float)_converter.Convert((particula.Position.X / 100) + 5, null, null, null); - float y = (float)_converter.Convert(particula.Position.Y / 100, null, null, null); - float radio = (float)_converter.Convert(tamañoParticula / 2, null, null, null); - - // Calcular opacidad basada en densidad - double opacidad = Math.Clamp(particula.Density / 15.0, 0.4, 0.9) * opacidadBase; - - // Calcular color basado en velocidad (opcional) - Color colorFinal = colorFluido; - double velocidad = Math.Sqrt(particula.Velocity.X * particula.Velocity.X + - particula.Velocity.Y * particula.Velocity.Y); - - if (velocidad > 50) - { - // Partículas más rápidas tienden a ser más claras - float factor = Math.Min(1.0f, (float)velocidad / 400); - colorFinal = MezclarColores(colorFluido, Colors.White, factor); - } - - SolidColorBrush brush = new SolidColorBrush(colorFinal) { Opacity = opacidad }; - - // Dibujar partícula - dc.DrawEllipse(brush, null, new Point(x, y), radio, radio); - } - } - - InvalidateVisual(); - } - - /// - /// Mezcla dos colores según un factor (0-1) - /// - private Color MezclarColores(Color color1, Color color2, float factor) - { - byte r = (byte)(color1.R + (color2.R - color1.R) * factor); - byte g = (byte)(color1.G + (color2.G - color1.G) * factor); - byte b = (byte)(color1.B + (color2.B - color1.B) * factor); - return Color.FromRgb(r, g, b); - } - } -} diff --git a/ObjetosSim/ucTuberiaFluido.xaml b/ObjetosSim/ucTuberiaFluido.xaml deleted file mode 100644 index 9582049..0000000 --- a/ObjetosSim/ucTuberiaFluido.xaml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/ObjetosSim/ucTuberiaFluido.xaml.cs b/ObjetosSim/ucTuberiaFluido.xaml.cs deleted file mode 100644 index 28ddc58..0000000 --- a/ObjetosSim/ucTuberiaFluido.xaml.cs +++ /dev/null @@ -1,268 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Windows; -using System.Windows.Controls; -using CtrEditor.FuncionesBase; -using CtrEditor.Simulacion.Fluids.Components; -using nkast.Aether.Physics2D.Common; -using CommunityToolkit.Mvvm.ComponentModel; -using System.ComponentModel; - -namespace CtrEditor.ObjetosSim -{ - /// - /// Lógica para ucTuberiaFluido.xaml - /// - public partial class ucTuberiaFluido : UserControl, IDataContainer - { - public osBase? Datos { get; set; } - public int zIndex_fromFrames { get; set; } - - public ucTuberiaFluido() - { - InitializeComponent(); - this.Loaded += OnLoaded; - this.Unloaded += OnUnloaded; - } - - private void OnLoaded(object sender, RoutedEventArgs e) - { - Datos?.ucLoaded(); - } - - private void OnUnloaded(object sender, RoutedEventArgs e) - { - Datos?.ucUnLoaded(); - } - - public void Highlight(bool State) { } - - public ZIndexEnum ZIndex_Base() - { - return ZIndexEnum.Estaticos; - } - } - - /// - /// ViewModel para la tubería de fluidos - /// - public partial class osTuberiaFluido : osBase, IosBase - { - // Tubería en la simulación de fluidos - private Tuberia _tuberia; - - // Referencia al sistema de fluidos - private osSistemaFluidos _sistemaFluidos; - - // Propiedades visuales - [ObservableProperty] - [property: Category("Configuración")] - [property: Description("Diámetro de la tubería en metros")] - [property: Name("Diámetro")] - private float diametro = 0.05f; - - partial void OnDiametroChanged(float value) - { - // Actualizar geometría si la tubería ya existe - if (_tuberia != null) - { - // No es posible cambiar el diámetro directamente, recrear la tubería - ReconstruirTuberia(); - } - } - - [ObservableProperty] - [property: Category("Apariencia")] - [property: Description("Diámetro interno para visualización del fluido")] - [property: Name("Diámetro Interno")] - private float diametroInterno; - - [ObservableProperty] - [property: Category("Apariencia")] - [property: Description("Color de la tubería")] - [property: Name("Color Tubería")] - private System.Windows.Media.Color color = System.Windows.Media.Colors.Gray; - - [ObservableProperty] - [property: Category("Apariencia")] - [property: Description("Color del fluido")] - [property: Name("Color Fluido")] - private System.Windows.Media.Color colorFluido = System.Windows.Media.Colors.CornflowerBlue; - - [ObservableProperty] - [property: Category("Información")] - [property: Description("Datos del path para dibujar la tubería")] - [property: Name("Path Data")] - private string pathData; - - [ObservableProperty] - [property: Category("Simulación")] - [property: Description("Densidad del fluido (0-1)")] - [property: Name("Densidad Fluido")] - private double densidadFluido = 0.7; - - // Lista de puntos que forman la tubería - private List _puntos = new List(); - - // Nombre de la clase para identificación - public static string NombreClase() - { - return "Tubería de Fluido"; - } - - private string nombre = NombreClase(); - - [property: Category("Identificación")] - [property: Description("Nombre identificativo del objeto")] - [property: Name("Nombre")] - public override string Nombre - { - get => nombre; - set => SetProperty(ref nombre, value); - } - - /// - /// Agrega un punto a la tubería - /// - public void AgregarPunto(float x, float y) - { - _puntos.Add(new Vector2(x, y)); - ActualizarPathData(); - - // Si la tubería ya está creada, agregar el punto a la simulación - if (_tuberia != null) - { - _tuberia.AgregarPunto(new Vector2(x, y)); - } - } - - /// - /// Actualiza la representación visual de la tubería - /// - private void ActualizarPathData() - { - if (_puntos.Count < 2) return; - - var converter = new MeterToPixelConverter(); - StringBuilder sb = new StringBuilder(); - - // Iniciar el path - sb.Append($"M {converter.Convert(_puntos[0].X, null, null, null)} {converter.Convert(_puntos[0].Y, null, null, null)} "); - - // Añadir los demás puntos - for (int i = 1; i < _puntos.Count; i++) - { - sb.Append($"L {converter.Convert(_puntos[i].X, null, null, null)} {converter.Convert(_puntos[i].Y, null, null, null)} "); - } - - PathData = sb.ToString(); - } - - /// - /// Reconstruye la tubería en la simulación - /// - private void ReconstruirTuberia() - { - if (_sistemaFluidos != null) - { - // Eliminar la tubería existente - if (_tuberia != null) - { - _sistemaFluidos.EliminarComponente(_tuberia); - } - - // Crear nueva tubería - _tuberia = _sistemaFluidos.CrearTuberia(Diametro); - - // Agregar todos los puntos existentes - foreach (var punto in _puntos) - { - _tuberia.AgregarPunto(punto); - } - } - } - - /// - /// Constructor - /// - public osTuberiaFluido() - { - DiametroInterno = Diametro * 0.8f; - } - - public override void OnMove(float LeftPixels, float TopPixels) - { - // Mover todos los puntos de la tubería - if (_puntos.Count > 0) - { - Vector2 delta = new Vector2(Left - CanvasGetLeftinMeter(), Top - CanvasGetTopinMeter()); - - for (int i = 0; i < _puntos.Count; i++) - { - _puntos[i] += delta; - } - - ActualizarPathData(); - ReconstruirTuberia(); - } - } - - public override void ucLoaded() - { - base.ucLoaded(); - - // Buscar el sistema de fluidos en la simulación - if (_mainViewModel?.ObjetosSimulables != null) - { - foreach (var obj in _mainViewModel.ObjetosSimulables) - { - if (obj is osSistemaFluidos sistemaFluidos) - { - _sistemaFluidos = sistemaFluidos; - break; - } - } - } - - // Si no hay puntos, agregar dos puntos iniciales - if (_puntos.Count == 0) - { - _puntos.Add(new Vector2(Left, Top)); - _puntos.Add(new Vector2(Left + Ancho, Top)); - ActualizarPathData(); - } - - // Crear la tubería en la simulación - ReconstruirTuberia(); - } - - public override void ucUnLoaded() - { - // Eliminar la tubería de la simulación - if (_sistemaFluidos != null && _tuberia != null) - { - _sistemaFluidos.EliminarComponente(_tuberia); - _tuberia = null; - } - } - - public override void UpdateControl(int elapsedMilliseconds) - { - // Actualizar la densidad del fluido basada en la simulación - if (_sistemaFluidos != null && _sistemaFluidos._simFluidos != null && _puntos.Count > 0) - { - // Calcular el centro aproximado de la tubería - Vector2 centro = Vector2.Zero; - foreach (var punto in _puntos) - { - centro += punto; - } - centro /= _puntos.Count; - - // Obtener densidad en esa posición - DensidadFluido = _sistemaFluidos._simFluidos.ObtenerDensidadEnPosicion(centro); - } - } - } -} diff --git a/ObjetosSim/ucValvulaFluido.xaml b/ObjetosSim/ucValvulaFluido.xaml deleted file mode 100644 index 43b7965..0000000 --- a/ObjetosSim/ucValvulaFluido.xaml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ObjetosSim/ucValvulaFluido.xaml.cs b/ObjetosSim/ucValvulaFluido.xaml.cs deleted file mode 100644 index 7b6148f..0000000 --- a/ObjetosSim/ucValvulaFluido.xaml.cs +++ /dev/null @@ -1,217 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; -using CtrEditor.FuncionesBase; -using CtrEditor.Simulacion.Fluids.Components; -using nkast.Aether.Physics2D.Common; -using CommunityToolkit.Mvvm.ComponentModel; -using Color = System.Windows.Media.Color; -using System.ComponentModel; -using LibS7Adv; - -namespace CtrEditor.ObjetosSim -{ - /// - /// Lógica para ucValvulaFluido.xaml - /// - public partial class ucValvulaFluido : UserControl, IDataContainer - { - public osBase? Datos { get; set; } - public int zIndex_fromFrames { get; set; } - - public ucValvulaFluido() - { - InitializeComponent(); - this.Loaded += OnLoaded; - this.Unloaded += OnUnloaded; - } - - private void OnLoaded(object sender, RoutedEventArgs e) - { - Datos?.ucLoaded(); - } - - private void OnUnloaded(object sender, RoutedEventArgs e) - { - Datos?.ucUnLoaded(); - } - - public void Highlight(bool State) { } - - public ZIndexEnum ZIndex_Base() - { - return ZIndexEnum.Estaticos; - } - } - - /// - /// ViewModel para la válvula de fluidos - /// - public partial class osValvulaFluido : osBase, IosBase - { - // Válvula en la simulación de fluidos - private Valvula _valvula; - - // Referencia al sistema de fluidos - private osSistemaFluidos _sistemaFluidos; - - // Propiedades dimensionales - [ObservableProperty] - [property: Category("Configuración")] - [property: Description("Ancho de la válvula en metros")] - [property: Name("Ancho")] - private float ancho = 0.1f; - - [ObservableProperty] - [property: Category("Configuración")] - [property: Description("Alto de la válvula en metros")] - [property: Name("Alto")] - private float alto = 0.06f; - - [ObservableProperty] - [property: Category("Configuración")] - [property: Description("Diámetro de la tubería conectada")] - [property: Name("Diámetro Tubería")] - private float diametroTuberia = 0.05f; - - // Propiedades visuales - [ObservableProperty] - [property: Category("Apariencia")] - [property: Description("Color de la válvula")] - [property: Name("Color")] - private Color color = Colors.Silver; - - [ObservableProperty] - [property: Category("Apariencia")] - [property: Description("Color del indicador de apertura")] - [property: Name("Color Indicador")] - private Color colorIndicador = Colors.CornflowerBlue; - - // Propiedades de funcionamiento - [ObservableProperty] - [property: Category("Simulación")] - [property: Description("Apertura de la válvula (0-1)")] - [property: Name("Apertura")] - private float apertura = 1.0f; - - partial void OnAperturaChanged(float value) - { - Apertura = Math.Clamp(value, 0, 1); - - // Actualizar la válvula en la simulación - if (_valvula != null) - { - _valvula.Apertura = Apertura; - } - } - - // Tag PLC para la válvula - [ObservableProperty] - [property: Category("Enlace PLC")] - [property: Description("Tag PLC para lectura/escritura de apertura (0-100%)")] - [property: Name("Tag Apertura")] - private string tagApertura; - - // Propiedades calculadas para visualización - public float AperturaVisual => Ancho * 0.8f * Apertura; - public float GrosorIndicador => Alto * 0.2f; - public float ValorApertura => Apertura; - - // Propiedades para posicionamiento de elementos - public float OffsetXRectangulo => -Ancho / 2; - public float OffsetYRectangulo => -Alto / 2; - public float OffsetXIndicador => -Ancho * 0.4f; - public float OffsetYIndicador => -GrosorIndicador / 2; - public float OffsetXTexto => -Ancho * 0.15f; - public float OffsetYTexto => Alto * 0.1f; - - // Nombre de la clase para identificación - public static string NombreClase() - { - return "Válvula de Fluido"; - } - - private string nombre = NombreClase(); - - [property: Category("Identificación")] - [property: Description("Nombre identificativo del objeto")] - [property: Name("Nombre")] - public override string Nombre - { - get => nombre; - set => SetProperty(ref nombre, value); - } - - /// - /// Constructor - /// - public osValvulaFluido() - { - // Constructor - } - - public override void OnMove(float LeftPixels, float TopPixels) - { - if (_valvula != null) - { - // Actualizar posición de la válvula en la simulación - _valvula.ActualizarPosicion(new Vector2(Left, Top)); - } - } - - public override void OnRotate(float Delta_Angle) - { - // La rotación visual ya está manejada por el XAML - // No necesita actualizaciones adicionales en la simulación - } - - public override void ucLoaded() - { - base.ucLoaded(); - - // Buscar el sistema de fluidos en la simulación - if (_mainViewModel?.ObjetosSimulables != null) - { - foreach (var obj in _mainViewModel.ObjetosSimulables) - { - if (obj is osSistemaFluidos sistemaFluidos) - { - _sistemaFluidos = sistemaFluidos; - break; - } - } - } - - // Crear la válvula en la simulación - if (_sistemaFluidos != null) - { - _valvula = _sistemaFluidos.CrearValvula( - new Vector2(Left, Top), - DiametroTuberia, - Apertura - ); - } - } - - public override void ucUnLoaded() - { - // Eliminar la válvula de la simulación - if (_sistemaFluidos != null && _valvula != null) - { - _sistemaFluidos.EliminarComponente(_valvula); - _valvula = null; - } - } - - public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds) - { - // Manejar la comunicación con PLC - if (!string.IsNullOrEmpty(TagApertura)) - { - float aperturaPlc = LeerWordTagScaled(TagApertura) / 100.0f; - Apertura = Math.Clamp(aperturaPlc, 0, 1); - } - } - } -} diff --git a/Simulacion/Aether.cs b/Simulacion/Aether.cs deleted file mode 100644 index b450eca..0000000 --- a/Simulacion/Aether.cs +++ /dev/null @@ -1,1054 +0,0 @@ -using System.Windows.Controls; -using System.Windows.Media; -using System.Windows.Shapes; -using System.Windows; -using System.Diagnostics; -using nkast.Aether.Physics2D.Dynamics; -using nkast.Aether.Physics2D.Common; -using nkast.Aether.Physics2D.Collision.Shapes; -using nkast.Aether.Physics2D.Dynamics.Contacts; -using nkast.Aether.Physics2D.Dynamics.Joints; -using System; -using System.Collections.Generic; - -namespace CtrEditor.Simulacion -{ - public class simBase - { - public Body Body { get; protected set; } - - public World _world; - public void RemoverBody() - { - if (Body != null && _world.BodyList.Count > 0 && _world.BodyList.Contains(Body)) - { - _world.Remove(Body); - } - } - - public static float Min(float Value, float Min = 0.01f) - { - return Value < Min ? Min : Value; - } - - public static float GradosARadianes(float grados) - { - return (float)(grados * (Math.PI / 180)); - } - - public static float RadianesAGrados(float radianes) - { - return (float)(radianes * (180 / Math.PI)); - } - - public void SetPosition(float x, float y) - { - Body.SetTransform(new Vector2(x, y), Body.Rotation); - } - - public void SetPosition(Vector2 centro) - { - try - { - Body.SetTransform(centro, Body.Rotation); - } - catch - { - } - } - } - - public class simCurve : simBase - { - private float _innerRadius; - private float _outerRadius; - private float _startAngle; - private float _endAngle; - public float Speed { get; set; } // Velocidad para efectos de cinta transportadora - - private List _deferredActions; - public simCurve(World world, List deferredActions, float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 position) - { - _world = world; - _deferredActions = deferredActions; - _innerRadius = innerRadius; - _outerRadius = outerRadius; - _startAngle = GradosARadianes(startAngle); - _endAngle = GradosARadianes(endAngle); - Create(position); - } - - public void Create(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 position) - { - if (_world == null) - return; - _innerRadius = innerRadius; - _outerRadius = outerRadius; - _startAngle = GradosARadianes(startAngle); - _endAngle = GradosARadianes(endAngle); - Create(position); - } - - public void Create(Vector2 position) - { - RemoverBody(); - // Crear triangulación de la curva - List triangles = CreateTriangulatedCurve(_innerRadius, _outerRadius, _startAngle, _endAngle); - Body = _world.CreateBody(); - foreach (var triangle in triangles) - { - var shape = new PolygonShape(triangle, 1f); - var fixture = Body.CreateFixture(shape); - fixture.IsSensor = true; - } - - Body.Position = position; - Body.BodyType = BodyType.Static; - Body.Tag = this; - } - - public void SetSpeed(float speed) - { - Speed = speed; - } - - private List CreateCurveVertices(float innerRadius, float outerRadius, float startAngle, float endAngle) - { - List verticesList = new List(); - int segments = 32; - float angleStep = (endAngle - startAngle) / segments; - Vertices innerVertices = new Vertices(); - Vertices outerVertices = new Vertices(); - for (int i = 0; i <= segments; i++) - { - float angle = startAngle + i * angleStep; - innerVertices.Add(new Vector2(innerRadius * (float)Math.Cos(angle), innerRadius * (float)Math.Sin(angle))); - outerVertices.Add(new Vector2(outerRadius * (float)Math.Cos(angle), outerRadius * (float)Math.Sin(angle))); - } - - outerVertices.Reverse(); - innerVertices.AddRange(outerVertices); - verticesList.Add(innerVertices); - return verticesList; - } - - private float CalculateAngleOverlap(Fixture bottle) - { - // Get bottle position relative to curve center - Vector2 centerToBottle = bottle.Body.Position - Body.Position; - // Calculate angle of bottle relative to curve center (in radians) - float bottleAngle = (float)Math.Atan2(centerToBottle.Y, centerToBottle.X); - - // Normalize all angles to the same range [0, 2π] - float normalizedBottleAngle = bottleAngle < 0 ? bottleAngle + 2 * (float)Math.PI : bottleAngle; - float normalizedStartAngle = _startAngle < 0 ? _startAngle + 2 * (float)Math.PI : _startAngle; - float normalizedEndAngle = _endAngle < 0 ? _endAngle + 2 * (float)Math.PI : _endAngle; - - // Handle case where the arc crosses the 0/2π boundary - if (normalizedStartAngle > normalizedEndAngle) - { - // Arc crosses 0/2π boundary - check if bottle is in either part of the arc - if (!(normalizedBottleAngle >= normalizedStartAngle || normalizedBottleAngle <= normalizedEndAngle)) - return 0; - } - else - { - // Normal case - arc doesn't cross boundary - if (normalizedBottleAngle < normalizedStartAngle || normalizedBottleAngle > normalizedEndAngle) - return 0; - } - // Calculate distance from center - float distanceToCenter = centerToBottle.Length(); - // If bottle is outside radius range, return 0 - if (distanceToCenter < _innerRadius || distanceToCenter > _outerRadius) - return 0; - // Calculate how far the bottle is from the edges using normalized angles - float angleFromStart, angleToEnd, totalAngle; - - if (normalizedStartAngle > normalizedEndAngle) - { - // Arc crosses 0/2π boundary - totalAngle = (2 * (float)Math.PI - normalizedStartAngle) + normalizedEndAngle; - if (normalizedBottleAngle >= normalizedStartAngle) - { - angleFromStart = normalizedBottleAngle - normalizedStartAngle; - angleToEnd = (2 * (float)Math.PI - normalizedBottleAngle) + normalizedEndAngle; - } - else - { - angleFromStart = (2 * (float)Math.PI - normalizedStartAngle) + normalizedBottleAngle; - angleToEnd = normalizedEndAngle - normalizedBottleAngle; - } - } - else - { - // Normal case - angleFromStart = normalizedBottleAngle - normalizedStartAngle; - angleToEnd = normalizedEndAngle - normalizedBottleAngle; - totalAngle = normalizedEndAngle - normalizedStartAngle; - } - - // Use the minimum distance to either edge to calculate overlap - float minAngleDistance = Math.Min(angleFromStart, angleToEnd); - // Calculate overlap percentage based on angle proximity to edges - float overlapPercentage = Math.Min(minAngleDistance / (totalAngle * 0.1f), 1.0f); - return overlapPercentage; - } - - public void ApplyCurveEffect(Fixture bottle) - { - Vector2 centerToBottle = bottle.Body.Position - Body.Position; - float distanceToCenter = centerToBottle.Length(); - if (distanceToCenter >= _innerRadius && distanceToCenter <= _outerRadius) - { - // Calculate overlap percentage - float overlapPercentage = CalculateAngleOverlap(bottle); - // Only apply effect if there's overlap - if (overlapPercentage > 0) - { - // Calculate the tangential velocity - float speedMetersPerSecond = Speed / 60.0f; - // Vector tangent (perpendicular to radius) - Vector2 tangent = new Vector2(-centerToBottle.Y, centerToBottle.X); - tangent.Normalize(); - // Adjust tangent direction based on speed sign - if (speedMetersPerSecond < 0) - tangent = -tangent; - // Current velocity magnitude - float currentSpeed = bottle.Body.LinearVelocity.Length(); - // Desired conveyor speed - float conveyorSpeed = Math.Abs(speedMetersPerSecond); - // Use the larger of the two speeds as base speed - float targetSpeed = Math.Max(currentSpeed, conveyorSpeed); - // Lerp between current direction and curve direction - Vector2 currentDir = bottle.Body.LinearVelocity; - if (currentDir.LengthSquared() > 0) - currentDir.Normalize(); - else - currentDir = tangent; - // Interpolate between current direction and curve direction - Vector2 newDirection = currentDir * (1 - overlapPercentage) + tangent * overlapPercentage; - newDirection.Normalize(); - // Apply new velocity with combined speed - bottle.Body.LinearVelocity = newDirection * targetSpeed; - } - } - } - - private const float SegmentationFactor = 32f / 3f; - private const int MinSegments = 8; - private const int MaxSegments = 64; - private List CreateTriangulatedCurve(float innerRadius, float outerRadius, float startAngle, float endAngle) - { - List triangles = new List(); - float arcLength = (endAngle - startAngle) * ((innerRadius + outerRadius) / 2f); - // Calcular número de segmentos basado en el tamaño del arco - int segments = (int)(arcLength * SegmentationFactor); - // Aplicar límites para asegurar calidad y eficiencia - segments = Math.Max(MinSegments, Math.Min(segments, MaxSegments)); - float angleStep = (endAngle - startAngle) / segments; - // Generar vértices para los arcos interior y exterior - Vector2[] innerPoints = new Vector2[segments + 1]; - Vector2[] outerPoints = new Vector2[segments + 1]; - for (int i = 0; i <= segments; i++) - { - float angle = startAngle + i * angleStep; - float cosAngle = (float)Math.Cos(angle); - float sinAngle = (float)Math.Sin(angle); - innerPoints[i] = new Vector2(innerRadius * cosAngle, innerRadius * sinAngle); - outerPoints[i] = new Vector2(outerRadius * cosAngle, outerRadius * sinAngle); - } - - // Crear triángulos - for (int i = 0; i < segments; i++) - { - // Primer triángulo (superior) del sector - Vertices upperTriangle = new Vertices(3); - upperTriangle.Add(innerPoints[i]); - upperTriangle.Add(outerPoints[i]); - upperTriangle.Add(outerPoints[i + 1]); - triangles.Add(upperTriangle); - // Segundo triángulo (inferior) del sector - Vertices lowerTriangle = new Vertices(3); - lowerTriangle.Add(innerPoints[i]); - lowerTriangle.Add(outerPoints[i + 1]); - lowerTriangle.Add(innerPoints[i + 1]); - triangles.Add(lowerTriangle); - } - - return triangles; - } - } - - public class simDescarte : simBase - { - private float _radius; - private List _deferredActions; - public simDescarte(World world, List deferredActions, float diameter, Vector2 position) - { - _world = world; - _deferredActions = deferredActions; - _radius = diameter / 2; - Create(position); - } - - public void SetDiameter(float diameter) - { - if (diameter <= 0) - diameter = 0.01f; - _radius = diameter / 2; - Create(Body.Position); // Recrear el círculo con el nuevo tamaño - } - - public void Create(Vector2 position) - { - RemoverBody(); - Body = _world.CreateCircle(_radius, 1f, position); - Body.FixtureList[0].IsSensor = true; - Body.BodyType = BodyType.Static; - Body.Tag = this; // Importante para la identificación durante la colisión - } - } - - public class simTransporte : simBase - { - public float Speed { get; set; } // Velocidad para efectos de cinta transportadora - public float Friction { get; set; } // Friccion para efectos de cinta transportadora - public float DistanceGuide2Guide { get; set; } - public bool isBrake { get; set; } - - public bool TransportWithGuides = false; - private List _deferredActions; - public float Width { get; set; } - public float Height { get; set; } - - public simTransporte(World world, List deferredActions, float width, float height, Vector2 position, float angle = 0) - { - _world = world; - _deferredActions = deferredActions; - Create(width, height, position, angle); - } - - public float Angle - { - get - { - return RadianesAGrados(Body.Rotation); - } - - set - { - Body.Rotation = GradosARadianes(value); - } - } - - public new void SetPosition(float x, float y) - { - Body.Position = new Vector2(x, y); - } - - public void SetSpeed(float speed) - { - Speed = speed; - } - - public void SetDimensions(float width, float height) - { - Body.Remove(Body.FixtureList[0]); - var newShape = new PolygonShape(PolygonTools.CreateRectangle(width / 2, height / 2), 1f); - Body.CreateFixture(newShape); - Width = width; - Height = height; - } - - public void Create(float width, float height, Vector2 position, float angle = 0) - { - RemoverBody(); - Width = width; - Height = height; - Friction = 0.1f; - Body = _world.CreateRectangle(width, height, 1f, position); - Body.FixtureList[0].IsSensor = true; - Body.BodyType = BodyType.Static; - Body.Rotation = GradosARadianes(angle); - Body.Tag = this; // Importante para la identificación durante la colisión - } - } - - public class simBarrera : simBase - { - public float Distancia; - public int LuzCortada; - public bool LuzCortadaNeck; - public bool DetectNeck; - public List ListSimBotellaContact; - float _height; - private List _deferredActions; - public simBarrera(World world, List deferredActions, float width, float height, Vector2 position, float angle = 0, bool detectectNeck = false) - { - _world = world; - _height = Min(height); - DetectNeck = detectectNeck; - _deferredActions = deferredActions; - ListSimBotellaContact = new List(); - Create(Min(width), _height, position, angle); - } - - public float Angle - { - get - { - return RadianesAGrados(Body.Rotation); - } - - set - { - Body.Rotation = GradosARadianes(value); - } - } - - public new void SetPosition(float x, float y) - { - Body.Position = new Vector2(x, y); - } - - public void SetDimensions(float width, float height) - { - Body.Remove(Body.FixtureList[0]); - var newShape = new PolygonShape(PolygonTools.CreateRectangle(Min(width) / 2, Min(height) / 2), 1f); - Body.CreateFixture(newShape); - } - - public void Create(float width, float height, Vector2 position, float angle = 0, bool detectectNeck = false) - { - RemoverBody(); - _height = Min(height); - DetectNeck = detectectNeck; - Body = _world.CreateRectangle(Min(width), _height, 1f, position); - Body.FixtureList[0].IsSensor = true; - Body.BodyType = BodyType.Static; - Body.Rotation = GradosARadianes(angle); - Body.Tag = this; // Importante para la identificación durante la colisión - LuzCortada = 0; - } - - public void CheckIfNecksIsTouching() - { - if (LuzCortada == 0) - return; - foreach (var botella in ListSimBotellaContact) - { - // Obtener el centro de la barrera - Vector2 sensorCenter = Body.Position; - // Calcular el vector de la línea horizontal centrada de la barrera - float halfHeight = _height / 2; - float cos = (float)Math.Cos(Body.Rotation); - float sin = (float)Math.Sin(Body.Rotation); - // Calcular los puntos inicial y final de la línea horizontal centrada y rotada - Vector2 lineStart = sensorCenter + new Vector2(-halfHeight * cos, halfHeight * sin); - Vector2 lineEnd = sensorCenter + new Vector2(halfHeight * cos, -halfHeight * sin); - // Proyectar el centro de la botella sobre la línea horizontal - Vector2 fixtureCenter = botella.Body.Position; - Vector2 closestPoint = ProjectPointOntoLine(fixtureCenter, lineStart, lineEnd); - // Calcular la distancia entre el punto más cercano y el centro del cuello de la botella - float distance; - Vector2.Distance(ref closestPoint, ref fixtureCenter, out distance); - Distancia = distance; - if (distance <= botella._neckRadius) - { - LuzCortadaNeck = true; - return; - } - } - - LuzCortadaNeck = false; - } - - private Vector2 ProjectPointOntoLine(Vector2 point, Vector2 lineStart, Vector2 lineEnd) - { - Vector2 lineDirection = lineEnd - lineStart; - lineDirection.Normalize(); - Vector2 pointToLineStart = point - lineStart; - float projectionLength; - Vector2.Dot(ref pointToLineStart, ref lineDirection, out projectionLength); - return lineStart + projectionLength * lineDirection; - } - } - - public class simGuia : simBase - { - private List _deferredActions; - public simGuia(World world, List deferredActions, Vector2 start, Vector2 end) - { - _world = world; - _deferredActions = deferredActions; - Create(start, end); - } - - public void Create(Vector2 start, Vector2 end) - { - RemoverBody(); - Body = _world.CreateEdge(start, end); - Body.BodyType = BodyType.Static; - Body.Tag = this; // Importante para la identificación durante la colisión - } - - public void UpdateVertices(Vector2 newStart, Vector2 newEnd) - { - Create(newStart, newEnd); // Recrear la línea con nuevos vértices - } - } - - public class simBotella : simBase - { - public float Radius; - private float _mass; - public bool Descartar = false; - public int isOnTransports; - public List ListOnTransports; - public bool isRestricted; - public bool isNoMoreRestricted; - public float OriginalMass; - public simTransporte ConveyorRestrictedTo; - public Fixture axisRestrictedBy; - public float OverlapPercentage; - public float _neckRadius; - PrismaticJoint _activeJoint; - private List _deferredActions; - public simBotella(World world, List deferredActions, float diameter, Vector2 position, float mass, float neckRadius = 0) - { - _world = world; - ListOnTransports = new List(); - _deferredActions = deferredActions; - // Asegurar que el diámetro sea al menos 0.01m para evitar errores - diameter = Min(diameter, 0.01f); - Radius = diameter / 2; - _mass = mass; - _activeJoint = null; - if (neckRadius <= 0) - neckRadius = diameter / 4; - _neckRadius = neckRadius; - Create(position); - } - - public float CenterX - { - get - { - return Body.Position.X; - } - - set - { - } - } - - public float CenterY - { - get - { - return Body.Position.Y; - } - - set - { - } - } - - public Vector2 Center - { - get - { - return Body.Position; - } - } - - public float Mass - { - get - { - if (_mass <= 0) - _mass = 1; - return _mass; - } - - set - { - _mass = value; - } - } - - private void Create(Vector2 position) - { - RemoverBody(); - isOnTransports = 0; - _activeJoint = null; - Body = _world.CreateCircle(Radius, 1f, position); - Body.BodyType = BodyType.Dynamic; - // Restablecer manejador de eventos de colisión - Body.OnCollision += HandleCollision; - Body.OnSeparation += HandleOnSeparation; - Body.Tag = this; // Importante para la identificación durante la colisión - // Configurar la fricción - //Body.SetFriction(0.2f); - // Configurar amortiguamiento - Body.LinearDamping = 5f; // Ajustar para controlar la reducción de la velocidad lineal - Body.AngularDamping = 21f; // Ajustar para controlar la reducción de la velocidad angular - //Body.SetRestitution(0f); // Baja restitución para menos rebote - Body.SleepingAllowed = false; - Body.IsBullet = true; - } - - public void SetDiameter(float diameter) - { - // Asegurar que el diámetro sea al menos 0.01m para evitar errores - diameter = Min(diameter, 0.01f); - Radius = diameter / 2; - Create(Body.Position); // Recrear el círculo con el nuevo tamaño - } - - public void SetMass(float mass) - { - Mass = mass; - } - - private bool HandleCollision(Fixture fixtureA, Fixture fixtureB, Contact contact) - { - if (fixtureB.Body.Tag is simBarrera Sensor) - { - Sensor.LuzCortada += 1; - Sensor.ListSimBotellaContact.Add(this); - return true; - } - else if (fixtureB.Body.Tag is simCurve curve) - { - isOnTransports += 1; - ListOnTransports.Add(curve); - return true; // No aplicar respuestas físicas - } - else if (fixtureB.Body.Tag is simDescarte) - { - Descartar = true; - return true; - } - else if (fixtureB.Body.Tag is simTransporte) - { - simTransporte conveyor = fixtureB.Body.Tag as simTransporte; - isOnTransports += 1; - ListOnTransports.Add(conveyor); - // Aplicar el efecto del transportador usando el porcentaje calculado - if (conveyor.TransportWithGuides && conveyor.isBrake) - { - ConveyorRestrictedTo = conveyor; - axisRestrictedBy = fixtureB; - OriginalMass = Body.Mass; - isRestricted = true; - isNoMoreRestricted = false; - } - - return true; // No aplicar respuestas físicas - } - - return true; // No aplicar respuestas físicas - } - - private float CalculateOverlapPercentage(simTransporte conveyor) - { - CircleShape circleShape = Body.FixtureList[0].Shape as CircleShape; - PolygonShape polygonShape = conveyor.Body.FixtureList[0].Shape as PolygonShape; - // Obtener centro y radio del círculo - Vector2 centroCirculo = Body.Position; - float radio = circleShape.Radius; - // Obtener los vértices del polígono (rectángulo) - Vector2[] vertices = new Vector2[polygonShape.Vertices.Count]; - float cos = (float)Math.Cos(conveyor.Body.Rotation); - float sin = (float)Math.Sin(conveyor.Body.Rotation); - for (int i = 0; i < polygonShape.Vertices.Count; i++) - { - Vector2 vertex = polygonShape.Vertices[i]; - float rotatedX = vertex.X * cos - vertex.Y * sin + conveyor.Body.Position.X; - float rotatedY = vertex.X * sin + vertex.Y * cos + conveyor.Body.Position.Y; - vertices[i] = new Vector2(rotatedX, rotatedY); - } - - // Calcular el porcentaje de la superficie compartida - return InterseccionCirculoRectangulo.CalcularSuperficieCompartida(vertices, centroCirculo, radio); - } - - private void HandleOnSeparation(Fixture sender, Fixture fixtureB, Contact contact) - { - if (isOnTransports > 0 && fixtureB.Body.Tag is simTransporte) - { - if (fixtureB.Body.Tag is simTransporte transport) - ListOnTransports.Remove(transport); - isOnTransports -= 1; - } - - if (isOnTransports > 0 && fixtureB.Body.Tag is simCurve) - { - if (fixtureB.Body.Tag is simCurve transport) - ListOnTransports.Remove(transport); - isOnTransports -= 1; - } - - if (isRestricted && fixtureB == axisRestrictedBy) - { - isRestricted = false; - isNoMoreRestricted = true; - } - - if (fixtureB.Body.Tag is simBarrera Sensor) - { - Sensor.LuzCortada -= 1; - Sensor.ListSimBotellaContact.Remove(this); - } - } - - /// - /// Aplica la fuerza de traccion a la botellas segun los - /// transportes sobre los que se encuentra - /// - /// - public void ApplyConveyorSpeed(float deltaTime_s) - { - foreach (var transporte in ListOnTransports) - { - if (transporte is simTransporte conveyorRect) - ApplyConveyorEffect(deltaTime_s, conveyorRect); - // if (ApplyConveyorEffect(deltaTime_s, conveyorRect)) - // break; - if (transporte is simCurve conveyorCurve) - conveyorCurve.ApplyCurveEffect(Body.FixtureList[0]); - } - } - - private bool ApplyConveyorEffect(float deltaTime_s, simTransporte conveyor) - { - // Calcular el porcentaje de superficie sobrepuesta - float overlapPercentage = CalculateOverlapedArea(this, conveyor); // CalculateOverlapPercentage(conveyor); - OverlapPercentage = overlapPercentage; - // Calcular la velocidad deseada del transporte - float speedMetersPerSecond = conveyor.Speed / 60.0f; - Vector2 desiredVelocity = new Vector2((float)Math.Cos(conveyor.Body.Rotation), (float)Math.Sin(conveyor.Body.Rotation)) * speedMetersPerSecond; - // Obtener la velocidad actual de la botella - Vector2 currentVelocity = Body.LinearVelocity; - // Calcular la diferencia de velocidad deseada - Vector2 velocityDifference = desiredVelocity - currentVelocity; - // Calcular la fuerza de fricción necesaria - 0 : ya esta en velocidad - 1 : esta detenido - float proporcionalVelocityNeeded = 1 - CalculateProportion(currentVelocity, desiredVelocity); - float frictionCoefficient; - switch (proporcionalVelocityNeeded) - { - case > 0.3f: - frictionCoefficient = conveyor.Friction * overlapPercentage; - break; - default: - frictionCoefficient = proporcionalVelocityNeeded * overlapPercentage; - break; - } - - if (isRestricted && conveyor == ConveyorRestrictedTo && overlapPercentage > 0.5f) - { - Body.LinearVelocity = desiredVelocity; - return true; - } - - // Aplicar la fuerza de fricción en función del porcentaje de superficie sobrepuesta - Body.LinearVelocity += frictionCoefficient * desiredVelocity; - //Body.ApplyForce(frictionForce * overlapPercentage); - return false; - } - - public float CalculateOverlapedArea(simBotella botella, simTransporte conveyor) - { - //float areaBotella = (float) ((botella.Radius * botella.Radius * Math.PI) / (Math.PI / 4)); // Math.PI/4 porque es un cuadrado en vez de un circulo - //float area = OverlapedArea.CalculateOverlapedArea(botella.Body, conveyor.Body); - //if (areaBotella == 0) return 0; - //return area/areaBotella; - return OverlapedAreaFast.CalculateOverlapedArea(botella, conveyor); - } - - public static float CalculateProportion(Vector2 currentVelocity, Vector2 desiredVelocity) - { - // Calcular la proyección escalar de v2 sobre v1 - float dotProduct; - Vector2.Dot(ref desiredVelocity, ref currentVelocity, out dotProduct); - float magnitudeV1Squared = desiredVelocity.LengthSquared(); - // Si la magnitud de v1 es 0, la proporción no está definida - if (magnitudeV1Squared == 0) - { - return 0; - } - - // Calcular la proporción - float proportion = dotProduct / magnitudeV1Squared; - return proportion; - } - - public void CenterFixtureOnConveyor() - { - if (!isRestricted || ConveyorRestrictedTo == null) - return; - // Obtener el centro del conveyor - Vector2 conveyorCenter = ConveyorRestrictedTo.Body.Position; - // Calcular el vector de la línea horizontal centrada de conveyor - float halfDistance = ConveyorRestrictedTo.DistanceGuide2Guide / 2; - float cos = (float)Math.Cos(ConveyorRestrictedTo.Body.Rotation); - float sin = (float)Math.Sin(ConveyorRestrictedTo.Body.Rotation); - Vector2 offset = new Vector2(halfDistance * cos, halfDistance * sin); - // Línea horizontal centrada de conveyor en el espacio del mundo - Vector2 lineStart = conveyorCenter - offset; - Vector2 lineEnd = conveyorCenter + offset; - // Proyectar el centro de fixtureA sobre la línea horizontal - Vector2 fixtureCenter = Body.Position; - Vector2 closestPoint = ProjectPointOntoLine(fixtureCenter, lineStart, lineEnd); - // Mover fixtureA al punto más cercano en la línea horizontal - Body.Position = closestPoint; - } - - private Vector2 ProjectPointOntoLine(Vector2 point, Vector2 lineStart, Vector2 lineEnd) - { - Vector2 lineDirection = lineEnd - lineStart; - lineDirection.Normalize(); - Vector2 pointToLineStart = point - lineStart; - Vector2.Dot(ref pointToLineStart, ref lineDirection, out float projectionLength); - return lineStart + projectionLength * lineDirection; - } - - public bool IsOnAnyTransport() - { - return isOnTransports > 0; - } - } - - public class SimulationManagerFP - { - private World world; - private Canvas simulationCanvas; - public List Cuerpos; - public List _deferredActions; - SolverIterations Iterations; - private Stopwatch stopwatch; - private double stopwatch_last; - public Canvas DebugCanvas { get => simulationCanvas; set => simulationCanvas = value; } - - public SimulationManagerFP() - { - world = new World(new Vector2(0, 0)); // Vector2.Zero - SolverIterations Iterations = new SolverIterations(); - Iterations.PositionIterations = 1; - Iterations.VelocityIterations = 1; - Cuerpos = new List(); - _deferredActions = new List(); - stopwatch = new Stopwatch(); - stopwatch.Start(); - } - - public void Clear() - { - if (world.BodyList.Count > 0) - world.Clear(); - if (Cuerpos.Count > 0) - Cuerpos.Clear(); - } - - // ****************************************************************************************************************************************** - // ****************************************************************************************************************************************** - public void Start() - { - stopwatch.Start(); - stopwatch_last = stopwatch.Elapsed.TotalMilliseconds; - } - - public void Step() - { - // Detener el cronómetro y obtener el tiempo transcurrido en milisegundos - float elapsedMilliseconds = (float)(stopwatch.Elapsed.TotalMilliseconds - stopwatch_last); - stopwatch_last = stopwatch.Elapsed.TotalMilliseconds; - // Pasar el tiempo transcurrido al método Step - - world.Step(elapsedMilliseconds / 1000.0f); - - foreach (var cuerpo in Cuerpos) - { - if (cuerpo is simBotella botella) - { - botella.ApplyConveyorSpeed(elapsedMilliseconds / 1000); - if (botella.isRestricted) - { - botella.CenterFixtureOnConveyor(); - botella.Body.Inertia = 0; - botella.Body.Mass = 100; - } - else if (botella.isNoMoreRestricted) - { - botella.isNoMoreRestricted = false; - botella.Body.Mass = botella.OriginalMass; - } - } - else if (cuerpo is simBarrera barrera) - barrera.CheckIfNecksIsTouching(); - } - - // Procesa las acciones diferidas - foreach (var action in _deferredActions) - { - action(); - } - - _deferredActions.Clear(); - } - - public void Remove(simBase Objeto) - { - if (Objeto != null) - { - Objeto.RemoverBody(); - Cuerpos.Remove(Objeto); - } - } - - public simCurve AddCurve(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 position) - { - simCurve curva = new simCurve(world, _deferredActions, innerRadius, outerRadius, startAngle, endAngle, position); - Cuerpos.Add(curva); - return curva; - } - - public simBotella AddCircle(float diameter, Vector2 position, float mass) - { - simBotella circle = new simBotella(world, _deferredActions, diameter, position, mass); - Cuerpos.Add(circle); - return circle; - } - - public simTransporte AddRectangle(float width, float height, Vector2 position, float angle) - { - simTransporte rectangle = new simTransporte(world, _deferredActions, width, height, position, angle); - Cuerpos.Add(rectangle); - return rectangle; - } - - public simBarrera AddBarrera(float width, float height, Vector2 position, float angle, bool detectarCuello) - { - simBarrera rectangle = new simBarrera(world, _deferredActions, width, height, position, angle, detectarCuello); - Cuerpos.Add(rectangle); - return rectangle; - } - - public simGuia AddLine(Vector2 start, Vector2 end) - { - simGuia line = new simGuia(world, _deferredActions, start, end); - Cuerpos.Add(line); - return line; - } - - public simDescarte AddDescarte(float diameter, Vector2 position) - { - simDescarte descarte = new simDescarte(world, _deferredActions, diameter, position); - Cuerpos.Add(descarte); - return descarte; - } - - public void Debug_DrawInitialBodies() - { - Debug_ClearSimulationShapes(); - world.Step(0.01f); // Para actualizar la BodyList - foreach (Body body in world.BodyList) - { - foreach (Fixture fixture in body.FixtureList) - { - DrawShape(fixture); - } - } - } - - public void Debug_ClearSimulationShapes() - { - var simulationShapes = simulationCanvas.Children.OfType().Where(s => s.Tag as string == "Simulation").ToList(); - foreach (var shape in simulationShapes) - { - simulationCanvas.Children.Remove(shape); - } - } - - private void DrawShape(Fixture fixture) - { - System.Windows.Shapes.Shape shape; - switch (fixture.Shape.ShapeType) - { - case ShapeType.Circle: - shape = DrawCircle(fixture); - break; - case ShapeType.Polygon: - shape = DrawPolygon(fixture); - break; - case ShapeType.Edge: - shape = DrawEdge(fixture); - break; - default: - return; - } - - shape.Tag = "Simulation"; // Marcar para simulación - Canvas.SetZIndex(shape, 20); - simulationCanvas.Children.Add(shape); - } - - private float p(float x) - { - float c = PixelToMeter.Instance.calc.MetersToPixels(x); - return c; - } - - private System.Windows.Shapes.Shape DrawEdge(Fixture fixture) - { - EdgeShape edge = fixture.Shape as EdgeShape; - Line line = new Line - { - X1 = p(edge.Vertex1.X + fixture.Body.Position.X), // Aplicar escala y posición - Y1 = p(edge.Vertex1.Y + fixture.Body.Position.Y), - X2 = p(edge.Vertex2.X + fixture.Body.Position.X), - Y2 = p(edge.Vertex2.Y + fixture.Body.Position.Y), - Stroke = Brushes.Black, - StrokeThickness = 2 - }; - return line; - } - - private System.Windows.Shapes.Shape DrawCircle(Fixture fixture) - { - CircleShape circle = fixture.Shape as CircleShape; - Ellipse ellipse = new Ellipse - { - Width = p(circle.Radius * 2), // Escalado para visualización - Height = p(circle.Radius * 2), // Escalado para visualización - Stroke = Brushes.Black, - StrokeThickness = 2 - }; - Canvas.SetLeft(ellipse, p(fixture.Body.Position.X - circle.Radius)); - Canvas.SetTop(ellipse, p(fixture.Body.Position.Y - circle.Radius)); - return ellipse; - } - - private System.Windows.Shapes.Shape DrawPolygon(Fixture fixture) - { - Polygon polygon = new Polygon - { - Stroke = Brushes.Black, - StrokeThickness = 2 - }; - PolygonShape polyShape = fixture.Shape as PolygonShape; - float cos = (float)Math.Cos(fixture.Body.Rotation); - float sin = (float)Math.Sin(fixture.Body.Rotation); - foreach (Vector2 vertex in polyShape.Vertices) - { - float rotatedX = vertex.X * cos - vertex.Y * sin + fixture.Body.Position.X; - float rotatedY = vertex.X * sin + vertex.Y * cos + fixture.Body.Position.Y; - polygon.Points.Add(new Point(p(rotatedX), p(rotatedY))); - } - - return polygon; - } - } -} \ No newline at end of file diff --git a/Simulacion/BEPU.cs b/Simulacion/BEPU.cs new file mode 100644 index 0000000..3d6e86b --- /dev/null +++ b/Simulacion/BEPU.cs @@ -0,0 +1,2615 @@ +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; +using System.Windows; +using System.Diagnostics; +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Linq; +using BepuPhysics; +using BepuPhysics.Collidables; +using BepuPhysics.CollisionDetection; +using BepuPhysics.Constraints; +using BepuUtilities; +using BepuUtilities.Memory; +using CtrEditor.FuncionesBase; + +namespace CtrEditor.Simulacion +{ + public class simBase + { + public BodyHandle BodyHandle { get; protected set; } + public Simulation _simulation; + protected bool _bodyCreated = false; // Bandera para saber si hemos creado un cuerpo + + // Constantes para las posiciones Z de los objetos 3D + public const float zPos_Transporte = 0f; // Z de la parte baja + public const float zAltura_Transporte = 0.1f; // Altura del transporte sobre zPos + + public const float zPos_Guia = 0.05f; // Z de la parte baja + public const float zAltura_Guia = 0.20f; // Altura de la guía sobre zPos + public const float zAltura_Barrera = zAltura_Guia; // Altura de la barrera sobre zPos + public const float zPos_Barrera = zPos_Guia; // Z de la parte baja + public const float zPos_Descarte = 0.1f; // Z de la parte baja + + // Constantes para configuración + public const float zPos_Curve = zPos_Transporte; // Z de la parte baja de la curva + public const float zAltura_Curve = zAltura_Transporte; // Altura de la curva (triángulos planos) + + + public void RemoverBody() + { + // Solo intentar remover si realmente hemos creado un cuerpo antes + if (_bodyCreated && _simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + _simulation.Bodies.Remove(BodyHandle); + _bodyCreated = false; // Marcar como no creado después de remover + } + } + + public static float Min(float Value, float Min = 0.01f) + { + return Math.Max(Value, Min); + } + + public static float GradosARadianes(float grados) + { + return grados * (float)Math.PI / 180f; + } + + public static float RadianesAGrados(float radianes) + { + return radianes * 180f / (float)Math.PI; + } + + public void SetPosition(float x, float y, float z = 0) + { + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle); + bodyReference.Pose.Position = new Vector3(x, y, z); + } + } + + public void SetPosition(Vector2 position) + { + // Invertir Y para convertir de WPF (Y hacia abajo) a 3D (Y hacia arriba) + // Mantener la coordenada Z actual para preservar la altura del objeto + var currentPosition = GetPosition(); + SetPosition(position.X, -position.Y, currentPosition.Z); + } + + public void SetPosition(Vector3 position) + { + SetPosition(position.X, position.Y, position.Z); + } + + public Vector3 GetPosition() + { + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle); + return bodyReference.Pose.Position; + } + return Vector3.Zero; + } + + public void SetRotation(float angleZ) + { + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle); + // ✅ Convertir de grados a radianes para Quaternion.CreateFromAxisAngle + bodyReference.Pose.Orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, GradosARadianes(angleZ)); + } + } + + public float GetRotationZ() + { + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle); + // Extraer el ángulo Z del quaternion + var q = bodyReference.Pose.Orientation; + return (float)Math.Atan2(2.0 * (q.W * q.Z + q.X * q.Y), 1.0 - 2.0 * (q.Y * q.Y + q.Z * q.Z)); + } + return 0f; + } + } + + public class simTransporte : simBase + { + public float Speed { get; set; } // Velocidad para efectos de cinta transportadora (m/s) + public float Friction { get; set; } // Friccion para efectos de cinta transportadora + public float DistanceGuide2Guide { get; set; } + public bool isBrake { get; set; } + + public bool TransportWithGuides = false; + private List _deferredActions; + public float Width { get; set; } + public float Height { get; set; } + + public simTransporte(Simulation simulation, List deferredActions, float width, float height, Vector2 topLeft, float angle = 0) + { + _simulation = simulation; + _deferredActions = deferredActions; + Width = width; + Height = height; + + // Usar el nuevo método Create que maneja Top-Left correctamente + Create(width, height, topLeft, angle); + } + + public float Angle + { + get + { + return GetRotationZ(); + } + set + { + SetRotation(value); + } + } + + public new void SetPosition(float x, float y, float z = 0) + { + base.SetPosition(x, y, z); + } + + public void SetSpeed(float speed) + { + Speed = speed; + } + + /// + /// Configura la velocidad del transporte en metros por segundo + /// Valores positivos mueven en la dirección del transporte, negativos en dirección opuesta + /// + /// Velocidad en m/s (típicamente entre -5.0 y 5.0) + public void SetTransportSpeed(float speedMeterPerSecond) + { + Speed = speedMeterPerSecond; + } + + /// + /// Detiene completamente el transporte + /// + public void StopTransport() + { + Speed = 0f; + } + + /// + /// Invierte la dirección del transporte manteniendo la misma velocidad + /// + public void ReverseTransport() + { + Speed = -Speed; + } + + public void SetDimensions(float width, float height) + { + Width = width; + Height = height; + + // Actualizar la forma del cuerpo existente + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var box = new Box(width, height, zAltura_Transporte); + var shapeIndex = _simulation.Shapes.Add(box); + _simulation.Bodies.SetShape(BodyHandle, shapeIndex); + } + } + + public void Create(float width, float height, Vector2 topLeft, float angle = 0) + { + // Calcular el offset del centro desde Top-Left (sin rotación) + var offsetX = width / 2f; + var offsetY = height / 2f; + + // Rotar el offset alrededor del Top-Left usando el ángulo + // ✅ Convertir de grados a radianes antes de usar Math.Cos/Sin + var angleRadians = GradosARadianes(angle); + var cos = (float)Math.Cos(angleRadians); + var sin = (float)Math.Sin(angleRadians); + + var rotatedOffsetX = offsetX * cos - offsetY * sin; + var rotatedOffsetY = offsetX * sin + offsetY * cos; + + // Calcular nueva posición del centro manteniendo Top-Left fijo + var centerX = topLeft.X + rotatedOffsetX; + var centerY = topLeft.Y + rotatedOffsetY; + + // Convertir a 3D e invertir Y - Transportes en Z = Depth/2 para estar sobre el suelo + var center3D = new Vector3(centerX, -centerY, zAltura_Transporte / 2f + zPos_Transporte); + + Create(width, height, center3D, -angle); // ✅ Invertir ángulo cuando se invierte Y + } + + public void Create(float width, float height, Vector3 position, float angle = 0) + { + RemoverBody(); + + var box = new Box(width, height, zAltura_Transporte); + var shapeIndex = _simulation.Shapes.Add(box); + + var bodyDescription = BodyDescription.CreateKinematic( + new RigidPose(position, Quaternion.CreateFromAxisAngle(Vector3.UnitZ, GradosARadianes(angle))), + new CollidableDescription(shapeIndex, 0.1f), + new BodyActivityDescription(0.01f) + ); + + BodyHandle = _simulation.Bodies.Add(bodyDescription); + _bodyCreated = true; // Marcar que hemos creado un cuerpo + } + } + + public class simBarrera : simBase + { + public float Distancia; + public bool LuzCortada; + public bool LuzCortadaNeck; + public bool DetectNeck; + public List ListSimBotellaContact; + float _height; + private List _deferredActions; + public float Width { get; set; } + public float Height { get; set; } + + public simBarrera(Simulation simulation, List deferredActions, float width, float height, Vector2 topLeft, float angle = 0, bool detectectNeck = false) + { + _simulation = simulation; + _deferredActions = deferredActions; + Width = width; + Height = height; + _height = height; + DetectNeck = detectectNeck; + ListSimBotellaContact = new List(); + + // Usar el nuevo método Create que maneja Top-Left correctamente + Create(width, height, topLeft, angle, detectectNeck); + } + + public float Angle + { + get + { + return GetRotationZ(); + } + set + { + SetRotation(value); + } + } + + public new void SetPosition(float x, float y, float z = 0) + { + base.SetPosition(x, y, z); + } + + public void SetDimensions(float width, float height) + { + _height = height; + // Actualizar la forma del cuerpo existente + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var box = new Box(width, height, zAltura_Barrera); // Altura de 20cm para detectar botellas + var shapeIndex = _simulation.Shapes.Add(box); + _simulation.Bodies.SetShape(BodyHandle, shapeIndex); + } + } + + public void Create(float width, float height, Vector2 topLeft, float angle = 0, bool detectectNeck = false) + { + // Calcular el offset del centro desde Top-Left (sin rotación) + var offsetX = width / 2f; + var offsetY = height / 2f; + + // Rotar el offset alrededor del Top-Left usando el ángulo + // ✅ Convertir de grados a radianes antes de usar Math.Cos/Sin + var angleRadians = GradosARadianes(angle); + var cos = (float)Math.Cos(angleRadians); + var sin = (float)Math.Sin(angleRadians); + + var rotatedOffsetX = offsetX * cos - offsetY * sin; + var rotatedOffsetY = offsetX * sin + offsetY * cos; + + // Calcular nueva posición del centro manteniendo Top-Left fijo + var centerX = topLeft.X + rotatedOffsetX; + var centerY = topLeft.Y + rotatedOffsetY; + + // Convertir a 3D e invertir Y - Sensores centrados en z=0.1 (rango 0-0.2) para detectar botellas + var center3D = new Vector3(centerX, -centerY, zPos_Barrera + zAltura_Barrera / 2f); + Create(width, height, center3D, -angle, detectectNeck); // ✅ Invertir ángulo cuando se invierte Y + } + + public void Create(float width, float height, Vector3 position, float angle = 0, bool detectectNeck = false) + { + RemoverBody(); + + // Crear box con altura de 0.2m (desde z=0 hasta z=0.2) para funcionar como detector + var box = new Box(width, height, zAltura_Barrera); // Altura de 20cm para detectar botellas completas + var shapeIndex = _simulation.Shapes.Add(box); + + // Crear como SENSOR (Kinematic con speculative margin 0 para detección pura) + // Configurar actividad para NUNCA dormir - los sensores deben estar siempre activos + var activityDescription = new BodyActivityDescription(-1f); // -1 = nunca dormir + + var bodyDescription = BodyDescription.CreateKinematic( + new RigidPose(position, Quaternion.CreateFromAxisAngle(Vector3.UnitZ, GradosARadianes(angle))), + new CollidableDescription(shapeIndex, 0f), // Speculative margin 0 para sensores + activityDescription + ); + + BodyHandle = _simulation.Bodies.Add(bodyDescription); + _bodyCreated = true; // Marcar que hemos creado un cuerpo + } + } + + public class simGuia : simBase + { + private List _deferredActions; + + // Propiedades para acceder a las dimensiones del objeto WPF + public float GuideThickness { get; set; } // Espesor de la guía + + + + public simGuia(Simulation simulation, List deferredActions, float width, float height, Vector2 topLeft, float angle) + { + _simulation = simulation; + _deferredActions = deferredActions; + GuideThickness = height; + Create(width, height, topLeft, angle); + } + + public void Create(float width, float height, Vector2 topLeft, float angle) + { + RemoverBody(); + + // Calcular el offset del centro desde Top-Left (sin rotación) + var offsetX = width / 2f; + var offsetY = height / 2f; + + // Rotar el offset alrededor del Top-Left usando el ángulo + // ✅ Convertir de grados a radianes antes de usar Math.Cos/Sin + var angleRadians = GradosARadianes(angle); + var cos = (float)Math.Cos(angleRadians); + var sin = (float)Math.Sin(angleRadians); + + var rotatedOffsetX = offsetX * cos - offsetY * sin; + var rotatedOffsetY = offsetX * sin + offsetY * cos; + + // Calcular nueva posición del centro manteniendo Top-Left fijo + var centerX = topLeft.X + rotatedOffsetX; + var centerY = topLeft.Y + rotatedOffsetY; + + // Convertir a 3D e invertir Y - Guías más altas que transportes + var center3D = new Vector3(centerX, -centerY, zAltura_Guia / 2 + zPos_Guia); + + // Crear el Box con las dimensiones dadas - más alto para contener botellas + var box = new Box(width, GuideThickness, zAltura_Guia); + var shapeIndex = _simulation.Shapes.Add(box); + + // ✅ Invertir ángulo cuando se invierte Y para coherencia de rotación + var orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, GradosARadianes(-angle)); + + var bodyDescription = BodyDescription.CreateKinematic( + new RigidPose(center3D, orientation), + new CollidableDescription(shapeIndex, 0.1f), + new BodyActivityDescription(0.01f) + ); + + BodyHandle = _simulation.Bodies.Add(bodyDescription); + _bodyCreated = true; + } + + // Método para actualizar las propiedades desde el objeto WPF + public void UpdateProperties(float ancho, float altoGuia, float angulo) + { + // Nota: ancho y angulo se ignoran ya que se calculan automáticamente desde start->end + GuideThickness = altoGuia; + } + } + + public class simBotella : simBase + { + public float Radius; + public float Height; // Altura para la visualización del cilindro en Helix + private float _mass; + public bool Descartar = false; + public int isOnTransports; + public List ListOnTransports; + public bool isRestricted; + public bool isNoMoreRestricted; + public float OriginalMass; + public simTransporte ConveyorRestrictedTo; + public float OverlapPercentage; + public float _neckRadius; + public bool isOnBrakeTransport; // Nueva propiedad para marcar si está en un transporte con freno + public simTransporte CurrentBrakeTransport { get; set; } // ✅ NUEVA: Referencia al transporte de freno actual + private List _deferredActions; + + public simBotella(Simulation simulation, List deferredActions, float diameter, Vector2 position, float mass, float neckRadius = 0) + { + _simulation = simulation; + _deferredActions = deferredActions; + Radius = diameter / 2f; + Height = diameter; // Altura igual al diámetro para mantener proporciones similares + _mass = mass; + OriginalMass = mass; + _neckRadius = neckRadius; + ListOnTransports = new List(); + + // Convertir Vector2 a Vector3 con Z=0.2 (altura estándar para botellas) + // Invertir Y para convertir de WPF (Y hacia abajo) a 3D (Y hacia arriba) + var position3D = new Vector3(position.X, -position.Y, Radius + zPos_Transporte + zAltura_Transporte); + Create(position3D); + } + + public float CenterX + { + get + { + return GetPosition().X; + } + set + { + var pos = GetPosition(); + SetPosition(value, pos.Y, pos.Z); + } + } + + public float CenterY + { + get + { + // Invertir Y de vuelta para convertir de 3D (Y hacia arriba) a WPF (Y hacia abajo) + return -GetPosition().Y; + } + set + { + var pos = GetPosition(); + // Invertir Y para convertir de WPF (Y hacia abajo) a 3D (Y hacia arriba) + SetPosition(pos.X, -value, pos.Z); + } + } + + public Vector2 Center + { + get + { + var pos3D = GetPosition(); + // Invertir Y de vuelta para convertir de 3D (Y hacia arriba) a WPF (Y hacia abajo) + return new Vector2(pos3D.X, -pos3D.Y); + } + set + { + // Mantener la Z actual, solo cambiar X, Y + var currentPos = GetPosition(); + // Invertir Y para convertir de WPF (Y hacia abajo) a 3D (Y hacia arriba) + SetPosition(value.X, -value.Y, currentPos.Z); + } + } + + public float Mass + { + get + { + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle); + return 1f / bodyReference.LocalInertia.InverseMass; + } + return _mass; + } + set + { + _mass = value; + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + // Usar esfera simple - sin complejidad de inercia personalizada + var sphere = new Sphere(Radius); + var inertia = sphere.ComputeInertia(value); + _simulation.Bodies.SetLocalInertia(BodyHandle, inertia); + } + } + } + + private void Create(Vector3 position) + { + RemoverBody(); + + // Usar ESFERA en BEPU para simplicidad matemática y eficiencia + var sphere = new Sphere(Radius); + var shapeIndex = _simulation.Shapes.Add(sphere); + + // Inercia estándar de esfera - sin complejidad adicional + var inertia = sphere.ComputeInertia(_mass); + + // NUNCA DORMIR - Valor negativo significa que las botellas JAMÁS entran en sleep mode + // Esto es crítico para detección continua de barreras, transportes y descartes + var activityDescription = new BodyActivityDescription(-1f); // -1 = NUNCA dormir + + var bodyDescription = BodyDescription.CreateDynamic( + new RigidPose(position), + new BodyVelocity(), + inertia, // Inercia estándar de esfera + new CollidableDescription(shapeIndex, 0.01f), + activityDescription + ); + + BodyHandle = _simulation.Bodies.Add(bodyDescription); + _bodyCreated = true; // Marcar que hemos creado un cuerpo + } + + public void SetDiameter(float diameter) + { + Radius = diameter / 2f; + Height = diameter; // Mantener altura igual al diámetro para proporciones consistentes + + // Actualizar la forma del cuerpo existente + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var sphere = new Sphere(Radius); + var shapeIndex = _simulation.Shapes.Add(sphere); + _simulation.Bodies.SetShape(BodyHandle, shapeIndex); + } + } + + public void SetHeight(float height) + { + Height = height; + + // Actualizar la forma del cuerpo existente + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var sphere = new Sphere(Radius); + var shapeIndex = _simulation.Shapes.Add(sphere); + _simulation.Bodies.SetShape(BodyHandle, shapeIndex); + } + } + + public void SetMass(float mass) + { + Mass = mass; + } + + /// + /// Limita la rotación de la botella solo al plano XY (siempre "de pie") + /// Esto simplifica enormemente la simulación y es más realista para botellas + /// + public void LimitRotationToXYPlane() + { + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle); + + // Extraer solo la rotación en Z (plano XY) y eliminar las rotaciones en X e Y + var currentOrientation = bodyReference.Pose.Orientation; + + // Convertir a ángulo en Z solamente + var rotationZ = GetRotationZ(); + + // Crear nueva orientación solo con rotación en Z (botella siempre "de pie") + var correctedOrientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, rotationZ); + + // Aplicar la orientación corregida + bodyReference.Pose.Orientation = correctedOrientation; + + // También limitar la velocidad angular a solo rotación en Z + var angularVelocity = bodyReference.Velocity.Angular; + bodyReference.Velocity.Angular = new Vector3(0, 0, angularVelocity.Z); + } + } + + public void ApplyLinearVelocity(Vector3 velocity) + { + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle); + bodyReference.Velocity.Linear = velocity; + } + } + + public Vector3 GetLinearVelocity() + { + if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle)) + { + var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle); + return bodyReference.Velocity.Linear; + } + return Vector3.Zero; + } + + public void CenterFixtureOnConveyor() + { + // Implementar lógica de centrado si es necesario + } + + public bool IsOnAnyTransport() + { + return isOnTransports > 0; + } + + /// + /// ✅ NUEVO: Método de seguridad para restaurar la masa original si la botella tiene masa aumentada + /// + public void RestoreOriginalMassIfNeeded() + { + if (OriginalMass > 0) // Solo si hay masa original guardada + { + try + { + SetMass(OriginalMass); + OriginalMass = 0; // Limpiar para evitar confusión + isOnBrakeTransport = false; + CurrentBrakeTransport = null; + } + catch (Exception ex) + { + // Error restoring original mass - continue + } + } + } + } + + public class simCurve : simBase + { + private float _innerRadius; + private float _outerRadius; + private float _startAngle; + private float _endAngle; + public float Speed { get; set; } // Velocidad para efectos de cinta transportadora (m/s) + + private List _deferredActions; + internal List _triangleBodyHandles; // Lista de todos los triángulos que componen la curva + private List> _originalTriangles; // ✅ NUEVO: Triángulos originales para visualización debug + + public float InnerRadius => _innerRadius; + public float OuterRadius => _outerRadius; + public float StartAngle => _startAngle; + public float EndAngle => _endAngle; + + /// + /// ✅ NUEVO: Expone los triángulos originales para visualización debug + /// + public List> GetOriginalTriangles() => _originalTriangles ?? new List>(); + + public simCurve(Simulation simulation, List deferredActions, float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 topLeft, float unused = 0) + { + _simulation = simulation; + _deferredActions = deferredActions; + _innerRadius = innerRadius; + _outerRadius = outerRadius; + // ✅ SENTIDO HORARIO: Convertir ángulos para que vayan en sentido horario + _startAngle = GradosARadianes(startAngle); + _endAngle = GradosARadianes(endAngle); + _triangleBodyHandles = new List(); + _originalTriangles = new List>(); // ✅ NUEVO: Inicializar lista de triángulos + + // Crear la curva con los ángulos que definen el sector + Create(innerRadius, outerRadius, startAngle, endAngle, topLeft, 0); + } + + public void SetSpeed(float speed) + { + Speed = speed; + } + + /// + /// Configura la velocidad de la curva en metros por segundo + /// Valores positivos mueven en sentido horario, negativos en sentido antihorario + /// + /// Velocidad en m/s (típicamente entre -5.0 y 5.0) + public void SetCurveSpeed(float speedMeterPerSecond) + { + Speed = speedMeterPerSecond; + } + + /// + /// Detiene completamente la curva + /// + public void StopCurve() + { + Speed = 0f; + } + + /// + /// Invierte la dirección de la curva manteniendo la misma velocidad + /// + public void ReverseCurve() + { + Speed = -Speed; + } + + public void UpdateCurve(float innerRadius, float outerRadius, float startAngle, float endAngle) + { + _innerRadius = innerRadius; + _outerRadius = outerRadius; + _startAngle = GradosARadianes(startAngle); + _endAngle = GradosARadianes(endAngle); + + // Recrear la curva con nuevos parámetros manteniendo posición actual + var currentPosition = GetPosition(); + Create(currentPosition); + } + + public new void RemoverBody() + { + // Remover todos los triángulos de la curva + if (_triangleBodyHandles != null && _simulation != null) + { + foreach (var triangleHandle in _triangleBodyHandles) + { + if (_simulation.Bodies.BodyExists(triangleHandle)) + { + _simulation.Bodies.Remove(triangleHandle); + } + } + _triangleBodyHandles.Clear(); + } + + // ✅ NUEVO: Limpiar también los triángulos originales + if (_originalTriangles != null) + { + _originalTriangles.Clear(); + } + + // Remover el cuerpo principal (si existe) + base.RemoverBody(); + } + + public void Create(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 topLeft, float unused = 0) + { + // ✅ CORRIGIDO: Como Aether - startAngle y endAngle definen directamente el sector + // No hay rotación separada del objeto + + // Actualizar parámetros internos + _innerRadius = innerRadius; + _outerRadius = outerRadius; + _startAngle = GradosARadianes(startAngle); + _endAngle = GradosARadianes(endAngle); + + // Calcular el offset del centro desde Top-Left + // Para curvas, el "tamaño" es el diámetro del radio exterior + var curveSize = outerRadius * 2f; + var offsetX = curveSize / 2f; + var offsetY = curveSize / 2f; + + // Calcular nueva posición del centro (sin rotación - el sector ya está definido por los ángulos) + var centerX = topLeft.X + offsetX; + var centerY = topLeft.Y + offsetY; + + // Convertir a 3D e invertir Y - Curvas en el mismo nivel que transportes + var center3D = new Vector3(centerX, -centerY, zAltura_Curve / 2f + zPos_Curve); + + Create(center3D); // Sin rotación adicional + } + + private void Create(Vector3 position) + { + RemoverBody(); + + // Crear triángulos para la curva - los ángulos ya definen la forma correcta + var triangles = CreateTriangulatedCurve(_innerRadius, _outerRadius, -_startAngle, -_endAngle); + + // ✅ NUEVO: Almacenar triángulos originales para visualización debug + _originalTriangles = new List>(triangles); + + foreach (var triangle in triangles) + { + CreateTriangleBody(triangle, position); + } + + // Crear un cuerpo principal invisible para referencia de posición + CreateMainBody(position); + } + + private void CreateMainBody(Vector3 position) + { + // Crear un cuerpo principal muy pequeño e invisible solo para referencia + var smallBox = new Box(0.01f, 0.01f, 0.01f); + var shapeIndex = _simulation.Shapes.Add(smallBox); + + var bodyDescription = BodyDescription.CreateKinematic( + new RigidPose(position), // Sin rotación - la forma ya está definida por los ángulos + new CollidableDescription(shapeIndex, 0f), // Sin speculative margin + new BodyActivityDescription(0.01f) + ); + + BodyHandle = _simulation.Bodies.Add(bodyDescription); + _bodyCreated = true; + } + + private void CreateTriangleBody(List triangle, Vector3 basePosition) + { + if (triangle.Count != 3) + return; + + try + { + // Calcular centro y dimensiones del triángulo + var center = (triangle[0] + triangle[1] + triangle[2]) / 3f; + var worldCenter = center + basePosition; + + // Calcular dimensiones aproximadas del triángulo para crear una caja plana + float minX = Math.Min(Math.Min(triangle[0].X, triangle[1].X), triangle[2].X); + float maxX = Math.Max(Math.Max(triangle[0].X, triangle[1].X), triangle[2].X); + float minY = Math.Min(Math.Min(triangle[0].Y, triangle[1].Y), triangle[2].Y); + float maxY = Math.Max(Math.Max(triangle[0].Y, triangle[1].Y), triangle[2].Y); + + float width = Math.Max(maxX - minX, 0.01f); + float height = Math.Max(maxY - minY, 0.01f); + float thickness = zAltura_Curve; // Altura muy pequeña para triángulos planos + + // Crear una caja plana que aproxime el triángulo + var box = new Box(width, height, thickness); + var shapeIndex = _simulation.Shapes.Add(box); + + // Crear como cuerpo estático sólido (NO sensor) + var activityDescription = new BodyActivityDescription(0.01f); + + var bodyDescription = BodyDescription.CreateKinematic( + new RigidPose(worldCenter), + new CollidableDescription(shapeIndex, 0.1f), // Speculative margin normal + activityDescription + ); + + var triangleHandle = _simulation.Bodies.Add(bodyDescription); + _triangleBodyHandles.Add(triangleHandle); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[simCurve] Error creating triangle: {ex.Message}"); + } + } + + private const float SegmentationFactor = 32f / 3f; + private const int MinSegments = 8; + private const int MaxSegments = 64; + + private List> CreateTriangulatedCurve(float innerRadius, float outerRadius, float startAngle, float endAngle) + { + var triangles = new List>(); + + // Calcular número de segmentos basado en el tamaño del arco + float arcLength = Math.Abs(endAngle - startAngle) * ((innerRadius + outerRadius) / 2f); + int segments = (int)(arcLength * SegmentationFactor); + segments = Math.Max(MinSegments, Math.Min(segments, MaxSegments)); + + // ✅ SENTIDO HORARIO: Intercambiar ángulos para que vaya en sentido horario + float fromAngle = endAngle; // Empezar desde el ángulo final + float toAngle = startAngle; // Terminar en el ángulo inicial + float angleStep = (toAngle - fromAngle) / segments; + + // Generar vértices para los arcos interior y exterior + var innerPoints = new Vector3[segments + 1]; + var outerPoints = new Vector3[segments + 1]; + + for (int i = 0; i <= segments; i++) + { + float angle = fromAngle + i * angleStep; + float cosAngle = (float)Math.Cos(angle); + float sinAngle = (float)Math.Sin(angle); + + // Triángulos planos en el plano XY (Z = 0 relativo) + innerPoints[i] = new Vector3(innerRadius * cosAngle, innerRadius * sinAngle, 0); + outerPoints[i] = new Vector3(outerRadius * cosAngle, outerRadius * sinAngle, 0); + } + + // Crear triángulos + for (int i = 0; i < segments; i++) + { + // Primer triángulo del sector + var upperTriangle = new List + { + innerPoints[i], + outerPoints[i], + outerPoints[i + 1] + }; + triangles.Add(upperTriangle); + + // Segundo triángulo del sector + var lowerTriangle = new List + { + innerPoints[i], + outerPoints[i + 1], + innerPoints[i + 1] + }; + triangles.Add(lowerTriangle); + } + + return triangles; + } + + + + /// + /// Calcula la dirección tangencial para aplicar fuerzas de curva + /// + public Vector3 GetTangentialDirection(Vector3 bottlePosition) + { + try + { + var curvePosition = GetPosition(); + var centerToBottle = new Vector2(bottlePosition.X - curvePosition.X, bottlePosition.Y - curvePosition.Y); + + if (centerToBottle.Length() < 0.001f) + return Vector3.Zero; + + // Vector tangente (perpendicular al radio) en el plano XY + var tangent = new Vector3(-centerToBottle.Y, centerToBottle.X, 0); + + // Normalizar + if (tangent.Length() > 0.001f) + { + tangent = Vector3.Normalize(tangent); + } + + // Ajustar dirección según la velocidad (signo) + if (Speed < 0) + tangent = -tangent; + + return tangent; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[simCurve] Error calculating tangential direction: {ex.Message}"); + return Vector3.Zero; + } + } + } + + public class simDescarte : simBase + { + private float _radius; + private List _deferredActions; + public List ListSimBotellaContact; + + public simDescarte(Simulation simulation, List deferredActions, float diameter, Vector2 position) + { + _simulation = simulation; + _deferredActions = deferredActions; + _radius = diameter / 2f; + ListSimBotellaContact = new List(); + + // Convertir Vector2 a Vector3 con Z=0.3 (altura para detectar botellas) + // Invertir Y para convertir de WPF (Y hacia abajo) a 3D (Y hacia arriba) + var position3D = new Vector3(position.X, -position.Y, zPos_Descarte + _radius); + Create(position3D); + } + + public float Radius + { + get { return _radius; } + set + { + _radius = Math.Max(value, 0.01f); // Mínimo 1cm + // Recrear el cuerpo con el nuevo radio + var currentPos = GetPosition(); + Create(currentPos); + } + } + + public void SetDiameter(float diameter) + { + Radius = diameter / 2f; + } + + public float GetDiameter() + { + return _radius * 2f; + } + + public void Create(Vector2 position) + { + // Convertir Vector2 a Vector3 con Z=0.3 (altura para detectar botellas) + // Invertir Y para convertir de WPF (Y hacia abajo) a 3D (Y hacia arriba) + var position3D = new Vector3(position.X, -position.Y, zPos_Descarte + _radius); + Create(position3D); + } + + private void Create(Vector3 position) + { + RemoverBody(); + + // Crear esfera sensor para detección + var sphere = new BepuPhysics.Collidables.Sphere(_radius); + var shapeIndex = _simulation.Shapes.Add(sphere); + + // Crear como SENSOR (Kinematic con speculative margin 0 para detección pura) + // Configurar actividad para NUNCA dormir - los sensores deben estar siempre activos + var activityDescription = new BodyActivityDescription(-1f); // -1 = nunca dormir + + var bodyDescription = BodyDescription.CreateKinematic( + new RigidPose(position), + new CollidableDescription(shapeIndex, 0f), // Speculative margin 0 para sensores + activityDescription + ); + + BodyHandle = _simulation.Bodies.Add(bodyDescription); + _bodyCreated = true; // Marcar que hemos creado un cuerpo + } + } + + // Callback handlers para BEPU + public struct NarrowPhaseCallbacks : INarrowPhaseCallbacks + { + private SimulationManagerBEPU _simulationManager; + + public NarrowPhaseCallbacks(SimulationManagerBEPU simulationManager) + { + _simulationManager = simulationManager; + } + + public bool AllowContactGeneration(int workerIndex, CollidableReference a, CollidableReference b, ref float speculativeMargin) + { + return true; + } + + public bool AllowContactGeneration(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB) + { + return true; + } + + public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, ref TManifold manifold, out PairMaterialProperties pairMaterial) where TManifold : unmanaged, IContactManifold + { + // Configuración de materiales físicos por defecto + pairMaterial = new PairMaterialProperties + { + FrictionCoefficient = 0.6f, // Fricción moderada + MaximumRecoveryVelocity = 2f, // Velocidad máxima de recuperación + SpringSettings = new SpringSettings(60, 4) // Rigidez y amortiguamiento para colisiones sólidas + }; + + // Detectar contactos que involucren botellas para aplicar propiedades especiales + if (_simulationManager != null) + { + var botellaA = GetBotellaFromCollidable(pair.A); + var botellaB = GetBotellaFromCollidable(pair.B); + var transportA = GetTransportFromCollidable(pair.A); + var transportB = GetTransportFromCollidable(pair.B); + var barreraA = GetBarreraFromCollidable(pair.A); + var barreraB = GetBarreraFromCollidable(pair.B); + var curveA = GetCurveFromCollidable(pair.A); + var curveB = GetCurveFromCollidable(pair.B); + var botella = botellaA ?? botellaB; + + // Detectar contactos con descarte + var descarteA = GetDescarteFromCollidable(pair.A); + var descarteB = GetDescarteFromCollidable(pair.B); + + // IMPORTANTE: Si hay una barrera involucrada, NO generar contacto físico + if (barreraA != null || barreraB != null) + { + // Ya no necesitamos registrar contactos - usamos detección geométrica pura + // Las barreras son sensores completamente virtuales + + // NO generar contacto físico para barreras - son sensores puros + return false; + } + + // Si hay un descarte involucrado, NO generar contacto físico pero registrar para eliminación + if (descarteA != null || descarteB != null) + { + var descarte = descarteA ?? descarteB; + + // Registrar la botella para eliminación si hay contacto + if (botella != null) + { + _simulationManager.RegisterDescarteContact(descarte, botella); + } + + // NO generar contacto físico para descartes - son sensores puros + return false; + } + + // Si hay una botella en contacto (pero no con barrera), aplicar mayor fricción para estabilidad + if (botella != null) + { + // Aumentar mucho la fricción para botellas (más estables) + pairMaterial.FrictionCoefficient = 0.9f; + + // Reducir la velocidad de recuperación para evitar rebotes excesivos + pairMaterial.MaximumRecoveryVelocity = 1f; + + // Hacer las colisiones más suaves y estables + pairMaterial.SpringSettings = new SpringSettings(80, 6); + } + + // Si hay un transporte y una botella en contacto + if ((transportA != null || transportB != null) && botella != null) + { + var transporte = transportA ?? transportB; + + // ✅ NUEVA LÓGICA: Detectar contactos con transporte de freno PRIMERO + if (transporte.isBrake) + { + // FRICCIÓN EXTREMA para transporte con freno - simula frenos mecánicos + pairMaterial.FrictionCoefficient = 5.0f; // Fricción extremadamente alta + + // Reducir velocidad de recuperación para evitar rebotes + pairMaterial.MaximumRecoveryVelocity = 0.1f; + + // Hacer el contacto más rígido para mejor control + pairMaterial.SpringSettings = new SpringSettings(200, 20); + + // ✅ APLICAR FUERZAS DE FRENADO DIRECTAMENTE EN EL MANIFOLD + ApplyBrakeForces(ref manifold, botella, transporte); + + // Registrar contacto con transporte de freno + _simulationManager.RegisterBrakeTransportContact(botella, transporte); + } + else + { + // ✅ NUEVA LÓGICA: Aplicar fuerzas de transporte DIRECTAMENTE en el manifold + if (Math.Abs(transporte.Speed) > 0.001f) // Solo si hay velocidad significativa + { + pairMaterial.FrictionCoefficient = 1.2f; // Fricción muy alta + + // ✅ APLICAR FUERZAS DE TRANSPORTE DIRECTAMENTE EN EL MANIFOLD + ApplyTransportForces(ref manifold, botella, transporte); + } + } + } + + // Si hay una curva y una botella en contacto + if ((curveA != null || curveB != null) && botella != null) + { + var curve = curveA ?? curveB; + + // Solo aplicar si hay velocidad significativa en la curva + if (Math.Abs(curve.Speed) > 0.001f) + { + pairMaterial.FrictionCoefficient = 1.0f; // Fricción alta para curvas + + // Aplicar fuerzas de curva directamente en el manifold + ApplyCurveForces(ref manifold, botella, curve); + } + } + } + + return true; + } + + private simTransporte GetTransportFromCollidable(CollidableReference collidable) + { + if (_simulationManager?.simulation != null) + { + if (collidable.Mobility == CollidableMobility.Kinematic) + { + var bodyHandle = collidable.BodyHandle; + var simBase = _simulationManager.GetSimBaseFromBodyHandle(bodyHandle); + return simBase as simTransporte; + } + } + return null; + } + + private simBotella GetBotellaFromCollidable(CollidableReference collidable) + { + if (_simulationManager?.simulation != null) + { + if (collidable.Mobility == CollidableMobility.Dynamic) + { + var bodyHandle = collidable.BodyHandle; + var simBase = _simulationManager.GetSimBaseFromBodyHandle(bodyHandle); + return simBase as simBotella; + } + } + return null; + } + + private simBarrera GetBarreraFromCollidable(CollidableReference collidable) + { + if (_simulationManager?.simulation != null) + { + if (collidable.Mobility == CollidableMobility.Kinematic) + { + var bodyHandle = collidable.BodyHandle; + var simBase = _simulationManager.GetSimBaseFromBodyHandle(bodyHandle); + return simBase as simBarrera; + } + } + return null; + } + + private simDescarte GetDescarteFromCollidable(CollidableReference collidable) + { + if (_simulationManager?.simulation != null) + { + if (collidable.Mobility == CollidableMobility.Kinematic) + { + var bodyHandle = collidable.BodyHandle; + var simBase = _simulationManager.GetSimBaseFromBodyHandle(bodyHandle); + return simBase as simDescarte; + } + } + return null; + } + + private simCurve GetCurveFromCollidable(CollidableReference collidable) + { + if (_simulationManager?.simulation != null) + { + if (collidable.Mobility == CollidableMobility.Kinematic) + { + var bodyHandle = collidable.BodyHandle; + + // ✅ NUEVO: Buscar específicamente en los triángulos de las curvas + return _simulationManager.GetCurveFromTriangleBodyHandle(bodyHandle); + } + } + return null; + } + + /// + /// ✅ NUEVO MÉTODO: Aplicar fuerzas de frenado directamente en el manifold + /// + private void ApplyBrakeForces(ref TManifold manifold, simBotella botella, simTransporte transporte) + where TManifold : unmanaged, IContactManifold + { + try + { + if (_simulationManager?.simulation == null) return; + + var botellaBody = _simulationManager.simulation.Bodies.GetBodyReference(botella.BodyHandle); + var transporteBody = _simulationManager.simulation.Bodies.GetBodyReference(transporte.BodyHandle); + + // Calcular velocidad relativa + var relativeVelocity = botellaBody.Velocity.Linear - transporteBody.Velocity.Linear; + + // Si la botella se mueve más rápido que el transporte, aplicar fuerza de frenado + var transportDirection = GetTransportDirection(transporte); + var velocityInTransportDirection = Vector3.Dot(relativeVelocity, transportDirection); + + // if (Math.Abs(velocityInTransportDirection) > Math.Abs(transporte.Speed / 18.5f)) + // { + // Calcular fuerza de frenado proporcional a la diferencia de velocidad + var targetSpeed = transporte.Speed / 18.5f; + var currentSpeed = Vector3.Dot(botellaBody.Velocity.Linear, transportDirection); + var speedDifference = currentSpeed - targetSpeed; + + // Aplicar impulso de frenado - esto se hace DURANTE la resolución de colisiones + var brakeImpulse = -transportDirection * speedDifference * botella.Mass * 0.5f; + botellaBody.ApplyLinearImpulse(brakeImpulse); + // } + } + catch (Exception ex) + { + // Error applying brake forces during collision resolution + } + } + + /// + /// ✅ NUEVO MÉTODO: Obtener dirección del transporte + /// + private Vector3 GetTransportDirection(simTransporte transporte) + { + var angle = transporte.GetRotationZ(); + return new Vector3((float)Math.Cos(angle), (float)Math.Sin(angle), 0); + } + + /// + /// ✅ NUEVO MÉTODO: Aplicar fuerzas de transporte normal directamente en el manifold + /// + private void ApplyTransportForces(ref TManifold manifold, simBotella botella, simTransporte transporte) + where TManifold : unmanaged, IContactManifold + { + try + { + if (_simulationManager?.simulation == null) return; + + var botellaBody = _simulationManager.simulation.Bodies.GetBodyReference(botella.BodyHandle); + var transporteBody = _simulationManager.simulation.Bodies.GetBodyReference(transporte.BodyHandle); + + // Calcular la dirección del transporte + var transportDirection = GetTransportDirection(transporte); + + // Calcular la fuerza proporcional a la velocidad deseada + var targetVelocity = transportDirection * transporte.Speed / 18.5f; + var currentVelocity = botellaBody.Velocity.Linear; + + // Solo aplicar fuerza en el plano horizontal (X-Y) + var horizontalVelocity = new Vector3(currentVelocity.X, currentVelocity.Y, 0); + + // Para transportes con freno (si aplica), limitar la velocidad máxima de la botella + bool isOnBrakeTransport = botella.isOnBrakeTransport; + if (isOnBrakeTransport) + { + var transportSpeed = Math.Abs(transporte.Speed / 18.5f); + var currentSpeed = horizontalVelocity.Length(); + + // Si la botella va más rápido que el transporte, limitar su velocidad + if (currentSpeed > transportSpeed * 1.1f) // 10% de tolerancia + { + var limitedVelocity = Vector3.Normalize(horizontalVelocity) * transportSpeed; + botellaBody.Velocity.Linear = new Vector3(limitedVelocity.X, limitedVelocity.Y, currentVelocity.Z); + horizontalVelocity = limitedVelocity; + } + } + + var velocityDiff = targetVelocity - horizontalVelocity; + + // Aplicar una fuerza proporcional a la diferencia de velocidad + var forceMultiplier = isOnBrakeTransport ? 3.0f : 1.0f; // 3x más fuerte para botellas con guías + var baseForceMagnitude = velocityDiff.Length() * botella.Mass * 10f; // Factor base + var forceMagnitude = baseForceMagnitude * forceMultiplier; // Aplicar multiplicador + + if (forceMagnitude > 0.01f) + { + var maxForce = botella.Mass * 50f * forceMultiplier; // Límite también escalado + var force = Vector3.Normalize(velocityDiff) * Math.Min(forceMagnitude, maxForce); + + // Aplicar impulso directamente durante la resolución de colisiones + botellaBody.ApplyLinearImpulse(force * (1f / 60f)); // Simular deltaTime típico + } + } + catch (Exception ex) + { + // Error applying transport forces during collision resolution + } + } + + /// + /// ✅ SIMPLIFICADO: Aplicar fuerzas de curva directamente en el manifold + /// Si hay colisión física, la botella está sobre la curva - no necesita cálculos adicionales + /// + private void ApplyCurveForces(ref TManifold manifold, simBotella botella, simCurve curve) + where TManifold : unmanaged, IContactManifold + { + try + { + if (_simulationManager?.simulation == null) return; + + var botellaBody = _simulationManager.simulation.Bodies.GetBodyReference(botella.BodyHandle); + var botellaPosition = botellaBody.Pose.Position; + + // ✅ SIMPLIFICADO: Si hay colisión física, aplicar fuerzas directamente + // Obtener la dirección tangencial + var tangentialDirection = curve.GetTangentialDirection(botellaPosition); + + if (tangentialDirection.Length() > 0.001f) + { + // Calcular velocidad objetivo + var speedMetersPerSecond = Math.Abs(curve.Speed) / 18.5f; // Convertir a m/s + var targetVelocity = tangentialDirection * speedMetersPerSecond; + + // Obtener velocidad actual + var currentVelocity = botellaBody.Velocity.Linear; + var horizontalVelocity = new Vector3(currentVelocity.X, currentVelocity.Y, 0); + + // Usar directamente la dirección tangencial (sin interpolación compleja) + var newDirection = tangentialDirection; + + // Usar la velocidad mayor entre la actual y la de la curva para continuidad + var currentSpeed = horizontalVelocity.Length(); + var targetSpeed = Math.Max(currentSpeed, speedMetersPerSecond); + + // Calcular nueva velocidad + var newVelocity = newDirection * targetSpeed; + var velocityDiff = newVelocity - horizontalVelocity; + + // Aplicar impulso directamente (sin factor de overlap - la colisión es suficiente) + var forceMagnitude = velocityDiff.Length() * botella.Mass * 8f; + if (forceMagnitude > 0.01f) + { + var maxForce = botella.Mass * 40f; + var force = Vector3.Normalize(velocityDiff) * Math.Min(forceMagnitude, maxForce); + botellaBody.ApplyLinearImpulse(force * (1f / 60f)); // Simular deltaTime típico + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[simCurve] Error applying curve forces: {ex.Message}"); + } + } + + public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB, ref ConvexContactManifold manifold) + { + return true; + } + + public void Initialize(Simulation simulation) + { + } + + public void Dispose() + { + } + } + + public struct PoseIntegratorCallbacks : IPoseIntegratorCallbacks + { + public Vector3 Gravity; + public float LinearDamping; + public float AngularDamping; + private SimulationManagerBEPU _simulationManager; // ✅ NUEVA REFERENCIA + + public PoseIntegratorCallbacks(Vector3 gravity, float linearDamping = 0.999f, float angularDamping = 0.995f, SimulationManagerBEPU simulationManager = null) + { + Gravity = gravity; + LinearDamping = linearDamping; + AngularDamping = angularDamping; + _simulationManager = simulationManager; // ✅ NUEVA INICIALIZACIÓN + } + + public readonly AngularIntegrationMode AngularIntegrationMode => AngularIntegrationMode.Nonconserving; + + public readonly bool AllowSubstepsForUnconstrainedBodies => false; + + public readonly bool IntegrateVelocityForKinematics => false; + + public void Initialize(Simulation simulation) + { + } + + public void PrepareForIntegration(float dt) + { + } + + public void IntegrateVelocity(Vector bodyIndices, Vector3Wide position, QuaternionWide orientation, BodyInertiaWide localInertia, Vector integrationMask, int workerIndex, Vector dt, ref BodyVelocityWide velocity) + { + // Aplicar gravedad + var gravityWide = Vector3Wide.Broadcast(Gravity); + velocity.Linear += gravityWide * dt; + + // ✅ NUEVA FUNCIONALIDAD: Aplicar fuerzas de frenado durante la integración + if (_simulationManager != null) + { + ApplyBrakeForcesInIntegration(bodyIndices, ref velocity, dt, workerIndex); + } + + // Aplicar amortiguamiento lineal y angular para simular resistencia del aire + // Esto es crucial para que los cilindros se detengan de forma realista + var linearDampingWide = Vector.One * LinearDamping; + var angularDampingWide = Vector.One * AngularDamping; + + velocity.Linear *= linearDampingWide; + velocity.Angular *= angularDampingWide; // ¡Esto es clave para detener rotaciones infinitas! + } + + /// + /// ✅ NUEVO MÉTODO: Aplicar fuerzas de frenado durante la integración de velocidades + /// ✅ SIMPLIFICADO: Solo aplicar amortiguamiento extra para botellas en transportes con freno + /// + private void ApplyBrakeForcesInIntegration(Vector bodyIndices, ref BodyVelocityWide velocity, Vector dt, int workerIndex) + { + try + { + // ✅ SIMPLIFICADO: Aplicar amortiguamiento extra para efectos de frenado + // Esto es más compatible con la arquitectura vectorizada de BEPU + + // Amortiguamiento adicional para simular efecto de frenado + var brakeDampingWide = Vector.One * 0.95f; // Más agresivo que el damping normal + + // Aplicar solo si tenemos referencia al simulation manager + if (_simulationManager != null) + { + // El amortiguamiento extra se aplica globalmente, + // las fuerzas específicas se manejan en ApplyBrakeForces del NarrowPhaseCallbacks + velocity.Linear *= brakeDampingWide; + } + } + catch (Exception ex) + { + // Error during brake force integration + } + } + } + + public class SimulationManagerBEPU + { + public Simulation simulation; + public List Cuerpos; + public List _deferredActions; + private BufferPool bufferPool; + private Stopwatch stopwatch; + private double stopwatch_last; + + // Referencia al manager de visualización 3D + public BEPUVisualization3DManager Visualization3DManager { get; set; } + + // Sistema de contactos de transporte para aplicar fuerzas + private Dictionary _transportContacts; + // Sistema de contactos de barrera para detección de paso + private Dictionary> _barreraContacts; + // Sistema de contactos de descarte para marcar botellas para eliminación + private Dictionary> _descarteContacts; + // Lista de botellas marcadas para eliminación + private HashSet _botellasParaEliminar; + // Sistema de contactos de transporte con freno para marcar botellas + private Dictionary _brakeTransportContacts; + private object _contactsLock = new object(); + + /// + /// Obtiene el objeto simBase correspondiente a un BodyHandle + /// + public simBase GetSimBaseFromBodyHandle(BodyHandle bodyHandle) + { + return Cuerpos.FirstOrDefault(c => c.BodyHandle.Equals(bodyHandle)); + } + + /// + /// ✅ NUEVO: Obtiene una simCurve si el BodyHandle pertenece a uno de sus triángulos + /// + public simCurve GetCurveFromTriangleBodyHandle(BodyHandle bodyHandle) + { + // Buscar en todas las curvas si alguna contiene este BodyHandle en sus triángulos + var curves = Cuerpos.OfType(); + + foreach (var curve in curves) + { + if (curve._triangleBodyHandles != null && curve._triangleBodyHandles.Contains(bodyHandle)) + { + return curve; + } + } + + // Si no se encuentra en triángulos, buscar en cuerpos principales como fallback + var simBase = GetSimBaseFromBodyHandle(bodyHandle); + return simBase as simCurve; + } + + public SimulationManagerBEPU() + { + Cuerpos = new List(); + _deferredActions = new List(); + _transportContacts = new Dictionary(); + _barreraContacts = new Dictionary>(); + _descarteContacts = new Dictionary>(); + _botellasParaEliminar = new HashSet(); + _brakeTransportContacts = new Dictionary(); + bufferPool = new BufferPool(); + stopwatch = new Stopwatch(); + + var narrowPhaseCallbacks = new NarrowPhaseCallbacks(this); + + // Configurar amortiguamiento para comportamiento realista: + // - LinearDamping: 0.999f (muy leve, objetos siguen moviéndose pero eventualmente se detienen) + // - AngularDamping: 0.995f (más agresivo para detener rotaciones infinitas de cilindros) + // ✅ MODIFICADO: Pasar referencia de this al PoseIntegratorCallbacks + var poseIntegratorCallbacks = new PoseIntegratorCallbacks( + gravity: new Vector3(0, 0, -9.81f), // Gravedad en Z + linearDamping: 0.999f, // Amortiguamiento lineal suave + angularDamping: 0.995f, // Amortiguamiento angular más fuerte para detener rotaciones + simulationManager: this // ✅ NUEVA REFERENCIA + ); + + var solveDescription = new SolveDescription(8, 1); + + simulation = Simulation.Create(bufferPool, narrowPhaseCallbacks, poseIntegratorCallbacks, solveDescription); + } + + public void Clear() + { + try + { + // ✅ NUEVO: Restaurar masas originales de todas las botellas antes de limpiar + foreach (var cuerpo in Cuerpos.OfType()) + { + try + { + cuerpo.RestoreOriginalMassIfNeeded(); + } + catch (Exception ex) + { + // Error restoring mass during clear - continue + } + } + + // Limpiar contactos primero para evitar referencias colgantes + lock (_contactsLock) + { + _transportContacts.Clear(); + _barreraContacts.Clear(); + _descarteContacts.Clear(); + _botellasParaEliminar.Clear(); + _brakeTransportContacts.Clear(); + } + + // Remover cuerpos de forma segura + var cuerposToRemove = new List(Cuerpos); + foreach (var cuerpo in cuerposToRemove) + { + try + { + if (cuerpo != null) + { + cuerpo.RemoverBody(); + } + } + catch (Exception ex) + { + // Error removing body - continue with cleanup + } + } + + Cuerpos.Clear(); + _deferredActions.Clear(); + + // Limpiar la simulación completamente si existe + if (simulation != null) + { + try + { + // Forzar un timestep pequeño para limpiar contactos pendientes + simulation.Timestep(1f / 1000f); // 1ms + } + catch (Exception ex) + { + // Warning during cleanup timestep - continue + } + } + + // Limpiar visualización 3D + Visualization3DManager?.Clear(); + } + catch (Exception ex) + { + // Critical error during clear - operation failed + } + } + + public void Start() + { + stopwatch.Start(); + stopwatch_last = stopwatch.Elapsed.TotalMilliseconds; + } + + public void Step() + { + try + { + var currentTime = stopwatch.Elapsed.TotalMilliseconds; + var deltaTime = (float)((currentTime - stopwatch_last) / 1000.0); + stopwatch_last = currentTime; + + // Validar deltaTime para evitar valores problemáticos + if (float.IsNaN(deltaTime) || float.IsInfinity(deltaTime) || deltaTime <= 0) + { + deltaTime = 1f / 60f; // Fallback a 60 FPS + } + + // Limitar deltaTime a 100ms máximo para evitar saltos tras pausas del debugger + const float maxDeltaTime = 0.1f; // 100ms + if (deltaTime > maxDeltaTime) + { + deltaTime = 1f / 60f; // Resetear a 60 FPS estándar si excede el límite + } + + // Procesar acciones diferidas + foreach (var action in _deferredActions) + { + action(); + } + _deferredActions.Clear(); + + // Validar que la simulación esté en buen estado antes del timestep + if (simulation?.Bodies == null) + { + return; + } + + // Validar que no hay cuerpos con handles inválidos + var invalidBodies = Cuerpos.Where(c => c != null && !simulation.Bodies.BodyExists(c.BodyHandle)).ToList(); + if (invalidBodies.Count > 0) + { + foreach (var invalidBody in invalidBodies) + { + Cuerpos.Remove(invalidBody); + } + } + + // Paso de simulación (genera las colisiones/contactos) + var timestepValue = Math.Max(deltaTime, 1f / 120f); // Mínimo 120 FPS para mejor estabilidad + + try + { + simulation.Timestep(timestepValue); + } + catch (AccessViolationException ex) + { + // Limpiar contactos que podrían estar corruptos + lock (_contactsLock) + { + // ✅ ELIMINADO: _transportContacts.Clear(); - Ya no se usa + _barreraContacts.Clear(); + } + throw; // Re-lanzar para debug + } + + // ✅ ELIMINADO: ApplyTransportForces - Ahora se aplica directamente en ConfigureContactManifold + // ApplyTransportForces(deltaTime); // ELIMINADO - Ahora en ConfigureContactManifold + + // Procesar contactos de transporte con freno para marcar/desmarcar botellas + ProcessBrakeTransportContacts(); + + // Limitar rotaciones de botellas solo al plano XY (siempre "de pie") + // Y GARANTIZAR que nunca entren en sleep mode + foreach (var cuerpo in Cuerpos.OfType().ToList()) // ToList para evitar modificación durante iteración + { + try + { + if (simulation.Bodies.BodyExists(cuerpo.BodyHandle)) + { + cuerpo.LimitRotationToXYPlane(); + + // FORZAR que la botella esté siempre despierta - doble seguridad + simulation.Awakener.AwakenBody(cuerpo.BodyHandle); + } + } + catch (Exception ex) + { + // Error limiting rotation for bottle - continue + } + } + + // Procesar contactos de barrera para detección de paso + ProcessBarreraContacts(); + + // Procesar contactos de descarte para eliminación de botellas + ProcessDescarteContacts(); + + // Procesar sistema de limpieza para eliminar botellas debajo de transportes + ProcessCleanupSystem(); + + // Limpiar contactos DESPUÉS de usarlos + lock (_contactsLock) + { + // ✅ ELIMINADO: _transportContacts.Clear(); - Ya no se usa + _barreraContacts.Clear(); + _descarteContacts.Clear(); + _brakeTransportContacts.Clear(); + } + + // Sincronizar con la visualización 3D + Visualization3DManager?.SynchronizeWorld(); + } + catch (Exception ex) + { + // En caso de error crítico, limpiar contactos para evitar estado corrupto + lock (_contactsLock) + { + // ✅ ELIMINADO: _transportContacts.Clear(); - Ya no se usa + _barreraContacts.Clear(); + _brakeTransportContacts.Clear(); + } + } + } + + public void Remove(simBase Objeto) + { + // ✅ NUEVO: Si es una botella con masa aumentada, restaurar masa original antes de eliminar + if (Objeto is simBotella botella && botella.OriginalMass > 0) + { + try + { + botella.SetMass(botella.OriginalMass); + botella.OriginalMass = 0; + botella.isOnBrakeTransport = false; + botella.CurrentBrakeTransport = null; + } + catch (Exception ex) + { + // Error restoring mass during removal - continue with removal + } + } + + Objeto.RemoverBody(); + Cuerpos.Remove(Objeto); + + // Actualizar visualización 3D tras eliminar objeto + Visualization3DManager?.SynchronizeWorld(); + } + + public simBotella AddCircle(float diameter, Vector2 position, float mass) + { + var botella = new simBotella(simulation, _deferredActions, diameter, position, mass); + Cuerpos.Add(botella); + + // Sincronizar con la visualización 3D + Visualization3DManager?.SynchronizeWorld(); + + return botella; + } + + public simTransporte AddRectangle(float width, float height, Vector2 position, float angle) + { + var transporte = new simTransporte(simulation, _deferredActions, width, height, position, angle); + Cuerpos.Add(transporte); + // Sincronizar con la visualización 3D + Visualization3DManager?.SynchronizeWorld(); + + return transporte; + } + + public simBarrera AddBarrera(float width, float height, Vector2 position, float angle, bool detectarCuello) + { + var barrera = new simBarrera(simulation, _deferredActions, width, height, position, angle, detectarCuello); + Cuerpos.Add(barrera); + // Sincronizar con la visualización 3D + Visualization3DManager?.SynchronizeWorld(); + + return barrera; + } + + + + public simGuia AddLine(float width, float height, Vector2 topLeft, float angle) + { + var guia = new simGuia(simulation, _deferredActions, width, height, topLeft, angle); + Cuerpos.Add(guia); + // Sincronizar con la visualización 3D + Visualization3DManager?.SynchronizeWorld(); + + return guia; + } + + public simDescarte AddDescarte(float diameter, Vector2 position) + { + var descarte = new simDescarte(simulation, _deferredActions, diameter, position); + Cuerpos.Add(descarte); + // Sincronizar con la visualización 3D + Visualization3DManager?.SynchronizeWorld(); + + return descarte; + } + + public simCurve AddCurve(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 topLeft, float unused = 0) + { + var curve = new simCurve(simulation, _deferredActions, innerRadius, outerRadius, startAngle, endAngle, topLeft, unused); + Cuerpos.Add(curve); + // Sincronizar con la visualización 3D + Visualization3DManager?.SynchronizeWorld(); + + return curve; + } + + public void Dispose() + { + Clear(); + simulation?.Dispose(); + bufferPool?.Clear(); + } + + /// + /// Registra un contacto entre una botella y un transporte para aplicar fuerzas + /// + /// Botella en contacto + /// Transporte en contacto + public void RegisterTransportContact(simBotella botella, simTransporte transporte) + { + if (botella != null && transporte != null) + { + lock (_contactsLock) + { + _transportContacts[botella] = transporte; + } + } + } + + /// + /// Registra un contacto entre una botella y un transporte con freno para marcar la botella + /// ✅ NUEVO: También aumenta la masa de la botella 10x para simular conexión mecánica + /// + /// Botella en contacto + /// Transporte con freno en contacto + public void RegisterBrakeTransportContact(simBotella botella, simTransporte transporte) + { + if (botella != null && transporte != null && transporte.isBrake) + { + lock (_contactsLock) + { + _brakeTransportContacts[botella] = transporte; + // ✅ NUEVO: Mantener referencia directa en la botella + botella.CurrentBrakeTransport = transporte; + botella.ConveyorRestrictedTo = transporte; + + // ✅ NUEVO: Aumentar masa 10x para simular conexión mecánica con el transporte + if (!botella.isOnBrakeTransport) // Solo si no estaba ya en un transporte con freno + { + // Guardar masa original si no está guardada ya + if (botella.OriginalMass <= 0) + { + botella.OriginalMass = botella.Mass; + } + + // Aumentar masa 10 veces para simular "enganche" mecánico + botella.SetMass(botella.OriginalMass * 100f); + } + } + } + } + + /// + /// Registra un contacto entre una barrera y una botella para detección de paso + /// + /// Barrera que detecta el paso + /// Botella que está pasando + public void RegisterBarreraContact(simBarrera barrera, simBotella botella) + { + if (barrera != null && botella != null) + { + lock (_contactsLock) + { + if (!_barreraContacts.ContainsKey(barrera)) + { + _barreraContacts[barrera] = new List(); + } + + if (!_barreraContacts[barrera].Contains(botella)) + { + _barreraContacts[barrera].Add(botella); + } + } + } + } + + /// + /// Registra un contacto entre un descarte y una botella para marcar eliminación + /// + /// Descarte que detecta la botella + /// Botella que debe ser eliminada + public void RegisterDescarteContact(simDescarte descarte, simBotella botella) + { + if (descarte != null && botella != null) + { + lock (_contactsLock) + { + // Marcar la botella para eliminación + _botellasParaEliminar.Add(botella); + botella.Descartar = true; + + // También registrar el contacto para la lista del descarte + if (!_descarteContacts.ContainsKey(descarte)) + { + _descarteContacts[descarte] = new List(); + } + + if (!_descarteContacts[descarte].Contains(botella)) + { + _descarteContacts[descarte].Add(botella); + } + } + } + } + + /// + /// Aplica fuerzas de transporte a las botellas en contacto + /// + /// Tiempo transcurrido desde el último paso + private void ApplyTransportForces(float deltaTime) + { + try + { + // Validar deltaTime + if (float.IsNaN(deltaTime) || float.IsInfinity(deltaTime) || deltaTime <= 0) + return; + + lock (_contactsLock) + { + // Crear copia de contactos para evitar modificación durante iteración + var contactsCopy = new Dictionary(_transportContacts); + + foreach (var contact in contactsCopy) + { + try + { + var botella = contact.Key; + var transporte = contact.Value; + + // Validar objetos + if (botella == null || transporte == null || simulation?.Bodies == null) + continue; + + // Verificar que ambos objetos aún existen + if (!simulation.Bodies.BodyExists(botella.BodyHandle) || !simulation.Bodies.BodyExists(transporte.BodyHandle)) + continue; + + // Solo aplicar si hay velocidad significativa + if (Math.Abs(transporte.Speed) <= 0.001f) + continue; + + // Determinar si es una botella en transporte con freno (guías laterales) + bool isOnBrakeTransport = botella.isOnBrakeTransport; + float forceMultiplier = isOnBrakeTransport ? 3.0f : 1.0f; // 3x más fuerte para botellas con guías + + // Aplicar fuerzas de movimiento (longitudinales) con control de velocidad + ApplyLongitudinalForces(botella, transporte, deltaTime, forceMultiplier, isOnBrakeTransport); + + } + catch (Exception ex) + { + // Error processing contact - continue + } + } + } + } + catch (Exception ex) + { + // Critical error in ApplyTransport - continue + } + } + + /// + /// Aplica fuerzas longitudinales para el movimiento del transporte con control de velocidad + /// + private void ApplyLongitudinalForces(simBotella botella, simTransporte transporte, float deltaTime, float forceMultiplier, bool isOnBrakeTransport) + { + try + { + // Calcular la dirección del transporte de forma segura + var angle = transporte.GetRotationZ(); + + // Validar ángulo + if (float.IsNaN(angle) || float.IsInfinity(angle)) + return; + + var cos = (float)Math.Cos(angle); + var sin = (float)Math.Sin(angle); + + // Validar valores trigonométricos + if (float.IsNaN(cos) || float.IsNaN(sin)) + return; + + // Vector de dirección del transporte (en el plano X-Y) + var transportDirection = new Vector3(cos, sin, 0); + + // Calcular la fuerza proporcional a la velocidad deseada + var targetVelocity = transportDirection * transporte.Speed / 18.5f; + var currentVelocity = botella.GetLinearVelocity(); + + // Validar velocidades + if (float.IsNaN(currentVelocity.X) || float.IsNaN(currentVelocity.Y) || float.IsNaN(currentVelocity.Z)) + return; + + // Solo aplicar fuerza en el plano horizontal (X-Y) + var horizontalVelocity = new Vector3(currentVelocity.X, currentVelocity.Y, 0); + + // Para transportes con freno, limitar la velocidad máxima de la botella + if (isOnBrakeTransport) + { + var transportSpeed = Math.Abs(transporte.Speed / 18.5f); + var currentSpeed = horizontalVelocity.Length(); + + // Si la botella va más rápido que el transporte, limitar su velocidad + if (currentSpeed > transportSpeed * 1f) // 10% de tolerancia + { + var limitedVelocity = Vector3.Normalize(horizontalVelocity) * transportSpeed; + botella.ApplyLinearVelocity(new Vector3(limitedVelocity.X, limitedVelocity.Y, currentVelocity.Z)); + horizontalVelocity = limitedVelocity; + } + } + + var velocityDiff = targetVelocity - horizontalVelocity; + + // Validar diferencia de velocidad + if (float.IsNaN(velocityDiff.X) || float.IsNaN(velocityDiff.Y) || float.IsNaN(velocityDiff.Z)) + return; + + // Aplicar una fuerza proporcional a la diferencia de velocidad + var baseForceMagnitude = velocityDiff.Length() * botella.Mass * 10f; // Factor base + var forceMagnitude = baseForceMagnitude * forceMultiplier; // Aplicar multiplicador + + // Validar magnitud de fuerza + if (float.IsNaN(forceMagnitude) || float.IsInfinity(forceMagnitude)) + return; + + if (forceMagnitude > 0.01f) + { + var maxForce = botella.Mass * 50f * forceMultiplier; // Límite también escalado + var force = Vector3.Normalize(velocityDiff) * Math.Min(forceMagnitude, maxForce); + + // Validar fuerza final + if (float.IsNaN(force.X) || float.IsNaN(force.Y) || float.IsNaN(force.Z)) + return; + + var bodyReference = simulation.Bodies.GetBodyReference(botella.BodyHandle); + bodyReference.ApplyLinearImpulse(force * deltaTime); + } + } + catch (Exception ex) + { + // Error in ApplyLongitudinalForces - continue + } + } + + + /// + /// Procesa contactos de transporte con freno para marcar/desmarcar botellas + /// ✅ NUEVO: También gestiona el cambio de masa - restaura masa original al salir del transporte + /// + private void ProcessBrakeTransportContacts() + { + try + { + // Primero, recopilar botellas que estaban en transporte con freno para detectar cambios + var todasLasBotellas = Cuerpos.OfType().ToList(); + var botellasQueEstabanEnBrakeTransport = new HashSet(); + + foreach (var botella in todasLasBotellas) + { + if (botella != null && botella.isOnBrakeTransport) + { + botellasQueEstabanEnBrakeTransport.Add(botella); + botella.isOnBrakeTransport = false; // Asumir que no están en contacto + } + } + + // Luego, marcar las botellas que SÍ están en contacto con transportes de freno + lock (_contactsLock) + { + var contactsCopy = new Dictionary(_brakeTransportContacts); + + foreach (var contact in contactsCopy) + { + try + { + var botella = contact.Key; + var transporte = contact.Value; + + // Validar objetos + if (botella == null || transporte == null || simulation?.Bodies == null) + continue; + + // Verificar que ambos objetos aún existen + if (!simulation.Bodies.BodyExists(botella.BodyHandle) || !simulation.Bodies.BodyExists(transporte.BodyHandle)) + continue; + + // Verificar que el transporte sigue teniendo freno activado + if (transporte.isBrake) + { + botella.isOnBrakeTransport = true; + // Quitar de la lista de botellas que estaban en brake transport + botellasQueEstabanEnBrakeTransport.Remove(botella); + } + } + catch (Exception ex) + { + // Error processing brake transport contact - continue + } + } + } + + // ✅ NUEVO: Restaurar masa original de botellas que salieron del transporte con freno + foreach (var botella in botellasQueEstabanEnBrakeTransport) + { + try + { + if (botella.OriginalMass > 0) // Solo si tenemos masa original guardada + { + botella.SetMass(botella.OriginalMass); + botella.OriginalMass = 0; // Limpiar para evitar confusión + botella.CurrentBrakeTransport = null; // Limpiar referencia + } + } + catch (Exception ex) + { + // Error restoring original mass - continue + } + } + } + catch (Exception ex) + { + // Critical error in ProcessBrakeTransportContacts - continue + } + } + + /// + /// Procesa todas las barreras usando detección geométrica pura + /// Ya no depende de contactos físicos de BEPU - usa cálculo geométrico para ambos flags + /// + private void ProcessBarreraContacts() + { + try + { + // Obtener todas las barreras y botellas + var barreras = Cuerpos.OfType().ToList(); + var botellas = Cuerpos.OfType().ToList(); + + + foreach (var barrera in barreras) + { + try + { + // Verificar que la barrera aún existe en la simulación + if (barrera == null || !simulation?.Bodies?.BodyExists(barrera.BodyHandle) == true) + continue; + + // Resetear flags + barrera.LuzCortada = false; + barrera.LuzCortadaNeck = false; + barrera.Distancia = float.MaxValue; + + // Limpiar la lista de forma segura + if (barrera.ListSimBotellaContact != null) + { + barrera.ListSimBotellaContact.Clear(); + } + + // Calcular detección usando geometría pura para TODAS las botellas + CalculateBarreraDetectionGeometric(barrera, botellas); + } + catch (Exception ex) + { + // Error processing barrera - continue + } + } + + // Limpiar contactos ya que no los usamos más + lock (_contactsLock) + { + _barreraContacts.Clear(); + } + } + catch (Exception ex) + { + // Critical error in ProcessBarrera - continue + } + } + + /// + /// Calcula detección geométrica pura para una barrera específica contra todas las botellas + /// Usa el mismo sistema geométrico para ambos flags: LuzCortada (radio completo) y LuzCortadaNeck (radio/2) + /// + /// Barrera que está detectando + /// Lista de TODAS las botellas en la simulación + private void CalculateBarreraDetectionGeometric(simBarrera barrera, List todasLasBotellas) + { + try + { + // Validaciones de seguridad + if (barrera == null || todasLasBotellas == null || simulation?.Bodies == null) + return; + + if (!simulation.Bodies.BodyExists(barrera.BodyHandle)) + return; + + var barrierBody = simulation.Bodies[barrera.BodyHandle]; + var barrierPosition = barrierBody.Pose.Position; + var barrierOrientation = barrierBody.Pose.Orientation; + + // Validar valores de posición y orientación + if (float.IsNaN(barrierPosition.X) || float.IsNaN(barrierPosition.Y) || float.IsNaN(barrierPosition.Z)) + { + return; + } + + // Obtener las dimensiones de la barrera de forma segura + var halfWidth = Math.Max(barrera.Width / 2f, 0.01f); // Mínimo 1cm + + float minDistance = float.MaxValue; + var botellasDetectadas = new List(); + + // Procesar TODAS las botellas (no solo las en contacto físico) + foreach (var botella in todasLasBotellas) + { + try + { + if (botella == null || !simulation.Bodies.BodyExists(botella.BodyHandle)) + continue; + + var botellaBody = simulation.Bodies[botella.BodyHandle]; + var botellaPosition = botellaBody.Pose.Position; + + // Validar posición de la botella + if (float.IsNaN(botellaPosition.X) || float.IsNaN(botellaPosition.Y) || float.IsNaN(botellaPosition.Z)) + { + continue; + } + + // CLAVE: Crear la línea del haz en el plano XY a la altura del centro de la botella + var bottleZ = botellaPosition.Z; // Altura actual del centro de la botella + + // Puntos de la línea del haz en el plano XY a la altura de la botella + var localStart = new Vector3(-halfWidth, 0, 0); + var localEnd = new Vector3(halfWidth, 0, 0); + + // Transformar a coordenadas mundiales pero en el plano Z de la botella + var worldStartXY = barrierPosition + Vector3.Transform(localStart, barrierOrientation); + var worldEndXY = barrierPosition + Vector3.Transform(localEnd, barrierOrientation); + + // Ajustar Z para que esté en el plano de la botella + worldStartXY = new Vector3(worldStartXY.X, worldStartXY.Y, bottleZ); + worldEndXY = new Vector3(worldEndXY.X, worldEndXY.Y, bottleZ); + + // Calcular distancia desde el centro de la botella a la línea del haz + var closestPoint = ProjectPointOntoLine(botellaPosition, worldStartXY, worldEndXY); + var distance = Vector3.Distance(closestPoint, botellaPosition); + + // Validar distancia calculada + if (float.IsNaN(distance) || float.IsInfinity(distance)) + { + continue; + } + + // Actualizar distancia mínima + if (distance < minDistance) + { + minDistance = distance; + } + + // NUEVO: Verificar LuzCortada usando radio completo + if (distance <= botella.Radius) + { + barrera.LuzCortada = true; + botellasDetectadas.Add(botella); + } + + // Verificar detección de cuello usando radio/2 (como antes) + if (barrera.DetectNeck && botella.Radius > 0) + { + var neckRadius = botella.Radius / 2f; + if (distance <= neckRadius) + { + barrera.LuzCortadaNeck = true; + // No hacer break aquí - queremos procesar todas las botellas para LuzCortada + } + } + } + catch (Exception ex) + { + // Error processing bottle - continue + } + } + + // Asignar resultados de forma segura + barrera.Distancia = minDistance == float.MaxValue ? 0f : minDistance; + + // Actualizar lista de botellas detectadas + if (barrera.ListSimBotellaContact != null) + { + barrera.ListSimBotellaContact.AddRange(botellasDetectadas); + } + } + catch (Exception ex) + { + // En caso de error, asignar valores seguros + if (barrera != null) + { + barrera.Distancia = float.MaxValue; + barrera.LuzCortada = false; + barrera.LuzCortadaNeck = false; + } + } + } + + /// + /// Procesa contactos de descarte para eliminar botellas marcadas usando detección geométrica + /// + private void ProcessDescarteContacts() + { + try + { + // Obtener todos los descartes y botellas + var descartes = Cuerpos.OfType().ToList(); + var botellas = Cuerpos.OfType().ToList(); + + foreach (var descarte in descartes) + { + try + { + // Verificar que el descarte aún existe en la simulación + if (descarte == null || !simulation?.Bodies?.BodyExists(descarte.BodyHandle) == true) + continue; + + // Limpiar la lista de forma segura + if (descarte.ListSimBotellaContact != null) + { + descarte.ListSimBotellaContact.Clear(); + } + + // Calcular detección usando geometría pura para TODAS las botellas + CalculateDescarteDetectionGeometric(descarte, botellas); + } + catch (Exception ex) + { + // Error processing descarte - continue + } + } + + // Eliminar botellas marcadas para eliminación (después de procesamiento) + RemoveMarkedBottles(); + + // Limpiar contactos ya que no los usamos más + lock (_contactsLock) + { + _descarteContacts.Clear(); + } + } + catch (Exception ex) + { + // Critical error in ProcessDescarte - continue + } + } + + /// + /// Calcula detección geométrica pura para un descarte específico contra todas las botellas + /// Usa detección de esfera contra esfera para determinar si hay contacto + /// + private void CalculateDescarteDetectionGeometric(simDescarte descarte, List todasLasBotellas) + { + try + { + // Validaciones de seguridad + if (descarte == null || todasLasBotellas == null || simulation?.Bodies == null) + return; + + if (!simulation.Bodies.BodyExists(descarte.BodyHandle)) + return; + + var descarteBody = simulation.Bodies[descarte.BodyHandle]; + var descartePosition = descarteBody.Pose.Position; + + // Validar valores de posición + if (float.IsNaN(descartePosition.X) || float.IsNaN(descartePosition.Y) || float.IsNaN(descartePosition.Z)) + { + return; + } + + var botellasDetectadas = new List(); + + // Procesar TODAS las botellas para detección geométrica + foreach (var botella in todasLasBotellas) + { + try + { + if (botella == null || !simulation.Bodies.BodyExists(botella.BodyHandle)) + continue; + + var botellaBody = simulation.Bodies[botella.BodyHandle]; + var botellaPosition = botellaBody.Pose.Position; + + // Validar posición de la botella + if (float.IsNaN(botellaPosition.X) || float.IsNaN(botellaPosition.Y) || float.IsNaN(botellaPosition.Z)) + { + continue; + } + + // Calcular distancia entre centros (detección esfera contra esfera) + var distance = Vector3.Distance(descartePosition, botellaPosition); + + // Validar distancia calculada + if (float.IsNaN(distance) || float.IsInfinity(distance)) + { + continue; + } + + // Verificar si las esferas se superponen + var totalRadius = descarte.Radius + botella.Radius; + if (distance <= totalRadius) + { + // Marcar botella para eliminación + botella.Descartar = true; + _botellasParaEliminar.Add(botella); + botellasDetectadas.Add(botella); + } + } + catch (Exception ex) + { + // Error processing bottle - continue + } + } + + // Actualizar lista de botellas detectadas + if (descarte.ListSimBotellaContact != null) + { + descarte.ListSimBotellaContact.AddRange(botellasDetectadas); + } + } + catch (Exception ex) + { + // Critical error in CalculateDescarteGeometric - continue + } + } + + /// + /// Elimina las botellas marcadas para eliminación de forma segura + /// + private void RemoveMarkedBottles() + { + try + { + List botellasAEliminar; + lock (_contactsLock) + { + botellasAEliminar = new List(_botellasParaEliminar); + _botellasParaEliminar.Clear(); + } + + foreach (var botella in botellasAEliminar) + { + try + { + if (botella != null && Cuerpos.Contains(botella)) + { + Remove(botella); + } + } + catch (Exception ex) + { + // Error removing bottle - continue + } + } + + // Botellas eliminadas: {botellasAEliminar.Count} + } + catch (Exception ex) + { + // Critical error in RemoveMarkedBottles - continue + } + } + + /// + /// Sistema de limpieza que elimina botellas que estén debajo de la altura de los transportes + /// Cualquier botella con Z menor al nivel superior de los transportes será eliminada + /// + private void ProcessCleanupSystem() + { + try + { + // Altura máxima de los transportes (nivel superior) + var maxTransportHeight = simBase.zPos_Transporte + simBase.zAltura_Transporte; + + // Obtener todas las botellas + var botellas = Cuerpos.OfType().ToList(); + + foreach (var botella in botellas) + { + try + { + // Validar que la botella aún existe en la simulación + if (botella == null || !simulation?.Bodies?.BodyExists(botella.BodyHandle) == true) + continue; + + var posicion = botella.GetPosition(); + + // Validar posición + if (float.IsNaN(posicion.Z) || float.IsInfinity(posicion.Z)) + continue; + + // Si la botella está debajo del nivel de los transportes, marcarla para eliminación + if (posicion.Z < (maxTransportHeight - 2 * botella.Radius) || posicion.Z > (maxTransportHeight + 2 * botella.Radius)) + { + lock (_contactsLock) + { + botella.Descartar = true; + _botellasParaEliminar.Add(botella); + } + } + } + catch (Exception ex) + { + // Error processing bottle in cleanup - continue + } + } + } + catch (Exception ex) + { + // Critical error in ProcessCleanupSystem - continue + } + } + + /// + /// Proyecta un punto sobre una línea definida por dos puntos + /// + private Vector3 ProjectPointOntoLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd) + { + var lineDirection = Vector3.Normalize(lineEnd - lineStart); + var pointToStart = point - lineStart; + var projectionLength = Vector3.Dot(pointToStart, lineDirection); + + // Restringir la proyección a los límites de la línea + var lineLength = Vector3.Distance(lineStart, lineEnd); + projectionLength = Math.Max(0, Math.Min(projectionLength, lineLength)); + + return lineStart + lineDirection * projectionLength; + } + + } +} \ No newline at end of file diff --git a/Simulacion/BEPUVisualization3D.cs b/Simulacion/BEPUVisualization3D.cs new file mode 100644 index 0000000..e79528d --- /dev/null +++ b/Simulacion/BEPUVisualization3D.cs @@ -0,0 +1,1090 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Windows.Media; +using System.Windows.Media.Media3D; +using BepuPhysics; +using BepuPhysics.Collidables; +using HelixToolkit.Wpf; +using System.Linq; +using System.Windows.Input; + +namespace CtrEditor.Simulacion +{ + /// + /// Estructura para almacenar las dimensiones de las formas para detectar cambios + /// + public struct ShapeDimensions + { + public float Width { get; set; } + public float Height { get; set; } + public float Length { get; set; } + public float Radius { get; set; } + public int ShapeType { get; set; } + + // ✅ NUEVO: Parámetros específicos para curvas + public float InnerRadius { get; set; } + public float OuterRadius { get; set; } + public float StartAngle { get; set; } + public float EndAngle { get; set; } + + public bool Equals(ShapeDimensions other) + { + return Math.Abs(Width - other.Width) < 0.001f && + Math.Abs(Height - other.Height) < 0.001f && + Math.Abs(Length - other.Length) < 0.001f && + Math.Abs(Radius - other.Radius) < 0.001f && + Math.Abs(InnerRadius - other.InnerRadius) < 0.001f && + Math.Abs(OuterRadius - other.OuterRadius) < 0.001f && + Math.Abs(StartAngle - other.StartAngle) < 0.001f && + Math.Abs(EndAngle - other.EndAngle) < 0.001f && + ShapeType == other.ShapeType; + } + } + + /// + /// Manager que sincroniza el mundo 3D de BEPU con la visualización HelixToolkit + /// Usa simBase como clave única para evitar problemas de reutilización de BodyHandle + /// + public class BEPUVisualization3DManager + { + private HelixViewport3D viewport3D; + private SimulationManagerBEPU simulationManager; + private Dictionary simBaseToModelMap; + private Dictionary lastKnownDimensions; + + public HelixViewport3D Viewport3D + { + get => viewport3D; + set => viewport3D = value; + } + + public BEPUVisualization3DManager(HelixViewport3D viewport, SimulationManagerBEPU simManager) + { + if (viewport == null) + throw new ArgumentNullException(nameof(viewport), "HelixViewport3D cannot be null"); + + if (simManager == null) + throw new ArgumentNullException(nameof(simManager), "SimulationManagerBEPU cannot be null"); + + viewport3D = viewport; + simulationManager = simManager; + simBaseToModelMap = new Dictionary(); + lastKnownDimensions = new Dictionary(); + + InitializeViewport(); + } + + private void InitializeViewport() + { + if (viewport3D == null) + { + System.Diagnostics.Debug.WriteLine($"[3D Init] ERROR: viewport3D is null"); + return; + } + + try + { + // Configurar la cámara usando el estado guardado + LoadCameraState(); + + // Agregar luces + var directionalLight = new DirectionalLight + { + Color = Colors.White, + Direction = new Vector3D(0, 0, -1) + }; + viewport3D.Children.Add(new ModelVisual3D { Content = directionalLight }); + + var ambientLight = new AmbientLight + { + Color = Color.FromRgb(64, 64, 64) + }; + viewport3D.Children.Add(new ModelVisual3D { Content = ambientLight }); + + // Suscribirse a eventos para detectar cambios en la cámara + SubscribeToCameraEvents(); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Init] ERROR initializing viewport: {ex.Message}"); + } + } + + /// + /// Sincroniza usando los objetos simBase del SimulationManager como fuente de verdad + /// Esto garantiza que cada objeto único tiene su propia visualización + /// + public void SynchronizeWorld() + { + // Validaciones críticas de null + if (simulationManager == null) + { + System.Diagnostics.Debug.WriteLine($"[3D Sync] ERROR: simulationManager is null"); + return; + } + + if (simulationManager.simulation == null) + { + System.Diagnostics.Debug.WriteLine($"[3D Sync] ERROR: simulation is null"); + return; + } + + if (viewport3D == null) + { + System.Diagnostics.Debug.WriteLine($"[3D Sync] ERROR: viewport3D is null"); + return; + } + + // Limpiar objetos que ya no existen + CleanupRemovedObjects(); + + // Sincronizar cada objeto simBase + foreach (var simObj in simulationManager.Cuerpos) + { + if (simObj == null || !simulationManager.simulation.Bodies.BodyExists(simObj.BodyHandle)) + continue; + + if (simBaseToModelMap.ContainsKey(simObj)) + { + // Actualizar visualización existente + UpdateVisualizationFromSimBase(simObj); + } + else + { + // Crear nueva visualización + CreateVisualizationFromSimBase(simObj); + } + } + } + + private void CleanupRemovedObjects() + { + if (simulationManager?.Cuerpos == null || viewport3D == null) + return; + + var objectsToRemove = new List(); + + try + { + foreach (var kvp in simBaseToModelMap) + { + var simObj = kvp.Key; + var model = kvp.Value; + + // Verificar si el objeto aún está en la lista de cuerpos activos + if (!simulationManager.Cuerpos.Contains(simObj) || + !simulationManager.simulation.Bodies.BodyExists(simObj.BodyHandle)) + { + if (model != null) + { + viewport3D.Children.Remove(model); + } + objectsToRemove.Add(simObj); + } + } + + foreach (var simObj in objectsToRemove) + { + simBaseToModelMap.Remove(simObj); + lastKnownDimensions.Remove(simObj); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Cleanup] ERROR during cleanup: {ex.Message}"); + } + } + + private void CreateVisualizationFromSimBase(simBase simObj) + { + if (simulationManager?.simulation == null || !simulationManager.simulation.Bodies.BodyExists(simObj.BodyHandle)) + { + System.Diagnostics.Debug.WriteLine($"[3D Create] ERROR: Body does not exist for simBase"); + return; + } + + try + { + var body = simulationManager.simulation.Bodies[simObj.BodyHandle]; + var collidable = body.Collidable; + var shapeIndex = collidable.Shape; + + if (!shapeIndex.Exists) + { + System.Diagnostics.Debug.WriteLine($"[3D Create] ERROR: Shape does not exist"); + return; + } + + ModelVisual3D visual3D = null; + + // Caso especial: simBotella usa esfera en BEPU pero se visualiza como cilindro + if (simObj is simBotella botella) + { + System.Diagnostics.Debug.WriteLine($"[3D Create] Creating CYLINDER visualization for simBotella (BEPU uses sphere)"); + visual3D = CreateCylinderVisualization(botella.Radius, botella.Height, simObj); + } + // Caso especial: simCurve usa múltiples triángulos pero se visualiza como superficie curva + else if (simObj is simCurve curve) + { + System.Diagnostics.Debug.WriteLine($"[3D Create] Creating CURVE visualization for simCurve"); + visual3D = CreateCurveVisualization(curve); + } + else + { + // Para otros objetos, usar la forma real de BEPU + var dimensions = GetShapeDimensions(shapeIndex, simObj); + System.Diagnostics.Debug.WriteLine($"[3D Create] Shape type: {dimensions.ShapeType}, Radius: {dimensions.Radius}"); + + if (dimensions.ShapeType == BepuPhysics.Collidables.Sphere.Id) + { + visual3D = CreateSphereVisualization(dimensions.Radius, simObj); + } + else if (dimensions.ShapeType == BepuPhysics.Collidables.Box.Id) + { + visual3D = CreateBoxVisualization(dimensions.Width, dimensions.Height, dimensions.Length, simObj); + } + else if (dimensions.ShapeType == BepuPhysics.Collidables.Cylinder.Id) + { + visual3D = CreateCylinderVisualization(dimensions.Radius, dimensions.Length, simObj); + } + else + { + System.Diagnostics.Debug.WriteLine($"[3D Create] WARNING: Unsupported shape type: {dimensions.ShapeType}"); + return; + } + } + + if (visual3D != null) + { + // Posicionar correctamente el objeto 3D + var position = simObj.GetPosition(); + var rotationZ = simObj.GetRotationZ(); + + var transform = new Transform3DGroup(); + transform.Children.Add(new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 0, 1), rotationZ * 180.0 / Math.PI))); + transform.Children.Add(new TranslateTransform3D(position.X, position.Y, position.Z)); + visual3D.Transform = transform; + + // Agregar a la vista 3D y asociar con simBase + viewport3D.Children.Add(visual3D); + simBaseToModelMap[simObj] = visual3D; + + System.Diagnostics.Debug.WriteLine($"[3D Create] Successfully created 3D visualization for {simObj.GetType().Name}"); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Create] ERROR: {ex.Message}"); + } + } + + private ModelVisual3D CreateSphereVisualization(float radius, simBase simObj = null) + { + var meshBuilder = new MeshBuilder(); + meshBuilder.AddSphere(new Point3D(0, 0, 0), (double)radius, 16, 16); + + Material material; + if (simObj is simDescarte) + { + // Material semi-transparente para descartes (85% transparente = 15% opacidad) + var descarteBrush = new SolidColorBrush(Color.FromRgb(255, 100, 255)); // Magenta + descarteBrush.Opacity = 0.15; // 15% opacidad = 85% transparencia + material = MaterialHelper.CreateMaterial( + descarteBrush, + specularPower: 60, + ambient: 120 + ); + } + else + { + // Material plástico brillante para botellas (rojo) + material = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(255, 80, 80)), + specularPower: 120, + ambient: 200 + ); + } + var model = new GeometryModel3D(meshBuilder.ToMesh(), material); + + return new ModelVisual3D { Content = model }; + } + + private ModelVisual3D CreateBoxVisualization(float width, float height, float length, simBase simObj = null) + { + var meshBuilder = new MeshBuilder(); + meshBuilder.AddBox(new Point3D(0, 0, 0), (double)width, (double)height, (double)length); + + // Determinar el color según el tipo de simBase y crear material plástico brillante + Material material; + if (simObj is simGuia) + { + // Material plástico brillante negro para guías + material = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(50, 50, 50)), + specularPower: 150, + ambient: 180 + ); + } + else if (simObj is simBarrera) + { + // Material semi-transparente amarillo para barreras (haz de luz) + var yellowBrush = new SolidColorBrush(Color.FromRgb(255, 255, 0)); + yellowBrush.Opacity = 0.3; // 30% opacidad para simular haz de luz + material = MaterialHelper.CreateMaterial( + yellowBrush, + specularPower: 50, + ambient: 150 + ); + } + else if (simObj is simTransporte) + { + // Material plástico brillante verde para transportes + material = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(80, 180, 80)), + specularPower: 140, + ambient: 200 + ); + } + else + { + // Material plástico brillante gris por defecto + material = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(128, 128, 128)), + specularPower: 100, + ambient: 190 + ); + } + var model = new GeometryModel3D(meshBuilder.ToMesh(), material); + + return new ModelVisual3D { Content = model }; + } + + private ModelVisual3D CreateCapsuleVisualization(float radius, float length) + { + var meshBuilder = new MeshBuilder(); + meshBuilder.AddCylinder(new Point3D(0, 0, -length / 2), new Point3D(0, 0, length / 2), radius, 16); + + // Material plástico brillante para cápsulas (amarillo) + var material = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(255, 255, 80)), + specularPower: 125, + ambient: 210 + ); + var model = new GeometryModel3D(meshBuilder.ToMesh(), material); + + return new ModelVisual3D { Content = model }; + } + + private ModelVisual3D CreateCylinderVisualization(float radius, float length, simBase simObj = null) + { + var meshBuilder = new MeshBuilder(); + + // Crear cilindro con tapas usando la versión mejorada + var p1 = new Point3D(0, 0, -length / 2); + var p2 = new Point3D(0, 0, length / 2); + meshBuilder.AddCylinder(p1, p2, radius: (double)radius, thetaDiv: 16, cap1: true, cap2: true); + + // Determinar el color según el tipo de simBase - Material plástico con reflexión (valores originales) + Material material; + if (simObj is simBotella) + { + // Material plástico brillante rojo para botellas (cilindros) + material = MaterialHelper.CreateMaterial( + new SolidColorBrush(Colors.Red), + specularPower: 120, + ambient: 200 + ); + } + else if (simObj is simTransporte) + { + // Material plástico verde para transportes (valores originales) + material = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(80, 180, 80)), + specularPower: 140, + ambient: 200 + ); + } + else if (simObj is simGuia) + { + // Material plástico gris oscuro para guías (valores originales) + material = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(50, 50, 50)), + specularPower: 150, + ambient: 180 + ); + } + else if (simObj is simBarrera) + { + // Material semi-transparente amarillo para barreras (haz de luz) + var yellowBrush = new SolidColorBrush(Color.FromRgb(255, 255, 0)); + yellowBrush.Opacity = 0.3; // 30% opacidad para simular haz de luz + material = MaterialHelper.CreateMaterial( + yellowBrush, + specularPower: 50, + ambient: 150 + ); + } + else + { + // Material plástico gris estándar para objetos no identificados (valores originales) + material = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(128, 128, 128)), + specularPower: 100, + ambient: 190 + ); + } + + var geometry = meshBuilder.ToMesh(); + var model = new GeometryModel3D(geometry, material); + var visual = new ModelVisual3D(); + visual.Content = model; + + return visual; + } + + private ModelVisual3D CreateCurveVisualization(simCurve curve) + { + try + { + var meshBuilder = new MeshBuilder(); + + // ✅ NUEVO: Usar directamente los triángulos originales de BEPU (debug real) + CreateCurveMeshFromBEPUTriangles(meshBuilder, curve); + + // Material plástico brillante para curvas (azul verdoso) + var material = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(80, 150, 200)), + specularPower: 130, + ambient: 190 + ); + + var geometry = meshBuilder.ToMesh(); + var model = new GeometryModel3D(geometry, material); + var visual = new ModelVisual3D(); + visual.Content = model; + + return visual; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D CreateCurve] Error creating curve visualization: {ex.Message}"); + return null; + } + } + + /// + /// ✅ NUEVO: Crea mesh directamente desde los triángulos de BEPU (debug real) + /// + private void CreateCurveMeshFromBEPUTriangles(MeshBuilder meshBuilder, simCurve curve) + { + try + { + // Obtener los triángulos originales directamente de BEPU + var originalTriangles = curve.GetOriginalTriangles(); + + System.Diagnostics.Debug.WriteLine($"[3D Debug] Creating curve mesh from {originalTriangles.Count} BEPU triangles"); + + if (originalTriangles.Count == 0) + { + System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: No triangles found in BEPU curve"); + return; + } + + // Altura para simular triángulos planos (misma que en BEPU) + const float curveHeight = 0.05f; // simCurve.zAltura_Curve + + // Convertir cada triángulo de BEPU a triángulos en Helix + foreach (var triangle in originalTriangles) + { + if (triangle.Count != 3) + { + System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: Invalid triangle with {triangle.Count} vertices"); + continue; + } + + // Convertir Vector3 de BEPU a Point3D de Helix + // Los triángulos en BEPU están en el plano XY (Z=0), crear superficie 3D + + // Puntos de la superficie inferior (Z = 0) + var p1Bottom = new Point3D(triangle[0].X, triangle[0].Y, 0); + var p2Bottom = new Point3D(triangle[1].X, triangle[1].Y, 0); + var p3Bottom = new Point3D(triangle[2].X, triangle[2].Y, 0); + + // Puntos de la superficie superior (Z = curveHeight) + var p1Top = new Point3D(triangle[0].X, triangle[0].Y, curveHeight); + var p2Top = new Point3D(triangle[1].X, triangle[1].Y, curveHeight); + var p3Top = new Point3D(triangle[2].X, triangle[2].Y, curveHeight); + + // Crear superficie superior del triángulo + meshBuilder.AddTriangle(p1Top, p2Top, p3Top); + + // Crear superficie inferior del triángulo (orden inverso para normales correctas) + meshBuilder.AddTriangle(p1Bottom, p3Bottom, p2Bottom); + + // Crear paredes laterales del triángulo (3 quads) + meshBuilder.AddQuad(p1Bottom, p2Bottom, p2Top, p1Top); // Lado 1-2 + meshBuilder.AddQuad(p2Bottom, p3Bottom, p3Top, p2Top); // Lado 2-3 + meshBuilder.AddQuad(p3Bottom, p1Bottom, p1Top, p3Top); // Lado 3-1 + } + + System.Diagnostics.Debug.WriteLine($"[3D Debug] Successfully created mesh from BEPU triangles"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR creating mesh from BEPU triangles: {ex.Message}"); + + // Fallback: usar el método anterior si falla la lectura de BEPU + System.Diagnostics.Debug.WriteLine($"[3D Debug] Falling back to recreated geometry"); + CreateCurveMeshFallback(meshBuilder, curve); + } + } + + /// + /// Método fallback que recrea la geometría (mantener por compatibilidad) + /// + private void CreateCurveMeshFallback(MeshBuilder meshBuilder, simCurve curve) + { + // Obtener parámetros de la curva + float innerRadius = curve.InnerRadius; + float outerRadius = curve.OuterRadius; + float startAngle = curve.StartAngle; + float endAngle = curve.EndAngle; + + System.Diagnostics.Debug.WriteLine($"[3D Fallback] Parameters - Inner: {innerRadius}, Outer: {outerRadius}, Start: {startAngle}, End: {endAngle}"); + + // Configuración de segmentos + const float SegmentationFactor = 32f / 3f; + const int MinSegments = 8; + const int MaxSegments = 64; + + // Calcular número de segmentos basado en el tamaño del arco + float arcLength = (endAngle - startAngle) * ((innerRadius + outerRadius) / 2f); + int segments = (int)(arcLength * SegmentationFactor); + segments = Math.Max(MinSegments, Math.Min(segments, MaxSegments)); + + float angleStep = (endAngle - startAngle) / segments; + + // Altura muy pequeña para simular triángulos planos + const float curveHeight = 0.05f; + + // Generar vértices para el arco interior y exterior + var innerBottomPoints = new Point3D[segments + 1]; + var innerTopPoints = new Point3D[segments + 1]; + var outerBottomPoints = new Point3D[segments + 1]; + var outerTopPoints = new Point3D[segments + 1]; + + for (int i = 0; i <= segments; i++) + { + float angle = startAngle + i * angleStep; + float cosAngle = (float)Math.Cos(angle); + float sinAngle = (float)Math.Sin(angle); + + // Puntos en la parte inferior (Z = 0) + innerBottomPoints[i] = new Point3D(innerRadius * cosAngle, innerRadius * sinAngle, 0); + outerBottomPoints[i] = new Point3D(outerRadius * cosAngle, outerRadius * sinAngle, 0); + + // Puntos en la parte superior (Z = curveHeight) + innerTopPoints[i] = new Point3D(innerRadius * cosAngle, innerRadius * sinAngle, curveHeight); + outerTopPoints[i] = new Point3D(outerRadius * cosAngle, outerRadius * sinAngle, curveHeight); + } + + // Crear la superficie superior de la curva + for (int i = 0; i < segments; i++) + { + meshBuilder.AddTriangle(innerTopPoints[i], outerTopPoints[i], outerTopPoints[i + 1]); + meshBuilder.AddTriangle(innerTopPoints[i], outerTopPoints[i + 1], innerTopPoints[i + 1]); + } + + // Crear la superficie inferior de la curva + for (int i = 0; i < segments; i++) + { + meshBuilder.AddTriangle(innerBottomPoints[i], outerBottomPoints[i + 1], outerBottomPoints[i]); + meshBuilder.AddTriangle(innerBottomPoints[i], innerBottomPoints[i + 1], outerBottomPoints[i + 1]); + } + + // Crear las paredes laterales + for (int i = 0; i < segments; i++) + { + meshBuilder.AddQuad(innerBottomPoints[i], innerBottomPoints[i + 1], innerTopPoints[i + 1], innerTopPoints[i]); + meshBuilder.AddQuad(outerBottomPoints[i], outerTopPoints[i], outerTopPoints[i + 1], outerBottomPoints[i + 1]); + } + + // Crear las paredes de los extremos + meshBuilder.AddQuad(innerBottomPoints[0], innerTopPoints[0], outerTopPoints[0], outerBottomPoints[0]); + meshBuilder.AddQuad(outerBottomPoints[segments], outerTopPoints[segments], innerTopPoints[segments], innerBottomPoints[segments]); + } + + private void UpdateVisualizationFromSimBase(simBase simObj) + { + if (!simBaseToModelMap.TryGetValue(simObj, out var visual)) + return; + + if (simulationManager?.simulation == null || visual == null) + return; + + try + { + var body = simulationManager.simulation.Bodies[simObj.BodyHandle]; + var pose = body.Pose; + var collidable = body.Collidable; + + // Verificar si las dimensiones han cambiado + var currentDimensions = GetShapeDimensions(collidable.Shape, simObj); + bool dimensionsChanged = false; + + if (lastKnownDimensions.TryGetValue(simObj, out var lastDimensions)) + { + dimensionsChanged = !currentDimensions.Equals(lastDimensions); + + // ✅ LOGGING: Debug para curvas + if (simObj is simCurve && dimensionsChanged) + { + System.Diagnostics.Debug.WriteLine($"[3D Update] CURVE dimensions changed! Old: Inner={lastDimensions.InnerRadius}, Outer={lastDimensions.OuterRadius}, Start={lastDimensions.StartAngle}, End={lastDimensions.EndAngle}"); + System.Diagnostics.Debug.WriteLine($"[3D Update] CURVE dimensions changed! New: Inner={currentDimensions.InnerRadius}, Outer={currentDimensions.OuterRadius}, Start={currentDimensions.StartAngle}, End={currentDimensions.EndAngle}"); + } + } + else + { + dimensionsChanged = true; // Primera vez + System.Diagnostics.Debug.WriteLine($"[3D Update] First time creating geometry for {simObj.GetType().Name}"); + } + + // Si las dimensiones cambiaron, recrear la geometría + if (dimensionsChanged) + { + System.Diagnostics.Debug.WriteLine($"[3D Update] Recreating geometry for {simObj.GetType().Name}"); + RecreateVisualizationGeometry(simObj, visual, currentDimensions); + lastKnownDimensions[simObj] = currentDimensions; + } + else + { + // System.Diagnostics.Debug.WriteLine($"[3D Update] No geometry changes needed for {simObj.GetType().Name}"); + } + + // Actualizar transformación del modelo 3D + var transform = new Transform3DGroup(); + + // ✅ ORDEN CORRECTO: Primero Rotación, luego Traslación + // Esto mantiene el pivot consistente con WPF (Top-Left) + + // 1. Rotación (primero, en el origen) + var rotation = new QuaternionRotation3D(new System.Windows.Media.Media3D.Quaternion( + (double)pose.Orientation.X, + (double)pose.Orientation.Y, + (double)pose.Orientation.Z, + (double)pose.Orientation.W + )); + var rotationTransform = new RotateTransform3D(rotation); + transform.Children.Add(rotationTransform); + + // 2. Traslación (después, mueve el objeto ya rotado) + var translation = new TranslateTransform3D( + (double)pose.Position.X, + (double)pose.Position.Y, + (double)pose.Position.Z + ); + transform.Children.Add(translation); + + visual.Transform = transform; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Update] ERROR updating visualization: {ex.Message}"); + } + } + + private ShapeDimensions GetShapeDimensions(TypedIndex shapeIndex, simBase simObj = null) + { + var dimensions = new ShapeDimensions(); + + if (!shapeIndex.Exists) + return dimensions; + + dimensions.ShapeType = shapeIndex.Type; + + try + { + // ✅ CASO ESPECIAL: Para simCurve, obtener dimensiones específicas de la curva + if (simObj is simCurve curve) + { + dimensions.InnerRadius = curve.InnerRadius; + dimensions.OuterRadius = curve.OuterRadius; + dimensions.StartAngle = curve.StartAngle; + dimensions.EndAngle = curve.EndAngle; + dimensions.ShapeType = -1; // Tipo especial para curvas + System.Diagnostics.Debug.WriteLine($"[3D GetDimensions] Curve - Inner: {curve.InnerRadius}, Outer: {curve.OuterRadius}, Start: {curve.StartAngle}, End: {curve.EndAngle}"); + return dimensions; + } + + // ✅ CASO ESPECIAL: Para simBotella, obtener dimensiones del cilindro visual + if (simObj is simBotella botella) + { + dimensions.Radius = botella.Radius; + dimensions.Height = botella.Height; + dimensions.ShapeType = -2; // Tipo especial para botellas (cilindro visual) + return dimensions; + } + + // Para otros objetos, usar la forma real de BEPU + if (shapeIndex.Type == BepuPhysics.Collidables.Sphere.Id) + { + var sphere = simulationManager.simulation.Shapes.GetShape(shapeIndex.Index); + dimensions.Radius = sphere.Radius; + } + else if (shapeIndex.Type == BepuPhysics.Collidables.Box.Id) + { + var box = simulationManager.simulation.Shapes.GetShape(shapeIndex.Index); + dimensions.Width = box.Width; + dimensions.Height = box.Height; + dimensions.Length = box.Length; + } + else if (shapeIndex.Type == BepuPhysics.Collidables.Cylinder.Id) + { + var cylinder = simulationManager.simulation.Shapes.GetShape(shapeIndex.Index); + dimensions.Radius = cylinder.Radius; + dimensions.Length = cylinder.Length; // En BEPU, Length es la altura del cilindro + } + else if (shapeIndex.Type == BepuPhysics.Collidables.Capsule.Id) + { + var capsule = simulationManager.simulation.Shapes.GetShape(shapeIndex.Index); + dimensions.Radius = capsule.Radius; + dimensions.Length = capsule.Length; + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D GetDimensions] ERROR getting dimensions: {ex.Message}"); + } + + return dimensions; + } + + private void RecreateVisualizationGeometry(simBase simObj, ModelVisual3D visual, ShapeDimensions dimensions) + { + if (visual?.Content is not GeometryModel3D geometryModel) + return; + + try + { + MeshGeometry3D newGeometry = null; + Material newMaterial = null; + + // Caso especial: simBotella usa esfera en BEPU pero se visualiza como cilindro + if (simObj is simBotella botella) + { + var meshBuilder = new MeshBuilder(); + + // Crear cilindro con tapas usando la versión mejorada + var p1 = new Point3D(0, 0, -botella.Height / 2); + var p2 = new Point3D(0, 0, botella.Height / 2); + meshBuilder.AddCylinder(p1, p2, radius: (double)botella.Radius, thetaDiv: 16, cap1: true, cap2: true); + + newGeometry = meshBuilder.ToMesh(); + + // Material específico para botellas - rojo brillante + newMaterial = MaterialHelper.CreateMaterial( + new SolidColorBrush(Colors.Red), + specularPower: 120, + ambient: 200 + ); + } + // Caso especial: simCurve necesita recreación completa de geometría + else if (simObj is simCurve curve) + { + System.Diagnostics.Debug.WriteLine($"[3D Recreate] Creating CURVE mesh from BEPU triangles - Inner: {curve.InnerRadius}, Outer: {curve.OuterRadius}, Start: {curve.StartAngle}, End: {curve.EndAngle}"); + + var meshBuilder = new MeshBuilder(); + + // ✅ NUEVO: Usar directamente los triángulos de BEPU (debug real) + CreateCurveMeshFromBEPUTriangles(meshBuilder, curve); + + newGeometry = meshBuilder.ToMesh(); + + // Material específico para curvas - azul verdoso brillante + newMaterial = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(80, 150, 200)), + specularPower: 130, + ambient: 190 + ); + + System.Diagnostics.Debug.WriteLine($"[3D Recreate] CURVE mesh created successfully with {newGeometry?.Positions?.Count ?? 0} vertices"); + } + else if (dimensions.ShapeType == BepuPhysics.Collidables.Sphere.Id) + { + var meshBuilder = new MeshBuilder(); + meshBuilder.AddSphere(new Point3D(0, 0, 0), dimensions.Radius); + newGeometry = meshBuilder.ToMesh(); + + // Color según tipo de objeto - Material plástico con reflexión (valores originales) + if (simObj is simDescarte) + { + // Material semi-transparente para descartes (85% transparente = 15% opacidad) + var descarteBrush = new SolidColorBrush(Color.FromRgb(255, 100, 255)); // Magenta + descarteBrush.Opacity = 0.15; // 15% opacidad = 85% transparencia + newMaterial = MaterialHelper.CreateMaterial( + descarteBrush, + specularPower: 60, + ambient: 120 + ); + } + else if (simObj is simBotella) + { + // Material plástico brillante rojo para botellas + newMaterial = MaterialHelper.CreateMaterial( + new SolidColorBrush(Colors.Red), + specularPower: 120, + ambient: 200 + ); + } + else if (simObj is simTransporte) + { + // Material plástico verde para transportes (valores originales) + newMaterial = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(80, 180, 80)), + specularPower: 140, + ambient: 200 + ); + } + else if (simObj is simGuia) + { + // Material plástico gris oscuro para guías (valores originales) + newMaterial = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(50, 50, 50)), + specularPower: 150, + ambient: 180 + ); + } + else if (simObj is simBarrera) + { + // Material semi-transparente amarillo para barreras (haz de luz) + var yellowBrush = new SolidColorBrush(Color.FromRgb(255, 255, 0)); + yellowBrush.Opacity = 0.3; // 30% opacidad para simular haz de luz + newMaterial = MaterialHelper.CreateMaterial( + yellowBrush, + specularPower: 50, + ambient: 150 + ); + } + else + { + // Material plástico gris estándar para otras esferas (valores originales) + newMaterial = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(128, 128, 128)), + specularPower: 100, + ambient: 190 + ); + } + } + else if (dimensions.ShapeType == BepuPhysics.Collidables.Box.Id) + { + var meshBuilder = new MeshBuilder(); + meshBuilder.AddBox(new Point3D(0, 0, 0), dimensions.Width, dimensions.Height, dimensions.Length); + newGeometry = meshBuilder.ToMesh(); + + // Color según tipo de objeto - Material plástico con reflexión (valores originales) + if (simObj is simTransporte) + { + // Material plástico verde para transportes (valores originales) + newMaterial = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(80, 180, 80)), + specularPower: 140, + ambient: 200 + ); + } + else if (simObj is simGuia) + { + // Material plástico gris oscuro para guías (valores originales) + newMaterial = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(50, 50, 50)), + specularPower: 150, + ambient: 180 + ); + } + else if (simObj is simBarrera) + { + // Material semi-transparente amarillo para barreras (haz de luz) + var yellowBrush = new SolidColorBrush(Color.FromRgb(255, 255, 0)); + yellowBrush.Opacity = 0.3; // 30% opacidad para simular haz de luz + newMaterial = MaterialHelper.CreateMaterial( + yellowBrush, + specularPower: 50, + ambient: 150 + ); + } + else + { + // Material plástico gris estándar para otros boxes (valores originales) + newMaterial = MaterialHelper.CreateMaterial( + new SolidColorBrush(Color.FromRgb(128, 128, 128)), + specularPower: 100, + ambient: 190 + ); + } + } + else if (dimensions.ShapeType == BepuPhysics.Collidables.Cylinder.Id) + { + var meshBuilder = new MeshBuilder(); + + // Crear cilindro con tapas usando la versión mejorada + var p1 = new Point3D(0, 0, -dimensions.Length / 2); + var p2 = new Point3D(0, 0, dimensions.Length / 2); + meshBuilder.AddCylinder(p1, p2, radius: (double)dimensions.Radius, thetaDiv: 16, cap1: true, cap2: true); + + newGeometry = meshBuilder.ToMesh(); + + newMaterial = MaterialHelper.CreateMaterial( + new SolidColorBrush(Colors.Green), + specularPower: 135, + ambient: 205 + ); + } + + if (newGeometry != null) + { + geometryModel.Geometry = newGeometry; + geometryModel.Material = newMaterial; + System.Diagnostics.Debug.WriteLine($"[3D Recreate] Successfully recreated geometry for {simObj.GetType().Name}"); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Recreate] ERROR recreating geometry: {ex.Message}"); + } + } + + public void Clear() + { + foreach (var model in simBaseToModelMap.Values) + { + viewport3D.Children.Remove(model); + } + simBaseToModelMap.Clear(); + lastKnownDimensions.Clear(); + } + + public void SetCameraView(CameraView view) + { + if (viewport3D?.Camera is not PerspectiveCamera camera) return; + + switch (view) + { + case CameraView.Top: + camera.Position = new Point3D(0, 0, 10); + camera.LookDirection = new Vector3D(0, 0, -1); + camera.UpDirection = new Vector3D(0, 1, 0); + break; + case CameraView.Side: + camera.Position = new Point3D(10, 0, 0); + camera.LookDirection = new Vector3D(-1, 0, 0); + camera.UpDirection = new Vector3D(0, 0, 1); + break; + case CameraView.Front: + camera.Position = new Point3D(0, 10, 0); + camera.LookDirection = new Vector3D(0, -1, 0); + camera.UpDirection = new Vector3D(0, 0, 1); + break; + case CameraView.Isometric: + camera.Position = new Point3D(7, 7, 7); + camera.LookDirection = new Vector3D(-1, -1, -1); + camera.UpDirection = new Vector3D(0, 0, 1); + break; + } + } + + /// + /// Carga el estado guardado de la cámara desde EstadoPersistente + /// + private void LoadCameraState() + { + try + { + var cameraSettings = EstadoPersistente.Instance.Camera; + + viewport3D.Camera = new PerspectiveCamera + { + Position = new Point3D(cameraSettings.PositionX, cameraSettings.PositionY, cameraSettings.PositionZ), + LookDirection = new Vector3D(cameraSettings.LookDirectionX, cameraSettings.LookDirectionY, cameraSettings.LookDirectionZ), + UpDirection = new Vector3D(cameraSettings.UpDirectionX, cameraSettings.UpDirectionY, cameraSettings.UpDirectionZ), + FieldOfView = cameraSettings.FieldOfView + }; + + System.Diagnostics.Debug.WriteLine($"[3D Camera] Loaded camera state from persistent storage"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Camera] ERROR loading camera state: {ex.Message}"); + // Fallback a configuración por defecto + viewport3D.Camera = new PerspectiveCamera + { + LookDirection = new Vector3D(-3.86, 18.1, -10), + UpDirection = new Vector3D(-0.1, 0.48, 0.87), + Position = new Point3D(3.86, -18.13, 10), + FieldOfView = 60 + }; + } + } + + /// + /// Guarda el estado actual de la cámara en EstadoPersistente + /// + private void SaveCameraState() + { + try + { + if (viewport3D?.Camera is PerspectiveCamera camera) + { + var cameraSettings = EstadoPersistente.Instance.Camera; + + cameraSettings.PositionX = camera.Position.X; + cameraSettings.PositionY = camera.Position.Y; + cameraSettings.PositionZ = camera.Position.Z; + cameraSettings.LookDirectionX = camera.LookDirection.X; + cameraSettings.LookDirectionY = camera.LookDirection.Y; + cameraSettings.LookDirectionZ = camera.LookDirection.Z; + cameraSettings.UpDirectionX = camera.UpDirection.X; + cameraSettings.UpDirectionY = camera.UpDirection.Y; + cameraSettings.UpDirectionZ = camera.UpDirection.Z; + cameraSettings.FieldOfView = camera.FieldOfView; + + EstadoPersistente.Instance.GuardarEstado(); + System.Diagnostics.Debug.WriteLine($"[3D Camera] Saved camera state to persistent storage"); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Camera] ERROR saving camera state: {ex.Message}"); + } + } + + /// + /// Se suscribe a eventos del viewport para detectar cambios en la cámara + /// + private void SubscribeToCameraEvents() + { + try + { + // Suscribirse a eventos del viewport para detectar cambios en la cámara + viewport3D.CameraChanged += (sender, e) => SaveCameraState(); + + // También suscribirse a eventos de mouse para detectar interacciones + viewport3D.MouseUp += (sender, e) => SaveCameraState(); + viewport3D.MouseWheel += (sender, e) => SaveCameraState(); + + System.Diagnostics.Debug.WriteLine($"[3D Camera] Subscribed to camera change events"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[3D Camera] ERROR subscribing to camera events: {ex.Message}"); + } + } + } + + public enum CameraView + { + Top, + Side, + Front, + Isometric + } +} \ No newline at end of file diff --git a/Simulacion/FPhysics.cs b/Simulacion/FPhysics.cs deleted file mode 100644 index cf81ff3..0000000 --- a/Simulacion/FPhysics.cs +++ /dev/null @@ -1,641 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows.Controls; -using System.Windows.Media; -using System.Windows.Shapes; -using FarseerPhysics.Dynamics; -using FarseerPhysics.Factories; -using FarseerPhysics.Collision.Shapes; -using nkast.Aether.Physics2D.Common; - -using FarseerPhysics.Common; -using System.Windows; -using System.Diagnostics; - -using FarseerPhysics.Dynamics.Joints; -using CtrEditor.ObjetosSim; -using System.Windows.Documents; - -namespace CtrEditor.Simulacion -{ - public class simBase - { - public Body Body { get; protected set; } - public World _world; - - public void RemoverBody() - { - if (Body != null) - { - _world.RemoveBody(Body); - } - } - public void SetPosition(float x, float y) - { - Body.SetTransform(new Vector2(x, y), Body.Rotation); - } - public void SetPosition(Vector2 centro) - { - Body.SetTransform(centro, Body.Rotation); - } - } - - public class simCurve : simBase - { - private float _innerRadius; - private float _outerRadius; - private float _startAngle; - private float _endAngle; - public float Speed { get; set; } // Velocidad para efectos de cinta transportadora - - public simCurve(World world, float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 position) - { - _world = world; - _innerRadius = innerRadius; - _outerRadius = outerRadius; - _startAngle = MathHelper.ToRadians(startAngle); - _endAngle = MathHelper.ToRadians(endAngle); - Create(position); - } - - public void Create(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 position) - { - if (_world == null) return; - _innerRadius = innerRadius; - _outerRadius = outerRadius; - _startAngle = MathHelper.ToRadians(startAngle); - _endAngle = MathHelper.ToRadians(endAngle); - Create(position); - } - - public void Create(Vector2 position) - { - RemoverBody(); - - // Crear la geometría del sensor de curva - List segments = CreateCurveVertices(_innerRadius, _outerRadius, _startAngle, _endAngle); - Body = new Body(_world); - foreach (var segment in segments) - { - var shape = new PolygonShape(segment, 1f); - var fixture = Body.CreateFixture(shape); - fixture.IsSensor = true; - } - Body.Position = position; - Body.BodyType = BodyType.Static; - Body.UserData = this; - } - - public void SetSpeed(float speed) - { - Speed = speed; - } - - private List CreateCurveVertices(float innerRadius, float outerRadius, float startAngle, float endAngle) - { - List verticesList = new List(); - int segments = 32; - float angleStep = (endAngle - startAngle) / segments; - - Vertices innerVertices = new Vertices(); - Vertices outerVertices = new Vertices(); - - for (int i = 0; i <= segments; i++) - { - float angle = startAngle + i * angleStep; - innerVertices.Add(new Vector2(innerRadius * (float)Math.Cos(angle), innerRadius * (float)Math.Sin(angle))); - outerVertices.Add(new Vector2(outerRadius * (float)Math.Cos(angle), outerRadius * (float)Math.Sin(angle))); - } - - outerVertices.Reverse(); - innerVertices.AddRange(outerVertices); - verticesList.Add(innerVertices); - - return verticesList; - } - - public void ApplyCurveEffect(Fixture bottle) - { - Vector2 centerToBottle = bottle.Body.Position - Body.Position; - float distanceToCenter = centerToBottle.Length(); - - if (distanceToCenter >= _innerRadius && distanceToCenter <= _outerRadius) - { - // Calcular la velocidad tangencial - float speedMetersPerSecond = Speed / 60.0f; - float angularVelocity = speedMetersPerSecond / distanceToCenter; - - // Vector tangente (perpendicular al radio) - Vector2 tangent = new Vector2(-centerToBottle.Y, centerToBottle.X); - tangent.Normalize(); - - // Velocidad deseada - Vector2 desiredVelocity = tangent * angularVelocity * distanceToCenter; - bottle.Body.LinearVelocity = desiredVelocity; - } - } - } - - - public class simDescarte : simBase - { - private float _radius; - - public simDescarte(World world, float diameter, Vector2 position) - { - _world = world; - _radius = diameter / 2; - Create(position); - } - - public void SetDiameter(float diameter) - { - _radius = diameter / 2; - Create(Body.Position); // Recrear el círculo con el nuevo tamaño - } - - public void Create(Vector2 position) - { - RemoverBody(); - Body = BodyFactory.CreateCircle(_world, _radius, 1f, position); - - Body.FixtureList[0].IsSensor = true; - Body.BodyType = BodyType.Static; - Body.UserData = this; // Importante para la identificación durante la colisión - } - } - - - public class simTransporte : simBase - { - public float Speed { get; set; } // Velocidad para efectos de cinta transportadora - public float DistanceGuide2Guide { get; set; } - public bool TransportWithGuides = false; - - public simTransporte(World world, float width, float height, Vector2 position, float angle = 0) - { - _world = world; - Create(width, height, position, angle); - } - - public float Angle - { - get { return MathHelper.ToDegrees(Body.Rotation); } - set { Body.Rotation = MathHelper.ToRadians(value); } - } - - public new void SetPosition(float x, float y) - { - Body.Position = new Vector2(x, y); - } - - public void SetSpeed(float speed) - { - Speed = speed; - } - - public void SetDimensions(float width, float height) - { - Body.DestroyFixture(Body.FixtureList[0]); - - var newShape = new PolygonShape(PolygonTools.CreateRectangle(width / 2, height / 2), 1f); - Body.CreateFixture(newShape); - } - public void Create(float width, float height, Vector2 position, float angle = 0) - { - RemoverBody(); - Body = BodyFactory.CreateRectangle(_world, width, height, 1f, position); - Body.FixtureList[0].IsSensor = true; - Body.BodyType = BodyType.Static; - Body.Rotation = MathHelper.ToRadians(angle); - Body.UserData = this; // Importante para la identificación durante la colisión - } - } - - public class simBarrera : simBase - { - public bool LuzCortada = false; - - public simBarrera(World world, float width, float height, Vector2 position, float angle = 0) - { - _world = world; - Create(width, height, position, angle); - } - - public float Angle - { - get { return MathHelper.ToDegrees(Body.Rotation); } - set { Body.Rotation = MathHelper.ToRadians(value); } - } - - public new void SetPosition(float x, float y) - { - Body.Position = new Vector2(x, y); - } - - public void SetDimensions(float width, float height) - { - Body.DestroyFixture(Body.FixtureList[0]); - - var newShape = new PolygonShape(PolygonTools.CreateRectangle(width / 2, height / 2), 1f); - Body.CreateFixture(newShape); - } - - public void Create(float width, float height, Vector2 position, float angle = 0) - { - RemoverBody(); - Body = BodyFactory.CreateRectangle(_world, width, height, 1f, position); - Body.FixtureList[0].IsSensor = true; - Body.BodyType = BodyType.Static; - Body.Rotation = MathHelper.ToRadians(angle); - Body.UserData = this; // Importante para la identificación durante la colisión - LuzCortada = false; - } - } - - public class simGuia : simBase - { - public simGuia(World world, Vector2 start, Vector2 end) - { - _world = world; - Create(start, end); - } - - public void Create(Vector2 start, Vector2 end) - { - RemoverBody(); - Body = BodyFactory.CreateEdge(_world, start, end); - Body.BodyType = BodyType.Static; - Body.UserData = this; // Importante para la identificación durante la colisión - } - - public void UpdateVertices(Vector2 newStart, Vector2 newEnd) - { - Create(newStart, newEnd); // Recrear la línea con nuevos vértices - } - } - - public class simBotella : simBase - { - private float _radius; - private float _mass; - public bool Descartar = false; - - public simBotella(World world, float diameter, Vector2 position, float mass) - { - _world = world; - _radius = diameter / 2; - _mass = mass; - Create(position); - } - - public float CenterX - { - get { return Body.Position.X; } - set { } - } - - public float CenterY - { - get { return Body.Position.Y; } - set { } - } - - public Vector2 Center - { - get { return Body.Position; } - } - - public float Mass - { - get - { - if (_mass <= 0) - _mass = 1; - return _mass; - } - set { _mass = value; } - } - - private void Create(Vector2 position) - { - RemoverBody(); - Body = BodyFactory.CreateCircle(_world, _radius, 0.2f, position); - Body.BodyType = BodyType.Dynamic; - - // Restablecer manejador de eventos de colisión - Body.OnCollision += HandleCollision; - //Body.OnSeparation += HandleOnSeparation; - - Body.UserData = this; // Importante para la identificación durante la colisión - - // Configurar la fricción - Body.Friction = 0.3f; // Ajustar según sea necesario para tu simulación - - // Configurar amortiguamiento - Body.LinearDamping = 0.4f; // Ajustar para controlar la reducción de la velocidad lineal - Body.AngularDamping = 0.4f; // Ajustar para controlar la reducción de la velocidad angular - Body.Restitution = 0.2f; // Baja restitución para menos rebote - // Body.IsBullet = true; - } - - public void SetDiameter(float diameter) - { - _radius = diameter / 2; - Create(Body.Position); // Recrear el círculo con el nuevo tamaño - } - - public void SetMass(float mass) - { - Mass = mass; - } - - private bool HandleCollision(Fixture fixtureA, Fixture fixtureB, FarseerPhysics.Dynamics.Contacts.Contact contact) - { - if (fixtureB.Body.UserData is simBarrera Sensor) - { - Sensor.LuzCortada = true; - return true; - } - else if (fixtureB.Body.UserData is simCurve curve) - { - curve.ApplyCurveEffect(fixtureA); - return true; // No aplicar respuestas físicas - } - else if (fixtureB.Body.UserData is simDescarte) - { - Descartar = true; - return true; - } else if (fixtureB.Body.UserData is simTransporte) - { - simTransporte conveyor = fixtureB.Body.UserData as simTransporte; - - if ( conveyor.Speed != 0 ) { - - CircleShape circleShape = fixtureA.Shape as CircleShape; - PolygonShape polygonShape = fixtureB.Shape as PolygonShape; - - // Obtener centro y radio del círculo - Vector2 centroCirculo = fixtureA.Body.Position; - float radio = circleShape.Radius; - - // Obtener los vértices del polígono (rectángulo) - Vector2[] vertices = new Vector2[polygonShape.Vertices.Count]; - float cos = (float)Math.Cos(fixtureB.Body.Rotation); - float sin = (float)Math.Sin(fixtureB.Body.Rotation); - - for (int i = 0; i < polygonShape.Vertices.Count; i++) - { - Vector2 vertex = polygonShape.Vertices[i]; - float rotatedX = vertex.X * cos - vertex.Y * sin + fixtureB.Body.Position.X; - float rotatedY = vertex.X * sin + vertex.Y * cos + fixtureB.Body.Position.Y; - vertices[i] = new Vector2(rotatedX, rotatedY); - } - - // Calcular el porcentaje de la superficie compartida - float porcentajeCompartido = InterseccionCirculoRectangulo.CalcularSuperficieCompartida(vertices, centroCirculo, radio); - - // Aplicar el efecto del transportador usando el porcentaje calculado - if (conveyor.TransportWithGuides) - if (conveyor.DistanceGuide2Guide <= radio * 2) - CenterFixtureOnConveyor(fixtureA, conveyor); - ApplyConveyorEffect(conveyor, fixtureA, porcentajeCompartido); - - } - return true; // No aplicar respuestas físicas - } - return true; // No aplicar respuestas físicas - } - - private void ApplyConveyorEffect(simTransporte conveyor, Fixture circleFixture, float porcentajeCompartido) - { - float speedMetersPerSecond = conveyor.Speed / 60.0f; - Vector2 desiredVelocity = new Vector2((float)Math.Cos(conveyor.Body.Rotation), (float)Math.Sin(conveyor.Body.Rotation)) * speedMetersPerSecond; - circleFixture.Body.LinearVelocity += desiredVelocity * porcentajeCompartido; - } - - private void CenterFixtureOnConveyor(Fixture fixtureA, simTransporte conveyor) - { - // Obtener el centro del conveyor - Vector2 conveyorCenter = conveyor.Body.Position; - - // Calcular el vector de la línea horizontal centrada de conveyor - float halfDistance = conveyor.DistanceGuide2Guide / 2; - float cos = (float)Math.Cos(conveyor.Body.Rotation); - float sin = (float)Math.Sin(conveyor.Body.Rotation); - - Vector2 offset = new Vector2(halfDistance * cos, halfDistance * sin); - - // Línea horizontal centrada de conveyor en el espacio del mundo - Vector2 lineStart = conveyorCenter - offset; - Vector2 lineEnd = conveyorCenter + offset; - - // Proyectar el centro de fixtureA sobre la línea horizontal - Vector2 fixtureCenter = fixtureA.Body.Position; - Vector2 closestPoint = ProjectPointOntoLine(fixtureCenter, lineStart, lineEnd); - - // Mover fixtureA al punto más cercano en la línea horizontal - fixtureA.Body.Position = closestPoint; - } - - private Vector2 ProjectPointOntoLine(Vector2 point, Vector2 lineStart, Vector2 lineEnd) - { - Vector2 lineDirection = lineEnd - lineStart; - lineDirection.Normalize(); - - Vector2 pointToLineStart = point - lineStart; - float projectionLength = Vector2.Dot(pointToLineStart, lineDirection); - - return lineStart + projectionLength * lineDirection; - } - } - - public class SimulationManagerFP - { - private World world; - private Canvas simulationCanvas; - public List Cuerpos; - - private Stopwatch stopwatch; - private double stopwatch_last; - - public Canvas DebugCanvas { get => simulationCanvas; set => simulationCanvas = value; } - - public SimulationManagerFP() - { - world = new World(new Vector2(0, 0)); // Vector2.Zero - Cuerpos = new List(); - stopwatch = new Stopwatch(); - stopwatch.Start(); - } - - public void Clear() - { - if (world.BodyList.Count > 0) - world.Clear(); - if (Cuerpos.Count > 0) - Cuerpos.Clear(); - } - - public void Step() - { - // Detener el cronómetro y obtener el tiempo transcurrido en milisegundos - float elapsedMilliseconds = (float) (stopwatch.Elapsed.TotalMilliseconds - stopwatch_last); - stopwatch_last = stopwatch.Elapsed.TotalMilliseconds; - - // Pasar el tiempo transcurrido al método Step - world.Step(elapsedMilliseconds / 1000.0f); - } - - public void Remove(simBase Objeto) - { - if (Objeto != null) - { - Objeto.RemoverBody(); - Cuerpos.Remove(Objeto); - } - } - - public simCurve AddCurve(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 position) - { - simCurve curva = new simCurve( world, innerRadius, outerRadius, startAngle, endAngle, position); - Cuerpos.Add(curva); - return curva; - } - - public simBotella AddCircle(float diameter, Vector2 position, float mass) - { - simBotella circle = new simBotella(world, diameter, position, mass); - Cuerpos.Add(circle); - return circle; - } - - public simTransporte AddRectangle(float width, float height, Vector2 position, float angle) - { - simTransporte rectangle = new simTransporte(world, width, height, position, angle); - Cuerpos.Add(rectangle); - return rectangle; - } - - public simBarrera AddBarrera(float width, float height, Vector2 position, float angle) - { - simBarrera rectangle = new simBarrera(world, width, height, position, angle); - Cuerpos.Add(rectangle); - return rectangle; - } - - public simGuia AddLine(Vector2 start, Vector2 end) - { - simGuia line = new simGuia(world, start, end); - Cuerpos.Add(line); - return line; - } - - public simDescarte AddDescarte(float diameter, Vector2 position) - { - simDescarte descarte = new simDescarte(world, diameter, position); - Cuerpos.Add(descarte); - return descarte; - } - - public void Debug_DrawInitialBodies() - { - Debug_ClearSimulationShapes(); - world.Step(0.01f); // Para actualizar la BodyList - foreach (Body body in world.BodyList) - { - foreach (Fixture fixture in body.FixtureList) - { - DrawShape(fixture); - } - } - } - - public void Debug_ClearSimulationShapes() - { - var simulationShapes = simulationCanvas.Children.OfType().Where(s => s.Tag as string == "Simulation").ToList(); - foreach (var shape in simulationShapes) - { - simulationCanvas.Children.Remove(shape); - } - } - - private void DrawShape(Fixture fixture) - { - System.Windows.Shapes.Shape shape; - switch (fixture.ShapeType) - { - case ShapeType.Circle: - shape = DrawCircle(fixture); - break; - case ShapeType.Polygon: - shape = DrawPolygon(fixture); - break; - case ShapeType.Edge: - shape = DrawEdge(fixture); - break; - default: - return; - } - shape.Tag = "Simulation"; // Marcar para simulación - Canvas.SetZIndex(shape, 20); - simulationCanvas.Children.Add(shape); - } - - private float p(float x) - { - float c = PixelToMeter.Instance.calc.MetersToPixels(x); - return c; - } - - private System.Windows.Shapes.Shape DrawEdge(Fixture fixture) - { - EdgeShape edge = fixture.Shape as EdgeShape; - Line line = new Line - { - X1 = p(edge.Vertex1.X + fixture.Body.Position.X), // Aplicar escala y posición - Y1 = p(edge.Vertex1.Y + fixture.Body.Position.Y), - X2 = p(edge.Vertex2.X + fixture.Body.Position.X), - Y2 = p(edge.Vertex2.Y + fixture.Body.Position.Y), - Stroke = Brushes.Black, - StrokeThickness = 2 - }; - return line; - } - - private System.Windows.Shapes.Shape DrawCircle(Fixture fixture) - { - CircleShape circle = fixture.Shape as CircleShape; - Ellipse ellipse = new Ellipse - { - Width = p(circle.Radius * 2), // Escalado para visualización - Height = p(circle.Radius * 2), // Escalado para visualización - Stroke = Brushes.Black, - StrokeThickness = 2 - }; - Canvas.SetLeft(ellipse, p(fixture.Body.Position.X - circle.Radius)); - Canvas.SetTop(ellipse, p(fixture.Body.Position.Y - circle.Radius)); - return ellipse; - } - - private System.Windows.Shapes.Shape DrawPolygon(Fixture fixture) - { - Polygon polygon = new Polygon { Stroke = Brushes.Black, StrokeThickness = 2 }; - PolygonShape polyShape = fixture.Shape as PolygonShape; - - float cos = (float)Math.Cos(fixture.Body.Rotation); - float sin = (float)Math.Sin(fixture.Body.Rotation); - - foreach (Vector2 vertex in polyShape.Vertices) - { - float rotatedX = vertex.X * cos - vertex.Y * sin + fixture.Body.Position.X; - float rotatedY = vertex.X * sin + vertex.Y * cos + fixture.Body.Position.Y; - - polygon.Points.Add(new Point(p(rotatedX), p(rotatedY))); - } - - return polygon; - } - } -} diff --git a/Simulacion/FluidSimulationManager.cs b/Simulacion/FluidSimulationManager.cs deleted file mode 100644 index 41bd12d..0000000 --- a/Simulacion/FluidSimulationManager.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using nkast.Aether.Physics2D.Common; -using CtrEditor.Simulacion.Fluids; -using CtrEditor.ObjetosSim; - -namespace CtrEditor.Simulacion -{ - /// - /// Gestor para la simulación de fluidos independiente de la simulación física principal - /// - public class FluidSimulationManager - { - private SimulacionFluidos _simulacion; - private readonly List _deferredActions = new List(); - private readonly List _sistemasRegistrados = new List(); - private float _defaultWidth = 10.0f; - private float _defaultHeight = 10.0f; - - private bool _isRunning = false; - private Stopwatch _stopwatch; - private double _lastUpdateTime; - - public bool IsRunning => _isRunning; - - /// - /// Constructor - /// - public FluidSimulationManager() - { - _stopwatch = new Stopwatch(); - } - - /// - /// Inicializa la simulación de fluidos - /// - public void Initialize() - { - if (_simulacion == null) - { - _simulacion = new SimulacionFluidos( - _defaultWidth, - _defaultHeight, - 10000, // max partículas - new Vector2(0, 9.8f) // gravedad por defecto - ); - } - } - - /// - /// Inicia la simulación de fluidos - /// - public void Start() - { - if (!_isRunning) - { - _isRunning = true; - _stopwatch.Start(); - _lastUpdateTime = _stopwatch.Elapsed.TotalMilliseconds; - - // Notificar a los sistemas de fluidos - foreach (var sistema in _sistemasRegistrados) - { - sistema.OnFluidSimulationStart(); - } - } - } - - /// - /// Detiene la simulación de fluidos - /// - public void Stop() - { - if (_isRunning) - { - _isRunning = false; - _stopwatch.Stop(); - _stopwatch.Reset(); - - // Notificar a los sistemas de fluidos - foreach (var sistema in _sistemasRegistrados) - { - sistema.OnFluidSimulationStop(); - } - } - } - - /// - /// Actualiza un paso de la simulación de fluidos - /// - public void Step() - { - if (!_isRunning) return; - - // Calcular delta time - double currentTime = _stopwatch.Elapsed.TotalMilliseconds; - float deltaTime = (float)(currentTime - _lastUpdateTime) / 1000.0f; - _lastUpdateTime = currentTime; - - // Asegurar que deltaTime no es demasiado grande (evita inestabilidades) - if (deltaTime > 0.05f) deltaTime = 0.05f; - - // Procesar acciones diferidas - foreach (var action in _deferredActions) - { - action(); - } - _deferredActions.Clear(); - - // Actualizar la simulación - _simulacion?.Actualizar(deltaTime); - - // Notificar a los sistemas de fluidos - foreach (var sistema in _sistemasRegistrados) - { - sistema.UpdateFluidSimulation(deltaTime); - } - } - - /// - /// Registra un sistema de fluidos para ser actualizado - /// - public void RegisterFluidSystem(osSistemaFluidos sistema) - { - if (!_sistemasRegistrados.Contains(sistema)) - { - _sistemasRegistrados.Add(sistema); - - // Si el sistema ya está corriendo, notificar al nuevo sistema - if (_isRunning) - { - sistema.OnFluidSimulationStart(); - } - } - } - - /// - /// Elimina un sistema de fluidos del registro - /// - public void UnregisterFluidSystem(osSistemaFluidos sistema) - { - _sistemasRegistrados.Remove(sistema); - } - - /// - /// Añade una acción para ser ejecutada en el próximo paso de simulación - /// - public void AddDeferredAction(Action action) - { - _deferredActions.Add(action); - } - - /// - /// Obtiene la instancia de simulación de fluidos - /// - public SimulacionFluidos GetSimulacion() - { - if (_simulacion == null) - { - Initialize(); - } - return _simulacion; - } - - /// - /// Limpia los recursos de la simulación - /// - public void Clear() - { - Stop(); - _simulacion = null; - _sistemasRegistrados.Clear(); - _deferredActions.Clear(); - } - } -} diff --git a/Simulacion/Fluids/1/FluidDefinition.cs b/Simulacion/Fluids/1/FluidDefinition.cs deleted file mode 100644 index 745c6ef..0000000 --- a/Simulacion/Fluids/1/FluidDefinition.cs +++ /dev/null @@ -1,121 +0,0 @@ -/* Original source Farseer Physics Engine: - * Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com - * Microsoft Permissive License (Ms-PL) v1.1 - */ - -using nkast.Aether.Physics2D.Common; - -namespace tainicom.Aether.Physics2D.Fluids -{ - /// - /// Fluid parameters, see pvfs.pdf for a detailed explanation - /// - public struct FluidDefinition - { - /// - /// Distance of influence between the particles - /// - public float InfluenceRadius; - - /// - /// Density of the fluid - /// - public float DensityRest; - - /// - /// Stiffness of the fluid (when particles are far) - /// - public float Stiffness; - - /// - /// Stiffness of the fluid (when particles are near) - /// Set by Check() - /// - public float StiffnessNear; - - /// - /// Toggles viscosity forces - /// - public bool UseViscosity; - - /// - /// See pvfs.pdf for more information - /// - public float ViscositySigma; - - /// - /// See pvfs.pdf for more information - /// - public float ViscosityBeta; - - /// - /// Toggles plasticity computation (springs etc.) - /// - public bool UsePlasticity; - - /// - /// Plasticity, amount of memory of the shape - /// See pvfs.pdf for more information - /// - public float Plasticity; - - /// - /// K of the springs used for plasticity - /// - public float KSpring; - - /// - /// Amount of change of the rest length of the springs (when compressed) - /// - public float YieldRatioCompress; - - /// - /// Amount of change of the rest length of the springs (when stretched) - /// - public float YieldRatioStretch; - - public static FluidDefinition Default - { - get - { - FluidDefinition def = new FluidDefinition - { - InfluenceRadius = 1.0f, - DensityRest = 10.0f, - Stiffness = 10.0f, - StiffnessNear = 0.0f, // Set by Check() - - UseViscosity = false, - ViscositySigma = 10.0f, - ViscosityBeta = 0.0f, - - UsePlasticity = false, - Plasticity = 0.3f, - KSpring = 2.0f, - YieldRatioCompress = 0.1f, - YieldRatioStretch = 0.1f - }; - - def.Check(); - - return def; - } - } - - public void Check() - { - InfluenceRadius = MathUtils.Clamp(InfluenceRadius, 0.1f, 10.0f); - DensityRest = MathUtils.Clamp(DensityRest, 1.0f, 100.0f); - Stiffness = MathUtils.Clamp(Stiffness, 0.1f, 10.0f); - StiffnessNear = Stiffness * 100.0f; // See pvfs.pdf - - ViscositySigma = Math.Max(ViscositySigma, 0.0f); - ViscosityBeta = Math.Max(ViscosityBeta, 0.0f); - - Plasticity = Math.Max(Plasticity, 0.0f); - KSpring = Math.Max(KSpring, 0.0f); - YieldRatioCompress = Math.Max(YieldRatioCompress, 0.0f); - YieldRatioStretch = Math.Max(YieldRatioStretch, 0.0f); - } - } -} \ No newline at end of file diff --git a/Simulacion/Fluids/1/FluidParticle.cs b/Simulacion/Fluids/1/FluidParticle.cs deleted file mode 100644 index 395ed4e..0000000 --- a/Simulacion/Fluids/1/FluidParticle.cs +++ /dev/null @@ -1,80 +0,0 @@ -/* Original source Farseer Physics Engine: - * Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com - * Microsoft Permissive License (Ms-PL) v1.1 - */ - -using System.Collections.Generic; -using nkast.Aether.Physics2D.Common; - -namespace tainicom.Aether.Physics2D.Fluids -{ - public class FluidParticle - { - public Vector2 Position; - public Vector2 PreviousPosition; - - public Vector2 Velocity; - public Vector2 Acceleration; - - internal FluidParticle(Vector2 position) - { - Neighbours = new List(); - IsActive = true; - MoveTo(position); - - Damping = 0.0f; - Mass = 1.0f; - } - - public bool IsActive { get; set; } - - public List Neighbours { get; private set; } - - // For gameplay purposes - public float Density { get; internal set; } - public float Pressure { get; internal set; } - - // Other properties - public int Index { get; internal set; } - - // Physics properties - public float Damping { get; set; } - public float Mass { get; set; } - - public void MoveTo(Vector2 p) - { - Position = p; - PreviousPosition = p; - - Velocity = Vector2.Zero; - Acceleration = Vector2.Zero; - } - - public void ApplyForce(ref Vector2 force) - { - Acceleration += force * Mass; - } - - public void ApplyImpulse(ref Vector2 impulse) - { - Velocity += impulse; - } - - public void Update(float deltaTime) - { - Velocity += Acceleration * deltaTime; - - Vector2 delta = (1.0f - Damping) * Velocity * deltaTime; - - PreviousPosition = Position; - Position += delta; - - Acceleration = Vector2.Zero; - } - - public void UpdateVelocity(float deltaTime) - { - Velocity = (Position - PreviousPosition) / deltaTime; - } - } -} diff --git a/Simulacion/Fluids/1/FluidSystem1.cs b/Simulacion/Fluids/1/FluidSystem1.cs deleted file mode 100644 index 9d6bc2c..0000000 --- a/Simulacion/Fluids/1/FluidSystem1.cs +++ /dev/null @@ -1,413 +0,0 @@ -/* Original source Farseer Physics Engine: - * Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com - * Microsoft Permissive License (Ms-PL) v1.1 - */ - -using System; -using System.Collections.Generic; -using nkast.Aether.Physics2D.Common; - -namespace tainicom.Aether.Physics2D.Fluids -{ - public class FluidSystem1 - { - private float _influenceRadiusSquared; - private HashGrid _hashGrid = new HashGrid(); - private Dictionary _springs = new Dictionary(); - private List _springsToRemove = new List(); - private Vector2 _totalForce; - - public FluidSystem1(Vector2 gravity) - { - Gravity = gravity; - Particles = new List(); - DefaultDefinition(); - } - - public FluidDefinition Definition { get; private set; } - public List Particles { get; private set; } - public int ParticlesCount { get { return Particles.Count; } } - public Vector2 Gravity { get; set; } - - public void DefaultDefinition() - { - SetDefinition(FluidDefinition.Default); - } - - public void SetDefinition(FluidDefinition def) - { - Definition = def; - Definition.Check(); - _influenceRadiusSquared = Definition.InfluenceRadius * Definition.InfluenceRadius; - } - - public FluidParticle AddParticle(Vector2 position) - { - FluidParticle particle = new FluidParticle(position) { Index = Particles.Count }; - Particles.Add(particle); - return particle; - } - - public void Clear() - { - //TODO - } - - public void ApplyForce(Vector2 f) - { - _totalForce += f; - } - - private void ApplyForces() - { - Vector2 f = Gravity + _totalForce; - - for (int i = 0; i < Particles.Count; ++i) - { - Particles[i].ApplyForce(ref f); - } - - _totalForce = Vector2.Zero; - } - - private void ApplyViscosity(FluidParticle p, float timeStep) - { - for (int i = 0; i < p.Neighbours.Count; ++i) - { - FluidParticle neighbour = p.Neighbours[i]; - - if (p.Index >= neighbour.Index) - { - continue; - } - - float q; - Vector2.DistanceSquared(ref p.Position, ref neighbour.Position, out q); - - if (q > _influenceRadiusSquared) - { - continue; - } - - Vector2 direction; - Vector2.Subtract(ref neighbour.Position, ref p.Position, out direction); - - if (direction.LengthSquared() < float.Epsilon) - { - continue; - } - - direction.Normalize(); - - Vector2 deltaVelocity; - Vector2.Subtract(ref p.Velocity, ref neighbour.Velocity, out deltaVelocity); - - float u; - Vector2.Dot(ref deltaVelocity, ref direction, out u); - - if (u > 0.0f) - { - q = 1.0f - (float)Math.Sqrt(q) / Definition.InfluenceRadius; - - float impulseFactor = 0.5f * timeStep * q * (u * (Definition.ViscositySigma + Definition.ViscosityBeta * u)); - - Vector2 impulse; - - Vector2.Multiply(ref direction, -impulseFactor, out impulse); - p.ApplyImpulse(ref impulse); - - Vector2.Multiply(ref direction, impulseFactor, out impulse); - neighbour.ApplyImpulse(ref impulse); - } - } - } - - private const int MaxNeighbors = 25; - //private int _len2; - //private int _j; - //private float _q; - //private float _qq; - - //private Vector2 _rij; - //private float _d; - //private Vector2 _dx; - private float _density; - private float _densityNear; - private float _pressure; - private float _pressureNear; - private float[] _distanceCache = new float[MaxNeighbors]; - - //private void DoubleDensityRelaxation1(FluidParticle p, float timeStep) - //{ - // _density = 0; - // _densityNear = 0; - - // _len2 = p.Neighbours.Count; - // if (_len2 > MaxNeighbors) - // _len2 = MaxNeighbors; - - // for (_j = 0; _j < _len2; _j++) - // { - // _q = Vector2.DistanceSquared(p.Position, p.Neighbours[_j].Position); - // _distanceCache[_j] = _q; - // if (_q < _influenceRadiusSquared && _q != 0) - // { - // _q = (float)Math.Sqrt(_q); - // _q /= Definition.InfluenceRadius; - // _qq = ((1 - _q) * (1 - _q)); - // _density += _qq; - // _densityNear += _qq * (1 - _q); - // } - // } - - // _pressure = Definition.Stiffness * (_density - Definition.DensityRest); - // _pressureNear = Definition.StiffnessNear * _densityNear; - - // _dx = Vector2.Zero; - - // for (_j = 0; _j < _len2; _j++) - // { - // _q = _distanceCache[_j]; - // if (_q < _influenceRadiusSquared && _q != 0) - // { - // _q = (float)Math.Sqrt(_q); - // _rij = p.Neighbours[_j].Position; - // _rij -= p.Position; - // _rij *= 1 / _q; - // _q /= _influenceRadiusSquared; - - // _d = ((timeStep * timeStep) * (_pressure * (1 - _q) + _pressureNear * (1 - _q) * (1 - _q))); - // _rij *= _d * 0.5f; - // p.Neighbours[_j].Position += _rij; - // _dx -= _rij; - // } - // } - // p.Position += _dx; - //} - - private void DoubleDensityRelaxation(FluidParticle particle, float deltaTime2) - { - _density = 0.0f; - _densityNear = 0.0f; - - int neightborCount = particle.Neighbours.Count; - - if (neightborCount > MaxNeighbors) - neightborCount = MaxNeighbors; - - for (int i = 0; i < neightborCount; ++i) - { - FluidParticle neighbour = particle.Neighbours[i]; - - if (particle.Index == neighbour.Index) - continue; - - float q; - Vector2.DistanceSquared(ref particle.Position, ref neighbour.Position, out q); - _distanceCache[i] = q; - - if (q > _influenceRadiusSquared) - continue; - - q = 1.0f - (float)Math.Sqrt(q) / Definition.InfluenceRadius; - - float densityDelta = q * q; - _density += densityDelta; - _densityNear += densityDelta * q; - } - - _pressure = Definition.Stiffness * (_density - Definition.DensityRest); - _pressureNear = Definition.StiffnessNear * _densityNear; - - // For gameplay purposes - particle.Density = _density + _densityNear; - particle.Pressure = _pressure + _pressureNear; - - Vector2 delta = Vector2.Zero; - - for (int i = 0; i < neightborCount; ++i) - { - FluidParticle neighbour = particle.Neighbours[i]; - - if (particle.Index == neighbour.Index) - continue; - - float q = _distanceCache[i]; - - if (q > _influenceRadiusSquared) - continue; - - q = 1.0f - (float)Math.Sqrt(q) / Definition.InfluenceRadius; - - float dispFactor = deltaTime2 * (q * (_pressure + _pressureNear * q)); - - Vector2 direction; - Vector2.Subtract(ref neighbour.Position, ref particle.Position, out direction); - - if (direction.LengthSquared() < float.Epsilon) - continue; - - direction.Normalize(); - - Vector2 disp; - - Vector2.Multiply(ref direction, dispFactor, out disp); - Vector2.Add(ref neighbour.Position, ref disp, out neighbour.Position); - - Vector2.Multiply(ref direction, -dispFactor, out disp); - Vector2.Add(ref delta, ref disp, out delta); - } - - Vector2.Add(ref particle.Position, ref delta, out particle.Position); - } - - private void CreateSprings(FluidParticle p) - { - for (int i = 0; i < p.Neighbours.Count; ++i) - { - FluidParticle neighbour = p.Neighbours[i]; - - if (p.Index >= neighbour.Index) - continue; - - float q; - Vector2.DistanceSquared(ref p.Position, ref neighbour.Position, out q); - - if (q > _influenceRadiusSquared) - continue; - - SpringHash hash = new SpringHash { P0 = p, P1 = neighbour }; - - if (!_springs.ContainsKey(hash)) - { - //TODO: Use pool? - Spring spring = new Spring(p, neighbour) { RestLength = (float)Math.Sqrt(q) }; - _springs.Add(hash, spring); - } - } - } - - private void AdjustSprings(float timeStep) - { - foreach (var pair in _springs) - { - Spring spring = pair.Value; - - spring.Update(timeStep, Definition.KSpring, Definition.InfluenceRadius); - - if (spring.Active) - { - float L = spring.RestLength; - float distance; - Vector2.Distance(ref spring.P0.Position, ref spring.P1.Position, out distance); - - if (distance > (L + (Definition.YieldRatioStretch * L))) - { - spring.RestLength += timeStep * Definition.Plasticity * (distance - L - (Definition.YieldRatioStretch * L)); - } - else if (distance < (L - (Definition.YieldRatioCompress * L))) - { - spring.RestLength -= timeStep * Definition.Plasticity * (L - (Definition.YieldRatioCompress * L) - distance); - } - } - else - { - _springsToRemove.Add(pair.Key); - } - } - - for (int i = 0; i < _springsToRemove.Count; ++i) - { - _springs.Remove(_springsToRemove[i]); - } - } - - private void ComputeNeighbours() - { - _hashGrid.GridSize = Definition.InfluenceRadius; - _hashGrid.Clear(); - - for (int i = 0; i < Particles.Count; ++i) - { - FluidParticle p = Particles[i]; - - if (p.IsActive) - { - _hashGrid.Add(p); - } - } - - for (int i = 0; i < Particles.Count; ++i) - { - FluidParticle p = Particles[i]; - p.Neighbours.Clear(); - _hashGrid.Find(ref p.Position, p.Neighbours); - } - } - - public void Update(float deltaTime) - { - if (deltaTime == 0) - return; - - float deltaTime2 = 0.5f * deltaTime * deltaTime; - - ComputeNeighbours(); - ApplyForces(); - - if (Definition.UseViscosity) - { - for (int i = 0; i < Particles.Count; ++i) - { - FluidParticle p = Particles[i]; - if (p.IsActive) - { - ApplyViscosity(p, deltaTime); - } - } - } - - for (int i = 0; i < Particles.Count; ++i) - { - FluidParticle p = Particles[i]; - if (p.IsActive) - { - p.Update(deltaTime); - } - } - - for (int i = 0; i < Particles.Count; ++i) - { - FluidParticle p = Particles[i]; - if (p.IsActive) - { - DoubleDensityRelaxation(p, deltaTime2); - } - } - - if (Definition.UsePlasticity) - { - for (int i = 0; i < Particles.Count; ++i) - { - FluidParticle p = Particles[i]; - if (p.IsActive) - { - CreateSprings(p); - } - } - } - - AdjustSprings(deltaTime); - - UpdateVelocities(deltaTime); - } - - internal void UpdateVelocities(float timeStep) - { - for (int i = 0; i < Particles.Count; ++i) - { - Particles[i].UpdateVelocity(timeStep); - } - } - } -} diff --git a/Simulacion/Fluids/1/HashGrid.cs b/Simulacion/Fluids/1/HashGrid.cs deleted file mode 100644 index 64dcd8a..0000000 --- a/Simulacion/Fluids/1/HashGrid.cs +++ /dev/null @@ -1,95 +0,0 @@ -/* Original source Farseer Physics Engine: - * Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com - * Microsoft Permissive License (Ms-PL) v1.1 - */ - -using System; -using System.Collections.Generic; -using nkast.Aether.Physics2D.Common; - -namespace tainicom.Aether.Physics2D.Fluids -{ - /// - /// Grid used by particle system to keep track of neightbor particles. - /// - public class HashGrid - { - private Dictionary> _hash = new Dictionary>(); - private Stack> _bucketPool = new Stack>(); - - public HashGrid() - { - GridSize = 1.0f; - } - - public float GridSize { get; set; } - - private static ulong HashKey(int x, int y) - { - return ((ulong)x * 2185031351ul) ^ ((ulong)y * 4232417593ul); - } - - private ulong HashKey(Vector2 position) - { - return HashKey( - (int)Math.Floor(position.X / GridSize), - (int)Math.Floor(position.Y / GridSize) - ); - } - - public void Clear() - { - foreach (KeyValuePair> pair in _hash) - { - pair.Value.Clear(); - _bucketPool.Push(pair.Value); - } - _hash.Clear(); - } - - public void Add(FluidParticle particle) - { - ulong key = HashKey(particle.Position); - List bucket; - if (!_hash.TryGetValue(key, out bucket)) - { - if (_bucketPool.Count > 0) - { - bucket = _bucketPool.Pop(); - } - else - { - bucket = new List(); - } - _hash.Add(key, bucket); - } - bucket.Add(particle); - } - - public void Find(ref Vector2 position, List neighbours) - { - int ix = (int)Math.Floor(position.X / GridSize); - int iy = (int)Math.Floor(position.Y / GridSize); - - // Check all 9 neighbouring cells - for (int x = ix - 1; x <= ix + 1; ++x) - { - for (int y = iy - 1; y <= iy + 1; ++y) - { - ulong key = HashKey(x, y); - List bucket; - if (_hash.TryGetValue(key, out bucket)) - { - for (int i = 0; i < bucket.Count; ++i) - { - if (bucket[i] != null) - { - neighbours.Add(bucket[i]); - } - } - } - } - } - } - } -} diff --git a/Simulacion/Fluids/1/Spring.cs b/Simulacion/Fluids/1/Spring.cs deleted file mode 100644 index 76625ae..0000000 --- a/Simulacion/Fluids/1/Spring.cs +++ /dev/null @@ -1,57 +0,0 @@ -/* Original source Farseer Physics Engine: - * Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com - * Microsoft Permissive License (Ms-PL) v1.1 - */ - -using nkast.Aether.Physics2D.Common; - -namespace tainicom.Aether.Physics2D.Fluids -{ - //TODO: Could be struct? - - public class Spring - { - public FluidParticle P0; - public FluidParticle P1; - - public Spring(FluidParticle p0, FluidParticle p1) - { - Active = true; - P0 = p0; - P1 = p1; - } - - public bool Active { get; set; } - public float RestLength { get; set; } - - public void Update(float timeStep, float kSpring, float influenceRadius) - { - if (!Active) - return; - - Vector2 dir = P1.Position - P0.Position; - float distance = dir.Length(); - dir.Normalize(); - - // This is to avoid imploding simulation with really springy fluids - if (distance < 0.5f * influenceRadius) - { - Active = false; - return; - } - if (RestLength > influenceRadius) - { - Active = false; - return; - } - - //Algorithm 3 - float displacement = timeStep * timeStep * kSpring * (1.0f - RestLength / influenceRadius) * (RestLength - distance) * 0.5f; - - dir *= displacement; - - P0.Position -= dir; - P1.Position += dir; - } - } -} diff --git a/Simulacion/Fluids/1/SpringHash.cs b/Simulacion/Fluids/1/SpringHash.cs deleted file mode 100644 index 49c9529..0000000 --- a/Simulacion/Fluids/1/SpringHash.cs +++ /dev/null @@ -1,26 +0,0 @@ -/* Original source Farseer Physics Engine: - * Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com - * Microsoft Permissive License (Ms-PL) v1.1 - */ - -using System.Collections.Generic; - -namespace tainicom.Aether.Physics2D.Fluids -{ - public class SpringHash : IEqualityComparer - { - public FluidParticle P0; - public FluidParticle P1; - - public bool Equals(SpringHash lhs, SpringHash rhs) - { - return (lhs.P0.Index == rhs.P0.Index && lhs.P1.Index == rhs.P1.Index) - || (lhs.P0.Index == rhs.P1.Index && lhs.P1.Index == rhs.P0.Index); - } - - public int GetHashCode(SpringHash s) - { - return (s.P0.Index * 73856093) ^ (s.P1.Index * 19349663) ^ (s.P0.Index * 19349663) ^ (s.P1.Index * 73856093); - } - } -} \ No newline at end of file diff --git a/Simulacion/Fluids/2/FluidSystem2.cs b/Simulacion/Fluids/2/FluidSystem2.cs deleted file mode 100644 index 288ed89..0000000 --- a/Simulacion/Fluids/2/FluidSystem2.cs +++ /dev/null @@ -1,445 +0,0 @@ -/* Original source Farseer Physics Engine: - * Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com - * Microsoft Permissive License (Ms-PL) v1.1 - */ - -using System; -using System.Collections.Generic; -using nkast.Aether.Physics2D.Common; - -namespace tainicom.Aether.Physics2D.Fluids -{ - public class FluidSystem2 - { - public const int MaxNeighbors = 25; - public const int CellSize = 1; - - // Most of these can be tuned at runtime with F1-F9 and keys 1-9 (no numpad) - public const float InfluenceRadius = 20.0f; - public const float InfluenceRadiusSquared = InfluenceRadius * InfluenceRadius; - public const float Stiffness = 0.504f; - public const float StiffnessFarNearRatio = 10.0f; - public const float StiffnessNear = Stiffness * StiffnessFarNearRatio; - public const float ViscositySigma = 0.0f; - public const float ViscosityBeta = 0.3f; - public const float DensityRest = 10.0f; - public const float KSpring = 0.3f; - public const float RestLength = 5.0f; - public const float RestLengthSquared = RestLength * RestLength; - public const float YieldRatioStretch = 0.5f; - public const float YieldRatioCompress = 0.5f; - public const float Plasticity = 0.5f; - public const int VelocityCap = 150; - public const float DeformationFactor = 0f; - public const float CollisionForce = 0.3f; - - private bool _isElasticityInitialized; - private bool _elasticityEnabled; - private bool _isPlasticityInitialized; - private bool _plasticityEnabled; - - private float _deltaTime2; - private Vector2 _dx = new Vector2(0.0f, 0.0f); - private const int Wpadding = 20; - private const int Hpadding = 20; - - public SpatialTable Particles; - - // Temp variables - private Vector2 _rij = new Vector2(0.0f, 0.0f); - private Vector2 _tempVect = new Vector2(0.0f, 0.0f); - - private Dictionary> _springPresenceTable; - private List _springs; - private List _tempParticles; - - private int _worldWidth; - private int _worldHeight; - - public int ParticlesCount { get { return Particles.Count; } } - - public FluidSystem2(Vector2 gravity, int maxParticleLimit, int worldWidth, int worldHeight) - { - _worldHeight = worldHeight; - _worldWidth = worldWidth; - Particles = new SpatialTable(worldWidth, worldHeight, CellSize); - MaxParticleLimit = maxParticleLimit; - Gravity = gravity; - } - - public Vector2 Gravity { get; set; } - public int MaxParticleLimit { get; private set; } - - public bool ElasticityEnabled - { - get { return _elasticityEnabled; } - set - { - if (!_isElasticityInitialized) - InitializeElasticity(); - - _elasticityEnabled = value; - } - } - - public bool PlasticityEnabled - { - get { return _plasticityEnabled; } - set - { - if (!_isPlasticityInitialized) - InitializePlasticity(); - - _plasticityEnabled = value; - } - } - - private void UpdateParticleVelocity(float deltaTime) - { - for(int i = 0; i < Particles.Count; i++) - { - Particle particle = Particles[i]; - particle.PreviousPosition = particle.Position; - particle.Position = new Vector2(particle.Position.X + (deltaTime * particle.Velocity.X), particle.Position.Y + (deltaTime * particle.Velocity.Y)); - } - } - - private void WallCollision(Particle pi) - { - float x = 0; - float y = 0; - - if (pi.Position.X > (_worldWidth / 2 - Wpadding)) - x -= (pi.Position.X - (_worldWidth / 2 - Wpadding)) / CollisionForce; - else if (pi.Position.X < (-_worldWidth / 2 + Wpadding)) - x += ((-_worldWidth / 2 + Wpadding) - pi.Position.X) / CollisionForce; - - if (pi.Position.Y > (_worldHeight - Hpadding)) - y -= (pi.Position.Y - (_worldHeight - Hpadding)) / CollisionForce; - else if (pi.Position.Y < Hpadding) - y += (Hpadding - pi.Position.Y) / CollisionForce; - - pi.Velocity.X += x; - pi.Velocity.Y += y; - } - - private void CapVelocity(Vector2 v) - { - if (v.X > VelocityCap) - v.X = VelocityCap; - else if (v.X < -VelocityCap) - v.X = -VelocityCap; - - if (v.Y > VelocityCap) - v.Y = VelocityCap; - else if (v.Y < -VelocityCap) - v.Y = -VelocityCap; - } - - private void InitializePlasticity() - { - _isPlasticityInitialized = true; - - _springs.Clear(); - float q; - foreach (Particle pa in Particles) - { - foreach (Particle pb in Particles) - { - if (pa.GetHashCode() == pb.GetHashCode()) - continue; - - Vector2.Distance(ref pa.Position, ref pb.Position, out q); - Vector2.Subtract(ref pb.Position, ref pa.Position, out _rij); - _rij /= q; - - if (q < RestLength) - { - _springs.Add(new Spring2(pa, pb, q)); - } - } - pa.Velocity = Vector2.Zero; - } - } - - private void CalculatePlasticity(float deltaTime) - { - foreach (Spring2 spring in _springs) - { - spring.Update(); - - if (spring.CurrentDistance == 0) - continue; - - Vector2.Subtract(ref spring.PB.Position, ref spring.PA.Position, out _rij); - _rij /= spring.CurrentDistance; - float D = deltaTime * KSpring * (spring.RestLength - spring.CurrentDistance); - _rij *= (D * 0.5f); - spring.PA.Position = new Vector2(spring.PA.Position.X - _rij.X, spring.PA.Position.Y - _rij.Y); - spring.PB.Position = new Vector2(spring.PB.Position.X + _rij.X, spring.PB.Position.Y + _rij.Y); - } - } - - private void InitializeElasticity() - { - _isElasticityInitialized = true; - - foreach (Particle particle in Particles) - { - _springPresenceTable.Add(particle.GetHashCode(), new List(MaxParticleLimit)); - particle.Velocity = Vector2.Zero; - } - } - - private void CalculateElasticity(float deltaTime) - { - float sqDist; - for (int i = 0; i < Particles.Count; i++) - { - Particle pa = Particles[i]; - - if (Particles.CountNearBy(pa) <= 1) - continue; - - _tempParticles = Particles.GetNearby(pa); - int len2 = _tempParticles.Count; - - if (len2 > MaxNeighbors) - len2 = MaxNeighbors; - - for (int j = 0; j < len2; j++) - { - Particle pb = Particles[j]; - Vector2.DistanceSquared(ref pa.Position, ref pb.Position, out sqDist); - if (sqDist > RestLengthSquared) - continue; - if (pa.GetHashCode() == pb.GetHashCode()) - continue; - if (!_springPresenceTable[pa.GetHashCode()].Contains(pb.GetHashCode())) - { - _springs.Add(new Spring2(pa, pb, RestLength)); - _springPresenceTable[pa.GetHashCode()].Add(pb.GetHashCode()); - } - } - } - - for (int i = _springs.Count - 1; i >= 0; i--) - { - Spring2 spring = _springs[i]; - spring.Update(); - - // Stretch - if (spring.CurrentDistance > (spring.RestLength + DeformationFactor)) - { - spring.RestLength += deltaTime * Plasticity * (spring.CurrentDistance - spring.RestLength - (YieldRatioStretch * spring.RestLength)); - } - // Compress - else if (spring.CurrentDistance < (spring.RestLength - DeformationFactor)) - { - spring.RestLength -= deltaTime * Plasticity * (spring.RestLength - (YieldRatioCompress * spring.RestLength) - spring.CurrentDistance); - } - // Remove springs with restLength longer than REST_LENGTH - if (spring.RestLength > RestLength) - { - _springs.RemoveAt(i); - _springPresenceTable[spring.PA.GetHashCode()].Remove(spring.PB.GetHashCode()); - } - else - { - if (spring.CurrentDistance == 0) - continue; - - Vector2.Subtract(ref spring.PB.Position, ref spring.PA.Position, out _rij); - _rij /= spring.CurrentDistance; - float D = deltaTime * KSpring * (spring.RestLength - spring.CurrentDistance); - _rij *= (D * 0.5f); - spring.PA.Position = new Vector2(spring.PA.Position.X - _rij.X, spring.PA.Position.Y - _rij.Y); - spring.PB.Position = new Vector2(spring.PB.Position.X + _rij.X, spring.PB.Position.Y + _rij.Y); - } - } - } - - private void ApplyGravity(Particle particle) - { - particle.Velocity = new Vector2(particle.Velocity.X + Gravity.X, particle.Velocity.Y + Gravity.Y); - } - - private void ApplyViscosity(float deltaTime) - { - float u, q; - for (int i = 0; i < Particles.Count; i++) - { - Particle particle = Particles[i]; - - _tempParticles = Particles.GetNearby(particle); - - int len2 = _tempParticles.Count; - if (len2 > MaxNeighbors) - len2 = MaxNeighbors; - - for (int j = 0; j < len2; j++) - { - Particle tempParticle = _tempParticles[j]; - - Vector2.DistanceSquared(ref particle.Position, ref tempParticle.Position, out q); - if ((q < InfluenceRadiusSquared) && (q != 0)) - { - q = (float)Math.Sqrt(q); - Vector2.Subtract(ref tempParticle.Position, ref particle.Position, out _rij); - Vector2.Divide(ref _rij, q, out _rij); - - Vector2.Subtract(ref particle.Velocity, ref tempParticle.Velocity, out _tempVect); - Vector2.Dot(ref _tempVect, ref _rij, out u); - if (u <= 0.0f) - continue; - - q /= InfluenceRadius; - - float I = (deltaTime * (1 - q) * (ViscositySigma * u + ViscosityBeta * u * u)); - Vector2.Multiply(ref _rij, (I * 0.5f), out _rij); - Vector2.Subtract(ref particle.Velocity, ref _rij, out _tempVect); - particle.Velocity = _tempVect; - _tempVect = tempParticle.Velocity; - _tempVect += _rij; - tempParticle.Velocity = _tempVect; - } - } - } - } - - private void DoubleDensityRelaxation() - { - float q; - for (int i = 0; i < Particles.Count; i++) - { - Particle particle = Particles[i]; - particle.Density = 0; - particle.NearDensity = 0; - - _tempParticles = Particles.GetNearby(particle); - - int len2 = _tempParticles.Count; - if (len2 > MaxNeighbors) - len2 = MaxNeighbors; - - for (int j = 0; j < len2; j++) - { - Particle tempParticle = _tempParticles[j]; - - Vector2.DistanceSquared(ref particle.Position, ref tempParticle.Position, out q); - if (q < InfluenceRadiusSquared && q != 0) - { - q = (float)Math.Sqrt(q); - q /= InfluenceRadius; - float qq = ((1 - q) * (1 - q)); - particle.Density += qq; - particle.NearDensity += qq * (1 - q); - } - } - - particle.Pressure = (Stiffness * (particle.Density - DensityRest)); - particle.NearPressure = (StiffnessNear * particle.NearDensity); - _dx = Vector2.Zero; - - for (int j = 0; j < len2; j++) - { - Particle tempParticle = _tempParticles[j]; - - Vector2.DistanceSquared(ref particle.Position, ref tempParticle.Position, out q); - if ((q < InfluenceRadiusSquared) && (q != 0)) - { - q = (float)Math.Sqrt(q); - Vector2.Subtract(ref tempParticle.Position, ref particle.Position, out _rij); - Vector2.Divide(ref _rij, q, out _rij); - q /= InfluenceRadius; - - float D = (_deltaTime2 * (particle.Pressure * (1 - q) + particle.NearPressure * (1 - q) * (1 - q))); - Vector2.Multiply(ref _rij, (D * 0.5f), out _rij); - tempParticle.Position = new Vector2(tempParticle.Position.X + _rij.X, tempParticle.Position.Y + _rij.Y); - Vector2.Subtract(ref _dx, ref _rij, out _dx); - } - } - particle.Position = particle.Position + _dx; - } - } - - public void Update(float deltaTime) - { - if (deltaTime == 0) - return; - - _deltaTime2 = deltaTime * deltaTime; - - ApplyViscosity(deltaTime); - - //Update velocity - UpdateParticleVelocity(deltaTime); - - Particles.Rehash(); - - if (_elasticityEnabled) - CalculateElasticity(deltaTime); - - if (_plasticityEnabled) - CalculatePlasticity(deltaTime); - - DoubleDensityRelaxation(); - - for(int i = 0; i < Particles.Count; i++) - { - Particle particle = Particles[i]; - particle.Velocity = new Vector2((particle.Position.X - particle.PreviousPosition.X) / deltaTime, (particle.Position.Y - particle.PreviousPosition.Y) / deltaTime); - ApplyGravity(particle); - WallCollision(particle); - CapVelocity(particle.Velocity); - } - } - - public void AddParticle(Vector2 position) - { - Particles.Add(new Particle(position.X, position.Y)); - } - - } - - public class Particle - { - public float Density; - public float NearDensity; - public float NearPressure; - public Vector2 Position = new Vector2(0, 0); - public float Pressure; - public Vector2 PreviousPosition = new Vector2(0, 0); - public Vector2 Velocity = new Vector2(0, 0); - - public Particle(float posX, float posY) - { - Position = new Vector2(posX, posY); - } - } - - public class Spring2 - { - public float CurrentDistance; - public Particle PA; - public Particle PB; - public float RestLength; - - public Spring2(Particle pa, Particle pb, float restLength) - { - PA = pa; - PB = pb; - RestLength = restLength; - } - - public void Update() - { - Vector2.Distance(ref PA.Position, ref PB.Position, out CurrentDistance); - } - - public bool Contains(Particle p) - { - return (PA.GetHashCode() == p.GetHashCode() || PB.GetHashCode() == p.GetHashCode()); - } - } -} \ No newline at end of file diff --git a/Simulacion/Fluids/2/SpartialTable.cs b/Simulacion/Fluids/2/SpartialTable.cs deleted file mode 100644 index e8ac716..0000000 --- a/Simulacion/Fluids/2/SpartialTable.cs +++ /dev/null @@ -1,179 +0,0 @@ -/* Original source Farseer Physics Engine: - * Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com - * Microsoft Permissive License (Ms-PL) v1.1 - */ - -using System.Collections; -using System.Collections.Generic; - -namespace tainicom.Aether.Physics2D.Fluids -{ - public class SpatialTable : IEnumerable - { - // default nearby table size - private const int DefaultNearbySize = 50; - private List _table; - private List _voidList = new List(1); - private List[][] _nearby; - bool _initialized; - - private int _row; - private int _column; - private int _cellSize; - - public SpatialTable(int column, int row, int cellSize) - { - _row = row; - _cellSize = cellSize; - _column = column; - } - - public void Initialize() - { - _table = new List((_row * _column) / 2); - _nearby = new List[_column][]; - - for (int i = 0; i < _column; ++i) - { - _nearby[i] = new List[_row]; - - for (int j = 0; j < _row; ++j) - { - _nearby[i][j] = new List(DefaultNearbySize); - } - } - _initialized = true; - } - - /// - /// Append value to the table and identify its position in the space. - /// Don't need to rehash table after append operation. - /// - public void Add(Particle value) - { - if (!_initialized) - Initialize(); - - AddInterRadius(value); - _table.Add(value); - } - - public Particle this[int i] - { - get { return _table[i]; } - set { _table[i] = value; } - } - - public void Remove(Particle value) - { - _table.Remove(value); - } - - public void Clear() - { - for (int i = 0; i < _column; ++i) - { - for (int j = 0; j < _row; ++j) - { - _nearby[i][j].Clear(); - _nearby[i][j] = null; - } - } - _table.Clear(); - } - - public int Count - { - get { return (_table == null)? 0 : _table.Count; } - } - - public List GetNearby(Particle value) - { - int x = posX(value); - int y = posY(value); - - if (!InRange(x, y)) - return _voidList; - - return _nearby[x][y]; - } - - private int posX(Particle value) - { - return (int)((value.Position.X + (_column / 2) + 0.3f) / _cellSize); - } - - private int posY(Particle value) - { - return (int)((value.Position.Y + 0.3f) / _cellSize); - } - - public int CountNearBy(Particle value) - { - return GetNearby(value).Count; - } - - /// - /// Updates the spatial relationships of objects. Rehash function - /// needed if elements change their position in the space. - /// - public void Rehash() - { - if (_table == null || _table.Count == 0) - return; - - for (int i = 0; i < _column; i++) - { - for (int j = 0; j < _row; j++) - { - if (_nearby[i][j] != null) - _nearby[i][j].Clear(); - } - } - - foreach (Particle particle in _table) - { - AddInterRadius(particle); - } - } - - /// - /// Add element to its position and neighbor cells. - /// - /// - private void AddInterRadius(Particle value) - { - for (int i = -1; i < 2; ++i) - { - for (int j = -1; j < 2; ++j) - { - int x = posX(value) + i; - int y = posY(value) + j; - if (InRange(x, y)) - _nearby[x][y].Add(value); - } - } - } - - /// - /// Check if a position is out of the spatial range - /// - /// - /// - /// true if position is in range. - private bool InRange(float x, float y) - { - return (x > 0 && x < _column && y > 0 && y < _row); - } - - public IEnumerator GetEnumerator() - { - return _table.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} diff --git a/Simulacion/Fluids/Components/ComponentesFluidos.cs b/Simulacion/Fluids/Components/ComponentesFluidos.cs deleted file mode 100644 index 7534106..0000000 --- a/Simulacion/Fluids/Components/ComponentesFluidos.cs +++ /dev/null @@ -1,412 +0,0 @@ -using System; -using System.Collections.Generic; -using nkast.Aether.Physics2D.Common; -using tainicom.Aether.Physics2D.Fluids; -using CtrEditor.Simulacion.Fluids.Components; - -namespace CtrEditor.Simulacion.Fluids.Components -{ - /// - /// Clase base para todos los componentes de fluidos - /// - public abstract class ComponenteFluido : IContenedorFluido - { - public Vector2 Posicion { get; set; } - public bool EsActivo { get; set; } = true; - - /// - /// Convierte una posición de metros/WPF a sistema de coordenadas de FluidSystem2 - /// - protected Vector2 ConvertirPosicion(Vector2 posicion, int worldWidth) - { - return new Vector2( - posicion.X * 100 - worldWidth/2, - posicion.Y * 100 - ); - } - - /// - /// Implementación en clases derivadas para restricciones específicas - /// - public abstract void RestringirParticula(Particle particula); - } - - /// - /// Tanque para contener fluidos - /// - public class Tanque : ComponenteFluido - { - public float Ancho { get; set; } - public float Alto { get; set; } - private int _worldWidth; - - // Coordenadas ajustadas al sistema de FluidSystem2 - private float _xMin, _xMax, _yMin, _yMax; - private float _coefRebote = 0.3f; // Factor de rebote en colisiones - - /// - /// Constructor para un tanque - /// - /// Posición del centro del tanque - /// Ancho del tanque - /// Alto del tanque - /// Ancho del mundo en cm (para conversión de coordenadas) - public Tanque(Vector2 posicion, float ancho, float alto, int worldWidth) - { - Posicion = posicion; - Ancho = ancho; - Alto = alto; - _worldWidth = worldWidth; - - ActualizarLimites(); - } - - /// - /// Actualiza los límites internos del tanque después de cambiar la posición o tamaño - /// - public void ActualizarLimites() - { - // Convertir a coordenadas internas - _xMin = (Posicion.X - Ancho/2) * 100 - _worldWidth/2; - _xMax = (Posicion.X + Ancho/2) * 100 - _worldWidth/2; - _yMin = Posicion.Y * 100; - _yMax = (Posicion.Y + Alto) * 100; - } - - /// - /// Restringe las partículas para que permanezcan dentro del tanque - /// - public override void RestringirParticula(Particle particula) - { - if (!EsActivo) return; - - // Comprobar si la partícula está dentro del área del tanque - bool dentroX = particula.Position.X >= _xMin && particula.Position.X <= _xMax; - bool dentroY = particula.Position.Y >= _yMin && particula.Position.Y <= _yMax; - - if (dentroX && dentroY) - { - // La partícula está dentro del tanque, comprobar si está cerca de los bordes - if (particula.Position.X < _xMin + 5) - { - particula.Position.X = _xMin + 5; - particula.Velocity.X *= -_coefRebote; - } - else if (particula.Position.X > _xMax - 5) - { - particula.Position.X = _xMax - 5; - particula.Velocity.X *= -_coefRebote; - } - - if (particula.Position.Y < _yMin + 5) - { - particula.Position.Y = _yMin + 5; - particula.Velocity.Y *= -_coefRebote; - } - else if (particula.Position.Y > _yMax - 5) - { - particula.Position.Y = _yMax - 5; - particula.Velocity.Y *= -_coefRebote; - } - } - } - - /// - /// Obtiene el nivel de llenado del tanque en porcentaje - /// - /// Instancia activa de la simulación - /// Porcentaje de llenado (0-1) - public float ObtenerNivelLlenado() - { - // Sería necesario acceder a las partículas para calcular esto - // Implementación en clases derivadas específicas - return 0.0f; - } - } - - /// - /// Representa un segmento de tubo para fluidos - /// - public class SegmentoTubo - { - public Vector2 Inicio { get; } - public Vector2 Fin { get; } - public float Radio { get; } - - public SegmentoTubo(Vector2 inicio, Vector2 fin, float radio) - { - Inicio = inicio; - Fin = fin; - Radio = radio; - } - - /// - /// Contiene y restringe el movimiento de una partícula dentro del tubo - /// - /// Verdadero si se aplicó alguna restricción - public bool ContenerParticula(Particle particula) - { - // Calcular distancia de la partícula al segmento de línea - Vector2 pos = new Vector2(particula.Position.X, particula.Position.Y); - float distancia = DistanciaPuntoALinea(pos, Inicio, Fin); - - // Si la distancia es mayor que el radio, aplicar restricción - if (distancia > Radio) - { - // Calcular punto más cercano en el segmento - Vector2 puntoMasCercano = PuntoMasCercanoEnLinea(pos, Inicio, Fin); - - // Dirección desde partícula hacia punto más cercano en el centro del tubo - Vector2 direccion = VectorUtils.NormalizeVector(puntoMasCercano - pos); - - // Mover la partícula al interior del tubo - particula.Position.X = puntoMasCercano.X - direccion.X * Radio * 0.9f; - particula.Position.Y = puntoMasCercano.Y - direccion.Y * Radio * 0.9f; - - // Ajustar velocidad para simular rebote - float velocidadNormal = VectorUtils.DotProduct( - new Vector2(particula.Velocity.X, particula.Velocity.Y), - direccion); - - particula.Velocity.X -= direccion.X * velocidadNormal * 1.8f; - particula.Velocity.Y -= direccion.Y * velocidadNormal * 1.8f; - - return true; - } - - return false; // La partícula está dentro del tubo - } - - /// - /// Calcula la distancia de un punto a una línea - /// - private float DistanciaPuntoALinea(Vector2 punto, Vector2 lineaInicio, Vector2 lineaFin) - { - // Vector que representa la dirección de la línea - Vector2 linea = lineaFin - lineaInicio; - float longitud = linea.Length(); - - if (longitud < 0.0001f) - return VectorUtils.DistanceBetween(punto, lineaInicio); // Es un punto, no una línea - - // Normalizar el vector de la línea - Vector2 lineaNormalizada = linea / longitud; - - // Vector desde el inicio de la línea hasta el punto - Vector2 puntoDesdeInicio = punto - lineaInicio; - - // Proyectar puntoDesdeInicio sobre la línea - float proyeccion = VectorUtils.DotProduct(puntoDesdeInicio, lineaNormalizada); - - // Limitar la proyección al segmento de línea - proyeccion = Math.Max(0, Math.Min(longitud, proyeccion)); - - // Punto más cercano en la línea - Vector2 puntoMasCercano = lineaInicio + lineaNormalizada * proyeccion; - - // Distancia desde el punto hasta el punto más cercano - return VectorUtils.DistanceBetween(punto, puntoMasCercano); - } - - /// Calcula el punto más cercano en una línea desde un punto dado - /// - private Vector2 PuntoMasCercanoEnLinea(Vector2 punto, Vector2 lineaInicio, Vector2 lineaFin) - { - // Vector que representa la dirección de la línea - Vector2 linea = lineaFin - lineaInicio; - float longitud = linea.Length(); - - if (longitud < 0.0001f) - return lineaInicio; // Es un punto, no una línea - - // Normalizar el vector de la línea - Vector2 lineaNormalizada = linea / longitud; - - // Vector desde el inicio de la línea hasta el punto - Vector2 puntoDesdeInicio = punto - lineaInicio; - - // Proyectar puntoDesdeInicio sobre la línea - float proyeccion; - Vector2.Dot(ref puntoDesdeInicio, ref lineaNormalizada, out proyeccion); - - // Limitar la proyección al segmento de línea - proyeccion = Math.Max(0, Math.Min(longitud, proyeccion)); - - // Punto más cercano en la línea - return lineaInicio + lineaNormalizada * proyeccion; - } - } - - /// - /// Tubería para conducir fluidos entre puntos - /// - public class Tuberia : ComponenteFluido - { - private List _segmentos = new List(); - private List _puntos = new List(); - public float Diametro { get; private set; } - private int _worldWidth; - - /// - /// Constructor para una tubería - /// - /// Diámetro de la tubería - /// Ancho del mundo en cm (para conversión de coordenadas) - public Tuberia(float diametro, int worldWidth) - { - Diametro = diametro; - _worldWidth = worldWidth; - } - - /// - /// Agrega un punto a la tubería, creando segmentos automáticamente - /// - public void AgregarPunto(Vector2 punto) - { - // Convertir punto a coordenadas internas - Vector2 puntoAjustado = ConvertirPosicion(punto, _worldWidth); - - // Si ya hay puntos, crear un segmento con el punto anterior - if (_puntos.Count > 0) - { - _segmentos.Add(new SegmentoTubo( - _puntos[_puntos.Count - 1], - puntoAjustado, - Diametro * 100 / 2 // Radio en unidades internas - )); - } - - _puntos.Add(puntoAjustado); - } - - /// - /// Restringe las partículas para que permanezcan dentro de la tubería - /// - public override void RestringirParticula(Particle particula) - { - if (!EsActivo) return; - - // Comprobar si la partícula está cerca de la tubería - Vector2 posParticula = new Vector2(particula.Position.X, particula.Position.Y); - float distanciaMinima = float.MaxValue; - SegmentoTubo segmentoMasCercano = null; - - // Encontrar el segmento más cercano - foreach (var segmento in _segmentos) - { - float distancia = DistanciaPuntoASegmento(posParticula, segmento.Inicio, segmento.Fin); - if (distancia < distanciaMinima) - { - distanciaMinima = distancia; - segmentoMasCercano = segmento; - } - } - - // Aplicar restricciones si hay un segmento cercano - if (segmentoMasCercano != null) - { - segmentoMasCercano.ContenerParticula(particula); - } - } - - /// - /// Calcula la distancia desde un punto a un segmento de línea - /// - private float DistanciaPuntoASegmento(Vector2 punto, Vector2 segmentoInicio, Vector2 segmentoFin) - { - // Vector dirección del segmento - Vector2 segmento = segmentoFin - segmentoInicio; - float longitudCuadrada = segmento.LengthSquared(); - - if (longitudCuadrada < 0.0001f) - return VectorUtils.DistanceBetween(punto, segmentoInicio); // Es un punto, no un segmento - - // Calcular proyección del punto sobre el segmento - float t = VectorUtils.DotProduct(punto - segmentoInicio, segmento) / longitudCuadrada; - t = Math.Max(0, Math.Min(1, t)); - - // Punto más cercano en el segmento - Vector2 proyeccion = segmentoInicio + t * segmento; - - // Distancia desde el punto hasta la proyección - return VectorUtils.DistanceBetween(punto, proyeccion); - } - } - - /// - /// Válvula para controlar el flujo de fluidos - /// - public class Valvula : ComponenteFluido - { - public float Apertura { get; set; } // 0.0 = cerrada, 1.0 = abierta - public float Diametro { get; set; } - private int _worldWidth; - private Vector2 _posicionAjustada; - - /// - /// Constructor para una válvula - /// - /// Posición de la válvula - /// Diámetro del conducto de la válvula - /// Apertura inicial (0-1) - /// Ancho del mundo en cm (para conversión de coordenadas) - public Valvula(Vector2 posicion, float diametro, float apertura, int worldWidth) - { - Posicion = posicion; - Diametro = diametro; - Apertura = Math.Clamp(apertura, 0, 1); - _worldWidth = worldWidth; - - _posicionAjustada = ConvertirPosicion(posicion, worldWidth); - } - - /// - /// Actualiza la posición ajustada después de cambiar la posición - /// - public void ActualizarPosicion(Vector2 nuevaPosicion) - { - Posicion = nuevaPosicion; - _posicionAjustada = ConvertirPosicion(nuevaPosicion, _worldWidth); - } - - /// - /// Restringe y modula el flujo de partículas a través de la válvula - /// - public override void RestringirParticula(Particle particula) - { - if (!EsActivo) return; - - // Calcular distancia al centro de la válvula - Vector2 posParticula = new Vector2(particula.Position.X, particula.Position.Y); - float distancia = VectorUtils.DistanceBetween(_posicionAjustada, posParticula); - float radioValvula = Diametro * 100 / 2; // Radio en unidades internas - - // Verificar si la partícula está dentro del radio de acción de la válvula - if (distancia < radioValvula * 1.2) - { - // Si la válvula está cerrada o casi cerrada, bloquear paso - if (Apertura < 0.05f) - { - // Rechazar partícula - Vector2 direccion = VectorUtils.NormalizeVector(posParticula - _posicionAjustada); - particula.Position.X = _posicionAjustada.X + direccion.X * (radioValvula * 1.2f); - particula.Position.Y = _posicionAjustada.Y + direccion.Y * (radioValvula * 1.2f); - - // Reducir velocidad significativamente - particula.Velocity.X *= 0.1f; - particula.Velocity.Y *= 0.1f; - } - // Si está parcialmente abierta, reducir velocidad proporcionalmente - else if (Apertura < 0.9f) - { - // Calcular factor de reducción basado en la apertura - float factorReduccion = Apertura * Apertura; // Relación no lineal - - // Aplicar resistencia proporcional a la apertura - particula.Velocity.X *= factorReduccion; - particula.Velocity.Y *= factorReduccion; - } - } - } - } -} diff --git a/Simulacion/Fluids/Components/VectorUtils.cs b/Simulacion/Fluids/Components/VectorUtils.cs deleted file mode 100644 index 660baa6..0000000 --- a/Simulacion/Fluids/Components/VectorUtils.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using nkast.Aether.Physics2D.Common; - -namespace CtrEditor.Simulacion.Fluids.Components -{ - /// - /// Utilidades para operaciones con Vector2 de nkast.Aether.Physics2D.Common - /// - public static class VectorUtils - { - /// - /// Calcula la distancia entre dos vectores - /// - public static float DistanceBetween(Vector2 v1, Vector2 v2) - { - float result; - Vector2 v1Ref = v1; - Vector2 v2Ref = v2; - Vector2.Distance(ref v1Ref, ref v2Ref, out result); - return result; - } - - /// - /// Calcula el producto punto entre dos vectores - /// - public static float DotProduct(Vector2 v1, Vector2 v2) - { - float result; - Vector2 v1Ref = v1; - Vector2 v2Ref = v2; - Vector2.Dot(ref v1Ref, ref v2Ref, out result); - return result; - } - - /// - /// Normaliza un vector y retorna el resultado - /// - public static Vector2 NormalizeVector(Vector2 v) - { - Vector2 result; - float length = v.Length(); - - if (length < 1e-6f) - return new Vector2(0, 0); - - Vector2.Divide(ref v, length, out result); - return result; - } - - /// - /// Calcula la reflexión de un vector respecto a una normal - /// - public static Vector2 Reflect(Vector2 vector, Vector2 normal) - { - Vector2 normalized = NormalizeVector(normal); - float dot; - Vector2 vectorRef = vector; - Vector2 normalizedRef = normalized; - Vector2.Dot(ref vectorRef, ref normalizedRef, out dot); - - Vector2 result; - Vector2.Multiply(ref normalizedRef, 2f * dot, out result); - Vector2 vectorRef2 = vector; - Vector2 resultRef = result; - Vector2.Subtract(ref vectorRef2, ref resultRef, out result); - - return result; - } - } -} diff --git a/Simulacion/Fluids/SimulacionFluidos.cs b/Simulacion/Fluids/SimulacionFluidos.cs deleted file mode 100644 index 3cbdf4e..0000000 --- a/Simulacion/Fluids/SimulacionFluidos.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; -using System.Collections.Generic; -using nkast.Aether.Physics2D.Common; -using tainicom.Aether.Physics2D.Fluids; -using CtrEditor.Simulacion.Fluids.Components; - -namespace CtrEditor.Simulacion.Fluids -{ - /// - /// Clase principal que gestiona la simulación de fluidos basada en FluidSystem2 - /// - public class SimulacionFluidos - { - public FluidSystem2 SistemaFluido { get; private set; } - private List _contenedores = new List(); - private int _worldWidth; - private int _worldHeight; - - public int ParticlesCount => SistemaFluido?.ParticlesCount ?? 0; - - /// - /// Constructor para el sistema de simulación de fluidos - /// - /// Ancho del área de simulación en metros - /// Alto del área de simulación en metros - /// Número máximo de partículas - /// Vector de gravedad - public SimulacionFluidos(float ancho, float alto, int maxParticulas, Vector2 gravedad) - { - // Convertir parámetros a unidades internas de FluidSystem2 - _worldWidth = (int)(ancho * 100); // Convertir a cm - _worldHeight = (int)(alto * 100); // Convertir a cm - - // Inicializar el sistema - SistemaFluido = new FluidSystem2( - gravedad, - maxParticulas, - _worldWidth, - _worldHeight - ); - - // Configurar comportamiento del fluido - SistemaFluido.ElasticityEnabled = true; - SistemaFluido.PlasticityEnabled = true; - } - - /// - /// Método para agregar una partícula al sistema - /// - /// Posición en metros - public void AgregarParticula(Vector2 posicion) - { - // Convertir a sistema de coordenadas de FluidSystem2 - float xAjustado = posicion.X * 100 - _worldWidth/2; - float yAjustado = posicion.Y * 100; - - SistemaFluido.AddParticle(new Vector2(xAjustado, yAjustado)); - } - - /// - /// Agrega múltiples partículas en un área rectangular - /// - /// Centro del área - /// Ancho del área - /// Alto del área - /// Cantidad de partículas a agregar - public void AgregarParticulasEnArea(Vector2 centro, float ancho, float alto, int cantidad) - { - Random rnd = new Random(); - for (int i = 0; i < cantidad; i++) - { - float x = centro.X - ancho/2 + (float)rnd.NextDouble() * ancho; - float y = centro.Y - alto/2 + (float)rnd.NextDouble() * alto; - AgregarParticula(new Vector2(x, y)); - } - } - - /// - /// Agrega un contenedor al sistema de fluidos - /// - /// Implementación de IContenedorFluido - public void AgregarContenedor(IContenedorFluido contenedor) - { - _contenedores.Add(contenedor); - } - - /// - /// Elimina un contenedor del sistema de fluidos - /// - /// Contenedor a eliminar - public void RemoverContenedor(IContenedorFluido contenedor) - { - _contenedores.Remove(contenedor); - } - - /// - /// Actualiza la simulación avanzando un paso de tiempo - /// - /// Tiempo transcurrido en segundos - public void Actualizar(float deltaTime) - { - // Aplicar restricciones de contenedores personalizados - foreach (var contenedor in _contenedores) - { - for (int i = 0; i < SistemaFluido.ParticlesCount; i++) - { - var particula = SistemaFluido.Particles[i]; - contenedor.RestringirParticula(particula); - } - } - - // Actualizar la física del sistema de fluidos - SistemaFluido.Update(deltaTime); - } - - /// - /// Obtiene la densidad del fluido en una posición específica - /// - /// Posición en metros - /// Densidad relativa de fluido (0-1) - public float ObtenerDensidadEnPosicion(Vector2 posicion) - { - // Convertir a sistema de coordenadas de FluidSystem2 - float xAjustado = posicion.X * 100 - _worldWidth/2; - float yAjustado = posicion.Y * 100; - Vector2 posAjustada = new Vector2(xAjustado, yAjustado); - - float densidadTotal = 0; - int particulasCercanas = 0; - - // Buscar partículas cercanas y sumar sus densidades - foreach (var p in SistemaFluido.Particles) - { - float distancia; -Vector2 posAjustadaRef = posAjustada; -Vector2 positionRef = p.Position; -Vector2.Distance(ref posAjustadaRef, ref positionRef, out distancia); - if (distancia < FluidSystem2.InfluenceRadius) - { - densidadTotal += p.Density; - particulasCercanas++; - } - } - - // Calcular densidad promedio, normalizada - if (particulasCercanas > 0) - return Math.Min(1.0f, densidadTotal / (particulasCercanas * FluidSystem2.DensityRest * 1.5f)); - - return 0; - } - - /// - /// Ajusta la gravedad del sistema - /// - /// Nuevo vector de gravedad - public void AjustarGravedad(Vector2 gravedad) - { - SistemaFluido.Gravity = gravedad; - } - - /// - /// Limpia todas las partículas del sistema - /// - public void LimpiarParticulas() - { - // No hay método clear() directo en FluidSystem2, recreamos el sistema - SistemaFluido = new FluidSystem2( - SistemaFluido.Gravity, - SistemaFluido.MaxParticleLimit, - _worldWidth, - _worldHeight - ); - - // Restaurar configuración - SistemaFluido.ElasticityEnabled = true; - SistemaFluido.PlasticityEnabled = true; - } - } - - /// - /// Interfaz para contenedores de fluido personalizados como tubos, tanques, etc. - /// - public interface IContenedorFluido - { - /// - /// Aplica restricciones a una partícula para mantenerla dentro o fuera del contenedor - /// - void RestringirParticula(Particle particula); - } -} diff --git a/Simulacion/GeometrySimulator.cs b/Simulacion/GeometrySimulator.cs deleted file mode 100644 index 6a32261..0000000 --- a/Simulacion/GeometrySimulator.cs +++ /dev/null @@ -1,375 +0,0 @@ -using CtrEditor.ObjetosSim; -using OpenCvSharp; -using System; -using System.Collections.Generic; -using System.Diagnostics.Eventing.Reader; -using System.Numerics; -using System.Windows.Shapes; - -public class Circle -{ - private Vector2 position; - public float Left - { - get { return position.X; } - set { position.X = value; } - } - public float Top - { - get { return position.Y; } - set { position.Y = value; } - } - public float Diameter { get; set; } - public float Mass { get; set; } - public float AngleofMovement { get; set; } // En grados - public float Speed { get; set; } - public float Overlap { get; set; } - - public Circle(float left = 0, float top = 0, float diameter = 10, float mass = 1, float angle = 0, float speed = 0) - { - position = new Vector2(left, top); - Diameter = diameter; - Mass = mass; - AngleofMovement = angle; - Speed = speed; - } - - public void Move(float timeStep_ms, List circles, List rectangles, List lines) - { - // Convertir timeStep de milisegundos a segundos para la simulación - float timeStepInSeconds = timeStep_ms / 1000.0f; - bool isTracted = false; // Indicador para verificar si el círculo está siendo traccionado - Overlap = 0; - - // Aplicar fuerza desde el rectángulo si está sobre uno - foreach (var rectangle in rectangles) - { - float overlap = CalculateOverlapPercentage(this, rectangle); - if (overlap > 10) - { - Overlap += overlap; - isTracted = true; // El círculo está siendo traccionado por un rectángulo - // Convertir la velocidad del rectángulo de metros por minuto a metros por segundo - float rectangleSpeedInMetersPerSecond = rectangle.Speed / 60.0f; - - if (rectangleSpeedInMetersPerSecond < Speed) - { - // Aplicar una fuerza de frenado si la velocidad del rectángulo es menor que la velocidad del círculo - float brakingForce = (Speed - rectangleSpeedInMetersPerSecond) * (overlap / 100.0f); - Speed -= brakingForce * timeStepInSeconds; - } - else - { - // Alinear gradualmente la velocidad del círculo con la del rectángulo si es mayor - Speed += (rectangleSpeedInMetersPerSecond - Speed) * (overlap / 100.0f) * timeStepInSeconds; - } - - AngleofMovement = rectangle.Angle; - } - } - - // Si el círculo no está siendo traccionado, aplicar desaceleración - if (!isTracted) - { - float deceleration = (1.0f / Mass) * 10.0f; // Coeficiente de desaceleración inversamente proporcional a la masa - Speed -= deceleration * timeStepInSeconds; - if (Speed < 0) Speed = 0; // Evitar que la velocidad sea negativa - } - - // Calcular nueva posición - Vector2 direction = new Vector2((float)Math.Cos(AngleofMovement * Math.PI / 180), (float)Math.Sin(AngleofMovement * Math.PI / 180)); - Vector2 velocity = direction * Speed * timeStepInSeconds; - position += velocity; - - // Ajustar por colisiones con líneas - foreach (var line in lines) - { - Vector2 movementVector = Vector2FromPolar(Speed, AngleofMovement); - Vector2 circleCenter = GetCircleCenter(this); - - // Calcular la nueva posición tentativa del centro del círculo - Vector2 newPosition = circleCenter + movementVector * timeStepInSeconds; - - if (LineCircleCollision(newPosition, Diameter / 2, line, out Vector2 collisionPoint)) - { - // Ajustar la posición del centro del círculo y el vector de movimiento - AdjustCircleAfterCollision(ref newPosition, ref movementVector, line, collisionPoint, Diameter / 2); - } - - // Actualizar la posición del círculo basada en el nuevo centro - Left = newPosition.X - Diameter / 2; - Top = newPosition.Y - Diameter / 2; - Speed = movementVector.Length(); - AngleofMovement = PolarAngleFromVector(movementVector); - } - - // Ajustar por superposición con otros círculos - foreach (var other in circles) - { - if (this != other && IsColliding(this, other)) - { - AdjustForOverlap(other); - } - } - - } - - Vector2 GetCircleCenter(Circle circle) - { - return new Vector2(circle.Left + circle.Diameter / 2, circle.Top + circle.Diameter / 2); - } - - - private bool LineCircleCollision(Vector2 circleCenter, float radius, Line line, out Vector2 collisionPoint) - { - // Transformar la línea a un vector con el ángulo rotado - float angleRadians = line.Angle * (float)Math.PI / 180; - Vector2 lineDirection = new Vector2( - (float)Math.Cos(angleRadians), - (float)Math.Sin(angleRadians) - ); - - // Calcular los puntos de inicio y fin de la línea - Vector2 lineStart = new Vector2(line.Left, line.Top); - Vector2 lineEnd = lineStart + lineDirection * line.Length; - - // Encontrar el punto más cercano del centro del círculo a la línea - Vector2 lineToCircle = circleCenter - lineStart; - float projection = Vector2.Dot(lineToCircle, lineDirection); - projection = Math.Clamp(projection, 0, line.Length); - Vector2 closestPointOnLine = lineStart + projection * lineDirection; - - // Calcular la distancia entre el círculo y el punto más cercano - float distance = Vector2.Distance(circleCenter, closestPointOnLine); - collisionPoint = closestPointOnLine; - - // Verificar si hay colisión - return distance <= radius; - } - - private void AdjustCircleAfterCollision(ref Vector2 circlePosition, ref Vector2 movementVector, Line line, Vector2 collisionPoint, float radius) - { - // Calcular el vector normal de la línea para reflejar la dirección de movimiento del círculo - float angleRadians = line.Angle * (float)Math.PI / 180; - Vector2 lineNormal = new Vector2(-(float)Math.Sin(angleRadians), (float)Math.Cos(angleRadians)); - - // Asegurar que la normal está correctamente orientada hacia fuera de la línea - if (Vector2.Dot(lineNormal, circlePosition - collisionPoint) < 0) - lineNormal = -lineNormal; - - // Calcular el desplazamiento necesario para separar el círculo de la línea - Vector2 displacement = lineNormal * (radius - Vector2.Distance(circlePosition, collisionPoint)); - circlePosition += displacement; - - // Opcionalmente, ajustar la velocidad si el círculo debe "rebotar" de la línea - movementVector = Vector2.Reflect(movementVector, lineNormal); - } - - - private Vector2 Vector2FromPolar(float speed, float angleDegrees) - { - float angleRadians = angleDegrees * (float)Math.PI / 180; - return new Vector2( - speed * (float)Math.Cos(angleRadians), - speed * (float)Math.Sin(angleRadians) - ); - } - - private float PolarAngleFromVector(Vector2 vector) - { - return (float)Math.Atan2(vector.Y, vector.X) * 180 / (float)Math.PI; - } - - - private void AdjustForOverlap(Circle other) - { - if (this == other) return; // No auto-interacción - - float distance = Vector2.Distance(this.position, other.position); - float radiusSum = (this.Diameter / 2) + (other.Diameter / 2); - - if (distance < radiusSum) // Los círculos están solapando - { - Vector2 directionToOther = Vector2.Normalize(other.position - this.position); - float overlapDistance = radiusSum - distance; - - // Decidir qué círculo mover basado en sus velocidades - if (this.Speed == 0 && other.Speed > 0) - { - // Mover este círculo si su velocidad es cero y el otro se está moviendo - this.position -= directionToOther * overlapDistance; - } - else if (other.Speed == 0 && this.Speed > 0) - { - // Mover el otro círculo si su velocidad es cero y este se está moviendo - other.position += directionToOther * overlapDistance; - } - else if (this.Speed == 0 && other.Speed == 0) - { - // Si ambos tienen velocidad cero, mover ambos a la mitad del solapamiento - this.position -= directionToOther * (overlapDistance / 2); - other.position += directionToOther * (overlapDistance / 2); - } - } - } - - - private bool IsColliding(Circle circle1, Circle circle2) - { - float distance = Vector2.Distance(circle1.position, circle2.position); - float radiusSum = (circle1.Diameter / 2) + (circle2.Diameter / 2); - return distance <= radiusSum; - } - - // Circulos sobre Rectangulos - public static float CalculateOverlapPercentage(Circle circle, Rectangle rectangle) - { - // Convertir el círculo en un cuadrado aproximado. - float squareSide = circle.Diameter / (float)Math.Sqrt(Math.PI); - RotatedRect square = new RotatedRect( - new Point2f(circle.Left + circle.Diameter / 2, circle.Top + circle.Diameter / 2), - new Size2f(squareSide, squareSide), - 0 // Sin rotación - ); - - // Ajustamos el rectángulo para que se considere rotado desde el centro, pero calculado desde Top-Left - RotatedRect rotatedRectangle = CreateRotatedRectFromTopLeft(rectangle); - - // Usar OpenCV para encontrar la intersección. - using (var mat = new Mat()) - { - var result = Cv2.RotatedRectangleIntersection(square, rotatedRectangle, mat); - if (result != RectanglesIntersectTypes.None) - { - // Calcular el área de la intersección - float intersectionArea = (float) Cv2.ContourArea(mat); - float circleArea = (float)(Math.PI * Math.Pow(circle.Diameter / 2, 2)); - return (intersectionArea / circleArea) * 100; - } - } - - return 0; // No hay intersección - } - - public static RotatedRect CreateRotatedRectFromTopLeft(Rectangle rectangle) - { - // El punto de pivote es Top-Left, calculamos el centro sin rotar - float originalCenterX = rectangle.Left + rectangle.Length / 2.0f; - float originalCenterY = rectangle.Top + rectangle.Width / 2.0f; - - // Convertimos el ángulo a radianes para la rotación - float angleRadians = rectangle.Angle * (float)Math.PI / 180; - - // Calcular las nuevas coordenadas del centro después de la rotación - float rotatedCenterX = rectangle.Left + (originalCenterX - rectangle.Left) * (float)Math.Cos(angleRadians) - (originalCenterY - rectangle.Top) * (float)Math.Sin(angleRadians); - float rotatedCenterY = rectangle.Top + (originalCenterX - rectangle.Left) * (float)Math.Sin(angleRadians) + (originalCenterY - rectangle.Top) * (float)Math.Cos(angleRadians); - - // Crear el RotatedRect con el nuevo centro y el tamaño original - RotatedRect rotatedRect = new RotatedRect( - new Point2f(rotatedCenterX, rotatedCenterY), - new Size2f(rectangle.Length, rectangle.Width), - rectangle.Angle - ); - - return rotatedRect; - } - - - -} - -public class Rectangle -{ - private Vector2 position; - public float Left - { - get { return position.X; } - set { position = new Vector2(value, position.Y); } - } - public float Top - { - get { return position.Y; } - set { position = new Vector2(position.X, value); } - } - public float Length { get; set; } - public float Width { get; set; } - public float Angle { get; set; } // En grados - public float Speed { get; set; } // Velocidad del rectángulo - - public Rectangle(float left = 0, float top = 0, float length = 10, float width = 10, float angle = 0, float speed = 0) - { - position = new Vector2(left, top); - Length = length; - Width = width; - Angle = angle; - Speed = speed; - } - -} - -public class Line -{ - private Vector2 position; - public float Left - { - get { return position.X; } - set { position = new Vector2(value, position.Y); } - } - public float Top - { - get { return position.Y; } - set { position = new Vector2(position.X, value); } - } - public float Length { get; set; } - public float Width { get; set; } - public float Angle { get; set; } // En grados - public float Grip { get; set; } // Friccion por contacto - - public Line(float left = 0, float top = 0, float length = 10, float angle = 0, float grip = 0) - { - position = new Vector2(left, top); - Length = length; - Angle = angle; - Grip = grip; - } -} - -public class Square -{ - public float Left { get; set; } - public float Top { get; set; } - public float Size { get; set; } // 'Size' es la longitud de un lado del cuadrado - - public Square(float left, float top, float size) - { - Left = left; - Top = top; - Size = size; - } -} - - - -// Clase principal que gestiona la simulación -public class SimulationManager -{ - public List circles; - public List rectangles; - public List lines; - - - public SimulationManager() - { - circles = new List(); - rectangles = new List(); - lines = new List(); - } - - public void Step(float timeStep) - { - foreach (var circle in circles) - { - circle.Move(timeStep, circles, rectangles, lines); - } - } -} \ No newline at end of file diff --git a/Simulacion/InterseccionCirculoRectangulo.cs b/Simulacion/InterseccionCirculoRectangulo.cs deleted file mode 100644 index 7e13661..0000000 --- a/Simulacion/InterseccionCirculoRectangulo.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.VisualBasic.Devices; -using nkast.Aether.Physics2D.Common; - -namespace CtrEditor.Simulacion -{ - internal class InterseccionCirculoRectangulo - { - // Definición de la función CalcularSuperficieCompartida - public static float CalcularSuperficieCompartida(Vector2[] vertices, Vector2 center, float radio) - { - float totalCircleArea = (float) (Math.PI * radio * radio); - - // Distancia a líneas ajustado - float[] distances = new float[4]; - for (int i = 0; i < 4; i++) - { - distances[i] = DistanceFromLine(center, vertices[i], vertices[(i + 1) % 4]); - } - - float minDistance = float.MaxValue; - foreach (var dist in distances) - { - if (Math.Abs(dist) < Math.Abs(minDistance)) - minDistance = dist; - } - float d = Math.Abs(minDistance); - - float sharedArea = 0; - if (Array.TrueForAll(distances, dist => Math.Abs(dist) > radio)) - { - sharedArea = totalCircleArea; - } - else if (d < radio) - { - float cosTheta = Math.Min(1, d / radio); - float sinTheta = (float)Math.Sqrt(Math.Max(0, radio * radio - d * d)); - if (minDistance < 0) // El centro está dentro del rectángulo - { - float areaOutside = radio * radio * (float)Math.Acos(cosTheta) - d * sinTheta; - sharedArea = totalCircleArea - areaOutside; - } - else // El centro está fuera del rectángulo - { - sharedArea = radio * radio * (float)Math.Acos(cosTheta) - d * sinTheta; - } - } - else - { - sharedArea = 0; - } - var area = (sharedArea / totalCircleArea) * 1.1f; - return area > 1 ? 1 : area; - } - - public static float DistanceFromLine(Vector2 point, Vector2 start, Vector2 end) - { - float A = end.Y - start.Y; - float B = start.X - end.X; - float C = end.X * start.Y - start.X * end.Y; - float distance = (A * point.X + B * point.Y + C) / (float)Math.Sqrt(A * A + B * B); - return distance; - } - - } - -} - - diff --git a/Simulacion/OverlapedArea.cs b/Simulacion/OverlapedArea.cs deleted file mode 100644 index 0848881..0000000 --- a/Simulacion/OverlapedArea.cs +++ /dev/null @@ -1,244 +0,0 @@ -using nkast.Aether.Physics2D.Collision.Shapes; -using nkast.Aether.Physics2D.Common; -using nkast.Aether.Physics2D.Dynamics; -using System.Diagnostics; -using CtrEditor.Simulacion; - -namespace CtrEditor.Simulacion -{ - - class OverlapedArea - { - public static float CalculateOverlapedArea(Body bodyA, Body bodyB) - { - List aVertices = GetRotatedVertices(bodyA); - List bVertices = GetRotatedVertices(bodyB); - - List intersectionPolygon = SutherlandHodgmanClip(aVertices, bVertices); - - //Debug.WriteLine(""); - //Debug.WriteLine(""); - //Debug.WriteLine("subjectPolygon :"); - //foreach (var vertex in aVertices) - //{ - // Debug.WriteLine(vertex); - //} - //Debug.WriteLine("clipPolygon :"); - //foreach (var vertex in bVertices) - //{ - // Debug.WriteLine(vertex); - //} - - //Debug.WriteLine("intersectionPolygon:"); - //foreach (var vertex in intersectionPolygon) - //{ - // Debug.WriteLine(vertex); - //} - return ComputePolygonArea(intersectionPolygon); - } - - public static float ComputePolygonArea(List polygon) - { - float area = 0; - int count = polygon.Count; - for (int i = 0; i < count; i++) - { - Vector2 current = polygon[i]; - Vector2 next = polygon[(i + 1) % count]; - area += current.X * next.Y - next.X * current.Y; - } - return Math.Abs(area) / 2.0f; - } - - public static List SutherlandHodgmanClip(List subjectPolygon, List clipPolygon) - { - List outputList = new List(subjectPolygon); - - for (int i = 0; i < clipPolygon.Count; i++) - { - Vector2 clipEdgeStart = clipPolygon[i]; - Vector2 clipEdgeEnd = clipPolygon[(i + 1) % clipPolygon.Count]; - List inputList = outputList; - outputList = new List(); - - for (int j = 0; j < inputList.Count; j++) - { - Vector2 currentVertex = inputList[j]; - Vector2 previousVertex = inputList[(j + inputList.Count - 1) % inputList.Count]; - - if (IsInside(clipEdgeStart, clipEdgeEnd, currentVertex)) - { - if (!IsInside(clipEdgeStart, clipEdgeEnd, previousVertex)) - { - outputList.Add(ComputeIntersection(clipEdgeStart, clipEdgeEnd, previousVertex, currentVertex)); - } - outputList.Add(currentVertex); - } - else if (IsInside(clipEdgeStart, clipEdgeEnd, previousVertex)) - { - outputList.Add(ComputeIntersection(clipEdgeStart, clipEdgeEnd, previousVertex, currentVertex)); - } - } - } - - return outputList; - } - - private static bool IsInside(Vector2 edgeStart, Vector2 edgeEnd, Vector2 point) - { - return (edgeEnd.X - edgeStart.X) * (point.Y - edgeStart.Y) > (edgeEnd.Y - edgeStart.Y) * (point.X - edgeStart.X); - } - - private static Vector2 ComputeIntersection(Vector2 edgeStart, Vector2 edgeEnd, Vector2 point1, Vector2 point2) - { - Vector2 edge = edgeEnd - edgeStart; - Vector2 segment = point2 - point1; - float edgeCrossSegment = Cross(edge, segment); - - if (Math.Abs(edgeCrossSegment) < float.Epsilon) - { - return point1; // Return any point on the segment since they are nearly parallel. - } - - float t = Cross(point1 - edgeStart, edge) / edgeCrossSegment; - return point1 + t * segment; - } - - public static float Cross(Vector2 v1, Vector2 v2) - { - return v1.X * v2.Y - v1.Y * v2.X; - } - - public static List GetRotatedRectangleVertices(Vector2 center, float width, float height, float rotation) - { - float halfWidth = width / 2.0f; - float halfHeight = height / 2.0f; - float cos = (float)Math.Cos(rotation); - float sin = (float)Math.Sin(rotation); - - return new List - { - new Vector2(center.X + cos * (-halfWidth) - sin * (-halfHeight), center.Y + sin * (-halfWidth) + cos * (-halfHeight)), - new Vector2(center.X + cos * (halfWidth) - sin * (-halfHeight), center.Y + sin * (halfWidth) + cos * (-halfHeight)), - new Vector2(center.X + cos * (halfWidth) - sin * (halfHeight), center.Y + sin * (halfWidth) + cos * (halfHeight)), - new Vector2(center.X + cos * (-halfWidth) - sin * (halfHeight), center.Y + sin * (-halfWidth) + cos * (halfHeight)) - }; - } - - public static List GetRotatedVertices(Body body) - { - List vertices = new List(); - - // Verificar el tipo de Shape del Body - foreach (var fixture in body.FixtureList) - { - if (fixture.Shape is PolygonShape polygonShape) - { - // Es un rectángulo o un polígono - foreach (var vertex in polygonShape.Vertices) - { - vertices.Add(RotatePoint(vertex, body.Position, body.Rotation)); - } - } - else if (fixture.Shape is CircleShape circleShape) - { - // Es un círculo - float radius = circleShape.Radius; - float halfSide = radius; // El lado del cuadrado inscrito es igual al radio - - Vector2[] squareVertices = new Vector2[] - { - new Vector2(-halfSide, -halfSide), - new Vector2(halfSide, -halfSide), - new Vector2(halfSide, halfSide), - new Vector2(-halfSide, halfSide) - }; - - foreach (var vertex in squareVertices) - { - vertices.Add(vertex + body.Position); // Trasladar el cuadrado a la posición del cuerpo - } - } - } - - return vertices; - } - - - private static Vector2 RotatePoint(Vector2 point, Vector2 origin, float rotation) - { - float cos = (float)Math.Cos(rotation); - float sin = (float)Math.Sin(rotation); - float x = point.X * cos - point.Y * sin; - float y = point.X * sin + point.Y * cos; - return new Vector2(x + origin.X, y + origin.Y); - } - } - - - public class OverlapedAreaFast - { - - public static float CalculateOverlapedArea(simBotella botella, simTransporte conveyor) - { - var porcentajeSuperpuesto = (float) (1.1 * CalculateOverlapPercentage(botella.Body.Position, botella.Radius, conveyor.Body.Position, conveyor.Width, conveyor.Height, conveyor.Body.Rotation)); - return porcentajeSuperpuesto > 1 ? 1 : porcentajeSuperpuesto; // 1.1 Porque las botellas apollan sobre un circulo inscripto 10 % menor. - } - - public static double[] RotatePoint(double px, double py, double ox, double oy, double angle) - { - double cosAngle = Math.Cos(angle); - double sinAngle = Math.Sin(angle); - - double qx = ox + cosAngle * (px - ox) - sinAngle * (py - oy); - double qy = oy + sinAngle * (px - ox) + cosAngle * (py - oy); - - return new double[] { qx, qy }; - } - - public static double CalculateOverlapPercentage( - Vector2 circleCenter, double circleRadius, Vector2 rectCenter, double rectWidth, double rectHeight, double rectAngle) - { - // Transform the center of the circle to the coordinate system of the rotated rectangle - double[] newCircleCenter = RotatePoint(circleCenter.X, circleCenter.Y, rectCenter.X, rectCenter.Y, -rectAngle); - - // Create a square with the same rotation as the rectangle - double squareSide = 2 * circleRadius; - double[] squareCenter = newCircleCenter; - - // Coordinates of the square (non-rotated) - double x3 = squareCenter[0] - circleRadius; - double y3 = squareCenter[1] - circleRadius; - double x4 = squareCenter[0] + circleRadius; - double y4 = squareCenter[1] + circleRadius; - - // Coordinates of the rectangle (non-rotated) - double x1 = rectCenter.X - rectWidth / 2; - double y1 = rectCenter.Y - rectHeight / 2; - double x2 = rectCenter.X + rectWidth / 2; - double y2 = rectCenter.Y + rectHeight / 2; - - // Limits of the intersection - double xLeft = Math.Max(x1, x3); - double xRight = Math.Min(x2, x4); - double yBottom = Math.Max(y1, y3); - double yTop = Math.Min(y2, y4); - - // Width and height of the intersection - double intersectWidth = Math.Max(0, xRight - xLeft); - double intersectHeight = Math.Max(0, yTop - yBottom); - - // Area of the intersection - double intersectionArea = intersectWidth * intersectHeight; - - // Area of the square - double squareArea = squareSide * squareSide; - - // Overlap percentage relative to the total area of the square - double squareOverlapPercentage = (squareArea > 0) ? (intersectionArea / squareArea) : 0; - - return squareOverlapPercentage; - } - } - -} diff --git a/SimulationFluidsViewModel.cs b/SimulationFluidsViewModel.cs index 21f3a60..ef40980 100644 --- a/SimulationFluidsViewModel.cs +++ b/SimulationFluidsViewModel.cs @@ -3,7 +3,7 @@ using System.Diagnostics; using System.Windows.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CtrEditor.Simulacion.Fluids; -using nkast.Aether.Physics2D.Common; +using System.Numerics; using CtrEditor.ObjetosSim; namespace CtrEditor