ra 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ra
4
- VERSION = "0.1.0"
4
+ VERSION = '0.2.0'
5
5
  end
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
- 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,97 @@
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.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-20 00:00:00.000000000 Z
12
- dependencies: []
13
- description: "..."
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
- - Rakefile
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
- - sig/ra.rbs
89
+ - lib/ra/world.rb
27
90
  homepage: https://github.com/ksylvest/ra
28
- licenses: []
91
+ licenses:
92
+ - MIT
29
93
  metadata:
30
- homepage_uri: https://github.com/ksylvest/ra
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: '0'
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
@@ -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