Skip to content

Commit fc46ef7

Browse files
committed
Skinned mesh cleanup: moving skinned meshes into a new graphics/skinned-meshes.zig file
1 parent e0ec068 commit fc46ef7

4 files changed

Lines changed: 309 additions & 254 deletions

File tree

src/examples/skinned-meshes.zig

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const images = delve.images;
1313
const input = delve.platform.input;
1414
const math = delve.math;
1515
const modules = delve.modules;
16-
const mesh = delve.graphics.mesh;
16+
const skinned_mesh = delve.graphics.skinned_mesh;
1717

1818
// easy access to some types
1919
const Vec3 = math.Vec3;
@@ -23,7 +23,7 @@ const Color = colors.Color;
2323
const shader_builtin = delve.shaders.default_skinned;
2424

2525
var time: f32 = 0.0;
26-
var mesh_test: ?mesh.Mesh = null;
26+
var mesh_test: ?skinned_mesh.SkinnedMesh = null;
2727
var camera: cam.Camera = undefined;
2828

2929
const mesh_file = "assets/meshes/CesiumMan.gltf";
@@ -74,7 +74,7 @@ fn on_init() !void {
7474
camera.direction = Vec3.new(0.0, 0.0, 1.0);
7575

7676
// Make our emissive shader from one that is pre-compiled
77-
const shader = graphics.Shader.initFromBuiltin(.{ .vertex_attributes = mesh.getSkinnedShaderAttributes() }, shader_builtin);
77+
const shader = graphics.Shader.initFromBuiltin(.{ .vertex_attributes = skinned_mesh.getSkinnedShaderAttributes() }, shader_builtin);
7878

7979
if (shader == null) {
8080
debug.log("Could not get shader", .{});
@@ -98,7 +98,7 @@ fn on_init() !void {
9898
});
9999

100100
// Load our mesh!
101-
mesh_test = mesh.Mesh.initFromFile(delve.mem.getAllocator(), mesh_file, .{ .material = material });
101+
mesh_test = skinned_mesh.SkinnedMesh.initFromFile(delve.mem.getAllocator(), mesh_file, .{ .material = material });
102102

103103
// start looping the first animation
104104
mesh_test.?.playAnimation(0, 1.0, true);

src/framework/delve.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub const graphics = struct {
4040
pub const batcher = @import("graphics/batcher.zig");
4141
pub const camera = @import("graphics/camera.zig");
4242
pub const mesh = @import("graphics/mesh.zig");
43+
pub const skinned_mesh = @import("graphics/skinned-mesh.zig");
4344
pub const sprites = @import("graphics/sprites.zig");
4445
};
4546

src/framework/graphics/mesh.zig

Lines changed: 6 additions & 250 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,6 @@ pub const MeshConfig = struct {
2424
material: ?graphics.Material = null,
2525
};
2626

27-
pub const PlayingAnimation = struct {
28-
anim_idx: usize = 0,
29-
time: f32 = 0.0,
30-
speed: f32 = 0.0,
31-
playing: bool = false,
32-
looping: bool = true,
33-
34-
// calculated on play
35-
duration: f32 = 0.0,
36-
37-
pub fn isDonePlaying(self: *PlayingAnimation) bool {
38-
return self.time >= self.duration;
39-
}
40-
};
41-
42-
const AnimationTransform = struct {
43-
translation: math.Vec3 = math.Vec3.zero,
44-
scale: math.Vec3 = math.Vec3.one,
45-
rotation: math.Quaternion = math.Quaternion.identity,
46-
47-
pub fn toMat4(self: *const AnimationTransform) math.Mat4 {
48-
return math.Mat4.recompose(self.translation, self.rotation, self.scale);
49-
}
50-
};
51-
5227
pub fn init() !void {
5328
zmesh.init(mem.getAllocator());
5429
}
@@ -63,10 +38,9 @@ pub const Mesh = struct {
6338
bindings: graphics.Bindings = undefined,
6439
material: graphics.Material = undefined,
6540
bounds: boundingbox.BoundingBox = undefined,
66-
zmesh_data: *zmesh.io.zcgltf.Data = undefined,
6741

68-
joint_locations: [64]math.Mat4 = [_]math.Mat4{math.Mat4.identity} ** 64,
69-
playing_animation: PlayingAnimation = .{},
42+
has_skin: bool = false,
43+
zmesh_data: *zmesh.io.zcgltf.Data = undefined,
7044

7145
pub fn initFromFile(allocator: std.mem.Allocator, filename: [:0]const u8, cfg: MeshConfig) ?Mesh {
7246
const data = zmesh.io.parseAndLoadFile(filename) catch {
@@ -169,240 +143,21 @@ pub const Mesh = struct {
169143
}
170144

171145
pub fn deinit(self: *Mesh) void {
172-
zmesh.io.freeData(self.zmesh_data);
146+
if (self.has_skin)
147+
zmesh.io.freeData(self.zmesh_data);
148+
173149
self.bindings.destroy();
174150
}
175151

176152
/// Draw this mesh
177153
pub fn draw(self: *Mesh, proj_view_matrix: math.Mat4, model_matrix: math.Mat4) void {
178-
self.material.params.joints = &self.joint_locations;
179154
graphics.drawWithMaterial(&self.bindings, &self.material, proj_view_matrix, model_matrix);
180155
}
181156

182157
/// Draw this mesh, using the specified material instead of the set one
183158
pub fn drawWithMaterial(self: *Mesh, material: *graphics.Material, proj_view_matrix: math.Mat4, model_matrix: math.Mat4) void {
184-
self.material.params.joints = &self.joint_locations;
185159
graphics.drawWithMaterial(&self.bindings, material, proj_view_matrix, model_matrix);
186160
}
187-
188-
pub fn resetJoints(self: *Mesh) void {
189-
for (0..self.joint_locations.len) |i| {
190-
self.joint_locations[i] = math.Mat4.identity;
191-
}
192-
}
193-
194-
pub fn getAnimationsCount(self: *Mesh) usize {
195-
return self.zmesh_data.animations_count;
196-
}
197-
198-
pub fn playAnimation(self: *Mesh, anim_idx: usize, speed: f32, loop: bool) void {
199-
self.playing_animation.looping = loop;
200-
self.playing_animation.time = 0.0;
201-
self.playing_animation.speed = speed;
202-
203-
if (anim_idx >= self.zmesh_data.animations_count) {
204-
debug.log("warning: animation {} not found!", .{anim_idx});
205-
self.playing_animation.anim_idx = 0;
206-
self.playing_animation.playing = false;
207-
self.playing_animation.duration = 0;
208-
return;
209-
}
210-
211-
self.playing_animation.anim_idx = anim_idx;
212-
self.playing_animation.playing = true;
213-
214-
const animation = self.zmesh_data.animations.?[anim_idx];
215-
self.playing_animation.duration = zmesh.io.computeAnimationDuration(&animation);
216-
}
217-
218-
pub fn playAnimationByName(self: *Mesh, anim_name: []const u8, speed: f32, loop: bool) void {
219-
// convert to a sentinel terminated pointer
220-
const anim_name_z = @as([*:0]u8, @constCast(@ptrCast(anim_name)));
221-
222-
// Go find the animation whose name matches
223-
for (0..self.zmesh_data.animations_count) |i| {
224-
if (self.zmesh_data.animations.?[i].name) |name| {
225-
const result = std.mem.orderZ(u8, name, anim_name_z);
226-
if (result == .eq) {
227-
// debug.log("Found animation index for {s} : {}", .{ anim_name, i });
228-
self.playAnimation(i, speed, loop);
229-
return;
230-
}
231-
}
232-
}
233-
234-
debug.log("Could not find skined mesh animation to play: '{s}'", .{anim_name});
235-
}
236-
237-
pub fn updateAnimation(self: *Mesh, delta_time: f32) void {
238-
if (self.zmesh_data.skins == null or self.zmesh_data.animations == null)
239-
return;
240-
241-
if (!self.playing_animation.playing)
242-
return;
243-
244-
self.playing_animation.time += delta_time * self.playing_animation.speed;
245-
246-
const animation = self.zmesh_data.animations.?[self.playing_animation.anim_idx];
247-
const animation_duration = self.playing_animation.duration;
248-
249-
// loop if we need to!
250-
var t = self.playing_animation.time;
251-
if (self.playing_animation.looping) {
252-
t = @mod(t, animation_duration);
253-
}
254-
255-
const nodes = self.zmesh_data.skins.?[0].joints;
256-
const nodes_count = self.zmesh_data.skins.?[0].joints_count;
257-
258-
var local_transforms: [64]AnimationTransform = [_]AnimationTransform{undefined} ** 64;
259-
260-
for (0..nodes_count) |i| {
261-
const node = nodes[i];
262-
local_transforms[i] = .{
263-
.translation = math.Vec3.fromArray(node.translation),
264-
.scale = math.Vec3.fromArray(node.scale),
265-
.rotation = math.Quaternion.new(node.rotation[0], node.rotation[1], node.rotation[2], node.rotation[3]),
266-
};
267-
}
268-
269-
for (0..animation.channels_count) |i| {
270-
const channel = animation.channels[i];
271-
const sampler = animation.samplers[i];
272-
273-
var node_idx: usize = 0;
274-
var found_node = false;
275-
for (0..nodes_count) |ni| {
276-
if (nodes[ni] == channel.target_node.?) {
277-
node_idx = ni;
278-
found_node = true;
279-
break;
280-
}
281-
}
282-
283-
if (!found_node)
284-
continue;
285-
286-
switch (channel.target_path) {
287-
.translation => {
288-
const sampled_translation = self.sampleAnimation(math.Vec3, sampler, t);
289-
local_transforms[node_idx].translation = sampled_translation;
290-
},
291-
.scale => {
292-
const sampled_scale = self.sampleAnimation(math.Vec3, sampler, t);
293-
local_transforms[node_idx].scale = sampled_scale;
294-
},
295-
.rotation => {
296-
const sampled_quaternion = self.sampleAnimation(math.Quaternion, sampler, t);
297-
local_transforms[node_idx].rotation = sampled_quaternion;
298-
},
299-
else => {
300-
// unhandled!
301-
},
302-
}
303-
}
304-
305-
var local_transform_mats: [64]math.Mat4 = [_]math.Mat4{math.Mat4.identity} ** 64;
306-
for (0..nodes_count) |i| {
307-
local_transform_mats[i] = local_transforms[i].toMat4();
308-
}
309-
310-
// update each joint location based on each node in the joint heirarchy
311-
for (0..nodes_count) |i| {
312-
var node = nodes[i];
313-
self.joint_locations[i] = local_transform_mats[i];
314-
315-
while (node.parent) |parent| : (node = parent) {
316-
var parent_idx: usize = 0;
317-
var found_node = false;
318-
319-
for (0..nodes_count) |ni| {
320-
if (nodes[ni] == parent) {
321-
parent_idx = ni;
322-
found_node = true;
323-
break;
324-
}
325-
}
326-
327-
if (!found_node)
328-
continue;
329-
330-
const parent_transform = local_transform_mats[parent_idx];
331-
self.joint_locations[i] = parent_transform.mul(self.joint_locations[i]);
332-
}
333-
}
334-
335-
// apply the inverse bind matrices
336-
const inverse_bind_mat_data = zmesh.io.getAnimationSamplerData(self.zmesh_data.skins.?[0].inverse_bind_matrices.?);
337-
for (0..nodes_count) |i| {
338-
const inverse_mat = access(math.Mat4, inverse_bind_mat_data, i);
339-
self.joint_locations[i] = self.joint_locations[i].mul(inverse_mat);
340-
}
341-
}
342-
343-
pub fn sampleAnimation(self: *Mesh, comptime T: type, sampler: zmesh.io.zcgltf.AnimationSampler, t: f32) T {
344-
_ = self;
345-
346-
const samples = zmesh.io.getAnimationSamplerData(sampler.input);
347-
const data = zmesh.io.getAnimationSamplerData(sampler.output);
348-
349-
switch (sampler.interpolation) {
350-
.step => {
351-
return access(T, data, stepInterpolation(samples, t));
352-
},
353-
.linear => {
354-
const r = linearInterpolation(samples, t);
355-
const v0 = access(T, data, r.prev_i);
356-
const v1 = access(T, data, r.next_i);
357-
358-
if (T == math.Quaternion) {
359-
return T.slerp(v0, v1, r.alpha);
360-
}
361-
362-
return T.lerp(v0, v1, r.alpha);
363-
},
364-
.cubic_spline => {
365-
@panic("Cubicspline in animations not implemented!");
366-
},
367-
}
368-
}
369-
370-
/// Returns the index of the last sample less than `t`.
371-
fn stepInterpolation(samples: []const f32, t: f32) usize {
372-
std.debug.assert(samples.len > 0);
373-
const S = struct {
374-
fn lessThan(_: void, lhs: f32, rhs: f32) bool {
375-
return lhs < rhs;
376-
}
377-
};
378-
const i = std.sort.lowerBound(f32, t, samples, {}, S.lessThan);
379-
return if (i > 0) i - 1 else 0;
380-
}
381-
382-
/// Returns the indices of the samples around `t` and `alpha` to interpolate between those.
383-
fn linearInterpolation(samples: []const f32, t: f32) struct {
384-
prev_i: usize,
385-
next_i: usize,
386-
alpha: f32,
387-
} {
388-
const i = stepInterpolation(samples, t);
389-
if (i == samples.len - 1) return .{ .prev_i = i, .next_i = i, .alpha = 0 };
390-
391-
const d = samples[i + 1] - samples[i];
392-
std.debug.assert(d > 0);
393-
const alpha = std.math.clamp((t - samples[i]) / d, 0, 1);
394-
395-
return .{ .prev_i = i, .next_i = i + 1, .alpha = alpha };
396-
}
397-
398-
pub fn access(comptime T: type, data: []const f32, i: usize) T {
399-
return switch (T) {
400-
Vec3 => Vec3.new(data[3 * i + 0], data[3 * i + 1], data[3 * i + 2]),
401-
math.Quaternion => math.Quaternion.new(data[4 * i + 0], data[4 * i + 1], data[4 * i + 2], data[4 * i + 3]),
402-
math.Mat4 => math.Mat4.fromSlice(data[16 * i ..][0..16]),
403-
else => @compileError("unexpected type"),
404-
};
405-
}
406161
};
407162

408163
/// Create a mesh out of some vertex data
@@ -431,6 +186,7 @@ pub fn createSkinnedMesh(vertices: []graphics.Vertex, indices: []u32, normals: [
431186
.material = material,
432187
.bounds = boundingbox.BoundingBox.initFromVerts(vertices),
433188
.zmesh_data = data,
189+
.has_skin = true,
434190
};
435191
return m;
436192
}

0 commit comments

Comments
 (0)