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");