Skip to content

Instantly share code, notes, and snippets.

@takehiko
Created January 1, 2019 13:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save takehiko/e74d23b7b8e8e73c144ee45b2f6bada6 to your computer and use it in GitHub Desktop.
Save takehiko/e74d23b7b8e8e73c144ee45b2f6bada6 to your computer and use it in GitHub Desktop.
Drawing SVG images of a regular icosahedron with "kuku" forms along the sides
#!/usr/bin/env ruby
# icosa-drawer.rb : drawing SVG images of a regular icosahedron
# with "kuku" forms along the sides
# by takehikom
module IcosaDrawer
class Triangle
def initialize(param = {})
@label = param[:label]
@x = param[:x] || 0.0
@y = param[:y] || 0.0
@inverted = param[:inv]
@side = param[:side] || 100.0
@fontstyle = param[:fontstyle] # "font-size:16px; font-family:Serif; font-style:normal; font-weight:normal; fill:#000000; stroke:none;"
@rot0 = param[:rot0]
@rot60 = param[:rot60]
@rot120 = param[:rot120]
@rot180 = param[:rot180]
@rot240 = param[:rot240]
@rot300 = param[:rot300]
end
attr_accessor :label, :x, :y, :inverted, :side, :fontstyle
attr_accessor :rot0, :rot60, :rot120, :rot180, :rot240, :rot300
def svg_transform(indent_level = 0)
' ' * indent_level +
'<g transform="translate(%f,%f)%s">' % [@x, @y, @inverted ? " rotate(180)" : ""] + "\n"
end
def svg_center(indent_level = 0)
return ""
' ' * indent_level +
'<text x="%f" y="%f">%s</text>' % [0, @side * 0.05, @label] +
"\n"
end
def svg_sidetext(rot, indent_level = 0)
text = self.send("rot#{rot}")
return "" if text.nil?
# inverted
# rot180 => rotate(360)
# rot300 => rotate(240)
# rot60 => rotate(120)
# !inverted
# rot0 => rotate(360)
# rot120 => rotate(240)
# rot240 => rotate(120)
' ' * indent_level +
'<g transform="rotate(%d)">' % [inverted ? (180 - rot) : (360 - rot)] +
'<text x="%f" y="%f">%s</text>' % [0, @side * 0.24, text] +
'</g>' + "\n"
end
def svg_end_transform(indent_level = 0)
' ' * indent_level +
'</g>' + "\n"
end
end
class Drawer
def initialize(param = {})
@filename_svg = param[:svg] || "icosa.svg"
# @mode == 0 : 面と頂点に番号
# @mode == 1 : 同一の辺にはa×bとb×a
# @mode == 2 : 同一の辺には同じ積の九九
@mode = (param[:mode] || 1).to_i
@margin = 10
@s = @side = 100 # 正三角形の一辺の長さ
@s_h = @s / 2.0
@s_r3 = @s.to_f * Math.sqrt(3.0)
@s_r3h = @s_r3 / 2.0 # 正三角形の高さ
end
def start
@width = (@s * 6 + @margin * 2).ceil
@height = (@s_r3h * 3 + @margin * 2).ceil
draw_header
draw_expansion
setup_surfaces
draw_surfaces
draw_footer
save
end
def draw_header
# SVGはじめ
@src = <<EOS
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg"
width="#{@width}" height="#{@height}" id="icosa#{@mode}">
<style type="text/css" >
<![CDATA[
.thinline { fill:none; stroke:black; stroke-width:1; }
.thickline { fill:none; stroke:black; stroke-width:2; }
text { font-size:16px; font-family:Serif; font-style:normal; font-weight:normal; fill:#000000; stroke:none; text-anchor:middle }
]]>
</style>
<rect x="0" y="0" width="#{@width}" height="#{@height}" fill="white" stroke="none"/>
EOS
end
def draw_expansion
# http://polyhedra.cocolog-nifty.com/blog/2017/11/post-dcc9.html
# http://polyhedra.cocolog-nifty.com/polyhedra20.pdf
# 横線4本
0.upto(3) do |i|
x1 = @margin + @s_h * ((i + 1) % 2)
y1 = @margin + @s_r3h * i
x2 = x1 + @s * (i == 1 ? 6 : 5)
y2 = y1
@src += '<path d="M %f,%f L %f,%f" class="thickline"/>' % [x1, y1, x2, y2]
@src += "\n"
end
# 右下がり斜め線5本
-1.upto(4) do |i|
x1 = @margin + @s_h + @s * i
y1 = @margin
x2 = x1 + @s * 1.5
y2 = y1 + @s_r3h * 3
if i == -1
x1 += @s * 0.5
y1 += @s_r3h
elsif i == 4
x2 -= @s * 0.5
y2 -= @s_r3h
end
@src += '<path d="M %f,%f L %f,%f" class="thickline"/>' % [x1, y1, x2, y2]
@src += "\n"
end
# 左下がり斜め線7本
0.upto(6) do |i|
x1 = @margin + @s_h + @s * i
y1 = @margin
x2 = x1 - @s * 1.5
y2 = y1 + @s_r3h * 3
if i == 0
x2 += @s * 0.5 * 2
y2 -= @s_r3h * 2
elsif i == 6
x1 -= @s * 0.5
y1 += @s_r3h
end
@src += '<path d="M %f,%f L %f,%f" class="thickline"/>' % [x1, y1, x2, y2]
@src += "\n"
end
# のりしろに◎
if @mode == 0
0.upto(4) do |i|
x0 = @margin + @s_h + @s * i
y0 = @margin + @s_r3h * 2.0 / 3.0
@src += '<g transform="translate(%f,%f)">' % [x0, y0]
@src += "\n"
@src += '<circle cx="0" cy="0" r="6" class="thinline"/>' + "\n"
@src += '<circle cx="0" cy="0" r="9" class="thinline"/>' + "\n"
@src += '</g>'
@src += "\n"
end
0.upto(5) do |i|
x0 = @margin + @s + @s * i
y0 = @margin + @s_r3h * (2.0 + 1.0 / 3.0)
if i == 5
x0 -= @s_h
y0 -= @s_r3h
end
@src += '<g transform="translate(%f,%f)">' % [x0, y0]
@src += "\n"
@src += '<circle cx="0" cy="0" r="6" class="thinline"/>' + "\n"
@src += '<circle cx="0" cy="0" r="9" class="thinline"/>' + "\n"
@src += '</g>'
@src += "\n"
end
end
end
def draw_footer
# SVGおわり
@src += <<EOS
</svg>
EOS
end
def save
open(@filename_svg, "w") do |f_out|
f_out.print @src
end
end
def setup_surfaces
@surf = []
# 上段の5つ(逆三角形)
0.upto(4) do |i|
param = {}
param[:label] = i.to_s
param[:x] = @margin + @s * (i + 1)
param[:y] = @margin + @s_r3h / 3.0
param[:inv] = true
param[:side] = @s
@surf << IcosaDrawer::Triangle.new(param)
end
# 中段の5つ(逆三角形)
0.upto(4) do |i|
param = {}
param[:label] = (5 + i).to_s
param[:x] = @margin + @s_h + @s * i
param[:y] = @margin + @s_r3h + @s_r3h / 3.0
param[:inv] = true
param[:side] = @s
@surf << IcosaDrawer::Triangle.new(param)
end
# 中段の5つ(通常の向きの三角形)
0.upto(4) do |i|
param = {}
param[:label] = (10 + i).to_s
param[:x] = @margin + @s * (i + 1)
param[:y] = @margin + @s_r3h + @s_r3h * 2.0 / 3.0
param[:side] = @s
@surf << IcosaDrawer::Triangle.new(param)
end
# 下段の5つ(通常の向きの三角形)
0.upto(4) do |i|
param = {}
param[:label] = (15 + i).to_s
param[:x] = @margin + @s_h + @s * i
param[:y] = @margin + @s_r3h * 2 + @s_r3h * 2.0 / 3.0
param[:side] = @s
@surf << IcosaDrawer::Triangle.new(param)
end
# 正二十面体で同一になる辺の対応付け
case @mode
when 1
match_edge_mode1
when 2
match_edge_mode2
else
match_edge_mode0
end
end
def match_edge_mode0
# 正二十面体で同一になる辺の対応付け
# 同一の辺にはカッコ書きで同じ番号を記載
0.upto(4) do |i|
j = (i + 1) % 5
@surf[i].rot60 = @surf[5 + j].rot180 = "(#{i + 1})"
@surf[i].rot180 = @surf[j].rot300 = "(#{i + 6})"
@surf[5 + i].rot60 = @surf[10 + i].rot240 = "(#{i + 11})"
@surf[10 + i].rot120 = @surf[5 + j].rot300 = "(#{i + 16})"
@surf[10 + i].rot0 = @surf[15 + j].rot240 = "(#{i + 21})"
@surf[15 + i].rot120 = @surf[15 + j].rot0 = "(#{i + 26})"
end
end
def match_edge_mode1
# 正二十面体で同一になる辺の対応付け
# 同一の辺にはa×bとb×a
srand(9981)
kuku = (2..8).map {|p| ((p + 1)..9).map {|q| [p, q]}}.inject(:+)
kuku << [1, 8] << [1, 9]
kuku.shuffle!
puts "kuku = #{kuku.inspect}" if $DEBUG
0.upto(4) do |i|
j = (i + 1) % 5
@surf[i].rot60, @surf[5 + j].rot180 = kuku_forms(kuku[i])
@surf[i].rot180, @surf[j].rot300 = kuku_forms(kuku[i + 5])
@surf[5 + i].rot60, @surf[10 + i].rot240 = kuku_forms(kuku[i + 10])
@surf[10 + i].rot120, @surf[5 + j].rot300 = kuku_forms(kuku[i + 15])
@surf[10 + i].rot0, @surf[15 + j].rot240 = kuku_forms(kuku[i + 20])
@surf[15 + i].rot120, @surf[15 + j].rot0 = kuku_forms(kuku[i + 25])
end
end
def kuku_forms(param)
a = ["#{param[0]}&#215;#{param[1]}", "#{param[1]}&#215;#{param[0]}"]
if rand(2) == 1
a.reverse!
end
a
end
def match_edge_mode2
# 正二十面体で同一になる辺の対応付け
# 同一の辺には同じ積の九九
srand(9981)
kuku = (1..9).map {|p| (p == 1 ? [4, 6, 8, 9] : (1..9)).map {|q| [p, q]}}.inject(:+)
# kuku = (1..9).map {|p| (1..9).map {|q| [p, q]}}.inject(:+)
product = {}
puts "kuku = #{kuku.inspect}" if $DEBUG
kuku.each do |p, q|
r = p * q
s = "%d&#215;%d" % [p, q]
if product.key?(r)
product[r] << s
else
product[r] = [s]
end
end
kuku_a = []
product.each_key do |r|
a = product[r].dup
while a.length >= 2
a.shuffle!
s1 = a.pop
s2 = a.pop
kuku_a << [s1, s2]
end
end
kuku_a.shuffle!
kuku_a = kuku_a[0, 30]
puts "kuku_a = #{kuku_a.inspect}" if $DEBUG
0.upto(4) do |i|
j = (i + 1) % 5
@surf[i].rot60, @surf[5 + j].rot180 = kuku_a[i]
@surf[i].rot180, @surf[j].rot300 = kuku_a[i + 5]
@surf[5 + i].rot60, @surf[10 + i].rot240 = kuku_a[i + 10]
@surf[10 + i].rot120, @surf[5 + j].rot300 = kuku_a[i + 15]
@surf[10 + i].rot0, @surf[15 + j].rot240 = kuku_a[i + 20]
@surf[15 + i].rot120, @surf[15 + j].rot0 = kuku_a[i + 25]
end
end
def draw_surfaces
@surf.each_with_index do |t, i|
@src += t.svg_transform
@src += t.svg_center
if t.inverted
@src += t.svg_sidetext(180)
@src += t.svg_sidetext(300)
@src += t.svg_sidetext(60)
else
@src += t.svg_sidetext(0)
@src += t.svg_sidetext(120)
@src += t.svg_sidetext(240)
end
@src += t.svg_end_transform
end
end
end
end
if __FILE__ == $0
puts "mode 0"
IcosaDrawer::Drawer.new(svg: "icosa-test.svg", mode: 0).start
puts "mode 1"
IcosaDrawer::Drawer.new(svg: "icosa1.svg", mode: 1).start
puts "mode 2"
IcosaDrawer::Drawer.new(svg: "icosa2.svg", mode: 2).start
# IcosaDrawer::Drawer.new.start
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment