Agregada funciones de Fluidos

This commit is contained in:
Miguel 2025-04-13 16:42:18 +02:00
parent 20bdad509b
commit d1ec333243
11 changed files with 3118 additions and 8 deletions

View File

@ -77,13 +77,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aether.Physics2D" Version="2.1.0" />
<PackageReference Include="ClosedXML" Version="0.104.2" />
<PackageReference Include="Aether.Physics2D" Version="2.2.0" />
<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.6.1" />
<PackageReference Include="Emgu.CV" Version="4.10.0.5680" />
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.10.0.5680" />
<PackageReference Include="Emgu.CV.UI" Version="4.10.0.5680" />
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.7.25104.5739" />
<PackageReference Include="LanguageDetection" Version="1.2.0" />
<PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc4.5" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />

File diff suppressed because it is too large Load Diff

View File

@ -553,11 +553,11 @@ namespace CtrEditor.Simulacion
Body.OnSeparation += HandleOnSeparation;
Body.Tag = this; // Importante para la identificación durante la colisión
// Configurar la fricción
Body.SetFriction(0.2f);
//Body.SetFriction(0.2f);
// Configurar amortiguamiento
Body.LinearDamping = 3f; // Ajustar para controlar la reducción de la velocidad lineal
Body.AngularDamping = 1f; // Ajustar para controlar la reducción de la velocidad angular
Body.SetRestitution(0f); // Baja restitución para menos rebote
//Body.SetRestitution(0f); // Baja restitución para menos rebote
Body.SleepingAllowed = false;
Body.IsBullet = true;
}

View File

@ -0,0 +1,121 @@
/* Original source Farseer Physics Engine:
* Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com
* Microsoft Permissive License (Ms-PL) v1.1
*/
using nkast.Aether.Physics2D.Common;
namespace tainicom.Aether.Physics2D.Fluids
{
/// <summary>
/// Fluid parameters, see pvfs.pdf for a detailed explanation
/// </summary>
public struct FluidDefinition
{
/// <summary>
/// Distance of influence between the particles
/// </summary>
public float InfluenceRadius;
/// <summary>
/// Density of the fluid
/// </summary>
public float DensityRest;
/// <summary>
/// Stiffness of the fluid (when particles are far)
/// </summary>
public float Stiffness;
/// <summary>
/// Stiffness of the fluid (when particles are near)
/// Set by Check()
/// </summary>
public float StiffnessNear;
/// <summary>
/// Toggles viscosity forces
/// </summary>
public bool UseViscosity;
/// <summary>
/// See pvfs.pdf for more information
/// </summary>
public float ViscositySigma;
/// <summary>
/// See pvfs.pdf for more information
/// </summary>
public float ViscosityBeta;
/// <summary>
/// Toggles plasticity computation (springs etc.)
/// </summary>
public bool UsePlasticity;
/// <summary>
/// Plasticity, amount of memory of the shape
/// See pvfs.pdf for more information
/// </summary>
public float Plasticity;
/// <summary>
/// K of the springs used for plasticity
/// </summary>
public float KSpring;
/// <summary>
/// Amount of change of the rest length of the springs (when compressed)
/// </summary>
public float YieldRatioCompress;
/// <summary>
/// Amount of change of the rest length of the springs (when stretched)
/// </summary>
public float YieldRatioStretch;
public static FluidDefinition Default
{
get
{
FluidDefinition def = new FluidDefinition
{
InfluenceRadius = 1.0f,
DensityRest = 10.0f,
Stiffness = 10.0f,
StiffnessNear = 0.0f, // Set by Check()
UseViscosity = false,
ViscositySigma = 10.0f,
ViscosityBeta = 0.0f,
UsePlasticity = false,
Plasticity = 0.3f,
KSpring = 2.0f,
YieldRatioCompress = 0.1f,
YieldRatioStretch = 0.1f
};
def.Check();
return def;
}
}
public void Check()
{
InfluenceRadius = MathUtils.Clamp(InfluenceRadius, 0.1f, 10.0f);
DensityRest = MathUtils.Clamp(DensityRest, 1.0f, 100.0f);
Stiffness = MathUtils.Clamp(Stiffness, 0.1f, 10.0f);
StiffnessNear = Stiffness * 100.0f; // See pvfs.pdf
ViscositySigma = Math.Max(ViscositySigma, 0.0f);
ViscosityBeta = Math.Max(ViscosityBeta, 0.0f);
Plasticity = Math.Max(Plasticity, 0.0f);
KSpring = Math.Max(KSpring, 0.0f);
YieldRatioCompress = Math.Max(YieldRatioCompress, 0.0f);
YieldRatioStretch = Math.Max(YieldRatioStretch, 0.0f);
}
}
}

View File

@ -0,0 +1,80 @@
/* Original source Farseer Physics Engine:
* Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com
* Microsoft Permissive License (Ms-PL) v1.1
*/
using System.Collections.Generic;
using nkast.Aether.Physics2D.Common;
namespace tainicom.Aether.Physics2D.Fluids
{
public class FluidParticle
{
public Vector2 Position;
public Vector2 PreviousPosition;
public Vector2 Velocity;
public Vector2 Acceleration;
internal FluidParticle(Vector2 position)
{
Neighbours = new List<FluidParticle>();
IsActive = true;
MoveTo(position);
Damping = 0.0f;
Mass = 1.0f;
}
public bool IsActive { get; set; }
public List<FluidParticle> Neighbours { get; private set; }
// For gameplay purposes
public float Density { get; internal set; }
public float Pressure { get; internal set; }
// Other properties
public int Index { get; internal set; }
// Physics properties
public float Damping { get; set; }
public float Mass { get; set; }
public void MoveTo(Vector2 p)
{
Position = p;
PreviousPosition = p;
Velocity = Vector2.Zero;
Acceleration = Vector2.Zero;
}
public void ApplyForce(ref Vector2 force)
{
Acceleration += force * Mass;
}
public void ApplyImpulse(ref Vector2 impulse)
{
Velocity += impulse;
}
public void Update(float deltaTime)
{
Velocity += Acceleration * deltaTime;
Vector2 delta = (1.0f - Damping) * Velocity * deltaTime;
PreviousPosition = Position;
Position += delta;
Acceleration = Vector2.Zero;
}
public void UpdateVelocity(float deltaTime)
{
Velocity = (Position - PreviousPosition) / deltaTime;
}
}
}

View File

@ -0,0 +1,413 @@
/* Original source Farseer Physics Engine:
* Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com
* Microsoft Permissive License (Ms-PL) v1.1
*/
using System;
using System.Collections.Generic;
using nkast.Aether.Physics2D.Common;
namespace tainicom.Aether.Physics2D.Fluids
{
public class FluidSystem1
{
private float _influenceRadiusSquared;
private HashGrid _hashGrid = new HashGrid();
private Dictionary<SpringHash, Spring> _springs = new Dictionary<SpringHash, Spring>();
private List<SpringHash> _springsToRemove = new List<SpringHash>();
private Vector2 _totalForce;
public FluidSystem1(Vector2 gravity)
{
Gravity = gravity;
Particles = new List<FluidParticle>();
DefaultDefinition();
}
public FluidDefinition Definition { get; private set; }
public List<FluidParticle> Particles { get; private set; }
public int ParticlesCount { get { return Particles.Count; } }
public Vector2 Gravity { get; set; }
public void DefaultDefinition()
{
SetDefinition(FluidDefinition.Default);
}
public void SetDefinition(FluidDefinition def)
{
Definition = def;
Definition.Check();
_influenceRadiusSquared = Definition.InfluenceRadius * Definition.InfluenceRadius;
}
public FluidParticle AddParticle(Vector2 position)
{
FluidParticle particle = new FluidParticle(position) { Index = Particles.Count };
Particles.Add(particle);
return particle;
}
public void Clear()
{
//TODO
}
public void ApplyForce(Vector2 f)
{
_totalForce += f;
}
private void ApplyForces()
{
Vector2 f = Gravity + _totalForce;
for (int i = 0; i < Particles.Count; ++i)
{
Particles[i].ApplyForce(ref f);
}
_totalForce = Vector2.Zero;
}
private void ApplyViscosity(FluidParticle p, float timeStep)
{
for (int i = 0; i < p.Neighbours.Count; ++i)
{
FluidParticle neighbour = p.Neighbours[i];
if (p.Index >= neighbour.Index)
{
continue;
}
float q;
Vector2.DistanceSquared(ref p.Position, ref neighbour.Position, out q);
if (q > _influenceRadiusSquared)
{
continue;
}
Vector2 direction;
Vector2.Subtract(ref neighbour.Position, ref p.Position, out direction);
if (direction.LengthSquared() < float.Epsilon)
{
continue;
}
direction.Normalize();
Vector2 deltaVelocity;
Vector2.Subtract(ref p.Velocity, ref neighbour.Velocity, out deltaVelocity);
float u;
Vector2.Dot(ref deltaVelocity, ref direction, out u);
if (u > 0.0f)
{
q = 1.0f - (float)Math.Sqrt(q) / Definition.InfluenceRadius;
float impulseFactor = 0.5f * timeStep * q * (u * (Definition.ViscositySigma + Definition.ViscosityBeta * u));
Vector2 impulse;
Vector2.Multiply(ref direction, -impulseFactor, out impulse);
p.ApplyImpulse(ref impulse);
Vector2.Multiply(ref direction, impulseFactor, out impulse);
neighbour.ApplyImpulse(ref impulse);
}
}
}
private const int MaxNeighbors = 25;
//private int _len2;
//private int _j;
//private float _q;
//private float _qq;
//private Vector2 _rij;
//private float _d;
//private Vector2 _dx;
private float _density;
private float _densityNear;
private float _pressure;
private float _pressureNear;
private float[] _distanceCache = new float[MaxNeighbors];
//private void DoubleDensityRelaxation1(FluidParticle p, float timeStep)
//{
// _density = 0;
// _densityNear = 0;
// _len2 = p.Neighbours.Count;
// if (_len2 > MaxNeighbors)
// _len2 = MaxNeighbors;
// for (_j = 0; _j < _len2; _j++)
// {
// _q = Vector2.DistanceSquared(p.Position, p.Neighbours[_j].Position);
// _distanceCache[_j] = _q;
// if (_q < _influenceRadiusSquared && _q != 0)
// {
// _q = (float)Math.Sqrt(_q);
// _q /= Definition.InfluenceRadius;
// _qq = ((1 - _q) * (1 - _q));
// _density += _qq;
// _densityNear += _qq * (1 - _q);
// }
// }
// _pressure = Definition.Stiffness * (_density - Definition.DensityRest);
// _pressureNear = Definition.StiffnessNear * _densityNear;
// _dx = Vector2.Zero;
// for (_j = 0; _j < _len2; _j++)
// {
// _q = _distanceCache[_j];
// if (_q < _influenceRadiusSquared && _q != 0)
// {
// _q = (float)Math.Sqrt(_q);
// _rij = p.Neighbours[_j].Position;
// _rij -= p.Position;
// _rij *= 1 / _q;
// _q /= _influenceRadiusSquared;
// _d = ((timeStep * timeStep) * (_pressure * (1 - _q) + _pressureNear * (1 - _q) * (1 - _q)));
// _rij *= _d * 0.5f;
// p.Neighbours[_j].Position += _rij;
// _dx -= _rij;
// }
// }
// p.Position += _dx;
//}
private void DoubleDensityRelaxation(FluidParticle particle, float deltaTime2)
{
_density = 0.0f;
_densityNear = 0.0f;
int neightborCount = particle.Neighbours.Count;
if (neightborCount > MaxNeighbors)
neightborCount = MaxNeighbors;
for (int i = 0; i < neightborCount; ++i)
{
FluidParticle neighbour = particle.Neighbours[i];
if (particle.Index == neighbour.Index)
continue;
float q;
Vector2.DistanceSquared(ref particle.Position, ref neighbour.Position, out q);
_distanceCache[i] = q;
if (q > _influenceRadiusSquared)
continue;
q = 1.0f - (float)Math.Sqrt(q) / Definition.InfluenceRadius;
float densityDelta = q * q;
_density += densityDelta;
_densityNear += densityDelta * q;
}
_pressure = Definition.Stiffness * (_density - Definition.DensityRest);
_pressureNear = Definition.StiffnessNear * _densityNear;
// For gameplay purposes
particle.Density = _density + _densityNear;
particle.Pressure = _pressure + _pressureNear;
Vector2 delta = Vector2.Zero;
for (int i = 0; i < neightborCount; ++i)
{
FluidParticle neighbour = particle.Neighbours[i];
if (particle.Index == neighbour.Index)
continue;
float q = _distanceCache[i];
if (q > _influenceRadiusSquared)
continue;
q = 1.0f - (float)Math.Sqrt(q) / Definition.InfluenceRadius;
float dispFactor = deltaTime2 * (q * (_pressure + _pressureNear * q));
Vector2 direction;
Vector2.Subtract(ref neighbour.Position, ref particle.Position, out direction);
if (direction.LengthSquared() < float.Epsilon)
continue;
direction.Normalize();
Vector2 disp;
Vector2.Multiply(ref direction, dispFactor, out disp);
Vector2.Add(ref neighbour.Position, ref disp, out neighbour.Position);
Vector2.Multiply(ref direction, -dispFactor, out disp);
Vector2.Add(ref delta, ref disp, out delta);
}
Vector2.Add(ref particle.Position, ref delta, out particle.Position);
}
private void CreateSprings(FluidParticle p)
{
for (int i = 0; i < p.Neighbours.Count; ++i)
{
FluidParticle neighbour = p.Neighbours[i];
if (p.Index >= neighbour.Index)
continue;
float q;
Vector2.DistanceSquared(ref p.Position, ref neighbour.Position, out q);
if (q > _influenceRadiusSquared)
continue;
SpringHash hash = new SpringHash { P0 = p, P1 = neighbour };
if (!_springs.ContainsKey(hash))
{
//TODO: Use pool?
Spring spring = new Spring(p, neighbour) { RestLength = (float)Math.Sqrt(q) };
_springs.Add(hash, spring);
}
}
}
private void AdjustSprings(float timeStep)
{
foreach (var pair in _springs)
{
Spring spring = pair.Value;
spring.Update(timeStep, Definition.KSpring, Definition.InfluenceRadius);
if (spring.Active)
{
float L = spring.RestLength;
float distance;
Vector2.Distance(ref spring.P0.Position, ref spring.P1.Position, out distance);
if (distance > (L + (Definition.YieldRatioStretch * L)))
{
spring.RestLength += timeStep * Definition.Plasticity * (distance - L - (Definition.YieldRatioStretch * L));
}
else if (distance < (L - (Definition.YieldRatioCompress * L)))
{
spring.RestLength -= timeStep * Definition.Plasticity * (L - (Definition.YieldRatioCompress * L) - distance);
}
}
else
{
_springsToRemove.Add(pair.Key);
}
}
for (int i = 0; i < _springsToRemove.Count; ++i)
{
_springs.Remove(_springsToRemove[i]);
}
}
private void ComputeNeighbours()
{
_hashGrid.GridSize = Definition.InfluenceRadius;
_hashGrid.Clear();
for (int i = 0; i < Particles.Count; ++i)
{
FluidParticle p = Particles[i];
if (p.IsActive)
{
_hashGrid.Add(p);
}
}
for (int i = 0; i < Particles.Count; ++i)
{
FluidParticle p = Particles[i];
p.Neighbours.Clear();
_hashGrid.Find(ref p.Position, p.Neighbours);
}
}
public void Update(float deltaTime)
{
if (deltaTime == 0)
return;
float deltaTime2 = 0.5f * deltaTime * deltaTime;
ComputeNeighbours();
ApplyForces();
if (Definition.UseViscosity)
{
for (int i = 0; i < Particles.Count; ++i)
{
FluidParticle p = Particles[i];
if (p.IsActive)
{
ApplyViscosity(p, deltaTime);
}
}
}
for (int i = 0; i < Particles.Count; ++i)
{
FluidParticle p = Particles[i];
if (p.IsActive)
{
p.Update(deltaTime);
}
}
for (int i = 0; i < Particles.Count; ++i)
{
FluidParticle p = Particles[i];
if (p.IsActive)
{
DoubleDensityRelaxation(p, deltaTime2);
}
}
if (Definition.UsePlasticity)
{
for (int i = 0; i < Particles.Count; ++i)
{
FluidParticle p = Particles[i];
if (p.IsActive)
{
CreateSprings(p);
}
}
}
AdjustSprings(deltaTime);
UpdateVelocities(deltaTime);
}
internal void UpdateVelocities(float timeStep)
{
for (int i = 0; i < Particles.Count; ++i)
{
Particles[i].UpdateVelocity(timeStep);
}
}
}
}

View File

@ -0,0 +1,95 @@
/* Original source Farseer Physics Engine:
* Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com
* Microsoft Permissive License (Ms-PL) v1.1
*/
using System;
using System.Collections.Generic;
using nkast.Aether.Physics2D.Common;
namespace tainicom.Aether.Physics2D.Fluids
{
/// <summary>
/// Grid used by particle system to keep track of neightbor particles.
/// </summary>
public class HashGrid
{
private Dictionary<ulong, List<FluidParticle>> _hash = new Dictionary<ulong, List<FluidParticle>>();
private Stack<List<FluidParticle>> _bucketPool = new Stack<List<FluidParticle>>();
public HashGrid()
{
GridSize = 1.0f;
}
public float GridSize { get; set; }
private static ulong HashKey(int x, int y)
{
return ((ulong)x * 2185031351ul) ^ ((ulong)y * 4232417593ul);
}
private ulong HashKey(Vector2 position)
{
return HashKey(
(int)Math.Floor(position.X / GridSize),
(int)Math.Floor(position.Y / GridSize)
);
}
public void Clear()
{
foreach (KeyValuePair<ulong, List<FluidParticle>> pair in _hash)
{
pair.Value.Clear();
_bucketPool.Push(pair.Value);
}
_hash.Clear();
}
public void Add(FluidParticle particle)
{
ulong key = HashKey(particle.Position);
List<FluidParticle> bucket;
if (!_hash.TryGetValue(key, out bucket))
{
if (_bucketPool.Count > 0)
{
bucket = _bucketPool.Pop();
}
else
{
bucket = new List<FluidParticle>();
}
_hash.Add(key, bucket);
}
bucket.Add(particle);
}
public void Find(ref Vector2 position, List<FluidParticle> neighbours)
{
int ix = (int)Math.Floor(position.X / GridSize);
int iy = (int)Math.Floor(position.Y / GridSize);
// Check all 9 neighbouring cells
for (int x = ix - 1; x <= ix + 1; ++x)
{
for (int y = iy - 1; y <= iy + 1; ++y)
{
ulong key = HashKey(x, y);
List<FluidParticle> bucket;
if (_hash.TryGetValue(key, out bucket))
{
for (int i = 0; i < bucket.Count; ++i)
{
if (bucket[i] != null)
{
neighbours.Add(bucket[i]);
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,57 @@
/* Original source Farseer Physics Engine:
* Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com
* Microsoft Permissive License (Ms-PL) v1.1
*/
using nkast.Aether.Physics2D.Common;
namespace tainicom.Aether.Physics2D.Fluids
{
//TODO: Could be struct?
public class Spring
{
public FluidParticle P0;
public FluidParticle P1;
public Spring(FluidParticle p0, FluidParticle p1)
{
Active = true;
P0 = p0;
P1 = p1;
}
public bool Active { get; set; }
public float RestLength { get; set; }
public void Update(float timeStep, float kSpring, float influenceRadius)
{
if (!Active)
return;
Vector2 dir = P1.Position - P0.Position;
float distance = dir.Length();
dir.Normalize();
// This is to avoid imploding simulation with really springy fluids
if (distance < 0.5f * influenceRadius)
{
Active = false;
return;
}
if (RestLength > influenceRadius)
{
Active = false;
return;
}
//Algorithm 3
float displacement = timeStep * timeStep * kSpring * (1.0f - RestLength / influenceRadius) * (RestLength - distance) * 0.5f;
dir *= displacement;
P0.Position -= dir;
P1.Position += dir;
}
}
}

View File

@ -0,0 +1,26 @@
/* Original source Farseer Physics Engine:
* Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com
* Microsoft Permissive License (Ms-PL) v1.1
*/
using System.Collections.Generic;
namespace tainicom.Aether.Physics2D.Fluids
{
public class SpringHash : IEqualityComparer<SpringHash>
{
public FluidParticle P0;
public FluidParticle P1;
public bool Equals(SpringHash lhs, SpringHash rhs)
{
return (lhs.P0.Index == rhs.P0.Index && lhs.P1.Index == rhs.P1.Index)
|| (lhs.P0.Index == rhs.P1.Index && lhs.P1.Index == rhs.P0.Index);
}
public int GetHashCode(SpringHash s)
{
return (s.P0.Index * 73856093) ^ (s.P1.Index * 19349663) ^ (s.P0.Index * 19349663) ^ (s.P1.Index * 73856093);
}
}
}

View File

@ -0,0 +1,445 @@
/* Original source Farseer Physics Engine:
* Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com
* Microsoft Permissive License (Ms-PL) v1.1
*/
using System;
using System.Collections.Generic;
using nkast.Aether.Physics2D.Common;
namespace tainicom.Aether.Physics2D.Fluids
{
public class FluidSystem2
{
public const int MaxNeighbors = 25;
public const int CellSize = 1;
// Most of these can be tuned at runtime with F1-F9 and keys 1-9 (no numpad)
public const float InfluenceRadius = 20.0f;
public const float InfluenceRadiusSquared = InfluenceRadius * InfluenceRadius;
public const float Stiffness = 0.504f;
public const float StiffnessFarNearRatio = 10.0f;
public const float StiffnessNear = Stiffness * StiffnessFarNearRatio;
public const float ViscositySigma = 0.0f;
public const float ViscosityBeta = 0.3f;
public const float DensityRest = 10.0f;
public const float KSpring = 0.3f;
public const float RestLength = 5.0f;
public const float RestLengthSquared = RestLength * RestLength;
public const float YieldRatioStretch = 0.5f;
public const float YieldRatioCompress = 0.5f;
public const float Plasticity = 0.5f;
public const int VelocityCap = 150;
public const float DeformationFactor = 0f;
public const float CollisionForce = 0.3f;
private bool _isElasticityInitialized;
private bool _elasticityEnabled;
private bool _isPlasticityInitialized;
private bool _plasticityEnabled;
private float _deltaTime2;
private Vector2 _dx = new Vector2(0.0f, 0.0f);
private const int Wpadding = 20;
private const int Hpadding = 20;
public SpatialTable Particles;
// Temp variables
private Vector2 _rij = new Vector2(0.0f, 0.0f);
private Vector2 _tempVect = new Vector2(0.0f, 0.0f);
private Dictionary<int, List<int>> _springPresenceTable;
private List<Spring2> _springs;
private List<Particle> _tempParticles;
private int _worldWidth;
private int _worldHeight;
public int ParticlesCount { get { return Particles.Count; } }
public FluidSystem2(Vector2 gravity, int maxParticleLimit, int worldWidth, int worldHeight)
{
_worldHeight = worldHeight;
_worldWidth = worldWidth;
Particles = new SpatialTable(worldWidth, worldHeight, CellSize);
MaxParticleLimit = maxParticleLimit;
Gravity = gravity;
}
public Vector2 Gravity { get; set; }
public int MaxParticleLimit { get; private set; }
public bool ElasticityEnabled
{
get { return _elasticityEnabled; }
set
{
if (!_isElasticityInitialized)
InitializeElasticity();
_elasticityEnabled = value;
}
}
public bool PlasticityEnabled
{
get { return _plasticityEnabled; }
set
{
if (!_isPlasticityInitialized)
InitializePlasticity();
_plasticityEnabled = value;
}
}
private void UpdateParticleVelocity(float deltaTime)
{
for(int i = 0; i < Particles.Count; i++)
{
Particle particle = Particles[i];
particle.PreviousPosition = particle.Position;
particle.Position = new Vector2(particle.Position.X + (deltaTime * particle.Velocity.X), particle.Position.Y + (deltaTime * particle.Velocity.Y));
}
}
private void WallCollision(Particle pi)
{
float x = 0;
float y = 0;
if (pi.Position.X > (_worldWidth / 2 - Wpadding))
x -= (pi.Position.X - (_worldWidth / 2 - Wpadding)) / CollisionForce;
else if (pi.Position.X < (-_worldWidth / 2 + Wpadding))
x += ((-_worldWidth / 2 + Wpadding) - pi.Position.X) / CollisionForce;
if (pi.Position.Y > (_worldHeight - Hpadding))
y -= (pi.Position.Y - (_worldHeight - Hpadding)) / CollisionForce;
else if (pi.Position.Y < Hpadding)
y += (Hpadding - pi.Position.Y) / CollisionForce;
pi.Velocity.X += x;
pi.Velocity.Y += y;
}
private void CapVelocity(Vector2 v)
{
if (v.X > VelocityCap)
v.X = VelocityCap;
else if (v.X < -VelocityCap)
v.X = -VelocityCap;
if (v.Y > VelocityCap)
v.Y = VelocityCap;
else if (v.Y < -VelocityCap)
v.Y = -VelocityCap;
}
private void InitializePlasticity()
{
_isPlasticityInitialized = true;
_springs.Clear();
float q;
foreach (Particle pa in Particles)
{
foreach (Particle pb in Particles)
{
if (pa.GetHashCode() == pb.GetHashCode())
continue;
Vector2.Distance(ref pa.Position, ref pb.Position, out q);
Vector2.Subtract(ref pb.Position, ref pa.Position, out _rij);
_rij /= q;
if (q < RestLength)
{
_springs.Add(new Spring2(pa, pb, q));
}
}
pa.Velocity = Vector2.Zero;
}
}
private void CalculatePlasticity(float deltaTime)
{
foreach (Spring2 spring in _springs)
{
spring.Update();
if (spring.CurrentDistance == 0)
continue;
Vector2.Subtract(ref spring.PB.Position, ref spring.PA.Position, out _rij);
_rij /= spring.CurrentDistance;
float D = deltaTime * KSpring * (spring.RestLength - spring.CurrentDistance);
_rij *= (D * 0.5f);
spring.PA.Position = new Vector2(spring.PA.Position.X - _rij.X, spring.PA.Position.Y - _rij.Y);
spring.PB.Position = new Vector2(spring.PB.Position.X + _rij.X, spring.PB.Position.Y + _rij.Y);
}
}
private void InitializeElasticity()
{
_isElasticityInitialized = true;
foreach (Particle particle in Particles)
{
_springPresenceTable.Add(particle.GetHashCode(), new List<int>(MaxParticleLimit));
particle.Velocity = Vector2.Zero;
}
}
private void CalculateElasticity(float deltaTime)
{
float sqDist;
for (int i = 0; i < Particles.Count; i++)
{
Particle pa = Particles[i];
if (Particles.CountNearBy(pa) <= 1)
continue;
_tempParticles = Particles.GetNearby(pa);
int len2 = _tempParticles.Count;
if (len2 > MaxNeighbors)
len2 = MaxNeighbors;
for (int j = 0; j < len2; j++)
{
Particle pb = Particles[j];
Vector2.DistanceSquared(ref pa.Position, ref pb.Position, out sqDist);
if (sqDist > RestLengthSquared)
continue;
if (pa.GetHashCode() == pb.GetHashCode())
continue;
if (!_springPresenceTable[pa.GetHashCode()].Contains(pb.GetHashCode()))
{
_springs.Add(new Spring2(pa, pb, RestLength));
_springPresenceTable[pa.GetHashCode()].Add(pb.GetHashCode());
}
}
}
for (int i = _springs.Count - 1; i >= 0; i--)
{
Spring2 spring = _springs[i];
spring.Update();
// Stretch
if (spring.CurrentDistance > (spring.RestLength + DeformationFactor))
{
spring.RestLength += deltaTime * Plasticity * (spring.CurrentDistance - spring.RestLength - (YieldRatioStretch * spring.RestLength));
}
// Compress
else if (spring.CurrentDistance < (spring.RestLength - DeformationFactor))
{
spring.RestLength -= deltaTime * Plasticity * (spring.RestLength - (YieldRatioCompress * spring.RestLength) - spring.CurrentDistance);
}
// Remove springs with restLength longer than REST_LENGTH
if (spring.RestLength > RestLength)
{
_springs.RemoveAt(i);
_springPresenceTable[spring.PA.GetHashCode()].Remove(spring.PB.GetHashCode());
}
else
{
if (spring.CurrentDistance == 0)
continue;
Vector2.Subtract(ref spring.PB.Position, ref spring.PA.Position, out _rij);
_rij /= spring.CurrentDistance;
float D = deltaTime * KSpring * (spring.RestLength - spring.CurrentDistance);
_rij *= (D * 0.5f);
spring.PA.Position = new Vector2(spring.PA.Position.X - _rij.X, spring.PA.Position.Y - _rij.Y);
spring.PB.Position = new Vector2(spring.PB.Position.X + _rij.X, spring.PB.Position.Y + _rij.Y);
}
}
}
private void ApplyGravity(Particle particle)
{
particle.Velocity = new Vector2(particle.Velocity.X + Gravity.X, particle.Velocity.Y + Gravity.Y);
}
private void ApplyViscosity(float deltaTime)
{
float u, q;
for (int i = 0; i < Particles.Count; i++)
{
Particle particle = Particles[i];
_tempParticles = Particles.GetNearby(particle);
int len2 = _tempParticles.Count;
if (len2 > MaxNeighbors)
len2 = MaxNeighbors;
for (int j = 0; j < len2; j++)
{
Particle tempParticle = _tempParticles[j];
Vector2.DistanceSquared(ref particle.Position, ref tempParticle.Position, out q);
if ((q < InfluenceRadiusSquared) && (q != 0))
{
q = (float)Math.Sqrt(q);
Vector2.Subtract(ref tempParticle.Position, ref particle.Position, out _rij);
Vector2.Divide(ref _rij, q, out _rij);
Vector2.Subtract(ref particle.Velocity, ref tempParticle.Velocity, out _tempVect);
Vector2.Dot(ref _tempVect, ref _rij, out u);
if (u <= 0.0f)
continue;
q /= InfluenceRadius;
float I = (deltaTime * (1 - q) * (ViscositySigma * u + ViscosityBeta * u * u));
Vector2.Multiply(ref _rij, (I * 0.5f), out _rij);
Vector2.Subtract(ref particle.Velocity, ref _rij, out _tempVect);
particle.Velocity = _tempVect;
_tempVect = tempParticle.Velocity;
_tempVect += _rij;
tempParticle.Velocity = _tempVect;
}
}
}
}
private void DoubleDensityRelaxation()
{
float q;
for (int i = 0; i < Particles.Count; i++)
{
Particle particle = Particles[i];
particle.Density = 0;
particle.NearDensity = 0;
_tempParticles = Particles.GetNearby(particle);
int len2 = _tempParticles.Count;
if (len2 > MaxNeighbors)
len2 = MaxNeighbors;
for (int j = 0; j < len2; j++)
{
Particle tempParticle = _tempParticles[j];
Vector2.DistanceSquared(ref particle.Position, ref tempParticle.Position, out q);
if (q < InfluenceRadiusSquared && q != 0)
{
q = (float)Math.Sqrt(q);
q /= InfluenceRadius;
float qq = ((1 - q) * (1 - q));
particle.Density += qq;
particle.NearDensity += qq * (1 - q);
}
}
particle.Pressure = (Stiffness * (particle.Density - DensityRest));
particle.NearPressure = (StiffnessNear * particle.NearDensity);
_dx = Vector2.Zero;
for (int j = 0; j < len2; j++)
{
Particle tempParticle = _tempParticles[j];
Vector2.DistanceSquared(ref particle.Position, ref tempParticle.Position, out q);
if ((q < InfluenceRadiusSquared) && (q != 0))
{
q = (float)Math.Sqrt(q);
Vector2.Subtract(ref tempParticle.Position, ref particle.Position, out _rij);
Vector2.Divide(ref _rij, q, out _rij);
q /= InfluenceRadius;
float D = (_deltaTime2 * (particle.Pressure * (1 - q) + particle.NearPressure * (1 - q) * (1 - q)));
Vector2.Multiply(ref _rij, (D * 0.5f), out _rij);
tempParticle.Position = new Vector2(tempParticle.Position.X + _rij.X, tempParticle.Position.Y + _rij.Y);
Vector2.Subtract(ref _dx, ref _rij, out _dx);
}
}
particle.Position = particle.Position + _dx;
}
}
public void Update(float deltaTime)
{
if (deltaTime == 0)
return;
_deltaTime2 = deltaTime * deltaTime;
ApplyViscosity(deltaTime);
//Update velocity
UpdateParticleVelocity(deltaTime);
Particles.Rehash();
if (_elasticityEnabled)
CalculateElasticity(deltaTime);
if (_plasticityEnabled)
CalculatePlasticity(deltaTime);
DoubleDensityRelaxation();
for(int i = 0; i < Particles.Count; i++)
{
Particle particle = Particles[i];
particle.Velocity = new Vector2((particle.Position.X - particle.PreviousPosition.X) / deltaTime, (particle.Position.Y - particle.PreviousPosition.Y) / deltaTime);
ApplyGravity(particle);
WallCollision(particle);
CapVelocity(particle.Velocity);
}
}
public void AddParticle(Vector2 position)
{
Particles.Add(new Particle(position.X, position.Y));
}
}
public class Particle
{
public float Density;
public float NearDensity;
public float NearPressure;
public Vector2 Position = new Vector2(0, 0);
public float Pressure;
public Vector2 PreviousPosition = new Vector2(0, 0);
public Vector2 Velocity = new Vector2(0, 0);
public Particle(float posX, float posY)
{
Position = new Vector2(posX, posY);
}
}
public class Spring2
{
public float CurrentDistance;
public Particle PA;
public Particle PB;
public float RestLength;
public Spring2(Particle pa, Particle pb, float restLength)
{
PA = pa;
PB = pb;
RestLength = restLength;
}
public void Update()
{
Vector2.Distance(ref PA.Position, ref PB.Position, out CurrentDistance);
}
public bool Contains(Particle p)
{
return (PA.GetHashCode() == p.GetHashCode() || PB.GetHashCode() == p.GetHashCode());
}
}
}

View File

@ -0,0 +1,179 @@
/* 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();
}
}
}