animations working again

This commit is contained in:
peterino2 2025-09-17 23:51:01 -07:00
parent 1b8488b411
commit 5b63271f5a
32 changed files with 1350 additions and 66 deletions

View File

@ -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 {

View File

@ -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();

View File

@ -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 => {},
}

View File

@ -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> scene: register(t0, space0);
struct BoneTransform
{
float4x4 final;
};
StructuredBuffer<BoneTransform> 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;

View File

@ -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,

View File

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

View File

@ -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;

View File

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

View File

@ -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;

View File

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

View File

@ -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;

View File

@ -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);
}

View File

@ -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 = .{

View File

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

View File

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

View File

@ -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,

View File

@ -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 = .{

View File

@ -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;

View File

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

View File

@ -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,

1
lib/p2/src/p2.zig vendored
View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

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

View File

@ -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

View File

@ -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()