ra 0.1.0 → 0.3.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
+ # @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