rray 0.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +49 -0
- data/Rakefile +8 -0
- data/lib/rray/camera.rb +97 -0
- data/lib/rray/color.rb +28 -0
- data/lib/rray/hit.rb +19 -0
- data/lib/rray/interval.rb +25 -0
- data/lib/rray/material/base.rb +11 -0
- data/lib/rray/material/dielectric.rb +43 -0
- data/lib/rray/material/lambertian.rb +23 -0
- data/lib/rray/material/metal.rb +23 -0
- data/lib/rray/object/base.rb +11 -0
- data/lib/rray/object/group.rb +32 -0
- data/lib/rray/object/sphere.rb +44 -0
- data/lib/rray/point3.rb +6 -0
- data/lib/rray/ray.rb +16 -0
- data/lib/rray/scatter.rb +12 -0
- data/lib/rray/scene.rb +72 -0
- data/lib/rray/tracer.rb +63 -0
- data/lib/rray/util.rb +17 -0
- data/lib/rray/vec3.rb +136 -0
- data/lib/rray/version.rb +5 -0
- data/lib/rray.rb +25 -0
- data/sig/rray.rbs +4 -0
- metadata +70 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: 8524e60bb5c72619ed588acd7778faf1c4164b93928094d9c75379e3315a1897
         | 
| 4 | 
            +
              data.tar.gz: ad9f79b082e057ff74b555672bbc7a11082fda5339353506efa1e004003642a3
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: f64cf7f310538afd73185ebc926ad928ee727752160012d6d506149a9e1433d1486579ffb8b51ab8fd6385aafc4d5a8b656d41dadcb59cf95babe97789eccdbd
         | 
| 7 | 
            +
              data.tar.gz: 9379ec81d4072e789115fd3534570733d0cd1d29fa5636e4a67e4f0e6f377e5414fa62fe66dc2cbc4872165cc6c78b85c50c2cb611d7f1a6098998b4f48e8b8b
         | 
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            The MIT License (MIT)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Copyright (c) 2024 Danielle Smith
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            +
            of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            +
            in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            +
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            +
            copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            +
            furnished to do so, subject to the following conditions:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            +
            all copies or substantial portions of the Software.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            +
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            +
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         | 
| 20 | 
            +
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
         | 
| 21 | 
            +
            THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            # Rray
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Simple raytracer written in ruby.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## Installation
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Install the gem and add to the application's Gemfile by executing:
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                $ bundle add rray
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            If bundler is not being used to manage dependencies, install the gem by executing:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                $ gem install rray
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            ## Usage
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            ```ruby
         | 
| 18 | 
            +
            require "rray"
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            scene = Rray::Scene.parse(File.read("scene.json"))
         | 
| 21 | 
            +
            tracer = Rray::Tracer.new(scene, width: 800, height: 600, samples_per_pixel: 50, max_depth: 100)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            output = Array.new(tracer.height) { Array.new(tracer.width) { [0, 0, 0] } }
         | 
| 24 | 
            +
            output.each.with_index do |row, i|
         | 
| 25 | 
            +
              row.each.with_index do |pixel, j|
         | 
| 26 | 
            +
                r, g, b = tracer.call(j, i)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                pixel[0] = r
         | 
| 29 | 
            +
                pixel[1] = g
         | 
| 30 | 
            +
                pixel[2] = b
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            # store output as an image
         | 
| 35 | 
            +
            ```
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            ## Development
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            ## Contributing
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            Bug reports and pull requests are welcome on GitHub at https://github.com/danini-the-panini/rray.
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            ## License
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
         | 
    
        data/Rakefile
    ADDED
    
    
    
        data/lib/rray/camera.rb
    ADDED
    
    | @@ -0,0 +1,97 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rray
         | 
| 4 | 
            +
              class Camera
         | 
| 5 | 
            +
                attr_reader \
         | 
| 6 | 
            +
                  :vfov,     # Vertical view angle (field of view)
         | 
| 7 | 
            +
                  :lookfrom, # Point camera is looking from
         | 
| 8 | 
            +
                  :lookat,   # Point camera is looking at
         | 
| 9 | 
            +
                  :vup,      # Camera-relative "up" direction
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  :defocus_angle, # Variation angle of rays through each pixel
         | 
| 12 | 
            +
                  :focus_dist     # Distance from camera lookfrom point to plane of perfect focus
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def initialize(
         | 
| 15 | 
            +
                  vfov: 90.0,
         | 
| 16 | 
            +
                  lookfrom: Point3.new(0.0, 0.0, 0.0),
         | 
| 17 | 
            +
                  lookat: Point3.new(0.0, 0.0, -1.0),
         | 
| 18 | 
            +
                  vup: Vec3.new(0.0, 1.0, 0.0),
         | 
| 19 | 
            +
                  defocus_angle: 0.0,
         | 
| 20 | 
            +
                  focus_dist: 10.0
         | 
| 21 | 
            +
                )
         | 
| 22 | 
            +
                  @vfov = vfov
         | 
| 23 | 
            +
                  @lookfrom = lookfrom
         | 
| 24 | 
            +
                  @lookat = lookat
         | 
| 25 | 
            +
                  @vup = vup
         | 
| 26 | 
            +
                  @defocus_angle = defocus_angle
         | 
| 27 | 
            +
                  @focus_dist = focus_dist
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def viewport(image_width, image_height)
         | 
| 31 | 
            +
                  @image_height = image_height
         | 
| 32 | 
            +
                  aspect_ratio = image_width.to_f / image_height.to_f
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  @center = lookfrom
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  # Determine viewport dimensions.
         | 
| 37 | 
            +
                  theta = Util.degrees_to_radians(vfov)
         | 
| 38 | 
            +
                  h = Math.tan(theta / 2.0)
         | 
| 39 | 
            +
                  viewport_height = 2.0 * h * focus_dist
         | 
| 40 | 
            +
                  viewport_width = viewport_height * aspect_ratio
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  # Calculate the u,v,w unit basis vectors for the camera coordinate frame.
         | 
| 43 | 
            +
                  @w = (lookfrom - lookat).normalize
         | 
| 44 | 
            +
                  @u = vup.cross(w).normalize
         | 
| 45 | 
            +
                  @v = w.cross(u)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  # Calculate the vectors across the horizontal and down the vertical viewport edges.
         | 
| 48 | 
            +
                  viewport_u = u * viewport_width   # Vector across viewport horizontal edge
         | 
| 49 | 
            +
                  viewport_v = -v * viewport_height # Vector down viewport vertical edge
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  # Calculate the horizontal and vertical delta vectors from pixel to pixel.
         | 
| 52 | 
            +
                  @pixel_delta_u = viewport_u / image_width.to_f
         | 
| 53 | 
            +
                  @pixel_delta_v = viewport_v / image_height.to_f
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  # Calculate the location of the upper left pixel.
         | 
| 56 | 
            +
                  viewport_upper_left =
         | 
| 57 | 
            +
                      center - (w*focus_dist) - viewport_u.div(2.0) - viewport_v.div(2.0)
         | 
| 58 | 
            +
                  @pixel00_loc = viewport_upper_left.add((pixel_delta_u + pixel_delta_v).mul(0.5))
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  # Calculate the camera defocus disk basis vectors.
         | 
| 61 | 
            +
                  defocus_radius = focus_dist * Math.tan(Util.degrees_to_radians(defocus_angle / 2.0))
         | 
| 62 | 
            +
                  @defocus_disk_u = u * defocus_radius
         | 
| 63 | 
            +
                  @defocus_disk_v = v * defocus_radius
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                # Construct a camera ray originating from the defocus disk and directed at a randomly
         | 
| 67 | 
            +
                # sampled point around the pixel location x, y.
         | 
| 68 | 
            +
                def ray(x, y)
         | 
| 69 | 
            +
                  offset = Util.sample_square
         | 
| 70 | 
            +
                  pixel_sample = pixel00_loc +
         | 
| 71 | 
            +
                                    (pixel_delta_u * (x + offset.x)) +
         | 
| 72 | 
            +
                                    (pixel_delta_v * (y + offset.y))
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  ray_origin = defocus_angle <= 0 ? center : defocus_disk_sample
         | 
| 75 | 
            +
                  ray_direction = pixel_sample - ray_origin
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  Ray.new(ray_origin, ray_direction)
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                private
         | 
| 81 | 
            +
                  attr_reader \
         | 
| 82 | 
            +
                    :image_height,   # Rendered image height
         | 
| 83 | 
            +
                    :center,         # Camera center
         | 
| 84 | 
            +
                    :pixel00_loc,    # Location of pixel 0, 0
         | 
| 85 | 
            +
                    :pixel_delta_u,  # Offset to pixel to the right
         | 
| 86 | 
            +
                    :pixel_delta_v,  # Offset to pixel below
         | 
| 87 | 
            +
                    :u, :v, :w,      # Camera frame basis vectors
         | 
| 88 | 
            +
                    :defocus_disk_u, # Defocus disk horizontal radius
         | 
| 89 | 
            +
                    :defocus_disk_v  # Defocus disk vertical radius
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  # Returns a random point in the camera defocus disk.
         | 
| 92 | 
            +
                  def defocus_disk_sample
         | 
| 93 | 
            +
                    v = Vec3.random_in_unit_disk
         | 
| 94 | 
            +
                    center + (defocus_disk_u * v.x) + (defocus_disk_v * v.y)
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
              end
         | 
| 97 | 
            +
            end
         | 
    
        data/lib/rray/color.rb
    ADDED
    
    | @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rray
         | 
| 4 | 
            +
              class Color < Vec3
         | 
| 5 | 
            +
                def r = x
         | 
| 6 | 
            +
                def g = y
         | 
| 7 | 
            +
                def b = z
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def r=(v)
         | 
| 10 | 
            +
                  self.x = v
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def g=(v)
         | 
| 14 | 
            +
                  self.y = v
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def b=(v)
         | 
| 18 | 
            +
                  self.z = v
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def multiply(v)
         | 
| 22 | 
            +
                  self.r *= v.r
         | 
| 23 | 
            +
                  self.g *= v.g
         | 
| 24 | 
            +
                  self.b *= v.b
         | 
| 25 | 
            +
                  self
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
    
        data/lib/rray/hit.rb
    ADDED
    
    | @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rray
         | 
| 4 | 
            +
              class Hit
         | 
| 5 | 
            +
                attr_accessor :point, :normal, :t, :mat, :front_face
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize(point, r, outward_normal, t, mat)
         | 
| 8 | 
            +
                  @point = point
         | 
| 9 | 
            +
                  @t = t
         | 
| 10 | 
            +
                  @mat = mat
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  # Sets the hit record normal vector.
         | 
| 13 | 
            +
                  # NOTE: the parameter `outward_normal` is assumed to have unit length.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  @front_face = r.direction.dot(outward_normal) < 0
         | 
| 16 | 
            +
                  @normal = @front_face ? outward_normal : -outward_normal
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rray
         | 
| 4 | 
            +
              class Interval
         | 
| 5 | 
            +
                attr_accessor :min, :max
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize(min, max)
         | 
| 8 | 
            +
                  @min = min
         | 
| 9 | 
            +
                  @max = max
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def size = max - min
         | 
| 13 | 
            +
                def include?(x) = min <= x && x <= max
         | 
| 14 | 
            +
                def surround?(x) = min < x && x < max
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def clamp(x)
         | 
| 17 | 
            +
                  return min if x < min
         | 
| 18 | 
            +
                  return max if x > max
         | 
| 19 | 
            +
                  x
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                EMPTY = new(Float::INFINITY, -Float::INFINITY)
         | 
| 23 | 
            +
                UNIVERSE = new(-Float::INFINITY, Float::INFINITY)
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rray
         | 
| 4 | 
            +
              module Material
         | 
| 5 | 
            +
                class Dielectric < Base
         | 
| 6 | 
            +
                  # Refractive index in vacuum or air, or the ratio of the material's refractive index over
         | 
| 7 | 
            +
                  # the refractive index of the enclosing media
         | 
| 8 | 
            +
                  attr_accessor :refraction_index
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize(refraction_index)
         | 
| 11 | 
            +
                    @refraction_index = refraction_index
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def scatter(r_in, rec)
         | 
| 15 | 
            +
                    attenuation = Color.new(1.0, 1.0, 1.0)
         | 
| 16 | 
            +
                    ri = rec.front_face ? 1.0/refraction_index : refraction_index
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    unit_direction = r_in.direction.unit
         | 
| 19 | 
            +
                    cos_theta = [(-unit_direction).dot(rec.normal), 1.0].min
         | 
| 20 | 
            +
                    sin_theta = Math.sqrt(1.0 - cos_theta**2)
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    cannot_refract = ri * sin_theta > 1.0
         | 
| 23 | 
            +
                    direction = if cannot_refract || reflectance(cos_theta, ri) > rand
         | 
| 24 | 
            +
                      unit_direction.reflect(rec.normal)
         | 
| 25 | 
            +
                    else
         | 
| 26 | 
            +
                      unit_direction.refract(rec.normal, ri)
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    scattered = Ray.new(rec.point, direction)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    Scatter.new(scattered, attenuation)
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  private
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    def reflectance(cosine, ri)
         | 
| 37 | 
            +
                      # Use Schlick's approximation for reflectance.
         | 
| 38 | 
            +
                      r0 = ((1.0 - ri) / (1.0 + ri))**2
         | 
| 39 | 
            +
                      r0 + (1-r0)*(1.0-cosine)**5
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rray
         | 
| 4 | 
            +
              module Material
         | 
| 5 | 
            +
                class Lambertian < Base
         | 
| 6 | 
            +
                  attr_accessor :albedo
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize(albedo)
         | 
| 9 | 
            +
                    @albedo = albedo
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def scatter(r_in, rec)
         | 
| 13 | 
            +
                    scatter_direction = rec.normal + Vec3.random_unit_vector
         | 
| 14 | 
            +
                    scattered = Ray.new(rec.point, scatter_direction)
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    # Catch degenerate scatter direction
         | 
| 17 | 
            +
                    scatter_direction = rec.normal if scatter_direction.zero?
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    Scatter.new(scattered, albedo)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rray
         | 
| 4 | 
            +
              module Material
         | 
| 5 | 
            +
                class Metal < Base
         | 
| 6 | 
            +
                  attr_accessor :albedo, :fuzz
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize(albedo, fuzz)
         | 
| 9 | 
            +
                    @albedo = albedo
         | 
| 10 | 
            +
                    @fuzz = fuzz
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def scatter(r_in, rec)
         | 
| 14 | 
            +
                    reflected = r_in.direction.reflect(rec.normal)
         | 
| 15 | 
            +
                    reflected = reflected.normalize.add(Vec3.random_unit_vector.mul(fuzz))
         | 
| 16 | 
            +
                    scattered = Ray.new(rec.point, reflected)
         | 
| 17 | 
            +
                    return nil if scattered.direction.dot(rec.normal) <= 0
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    Scatter.new(scattered, albedo)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rray
         | 
| 4 | 
            +
              module Object
         | 
| 5 | 
            +
                class Group < Base
         | 
| 6 | 
            +
                  attr_reader :objects
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize(objects = [])
         | 
| 9 | 
            +
                    @objects = objects
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def <<(object)
         | 
| 13 | 
            +
                    objects << object
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def hit(r, ray_t)
         | 
| 17 | 
            +
                    rec = nil
         | 
| 18 | 
            +
                    closest_so_far = ray_t.end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    objects.each do |object|
         | 
| 21 | 
            +
                      temp_rec = object.hit(r, ray_t.begin..closest_so_far)
         | 
| 22 | 
            +
                      next unless temp_rec
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                      rec = temp_rec
         | 
| 25 | 
            +
                      closest_so_far = rec.t
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    rec
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| @@ -0,0 +1,44 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rray
         | 
| 4 | 
            +
              module Object
         | 
| 5 | 
            +
                class Sphere < Base
         | 
| 6 | 
            +
                  attr_accessor :center, :radius, :mat
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize(center, radius, mat)
         | 
| 9 | 
            +
                    @center = center
         | 
| 10 | 
            +
                    @radius = radius
         | 
| 11 | 
            +
                    @mat = mat
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def hit(r, ray_t)
         | 
| 15 | 
            +
                    oc = center - r.origin
         | 
| 16 | 
            +
                    a = r.direction.length_squared
         | 
| 17 | 
            +
                    h = r.direction.dot(oc)
         | 
| 18 | 
            +
                    c = oc.length_squared - radius**2
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    discriminant = h**2 - a*c
         | 
| 21 | 
            +
                    return nil if discriminant < 0
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    sqrtd = Math.sqrt(discriminant)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    # Find the nearest root that lies in the acceptable range.
         | 
| 26 | 
            +
                    root = (h - sqrtd) / a
         | 
| 27 | 
            +
                    unless ray_t.cover?(root)
         | 
| 28 | 
            +
                      root = (h + sqrtd) / a
         | 
| 29 | 
            +
                      return nil unless ray_t.cover?(root)
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    point = r.at(root)
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    Hit.new(
         | 
| 35 | 
            +
                      point,
         | 
| 36 | 
            +
                      r,
         | 
| 37 | 
            +
                      (point - center).div(radius),
         | 
| 38 | 
            +
                      root,
         | 
| 39 | 
            +
                      mat
         | 
| 40 | 
            +
                    )
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
            end
         | 
    
        data/lib/rray/point3.rb
    ADDED
    
    
    
        data/lib/rray/ray.rb
    ADDED
    
    | @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rray
         | 
| 4 | 
            +
              class Ray
         | 
| 5 | 
            +
                attr_accessor :origin, :direction
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize(origin, direction)
         | 
| 8 | 
            +
                  @origin = origin
         | 
| 9 | 
            +
                  @direction = direction
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def at(t) = (direction*t).add(origin)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def dup = self.class.new(origin, direction)
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
    
        data/lib/rray/scatter.rb
    ADDED
    
    
    
        data/lib/rray/scene.rb
    ADDED
    
    | @@ -0,0 +1,72 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "json"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Rray
         | 
| 6 | 
            +
              class Scene
         | 
| 7 | 
            +
                attr_reader :world, :camera
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def initialize(world, camera)
         | 
| 10 | 
            +
                  @world = world
         | 
| 11 | 
            +
                  @camera = camera
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def self.parse(json)
         | 
| 15 | 
            +
                  return parse(JSON.parse(json)) if json.is_a? String
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  Parser.new(json).parse
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                class Parser
         | 
| 21 | 
            +
                  class Error < Rray::Error; end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  attr_reader :json
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def initialize(json)
         | 
| 26 | 
            +
                    @json = json
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def parse
         | 
| 30 | 
            +
                    @materials = json["materials"].transform_values { parse_material(_1) }
         | 
| 31 | 
            +
                    world = parse_object(json["world"])
         | 
| 32 | 
            +
                    camera = parse_camera(json["camera"])
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    Scene.new(world, camera)
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def parse_material(mat)
         | 
| 38 | 
            +
                    case mat["type"]
         | 
| 39 | 
            +
                    when "Lambertian" then Material::Lambertian.new(Color.new(*mat["albedo"]))
         | 
| 40 | 
            +
                    when "Metal" then Material::Metal.new(Color.new(*mat["albedo"]), mat["fuzz"])
         | 
| 41 | 
            +
                    when "Dielectric" then Material::Dielectric.new(mat["refractive_index"])
         | 
| 42 | 
            +
                    else raise ParserError, "unknown material type #{mat["type"]}"
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  def parse_object(obj)
         | 
| 47 | 
            +
                    case obj["type"]
         | 
| 48 | 
            +
                    when "Group" then Object::Group.new(obj["objects"].map { parse_object(_1) })
         | 
| 49 | 
            +
                    when "Sphere" then Object::Sphere.new(Point3.new(*obj["center"]), obj["radius"], material(obj["material"]))
         | 
| 50 | 
            +
                    else raise ParserError, "unknown object type #{obj["type"]}"
         | 
| 51 | 
            +
                    end
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def material(name)
         | 
| 55 | 
            +
                    raise ParserError, "unknown material #{name}" unless @materials.key?(name)
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                    @materials[name]
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def parse_camera(cam)
         | 
| 61 | 
            +
                    args = {}
         | 
| 62 | 
            +
                    args[:vfov] = cam["vfov"] if cam.key?("vfov")
         | 
| 63 | 
            +
                    args[:lookfrom] = Point3.new(*cam["lookfrom"]) if cam.key?("lookfrom")
         | 
| 64 | 
            +
                    args[:lookat] = Point3.new(*cam["lookat"]) if cam.key?("lookat")
         | 
| 65 | 
            +
                    args[:vup] = Vec3.new(*cam["vup"]) if cam.key?("vup")
         | 
| 66 | 
            +
                    args[:defocus_angle] = cam["defocus_angle"] if cam.key?("defocus_angle")
         | 
| 67 | 
            +
                    args[:focus_dist] = cam["focus_dist"] if cam.key?("focus_dist")
         | 
| 68 | 
            +
                    Camera.new(**args)
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
              end
         | 
| 72 | 
            +
            end
         | 
    
        data/lib/rray/tracer.rb
    ADDED
    
    | @@ -0,0 +1,63 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rray
         | 
| 4 | 
            +
              class Tracer
         | 
| 5 | 
            +
                attr_reader :scene, :width, :height, :samples_per_pixel, :max_depth
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize(scene, width: 256, height: 256, samples_per_pixel: 10, max_depth: 10)
         | 
| 8 | 
            +
                  @scene = scene
         | 
| 9 | 
            +
                  @width = width
         | 
| 10 | 
            +
                  @height = height
         | 
| 11 | 
            +
                  @samples_per_pixel = samples_per_pixel
         | 
| 12 | 
            +
                  @max_depth = max_depth
         | 
| 13 | 
            +
                  @pixel_sample_scale = 1.0 / samples_per_pixel
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  scene.camera.viewport(width, height)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def call(x, y)
         | 
| 19 | 
            +
                  pixel_color = Color.new(0.0, 0.0, 0.0)
         | 
| 20 | 
            +
                  samples_per_pixel.times do
         | 
| 21 | 
            +
                    pixel_color.add(ray_color(scene.camera.ray(x, y)))
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                  color(pixel_color.mul(@pixel_sample_scale))
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def ray_color(r, depth = max_depth)
         | 
| 27 | 
            +
                  # If we've exceeded the ray bounce limit, no more light is gathered.
         | 
| 28 | 
            +
                  return Color.new(0.0, 0.0, 0.0) unless depth > 0
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  rec = scene.world.hit(r, 0.001..Float::INFINITY)
         | 
| 31 | 
            +
                  if rec
         | 
| 32 | 
            +
                    scattered = rec.mat.scatter(r, rec)
         | 
| 33 | 
            +
                    return Color.new(0.0, 0.0, 0.0) unless scattered
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    return ray_color(scattered.ray, depth-1).multiply(scattered.attenuation)
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  unit_direction = r.direction.unit
         | 
| 39 | 
            +
                  a = 0.5*(unit_direction.y + 1.0)
         | 
| 40 | 
            +
                  Color.new(1.0, 1.0, 1.0).mul(1.0-a).add(Color.new(0.5, 0.7, 1.0).mul(a))
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def color(pixel_color)
         | 
| 44 | 
            +
                  r = pixel_color.r
         | 
| 45 | 
            +
                  g = pixel_color.g
         | 
| 46 | 
            +
                  b = pixel_color.b
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  # Apply a linear to gamma transform for gamma 2
         | 
| 49 | 
            +
                  r = Util.linear_to_gamma(r)
         | 
| 50 | 
            +
                  g = Util.linear_to_gamma(g)
         | 
| 51 | 
            +
                  b = Util.linear_to_gamma(b)
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  # Translate the [0,1] component values to the byte range [0,255].
         | 
| 54 | 
            +
                  intensity = Interval.new(0.000, 0.999)
         | 
| 55 | 
            +
                  rbyte = (256 * intensity.clamp(r)).to_i
         | 
| 56 | 
            +
                  gbyte = (256 * intensity.clamp(g)).to_i
         | 
| 57 | 
            +
                  bbyte = (256 * intensity.clamp(b)).to_i
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  # Return the pixel color components.
         | 
| 60 | 
            +
                  [rbyte, gbyte, bbyte]
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
            end
         | 
    
        data/lib/rray/util.rb
    ADDED
    
    | @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rray
         | 
| 4 | 
            +
              module Util
         | 
| 5 | 
            +
                def self.degrees_to_radians(degrees) = degrees * Math::PI / 180.0
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                # Returns a random real in [min,max).
         | 
| 8 | 
            +
                def self.random(min, max) = min + (max-min)*rand
         | 
| 9 | 
            +
                def self.sample_square = Vec3.new(rand - 0.5, rand - 0.5, 0.0)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def self.linear_to_gamma(linear_component)
         | 
| 12 | 
            +
                  return 0 unless linear_component > 0
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  Math.sqrt(linear_component)
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
    
        data/lib/rray/vec3.rb
    ADDED
    
    | @@ -0,0 +1,136 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rray
         | 
| 4 | 
            +
              class Vec3
         | 
| 5 | 
            +
                def initialize(x = 0.0, y = 0.0, z = 0.0)
         | 
| 6 | 
            +
                  @e = [x, y, z]
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def x = @e[0]
         | 
| 10 | 
            +
                def y = @e[1]
         | 
| 11 | 
            +
                def z = @e[2]
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def x=(v)
         | 
| 14 | 
            +
                  @e[0] = v
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def y=(v)
         | 
| 18 | 
            +
                  @e[1] = v
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def z=(v)
         | 
| 22 | 
            +
                  @e[2] = v
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def -@ = self.class.new(-x, -y, -z)
         | 
| 26 | 
            +
                def [](i) = @e[i]
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def []=(i, v)
         | 
| 29 | 
            +
                  @e[i] = v
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def +(v) = Vec3.new(x+v.x, y+v.y, z+v.z)
         | 
| 33 | 
            +
                def -(v) = Vec3.new(x-v.x, y-v.y, z-v.z)
         | 
| 34 | 
            +
                def *(t) = Vec3.new(x*t, y*t, z*t)
         | 
| 35 | 
            +
                def /(t) = self * (1.0/t)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def add(v)
         | 
| 38 | 
            +
                  self.x += v.x
         | 
| 39 | 
            +
                  self.y += v.y
         | 
| 40 | 
            +
                  self.z += v.z
         | 
| 41 | 
            +
                  self
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def sub(v)
         | 
| 45 | 
            +
                  self.x -= v.x
         | 
| 46 | 
            +
                  self.y -= v.y
         | 
| 47 | 
            +
                  self.z -= v.z
         | 
| 48 | 
            +
                  self
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def mul(t)
         | 
| 52 | 
            +
                  self.x *= t
         | 
| 53 | 
            +
                  self.y *= t
         | 
| 54 | 
            +
                  self.z *= t
         | 
| 55 | 
            +
                  self
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def div(t)
         | 
| 59 | 
            +
                  mul(1.0/t)
         | 
| 60 | 
            +
                  self
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def neg
         | 
| 64 | 
            +
                  self.x = -x
         | 
| 65 | 
            +
                  self.y = -y
         | 
| 66 | 
            +
                  self.z = -z
         | 
| 67 | 
            +
                  self
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                def length = Math.sqrt(length_squared)
         | 
| 71 | 
            +
                def length_squared = dot(self)
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                def dot(v) = x*v.x + y*v.y + z*v.z
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def cross(v)
         | 
| 76 | 
            +
                  Vec3.new(
         | 
| 77 | 
            +
                    y*v.z - z*v.y,
         | 
| 78 | 
            +
                    z*v.x - x*v.z,
         | 
| 79 | 
            +
                    x*v.y - y*v.x
         | 
| 80 | 
            +
                  )
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                def unit = self / length
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def normalize
         | 
| 86 | 
            +
                  div(length)
         | 
| 87 | 
            +
                  self
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                def dup = self.class.new(*@e)
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                EPSILON = 1e-8
         | 
| 93 | 
            +
                # Return true if the vector is close to zero in all dimensions.
         | 
| 94 | 
            +
                def zero? = x.abs < EPSILON && y.abs < EPSILON && z.abs < EPSILON
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                def reflect(n)
         | 
| 97 | 
            +
                  self - n*(dot(n)*2.0)
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                def refract(n, etai_over_etat)
         | 
| 101 | 
            +
                  cos_theta = [(-self).dot(n), 1.0].min
         | 
| 102 | 
            +
                  r_out_perp = (self + n*cos_theta).mul(etai_over_etat)
         | 
| 103 | 
            +
                  r_out_parallel = n * -Math.sqrt((1.0 - r_out_perp.length_squared).abs)
         | 
| 104 | 
            +
                  r_out_perp.add(r_out_parallel)
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                def self.random(min = 0.0, max = 1.0)
         | 
| 108 | 
            +
                  Vec3.new(Util.random(min, max), Util.random(min, max), Util.random(min, max))
         | 
| 109 | 
            +
                end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                def self.random_in_unit_sphere
         | 
| 112 | 
            +
                  loop do
         | 
| 113 | 
            +
                    v = Vec3.random(-1.0, 1.0)
         | 
| 114 | 
            +
                    return v if v.length_squared < 1
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                def self.random_unit_vector = random_in_unit_sphere.normalize
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                def self.random_on_hemisphere(normal)
         | 
| 121 | 
            +
                  on_unit_sphere = random_unit_vector
         | 
| 122 | 
            +
                  if on_unit_sphere.dot(normal) > 0.0 # In the same hemisphere as the normal
         | 
| 123 | 
            +
                    on_unit_sphere
         | 
| 124 | 
            +
                  else
         | 
| 125 | 
            +
                    -on_unit_sphere
         | 
| 126 | 
            +
                  end
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                def self.random_in_unit_disk
         | 
| 130 | 
            +
                  loop do
         | 
| 131 | 
            +
                    v = Vec3.new(Util.random(-1.0, 1.0), Util.random(-1.0, 1.0), 0.0)
         | 
| 132 | 
            +
                    return v if v.length_squared < 1
         | 
| 133 | 
            +
                  end
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
              end
         | 
| 136 | 
            +
            end
         | 
    
        data/lib/rray/version.rb
    ADDED
    
    
    
        data/lib/rray.rb
    ADDED
    
    | @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "rray/version"
         | 
| 4 | 
            +
            require_relative "rray/util"
         | 
| 5 | 
            +
            require_relative "rray/vec3"
         | 
| 6 | 
            +
            require_relative "rray/point3"
         | 
| 7 | 
            +
            require_relative "rray/color"
         | 
| 8 | 
            +
            require_relative "rray/ray"
         | 
| 9 | 
            +
            require_relative "rray/interval"
         | 
| 10 | 
            +
            require_relative "rray/hit"
         | 
| 11 | 
            +
            require_relative "rray/scatter"
         | 
| 12 | 
            +
            require_relative "rray/object/base"
         | 
| 13 | 
            +
            require_relative "rray/object/sphere"
         | 
| 14 | 
            +
            require_relative "rray/object/group"
         | 
| 15 | 
            +
            require_relative "rray/material/base"
         | 
| 16 | 
            +
            require_relative "rray/material/lambertian"
         | 
| 17 | 
            +
            require_relative "rray/material/metal"
         | 
| 18 | 
            +
            require_relative "rray/material/dielectric"
         | 
| 19 | 
            +
            require_relative "rray/camera"
         | 
| 20 | 
            +
            require_relative "rray/scene"
         | 
| 21 | 
            +
            require_relative "rray/tracer"
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            module Rray
         | 
| 24 | 
            +
              class Error < StandardError; end
         | 
| 25 | 
            +
            end
         | 
    
        data/sig/rray.rbs
    ADDED
    
    
    
        metadata
    ADDED
    
    | @@ -0,0 +1,70 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: rray
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Danielle Smith
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: exe
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2024-04-15 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies: []
         | 
| 13 | 
            +
            description: Simple raytracer written in ruby. Can be used a CLI tool or library.
         | 
| 14 | 
            +
            email:
         | 
| 15 | 
            +
            - code@danini.dev
         | 
| 16 | 
            +
            executables: []
         | 
| 17 | 
            +
            extensions: []
         | 
| 18 | 
            +
            extra_rdoc_files: []
         | 
| 19 | 
            +
            files:
         | 
| 20 | 
            +
            - LICENSE.txt
         | 
| 21 | 
            +
            - README.md
         | 
| 22 | 
            +
            - Rakefile
         | 
| 23 | 
            +
            - lib/rray.rb
         | 
| 24 | 
            +
            - lib/rray/camera.rb
         | 
| 25 | 
            +
            - lib/rray/color.rb
         | 
| 26 | 
            +
            - lib/rray/hit.rb
         | 
| 27 | 
            +
            - lib/rray/interval.rb
         | 
| 28 | 
            +
            - lib/rray/material/base.rb
         | 
| 29 | 
            +
            - lib/rray/material/dielectric.rb
         | 
| 30 | 
            +
            - lib/rray/material/lambertian.rb
         | 
| 31 | 
            +
            - lib/rray/material/metal.rb
         | 
| 32 | 
            +
            - lib/rray/object/base.rb
         | 
| 33 | 
            +
            - lib/rray/object/group.rb
         | 
| 34 | 
            +
            - lib/rray/object/sphere.rb
         | 
| 35 | 
            +
            - lib/rray/point3.rb
         | 
| 36 | 
            +
            - lib/rray/ray.rb
         | 
| 37 | 
            +
            - lib/rray/scatter.rb
         | 
| 38 | 
            +
            - lib/rray/scene.rb
         | 
| 39 | 
            +
            - lib/rray/tracer.rb
         | 
| 40 | 
            +
            - lib/rray/util.rb
         | 
| 41 | 
            +
            - lib/rray/vec3.rb
         | 
| 42 | 
            +
            - lib/rray/version.rb
         | 
| 43 | 
            +
            - sig/rray.rbs
         | 
| 44 | 
            +
            homepage: https://github.com/danini-the-panini/rray
         | 
| 45 | 
            +
            licenses:
         | 
| 46 | 
            +
            - MIT
         | 
| 47 | 
            +
            metadata:
         | 
| 48 | 
            +
              homepage_uri: https://github.com/danini-the-panini/rray
         | 
| 49 | 
            +
              source_code_uri: https://github.com/danini-the-panini/rray
         | 
| 50 | 
            +
              changelog_uri: https://github.com/danini-the-panini/rray/releases
         | 
| 51 | 
            +
            post_install_message: 
         | 
| 52 | 
            +
            rdoc_options: []
         | 
| 53 | 
            +
            require_paths:
         | 
| 54 | 
            +
            - lib
         | 
| 55 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 56 | 
            +
              requirements:
         | 
| 57 | 
            +
              - - ">="
         | 
| 58 | 
            +
                - !ruby/object:Gem::Version
         | 
| 59 | 
            +
                  version: 3.0.0
         | 
| 60 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 61 | 
            +
              requirements:
         | 
| 62 | 
            +
              - - ">="
         | 
| 63 | 
            +
                - !ruby/object:Gem::Version
         | 
| 64 | 
            +
                  version: '0'
         | 
| 65 | 
            +
            requirements: []
         | 
| 66 | 
            +
            rubygems_version: 3.5.3
         | 
| 67 | 
            +
            signing_key: 
         | 
| 68 | 
            +
            specification_version: 4
         | 
| 69 | 
            +
            summary: Raytracer in Ruby
         | 
| 70 | 
            +
            test_files: []
         |