aboutsummaryrefslogtreecommitdiff
path: root/lib/vec3.rb
blob: 46d8ee10d25743c9c9d1c337a26161db5f7f14eb (plain)
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
class Vec3
  def initialize(x = 0.0, y = 0.0, z = 0.0)
    @x = x
    @y = y
    @z = z
  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 reflect(normal)
    self - normal * dot(normal) * 2
  end

  def refract(normal, etaratio)
    costheta = [(-self).dot(normal), 1.0].min
    rout_perp = (self + normal * costheta) * etaratio
    rout_parr = normal * -((1.0 - rout_perp.mag_sqr).abs ** 0.5)
    rout_perp + rout_parr
  end

  def in_unit?
    mag_sqr < 1
  end

  def near_zero?(s = 1e-8)
    @x.abs < s && @y.abs < s && @z.abs < s
  end

  def to_s
    "{#{@x}, #{@y}, #{@z}}"
  end

  def self.random(min: -1.0, max: 1.0, dimensions: 3)
    interval = Interval.new(min, max)
    self.new(
      dimensions >= 1 ? interval.sample : 0,
      dimensions >= 2 ? interval.sample : 0,
      dimensions >= 3 ? interval.sample : 0
    )
  end

  def self.random_in_unit(normal: nil, dimensions: 3)
    p = nil
    while p.nil?
      c = self.random(dimensions: dimensions)
      p = c.unit if c.in_unit?
    end
    normal.nil? || p.dot(normal) > 0 ? p : -p
  end
end

class Point < Vec3; end

class Colour < Vec3
  def r; @x; end
  def g; @y; end
  def b; @z; end

  def gamma(gma = 2)
    Colour.new(
      r > 0 ? r ** (1.0 / gma) : r,
      g > 0 ? g ** (1.0 / gma) : g,
      b > 0 ? b ** (1.0 / gma) : b
    )
  end

  def clamp(interval)
    Colour.new(
      interval.clamp(r),
      interval.clamp(g),
      interval.clamp(b)
    )
  end

  def to_ppm
    g_space = gamma.clamp(Interval.new(0.0, 0.999))
    "%3d %3d %3d" % [
      (g_space.r * 256).to_i,
      (g_space.g * 256).to_i,
      (g_space.b * 256).to_i
    ]
  end
end