Primeras funciones de Fluidos

This commit is contained in:
Miguel 2025-04-13 17:07:54 +02:00
parent d1ec333243
commit 53af46ec06
10 changed files with 1736 additions and 0 deletions

View File

@ -0,0 +1,99 @@
# Guía de Uso: Sistema de Fluidos
## Introducción
El Sistema de Fluidos es una extensión para CtrEditor que permite simular el comportamiento de líquidos en elementos como tuberías, válvulas y tanques. Utiliza una implementación optimizada basada en el método SPH (Smoothed Particle Hydrodynamics) para la simulación de fluidos.
## Componentes del Sistema
El sistema de fluidos incluye los siguientes componentes:
1. **SistemaFluidos**: El componente principal que gestiona la simulación de partículas.
2. **TuberíaFluido**: Componente para crear tuberías por las que circula el fluido.
3. **ValvulaFluido**: Componente que permite regular el flujo del fluido.
## Añadir el Sistema de Fluidos
Para comenzar a utilizar el sistema de fluidos:
1. Inserte un componente "SistemaFluidos" en su diseño.
2. Configure el tamaño del área de simulación y la gravedad según sus necesidades.
3. Añada los componentes de tuberías y válvulas que necesite.
## Propiedades Principales
### SistemaFluidos
- **AnchoSimulacion/AltoSimulacion**: Dimensiones del área de simulación.
- **GravedadX/GravedadY**: Vector de gravedad para la simulación.
- **TamañoParticula**: Tamaño visual de las partículas de fluido.
- **ColorFluido**: Color base para las partículas de fluido.
- **OpacidadParticulas**: Transparencia de las partículas (0-1).
### TuberíaFluido
- **Diametro**: Diámetro de la tubería.
- **Color**: Color de la tubería.
- **ColorFluido**: Color del fluido dentro de la tubería.
### ValvulaFluido
- **Apertura**: Grado de apertura de la válvula (0=cerrada, 1=abierta).
- **DiametroTuberia**: Diámetro de las tuberías conectadas.
- **TagApertura**: Tag PLC para controlar la apertura.
## Integración con PLC
El sistema de fluidos permite integración con PLC mediante tags:
- **TagNivelTanque1**: Lectura/escritura del nivel de llenado del tanque.
- **TagAperturaValvula1**: Lectura/escritura de la apertura de la válvula.
## Añadir Fluido a la Simulación
Para añadir partículas de fluido:
1. Seleccione el componente SistemaFluidos.
2. Utilice el método `AgregarParticulasEnArea` para añadir partículas en una región.
Ejemplo:
```csharp
// Añadir 100 partículas en un área centrada en (x,y) con ancho y alto especificados
_sistemaFluidos.AgregarParticulasEnArea(new Vector2(x, y), ancho, alto, 100);
```
## Optimización
El Sistema de Fluidos utiliza `DrawingVisual` para una renderización eficiente, lo que permite simular miles de partículas con un impacto mínimo en el rendimiento.
## Limitaciones
- La simulación está optimizada para cantidades moderadas de partículas (hasta 10,000).
- Los contenedores (tuberías, válvulas) son aproximaciones y pueden presentar fugas o comportamientos inesperados con flujos muy rápidos.
## Recomendaciones
- Comience con pocas partículas y aumente gradualmente según sea necesario.
- Mantenga la configuración de gravedad en valores realistas.
- Para simular líquidos específicos, ajuste la visualización con el color y opacidad adecuados.
## Ejemplos de Uso
### Sistema Básico de Flujo
```
SistemaFluidos → TuberíaFluido → ValvulaFluido → TuberíaFluido → Tanque
```
### Circuito Cerrado
```
SistemaFluidos → TuberíaFluido → ValvulaFluido → TuberíaFluido → TuberíaFluido (retorno)
```
## Solución de Problemas
- **Partículas escapando de contenedores**: Verifique que los componentes estén correctamente posicionados y conectados.
- **Bajo rendimiento**: Reduzca el número de partículas o el tamaño visual.
- **Cambios de apertura de válvula no afectan el flujo**: Verifique la conexión PLC y la configuración de tags.

View File

@ -0,0 +1,279 @@
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
{
/// <summary>
/// ViewModel para el sistema de fluidos
/// </summary>
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: Description("Ancho del área de simulación en metros")]
[property: Category("Simulación:")]
private float anchoSimulacion = 10.0f;
[ObservableProperty]
[property: Description("Alto del área de simulación en metros")]
[property: Category("Simulación:")]
private float altoSimulacion = 10.0f;
// Propiedades del fluido
[ObservableProperty]
[property: Description("Tamaño visual de las partículas")]
[property: Category("Visual:")]
private float tamañoParticula = 0.01f;
[ObservableProperty]
[property: Description("Color del fluido")]
[property: Category("Visual:")]
private Color colorFluido = Colors.CornflowerBlue;
[ObservableProperty]
[property: Description("Opacidad de las partículas")]
[property: Category("Visual:")]
private double opacidadParticulas = 0.7;
// Propiedades de gravedad
[ObservableProperty]
[property: Description("Gravedad en X (m/s²)")]
[property: Category("Física:")]
private float gravedadX = 0.0f;
[ObservableProperty]
[property: Description("Gravedad en Y (m/s²)")]
[property: Category("Física:")]
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: Description("Número de partículas")]
[property: Category("Estadísticas:")]
private int numeroParticulas;
[ObservableProperty]
[property: Description("Rendimiento en FPS")]
[property: Category("Estadísticas:")]
private double fps;
// Referencia a componentes (solo para la función Add)
private List<IContenedorFluido> _contenedores = new List<IContenedorFluido>();
// Nombre de la clase para identificación
public static string NombreClase()
{
return "SistemaFluidos";
}
private string nombre = NombreClase();
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
// Métodos para interactuar con la simulación
/// <summary>
/// Agrega partículas en un punto específico
/// </summary>
public void AgregarParticula(Vector2 posicion)
{
_simFluidos?.AgregarParticula(posicion);
}
/// <summary>
/// Agrega múltiples partículas en un área
/// </summary>
public void AgregarParticulasEnArea(Vector2 centro, float ancho, float alto, int cantidad)
{
_simFluidos?.AgregarParticulasEnArea(centro, ancho, alto, cantidad);
}
/// <summary>
/// Crea un nuevo tanque y lo agrega a la simulación
/// </summary>
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;
}
/// <summary>
/// Crea una nueva tubería
/// </summary>
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;
}
/// <summary>
/// Crea una nueva válvula
/// </summary>
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;
}
/// <summary>
/// Elimina un componente de la simulación
/// </summary>
public void EliminarComponente(IContenedorFluido componente)
{
if (_simFluidos == null || componente == null) return;
_simFluidos.RemoverContenedor(componente);
_contenedores.Remove(componente);
}
/// <summary>
/// Limpia todas las partículas de la simulación
/// </summary>
public void LimpiarParticulas()
{
_simFluidos?.LimpiarParticulas();
}
/// <summary>
/// Actualiza el vector de gravedad según las propiedades
/// </summary>
private void ActualizarGravedad()
{
if (_simFluidos != null)
{
_simFluidos.AjustarGravedad(new Vector2(GravedadX, GravedadY));
}
}
/// <summary>
/// Constructor de la clase
/// </summary>
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;
}
}
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);
}
}
}
}

View File

@ -0,0 +1,33 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucSistemaFluidos"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim">
<UserControl.DataContext>
<vm:osSistemaFluidos/>
</UserControl.DataContext>
<Grid>
<Grid x:Name="ContenedorVisual" Background="Transparent">
<!-- El DrawingVisual renderizará las partículas aquí -->
</Grid>
<!-- Panel informativo opcional que se puede ocultar -->
<Border Padding="5"
Background="#60000000"
CornerRadius="5"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="5">
<StackPanel>
<TextBlock Text="{Binding Nombre}"
Foreground="White"
FontWeight="Bold"/>
<TextBlock Text="{Binding NumeroParticulas, StringFormat='Partículas: {0}'}"
Foreground="White"/>
<TextBlock Text="{Binding Fps, StringFormat='FPS: {0:F1}'}"
Foreground="White"/>
</StackPanel>
</Border>
</Grid>
</UserControl>

View File

@ -0,0 +1,188 @@
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
{
/// <summary>
/// Lógica para ucSistemaFluidos.xaml
/// </summary>
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;
}
/// <summary>
/// Actualiza la visualización de partículas
/// </summary>
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);
}
}
}
}
/// <summary>
/// Clase que maneja el rendering optimizado de partículas usando DrawingVisual
/// </summary>
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;
}
/// <summary>
/// Actualiza la visualización de las partículas de fluido
/// </summary>
/// <param name="particulas">Lista de partículas a visualizar</param>
/// <param name="tamañoParticula">Tamaño de las partículas en metros</param>
/// <param name="colorFluido">Color base del fluido</param>
/// <param name="opacidadBase">Opacidad base de las partículas</param>
public void ActualizarParticulasFluido(IEnumerable<Particle> 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();
}
/// <summary>
/// Mezcla dos colores según un factor (0-1)
/// </summary>
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);
}
}
}

View File

@ -0,0 +1,34 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucTuberiaFluido"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim">
<UserControl.DataContext>
<vm:osTuberiaFluido/>
</UserControl.DataContext>
<Grid>
<Path x:Name="TuberiaPath"
Stroke="{Binding Color, Converter={StaticResource ColorToBrushConverter}}"
StrokeThickness="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
Data="{Binding PathData}"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round"
StrokeLineJoin="Round">
<Path.Effect>
<BlurEffect Radius="0.5"/>
</Path.Effect>
</Path>
<!-- Visualización opcional de la densidad del fluido dentro de la tubería -->
<Path x:Name="FluidoPath"
Data="{Binding PathData}"
StrokeThickness="{Binding DiametroInterno, Converter={StaticResource MeterToPixelConverter}}"
Stroke="{Binding ColorFluido, Converter={StaticResource ColorToBrushConverter}}"
Opacity="{Binding DensidadFluido}"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round"
StrokeLineJoin="Round">
</Path>
</Grid>
</UserControl>

View File

@ -0,0 +1,258 @@
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
{
/// <summary>
/// Lógica para ucTuberiaFluido.xaml
/// </summary>
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;
}
}
/// <summary>
/// ViewModel para la tubería de fluidos
/// </summary>
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: Description("Diámetro de la tubería en metros")]
[property: Category("Dimensiones:")]
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: Description("Diámetro interno para visualización del fluido")]
[property: Category("Visual:")]
private float diametroInterno;
[ObservableProperty]
[property: Description("Color de la tubería")]
[property: Category("Visual:")]
private System.Windows.Media.Color color = System.Windows.Media.Colors.Gray;
[ObservableProperty]
[property: Description("Color del fluido")]
[property: Category("Visual:")]
private System.Windows.Media.Color colorFluido = System.Windows.Media.Colors.CornflowerBlue;
[ObservableProperty]
[property: Description("Datos del path para dibujar la tubería")]
[property: Category("Interno:")]
private string pathData;
[ObservableProperty]
[property: Description("Densidad del fluido (0-1)")]
[property: Category("Simulación:")]
private double densidadFluido = 0.7;
// Lista de puntos que forman la tubería
private List<Vector2> _puntos = new List<Vector2>();
// Nombre de la clase para identificación
public static string NombreClase()
{
return "TuberiaFluido";
}
private string nombre = NombreClase();
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
/// <summary>
/// Agrega un punto a la tubería
/// </summary>
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));
}
}
/// <summary>
/// Actualiza la representación visual de la tubería
/// </summary>
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();
}
/// <summary>
/// Reconstruye la tubería en la simulación
/// </summary>
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);
}
}
}
/// <summary>
/// Constructor
/// </summary>
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);
}
}
}
}

View File

@ -0,0 +1,42 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucValvulaFluido"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim">
<UserControl.DataContext>
<vm:osValvulaFluido/>
</UserControl.DataContext>
<Grid>
<Canvas RenderTransformOrigin="0.5,0.5">
<Canvas.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding Angulo}"/>
</TransformGroup>
</Canvas.RenderTransform>
<!-- Cuerpo de la válvula -->
<Rectangle Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}"
RadiusX="5" RadiusY="5"
Canvas.Left="{Binding OffsetXRectangulo, Converter={StaticResource MeterToPixelConverter}}"
Canvas.Top="{Binding OffsetYRectangulo, Converter={StaticResource MeterToPixelConverter}}"/>
<!-- Indicador de apertura -->
<Rectangle Width="{Binding AperturaVisual, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding GrosorIndicador, Converter={StaticResource MeterToPixelConverter}}"
Fill="{Binding ColorIndicador, Converter={StaticResource ColorToBrushConverter}}"
Canvas.Left="{Binding OffsetXIndicador, Converter={StaticResource MeterToPixelConverter}}"
Canvas.Top="{Binding OffsetYIndicador, Converter={StaticResource MeterToPixelConverter}}"/>
<!-- Texto con el valor numérico de apertura -->
<TextBlock Text="{Binding ValorApertura, StringFormat='{}{0:P0}'}"
Canvas.Left="{Binding OffsetXTexto, Converter={StaticResource MeterToPixelConverter}}"
Canvas.Top="{Binding OffsetYTexto, Converter={StaticResource MeterToPixelConverter}}"
FontWeight="Bold"
Foreground="White"
FontSize="12"/>
</Canvas>
</Grid>
</UserControl>

View File

@ -0,0 +1,206 @@
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
{
/// <summary>
/// Lógica para ucValvulaFluido.xaml
/// </summary>
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;
}
}
/// <summary>
/// ViewModel para la válvula de fluidos
/// </summary>
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("Dimensiones:")]
[property: Description("Ancho de la válvula en metros")]
private float ancho = 0.1f;
[ObservableProperty]
[property: Description("Alto de la válvula en metros")]
[property: Category("Dimensiones:")]
private float alto = 0.06f;
[ObservableProperty]
[property: Description("Diámetro de la tubería conectada")]
[property: Category("Dimensiones:")]
private float diametroTuberia = 0.05f;
// Propiedades visuales
[ObservableProperty]
[property: Description("Color de la válvula")]
[property: Category("Visual:")]
private Color color = Colors.Silver;
[ObservableProperty]
[property: Description("Color del indicador de apertura")]
[property: Category("Visual:")]
private Color colorIndicador = Colors.CornflowerBlue;
// Propiedades de funcionamiento
[ObservableProperty]
[property: Description("Apertura de la válvula (0-1)")]
[property: Category("Operación:")]
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: Description("Tag de lectura/escritura de la apertura (0-100%)")]
[property: Category("PLC:")]
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 "ValvulaFluido";
}
private string nombre = NombreClase();
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
/// <summary>
/// Constructor
/// </summary>
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);
}
}
}
}

View File

@ -0,0 +1,411 @@
using System;
using System.Collections.Generic;
using nkast.Aether.Physics2D.Common;
using tainicom.Aether.Physics2D.Fluids;
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 = 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
}
/// <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 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);
}
/// <summary>
/// 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(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;
}
}
/// <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 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);
}
}
/// <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 = 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;
}
}
}
}
}

View File

@ -0,0 +1,186 @@
using System;
using System.Collections.Generic;
using nkast.Aether.Physics2D.Common;
using tainicom.Aether.Physics2D.Fluids;
namespace CtrEditor.Simulacion.Fluids
{
/// <summary>
/// Clase principal que gestiona la simulación de fluidos basada en FluidSystem2
/// </summary>
public class SimulacionFluidos
{
public FluidSystem2 SistemaFluido { get; private set; }
private List<IContenedorFluido> _contenedores = new List<IContenedorFluido>();
private int _worldWidth;
private int _worldHeight;
public int ParticlesCount => SistemaFluido?.ParticlesCount ?? 0;
/// <summary>
/// Constructor para el sistema de simulación de fluidos
/// </summary>
/// <param name="ancho">Ancho del área de simulación en metros</param>
/// <param name="alto">Alto del área de simulación en metros</param>
/// <param name="maxParticulas">Número máximo de partículas</param>
/// <param name="gravedad">Vector de gravedad</param>
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;
}
/// <summary>
/// Método para agregar una partícula al sistema
/// </summary>
/// <param name="posicion">Posición en metros</param>
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));
}
/// <summary>
/// Agrega múltiples partículas en un área rectangular
/// </summary>
/// <param name="centro">Centro del área</param>
/// <param name="ancho">Ancho del área</param>
/// <param name="alto">Alto del área</param>
/// <param name="cantidad">Cantidad de partículas a agregar</param>
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));
}
}
/// <summary>
/// Agrega un contenedor al sistema de fluidos
/// </summary>
/// <param name="contenedor">Implementación de IContenedorFluido</param>
public void AgregarContenedor(IContenedorFluido contenedor)
{
_contenedores.Add(contenedor);
}
/// <summary>
/// Elimina un contenedor del sistema de fluidos
/// </summary>
/// <param name="contenedor">Contenedor a eliminar</param>
public void RemoverContenedor(IContenedorFluido contenedor)
{
_contenedores.Remove(contenedor);
}
/// <summary>
/// Actualiza la simulación avanzando un paso de tiempo
/// </summary>
/// <param name="deltaTime">Tiempo transcurrido en segundos</param>
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);
}
/// <summary>
/// Obtiene la densidad del fluido en una posición específica
/// </summary>
/// <param name="posicion">Posición en metros</param>
/// <returns>Densidad relativa de fluido (0-1)</returns>
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.Distance(posAjustada, p.Position);
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;
}
/// <summary>
/// Ajusta la gravedad del sistema
/// </summary>
/// <param name="gravedad">Nuevo vector de gravedad</param>
public void AjustarGravedad(Vector2 gravedad)
{
SistemaFluido.Gravity = gravedad;
}
/// <summary>
/// Limpia todas las partículas del sistema
/// </summary>
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;
}
}
/// <summary>
/// Interfaz para contenedores de fluido personalizados como tubos, tanques, etc.
/// </summary>
public interface IContenedorFluido
{
/// <summary>
/// Aplica restricciones a una partícula para mantenerla dentro o fuera del contenedor
/// </summary>
void RestringirParticula(Particle particula);
}
}