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
|
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 in_unit_sphere?
mag_sqr < 1
end
def to_s
"{#{@x}, #{@y}, #{@z}}"
end
def self.random(min = 0, max = 1)
interval = Interval.new(min.to_f, max.to_f)
Vec3.new(interval.sample, interval.sample, interval.sample)
end
def self.random_unit(normal = nil)
p = nil
while p.nil?
c = Vec3.random
p = c.unit if c.in_unit_sphere?
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
|