commit e8c54fdd890cb1cd5da91b2de598de228d03a766
parent 50544678c6d4e3c38597da9dcf23562fa02942c6
Author: Christian Ermann <christianermann@gmail.com>
Date: Sun, 8 Dec 2024 13:46:44 -0800
Add UV sphere
Diffstat:
3 files changed, 184 insertions(+), 2 deletions(-)
diff --git a/src/camera.zig b/src/camera.zig
@@ -75,12 +75,12 @@ fn f32x3_dot(u: f32x3, v: f32x3) f32 {
return @reduce(.Add, u * v);
}
-fn f32x3_normalize(v: f32x3) f32x3 {
+pub fn f32x3_normalize(v: f32x3) f32x3 {
return v / @as(f32x3, @splat(@sqrt(f32x3_dot(v, v))));
}
/// Modified from https://geometrian.com/programming/tutorials/cross-product/index.php
-fn f32x3_mul(u: f32x3, v: f32x3) f32x3 {
+pub fn f32x3_mul(u: f32x3, v: f32x3) f32x3 {
const mask = @Vector(3, i32){ 1, 2, 0 };
const t1 = u * @shuffle(f32, v, undefined, mask);
const t2 = v * @shuffle(f32, u, undefined, mask);
diff --git a/src/main.zig b/src/main.zig
@@ -428,6 +428,9 @@ pub fn main() !void {
const cbunny_mesh = try buildClusters(allocator, &bunny_mesh, 64, 126);
std.log.info("num clusters: {}", .{cluster_mesh.descriptors.?.len});
+ const generateUVSphere = @import("uvsphere.zig").generateUVSphere;
+ const sphere_mesh = try generateUVSphere(16, 16, allocator);
+
var rp = try MeshRenderPipeline.init(
&app,
.{
@@ -458,6 +461,7 @@ pub fn main() !void {
var transform_1 = Transform{};
var transform_2 = Transform{};
+ var transform_3 = Transform{ .offset = .{ 3, 3, 3 } };
var render_mesh = try render_data.addMesh(
&transform_1,
@@ -471,6 +475,12 @@ pub fn main() !void {
allocator,
);
+ _ = try render_data.addMesh(
+ &transform_3,
+ &sphere_mesh,
+ allocator,
+ );
+
const corners = mesh.bboxVertices();
const bbox_indices = Mesh.bboxIndices();
diff --git a/src/uvsphere.zig b/src/uvsphere.zig
@@ -0,0 +1,172 @@
+const std = @import("std");
+
+const Mesh = @import("mesh.zig");
+const f32x3_normalize = @import("camera.zig").f32x3_normalize;
+const f32x3_mul = @import("camera.zig").f32x3_mul;
+
+const f32x3 = @Vector(3, f32);
+const f32x4 = @Vector(4, f32);
+
+const pi: f32 = 3.14159;
+
+pub fn generateUVSphere(
+ n_rows: u32,
+ n_cols: u32,
+ allocator: std.mem.Allocator,
+) !Mesh {
+ const positions = try generatePositions(n_rows, n_cols, allocator);
+ const normals = try generateNormals(positions, allocator);
+ const tex_coords = try generateTexCoordsEqualArea(normals, allocator);
+ const tangents = try generateTangents(positions, allocator);
+ const bitangents = try generateBitangents(normals, tangents, allocator);
+
+ const n_vertices: u32 = @intCast(positions.len);
+ const indices = try generateIndices(n_rows, n_cols, n_vertices, allocator);
+
+ return .{
+ .positions = positions,
+ .normals = normals,
+ .tex_coords = tex_coords,
+ .indices = indices,
+ .tangents = tangents,
+ .bitangents = bitangents,
+ };
+}
+
+fn generatePositions(
+ n_rows: u32,
+ n_cols: u32,
+ allocator: std.mem.Allocator,
+) ![]f32x4 {
+ var positions = std.ArrayList(f32x4).init(allocator);
+ defer positions.deinit();
+ // top
+ try positions.append(f32x4{ 0.0, 1.0, 0.0, 1.0 });
+ // middle
+ for (1..n_rows) |i| {
+ const phi = pi * @as(f32, @floatFromInt(i)) / @as(f32, @floatFromInt(n_rows));
+ for (0..n_cols) |j| {
+ const theta = 2.0 * pi * @as(f32, @floatFromInt(j)) / @as(f32, @floatFromInt(n_cols));
+ const x = @sin(phi) * @cos(theta);
+ const y = @cos(phi);
+ const z = @sin(phi) * @sin(theta);
+ try positions.append(f32x4{ x, y, z, 1.0 });
+ }
+ }
+ // bottom
+ try positions.append(f32x4{ 0.0, -1.0, 0.0, 1.0 });
+ return try positions.toOwnedSlice();
+}
+
+fn generateNormals(
+ positions: []f32x4,
+ allocator: std.mem.Allocator,
+) ![]f32x3 {
+ const normals = try allocator.alloc(f32x3, positions.len);
+ for (positions, normals) |p, *n| {
+ n.* = f32x3_normalize(.{ p[0], p[1], p[2] });
+ }
+ return normals;
+}
+
+fn generateTangents(
+ positions: []f32x4,
+ allocator: std.mem.Allocator,
+) ![]f32x3 {
+ const up = f32x3{ 0, 1, 0 };
+ const tangents = try allocator.alloc(f32x3, positions.len);
+ for (positions, tangents) |p, *t| {
+ t.* = f32x3_normalize(f32x3_mul(up, .{ p[0], p[1], p[2] }));
+ }
+ tangents[0] = .{ 1, 0, 0 };
+ tangents[tangents.len - 1] = .{ 1, 0, 0 };
+ return tangents;
+}
+
+fn generateBitangents(
+ normals: []f32x3,
+ tangents: []f32x3,
+ allocator: std.mem.Allocator,
+) ![]f32x3 {
+ const bitangents = try allocator.alloc(f32x3, normals.len);
+ for (normals, tangents, bitangents) |n, t, *b| {
+ b.* = f32x3_mul(n, t);
+ }
+ bitangents[0] = .{ 0, 0, 1 };
+ bitangents[bitangents.len - 1] = .{ 0, 0, 1 };
+ return bitangents;
+}
+
+fn generateTexCoordsEqualArea(
+ normals: []f32x3,
+ allocator: std.mem.Allocator,
+) ![]f32x3 {
+ const tex_coords = try allocator.alloc(f32x3, normals.len);
+ for (normals, tex_coords) |n, *tc| {
+ tc.* = .{
+ (std.math.atan2(n[0], -n[2]) / pi + 1.0) * 0.5,
+ std.math.asin(n[1]) / pi + 0.5,
+ 0.0,
+ };
+ }
+ return tex_coords;
+}
+
+fn generateTexCoordsEquirectangular(
+ normals: []f32x3,
+ allocator: std.mem.Allocator,
+) ![]f32x3 {
+ const tex_coords = try allocator.alloc(f32x3, normals.len);
+ for (normals, tex_coords) |n, *tc| {
+ tc.* = .{
+ // equirectangular
+ std.math.atan2(n[0], n[2]) / (2.0 * pi) + 0.5,
+ n[1] * 0.5 + 0.5,
+ 0.0,
+ };
+ }
+}
+
+fn generateIndices(
+ n_rows: u32,
+ n_cols: u32,
+ n_vertices: u32,
+ allocator: std.mem.Allocator,
+) ![]u32 {
+ var indices = std.ArrayList(u32).init(allocator);
+ defer indices.deinit();
+ // top
+ for (0..n_cols) |i| {
+ const a = (i + 1) % n_rows + 1;
+ const b = i + 1;
+ try indices.append(0);
+ try indices.append(@intCast(a));
+ try indices.append(@intCast(b));
+ }
+ // bottom
+ for (0..n_cols) |i| {
+ const a = i + n_cols * (n_rows - 2) + 1;
+ const b = (i + 1) % n_cols + n_cols * (n_rows - 2) + 1;
+ try indices.append(@intCast(n_vertices - 1));
+ try indices.append(@intCast(a));
+ try indices.append(@intCast(b));
+ }
+ // center
+ for (0..n_rows - 2) |j| {
+ const x = j * n_cols + 1;
+ const y = (j + 1) * n_cols + 1;
+ for (0..n_cols) |i| {
+ const a = x + i;
+ const b = x + (i + 1) % n_cols;
+ const c = y + (i + 1) % n_cols;
+ const d = y + i;
+ try indices.append(@intCast(a));
+ try indices.append(@intCast(b));
+ try indices.append(@intCast(c));
+ try indices.append(@intCast(a));
+ try indices.append(@intCast(c));
+ try indices.append(@intCast(d));
+ }
+ }
+ return try indices.toOwnedSlice();
+}