646 lines
24 KiB
C++
646 lines
24 KiB
C++
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
|
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
|
|
#include <XCEngine/Resources/Material/Material.h>
|
|
#include <XCEngine/Resources/Texture/Texture.h>
|
|
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
|
#include <XCEngine/Core/Math/Matrix4.h>
|
|
#include <assimp/Importer.hpp>
|
|
#include <assimp/material.h>
|
|
#include <assimp/pbrmaterial.h>
|
|
#include <assimp/postprocess.h>
|
|
#include <assimp/scene.h>
|
|
#include <algorithm>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
namespace XCEngine {
|
|
namespace Resources {
|
|
|
|
namespace {
|
|
|
|
struct ImportedMeshData {
|
|
std::vector<StaticMeshVertex> vertices;
|
|
std::vector<Core::uint32> indices;
|
|
VertexAttribute attributes = VertexAttribute::Position;
|
|
Math::Bounds bounds;
|
|
};
|
|
|
|
struct TextureImportContext {
|
|
const aiScene& scene;
|
|
const Containers::String& sourcePath;
|
|
std::filesystem::path sourceDirectory;
|
|
TextureLoader textureLoader;
|
|
std::unordered_map<std::string, Texture*> textureCache;
|
|
std::vector<Texture*> ownedTextures;
|
|
};
|
|
|
|
Math::Bounds ComputeBounds(const std::vector<StaticMeshVertex>& vertices) {
|
|
if (vertices.empty()) {
|
|
return Math::Bounds();
|
|
}
|
|
|
|
Math::Vector3 min = vertices.front().position;
|
|
Math::Vector3 max = vertices.front().position;
|
|
|
|
for (const StaticMeshVertex& vertex : vertices) {
|
|
min.x = std::min(min.x, vertex.position.x);
|
|
min.y = std::min(min.y, vertex.position.y);
|
|
min.z = std::min(min.z, vertex.position.z);
|
|
max.x = std::max(max.x, vertex.position.x);
|
|
max.y = std::max(max.y, vertex.position.y);
|
|
max.z = std::max(max.z, vertex.position.z);
|
|
}
|
|
|
|
Math::Bounds bounds;
|
|
bounds.SetMinMax(min, max);
|
|
return bounds;
|
|
}
|
|
|
|
Math::Matrix4 ConvertAssimpMatrix(const aiMatrix4x4& matrix) {
|
|
Math::Matrix4 result;
|
|
result.m[0][0] = matrix.a1; result.m[0][1] = matrix.a2; result.m[0][2] = matrix.a3; result.m[0][3] = matrix.a4;
|
|
result.m[1][0] = matrix.b1; result.m[1][1] = matrix.b2; result.m[1][2] = matrix.b3; result.m[1][3] = matrix.b4;
|
|
result.m[2][0] = matrix.c1; result.m[2][1] = matrix.c2; result.m[2][2] = matrix.c3; result.m[2][3] = matrix.c4;
|
|
result.m[3][0] = matrix.d1; result.m[3][1] = matrix.d2; result.m[3][2] = matrix.d3; result.m[3][3] = matrix.d4;
|
|
return result;
|
|
}
|
|
|
|
Core::uint32 BuildPostProcessFlags(const MeshImportSettings& settings) {
|
|
Core::uint32 flags = aiProcess_Triangulate |
|
|
aiProcess_JoinIdenticalVertices |
|
|
aiProcess_SortByPType |
|
|
aiProcess_ValidateDataStructure |
|
|
aiProcess_FindInvalidData;
|
|
|
|
if (settings.GetAxisConversion()) {
|
|
flags |= aiProcess_MakeLeftHanded;
|
|
flags |= aiProcess_FlipWindingOrder;
|
|
}
|
|
|
|
if (settings.HasImportFlag(MeshImportFlags::FlipUVs)) {
|
|
flags |= aiProcess_FlipUVs;
|
|
}
|
|
|
|
if (settings.HasImportFlag(MeshImportFlags::FlipWindingOrder)) {
|
|
flags |= aiProcess_FlipWindingOrder;
|
|
}
|
|
|
|
if (settings.HasImportFlag(MeshImportFlags::GenerateNormals)) {
|
|
flags |= aiProcess_GenSmoothNormals;
|
|
}
|
|
|
|
if (settings.HasImportFlag(MeshImportFlags::GenerateTangents)) {
|
|
flags |= aiProcess_CalcTangentSpace;
|
|
}
|
|
|
|
if (settings.HasImportFlag(MeshImportFlags::OptimizeMesh)) {
|
|
flags |= aiProcess_ImproveCacheLocality;
|
|
flags |= aiProcess_OptimizeMeshes;
|
|
flags |= aiProcess_OptimizeGraph;
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
Containers::String ToContainersString(const std::string& value) {
|
|
return Containers::String(value.c_str());
|
|
}
|
|
|
|
Containers::String BuildSubResourcePath(const Containers::String& sourcePath,
|
|
const char* category,
|
|
Core::uint32 index,
|
|
const Containers::String& extension = Containers::String()) {
|
|
std::string path = sourcePath.CStr();
|
|
path += "#";
|
|
path += category;
|
|
path += "[";
|
|
path += std::to_string(index);
|
|
path += "]";
|
|
if (!extension.Empty()) {
|
|
path += extension.CStr();
|
|
}
|
|
return Containers::String(path.c_str());
|
|
}
|
|
|
|
Containers::String GetResourceNameFromPath(const Containers::String& path) {
|
|
const std::filesystem::path filePath(path.CStr());
|
|
const std::string fileName = filePath.filename().string();
|
|
if (!fileName.empty()) {
|
|
return Containers::String(fileName.c_str());
|
|
}
|
|
return path;
|
|
}
|
|
|
|
Core::uint32 FindEmbeddedTextureIndex(const aiScene& scene, const aiTexture& embeddedTexture) {
|
|
for (Core::uint32 index = 0; index < scene.mNumTextures; ++index) {
|
|
if (scene.mTextures[index] == &embeddedTexture) {
|
|
return index;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Texture* CreateRawTexture(const Containers::String& texturePath,
|
|
TextureFormat format,
|
|
Core::uint32 width,
|
|
Core::uint32 height,
|
|
const void* data,
|
|
size_t dataSize) {
|
|
auto* texture = new Texture();
|
|
|
|
IResource::ConstructParams params;
|
|
params.name = GetResourceNameFromPath(texturePath);
|
|
params.path = texturePath;
|
|
params.guid = ResourceGUID::Generate(texturePath);
|
|
params.memorySize = dataSize;
|
|
texture->Initialize(params);
|
|
|
|
if (!texture->Create(width,
|
|
height,
|
|
1,
|
|
1,
|
|
TextureType::Texture2D,
|
|
format,
|
|
data,
|
|
dataSize)) {
|
|
delete texture;
|
|
return nullptr;
|
|
}
|
|
|
|
return texture;
|
|
}
|
|
|
|
Texture* LoadEmbeddedTexture(const aiTexture& embeddedTexture,
|
|
const Containers::String& texturePath,
|
|
TextureImportContext& context) {
|
|
const std::string cacheKey(texturePath.CStr());
|
|
const auto cacheIt = context.textureCache.find(cacheKey);
|
|
if (cacheIt != context.textureCache.end()) {
|
|
return cacheIt->second;
|
|
}
|
|
|
|
Texture* texture = nullptr;
|
|
if (embeddedTexture.mHeight == 0) {
|
|
LoadResult result = context.textureLoader.LoadFromMemory(texturePath,
|
|
embeddedTexture.pcData,
|
|
embeddedTexture.mWidth);
|
|
if (result) {
|
|
texture = static_cast<Texture*>(result.resource);
|
|
}
|
|
} else {
|
|
const size_t texelCount = static_cast<size_t>(embeddedTexture.mWidth) *
|
|
static_cast<size_t>(embeddedTexture.mHeight);
|
|
std::vector<Core::uint8> rgbaPixels(texelCount * 4u);
|
|
|
|
for (size_t i = 0; i < texelCount; ++i) {
|
|
const aiTexel& texel = embeddedTexture.pcData[i];
|
|
rgbaPixels[i * 4 + 0] = texel.r;
|
|
rgbaPixels[i * 4 + 1] = texel.g;
|
|
rgbaPixels[i * 4 + 2] = texel.b;
|
|
rgbaPixels[i * 4 + 3] = texel.a;
|
|
}
|
|
|
|
texture = CreateRawTexture(texturePath,
|
|
TextureFormat::RGBA8_UNORM,
|
|
embeddedTexture.mWidth,
|
|
embeddedTexture.mHeight,
|
|
rgbaPixels.data(),
|
|
rgbaPixels.size());
|
|
}
|
|
|
|
if (texture != nullptr) {
|
|
context.textureCache.emplace(cacheKey, texture);
|
|
context.ownedTextures.push_back(texture);
|
|
}
|
|
|
|
return texture;
|
|
}
|
|
|
|
Texture* LoadExternalTexture(const std::filesystem::path& textureFilePath,
|
|
TextureImportContext& context) {
|
|
const std::string normalizedPath = textureFilePath.lexically_normal().string();
|
|
const auto cacheIt = context.textureCache.find(normalizedPath);
|
|
if (cacheIt != context.textureCache.end()) {
|
|
return cacheIt->second;
|
|
}
|
|
|
|
LoadResult result = context.textureLoader.Load(Containers::String(normalizedPath.c_str()));
|
|
if (!result) {
|
|
return nullptr;
|
|
}
|
|
|
|
Texture* texture = static_cast<Texture*>(result.resource);
|
|
context.textureCache.emplace(normalizedPath, texture);
|
|
context.ownedTextures.push_back(texture);
|
|
return texture;
|
|
}
|
|
|
|
Texture* LoadTextureReference(const aiString& textureReference, TextureImportContext& context) {
|
|
if (textureReference.length == 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
const aiTexture* embeddedTexture = context.scene.GetEmbeddedTexture(textureReference.C_Str());
|
|
if (embeddedTexture != nullptr) {
|
|
const Core::uint32 embeddedTextureIndex = FindEmbeddedTextureIndex(context.scene, *embeddedTexture);
|
|
std::string extension;
|
|
if (embeddedTexture->mHeight == 0 && embeddedTexture->achFormatHint[0] != '\0') {
|
|
extension = ".";
|
|
extension += embeddedTexture->achFormatHint;
|
|
} else {
|
|
extension = ".tex";
|
|
}
|
|
|
|
const Containers::String texturePath =
|
|
BuildSubResourcePath(context.sourcePath,
|
|
"embedded_texture",
|
|
embeddedTextureIndex,
|
|
Containers::String(extension.c_str()));
|
|
return LoadEmbeddedTexture(*embeddedTexture, texturePath, context);
|
|
}
|
|
|
|
std::filesystem::path resolvedPath(textureReference.C_Str());
|
|
if (!resolvedPath.is_absolute()) {
|
|
resolvedPath = context.sourceDirectory / resolvedPath;
|
|
}
|
|
|
|
return LoadExternalTexture(resolvedPath, context);
|
|
}
|
|
|
|
Texture* LoadMaterialTexture(const aiMaterial& assimpMaterial,
|
|
std::initializer_list<aiTextureType> textureTypes,
|
|
TextureImportContext& context) {
|
|
for (aiTextureType textureType : textureTypes) {
|
|
if (assimpMaterial.GetTextureCount(textureType) == 0) {
|
|
continue;
|
|
}
|
|
|
|
aiString texturePath;
|
|
if (assimpMaterial.GetTexture(textureType, 0, &texturePath) != AI_SUCCESS) {
|
|
continue;
|
|
}
|
|
|
|
Texture* texture = LoadTextureReference(texturePath, context);
|
|
if (texture != nullptr) {
|
|
return texture;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void ImportMaterialProperties(const aiMaterial& assimpMaterial, Material& material) {
|
|
float opacity = 1.0f;
|
|
if (assimpMaterial.Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) {
|
|
material.SetFloat("opacity", opacity);
|
|
}
|
|
|
|
aiColor4D baseColor;
|
|
if (assimpMaterial.Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, baseColor) == AI_SUCCESS) {
|
|
material.SetFloat4("baseColor",
|
|
Math::Vector4(baseColor.r, baseColor.g, baseColor.b, baseColor.a));
|
|
} else {
|
|
aiColor3D diffuseColor;
|
|
if (assimpMaterial.Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor) == AI_SUCCESS) {
|
|
material.SetFloat4("baseColor",
|
|
Math::Vector4(diffuseColor.r, diffuseColor.g, diffuseColor.b, opacity));
|
|
}
|
|
}
|
|
|
|
aiColor3D emissiveColor;
|
|
if (assimpMaterial.Get(AI_MATKEY_COLOR_EMISSIVE, emissiveColor) == AI_SUCCESS) {
|
|
material.SetFloat3("emissiveColor",
|
|
Math::Vector3(emissiveColor.r, emissiveColor.g, emissiveColor.b));
|
|
}
|
|
|
|
aiColor3D specularColor;
|
|
if (assimpMaterial.Get(AI_MATKEY_COLOR_SPECULAR, specularColor) == AI_SUCCESS) {
|
|
material.SetFloat3("specularColor",
|
|
Math::Vector3(specularColor.r, specularColor.g, specularColor.b));
|
|
}
|
|
|
|
float metallic = 0.0f;
|
|
if (assimpMaterial.Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, metallic) == AI_SUCCESS) {
|
|
material.SetFloat("metallic", metallic);
|
|
}
|
|
|
|
float roughness = 0.0f;
|
|
if (assimpMaterial.Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, roughness) == AI_SUCCESS) {
|
|
material.SetFloat("roughness", roughness);
|
|
}
|
|
|
|
float shininess = 0.0f;
|
|
if (assimpMaterial.Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) {
|
|
material.SetFloat("shininess", shininess);
|
|
}
|
|
|
|
int twoSided = 0;
|
|
if (assimpMaterial.Get(AI_MATKEY_TWOSIDED, twoSided) == AI_SUCCESS) {
|
|
material.SetBool("twoSided", twoSided != 0);
|
|
}
|
|
}
|
|
|
|
void ImportMaterialTextures(const aiMaterial& assimpMaterial,
|
|
Material& material,
|
|
TextureImportContext& context) {
|
|
auto assignTexture = [&](const char* propertyName,
|
|
std::initializer_list<aiTextureType> textureTypes) {
|
|
Texture* texture = LoadMaterialTexture(assimpMaterial, textureTypes, context);
|
|
if (texture != nullptr) {
|
|
material.SetTexture(Containers::String(propertyName), ResourceHandle<Texture>(texture));
|
|
}
|
|
};
|
|
|
|
assignTexture("baseColorTexture", { aiTextureType_BASE_COLOR, aiTextureType_DIFFUSE });
|
|
assignTexture("normalTexture", { aiTextureType_NORMAL_CAMERA, aiTextureType_NORMALS, aiTextureType_HEIGHT });
|
|
assignTexture("specularTexture", { aiTextureType_SPECULAR });
|
|
assignTexture("emissiveTexture", { aiTextureType_EMISSION_COLOR, aiTextureType_EMISSIVE });
|
|
assignTexture("metallicTexture", { aiTextureType_METALNESS });
|
|
assignTexture("roughnessTexture", { aiTextureType_DIFFUSE_ROUGHNESS });
|
|
assignTexture("occlusionTexture", { aiTextureType_AMBIENT_OCCLUSION, aiTextureType_LIGHTMAP });
|
|
assignTexture("opacityTexture", { aiTextureType_OPACITY });
|
|
}
|
|
|
|
Material* ImportSingleMaterial(const aiMaterial& assimpMaterial,
|
|
const Containers::String& sourcePath,
|
|
Core::uint32 materialIndex,
|
|
TextureImportContext& context) {
|
|
aiString assimpName;
|
|
const bool hasName = assimpMaterial.Get(AI_MATKEY_NAME, assimpName) == AI_SUCCESS &&
|
|
assimpName.length > 0;
|
|
const std::string materialName = hasName
|
|
? std::string(assimpName.C_Str())
|
|
: std::string("Material_") + std::to_string(materialIndex);
|
|
|
|
auto* material = new Material();
|
|
|
|
IResource::ConstructParams params;
|
|
params.name = Containers::String(materialName.c_str());
|
|
params.path = BuildSubResourcePath(sourcePath, "material", materialIndex, Containers::String(".mat"));
|
|
params.guid = ResourceGUID::Generate(params.path);
|
|
params.memorySize = materialName.length() + params.path.Length();
|
|
material->Initialize(params);
|
|
|
|
ImportMaterialProperties(assimpMaterial, *material);
|
|
ImportMaterialTextures(assimpMaterial, *material, context);
|
|
return material;
|
|
}
|
|
|
|
ImportedMeshData ImportSingleMesh(const aiMesh& mesh,
|
|
const Math::Matrix4& worldTransform,
|
|
float globalScale,
|
|
const Math::Vector3& offset) {
|
|
ImportedMeshData result;
|
|
result.vertices.reserve(mesh.mNumVertices);
|
|
result.indices.reserve(mesh.mNumFaces * 3);
|
|
|
|
VertexAttribute attributes = VertexAttribute::Position;
|
|
if (mesh.HasNormals()) {
|
|
attributes = attributes | VertexAttribute::Normal;
|
|
}
|
|
if (mesh.HasTangentsAndBitangents()) {
|
|
attributes = attributes | VertexAttribute::Tangent | VertexAttribute::Bitangent;
|
|
}
|
|
if (mesh.HasTextureCoords(0)) {
|
|
attributes = attributes | VertexAttribute::UV0;
|
|
}
|
|
|
|
const Math::Matrix4 normalTransform = worldTransform.Inverse().Transpose();
|
|
const float appliedScale = globalScale;
|
|
|
|
for (Core::uint32 vertexIndex = 0; vertexIndex < mesh.mNumVertices; ++vertexIndex) {
|
|
StaticMeshVertex vertex;
|
|
|
|
const aiVector3D& position = mesh.mVertices[vertexIndex];
|
|
const Math::Vector3 transformedPosition = worldTransform.MultiplyPoint(Math::Vector3(position.x, position.y, position.z));
|
|
vertex.position = transformedPosition * appliedScale + offset;
|
|
|
|
if (mesh.HasNormals()) {
|
|
const aiVector3D& normal = mesh.mNormals[vertexIndex];
|
|
vertex.normal = normalTransform.MultiplyVector(Math::Vector3(normal.x, normal.y, normal.z)).Normalized();
|
|
}
|
|
|
|
if (mesh.HasTangentsAndBitangents()) {
|
|
const aiVector3D& tangent = mesh.mTangents[vertexIndex];
|
|
const aiVector3D& bitangent = mesh.mBitangents[vertexIndex];
|
|
vertex.tangent = normalTransform.MultiplyVector(Math::Vector3(tangent.x, tangent.y, tangent.z)).Normalized();
|
|
vertex.bitangent = normalTransform.MultiplyVector(Math::Vector3(bitangent.x, bitangent.y, bitangent.z)).Normalized();
|
|
}
|
|
|
|
if (mesh.HasTextureCoords(0)) {
|
|
const aiVector3D& uv = mesh.mTextureCoords[0][vertexIndex];
|
|
vertex.uv0 = Math::Vector2(uv.x, uv.y);
|
|
}
|
|
|
|
result.vertices.push_back(vertex);
|
|
}
|
|
|
|
for (Core::uint32 faceIndex = 0; faceIndex < mesh.mNumFaces; ++faceIndex) {
|
|
const aiFace& face = mesh.mFaces[faceIndex];
|
|
if (face.mNumIndices != 3) {
|
|
continue;
|
|
}
|
|
|
|
result.indices.push_back(face.mIndices[0]);
|
|
result.indices.push_back(face.mIndices[1]);
|
|
result.indices.push_back(face.mIndices[2]);
|
|
}
|
|
|
|
result.attributes = attributes;
|
|
result.bounds = ComputeBounds(result.vertices);
|
|
return result;
|
|
}
|
|
|
|
void ProcessNode(const aiNode& node,
|
|
const aiScene& scene,
|
|
const Math::Matrix4& parentTransform,
|
|
const MeshImportSettings& settings,
|
|
std::vector<StaticMeshVertex>& vertices,
|
|
std::vector<Core::uint32>& indices,
|
|
Containers::Array<MeshSection>& sections,
|
|
VertexAttribute& attributes) {
|
|
const Math::Matrix4 worldTransform = parentTransform * ConvertAssimpMatrix(node.mTransformation);
|
|
const float globalScale = settings.GetScale() * settings.GetImportScale();
|
|
|
|
for (Core::uint32 meshIndex = 0; meshIndex < node.mNumMeshes; ++meshIndex) {
|
|
const aiMesh* mesh = scene.mMeshes[node.mMeshes[meshIndex]];
|
|
if (mesh == nullptr || mesh->mNumVertices == 0) {
|
|
continue;
|
|
}
|
|
|
|
const ImportedMeshData meshData = ImportSingleMesh(*mesh, worldTransform, globalScale, settings.GetOffset());
|
|
if (meshData.vertices.empty() || meshData.indices.empty()) {
|
|
continue;
|
|
}
|
|
|
|
const Core::uint32 baseVertex = static_cast<Core::uint32>(vertices.size());
|
|
const Core::uint32 startIndex = static_cast<Core::uint32>(indices.size());
|
|
|
|
vertices.insert(vertices.end(), meshData.vertices.begin(), meshData.vertices.end());
|
|
indices.reserve(indices.size() + meshData.indices.size());
|
|
for (Core::uint32 index : meshData.indices) {
|
|
indices.push_back(baseVertex + index);
|
|
}
|
|
|
|
MeshSection section{};
|
|
section.baseVertex = baseVertex;
|
|
section.vertexCount = static_cast<Core::uint32>(meshData.vertices.size());
|
|
section.startIndex = startIndex;
|
|
section.indexCount = static_cast<Core::uint32>(meshData.indices.size());
|
|
section.materialID = mesh->mMaterialIndex;
|
|
section.bounds = meshData.bounds;
|
|
sections.PushBack(section);
|
|
|
|
attributes = attributes | meshData.attributes;
|
|
}
|
|
|
|
for (Core::uint32 childIndex = 0; childIndex < node.mNumChildren; ++childIndex) {
|
|
const aiNode* child = node.mChildren[childIndex];
|
|
if (child != nullptr) {
|
|
ProcessNode(*child, scene, worldTransform, settings, vertices, indices, sections, attributes);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
MeshLoader::MeshLoader() = default;
|
|
MeshLoader::~MeshLoader() = default;
|
|
|
|
Containers::Array<Containers::String> MeshLoader::GetSupportedExtensions() const {
|
|
Containers::Array<Containers::String> extensions;
|
|
extensions.PushBack(Containers::String("fbx"));
|
|
extensions.PushBack(Containers::String("obj"));
|
|
extensions.PushBack(Containers::String("gltf"));
|
|
extensions.PushBack(Containers::String("glb"));
|
|
extensions.PushBack(Containers::String("dae"));
|
|
extensions.PushBack(Containers::String("stl"));
|
|
return extensions;
|
|
}
|
|
|
|
bool MeshLoader::CanLoad(const Containers::String& path) const {
|
|
Containers::String ext = GetExtension(path).ToLower();
|
|
|
|
return ext == "fbx" || ext == "obj" || ext == "gltf" ||
|
|
ext == "glb" || ext == "dae" || ext == "stl";
|
|
}
|
|
|
|
LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings* settings) {
|
|
const Containers::String ext = GetExtension(path).ToLower();
|
|
|
|
if (!CanLoad(path)) {
|
|
return LoadResult(Containers::String("Unsupported mesh format: ") + ext);
|
|
}
|
|
|
|
std::ifstream file(path.CStr(), std::ios::binary);
|
|
if (!file.is_open()) {
|
|
return LoadResult(Containers::String("Failed to read file: ") + path);
|
|
}
|
|
|
|
MeshImportSettings defaultSettings;
|
|
const MeshImportSettings* resolvedSettings = dynamic_cast<const MeshImportSettings*>(settings);
|
|
if (resolvedSettings == nullptr) {
|
|
resolvedSettings = &defaultSettings;
|
|
}
|
|
|
|
Assimp::Importer importer;
|
|
const aiScene* scene = importer.ReadFile(path.CStr(), BuildPostProcessFlags(*resolvedSettings));
|
|
if (scene == nullptr || scene->mRootNode == nullptr || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) != 0) {
|
|
const char* errorText = importer.GetErrorString();
|
|
return LoadResult(Containers::String("Assimp failed to load mesh: ") +
|
|
Containers::String(errorText != nullptr ? errorText : "Unknown error"));
|
|
}
|
|
|
|
std::vector<StaticMeshVertex> vertices;
|
|
std::vector<Core::uint32> indices;
|
|
Containers::Array<MeshSection> sections;
|
|
VertexAttribute attributes = VertexAttribute::Position;
|
|
|
|
ProcessNode(*scene->mRootNode,
|
|
*scene,
|
|
Math::Matrix4::Identity(),
|
|
*resolvedSettings,
|
|
vertices,
|
|
indices,
|
|
sections,
|
|
attributes);
|
|
|
|
if (vertices.empty() || indices.empty()) {
|
|
return LoadResult(Containers::String("No triangle mesh data found in: ") + path);
|
|
}
|
|
|
|
auto* mesh = new Mesh();
|
|
|
|
IResource::ConstructParams params;
|
|
const std::string fileName = std::filesystem::path(path.CStr()).filename().string();
|
|
params.name = Containers::String(fileName.c_str());
|
|
params.path = path;
|
|
params.guid = ResourceGUID::Generate(path);
|
|
params.memorySize = 0;
|
|
mesh->Initialize(params);
|
|
|
|
mesh->SetVertexData(vertices.data(),
|
|
vertices.size() * sizeof(StaticMeshVertex),
|
|
static_cast<Core::uint32>(vertices.size()),
|
|
sizeof(StaticMeshVertex),
|
|
attributes);
|
|
|
|
const bool use32BitIndex = vertices.size() > std::numeric_limits<Core::uint16>::max();
|
|
if (use32BitIndex) {
|
|
mesh->SetIndexData(indices.data(),
|
|
indices.size() * sizeof(Core::uint32),
|
|
static_cast<Core::uint32>(indices.size()),
|
|
true);
|
|
} else {
|
|
std::vector<Core::uint16> compactIndices;
|
|
compactIndices.reserve(indices.size());
|
|
for (Core::uint32 index : indices) {
|
|
compactIndices.push_back(static_cast<Core::uint16>(index));
|
|
}
|
|
mesh->SetIndexData(compactIndices.data(),
|
|
compactIndices.size() * sizeof(Core::uint16),
|
|
static_cast<Core::uint32>(compactIndices.size()),
|
|
false);
|
|
}
|
|
|
|
for (const MeshSection& section : sections) {
|
|
mesh->AddSection(section);
|
|
}
|
|
mesh->SetBounds(ComputeBounds(vertices));
|
|
|
|
if (scene->HasMaterials() && resolvedSettings->HasImportFlag(MeshImportFlags::ImportMaterials)) {
|
|
TextureImportContext textureContext{
|
|
*scene,
|
|
path,
|
|
std::filesystem::path(path.CStr()).parent_path()
|
|
};
|
|
|
|
for (Core::uint32 materialIndex = 0; materialIndex < scene->mNumMaterials; ++materialIndex) {
|
|
const aiMaterial* assimpMaterial = scene->mMaterials[materialIndex];
|
|
mesh->AddMaterial(assimpMaterial != nullptr
|
|
? ImportSingleMaterial(*assimpMaterial, path, materialIndex, textureContext)
|
|
: nullptr);
|
|
}
|
|
|
|
for (Texture* texture : textureContext.ownedTextures) {
|
|
mesh->AddTexture(texture);
|
|
}
|
|
}
|
|
|
|
return LoadResult(mesh);
|
|
}
|
|
|
|
ImportSettings* MeshLoader::GetDefaultSettings() const {
|
|
return new MeshImportSettings();
|
|
}
|
|
|
|
REGISTER_RESOURCE_LOADER(MeshLoader);
|
|
|
|
} // namespace Resources
|
|
} // namespace XCEngine
|