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.
- checksums.yaml +4 -4
 - data/README.md +9 -22
 - data/exe/ra +130 -0
 - data/lib/ra/camera.rb +92 -0
 - data/lib/ra/canvas.rb +71 -0
 - data/lib/ra/color.rb +158 -0
 - data/lib/ra/engine.rb +40 -0
 - data/lib/ra/intersection.rb +45 -0
 - data/lib/ra/light.rb +21 -0
 - data/lib/ra/lighting.rb +96 -0
 - data/lib/ra/logger.rb +19 -0
 - data/lib/ra/material.rb +37 -0
 - data/lib/ra/pattern/base.rb +14 -0
 - data/lib/ra/pattern/checkers.rb +37 -0
 - data/lib/ra/pattern/gradient.rb +28 -0
 - data/lib/ra/pattern/rings.rb +26 -0
 - data/lib/ra/pattern/stripes.rb +27 -0
 - data/lib/ra/pattern/texture.rb +52 -0
 - data/lib/ra/ray.rb +53 -0
 - data/lib/ra/shape/base.rb +58 -0
 - data/lib/ra/shape/cube.rb +155 -0
 - data/lib/ra/shape/plane.rb +52 -0
 - data/lib/ra/shape/sphere.rb +90 -0
 - data/lib/ra/surface.rb +24 -0
 - data/lib/ra/transform.rb +141 -0
 - data/lib/ra/tuple.rb +26 -0
 - data/lib/ra/version.rb +1 -1
 - data/lib/ra/world.rb +52 -0
 - data/lib/ra.rb +12 -3
 - metadata +92 -13
 - data/.rspec +0 -3
 - data/.rubocop.yml +0 -13
 - data/Rakefile +0 -12
 - data/sig/ra.rbs +0 -4
 
    
        data/lib/ra/logger.rb
    ADDED
    
    | 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ra
         
     | 
| 
      
 4 
     | 
    
         
            +
              # A logger used to make exe testing possible.
         
     | 
| 
      
 5 
     | 
    
         
            +
              #
         
     | 
| 
      
 6 
     | 
    
         
            +
              #   logger = Logger.new
         
     | 
| 
      
 7 
     | 
    
         
            +
              #   logger.log("Greetings!")
         
     | 
| 
      
 8 
     | 
    
         
            +
              class Logger
         
     | 
| 
      
 9 
     | 
    
         
            +
                # @param stream [IO]
         
     | 
| 
      
 10 
     | 
    
         
            +
                def initialize(stream: $stdout)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @stream = stream
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                # @param message [String]
         
     | 
| 
      
 15 
     | 
    
         
            +
                def log(message = nil)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @stream.puts message
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/ra/material.rb
    ADDED
    
    | 
         @@ -0,0 +1,37 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ra
         
     | 
| 
      
 4 
     | 
    
         
            +
              # A material is used to define the properties of an object that impact the color applied. For example:
         
     | 
| 
      
 5 
     | 
    
         
            +
              #
         
     | 
| 
      
 6 
     | 
    
         
            +
              #   material = Ra::Material.new(
         
     | 
| 
      
 7 
     | 
    
         
            +
              #    base: Ra::Color.uniform(0.5),
         
     | 
| 
      
 8 
     | 
    
         
            +
              #    ambient: 0.2,
         
     | 
| 
      
 9 
     | 
    
         
            +
              #    diffuse: 0.5,
         
     | 
| 
      
 10 
     | 
    
         
            +
              #    specular: 0.7,
         
     | 
| 
      
 11 
     | 
    
         
            +
              #    shininess: 200,
         
     | 
| 
      
 12 
     | 
    
         
            +
              #   )
         
     | 
| 
      
 13 
     | 
    
         
            +
              class Material
         
     | 
| 
      
 14 
     | 
    
         
            +
                attr_accessor :base, :ambient, :diffuse, :specular, :shininess
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                # @param base [Ra::Color, Ra::Pattern:::Base]
         
     | 
| 
      
 17 
     | 
    
         
            +
                # @param ambient [Float] between 0.0 and 1.0
         
     | 
| 
      
 18 
     | 
    
         
            +
                # @param diffuse [Float] between 0.0 and 1.0
         
     | 
| 
      
 19 
     | 
    
         
            +
                # @param specular [Float] between 0.0 and 1.0
         
     | 
| 
      
 20 
     | 
    
         
            +
                # @param shininess [Numeric]
         
     | 
| 
      
 21 
     | 
    
         
            +
                def initialize(base:, ambient: 0.0, diffuse: 0.8, specular: 0.2, shininess: 80)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @base = base
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @ambient = ambient
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @diffuse = diffuse
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @specular = specular
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @shininess = shininess
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                # @param point [Vector]
         
     | 
| 
      
 30 
     | 
    
         
            +
                # @return [Ra::Color]
         
     | 
| 
      
 31 
     | 
    
         
            +
                def color(point:)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  return base if base.is_a?(Color)
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  base.color(point:)
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,14 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ra
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Pattern
         
     | 
| 
      
 5 
     | 
    
         
            +
                # An abstract pattern. Any concrete subclass of pattern must implement the method `color`.
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Base
         
     | 
| 
      
 7 
     | 
    
         
            +
                  # @param point [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 8 
     | 
    
         
            +
                  # @return [Ra::Color]
         
     | 
| 
      
 9 
     | 
    
         
            +
                  def color(point:)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    raise NotImplementedError, '#color must be implemented by a concrete subclass'
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,37 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ra
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Pattern
         
     | 
| 
      
 5 
     | 
    
         
            +
                # A checkers pattern that alternates colors using:
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                #   colors[⌊u * rows⌋ + ⌊v * cols)⌋ % colors.count]
         
     | 
| 
      
 8 
     | 
    
         
            +
                class Checkers < Base
         
     | 
| 
      
 9 
     | 
    
         
            +
                  DEFAULT_ROWS = 2
         
     | 
| 
      
 10 
     | 
    
         
            +
                  DEFAULT_COLS = 2
         
     | 
| 
      
 11 
     | 
    
         
            +
                  DEFAULT_COLORS = [
         
     | 
| 
      
 12 
     | 
    
         
            +
                    Color.black,
         
     | 
| 
      
 13 
     | 
    
         
            +
                    Color.white,
         
     | 
| 
      
 14 
     | 
    
         
            +
                  ].freeze
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  # @param rows [Integer]
         
     | 
| 
      
 17 
     | 
    
         
            +
                  # @param cols [Integer]
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # @param colors [Array<Ra::Color>]
         
     | 
| 
      
 19 
     | 
    
         
            +
                  def initialize(cols: DEFAULT_COLS, rows: DEFAULT_ROWS, colors: DEFAULT_COLORS)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 21 
     | 
    
         
            +
                    @rows = rows
         
     | 
| 
      
 22 
     | 
    
         
            +
                    @cols = cols
         
     | 
| 
      
 23 
     | 
    
         
            +
                    @colors = colors
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  # @param point [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 27 
     | 
    
         
            +
                  # @return [Ra::Color]
         
     | 
| 
      
 28 
     | 
    
         
            +
                  def color(point:)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    u = point[0]
         
     | 
| 
      
 30 
     | 
    
         
            +
                    v = point[1]
         
     | 
| 
      
 31 
     | 
    
         
            +
                    index = (u * @rows).floor + (v * @cols).floor
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                    @colors[index % @colors.count]
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,28 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ra
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Pattern
         
     | 
| 
      
 5 
     | 
    
         
            +
                # A graident pattern from `color_a` to `color_b` using:
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                #   color_b + (color_b - color_a) * (u + v) / 2
         
     | 
| 
      
 8 
     | 
    
         
            +
                class Gradient < Base
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # @param color_a [Ra::Color]
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # @param color_b [Ra::Color]
         
     | 
| 
      
 11 
     | 
    
         
            +
                  def initialize(color_a:, color_b:)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @color_a = color_a
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @color_b = color_b
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  # @param point [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # @return [Ra::Color]
         
     | 
| 
      
 19 
     | 
    
         
            +
                  def color(point:)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    u = point[0]
         
     | 
| 
      
 21 
     | 
    
         
            +
                    v = point[1]
         
     | 
| 
      
 22 
     | 
    
         
            +
                    value = (u + v) / 2
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    @color_a + ((@color_b - @color_a) * value)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ra
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Pattern
         
     | 
| 
      
 5 
     | 
    
         
            +
                # A rings pattern that alternates colors using:
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                #   colors[⌊√(u² + v²)⌋]
         
     | 
| 
      
 8 
     | 
    
         
            +
                class Rings < Base
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # @param colors [Array<Ra::Color>]
         
     | 
| 
      
 10 
     | 
    
         
            +
                  def initialize(colors:)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @colors = colors
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  # @param point [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 16 
     | 
    
         
            +
                  # @return [Ra::Color]
         
     | 
| 
      
 17 
     | 
    
         
            +
                  def color(point:)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    u = point[0]
         
     | 
| 
      
 19 
     | 
    
         
            +
                    v = point[1]
         
     | 
| 
      
 20 
     | 
    
         
            +
                    index = Math.sqrt((u**2) + (v**2)).floor
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                    @colors[index % @colors.count]
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ra
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Pattern
         
     | 
| 
      
 5 
     | 
    
         
            +
                # A stripe pattern that alternates colors using:
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                #   colors[⌊point.x⌋]
         
     | 
| 
      
 8 
     | 
    
         
            +
                class Stripes < Base
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # @param colors [Array<Ra::Color>]
         
     | 
| 
      
 10 
     | 
    
         
            +
                  def initialize(colors:)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @colors = colors
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  # @param point [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 16 
     | 
    
         
            +
                  # @return [Ra::Color]
         
     | 
| 
      
 17 
     | 
    
         
            +
                  def color(point:)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    count = @colors.count
         
     | 
| 
      
 19 
     | 
    
         
            +
                    u = point[0]
         
     | 
| 
      
 20 
     | 
    
         
            +
                    v = point[1]
         
     | 
| 
      
 21 
     | 
    
         
            +
                    value = (u + v) * (2 * count)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    @colors[value.floor % count]
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,52 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'mini_magick'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Ra
         
     | 
| 
      
 6 
     | 
    
         
            +
              module Pattern
         
     | 
| 
      
 7 
     | 
    
         
            +
                # A texture that can load an AVIF / JPG / PNG / BMP:
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                #   colors[⌊point.x⌋]
         
     | 
| 
      
 10 
     | 
    
         
            +
                class Texture < Base
         
     | 
| 
      
 11 
     | 
    
         
            +
                  # @param path [Pathname]
         
     | 
| 
      
 12 
     | 
    
         
            +
                  def initialize(path:)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @path = path
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  # @param point [Array<Numeric,Numeric>] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # @return [Ra::Color]
         
     | 
| 
      
 19 
     | 
    
         
            +
                  def color(point:)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    pixel = pixel(point:)
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                    Color.new(
         
     | 
| 
      
 23 
     | 
    
         
            +
                      r: Float(pixel[0]) / Color::PRECISION,
         
     | 
| 
      
 24 
     | 
    
         
            +
                      g: Float(pixel[1]) / Color::PRECISION,
         
     | 
| 
      
 25 
     | 
    
         
            +
                      b: Float(pixel[2]) / Color::PRECISION,
         
     | 
| 
      
 26 
     | 
    
         
            +
                    )
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  private
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  # @return [Array<0..255,0..255,0..255>]
         
     | 
| 
      
 32 
     | 
    
         
            +
                  def pixel(point:)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    u = point[0]
         
     | 
| 
      
 34 
     | 
    
         
            +
                    v = 1.0 - point[1]
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                    x = (u * image.width.pred).round
         
     | 
| 
      
 37 
     | 
    
         
            +
                    y = (v * image.height.pred).round
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    pixels[y][x]
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  # @return [Array<Array<0..255,0..255,0.255>>]
         
     | 
| 
      
 43 
     | 
    
         
            +
                  def pixels
         
     | 
| 
      
 44 
     | 
    
         
            +
                    @pixels ||= image.get_pixels
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  def image
         
     | 
| 
      
 48 
     | 
    
         
            +
                    @image ||= MiniMagick::Image.open(@path)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
              end
         
     | 
| 
      
 52 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/ra/ray.rb
    ADDED
    
    | 
         @@ -0,0 +1,53 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ra
         
     | 
| 
      
 4 
     | 
    
         
            +
              # A ray is positioned at an origin travelling by a direction. A ray is cast and used to identify collisions with
         
     | 
| 
      
 5 
     | 
    
         
            +
              # objects. For example:
         
     | 
| 
      
 6 
     | 
    
         
            +
              #
         
     | 
| 
      
 7 
     | 
    
         
            +
              #   ray = Ra::Ray.new(
         
     | 
| 
      
 8 
     | 
    
         
            +
              #     origin: Vector[0, 0, 0, Ra::Tuple::POINT],
         
     | 
| 
      
 9 
     | 
    
         
            +
              #     direction: Vector[1, 2, 3, Ra::Tuple::VECTOR],
         
     | 
| 
      
 10 
     | 
    
         
            +
              #   )
         
     | 
| 
      
 11 
     | 
    
         
            +
              #   ray.position(t: 1) == Vector[1, 2, 3, Ra::Tuple::VECTOR]
         
     | 
| 
      
 12 
     | 
    
         
            +
              #   ray.position(t: 2) == Vector[2, 4, 6, Ra::Tuple::VECTOR]
         
     | 
| 
      
 13 
     | 
    
         
            +
              #   ray.position(t: 3) == Vector[3, 6, 9, Ra::Tuple::VECTOR]
         
     | 
| 
      
 14 
     | 
    
         
            +
              #
         
     | 
| 
      
 15 
     | 
    
         
            +
              # A ray can be transformed. This is useful when considering the ray relative to an object that has a transform
         
     | 
| 
      
 16 
     | 
    
         
            +
              # associated with it. For example:
         
     | 
| 
      
 17 
     | 
    
         
            +
              #
         
     | 
| 
      
 18 
     | 
    
         
            +
              #   ray = Ra::Ray.new(
         
     | 
| 
      
 19 
     | 
    
         
            +
              #     origin: Vector[0, 0, 0, Ra::Tuple::POINT],
         
     | 
| 
      
 20 
     | 
    
         
            +
              #     direction: Vector[1, 2, 3, Ra::Tuple::VECTOR],
         
     | 
| 
      
 21 
     | 
    
         
            +
              #   )
         
     | 
| 
      
 22 
     | 
    
         
            +
              #   ray.transform(transform: Ra::Transform.scale(1, 2, 3))
         
     | 
| 
      
 23 
     | 
    
         
            +
              class Ray
         
     | 
| 
      
 24 
     | 
    
         
            +
                attr_accessor :origin, :direction
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                # @param origin [Vector] e.g. Vector[1, 2, 3, Ra::Tuple::POINT]
         
     | 
| 
      
 27 
     | 
    
         
            +
                # @param direction [Vector] e.g. Vector[1, 2, 3, Ra::Tuple::VECTOR]
         
     | 
| 
      
 28 
     | 
    
         
            +
                def initialize(origin:, direction:)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @origin = origin
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @direction = direction
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                # @param t [Numeric]
         
     | 
| 
      
 34 
     | 
    
         
            +
                # @return [Vector]
         
     | 
| 
      
 35 
     | 
    
         
            +
                def position(t:)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  @origin + (@direction * t)
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                # @param transform [Ra::Transform]
         
     | 
| 
      
 40 
     | 
    
         
            +
                # @return [Ra::Ray]
         
     | 
| 
      
 41 
     | 
    
         
            +
                def transform(transform)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  self.class.new(
         
     | 
| 
      
 43 
     | 
    
         
            +
                    origin: transform * @origin,
         
     | 
| 
      
 44 
     | 
    
         
            +
                    direction: transform * @direction,
         
     | 
| 
      
 45 
     | 
    
         
            +
                  )
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
      
 49 
     | 
    
         
            +
                def ==(other)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  origin == other.origin && direction == other.direction
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
              end
         
     | 
| 
      
 53 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,58 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ra
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Shape
         
     | 
| 
      
 5 
     | 
    
         
            +
                # An abstract shape. Any concrete subclass of shape must implement the
         
     | 
| 
      
 6 
     | 
    
         
            +
                # methods `l_normal` and `t_intersect`. Both methods use a point / ray
         
     | 
| 
      
 7 
     | 
    
         
            +
                # with a local transform applied.
         
     | 
| 
      
 8 
     | 
    
         
            +
                class Base
         
     | 
| 
      
 9 
     | 
    
         
            +
                  attr_accessor :material
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  # @param material [Ra::Material]
         
     | 
| 
      
 12 
     | 
    
         
            +
                  # @param transform [Ra::Matrix]
         
     | 
| 
      
 13 
     | 
    
         
            +
                  def initialize(material:, transform: Transform::IDENTITY)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @material = material
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @transform = transform
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  # @param ray [Ra::Ray]
         
     | 
| 
      
 19 
     | 
    
         
            +
                  # @return [Array<Ra::Intersection>]
         
     | 
| 
      
 20 
     | 
    
         
            +
                  def intersect(ray:)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    t_intersect(ray: ray.transform(@transform.inverse))
         
     | 
| 
      
 22 
     | 
    
         
            +
                      .map { |t| Ra::Intersection.new(ray:, shape: self, t:) }
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  # @param point [Vector] <x, y, z, Tuple::POINT>
         
     | 
| 
      
 26 
     | 
    
         
            +
                  # @return [Vector] <x, y, z, Tuple::POINT>
         
     | 
| 
      
 27 
     | 
    
         
            +
                  def normal(point:)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    normal = @transform.inverse.transpose * l_normal(point: @transform.inverse * point)
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                    Vector[normal[0], normal[1], normal[2], Ra::Tuple::VECTOR].normalize
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  # @param point [Vector] <x, y, z, Tuple::POINT>
         
     | 
| 
      
 34 
     | 
    
         
            +
                  # @return [Color]
         
     | 
| 
      
 35 
     | 
    
         
            +
                  def color(point:)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    @material.color(point: uv_point(point: @transform.inverse * point))
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  # @param point [Vector] <x, y, z, Tuple::POINT>
         
     | 
| 
      
 40 
     | 
    
         
            +
                  # @return [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 41 
     | 
    
         
            +
                  def uv_point(point:)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    raise NotImplementedError, '#uv_point must be implemented by a concrete subclass'
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  # @param ray [Ra::Ray] local
         
     | 
| 
      
 46 
     | 
    
         
            +
                  # @return [Array<Intersection>]
         
     | 
| 
      
 47 
     | 
    
         
            +
                  def t_intersect(ray:)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    raise NotImplementedError, '#t_intersect must be implemented by a concrete subclass'
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  # @param point [Vector] local
         
     | 
| 
      
 52 
     | 
    
         
            +
                  # @return [Vector]
         
     | 
| 
      
 53 
     | 
    
         
            +
                  def l_normal(point:)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    raise NotImplementedError, '#l_normal must be implemented by a concrete subclass'
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
              end
         
     | 
| 
      
 58 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,155 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ra
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Shape
         
     | 
| 
      
 5 
     | 
    
         
            +
                # A cube centered at <0,0,0> with sides of l=2. A cube surface is defined:
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                #   x between (-1..+1)
         
     | 
| 
      
 8 
     | 
    
         
            +
                #   y between (-1..+1)
         
     | 
| 
      
 9 
     | 
    
         
            +
                #   z between (-1..+1)
         
     | 
| 
      
 10 
     | 
    
         
            +
                #   x = ±1 OR y = ±1 OR z = ±1
         
     | 
| 
      
 11 
     | 
    
         
            +
                #
         
     | 
| 
      
 12 
     | 
    
         
            +
                # A ray `x` / `y` / `z` values at `t` use the `origin` and `direction`:
         
     | 
| 
      
 13 
     | 
    
         
            +
                #
         
     | 
| 
      
 14 
     | 
    
         
            +
                #   x = origin.x + direction.x * t
         
     | 
| 
      
 15 
     | 
    
         
            +
                #   y = origin.y + direction.y * t
         
     | 
| 
      
 16 
     | 
    
         
            +
                #   z = origin.z + direction.z * t
         
     | 
| 
      
 17 
     | 
    
         
            +
                #
         
     | 
| 
      
 18 
     | 
    
         
            +
                # The ray therefore may intersect when:
         
     | 
| 
      
 19 
     | 
    
         
            +
                #
         
     | 
| 
      
 20 
     | 
    
         
            +
                #   origin.x + direction.x * t = ±1 OR origin.y + direction.y * t = ±1 OR origin.z + direction.z * t = ±1
         
     | 
| 
      
 21 
     | 
    
         
            +
                #
         
     | 
| 
      
 22 
     | 
    
         
            +
                # Thus 6 planes can be checked for intersect.
         
     | 
| 
      
 23 
     | 
    
         
            +
                class Cube < Base
         
     | 
| 
      
 24 
     | 
    
         
            +
                  # @param point [Vector] <x, y, z, Tuple::POINT>
         
     | 
| 
      
 25 
     | 
    
         
            +
                  # @return [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 26 
     | 
    
         
            +
                  def uv_point(point:)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    x = point[0]
         
     | 
| 
      
 28 
     | 
    
         
            +
                    y = point[1]
         
     | 
| 
      
 29 
     | 
    
         
            +
                    z = point[2]
         
     | 
| 
      
 30 
     | 
    
         
            +
                    value = [x, y, z].max_by(&:abs)
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                    case value
         
     | 
| 
      
 33 
     | 
    
         
            +
                    when x then x.positive? ? uv_point_r(point:) : uv_point_l(point:)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    when y then y.positive? ? uv_point_u(point:) : uv_point_d(point:)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    else        z.positive? ? uv_point_f(point:) : uv_point_b(point:)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  # @param ray [Ra::Ray] local
         
     | 
| 
      
 40 
     | 
    
         
            +
                  # @return [Array<Numeric>]
         
     | 
| 
      
 41 
     | 
    
         
            +
                  def t_intersect(ray:)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    t_min = t_min(ray:)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    t_max = t_max(ray:)
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    return [] if !t_min || !t_max || t_min > t_max
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    [
         
     | 
| 
      
 48 
     | 
    
         
            +
                      t_min,
         
     | 
| 
      
 49 
     | 
    
         
            +
                      t_max,
         
     | 
| 
      
 50 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                  # @param point [Vector]
         
     | 
| 
      
 54 
     | 
    
         
            +
                  # @return [Ra::Tuple]
         
     | 
| 
      
 55 
     | 
    
         
            +
                  def l_normal(point:)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    x = point[0].abs
         
     | 
| 
      
 57 
     | 
    
         
            +
                    y = point[1].abs
         
     | 
| 
      
 58 
     | 
    
         
            +
                    z = point[2].abs
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                    Vector[
         
     | 
| 
      
 61 
     | 
    
         
            +
                      (is_x = x > y && x > z) ? 1 : 0,
         
     | 
| 
      
 62 
     | 
    
         
            +
                      (is_y = y > x && y > z) ? 1 : 0,
         
     | 
| 
      
 63 
     | 
    
         
            +
                      is_x || is_y ? 0 : 1,
         
     | 
| 
      
 64 
     | 
    
         
            +
                      Ra::Tuple::VECTOR
         
     | 
| 
      
 65 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  private
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  # @param ray [Ra::Ray]
         
     | 
| 
      
 71 
     | 
    
         
            +
                  # @return [Integer]
         
     | 
| 
      
 72 
     | 
    
         
            +
                  def t_min(ray:)
         
     | 
| 
      
 73 
     | 
    
         
            +
                    (0..2).map { |i| t_min_max(ray.origin[i], ray.direction[i]).min }.max
         
     | 
| 
      
 74 
     | 
    
         
            +
                  end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                  # @param ray [Ra::Ray]
         
     | 
| 
      
 77 
     | 
    
         
            +
                  # @return [Integer]
         
     | 
| 
      
 78 
     | 
    
         
            +
                  def t_max(ray:)
         
     | 
| 
      
 79 
     | 
    
         
            +
                    (0..2).map { |i| t_min_max(ray.origin[i], ray.direction[i]).max }.min
         
     | 
| 
      
 80 
     | 
    
         
            +
                  end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                  # @param origin [Numeric]
         
     | 
| 
      
 83 
     | 
    
         
            +
                  # @param direction [Numeric]
         
     | 
| 
      
 84 
     | 
    
         
            +
                  # @return [Array<Numeric,Numeric>]
         
     | 
| 
      
 85 
     | 
    
         
            +
                  def t_min_max(origin, direction)
         
     | 
| 
      
 86 
     | 
    
         
            +
                    t_min_numerator = -1 - origin
         
     | 
| 
      
 87 
     | 
    
         
            +
                    t_max_numerator = +1 - origin
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                    if direction.abs < EPSILON
         
     | 
| 
      
 90 
     | 
    
         
            +
                      t_min = t_min_numerator * Float::INFINITY
         
     | 
| 
      
 91 
     | 
    
         
            +
                      t_max = t_max_numerator * Float::INFINITY
         
     | 
| 
      
 92 
     | 
    
         
            +
                    else
         
     | 
| 
      
 93 
     | 
    
         
            +
                      t_min = t_min_numerator / direction
         
     | 
| 
      
 94 
     | 
    
         
            +
                      t_max = t_max_numerator / direction
         
     | 
| 
      
 95 
     | 
    
         
            +
                    end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                    t_min < t_max ? [t_min, t_max] : [t_max, t_min]
         
     | 
| 
      
 98 
     | 
    
         
            +
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                  # @param point [Vector] <x, y, z, Tuple::POINT>
         
     | 
| 
      
 101 
     | 
    
         
            +
                  # @return [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 102 
     | 
    
         
            +
                  def uv_point_u(point:)
         
     | 
| 
      
 103 
     | 
    
         
            +
                    Vector[
         
     | 
| 
      
 104 
     | 
    
         
            +
                      ((point[0] + 1) % 2) / 2,
         
     | 
| 
      
 105 
     | 
    
         
            +
                      ((1 - point[2]) % 2) / 2,
         
     | 
| 
      
 106 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 107 
     | 
    
         
            +
                  end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                  # @param point [Vector] <x, y, z, Tuple::POINT>
         
     | 
| 
      
 110 
     | 
    
         
            +
                  # @return [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 111 
     | 
    
         
            +
                  def uv_point_d(point:)
         
     | 
| 
      
 112 
     | 
    
         
            +
                    Vector[
         
     | 
| 
      
 113 
     | 
    
         
            +
                      ((point[0] + 1) % 2) / 2,
         
     | 
| 
      
 114 
     | 
    
         
            +
                      ((point[2] + 1) % 2) / 2,
         
     | 
| 
      
 115 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 116 
     | 
    
         
            +
                  end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                  # @param point [Vector] <x, y, z, Tuple::POINT>
         
     | 
| 
      
 119 
     | 
    
         
            +
                  # @return [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 120 
     | 
    
         
            +
                  def uv_point_l(point:)
         
     | 
| 
      
 121 
     | 
    
         
            +
                    Vector[
         
     | 
| 
      
 122 
     | 
    
         
            +
                      ((point[2] + 1) % 2) / 2,
         
     | 
| 
      
 123 
     | 
    
         
            +
                      ((point[1] + 1) % 2) / 2,
         
     | 
| 
      
 124 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 125 
     | 
    
         
            +
                  end
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                  # @param point [Vector] <x, y, z, Tuple::POINT>
         
     | 
| 
      
 128 
     | 
    
         
            +
                  # @return [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 129 
     | 
    
         
            +
                  def uv_point_r(point:)
         
     | 
| 
      
 130 
     | 
    
         
            +
                    Vector[
         
     | 
| 
      
 131 
     | 
    
         
            +
                      ((1 - point[2]) % 2) / 2,
         
     | 
| 
      
 132 
     | 
    
         
            +
                      ((point[1] + 1) % 2) / 2,
         
     | 
| 
      
 133 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 134 
     | 
    
         
            +
                  end
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                  # @param point [Vector] <x, y, z, Tuple::POINT>
         
     | 
| 
      
 137 
     | 
    
         
            +
                  # @return [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 138 
     | 
    
         
            +
                  def uv_point_b(point:)
         
     | 
| 
      
 139 
     | 
    
         
            +
                    Vector[
         
     | 
| 
      
 140 
     | 
    
         
            +
                      ((1 - point[0]) % 2) / 2,
         
     | 
| 
      
 141 
     | 
    
         
            +
                      ((point[1] + 1) % 2) / 2,
         
     | 
| 
      
 142 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 143 
     | 
    
         
            +
                  end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                  # @param point [Vector] <x, y, z, Tuple::POINT>
         
     | 
| 
      
 146 
     | 
    
         
            +
                  # @return [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 147 
     | 
    
         
            +
                  def uv_point_f(point:)
         
     | 
| 
      
 148 
     | 
    
         
            +
                    Vector[
         
     | 
| 
      
 149 
     | 
    
         
            +
                      ((point[0] + 1) % 2) / 2,
         
     | 
| 
      
 150 
     | 
    
         
            +
                      ((point[1] + 1) % 2) / 2,
         
     | 
| 
      
 151 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 152 
     | 
    
         
            +
                  end
         
     | 
| 
      
 153 
     | 
    
         
            +
                end
         
     | 
| 
      
 154 
     | 
    
         
            +
              end
         
     | 
| 
      
 155 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,52 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ra
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Shape
         
     | 
| 
      
 5 
     | 
    
         
            +
                # A plane for all x / z where y = 0. A plane surface is defined:
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                #   y = 0
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                # A ray `x` / `y` / `z` values at `t` use the `origin` and `direction`:
         
     | 
| 
      
 10 
     | 
    
         
            +
                #
         
     | 
| 
      
 11 
     | 
    
         
            +
                #   x = origin.x + direction.x * t
         
     | 
| 
      
 12 
     | 
    
         
            +
                #   y = origin.y + direction.y * t
         
     | 
| 
      
 13 
     | 
    
         
            +
                #   z = origin.z + direction.z * t
         
     | 
| 
      
 14 
     | 
    
         
            +
                #
         
     | 
| 
      
 15 
     | 
    
         
            +
                # Therefore, a plane has a single intersection at:
         
     | 
| 
      
 16 
     | 
    
         
            +
                #
         
     | 
| 
      
 17 
     | 
    
         
            +
                #   t = -origin.y / direction.y
         
     | 
| 
      
 18 
     | 
    
         
            +
                #
         
     | 
| 
      
 19 
     | 
    
         
            +
                # A direction.y < EPISLON indicates the ray does not intersect the plane.
         
     | 
| 
      
 20 
     | 
    
         
            +
                class Plane < Base
         
     | 
| 
      
 21 
     | 
    
         
            +
                  # @param point [Vector] <x, y, z, Tuple::POINT>
         
     | 
| 
      
 22 
     | 
    
         
            +
                  # @return [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 23 
     | 
    
         
            +
                  def uv_point(point:)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    Vector[
         
     | 
| 
      
 25 
     | 
    
         
            +
                      point[0] % 1, # u = x % 1
         
     | 
| 
      
 26 
     | 
    
         
            +
                      point[2] % 1, # v = y % 2
         
     | 
| 
      
 27 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  # @param ray [Ra::Ray] local
         
     | 
| 
      
 31 
     | 
    
         
            +
                  # @return [Array<Numeric>]
         
     | 
| 
      
 32 
     | 
    
         
            +
                  def t_intersect(ray:)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    origin_y = ray.origin[1]
         
     | 
| 
      
 34 
     | 
    
         
            +
                    direction_y = ray.direction[1]
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                    return [] if direction_y.abs < EPSILON
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                    [-origin_y / direction_y]
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  # @return [Ra::Tuple]
         
     | 
| 
      
 42 
     | 
    
         
            +
                  def l_normal(*)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    Vector[
         
     | 
| 
      
 44 
     | 
    
         
            +
                      0,
         
     | 
| 
      
 45 
     | 
    
         
            +
                      1,
         
     | 
| 
      
 46 
     | 
    
         
            +
                      0,
         
     | 
| 
      
 47 
     | 
    
         
            +
                      Ra::Tuple::VECTOR
         
     | 
| 
      
 48 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
              end
         
     | 
| 
      
 52 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,90 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ra
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Shape
         
     | 
| 
      
 5 
     | 
    
         
            +
                # A sphere at origin <0,0,0> with a radius 1. A sphere surface is defined:
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                #   x² + y² + z² = radius²
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                # A unit radius simplifies further:
         
     | 
| 
      
 10 
     | 
    
         
            +
                #
         
     | 
| 
      
 11 
     | 
    
         
            +
                #   x² + y² + z² = 1
         
     | 
| 
      
 12 
     | 
    
         
            +
                #
         
     | 
| 
      
 13 
     | 
    
         
            +
                # A ray `x` / `y` / `z` values at `t` use the `origin` and `direction`:
         
     | 
| 
      
 14 
     | 
    
         
            +
                #
         
     | 
| 
      
 15 
     | 
    
         
            +
                #   x = origin.x + direction.x * t
         
     | 
| 
      
 16 
     | 
    
         
            +
                #   y = origin.y + direction.y * t
         
     | 
| 
      
 17 
     | 
    
         
            +
                #   z = origin.z + direction.z * t
         
     | 
| 
      
 18 
     | 
    
         
            +
                #
         
     | 
| 
      
 19 
     | 
    
         
            +
                # Substituting `x` / `y` / `z` allows for solving for `t`:
         
     | 
| 
      
 20 
     | 
    
         
            +
                #
         
     | 
| 
      
 21 
     | 
    
         
            +
                #   1 = (origin.x + direction.x * t)²
         
     | 
| 
      
 22 
     | 
    
         
            +
                #     + (origin.y + direction.y * t)²
         
     | 
| 
      
 23 
     | 
    
         
            +
                #     + (origin.z + direction.z * t)²
         
     | 
| 
      
 24 
     | 
    
         
            +
                #
         
     | 
| 
      
 25 
     | 
    
         
            +
                # Simplifying gives us a quadratic formula with terms defined as:
         
     | 
| 
      
 26 
     | 
    
         
            +
                #
         
     | 
| 
      
 27 
     | 
    
         
            +
                #   a = direction.x² + direction.y² + direction.z²
         
     | 
| 
      
 28 
     | 
    
         
            +
                #   b = 2 * ((origin.x * direction.x) + (origin.y * direction.y) + (origin.z * direction.z))
         
     | 
| 
      
 29 
     | 
    
         
            +
                #   c = origin.x² + origin.y² + origin.z² - 1
         
     | 
| 
      
 30 
     | 
    
         
            +
                #   discriminant = b² - 4ac
         
     | 
| 
      
 31 
     | 
    
         
            +
                #   t = (-b ± √discriminant) / (2a)
         
     | 
| 
      
 32 
     | 
    
         
            +
                #
         
     | 
| 
      
 33 
     | 
    
         
            +
                # A discriminant <0 indicates the ray does not intersect the sphere.
         
     | 
| 
      
 34 
     | 
    
         
            +
                class Sphere < Base
         
     | 
| 
      
 35 
     | 
    
         
            +
                  # @param point [Vector] <x, y, z, Tuple::POINT>
         
     | 
| 
      
 36 
     | 
    
         
            +
                  # @return [Vector] <u = 0.0..1.0, v = 0.0..1.0>
         
     | 
| 
      
 37 
     | 
    
         
            +
                  def uv_point(point:)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    x = point[0]
         
     | 
| 
      
 39 
     | 
    
         
            +
                    y = point[1]
         
     | 
| 
      
 40 
     | 
    
         
            +
                    z = point[2]
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                    radius = Vector[x, y, z].magnitude
         
     | 
| 
      
 43 
     | 
    
         
            +
                    theta = Math.atan2(x, z)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    phi = Math.acos(y / radius)
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                    u = 1 - ((theta / (2 * Math::PI)) + 0.5)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    v = 1 - (phi / Math::PI)
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                    Vector[u, v]
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  # @param ray [Ra::Ray] local
         
     | 
| 
      
 53 
     | 
    
         
            +
                  # @return [Array<Numeric>]
         
     | 
| 
      
 54 
     | 
    
         
            +
                  def t_intersect(ray:)
         
     | 
| 
      
 55 
     | 
    
         
            +
                    origin = ray.origin - Vector[0, 0, 0, Ra::Tuple::POINT]
         
     | 
| 
      
 56 
     | 
    
         
            +
                    direction = ray.direction
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                    quadratic(
         
     | 
| 
      
 59 
     | 
    
         
            +
                      a: direction.dot(direction),
         
     | 
| 
      
 60 
     | 
    
         
            +
                      b: 2 * direction.dot(origin),
         
     | 
| 
      
 61 
     | 
    
         
            +
                      c: origin.dot(origin) - 1,
         
     | 
| 
      
 62 
     | 
    
         
            +
                    )
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  # @param point [Vector]
         
     | 
| 
      
 66 
     | 
    
         
            +
                  # @return [Ra::Tuple]
         
     | 
| 
      
 67 
     | 
    
         
            +
                  def l_normal(point:)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    point - Vector[0, 0, 0, Ra::Tuple::VECTOR]
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  private
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  # (-b ± √(b² - 4ac)) / (2a)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  #
         
     | 
| 
      
 75 
     | 
    
         
            +
                  # @param a [Numeric]
         
     | 
| 
      
 76 
     | 
    
         
            +
                  # @param b [Numeric]
         
     | 
| 
      
 77 
     | 
    
         
            +
                  # @param c [Numeric]
         
     | 
| 
      
 78 
     | 
    
         
            +
                  # @return [Array<Numeric>]
         
     | 
| 
      
 79 
     | 
    
         
            +
                  def quadratic(a:, b:, c:)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    discriminant = (b**2) - (4 * a * c)
         
     | 
| 
      
 81 
     | 
    
         
            +
                    return [] if discriminant.negative?
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                    [
         
     | 
| 
      
 84 
     | 
    
         
            +
                      (-b - Math.sqrt(discriminant)) / (2 * a),
         
     | 
| 
      
 85 
     | 
    
         
            +
                      (-b + Math.sqrt(discriminant)) / (2 * a),
         
     | 
| 
      
 86 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
      
 89 
     | 
    
         
            +
              end
         
     | 
| 
      
 90 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/ra/surface.rb
    ADDED
    
    | 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Ra
         
     | 
| 
      
 4 
     | 
    
         
            +
              # A surface contains everything needed to apply lighting.
         
     | 
| 
      
 5 
     | 
    
         
            +
              class Surface
         
     | 
| 
      
 6 
     | 
    
         
            +
                attr_accessor :eyev, :normalv, :shape, :point
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                # @param eyev [Vector]
         
     | 
| 
      
 9 
     | 
    
         
            +
                # @param normalv [Vector]
         
     | 
| 
      
 10 
     | 
    
         
            +
                # @param shape [Ra::Shape]
         
     | 
| 
      
 11 
     | 
    
         
            +
                # @param point [Vector]
         
     | 
| 
      
 12 
     | 
    
         
            +
                def initialize(eyev:, normalv:, shape:, point:)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @eyev = eyev
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @normalv = normalv.dot(eyev).negative? ? -normalv : +normalv
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @shape = shape
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @point = point
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                # @return [Vector]
         
     | 
| 
      
 20 
     | 
    
         
            +
                def hpoint
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @hpoint ||= point + (normalv * EPSILON)
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     |