overdev / raylib-py

A Python binding for the great C library raylib.
Other
184 stars 20 forks source link

Model Animation bug #47

Open nuclearshadow opened 2 weeks ago

nuclearshadow commented 2 weeks ago

Loading 3D skeletal animation or updating model with said animation doesn't seem to work properly. I have tried almost identical pieces of code for both python and the Zig binding for raylib. It works correctly for the zig version.

Here's the comparison:

Python

Screenshot

animation_python

Code

import raylibpy as rl

def main():
    width = 800
    height = 600
    rl.init_window(width, height, "Model animation test")
    rl.set_target_fps(60)

    camera = rl.Camera(
        position = rl.Vector3( x = 0, y = 5, z = 15 ),
        target = rl.Vector3( x = 0, y = 0, z = 0 ),
        up = rl.Vector3( x = 0, y = 1, z = 0 ),
        fovy = 45,
        projection = rl.CAMERA_PERSPECTIVE
    )

    modelPath = "resources/3d_models/avatar_rigged.glb"

    model = rl.load_model(modelPath)

    animCount = 0
    anims = rl.load_model_animations(modelPath, animCount)
    for anim in anims:
        print("%s" % anim.name)

    anim = anims[0]
    print(f"{anim}")

    frameCounter = 0

    while not rl.window_should_close():
        rl.begin_drawing()
        rl.clear_background(rl.RAYWHITE)

        rl.update_model_animation(model, anim, 0)
        # rl.update_model_animation(model, anim, frameCounter)
        frameCounter = (frameCounter + 1) % anim.frame_count

        rl.begin_mode3d(camera)

        rl.draw_model_ex(model, rl.vector3_zero(), rl.Vector3(1, 0, 0), 0, rl.Vector3(1, 1, 1), rl.WHITE)

        rl.end_mode3d()

        rl.end_drawing()

    model.unload()
    rl.close_window()

if __name__ == '__main__':
    main()

Zig

Screenshot

animation_zig

Code

const std = @import("std");
const rl = @import("raylib");

pub fn main() !void {
    const width = 800;
    const height = 600;
    rl.initWindow(width, height, "Model animation test");
    defer rl.closeWindow();
    rl.setTargetFPS(60);

    var camera = rl.Camera{
        .position = .{ .x = 0, .y = 5, .z = 15 },
        .target = .{ .x = 0, .y = 0, .z = 0 },
        .up = .{ .x = 0, .y = 1, .z = 0 },
        .fovy = 45,
        .projection = .camera_perspective,
    };

    const modelPath = "../resources/3d_models/avatar_rigged.glb";

    var model = rl.loadModel(modelPath);
    defer model.unload();

    const anims = try rl.loadModelAnimations(modelPath);
    for (anims) |anim| {
        std.debug.print("{s}\n", .{anim.name});
    }

    const anim = anims[0];
    std.debug.print("{any}", .{anim});

    var frameCounter: c_int = 0;

    while (!rl.windowShouldClose()) {
        rl.beginDrawing();
        rl.clearBackground(rl.Color.ray_white);

        rl.updateModelAnimation(model, anim, 0);
        // rl.updateModelAnimation(model, anim, frameCounter);
        frameCounter = @mod(frameCounter + 1, anim.frameCount);

        camera.begin();

        model.drawEx(rl.Vector3.zero(), rl.Vector3{ .x = 1, .y = 0, .z = 0 }, 0, rl.Vector3{ .x = 1, .y = 1, .z = 1 }, rl.Color.white);

        camera.end();

        rl.endDrawing();
    }
}

It shows as expected on the zig version but breaks in the python version.

Another thing is that python version seems to load 1 less frame of animation compared to zig version Here are the log messages for animations loading:

Python

INFO: MODEL: [resources/3d_models/avatar_rigged.glb] Loaded animation: ArmatureAction (3 frames, 0.066667s)
INFO: MODEL: [resources/3d_models/avatar_rigged.glb] Loaded animation: Armature|mixamo.com|Layer0 (3 frames, 0.066667s)

Zig

INFO: MODEL: [../resources/3d_models/avatar_rigged.glb] Loaded animation: ArmatureAction (4 frames, 0.066667s)
INFO: MODEL: [../resources/3d_models/avatar_rigged.glb] Loaded animation: Armature|mixamo.com|Layer0 (4 frames, 0.066667s)

And from my testing with 2 different animations, this only occurred when I used an animation with duplicate or hold keyframes in animation (in blender, don't how gltf stores animation. The animation in the screenshot only has 1 keyframe for the pose at the very beginning and another duplicate keyframe in blender) but not for an animation where every keyframe is unique (again, in blender) for the animation I downloaded from mixamo. I don't know how helpful this bit of information is.

I think the combination of 1 less frame and duplicate keyframes appears to cause this.

overdev commented 6 days ago

Hi, @nuclearshadow. Thank you for the feedback and sorry for the delayed response.

I fail to see how this bug could be on the python side of the code, but a little bit of investigation won't hurt.

I imagine as a possibility that the gltf loader had some bugfix that is present in the zig binding binaries but not in the python ones. The python binding is basically glue code and type casting back and forth, so no transformation is done to the values between c and python.

To see if this is the problem, you can do the same comparisson as above by running the "model_loading_gltf" example provided by raylib-py (and hopefully by the raylib zig binding). Also, it is good to check whether the zig binding uses in fact a more recent dll/so/dylib than the ones shipped with raylib-py.

To run the python examples provided with raylib-py, you can either put the .py file in the same directory of its corresponding .c example and run from there, or you can pass the directory path of the C example as argument in the prompt. In these ways, the resources can be loaded correctly.

In case the same bug is occurring with the raylib gltf animation example, the next step is probably to update the raylib-py package binaries and hope that this will fix the bug.