using System;
using System.Collections.Generic;
using nkast.Aether.Physics2D.Common;
using tainicom.Aether.Physics2D.Fluids;
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 = Vector2.Normalize(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 = Vector2.Dot(
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 Vector2.Distance(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 = Vector2.Dot(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 Vector2.Distance(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(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
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 Vector2.Distance(punto, segmentoInicio); // Es un punto, no un segmento
// Calcular proyección del punto sobre el segmento
float t = Vector2.Dot(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 Vector2.Distance(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 = Vector2.Distance(_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 = Vector2.Normalize(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;
}
}
}
}
}