ra 0.3.0 → 0.4.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/exe/ra +58 -40
- data/lib/ra/engine.rb +1 -4
- data/lib/ra/intersection.rb +2 -1
- data/lib/ra/lighting.rb +1 -1
- data/lib/ra/material.rb +9 -2
- data/lib/ra/surface.rb +3 -2
- data/lib/ra/tuple.rb +7 -0
- data/lib/ra/version.rb +1 -1
- data/lib/ra/world.rb +24 -6
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96608d9848ea1181d69813cd6a37cfcc5cb0ac04564048ed4b2a9d6f4af27414
|
4
|
+
data.tar.gz: a1bfbd9254cc6c56abab623bdf65d9a3282904c4b43039c3609934a2d8d273b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b63eba61eb50a16fc55388b409b27dde40e092009f920ffe614fb15e29683ac58b129a2bf3848c0944b220ed2b6aca1cfb434a2441d050a601a801fb60c5e0ae
|
7
|
+
data.tar.gz: 004eb7ca2f72a3804f62332f1f909cd2875dbc46928ea6392e22b32a34ae5a3ea55117a6bda5dfd15b84b33d8f1c3d990517b10b1d64ed08497e3fe5b8619750
|
data/exe/ra
CHANGED
@@ -8,8 +8,8 @@ require 'slop'
|
|
8
8
|
config = Slop.parse(ARGV) do |options|
|
9
9
|
options.banner = 'Usage: ra -w 2560 -h 2048 | convert - sample.avif'
|
10
10
|
|
11
|
-
options.integer '-w', '--width', 'width', default:
|
12
|
-
options.integer '-h', '--height', 'height', default:
|
11
|
+
options.integer '-w', '--width', 'width', default: 2560
|
12
|
+
options.integer '-h', '--height', 'height', default: 2048
|
13
13
|
options.integer '-fov', 'degrees', default: 60
|
14
14
|
|
15
15
|
options.on('--help', 'help') do
|
@@ -26,13 +26,13 @@ end
|
|
26
26
|
earth = Ra::Pattern::Texture.new(path: File.join(File.dirname(__FILE__), '..', 'textures/earth.avif'))
|
27
27
|
|
28
28
|
light_l = Ra::Light.new(
|
29
|
-
position: Vector[+5, +
|
30
|
-
intensity: Ra::Color.uniform(0.
|
29
|
+
position: Vector[+5, +3, -9, Ra::Tuple::POINT],
|
30
|
+
intensity: Ra::Color.uniform(0.5),
|
31
31
|
)
|
32
32
|
|
33
33
|
light_r = Ra::Light.new(
|
34
|
-
position: Vector[-5, +
|
35
|
-
intensity: Ra::Color.uniform(0.
|
34
|
+
position: Vector[-5, +3, -9, Ra::Tuple::POINT],
|
35
|
+
intensity: Ra::Color.uniform(0.5),
|
36
36
|
)
|
37
37
|
|
38
38
|
camera = Ra::Camera.new(
|
@@ -40,43 +40,53 @@ camera = Ra::Camera.new(
|
|
40
40
|
h: config[:h],
|
41
41
|
fov: config[:fov] * Math::PI / 180,
|
42
42
|
transform: Ra::Transform.view(
|
43
|
-
from: Vector[0, +1.5, -
|
44
|
-
to: Vector[0, 0,
|
43
|
+
from: Vector[0, +1.5, -4.0, Ra::Tuple::POINT],
|
44
|
+
to: Vector[0, 0, 0, Ra::Tuple::POINT],
|
45
45
|
up: Vector[0, 1, 0, Ra::Tuple::VECTOR],
|
46
46
|
),
|
47
47
|
)
|
48
48
|
|
49
49
|
floor = Ra::Shape::Plane.new(
|
50
|
-
material: Ra::Material.new(
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
50
|
+
material: Ra::Material.new(
|
51
|
+
base: Ra::Pattern::Checkers.new(
|
52
|
+
colors: [
|
53
|
+
Ra::Color.hex('#e2e8f0'),
|
54
|
+
Ra::Color.hex('#1e293b'),
|
55
|
+
],
|
56
|
+
),
|
57
|
+
reflective: 0.2,
|
58
|
+
),
|
59
|
+
transform: Ra::Transform
|
60
|
+
.rotate_y(Math::PI / 4)
|
61
|
+
.scale(0.5, 0.5, 0.5),
|
56
62
|
)
|
57
63
|
|
58
64
|
wall_l = Ra::Shape::Plane.new(
|
59
|
-
material: Ra::Material.new(
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
+
material: Ra::Material.new(
|
66
|
+
base: Ra::Pattern::Stripes.new(
|
67
|
+
colors: [
|
68
|
+
Ra::Color.hex('#f5f5f4'),
|
69
|
+
Ra::Color.hex('#e7e5e4'),
|
70
|
+
],
|
71
|
+
),
|
72
|
+
),
|
65
73
|
transform: Ra::Transform
|
66
|
-
.translate(0, 0, +
|
74
|
+
.translate(0, 0, +3.0)
|
67
75
|
.rotate_y(-Math::PI / 4)
|
68
76
|
.rotate_x(Math::PI / 2),
|
69
77
|
)
|
70
78
|
|
71
79
|
wall_r = Ra::Shape::Plane.new(
|
72
|
-
material: Ra::Material.new(
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
80
|
+
material: Ra::Material.new(
|
81
|
+
base: Ra::Pattern::Stripes.new(
|
82
|
+
colors: [
|
83
|
+
Ra::Color.hex('#f5f5f4'),
|
84
|
+
Ra::Color.hex('#e7e5e4'),
|
85
|
+
],
|
86
|
+
),
|
87
|
+
),
|
78
88
|
transform: Ra::Transform
|
79
|
-
.translate(0, 0, +
|
89
|
+
.translate(0, 0, +3.0)
|
80
90
|
.rotate_y(Math::PI / 4)
|
81
91
|
.rotate_x(Math::PI / 2),
|
82
92
|
)
|
@@ -90,23 +100,31 @@ sphere = Ra::Shape::Sphere.new(
|
|
90
100
|
)
|
91
101
|
|
92
102
|
cube_l = Ra::Shape::Cube.new(
|
93
|
-
material: Ra::Material.new(
|
94
|
-
|
95
|
-
|
96
|
-
|
103
|
+
material: Ra::Material.new(
|
104
|
+
base: Ra::Pattern::Gradient.new(
|
105
|
+
color_a: Ra::Color.hex('#f43f5e'),
|
106
|
+
color_b: Ra::Color.hex('#8b5cf6'),
|
107
|
+
),
|
108
|
+
reflective: 0.2,
|
109
|
+
),
|
97
110
|
transform: Ra::Transform
|
98
|
-
.translate(+1.0, +0.3, -1.
|
99
|
-
.scale(0.3, 0.3, 0.3)
|
111
|
+
.translate(+1.0, +0.3, -1.0)
|
112
|
+
.scale(0.3, 0.3, 0.3)
|
113
|
+
.rotate_y(-Math::PI / 8),
|
100
114
|
)
|
101
115
|
|
102
116
|
cube_r = Ra::Shape::Cube.new(
|
103
|
-
material: Ra::Material.new(
|
104
|
-
|
105
|
-
|
106
|
-
|
117
|
+
material: Ra::Material.new(
|
118
|
+
base: Ra::Pattern::Gradient.new(
|
119
|
+
color_a: Ra::Color.hex('#84cc16'),
|
120
|
+
color_b: Ra::Color.hex('#f97316'),
|
121
|
+
),
|
122
|
+
reflective: 0.5,
|
123
|
+
),
|
107
124
|
transform: Ra::Transform
|
108
|
-
.translate(-1.0, +0.3, -1.
|
109
|
-
.scale(0.3, 0.3, 0.3)
|
125
|
+
.translate(-1.0, +0.3, -1.0)
|
126
|
+
.scale(0.3, 0.3, 0.3)
|
127
|
+
.rotate_y(+Math::PI / 8),
|
110
128
|
)
|
111
129
|
|
112
130
|
lights = [
|
data/lib/ra/engine.rb
CHANGED
@@ -31,10 +31,7 @@ module Ra
|
|
31
31
|
def draw(x:, y:, canvas:)
|
32
32
|
ray = @camera.ray(x:, y:)
|
33
33
|
|
34
|
-
|
35
|
-
intersection = Intersection.hit(intersections:)
|
36
|
-
|
37
|
-
canvas[x, y] = @world.color(intersection:) if intersection
|
34
|
+
canvas[x, y] = @world.color(ray:)
|
38
35
|
end
|
39
36
|
end
|
40
37
|
end
|
data/lib/ra/intersection.rb
CHANGED
@@ -38,8 +38,9 @@ module Ra
|
|
38
38
|
point = ray.position(t:)
|
39
39
|
eyev = -ray.direction
|
40
40
|
normalv = shape.normal(point:)
|
41
|
+
reflectv = Tuple.reflect(ray.direction, normalv)
|
41
42
|
|
42
|
-
Surface.new(shape:, eyev:, normalv:, point:)
|
43
|
+
Surface.new(shape:, eyev:, normalv:, reflectv:, point:)
|
43
44
|
end
|
44
45
|
end
|
45
46
|
end
|
data/lib/ra/lighting.rb
CHANGED
data/lib/ra/material.rb
CHANGED
@@ -11,17 +11,24 @@ module Ra
|
|
11
11
|
# shininess: 200,
|
12
12
|
# )
|
13
13
|
class Material
|
14
|
-
attr_accessor :base, :ambient, :diffuse, :specular, :shininess
|
14
|
+
attr_accessor :base, :ambient, :diffuse, :reflective, :specular, :shininess
|
15
15
|
|
16
16
|
# @param base [Ra::Color, Ra::Pattern:::Base]
|
17
17
|
# @param ambient [Float] between 0.0 and 1.0
|
18
18
|
# @param diffuse [Float] between 0.0 and 1.0
|
19
|
+
# @param reflective [Float] between 0.0 and 1.0
|
19
20
|
# @param specular [Float] between 0.0 and 1.0
|
20
21
|
# @param shininess [Numeric]
|
21
|
-
def initialize(base:, ambient: 0.0, diffuse: 0.8, specular: 0.2, shininess: 80)
|
22
|
+
def initialize(base:, ambient: 0.0, diffuse: 0.8, reflective: 0.0, specular: 0.2, shininess: 80)
|
23
|
+
raise ArgumentError, "ambient=#{ambient} must be between 0 and 1" unless ambient.between?(0, 1)
|
24
|
+
raise ArgumentError, "ambient=#{diffuse} must be between 0 and 1" unless diffuse.between?(0, 1)
|
25
|
+
raise ArgumentError, "ambient=#{reflective} must be between 0 and 1" unless reflective.between?(0, 1)
|
26
|
+
raise ArgumentError, "specular=#{specular} must be between 0 and 1" unless specular.between?(0, 1)
|
27
|
+
|
22
28
|
@base = base
|
23
29
|
@ambient = ambient
|
24
30
|
@diffuse = diffuse
|
31
|
+
@reflective = reflective
|
25
32
|
@specular = specular
|
26
33
|
@shininess = shininess
|
27
34
|
end
|
data/lib/ra/surface.rb
CHANGED
@@ -3,15 +3,16 @@
|
|
3
3
|
module Ra
|
4
4
|
# A surface contains everything needed to apply lighting.
|
5
5
|
class Surface
|
6
|
-
attr_accessor :eyev, :normalv, :shape, :point
|
6
|
+
attr_accessor :eyev, :normalv, :reflectv, :shape, :point
|
7
7
|
|
8
8
|
# @param eyev [Vector]
|
9
9
|
# @param normalv [Vector]
|
10
10
|
# @param shape [Ra::Shape]
|
11
11
|
# @param point [Vector]
|
12
|
-
def initialize(eyev:, normalv:, shape:, point:)
|
12
|
+
def initialize(eyev:, normalv:, reflectv:, shape:, point:)
|
13
13
|
@eyev = eyev
|
14
14
|
@normalv = normalv.dot(eyev).negative? ? -normalv : +normalv
|
15
|
+
@reflectv = reflectv
|
15
16
|
@shape = shape
|
16
17
|
@point = point
|
17
18
|
end
|
data/lib/ra/tuple.rb
CHANGED
data/lib/ra/version.rb
CHANGED
data/lib/ra/world.rb
CHANGED
@@ -24,21 +24,39 @@ module Ra
|
|
24
24
|
Intersection.hit(intersections:)
|
25
25
|
end
|
26
26
|
|
27
|
-
# @param
|
27
|
+
# @param ray [Ra::Ray]
|
28
28
|
# @return [Ra::Color]
|
29
|
-
def color(
|
29
|
+
def color(ray:, remaining: 4)
|
30
|
+
intersection = intersection(ray:)
|
31
|
+
return unless intersection
|
32
|
+
|
30
33
|
surface = intersection.surface
|
31
|
-
point = surface.hpoint
|
32
34
|
|
33
35
|
colors = @lights.map do |light|
|
34
|
-
shadowed = shadowed?(point
|
35
|
-
lighting = Lighting.new(
|
36
|
+
shadowed = shadowed?(point: surface.hpoint, light:)
|
37
|
+
lighting = Lighting.new(light:, shadowed:, surface:)
|
36
38
|
lighting.color
|
37
39
|
end
|
38
40
|
|
39
|
-
colors.reduce(&:+)
|
41
|
+
colors.reduce(&:+) + reflect(surface:, remaining:)
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param surface [Ra::Surface]
|
45
|
+
# @param remaining [Integer]
|
46
|
+
# @return [Ra::Color]
|
47
|
+
def reflect(surface:, remaining:)
|
48
|
+
return if remaining.zero?
|
49
|
+
|
50
|
+
material = surface.shape.material
|
51
|
+
return unless material.reflective.positive?
|
52
|
+
|
53
|
+
ray = Ray.new(origin: surface.hpoint, direction: surface.reflectv)
|
54
|
+
|
55
|
+
color = color(ray:, remaining: remaining.pred)
|
56
|
+
color * material.reflective if color
|
40
57
|
end
|
41
58
|
|
59
|
+
# @param light [Ra::Light]
|
42
60
|
# @param point [Ra::Point]
|
43
61
|
def shadowed?(light:, point:)
|
44
62
|
vector = light.position - point
|