Skip to content

Commit

Permalink
Add groups
Browse files Browse the repository at this point in the history
  • Loading branch information
SinclaM committed Jul 17, 2023
1 parent d816a1c commit af96783
Show file tree
Hide file tree
Showing 6 changed files with 383 additions and 10 deletions.
114 changes: 114 additions & 0 deletions src/examples/hexagon.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
const std = @import("std");
const pi = std.math.pi;
const Allocator = std.mem.Allocator;

const Tuple = @import("../raytracer/tuple.zig").Tuple;
const Matrix = @import("../raytracer/matrix.zig").Matrix;
const Color = @import("../raytracer/color.zig").Color;
const Material = @import("../raytracer/material.zig").Material;
const Shape = @import("../raytracer/shapes/shape.zig").Shape;
const Light = @import("../raytracer/light.zig").Light;
const World = @import("../raytracer/world.zig").World;
const Camera = @import("../raytracer/camera.zig").Camera;
const Pattern = @import("../raytracer/patterns/pattern.zig").Pattern;

fn hexagonCorner(comptime T: type) !Shape(T) {
var corner = Shape(T).sphere();
try corner.setTransform(
Matrix(T, 4)
.identity()
.scale(0.25, 0.25, 0.25)
.translate(0.0, 0.0, -1.0)
);

return corner;
}

fn hexagonEdge(comptime T: type) !Shape(T) {
var edge = Shape(T).cylinder();
edge.variant.cylinder.min = 0.0;
edge.variant.cylinder.max = 1.0;

try edge.setTransform(
Matrix(T, 4)
.identity()
.scale(0.25, 1.0, 0.25)
.rotateZ(-pi / 2.0)
.rotateY(-pi / 6.0)
.translate(0.0, 0.0, -1.0)
);

return edge;
}

fn hexagonSide(comptime T: type, allocator: Allocator) !*Shape(T) {
var side = try allocator.create(Shape(T));
side.* = Shape(T).group(allocator);

var corner = try allocator.create(Shape(T));
corner.* = try hexagonCorner(T);

var edge = try allocator.create(Shape(T));
edge.* = try hexagonEdge(T);

try side.addChild(corner);
try side.addChild(edge);

return side;
}

fn hexagon(comptime T: type, allocator: Allocator) !*Shape(T) {
var hex = try allocator.create(Shape(T));
hex.* = Shape(T).group(allocator);

for (0..6) |n| {
var side = try hexagonSide(T, allocator);
try side.setTransform(Matrix(T, 4).identity().rotateY(@as(T, @floatFromInt(n)) * pi / 3.0));

try hex.addChild(side);
}

return hex;
}


pub fn renderHexagon() !void {
// Use an arena for the (few) allocations needed to make the hexagon so
// that we don't have to track them down one-by-one to free them.
var arena = std.heap.ArenaAllocator.init(std.heap.raw_c_allocator);
defer arena.deinit();
var hex = try hexagon(f64, arena.allocator());

const allocator = std.heap.raw_c_allocator;

var world = World(f64).new(allocator);
defer world.destroy();

try world.objects.append(hex.*);

try world.lights.append(Light(f64).pointLight(
Tuple(f64).point(2.0, 10.0, -5.0), Color(f64).new(0.9, 0.9, 0.9)
));

var camera = Camera(f64).new(500, 500, 0.45);
try camera.setTransform(
Matrix(f64, 4).viewTransform(
Tuple(f64).point(0.0, 3.0, -5.0), Tuple(f64).point(0.0, 0.0, 0.0), Tuple(f64).vec3(0.0, 1.0, 0.0)
)
);

const canvas = try camera.render(allocator, world);
defer canvas.destroy();

const ppm = try canvas.ppm(allocator);
defer allocator.free(ppm);

const file = try std.fs.cwd().createFile(
"images/hexagon.ppm",
.{ .read = true },
);
defer file.close();

_ = try file.writeAll(ppm);
}

2 changes: 2 additions & 0 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const sphere = @import("examples/sphere.zig");
const simple_world = @import("examples/simple_world.zig");
const simple_superflat = @import("examples/simple_superflat.zig");
const fresnel = @import("examples/fresnel.zig");
const hexagon = @import("examples/hexagon.zig");

const parseScene = @import("parser/parser.zig").parseScene;

Expand All @@ -18,6 +19,7 @@ pub fn main() !void {
try simple_world.renderSimpleWorld();
try simple_superflat.renderSimpleSuperflat();
try fresnel.renderFresnel();
try hexagon.renderHexagon();

const scenes_to_render = [_][]const u8 {
"ch11_reflection_and_refraction",
Expand Down
2 changes: 1 addition & 1 deletion src/raytracer/patterns/pattern.zig
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ pub fn Pattern(comptime T: type) type {
///
/// Assumes `world_point` is a point.
pub fn patternAtShape(self: Self, shape: *const Shape(T), world_point: Tuple(T)) Color(T) {
const object_point = shape._inverse_transform.tupleMul(world_point);
const object_point = shape.worldToObject(world_point);
return self.patternAt(object_point);
}
};
Expand Down
145 changes: 145 additions & 0 deletions src/raytracer/shapes/group.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
const std = @import("std");
const testing = std.testing;
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;

const Tuple = @import("../tuple.zig").Tuple;
const Matrix = @import("../matrix.zig").Matrix;
const Ray = @import("../ray.zig").Ray;

const shape = @import("shape.zig");
const Intersection = shape.Intersection;
const Intersections = shape.Intersections;
const sortIntersections = shape.sortIntersections;
const Shape = shape.Shape;

/// A group of objects, backed by floats of type `T`.
pub fn Group(comptime T: type) type {
return struct {
const Self = @This();

children: ArrayList(*Shape(T)),

pub fn destroy(self: Self) void {
self.children.deinit();
}

pub fn localIntersect(
self: Self, allocator: Allocator, super: *const Shape(T), ray: Ray(T)
) !Intersections(T) {
_ = super;

var all = Intersections(T).init(allocator);

for (self.children.items) |child| {
const xs: Intersections(T) = try child.intersect(allocator, ray);
defer xs.deinit();

try all.appendSlice(xs.items);
}

sortIntersections(T, all.items);

return all;
}

pub fn localNormalAt(self: Self, super: Shape(T), point: Tuple(T)) Tuple(T) {
_ = self;
_ = super;
_ = point;

// TODO: can this be a compile error with duck typing?

@panic("`localNormalAt` not implemented for groups");
}
};
}

test "Creating a new group" {
const allocator = testing.allocator;

const g = Shape(f32).group(allocator);
defer g.variant.group.destroy();

try testing.expectEqual(g._transform, Matrix(f32, 4).identity());
try testing.expectEqual(g.variant.group.children.items.len, 0);
}

test "Adding a child to a group" {
const allocator = testing.allocator;

var g = Shape(f32).group(allocator);
defer g.variant.group.destroy();

var s = Shape(f32).testShape();

try g.addChild(&s);

try testing.expectEqual(g.variant.group.children.items.len, 1);
try testing.expectEqual(g.variant.group.children.items[0], &s);
try testing.expectEqual(s.parent, &g);
}

test "Intersecting a ray with an empty group" {
const allocator = testing.allocator;

var g = Shape(f32).group(allocator);
defer g.variant.group.destroy();

const r = Ray(f32).new(Tuple(f32).point(0.0, 0.0, 0.0), Tuple(f32).vec3(0.0, 0.0, 1.0));

const xs = try g.intersect(allocator, r);
defer xs.deinit();

try testing.expectEqual(xs.items.len, 0);
}

test "Intersecting a ray with an nonempty group" {
const allocator = testing.allocator;

var g = Shape(f32).group(allocator);
defer g.variant.group.destroy();

var s1 = Shape(f32).sphere();
var s2 = Shape(f32).sphere();
try s2.setTransform(Matrix(f32, 4).identity().translate(0.0, 0.0, -3.0));
var s3 = Shape(f32).sphere();
try s3.setTransform(Matrix(f32, 4).identity().translate(5.0, 0.0, 0.0));

try g.addChild(&s1);
try g.addChild(&s2);
try g.addChild(&s3);

const r = Ray(f32).new(Tuple(f32).point(0.0, 0.0, -5.0), Tuple(f32).vec3(0.0, 0.0, 1.0));

const xs = try g.intersect(allocator, r);
defer xs.deinit();

try testing.expectEqual(xs.items.len, 4);

try testing.expectEqual(xs.items[0].object, &s2);
try testing.expectEqual(xs.items[1].object, &s2);
try testing.expectEqual(xs.items[2].object, &s1);
try testing.expectEqual(xs.items[3].object, &s1);
}

test "Intersecting a transformed group" {
const allocator = testing.allocator;

var g = Shape(f32).group(allocator);
defer g.variant.group.destroy();

try g.setTransform(Matrix(f32, 4).identity().scale(2.0, 2.0, 2.0));

var s = Shape(f32).sphere();
try s.setTransform(Matrix(f32, 4).identity().translate(5.0, 0.0, 0.0));

try g.addChild(&s);

const r = Ray(f32).new(Tuple(f32).point(10.0, 0.0, -10.0), Tuple(f32).vec3(0.0, 0.0, 1.0));

const xs = try g.intersect(allocator, r);
defer xs.deinit();

try testing.expectEqual(xs.items.len, 2);
}
Loading

0 comments on commit af96783

Please sign in to comment.