graphics 1.0.0b1

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