Skip to content

Instantly share code, notes, and snippets.

@jesterKing
Last active July 12, 2024 07:33
Show Gist options
  • Select an option

  • Save jesterKing/82008d04287c06e64972469caeefeef9 to your computer and use it in GitHub Desktop.

Select an option

Save jesterKing/82008d04287c06e64972469caeefeef9 to your computer and use it in GitHub Desktop.
Chapter 6 Raytracing in one weekend
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);
}
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();
pub fn main() !void {
var _world = HittableList.init();
defer _world.deinit();
var world = Hittable{.hittable_list = &_world};
const aspect_ratio : f64 = 16.0 / 9.0;
const image_width : usize = 400;
var image_height = @as(usize, @intFromFloat(image_width / aspect_ratio));
image_height = if (image_height < 1) 1 else image_height;
const viewport_height = 2.0;
const viewport_width = viewport_height * (@as(f64, @floatFromInt(image_width)))/(@as(f64, @floatFromInt(image_height)));
const focal_length = 1.0;
const camera_center = Point3.initZero();
const viewport_u = Vec3.init(viewport_width, 0.0, 0.0);
const viewport_v = Vec3.init(0.0, -viewport_height, 0.0);
const pixel_delta_u = viewport_u.divide(usize, image_width);
const pixel_delta_v = viewport_v.divide(usize, image_height);
const viewport_upper_left =
camera_center
.subtract(Vec3.init(0, 0, focal_length))
.subtract(viewport_u.divide(i32, 2))
.subtract(viewport_v.divide(i32, 2));
const pixel00_loc =
viewport_upper_left
.add(
pixel_delta_u
.add(pixel_delta_v)
.multiply(f64, 0.5)
);
var sphere1 = Sphere.init(Point3.init(0, 0, -1), 0.5);
var sphere2 = Sphere.init(Point3.init(0, -100.5, -1), 100.0);
const a1 = Hittable{.sphere = &sphere1 };
const a2 = Hittable{.sphere = &sphere2 };
try world.append(a1);
try world.append(a2);
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", .{ image_width, image_height });
for (0..image_height) |h| {
ppm_writer_log.info("\rScanlines remaining: {}", .{image_height - h});
for (0..image_width) |w| {
const pixel_center = pixel00_loc.add(pixel_delta_u.multiply(usize, w)).add(pixel_delta_v.multiply(usize, h));
const ray_direction = pixel_center.subtract(camera_center);
var ray = Ray{
.origin = camera_center,
.direction = ray_direction,
};
const col = ray_color(&ray, &world);
writeColor(col);
}
try stdout.print("\n", .{});
}
ppm_writer_log.info("\rDone. \n", .{});
}
fn ray_color(r: *Ray, world: *Hittable) Color {
var hit_record = HitRecord{};
if(world.hit(r, Interval.init(0.0, infinity), &hit_record)) {
const N = hit_record.n;
const col = Color
.init(N.x + 1, N.y + 1, N.z + 1)
.multiply(f64, 0.5);
return col;
}
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)
);
}
fn writeColor(color: Color) void {
const ir = @as(u8, @intFromFloat(255.999 * color.x));
const ig = @as(u8, @intFromFloat(255.999 * color.y));
const ib = @as(u8, @intFromFloat(255.999 * color.z));
nosuspend stdout.print("{} {} {} ", .{ ir, ig, ib }) catch return;
}
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, t: f64) bool {
return (self.min <= t) and (t <= self.max);
}
pub fn surrounds(self: Interval, x: f64) bool {
return (self.min < x) and (x < self.max);
}
};
const empty = Interval.init(infinity, -infinity);
const universe = Interval.initInfinity();
const HitRecord = struct {
p: Point3 = Point3.initZero(),
n: Vec3 = Vec3.initZero(),
t: f64 = 0.0,
f: bool = false,
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() HittableList {
return HittableList{
.objects = std.ArrayList(Hittable).init(std.heap.page_allocator),
};
}
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;
}
}
return hit_anything;
}
};
const Sphere = struct {
center: Point3,
radius: f64,
pub fn init(center: Point3, radius: f64) Sphere {
return Sphere{
.center = center,
.radius = @max(0, radius),
};
}
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);
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 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 inline fn neg(self: Vec3) Vec3 {
return Vec3{
.x = -self.x,
.y = -self.y,
.z = -self.z,
};
}
pub inline 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 inline fn subtract(self: Vec3, other: Vec3) Vec3 {
return self.add(other.neg());
}
pub inline 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 inline 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 inline fn length(self: Vec3) f64 {
return std.math.sqrt(self.length_squared());
}
pub inline fn length_squared(self: Vec3) f64 {
return self.x * self.x + self.y * self.y + self.z * self.z;
}
pub inline fn dot(self: Vec3, other: Vec3) f64 {
return self.x * other.x + self.y * other.y + self.z * other.z;
}
pub inline 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 inline fn unitized(self: Vec3) Vec3 {
return self.divide(f64, self.length());
}
};
const Ray = struct {
origin: Point3 = Point3{},
direction: Vec3 = Vec3{},
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