rray 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|