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