graphics 1.0.0b1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6db37414cb4bf4a73f707c922329202ada329be1
4
+ data.tar.gz: 57d39f17362848aa3c4e896ca5eee74f10bdac56
5
+ SHA512:
6
+ metadata.gz: 907e6750f1311907d6083bbb09e87b51ad32c401b8990aa47ccb5645bc98c362ab2d9d4d41057b8dfab858a0370b402da7a53ce001647c8ce202f7a9125e1565
7
+ data.tar.gz: 6f4edf4b031ca928dda681d270c885870ed3be0a536028290c7a0d629edec1d28092a6e2a8db6aa177865632d8bfe2601d51caa370dead3a40887c01ad3a253a
Binary file
Binary file
@@ -0,0 +1,26 @@
1
+ # -*- ruby -*-
2
+
3
+ require "autotest/restart"
4
+
5
+ Autotest.add_hook :initialize do |at|
6
+ at.testlib = "minitest/autorun"
7
+ at.add_exception "tmp"
8
+
9
+ # at.extra_files << "../some/external/dependency.rb"
10
+ #
11
+ # at.libs << ":../some/external"
12
+ #
13
+ # at.add_exception "vendor"
14
+ #
15
+ # at.add_mapping(/dependency.rb/) do |f, _|
16
+ # at.files_matching(/test_.*rb$/)
17
+ # end
18
+ #
19
+ # %w(TestA TestB).each do |klass|
20
+ # at.extra_class_map[klass] = "test/test_misc.rb"
21
+ # end
22
+ end
23
+
24
+ # Autotest.add_hook :run_command do |at|
25
+ # system "rake build"
26
+ # end
File without changes
@@ -0,0 +1,12 @@
1
+ === 1.0.0b1 / 2015-08-04
2
+
3
+ * 1 major enhancement
4
+
5
+ * Beta Birthday!
6
+
7
+ === 1.0.0.a1 / 2015-03-31
8
+
9
+ * 1 major enhancement
10
+
11
+ * Alpha Birthday!
12
+
@@ -0,0 +1,37 @@
1
+ .autotest
2
+ History.rdoc
3
+ Manifest.txt
4
+ README.rdoc
5
+ Rakefile
6
+ examples/boid.rb
7
+ examples/bounce.rb
8
+ examples/collision.rb
9
+ examples/demo.rb
10
+ examples/editor.rb
11
+ examples/fluid.rb
12
+ examples/fluid2.rb
13
+ examples/lito.rb
14
+ examples/lito2.rb
15
+ examples/logo.rb
16
+ examples/math.rb
17
+ examples/radar.rb
18
+ examples/tank.rb
19
+ examples/tank2.rb
20
+ examples/targeting.rb
21
+ examples/vants.rb
22
+ examples/walker.rb
23
+ examples/zenspider1.rb
24
+ examples/zenspider2.rb
25
+ examples/zenspider3.rb
26
+ examples/zenspider4.rb
27
+ examples/zombies.rb
28
+ lib/graphics.rb
29
+ lib/graphics/body.rb
30
+ lib/graphics/extensions.rb
31
+ lib/graphics/simulation.rb
32
+ lib/graphics/trail.rb
33
+ lib/graphics/v.rb
34
+ resources/images/body.png
35
+ resources/images/turret.png
36
+ rubysdl_setup.sh
37
+ test/test_graphics.rb
@@ -0,0 +1,71 @@
1
+ = graphics
2
+
3
+ home :: https://github.com/seattlerb/graphics
4
+ rdoc :: http://docs.seattlerb.org/graphics
5
+
6
+ == DESCRIPTION:
7
+
8
+ Graphics provides a simple framework to implement games and/or
9
+ simulations and is designed to follow mathematical conventions, NOT
10
+ game programming conventions. Particularly it:
11
+
12
+ * Uses degrees.
13
+ * Draws in quadrant 1 (0-90 degrees).
14
+ * Right hand rule: 0 degrees is east, 90 is north, etc.
15
+
16
+ These allow simple things like Trigonometry functions to work as
17
+ expected. It means that all that stuff you were taught it grade school
18
+ still work as intended. This makes one less thing you have to adjust
19
+ when implementing your simulation.
20
+
21
+ == FEATURES/PROBLEMS:
22
+
23
+ * REAL MATHS!
24
+ * Simple drawing primitives.
25
+ * PRETTY drawing primitives! Nearly everything is anti-aliased.
26
+ * Plenty of helpers to make your code clean
27
+
28
+ == SYNOPSIS:
29
+
30
+ See examples/*.rb
31
+
32
+ == REQUIREMENTS:
33
+
34
+ * rsdl
35
+ * ruby-sdl
36
+ * libsdl & friends
37
+
38
+ See and/or run rubysdl_setup.sh. If you're on OSX and have homebrew
39
+ installed, running this will ensure you have a working setup.
40
+
41
+ You may want to run `brew update` beforehand to ensure you get
42
+ up-to-date versions.
43
+
44
+ == INSTALL:
45
+
46
+ * sudo gem install graphics
47
+
48
+ == LICENSE:
49
+
50
+ (The MIT License)
51
+
52
+ Copyright (c) Ryan Davis, seattle.rb
53
+
54
+ Permission is hereby granted, free of charge, to any person obtaining
55
+ a copy of this software and associated documentation files (the
56
+ 'Software'), to deal in the Software without restriction, including
57
+ without limitation the rights to use, copy, modify, merge, publish,
58
+ distribute, sublicense, and/or sell copies of the Software, and to
59
+ permit persons to whom the Software is furnished to do so, subject to
60
+ the following conditions:
61
+
62
+ The above copyright notice and this permission notice shall be
63
+ included in all copies or substantial portions of the Software.
64
+
65
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
66
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
67
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
68
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
69
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
70
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
71
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,26 @@
1
+ # -*- ruby -*-
2
+
3
+ require "rubygems"
4
+ require "hoe"
5
+
6
+ Hoe.plugin :isolate
7
+ Hoe.plugin :minitest, :history, :email # Hoe.plugin :seattlerb - :perforce
8
+ Hoe.plugin :rdoc
9
+ Hoe.plugin :git
10
+
11
+ Hoe.spec "graphics" do
12
+ developer "Ryan Davis", "ryand-ruby@zenspider.com"
13
+ license "MIT"
14
+
15
+ dependency "rsdl", "~> 0.1"
16
+ dependency "rubysdl", "~> 2.2"
17
+ end
18
+
19
+ task :demos do
20
+ Dir["examples/*.rb"].each do |script|
21
+ puts script
22
+ system "rsdl -Ilib #{script}"
23
+ end
24
+ end
25
+
26
+ # vim: syntax=ruby
@@ -0,0 +1,653 @@
1
+ #!/usr/local/bin/ruby -w
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require "graphics"
5
+
6
+ class Boid < Graphics::Body
7
+ COUNT = 50
8
+
9
+ AA = SDL::Surface::TRANSFORM_AA
10
+
11
+ PCT_DAMPENER = 0.01
12
+ TOO_CLOSE = 50
13
+ MAX_VELOCITY = 5
14
+
15
+ @@max_distance = 100
16
+
17
+ def self.max_distance= n
18
+ @@max_distance = n
19
+ end
20
+
21
+ def self.max_distance
22
+ @@max_distance
23
+ end
24
+
25
+ def initialize w
26
+ super
27
+
28
+ self.m = rand(20)
29
+ self.a = random_angle
30
+ end
31
+
32
+ def draw
33
+ # the blit looks HORRIBLE when rotated... dunno why
34
+ w.circle x, y, @@max_distance, :gray if w.visual_debug?
35
+ w.blit w.body_img, x, y, 0, AA
36
+ w.angle x, y, a, 3 * m, :red
37
+ end
38
+
39
+ def label
40
+ l = "%.1f [%.2f, %.2f]" % [a, *dx_dy]
41
+ w.text l, x-10, y+10, :white
42
+ end
43
+
44
+ def update
45
+ v1 = rule1
46
+ v2 = rule2
47
+ v3 = rule3
48
+
49
+ self.velocity += v1 + v2 + v3
50
+ limit_velocity
51
+ self.position += self.velocity
52
+
53
+ @nearby = nil
54
+ end
55
+
56
+ def nearby
57
+ @nearby ||= begin
58
+ p = self.position
59
+ w.boids.find_all { |b| (b.position - p).magnitude.abs < @@max_distance }
60
+ end
61
+ end
62
+
63
+ def limit_velocity
64
+ if velocity.magnitude > MAX_VELOCITY then
65
+ self.velocity = (self.velocity / self.velocity.magnitude) * MAX_VELOCITY
66
+ end
67
+ end
68
+
69
+ def center_mass
70
+ pos = V::ZERO
71
+ nearby.each do |b|
72
+ next if self == b
73
+
74
+ pos += b.position
75
+ end
76
+
77
+ size = nearby.size - 1
78
+
79
+ return self.position if size == 0
80
+
81
+ pos /= size
82
+
83
+ pos
84
+ end
85
+
86
+ ##
87
+ # Rule 1: Boids try to fly towards the centre of mass of neighbouring
88
+ # boids.
89
+ #
90
+ # The 'centre of mass' is simply the average position of all the boids.
91
+ # I use the term centre of mass by analogy with the corresponding
92
+ # physical formula (however we ignore individual masses here and treat
93
+ # all boids having the same mass).
94
+ #
95
+ # Assume we have N boids, called b1, b2, ..., bN. Also, the position of
96
+ # a boid b is denoted b.position. Then the 'centre of mass' c of all N
97
+ # boids is given by:
98
+ #
99
+ # c = (b1.position + b2.position + ... + bN.position) / N
100
+ #
101
+ # Remember that the positions here are vectors, and N is a scalar.
102
+ #
103
+ # However, the 'centre of mass' is a property of the entire flock; it is
104
+ # not something that would be considered by an individual boid. I prefer
105
+ # to move the boid toward its 'perceived centre', which is the centre of
106
+ # all the other boids, not including itself. Thus, for boidJ (1 <= J <=
107
+ # N), the perceived centre pcJ is given by:
108
+ #
109
+ # pcJ = (b1.position + b2.position + ... + bJ-1.position +
110
+ # bJ+1.position + ... + bN.position) / (N-1)
111
+ #
112
+ # Having calculated the perceived centre, we need to work out how to
113
+ # move the boid towards it. To move it 1% of the way towards the centre
114
+ # (this is about the factor I use) this is given by (pcJ - bJ.position)
115
+ # / 100.
116
+ #
117
+ # Summarising this in pseudocode:
118
+ #
119
+ # PROCEDURE rule1(boid bJ)
120
+ #
121
+ # Vector pcJ
122
+ #
123
+ # FOR EACH BOID b
124
+ # IF b != bJ THEN
125
+ # pcJ = pcJ + b.position
126
+ # END IF
127
+ # END
128
+ #
129
+ # pcJ = pcJ / N-1
130
+ #
131
+ # RETURN (pcJ - bJ.position) / 100
132
+ #
133
+ # END PROCEDURE
134
+ #
135
+ # Thus we have calculated the first vector offset, v1, for the boid.
136
+
137
+ def rule1
138
+ (center_mass - self.position) * PCT_DAMPENER
139
+ end
140
+
141
+ ##
142
+ # Rule 2: Boids try to keep a small distance away from other objects
143
+ # (including other boids).
144
+ #
145
+ # The purpose of this rule is to for boids to make sure they don't
146
+ # collide into each other. I simply look at each boid, and if it's
147
+ # within a defined small distance (say 100 units) of another boid move
148
+ # it as far away again as it already is. This is done by subtracting
149
+ # from a vector c the displacement of each boid which is near by. We
150
+ # initialise c to zero as we want this rule to give us a vector which
151
+ # when added to the current position moves a boid away from those near
152
+ # it.
153
+ #
154
+ # In pseudocode:
155
+ #
156
+ # PROCEDURE rule2(boid bJ)
157
+ #
158
+ # Vector c = 0;
159
+ #
160
+ # FOR EACH BOID b
161
+ # IF b != bJ THEN
162
+ # IF |b.position - bJ.position| < 100 THEN
163
+ # c = c - (b.position - bJ.position)
164
+ # END IF
165
+ # END IF
166
+ # END
167
+ #
168
+ # RETURN c
169
+ #
170
+ # END PROCEDURE
171
+ #
172
+ # It may seem odd that we choose to simply double the distance from
173
+ # nearby boids, as it means that boids which are very close are not
174
+ # immediately "repelled". Remember that if two boids are near each
175
+ # other, this rule will be applied to both of them. They will be
176
+ # slightly steered away from each other, and at the next time step if
177
+ # they are still near each other they will be pushed further apart.
178
+ # Hence, the resultant repulsion takes the form of a smooth
179
+ # acceleration. It is a good idea to maintain a principle of ensuring
180
+ # smooth motion. If two boids are very close to each other it's probably
181
+ # because they have been flying very quickly towards each other,
182
+ # considering that their previous motion has also been restrained by
183
+ # this rule. Suddenly jerking them away from each other, such that they
184
+ # each have their motion reversed, would appear unnatural, as if they
185
+ # bounced off each other's invisible force fields. Instead, we have them
186
+ # slow down and accelerate away from each other until they are far
187
+ # enough apart for our liking.
188
+
189
+ def rule2
190
+ c = V::ZERO
191
+
192
+ hits = 0
193
+
194
+ nearby.each do |b|
195
+ next if self == b
196
+ diff = b.position - self.position
197
+ next unless diff.magnitude.abs < TOO_CLOSE
198
+ hits += 1
199
+ c -= diff if diff.magnitude.abs < TOO_CLOSE
200
+ end
201
+
202
+ c /= hits unless hits == 0 # average it out so they don't overdo it
203
+
204
+ c / 8
205
+ end
206
+
207
+ ##
208
+ # Rule 3: Boids try to match velocity with near boids.
209
+ #
210
+ # This is similar to Rule 1, however instead of averaging the positions
211
+ # of the other boids we average the velocities. We calculate a
212
+ # 'perceived velocity', pvJ, then add a small portion (about an eighth)
213
+ # to the boid's current velocity.
214
+ #
215
+ # PROCEDURE rule3(boid bJ)
216
+ #
217
+ # Vector pvJ
218
+ #
219
+ # FOR EACH BOID b
220
+ # IF b != bJ THEN
221
+ # pvJ = pvJ + b.velocity
222
+ # END IF
223
+ # END
224
+ #
225
+ # pvJ = pvJ / N-1
226
+ #
227
+ # RETURN (pvJ - bJ.velocity) / 8
228
+ #
229
+ # END PROCEDURE
230
+
231
+ def rule3
232
+ v = V::ZERO
233
+
234
+ nearby.each do |b|
235
+ next if self == b
236
+ v += b.velocity
237
+ end
238
+
239
+ size = nearby.size - 1
240
+
241
+ return self.velocity if size == 0
242
+
243
+ v /= size unless size == 0
244
+
245
+ (v - self.velocity) / 4
246
+ end
247
+ end
248
+
249
+ class Boids < Graphics::Simulation
250
+ attr_accessor :boids, :body_img, :cmap, :visual_debug
251
+
252
+ alias :visual_debug? :visual_debug
253
+
254
+ def initialize
255
+ super 850, 850, 16, "Boid"
256
+
257
+ SDL::Key.enable_key_repeat 500, 250
258
+
259
+ self.visual_debug = false
260
+
261
+ self.boids = populate Boid
262
+
263
+ self.body_img = sprite 20, 20 do
264
+ circle 10, 10, 5, :white, :filled
265
+ end
266
+ end
267
+
268
+ def update n
269
+ boids.each(&:update)
270
+ self.boids.each(&:wrap)
271
+ # sleep 0.1
272
+ end
273
+
274
+ def handle_event e, n
275
+ case e
276
+ when SDL::Event::KeyDown then
277
+ self.visual_debug = ! visual_debug if SDL::Key.press? SDL::Key::D
278
+
279
+ Boid.max_distance += 5 if SDL::Key.press? SDL::Key::B
280
+ Boid.max_distance -= 5 if SDL::Key.press? SDL::Key::S
281
+ else
282
+ super
283
+ end
284
+ end
285
+
286
+ def draw n
287
+ clear
288
+
289
+ boids.each(&:draw)
290
+
291
+ debug "r = #{Boid.max_distance}" if visual_debug?
292
+ fps n
293
+ end
294
+ end
295
+
296
+ if $0 == __FILE__
297
+ if ARGV.first == "test"
298
+ require "minitest/autorun"
299
+ else
300
+ Boids.new.run
301
+ end
302
+ end
303
+
304
+ # Further tweaks
305
+ #
306
+ # The three boids rules sufficiently demonstrate a complex emergent
307
+ # flocking behaviour. They are all that is required to simulate a
308
+ # distributed, leaderless flocking behaviour. However in order to make
309
+ # other aspects of the behaviour more life-like, extra rules and
310
+ # limitations can be implemented.
311
+ #
312
+ # These rules will simply be called in the
313
+ # move_all_boids_to_new_positions() procedure as follows:
314
+ #
315
+ # PROCEDURE move_all_boids_to_new_positions()
316
+ #
317
+ # Vector v1, v2, v3, v4, ...
318
+ #
319
+ # FOR EACH BOID b
320
+ # v1 = rule1(b)
321
+ # v2 = rule2(b)
322
+ # v3 = rule3(b)
323
+ # v4 = rule4(b)
324
+ # .
325
+ # .
326
+ # .
327
+ #
328
+ # b.velocity = b.velocity + v1 + v2 + v3 + v4 + ...
329
+ # b.position = b.position + b.velocity
330
+ # END
331
+ #
332
+ # END PROCEDURE
333
+ #
334
+ # Hence each of the following rules is implemented as a new procedure
335
+ # returning a vector to be added to a boid's velocity.
336
+ #
337
+ # Goal setting
338
+ #
339
+ # Reynolds [1] uses goal setting to steer a flock down a set path or in
340
+ # a general direction, as required to ensure generally predictable
341
+ # motion for use in computer animations and film work. I have not used
342
+ # such goal setting in my simulations, however here are some example
343
+ # implementations:
344
+ #
345
+ # Action of a strong wind or current
346
+ #
347
+ # For example, to simulate fish schooling in a moving river or birds
348
+ # flying through a strong breeze.
349
+ #
350
+ # PROCEDURE strong_wind(Boid b)
351
+ # Vector wind
352
+ #
353
+ # RETURN wind
354
+ # END PROCEDURE
355
+ #
356
+ # This function returns the same value independent of the boid being
357
+ # examined; hence the entire flock will have the same push due to the
358
+ # wind.
359
+ #
360
+ # Tendency towards a particular place
361
+ #
362
+ # For example, to steer a sparse flock of sheep or cattle to a narrow
363
+ # gate. Upon reaching this point, the goal for a particular boid could
364
+ # be changed to encourage it to move away to make room for other members
365
+ # of the flock. Note that if this 'gate' is flanked by impenetrable
366
+ # objects as accounted for in Rule 2 above, then the flock will
367
+ # realistically mill around the gate and slowly trickle through it.
368
+ #
369
+ # PROCEDURE tend_to_place(Boid b)
370
+ # Vector place
371
+ #
372
+ # RETURN (place - b.position) / 100
373
+ # END PROCEDURE
374
+ #
375
+ # Note that this rule moves the boid 1% of the way towards the goal at
376
+ # each step. Especially for distant goals, one may want to limit the
377
+ # magnitude of the returned vector.
378
+ #
379
+ # Limiting the speed
380
+ #
381
+ # I find it a good idea to limit the magnitude of the boids' velocities,
382
+ # this way they don't go too fast. Without such limitations, their speed
383
+ # will actually fluctuate with a flocking-like tendency, and it is
384
+ # possible for them to momentarily go very fast. We assume that real
385
+ # animals can't go arbitrarily fast, and so we limit the boids' speed.
386
+ # (Note that I am using the physical definitions of velocity and speed
387
+ # here; velocity is a vector and thus has both magnitude and direction,
388
+ # whereas speed is a scalar and is equal to the magnitude of the
389
+ # velocity).
390
+ #
391
+ # For a limiting speed vlim:
392
+ #
393
+ # PROCEDURE limit_velocity(Boid b)
394
+ # Integer vlim
395
+ # Vector v
396
+ #
397
+ # IF |b.velocity| > vlim THEN
398
+ # b.velocity = (b.velocity / |b.velocity|) * vlim
399
+ # END IF
400
+ # END PROCEDURE
401
+ #
402
+ # This procedure creates a unit vector by dividing b.velocity by its
403
+ # magnitude, then multiplies this unit vector by vlim. The resulting
404
+ # velocity vector has the same direction as the original velocity but
405
+ # with magnitude vlim.
406
+ #
407
+ # Note that this procedure operates directly on b.velocity, rather than
408
+ # returning an offset vector. It is not used like the other rules;
409
+ # rather, this procedure is called after all the other rules have been
410
+ # applied and before calculating the new position, ie. within the
411
+ # procedure move_all_boids_to_new_positions:
412
+ #
413
+ # b.velocity = b.velocity + v1 + v2 + v3 + ...
414
+ # limit_velocity(b)
415
+ # b.position = b.position + b.velocity
416
+ # Bounding the position
417
+ #
418
+ # In order to keep the flock within a certain area (eg. to keep them
419
+ # on-screen) Rather than unrealistically placing them within some
420
+ # confines and thus bouncing off invisible walls, we implement a rule
421
+ # which encourages them to stay within rough boundaries. That way they
422
+ # can fly out of them, but then slowly turn back, avoiding any harsh
423
+ # motions.
424
+ #
425
+ # PROCEDURE bound_position(Boid b)
426
+ # Integer Xmin, Xmax, Ymin, Ymax, Zmin, Zmax
427
+ # Vector v
428
+ #
429
+ # IF b.position.x < Xmin THEN
430
+ # v.x = 10
431
+ # ELSE IF b.position.x > Xmax THEN
432
+ # v.x = -10
433
+ # END IF
434
+ # IF b.position.y < Ymin THEN
435
+ # v.y = 10
436
+ # ELSE IF b.position.y > Ymax THEN
437
+ # v.y = -10
438
+ # END IF
439
+ # IF b.position.z < Zmin THEN
440
+ # v.z = 10
441
+ # ELSE IF b.position.z > Zmax THEN
442
+ # v.z = -10
443
+ # END IF
444
+ #
445
+ # RETURN v
446
+ # END PROCEDURE
447
+ #
448
+ # Here of course the value 10 is an arbitrary amount to encourage them
449
+ # to fly in a particular direction.
450
+ #
451
+ # Perching
452
+ #
453
+ # The desired behaviour here has the boids occasionally landing and
454
+ # staying on the ground for a brief period of time before returning to
455
+ # the flock. This is accomplished by simply holding the boid on the
456
+ # ground for a breif period (of random length) whenever it gets to
457
+ # ground level, and then letting it go.
458
+ #
459
+ # When checking the bounds, we test if the boid is at or below ground
460
+ # level, and if so we make it perch. We introduce the Boolean b.perching
461
+ # for each boid b. In addition, we introduce a timer b.perch_timer which
462
+ # determines how long the boid will perch for. We make this a random
463
+ # time, assuming we are simulating the boid eating or resting.
464
+ #
465
+ # Thus, within the bound_position procedure, we add the following lines:
466
+ #
467
+ # Integer GroundLevel
468
+ #
469
+ # ...
470
+ #
471
+ # IF b.position.y < GroundLevel THEN
472
+ # b.position.y = GroundLevel
473
+ # b.perching = True
474
+ # END IF
475
+ #
476
+ # It is held on the ground by simply not applying the boids rules to its
477
+ # behaviour (obviously, as we don't want it to move). Thus, before
478
+ # attempting to apply the rules we check if the boid is perching, and if
479
+ # so we decrement the timer b.perch_timer and skip the rest of the loop.
480
+ # If the boid has finished perching then we reset the b.perching flag to
481
+ # allow it to return to the flock.
482
+ #
483
+ # PROCEDURE move_all_boids_to_new_positions()
484
+ #
485
+ # Vector v1, v2, v3, ...
486
+ # Boid b
487
+ #
488
+ # FOR EACH BOID b
489
+ #
490
+ # IF b.perching THEN
491
+ # IF b.perch_timer > 0 THEN
492
+ # b.perch_timer = b.perch_timer - 1
493
+ # NEXT
494
+ # ELSE
495
+ # b.perching = FALSE
496
+ # END IF
497
+ # END IF
498
+ #
499
+ #
500
+ # v1 = rule1(b)
501
+ # v2 = rule2(b)
502
+ # v3 = rule3(b)
503
+ # ...
504
+ #
505
+ # b.velocity = b.velocity + v1 + v2 + v3 + ...
506
+ # ...
507
+ # b.position = b.position + b.velocity
508
+ # END
509
+ # END PROCEDURE
510
+ #
511
+ # Note that nothing else needs to be done to simulate the perching
512
+ # behaviour. As soon as we re-apply the boids rules this boid will fly
513
+ # directly towards the flock and continue on as normal.
514
+ #
515
+ # A detail I implement here is that the lower bound for the boids'
516
+ # motion is actually a little above ground level. That way the boids are
517
+ # actually discouraged from going too near the ground, and when they do
518
+ # go to the ground they land gently rather than ploughing into it as
519
+ # there is an upward push from the bounding rule. They also land less
520
+ # often which stops them becoming too lazy.
521
+ #
522
+ # Anti-flocking behaviour
523
+ #
524
+ # During the course of a simulation, one may want to break up the flock
525
+ # for various reasons. For example the introduction of a predator may
526
+ # cause the flock to scatter in all directions.
527
+ #
528
+ # Scattering the flock
529
+ #
530
+ # Here we simply want the flock to disperse; they are not necessarily
531
+ # moving away from any particular object, we just want to break the
532
+ # cohesion (for example, the flock is startled by a loud noise). Thus we
533
+ # actually want to negate part of the influence of the boids rules.
534
+ #
535
+ # Of the three rules, it turns out we only want to negate the first one
536
+ # (moving towards the centre of mass of neighbours) -- ie. we want to
537
+ # make the boids move away from the centre of mass. As for the other
538
+ # rules: negating the second rule (avoiding nearby objects) will simply
539
+ # cause the boids to actively run into each other, and negating the
540
+ # third rule (matching velocity with nearby boids) will introduce a
541
+ # semi-chaotic oscillation.
542
+ #
543
+ # It is a good idea to use non-constant multipliers for each of the
544
+ # rules, allowing you to vary the influence of each rule over the course
545
+ # of the simulation. If you put these multipliers in the
546
+ # move_all_boids_to_new_positions procedure, ending up with something
547
+ # like:
548
+ #
549
+ # PROCEDURE move_all_boids_to_new_positions()
550
+ #
551
+ # Vector v1, v2, v3, ...
552
+ # Integer m1, m2, m3, ...
553
+ # Boid b
554
+ #
555
+ # FOR EACH BOID b
556
+ #
557
+ # ...
558
+ #
559
+ # v1 = m1 * rule1(b)
560
+ # v2 = m2 * rule2(b)
561
+ # v3 = m3 * rule3(b)
562
+ # ...
563
+ #
564
+ # b.velocity = b.velocity + v1 + v2 + v3 + ...
565
+ # ...
566
+ # b.position = b.position + b.velocity
567
+ # END
568
+ #
569
+ # END PROCEDURE
570
+ #
571
+ # then, during the course of the simulation, simply make m1 negative to
572
+ # scatter the flock. Setting m1 to a positive value again will cause the
573
+ # flock to spontaneously re-form.
574
+ #
575
+ # Tendency away from a particular place
576
+ #
577
+ # If, on the other hand, we want the flock to continue the flocking
578
+ # behaviour but to move away from a particular place or object (such as
579
+ # a predator), then we need to move each boid individually away from
580
+ # that point. The calculation required is identical to that of moving
581
+ # towards a particular place, implemented above as tend_to_place; all
582
+ # that is required is a negative multiplier:
583
+ #
584
+ # Vector v
585
+ # Integer m
586
+ # Boid b
587
+ #
588
+ # ...
589
+ #
590
+ # v = -m * tend_to_place(b)
591
+ #
592
+ # So we see that each of the extra routines are very simple to
593
+ # implement, as are the initial rules. We achieve complex, life-like
594
+ # behaviour by combining all of them together. By varying the influence
595
+ # of each rule over time we can change the behaviour of the flock to
596
+ # respond to events in the environment such as sounds, currents and
597
+ # predators.
598
+ #
599
+ # Auxiliary functions
600
+ #
601
+ # You will find it handy to set up a set of Vector manipulation routines
602
+ # first to do addition, subtraction and scalar multiplication and
603
+ # division. For example, all the additions and subtractions in the above
604
+ # pseudocode are vector operations, so for example the line:
605
+ #
606
+ # pcJ = pcJ + b.position
607
+ #
608
+ # will end up looking something like:
609
+ #
610
+ # pcJ = Vector_Add(pcJ, b.position)
611
+ #
612
+ # where Vector_Add is a procedure defined thus:
613
+ #
614
+ # PROCEDURE Vector_Add(Vector v1, Vector v2)
615
+ #
616
+ # Vector v
617
+ #
618
+ # v.x = v1.x + v2.x
619
+ # v.y = v1.y + v2.y
620
+ # v.z = v1.z + v2.z
621
+ #
622
+ # RETURN v
623
+ #
624
+ # END PROCEDURE
625
+ #
626
+ # and the line:
627
+ #
628
+ # pcJ = pcJ / N-1
629
+ #
630
+ # will be something like:
631
+ #
632
+ # pcJ = Vector_Div(pcJ, N-1)
633
+ #
634
+ # where Vector_Div is a scalar division:
635
+ #
636
+ # PROCEDURE Vector_Div(Vector v1, Integer A)
637
+ # Vector v
638
+ #
639
+ # v.x = v1.x / A
640
+ # v.y = v1.y / A
641
+ # v.z = v1.z / A
642
+ #
643
+ # RETURN v
644
+ # END PROCEDURE
645
+ #
646
+ # Of course if you're doing this in two dimensions you won't need the
647
+ # z-axis terms, and if you're doing this in more than three dimensions
648
+ # you'll need to add more terms :)
649
+ #
650
+ # References
651
+ #
652
+ # [1] Craig W. Reynold's home page, http://www.red3d.com/cwr/
653
+ # [2] Computer Graphics, Principles and Practice by Foley, van Dam, Feiner and Hughes, Addison Wesley 1990