From 5b63271f5a6a3126b881a4de9eb7787149c1549a Mon Sep 17 00:00:00 2001 From: peterino2 Date: Wed, 17 Sep 2025 23:51:01 -0700 Subject: [PATCH] animations working again --- build.zig | 8 - engine/assets/src/assets.zig | 5 + engine/assets/src/cook.zig | 14 +- engine/rend/shaders/meshes.vert.hlsl | 43 +- engine/rend/shaders/meshes.vert.json | 65 ++- .../rend/src/animations/animationResolver.zig | 232 +++++++++ .../rend/src/animations/animationSystem.zig | 486 ++++++++++++++++++ engine/rend/src/animations/loaders.zig | 90 ++++ engine/rend/src/meshes/MeshComponent.zig | 6 + engine/rend/src/meshes/gltfLoader.zig | 2 + engine/rend/src/meshes/meshes.zig | 6 +- engine/rend/src/rend.zig | 10 + engine/rend/src/sgpu/SkyboxSystem.zig | 4 +- engine/rend/src/sgpu/mesh-pool.zig | 41 +- engine/rend/src/sgpu/renderer.zig | 75 ++- engine/rend/src/sgpu/vertexAttributes.zig | 8 +- engine/ui/src/sgpu/papyrusSgpu.zig | 2 +- .../gameExtras/src/debuggers/fileBrowser.zig | 107 ++++ extras/gameExtras/src/gameExtras.zig | 2 + lib/cimgui/src/cimgui.zig | 3 +- lib/p2/src/p2.zig | 1 + lib/p2/src/structures/utils.zig | 2 + projects/build.zig | 1 + .../content/_shaders/dxil/meshes.vert.dxil | Bin 7596 -> 8584 bytes projects/content/_shaders/msl/meshes.vert.msl | 54 +- projects/content/_shaders/spv/meshes.vert.spv | Bin 3520 -> 4936 bytes projects/sampleGame/externGame/externGame.zig | 25 +- projects/sampleGame/main.zig | 13 + projects/tools/toolbox.zig | 10 +- projects/tools/toolbox/animationStore.zig | 50 +- projects/zigbuildinstallTools.bat | 2 +- tools/blender/hello_world_addon/__init__.py | 49 ++ 32 files changed, 1350 insertions(+), 66 deletions(-) create mode 100644 engine/rend/src/animations/animationResolver.zig create mode 100644 engine/rend/src/animations/animationSystem.zig create mode 100644 engine/rend/src/animations/loaders.zig create mode 100644 extras/gameExtras/src/debuggers/fileBrowser.zig create mode 100644 tools/blender/hello_world_addon/__init__.py diff --git a/build.zig b/build.zig index 769f8cd..11f6320 100644 --- a/build.zig +++ b/build.zig @@ -391,15 +391,7 @@ pub const Program = struct { return; } - // const b = self.buildSystem.b; - //const fmt = b.fmt("content/{s}", .{path}); - //const p = b.path(fmt); - - //const absolute = p.getPath(b); - self.iconPath = self.buildSystem.generateRc(self.opts.name, path) catch return; - // std.debug.print("absolute path: {s}", .{absolute}); - //std.debug.print("\nabsolute path: {s}\n", .{absolute}); } pub fn setModuleEnabled(self: *@This(), module: []const u8, enable: bool) void { diff --git a/engine/assets/src/assets.zig b/engine/assets/src/assets.zig index 6a1b5f6..c262c80 100644 --- a/engine/assets/src/assets.zig +++ b/engine/assets/src/assets.zig @@ -45,6 +45,11 @@ pub fn start_module(spec: *core.SpecVariantMap, args: anytype, allocator: std.me memory.MTPrintStatsDelta(); } +pub fn initCooking() !void { + cooking = true; + try cook.startup(core.getEngineObject(AssetReferenceSys).?.allocator); +} + pub fn shutdown_module(allocator: std.mem.Allocator) void { if (cooking) { cook.shutdown(); diff --git a/engine/assets/src/cook.zig b/engine/assets/src/cook.zig index 4e01336..21f3fc9 100644 --- a/engine/assets/src/cook.zig +++ b/engine/assets/src/cook.zig @@ -197,12 +197,16 @@ pub fn generateAllCookFiles(allocator: std.mem.Allocator, dir: std.fs.Dir) !void try generateCookFile(allocator, dir, next.name); }, .directory => { - if (!std.mem.eql(u8, "_cooked", next.name)) { - var subDir = try dir.openDir(next.name, .{ .iterate = true }); - defer subDir.close(); - - try generateAllCookFiles(allocator, subDir); + if (std.mem.eql(u8, "_cooked", next.name)) { + continue; } + + if (std.mem.eql(u8, ".gitignore", next.name)) {} + + var subDir = try dir.openDir(next.name, .{ .iterate = true }); + defer subDir.close(); + + try generateAllCookFiles(allocator, subDir); }, else => {}, } diff --git a/engine/rend/shaders/meshes.vert.hlsl b/engine/rend/shaders/meshes.vert.hlsl index 45c9b79..09cfea8 100644 --- a/engine/rend/shaders/meshes.vert.hlsl +++ b/engine/rend/shaders/meshes.vert.hlsl @@ -1,5 +1,10 @@ // I just reuse this one for the shadow mapping right? +uint getCharFromUintArray(uint color, uint index) +{ + return uint((color >> 8 * index) & 0xFF); +} + struct Input { float3 Position : TEXCOORD0; @@ -31,13 +36,21 @@ struct Scene uint textureMode; // 0 = regular triple, // 0x10 = video yuv, - uint pad0; // 0 = regular triple, - uint pad1; // 0 = regular triple, - uint pad2; // 0 = regular triple, + int animation; // -1 means no animation, any non-zero value means an index into the animationBuffer + uint flags; // 0 -> 0: always in front + // 1 -> 1: useAltCamera + uint pad2; }; StructuredBuffer scene: register(t0, space0); +struct BoneTransform +{ + float4x4 final; +}; + +StructuredBuffer animationBuffer: register(t1, space0); + cbuffer Uniforms : register(b0, space1) { float4x4 ViewProjection; @@ -73,11 +86,33 @@ Output main(Input input) Output output; output.TexCoord = input.UV; - float4 pos = float4(input.Position, 1.0); + int animation = scene[input.Instance].animation; + + float3 vertexPos = input.Position; + + if(animation != -1) + { + vertexPos = float3(0.0, 0.0, 0.0); + for(int i = 0; i < 4; i += 1) + { + uint boneIndex = getCharFromUintArray(input.Bones, i); + + float weight = float(getCharFromUintArray(input.weights, i)) / 255; + + // this will depend on ozz's finals format + BoneTransform boneTransform = animationBuffer[uint(animation) + boneIndex]; + vertexPos += weight * ( mul(boneTransform.final, float4(input.Position, 1.0)) ).xyz; + } + } + + // float4 pos = float4(input.Position, 1.0); + float4 pos = float4(vertexPos, 1.0); // pos.x += 0.2 * sin(time * 0.5 ) * pos.y * 0.5; // pos.y += 0.2 * cos(time * 0.5 ) * pos.z * 0.5; + // todo. maybe all world position values should be + // transformed into screen space float4 WorldPos = mul(scene[input.Instance].Model, pos); output.Position = mul(ViewProjection, mul(scene[input.Instance].Model, pos)); output.WorldPos = WorldPos.xyz; diff --git a/engine/rend/shaders/meshes.vert.json b/engine/rend/shaders/meshes.vert.json index 218501d..1d2b116 100644 --- a/engine/rend/shaders/meshes.vert.json +++ b/engine/rend/shaders/meshes.vert.json @@ -6,7 +6,7 @@ } ], "types" : { - "_15" : { + "_17" : { "name" : "Scene", "members" : [ { @@ -22,12 +22,12 @@ "offset" : 64 }, { - "name" : "pad0", - "type" : "uint", + "name" : "animation", + "type" : "int", "offset" : 68 }, { - "name" : "pad1", + "name" : "flags", "type" : "uint", "offset" : 72 }, @@ -38,12 +38,12 @@ } ] }, - "_14" : { + "_16" : { "name" : "type.StructuredBuffer.Scene", "members" : [ { "name" : "_m0", - "type" : "_15", + "type" : "_17", "array" : [ 0 ], @@ -55,7 +55,36 @@ } ] }, - "_17" : { + "_20" : { + "name" : "BoneTransform", + "members" : [ + { + "name" : "final", + "type" : "mat4", + "offset" : 0, + "matrix_stride" : 16, + "row_major" : true + } + ] + }, + "_19" : { + "name" : "type.StructuredBuffer.BoneTransform", + "members" : [ + { + "name" : "_m0", + "type" : "_20", + "array" : [ + 0 + ], + "array_size_is_literal" : [ + true + ], + "offset" : 0, + "array_stride" : 64 + } + ] + }, + "_22" : { "name" : "type.Uniforms", "members" : [ { @@ -102,6 +131,16 @@ "type" : "vec2", "name" : "in.var.TEXCOORD3", "location" : 3 + }, + { + "type" : "uint", + "name" : "in.var.TEXCOORD4", + "location" : 4 + }, + { + "type" : "uint", + "name" : "in.var.TEXCOORD5", + "location" : 5 } ], "outputs" : [ @@ -138,17 +177,25 @@ ], "ssbos" : [ { - "type" : "_14", + "type" : "_16", "name" : "scene", "readonly" : true, "block_size" : 0, "set" : 0, "binding" : 0 + }, + { + "type" : "_19", + "name" : "animationBuffer", + "readonly" : true, + "block_size" : 0, + "set" : 0, + "binding" : 1 } ], "ubos" : [ { - "type" : "_17", + "type" : "_22", "name" : "type.Uniforms", "block_size" : 196, "set" : 1, diff --git a/engine/rend/src/animations/animationResolver.zig b/engine/rend/src/animations/animationResolver.zig new file mode 100644 index 0000000..d7b051d --- /dev/null +++ b/engine/rend/src/animations/animationResolver.zig @@ -0,0 +1,232 @@ +pub const AnimResolverRef = core.Reference(AnimResolverInterface); +pub const AnimResolverInterface = core.MakeInterface("AnimResolverVTable", struct { + // this tick function should evaluate the current state of the resolver + // and then update the animator's finals[] matrix list. + resolve: *const fn (*anyopaque, f64, *Animator) void, + onSkeletonSet: ?*const fn (*anyopaque, *Animator) void = null, + + create: *const fn (std.mem.Allocator) core.EngineDataEventError!*anyopaque, + destroy: *const fn (*anyopaque) void, + + pub fn Implement(comptime TargetType: type) @This() { + const Wrap = struct { + pub fn create(allocator: std.mem.Allocator) core.EngineDataEventError!*anyopaque { + const new = TargetType.create(allocator) catch return core.EngineDataEventError.BadInit; + return @ptrCast(new); + } + + pub fn destroy(p: *anyopaque) void { + const ptr: *TargetType = @ptrCast(@alignCast(p)); + ptr.destroy(); + } + + pub fn onSkeletonSet(p: *anyopaque, a: *Animator) void { + const ptr: *TargetType = @ptrCast(@alignCast(p)); + ptr.onSkeletonSet(a) catch unreachable; + } + + pub fn resolve(p: *anyopaque, dt: f64, a: *Animator) void { + const ptr: *TargetType = @ptrCast(@alignCast(p)); + ptr.resolve(dt, a) catch unreachable; + } + }; + return .{ + .destroy = Wrap.destroy, + .create = Wrap.create, + .resolve = Wrap.resolve, + }; + } +}); + +pub const AnimSampler = struct { + name: ?core.Name = null, + track: ?*AnimationTrack = null, + playbackRate: f32 = 1.0, + time: f32 = 0.0, + outputLocals: std.ArrayListUnmanaged(ozz.SoaTransform) = .{}, + + // other features + // paused: bool = false, + pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void { + self.outputLocals.deinit(allocator); + } + + pub fn getOutput(self: *@This()) []ozz.SoaTransform { + return self.outputLocals.items; + } + + pub fn sampleAndAdvance(self: *@This(), allocator: std.mem.Allocator, dt: f64, animator: *Animator) void { + self.sample(allocator, animator); + self.advance(dt); + } + + pub fn advance(self: *@This(), dt: f64) void { + if (self.track == null) { + return; + } + + FloatHelpers.updateTrackTime(&self.time, dt, self.playbackRate, self.track.?.endTime); + } + + pub fn setName(self: *@This(), name: core.Name) void { + self.name = name; + self.track = null; + } + + pub fn sample(self: *@This(), allocator: std.mem.Allocator, animator: *Animator) void { + if (self.name == null) { + return; + } + + if (self.track == null) { + self.track = animation_system.gAnimationSys.animTracks.get(self.name.?.handle()); + } + + self.outputLocals.resize(allocator, animator.jointLength) catch return; + + if (self.track) |track| { + if (track.endTime < 0.01) { + return; + } + + animator.sampleAnimation(self.time, track, self.outputLocals.items); + } + } +}; + +pub const BlenderList = struct { + backing: std.mem.Allocator, + arena: std.heap.ArenaAllocator, + + jobLayers: std.ArrayListUnmanaged(ozz.Layer) = .{}, + jobLayersAdditive: std.ArrayListUnmanaged(ozz.Layer) = .{}, + useAdditive: bool = false, + + threshold: f32 = 0.01, + jointLength: usize = 0, + blendingJob: ozz.BlendingJob = .{}, + + pub fn create(backingAllocator: std.mem.Allocator) !*@This() { + const self = try backingAllocator.create(@This()); + self.* = .{ + .backing = backingAllocator, + .arena = std.heap.ArenaAllocator.init(backingAllocator), + }; + + return self; + } + + pub fn destroy(self: *@This()) void { + self.arena.deinit(); + self.backing.destroy(self); + } + + pub fn updateRestPose(self: *@This(), animator: *Animator) !void { + if (animator.skeleton) |skeleton| { + self.jointLength = skeleton.sk.numJoints(); + self.blendingJob.rest_pose = skeleton.sk.getRestPoseModel(); + } + } + + pub fn clearLayers(self: *@This()) void { + self.jobLayersAdditive.clearRetainingCapacity(); + self.jobLayers.clearRetainingCapacity(); + } + + pub fn addLayer(self: *@This(), transform: []ozz.SoaTransform, weight: f32, settings: anytype) void { + const layer = self.jobLayers.addOne(self.arena.allocator()) catch unreachable; + layer.* = .{ + .weight = weight, + .transform = ozz.makeSpan(transform), + }; + _ = settings; + } + + pub fn updateAndRun(self: *@This(), output: []ozz.SoaTransform) void { + self.updateBlendingJob(); + self.runBlendingJob(output) catch return; + } + + pub fn updateBlendingJob(self: *@This()) void { + self.blendingJob.threshold = self.threshold; + self.blendingJob.layers = ozz.makeSpan(self.jobLayers.items); + //self.blendingJob.additive_layers = if (self.useAdditive) ozz.makeSpan(self.jobLayersAdditive.items) else .{}; + self.blendingJob.additive_layers = .{}; + } + + pub fn runBlendingJob(self: *@This(), output: []ozz.SoaTransform) !void { + self.blendingJob.output = ozz.makeSpan(output); + if (!self.blendingJob.run()) { + core.engine_logs("blending job failed"); + return; + } + } +}; + +// resolver helpers +pub const FloatHelpers = struct { + pub inline fn updateTrackTime(target: *f32, dt: f64, rate: f32, endTime: f32) void { + target.* += @as(f32, @floatCast(dt)) * rate; + while (target.* > endTime) { + target.* -= endTime; + } + } +}; + +// samples a single animation, same as the default behaviour. +// used as a test for the resolver system +pub const SingleAnimationResolver = struct { + allocator: std.mem.Allocator, + locals: std.ArrayListUnmanaged(ozz.SoaTransform) = .{}, + + track: ?*AnimationTrack = null, + playback: f32 = 0.0, + playbackRate: f32 = 1.0, + + pub const AnimResolverVTable = AnimResolverInterface.Implement(@This()); + + pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + self.* = .{ + .allocator = allocator, + }; + + return self; + } + + pub fn onSkeletonSet(self: *@This(), animator: *Animator) !void { + if (animator.skeleton) |skeleton| { + try self.locals.resize(self.allocator, skeleton.sk.numSoaJoints()); + } + } + + pub fn resolve(self: *@This(), dt: f64, animator: *Animator) !void { + self.track = animator.track; + if (self.track == null) { + return; + } + + const track = self.track.?; + if (track.endTime < 0.01) { + return; + } + + FloatHelpers.updateTrackTime(&self.playback, dt, self.playbackRate, track.endTime); + animator.sampleAnimation(self.playback, track, self.locals.items); + animator.commitLocalToModel(self.locals.items); + animator.modelToFinal(); + } + + pub fn destroy(self: *@This()) void { + self.locals.deinit(self.allocator); + self.allocator.destroy(self); + } +}; + +const animation_system = @import("animationSystem.zig"); +const Animator = animation_system.Animator; +const AnimationTrack = animation_system.AnimationTrack; + +const core = @import("core"); +const std = @import("std"); +const ozz = @import("ozz"); diff --git a/engine/rend/src/animations/animationSystem.zig b/engine/rend/src/animations/animationSystem.zig new file mode 100644 index 0000000..bd1309b --- /dev/null +++ b/engine/rend/src/animations/animationSystem.zig @@ -0,0 +1,486 @@ +// big main sy.itemsstem for animation + +const ozz = @import("ozz"); +const core = @import("core"); +const std = @import("std"); + +pub const BoneHandle = enum(u8) { _ }; + +pub const Skeleton = struct { + sk: *ozz.Skeleton, + inverseBinds: std.ArrayListUnmanaged(core.Mat) = .{}, + jointMapping: std.StringHashMapUnmanaged(u8) = .{}, + + pub fn buildJointMap(self: *@This(), allocator: std.mem.Allocator) !void { + for (self.sk.getJointsList(), 0..) |jointName, i| { + // std.debug.print("jointName {d} {s}\n", .{ i, jointName }); + const str = std.mem.span(jointName); + try self.jointMapping.put(allocator, str, @intCast(i)); + } + } + + pub fn getBoneHandleByName(self: @This(), string: []const u8) ?BoneHandle { + if (self.jointMapping.get(string)) |x| { + return @enumFromInt(x); + } else { + return null; + } + } + + pub fn deinit(self: *@This()) void { + self.sk.destroy(); + } +}; + +pub const AnimationTrack = struct { + animation: *ozz.Animation, + endTime: f32 = 1.0, + + pub fn deinit(self: *@This()) void { + self.animation.destroy(); + } +}; + +pub const PlaybackTrack = struct { + track: ?*AnimationTrack = null, + playback: f32 = 0.0, + playbackRate: f32 = 1.0, +}; + +pub const Animator = struct { + jointRemap: ?[]u8 = null, + animationName: ?core.Name = null, + skeleton: ?*Skeleton = null, + skeletonName: ?core.Name = null, + sjc: *ozz.SamplingJobContext = undefined, + + track: ?*AnimationTrack = null, + playback: f32 = 0.0, + playbackRate: f32 = 1.0, + + // todo.. implement blending + // animations: [4]*ozz.Animation = undefined, + // timelines: [4]f32 = .{ 0, 0, 0, 0 }, + animationCount: u32 = 0, + + locals: std.ArrayListUnmanaged(ozz.SoaTransform) = .{}, + models: std.ArrayListUnmanaged(ozz.Float4x4) = .{}, + finals: std.ArrayListUnmanaged(core.Mat) = .{}, + finalsSpan: core.Span = undefined, + + entity: core.Entity = undefined, + jointLength: usize = 0, + + resolverRef: ?AnimResolverRef = null, + + pub var allocator: std.mem.Allocator = undefined; + // oh god if I want to support multiple animation blending... + // maybe the kernel should contain a fixed amount of animations? + + pub fn initECS(self: *@This(), handle: core.SetHandle) void { + // get the mesh component + self.entity = core.Entity{ .handle = handle }; + + if (self.entity.fetch(rend.MeshComponent)) |mesh| { + mesh.animated = true; //todo + mesh.animator = self; + self.sjc = ozz.SamplingJobContext.createMaxTracks(256); + } else { + @panic("animator added to an entity that does not have a mesh component"); + } + } + + pub fn getBoneTransform(self: *@This(), handle: BoneHandle) core.Mat { + return @bitCast(self.models.items[@intFromEnum(handle)]); + } + + pub fn setSkeletonByName(self: *@This(), skName: core.Name) !void { + if (self.skeleton != null) { + // return the previous span and allocate a new one. + gAnimationSys.slots.removeSpan(self.finalsSpan); + } + + self.skeletonName = skName; + self.skeleton = gAnimationSys.skeletons.get(self.skeletonName.?.handle()).?; + const numJoints = self.skeleton.?.sk.numJoints(); + self.jointLength = numJoints; + + self.sjc.resize(@intCast(numJoints)); + try self.locals.resize(allocator, self.skeleton.?.sk.numSoaJoints()); + try self.models.resize(allocator, numJoints); + try self.finals.resize(allocator, numJoints); + self.finalsSpan = try gAnimationSys.slots.allocate(@intCast(numJoints)); + + if (self.resolverRef) |ref| { + if (ref.vtable.onSkeletonSet) |f| { + f(ref.ptr, self); + } + } + + self.jointRemap = null; + } + + pub fn setSkeleton(self: *@This(), skeleton: []const u8) void { + self.setSkeletonByName(core.MakeName(skeleton)) catch unreachable; + } + + pub fn addResolver(self: *@This(), comptime Resolver: type) !*Resolver { + const resolver = try Resolver.create(allocator); + try resolver.onSkeletonSet(self); + + self.resolverRef = core.refFromPtr(AnimResolverInterface, resolver); + + return resolver; + } + + pub fn removeResolver(self: *@This()) void { + if (self.resolverRef) |ref| { + ref.vtable.destroy(ref.ptr); + self.resolverRef = null; + } + } + + pub fn update(self: *@This(), dt: f64) void { + if (self.skeleton == null) { + return; + } + + // if a resolver is present, use that to update my the finals instead of the default function below + if (self.resolverRef) |ref| { + ref.vtable.resolve(ref.ptr, dt, self); + return; + } + + if (!self.defaultSample(dt)) { + return; + } + + self.modelToFinal(); + } + + fn defaultSample(self: *@This(), dt: f64) bool { + if (self.track == null) + return false; + + const track = self.track.?; + if (track.endTime < 0.01) + return false; + + const skeleton = self.skeleton.?; + self.playback += @as(f32, @floatCast(dt)) * self.playbackRate; + + while (self.playback > track.endTime) { + self.playback -= track.endTime; + } + + var samplingJob: ozz.SamplingJob = .{ + .ratio = self.playback / track.endTime, + .animation = track.animation, + .context = self.sjc, + .output = ozz.makeSpan(self.locals.items), + }; + + if (!samplingJob.run()) { + core.engine_errs("sampling job failed"); + return false; + } + + var ltmJob: ozz.LocalToModelJob = .{ + .skeleton = skeleton.sk, + .input = ozz.makeSpan(self.locals.items), + .output = ozz.makeSpan(self.models.items), + }; + + if (!ltmJob.run()) { + core.engine_errs("local to model job failed"); + return false; + } + + return true; + } + + pub fn commitLocalToModel(self: *@This(), input: []ozz.SoaTransform) void { + self.localToModel(input, self.models.items); + } + + pub fn localToModel(self: *@This(), input: []ozz.SoaTransform, output: []ozz.Float4x4) void { + var ltmJob: ozz.LocalToModelJob = .{ + .skeleton = self.skeleton.?.sk, + .input = ozz.makeSpan(input), + .output = ozz.makeSpan(output), + }; + + if (!ltmJob.run()) { + core.engine_errs("local to model job failed"); + return; + } + } + + pub fn sampleAnimation(self: *@This(), time: f32, track: *AnimationTrack, output: []ozz.SoaTransform) void { + var samplingJob: ozz.SamplingJob = .{ + .ratio = time / track.endTime, + .animation = track.animation, + .context = self.sjc, + .output = ozz.makeSpan(output), + }; + + if (!samplingJob.run()) { + core.engine_errs("sampling job failed"); + return; + } + } + + pub fn modelToFinal(self: *@This()) void { + if (self.jointRemap == null) { + if (self.entity.fetch(rend.MeshComponent)) |meshComponent| { + if (meshComponent.mesh) |mesh| { + self.jointRemap = mesh.jointRemap; + } + } + } + + const skeleton = self.skeleton.?; + for (self.models.items, 0..) |model, i| { + const transform: core.Mat = @bitCast(model); + + // const p: core.zm.Vec = .{ 0, 0, 0, 1 }; + // graphics.debugSphere(core.Vectorf.fromZm(core.zm.mul(p, transform)), 0.03, .{ + // .color = if (i == 15) .{ .x = 1 } else .{ .y = 1 }, + // }); + + const final = core.zm.mul(skeleton.inverseBinds.items[i], transform); + // joint remap ozz -> gltf + if (self.jointRemap) |jr| { + // core.engine_log("{d} xx {d}", .{ i, jr[i] }); + self.finals.items[@intCast(jr[i])] = final; + } else { + self.finals.items[i] = final; + } + // core.engine_log( + // "[{d}] {d} {d} {d} {d}, {d} {d} {d} {d}", + // .{ i, transform[0][0], transform[0][1], transform[0][2], transform[0][3], transform[1][0], transform[1][1], transform[1][2], transform[1][3] }, + // ); + } + } + + pub fn setAnimationByName(self: *@This(), _name: core.Name) !void { + var name = _name; + self.track = gAnimationSys.animTracks.get(name.handle()); + } + + pub fn setAnimation(self: *@This(), path: []const u8) void { + const name = core.MakeName(path); + self.setAnimationByName(name) catch unreachable; + } + + pub fn deinit(self: *@This()) void { + self.sjc.destroy(); + self.removeResolver(); + self.finals.deinit(allocator); + self.locals.deinit(allocator); + self.models.deinit(allocator); + } + + pub var BaseContainer: *core.SparseMap(@This()) = undefined; + pub const ComponentName = "Animator"; + pub const ScriptExports: []const []const u8 = &.{}; +}; + +pub const AnimationSystem = struct { + backingAllocator: std.mem.Allocator, + + arena: std.heap.ArenaAllocator, + slots: MergedSpans, + + // Only AnimationTrack and Skeletons are made using the ArenaAllocator + animTracks: std.AutoHashMapUnmanaged(u32, *AnimationTrack) = .{}, + skeletons: std.AutoHashMapUnmanaged(u32, *Skeleton) = .{}, + + sharedArena: [2]std.heap.ArenaAllocator, // could be a good usecase for a fat bump arena + shared: [2]std.ArrayListUnmanaged(MatrixUploads) = .{ .{}, .{} }, + sharedLocks: [2]std.Thread.Mutex = .{ .{}, .{} }, // could be a good usecase for a fat bump arena + + pub const MatrixUploads = struct { + offset: u32, + matrices: std.ArrayListUnmanaged(core.Mat) = .{}, + }; + + pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "rend.AnimationSystem"); + + pub fn preTick(self: *@This(), dt: f64) !void { + _ = self; + + var z1 = core.tracy.ZoneN(@src(), "animation system tick"); + defer z1.End(); + + for (Animator.BaseContainer.list.items) |animator| { + animator.update(dt); + } + } + + pub fn newAnimTrack(self: *@This(), _name: core.Name, anim: *ozz.Animation) !void { + var name = _name; + const new = try self.arenaAllocator().create(AnimationTrack); + new.* = .{ + .animation = anim, + .endTime = anim.getDuration(), + }; + try self.animTracks.put(self.backingAllocator, name.handle(), new); + } + + pub fn newSkeleton(self: *@This(), _name: core.Name, sk: *ozz.Skeleton) !void { + const new = try self.arenaAllocator().create(Skeleton); + new.* = .{ + .sk = sk, + }; + var name = _name; + + try new.buildJointMap(self.arenaAllocator()); + + try new.inverseBinds.resize(self.arenaAllocator(), new.sk.numJoints()); + + if (new.inverseBinds.items.len > 256) { + @panic("too many bones in skeleton, not supported"); + } + + var bindModels = std.ArrayList(ozz.Float4x4).init(self.backingAllocator); + defer bindModels.deinit(); + + try bindModels.resize(new.sk.numJoints()); + + var ltmJob: ozz.LocalToModelJob = .{ + .skeleton = new.sk, + .input = new.sk.getRestPoseModel(), + .output = ozz.makeSpan(bindModels.items), + }; + + core.engine_log("creating bind pose {d} joints", .{new.inverseBinds.items.len}); + + if (!ltmJob.run()) { + core.engine_logs("unable to get bind pose"); + return error.UnableToLoad; + } + + for (bindModels.items, 0..) |bind, i| { + // const p: core.zm.Vec = .{ 0, 0, 0, 1 }; + // graphics.debugSphere(core.Vectorf.fromZm(core.zm.mul(p, @as(core.Mat, @bitCast(bind)))), 0.1, .{ .duration = 100 }); + + new.inverseBinds.items[i] = core.zm.inverse(@as(core.Mat, @bitCast(bind))); + } + + try self.skeletons.put(self.backingAllocator, name.handle(), new); + } + + pub fn arenaAllocator(self: *@This()) std.mem.Allocator { + return self.arena.allocator(); + } + + pub fn getShared(self: @This(), fi: u32) []const MatrixUploads { + return self.shared[fi].items; + } + + pub fn sendShared(self: *@This(), frameIndex: u32) void { + const fi: usize = @intCast(frameIndex); + + self.sharedLocks[fi].lock(); + defer self.sharedLocks[fi].unlock(); + + _ = self.sharedArena[fi].reset(.retain_capacity); + + const allocator = self.sharedArena[fi].allocator(); + const shared = &self.shared[fi]; + shared.* = .{}; + + for (Animator.BaseContainer.list.items) |animator| { + var upload: MatrixUploads = .{ .offset = animator.finalsSpan.start }; + // core.engine_log( + // "finalsSpan size offset{d} {d} animator finals {d}\n", + // .{ + // animator.finalsSpan.start, + // animator.finalsSpan.size, + // animator.finals.items.len + // }); + + upload.matrices.resize(allocator, animator.finalsSpan.size) catch unreachable; + + for (animator.finals.items, 0..) |final, i| { + upload.matrices.items[i] = final; + } + + shared.append(allocator, upload) catch unreachable; + } + } + + pub fn init(alloc: std.mem.Allocator) !*@This() { + const self = try alloc.create(@This()); + self.* = .{ + .backingAllocator = alloc, + .arena = std.heap.ArenaAllocator.init(alloc), + .sharedArena = .{ + std.heap.ArenaAllocator.init(alloc), + std.heap.ArenaAllocator.init(alloc), + }, + .slots = try MergedSpans.init(alloc, max_skin_slots), + }; + + gAnimationSys = self; + Animator.allocator = alloc; + + core.engine_logs("Animation System initialized"); + try core.defineComponent(Animator, alloc); + return self; + } + + pub fn deinit(self: *@This()) void { + core.engine_logs("deinitializing animation system"); + { + core.engine_log("skeleton count {d}", .{self.skeletons.count()}); + var iter = self.skeletons.iterator(); + while (iter.next()) |i| { + i.value_ptr.*.deinit(); + } + } + + { + core.engine_log("animTracks count {d}", .{self.animTracks.count()}); + var iter = self.animTracks.iterator(); + while (iter.next()) |i| { + i.value_ptr.*.deinit(); + } + } + + for (self.sharedArena) |arena| { + arena.deinit(); + } + + for (Animator.BaseContainer.list.items) |animator| { + animator.deinit(); + } + self.slots.deinit(); + core.undefineComponent(Animator); + self.arena.deinit(); + self.skeletons.deinit(self.backingAllocator); + self.animTracks.deinit(self.backingAllocator); + self.backingAllocator.destroy(self); + } +}; + +pub var gAnimationSys: *AnimationSystem = undefined; + +pub fn getAnimationSystem() *AnimationSystem { + return gAnimationSys; +} + +pub fn getSkeletonByName(_name: core.Name) ?*Skeleton { + var name = _name; + return gAnimationSys.skeletons.get(name.handle()); +} + +const rend = @import("../rend.zig"); +const MergedSpans = core.MergedSpans; + +pub const max_skin_slots = 100_000; + +const anim_resolver = @import("animationResolver.zig"); +const AnimResolverRef = anim_resolver.AnimResolverRef; +const AnimResolverInterface = anim_resolver.AnimResolverInterface; diff --git a/engine/rend/src/animations/loaders.zig b/engine/rend/src/animations/loaders.zig new file mode 100644 index 0000000..e826d96 --- /dev/null +++ b/engine/rend/src/animations/loaders.zig @@ -0,0 +1,90 @@ +pub const AnimationLoader = struct { + pub var LoaderInterfaceVTable: assets.AssetLoaderInterface = assets.AssetLoaderInterface.from("Animation", @This()); + pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "rend.AnimationLoader"); + + sys: *animation_system.AnimationSystem, + allocator: std.mem.Allocator, + + pub fn discardAll(self: *@This()) void { + _ = self; + } + + pub fn loadAsset(self: *@This(), assetRef: assets.AssetRef, propertiesBag: ?assets.AssetPropertiesBag) assets.AssetLoaderError!void { + const animation = ozz.Animation.create(); + + const mapping = core.fs().loadFile(propertiesBag.?.path) catch return error.UnableToLoad; + defer core.fs().unmap(mapping); + animation.loadFromBytes(mapping.bytes); + + self.sys.newAnimTrack(assetRef.name, animation) catch return error.UnableToLoad; + } + + pub fn init(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + + self.* = .{ + .sys = animation_system.gAnimationSys, + .allocator = allocator, + }; + + return self; + } + + pub fn destroy(self: *@This()) void { + self.allocator.destroy(self); + } +}; + +pub const SkeletonLoader = struct { + pub var LoaderInterfaceVTable: assets.AssetLoaderInterface = assets.AssetLoaderInterface.from("Skeleton", @This()); + pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "rend.SkeletonLoader"); + + sys: *animation_system.AnimationSystem, + allocator: std.mem.Allocator, + + pub fn discardAll(self: *@This()) void { + _ = self; + } + + pub fn loadAsset(self: *@This(), assetRef: assets.AssetRef, propertiesBag: ?assets.AssetPropertiesBag) assets.AssetLoaderError!void { + const sk = ozz.Skeleton.create(); + + const mapping = core.fs().loadFile(propertiesBag.?.path) catch return error.UnableToLoad; + defer core.fs().unmap(mapping); + sk.loadFromBytes(mapping.bytes); + + self.sys.newSkeleton(assetRef.name, sk) catch return error.UnableToLoad; + } + + pub fn init(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + + self.* = .{ + .sys = animation_system.gAnimationSys, + .allocator = allocator, + }; + + return self; + } + + pub fn destroy(self: *@This()) void { + self.allocator.destroy(self); + } +}; + +pub var gSkeletonLoader: *SkeletonLoader = undefined; +pub var gAnimationLoader: *AnimationLoader = undefined; + +pub fn initLoaders() !void { + gSkeletonLoader = try core.createObject(SkeletonLoader, .{}); + gAnimationLoader = try core.createObject(AnimationLoader, .{}); + + try assets.getAssets().registerLoader(gSkeletonLoader); + try assets.getAssets().registerLoader(gAnimationLoader); +} + +const animation_system = @import("animationSystem.zig"); +const assets = @import("assets"); +const core = @import("core"); +const std = @import("std"); +const ozz = @import("ozz"); diff --git a/engine/rend/src/meshes/MeshComponent.zig b/engine/rend/src/meshes/MeshComponent.zig index 253aee8..22f792e 100644 --- a/engine/rend/src/meshes/MeshComponent.zig +++ b/engine/rend/src/meshes/MeshComponent.zig @@ -9,6 +9,9 @@ texture: ?*Texture = null, entity: core.Entity = undefined, +animated: bool = false, +animator: ?*Animator = null, + textureMode: ComponentTextureMode = .{}, // bit // DEBUG for-fun function, completely override what happens when the renderer tries to render this mesh. cmrf: ?rend.renderer.CustomMeshRenderFunc = null, @@ -91,3 +94,6 @@ const core = @import("core"); const std = @import("std"); const rend = @import("../rend.zig"); const Texture = rend.Texture; + +const animationSystem = rend.animationSystem; +const Animator = animationSystem.Animator; diff --git a/engine/rend/src/meshes/gltfLoader.zig b/engine/rend/src/meshes/gltfLoader.zig index 5a678c5..69b0659 100644 --- a/engine/rend/src/meshes/gltfLoader.zig +++ b/engine/rend/src/meshes/gltfLoader.zig @@ -269,6 +269,8 @@ pub fn loadIndexedMeshForPoolingGltf(allocator: std.mem.Allocator, meshName: cor }, }; + // grab the animation system and generate a joint remap + core.graphics_log("[{s}] gltf loaded vertex count vertices={d} indices={d}", .{ path, rv.new.vertices.len, rv.new.indices.len }); // try gMeshPoolBuffer.updateRequests.pushLocked(rv); diff --git a/engine/rend/src/meshes/meshes.zig b/engine/rend/src/meshes/meshes.zig index 6ef08ac..77304ee 100644 --- a/engine/rend/src/meshes/meshes.zig +++ b/engine/rend/src/meshes/meshes.zig @@ -3,8 +3,8 @@ pub const MeshVertex = extern struct { normal: core.Vectorf = .{}, color: core.colors.Color = .{}, uv: core.Vector2f = .{}, - bones: [4]u8 = .{ 0, 0, 0, 0 }, - weights: [4]u8 = .{ 0, 0, 0, 0 }, + bones: u8vec4 = .{ 0, 0, 0, 0 }, + weights: u8vec4 = .{ 0, 0, 0, 0 }, }; pub const MeshUpdate = union(enum(u8)) { @@ -95,3 +95,5 @@ const rend = @import("../rend.zig"); const renderer = rend.renderer; const std = @import("std"); const zgltf = @import("zgltf"); +const shaderTypes = @import("sdl3").shaderTypes; +const u8vec4 = shaderTypes.u8vec4; diff --git a/engine/rend/src/rend.zig b/engine/rend/src/rend.zig index ee0c1bd..66e27a2 100644 --- a/engine/rend/src/rend.zig +++ b/engine/rend/src/rend.zig @@ -36,6 +36,12 @@ pub const textureExists = renderer.textureExists; pub const texture = @import("texture/texture.zig"); pub const Texture = texture.Texture; +pub const animationSystem = @import("animations/animationSystem.zig"); +pub const animationResolver = @import("animations/animationResolver.zig"); +const animationLoaders = @import("animations/loaders.zig"); + +pub const Animator = animationSystem.Animator; + pub const setSkyboxTexture = renderer.setSkyboxTexture; var rendAllocator: std.mem.Allocator = undefined; @@ -55,9 +61,13 @@ pub fn start_module(spec: *core.SpecVariantMap, args: anytype, allocator: std.me _ = spec; rendAllocator = allocator; + try renderer.createInstance(); try renderer.start(); + _ = try context().createRendererEngineObject(animationSystem.AnimationSystem); + + try animationLoaders.initLoaders(); try core.defineComponentList(ComponentList, allocator); } diff --git a/engine/rend/src/sgpu/SkyboxSystem.zig b/engine/rend/src/sgpu/SkyboxSystem.zig index 2e3ddfa..2d887aa 100644 --- a/engine/rend/src/sgpu/SkyboxSystem.zig +++ b/engine/rend/src/sgpu/SkyboxSystem.zig @@ -48,7 +48,9 @@ fn createPipeline(self: *@This()) !void { pci.vertex_shader = vertex; pci.fragment_shader = fragment; - var attributes = try ctx.generateVertexAttributeList(); + //var attributes = try ctx.generateVertexAttributeList(); + + var attributes = try ctx.addVertexAttributes(&pci); defer attributes.deinit(); pci.vertex_input_state = .{ diff --git a/engine/rend/src/sgpu/mesh-pool.zig b/engine/rend/src/sgpu/mesh-pool.zig index 667b70d..e80e865 100644 --- a/engine/rend/src/sgpu/mesh-pool.zig +++ b/engine/rend/src/sgpu/mesh-pool.zig @@ -74,6 +74,8 @@ pub const MeshPool = struct { self.destroyList.clearRetainingCapacity(); } + pub const JointMap = std.AutoHashMapUnmanaged(u32, u32); + pub fn onUploadInner(self: *@This(), copyPass: *gpu.GPUCopyPass) !void { self.meshUpdates.lock(); defer self.meshUpdates.unlock(); @@ -93,12 +95,48 @@ pub const MeshPool = struct { core.graphics_log("uploading {d} vertices and {d} indices", .{ vertexSpan.size, indexSpan.size }); + var maybeJointRemap: ?[]u8 = null; + + if (new.skeletonName) |skName| { + if (animationSystem.getSkeletonByName(skName)) |sk| { + var jointMap: JointMap = .{}; + + for (new.jointNames) |entry| { + var entryName = core.MakeName(entry.name); + // core.engine_log("gtlf bone found {s} -> {d}", .{ entry.name, entry.index }); + try jointMap.put(self.allocator, entryName.handle(), entry.index); + } + // build the joint remap + // this is a map from ozz's index to gltf's index + var iter = sk.jointMapping.iterator(); + var jointRemap = try self.allocator.alloc(u8, sk.jointMapping.count()); + + while (iter.next()) |i| { + const jointName = i.key_ptr.*; + const ozzIndex = i.value_ptr.*; + var jn = core.MakeName(jointName); + var gltfIndex = jointMap.get(jn.handle()); + if (gltfIndex == null) { + gltfIndex = 0; + // core.engine_log("ERROR REMAPPING BONE setting to zero {s}", .{jointName}); + } + + // core.engine_log("remapping bone from {s} ozz {d} -> {d} gltf", .{ jointName, ozzIndex, gltfIndex.? }); + + jointRemap[ozzIndex] = @intCast(gltfIndex.?); + } + + maybeJointRemap = jointRemap; + // todo.. self.jointMaps.put(new.name().handle(), jointMap); + } + } + core.engine_log("install mesh by name {d}", .{name.handle()}); try self.installedMeshes.put(self.allocator, name.handle(), .{ .vertex = vertexSpan, .index = indexSpan, .name = new.name, - .jointRemap = null, // joint remaps... TODO parse the installed skeletal meshes to generate this remap + .jointRemap = maybeJointRemap, // joint remaps... TODO parse the installed skeletal meshes to generate this remap }); }, .free => |free| { @@ -199,6 +237,7 @@ const MeshAssetLoader = @import("MeshAssetLoader.zig"); const sdl3 = @import("sdl3"); const gpu = sdl3.gpu; const rend = @import("../rend.zig"); +const animationSystem = rend.animationSystem; const std = @import("std"); const core = @import("core"); const assets = @import("assets"); diff --git a/engine/rend/src/sgpu/renderer.zig b/engine/rend/src/sgpu/renderer.zig index a132c05..cb02bc5 100644 --- a/engine/rend/src/sgpu/renderer.zig +++ b/engine/rend/src/sgpu/renderer.zig @@ -26,6 +26,9 @@ pub const Renderer = struct { ssboScene: *gpu.GPUBuffer = undefined, ssboSceneUpload: *gpu.GPUTransferBuffer = undefined, + ssboAnimation: *gpu.GPUBuffer = undefined, + ssboAnimationUpload: *gpu.GPUTransferBuffer = undefined, + meshPool: *MeshPool = undefined, uploads: std.ArrayListUnmanaged(struct { ptr: *anyopaque, func: *const fn (*anyopaque, *gpu.GPUCopyPass) void }) = .{}, @@ -219,7 +222,8 @@ pub const Renderer = struct { } pub fn addVertexAttributes(self: *@This(), pci: *gpu.GPUGraphicsPipelineCreateInfo) !std.ArrayList(gpu.GPUVertexAttribute) { - const attributes = try self.generateVertexAttributeList(); + _ = self; + const attributes = try addVertexAttributesFromStruct(rend.MeshVertex, pci); pci.vertex_input_state = .{ .num_vertex_buffers = 1, @@ -241,7 +245,10 @@ pub const Renderer = struct { pci.vertex_shader = vertex; pci.fragment_shader = fragment; - var attributes = try self.generateVertexAttributeList(); + //return rend.renderer.addVertexAttributesFromStruct(TextMeshVertex, pci); + //var attributes = try self.generateVertexAttributeList(); + + var attributes = try addVertexAttributesFromStruct(rend.MeshVertex, &pci); defer attributes.deinit(); pci.vertex_input_state = .{ @@ -334,7 +341,9 @@ pub const Renderer = struct { pub fn createRendererEngineObject(self: *@This(), T: type) !*T { const object = try core.createObject(T, .{}); - try object.setup(self.device); + if (@hasDecl(T, "setup")) { + try object.setup(self.device); + } try self.registerRendererObject(T, object); @@ -405,7 +414,7 @@ pub const Renderer = struct { offset.* = offset.* + size; } - pub fn generateVertexAttributeList(self: *@This()) !std.ArrayList(gpu.GPUVertexAttribute) { + pub fn generateVertexAttributeList_deprecated(self: *@This()) !std.ArrayList(gpu.GPUVertexAttribute) { var list = std.ArrayList(gpu.GPUVertexAttribute).init(self.allocator); var offset: u32 = 0; { @@ -427,7 +436,8 @@ pub const Renderer = struct { pci.vertex_shader = vertex; pci.fragment_shader = fragment; - var attributes = try self.generateVertexAttributeList(); + // var attributes = try self.generateVertexAttributeList(); + var attributes = try self.addVertexAttributes(&pci); defer attributes.deinit(); pci.vertex_input_state = .{ @@ -465,7 +475,8 @@ pub const Renderer = struct { pci.vertex_shader = vertex; pci.fragment_shader = fragment; - var attributes = try self.generateVertexAttributeList(); + //var attributes = try self.generateVertexAttributeList(); + var attributes = try self.addVertexAttributes(&pci); defer attributes.deinit(); pci.vertex_input_state = .{ @@ -531,6 +542,21 @@ pub const Renderer = struct { .size = MaxObjectCount * @sizeOf(meshes_vert.Scene), .props = 0, }); + + self.ssboAnimation = self.device.createGPUBuffer(&.{ + .usage = .{ + .bufferusageGraphicsStorageRead = true, + .bufferusageVertex = true, + }, + .size = MaxObjectCount * @sizeOf(meshes_vert.BoneTransform), + .props = 0, + }); + + self.ssboAnimationUpload = self.device.createGPUTransferBuffer(&.{ + .usage = .transferbufferusageUpload, + .size = MaxObjectCount * @sizeOf(meshes_vert.BoneTransform), + .props = 0, + }); } pub fn uploadSSBOs(self: *@This(), copyPass: *gpu.GPUCopyPass) !void { @@ -551,6 +577,30 @@ pub const Renderer = struct { } uploadMapped[i].Model = @bitCast(transform); uploadMapped[i].textureMode = @bitCast(object.textureMode); + if (container.dense.items[i].value.animator) |animator| { + uploadMapped[i].animation = @intCast(animator.finalsSpan.start); + } else { + uploadMapped[i].animation = -1; + } + } + } + + var animationHighest: u32 = 0; + + // upload animation buffers + { + const animationMapped: [*]meshes_vert.BoneTransform = @ptrCast(@alignCast(self.device.mapGPUTransferBuffer(self.ssboAnimationUpload, true))); + defer self.device.unmapGPUTransferBuffer(self.ssboAnimationUpload); + // const as = animationSystem.getAnimationSystem(); + + for (animationSystem.Animator.BaseContainer.list.items) |animator| { + const span = animator.finalsSpan; + + for (0..span.size) |i| { + animationMapped[span.start + i].final = animator.finals.items[i]; + } + if (span.start + span.size > animationHighest) + animationHighest = span.start + span.size; } } @@ -562,6 +612,15 @@ pub const Renderer = struct { .offset = 0, .size = @intCast(uploadCount * @sizeOf(meshes_vert.Scene)), }, true); + + if (animationHighest == 0) + return; + + copyPass.uploadToGPUBuffer(&.{ .transfer_buffer = self.ssboAnimationUpload, .offset = 0 }, &.{ + .buffer = self.ssboAnimation, + .offset = 0, + .size = @intCast(animationHighest * @sizeOf(meshes_vert.BoneTransform)), + }, true); } pub fn loadShader( @@ -764,6 +823,7 @@ pub const Renderer = struct { const renderpass = cmd.beginGPURenderPass(null, 0, &depthTarget); renderpass.bindGPUGraphicsPipeline(self.shadowCastingPipeline); renderpass.bindGPUVertexStorageBuffers(0, &self.ssboScene, 1); + renderpass.bindGPUVertexStorageBuffers(1, &self.ssboAnimation, 1); renderpass.bindGPUVertexBuffers(0, &.{ .buffer = self.meshPool.vertexBuffer, .offset = 0 }, 1); renderpass.bindGPUIndexBuffer(&.{ .buffer = self.meshPool.indexBuffer, .offset = 0 }, .indexelementsize32bit); @@ -822,6 +882,7 @@ pub const Renderer = struct { renderpass.bindGPUFragmentSamplers(2, &.{ .sampler = self.blockySampler, .texture = self.defaultTexture.texture }, 1); renderpass.bindGPUVertexStorageBuffers(0, &self.ssboScene, 1); + renderpass.bindGPUVertexStorageBuffers(1, &self.ssboAnimation, 1); renderpass.bindGPUFragmentStorageBuffers(0, &self.ssboScene, 1); renderpass.bindGPUVertexBuffers(0, &.{ .buffer = self.meshPool.vertexBuffer, .offset = 0 }, 1); renderpass.bindGPUIndexBuffer(&.{ .buffer = self.meshPool.indexBuffer, .offset = 0 }, .indexelementsize32bit); @@ -1068,5 +1129,7 @@ const texture_sample_png align(8) = @embedFile("embedded/texture_sample.png").*; const primitive_box_obj align(8) = @embedFile("embedded/primitive_box.obj").*; const tracy = core.tracy; +const animationSystem = rend.animationSystem; + const builtin = @import("builtin"); pub const SsaoSystem = @import("ssao.zig"); diff --git a/engine/rend/src/sgpu/vertexAttributes.zig b/engine/rend/src/sgpu/vertexAttributes.zig index 31526a8..d25db61 100644 --- a/engine/rend/src/sgpu/vertexAttributes.zig +++ b/engine/rend/src/sgpu/vertexAttributes.zig @@ -8,15 +8,16 @@ pub fn getVertexFormatFromType(comptime T: type) gpu.GPUVertexElementFormat { u32, u8vec4 => { return gpu.GPUVertexElementFormat.vertexelementformatUint; }, - vec2 => { + vec2, core.Vector2f => { return gpu.GPUVertexElementFormat.vertexelementformatFloat2; }, - vec3 => { + vec3, core.Vectorf => { return gpu.GPUVertexElementFormat.vertexelementformatFloat3; }, - vec4 => { + vec4, core.colors.Color => { return gpu.GPUVertexElementFormat.vertexelementformatFloat4; }, + else => { @compileError("unable to get vertex format from subtype" ++ @typeName(T)); }, @@ -31,6 +32,7 @@ pub fn addVertexAttributesFromStruct(comptime T: type, pci: *gpu.GPUGraphicsPipe inline for (@typeInfo(T).@"struct".fields) |field| { try Renderer.addAttribute(&list, &offset, @sizeOf(field.type), getVertexFormatFromType(field.type)); } + core.engine_log("# of attributes added : {d}", .{list.items.len}); pci.vertex_input_state = .{ .num_vertex_buffers = 1, diff --git a/engine/ui/src/sgpu/papyrusSgpu.zig b/engine/ui/src/sgpu/papyrusSgpu.zig index fb9c8e9..f8ebd6e 100644 --- a/engine/ui/src/sgpu/papyrusSgpu.zig +++ b/engine/ui/src/sgpu/papyrusSgpu.zig @@ -225,7 +225,7 @@ pub fn createRectPipeline(self: *@This()) !void { pci.vertex_shader = vertex; pci.fragment_shader = fragment; - var attributes = try ctx.generateVertexAttributeList(); + var attributes = try ctx.addVertexAttributes(&pci); defer attributes.deinit(); pci.vertex_input_state = .{ diff --git a/extras/gameExtras/src/debuggers/fileBrowser.zig b/extras/gameExtras/src/debuggers/fileBrowser.zig new file mode 100644 index 0000000..810ffa1 --- /dev/null +++ b/extras/gameExtras/src/debuggers/fileBrowser.zig @@ -0,0 +1,107 @@ +pub const SortMode = enum { + unordered, + name, + dateModified, +}; + +pub fn Browser(comptime T: type) type { + return struct { + list: ?*[]T = null, + + display: std.ArrayListUnmanaged(u32) = .{}, + allocator: std.mem.Allocator, + + sameWindow: bool = false, + windowOpen: bool = false, + + browserName: []u8, + + sortMode: SortMode = .unordered, + + pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + + self.* = .{ + .browserName = try std.fmt.allocPrintZ(allocator, "Browser", .{}), + .allocator = allocator, + }; + + return self; + } + + pub fn setList(self: *@This(), list: *[]T) void { + self.list = list; + } + + fn sortList(self: *@This()) void { + if (self.list) |list| { + self.display.clearRetainingCapacity(); + if (self.sortMode == .unordered) { + for (list.*, 0..) |x, i| { + _ = x; + self.display.append(self.allocator, @intCast(i)) catch unreachable; + } + } + } + } + + pub fn tickDisplay(self: *@This()) void { + self.sortList(); + + if (!self.sameWindow) { + if (!ig.begin(self.browserName.ptr, &self.windowOpen, .{})) { + ig.end(); + return; + } + } + + if (ig.beginTable("FileBrowserTable", 2, .{ .resizable = true }, .{}, 0)) { + ig.tableSetupColumn("File Name", .{ .width_stretch = true }, 0, 0); + ig.tableSetupColumn("Modified Time", .{ .width_fixed = true }, 150, 0); + ig.tableHeadersRow(); + + if (self.list) |list| { + for (self.display.items) |i| { + const item = &list.*[i]; + + ig.tableNextRow(.{}, 0); + _ = ig.tableSetColumnIndex(0); + if (@hasDecl(T, "fileName")) { + const filename_str = item.fileName(); + ig.textSlice(filename_str); + } else { + continue; + } + + _ = ig.tableSetColumnIndex(1); + if (@hasDecl(T, "modtime")) { + const modtime_str = item.modtime(); + ig.textSlice(modtime_str); + } else { + ig.textSlice("N/A"); + } + } + } else {} + + ig.endTable(); + } + + if (!self.sameWindow) { + ig.end(); + } + } + + pub fn destroy(self: *@This()) void { + self.display.deinit(self.allocator); + self.allocator.free(self.browserName); + self.allocator.destroy(self); + } + }; +} + +const std = @import("std"); +const backlog = @import("Backlog"); +const core = backlog.core; +const rend = backlog.rend; +const ig = backlog.imgui.api; +const igutils = backlog.imgui.utils; diff --git a/extras/gameExtras/src/gameExtras.zig b/extras/gameExtras/src/gameExtras.zig index 4238ab8..614e3d8 100644 --- a/extras/gameExtras/src/gameExtras.zig +++ b/extras/gameExtras/src/gameExtras.zig @@ -6,3 +6,5 @@ pub const ObjectSpawner = @import("debuggers/objectSpawner.zig"); pub const RendererDebug = @import("debuggers/RendererDebug.zig"); pub const EngineTool = @import("debuggers/EngineTool.zig"); pub const PhysicsObjectList = @import("debuggers/PhysicsObjectList.zig"); + +pub const browser = @import("debuggers/fileBrowser.zig"); diff --git a/lib/cimgui/src/cimgui.zig b/lib/cimgui/src/cimgui.zig index 2288bdf..a892f5c 100644 --- a/lib/cimgui/src/cimgui.zig +++ b/lib/cimgui/src/cimgui.zig @@ -296,7 +296,7 @@ pub const TableFlags = packed struct(c_int) { scroll_y: bool = false, // ImGuiTableFlags_ScrollY = 1 << 25, sort_multi: bool = false, // ImGuiTableFlags_SortMulti = 1 << 26, sort_tristate: bool = false, // ImGuiTableFlags_SortTristate = 1 << 27, - reserved: u4 = 0, + reserved: u3 = 0, pub const borders_h = .{ .borders_inner_h = true, .borders_outer_h = true }; pub const borders_v = .{ .borders_inner_v = true, .borders_outer_v = true }; @@ -325,6 +325,7 @@ pub const TableColumnFlags = packed struct(c_int) { prefer_sort_descending: bool = false, // ImGuiTableColumnFlags_PreferSortDescending = 1 << 15, indent_enable: bool = false, // ImGuiTableColumnFlags_IndentEnable = 1 << 16, indent_disable: bool = false, // ImGuiTableColumnFlags_IndentDisable = 1 << 17, + reserved0: u7 = 0, is_enabled: bool = false, // ImGuiTableColumnFlags_IsEnabled = 1 << 24, is_visible: bool = false, // ImGuiTableColumnFlags_IsVisible = 1 << 25, is_sorted: bool = false, // ImGuiTableColumnFlags_IsSorted = 1 << 26, diff --git a/lib/p2/src/p2.zig b/lib/p2/src/p2.zig index 3c7f0c1..3e6b99b 100644 --- a/lib/p2/src/p2.zig +++ b/lib/p2/src/p2.zig @@ -4,6 +4,7 @@ const utils = @import("structures/utils.zig"); pub const getFileExtension = utils.getFileExtension; pub const getBasePath = utils.getBasePath; pub const getFolder = utils.getFolder; +pub const getDir = utils.getFolder; pub const BumpArena = @import("structures/bump-arena.zig").BumpArena; diff --git a/lib/p2/src/structures/utils.zig b/lib/p2/src/structures/utils.zig index 6a753ec..bbdde2a 100644 --- a/lib/p2/src/structures/utils.zig +++ b/lib/p2/src/structures/utils.zig @@ -97,6 +97,7 @@ pub fn assert(eval: anytype) !void { } } +// gets the basename of the file/path pub fn getBasePath(path: []const u8) []const u8 { if (path.len == 0) { return ""; @@ -119,6 +120,7 @@ pub fn getBasePath(path: []const u8) []const u8 { return path; } +// gets the folder name of the dir/path pub fn getFolder(path: []const u8) []const u8 { if (path.len == 0) { unreachable; diff --git a/projects/build.zig b/projects/build.zig index 87682c6..7d97265 100644 --- a/projects/build.zig +++ b/projects/build.zig @@ -63,6 +63,7 @@ pub fn build(b: *std.Build) void { toolbox.setModuleEnabled("imgui", true); toolbox.setModuleEnabled("audio", false); toolbox.setModuleEnabled("sys", true); + toolbox.addExtraModule("gameExtras"); const toolboxExe = toolbox.compileInstall(); // Add Windows icon resource for toolbox diff --git a/projects/content/_shaders/dxil/meshes.vert.dxil b/projects/content/_shaders/dxil/meshes.vert.dxil index 147b51a227f5228efe28bd6d4fabace1c5d4f75c..60fe6bf237262123f69ce26c7ac6b8db19d8cdad 100644 GIT binary patch delta 5220 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 delta 4255 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 RZ{}i3U^#0)GXKwm{|DlQaK``u diff --git a/projects/content/_shaders/msl/meshes.vert.msl b/projects/content/_shaders/msl/meshes.vert.msl index 0f92f0a..5cb0413 100644 --- a/projects/content/_shaders/msl/meshes.vert.msl +++ b/projects/content/_shaders/msl/meshes.vert.msl @@ -7,8 +7,8 @@ struct Scene { float4x4 Model; uint textureMode; - uint pad0; - uint pad1; + int animation; + uint flags; uint pad2; }; @@ -17,6 +17,16 @@ struct type_StructuredBuffer_Scene Scene _m0[1]; }; +struct BoneTransform +{ + float4x4 final; +}; + +struct type_StructuredBuffer_BoneTransform +{ + BoneTransform _m0[1]; +}; + struct type_Uniforms { float4x4 ViewProjection; @@ -41,22 +51,42 @@ struct main0_in float3 in_var_TEXCOORD0 [[attribute(0)]]; float3 in_var_TEXCOORD1 [[attribute(1)]]; float2 in_var_TEXCOORD3 [[attribute(3)]]; + uint in_var_TEXCOORD4 [[attribute(4)]]; + uint in_var_TEXCOORD5 [[attribute(5)]]; }; -vertex main0_out main0(main0_in in [[stage_in]], constant type_Uniforms& Uniforms [[buffer(0)]], const device type_StructuredBuffer_Scene& scene [[buffer(1)]], uint gl_InstanceIndex [[instance_id]]) +vertex main0_out main0(main0_in in [[stage_in]], constant type_Uniforms& Uniforms [[buffer(0)]], const device type_StructuredBuffer_Scene& scene [[buffer(1)]], const device type_StructuredBuffer_BoneTransform& animationBuffer [[buffer(2)]], uint gl_InstanceIndex [[instance_id]]) { main0_out out = {}; - float4 _53 = float4(in.in_var_TEXCOORD0, 1.0); - float4 _56 = scene._m0[gl_InstanceIndex].Model * _53; - float4x4 _91 = float4x4(float4(fast::normalize(float3(scene._m0[gl_InstanceIndex].Model[0][0], scene._m0[gl_InstanceIndex].Model[1][0], scene._m0[gl_InstanceIndex].Model[2][0])), 0.0), float4(fast::normalize(float3(scene._m0[gl_InstanceIndex].Model[0][1], scene._m0[gl_InstanceIndex].Model[1][1], scene._m0[gl_InstanceIndex].Model[2][1])), 0.0), float4(fast::normalize(float3(scene._m0[gl_InstanceIndex].Model[0][2], scene._m0[gl_InstanceIndex].Model[1][2], scene._m0[gl_InstanceIndex].Model[2][2])), 0.0), float4(0.0, 0.0, 0.0, 1.0)); - float4 _95 = float4(in.in_var_TEXCOORD1, 1.0); + float3 _103; + if (scene._m0[gl_InstanceIndex].animation != (-1)) + { + float3 _75; + _75 = float3(0.0); + for (int _78 = 0; _78 < 4; ) + { + uint _84 = (8u * uint(_78)) & 31u; + _75 += ((animationBuffer._m0[uint(scene._m0[gl_InstanceIndex].animation) + ((in.in_var_TEXCOORD4 >> _84) & 255u)].final * float4(in.in_var_TEXCOORD0, 1.0)).xyz * (float((in.in_var_TEXCOORD5 >> _84) & 255u) * 0.0039215688593685626983642578125)); + _78++; + continue; + } + _103 = _75; + } + else + { + _103 = in.in_var_TEXCOORD0; + } + float4 _107 = float4(_103, 1.0); + float4 _110 = scene._m0[gl_InstanceIndex].Model * _107; + float4x4 _145 = float4x4(float4(fast::normalize(float3(scene._m0[gl_InstanceIndex].Model[0][0], scene._m0[gl_InstanceIndex].Model[1][0], scene._m0[gl_InstanceIndex].Model[2][0])), 0.0), float4(fast::normalize(float3(scene._m0[gl_InstanceIndex].Model[0][1], scene._m0[gl_InstanceIndex].Model[1][1], scene._m0[gl_InstanceIndex].Model[2][1])), 0.0), float4(fast::normalize(float3(scene._m0[gl_InstanceIndex].Model[0][2], scene._m0[gl_InstanceIndex].Model[1][2], scene._m0[gl_InstanceIndex].Model[2][2])), 0.0), float4(0.0, 0.0, 0.0, 1.0)); + float4 _149 = float4(in.in_var_TEXCOORD1, 1.0); out.out_var_TEXCOORD0 = in.in_var_TEXCOORD3; - out.out_var_TEXCOORD1 = _56.xyz; - out.out_var_TEXCOORD2 = fast::normalize(_95 * _91).xyz; - out.out_var_TEXCOORD3 = Uniforms.ShadowMapProjection * _56; + out.out_var_TEXCOORD1 = _110.xyz; + out.out_var_TEXCOORD2 = fast::normalize(_149 * _145).xyz; + out.out_var_TEXCOORD3 = Uniforms.ShadowMapProjection * _110; out.out_var_TEXCOORD4 = gl_InstanceIndex; - out.out_var_TEXCOORD5 = (_95 * (_91 * transpose(Uniforms.NoTranslateView))).xyz; - out.gl_Position = Uniforms.ViewProjection * (scene._m0[gl_InstanceIndex].Model * _53); + out.out_var_TEXCOORD5 = (_149 * (_145 * transpose(Uniforms.NoTranslateView))).xyz; + out.gl_Position = Uniforms.ViewProjection * (scene._m0[gl_InstanceIndex].Model * _107); return out; } diff --git a/projects/content/_shaders/spv/meshes.vert.spv b/projects/content/_shaders/spv/meshes.vert.spv index a87f72c97c746d0c1b204a5a41e827222b4c3131..bead25867671735e53af71110ba80137f82168bf 100644 GIT binary patch literal 4936 zcmZveXLnRp6ozjC2_+PjWzz8@OkdN8;+wVYiIBM?t1n)_au`#mrlvDjx5V&W#9ALb!pa_BRyGn&bMwK z*xozV8th$r>rL8cWs_vNZFY89E_d>KxLR+hXq^moLER7qvZ>HCXgbsb&46Y?v!L0~ zB!2WiK%y=@FF|fSKT_)*XpN2^Y>kiB1~-lm4b?__2M*R6HGC%1XOK}Q8#hX z>y%8~wZC3FzI(KJq;{~_TsNnn%~|Yh=KUP4wraXjWomA79s`G~gU#bRsv}9Tb1~Mr zwCck(7)V#P0G^ZwnUed|8@

qrH2#9N4sL*Mpn$JEP4Rw%Kkja!n!jKejbt^A5Ge ztqq%ZtIc+6*t~De@z#Huu@~=QyUk{_c|Y529=ta1ZM)4zwRxZ0Z8oaSd){udQSWj3 z?QBK&Td{{#&{ifI--$NA6>Tnc&ty@q6}mH4_nfsmS9Sf=ou#^d>dsGHKXqrO?oxM7 z>XFad>h`SNd8kKzdsUD8?prEp78!1VQ; z)y=JcyagBiFJkFF!Y}511@YGHEA#j+xi03JoWE6KQCFX+8}G+O|Ki)%Kdl>nF~4|Q zjO&BkLtyQ3&N+>X8BZi}V(oEaX`DSQ^DVLSKlif9O{u zpJ&Y{mv^>O+LW(>Tnormfs0vxHPcpK4CxaTIhTOhv$`?%n`IOCgnb!W2V~FJ!YhzV zyZUvI??LFxk*yK>^~mQT`&t3FhV@o6J@u7P^dz_b<#_nEXxDcUzJ3p4)^v7q>1uZA zJ<+}h**mIUA#yd``H5-sE}Fv_@1eT5_eEn;?_nK423uU{`*Kke!_!aH)g z*y~Nm`s$C}s_n^K8?s{*uWqs~| zTi zHz4fleoLwDoeJIAOoOaperMympXxn{?yP+G!|tqn_d_?{cRzIf{kx^^z4hGghE7aO ztiO%Uou_+sIp06fHo))3w*tQhGB)%+WcRP`dGtZ=i}LU7Q>}I=G8zqmv}$>G`

{-tpvB_4U6MYm4m8AUdacwl45 z;>N~Vw2-YSx#BD$?>Lxw_i)F&6UQL2Sb-l02)vk8;s0EM7xOCqKX@M8T*m1Wd0s#^ zk2zd{zla=on*XH)PxHSFZZ6~Wi9D|$n@3+);IASFp5~uO@HGDkaB~?q0nK1O75HnA z@mnDG82IZ6Zf}8~OmK6?9Xo|=E>~c0AV>bd-Yl`Ax8i$z3(R=^wO<2;|J%s+;tK2? zYL=;>7*rr2X!P#H3^^tYYZ$Wr^CPtSM18FO zX|0~;-n-?j>996?@85nu=bkeI{WC*Z)|X}3X!a}rT`y++IdUwk=6qpkeW|wJZPYGZ zzMy?H8<6F;6WI&7+|U1=dTUojYXusFst^UTVdw~S6gmctK*ym0{`A`{Hq6{oU4fyR);8US(Qw*MnB`>1wC_ zxVhDBwb_}l+1K6nMyI~J|D@h+>V|DNkF&S+NA*Vg>2iH93ARu3+Gn@5(}aNxW-q~$ z@*qRGPiwdKTfI};xU+eCW#!&{KC3oo>apcBYx5j>Y##yTIX%PnLHB=2jq_}KZ6?*` znfKbTUfSIsb^X-clhogRNd28%-JbM!-Zb9Y>ekWjTznDY4caFe%VriT`+vneq zb68KGQ+T}$pMv^V7KMUy-WW8}b{06n_JAtp?hnO`|I!n)zfF9XYj2_b>EuMoyoT*bZ7Fd3Eg<#n$Yzxev7m03Y|Eoy}O+IAGG)3*Qq@P zuR+F!{sFS{sL#MJK+dD?K3;^JXARV|^Zhr6D=^=EF=r0U_g>6iR?BbJciwpYwfoJ^ zVe?F`K%Rql%9?U}@SHw|wEsyDp7mA8T;g+J#(x5NZfV@7B_4UMp{IF1LpP6jle*xTF#_1Dz7Lm=PuPg8`kONQif0^KE{;$BzWt=`E?9UW@ z2{PWg&KUS|g4nuKYtq{ z*Z0WYaaUkJAd3}eSlsI$ODwJV6PWS(Yd;OeJ$Z<%-P)eh8F(FvbA5;$_vh!*rro8F zb>i-BA$xbnp}12GWbwba-_F>CtR)^jY$Mx)wy5YdHk>T~r>&aO0GHuYJRmN8t9Pd>rok@-JOoh5iGZZRV8# diff --git a/projects/sampleGame/externGame/externGame.zig b/projects/sampleGame/externGame/externGame.zig index 3a1c679..fb9bd3c 100644 --- a/projects/sampleGame/externGame/externGame.zig +++ b/projects/sampleGame/externGame/externGame.zig @@ -86,6 +86,11 @@ pub const ExternGameObject = struct { _ = fireInput.data.addListener(self, onFire); fireInput.activate(); + const altFireInput = try core.ActionBinding.create(core.MakeName("altFire")); + altFireInput.addKey(.Mouse3, .keyDown); + _ = altFireInput.data.addListener(self, onAltFire); + altFireInput.activate(); + const settings: physics.ShapeSettings = .{ .box = try physics.BoxShapeSettings.create(.{ 1.0, 1.0, 1.0 }) }; defer settings.release(); try physics.addShape("ph_small_box", settings); @@ -135,7 +140,7 @@ pub const ExternGameObject = struct { }); } - fn onFire(ctx: ?*anyopaque, _: core.ActionEvent) void { + fn onAltFire(ctx: ?*anyopaque, _: core.ActionEvent) void { const self: *@This() = @ptrCast(@alignCast(ctx)); if (platform.context().isCursorEnabled()) { @@ -145,8 +150,6 @@ pub const ExternGameObject = struct { if (r.normal) |n| { const p = r.point.add(n.fmul(0.2)); core.debugSphere(p, 0.2, .{ .color = .{ .y = 1.0 }, .duration = 10.0 }); - - addBox(p.add(n.fmul(1.0))) catch unreachable; rend.context().lightPosition = p; } @@ -156,6 +159,22 @@ pub const ExternGameObject = struct { } } + fn onFire(ctx: ?*anyopaque, _: core.ActionEvent) void { + const self: *@This() = @ptrCast(@alignCast(ctx)); + + if (platform.context().isCursorEnabled()) { + core.engine_logs("moving light"); + const ray = rend.context().activeCamera.?.getNormalRayFromScreen(platform.getCursorPosition()); + if (physics.traceLine(ray.start, ray.dir.fmul(10000), .{ .bodyFilter = &self.fireTraceFilter, .getNormalsSlow = true })) |r| { + if (r.normal) |n| { + const p = r.point.add(n.fmul(0.2)); + + addBox(p.add(n.fmul(1.0))) catch unreachable; + } + } + } + } + pub fn tick(self: *@This(), dt: f64) void { const rctx = rend.context(); _ = rctx; diff --git a/projects/sampleGame/main.zig b/projects/sampleGame/main.zig index 984438c..335606e 100644 --- a/projects/sampleGame/main.zig +++ b/projects/sampleGame/main.zig @@ -48,6 +48,16 @@ const assetReferences = [_]assets.AssetImportReference{ "m_skybox", .{ .path = "meshes/skybox.obj" }, ), + assets.MakeImportRefOptions( + "Skeleton", + "sk_fox", + .{ .path = "gltf-samples/Fox/glTF/skeleton.ozz" }, + ), + assets.MakeImportRefOptions( + "Animation", + "a_fox_survey", + .{ .path = "gltf-samples/Fox/glTF/Survey.ozz" }, + ), assets.MakeImportRefOptions( "Mesh", "m_fox", @@ -96,6 +106,9 @@ pub const FoxObject = struct { const mesh = fox.addComponent(rend.MeshComponent).?; mesh.setMesh("m_fox"); mesh.setTexture("t_fox"); + const animator = fox.addComponent(rend.Animator).?; + animator.setSkeleton("sk_fox"); + animator.setAnimation("a_fox_survey"); self.entity = fox; diff --git a/projects/tools/toolbox.zig b/projects/tools/toolbox.zig index 8b50b6c..0ced155 100644 --- a/projects/tools/toolbox.zig +++ b/projects/tools/toolbox.zig @@ -29,6 +29,8 @@ pub fn init(allocator: std.mem.Allocator) !*@This() { pub fn prepare(self: *@This()) !void { core.engine_log("program ready", .{}); + try assets.initCooking(); + if (core.getEngineObject(imgui.utils.TopBar)) |topbar| { topbar.menuOpen = true; } @@ -42,6 +44,7 @@ pub fn tick(self: *@This(), dt: f64) void { std.time.sleep(10 * 1000 * 1000); self.tickTasks() catch {}; + self.animationStore.tick(); if (self.activeCommand != null or self.commandQueue.count() > 0) { //ig.setNextWindowPos(.{ .x = 20, .y = 20 }, .{}, .{}); @@ -71,8 +74,6 @@ pub fn tick(self: *@This(), dt: f64) void { // Create a docked window if (ig.begin("Toolbox Window", null, .{})) { - ig.textf("This is a docked toolbox window", .{}); - if (ig.button("Compile Shaders", .{})) { start_CompileShaders() catch {}; } @@ -82,7 +83,9 @@ pub fn tick(self: *@This(), dt: f64) void { } ig.separator(); - ig.textf("Additional toolbox content can go here", .{}); + ig.textf("animation store", .{}); + + self.animationStore.tickDisplay(); if (self.activeCommand != null or self.commandQueue.count() > 0) { ig.textf("outstanding tasks: {d}", .{self.commandQueue.count() + 1}); @@ -292,6 +295,7 @@ const builtin = @import("builtin"); const std = @import("std"); const api = @import("backlog"); const imgui = api.imgui; +const assets = api.assets; const ig = api.imgui.api; const core = api.core; const sys = api.sys; diff --git a/projects/tools/toolbox/animationStore.zig b/projects/tools/toolbox/animationStore.zig index 372aed9..bd1bf29 100644 --- a/projects/tools/toolbox/animationStore.zig +++ b/projects/tools/toolbox/animationStore.zig @@ -4,6 +4,25 @@ stringArena: std.heap.ArenaAllocator, filesToProcess: std.ArrayListUnmanaged(FileToProcess) = .{}, +animations: std.ArrayListUnmanaged(AnimationData) = .{}, + +browser: *AnimationDataBrowser, + +const AnimationDataBrowser = extras.browser.Browser(AnimationData); + +pub const AnimationData = struct { + name: []const u8, + + pub fn fileName(self: *@This()) []const u8 { + return self.name; + } + + pub fn getModTime(self: *@This()) []const u8 { + _ = self; + return "0"; + } +}; + pub const FileToProcess = struct { path: []const u8, }; @@ -23,8 +42,12 @@ pub fn create(allocator: std.mem.Allocator) !*@This() { self.* = .{ .allocator = allocator, .stringArena = std.heap.ArenaAllocator.init(allocator), + .browser = try AnimationDataBrowser.create(allocator), }; + self.browser.sameWindow = true; + self.browser.setList(&self.animations.items); + try self.setupCallbacks(); try self.initialLoad(); @@ -52,7 +75,8 @@ pub fn scanAll(self: *@This(), dir_path: []const u8) !void { try self.scanAll(full_path); // Recursive call for subdirectories } else if (entry.kind == .file) { // core.engine_log("processing file: {s}", .{entry.name}); - try self.filesToProcess.append(self.allocator, .{ .path = entry.name }); + var pathAsName = core.MakeName(full_path); + try self.filesToProcess.append(self.allocator, .{ .path = pathAsName.utf8() }); } } } @@ -65,7 +89,9 @@ pub fn fileChangedCallback(path: []const u8, ctx: ?*anyopaque) void { const self: *@This() = @ptrCast(@alignCast(ctx.?)); // core.engine_log("file change seen {s}", .{path}); // - self.filesToProcess.append(self.allocator, .{ .path = path }) catch {}; + var name = core.MakeName(path); + + self.filesToProcess.append(self.allocator, .{ .path = name.utf8() }) catch {}; // self.updatePath(path) catch return; } @@ -85,21 +111,28 @@ pub fn updatePath(self: *@This(), path: []const u8) !void { const pathstr = try std.fmt.allocPrint(self.salloc(), "{s}.cook", .{path[0 .. path.len - 5]}); defer self.salloc().free(pathstr); - core.engine_log("checking path: {s}", pathstr); + core.engine_log("checking path: {s}", .{pathstr}); if (!core.fs().fileExists(pathstr)) { - // create a cook file under the pathstr, the file watcher should see it.. - core.engine_log("creating cooker {s}", .{pathstr}); + const dir = try std.fs.cwd().openDir(p2.getDir(pathstr), .{ .iterate = true }); + try cook.generateAllCookFiles(self.allocator, dir); } + try self.animations.append(self.allocator, .{ .name = path }); + //var name = core.MakeName(pathstr); // if there is a cook file // var mapping = try core.fs().loadFile(path); // zgltf.init(self.allocator); // core.fs().unmap(mapping); + } } +pub fn tickDisplay(self: *@This()) void { + self.browser.tickDisplay(); +} + pub fn tick(self: *@This()) void { const start = core.getEngineTime(); @@ -109,11 +142,12 @@ pub fn tick(self: *@This()) void { break; } - self.updatePath(pop.path) catch {}; + self.updatePath(pop.path) catch unreachable; } } pub fn destroy(self: *@This()) void { + self.browser.destroy(); self.allocator.destroy(self); } @@ -121,6 +155,10 @@ const std = @import("std"); const api = @import("backlog"); const imgui = api.imgui; const ig = api.imgui.api; +const p2 = core.algorithm; const core = api.core; const sys = api.sys; const zgltf = core.zgltf; +const assets = api.assets; +const cook = assets.cook; +const extras = @import("gameExtras"); diff --git a/projects/zigbuildinstallTools.bat b/projects/zigbuildinstallTools.bat index 19613f8..45bf572 100644 --- a/projects/zigbuildinstallTools.bat +++ b/projects/zigbuildinstallTools.bat @@ -1 +1 @@ -zig build -Dstatic_build --watch --prominent-compile-errors -freference-trace -p binaries install +zig build -Dstatic_build --watch --prominent-compile-errors -freference-trace -p binaries %* install diff --git a/tools/blender/hello_world_addon/__init__.py b/tools/blender/hello_world_addon/__init__.py new file mode 100644 index 0000000..736c99f --- /dev/null +++ b/tools/blender/hello_world_addon/__init__.py @@ -0,0 +1,49 @@ +bl_info = { + "name": "Hello World Addon", + "author": "BacklogEngine", + "version": (1, 0), + "blender": (2, 80, 0), + "location": "View3D > Sidebar > Hello World", + "description": "Adds a button that prints Hello World", + "category": "Development", +} + +import bpy + +class HELLO_OT_print_hello_world(bpy.types.Operator): + """Print Hello World to console""" + bl_idname = "hello.print_hello_world" + bl_label = "Print Hello World" + + def execute(self, context): + print("Hello World") + self.report({'INFO'}, "Hello World printed to console!") + return {'FINISHED'} + +class HELLO_PT_main_panel(bpy.types.Panel): + """Creates a Panel in the 3D Viewport N-Panel""" + bl_label = "Hello World" + bl_idname = "HELLO_PT_main_panel" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "Hello World" + + def draw(self, context): + layout = self.layout + layout.operator("hello.print_hello_world") + +classes = ( + HELLO_OT_print_hello_world, + HELLO_PT_main_panel, +) + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + +def unregister(): + for cls in reversed(classes): + bpy.utils.unregister_class(cls) + +if __name__ == "__main__": + register() \ No newline at end of file