Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.
| Beide Seiten, vorherige Überarbeitung Vorherige Überarbeitung Nächste Überarbeitung | Vorherige Überarbeitung | ||
| talit:fluid_simulation [2025-11-16 15:37] – sca | talit:fluid_simulation [2025-12-15 14:45] (aktuell) – [2. Smoothed Particle Hydrodynamics (SPH)] sca | ||
|---|---|---|---|
| Zeile 450: | Zeile 450: | ||
| myDict.ContainsKey(someKey); | myDict.ContainsKey(someKey); | ||
| + | </ | ||
| + | |||
| + | == Hash == | ||
| + | |||
| + | <code csharp> | ||
| + | int CalculateCellHash(int xCell, int yCell) | ||
| + | { | ||
| + | return xCell * 73856093 ^ yCell * 19349663; | ||
| + | } | ||
| </ | </ | ||
| Zeile 460: | Zeile 469: | ||
| using UnityEngine; | using UnityEngine; | ||
| using System.Collections.Generic; | using System.Collections.Generic; | ||
| + | |||
| public enum CollisionDetectionType{ | public enum CollisionDetectionType{ | ||
| BruteForce, | BruteForce, | ||
| SpatialHashingDict | SpatialHashingDict | ||
| } | } | ||
| + | |||
| public class MainScript : MonoBehaviour | public class MainScript : MonoBehaviour | ||
| { | { | ||
| Zeile 471: | Zeile 480: | ||
| public GameObject prefabParticle; | public GameObject prefabParticle; | ||
| // and all other game settings | // and all other game settings | ||
| + | |||
| // PRIVARTE FIELDS | // PRIVARTE FIELDS | ||
| // screen boarders | // screen boarders | ||
| Zeile 478: | Zeile 487: | ||
| private float yMin = -5; | private float yMin = -5; | ||
| private float yMax = 5; | private float yMax = 5; | ||
| + | |||
| // particles | // particles | ||
| private bool giveParticlesInitVel = true; | private bool giveParticlesInitVel = true; | ||
| private float vCompMax = 2.5f; | private float vCompMax = 2.5f; | ||
| - | private float particleDiameter = 0.05f; // 0.05f | + | private float particleDiameter = 2f; // 0.05f |
| - | private float particleInitSep = 0.01f; // 0.01f | + | private float particleInitSep = 2f; // 0.01f |
| - | private int particleCount = 5000; // 500, make public later s.t. can adjust in Inspector | + | private int particleCount = 10; // 500, make public later s.t. can adjust in Inspector |
| private float widthToHeightRatioInitState = 2.0f; // radio of rectangle in which particles are placed initially | private float widthToHeightRatioInitState = 2.0f; // radio of rectangle in which particles are placed initially | ||
| private float particleInitSepRandRange = 0.0f; // should be smaller than particleInitSep / 2 to avoid overlap | private float particleInitSepRandRange = 0.0f; // should be smaller than particleInitSep / 2 to avoid overlap | ||
| + | |||
| // spatial hashing stuff | // spatial hashing stuff | ||
| private CollisionDetectionType collisionDetectionType = CollisionDetectionType.SpatialHashingDict; | private CollisionDetectionType collisionDetectionType = CollisionDetectionType.SpatialHashingDict; | ||
| Dictionary< | Dictionary< | ||
| private float gridSize; // should equal diameter of largest particle | private float gridSize; // should equal diameter of largest particle | ||
| + | |||
| // fields used in code below | // fields used in code below | ||
| private ParticleModel[] particleModels; | private ParticleModel[] particleModels; | ||
| Zeile 501: | Zeile 510: | ||
| private Color colorNoCollision = Color.green; | private Color colorNoCollision = Color.green; | ||
| private Color colorCollision = Color.red; | private Color colorCollision = Color.red; | ||
| + | |||
| void Start() | void Start() | ||
| { | { | ||
| Zeile 507: | Zeile 516: | ||
| InitParticles(); | InitParticles(); | ||
| } | } | ||
| + | |||
| void InitParticles() | void InitParticles() | ||
| { | { | ||
| Debug.Log(" | Debug.Log(" | ||
| Debug.Log(" | Debug.Log(" | ||
| + | |||
| // some calcs | // some calcs | ||
| minDistanceSquared = particleDiameter * particleDiameter; | minDistanceSquared = particleDiameter * particleDiameter; | ||
| particleRadius = particleDiameter / 2; | particleRadius = particleDiameter / 2; | ||
| gridSize = particleDiameter; | gridSize = particleDiameter; | ||
| + | |||
| // Check if prefab is assigned | // Check if prefab is assigned | ||
| if (prefabParticle == null) | if (prefabParticle == null) | ||
| Zeile 524: | Zeile 533: | ||
| return; | return; | ||
| } | } | ||
| + | |||
| // Create new arrays for particle models and views | // Create new arrays for particle models and views | ||
| particleModels = new ParticleModel[particleCount]; | particleModels = new ParticleModel[particleCount]; | ||
| particleViews = new ParticleView[particleCount]; | particleViews = new ParticleView[particleCount]; | ||
| particleRenderers = new SpriteRenderer[particleCount]; | particleRenderers = new SpriteRenderer[particleCount]; | ||
| + | |||
| // Setup stuff for inital positions | // Setup stuff for inital positions | ||
| int nx = Mathf.FloorToInt(Mathf.Sqrt((float)particleCount) * Mathf.Sqrt((float)widthToHeightRatioInitState)); | int nx = Mathf.FloorToInt(Mathf.Sqrt((float)particleCount) * Mathf.Sqrt((float)widthToHeightRatioInitState)); | ||
| Zeile 538: | Zeile 547: | ||
| // throw; // new Exception(" | // throw; // new Exception(" | ||
| } | } | ||
| - | | + | |
| // CREATE A PARTICLE AND ITS VIEW, THEN ASSIGN TO ARRAY | // CREATE A PARTICLE AND ITS VIEW, THEN ASSIGN TO ARRAY | ||
| for (int i = 0; i < particleCount; | for (int i = 0; i < particleCount; | ||
| Zeile 548: | Zeile 557: | ||
| float y = ny / 2 * (particleRadius + particleInitSep) - iy * (particleRadius + particleInitSep); | float y = ny / 2 * (particleRadius + particleInitSep) - iy * (particleRadius + particleInitSep); | ||
| Vector2 position = new Vector2(UnityEngine.Random.Range(x - particleInitSepRandRange, | Vector2 position = new Vector2(UnityEngine.Random.Range(x - particleInitSepRandRange, | ||
| + | |||
| Vector2 velocity; | Vector2 velocity; | ||
| // initial velocity | // initial velocity | ||
| Zeile 559: | Zeile 568: | ||
| velocity = new Vector2(0, 0); | velocity = new Vector2(0, 0); | ||
| } | } | ||
| + | |||
| ParticleModel particleModel = new ParticleModel(position, | ParticleModel particleModel = new ParticleModel(position, | ||
| + | |||
| // create game object for particle with prefab as image, will be added to Unity Hierarchy: | // create game object for particle with prefab as image, will be added to Unity Hierarchy: | ||
| GameObject particleGameObject = Instantiate(prefabParticle, | GameObject particleGameObject = Instantiate(prefabParticle, | ||
| Zeile 574: | Zeile 583: | ||
| Debug.LogWarning(" | Debug.LogWarning(" | ||
| } | } | ||
| + | |||
| particleView.Bind(particleModel); | particleView.Bind(particleModel); | ||
| + | |||
| // assign particle model, view and renderer to array | // assign particle model, view and renderer to array | ||
| particleModels[i] = particleModel; | particleModels[i] = particleModel; | ||
| Zeile 587: | Zeile 596: | ||
| } | } | ||
| } | } | ||
| + | |||
| // Set correct color | // Set correct color | ||
| for (int i = 0; i < particleCount; | for (int i = 0; i < particleCount; | ||
| Zeile 593: | Zeile 602: | ||
| particleRenderers[i].color = colorNoCollision; | particleRenderers[i].color = colorNoCollision; | ||
| } | } | ||
| + | |||
| } | } | ||
| + | |||
| void Update() | void Update() | ||
| { | { | ||
| Step(); // one simulation step | Step(); // one simulation step | ||
| } | } | ||
| - | + | ||
| - | void CollisionDetectionBruteForce() | + | |
| - | { | + | |
| - | for (int i = 0; i < particleCount; | + | |
| - | { | + | |
| - | ParticleModel particleModel1 = particleModels[i]; | + | |
| - | for (int j = i + 1; j < particleCount; | + | |
| - | { | + | |
| - | if (i == j) continue; | + | |
| - | ParticleModel particleModel2 = particleModels[j]; | + | |
| - | + | ||
| - | Vector2 delta = particleModel1.position - particleModel2.position; | + | |
| - | float distSq = delta.sqrMagnitude; | + | |
| - | if (distSq < minDistanceSquared) | + | |
| - | { | + | |
| - | particleModel1.isColliding = true; | + | |
| - | particleModel2.isColliding = true; | + | |
| - | } | + | |
| - | } | + | |
| - | } | + | |
| - | } | + | |
| // SPATIAL HASING METHODS | // SPATIAL HASING METHODS | ||
| int GetGridCellCoord(float x) | int GetGridCellCoord(float x) | ||
| Zeile 627: | Zeile 615: | ||
| return Mathf.FloorToInt(x / gridSize); | return Mathf.FloorToInt(x / gridSize); | ||
| } | } | ||
| - | | + | |
| int CalculateCellHash(int xCell, int yCell) | int CalculateCellHash(int xCell, int yCell) | ||
| { | { | ||
| return xCell * 73856093 ^ yCell * 19349663; | return xCell * 73856093 ^ yCell * 19349663; | ||
| } | } | ||
| - | + | ||
| - | void CollisionDetectionHashTableDict(){ | + | |
| - | // BUILD PARTICLE DICT | + | // get all particles in same and eight neighboring cells |
| - | // clear dict | + | // two options: same method once as IEnumerable and once returning a list. Same performance. |
| - | | + | IEnumerable< |
| - | | + | { |
| - | | + | |
| + | | ||
| + | | ||
| { | { | ||
| - | int xCell = GetGridCellCoord(particleModel.position.x); | + | int x = xCell + dx; |
| - | int yCell = GetGridCellCoord(particleModel.position.y); | + | |
| - | int hash = CalculateCellHash(xCell, | + | |
| - | if (!particleModelDict.ContainsKey(hash)) | + | |
| { | { | ||
| - | | + | |
| - | } | + | |
| - | particleModelDict[hash].Add(particleModel); | + | |
| - | } | + | |
| - | + | ||
| - | // CHECK FOR COLLISIONS | + | |
| - | foreach | + | |
| - | { | + | |
| - | foreach | + | |
| - | { | + | |
| - | if (particleModel == neighbour) continue; | + | |
| - | + | ||
| - | | + | |
| - | if(distanceSquared < minDistanceSquared) | + | |
| { | { | ||
| - | | + | |
| - | neighbour.isColliding = true; | + | |
| } | } | ||
| } | } | ||
| + | |||
| } | } | ||
| } | } | ||
| - | + | ||
| - | | + | |
| - | IEnumerable< | + | |
| { | { | ||
| + | List< | ||
| int xCell = GetGridCellCoord(position.x); | int xCell = GetGridCellCoord(position.x); | ||
| int yCell = GetGridCellCoord(position.y); | int yCell = GetGridCellCoord(position.y); | ||
| Zeile 682: | Zeile 660: | ||
| foreach (var particle in particleModelDict[hash]) | foreach (var particle in particleModelDict[hash]) | ||
| { | { | ||
| - | | + | |
| } | } | ||
| } | } | ||
| + | |||
| } | } | ||
| + | return neigbours; | ||
| } | } | ||
| + | |||
| void Step() | void Step() | ||
| { | { | ||
| Zeile 697: | Zeile 676: | ||
| particleModel.position += particleModel.velocity * dt; | particleModel.position += particleModel.velocity * dt; | ||
| } | } | ||
| + | |||
| for (int i = 0; i < particleCount; | for (int i = 0; i < particleCount; | ||
| { | { | ||
| Zeile 703: | Zeile 682: | ||
| particleModels[i].isColliding = false; | particleModels[i].isColliding = false; | ||
| } | } | ||
| + | |||
| // COLLISION DETECTION | // COLLISION DETECTION | ||
| if (collisionDetectionType == CollisionDetectionType.BruteForce){ | if (collisionDetectionType == CollisionDetectionType.BruteForce){ | ||
| - | | + | |
| + | for (int i = 0; i < particleCount; | ||
| + | { | ||
| + | ParticleModel particleModel1 = particleModels[i]; | ||
| + | for (int j = i + 1; j < particleCount; | ||
| + | { | ||
| + | if (i == j) continue; | ||
| + | ParticleModel particleModel2 = particleModels[j]; | ||
| + | |||
| + | Vector2 delta = particleModel1.position - particleModel2.position; | ||
| + | float distSq = delta.sqrMagnitude; | ||
| + | if (distSq < minDistanceSquared) | ||
| + | { | ||
| + | particleModel1.isColliding = true; | ||
| + | particleModel2.isColliding = true; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | // // COLLISION DETECTION BRUTE END | ||
| } | } | ||
| else if (collisionDetectionType == CollisionDetectionType.SpatialHashingDict) | else if (collisionDetectionType == CollisionDetectionType.SpatialHashingDict) | ||
| { | { | ||
| - | | + | |
| + | // build particle dict | ||
| + | // clear dict | ||
| + | particleModelDict.Clear(); | ||
| + | // iterate over all particles, determine hash and add to dict | ||
| + | foreach (var particleModel in particleModels) | ||
| + | { | ||
| + | int xCell = GetGridCellCoord(particleModel.position.x); | ||
| + | int yCell = GetGridCellCoord(particleModel.position.y); | ||
| + | int hash = CalculateCellHash(xCell, | ||
| + | if (!particleModelDict.ContainsKey(hash)) | ||
| + | { | ||
| + | particleModelDict[hash] = new List< | ||
| + | } | ||
| + | particleModelDict[hash].Add(particleModel); | ||
| + | } | ||
| + | |||
| + | // check for collisions | ||
| + | foreach (var particleModel in particleModels) | ||
| + | { | ||
| + | foreach (var neighbour in GetNeighbors(particleModel.position)) | ||
| + | { | ||
| + | if (particleModel == neighbour) continue; | ||
| + | |||
| + | float distanceSquared = (particleModel.position - neighbour.position).sqrMagnitude; | ||
| + | if (distanceSquared < minDistanceSquared) | ||
| + | { | ||
| + | particleModel.isColliding = true; | ||
| + | neighbour.isColliding = true; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | // // COLLISION DETECTION WITH HASH TABLE | ||
| } | } | ||
| else{ | else{ | ||
| Debug.Log(" | Debug.Log(" | ||
| } | } | ||
| + | |||
| // CHANGE COLOR IF COLLIDE | // CHANGE COLOR IF COLLIDE | ||
| for (int i = 0; i < particleCount; | for (int i = 0; i < particleCount; | ||
| Zeile 728: | Zeile 757: | ||
| } | } | ||
| } | } | ||
| + | |||
| // COLLISION HANDLING: WALLS ONLY | // COLLISION HANDLING: WALLS ONLY | ||
| for (int i = 0; i < particleCount; | for (int i = 0; i < particleCount; | ||
| Zeile 763: | Zeile 792: | ||
| ==== Auftrag A - Teil III ==== | ==== Auftrag A - Teil III ==== | ||
| + | |||
| + | 1. Implementiere nun elastische Stösse für die Brute-Force Collision Detection (siehe Theorie unten). | ||
| + | 1. Füge nun einen Energie-/ | ||
| + | 1. Mache nun das Gleiche für die Collision Detection mit Spatial Hashing. | ||
| Formel für elastischen Stoss in 2D: | Formel für elastischen Stoss in 2D: | ||
| [[https:// | [[https:// | ||
| + | |||
| + | ===== - Smoothed Particle Hydrodynamics (SPH) ===== | ||
| + | |||
| + | **Quellen: | ||
| + | |||
| + | * {{ : | ||
| + | * {{ : | ||
| + | |||