diff --git a/README.md b/README.md index 3c3ba193..a1b7f0ae 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ zig build run-skinned-meshes zig build run-sprite-animation zig build run-sprites zig build run-stresstest +zig build run-multiple-materials ``` ### Set optimization diff --git a/assets/meshes/CesiumMilkTruck.bin b/assets/meshes/CesiumMilkTruck.bin new file mode 100644 index 00000000..4bda5f39 Binary files /dev/null and b/assets/meshes/CesiumMilkTruck.bin differ diff --git a/assets/meshes/CesiumMilkTruck.gltf b/assets/meshes/CesiumMilkTruck.gltf new file mode 100644 index 00000000..61a189b2 --- /dev/null +++ b/assets/meshes/CesiumMilkTruck.gltf @@ -0,0 +1,436 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.4.55", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 5 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Wheels", + "rotation":[ + 0, + -0.08848591148853302, + 0, + 0.9960774183273315 + ] + }, + { + "children":[ + 0 + ], + "name":"Node", + "translation":[ + 1.432669997215271, + 0, + -0.4277220070362091 + ] + }, + { + "mesh":0, + "name":"Wheels.001", + "rotation":[ + 0, + -0.08848591148853302, + 0, + 0.9960774183273315 + ] + }, + { + "children":[ + 2 + ], + "name":"Node.001", + "translation":[ + -1.352329969406128, + 0, + -0.4277220070362091 + ] + }, + { + "children":[ + 1, + 3 + ], + "mesh":1, + "name":"Cesium_Milk_Truck" + }, + { + "children":[ + 4 + ], + "name":"Yup2Zup", + "rotation":[ + 0.5, + -0.5000000596046448, + 0.5000001192092896, + 0.5 + ] + } + ], + "materials":[ + { + "name":"wheels", + "pbrMetallicRoughness":{ + "baseColorTexture":{ + "index":0 + }, + "metallicFactor":0 + } + }, + { + "name":"truck", + "pbrMetallicRoughness":{ + "baseColorTexture":{ + "index":1 + }, + "metallicFactor":0 + } + }, + { + "name":"glass", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0, + 0.04050629958510399, + 0.021240700036287308, + 1 + ], + "metallicFactor":0 + } + }, + { + "name":"window_trim", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.06400000303983688, + 0.06400000303983688, + 0.06400000303983688, + 1 + ], + "metallicFactor":0 + } + } + ], + "meshes":[ + { + "name":"Wheels", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + } + ] + }, + { + "name":"Cesium_Milk_Truck", + "primitives":[ + { + "attributes":{ + "POSITION":4, + "NORMAL":5, + "TEXCOORD_0":6 + }, + "indices":7, + "material":1 + }, + { + "attributes":{ + "POSITION":8, + "NORMAL":9, + "TEXCOORD_0":10 + }, + "indices":11, + "material":2 + }, + { + "attributes":{ + "POSITION":12, + "NORMAL":13, + "TEXCOORD_0":14 + }, + "indices":15, + "material":3 + } + ] + } + ], + "textures":[ + { + "sampler":0, + "source":0 + }, + { + "sampler":0, + "source":0 + } + ], + "images":[ + { + "mimeType":"image/jpeg", + "name":"CesiumMilkTruck", + "uri":"CesiumMilkTruck.jpg" + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":828, + "max":[ + 0.4277999997138977, + 1.0579999685287476, + 0.4277999997138977 + ], + "min":[ + -0.4277999997138977, + -1.0579999685287476, + -0.4277999997138977 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":828, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":828, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":2304, + "type":"SCALAR" + }, + { + "bufferView":4, + "componentType":5126, + "count":2366, + "max":[ + 2.437999963760376, + 1.3960000276565552, + -0.2667999863624573 + ], + "min":[ + -2.430910110473633, + -1.3960000276565552, + -2.5843698978424072 + ], + "type":"VEC3" + }, + { + "bufferView":5, + "componentType":5126, + "count":2366, + "type":"VEC3" + }, + { + "bufferView":6, + "componentType":5126, + "count":2366, + "type":"VEC2" + }, + { + "bufferView":7, + "componentType":5123, + "count":5232, + "type":"SCALAR" + }, + { + "bufferView":8, + "componentType":5126, + "count":151, + "max":[ + 1.6011799573898315, + 1.3960000276565552, + -1.631850004196167 + ], + "min":[ + 0.22885000705718994, + -1.3960000276565552, + -2.3545401096343994 + ], + "type":"VEC3" + }, + { + "bufferView":9, + "componentType":5126, + "count":151, + "type":"VEC3" + }, + { + "bufferView":10, + "componentType":5126, + "count":151, + "type":"VEC2" + }, + { + "bufferView":11, + "componentType":5123, + "count":168, + "type":"SCALAR" + }, + { + "bufferView":12, + "componentType":5126, + "count":663, + "max":[ + 1.62267005443573, + 1.100000023841858, + -1.5961999893188477 + ], + "min":[ + 0.1932000070810318, + -1.1100000143051147, + -2.3919999599456787 + ], + "type":"VEC3" + }, + { + "bufferView":13, + "componentType":5126, + "count":663, + "type":"VEC3" + }, + { + "bufferView":14, + "componentType":5126, + "count":663, + "type":"VEC2" + }, + { + "bufferView":15, + "componentType":5123, + "count":864, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":9936, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":9936, + "byteOffset":9936, + "target":34962 + }, + { + "buffer":0, + "byteLength":6624, + "byteOffset":19872, + "target":34962 + }, + { + "buffer":0, + "byteLength":4608, + "byteOffset":26496, + "target":34963 + }, + { + "buffer":0, + "byteLength":28392, + "byteOffset":31104, + "target":34962 + }, + { + "buffer":0, + "byteLength":28392, + "byteOffset":59496, + "target":34962 + }, + { + "buffer":0, + "byteLength":18928, + "byteOffset":87888, + "target":34962 + }, + { + "buffer":0, + "byteLength":10464, + "byteOffset":106816, + "target":34963 + }, + { + "buffer":0, + "byteLength":1812, + "byteOffset":117280, + "target":34962 + }, + { + "buffer":0, + "byteLength":1812, + "byteOffset":119092, + "target":34962 + }, + { + "buffer":0, + "byteLength":1208, + "byteOffset":120904, + "target":34962 + }, + { + "buffer":0, + "byteLength":336, + "byteOffset":122112, + "target":34963 + }, + { + "buffer":0, + "byteLength":7956, + "byteOffset":122448, + "target":34962 + }, + { + "buffer":0, + "byteLength":7956, + "byteOffset":130404, + "target":34962 + }, + { + "buffer":0, + "byteLength":5304, + "byteOffset":138360, + "target":34962 + }, + { + "buffer":0, + "byteLength":1728, + "byteOffset":143664, + "target":34963 + } + ], + "samplers":[ + { + "magFilter":9729, + "minFilter":9987 + } + ], + "buffers":[ + { + "byteLength":145392, + "uri":"CesiumMilkTruck.bin" + } + ] +} diff --git a/assets/meshes/CesiumMilkTruck.jpg b/assets/meshes/CesiumMilkTruck.jpg new file mode 100644 index 00000000..0bebaadc Binary files /dev/null and b/assets/meshes/CesiumMilkTruck.jpg differ diff --git a/build.zig b/build.zig index 6e1264c8..d9d1bd1d 100644 --- a/build.zig +++ b/build.zig @@ -178,6 +178,7 @@ pub fn build(b: *std.Build) !void { "rays", "skinned-meshes", "stresstest", + "multiple-materials", }; for (examples) |example_item| { diff --git a/src/examples/frustums.zig b/src/examples/frustums.zig index 70de216d..3d2008d1 100644 --- a/src/examples/frustums.zig +++ b/src/examples/frustums.zig @@ -115,7 +115,7 @@ pub fn on_draw() void { const bounds = cube_mesh.bounds.translate(cube_pos); if (frustum.containsBoundingBox(bounds)) { - cube_mesh.drawWithMaterial(material_highlight, view_mats, cube_model_matrix); + cube_mesh.drawWithMaterials((&material_highlight)[0..1], view_mats, cube_model_matrix); } else { cube_mesh.draw(view_mats, cube_model_matrix); } @@ -126,7 +126,10 @@ pub fn on_draw() void { } pub fn on_cleanup() !void { + frustum_mesh.materials.deinit(); frustum_mesh.deinit(); + cube_mesh.materials.deinit(); + cube_mesh.deinit(); material_highlight.deinit(); material_cube.deinit(); material_frustum.deinit(); diff --git a/src/examples/lighting.zig b/src/examples/lighting.zig index f58fe059..aa3b2c72 100644 --- a/src/examples/lighting.zig +++ b/src/examples/lighting.zig @@ -14,6 +14,7 @@ const input = delve.platform.input; const math = delve.math; const modules = delve.modules; const skinned_mesh = delve.graphics.skinned_mesh; +const gltf = delve.assets.gltf; // easy access to some types const Vec3 = math.Vec3; @@ -33,13 +34,16 @@ var animation: skinned_mesh.PlayingAnimation = undefined; var time: f32 = 0.0; var camera: cam.Camera = undefined; -const mesh_file = "assets/meshes/CesiumMan.gltf"; +var gltf_data: *gltf.Data = undefined; + +const mesh_file = "CesiumMan.gltf"; const mesh_texture_file = "assets/meshes/CesiumMan.png"; var cube1: delve.graphics.mesh.Mesh = undefined; var cube2: delve.graphics.mesh.Mesh = undefined; var skinned_mesh_material: delve.platform.graphics.Material = undefined; +var skinned_mesh_materials: std.ArrayList(delve.platform.graphics.Material) = undefined; var static_mesh_material: delve.platform.graphics.Material = undefined; // This example shows an example of some simple lighting in a shader @@ -102,6 +106,9 @@ fn on_init() !void { .default_fs_uniform_layout = delve.platform.graphics.default_lit_fs_uniforms, }); + skinned_mesh_materials = std.ArrayList(graphics.Material).init(delve.mem.getAllocator()); + try skinned_mesh_materials.append(skinned_mesh_material); + // Create a material out of the texture static_mesh_material = try graphics.Material.init(.{ .shader = static_shader, @@ -112,8 +119,10 @@ fn on_init() !void { .default_fs_uniform_layout = delve.platform.graphics.default_lit_fs_uniforms, }); + gltf_data = try gltf.loadData(mesh_file, "assets/meshes/"); + // Load an animated mesh - const loaded_mesh = skinned_mesh.SkinnedMesh.initFromFile(delve.mem.getAllocator(), mesh_file, .{ .material = skinned_mesh_material }); + const loaded_mesh = skinned_mesh.SkinnedMesh.initFromData(delve.mem.getAllocator(), gltf_data, 0, .{ .materials = skinned_mesh_materials }); if (loaded_mesh == null) { debug.fatal("Could not load skinned mesh!", .{}); @@ -181,12 +190,20 @@ fn on_draw() void { fn on_cleanup() !void { debug.log("Lighting example module cleaning up", .{}); + gltf.freeData(gltf_data); + skinned_shader.destroy(); static_shader.destroy(); skinned_mesh_material.deinit(); + skinned_mesh_materials.deinit(); static_mesh_material.deinit(); + cube1.materials.deinit(); + cube1.deinit(); + cube2.materials.deinit(); + cube2.deinit(); + animation.deinit(); animated_mesh.deinit(); } diff --git a/src/examples/meshbuilder.zig b/src/examples/meshbuilder.zig index 3d993ef2..eb594cef 100644 --- a/src/examples/meshbuilder.zig +++ b/src/examples/meshbuilder.zig @@ -15,6 +15,8 @@ var cube1: delve.graphics.mesh.Mesh = undefined; var cube2: delve.graphics.mesh.Mesh = undefined; var cube3: delve.graphics.mesh.Mesh = undefined; +var allocator: std.mem.Allocator = undefined; + var time: f64 = 0.0; pub fn main() !void { @@ -61,6 +63,8 @@ pub fn on_init() !void { .samplers = &[_]graphics.FilterMode{.NEAREST}, }); + allocator = delve.mem.getAllocator(); + // create our camera camera = delve.graphics.camera.Camera.initThirdPerson(90.0, 0.01, 20.0, 5.0, math.Vec3.up); @@ -114,4 +118,10 @@ pub fn on_draw() void { pub fn on_cleanup() !void { material.deinit(); + cube1.materials.deinit(); + cube1.deinit(); + cube2.materials.deinit(); + cube2.deinit(); + cube3.materials.deinit(); + cube3.deinit(); } diff --git a/src/examples/meshes.zig b/src/examples/meshes.zig index eea96e05..cf848cab 100644 --- a/src/examples/meshes.zig +++ b/src/examples/meshes.zig @@ -14,6 +14,7 @@ const input = delve.platform.input; const math = delve.math; const modules = delve.modules; const mesh = delve.graphics.mesh; +const gltf = delve.assets.gltf; // easy access to some types const Vec3 = math.Vec3; @@ -26,8 +27,10 @@ var time: f32 = 0.0; var camera: cam.Camera = undefined; var mesh_test: ?mesh.Mesh = null; +var gltf_data: *gltf.Data = undefined; var shader: delve.platform.graphics.Shader = undefined; var material: graphics.Material = undefined; +var materials: std.ArrayList(graphics.Material) = undefined; // This example shows loading and drawing meshes @@ -96,8 +99,16 @@ fn on_init() !void { .texture_1 = tex_emissive, }); + materials = std.ArrayList(graphics.Material).init(delve.mem.getAllocator()); + try materials.append(material); + + const path = "assets/meshes/"; + const filename = "SciFiHelmet.gltf"; + + gltf_data = try gltf.loadData(filename, path); + // Load our mesh! - mesh_test = mesh.Mesh.initFromFile(delve.mem.getAllocator(), "assets/meshes/SciFiHelmet.gltf", .{ .material = material }); + mesh_test = mesh.Mesh.initFromData(delve.mem.getAllocator(), gltf_data, 0, .{ .materials = materials }); } fn on_tick(delta: f32) void { @@ -121,7 +132,8 @@ fn on_draw() void { model = model.mul(Mat4.rotate(time * 0.6, Vec3.new(0.0, 1.0, 0.0))); const sin_val = std.math.sin(time * 0.006) + 0.5; - mesh_test.?.material.state.params.draw_color = Color.new(sin_val, sin_val, sin_val, 1.0); + material.state.params.draw_color = Color.new(sin_val, sin_val, sin_val, 1.0); + mesh_test.?.draw(view_mats, model); model = Mat4.translate(Vec3.new(-2.0, 0.0, 0.0)); @@ -131,10 +143,14 @@ fn on_draw() void { fn on_cleanup() !void { debug.log("Mesh example module cleaning up", .{}); + material.deinit(); + materials.deinit(); + shader.destroy(); + + gltf.freeData(gltf_data); + if (mesh_test == null) return; mesh_test.?.deinit(); - material.deinit(); - shader.destroy(); } diff --git a/src/examples/multiple-materials.zig b/src/examples/multiple-materials.zig new file mode 100644 index 00000000..a4f74c37 --- /dev/null +++ b/src/examples/multiple-materials.zig @@ -0,0 +1,129 @@ +const std = @import("std"); +const delve = @import("delve"); +const app = delve.app; + +var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + +// easy access to some imports +const cam = delve.graphics.camera; +const colors = delve.colors; +const debug = delve.debug; +const graphics = delve.platform.graphics; +const images = delve.images; +const input = delve.platform.input; +const math = delve.math; +const modules = delve.modules; +const gltf = delve.assets.gltf; +const mesh = delve.graphics.mesh; + +// easy access to some types +const Vec3 = math.Vec3; +const Mat4 = math.Mat4; +const Color = colors.Color; + +const default_shader_builtin = delve.shaders.default; + +var time: f32 = 0.0; +var camera: cam.Camera = undefined; + +var mesh1: ?mesh.Mesh = null; +var mesh2: ?mesh.Mesh = null; +var gltf_data: *gltf.Data = undefined; +var shader: delve.platform.graphics.Shader = undefined; +var materials: std.ArrayList(graphics.Material) = undefined; +var allocator: std.mem.Allocator = undefined; + +// This example shows loading and drawing meshes + +pub fn main() !void { + // Pick the allocator to use depending on platform + const builtin = @import("builtin"); + if (builtin.os.tag == .wasi or builtin.os.tag == .emscripten) { + // Web builds hack: use the C allocator to avoid OOM errors + // See https://github.com/ziglang/zig/issues/19072 + try delve.init(std.heap.c_allocator); + } else { + // Using the default allocator will let us detect memory leaks + try delve.init(delve.mem.createDefaultAllocator()); + } + + try registerModule(); + try app.start(app.AppConfig{ .title = "Delve Framework - Multiple Materials" }); +} + +pub fn registerModule() !void { + const meshExample = modules.Module{ + .name = "multiple_materials_example", + .init_fn = on_init, + .tick_fn = on_tick, + .draw_fn = on_draw, + .cleanup_fn = on_cleanup, + }; + + try modules.registerModule(meshExample); +} + +fn on_init() !void { + debug.log("Mesh example module initializing", .{}); + + graphics.setClearColor(colors.examples_bg_light); + + // Make a perspective camera, with a 90 degree FOV + camera = cam.Camera.initThirdPerson(90, 0.01, 50.0, 5.0, Vec3.new(0, -4, -4)); + camera.position = Vec3.new(-2, 0, 0); + + shader = try graphics.Shader.initFromBuiltin(.{ .vertex_attributes = mesh.getShaderAttributes() }, default_shader_builtin); + + allocator = delve.mem.getAllocator(); + + const path = "assets/meshes/"; + // https://github.com/KhronosGroup/glTF-Sample-Assets/blob/main/Models/CesiumMilkTruck/README.md + const filename = "CesiumMilkTruck.gltf"; + + gltf_data = try gltf.loadData(filename, path); + + materials = std.ArrayList(graphics.Material).init(allocator); + + gltf.loadMaterials(gltf_data, 0, path, shader, &materials); + + std.debug.print("amount of materials {d} \n", .{materials.items.len}); + + mesh1 = mesh.Mesh.initFromData(allocator, gltf_data, 0, .{ .materials = materials }); + // mesh2 = mesh.Mesh.initFromData(allocator, gltf_data, 1, .{ .materials = materials }); +} + +fn on_tick(delta: f32) void { + // There is a built in fly mode, but you can also just set the position / direction + camera.runSimpleCamera(4 * delta, 120 * delta, false); + + time += delta * 100; + + if (input.isKeyJustPressed(.ESCAPE)) + delve.platform.app.exit(); +} + +fn on_draw() void { + const view_mats = camera.update(); + + var model_matrix = Mat4.translate(Vec3.new(2.0, 0.0, 0.0)); + model_matrix = model_matrix.mul(Mat4.rotate(time * 0.6, Vec3.new(0.0, 1.0, 0.0))); + + model_matrix = Mat4.translate(Vec3.new(-2.0, 0.0, 0.0)); + mesh1.?.draw(view_mats, model_matrix); + // mesh2.?.draw(view_mats, model_matrix); +} + +fn on_cleanup() !void { + debug.log("Model example module cleaning up", .{}); + + for (materials.items) |*m| { + m.deinit(); + } + materials.deinit(); + shader.destroy(); + + gltf.freeData(gltf_data); + + mesh1.?.deinit(); + // mesh2.?.deinit(); +} diff --git a/src/examples/quakemap.zig b/src/examples/quakemap.zig index b9aae9ec..e7c8a149 100644 --- a/src/examples/quakemap.zig +++ b/src/examples/quakemap.zig @@ -338,5 +338,8 @@ pub fn on_cleanup() !void { materials.deinit(); shader.destroy(); + cube_mesh.materials.deinit(); + cube_mesh.deinit(); + quake_map.deinit(); } diff --git a/src/examples/rays.zig b/src/examples/rays.zig index a1b28268..d3be3b97 100644 --- a/src/examples/rays.zig +++ b/src/examples/rays.zig @@ -132,10 +132,10 @@ pub fn on_draw() void { const rayhit = ray.intersectOrientedBoundingBox(bounds); if (rayhit != null) { - cube_mesh.drawWithMaterial(material_highlight, view_mats, cube_model_matrix); + cube_mesh.drawWithMaterials((&material_highlight)[0..1], view_mats, cube_model_matrix); const hit_model_matrix = delve.math.Mat4.translate(rayhit.?.hit_pos); - hit_mesh.drawWithMaterial(material_hitpoint, view_mats, hit_model_matrix); + hit_mesh.drawWithMaterials((&material_hitpoint)[0..1], view_mats, hit_model_matrix); } else { cube_mesh.draw(view_mats, cube_model_matrix); } diff --git a/src/examples/skinned-meshes.zig b/src/examples/skinned-meshes.zig index 393b757c..9cd3bbd7 100644 --- a/src/examples/skinned-meshes.zig +++ b/src/examples/skinned-meshes.zig @@ -14,6 +14,7 @@ const input = delve.platform.input; const math = delve.math; const modules = delve.modules; const skinned_mesh = delve.graphics.skinned_mesh; +const gltf = delve.assets.gltf; // easy access to some types const Vec3 = math.Vec3; @@ -23,13 +24,16 @@ const Color = colors.Color; const shader_builtin = delve.shaders.default_skinned; var material: delve.platform.graphics.Material = undefined; +var materials: std.ArrayList(delve.platform.graphics.Material) = undefined; var mesh_test: skinned_mesh.SkinnedMesh = undefined; var animation: skinned_mesh.PlayingAnimation = undefined; var time: f32 = 0.0; var camera: cam.Camera = undefined; -const mesh_file = "assets/meshes/CesiumMan.gltf"; +var gltf_data: *gltf.Data = undefined; + +const mesh_file = "CesiumMan.gltf"; const mesh_texture_file = "assets/meshes/CesiumMan.png"; // currently playing animation @@ -96,8 +100,13 @@ fn on_init() !void { .default_vs_uniform_layout = delve.platform.graphics.default_skinned_mesh_vs_uniforms, }); + materials = std.ArrayList(delve.platform.graphics.Material).init(delve.mem.getAllocator()); + try materials.append(material); + + gltf_data = try gltf.loadData(mesh_file, "assets/meshes/"); + // Load our mesh! - const loaded = skinned_mesh.SkinnedMesh.initFromFile(delve.mem.getAllocator(), mesh_file, .{ .material = material }); + const loaded = skinned_mesh.SkinnedMesh.initFromData(delve.mem.getAllocator(), gltf_data, 0, .{ .materials = materials }); if (loaded == null) { debug.fatal("Could not load skinned mesh!", .{}); @@ -171,7 +180,10 @@ fn on_draw() void { fn on_cleanup() !void { debug.log("Skinned mesh example module cleaning up", .{}); + gltf.freeData(gltf_data); + material.deinit(); + materials.deinit(); animation.deinit(); mesh_test.deinit(); } diff --git a/src/framework/assets/gltf.zig b/src/framework/assets/gltf.zig new file mode 100644 index 00000000..40b29a81 --- /dev/null +++ b/src/framework/assets/gltf.zig @@ -0,0 +1,83 @@ +const std = @import("std"); +const graphics = @import("../platform/graphics.zig"); +const debug = @import("../debug.zig"); +const zmesh = @import("zmesh"); +const images = @import("../images.zig"); + +var gpa = std.heap.GeneralPurposeAllocator(.{}){}; +var allocator = gpa.allocator(); + +pub const Data = zmesh.io.zcgltf.Data; + +pub fn freeData(data: ?*Data) void { + if (data != null) { + zmesh.io.zcgltf.free(data.?); + } +} + +pub fn loadData(filename: [:0]const u8, path: [:0]const u8) !*zmesh.io.zcgltf.Data { + const src = std.fs.path.joinZ(allocator, &[_][]const u8{ path, filename }) catch |err| { + debug.log("cannot create src: {}", .{err}); + return err; + }; + defer allocator.free(src); + + return zmesh.io.parseAndLoadFile(src) catch |err| { + debug.log("Could not load mesh file {s}", .{filename}); + return err; + }; +} + +pub fn loadTexture(texture: ?*zmesh.io.zcgltf.Texture, path: [:0]const u8) ?graphics.Texture { + if (texture != null) { + const u = texture.?.image.?.uri; + if (u) |uri| { + const image_path = std.fs.path.joinZ(allocator, &[_][]const u8{ path, std.mem.span(uri) }) catch |err| { + debug.log("cannot create src: {}", .{err}); + return null; + }; + defer allocator.free(image_path); + + var base_img: images.Image = images.loadFile(image_path) catch { + debug.log("Assets: Error loading image asset: {s}", .{image_path}); + return null; + }; + defer base_img.deinit(); + + return graphics.Texture.init(base_img); + } + } + return null; +} + +pub fn loadMaterials(data: *zmesh.io.zcgltf.Data, mesh_index: usize, path: [:0]const u8, shader: graphics.Shader, materials: *std.ArrayList(graphics.Material)) void { + const dmesh = data.meshes.?[mesh_index]; + for (0..dmesh.primitives_count) |primitive_index| { + const primitive = dmesh.primitives[primitive_index]; + + const zcgltf_material = primitive.material; + if (zcgltf_material != null) { + var texture_0: ?graphics.Texture = undefined; + // var texture_1: ?graphics.Texture = undefined; + + std.debug.print("Loading material: {?s} \n", .{zcgltf_material.?.name}); + + const base_color_texture = zcgltf_material.?.pbr_metallic_roughness.base_color_texture.texture; + texture_0 = loadTexture(base_color_texture, path); + // const normal_texture = zcgltf_material.?.normal_texture.texture; + // texture_1 = loadTexture(allocator, normal_texture, path); + + // Create a material out of our shader and textures + const material = graphics.Material.init(.{ + .shader = shader, + .texture_0 = texture_0, + }) catch |err| { + std.debug.print("Failed to create material {}\n", .{err}); + continue; + }; + materials.append(material) catch |err| { + std.debug.print("Failed to append material {}\n", .{err}); + }; + } + } +} diff --git a/src/framework/delve.zig b/src/framework/delve.zig index 1d669ffa..504df6c1 100644 --- a/src/framework/delve.zig +++ b/src/framework/delve.zig @@ -66,6 +66,10 @@ pub const spatial = struct { pub const Rect = @import("spatial/rect.zig").Rect; }; +pub const assets = struct { + pub const gltf = @import("assets/gltf.zig"); +}; + pub const utils = struct { pub const interpolation = @import("utils/interpolation.zig"); pub const quakemap = @import("utils/quakemap.zig"); diff --git a/src/framework/graphics/mesh.zig b/src/framework/graphics/mesh.zig index d561c92b..126d58ca 100644 --- a/src/framework/graphics/mesh.zig +++ b/src/framework/graphics/mesh.zig @@ -24,7 +24,7 @@ const FSParams = graphics.FSDefaultUniforms; const vertex_layout = getVertexLayout(); pub const MeshConfig = struct { - material: graphics.Material, + materials: std.ArrayList(graphics.Material), }; pub fn init() !void { @@ -38,184 +38,181 @@ pub fn deinit() void { /// A mesh is a drawable set of vertex positions, normals, tangents, and uvs. /// These can be created on the fly, using a MeshBuilder, or loaded from GLTF files. pub const Mesh = struct { - bindings: graphics.Bindings = undefined, - material: graphics.Material = undefined, + bindings_list: std.ArrayList(graphics.Bindings) = undefined, + materials: std.ArrayList(graphics.Material) = undefined, bounds: boundingbox.BoundingBox = undefined, has_skin: bool = false, zmesh_data: ?*zmesh.io.zcgltf.Data = null, - pub fn initFromFile(allocator: std.mem.Allocator, filename: [:0]const u8, cfg: MeshConfig) ?Mesh { - const data = zmesh.io.parseAndLoadFile(filename) catch { - debug.log("Could not load mesh file {s}", .{filename}); - return null; - }; - - var mesh_indices = std.ArrayList(u32).init(allocator); - var mesh_positions = std.ArrayList([3]f32).init(allocator); - var mesh_normals = std.ArrayList([3]f32).init(allocator); - var mesh_texcoords = std.ArrayList([2]f32).init(allocator); - var mesh_tangents = std.ArrayList([4]f32).init(allocator); - - var mesh_joints = std.ArrayList([4]f32).init(allocator); - var mesh_weights = std.ArrayList([4]f32).init(allocator); - - defer mesh_indices.deinit(); - defer mesh_positions.deinit(); - defer mesh_normals.deinit(); - defer mesh_texcoords.deinit(); - defer mesh_tangents.deinit(); - defer mesh_joints.deinit(); - defer mesh_weights.deinit(); - - for (0..data.meshes_count) |i| { - const mesh = data.meshes.?[i]; - for (0..mesh.primitives_count) |pi| { - zmesh.io.appendMeshPrimitive( - data, // *zmesh.io.cgltf.Data - @intCast(i), // mesh index - @intCast(pi), // gltf primitive index (submesh index) - &mesh_indices, - &mesh_positions, - &mesh_normals, // normals (optional) - &mesh_texcoords, // texcoords (optional) - &mesh_tangents, // tangents (optional) - &mesh_joints, // joints (optional) - &mesh_weights, // weights (optional) - ) catch { + pub fn initFromData(allocator: std.mem.Allocator, data: *zmesh.io.zcgltf.Data, mesh_index: usize, cfg: MeshConfig) ?Mesh { + var bindings_list = std.ArrayList(graphics.Bindings).init(allocator); + + var vertices = std.ArrayList(PackedVertex).init(allocator); + defer vertices.deinit(); + + var any_submesh_had_joints: bool = false; + + // vertices is global but need partial for each bindings + var vertices_start: usize = 0; + + const mesh = data.meshes.?[mesh_index]; + for (0..mesh.primitives_count) |primitive_index| { + var mesh_indices = std.ArrayList(u32).init(allocator); + var mesh_positions = std.ArrayList([3]f32).init(allocator); + var mesh_normals = std.ArrayList([3]f32).init(allocator); + var mesh_texcoords = std.ArrayList([2]f32).init(allocator); + var mesh_tangents = std.ArrayList([4]f32).init(allocator); + + var mesh_joints = std.ArrayList([4]f32).init(allocator); + var mesh_weights = std.ArrayList([4]f32).init(allocator); + + defer mesh_indices.deinit(); + defer mesh_positions.deinit(); + defer mesh_normals.deinit(); + defer mesh_texcoords.deinit(); + defer mesh_tangents.deinit(); + defer mesh_joints.deinit(); + defer mesh_weights.deinit(); + + zmesh.io.appendMeshPrimitive( + data, // *zmesh.io.cgltf.Data + @intCast(mesh_index), // mesh index + @intCast(primitive_index), // gltf primitive index (submesh index) + &mesh_indices, + &mesh_positions, + &mesh_normals, // normals (optional) + &mesh_texcoords, // texcoords (optional) + &mesh_tangents, // tangents (optional) + &mesh_joints, // joints (optional) + &mesh_weights, // weights (optional) + ) catch { + debug.log("Could not process mesh file!", .{}); + return null; + }; + + const white_color = colors.white.toArray(); + + for (mesh_positions.items, 0..) |vert, i| { + var u_textcoord: f32 = 0.0; + var v_textcoord: f32 = 0.0; + + if (mesh_texcoords.items.len > i) { + u_textcoord = mesh_texcoords.items[i][0]; + v_textcoord = mesh_texcoords.items[i][1]; + } + + vertices.append(.{ .x = vert[0], .y = vert[1], .z = vert[2], .u = u_textcoord, .v = v_textcoord, .color = white_color }) catch { debug.log("Could not process mesh file!", .{}); return null; }; } - } - debug.log("Loaded mesh file {s} with {d} indices", .{ filename, mesh_indices.items.len }); - - var vertices = allocator.alloc(PackedVertex, mesh_positions.items.len) catch { - debug.log("Could not process mesh file!", .{}); - return null; - }; - defer allocator.free(vertices); + // Fill in normals if none were given, to match vertex layout + if (mesh_normals.items.len == 0) { + for (0..mesh_positions.items.len) |_| { + const empty = [3]f32{ 0.0, 0.0, 0.0 }; + mesh_normals.append(empty) catch { + return null; + }; + } + } - const white_color = colors.white.toArray(); + // Fill in tangents if none were given, to match vertex layout + if (mesh_tangents.items.len == 0) { + for (0..mesh_positions.items.len) |_| { + const empty = [4]f32{ 0.0, 0.0, 0.0, 0.0 }; + mesh_tangents.append(empty) catch { + return null; + }; + } + } - for (mesh_positions.items, 0..) |vert, i| { - vertices[i].x = vert[0]; - vertices[i].y = vert[1]; - vertices[i].z = vert[2]; + // Fill in joints, if none were given, to match vertex layout + any_submesh_had_joints = mesh_joints.items.len > 0 and mesh_weights.items.len > 0; - vertices[i].color = white_color; + var bindings: graphics.Bindings = undefined; - if (mesh_texcoords.items.len > i) { - vertices[i].u = mesh_texcoords.items[i][0]; - vertices[i].v = mesh_texcoords.items[i][1]; - } else { - vertices[i].u = 0.0; - vertices[i].v = 0.0; - } - } + if (any_submesh_had_joints) { + debug.log("Creating skinned mesh: {d} indices", .{mesh_indices.items.len}); - const material: graphics.Material = cfg.material; + const layout = getSkinnedVertexLayout(); + bindings = graphics.Bindings.init(.{ + .index_len = mesh_indices.items.len, + .vert_len = vertices.items.len, + .vertex_layout = layout, + }); - // Fill in normals if none were given, to match vertex layout - if (mesh_normals.items.len == 0) { - for (0..mesh_positions.items.len) |_| { - const empty = [3]f32{ 0.0, 0.0, 0.0 }; - mesh_normals.append(empty) catch { - return null; - }; - } - } + bindings.setWithJoints(vertices.items[vertices_start..], mesh_indices.items, mesh_normals.items, mesh_tangents.items, mesh_joints.items, mesh_weights.items, mesh_indices.items.len); + } else { + bindings = graphics.Bindings.init(.{ + .index_len = mesh_indices.items.len, + .vert_len = vertices.items.len, + .vertex_layout = vertex_layout, + }); - // Fill in tangents if none were given, to match vertex layout - if (mesh_tangents.items.len == 0) { - for (0..mesh_positions.items.len) |_| { - const empty = [4]f32{ 0.0, 0.0, 0.0, 0.0 }; - mesh_tangents.append(empty) catch { - return null; - }; + bindings.set(vertices.items[vertices_start..], mesh_indices.items, mesh_normals.items, mesh_tangents.items, mesh_indices.items.len); } - } - // Fill in joints, if none were given, to match vertex layout - const had_joints = mesh_joints.items.len > 0 and mesh_weights.items.len > 0; + bindings_list.append(bindings) catch { + return null; + }; - if (had_joints) { - return createSkinnedMesh(vertices, mesh_indices.items, mesh_normals.items, mesh_tangents.items, mesh_joints.items, mesh_weights.items, material, data); + vertices_start = vertices.items.len; } - // only skinned meshes need to keep the model metadata around - zmesh.io.freeData(data); + if (any_submesh_had_joints) { + return createSkinnedMesh(vertices.items, bindings_list, cfg.materials, data); + } - return createMesh(vertices, mesh_indices.items, mesh_normals.items, mesh_tangents.items, material); + return createMesh(vertices.items, bindings_list, cfg.materials); } pub fn deinit(self: *Mesh) void { - if (self.zmesh_data) |mesh_data| { - zmesh.io.freeData(mesh_data); + for (self.bindings_list.items) |*b| { + b.destroy(); } - self.bindings.destroy(); + self.bindings_list.deinit(); } /// Draw this mesh pub fn draw(self: *Mesh, cam_matrices: CameraMatrices, model_matrix: math.Mat4) void { - graphics.drawWithMaterial(&self.bindings, &self.material, cam_matrices, model_matrix); + for (self.bindings_list.items, self.materials.items) |*bindings, *material| { + graphics.drawWithMaterial(bindings, material, cam_matrices, model_matrix); + } } - /// Draw this mesh, using the specified material instead of the set one - pub fn drawWithMaterial(self: *Mesh, material: graphics.Material, cam_matrices: CameraMatrices, model_matrix: math.Mat4) void { - graphics.drawWithMaterial(&self.bindings, @constCast(&material), cam_matrices, model_matrix); + /// Draw this mesh, using the specified materials instead of the set ones + pub fn drawWithMaterials(self: *Mesh, materials: []graphics.Material, cam_matrices: CameraMatrices, model_matrix: math.Mat4) void { + for (self.bindings_list.items, materials) |*bindings, *material| { + graphics.drawWithMaterial(bindings, material, cam_matrices, model_matrix); + } } }; /// Create a mesh out of some vertex data -pub fn createMesh(vertices: []PackedVertex, indices: []u32, normals: [][3]f32, tangents: [][4]f32, material: graphics.Material) Mesh { +pub fn createMesh(vertices: []PackedVertex, bindings_list: std.ArrayList(graphics.Bindings), materials: std.ArrayList(graphics.Material)) Mesh { // create a mesh with the default vertex layout - return createMeshWithLayout(vertices, indices, normals, tangents, material, vertex_layout); -} - -pub fn createSkinnedMesh(vertices: []PackedVertex, indices: []u32, normals: [][3]f32, tangents: [][4]f32, joints: [][4]f32, weights: [][4]f32, material: graphics.Material, data: *zmesh.io.zcgltf.Data) Mesh { - // create a mesh with the default vertex layout - // debug.log("Creating skinned mesh: {d} indices, {d} normals, {d}tangents, {d} joints, {d} weights", .{ indices.len, normals.len, tangents.len, joints.len, weights.len }); - - debug.log("Creating skinned mesh: {d} indices", .{indices.len}); - - const layout = getSkinnedVertexLayout(); - var bindings = graphics.Bindings.init(.{ - .index_len = indices.len, - .vert_len = vertices.len, - .vertex_layout = layout, - }); - - bindings.setWithJoints(vertices, indices, normals, tangents, joints, weights, indices.len); const m: Mesh = Mesh{ - .bindings = bindings, - .material = material, + .bindings_list = bindings_list, + .materials = materials, .bounds = boundingbox.BoundingBox.initFromVerts(vertices), - .zmesh_data = data, - .has_skin = true, }; return m; } -/// Create a mesh out of some vertex data with a given vertex layout -pub fn createMeshWithLayout(vertices: []PackedVertex, indices: []u32, normals: [][3]f32, tangents: [][4]f32, material: graphics.Material, layout: graphics.VertexLayout) Mesh { - // debug.log("Creating mesh: {d} indices", .{indices.len}); - - var bindings = graphics.Bindings.init(.{ - .index_len = indices.len, - .vert_len = vertices.len, - .vertex_layout = layout, - }); - - bindings.set(vertices, indices, normals, tangents, indices.len); +pub fn createSkinnedMesh(vertices: []PackedVertex, bindings_list: std.ArrayList(graphics.Bindings), materials: std.ArrayList(graphics.Material), data: *zmesh.io.zcgltf.Data) Mesh { + // create a mesh with the default vertex layout + // debug.log("Creating skinned mesh: {d} indices, {d} normals, {d}tangents, {d} joints, {d} weights", .{ indices.len, normals.len, tangents.len, joints.len, weights.len }); const m: Mesh = Mesh{ - .bindings = bindings, - .material = material, + .bindings_list = bindings_list, + .materials = materials, .bounds = boundingbox.BoundingBox.initFromVerts(vertices), + .zmesh_data = data, + .has_skin = true, }; return m; } @@ -282,6 +279,7 @@ pub const MeshBuilder = struct { indices: std.ArrayList(u32) = undefined, normals: std.ArrayList([3]f32) = undefined, tangents: std.ArrayList([4]f32) = undefined, + allocator: std.mem.Allocator = undefined, pub fn init(allocator: std.mem.Allocator) MeshBuilder { return MeshBuilder{ @@ -289,6 +287,7 @@ pub const MeshBuilder = struct { .indices = std.ArrayList(u32).init(allocator), .normals = std.ArrayList([3]f32).init(allocator), .tangents = std.ArrayList([4]f32).init(allocator), + .allocator = allocator, }; } @@ -468,13 +467,26 @@ pub const MeshBuilder = struct { } /// Bakes a mesh out of the mesh builder from the current state - pub fn buildMesh(self: *const MeshBuilder, material: graphics.Material) Mesh { - const layout = getVertexLayout(); - return createMeshWithLayout(self.vertices.items, self.indices.items, self.normals.items, self.tangents.items, material, layout); + pub fn buildMesh(self: *const MeshBuilder, material: graphics.Material) !Mesh { + var bindings = graphics.Bindings.init(.{ + .index_len = self.indices.items.len, + .vert_len = self.vertices.items.len, + .vertex_layout = vertex_layout, + }); + + bindings.set(self.vertices.items, self.indices.items, self.normals.items, self.tangents.items, self.indices.items.len); + + var bindings_list = std.ArrayList(graphics.Bindings).init(self.allocator); + try bindings_list.append(bindings); + var materials = std.ArrayList(graphics.Material).init(self.allocator); + try materials.append(material); + + return createMesh(self.vertices.items, bindings_list, materials); } /// Cleans up a mesh builder pub fn deinit(self: *MeshBuilder) void { + // maybe use arena here self.vertices.deinit(); self.indices.deinit(); self.normals.deinit(); diff --git a/src/framework/graphics/skinned-mesh.zig b/src/framework/graphics/skinned-mesh.zig index d273a202..03c78034 100644 --- a/src/framework/graphics/skinned-mesh.zig +++ b/src/framework/graphics/skinned-mesh.zig @@ -173,9 +173,9 @@ pub const SkinnedMesh = struct { // the index at which a named joint lives bone_indices: std.StringHashMap(usize) = undefined, - /// Load a mesh with animation data from a gltf file - pub fn initFromFile(allocator: std.mem.Allocator, filename: [:0]const u8, cfg: MeshConfig) ?SkinnedMesh { - const loaded_mesh = Mesh.initFromFile(allocator, filename, cfg); + /// Load a mesh with animation data from gltf data + pub fn initFromData(allocator: std.mem.Allocator, data: *zmesh.io.zcgltf.Data, mesh_index: usize, cfg: MeshConfig) ?SkinnedMesh { + const loaded_mesh = Mesh.initFromData(allocator, data, mesh_index, cfg); if (loaded_mesh) |loaded| { var transforms = std.ArrayList(AnimationTransform).init(allocator); for (0..max_joints) |_| { @@ -220,8 +220,11 @@ pub const SkinnedMesh = struct { if (self.joint_locations_dirty) self.applySkeletonTransforms(); - self.mesh.material.state.params.joints = &self.joint_locations; - graphics.drawWithMaterial(&self.mesh.bindings, &self.mesh.material, cam_matrices, model_matrix); + // TODO check this because was from mesh only had one material + self.mesh.materials.items[0].state.params.joints = &self.joint_locations; + for (self.mesh.bindings_list.items, self.mesh.materials.items) |*bindings, *material| { + graphics.drawWithMaterial(bindings, material, cam_matrices, model_matrix); + } } /// Draw this mesh, using the specified material instead of the set one @@ -229,8 +232,11 @@ pub const SkinnedMesh = struct { if (self.joint_locations_dirty) self.applySkeletonTransforms(); - self.mesh.material.state.params.joints = &self.joint_locations; - graphics.drawWithMaterial(&self.mesh.bindings, material, cam_matrices, model_matrix); + // TODO check this because was from mesh only had one material + self.mesh.materials[0].state.params.joints = &self.joint_locations; + for (self.mesh.bindings_list.items) |*bindings| { + graphics.drawWithMaterial(bindings, material, cam_matrices, model_matrix); + } } /// Resets all joints back to their identity matrix diff --git a/src/framework/platform/backends/sokol/graphics.zig b/src/framework/platform/backends/sokol/graphics.zig index fa132155..c6f7ddc7 100644 --- a/src/framework/platform/backends/sokol/graphics.zig +++ b/src/framework/platform/backends/sokol/graphics.zig @@ -76,6 +76,8 @@ pub const BindingsImpl = struct { self.length = length; + // here + for (self.config.vertex_layout.attributes, 0..) |attr, idx| { self.impl.sokol_bindings.?.vertex_buffers[idx] = sg.makeBuffer(.{ .data = switch (attr.binding) { diff --git a/src/framework/platform/graphics.zig b/src/framework/platform/graphics.zig index 89f96ced..5e0ba815 100644 --- a/src/framework/platform/graphics.zig +++ b/src/framework/platform/graphics.zig @@ -147,6 +147,7 @@ pub const CameraMatrices = struct { }; /// A packed mesh vertex +/// This structure is tied to sokol, changing or adding any type breaks rendering as this is transformed into a sokol vertex_buffer pub const PackedVertex = struct { x: f32, y: f32, diff --git a/src/framework/utils/quakemap.zig b/src/framework/utils/quakemap.zig index 816f01ee..22564159 100644 --- a/src/framework/utils/quakemap.zig +++ b/src/framework/utils/quakemap.zig @@ -755,19 +755,21 @@ pub const QuakeMap = struct { } /// builds meshes out of a map of MeshBuilders, and adds them to an ArrayList - pub fn buildMeshes(builders: *const std.StringHashMap(mesh.MeshBuilder), materials: *std.StringHashMap(QuakeMaterial), fallback_material: ?*QuakeMaterial, out_meshes: *std.ArrayList(mesh.Mesh)) !void { + pub fn buildMeshes(builders: *const std.StringHashMap(mesh.MeshBuilder), materials_map: *std.StringHashMap(QuakeMaterial), fallback_material: ?*QuakeMaterial, out_meshes: *std.ArrayList(mesh.Mesh)) !void { var it = builders.iterator(); while (it.next()) |builder| { const b = builder.value_ptr; if (b.indices.items.len == 0) continue; - const found_material = materials.getPtr(builder.key_ptr.*); + const found_material = materials_map.getPtr(builder.key_ptr.*); + var m: Mesh = undefined; if (found_material == null) { - try out_meshes.append(b.buildMesh(fallback_material.?.material)); + m = try b.buildMesh(fallback_material.?.material); } else { - try out_meshes.append(b.buildMesh(found_material.?.material)); + m = try b.buildMesh(found_material.?.material); } + try out_meshes.append(m); } }