Ruboid 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2006 Guilhem Vellut <guilhem.vellut+georuby@gmail.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,46 @@
1
+ =GeoRuby
2
+ Ruboid is a ruby implementation of Craig Reynolds' Boid alogrithm (http://www.red3d.com/cwr/boids/), which realistically simulates the behaviour of a flock of creatures with a small set of simple rules. See Conrad Parker's page at http://www.vergenet.net/~conrad/boids/pseudocode.html for a more detailed explanation of how the simulation is performed.
3
+
4
+ ===Operations
5
+ ==== Basic operations
6
+ Currently, the library implements the 3 rules descibed by Reynolds. You could create a Flock and give it a few boids like this:
7
+ flock = Flock.new
8
+ 1.upto(10) do |i|
9
+ flock << Boid.new(Vector.new([rand(300),rand(300)]),
10
+ Vector.new([rand(10)-5,rand(10)-5]))
11
+ end
12
+ Here I created a flock composed of 10 boids with a random starting position and an initial velocity between -5 and 5. The velocity can be understood as the distance the boid moves per time step.
13
+
14
+ To move the flock, you must call the method +update_and_move+ on the flock:
15
+ 1.upto(100) do |i|
16
+ flock.update_and_move
17
+ end
18
+ The flock has just moved of 100 steps. You should perform some actions (like drawing the flock) between each update.
19
+
20
+ ==== More operations
21
+ On top of these basic rules, some addition ones have been implemented:
22
+ - +scatter+: Scatters the flock, not as a reaction to a particular obstacle, but for example, as a reaction to a loud noise. It will break the cohesion. To reverse it, you should use +regroup+.
23
+ - +go_to+: Gives a goal to reach to the boids. Boids will have a tendency to go in the direction of this goal. Use +forget_goal+ to reverse it.
24
+ - +bound+: Encourages the boids to stay in a certain area (for example the drawing area). Use +move_freely+ to reverse it.
25
+ - +rebel+: Adds an element of chance to the movement of boids. Use +calm_down+ to reverse it.
26
+
27
+ ==== Examples
28
+ I have included 2 examples in the distribution. One is stand-alone, the other necessitates RMagick and draws an animated GIF.
29
+
30
+ ==== Vector class
31
+ To ease the computations, I have created a Vector class, which can be of any dimension. All the positions and velocities of the boids are in this format. You can create a vector from an array of value like this:
32
+ v = Vector.new(array)
33
+ And access it like that :
34
+ v[0]
35
+ v.each ...
36
+
37
+
38
+ ===Installation
39
+ To install the latest version, just type :
40
+ gem install Ruboid
41
+
42
+ ===License
43
+ Ruboid is released under the MIT license.
44
+
45
+ ===Support
46
+ Any questions, enhancement proposals, bug notifications or corrections can be sent to mailto:guilhem.vellut+georuby@gmail.com.
data/lib/ruboid.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'ruboid/vector'
2
+ require 'ruboid/ruboid'
@@ -0,0 +1,220 @@
1
+ module Ruboid
2
+
3
+ #Represents a flock of boids, which move together according to Reynolds' rules
4
+ class Flock
5
+ attr_accessor :boids, :velocity_limit, :safety_distance, :rebellion
6
+ attr_accessor :separation_factor, :cohesion_factor, :alignment_factor, :goal_factor
7
+
8
+ include Enumerable
9
+
10
+ def initialize(velocity_limit = 10,
11
+ safety_distance = 30,
12
+ separation_factor = 1.0,
13
+ cohesion_factor = 0.01,
14
+ alignment_factor = 0.125)
15
+
16
+ @boids = []
17
+
18
+ @velocity_limit = velocity_limit
19
+ @safety_distance = safety_distance
20
+ @separation_factor = separation_factor
21
+ @cohesion_factor = cohesion_factor
22
+ @alignment_factor = alignment_factor
23
+ @goal_factor = goal_factor
24
+ @rebellion = rebellion
25
+
26
+ #indicates if the flock must scatter or be grouped
27
+ @flock = 1
28
+
29
+ @goal=nil
30
+ @goal_factor = 0
31
+
32
+ @bound_corner1=nil
33
+ @bound_corner2=nil
34
+ @bound_encouragement = nil
35
+
36
+ @free_will=false
37
+ @rebellion = 0
38
+
39
+ end
40
+
41
+ #Adds a boid to the flock
42
+ def <<(boid)
43
+ @boids << boid
44
+ end
45
+
46
+ def each
47
+ boids.each {|b| yield b}
48
+ end
49
+
50
+ #Scatters the flock, not as a reaction to a particular obstacle, but for example, as a reaction to a loud noise. It will break the cohesion.
51
+ def scatter
52
+ @flock = -1
53
+ end
54
+
55
+ #Regroups the flock
56
+ def regroup
57
+ @flock = 1
58
+ end
59
+
60
+ #set a goal for the flock
61
+ def go_to(goal,goal_factor = 0.05)
62
+ @goal = goal
63
+ @goal_factor = goal_factor
64
+ end
65
+
66
+ #go back to meaningless wandering
67
+ def forget_goal
68
+ @goal = nil
69
+ end
70
+
71
+ #bound the boids
72
+ def bound(corner1,corner2,bound_encouragement = 10)
73
+ @bound_corner1 = corner1
74
+ @bound_corner2 = corner2
75
+ @bound_encouragement = bound_encouragement
76
+ end
77
+
78
+ def move_freely
79
+ @bound_corner1 = nil
80
+ @bound_corner2 = nil
81
+ @bound_encouragement = nil
82
+ end
83
+
84
+ #degree of
85
+ def rebel(rebellion = 5)
86
+ @free_will = true
87
+ @rebellion = rebellion
88
+ end
89
+
90
+ def calm_down
91
+ @free_will = false
92
+ end
93
+
94
+ #Main method of the class : calculates a variation of the velocity of each boid according to the position and velocity of the other boids. Finally update the velocity and the position of each boid.
95
+ def update_and_move
96
+ #calculate the new velocity : keep in a temp variable so all the boids get updated at the same time
97
+ velocity_update = Array.new
98
+
99
+ #calculate the variation of velocity of the boids
100
+ @boids.each do |boid|
101
+ d_vc = boid.cohesion(@boids).mul(@cohesion_factor * @flock)
102
+ d_vs = boid.separation(@boids,@safety_distance).mul(@separation_factor)
103
+ d_va = boid.alignment(@boids).mul(@alignment_factor)
104
+
105
+ d_v = d_vc.add(d_vs).add(d_va)
106
+
107
+ if @goal
108
+ d_vg = boid.go_to(@goal).mul(@goal_factor)
109
+ d_v.add(d_vg)
110
+ end
111
+
112
+ if @bound_corner1
113
+ d_vb = boid.bound(@bound_corner1,@bound_corner2,@bound_encouragement)
114
+ d_v.add(d_vb)
115
+ end
116
+
117
+ if @free_will
118
+ d_vf = Vector.new([rand(@rebellion),rand(@rebellion)])
119
+ d_v.add(d_vf)
120
+ end
121
+
122
+ velocity_update << d_v
123
+ end
124
+
125
+ #calculate a new velocity, limit the speed and update the position of the boids
126
+ @boids.each_with_index do |boid,index|
127
+ boid.velocity.add(velocity_update[index])
128
+ boid.limit(@velocity_limit)
129
+ boid.move
130
+ end
131
+
132
+ end
133
+ end
134
+
135
+ #Represents a boid : You can think of it as a fish or a bird
136
+ class Boid
137
+ #position of the boid
138
+ attr_accessor :position
139
+ #distance done by the boid by unit of time chosen by the application using the lib
140
+ attr_accessor :velocity
141
+
142
+ #the initial parameters of the boid are the responsibility of the calling application
143
+ def initialize(position,velocity)
144
+ @position = position
145
+ @velocity = velocity
146
+ end
147
+
148
+ def dimension
149
+ @position.dimension
150
+ end
151
+
152
+ #Move a boid of one step
153
+ def move
154
+ @position.add(@velocity)
155
+ end
156
+
157
+ #Limits the velocity of the boid so it doesn't go supersonic.
158
+ def limit(velocity_limit)
159
+ velocity_norm = @velocity.norm
160
+ if velocity_norm > velocity_limit
161
+ @velocity.mul(velocity_limit / velocity_norm)
162
+ end
163
+ end
164
+
165
+ def go_to(goal)
166
+ goal.clone.sub(@position)
167
+ end
168
+
169
+ def bound(bound_corner1,bound_corner2,bound_encouragement)
170
+ v = Vector.zero(dimension)
171
+ v.each_index do |i|
172
+ if @position[i] < bound_corner1[i]
173
+ v[i] = bound_encouragement[i]
174
+ elsif @position[i] > bound_corner2[i]
175
+ v[i] = -bound_encouragement[i]
176
+ end
177
+ end
178
+ v
179
+ end
180
+
181
+ #Moves the boid to the center of mass : Rule 1 of Conrad Parker's pseudo code
182
+ def cohesion(boids)
183
+ perceived_center = Vector.zero(dimension)
184
+ boids.each do |b|
185
+ if b != self
186
+ perceived_center.add(b.position)
187
+ end
188
+ end
189
+ perceived_center.div(boids.length - 1).sub(@position)
190
+ end
191
+
192
+ #Moves the boid away from the other boids which are too close to it : Rule 2 of Conrad Parker's pseudo code
193
+ def separation(boids,distance)
194
+ c = Vector.zero(dimension)
195
+ boids.each do |b|
196
+ if b != self
197
+ if b.position.distance_to(@position) < distance
198
+ c.add(@position).sub(b.position)
199
+ end
200
+ end
201
+ end
202
+ c
203
+ end
204
+
205
+ #Modifies the velocity so the boids have the tendency to go in the same direction : Rule 3 of Conrad Parker's pseudo code
206
+ def alignment(boids)
207
+ perceived_velocity = Vector.zero(dimension)
208
+ boids.each do |b|
209
+ if b != self
210
+ perceived_velocity.add(b.velocity)
211
+ end
212
+ end
213
+ perceived_velocity.div(boids.length - 1).sub(@velocity)
214
+ end
215
+
216
+ end
217
+
218
+
219
+
220
+ end
@@ -0,0 +1,98 @@
1
+ module Ruboid
2
+
3
+ #Pure Ruby simplistic vector arithmetic class with no checks at all
4
+ #For speed, the NArray library could be considered (but some part is in C)
5
+ #Vectors taking part in operations are assumed to be of the same dimension
6
+ class Vector
7
+ attr_accessor :coords
8
+
9
+ include Enumerable
10
+
11
+ def initialize(array)
12
+ @coords = array
13
+ end
14
+
15
+ def clone
16
+ Vector.new(@coords.clone)
17
+ end
18
+
19
+ def self.zero(dimension)
20
+ Vector.new(Array.new(dimension,0.0))
21
+ end
22
+
23
+ def to_s
24
+ @coords.join(",")
25
+ end
26
+
27
+ def each
28
+ @coords.each { |c| yield c}
29
+ end
30
+
31
+ def each_index
32
+ 0.upto(dimension-1) { |i| yield i}
33
+ end
34
+
35
+ def dimension
36
+ @coords.length
37
+ end
38
+
39
+ def [](n)
40
+ @coords[n]
41
+ end
42
+
43
+ def []=(n,value)
44
+ @coords[n]=value
45
+ end
46
+
47
+ #Calculates the euclidian distance between 2 vectors
48
+ def distance_to(vector)
49
+ diff = self.clone.sub(vector)
50
+ distance = diff.inject(0) do |distance_acc,value|
51
+ distance_acc + value**2
52
+ end
53
+ Math.sqrt(distance)
54
+ end
55
+
56
+ #Calculates the euclidian norm of the vector
57
+ def norm
58
+ distance_to(Vector.zero(dimension))
59
+ end
60
+
61
+ def add(vector)
62
+ @coords.each_index do |i|
63
+ @coords[i] += vector[i]
64
+ end
65
+ self
66
+ end
67
+
68
+ def sub(vector)
69
+ @coords.each_index do |i|
70
+ @coords[i] -= vector[i]
71
+ end
72
+ self
73
+ end
74
+
75
+ def div(scalar)
76
+ @coords.each_index do |i|
77
+ @coords[i] /= scalar
78
+ end
79
+ self
80
+ end
81
+
82
+ def mul(scalar)
83
+ @coords.each_index do |i|
84
+ @coords[i] *= scalar
85
+ end
86
+ self
87
+ end
88
+
89
+ #Tests for the equality of vectors
90
+ def ==(vector)
91
+ @coords.each_index do |i|
92
+ return false if @coords[i] != vector[i]
93
+ end
94
+ true
95
+ end
96
+ end
97
+
98
+ end
data/rakefile.rb ADDED
@@ -0,0 +1,48 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+
6
+ task :default => :test
7
+
8
+ desc "Run the tests"
9
+ Rake::TestTask::new do |t|
10
+ t.test_files = FileList['test/test*.rb']
11
+ t.verbose = true
12
+ end
13
+
14
+ desc "Generate the documentation"
15
+ Rake::RDocTask::new do |rdoc|
16
+ rdoc.rdoc_dir = 'ruboid-doc/'
17
+ rdoc.title = "Ruboid Documentation"
18
+ rdoc.options << '--line-numbers' << '--inline-source'
19
+ rdoc.rdoc_files.include('README')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ spec = Gem::Specification::new do |s|
24
+ s.platform = Gem::Platform::RUBY
25
+
26
+ s.name = 'Ruboid'
27
+ s.version = "0.0.1"
28
+ s.summary = "Ruby Boid Library"
29
+ s.description ="Ruboid is a ruby implementation of Craig Reynolds' Boid alogrithm (http://www.red3d.com/cwr/boids/), which realistically simulates the behaviour of a flock of creatures with a small set of simple rules."
30
+ s.author = 'Guilhem Vellut'
31
+ s.email = 'guilhem.vellut+georuby@gmail.com'
32
+ s.homepage = "http://thepochisuperstarmegashow.com"
33
+
34
+ s.requirements << 'none'
35
+ s.require_path = 'lib'
36
+ s.files = FileList["lib/**/*.rb", "test/**/*.rb", "README","MIT-LICENSE","rakefile.rb"]
37
+ s.test_files = FileList['test/test*.rb']
38
+
39
+ s.has_rdoc = true
40
+ s.extra_rdoc_files = ["README"]
41
+ s.rdoc_options.concat ['--main', 'README']
42
+ end
43
+
44
+ desc "Package the library as a gem"
45
+ Rake::GemPackageTask.new(spec) do |pkg|
46
+ pkg.need_zip = true
47
+ pkg.need_tar = true
48
+ end
@@ -0,0 +1,71 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'ruboid'
4
+ require 'test/unit'
5
+
6
+ include Ruboid
7
+
8
+ class TestVector < Test::Unit::TestCase
9
+
10
+ def test_add
11
+ a = Vector.new([1,2])
12
+ b = Vector.new([3,4])
13
+ assert_equal([4,6],a.add(b).coords)
14
+ end
15
+
16
+ def test_sub
17
+ a = Vector.new([1.5,2.6])
18
+ b = Vector.new([3.1,4.7])
19
+ assert_equal([-1.6,-2.1],a.sub(b).coords)
20
+ end
21
+
22
+ def test_mul
23
+ a = Vector.new([1.5,2.6])
24
+ assert_equal([3.0,5.2],a.mul(2).coords)
25
+ end
26
+
27
+ def test_div
28
+ a = Vector.new([1.5,2.6])
29
+ assert_equal([0.75,1.3],a.div(2).coords)
30
+ end
31
+
32
+ def test_equal
33
+ a = Vector.new([1.5,2.6])
34
+ b = Vector.new([3.1,4.7])
35
+ assert(a != b)
36
+
37
+ c = Vector.new([1.5,2.6])
38
+ assert(a == c)
39
+ end
40
+
41
+ def test_dimension
42
+ assert_equal(2,Vector.new([1.5,2.6]).dimension)
43
+ end
44
+
45
+ def test_modification
46
+ a = Vector.new([1.5,2.6])
47
+ a[0] = 2
48
+
49
+ assert_equal([2,2.6],a.coords)
50
+ assert_equal(2,a[0])
51
+ end
52
+
53
+ def test_zero
54
+ a = Vector.zero(2)
55
+ assert_equal(2,a.dimension)
56
+ assert_equal([0,0],a.coords)
57
+ end
58
+
59
+ def test_norm
60
+ a = Vector.new([3,4])
61
+ assert_equal(5,a.norm)
62
+ end
63
+
64
+ def test_distance_to
65
+ a = Vector.new([2,3])
66
+ b = Vector.new([2,9])
67
+
68
+ assert_equal(6,a.distance_to(b))
69
+ end
70
+
71
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: Ruboid
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2006-05-11 00:00:00 +05:00
8
+ summary: Ruby Boid Library
9
+ require_paths:
10
+ - lib
11
+ email: guilhem.vellut+georuby@gmail.com
12
+ homepage: http://thepochisuperstarmegashow.com
13
+ rubyforge_project:
14
+ description: Ruboid is a ruby implementation of Craig Reynolds' Boid alogrithm (http://www.red3d.com/cwr/boids/), which realistically simulates the behaviour of a flock of creatures with a small set of simple rules.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Guilhem Vellut
30
+ files:
31
+ - lib/ruboid.rb
32
+ - lib/ruboid/ruboid.rb
33
+ - lib/ruboid/vector.rb
34
+ - test/test_vector.rb
35
+ - README
36
+ - MIT-LICENSE
37
+ - rakefile.rb
38
+ test_files:
39
+ - test/test_vector.rb
40
+ rdoc_options:
41
+ - --main
42
+ - README
43
+ extra_rdoc_files:
44
+ - README
45
+ executables: []
46
+
47
+ extensions: []
48
+
49
+ requirements:
50
+ - none
51
+ dependencies: []
52
+