Compare commits
22 Commits
Aether
...
Bepuphysic
Author | SHA1 | Date |
---|---|---|
|
d1ec7f4d12 | |
|
83fc828a4c | |
|
c91b1419b4 | |
|
71c08d8047 | |
|
eb6ed62d5b | |
|
de0e3ce5f0 | |
|
3eee0e3d9b | |
|
a6cbd8c4ab | |
|
095228144a | |
|
ab8066d1e8 | |
|
f431ede7bd | |
|
a00183c4f6 | |
|
ba073a9e80 | |
|
c1584e8d55 | |
|
501f0ffb9b | |
|
4ff93a5802 | |
|
e38adc9f56 | |
|
fd215bc677 | |
|
3e53a51e8b | |
|
3773da0ee3 | |
|
fbac81ec45 | |
|
121e586d53 |
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
Quisiera que con los conocimientos importantes y desiciones importantes que hemos adquirido y utilizado los agregues a Documentation\MemoriadeEvolucion.md manteniendo el estilo que ya tenemos de texto simple sin demasiado codigo y una semantica resumida.
|
|
@ -0,0 +1,33 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace CtrEditor.Converters
|
||||||
|
{
|
||||||
|
public class BooleanToLocalizedTextConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public string TrueText { get; set; } = "True";
|
||||||
|
public string FalseText { get; set; } = "False";
|
||||||
|
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is bool boolValue)
|
||||||
|
{
|
||||||
|
return boolValue ? TrueText : FalseText;
|
||||||
|
}
|
||||||
|
return FalseText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is string text)
|
||||||
|
{
|
||||||
|
if (text == TrueText)
|
||||||
|
return true;
|
||||||
|
if (text == FalseText)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../Libraries/LibS7Adv"
|
"path": "../Libraries/LibS7Adv"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../Librerias/bepuphysics2-master"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {}
|
"settings": {}
|
||||||
|
|
|
@ -28,11 +28,16 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Remove="ObjetosSim\Fluids\**" />
|
||||||
|
<EmbeddedResource Remove="ObjetosSim\Fluids\**" />
|
||||||
|
<None Remove="ObjetosSim\Fluids\**" />
|
||||||
|
<Page Remove="ObjetosSim\Fluids\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="Documentation\BEPU Forces.cs" />
|
||||||
<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 +81,24 @@
|
||||||
</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\BEPU Forces.cs" />
|
||||||
<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 +106,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 +188,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\" />
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,32 @@
|
||||||
|
### Aqui se mantiene la memoria de evolucion de las distintas decisiones que fueron tomadas y porque
|
||||||
|
|
||||||
|
BEPU.cs : SimulationManagerBEPU gestor de la simulacion con el motor BEPUphysics y punto de creacion y modificacion de los objetos dentro del mundo que se derivan des simBase
|
||||||
|
|
||||||
|
simBase : Clase base que da un marco a el resto de los objetos
|
||||||
|
simBarrera : Simula una fotocelula con un espejo usando RayCast
|
||||||
|
simBotella : Simula una botella que puede transitar por los transportes simTransporte o simCurve
|
||||||
|
simCurve : Simula una curva o arco de curva de transporte
|
||||||
|
simDescarte : Permite la eliminacion localizada de botellas en un punto del mundo
|
||||||
|
simGuia : Es un box con el que las botellas pueden colisionar y cambiar de direccion.
|
||||||
|
simTransporte : Es un box por donde las botellas pueden desplazarse usando un truco de aplicar la Velocity.Linear pero sin integrar esta velocidad para que no se mueva el objeto transporte.
|
||||||
|
|
||||||
|
* Se usaron esferas en vez de cilindros para mejorar la eficiencia. En el Debug3D si se usan cilindros. **Revertido**: Se ha vuelto a usar `Cylinder` para las `simBotella` ya que el nuevo sistema de fricción debería prevenir la rotación indeseada que ocurría con los `LinearAxisMotor`.
|
||||||
|
|
||||||
|
* Se reemplazó el sistema de `LinearAxisMotor` que actuaba sobre las `simBotella` por un sistema basado en fricción. Los transportes (`simTransporte` y `simCurve`) ahora son cuerpos cinemáticos que no se mueven de su sitio. Para lograr que arrastren a las botellas, se les asigna una velocidad (`Velocity.Linear` o `Velocity.Angular`) justo antes de que el solver se ejecute (en `OnSubstepStarted`) y se les quita justo después (en `OnSubstepEnded`). Esto permite que los cuerpos cinemáticos transmitan su velocidad a través de la fricción durante la simulación, pero evita que el `PoseIntegrator` los desplace de su posición original, ya que su velocidad es cero cuando se integra la pose.
|
||||||
|
|
||||||
|
* Para aumentar la estabilidad de las `simBotella` y evitar que roten descontroladamente sobre su eje Z al ser arrastradas, se implementó un callback `IntegrateVelocity` personalizado. Este callback identifica las botellas durante la integración y les aplica un amortiguamiento angular (`AngularDamping`) adicional solo en el eje Z. Se descartó la idea inicial de modificar dinámicamente la masa de las botellas dentro de este callback, ya que la arquitectura de BEPUphysics no permite cambiar la masa o la inercia de un cuerpo durante la fase de integración de velocidad.
|
||||||
|
|
||||||
|
* Originalmente se habia puesto todos los objetos en awaken para poder usar las colisiones constantemente incluso con objetos en modo sleep para que los simBarrera puedan detectar las colisiones. Ahora que se usar RayCast podemos dejar que las simBotellas se duerman
|
||||||
|
|
||||||
|
* La unica clase que se ha terminado de refactorizar respecto a el cambio de coordenadas es simBarrera y ucPhotocell. El concepto es poder separar usando metodos de SimulationManagerBEPU y estructuras como BarreraData la conversion de coordenadas de WPF a coordenadas BEPU. Para esto se usa CoordinateConverter que permite bidireccionalmente convertir las coordenadas pero esto solo se debe usar en SimulationManagerBEPU las clases derivadas de osBase solo deben manejar coordenadas WPF, mientras que las clases dervadas de simBase solo deben almacenar y usar coordenadas BEPU. La z tambien es algo que se debe transferir a SimulationManagerBEPU ya que los objetos simBase deberian recibir tambien sus coordenadas de Z desde SimulationManagerBEPU y ser SimulationManagerBEPU el que gestione las Z.
|
||||||
|
|
||||||
|
* Se ha implementado un sistema para evitar que las botellas (`simBotella`) "floten" o se eleven de manera irreal por la acumulación de presión en la simulación. Cada botella ahora registra si está en contacto con un transporte y almacena la última coordenada Z válida durante dicho contacto. Si la botella deja de tener contacto con transportes por varios frames consecutivos, se incrementa un contador de "presión". Al superar un umbral, el sistema reestablece la posición Z de la botella a su última altura conocida, previniendo la flotación. Este contador de presión se decrementa rápidamente al volver a hacer contacto con un transporte.
|
||||||
|
|
||||||
|
* Cuando una botella (`simBotella`) entra en contacto con un transporte de frenado (`simTransporte` con `isBrake = true`), su posición se ajusta automáticamente para centrarla en el eje longitudinal del transporte. Esto se realiza una única vez, en el primer contacto, para asegurar un acoplamiento suave y predecible. La posición de la botella se proyecta sobre la línea central del transporte y su velocidad lateral se anula, evitando que la botella se desvíe mientras frena y se alinea con el flujo de salida.
|
||||||
|
|
||||||
|
* Se ha implementado un indicador visual simple para mostrar cuando los transportes (`simTransporte`) están en movimiento. El sistema detecta automáticamente si un transporte tiene velocidad (`Speed > 0.001`) y cambia su color a verde brillante. Los transportes detenidos se muestran en verde normal. Esta funcionalidad se integra en el sistema de materiales del `BEPUVisualization3DManager` y se actualiza automáticamente cuando cambia el estado del transporte, proporcionando una retroalimentación visual inmediata del estado de operación.
|
||||||
|
|
||||||
|
* Se ha implementado un sistema de animaciones automáticas usando StoryBoard de WPF para los transportes en movimiento. Los transportes activos muestran una animación continua que combina: (1) rotación sutil muy lenta alrededor del eje Z (20 segundos por vuelta completa) y (2) pulsación cíclica del color del material (1.5 segundos por ciclo). Las animaciones se crean y destruyen automáticamente según el estado del transporte, sin necesidad de actualización manual en cada frame. El sistema gestiona las animaciones activas en un diccionario y las limpia correctamente cuando se eliminan objetos. Se resolvió el problema de `InvalidOperationException` al animar brushes inmutables creando una función `CreateAnimatableMaterial` que genera materiales específicamente diseñados para ser animados sin estar "frozen", proporcionando una experiencia visual fluida y eficiente.
|
||||||
|
|
||||||
|
* Se ha mejorado el sistema de guías curvas (`ucTransporteCurvaGuias`) para incluir apertura en cono en los extremos de entrada y salida. Se agregó el parámetro `AnguloAperturaGuias` (por defecto 5 grados) que permite configurar la apertura modificando los radios de las guías en los puntos extremos. En lugar de cambiar ángulos, se reduce el radio de la guía superior (externa) y se aumenta el radio de la guía inferior (interna) en los segmentos inicial y final, creando naturalmente la apertura en cono. La modificación del radio se calcula usando `Math.Sin(anguloApertura)` para obtener el desplazamiento apropiado. Esta apertura facilita la entrada y salida de botellas del transporte curvo, reduciendo atascos y mejorando el flujo de materiales manteniendo la continuidad geométrica de las guías.
|
||||||
|
|
102
MainViewModel.cs
102
MainViewModel.cs
|
@ -6,7 +6,7 @@ using Ookii.Dialogs.Wpf;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using CtrEditor.ObjetosSim;
|
using CtrEditor.ObjetosSim;
|
||||||
using LibS7Adv;
|
using LibS7Adv; // Using stub implementation
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
@ -26,6 +26,7 @@ using CtrEditor.Serialization; // Add this line
|
||||||
using CtrEditor.Controls; // Add this using directive
|
using CtrEditor.Controls; // Add this using directive
|
||||||
using CtrEditor.PopUps; // Add this using directive
|
using CtrEditor.PopUps; // Add this using directive
|
||||||
|
|
||||||
|
|
||||||
namespace CtrEditor
|
namespace CtrEditor
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -44,14 +45,18 @@ 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;
|
||||||
private readonly DispatcherTimer _timerDisplayUpdate;
|
private readonly DispatcherTimer _timerDisplayUpdate;
|
||||||
|
private readonly DispatcherTimer _timer3DUpdate; // Nuevo timer para actualización 3D cuando simulación está detenida
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
@ -91,7 +96,6 @@ namespace CtrEditor
|
||||||
public ICommand StopSimulationCommand { get; }
|
public ICommand StopSimulationCommand { get; }
|
||||||
public ICommand ItemDoubleClickCommand { get; private set; }
|
public ICommand ItemDoubleClickCommand { get; private set; }
|
||||||
|
|
||||||
public SimulationFluidsViewModel FluidSimulation { get; private set; }
|
|
||||||
|
|
||||||
public ICommand TBStartSimulationCommand { get; }
|
public ICommand TBStartSimulationCommand { get; }
|
||||||
public ICommand TBStopSimulationCommand { get; }
|
public ICommand TBStopSimulationCommand { get; }
|
||||||
|
@ -114,6 +118,12 @@ namespace CtrEditor
|
||||||
public ICommand TBMultiPageMatrixCommand { get; }
|
public ICommand TBMultiPageMatrixCommand { get; }
|
||||||
public ICommand TBLibraryManagerCommand { get; }
|
public ICommand TBLibraryManagerCommand { get; }
|
||||||
|
|
||||||
|
// Comandos para vista 3D
|
||||||
|
public ICommand TB3DViewTopCommand { get; }
|
||||||
|
public ICommand TB3DViewSideCommand { get; }
|
||||||
|
public ICommand TB3DViewFrontCommand { get; }
|
||||||
|
public ICommand TB3DViewIsometricCommand { get; }
|
||||||
|
|
||||||
|
|
||||||
public ICommand TBTogglePLCConnectionCommand => new RelayCommand(() =>
|
public ICommand TBTogglePLCConnectionCommand => new RelayCommand(() =>
|
||||||
{
|
{
|
||||||
|
@ -171,6 +181,18 @@ namespace CtrEditor
|
||||||
partial void OnIsSimulationRunningChanged(bool value)
|
partial void OnIsSimulationRunningChanged(bool value)
|
||||||
{
|
{
|
||||||
CommandManager.InvalidateRequerySuggested(); // Notificar que el estado de los comandos ha cambiado
|
CommandManager.InvalidateRequerySuggested(); // Notificar que el estado de los comandos ha cambiado
|
||||||
|
|
||||||
|
// Controlar el timer de actualización 3D
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
// Simulación iniciada - detener timer 3D (la sincronización se hace en Step())
|
||||||
|
_timer3DUpdate.Stop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Simulación detenida - iniciar timer 3D para mantener sincronización
|
||||||
|
_timer3DUpdate.Start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnHasUnsavedChangesChanged(bool value)
|
partial void OnHasUnsavedChangesChanged(bool value)
|
||||||
|
@ -367,16 +389,18 @@ namespace CtrEditor
|
||||||
_timerDisplayUpdate.Tick += OnDisplayUpdate;
|
_timerDisplayUpdate.Tick += OnDisplayUpdate;
|
||||||
_timerDisplayUpdate.Start();
|
_timerDisplayUpdate.Start();
|
||||||
|
|
||||||
|
_timer3DUpdate = new DispatcherTimer();
|
||||||
|
_timer3DUpdate.Interval = TimeSpan.FromMilliseconds(20);
|
||||||
|
_timer3DUpdate.Tick += OnTick3DUpdate;
|
||||||
|
_timer3DUpdate.Start(); // Iniciar porque la simulación empieza detenida
|
||||||
|
|
||||||
StartSimulationCommand = new RelayCommand(StartSimulation);
|
StartSimulationCommand = new RelayCommand(StartSimulation);
|
||||||
StopSimulationCommand = new RelayCommand(StopSimulation);
|
StopSimulationCommand = new RelayCommand(StopSimulation);
|
||||||
|
|
||||||
TBStartSimulationCommand = new RelayCommand(StartSimulation, () => !IsSimulationRunning);
|
TBStartSimulationCommand = new RelayCommand(StartSimulation, () => !IsSimulationRunning);
|
||||||
TBStopSimulationCommand = new RelayCommand(StopSimulation, () => IsSimulationRunning);
|
TBStopSimulationCommand = new RelayCommand(StopSimulation, () => IsSimulationRunning);
|
||||||
|
|
||||||
// Inicializar simulación de fluidos
|
|
||||||
FluidSimulation = new SimulationFluidsViewModel(this);
|
|
||||||
TBStartFluidSimulationCommand = new RelayCommand(StartFluidSimulation, () => !FluidSimulation.IsFluidSimulationRunning);
|
|
||||||
TBStopFluidSimulationCommand = new RelayCommand(StopFluidSimulation, () => FluidSimulation.IsFluidSimulationRunning);
|
|
||||||
TBSaveCommand = new RelayCommand(Save);
|
TBSaveCommand = new RelayCommand(Save);
|
||||||
|
|
||||||
TBEliminarUserControlCommand = new RelayCommand(EliminarUserControl, () => habilitarEliminarUserControl);
|
TBEliminarUserControlCommand = new RelayCommand(EliminarUserControl, () => habilitarEliminarUserControl);
|
||||||
|
@ -389,6 +413,13 @@ namespace CtrEditor
|
||||||
TBMultiPageAnalizeCommand = new RelayCommand(MultiPageAnalizeCommand);
|
TBMultiPageAnalizeCommand = new RelayCommand(MultiPageAnalizeCommand);
|
||||||
TBMultiPageMatrixCommand = new RelayCommand(MultiPageMatrixCommand);
|
TBMultiPageMatrixCommand = new RelayCommand(MultiPageMatrixCommand);
|
||||||
TBLibraryManagerCommand = new RelayCommand(ShowLibraryManager);
|
TBLibraryManagerCommand = new RelayCommand(ShowLibraryManager);
|
||||||
|
|
||||||
|
// Comandos para vista 3D
|
||||||
|
TB3DViewTopCommand = new RelayCommand(() => Visualization3DManager?.SetCameraView(CameraView.Top));
|
||||||
|
TB3DViewSideCommand = new RelayCommand(() => Visualization3DManager?.SetCameraView(CameraView.Side));
|
||||||
|
TB3DViewFrontCommand = new RelayCommand(() => Visualization3DManager?.SetCameraView(CameraView.Front));
|
||||||
|
TB3DViewIsometricCommand = new RelayCommand(() => Visualization3DManager?.SetCameraView(CameraView.Isometric));
|
||||||
|
TBToggle3DUpdateCommand = new RelayCommand(() => Is3DUpdateEnabled = !Is3DUpdateEnabled);
|
||||||
RenameImageCommand = new RelayCommand<string>(RenameImage);
|
RenameImageCommand = new RelayCommand<string>(RenameImage);
|
||||||
|
|
||||||
stopwatch_Sim = new Stopwatch();
|
stopwatch_Sim = new Stopwatch();
|
||||||
|
@ -408,6 +439,9 @@ namespace CtrEditor
|
||||||
|
|
||||||
// Conectar DatosDeTrabajo con este ViewModel para el escaneo de imágenes
|
// Conectar DatosDeTrabajo con este ViewModel para el escaneo de imágenes
|
||||||
datosDeTrabajo.SetMainViewModel(this);
|
datosDeTrabajo.SetMainViewModel(this);
|
||||||
|
|
||||||
|
// NOTA: La conexión del manager 3D se hace en MainWindow_Loaded
|
||||||
|
// después de que se cree la instancia de BEPUVisualization3DManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// Métodos para manejo de datos de imágenes
|
// Métodos para manejo de datos de imágenes
|
||||||
|
@ -873,8 +907,6 @@ namespace CtrEditor
|
||||||
|
|
||||||
private void StartSimulation()
|
private void StartSimulation()
|
||||||
{
|
{
|
||||||
// Detener simulación de fluidos si está ejecutándose
|
|
||||||
StopFluidSimulation();
|
|
||||||
|
|
||||||
IsSimulationRunning = true;
|
IsSimulationRunning = true;
|
||||||
|
|
||||||
|
@ -884,7 +916,7 @@ namespace CtrEditor
|
||||||
foreach (var objetoSimulable in ObjetosSimulables)
|
foreach (var objetoSimulable in ObjetosSimulables)
|
||||||
objetoSimulable.UpdateGeometryStart();
|
objetoSimulable.UpdateGeometryStart();
|
||||||
|
|
||||||
simulationManager.Debug_DrawInitialBodies();
|
|
||||||
TiempoDesdeStartSimulacion = 0;
|
TiempoDesdeStartSimulacion = 0;
|
||||||
Debug_SimulacionCreado = true;
|
Debug_SimulacionCreado = true;
|
||||||
|
|
||||||
|
@ -901,7 +933,6 @@ namespace CtrEditor
|
||||||
|
|
||||||
if (Debug_SimulacionCreado)
|
if (Debug_SimulacionCreado)
|
||||||
{
|
{
|
||||||
simulationManager.Debug_ClearSimulationShapes();
|
|
||||||
Debug_SimulacionCreado = false;
|
Debug_SimulacionCreado = false;
|
||||||
}
|
}
|
||||||
_timerSimulacion.Stop();
|
_timerSimulacion.Stop();
|
||||||
|
@ -913,26 +944,7 @@ namespace CtrEditor
|
||||||
MainWindow?.ClearUndoHistory();
|
MainWindow?.ClearUndoHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Inicia la simulación de fluidos independiente
|
|
||||||
/// </summary>
|
|
||||||
public void StartFluidSimulation()
|
|
||||||
{
|
|
||||||
// Detener simulación física si está ejecutándose
|
|
||||||
StopSimulation();
|
|
||||||
|
|
||||||
FluidSimulation.StartFluidSimulation();
|
|
||||||
CommandManager.InvalidateRequerySuggested();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Detiene la simulación de fluidos independiente
|
|
||||||
/// </summary>
|
|
||||||
public void StopFluidSimulation()
|
|
||||||
{
|
|
||||||
FluidSimulation.StopFluidSimulation();
|
|
||||||
CommandManager.InvalidateRequerySuggested();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTickSimulacion(object sender, EventArgs e)
|
private void OnTickSimulacion(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
@ -946,10 +958,8 @@ namespace CtrEditor
|
||||||
accumulatedSimTime += elapsedMilliseconds;
|
accumulatedSimTime += elapsedMilliseconds;
|
||||||
simSampleCount++;
|
simSampleCount++;
|
||||||
|
|
||||||
// Eliminar el diseño de Debug luego de 2 segundos
|
// Contador de tiempo desde el inicio de la simulación
|
||||||
if (TiempoDesdeStartSimulacion > 1200)
|
if (TiempoDesdeStartSimulacion <= 1200)
|
||||||
simulationManager.Debug_ClearSimulationShapes();
|
|
||||||
else
|
|
||||||
TiempoDesdeStartSimulacion += (float)elapsedMilliseconds;
|
TiempoDesdeStartSimulacion += (float)elapsedMilliseconds;
|
||||||
|
|
||||||
foreach (var objetoSimulable in ObjetosSimulables)
|
foreach (var objetoSimulable in ObjetosSimulables)
|
||||||
|
@ -1186,6 +1196,20 @@ namespace CtrEditor
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private double plcUpdateSpeed;
|
private double plcUpdateSpeed;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool is3DUpdateEnabled = true;
|
||||||
|
|
||||||
|
public ICommand TBToggle3DUpdateCommand { get; }
|
||||||
|
|
||||||
|
partial void OnIs3DUpdateEnabledChanged(bool value)
|
||||||
|
{
|
||||||
|
// Sincronizar la propiedad con el simulationManager
|
||||||
|
if (simulationManager != null)
|
||||||
|
{
|
||||||
|
simulationManager.Is3DUpdateEnabled = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnDisplayUpdate(object? sender, EventArgs e)
|
private void OnDisplayUpdate(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (simSampleCount > 0)
|
if (simSampleCount > 0)
|
||||||
|
@ -1265,7 +1289,6 @@ namespace CtrEditor
|
||||||
{
|
{
|
||||||
// Detener simulaciones antes de cambiar la escala
|
// Detener simulaciones antes de cambiar la escala
|
||||||
StopSimulation();
|
StopSimulation();
|
||||||
StopFluidSimulation();
|
|
||||||
DisconnectPLC();
|
DisconnectPLC();
|
||||||
|
|
||||||
// Actualizar la escala en el UnitConverter
|
// Actualizar la escala en el UnitConverter
|
||||||
|
@ -1338,6 +1361,15 @@ namespace CtrEditor
|
||||||
libraryWindow.Show();
|
libraryWindow.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnTick3DUpdate(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Solo actualizar Helix3D si la simulación está detenida y la actualización 3D está habilitada
|
||||||
|
// Cuando la simulación está corriendo, la sincronización se hace en simulationManager.Step()
|
||||||
|
if (!IsSimulationRunning && Is3DUpdateEnabled && Visualization3DManager != null)
|
||||||
|
{
|
||||||
|
Visualization3DManager.SynchronizeWorld();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
|
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
|
||||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||||
xmlns:ObjetosSim="clr-namespace:CtrEditor.ObjetosSim"
|
xmlns:ObjetosSim="clr-namespace:CtrEditor.ObjetosSim"
|
||||||
|
xmlns:helix="http://helix-toolkit.org/wpf"
|
||||||
xmlns:ObjetosExtraccion="clr-namespace:CtrEditor.ObjetosSim.Extraccion_Datos"
|
xmlns:ObjetosExtraccion="clr-namespace:CtrEditor.ObjetosSim.Extraccion_Datos"
|
||||||
x:Class="CtrEditor.MainWindow"
|
x:Class="CtrEditor.MainWindow"
|
||||||
Height="900" Width="1600" WindowState="Maximized" ResizeMode="CanResize" Title="{Binding directorioTrabajo, Converter={StaticResource UnsavedChangesConverter}}"
|
Height="900" Width="1600" WindowState="Maximized" ResizeMode="CanResize" Title="{Binding directorioTrabajo, Converter={StaticResource UnsavedChangesConverter}}"
|
||||||
|
@ -122,7 +123,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">
|
||||||
|
@ -199,6 +202,36 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Button Command="{Binding TBToggle3DUpdateCommand}" ToolTip="Activar/Desactivar actualización Debug 3D para mejorar rendimiento">
|
||||||
|
<StackPanel>
|
||||||
|
<Image Source="Icons/app.png" Width="24" Height="24" />
|
||||||
|
<TextBlock>
|
||||||
|
<TextBlock.Text>
|
||||||
|
<MultiBinding StringFormat="Debug3D: {0}">
|
||||||
|
<Binding Path="Is3DUpdateEnabled">
|
||||||
|
<Binding.Converter>
|
||||||
|
<converters:BooleanToLocalizedTextConverter TrueText="Activado" FalseText="Desactivado" />
|
||||||
|
</Binding.Converter>
|
||||||
|
</Binding>
|
||||||
|
</MultiBinding>
|
||||||
|
</TextBlock.Text>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<Button.Style>
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Is3DUpdateEnabled}" Value="True">
|
||||||
|
<Setter Property="Background" Value="LightGreen" />
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Is3DUpdateEnabled}" Value="False">
|
||||||
|
<Setter Property="Background" Value="LightCoral" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Button.Style>
|
||||||
|
</Button>
|
||||||
|
|
||||||
</ToolBar>
|
</ToolBar>
|
||||||
</ToolBarTray>
|
</ToolBarTray>
|
||||||
|
|
||||||
|
@ -223,6 +256,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 -->
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using CtrEditor.ObjetosSim;
|
using CtrEditor.ObjetosSim;
|
||||||
|
using CtrEditor.Simulacion;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
@ -45,6 +46,9 @@ namespace CtrEditor
|
||||||
|
|
||||||
private dataDebug dataDebug = new dataDebug();
|
private dataDebug dataDebug = new dataDebug();
|
||||||
|
|
||||||
|
// Manager para la visualización 3D
|
||||||
|
private BEPUVisualization3DManager _visualization3DManager;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
@ -91,11 +95,18 @@ namespace CtrEditor
|
||||||
viewModel.MainWindow = this;
|
viewModel.MainWindow = this;
|
||||||
viewModel.ImageSelected += ViewModel_ImageSelected;
|
viewModel.ImageSelected += ViewModel_ImageSelected;
|
||||||
viewModel?.LoadInitialData();
|
viewModel?.LoadInitialData();
|
||||||
viewModel.simulationManager.DebugCanvas = ImagenEnTrabajoCanvas;
|
|
||||||
viewModel.MainCanvas = ImagenEnTrabajoCanvas;
|
viewModel.MainCanvas = ImagenEnTrabajoCanvas;
|
||||||
|
|
||||||
// Inicializar ObjectHierarchyView
|
// Inicializar ObjectHierarchyView
|
||||||
ObjectHierarchy.Initialize(viewModel);
|
ObjectHierarchy.Initialize(viewModel);
|
||||||
|
|
||||||
|
// Inicializar el manager de visualización 3D
|
||||||
|
_visualization3DManager = new BEPUVisualization3DManager(Debug3DViewport, viewModel.simulationManager);
|
||||||
|
viewModel.Visualization3DManager = _visualization3DManager;
|
||||||
|
|
||||||
|
// Conectar el manager 3D con el simulation manager (orden correcto de inicialización)
|
||||||
|
viewModel.simulationManager.Visualization3DManager = _visualization3DManager;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
<UserControl x:Class="CtrEditor.ObjetosSim.ucBotella"
|
<UserControl x:Class="CtrEditor.ObjetosSim.ucBotella" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim">
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
|
||||||
>
|
|
||||||
|
|
||||||
<UserControl.DataContext>
|
<UserControl.DataContext>
|
||||||
<vm:osBotella/>
|
<vm:osBotella />
|
||||||
</UserControl.DataContext>
|
</UserControl.DataContext>
|
||||||
|
|
||||||
<Ellipse Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
|
<Ellipse Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Stroke="{Binding ColorButton_oculto}" Fill="Gray"
|
Stroke="{Binding ColorButton_oculto}" Fill="Gray"
|
||||||
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}" StrokeThickness="0.5"/>
|
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}" StrokeThickness="0.5" />
|
||||||
|
|
||||||
</UserControl>
|
</UserControl>
|
|
@ -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;
|
||||||
|
@ -83,18 +81,18 @@ namespace CtrEditor.ObjetosSim
|
||||||
[property: Name("Inercia de Simulación")]
|
[property: Name("Inercia de Simulación")]
|
||||||
private float inercia_desde_simulacion;
|
private float inercia_desde_simulacion;
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
[property: Category("Configuración")]
|
|
||||||
[property: Description("Conservar objeto cuando sale de transporte")]
|
|
||||||
[property: Name("Conservar Fuera de Transporte")]
|
|
||||||
private bool preserve_Outside_Transport;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Category("Información")]
|
[property: Category("Información")]
|
||||||
[property: Description("Porcentaje de tracción con transporte")]
|
[property: Description("Porcentaje de tracción con transporte")]
|
||||||
[property: Name("Porcentaje de Tracción")]
|
[property: Name("Porcentaje de Tracción")]
|
||||||
private float porcentaje_Traccion;
|
private float porcentaje_Traccion;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Category("Información")]
|
||||||
|
[property: Description("Indica si la botella está en un transporte con freno")]
|
||||||
|
[property: Name("En Transporte con Freno")]
|
||||||
|
private bool enTransporteConFreno;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Category("Simulación")]
|
[property: Category("Simulación")]
|
||||||
[property: Description("Masa del objeto en kg")]
|
[property: Description("Masa del objeto en kg")]
|
||||||
|
@ -111,7 +109,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCentro(float x, float y)
|
public void SetCentro(float x, float y)
|
||||||
{ Left = x; Top = y; }
|
{ Left = x - Diametro / 2; Top = y - Diametro / 2; }
|
||||||
|
|
||||||
public void SetCentro(Vector2 centro)
|
public void SetCentro(Vector2 centro)
|
||||||
{
|
{
|
||||||
|
@ -132,7 +130,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
Diametro = 0.10f;
|
Diametro = 0.10f;
|
||||||
Mass = 1;
|
Mass = 1;
|
||||||
ColorButton_oculto = Brushes.Gray;
|
ColorButton_oculto = Brushes.Gray;
|
||||||
Preserve_Outside_Transport = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateAfterMove()
|
public void UpdateAfterMove()
|
||||||
|
@ -146,39 +143,52 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
public override void UpdateGeometryStart()
|
public override void UpdateGeometryStart()
|
||||||
{
|
{
|
||||||
// Se llama antes de la simulacion
|
// Se llama cuando inicia la simulación - crear geometría si no existe
|
||||||
ActualizarGeometrias();
|
if (SimGeometria == null)
|
||||||
SimGeometria?.SetDiameter(Diametro);
|
{
|
||||||
|
SimGeometria = simulationManager.AddBotella(Diametro, GetCentro(), Mass);
|
||||||
|
SimGeometria.SimObjectType = "Botella";
|
||||||
|
SimGeometria.WpfObject = this;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ActualizarGeometrias();
|
||||||
|
SimGeometria?.SetDiameter(Diametro);
|
||||||
|
SimGeometria?.SetMass(Mass);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateGeometryStep()
|
public override void UpdateGeometryStep()
|
||||||
{
|
{
|
||||||
// Se llama antes de la simulacion
|
// Se llama durante cada paso de la simulación
|
||||||
ActualizarGeometrias();
|
if (SimGeometria != null)
|
||||||
|
{
|
||||||
|
// Actualizar posición desde la simulación hacia WPF
|
||||||
|
SetCentro(SimGeometria.Center);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public override void UpdateControl(int elapsedMilliseconds)
|
public override void UpdateControl(int elapsedMilliseconds)
|
||||||
{
|
{
|
||||||
SetCentro(SimGeometria.Center);
|
SetCentro(SimGeometria.Center);
|
||||||
if (SimGeometria.isRestricted)
|
|
||||||
ColorButton_oculto = Brushes.Yellow;
|
// Sistema de colores jerarquizado para diferentes estados
|
||||||
|
if (SimGeometria.isOnBrakeTransport)
|
||||||
|
ColorButton_oculto = Brushes.Blue; // En transporte con freno (prioridad sobre presión)
|
||||||
|
else if (!SimGeometria.isOnBrakeTransport && SimGeometria.PressureBuildup>1)
|
||||||
|
ColorButton_oculto = Brushes.Red; // La botella tiene mucha presion
|
||||||
else
|
else
|
||||||
{
|
ColorButton_oculto = Brushes.Gray; // 5. Estado libre
|
||||||
if (SimGeometria.IsOnAnyTransport())
|
|
||||||
ColorButton_oculto = Brushes.Red;
|
|
||||||
else
|
|
||||||
ColorButton_oculto = Brushes.Gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ha sido marcada para remover
|
// Ha sido marcada para remover
|
||||||
if (SimGeometria.Descartar)
|
if (SimGeometria.Descartar)
|
||||||
RemoverDesdeSimulacion = true;
|
RemoverDesdeSimulacion = true;
|
||||||
|
|
||||||
// Eliminar la botella si esta fuera de un transporte
|
// ✅ ELIMINADO: Lógica redundante - BEPU.cs ya elimina botellas que caen en Z automáticamente
|
||||||
if (!Preserve_Outside_Transport && !SimGeometria.IsOnAnyTransport())
|
|
||||||
RemoverDesdeSimulacion = true;
|
|
||||||
|
|
||||||
Velocidad_desde_simulacion = SimGeometria.Body.LinearVelocity.ToString();
|
Velocidad_desde_simulacion = SimGeometria.GetLinearVelocity().ToString();
|
||||||
Inercia_desde_simulacion = SimGeometria.Body.Inertia;
|
Inercia_desde_simulacion = SimGeometria.Mass; // En BEPU usamos masa en lugar de inercia directa
|
||||||
Porcentaje_Traccion = SimGeometria.OverlapPercentage;
|
Porcentaje_Traccion = SimGeometria.OverlapPercentage;
|
||||||
|
EnTransporteConFreno = SimGeometria.isOnBrakeTransport; // Actualizar estado de freno
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ucLoaded()
|
public override void ucLoaded()
|
||||||
|
@ -186,7 +196,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
|
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
|
||||||
// crear el objeto de simulacion
|
// crear el objeto de simulacion
|
||||||
base.ucLoaded();
|
base.ucLoaded();
|
||||||
SimGeometria = simulationManager.AddCircle(Diametro, GetCentro(), Mass);
|
SimGeometria = simulationManager.AddBotella(Diametro, GetCentro(), Mass);
|
||||||
}
|
}
|
||||||
public override void ucUnLoaded()
|
public override void ucUnLoaded()
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -80,6 +80,18 @@ namespace CtrEditor.ObjetosSim
|
||||||
[property: Name("Inercia Simulación")]
|
[property: Name("Inercia Simulación")]
|
||||||
private float inercia_desde_simulacion;
|
private float inercia_desde_simulacion;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Category("Información")]
|
||||||
|
[property: Description("Porcentaje de tracción con transporte")]
|
||||||
|
[property: Name("Porcentaje de Tracción")]
|
||||||
|
private float porcentaje_Traccion;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Category("Información")]
|
||||||
|
[property: Description("Indica si la botella está en un transporte con freno")]
|
||||||
|
[property: Name("En Transporte con Freno")]
|
||||||
|
private bool enTransporteConFreno;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Category("Simulación")]
|
[property: Category("Simulación")]
|
||||||
[property: Description("Masa de la botella")]
|
[property: Description("Masa de la botella")]
|
||||||
|
@ -140,19 +152,27 @@ namespace CtrEditor.ObjetosSim
|
||||||
public override void UpdateControl(int elapsedMilliseconds)
|
public override void UpdateControl(int elapsedMilliseconds)
|
||||||
{
|
{
|
||||||
SetCentro(SimGeometria.Center);
|
SetCentro(SimGeometria.Center);
|
||||||
|
|
||||||
|
// Sistema de colores jerarquizado para diferentes estados
|
||||||
if (SimGeometria.isRestricted)
|
if (SimGeometria.isRestricted)
|
||||||
ColorButton_oculto = Brushes.Yellow;
|
ColorButton_oculto = Brushes.Yellow; // Estado restringido (prioridad alta)
|
||||||
|
else if (SimGeometria.isOnBrakeTransport)
|
||||||
|
ColorButton_oculto = Brushes.Blue; // En transporte con freno - NUEVO ESTADO
|
||||||
|
else if (SimGeometria.IsOnAnyTransport())
|
||||||
|
ColorButton_oculto = Brushes.Red; // En transporte normal
|
||||||
else
|
else
|
||||||
{
|
ColorButton_oculto = Brushes.Gray; // Estado libre
|
||||||
if (SimGeometria.isOnTransports > 0)
|
|
||||||
ColorButton_oculto = Brushes.Red;
|
// Ha sido marcada para remover
|
||||||
else
|
if (SimGeometria.Descartar)
|
||||||
ColorButton_oculto = Brushes.Gray;
|
|
||||||
}
|
|
||||||
if (SimGeometria.Descartar) // Ha sido marcada para remover
|
|
||||||
RemoverDesdeSimulacion = true;
|
RemoverDesdeSimulacion = true;
|
||||||
Velocidad_desde_simulacion = SimGeometria.Body.LinearVelocity.ToString();
|
|
||||||
Inercia_desde_simulacion = SimGeometria.Body.Inertia;
|
// ✅ ELIMINADO: Lógica redundante - BEPU.cs ya elimina botellas que caen en Z automáticamente
|
||||||
|
|
||||||
|
Velocidad_desde_simulacion = SimGeometria.GetLinearVelocity().ToString();
|
||||||
|
Inercia_desde_simulacion = SimGeometria.Mass; // En BEPU usamos masa en lugar de inercia directa
|
||||||
|
Porcentaje_Traccion = SimGeometria.OverlapPercentage;
|
||||||
|
EnTransporteConFreno = SimGeometria.isOnBrakeTransport; // Actualizar estado de freno
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ucLoaded()
|
public override void ucLoaded()
|
||||||
|
@ -160,7 +180,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
|
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
|
||||||
// crear el objeto de simulacion
|
// crear el objeto de simulacion
|
||||||
base.ucLoaded();
|
base.ucLoaded();
|
||||||
SimGeometria = simulationManager.AddCircle(Diametro, GetCentro(), Mass);
|
SimGeometria = simulationManager.AddBotella(Diametro, GetCentro(), Mass);
|
||||||
}
|
}
|
||||||
public override void ucUnLoaded()
|
public override void ucUnLoaded()
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
<UserControl x:Class="CtrEditor.ObjetosSim.ucBottGenerator"
|
<UserControl x:Class="CtrEditor.ObjetosSim.ucBottGenerator"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d"
|
||||||
mc:Ignorable="d"
|
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim">
|
||||||
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim">
|
|
||||||
|
|
||||||
|
|
||||||
<UserControl.DataContext>
|
<UserControl.DataContext>
|
||||||
<vm:osFiller/>
|
<vm:osBottGenerator />
|
||||||
</UserControl.DataContext>
|
</UserControl.DataContext>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Image Source="/imagenes/gear.png"
|
<Image Source="/imagenes/gear.png" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}" Stretch="Uniform">
|
||||||
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
|
||||||
Stretch="Uniform">
|
|
||||||
<Image.RenderTransform>
|
<Image.RenderTransform>
|
||||||
<RotateTransform Angle="{Binding Angulo}" />
|
<RotateTransform Angle="{Binding Angulo}" />
|
||||||
</Image.RenderTransform>
|
</Image.RenderTransform>
|
||||||
|
|
|
@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using CtrEditor.FuncionesBase;
|
using CtrEditor.FuncionesBase;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using DocumentFormat.OpenXml.Spreadsheet;
|
||||||
|
|
||||||
namespace CtrEditor.ObjetosSim
|
namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
|
@ -39,11 +40,8 @@ namespace CtrEditor.ObjetosSim
|
||||||
[property: Name("Offset Vertical")]
|
[property: Name("Offset Vertical")]
|
||||||
private float offsetTopSalida;
|
private float offsetTopSalida;
|
||||||
|
|
||||||
[ObservableProperty]
|
// ✅ ELIMINADO: preserve_Outside_Transport - Redundante con ProcessCleanupSystem de BEPU
|
||||||
[property: Description("Las botellas se destruirán si caen fuera del transporte")]
|
// Las botellas que caen en Z son eliminadas automáticamente por BEPU.cs
|
||||||
[property: Category("Configuración")]
|
|
||||||
[property: Name("Conservar Fuera de Transporte")]
|
|
||||||
private bool preserve_Outside_Transport;
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Description("Tag PLC para habilitar funcionamiento. 1 => siempre activo")]
|
[property: Description("Tag PLC para habilitar funcionamiento. 1 => siempre activo")]
|
||||||
[property: Category("Enlace PLC")]
|
[property: Category("Enlace PLC")]
|
||||||
|
@ -89,6 +87,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
[property: Category("Información")]
|
[property: Category("Información")]
|
||||||
[property: Name("Salida Filtro")]
|
[property: Name("Salida Filtro")]
|
||||||
bool filter_Output;
|
bool filter_Output;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Description("Cantidad de botellas generadas por hora")]
|
[property: Description("Cantidad de botellas generadas por hora")]
|
||||||
[property: Category("Configuración")]
|
[property: Category("Configuración")]
|
||||||
|
@ -115,11 +114,32 @@ namespace CtrEditor.ObjetosSim
|
||||||
[property: Name("Velocidad Actual (%)")]
|
[property: Name("Velocidad Actual (%)")]
|
||||||
private float velocidad_actual_percentual;
|
private float velocidad_actual_percentual;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Description("Distancia libre minima sin botellas sobre el transporte para colocar otra botella")]
|
||||||
|
[property: Category("Simulación")]
|
||||||
|
[property: Name("Diametro libre minimo")]
|
||||||
|
private float distancia_libre;
|
||||||
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Description("Diámetro de las botellas generadas")]
|
[property: Description("Diámetro de las botellas generadas")]
|
||||||
[property: Category("Configuración")]
|
[property: Category("Configuración")]
|
||||||
[property: Name("Diámetro Botella")]
|
[property: Name("Diámetro Botella")]
|
||||||
private float diametro_botella;
|
private float diametro_botella;
|
||||||
|
|
||||||
|
public osBottGenerator()
|
||||||
|
{
|
||||||
|
Ancho = 0.40f;
|
||||||
|
Alto = 0.40f;
|
||||||
|
Angulo = 0;
|
||||||
|
Velocidad_actual_percentual = 100;
|
||||||
|
Diametro_botella = 0.1f;
|
||||||
|
Botellas_hora = 10000;
|
||||||
|
Consenso = true;
|
||||||
|
Distancia_libre = 0.1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
||||||
{
|
{
|
||||||
if (Consenso_NC)
|
if (Consenso_NC)
|
||||||
|
@ -130,7 +150,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
private bool HayEspacioParaNuevaBotella(float X, float Y)
|
private bool HayEspacioParaNuevaBotella(float X, float Y)
|
||||||
{
|
{
|
||||||
float radioMinimo = Diametro_botella / 4; // Distancia mínima entre centros
|
float radioMinimo = Distancia_libre; // Distancia mínima entre centros
|
||||||
float radioMinimoCuadrado = radioMinimo * radioMinimo;
|
float radioMinimoCuadrado = radioMinimo * radioMinimo;
|
||||||
|
|
||||||
// Buscar todas las botellas cercanas
|
// Buscar todas las botellas cercanas
|
||||||
|
@ -184,7 +204,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
var nuevaBotella = _mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y);
|
var nuevaBotella = _mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y);
|
||||||
((osBotella)nuevaBotella).Diametro = Diametro_botella;
|
((osBotella)nuevaBotella).Diametro = Diametro_botella;
|
||||||
((osBotella)nuevaBotella).Preserve_Outside_Transport = Preserve_Outside_Transport;
|
// ✅ ELIMINADO: Preserve_Outside_Transport - Ya no es necesario
|
||||||
nuevaBotella.AutoCreated = true;
|
nuevaBotella.AutoCreated = true;
|
||||||
|
|
||||||
// Recalcular el tiempo entre botellas por si cambió la velocidad
|
// Recalcular el tiempo entre botellas por si cambió la velocidad
|
||||||
|
|
|
@ -1,42 +1,31 @@
|
||||||
<UserControl x:Class="CtrEditor.ObjetosSim.ucDescarte"
|
<UserControl x:Class="CtrEditor.ObjetosSim.ucDescarte" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:CtrEditor.ObjetosSim"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim" mc:Ignorable="d">
|
||||||
xmlns:local="clr-namespace:CtrEditor.ObjetosSim"
|
|
||||||
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
|
||||||
mc:Ignorable="d">
|
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<Storyboard x:Key="PulsingStoryboard" RepeatBehavior="Forever">
|
<Storyboard x:Key="PulsingStoryboard" RepeatBehavior="Forever">
|
||||||
<DoubleAnimation
|
<DoubleAnimation Storyboard.TargetName="AnimatedEllipse"
|
||||||
Storyboard.TargetName="AnimatedEllipse"
|
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
|
||||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
|
From="1" To="0.5" Duration="0:0:1" AutoReverse="True" />
|
||||||
From="1" To="0.5" Duration="0:0:1" AutoReverse="True" />
|
<DoubleAnimation Storyboard.TargetName="AnimatedEllipse"
|
||||||
<DoubleAnimation
|
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
|
||||||
Storyboard.TargetName="AnimatedEllipse"
|
From="1" To="0.5" Duration="0:0:1" AutoReverse="True" />
|
||||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
|
|
||||||
From="1" To="0.5" Duration="0:0:1" AutoReverse="True" />
|
|
||||||
</Storyboard>
|
</Storyboard>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
<UserControl.DataContext>
|
<UserControl.DataContext>
|
||||||
<vm:osDescarte/>
|
<vm:osDescarte />
|
||||||
</UserControl.DataContext>
|
</UserControl.DataContext>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Ellipse
|
<Ellipse Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
|
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}" Stroke="Yellow"
|
||||||
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
|
Fill="Black" Opacity="0.5" />
|
||||||
Stroke="Yellow"
|
<Ellipse x:Name="AnimatedEllipse" Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Fill="Black"
|
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}" Stroke="Blue"
|
||||||
Opacity="0.5"/>
|
Fill="Transparent" RenderTransformOrigin="0.5,0.5">
|
||||||
<Ellipse x:Name="AnimatedEllipse"
|
|
||||||
Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
|
|
||||||
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
|
|
||||||
Stroke="Blue"
|
|
||||||
Fill="Transparent"
|
|
||||||
RenderTransformOrigin="0.5,0.5">
|
|
||||||
<Ellipse.RenderTransform>
|
<Ellipse.RenderTransform>
|
||||||
<TransformGroup>
|
<TransformGroup>
|
||||||
<ScaleTransform />
|
<ScaleTransform />
|
||||||
|
|
|
@ -3,11 +3,11 @@ 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.Windows.Media.Animation;
|
using System.Windows.Media.Animation;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CtrEditor.FuncionesBase;
|
using CtrEditor.FuncionesBase;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Numerics;
|
||||||
namespace CtrEditor.ObjetosSim
|
namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -41,7 +41,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
partial void OnDiametroChanged(float value)
|
partial void OnDiametroChanged(float value)
|
||||||
{
|
{
|
||||||
SimGeometria?.SetDiameter(Diametro);
|
ActualizarGeometrias();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector2 GetCentro()
|
public Vector2 GetCentro()
|
||||||
|
@ -73,8 +73,15 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
if (SimGeometria != null)
|
if (SimGeometria != null)
|
||||||
{
|
{
|
||||||
SimGeometria.SetDiameter(Diametro);
|
// ✅ SISTEMA INTELIGENTE: Solo recrear si el diámetro cambió
|
||||||
SimGeometria.SetPosition(GetCentro());
|
if (HasDiscardDimensionsChanged(SimGeometria, Diametro))
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[osDescarte] Recreando descarte por cambio de diámetro");
|
||||||
|
SimGeometria.SetDiameter(Diametro);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ USAR MÉTODO COORDINATECONVERTER
|
||||||
|
SimGeometria.UpdateFromWpfCenter(GetCentro());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ using LibS7Adv;
|
||||||
using CtrEditor.Simulacion;
|
using CtrEditor.Simulacion;
|
||||||
using CtrEditor.FuncionesBase;
|
using CtrEditor.FuncionesBase;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace CtrEditor.ObjetosSim
|
namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
|
@ -32,14 +33,35 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Category("Configuración")]
|
[property: Category("Configuración")]
|
||||||
[property: Description("Alto de la guía en metros")]
|
[property: Description("Grosor de la guía en metros")]
|
||||||
[property: Name("Alto de la Guía")]
|
[property: Name("Grosor de la Guía")]
|
||||||
public float altoGuia;
|
public float altoGuia;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void ActualizarGeometrias()
|
private void ActualizarGeometrias()
|
||||||
{
|
{
|
||||||
if (_visualRepresentation is ucGuia uc)
|
if (_visualRepresentation is ucGuia uc && SimGeometria != null)
|
||||||
UpdateOrCreateLine(SimGeometria, uc.Guia);
|
{
|
||||||
|
var topLeft = new Vector2(Left, Top);
|
||||||
|
|
||||||
|
// ✅ SISTEMA INTELIGENTE: Solo recrear si las dimensiones han cambiado
|
||||||
|
if (HasGuideDimensionsChanged(SimGeometria, Ancho, AltoGuia))
|
||||||
|
{
|
||||||
|
//System.Diagnostics.Debug.WriteLine($"[osGuia] Recreando guía por cambio de dimensiones: {Ancho}x{AltoGuia}");
|
||||||
|
|
||||||
|
// ✅ RECREAR COMPLETAMENTE: Las dimensiones cambiaron
|
||||||
|
SimGeometria.Create(Ancho, AltoGuia, topLeft, Angulo);
|
||||||
|
SimGeometria.SetDimensions(Ancho, AltoGuia);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// System.Diagnostics.Debug.WriteLine($"[osGuia] Solo actualizando posición/rotación: Left={Left}, Top={Top}, Angulo={Angulo}");
|
||||||
|
|
||||||
|
// ✅ SOLO ACTUALIZAR POSICIÓN/ROTACIÓN: Usar dimensiones reales para conversión correcta
|
||||||
|
SimGeometria.UpdateFromWpfParameters(topLeft, Angulo, Ancho, AltoGuia);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnMoveResizeRotate()
|
public override void OnMoveResizeRotate()
|
||||||
|
@ -81,7 +103,21 @@ namespace CtrEditor.ObjetosSim
|
||||||
simulationManager?.Remove(SimGeometria);
|
simulationManager?.Remove(SimGeometria);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void AnchoChanged(float value)
|
||||||
|
{
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AnguloChanged(float value)
|
||||||
|
{
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Método llamado cuando cambia AltoGuia
|
||||||
|
partial void OnAltoGuiaChanged(float value)
|
||||||
|
{
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class ucGuia : UserControl, IDataContainer
|
public partial class ucGuia : UserControl, IDataContainer
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
<UserControl x:Class="CtrEditor.ObjetosSim.ucTransporteCurva"
|
<UserControl x:Class="CtrEditor.ObjetosSim.ucTransporteCurva"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:localuc="clr-namespace:CtrEditor.ObjetosSim.UserControls"
|
xmlns:localuc="clr-namespace:CtrEditor.ObjetosSim.UserControls" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
||||||
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
mc:Ignorable="d">
|
||||||
mc:Ignorable="d">
|
|
||||||
|
|
||||||
<UserControl.DataContext>
|
<UserControl.DataContext>
|
||||||
<vm:osTransporteCurva />
|
<vm:osTransporteCurva />
|
||||||
|
@ -13,7 +12,9 @@
|
||||||
|
|
||||||
<Canvas x:Name="MainCanvas">
|
<Canvas x:Name="MainCanvas">
|
||||||
|
|
||||||
<localuc:CircularSegment x:Name="Transporte" Angle="0" OuterRadius="{Binding RadioExterno, Converter={StaticResource MeterToPixelConverter}}" InnerRadius="{Binding RadioInterno, Converter={StaticResource MeterToPixelConverter}}"
|
<localuc:CircularSegment x:Name="Transporte" Angle="0"
|
||||||
StartAngle="{Binding Angulo}" EndAngle="{Binding AnguloFinal}" />
|
OuterRadius="{Binding RadioExterno, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
InnerRadius="{Binding RadioInterno, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
StartAngle="{Binding Angulo}" EndAngle="{Binding AnguloFinal}" />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
|
@ -7,6 +7,7 @@ using CtrEditor.Simulacion;
|
||||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||||
using CtrEditor.FuncionesBase;
|
using CtrEditor.FuncionesBase;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace CtrEditor.ObjetosSim
|
namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
|
@ -93,6 +94,50 @@ namespace CtrEditor.ObjetosSim
|
||||||
ActualizarGeometrias();
|
ActualizarGeometrias();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
partial void OnRadioInternoChanged(float value)
|
||||||
|
{
|
||||||
|
// Ensure radioInterno is always less than radioExterno
|
||||||
|
if (value >= RadioExterno)
|
||||||
|
{
|
||||||
|
RadioInterno = RadioExterno * 0.75f; // Maintain proportion
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnArco_en_gradosChanged(float value)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(AnguloFinal));
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manejar cambios de posición usando métodos virtuales de osBase
|
||||||
|
public override void LeftChanging(float oldValue, float newValue)
|
||||||
|
{
|
||||||
|
base.LeftChanging(oldValue, newValue);
|
||||||
|
ActualizarPosicionBEPU();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void TopChanging(float oldValue, float newValue)
|
||||||
|
{
|
||||||
|
base.TopChanging(oldValue, newValue);
|
||||||
|
ActualizarPosicionBEPU();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ActualizarPosicionBEPU()
|
||||||
|
{
|
||||||
|
if (Simulation_TransporteCurva != null)
|
||||||
|
{
|
||||||
|
// ✅ CORRIGIDO: startAngle = Angulo, endAngle = Angulo + Arco_en_grados (como Aether)
|
||||||
|
var topLeft = new Vector2(Left, Top);
|
||||||
|
Simulation_TransporteCurva.Create(RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados, topLeft, 0);
|
||||||
|
|
||||||
|
// Sincronizar con la visualización 3D tras la actualización
|
||||||
|
simulationManager?.Visualization3DManager?.SynchronizeWorld();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Category("Configuración")]
|
[property: Category("Configuración")]
|
||||||
[property: Description("Radio interior de la curva en metros")]
|
[property: Description("Radio interior de la curva en metros")]
|
||||||
|
@ -140,6 +185,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
public override void AnguloChanged(float value)
|
public override void AnguloChanged(float value)
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(AnguloFinal));
|
OnPropertyChanged(nameof(AnguloFinal));
|
||||||
|
ActualizarGeometrias();
|
||||||
}
|
}
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
|
@ -156,7 +202,15 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
if (_visualRepresentation is ucTransporteCurva uc)
|
if (_visualRepresentation is ucTransporteCurva uc)
|
||||||
{
|
{
|
||||||
UpdateCurve(Simulation_TransporteCurva, RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados);
|
if (Simulation_TransporteCurva != null)
|
||||||
|
{
|
||||||
|
// ✅ CORRIGIDO: startAngle = Angulo, endAngle = Angulo + Arco_en_grados (como Aether)
|
||||||
|
var topLeft = new Vector2(Left, Top);
|
||||||
|
Simulation_TransporteCurva.Create(RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados, topLeft, 0);
|
||||||
|
|
||||||
|
// Sincronizar con la visualización 3D tras la actualización
|
||||||
|
simulationManager?.Visualization3DManager?.SynchronizeWorld();
|
||||||
|
}
|
||||||
SetSpeed();
|
SetSpeed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,6 +263,9 @@ namespace CtrEditor.ObjetosSim
|
||||||
// Ensure radioInterno is always less than radioExterno
|
// Ensure radioInterno is always less than radioExterno
|
||||||
if (RadioInterno >= RadioExterno)
|
if (RadioInterno >= RadioExterno)
|
||||||
RadioInterno = RadioExterno * 0.75f;
|
RadioInterno = RadioExterno * 0.75f;
|
||||||
|
|
||||||
|
// Actualizar geometrías en BEPU después del redimensionamiento
|
||||||
|
ActualizarGeometrias();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -256,7 +313,9 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
if (_visualRepresentation is ucTransporteCurva uc)
|
if (_visualRepresentation is ucTransporteCurva uc)
|
||||||
{
|
{
|
||||||
Simulation_TransporteCurva = AddCurve(RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados);
|
// ✅ CORRIGIDO: startAngle = Angulo, endAngle = Angulo + Arco_en_grados (como Aether)
|
||||||
|
var topLeft = new Vector2(Left, Top);
|
||||||
|
Simulation_TransporteCurva = simulationManager?.AddCurve(RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados, topLeft, 0);
|
||||||
CrearAnimacionStoryBoardTrasnporteCircular(uc.Transporte.TransportePath, InvertirDireccion, Angulo);
|
CrearAnimacionStoryBoardTrasnporteCircular(uc.Transporte.TransportePath, InvertirDireccion, Angulo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||||
using CtrEditor.FuncionesBase;
|
using CtrEditor.FuncionesBase;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace CtrEditor.ObjetosSim
|
namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
|
@ -133,6 +134,17 @@ namespace CtrEditor.ObjetosSim
|
||||||
ActualizarGeometrias();
|
ActualizarGeometrias();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Category("Configuración")]
|
||||||
|
[property: Description("Ángulo de apertura en grados para los extremos de las guías")]
|
||||||
|
[property: Name("Ángulo Apertura Guías")]
|
||||||
|
private float anguloAperturaGuias = 5f;
|
||||||
|
|
||||||
|
partial void OnAnguloAperturaGuiasChanged(float value)
|
||||||
|
{
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Category("Configuración")]
|
[property: Category("Configuración")]
|
||||||
[property: Description("Distancia de separación de las guías desde el borde")]
|
[property: Description("Distancia de separación de las guías desde el borde")]
|
||||||
|
@ -239,9 +251,42 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
partial void OnArco_en_gradosChanged(float value)
|
partial void OnArco_en_gradosChanged(float value)
|
||||||
{
|
{
|
||||||
|
OnPropertyChanged(nameof(AnguloFinal));
|
||||||
ActualizarGeometrias();
|
ActualizarGeometrias();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ NUEVO: Manejar cambios de posición usando métodos virtuales de osBase
|
||||||
|
public override void LeftChanging(float oldValue, float newValue)
|
||||||
|
{
|
||||||
|
base.LeftChanging(oldValue, newValue);
|
||||||
|
ActualizarPosicionBEPU();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void TopChanging(float oldValue, float newValue)
|
||||||
|
{
|
||||||
|
base.TopChanging(oldValue, newValue);
|
||||||
|
ActualizarPosicionBEPU();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ CORREGIDO: Actualizar posición en BEPU usando métodos de conversión apropiados
|
||||||
|
/// </summary>
|
||||||
|
private void ActualizarPosicionBEPU()
|
||||||
|
{
|
||||||
|
if (Simulation_TransporteCurvaGuias != null)
|
||||||
|
{
|
||||||
|
// ✅ USAR MÉTODOS DE COORDINATECONVERTER
|
||||||
|
var topLeft = new Vector2(Left, Top);
|
||||||
|
Simulation_TransporteCurvaGuias.UpdateFromWpfParameters(topLeft, 0f, RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados);
|
||||||
|
|
||||||
|
// Recrear las guías en la nueva posición
|
||||||
|
ActualizarGuiasCurvas();
|
||||||
|
|
||||||
|
// Sincronizar con la visualización 3D tras la actualización
|
||||||
|
simulationManager?.Visualization3DManager?.SynchronizeWorld();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Hidden]
|
[Hidden]
|
||||||
public float AnguloFinal
|
public float AnguloFinal
|
||||||
{
|
{
|
||||||
|
@ -252,7 +297,12 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
if (_visualRepresentation is ucTransporteCurvaGuias uc)
|
if (_visualRepresentation is ucTransporteCurvaGuias uc)
|
||||||
{
|
{
|
||||||
UpdateCurve(Simulation_TransporteCurvaGuias, RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados);
|
// ✅ USAR MÉTODO DE COORDINATECONVERTER PARA ACTUALIZACIÓN COMPLETA
|
||||||
|
if (Simulation_TransporteCurvaGuias != null)
|
||||||
|
{
|
||||||
|
var topLeft = new Vector2(Left, Top);
|
||||||
|
Simulation_TransporteCurvaGuias.UpdateFromWpfParameters(topLeft, 0f, RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados);
|
||||||
|
}
|
||||||
ActualizarGuiasCurvas();
|
ActualizarGuiasCurvas();
|
||||||
SetSpeed();
|
SetSpeed();
|
||||||
}
|
}
|
||||||
|
@ -293,16 +343,17 @@ namespace CtrEditor.ObjetosSim
|
||||||
if (radioGuiaInferior < 0.01f)
|
if (radioGuiaInferior < 0.01f)
|
||||||
radioGuiaInferior = 0.01f;
|
radioGuiaInferior = 0.01f;
|
||||||
|
|
||||||
// Convertir ángulos a radianes
|
// ✅ CORREGIDO: Convertir ángulos a radianes usando método estándar
|
||||||
float anguloInicioRad = simBase.GradosARadianes(Angulo);
|
float anguloInicioRad = simBase.GradosARadianes(Angulo);
|
||||||
float anguloFinalRad = simBase.GradosARadianes(AnguloFinal);
|
float anguloFinalRad = simBase.GradosARadianes(AnguloFinal);
|
||||||
float rangoAngular = anguloFinalRad - anguloInicioRad;
|
float rangoAngular = anguloFinalRad - anguloInicioRad;
|
||||||
|
float anguloAperturaRad = simBase.GradosARadianes(AnguloAperturaGuias);
|
||||||
|
|
||||||
// Calcular el paso angular entre segmentos
|
// Calcular el paso angular entre segmentos
|
||||||
float pasoAngular = rangoAngular / NumeroSegmentosGuias;
|
float pasoAngular = rangoAngular / NumeroSegmentosGuias;
|
||||||
|
|
||||||
// Obtener el centro una vez para todo el método
|
// Obtener el centro una vez para todo el método
|
||||||
nkast.Aether.Physics2D.Common.Vector2 centro = GetCurveCenterInMeter(RadioExterno);
|
Vector2 centro = GetCurveCenterInMeter(RadioExterno);
|
||||||
|
|
||||||
// Crear segmentos para guía superior (externa)
|
// Crear segmentos para guía superior (externa)
|
||||||
for (int i = 0; i < NumeroSegmentosGuias; i++)
|
for (int i = 0; i < NumeroSegmentosGuias; i++)
|
||||||
|
@ -310,22 +361,36 @@ namespace CtrEditor.ObjetosSim
|
||||||
float angulo1 = anguloInicioRad + i * pasoAngular;
|
float angulo1 = anguloInicioRad + i * pasoAngular;
|
||||||
float angulo2 = anguloInicioRad + (i + 1) * pasoAngular;
|
float angulo2 = anguloInicioRad + (i + 1) * pasoAngular;
|
||||||
|
|
||||||
nkast.Aether.Physics2D.Common.Vector2 punto1 = new nkast.Aether.Physics2D.Common.Vector2(
|
// Calcular radios ajustados para crear apertura en cono
|
||||||
radioGuiaSuperior * (float)Math.Cos(angulo1),
|
// Para guía superior: reducir radio en los extremos para crear apertura
|
||||||
radioGuiaSuperior * (float)Math.Sin(angulo1)
|
float radio1 = radioGuiaSuperior;
|
||||||
|
float radio2 = radioGuiaSuperior;
|
||||||
|
|
||||||
|
// Calcular reducción del radio basada en el ángulo de apertura
|
||||||
|
float reduccionRadio = radioGuiaSuperior * (float)Math.Sin(anguloAperturaRad/10);
|
||||||
|
|
||||||
|
if (InvertirDireccion && i == 0) // Primer segmento
|
||||||
|
radio1 += reduccionRadio;
|
||||||
|
if (!InvertirDireccion && i == NumeroSegmentosGuias - 1) // Último segmento
|
||||||
|
radio2 += reduccionRadio;
|
||||||
|
|
||||||
|
Vector2 punto1 = new Vector2(
|
||||||
|
radio1 * (float)Math.Cos(angulo1),
|
||||||
|
radio1 * (float)Math.Sin(angulo1)
|
||||||
);
|
);
|
||||||
|
|
||||||
nkast.Aether.Physics2D.Common.Vector2 punto2 = new nkast.Aether.Physics2D.Common.Vector2(
|
Vector2 punto2 = new Vector2(
|
||||||
radioGuiaSuperior * (float)Math.Cos(angulo2),
|
radio2 * (float)Math.Cos(angulo2),
|
||||||
radioGuiaSuperior * (float)Math.Sin(angulo2)
|
radio2 * (float)Math.Sin(angulo2)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ajustar por la posición del objeto
|
// Ajustar por la posición del objeto
|
||||||
punto1 += centro;
|
punto1 += centro;
|
||||||
punto2 += centro;
|
punto2 += centro;
|
||||||
|
|
||||||
simGuia guiaSegmento = simulationManager.AddLine(punto1, punto2);
|
simGuia guiaSegmento = CrearGuiaDesdeDosPuntos(punto1, punto2);
|
||||||
GuiasSuperiores.Add(guiaSegmento);
|
if (guiaSegmento != null)
|
||||||
|
GuiasSuperiores.Add(guiaSegmento);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crear segmentos para guía inferior (interna)
|
// Crear segmentos para guía inferior (interna)
|
||||||
|
@ -334,22 +399,64 @@ namespace CtrEditor.ObjetosSim
|
||||||
float angulo1 = anguloInicioRad + i * pasoAngular;
|
float angulo1 = anguloInicioRad + i * pasoAngular;
|
||||||
float angulo2 = anguloInicioRad + (i + 1) * pasoAngular;
|
float angulo2 = anguloInicioRad + (i + 1) * pasoAngular;
|
||||||
|
|
||||||
nkast.Aether.Physics2D.Common.Vector2 punto1 = new nkast.Aether.Physics2D.Common.Vector2(
|
// Calcular radios ajustados para crear apertura en cono
|
||||||
radioGuiaInferior * (float)Math.Cos(angulo1),
|
// Para guía inferior: aumentar radio en los extremos para crear apertura
|
||||||
radioGuiaInferior * (float)Math.Sin(angulo1)
|
float radio1 = radioGuiaInferior;
|
||||||
|
float radio2 = radioGuiaInferior;
|
||||||
|
|
||||||
|
// Calcular aumento del radio basada en el ángulo de apertura
|
||||||
|
float aumentoRadio = radioGuiaInferior * (float)Math.Sin(anguloAperturaRad/10);
|
||||||
|
|
||||||
|
if (InvertirDireccion && i == 0) // Primer segmento
|
||||||
|
radio1 -= aumentoRadio;
|
||||||
|
if (InvertirDireccion && i == NumeroSegmentosGuias - 1) // Último segmento
|
||||||
|
radio2 -= aumentoRadio;
|
||||||
|
|
||||||
|
Vector2 punto1 = new Vector2(
|
||||||
|
radio1 * (float)Math.Cos(angulo1),
|
||||||
|
radio1 * (float)Math.Sin(angulo1)
|
||||||
);
|
);
|
||||||
|
|
||||||
nkast.Aether.Physics2D.Common.Vector2 punto2 = new nkast.Aether.Physics2D.Common.Vector2(
|
Vector2 punto2 = new Vector2(
|
||||||
radioGuiaInferior * (float)Math.Cos(angulo2),
|
radio2 * (float)Math.Cos(angulo2),
|
||||||
radioGuiaInferior * (float)Math.Sin(angulo2)
|
radio2 * (float)Math.Sin(angulo2)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ajustar por la posición del objeto
|
// Ajustar por la posición del objeto
|
||||||
punto1 += centro;
|
punto1 += centro;
|
||||||
punto2 += centro;
|
punto2 += centro;
|
||||||
|
|
||||||
simGuia guiaSegmento = simulationManager.AddLine(punto1, punto2);
|
simGuia guiaSegmento = CrearGuiaDesdeDosPuntos(punto1, punto2);
|
||||||
GuiasInferiores.Add(guiaSegmento);
|
if (guiaSegmento != null)
|
||||||
|
GuiasInferiores.Add(guiaSegmento);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ CORREGIDO: Método helper para crear una guía desde dos puntos usando conversiones apropiadas
|
||||||
|
/// Convierte dos puntos Vector2 a los parámetros requeridos por AddLine
|
||||||
|
/// </summary>
|
||||||
|
private simGuia CrearGuiaDesdeDosPuntos(Vector2 punto1, Vector2 punto2)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Calcular la longitud entre los dos puntos
|
||||||
|
var direccion = punto2 - punto1;
|
||||||
|
float longitud = direccion.Length();
|
||||||
|
|
||||||
|
if (longitud < 0.001f) // Evitar líneas de longitud cero
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Calcular el ángulo de la línea correctamente para WPF
|
||||||
|
float angulo = (float)Math.Atan2(direccion.Y, direccion.X) * 180f / (float)Math.PI;
|
||||||
|
|
||||||
|
// ✅ USAR punto1 como topLeft - simulationManager.AddLine ya maneja las conversiones WPF->BEPU
|
||||||
|
return simulationManager.AddLine(longitud, GrosorGuias, punto1, angulo);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Error creando guía desde dos puntos: {ex.Message}");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,6 +523,9 @@ namespace CtrEditor.ObjetosSim
|
||||||
// Ensure radioInterno is always less than radioExterno
|
// Ensure radioInterno is always less than radioExterno
|
||||||
if (RadioInterno >= RadioExterno)
|
if (RadioInterno >= RadioExterno)
|
||||||
RadioInterno = RadioExterno * 0.75f;
|
RadioInterno = RadioExterno * 0.75f;
|
||||||
|
|
||||||
|
// ✅ NUEVO: Actualizar geometrías en BEPU después del redimensionamiento
|
||||||
|
ActualizarGeometrias();
|
||||||
}
|
}
|
||||||
|
|
||||||
public osTransporteCurvaGuias()
|
public osTransporteCurvaGuias()
|
||||||
|
@ -465,7 +575,9 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
if (_visualRepresentation is ucTransporteCurvaGuias uc)
|
if (_visualRepresentation is ucTransporteCurvaGuias uc)
|
||||||
{
|
{
|
||||||
Simulation_TransporteCurvaGuias = AddCurve(RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados);
|
// ✅ CORREGIDO: Usar simulationManager?.AddCurve con conversión WPF correcta
|
||||||
|
var topLeft = new Vector2(Left, Top);
|
||||||
|
Simulation_TransporteCurvaGuias = simulationManager?.AddCurve(RadioInterno, RadioExterno, Angulo, Angulo + Arco_en_grados, topLeft, 0);
|
||||||
CrearGuiasCurvas(); // Crear las guías curvas
|
CrearGuiasCurvas(); // Crear las guías curvas
|
||||||
CrearAnimacionStoryBoardTrasnporteCircular(uc.Transporte.TransportePath, InvertirDireccion, Angulo);
|
CrearAnimacionStoryBoardTrasnporteCircular(uc.Transporte.TransportePath, InvertirDireccion, Angulo);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<UserControl x:Class="CtrEditor.ObjetosSim.ucTransporteGuias"
|
<UserControl x:Class="CtrEditor.ObjetosSim.ucTransporteGuias"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
||||||
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
mc:Ignorable="d">
|
||||||
mc:Ignorable="d">
|
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<!-- Define the VisualBrush for the conveyor belt pattern -->
|
<!-- Define the VisualBrush for the conveyor belt pattern -->
|
||||||
<VisualBrush x:Key="BeltBrush" TileMode="Tile" Viewport="0,0,20,10" ViewportUnits="Absolute" Viewbox="0,0,20,10" ViewboxUnits="Absolute">
|
<VisualBrush x:Key="BeltBrush" TileMode="Tile" Viewport="0,0,20,10" ViewportUnits="Absolute" Viewbox="0,0,20,10"
|
||||||
|
ViewboxUnits="Absolute">
|
||||||
<VisualBrush.Transform>
|
<VisualBrush.Transform>
|
||||||
<TransformGroup>
|
<TransformGroup>
|
||||||
<TranslateTransform/>
|
<TranslateTransform />
|
||||||
</TransformGroup>
|
</TransformGroup>
|
||||||
</VisualBrush.Transform>
|
</VisualBrush.Transform>
|
||||||
<VisualBrush.Visual>
|
<VisualBrush.Visual>
|
||||||
|
@ -24,41 +24,42 @@
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
<UserControl.DataContext>
|
<UserControl.DataContext>
|
||||||
<vm:osTransporteGuias/>
|
<vm:osTransporteGuias />
|
||||||
</UserControl.DataContext>
|
</UserControl.DataContext>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Canvas RenderTransformOrigin="0,0">
|
<Canvas RenderTransformOrigin="0,0">
|
||||||
<Canvas.RenderTransform>
|
<Canvas.RenderTransform>
|
||||||
<TransformGroup>
|
<TransformGroup>
|
||||||
<ScaleTransform/>
|
<ScaleTransform />
|
||||||
<SkewTransform/>
|
<SkewTransform />
|
||||||
<RotateTransform Angle="{Binding Angulo}"/>
|
<RotateTransform Angle="{Binding Angulo}" />
|
||||||
<TranslateTransform/>
|
<TranslateTransform />
|
||||||
</TransformGroup>
|
</TransformGroup>
|
||||||
</Canvas.RenderTransform>
|
</Canvas.RenderTransform>
|
||||||
<StackPanel x:Name="RectanglesContainer">
|
<StackPanel x:Name="RectanglesContainer">
|
||||||
<Rectangle x:Name="GuiaSuperior" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
<Rectangle x:Name="GuiaSuperior"
|
||||||
Height="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}"
|
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}"
|
Height="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Margin="{Binding Distance, Converter={StaticResource DistanceToMarginConverter}}"/>
|
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||||
<Rectangle x:Name="Transporte" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
Margin="{Binding Distance, Converter={StaticResource DistanceToMarginConverter}}" />
|
||||||
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
<Rectangle x:Name="Transporte" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Margin="{Binding Distance, Converter={StaticResource DistanceToMarginConverter}}"
|
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Fill="{StaticResource BeltBrush}"/>
|
Margin="{Binding Distance, Converter={StaticResource DistanceToMarginConverter}}"
|
||||||
<Rectangle x:Name="GuiaInferior" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
Fill="{StaticResource BeltBrush}" />
|
||||||
Height="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}"
|
<Rectangle x:Name="GuiaInferior"
|
||||||
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}"/>
|
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
Height="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
|
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Viewbox Canvas.Top="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}"
|
<Viewbox Canvas.Top="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||||
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}" Stretch="Uniform">
|
||||||
Stretch="Uniform">
|
<Label Content="{Binding Nombre}" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||||
<Label Content="{Binding Nombre}" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" FontSize="18" Opacity="0.9"/>
|
FontWeight="Bold" FontSize="18" Opacity="0.9" />
|
||||||
</Viewbox>
|
</Viewbox>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
@ -8,6 +9,7 @@ using CtrEditor.Simulacion;
|
||||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||||
using CtrEditor.FuncionesBase;
|
using CtrEditor.FuncionesBase;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using Siemens.Simatic.Simulation.Runtime;
|
||||||
|
|
||||||
namespace CtrEditor.ObjetosSim
|
namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
|
@ -124,6 +126,16 @@ namespace CtrEditor.ObjetosSim
|
||||||
ActualizarGeometrias();
|
ActualizarGeometrias();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void AnchoChanged(float value)
|
||||||
|
{
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AnguloChanged(float value)
|
||||||
|
{
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Category("Configuración")]
|
[property: Category("Configuración")]
|
||||||
[property: Description("Actuar como freno")]
|
[property: Description("Actuar como freno")]
|
||||||
|
@ -132,8 +144,14 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
partial void OnEsFrenoChanged(bool value)
|
partial void OnEsFrenoChanged(bool value)
|
||||||
{
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[🔧 FRENO CAMBIO] Transporte '{Nombre}' - EsFreno cambiado a: {value}");
|
||||||
|
ActualizarGeometrias();
|
||||||
|
|
||||||
|
// Verificar que la asignación se realizó correctamente
|
||||||
if (SimGeometria != null)
|
if (SimGeometria != null)
|
||||||
SimGeometria.isBrake = value;
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[✅ FRENO SYNC] SimGeometria.isBrake = {SimGeometria.isBrake}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
|
@ -142,6 +160,11 @@ namespace CtrEditor.ObjetosSim
|
||||||
[property: Name("Coeficiente Fricción")]
|
[property: Name("Coeficiente Fricción")]
|
||||||
public float frictionCoefficient;
|
public float frictionCoefficient;
|
||||||
|
|
||||||
|
partial void OnFrictionCoefficientChanged(float value)
|
||||||
|
{
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Category("Configuración")]
|
[property: Category("Configuración")]
|
||||||
[property: Description("Velocidad máxima a 50Hz")]
|
[property: Description("Velocidad máxima a 50Hz")]
|
||||||
|
@ -160,32 +183,132 @@ namespace CtrEditor.ObjetosSim
|
||||||
[property: Name("En Marcha")]
|
[property: Name("En Marcha")]
|
||||||
public bool esMarcha;
|
public bool esMarcha;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[property: Category("Información")]
|
||||||
|
[property: Description("Información sobre botellas en el transporte con freno")]
|
||||||
|
[property: Name("Info Botellas Freno")]
|
||||||
|
public string infoBotellasFreno = "Sin información";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza la información de botellas en el transporte con freno
|
||||||
|
/// </summary>
|
||||||
|
public void ActualizarInfoBotellasFreno()
|
||||||
|
{
|
||||||
|
if (SimGeometria?.isBrake == true && simulationManager != null)
|
||||||
|
{
|
||||||
|
// Contar botellas marcadas como en transporte con freno
|
||||||
|
var botellasEnFreno = simulationManager.Cuerpos
|
||||||
|
.OfType<simBotella>()
|
||||||
|
.Where(b => b.isOnBrakeTransport)
|
||||||
|
.Count();
|
||||||
|
|
||||||
|
InfoBotellasFreno = $"Botellas con freno: {botellasEnFreno}";
|
||||||
|
}
|
||||||
|
else if (SimGeometria?.isBrake == false)
|
||||||
|
{
|
||||||
|
InfoBotellasFreno = "Transporte SIN freno";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InfoBotellasFreno = "Sin información";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Category("Configuración")]
|
[property: Category("Configuración")]
|
||||||
[property: Description("Distancia entre guías")]
|
[property: Description("Distancia entre guías")]
|
||||||
[property: Name("Distancia")]
|
[property: Name("Distancia")]
|
||||||
private float distance;
|
private float distance;
|
||||||
|
|
||||||
|
partial void OnDistanceChanged(float value)
|
||||||
|
{
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Category("Configuración")]
|
[property: Category("Configuración")]
|
||||||
[property: Description("Alto de las guías")]
|
[property: Description("Alto de las guías")]
|
||||||
[property: Name("Alto Guía")]
|
[property: Name("Alto Guía")]
|
||||||
private float altoGuia;
|
private float altoGuia;
|
||||||
|
|
||||||
|
partial void OnAltoGuiaChanged(float value)
|
||||||
|
{
|
||||||
|
ActualizarGeometrias();
|
||||||
|
}
|
||||||
|
|
||||||
private void ActualizarGeometrias()
|
private void ActualizarGeometrias()
|
||||||
{
|
{
|
||||||
if (_visualRepresentation is ucTransporteGuias uc)
|
if (_visualRepresentation is ucTransporteGuias uc)
|
||||||
{
|
{
|
||||||
UpdateRectangle(SimGeometria, uc.Transporte, Alto, Ancho, Angulo);
|
UpdateRectangle(SimGeometria, uc.Transporte, Alto, Ancho, Angulo);
|
||||||
UpdateOrCreateLine(Guia_Superior, uc.GuiaSuperior);
|
|
||||||
UpdateOrCreateLine(Guia_Inferior, uc.GuiaInferior);
|
|
||||||
|
|
||||||
SimGeometria.DistanceGuide2Guide = Alto;
|
// Actualizar guías con método específico para transporte
|
||||||
SimGeometria.isBrake = esFreno;
|
UpdateTransportGuide(Guia_Superior, uc.GuiaSuperior, isTopGuide: true);
|
||||||
|
UpdateTransportGuide(Guia_Inferior, uc.GuiaInferior, isTopGuide: false);
|
||||||
|
|
||||||
|
if (SimGeometria != null)
|
||||||
|
{
|
||||||
|
SimGeometria.TransportWithGuides = true;
|
||||||
|
SimGeometria.DistanceGuide2Guide = Distance;
|
||||||
|
SimGeometria.isBrake = EsFreno; // Usar propiedad generada
|
||||||
|
SimGeometria.Friction = FrictionCoefficient; // Usar propiedad generada
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[🔄 ACTUALIZAR GEOMETRIAS] Transporte '{Nombre}' - TransportWithGuides={SimGeometria.TransportWithGuides}, DistanceGuide2Guide={SimGeometria.DistanceGuide2Guide:F3}, isBrake={SimGeometria.isBrake}, Friction={SimGeometria.Friction:F3}, Speed={SimGeometria.Speed:F1}");
|
||||||
|
|
||||||
|
// Actualizar información de botellas si es un transporte con freno
|
||||||
|
ActualizarInfoBotellasFreno();
|
||||||
|
}
|
||||||
|
|
||||||
SetSpeed();
|
SetSpeed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza una guía específica del transporte (superior o inferior)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="simGuia">Objeto de simulación de la guía</param>
|
||||||
|
/// <param name="wpfRect">Rectángulo WPF de la guía</param>
|
||||||
|
/// <param name="isTopGuide">true para guía superior, false para inferior</param>
|
||||||
|
private void UpdateTransportGuide(simGuia simGuia, System.Windows.Shapes.Rectangle wpfRect, bool isTopGuide)
|
||||||
|
{
|
||||||
|
if (simGuia != null && wpfRect != null)
|
||||||
|
{
|
||||||
|
// Actualizar propiedades específicas de la guía
|
||||||
|
simGuia.UpdateProperties(Ancho, AltoGuia, Angulo);
|
||||||
|
|
||||||
|
// Calcular posición usando el rectángulo WPF actual
|
||||||
|
var topLeft2D = GetRectangleTopLeft(wpfRect);
|
||||||
|
|
||||||
|
// Crear/actualizar la guía con las dimensiones correctas
|
||||||
|
simGuia.Create(Ancho, AltoGuia, topLeft2D, Angulo);
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[UpdateTransportGuide] {(isTopGuide ? "Superior" : "Inferior")} - Pos: ({topLeft2D.X:F3}, {topLeft2D.Y:F3}), Ancho: {Ancho:F3}, Alto: {AltoGuia:F3}, Ángulo: {Angulo:F1}°");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crea una guía específica del transporte (superior o inferior)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="wpfRect">Rectángulo WPF de la guía</param>
|
||||||
|
/// <param name="isTopGuide">true para guía superior, false para inferior</param>
|
||||||
|
/// <returns>Objeto simGuia creado</returns>
|
||||||
|
private simGuia CreateTransportGuide(System.Windows.Shapes.Rectangle wpfRect, bool isTopGuide)
|
||||||
|
{
|
||||||
|
if (wpfRect != null)
|
||||||
|
{
|
||||||
|
// Calcular posición usando el rectángulo WPF actual
|
||||||
|
var topLeft2D = GetRectangleTopLeft(wpfRect);
|
||||||
|
|
||||||
|
// Crear la guía con las dimensiones del transporte
|
||||||
|
var simGuia = simulationManager.AddLine(Ancho, AltoGuia, topLeft2D, Angulo);
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[CreateTransportGuide] {(isTopGuide ? "Superior" : "Inferior")} creada - Pos: ({topLeft2D.X:F3}, {topLeft2D.Y:F3}), Ancho: {Ancho:F3}, Alto: {AltoGuia:F3}, Ángulo: {Angulo:F1}°");
|
||||||
|
|
||||||
|
return simGuia;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public override void OnMoveResizeRotate()
|
public override void OnMoveResizeRotate()
|
||||||
{
|
{
|
||||||
ActualizarGeometrias();
|
ActualizarGeometrias();
|
||||||
|
@ -196,7 +319,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
Ancho = 1;
|
Ancho = 1;
|
||||||
Alto = 0.10f;
|
Alto = 0.10f;
|
||||||
AltoGuia = 0.03f;
|
AltoGuia = 0.03f;
|
||||||
Distance = 0.01f;
|
Distance = 0.5f;
|
||||||
Tag_ReleActivatedMotor = "1";
|
Tag_ReleActivatedMotor = "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,6 +343,12 @@ namespace CtrEditor.ObjetosSim
|
||||||
VelocidadActual = id_motor.Velocidad;
|
VelocidadActual = id_motor.Velocidad;
|
||||||
else
|
else
|
||||||
VelocidadActual = 0;
|
VelocidadActual = 0;
|
||||||
|
|
||||||
|
// Actualizar información de botellas en tiempo real si es un transporte con freno
|
||||||
|
if (EsFreno)
|
||||||
|
{
|
||||||
|
ActualizarInfoBotellasFreno();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ucLoaded()
|
public override void ucLoaded()
|
||||||
|
@ -233,12 +362,22 @@ namespace CtrEditor.ObjetosSim
|
||||||
if (_visualRepresentation is ucTransporteGuias uc)
|
if (_visualRepresentation is ucTransporteGuias uc)
|
||||||
{
|
{
|
||||||
SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo);
|
SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo);
|
||||||
SimGeometria.TransportWithGuides = true;
|
|
||||||
SimGeometria.DistanceGuide2Guide = Alto;
|
// CORREGIR: Asegurar que todas las propiedades se inicialicen correctamente
|
||||||
Guia_Superior = AddLine(simulationManager, uc.GuiaSuperior);
|
if (SimGeometria != null)
|
||||||
Guia_Inferior = AddLine(simulationManager, uc.GuiaInferior);
|
{
|
||||||
|
SimGeometria.TransportWithGuides = true; // Siempre true para TransporteGuias
|
||||||
|
SimGeometria.DistanceGuide2Guide = Distance; // Usar Distance en lugar de Alto
|
||||||
|
SimGeometria.isBrake = EsFreno; // Usar propiedad generada
|
||||||
|
SimGeometria.Friction = FrictionCoefficient; // Usar propiedad generada
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear guías usando método específico para transporte
|
||||||
|
Guia_Superior = CreateTransportGuide(uc.GuiaSuperior, isTopGuide: true);
|
||||||
|
Guia_Inferior = CreateTransportGuide(uc.GuiaInferior, isTopGuide: false);
|
||||||
|
|
||||||
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
|
CrearAnimacionStoryBoardTrasnporte(uc.Transporte, InvertirDireccion);
|
||||||
|
SetSpeed(); // Aplicar velocidad inicial
|
||||||
}
|
}
|
||||||
OnId_MotorChanged(Id_Motor); // Link Id_Motor = Motor
|
OnId_MotorChanged(Id_Motor); // Link Id_Motor = Motor
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using CtrEditor.FuncionesBase;
|
using CtrEditor.FuncionesBase;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace CtrEditor.ObjetosSim
|
namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
using CtrEditor.Simulacion;
|
using CtrEditor.Simulacion;
|
||||||
using LibS7Adv;
|
using LibS7Adv; // Using stub implementation
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
@ -9,6 +9,7 @@ using CtrEditor.FuncionesBase;
|
||||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||||
using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute;
|
using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace CtrEditor.ObjetosSim
|
namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
|
@ -34,7 +35,14 @@ namespace CtrEditor.ObjetosSim
|
||||||
public override string Nombre
|
public override string Nombre
|
||||||
{
|
{
|
||||||
get => nombre;
|
get => nombre;
|
||||||
set => SetProperty(ref nombre, value);
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref nombre, value))
|
||||||
|
{
|
||||||
|
// Asegurar que siempre se notifique el cambio, incluso en inicialización
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
|
@ -86,12 +94,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
[property: Name("Ancho del Haz")]
|
[property: Name("Ancho del Haz")]
|
||||||
float ancho_Haz_De_Luz;
|
float ancho_Haz_De_Luz;
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
[property: Description("Distancia al cuello de la botella")]
|
|
||||||
[property: Category("Información")]
|
|
||||||
[property: Name("Distancia al Cuello")]
|
|
||||||
float distancia_cuello;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: Description("Tipo de detección: cuello de botella o botella completa")]
|
[property: Description("Tipo de detección: cuello de botella o botella completa")]
|
||||||
[property: Category("Configuración")]
|
[property: Category("Configuración")]
|
||||||
|
@ -102,7 +104,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
if (Simulation_Photocell == null) return;
|
if (Simulation_Photocell == null) return;
|
||||||
|
|
||||||
Simulation_Photocell.DetectNeck = value;
|
simulationManager.SetBarreraDetectNeck(Simulation_Photocell, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -200,6 +202,10 @@ namespace CtrEditor.ObjetosSim
|
||||||
Frecuency = 0;
|
Frecuency = 0;
|
||||||
timer = new Stopwatch();
|
timer = new Stopwatch();
|
||||||
timer.Start();
|
timer.Start();
|
||||||
|
Color = Brushes.Black;
|
||||||
|
|
||||||
|
// Forzar notificación de la propiedad Nombre para asegurar binding inicial
|
||||||
|
OnPropertyChanged(nameof(Nombre));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateGeometryStart()
|
public override void UpdateGeometryStart()
|
||||||
|
@ -209,11 +215,14 @@ namespace CtrEditor.ObjetosSim
|
||||||
}
|
}
|
||||||
public override void UpdateControl(int elapsedMilliseconds)
|
public override void UpdateControl(int elapsedMilliseconds)
|
||||||
{
|
{
|
||||||
Distancia_cuello = Simulation_Photocell.Distancia;
|
if (Simulation_Photocell == null) return;
|
||||||
|
|
||||||
|
var barreraData = simulationManager.GetBarreraData(Simulation_Photocell);
|
||||||
|
|
||||||
if (DetectarCuello)
|
if (DetectarCuello)
|
||||||
LuzCortada = Simulation_Photocell.LuzCortadaNeck;
|
LuzCortada = barreraData.LuzCortadaNeck;
|
||||||
else
|
else
|
||||||
LuzCortada = Simulation_Photocell.LuzCortada > 0;
|
LuzCortada = barreraData.LuzCortada;
|
||||||
}
|
}
|
||||||
public override void UpdatePLCPrimerCiclo()
|
public override void UpdatePLCPrimerCiclo()
|
||||||
{
|
{
|
||||||
|
@ -232,13 +241,17 @@ namespace CtrEditor.ObjetosSim
|
||||||
base.ucLoaded();
|
base.ucLoaded();
|
||||||
|
|
||||||
if (_visualRepresentation is ucPhotocell uc)
|
if (_visualRepresentation is ucPhotocell uc)
|
||||||
Simulation_Photocell = AddBarrera(simulationManager, uc.Photocell, Alto, Ancho, Angulo, DetectarCuello);
|
Simulation_Photocell = AddBarrera(simulationManager, uc.Photocell, Ancho_Haz_De_Luz, Ancho, Angulo, DetectarCuello);
|
||||||
}
|
}
|
||||||
public override void ucUnLoaded()
|
public override void ucUnLoaded()
|
||||||
{
|
{
|
||||||
// El UserControl se esta eliminando
|
// El UserControl se esta eliminando
|
||||||
// eliminar el objeto de simulacion
|
// eliminar el objeto de simulacion
|
||||||
simulationManager.Remove(Simulation_Photocell);
|
if (Simulation_Photocell != null)
|
||||||
|
{
|
||||||
|
simulationManager.RemoveBarrera(Simulation_Photocell);
|
||||||
|
Simulation_Photocell = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AssignDatos(UserControl userControl, osBase datos, SimulationManagerFP simulationManager)
|
public static void AssignDatos(UserControl userControl, osBase datos, SimulationManagerBEPU simulationManager)
|
||||||
{
|
{
|
||||||
if (userControl is IDataContainer dataContainer)
|
if (userControl is IDataContainer dataContainer)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,30 +1,29 @@
|
||||||
<UserControl x:Class="CtrEditor.ObjetosSim.UserControls.CircularSegment"
|
<UserControl x:Class="CtrEditor.ObjetosSim.UserControls.CircularSegment"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:CtrEditor.ObjetosSim.UserControls"
|
xmlns:local="clr-namespace:CtrEditor.ObjetosSim.UserControls" mc:Ignorable="d" Name="circularSegmentControl">
|
||||||
mc:Ignorable="d" Name="circularSegmentControl">
|
|
||||||
<Canvas Name="mainCanvas">
|
<Canvas Name="mainCanvas">
|
||||||
<Path Name="path" Stroke="Black" StrokeThickness="1">
|
<Path Name="path" Stroke="Black" StrokeThickness="1">
|
||||||
<Path.Fill>
|
<Path.Fill>
|
||||||
<VisualBrush TileMode="Tile" Viewport="0,0,20,20" ViewportUnits="Absolute">
|
<VisualBrush TileMode="Tile" Viewport="0,0,20,20" ViewportUnits="Absolute">
|
||||||
<VisualBrush.Transform>
|
<VisualBrush.Transform>
|
||||||
<TransformGroup>
|
<TransformGroup>
|
||||||
<TranslateTransform X="0" Y="0"/>
|
<TranslateTransform X="0" Y="0" />
|
||||||
</TransformGroup>
|
</TransformGroup>
|
||||||
</VisualBrush.Transform>
|
</VisualBrush.Transform>
|
||||||
<VisualBrush.Visual>
|
<VisualBrush.Visual>
|
||||||
<Canvas Width="20" Height="20">
|
<Canvas Width="20" Height="20">
|
||||||
<!-- Patrón de rayas radiales que simulan el movimiento tangencial -->
|
<!-- Patrón de rayas radiales que simulan el movimiento tangencial -->
|
||||||
<Rectangle Fill="LightGray" Width="10" Height="10" Canvas.Left="0" Canvas.Top="0"/>
|
<Rectangle Fill="LightGray" Width="10" Height="10" Canvas.Left="0" Canvas.Top="0" />
|
||||||
<Rectangle Fill="GhostWhite" Width="10" Height="10" Canvas.Left="0" Canvas.Top="10"/>
|
<Rectangle Fill="GhostWhite" Width="10" Height="10" Canvas.Left="0" Canvas.Top="10" />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</VisualBrush.Visual>
|
</VisualBrush.Visual>
|
||||||
</VisualBrush>
|
</VisualBrush>
|
||||||
</Path.Fill>
|
</Path.Fill>
|
||||||
<Path.RenderTransform>
|
<Path.RenderTransform>
|
||||||
<RotateTransform Angle="{Binding Angle, ElementName=circularSegmentControl}"/>
|
<RotateTransform Angle="{Binding Angle, ElementName=circularSegmentControl}" />
|
||||||
</Path.RenderTransform>
|
</Path.RenderTransform>
|
||||||
</Path>
|
</Path>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
|
@ -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 ...
|
||||||
|
@ -16,7 +16,6 @@ using System.Windows.Media;
|
||||||
using System.Windows.Media.Animation;
|
using System.Windows.Media.Animation;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Tesseract;
|
|
||||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||||
using Application = System.Windows.Application;
|
using Application = System.Windows.Application;
|
||||||
using ItemCollection = Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection;
|
using ItemCollection = Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection;
|
||||||
|
@ -44,16 +43,16 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
private MainViewModel? _mainViewModel;
|
private MainViewModel? _mainViewModel;
|
||||||
private UserControl? VisualRepresentation;
|
private UserControl? VisualRepresentation;
|
||||||
private SimulationManagerFP? simulationManager;
|
private SimulationManagerBEPU? simulationManager;
|
||||||
|
|
||||||
public DataSaveToSerialize(MainViewModel a, UserControl b, SimulationManagerFP c)
|
public DataSaveToSerialize(MainViewModel a, UserControl b, SimulationManagerBEPU c)
|
||||||
{
|
{
|
||||||
_mainViewModel = a;
|
_mainViewModel = a;
|
||||||
VisualRepresentation = b;
|
VisualRepresentation = b;
|
||||||
simulationManager = c;
|
simulationManager = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DataRestoreAfterSerialize(out MainViewModel a, out UserControl b, out SimulationManagerFP c)
|
public void DataRestoreAfterSerialize(out MainViewModel a, out UserControl b, out SimulationManagerBEPU c)
|
||||||
{
|
{
|
||||||
a = _mainViewModel;
|
a = _mainViewModel;
|
||||||
b = VisualRepresentation;
|
b = VisualRepresentation;
|
||||||
|
@ -62,6 +61,25 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Estructura para almacenar las dimensiones de los objetos de simulación
|
||||||
|
/// </summary>
|
||||||
|
public struct SimObjectDimensions
|
||||||
|
{
|
||||||
|
public float Width { get; set; }
|
||||||
|
public float Height { get; set; }
|
||||||
|
public float Radius { get; set; }
|
||||||
|
public int ObjectType { get; set; } // Tipo de objeto: 1=Transport, 2=Barrier, 3=Guide, etc.
|
||||||
|
|
||||||
|
public bool Equals(SimObjectDimensions other)
|
||||||
|
{
|
||||||
|
return Math.Abs(Width - other.Width) < 0.001f &&
|
||||||
|
Math.Abs(Height - other.Height) < 0.001f &&
|
||||||
|
Math.Abs(Radius - other.Radius) < 0.001f &&
|
||||||
|
ObjectType == other.ObjectType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public abstract partial class osBase : ObservableObject
|
public abstract partial class osBase : ObservableObject
|
||||||
{
|
{
|
||||||
public virtual string Nombre { get; set; } = "osBase";
|
public virtual string Nombre { get; set; } = "osBase";
|
||||||
|
@ -71,6 +89,11 @@ namespace CtrEditor.ObjetosSim
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private System.Threading.Timer timer = null;
|
private System.Threading.Timer timer = null;
|
||||||
|
|
||||||
|
// ✅ SISTEMA DE DIMENSIONES: Para evitar recreación innecesaria de objetos físicos
|
||||||
|
[JsonIgnore]
|
||||||
|
private static Dictionary<simBase, SimObjectDimensions> _lastKnownDimensions = new Dictionary<simBase, SimObjectDimensions>();
|
||||||
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[property: JsonIgnore]
|
[property: JsonIgnore]
|
||||||
[property: Hidden]
|
[property: Hidden]
|
||||||
|
@ -113,6 +136,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
// Actualizar posición relativa si el movimiento no viene del FramePlate
|
// Actualizar posición relativa si el movimiento no viene del FramePlate
|
||||||
UpdateFramePlateRelativePosition();
|
UpdateFramePlateRelativePosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void LeftChanging(float oldValue, float newValue) { }
|
public virtual void LeftChanging(float oldValue, float newValue) { }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
|
@ -566,89 +590,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public string CaptureImageAreaAndDoOCR(float Left, float Top, float Ancho, float Alto, float Angulo = 0, bool ShowPreview = false)
|
|
||||||
{
|
|
||||||
if (_mainViewModel?.MainCanvas.Children[0] is Image imagenDeFondo)
|
|
||||||
{
|
|
||||||
if (imagenDeFondo.Source is BitmapSource bitmapSource)
|
|
||||||
{
|
|
||||||
float originalDpiX = (float)bitmapSource.DpiX;
|
|
||||||
float originalDpiY = (float)bitmapSource.DpiY;
|
|
||||||
|
|
||||||
float canvasDpiX = 96;
|
|
||||||
float canvasDpiY = 96;
|
|
||||||
|
|
||||||
float scaleFactorX = originalDpiX / canvasDpiX;
|
|
||||||
float scaleFactorY = originalDpiY / canvasDpiY;
|
|
||||||
|
|
||||||
int x = (int)MeterToPixels(Left * scaleFactorX);
|
|
||||||
int y = (int)MeterToPixels(Top * scaleFactorY);
|
|
||||||
int width = (int)MeterToPixels(Ancho * scaleFactorX);
|
|
||||||
int height = (int)MeterToPixels(Alto * scaleFactorY);
|
|
||||||
|
|
||||||
if (x < 0) x = 0;
|
|
||||||
if (y < 0) y = 0;
|
|
||||||
if (x + width > bitmapSource.PixelWidth) width = bitmapSource.PixelWidth - x;
|
|
||||||
if (y + height > bitmapSource.PixelHeight) height = bitmapSource.PixelHeight - y;
|
|
||||||
|
|
||||||
CroppedBitmap croppedBitmap = new CroppedBitmap(bitmapSource, new Int32Rect(x, y, width, height));
|
|
||||||
|
|
||||||
TransformedBitmap transformedBitmap = new TransformedBitmap();
|
|
||||||
transformedBitmap.BeginInit();
|
|
||||||
transformedBitmap.Source = croppedBitmap;
|
|
||||||
|
|
||||||
if (Angulo != 0)
|
|
||||||
{
|
|
||||||
// TransformedBitmap only accepts rotations in 90-degree increments
|
|
||||||
// Round to nearest 90 degrees: 0, 90, 180, or 270
|
|
||||||
double normalizedAngle = Math.Round(Angulo / 90.0) * 90.0;
|
|
||||||
RotateTransform rotateTransform = new RotateTransform(-normalizedAngle);
|
|
||||||
transformedBitmap.Transform = rotateTransform;
|
|
||||||
}
|
|
||||||
|
|
||||||
transformedBitmap.EndInit();
|
|
||||||
|
|
||||||
PngBitmapEncoder encoder = new PngBitmapEncoder();
|
|
||||||
encoder.Frames.Add(BitmapFrame.Create(transformedBitmap));
|
|
||||||
|
|
||||||
using (MemoryStream memoryStream = new MemoryStream())
|
|
||||||
{
|
|
||||||
encoder.Save(memoryStream);
|
|
||||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
if (ShowPreview) ShowPreviewWindow(memoryStream);
|
|
||||||
|
|
||||||
using (var bmp = new System.Drawing.Bitmap(memoryStream))
|
|
||||||
{
|
|
||||||
int targetDpi = 400;
|
|
||||||
var resizedBmp = new System.Drawing.Bitmap(bmp, new System.Drawing.Size(bmp.Width * targetDpi / (int)originalDpiX, bmp.Height * targetDpi / (int)originalDpiY));
|
|
||||||
|
|
||||||
using (var msResized = new MemoryStream())
|
|
||||||
{
|
|
||||||
resizedBmp.Save(msResized, System.Drawing.Imaging.ImageFormat.Png);
|
|
||||||
msResized.Seek(0, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
using (var img = Pix.LoadFromMemory(msResized.ToArray()))
|
|
||||||
{
|
|
||||||
// Use AppDomain.CurrentDomain.BaseDirectory to ensure we find Tesseract in the application directory
|
|
||||||
string tesseractPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Tesseract");
|
|
||||||
using (var engine = new TesseractEngine(tesseractPath, "eng", EngineMode.Default))
|
|
||||||
{
|
|
||||||
// Configuraciones para mejorar el OCR de una sola letra
|
|
||||||
engine.SetVariable("tessedit_char_whitelist", " ABCDEFGHIJKLMNÑOPQRSTUVWXYZabcdefghijklmnñopqrstuvwxyz0123456789-./"); // Lista blanca de caracteres
|
|
||||||
var result = engine.Process(img);
|
|
||||||
return result.GetText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Reemplaza el método existente CaptureImageAreaAndDoOCR con esta implementación
|
// Reemplaza el método existente CaptureImageAreaAndDoOCR con esta implementación
|
||||||
public string CaptureImageAreaAndDoOCRPPaddle(float Left, float Top, float Ancho, float Alto, float Angulo = 0, bool ShowPreview = false)
|
public string CaptureImageAreaAndDoOCRPPaddle(float Left, float Top, float Ancho, float Alto, float Angulo = 0, bool ShowPreview = false)
|
||||||
{
|
{
|
||||||
|
@ -768,7 +709,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
[property: Name("Habilitado en Todas Páginas")]
|
[property: Name("Habilitado en Todas Páginas")]
|
||||||
private bool enable_On_All_Pages;
|
private bool enable_On_All_Pages;
|
||||||
|
|
||||||
|
|
||||||
partial void OnEnable_On_All_PagesChanged(bool value)
|
partial void OnEnable_On_All_PagesChanged(bool value)
|
||||||
{
|
{
|
||||||
// Si se está desactivando el modo global
|
// Si se está desactivando el modo global
|
||||||
|
@ -785,7 +725,6 @@ namespace CtrEditor.ObjetosSim
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Local Data for Global Objects
|
// Local Data for Global Objects
|
||||||
[NotifyPropertyChangedFor(nameof(Show_On_This_Page))]
|
[NotifyPropertyChangedFor(nameof(Show_On_This_Page))]
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
|
@ -1050,6 +989,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
/// <param name="elapsedMilliseconds"></param>
|
/// <param name="elapsedMilliseconds"></param>
|
||||||
public virtual void UpdateControl(int elapsedMilliseconds) { }
|
public virtual void UpdateControl(int elapsedMilliseconds) { }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Se llama antes de comenzar la simulacion con el boton de Iniciar simulacion.
|
/// Se llama antes de comenzar la simulacion con el boton de Iniciar simulacion.
|
||||||
/// La idea es actualizar los objetos en el motor fisico antes de comenzar la simulacion fisica.
|
/// La idea es actualizar los objetos en el motor fisico antes de comenzar la simulacion fisica.
|
||||||
|
@ -1111,7 +1051,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
/// Link al Simualdor fisico.
|
/// Link al Simualdor fisico.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public SimulationManagerFP simulationManager;
|
public SimulationManagerBEPU simulationManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prepara la clase para ser serializable poniendo a null los objetos que tienen referencias circulares
|
/// Prepara la clase para ser serializable poniendo a null los objetos que tienen referencias circulares
|
||||||
|
@ -1602,56 +1542,112 @@ namespace CtrEditor.ObjetosSim
|
||||||
return new Vector2((topLeft.X + bottomRight.X) / 2, (topLeft.Y + bottomRight.Y) / 2);
|
return new Vector2((topLeft.X + bottomRight.X) / 2, (topLeft.Y + bottomRight.Y) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Vector2 GetRectangleTopLeft(Rectangle wpfRect)
|
||||||
|
{
|
||||||
|
var coords = GetRectangleCoordinatesInMeter(wpfRect);
|
||||||
|
return coords.TopLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void UpdateRectangle(simTransporte simRect, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
|
public void UpdateRectangle(simTransporte simRect, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
|
||||||
{
|
{
|
||||||
if (simRect != null)
|
if (simRect != null)
|
||||||
simRect.Create(Ancho, Alto, GetRectangleCenter(wpfRect), Angulo);
|
{
|
||||||
|
var topLeft2D = GetRectangleTopLeft(wpfRect);
|
||||||
|
|
||||||
|
// ✅ SISTEMA INTELIGENTE: Solo recrear si las dimensiones han cambiado
|
||||||
|
bool dimensionsChanged = HasTransportDimensionsChanged(simRect, Ancho, Alto);
|
||||||
|
|
||||||
|
if (dimensionsChanged)
|
||||||
|
{
|
||||||
|
// Las dimensiones cambiaron, recrear el objeto físico
|
||||||
|
simRect.Create(Ancho, Alto, topLeft2D, Angulo);
|
||||||
|
simRect.SetDimensions(Ancho, Alto); // Actualizar dimensiones internas
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// ✅ CORREGIDO: Usar UpdateFromWpfParameters para manejar correctamente Top-Left + Ángulo
|
||||||
|
simRect.UpdateFromWpfParameters(topLeft2D, Angulo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar propiedades cacheadas
|
||||||
|
simRect.UpdateCachedProperties();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateRectangle(simBarrera simRect, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
|
public void UpdateRectangle(simBarrera barrera, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
|
||||||
{
|
{
|
||||||
if (simRect != null)
|
if (barrera != null)
|
||||||
simRect.Create(Ancho, Alto, GetRectangleCenter(wpfRect), Angulo);
|
{
|
||||||
|
var topLeft2D = GetRectangleTopLeft(wpfRect);
|
||||||
|
simulationManager.UpdateBarrera(barrera, Ancho, Alto, topLeft2D, Angulo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateCurve(simCurve curva, float RadioInterno, float RadioExterno, float startAngle, float endAngle)
|
public simTransporte AddRectangle(SimulationManagerBEPU simulationManager, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
|
||||||
{
|
{
|
||||||
curva.Create(RadioInterno, RadioExterno, startAngle, endAngle, GetCurveCenterInMeter(RadioExterno));
|
var topLeft2D = GetRectangleTopLeft(wpfRect);
|
||||||
|
return simulationManager.AddRectangle(Ancho, Alto, topLeft2D, Angulo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public simCurve AddCurve(float RadioInterno, float RadioExterno, float startAngle, float endAngle)
|
public simBarrera AddBarrera(SimulationManagerBEPU simulationManager, Rectangle wpfRect, float Alto, float Ancho, float Angulo, bool detectarCuello)
|
||||||
{
|
{
|
||||||
return simulationManager.AddCurve(RadioInterno, RadioExterno, startAngle, endAngle, GetCurveCenterInMeter(RadioExterno));
|
var topLeft2D = GetRectangleTopLeft(wpfRect);
|
||||||
}
|
// Convertir Vector2 a Vector3 para BEPU (Z = 0 para objetos 2D)
|
||||||
|
return simulationManager.CreateBarrera(Ancho, Alto, topLeft2D, Angulo, detectarCuello);
|
||||||
public simTransporte AddRectangle(SimulationManagerFP simulationManager, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
|
|
||||||
{
|
|
||||||
return simulationManager.AddRectangle(Ancho, Alto, GetRectangleCenter(wpfRect), Angulo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public simBarrera AddBarrera(SimulationManagerFP simulationManager, Rectangle wpfRect, float Alto, float Ancho, float Angulo, bool detectarCuello)
|
|
||||||
{
|
|
||||||
return simulationManager.AddBarrera(Ancho, Alto, GetRectangleCenter(wpfRect), Angulo, detectarCuello);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateOrCreateLine(simGuia simGuia, Rectangle wpfRect)
|
public void UpdateOrCreateLine(simGuia simGuia, Rectangle wpfRect)
|
||||||
{
|
{
|
||||||
if (simGuia != null)
|
if (simGuia != null)
|
||||||
{
|
{
|
||||||
|
// Usar el mismo sistema que transportes: Top-Left + Ángulo
|
||||||
|
var topLeft2D = GetRectangleTopLeft(wpfRect);
|
||||||
|
|
||||||
var coords = GetCenterLineVectors(wpfRect);
|
// Actualizar las propiedades desde el objeto osGuia
|
||||||
|
if (this is osGuia guiaObj)
|
||||||
// Crear o actualizar simRectangle
|
{
|
||||||
simGuia.Create(coords.Start, coords.End); // asumiendo que el ángulo inicial es 0
|
// ✅ SISTEMA INTELIGENTE: Solo recrear si las dimensiones han cambiado
|
||||||
|
bool dimensionsChanged = HasGuideDimensionsChanged(simGuia, guiaObj.Ancho, guiaObj.AltoGuia);
|
||||||
|
|
||||||
|
if (dimensionsChanged)
|
||||||
|
{
|
||||||
|
// Las dimensiones cambiaron, recrear el objeto físico
|
||||||
|
simGuia.Create(guiaObj.Ancho, guiaObj.AltoGuia, topLeft2D, guiaObj.Angulo);
|
||||||
|
simGuia.SetDimensions(guiaObj.Ancho, guiaObj.AltoGuia); // Actualizar dimensiones internas
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Solo actualizar posición y rotación
|
||||||
|
simGuia.SetPosition(topLeft2D, guiaObj.Angulo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar propiedades internas
|
||||||
|
simGuia.UpdateProperties(guiaObj.Ancho, guiaObj.AltoGuia, guiaObj.Angulo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public simGuia AddLine(SimulationManagerFP simulationManager, Rectangle wpfRect)
|
public simGuia AddLine(SimulationManagerBEPU simulationManager, Rectangle wpfRect)
|
||||||
{
|
{
|
||||||
var coords = GetCenterLineVectors(wpfRect);
|
// Usar el mismo sistema que transportes: Top-Left + Ángulo
|
||||||
return simulationManager.AddLine(coords.Start, coords.End);
|
var topLeft2D = GetRectangleTopLeft(wpfRect);
|
||||||
|
|
||||||
|
// Obtener propiedades desde el objeto osGuia
|
||||||
|
float ancho = 1.0f;
|
||||||
|
float altoGuia = 0.05f;
|
||||||
|
float angulo = 0f;
|
||||||
|
|
||||||
|
if (this is osGuia guiaObj)
|
||||||
|
{
|
||||||
|
ancho = guiaObj.Ancho;
|
||||||
|
altoGuia = guiaObj.AltoGuia;
|
||||||
|
angulo = guiaObj.Angulo;
|
||||||
|
}
|
||||||
|
|
||||||
|
var simGuia = simulationManager.AddLine(ancho, altoGuia, topLeft2D, angulo);
|
||||||
|
|
||||||
|
return simGuia;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageSource ImageFromPath(string value)
|
public ImageSource ImageFromPath(string value)
|
||||||
|
@ -1716,8 +1712,131 @@ namespace CtrEditor.ObjetosSim
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
// ✅ MÉTODOS PARA GESTIÓN INTELIGENTE DE DIMENSIONES
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifica si las dimensiones de un transporte han cambiado
|
||||||
|
/// </summary>
|
||||||
|
protected bool HasTransportDimensionsChanged(simTransporte transport, float newWidth, float newHeight)
|
||||||
|
{
|
||||||
|
if (transport == null) return true;
|
||||||
|
|
||||||
|
var newDimensions = new SimObjectDimensions
|
||||||
|
{
|
||||||
|
Width = newWidth,
|
||||||
|
Height = newHeight,
|
||||||
|
ObjectType = 1 // Transport
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_lastKnownDimensions.TryGetValue(transport, out var lastDimensions))
|
||||||
|
{
|
||||||
|
bool changed = !newDimensions.Equals(lastDimensions);
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
_lastKnownDimensions[transport] = newDimensions;
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[Dimensions] Transport dimensions CHANGED: {newWidth}x{newHeight}");
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Primera vez - consideramos como cambio
|
||||||
|
_lastKnownDimensions[transport] = newDimensions;
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[Dimensions] Transport first time: {newWidth}x{newHeight}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifica si las dimensiones de una guía han cambiado
|
||||||
|
/// </summary>
|
||||||
|
protected bool HasGuideDimensionsChanged(simGuia guide, float newWidth, float newHeight)
|
||||||
|
{
|
||||||
|
if (guide == null) return true;
|
||||||
|
|
||||||
|
var newDimensions = new SimObjectDimensions
|
||||||
|
{
|
||||||
|
Width = newWidth,
|
||||||
|
Height = newHeight,
|
||||||
|
ObjectType = 3 // Guide
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_lastKnownDimensions.TryGetValue(guide, out var lastDimensions))
|
||||||
|
{
|
||||||
|
bool changed = !newDimensions.Equals(lastDimensions);
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
_lastKnownDimensions[guide] = newDimensions;
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[Dimensions] Guide dimensions CHANGED: {newWidth}x{newHeight}");
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Primera vez - consideramos como cambio
|
||||||
|
_lastKnownDimensions[guide] = newDimensions;
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[Dimensions] Guide first time: {newWidth}x{newHeight}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO - Sistema inteligente de verificación de dimensiones de descartes
|
||||||
|
/// Verifica si el diámetro de un descarte ha cambiado
|
||||||
|
/// </summary>
|
||||||
|
protected bool HasDiscardDimensionsChanged(simDescarte discard, float newDiameter)
|
||||||
|
{
|
||||||
|
if (discard == null) return true;
|
||||||
|
|
||||||
|
var newDimensions = new SimObjectDimensions
|
||||||
|
{
|
||||||
|
Width = newDiameter,
|
||||||
|
Height = newDiameter,
|
||||||
|
Radius = newDiameter / 2f,
|
||||||
|
ObjectType = 4 // Tipo 4 = Descarte
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_lastKnownDimensions.TryGetValue(discard, out var lastDimensions))
|
||||||
|
{
|
||||||
|
bool changed = !newDimensions.Equals(lastDimensions);
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
_lastKnownDimensions[discard] = newDimensions;
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[Dimensions] Discard dimensions CHANGED: Ø{newDiameter}");
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Primera vez - consideramos como cambio
|
||||||
|
_lastKnownDimensions[discard] = newDimensions;
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[Dimensions] Discard first time: Ø{newDiameter}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Limpia las dimensiones almacenadas para un objeto específico
|
||||||
|
/// </summary>
|
||||||
|
public static void ClearStoredDimensions(simBase simObj)
|
||||||
|
{
|
||||||
|
if (simObj != null && _lastKnownDimensions.ContainsKey(simObj))
|
||||||
|
{
|
||||||
|
_lastKnownDimensions.Remove(simObj);
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[Dimensions] Cleared stored dimensions for {simObj.GetType().Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Limpia todas las dimensiones almacenadas (útil al cerrar simulación)
|
||||||
|
/// </summary>
|
||||||
|
public static void ClearAllStoredDimensions()
|
||||||
|
{
|
||||||
|
_lastKnownDimensions.Clear();
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[Dimensions] Cleared ALL stored dimensions");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
public class UniqueId
|
public class UniqueId
|
||||||
{
|
{
|
||||||
public int Value { get; set; }
|
public int Value { get; set; }
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,9 +14,9 @@ namespace CtrEditor.Serialization
|
||||||
{
|
{
|
||||||
private readonly DatosDeTrabajo _datosDeTrabajo;
|
private readonly DatosDeTrabajo _datosDeTrabajo;
|
||||||
private readonly MainViewModel _mainViewModel;
|
private readonly MainViewModel _mainViewModel;
|
||||||
private readonly SimulationManagerFP _simulationManager;
|
private readonly SimulationManagerBEPU _simulationManager;
|
||||||
|
|
||||||
public StateSerializer(MainViewModel mainViewModel, DatosDeTrabajo datosDeTrabajo, SimulationManagerFP simulationManager)
|
public StateSerializer(MainViewModel mainViewModel, DatosDeTrabajo datosDeTrabajo, SimulationManagerBEPU simulationManager)
|
||||||
{
|
{
|
||||||
_mainViewModel = mainViewModel;
|
_mainViewModel = mainViewModel;
|
||||||
_datosDeTrabajo = datosDeTrabajo;
|
_datosDeTrabajo = datosDeTrabajo;
|
||||||
|
|
1054
Simulacion/Aether.cs
1054
Simulacion/Aether.cs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,240 @@
|
||||||
|
using BepuPhysics;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CtrEditor.Simulacion
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Clase centralizada para manejar todas las conversiones entre coordenadas WPF y BEPU
|
||||||
|
/// WPF: Y hacia abajo, ángulos en sentido horario, Top-Left como referencia
|
||||||
|
/// BEPU: Y hacia arriba, ángulos en sentido antihorario, Center como referencia
|
||||||
|
/// </summary>
|
||||||
|
public static class CoordinateConverter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Convierte ángulo de WPF a BEPU (invierte signo) - USO INTERNO
|
||||||
|
/// </summary>
|
||||||
|
private static float WpfAngleToBepuAngle(float wpfAngle)
|
||||||
|
{
|
||||||
|
return -wpfAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convierte ángulo de BEPU a WPF (invierte signo) - USO INTERNO
|
||||||
|
/// </summary>
|
||||||
|
private static float BepuAngleToWpfAngle(float bepuAngle)
|
||||||
|
{
|
||||||
|
return -bepuAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convierte posición Y de WPF a BEPU (invierte signo) - USO INTERNO
|
||||||
|
/// </summary>
|
||||||
|
internal static float WpfYToBepuY(float wpfY)
|
||||||
|
{
|
||||||
|
return -wpfY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convierte posición Y de BEPU a WPF (invierte signo) - RETORNA VALOR WPF
|
||||||
|
/// </summary>
|
||||||
|
public static float BepuYToWpfY(float bepuY)
|
||||||
|
{
|
||||||
|
return -bepuY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convierte Vector2 de WPF a BEPU (solo invierte Y) - USO INTERNO
|
||||||
|
/// </summary>
|
||||||
|
internal static Vector2 WpfToBepuVector2(Vector2 wpfVector)
|
||||||
|
{
|
||||||
|
return new Vector2(wpfVector.X, -wpfVector.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convierte Vector2 de BEPU a WPF (solo invierte Y) - RETORNA VALOR WPF
|
||||||
|
/// </summary>
|
||||||
|
public static Vector2 BepuToWpfVector2(Vector2 bepuVector)
|
||||||
|
{
|
||||||
|
return new Vector2(bepuVector.X, -bepuVector.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convierte Vector3 de BEPU a Vector2 WPF (proyección XY con Y invertida) - RETORNA VALOR WPF
|
||||||
|
/// </summary>
|
||||||
|
public static Vector2 BepuVector3ToWpfVector2(Vector3 bepuVector)
|
||||||
|
{
|
||||||
|
return new Vector2(bepuVector.X, -bepuVector.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calcula la posición del centro en coordenadas BEPU desde Top-Left WPF - USO INTERNO
|
||||||
|
/// Maneja correctamente la rotación del objeto
|
||||||
|
/// </summary>
|
||||||
|
internal static Vector3 CalculateBepuCenterFromWpfTopLeft(Vector2 wpfTopLeft, float width, float height, float wpfAngle, float zPosition)
|
||||||
|
{
|
||||||
|
// Calcular el offset del centro desde Top-Left (sin rotación)
|
||||||
|
var offsetX = width / 2f;
|
||||||
|
var offsetY = height / 2f;
|
||||||
|
|
||||||
|
// Convertir ángulo WPF a radianes para cálculos trigonométricos
|
||||||
|
var angleRadians = simBase.GradosARadianes(wpfAngle);
|
||||||
|
var cos = (float)Math.Cos(angleRadians);
|
||||||
|
var sin = (float)Math.Sin(angleRadians);
|
||||||
|
|
||||||
|
// Rotar el offset alrededor del Top-Left usando el ángulo WPF
|
||||||
|
var rotatedOffsetX = offsetX * cos - offsetY * sin;
|
||||||
|
var rotatedOffsetY = offsetX * sin + offsetY * cos;
|
||||||
|
|
||||||
|
// Calcular nueva posición del centro manteniendo Top-Left fijo
|
||||||
|
var centerX = wpfTopLeft.X + rotatedOffsetX;
|
||||||
|
var centerY = wpfTopLeft.Y + rotatedOffsetY;
|
||||||
|
|
||||||
|
// Convertir a 3D con Y invertida para BEPU
|
||||||
|
var bepuY = WpfYToBepuY(centerY);
|
||||||
|
var result = new Vector3(centerX, bepuY, zPosition);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calcula la posición Top-Left WPF desde el centro BEPU - RETORNA VALOR WPF
|
||||||
|
/// Maneja correctamente la rotación del objeto
|
||||||
|
/// </summary>
|
||||||
|
public static Vector2 CalculateWpfTopLeftFromBepuCenter(Vector3 bepuCenter, float width, float height, float wpfAngle)
|
||||||
|
{
|
||||||
|
// Convertir centro BEPU a WPF
|
||||||
|
var wpfCenterX = bepuCenter.X;
|
||||||
|
var wpfCenterY = BepuYToWpfY(bepuCenter.Y);
|
||||||
|
|
||||||
|
// Calcular el offset del centro al Top-Left (sin rotación)
|
||||||
|
var offsetX = -width / 2f;
|
||||||
|
var offsetY = -height / 2f;
|
||||||
|
|
||||||
|
// Convertir ángulo WPF a radianes para cálculos trigonométricos
|
||||||
|
var angleRadians = simBase.GradosARadianes(wpfAngle);
|
||||||
|
var cos = (float)Math.Cos(angleRadians);
|
||||||
|
var sin = (float)Math.Sin(angleRadians);
|
||||||
|
|
||||||
|
// Rotar el offset usando el ángulo WPF
|
||||||
|
var rotatedOffsetX = offsetX * cos - offsetY * sin;
|
||||||
|
var rotatedOffsetY = offsetX * sin + offsetY * cos;
|
||||||
|
|
||||||
|
// Calcular Top-Left desde el centro
|
||||||
|
var topLeftX = wpfCenterX + rotatedOffsetX;
|
||||||
|
var topLeftY = wpfCenterY + rotatedOffsetY;
|
||||||
|
|
||||||
|
return new Vector2(topLeftX, topLeftY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crea un Quaternion para BEPU desde un ángulo WPF - USO INTERNO
|
||||||
|
/// </summary>
|
||||||
|
internal static Quaternion CreateBepuQuaternionFromWpfAngle(float wpfAngle)
|
||||||
|
{
|
||||||
|
var bepuAngle = WpfAngleToBepuAngle(wpfAngle);
|
||||||
|
return Quaternion.CreateFromAxisAngle(Vector3.UnitZ, simBase.GradosARadianes(bepuAngle));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extrae el ángulo WPF desde un Quaternion BEPU - RETORNA VALOR WPF
|
||||||
|
/// </summary>
|
||||||
|
public static float ExtractWpfAngleFromBepuQuaternion(Quaternion bepuQuaternion)
|
||||||
|
{
|
||||||
|
// Extraer ángulo Z del quaternion
|
||||||
|
var bepuAngleRadians = (float)Math.Atan2(
|
||||||
|
2.0 * (bepuQuaternion.W * bepuQuaternion.Z + bepuQuaternion.X * bepuQuaternion.Y),
|
||||||
|
1.0 - 2.0 * (bepuQuaternion.Y * bepuQuaternion.Y + bepuQuaternion.Z * bepuQuaternion.Z)
|
||||||
|
);
|
||||||
|
|
||||||
|
var bepuAngleDegrees = simBase.RadianesAGrados(bepuAngleRadians);
|
||||||
|
return BepuAngleToWpfAngle(bepuAngleDegrees);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza la posición de un body BEPU manteniendo su rotación actual - USO INTERNO
|
||||||
|
/// </summary>
|
||||||
|
internal static void UpdateBepuBodyPosition(Simulation simulation, BodyHandle bodyHandle, Vector3 newBepuPosition)
|
||||||
|
{
|
||||||
|
if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
|
||||||
|
{
|
||||||
|
var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
|
||||||
|
bodyReference.Pose.Position = newBepuPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza la rotación de un body BEPU manteniendo su posición actual - USO INTERNO
|
||||||
|
/// </summary>
|
||||||
|
internal static void UpdateBepuBodyRotation(Simulation simulation, BodyHandle bodyHandle, float wpfAngle)
|
||||||
|
{
|
||||||
|
if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
|
||||||
|
{
|
||||||
|
var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
|
||||||
|
bodyReference.Pose.Orientation = CreateBepuQuaternionFromWpfAngle(wpfAngle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza posición y rotación de un body BEPU simultáneamente - USO INTERNO
|
||||||
|
/// </summary>
|
||||||
|
internal static void UpdateBepuBodyPose(Simulation simulation, BodyHandle bodyHandle, Vector3 newBepuPosition, float wpfAngle)
|
||||||
|
{
|
||||||
|
if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
|
||||||
|
{
|
||||||
|
var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
|
||||||
|
bodyReference.Pose.Position = newBepuPosition;
|
||||||
|
bodyReference.Pose.Orientation = CreateBepuQuaternionFromWpfAngle(wpfAngle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtiene la posición del centro en coordenadas BEPU - USO INTERNO
|
||||||
|
/// </summary>
|
||||||
|
internal static Vector3 GetBepuBodyPosition(Simulation simulation, BodyHandle bodyHandle)
|
||||||
|
{
|
||||||
|
if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
|
||||||
|
{
|
||||||
|
var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
|
||||||
|
return bodyReference.Pose.Position;
|
||||||
|
}
|
||||||
|
return Vector3.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtiene el ángulo WPF desde un body BEPU - RETORNA VALOR WPF
|
||||||
|
/// </summary>
|
||||||
|
public static float GetWpfAngleFromBepuBody(Simulation simulation, BodyHandle bodyHandle)
|
||||||
|
{
|
||||||
|
if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
|
||||||
|
{
|
||||||
|
var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
|
||||||
|
return ExtractWpfAngleFromBepuQuaternion(bodyReference.Pose.Orientation);
|
||||||
|
}
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Convierte directamente de grados WPF a radianes BEPU - USO INTERNO
|
||||||
|
/// Maneja tanto la inversión de signo como la conversión a radianes en una sola operación
|
||||||
|
/// </summary>
|
||||||
|
internal static float WpfDegreesToBepuRadians(float wpfDegrees)
|
||||||
|
{
|
||||||
|
return simBase.GradosARadianes(WpfAngleToBepuAngle(wpfDegrees));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Convierte directamente de radianes BEPU a grados WPF - RETORNA VALOR WPF
|
||||||
|
/// Maneja tanto la conversión a grados como la inversión de signo en una sola operación
|
||||||
|
/// </summary>
|
||||||
|
public static float BepuRadiansToWpfDegrees(float bepuRadians)
|
||||||
|
{
|
||||||
|
return BepuAngleToWpfAngle(simBase.RadianesAGrados(bepuRadians));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,344 @@
|
||||||
|
using BepuPhysics;
|
||||||
|
using BepuPhysics.Collidables;
|
||||||
|
using BepuPhysics.Trees;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CtrEditor.Simulacion
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ CORREGIDO: RayHitHandler personalizado para simBarrera
|
||||||
|
/// Calcula la distancia mínima del rayo al centro de cada botella
|
||||||
|
/// </summary>
|
||||||
|
public struct BarreraRayHitHandler : IRayHitHandler
|
||||||
|
{
|
||||||
|
private simBarrera _barrera;
|
||||||
|
private List<simBotella> _detectedBottles;
|
||||||
|
private SimulationManagerBEPU _simulationManager;
|
||||||
|
private float _minDistance;
|
||||||
|
private bool _neckDetected;
|
||||||
|
private bool _fullDetected;
|
||||||
|
private Vector3 _rayOrigin;
|
||||||
|
private Vector3 _rayDirection;
|
||||||
|
|
||||||
|
internal BarreraRayHitHandler(simBarrera barrera, SimulationManagerBEPU simulationManager, Vector3 rayOrigin, Vector3 rayDirection)
|
||||||
|
{
|
||||||
|
_barrera = barrera;
|
||||||
|
_simulationManager = simulationManager;
|
||||||
|
_detectedBottles = new List<simBotella>();
|
||||||
|
_minDistance = float.MaxValue;
|
||||||
|
_neckDetected = false;
|
||||||
|
_fullDetected = false;
|
||||||
|
_rayOrigin = rayOrigin;
|
||||||
|
_rayDirection = rayDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowTest(CollidableReference collidable)
|
||||||
|
{
|
||||||
|
// Solo testear botellas dinámicas
|
||||||
|
if (collidable.Mobility == CollidableMobility.Dynamic)
|
||||||
|
{
|
||||||
|
var bottle = _simulationManager?.GetSimBaseFromBodyHandle(collidable.BodyHandle) as simBotella;
|
||||||
|
return bottle != null;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowTest(CollidableReference collidable, int childIndex)
|
||||||
|
{
|
||||||
|
// Para botellas (esferas), childIndex siempre es 0
|
||||||
|
return AllowTest(collidable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnRayHit(in RayData ray, ref float maximumT, float t, Vector3 normal, CollidableReference collidable, int childIndex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Obtener la botella que fue hit
|
||||||
|
var bottle = _simulationManager?.GetSimBaseFromBodyHandle(collidable.BodyHandle) as simBotella;
|
||||||
|
if (bottle == null) return;
|
||||||
|
|
||||||
|
// Obtener posición del centro de la botella
|
||||||
|
var bottleCenter = bottle.GetPosition();
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Calcular la distancia mínima del rayo (línea) al centro de la botella
|
||||||
|
var minDistanceToCenter = CalculateMinimumDistanceFromRayToPoint(_rayOrigin, _rayDirection, bottleCenter);
|
||||||
|
|
||||||
|
// Actualizar distancia mínima global
|
||||||
|
if (minDistanceToCenter < _minDistance)
|
||||||
|
{
|
||||||
|
_minDistance = minDistanceToCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Verificar detección completa usando distancia mínima del rayo al centro
|
||||||
|
if (minDistanceToCenter <= bottle.Radius)
|
||||||
|
{
|
||||||
|
_fullDetected = true;
|
||||||
|
if (!_detectedBottles.Contains(bottle))
|
||||||
|
{
|
||||||
|
_detectedBottles.Add(bottle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Verificar detección de cuello usando circunferencia imaginaria con radio/2
|
||||||
|
if (_barrera.DetectNeck && minDistanceToCenter <= (bottle.Radius / 2f))
|
||||||
|
{
|
||||||
|
_neckDetected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// System.Diagnostics.Debug.WriteLine($"[BarreraRayHitHandler] Hit: Botella={bottle.BodyHandle}, distanciaMinima={minDistanceToCenter:F3}, radio={bottle.Radius:F3}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[BarreraRayHitHandler] Error en OnRayHit: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Calcula la distancia mínima de un rayo (línea) a un punto
|
||||||
|
/// </summary>
|
||||||
|
private float CalculateMinimumDistanceFromRayToPoint(Vector3 rayOrigin, Vector3 rayDirection, Vector3 point)
|
||||||
|
{
|
||||||
|
// Vector desde el origen del rayo al punto
|
||||||
|
var rayToPoint = point - rayOrigin;
|
||||||
|
|
||||||
|
// Proyección del vector rayToPoint sobre la dirección del rayo
|
||||||
|
var projection = Vector3.Dot(rayToPoint, rayDirection);
|
||||||
|
|
||||||
|
// Punto más cercano en el rayo al punto dado
|
||||||
|
var closestPointOnRay = rayOrigin + rayDirection * projection;
|
||||||
|
|
||||||
|
// Distancia mínima del rayo al punto
|
||||||
|
return Vector3.Distance(closestPointOnRay, point);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ NUEVO: Método para obtener los resultados del raycast
|
||||||
|
public void GetResults(out bool luzCortada, out bool luzCortadaNeck, out float distancia, out List<simBotella> botellas)
|
||||||
|
{
|
||||||
|
luzCortada = _fullDetected;
|
||||||
|
luzCortadaNeck = _neckDetected;
|
||||||
|
distancia = _minDistance == float.MaxValue ? 0f : _minDistance;
|
||||||
|
botellas = new List<simBotella>(_detectedBottles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Estructura para datos de barrera que se exponen a WPF
|
||||||
|
/// </summary>
|
||||||
|
public struct BarreraData
|
||||||
|
{
|
||||||
|
public bool LuzCortada;
|
||||||
|
public bool LuzCortadaNeck;
|
||||||
|
|
||||||
|
public BarreraData(bool luzCortada, bool luzCortadaNeck)
|
||||||
|
{
|
||||||
|
LuzCortada = luzCortada;
|
||||||
|
LuzCortadaNeck = luzCortadaNeck;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class simBarrera : simBase
|
||||||
|
{
|
||||||
|
internal bool LuzCortada;
|
||||||
|
internal bool LuzCortadaNeck;
|
||||||
|
public List<simBotella> ListSimBotellaContact;
|
||||||
|
public float Width { get; set; }
|
||||||
|
public float Height { get; set; }
|
||||||
|
public bool DetectNeck { get; set; }
|
||||||
|
|
||||||
|
// ✅ NUEVO: Propiedades para raycast
|
||||||
|
private SimulationManagerBEPU _simulationManager;
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Almacenar ángulo internamente como radianes BEPU (como simCurve)
|
||||||
|
private float _angleRadians; // ✅ SIEMPRE en radianes BEPU (ya convertido desde WPF)
|
||||||
|
|
||||||
|
internal simBarrera(Simulation simulation, float width, float height, Vector3 bepuPosition, float bepuRadians = 0, bool detectectNeck = false, SimulationManagerBEPU simulationManager = null)
|
||||||
|
{
|
||||||
|
_simulation = simulation;
|
||||||
|
_simulationManager = simulationManager;
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
DetectNeck = detectectNeck;
|
||||||
|
ListSimBotellaContact = new List<simBotella>();
|
||||||
|
|
||||||
|
// ✅ CREAR POSICIÓN SIN BODY FÍSICO usando parámetros BEPU internos
|
||||||
|
CreatePosition(width, height, bepuPosition, bepuRadians);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Update(float width, float height, Vector3 bepuPosition, float bepuRadians = 0)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
ListSimBotellaContact = new List<simBotella>();
|
||||||
|
|
||||||
|
// ✅ CREAR POSICIÓN SIN BODY FÍSICO usando parámetros BEPU internos
|
||||||
|
CreatePosition(width, height, bepuPosition, bepuRadians);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ✅ NUEVO: Variables para almacenar pose sin crear body físico
|
||||||
|
private Vector3 _position;
|
||||||
|
private Quaternion _orientation;
|
||||||
|
private bool _poseInitialized = false;
|
||||||
|
|
||||||
|
// ✅ NUEVO: Propiedad pública para acceder a la posición
|
||||||
|
public Vector3 Position
|
||||||
|
{
|
||||||
|
get => _poseInitialized ? _position : Vector3.Zero;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_position = value;
|
||||||
|
_poseInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ NUEVO: Propiedad pública para acceder al ángulo en radianes BEPU
|
||||||
|
public float AngleRadians
|
||||||
|
{
|
||||||
|
get => _poseInitialized ? _angleRadians : 0f;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_angleRadians = value;
|
||||||
|
UpdateOrientationFromInternalAngle();
|
||||||
|
_poseInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ NUEVO: Método interno para actualizar orientación desde ángulo interno
|
||||||
|
private void UpdateOrientationFromInternalAngle()
|
||||||
|
{
|
||||||
|
// ✅ CREAR QUATERNION DESDE RADIANES BEPU INTERNOS
|
||||||
|
_orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, _angleRadians);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ INTERNO: Actualiza posición y rotación desde parámetros BEPU internos
|
||||||
|
/// </summary>
|
||||||
|
internal void Update(Vector3 bepuPosition, float bepuRadians)
|
||||||
|
{
|
||||||
|
Position = bepuPosition;
|
||||||
|
AngleRadians = bepuRadians;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetDimensions(float width, float height)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ INTERNO: Crea la posición usando parámetros BEPU internos
|
||||||
|
/// </summary>
|
||||||
|
internal void CreatePosition(float width, float height, Vector3 bepuPosition, float bepuRadians)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
|
||||||
|
// ✅ ALMACENAR DIRECTAMENTE parámetros BEPU internos
|
||||||
|
Position = bepuPosition;
|
||||||
|
AngleRadians = bepuRadians;
|
||||||
|
|
||||||
|
// ✅ NO CREAR BODY FÍSICO - solo almacenar datos
|
||||||
|
_bodyCreated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ CORREGIDO: Realiza raycast para detectar botellas usando coordenadas BEPU internas
|
||||||
|
/// </summary>
|
||||||
|
public void PerformRaycast()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Resetear flags
|
||||||
|
LuzCortada = false;
|
||||||
|
LuzCortadaNeck = false;
|
||||||
|
ListSimBotellaContact?.Clear();
|
||||||
|
|
||||||
|
// Validar que tenemos simulación y manager
|
||||||
|
if (_simulation == null || _simulationManager == null || !_poseInitialized)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("[PerformRaycast] Simulación, manager o pose no inicializados");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Usar coordenadas BEPU internas para cálculos
|
||||||
|
var bepuPosition = Position;
|
||||||
|
var Orientation = _orientation;
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Crear puntos del ray a la altura correcta
|
||||||
|
var halfWidth = Width / 2f;
|
||||||
|
var rayStartLocal = new Vector3(-halfWidth, 0, 0);
|
||||||
|
var rayEndLocal = new Vector3(halfWidth, 0, 0);
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Ajustar la posición de la barrera para que el ray esté a la altura correcta
|
||||||
|
var barreraPosition = new Vector3(bepuPosition.X, bepuPosition.Y, bepuPosition.Z);
|
||||||
|
|
||||||
|
// ✅ TRANSFORMAR PUNTOS LOCALES A COORDENADAS MUNDIALES A LA ALTURA CORRECTA
|
||||||
|
var worldStart = barreraPosition + Vector3.Transform(rayStartLocal, Orientation);
|
||||||
|
var worldEnd = barreraPosition + Vector3.Transform(rayEndLocal, Orientation);
|
||||||
|
|
||||||
|
// ✅ CALCULAR DIRECCIÓN Y DISTANCIA DEL RAY
|
||||||
|
var rayDirection = worldEnd - worldStart;
|
||||||
|
var rayDistance = rayDirection.Length();
|
||||||
|
|
||||||
|
if (rayDistance < 0.001f)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("[PerformRaycast] Ray demasiado corto");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rayDirection = Vector3.Normalize(rayDirection);
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: CREAR HANDLER PARA PROCESAR HITS con parámetros correctos
|
||||||
|
var rayHandler = new BarreraRayHitHandler(this, _simulationManager, worldStart, rayDirection);
|
||||||
|
|
||||||
|
// ✅ REALIZAR RAYCAST
|
||||||
|
_simulation.RayCast(worldStart, rayDirection, rayDistance, ref rayHandler);
|
||||||
|
|
||||||
|
// ✅ OBTENER RESULTADOS
|
||||||
|
rayHandler.GetResults(out bool luzCortada, out bool luzCortadaNeck, out float distancia, out List<simBotella> botellas);
|
||||||
|
|
||||||
|
// ✅ ASIGNAR RESULTADOS
|
||||||
|
LuzCortada = luzCortada;
|
||||||
|
LuzCortadaNeck = luzCortadaNeck;
|
||||||
|
|
||||||
|
if (ListSimBotellaContact != null)
|
||||||
|
{
|
||||||
|
ListSimBotellaContact.Clear();
|
||||||
|
ListSimBotellaContact.AddRange(botellas);
|
||||||
|
}
|
||||||
|
|
||||||
|
//System.Diagnostics.Debug.WriteLine($"[PerformRaycast] {worldStart} - {worldEnd} - {rayDistance} - {rayDirection} ");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[PerformRaycast] Error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ INTERNO: Métodos Create que trabajan solo con parámetros BEPU internos
|
||||||
|
/// </summary>
|
||||||
|
internal void Create(float width, float height, Vector3 bepuPosition, float bepuRadians = 0, bool detectectNeck = false)
|
||||||
|
{
|
||||||
|
CreatePosition(width, height, bepuPosition, bepuRadians);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ OVERRIDE: RemoverBody no hace nada porque no hay body físico
|
||||||
|
/// </summary>
|
||||||
|
public new void RemoverBody()
|
||||||
|
{
|
||||||
|
// ✅ NO HAY BODY FÍSICO QUE REMOVER
|
||||||
|
_bodyCreated = false;
|
||||||
|
_poseInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
using BepuPhysics.Collidables;
|
||||||
|
using BepuPhysics;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CtrEditor.Simulacion
|
||||||
|
{
|
||||||
|
public class simBase
|
||||||
|
{
|
||||||
|
public object WpfObject { get; set; }
|
||||||
|
public string SimObjectType { get; set; }
|
||||||
|
public BodyHandle BodyHandle { get; protected set; }
|
||||||
|
public Simulation _simulation;
|
||||||
|
protected bool _bodyCreated = false; // Bandera para saber si hemos creado un cuerpo
|
||||||
|
protected SimulationManagerBEPU _simulationManager; // ✅ NUEVO: Referencia al manager
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Restaurar factor de conversión correcto
|
||||||
|
static public float SpeedConversionFactor
|
||||||
|
{
|
||||||
|
get => 60f; // Factor de conversión de velocidad interna a m/s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constantes para las posiciones Z de los objetos 3D
|
||||||
|
public const float zPos_Transporte = 0f; // Z de la parte baja
|
||||||
|
public const float zAltura_Transporte = 0.1f; // Altura del transporte sobre zPos
|
||||||
|
|
||||||
|
public const float zPos_Guia = 0.05f; // Z de la parte baja
|
||||||
|
public const float zAltura_Guia = 0.20f; // Altura de la guía sobre zPos
|
||||||
|
public const float zPos_Barrera = zPos_Transporte + zAltura_Transporte + 0.05f; // Z de la parte baja - 0.1 Altura Botella
|
||||||
|
public const float zPos_Descarte = 0.1f; // Z de la parte baja
|
||||||
|
|
||||||
|
// Constantes para configuración
|
||||||
|
public const float zPos_Curve = zPos_Transporte+ zAltura_Transporte; // Z de la parte alta de la curva
|
||||||
|
|
||||||
|
|
||||||
|
public void RemoverBody()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Solo intentar remover si realmente hemos creado un cuerpo antes
|
||||||
|
if (_bodyCreated && _simulation != null && _simulation.Bodies != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
_simulation.Bodies.Remove(BodyHandle);
|
||||||
|
_bodyCreated = false; // Marcar como no creado después de remover
|
||||||
|
//System.Diagnostics.Debug.WriteLine($"[simBase.RemoverBody] ✅ Body eliminado: {BodyHandle}");
|
||||||
|
}
|
||||||
|
//else
|
||||||
|
//{
|
||||||
|
// System.Diagnostics.Debug.WriteLine($"[simBase.RemoverBody] ⚠️ Body no existe o no creado: {BodyHandle}");
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[simBase.RemoverBody] ❌ ERROR: {ex.Message}");
|
||||||
|
_bodyCreated = false; // Marcar como no creado en caso de error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Cambia la forma de un body existente, limpiando la forma anterior para evitar memory leaks
|
||||||
|
/// </summary>
|
||||||
|
protected void ChangeBodyShape(TypedIndex newShapeIndex)
|
||||||
|
{
|
||||||
|
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||||
|
|
||||||
|
// ✅ CRÍTICO: Obtener la forma anterior para limpiarla del pool de shapes
|
||||||
|
var oldShapeIndex = bodyReference.Collidable.Shape;
|
||||||
|
|
||||||
|
// Cambiar a la nueva forma
|
||||||
|
_simulation.Bodies.SetShape(BodyHandle, newShapeIndex);
|
||||||
|
|
||||||
|
// ✅ CRÍTICO: Limpiar la forma anterior del pool para evitar memory leaks
|
||||||
|
// Nota: Solo limpiar si es diferente (para evitar limpiar la forma que acabamos de asignar)
|
||||||
|
if (oldShapeIndex.Packed != newShapeIndex.Packed)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_simulation.Shapes.RemoveAndDispose(oldShapeIndex, _simulation.BufferPool);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[simBase] Warning: Could not dispose old shape: {ex.Message}");
|
||||||
|
// Continuar - esto no es crítico para la funcionalidad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float Min(float Value, float Min = 0.01f)
|
||||||
|
{
|
||||||
|
return Math.Max(Value, Min);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float GradosARadianes(float grados)
|
||||||
|
{
|
||||||
|
return grados * (float)Math.PI / 180f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float RadianesAGrados(float radianes)
|
||||||
|
{
|
||||||
|
return radianes * 180f / (float)Math.PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPosition(float x, float y, float z = 0)
|
||||||
|
{
|
||||||
|
CoordinateConverter.UpdateBepuBodyPosition(_simulation, BodyHandle, new Vector3(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPosition(Vector2 wpfPosition)
|
||||||
|
{
|
||||||
|
// Mantener la coordenada Z actual para preservar la altura del objeto
|
||||||
|
var currentBepuPosition = CoordinateConverter.GetBepuBodyPosition(_simulation, BodyHandle);
|
||||||
|
var newBepuPosition = new Vector3(wpfPosition.X, CoordinateConverter.WpfYToBepuY(wpfPosition.Y), currentBepuPosition.Z);
|
||||||
|
CoordinateConverter.UpdateBepuBodyPosition(_simulation, BodyHandle, newBepuPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPosition(Vector3 bepuPosition)
|
||||||
|
{
|
||||||
|
CoordinateConverter.UpdateBepuBodyPosition(_simulation, BodyHandle, bepuPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetPosition()
|
||||||
|
{
|
||||||
|
return CoordinateConverter.GetBepuBodyPosition(_simulation, BodyHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetRotation(float wpfAngle)
|
||||||
|
{
|
||||||
|
CoordinateConverter.UpdateBepuBodyRotation(_simulation, BodyHandle, wpfAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetRotationZ()
|
||||||
|
{
|
||||||
|
return CoordinateConverter.GetWpfAngleFromBepuBody(_simulation, BodyHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,274 @@
|
||||||
|
using BepuPhysics.Collidables;
|
||||||
|
using BepuPhysics.Constraints;
|
||||||
|
using BepuPhysics;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DocumentFormat.OpenXml.Spreadsheet;
|
||||||
|
using System.Windows.Media.Media3D;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace CtrEditor.Simulacion
|
||||||
|
{
|
||||||
|
|
||||||
|
public class simBotella : simBase
|
||||||
|
{
|
||||||
|
public float Radius;
|
||||||
|
public float Height; // Altura para la visualización del cilindro en Helix
|
||||||
|
private float _mass;
|
||||||
|
public bool Descartar = false;
|
||||||
|
public int isOnTransports;
|
||||||
|
public List<simBase> ListOnTransports;
|
||||||
|
public bool isRestricted;
|
||||||
|
public bool isNoMoreRestricted;
|
||||||
|
|
||||||
|
public simTransporte ConveyorRestrictedTo;
|
||||||
|
public float OverlapPercentage;
|
||||||
|
public float _neckRadius;
|
||||||
|
public bool isOnBrakeTransport; // Nueva propiedad para marcar si está en un transporte con freno
|
||||||
|
public simTransporte CurrentBrakeTransport { get; set; } // ✅ NUEVA: Referencia al transporte de freno actual
|
||||||
|
|
||||||
|
// ✅ NUEVO: Propiedades para control de flotación/presión
|
||||||
|
public float? LastTransportCollisionZ { get; set; }
|
||||||
|
public float PressureBuildup { get; set; } = 0;
|
||||||
|
public bool IsTouchingTransport { get; set; } = false;
|
||||||
|
|
||||||
|
// ✅ NUEVO: Propiedades para la física personalizada
|
||||||
|
public float BaseInverseMass { get; private set; }
|
||||||
|
public Vector3 InitialPosition3D { get; private set; }
|
||||||
|
|
||||||
|
private List<Action> _deferredActions;
|
||||||
|
|
||||||
|
public simBotella(Simulation simulation, List<Action> deferredActions, float diameter, Vector2 position, float mass, float neckRadius = 0)
|
||||||
|
{
|
||||||
|
_simulation = simulation;
|
||||||
|
_deferredActions = deferredActions;
|
||||||
|
Radius = diameter / 2f;
|
||||||
|
Height = diameter; // Altura igual al diámetro para mantener proporciones similares
|
||||||
|
_mass = mass;
|
||||||
|
_neckRadius = neckRadius;
|
||||||
|
ListOnTransports = new List<simBase>();
|
||||||
|
|
||||||
|
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||||
|
var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), Radius + zPos_Transporte + zAltura_Transporte);
|
||||||
|
Create(position3D);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float CenterX
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return GetPosition().X;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
var pos = GetPosition();
|
||||||
|
SetPosition(value, pos.Y, pos.Z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float CenterY
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||||
|
return CoordinateConverter.BepuYToWpfY(GetPosition().Y);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
var pos = GetPosition();
|
||||||
|
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||||
|
SetPosition(pos.X, CoordinateConverter.WpfYToBepuY(value), pos.Z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 Center
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var pos3D = GetPosition();
|
||||||
|
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||||
|
return CoordinateConverter.BepuVector3ToWpfVector2(pos3D);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
// Mantener la Z actual, solo cambiar X, Y
|
||||||
|
var currentPos = GetPosition();
|
||||||
|
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||||
|
SetPosition(value.X, CoordinateConverter.WpfYToBepuY(value.Y), currentPos.Z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Mass
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||||
|
return 1f / bodyReference.LocalInertia.InverseMass;
|
||||||
|
}
|
||||||
|
return _mass;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_mass = value;
|
||||||
|
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
var cylinder = new Cylinder(Radius, Height);
|
||||||
|
var inertia = cylinder.ComputeInertia(_mass);
|
||||||
|
|
||||||
|
// Modificamos el tensor de inercia para evitar que la botella vuelque.
|
||||||
|
var inverseInertia = inertia.InverseInertiaTensor;
|
||||||
|
inverseInertia.XX = 0f; // Resistencia infinita a la rotación en el eje X (vuelco).
|
||||||
|
inverseInertia.YY = 0f; // Resistencia infinita a la rotación en el eje Y (vuelco).
|
||||||
|
// La resistencia en ZZ (eje Z) se mantiene, permitiendo que la botella gire sobre su base.
|
||||||
|
inertia.InverseInertiaTensor = inverseInertia;
|
||||||
|
|
||||||
|
_simulation.Bodies.SetLocalInertia(BodyHandle, inertia);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Create(Vector3 position)
|
||||||
|
{
|
||||||
|
RemoverBody();
|
||||||
|
|
||||||
|
var cylinder = new Cylinder(Radius, Height);
|
||||||
|
var shapeIndex = _simulation.Shapes.Add(cylinder);
|
||||||
|
|
||||||
|
// 1. Creamos el cuerpo con una inercia por defecto.
|
||||||
|
var defaultInertia = cylinder.ComputeInertia(_mass);
|
||||||
|
var activityDescription = new BodyActivityDescription(-1f);
|
||||||
|
var bodyDescription = BodyDescription.CreateDynamic(
|
||||||
|
new RigidPose(position),
|
||||||
|
new BodyVelocity(),
|
||||||
|
defaultInertia,
|
||||||
|
new CollidableDescription(shapeIndex, 0),
|
||||||
|
activityDescription
|
||||||
|
);
|
||||||
|
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||||
|
_bodyCreated = true;
|
||||||
|
|
||||||
|
// 2. Inmediatamente después de crearlo, aplicamos la inercia personalizada.
|
||||||
|
// Esto asegura que SetLocalInertia se llame para todos los objetos nuevos.
|
||||||
|
var inertia = cylinder.ComputeInertia(_mass);
|
||||||
|
var inverseInertia = inertia.InverseInertiaTensor;
|
||||||
|
inverseInertia.XX = 0f;
|
||||||
|
inverseInertia.YY = 0f;
|
||||||
|
inertia.InverseInertiaTensor = inverseInertia;
|
||||||
|
_simulation.Bodies.SetLocalInertia(BodyHandle, inertia);
|
||||||
|
|
||||||
|
// ✅ NUEVO: Almacenar la masa inversa base después de crear el cuerpo
|
||||||
|
// Esto nos sirve como referencia para la lógica de masa dinámica.
|
||||||
|
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||||
|
this.BaseInverseMass = bodyReference.LocalInertia.InverseMass;
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Usar el diccionario del SimulationManager y la clave .Value del handle.
|
||||||
|
if (_simulationManager != null)
|
||||||
|
_simulationManager.CollidableData[BodyHandle.Value] = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetDiameter(float diameter)
|
||||||
|
{
|
||||||
|
Radius = diameter / 2f;
|
||||||
|
Height = diameter; // Mantener altura igual al diámetro para proporciones consistentes
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
|
||||||
|
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
var cylinder = new Cylinder(Radius, Height);
|
||||||
|
var shapeIndex = _simulation.Shapes.Add(cylinder);
|
||||||
|
ChangeBodyShape(shapeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetHeight(float height)
|
||||||
|
{
|
||||||
|
Height = height;
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
|
||||||
|
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
var cylinder = new Cylinder(Radius, Height);
|
||||||
|
var shapeIndex = _simulation.Shapes.Add(cylinder);
|
||||||
|
ChangeBodyShape(shapeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMass(float mass)
|
||||||
|
{
|
||||||
|
Mass = mass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Limita la rotación de la botella solo al plano XY (siempre "de pie")
|
||||||
|
/// Esto simplifica enormemente la simulación y es más realista para botellas
|
||||||
|
/// </summary>
|
||||||
|
public void LimitRotationToXYPlane()
|
||||||
|
{
|
||||||
|
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||||
|
|
||||||
|
// Extraer solo la rotación en Z (plano XY) y eliminar las rotaciones en X e Y
|
||||||
|
var currentOrientation = bodyReference.Pose.Orientation;
|
||||||
|
|
||||||
|
// Convertir a ángulo en Z solamente
|
||||||
|
var rotationZ = GetRotationZ();
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Calificar explícitamente para evitar ambigüedad de tipos.
|
||||||
|
var correctedOrientation = System.Numerics.Quaternion.CreateFromAxisAngle(Vector3.UnitZ, rotationZ);
|
||||||
|
|
||||||
|
// Aplicar la orientación corregida
|
||||||
|
bodyReference.Pose.Orientation = correctedOrientation;
|
||||||
|
|
||||||
|
// También limitar la velocidad angular a solo rotación en Z
|
||||||
|
var angularVelocity = bodyReference.Velocity.Angular;
|
||||||
|
bodyReference.Velocity.Angular = new Vector3(0, 0, angularVelocity.Z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyLinearVelocity(Vector3 velocity)
|
||||||
|
{
|
||||||
|
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||||
|
bodyReference.Velocity.Linear = velocity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetLinearVelocity()
|
||||||
|
{
|
||||||
|
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||||
|
return bodyReference.Velocity.Linear;
|
||||||
|
}
|
||||||
|
return Vector3.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CenterFixtureOnConveyor()
|
||||||
|
{
|
||||||
|
// Implementar lógica de centrado si es necesario
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsOnAnyTransport()
|
||||||
|
{
|
||||||
|
return isOnTransports > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ SIMPLIFICADO: RemoverBody que confía en BEPU para limpiar constraints
|
||||||
|
/// </summary>
|
||||||
|
public new void RemoverBody()
|
||||||
|
{
|
||||||
|
base.RemoverBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,348 @@
|
||||||
|
using BepuPhysics.Collidables;
|
||||||
|
using BepuPhysics;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CtrEditor.Simulacion
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Representa una curva o arco en la simulación física.
|
||||||
|
/// ✅ IMPORTANTE: Los ángulos _startAngle y _endAngle se almacenan INTERNAMENTE en radianes BEPU
|
||||||
|
/// (ya convertidos desde grados WPF usando CoordinateConverter.WpfDegreesToBepuRadians)
|
||||||
|
/// Las propiedades públicas StartAngle/EndAngle devuelven grados WPF usando CoordinateConverter.BepuRadiansToWpfDegrees
|
||||||
|
/// ✅ NUEVO: Cada triángulo actúa como un mini-transporte con su propia dirección tangencial fija
|
||||||
|
/// </summary>
|
||||||
|
public class simCurve : simBase
|
||||||
|
{
|
||||||
|
private float _innerRadius;
|
||||||
|
private float _outerRadius;
|
||||||
|
private float _startAngle; // ✅ SIEMPRE en radianes BEPU
|
||||||
|
private float _endAngle; // ✅ SIEMPRE en radianes BEPU
|
||||||
|
public float Speed { get; set; } // Velocidad para efectos de cinta transportadora (m/s)
|
||||||
|
|
||||||
|
private List<Action> _deferredActions;
|
||||||
|
|
||||||
|
// ✅ NUEVO: Almacenar el centro real de la curva
|
||||||
|
private Vector3 _curveCenter;
|
||||||
|
|
||||||
|
// ✅ SIMPLIFICADO: Propiedades esenciales únicamente
|
||||||
|
public List<simBotella> BottlesOnCurve { get; private set; } = new List<simBotella>();
|
||||||
|
|
||||||
|
// ✅ NUEVO: Almacenar triángulos creados para acceso directo
|
||||||
|
private Triangle[] _storedTriangles;
|
||||||
|
|
||||||
|
public float InnerRadius => _innerRadius;
|
||||||
|
public float OuterRadius => _outerRadius;
|
||||||
|
public float StartAngle => CoordinateConverter.BepuRadiansToWpfDegrees(_startAngle); // Convertir de radianes BEPU internos a grados WPF
|
||||||
|
public float EndAngle => CoordinateConverter.BepuRadiansToWpfDegrees(_endAngle); // Convertir de radianes BEPU internos a grados WPF
|
||||||
|
|
||||||
|
// ✅ NUEVO: Propiedad para acceder al centro real de la curva
|
||||||
|
public Vector3 CurveCenter => _curveCenter;
|
||||||
|
|
||||||
|
public simCurve(Simulation simulation, List<Action> deferredActions, float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 topLeft, float unused = 0, SimulationManagerBEPU simulationManager = null)
|
||||||
|
{
|
||||||
|
_simulation = simulation;
|
||||||
|
_deferredActions = deferredActions;
|
||||||
|
_simulationManager = simulationManager; // ✅ NUEVO: Almacenar referencia
|
||||||
|
_innerRadius = innerRadius;
|
||||||
|
_outerRadius = outerRadius;
|
||||||
|
// ✅ SIMPLIFICADO: Usar conversión directa WPF grados → BEPU radianes
|
||||||
|
_startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
|
||||||
|
_endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
|
||||||
|
|
||||||
|
// ✅ NUEVO: Calcular y almacenar el centro real de la curva
|
||||||
|
var curveSize = outerRadius * 2f;
|
||||||
|
var zPosition = zPos_Curve;
|
||||||
|
_curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(topLeft, curveSize, curveSize, 0f, zPosition);
|
||||||
|
|
||||||
|
// ✅ SIMPLIFICADO: Crear la curva directamente
|
||||||
|
Create(innerRadius, outerRadius, startAngle, endAngle, topLeft, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ MODIFICADO: Actualizar la velocidad y luego la del cuerpo cinemático
|
||||||
|
public void SetSpeed(float speed)
|
||||||
|
{
|
||||||
|
Speed = speed;
|
||||||
|
ActualizarVelocidadCinematica();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Aplica la velocidad angular al cuerpo cinemático de BEPU.
|
||||||
|
/// </summary>
|
||||||
|
public void ActualizarVelocidadCinematica()
|
||||||
|
{
|
||||||
|
if (_simulation != null && this.BodyHandle.Value >= 0 && _simulation.Bodies.BodyExists(this.BodyHandle))
|
||||||
|
{
|
||||||
|
var body = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||||
|
|
||||||
|
float effectiveRadius = (_innerRadius + _outerRadius) / 2.0f;
|
||||||
|
if (effectiveRadius > 0.001f)
|
||||||
|
{
|
||||||
|
// La velocidad tangencial (Speed) se convierte a velocidad angular (ω = v / r)
|
||||||
|
// ✅ CORREGIDO: Convertir velocidad de m/min a m/s para la simulación
|
||||||
|
float angularSpeed = (Speed / SpeedConversionFactor) / effectiveRadius;
|
||||||
|
|
||||||
|
// La rotación es alrededor del eje Z en el sistema de coordenadas de BEPU
|
||||||
|
body.Velocity.Angular = new Vector3(0, 0, angularSpeed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
body.Velocity.Angular = Vector3.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Detiene completamente la curva
|
||||||
|
/// </summary>
|
||||||
|
public void StopCurve()
|
||||||
|
{
|
||||||
|
SetSpeed(0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Invierte la dirección de la curva manteniendo la misma velocidad
|
||||||
|
/// </summary>
|
||||||
|
public void ReverseCurve()
|
||||||
|
{
|
||||||
|
SetSpeed(-Speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Actualiza tanto posición como rotación desde parámetros WPF
|
||||||
|
/// </summary>
|
||||||
|
internal void UpdateFromWpfParameters(Vector2 wpfTopLeft, float wpfAngle, float innerRadius, float outerRadius, float startAngle, float endAngle)
|
||||||
|
{
|
||||||
|
// Actualizar parámetros de la curva
|
||||||
|
_innerRadius = innerRadius;
|
||||||
|
_outerRadius = outerRadius;
|
||||||
|
// ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes
|
||||||
|
_startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
|
||||||
|
_endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
|
||||||
|
|
||||||
|
// ✅ NUEVO: Actualizar el centro real de la curva
|
||||||
|
var curveSize = outerRadius * 2f;
|
||||||
|
var zPosition = zPos_Curve;
|
||||||
|
_curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition);
|
||||||
|
|
||||||
|
Create(_curveCenter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Actualiza posición desde Top-Left WPF manteniendo parámetros actuales
|
||||||
|
/// </summary>
|
||||||
|
internal void SetPositionFromWpfTopLeft(Vector2 wpfTopLeft)
|
||||||
|
{
|
||||||
|
var curveSize = _outerRadius * 2f;
|
||||||
|
var zPosition = GetPosition().Z; // Mantener Z actual
|
||||||
|
_curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition);
|
||||||
|
|
||||||
|
Create(_curveCenter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Obtiene Top-Left WPF desde la posición actual
|
||||||
|
/// </summary>
|
||||||
|
internal Vector2 GetWpfTopLeft()
|
||||||
|
{
|
||||||
|
var curveSize = _outerRadius * 2f;
|
||||||
|
return CoordinateConverter.CalculateWpfTopLeftFromBepuCenter(_curveCenter, curveSize, curveSize, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ CORREGIDO: Obtiene los triángulos reales creados para la curva
|
||||||
|
/// Devuelve los triángulos en coordenadas locales para evitar transformación duplicada
|
||||||
|
/// </summary>
|
||||||
|
public Triangle[] GetRealBEPUTriangles()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_storedTriangles == null || _storedTriangles.Length == 0)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] No hay triángulos almacenados");
|
||||||
|
return new Triangle[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Devolver triángulos en coordenadas locales
|
||||||
|
// La visualización 3D aplicará la transformación una sola vez
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] Devolviendo {_storedTriangles.Length} triángulos en coordenadas locales");
|
||||||
|
return _storedTriangles;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] Error: {ex.Message}");
|
||||||
|
return new Triangle[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Obtiene los triángulos transformados a coordenadas mundiales (solo para debugging)
|
||||||
|
/// </summary>
|
||||||
|
public Triangle[] GetWorldBEPUTriangles()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_storedTriangles == null || _storedTriangles.Length == 0)
|
||||||
|
{
|
||||||
|
return new Triangle[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var worldTriangles = TransformTrianglesToWorldCoordinates(_storedTriangles);
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[GetWorldBEPUTriangles] Devolviendo {worldTriangles.Length} triángulos en coordenadas mundiales");
|
||||||
|
return worldTriangles;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[GetWorldBEPUTriangles] Error: {ex.Message}");
|
||||||
|
return new Triangle[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Transforma triángulos de coordenadas locales a mundiales
|
||||||
|
/// </summary>
|
||||||
|
private Triangle[] TransformTrianglesToWorldCoordinates(Triangle[] localTriangles)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_simulation == null || !_simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[TransformTriangles] Cuerpo no existe, devolviendo triángulos locales");
|
||||||
|
return localTriangles; // Fallback: devolver triángulos sin transformar
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = _simulation.Bodies[BodyHandle];
|
||||||
|
var bodyPosition = body.Pose.Position;
|
||||||
|
var bodyOrientation = body.Pose.Orientation;
|
||||||
|
|
||||||
|
var transformedTriangles = new Triangle[localTriangles.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < localTriangles.Length; i++)
|
||||||
|
{
|
||||||
|
var localTriangle = localTriangles[i];
|
||||||
|
|
||||||
|
// Transformar cada vértice del triángulo a coordenadas mundiales
|
||||||
|
var worldA = bodyPosition + Vector3.Transform(localTriangle.A, bodyOrientation);
|
||||||
|
var worldB = bodyPosition + Vector3.Transform(localTriangle.B, bodyOrientation);
|
||||||
|
var worldC = bodyPosition + Vector3.Transform(localTriangle.C, bodyOrientation);
|
||||||
|
|
||||||
|
transformedTriangles[i] = new Triangle(worldA, worldB, worldC);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[TransformTriangles] Transformados {transformedTriangles.Length} triángulos. Body pos: {bodyPosition}");
|
||||||
|
return transformedTriangles;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[TransformTriangles] Error: {ex.Message}, devolviendo triángulos locales");
|
||||||
|
return localTriangles; // Fallback en caso de error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void RemoverBody()
|
||||||
|
{
|
||||||
|
// ✅ NUEVO: Limpiar triángulos almacenados
|
||||||
|
_storedTriangles = null;
|
||||||
|
|
||||||
|
// ✅ SIMPLIFICADO: Solo remover el cuerpo principal (Mesh único)
|
||||||
|
base.RemoverBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Create(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 wpfTopLeft, float unused = 0)
|
||||||
|
{
|
||||||
|
// ✅ CORREGIDO: Como Aether - startAngle y endAngle definen directamente el sector
|
||||||
|
// No hay rotación separada del objeto
|
||||||
|
|
||||||
|
// Actualizar parámetros internos
|
||||||
|
_innerRadius = innerRadius;
|
||||||
|
_outerRadius = outerRadius;
|
||||||
|
// ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes
|
||||||
|
_startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
|
||||||
|
_endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
|
||||||
|
|
||||||
|
// ✅ NUEVO: Actualizar el centro real de la curva
|
||||||
|
var curveSize = outerRadius * 2f;
|
||||||
|
var zPosition = zPos_Curve;
|
||||||
|
_curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition);
|
||||||
|
|
||||||
|
Create(_curveCenter); // Llamar a la sobrecarga privada
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Create(Vector3 position)
|
||||||
|
{
|
||||||
|
RemoverBody();
|
||||||
|
|
||||||
|
var triangles = CreateSimpleArcTriangles(_innerRadius, _outerRadius, _startAngle, _endAngle);
|
||||||
|
if (triangles.Length == 0)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[simCurve] No se crearon triángulos, no se creará el cuerpo.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_storedTriangles = triangles;
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Convertir el array de triángulos a un Buffer<Triangle> de BEPU.
|
||||||
|
_simulation.BufferPool.Take(triangles.Length, out BepuUtilities.Memory.Buffer<Triangle> triangleBuffer);
|
||||||
|
for (int i = 0; i < triangles.Length; i++)
|
||||||
|
{
|
||||||
|
triangleBuffer[i] = triangles[i];
|
||||||
|
}
|
||||||
|
var mesh = new Mesh(triangleBuffer, Vector3.One, _simulation.BufferPool);
|
||||||
|
var shapeIndex = _simulation.Shapes.Add(mesh);
|
||||||
|
|
||||||
|
var bodyDescription = BodyDescription.CreateKinematic(
|
||||||
|
new RigidPose(position, Quaternion.Identity), // La rotación se maneja con la velocidad angular, no con la pose inicial
|
||||||
|
new CollidableDescription(shapeIndex, 0.1f),
|
||||||
|
new BodyActivityDescription(-1f) // ✅ CORREGIDO: -1f para que el cuerpo cinemático NUNCA duerma y no se mueva por su cuenta.
|
||||||
|
);
|
||||||
|
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||||
|
_bodyCreated = true;
|
||||||
|
|
||||||
|
// ✅ NUEVO: Establecer la velocidad cinemática inicial al crear el cuerpo.
|
||||||
|
ActualizarVelocidadCinematica();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ SIMPLIFICADO: Crea triángulos usando Triangle de BEPU directamente
|
||||||
|
/// Solo superficie superior, eliminando complejidad innecesaria
|
||||||
|
/// Los triángulos se crean en coordenadas locales (Z=0) y la posición del cuerpo los ubica correctamente
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="innerRadius">Radio interno del arco</param>
|
||||||
|
/// <param name="outerRadius">Radio externo del arco</param>
|
||||||
|
/// <param name="startAngle">Ángulo inicial en radianes BEPU</param>
|
||||||
|
/// <param name="endAngle">Ángulo final en radianes BEPU</param>
|
||||||
|
/// <returns>Array de triángulos nativos de BEPU en coordenadas locales</returns>
|
||||||
|
private Triangle[] CreateSimpleArcTriangles(float innerRadius, float outerRadius, float startAngle, float endAngle)
|
||||||
|
{
|
||||||
|
var triangles = new List<Triangle>();
|
||||||
|
|
||||||
|
// ✅ SIMPLIFICADO: Menos segmentos, menos complejidad
|
||||||
|
float arcLength = Math.Abs(endAngle - startAngle) * ((innerRadius + outerRadius) / 2f);
|
||||||
|
int segments = Math.Max(8, Math.Min((int)(arcLength * 8), 32)); // Menos segmentos
|
||||||
|
float angleStep = (endAngle - startAngle) / segments;
|
||||||
|
|
||||||
|
// ✅ SIMPLIFICADO: Sin inversión compleja de ángulos
|
||||||
|
for (int i = 0; i < segments; i++)
|
||||||
|
{
|
||||||
|
float angle1 = startAngle + i * angleStep;
|
||||||
|
float angle2 = startAngle + (i + 1) * angleStep;
|
||||||
|
|
||||||
|
// ✅ COORDENADAS LOCALES: Triángulos en Z=0, el cuerpo los posiciona correctamente
|
||||||
|
var inner1 = new Vector3(innerRadius * (float)Math.Cos(angle1), innerRadius * (float)Math.Sin(angle1), 0);
|
||||||
|
var outer1 = new Vector3(outerRadius * (float)Math.Cos(angle1), outerRadius * (float)Math.Sin(angle1), 0);
|
||||||
|
var inner2 = new Vector3(innerRadius * (float)Math.Cos(angle2), innerRadius * (float)Math.Sin(angle2), 0);
|
||||||
|
var outer2 = new Vector3(outerRadius * (float)Math.Cos(angle2), outerRadius * (float)Math.Sin(angle2), 0);
|
||||||
|
|
||||||
|
// ✅ USAR Triangle NATIVO DE BEPU (solo superficie superior)
|
||||||
|
triangles.Add(new Triangle(inner1, outer1, outer2));
|
||||||
|
triangles.Add(new Triangle(inner1, outer2, inner2));
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[CreateSimpleArcTriangles] Creados {triangles.Count} triángulos en coordenadas locales");
|
||||||
|
return triangles.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
using BepuPhysics.Collidables;
|
||||||
|
using BepuPhysics;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CtrEditor.Simulacion
|
||||||
|
{
|
||||||
|
|
||||||
|
public class simDescarte : simBase
|
||||||
|
{
|
||||||
|
private float _radius;
|
||||||
|
private List<Action> _deferredActions;
|
||||||
|
public List<simBotella> ListSimBotellaContact;
|
||||||
|
|
||||||
|
public simDescarte(Simulation simulation, List<Action> deferredActions, float diameter, Vector2 position)
|
||||||
|
{
|
||||||
|
_simulation = simulation;
|
||||||
|
_deferredActions = deferredActions;
|
||||||
|
_radius = diameter / 2f;
|
||||||
|
ListSimBotellaContact = new List<simBotella>();
|
||||||
|
|
||||||
|
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||||
|
var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), zPos_Descarte + _radius);
|
||||||
|
Create(position3D);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Radius
|
||||||
|
{
|
||||||
|
get { return _radius; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_radius = Math.Max(value, 0.01f); // Mínimo 1cm
|
||||||
|
// Recrear el cuerpo con el nuevo radio
|
||||||
|
var currentPos = GetPosition();
|
||||||
|
Create(currentPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetDiameter(float diameter)
|
||||||
|
{
|
||||||
|
Radius = diameter / 2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetDiameter()
|
||||||
|
{
|
||||||
|
return _radius * 2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Create(Vector2 position)
|
||||||
|
{
|
||||||
|
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||||
|
var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), zPos_Descarte + _radius);
|
||||||
|
Create(position3D);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Actualiza posición usando coordenadas WPF apropiadas
|
||||||
|
/// </summary>
|
||||||
|
internal void UpdateFromWpfCenter(Vector2 wpfCenter)
|
||||||
|
{
|
||||||
|
var position3D = new Vector3(wpfCenter.X, CoordinateConverter.WpfYToBepuY(wpfCenter.Y), zPos_Descarte + _radius);
|
||||||
|
|
||||||
|
// Actualizar solo posición manteniendo orientación
|
||||||
|
CoordinateConverter.UpdateBepuBodyPosition(_simulation, BodyHandle, position3D);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Create(Vector3 position)
|
||||||
|
{
|
||||||
|
RemoverBody();
|
||||||
|
|
||||||
|
// Crear esfera sensor para detección
|
||||||
|
var sphere = new BepuPhysics.Collidables.Sphere(_radius);
|
||||||
|
var shapeIndex = _simulation.Shapes.Add(sphere);
|
||||||
|
|
||||||
|
// Crear como SENSOR (Kinematic con speculative margin 0 para detección pura)
|
||||||
|
// Configurar actividad para NUNCA dormir - los sensores deben estar siempre activos
|
||||||
|
var activityDescription = new BodyActivityDescription(-1f); // -1 = nunca dormir
|
||||||
|
|
||||||
|
var bodyDescription = BodyDescription.CreateKinematic(
|
||||||
|
new RigidPose(position),
|
||||||
|
new CollidableDescription(shapeIndex, 0f), // Speculative margin 0 para sensores
|
||||||
|
activityDescription
|
||||||
|
);
|
||||||
|
|
||||||
|
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||||
|
_bodyCreated = true; // Marcar que hemos creado un cuerpo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
using BepuPhysics.Collidables;
|
||||||
|
using BepuPhysics;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CtrEditor.Simulacion
|
||||||
|
{
|
||||||
|
|
||||||
|
public class simGuia : simBase
|
||||||
|
{
|
||||||
|
private List<Action> _deferredActions;
|
||||||
|
|
||||||
|
// Propiedades para acceder a las dimensiones del objeto WPF
|
||||||
|
public float GuideThickness { get; set; } // Espesor de la guía
|
||||||
|
|
||||||
|
// ✅ NUEVO: Agregar propiedades Width y Height para almacenar dimensiones reales
|
||||||
|
public float Width { get; set; }
|
||||||
|
public float Height { get; set; }
|
||||||
|
|
||||||
|
public simGuia(Simulation simulation, List<Action> deferredActions, float width, float height, Vector2 topLeft, float angle)
|
||||||
|
{
|
||||||
|
_simulation = simulation;
|
||||||
|
_deferredActions = deferredActions;
|
||||||
|
|
||||||
|
// ✅ NUEVO: Almacenar dimensiones reales
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
GuideThickness = height; // Mantener compatibilidad
|
||||||
|
|
||||||
|
Create(width, height, topLeft, angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Create(float width, float height, Vector2 wpfTopLeft, float wpfAngle)
|
||||||
|
{
|
||||||
|
RemoverBody();
|
||||||
|
|
||||||
|
// ✅ NUEVO: Actualizar dimensiones almacenadas
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
GuideThickness = height; // Mantener compatibilidad
|
||||||
|
|
||||||
|
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||||
|
var zPosition = zAltura_Guia / 2 + zPos_Guia;
|
||||||
|
var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, width, height, wpfAngle, zPosition);
|
||||||
|
|
||||||
|
// ✅ CRÍTICO: Crear el Box con las dimensiones correctas almacenadas
|
||||||
|
var box = new Box(Width, GuideThickness, zAltura_Guia);
|
||||||
|
var shapeIndex = _simulation.Shapes.Add(box);
|
||||||
|
|
||||||
|
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||||
|
var bodyDescription = BodyDescription.CreateKinematic(
|
||||||
|
new RigidPose(bepuCenter, CoordinateConverter.CreateBepuQuaternionFromWpfAngle(wpfAngle)),
|
||||||
|
new CollidableDescription(shapeIndex, 0.1f),
|
||||||
|
new BodyActivityDescription(0.01f)
|
||||||
|
);
|
||||||
|
|
||||||
|
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||||
|
_bodyCreated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Método para actualizar las propiedades desde el objeto WPF
|
||||||
|
public void UpdateProperties(float ancho, float altoGuia, float angulo)
|
||||||
|
{
|
||||||
|
// ✅ CORREGIDO: Actualizar todas las dimensiones
|
||||||
|
Width = ancho;
|
||||||
|
Height = altoGuia;
|
||||||
|
GuideThickness = altoGuia; // Mantener compatibilidad
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ CORREGIDO - Actualiza las dimensiones y recrea el cuerpo físico si es necesario
|
||||||
|
/// </summary>
|
||||||
|
public void SetDimensions(float width, float height)
|
||||||
|
{
|
||||||
|
// ✅ NUEVO: Actualizar dimensiones almacenadas
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
GuideThickness = height; // Mantener compatibilidad
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
|
||||||
|
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
var box = new Box(width, GuideThickness, zAltura_Guia);
|
||||||
|
var shapeIndex = _simulation.Shapes.Add(box);
|
||||||
|
ChangeBodyShape(shapeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ CORREGIDO - Actualiza posición usando las dimensiones reales almacenadas
|
||||||
|
/// </summary>
|
||||||
|
public void SetPosition(Vector2 wpfTopLeft, float wpfAngle, float actualWidth, float actualHeight)
|
||||||
|
{
|
||||||
|
// ✅ NUEVO: Actualizar dimensiones almacenadas
|
||||||
|
Width = actualWidth;
|
||||||
|
Height = actualHeight;
|
||||||
|
GuideThickness = actualHeight; // Mantener compatibilidad
|
||||||
|
|
||||||
|
// ✅ CRÍTICO: Recrear el box colisionador con las nuevas dimensiones
|
||||||
|
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
var box = new Box(actualWidth, GuideThickness, zAltura_Guia);
|
||||||
|
var shapeIndex = _simulation.Shapes.Add(box);
|
||||||
|
ChangeBodyShape(shapeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ USAR COORDINATECONVERTER para conversión centralizada con dimensiones reales
|
||||||
|
var zPosition = zAltura_Guia / 2 + zPos_Guia;
|
||||||
|
var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, actualWidth, actualHeight, wpfAngle, zPosition);
|
||||||
|
|
||||||
|
// Actualizar posición y rotación simultáneamente
|
||||||
|
CoordinateConverter.UpdateBepuBodyPose(_simulation, BodyHandle, bepuCenter, wpfAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ CORREGIDO: Actualiza tanto posición como rotación usando dimensiones reales
|
||||||
|
/// </summary>
|
||||||
|
internal void UpdateFromWpfParameters(Vector2 wpfTopLeft, float wpfAngle, float width, float height)
|
||||||
|
{
|
||||||
|
// ✅ NUEVO: Actualizar dimensiones almacenadas
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
GuideThickness = height; // Mantener compatibilidad
|
||||||
|
|
||||||
|
// ✅ CRÍTICO: Recrear el box colisionador con las nuevas dimensiones
|
||||||
|
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
var box = new Box(width, GuideThickness, zAltura_Guia);
|
||||||
|
var shapeIndex = _simulation.Shapes.Add(box);
|
||||||
|
ChangeBodyShape(shapeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ USAR COORDINATECONVERTER para conversión centralizada con dimensiones reales
|
||||||
|
var zPosition = zAltura_Guia / 2 + zPos_Guia;
|
||||||
|
var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, width, height, wpfAngle, zPosition);
|
||||||
|
|
||||||
|
// Actualizar posición y rotación simultáneamente
|
||||||
|
CoordinateConverter.UpdateBepuBodyPose(_simulation, BodyHandle, bepuCenter, wpfAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ CORREGIDO - Usar dimensiones reales almacenadas en lugar de aproximaciones
|
||||||
|
/// </summary>
|
||||||
|
public void SetPosition(Vector2 wpfTopLeft, float wpfAngle = 0)
|
||||||
|
{
|
||||||
|
// ✅ CORREGIDO: Usar dimensiones reales almacenadas en lugar de aproximaciones
|
||||||
|
SetPosition(wpfTopLeft, wpfAngle, Width, Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,237 @@
|
||||||
|
using BepuPhysics.Collidables;
|
||||||
|
using BepuPhysics;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CtrEditor.Simulacion
|
||||||
|
{
|
||||||
|
|
||||||
|
public class simTransporte : simBase
|
||||||
|
{
|
||||||
|
public float Speed { get; set; } // Velocidad para efectos de cinta transportadora (m/s)
|
||||||
|
public float Friction { get; set; } // Friccion para efectos de cinta transportadora
|
||||||
|
public float DistanceGuide2Guide { get; set; }
|
||||||
|
public bool isBrake { get; set; }
|
||||||
|
|
||||||
|
public bool TransportWithGuides = false;
|
||||||
|
private List<Action> _deferredActions;
|
||||||
|
public float Width { get; set; }
|
||||||
|
public float Height { get; set; }
|
||||||
|
|
||||||
|
// ✅ NUEVAS PROPIEDADES - cachear cálculos costosos
|
||||||
|
public Vector3 DirectionVector { get; private set; }
|
||||||
|
public List<simBotella> BottlesOnTransport { get; private set; } = new List<simBotella>();
|
||||||
|
|
||||||
|
public simTransporte(Simulation simulation, List<Action> deferredActions, float width, float height, Vector2 topLeft, float angle = 0, SimulationManagerBEPU simulationManager = null)
|
||||||
|
{
|
||||||
|
_simulation = simulation;
|
||||||
|
_deferredActions = deferredActions;
|
||||||
|
_simulationManager = simulationManager; // ✅ NUEVO: Almacenar referencia
|
||||||
|
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
|
||||||
|
// Usar el nuevo método Create que maneja Top-Left correctamente
|
||||||
|
Create(width, height, topLeft, angle);
|
||||||
|
|
||||||
|
// ✅ INICIALIZAR PROPIEDADES CRÍTICAS
|
||||||
|
UpdateCachedProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Angle
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return GetRotationZ();
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetRotation(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ SOBRESCRITO: SetRotation que actualiza automáticamente las propiedades cacheadas
|
||||||
|
/// </summary>
|
||||||
|
public new void SetRotation(float wpfAngle)
|
||||||
|
{
|
||||||
|
base.SetRotation(wpfAngle);
|
||||||
|
|
||||||
|
// ✅ CRÍTICO: Actualizar propiedades cacheadas y velocidad después del cambio de rotación
|
||||||
|
UpdateCachedProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void SetPosition(float x, float y, float z = 0)
|
||||||
|
{
|
||||||
|
base.SetPosition(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Actualiza posición desde Top-Left WPF con dimensiones y ángulo actuales
|
||||||
|
/// </summary>
|
||||||
|
internal void SetPositionFromWpfTopLeft(Vector2 wpfTopLeft)
|
||||||
|
{
|
||||||
|
var currentWpfAngle = GetRotationZ(); // Ya usa CoordinateConverter
|
||||||
|
var zPosition = GetPosition().Z; // Mantener Z actual
|
||||||
|
var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, Width, Height, currentWpfAngle, zPosition);
|
||||||
|
SetPosition(bepuCenter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Obtiene Top-Left WPF desde la posición actual
|
||||||
|
/// </summary>
|
||||||
|
internal Vector2 GetWpfTopLeft()
|
||||||
|
{
|
||||||
|
var bepuCenter = GetPosition();
|
||||||
|
var wpfAngle = GetRotationZ(); // Ya usa CoordinateConverter
|
||||||
|
return CoordinateConverter.CalculateWpfTopLeftFromBepuCenter(bepuCenter, Width, Height, wpfAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Actualiza tanto posición como rotación desde parámetros WPF
|
||||||
|
/// </summary>
|
||||||
|
internal void UpdateFromWpfParameters(Vector2 wpfTopLeft, float wpfAngle)
|
||||||
|
{
|
||||||
|
var zPosition = GetPosition().Z; // Mantener Z actual
|
||||||
|
var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, Width, Height, wpfAngle, zPosition);
|
||||||
|
CoordinateConverter.UpdateBepuBodyPose(_simulation, BodyHandle, bepuCenter, wpfAngle);
|
||||||
|
|
||||||
|
// ✅ CRÍTICO: Actualizar propiedades cacheadas y velocidad después del cambio de orientación
|
||||||
|
UpdateCachedProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ NUEVO MÉTODO - actualizar propiedades cacheadas y velocidad cinemática
|
||||||
|
internal void UpdateCachedProperties()
|
||||||
|
{
|
||||||
|
// ✅ CORREGIDO: La dirección siempre es UnitX rotado por el ángulo del transporte
|
||||||
|
// NO depende de las dimensiones (Width >= Height) sino solo de la rotación
|
||||||
|
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||||
|
var bepuQuaternion = bodyReference.Pose.Orientation;
|
||||||
|
|
||||||
|
// ✅ SIEMPRE usar UnitX y aplicar la rotación
|
||||||
|
DirectionVector = Vector3.Transform(Vector3.UnitX, bepuQuaternion);
|
||||||
|
|
||||||
|
// 🔍 DEBUG: Agregar información detallada
|
||||||
|
var wpfAngle = GetRotationZ();
|
||||||
|
//System.Diagnostics.Debug.WriteLine($"[UpdateCached] WPF Angle: {wpfAngle}°, DirectionVector: {DirectionVector}, Length: {DirectionVector.Length()}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// ✅ CORREGIDO: Aplicar conversión de coordenadas WPF→BEPU en el vector
|
||||||
|
var wpfAngle = GetRotationZ(); // Ángulo WPF en grados
|
||||||
|
var wpfAngleRadians = simBase.GradosARadianes(wpfAngle);
|
||||||
|
|
||||||
|
// Calcular el vector en coordenadas WPF
|
||||||
|
var wpfX = (float)Math.Cos(wpfAngleRadians);
|
||||||
|
var wpfY = (float)Math.Sin(wpfAngleRadians);
|
||||||
|
|
||||||
|
// ✅ APLICAR CONVERSIÓN Y: En WPF Y+ es abajo, en BEPU Y+ es arriba
|
||||||
|
DirectionVector = new Vector3(wpfX, -wpfY, 0); // Invertir Y para conversión WPF→BEPU
|
||||||
|
|
||||||
|
// 🔍 DEBUG: Agregar información detallada
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[UpdateCached-Fallback] WPF Angle: {wpfAngle}°, WPF Vector: ({wpfX:F3}, {wpfY:F3}), BEPU DirectionVector: {DirectionVector}, Length: {DirectionVector.Length()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ NUEVO: Actualizar la velocidad del cuerpo cinemático siempre que cambien las propiedades
|
||||||
|
ActualizarVelocidadCinematica();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ MODIFICADO: Actualizar la velocidad y luego la del cuerpo cinemático
|
||||||
|
public void SetSpeed(float speed)
|
||||||
|
{
|
||||||
|
Speed = speed;
|
||||||
|
ActualizarVelocidadCinematica();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ NUEVO: Aplica la velocidad al cuerpo cinemático de BEPU.
|
||||||
|
/// </summary>
|
||||||
|
public void ActualizarVelocidadCinematica()
|
||||||
|
{
|
||||||
|
if (_simulation != null && this.BodyHandle.Value >= 0 && _simulation.Bodies.BodyExists(this.BodyHandle))
|
||||||
|
{
|
||||||
|
var body = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||||
|
// ✅ CORREGIDO: Convertir velocidad de m/min a m/s para la simulación
|
||||||
|
body.Velocity.Linear = this.DirectionVector * (this.Speed / SpeedConversionFactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detiene completamente el transporte
|
||||||
|
/// </summary>
|
||||||
|
public void StopTransport()
|
||||||
|
{
|
||||||
|
SetSpeed(0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invierte la dirección del transporte manteniendo la misma velocidad
|
||||||
|
/// </summary>
|
||||||
|
public void ReverseTransport()
|
||||||
|
{
|
||||||
|
SetSpeed(-Speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetDimensions(float width, float height)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
|
||||||
|
// ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
|
||||||
|
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||||
|
{
|
||||||
|
var box = new Box(width, height, zAltura_Transporte);
|
||||||
|
var shapeIndex = _simulation.Shapes.Add(box);
|
||||||
|
ChangeBodyShape(shapeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ CRÍTICO: Actualizar propiedades cacheadas después del cambio de dimensiones
|
||||||
|
UpdateCachedProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Create(float width, float height, Vector2 wpfTopLeft, float wpfAngle = 0)
|
||||||
|
{
|
||||||
|
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||||
|
var zPosition = zAltura_Transporte / 2f + zPos_Transporte;
|
||||||
|
var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, width, height, wpfAngle, zPosition);
|
||||||
|
|
||||||
|
Create(width, height, bepuCenter, wpfAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ✅ SIMPLIFICADO: RemoverBody ya no necesita limpiar restricciones de botellas.
|
||||||
|
/// </summary>
|
||||||
|
public new void RemoverBody()
|
||||||
|
{
|
||||||
|
base.RemoverBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Create(float width, float height, Vector3 bepuPosition, float wpfAngle = 0)
|
||||||
|
{
|
||||||
|
RemoverBody();
|
||||||
|
|
||||||
|
var box = new Box(width, height, zAltura_Transporte);
|
||||||
|
var shapeIndex = _simulation.Shapes.Add(box);
|
||||||
|
|
||||||
|
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||||
|
var bodyDescription = BodyDescription.CreateKinematic(
|
||||||
|
new RigidPose(bepuPosition, CoordinateConverter.CreateBepuQuaternionFromWpfAngle(wpfAngle)),
|
||||||
|
new CollidableDescription(shapeIndex, 0.1f),
|
||||||
|
new BodyActivityDescription(-1f)
|
||||||
|
);
|
||||||
|
|
||||||
|
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||||
|
_bodyCreated = true; // Marcar que hemos creado un cuerpo
|
||||||
|
|
||||||
|
// ✅ CRÍTICO: Actualizar propiedades cacheadas después de crear el body
|
||||||
|
UpdateCachedProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,126 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Windows.Threading;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CtrEditor.Simulacion.Fluids;
|
|
||||||
using nkast.Aether.Physics2D.Common;
|
|
||||||
using CtrEditor.ObjetosSim;
|
|
||||||
|
|
||||||
namespace CtrEditor
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// ViewModel para controlar la simulación de fluidos de forma independiente
|
|
||||||
/// </summary>
|
|
||||||
public partial class SimulationFluidsViewModel : ObservableObject
|
|
||||||
{
|
|
||||||
private MainViewModel _mainViewModel;
|
|
||||||
private readonly DispatcherTimer _timerSimulacionFluidos;
|
|
||||||
private Stopwatch _stopwatch;
|
|
||||||
private double _lastUpdateTime;
|
|
||||||
|
|
||||||
// Propiedades observables
|
|
||||||
[ObservableProperty]
|
|
||||||
private bool isFluidSimulationRunning;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private float fps;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private int particulasCount;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mainViewModel">Referencia al ViewModel principal</param>
|
|
||||||
public SimulationFluidsViewModel(MainViewModel mainViewModel)
|
|
||||||
{
|
|
||||||
_mainViewModel = mainViewModel;
|
|
||||||
|
|
||||||
// Inicializar timer para simulación de fluidos
|
|
||||||
_timerSimulacionFluidos = new DispatcherTimer();
|
|
||||||
_timerSimulacionFluidos.Interval = TimeSpan.FromMilliseconds(16); // ~60fps
|
|
||||||
_timerSimulacionFluidos.Tick += OnTickSimulacionFluidos;
|
|
||||||
|
|
||||||
_stopwatch = new Stopwatch();
|
|
||||||
_stopwatch.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Inicia la simulación de fluidos
|
|
||||||
/// </summary>
|
|
||||||
public void StartFluidSimulation()
|
|
||||||
{
|
|
||||||
if (IsFluidSimulationRunning)
|
|
||||||
return;
|
|
||||||
|
|
||||||
IsFluidSimulationRunning = true;
|
|
||||||
|
|
||||||
// Notificar a todos los objetos que usan fluidos que inicie su simulación
|
|
||||||
foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables)
|
|
||||||
{
|
|
||||||
if (objetoSimulable is osSistemaFluidos sistemaFluidos)
|
|
||||||
{
|
|
||||||
sistemaFluidos.UpdateGeometryStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastUpdateTime = _stopwatch.Elapsed.TotalMilliseconds;
|
|
||||||
_timerSimulacionFluidos.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Detiene la simulación de fluidos
|
|
||||||
/// </summary>
|
|
||||||
public void StopFluidSimulation()
|
|
||||||
{
|
|
||||||
if (!IsFluidSimulationRunning)
|
|
||||||
return;
|
|
||||||
|
|
||||||
IsFluidSimulationRunning = false;
|
|
||||||
_timerSimulacionFluidos.Stop();
|
|
||||||
|
|
||||||
// Notificar a todos los objetos que usan fluidos que detenga su simulación
|
|
||||||
foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables)
|
|
||||||
{
|
|
||||||
if (objetoSimulable is osSistemaFluidos sistemaFluidos)
|
|
||||||
{
|
|
||||||
sistemaFluidos.SimulationStop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evento que se dispara cada tick de la simulación de fluidos
|
|
||||||
/// </summary>
|
|
||||||
private void OnTickSimulacionFluidos(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
// Calcular delta time
|
|
||||||
double currentTime = _stopwatch.Elapsed.TotalMilliseconds;
|
|
||||||
float deltaTime = (float)(currentTime - _lastUpdateTime) / 1000.0f; // convertir a segundos
|
|
||||||
_lastUpdateTime = currentTime;
|
|
||||||
|
|
||||||
int totalParticleCount = 0;
|
|
||||||
|
|
||||||
// Actualizar todos los sistemas de fluidos
|
|
||||||
foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables)
|
|
||||||
{
|
|
||||||
if (objetoSimulable is osSistemaFluidos sistemaFluidos && sistemaFluidos.Show_On_This_Page)
|
|
||||||
{
|
|
||||||
// Actualizar la simulación con el deltaTime calculado
|
|
||||||
if (sistemaFluidos._simFluidos != null)
|
|
||||||
{
|
|
||||||
sistemaFluidos._simFluidos.Actualizar(deltaTime);
|
|
||||||
totalParticleCount += sistemaFluidos._simFluidos.ParticlesCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actualizar controles visuales
|
|
||||||
sistemaFluidos.UpdateControl((int)(deltaTime * 1000));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actualizar estadísticas
|
|
||||||
Fps = 1.0f / Math.Max(deltaTime, 0.001f);
|
|
||||||
ParticulasCount = totalParticleCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -34,6 +34,20 @@ namespace CtrEditor
|
||||||
public bool IsMaximized { get; set; } = false;
|
public bool IsMaximized { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class CameraSettings
|
||||||
|
{
|
||||||
|
public double PositionX { get; set; } = 3.86;
|
||||||
|
public double PositionY { get; set; } = -18.13;
|
||||||
|
public double PositionZ { get; set; } = 10;
|
||||||
|
public double LookDirectionX { get; set; } = -3.86;
|
||||||
|
public double LookDirectionY { get; set; } = 18.1;
|
||||||
|
public double LookDirectionZ { get; set; } = -10;
|
||||||
|
public double UpDirectionX { get; set; } = -0.1;
|
||||||
|
public double UpDirectionY { get; set; } = 0.48;
|
||||||
|
public double UpDirectionZ { get; set; } = 0.87;
|
||||||
|
public double FieldOfView { get; set; } = 60;
|
||||||
|
}
|
||||||
|
|
||||||
internal class EstadoPersistente
|
internal class EstadoPersistente
|
||||||
{
|
{
|
||||||
// Ruta donde se guardará el estado
|
// Ruta donde se guardará el estado
|
||||||
|
@ -58,6 +72,8 @@ namespace CtrEditor
|
||||||
|
|
||||||
public LibraryWindowSettings LibraryWindow { get; set; } = new LibraryWindowSettings();
|
public LibraryWindowSettings LibraryWindow { get; set; } = new LibraryWindowSettings();
|
||||||
|
|
||||||
|
public CameraSettings Camera { get; set; } = new CameraSettings();
|
||||||
|
|
||||||
// Propiedad pública con get y set para controlar el acceso a _strDirectorioTrabajo
|
// Propiedad pública con get y set para controlar el acceso a _strDirectorioTrabajo
|
||||||
public string directorio
|
public string directorio
|
||||||
{
|
{
|
||||||
|
@ -91,6 +107,7 @@ namespace CtrEditor
|
||||||
_strDirectorioTrabajo = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
_strDirectorioTrabajo = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||||
RecentDirectories = new List<string>();
|
RecentDirectories = new List<string>();
|
||||||
LibraryWindow = new LibraryWindowSettings();
|
LibraryWindow = new LibraryWindowSettings();
|
||||||
|
Camera = new CameraSettings();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +150,9 @@ namespace CtrEditor
|
||||||
// Asegurar que LibraryWindow esté inicializado
|
// Asegurar que LibraryWindow esté inicializado
|
||||||
if (estado.LibraryWindow == null)
|
if (estado.LibraryWindow == null)
|
||||||
estado.LibraryWindow = new LibraryWindowSettings();
|
estado.LibraryWindow = new LibraryWindowSettings();
|
||||||
|
// Asegurar que Camera esté inicializado
|
||||||
|
if (estado.Camera == null)
|
||||||
|
estado.Camera = new CameraSettings();
|
||||||
return estado;
|
return estado;
|
||||||
}
|
}
|
||||||
return new EstadoPersistente().Inizializar(); // Devuelve una nueva instancia si no existe el archivo
|
return new EstadoPersistente().Inizializar(); // Devuelve una nueva instancia si no existe el archivo
|
||||||
|
|
Loading…
Reference in New Issue