Skip to content

Commit

Permalink
Add triangles
Browse files Browse the repository at this point in the history
  • Loading branch information
SinclaM committed Jul 19, 2023
1 parent 440527a commit 735e574
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 1 deletion.
14 changes: 13 additions & 1 deletion src/parser/parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ fn ObjectConfig(comptime T: type) type {
@"type": union(enum) {
@"from-definition": []const u8,
sphere: void,
plane: void,
cube: void,
cylinder: *struct {
min: T = -std.math.inf(T),
Expand All @@ -89,6 +88,12 @@ fn ObjectConfig(comptime T: type) type {
max: T = std.math.inf(T),
closed: bool = false,
},
triangle: *struct {
p1: [3]T,
p2: [3]T,
p3: [3]T,
},
plane: void,
group: []ObjectConfig(T),
},
transform: ?TransformConfig(T) = null,
Expand Down Expand Up @@ -276,6 +281,13 @@ fn parseObject(
shape.*.variant.cone.max = cyl.max;
shape.*.variant.cone.closed = cyl.closed;
},
.triangle => |tri| {
shape.* = Shape(T).triangle(
Tuple(T).point(tri.p1[0], tri.p1[1], tri.p1[2]),
Tuple(T).point(tri.p2[0], tri.p2[1], tri.p2[2]),
Tuple(T).point(tri.p3[0], tri.p3[1], tri.p3[2])
);
},
.group => |children| {
shape.* = Shape(T).group(allocator);

Expand Down
23 changes: 23 additions & 0 deletions src/raytracer/shapes/shape.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const Sphere = @import("sphere.zig").Sphere;
const Cube = @import("cube.zig").Cube;
const Cylinder = @import("cylinder.zig").Cylinder;
const Cone = @import("cone.zig").Cone;
const Triangle = @import("triangle.zig").Triangle;
const Plane = @import("plane.zig").Plane;
const Group = @import("group.zig").Group;
const PreComputations = @import("../world.zig").PreComputations;
Expand Down Expand Up @@ -82,6 +83,7 @@ pub fn Shape(comptime T: type) type {
cylinder: Cylinder(T),
cone: Cone(T),
plane: Plane(T),
triangle: Triangle(T),
group: Group(T),
};

Expand Down Expand Up @@ -161,6 +163,27 @@ pub fn Shape(comptime T: type) type {
return Self.new(Self.Variant { .cone = Cone(T) {} });
}

/// Creates a new triangle.
pub fn triangle(p1: Tuple(T), p2: Tuple(T), p3: Tuple(T)) Self {
const e1 = p2.sub(p1);
const e2 = p3.sub(p1);

const normal = e2.cross(e1).normalized();

return Self.new(
Self.Variant {
.triangle = Triangle(T) {
.p1 = p1,
.p2 = p2,
.p3 = p3,
.e1 = e1,
.e2 = e2,
.normal = normal
}
}
);
}

/// Creates a new plane.
pub fn plane() Self {
return Self.new(Self.Variant { .plane = Plane(T) {} });
Expand Down
187 changes: 187 additions & 0 deletions src/raytracer/shapes/triangle.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
const std = @import("std");
const testing = std.testing;
const Allocator = std.mem.Allocator;
const inf = std.math.inf;

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 triangle object, backed by floats of type `T`.
pub fn Triangle(comptime T: type) type {
return struct {
const Self = @This();
const tolerance = 1e-5;

p1: Tuple(T),
p2: Tuple(T),
p3: Tuple(T),
e1: Tuple(T),
e2: Tuple(T),
normal: Tuple(T),

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

const dir_cross_e2 = ray.direction.cross(self.e2);
const det = self.e1.dot(dir_cross_e2);

if (@fabs(det) < Self.tolerance) {
// The ray is parallel and misses.
return xs;
}

const f = 1.0 / det;
const p1_to_origin = ray.origin.sub(self.p1);
const u = f * p1_to_origin.dot(dir_cross_e2);

if (u < 0.0 or u > 1.0) {
// The ray passes beyond the p1-p3 edge and misses.
return xs;
}

const p1_to_origin_cross_e1 = p1_to_origin.cross(self.e1);
const v = f * ray.direction.dot(p1_to_origin_cross_e1);

if (v < 0.0 or (u + v) > 1.0) {
// The ray passes beyond the p1-p2 or the p2-p3 edge and misses.
return xs;
}

const t = f * self.e2.dot(p1_to_origin_cross_e1);
try xs.append(Intersection(T).new(t, super));

return xs;
}

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

return self.normal;
}
};
}

test "Constructing a triangle" {
const p1 = Tuple(f32).point(0.0, 1.0, 0.0);
const p2 = Tuple(f32).point(-1.0, 0.0, 0.0);
const p3 = Tuple(f32).point(1.0, 0.0, 0.0);

const t = Shape(f32).triangle(p1, p2, p3);

const triangle = &t.variant.triangle;

try testing.expect(triangle.p1.approxEqual(p1));
try testing.expect(triangle.p2.approxEqual(p2));
try testing.expect(triangle.p3.approxEqual(p3));

try testing.expect(triangle.e1.approxEqual(Tuple(f32).vec3(-1.0, -1.0, 0.0)));
try testing.expect(triangle.e2.approxEqual(Tuple(f32).vec3(1.0, -1.0, 0.0)));
try testing.expect(triangle.normal.approxEqual(Tuple(f32).vec3(0.0, 0.0, -1.0)));
}

test "Finding the normal on a triangle" {
const t = Shape(f32).triangle(
Tuple(f32).point(0.0, 1.0, 0.0),
Tuple(f32).point(-1.0, 0.0, 0.0),
Tuple(f32).point(1.0, 0.0, 0.0),
);

const n1 = t.normalAt(Tuple(f32).point(0.0, 0.5, 0.0));
const n2 = t.normalAt(Tuple(f32).point(-0.5, 0.75, 0.0));
const n3 = t.normalAt(Tuple(f32).point(0.5, 0.25, 0.0));

try testing.expectEqual(n1, t.variant.triangle.normal);
try testing.expectEqual(n2, t.variant.triangle.normal);
try testing.expectEqual(n3, t.variant.triangle.normal);
}

test "Intersecting a ray parallel to the triangle" {
const allocator = testing.allocator;

const t = Shape(f32).triangle(
Tuple(f32).point(0.0, 1.0, 0.0),
Tuple(f32).point(-1.0, 0.0, 0.0),
Tuple(f32).point(1.0, 0.0, 0.0),
);

const r = Ray(f32).new(Tuple(f32).point(0.0, -1.0, -2.0), Tuple(f32).vec3(0.0, 1.0, 0.0));
const xs = try t.intersect(allocator, r);
defer xs.deinit();

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

test "A ray misses the p1-p3 edge" {
const allocator = testing.allocator;

const t = Shape(f32).triangle(
Tuple(f32).point(0.0, 1.0, 0.0),
Tuple(f32).point(-1.0, 0.0, 0.0),
Tuple(f32).point(1.0, 0.0, 0.0),
);

const r = Ray(f32).new(Tuple(f32).point(1.0, 1.0, -2.0), Tuple(f32).vec3(0.0, 0.0, 1.0));
const xs = try t.intersect(allocator, r);
defer xs.deinit();

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

test "A ray misses the p1-p2 edge" {
const allocator = testing.allocator;

const t = Shape(f32).triangle(
Tuple(f32).point(0.0, 1.0, 0.0),
Tuple(f32).point(-1.0, 0.0, 0.0),
Tuple(f32).point(1.0, 0.0, 0.0),
);

const r = Ray(f32).new(Tuple(f32).point(-1.0, 1.0, -2.0), Tuple(f32).vec3(0.0, 0.0, 1.0));
const xs = try t.intersect(allocator, r);
defer xs.deinit();

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

test "A ray misses the p2-p3 edge" {
const allocator = testing.allocator;

const t = Shape(f32).triangle(
Tuple(f32).point(0.0, 1.0, 0.0),
Tuple(f32).point(-1.0, 0.0, 0.0),
Tuple(f32).point(1.0, 0.0, 0.0),
);

const r = Ray(f32).new(Tuple(f32).point(0.0, -1.0, -2.0), Tuple(f32).vec3(0.0, 0.0, 1.0));
const xs = try t.intersect(allocator, r);
defer xs.deinit();

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

test "A ray strikes a triangle" {
const allocator = testing.allocator;

const t = Shape(f32).triangle(
Tuple(f32).point(0.0, 1.0, 0.0),
Tuple(f32).point(-1.0, 0.0, 0.0),
Tuple(f32).point(1.0, 0.0, 0.0),
);

const r = Ray(f32).new(Tuple(f32).point(0.0, 0.5, -2.0), Tuple(f32).vec3(0.0, 0.0, 1.0));
const xs = try t.intersect(allocator, r);
defer xs.deinit();

try testing.expectEqual(xs.items.len, 1);
try testing.expectEqual(xs.items[0].t, 2.0);
}
1 change: 1 addition & 0 deletions src/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ comptime {
_ = @import("raytracer/shapes/cylinder.zig");
_ = @import("raytracer/shapes/cone.zig");
_ = @import("raytracer/shapes/plane.zig");
_ = @import("raytracer/shapes/triangle.zig");
_ = @import("raytracer/shapes/group.zig");
_ = @import("raytracer/patterns/pattern.zig");
_ = @import("raytracer/patterns/solid.zig");
Expand Down

0 comments on commit 735e574

Please sign in to comment.