zyps 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING.LESSER.txt +165 -0
- data/COPYING.txt +674 -0
- data/README.txt +80 -0
- data/bin/zyps +41 -0
- data/bin/zyps_demo +525 -0
- data/lib/zyps.rb +358 -0
- data/lib/zyps/views/trails.rb +147 -0
- data/test/test_zyps.rb +310 -0
- metadata +61 -0
data/lib/zyps.rb
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# Copyright 2007 Jay McGavren, jay@mcgavren.com.
|
|
2
|
+
#
|
|
3
|
+
# This file is part of Zyps.
|
|
4
|
+
#
|
|
5
|
+
# Zyps is free software; you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
require 'observer'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
#A virtual environment.
|
|
24
|
+
class Environment
|
|
25
|
+
|
|
26
|
+
include Observable
|
|
27
|
+
|
|
28
|
+
#An array of GameObject objects that reside in the Environment.
|
|
29
|
+
attr_accessor :objects
|
|
30
|
+
#An array of EnvironmentalFactor objects that act on any GameObject in the Environment.
|
|
31
|
+
attr_accessor :environmental_factors
|
|
32
|
+
|
|
33
|
+
def initialize (objects = [], environmental_factors = [])
|
|
34
|
+
@objects, @environmental_factors = objects, environmental_factors
|
|
35
|
+
@clock = Clock.new
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
#Allow everything in the environment to interact with each other.
|
|
39
|
+
#Objects are first moved according to their preexisting vectors and the amount of time since the last call.
|
|
40
|
+
#Then each EnvironmentalFactor is allowed to act on each object.
|
|
41
|
+
#Finally, each GameObject with an act() method is allowed to act on every other object.
|
|
42
|
+
def interact
|
|
43
|
+
|
|
44
|
+
#Get time since last interaction.
|
|
45
|
+
elapsed_time = @clock.elapsed_time
|
|
46
|
+
|
|
47
|
+
objects.each do |object|
|
|
48
|
+
|
|
49
|
+
#Move each object according to its vector.
|
|
50
|
+
object.location.x += object.vector.x * elapsed_time
|
|
51
|
+
object.location.y += object.vector.y * elapsed_time
|
|
52
|
+
|
|
53
|
+
#Have all environmental factors interact with each object.
|
|
54
|
+
environmental_factors.each {|factor| factor.act(factor, object)}
|
|
55
|
+
|
|
56
|
+
#Have all creatures interact with each other.
|
|
57
|
+
if object.respond_to?(:act)
|
|
58
|
+
objects.each do |target|
|
|
59
|
+
next if target.equal?(object) #Ensure object does not act on itself.
|
|
60
|
+
object.act(object, target)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
#Mark environment as changed.
|
|
67
|
+
changed
|
|
68
|
+
|
|
69
|
+
#Alert observers.
|
|
70
|
+
notify_observers(self)
|
|
71
|
+
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
#An object in the virtual environment.
|
|
79
|
+
class GameObject
|
|
80
|
+
|
|
81
|
+
#The object's Location in space.
|
|
82
|
+
attr_accessor :location
|
|
83
|
+
#A Color that will be used to draw the object.
|
|
84
|
+
attr_accessor :color
|
|
85
|
+
#A Vector with the object's current speed and direction of travel.
|
|
86
|
+
attr_accessor :vector
|
|
87
|
+
#A String with the object's name.
|
|
88
|
+
attr_accessor :name
|
|
89
|
+
#An array of Strings with tags that determine how the object will be treated by Creature and EnvironmentalFactor objects in its environment.
|
|
90
|
+
attr_accessor :tags
|
|
91
|
+
|
|
92
|
+
def initialize (name = nil, location = Location.new, color = Color.new, vector = Vector.new, age = 0, tags = [])
|
|
93
|
+
@name, @location, @color, @vector, @tags = name, location, color, vector, tags
|
|
94
|
+
self.age = age
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
#Time since the object was created, in seconds.
|
|
98
|
+
def age; Time.new.to_f - @birth_time; end
|
|
99
|
+
def age=(age); @birth_time = Time.new.to_f - age; end
|
|
100
|
+
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
#Mixin to have an object (usually a Creature) act on other objects.
|
|
106
|
+
module Responsive
|
|
107
|
+
|
|
108
|
+
#Call Behavior.perform on each of the object's assigned Behaviors, with the object and a target as arguments.
|
|
109
|
+
def act(subject, target)
|
|
110
|
+
behaviors.each {|behavior| behavior.perform(subject, target)}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
#A Creature is a GameObject that can sense and respond to other GameObjects (including other Creature objects).
|
|
118
|
+
class Creature < GameObject
|
|
119
|
+
|
|
120
|
+
include Responsive
|
|
121
|
+
|
|
122
|
+
#A list of Behavior objects that determine the creature's response to its environment.
|
|
123
|
+
attr_accessor :behaviors
|
|
124
|
+
|
|
125
|
+
#Identical to the GameObject constructor, except that it also takes a list of Behavior objects.
|
|
126
|
+
def initialize (name = nil, location = Location.new, color = Color.new, vector = Vector.new, age = 0, tags = [], behaviors = [])
|
|
127
|
+
super(name, location, color, vector, age, tags)
|
|
128
|
+
@behaviors = behaviors
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
#Something in the environment that acts on creatures.
|
|
136
|
+
class EnvironmentalFactor
|
|
137
|
+
|
|
138
|
+
include Responsive
|
|
139
|
+
|
|
140
|
+
#A list of Behavior objects, each called in the same way as those of a Creature.
|
|
141
|
+
attr_accessor :behaviors
|
|
142
|
+
|
|
143
|
+
def initialize (behaviors = [])
|
|
144
|
+
@behaviors = behaviors
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
#A behavior that a Creature or EnvironmentalFactor object (or other classes that include Responsive) engage in.
|
|
152
|
+
#The target can have its tags or colors changed, it can be "herded", it can be destroyed, or any other action the library user can dream up.
|
|
153
|
+
#Likewise, the subject can change its own attributes, it can approach or flee from the target, it can spawn new Creatures or GameObjects (like bullets), or anything else.
|
|
154
|
+
class Behavior
|
|
155
|
+
|
|
156
|
+
#A list of conditions, which are Proc objects called with the object itself and its target. A condition can consider the tags on the target, the distance from the subject, or any other criteria. If any condition returns false, the behavior will not be carried out.
|
|
157
|
+
attr_accessor :conditions
|
|
158
|
+
#A list of actions, which are Proc objects called with the object and its target when all conditions are met. An action can act on the subject or its target.
|
|
159
|
+
attr_accessor :actions
|
|
160
|
+
|
|
161
|
+
#Optionally takes an array of actions and one of conditions.
|
|
162
|
+
def initialize (actions = [], conditions = [])
|
|
163
|
+
@actions, @conditions = actions, conditions
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
#Calls each Proc object in the list of conditions with the subject and its target. Returns nil if any condition returns false.
|
|
167
|
+
#Then calls each Proc object in the list of actions, also with the subject and its target.
|
|
168
|
+
def perform(subject, target)
|
|
169
|
+
conditions.each {|condition| return nil unless condition.call(subject, target)}
|
|
170
|
+
actions.each {|action| action.call(subject, target)}
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
#An object's color. Has red, green, and blue components, each ranging from 0 to 1.
|
|
178
|
+
#* Red: <tt>Color.new(1, 0, 0)</tt>
|
|
179
|
+
#* Green: <tt>Color.new(0, 1, 0)</tt>
|
|
180
|
+
#* Blue: <tt>Color.new(0, 0, 1)</tt>
|
|
181
|
+
#* White: <tt>Color.new(1, 1, 1)</tt>
|
|
182
|
+
#* Black: <tt>Color.new(0, 0, 0)</tt>
|
|
183
|
+
class Color
|
|
184
|
+
|
|
185
|
+
include Comparable
|
|
186
|
+
|
|
187
|
+
#Components which range from 0 to 1, which combine to form the Color.
|
|
188
|
+
attr_accessor :red, :green, :blue
|
|
189
|
+
|
|
190
|
+
def initialize (red = 1, green = 1, blue = 1)
|
|
191
|
+
@red, @green, @blue = red, green, blue
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
#Automatically constrains value to the range 0 - 1.
|
|
195
|
+
def red=(v); v = 0 if v < 0; v = 1 if v > 1; @red = v; end
|
|
196
|
+
#Automatically constrains value to the range 0 - 1.
|
|
197
|
+
def green=(v); v = 0 if v < 0; v = 1 if v > 1; @green = v; end
|
|
198
|
+
#Automatically constrains value to the range 0 - 1.
|
|
199
|
+
def blue=(v); v = 0 if v < 0; v = 1 if v > 1; @blue = v; end
|
|
200
|
+
|
|
201
|
+
#Compares this Color with another to see which is brighter.
|
|
202
|
+
#The sum of all components (red + green + blue) for each color determines which is greater.
|
|
203
|
+
def <=>(other)
|
|
204
|
+
@red + @green + @blue <=> other.red + other.green + other.blue
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
#Averages each component of this Color with the corresponding component of color2, returning a new Color.
|
|
208
|
+
def +(color2)
|
|
209
|
+
Color.new(
|
|
210
|
+
self.red + color2.red / 2.0,
|
|
211
|
+
self.green + color2.green / 2.0,
|
|
212
|
+
self.blue + color2.blue / 2.0
|
|
213
|
+
)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
#An object's location, with x and y coordinates.
|
|
221
|
+
class Location
|
|
222
|
+
|
|
223
|
+
#Coordinates can be negative, and don't have to be integers.
|
|
224
|
+
attr_accessor :x, :y
|
|
225
|
+
|
|
226
|
+
def initialize (x = 0, y = 0)
|
|
227
|
+
@x, @y = x, y
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
#An object or force's velocity.
|
|
235
|
+
#Has speed and angle components.
|
|
236
|
+
class Vector
|
|
237
|
+
|
|
238
|
+
#The length of the Vector.
|
|
239
|
+
attr_accessor :speed
|
|
240
|
+
|
|
241
|
+
def initialize (speed = 0, pitch = 0)
|
|
242
|
+
self.speed = speed
|
|
243
|
+
self.pitch = pitch
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
#The angle along the X/Y axes.
|
|
247
|
+
def pitch; Utility.to_degrees(@pitch); end
|
|
248
|
+
def pitch=(degrees)
|
|
249
|
+
#Constrain degrees to 0 to 360.
|
|
250
|
+
value = degrees % 360
|
|
251
|
+
value += 360 if value < 0
|
|
252
|
+
#Store as radians internally.
|
|
253
|
+
@pitch = Utility.to_radians(value)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
#The X component.
|
|
257
|
+
def x; @speed.to_f * Math.cos(@pitch); end
|
|
258
|
+
def x=(value)
|
|
259
|
+
@speed, @pitch = Math.sqrt(value ** 2 + y ** 2), Math.atan(y / value)
|
|
260
|
+
end
|
|
261
|
+
#The Y component.
|
|
262
|
+
def y; @speed.to_f * Math.sin(@pitch); end
|
|
263
|
+
def y=(value)
|
|
264
|
+
@speed, @pitch = Math.sqrt(x ** 2 + value ** 2), Math.atan(value / x)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
#Add this Vector to vector2, returning a new Vector.
|
|
268
|
+
#This operation is useful when calculating the effect of wind or thrust on an object's current heading.
|
|
269
|
+
def +(vector2)
|
|
270
|
+
#Get the x and y components of the new vector.
|
|
271
|
+
new_x = (self.x + vector2.x)
|
|
272
|
+
new_y = (self.y + vector2.y)
|
|
273
|
+
new_length_squared = new_x ** 2 + new_y ** 2
|
|
274
|
+
new_length = (new_length_squared == 0 ? 0 : Math.sqrt(new_length_squared))
|
|
275
|
+
new_angle = (new_x == 0 ? 0 : Utility.to_degrees(Math.atan2(new_y, new_x)))
|
|
276
|
+
#Calculate speed and angle of new vector with components.
|
|
277
|
+
Vector.new(new_length, new_angle)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
#A clock to use for timing actions.
|
|
285
|
+
class Clock
|
|
286
|
+
|
|
287
|
+
def initialize
|
|
288
|
+
@last_check_time = Time.new.to_f
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
#Returns the time in (fractional) seconds since this method was last called (or on the first call, time since the Clock was created).
|
|
292
|
+
def elapsed_time
|
|
293
|
+
time = Time.new.to_f
|
|
294
|
+
elapsed_time = time - @last_check_time
|
|
295
|
+
@last_check_time = time
|
|
296
|
+
elapsed_time
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
#Various methods for working with Vectors, etc.
|
|
304
|
+
module Utility
|
|
305
|
+
|
|
306
|
+
PI2 = Math::PI * 2.0
|
|
307
|
+
|
|
308
|
+
#Get the angle (in degrees) from one Location to another.
|
|
309
|
+
def Utility.find_angle(origin, target)
|
|
310
|
+
#Get vector from origin to target.
|
|
311
|
+
x_difference = target.x - origin.x
|
|
312
|
+
y_difference = target.y - origin.y
|
|
313
|
+
#Get vector's angle.
|
|
314
|
+
radians = Math.atan2(y_difference, x_difference)
|
|
315
|
+
#Result will range from negative Pi to Pi, so correct it.
|
|
316
|
+
radians += PI2 if radians < 0
|
|
317
|
+
#Convert to degrees.
|
|
318
|
+
to_degrees(radians)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
#Get the distance from one Location to another.
|
|
322
|
+
def Utility.find_distance(origin, target)
|
|
323
|
+
#Get vector from origin to target.
|
|
324
|
+
x_difference = origin.x - target.x
|
|
325
|
+
y_difference = origin.y - target.y
|
|
326
|
+
#Get distance.
|
|
327
|
+
Math.sqrt(x_difference ** 2 + y_difference ** 2)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
#Convert radians to degrees.
|
|
331
|
+
def Utility.to_degrees(radians)
|
|
332
|
+
radians / PI2 * 360
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
#Convert degrees to radians.
|
|
336
|
+
def Utility.to_radians(degrees)
|
|
337
|
+
radians = degrees / 360.0 * PI2
|
|
338
|
+
radians = radians % PI2
|
|
339
|
+
radians += PI2 if radians < 0
|
|
340
|
+
radians
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
#Reduce a number to within an allowed maximum (or minimum, if the number is negative).
|
|
344
|
+
def Utility.constrain_value(value, absolute_maximum)
|
|
345
|
+
if (value.abs > absolute_maximum) then
|
|
346
|
+
if value >= 0 then
|
|
347
|
+
value = absolute_maximum
|
|
348
|
+
else
|
|
349
|
+
value = absolute_maximum * -1
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
value
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Copyright 2007 Jay McGavren, jay@mcgavren.com.
|
|
2
|
+
#
|
|
3
|
+
# This file is part of Zyps.
|
|
4
|
+
#
|
|
5
|
+
# Zyps is free software; you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
require 'gtk2'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
#A view of game objects.
|
|
23
|
+
class TrailsView
|
|
24
|
+
|
|
25
|
+
#A Gtk::DrawingArea to render objects on.
|
|
26
|
+
attr_reader :canvas
|
|
27
|
+
#Dimensions of the view.
|
|
28
|
+
attr_reader :width, :height
|
|
29
|
+
#Number of line segments to draw for each object.
|
|
30
|
+
attr_accessor :trail_length
|
|
31
|
+
#Width of the trail behind each object.
|
|
32
|
+
attr_accessor :trail_width
|
|
33
|
+
|
|
34
|
+
def initialize (width = 600, height = 400, trail_length = 5, trail_width = trail_length)
|
|
35
|
+
|
|
36
|
+
@width, @height, @trail_length, @trail_width, @background = width, height, trail_length, trail_width
|
|
37
|
+
|
|
38
|
+
#Create a drawing area.
|
|
39
|
+
@canvas = Gtk::DrawingArea.new
|
|
40
|
+
#Set to correct size.
|
|
41
|
+
resize
|
|
42
|
+
|
|
43
|
+
#Whenever the drawing area needs updating...
|
|
44
|
+
@canvas.signal_connect("expose_event") do
|
|
45
|
+
#Copy buffer bitmap to canvas.
|
|
46
|
+
@canvas.window.draw_drawable(
|
|
47
|
+
@canvas.style.fg_gc(@canvas.state), #Gdk::GC (graphics context) to use when drawing.
|
|
48
|
+
buffer, #Gdk::Drawable source to copy onto canvas.
|
|
49
|
+
0, 0, #Pull from upper left of source.
|
|
50
|
+
0, 0, #Copy to upper left of canvas.
|
|
51
|
+
-1, -1 #-1 width and height signals to copy entire source over.
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
#Track a list of locations for each object.
|
|
56
|
+
@locations = Hash.new {|h, k| h[k] = Array.new}
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def width= (pixels) #:nodoc:
|
|
61
|
+
@width = pixels
|
|
62
|
+
resize
|
|
63
|
+
end
|
|
64
|
+
def height= (pixels) #:nodoc:
|
|
65
|
+
@height = pixels
|
|
66
|
+
resize
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
#Takes an Environment, and draws it to the canvas.
|
|
70
|
+
#Tracks the position of each GameObject over time so it can draw a trail behind it.
|
|
71
|
+
#The head will match the object's Color exactly, fading to black at the tail.
|
|
72
|
+
#trail_width will be used as the line thickness at the object's head, diminishing to 1 at the tail.
|
|
73
|
+
def update(environment)
|
|
74
|
+
|
|
75
|
+
#Clear the background on the buffer.
|
|
76
|
+
graphics_context = Gdk::GC.new(buffer)
|
|
77
|
+
graphics_context.rgb_fg_color = Gdk::Color.new(0, 0, 0)
|
|
78
|
+
buffer.draw_rectangle(
|
|
79
|
+
graphics_context,
|
|
80
|
+
true, #Filled.
|
|
81
|
+
0, 0, #Upper-left corner.
|
|
82
|
+
@width, @height #Lower-right corner.
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
#For each GameObject in the environment:
|
|
86
|
+
environment.objects.each do |object|
|
|
87
|
+
|
|
88
|
+
#Add the object's current location to the list.
|
|
89
|
+
@locations[object] << [object.location.x, object.location.y]
|
|
90
|
+
#If the list is larger than the number of tail segments, delete the first position.
|
|
91
|
+
@locations[object].shift if @locations[object].length > @trail_length
|
|
92
|
+
|
|
93
|
+
#For each location in this object's list:
|
|
94
|
+
@locations[object].each_with_index do |location, index|
|
|
95
|
+
|
|
96
|
+
#Skip first location.
|
|
97
|
+
next if index == 0
|
|
98
|
+
|
|
99
|
+
#Divide the current segment number by trail segment count to get the multiplier to use for brightness and width.
|
|
100
|
+
multiplier = index.to_f / @locations[object].length.to_f
|
|
101
|
+
|
|
102
|
+
#Set the drawing color to use the object's colors, adjusted by the multiplier.
|
|
103
|
+
graphics_context.rgb_fg_color = Gdk::Color.new( #Don't use Gdk::GC.foreground= here, as that requires a color to be in the color map already.
|
|
104
|
+
object.color.red * multiplier * 65535,
|
|
105
|
+
object.color.green * multiplier * 65535,
|
|
106
|
+
object.color.blue * multiplier * 65535
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
#Multiply the actual drawing width by the current multiplier to get the current drawing width.
|
|
110
|
+
graphics_context.set_line_attributes(
|
|
111
|
+
(@trail_width * multiplier).ceil,
|
|
112
|
+
Gdk::GC::LINE_SOLID,
|
|
113
|
+
Gdk::GC::CAP_ROUND, #Line ends drawn as semicircles.
|
|
114
|
+
Gdk::GC::JOIN_MITER #Only used for polygons.
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
#Get previous location so we can draw a line from it.
|
|
118
|
+
previous_location = @locations[object][index - 1]
|
|
119
|
+
|
|
120
|
+
#Draw a line with the current width from the prior location to the current location.
|
|
121
|
+
buffer.draw_line(
|
|
122
|
+
graphics_context,
|
|
123
|
+
previous_location[0], previous_location[1],
|
|
124
|
+
location[0], location[1]
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
@canvas.queue_draw_area(0, 0, @width, @height)
|
|
132
|
+
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
def resize
|
|
139
|
+
@canvas.set_size_request(@width, @height)
|
|
140
|
+
@buffer = nil #Causes buffer to reset its size next time it's accessed.
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def buffer
|
|
144
|
+
@buffer ||= Gdk::Pixmap.new(@canvas.window, @width, @height, -1)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
end
|