ra 0.1.0 → 0.3.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.
@@ -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
+ # @param source [Vector]
13
+ # @param 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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ra
4
- VERSION = "0.1.0"
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/ra/world.rb ADDED
@@ -0,0 +1,52 @@
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
+ # @param lights [Ra::Light]
7
+ # @param shapes [Array<Ra::Shape>]
8
+ def initialize(lights:, shapes:)
9
+ @lights = lights
10
+ @shapes = shapes
11
+ end
12
+
13
+ # @param ray [Ra::Ray]
14
+ # @return [Array<Ra::Intersection>]
15
+ def intersect(ray:)
16
+ @shapes
17
+ .map { |shape| shape.intersect(ray:) }
18
+ .reduce(:concat) || []
19
+ end
20
+
21
+ # @return [Ra::Intersection, nil]
22
+ def intersection(ray:)
23
+ intersections = intersect(ray:)
24
+ Intersection.hit(intersections:)
25
+ end
26
+
27
+ # @param intersection [Ra::Intersection]
28
+ # @return [Ra::Color]
29
+ def color(intersection:)
30
+ surface = intersection.surface
31
+ point = surface.hpoint
32
+
33
+ colors = @lights.map do |light|
34
+ shadowed = shadowed?(point:, light:)
35
+ lighting = Lighting.new(surface:, light:, shadowed:)
36
+ lighting.color
37
+ end
38
+
39
+ colors.reduce(&:+)
40
+ end
41
+
42
+ # @param point [Ra::Point]
43
+ def shadowed?(light:, point:)
44
+ vector = light.position - point
45
+ distance = vector.magnitude
46
+ direction = vector.normalize
47
+ ray = Ray.new(origin: point, direction:)
48
+ hit = intersection(ray:)
49
+ hit && hit.t < distance
50
+ end
51
+ end
52
+ end
data/lib/ra.rb CHANGED
@@ -1,8 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "ra/version"
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
- class Error < StandardError; end
7
- # Your code goes here...
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,112 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ra
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.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-20 00:00:00.000000000 Z
12
- dependencies: []
13
- description: "..."
11
+ date: 2023-11-23 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: mini_magick
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: slop
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
+ - !ruby/object:Gem::Dependency
56
+ name: zeitwerk
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: This should probably never be used for anything but learning.
14
70
  email:
15
71
  - kevin@ksylvest.com
16
- executables: []
72
+ executables:
73
+ - ra
17
74
  extensions: []
18
75
  extra_rdoc_files: []
19
76
  files:
20
- - ".rspec"
21
- - ".rubocop.yml"
22
77
  - README.md
23
- - Rakefile
78
+ - exe/ra
24
79
  - lib/ra.rb
80
+ - lib/ra/camera.rb
81
+ - lib/ra/canvas.rb
82
+ - lib/ra/color.rb
83
+ - lib/ra/engine.rb
84
+ - lib/ra/intersection.rb
85
+ - lib/ra/light.rb
86
+ - lib/ra/lighting.rb
87
+ - lib/ra/logger.rb
88
+ - lib/ra/material.rb
89
+ - lib/ra/pattern/base.rb
90
+ - lib/ra/pattern/checkers.rb
91
+ - lib/ra/pattern/gradient.rb
92
+ - lib/ra/pattern/rings.rb
93
+ - lib/ra/pattern/stripes.rb
94
+ - lib/ra/pattern/texture.rb
95
+ - lib/ra/ray.rb
96
+ - lib/ra/shape/base.rb
97
+ - lib/ra/shape/cube.rb
98
+ - lib/ra/shape/plane.rb
99
+ - lib/ra/shape/sphere.rb
100
+ - lib/ra/surface.rb
101
+ - lib/ra/transform.rb
102
+ - lib/ra/tuple.rb
25
103
  - lib/ra/version.rb
26
- - sig/ra.rbs
104
+ - lib/ra/world.rb
27
105
  homepage: https://github.com/ksylvest/ra
28
- licenses: []
106
+ licenses:
107
+ - MIT
29
108
  metadata:
30
- homepage_uri: https://github.com/ksylvest/ra
109
+ rubygems_mfa_required: 'true'
31
110
  post_install_message:
32
111
  rdoc_options: []
33
112
  require_paths:
@@ -36,7 +115,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
36
115
  requirements:
37
116
  - - ">="
38
117
  - !ruby/object:Gem::Version
39
- version: '0'
118
+ version: '3.2'
40
119
  required_rubygems_version: !ruby/object:Gem::Requirement
41
120
  requirements:
42
121
  - - ">="
@@ -46,5 +125,5 @@ requirements: []
46
125
  rubygems_version: 3.4.22
47
126
  signing_key:
48
127
  specification_version: 4
49
- summary: "..."
128
+ summary: A graphics library written for fun.
50
129
  test_files: []
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,13 +0,0 @@
1
- AllCops:
2
- TargetRubyVersion: 2.6
3
-
4
- Style/StringLiterals:
5
- Enabled: true
6
- EnforcedStyle: double_quotes
7
-
8
- Style/StringLiteralsInInterpolation:
9
- Enabled: true
10
- EnforcedStyle: double_quotes
11
-
12
- Layout/LineLength:
13
- Max: 120
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- require "rubocop/rake_task"
9
-
10
- RuboCop::RakeTask.new
11
-
12
- task default: %i[spec rubocop]
data/sig/ra.rbs DELETED
@@ -1,4 +0,0 @@
1
- module Ra
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end