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/bounce.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
require "graphics"
|
4
|
+
|
5
|
+
class Ball < Graphics::Body
|
6
|
+
COUNT = 50
|
7
|
+
|
8
|
+
G = V[0, -18 / 60.0]
|
9
|
+
|
10
|
+
attr_accessor :g
|
11
|
+
|
12
|
+
def initialize w
|
13
|
+
super
|
14
|
+
|
15
|
+
self.a = rand 180
|
16
|
+
self.m = rand 100
|
17
|
+
self.g = G
|
18
|
+
end
|
19
|
+
|
20
|
+
def update
|
21
|
+
fall
|
22
|
+
move
|
23
|
+
bounce
|
24
|
+
end
|
25
|
+
|
26
|
+
def fall
|
27
|
+
self.velocity += g
|
28
|
+
end
|
29
|
+
|
30
|
+
def label
|
31
|
+
l = "%.1f %.1f" % dx_dy
|
32
|
+
w.text l, x-10, y-40, :white
|
33
|
+
end
|
34
|
+
|
35
|
+
def draw
|
36
|
+
# w.angle x, y, a, 3*m, :red
|
37
|
+
w.angle x, y, a, 50, :red
|
38
|
+
w.circle x, y, 5, :white, :filled
|
39
|
+
# label
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class BounceSimulation < Graphics::Simulation
|
44
|
+
attr_accessor :bs
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
super 640, 640, 16, "Bounce"
|
48
|
+
|
49
|
+
self.bs = populate Ball
|
50
|
+
end
|
51
|
+
|
52
|
+
def update n
|
53
|
+
bs.each(&:update)
|
54
|
+
end
|
55
|
+
|
56
|
+
def draw n
|
57
|
+
clear
|
58
|
+
bs.each(&:draw)
|
59
|
+
bs.first.label
|
60
|
+
fps n
|
61
|
+
end
|
62
|
+
|
63
|
+
def handle_keys
|
64
|
+
super
|
65
|
+
randomize if SDL::Key.press? SDL::Key::SPACE
|
66
|
+
reverse if SDL::Key.press? SDL::Key::R
|
67
|
+
end
|
68
|
+
|
69
|
+
def randomize
|
70
|
+
bs.each do |b|
|
71
|
+
b.m = rand(100)
|
72
|
+
b.a = rand(180)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def reverse
|
77
|
+
return if @guard
|
78
|
+
@guard = true
|
79
|
+
bs.each do |b|
|
80
|
+
b.g *= -1
|
81
|
+
end
|
82
|
+
@guard = false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
BounceSimulation.new.run
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
require "graphics"
|
4
|
+
|
5
|
+
class Sprite < Graphics::Body
|
6
|
+
COUNT = 8
|
7
|
+
|
8
|
+
attr_accessor :image
|
9
|
+
|
10
|
+
def initialize w
|
11
|
+
super w
|
12
|
+
|
13
|
+
self.a = random_angle
|
14
|
+
self.m = 5
|
15
|
+
end
|
16
|
+
|
17
|
+
def update
|
18
|
+
move
|
19
|
+
bounce
|
20
|
+
end
|
21
|
+
|
22
|
+
def collide
|
23
|
+
self.a = (a + 180).degrees
|
24
|
+
end
|
25
|
+
|
26
|
+
def draw
|
27
|
+
w.blit image, x, y, a
|
28
|
+
end
|
29
|
+
|
30
|
+
def collide_with? other
|
31
|
+
w.cmap.collision_check(x, y, w.cmap, other.x, other.y) != nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Collision < Graphics::Simulation
|
36
|
+
attr_accessor :sprites, :cmap, :image
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
super 850, 850, 16, "Collision"
|
40
|
+
|
41
|
+
self.image = SDL::Surface.load "resources/images/body.png"
|
42
|
+
self.cmap = image.make_collision_map
|
43
|
+
|
44
|
+
self.sprites = populate Sprite do |s|
|
45
|
+
s.image = image
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def inspect
|
50
|
+
"<Screen ...>"
|
51
|
+
end
|
52
|
+
|
53
|
+
def detect_collisions(sprites)
|
54
|
+
collisions = []
|
55
|
+
sprites.combination(2).each do |a, b|
|
56
|
+
collisions << a << b if a.collide_with? b
|
57
|
+
end
|
58
|
+
collisions.uniq
|
59
|
+
end
|
60
|
+
|
61
|
+
def update n
|
62
|
+
sprites.each(&:update)
|
63
|
+
detect_collisions(sprites).each(&:collide)
|
64
|
+
end
|
65
|
+
|
66
|
+
def draw n
|
67
|
+
clear
|
68
|
+
|
69
|
+
sprites.each(&:draw)
|
70
|
+
fps n
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
Collision.new.run
|
data/examples/demo.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
require "graphics"
|
5
|
+
|
6
|
+
class Ball < Graphics::Body
|
7
|
+
def initialize w
|
8
|
+
super
|
9
|
+
|
10
|
+
self.x = 50
|
11
|
+
self.y = 50
|
12
|
+
|
13
|
+
self.a = 60
|
14
|
+
self.m = 3
|
15
|
+
end
|
16
|
+
|
17
|
+
def update
|
18
|
+
move
|
19
|
+
wrap
|
20
|
+
end
|
21
|
+
|
22
|
+
def draw n
|
23
|
+
a = n % 360
|
24
|
+
|
25
|
+
w.angle x, y, a, 50, :green
|
26
|
+
w.circle x, y, 5, :white, :filled
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Demo < Graphics::Simulation
|
31
|
+
attr_accessor :ball, :img
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
super 800, 800, 16, "Boid"
|
35
|
+
self.ball = Ball.new self
|
36
|
+
|
37
|
+
r = color[:red]
|
38
|
+
|
39
|
+
self.img = sprite 10, 10 do
|
40
|
+
circle 5, 5, 5, :white, :filled
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def update n
|
45
|
+
ball.update
|
46
|
+
end
|
47
|
+
|
48
|
+
def draw n
|
49
|
+
clear
|
50
|
+
|
51
|
+
line 100, 50, 125, 75, :white
|
52
|
+
|
53
|
+
hline 100, :white
|
54
|
+
|
55
|
+
vline 100, :white
|
56
|
+
|
57
|
+
angle 125, 50, 45, 10, :white
|
58
|
+
|
59
|
+
fast_rect 150, 50, 10, 10, :white
|
60
|
+
|
61
|
+
point 175, 50, :green
|
62
|
+
|
63
|
+
rect 200, 50, 10, 10, :white
|
64
|
+
|
65
|
+
circle 225, 50, 10, :white
|
66
|
+
|
67
|
+
ellipse 250, 50, 10, 10, :white
|
68
|
+
|
69
|
+
bezier 275, 50, 275, 100, 285, 0, 300, 50, :white
|
70
|
+
|
71
|
+
rect 300, 25, 50, 50, :white
|
72
|
+
blit img, 325, 50, 0
|
73
|
+
|
74
|
+
text "blah", 350, 50, :white
|
75
|
+
|
76
|
+
x, y, * = mouse
|
77
|
+
rect x, y, 150, 50, :white
|
78
|
+
text "#{x}/#{y}", x, y, :white
|
79
|
+
|
80
|
+
debug "debug"
|
81
|
+
|
82
|
+
ball.draw n
|
83
|
+
|
84
|
+
fps n
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
if $0 == __FILE__
|
89
|
+
Demo.new.run
|
90
|
+
end
|
data/examples/editor.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
require "graphics"
|
5
|
+
|
6
|
+
class Editor < Graphics::Simulation
|
7
|
+
attr_accessor :overlay, :s, :lines
|
8
|
+
|
9
|
+
alias :overlay? :overlay
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
super 850, 850, 16, self.class.name
|
13
|
+
|
14
|
+
self.overlay = true
|
15
|
+
self.s = ""
|
16
|
+
self.lines = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def handle_event e, n
|
20
|
+
case e
|
21
|
+
when SDL::Event::KeyDown then
|
22
|
+
if e.mod & (SDL::Key::MOD_LCTRL | SDL::Key::MOD_RCTRL) != 0 then
|
23
|
+
case e.sym.chr
|
24
|
+
when "t" then
|
25
|
+
self.overlay = ! self.overlay
|
26
|
+
end
|
27
|
+
else
|
28
|
+
c = e.sym.chr rescue ""
|
29
|
+
c.upcase! if e.mod & (SDL::Key::MOD_LSHIFT | SDL::Key::MOD_RSHIFT) != 0
|
30
|
+
case c
|
31
|
+
when "\r" then
|
32
|
+
c = "\n"
|
33
|
+
lines << s.dup
|
34
|
+
s.clear
|
35
|
+
return
|
36
|
+
when "\b" then
|
37
|
+
self.s = s[0..-2]
|
38
|
+
return
|
39
|
+
end
|
40
|
+
s << c
|
41
|
+
end
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def draw n
|
48
|
+
clear
|
49
|
+
|
50
|
+
draw_scene
|
51
|
+
draw_overlay
|
52
|
+
end
|
53
|
+
|
54
|
+
def draw_scene
|
55
|
+
end
|
56
|
+
|
57
|
+
def draw_overlay
|
58
|
+
if overlay? then
|
59
|
+
lines.each_with_index do |l, i|
|
60
|
+
text l, 10, ((lines.size-i)*font.height), :gray
|
61
|
+
end
|
62
|
+
|
63
|
+
text "> #{s}_", 10, 0, :white
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
if $0 == __FILE__
|
69
|
+
Editor.new.run
|
70
|
+
end
|
data/examples/fluid.rb
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
require "graphics"
|
2
|
+
|
3
|
+
class Float
|
4
|
+
##
|
5
|
+
# A floating-point friendly `between?` function that excludes
|
6
|
+
# the lower bound.
|
7
|
+
# Equivalent to `min < x <= max`
|
8
|
+
##
|
9
|
+
def xbetween? min, max
|
10
|
+
min < self && self <= max
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Particle
|
15
|
+
attr_accessor :density, :position, :velocity,
|
16
|
+
:pressure_force, :viscosity_force
|
17
|
+
def initialize pos
|
18
|
+
# Scalars
|
19
|
+
@density = 0
|
20
|
+
|
21
|
+
# Forces
|
22
|
+
@position = pos
|
23
|
+
@velocity = V::ZERO
|
24
|
+
@pressure_force = V::ZERO
|
25
|
+
@viscosity_force = V::ZERO
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class SPH
|
30
|
+
##
|
31
|
+
# Constants
|
32
|
+
#
|
33
|
+
|
34
|
+
MASS = 5 # Particle mass
|
35
|
+
DENSITY = 1 # Rest density
|
36
|
+
GRAVITY = V[0, -0.5]
|
37
|
+
H = 1 # Smoothing cutoff- essentially, particle size
|
38
|
+
K = 20 # Temperature constant- higher means particle repel more strongly
|
39
|
+
ETA = 1 # Viscosity constant- higher for more viscous
|
40
|
+
|
41
|
+
attr_reader :particles
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
# Instantiate particles!
|
45
|
+
@particles = []
|
46
|
+
(0..10).each do |x|
|
47
|
+
(0..10).each do |y|
|
48
|
+
jitter = rand * 0.1
|
49
|
+
particles << Particle.new(V[x+1+jitter, y+5])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# A weighting function (kernel) for the contribution of each neighbor
|
56
|
+
# to a particle's density. Forms a nice smooth gradient from the center
|
57
|
+
# of a particle to H, where it's 0
|
58
|
+
#
|
59
|
+
|
60
|
+
def weight r, h
|
61
|
+
len_r = r.magnitude
|
62
|
+
|
63
|
+
if len_r.xbetween? 0, h
|
64
|
+
315.0 / (64 * Math::PI * h**9) * (h**2 - len_r**2)**3
|
65
|
+
else
|
66
|
+
0.0
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Gradient ( that is, V(dx, dy) ) of a weighting function for
|
72
|
+
# a particle's pressure. This weight function is spiky (not flat or
|
73
|
+
# smooth at x=0) so particles close together repel strongly.
|
74
|
+
#
|
75
|
+
|
76
|
+
def gradient_weight_spiky r, h
|
77
|
+
len_r = r.magnitude
|
78
|
+
|
79
|
+
if len_r.xbetween? 0, h
|
80
|
+
r * (45.0 / (Math::PI * h**6 * len_r)) * (h - len_r)**2 * (-1.0)
|
81
|
+
else
|
82
|
+
V::ZERO
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# The laplacian of a weighting function that tends towards infinity when
|
88
|
+
# approching 0 (slows down particles moving faster than their neighbors)
|
89
|
+
#
|
90
|
+
|
91
|
+
def laplacian_weight_viscosity r, h
|
92
|
+
len_r = r.magnitude
|
93
|
+
|
94
|
+
if len_r.xbetween? 0, h
|
95
|
+
45.0 / (2 * Math::PI * h**5) * (1 - len_r / h)
|
96
|
+
else
|
97
|
+
0.0
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def clear
|
102
|
+
# Clear everything
|
103
|
+
particles.each do |particle|
|
104
|
+
particle.density = DENSITY
|
105
|
+
particle.pressure_force = V::ZERO
|
106
|
+
particle.viscosity_force = V::ZERO
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def calculate_density
|
111
|
+
# Calculate fluid density around each particle
|
112
|
+
particles.each do |particle|
|
113
|
+
particles.each do |neighbor|
|
114
|
+
|
115
|
+
# If particles are close together, density increases
|
116
|
+
distance = particle.position - neighbor.position
|
117
|
+
|
118
|
+
if distance.magnitude < H # Particles are close enough to matter
|
119
|
+
particle.density += MASS * weight(distance, H)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
# p particle.density if particle.density > 2*H
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def calculate_forces
|
127
|
+
# Calculate forces on each particle based on density
|
128
|
+
particles.each do |particle|
|
129
|
+
particles.each do |neighbor|
|
130
|
+
distance = particle.position - neighbor.position
|
131
|
+
if distance.magnitude <= H then
|
132
|
+
# Temporary terms used to caclulate forces
|
133
|
+
density_p = particle.density
|
134
|
+
density_n = neighbor.density
|
135
|
+
|
136
|
+
# This *should* never happen, but it's good to check,
|
137
|
+
# because we're dividing by density later
|
138
|
+
raise "Particle density is, impossibly, 0" unless density_n != 0
|
139
|
+
|
140
|
+
# Pressure derived from the ideal gas law (constant temp)
|
141
|
+
pressure_p = K * (density_p - DENSITY)
|
142
|
+
pressure_n = K * (density_n - DENSITY)
|
143
|
+
|
144
|
+
# Navier-Stokes equations for pressure and viscosity
|
145
|
+
# (ignoring surface tension)
|
146
|
+
particle.pressure_force += gradient_weight_spiky(distance, H) *
|
147
|
+
(-1.0 * MASS * (pressure_p + pressure_n) / (2 * density_n))
|
148
|
+
|
149
|
+
particle.viscosity_force +=
|
150
|
+
(neighbor.velocity - particle.velocity) *
|
151
|
+
(ETA * MASS * (1/density_n) * laplacian_weight_viscosity(distance, H))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def apply_forces delta_time
|
158
|
+
# Apply forces to particles- make them move!
|
159
|
+
particles.each do |particle|
|
160
|
+
total_force = particle.pressure_force + particle.viscosity_force
|
161
|
+
|
162
|
+
# 'Eulerian' style momentum:
|
163
|
+
|
164
|
+
# Calculate acceleration from forces
|
165
|
+
acceleration = (total_force * (1.0 / particle.density * delta_time)) + GRAVITY
|
166
|
+
|
167
|
+
# Update position and velocity
|
168
|
+
particle.velocity += acceleration * delta_time
|
169
|
+
particle.position += particle.velocity * delta_time
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def step delta_time
|
174
|
+
clear
|
175
|
+
calculate_density
|
176
|
+
calculate_forces
|
177
|
+
apply_forces delta_time
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# The walls nudge particles back in-bounds, plus a little jitter
|
182
|
+
# so nothing gets stuck
|
183
|
+
#
|
184
|
+
|
185
|
+
def make_particles_stay_in_bounds scale
|
186
|
+
# TODO: Better boundary conditions (THESE ARE A LAME WORKAROUND)
|
187
|
+
particles.each do |particle|
|
188
|
+
if particle.position.x >= scale - 0.01
|
189
|
+
particle.position.x = scale - (0.01 + 0.1*rand)
|
190
|
+
particle.velocity.x = 0
|
191
|
+
elsif particle.position.x < 0.01
|
192
|
+
particle.position.x = 0.01 + 0.1*rand
|
193
|
+
particle.velocity.x = 0
|
194
|
+
end
|
195
|
+
|
196
|
+
if particle.position.y >= scale - 0.01
|
197
|
+
particle.position.y = scale - (0.01+rand*0.1)
|
198
|
+
particle.velocity.y = 0
|
199
|
+
elsif particle.position.y < 0.01
|
200
|
+
particle.position.y = 0.01 + rand*0.1
|
201
|
+
particle.velocity.y = 0
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
class SimulationWindow < Graphics::Simulation
|
208
|
+
WINSIZE = 500
|
209
|
+
|
210
|
+
attr_reader :simulation, :s
|
211
|
+
|
212
|
+
DELTA_TIME = 0.1
|
213
|
+
|
214
|
+
def initialize
|
215
|
+
super WINSIZE, WINSIZE, 16, "Smoothed Particle Hydrodynamics"
|
216
|
+
@simulation = SPH.new
|
217
|
+
@scale = 15
|
218
|
+
@s = WINSIZE.div @scale
|
219
|
+
end
|
220
|
+
|
221
|
+
def update time
|
222
|
+
simulation.step DELTA_TIME
|
223
|
+
simulation.make_particles_stay_in_bounds @scale
|
224
|
+
end
|
225
|
+
|
226
|
+
def draw time
|
227
|
+
clear
|
228
|
+
|
229
|
+
simulation.particles.each do |particle|
|
230
|
+
pos = particle.position * s
|
231
|
+
vel = particle.velocity * s
|
232
|
+
|
233
|
+
# Particles
|
234
|
+
circle(pos.x, pos.y, 5, :white)
|
235
|
+
circle(pos.x, pos.y, particle.density, :gray)
|
236
|
+
|
237
|
+
# Velocity vectors
|
238
|
+
p2 = pos + vel
|
239
|
+
line(pos.x, pos.y, p2.x, p2.y, :red)
|
240
|
+
|
241
|
+
fps time
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
SimulationWindow.new.run
|