Hier werden die Unterschiede zwischen zwei Versionen gezeigt.
Beide Seiten der vorigen Revision Vorhergehende Überarbeitung Nächste Überarbeitung | Vorhergehende Überarbeitung | ||
ws2122:boids:boids [2022/04/02 13:53] mak.18 [Ergebnis] |
ws2122:boids:boids [2022/04/03 21:09] (aktuell) mak.18 [Grundaufbau] |
||
---|---|---|---|
Zeile 48: | Zeile 48: | ||
5. **Positionen aller Boids updaten.** | 5. **Positionen aller Boids updaten.** | ||
+ | Mithilfe von pygame (2D-Version) bzw. vpython (3D-Version) erfolgt dann die Darstellung der Boids auf dem Bildschirm. | ||
+ | ---- | ||
+ | //Nachfolgend gezeigte Code-Ausschnitte stammen aus der 2D-Version. Die 3D-Version hat jedoch einen ähnlichen Code.// | ||
==== Erzeugung Boids ==== | ==== Erzeugung Boids ==== | ||
- | Für die erste Erzeugung der Boids bei Start werden Anzahl der Boids, Fressfeinde, Hindernisse sowie für spätere Berechnungen, der Radius festgelgt. Eine Liste mit Positionen und Geschwindigkeiten dieser wird zu Beginn durch die Funktion self.generate_agents() erzeugt. | + | Für die erste Erzeugung der Boids bei Start werden Anzahl der Boids, Fressfeinde, Hindernisse sowie für spätere Berechnungen, der Radius festgelgt. |
+ | <code python> | ||
+ | def __init__(self, n, s, r): # B = Boids([30, 1, 0], [1000, 1000], 100) | ||
+ | self.n = n | ||
+ | self.boundry_size = s | ||
+ | self.radius = r | ||
+ | self.boid_positions, self.boid_velocities = self.generate_agents(0) | ||
+ | self.pred_positions, self.pred_velocities = self.generate_agents(1) | ||
+ | self.obstacle_positions = np.random.rand(self.n[2], 2) * self.boundry_size | ||
+ | |||
+ | self.target_framerate = 60 | ||
+ | self.speed = 60 / self.target_framerate | ||
+ | </code> | ||
+ | Eine Liste mit Positionen und Geschwindigkeiten von Boids und Fressfeinden wird zu Beginn durch die Funktion self.generate_agents() erzeugt. Die Fressfeinde bewegen sich schneller als die Boids. | ||
+ | <code python> | ||
+ | # typ 0: boids / typ 1: predators | ||
+ | def generate_agents(self, typ): | ||
+ | positions = np.random.rand(self.n[typ], 2) * self.boundry_size | ||
+ | velocities = np.ones([self.n[typ], 2]) * [1, 0] | ||
+ | angles = np.random.rand(self.n[typ]) * np.pi * 2 | ||
+ | |||
+ | for i in range(self.n[typ]): | ||
+ | velocities[i] = rotate_vector(velocities[i], angles[i]) | ||
+ | return (positions, velocities * self.speed * (2 + typ)) | ||
+ | </code> | ||
==== Target Boids bestimmen ==== | ==== Target Boids bestimmen ==== | ||
Nun werden alle vom aktuellen Boid innerhalb des Radius sichtbare Boids bestimmt. Nur diese spielen später bei der Kräfteberechnung eine Rolle. Die Funktion get_target_mask() gibt ein Array zurück. Dabei wird die Liste aller Positionen der Boids als Grundlage genommen und geschaut, ob der Boid der jeweiligen Position innerhalb oder außerhalb des Radius ist. Befindet er sich im Radius, wird sein Wert auf True gesetzt. Da die Positionen innerhalb der Liste gleich bleiben, kann jeder Boid später auf eine Position zurückverfolgt werden. Hierbei wird unterschieden, ob es sich beim aktuellen Boid um Boid oder Predator handelt. Handelt es sich um einen Predator wird der betrachtete Radius verdreifacht. | Nun werden alle vom aktuellen Boid innerhalb des Radius sichtbare Boids bestimmt. Nur diese spielen später bei der Kräfteberechnung eine Rolle. Die Funktion get_target_mask() gibt ein Array zurück. Dabei wird die Liste aller Positionen der Boids als Grundlage genommen und geschaut, ob der Boid der jeweiligen Position innerhalb oder außerhalb des Radius ist. Befindet er sich im Radius, wird sein Wert auf True gesetzt. Da die Positionen innerhalb der Liste gleich bleiben, kann jeder Boid später auf eine Position zurückverfolgt werden. Hierbei wird unterschieden, ob es sich beim aktuellen Boid um Boid oder Predator handelt. Handelt es sich um einen Predator wird der betrachtete Radius verdreifacht. | ||
+ | <code python> | ||
+ | def get_target_mask(self, current_pos, current_vel = np.zeros(2)): | ||
+ | boid_mask = np.zeros([self.n[0]], dtype=bool) | ||
+ | ... | ||
+ | for i in range(self.n[0]): | ||
+ | if (self.boid_positions[i] != current_pos).all() and np.dot(current_vel, self.boid_positions[i] - current_pos) >= 0: | ||
+ | if current_pos in self.pred_positions: | ||
+ | boid_mask[i] = np.linalg.norm(self.boid_positions[i] - current_pos) < self.radius * 3 | ||
+ | else: | ||
+ | boid_mask[i] = np.linalg.norm(self.boid_positions[i] - current_pos) < self.radius | ||
+ | ... | ||
+ | |||
+ | return (boid_mask, pred_mask, obstacle_mask) | ||
+ | |||
+ | </code> | ||
==== Kräfteberechung ==== | ==== Kräfteberechung ==== | ||
Nachdem alle Boids erzeugt wurden können wir mit der eigentlichen Simulation des Schwarmverhaltens beginnen. Jedes Individuum eines Schwarms folgt immer drei bestimmten Regeln - Separation, Kohäsion und Ausrichtung. Alle drei Kräfte ermöglichen zusammen das typische Schwarmverhalten. Bei uns sind die Kräfte Vektoren, die die neue Richtung des aktuellen Boids anzeigen. | Nachdem alle Boids erzeugt wurden können wir mit der eigentlichen Simulation des Schwarmverhaltens beginnen. Jedes Individuum eines Schwarms folgt immer drei bestimmten Regeln - Separation, Kohäsion und Ausrichtung. Alle drei Kräfte ermöglichen zusammen das typische Schwarmverhalten. Bei uns sind die Kräfte Vektoren, die die neue Richtung des aktuellen Boids anzeigen. | ||
Zeile 62: | Zeile 104: | ||
**Separation** - Der aktuelle Boid (grün) bewegt sich in eine Richtung mit der eine Häufung von mehreren Boids an einem Ort verhindert werden kann. Wir berechnen diese Kraft (roter Pfeil), indem wir die alle Normvektoren zwischen aktuellen Boid (grün) und jeweiligem blauen Boid nach Nähe zum aktuellen Boid gewichten und addieren. | **Separation** - Der aktuelle Boid (grün) bewegt sich in eine Richtung mit der eine Häufung von mehreren Boids an einem Ort verhindert werden kann. Wir berechnen diese Kraft (roter Pfeil), indem wir die alle Normvektoren zwischen aktuellen Boid (grün) und jeweiligem blauen Boid nach Nähe zum aktuellen Boid gewichten und addieren. | ||
+ | <code python> | ||
+ | def get_separation_force(self, current_pos, target_pos): | ||
+ | vectors = current_pos - target_pos | ||
+ | distances = np.reshape(np.linalg.norm(vectors, axis=1), (len(vectors), 1)) | ||
+ | norm_vectors = vectors / distances | ||
+ | weighted_vectors = norm_vectors * (self.radius - distances) | ||
+ | force = np.sum(weighted_vectors, 0) / len(vectors) | ||
+ | return force | ||
+ | </code> | ||
[[http://www.php.net|{{ws2122:boids:rule_alignment.gif}}]] | [[http://www.php.net|{{ws2122:boids:rule_alignment.gif}}]] | ||
Zeile 67: | Zeile 118: | ||
**Alignment** - Der aktuelle Boid bewegt sich ungefähr in die selbe Richtung wie seine Nachbarn. Hierfür gewichten wir die Normvektoren der Geschwindigkeiten der betrachteten Boids nach Abstand zum aktuellen Boid. | **Alignment** - Der aktuelle Boid bewegt sich ungefähr in die selbe Richtung wie seine Nachbarn. Hierfür gewichten wir die Normvektoren der Geschwindigkeiten der betrachteten Boids nach Abstand zum aktuellen Boid. | ||
+ | <code python> | ||
+ | def get_alignment_force(self, current_pos, target_pos, target_vel): | ||
+ | vectors_pos = current_pos - target_pos | ||
+ | distances = np.reshape(np.linalg.norm(vectors_pos, axis=1), (len(vectors_pos), 1)) | ||
+ | |||
+ | length_vectors_vel = np.reshape(np.linalg.norm(target_vel, axis=1), (len(target_vel), 1)) | ||
+ | norm_vectors_vel = target_vel / length_vectors_vel | ||
+ | weighted_vectors = norm_vectors_vel * (self.radius - distances) | ||
+ | force = np.sum(weighted_vectors, 0) / len(weighted_vectors) | ||
+ | return force | ||
+ | </code> | ||
[[http://www.php.net|{{ws2122:boids:rule_cohesion.gif}}]] | [[http://www.php.net|{{ws2122:boids:rule_cohesion.gif}}]] | ||
Zeile 72: | Zeile 134: | ||
**Kohäsion** - Der aktuelle Boid bewegt sich zum Mittelpunkt der betrachteten Boids. Für den Mittelpunkt der betrachteten Boids werden alle Position addiert und durch die Anzahl geteilt. Der Vektor ensteht aus Differenz zwischen Mittelpunkt und Position. | **Kohäsion** - Der aktuelle Boid bewegt sich zum Mittelpunkt der betrachteten Boids. Für den Mittelpunkt der betrachteten Boids werden alle Position addiert und durch die Anzahl geteilt. Der Vektor ensteht aus Differenz zwischen Mittelpunkt und Position. | ||
- | ===== Update ===== | + | <code python> |
- | Zuerst werden aus der Maske alle Positionen der Target boids bestimmt. Nun werden die Kräfte mithilfe der zuvor bestimmten Funktionen berechnet. Ist im Sichtfeld des aktuellen Boid mind. ein Feind, wird die zehnfache Separation-Kraft angewendet - schließlich will der Boid schneller vom Predator wegschwimmen, sobald er ihn sieht. | + | def get_cohesion_force(self, current_pos, target_pos): |
+ | center_position = np.sum(target_pos, 0) / len(target_pos) | ||
+ | force = center_position - current_pos | ||
+ | return force | ||
+ | </code> | ||
+ | ==== Implementierung der Fressfeinde und Hindernisse ==== | ||
+ | Nachdem die Fressfeinde zusammen mit den Boids erzeugt wurden und durch ein Array unterschieden wird, welche Boids als Predator behandelt werden sollen, bekommen sie in der draw-Funktion eine rote Farbe und handeln nach ausgewählten Kräften. Einerseits nutzen die Fressfeinde die Kohäsionskraft, um sich zu den normalen Boids zu bewegen. Andererseits nutzen diese die Separationskraft in Bezug auf die Fressfeinde, verstärkt um den Faktor 10 für ein verstärktes Fluchtverhalten. | ||
+ | |||
+ | Hindernisse funktionieren auf dieselbe Weise, nur dass diese durch Stillstehen keine Geschwindigkeitszuweisung benötigen und daher keine Kohäsionskraft anwenden, also nur von den Boids die Separationskraft berechnet wird. Ihnen wurde die Farbe blau zugeordnet. | ||
+ | |||
+ | ==== Update ==== | ||
+ | Zuerst werden aus der Maske alle Positionen der Target boids bestimmt. | ||
+ | <code python> | ||
+ | def update_velocity(self, current_pos, current_vel): | ||
+ | |||
+ | boid_target_mask, pred_target_mask, obstacle_target_mask = self.get_target_mask(current_pos, current_vel) | ||
+ | |||
+ | # applying target masks | ||
+ | # to get the positions and velocities of visible boids and obstacles | ||
+ | boid_target_pos, boid_target_vel = self.boid_positions[boid_target_mask], self.boid_velocities[boid_target_mask] | ||
+ | pred_target_pos, pred_target_vel = self.pred_positions[pred_target_mask], self.pred_velocities[pred_target_mask] | ||
+ | obstacle_target_pos = self.obstacle_positions[obstacle_target_mask] | ||
+ | ... | ||
+ | </code> | ||
+ | Nun werden die Kräfte mithilfe der zuvor bestimmten Funktionen berechnet. Ist im Sichtfeld des aktuellen Boid mind. ein Feind, wird die zehnfache Separation-Kraft angewendet - schließlich will der Boid schneller vom Predator wegschwimmen, sobald er ihn sieht. | ||
Letztendlich werden die neuen Geschwindigkeiten zu den Positionen hinzuaddiert. | Letztendlich werden die neuen Geschwindigkeiten zu den Positionen hinzuaddiert. | ||
---- | ---- | ||
+ | =====Zusatz: interaktiver Modus in der 2D-Version (debug mode)===== | ||
+ | Der Modus lässt sich durch die Home-Taste (Pos1) starten. Durch das Eingeben einer Zahl von 1-5 wird ein bestimmter Modus ausgewählt. Der interaktive Modus ist dazu da die wirkenden Kräfte auch sehen zu können. | ||
+ | Bei Modus 1-4 wird der Mauszeiger als weiterer Boid behandelt. Mit dem anfangs festgelegten Radius wird ein Kreis um den Mauszeiger dargestellt. Alle weiteren Boids bewegen sich nicht. | ||
+ | Für den Mauszeiger-Boid wird/werden je nach ausgewählten Modus | ||
+ | - die Kohäsionskraft als blaue Linie angezeigt. | ||
+ | - die Alignment-Kraft als blaue Linie angezeigt. | ||
+ | - die Separationskraft als blaue Linie angezeigt. | ||
+ | - alle einzelnen Kräfte (grau) und die insgesamt wirkende Kraft (grün) angezeigt. | ||
+ | Modus 3: Separation {{:ws2122:boids:boids_separation.png?200|Modus 3: Separation}} | ||
+ | {{ :ws2122:boids:boids_alle_kraefte.png?200|Modus 4: alle Kräfte}} | ||
+ | Modus 4: alle Kräfte | ||
+ | |||
+ | In Modus 5 werden die Kräfte wie in Modus 4 dargestellt. Nur diesmal beziehen sie sich nicht auf den Mauszeiger, sondern auf einen Boid. Die Boids bewegen sich in diesem Modus wieder. | ||
+ | ---- | ||
===== Ergebnis ===== | ===== Ergebnis ===== | ||
- | {{:ws2122:boids:boids_aufnahme.mp4|}} | + | 2D |
- | 2D-Version | + | {{:ws2122:boids:boids_aufnahme.mp4|2D-Version}} |
- | {{:ws2122:boids:boids3d_aufnahme.mp4|}} | + | 3D |
- | 3D-Version | + | {{:ws2122:boids:boids3d_aufnahme.mp4|3D-Version}} |
---- | ---- | ||