Welcome, guest | Sign In | My Account | Store | Cart

Recipe 578004 provides a simple way of creating 2D graphical experiments based on http://processing.org/ and its easy-to-use Java framework. This module is meant to be a simple demonstration of that recipe and shows a simple BOIDs example that uses processing. Though the API is highly incomplete and slightly different, it shows what was inspired by the faster-running http://processing.org/learning/topics/flocking.html .

Python, 242 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#! /usr/bin/env python
import collections
import math
import random
import processing
import vector

################################################################################

# QVGA Resolution
WIDTH = 320
HEIGHT = 240

################################################################################

class Demo(processing.Process):

    "Demo.main(width, height) -> Starts the demonstration"

    NOT_CREATING_FISH = -1
    SCHOOLS = ('red', 'yellow'), ('blue', 'green')

    def setup(self, background):
        "Setup the screen and boids before starting simulation."
        background('black')
        self.schools = []
        self.sources = []
        for body_color, trim_color in self.SCHOOLS:
            new_school = School(body_color, trim_color)
            new_source = vector.Vector2(random.random() * WIDTH,
                                        random.random() * HEIGHT)
            self.schools.append(new_school)
            self.sources.append(new_source)
        self.pointer = 0

    def render(self, graphics):
        "Displays the boids on the graphics every time called."
        self.add_fish()
        graphics.clear()
        fish = 0
        for school in self.schools:
            school.render(graphics)
            fish += school.size()
        graphics.write(5, 5, fish, 'white')

    def add_fish(self):
        "Add a fish to the next school while creating more fish."
        if self.pointer != self.NOT_CREATING_FISH:
            new_fish = Fish(self.sources[self.pointer])
            self.schools[self.pointer].add_fish(new_fish)
            self.pointer = (self.pointer + 1) % len(self.SCHOOLS)

    def update(self, interval):
        "Run one step of the physics simulation for the interval."
        for school in self.schools:
            school.update(interval)

    def mouse_pressed(self, event):
        "Create fish at cursor and add to the smallest school."
        new_fish = Fish(vector.Vector2(event.x, event.y))
        min(self.schools, key=School.size).add_fish(new_fish)

    def speed_warning(self):
        "Stop creating fish and remove fish from largest school."
        self.pointer = self.NOT_CREATING_FISH
        max(self.schools, key=School.size).kill_fish()

################################################################################

class Fish:

    "Fish(location) -> Fish"

    HOW_WIDE = 6            # Width of the fish
    HOW_LONG = 12           # Length of the fish
    
    MAX_FORCE = 0.05        # Maximum directional steering force
    MAX_SPEED = 60.0        # Maximum speed at which to travel

    SEP_FACTOR = 1.5        # Arbitrary separation mutliplier
    ALI_FACTOR = 1.0        # Arbitrary alignment mutliplier
    COH_FACTOR = 1.0        # Arbitrary cohesion mutliplier
    
    DESIRED_SEPARATION = 7  # Turn from each other when closer
    NEIGHBOR_DISTANCE = 17  # Maximum distance for interactions

    ########################################################################

    # DO NOT CHANGE THE FOLLOWING SECTION
    
    limits = math.hypot(HOW_LONG, HOW_WIDE)
    radius = limits / 2

    DESIRED_SEPARATION += limits
    NEIGHBOR_DISTANCE += limits
    
    TOP = 0 - radius
    LEFT = 0 - radius
    RIGHT = WIDTH + radius
    BOTTOM = HEIGHT + radius
    
    SHAPE = processing.Polygon(vector.Vector2(HOW_LONG / +2, 0),
                               vector.Vector2(HOW_LONG / -2, HOW_WIDE / +2),
                               vector.Vector2(HOW_LONG / -2, HOW_WIDE / -2))
    
    del limits, radius, HOW_LONG, HOW_WIDE

    __slots__ = 'location', 'velocity', 'steering', 'body_color', 'trim_color'

    # END OF PRECALCULATED FISH VARIABLES

    ########################################################################

    def __init__(self, location):
        "Initialize the fish with several vectors and colors."
        self.location = location.copy()
        self.velocity = vector.Polar2(random.random() * self.MAX_SPEED,
                                      random.random() * 360)
        self.body_color = ''
        self.trim_color = ''

    def paint(self, body_color, trim_color):
        "Assign colors to the fish's body and trim (outline)."
        self.body_color = body_color
        self.trim_color = trim_color

    def render(self, graphics):
        "Draw the fish's shape on the given graphics context."
        polygon = self.SHAPE.copy()
        polygon.rotate(self.velocity.direction)
        polygon.translate(self.location)
        graphics.draw(polygon, self.body_color, self.trim_color)

    def run_AI(self, school):
        "Execute the three boid rules and store in steering."
        self.steering = vector.Vector2(0, 0)
        # Follow rules of separation, alignment, and cohesion.
        separation = vector.Vector2(0, 0)
        alignment = vector.Vector2(0, 0)
        cohesion = vector.Vector2(0, 0)
        # Track fish that are too close along with neighbours.
        too_close = False
        neighbors = 0
        # Loop over all other fish from the school fish is in.
        for fish in school:
            if fish is not self:
                # Get the difference in location and distance.
                offset = self.location - fish.location
                length = offset.magnitude
                # Find fish that are too close to current one.
                if length < self.DESIRED_SEPARATION:
                    separation += offset.normalize() / length
                    too_close = True
                # Try joining fish in fish's present vicinity.
                if length < self.NEIGHBOR_DISTANCE:
                    alignment += fish.velocity
                    cohesion += fish.location
                    neighbors += 1
        # Steer away from fish in this school that are nearby.
        if too_close:
            self.steering += self.correction(separation) * self.SEP_FACTOR
        # Gather with and align to schoolmates detected above.
        if neighbors:
            self.steering += self.correction(alignment) * self.ALI_FACTOR
            cohesion /= neighbors
            cohesion -= self.location
            self.steering += self.correction(cohesion) * self.ALI_FACTOR

    def correction(self, target):
        "Create a force towards the direction of the target."
        target.magnitude = self.MAX_SPEED
        return (target - self.velocity).limit(self.MAX_FORCE)

    def update(self, interval):
        "Change velocity and location with respect to time."
        self.velocity += self.steering / interval
        self.location += self.velocity.limit(self.MAX_SPEED) * interval
        self.wraparound()
    
    def wraparound(self):
        "Move the fish to wrap around the edges of the screen."
        if self.location.y < self.TOP:
            self.location.y = self.BOTTOM
        elif self.location.y > self.BOTTOM:
            self.location.y = self.TOP
        if self.location.x < self.LEFT:
            self.location.x = self.RIGHT
        elif self.location.x > self.RIGHT:
            self.location.x = self.LEFT

################################################################################

class School:

    "School(body_color, trim_color) -> School"

    __slots__ = 'body_color', 'trim_color', 'fish_deque'

    def __init__(self, body_color, trim_color):
        "Initialize school with color identity and fish container."
        self.body_color = body_color
        self.trim_color = trim_color
        self.fish_deque = collections.deque()

    def add_fish(self, fish):
        "Paint the fish with identity before adding to fish list."
        fish.paint(self.body_color, self.trim_color)
        self.fish_deque.append(fish)

    def remove_fish(self):
        "Take a fish from this school and return fish to caller."
        return self.fish_deque.popleft()

    def size(self):
        "Get number of fish in school and return the total count."
        return len(self.fish_deque)

    def render(self, graphics):
        "Draw each fish in this school to the graphics context."
        for fish in self.fish_deque:
            fish.render(graphics)

    def update(self, interval):
        "Run the AI code of each fish before updating positions."
        for fish in self.fish_deque:
            fish.run_AI(self.fish_deque)
        for fish in self.fish_deque:
            fish.update(interval)

    def kill_fish(self):
        "If there are any fish in this school, remove one of them."
        if self.size() > 0:
            self.remove_fish()

################################################################################

import recipe576904; recipe576904.bind_all(globals())
    
################################################################################

if __name__ == '__main__':
    Demo.main(WIDTH, HEIGHT)