diff options
Diffstat (limited to 'src/rtw')
| -rw-r--r-- | src/rtw/aabb.zig | 61 | ||||
| -rw-r--r-- | src/rtw/hit_record.zig | 21 | ||||
| -rw-r--r-- | src/rtw/hittable.zig | 335 | ||||
| -rw-r--r-- | src/rtw/material.zig | 100 | ||||
| -rw-r--r-- | src/rtw/rand.zig | 40 | ||||
| -rw-r--r-- | src/rtw/ray.zig | 13 | ||||
| -rw-r--r-- | src/rtw/texture.zig | 72 | ||||
| -rw-r--r-- | src/rtw/vec.zig | 109 |
8 files changed, 751 insertions, 0 deletions
diff --git a/src/rtw/aabb.zig b/src/rtw/aabb.zig new file mode 100644 index 0000000..2c283c0 --- /dev/null +++ b/src/rtw/aabb.zig @@ -0,0 +1,61 @@ +const Ray = @import("ray.zig").Ray; +const Point3 = @import("vec.zig").Point3; + +pub const Aabb = struct { + min: Point3, + max: Point3, + + pub fn hit(aabb: Aabb, r: Ray, tMin: f64, tMax: f64) bool { + var tMin_ = tMin; + var tMax_ = tMax; + { + const s0 = (aabb.min.x - r.origin.x) / r.dir.x; + const s1 = (aabb.max.x - r.origin.x) / r.dir.x; + const t0 = @min(s0, s1); + const t1 = @max(s0, s1); + tMin_ = @max(t0, tMin_); + tMax_ = @min(t1, tMax_); + if (tMax_ <= tMin_) { + return false; + } + } + { + const s0 = (aabb.min.y - r.origin.y) / r.dir.y; + const s1 = (aabb.max.y - r.origin.y) / r.dir.y; + const t0 = @min(s0, s1); + const t1 = @max(s0, s1); + tMin_ = @max(t0, tMin_); + tMax_ = @min(t1, tMax_); + if (tMax_ <= tMin_) { + return false; + } + } + { + const s0 = (aabb.min.z - r.origin.z) / r.dir.z; + const s1 = (aabb.max.z - r.origin.z) / r.dir.z; + const t0 = @min(s0, s1); + const t1 = @max(s0, s1); + tMin_ = @max(t0, tMin_); + tMax_ = @min(t1, tMax_); + if (tMax_ <= tMin_) { + return false; + } + } + return true; + } + + pub fn surroundingBox(box0: Aabb, box1: Aabb) Aabb { + return .{ + .min = .{ + .x = @min(box0.min.x, box1.min.x), + .y = @min(box0.min.y, box1.min.y), + .z = @min(box0.min.z, box1.min.z), + }, + .max = .{ + .x = @max(box0.max.x, box1.max.x), + .y = @max(box0.max.y, box1.max.y), + .z = @max(box0.max.z, box1.max.z), + }, + }; + } +}; diff --git a/src/rtw/hit_record.zig b/src/rtw/hit_record.zig new file mode 100644 index 0000000..f71904e --- /dev/null +++ b/src/rtw/hit_record.zig @@ -0,0 +1,21 @@ +const vec = @import("vec.zig"); +const material = @import("material.zig"); +const Vec3 = vec.Vec3; +const Point3 = vec.Point3; +const Material = material.Material; + +pub const HitRecord = struct { + // The point where the ray and the hittable hits. + p: Point3, + // The normal of the hittable at p. + normal: Vec3, + // The material at p. + material: *const Material, + // p = ray.at(t) + t: f64, + // The coordinate of the surface where the ray intersects. + u: f64, + v: f64, + // True if the ray hits the hittable from the front face, i.e., outside of it. + front_face: bool, +}; diff --git a/src/rtw/hittable.zig b/src/rtw/hittable.zig new file mode 100644 index 0000000..c84b3d0 --- /dev/null +++ b/src/rtw/hittable.zig @@ -0,0 +1,335 @@ +const std = @import("std"); +const debug = std.debug; +const math = std.math; +const pi = math.pi; +const ArrayList = std.ArrayList; + +const Ray = @import("ray.zig").Ray; +const Vec3 = @import("vec.zig").Vec3; +const Point3 = @import("vec.zig").Point3; +const Color = @import("vec.zig").Color; +const rgb = @import("vec.zig").rgb; +const randomPointInUnitSphere = @import("rand.zig").randomPointInUnitSphere; +const randomPointInUnitDisk = @import("rand.zig").randomPointInUnitDisk; +const randomUnitVector = @import("rand.zig").randomUnitVector; +const randomInt = @import("rand.zig").randomInt; +const randomReal01 = @import("rand.zig").randomReal01; +const randomReal = @import("rand.zig").randomReal; + +const Material = @import("material.zig").Material; +const DiffuseMaterial = @import("material.zig").DiffuseMaterial; +const MetalMaterial = @import("material.zig").MetalMaterial; +const DielectricMaterial = @import("material.zig").DielectricMaterial; + +const Texture = @import("texture.zig").Texture; +const SolidTexture = @import("texture.zig").SolidTexture; +const CheckerTexture = @import("texture.zig").CheckerTexture; + +const HitRecord = @import("hit_record.zig").HitRecord; +const Aabb = @import("aabb.zig").Aabb; + +const HittableTag = enum { + sphere, + movingSphere, + list, + bvhNode, +}; + +pub const Hittable = union(HittableTag) { + sphere: Sphere, + movingSphere: MovingSphere, + list: HittableList, + bvhNode: BvhNode, + + pub fn hit(h: Hittable, r: Ray, tMin: f64, tMax: f64, record: *HitRecord) bool { + return switch (h) { + HittableTag.sphere => |sphere| sphere.hit(r, tMin, tMax, record), + HittableTag.movingSphere => |movingSphere| movingSphere.hit(r, tMin, tMax, record), + HittableTag.list => |list| list.hit(r, tMin, tMax, record), + HittableTag.bvhNode => |node| node.hit(r, tMin, tMax, record), + }; + } + + fn boudingBox(h: Hittable, time0: f64, time1: f64, outputBox: *Aabb) bool { + return switch (h.*) { + HittableTag.sphere => |sphere| sphere.boudingBox(time0, time1, outputBox), + HittableTag.movingSphere => |movingSphere| movingSphere.boudingBox(time0, time1, outputBox), + HittableTag.list => |list| list.boudingBox(time0, time1, outputBox), + HittableTag.bvhNode => |node| node.boudingBox(time0, time1, outputBox), + }; + } + + pub fn deinit(h: *const Hittable, allocator: anytype) void { + return switch (h.*) { + HittableTag.sphere => |sphere| sphere.deinit(allocator), + HittableTag.movingSphere => |movingSphere| movingSphere.deinit(allocator), + HittableTag.list => |list| list.deinit(allocator), + HittableTag.bvhNode => |node| node.deinit(allocator), + }; + } +}; + +const Sphere = struct { + center: Point3, + radius: f64, + material: *const Material, + + fn hit(sphere: Sphere, r: Ray, tMin: f64, tMax: f64, record: *HitRecord) bool { + const oc = r.origin.sub(sphere.center); + const a = r.dir.normSquared(); + const half_b = Vec3.dot(oc, r.dir); + const c = oc.normSquared() - sphere.radius * sphere.radius; + + const discriminant = half_b * half_b - a * c; + if (discriminant < 0.0) { + // r does not intersect the sphere. + return false; + } + const sqrtd = @sqrt(discriminant); + + // Find the nearest root that lies in the acceptable range. + var root = (-half_b - sqrtd) / a; + if (root < tMin or tMax < root) { + root = (-half_b + sqrtd) / a; + if (root < tMin or tMax < root) { + // out of range + return false; + } + } + + record.t = root; + record.p = r.at(root); + const outward_normal = (record.p.sub(sphere.center)).div(sphere.radius); + record.front_face = Vec3.dot(outward_normal, r.dir) < 0.0; + if (record.front_face) { + record.normal = outward_normal; + } else { + record.normal = outward_normal.mul(-1.0); + } + Sphere.getSphereUv(outward_normal, &record.u, &record.v); + record.material = sphere.material; + + return true; + } + + fn boudingBox(sphere: Sphere, time0: f64, time1: f64, outputBox: *Aabb) bool { + _ = time0; + _ = time1; + const o = sphere.center; + const r = sphere.radius; + outputBox.* = .{ + .min = o.sub(.{ .x = r, .y = r, .z = r }), + .max = o.add(.{ .x = r, .y = r, .z = r }), + }; + return true; + } + + fn getSphereUv(p: Point3, u: *f64, v: *f64) void { + const phi = math.atan2(f64, -p.z, p.x) + pi; + const theta = math.acos(-p.y); + u.* = phi / (2.0 * pi); + v.* = theta / pi; + } + + fn deinit(sphere: *const Sphere, allocator: anytype) void { + allocator.destroy(sphere.material); + } +}; + +const MovingSphere = struct { + center0: Point3, + center1: Point3, + time0: f64, + time1: f64, + radius: f64, + material: *const Material, + + fn hit(sphere: MovingSphere, r: Ray, tMin: f64, tMax: f64, record: *HitRecord) bool { + const center_ = sphere.center(r.time); + const oc = r.origin.sub(center_); + const a = r.dir.normSquared(); + const half_b = Vec3.dot(oc, r.dir); + const c = oc.normSquared() - sphere.radius * sphere.radius; + + const discriminant = half_b * half_b - a * c; + if (discriminant < 0.0) { + // r does not intersect the sphere. + return false; + } + const sqrtd = @sqrt(discriminant); + + // Find the nearest root that lies in the acceptable range. + var root = (-half_b - sqrtd) / a; + if (root < tMin or tMax < root) { + root = (-half_b + sqrtd) / a; + if (root < tMin or tMax < root) { + // out of range + return false; + } + } + + record.t = root; + record.p = r.at(root); + const outward_normal = (record.p.sub(center_)).div(sphere.radius); + record.front_face = Vec3.dot(outward_normal, r.dir) < 0.0; + if (record.front_face) { + record.normal = outward_normal; + } else { + record.normal = outward_normal.mul(-1.0); + } + record.material = sphere.material; + + return true; + } + + fn boudingBox(sphere: MovingSphere, time0: f64, time1: f64, outputBox: *Aabb) bool { + const o0 = sphere.center(time0); + const o1 = sphere.center(time1); + const r = sphere.radius; + const box0 = .{ + .min = o0.sub(.{ .x = r, .y = r, .z = r }), + .max = o0.add(.{ .x = r, .y = r, .z = r }), + }; + const box1 = .{ + .min = o1.sub(.{ .x = r, .y = r, .z = r }), + .max = o1.add(.{ .x = r, .y = r, .z = r }), + }; + outputBox.* = Aabb.surroundingBox(box0, box1); + return true; + } + + fn center(sphere: MovingSphere, t: f64) Point3 { + return sphere.center0.add(sphere.center1.sub(sphere.center0).mul((t - sphere.time0) / (sphere.time1 - sphere.time0))); + } + + fn deinit(sphere: *const MovingSphere, allocator: anytype) void { + allocator.destroy(sphere.material); + } +}; + +const HittableList = struct { + objects: ArrayList(Hittable), + + fn hit(list: HittableList, r: Ray, tMin: f64, tMax: f64, record: *HitRecord) bool { + var hit_anything = false; + var closest_so_far = tMax; + + for (list.objects.items) |object| { + var rec: HitRecord = undefined; + if (object.hit(r, tMin, closest_so_far, &rec)) { + hit_anything = true; + closest_so_far = rec.t; + record.* = rec; + } + } + return hit_anything; + } + + fn boudingBox(list: HittableList, time0: f64, time1: f64, outputBox: *Aabb) bool { + if (list.objects.items.len == 0) { + return false; + } + var firstBox = true; + var tmpBox: Aabb = undefined; + for (list.objects.items) |object| { + if (!object.boudingBox(time0, time1, tmpBox)) { + return false; + } + outputBox = if (firstBox) tmpBox else Aabb.surroundingBox(outputBox, tmpBox); + firstBox = false; + } + return true; + } + + fn deinit(list: *const HittableList, allocator: anytype) void { + for (list.objects.items) |object| { + object.deinit(allocator); + } + list.objects.deinit(); + } +}; + +const BvhNode = struct { + left: *Hittable, // TODO + right: *Hittable, // TODO + box: Aabb, + + fn init( + objects: ArrayList(*Hittable), + start: usize, + end: usize, + time0: f64, + time1: f64, + ) BvhNode { + var left: *Hittable = undefined; + var right: *Hittable = undefined; + + var objects_ = objects; + const axis = randomInt(u8, 0, 3); + + const objectSpan = end - start; + if (objectSpan == 1) { + left = objects.items[start]; + right = objects.items[start]; + } else if (objectSpan == 2) { + if (BvhNode.boxCompare(axis, objects[start], objects[start + 1])) { + left = objects[start]; + right = objects[start + 1]; + } else { + left = objects[start + 1]; + right = objects[start]; + } + } else { + std.sort.sort(*Hittable, objects_, axis, BvhNode.boxCompare); + const mid = start + objectSpan / 2; + left.* = .{ .bvhNode = BvhNode.init(objects, start, mid, time0, time1) }; + right.* = .{ .bvhNode = BvhNode.init(objects, mid, end, time0, time1) }; + } + + var boxLeft: Aabb = undefined; + var boxRight: Aabb = undefined; + + if (!left.boudingBox(time0, time1, boxLeft) || !right.boudingBox(time0, time1, boxRight)) { + // ERROR + } + + return .{ + .left = left, + .right = right, + .box = Aabb.surroundingBox(boxLeft, boxRight), + }; + } + + fn boxCompare(axis: u8, a: *const Hittable, b: *const Hittable) bool { + if (axis == 0) { + return a.x < b.x; + } else if (axis == 1) { + return a.y < b.y; + } else { + return a.z < b.z; + } + } + + fn hit(node: BvhNode, r: Ray, tMin: f64, tMax: f64, record: *HitRecord) bool { + if (!node.box.hit(r, tMin, tMax)) { + return false; + } + + const hitLeft = node.left.hit(r, tMin, tMax, record); + const hitRight = node.right.hit(r, tMin, if (hitLeft) record.t else tMax, record); + + return hitLeft or hitRight; + } + + fn boudingBox(node: BvhNode, time0: f64, time1: f64, outputBox: *Aabb) bool { + _ = time0; + _ = time1; + outputBox.* = node.box; + return true; + } + + fn deinit(node: *const BvhNode, allocator: anytype) void { + _ = node; + _ = allocator; + } +}; diff --git a/src/rtw/material.zig b/src/rtw/material.zig new file mode 100644 index 0000000..87244ab --- /dev/null +++ b/src/rtw/material.zig @@ -0,0 +1,100 @@ +const std = @import("std"); +const debug = std.debug; +const math = std.math; + +const Ray = @import("ray.zig").Ray; +const vec = @import("vec.zig"); +const Vec3 = vec.Vec3; +const rgb = vec.rgb; +const Color = vec.Color; +const HitRecord = @import("hit_record.zig").HitRecord; +const Texture = @import("texture.zig").Texture; +const rand = @import("rand.zig"); +const Random = rand.Random; +const randomPointInUnitSphere = rand.randomPointInUnitSphere; +const randomUnitVector = rand.randomUnitVector; +const randomReal01 = rand.randomReal01; + +const MaterialTag = enum { + diffuse, + metal, + dielectric, +}; + +pub const Material = union(MaterialTag) { + diffuse: DiffuseMaterial, + metal: MetalMaterial, + dielectric: DielectricMaterial, + + pub fn scatter(mat: Material, r_in: Ray, record: HitRecord, attenuation: *Color, scattered: *Ray, rng: Random) bool { + return switch (mat) { + MaterialTag.diffuse => |diffuse_mat| diffuse_mat.scatter(r_in, record, attenuation, scattered, rng), + MaterialTag.metal => |metal_mat| metal_mat.scatter(r_in, record, attenuation, scattered, rng), + MaterialTag.dielectric => |dielectric_mat| dielectric_mat.scatter(r_in, record, attenuation, scattered, rng), + }; + } +}; + +pub const DiffuseMaterial = struct { + albedo: Texture, + + fn scatter(mat: DiffuseMaterial, r_in: Ray, record: HitRecord, attenuation: *Color, scattered: *Ray, rng: Random) bool { + var scatter_direction = record.normal.add(randomUnitVector(rng)); + if (scatter_direction.near_zero()) { + scatter_direction = record.normal; + } + scattered.* = .{ .origin = record.p, .dir = scatter_direction, .time = r_in.time }; + attenuation.* = mat.albedo.value(record.u, record.v, record.p); + return true; + } +}; + +pub const MetalMaterial = struct { + albedo: Color, + fuzz: f64, + + fn scatter(mat: MetalMaterial, r_in: Ray, record: HitRecord, attenuation: *Color, scattered: *Ray, rng: Random) bool { + debug.assert(mat.fuzz <= 1.0); + const reflected = reflect(r_in.dir.normalized(), record.normal); + scattered.* = .{ .origin = record.p, .dir = reflected.add(randomPointInUnitSphere(rng).mul(mat.fuzz)), .time = r_in.time }; + attenuation.* = mat.albedo; + return reflected.dot(record.normal) > 0.0; + } +}; + +pub const DielectricMaterial = struct { + // index of refraction. + ir: f64, + + fn scatter(mat: DielectricMaterial, r_in: Ray, record: HitRecord, attenuation: *Color, scattered: *Ray, rng: Random) bool { + const refraction_ratio = if (record.front_face) 1.0 / mat.ir else mat.ir; + const unit_dir = r_in.dir.normalized(); + + const cos_theta = @min(unit_dir.mul(-1.0).dot(record.normal), 1.0); + const sin_theta = @sqrt(1.0 - cos_theta * cos_theta); + // sin(theta') = refraction_ratio * sin(theta) <= 1 + const can_refract = refraction_ratio * sin_theta <= 1.0; + + const dir = if (can_refract and reflectance(cos_theta, refraction_ratio) < randomReal01(rng)) refract(unit_dir, record.normal, refraction_ratio) else reflect(unit_dir, record.normal); + scattered.* = .{ .origin = record.p, .dir = dir, .time = r_in.time }; + attenuation.* = rgb(1.0, 1.0, 1.0); + return true; + } + + fn reflectance(cos: f64, refraction_idx: f64) f64 { + const r0 = (1.0 - refraction_idx) / (1.0 + refraction_idx); + const r1 = r0 * r0; + return r1 + (1.0 - r1) * math.pow(f64, 1.0 - cos, 5.0); + } +}; + +fn reflect(v: Vec3, n: Vec3) Vec3 { + return v.sub(n.mul(2 * v.dot(n))); +} + +fn refract(uv: Vec3, n: Vec3, etai_over_etat: f64) Vec3 { + const cos_theta = @min(uv.mul(-1.0).dot(n), 1.0); + const r_out_perpendicular = uv.add(n.mul(cos_theta)).mul(etai_over_etat); + const r_out_parallel = n.mul(-@sqrt(@fabs(1.0 - r_out_perpendicular.normSquared()))); + return r_out_perpendicular.add(r_out_parallel); +} diff --git a/src/rtw/rand.zig b/src/rtw/rand.zig new file mode 100644 index 0000000..47a8d61 --- /dev/null +++ b/src/rtw/rand.zig @@ -0,0 +1,40 @@ +const std = @import("std"); + +const Vec3 = @import("vec.zig").Vec3; + +pub const Random = std.rand.Random; + +// [min, max) +pub fn randomInt(comptime T: type, rand: Random, min: T, max: T) T { + return rand.intRangeLessThan(T, min, max); +} + +// [0, 1) +pub fn randomReal01(rand: Random) f64 { + return rand.float(f64); +} + +// [min, max) +pub fn randomReal(rand: Random, min: f64, max: f64) f64 { + return min + randomReal01(rand) * (max - min); +} + +pub fn randomPointInUnitSphere(rand: Random) Vec3 { + while (true) { + const p = Vec3.random(rand, -1.0, 1.0); + if (p.norm() >= 1) continue; + return p; + } +} + +pub fn randomPointInUnitDisk(rand: Random) Vec3 { + while (true) { + const p = Vec3{ .x = randomReal(rand, -1.0, 1.0), .y = randomReal(rand, -1.0, 1.0), .z = 0.0 }; + if (p.norm() >= 1) continue; + return p; + } +} + +pub fn randomUnitVector(rand: Random) Vec3 { + return randomPointInUnitSphere(rand).normalized(); +} diff --git a/src/rtw/ray.zig b/src/rtw/ray.zig new file mode 100644 index 0000000..e46198f --- /dev/null +++ b/src/rtw/ray.zig @@ -0,0 +1,13 @@ +const vec = @import("vec.zig"); +const Vec3 = vec.Vec3; +const Point3 = vec.Point3; + +pub const Ray = struct { + origin: Vec3, + dir: Vec3, + time: f64, + + pub fn at(r: Ray, t: f64) Point3 { + return r.origin.add(r.dir.mul(t)); + } +}; diff --git a/src/rtw/texture.zig b/src/rtw/texture.zig new file mode 100644 index 0000000..47c3c4f --- /dev/null +++ b/src/rtw/texture.zig @@ -0,0 +1,72 @@ +const std = @import("std"); + +const Color = @import("vec.zig").Color; +const Vec3 = @import("vec.zig").Vec3; + +const TextureTag = enum { + solid, + checker, +}; + +pub const Texture = union(TextureTag) { + solid: SolidTexture, + checker: CheckerTexture, + + pub fn makeSolid(color: Color) Texture { + return .{ .solid = .{ .color = color } }; + } + + pub fn makeChecker(allocator: std.mem.Allocator, odd: Color, even: Color) !Texture { + return .{ .checker = try CheckerTexture.init( + allocator, + Texture.makeSolid(odd), + Texture.makeSolid(even), + ) }; + } + + pub fn value(tx: Texture, u: f64, v: f64, p: Vec3) Color { + return switch (tx) { + TextureTag.solid => |solidTx| solidTx.value(u, v, p), + TextureTag.checker => |checkerTx| checkerTx.value(u, v, p), + }; + } +}; + +pub const SolidTexture = struct { + color: Color, + + fn value(tx: SolidTexture, u: f64, v: f64, p: Vec3) Color { + _ = u; + _ = v; + _ = p; + return tx.color; + } +}; + +pub const CheckerTexture = struct { + allocator: std.mem.Allocator, + odd: *const Texture, + even: *const Texture, + + fn init(allocator: std.mem.Allocator, odd: Texture, even: Texture) !CheckerTexture { + var odd_ = try allocator.create(Texture); + var even_ = try allocator.create(Texture); + odd_.* = odd; + even_.* = even; + return .{ + .allocator = allocator, + .odd = odd_, + .even = even_, + }; + } + + fn deinit(tx: CheckerTexture) void { + tx.allocator.destroy(tx.even); + tx.allocator.destroy(tx.odd); + } + + fn value(tx: CheckerTexture, u: f64, v: f64, p: Vec3) Color { + const sines = @sin(10 * p.x) * @sin(10 * p.y) * @sin(10 * p.z); + return if (sines < 0) tx.odd.value(u, v, p) else tx.even.value(u, v, p); + } +}; diff --git a/src/rtw/vec.zig b/src/rtw/vec.zig new file mode 100644 index 0000000..9b2c3f0 --- /dev/null +++ b/src/rtw/vec.zig @@ -0,0 +1,109 @@ +const std = @import("std"); + +const rand = @import("rand.zig"); +const Random = rand.Random; +const randomReal = rand.randomReal; +const randomReal01 = rand.randomReal01; + +pub const Vec3 = struct { + x: f64, + y: f64, + z: f64, + + pub fn norm(v: Vec3) f64 { + return @sqrt(v.normSquared()); + } + + pub fn normSquared(v: Vec3) f64 { + return v.x * v.x + v.y * v.y + v.z * v.z; + } + + pub fn dot(u: Vec3, v: Vec3) f64 { + return u.x * v.x + u.y * v.y + u.z * v.z; + } + + pub fn cross(u: Vec3, v: Vec3) Vec3 { + return Vec3{ + .x = u.y * v.z - u.z * v.y, + .y = u.z * v.x - u.x * v.z, + .z = u.x * v.y - u.y * v.x, + }; + } + + pub fn normalized(v: Vec3) Vec3 { + const n = v.norm(); + if (n == 0.0) { + return v; + } else { + return v.div(n); + } + } + + pub fn add(u: Vec3, v: Vec3) Vec3 { + return Vec3{ + .x = u.x + v.x, + .y = u.y + v.y, + .z = u.z + v.z, + }; + } + + pub fn sub(u: Vec3, v: Vec3) Vec3 { + return Vec3{ + .x = u.x - v.x, + .y = u.y - v.y, + .z = u.z - v.z, + }; + } + + pub fn mul(v: Vec3, t: f64) Vec3 { + return Vec3{ + .x = v.x * t, + .y = v.y * t, + .z = v.z * t, + }; + } + + pub fn mulV(u: Vec3, v: Vec3) Vec3 { + return Vec3{ + .x = u.x * v.x, + .y = u.y * v.y, + .z = u.z * v.z, + }; + } + + pub fn div(v: Vec3, t: f64) Vec3 { + return Vec3{ + .x = v.x / t, + .y = v.y / t, + .z = v.z / t, + }; + } + + pub fn random01(rng: Random) Vec3 { + return Vec3{ + .x = randomReal01(rng), + .y = randomReal01(rng), + .z = randomReal01(rng), + }; + } + + pub fn random(rng: Random, min: f64, max: f64) Vec3 { + return Vec3{ + .x = randomReal(rng, min, max), + .y = randomReal(rng, min, max), + .z = randomReal(rng, min, max), + }; + } + + pub fn near_zero(v: Vec3) bool { + const epsilon = 1e-8; + return @fabs(v.x) < epsilon and @fabs(v.y) < epsilon and @fabs(v.z) < epsilon; + } +}; + +pub const Point3 = Vec3; +pub const Color = Vec3; + +pub fn rgb(r: f64, g: f64, b: f64) Color { + return .{ .x = r, .y = g, .z = b }; +} |
