graphics 1.0.0b1
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.
- 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
|