diff --git a/3rdParty/CMakeLists.txt b/3rdParty/CMakeLists.txt index 1a3e4e4..7e5c561 100644 --- a/3rdParty/CMakeLists.txt +++ b/3rdParty/CMakeLists.txt @@ -40,3 +40,4 @@ if(ENABLE_TEST) add_subdirectory(catch2) endif() +add_subdirectory(tinyusdz) \ No newline at end of file diff --git a/3rdParty/tinyusdz/CMakeLists.txt b/3rdParty/tinyusdz/CMakeLists.txt new file mode 100644 index 0000000..ec982ae --- /dev/null +++ b/3rdParty/tinyusdz/CMakeLists.txt @@ -0,0 +1,22 @@ +include(Utils) +include(FetchContent) + +message("-- Building TinyUSDZ") + +FetchContent_Declare( + tinyusdz + GIT_REPOSITORY https://github.com/syoyo/tinyusdz.git + GIT_TAG v0.8.0rc8 + GIT_SHALLOW TRUE + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR}/INSTALL + -DCMAKE_BUILD_TYPE:STRING=Release + -DTINYUSDZ_BUILD_TESTS:BOOL=OFF + -DTINYUSDZ_BUILD_EXAMPLES:BOOL=OFF +) +FetchContent_MakeAvailable(tinyusdz) + +function (LinkTinyUSDZ TARGET) + target_include_directories(${TARGET} PRIVATE ${tinyusdz_SOURCE_DIR}/src) + target_link_libraries(${TARGET} PRIVATE tinyusdz_static) +endfunction () diff --git a/CMakeLists.txt b/CMakeLists.txt index e33b3d8..61ae9c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,7 @@ add_subdirectory(3rdParty) # Then generate the OpenVulkano Library add_subdirectory(openVulkanoCpp) +LinkTinyUSDZ(openVulkanoCpp) if(ENABLE_TEST AND NOT IOS) enable_testing() diff --git a/openVulkanoCpp/Scene/Geometry.cpp b/openVulkanoCpp/Scene/Geometry.cpp index 3d277d3..d9cda39 100644 --- a/openVulkanoCpp/Scene/Geometry.cpp +++ b/openVulkanoCpp/Scene/Geometry.cpp @@ -5,16 +5,8 @@ */ #include "Geometry.hpp" -#include "Vertex.hpp" #include "Base/Utils.hpp" #include "Base/Logger.hpp" -#if __has_include("assimp/Importer.hpp") -#include -#include -#include -#include -#define ASSIMP_AVAILABLE -#endif #include namespace OpenVulkano::Scene @@ -126,68 +118,6 @@ namespace OpenVulkano::Scene renderGeo = nullptr; } - void Geometry::InitFromFile(const std::string& file) - { -#ifdef ASSIMP_AVAILABLE - Assimp::Importer importer; - - const uint32_t flags = aiProcess_CalcTangentSpace | aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_GenNormals | - aiProcess_ImproveCacheLocality | aiProcess_RemoveRedundantMaterials | aiProcess_GenUVCoords | aiProcess_TransformUVCoords | - aiProcess_ConvertToLeftHanded | aiProcess_PreTransformVertices | aiProcess_OptimizeGraph; - - const aiScene* scene = importer.ReadFile(file, flags); - if (!scene) throw std::runtime_error("Failed to load file \"" + file + "\" Error: " + importer.GetErrorString()); - if (!scene->HasMeshes()) throw std::runtime_error("File \"" + file + "\" does not have any meshes"); - if (scene->mNumMeshes > 1) Logger::DATA->warn("File {0} contains more than one mesh. Only first one will be loaded", file); - Init(scene->mMeshes[0]); - importer.FreeScene(); -#else - throw std::runtime_error("ASSIMP not available!"); -#endif - } - - void Geometry::Init(aiMesh* mesh) - { -#ifdef ASSIMP_AVAILABLE - aabb.Reset(); - Init(mesh->mNumVertices, mesh->mNumFaces * 3); // Reserve the space for the data - for (unsigned int i = 0; i < mesh->mNumVertices; i++) - { - vertices[i].Set(mesh->mVertices[i]); - if (mesh->HasNormals()) vertices[i].SetNormal(mesh->mNormals[i]); - if (mesh->HasTangentsAndBitangents()) - { - vertices[i].SetTangentAndBiTangent(mesh->mTangents[i], mesh->mBitangents[i]); - } - if (mesh->HasTextureCoords(0)) vertices[i].SetTextureCoordinates(mesh->mTextureCoords[0][i]); - if (mesh->HasVertexColors(0)) vertices[i].SetColor(mesh->mColors[0][i]); - aabb.Grow(vertices[i].position); - } - - for (unsigned int i = 0; i < mesh->mNumFaces; i++) - { - const aiFace face = mesh->mFaces[i]; - if (face.mNumIndices != 3) throw std::runtime_error("Mesh is not a triangle mesh!"); - for (unsigned int j = 0; j < face.mNumIndices; j++) - { - if (indexType == VertexIndexType::UINT16) - { - static_cast(indices)[i * face.mNumIndices + j] = static_cast(face.mIndices[j]); - } - else - { - static_cast(indices)[i * face.mNumIndices + j] = face.mIndices[j]; - } - } - } - - //TODO load bones - //TODO load materials -#else - throw std::runtime_error("ASSIMP not available!"); -#endif - } - void Geometry::SetIndices(const uint32_t* data, uint32_t size, uint32_t dstOffset) const { for(uint32_t i = 0; i < size; i++) diff --git a/openVulkanoCpp/Scene/Geometry.hpp b/openVulkanoCpp/Scene/Geometry.hpp index 37582c0..68dda0c 100644 --- a/openVulkanoCpp/Scene/Geometry.hpp +++ b/openVulkanoCpp/Scene/Geometry.hpp @@ -9,14 +9,11 @@ #include "Base/ICloseable.hpp" #include "Math/AABB.hpp" #include "Base/Utils.hpp" +#include "Vertex.hpp" #include -class aiMesh; - namespace OpenVulkano { - struct Vertex; - namespace Scene { enum class VertexIndexType @@ -26,6 +23,7 @@ namespace OpenVulkano class Geometry : public ICloseable { + friend class MeshLoader; public: uint32_t vertexCount = 0, indexCount = 0; Vertex* vertices = nullptr; @@ -42,15 +40,6 @@ namespace OpenVulkano Geometry& operator=(Geometry&& other) noexcept; ~Geometry(); - static Geometry* LoadFromFile(const std::string& file) - { - Geometry* mesh = new Geometry(); - mesh->InitFromFile(file); - return mesh; - } - - void InitFromFile(const std::string& file); - /** * \brief Creates the arrays for the vertices and indices. They will not be filled! * \param vertexCount The amount of vertices that will be used @@ -58,8 +47,6 @@ namespace OpenVulkano */ void Init(uint32_t vertexCount, uint32_t indexCount); - void Init(aiMesh* mesh); - void SetIndices(const uint32_t* data, uint32_t size, uint32_t dstOffset = 0) const; void Close() override; diff --git a/openVulkanoCpp/Scene/MeshLoader.cpp b/openVulkanoCpp/Scene/MeshLoader.cpp new file mode 100644 index 0000000..4cd6e9f --- /dev/null +++ b/openVulkanoCpp/Scene/MeshLoader.cpp @@ -0,0 +1,168 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include "MeshLoader.hpp" +#include "Base/Logger.hpp" +#if __has_include("assimp/Importer.hpp") +#include +#include +#include +#include +#define ASSIMP_AVAILABLE +#endif + +#include "tinyusdz.hh" +#include "tydra/attribute-eval.hh" +#include "tydra/render-data.hh" +#include "tydra/scene-access.hh" +#include "tydra/shader-network.hh" +#include "usdShade.hh" +#include "pprinter.hh" +#include "prim-pprint.hh" +#include "value-pprint.hh" +#include "value-types.hh" + +namespace +{ + static bool ends_with(std::string_view str, std::string_view suffix) + { + return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; + } +} + +namespace OpenVulkano::Scene +{ + void MeshLoader::parseAssimpFile(Geometry *geometry, const std::string& file) + { +#ifdef ASSIMP_AVAILABLE + Assimp::Importer importer; + + const uint32_t flags = aiProcess_CalcTangentSpace | aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_GenNormals | + aiProcess_ImproveCacheLocality | aiProcess_RemoveRedundantMaterials | aiProcess_GenUVCoords | aiProcess_TransformUVCoords | + aiProcess_ConvertToLeftHanded | aiProcess_PreTransformVertices | aiProcess_OptimizeGraph; + + const aiScene* scene = importer.ReadFile(file, flags); + if (!scene) throw std::runtime_error("Failed to load file \"" + file + "\" Error: " + importer.GetErrorString()); + if (!scene->HasMeshes()) throw std::runtime_error("File \"" + file + "\" does not have any meshes"); + if (scene->mNumMeshes > 1) Logger::DATA->warn("File {0} contains more than one mesh. Only first one will be loaded", file); + aiMesh *mesh = scene->mMeshes[0]; + + geometry->aabb.Reset(); + geometry->Init(mesh->mNumVertices, mesh->mNumFaces * 3); // Reserve the space for the data + for (unsigned int i = 0; i < mesh->mNumVertices; i++) + { + geometry->vertices[i].Set(mesh->mVertices[i]); + if (mesh->HasNormals()) geometry->vertices[i].SetNormal(mesh->mNormals[i]); + if (mesh->HasTangentsAndBitangents()) + { + geometry->vertices[i].SetTangentAndBiTangent(mesh->mTangents[i], mesh->mBitangents[i]); + } + if (mesh->HasTextureCoords(0)) geometry->vertices[i].SetTextureCoordinates(mesh->mTextureCoords[0][i]); + if (mesh->HasVertexColors(0)) geometry->vertices[i].SetColor(mesh->mColors[0][i]); + geometry->aabb.Grow(geometry->vertices[i].position); + } + + for (unsigned int i = 0; i < mesh->mNumFaces; i++) + { + const aiFace face = mesh->mFaces[i]; + if (face.mNumIndices != 3) throw std::runtime_error("Mesh is not a triangle mesh!"); + for (unsigned int j = 0; j < face.mNumIndices; j++) + { + if (geometry->indexType == VertexIndexType::UINT16) + { + static_cast(geometry->indices)[i * face.mNumIndices + j] = static_cast(face.mIndices[j]); + } + else + { + static_cast(geometry->indices)[i * face.mNumIndices + j] = face.mIndices[j]; + } + } + } + + //TODO load bones + //TODO load materials + importer.FreeScene(); +#else + throw std::runtime_error("ASSIMP not available!"); +#endif + } + + void MeshLoader::parseUSDFile(Geometry *geometry, const std::string& file) + { + tinyusdz::Stage stage; + std::string warning, err; + + auto result = tinyusdz::LoadUSDFromFile(file, &stage, &warning, &err); + if (!result) + { + throw std::runtime_error("Failed to load USD file: " + file); + } + + for (auto &prim : stage.root_prims()) + { + if (prim.type_name() == "Xform") + { + for (auto& child : prim.children()) + { + auto mesh = child.as(); + if (child.prim_type_name() == "Mesh" && mesh) + { + const auto& points = mesh->get_points(); + const auto& indices = mesh->get_faceVertexIndices(); + + geometry->Init(static_cast(points.size()), static_cast(indices.size())); + + for (uint32_t i = 0; i < geometry->vertexCount; ++i) + { + auto point = points[i]; + OpenVulkano::Math::Vector3f p; + p.x = point.x; + p.y = point.y; + p.z = point.z; + geometry->vertices[i].position = p; + geometry->aabb.Grow(p); + } + + if (geometry->indexType == VertexIndexType::UINT16) + { + uint16_t* indices16 = static_cast(geometry->indices); + for (uint32_t i = 0; i < geometry->indexCount; ++i) + { + indices16[i] = static_cast(indices[i]); + } + } + else + { + uint32_t* indices32 = static_cast(geometry->indices); + for (uint32_t i = 0; i < geometry->indexCount; ++i) + { + indices32[i] = indices[i]; + } + } + return; + } + } + } + } + throw std::runtime_error("No mesh found inside a xform in USD file: " + file); + } + + Geometry* MeshLoader::LoadFromFile(const std::string& file) + { + Geometry* geometry = new Geometry(); + + if (ends_with(file, ".usd") || ends_with(file, ".usda") || ends_with(file, ".usdc") || ends_with(file, ".usdz")) + { + parseUSDFile(geometry, file); + } + else + { + parseAssimpFile(geometry, file); + } + + return geometry; + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/MeshLoader.hpp b/openVulkanoCpp/Scene/MeshLoader.hpp new file mode 100644 index 0000000..efefecd --- /dev/null +++ b/openVulkanoCpp/Scene/MeshLoader.hpp @@ -0,0 +1,21 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "Scene/Geometry.hpp" +#include + +namespace OpenVulkano::Scene +{ + class MeshLoader + { + static void parseAssimpFile(Geometry *geometry, const std::string& file); + static void parseUSDFile(Geometry *geometry, const std::string& file); + public: + static Geometry* LoadFromFile(const std::string& file); + }; +} \ No newline at end of file