Vec.new(@x - other.x, @y - other.y, @z - other.z)
@x*other.x + @y*other.y + @z*other.z
ox, oy, oz = other.x, other.y, other.z
Vec.new(@y*oz-@z*oy, @z*ox-@x*oz, @x*oy-@y*ox)
r = Math.sqrt(self.dot(self))
Vec.new(@x / r, @y / r, @z / r)
@data = (0..h-2).map { [0] * w }
@scale = [w / 2.0, h-2].min
@center = Complex(w / 2, h-2)
def line((x1, y1), (x2, y2))
p1 = Complex(x1, y1) / 2 * @scale + @center
p2 = Complex(x2, y2) / 2 * @scale + @center
private def line0(p1, p2)
@data[y / 2][x] |= (y % 2 > 1 ? 2 : 1)
@data.each {|row| row.fill(0) }
@data.map {|row| row.map {|n| " ',;"[n] }.join }.join("\n")
cap_vertices = (0..5).map {|i| Vec.new(*Complex.polar(1, i * Math::PI / 3).rect, 1) }
middle_vertices = (0..5).map {|i| Vec.new(*Complex.polar(2, (i + 0.5) * Math::PI / 3).rect, 0) }
bottom_vertex = Vec.new(0, 0, -2)
faces << [cap_vertices[i], middle_vertices[i], cap_vertices[j]]
faces << [cap_vertices[j], middle_vertices[i], middle_vertices[j]]
faces << [middle_vertices[i], bottom_vertex, middle_vertices[j]]
dir = Vec.new(*Complex.polar(1, angle).rect, Math.sin(angle)).normalize
dir2 = Vec.new(*Complex.polar(1, angle - Math::PI/2).rect, 0)
@faces.each do |vertices|
if v1.sub(v0).cross(v2.sub(v0)).dot(dir) > 0
points = vertices.map {|p| [nm.dot(p), up.dot(p)] }
(points + [points[0]]).each_cons(2) do |p1, p2|
private def easter_egg(type = nil)
type ||= [:logo, :dancing].sample
File.open(File.join(__dir__, 'ruby_logo.aa')) do |f|
RDoc::RI::Driver.new.page do |io|
canvas = Canvas.new(Reline.get_screen_size)
Reline::IOGate.set_winch_handler do
canvas = Canvas.new(Reline.get_screen_size)
ruby_model = RubyModel.new
0.step do |i| # TODO (0..).each needs Ruby 2.6 or later
ruby_model.render_frame(i) do |p1, p2|
buff[0, 20] = "\e[0mPress Ctrl+C to stop\e[31m\e[1m"
IRB.send(:easter_egg, ARGV[0]&.to_sym) if $0 == __FILE__