正十二面体

前の Shibuya.lisp で、正十二面体を描画する話があって、そのブログエントリ (http://parametron.blogspot.com/2009/02/blog-post_20.html) 自体は前から見てたんだけど、実際にコードを見せてもらうとなんか12面作る部分はもうちょっと抽象化できないものかなぁ…とか思ったので質問した、という話があって。

とりあえず Ruby で書いてみた。12面作る部分はこんなかんじ。

a = [[l0, -l2, 0], [l1, -l1, l1], [l2, 0, l0], [l1, l1, l1], [l0, l2, 0]]
b = a.map(&:rev_xy).reverse
c = a.map(&:rev_yz).reverse
d = c.map(&:rev_xy).reverse
e = a.map(&:rot_x).map(&:rot_y)
f = e.map(&:rev_zx).reverse
g = e.map(&:rev_xy).reverse
h = g.map(&:rev_zx).reverse
i = e.map(&:rot_y).map(&:rot_z)
j = i.map(&:rev_yz).reverse
k = i.map(&:rev_zx).reverse
l = k.map(&:rev_yz).reverse
dodeca = [a, b, c, d, e, f, g, h, i, j, k, l]

解答は、これはすごく綺麗な順序で並べられている、とおっしゃっていて、それには同意なのだけど、なんとなく、こう似たようなものが並んでるとまとめちゃいたくなるものだと思うんですよね。というわけで適当にやってみると、

def rot(d)d=='x'?'y':d=='y'?'z':'x'end
a = [[l0, -l2, 0], [l1, -l1, l1], [l2, 0, l0], [l1, l1, l1], [l0, l2, 0]]
dodeca = [a,
          e=a.map(&:rot_x).map(&:rot_y),
          i=e.map(&:rot_y).map(&:rot_z)]
[[a, 'x'], [e, 'z'], [i, 'y']].each{|a, x|
  y = rot(x)
  z = rot(y)
  dodeca << b = a.map(&"rev_#{x}#{y}".to_sym).reverse
  dodeca << c = a.map(&"rev_#{y}#{z}".to_sym).reverse
  dodeca << d = c.map(&"rev_#{x}#{y}".to_sym).reverse
}

うーんやっぱあきらかに読みにくくなってるなぁ…というか to_sym とかキモいし。でもなんか、ループ内で「ここから x は y の意味で y は z の意味で z は x の意味です!」とかできると綺麗にまとめれそうにも思うんだけど。特に結論とかもなく。

あと最初っから PostScript で全部書くってのはナシなのかなーと思ったのだけど、私の PostScript 力が低すぎてキツそうなのでやめておくことに。

以下全ソース。

class Array
  def x
    self[0]
  end
  def y
    self[1]
  end
  def z
    self[2]
  end

  def rot_x
    [x, z, -y]
  end
  def rot_y
    [-z, y, x]
  end
  def rot_z
    [y, -x, z]
  end

  def rev_yz
    [-x, y, z]
  end
  def rev_zx
    [x, -y, z]
  end
  def rev_xy
    [x, y, -z]
  end

  def rot_yv(a)
    r = Math::PI * a / 180
    c = Math.cos(r)
    s = Math.sin(r)
    [c * x - s * z, y, c * z + s * x]
  end
  def rot_zv(a)
    r = Math::PI * a / 180
    c = Math.cos(r)
    s = Math.sin(r)
    [c * x + s * y, c * y - s * x, z]
  end

  def vec(p)
    [x-p.x, y-p.y, z-p.z]
  end

  def dot(p)
    [y*p.z-z*p.y, z*p.x-x*p.z, x*p.y-y*p.x]
  end
end

l0 = (Math.sqrt(5)+3) / 2
l1 = (Math.sqrt(5)+1) / 2
l2 = 1

a = [[l0, -l2, 0], [l1, -l1, l1], [l2, 0, l0], [l1, l1, l1], [l0, l2, 0]]
b = a.map(&:rev_xy).reverse
c = a.map(&:rev_yz).reverse
d = c.map(&:rev_xy).reverse
e = a.map(&:rot_x).map(&:rot_y)
f = e.map(&:rev_zx).reverse
g = e.map(&:rev_xy).reverse
h = g.map(&:rev_zx).reverse
i = e.map(&:rot_y).map(&:rot_z)
j = i.map(&:rev_yz).reverse
k = i.map(&:rev_zx).reverse
l = k.map(&:rev_yz).reverse

dodeca = [a, b, c, d, e, f, g, h, i, j, k, l]

dodeca = dodeca.map{|pent|pent.map{|pt|pt.rot_zv(15).rot_yv(-15)}}

visible = dodeca.select{|pent|
  (pent[0].vec(pent[1])).dot(pent[2].vec(pent[1]))[0] >= 0
}
invisible = dodeca - visible

[[invisible, 0.8], [visible, 0]].each{|pents, color|
  puts "%f setgray" % color
  pents.each{|pent|
    pent.each_with_index{|pt, i|
      print pt[1,2].map{|v|(v*50)+300} * ' '
      puts i == 0 ? ' moveto' : ' lineto'
    }
    puts 'closepath'
    puts 'stroke'
  }
}
puts 'showpage'
なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h