# Ported from Rust from https://gist.github.com/joshmarinacci/c84d0979e100d107f685 record Vector, x : Float64, y : Float64, z : Float64 do def scale(s) Vector.new(x * s, y * s, z * s) end def +(other) Vector.new(x + other.x, y + other.y, z + other.z) end def -(other) Vector.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 magnitude Math.sqrt self.dot(self) end def normalize scale(1.0 / magnitude) end end record Ray, orig : Vector, dir : Vector record Color, r : Float64, g : Float64, b : Float64 do def scale(s) Color.new(r * s, g * s, b * s) end def +(other) Color.new(r + other.r, g + other.g, b + other.b) end end record Sphere, center : Vector, radius : Float64, color : Color do def get_normal(pt) (pt - center).normalize end end record Light, position : Vector, color : Color record Hit, obj : Sphere, value : Float64 WHITE = Color.new(1.0, 1.0, 1.0) RED = Color.new(1.0, 0.0, 0.0) GREEN = Color.new(0.0, 1.0, 0.0) BLUE = Color.new(0.0, 0.0, 1.0) LIGHT1 = Light.new(Vector.new(0.7, -1.0, 1.7), WHITE) def shade_pixel(ray, obj, tval) pi = ray.orig + ray.dir.scale(tval) color = diffuse_shading pi, obj, LIGHT1 col = (color.r + color.g + color.b) / 3.0 (col * 6.0).to_i end def intersect_sphere(ray, center, radius) l = center - ray.orig tca = l.dot(ray.dir) if tca < 0.0 return nil end d2 = l.dot(l) - tca*tca r2 = radius*radius if d2 > r2 return nil end thc = Math.sqrt(r2 - d2) t0 = tca - thc # t1 = tca + thc if t0 > 10_000 return nil end t0 end def clamp(x, a, b) return a if x < a return b if x > b x end def diffuse_shading(pi, obj, light) n = obj.get_normal(pi) lam1 = (light.position - pi).normalize.dot(n) lam2 = clamp lam1, 0.0, 1.0 light.color.scale(lam2*0.5) + obj.color.scale(0.3) end puts "Hello, worlds!" lut = %w(. - + * X M) w = 20 * 4 h = 10 * 4 scene = [ Sphere.new(Vector.new(-1.0, 0.0, 3.0), 0.3, RED), Sphere.new(Vector.new(0.0, 0.0, 3.0), 0.8, GREEN), Sphere.new(Vector.new(1.0, 0.0, 3.0), 0.4, BLUE), ] (0...h).each do |j| puts "--" (0...w).each do |i| fw, fi, fj, fh = w.to_f, i.to_f, j.to_f, h.to_f ray = Ray.new( Vector.new(0.0, 0.0, 0.0), Vector.new((fi - fw/2.0)/fw, (fj - fh/2.0)/fh, 1.0).normalize ) hit = nil scene.each do |obj| ret = intersect_sphere(ray, obj.center, obj.radius) if ret hit = Hit.new obj, ret end end if hit pixel = lut[shade_pixel(ray, hit.obj, hit.value)] else pixel = ' ' end print pixel end end puts "we are done!"