Skip to content

Commit

Permalink
Support for parsing multiple internal assets (#32)
Browse files Browse the repository at this point in the history
* Work done minus cleanup and adding additional tests.

* Working on discussion feedback

* Updating unit tests

* newline

* Getting unit tests to work

* Unit tests passing

* Final cleanup prior to code review

* wording

* Added test around not throwing if internal mesh is not present

* Code cleanup

* Code review feedback

* Code review feedback
  • Loading branch information
jaremieromer authored Oct 20, 2023
1 parent fd402b6 commit 6764345
Show file tree
Hide file tree
Showing 23 changed files with 314 additions and 67 deletions.
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,20 @@ You can also use a JSON manifest file to convert many files at once:
"mesh": [
{
"sourcePath": "path/to/mesh1.fbx",
"assetName": "myMesh1"
"assetNames": [
{
"subResourceName" : "mesh1head",
"assetName" : "head"
},
{
"subResourceName" : "mesh1shoulders",
"assetName" : "shoulders"
}
]
},
{
"sourcePath": "path/to/mesh2.fbx",
"assetName": "myMesh2"
"assetName": "mesh2"
}
],
"texture": [
Expand Down
8 changes: 5 additions & 3 deletions docs/SourceFileRequirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ Audio clips can be converted from any stereo or mono WAV file.
> Supported file types: .fbx, .obj
`concave-collider`, `hull-collider`, and `mesh` asset types are all generated from
geometry data, and therefore have similar requirements. Currently, only the first
mesh in an input file will be processed, and and non-geometry data in the input
file is ignored.
geometry data, and therefore have similar requirements. For `concave-collider` and `hull-collider`, only the first
mesh in an input file will be processed, and non-geometry data in the input
file is ignored. For `mesh` asset types, if nc-convert is called in single-target mode, only the first mesh in an input
file will be processed. If the manifest is provided, either the first mesh in an input file is processed, or every mesh in an
input file is processed if the sub-resource names are specified.

It is usually recommended for the input geometry to be centered around the origin.
This makes `Transform` operations within NcEngine less surprising. Additionally,
Expand Down
29 changes: 19 additions & 10 deletions source/ncconvert/Main.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include "Config.h"
#include "ReturnCodes.h"
#include "builder/BuildOrchestrator.h"
#include "utility/EnumConversion.h"
#include "utility/EnumExtensions.h"

#include "ncutility/NcError.h"

Expand All @@ -22,13 +22,13 @@ Options
-m <manifest> Perform conversions specified in <manifest>.
-i <assetPath> Print details about an existing asset file.
Asset types Supported file types
mesh fbx, obj
hull-collider fbx, obj
concave-collider fbx, obj
texture jpg, png, bmp
cube-map jpg, png, bmp
audio-clip wav
Asset types Supported file types Can produce multiple assets
mesh fbx, obj true
hull-collider fbx, obj false
concave-collider fbx, obj false
texture jpg, png, bmp false
cube-map jpg, png, bmp false
audio-clip wav false
Asset names
The provided asset name is used to construct the output file path. It may
Expand All @@ -49,11 +49,20 @@ Json Manifest
"mesh": [
{
"sourcePath": "path/to/mesh1.fbx",
"assetName": "myMesh1"
"assetNames": [
{
"subResourceName" : "mesh1head",
"assetName" : "head"
},
{
"subResourceName" : "mesh1shoulders",
"assetName" : "shoulders"
}
]
},
{
"sourcePath": "path/to/mesh2.fbx",
"assetName": "myMesh2"
"assetName": "mesh2"
}
],
"texture": [
Expand Down
2 changes: 1 addition & 1 deletion source/ncconvert/builder/BuildOrchestrator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "BuildInstructions.h"
#include "Inspect.h"
#include "Target.h"
#include "utility/EnumConversion.h"
#include "utility/EnumExtensions.h"
#include "utility/Log.h"

#include "ncasset/AssetType.h"
Expand Down
5 changes: 2 additions & 3 deletions source/ncconvert/builder/Builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "converters/AudioConverter.h"
#include "converters/GeometryConverter.h"
#include "converters/TextureConverter.h"
#include "utility/Log.h"

#include "ncasset/Assets.h"

Expand Down Expand Up @@ -58,7 +59,6 @@ auto Builder::Build(asset::AssetType type, const Target& target) -> bool
{
auto outFile = ::OpenOutFile(target.destinationPath);
const auto assetId = ::GetAssetId(target.destinationPath);

switch (type)
{
case asset::AssetType::AudioClip:
Expand Down Expand Up @@ -87,7 +87,7 @@ auto Builder::Build(asset::AssetType type, const Target& target) -> bool
}
case asset::AssetType::Mesh:
{
const auto asset = m_geometryConverter->ImportMesh(target.sourcePath);
const auto asset = m_geometryConverter->ImportMesh(target.sourcePath, target.subResourceName);
convert::Serialize(outFile, asset, assetId);
return true;
}
Expand All @@ -102,7 +102,6 @@ auto Builder::Build(asset::AssetType type, const Target& target) -> bool
return true;
}
}

throw NcError(fmt::format("Unknown AssetType: {} for {}",
static_cast<int>(type), target.sourcePath.string()
));
Expand Down
2 changes: 1 addition & 1 deletion source/ncconvert/builder/Inspect.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "Inspect.h"
#include "utility/Log.h"
#include "utility/EnumConversion.h"
#include "utility/EnumExtensions.h"

#include "ncasset/Import.h"
#include "ncutility/NcError.h"
Expand Down
43 changes: 33 additions & 10 deletions source/ncconvert/builder/Manifest.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "Manifest.h"
#include "Target.h"
#include "utility/EnumConversion.h"
#include "utility/EnumExtensions.h"
#include "utility/Log.h"
#include "utility/Path.h"

Expand Down Expand Up @@ -56,12 +56,21 @@ void ProcessOptions(GlobalManifestOptions& options, const std::filesystem::path&
}
}

auto BuildTarget(const nlohmann::json& json, const std::filesystem::path& outputDirectory) -> nc::convert::Target
auto BuildTarget(const std::string& assetName, const std::string& sourcePath, const std::filesystem::path& outputDirectory, const std::optional<std::string>& subResourceName = std::nullopt) -> nc::convert::Target
{
return nc::convert::Target{
json.at("sourcePath"),
nc::convert::AssetNameToNcaPath(json.at("assetName"), outputDirectory)
auto target = nc::convert::Target
{
sourcePath,
nc::convert::AssetNameToNcaPath(assetName, outputDirectory),
subResourceName
};

if (!std::filesystem::is_regular_file(target.sourcePath))
{
throw nc::NcError("Invalid source file: ", target.sourcePath.string());
}

return target;
}

auto IsUpToDate(const nc::convert::Target& target) -> bool
Expand Down Expand Up @@ -99,19 +108,33 @@ void ReadManifest(const std::filesystem::path& manifestPath, std::unordered_map<
const auto type = ToAssetType(typeTag);
for (const auto& asset : json.at(typeTag))
{
auto target = ::BuildTarget(asset, options.outputDirectory);
if (!std::filesystem::is_regular_file(target.sourcePath))
// Types that CanOutputMany support both single target (legacy) mode and multiple output mode.
if (CanOutputMany(type))
{
throw nc::NcError("Invalid source file: ", target.sourcePath.string());
// // Multiple output mode
if (asset.contains("assetNames"))
{
for (const auto& subResource : asset.at("assetNames"))
{
auto target = BuildTarget(subResource.at("assetName"), asset.at("sourcePath"), options.outputDirectory, subResource.at("subResourceName"));
if (::IsUpToDate(target))
{
LOG("Up-to-date: {}", target.destinationPath.string());
continue;
}
instructions.at(type).push_back(std::move(target));
}
continue;
}
}

// Single target mode
auto target = BuildTarget(asset.at("assetName"), asset.at("sourcePath"), options.outputDirectory);
if (::IsUpToDate(target))
{
LOG("Up-to-date: {}", target.destinationPath.string());
continue;
}

LOG("Adding build target: {} -> {}", target.sourcePath.string(), target.destinationPath.string());
instructions.at(type).push_back(std::move(target));
}
}
Expand Down
2 changes: 2 additions & 0 deletions source/ncconvert/builder/Target.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <filesystem>
#include <optional>

namespace nc::convert
{
Expand All @@ -9,5 +10,6 @@ struct Target
{
std::filesystem::path sourcePath;
std::filesystem::path destinationPath;
std::optional<std::string> subResourceName = std::nullopt;
};
}
59 changes: 47 additions & 12 deletions source/ncconvert/converters/GeometryConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,32 @@ auto ReadFbx(const std::filesystem::path& path, Assimp::Importer* importer, unsi
{
throw nc::NcError("Fbx contains no mesh data\n file: ", path.string());
}
else if (scene->mNumMeshes > 1)

return scene;
}

auto GetMeshFromScene(const aiScene* scene, const std::optional<std::string>& subResourceName = std::nullopt) -> aiMesh*
{
aiMesh* mesh = nullptr;

NC_ASSERT(scene->mNumMeshes != 0, "No meshes found in scene.");

if (!subResourceName.has_value())
{
LOG("Fbx {} has {} meshes. Only the first will be parsed.", path.string(), scene->mNumMeshes);
return scene->mMeshes[0];
}

if (scene->mMeshes[0]->mNumVertices == 0)
for (auto* sceneMesh : std::span(scene->mMeshes, scene->mNumMeshes))
{
throw nc::NcError("No vertices found for: ", path.string());
if (std::string{sceneMesh->mName.C_Str()} == subResourceName)
{
mesh = sceneMesh;
break;
}
}
if (mesh == nullptr) throw nc::NcError("A sub-resource name was provided but no mesh was found by that name: {}. No asset will be created.", subResourceName.value());

return scene;
return mesh;
}

auto ToVector3(const aiVector3D& in) -> nc::Vector3
Expand Down Expand Up @@ -300,6 +315,12 @@ class GeometryConverter::impl
auto ImportConcaveCollider(const std::filesystem::path& path) -> asset::ConcaveCollider
{
const auto mesh = ::ReadFbx(path, &m_importer, concaveColliderFlags)->mMeshes[0];

if (mesh->mNumVertices == 0)
{
throw nc::NcError("No vertices found for: ", path.string());
}

auto triangles = ::ConvertToTriangles(::ViewFaces(mesh), ::ViewVertices(mesh));
if(auto count = Sanitize(triangles))
{
Expand All @@ -316,6 +337,12 @@ class GeometryConverter::impl
auto ImportHullCollider(const std::filesystem::path& path) -> asset::HullCollider
{
const auto mesh = ::ReadFbx(path, &m_importer, hullColliderFlags)->mMeshes[0];

if (mesh->mNumVertices == 0)
{
throw nc::NcError("No vertices found for: ", path.string());
}

auto convertedVertices = ::ConvertToVertices(::ViewVertices(mesh));
if(auto count = Sanitize(convertedVertices))
{
Expand All @@ -329,10 +356,17 @@ class GeometryConverter::impl
};
}

auto ImportMesh(const std::filesystem::path& path) -> asset::Mesh
auto ImportMesh(const std::filesystem::path& path, const std::optional<std::string>& subResourceName) -> asset::Mesh
{
const auto scene = ::ReadFbx(path, &m_importer, meshFlags);
auto convertedVertices = ::ConvertToMeshVertices(scene->mMeshes[0]);
auto mesh = GetMeshFromScene(scene, subResourceName);

if (mesh->mNumVertices == 0)
{
throw nc::NcError("No vertices found for: ", path.string());
}

auto convertedVertices = ::ConvertToMeshVertices(mesh);
if(auto count = Sanitize(convertedVertices))
{
LOG("Warning: Bad values detected in mesh. {} values have been set to 0.", count);
Expand All @@ -342,8 +376,8 @@ class GeometryConverter::impl
GetMeshVertexExtents(convertedVertices),
FindFurthestDistanceFromOrigin(convertedVertices),
std::move(convertedVertices),
::ConvertToIndices(::ViewFaces(scene->mMeshes[0])),
GetBonesData(scene->mMeshes[0], scene->mRootNode)
::ConvertToIndices(::ViewFaces(mesh)),
GetBonesData(mesh, scene->mRootNode)
};
}

Expand All @@ -368,8 +402,9 @@ auto GeometryConverter::ImportHullCollider(const std::filesystem::path& path) ->
return m_impl->ImportHullCollider(path);
}

auto GeometryConverter::ImportMesh(const std::filesystem::path& path) -> asset::Mesh
auto GeometryConverter::ImportMesh(const std::filesystem::path& path, const std::optional<std::string>& subResourceName) -> asset::Mesh
{
return m_impl->ImportMesh(path);
return m_impl->ImportMesh(path, subResourceName);
}

} // namespace nc::convert
6 changes: 4 additions & 2 deletions source/ncconvert/converters/GeometryConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include <filesystem>
#include <memory>
#include <optional>
#include <vector>

namespace nc::convert
{
Expand All @@ -19,8 +21,8 @@ class GeometryConverter
/** Process an fbx file as geometry for a hull collider. */
auto ImportHullCollider(const std::filesystem::path& path) -> asset::HullCollider;

/** Proecess an fbx file as geometry for a mesh renderer. */
auto ImportMesh(const std::filesystem::path& path) -> asset::Mesh;
/** Process an fbx file as geometry for a mesh renderer. Supply a subResourceName of the mesh to extract if there are multiple meshes in the fbx file. */
auto ImportMesh(const std::filesystem::path& path, const std::optional<std::string>& subResourceName = std::nullopt) -> asset::Mesh;

private:
class impl;
Expand Down
2 changes: 1 addition & 1 deletion source/ncconvert/utility/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
target_sources(nc-convert
PRIVATE
${PROJECT_SOURCE_DIR}/source/ncconvert/utility/BlobSize.cpp
${PROJECT_SOURCE_DIR}/source/ncconvert/utility/EnumConversion.cpp
${PROJECT_SOURCE_DIR}/source/ncconvert/utility/EnumExtensions.cpp
${PROJECT_SOURCE_DIR}/source/ncconvert/utility/Path.cpp
)
Loading

0 comments on commit 6764345

Please sign in to comment.