iamwilhelm / frock

A flocking simulation written in Lua Love

This URL has Read+Write access

Wilhelm Chung (author)
Tue Mar 03 21:31:56 -0800 2009
commit  957eb561d41ea15b3ce0f6af5ceb3132378fcbf3
tree    60a34822cd3c5324be15e06eb7b203c69270ad36
parent  2c8cec83778a689e94552b6f22babe17b12c6dfe
frock / boid.lua
100644 188 lines (159 sloc) 6.144 kb
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
require 'vector.lua'
 
Boid = {
   identity = "Boid class",
   radius = 15
}
 
Boid.MAX_SPEED = 175
Boid.MIN_SPEED = 100
Boid.ATTRACTION_RADIUS = Boid.radius * 8
Boid.ATTRACTION_DAMPER = 10
Boid.AVOID_RADIUS = Boid.radius * 3
Boid.AVOID_AMPLIFIER = 6
Boid.ALIGNMENT_RADIUS = Boid.radius * 3
Boid.ALIGNMENT_DAMPER = 8
Boid.HUNTING_RADIUS = Boid.radius * 10
Boid.HUNTING_DAMPER = 5
Boid.STAY_VISIBLE_DAMPER = 40
 
function Boid:new(x, y, vx, vy)
   local instance = {}
   setmetatable(instance, self)
   self.__index = self
 
   instance.velocity = Vector:new(vx, vy)
   instance.position = Vector:new(x, y)
   instance.velocity_delta = Vector:new(0, 0)
 
   instance.left_sprite = love.graphics.newImage("images/left_chicken.png")
   instance.right_sprite = love.graphics.newImage("images/right_chicken.png")
   instance.left_anim = love.graphics.newAnimation(instance.left_sprite, 18, 18,
                                                   instance:flap_rate())
   instance.right_anim = love.graphics.newAnimation(instance.right_sprite, 18, 18,
                                                    instance:flap_rate())
   instance.anim = left_anim
 
   return instance
end
 
function Boid:flap_rate()
   return 0.1--self.velocity:r() / Boid.MAX_SPEED / 4
end
 
function Boid:calculate_avoidance_delta(boids)
   for _, other in ipairs(boids) do
      if self.position:isNearby(Boid.AVOID_RADIUS, other.position) then
         local avoid_vector = (self.position - other.position)
         local unit_avoid_accel = avoid_vector:norm()
         local avoid_multiplier = Boid.AVOID_RADIUS * Boid.AVOID_AMPLIFIER / avoid_vector:r()
         local avoid_accel = unit_avoid_accel * avoid_multiplier
         self.velocity_delta = self.velocity_delta + avoid_accel
      end
   end
end
 
function Boid:calculate_attraction_delta(boids)
   local average_position = Vector:new(0, 0)
   local visible_boids = 0
   for _, other in ipairs(boids) do
      if self.position:isNearby(Boid.ATTRACTION_RADIUS, other.position) then
         average_position = average_position + other.position
         visible_boids = visible_boids + 1
      end
   end
   average_position = average_position / visible_boids
   self.velocity_delta = self.velocity_delta +
      ((average_position - self.position) / Boid.ATTRACTION_DAMPER)
end
 
function Boid:calculate_alignment_delta(boids)
   local alignment_delta = Vector:new(0, 0)
   local visible_boids = 0
   for _, other in ipairs(boids) do
      if self.position:isNearby(Boid.ALIGNMENT_RADIUS, other.position) then
         alignment_delta = alignment_delta + other.velocity
         visible_boids = visible_boids + 1
      end
   end
   alignment_delta = alignment_delta / visible_boids
   self.velocity_delta = self.velocity_delta +
      ((alignment_delta / Boid.ALIGNMENT_DAMPER))
end
 
function Boid:calculate_hunting_delta(foodstuffs)
   for _, food in ipairs(foodstuffs) do
      if self.position:isNearby(Boid.HUNTING_RADIUS, food.position) then
         self.velocity_delta = self.velocity_delta +
            ((food.position - self.position) / Boid.HUNTING_DAMPER)
      end
   end
end
 
function Boid:calculate_stay_visible_delta(boids)
   local mid_x = love.graphics.getWidth() / 2
   local mid_y = love.graphics.getHeight() / 2
   local center_vector = Vector:new(mid_x, mid_y)
   
   self.velocity_delta = self.velocity_delta -
      ((self.position - center_vector) / Boid.STAY_VISIBLE_DAMPER)
end
 
function Boid:apply_deltas()
   self.velocity = self.velocity + self.velocity_delta
   self.velocity_delta = Vector:new(0, 0)
end
 
function Boid:limit_speed()
   if self.velocity:r() > Boid.MAX_SPEED then
      self.velocity = self.velocity / self.velocity:r() * Boid.MAX_SPEED
   end
   if self.velocity:r() < Boid.MIN_SPEED then
      self.velocity = self.velocity / self.velocity:r() * Boid.MIN_SPEED
   end
end
 
function Boid:navigate(boids, foodstuffs)
   self:calculate_avoidance_delta(boids)
   self:calculate_attraction_delta(boids)
   self:calculate_alignment_delta(boids)
   self:calculate_hunting_delta(foodstuffs)
-- self:calculate_stay_visible_delta(boids)
end
 
function Boid:move(dt)
   self:apply_deltas()
   self:limit_speed()
   self.position = self.position + self.velocity * dt
end
 
function Boid:animate(dt)
   if self.velocity.x <= 0 then
      self.anim = self.left_anim
   else
      self.anim = self.right_anim
   end
 
   self.anim:update(dt)
end
 
function Boid:draw()
   love.graphics.draw(self.anim, self.position.x, self.position.y,
                      math.deg(self.velocity:ang()), 1.5)
end
 
function Boid:draw_debug()
   love.graphics.setColor(255, 165, 0)
   love.graphics.circle(love.draw_line, self.position.x, self.position.y, 10)
   love.graphics.draw("("..math.floor(self.position.x)..", "..math.floor(self.position.y)..")", self.position.x + 5, self.position.y)
   love.graphics.setColor(255, 255, 255)
end
 
-- draw the physical radius
function Boid:draw_physical()
   love.graphics.setColor(255, 255, 255, 128)
   love.graphics.circle(love.draw_line, self.position.x, self.position.y, Boid.radius, 10)
   love.graphics.setColor(255, 255, 255, 255)
end
 
-- draw the attraction radius
function Boid:draw_attraction()
   love.graphics.setColor(255, 0, 0, 128)
   love.graphics.circle(love.draw_line, self.position.x, self.position.y, Boid.ATTRACTION_RADIUS, 10)
   love.graphics.setColor(255, 255, 255, 255)
end
 
-- draw the avoidance radius
function Boid:draw_avoidance()
   love.graphics.setColor(0, 255, 0, 128)
   love.graphics.circle(love.draw_line, self.position.x, self.position.y, Boid.AVOID_RADIUS, 10)
   love.graphics.setColor(255, 255, 255, 255)
end
 
-- draw the alignment radius
function Boid:draw_alignment()
   love.graphics.setColor(0, 0, 255, 128)
   love.graphics.circle(love.draw_line, self.position.x, self.position.y, Boid.ALIGNMENT_RADIUS, 10)
   love.graphics.setColor(255, 255, 255, 255)
end
 
-- draw the hunting radius
function Boid:draw_hunting()
   love.graphics.setColor(255, 225, 132, 128)
   love.graphics.circle(love.draw_line, self.position.x, self.position.y, Boid.HUNTING_RADIUS, 10)
   love.graphics.setColor(255, 255, 255, 255)
end