rubylabs 0.6.4 → 0.7.0
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/VERSION +1 -1
- data/lib/randomlab.rb +143 -224
- data/lib/rubylabs.rb +262 -38
- data/lib/spherelab.rb +566 -209
- data/lib/tsplab.rb +308 -276
- data/test/sphere_test.rb +32 -0
- metadata +2 -22
- data/data/aafreq.txt +0 -20
- data/data/cars.txt +0 -50
- data/data/century.txt +0 -1
- data/data/colors.txt +0 -64
- data/data/earth.yaml +0 -15
- data/data/fruit.txt +0 -45
- data/data/hacodes.txt +0 -35
- data/data/hafreq.txt +0 -16
- data/data/hvfreq.txt +0 -5
- data/data/nbody.R +0 -23
- data/data/nbody.out +0 -731
- data/data/nbody.pdf +0 -3111
- data/data/nbody.png +0 -0
- data/data/nbody3d.pdf +0 -3201
- data/data/outer.pdf +0 -182785
- data/data/solarsystem.txt +0 -17
- data/data/suits.txt +0 -1
- data/data/wordlist.txt +0 -210653
- data/lib/sortlab.rb +0 -213
- data/lib/viewer.rb +0 -65
data/lib/spherelab.rb
CHANGED
@@ -7,40 +7,86 @@ Definition of Vector and Body objects used for n-body simulations.
|
|
7
7
|
|
8
8
|
=end
|
9
9
|
|
10
|
+
include Math
|
11
|
+
|
10
12
|
module RubyLabs
|
11
13
|
|
12
14
|
module SphereLab
|
15
|
+
|
16
|
+
=begin rdoc
|
17
|
+
These class variables maintain the state of the display and other miscellaneous
|
18
|
+
global values.
|
19
|
+
=end
|
20
|
+
|
21
|
+
@@dataDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'spheres')
|
22
|
+
|
23
|
+
@@viewerOptions = {
|
24
|
+
:dotColor => '#000080',
|
25
|
+
:dotRadius => 1.0,
|
26
|
+
:origin => :center,
|
27
|
+
}
|
28
|
+
|
29
|
+
@@droppingOptions = {
|
30
|
+
:canvasSize => 400,
|
31
|
+
:mxmin => 100,
|
32
|
+
:mymin => 50,
|
33
|
+
:hmax => 100,
|
34
|
+
}
|
35
|
+
|
36
|
+
@@robotOptions = {
|
37
|
+
:canvasSize => 400,
|
38
|
+
:polygon => [4,0,0,10,4,8,8,10],
|
39
|
+
}
|
40
|
+
|
41
|
+
@@drawing = nil
|
42
|
+
@@delay = 0.1
|
43
|
+
|
44
|
+
NBodyView = Struct.new(:bodies, :origin, :scale, :options)
|
45
|
+
TurtleView = Struct.new(:turtle, :center)
|
46
|
+
MelonView = Struct.new(:bodies, :scale, :ground, :startloc, :options)
|
13
47
|
|
14
48
|
=begin rdoc
|
15
49
|
The constant G is the universal gravitational constant, assuming mass is
|
16
50
|
in units of kilograms, distances are in meters, and time is in seconds.
|
17
51
|
=end
|
18
52
|
|
19
|
-
G = 6.67E-11
|
53
|
+
G = 6.67E-11
|
20
54
|
|
55
|
+
=begin rdoc
|
56
|
+
A Vector is a 3-tuple of (x,y,z) coordinates. Operators defined for
|
57
|
+
vector objects (where v is a vector and a is a scalar):
|
58
|
+
v == v
|
59
|
+
v + v
|
60
|
+
v - v
|
61
|
+
v * a
|
62
|
+
v.add(v) (equivalent of v += v)
|
63
|
+
v.sub(v) (equivalent of v -= v)
|
64
|
+
v.scale(a) (equivalent of v *= a)
|
65
|
+
v.norm
|
66
|
+
=end
|
21
67
|
|
22
|
-
class Vector
|
23
|
-
|
68
|
+
class Vector
|
69
|
+
attr_accessor :x, :y, :z
|
24
70
|
|
25
71
|
=begin rdoc
|
26
72
|
Make a new vector with the specified x, y, and z components.
|
27
73
|
=end
|
28
74
|
|
29
|
-
|
30
|
-
|
31
|
-
|
75
|
+
def initialize(*args)
|
76
|
+
@x, @y, @z = args
|
77
|
+
end
|
32
78
|
|
33
|
-
|
34
|
-
|
35
|
-
|
79
|
+
def inspect
|
80
|
+
sprintf "<%s, %s, %s>", @x.to_s, @y.to_s, @z.to_s
|
81
|
+
end
|
36
82
|
|
37
83
|
=begin rdoc
|
38
84
|
v1 == v2 if the three components are the same
|
39
85
|
=end
|
40
86
|
|
41
|
-
|
42
|
-
|
43
|
-
|
87
|
+
def ==(v)
|
88
|
+
return (@x == v.x) && (@y == v.y) && (@z == v.z)
|
89
|
+
end
|
44
90
|
|
45
91
|
=begin rdoc
|
46
92
|
Arithmetic methods are invoked for a vector v1 when Ruby evaluates an expression of the
|
@@ -49,51 +95,84 @@ class Vector
|
|
49
95
|
is a vector-scalar multiplication.
|
50
96
|
=end
|
51
97
|
|
52
|
-
|
53
|
-
|
54
|
-
|
98
|
+
def +(v)
|
99
|
+
Vector.new(@x + v.x, @y + v.y, @z + v.z)
|
100
|
+
end
|
55
101
|
|
56
|
-
|
57
|
-
|
58
|
-
|
102
|
+
def -(v)
|
103
|
+
Vector.new(@x - v.x, @y - v.y, @z - v.z)
|
104
|
+
end
|
59
105
|
|
60
|
-
|
61
|
-
|
62
|
-
|
106
|
+
def *(a)
|
107
|
+
Vector.new(@x * a, @y * a, @z * a)
|
108
|
+
end
|
63
109
|
|
64
110
|
=begin rdoc
|
65
111
|
v1.add(v2) adds the components of v2 to v1 -- would be v1 += v2 if Ruby
|
66
112
|
allowed us to overload +=
|
67
113
|
=end
|
68
114
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
115
|
+
def add(v)
|
116
|
+
@x += v.x
|
117
|
+
@y += v.y
|
118
|
+
@z += v.z
|
119
|
+
self
|
120
|
+
end
|
75
121
|
|
76
122
|
=begin rdoc
|
77
123
|
v1.sub(v2) subtracts the components of v2 from v1 -- would be v1 -= v2 if Ruby
|
78
124
|
allowed us to overload -=
|
79
125
|
=end
|
80
126
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
127
|
+
def sub(v)
|
128
|
+
@x -= v.x
|
129
|
+
@y -= v.y
|
130
|
+
@z -= v.z
|
131
|
+
self
|
132
|
+
end
|
133
|
+
|
134
|
+
=begin rdoc
|
135
|
+
v.scale(a) -- multiply each element in v by scalar a
|
136
|
+
=end
|
137
|
+
|
138
|
+
def scale(a)
|
139
|
+
@x *= a
|
140
|
+
@y *= a
|
141
|
+
@z *= a
|
142
|
+
end
|
87
143
|
|
88
144
|
=begin rdoc
|
89
145
|
The magnitude of a vector is the Euclidean norm: sqrt(x**2 + y**2 + z**2)
|
90
146
|
=end
|
91
147
|
|
92
|
-
|
93
|
-
|
94
|
-
|
148
|
+
def norm
|
149
|
+
sqrt(@x*@x + @y*@y + @z*@z)
|
150
|
+
end
|
151
|
+
|
152
|
+
=begin rdoc
|
153
|
+
Compute the angle between this vector and v -- used when drawing 2D vectors, so
|
154
|
+
the z dimension is ignored
|
155
|
+
=end
|
156
|
+
|
157
|
+
def angle(v)
|
158
|
+
acos((@x * v.x + @y * v.y) / (norm * v.norm))
|
159
|
+
end
|
160
|
+
|
161
|
+
=begin rdoc
|
162
|
+
Accessor functions for the coordinates as a vector of three floats
|
163
|
+
=end
|
164
|
+
|
165
|
+
def coords
|
166
|
+
[@x, @y, @z]
|
167
|
+
end
|
168
|
+
|
169
|
+
def coords=(a)
|
170
|
+
@x = a[0]
|
171
|
+
@y = a[1]
|
172
|
+
@z = a[2]
|
173
|
+
end
|
95
174
|
|
96
|
-
end # Vector
|
175
|
+
end # Vector
|
97
176
|
|
98
177
|
|
99
178
|
=begin rdoc
|
@@ -103,8 +182,9 @@ end # Vector
|
|
103
182
|
the visualization methods.
|
104
183
|
=end
|
105
184
|
|
106
|
-
class Body
|
107
|
-
|
185
|
+
class Body
|
186
|
+
attr_accessor :mass, :position, :velocity, :force
|
187
|
+
attr_accessor :name, :size, :color, :prevx, :prevy, :graphic
|
108
188
|
|
109
189
|
=begin rdoc
|
110
190
|
The constructor can be called with a variety of different arguments:
|
@@ -118,233 +198,510 @@ class Body
|
|
118
198
|
* pass no parameters to get a body with all attributes set to 0
|
119
199
|
=end
|
120
200
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
201
|
+
def initialize(*args)
|
202
|
+
if args[1].class == Vector
|
203
|
+
@mass, @position, @velocity, @name = args
|
204
|
+
elsif args[0] == :random
|
205
|
+
@mass = rand(10000) * 1e6
|
206
|
+
@position = Vector.new(rand(300)-150, rand(300)-150, 0)
|
207
|
+
@velocity = Vector.new(rand(20)-10, rand(10)-5, 0)
|
208
|
+
@name = "b" + self.object_id.to_s
|
209
|
+
elsif args[0].is_a?(Numeric) && args[1].is_a?(Numeric)
|
210
|
+
@mass = args[0]
|
211
|
+
@position = Vector.new(args[1], 0.0, 0.0)
|
212
|
+
@velocity = Vector.new(0.0, 0.0, 0.0)
|
213
|
+
@name = args[2]
|
214
|
+
@linear = true
|
215
|
+
else
|
216
|
+
@mass = 0.0
|
217
|
+
@position = Vector.new(0.0, 0.0, 0.0)
|
218
|
+
@velocity = Vector.new(0.0, 0.0, 0.0)
|
219
|
+
@name = nil
|
220
|
+
end
|
221
|
+
@force = Vector.new(0.0, 0.0, 0.0)
|
140
222
|
end
|
141
|
-
@force = Vector.new(0.0, 0.0, 0.0)
|
142
|
-
end
|
143
223
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
224
|
+
def inspect
|
225
|
+
s = ""
|
226
|
+
s << @name + ": " if @name
|
227
|
+
s << @mass.to_s + "g "
|
228
|
+
if @linear
|
229
|
+
s << "x: " + @position.x.to_s
|
230
|
+
else
|
231
|
+
s << @position.inspect + " " + @velocity.inspect
|
232
|
+
end
|
233
|
+
return s
|
152
234
|
end
|
153
|
-
return s
|
154
|
-
end
|
155
235
|
|
156
236
|
=begin rdoc
|
157
237
|
Reset the force vector to 0 (to get ready for a new round of interaction calculations).
|
158
238
|
=end
|
159
239
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
240
|
+
def clear_force
|
241
|
+
@force.x = 0.0
|
242
|
+
@force.y = 0.0
|
243
|
+
@force.z = 0.0
|
244
|
+
end
|
165
245
|
|
166
246
|
=begin rdoc
|
167
247
|
Compute force acting on this body wrt body b
|
168
248
|
=end
|
169
249
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
250
|
+
def add_force(b)
|
251
|
+
r = @position - b.position
|
252
|
+
nr = r.norm ** 3
|
253
|
+
mr = b.mass / nr
|
254
|
+
@force.add(r * mr)
|
255
|
+
end
|
176
256
|
|
177
257
|
=begin rdoc
|
178
258
|
Move this body by applying current force vector for dt seconds
|
179
259
|
=end
|
180
260
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
261
|
+
def move(dt)
|
262
|
+
acc = @force * G * -1.0
|
263
|
+
@velocity.add( acc * dt )
|
264
|
+
@position.add( @velocity * dt )
|
265
|
+
end
|
186
266
|
|
187
267
|
=begin rdoc
|
188
268
|
Class method to compute the interaction between bodies b1 and b2 and update
|
189
269
|
their force vectors.
|
190
270
|
=end
|
191
271
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
272
|
+
def Body.interaction(b1, b2)
|
273
|
+
r = b1.position - b2.position
|
274
|
+
a = r.norm ** 3
|
275
|
+
b1.force.add(r * (b2.mass / a))
|
276
|
+
b2.force.add(r * (-b1.mass / a))
|
277
|
+
end
|
278
|
+
|
279
|
+
end # Body
|
280
|
+
|
281
|
+
=begin rdoc
|
282
|
+
A class for rudimentary Turtle graphics. All we need is enough functionality to
|
283
|
+
implement a robot that draws a circle by moving forward, then correcting its direction.
|
284
|
+
Attributes: location and velocity vectors.
|
285
|
+
=end
|
286
|
+
|
287
|
+
class Turtle
|
288
|
+
|
289
|
+
attr_accessor :position, :velocity, :graphic
|
290
|
+
|
291
|
+
@@turtleOptions = {
|
292
|
+
:x => 20,
|
293
|
+
:y => 100,
|
294
|
+
:heading => 0,
|
295
|
+
:speed => 10,
|
296
|
+
}
|
297
|
+
|
298
|
+
@@north = Vector.new(0, 10, 0)
|
299
|
+
|
300
|
+
def initialize(args = nil)
|
301
|
+
args = { } if args.nil?
|
302
|
+
raise "Turtle: initialize: args must be a Hash" unless args.class == Hash
|
303
|
+
options = @@turtleOptions.merge(args)
|
304
|
+
alpha = Canvas.radians(options[:heading] + 90.0)
|
305
|
+
@position = Vector.new(options[:x], options[:y], 0.0)
|
306
|
+
@velocity = Vector.new(-1.0 * options[:speed] * cos(alpha), options[:speed] * sin(alpha), 0.0)
|
307
|
+
@graphic = nil
|
308
|
+
end
|
309
|
+
|
310
|
+
def inspect
|
311
|
+
sprintf "#<SphereLab::Turtle x: %d y: %d heading: %d speed: %.2f>", @position.x, @position.y, heading, speed
|
312
|
+
end
|
313
|
+
|
314
|
+
def location
|
315
|
+
return [@position.x, @position.y]
|
316
|
+
end
|
317
|
+
|
318
|
+
def heading
|
319
|
+
d = Canvas.degrees( @velocity.angle(@@north) )
|
320
|
+
return (@velocity.x < 0) ? (360 - d) : d
|
321
|
+
end
|
322
|
+
|
323
|
+
def speed
|
324
|
+
@velocity.norm
|
325
|
+
end
|
326
|
+
|
327
|
+
def turn(alpha)
|
328
|
+
theta = -1.0 * Canvas.radians(alpha)
|
329
|
+
x = @velocity.x * cos(theta) - @velocity.y * sin(theta)
|
330
|
+
y = @velocity.x * sin(theta) + @velocity.y * cos(theta)
|
331
|
+
@velocity.x = x
|
332
|
+
@velocity.y = y
|
333
|
+
if graphic
|
334
|
+
Canvas.rotate(graphic,alpha)
|
335
|
+
Canvas.sync
|
336
|
+
end
|
337
|
+
return alpha
|
338
|
+
end
|
339
|
+
|
340
|
+
def advance(dt)
|
341
|
+
prevx = @position.x
|
342
|
+
prevy = @position.y
|
343
|
+
@position.add( @velocity * dt )
|
344
|
+
if graphic
|
345
|
+
Canvas.move(graphic, @position.x-prevx, prevy-@position.y, :track)
|
346
|
+
Canvas.sync
|
347
|
+
end
|
348
|
+
return dt
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
=begin rdoc
|
353
|
+
Set up an experiment with a robot/turtle. Initialize the canvas, make the turtle,
|
354
|
+
assign it a graphic. These methods are all defined with respect to a canvas, and
|
355
|
+
they will print an error message if the experiment has not been set up with a call
|
356
|
+
to init_robot (which sets @@drawing).
|
357
|
+
=end
|
358
|
+
|
359
|
+
def view_robot(userOptions = {})
|
360
|
+
options = @@robotOptions.merge(userOptions)
|
361
|
+
poly = options[:polygon].clone
|
362
|
+
edge = options[:canvasSize]
|
363
|
+
(0...poly.length).step(2) do |i|
|
364
|
+
poly[i] += edge/10
|
365
|
+
poly[i+1] += edge/2
|
366
|
+
end
|
367
|
+
Canvas.init(edge, edge, "SphereLab")
|
368
|
+
turtle = Turtle.new( :x => edge/10, :y => edge/2, :heading => 0, :speed => 10 )
|
369
|
+
turtle.graphic = Canvas.polygon(poly, :outline => 'black', :fill => '#00ff88')
|
370
|
+
Canvas.circle( edge/2, edge/2, 3, :fill => 'darkblue' )
|
371
|
+
@@drawing = TurtleView.new( turtle, [edge/2, edge/2] )
|
372
|
+
Canvas.sync
|
373
|
+
return true
|
374
|
+
end
|
375
|
+
|
376
|
+
def robot
|
377
|
+
if @@drawing.nil?
|
378
|
+
puts "No robot; call view_robot to initialize"
|
379
|
+
return nil
|
380
|
+
else
|
381
|
+
return @@drawing.turtle
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# TODO -- aim toward point on circle at original radius, not current radius...
|
386
|
+
|
387
|
+
def orient_robot
|
388
|
+
r = robot
|
389
|
+
mx, my = @@drawing.center
|
390
|
+
tx, ty = r.position.x, r.position.y
|
391
|
+
mid = Vector.new(tx-my, ty-my, 0.0)
|
392
|
+
theta = Canvas.degrees(mid.angle(r.velocity))
|
393
|
+
alpha = 2 * (90.0 - theta)
|
394
|
+
r.turn(alpha)
|
395
|
+
Canvas.sync
|
396
|
+
return alpha
|
197
397
|
end
|
198
398
|
|
199
|
-
|
399
|
+
# :begin :make_circle
|
400
|
+
def make_circle(nseg, time, angle)
|
401
|
+
view_robot
|
402
|
+
turn_robot( angle/2 )
|
403
|
+
nseg.times { advance_robot(time); turn_robot(angle) }
|
404
|
+
turn_robot( -angle/2 )
|
405
|
+
return true
|
406
|
+
end
|
407
|
+
# :end :make_circle
|
408
|
+
|
200
409
|
|
201
410
|
=begin rdoc
|
202
|
-
|
411
|
+
These methods create a set of bodies with random size, location, and initial
|
412
|
+
velocity.
|
203
413
|
=end
|
204
414
|
|
205
|
-
def
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
415
|
+
def random_vectors(r, i, n)
|
416
|
+
theta = (2 * PI / n) * i + (PI * rand / n)
|
417
|
+
radius = r + (r/3)*(rand-0.5)
|
418
|
+
x = radius * cos(theta)
|
419
|
+
y = radius * sin(theta)
|
420
|
+
vtheta = (PI - theta) * -1 + PI * (rand-0.5)
|
421
|
+
vx = radius/20 * cos(vtheta)
|
422
|
+
vy = radius/20 * sin(vtheta)
|
423
|
+
return Vector.new(x, y, 0), Vector.new(vx, vy, 0)
|
424
|
+
end
|
425
|
+
|
426
|
+
def random_velocity(v, r)
|
427
|
+
res = Vector.new(-r * cos)
|
428
|
+
end
|
429
|
+
|
430
|
+
def random_bodies(n, big = 1)
|
431
|
+
res = []
|
432
|
+
mm = 1e12 # average mass
|
433
|
+
mr = 150 # average distance from origin
|
434
|
+
# write_tuple( :scale, [ 1.0 ] )
|
435
|
+
big.times do |i|
|
436
|
+
r0, v0 = random_vectors(mr/2, i, big)
|
437
|
+
res << Body.new(mm*100/big, r0, v0)
|
438
|
+
# write_tuple( :view, [i, 10, "#0080ff"] )
|
439
|
+
end
|
440
|
+
(n-big).times do |i|
|
441
|
+
r, v = random_vectors(mr, i, n-big)
|
442
|
+
b = Body.new(mm, r, v)
|
443
|
+
# write_tuple( :view, [i+big, 5, "#0080ff"] )
|
444
|
+
res << b
|
214
445
|
end
|
215
|
-
b =
|
216
|
-
|
217
|
-
b.color = a[-1]
|
218
|
-
bodies << b
|
446
|
+
res.each_with_index { |b,i| b.name = "b#{i}"; b.color = '#0080ff'; b.size = 5 }
|
447
|
+
return res
|
219
448
|
end
|
220
|
-
|
221
|
-
|
449
|
+
|
450
|
+
=begin rdoc
|
451
|
+
Initialize a new n-body system, returning an array of body objects. The
|
452
|
+
parameter is the name of a predefined system, the name of a file, or the
|
453
|
+
symbol :random.
|
454
|
+
=end
|
222
455
|
|
456
|
+
def make_system(*args)
|
457
|
+
bodies = []
|
458
|
+
begin
|
459
|
+
raise "usage: make_system(id)" unless args.length > 0
|
460
|
+
if args[0] == :random
|
461
|
+
raise "usage: make_system(:random, n)" unless args.length == 2 && args[1].class == Fixnum
|
462
|
+
return random_bodies(args[1])
|
463
|
+
end
|
464
|
+
filename = args[0]
|
465
|
+
if filename.class == Symbol
|
466
|
+
filename = File.join(@@dataDirectory, filename.to_s + ".txt")
|
467
|
+
end
|
468
|
+
File.open(filename).each do |line|
|
469
|
+
line.strip!
|
470
|
+
next if line.length == 0
|
471
|
+
next if line[0] == ?#
|
472
|
+
a = line.chomp.split
|
473
|
+
for i in 1..7
|
474
|
+
a[i] = a[i].to_f
|
475
|
+
end
|
476
|
+
b = Body.new( a[1], Vector.new(a[2],a[3],a[4]), Vector.new(a[5],a[6],a[7]), a[0] )
|
477
|
+
b.size = a[-2].to_i
|
478
|
+
b.color = a[-1]
|
479
|
+
bodies << b
|
480
|
+
end
|
481
|
+
rescue
|
482
|
+
puts "error: #{$!}"
|
483
|
+
return nil
|
484
|
+
end
|
485
|
+
return bodies
|
486
|
+
end
|
487
|
+
|
488
|
+
=begin rdoc
|
489
|
+
Initialize the drawing canvas by drawing a circle for each body in list b.
|
490
|
+
=end
|
491
|
+
|
492
|
+
def view_system(blist, userOptions = {})
|
493
|
+
Canvas.init(700, 700, "SphereLab")
|
494
|
+
options = @@viewerOptions.merge(userOptions)
|
495
|
+
origin = setOrigin(options[:origin])
|
496
|
+
sf = setScale(blist, options[:origin], options[:scale])
|
497
|
+
blist.each do |b|
|
498
|
+
x, y = scale(b.position, origin, sf)
|
499
|
+
circle = Canvas.circle(x, y, b.size, :fill => b.color)
|
500
|
+
b.graphic = circle
|
501
|
+
b.prevx = x
|
502
|
+
b.prevy = y
|
503
|
+
end
|
504
|
+
@@drawing = NBodyView.new(blist, origin, sf, options)
|
505
|
+
if options.has_key?(:dash)
|
506
|
+
options[:dashcount] = 0
|
507
|
+
options[:pendown] = :track
|
508
|
+
elsif options.has_key?(:pendown)
|
509
|
+
options[:pendown] = :track
|
510
|
+
end
|
511
|
+
Canvas.sync
|
512
|
+
return true
|
513
|
+
end
|
514
|
+
|
223
515
|
=begin rdoc
|
224
516
|
Call SphereLab::step(bodies, time) to calculate the pairwise interactions between all
|
225
517
|
Body objects in the array +bodies+ and then compute their new positions after +time+ seconds.
|
226
518
|
=end
|
227
519
|
|
228
|
-
# :begin :
|
229
|
-
def
|
230
|
-
|
520
|
+
# :begin :step_system
|
521
|
+
def step_system(bodies, time)
|
522
|
+
nb = bodies.length
|
231
523
|
|
232
|
-
|
233
|
-
|
234
|
-
|
524
|
+
for i in 0...nb # compute all pairwise interactions
|
525
|
+
for j in (i+1)...nb
|
526
|
+
Body.interaction( bodies[i], bodies[j] )
|
527
|
+
end
|
235
528
|
end
|
236
|
-
end
|
237
529
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
end
|
243
|
-
# :end :step
|
244
|
-
|
245
|
-
def random_vectors(r, i, n)
|
246
|
-
theta = (2 * Math::PI / n) * i + (Math::PI * rand / n)
|
247
|
-
radius = r + (r/3)*(rand-0.5)
|
248
|
-
x = radius * Math.cos(theta)
|
249
|
-
y = radius * Math.sin(theta)
|
250
|
-
vtheta = (Math::PI - theta) * -1 + Math::PI * (rand-0.5)
|
251
|
-
vx = radius/20 * Math.cos(vtheta)
|
252
|
-
vy = radius/20 * Math.sin(vtheta)
|
253
|
-
return Vector.new(x, y, 0), Vector.new(vx, vy, 0)
|
254
|
-
end
|
255
|
-
|
256
|
-
def random_velocity(v, r)
|
257
|
-
res = Vector.new(-r * Math.cos)
|
258
|
-
end
|
259
|
-
|
260
|
-
def random_bodies(n, big = 1)
|
261
|
-
res = []
|
262
|
-
mm = 1e12 # average mass
|
263
|
-
mr = 150 # average distance from origin
|
264
|
-
write_tuple( :scale, [ 1.0 ] )
|
265
|
-
big.times do |i|
|
266
|
-
r0, v0 = random_vectors(mr/2, i, big)
|
267
|
-
res << Body.new(mm*100/big, r0, v0)
|
268
|
-
write_tuple( :view, [i, 10, "#0080ff"] )
|
530
|
+
bodies.each do |b|
|
531
|
+
b.move(time) # apply the accumulated forces
|
532
|
+
b.clear_force # reset force to 0 for next round
|
533
|
+
end
|
269
534
|
end
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
535
|
+
# :end :step_system
|
536
|
+
|
537
|
+
=begin rdoc
|
538
|
+
After making a canvas with view_system, call this method to move the bodies on the canvas.
|
539
|
+
=end
|
540
|
+
|
541
|
+
def update_system(bodies, dt)
|
542
|
+
return false unless @@drawing
|
543
|
+
step_system(bodies, dt)
|
544
|
+
if @@drawing.options.has_key?(:dash)
|
545
|
+
@@drawing.options[:dashcount] = (@@drawing.options[:dashcount] + 1) % @@drawing.options[:dash]
|
546
|
+
if @@drawing.options[:dashcount] == 0
|
547
|
+
@@drawing.options[:pendown] = @@drawing.options[:pendown].nil? ? :track : nil
|
548
|
+
end
|
549
|
+
end
|
550
|
+
bodies.each do |b|
|
551
|
+
next unless b.graphic
|
552
|
+
newx, newy = scale(b.position, @@drawing.origin, @@drawing.scale)
|
553
|
+
Canvas.move(b.graphic, newx-b.prevx, newy-b.prevy, @@drawing.options[:pendown])
|
554
|
+
b.prevx = newx
|
555
|
+
b.prevy = newy
|
556
|
+
end
|
557
|
+
Canvas.sync
|
558
|
+
return true
|
276
559
|
end
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
# the earth:
|
289
|
-
e = Body.new(5.9736E+24, 0)
|
290
|
-
e.name = "earth"
|
291
|
-
e.size = 100
|
292
|
-
e.color = "#004080"
|
293
|
-
write_tuple( :view, [0, e.size, e.color] )
|
294
|
-
return [e, w]
|
295
|
-
end
|
296
|
-
|
297
|
-
def four_body
|
298
|
-
write_tuple( :scale, [ 0.5 ] )
|
299
|
-
a = Body.new(1.4e18, Vector.new(50, 70, 0), Vector.new(0, 1, 0))
|
300
|
-
write_tuple( :view, [0, 10, "#ff6666"] )
|
301
|
-
b = Body.new(1.2e16, Vector.new(50, 225, 0), Vector.new(-1000, 2, 0))
|
302
|
-
write_tuple( :view, [1, 3, "#ff6666"] )
|
303
|
-
c = Body.new(2.1e16, Vector.new(175, -75, 0), Vector.new(-1, -5, 0))
|
304
|
-
write_tuple( :view, [2, 5, "#ff6666"] )
|
305
|
-
d = Body.new(4.5e16, Vector.new(300, -75, 0), Vector.new(-5, 2, 0))
|
306
|
-
write_tuple( :view, [3, 6, "#ff6666"] )
|
307
|
-
return [a,b,c,d]
|
308
|
-
end
|
309
|
-
|
310
|
-
=begin rdoc
|
311
|
-
Attach a probe to the step method, have it call this method to show the
|
312
|
-
current state of the simulation.
|
313
|
-
=end
|
314
|
-
|
315
|
-
def display(bodies)
|
316
|
-
for i in 0...bodies.length
|
317
|
-
write_tuple( :body, [i, bodies[i].position.x, bodies[i].position.y] )
|
560
|
+
|
561
|
+
=begin rdoc
|
562
|
+
Map a simulation's (x,y) coordinates to screen coordinates using origin
|
563
|
+
and scale factor
|
564
|
+
=end
|
565
|
+
|
566
|
+
def scale(vec, origin, sf)
|
567
|
+
loc = vec.clone
|
568
|
+
loc.scale(sf)
|
569
|
+
loc.add(origin)
|
570
|
+
return loc.x, loc.y
|
318
571
|
end
|
319
|
-
|
320
|
-
|
321
|
-
|
572
|
+
|
573
|
+
=begin rdoc
|
574
|
+
Make a vector that defines the location of the origin (in pixels).
|
575
|
+
=end
|
322
576
|
|
577
|
+
def setOrigin(type)
|
578
|
+
case type
|
579
|
+
when :center
|
580
|
+
Vector.new( Canvas.width/2, Canvas.height/2, 0)
|
581
|
+
else
|
582
|
+
Vector.new(0,0,0)
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
323
586
|
=begin rdoc
|
324
|
-
|
325
|
-
|
326
|
-
space and viewer.
|
587
|
+
Set the scale factor. Use the parameter passed by the user, or find the
|
588
|
+
largest coordinate in a list of bodies. Add 20% for a margin
|
327
589
|
=end
|
328
590
|
|
329
|
-
def
|
330
|
-
|
331
|
-
|
332
|
-
|
591
|
+
def setScale(blist, origin, scale)
|
592
|
+
if scale == nil
|
593
|
+
dmax = 0.0
|
594
|
+
blist.each do |b|
|
595
|
+
b.position.coords.each { |val| dmax = val.abs if val.abs > dmax }
|
596
|
+
end
|
597
|
+
else
|
598
|
+
dmax = scale
|
599
|
+
end
|
600
|
+
sf = (origin == :center) ? (Canvas.width / 2.0) : Canvas.width
|
601
|
+
return (sf / dmax) * 0.8
|
602
|
+
end
|
603
|
+
|
604
|
+
=begin rdoc
|
605
|
+
Initialize the drawing for a "2-body system" with a small object and the
|
606
|
+
earth. The parameters are the list of Body objects and viewing options.
|
607
|
+
=end
|
608
|
+
|
609
|
+
def view_melon(blist, userOptions = {})
|
610
|
+
options = @@droppingOptions.merge(userOptions)
|
611
|
+
options[:dash] = 1
|
612
|
+
options[:dashcount] = 0
|
613
|
+
options[:pendown] = :track
|
614
|
+
edge = options[:canvasSize]
|
615
|
+
mxmin = options[:mxmin]
|
616
|
+
mymin = options[:mymin]
|
617
|
+
hmax = options[:hmax]
|
618
|
+
Canvas.init(edge, edge, "SphereLab")
|
619
|
+
earth = Canvas.circle(200, 2150, 1800, :fill => blist[1].color)
|
620
|
+
blist[1].graphic = earth
|
621
|
+
mymax = earth.coords[1]
|
622
|
+
melon = Canvas.circle(mxmin, mymax, 5, :fill => blist[0].color)
|
623
|
+
blist[0].graphic = melon
|
624
|
+
scale = (mymax-mymin) / hmax.to_f
|
625
|
+
@@drawing = MelonView.new(blist, scale, blist[0].position.y, melon.coords, options)
|
626
|
+
Canvas.sync
|
627
|
+
return true
|
628
|
+
end
|
629
|
+
|
630
|
+
=begin rdoc
|
631
|
+
Position the melon (body 0) at a specified height. If no drawing, save the current
|
632
|
+
position in prevy, but if drawing, get the earth's surface from the drawing (this allows
|
633
|
+
students to move the melon up or down in the middle of an experiment)
|
634
|
+
=end
|
635
|
+
|
636
|
+
def position_melon(blist, height)
|
637
|
+
melon = blist[0]
|
638
|
+
if @@drawing
|
639
|
+
if height > @@drawing.options[:hmax]
|
640
|
+
puts "max height: #{@@drawing.options[:hmax]}"
|
641
|
+
return false
|
642
|
+
end
|
643
|
+
melon.prevy = @@drawing.ground
|
644
|
+
melon.position.y = @@drawing.ground + height
|
645
|
+
a = @@drawing.startloc.clone
|
646
|
+
a[1] -= height * @@drawing.scale
|
647
|
+
a[3] -= height * @@drawing.scale
|
648
|
+
melon.graphic.coords = a
|
649
|
+
Canvas.sync
|
650
|
+
else
|
651
|
+
melon.prevy = melon.position.y
|
652
|
+
melon.position.y += height
|
653
|
+
end
|
654
|
+
melon.velocity.y = 0.0
|
655
|
+
return height
|
656
|
+
end
|
657
|
+
|
658
|
+
def update_melon(blist, dt)
|
659
|
+
melon = blist[0]
|
660
|
+
mx = melon.position.x
|
661
|
+
my = melon.position.y
|
662
|
+
return "splat" if melon.prevy.nil? || my < melon.prevy
|
663
|
+
step_system(blist, dt)
|
664
|
+
if @@drawing
|
665
|
+
@@drawing.options[:dashcount] = (@@drawing.options[:dashcount] + 1) % @@drawing.options[:dash]
|
666
|
+
if @@drawing.options[:dashcount] == 0
|
667
|
+
@@drawing.options[:pendown] = @@drawing.options[:pendown].nil? ? :track : nil
|
668
|
+
end
|
669
|
+
dx = (melon.position.x - mx) * @@drawing.scale
|
670
|
+
dy = (my - melon.position.y) * @@drawing.scale
|
671
|
+
Canvas.move(melon.graphic, dx, dy, @@drawing.options[:pendown])
|
672
|
+
Canvas.sync
|
673
|
+
end
|
674
|
+
return dt
|
675
|
+
end
|
676
|
+
|
677
|
+
# :begin :drop_melon
|
678
|
+
def drop_melon(blist, dt)
|
679
|
+
count = 0
|
680
|
+
loop do
|
681
|
+
res = update_melon(blist, dt)
|
682
|
+
break if res == "splat"
|
683
|
+
count += 1
|
684
|
+
end
|
685
|
+
Canvas.sync if @@drawing
|
686
|
+
return count * dt
|
687
|
+
end
|
688
|
+
# :end :drop_melon
|
333
689
|
|
690
|
+
|
334
691
|
=begin rdoc
|
335
692
|
Methods used in IRB sessions.
|
336
693
|
=end
|
337
694
|
|
338
695
|
# :begin :dist
|
339
|
-
def dist(r, t)
|
340
|
-
|
341
|
-
end
|
696
|
+
def dist(r, t)
|
697
|
+
return r * t
|
698
|
+
end
|
342
699
|
# :end :dist
|
343
700
|
|
344
701
|
# :begin :falling
|
345
|
-
def falling(t)
|
346
|
-
|
347
|
-
end
|
702
|
+
def falling(t)
|
703
|
+
return 0.5 * 9.8 * t**2
|
704
|
+
end
|
348
705
|
# :end :falling
|
349
706
|
|
350
707
|
end # SphereLab
|