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