class Material def initialize(r, g, b) @albedo = Colour.new(r, g, b) end def attenuation @albedo end def scatter(ray, record) nil end end class Lambertian < Material def scatter(ray, record) scat = record.normal + Vec3.random_unit scat = rec.normal if scat.near_zero? Ray.new(record.point, scat) end end class Metal < Material def initialize(r, g, b, fuzz) @fuzz = fuzz < 1 ? fuzz : 1 super(r, g, b) end def scatter(ray, record) refl = ray.direction.reflect(record.normal) refl = refl.unit + (Vec3.random_unit * @fuzz) if refl.dot(record.normal) > 0 Ray.new(record.point, refl) else nil end end end class Dielectric < Material def initialize(ref_index) @ref_index = ref_index end def attenuation Colour.new(1.0, 1.0, 1.0) end def scatter(ray, record) ri = record.front_face ? (1.0 / @ref_index) : @ref_index unit_dir = ray.direction.unit costheta = [(-unit_dir).dot(record.normal), 1.0].min sintheta = (1.0 - costheta ** 2) ** 0.5 cannot_refract = ri * sintheta > 1.0 maybe_reflect_anyway = Dielectric.reflectance(costheta, ri) > rand refr = cannot_refract || maybe_reflect_anyway ? unit_dir.reflect(record.normal) : unit_dir.refract(record.normal, ri) Ray.new(record.point, refr) end def self.reflectance(costheta, ri) r0 = ((1.0 - ri) / (1.0 + ri)) ** 2 r0 + (1.0 - r0) * (1.0 - costheta) ** 5 end end