From df7c4a808d691e1d927047bb86aa9738780e694d Mon Sep 17 00:00:00 2001 From: Nat Lasseter Date: Wed, 12 Jun 2024 17:37:40 +0100 Subject: Initial Commit (Up to Chapter 7) --- .gitignore | 2 ++ Makefile | 10 +++++++ Readme | 6 +++++ lib/hittable.rb | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/interval.rb | 20 ++++++++++++++ lib/ray.rb | 12 +++++++++ lib/vec3.rb | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++ rtiaw | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 284 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 Readme create mode 100644 lib/hittable.rb create mode 100644 lib/interval.rb create mode 100644 lib/ray.rb create mode 100644 lib/vec3.rb create mode 100755 rtiaw diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..470cb69 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.swp +out.ppm diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3536afe --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +out.ppm: rtiaw $(wildcard lib/*.rb) + ./$< > $@ + +.phony: view clean + +view: out.ppm + eog out.ppm & + +clean: + rm -f out.ppm diff --git a/Readme b/Readme new file mode 100644 index 0000000..28343de --- /dev/null +++ b/Readme @@ -0,0 +1,6 @@ +Ray Tracing in a Weekend in Ruby. + +I've made some decisions when porting it. +Disagree with me if you like. That sounds like your problem. + +https://raytracing.github.io/books/RayTracingInOneWeekend.html diff --git a/lib/hittable.rb b/lib/hittable.rb new file mode 100644 index 0000000..80254d1 --- /dev/null +++ b/lib/hittable.rb @@ -0,0 +1,81 @@ +class HitRecord + def initialize(point, t, ray, out_normal) + @point = point + @t = t + set_face_normal(ray, out_normal) + end + + attr_accessor :point, :normal, :t + + def set_face_normal(ray, out_normal) + @front_face = ray.direction.dot(out_normal) < 0 + @normal = @front_face ? out_normal : -out_normal + end +end + +class Hittable + def hit(ray, trange) + nil + end +end + +class Hittables < Hittable + def initialize + clear + end + + def clear + @objects = [] + end + + def <<(object) + @objects << object + end + + def hit(ray, trange) + rec = nil + closest = trange.max + + @objects.each do |object| + if trec = object.hit(ray, Interval.new(trange.min, closest)) + rec = trec + closest = trec.t + end + end + + rec + end +end + +class Sphere < Hittable + def initialize(ox, oy, oz, radius = 1) + @centre = Point.new(ox, oy, oz) + @radius = radius + end + + attr_reader :centre, :radius + + def hit(ray, trange) + oc = @centre - ray.origin + a = ray.direction.mag_sqr + h = ray.direction.dot(oc) + c = oc.mag_sqr - @radius ** 2 + disc = h ** 2 - a * c + + return nil if disc < 0 + + sqrtd = disc ** 0.5 + root = (h - sqrtd) / a + if !trange.surround?(root) + root = (h + sqrtd) / a + if !trange.surround?(root) + return nil + end + end + + t = root + p = ray.at(t) + o_n = (p - @centre) / @radius + HitRecord.new(p, t, ray, o_n) + end +end diff --git a/lib/interval.rb b/lib/interval.rb new file mode 100644 index 0000000..0aa787f --- /dev/null +++ b/lib/interval.rb @@ -0,0 +1,20 @@ +class Interval + def initialize(min = Float::INFINITY, max = -Float::INFINITY) + @min = min + @max = max + end + + attr_reader :min, :max + + def size + @max - @min + end + + def include?(x) + min <= x && x <= max + end + + def surround?(x) + min < x && x < max + end +end diff --git a/lib/ray.rb b/lib/ray.rb new file mode 100644 index 0000000..4d282cb --- /dev/null +++ b/lib/ray.rb @@ -0,0 +1,12 @@ +class Ray + def initialize(origin = Point.new, direction = Vec3.new) + @origin = origin + @direction = direction + end + + attr_reader :origin, :direction + + def at(time) + @origin + (@direction * time) + end +end diff --git a/lib/vec3.rb b/lib/vec3.rb new file mode 100644 index 0000000..6287813 --- /dev/null +++ b/lib/vec3.rb @@ -0,0 +1,74 @@ +class Vec3 + def initialize(x = 0, y = 0, z = 0) + @x = x.to_f + @y = y.to_f + @z = z.to_f + end + + attr_reader :x, :y, :z + + def +(oth) + self.class.new(@x + oth.x, @y + oth.y, @z + oth.z) + end + + def -@ + self.class.new(-@x, -@y, -@z) + end + + def -(oth) + self + -oth + end + + def *(oth) + if oth.is_a?(Vec3) + self.class.new(@x * oth.x, @y * oth.y, @z * oth.z) + else + self.class.new(@x * oth, @y * oth, @z * oth) + end + end + + def /(oth) + self * (1.0 / oth) + end + + def dot(oth) + v = self * oth + v.x + v.y + v.z + end + + def cross(oth) + self.class.new( + @y * oth.z - @z * oth.y, + @z * oth.x - @x * oth.z, + @x * oth.y - @y * oth.x + ) + end + + def mag_sqr + @x ** 2 + @y ** 2 + @z ** 2 + end + + def mag + mag_sqr ** 0.5 + end + + def unit + self / mag + end + + def to_s + "{#{@x}, #{@y}, #{@z}}" + end +end + +class Point < Vec3; end + +class Colour < Vec3 + def r; @x; end + def g; @y; end + def b; @z; end + + def to_ppm + "%3d %3d %3d" % [(r * 255.999).to_i, (g * 255.999).to_i, (b * 255.999).to_i] + end +end diff --git a/rtiaw b/rtiaw new file mode 100755 index 0000000..5fb7e5a --- /dev/null +++ b/rtiaw @@ -0,0 +1,79 @@ +#!/usr/bin/env ruby + +start = Time.now.to_f + +$LOAD_PATH.unshift File.dirname(__FILE__) + '/lib' +require 'interval' +require 'vec3' +require 'ray' +require 'hittable' + +def ray_colour(ray, world) + if rec = world.hit(ray, Interval.new(0, Float::INFINITY)) + return (Colour.new(1,1,1) + rec.normal) * 0.5 + end + + unit_dir = ray.direction.unit + a = (unit_dir.y + 1) / 2 + Colour.new(1.0, 1.0, 1.0) * (1-a) + Colour.new(0.5, 0.7, 1.0) * a +end + +# Define image +width = 400 +aspect = 16.0 / 9 +max = 255 + +# Calculate height +height = (width / aspect).to_i +height = height < 1 ? 1 : height + +# World +world = Hittables.new +world << Sphere.new(0,0,-1, 0.5) +world << Sphere.new(0,-100.5,-1, 100) + +# Camera +c_focallength = 1.0 +c_centre = Point.new(0,0,0) + +# Viewport +v_height = 2.0 +v_width = v_height * width.to_f / height +v_u = Vec3.new(v_width, 0, 0) +v_v = Vec3.new(0, -v_height, 0) + +# Pixel deltas +pd_u = v_u / width +pd_v = v_v / height + +# Upper left pixel +v_upperleft = c_centre - + Vec3.new(0, 0, c_focallength) - + v_u / 2 - + v_v / 2 +p_00loc = v_upperleft + + (pd_u + pd_v) / 2 + +puts <<-EOH +P3 +#{width} #{height} +#{max} +EOH + +height.times do |row| + $stderr.print "Scanlines remaining: %4d" % (height - row) + width.times do |col| + p_centre = p_00loc + (pd_u * col) + (pd_v * row) + ray_dir = p_centre - c_centre + ray = Ray.new(c_centre, ray_dir) + + p_colour = ray_colour(ray, world) + puts p_colour.to_ppm + end + puts +end + +finish = Time.now.to_f +took = finish - start + +$stderr.puts "Done. Took %4.2f seconds. " % took -- cgit v1.2.1