Inicio de Migracion de 2D a 3D

This commit is contained in:
Miguel 2025-07-01 19:03:06 +02:00
parent d259f53081
commit 121e586d53
36 changed files with 3756 additions and 6046 deletions

View File

@ -27,12 +27,16 @@
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="ObjetosSim\Fluids\**" />
<EmbeddedResource Remove="ObjetosSim\Fluids\**" />
<None Remove="ObjetosSim\Fluids\**" />
<Page Remove="ObjetosSim\Fluids\**" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="Documentation\PlantillaEstandarizacion.cs" /> <Compile Remove="Documentation\PlantillaEstandarizacion.cs" />
<Compile Remove="ObjetosSim\ucBasicExample.xaml.cs" />
<Compile Remove="ObjetosSim\ucTransporteCurva.xaml.cs" /> <Compile Remove="ObjetosSim\ucTransporteCurva.xaml.cs" />
<Compile Remove="Simulacion\FPhysics.cs" />
<Compile Remove="Simulacion\GeometrySimulator.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -76,26 +80,23 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Page Remove="ObjetosSim\ucBasicExample.xaml" />
<Page Remove="ObjetosSim\ucTransporteCurva.xaml" /> <Page Remove="ObjetosSim\ucTransporteCurva.xaml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Documentation\PlantillaEstandarizacion.cs" /> <None Include="Documentation\PlantillaEstandarizacion.cs" />
<None Include="ObjetosSim\ucBasicExample.xaml" />
<None Include="ObjetosSim\ucBasicExample.xaml.cs" />
<None Include="Simulacion\FPhysics.cs" />
<None Include="Simulacion\GeometrySimulator.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Aether.Physics2D" Version="2.2.0" /> <PackageReference Include="BepuPhysics" Version="2.5.0-beta.26" />
<PackageReference Include="BepuUtilities" Version="2.5.0-beta.26" />
<PackageReference Include="ClosedXML" Version="0.105.0-rc" /> <PackageReference Include="ClosedXML" Version="0.105.0-rc" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Emgu.CV" Version="4.9.0.5494" /> <PackageReference Include="Emgu.CV" Version="4.9.0.5494" />
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.9.0.5494" /> <PackageReference Include="Emgu.CV.runtime.windows" Version="4.9.0.5494" />
<PackageReference Include="Emgu.CV.UI" Version="4.9.0.5494" /> <PackageReference Include="Emgu.CV.UI" Version="4.9.0.5494" />
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.7.25104.5739" /> <PackageReference Include="Extended.Wpf.Toolkit" Version="4.7.25104.5739" />
<PackageReference Include="HelixToolkit.Wpf" Version="2.27.0" />
<PackageReference Include="LanguageDetection" Version="1.2.0" /> <PackageReference Include="LanguageDetection" Version="1.2.0" />
<PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc3.3" /> <PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc3.3" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" /> <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
@ -103,8 +104,6 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" /> <PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
<PackageReference Include="PaddleOCRSharp" Version="4.5.0.1" /> <PackageReference Include="PaddleOCRSharp" Version="4.5.0.1" />
<PackageReference Include="Tesseract" Version="5.2.0" />
<PackageReference Include="Tesseract.Drawing" Version="5.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -187,7 +186,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="ObjetosSim\Fluids\" />
<Folder Include="paddleocr\cls\inference\" /> <Folder Include="paddleocr\cls\inference\" />
<Folder Include="paddleocr\det\inference\" /> <Folder Include="paddleocr\det\inference\" />
<Folder Include="paddleocr\keys\" /> <Folder Include="paddleocr\keys\" />

View File

@ -44,7 +44,7 @@ namespace CtrEditor
private float TiempoDesdeStartSimulacion; private float TiempoDesdeStartSimulacion;
private bool Debug_SimulacionCreado = false; private bool Debug_SimulacionCreado = false;
public SimulationManagerFP simulationManager = new SimulationManagerFP(); public SimulationManagerBEPU simulationManager = new SimulationManagerBEPU();
private readonly DispatcherTimer _timerSimulacion; private readonly DispatcherTimer _timerSimulacion;
private readonly DispatcherTimer _timerPLCUpdate; private readonly DispatcherTimer _timerPLCUpdate;
@ -52,6 +52,9 @@ namespace CtrEditor
public Canvas MainCanvas; public Canvas MainCanvas;
// Manager para la visualización 3D
public BEPUVisualization3DManager Visualization3DManager { get; set; }
[ObservableProperty] [ObservableProperty]
private bool isConnected; private bool isConnected;

View File

@ -122,7 +122,9 @@
<Grid Grid.Column="1"> <Grid Grid.Column="1">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="3*" />
<RowDefinition Height="5" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<ToolBarTray Grid.Row="0"> <ToolBarTray Grid.Row="0">
@ -223,6 +225,35 @@
</Canvas.RenderTransform> </Canvas.RenderTransform>
</Canvas> </Canvas>
</ScrollViewer> </ScrollViewer>
<!-- Separador entre vistas -->
<GridSplitter Grid.Row="2" Height="5" HorizontalAlignment="Stretch" Background="DarkGray"
ResizeDirection="Rows" VerticalAlignment="Center" />
<!-- Vista 3D (Inferior) -->
<Border Grid.Row="3" BorderBrush="Gray" BorderThickness="1" Margin="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header del panel 3D -->
<TextBlock Grid.Row="0" Text="Debug 3D - Mundo BEPU" Background="DarkGray" Foreground="White"
Padding="5" FontWeight="Bold" />
<!-- HelixViewport3D -->
<helix:HelixViewport3D x:Name="Debug3DViewport" Grid.Row="1" ShowCoordinateSystem="True"
ShowFrameRate="True" CoordinateSystemLabelZ="Z - Altura" EnableCurrentPosition="True"
ShowFieldOfView="True" CalculateCursorPosition="True" IsManipulationEnabled="True"
Orthographic="True">
<helix:DefaultLights />
<helix:GridLinesVisual3D Width="100" Length="100" MinorDistance="1" Thickness="0.01"
MajorDistance="7" />
<!-- Contenido del viewport se configurará desde code-behind -->
</helix:HelixViewport3D>
</Grid>
</Border>
</Grid> </Grid>
<!-- GridSplitter --> <!-- GridSplitter -->

View File

@ -1,11 +1,9 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
//using using Microsoft.Xna.Framework;
using LibS7Adv; using LibS7Adv;
using CtrEditor.Simulacion; using CtrEditor.Simulacion;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using nkast.Aether.Physics2D.Common; using System.Numerics;
using System.Windows.Media; using System.Windows.Media;
using CtrEditor.FuncionesBase; using CtrEditor.FuncionesBase;
using System.ComponentModel; using System.ComponentModel;

View File

@ -5,7 +5,7 @@ using System.Windows.Controls;
using LibS7Adv; using LibS7Adv;
using CtrEditor.Simulacion; using CtrEditor.Simulacion;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using nkast.Aether.Physics2D.Common; using System.Numerics;
using System.Windows.Media; using System.Windows.Media;
using CtrEditor.FuncionesBase; using CtrEditor.FuncionesBase;
using System.ComponentModel; using System.ComponentModel;

View File

@ -3,7 +3,7 @@ using LibS7Adv;
using CtrEditor.Simulacion; using CtrEditor.Simulacion;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using nkast.Aether.Physics2D.Common; using System.Numerics;
using System.Windows.Media.Animation; using System.Windows.Media.Animation;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.FuncionesBase; using CtrEditor.FuncionesBase;

View File

@ -4,7 +4,7 @@ using CtrEditor.Serialization;
using CtrEditor.Services; using CtrEditor.Services;
using CtrEditor.Simulacion; using CtrEditor.Simulacion;
using LibS7Adv; using LibS7Adv;
using nkast.Aether.Physics2D.Common; using System.Numerics;
using PaddleOCRSharp; using PaddleOCRSharp;
using Siemens.Simatic.Simulation.Runtime; using Siemens.Simatic.Simulation.Runtime;
using System.ComponentModel; // Para poder usar [property: Category ... using System.ComponentModel; // Para poder usar [property: Category ...

View File

@ -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
{
/// <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: 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<IContenedorFluido> _contenedores = new List<IContenedorFluido>();
// 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
/// <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;
}
}
/// <summary>
/// Llamado cuando se inicia la simulación de fluidos
/// </summary>
public void OnFluidSimulationStart()
{
// Crear la simulación de fluidos si es necesario
UpdateGeometryStart();
}
/// <summary>
/// Llamado cuando se detiene la simulación de fluidos
/// </summary>
public void OnFluidSimulationStop()
{
// Detener recursos si es necesario
SimulationStop();
}
/// <summary>
/// Actualiza la simulación de fluidos
/// </summary>
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);
}
}
}
}

View File

@ -1,24 +0,0 @@
<UserControl x:Class="CtrEditor.ObjetosSim.ucBasicExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:osBasicExample Ancho="2"/>
</UserControl.DataContext>
<Canvas>
<Rectangle x:Name="Transporte"
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
Fill="{StaticResource BeltBrush}">
<Rectangle.RenderTransform>
<RotateTransform Angle="{Binding Angulo}"/>
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
</UserControl>

View File

@ -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
{
/// <summary>
/// Interaction logic for ucBasicExample.xaml
/// </summary>
///
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;
}
}
}

View File

@ -1,33 +0,0 @@
<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

@ -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
{
/// <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

@ -1,31 +0,0 @@
<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:local="clr-namespace:CtrEditor.ObjetosSim"
xmlns:ctr="clr-namespace:CtrEditor">
<UserControl.DataContext>
<local:osTuberiaFluido/>
</UserControl.DataContext>
<Grid>
<Path x:Name="TuberiaPath"
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}"
Opacity="{Binding DensidadFluido}"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round"
StrokeLineJoin="Round">
</Path>
</Grid>
</UserControl>

View File

@ -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
{
/// <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: 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<Vector2> _puntos = new List<Vector2>();
// 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);
}
/// <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

@ -1,42 +0,0 @@
<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:local="clr-namespace:CtrEditor.ObjetosSim">
<UserControl.DataContext>
<local: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

@ -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
{
/// <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("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);
}
/// <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);
}
}
}
}

File diff suppressed because it is too large Load Diff

2615
Simulacion/BEPU.cs Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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<Vertices> 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<Vertices> CreateCurveVertices(float innerRadius, float outerRadius, float startAngle, float endAngle)
{
List<Vertices> verticesList = new List<Vertices>();
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<simBase> 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<simBase>();
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<System.Windows.Shapes.Shape>().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;
}
}
}

View File

@ -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
{
/// <summary>
/// Gestor para la simulación de fluidos independiente de la simulación física principal
/// </summary>
public class FluidSimulationManager
{
private SimulacionFluidos _simulacion;
private readonly List<Action> _deferredActions = new List<Action>();
private readonly List<osSistemaFluidos> _sistemasRegistrados = new List<osSistemaFluidos>();
private float _defaultWidth = 10.0f;
private float _defaultHeight = 10.0f;
private bool _isRunning = false;
private Stopwatch _stopwatch;
private double _lastUpdateTime;
public bool IsRunning => _isRunning;
/// <summary>
/// Constructor
/// </summary>
public FluidSimulationManager()
{
_stopwatch = new Stopwatch();
}
/// <summary>
/// Inicializa la simulación de fluidos
/// </summary>
public void Initialize()
{
if (_simulacion == null)
{
_simulacion = new SimulacionFluidos(
_defaultWidth,
_defaultHeight,
10000, // max partículas
new Vector2(0, 9.8f) // gravedad por defecto
);
}
}
/// <summary>
/// Inicia la simulación de fluidos
/// </summary>
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();
}
}
}
/// <summary>
/// Detiene la simulación de fluidos
/// </summary>
public void Stop()
{
if (_isRunning)
{
_isRunning = false;
_stopwatch.Stop();
_stopwatch.Reset();
// Notificar a los sistemas de fluidos
foreach (var sistema in _sistemasRegistrados)
{
sistema.OnFluidSimulationStop();
}
}
}
/// <summary>
/// Actualiza un paso de la simulación de fluidos
/// </summary>
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);
}
}
/// <summary>
/// Registra un sistema de fluidos para ser actualizado
/// </summary>
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();
}
}
}
/// <summary>
/// Elimina un sistema de fluidos del registro
/// </summary>
public void UnregisterFluidSystem(osSistemaFluidos sistema)
{
_sistemasRegistrados.Remove(sistema);
}
/// <summary>
/// Añade una acción para ser ejecutada en el próximo paso de simulación
/// </summary>
public void AddDeferredAction(Action action)
{
_deferredActions.Add(action);
}
/// <summary>
/// Obtiene la instancia de simulación de fluidos
/// </summary>
public SimulacionFluidos GetSimulacion()
{
if (_simulacion == null)
{
Initialize();
}
return _simulacion;
}
/// <summary>
/// Limpia los recursos de la simulación
/// </summary>
public void Clear()
{
Stop();
_simulacion = null;
_sistemasRegistrados.Clear();
_deferredActions.Clear();
}
}
}

View File

@ -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
{
/// <summary>
/// Fluid parameters, see pvfs.pdf for a detailed explanation
/// </summary>
public struct FluidDefinition
{
/// <summary>
/// Distance of influence between the particles
/// </summary>
public float InfluenceRadius;
/// <summary>
/// Density of the fluid
/// </summary>
public float DensityRest;
/// <summary>
/// Stiffness of the fluid (when particles are far)
/// </summary>
public float Stiffness;
/// <summary>
/// Stiffness of the fluid (when particles are near)
/// Set by Check()
/// </summary>
public float StiffnessNear;
/// <summary>
/// Toggles viscosity forces
/// </summary>
public bool UseViscosity;
/// <summary>
/// See pvfs.pdf for more information
/// </summary>
public float ViscositySigma;
/// <summary>
/// See pvfs.pdf for more information
/// </summary>
public float ViscosityBeta;
/// <summary>
/// Toggles plasticity computation (springs etc.)
/// </summary>
public bool UsePlasticity;
/// <summary>
/// Plasticity, amount of memory of the shape
/// See pvfs.pdf for more information
/// </summary>
public float Plasticity;
/// <summary>
/// K of the springs used for plasticity
/// </summary>
public float KSpring;
/// <summary>
/// Amount of change of the rest length of the springs (when compressed)
/// </summary>
public float YieldRatioCompress;
/// <summary>
/// Amount of change of the rest length of the springs (when stretched)
/// </summary>
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);
}
}
}

View File

@ -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<FluidParticle>();
IsActive = true;
MoveTo(position);
Damping = 0.0f;
Mass = 1.0f;
}
public bool IsActive { get; set; }
public List<FluidParticle> 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;
}
}
}

View File

@ -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<SpringHash, Spring> _springs = new Dictionary<SpringHash, Spring>();
private List<SpringHash> _springsToRemove = new List<SpringHash>();
private Vector2 _totalForce;
public FluidSystem1(Vector2 gravity)
{
Gravity = gravity;
Particles = new List<FluidParticle>();
DefaultDefinition();
}
public FluidDefinition Definition { get; private set; }
public List<FluidParticle> 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);
}
}
}
}

View File

@ -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
{
/// <summary>
/// Grid used by particle system to keep track of neightbor particles.
/// </summary>
public class HashGrid
{
private Dictionary<ulong, List<FluidParticle>> _hash = new Dictionary<ulong, List<FluidParticle>>();
private Stack<List<FluidParticle>> _bucketPool = new Stack<List<FluidParticle>>();
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<ulong, List<FluidParticle>> pair in _hash)
{
pair.Value.Clear();
_bucketPool.Push(pair.Value);
}
_hash.Clear();
}
public void Add(FluidParticle particle)
{
ulong key = HashKey(particle.Position);
List<FluidParticle> bucket;
if (!_hash.TryGetValue(key, out bucket))
{
if (_bucketPool.Count > 0)
{
bucket = _bucketPool.Pop();
}
else
{
bucket = new List<FluidParticle>();
}
_hash.Add(key, bucket);
}
bucket.Add(particle);
}
public void Find(ref Vector2 position, List<FluidParticle> 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<FluidParticle> bucket;
if (_hash.TryGetValue(key, out bucket))
{
for (int i = 0; i < bucket.Count; ++i)
{
if (bucket[i] != null)
{
neighbours.Add(bucket[i]);
}
}
}
}
}
}
}
}

View File

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

View File

@ -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<SpringHash>
{
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);
}
}
}

View File

@ -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<int, List<int>> _springPresenceTable;
private List<Spring2> _springs;
private List<Particle> _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<int>(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());
}
}
}

View File

@ -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<Particle>
{
// default nearby table size
private const int DefaultNearbySize = 50;
private List<Particle> _table;
private List<Particle> _voidList = new List<Particle>(1);
private List<Particle>[][] _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<Particle>((_row * _column) / 2);
_nearby = new List<Particle>[_column][];
for (int i = 0; i < _column; ++i)
{
_nearby[i] = new List<Particle>[_row];
for (int j = 0; j < _row; ++j)
{
_nearby[i][j] = new List<Particle>(DefaultNearbySize);
}
}
_initialized = true;
}
/// <summary>
/// Append value to the table and identify its position in the space.
/// Don't need to rehash table after append operation.</summary>
/// <param name="value"></param>
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<Particle> 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;
}
/// <summary>
/// Updates the spatial relationships of objects. Rehash function
/// needed if elements change their position in the space.
/// </summary>
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);
}
}
/// <summary>
/// Add element to its position and neighbor cells.
/// </summary>
/// <param name="value"></param>
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);
}
}
}
/// <summary>
/// Check if a position is out of the spatial range
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns>true if position is in range.</returns>
private bool InRange(float x, float y)
{
return (x > 0 && x < _column && y > 0 && y < _row);
}
public IEnumerator<Particle> GetEnumerator()
{
return _table.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -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
{
/// <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;
}
}
}
}
}

View File

@ -1,70 +0,0 @@
using System;
using nkast.Aether.Physics2D.Common;
namespace CtrEditor.Simulacion.Fluids.Components
{
/// <summary>
/// Utilidades para operaciones con Vector2 de nkast.Aether.Physics2D.Common
/// </summary>
public static class VectorUtils
{
/// <summary>
/// Calcula la distancia entre dos vectores
/// </summary>
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;
}
/// <summary>
/// Calcula el producto punto entre dos vectores
/// </summary>
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;
}
/// <summary>
/// Normaliza un vector y retorna el resultado
/// </summary>
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;
}
/// <summary>
/// Calcula la reflexión de un vector respecto a una normal
/// </summary>
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;
}
}
}

View File

@ -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
{
/// <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 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;
}
/// <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);
}
}

View File

@ -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<Circle> circles, List<Rectangle> rectangles, List<Line> 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<Circle> circles;
public List<Rectangle> rectangles;
public List<Line> lines;
public SimulationManager()
{
circles = new List<Circle>();
rectangles = new List<Rectangle>();
lines = new List<Line>();
}
public void Step(float timeStep)
{
foreach (var circle in circles)
{
circle.Move(timeStep, circles, rectangles, lines);
}
}
}

View File

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

View File

@ -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<Vector2> aVertices = GetRotatedVertices(bodyA);
List<Vector2> bVertices = GetRotatedVertices(bodyB);
List<Vector2> 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<Vector2> 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<Vector2> SutherlandHodgmanClip(List<Vector2> subjectPolygon, List<Vector2> clipPolygon)
{
List<Vector2> outputList = new List<Vector2>(subjectPolygon);
for (int i = 0; i < clipPolygon.Count; i++)
{
Vector2 clipEdgeStart = clipPolygon[i];
Vector2 clipEdgeEnd = clipPolygon[(i + 1) % clipPolygon.Count];
List<Vector2> inputList = outputList;
outputList = new List<Vector2>();
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<Vector2> 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<Vector2>
{
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<Vector2> GetRotatedVertices(Body body)
{
List<Vector2> vertices = new List<Vector2>();
// 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;
}
}
}

View File

@ -3,7 +3,7 @@ using System.Diagnostics;
using System.Windows.Threading; using System.Windows.Threading;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CtrEditor.Simulacion.Fluids; using CtrEditor.Simulacion.Fluids;
using nkast.Aether.Physics2D.Common; using System.Numerics;
using CtrEditor.ObjetosSim; using CtrEditor.ObjetosSim;
namespace CtrEditor namespace CtrEditor