graphics 1.0.0b1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.autotest +26 -0
- data/.gemtest +0 -0
- data/History.rdoc +12 -0
- data/Manifest.txt +37 -0
- data/README.rdoc +71 -0
- data/Rakefile +26 -0
- data/examples/boid.rb +653 -0
- data/examples/bounce.rb +86 -0
- data/examples/collision.rb +74 -0
- data/examples/demo.rb +90 -0
- data/examples/editor.rb +70 -0
- data/examples/fluid.rb +246 -0
- data/examples/fluid2.rb +199 -0
- data/examples/lito.rb +108 -0
- data/examples/lito2.rb +110 -0
- data/examples/logo.rb +73 -0
- data/examples/math.rb +42 -0
- data/examples/radar.rb +31 -0
- data/examples/tank.rb +160 -0
- data/examples/tank2.rb +173 -0
- data/examples/targeting.rb +46 -0
- data/examples/vants.rb +69 -0
- data/examples/walker.rb +116 -0
- data/examples/zenspider1.rb +93 -0
- data/examples/zenspider2.rb +123 -0
- data/examples/zenspider3.rb +104 -0
- data/examples/zenspider4.rb +90 -0
- data/examples/zombies.rb +385 -0
- data/lib/graphics.rb +9 -0
- data/lib/graphics/body.rb +216 -0
- data/lib/graphics/extensions.rb +48 -0
- data/lib/graphics/simulation.rb +377 -0
- data/lib/graphics/trail.rb +69 -0
- data/lib/graphics/v.rb +71 -0
- data/resources/images/body.png +0 -0
- data/resources/images/turret.png +0 -0
- data/rubysdl_setup.sh +34 -0
- data/test/test_graphics.rb +408 -0
- metadata +191 -0
- metadata.gz.sig +2 -0
checksums.yaml
ADDED
@@ -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
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
ADDED
Binary file
|
data/.autotest
ADDED
@@ -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
|
data/.gemtest
ADDED
File without changes
|
data/History.rdoc
ADDED
data/Manifest.txt
ADDED
@@ -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
|
data/README.rdoc
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/examples/boid.rb
ADDED
@@ -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
|