1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
|
require "reline"
module IRB
class << self
class Vec
def initialize(x, y, z)
@x, @y, @z = x, y, z
end
attr_reader :x, :y, :z
def sub(other)
Vec.new(@x - other.x, @y - other.y, @z - other.z)
end
def dot(other)
@x*other.x + @y*other.y + @z*other.z
end
def cross(other)
ox, oy, oz = other.x, other.y, other.z
Vec.new(@y*oz-@z*oy, @z*ox-@x*oz, @x*oy-@y*ox)
end
def normalize
r = Math.sqrt(self.dot(self))
Vec.new(@x / r, @y / r, @z / r)
end
end
class Canvas
def initialize((h, w))
@data = (0..h-2).map { [0] * w }
@scale = [w / 2.0, h-2].min
@center = Complex(w / 2, h-2)
end
def line((x1, y1), (x2, y2))
p1 = Complex(x1, y1) / 2 * @scale + @center
p2 = Complex(x2, y2) / 2 * @scale + @center
line0(p1, p2)
end
private def line0(p1, p2)
mid = (p1 + p2) / 2
if (p1 - p2).abs < 1
x, y = mid.rect
@data[y / 2][x] |= (y % 2 > 1 ? 2 : 1)
else
line0(p1, mid)
line0(p2, mid)
end
end
def draw
@data.each {|row| row.fill(0) }
yield
@data.map {|row| row.map {|n| " ',;"[n] }.join }.join("\n")
end
end
class RubyModel
def initialize
@faces = init_ruby_model
end
def init_ruby_model
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]
6.times do |j|
i = j-1
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]]
end
faces
end
def render_frame(i)
angle = i / 10.0
dir = Vec.new(*Complex.polar(1, angle).rect, Math.sin(angle)).normalize
dir2 = Vec.new(*Complex.polar(1, angle - Math::PI/2).rect, 0)
up = dir.cross(dir2)
nm = dir.cross(up)
@faces.each do |vertices|
v0, v1, v2, = 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|
yield p1, p2
end
end
end
end
end
private def easter_egg(type = nil)
type ||= [:logo, :dancing].sample
case type
when :logo
File.open(File.join(__dir__, 'ruby_logo.aa')) do |f|
require "rdoc"
RDoc::RI::Driver.new.page do |io|
IO.copy_stream(f, io)
end
end
when :dancing
begin
canvas = Canvas.new(Reline.get_screen_size)
Reline::IOGate.set_winch_handler do
canvas = Canvas.new(Reline.get_screen_size)
end
ruby_model = RubyModel.new
print "\e[?1049h"
0.step do |i| # TODO (0..).each needs Ruby 2.6 or later
buff = canvas.draw do
ruby_model.render_frame(i) do |p1, p2|
canvas.line(p1, p2)
end
end
buff[0, 20] = "\e[0mPress Ctrl+C to stop\e[31m\e[1m"
print "\e[H" + buff
sleep 0.05
end
ensure
print "\e[0m\e[?1049l"
end
end
end
end
end
IRB.send(:easter_egg, ARGV[0]&.to_sym) if $0 == __FILE__
|