aboutsummaryrefslogtreecommitdiff
path: root/lib/camera.rb
blob: 3ddf7692f377c87d977b7d2670b8f653af16a4de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
class Camera
  def initialize(width = 100, aspect = 1.0,
                 aliasing: 10, depth: 10, vfov: 90.0,
                 lookfrom: Point.new(0, 0, 0),
                 lookat: Point.new(0, 0, -1),
                 vup: Vec3.new(0, 1, 0),
                 defocus_angle: 0, focus_dist: 10)
    @width = width
    @aliasing = aliasing
    @depth = depth

    # Height
    @height = (@width / aspect).to_i
    @height = @height < 1 ? 1 : @height

    # Camera
    @c_centre = lookfrom

    # Viewport
    theta = vfov * Maths::PI / 180
    h = Maths.tan(theta / 2)
    v_height = 2.0 * h * focus_dist
    v_width = (v_height * @width) / @height

    # Camera basis vectors
    w = (lookfrom - lookat).unit
    u = vup.cross(w).unit
    v = w.cross(u)

    v_u = u * v_width
    v_v = -v * v_height

    # Pixel deltas
    @pd_u = v_u / @width
    @pd_v = v_v / @height

    # Upper left pixel
    v_upperleft = @c_centre - v_u / 2 - v_v / 2 -
                  w * focus_dist
    @p00_loc = v_upperleft + (@pd_u + @pd_v) / 2

    @defocus_angle = defocus_angle
    defocus_radius = focus_dist * Maths.tan((@defocus_angle / 2) *
                                            Maths::PI / 180)
    @defocus_disk_u = u * defocus_radius
    @defocus_disk_v = v * defocus_radius
  end

  def render(world)
    start = Time.now.to_f

    puts "P3"
    puts "#{@width} #{@height}"
    puts "255"

    @height.times do |row|
      $stderr.print "Scanlines remaining: %4d" % (@height - row)
      @width.times do |col|
        p_colour = Colour.new
        @aliasing.times do
          p_colour += get_ray(row, col).colour(world, @depth)
        end
        puts (p_colour / @aliasing).to_ppm
      end
      puts
    end

    finish = Time.now.to_f
    took = finish - start

    $stderr.puts "Done. Took %4.2f seconds. " % took
  end

  def get_ray(row, col)
    p_sample = @p00_loc +
               @pd_u * (col + rand - 0.5) +
               @pd_v * (row + rand - 0.5)

    ray_origin = @defocus_angle <= 0 ? @c_centre : defocus_disk_sample
    ray_dir = p_sample - ray_origin
    Ray.new(ray_origin, ray_dir)
  end

  def defocus_disk_sample
    p = Vec3.random_in_unit(dimensions: 2)
    @c_centre + @defocus_disk_u * p.x + @defocus_disk_v * p.y
  end
end