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.
@@ -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
@@ -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
@@ -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
@@ -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