Zig library for loading, generating, processing and optimising triangle meshes.
Under the hood this library uses below C/C++ libraries:
All memory allocations go through user-supplied, Zig allocator.
As an example program please see procedural mesh (wgpu).
Example build.zig:
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{ ... });
const zmesh = b.dependency("zmesh", .{});
exe.root_module.addImport("zmesh", zmesh.module("root"));
exe.linkLibrary(zmesh.artifact("zmesh"));
}Zemscripten build.zig example:
pub fn build(b: *std.Build) void {
const target = b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.os_tag = .emscripten,
});
const wasm = b.addLibrary(.{ .name = "zemscripten-build", .linkage = .static, .root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = target,
.optimize = optimize,
}) });
// Add Emscripten include paths for @cImport
const emsdk_path = b.dependency("emsdk", .{}).path("").getPath(b);
const emscripten_include_path = b.pathJoin(&.{ emsdk_path, "upstream", "emscripten", "cache", "sysroot", "include" });
wasm.root_module.addSystemIncludePath(.{ .cwd_relative = emscripten_include_path });
const zemscripten = b.dependency("zemscripten", .{});
const zmesh = b.dependency("zmesh", .{
.target = target,
.optimize = optimize,
.emscripten_include_path = emscripten_include_path,
});
wasm.root_module.addImport("zmesh", zmesh.module("root"));
wasm.linkLibrary(zmesh.artifact("zmesh"));
...
}Now in your code you may import and use zmesh:
const zmesh = @import("zmesh");
pub fn main() !void {
...
zmesh.init(allocator);
defer zmesh.deinit();
var custom = zmesh.Shape.init(indices, positions, normals, texcoords);
defer custom.deinit();
var disk = zmesh.Shape.initParametricDisk(10, 2);
defer disk.deinit();
disk.invert(0, 0);
var cylinder = zmesh.Shape.initCylinder(10, 4);
defer cylinder.deinit();
cylinder.merge(disk);
cylinder.translate(0, 0, -1);
disk.invert(0, 0);
cylinder.merge(disk);
cylinder.scale(0.5, 0.5, 2);
cylinder.rotate(math.pi * 0.5, 1.0, 0.0, 0.0);
cylinder.unweld();
cylinder.computeNormals();
...
}const zmesh = @import("zmesh");
pub fn main() !void {
zmesh.init(allocator);
defer zmesh.deinit();
//
// Load mesh
//
const data = try zmesh.io.zcgltf.parseAndLoadFile(content_dir ++ "cube.gltf");
defer zmesh.io.zcgltf.freeData(data);
var mesh_indices = std.ArrayListUnmanaged(u32){};
var mesh_positions = std.ArrayListUnmanaged([3]f32){};
var mesh_normals = std.ArrayListUnmanaged([3]f32){};
try zmesh.io.zcgltf.appendMeshPrimitive(
allocator,
data,
0, // mesh index
0, // gltf primitive index (submesh index)
&mesh_indices,
&mesh_positions,
&mesh_normals, // normals (optional)
null, // texcoords (optional)
null, // tangents (optional)
);
...
//
// Optimize mesh
//
const Vertex = struct {
position: [3]f32,
normal: [3]f32,
};
var remap = std.ArrayListUnmanaged(u32){};
try remap.resize(allocator, src_indices.items.len);
const num_unique_vertices = zmesh.opt.generateVertexRemap(
remap.items, // 'vertex remap' (destination)
src_indices.items, // non-optimized indices
Vertex, // Zig type describing your vertex
src_vertices.items, // non-optimized vertices
);
var optimized_vertices = std.ArrayListUnmanaged(Vertex){};
try optimized_vertices.resize(allocator, num_unique_vertices);
zmesh.opt.remapVertexBuffer(
Vertex, // Zig type describing your vertex
optimized_vertices.items, // optimized vertices (destination)
src_vertices.items, // non-optimized vertices (source)
remap.items, // 'vertex remap' generated by generateVertexRemap()
);
// More optimization steps are available - see `zmeshoptimizer.zig` file.
}