Skip to content
Open
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
25 changes: 13 additions & 12 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,18 @@ mt_library(
io_file_save_options_public_headers
)

mt_library(
NAME io_json_utils
HEADERS_VARS io_json_utils_public_headers
SOURCES_VARS io_json_utils_sources
PUBLIC_LINK_LIBRARIES
character
nlohmann_json::nlohmann_json
PRIVATE_LINK_LIBRARIES
common
math
)

mt_library(
NAME io_fbx
HEADERS_VARS
Expand All @@ -488,6 +500,7 @@ mt_library(
io_skeleton
PRIVATE_LINK_LIBRARIES
io_common
io_json_utils
${io_fbx_private_link_libraries}
OpenFBX
)
Expand All @@ -496,18 +509,6 @@ if(MOMENTUM_ENABLE_FBX_SAVING)
target_compile_definitions(io_fbx PRIVATE MOMENTUM_WITH_FBX_SDK)
endif()

mt_library(
NAME io_json_utils
HEADERS_VARS io_json_utils_public_headers
SOURCES_VARS io_json_utils_sources
PUBLIC_LINK_LIBRARIES
character
nlohmann_json::nlohmann_json
PRIVATE_LINK_LIBRARIES
common
math
)

mt_library(
NAME io_gltf
HEADERS_VARS
Expand Down
2 changes: 2 additions & 0 deletions momentum/io/fbx/fbx_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ void FbxBuilder::addCharacter(const Character& character, const FileSaveOptions&
// Create skeleton hierarchy
auto skeletonResult = createSkeletonNodes(character, impl_->scene);
addMetaData(skeletonResult.rootNode, character);
addPhysicalProperties(character, skeletonResult.jointToNodeMap);

if (options.locators) {
createLocatorNodes(character, impl_->scene, skeletonResult.nodes);
Expand Down Expand Up @@ -119,6 +120,7 @@ void FbxBuilder::addRigidBody(
// Create skeleton hierarchy
auto skeletonResult = createSkeletonNodes(character, impl_->scene);
addMetaData(skeletonResult.rootNode, character);
addPhysicalProperties(character, skeletonResult.jointToNodeMap);

// Prefix skeleton joint names with charName to avoid collisions when
// multiple rigid bodies share the scene (e.g. "root" -> "controller_l:root").
Expand Down
18 changes: 10 additions & 8 deletions momentum/io/fbx/fbx_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#include "momentum/io/skeleton/parameter_transform_io.h"
#include "momentum/io/skeleton/parameters_io.h"

#include <gsl/gsl>

#include <variant>
#endif // MOMENTUM_WITH_FBX_SDK

Expand Down Expand Up @@ -47,6 +49,11 @@ void saveFbxCommon(
// initialize FBX SDK and prepare for export
// ---------------------------------------------
auto* manager = ::fbxsdk::FbxManager::Create();
// FBX SDK uses manual memory management. Use a scope guard so the manager — and the exporter,
// scene, and nodes it owns — are always destroyed, including when an exception is thrown mid-
// export (e.g. by addPhysicalProperties). Without it, a throw leaks the manager (flagged by
// ASAN).
const auto managerGuard = gsl::finally([manager]() { manager->Destroy(); });
auto* ios = ::fbxsdk::FbxIOSettings::Create(manager, IOSROOT);
manager->SetIOSettings(ios);

Expand Down Expand Up @@ -89,6 +96,7 @@ void saveFbxCommon(
// Create skeleton hierarchy
auto skeletonResult = createSkeletonNodes(character, scene);
addMetaData(skeletonResult.rootNode, character);
addPhysicalProperties(character, skeletonResult.jointToNodeMap);
createLocatorNodes(character, scene, skeletonResult.nodes);
createCollisionGeometryNodes(character, scene, skeletonResult.nodes);

Expand Down Expand Up @@ -158,15 +166,9 @@ void saveFbxCommon(
// close the fbx exporter
// ---------------------------------------------

// finally export the scene
// finally export the scene; managerGuard destroys the exporter, scene, and manager (and
// everything they own) when this function returns or throws.
lExporter->Export(scene);
lExporter->Destroy();

// destroy the scene and the manager
if (scene != nullptr) {
scene->Destroy();
}
manager->Destroy();
}

} // namespace
Expand Down
36 changes: 36 additions & 0 deletions momentum/io/fbx/fbx_io_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@

#include "momentum/character/blend_shape.h"
#include "momentum/character/character.h"
#include "momentum/character/character_utility.h"
#include "momentum/character/collision_geometry_state.h"
#include "momentum/character/marker.h"
#include "momentum/character/skin_weights.h"
#include "momentum/common/exception.h"
#include "momentum/common/log.h"
#include "momentum/io/common/json_utils.h"
#include "momentum/io/file_save_options.h"
#include "momentum/math/constants.h"
#include "momentum/math/mesh.h"
Expand All @@ -28,13 +30,16 @@
// They do the most awful things to isnan in here
#include <fbxsdk.h>
#include <fbxsdk/fileio/fbxiosettings.h>
#include <nlohmann/json.hpp>

#ifdef isnan
#undef isnan
#endif

#include <span>
#include <string>
#include <unordered_map>
#include <unordered_set>

namespace momentum::fbx_internal {

Expand Down Expand Up @@ -177,6 +182,37 @@ inline SkeletonNodeResult createSkeletonNodes(
return result;
}

inline void addPhysicalProperties(
const Character& character,
const std::unordered_map<size_t, fbxsdk::FbxNode*>& jointToNodeMap) {
std::unordered_set<size_t> seenJoints;
for (const auto& jointProperties : character.physicalProperties) {
const size_t jointIndex =
resolvePhysicalPropertiesJointIndex(jointProperties, character.skeleton);

MT_THROW_IF(
jointIndex == kInvalidIndex || jointIndex >= character.skeleton.joints.size(),
"Physical properties entry references unknown joint '{}'",
jointProperties.jointName);
MT_THROW_IF(
!seenJoints.insert(jointIndex).second,
"Multiple physical properties entries resolve to joint '{}' (index {})",
character.skeleton.joints.at(jointIndex).name,
jointIndex);

const auto nodeIt = jointToNodeMap.find(jointIndex);
MT_THROW_IF(
nodeIt == jointToNodeMap.end() || nodeIt->second == nullptr,
"Unable to find FBX node for physical properties joint '{}'",
character.skeleton.joints.at(jointIndex).name);

nlohmann::json physicalPropertiesJson;
jointPhysicalPropertiesToJson(jointProperties, physicalPropertiesJson);
::fbxsdk::FbxProperty::Create(nodeIt->second, ::fbxsdk::FbxStringDT, "physicalProperties")
.Set(FbxString(physicalPropertiesJson.dump().c_str()));
}
}

// ============================================================================
// Metadata, locators, collision geometry
// ============================================================================
Expand Down
41 changes: 41 additions & 0 deletions momentum/io/fbx/openfbx_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "momentum/common/filesystem.h"
#include "momentum/common/log.h"
#include "momentum/io/common/gsl_utils.h"
#include "momentum/io/common/json_utils.h"
#include "momentum/io/fbx/polygon_data.h"
#include "momentum/io/skeleton/locator_io.h"
#include "momentum/io/skeleton/parameter_limits_io.h"
Expand All @@ -30,6 +31,7 @@
#include <gsl/span_ext>

#include <cmath>
#include <exception>
#include <fstream>
#include <functional>
#include <numeric>
Expand Down Expand Up @@ -113,6 +115,44 @@ const char* propertyTypeStr(ofbx::IElementProperty::Type type) {
}
}

ofbx::IElement* resolveProperty(const ofbx::Object& object, const char* name);
std::string resolveStringProperty(const ofbx::Object& object, const char* name);

PhysicalProperties loadPhysicalPropertiesFromNodes(
const Skeleton& skeleton,
const std::vector<const ofbx::Object*>& jointFbxNodes) {
PhysicalProperties result;

for (size_t jointIndex = 0; jointIndex < jointFbxNodes.size(); ++jointIndex) {
const ofbx::Object* jointNode = jointFbxNodes.at(jointIndex);
if (jointNode == nullptr) {
continue;
}

// Reading the FBX property itself can throw: resolveProperty/resolveStringProperty use MT_THROW
// (std::runtime_error) when a node carries a malformed "physicalProperties" property, and the
// embedded JSON may be corrupt. Catch broadly so a single bad joint is skipped rather than
// aborting the whole character load.
try {
if (resolveProperty(*jointNode, "physicalProperties") == nullptr) {
continue;
}

const nlohmann::json physicalPropertiesJson =
nlohmann::json::parse(resolveStringProperty(*jointNode, "physicalProperties"));
result.push_back(jointPhysicalPropertiesFromJson(
physicalPropertiesJson, skeleton.joints.at(jointIndex).name, jointIndex));
} catch (const std::exception& e) {
MT_LOGW(
"Skipping physical properties for FBX joint '{}': {}",
skeleton.joints.at(jointIndex).name,
e.what());
}
}

return result;
}

template <typename T>
Eigen::Quaternion<T> computeEulerRotation(
const Eigen::Vector3<T>& angles,
Expand Down Expand Up @@ -1471,6 +1511,7 @@ std::tuple<Character, std::vector<MatrixXf>, float> loadOpenFbx(
metadata);
result.resetJointMap();
result.inverseBindPose = inverseBindPoseTransforms;
result.physicalProperties = loadPhysicalPropertiesFromNodes(result.skeleton, jointFbxNodes);

return {result, jointParamMotions, scene->getSceneFrameRate()};
}
Expand Down
Loading
Loading