Skip to content

Instantly share code, notes, and snippets.

@takehiko
Created February 25, 2015 20:57
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/e127e6f536fd7c24765e to your computer and use it in GitHub Desktop.
Save takehiko/e127e6f536fd7c24765e to your computer and use it in GitHub Desktop.
三角形三角形問題の描画
#!/usr/bin/env ruby
# triangle-triangle.rb
# 三角形三角形問題の描画
# by takehikom
# 要ImageMagick (convertコマンド), フォント (IPAexゴシック)
# ruby triangle-triangle.rb
# env FONT=/full/path/of/ipaexg.ttf ruby triangle-triangle.rb
# ruby triangle-triangle.rb test
module TriangleTriangle
class Point
def initialize(x_ = 0.0, y_ = 0.0, label_ = nil)
@x = x_.to_f
@y = y_.to_f
@label = label_
end
attr_accessor :x, :y, :label
def to_s
s = ""
s += "[#{label}] " if @label
s += "(#{@x},#{@y})"
s
end
def internal_division(p2, r1, r2, label_ = nil)
# http://manapedia.jp/text/293
raise if (r1 + r2).zero?
p = Point.new(0, 0, label_)
p.x = (r2 * self.x + r1 * p2.x) / (r1 + r2)
p.y = (r2 * self.y + r1 * p2.y) / (r1 + r2)
p
end
def external_division(p2, r1, r2, label_ = nil)
# http://manapedia.jp/text/293?page=2
raise if (r1 - r2).zero?
p = Point.new(0, 0, label_)
p.x = (-r2 * self.x + r1 * p2.x) / (r1 - r2)
p.y = (-r2 * self.y + r1 * p2.y) / (r1 - r2)
p
end
end
class Plane
def self.internal_division(p1, p2, r1, r2, label_ = nil)
p1.internal_division(p2, r1, r2, label_)
end
def self.external_division(p1, p2, r1, r2, label_ = nil)
p1.external_division(p2, r1, r2, label_)
end
def length(p1, p2)
d_x = p1.x - p2.x
d_y = p1.y - p2.y
Math.sqrt(d_x * d_x + d_y * d_y)
end
def self.intersection(p1, p2, q1, q2, label_ = nil)
a = p2.y - p1.y # (p1, p2) => a * x + b * y = c
b = p1.x - p2.x
c = p1.x * p2.y - p2.x * p1.y
d = q2.y - q1.y # (p1, p2) => d * x + e * y = f
e = q1.x - q2.x
f = q1.x * q2.y - q2.x * q1.y
puts "l1: #{a} x + #{b}y = #{c}" if $debug
puts "l2: #{d} x + #{e}y = #{f}" if $debug
raise if (a.zero? && b.zero?) || (d.zero? && e.zero?)
det = a * e - b * d
raise if det.zero?
x = (c * e - b * f) / det
y = (a * f - c * d) / det
Point.new(x, y, label_)
end
def self.area(p1, p2, p3)
# http://www004.upp.so-net.ne.jp/s_honma/heron/heron.htm
p4 = Point.new(p1.x - p3.x, p1.y - p3.y)
p5 = Point.new(p2.x - p3.x, p2.y - p3.y)
(p4.x * p5.y - p4.y * p5.x).abs * 0.5
end
def self.transform(m, *points)
points.map do |p|
Point.new(m[0] * p.x + m[1] * p.y + m[2],
m[3] * p.x + m[4] * p.y + m[5],
p.label)
end
end
end
class Drawer
def initialize(tt_, param = Hash.new)
@tt_orig = tt_
@m = param[:matrix] || [1, 0, 0, 0, 1, 0]
@margin = param[:margin] || 30
@linewidth = param[:linewidth] || 2
@extra = param[:extra] || ""
@fontname = param[:fontname] || ENV["FONT"] || "ipaexg.ttf"
@fontsize = param[:fontsize] || 24
@filename = param[:filename] || "triangle-triangle.png"
if param[:bestfit]
@tt = @tt_orig.transform2(param[:width] || 300,
param[:height] || 200,
param[:margin] || 30,
param[:flap].nil? ? true : param[:flap])
else
@tt = @tt_orig.transform(@m)
end
@command = ""
end
attr_reader :tt, :tt_orig, :margin, :linewidth, :filename
attr_reader :fontname, :fontsize, :extra
attr_reader :command
def to_s; @command; end
def set_command
width = ([@tt.a.x, @tt.b.x, @tt.c.x].max + @margin).to_i
height = ([@tt.a.y, @tt.b.y, @tt.c.y].max + @margin).to_i
@command = "convert -size #{width}x#{height} xc:white"
add_auxline2 if @extra.index("line2")
if @extra.index("line1")
if @extra.index("line1") == @extra.index("line1ae")
add_auxline1ae
elsif @extra.index("line1+ae")
add_auxline1
add_auxline1ae
else
add_auxline1
end
end
add_label_abcdef if @extra.index("abcdef")
add_label_ghi if @extra.index("ghi")
add_s213 if @extra.index("s213")
if @extra.index("sabc")
if @extra.index("sabcabc")
add_sabcabc
else
add_sabc
end
end
add_ratio if @extra.index("ratio")
add_triangle_triangle
@command += " -quality 95 #{@filename}"
self
end
alias :start :set_command
def insert_command(c)
@command[" -quality"] = c + " -quality"
end
def add_triangle_triangle
@command += " -stroke black -strokewidth #{@linewidth} -fill none"
@command += " -draw \""
@command += "line #{@tt.a.x},#{@tt.a.y},#{@tt.b.x},#{@tt.b.y}"
@command += " line #{@tt.b.x},#{@tt.b.y},#{@tt.c.x},#{@tt.c.y}"
@command += " line #{@tt.c.x},#{@tt.c.y},#{@tt.a.x},#{@tt.a.y}"
@command += " line #{@tt.a.x},#{@tt.a.y},#{@tt.f.x},#{@tt.f.y}"
@command += " line #{@tt.b.x},#{@tt.b.y},#{@tt.d.x},#{@tt.d.y}"
@command += " line #{@tt.c.x},#{@tt.c.y},#{@tt.e.x},#{@tt.e.y}"
@command += "\""
end
def add_auxline1
@command += " -stroke blue -strokewidth #{@linewidth} -fill none"
@command += " -draw \""
@command += "line #{@tt.a.x},#{@tt.a.y},#{@tt.e.x},#{@tt.e.y}"
@command += " line #{@tt.b.x},#{@tt.b.y},#{@tt.f.x},#{@tt.f.y}"
@command += " line #{@tt.c.x},#{@tt.c.y},#{@tt.d.x},#{@tt.d.y}"
@command += "\""
end
def add_auxline2
@command += " -stroke maroon -strokewidth #{@linewidth} -fill none"
@command += " -draw \""
@command += "line #{@tt.f.x},#{@tt.f.y},#{@tt.g.x},#{@tt.g.y}"
@command += " line #{@tt.d.x},#{@tt.d.y},#{@tt.h.x},#{@tt.h.y}"
@command += " line #{@tt.e.x},#{@tt.e.y},#{@tt.i.x},#{@tt.i.y}"
@command += "\""
end
def add_auxline1ae
@command += " -stroke black -strokewidth #{@linewidth} -fill none"
@command += " -draw \""
@command += "line #{@tt.a.x},#{@tt.a.y},#{@tt.e.x},#{@tt.e.y}"
@command += "\""
end
# 以下のuの第2・第3引数の値は,デフォルト設定(IPAexゴシック, 24pt)
# に依存しているため,他のフォントやサイズにすると適切に
# 表示しない可能性あり
def add_label_abcdef
@command += " -font #{@fontname} -pointsize #{@fontsize} -fill black -stroke none"
@command += " -draw \""
@command += "text #{u('a', -18, +1)} \'A\'"
@command += " text #{u('b', -17, -1)} \'B\'"
@command += " text #{u('c', 1, -1)} \'C\'"
@command += " text #{u('d', 4, +4)} \'D\'"
@command += " text #{u('e', -11, -4)} \'E\'"
@command += " text #{u('f', -8, +24)} \'F\'"
@command += "\""
end
def add_label_ghi
@command += " -font #{@fontname} -pointsize #{@fontsize} -fill black -stroke none"
@command += " -draw \""
@command += "text #{u('g', -9, +22)} \'G\'"
@command += " text #{u('h', 3, +1)} \'H\'"
@command += " text #{u('i', -12, -1)} \'I\'"
@command += "\""
# tt3.insert_command(" -font #{font} -pointsize 24 -fill black -stroke none -draw \"text #{tt3.u('g', -9, +22)} \'G\' text #{tt3.u('h', 3, +1)} \'H\' text #{tt3.u('i', -12, -1)} \'I\'\"")
end
def add_s213
@command += " -font #{@fontname} -pointsize #{@fontsize * 0.75} -fill green4 -stroke none"
@command += " -draw \""
@command += "text #{u('def', -5, +10)} \'s\'"
@command += " text #{u('ade', -8, +10)} \'2s\'"
@command += " text #{u('bef', -8, +5)} \'s\'"
@command += " text #{u('cfd', -8, +10)} \'3s\'"
@command += " text #{u('eab', -6, +6)} \'2s\'"
@command += " text #{u('fbc', -5, +6)} \'3s\'"
@command += " text #{u('dca', -16, +8)} \'6s\'"
@command += "\""
end
def add_sabc
@command += " -font #{@fontname} -pointsize #{@fontsize * 0.75} -fill green4 -stroke none"
@command += " -draw \""
@command += "text #{u('def', -5, +10)} \'s\'"
@command += " text #{u('ade', -8, +10)} \'as\'"
@command += " text #{u('bef', -8, +5)} \'bs\'"
@command += " text #{u('cfd', -8, +10)} \'cs\'"
@command += "\""
end
def add_sabcabc
@command += " -font #{@fontname} -pointsize #{@fontsize * 0.75} -fill green4 -stroke none"
@command += " -draw \""
@command += "text #{u('def', -5, +10)} \'s\'"
@command += " text #{u('ade', -8, +10)} \'as\'"
@command += " text #{u('bef', -8, +5)} \'bs\'"
@command += " text #{u('cfd', -8, +10)} \'cs\'"
@command += " text #{u('eab', -15, +13)} \'abs\'"
@command += " text #{u('fbc', -5, +6)} \'bcs\'"
@command += " text #{u('dca', -16, +8)} \'cas\'"
@command += "\""
end
def add_ratio
@command += " -font #{@fontname} -pointsize #{@fontsize * 0.75} -stroke none"
@command += " -fill chocolate"
@command += " -draw \"text #{u('ai', -33, +0)} \'a+1\' text #{u('bi', -12, -1)} \'b\'\""
@command += " -fill goldenrod"
@command += " -draw \"text #{u('bg', -14, +20)} \'b+1\' text #{u('cg', -8, +15)} \'c\'\""
@command += " -fill IndianRed"
@command += " -draw \"text #{u('ch', -2, -5)} \'c+1\' text #{u('ah', +5, +2)} \'a\'\""
end
def u(points, del_x = 0, del_y = 0, flag_array = false)
# pointsは1つ以上の点の文字列
# 1点ならその座標,2点なら中点,3点以上なら重心について
# del_x,del_yの補正を加え,convertコマンドの座標となる
# 文字列を返す
x = y = 0
points.downcase.each_char do |c|
x += @tt.send(c.to_sym).x
y += @tt.send(c.to_sym).y
end
x = x / points.length + del_x
y = y / points.length + del_y
flag_array ? [x, y] : "#{x},#{y}"
end
alias :point :u
alias :center :u
def exec_command(opt_print = true)
puts @command if opt_print
system @command
end
end
class Calculator
def initialize(h = Hash.new)
@a = h["A"]
@b = h["B"]
@c = h["C"]
@ad_df = h["AD_DF"].to_f
@be_ed = h["BE_ED"].to_f
@cf_fe = h["CF_FE"].to_f
puts "AD:DF=#{@ad_df}:1", "BE:ED=#{@be_ed}:1", "CF:FE=#{@cf_fe}:1"
raise if @ad_df.zero? || @be_ed.zero? || @cf_fe.zero?
raise if @ad_df.nan? || @be_ed.nan? || @cf_fe.nan?
end
attr_accessor :a, :b, :c, :ad_df, :be_ed, :cf_fe
attr_reader :d, :e, :f, :g, :h, :i
def print_result
puts @a, @b, @c
puts "AD:DF=#{@ad_df}:1", "BE:ED=#{@be_ed}:1", "CF:FE=#{@cf_fe}:1"
puts @d, @e, @f
puts @g, @h, @i
d_ext = Plane.external_division(@b, @e, @be_ed + 1, 1, "Dext")
e_ext = Plane.external_division(@c, @f, @cf_fe + 1, 1, "Eext")
f_ext = Plane.external_division(@a, @d, @ad_df + 1, 1, "Fext")
puts d_ext, e_ext, f_ext
area_whole = Plane.area(@a, @b, @c)
area_min = Plane.area(@d, @e, @f)
puts "ABC = #{area_whole}", "DEF = #{area_min}", "ABC/DEF = #{area_whole / area_min}"
end
def transform(m = nil)
tt = self.dup
return tt if m.nil?
tt.a, tt.b, tt.c = Plane.transform(m, @a, @b, @c)
tt.start
end
def transform2(width, height, margin, flag_flip)
# (0,0)-(width+margin*2, height+margin*2)に描画できるよう変換
left = [@a.x, @b.x, @c.x].min
right = [@a.x, @b.x, @c.x].max
top = [@a.y, @b.y, @c.y].max
bottom = [@a.y, @b.y, @c.y].min
raise if (right - left).zero? || (bottom - top).zero?
a2, b2, c2 = Plane.transform([1, 0, -left, 0, 1, flag_flip ? -top : -bottom], @a, @b, @c)
a3, b3, c3 = Plane.transform([width / (right - left), 0, 0, 0, height / (top - bottom) * (flag_flip ? -1 : 1), 0], a2, b2, c2)
a4, b4, c4 = Plane.transform([1, 0, margin, 0, 1, margin], a3, b3, c3)
tt = self.dup
tt.a, tt.b, tt.c = a4, b4, c4
tt.start
end
def find_point
@g = Plane.internal_division(@b, @c, @be_ed + 1, @cf_fe, "G")
@h = Plane.internal_division(@c, @a, @cf_fe + 1, @ad_df, "H")
@i = Plane.internal_division(@a, @b, @ad_df + 1, @be_ed, "I")
@d = Plane.intersection(@a, @g, @b, @h, "D")
@e = Plane.intersection(@b, @h, @c, @i, "E")
@f = Plane.intersection(@c, @i, @a, @g, "F")
self
end
alias :start :find_point
end
end
if __FILE__ == $0
if ARGV.first == "test"
include TriangleTriangle
puts "internal division"
p1 = Point.new(0, 0)
p2 = Point.new(3, 0.3)
r1 = 1.0
r2 = 2.0
d = Plane.internal_division(p1, p2, r1, r2)
puts "#{p1} #{r1}:#{r2} #{p2} => #{d}"
puts "external division"
p1 = Point.new(0, 0)
p2 = Point.new(3, 0.6)
r1 = 3.0
r2 = 1.0
d = Plane.external_division(p1, p2, r1, r2)
puts "#{p1} #{r1}:#{r2} #{p2} => #{d}"
puts "intersection"
p1 = Point.new(0, 0)
p2 = Point.new(5, 3)
q1 = Point.new(0, 2)
q2 = Point.new(2, 0)
c = Plane.intersection(p1, p2, q1, q2)
puts "#{p1}-#{p2}, #{q1}-#{q2} => #{c}"
exit
end
include TriangleTriangle
h = {
"A" => Point.new(2.3, 4, "A"),
"B" => Point.new(0, 0, "B"),
"C" => Point.new(5, 0, "C"),
"AD_DF" => 2.0,
"BE_ED" => 1.0,
"CF_FE" => 3.0
}
tt1 = Calculator.new(h).start
tt1.print_result
d1 = Drawer.new(tt1, :bestfit => true, :filename => "tritri1.png",
:extra => "abcdef").start
d1.tt.print_result
d1.exec_command
d2 = Drawer.new(tt1, :bestfit => true, :filename => "tritri2.png",
:extra => "abcdef line1 s213").start
d2.tt.print_result
d2.exec_command
d3 = Drawer.new(tt1, :bestfit => true, :filename => "tritri3.png",
:extra => "abcdef line1").start
d3.tt.print_result
d3.exec_command
d4 = Drawer.new(tt1, :bestfit => true, :filename => "tritri4.png",
:extra => "abcdef line1 sabc").start
d4.tt.print_result
d4.exec_command
d5 = Drawer.new(tt1, :bestfit => true, :filename => "tritri5.png",
:extra => "abcdef line1 sabcabc").start
d5.tt.print_result
d5.exec_command
d6 = Drawer.new(tt1, :bestfit => true, :filename => "tritri6.png",
:extra => "abcdef ghi line1 line2").start
d6.tt.print_result
d6.exec_command
d7 = Drawer.new(tt1, :bestfit => true, :filename => "tritri7.png",
:extra => "abcdef ghi line1 line2 sabc ratio").start
d7.tt.print_result
d7.exec_command
d8 = Drawer.new(tt1, :bestfit => true, :filename => "tritri1ae.png",
:extra => "abcdef line1ae").start
d8.tt.print_result
d8.exec_command
d9 = Drawer.new(tt1, :bestfit => true, :filename => "tritri2ae.png",
:extra => "abcdef line1 s213 line1+ae").start
d9.tt.print_result
d9.exec_command
d = Drawer.new(tt1, :bestfit => true, :filename => "tritri_test.png",
:extra => "abcdef ghi line1 line2 sabcabc ratio").start
d.tt.print_result
d.exec_command
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment