forked from mckennapsean/code-examples
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpolyhedron.rb
223 lines (182 loc) · 4.76 KB
/
polyhedron.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# inspired by Leon Tabak
# several classes to abstract building a polyhedron and outputting it to SVG
# define a vector
class Vector
# access vector components (scalars)
attr :x
attr :y
attr :z
# creating a vector
def initialize x, y, z
@x = x
@y = y
@z = z
end
# vector addition
def add v
Vector.new @x + v.x, @y + v.y, @z + v.z
end
# vector subtraction
def subtract v
Vector.new @x - v.x, @y - v.y, @z - v.z
end
# vector scalar multiplication (scaling)
def scale scalar
Vector.new scalar * @x, scalar * @y, scalar * @z
end
# dot product of two vectors
def dot v
@x * v.x + @y * v.y + @z * v.z
end
# vector magnitude (from dot product)
def mag
Math.sqrt self.dot self
end
# vector normalization (magnitude & scale)
def normalize
mag = self.mag
scale = 1 / mag
self.scale scale
end
# cross product of two vectors
def cross v
x = @y * v.z - @z * v.y
y = @z * v.x - @x * v.z
z = @x * v.y - @y * v.x
Vector.new x, y, z
end
# print out vector
def to_s
"(" + x.to_s + "," + y.to_s + "," + z.to_s + ")"
end
end
# define a line
class Line
# access line components (vectors)
attr :head
attr :tail
# creating a line
def initialize head, tail
@head = head
@tail = tail
end
# vector from tail to head
def vector
head.subtract tail
end
# length of the line
def length
self.vector.mag
end
# normalize the line's vector
def direction
self.vector.normalize
end
# output a line as an SVG element
def svg
'<line x1= "' + @head.x.to_s + '" ' + 'y1= "' + @head.y.to_s + '" ' +
'x2= "' + @tail.x.to_s + '" ' + 'y2= "' + @tail.y.to_s + '" ' +
"\n\t" + 'style="stroke:rgb(0,0,0);stroke-width:2"/>'
end
end
# define a triangle
class Triangle
# access triangle components (lines)
attr :a
attr :b
attr :c
# creating a triangle
def initialize a, b, c
@a = a
@b = b
@c = c
end
# get the triangle's normal vector
def normal
u = (a.subtract b).normalize
v = (c.subtract b).normalize
u.cross v
end
# break the triangle into smaller pieces (4)
def subdivide
ab = (midpoint a, b).normalize
bc = (midpoint b, c).normalize
ca = (midpoint c, a).normalize
smallerTriangles = Array.new
smallerTriangles << (Triangle.new a, ab, ca)
smallerTriangles << (Triangle.new ab, b, bc)
smallerTriangles << (Triangle.new bc, c, ca)
smallerTriangles << (Triangle.new ab, bc, ca)
end
# output a triangle as an SVG element
def svg radius
translation = Vector.new radius, radius, radius
p0 = (@a.scale radius).add translation
p1 = (@b.scale radius).add translation
p2 = (@c.scale radius).add translation
p01 = Line.new p0, p1
p12 = Line.new p1, p2
p20 = Line.new p2, p0
p01.svg + "\n" + p12.svg + "\n" + p20.svg + "\n"
end
# determine midpoints along the line
private
def midpoint u, v
(u.add v).scale 0.5
end
end
# define a polyhedron
class Polyhedron
# access polyhedron components (faces)
attr :faces
# creating a polyhedron (specifically, an octahedron)
def initialize
# create an octahedron
north = Vector.new 0.0, 1.0, 0.0
east = Vector.new 1.0, 0.0, 0.0
south = Vector.new 0.0, -1.0, 0.0
west = Vector.new -1.0, 0.0, 0.0
near = Vector.new 0.0, 0.0, 1.0
northeast = Triangle.new north, east, near
southeast = Triangle.new east, south, near
southwest = Triangle.new near, south, west
northwest = Triangle.new north, near, west
@faces = [northeast, southeast, southwest, northwest]
end
# break each face into smaller triangular pieces
def subdivide
smallerFaces = Array.new
@faces.each{|t| smallerFaces.concat t.subdivide}
@faces = smallerFaces
end
# output a polyhedron as an SVG element embedded in an HTML page
def html
# size for the polyhedron
radius = 256
# set the polyhedron face size
edges = String.new
@faces.each{|t| edges << (t.svg radius / 2) << "\n"}
# HTML to output on the page
"<html>" + "\n" +
"<body>" + "\n" +
"<h1>A Polyhedron</h1>" + "\n" +
# creating the SVG embedding tag
'<svg xmlns="http://www.w3.org/2000/svg"' + "\n" +
'version="1.1" height="' + (2 * radius).to_s +
'" width="' + (2 * radius).to_s + '">' + "\n" +
# embed all edges of the polyhedron
edges + "\n" +
# wrap up the HTML output
"</svg>" + "\n" +
"</body>" + "\n" +
"</html>"
end
end
# this ends the classes, the rest is a test of the program
# create a polyhedron (technically, only an octahedron)
p = Polyhedron.new
# subdivide the faces a few times
p.subdivide
p.subdivide
# output the SVG as a webpage
puts p.html