Skip to content

Commit

Permalink
Support FBX mesh instancing
Browse files Browse the repository at this point in the history
  • Loading branch information
shrinktofit committed Aug 24, 2023
1 parent 2db68ea commit 729e0b1
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 110 deletions.
1 change: 1 addition & 0 deletions .github/workflows/Test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ jobs:
shell: pwsh
run: |
out/build/Release/Release/FBX-glTF-conv-test.exe
out/build/Release/Release/FBX-glTF-conv-core-test.exe
18 changes: 18 additions & 0 deletions Cli/ReadCliArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ getCommandLineArgsU8(int argc_, const char *argv_[]) {

template <auto MemberPtr> struct ConvertOptionBindingTrait {};

template <>
struct ConvertOptionBindingTrait<&bee::ConvertOptions::no_mesh_instancing> {
constexpr static auto name = "no-mesh-instancing";
constexpr static auto description =
"Whether to disable mesh instancing. "
"By default, if a mesh is shared by multi nodes. They reference to the "
"same mesh.";
constexpr static auto default_value = "false";
};

template <>
struct ConvertOptionBindingTrait<&bee::ConvertOptions::export_node_visibility> {
constexpr static auto name = "export-node-visibility";
Expand Down Expand Up @@ -194,6 +204,10 @@ std::optional<CliArgs> readCliArgs(std::span<std::string_view> args_) {
"Prefer local time spans recorded in FBX file for animation "
"exporting.",
cxxopts::value<bool>()->default_value("true"));

add_cxx_option
.template operator()<&bee::ConvertOptions::no_mesh_instancing>();

options.add_options()("match-mesh-names",
"Prefer mesh names "
"exporting.",
Expand Down Expand Up @@ -316,6 +330,10 @@ std::optional<CliArgs> readCliArgs(std::span<std::string_view> args_) {
cliArgs.convertOptions.prefer_local_time_span =
cliParseResult["prefer-local-time-span"].as<bool>();
}

fetch_convert_option
.template operator()<&bee::ConvertOptions::no_mesh_instancing>();

if (cliParseResult.count("match-mesh-names")) {
cliArgs.convertOptions.match_mesh_names =
cliParseResult["match-mesh-names"].as<bool>();
Expand Down
6 changes: 6 additions & 0 deletions Cli/Test/ReadCliArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ TEST_CASE("Read CLI arguments") {
CHECK_EQ(u8toexe(convertOptions->fbmDir), "");
CHECK_EQ(convertOptions->logFile, std::nullopt);
CHECK_EQ(convertOptions->convertOptions.prefer_local_time_span, true);
CHECK_EQ(convertOptions->convertOptions.no_mesh_instancing, false);
CHECK_EQ(convertOptions->convertOptions.match_mesh_names, true);
CHECK_EQ(convertOptions->convertOptions.animationBakeRate, 0);
CHECK_EQ(convertOptions->convertOptions.animation_position_error_multiplier,
Expand Down Expand Up @@ -167,6 +168,11 @@ CHECK_EQ(u8toexe(args->convertOptions.textureResolution.locations[0]), "/a"s);
"prefer-local-time-span");
}

{ // --no-mesh-instancing
test_boolean_arg<&bee::ConvertOptions::no_mesh_instancing>(
"no-mesh-instancing");
}

{ // --match-mesh-names
test_boolean_arg<&bee::ConvertOptions::match_mesh_names>("match-mesh-names");
}
Expand Down
1 change: 1 addition & 0 deletions Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ if (NOT APPLE) # FIXME: iconv is not found on GitHub macOS release flow
target_include_directories (FBX-glTF-conv-core-test PRIVATE "${CMAKE_CURRENT_LIST_DIR}/../Polyfills/nlohmann-json")
target_include_directories (FBX-glTF-conv-core-test PRIVATE "${CMAKE_CURRENT_LIST_DIR}/../Core/fx/include")
target_link_libraries (FBX-glTF-conv-core-test PUBLIC BeeCore)
target_link_libraries(FBX-glTF-conv-core-test PRIVATE fmt::fmt fmt::fmt-header-only)
if (APPLE)
find_library (CF_FRAMEWORK CoreFoundation)
message("CoreFoundation Framework: ${CF_FRAMEWORK}")
Expand Down
56 changes: 46 additions & 10 deletions Core/Source/bee/Convert/SceneConverter.Mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <bee/Convert/ConvertError.h>
#include <bee/Convert/SceneConverter.h>
#include <bee/Convert/fbxsdk/Spreader.h>
#include <bee/Convert/fbxsdk/String.h>
#include <bee/UntypedVertex.h>
#include <fmt/format.h>

Expand Down Expand Up @@ -48,13 +49,39 @@ SceneConverter::_convertNodeMeshes(
fbxsdk::FbxNode &fbx_node_) {
assert(!fbx_meshes_.empty());

auto meshName = _getName(*fbx_meshes_.front(), fbx_node_);
auto [vertexTransform, normalTransform] = _getGeometrixTransform(fbx_node_);
auto vertexTransformX =
(vertexTransform == fbxsdk::FbxMatrix{}) ? nullptr : &vertexTransform;
auto normalTransformX =
(normalTransform == fbxsdk::FbxMatrix{}) ? nullptr : &normalTransform;

// FBX supports mesh instancing:
// See
// http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/index.html?url=files/GUID-0D483705-23D9-476D-A567-09609396B190.htm,topicNumber=d30e10223
// As the document pointed out:
// > a single instance of FbxMesh can be bound to multiple instances of
// > FbxNode...
// > ...This is called instancing.
//
// But we have more requirements on that.
// A node is consider being instancing a mesh if and only if:
// - it has only one mesh bount.
// - it does not has any geometrix transform on that.
std::optional<decltype(_meshInstanceMap)::key_type> meshInstancingKey;
if (!_options.no_mesh_instancing) {
if (fbx_meshes_.size() == 1 && (!vertexTransformX && !normalTransformX)) {
meshInstancingKey = fbx_meshes_.front();
}
}

// If the mesh could be instanced, try to use the instance previously created.
if (meshInstancingKey) {
const auto iter = _meshInstanceMap.find(*meshInstancingKey);
if (iter != _meshInstanceMap.end()) {
return iter->second;
}
}

std::optional<NodeMeshesSkinData> nodeMeshesSkinData;
nodeMeshesSkinData = _extractNodeMeshesSkinData(fbx_meshes_);

Expand All @@ -66,13 +93,18 @@ SceneConverter::_convertNodeMeshes(
std::optional<std::uint32_t> glTFSkinIndex;

fx::gltf::Mesh glTFMesh;
if (_options.match_mesh_names) {
if (meshInstancingKey) {
glTFMesh.name = fbx_string_to_utf8_checked(fbx_meshes_.front()->GetName());
} else {
auto meshName = _getName(*fbx_meshes_.front(), fbx_node_);
if (_options.match_mesh_names) {
auto it = nodeMeshMap.find(&fbx_node_);
glTFMesh.name = it == nodeMeshMap.end() ? meshName : it->second;
} else {
} else {
glTFMesh.name = meshName;
}
}

for (decltype(fbx_meshes_.size()) iFbxMesh = 0; iFbxMesh < fbx_meshes_.size();
++iFbxMesh) {
const auto fbxMesh = fbx_meshes_[iFbxMesh];
Expand All @@ -89,10 +121,10 @@ SceneConverter::_convertNodeMeshes(

MaterialUsage materialUsage;
auto glTFPrimitive = _convertMeshAsPrimitive(
*fbxMesh, meshName, vertexTransformX, normalTransformX, fbxShapes,
*fbxMesh, glTFMesh.name, vertexTransformX, normalTransformX, fbxShapes,
skinInfluenceChannels, materialUsage);

if (auto fbxMaterial = _getTheUniqueMaterial(*fbxMesh, fbx_node_)) {
if (auto fbxMaterial = _getTheUniqueMaterial(*fbxMesh)) {
if (auto glTFMaterialIndex =
_convertMaterial(*fbxMaterial, materialUsage)) {
glTFPrimitive.material = *glTFMaterialIndex;
Expand Down Expand Up @@ -129,12 +161,17 @@ SceneConverter::_convertNodeMeshes(
myMeta.meshes = fbx_meshes_;
node_meta_.meshes = myMeta;

// If the mesh could be instanced, cache the convert result.
if (meshInstancingKey) {
_meshInstanceMap.emplace(*meshInstancingKey, convertMeshResult);
}

return convertMeshResult;
}

std::string SceneConverter::_getName(fbxsdk::FbxMesh &fbx_mesh_,
fbxsdk::FbxNode &fbx_node_) {
auto meshName = std::string{fbx_mesh_.GetName()};
auto meshName = fbx_string_to_utf8_checked(fbx_mesh_.GetName());
if (!meshName.empty()) {
return meshName;
} else {
Expand All @@ -143,7 +180,7 @@ std::string SceneConverter::_getName(fbxsdk::FbxMesh &fbx_mesh_,
}

std::tuple<fbxsdk::FbxMatrix, fbxsdk::FbxMatrix>
SceneConverter::_getGeometrixTransform(fbxsdk::FbxNode &fbx_node_) {
SceneConverter::_getGeometrixTransform(const fbxsdk::FbxNode &fbx_node_) {
const auto meshTranslation = fbx_node_.GetGeometricTranslation(
fbxsdk::FbxNode::EPivotSet::eSourcePivot);
const auto meshRotation =
Expand Down Expand Up @@ -699,8 +736,7 @@ SceneConverter::_typeVertices(const FbxMeshVertexLayout &vertex_layout_) {
}

fbxsdk::FbxSurfaceMaterial *
SceneConverter::_getTheUniqueMaterial(fbxsdk::FbxMesh &fbx_mesh_,
fbxsdk::FbxNode &fbx_node_) {
SceneConverter::_getTheUniqueMaterial(fbxsdk::FbxMesh &fbx_mesh_) {
const auto nElementMaterialCount = fbx_mesh_.GetElementMaterialCount();
if (!nElementMaterialCount) {
return nullptr;
Expand Down
3 changes: 1 addition & 2 deletions Core/Source/bee/Convert/SceneConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ void SceneConverter::_traverseNodes(FbxNode *node) {
}
}


void SceneConverter::_announceNodes(const fbxsdk::FbxScene &fbx_scene_) {
auto rootNode = fbx_scene_.GetRootNode();
auto nChildren = rootNode->GetChildCount();
Expand Down Expand Up @@ -386,6 +385,6 @@ void SceneConverter::_convertNode(fbxsdk::FbxNode &fbx_node_) {
}

std::string SceneConverter::_getName(fbxsdk::FbxNode &fbx_node_) {
return fbx_node_.GetName();
return fbx_string_to_utf8_checked(fbx_node_.GetName());
}
} // namespace bee
6 changes: 3 additions & 3 deletions Core/Source/bee/Convert/SceneConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ class SceneConverter {
_textureMap;
std::unordered_map<const fbxsdk::FbxNode *, FbxNodeDumpMeta> _nodeDumpMetaMap;
std::optional<fbxsdk::FbxDouble> _unitScaleFactor = 1.0;
std::unordered_map<fbxsdk::FbxMesh*, ConvertMeshResult> _meshInstanceMap;

inline fbxsdk::FbxVector4
_applyUnitScaleFactorV3(const fbxsdk::FbxVector4 &v_) const {
Expand Down Expand Up @@ -330,7 +331,7 @@ class SceneConverter {
std::string _getName(fbxsdk::FbxMesh &fbx_mesh_, fbxsdk::FbxNode &fbx_node_);

std::tuple<fbxsdk::FbxMatrix, fbxsdk::FbxMatrix>
_getGeometrixTransform(fbxsdk::FbxNode &fbx_node_);
_getGeometrixTransform(const fbxsdk::FbxNode &fbx_node_);

fx::gltf::Primitive _convertMeshAsPrimitive(
fbxsdk::FbxMesh &fbx_mesh_,
Expand All @@ -357,8 +358,7 @@ class SceneConverter {
std::list<VertexBulk>
_typeVertices(const FbxMeshVertexLayout &vertex_layout_);

fbxsdk::FbxSurfaceMaterial *_getTheUniqueMaterial(fbxsdk::FbxMesh &fbx_mesh_,
fbxsdk::FbxNode &fbx_node_);
fbxsdk::FbxSurfaceMaterial *_getTheUniqueMaterial(fbxsdk::FbxMesh &fbx_mesh_);

/// <summary>
/// Things get even more complicated if there are more than one mesh attached to a node.
Expand Down
8 changes: 8 additions & 0 deletions Core/Source/bee/Converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ struct ConvertOptions {

bool match_mesh_names = true;

/// <summary>
/// Whether to disable mesh instancing.
/// </summary>
/// <default>
/// false
/// </default>
bool no_mesh_instancing = false;

float animation_position_error_multiplier = 1e-5f;

float animation_scale_error_multiplier = 1e-5f;
Expand Down
Loading

0 comments on commit 729e0b1

Please sign in to comment.