Last active
May 23, 2024 00:50
-
-
Save bqqbarbhg/ea4141fc835b5423d0fcc5b54945d36e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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