CtrEditor/Simulacion/Fluids/Components/ComponentesFluidos.cs

413 lines
16 KiB
C#

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
{
/// <summary>
/// Clase base para todos los componentes de fluidos
/// </summary>
public abstract class ComponenteFluido : IContenedorFluido
{
public Vector2 Posicion { get; set; }
public bool EsActivo { get; set; } = true;
/// <summary>
/// Convierte una posición de metros/WPF a sistema de coordenadas de FluidSystem2
/// </summary>
protected Vector2 ConvertirPosicion(Vector2 posicion, int worldWidth)
{
return new Vector2(
posicion.X * 100 - worldWidth/2,
posicion.Y * 100
);
}
/// <summary>
/// Implementación en clases derivadas para restricciones específicas
/// </summary>
public abstract void RestringirParticula(Particle particula);
}
/// <summary>
/// Tanque para contener fluidos
/// </summary>
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
/// <summary>
/// Constructor para un tanque
/// </summary>
/// <param name="posicion">Posición del centro del tanque</param>
/// <param name="ancho">Ancho del tanque</param>
/// <param name="alto">Alto del tanque</param>
/// <param name="worldWidth">Ancho del mundo en cm (para conversión de coordenadas)</param>
public Tanque(Vector2 posicion, float ancho, float alto, int worldWidth)
{
Posicion = posicion;
Ancho = ancho;
Alto = alto;
_worldWidth = worldWidth;
ActualizarLimites();
}
/// <summary>
/// Actualiza los límites internos del tanque después de cambiar la posición o tamaño
/// </summary>
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;
}
/// <summary>
/// Restringe las partículas para que permanezcan dentro del tanque
/// </summary>
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;
}
}
}
/// <summary>
/// Obtiene el nivel de llenado del tanque en porcentaje
/// </summary>
/// <param name="simulacion">Instancia activa de la simulación</param>
/// <returns>Porcentaje de llenado (0-1)</returns>
public float ObtenerNivelLlenado()
{
// Sería necesario acceder a las partículas para calcular esto
// Implementación en clases derivadas específicas
return 0.0f;
}
}
/// <summary>
/// Representa un segmento de tubo para fluidos
/// </summary>
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;
}
/// <summary>
/// Contiene y restringe el movimiento de una partícula dentro del tubo
/// </summary>
/// <returns>Verdadero si se aplicó alguna restricción</returns>
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
}
/// <summary>
/// Calcula la distancia de un punto a una línea
/// </summary>
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
/// </summary>
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;
}
}
/// <summary>
/// Tubería para conducir fluidos entre puntos
/// </summary>
public class Tuberia : ComponenteFluido
{
private List<SegmentoTubo> _segmentos = new List<SegmentoTubo>();
private List<Vector2> _puntos = new List<Vector2>();
public float Diametro { get; private set; }
private int _worldWidth;
/// <summary>
/// Constructor para una tubería
/// </summary>
/// <param name="diametro">Diámetro de la tubería</param>
/// <param name="worldWidth">Ancho del mundo en cm (para conversión de coordenadas)</param>
public Tuberia(float diametro, int worldWidth)
{
Diametro = diametro;
_worldWidth = worldWidth;
}
/// <summary>
/// Agrega un punto a la tubería, creando segmentos automáticamente
/// </summary>
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);
}
/// <summary>
/// Restringe las partículas para que permanezcan dentro de la tubería
/// </summary>
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);
}
}
/// <summary>
/// Calcula la distancia desde un punto a un segmento de línea
/// </summary>
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);
}
}
/// <summary>
/// Válvula para controlar el flujo de fluidos
/// </summary>
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;
/// <summary>
/// Constructor para una válvula
/// </summary>
/// <param name="posicion">Posición de la válvula</param>
/// <param name="diametro">Diámetro del conducto de la válvula</param>
/// <param name="apertura">Apertura inicial (0-1)</param>
/// <param name="worldWidth">Ancho del mundo en cm (para conversión de coordenadas)</param>
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);
}
/// <summary>
/// Actualiza la posición ajustada después de cambiar la posición
/// </summary>
public void ActualizarPosicion(Vector2 nuevaPosicion)
{
Posicion = nuevaPosicion;
_posicionAjustada = ConvertirPosicion(nuevaPosicion, _worldWidth);
}
/// <summary>
/// Restringe y modula el flujo de partículas a través de la válvula
/// </summary>
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;
}
}
}
}
}