Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,13 @@ else()
set(io_fbx_private_link_libraries )
endif()

# Header-only library for file save options
mt_library(
NAME io_file_save_options
HEADERS_VARS
io_file_save_options_public_headers
)

mt_library(
NAME io_fbx
HEADERS_VARS
Expand All @@ -402,6 +409,7 @@ mt_library(
io_fbx_sources
PRIVATE_LINK_LIBRARIES
io_common
io_file_save_options
io_skeleton
${io_fbx_private_link_libraries}
OpenFBX
Expand All @@ -421,6 +429,7 @@ mt_library(
PUBLIC_LINK_LIBRARIES
character
io_common
io_file_save_options
io_skeleton
fx-gltf::fx-gltf
PRIVATE_LINK_LIBRARIES
Expand Down
5 changes: 4 additions & 1 deletion cmake/build_variables.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,10 @@ io_common_test_sources = [
"test/io/common/stream_utils_test.cpp",
]

io_file_save_options_public_headers = [
"io/file_save_options.h",
]

io_skeleton_public_headers = [
"io/skeleton/locator_io.h",
"io/skeleton/mppca_io.h",
Expand Down Expand Up @@ -441,7 +445,6 @@ io_fbx_test_sources = [

io_gltf_public_headers = [
"io/gltf/gltf_builder.h",
"io/gltf/gltf_file_format.h",
"io/gltf/gltf_io.h",
]

Expand Down
2 changes: 1 addition & 1 deletion momentum/examples/animate_shapes/animate_shapes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ int main(int argc, char* argv[]) {
}

// save the result
saveCharacter(
saveGltfCharacter(
options->outFile,
character,
20.f,
Expand Down
4 changes: 2 additions & 2 deletions momentum/examples/convert_model/convert_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,15 +221,15 @@ int main(int argc, char** argv) {
} else if (oextension == ".glb" || oextension == ".gltf") {
MT_LOGI("Saving gltf/glb file...");
if (hasMotion) {
saveCharacter(
saveGltfCharacter(
options->output_model_file,
character,
fps,
{character.parameterTransform.name, poses},
{character.skeleton.getJointNames(), offsets},
markerSequence.frames);
} else {
saveCharacter(options->output_model_file, character);
saveGltfCharacter(options->output_model_file, character);
}
}
if (!options->output_locator_local.empty()) {
Expand Down
4 changes: 2 additions & 2 deletions momentum/io/character_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,9 @@ void saveCharacter(
void saveCharacter(
const filesystem::path& filename,
const Character& character,
const float fps,
std::span<const SkeletonState> skeletonStates,
const std::vector<std::vector<Marker>>& markerSequence,
const float fps) {
const std::vector<std::vector<Marker>>& markerSequence) {
// Parse format from file extension
const auto format = parseCharacterFormat(filename);
MT_THROW_IF(
Expand Down
11 changes: 6 additions & 5 deletions momentum/io/character_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <momentum/character/fwd.h>
#include <momentum/character/marker.h>
#include <momentum/common/filesystem.h>
#include <momentum/io/file_save_options.h>
#include <momentum/math/fwd.h>
#include <momentum/math/types.h>

Expand Down Expand Up @@ -68,9 +69,9 @@ enum class CharacterFormat : uint8_t {
void saveCharacter(
const filesystem::path& filename,
const Character& character,
float fps /* = 120.f*/,
const MatrixXf& motion /* = MatrixXf()*/,
const std::vector<std::vector<Marker>>& markerSequence /* = {}*/);
float fps = 120.f,
const MatrixXf& motion = MatrixXf(),
const std::vector<std::vector<Marker>>& markerSequence = {});

/// High level function to save a character with motion in skeleton states and markers to any
/// supported format.
Expand All @@ -85,7 +86,7 @@ void saveCharacter(
void saveCharacter(
const filesystem::path& filename,
const Character& character,
float fps,
std::span<const SkeletonState> skeletonStates,
const std::vector<std::vector<Marker>>& markerSequence = {},
float fps = 120.f);
const std::vector<std::vector<Marker>>& markerSequence = {});
} // namespace momentum
3 changes: 3 additions & 0 deletions momentum/io/fbx/fbx_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,9 @@ std::vector<::fbxsdk::FbxNode*> createMarkerNodes(
return markerNodes;
}

// Set the framerate for the scene
setFrameRate(scene, framerate);

// Create a root node for all markers
::fbxsdk::FbxNode* markersRootNode = ::fbxsdk::FbxNode::Create(scene, "Markers");
::fbxsdk::FbxNull* markersRootAttr = ::fbxsdk::FbxNull::Create(scene, "MarkersRootNull");
Expand Down
25 changes: 1 addition & 24 deletions momentum/io/fbx/fbx_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <momentum/character/fwd.h>
#include <momentum/character/marker.h>
#include <momentum/common/filesystem.h>
#include <momentum/io/file_save_options.h>
#include <momentum/math/types.h>

#include <span>
Expand All @@ -18,36 +19,12 @@

namespace momentum {

// UpVector Specifies which canonical axis represents up in the system
// (typically Y or Z). Maps to fbxsdk::FbxAxisSystem::EUpVector
enum class FBXUpVector { XAxis = 1, YAxis = 2, ZAxis = 3 };

// FrontVector Vector with origin at the screen pointing toward the camera.
// This is a subset of enum EUpVector because axis cannot be repeated.
// We use the system of "parity" to define this vector because its value (X,Y or
// Z axis) really depends on the up-vector. The EPreDefinedAxisSystem list the
// up-vector, parity and coordinate system values for the predefined systems.
// Maps to fbxsdk::FbxAxisSystem::EFrontVector
enum class FBXFrontVector { ParityEven = 1, ParityOdd = 2 };

// CoordSystem Specifies the third vector of the system.
// Maps to fbxsdk::FbxAxisSystem::ECoordSystem
enum class FBXCoordSystem { RightHanded, LeftHanded };

// KeepLocators Specifies whether Nulls in the transform hierarchy should be turned into Locators.
enum class KeepLocators { No, Yes };

// LoadBlendShapes Specifies whether blendshapes should be loaded or not
enum class LoadBlendShapes { No, Yes };

// A struct containing the up, front vectors and coordinate system
struct FBXCoordSystemInfo {
// Default to the same orientations as FbxAxisSystem::eMayaYUp
FBXUpVector upVector = FBXUpVector::YAxis;
FBXFrontVector frontVector = FBXFrontVector::ParityOdd;
FBXCoordSystem coordSystem = FBXCoordSystem::RightHanded;
};

// Using keepLocators means the Nulls in the transform hierarchy will be turned into Locators.
// This is different from historical momentum behavior so it's off by default.
// Permissive mode allows loading mesh-only characters (without skin weights).
Expand Down
43 changes: 29 additions & 14 deletions momentum/io/fbx/openfbx_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,7 @@ parseMarkerSequence(const ofbx::IScene* scene, const ofbx::Object* root, const f

markersRoot = findMarkersRoot(root);
if (!markersRoot) {
// Return empty sequence with no frames
return result;
}

Expand All @@ -1076,12 +1077,14 @@ parseMarkerSequence(const ofbx::IScene* scene, const ofbx::Object* root, const f
}

if (markerNodes.empty()) {
// Return empty sequence with no frames
return result;
}

// Get animation data for all marker nodes
const ofbx::AnimationStack* animStack = scene->getAnimationStack(0);
if (!animStack) {
// Return empty sequence with no frames
return result;
}

Expand All @@ -1105,6 +1108,7 @@ parseMarkerSequence(const ofbx::IScene* scene, const ofbx::Object* root, const f
}

if (markerAnimCurves.empty()) {
// Return empty sequence with no frames
return result;
}

Expand All @@ -1131,35 +1135,46 @@ parseMarkerSequence(const ofbx::IScene* scene, const ofbx::Object* root, const f
}

if (translationCurve) {
// Collect all unique keyframe times across X, Y, Z channels
// Get the individual animation curves for X, Y, Z components
const ofbx::AnimationCurve* curveX = translationCurve->getCurve(0);
const ofbx::AnimationCurve* curveY = translationCurve->getCurve(1);
const ofbx::AnimationCurve* curveZ = translationCurve->getCurve(2);

// Collect all unique keyframe times from all three curves
std::set<ofbx::i64> keyframeTimes;
for (int iChannel = 0; iChannel < 3; ++iChannel) {
const ofbx::AnimationCurve* channel = translationCurve->getCurve(iChannel);
if (channel == nullptr) {
continue;
if (curveX) {
const int keyCount = curveX->getKeyCount();
const ofbx::i64* times = curveX->getKeyTime();
for (int i = 0; i < keyCount; ++i) {
keyframeTimes.insert(times[i]);
}
const int keyCount = channel->getKeyCount();
if (keyCount <= 0) {
continue;
}
if (curveY) {
const int keyCount = curveY->getKeyCount();
const ofbx::i64* times = curveY->getKeyTime();
for (int i = 0; i < keyCount; ++i) {
keyframeTimes.insert(times[i]);
}
const ofbx::i64* times = channel->getKeyTime();
}
if (curveZ) {
const int keyCount = curveZ->getKeyCount();
const ofbx::i64* times = curveZ->getKeyTime();
for (int i = 0; i < keyCount; ++i) {
keyframeTimes.insert(times[i]);
}
}

// For each unique keyframe time, add a marker at that frame
// This matches GLTF behavior: only add markers at explicitly keyed frames
for (ofbx::i64 fbxTime : keyframeTimes) {
// Add markers only at frames where keyframes exist
for (const ofbx::i64 fbxTime : keyframeTimes) {
const double timeInSeconds = ofbx::fbxTimeToSeconds(fbxTime);
const auto frameIndex = static_cast<size_t>(std::lround(timeInSeconds * fps));
const size_t frameIndex = std::round(timeInSeconds * fps);

// Skip if frame index is out of bounds
if (frameIndex >= numFrames) {
continue;
}

// Evaluate position at this specific keyframe time
// Evaluate position at this keyframe time
const ofbx::DVec3 position = translationCurve->getNodeLocalTransform(timeInSeconds);

Marker marker;
Expand Down
113 changes: 113 additions & 0 deletions momentum/io/file_save_options.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include <string_view>

namespace momentum {

// ============================================================================
// GLTF File Format
// ============================================================================

/// File format in which the character is saved
enum class GltfFileFormat {
Extension = 0, // The file extension is used for deduction (e.g. ".gltf" --> ASCII)
GltfBinary = 1, // Binary format (generally .glb)
GltfAscii = 2, // ASCII format (generally .gltf)
};

/// Options for GLTF file export
struct GltfOptions {
/// Include GLTF extensions in the output.
bool extensions = true;
/// Include collision geometry in the output.
bool collisions = true;
/// Include locators in the output.
bool locators = true;
/// Include mesh geometry in the output.
bool mesh = true;
/// Include blend shapes in the output.
bool blendShapes = true;
};

// ============================================================================
// FBX Coordinate System Options
// =====================================================================

/// Specifies which canonical axis represents up in the system (typically Y or Z).
/// Maps to fbxsdk::FbxAxisSystem::EUpVector
enum class FBXUpVector { XAxis = 1, YAxis = 2, ZAxis = 3 };

/// Vector with origin at the screen pointing toward the camera.
/// This is a subset of enum EUpVector because axis cannot be repeated.
/// We use the system of "parity" to define this vector because its value (X,Y or
/// Z axis) really depends on the up-vector.
/// Maps to fbxsdk::FbxAxisSystem::EFrontVector
enum class FBXFrontVector { ParityEven = 1, ParityOdd = 2 };

/// Specifies the third vector of the system.
/// Maps to fbxsdk::FbxAxisSystem::ECoordSystem
enum class FBXCoordSystem { RightHanded, LeftHanded };

/// A struct containing the up, front vectors and coordinate system for FBX export.
struct FBXCoordSystemInfo {
/// Default to the same orientations as FbxAxisSystem::eMayaYUp
FBXUpVector upVector = FBXUpVector::YAxis;
FBXFrontVector frontVector = FBXFrontVector::ParityOdd;
FBXCoordSystem coordSystem = FBXCoordSystem::RightHanded;
};

// ============================================================================
// Unified File Save Options
// ============================================================================

/// Unified options for saving files in both FBX and GLTF formats.
///
/// This struct consolidates save options that were previously scattered across
/// multiple function parameters. Format-specific options (e.g., FBX coordinate
/// system, GLTF extensions) are included but only used by their respective formats.
struct FileSaveOptions {
// ---- Common Options (used by both FBX and GLTF) ----

/// Include mesh geometry in the output (default: true)
bool mesh = true;

/// Include locators in the output (default: true)
bool locators = true;

/// Include collision geometry in the output (default: true)
bool collisions = true;

/// Include blend shapes in the output (default: true)
bool blendShapes = true;

/// Permissive mode: allow saving mesh-only characters without skin weights (default: false)
bool permissive = false;

// ---- FBX-Specific Options ----

/// FBX coordinate system configuration (default: Maya Y-up)
FBXCoordSystemInfo coordSystemInfo = {};

/// Optional namespace prefix for FBX node names (e.g., "ns" becomes "ns:")
/// Only used for FBX output (default: empty = no namespace)
std::string_view fbxNamespace = "";

// ---- GLTF-Specific Options ----

/// Enable GLTF extensions (default: true)
/// Only used for GLTF output
bool extensions = true;

/// GLTF file format selection (default: Extension)
/// Only used for GLTF output
GltfFileFormat gltfFileFormat = GltfFileFormat::Extension;
};

} // namespace momentum
2 changes: 1 addition & 1 deletion momentum/io/gltf/gltf_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#include <momentum/character/marker.h>
#include <momentum/character/skeleton.h>
#include <momentum/common/filesystem.h>
#include <momentum/io/gltf/gltf_file_format.h>
#include <momentum/io/file_save_options.h>
#include <momentum/io/gltf/gltf_io.h>
#include <momentum/math/mesh.h>
#include <momentum/math/types.h>
Expand Down
Loading