From 341e69b32e77b2068b166e62ec85f4ec5bc2a23b Mon Sep 17 00:00:00 2001 From: Peter Li Date: Sun, 13 Apr 2025 18:13:45 -0700 Subject: [PATCH] The shader compilation story kicks ass now --- build.zig | 1 + build.zig.zon | 1 + engine/backlog.zig | 1 + engine/modulelist.zig | 2 + engine/physics/src/physicsCharacter.zig | 3 +- engine/platform/build.zig | 14 - engine/rend/build.zig | 47 + engine/rend/build.zig.zon | 19 + engine/rend/src/rend.zig | 24 + engine/rend/tests/tests.zig | 1 + lib/sdl3/build.zig | 12 + lib/zgltf/.gitignore | 3 + lib/zgltf/LICENSE | 21 + lib/zgltf/README.md | 162 ++ lib/zgltf/build.zig | 19 + lib/zgltf/build.zig.zon | 15 + lib/zgltf/src/helpers.zig | 99 + lib/zgltf/src/main.zig | 1612 +++++++++++++++++ lib/zgltf/src/types.zig | 636 +++++++ lib/zgltf/test-samples/box/Box.gltf | 142 ++ lib/zgltf/test-samples/box/Box0.bin | Bin 0 -> 648 bytes lib/zgltf/test-samples/box_binary/Box.glb | Bin 0 -> 1664 bytes .../box_binary_textured/BoxTextured.glb | Bin 0 -> 6540 bytes .../test-samples/box_binary_textured/test.png | Bin 0 -> 4333 bytes lib/zgltf/test-samples/cameras/Cameras.gltf | 99 + .../test-samples/cameras/simpleSquare.bin | Bin 0 -> 60 bytes .../khr_lights_punctual/Lights.gltf | 158 ++ .../rigged_simple/RiggedSimple.gltf | 451 +++++ .../rigged_simple/RiggedSimple0.bin | Bin 0 -> 11136 bytes .../content/_shaders/_def/sample.frag.json | 22 - .../content/_shaders/_def/sample.vert.json | 54 - projects/sampleGame/main.zig | 5 +- 32 files changed, 3529 insertions(+), 94 deletions(-) create mode 100644 engine/rend/build.zig create mode 100644 engine/rend/build.zig.zon create mode 100644 engine/rend/src/rend.zig create mode 100644 engine/rend/tests/tests.zig create mode 100644 lib/zgltf/.gitignore create mode 100644 lib/zgltf/LICENSE create mode 100644 lib/zgltf/README.md create mode 100644 lib/zgltf/build.zig create mode 100644 lib/zgltf/build.zig.zon create mode 100644 lib/zgltf/src/helpers.zig create mode 100644 lib/zgltf/src/main.zig create mode 100644 lib/zgltf/src/types.zig create mode 100644 lib/zgltf/test-samples/box/Box.gltf create mode 100644 lib/zgltf/test-samples/box/Box0.bin create mode 100644 lib/zgltf/test-samples/box_binary/Box.glb create mode 100644 lib/zgltf/test-samples/box_binary_textured/BoxTextured.glb create mode 100644 lib/zgltf/test-samples/box_binary_textured/test.png create mode 100644 lib/zgltf/test-samples/cameras/Cameras.gltf create mode 100644 lib/zgltf/test-samples/cameras/simpleSquare.bin create mode 100644 lib/zgltf/test-samples/khr_lights_punctual/Lights.gltf create mode 100644 lib/zgltf/test-samples/rigged_simple/RiggedSimple.gltf create mode 100644 lib/zgltf/test-samples/rigged_simple/RiggedSimple0.bin delete mode 100644 projects/content/_shaders/_def/sample.frag.json delete mode 100644 projects/content/_shaders/_def/sample.vert.json diff --git a/build.zig b/build.zig index 90ce479..9c35304 100644 --- a/build.zig +++ b/build.zig @@ -21,6 +21,7 @@ const engineDepList = [_][]const u8{ "core", "papyrus", "platform", + "rend", "physics", }; diff --git a/build.zig.zon b/build.zig.zon index f9739f9..acabc78 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -8,6 +8,7 @@ .papyrus = .{ .path = "engine/papyrus" }, .physics = .{ .path = "engine/physics" }, .platform = .{ .path = "engine/platform" }, + .rend = .{.path = "engine/rend" }, .SpirvReflect = .{ .path = "lib/spirv-reflect-zig" }, .ozz = .{ .path = "lib/ozz" }, }, diff --git a/engine/backlog.zig b/engine/backlog.zig index 729167f..0ce3821 100644 --- a/engine/backlog.zig +++ b/engine/backlog.zig @@ -1,6 +1,7 @@ pub const core = @import("core"); pub const platform = @import("platform"); pub const assets = @import("assets"); +pub const rend = @import("rend"); pub const audio = @import("audio"); // pub const graphics = @import("graphics"); // pub const vkImgui = @import("vkImgui"); diff --git a/engine/modulelist.zig b/engine/modulelist.zig index cb0a8c1..b65670b 100644 --- a/engine/modulelist.zig +++ b/engine/modulelist.zig @@ -4,6 +4,8 @@ pub const list = [_][]const u8{ "assets", "audio", "physics", + + "rend", // to be implemented // "graphics", // "ui", diff --git a/engine/physics/src/physicsCharacter.zig b/engine/physics/src/physicsCharacter.zig index 9fa50f8..69e73bb 100644 --- a/engine/physics/src/physicsCharacter.zig +++ b/engine/physics/src/physicsCharacter.zig @@ -53,9 +53,8 @@ pub const PhysicsCharacter = struct { const scene = self.entity.get(core.Scene).?; const p = self.character.getPosition(); - const position = .{ .x = p[0], .y = p[1], .z = p[2] }; // core.debugSphere(position, 20, .{}); - scene.setPosition(position); + scene.setPosition(.{ .x = p[0], .y = p[1], .z = p[2] }); } pub fn setVelocity(self: *@This(), v: core.Vectorf) void { diff --git a/engine/platform/build.zig b/engine/platform/build.zig index fadd8e0..9ff59ba 100644 --- a/engine/platform/build.zig +++ b/engine/platform/build.zig @@ -1,14 +1,6 @@ const std = @import("std"); const sdl3 = @import("sdl3"); -// pub fn addLib(b: *std.Build, exe: *std.Build.Step.Compile, comptime packagePath: []const u8, cflags: []const []const u8) void { -// _ = b; -// _ = cflags; -// -// exe.addIncludePath(.{ .path = packagePath ++ "/lib" }); -// exe.addLibraryPath(.{ .path = packagePath ++ "/lib" }); -// } - const dependencyList = [_][]const u8{ "core", "sdl3", @@ -18,12 +10,6 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - // oh that is interesting. what I can do is have two - // different modules specified here. - // - // one module for each graphics backend - // - // todo, const mod = b.addModule("platform", .{ .target = target, .optimize = optimize, diff --git a/engine/rend/build.zig b/engine/rend/build.zig new file mode 100644 index 0000000..5af8941 --- /dev/null +++ b/engine/rend/build.zig @@ -0,0 +1,47 @@ +const std = @import("std"); +const sdl3 = @import("sdl3"); + +const dependencyList = [_][]const u8{ + "core", + "sdl3", + "platform", + "shaderTypes", + "objLoader", + "ozz", +}; + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const mod = b.addModule("rend", .{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("src/rend.zig"), + }); + + for (dependencyList) |depName| { + const dep = b.dependency(depName, .{ .target = target, .optimize = optimize }); + const dep_mod = dep.module(depName); + mod.addImport(depName, dep_mod); + + if (std.mem.eql(u8, depName, "ozz")) { + mod.linkLibrary(dep.artifact("ozz_cpp")); + } + } + + sdl3.shaderDefintion(b, mod, "../../lib/sdl3", optimize, "sample.vert", b.path("shaders/sample.vert.json")); + + // ========== tests ========== + const tests = b.addTest(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("tests/tests.zig"), + }); + const test_step = b.step("test", "run unit tests for rend"); + + tests.root_module.addImport("platform", mod); + const runArtifact = b.addRunArtifact(tests); + test_step.dependOn(&runArtifact.step); + b.installArtifact(tests); +} diff --git a/engine/rend/build.zig.zon b/engine/rend/build.zig.zon new file mode 100644 index 0000000..50e2fdb --- /dev/null +++ b/engine/rend/build.zig.zon @@ -0,0 +1,19 @@ +.{ + .name = .rend, + .version = "0.0.0", + .dependencies = .{ + .core = .{ .path = "../core" }, + .assets = .{ .path = "../assets" }, + .platform = .{ .path = "../platform" }, + + .sdl3 = .{ .path = "../../lib/sdl3" }, + .ozz = .{ .path = "../../lib/ozz" }, + .shaderTypes = .{ .path = "../../lib/sdl3/shaderTypes" }, + .cgltf = .{ .path = "../../lib/cgltf" }, + .objLoader = .{ .path = "../../lib/objLoader" }, + }, + .paths = .{ + "", + }, + .fingerprint = 0x1fcf5da860255f09, +} diff --git a/engine/rend/src/rend.zig b/engine/rend/src/rend.zig new file mode 100644 index 0000000..12f7d4f --- /dev/null +++ b/engine/rend/src/rend.zig @@ -0,0 +1,24 @@ +const std = @import("std"); +const core = @import("core"); +const sample_vert = @import("sample.vert"); + +// controls glfw and general windowing +// graphics depends on this one + +pub const Module: core.ModuleDescription = .{ + .name = "rend", + .enabledByDefault = true, +}; + +pub fn start_module(comptime programSpec: anytype, args: anytype, allocator: std.mem.Allocator) !void { + _ = args; + _ = programSpec; + _ = allocator; + core.engine_log("starting up [REND] module...", .{}); + core.engine_log("sample_vert.Scene size = {d}", .{@sizeOf(sample_vert.Scene)}); +} + +pub fn shutdown_module(allocator: std.mem.Allocator) void { + _ = allocator; + core.engine_log("shutting down [REND] module...", .{}); +} diff --git a/engine/rend/tests/tests.zig b/engine/rend/tests/tests.zig new file mode 100644 index 0000000..deb5330 --- /dev/null +++ b/engine/rend/tests/tests.zig @@ -0,0 +1 @@ +test "this does nothing" {} diff --git a/lib/sdl3/build.zig b/lib/sdl3/build.zig index 8123858..e8233da 100644 --- a/lib/sdl3/build.zig +++ b/lib/sdl3/build.zig @@ -24,6 +24,18 @@ pub fn addShaderDefinition( return module; } +pub fn shaderDefintion( + b: *std.Build, + module: *std.Build.Module, + comptime sdl3Path: []const u8, + optimize: std.builtin.OptimizeMode, + shaderName: []const u8, + jsonPath: std.Build.LazyPath, +) void { + const mod = addShaderDefinition(b, sdl3Path, optimize, shaderName, jsonPath); + module.addImport(shaderName, mod); +} + pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); diff --git a/lib/zgltf/.gitignore b/lib/zgltf/.gitignore new file mode 100644 index 0000000..cd21511 --- /dev/null +++ b/lib/zgltf/.gitignore @@ -0,0 +1,3 @@ +.zig-cache/ +build_runner.zig +.DS_Store diff --git a/lib/zgltf/LICENSE b/lib/zgltf/LICENSE new file mode 100644 index 0000000..a54d1a8 --- /dev/null +++ b/lib/zgltf/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Alexandre Chêne + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/zgltf/README.md b/lib/zgltf/README.md new file mode 100644 index 0000000..65a0f42 --- /dev/null +++ b/lib/zgltf/README.md @@ -0,0 +1,162 @@ +# glTF parser for Zig codebase + +This project is a glTF 2.0 parser written in Zig, aiming to replace the use of some C/C++ libraries. All glTF types are fully documented, so it comes nicely with IDE autocompletion, reducing +back and forth with the [specification](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html). + +This library intends to mimic the glTF file structure in memory. Thereby it's designed around arrays and indexes instead of pointers as you may see in `cgltf` or other libraries. Also, it's the **user's responsibility** to load glTF files and their related binaries in memory. + +Note: It's not as complete as the glTF specification yet, but because it's straightforward to add new parsed fields, we'll get new stuff incrementally and on-demand. + +If you would like to contribute, don't hesitate! :) + +## Examples + +```zig +const std = @import("std"); +const Gltf = @import("zgltf"); + +const allocator = std.heap.page_allocator; +const print = std.debug.print; + +pub fn main() void { + const buffer = try std.fs.cwd().readFileAllocOptions( + allocator, + "test-samples/rigged_simple/RiggedSimple.gltf", + 512_000, + null, + 4, + null + ); + defer allocator.free(buf); + + var gltf = Self.init(allocator); + defer gltf.deinit(); + + try gltf.parse(buf); + + for (gltf.nodes.items) |node| { + const message = + \\\ Node's name: {s} + \\\ Children count: {} + \\\ Have skin: {} + ; + + print(message, .{ + node.name, + node.children.items.len, + node.skin != null, + }); + } + + // Or use the debufPrint method. + gltf.debugPrint(); +} +``` + +Also you could easily load data from an `Accessor` with `getDataFromBufferView`: + +```zig +const gltf = Gltf.init(allocator); +try gltf.parse(my_gltf_buf); + +const bin = try std.fs.cwd().readFileAllocOptions( + allocator, + "test-samples/rigged_simple/RiggedSimple0.bin", + 5_000_000, + null, + 4, + null +); +defer allocator.free(buf); + +var vertices = ArrayList(f32).init(allocator); +defer vertices.deinit(); + +const mesh = gltf.data.meshes.items[0]; +for (mesh.primitives.items) |primitive| { + for (primitive.attributes.items) |attribute| { + switch (attribute) { + // Accessor for mesh vertices: + .position => |accessor_index| { + const accessor = gltf.data.accessors.items[accessor_index]; + gltf.getDataFromBufferView(f32, &vertices, accessor, bin); + }, + else => {} + } + } +} + +``` + +Also, there is an `iterator` method that helps you pull data from accessors: + +```zig +// ... +for (primitive.attributes.items) |attribute| { + switch (attribute) { + .position => |idx| { + const accessor = gltf.data.accessors.items[idx]; + var it = accessor.iterator(f32, &gltf, gltf.glb_binary.?); + while (it.next()) |v| { + try vertices.append(.{ + .pos = .{ v[0], v[1], v[2] }, + .normal = .{ 1, 0, 0 }, + .color = .{ 1, 1, 1, 1 }, + .uv_x = 0, + .uv_y = 0, + }); + } + }, + .normal => |idx| { + const accessor = gltf.data.accessors.items[idx]; + var it = accessor.iterator(f32, &gltf, gltf.glb_binary.?); + var i: u32 = 0; + while (it.next()) |n| : (i += 1) { + vertices.items[initial_vertex + i].normal = .{ n[0], n[1], n[2] }; + } + }, + else => {}, + } +} +``` + +## Install + +Note: **Zig 0.11.x is required.** + +```zig +const zgltf = @import("path-to-zgltf/build.zig"); +exe.addModule("zgltf", zgltf.module(b)); +``` + +## Features + +- [x] glTF 2.0 json file +- [x] Scenes +- [x] Nodes +- [x] Buffers/BufferViews +- [x] Meshes +- [x] Images +- [x] Materials +- [x] Animations +- [x] Skins +- [x] Cameras +- [x] Parse `glb` files +- [ ] Morth targets +- [ ] Extras data +- [ ] glTF writer + +Also, we supports some glTF extensions: + +- [x] khr_lights_punctual +- [x] khr_materials_emissive_strength +- [x] khr_materials_ior +- [x] khr_materials_transmission +- [x] khr_materials_volume +- [x] khr_materials_dispersion + +## Contributing to the project + +Don’t be shy about shooting any questions you may have. If you are a beginner/junior, don’t hesitate, I will always encourage you. It’s a safe place here. Also, I would be very happy to receive any kind of pull requests, you will have (at least) some feedback/guidance rapidly. + +Behind screens, there are human beings, living any sort of story. So be always kind and respectful, because we all sheer to learn new things. diff --git a/lib/zgltf/build.zig b/lib/zgltf/build.zig new file mode 100644 index 0000000..0e38f4e --- /dev/null +++ b/lib/zgltf/build.zig @@ -0,0 +1,19 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + _ = b.addModule("zgltf", .{ + .root_source_file = b.path("src/main.zig"), + }); + + var tests = b.addTest(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&tests.step); +} diff --git a/lib/zgltf/build.zig.zon b/lib/zgltf/build.zig.zon new file mode 100644 index 0000000..1acfc40 --- /dev/null +++ b/lib/zgltf/build.zig.zon @@ -0,0 +1,15 @@ +.{ + .name = .zgltf, + .version = "0.1.0", + .minimum_zig_version = "0.11.0", + .dependencies = .{}, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + "test-samples", + "LICENSE", + "README.md", + }, + .fingerprint = 0x7dfe8a12f6d6c124, +} diff --git a/lib/zgltf/src/helpers.zig b/lib/zgltf/src/helpers.zig new file mode 100644 index 0000000..71c0ffe --- /dev/null +++ b/lib/zgltf/src/helpers.zig @@ -0,0 +1,99 @@ +// +// Mostly taken from `zalgebra`. I didn't wanted to import the all library. +// + +pub const Mat4 = [4][4]f32; +pub const Vec3 = [3]f32; +pub const Quat = [4]f32; +pub const identity = Mat4{ + .{ 1, 0, 0, 0 }, + .{ 0, 1, 0, 0 }, + .{ 0, 0, 1, 0 }, + .{ 0, 0, 0, 1 }, +}; + +/// Return 4x4 matrix from given all transform components; `translation`, `rotation` and `scale`. +/// The final order is T * R * S. +pub fn recompose(translation: Vec3, rotation: Quat, scale: Vec3) Mat4 { + const t = blk: { + var mat = identity; + mat[3][0] = translation[0]; + mat[3][1] = translation[1]; + mat[3][2] = translation[2]; + + break :blk mat; + }; + + const r = blk: { + var result = identity; + + const x = rotation[0]; + const y = rotation[1]; + const z = rotation[2]; + const w = rotation[3]; + + const xx = x * x; + const yy = y * y; + const zz = z * z; + const xy = x * y; + const xz = x * z; + const yz = y * z; + const wx = w * x; + const wy = w * y; + const wz = w * z; + + result[0][0] = 1.0 - 2.0 * (yy + zz); + result[0][1] = 2.0 * (xy + wz); + result[0][2] = 2.0 * (xz - wy); + result[0][3] = 0.0; + + result[1][0] = 2.0 * (xy - wz); + result[1][1] = 1.0 - 2.0 * (xx + zz); + result[1][2] = 2.0 * (yz + wx); + result[1][3] = 0.0; + + result[2][0] = 2.0 * (xz + wy); + result[2][1] = 2.0 * (yz - wx); + result[2][2] = 1.0 - 2.0 * (xx + yy); + result[2][3] = 0.0; + + result[3][0] = 0.0; + result[3][1] = 0.0; + result[3][2] = 0.0; + result[3][3] = 1.0; + + break :blk result; + }; + + const s = blk: { + var mat = identity; + mat[0][0] = scale[0]; + mat[1][1] = scale[1]; + mat[2][2] = scale[2]; + + break :blk mat; + }; + + return mul(t, mul(r, s)); +} + +/// Matrices' multiplication. +/// Produce a new matrix from given two matrices. +pub fn mul(left: Mat4, right: Mat4) Mat4 { + var result = identity; + + for (result, 0..) |_, column| { + for (result[column], 0..) |_, row| { + var sum: f32 = 0; + var left_column: usize = 0; + + while (left_column < 4) : (left_column += 1) { + sum += left[left_column][row] * right[column][left_column]; + } + + result[column][row] = sum; + } + } + + return result; +} diff --git a/lib/zgltf/src/main.zig b/lib/zgltf/src/main.zig new file mode 100644 index 0000000..ab7157d --- /dev/null +++ b/lib/zgltf/src/main.zig @@ -0,0 +1,1612 @@ +/// +/// glTF™ 2.0 Specification is available here: +/// https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html +/// +const Self = @This(); + +const std = @import("std"); +const helpers = @import("helpers.zig"); +const types = @import("types.zig"); + +const mem = std.mem; +const math = std.math; +const json = std.json; +const fmt = std.fmt; +const panic = std.debug.panic; +const print = std.debug.print; +const assert = std.debug.assert; +const ArrayList = std.ArrayList; +const Allocator = std.mem.Allocator; +const ArenaAllocator = std.heap.ArenaAllocator; +const Mat4 = helpers.Mat4; +const Vec3 = helpers.Vec3; +const Quat = helpers.Quat; + +pub const Scene = types.Scene; +pub const Node = types.Node; +pub const Index = types.Index; +pub const Mesh = types.Mesh; +pub const Material = types.Material; +pub const Skin = types.Skin; +pub const TextureSampler = types.TextureSampler; +pub const Image = types.Image; +pub const Camera = types.Camera; +pub const Animation = types.Animation; +pub const Texture = types.Texture; +pub const Accessor = types.Accessor; +pub const AccessorType = types.AccessorType; +pub const AccessorIterator = types.AccessorIterator; +pub const BufferView = types.BufferView; +pub const Buffer = types.Buffer; +pub const Primitive = types.Primitive; +pub const Attribute = types.Attribute; +pub const Mode = types.Mode; +pub const ComponentType = types.ComponentType; +pub const Target = types.Target; +pub const MetallicRoughness = types.MetallicRoughness; +pub const AnimationSampler = types.AnimationSampler; +pub const Channel = types.Channel; +pub const MagFilter = types.MagFilter; +pub const MinFilter = types.MinFilter; +pub const WrapMode = types.WrapMode; +pub const TargetProperty = types.TargetProperty; +pub const Asset = types.Asset; +pub const LightType = types.LightType; +pub const Light = types.Light; +pub const LightSpot = types.LightSpot; + +pub const Data = struct { + asset: Asset, + scene: ?Index = null, + scenes: ArrayList(Scene), + cameras: ArrayList(Camera), + nodes: ArrayList(Node), + meshes: ArrayList(Mesh), + materials: ArrayList(Material), + skins: ArrayList(Skin), + samplers: ArrayList(TextureSampler), + images: ArrayList(Image), + animations: ArrayList(Animation), + textures: ArrayList(Texture), + accessors: ArrayList(Accessor), + buffer_views: ArrayList(BufferView), + buffers: ArrayList(Buffer), + lights: ArrayList(Light), +}; + +arena: *ArenaAllocator, +data: Data, + +glb_binary: ?[]align(4) const u8 = null, + +pub fn init(allocator: Allocator) Self { + var arena = allocator.create(ArenaAllocator) catch { + panic("Error while allocating memory for gltf arena.", .{}); + }; + + arena.* = ArenaAllocator.init(allocator); + + const alloc = arena.allocator(); + return Self{ + .arena = arena, + .data = .{ + .asset = Asset{ .version = "Undefined" }, + .scenes = ArrayList(Scene).init(alloc), + .nodes = ArrayList(Node).init(alloc), + .cameras = ArrayList(Camera).init(alloc), + .meshes = ArrayList(Mesh).init(alloc), + .materials = ArrayList(Material).init(alloc), + .skins = ArrayList(Skin).init(alloc), + .samplers = ArrayList(TextureSampler).init(alloc), + .images = ArrayList(Image).init(alloc), + .animations = ArrayList(Animation).init(alloc), + .textures = ArrayList(Texture).init(alloc), + .accessors = ArrayList(Accessor).init(alloc), + .buffer_views = ArrayList(BufferView).init(alloc), + .buffers = ArrayList(Buffer).init(alloc), + .lights = ArrayList(Light).init(alloc), + }, + }; +} + +/// Fill data by parsing a glTF file's buffer. +pub fn parse(self: *Self, file_buffer: []align(4) const u8) !void { + if (isGlb(file_buffer)) { + try self.parseGlb(file_buffer); + } else { + try self.parseGltfJson(file_buffer); + } +} + +pub fn debugPrint(self: *const Self) void { + const msg = + \\ + \\ glTF file info: + \\ + \\ Node {} + \\ Mesh {} + \\ Skin {} + \\ Animation {} + \\ Texture {} + \\ Material {} + \\ + \\ + ; + + print(msg, .{ + self.data.nodes.items.len, + self.data.meshes.items.len, + self.data.skins.items.len, + self.data.animations.items.len, + self.data.textures.items.len, + self.data.materials.items.len, + }); + + print(" Details:\n\n", .{}); + + if (self.data.skins.items.len > 0) { + print(" Skins found:\n", .{}); + + for (self.data.skins.items) |skin| { + print(" '{s}' found with {} joint(s).\n", .{ + skin.name, + skin.joints.items.len, + }); + } + + print("\n", .{}); + } + + if (self.data.animations.items.len > 0) { + print(" Animations found:\n", .{}); + + for (self.data.animations.items) |anim| { + print( + " '{s}' found with {} sampler(s) and {} channel(s).\n", + .{ anim.name, anim.samplers.items.len, anim.channels.items.len }, + ); + } + + print("\n", .{}); + } +} + +/// Retrieve actual data from a glTF BufferView through a given glTF Accessor. +/// Note: This library won't pull to memory the binary buffer corresponding +/// to the BufferView. +pub fn getDataFromBufferView( + self: *const Self, + comptime T: type, + /// List that will be fill with data. + list: *ArrayList(T), + accessor: Accessor, + binary: []const u8, +) void { + if (switch (accessor.component_type) { + .byte => T != i8, + .unsigned_byte => T != u8, + .short => T != i16, + .unsigned_short => T != u16, + .unsigned_integer => T != u32, + .float => T != f32, + }) { + panic( + "Mismatch between gltf component '{}' and given type '{}'.", + .{ accessor.component_type, T }, + ); + } + + if (accessor.buffer_view == null) { + panic("Accessors without buffer_view are not supported yet.", .{}); + } + + const buffer_view = self.data.buffer_views.items[accessor.buffer_view.?]; + + const comp_size = @sizeOf(T); + const offset = (accessor.byte_offset + buffer_view.byte_offset) / comp_size; + + const stride = blk: { + if (buffer_view.byte_stride) |byte_stride| { + break :blk byte_stride / comp_size; + } else { + break :blk accessor.stride / comp_size; + } + }; + + const total_count = accessor.count; + const datum_count: usize = switch (accessor.type) { + // Scalar. + .scalar => 1, + // Vec2. + .vec2 => 2, + // Vec3. + .vec3 => 3, + // Vec4. + .vec4 => 4, + // Vec4. + .mat4x4 => 16, + else => { + panic("Accessor type '{}' not implemented.", .{accessor.type}); + }, + }; + + const data = @as([*]const T, @ptrCast(@alignCast(binary.ptr))); + + var current_count: usize = 0; + while (current_count < total_count) : (current_count += 1) { + const slice = (data + offset + current_count * stride)[0..datum_count]; + list.appendSlice(slice) catch unreachable; + } +} + +pub fn deinit(self: *Self) void { + self.arena.deinit(); + self.arena.child_allocator.destroy(self.arena); +} + +pub fn getLocalTransform(node: Node) Mat4 { + return blk: { + if (node.matrix) |mat4x4| { + break :blk .{ + mat4x4[0..4].*, + mat4x4[4..8].*, + mat4x4[8..12].*, + mat4x4[12..16].*, + }; + } + + break :blk helpers.recompose( + node.translation, + node.rotation, + node.scale, + ); + }; +} + +pub fn getGlobalTransform(data: *const Data, node: Node) Mat4 { + var parent_index = node.parent; + var node_transform: Mat4 = getLocalTransform(node); + + while (parent_index != null) { + const parent = data.nodes.items[parent_index.?]; + const parent_transform = getLocalTransform(parent); + + node_transform = helpers.mul(parent_transform, node_transform); + parent_index = parent.parent; + } + + return node_transform; +} + +fn isGlb(glb_buffer: []align(4) const u8) bool { + const GLB_MAGIC_NUMBER: u32 = 0x46546C67; // 'gltf' in ASCII. + const fields = @as([*]const u32, @ptrCast(glb_buffer)); + + return fields[0] == GLB_MAGIC_NUMBER; +} + +fn parseGlb(self: *Self, glb_buffer: []align(4) const u8) !void { + const GLB_CHUNK_TYPE_JSON: u32 = 0x4E4F534A; // 'JSON' in ASCII. + const GLB_CHUNK_TYPE_BIN: u32 = 0x004E4942; // 'BIN' in ASCII. + + // Keep track of the moving index in the glb buffer. + var index: usize = 0; + + // 'cause most of the interesting fields are u32s in the buffer, it's + // easier to read them with a pointer cast. + const fields = @as([*]const u32, @ptrCast(glb_buffer)); + + // The 12-byte header consists of three 4-byte entries: + // u32 magic + // u32 version + // u32 length + const total_length = blk: { + const header = fields[0..3]; + + const version = header[1]; + const length = header[2]; + + if (!isGlb(glb_buffer)) { + panic("First 32 bits are not equal to magic number.", .{}); + } + + if (version != 2) { + panic("Only glTF spec v2 is supported.", .{}); + } + + index = header.len * @sizeOf(u32); + break :blk length; + }; + + // Each chunk has the following structure: + // u32 chunkLength + // u32 chunkType + // ubyte[] chunkData + const json_buffer = blk: { + const json_chunk = fields[3..6]; + + if (json_chunk[1] != GLB_CHUNK_TYPE_JSON) { + panic("First GLB chunk must be JSON data.", .{}); + } + + const json_bytes: u32 = fields[3]; + const start = index + 2 * @sizeOf(u32); + const end = start + json_bytes; + + const json_buffer = glb_buffer[start..end]; + + index = end; + break :blk json_buffer; + }; + + const binary_buffer = blk: { + const fields_index = index / @sizeOf(u32); + + const binary_bytes = fields[fields_index]; + const start = index + 2 * @sizeOf(u32); + const end = start + binary_bytes; + + assert(end == total_length); + + std.debug.assert(start % 4 == 0); + std.debug.assert(end % 4 == 0); + const binary: []align(4) const u8 = @alignCast(glb_buffer[start..end]); + + if (fields[fields_index + 1] != GLB_CHUNK_TYPE_BIN) { + panic("Second GLB chunk must be binary data.", .{}); + } + + index = end; + break :blk binary; + }; + + try self.parseGltfJson(json_buffer); + self.glb_binary = binary_buffer; + + const buffer_views = self.data.buffer_views.items; + + for (self.data.images.items) |*image| { + if (image.buffer_view) |buffer_view_index| { + const buffer_view = buffer_views[buffer_view_index]; + const start = buffer_view.byte_offset; + const end = start + buffer_view.byte_length; + image.data = binary_buffer[start..end]; + } + } +} + +fn parseGltfJson(self: *Self, gltf_json: []const u8) !void { + const alloc = self.arena.allocator(); + + var gltf_parsed = try json.parseFromSlice(json.Value, alloc, gltf_json, .{}); + defer gltf_parsed.deinit(); + + const gltf: *json.Value = &gltf_parsed.value; + + if (gltf.object.get("asset")) |json_value| { + var asset = &self.data.asset; + + if (json_value.object.get("version")) |version| { + asset.version = try alloc.dupe(u8, version.string); + } else { + panic("Asset's version is missing.", .{}); + } + + if (json_value.object.get("generator")) |generator| { + asset.generator = try alloc.dupe(u8, generator.string); + } + + if (json_value.object.get("copyright")) |copyright| { + asset.copyright = try alloc.dupe(u8, copyright.string); + } + } + + if (gltf.object.get("nodes")) |nodes| { + for (nodes.array.items, 0..) |item, index| { + const object = item.object; + + var node = Node{ + .name = undefined, + .children = ArrayList(Index).init(alloc), + }; + + if (object.get("name")) |name| { + node.name = try alloc.dupe(u8, name.string); + } else { + node.name = try fmt.allocPrint(alloc, "Node_{}", .{index}); + } + + if (object.get("mesh")) |mesh| { + node.mesh = parseIndex(mesh); + } + + if (object.get("camera")) |camera_index| { + node.camera = parseIndex(camera_index); + } + + if (object.get("skin")) |skin| { + node.skin = parseIndex(skin); + } + + if (object.get("children")) |children| { + for (children.array.items) |value| { + try node.children.append(parseIndex(value)); + } + } + + if (object.get("rotation")) |rotation| { + for (rotation.array.items, 0..) |component, i| { + node.rotation[i] = parseFloat(f32, component); + } + } + + if (object.get("translation")) |translation| { + for (translation.array.items, 0..) |component, i| { + node.translation[i] = parseFloat(f32, component); + } + } + + if (object.get("scale")) |scale| { + for (scale.array.items, 0..) |component, i| { + node.scale[i] = parseFloat(f32, component); + } + } + + if (object.get("matrix")) |matrix| { + node.matrix = [16]f32{ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + }; + + for (matrix.array.items, 0..) |component, i| { + node.matrix.?[i] = parseFloat(f32, component); + } + } + + if (object.get("extensions")) |extensions| { + if (extensions.object.get("KHR_lights_punctual")) |lights_punctual| { + if (lights_punctual.object.get("light")) |light| { + node.light = @as(Index, @intCast(light.integer)); + } + } + } + + try self.data.nodes.append(node); + } + } + + if (gltf.object.get("cameras")) |cameras| { + for (cameras.array.items, 0..) |item, index| { + const object = item.object; + + var camera = Camera{ + .name = undefined, + .type = undefined, + }; + + if (object.get("name")) |name| { + camera.name = try alloc.dupe(u8, name.string); + } else { + camera.name = try fmt.allocPrint(alloc, "Camera_{}", .{index}); + } + + if (object.get("type")) |name| { + if (mem.eql(u8, name.string, "perspective")) { + if (object.get("perspective")) |perspective| { + var value = perspective.object; + + camera.type = .{ + .perspective = .{ + .aspect_ratio = if (value.get("aspectRatio")) |aspect_ratio| parseFloat( + f32, + aspect_ratio, + ) else null, + .yfov = parseFloat(f32, value.get("yfov").?), + .zfar = if (value.get("zfar")) |zfar| parseFloat( + f32, + zfar, + ) else null, + .znear = parseFloat(f32, value.get("znear").?), + }, + }; + } else { + panic("Camera's perspective value is missing.", .{}); + } + } else if (mem.eql(u8, name.string, "orthographic")) { + if (object.get("orthographic")) |orthographic| { + var value = orthographic.object; + + camera.type = .{ + .orthographic = .{ + .xmag = parseFloat(f32, value.get("xmag").?), + .ymag = parseFloat(f32, value.get("ymag").?), + .zfar = parseFloat(f32, value.get("zfar").?), + .znear = parseFloat(f32, value.get("znear").?), + }, + }; + } else { + panic("Camera's orthographic value is missing.", .{}); + } + } else { + panic( + "Camera's type must be perspective or orthographic.", + .{}, + ); + } + } + + try self.data.cameras.append(camera); + } + } + + if (gltf.object.get("skins")) |skins| { + for (skins.array.items, 0..) |item, index| { + const object = item.object; + + var skin = Skin{ + .name = undefined, + .joints = ArrayList(Index).init(alloc), + }; + + if (object.get("name")) |name| { + skin.name = try alloc.dupe(u8, name.string); + } else { + skin.name = try fmt.allocPrint(alloc, "Skin_{}", .{index}); + } + + if (object.get("joints")) |joints| { + for (joints.array.items) |join| { + try skin.joints.append(parseIndex(join)); + } + } + + if (object.get("skeleton")) |skeleton| { + skin.skeleton = parseIndex(skeleton); + } + + if (object.get("inverseBindMatrices")) |inv_bind_mat4| { + skin.inverse_bind_matrices = parseIndex(inv_bind_mat4); + } + + try self.data.skins.append(skin); + } + } + + if (gltf.object.get("meshes")) |meshes| { + for (meshes.array.items, 0..) |item, index| { + const object = item.object; + + var mesh: Mesh = .{ + .name = undefined, + .primitives = ArrayList(Primitive).init(alloc), + }; + + if (object.get("name")) |name| { + mesh.name = try alloc.dupe(u8, name.string); + } else { + mesh.name = try fmt.allocPrint(alloc, "Mesh_{}", .{index}); + } + + if (object.get("primitives")) |primitives| { + for (primitives.array.items) |prim_item| { + var primitive: Primitive = .{ + .attributes = ArrayList(Attribute).init(alloc), + }; + + if (prim_item.object.get("mode")) |mode| { + primitive.mode = @as(Mode, @enumFromInt(mode.integer)); + } + + if (prim_item.object.get("indices")) |indices| { + primitive.indices = parseIndex(indices); + } + + if (prim_item.object.get("material")) |material| { + primitive.material = parseIndex(material); + } + + if (prim_item.object.get("attributes")) |attributes| { + if (attributes.object.get("POSITION")) |position| { + try primitive.attributes.append( + .{ + .position = parseIndex(position), + }, + ); + } + + if (attributes.object.get("NORMAL")) |normal| { + try primitive.attributes.append( + .{ + .normal = parseIndex(normal), + }, + ); + } + + if (attributes.object.get("TANGENT")) |tangent| { + try primitive.attributes.append( + .{ + .tangent = parseIndex(tangent), + }, + ); + } + + const texcoords = [_][]const u8{ + "TEXCOORD_0", + "TEXCOORD_1", + "TEXCOORD_2", + "TEXCOORD_3", + "TEXCOORD_4", + "TEXCOORD_5", + "TEXCOORD_6", + }; + + for (texcoords) |tex_name| { + if (attributes.object.get(tex_name)) |texcoord| { + try primitive.attributes.append( + .{ + .texcoord = parseIndex(texcoord), + }, + ); + } + } + + const joints = [_][]const u8{ + "JOINTS_0", + "JOINTS_1", + "JOINTS_2", + "JOINTS_3", + "JOINTS_4", + "JOINTS_5", + "JOINTS_6", + }; + + for (joints) |join_count| { + if (attributes.object.get(join_count)) |joint| { + try primitive.attributes.append( + .{ + .joints = parseIndex(joint), + }, + ); + } + } + + const weights = [_][]const u8{ + "WEIGHTS_0", + "WEIGHTS_1", + "WEIGHTS_2", + "WEIGHTS_3", + "WEIGHTS_4", + "WEIGHTS_5", + "WEIGHTS_6", + }; + + for (weights) |weight_count| { + if (attributes.object.get(weight_count)) |weight| { + try primitive.attributes.append( + .{ + .weights = parseIndex(weight), + }, + ); + } + } + } + + try mesh.primitives.append(primitive); + } + } + + try self.data.meshes.append(mesh); + } + } + + if (gltf.object.get("accessors")) |accessors| { + for (accessors.array.items) |item| { + const object = item.object; + + var accessor = Accessor{ + .component_type = undefined, + .type = undefined, + .count = undefined, + .stride = undefined, + }; + + if (object.get("componentType")) |component_type| { + accessor.component_type = @as(ComponentType, @enumFromInt(component_type.integer)); + } else { + panic("Accessor's componentType is missing.", .{}); + } + + if (object.get("count")) |count| { + accessor.count = @as(i32, @intCast(count.integer)); + } else { + panic("Accessor's count is missing.", .{}); + } + + if (object.get("type")) |accessor_type| { + if (mem.eql(u8, accessor_type.string, "SCALAR")) { + accessor.type = .scalar; + } else if (mem.eql(u8, accessor_type.string, "VEC2")) { + accessor.type = .vec2; + } else if (mem.eql(u8, accessor_type.string, "VEC3")) { + accessor.type = .vec3; + } else if (mem.eql(u8, accessor_type.string, "VEC4")) { + accessor.type = .vec4; + } else if (mem.eql(u8, accessor_type.string, "MAT2")) { + accessor.type = .mat2x2; + } else if (mem.eql(u8, accessor_type.string, "MAT3")) { + accessor.type = .mat3x3; + } else if (mem.eql(u8, accessor_type.string, "MAT4")) { + accessor.type = .mat4x4; + } else { + panic("Accessor's type '{s}' is invalid.", .{accessor_type.string}); + } + } else { + panic("Accessor's type is missing.", .{}); + } + + if (object.get("normalized")) |normalized| { + accessor.normalized = normalized.bool; + } + + if (object.get("bufferView")) |buffer_view| { + accessor.buffer_view = parseIndex(buffer_view); + } + + if (object.get("byteOffset")) |byte_offset| { + accessor.byte_offset = @as(usize, @intCast(byte_offset.integer)); + } + + const component_size: usize = switch (accessor.component_type) { + .byte => @sizeOf(i8), + .unsigned_byte => @sizeOf(u8), + .short => @sizeOf(i16), + .unsigned_short => @sizeOf(u16), + .unsigned_integer => @sizeOf(u32), + .float => @sizeOf(f32), + }; + + accessor.stride = switch (accessor.type) { + .scalar => component_size, + .vec2 => 2 * component_size, + .vec3 => 3 * component_size, + .vec4 => 4 * component_size, + .mat2x2 => 4 * component_size, + .mat3x3 => 9 * component_size, + .mat4x4 => 16 * component_size, + }; + + try self.data.accessors.append(accessor); + } + } + + if (gltf.object.get("bufferViews")) |buffer_views| { + for (buffer_views.array.items) |item| { + const object = item.object; + + var buffer_view = BufferView{ + .buffer = undefined, + .byte_length = undefined, + }; + + if (object.get("buffer")) |buffer| { + buffer_view.buffer = parseIndex(buffer); + } + + if (object.get("byteLength")) |byte_length| { + buffer_view.byte_length = @as(usize, @intCast(byte_length.integer)); + } + + if (object.get("byteOffset")) |byte_offset| { + buffer_view.byte_offset = @as(usize, @intCast(byte_offset.integer)); + } + + if (object.get("byteStride")) |byte_stride| { + buffer_view.byte_stride = @as(usize, @intCast(byte_stride.integer)); + } + + if (object.get("target")) |target| { + buffer_view.target = @as(Target, @enumFromInt(target.integer)); + } + + try self.data.buffer_views.append(buffer_view); + } + } + + if (gltf.object.get("buffers")) |buffers| { + for (buffers.array.items) |item| { + const object = item.object; + + var buffer = Buffer{ + .byte_length = undefined, + }; + + if (object.get("uri")) |uri| { + buffer.uri = uri.string; + } + + if (object.get("byteLength")) |byte_length| { + buffer.byte_length = @as(usize, @intCast(byte_length.integer)); + } else { + panic("Buffer's byteLength is missing.", .{}); + } + + try self.data.buffers.append(buffer); + } + } + + if (gltf.object.get("scene")) |default_scene| { + self.data.scene = parseIndex(default_scene); + } + + if (gltf.object.get("scenes")) |scenes| { + for (scenes.array.items, 0..) |item, index| { + const object = item.object; + + var scene = Scene{ + .name = undefined, + }; + + if (object.get("name")) |name| { + scene.name = try alloc.dupe(u8, name.string); + } else { + scene.name = try fmt.allocPrint(alloc, "Scene_{}", .{index}); + } + + if (object.get("nodes")) |nodes| { + scene.nodes = ArrayList(Index).init(alloc); + + for (nodes.array.items) |node| { + try scene.nodes.?.append(parseIndex(node)); + } + } + + try self.data.scenes.append(scene); + } + } + + if (gltf.object.get("materials")) |materials| { + for (materials.array.items, 0..) |item, m_index| { + const object = item.object; + + var material = Material{ + .name = undefined, + }; + + if (object.get("name")) |name| { + material.name = try alloc.dupe(u8, name.string); + } else { + material.name = try fmt.allocPrint(alloc, "Material_{}", .{m_index}); + } + + if (object.get("pbrMetallicRoughness")) |pbrMetallicRoughness| { + var metallic_roughness: MetallicRoughness = .{}; + if (pbrMetallicRoughness.object.get("baseColorFactor")) |color_factor| { + for (color_factor.array.items, 0..) |factor, i| { + metallic_roughness.base_color_factor[i] = parseFloat(f32, factor); + } + } + + if (pbrMetallicRoughness.object.get("metallicFactor")) |factor| { + metallic_roughness.metallic_factor = parseFloat(f32, factor); + } + + if (pbrMetallicRoughness.object.get("roughnessFactor")) |factor| { + metallic_roughness.roughness_factor = parseFloat(f32, factor); + } + + if (pbrMetallicRoughness.object.get("baseColorTexture")) |texture_info| { + metallic_roughness.base_color_texture = .{ + .index = undefined, + }; + + if (texture_info.object.get("index")) |index| { + metallic_roughness.base_color_texture.?.index = parseIndex(index); + } + + if (texture_info.object.get("texCoord")) |texcoord| { + metallic_roughness.base_color_texture.?.texcoord = @as(i32, @intCast(texcoord.integer)); + } + } + + if (pbrMetallicRoughness.object.get("metallicRoughnessTexture")) |texture_info| { + metallic_roughness.metallic_roughness_texture = .{ + .index = undefined, + }; + + if (texture_info.object.get("index")) |index| { + metallic_roughness.metallic_roughness_texture.?.index = parseIndex(index); + } + + if (texture_info.object.get("texCoord")) |texcoord| { + metallic_roughness.metallic_roughness_texture.?.texcoord = @as(i32, @intCast(texcoord.integer)); + } + } + + material.metallic_roughness = metallic_roughness; + } + + if (object.get("normalTexture")) |normal_texture| { + material.normal_texture = .{ + .index = undefined, + }; + + if (normal_texture.object.get("index")) |index| { + material.normal_texture.?.index = parseIndex(index); + } + + if (normal_texture.object.get("texCoord")) |index| { + material.normal_texture.?.texcoord = @as(i32, @intCast(index.integer)); + } + + if (normal_texture.object.get("scale")) |scale| { + material.normal_texture.?.scale = parseFloat(f32, scale); + } + } + + if (object.get("emissiveTexture")) |emissive_texture| { + material.emissive_texture = .{ + .index = undefined, + }; + + if (emissive_texture.object.get("index")) |index| { + material.emissive_texture.?.index = parseIndex(index); + } + + if (emissive_texture.object.get("texCoord")) |index| { + material.emissive_texture.?.texcoord = @as(i32, @intCast(index.integer)); + } + } + + if (object.get("occlusionTexture")) |occlusion_texture| { + material.occlusion_texture = .{ + .index = undefined, + }; + + if (occlusion_texture.object.get("index")) |index| { + material.occlusion_texture.?.index = parseIndex(index); + } + + if (occlusion_texture.object.get("texCoord")) |index| { + material.occlusion_texture.?.texcoord = @as(i32, @intCast(index.integer)); + } + + if (occlusion_texture.object.get("strength")) |strength| { + material.occlusion_texture.?.strength = parseFloat(f32, strength); + } + } + + if (object.get("alphaMode")) |alpha_mode| { + if (mem.eql(u8, alpha_mode.string, "OPAQUE")) { + material.alpha_mode = .@"opaque"; + } + if (mem.eql(u8, alpha_mode.string, "MASK")) { + material.alpha_mode = .mask; + } + if (mem.eql(u8, alpha_mode.string, "BLEND")) { + material.alpha_mode = .blend; + } + } + + if (object.get("doubleSided")) |double_sided| { + material.is_double_sided = double_sided.bool; + } + + if (object.get("alphaCutoff")) |alpha_cutoff| { + material.alpha_cutoff = parseFloat(f32, alpha_cutoff); + } + + if (object.get("emissiveFactor")) |emissive_factor| { + for (emissive_factor.array.items, 0..) |factor, i| { + material.emissive_factor[i] = parseFloat(f32, factor); + } + } + + if (object.get("extensions")) |extensions| { + if (extensions.object.get("KHR_materials_emissive_strength")) |materials_emissive_strength| { + if (materials_emissive_strength.object.get("emissiveStrength")) |emissive_strength| { + material.emissive_strength = parseFloat(f32, emissive_strength); + } + } + + if (extensions.object.get("KHR_materials_ior")) |materials_ior| { + if (materials_ior.object.get("ior")) |ior| { + material.ior = parseFloat(f32, ior); + } + } + + if (extensions.object.get("KHR_materials_transmission")) |materials_transmission| { + if (materials_transmission.object.get("transmissionFactor")) |transmission_factor| { + material.transmission_factor = parseFloat(f32, transmission_factor); + } + + if (materials_transmission.object.get("transmissionTexture")) |transmission_texture| { + material.transmission_texture = .{ + .index = undefined, + }; + + if (transmission_texture.object.get("index")) |index| { + material.transmission_texture.?.index = parseIndex(index); + } + + if (transmission_texture.object.get("texCoord")) |index| { + material.transmission_texture.?.texcoord = @as(i32, @intCast(index.integer)); + } + } + } + + if (extensions.object.get("KHR_materials_volume")) |materials_volume| { + if (materials_volume.object.get("thicknessFactor")) |thickness_factor| { + material.thickness_factor = parseFloat(f32, thickness_factor); + } + + if (materials_volume.object.get("thicknessTexture")) |thickness_texture| { + material.thickness_texture = .{ + .index = undefined, + }; + + if (thickness_texture.object.get("index")) |index| { + material.thickness_texture.?.index = parseIndex(index); + } + + if (thickness_texture.object.get("texCoord")) |index| { + material.thickness_texture.?.texcoord = @as(i32, @intCast(index.integer)); + } + } + + if (materials_volume.object.get("attenuationDistance")) |attenuation_distance| { + material.attenuation_distance = parseFloat(f32, attenuation_distance); + } + + if (materials_volume.object.get("attenuationColor")) |attenuation_color| { + for (&material.attenuation_color, attenuation_color.array.items) |*dst, src| { + dst.* = parseFloat(f32, src); + } + } + } + + if (extensions.object.get("KHR_materials_dispersion")) |materials_dispersion| { + if (materials_dispersion.object.get("dispersion")) |dispersion| { + material.dispersion = parseFloat(f32, dispersion); + } + } + } + + try self.data.materials.append(material); + } + } + + if (gltf.object.get("textures")) |textures| { + for (textures.array.items) |item| { + var texture = Texture{}; + + if (item.object.get("source")) |source| { + texture.source = parseIndex(source); + } + + if (item.object.get("sampler")) |sampler| { + texture.sampler = parseIndex(sampler); + } + + try self.data.textures.append(texture); + } + } + + if (gltf.object.get("animations")) |animations| { + for (animations.array.items, 0..) |item, index| { + const object = item.object; + + var animation = Animation{ + .samplers = ArrayList(AnimationSampler).init(alloc), + .channels = ArrayList(Channel).init(alloc), + .name = undefined, + }; + + if (item.object.get("name")) |name| { + animation.name = try alloc.dupe(u8, name.string); + } else { + animation.name = try fmt.allocPrint(alloc, "Animation_{}", .{index}); + } + + if (object.get("samplers")) |samplers| { + for (samplers.array.items) |sampler_item| { + var sampler: AnimationSampler = .{ + .input = undefined, + .output = undefined, + }; + + if (sampler_item.object.get("input")) |input| { + sampler.input = parseIndex(input); + } else { + panic("Animation sampler's input is missing.", .{}); + } + + if (sampler_item.object.get("output")) |output| { + sampler.output = parseIndex(output); + } else { + panic("Animation sampler's output is missing.", .{}); + } + + if (sampler_item.object.get("interpolation")) |interpolation| { + if (mem.eql(u8, interpolation.string, "LINEAR")) { + sampler.interpolation = .linear; + } + + if (mem.eql(u8, interpolation.string, "STEP")) { + sampler.interpolation = .step; + } + + if (mem.eql(u8, interpolation.string, "CUBICSPLINE")) { + sampler.interpolation = .cubicspline; + } + } + + try animation.samplers.append(sampler); + } + } + + if (object.get("channels")) |channels| { + for (channels.array.items) |channel_item| { + var channel: Channel = .{ .sampler = undefined, .target = .{ + .node = undefined, + .property = undefined, + } }; + + if (channel_item.object.get("sampler")) |sampler_index| { + channel.sampler = parseIndex(sampler_index); + } else { + panic("Animation channel's sampler is missing.", .{}); + } + + if (channel_item.object.get("target")) |target_item| { + if (target_item.object.get("node")) |node_index| { + channel.target.node = parseIndex(node_index); + } else { + panic("Animation target's node is missing.", .{}); + } + + if (target_item.object.get("path")) |path| { + if (mem.eql(u8, path.string, "translation")) { + channel.target.property = .translation; + } else if (mem.eql(u8, path.string, "rotation")) { + channel.target.property = .rotation; + } else if (mem.eql(u8, path.string, "scale")) { + channel.target.property = .scale; + } else if (mem.eql(u8, path.string, "weights")) { + channel.target.property = .weights; + } else { + panic("Animation path/property is invalid.", .{}); + } + } else { + panic("Animation target's path/property is missing.", .{}); + } + } else { + panic("Animation channel's target is missing.", .{}); + } + + try animation.channels.append(channel); + } + } + + try self.data.animations.append(animation); + } + } + + if (gltf.object.get("samplers")) |samplers| { + for (samplers.array.items) |item| { + const object = item.object; + var sampler = TextureSampler{}; + + if (object.get("magFilter")) |mag_filter| { + sampler.mag_filter = @as(MagFilter, @enumFromInt(mag_filter.integer)); + } + + if (object.get("minFilter")) |min_filter| { + sampler.min_filter = @as(MinFilter, @enumFromInt(min_filter.integer)); + } + + if (object.get("wrapS")) |wrap_s| { + sampler.wrap_s = @as(WrapMode, @enumFromInt(wrap_s.integer)); + } + + if (object.get("wrapt")) |wrap_t| { + sampler.wrap_t = @as(WrapMode, @enumFromInt(wrap_t.integer)); + } + + try self.data.samplers.append(sampler); + } + } + + if (gltf.object.get("images")) |images| { + for (images.array.items) |item| { + const object = item.object; + var image = Image{}; + + if (object.get("uri")) |uri| { + image.uri = try alloc.dupe(u8, uri.string); + } + + if (object.get("mimeType")) |mime_type| { + image.mime_type = try alloc.dupe(u8, mime_type.string); + } + + if (object.get("bufferView")) |buffer_view| { + image.buffer_view = parseIndex(buffer_view); + } + + try self.data.images.append(image); + } + } + + if (gltf.object.get("extensions")) |extensions| { + if (extensions.object.get("KHR_lights_punctual")) |lights_punctual| { + if (lights_punctual.object.get("lights")) |lights| { + for (lights.array.items) |item| { + const object: json.ObjectMap = item.object; + + var light = Light{ + .name = null, + .type = undefined, + .range = math.inf(f32), + .spot = null, + }; + + if (object.get("name")) |name| { + light.name = try alloc.dupe(u8, name.string); + } + + if (object.get("color")) |color| { + for (color.array.items, 0..) |component, i| { + light.color[i] = parseFloat(f32, component); + } + } + + if (object.get("intensity")) |intensity| { + light.intensity = parseFloat(f32, intensity); + } + + if (object.get("type")) |@"type"| { + if (std.meta.stringToEnum(LightType, @"type".string)) |light_type| { + light.type = light_type; + } else panic("Light's type invalid", .{}); + } + + if (object.get("range")) |range| { + light.range = parseFloat(f32, range); + } + + if (object.get("spot")) |spot| { + light.spot = .{}; + + if (spot.object.get("innerConeAngle")) |inner_cone_angle| { + light.spot.?.inner_cone_angle = parseFloat(f32, inner_cone_angle); + } + + if (spot.object.get("outerConeAngle")) |outer_cone_angle| { + light.spot.?.outer_cone_angle = parseFloat(f32, outer_cone_angle); + } + } + + try self.data.lights.append(light); + } + } + } + } + + // For each node, fill parent indexes. + for (self.data.scenes.items) |scene| { + if (scene.nodes) |nodes| { + for (nodes.items) |node_index| { + const node = &self.data.nodes.items[node_index]; + fillParents(&self.data, node, node_index); + } + } + } +} + +// In 'gltf' files, often values are array indexes; +// this function casts Integer to 'usize'. +fn parseIndex(component: json.Value) usize { + return switch (component) { + .integer => |val| @as(usize, @intCast(val)), + else => panic( + "The json component '{any}' is not valid number.", + .{component}, + ), + }; +} + +// Exact values could be interpreted as Integer, often we want only +// floating numbers. +fn parseFloat(comptime T: type, component: json.Value) T { + const type_info = @typeInfo(T); + if (type_info != .Float) { + panic( + "Given type '{any}' is not a floating number.", + .{type_info}, + ); + } + + return switch (component) { + .float => |val| @as(T, @floatCast(val)), + .integer => |val| @as(T, @floatFromInt(val)), + else => panic( + "The json component '{any}' is not a number.", + .{component}, + ), + }; +} + +fn fillParents(data: *Data, node: *Node, parent_index: Index) void { + for (node.children.items) |child_index| { + var child_node = &data.nodes.items[child_index]; + child_node.parent = parent_index; + fillParents(data, child_node, child_index); + } +} + +test "gltf.parseGlb" { + const allocator = std.testing.allocator; + const expectEqualSlices = std.testing.expectEqualSlices; + + // This is the '.glb' file. + const glb_buf = try std.fs.cwd().readFileAllocOptions(allocator, "test-samples/box_binary/Box.glb", 512_000, null, 4, null); + defer allocator.free(glb_buf); + + var gltf = Self.init(allocator); + defer gltf.deinit(); + + try expectEqualSlices(u8, gltf.data.asset.version, "Undefined"); + + try gltf.parseGlb(glb_buf); + + const mesh = gltf.data.meshes.items[0]; + for (mesh.primitives.items) |primitive| { + for (primitive.attributes.items) |attribute| { + switch (attribute) { + .position => |accessor_index| { + var tmp = ArrayList(f32).init(allocator); + defer tmp.deinit(); + + const accessor = gltf.data.accessors.items[accessor_index]; + gltf.getDataFromBufferView(f32, &tmp, accessor, gltf.glb_binary.?); + + try expectEqualSlices(f32, tmp.items, &[72]f32{ + // zig fmt: off + -0.50, -0.50, 0.50, 0.50, -0.50, 0.50, -0.50, 0.50, 0.50, + 0.50, 0.50, 0.50, 0.50, -0.50, 0.50, -0.50, -0.50, 0.50, + 0.50, -0.50, -0.50, -0.50, -0.50, -0.50, 0.50, 0.50, 0.50, + 0.50, -0.50, 0.50, 0.50, 0.50, -0.50, 0.50, -0.50, -0.50, + -0.50, 0.50, 0.50, 0.50, 0.50, 0.50, -0.50, 0.50, -0.50, + 0.50, 0.50, -0.50, -0.50, -0.50, 0.50, -0.50, 0.50, 0.50, + -0.50, -0.50, -0.50, -0.50, 0.50, -0.50, -0.50, -0.50, -0.50, + -0.50, 0.50, -0.50, 0.50, -0.50, -0.50, 0.50, 0.50, -0.50, + }); + }, + else => {}, + } + } + } +} + +test "gltf.parseGlbTextured" { + const allocator = std.testing.allocator; + const expectEqualSlices = std.testing.expectEqualSlices; + + // This is the '.glb' file. + const glb_buf = try std.fs.cwd().readFileAllocOptions( + allocator, + "test-samples/box_binary_textured/BoxTextured.glb", + 512_000, + null, + 4, + null + ); + defer allocator.free(glb_buf); + + var gltf = Self.init(allocator); + defer gltf.deinit(); + + try gltf.parseGlb(glb_buf); + + const test_to_check = try std.fs.cwd().readFileAlloc( + allocator, + "test-samples/box_binary_textured/test.png", + 512_000 + ); + defer allocator.free(test_to_check); + + const data = gltf.data.images.items[0].data.?; + try expectEqualSlices(u8, test_to_check, data); +} + +test "gltf.parse" { + const allocator = std.testing.allocator; + const expectEqualSlices = std.testing.expectEqualSlices; + const expectEqual = std.testing.expectEqual; + + // This is the '.gltf' file, a json specifying what information is in the + // model and how to retrieve it inside binary file(s). + const buf = try std.fs.cwd().readFileAllocOptions( + allocator, + "test-samples/rigged_simple/RiggedSimple.gltf", + 512_000, + null, + 4, + null + ); + defer allocator.free(buf); + + var gltf = Self.init(allocator); + defer gltf.deinit(); + + try expectEqualSlices(u8, gltf.data.asset.version, "Undefined"); + + try gltf.parse(buf); + + try expectEqualSlices(u8, gltf.data.asset.version, "2.0"); + try expectEqualSlices(u8, gltf.data.asset.generator.?, "COLLADA2GLTF"); + + try expectEqual(gltf.data.scene, 0); + + // Nodes. + const nodes = gltf.data.nodes.items; + try expectEqualSlices(u8, nodes[0].name, "Z_UP"); + try expectEqualSlices(usize, nodes[0].children.items, &[_]usize{1}); + try expectEqualSlices(u8, nodes[2].name, "Cylinder"); + try expectEqual(nodes[2].skin, 0); + + try expectEqual(gltf.data.buffers.items.len > 0, true); + + // Skin + const skin = gltf.data.skins.items[0]; + try expectEqualSlices(u8, skin.name, "Armature"); +} + +test "gltf.parse (cameras)" { + const allocator = std.testing.allocator; + const expectEqual = std.testing.expectEqual; + + const buf = try std.fs.cwd().readFileAllocOptions( + allocator, + "test-samples/cameras/Cameras.gltf", + 512_000, + null, + 4, + null + ); + defer allocator.free(buf); + + var gltf = Self.init(allocator); + defer gltf.deinit(); + + try gltf.parse(buf); + + try expectEqual(gltf.data.nodes.items[1].camera, 0); + try expectEqual(gltf.data.nodes.items[2].camera, 1); + + const camera_0 = gltf.data.cameras.items[0]; + try expectEqual(camera_0.type.perspective, Camera.Perspective{ + .aspect_ratio = 1.0, + .yfov = 0.7, + .zfar = 100, + .znear = 0.01, + }); + + const camera_1 = gltf.data.cameras.items[1]; + try expectEqual(camera_1.type.orthographic, Camera.Orthographic{ + .xmag = 1.0, + .ymag = 1.0, + .zfar = 100, + .znear = 0.01, + }); +} + +test "gltf.getDataFromBufferView" { + const allocator = std.testing.allocator; + const expectEqualSlices = std.testing.expectEqualSlices; + + const buf = try std.fs.cwd().readFileAllocOptions( + allocator, + "test-samples/box/Box.gltf", + 512_000, + null, + 4, + null + ); + defer allocator.free(buf); + + // This is the '.bin' file containing all the gltf underneath data. + const binary = try std.fs.cwd().readFileAllocOptions( + allocator, + "test-samples/box/Box0.bin", + 5_000_000, + null, + // From gltf spec, data from BufferView should be 4 bytes aligned. + 4, + null, + ); + defer allocator.free(binary); + + var gltf = Self.init(allocator); + defer gltf.deinit(); + + try gltf.parse(buf); + + const mesh = gltf.data.meshes.items[0]; + for (mesh.primitives.items) |primitive| { + for (primitive.attributes.items) |attribute| { + switch (attribute) { + .position => |accessor_index| { + var tmp = ArrayList(f32).init(allocator); + defer tmp.deinit(); + + const accessor = gltf.data.accessors.items[accessor_index]; + gltf.getDataFromBufferView(f32, &tmp, accessor, binary); + + try expectEqualSlices(f32, tmp.items, &[72]f32{ + // zig fmt: off + -0.50, -0.50, 0.50, 0.50, -0.50, 0.50, -0.50, 0.50, 0.50, + 0.50, 0.50, 0.50, 0.50, -0.50, 0.50, -0.50, -0.50, 0.50, + 0.50, -0.50, -0.50, -0.50, -0.50, -0.50, 0.50, 0.50, 0.50, + 0.50, -0.50, 0.50, 0.50, 0.50, -0.50, 0.50, -0.50, -0.50, + -0.50, 0.50, 0.50, 0.50, 0.50, 0.50, -0.50, 0.50, -0.50, + 0.50, 0.50, -0.50, -0.50, -0.50, 0.50, -0.50, 0.50, 0.50, + -0.50, -0.50, -0.50, -0.50, 0.50, -0.50, -0.50, -0.50, -0.50, + -0.50, 0.50, -0.50, 0.50, -0.50, -0.50, 0.50, 0.50, -0.50, + }); + }, + else => {}, + } + } + } +} + +test "gltf.parse (lights)" { + const allocator = std.testing.allocator; + const expect = std.testing.expect; + const expectEqual = std.testing.expectEqual; + + const buf = try std.fs.cwd().readFileAllocOptions( + allocator, + "test-samples/khr_lights_punctual/Lights.gltf", + 512_000, + null, + 4, + null + ); + defer allocator.free(buf); + + var gltf = Self.init(allocator); + defer gltf.deinit(); + + try gltf.parse(buf); + + try expectEqual(@as(usize, 3), gltf.data.lights.items.len); + + try expect(gltf.data.lights.items[0].name != null); + try expect(std.mem.eql(u8, "Light", gltf.data.lights.items[0].name.?)); + try expectEqual([3]f32 { 1, 1, 1 }, gltf.data.lights.items[0].color); + try expectEqual(@as(f32, 1000), gltf.data.lights.items[0].intensity); + try expectEqual(LightType.point, gltf.data.lights.items[0].type); + + try expect(gltf.data.lights.items[1].name != null); + try expect(std.mem.eql(u8, "Light.001", gltf.data.lights.items[1].name.?)); + try expectEqual([3]f32 { 1, 1, 1 }, gltf.data.lights.items[1].color); + try expectEqual(@as(f32, 1000), gltf.data.lights.items[1].intensity); + try expectEqual(LightType.spot, gltf.data.lights.items[1].type); + + try expect(gltf.data.lights.items[1].spot != null); + try expectEqual(@as(f32, 0), gltf.data.lights.items[1].spot.?.inner_cone_angle); + try expectEqual(@as(f32, 1), gltf.data.lights.items[1].spot.?.outer_cone_angle); + + try expect(gltf.data.lights.items[2].name != null); + try expect(std.mem.eql(u8, "Light.002", gltf.data.lights.items[2].name.?)); + try expectEqual([3]f32 { 1, 1, 1 }, gltf.data.lights.items[2].color); + try expectEqual(@as(f32, 1000), gltf.data.lights.items[2].intensity); + try expectEqual(LightType.directional, gltf.data.lights.items[2].type); + + try expect(gltf.data.nodes.items[0].light != null); + try expectEqual(@as(?Index, 0), gltf.data.nodes.items[0].light); +} diff --git a/lib/zgltf/src/types.zig b/lib/zgltf/src/types.zig new file mode 100644 index 0000000..6a2351d --- /dev/null +++ b/lib/zgltf/src/types.zig @@ -0,0 +1,636 @@ +const std = @import("std"); +const Gltf = @import("main.zig"); +const pi = std.math.pi; +const ArrayList = std.ArrayList; +const panic = std.debug.panic; + +/// Index of element in data arrays. +pub const Index = usize; + +/// A node in the node hierarchy. +/// +/// When the node contains skin, all mesh.primitives must contain +/// JOINTS_0 and WEIGHTS_0 attributes. A node may have either a matrix +/// or any combination of translation/rotation/scale (TRS) properties. +/// TRS properties are converted to matrices and postmultiplied in +/// the T * R * S order to compose the transformation matrix. +/// If none are provided, the transform is the identity. +/// +/// When a node is targeted for animation (referenced by +/// an animation.channel.target), matrix must not be present. +pub const Node = struct { + /// The user-defined name of this object. + /// Default to `Node_{index}`. + name: []const u8, + /// The index of the node's parent. + /// A node is called a root node when it doesn’t have a parent. + parent: ?Index = null, + /// The index of the mesh in this node. + mesh: ?Index = null, + /// The index of the camera referenced by this node. + camera: ?Index = null, + /// The index of the skin referenced by this node. + skin: ?Index = null, + /// The indices of this node’s children. + children: ArrayList(Index), + /// A floating-point 4x4 transformation matrix stored in column-major order. + matrix: ?[16]f32 = null, + /// The node’s unit quaternion rotation in the order (x, y, z, w), + /// where w is the scalar. + rotation: [4]f32 = [_]f32{ 0, 0, 0, 1 }, + /// The node’s non-uniform scale, given as the scaling factors + /// along the x, y, and z axes. + scale: [3]f32 = [_]f32{ 1, 1, 1 }, + /// The node’s translation along the x, y, and z axes. + translation: [3]f32 = [_]f32{ 0, 0, 0 }, + /// The weights of the instantiated morph target. + /// The number of array elements must match the number of morph targets + /// of the referenced mesh. When defined, mesh mush also be defined. + weights: ?[]usize = null, + ///The index of the light referenced by this node. + light: ?Index = null, +}; + +/// A buffer points to binary geometry, animation, or skins. +pub const Buffer = struct { + /// Relative paths are relative to the current glTF asset. + /// It could contains a data:-URI instead of a path. + /// Note: data-uri isn't implemented in this library. + uri: ?[]const u8 = null, + /// The length of the buffer in bytes. + byte_length: usize, +}; + +/// A view into a buffer generally representing a subset of the buffer. +pub const BufferView = struct { + /// The index of the buffer. + buffer: Index, + /// The length of the bufferView in bytes. + byte_length: usize, + /// The offset into the buffer in bytes. + byte_offset: usize = 0, + /// The stride, in bytes. + byte_stride: ?usize = null, + /// The hint representing the intended GPU buffer type + /// to use with this buffer view. + target: ?Target = null, +}; + +/// A typed view into a buffer view that contains raw binary data. +pub const Accessor = struct { + /// The index of the bufferView. + buffer_view: ?Index = null, + /// The offset relative to the start of the buffer view in bytes. + byte_offset: usize = 0, + /// The datatype of the accessor’s components. + component_type: ComponentType, + /// Specifies if the accessor’s elements are scalars, vectors, or matrices. + type: AccessorType, + /// Computed stride: @sizeOf(component_type) * type. + stride: usize, + /// The number of elements referenced by this accessor. + count: i32, + /// Specifies whether integer data values are normalized before usage. + normalized: bool = false, + + pub fn iterator( + accessor: Accessor, + comptime T: type, + gltf: *const Gltf, + binary: []align(4) const u8, + ) AccessorIterator(T) { + if (switch (accessor.component_type) { + .byte => T != i8, + .unsigned_byte => T != u8, + .short => T != i16, + .unsigned_short => T != u16, + .unsigned_integer => T != u32, + .float => T != f32, + }) { + panic( + "Mismatch between gltf component '{}' and given type '{}'.", + .{ accessor.component_type, T }, + ); + } + + if (accessor.buffer_view == null) { + panic("Accessors without buffer_view are not supported yet.", .{}); + } + + const buffer_view = gltf.data.buffer_views.items[accessor.buffer_view.?]; + + const comp_size = @sizeOf(T); + const offset = (accessor.byte_offset + buffer_view.byte_offset) / comp_size; + + const stride = blk: { + if (buffer_view.byte_stride) |byte_stride| { + break :blk byte_stride / comp_size; + } else { + break :blk accessor.stride / comp_size; + } + }; + + const total_count: usize = @intCast(accessor.count); + const datum_count: usize = switch (accessor.type) { + .scalar => 1, + .vec2 => 2, + .vec3 => 3, + .vec4 => 4, + .mat4x4 => 16, + else => { + panic("Accessor type '{}' not implemented.", .{accessor.type}); + }, + }; + + const data: [*]const T = @ptrCast(@alignCast(binary.ptr)); + + return .{ + .offset = offset, + .stride = stride, + .total_count = total_count, + .datum_count = datum_count, + .data = data, + .current = 0, + }; + } +}; + +/// Iterator over accessor elements +pub fn AccessorIterator(comptime T: type) type { + return struct { + offset: usize, + stride: usize, + total_count: usize, + datum_count: usize, + data: [*]const T, + + current: usize, + + /// Returns the next element of the accessor, or null if iteration is done. + pub fn next(self: *@This()) ?[]const T { + if (self.current >= self.total_count) return null; + + const slice = (self.data + self.offset + self.current * self.stride)[0..self.datum_count]; + self.current += 1; + return slice; + } + + /// Returns the next element of the accessor, or null if iteration is done. Does not change self.current. + pub fn peek(self: *const @This()) ?[]const T { + var copy = self.*; + return copy.next(); + } + + /// Resets the iterator to the first element + pub fn reset(self: *@This()) void { + self.current = 0; + } + }; +} + +/// The root nodes of a scene. +pub const Scene = struct { + /// The user-defined name of this object. + name: []const u8, + /// The indices of each root node. + nodes: ?ArrayList(Index) = null, +}; + +/// Joints and matrices defining a skin. +pub const Skin = struct { + /// The user-defined name of this object. + name: []const u8, + /// The index of the accessor containing the floating-point + /// 4x4 inverse-bind matrices. + inverse_bind_matrices: ?Index = null, + /// The index of the node used as a skeleton root. + skeleton: ?Index = null, + /// Indices of skeleton nodes, used as joints in this skin. + joints: ArrayList(Index), +}; + +/// Reference to a texture. +const TextureInfo = struct { + /// The index of the texture. + index: Index, + /// The set index of texture’s TEXCOORD attribute + /// used for texture coordinate mapping. + texcoord: i32 = 0, +}; + +/// Reference to a normal texture. +const NormalTextureInfo = struct { + /// The index of the texture. + index: Index, + /// The set index of texture’s TEXCOORD attribute + /// used for texture coordinate mapping. + texcoord: i32 = 0, + /// The scalar parameter applied to each normal + /// vector of the normal texture. + scale: f32 = 1, +}; + +/// Reference to an occlusion texture. +const OcclusionTextureInfo = struct { + /// The index of the texture. + index: Index, + /// The set index of texture’s TEXCOORD attribute + /// used for texture coordinate mapping. + texcoord: i32 = 0, + /// A scalar multiplier controlling the amount of occlusion applied. + strength: f32 = 1, +}; + +/// A set of parameter values that are used to define +/// the metallic-roughness material model +/// from Physically-Based Rendering methodology. +pub const MetallicRoughness = struct { + /// The factors for the base color of the material. + base_color_factor: [4]f32 = [_]f32{ 1, 1, 1, 1 }, + /// The base color texture. + base_color_texture: ?TextureInfo = null, + /// The factor for the metalness of the material. + metallic_factor: f32 = 1, + /// The factor for the roughness of the material. + roughness_factor: f32 = 1, + /// The metallic-roughness texture. + metallic_roughness_texture: ?TextureInfo = null, +}; + +/// The material appearance of a primitive. +pub const Material = struct { + /// The user-defined name of this object. + name: []const u8, + /// A set of parameter values that are used to define + /// the metallic-roughness material model + /// from Physically Based Rendering methodology. + metallic_roughness: MetallicRoughness = .{}, + /// The tangent space normal texture. + normal_texture: ?NormalTextureInfo = null, + /// The occlusion texture. + occlusion_texture: ?OcclusionTextureInfo = null, + /// The emissive texture. + emissive_texture: ?TextureInfo = null, + /// The factors for the emissive color of the material. + emissive_factor: [3]f32 = [_]f32{ 0, 0, 0 }, + /// The alpha rendering mode of the material. + alpha_mode: AlphaMode = .@"opaque", + /// The alpha cutoff value of the material. + alpha_cutoff: f32 = 0.5, + /// Specifies whether the material is double sided. + /// If it's false, back-face culling is enabled. + /// If it's true, back-face culling is disabled and + /// double sided lighting is enabled. + is_double_sided: bool = false, + /// Emissive strength multiplier for the emissive factor/texture. + /// Note: from khr_materials_emissive_strength extension. + emissive_strength: f32 = 1.0, + /// Index of refraction of material. + /// Note: from khr_materials_ior extension. + ior: f32 = 1.5, + /// The factor for the transmission of the material. + /// Note: from khr_materials_transmission extension. + transmission_factor: f32 = 0.0, + /// The transmission texture. + /// Note: from khr_materials_transmission extension. + transmission_texture: ?TextureInfo = null, + /// The thickness of the volume beneath the surface. + /// Note: from khr_materials_volume extension. + thickness_factor: f32 = 0.0, + /// A texture that defines the thickness, stored in the G channel. + /// Note: from khr_materials_volume extension. + thickness_texture: ?TextureInfo = null, + /// Density of the medium. + /// Note: from khr_materials_volume extension. + attenuation_distance: f32 = std.math.inf(f32), + /// The color that white light turns into due to absorption. + /// Note: from khr_materials_volume extension. + attenuation_color: [3]f32 = [_]f32{ 1, 1, 1 }, + /// The strength of the dispersion effect. + /// Note: from khr_materials_dispersion extension. + dispersion: f32 = 0.0, +}; + +/// The material’s alpha rendering mode enumeration specifying +/// the interpretation of the alpha value of the base color. +const AlphaMode = enum { + /// The alpha value is ignored, and the rendered output is fully opaque. + @"opaque", + /// The rendered output is either fully opaque or fully transparent + /// depending on the alpha value and the specified alpha_cutoff value. + /// Note: The exact appearance of the edges may be subject to + /// implementation-specific techniques such as “Alpha-to-Coverage”. + mask, + /// The alpha value is used to composite the source and destination areas. + /// The rendered output is combined with the background using + /// the normal painting operation (i.e. the Porter and Duff over operator). + blend, +}; + +/// A texture and its sampler. +pub const Texture = struct { + /// The index of the sampler used by this texture. + /// When undefined, a sampler with repeat wrapping and + /// auto filtering should be used. + sampler: ?Index = null, + /// The index of the image used by this texture. + /// When undefined, an extension or other mechanism should supply + /// an alternate texture source, otherwise behavior is undefined. + source: ?Index = null, +}; + +/// Image data used to create a texture. +/// Image may be referenced by an uri or a buffer view index. +pub const Image = struct { + /// The URI (or IRI) of the image. + uri: ?[]const u8 = null, + /// The image’s media type. + /// This field must be defined when bufferView is defined. + mime_type: ?[]const u8 = null, + /// The index of the bufferView that contains the image. + /// Note: This field must not be defined when uri is defined. + buffer_view: ?Index = null, + /// The image's data calculated from the buffer/buffer_view. + /// Only there if glb file is loaded. + data: ?[]const u8 = null, +}; + +pub const WrapMode = enum(u32) { + clamp_to_edge = 33071, + mirrored_repeat = 33648, + repeat = 10497, +}; + +pub const MinFilter = enum(u32) { + nearest = 9728, + linear = 9729, + nearest_mipmap_nearest = 9984, + linear_mipmap_nearest = 9985, + nearest_mipmap_linear = 9986, + linear_mipmap_linear = 9987, +}; + +pub const MagFilter = enum(u32) { + nearest = 9728, + linear = 9729, +}; + +/// Texture sampler properties for filtering and wrapping modes. +pub const TextureSampler = struct { + /// Magnification filter. + mag_filter: ?MagFilter = null, + /// Minification filter. + min_filter: ?MinFilter = null, + /// S (U) wrapping mode. + wrap_s: WrapMode = .repeat, + /// T (U) wrapping mode. + wrap_t: WrapMode = .repeat, +}; + +/// Values are Accessor's index. +pub const Attribute = union(enum) { + position: Index, + normal: Index, + tangent: Index, + texcoord: Index, + color: Index, + joints: Index, + weights: Index, +}; + +pub const AccessorType = enum { + scalar, + vec2, + vec3, + vec4, + mat2x2, + mat3x3, + mat4x4, +}; + +/// Enum values from GLTF 2.0 spec. +pub const Target = enum(u32) { + array_buffer = 34962, + element_array_buffer = 34963, +}; + +/// Enum values from GLTF 2.0 spec. +pub const ComponentType = enum(u32) { + /// i8. + byte = 5120, + /// u8. + unsigned_byte = 5121, + /// i16. + short = 5122, + /// u16. + unsigned_short = 5123, + /// u32. + unsigned_integer = 5125, + /// f32. + float = 5126, +}; + +/// The topology type of primitives to render. +pub const Mode = enum(u32) { + points = 0, + lines = 1, + line_loop = 2, + line_strip = 3, + triangles = 4, + triangle_strip = 5, + triangle_fan = 6, +}; + +/// The name of the node’s TRS property to animate. +pub const TargetProperty = enum { + /// For the "translation" property, the values that are provided by the + /// sampler are the translation along the X, Y, and Z axes. + translation, + /// For the "rotation" property, the values are a quaternion + /// in the order (x, y, z, w), where w is the scalar. + rotation, + /// For the "scale" property, the values are the scaling + /// factors along the X, Y, and Z axes. + scale, + /// The "weights" of the Morph Targets it instantiates. + weights, +}; + +/// An animation channel combines an animation sampler +/// with a target property being animated. +pub const Channel = struct { + /// The index of a sampler in this animation used to + /// compute the value for the target. + sampler: Index, + /// The descriptor of the animated property. + target: struct { + /// The index of the node to animate. + /// When undefined, the animated object may be defined by an extension. + node: Index, + /// The name of the node’s TRS property to animate, or the "weights" + /// of the Morph Targets it instantiates. + property: TargetProperty, + }, +}; + +/// Interpolation algorithm. +pub const Interpolation = enum { + /// The animated values are linearly interpolated between keyframes. + /// When targeting a rotation, spherical linear interpolation (slerp) + /// should be used to interpolate quaternions. + linear, + /// The animated values remain constant to the output of the first + /// keyframe, until the next keyframe. + step, + /// The animation’s interpolation is computed using a cubic + /// spline with specified tangents. + cubicspline, +}; + +/// An animation sampler combines timestamps +/// with a sequence of output values and defines an interpolation algorithm. +pub const AnimationSampler = struct { + /// The index of an accessor containing keyframe timestamps. + input: Index, + /// The index of an accessor, containing keyframe output values. + output: Index, + /// Interpolation algorithm. + interpolation: Interpolation = .linear, +}; + +/// A keyframe animation. +pub const Animation = struct { + /// The user-defined name of this object. + name: []const u8, + /// An array of animation channels. + /// An animation channel combines an animation sampler with a target + /// property being animated. + /// Different channels of the same animation must not have the same targets. + channels: ArrayList(Channel), + /// An array of animation samplers. + /// An animation sampler combines timestamps with a sequence of output + /// values and defines an interpolation algorithm. + samplers: ArrayList(AnimationSampler), +}; + +/// Geometry to be rendered with the given material. +pub const Primitive = struct { + attributes: ArrayList(Attribute), + /// The topology type of primitives to render. + mode: Mode = .triangles, + /// The index of the accessor that contains the vertex indices. + indices: ?Index = null, + /// The index of the material to apply to this primitive when rendering. + material: ?Index = null, +}; + +/// A set of primitives to be rendered. +/// Its global transform is defined by a node that references it. +pub const Mesh = struct { + /// The user-defined name of this object. + name: []const u8, + /// An array of primitives, each defining geometry to be rendered. + primitives: ArrayList(Primitive), +}; + +/// Metadata about the glTF asset. +pub const Asset = struct { + /// The glTF version that this asset targets. + version: []const u8, + /// Tool that generated this glTF model. Useful for debugging. + generator: ?[]const u8 = null, + /// A copyright message suitable for display to credit the content creator. + copyright: ?[]const u8 = null, +}; + +/// A camera’s projection. +/// A node may reference a camera to apply a transform to place the camera +/// in the scene. +pub const Camera = struct { + /// A perspective camera containing properties to create a + /// perspective projection matrix. + pub const Perspective = struct { + /// The aspect ratio of the field of view. + aspect_ratio: ?f32, + /// The vertical field of view in radians. + /// This value should be less than π. + yfov: f32, + /// The distance to the far clipping plane. + zfar: ?f32, + /// The distance to the near clipping plane. + znear: f32, + }; + + /// An orthographic camera containing properties to create an + /// orthographic projection matrix. + pub const Orthographic = struct { + /// The horizontal magnification of the view. + /// This value must not be equal to zero. + /// This value should not be negative. + xmag: f32, + /// The vertical magnification of the view. + /// This value must not be equal to zero. + /// This value should not be negative. + ymag: f32, + /// The distance to the far clipping plane. + /// This value must not be equal to zero. + /// This value must be greater than znear. + zfar: f32, + /// The distance to the near clipping plane. + znear: f32, + }; + + name: []const u8, + type: union(enum) { + perspective: Perspective, + orthographic: Orthographic, + }, +}; + +/// Specifies the light type. +pub const LightType = enum { + /// Directional lights act as though they are infinitely far away and emit light in the direction of the local -z axis. + /// This light type inherits the orientation of the node that it belongs to; position and scale are ignored + /// except for their effect on the inherited node orientation. Because it is at an infinite distance, + /// the light is not attenuated. Its intensity is defined in lumens per metre squared, or lux (lm/m^2). + directional, + /// Point lights emit light in all directions from their position in space; rotation and scale are ignored except + /// for their effect on the inherited node position. + /// The brightness of the light attenuates in a physically correct manner as distance increases from + /// the light's position (i.e. brightness goes like the inverse square of the distance). + /// Point light intensity is defined in candela, which is lumens per square radian (lm/sr). + point, + /// Spot lights emit light in a cone in the direction of the local -z axis. + /// The angle and falloff of the cone is defined using two numbers, the innerConeAngle and outerConeAngle. + /// As with point lights, the brightness also attenuates in a physically correct manner as distance + /// increases from the light's position (i.e. brightness goes like the inverse square of the distance). + /// Spot light intensity refers to the brightness inside the innerConeAngle (and at the location of the light) and + /// is defined in candela, which is lumens per square radian (lm/sr). + /// + /// Engines that don't support two angles for spotlights should use outerConeAngle as the spotlight angle, + /// leaving innerConeAngle to implicitly be 0. + spot, +}; + +/// A directional, point or spot light. +pub const Light = struct { + name: ?[]const u8, + /// Color of the light source. + color: [3]f32 = .{ 1, 1, 1 }, + /// Intensity of the light source. `point` and `spot` lights use luminous intensity in candela (lm/sr) + /// while `directional` lights use illuminance in lux (lm/m^2). + intensity: f32 = 1, + /// Specifies the light type. + type: LightType, + /// When a light's type is spot, the spot property on the light is required. + spot: ?LightSpot, + /// A distance cutoff at which the light's intensity may be considered to have reached zero. + range: f32, +}; + +pub const LightSpot = struct { + /// Angle in radians from centre of spotlight where falloff begins. + inner_cone_angle: f32 = 0, + /// Angle in radians from centre of spotlight where falloff ends. + outer_cone_angle: f32 = pi / @as(f32, 4), +}; diff --git a/lib/zgltf/test-samples/box/Box.gltf b/lib/zgltf/test-samples/box/Box.gltf new file mode 100644 index 0000000..7f603f0 --- /dev/null +++ b/lib/zgltf/test-samples/box/Box.gltf @@ -0,0 +1,142 @@ +{ + "asset": { + "generator": "COLLADA2GLTF", + "version": "2.0" + }, + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "nodes": [ + { + "children": [ + 1 + ], + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 2 + }, + "indices": 0, + "mode": 4, + "material": 0 + } + ], + "name": "Mesh" + } + ], + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5123, + "count": 36, + "max": [ + 23 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "max": [ + 1.0, + 1.0, + 1.0 + ], + "min": [ + -1.0, + -1.0, + -1.0 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 288, + "componentType": 5126, + "count": 24, + "max": [ + 0.5, + 0.5, + 0.5 + ], + "min": [ + -0.5, + -0.5, + -0.5 + ], + "type": "VEC3" + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.800000011920929, + 0.0, + 0.0, + 1.0 + ], + "metallicFactor": 0.0 + }, + "name": "Red" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 576, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 576, + "byteStride": 12, + "target": 34962 + } + ], + "buffers": [ + { + "byteLength": 648, + "uri": "Box0.bin" + } + ] +} diff --git a/lib/zgltf/test-samples/box/Box0.bin b/lib/zgltf/test-samples/box/Box0.bin new file mode 100644 index 0000000000000000000000000000000000000000..d7798abb5161eca17ac8a6883a1f53c6786b484d GIT binary patch literal 648 zcmb7;3kt$O5JO+ps`X#BdYoRZH}h&v#Ne^JfXY|f(@CPIkBiUbn22;I3GyAPRY#|SxE#v~@J z-RZV!7Blr6`_bt&`^`?9nFdzn`eWB2AFOMR#W1rd(&eFRdl`st&r#1>1WTZ{gEyie zT(@AfoJ@Fl@A97_$mlWVoykNr7-KrYd=dEEkNb}c3{ujK0x6e1_PSp7z%Cj)?0>TUa1Jlw h71BAph6{KDmq-`z7OvnOyhpl%4{!}1;SXa6dqf!cKL5eAb zN>LIO8GF*P6d}ue&&c%p&ij7nyua_6xt?pgul>5`kMRv~v_Js>Qda?>hXuge!Ok|C z=ouPH2_qUr6MZQ(N{DAzPzce0XlA!7 zC$n6n?jEZYO$?-j`XPbX9@*q#fM_B;gc?WHuyVArvn3j6C=neuJDb_r*_(T)5e+nvBB(SUsyEvNDI*ZcooJx7m>ngA>KU*U zlja$S$cZ-WEZ8}CdLtI0K_NdY_6py-mlCpzN{L)_;`EXtkn(ePh>M3*=``QPB9?spbc~o=Py>*vhz9z) z8v2XN^h=JoUf$()b%+zl=GFsOvAS z3&$Ih=<>w=&Y7OpUsBW3)YSY}SC%@mtZHp_U7c9>SiKbhm|EE)zowiAHs(+O{2l#w zI*y0sXV|jk{C`E)a6BQl|3Lqfelh+s9~{sBwH@1*4aQ5rp*ff%|G)5G&vAG*=78}s z{~VgFW78bGoaV^bm_u{)9NAwnN6*F_dik0EBxCD2at`LmmUspUIfFcTq(uZ6qy<(y zqRU&t@RFRPXY=r)erYe3^laJUgYayc10;lJKO8@7eo?oCIeLKb?1!Ug^S^v9YJTR$ zk-^44c?h5Y8UYAk02bf?7Xnzo4e)>m@FIW*e1IPa06_%!0RadBVIYEl5Lf|3!Ac;8 zfG9Y+-PV$yXEhHJh~H|9xjjJqv4I~ zFJnX9BQpcTOlDVi*T>F|j*j+@mN)M|KL0RO-TJBVP3?`wfd|h@%4#p3e$=0QulMr3 zh)E{zSi(M7%9MZVgVa%ErK1-H=CR^Dl9Zprd4hh=soVqbV2lLUZZXk~8V z81??ooS{=66bOgX#FDOh+|x62gvqNFE3lu>CrTAN zjMYwxk(R&iKw!?|;pmTzz~RV6M~S-N#`xcPN*|By2bc3&ZYvxsyDqN6^so3_rj$ao z={@?$#YG4RJj&+Uqx>sH$7Xl+0A|2|2-LqgO@wc~x;{jc%i7gQN)HWnlL1}& zOL3tIrpsU}DKGqZ^{Heth}lx@r=>WW$;@4xA4zHN!ap*7Z{sv6NVHJNSX@5ldf|?AGtxD z=MMDkbJo|nWa?Z2ff9eqeJ+dS@L1*}AZ5Q@@m`YKPkDn`JgBGF7;nBLR*V5$!JW4a z?H%f&J%V6hsB6T!SttF{_*3PDy;3b6JcZJw!oX;rT4jirb><>DKYF=`wHDS8Z~_F~ z*;EgLU(YBGKKS5WSMIeI5WfMeVeRRvApm(Yo_V52moUafxLlRPVp%CAcVVTsMM@co zkygivz+q0UCA&z0*7+w6u||ETyrszwD=rw-u@=^<8KaHV;;hIfVElNG7^a5;C51;9 zOh5Uqw}$`(o`0vW_rAXZNUThL#R3f`qvfzjPv#lb0UV^}|9o06}Ke1J8HeHF-eF6%$`VhD?W$cp5 z+jR5i?UE$tfGTX_-Rz#z_fDQNOS~H+NN(CBy}tJ}Cg6nq1g_K-m-ON;p7i|F#LgWy z$_{z>E(#-V3MM%e0NCDNAOEDNODSh}p8Q0Euc?D5?v_}IB+`lXKyUO_{}UlJkPw>% z&8DkX-8EG)L}|jE5?E8$I+h3Y`RyuD@C_tITfswLhx^xtkKenpHH9t!&X0UjBETzO zVQuc2W3k3|4?{J`V*BzWv@eCPw(N1vyS!rKCW1D>&XH}}V{`a?`q$BoT{ zW%gzNjxC8C|3H6PUJhbP9zUGWn_$V=7F2ZJv>j-p&JEIE_DRAge{9OQ@_uv6bK9-6 zX{j}Xq8_VL1|wGy4xs#e#O)5{QQLMpSbW-NIdmDvjF;yrjJaU1u)gg)?zWYXeQM7& z6thMImv{Aifd9pbp(-3VQw6ZtIAns!L z1g0f4i?CNsQL6=tmi=KknD5z?ib4#H!%pwP`Oiic}2O!W=$0+&lr z9&LNQY5+4J`rA8WDa|t(d%+<*Tsd?kgr>JTE%wlx2aq!gE+eiJoQyJ%1VQz!bxqm= zN3%#nK}pSzpJ9g<9Mh6_gE6|>p((Ze8397|C6u1;Yj6q+$aJ?H{;+AovjyPL0h6Bb zqSj#$Wl*d|N(*a+J$RZm%A7MRagy+v#MgZl2V}RM8s!IRcGhSnp8PvRF?2u%6`g}# zt#i2g0dygwPC1XYLd}pi72>9uaBFiAqZ4E{SVn_2>E}|CH1|npwZi-6KF65%y_oei z0`O{ywzZdwaogWL$`U0t)rK{IK_2P-TbObHy$h;%^F|R0o0*Q_9szJ&1GB<{dH{Ws z7jpS@0N5#3nKpC ztb4uBd<2Ex#%V5@lk=clj%hYdy5cXLAAxgraRha7Br*nUmwv9C`7wF)nH8CWZdT}( zemBC|nfe0s)Lh*GJI0vmkX-8sNTbES7iaE*Dv~?!X=XlVHSVf2h&3;?iy( zc2=!mhfroK?6CP*+3^=v#{596z8^n$`KIZ5lx85{+XT$Ci#JK!3GcXHp5Y;v9~}gw z4fLG~0;HWIjTv3R^F;~oD~Xn^YVr%dcBLbSkKE`DVZQX*7V5(@z(eu|wR#&S^+2bj zjA8wY`YRdjw+yP>@=%>uCAKToVmT#R_15f_D zn}A1}fQ`Lh%LzN9^jO>Lv}34NgJNSi-I-M&an_*kkA-8Yhr_?j2P|QR2{6}-tpG#XP{E! z0_*uvik}>G?P!g!4zFE47bEv-G2DpwK@aGtE6ds#y$2QEo8XFqW79UeGBri3qhfvi z7qrCsPua|zTSLm$neg}mn#YdSb|O85Z1ZrLwTi^P+zjvBBCrLe9qDoLu43yqDN2MZ zc~I6tWyNWm7TemZ`wje62HsIz^RbL@i^O}|Z_ElOl@9iJb>K1whV%0;0Y^iLH}Cp; zorUIb4AC6IJ-7VlSQ(o+iRrIiP4zoP@_w@|kQC4bdy`!n!6x{_OnJsWzVzV@-!@sR z)Y7+m4Ow1qas&l^3R;Xa(Wq?I?PvPBPD+hFjRxf>PUiIf7$ik#JY_lhH{#<2N!7T6 zy+5ACIedQR{OA5eQO zo~PJ7hwp2s1G$ZHZ9*71puyi6OM(SUQ6$~!jU1{Ga=_7L z!WUAe+oUt2SAc=q3NjAXwEq+2*g-d1ZlArm(kG{u{#%u|D7eK>43SdeYiq-g9=*9B zv@oiH)&hL}W7;B;nXOvs?h&z7b31z*I!1gjw}9I#D<1dC&eSIDCl*F;G+!sh_tPFr zsIKkM0uvi#ObbLR+D?(+=C;adp7=18JKA7Ek@U5GtYb^QY?Q?ikOSr4 z&Sb7FxyLmX_r1e>u5$j5qOVDAO`l@cS?%hq`dFK?JGWy1#psJ0e0b~ChS_uly$843s?rA2Yg9yZwl3|07H4ySOHssCT|7Vj#4L48=(LQQ~o8?o-~3| z0_3cCK6v~>JDQtjRd3`YoVrTj3q;Q1mx4$u9_nrYuepp9fcToDy){O5S@m4oP_K9* zFa+l3=?xFVR9|y5Lx)Xvpzom>R*_>X2sz|P;5M`H{mI=8uox_URUijdCiM9%RNlwP zf#g1rUIJ9sM6x?CMZ5t`A_-Kje5P_l42C$Lse%QRqy}HSbk2;^tz_Qt+Fb+@)8X!AdNA0xd)?XPQW$yo4}Q4l|}<>2j_aKJ4a z_2mh7_S|p&n*n5X%~OoKZd|2^B?>XcpH>BjVgkMtVR|PadS~K`2bpPRr+KG5#9<{~ zsoKNb?b05sm7qdxR3uQoa_R#giL~WzwaAulZ53U|fSR1u$C3>4iUCWtkHWB9GuSjE+@yLLEHGU zt_{!1yQV|EtCPm<-j)Xe)8D?GQR90)*_50xSiL1yLIMTEWS(l!7<)90Wm0})A=wUa zXWbXKP#IWLl|8-tt46gq6jceyS)JmwTjABH5>|YnYE6bw#Zl$1aj-2tN*3?iyy-^* zp2u{wb3}Bu+4x@7kp-Q~g&h#Lr!6G^j@Pt{cb|_-aJl%81<{yOOpk|B5{w#QfrUrS zleO_bC|_&dOqDdpf2(SO#)M~_tWNds-??*Y_CYUe+_-=DDSC;Bi~3wEgo(0O^UOSp zsyEdbHWp&2b~}~o%8VBojM?EzrRK4OIr_3!6kq}Tp7)a@jk!$ZFAHF`+19++l$?l= F{{!Z{;e7xA literal 0 HcmV?d00001 diff --git a/lib/zgltf/test-samples/box_binary_textured/test.png b/lib/zgltf/test-samples/box_binary_textured/test.png new file mode 100644 index 0000000000000000000000000000000000000000..e7056c498ae61202fe777f6dffbc7b3338788271 GIT binary patch literal 4333 zcmYjVcRbbq|9!ph;u_brl8}osG82*cPD;p#MCQ%NNM=S^Z&E5Ve9*wdOC=znPv6DCrTL|0~F*m#&;=Fc=#O%>EViKv?NE1yg+3-f$KbAL@E7tvy0-VD#O^-tG# zidV3Wk+6Osaybh7+3~Ru07P|t-LqGMzyFsxpEfEmQE+n6 zay`JX=!D-XCIcHSVO2CVK?Xjg#gf9a7@C?PObK|>m=<#mq7O8>DM>9Sv9h}oWheoF zEST54#AmVQh-`7tT|$m?&<{nf_D3 zOWukWn+!uS*RDo>T;Cr+(u$1A--{xtbK&(-z#vxn`f;E-JyBg;G{L-*09ICFt+x*h zP~fuAL?)n~ePOU*qX*|0x8>Gog7RVMVKxU*;#EY2iP(PM=YSmpe;OHJ--n{H)d!z3 zMiXbL!hjIH<&B45GeJL?LHqT~=(o=eUNecb+W?|VM5WzcGHl)|@&mvbUwOU@F}ul1 zpNjw;K26%^^Z80JfXR3Kt-6_c2V~CyX6I>327RX!@>f5~9^4Qfa9}AUm2m;BefN5G zyqFaeY}MRj&p3uC5-b5xWjD@&=r*~GL+&+vqh-Gu01?vQD8ruCLyyud)hCmDm`N8XIv^x(yKPg|o(Kq^HhoSwz2^afhhn-I`livr{6JNs8zreM^o;JJ zNO}`A!cd6)IcoOE8&|Neg7aS*q6tlnR#*KK%0*aTVmrLas zKWyuj$$;DTskx!!>VajDJW!Q!t1p7b*y8^E$#ve>TInmZ8>b@&P#-}J&(EwI^6e(b zz!NEuum0#Qi`bQXxvvSjpGJgWE6;jtR8_`gdR=PTxu~j84sx$1>BLkz#xuoi9am<9 zi~d}Nhd=#Xx43L1Yfiz_T&Uq&2yC7Q5HpW6_}0e0xU*qM36R>YNu;quCqk)L^2J=uZT0vmNS9jxa<#LF>`VDttu!8K9aHl2g?P>#t21f5k#xI-TTr>hXOHS|mIufEs07c{LeR%YYsQ{!D z`|S|#WYe;vD;}0;`z1wkMHp@ly&jL1;-@5y&3CwmPWRtbol$zB%FYg0Rw8WeTEEQr zot0nJ*7X&+neh*{G-!3yr>D9agqF5`SXW(Vh#TkCjF%hF4!iHo`SeT+A*p{f6JLHm zKk&)ea5p}#X^z+7NbFn?Kk+Wg%~`H9FNotkKFFA}BJ;lNsb zlJ(8XfAJsUlfqeQY(OHzId$>Z8=`i9BE106@5mCrZ!6hoFg0p3XnA64HzCy!($HL! zi!06Hx2cf?7Brpmr4$`R%5A;h+h^>rZ2)VZE`|yMb?rK#;|d}wpn@g%Y1ku%~@;o6hi4i8!$gyMmzG&^a2 z2W^4jnz8NYtDe6hbl}y4s<=x)7)@=xjENLCFFnJTIxYd*4FeaE3nRoC|;%= z@}6|hkhO2x!l#}l-T?RUNbUSXKQGlI@nQD|8z3tbQXzVrV1IU{Vj#u9Rjg2AEoNr57MxJ>|>kBetTQ}Ssu85_w3|q@z4SY zRx440<8Kck4lI3&6;?^LI3eU|RR46%U9n4P%j_WD!~jjllmCHS)Xo166_SZQa{57I z1C*86E=ytXD5!gF`PrU|tUT{c9S2FBdLiIw!t>bZQ#S>bhLD?kze07VKkvF~0py6_ z@UemtoawjbWL~(p<#s2SVlZRa&MDJtv4^uu0GmfDPEv5#V9M#~ z4^X(a>Sdj@u@9=n>E~AAmmZ|tK%ABJ70@0IlQF=Q^hq}9N6d1UKG_A`Cpjh2#oIT= zbt>q7058Y}1dJl>2N<%*i_?q;%vIWal>90tvV8m9ckHQP$>@&1R#!>cg!BJW4S~y? zNkfSF`NtJcKI?0<1HO(K{9Hk~&I#11TYyayoKr5*6nqyj_P#pNK|D9a8<5n_Smt?> ztb(+uG~fN=$nUkHdP52l2d*Y%OAj8tnewCeI9~F1W|?Jyoj{AK^oA-J7ZOqL_}uX_ zaimhM-j;$IFA{X}JcJ~W$7N3zA4Pm@<;yRR3nUe}`JYVe?$-Nic?4W%uhIk#@dP6? zw}BKBt%NY+*ItiNLuw`3NP-n3Pw<)A^uGs><5u+~fYFm%9MmQi_^iW|klCqi(xK|M zaiO3ISOn<-4-6%%Pc$uTlLo_EO;Y+ zHhH!1P*mIb!a3FKV3$8PIzxH?cnqKwD^_j{_yXnKYxF z=SSg;)9Vh4pl{`I%lO|8A!7^R7nl33A+T^kmtnA<#^JK z28>&Z?x=al&3<#S$;DCwE=1LtzS-r9E}NTl9K$8eF68FsgDdKSgWqPRtT^{@RNhQt zoo((XtccN`;P!9F-VO_H%3cpFD2@BF|ng(($KBkWIdarx0 zfV^o*CF;`WL(Z8kK8Nd_ctIt*sGqP5+wd@c`BC`+=fScfS_!bttSEB} zB@HPh*ae2w?^#TBjx9N3DuL}6eHOdg@wi^)whLN=r~0EKX1rPj<&TXifpuvSojmTE z;WQZO8?N1EiMTEIP8qCA!M{6J#xAz}E6Nb_F6Nh=c02r%A_3>zKB}H^+4M zYWM#w{vB=G`!n>o{?+mNA1$%hvd3mo)amfK50zh}clSA0I{cZpLfT?l?rj|*a5t2F z1IZnoZSLN(rw#59^gPkCuJG5&xt?hi(jE0dHiVUF+&>8OzEue6?gb@rTj5$4J!?{^ zETA?qI~6slJ4m3)E^A9-YtUpvka0yg6{P`rLYV4Be=~RqB?HLG5p2k6)(D!}OTR(cbAY;LAYF@}m&F;g3m&v?}5hNSp&>8x800*DN&aLWVc5t0Mr5U5dD=DsCSyEV!N z!xyU>xi78_*U%mV1#$f!rHN#oSv`dxT!<~9!ov;|7ZcI@uyDT$N3v|)&{A-U+pya> z5f+4^^F>|%r{rWvn3-fVjT4A0J&1c_zGD^q*JkA{DQ+=DA|gGuIVcgd9zFT-RZL`L z=>~p*CJnr>j^}H(K1JOA7`%ROJ9$%FoZ$ssVoQFtuq$Dc08Jf@ z`?S#;6FJv-F-%Yp1^7fhDtb}vPic$99%lSaJIkE>Uf_b2X{{dix-V*=- literal 0 HcmV?d00001 diff --git a/lib/zgltf/test-samples/cameras/Cameras.gltf b/lib/zgltf/test-samples/cameras/Cameras.gltf new file mode 100644 index 0000000..3f06e2d --- /dev/null +++ b/lib/zgltf/test-samples/cameras/Cameras.gltf @@ -0,0 +1,99 @@ +{ + "scene" : 0, + "scenes" : [ + { + "nodes" : [ 0, 1, 2 ] + } + ], + "nodes" : [ + { + "rotation" : [ -0.383, 0.0, 0.0, 0.92375 ], + "mesh" : 0 + }, + { + "translation" : [ 0.5, 0.5, 3.0 ], + "camera" : 0 + }, + { + "translation" : [ 0.5, 0.5, 3.0 ], + "camera" : 1 + } + ], + + "cameras" : [ + { + "type": "perspective", + "perspective": { + "aspectRatio": 1.0, + "yfov": 0.7, + "zfar": 100, + "znear": 0.01 + } + }, + { + "type": "orthographic", + "orthographic": { + "xmag": 1.0, + "ymag": 1.0, + "zfar": 100, + "znear": 0.01 + } + } + ], + + "meshes" : [ + { + "primitives" : [ { + "attributes" : { + "POSITION" : 1 + }, + "indices" : 0 + } ] + } + ], + + "buffers" : [ + { + "uri" : "simpleSquare.bin", + "byteLength" : 60 + } + ], + "bufferViews" : [ + { + "buffer" : 0, + "byteOffset" : 0, + "byteLength" : 12, + "target" : 34963 + }, + { + "buffer" : 0, + "byteOffset" : 12, + "byteLength" : 48, + "target" : 34962 + } + ], + "accessors" : [ + { + "bufferView" : 0, + "byteOffset" : 0, + "componentType" : 5123, + "count" : 6, + "type" : "SCALAR", + "max" : [ 3 ], + "min" : [ 0 ] + }, + { + "bufferView" : 1, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 4, + "type" : "VEC3", + "max" : [ 1.0, 1.0, 0.0 ], + "min" : [ 0.0, 0.0, 0.0 ] + } + ], + + "asset" : { + "version" : "2.0" + } +} diff --git a/lib/zgltf/test-samples/cameras/simpleSquare.bin b/lib/zgltf/test-samples/cameras/simpleSquare.bin new file mode 100644 index 0000000000000000000000000000000000000000..a6edb3b0d31b460695b9a225476ee921ab01ab58 GIT binary patch literal 60 ZcmZQzU}RtdVrC$T3L5OO^FiVeIRG=<0|o#9 literal 0 HcmV?d00001 diff --git a/lib/zgltf/test-samples/khr_lights_punctual/Lights.gltf b/lib/zgltf/test-samples/khr_lights_punctual/Lights.gltf new file mode 100644 index 0000000..ed20b30 --- /dev/null +++ b/lib/zgltf/test-samples/khr_lights_punctual/Lights.gltf @@ -0,0 +1,158 @@ +{ + "asset" : { + "generator" : "Khronos glTF Blender I/O v1.1.46", + "version" : "2.0" + }, + "extensionsUsed" : [ + "KHR_lights_punctual" + ], + "extensionsRequired" : [ + "KHR_lights_punctual" + ], + "extensions" : { + "KHR_lights_punctual" : { + "lights" : [ + { + "color" : [ + 1, + 1, + 1 + ], + "intensity" : 1000, + "type" : "point", + "name" : "Light" + }, + { + "color" : [ + 1, + 1, + 1 + ], + "intensity" : 1000, + "type" : "spot", + "spot": { + "innerConeAngle": 0, + "outerConeAngle": 1 + }, + "name" : "Light.001" + }, + { + "color" : [ + 1, + 1, + 1 + ], + "intensity" : 1000, + "type" : "directional", + "name" : "Light.002" + } + ] + } + }, + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 1, + 3, + 5 + ] + } + ], + "nodes" : [ + { + "extensions" : { + "KHR_lights_punctual" : { + "light" : 0 + } + }, + "name" : "Light_Orientation", + "rotation" : [ + -0.7071067690849304, + 0, + 0, + 0.7071067690849304 + ] + }, + { + "children" : [ + 0 + ], + "name" : "Light", + "rotation" : [ + 0.16907575726509094, + 0.7558803558349609, + -0.27217137813568115, + 0.570947527885437 + ], + "translation" : [ + 4.076245307922363, + 5.903861999511719, + -1.0054539442062378 + ] + }, + { + "extensions" : { + "KHR_lights_punctual" : { + "light" : 1 + } + }, + "name" : "Light.001_Orientation", + "rotation" : [ + -0.7071067690849304, + 0, + 0, + 0.7071067690849304 + ] + }, + { + "children" : [ + 2 + ], + "name" : "Light.001", + "rotation" : [ + 0.16907575726509094, + 0.7558803558349609, + -0.27217137813568115, + 0.570947527885437 + ], + "translation" : [ + -3.9570322036743164, + 1.8591556549072266, + -4.55197811126709 + ] + }, + { + "extensions" : { + "KHR_lights_punctual" : { + "light" : 2 + } + }, + "name" : "Light.002_Orientation", + "rotation" : [ + -0.7071067690849304, + 0, + 0, + 0.7071067690849304 + ] + }, + { + "children" : [ + 4 + ], + "name" : "Light.002", + "rotation" : [ + 0.16907575726509094, + 0.7558803558349609, + -0.27217137813568115, + 0.570947527885437 + ], + "translation" : [ + 0.6759738922119141, + 1.6738548278808594, + 3.1679487228393555 + ] + } + ] +} diff --git a/lib/zgltf/test-samples/rigged_simple/RiggedSimple.gltf b/lib/zgltf/test-samples/rigged_simple/RiggedSimple.gltf new file mode 100644 index 0000000..a8f3cab --- /dev/null +++ b/lib/zgltf/test-samples/rigged_simple/RiggedSimple.gltf @@ -0,0 +1,451 @@ +{ + "asset": { + "generator": "COLLADA2GLTF", + "version": "2.0" + }, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "scene": 0, + "nodes": [ + { + "children": [ + 1 + ], + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ], + "name": "Z_UP" + }, + { + "children": [ + 3, + 2 + ], + "matrix": [ + -4.371139894487897e-8, + -1.0, + 0.0, + 0.0, + 1.0, + -4.371139894487897e-8, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ], + "name": "Armature" + }, + { + "mesh": 0, + "skin": 0, + "name": "Cylinder" + }, + { + "children": [ + 4 + ], + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + -1.3597299641787689e-7, + -4.1803297996521, + 1.0 + ], + "name": "Bone" + }, + { + "translation": [ + 1.2150299863455948e-11, + 0.02797747030854225, + 4.187077045440674 + ], + "rotation": [ + -0.0, + 0.0002899225219152868, + -0.0, + -0.9999999403953552 + ], + "name": "Bone.001" + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "JOINTS_0": 1, + "NORMAL": 2, + "POSITION": 3, + "WEIGHTS_0": 4 + }, + "indices": 0, + "mode": 4, + "material": 0 + } + ], + "name": "Cylinder" + } + ], + "animations": [ + { + "channels": [ + { + "sampler": 0, + "target": { + "node": 4, + "path": "translation" + } + }, + { + "sampler": 1, + "target": { + "node": 4, + "path": "rotation" + } + }, + { + "sampler": 2, + "target": { + "node": 4, + "path": "scale" + } + } + ], + "samplers": [ + { + "input": 5, + "interpolation": "LINEAR", + "output": 6 + }, + { + "input": 5, + "interpolation": "LINEAR", + "output": 7 + }, + { + "input": 5, + "interpolation": "LINEAR", + "output": 8 + } + ] + } + ], + "skins": [ + { + "inverseBindMatrices": 9, + "skeleton": 3, + "joints": [ + 3, + 4 + ], + "name": "Armature" + } + ], + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5123, + "count": 564, + "max": [ + 159 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5123, + "count": 160, + "max": [ + 1, + 1, + 0, + 0 + ], + "min": [ + 0, + 0, + 0, + 0 + ], + "type": "VEC4" + }, + { + "bufferView": 2, + "byteOffset": 0, + "componentType": 5126, + "count": 160, + "max": [ + 0.9999632239341736, + 0.9999632239341736, + 1.0 + ], + "min": [ + -0.9999632239341736, + -0.9999632239341736, + -1.0 + ], + "type": "VEC3" + }, + { + "bufferView": 2, + "byteOffset": 1920, + "componentType": 5126, + "count": 160, + "max": [ + 1.0, + 1.0, + 4.575077056884766 + ], + "min": [ + -1.0, + -0.9999995827674866, + -4.575077056884766 + ], + "type": "VEC3" + }, + { + "bufferView": 3, + "byteOffset": 0, + "componentType": 5126, + "count": 160, + "max": [ + 1.0, + 0.26139819622039797, + 0.0, + 0.0 + ], + "min": [ + 0.738601803779602, + 0.0, + 0.0, + 0.0 + ], + "type": "VEC4" + }, + { + "bufferView": 4, + "byteOffset": 0, + "componentType": 5126, + "count": 50, + "max": [ + 2.083333015441895 + ], + "min": [ + 0.04166661947965622 + ], + "type": "SCALAR" + }, + { + "bufferView": 5, + "byteOffset": 0, + "componentType": 5126, + "count": 50, + "max": [ + -3.4530497493474868e-14, + 0.027977529913187028, + 4.187077045440674 + ], + "min": [ + -3.047870116013041e-13, + 0.027977488934993745, + 4.187077045440674 + ], + "type": "VEC3" + }, + { + "bufferView": 6, + "byteOffset": 0, + "componentType": 5126, + "count": 50, + "max": [ + 0.2953396439552307, + 0.0002899225219152868, + 4.162090377207717e-12, + -0.9553922414779664 + ], + "min": [ + -4.6566102085421338e-9, + 0.0002769898856058717, + -0.00008562587754568085, + -0.9999999403953552 + ], + "type": "VEC4" + }, + { + "bufferView": 5, + "byteOffset": 600, + "componentType": 5126, + "count": 50, + "max": [ + 1.0000001192092896, + 1.0000001192092896, + 1.0 + ], + "min": [ + 0.9999999403953552, + 0.9999998211860656, + 0.9999998211860656 + ], + "type": "VEC3" + }, + { + "bufferView": 7, + "byteOffset": 0, + "componentType": 5126, + "count": 2, + "max": [ + 0.0, + 1.0, + 0.0, + 0.0, + -0.9999998211860656, + 0.0, + 0.0005798449856229126, + 0.0, + 0.0005798449856229126, + 0.0, + 1.0, + 0.0, + 0.0, + 1.3597299641787689e-7, + 4.1803297996521, + 1.0 + ], + "min": [ + 0.0, + 1.0, + 0.0, + 0.0, + -1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.9999998211860656, + 0.0, + -0.000003912100055458723, + -0.02797728031873703, + -0.006746708881109953, + 1.0 + ], + "type": "MAT4" + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.27963539958000185, + 0.6399999856948853, + 0.21094390749931336, + 1.0 + ], + "metallicFactor": 0.0 + }, + "emissiveFactor": [ + 0.0, + 0.0, + 0.0 + ], + "name": "Material_001-effect" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 10008, + "byteLength": 1128, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 8528, + "byteLength": 1280, + "byteStride": 8, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 4688, + "byteLength": 3840, + "byteStride": 12, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 928, + "byteLength": 2560, + "byteStride": 16, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 9808, + "byteLength": 200 + }, + { + "buffer": 0, + "byteOffset": 3488, + "byteLength": 1200 + }, + { + "buffer": 0, + "byteOffset": 128, + "byteLength": 800 + }, + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 128 + } + ], + "buffers": [ + { + "byteLength": 11136, + "uri": "RiggedSimple0.bin" + } + ] +} diff --git a/lib/zgltf/test-samples/rigged_simple/RiggedSimple0.bin b/lib/zgltf/test-samples/rigged_simple/RiggedSimple0.bin new file mode 100644 index 0000000000000000000000000000000000000000..dd3200d6ed19b94fab18fadaa622f099afad587b GIT binary patch literal 11136 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 literal 0 HcmV?d00001 diff --git a/projects/content/_shaders/_def/sample.frag.json b/projects/content/_shaders/_def/sample.frag.json deleted file mode 100644 index 4a19cc2..0000000 --- a/projects/content/_shaders/_def/sample.frag.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "entryPoints" : [ - { - "name" : "main", - "mode" : "frag" - } - ], - "inputs" : [ - { - "type" : "vec4", - "name" : "in.var.TEXCOORD0", - "location" : 0 - } - ], - "outputs" : [ - { - "type" : "vec4", - "name" : "out.var.SV_Target0", - "location" : 0 - } - ] -} \ No newline at end of file diff --git a/projects/content/_shaders/_def/sample.vert.json b/projects/content/_shaders/_def/sample.vert.json deleted file mode 100644 index 93c9ed7..0000000 --- a/projects/content/_shaders/_def/sample.vert.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "entryPoints" : [ - { - "name" : "main", - "mode" : "vert" - } - ], - "types" : { - "_6" : { - "name" : "Scene", - "members" : [ - { - "name" : "color", - "type" : "vec3", - "offset" : 0 - } - ] - }, - "_5" : { - "name" : "type.StructuredBuffer.Scene", - "members" : [ - { - "name" : "_m0", - "type" : "_6", - "array" : [ - 0 - ], - "array_size_is_literal" : [ - true - ], - "offset" : 0, - "array_stride" : 16 - } - ] - } - }, - "outputs" : [ - { - "type" : "vec4", - "name" : "out.var.TEXCOORD0", - "location" : 0 - } - ], - "ssbos" : [ - { - "type" : "_5", - "name" : "test", - "readonly" : true, - "block_size" : 0, - "set" : 0, - "binding" : 0 - } - ] -} \ No newline at end of file diff --git a/projects/sampleGame/main.zig b/projects/sampleGame/main.zig index e740903..ba2f38a 100644 --- a/projects/sampleGame/main.zig +++ b/projects/sampleGame/main.zig @@ -26,10 +26,11 @@ pub fn deinit(self: *@This()) void { } pub fn main() anyerror!void { - // std.debug.print("hello world\n", .{}); - try backlog.initializeAndRunStandardProgram(@This(), .{ .name = "Hello World", + .enabledModules = .{ + .physics = true, + }, }); }