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; } } } } }