From: Dennis Ranke Date: 2004-10-11T19:24:38+09:00 Subject: [SOLUTION] Geodesic Dome Faces (#3) Here is my solution to this quiz. It's fairly straightforward the only (slightly) noteworthy point is that I decided to use spherical interpolation instead of linear interpolation + normalize to calculate the subdivided vertices. When using linear interpolation + normalize along a line, the points in the middle will be further apart than the ones at the ends. With spherical interpolation they are distributed evenly across the arc on the spherical surface. I thought that would make the resulting triangles equal sized but I had to realise that this is actually impossible on the surface of a sphere. So the triangles of my solution are closer to being equal sized but the ones in the centres of the original faces are still bigger than the ones at the original vertices. Anyway, here is my code: class Vector include Math attr_reader :x, :y, :z def self.[](x, y, z) self.new(x, y, z) end def initialize(x, y, z) @x = x @y = y @z = z end def +(o) Vector.new(@x+o.x, @y+o.y, @z+o.z) end def -(o) Vector.new(@x-o.x, @y-o.y, @z-o.z) end def *(o) case o when Vector @x*o.x + @y*o.y + @z*o.z else Vector.new(@x*o, @y*o, @z*o) end end def length sqrt(self*self) end def normalize return self * (1 / length) end def slerp(vec, f) cosinus = self * vec angle = acos(cosinus) l1 = sin(angle / 2) r = cos(angle / 2) l2 = r * tan(angle * (f - 0.5)) f = l2 / l1 * 0.5 + 0.5 return (self * (1 - f) + vec * f).normalize end def to_s "[%7.4f, %7.4f, %7.4f]" % [@x, @y, @z] end end class Triangle def initialize(v1, v2, v3) @v1 = v1 @v2 = v2 @v3 = v3 end def subdivide(frequency) @freq = frequency + 1 faces = [] for y in 0..frequency for x in 0..y faces << Triangle.new(self[x, y], self[x, y+1], self[x+1, y+1]) faces << Triangle.new(self[x, y], self[x+1, y+1], self[x+1, y]) if x < y end end return faces end def to_s "[%s, %s, %s]" % [@v1, @v2, @v3] end private def [](x, y) return @v1 if y == 0 p1 = @v1.slerp(@v2, y.to_f / @freq) p2 = @v1.slerp(@v3, y.to_f / @freq) return p1.slerp(p2, x.to_f / y) end end class Dome def initialize(datahash = nil) @faces = [] if datahash points = datahash[:points] datahash[:faces].each do |face| vertices = face.split(//).map {|v| points[v]} add_face(Triangle.new(*vertices)) end end end def subdivide(frequency) new_dome = Dome.new @faces.each do |face| face.subdivide(frequency).each do |new_face| new_dome.add_face(new_face) end end return new_dome end def add_face(face) @faces << face end def to_s @faces.join("\n") end end SQRT2 = Math.sqrt(2) SQRT3 = Math.sqrt(3) TETRA_Q = SQRT2 / 3 TETRA_R = 1.0 / 3 TETRA_S = SQRT2 / SQRT3 TETRA_T = 2 * SQRT2 / 3 GOLDEN_MEAN = (Math.sqrt(5)+1)/2 PRIMITIVES = { :tetrahedron => { :points => { 'a' => Vector[ -TETRA_S, -TETRA_Q, -TETRA_R ], 'b' => Vector[ TETRA_S, -TETRA_Q, -TETRA_R ], 'c' => Vector[ 0, TETRA_T, -TETRA_R ], 'd' => Vector[ 0, 0, 1 ] }, :faces => %w| acb abd adc dbc | }, :octahedron => { :points => { 'a' => Vector[ 0, 0, 1 ], 'b' => Vector[ 1, 0, 0 ], 'c' => Vector[ 0, -1, 0 ], 'd' => Vector[ -1, 0, 0 ], 'e' => Vector[ 0, 1, 0 ], 'f' => Vector[ 0, 0, -1 ] }, :faces => %w| cba dca eda bea def ebf bcf cdf | }, :icosahedron => { :points => { 'a' => Vector[ 1, GOLDEN_MEAN, 0 ], 'b' => Vector[ 1, -GOLDEN_MEAN, 0 ], 'c' => Vector[ -1, -GOLDEN_MEAN, 0 ], 'd' => Vector[ -1, GOLDEN_MEAN, 0 ], 'e' => Vector[ GOLDEN_MEAN, 0, 1 ], 'f' => Vector[ -GOLDEN_MEAN, 0, 1 ], 'g' => Vector[ -GOLDEN_MEAN, 0, -1 ], 'h' => Vector[ GOLDEN_MEAN, 0, -1 ], 'i' => Vector[ 0, 1, GOLDEN_MEAN ], 'j' => Vector[ 0, 1, -GOLDEN_MEAN ], 'k' => Vector[ 0, -1, -GOLDEN_MEAN ], 'l' => Vector[ 0, -1, GOLDEN_MEAN ] }, :faces => %w| iea iad idf ifl ile eha ajd dgf fcl lbe ebh ahj djg fgc lcb khb kjh kgj kcg kbc | } } puts Dome.new(PRIMITIVES[:octahedron]).subdivide(2) -- exoticorn/farbrausch