Skip to content

Instantly share code, notes, and snippets.

@bqqbarbhg
Last active May 23, 2024 00:50
Show Gist options
  • Save bqqbarbhg/ea4141fc835b5423d0fcc5b54945d36e to your computer and use it in GitHub Desktop.
Save bqqbarbhg/ea4141fc835b5423d0fcc5b54945d36e to your computer and use it in GitHub Desktop.
#define _CRT_SECURE_NO_WARNINGS
#include "ufbx.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
void print_indent(FILE *f, int indent)
{
for (int i = 0; i < indent; i++) {
fputc('\t', f);
}
}
void print_joints(FILE *f, ufbx_node *node, int indent)
{
ufbx_vec3 offset = node->local_transform.translation;
// Print the name of the node, the top-level node is called ROOT
print_indent(f, indent);
if (indent == 0) {
// Allow passing `ufbx_scene.root_node` here, which has no name.
fprintf(f, "ROOT %s\n", node->name.length > 0 ? node->name.data : "Root");
} else {
fprintf(f, "JOINT %s\n", node->name.data);
}
print_indent(f, indent);
fprintf(f, "{\n");
// Bone offset
print_indent(f, indent + 1);
fprintf(f, "OFFSET %f %f %f\n", offset.x, offset.y, offset.z);
// Hardcoded channels
print_indent(f, indent + 1);
fprintf(f, "CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation\n");
if (node->children.count > 0) {
// Print children within self if they exist
for (size_t i = 0; i < node->children.count; i++) {
print_joints(f, node->children.data[i], indent + 1);
}
} else {
// Print "End Site" block if we're the last node.
// NOTE: We do not have real End Sites here, in practice many exporters do export bone tips,
// but we don't rely on that here for simplicity.
print_indent(f, indent + 1);
fprintf(f, "End Site\n");
print_indent(f, indent + 1);
fprintf(f, "{\n");
print_indent(f, indent + 2);
fprintf(f, "OFFSET 0.0 0.0 0.0\n");
print_indent(f, indent + 1);
fprintf(f, "}\n");
}
print_indent(f, indent);
fprintf(f, "}\n");
}
void print_motion(FILE *f, ufbx_anim *anim, ufbx_node *node, double time)
{
ufbx_transform transform = ufbx_evaluate_transform(anim, node, time);
// BVH uses the YXZ rotation order: `vR = vYXZ`
ufbx_vec3 euler = ufbx_quat_to_euler(transform.rotation, UFBX_ROTATION_ORDER_YXZ);
ufbx_vec3 offset = transform.translation;
fprintf(f, "%f %f %f ", offset.x, offset.y, offset.z);
fprintf(f, "%f %f %f ", euler.z, euler.x, euler.y);
// The motion data must be in the same order as the skeleton hierarchy,
// so recurse here in the same order as in `print_joints()`.
for (size_t i = 0; i < node->children.count; i++) {
print_motion(f, anim, node->children.data[i], time);
}
}
int main(int argc, char **argv)
{
const char *input_file = NULL, *output_file = NULL, *root_name = NULL, *anim_name = NULL;
double sample_fps = 30.0;
bool help = false;
// Parse options
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-o")) {
if (++i < argc) output_file = argv[i];
} else if (!strcmp(argv[i], "--root")) {
if (++i < argc) root_name = argv[i];
} else if (!strcmp(argv[i], "--anim")) {
if (++i < argc) anim_name = argv[i];
} else if (!strcmp(argv[i], "--fps")) {
if (++i < argc) sample_fps = strtod(argv[i], NULL);
} else if (!strcmp(argv[i], "--help")) {
help = true;
} else {
input_file = argv[i];
}
}
if (help || argc == 1) {
fprintf(stderr, "Usage: fbx2bvh input.fbx -o output.bvh\n"
"\nOptional settings:\n"
" -o <filename> Output filename (default: stdout)\n"
" --root <name> Specify the name of the root node\n"
" --anim <name> Specify the name of the animation\n"
" --fps <number> Specify the sample rate\n"
);
return 0;
}
if (input_file == NULL) {
fprintf(stderr, "Error: No input file specified\n");
return 1;
}
ufbx_load_opts opts = { 0 };
// We can ignore all geometry and embedded images in the file as we care only about animation.
opts.ignore_geometry = true;
opts.ignore_embedded = true;
// Convert the scene to Y-up and resolve pivots.
opts.target_axes = ufbx_axes_right_handed_y_up;
opts.space_conversion = UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY;
opts.pivot_handling = UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT;
ufbx_error error;
ufbx_scene *scene = ufbx_load_file(input_file, &opts, &error);
if (!scene) {
char error_buf[512];
ufbx_format_error(error_buf, sizeof(error_buf), &error);
fprintf(stderr, "Failed to load input file: %s\n%s\n", input_file, error_buf);
return 1;
}
// Use the root of the scene if the user did not provide '--root'
ufbx_node *root_node = scene->root_node;
if (root_name) {
root_node = ufbx_find_node(scene, root_name);
if (!root_node) {
fprintf(stderr, "Failed to find root node: %s\n", root_name);
return 1;
}
}
FILE *f = output_file ? fopen(output_file, "w") : stdout;
if (!f) {
fprintf(stderr, "Failed to open output file: %s\n", output_file);
return 1;
}
// Print the HIERARCHY section
fprintf(f, "HIERARCHY\n");
print_joints(f, root_node, 0);
// Find the animation to use
ufbx_anim *anim = scene->anim;
if (anim_name) {
ufbx_anim_stack *stack = ufbx_find_anim_stack(scene, anim_name);
if (!stack) {
fprintf(stderr, "Failed to find animation: %s\n", anim_name);
return 1;
}
anim = stack->anim;
}
// Print the MOTION section
double begin = anim->time_begin;
double duration = anim->time_end - begin;
double frame_time = 1.0 / sample_fps;
int frames = (int)ceil(duration * sample_fps);
fprintf(f, "MOTION\n");
fprintf(f, "Frames: %d\n", frames);
fprintf(f, "Frame Time: %f\n", frame_time);
for (int i = 0; i < frames; i++) {
double time = begin + (double)i * frame_time;
print_motion(f, anim, root_node, time);
fprintf(f, "\n");
}
if (f != stdout) {
fclose(f);
}
ufbx_free_scene(scene);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment