graphics 1.0.0b1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.autotest +26 -0
- data/.gemtest +0 -0
- data/History.rdoc +12 -0
- data/Manifest.txt +37 -0
- data/README.rdoc +71 -0
- data/Rakefile +26 -0
- data/examples/boid.rb +653 -0
- data/examples/bounce.rb +86 -0
- data/examples/collision.rb +74 -0
- data/examples/demo.rb +90 -0
- data/examples/editor.rb +70 -0
- data/examples/fluid.rb +246 -0
- data/examples/fluid2.rb +199 -0
- data/examples/lito.rb +108 -0
- data/examples/lito2.rb +110 -0
- data/examples/logo.rb +73 -0
- data/examples/math.rb +42 -0
- data/examples/radar.rb +31 -0
- data/examples/tank.rb +160 -0
- data/examples/tank2.rb +173 -0
- data/examples/targeting.rb +46 -0
- data/examples/vants.rb +69 -0
- data/examples/walker.rb +116 -0
- data/examples/zenspider1.rb +93 -0
- data/examples/zenspider2.rb +123 -0
- data/examples/zenspider3.rb +104 -0
- data/examples/zenspider4.rb +90 -0
- data/examples/zombies.rb +385 -0
- data/lib/graphics.rb +9 -0
- data/lib/graphics/body.rb +216 -0
- data/lib/graphics/extensions.rb +48 -0
- data/lib/graphics/simulation.rb +377 -0
- data/lib/graphics/trail.rb +69 -0
- data/lib/graphics/v.rb +71 -0
- data/resources/images/body.png +0 -0
- data/resources/images/turret.png +0 -0
- data/rubysdl_setup.sh +34 -0
- data/test/test_graphics.rb +408 -0
- metadata +191 -0
- metadata.gz.sig +2 -0
data/examples/fluid2.rb
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
require "graphics"
|
2
|
+
|
3
|
+
class Particle < Graphics::Body
|
4
|
+
MASS = 5 # Particle mass
|
5
|
+
DENSITY = 1 # Rest density
|
6
|
+
GRAVITY = V[0, -0.5] #
|
7
|
+
H = 1 # Smoothing cutoff: essentially, particle size
|
8
|
+
K = 20 # Temperature constant: higher repels more strongly
|
9
|
+
ETA = 1 # Viscosity constant: higher for more viscous
|
10
|
+
DELTA_TIME = 0.1 #
|
11
|
+
|
12
|
+
attr_accessor :density, :pressure_force, :viscosity_force, :s
|
13
|
+
attr_writer :nearby
|
14
|
+
|
15
|
+
def initialize w, x, y, s
|
16
|
+
super w
|
17
|
+
self.x = x
|
18
|
+
self.y = y
|
19
|
+
self.s = s
|
20
|
+
self.nearby = nil
|
21
|
+
|
22
|
+
clear
|
23
|
+
end
|
24
|
+
|
25
|
+
def draw
|
26
|
+
x = self.x * s
|
27
|
+
y = self.y * s
|
28
|
+
|
29
|
+
w.circle(x, y, density, :gray)
|
30
|
+
w.circle(x, y, 5, :white)
|
31
|
+
|
32
|
+
w.angle x, y, a, m * s, :red
|
33
|
+
end
|
34
|
+
|
35
|
+
def clear
|
36
|
+
self.nearby = nil
|
37
|
+
self.density = DENSITY
|
38
|
+
self.pressure_force = V::ZERO
|
39
|
+
self.viscosity_force = V::ZERO
|
40
|
+
end
|
41
|
+
|
42
|
+
def nearby
|
43
|
+
@nearby ||= begin
|
44
|
+
p = self.position
|
45
|
+
w.particles.find_all { |neighbor|
|
46
|
+
(p - neighbor.position).magnitude < H
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def calculate_density
|
52
|
+
nearby.each do |neighbor|
|
53
|
+
distance = (position - neighbor.position)
|
54
|
+
|
55
|
+
self.density += MASS * weight(distance, H) # if distance.magnitude < H
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def calculate_forces
|
60
|
+
particle = self
|
61
|
+
nearby.each do |neighbor|
|
62
|
+
distance = (particle.position - neighbor.position)
|
63
|
+
|
64
|
+
# Temporary terms used to caclulate forces
|
65
|
+
density_p = particle.density
|
66
|
+
density_n = neighbor.density
|
67
|
+
|
68
|
+
# This *should* never happen, but it's good to check, because
|
69
|
+
# we're dividing by density later
|
70
|
+
raise "Particle density is, impossibly, 0" unless density_n != 0
|
71
|
+
|
72
|
+
# Pressure derived from the ideal gas law (constant temp)
|
73
|
+
pressure_p = K * (density_p - DENSITY)
|
74
|
+
pressure_n = K * (density_n - DENSITY)
|
75
|
+
|
76
|
+
# Navier-Stokes equations for pressure and viscosity
|
77
|
+
# (ignoring surface tension)
|
78
|
+
particle.pressure_force += gradient_weight_spiky(distance, H) *
|
79
|
+
(-1.0 * MASS * (pressure_p + pressure_n) / (2 * density_n))
|
80
|
+
|
81
|
+
particle.viscosity_force += (neighbor.velocity - particle.velocity) *
|
82
|
+
(ETA * MASS * (1/density_n) * laplacian_weight_viscosity(distance, H))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def apply_forces
|
87
|
+
particle = self
|
88
|
+
total_force = particle.pressure_force + particle.viscosity_force
|
89
|
+
|
90
|
+
# 'Eulerian' style momentum:
|
91
|
+
|
92
|
+
# Calculate acceleration from forces
|
93
|
+
acceleration = (total_force * (1.0 / particle.density * DELTA_TIME)) + GRAVITY
|
94
|
+
|
95
|
+
# Update position and velocity
|
96
|
+
particle.velocity += acceleration * DELTA_TIME
|
97
|
+
particle.position += particle.velocity * DELTA_TIME
|
98
|
+
|
99
|
+
limit
|
100
|
+
end
|
101
|
+
|
102
|
+
E = 0.01
|
103
|
+
|
104
|
+
def limit width = 15
|
105
|
+
if x >= width - E
|
106
|
+
self.x = width - (E + 0.1*rand)
|
107
|
+
self.m = 0
|
108
|
+
elsif x < E
|
109
|
+
self.x = E + 0.1*rand
|
110
|
+
self.m = 0
|
111
|
+
end
|
112
|
+
|
113
|
+
if y >= width - E
|
114
|
+
self.y = width - (E+rand*0.1)
|
115
|
+
self.m = 0
|
116
|
+
elsif y < E
|
117
|
+
self.y = E + rand*0.1
|
118
|
+
self.m = 0
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
######################################################################
|
123
|
+
# Helpers
|
124
|
+
|
125
|
+
def weight r, h
|
126
|
+
len_r = r.magnitude
|
127
|
+
|
128
|
+
if len_r.xbetween? 0, h
|
129
|
+
315.0 / (64 * Math::PI * h**9) * (h**2 - len_r**2)**3
|
130
|
+
else
|
131
|
+
0.0
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def gradient_weight_spiky r, h
|
136
|
+
len_r = r.magnitude
|
137
|
+
|
138
|
+
if len_r.xbetween? 0, h
|
139
|
+
r * (45.0 / (Math::PI * h**6 * len_r)) * (h - len_r)**2 * (-1.0)
|
140
|
+
else
|
141
|
+
V::ZERO
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def laplacian_weight_viscosity r, h
|
146
|
+
len_r = r.magnitude
|
147
|
+
|
148
|
+
if len_r.xbetween? 0, h
|
149
|
+
45.0 / (2 * Math::PI * h**5) * (1 - len_r / h)
|
150
|
+
else
|
151
|
+
0.0
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class Float
|
157
|
+
def xbetween? min, max
|
158
|
+
min < self && self <= max
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class FluidDynamics < Graphics::Simulation
|
163
|
+
WINSIZE = 500
|
164
|
+
SCALE = 15
|
165
|
+
S = WINSIZE / SCALE
|
166
|
+
|
167
|
+
attr_accessor :particles, :scale
|
168
|
+
|
169
|
+
def initialize
|
170
|
+
super WINSIZE, WINSIZE, 16, "Smoothed Particle Hydrodynamics"
|
171
|
+
|
172
|
+
self.particles = []
|
173
|
+
self.scale = SCALE
|
174
|
+
|
175
|
+
# Instantiate particles!
|
176
|
+
(0..10).each do |x|
|
177
|
+
(0..10).each do |y|
|
178
|
+
jitter = rand * 0.1
|
179
|
+
|
180
|
+
particles << Particle.new(self, x + 1 + jitter, y + 5, S)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def draw n
|
186
|
+
clear
|
187
|
+
particles.each(&:draw)
|
188
|
+
fps n
|
189
|
+
end
|
190
|
+
|
191
|
+
def update n
|
192
|
+
particles.each(&:clear)
|
193
|
+
particles.each(&:calculate_density)
|
194
|
+
particles.each(&:calculate_forces)
|
195
|
+
particles.each(&:apply_forces)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
FluidDynamics.new.run
|
data/examples/lito.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'matrix'
|
2
|
+
|
3
|
+
class Matrix
|
4
|
+
def rotate x, y
|
5
|
+
# I can't find a neat matrix-math solution for
|
6
|
+
# this, so let's do it with regular 'ol `map`.
|
7
|
+
Matrix[ *self.to_a.rotate(y).map {|row| row.rotate x} ]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Pad or shrink a matrix
|
11
|
+
def take x, y
|
12
|
+
Matrix.build(y, x){|i, j| if self[i, j].nil? then 0 else self[i, j] end }
|
13
|
+
end
|
14
|
+
|
15
|
+
# Bitwise operations on boolean matrices
|
16
|
+
def & other
|
17
|
+
Matrix.Raise ErrDimensionMismatch unless
|
18
|
+
self.row_count == other.row_count and
|
19
|
+
self.column_count == other.column_count
|
20
|
+
|
21
|
+
Matrix.build(self.row_count){|i, j| self[i, j] & other[i, j] }
|
22
|
+
end
|
23
|
+
|
24
|
+
def | other
|
25
|
+
Matrix.Raise ErrDimensionMismatch unless
|
26
|
+
self.row_count == other.row_count and
|
27
|
+
self.column_count == other.column_count
|
28
|
+
|
29
|
+
Matrix.build(self.row_count){|i, j| self[i, j] | other[i, j] }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def sum l
|
34
|
+
l.reduce :+
|
35
|
+
end
|
36
|
+
|
37
|
+
def twos grid
|
38
|
+
grid.map{|i| if i == 2 then 1 else 0 end}
|
39
|
+
end
|
40
|
+
|
41
|
+
def threes grid
|
42
|
+
grid.map{|i| if i == 3 then 1 else 0 end}
|
43
|
+
end
|
44
|
+
|
45
|
+
AROUND = [-1, 0, 1].product([-1, 0, 1])
|
46
|
+
|
47
|
+
def neighbors grid
|
48
|
+
sum(AROUND.map{|x, y| grid.rotate x, y } ) - grid
|
49
|
+
end
|
50
|
+
|
51
|
+
def life grid
|
52
|
+
((twos neighbors grid) & grid) | (threes neighbors grid)
|
53
|
+
end
|
54
|
+
|
55
|
+
size, width, count = 10, 64, 256
|
56
|
+
|
57
|
+
require "sdl"
|
58
|
+
|
59
|
+
SDL.init SDL::INIT_VIDEO
|
60
|
+
SDL::WM::set_caption "Conway's Game of Life", "Conway's Game of Life"
|
61
|
+
|
62
|
+
screen = SDL::Screen.open 640, 640, 16, SDL::HWSURFACE|SDL::DOUBLEBUF
|
63
|
+
|
64
|
+
black = screen.format.map_rgb 0, 0, 0
|
65
|
+
white = screen.format.map_rgb 255, 255, 255
|
66
|
+
|
67
|
+
w, h = screen.w, screen.h
|
68
|
+
|
69
|
+
matrix = Matrix[[1, 1, 1],
|
70
|
+
[0, 0, 1],
|
71
|
+
[1, 1, 1]].take(width, width).rotate(-(width/2), -(width/2))
|
72
|
+
|
73
|
+
paused = false
|
74
|
+
step = false
|
75
|
+
(1..(1.0/0)).each do |n|
|
76
|
+
puts n if n % 100 == 0
|
77
|
+
|
78
|
+
screen.fill_rect 0, 0, w, h, black
|
79
|
+
|
80
|
+
matrix.to_a.each_with_index do |row, y|
|
81
|
+
row.each_with_index do |c, x|
|
82
|
+
if c == 1 then
|
83
|
+
screen.fill_rect x*size, y*size, size-1, size-1, white
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
screen.flip
|
89
|
+
|
90
|
+
while event = SDL::Event.poll
|
91
|
+
case event
|
92
|
+
when SDL::Event::KeyDown then
|
93
|
+
c = event.sym.chr
|
94
|
+
exit if c == "q" or c == "Q" or c == "\e"
|
95
|
+
step = true if c == " "
|
96
|
+
puts n
|
97
|
+
paused = ! paused
|
98
|
+
when SDL::Event::Quit then
|
99
|
+
exit
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
matrix = life matrix unless paused
|
104
|
+
if step then
|
105
|
+
paused = true
|
106
|
+
step = false
|
107
|
+
end
|
108
|
+
end
|
data/examples/lito2.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require "matrix"
|
2
|
+
require "graphics"
|
3
|
+
|
4
|
+
class Matrix
|
5
|
+
def rotate x, y
|
6
|
+
# I can't find a neat matrix-math solution for
|
7
|
+
# this, so let's do it with regular 'ol `map`.
|
8
|
+
Matrix[ *self.to_a.rotate(y).map {|row| row.rotate x} ]
|
9
|
+
end
|
10
|
+
|
11
|
+
# Pad or shrink a matrix
|
12
|
+
def take x, y
|
13
|
+
Matrix.build(y, x){|i, j| if self[i, j].nil? then 0 else self[i, j] end }
|
14
|
+
end
|
15
|
+
|
16
|
+
# Bitwise operations on boolean matrices
|
17
|
+
def & other
|
18
|
+
Matrix.Raise ErrDimensionMismatch unless
|
19
|
+
self.row_count == other.row_count and
|
20
|
+
self.column_count == other.column_count
|
21
|
+
|
22
|
+
Matrix.build(self.row_count){|i, j| self[i, j] & other[i, j] }
|
23
|
+
end
|
24
|
+
|
25
|
+
def | other
|
26
|
+
Matrix.Raise ErrDimensionMismatch unless
|
27
|
+
self.row_count == other.row_count and
|
28
|
+
self.column_count == other.column_count
|
29
|
+
|
30
|
+
Matrix.build(self.row_count){|i, j| self[i, j] | other[i, j] }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class LitoGol
|
35
|
+
AROUND = [-1, 0, 1].product([-1, 0, 1])
|
36
|
+
|
37
|
+
attr_accessor :matrix
|
38
|
+
|
39
|
+
def initialize width
|
40
|
+
count = ((width*width) * 0.15).to_i
|
41
|
+
dimensions = width.times.to_a
|
42
|
+
data = dimensions.product(dimensions).sample(count).sort
|
43
|
+
|
44
|
+
self.matrix = Matrix.build(width, width) do |r, c|
|
45
|
+
data.include?([r, c]) ? 1 : 0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def sum l
|
50
|
+
l.reduce :+
|
51
|
+
end
|
52
|
+
|
53
|
+
def twos grid
|
54
|
+
grid.map{|i| if i == 2 then 1 else 0 end}
|
55
|
+
end
|
56
|
+
|
57
|
+
def threes grid
|
58
|
+
grid.map{|i| if i == 3 then 1 else 0 end}
|
59
|
+
end
|
60
|
+
|
61
|
+
def neighbors grid
|
62
|
+
sum(AROUND.map{|x, y| grid.rotate x, y } ) - grid
|
63
|
+
end
|
64
|
+
|
65
|
+
def life grid
|
66
|
+
((twos neighbors grid) & grid) | (threes neighbors grid)
|
67
|
+
end
|
68
|
+
|
69
|
+
def update
|
70
|
+
self.matrix = life matrix
|
71
|
+
end
|
72
|
+
|
73
|
+
def each
|
74
|
+
matrix.to_a.each_with_index do |row, y|
|
75
|
+
row.each_with_index do |c, x|
|
76
|
+
yield c, x, y
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class LitoGolSimulation < Graphics::Simulation
|
83
|
+
attr_accessor :gol
|
84
|
+
|
85
|
+
SIZE, WIDTH = 10, 64
|
86
|
+
|
87
|
+
def initialize
|
88
|
+
super 640, 640, 16, "Conway's Game of Life"
|
89
|
+
|
90
|
+
self.gol = LitoGol.new WIDTH
|
91
|
+
end
|
92
|
+
|
93
|
+
def draw n
|
94
|
+
clear
|
95
|
+
|
96
|
+
gol.each do |c, x, y|
|
97
|
+
if c == 1 then
|
98
|
+
ellipse x*SIZE, y*SIZE, (SIZE-1)/2, (SIZE-1)/2, :white
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
fps n
|
103
|
+
end
|
104
|
+
|
105
|
+
def update n
|
106
|
+
self.gol.update
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
LitoGolSimulation.new.run
|
data/examples/logo.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
require "graphics"
|
5
|
+
$: << "."
|
6
|
+
require "examples/editor"
|
7
|
+
|
8
|
+
class Turtle < Graphics::Body
|
9
|
+
attr_accessor :src, :pen
|
10
|
+
|
11
|
+
def initialize w, src
|
12
|
+
super w
|
13
|
+
self.x = w.w/2
|
14
|
+
self.y = w.h/2
|
15
|
+
self.a = 90
|
16
|
+
self.src = src
|
17
|
+
self.pen = true
|
18
|
+
end
|
19
|
+
|
20
|
+
def draw
|
21
|
+
self.x = w.w/2
|
22
|
+
self.y = w.h/2
|
23
|
+
self.a = 90
|
24
|
+
|
25
|
+
src.each do |line|
|
26
|
+
case line
|
27
|
+
when "pd" then
|
28
|
+
self.pen = true
|
29
|
+
when "pu" then
|
30
|
+
self.pen = false
|
31
|
+
when /rt (\d+)/ then
|
32
|
+
self.a -= $1.to_i
|
33
|
+
when /lt (\d+)/ then
|
34
|
+
self.a += $1.to_i
|
35
|
+
when /f (\d+)/ then
|
36
|
+
dist = $1.to_i
|
37
|
+
if pen then
|
38
|
+
w.angle x, y, a, dist, :white
|
39
|
+
end
|
40
|
+
move_by a, dist
|
41
|
+
else
|
42
|
+
src.delete line
|
43
|
+
warn "ERROR: #{line}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
draw_turtle
|
47
|
+
end
|
48
|
+
|
49
|
+
def draw_turtle
|
50
|
+
p1 = w.project(x, y, a, 15)
|
51
|
+
p2 = w.project(x, y, a+90, 5)
|
52
|
+
p3 = w.project(x, y, a-90, 5)
|
53
|
+
|
54
|
+
polygon p1, p2, p3, :green
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Logo < Editor
|
59
|
+
attr_accessor :turtle
|
60
|
+
|
61
|
+
def initialize
|
62
|
+
super
|
63
|
+
self.turtle = Turtle.new self, lines
|
64
|
+
end
|
65
|
+
|
66
|
+
def draw_scene
|
67
|
+
turtle.draw
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if $0 == __FILE__
|
72
|
+
Logo.new.run
|
73
|
+
end
|