diff --git a/zigrtiow/src/hittable.zig b/zigrtiow/src/hittable.zig new file mode 100644 index 0000000..d009b3f --- /dev/null +++ b/zigrtiow/src/hittable.zig @@ -0,0 +1,36 @@ +const vec = @import("./vec.zig"); +const ray = @import("./ray.zig"); + +const Sphere = @import("./sphere.zig").Sphere; +const HittableList = @import("./hittable_list.zig").HittableList; + +const Vec3 = vec.Vec3; +const Point3 = vec.Point3; +const Ray = ray.Ray; + +pub const Hittable = union(enum) { + sphere: Sphere, + hittable_list: HittableList, + + pub fn hit(hittable: Hittable, r: Ray, t_min: f32, t_max: f32) ?HitRecord { + return switch (hittable) { + .sphere => |sphere| sphere.hit(r, t_min, t_max), + .hittable_list => |hittable_list| hittable_list.hit(r, t_min, t_max), + }; + } +}; + +pub const HitRecord = struct { + p: Point3, + normal: Vec3, + t: f32, + front_face: bool, + + pub fn set_face_normal(hr: *HitRecord, r: Ray, outward_normal: Vec3) void { + hr.front_face = r.direction().dot(outward_normal) < 0; + hr.normal = if (hr.front_face) + outward_normal + else + outward_normal.scale(-1); + } +}; diff --git a/zigrtiow/src/hittable_list.zig b/zigrtiow/src/hittable_list.zig new file mode 100644 index 0000000..a5f5709 --- /dev/null +++ b/zigrtiow/src/hittable_list.zig @@ -0,0 +1,35 @@ +const std = @import("std"); +const ray = @import("./ray.zig"); +const hittable = @import("./hittable.zig"); + +const info = std.log.info; +const ArrayList = std.ArrayList; +const Ray = ray.Ray; +const HitRecord = hittable.HitRecord; +const Hittable = hittable.Hittable; + +pub const HittableList = struct { + objects: ArrayList(Hittable), + + pub fn init() HittableList { + const allocator = std.heap.page_allocator; + + return HittableList{ + .objects = ArrayList(Hittable).init(allocator), + }; + } + pub fn hit(hittable_list: HittableList, r: Ray, t_min: f32, t_max: f32) ?HitRecord { + var temp_rec: ?HitRecord = null; + var closest_so_far = t_max; + for (hittable_list.objects.items) |object| { + if (object.hit(r, t_min, closest_so_far)) |rec| { + temp_rec = rec; + closest_so_far = rec.t; + } + } + return temp_rec; + } + pub fn add(hittable_list: *HittableList, object: Hittable) anyerror!void { + try hittable_list.objects.append(object); + } +}; diff --git a/zigrtiow/src/main.zig b/zigrtiow/src/main.zig index 82d30fc..72dace7 100644 --- a/zigrtiow/src/main.zig +++ b/zigrtiow/src/main.zig @@ -2,27 +2,24 @@ const std = @import("std"); const color = @import("./color.zig"); const ray = @import("./ray.zig"); const vec = @import("./vec.zig"); +const sphere = @import("./sphere.zig"); +const hittable = @import("./hittable.zig"); +const hittable_list = @import("./hittable_list.zig"); const Color = vec.Color; -const Vec3 = vec.Vec3; +const Hittable = hittable.Hittable; +const HittableList = hittable_list.HittableList; const Point3 = vec.Point3; +const Ray = ray.Ray; +const Sphere = sphere.Sphere; +const Vec3 = vec.Vec3; const info = std.log.info; const write_color = color.write_color; -const Ray = ray.Ray; -fn hit_sphere(center: Point3, radius: f32, r: Ray) f32 { - const oc = r.origin().sub(center); - const a = r.direction().length_squared(); - const half_b = Vec3.dot(oc, r.direction()); - const c = oc.length_squared() - radius * radius; - const discriminant = half_b * half_b - a * c; - return if (discriminant < 0) -1 else (-half_b - @sqrt(discriminant)) / a; -} - -fn ray_color(r: Ray) Color { - var t = hit_sphere(Point3.init(0, 0, -1), 0.5, r); - if (t > 0) { - const n = r.at(t).sub(Vec3.init(0, 0, -1)).unit(); +fn ray_color(r: Ray, world: Hittable) Color { + var hit = world.hit(r, 0, std.math.inf(f32)); + if (hit) |rec| { + const n = rec.normal; return Color.init( n.x() + 1, n.y() + 1, @@ -30,7 +27,7 @@ fn ray_color(r: Ray) Color { ).scale(0.5); } var unit_direction = r.direction().unit(); - t = 0.5 * (unit_direction.y() + 1); + const t = 0.5 * (unit_direction.y() + 1); return Color.init(1, 1, 1).scale(1 - t).add(Color.init(0.5, 0.7, 1.0).scale(t)); } @@ -40,6 +37,11 @@ pub fn main() anyerror!void { const image_width = 400; const image_height = @floatToInt(isize, @intToFloat(f32, image_width) / aspect_ratio); + // World + var world = HittableList.init(); + try world.add(Hittable{ .sphere = Sphere.init(Point3.init(0, 0, -1), 0.5) }); + try world.add(Hittable{ .sphere = Sphere.init(Point3.init(0, -100.5, -1), 100) }); + // Camera const viewport_height = 2; const viewport_width = aspect_ratio * viewport_height; @@ -64,7 +66,7 @@ pub fn main() anyerror!void { const u = @intToFloat(f32, i) / @intToFloat(f32, image_width - 1); const v = @intToFloat(f32, j) / @intToFloat(f32, image_height - 1); const r = Ray.init(origin, lower_left_corner.add(horizontal.scale(u)).add(vertical.scale(v)).sub(origin)); - const pixel_color = ray_color(r); + const pixel_color = ray_color(r, Hittable{ .hittable_list = world }); try write_color(pixel_color); } } diff --git a/zigrtiow/src/sphere.zig b/zigrtiow/src/sphere.zig new file mode 100644 index 0000000..81eaa23 --- /dev/null +++ b/zigrtiow/src/sphere.zig @@ -0,0 +1,51 @@ +const vec = @import("./vec.zig"); +const ray = @import("./ray.zig"); +const hittable = @import("./hittable.zig"); + +const Vec3 = vec.Vec3; +const Point3 = vec.Point3; +const Ray = ray.Ray; +const HitRecord = hittable.HitRecord; + +pub const Sphere = struct { + center: Point3, + radius: f32, + + pub fn init(center: Point3, radius: f32) Sphere { + return Sphere{ + .center = center, + .radius = radius, + }; + } + pub fn hit(sphere: Sphere, r: Ray, t_min: f32, t_max: f32) ?HitRecord { + const center = sphere.center; + const radius = sphere.radius; + const oc = r.origin().sub(center); + const a = r.direction().length_squared(); + const half_b = Vec3.dot(oc, r.direction()); + const c = oc.length_squared() - radius * radius; + + const discriminant = half_b * half_b - a * c; + if (discriminant < 0) return null; + + const sqrtd = @sqrt(discriminant); + + // Find the nearest root that lies in the acceptable range. + var root = (-half_b - sqrtd) / a; + + if ((root < t_min) or (t_max < root)) { + root = (-half_b + sqrtd) / a; + if ((root < t_min) or (t_max < root)) return null; + } + const p = r.at(root); + const outward_normal = p.sub(center).scale(1 / radius); + var hr = HitRecord{ + .t = root, + .p = p, + .normal = Vec3.init(0, 0, 0), + .front_face = false, + }; + hr.set_face_normal(r, outward_normal); + return hr; + } +};