Compare commits
18 Commits
256d86aca5
...
3eee0e3d9b
Author | SHA1 | Date |
---|---|---|
|
3eee0e3d9b | |
|
a6cbd8c4ab | |
|
095228144a | |
|
ab8066d1e8 | |
|
f431ede7bd | |
|
a00183c4f6 | |
|
ba073a9e80 | |
|
c1584e8d55 | |
|
501f0ffb9b | |
|
4ff93a5802 | |
|
e38adc9f56 | |
|
fd215bc677 | |
|
3e53a51e8b | |
|
3773da0ee3 | |
|
fbac81ec45 | |
|
121e586d53 | |
|
d259f53081 | |
|
2cb90ec2dc |
|
@ -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": "../Librerias/bepuphysics2-master"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
|
|
|
@ -7,22 +7,37 @@
|
|||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<!-- Configuración para Hot Reload -->
|
||||
<EnableHotReload>true</EnableHotReload>
|
||||
<HotReloadLogger>true</HotReloadLogger>
|
||||
<ForceGenerateMetadataAssemblyFormat>true</ForceGenerateMetadataAssemblyFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<Optimize>True</Optimize>
|
||||
<Optimize>False</Optimize>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>portable</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<Optimize>True</Optimize>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<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="ObjetosSim\ucBasicExample.xaml.cs" />
|
||||
<Compile Remove="ObjetosSim\ucTransporteCurva.xaml.cs" />
|
||||
<Compile Remove="Simulacion\FPhysics.cs" />
|
||||
<Compile Remove="Simulacion\GeometrySimulator.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -66,26 +81,24 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Remove="ObjetosSim\ucBasicExample.xaml" />
|
||||
<Page Remove="ObjetosSim\ucTransporteCurva.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Documentation\BEPU Forces.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>
|
||||
<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="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="Emgu.CV" 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="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="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc3.3" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
|
||||
|
@ -93,8 +106,6 @@
|
|||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="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>
|
||||
|
@ -177,7 +188,6 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="ObjetosSim\Fluids\" />
|
||||
<Folder Include="paddleocr\cls\inference\" />
|
||||
<Folder Include="paddleocr\det\inference\" />
|
||||
<Folder Include="paddleocr\keys\" />
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,937 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using BulletSharp;
|
||||
using BulletSharp.Math;
|
||||
|
||||
namespace CtrEditor.Simulacion
|
||||
{
|
||||
public class simBase
|
||||
{
|
||||
public RigidBody Body { get; protected set; }
|
||||
public DiscreteDynamicsWorld _world;
|
||||
|
||||
public void RemoverBody()
|
||||
{
|
||||
if (Body != null && _world != null)
|
||||
{
|
||||
_world.RemoveRigidBody(Body);
|
||||
Body?.Dispose();
|
||||
Body = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static float Min(float Value, float Min = 0.01f)
|
||||
{
|
||||
return Value < Min ? Min : Value;
|
||||
}
|
||||
|
||||
public static float GradosARadianes(float grados)
|
||||
{
|
||||
return (float)(grados * (Math.PI / 180));
|
||||
}
|
||||
|
||||
public static float RadianesAGrados(float radianes)
|
||||
{
|
||||
return (float)(radianes * (180 / Math.PI));
|
||||
}
|
||||
|
||||
public void SetPosition(float x, float y)
|
||||
{
|
||||
if (Body != null)
|
||||
{
|
||||
var transform = Body.WorldTransform;
|
||||
transform.Origin = new Vector3(x, y, 0);
|
||||
Body.WorldTransform = transform;
|
||||
Body.Activate();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPosition(Vector3 centro)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Body != null)
|
||||
{
|
||||
var transform = Body.WorldTransform;
|
||||
transform.Origin = centro;
|
||||
Body.WorldTransform = transform;
|
||||
Body.Activate();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 GetPosition()
|
||||
{
|
||||
return Body?.WorldTransform.Origin ?? Vector3.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public class simCurve : simBase
|
||||
{
|
||||
private float _innerRadius;
|
||||
private float _outerRadius;
|
||||
private float _startAngle;
|
||||
private float _endAngle;
|
||||
public float Speed { get; set; }
|
||||
|
||||
private List<Action> _deferredActions;
|
||||
|
||||
public simCurve(DiscreteDynamicsWorld world, List<Action> deferredActions, float innerRadius, float outerRadius, float startAngle, float endAngle, Vector3 position)
|
||||
{
|
||||
_world = world;
|
||||
_deferredActions = deferredActions;
|
||||
_innerRadius = innerRadius;
|
||||
_outerRadius = outerRadius;
|
||||
_startAngle = GradosARadianes(startAngle);
|
||||
_endAngle = GradosARadianes(endAngle);
|
||||
Create(position);
|
||||
}
|
||||
|
||||
public void Create(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector3 position)
|
||||
{
|
||||
if (_world == null)
|
||||
return;
|
||||
_innerRadius = innerRadius;
|
||||
_outerRadius = outerRadius;
|
||||
_startAngle = GradosARadianes(startAngle);
|
||||
_endAngle = GradosARadianes(endAngle);
|
||||
Create(position);
|
||||
}
|
||||
|
||||
public void Create(Vector3 position)
|
||||
{
|
||||
RemoverBody();
|
||||
|
||||
// Crear una forma compuesta para la curva
|
||||
var compoundShape = new CompoundShape();
|
||||
|
||||
// Crear segmentos de la curva como cajas pequeñas
|
||||
int segments = 32;
|
||||
float angleStep = (_endAngle - _startAngle) / segments;
|
||||
float thickness = 0.1f; // Grosor de la curva
|
||||
|
||||
for (int i = 0; i < segments; i++)
|
||||
{
|
||||
float angle = _startAngle + i * angleStep;
|
||||
float midRadius = (_innerRadius + _outerRadius) / 2f;
|
||||
float segmentWidth = (_outerRadius - _innerRadius);
|
||||
float segmentLength = midRadius * angleStep;
|
||||
|
||||
var segmentShape = new BoxShape(segmentLength / 2f, segmentWidth / 2f, thickness / 2f);
|
||||
|
||||
var segmentTransform = Matrix.Identity;
|
||||
segmentTransform.Origin = new Vector3(
|
||||
midRadius * (float)Math.Cos(angle),
|
||||
midRadius * (float)Math.Sin(angle),
|
||||
0
|
||||
);
|
||||
segmentTransform = Matrix.RotationZ(angle) * segmentTransform;
|
||||
|
||||
compoundShape.AddChildShape(segmentTransform, segmentShape);
|
||||
}
|
||||
|
||||
var motionState = new DefaultMotionState(Matrix.Translation(position));
|
||||
var rbInfo = new RigidBodyConstructionInfo(0, motionState, compoundShape, Vector3.Zero);
|
||||
Body = new RigidBody(rbInfo);
|
||||
Body.UserObject = this;
|
||||
Body.CollisionFlags |= CollisionFlags.NoContactResponse; // Sensor
|
||||
|
||||
_world.AddRigidBody(Body);
|
||||
}
|
||||
|
||||
public void SetSpeed(float speed)
|
||||
{
|
||||
Speed = speed;
|
||||
}
|
||||
|
||||
public void ApplyCurveEffect(RigidBody bottle)
|
||||
{
|
||||
Vector3 centerToBottle = bottle.WorldTransform.Origin - Body.WorldTransform.Origin;
|
||||
float distanceToCenter = centerToBottle.Length;
|
||||
|
||||
if (distanceToCenter >= _innerRadius && distanceToCenter <= _outerRadius)
|
||||
{
|
||||
float overlapPercentage = CalculateAngleOverlap(bottle);
|
||||
|
||||
if (overlapPercentage > 0)
|
||||
{
|
||||
float speedMetersPerSecond = Speed / 60.0f;
|
||||
Vector3 tangent = new Vector3(-centerToBottle.Y, centerToBottle.X, 0);
|
||||
tangent.Normalize();
|
||||
|
||||
if (speedMetersPerSecond < 0)
|
||||
tangent = -tangent;
|
||||
|
||||
Vector3 currentVelocity = bottle.LinearVelocity;
|
||||
float currentSpeed = currentVelocity.Length;
|
||||
float conveyorSpeed = Math.Abs(speedMetersPerSecond);
|
||||
float targetSpeed = Math.Max(currentSpeed, conveyorSpeed);
|
||||
|
||||
Vector3 currentDir = currentVelocity;
|
||||
if (currentDir.LengthSquared > 0)
|
||||
currentDir.Normalize();
|
||||
else
|
||||
currentDir = tangent;
|
||||
|
||||
Vector3 newDirection = currentDir * (1 - overlapPercentage) + tangent * overlapPercentage;
|
||||
newDirection.Normalize();
|
||||
|
||||
bottle.LinearVelocity = newDirection * targetSpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float CalculateAngleOverlap(RigidBody bottle)
|
||||
{
|
||||
Vector3 centerToBottle = bottle.WorldTransform.Origin - Body.WorldTransform.Origin;
|
||||
float bottleAngle = (float)Math.Atan2(centerToBottle.Y, centerToBottle.X);
|
||||
|
||||
float normalizedBottleAngle = bottleAngle < 0 ? bottleAngle + 2 * (float)Math.PI : bottleAngle;
|
||||
float normalizedStartAngle = _startAngle < 0 ? _startAngle + 2 * (float)Math.PI : _startAngle;
|
||||
float normalizedEndAngle = _endAngle < 0 ? _endAngle + 2 * (float)Math.PI : _endAngle;
|
||||
|
||||
if (normalizedStartAngle > normalizedEndAngle)
|
||||
{
|
||||
if (!(normalizedBottleAngle >= normalizedStartAngle || normalizedBottleAngle <= normalizedEndAngle))
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (normalizedBottleAngle < normalizedStartAngle || normalizedBottleAngle > normalizedEndAngle)
|
||||
return 0;
|
||||
}
|
||||
|
||||
float distanceToCenter = centerToBottle.Length;
|
||||
if (distanceToCenter < _innerRadius || distanceToCenter > _outerRadius)
|
||||
return 0;
|
||||
|
||||
float angleFromStart, angleToEnd, totalAngle;
|
||||
|
||||
if (normalizedStartAngle > normalizedEndAngle)
|
||||
{
|
||||
totalAngle = (2 * (float)Math.PI - normalizedStartAngle) + normalizedEndAngle;
|
||||
if (normalizedBottleAngle >= normalizedStartAngle)
|
||||
{
|
||||
angleFromStart = normalizedBottleAngle - normalizedStartAngle;
|
||||
angleToEnd = (2 * (float)Math.PI - normalizedBottleAngle) + normalizedEndAngle;
|
||||
}
|
||||
else
|
||||
{
|
||||
angleFromStart = (2 * (float)Math.PI - normalizedStartAngle) + normalizedBottleAngle;
|
||||
angleToEnd = normalizedEndAngle - normalizedBottleAngle;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
angleFromStart = normalizedBottleAngle - normalizedStartAngle;
|
||||
angleToEnd = normalizedEndAngle - normalizedBottleAngle;
|
||||
totalAngle = normalizedEndAngle - normalizedStartAngle;
|
||||
}
|
||||
|
||||
float minAngleDistance = Math.Min(angleFromStart, angleToEnd);
|
||||
float overlapPercentage = Math.Min(minAngleDistance / (totalAngle * 0.1f), 1.0f);
|
||||
return overlapPercentage;
|
||||
}
|
||||
}
|
||||
|
||||
public class simDescarte : simBase
|
||||
{
|
||||
private float _radius;
|
||||
private List<Action> _deferredActions;
|
||||
|
||||
public simDescarte(DiscreteDynamicsWorld world, List<Action> deferredActions, float diameter, Vector3 position)
|
||||
{
|
||||
_world = world;
|
||||
_deferredActions = deferredActions;
|
||||
_radius = diameter / 2;
|
||||
Create(position);
|
||||
}
|
||||
|
||||
public void SetDiameter(float diameter)
|
||||
{
|
||||
if (diameter <= 0)
|
||||
diameter = 0.01f;
|
||||
_radius = diameter / 2;
|
||||
Create(Body.WorldTransform.Origin);
|
||||
}
|
||||
|
||||
public void Create(Vector3 position)
|
||||
{
|
||||
RemoverBody();
|
||||
|
||||
var shape = new CylinderShape(_radius, _radius, 0.1f); // Cilindro plano para simular círculo 2D
|
||||
var motionState = new DefaultMotionState(Matrix.Translation(position));
|
||||
var rbInfo = new RigidBodyConstructionInfo(0, motionState, shape, Vector3.Zero);
|
||||
Body = new RigidBody(rbInfo);
|
||||
Body.UserObject = this;
|
||||
Body.CollisionFlags |= CollisionFlags.NoContactResponse; // Sensor
|
||||
|
||||
_world.AddRigidBody(Body);
|
||||
}
|
||||
}
|
||||
|
||||
public class simTransporte : simBase
|
||||
{
|
||||
public float Speed { get; set; }
|
||||
public float Friction { get; set; }
|
||||
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; }
|
||||
|
||||
public simTransporte(DiscreteDynamicsWorld world, List<Action> deferredActions, float width, float height, Vector3 position, float angle = 0)
|
||||
{
|
||||
_world = world;
|
||||
_deferredActions = deferredActions;
|
||||
Create(width, height, position, angle);
|
||||
}
|
||||
|
||||
public float Angle
|
||||
{
|
||||
get
|
||||
{
|
||||
var rotation = Body.WorldTransform.GetBasis();
|
||||
return RadianesAGrados((float)Math.Atan2(rotation.M21, rotation.M11));
|
||||
}
|
||||
set
|
||||
{
|
||||
var transform = Body.WorldTransform;
|
||||
transform.Basis = Matrix3x3.CreateRotationZ(GradosARadianes(value));
|
||||
Body.WorldTransform = transform;
|
||||
}
|
||||
}
|
||||
|
||||
public new void SetPosition(float x, float y)
|
||||
{
|
||||
var transform = Body.WorldTransform;
|
||||
transform.Origin = new Vector3(x, y, 0);
|
||||
Body.WorldTransform = transform;
|
||||
}
|
||||
|
||||
public void SetSpeed(float speed)
|
||||
{
|
||||
Speed = speed;
|
||||
}
|
||||
|
||||
public void SetDimensions(float width, float height)
|
||||
{
|
||||
RemoverBody();
|
||||
Create(width, height, Body.WorldTransform.Origin, Angle);
|
||||
}
|
||||
|
||||
public void Create(float width, float height, Vector3 position, float angle = 0)
|
||||
{
|
||||
RemoverBody();
|
||||
Width = width;
|
||||
Height = height;
|
||||
Friction = 0.1f;
|
||||
|
||||
var shape = new BoxShape(width / 2, height / 2, 0.05f); // Caja plana para simular rectángulo 2D
|
||||
var transform = Matrix.Translation(position) * Matrix.RotationZ(GradosARadianes(angle));
|
||||
var motionState = new DefaultMotionState(transform);
|
||||
var rbInfo = new RigidBodyConstructionInfo(0, motionState, shape, Vector3.Zero);
|
||||
Body = new RigidBody(rbInfo);
|
||||
Body.UserObject = this;
|
||||
Body.CollisionFlags |= CollisionFlags.NoContactResponse; // Sensor
|
||||
|
||||
_world.AddRigidBody(Body);
|
||||
}
|
||||
}
|
||||
|
||||
public class simBarrera : simBase
|
||||
{
|
||||
public float Distancia;
|
||||
public int LuzCortada;
|
||||
public bool LuzCortadaNeck;
|
||||
public bool DetectNeck;
|
||||
public List<simBotella> ListSimBotellaContact;
|
||||
|
||||
float _height;
|
||||
private List<Action> _deferredActions;
|
||||
|
||||
public simBarrera(DiscreteDynamicsWorld world, List<Action> deferredActions, float width, float height, Vector3 position, float angle = 0, bool detectectNeck = false)
|
||||
{
|
||||
_world = world;
|
||||
_height = Min(height);
|
||||
DetectNeck = detectectNeck;
|
||||
_deferredActions = deferredActions;
|
||||
ListSimBotellaContact = new List<simBotella>();
|
||||
Create(Min(width), _height, position, angle);
|
||||
}
|
||||
|
||||
public float Angle
|
||||
{
|
||||
get
|
||||
{
|
||||
var rotation = Body.WorldTransform.GetBasis();
|
||||
return RadianesAGrados((float)Math.Atan2(rotation.M21, rotation.M11));
|
||||
}
|
||||
set
|
||||
{
|
||||
var transform = Body.WorldTransform;
|
||||
transform.Basis = Matrix3x3.CreateRotationZ(GradosARadianes(value));
|
||||
Body.WorldTransform = transform;
|
||||
}
|
||||
}
|
||||
|
||||
public new void SetPosition(float x, float y)
|
||||
{
|
||||
var transform = Body.WorldTransform;
|
||||
transform.Origin = new Vector3(x, y, 0);
|
||||
Body.WorldTransform = transform;
|
||||
}
|
||||
|
||||
public void SetDimensions(float width, float height)
|
||||
{
|
||||
RemoverBody();
|
||||
Create(Min(width), Min(height), Body.WorldTransform.Origin, Angle);
|
||||
}
|
||||
|
||||
public void Create(float width, float height, Vector3 position, float angle = 0, bool detectectNeck = false)
|
||||
{
|
||||
RemoverBody();
|
||||
_height = Min(height);
|
||||
DetectNeck = detectectNeck;
|
||||
|
||||
var shape = new BoxShape(Min(width) / 2, _height / 2, 0.05f);
|
||||
var transform = Matrix.Translation(position) * Matrix.RotationZ(GradosARadianes(angle));
|
||||
var motionState = new DefaultMotionState(transform);
|
||||
var rbInfo = new RigidBodyConstructionInfo(0, motionState, shape, Vector3.Zero);
|
||||
Body = new RigidBody(rbInfo);
|
||||
Body.UserObject = this;
|
||||
Body.CollisionFlags |= CollisionFlags.NoContactResponse; // Sensor
|
||||
|
||||
_world.AddRigidBody(Body);
|
||||
LuzCortada = 0;
|
||||
}
|
||||
|
||||
public void CheckIfNecksIsTouching()
|
||||
{
|
||||
if (LuzCortada == 0)
|
||||
return;
|
||||
|
||||
foreach (var botella in ListSimBotellaContact)
|
||||
{
|
||||
Vector3 sensorCenter = Body.WorldTransform.Origin;
|
||||
float halfHeight = _height / 2;
|
||||
var rotation = Body.WorldTransform.GetBasis();
|
||||
float cos = rotation.M11;
|
||||
float sin = rotation.M21;
|
||||
|
||||
Vector3 lineStart = sensorCenter + new Vector3(-halfHeight * cos, halfHeight * sin, 0);
|
||||
Vector3 lineEnd = sensorCenter + new Vector3(halfHeight * cos, -halfHeight * sin, 0);
|
||||
|
||||
Vector3 fixtureCenter = botella.Body.WorldTransform.Origin;
|
||||
Vector3 closestPoint = ProjectPointOntoLine(fixtureCenter, lineStart, lineEnd);
|
||||
|
||||
float distance = (closestPoint - fixtureCenter).Length;
|
||||
Distancia = distance;
|
||||
|
||||
if (distance <= botella._neckRadius)
|
||||
{
|
||||
LuzCortadaNeck = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LuzCortadaNeck = false;
|
||||
}
|
||||
|
||||
private Vector3 ProjectPointOntoLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd)
|
||||
{
|
||||
Vector3 lineDirection = lineEnd - lineStart;
|
||||
lineDirection.Normalize();
|
||||
Vector3 pointToLineStart = point - lineStart;
|
||||
float projectionLength = Vector3.Dot(pointToLineStart, lineDirection);
|
||||
return lineStart + projectionLength * lineDirection;
|
||||
}
|
||||
}
|
||||
|
||||
public class simGuia : simBase
|
||||
{
|
||||
private List<Action> _deferredActions;
|
||||
|
||||
public simGuia(DiscreteDynamicsWorld world, List<Action> deferredActions, Vector3 start, Vector3 end)
|
||||
{
|
||||
_world = world;
|
||||
_deferredActions = deferredActions;
|
||||
Create(start, end);
|
||||
}
|
||||
|
||||
public void Create(Vector3 start, Vector3 end)
|
||||
{
|
||||
RemoverBody();
|
||||
|
||||
Vector3 center = (start + end) / 2;
|
||||
Vector3 direction = end - start;
|
||||
float length = direction.Length;
|
||||
|
||||
var shape = new BoxShape(length / 2, 0.01f, 0.05f); // Línea como caja muy delgada
|
||||
|
||||
float angle = (float)Math.Atan2(direction.Y, direction.X);
|
||||
var transform = Matrix.Translation(center) * Matrix.RotationZ(angle);
|
||||
var motionState = new DefaultMotionState(transform);
|
||||
var rbInfo = new RigidBodyConstructionInfo(0, motionState, shape, Vector3.Zero);
|
||||
Body = new RigidBody(rbInfo);
|
||||
Body.UserObject = this;
|
||||
|
||||
_world.AddRigidBody(Body);
|
||||
}
|
||||
|
||||
public void UpdateVertices(Vector3 newStart, Vector3 newEnd)
|
||||
{
|
||||
Create(newStart, newEnd);
|
||||
}
|
||||
}
|
||||
|
||||
public class simBotella : simBase
|
||||
{
|
||||
public float Radius;
|
||||
private float _mass;
|
||||
public bool Descartar = false;
|
||||
public int isOnTransports;
|
||||
public List<simBase> ListOnTransports;
|
||||
public bool isRestricted;
|
||||
public bool isNoMoreRestricted;
|
||||
public float OriginalMass;
|
||||
public simTransporte ConveyorRestrictedTo;
|
||||
public RigidBody axisRestrictedBy;
|
||||
public float OverlapPercentage;
|
||||
public float _neckRadius;
|
||||
|
||||
private List<Action> _deferredActions;
|
||||
private DiscreteDynamicsWorld _worldRef;
|
||||
|
||||
public simBotella(DiscreteDynamicsWorld world, List<Action> deferredActions, float diameter, Vector3 position, float mass, float neckRadius = 0)
|
||||
{
|
||||
_world = world;
|
||||
_worldRef = world;
|
||||
ListOnTransports = new List<simBase>();
|
||||
_deferredActions = deferredActions;
|
||||
|
||||
diameter = Min(diameter, 0.01f);
|
||||
Radius = diameter / 2;
|
||||
_mass = mass;
|
||||
|
||||
if (neckRadius <= 0)
|
||||
neckRadius = diameter / 4;
|
||||
_neckRadius = neckRadius;
|
||||
Create(position);
|
||||
}
|
||||
|
||||
public float CenterX => Body.WorldTransform.Origin.X;
|
||||
public float CenterY => Body.WorldTransform.Origin.Y;
|
||||
public Vector3 Center => Body.WorldTransform.Origin;
|
||||
|
||||
public float Mass
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_mass <= 0)
|
||||
_mass = 1;
|
||||
return _mass;
|
||||
}
|
||||
set
|
||||
{
|
||||
_mass = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void Create(Vector3 position)
|
||||
{
|
||||
RemoverBody();
|
||||
isOnTransports = 0;
|
||||
|
||||
var shape = new CylinderShape(Radius, Radius, 0.1f); // Cilindro para simular círculo 2D
|
||||
var motionState = new DefaultMotionState(Matrix.Translation(position));
|
||||
|
||||
Vector3 inertia;
|
||||
shape.CalculateLocalInertia(_mass, out inertia);
|
||||
var rbInfo = new RigidBodyConstructionInfo(_mass, motionState, shape, inertia);
|
||||
|
||||
Body = new RigidBody(rbInfo);
|
||||
Body.UserObject = this;
|
||||
Body.SetDamping(3f, 1f); // Linear y angular damping
|
||||
Body.SetSleepingThresholds(0, 0); // No dormir
|
||||
Body.CcdMotionThreshold = 1e-7f;
|
||||
Body.CcdSweptSphereRadius = Radius * 0.2f;
|
||||
|
||||
_world.AddRigidBody(Body);
|
||||
}
|
||||
|
||||
public void SetDiameter(float diameter)
|
||||
{
|
||||
diameter = Min(diameter, 0.01f);
|
||||
Radius = diameter / 2;
|
||||
Create(Body.WorldTransform.Origin);
|
||||
}
|
||||
|
||||
public void SetMass(float mass)
|
||||
{
|
||||
Mass = mass;
|
||||
}
|
||||
|
||||
public void ApplyConveyorSpeed(float deltaTime_s)
|
||||
{
|
||||
foreach (var transporte in ListOnTransports)
|
||||
{
|
||||
if (transporte is simTransporte conveyorRect)
|
||||
ApplyConveyorEffect(deltaTime_s, conveyorRect);
|
||||
if (transporte is simCurve conveyorCurve)
|
||||
conveyorCurve.ApplyCurveEffect(Body);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ApplyConveyorEffect(float deltaTime_s, simTransporte conveyor)
|
||||
{
|
||||
float overlapPercentage = CalculateOverlapedArea(this, conveyor);
|
||||
OverlapPercentage = overlapPercentage;
|
||||
|
||||
float speedMetersPerSecond = conveyor.Speed / 60.0f;
|
||||
var rotation = conveyor.Body.WorldTransform.GetBasis();
|
||||
Vector3 desiredVelocity = new Vector3(rotation.M11, rotation.M21, 0) * speedMetersPerSecond;
|
||||
|
||||
Vector3 currentVelocity = Body.LinearVelocity;
|
||||
Vector3 velocityDifference = desiredVelocity - currentVelocity;
|
||||
|
||||
float proporcionalVelocityNeeded = 1 - CalculateProportion(currentVelocity, desiredVelocity);
|
||||
float frictionCoefficient;
|
||||
|
||||
switch (proporcionalVelocityNeeded)
|
||||
{
|
||||
case > 0.3f:
|
||||
frictionCoefficient = conveyor.Friction * overlapPercentage;
|
||||
break;
|
||||
default:
|
||||
frictionCoefficient = proporcionalVelocityNeeded * overlapPercentage;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isRestricted && conveyor == ConveyorRestrictedTo && overlapPercentage > 0.5f)
|
||||
{
|
||||
Body.LinearVelocity = desiredVelocity;
|
||||
return true;
|
||||
}
|
||||
|
||||
Body.LinearVelocity += frictionCoefficient * desiredVelocity;
|
||||
return false;
|
||||
}
|
||||
|
||||
public float CalculateOverlapedArea(simBotella botella, simTransporte conveyor)
|
||||
{
|
||||
// Implementación simplificada del cálculo de área superpuesta
|
||||
Vector3 bottlePos = botella.Body.WorldTransform.Origin;
|
||||
Vector3 conveyorPos = conveyor.Body.WorldTransform.Origin;
|
||||
|
||||
// Transformar posición de botella al espacio local del transportador
|
||||
Matrix invTransform = conveyor.Body.WorldTransform.Inverse();
|
||||
Vector3 localBottlePos = Vector3.TransformCoordinate(bottlePos, invTransform);
|
||||
|
||||
// Verificar si la botella está dentro del rectángulo del transportador
|
||||
float halfWidth = conveyor.Width / 2;
|
||||
float halfHeight = conveyor.Height / 2;
|
||||
|
||||
if (Math.Abs(localBottlePos.X) <= halfWidth + botella.Radius &&
|
||||
Math.Abs(localBottlePos.Y) <= halfHeight + botella.Radius)
|
||||
{
|
||||
// Calcular área superpuesta aproximada
|
||||
float overlapX = Math.Max(0, Math.Min(halfWidth, localBottlePos.X + botella.Radius) - Math.Max(-halfWidth, localBottlePos.X - botella.Radius));
|
||||
float overlapY = Math.Max(0, Math.Min(halfHeight, localBottlePos.Y + botella.Radius) - Math.Max(-halfHeight, localBottlePos.Y - botella.Radius));
|
||||
|
||||
float overlapArea = overlapX * overlapY;
|
||||
float bottleArea = (float)(Math.PI * botella.Radius * botella.Radius);
|
||||
|
||||
return Math.Min(1.0f, overlapArea / bottleArea);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static float CalculateProportion(Vector3 currentVelocity, Vector3 desiredVelocity)
|
||||
{
|
||||
float dotProduct = Vector3.Dot(desiredVelocity, currentVelocity);
|
||||
float magnitudeV1Squared = desiredVelocity.LengthSquared;
|
||||
|
||||
if (magnitudeV1Squared == 0)
|
||||
return 0;
|
||||
|
||||
float proportion = dotProduct / magnitudeV1Squared;
|
||||
return proportion;
|
||||
}
|
||||
|
||||
public void CenterFixtureOnConveyor()
|
||||
{
|
||||
if (!isRestricted || ConveyorRestrictedTo == null)
|
||||
return;
|
||||
|
||||
Vector3 conveyorCenter = ConveyorRestrictedTo.Body.WorldTransform.Origin;
|
||||
float halfDistance = ConveyorRestrictedTo.DistanceGuide2Guide / 2;
|
||||
var rotation = ConveyorRestrictedTo.Body.WorldTransform.GetBasis();
|
||||
|
||||
Vector3 offset = new Vector3(halfDistance * rotation.M11, halfDistance * rotation.M21, 0);
|
||||
Vector3 lineStart = conveyorCenter - offset;
|
||||
Vector3 lineEnd = conveyorCenter + offset;
|
||||
|
||||
Vector3 fixtureCenter = Body.WorldTransform.Origin;
|
||||
Vector3 closestPoint = ProjectPointOntoLine(fixtureCenter, lineStart, lineEnd);
|
||||
|
||||
var transform = Body.WorldTransform;
|
||||
transform.Origin = closestPoint;
|
||||
Body.WorldTransform = transform;
|
||||
}
|
||||
|
||||
private Vector3 ProjectPointOntoLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd)
|
||||
{
|
||||
Vector3 lineDirection = lineEnd - lineStart;
|
||||
lineDirection.Normalize();
|
||||
Vector3 pointToLineStart = point - lineStart;
|
||||
float projectionLength = Vector3.Dot(pointToLineStart, lineDirection);
|
||||
return lineStart + projectionLength * lineDirection;
|
||||
}
|
||||
|
||||
public bool IsOnAnyTransport()
|
||||
{
|
||||
return isOnTransports > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public class SimulationManagerFP
|
||||
{
|
||||
private DiscreteDynamicsWorld world;
|
||||
private Canvas simulationCanvas;
|
||||
public List<simBase> Cuerpos;
|
||||
public List<Action> _deferredActions;
|
||||
private Stopwatch stopwatch;
|
||||
private double stopwatch_last;
|
||||
|
||||
// Componentes de Bullet Physics
|
||||
private DefaultCollisionConfiguration collisionConfiguration;
|
||||
private CollisionDispatcher dispatcher;
|
||||
private DbvtBroadphase broadphase;
|
||||
private SequentialImpulseConstraintSolver solver;
|
||||
|
||||
public Canvas DebugCanvas { get => simulationCanvas; set => simulationCanvas = value; }
|
||||
|
||||
public SimulationManagerFP()
|
||||
{
|
||||
// Inicializar Bullet Physics
|
||||
collisionConfiguration = new DefaultCollisionConfiguration();
|
||||
dispatcher = new CollisionDispatcher(collisionConfiguration);
|
||||
broadphase = new DbvtBroadphase();
|
||||
solver = new SequentialImpulseConstraintSolver();
|
||||
|
||||
world = new DiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
|
||||
world.Gravity = new Vector3(0, 0, 0); // Sin gravedad para simular 2D
|
||||
|
||||
Cuerpos = new List<simBase>();
|
||||
_deferredActions = new List<Action>();
|
||||
stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (world.NumCollisionObjects > 0)
|
||||
{
|
||||
for (int i = world.NumCollisionObjects - 1; i >= 0; i--)
|
||||
{
|
||||
var obj = world.CollisionObjectArray[i];
|
||||
var body = RigidBody.Upcast(obj);
|
||||
if (body != null && body.MotionState != null)
|
||||
{
|
||||
body.MotionState.Dispose();
|
||||
}
|
||||
world.RemoveCollisionObject(obj);
|
||||
obj.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (Cuerpos.Count > 0)
|
||||
Cuerpos.Clear();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
stopwatch.Start();
|
||||
stopwatch_last = stopwatch.Elapsed.TotalMilliseconds;
|
||||
}
|
||||
|
||||
public void Step()
|
||||
{
|
||||
float elapsedMilliseconds = (float)(stopwatch.Elapsed.TotalMilliseconds - stopwatch_last);
|
||||
stopwatch_last = stopwatch.Elapsed.TotalMilliseconds;
|
||||
|
||||
world.StepSimulation(elapsedMilliseconds / 1000.0f, 10);
|
||||
|
||||
// Detectar colisiones manualmente para sensores
|
||||
CheckCollisions();
|
||||
|
||||
foreach (var cuerpo in Cuerpos)
|
||||
{
|
||||
if (cuerpo is simBotella botella)
|
||||
{
|
||||
botella.ApplyConveyorSpeed(elapsedMilliseconds / 1000);
|
||||
if (botella.isRestricted)
|
||||
{
|
||||
botella.CenterFixtureOnConveyor();
|
||||
botella.Body.SetMassProps(100, Vector3.Zero);
|
||||
}
|
||||
else if (botella.isNoMoreRestricted)
|
||||
{
|
||||
botella.isNoMoreRestricted = false;
|
||||
botella.Body.SetMassProps(botella.OriginalMass, Vector3.Zero);
|
||||
}
|
||||
}
|
||||
else if (cuerpo is simBarrera barrera)
|
||||
barrera.CheckIfNecksIsTouching();
|
||||
}
|
||||
|
||||
foreach (var action in _deferredActions)
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
_deferredActions.Clear();
|
||||
}
|
||||
|
||||
private void CheckCollisions()
|
||||
{
|
||||
int numManifolds = world.Dispatcher.NumManifolds;
|
||||
|
||||
for (int i = 0; i < numManifolds; i++)
|
||||
{
|
||||
PersistentManifold contactManifold = world.Dispatcher.GetManifoldByIndexInternal(i);
|
||||
var obA = contactManifold.Body0;
|
||||
var obB = contactManifold.Body1;
|
||||
|
||||
var bodyA = RigidBody.Upcast(obA);
|
||||
var bodyB = RigidBody.Upcast(obB);
|
||||
|
||||
if (bodyA?.UserObject is simBotella botella && bodyB?.UserObject is simBase other)
|
||||
{
|
||||
HandleCollision(botella, other, bodyB);
|
||||
}
|
||||
else if (bodyB?.UserObject is simBotella botella2 && bodyA?.UserObject is simBase other2)
|
||||
{
|
||||
HandleCollision(botella2, other2, bodyA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCollision(simBotella botella, simBase other, RigidBody otherBody)
|
||||
{
|
||||
switch (other)
|
||||
{
|
||||
case simBarrera sensor:
|
||||
if (!sensor.ListSimBotellaContact.Contains(botella))
|
||||
{
|
||||
sensor.LuzCortada += 1;
|
||||
sensor.ListSimBotellaContact.Add(botella);
|
||||
}
|
||||
break;
|
||||
|
||||
case simCurve curve:
|
||||
if (!botella.ListOnTransports.Contains(curve))
|
||||
{
|
||||
botella.isOnTransports += 1;
|
||||
botella.ListOnTransports.Add(curve);
|
||||
}
|
||||
break;
|
||||
|
||||
case simDescarte:
|
||||
botella.Descartar = true;
|
||||
break;
|
||||
|
||||
case simTransporte conveyor:
|
||||
if (!botella.ListOnTransports.Contains(conveyor))
|
||||
{
|
||||
botella.isOnTransports += 1;
|
||||
botella.ListOnTransports.Add(conveyor);
|
||||
|
||||
if (conveyor.TransportWithGuides && conveyor.isBrake)
|
||||
{
|
||||
botella.ConveyorRestrictedTo = conveyor;
|
||||
botella.axisRestrictedBy = otherBody;
|
||||
botella.OriginalMass = botella.Body.Mass;
|
||||
botella.isRestricted = true;
|
||||
botella.isNoMoreRestricted = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(simBase Objeto)
|
||||
{
|
||||
if (Objeto != null)
|
||||
{
|
||||
Objeto.RemoverBody();
|
||||
Cuerpos.Remove(Objeto);
|
||||
}
|
||||
}
|
||||
|
||||
public simCurve AddCurve(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector3 position)
|
||||
{
|
||||
simCurve curva = new simCurve(world, _deferredActions, innerRadius, outerRadius, startAngle, endAngle, position);
|
||||
Cuerpos.Add(curva);
|
||||
return curva;
|
||||
}
|
||||
|
||||
public simBotella AddCircle(float diameter, Vector3 position, float mass)
|
||||
{
|
||||
simBotella circle = new simBotella(world, _deferredActions, diameter, position, mass);
|
||||
Cuerpos.Add(circle);
|
||||
return circle;
|
||||
}
|
||||
|
||||
public simTransporte AddRectangle(float width, float height, Vector3 position, float angle)
|
||||
{
|
||||
simTransporte rectangle = new simTransporte(world, _deferredActions, width, height, position, angle);
|
||||
Cuerpos.Add(rectangle);
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
public simBarrera AddBarrera(float width, float height, Vector3 position, float angle, bool detectarCuello)
|
||||
{
|
||||
simBarrera rectangle = new simBarrera(world, _deferredActions, width, height, position, angle, detectarCuello);
|
||||
Cuerpos.Add(rectangle);
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
public simGuia AddLine(Vector3 start, Vector3 end)
|
||||
{
|
||||
simGuia line = new simGuia(world, _deferredActions, start, end);
|
||||
Cuerpos.Add(line);
|
||||
return line;
|
||||
}
|
||||
|
||||
public simDescarte AddDescarte(float diameter, Vector3 position)
|
||||
{
|
||||
simDescarte descarte = new simDescarte(world, _deferredActions, diameter, position);
|
||||
Cuerpos.Add(descarte);
|
||||
return descarte;
|
||||
}
|
||||
|
||||
~SimulationManagerFP()
|
||||
{
|
||||
Clear();
|
||||
world?.Dispose();
|
||||
solver?.Dispose();
|
||||
broadphase?.Dispose();
|
||||
dispatcher?.Dispose();
|
||||
collisionConfiguration?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
102
MainViewModel.cs
102
MainViewModel.cs
|
@ -6,7 +6,7 @@ using Ookii.Dialogs.Wpf;
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Threading;
|
||||
using CtrEditor.ObjetosSim;
|
||||
using LibS7Adv;
|
||||
using LibS7Adv; // Using stub implementation
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using System.Windows;
|
||||
|
@ -26,6 +26,7 @@ using CtrEditor.Serialization; // Add this line
|
|||
using CtrEditor.Controls; // Add this using directive
|
||||
using CtrEditor.PopUps; // Add this using directive
|
||||
|
||||
|
||||
namespace CtrEditor
|
||||
{
|
||||
|
||||
|
@ -44,14 +45,18 @@ namespace CtrEditor
|
|||
private float TiempoDesdeStartSimulacion;
|
||||
private bool Debug_SimulacionCreado = false;
|
||||
|
||||
public SimulationManagerFP simulationManager = new SimulationManagerFP();
|
||||
public SimulationManagerBEPU simulationManager = new SimulationManagerBEPU();
|
||||
|
||||
private readonly DispatcherTimer _timerSimulacion;
|
||||
private readonly DispatcherTimer _timerPLCUpdate;
|
||||
private readonly DispatcherTimer _timerDisplayUpdate;
|
||||
private readonly DispatcherTimer _timer3DUpdate; // Nuevo timer para actualización 3D cuando simulación está detenida
|
||||
|
||||
public Canvas MainCanvas;
|
||||
|
||||
// Manager para la visualización 3D
|
||||
public BEPUVisualization3DManager Visualization3DManager { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isConnected;
|
||||
|
||||
|
@ -91,7 +96,6 @@ namespace CtrEditor
|
|||
public ICommand StopSimulationCommand { get; }
|
||||
public ICommand ItemDoubleClickCommand { get; private set; }
|
||||
|
||||
public SimulationFluidsViewModel FluidSimulation { get; private set; }
|
||||
|
||||
public ICommand TBStartSimulationCommand { get; }
|
||||
public ICommand TBStopSimulationCommand { get; }
|
||||
|
@ -114,6 +118,12 @@ namespace CtrEditor
|
|||
public ICommand TBMultiPageMatrixCommand { 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(() =>
|
||||
{
|
||||
|
@ -171,6 +181,18 @@ namespace CtrEditor
|
|||
partial void OnIsSimulationRunningChanged(bool value)
|
||||
{
|
||||
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)
|
||||
|
@ -367,16 +389,18 @@ namespace CtrEditor
|
|||
_timerDisplayUpdate.Tick += OnDisplayUpdate;
|
||||
_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);
|
||||
StopSimulationCommand = new RelayCommand(StopSimulation);
|
||||
|
||||
TBStartSimulationCommand = new RelayCommand(StartSimulation, () => !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);
|
||||
|
||||
TBEliminarUserControlCommand = new RelayCommand(EliminarUserControl, () => habilitarEliminarUserControl);
|
||||
|
@ -389,6 +413,13 @@ namespace CtrEditor
|
|||
TBMultiPageAnalizeCommand = new RelayCommand(MultiPageAnalizeCommand);
|
||||
TBMultiPageMatrixCommand = new RelayCommand(MultiPageMatrixCommand);
|
||||
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);
|
||||
|
||||
stopwatch_Sim = new Stopwatch();
|
||||
|
@ -408,6 +439,9 @@ namespace CtrEditor
|
|||
|
||||
// Conectar DatosDeTrabajo con este ViewModel para el escaneo de imágenes
|
||||
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
|
||||
|
@ -873,8 +907,6 @@ namespace CtrEditor
|
|||
|
||||
private void StartSimulation()
|
||||
{
|
||||
// Detener simulación de fluidos si está ejecutándose
|
||||
StopFluidSimulation();
|
||||
|
||||
IsSimulationRunning = true;
|
||||
|
||||
|
@ -884,7 +916,7 @@ namespace CtrEditor
|
|||
foreach (var objetoSimulable in ObjetosSimulables)
|
||||
objetoSimulable.UpdateGeometryStart();
|
||||
|
||||
simulationManager.Debug_DrawInitialBodies();
|
||||
|
||||
TiempoDesdeStartSimulacion = 0;
|
||||
Debug_SimulacionCreado = true;
|
||||
|
||||
|
@ -901,7 +933,6 @@ namespace CtrEditor
|
|||
|
||||
if (Debug_SimulacionCreado)
|
||||
{
|
||||
simulationManager.Debug_ClearSimulationShapes();
|
||||
Debug_SimulacionCreado = false;
|
||||
}
|
||||
_timerSimulacion.Stop();
|
||||
|
@ -913,26 +944,7 @@ namespace CtrEditor
|
|||
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)
|
||||
{
|
||||
|
@ -946,10 +958,8 @@ namespace CtrEditor
|
|||
accumulatedSimTime += elapsedMilliseconds;
|
||||
simSampleCount++;
|
||||
|
||||
// Eliminar el diseño de Debug luego de 2 segundos
|
||||
if (TiempoDesdeStartSimulacion > 12000)
|
||||
simulationManager.Debug_ClearSimulationShapes();
|
||||
else
|
||||
// Contador de tiempo desde el inicio de la simulación
|
||||
if (TiempoDesdeStartSimulacion <= 1200)
|
||||
TiempoDesdeStartSimulacion += (float)elapsedMilliseconds;
|
||||
|
||||
foreach (var objetoSimulable in ObjetosSimulables)
|
||||
|
@ -1186,6 +1196,20 @@ namespace CtrEditor
|
|||
[ObservableProperty]
|
||||
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)
|
||||
{
|
||||
if (simSampleCount > 0)
|
||||
|
@ -1265,7 +1289,6 @@ namespace CtrEditor
|
|||
{
|
||||
// Detener simulaciones antes de cambiar la escala
|
||||
StopSimulation();
|
||||
StopFluidSimulation();
|
||||
DisconnectPLC();
|
||||
|
||||
// Actualizar la escala en el UnitConverter
|
||||
|
@ -1338,6 +1361,15 @@ namespace CtrEditor
|
|||
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:sys="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:ObjetosSim="clr-namespace:CtrEditor.ObjetosSim"
|
||||
xmlns:helix="http://helix-toolkit.org/wpf"
|
||||
xmlns:ObjetosExtraccion="clr-namespace:CtrEditor.ObjetosSim.Extraccion_Datos"
|
||||
x:Class="CtrEditor.MainWindow"
|
||||
Height="900" Width="1600" WindowState="Maximized" ResizeMode="CanResize" Title="{Binding directorioTrabajo, Converter={StaticResource UnsavedChangesConverter}}"
|
||||
|
@ -122,7 +123,9 @@
|
|||
<Grid Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="3*" />
|
||||
<RowDefinition Height="5" />
|
||||
<RowDefinition Height="2*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ToolBarTray Grid.Row="0">
|
||||
|
@ -199,6 +202,36 @@
|
|||
</StackPanel>
|
||||
</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>
|
||||
</ToolBarTray>
|
||||
|
||||
|
@ -223,6 +256,35 @@
|
|||
</Canvas.RenderTransform>
|
||||
</Canvas>
|
||||
</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>
|
||||
|
||||
<!-- GridSplitter -->
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using CtrEditor.ObjetosSim;
|
||||
using CtrEditor.Simulacion;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
|
@ -45,6 +46,9 @@ namespace CtrEditor
|
|||
|
||||
private dataDebug dataDebug = new dataDebug();
|
||||
|
||||
// Manager para la visualización 3D
|
||||
private BEPUVisualization3DManager _visualization3DManager;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
@ -91,11 +95,18 @@ namespace CtrEditor
|
|||
viewModel.MainWindow = this;
|
||||
viewModel.ImageSelected += ViewModel_ImageSelected;
|
||||
viewModel?.LoadInitialData();
|
||||
viewModel.simulationManager.DebugCanvas = ImagenEnTrabajoCanvas;
|
||||
|
||||
viewModel.MainCanvas = ImagenEnTrabajoCanvas;
|
||||
|
||||
// Inicializar ObjectHierarchyView
|
||||
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"
|
||||
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 x:Class="CtrEditor.ObjetosSim.ucBotella" 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:osBotella/>
|
||||
<vm:osBotella />
|
||||
</UserControl.DataContext>
|
||||
|
||||
<Ellipse Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Stroke="{Binding ColorButton_oculto}" Fill="Gray"
|
||||
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}" StrokeThickness="0.5"/>
|
||||
Stroke="{Binding ColorButton_oculto}" Fill="Gray"
|
||||
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}" StrokeThickness="0.5" />
|
||||
|
||||
</UserControl>
|
|
@ -1,11 +1,9 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
//using using Microsoft.Xna.Framework;
|
||||
|
||||
using LibS7Adv;
|
||||
using CtrEditor.Simulacion;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using nkast.Aether.Physics2D.Common;
|
||||
using System.Numerics;
|
||||
using System.Windows.Media;
|
||||
using CtrEditor.FuncionesBase;
|
||||
using System.ComponentModel;
|
||||
|
@ -83,18 +81,18 @@ namespace CtrEditor.ObjetosSim
|
|||
[property: Name("Inercia de Simulación")]
|
||||
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]
|
||||
[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]
|
||||
[property: Category("Simulación")]
|
||||
[property: Description("Masa del objeto en kg")]
|
||||
|
@ -111,7 +109,7 @@ namespace CtrEditor.ObjetosSim
|
|||
}
|
||||
|
||||
public void SetCentro(float x, float y)
|
||||
{ Left = x; Top = y; }
|
||||
{ Left = x - Diametro / 2; Top = y - Diametro / 2; }
|
||||
|
||||
public void SetCentro(Vector2 centro)
|
||||
{
|
||||
|
@ -132,7 +130,6 @@ namespace CtrEditor.ObjetosSim
|
|||
Diametro = 0.10f;
|
||||
Mass = 1;
|
||||
ColorButton_oculto = Brushes.Gray;
|
||||
Preserve_Outside_Transport = true;
|
||||
}
|
||||
|
||||
public void UpdateAfterMove()
|
||||
|
@ -146,39 +143,52 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
public override void UpdateGeometryStart()
|
||||
{
|
||||
// Se llama antes de la simulacion
|
||||
ActualizarGeometrias();
|
||||
SimGeometria?.SetDiameter(Diametro);
|
||||
// Se llama cuando inicia la simulación - crear geometría si no existe
|
||||
if (SimGeometria == null)
|
||||
{
|
||||
SimGeometria = simulationManager.AddCircle(Diametro, GetCentro(), Mass);
|
||||
}
|
||||
else
|
||||
{
|
||||
ActualizarGeometrias();
|
||||
SimGeometria?.SetDiameter(Diametro);
|
||||
SimGeometria?.SetMass(Mass);
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateGeometryStep()
|
||||
{
|
||||
// Se llama antes de la simulacion
|
||||
ActualizarGeometrias();
|
||||
// Se llama durante cada paso de la simulación
|
||||
if (SimGeometria != null)
|
||||
{
|
||||
// Actualizar posición desde la simulación hacia WPF
|
||||
SetCentro(SimGeometria.Center);
|
||||
}
|
||||
}
|
||||
public override void UpdateControl(int elapsedMilliseconds)
|
||||
{
|
||||
SetCentro(SimGeometria.Center);
|
||||
|
||||
// Sistema de colores jerarquizado para diferentes estados
|
||||
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
|
||||
{
|
||||
if (SimGeometria.IsOnAnyTransport())
|
||||
ColorButton_oculto = Brushes.Red;
|
||||
else
|
||||
ColorButton_oculto = Brushes.Gray;
|
||||
}
|
||||
ColorButton_oculto = Brushes.Gray; // Estado libre
|
||||
|
||||
// Ha sido marcada para remover
|
||||
if (SimGeometria.Descartar)
|
||||
RemoverDesdeSimulacion = true;
|
||||
|
||||
// Eliminar la botella si esta fuera de un transporte
|
||||
if (!Preserve_Outside_Transport && !SimGeometria.IsOnAnyTransport())
|
||||
RemoverDesdeSimulacion = true;
|
||||
// ✅ ELIMINADO: Lógica redundante - BEPU.cs ya elimina botellas que caen en Z automáticamente
|
||||
|
||||
Velocidad_desde_simulacion = SimGeometria.Body.LinearVelocity.ToString();
|
||||
Inercia_desde_simulacion = SimGeometria.Body.Inertia;
|
||||
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()
|
||||
|
|
|
@ -5,7 +5,7 @@ using System.Windows.Controls;
|
|||
using LibS7Adv;
|
||||
using CtrEditor.Simulacion;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using nkast.Aether.Physics2D.Common;
|
||||
using System.Numerics;
|
||||
using System.Windows.Media;
|
||||
using CtrEditor.FuncionesBase;
|
||||
using System.ComponentModel;
|
||||
|
@ -80,6 +80,18 @@ namespace CtrEditor.ObjetosSim
|
|||
[property: Name("Inercia Simulación")]
|
||||
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]
|
||||
[property: Category("Simulación")]
|
||||
[property: Description("Masa de la botella")]
|
||||
|
@ -140,19 +152,27 @@ namespace CtrEditor.ObjetosSim
|
|||
public override void UpdateControl(int elapsedMilliseconds)
|
||||
{
|
||||
SetCentro(SimGeometria.Center);
|
||||
|
||||
// Sistema de colores jerarquizado para diferentes estados
|
||||
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
|
||||
{
|
||||
if (SimGeometria.isOnTransports > 0)
|
||||
ColorButton_oculto = Brushes.Red;
|
||||
else
|
||||
ColorButton_oculto = Brushes.Gray;
|
||||
}
|
||||
if (SimGeometria.Descartar) // Ha sido marcada para remover
|
||||
ColorButton_oculto = Brushes.Gray; // Estado libre
|
||||
|
||||
// Ha sido marcada para remover
|
||||
if (SimGeometria.Descartar)
|
||||
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()
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
<UserControl x:Class="CtrEditor.ObjetosSim.ucBottGenerator"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim">
|
||||
|
||||
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" mc:Ignorable="d"
|
||||
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim">
|
||||
|
||||
<UserControl.DataContext>
|
||||
<vm:osFiller/>
|
||||
<vm:osBottGenerator />
|
||||
</UserControl.DataContext>
|
||||
|
||||
<Grid>
|
||||
<Image Source="/imagenes/gear.png"
|
||||
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Stretch="Uniform">
|
||||
<Image Source="/imagenes/gear.png" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}" Stretch="Uniform">
|
||||
<Image.RenderTransform>
|
||||
<RotateTransform Angle="{Binding Angulo}" />
|
||||
</Image.RenderTransform>
|
||||
|
|
|
@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||
using System.Diagnostics;
|
||||
using CtrEditor.FuncionesBase;
|
||||
using System.ComponentModel;
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
|
||||
namespace CtrEditor.ObjetosSim
|
||||
{
|
||||
|
@ -39,11 +40,8 @@ namespace CtrEditor.ObjetosSim
|
|||
[property: Name("Offset Vertical")]
|
||||
private float offsetTopSalida;
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Description("Las botellas se destruirán si caen fuera del transporte")]
|
||||
[property: Category("Configuración")]
|
||||
[property: Name("Conservar Fuera de Transporte")]
|
||||
private bool preserve_Outside_Transport;
|
||||
// ✅ ELIMINADO: preserve_Outside_Transport - Redundante con ProcessCleanupSystem de BEPU
|
||||
// Las botellas que caen en Z son eliminadas automáticamente por BEPU.cs
|
||||
[ObservableProperty]
|
||||
[property: Description("Tag PLC para habilitar funcionamiento. 1 => siempre activo")]
|
||||
[property: Category("Enlace PLC")]
|
||||
|
@ -184,7 +182,7 @@ namespace CtrEditor.ObjetosSim
|
|||
{
|
||||
var nuevaBotella = _mainViewModel.CrearObjetoSimulable(typeof(osBotella), X, Y);
|
||||
((osBotella)nuevaBotella).Diametro = Diametro_botella;
|
||||
((osBotella)nuevaBotella).Preserve_Outside_Transport = Preserve_Outside_Transport;
|
||||
// ✅ ELIMINADO: Preserve_Outside_Transport - Ya no es necesario
|
||||
nuevaBotella.AutoCreated = true;
|
||||
|
||||
// Recalcular el tiempo entre botellas por si cambió la velocidad
|
||||
|
|
|
@ -1,42 +1,31 @@
|
|||
<UserControl x:Class="CtrEditor.ObjetosSim.ucDescarte"
|
||||
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:local="clr-namespace:CtrEditor.ObjetosSim"
|
||||
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl x:Class="CtrEditor.ObjetosSim.ucDescarte" 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:local="clr-namespace:CtrEditor.ObjetosSim"
|
||||
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim" mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<Storyboard x:Key="PulsingStoryboard" RepeatBehavior="Forever">
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="AnimatedEllipse"
|
||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
|
||||
From="1" To="0.5" Duration="0:0:1" AutoReverse="True" />
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="AnimatedEllipse"
|
||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
|
||||
From="1" To="0.5" Duration="0:0:1" AutoReverse="True" />
|
||||
<DoubleAnimation Storyboard.TargetName="AnimatedEllipse"
|
||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
|
||||
From="1" To="0.5" Duration="0:0:1" AutoReverse="True" />
|
||||
<DoubleAnimation Storyboard.TargetName="AnimatedEllipse"
|
||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
|
||||
From="1" To="0.5" Duration="0:0:1" AutoReverse="True" />
|
||||
</Storyboard>
|
||||
</UserControl.Resources>
|
||||
|
||||
<UserControl.DataContext>
|
||||
<vm:osDescarte/>
|
||||
<vm:osDescarte />
|
||||
</UserControl.DataContext>
|
||||
|
||||
<Grid>
|
||||
<Ellipse
|
||||
Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Stroke="Yellow"
|
||||
Fill="Black"
|
||||
Opacity="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 Height="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Width="{Binding Diametro, Converter={StaticResource MeterToPixelConverter}}" Stroke="Yellow"
|
||||
Fill="Black" Opacity="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>
|
||||
<TransformGroup>
|
||||
<ScaleTransform />
|
||||
|
|
|
@ -3,11 +3,11 @@ using LibS7Adv;
|
|||
using CtrEditor.Simulacion;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using nkast.Aether.Physics2D.Common;
|
||||
using System.Windows.Media.Animation;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CtrEditor.FuncionesBase;
|
||||
using System.ComponentModel;
|
||||
using System.Numerics;
|
||||
namespace CtrEditor.ObjetosSim
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -41,7 +41,7 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
partial void OnDiametroChanged(float value)
|
||||
{
|
||||
SimGeometria?.SetDiameter(Diametro);
|
||||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
public Vector2 GetCentro()
|
||||
|
@ -73,8 +73,15 @@ namespace CtrEditor.ObjetosSim
|
|||
{
|
||||
if (SimGeometria != null)
|
||||
{
|
||||
SimGeometria.SetDiameter(Diametro);
|
||||
SimGeometria.SetPosition(GetCentro());
|
||||
// ✅ SISTEMA INTELIGENTE: Solo recrear si el diámetro cambió
|
||||
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.FuncionesBase;
|
||||
using System.ComponentModel;
|
||||
using System.Numerics;
|
||||
|
||||
namespace CtrEditor.ObjetosSim
|
||||
{
|
||||
|
@ -32,14 +33,35 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
[ObservableProperty]
|
||||
[property: Category("Configuración")]
|
||||
[property: Description("Alto de la guía en metros")]
|
||||
[property: Name("Alto de la Guía")]
|
||||
[property: Description("Grosor de la guía en metros")]
|
||||
[property: Name("Grosor de la Guía")]
|
||||
public float altoGuia;
|
||||
|
||||
|
||||
|
||||
private void ActualizarGeometrias()
|
||||
{
|
||||
if (_visualRepresentation is ucGuia uc)
|
||||
UpdateOrCreateLine(SimGeometria, uc.Guia);
|
||||
if (_visualRepresentation is ucGuia uc && SimGeometria != null)
|
||||
{
|
||||
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()
|
||||
|
@ -81,7 +103,21 @@ namespace CtrEditor.ObjetosSim
|
|||
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
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<UserControl x:Class="CtrEditor.ObjetosSim.ucTransporteCurva"
|
||||
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:localuc="clr-namespace:CtrEditor.ObjetosSim.UserControls"
|
||||
xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
||||
mc:Ignorable="d">
|
||||
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:localuc="clr-namespace:CtrEditor.ObjetosSim.UserControls" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.DataContext>
|
||||
<vm:osTransporteCurva />
|
||||
|
@ -13,7 +12,9 @@
|
|||
|
||||
<Canvas x:Name="MainCanvas">
|
||||
|
||||
<localuc:CircularSegment x:Name="Transporte" Angle="0" OuterRadius="{Binding RadioExterno, Converter={StaticResource MeterToPixelConverter}}" InnerRadius="{Binding RadioInterno, Converter={StaticResource MeterToPixelConverter}}"
|
||||
StartAngle="{Binding Angulo}" EndAngle="{Binding AnguloFinal}" />
|
||||
<localuc:CircularSegment x:Name="Transporte" Angle="0"
|
||||
OuterRadius="{Binding RadioExterno, Converter={StaticResource MeterToPixelConverter}}"
|
||||
InnerRadius="{Binding RadioInterno, Converter={StaticResource MeterToPixelConverter}}"
|
||||
StartAngle="{Binding Angulo}" EndAngle="{Binding AnguloFinal}" />
|
||||
</Canvas>
|
||||
</UserControl>
|
||||
|
|
|
@ -7,6 +7,7 @@ using CtrEditor.Simulacion;
|
|||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||
using CtrEditor.FuncionesBase;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Numerics;
|
||||
|
||||
namespace CtrEditor.ObjetosSim
|
||||
{
|
||||
|
@ -93,6 +94,50 @@ namespace CtrEditor.ObjetosSim
|
|||
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]
|
||||
[property: Category("Configuración")]
|
||||
[property: Description("Radio interior de la curva en metros")]
|
||||
|
@ -140,6 +185,7 @@ namespace CtrEditor.ObjetosSim
|
|||
public override void AnguloChanged(float value)
|
||||
{
|
||||
OnPropertyChanged(nameof(AnguloFinal));
|
||||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
|
@ -156,7 +202,15 @@ namespace CtrEditor.ObjetosSim
|
|||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -209,6 +263,9 @@ namespace CtrEditor.ObjetosSim
|
|||
// Ensure radioInterno is always less than radioExterno
|
||||
if (RadioInterno >= RadioExterno)
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
|||
using CtrEditor.FuncionesBase;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Windows.Media;
|
||||
using System.Numerics;
|
||||
|
||||
namespace CtrEditor.ObjetosSim
|
||||
{
|
||||
|
@ -239,9 +240,42 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
partial void OnArco_en_gradosChanged(float value)
|
||||
{
|
||||
OnPropertyChanged(nameof(AnguloFinal));
|
||||
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]
|
||||
public float AnguloFinal
|
||||
{
|
||||
|
@ -252,7 +286,12 @@ namespace CtrEditor.ObjetosSim
|
|||
{
|
||||
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();
|
||||
SetSpeed();
|
||||
}
|
||||
|
@ -293,7 +332,7 @@ namespace CtrEditor.ObjetosSim
|
|||
if (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 anguloFinalRad = simBase.GradosARadianes(AnguloFinal);
|
||||
float rangoAngular = anguloFinalRad - anguloInicioRad;
|
||||
|
@ -302,7 +341,7 @@ namespace CtrEditor.ObjetosSim
|
|||
float pasoAngular = rangoAngular / NumeroSegmentosGuias;
|
||||
|
||||
// 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)
|
||||
for (int i = 0; i < NumeroSegmentosGuias; i++)
|
||||
|
@ -310,12 +349,12 @@ namespace CtrEditor.ObjetosSim
|
|||
float angulo1 = anguloInicioRad + i * pasoAngular;
|
||||
float angulo2 = anguloInicioRad + (i + 1) * pasoAngular;
|
||||
|
||||
nkast.Aether.Physics2D.Common.Vector2 punto1 = new nkast.Aether.Physics2D.Common.Vector2(
|
||||
Vector2 punto1 = new Vector2(
|
||||
radioGuiaSuperior * (float)Math.Cos(angulo1),
|
||||
radioGuiaSuperior * (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),
|
||||
radioGuiaSuperior * (float)Math.Sin(angulo2)
|
||||
);
|
||||
|
@ -324,8 +363,9 @@ namespace CtrEditor.ObjetosSim
|
|||
punto1 += centro;
|
||||
punto2 += centro;
|
||||
|
||||
simGuia guiaSegmento = simulationManager.AddLine(punto1, punto2);
|
||||
GuiasSuperiores.Add(guiaSegmento);
|
||||
simGuia guiaSegmento = CrearGuiaDesdeDosPuntos(punto1, punto2);
|
||||
if (guiaSegmento != null)
|
||||
GuiasSuperiores.Add(guiaSegmento);
|
||||
}
|
||||
|
||||
// Crear segmentos para guía inferior (interna)
|
||||
|
@ -334,12 +374,12 @@ namespace CtrEditor.ObjetosSim
|
|||
float angulo1 = anguloInicioRad + i * pasoAngular;
|
||||
float angulo2 = anguloInicioRad + (i + 1) * pasoAngular;
|
||||
|
||||
nkast.Aether.Physics2D.Common.Vector2 punto1 = new nkast.Aether.Physics2D.Common.Vector2(
|
||||
Vector2 punto1 = new Vector2(
|
||||
radioGuiaInferior * (float)Math.Cos(angulo1),
|
||||
radioGuiaInferior * (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),
|
||||
radioGuiaInferior * (float)Math.Sin(angulo2)
|
||||
);
|
||||
|
@ -348,8 +388,37 @@ namespace CtrEditor.ObjetosSim
|
|||
punto1 += centro;
|
||||
punto2 += centro;
|
||||
|
||||
simGuia guiaSegmento = simulationManager.AddLine(punto1, punto2);
|
||||
GuiasInferiores.Add(guiaSegmento);
|
||||
simGuia guiaSegmento = CrearGuiaDesdeDosPuntos(punto1, punto2);
|
||||
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 +485,9 @@ namespace CtrEditor.ObjetosSim
|
|||
// Ensure radioInterno is always less than radioExterno
|
||||
if (RadioInterno >= RadioExterno)
|
||||
RadioInterno = RadioExterno * 0.75f;
|
||||
|
||||
// ✅ NUEVO: Actualizar geometrías en BEPU después del redimensionamiento
|
||||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
public osTransporteCurvaGuias()
|
||||
|
@ -465,7 +537,9 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
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
|
||||
CrearAnimacionStoryBoardTrasnporteCircular(uc.Transporte.TransportePath, InvertirDireccion, Angulo);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<UserControl x:Class="CtrEditor.ObjetosSim.ucTransporteGuias"
|
||||
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:vm="clr-namespace:CtrEditor.ObjetosSim"
|
||||
mc:Ignorable="d">
|
||||
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:vm="clr-namespace:CtrEditor.ObjetosSim"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<!-- 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>
|
||||
<TransformGroup>
|
||||
<TranslateTransform/>
|
||||
<TranslateTransform />
|
||||
</TransformGroup>
|
||||
</VisualBrush.Transform>
|
||||
<VisualBrush.Visual>
|
||||
|
@ -24,41 +24,42 @@
|
|||
</UserControl.Resources>
|
||||
|
||||
<UserControl.DataContext>
|
||||
<vm:osTransporteGuias/>
|
||||
<vm:osTransporteGuias />
|
||||
</UserControl.DataContext>
|
||||
|
||||
<Grid>
|
||||
<Canvas RenderTransformOrigin="0,0">
|
||||
<Canvas.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform/>
|
||||
<SkewTransform/>
|
||||
<RotateTransform Angle="{Binding Angulo}"/>
|
||||
<TranslateTransform/>
|
||||
<ScaleTransform />
|
||||
<SkewTransform />
|
||||
<RotateTransform Angle="{Binding Angulo}" />
|
||||
<TranslateTransform />
|
||||
</TransformGroup>
|
||||
</Canvas.RenderTransform>
|
||||
<StackPanel x:Name="RectanglesContainer">
|
||||
<Rectangle x:Name="GuiaSuperior" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Height="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Margin="{Binding Distance, Converter={StaticResource DistanceToMarginConverter}}"/>
|
||||
<Rectangle x:Name="Transporte" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Margin="{Binding Distance, Converter={StaticResource DistanceToMarginConverter}}"
|
||||
Fill="{StaticResource BeltBrush}"/>
|
||||
<Rectangle x:Name="GuiaInferior" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Height="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
<Rectangle x:Name="GuiaSuperior"
|
||||
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Height="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Margin="{Binding Distance, Converter={StaticResource DistanceToMarginConverter}}" />
|
||||
<Rectangle x:Name="Transporte" Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Margin="{Binding Distance, Converter={StaticResource DistanceToMarginConverter}}"
|
||||
Fill="{StaticResource BeltBrush}" />
|
||||
<Rectangle x:Name="GuiaInferior"
|
||||
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Height="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}" />
|
||||
</StackPanel>
|
||||
|
||||
<Viewbox Canvas.Top="{Binding AltoGuia, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Stretch="Uniform">
|
||||
<Label Content="{Binding Nombre}" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" FontSize="18" Opacity="0.9"/>
|
||||
Width="{Binding Ancho, Converter={StaticResource MeterToPixelConverter}}"
|
||||
Height="{Binding Alto, Converter={StaticResource MeterToPixelConverter}}" Stretch="Uniform">
|
||||
<Label Content="{Binding Nombre}" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
FontWeight="Bold" FontSize="18" Opacity="0.9" />
|
||||
</Viewbox>
|
||||
</Canvas>
|
||||
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
@ -8,6 +9,7 @@ using CtrEditor.Simulacion;
|
|||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||
using CtrEditor.FuncionesBase;
|
||||
using System.Text.Json.Serialization;
|
||||
using Siemens.Simatic.Simulation.Runtime;
|
||||
|
||||
namespace CtrEditor.ObjetosSim
|
||||
{
|
||||
|
@ -124,6 +126,16 @@ namespace CtrEditor.ObjetosSim
|
|||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
public override void AnchoChanged(float value)
|
||||
{
|
||||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
public override void AnguloChanged(float value)
|
||||
{
|
||||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Category("Configuración")]
|
||||
[property: Description("Actuar como freno")]
|
||||
|
@ -132,8 +144,14 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
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)
|
||||
SimGeometria.isBrake = value;
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[✅ FRENO SYNC] SimGeometria.isBrake = {SimGeometria.isBrake}");
|
||||
}
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
|
@ -142,6 +160,11 @@ namespace CtrEditor.ObjetosSim
|
|||
[property: Name("Coeficiente Fricción")]
|
||||
public float frictionCoefficient;
|
||||
|
||||
partial void OnFrictionCoefficientChanged(float value)
|
||||
{
|
||||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Category("Configuración")]
|
||||
[property: Description("Velocidad máxima a 50Hz")]
|
||||
|
@ -160,32 +183,132 @@ namespace CtrEditor.ObjetosSim
|
|||
[property: Name("En Marcha")]
|
||||
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]
|
||||
[property: Category("Configuración")]
|
||||
[property: Description("Distancia entre guías")]
|
||||
[property: Name("Distancia")]
|
||||
private float distance;
|
||||
|
||||
partial void OnDistanceChanged(float value)
|
||||
{
|
||||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Category("Configuración")]
|
||||
[property: Description("Alto de las guías")]
|
||||
[property: Name("Alto Guía")]
|
||||
private float altoGuia;
|
||||
|
||||
partial void OnAltoGuiaChanged(float value)
|
||||
{
|
||||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
private void ActualizarGeometrias()
|
||||
{
|
||||
if (_visualRepresentation is ucTransporteGuias uc)
|
||||
{
|
||||
UpdateRectangle(SimGeometria, uc.Transporte, Alto, Ancho, Angulo);
|
||||
UpdateOrCreateLine(Guia_Superior, uc.GuiaSuperior);
|
||||
UpdateOrCreateLine(Guia_Inferior, uc.GuiaInferior);
|
||||
|
||||
SimGeometria.DistanceGuide2Guide = Alto;
|
||||
SimGeometria.isBrake = esFreno;
|
||||
// Actualizar guías con método específico para transporte
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
ActualizarGeometrias();
|
||||
|
@ -196,7 +319,7 @@ namespace CtrEditor.ObjetosSim
|
|||
Ancho = 1;
|
||||
Alto = 0.10f;
|
||||
AltoGuia = 0.03f;
|
||||
Distance = 0.01f;
|
||||
Distance = 0.5f;
|
||||
Tag_ReleActivatedMotor = "1";
|
||||
}
|
||||
|
||||
|
@ -220,6 +343,12 @@ namespace CtrEditor.ObjetosSim
|
|||
VelocidadActual = id_motor.Velocidad;
|
||||
else
|
||||
VelocidadActual = 0;
|
||||
|
||||
// Actualizar información de botellas en tiempo real si es un transporte con freno
|
||||
if (EsFreno)
|
||||
{
|
||||
ActualizarInfoBotellasFreno();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ucLoaded()
|
||||
|
@ -233,12 +362,22 @@ namespace CtrEditor.ObjetosSim
|
|||
if (_visualRepresentation is ucTransporteGuias uc)
|
||||
{
|
||||
SimGeometria = AddRectangle(simulationManager, uc.Transporte, Alto, Ancho, Angulo);
|
||||
SimGeometria.TransportWithGuides = true;
|
||||
SimGeometria.DistanceGuide2Guide = Alto;
|
||||
Guia_Superior = AddLine(simulationManager, uc.GuiaSuperior);
|
||||
Guia_Inferior = AddLine(simulationManager, uc.GuiaInferior);
|
||||
|
||||
// CORREGIR: Asegurar que todas las propiedades se inicialicen correctamente
|
||||
if (SimGeometria != null)
|
||||
{
|
||||
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);
|
||||
SetSpeed(); // Aplicar velocidad inicial
|
||||
}
|
||||
OnId_MotorChanged(Id_Motor); // Link Id_Motor = Motor
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
|||
using System.ComponentModel;
|
||||
using CtrEditor.FuncionesBase;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Numerics;
|
||||
|
||||
namespace CtrEditor.ObjetosSim
|
||||
{
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
|
||||
<Grid Width="50" Height="50">
|
||||
<Ellipse Fill="{Binding Color_oculto}" Stroke="DarkGray" StrokeThickness="2" Width="30" Height="30" />
|
||||
|
||||
<Label Content="{Binding Valor_Actual}" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="8" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using CtrEditor.Simulacion;
|
||||
using LibS7Adv;
|
||||
using LibS7Adv; // Using stub implementation
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
@ -9,6 +9,7 @@ using CtrEditor.FuncionesBase;
|
|||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||
using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute;
|
||||
using System.ComponentModel;
|
||||
using System.Numerics;
|
||||
|
||||
namespace CtrEditor.ObjetosSim
|
||||
{
|
||||
|
@ -34,7 +35,14 @@ namespace CtrEditor.ObjetosSim
|
|||
public override string 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]
|
||||
|
@ -86,12 +94,6 @@ namespace CtrEditor.ObjetosSim
|
|||
[property: Name("Ancho del Haz")]
|
||||
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]
|
||||
[property: Description("Tipo de detección: cuello de botella o botella completa")]
|
||||
[property: Category("Configuración")]
|
||||
|
@ -102,7 +104,7 @@ namespace CtrEditor.ObjetosSim
|
|||
{
|
||||
if (Simulation_Photocell == null) return;
|
||||
|
||||
Simulation_Photocell.DetectNeck = value;
|
||||
simulationManager.SetBarreraDetectNeck(Simulation_Photocell, value);
|
||||
}
|
||||
|
||||
|
||||
|
@ -200,6 +202,10 @@ namespace CtrEditor.ObjetosSim
|
|||
Frecuency = 0;
|
||||
timer = new Stopwatch();
|
||||
timer.Start();
|
||||
Color = Brushes.Black;
|
||||
|
||||
// Forzar notificación de la propiedad Nombre para asegurar binding inicial
|
||||
OnPropertyChanged(nameof(Nombre));
|
||||
}
|
||||
|
||||
public override void UpdateGeometryStart()
|
||||
|
@ -209,11 +215,14 @@ namespace CtrEditor.ObjetosSim
|
|||
}
|
||||
public override void UpdateControl(int elapsedMilliseconds)
|
||||
{
|
||||
Distancia_cuello = Simulation_Photocell.Distancia;
|
||||
if (Simulation_Photocell == null) return;
|
||||
|
||||
var barreraData = simulationManager.GetBarreraData(Simulation_Photocell);
|
||||
|
||||
if (DetectarCuello)
|
||||
LuzCortada = Simulation_Photocell.LuzCortadaNeck;
|
||||
LuzCortada = barreraData.LuzCortadaNeck;
|
||||
else
|
||||
LuzCortada = Simulation_Photocell.LuzCortada > 0;
|
||||
LuzCortada = barreraData.LuzCortada;
|
||||
}
|
||||
public override void UpdatePLCPrimerCiclo()
|
||||
{
|
||||
|
@ -232,13 +241,17 @@ namespace CtrEditor.ObjetosSim
|
|||
base.ucLoaded();
|
||||
|
||||
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()
|
||||
{
|
||||
// El UserControl se esta eliminando
|
||||
// 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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -1,30 +1,29 @@
|
|||
<UserControl x:Class="CtrEditor.ObjetosSim.UserControls.CircularSegment"
|
||||
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:local="clr-namespace:CtrEditor.ObjetosSim.UserControls"
|
||||
mc:Ignorable="d" Name="circularSegmentControl">
|
||||
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:local="clr-namespace:CtrEditor.ObjetosSim.UserControls" mc:Ignorable="d" Name="circularSegmentControl">
|
||||
<Canvas Name="mainCanvas">
|
||||
<Path Name="path" Stroke="Black" StrokeThickness="1">
|
||||
<Path.Fill>
|
||||
<VisualBrush TileMode="Tile" Viewport="0,0,20,20" ViewportUnits="Absolute">
|
||||
<VisualBrush.Transform>
|
||||
<TransformGroup>
|
||||
<TranslateTransform X="0" Y="0"/>
|
||||
<TranslateTransform X="0" Y="0" />
|
||||
</TransformGroup>
|
||||
</VisualBrush.Transform>
|
||||
<VisualBrush.Visual>
|
||||
<Canvas Width="20" Height="20">
|
||||
<!-- 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="GhostWhite" Width="10" Height="10" Canvas.Left="0" Canvas.Top="10"/>
|
||||
<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" />
|
||||
</Canvas>
|
||||
</VisualBrush.Visual>
|
||||
</VisualBrush>
|
||||
</Path.Fill>
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="{Binding Angle, ElementName=circularSegmentControl}"/>
|
||||
<RotateTransform Angle="{Binding Angle, ElementName=circularSegmentControl}" />
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Canvas>
|
||||
|
|
|
@ -4,7 +4,7 @@ using CtrEditor.Serialization;
|
|||
using CtrEditor.Services;
|
||||
using CtrEditor.Simulacion;
|
||||
using LibS7Adv;
|
||||
using nkast.Aether.Physics2D.Common;
|
||||
using System.Numerics;
|
||||
using PaddleOCRSharp;
|
||||
using Siemens.Simatic.Simulation.Runtime;
|
||||
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.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Tesseract;
|
||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||
using Application = System.Windows.Application;
|
||||
using ItemCollection = Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection;
|
||||
|
@ -44,16 +43,16 @@ namespace CtrEditor.ObjetosSim
|
|||
{
|
||||
private MainViewModel? _mainViewModel;
|
||||
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;
|
||||
VisualRepresentation = b;
|
||||
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;
|
||||
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 virtual string Nombre { get; set; } = "osBase";
|
||||
|
@ -71,6 +89,11 @@ namespace CtrEditor.ObjetosSim
|
|||
[JsonIgnore]
|
||||
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]
|
||||
[property: JsonIgnore]
|
||||
[property: Hidden]
|
||||
|
@ -113,6 +136,7 @@ namespace CtrEditor.ObjetosSim
|
|||
// Actualizar posición relativa si el movimiento no viene del FramePlate
|
||||
UpdateFramePlateRelativePosition();
|
||||
}
|
||||
|
||||
public virtual void LeftChanging(float oldValue, float newValue) { }
|
||||
|
||||
[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
|
||||
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")]
|
||||
private bool enable_On_All_Pages;
|
||||
|
||||
|
||||
partial void OnEnable_On_All_PagesChanged(bool value)
|
||||
{
|
||||
// Si se está desactivando el modo global
|
||||
|
@ -785,7 +725,6 @@ namespace CtrEditor.ObjetosSim
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Local Data for Global Objects
|
||||
[NotifyPropertyChangedFor(nameof(Show_On_This_Page))]
|
||||
[ObservableProperty]
|
||||
|
@ -1050,6 +989,7 @@ namespace CtrEditor.ObjetosSim
|
|||
/// <param name="elapsedMilliseconds"></param>
|
||||
public virtual void UpdateControl(int elapsedMilliseconds) { }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
|
@ -1111,7 +1051,7 @@ namespace CtrEditor.ObjetosSim
|
|||
/// Link al Simualdor fisico.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public SimulationManagerFP simulationManager;
|
||||
public SimulationManagerBEPU simulationManager;
|
||||
|
||||
/// <summary>
|
||||
/// 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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
simRect.Create(Ancho, Alto, GetRectangleCenter(wpfRect), Angulo);
|
||||
if (barrera != null)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
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);
|
||||
var topLeft2D = GetRectangleTopLeft(wpfRect);
|
||||
// Convertir Vector2 a Vector3 para BEPU (Z = 0 para objetos 2D)
|
||||
return simulationManager.CreateBarrera(Ancho, Alto, topLeft2D, Angulo, detectarCuello);
|
||||
}
|
||||
|
||||
public void UpdateOrCreateLine(simGuia simGuia, Rectangle wpfRect)
|
||||
{
|
||||
if (simGuia != null)
|
||||
{
|
||||
// Usar el mismo sistema que transportes: Top-Left + Ángulo
|
||||
var topLeft2D = GetRectangleTopLeft(wpfRect);
|
||||
|
||||
var coords = GetCenterLineVectors(wpfRect);
|
||||
|
||||
// Crear o actualizar simRectangle
|
||||
simGuia.Create(coords.Start, coords.End); // asumiendo que el ángulo inicial es 0
|
||||
// Actualizar las propiedades desde el objeto osGuia
|
||||
if (this is osGuia guiaObj)
|
||||
{
|
||||
// ✅ 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);
|
||||
return simulationManager.AddLine(coords.Start, coords.End);
|
||||
// Usar el mismo sistema que transportes: Top-Left + Ángulo
|
||||
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)
|
||||
|
@ -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 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 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;
|
||||
_datosDeTrabajo = datosDeTrabajo;
|
||||
|
|
1052
Simulacion/Aether.cs
1052
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,142 @@
|
|||
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 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,553 @@
|
|||
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;
|
||||
|
||||
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 SISTEMA SIMPLIFICADO: Motor dinámico que se crea solo cuando es necesario
|
||||
public ConstraintHandle CurrentMotor { get; private set; } = default;
|
||||
public ConstraintHandle CurrentDistanceLimit { get; private set; } = default; // ✅ NUEVO: Para curvas
|
||||
public simBase CurrentMotorTarget { get; private set; } = null; // Transporte o curva actual
|
||||
public bool _hasMotor = false; // ✅ BANDERA PÚBLICA para acceso desde callbacks
|
||||
public bool HasMotor => _hasMotor;
|
||||
|
||||
// ✅ NUEVAS PROPIEDADES para el motor dinámico
|
||||
public Vector3 CurrentDirection { get; private set; } = Vector3.UnitX; // Dirección por defecto (1,0,0)
|
||||
public float CurrentSpeed { get; private set; } = 0f; // Velocidad por defecto
|
||||
public bool IsOnElement { get; private set; } = false; // Si está sobre un elemento activo
|
||||
|
||||
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);
|
||||
|
||||
// ✅ ELIMINADO: No crear motor permanente - se creará cuando sea necesario
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
// Usar esfera simple - sin complejidad de inercia personalizada
|
||||
var sphere = new Sphere(Radius);
|
||||
var inertia = sphere.ComputeInertia(value);
|
||||
_simulation.Bodies.SetLocalInertia(BodyHandle, inertia);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Create(Vector3 position)
|
||||
{
|
||||
RemoverBody();
|
||||
|
||||
// Usar ESFERA en BEPU para simplicidad matemática y eficiencia
|
||||
var sphere = new Sphere(Radius);
|
||||
var shapeIndex = _simulation.Shapes.Add(sphere);
|
||||
|
||||
// Inercia estándar de esfera - sin complejidad adicional
|
||||
var inertia = sphere.ComputeInertia(_mass);
|
||||
|
||||
// NUNCA DORMIR - Valor negativo significa que las botellas JAMÁS entran en sleep mode
|
||||
// Esto es crítico para detección continua de barreras, transportes y descartes
|
||||
var activityDescription = new BodyActivityDescription(-1f); // -1 = NUNCA dormir
|
||||
|
||||
var bodyDescription = BodyDescription.CreateDynamic(
|
||||
new RigidPose(position),
|
||||
new BodyVelocity(),
|
||||
inertia, // Inercia estándar de esfera
|
||||
new CollidableDescription(shapeIndex, 0.001f),
|
||||
activityDescription
|
||||
);
|
||||
|
||||
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||
_bodyCreated = true; // Marcar que hemos creado un cuerpo
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Crea o actualiza el motor conectándolo al transporte/curva actual
|
||||
/// </summary>
|
||||
public void CreateOrUpdateMotor(simBase target, Vector3 direction, float speed)
|
||||
{
|
||||
try
|
||||
{
|
||||
// ✅ VALIDAR DIRECCIÓN
|
||||
if (direction.Length() < 0.001f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ VALIDAR QUE EL TARGET Y LA SIMULACIÓN EXISTAN
|
||||
if (target == null || _simulation == null || !_simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ VERIFICAR SI NECESITAMOS CREAR O ACTUALIZAR EL MOTOR
|
||||
bool needsNewMotor = false;
|
||||
|
||||
if (!HasMotor)
|
||||
{
|
||||
// ✅ PRIMERA VEZ: Crear motor nuevo
|
||||
needsNewMotor = true;
|
||||
//System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] 🆕 Creando motor nuevo para {target?.GetType().Name}");
|
||||
}
|
||||
else if (CurrentMotorTarget != target)
|
||||
{
|
||||
// ✅ CAMBIO DE OBJETO: Eliminar motor anterior y crear uno nuevo
|
||||
RemoveCurrentMotor();
|
||||
needsNewMotor = true;
|
||||
//System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] 🔄 Cambiando motor de {CurrentMotorTarget?.GetType().Name} a {target?.GetType().Name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// ✅ MISMO OBJETO: Solo actualizar velocidad
|
||||
UpdateMotorSpeed(direction, speed / simBase.SpeedConversionFactor);
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ CREAR NUEVO MOTOR SI ES NECESARIO
|
||||
if (needsNewMotor && target != null)
|
||||
{
|
||||
CreateMotorForTarget(target, direction, speed / simBase.SpeedConversionFactor);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] ❌ ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Crea un motor específico para un transporte o curva
|
||||
/// </summary>
|
||||
private void CreateMotorForTarget(simBase target, Vector3 direction, float speed)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_simulation == null || _simulation.Solver == null || !_simulation.Bodies.BodyExists(BodyHandle) || target == null)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ Simulación, solver, body o target no disponible");
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ VERIFICAR QUE EL TARGET TENGA UN BODY VÁLIDO
|
||||
if (!_simulation.Bodies.BodyExists(target.BodyHandle))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ Target body no existe: {target.BodyHandle}");
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ VERIFICAR QUE NO TENGA YA UN MOTOR VÁLIDO
|
||||
if (HasMotor && CurrentMotor.Value != 0)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ⚠️ Ya existe un motor válido: {CurrentMotor}");
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ NORMALIZAR LA DIRECCIÓN TANGENCIAL
|
||||
var tangentDir = Vector3.Normalize(direction);
|
||||
|
||||
// ✅ CREAR MOTOR CONECTADO AL TARGET
|
||||
var motor = new LinearAxisMotor()
|
||||
{
|
||||
LocalOffsetA = Vector3.Zero, // Target
|
||||
LocalOffsetB = Vector3.Zero, // Botella
|
||||
LocalAxis = tangentDir, // ✅ CORREGIDO: Usar la dirección tangencial calculada
|
||||
TargetVelocity = speed, // ✅ CORREGIDO: Usar la velocidad directamente
|
||||
Settings = new MotorSettings(Math.Max(_mass * 20f, 8f), 0f)
|
||||
};
|
||||
|
||||
// ✅ CONECTAR BOTELLA CON EL TARGET (transporte o curva)
|
||||
CurrentMotor = _simulation.Solver.Add(target.BodyHandle, BodyHandle, motor);
|
||||
|
||||
CurrentMotorTarget = target;
|
||||
_hasMotor = true; // ✅ ESTABLECER BANDERA
|
||||
|
||||
//if (target is simCurve curva) {
|
||||
// // Calcular el vector desde el centro de la curva hasta la botella (en el plano XY)
|
||||
// var curveCenter = curva.CurveCenter;
|
||||
// var bottlePosition = GetPosition();
|
||||
|
||||
// var radiusVector = new Vector3(bottlePosition.X - curveCenter.X, bottlePosition.Y - curveCenter.Y, 0f);
|
||||
// var radius = radiusVector.Length();
|
||||
|
||||
// if (radius > 1e-3f)
|
||||
// {
|
||||
|
||||
// // Calcular offsets locales
|
||||
// var localOffsetA = curveCenter; // Desde el centro de la curva hasta el punto de anclaje
|
||||
// var localOffsetB = Vector3.Zero; // ✅ SIMPLIFICADO: Conectar al centro de la botella
|
||||
|
||||
// var distanceLimit = new DistanceLimit()
|
||||
// {
|
||||
// LocalOffsetA = Vector3.Zero,
|
||||
// LocalOffsetB = Vector3.Zero,
|
||||
// MinimumDistance = radius- 4*Radius, // Distancia mínima = radio actual
|
||||
// MaximumDistance = radius+ 4*Radius, // Distancia máxima = radio actual (mantener distancia fija)
|
||||
// SpringSettings = new SpringSettings(30f, 0f)
|
||||
// };
|
||||
|
||||
// //CurrentDistanceLimit = _simulation.Solver.Add(target.BodyHandle, BodyHandle, distanceLimit);
|
||||
|
||||
// //System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget-Curve] 📏 DistanceLimit creado:");
|
||||
// //System.Diagnostics.Debug.WriteLine($" Radio actual: {radius:F3}");
|
||||
// //System.Diagnostics.Debug.WriteLine($" Punto de anclaje: {anchorPoint}");
|
||||
// //System.Diagnostics.Debug.WriteLine($" LocalOffsetA (curva): {localOffsetA}");
|
||||
// //System.Diagnostics.Debug.WriteLine($" LocalOffsetB (botella): {localOffsetB} (centro)");
|
||||
// //System.Diagnostics.Debug.WriteLine($" Distancia objetivo: {radius:F3}");
|
||||
// //System.Diagnostics.Debug.WriteLine($" DistanceLimit Handle: {CurrentDistanceLimit}");
|
||||
// }
|
||||
//}
|
||||
// ✅ ACTUALIZAR PROPIEDADES INTERNAS
|
||||
CurrentDirection = direction;
|
||||
CurrentSpeed = speed;
|
||||
IsOnElement = Math.Abs(speed) > 0.001f;
|
||||
|
||||
//System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ✅ Motor creado:");
|
||||
//System.Diagnostics.Debug.WriteLine($" Botella: {BodyHandle}");
|
||||
//System.Diagnostics.Debug.WriteLine($" Target: {target.BodyHandle} ({target.GetType().Name})");
|
||||
//System.Diagnostics.Debug.WriteLine($" Motor Handle: {CurrentMotor} (Value: {CurrentMotor.Value})");
|
||||
//System.Diagnostics.Debug.WriteLine($" Dirección: {direction}");
|
||||
//System.Diagnostics.Debug.WriteLine($" Velocidad efectiva: {effectiveSpeed:F3}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Actualiza solo la velocidad del motor existente
|
||||
/// </summary>
|
||||
private void UpdateMotorSpeed(Vector3 direction, float speed)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!HasMotor || _simulation == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ NORMALIZAR LA DIRECCIÓN TANGENCIAL
|
||||
var tangentDir = Vector3.Normalize(direction);
|
||||
|
||||
// ✅ OBTENER LA DESCRIPCIÓN ACTUAL DEL MOTOR
|
||||
_simulation.Solver.GetDescription(CurrentMotor, out LinearAxisMotor motor);
|
||||
|
||||
// ✅ ACTUALIZAR DIRECCIÓN Y VELOCIDAD
|
||||
motor.LocalAxis = tangentDir;
|
||||
motor.TargetVelocity = speed;
|
||||
|
||||
// ✅ ACTUALIZAR EL MOTOR EN EL SOLVER
|
||||
_simulation.Solver.ApplyDescription(CurrentMotor, motor);
|
||||
|
||||
// ✅ ACTUALIZAR PROPIEDADES INTERNAS
|
||||
CurrentDirection = direction;
|
||||
CurrentSpeed = speed;
|
||||
IsOnElement = Math.Abs(speed) > 0.001f;
|
||||
|
||||
//System.Diagnostics.Debug.WriteLine($"[UpdateMotorSpeed] 🔄 Velocidad actualizada: {effectiveSpeed:F3}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[UpdateMotorSpeed] ❌ ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Elimina el motor actual
|
||||
/// </summary>
|
||||
public void RemoveCurrentMotor()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (HasMotor && _simulation != null && _simulation.Solver != null)
|
||||
{
|
||||
// ✅ VERIFICAR QUE EL MOTOR EXISTE ANTES DE ELIMINARLO
|
||||
if (CurrentMotor.Value != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
_simulation.Solver.Remove(CurrentMotor);
|
||||
//System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] 🗑️ Motor eliminado: {CurrentMotor}");
|
||||
}
|
||||
catch (Exception removeEx)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ⚠️ Error eliminando motor {CurrentMotor}: {removeEx.Message}");
|
||||
// Continuar con la limpieza incluso si falla la eliminación
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ⚠️ Motor ya eliminado o inválido: {CurrentMotor}");
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NUEVO: Eliminar DistanceLimit si existe
|
||||
if (CurrentDistanceLimit.Value != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
_simulation.Solver.Remove(CurrentDistanceLimit);
|
||||
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] 🗑️ DistanceLimit eliminado: {CurrentDistanceLimit}");
|
||||
}
|
||||
catch (Exception removeEx)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ⚠️ Error eliminando DistanceLimit {CurrentDistanceLimit}: {removeEx.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ❌ ERROR: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// ✅ LIMPIAR REFERENCIAS SIEMPRE
|
||||
CurrentMotor = default;
|
||||
CurrentDistanceLimit = default; // ✅ NUEVO: Limpiar DistanceLimit
|
||||
CurrentMotorTarget = null;
|
||||
_hasMotor = false; // ✅ LIMPIAR BANDERA
|
||||
CurrentDirection = Vector3.UnitX;
|
||||
CurrentSpeed = 0f;
|
||||
IsOnElement = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Detiene el motor (velocidad 0) pero mantiene la conexión
|
||||
/// </summary>
|
||||
public void StopMotor()
|
||||
{
|
||||
UpdateMotorSpeed(CurrentDirection, 0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Limpia las restricciones asociadas a un elemento específico
|
||||
/// Solo limpia los datos internos de simBotella, no elimina las restricciones de BEPU
|
||||
/// </summary>
|
||||
/// <param name="target">Elemento (simTransporte o simCurve) del cual limpiar las restricciones</param>
|
||||
public void CleanRestrictions(simBase target)
|
||||
{
|
||||
try
|
||||
{
|
||||
// ✅ VERIFICAR SI EL TARGET COINCIDE CON EL ACTUAL
|
||||
if (CurrentMotorTarget == target)
|
||||
{
|
||||
// ✅ LIMPIAR SOLO LOS DATOS INTERNOS (no eliminar restricciones de BEPU)
|
||||
CurrentMotorTarget = null;
|
||||
_hasMotor = false; // ✅ CRÍTICO: Limpiar el flag del motor
|
||||
CurrentDirection = Vector3.UnitX;
|
||||
CurrentSpeed = 0f;
|
||||
IsOnElement = false;
|
||||
|
||||
// ✅ NO LIMPIAR CurrentMotor ni CurrentDistanceLimit - BEPU los maneja automáticamente
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[CleanRestrictions] ✅ Restricciones limpiadas para {target?.GetType().Name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CleanRestrictions] ⚠️ Target no coincide: actual={CurrentMotorTarget?.GetType().Name}, solicitado={target?.GetType().Name}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CleanRestrictions] ❌ ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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 sphere = new Sphere(Radius);
|
||||
var shapeIndex = _simulation.Shapes.Add(sphere);
|
||||
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 sphere = new Sphere(Radius);
|
||||
var shapeIndex = _simulation.Shapes.Add(sphere);
|
||||
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();
|
||||
|
||||
// Crear nueva orientación solo con rotación en Z (botella siempre "de pie")
|
||||
var correctedOrientation = 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,350 @@
|
|||
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>();
|
||||
|
||||
// ✅ EVENTO para actualización de motores
|
||||
public event Action<simCurve> OnSpeedChanged;
|
||||
|
||||
// ✅ 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);
|
||||
}
|
||||
|
||||
// ✅ SIMPLIFICADO: Configurar velocidad angular para AngularAxisMotor
|
||||
public void SetSpeed(float speed)
|
||||
{
|
||||
Speed = speed; // Velocidad angular directa (sin inversión)
|
||||
OnSpeedChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
/// <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 restricciones de botellas conectadas antes de eliminar el cuerpo
|
||||
if (_simulationManager != null)
|
||||
{
|
||||
var connectedBottles = _simulationManager.GetBottlesConnectedToElement(this);
|
||||
foreach (var bottle in connectedBottles)
|
||||
{
|
||||
bottle.CleanRestrictions(this);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 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)
|
||||
{
|
||||
// ✅ CORRIGIDO: 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
|
||||
// Para curvas, el "tamaño" es el diámetro del radio exterior
|
||||
var curveSize = outerRadius * 2f;
|
||||
var zPosition = zPos_Curve;
|
||||
|
||||
// Calcular nueva posición del centro (sin rotación - el sector ya está definido por los ángulos)
|
||||
_curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition);
|
||||
|
||||
Create(_curveCenter); // Sin rotación adicional
|
||||
}
|
||||
|
||||
private void Create(Vector3 position)
|
||||
{
|
||||
RemoverBody();
|
||||
|
||||
// ✅ SIMPLIFICADO: Crear superficie usando Triangle de BEPU directamente
|
||||
var triangles = CreateSimpleArcTriangles(_innerRadius, _outerRadius, _startAngle, _endAngle);
|
||||
|
||||
// ✅ ALMACENAR triángulos para acceso directo
|
||||
_storedTriangles = triangles;
|
||||
|
||||
// ✅ SIMPLIFICADO: Crear un solo cuerpo con múltiples Triangle shapes usando Mesh
|
||||
if (triangles.Length > 0)
|
||||
{
|
||||
// ✅ CREAR MESH CON LA API CORRECTA DE BEPU
|
||||
var triangleBuffer = new BepuUtilities.Memory.Buffer<Triangle>(triangles.Length, _simulation.BufferPool);
|
||||
for (int i = 0; i < triangles.Length; i++)
|
||||
{
|
||||
triangleBuffer[i] = triangles[i];
|
||||
}
|
||||
var mesh = new BepuPhysics.Collidables.Mesh(triangleBuffer, Vector3.One, _simulation.BufferPool);
|
||||
var shapeIndex = _simulation.Shapes.Add(mesh);
|
||||
|
||||
var bodyDescription = BodyDescription.CreateKinematic(
|
||||
new RigidPose(position),
|
||||
new CollidableDescription(shapeIndex, 0.001f), // ✅ SpeculativeMargin bajo para detección precisa
|
||||
new BodyActivityDescription(0.01f)
|
||||
);
|
||||
|
||||
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||
_bodyCreated = true;
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[simCurve] Creado con {triangles.Length} triángulos reales almacenados");
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ MÉTODOS ELIMINADOS: CreateMainBody y CreateTriangleBody ya no son necesarios
|
||||
// La curva ahora se crea como un solo Mesh en el método Create simplificado
|
||||
|
||||
/// <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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ✅ MÉTODOS ELIMINADOS: Los cálculos tangenciales complejos ya no son necesarios
|
||||
// AngularAxisMotor maneja automáticamente la rotación en curvas
|
||||
}
|
||||
|
||||
}
|
|
@ -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,247 @@
|
|||
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>();
|
||||
|
||||
// ✅ NUEVO EVENTO - para actualización de motores
|
||||
public event Action<simTransporte> OnSpeedChanged;
|
||||
|
||||
|
||||
|
||||
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 después del cambio de rotación
|
||||
UpdateCachedProperties();
|
||||
|
||||
// ✅ CRÍTICO: Disparar evento para actualizar motores activos con nueva dirección
|
||||
OnSpeedChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
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 después del cambio de orientación
|
||||
UpdateCachedProperties();
|
||||
|
||||
// ✅ CRÍTICO: Disparar evento para actualizar motores activos con nueva dirección
|
||||
OnSpeedChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
// ✅ NUEVO MÉTODO - actualizar propiedades cacheadas
|
||||
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()}");
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ MODIFICAR MÉTODO EXISTENTE - disparar evento
|
||||
public void SetSpeed(float speed)
|
||||
{
|
||||
Speed = speed;
|
||||
UpdateCachedProperties();
|
||||
// Disparar evento para actualizar motores activos
|
||||
OnSpeedChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
/// <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();
|
||||
|
||||
// ✅ CRÍTICO: Disparar evento para actualizar motores activos con nueva dirección
|
||||
OnSpeedChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
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 que limpia restricciones y elimina el body
|
||||
/// </summary>
|
||||
public new void RemoverBody()
|
||||
{
|
||||
// ✅ NUEVO: Limpiar restricciones de botellas conectadas antes de eliminar el cuerpo
|
||||
if (_simulationManager != null)
|
||||
{
|
||||
var connectedBottles = _simulationManager.GetBottlesConnectedToElement(this);
|
||||
foreach (var bottle in connectedBottles)
|
||||
{
|
||||
bottle.CleanRestrictions(this);
|
||||
}
|
||||
}
|
||||
|
||||
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(0.01f)
|
||||
);
|
||||
|
||||
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 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
|
||||
{
|
||||
// Ruta donde se guardará el estado
|
||||
|
@ -58,6 +72,8 @@ namespace CtrEditor
|
|||
|
||||
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
|
||||
public string directorio
|
||||
{
|
||||
|
@ -91,6 +107,7 @@ namespace CtrEditor
|
|||
_strDirectorioTrabajo = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
RecentDirectories = new List<string>();
|
||||
LibraryWindow = new LibraryWindowSettings();
|
||||
Camera = new CameraSettings();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -133,6 +150,9 @@ namespace CtrEditor
|
|||
// Asegurar que LibraryWindow esté inicializado
|
||||
if (estado.LibraryWindow == null)
|
||||
estado.LibraryWindow = new LibraryWindowSettings();
|
||||
// Asegurar que Camera esté inicializado
|
||||
if (estado.Camera == null)
|
||||
estado.Camera = new CameraSettings();
|
||||
return estado;
|
||||
}
|
||||
return new EstadoPersistente().Inizializar(); // Devuelve una nueva instancia si no existe el archivo
|
||||
|
|
Loading…
Reference in New Issue