Last active
July 13, 2024 17:34
-
-
Save jesterKing/bd0b1272e576a9d08dc6327078dee76d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Using 0.14.0-dev.23 | |
Run on command-line: | |
zig build && ./zig-out/bin/raytrace > img.ppm |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
pub fn build(b: *std.Build) void { | |
const target = b.standardTargetOptions(.{}); | |
const optimize = b.standardOptimizeOption(.{}); | |
const exe = b.addExecutable(.{ | |
.name = "raytrace", | |
.target = target, | |
.optimize = optimize, | |
.root_source_file = b.path("raytrace.zig"), | |
}); | |
b.installArtifact(exe); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
// set logging level to info. | |
pub const std_options = .{ | |
.log_level = .info, | |
.logFn = myLogFn, | |
}; | |
const infinity = std.math.inf(f64); | |
// log function that writes to stderr | |
pub fn myLogFn( | |
comptime _: std.log.Level, | |
comptime _: @TypeOf(.EnumLiteral), | |
comptime format: []const u8, | |
args: anytype, | |
) void { | |
std.debug.lockStdErr(); | |
defer std.debug.unlockStdErr(); | |
const stderr = std.io.getStdErr().writer(); | |
nosuspend stderr.print(format, args) catch return; | |
} | |
const stdout = std.io.getStdOut().writer(); | |
var _rng = std.Random.DefaultPrng.init(42); | |
const _random = _rng.random(); | |
fn next_float() f64 { | |
return next_float_range(std.math.floatMin(f64), std.math.floatMax(f64)); | |
} | |
fn next_float_range(min: f64, max: f64) f64 { | |
return min + (max - min) * _random.float(f64); | |
} | |
var mem: std.mem.Allocator = std.heap.page_allocator; | |
pub fn main() !void { | |
var arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
defer arena_allocator.deinit(); | |
mem = arena_allocator.allocator(); | |
var _world = HittableList.init(mem); | |
defer _world.deinit(); | |
var world = Hittable{ .hittable_list = &_world }; | |
const material_ground = Lambertian.createMaterial(Color.init(0.5, 0.5, 0.5)); | |
try world.append(Sphere.createHittable(Point3.init(0, -1000, -1), 1000.0, material_ground)); | |
const mat1 = Dielectric.createMaterial(1.5); | |
try world.append(Sphere.createHittable(Point3.init(0, 1, 0), 1.0, mat1)); | |
const mat2 = Lambertian.createMaterial(Color.init(0.4, 0.2, 0.1)); | |
try world.append(Sphere.createHittable(Point3.init(-4, 1, 0), 1.0, mat2)); | |
const mat3 = Metal.createMaterial(Color.init(0.7, 0.6, 0.5), 0.0); | |
try world.append(Sphere.createHittable(Point3.init(4, 1, 0), 1.0, mat3)); | |
const p = Point3.init(4, 0.2, 0); | |
var a: i32 = -11; | |
while (a < 11) : (a += 1) { | |
var b: i32 = -11; | |
while (b < 11) : (b += 1) { | |
const choose_mat = _random.float(f64); | |
const center = Point3.init(@as(f64, @floatFromInt(a)) + 0.9 * _random.float(f64), 0.2, @as(f64, @floatFromInt(b)) + 0.9 * _random.float(f64)); | |
if (center.subtract(p).length() > 0.9) { | |
if (choose_mat < 0.8) { | |
const albedo = Color.random().multiply_compwise(Color.random()); | |
const sphere_material = Lambertian.createMaterial(albedo); | |
try world.append(Sphere.createHittable(center, 0.2, sphere_material)); | |
} else if (choose_mat < 0.95) { | |
const albedo = Color.random_range(0.5, 1.0); | |
const fuzz = next_float_range(0.0, 0.5); | |
const sphere_material = Metal.createMaterial(albedo, fuzz); | |
try world.append(Sphere.createHittable(center, 0.2, sphere_material)); | |
} else { | |
const sphere_material = Dielectric.createMaterial(1.5); | |
try world.append(Sphere.createHittable(center, 0.2, sphere_material)); | |
} | |
} | |
} | |
} | |
var cam = Camera.create( | |
16.0 / 9.0, | |
1920, | |
42, | |
100, | |
10, | |
30.0, | |
); | |
cam.lookfrom = Point3.init(13, 2, 3); | |
cam.lookat = Point3.init(0, 0, 0); | |
cam.vup = Vec3.init(0, 1, 0); | |
cam.defocus_angle = 0.6; | |
cam.focus_distance = 10.0; | |
try cam.render(&world); | |
} | |
const intensity = Interval.init(0.000, 0.999); | |
const Camera = struct { | |
aspect_ratio: f64, | |
image_width: usize, | |
seed: u64, | |
samples_per_pixel: u64, | |
max_ray_depth: usize, | |
vfov: f64, | |
defocus_angle: f64 = 0.0, | |
focus_distance: f64 = 10.0, | |
_image_height: usize = 0, | |
_pixel_samples_scale: f64 = 1.0, | |
_center: Point3 = Point3.initZero(), | |
_pixel00_loc: Point3 = Point3.initZero(), | |
_pixel_delta_u: Vec3 = Vec3.initZero(), | |
_pixel_delta_v: Vec3 = Vec3.initZero(), | |
lookfrom: Point3 = Point3.init(0, 0, 0), | |
lookat: Point3 = Point3.init(0, 0, -1), | |
vup: Vec3 = Vec3.init(0, 1, 0), | |
_u: Vec3 = Vec3.initZero(), | |
_v: Vec3 = Vec3.initZero(), | |
_w: Vec3 = Vec3.initZero(), | |
_defocus_disk_u: Vec3 = Vec3.initZero(), | |
_defocus_disk_v: Vec3 = Vec3.initZero(), | |
_rng: std.Random.Xoshiro256, | |
_random: std.Random, | |
pub fn create(aspect_ratio: f64, image_width: usize, seed: u64, samples: u64, max_ray_depth: usize, vfov: f64) Camera { | |
var rng = std.Random.DefaultPrng.init(seed); | |
const cam = Camera{ | |
.aspect_ratio = aspect_ratio, | |
.image_width = image_width, | |
.vfov = vfov, | |
.seed = seed, | |
.samples_per_pixel = samples, | |
.max_ray_depth = max_ray_depth, | |
._rng = std.Random.DefaultPrng.init(seed), | |
._random = rng.random(), | |
}; | |
return cam; | |
} | |
pub fn render(self: *Camera, world: *Hittable) !void { | |
self.initialize(); | |
const ppm_writer_log = std.log.scoped(.ppm_write); | |
ppm_writer_log.info("Starting to write PPM image ...\n\n", .{}); | |
try stdout.print("P3\n{} {}\n255\n", .{ self.image_width, self._image_height }); | |
for (0..self._image_height) |h| { | |
ppm_writer_log.info("\rScanlines remaining: {} ", .{self._image_height - h}); | |
var pixel_color = Color.initZero(); | |
for (0..self.image_width) |w| { | |
for (0..self.samples_per_pixel) |_| { | |
var ray = self.get_ray(w, h); | |
pixel_color = pixel_color.add(ray_color(&ray, self.max_ray_depth, world)); | |
} | |
pixel_color = pixel_color.multiply(f64, self._pixel_samples_scale); | |
writeColor(pixel_color); | |
} | |
try stdout.print("\n", .{}); | |
} | |
ppm_writer_log.info("\rDone. \n", .{}); | |
} | |
fn initialize(self: *Camera) void { | |
// set up random number generator | |
self._random = self._rng.random(); | |
// determine image size | |
self._image_height = @as(usize, @intFromFloat(@as(f64, @floatFromInt(self.image_width)) / self.aspect_ratio)); | |
self._image_height = if (self._image_height < 1) 1 else self._image_height; | |
self._pixel_samples_scale = 1.0 / @as(f64, @floatFromInt(self.samples_per_pixel)); | |
self._center = self.lookfrom; | |
// set up viewport dimensions | |
const theta = std.math.degreesToRadians(self.vfov); | |
const h = @tan(theta / 2.0); | |
const viewport_height = 2.0 * h * self.focus_distance; | |
const viewport_width = viewport_height * (@as(f64, @floatFromInt(self.image_width))) / (@as(f64, @floatFromInt(self._image_height))); | |
self._w = self.lookfrom.subtract(self.lookat).unitized(); | |
self._u = self.vup.cross(self._w).unitized(); | |
self._v = self._w.cross(self._u); | |
const viewport_u = self._u.multiply(f64, viewport_width); | |
const viewport_v = self._v.neg().multiply(f64, viewport_height); | |
self._pixel_delta_u = viewport_u.divide(usize, self.image_width); | |
self._pixel_delta_v = viewport_v.divide(usize, self._image_height); | |
// calculate the location of the upper left pixel | |
const viewport_upper_left = | |
self._center | |
.subtract(self._w.multiply(f64, self.focus_distance)) | |
.subtract(viewport_u.multiply(f64, 0.5)) | |
.subtract(viewport_v.multiply(f64, 0.5)); | |
self._pixel00_loc = | |
viewport_upper_left | |
.add(self._pixel_delta_u | |
.add(self._pixel_delta_v) | |
.multiply(f64, 0.5)); | |
// defocus disk vectors | |
const defocus_radius = self.focus_distance * @tan(std.math.degreesToRadians(self.defocus_angle / 2.0)); | |
self._defocus_disk_u = self._u.multiply(f64, defocus_radius); | |
self._defocus_disk_v = self._v.multiply(f64, defocus_radius); | |
std.log.debug("pixel_samples_scale: {d}\n", .{self._pixel_samples_scale}); | |
} | |
fn linear_to_gamma(linear_component: f64) f64 { | |
return if (linear_component > 0) std.math.sqrt(linear_component) else 0; | |
} | |
fn writeColor(color: Color) void { | |
const r = linear_to_gamma(color.x); | |
const g = linear_to_gamma(color.y); | |
const b = linear_to_gamma(color.z); | |
const ir = @as(u8, @intFromFloat(255.999 * intensity.clamp(r))); | |
const ig = @as(u8, @intFromFloat(255.999 * intensity.clamp(g))); | |
const ib = @as(u8, @intFromFloat(255.999 * intensity.clamp(b))); | |
nosuspend stdout.print("{} {} {} ", .{ ir, ig, ib }) catch return; | |
} | |
fn get_ray(self: *Camera, x: usize, y: usize) Ray { | |
const offset = self.sample_square(); | |
const pixel_sample = self._pixel00_loc | |
.add(self._pixel_delta_u.multiply(f64, @as(f64, @floatFromInt(x)) + offset.x)) | |
.add(self._pixel_delta_v.multiply(f64, @as(f64, @floatFromInt(y)) + offset.y)); | |
const ray_origin = if (self.defocus_angle <= 0) self._center else self.defocus_disk_sample(); | |
const ray_direction = pixel_sample.subtract(ray_origin); | |
return Ray{ | |
.origin = ray_origin, | |
.direction = ray_direction, | |
}; | |
} | |
fn sample_square(self: *Camera) Vec3 { | |
const x = self._random.float(f64); | |
const y = self._random.float(f64); | |
return Vec3.init(x - 0.5, y - 0.5, 0.0); | |
} | |
fn defocus_disk_sample(self: *Camera) Point3 { | |
const disk_sample = Vec3.random_in_unit_disk(); | |
return self._center | |
.add(self._defocus_disk_u.multiply(f64, disk_sample.x)) | |
.add(self._defocus_disk_v.multiply(f64, disk_sample.y)); | |
} | |
fn ray_color(r: *Ray, depth: usize, world: *Hittable) Color { | |
if (depth <= 0) | |
return Color.initZero(); | |
var hit_record = HitRecord{}; | |
if (world.hit(r, Interval.init(0.001, infinity), &hit_record)) { | |
var scattered = Ray.init(Point3.initZero(), Vec3.initZero()); | |
var attenuation = Color.initZero(); | |
if (hit_record.m) |mat| { | |
if (mat.scatter(r, &hit_record, &attenuation, &scattered)) { | |
return attenuation.multiply_compwise(ray_color(&scattered, depth - 1, world)); | |
} | |
} | |
return Color.initZero(); | |
} | |
const unit_direction = r.direction.unitized(); | |
const a: f64 = 0.5 * (unit_direction.y + 1.0); | |
const blue = Color.init(0.2, 0.4, 0.7); | |
const white = Color.init(1.0, 1.0, 1.0); | |
return white | |
.multiply(f64, 1.0 - a) | |
.add(blue | |
.multiply(f64, a)); | |
} | |
}; | |
const Interval = struct { | |
min: f64, | |
max: f64, | |
pub fn initInfinity() Interval { | |
return Interval{ | |
.min = -infinity, | |
.max = infinity, | |
}; | |
} | |
pub fn init(min: f64, max: f64) Interval { | |
return Interval{ | |
.min = min, | |
.max = max, | |
}; | |
} | |
pub fn contains(self: Interval, x: f64) bool { | |
return (self.min <= x) and (x <= self.max); | |
} | |
pub fn surrounds(self: Interval, x: f64) bool { | |
return (self.min < x) and (x < self.max); | |
} | |
pub fn clamp(self: Interval, x: f64) f64 { | |
return if (x < self.min) self.min else if (x > self.max) self.max else x; | |
} | |
}; | |
const empty = Interval.init(infinity, -infinity); | |
const universe = Interval.initInfinity(); | |
var _magentaLambertian = Lambertian.init(Color.init(1, 0, 1)); | |
var _magentaMaterial = Material{ .lambertian = &_magentaLambertian }; | |
var _magentaSphere = Sphere.init(Point3.initZero(), 0.0, &_magentaMaterial); | |
var _magentaHittable = Hittable{ .sphere = &_magentaSphere }; | |
const HitRecord = struct { | |
p: Point3 = Point3.initZero(), | |
n: Vec3 = Vec3.initZero(), | |
t: f64 = 0.0, | |
f: bool = false, | |
m: ?*Material = null, | |
pub fn set_face_normal(self: *HitRecord, r: *Ray, outward_normal: Vec3) void { | |
self.f = r.direction.dot(outward_normal) < 0; | |
self.n = if (self.f) outward_normal else outward_normal.neg(); | |
} | |
}; | |
const HittableList = struct { | |
objects: std.ArrayList(Hittable), | |
pub fn init(_mem: std.mem.Allocator) HittableList { | |
return HittableList{ | |
.objects = std.ArrayList(Hittable).init(_mem), | |
}; | |
} | |
pub fn deinit(self: *HittableList) void { | |
self.objects.deinit(); | |
} | |
pub fn append(self: *HittableList, object: *Hittable) !void { | |
try self.objects.append(object.*); | |
} | |
pub fn hit(self: HittableList, r: *Ray, ray_t: Interval, rec: *HitRecord) bool { | |
var temp_rec = HitRecord{}; | |
var hit_anything = false; | |
var closest_so_far = ray_t.max; | |
for (self.objects.items) |object| { | |
if (object.hit(r, Interval.init(ray_t.min, closest_so_far), &temp_rec)) { | |
hit_anything = true; | |
closest_so_far = temp_rec.t; | |
rec.t = temp_rec.t; | |
rec.p = temp_rec.p; | |
rec.n = temp_rec.n; | |
rec.f = temp_rec.f; | |
rec.m = temp_rec.m; | |
} | |
} | |
return hit_anything; | |
} | |
}; | |
const Sphere = struct { | |
center: Point3, | |
radius: f64, | |
material: *Material, | |
pub fn createHittable(center: Point3, radius: f64, material: *Material) *Hittable { | |
const sphere = mem.create(Sphere) catch return &_magentaHittable; | |
sphere.* = .{ .center = center, .radius = @max(0, radius), .material = material }; | |
const hittable = mem.create(Hittable) catch return &_magentaHittable; | |
hittable.* = .{ .sphere = sphere }; | |
return hittable; | |
} | |
pub fn init(center: Point3, radius: f64, material: *Material) Sphere { | |
return Sphere{ | |
.center = center, | |
.radius = @max(0, radius), | |
.material = material, | |
}; | |
} | |
pub fn hit(self: *Sphere, r: *Ray, ray_t: Interval, rec: *HitRecord) bool { | |
const oc = self.center.subtract(r.origin); | |
const a = r.direction.length_squared(); | |
const h = r.direction.dot(oc); | |
const c = oc.length_squared() - self.radius * self.radius; | |
const discriminant = h * h - a * c; | |
if (discriminant < 0) { | |
return false; | |
} | |
const sqrtd = std.math.sqrt(discriminant); | |
var root = (h - sqrtd) / a; | |
if (!ray_t.surrounds(root)) { | |
root = (h + sqrtd) / a; | |
if (!ray_t.surrounds(root)) { | |
return false; | |
} | |
} | |
rec.t = root; | |
rec.p = r.at(rec.t); | |
const outward_normal = rec.p.subtract(self.center).divide(f64, self.radius).unitized(); | |
rec.set_face_normal(r, outward_normal); | |
rec.m = self.material; | |
return true; | |
} | |
}; | |
const Hittable = union(enum) { | |
sphere: *Sphere, | |
hittable_list: *HittableList, | |
pub fn hit(self: Hittable, r: *Ray, ray_t: Interval, rec: *HitRecord) bool { | |
return switch (self) { | |
inline else => |s| s.hit(r, ray_t, rec), | |
}; | |
} | |
pub fn append(self: Hittable, object: *Hittable) !void { | |
switch (self) { | |
.hittable_list => |l| try l.append(object), | |
else => return, | |
} | |
} | |
}; | |
const Lambertian = struct { | |
albedo: Color, | |
pub fn createMaterial(albedo: Color) *Material { | |
const mat: *Material = mem.create(Material) catch return &_magentaMaterial; | |
const lambertian = mem.create(Lambertian) catch return &_magentaMaterial; | |
lambertian.* = .{ .albedo = albedo }; | |
mat.* = .{ .lambertian = lambertian }; | |
return mat; | |
} | |
pub fn init(albedo: Color) Lambertian { | |
return Lambertian{ | |
.albedo = albedo, | |
}; | |
} | |
pub fn scatter(self: *Lambertian, r_in: *Ray, rec: *HitRecord, attenuation: *Color, scattered: *Ray) bool { | |
_ = r_in; | |
const scatter_direction = rec.n.add(Vec3.random_unit_vector()); | |
scattered.origin = rec.p; | |
scattered.direction = scatter_direction; | |
if (scatter_direction.near_zero()) { | |
scattered.direction = rec.n; | |
} | |
attenuation.x = self.albedo.x; | |
attenuation.y = self.albedo.y; | |
attenuation.z = self.albedo.z; | |
return true; | |
} | |
}; | |
const Metal = struct { | |
albedo: Color, | |
fuzz: f64, | |
pub fn createMaterial(albedo: Color, fuzz: f64) *Material { | |
const mat: *Material = mem.create(Material) catch return &_magentaMaterial; | |
const metal = mem.create(Metal) catch return &_magentaMaterial; | |
metal.* = .{ .albedo = albedo, .fuzz = @min(1.0, fuzz) }; | |
mat.* = .{ .metal = metal }; | |
return mat; | |
} | |
pub fn init(albedo: Color, fuzz: f64) Metal { | |
return Metal{ | |
.albedo = albedo, | |
.fuzz = @min(1.0, fuzz), | |
}; | |
} | |
pub fn scatter(self: *Metal, r_in: *Ray, rec: *HitRecord, attenuation: *Color, scattered: *Ray) bool { | |
const _reflected = Vec3.reflect(r_in.direction, rec.n).unitized(); | |
const reflected = _reflected.add(Vec3.random_unit_vector().multiply(f64, self.fuzz)); | |
scattered.origin = rec.p; | |
scattered.direction = reflected; | |
attenuation.x = self.albedo.x; | |
attenuation.y = self.albedo.y; | |
attenuation.z = self.albedo.z; | |
return scattered.direction.dot(rec.n) > 0; | |
} | |
}; | |
const Dielectric = struct { | |
refraction_index: f64, | |
pub fn createMaterial(refraction_index: f64) *Material { | |
const mat: *Material = mem.create(Material) catch return &_magentaMaterial; | |
const dielectric = mem.create(Dielectric) catch return &_magentaMaterial; | |
dielectric.* = .{ .refraction_index = refraction_index }; | |
mat.* = .{ .dielectric = dielectric }; | |
return mat; | |
} | |
pub fn init(refraction_index: f64) Dielectric { | |
return Dielectric{ | |
.refraction_index = refraction_index, | |
}; | |
} | |
pub fn scatter(self: *Dielectric, r_in: *Ray, rec: *HitRecord, attenuation: *Color, scattered: *Ray) bool { | |
attenuation.x = 1.0; | |
attenuation.y = 1.0; | |
attenuation.z = 1.0; | |
const ri = if (rec.f) 1.0 / self.refraction_index else self.refraction_index; | |
const unit_direction = r_in.direction.unitized(); | |
const cos_theta = @min(unit_direction.neg().dot(rec.n), 1.0); | |
const sin_theta = std.math.sqrt(1.0 - cos_theta * cos_theta); | |
const cannot_refract = ri * sin_theta > 1.0; | |
const reflect = cannot_refract or (reflectance(cos_theta, ri) > _random.float(f64)); | |
const direction = if (reflect) Vec3.reflect(unit_direction, rec.n) else Vec3.refract(unit_direction, rec.n, ri); | |
scattered.origin = rec.p; | |
scattered.direction = direction; | |
return true; | |
} | |
fn reflectance(cosine: f64, refraction_index: f64) f64 { | |
var r0 = (1.0 - refraction_index) / (1.0 + refraction_index); | |
r0 = r0 * r0; | |
return r0 + (1.0 - r0) * std.math.pow(f64, 1.0 - cosine, 5.0); | |
} | |
}; | |
const Material = union(enum) { | |
lambertian: *Lambertian, | |
metal: *Metal, | |
dielectric: *Dielectric, | |
pub fn scatter(self: Material, r_in: *Ray, rec: *HitRecord, attenuation: *Color, scattered: *Ray) bool { | |
return switch (self) { | |
inline else => |s| s.scatter(r_in, rec, attenuation, scattered), | |
}; | |
} | |
}; | |
const Color = Vec3; | |
const Point3 = Vec3; | |
const Vec3 = struct { | |
x: f64 = 0.0, | |
y: f64 = 0.0, | |
z: f64 = 0.0, | |
pub fn initZero() Vec3 { | |
return Vec3{}; | |
} | |
pub fn init(e0: f64, e1: f64, e2: f64) Vec3 { | |
return Vec3{ | |
.x = e0, | |
.y = e1, | |
.z = e2, | |
}; | |
} | |
pub fn neg(self: Vec3) Vec3 { | |
return Vec3{ | |
.x = -self.x, | |
.y = -self.y, | |
.z = -self.z, | |
}; | |
} | |
pub fn add(self: Vec3, other: Vec3) Vec3 { | |
return Vec3{ | |
.x = self.x + other.x, | |
.y = self.y + other.y, | |
.z = self.z + other.z, | |
}; | |
} | |
pub fn subtract(self: Vec3, other: Vec3) Vec3 { | |
return self.add(other.neg()); | |
} | |
pub fn multiply(self: Vec3, comptime T: type, t: T) Vec3 { | |
const _t = if (@TypeOf(t) == f64) t else @as(f64, @floatFromInt(t)); | |
return Vec3{ | |
.x = self.x * _t, | |
.y = self.y * _t, | |
.z = self.z * _t, | |
}; | |
} | |
pub fn multiply_compwise(self: Vec3, other: Vec3) Vec3 { | |
return Vec3{ | |
.x = self.x * other.x, | |
.y = self.y * other.y, | |
.z = self.z * other.z, | |
}; | |
} | |
pub fn divide(self: Vec3, comptime T: type, t: T) Vec3 { | |
const _t = if (@TypeOf(t) == f64) t else @as(f64, @floatFromInt(t)); | |
return self.multiply(f64, 1.0 / _t); | |
} | |
pub fn length(self: Vec3) f64 { | |
return std.math.sqrt(self.length_squared()); | |
} | |
pub fn length_squared(self: Vec3) f64 { | |
return self.x * self.x + self.y * self.y + self.z * self.z; | |
} | |
pub fn dot(self: Vec3, other: Vec3) f64 { | |
return self.x * other.x + self.y * other.y + self.z * other.z; | |
} | |
pub fn cross(self: Vec3, other: Vec3) Vec3 { | |
return Vec3.init( | |
self.y * other.z - self.z * other.y, | |
self.z * other.x - self.x * other.z, | |
self.x * other.y - self.y * other.x, | |
); | |
} | |
pub fn unitized(self: Vec3) Vec3 { | |
return self.divide(f64, self.length()); | |
} | |
pub fn near_zero(self: Vec3) bool { | |
const s = 1e-8; | |
return (@abs(self.x) < s) and (@abs(self.y) < s) and (@abs(self.z) < s); | |
} | |
pub fn random() Vec3 { | |
return Vec3.init(_random.float(f64), _random.float(f64), _random.float(f64)); | |
} | |
pub fn randomfull() Vec3 { | |
return Vec3.init(next_float(), next_float(), next_float()); | |
} | |
pub fn random_range(min: f64, max: f64) Vec3 { | |
return Vec3.init(next_float_range(min, max), next_float_range(min, max), next_float_range(min, max)); | |
} | |
pub fn random_in_unit_sphere() Vec3 { | |
while (true) { | |
const p = Vec3.random_range(-1.0, 1.0); | |
if (p.length_squared() < 1.0) { | |
return p; | |
} | |
} | |
unreachable; | |
} | |
pub fn random_in_unit_disk() Vec3 { | |
while (true) { | |
const p = Vec3.init(next_float_range(-1.0, 1.0), next_float_range(-1.0, 1.0), 0.0); | |
if (p.length_squared() < 1.0) { | |
return p; | |
} | |
} | |
unreachable; | |
} | |
pub fn random_unit_vector() Vec3 { | |
return Vec3.random_in_unit_sphere().unitized(); | |
} | |
pub fn random_on_hemisphere(normal: Vec3) Vec3 { | |
const in_unit_sphere = Vec3.random_in_unit_sphere(); | |
return if (in_unit_sphere.dot(normal) > 0.0) in_unit_sphere else in_unit_sphere.neg(); | |
} | |
pub fn reflect(v: Vec3, n: Vec3) Vec3 { | |
return v.subtract(n.multiply(f64, 2.0 * v.dot(n))); | |
} | |
pub fn refract(uv: Vec3, n: Vec3, etai_over_etat: f64) Vec3 { | |
const cos_theta = @min(uv.neg().dot(n), 1.0); | |
const r_out_perp = uv.add(n.multiply(f64, cos_theta)).multiply(f64, etai_over_etat); | |
const negsqrt: f64 = -std.math.sqrt(1.0 - r_out_perp.length_squared()); | |
const r_out_parallel = n.multiply(f64, negsqrt); | |
return r_out_perp.add(r_out_parallel); | |
} | |
}; | |
const Ray = struct { | |
origin: Point3 = Point3{}, | |
direction: Vec3 = Vec3{}, | |
pub fn init(origin: Point3, direction: Vec3) Ray { | |
return Ray{ | |
.origin = origin, | |
.direction = direction, | |
}; | |
} | |
pub fn at(self: Ray, t: f64) Point3 { | |
return self.origin.add(self.direction.multiply(f64, t)); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment