rray 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/rray/aabb.rb +78 -0
- data/lib/rray/camera.rb +14 -2
- data/lib/rray/hit.rb +4 -4
- data/lib/rray/interval.rb +23 -1
- data/lib/rray/material/base.rb +12 -0
- data/lib/rray/material/dielectric.rb +9 -2
- data/lib/rray/material/lambertian.rb +8 -1
- data/lib/rray/material/metal.rb +9 -1
- data/lib/rray/object/base.rb +16 -0
- data/lib/rray/object/bvh.rb +54 -0
- data/lib/rray/object/group.rb +21 -9
- data/lib/rray/object/moving_sphere.rb +43 -0
- data/lib/rray/object/sphere.rb +23 -5
- data/lib/rray/ray.rb +4 -3
- data/lib/rray/scene.rb +44 -6
- data/lib/rray/tracer.rb +1 -1
- data/lib/rray/util.rb +0 -1
- data/lib/rray/vec3.rb +21 -6
- data/lib/rray/version.rb +1 -1
- data/lib/rray.rb +3 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1f6b0d451c5f0098de3d1aaf967766038c58b622dab31a26b3d4400a28c72bd
|
4
|
+
data.tar.gz: 97a44a8efa2be4a28e81b6dcf10de54646bb034787326787e3d6925fe79afab8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50d401573d8e62609cd940451580269fe0f153d086217153f9a3ca9ec571e1ddb0c28a456adb0ae9c55661b67c5733dab4e4b81817290b950618ca475c4d9ed9
|
7
|
+
data.tar.gz: 0d46651fc158a7cef699a2b4371a0a5892df107c1f0f5c8a93778b7f03fd76a63706a175d4775a6ebc28b55ad428ae601463802a1519b33e42ef9392f190d508
|
data/lib/rray/aabb.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './interval'
|
4
|
+
|
5
|
+
module Rray
|
6
|
+
class AABB
|
7
|
+
attr_accessor :x, :y, :z
|
8
|
+
|
9
|
+
# The default AABB is empty, since intervals are empty by default.
|
10
|
+
def initialize(x = Interval.new, y = Interval.new, z = Interval.new)
|
11
|
+
@x = x
|
12
|
+
@y = y
|
13
|
+
@z = z
|
14
|
+
end
|
15
|
+
|
16
|
+
# Treat the two points a and b as extrema for the bounding box, so we don't require a
|
17
|
+
# particular minimum/maximum coordinate order.
|
18
|
+
def self.from_points(a, b)
|
19
|
+
new(
|
20
|
+
a.x < b.x ? Interval.new(a.x, b.x) : Interval.new(b.x, a.x),
|
21
|
+
a.y < b.y ? Interval.new(a.y, b.y) : Interval.new(b.y, a.y),
|
22
|
+
a.z < b.z ? Interval.new(a.z, b.z) : Interval.new(b.z, a.z)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.from_aabbs(box0, box1)
|
27
|
+
new(
|
28
|
+
Interval.from_intervals(box0.x, box1.x),
|
29
|
+
Interval.from_intervals(box0.y, box1.y),
|
30
|
+
Interval.from_intervals(box0.z, box1.z)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](n)
|
35
|
+
case n
|
36
|
+
when 0 then x
|
37
|
+
when 1 then y
|
38
|
+
when 2 then z
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def hit(r, ray_t)
|
43
|
+
t = Interval.from_range(ray_t)
|
44
|
+
|
45
|
+
(0...3).each do |axis|
|
46
|
+
ax = self[axis]
|
47
|
+
adinv = 1.0 / r.direction[axis]
|
48
|
+
|
49
|
+
t0 = (ax.min - r.origin[axis]) * adinv
|
50
|
+
t1 = (ax.max - r.origin[axis]) * adinv
|
51
|
+
|
52
|
+
if t0 < t1
|
53
|
+
t.min = t0 if t0 > t.min
|
54
|
+
t.max = t1 if t1 < t.max
|
55
|
+
else
|
56
|
+
t.min = t1 if t1 > t.min
|
57
|
+
t.max = t0 if t0 < t.max
|
58
|
+
end
|
59
|
+
|
60
|
+
return false if t.max <= t.min
|
61
|
+
end
|
62
|
+
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the index of the longest axis of the bounding box.
|
67
|
+
def longest_axis
|
68
|
+
if x.size > y.size
|
69
|
+
x.size > z.size ? 0 : 2
|
70
|
+
else
|
71
|
+
y.size > z.size ? 1 : 2
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
EMPTY = new(Interval::EMPTY, Interval::EMPTY, Interval::EMPTY)
|
76
|
+
UNIVERSE = new(Interval::UNIVERSE, Interval::UNIVERSE, Interval::UNIVERSE)
|
77
|
+
end
|
78
|
+
end
|
data/lib/rray/camera.rb
CHANGED
@@ -66,15 +66,27 @@ module Rray
|
|
66
66
|
# Construct a camera ray originating from the defocus disk and directed at a randomly
|
67
67
|
# sampled point around the pixel location x, y.
|
68
68
|
def ray(x, y)
|
69
|
-
offset =
|
69
|
+
offset = Vec3.sample_square
|
70
70
|
pixel_sample = pixel00_loc +
|
71
71
|
(pixel_delta_u * (x + offset.x)) +
|
72
72
|
(pixel_delta_v * (y + offset.y))
|
73
73
|
|
74
74
|
ray_origin = defocus_angle <= 0 ? center : defocus_disk_sample
|
75
75
|
ray_direction = pixel_sample - ray_origin
|
76
|
+
ray_time = rand
|
76
77
|
|
77
|
-
Ray.new(ray_origin, ray_direction)
|
78
|
+
Ray.new(ray_origin, ray_direction, ray_time)
|
79
|
+
end
|
80
|
+
|
81
|
+
def export
|
82
|
+
{
|
83
|
+
vfov: vfov,
|
84
|
+
lookfrom: lookfrom.to_a,
|
85
|
+
lookat: lookat.to_a,
|
86
|
+
vup: vup.to_a,
|
87
|
+
defocus_angle: defocus_angle,
|
88
|
+
focus_dist: focus_dist
|
89
|
+
}
|
78
90
|
end
|
79
91
|
|
80
92
|
private
|
data/lib/rray/hit.rb
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
module Rray
|
4
4
|
class Hit
|
5
|
-
attr_accessor :point, :normal, :t, :
|
5
|
+
attr_accessor :point, :normal, :t, :material, :front_face
|
6
6
|
|
7
|
-
def initialize(point, r, outward_normal, t,
|
7
|
+
def initialize(point, r, outward_normal, t, material)
|
8
8
|
@point = point
|
9
9
|
@t = t
|
10
|
-
@
|
10
|
+
@material = material
|
11
11
|
|
12
|
-
# Sets the hit
|
12
|
+
# Sets the hit's normal vector.
|
13
13
|
# NOTE: the parameter `outward_normal` is assumed to have unit length.
|
14
14
|
|
15
15
|
@front_face = r.direction.dot(outward_normal) < 0
|
data/lib/rray/interval.rb
CHANGED
@@ -4,11 +4,24 @@ module Rray
|
|
4
4
|
class Interval
|
5
5
|
attr_accessor :min, :max
|
6
6
|
|
7
|
-
|
7
|
+
# Default interval is empty
|
8
|
+
def initialize(min = Float::INFINITY, max = -Float::INFINITY)
|
8
9
|
@min = min
|
9
10
|
@max = max
|
10
11
|
end
|
11
12
|
|
13
|
+
def self.from_range(r)
|
14
|
+
new(r.min, r.max)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Create the interval tightly enclosing the two input intervals.
|
18
|
+
def self.from_intervals(a, b)
|
19
|
+
new(
|
20
|
+
a.min <= b.min ? a.min : b.min,
|
21
|
+
a.max >= b.max ? a.max : b.max
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
12
25
|
def size = max - min
|
13
26
|
def include?(x) = min <= x && x <= max
|
14
27
|
def surround?(x) = min < x && x < max
|
@@ -19,6 +32,15 @@ module Rray
|
|
19
32
|
x
|
20
33
|
end
|
21
34
|
|
35
|
+
def expand(delta)
|
36
|
+
padding = delta / 2.0
|
37
|
+
Interval.new(min - padding, max + padding)
|
38
|
+
end
|
39
|
+
|
40
|
+
def dup
|
41
|
+
Interval.new(min, max)
|
42
|
+
end
|
43
|
+
|
22
44
|
EMPTY = new(Float::INFINITY, -Float::INFINITY)
|
23
45
|
UNIVERSE = new(-Float::INFINITY, Float::INFINITY)
|
24
46
|
end
|
data/lib/rray/material/base.rb
CHANGED
@@ -3,9 +3,21 @@
|
|
3
3
|
module Rray
|
4
4
|
module Material
|
5
5
|
class Base
|
6
|
+
def id
|
7
|
+
"#{type.downcase}-#{object_id.to_s(16)}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def type
|
11
|
+
self.class.name.split('::').last
|
12
|
+
end
|
13
|
+
|
6
14
|
def scatter(r_in, rec)
|
7
15
|
raise NotImplementedError, "Implement #{self.class}#scatter"
|
8
16
|
end
|
17
|
+
|
18
|
+
def export
|
19
|
+
raise NotImplementedError, "Implement #{self.class.name}#export"
|
20
|
+
end
|
9
21
|
end
|
10
22
|
end
|
11
23
|
end
|
@@ -26,17 +26,24 @@ module Rray
|
|
26
26
|
unit_direction.refract(rec.normal, ri)
|
27
27
|
end
|
28
28
|
|
29
|
-
scattered = Ray.new(rec.point, direction)
|
29
|
+
scattered = Ray.new(rec.point, direction, r_in.time)
|
30
30
|
|
31
31
|
Scatter.new(scattered, attenuation)
|
32
32
|
end
|
33
33
|
|
34
|
+
def export
|
35
|
+
{
|
36
|
+
type: "Dielectric",
|
37
|
+
refraction_index: refraction_index
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
34
41
|
private
|
35
42
|
|
36
43
|
def reflectance(cosine, ri)
|
37
44
|
# Use Schlick's approximation for reflectance.
|
38
45
|
r0 = ((1.0 - ri) / (1.0 + ri))**2
|
39
|
-
r0 + (1-r0)*(1.0-cosine)**5
|
46
|
+
r0 + (1.0-r0)*(1.0-cosine)**5
|
40
47
|
end
|
41
48
|
end
|
42
49
|
end
|
@@ -11,13 +11,20 @@ module Rray
|
|
11
11
|
|
12
12
|
def scatter(r_in, rec)
|
13
13
|
scatter_direction = rec.normal + Vec3.random_unit_vector
|
14
|
-
scattered = Ray.new(rec.point, scatter_direction)
|
14
|
+
scattered = Ray.new(rec.point, scatter_direction, r_in.time)
|
15
15
|
|
16
16
|
# Catch degenerate scatter direction
|
17
17
|
scatter_direction = rec.normal if scatter_direction.zero?
|
18
18
|
|
19
19
|
Scatter.new(scattered, albedo)
|
20
20
|
end
|
21
|
+
|
22
|
+
def export
|
23
|
+
{
|
24
|
+
type: "Lambertian",
|
25
|
+
albedo: albedo.to_a
|
26
|
+
}
|
27
|
+
end
|
21
28
|
end
|
22
29
|
end
|
23
30
|
end
|
data/lib/rray/material/metal.rb
CHANGED
@@ -13,11 +13,19 @@ module Rray
|
|
13
13
|
def scatter(r_in, rec)
|
14
14
|
reflected = r_in.direction.reflect(rec.normal)
|
15
15
|
reflected = reflected.normalize.add(Vec3.random_unit_vector.mul(fuzz))
|
16
|
-
scattered = Ray.new(rec.point, reflected)
|
16
|
+
scattered = Ray.new(rec.point, reflected, r_in.time)
|
17
17
|
return nil if scattered.direction.dot(rec.normal) <= 0
|
18
18
|
|
19
19
|
Scatter.new(scattered, albedo)
|
20
20
|
end
|
21
|
+
|
22
|
+
def export
|
23
|
+
{
|
24
|
+
type: "Metal",
|
25
|
+
albedo: albedo.to_a,
|
26
|
+
fuzz: fuzz
|
27
|
+
}
|
28
|
+
end
|
21
29
|
end
|
22
30
|
end
|
23
31
|
end
|
data/lib/rray/object/base.rb
CHANGED
@@ -3,9 +3,25 @@
|
|
3
3
|
module Rray
|
4
4
|
module Object
|
5
5
|
class Base
|
6
|
+
def id
|
7
|
+
"#{type.downcase}-#{object_id.to_s(16)}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def type
|
11
|
+
self.class.name.split('::').last
|
12
|
+
end
|
13
|
+
|
6
14
|
def hit(r, ray_t)
|
7
15
|
raise NotImplementedError, "Implement #{self.class.name}#hit"
|
8
16
|
end
|
17
|
+
|
18
|
+
def export
|
19
|
+
raise NotImplementedError, "Implement #{self.class.name}#export"
|
20
|
+
end
|
21
|
+
|
22
|
+
def bounding_box
|
23
|
+
raise NotImplementedError, "Implement #{self.class.name}#bounding_box"
|
24
|
+
end
|
9
25
|
end
|
10
26
|
end
|
11
27
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rray
|
4
|
+
module Object
|
5
|
+
class BVH < Base
|
6
|
+
attr_reader :bounding_box
|
7
|
+
|
8
|
+
def initialize(objects)
|
9
|
+
@bounding_box = AABB::EMPTY
|
10
|
+
objects.each do |object|
|
11
|
+
@bounding_box = AABB.from_aabbs(@bounding_box, object.bounding_box)
|
12
|
+
end
|
13
|
+
|
14
|
+
axis = @bounding_box.longest_axis
|
15
|
+
|
16
|
+
case objects.length
|
17
|
+
when 1
|
18
|
+
@left = @right = objects[0]
|
19
|
+
when 2
|
20
|
+
@left = objects[0]
|
21
|
+
@right = objects[1]
|
22
|
+
else
|
23
|
+
objects.sort_by! { _1.bounding_box[axis].min }
|
24
|
+
mid = objects.length / 2
|
25
|
+
@left = BVH.new(objects[0...mid])
|
26
|
+
@right = BVH.new(objects[mid..])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def hit(r, ray_t)
|
31
|
+
return nil unless bounding_box.hit(r, ray_t)
|
32
|
+
|
33
|
+
hit_left = @left.hit(r, ray_t)
|
34
|
+
hit_right = @right.hit(r, ray_t.min..(hit_left&.t || ray_t.max))
|
35
|
+
|
36
|
+
hit_right || hit_left
|
37
|
+
end
|
38
|
+
|
39
|
+
def export
|
40
|
+
raise NotImplementedError, "Cannot export BVH, perhaps you meant to use a group?"
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s(depth=0)
|
44
|
+
indent = " "*depth
|
45
|
+
"#{indent}BVH {\n" +
|
46
|
+
"#{indent} left=\n"+
|
47
|
+
"#{@left.to_s(depth+2)}\n"+
|
48
|
+
"#{indent} right=\n"+
|
49
|
+
"#{@right.to_s(depth+2)}\n"+
|
50
|
+
"#{indent}}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/rray/object/group.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative './bvh'
|
4
|
+
|
3
5
|
module Rray
|
4
6
|
module Object
|
5
7
|
class Group < Base
|
@@ -7,25 +9,35 @@ module Rray
|
|
7
9
|
|
8
10
|
def initialize(objects = [])
|
9
11
|
@objects = objects
|
12
|
+
@bvh = BVH.new(objects)
|
10
13
|
end
|
11
14
|
|
12
15
|
def <<(object)
|
13
16
|
objects << object
|
17
|
+
@bvh = BVH.new(objects)
|
18
|
+
object
|
14
19
|
end
|
15
20
|
|
16
21
|
def hit(r, ray_t)
|
17
|
-
|
18
|
-
|
22
|
+
@bvh.hit(r, ray_t)
|
23
|
+
end
|
19
24
|
|
20
|
-
|
21
|
-
|
22
|
-
|
25
|
+
def bounding_box
|
26
|
+
@bvh.bounding_box
|
27
|
+
end
|
23
28
|
|
24
|
-
|
25
|
-
|
26
|
-
|
29
|
+
def export
|
30
|
+
{
|
31
|
+
type: "Group",
|
32
|
+
objects: objects.map(&:export)
|
33
|
+
}
|
34
|
+
end
|
27
35
|
|
28
|
-
|
36
|
+
def to_s(depth=0)
|
37
|
+
indent = " "*depth
|
38
|
+
"#{indent}Group {\n" +
|
39
|
+
objects.map { "#{indent} #{_1.to_s(depth+1)}\n" } +
|
40
|
+
"#{indent}}"
|
29
41
|
end
|
30
42
|
end
|
31
43
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rray
|
4
|
+
module Object
|
5
|
+
class MovingSphere < Sphere
|
6
|
+
attr_accessor :center_vec
|
7
|
+
|
8
|
+
def initialize(center1, center2, radius, material)
|
9
|
+
super(center1, radius, material)
|
10
|
+
@center_vec = center2 - center1
|
11
|
+
rvec = Vec3.new(radius, radius, radius)
|
12
|
+
box1 = AABB.from_points(center1 - rvec, center1 + rvec)
|
13
|
+
box2 = AABB.from_points(center2 - rvec, center2 + rvec)
|
14
|
+
@bounding_box = AABB.from_aabbs(box1, box2)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Linearly interpolate from center1 to center2 according to time, where t=0 yields
|
18
|
+
# center1, and t=1 yields center2.
|
19
|
+
def center_at(time = 0.0)
|
20
|
+
center + center_vec * time
|
21
|
+
end
|
22
|
+
|
23
|
+
def hit(r, ray_t)
|
24
|
+
super(r, ray_t, center_at(r.time))
|
25
|
+
end
|
26
|
+
|
27
|
+
def export
|
28
|
+
{
|
29
|
+
type: "MovingSphere",
|
30
|
+
center1: center.to_a,
|
31
|
+
center2: center_at(1.0).to_a,
|
32
|
+
radius: radius,
|
33
|
+
material: material.id
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s(depth=0)
|
38
|
+
indent = " "*depth
|
39
|
+
"#{indent}MovingSphere { radius=#{radius}, center=#{center}, vec=#{center_vec} }"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/rray/object/sphere.rb
CHANGED
@@ -3,15 +3,19 @@
|
|
3
3
|
module Rray
|
4
4
|
module Object
|
5
5
|
class Sphere < Base
|
6
|
-
attr_accessor :center, :radius, :
|
6
|
+
attr_accessor :center, :radius, :material
|
7
7
|
|
8
|
-
|
8
|
+
attr_reader :bounding_box
|
9
|
+
|
10
|
+
def initialize(center, radius, material)
|
9
11
|
@center = center
|
10
12
|
@radius = radius
|
11
|
-
@
|
13
|
+
@material = material
|
14
|
+
rvec = Vec3.new(radius, radius, radius)
|
15
|
+
@bounding_box = AABB.from_points(center - rvec, center + rvec)
|
12
16
|
end
|
13
17
|
|
14
|
-
def hit(r, ray_t)
|
18
|
+
def hit(r, ray_t, center = @center)
|
15
19
|
oc = center - r.origin
|
16
20
|
a = r.direction.length_squared
|
17
21
|
h = r.direction.dot(oc)
|
@@ -36,9 +40,23 @@ module Rray
|
|
36
40
|
r,
|
37
41
|
(point - center).div(radius),
|
38
42
|
root,
|
39
|
-
|
43
|
+
material
|
40
44
|
)
|
41
45
|
end
|
46
|
+
|
47
|
+
def export
|
48
|
+
{
|
49
|
+
type: "Sphere",
|
50
|
+
center: center.to_a,
|
51
|
+
radius: radius,
|
52
|
+
material: material.id
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s(depth=0)
|
57
|
+
indent = " "*depth
|
58
|
+
"#{indent}Sphere { radius=#{radius}, center=#{center} }"
|
59
|
+
end
|
42
60
|
end
|
43
61
|
end
|
44
62
|
end
|
data/lib/rray/ray.rb
CHANGED
@@ -2,15 +2,16 @@
|
|
2
2
|
|
3
3
|
module Rray
|
4
4
|
class Ray
|
5
|
-
attr_accessor :origin, :direction
|
5
|
+
attr_accessor :origin, :direction, :time
|
6
6
|
|
7
|
-
def initialize(origin, direction)
|
7
|
+
def initialize(origin, direction, time = 0.0)
|
8
8
|
@origin = origin
|
9
9
|
@direction = direction
|
10
|
+
@time = time
|
10
11
|
end
|
11
12
|
|
12
13
|
def at(t) = (direction*t).add(origin)
|
13
14
|
|
14
|
-
def dup = self.class.new(origin, direction)
|
15
|
+
def dup = self.class.new(origin, direction, time)
|
15
16
|
end
|
16
17
|
end
|
data/lib/rray/scene.rb
CHANGED
@@ -17,6 +17,31 @@ module Rray
|
|
17
17
|
Parser.new(json).parse
|
18
18
|
end
|
19
19
|
|
20
|
+
def export
|
21
|
+
{
|
22
|
+
world: world.export,
|
23
|
+
camera: camera.export,
|
24
|
+
materials: export_materials
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def export_materials
|
31
|
+
materials = {}
|
32
|
+
traverse do |object|
|
33
|
+
materials[object.material.id] ||= object.material.export
|
34
|
+
end
|
35
|
+
materials
|
36
|
+
end
|
37
|
+
|
38
|
+
def traverse(object = world, &block)
|
39
|
+
case object
|
40
|
+
when Object::Group then object.objects.each(&block)
|
41
|
+
else yield object
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
20
45
|
class Parser
|
21
46
|
class Error < Rray::Error; end
|
22
47
|
|
@@ -38,21 +63,34 @@ module Rray
|
|
38
63
|
case mat["type"]
|
39
64
|
when "Lambertian" then Material::Lambertian.new(Color.new(*mat["albedo"]))
|
40
65
|
when "Metal" then Material::Metal.new(Color.new(*mat["albedo"]), mat["fuzz"])
|
41
|
-
when "Dielectric" then Material::Dielectric.new(mat["
|
42
|
-
else raise
|
66
|
+
when "Dielectric" then Material::Dielectric.new(mat["refraction_index"])
|
67
|
+
else raise Error, "unknown material type #{mat["type"]}"
|
43
68
|
end
|
44
69
|
end
|
45
70
|
|
46
71
|
def parse_object(obj)
|
47
72
|
case obj["type"]
|
48
|
-
when "Group" then
|
49
|
-
when "Sphere" then
|
50
|
-
|
73
|
+
when "Group" then parse_group(obj)
|
74
|
+
when "Sphere" then parse_sphere(obj)
|
75
|
+
when "MovingSphere" then parse_moving_sphere(obj)
|
76
|
+
else raise Error, "unknown object type #{obj["type"]}"
|
51
77
|
end
|
52
78
|
end
|
53
79
|
|
80
|
+
def parse_group(obj)
|
81
|
+
Object::Group.new(obj["objects"].map { parse_object(_1) })
|
82
|
+
end
|
83
|
+
|
84
|
+
def parse_sphere(obj)
|
85
|
+
Object::Sphere.new(Point3.new(*obj["center"]), obj["radius"], material(obj["material"]))
|
86
|
+
end
|
87
|
+
|
88
|
+
def parse_moving_sphere(obj)
|
89
|
+
Object::MovingSphere.new(Point3.new(*obj["center1"]), Point3.new(*obj["center2"]), obj["radius"], material(obj["material"]))
|
90
|
+
end
|
91
|
+
|
54
92
|
def material(name)
|
55
|
-
raise
|
93
|
+
raise Error, "unknown material #{name}" unless @materials.key?(name)
|
56
94
|
|
57
95
|
@materials[name]
|
58
96
|
end
|
data/lib/rray/tracer.rb
CHANGED
@@ -29,7 +29,7 @@ module Rray
|
|
29
29
|
|
30
30
|
rec = scene.world.hit(r, 0.001..Float::INFINITY)
|
31
31
|
if rec
|
32
|
-
scattered = rec.
|
32
|
+
scattered = rec.material.scatter(r, rec)
|
33
33
|
return Color.new(0.0, 0.0, 0.0) unless scattered
|
34
34
|
|
35
35
|
return ray_color(scattered.ray, depth-1).multiply(scattered.attenuation)
|
data/lib/rray/util.rb
CHANGED
data/lib/rray/vec3.rb
CHANGED
@@ -6,6 +6,10 @@ module Rray
|
|
6
6
|
@e = [x, y, z]
|
7
7
|
end
|
8
8
|
|
9
|
+
def to_a
|
10
|
+
@e.dup
|
11
|
+
end
|
12
|
+
|
9
13
|
def x = @e[0]
|
10
14
|
def y = @e[1]
|
11
15
|
def z = @e[2]
|
@@ -22,10 +26,15 @@ module Rray
|
|
22
26
|
@e[2] = v
|
23
27
|
end
|
24
28
|
|
25
|
-
def -@
|
26
|
-
|
29
|
+
def -@
|
30
|
+
self.class.new(-x, -y, -z)
|
31
|
+
end
|
32
|
+
|
33
|
+
def [](i)
|
34
|
+
@e[i]
|
35
|
+
end
|
27
36
|
|
28
|
-
|
37
|
+
def []=(i, v)
|
29
38
|
@e[i] = v
|
30
39
|
end
|
31
40
|
|
@@ -104,13 +113,17 @@ module Rray
|
|
104
113
|
r_out_perp.add(r_out_parallel)
|
105
114
|
end
|
106
115
|
|
116
|
+
def to_s
|
117
|
+
"(#{x},#{y},#{z})"
|
118
|
+
end
|
119
|
+
|
107
120
|
def self.random(min = 0.0, max = 1.0)
|
108
|
-
|
121
|
+
new(Util.random(min, max), Util.random(min, max), Util.random(min, max))
|
109
122
|
end
|
110
123
|
|
111
124
|
def self.random_in_unit_sphere
|
112
125
|
loop do
|
113
|
-
v =
|
126
|
+
v = random(-1.0, 1.0)
|
114
127
|
return v if v.length_squared < 1
|
115
128
|
end
|
116
129
|
end
|
@@ -128,9 +141,11 @@ module Rray
|
|
128
141
|
|
129
142
|
def self.random_in_unit_disk
|
130
143
|
loop do
|
131
|
-
v =
|
144
|
+
v = new(Util.random(-1.0, 1.0), Util.random(-1.0, 1.0), 0.0)
|
132
145
|
return v if v.length_squared < 1
|
133
146
|
end
|
134
147
|
end
|
148
|
+
|
149
|
+
def self.sample_square = new(rand - 0.5, rand - 0.5, 0.0)
|
135
150
|
end
|
136
151
|
end
|
data/lib/rray/version.rb
CHANGED
data/lib/rray.rb
CHANGED
@@ -11,11 +11,14 @@ require_relative "rray/point3"
|
|
11
11
|
require_relative "rray/color"
|
12
12
|
require_relative "rray/ray"
|
13
13
|
require_relative "rray/interval"
|
14
|
+
require_relative "rray/aabb"
|
14
15
|
require_relative "rray/hit"
|
15
16
|
require_relative "rray/scatter"
|
16
17
|
require_relative "rray/object/base"
|
17
18
|
require_relative "rray/object/sphere"
|
19
|
+
require_relative "rray/object/moving_sphere"
|
18
20
|
require_relative "rray/object/group"
|
21
|
+
require_relative "rray/object/bvh"
|
19
22
|
require_relative "rray/material/base"
|
20
23
|
require_relative "rray/material/lambertian"
|
21
24
|
require_relative "rray/material/metal"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rray
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Danielle Smith
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-01 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Simple raytracer written in ruby. Can be used a CLI tool or library.
|
14
14
|
email:
|
@@ -21,6 +21,7 @@ files:
|
|
21
21
|
- README.md
|
22
22
|
- Rakefile
|
23
23
|
- lib/rray.rb
|
24
|
+
- lib/rray/aabb.rb
|
24
25
|
- lib/rray/camera.rb
|
25
26
|
- lib/rray/color.rb
|
26
27
|
- lib/rray/hit.rb
|
@@ -30,7 +31,9 @@ files:
|
|
30
31
|
- lib/rray/material/lambertian.rb
|
31
32
|
- lib/rray/material/metal.rb
|
32
33
|
- lib/rray/object/base.rb
|
34
|
+
- lib/rray/object/bvh.rb
|
33
35
|
- lib/rray/object/group.rb
|
36
|
+
- lib/rray/object/moving_sphere.rb
|
34
37
|
- lib/rray/object/sphere.rb
|
35
38
|
- lib/rray/point3.rb
|
36
39
|
- lib/rray/ray.rb
|