Fluid Simulation

Umgang mit AI:

  • Der Weg ist das Ziel!
  • Challenge:
    • Verwende keine AI für die Logik.
    • Lasse dir von AI keinen Code schreiben.
    • Verwende AI höchstens als Tutor, um dir Dinge zu erklären.

Als Vorbereitung versuchen wir zuerst, ein 2D-Fluid aus vielen Teilchen zu simulieren, die sich gegenseitig stossen. Die Grundidee hinter diesem - physikalisch nicht sehr realistischen - Fluid ist wie folgt:

  1. Spawning: Erzeuge eine möglichst grosse Anzahl an Teilchen, alle mit identischem Radius und Masse. Visualisiert werden sie als kleine, einfarbige Kreise. Spawne diese z.B. an Zufallspositionen mit zufälligem Anfangsgeschwindigkeitsvektor.
  2. Collision Detection: Ermittle, welche Teilchen mit den Wänden und v.a. miteinander kollidieren, also aktuell überlappen. Für diesen Schritt werden wir verschiedene Möglichkeiten anschauen.
  3. Collision Handling: Kümmere dich um alle Teilchen, die aktuell gerade kollidieren. Zum Beispiel muss man dafür die neuen Geschwindigkeitsvektoren nach der Kollision berechnen. Für einen gewissen Realismus sollten die Teilchen bei jeder Kollision einen gewissen Anteil ihrer Energie oder Geschwindigkeit verlieren.
  1. Erstelle ein GitHub-Repo für alle Projekte in diesem Modul. Nenne es z.B. FluidSimulationsUnity und wähle die passenden Einstellungen (private, gitignore-File). Clone es auf deinen Computer. Arbeite von nun an darin. Add, commit, push selbständig und regelmässig.

  2. Installiere Unity-Hub und darin eine aktuelle Unity-Version.

  3. Erstelle nun ein neues Unity Projekt vom Typ Core: Universal 2D (z.B. FluidParticles).

  4. Implementiere nun das Template-Projekt (siehe unten), welche als Ausgangslage dient. Wird das Projekt ausgeführt, sollte ein farbiges Teilchen erscheinen, welches sich nach oben bewegt. Studiere den Code genau und stelle sicher, dass du diesen verstehst.

  5. Passe den Code nun so an, dass eine grössere Anzahl Teilchen (sagen wir ca. $100$) erzeugt werden. Diese sollen an Zufallspositionen gespawned werden. Dafür werden zwei Arrays benötigt: Eines für die ParticleModels und eines für die ParticleViews.

  6. Implementiere eine erste Dynamik:
    1. Jedes Particle soll eine zufällige Anfangsgeschwindigkeit haben (versch. Richtungen und Speed) und sich entsprechend bewegen.
    2. An den Wänden sollen sie ohne Energieverlust reflektiert werden.
    3. Kollisionen zwischen Teilchen werden noch ignoriert, sie bewegen sich also einfach durch einander hindurch.

  7. Collision Detection V1: Brute Force
    1. Implementiere nun eine erste Version - Brute Force - der Collision Detection: Iteriere über alle Teilchen. Iteriere für jedes Teilchen über alle anderen Teilchen und schaue, ob sie kollidieren. Dafür benötigst du also zwei ineinander verschachtelte Schleifen.
    2. Zwei Teilchen kollidieren dann, wenn deren Abstand kleiner ist als $2\times$ der Radius.
    3. Alle Teilchen sollen, während sie miteinander kollidieren, eine andere Farbe annehmen. Siehe Tipps unten.
    4. Für wie viele Teilchen kannst du deine Simulation flüssig laufen lassen? Warum geht nicht mehr? Was ist das Problem mit dieser Art von Collision Detection?

Unity Tipps

Template-Projekt
  1. Neues 2D Unity-Projekt vom Type Core: Universal 2D

  2. Erstelle unter Assets folgende Unterordner für saubere Struktur: Prefabs, Scripts. Ziehe dann später alle Scripts und Prefabs in den entsprechenden Ordner.

  3. Erstelle ein neues, leeres GameObject z.B. mit Namen Simulation. Hänge diesem das Script MainScript (siehe unten) an. Alternativ könnte man das Scipt auch der MainCamera anhängen.

  4. Füge die Scripts Particle.cs und ParticleView.cs an. Die Idee ist, dass unser Code einer Trennung von Modell (Particle) und Anzeige (ParticleView.cs) folgt.

  5. Particle Prefab:
    1. Erstelle in der Hierarchy eine farbige Kugel (2D Objects / Sprites / Circle), Farbe aussuchen.
    2. Gib Namen Particle.
    3. Hänge ParticleView Script an.
    4. Speichere als Prefab: Einfach aus Hierarchy in Prefab-Ordner ziehen.
    5. Lösche wieder aus Hierarchy.

  6. Der Code sollte nun wie folgt funktionieren: Führt man den Code aus, so erschein in der Mitte des Bildschirms ein farbige Kugel, die sich nach oben bewegt.

Code von Template

Farbe von Kugel ändern
  • Greife auf SpriteRenderer von particleView-Objekt zu und ändere seine Farbe:
    var spriteRenderer = particleView.GetComponent<SpriteRenderer>();
    spriteRenderer.color = Color.blue;
  • Tipp 1: Damit man nicht für jedes Frame für jede particleView den GetComponent()-Befehl wiederholt aufrufen muss, lohnt es sich, ein Array particleRenderers zu erstellen, in dem man die spriteRenderer für die entsprechenden Particles speichert. \\ 
  • Tipp 2: Passe gegebenenfalls den Code so an, dass die Farbzuweisung ... = Color.blue nur dann ausgeführt wird, wenn sich die Farbe des Teilchens auch ändert. Verwende dazu z.B. zwei Flags (bool) wasColliding (ob kollidierte im letzten Frame) und isColliding. Jetzt wird nur die Farbe neu gesetzt, falls sich der Zustand von wasColliding zu isColliding geändert hat.
Grösse von GameObject im Code ändern
someGameObject.transform.localScale = Vector3.one * 0.1f;
Zufallszahlen
UnityEngine.Random.InitState(42); // Zufallszahlen mit Seed (immer gleicher 'Zufall')
UnityEngine.Random.Range(0,10);

Wahrscheinlich hast du gemerkt, dass die Brute Force Methode für die Particle Collision Detection nicht sehr effizient ist, da man den Abstand von jedem Teilchen zu jedem anderen Teilchen berechnet, selbst wenn diese Teilchen sehr weit auseinander liegen. Hat man beispielsweise $1000$ Teilchen, so sind für diese Methode Rechenschritte in der Grössenordnung von $1000^2 =1’000’000$ notwendig. Dies wollen wir drastisch verbessern, indem wir Spatial Hashing für die Collision Detection verwenden. Auf dem MacBook eures Lehrers läuft die Simulation mit $5000$ Teilchens wie folgt:

  • Brute Force: $4-5$ FPS
  • Spatial Hashing: knapp $60$ FPS

Implementiere nun Spatial Hashing, um die Collision Detection zu optimieren:

  1. Der Bildschirm wird gleichmässig unterteilt in Zellen. Als Zellenbreite eignet sich der Teilchenradius.
  2. Jeder Zelle wird ein (möglichst) eindeutiger Hash (key) zugewiesen.
  3. Es wird ein Dictionary angelegt. Die Hashes dienen als key. Die Values sind Listen, die alle Particles beinhalten, die in der entsprechenden Zelle liegen.
  4. Anstelle dass man ein bestimmtes Particle mit allen anderen vergleicht, vergleicht man es nur mit denjenigen in der gleichen und angrenzenden Zellen. Dadurch skaliert der Code nur noch mit $O(n)$!
  5. Dazu ermittelt man zuerst die Hashes dieser (insg. $9$) Zellen.
  6. Dann liest man die entsprechenden Particles aus dem Dictionary aus.
  7. Nachdem man alle Zellen updated hat, aktualisiert man das Dictionary.
Dictionaries
using System.Collections.Generic; // required for dictionary
 
Dictionary<int, List<someDataType>> myDict = new Dictionary<int, List<someDataType>>(); // create empty dict
 
myDict.clear(); // clear the dictionary
 
myDict.clear[hash].Add(someObject); // add something to dict
 
myDict.ContainsKey(someKey); // checks if myDict contains a certain key
  • talit/fluid_simulation.1763306425.txt.gz
  • Zuletzt geändert: 2025-11-16 15:20
  • von sca