aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorNat Lasseter <user@4574.co.uk>2024-06-12 17:37:40 +0100
committerNat Lasseter <user@4574.co.uk>2024-06-12 17:37:40 +0100
commitdf7c4a808d691e1d927047bb86aa9738780e694d (patch)
treeda197b87ebf2c48fe27b9aabf2db1fff5786c7f4 /lib
Initial Commit (Up to Chapter 7)
Diffstat (limited to 'lib')
-rw-r--r--lib/hittable.rb81
-rw-r--r--lib/interval.rb20
-rw-r--r--lib/ray.rb12
-rw-r--r--lib/vec3.rb74
4 files changed, 187 insertions, 0 deletions
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