raytracing/in_one_weekend/src/material.zig

87 lines
2.7 KiB
Zig

const std = @import("std");
const Random = std.rand.Random;
const math = std.math;
const vec3 = @import("vec3.zig");
const Color = @import("color.zig").Color;
const Ray = @import("ray.zig").Ray;
const HitRecord = @import("hittable.zig").HitRecord;
pub const MaterialType = enum {
lambertian,
metal,
dielectric,
};
pub const Lambertian = struct {
color: Color,
pub fn scatter(self: Lambertian, ray: Ray, hit: HitRecord, rng: *Random) ?ScatteredRay {
var scatterDirection = hit.normal.add(vec3.random_unit_vector(rng));
// Catch degenerate scatter direction
if (scatterDirection.nearZero()) {
scatterDirection = hit.normal;
}
return ScatteredRay{
.ray = Ray{ .origin = hit.p, .direction = scatterDirection},
.color = self.color,
};
}
};
pub const Metal = struct {
color: Color,
fuzz: f32,
pub fn scatter(self: Metal, ray: Ray, hit: HitRecord, rng: *Random) ?ScatteredRay {
const reflected = ray.direction.unit().reflect(hit.normal);
const scattered = Ray{ .origin = hit.p, .direction = reflected.add(vec3.random_in_unit_sphere(rng).mul_s(self.fuzz)) };
if (scattered.direction.dot(hit.normal) <= 0) {
return null;
}
return ScatteredRay{
.ray = scattered,
.color = self.color,
};
}
};
pub const Dielectric = struct {
refraction_index: f32,
pub fn scatter(self: Dielectric, ray: Ray, hit: HitRecord, rng: *Random) ?ScatteredRay {
const attenuation = Color{ .x = 1.0, .y = 1.0, .z = 1.0 };
const refraction_ratio = if (hit.front_face) 1.0 / self.refraction_index else self.refraction_index;
const unit_direction = ray.direction.unit();
const cos_theta = math.min(unit_direction.mul_s(-1).dot(hit.normal), 1.0);
const sin_theta = math.sqrt(1.0 - cos_theta * cos_theta);
const cant_refract = refraction_ratio * sin_theta > 1.0;
const direction = if (cant_refract) unit_direction.reflect(hit.normal)
else unit_direction.refract(hit.normal, refraction_ratio);
return ScatteredRay{
.ray = Ray{ .origin = hit.p, .direction = direction},
.color = attenuation,
};
}
};
pub const ScatteredRay = struct {
ray: Ray,
color: Color,
};
pub const Material = union(MaterialType) {
lambertian: Lambertian,
metal: Metal,
dielectric: Dielectric,
};
pub fn scatter(ray: Ray, hit: HitRecord, rng: *Random) ?ScatteredRay {
return switch (hit.material) {
.lambertian => |m| m.scatter(ray, hit, rng),
.metal => |m| m.scatter(ray, hit, rng),
.dielectric => |m| m.scatter(ray, hit, rng),
};
}