rubylabs 0.6.4 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|