const std = @import("std"); const print = std.debug.print; const Random = std.rand.Random; const sdl = @import("sdl.zig"); const vec3 = @import("vec3.zig"); const Vec3 = vec3.Vec3; const Point3 = vec3.Point3; const color = @import("color.zig"); const Color = color.Color; const Ray = @import("ray.zig").Ray; const Sphere = @import("sphere.zig").Sphere; const World = @import("world.zig").World; const Camera = @import("camera.zig").Camera; const material = @import("material.zig"); const Material = material.Material; // From: https://github.com/Nelarius/weekend-raytracer-zig/blob/master/src/main.zig // See https://github.com/zig-lang/zig/issues/565 // SDL_video.h:#define SDL_WINDOWPOS_UNDEFINED SDL_WINDOWPOS_UNDEFINED_DISPLAY(0) // SDL_video.h:#define SDL_WINDOWPOS_UNDEFINED_DISPLAY(X) (SDL_WINDOWPOS_UNDEFINED_MASK|(X)) // SDL_video.h:#define SDL_WINDOWPOS_UNDEFINED_MASK 0x1FFF0000u const SDL_WINDOWPOS_UNDEFINED = @bitCast(c_int, sdl.c.SDL_WINDOWPOS_UNDEFINED_MASK); const fps = 60; fn hitSphere(center: Point3, radius: f32, ray: Ray) f32 { const ssr = ray.origin.sub(center); // Sphere-space ray, (A - C) in book const a = ray.direction.length_squared(); const half_b = Vec3.dot(ssr, ray.direction); const c = ssr.length_squared() - (radius * radius); const discriminant = half_b * half_b - a * c; if (discriminant < 0) { return -1; } else { return (-half_b - std.math.sqrt(discriminant)) / a; } } fn rayColor(ray: Ray, world: World, rng: *Random, depth: i32) Color { // If we've exceeded the ray bounce limit, no more light is gathered. if (depth <= 0) { return Color{ .x = 0, .y = 0, .z = 0 }; } if (world.hit(ray, 0.001, 99999)) |hit| { if (material.scatter(ray, hit, rng)) |sRay| { return sRay.color.mul(rayColor(sRay.ray, world, rng, depth - 1)); } return Color{ .x = 0, .y = 0, .z = 0 }; } const unitDirection = ray.direction.unit(); const t = 0.5 * (unitDirection.y + 1.0); const white = Color{ .x = 1.0, .y = 1.0, .z = 1.0 }; const blue = Color{ .x = 0.5, .y = 0.7, .z = 1.0 }; return white.mul_s(1.0 - t).add(blue.mul_s(t)); } fn setProgress(surface: *sdl.c.SDL_Surface, width: usize, height: usize, percent: f32) void { if (percent == 1.0) { return; } const progressBarHeight = 3; const progressWidth = @floatToInt(usize, @intToFloat(f32, width) * percent); const progressColor = Color{ .x = 1.0 }; var j: usize = 0; while (j < progressBarHeight) { var i: usize = 0; while (i < progressWidth) { sdl.setSurfacePixel(surface, i, j, progressColor); i += 1; } j += 1; } } fn setupWorld(world: *World, rng: *Random) !void { try world.spheres.append(Sphere{ .center = Point3{ .x = 0, .y = -1000, .z = 0 }, .radius = 1000, .material = Material{ .lambertian = material.Lambertian{ .color = Color{ .x = 0.5, .y = 0.5, .z = 0.5 } }} }); var a: f32 = -11; while (a < 11) : (a += 1) { var b: f32 = -11; while (b < 11) : (b += 1) { const chooseMat = rng.float(f32); const center = Point3{ .x = a + 0.9 * rng.float(f32), .y = 0.2, .z = b + 0.9 * rng.float(f32), }; if (center.sub(Point3{ .x = 4, .y = 0.2 }).length() > 0.9) { if (chooseMat < 0.8) { // diffuse try world.spheres.append(Sphere{ .center = center, .radius = 0.2, .material = Material{ .lambertian = material.Lambertian{ .color = Color{ .x = rng.float(f32) * rng.float(f32), .y = rng.float(f32) * rng.float(f32), .z = rng.float(f32) * rng.float(f32), } }}, }); } else if (chooseMat < 0.95) { // metal try world.spheres.append(Sphere{ .center = center, .radius = 0.2, .material = Material{ .metal = material.Metal{ .color = Color{ .x = 0.5 + rng.float(f32) / 2, .y = 0.5 + rng.float(f32) / 2, .z = 0.5 + rng.float(f32) / 2, }, .fuzz = rng.float(f32) / 2, }}, }); } else { // glass try world.spheres.append(Sphere{ .center = center, .radius = 0.2, .material = Material{ .dielectric = material.Dielectric{ .refraction_index = 1.5, }}, }); } } } } try world.spheres.append(Sphere{ .center = Point3{ .x = 0, .y = 1, .z = 0 }, .radius = 1, .material = Material{ .dielectric = material.Dielectric{ .refraction_index = 1.5 }}, }); try world.spheres.append(Sphere{ .center = Point3{ .x = -4, .y = 1, .z = 0 }, .radius = 1, .material = Material{ .lambertian = material.Lambertian{ .color = Color{ .x = 0.4, .y = 0.2, .z = 0.1 } }}, }); try world.spheres.append(Sphere{ .center = Point3{ .x = 4, .y = 1, .z = 0 }, .radius = 1, .material = Material{ .metal = material.Metal{ .color = Color{ .x = 0.7, .y = 0.6, .z = 0.5 }, .fuzz = 0.0 }}, }); } pub fn main() anyerror!void { if (sdl.c.SDL_Init(sdl.c.SDL_INIT_VIDEO) != 0) { std.log.err("Unable to initialize SDL: {}", .{sdl.c.SDL_GetError()}); return error.SDLInitializationFailed; } defer sdl.c.SDL_Quit(); var prng = std.rand.DefaultPrng.init(42); // Image const aspectRatio: f32 = 3.0 / 2.0; const imageWidth = 600; const imageHeight = @floatToInt(usize, imageWidth / aspectRatio); const samplesPerPixel = 128; const maxDepth = 16; // World var world = World.init(); defer world.deinit(); try setupWorld(&world, &prng.random); // Camera const lookFrom = Point3{ .x = 13, .y = 2, .z = 3 }; const lookAt = Point3{ .x = 0, .y = 0, .z = 0 }; const vup = Vec3{ .x = 0, .y = 1, .z = 0 }; const distToFocus = 10; const aperture = 0.1; const camera = Camera.init( lookFrom, lookAt, vup, 20.0, aspectRatio, aperture, distToFocus ); const window = sdl.c.SDL_CreateWindow( "Raytracing in One Weekend", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, imageWidth, imageHeight, sdl.c.SDL_WINDOW_OPENGL ) orelse { std.log.err("Unable to create window: {}", .{sdl.c.SDL_GetError()}); return error.SDLInitializationFailed; }; const surface = sdl.c.SDL_GetWindowSurface(window) orelse { std.log.err("Unable to get window surface: {}", .{sdl.c.SDL_GetError()}); return error.SDLInitializationFailed; }; var pixelAccu: [imageWidth * imageHeight]Color = undefined; for (pixelAccu) |*pixel| { pixel.* = Color{}; } if (sdl.c.SDL_UpdateWindowSurface(window) != 0) { std.log.err("Error updating window surface: {}", .{sdl.c.SDL_GetError()}); return error.SDLUpdateWindowFailed; } var running = true; var k: usize = 0; while (running) { var event: sdl.c.SDL_Event = undefined; while (sdl.c.SDL_PollEvent(&event) != 0) { switch (event.@"type") { sdl.c.SDL_QUIT => { running = false; }, else => {}, } } if (k < samplesPerPixel) { // Render _ = sdl.c.SDL_LockSurface(surface); var j: usize = 0; while (j < imageHeight) { var i: usize = 0; while (i < imageWidth) { var imageIndex = i + j * imageWidth; var pixelColor = &pixelAccu[imageIndex]; const u = (@intToFloat(f32, i) + prng.random.float(f32)) / @intToFloat(f32, (imageWidth - 1)); const v = (@intToFloat(f32, j) + prng.random.float(f32)) / @intToFloat(f32, (imageHeight - 1)); const r = camera.getRay(u, v, &prng.random); const sample = rayColor(r, world, &prng.random, maxDepth); pixelColor.* = pixelColor.*.add(sample); const averageColor = color.averageColor(pixelColor, @intCast(i32, k)); // SDL coordinate system is flipped compared to the raytracer sdl.setSurfacePixel(surface, i, imageHeight - 1 - j, averageColor); i += 1; } j += 1; } setProgress(surface, imageWidth, imageHeight, @intToFloat(f32, k + 1) / @intToFloat(f32, samplesPerPixel)); sdl.c.SDL_UnlockSurface(surface); _ = sdl.c.SDL_UpdateWindowSurface(window); k += 1; } else { sdl.c.SDL_Delay(1000 / fps); } } }