ra 0.1.0 → 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/README.md +8 -22
- data/exe/ra +138 -0
- data/lib/ra/camera.rb +92 -0
- data/lib/ra/canvas.rb +78 -0
- data/lib/ra/color.rb +149 -0
- data/lib/ra/engine.rb +40 -0
- data/lib/ra/intersection.rb +45 -0
- data/lib/ra/light.rb +21 -0
- data/lib/ra/lighting.rb +97 -0
- data/lib/ra/logger.rb +19 -0
- data/lib/ra/material.rb +37 -0
- data/lib/ra/pattern/base.rb +31 -0
- data/lib/ra/pattern/checkers.rb +32 -0
- data/lib/ra/pattern/gradient.rb +29 -0
- data/lib/ra/pattern/rings.rb +31 -0
- data/lib/ra/pattern/stripes.rb +30 -0
- data/lib/ra/ray.rb +53 -0
- data/lib/ra/shape/base.rb +54 -0
- data/lib/ra/shape/cube.rb +89 -0
- data/lib/ra/shape/plane.rb +45 -0
- data/lib/ra/shape/sphere.rb +75 -0
- data/lib/ra/surface.rb +24 -0
- data/lib/ra/transform.rb +141 -0
- data/lib/ra/tuple.rb +26 -0
- data/lib/ra/version.rb +1 -1
- data/lib/ra/world.rb +48 -0
- data/lib/ra.rb +12 -3
- metadata +77 -13
- data/.rspec +0 -3
- data/.rubocop.yml +0 -13
- data/Rakefile +0 -12
- data/sig/ra.rbs +0 -4
data/lib/ra/transform.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ra
|
4
|
+
# A transform is represnted with a four by four matrix. These transforms are useful manupulate vectors. For example:
|
5
|
+
#
|
6
|
+
# p = Vector[1, 2, 3, Ra::Tuple::POINT]
|
7
|
+
# Ra::Transform.translate(2, 3, 4) * p == Vector[3, 5, 7, Ra::Tupl::POINT]
|
8
|
+
# Ra::Transform.scale(1, 2, 3) * p == Vector[1, 4, 9, Ra::Tuple::POINT]
|
9
|
+
#
|
10
|
+
# As a convenience, transforms can also be chained:
|
11
|
+
#
|
12
|
+
# Ra::Transform
|
13
|
+
# .translate(2, 3, 4)
|
14
|
+
# .scale(1, 2, 3)
|
15
|
+
# .rotate_x(Math::PI / 4)
|
16
|
+
# .rotate_y(Math::PI / 4)
|
17
|
+
# .rotate_z(Math::PI / 4)
|
18
|
+
class Transform < ::Matrix
|
19
|
+
IDENTITY = identity(4)
|
20
|
+
|
21
|
+
# @param from [Vector]
|
22
|
+
# @param to [Vector]
|
23
|
+
# @param up [Vector]
|
24
|
+
# @return [Ra::Transform]
|
25
|
+
def self.view(from:, to:, up:)
|
26
|
+
f = (to - from).normalize
|
27
|
+
l = Tuple.cross(f, up.normalize)
|
28
|
+
u = Tuple.cross(l, f)
|
29
|
+
|
30
|
+
self[l, u, -f, [0, 0, 0, 1]]
|
31
|
+
.translate(-from[0], -from[1], -from[2])
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Ra::Transform]
|
35
|
+
# @param x [Numeric]
|
36
|
+
# @param y [Numeric]
|
37
|
+
# @param z [Numeric]
|
38
|
+
def self.translate(x, y, z)
|
39
|
+
self[
|
40
|
+
[1, 0, 0, x],
|
41
|
+
[0, 1, 0, y],
|
42
|
+
[0, 0, 1, z],
|
43
|
+
[0, 0, 0, 1],
|
44
|
+
]
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Ra::Transform]
|
48
|
+
# @param x [Numeric]
|
49
|
+
# @param y [Numeric]
|
50
|
+
# @param z [Numeric]
|
51
|
+
def self.scale(x, y, z)
|
52
|
+
self[
|
53
|
+
[x, 0, 0, 0],
|
54
|
+
[0, y, 0, 0],
|
55
|
+
[0, 0, z, 0],
|
56
|
+
[0, 0, 0, 1],
|
57
|
+
]
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Ra::Transform]
|
61
|
+
# @param rotation [Numeric]
|
62
|
+
def self.rotate_x(rotation)
|
63
|
+
self[
|
64
|
+
[1, 0, 0, 0],
|
65
|
+
[0, +Math.cos(rotation), -Math.sin(rotation), 0],
|
66
|
+
[0, +Math.sin(rotation), +Math.cos(rotation), 0],
|
67
|
+
[0, 0, 0, 1],
|
68
|
+
]
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Ra::Transform]
|
72
|
+
# @param rotation [Numeric]
|
73
|
+
def self.rotate_y(rotation)
|
74
|
+
self[
|
75
|
+
[+Math.cos(rotation), 0, +Math.sin(rotation), 0],
|
76
|
+
[0, 1, 0, 0],
|
77
|
+
[-Math.sin(rotation), 0, +Math.cos(rotation), 0],
|
78
|
+
[0, 0, 0, 1],
|
79
|
+
]
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Ra::Transform]
|
83
|
+
# @param rotation [Numeric]
|
84
|
+
def self.rotate_z(rotation)
|
85
|
+
self[
|
86
|
+
[+Math.cos(rotation), -Math.sin(rotation), 0, 0],
|
87
|
+
[+Math.sin(rotation), +Math.cos(rotation), 0, 0],
|
88
|
+
[0, 0, 1, 0],
|
89
|
+
[0, 0, 0, 1],
|
90
|
+
]
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Ra::Transform]
|
94
|
+
# @param x [Numeric]
|
95
|
+
# @param y [Numeric]
|
96
|
+
# @param z [Numeric]
|
97
|
+
def translate(...)
|
98
|
+
self * self.class.translate(...)
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [Ra::Transform]
|
102
|
+
# @param x [Numeric]
|
103
|
+
# @param y [Numeric]
|
104
|
+
# @param z [Numeric]
|
105
|
+
def scale(...)
|
106
|
+
self * self.class.scale(...)
|
107
|
+
end
|
108
|
+
|
109
|
+
# @return [Ra::Transform]
|
110
|
+
# @param rotation [Numeric]
|
111
|
+
def rotate_x(...)
|
112
|
+
self * self.class.rotate_x(...)
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return [Ra::Transform]
|
116
|
+
# @param rotation [Numeric]
|
117
|
+
def rotate_y(...)
|
118
|
+
self * self.class.rotate_y(...)
|
119
|
+
end
|
120
|
+
|
121
|
+
# @return [Ra::Transform]
|
122
|
+
# @param rotation [Numeric]
|
123
|
+
def rotate_z(...)
|
124
|
+
self * self.class.rotate_z(...)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Avoid re-computing a transform inverse by memoizing.
|
128
|
+
#
|
129
|
+
# @return [Ra::Transform]
|
130
|
+
def inverse
|
131
|
+
@inverse ||= super
|
132
|
+
end
|
133
|
+
|
134
|
+
# Avoid re-computing a transform tranpose by memoizing.
|
135
|
+
#
|
136
|
+
# @return [Ra::Transform]
|
137
|
+
def tranpose
|
138
|
+
@tranpose ||= super
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/ra/tuple.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ra
|
4
|
+
# A Tuple can classify a Vector as a POINT or VECTOR. For example:
|
5
|
+
#
|
6
|
+
# point = Vector[x = 1, y = 2, z = 3, w = Ra::Tuple::POINT]
|
7
|
+
# vector = Vector[x = 1, y = 2, z = 3, w = Ra::Tuple::VECTOR]
|
8
|
+
module Tuple
|
9
|
+
POINT = 1
|
10
|
+
VECTOR = 0
|
11
|
+
|
12
|
+
# @return source [Vector]
|
13
|
+
# @return target [Vector]
|
14
|
+
# @return [Vector]
|
15
|
+
def self.cross(source, target)
|
16
|
+
cross = Vector[source[0], source[1], source[2]].cross(Vector[target[0], target[1], target[2]])
|
17
|
+
|
18
|
+
Vector[
|
19
|
+
cross[0],
|
20
|
+
cross[1],
|
21
|
+
cross[2],
|
22
|
+
Tuple::VECTOR,
|
23
|
+
]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/ra/version.rb
CHANGED
data/lib/ra/world.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ra
|
4
|
+
# A world is composed of objects (lights / cameras) and handles coloring of rays.
|
5
|
+
class World
|
6
|
+
attr_accessor :light, :shapes
|
7
|
+
|
8
|
+
# @param light [Ra::Light]
|
9
|
+
# @param shapes [Array<Ra::Shape>]
|
10
|
+
def initialize(light:, shapes:)
|
11
|
+
@light = light
|
12
|
+
@shapes = shapes
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param ray [Ra::Ray]
|
16
|
+
# @return [Array<Ra::Intersection>]
|
17
|
+
def intersect(ray:)
|
18
|
+
@shapes
|
19
|
+
.map { |shape| shape.intersect(ray:) }
|
20
|
+
.reduce(:concat) || []
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Ra::Intersection, nil]
|
24
|
+
def intersection(ray:)
|
25
|
+
intersections = intersect(ray:)
|
26
|
+
Intersection.hit(intersections:)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param intersection [Ra::Intersection]
|
30
|
+
# @return [Ra::Color]
|
31
|
+
def color(intersection:)
|
32
|
+
surface = intersection.surface
|
33
|
+
shadowed = shadowed?(point: surface.hpoint)
|
34
|
+
|
35
|
+
Lighting.new(shadowed:, surface:, light:).color
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param point [Ra::Point]
|
39
|
+
def shadowed?(point:)
|
40
|
+
vector = @light.position - point
|
41
|
+
distance = vector.magnitude
|
42
|
+
direction = vector.normalize
|
43
|
+
ray = Ray.new(origin: point, direction:)
|
44
|
+
hit = intersection(ray:)
|
45
|
+
hit && hit.t < distance
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/ra.rb
CHANGED
@@ -1,8 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'matrix'
|
4
|
+
require 'zeitwerk'
|
4
5
|
|
6
|
+
loader = Zeitwerk::Loader.for_gem
|
7
|
+
loader.setup
|
8
|
+
|
9
|
+
# Named for ["Ra"](https://en.wikipedia.org/wiki/Ra).
|
5
10
|
module Ra
|
6
|
-
|
7
|
-
|
11
|
+
EPSILON = (2**-16)
|
12
|
+
|
13
|
+
# @return [Ra::Logger]
|
14
|
+
def self.logger
|
15
|
+
@logger ||= Logger.new
|
16
|
+
end
|
8
17
|
end
|
metadata
CHANGED
@@ -1,33 +1,97 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ra
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Sylvestre
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-11-
|
12
|
-
dependencies:
|
13
|
-
|
11
|
+
date: 2023-11-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: matrix
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: slop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: zeitwerk
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: This should probably never be used for anything but learning.
|
14
56
|
email:
|
15
57
|
- kevin@ksylvest.com
|
16
|
-
executables:
|
58
|
+
executables:
|
59
|
+
- ra
|
17
60
|
extensions: []
|
18
61
|
extra_rdoc_files: []
|
19
62
|
files:
|
20
|
-
- ".rspec"
|
21
|
-
- ".rubocop.yml"
|
22
63
|
- README.md
|
23
|
-
-
|
64
|
+
- exe/ra
|
24
65
|
- lib/ra.rb
|
66
|
+
- lib/ra/camera.rb
|
67
|
+
- lib/ra/canvas.rb
|
68
|
+
- lib/ra/color.rb
|
69
|
+
- lib/ra/engine.rb
|
70
|
+
- lib/ra/intersection.rb
|
71
|
+
- lib/ra/light.rb
|
72
|
+
- lib/ra/lighting.rb
|
73
|
+
- lib/ra/logger.rb
|
74
|
+
- lib/ra/material.rb
|
75
|
+
- lib/ra/pattern/base.rb
|
76
|
+
- lib/ra/pattern/checkers.rb
|
77
|
+
- lib/ra/pattern/gradient.rb
|
78
|
+
- lib/ra/pattern/rings.rb
|
79
|
+
- lib/ra/pattern/stripes.rb
|
80
|
+
- lib/ra/ray.rb
|
81
|
+
- lib/ra/shape/base.rb
|
82
|
+
- lib/ra/shape/cube.rb
|
83
|
+
- lib/ra/shape/plane.rb
|
84
|
+
- lib/ra/shape/sphere.rb
|
85
|
+
- lib/ra/surface.rb
|
86
|
+
- lib/ra/transform.rb
|
87
|
+
- lib/ra/tuple.rb
|
25
88
|
- lib/ra/version.rb
|
26
|
-
-
|
89
|
+
- lib/ra/world.rb
|
27
90
|
homepage: https://github.com/ksylvest/ra
|
28
|
-
licenses:
|
91
|
+
licenses:
|
92
|
+
- MIT
|
29
93
|
metadata:
|
30
|
-
|
94
|
+
rubygems_mfa_required: 'true'
|
31
95
|
post_install_message:
|
32
96
|
rdoc_options: []
|
33
97
|
require_paths:
|
@@ -36,7 +100,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
36
100
|
requirements:
|
37
101
|
- - ">="
|
38
102
|
- !ruby/object:Gem::Version
|
39
|
-
version: '
|
103
|
+
version: '3.2'
|
40
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
105
|
requirements:
|
42
106
|
- - ">="
|
@@ -46,5 +110,5 @@ requirements: []
|
|
46
110
|
rubygems_version: 3.4.22
|
47
111
|
signing_key:
|
48
112
|
specification_version: 4
|
49
|
-
summary:
|
113
|
+
summary: A graphics library written for fun.
|
50
114
|
test_files: []
|
data/.rspec
DELETED
data/.rubocop.yml
DELETED
data/Rakefile
DELETED
data/sig/ra.rbs
DELETED