diff --git a/build/scripts/common.lua b/build/scripts/common.lua index 1018890b8..d88333746 100644 --- a/build/scripts/common.lua +++ b/build/scripts/common.lua @@ -243,12 +243,17 @@ function NazaraBuild:Execute() -- Tools for k, toolTable in ipairs(self.OrderedTools) do - project("Nazara" .. toolTable.Name) + local prefix = "Nazara" + if (toolTable.Kind == "plugin") then + prefix = "Plugin" + end + + project(prefix .. toolTable.Name) location(_ACTION .. "/tools") targetdir(toolTable.Directory) - if (toolTable.Kind == "library") then + if (toolTable.Kind == "plugin" or toolTable.Kind == "library") then kind("SharedLib") elseif (toolTable.Kind == "consoleapp") then debugdir(toolTable.Directory) @@ -257,7 +262,7 @@ function NazaraBuild:Execute() debugdir(toolTable.Directory) kind("WindowedApp") else - assert(false, "wut") + assert(false, "Invalid tool Kind") end includedirs({ @@ -280,6 +285,8 @@ function NazaraBuild:Execute() libdirs("../lib/" .. makeLibDir .. "/x86") if (toolTable.Kind == "library") then targetdir("../lib/" .. makeLibDir .. "/x86") + elseif (toolTable.Kind == "plugin") then + targetdir("../plugins/" .. toolTable.Name .. "/lib/" .. makeLibDir .. "/x32") end configuration({"codeblocks or codelite or gmake", "x64"}) @@ -287,6 +294,8 @@ function NazaraBuild:Execute() libdirs("../lib/" .. makeLibDir .. "/x64") if (toolTable.Kind == "library") then targetdir("../lib/" .. makeLibDir .. "/x64") + elseif (toolTable.Kind == "plugin") then + targetdir("../plugins/" .. toolTable.Name .. "/lib/" .. makeLibDir .. "/x64") end configuration({"vs*", "x32"}) @@ -294,6 +303,8 @@ function NazaraBuild:Execute() libdirs("../lib/msvc/x86") if (toolTable.Kind == "library") then targetdir("../lib/msvc/x86") + elseif (toolTable.Kind == "plugin") then + targetdir("../plugins/" .. toolTable.Name .. "/lib/msvc/x86") end configuration({"vs*", "x64"}) @@ -301,6 +312,8 @@ function NazaraBuild:Execute() libdirs("../lib/msvc/x64") if (toolTable.Kind == "library") then targetdir("../lib/msvc/x64") + elseif (toolTable.Kind == "plugin") then + targetdir("../plugins/" .. toolTable.Name .. "/lib/msvc/x64") end configuration({"xcode3 or xcode4", "x32"}) @@ -308,6 +321,8 @@ function NazaraBuild:Execute() libdirs("../lib/xcode/x86") if (toolTable.Kind == "library") then targetdir("../lib/xcode/x86") + elseif (toolTable.Kind == "plugin") then + targetdir("../plugins/" .. toolTable.Name .. "/lib/xcode/x86") end configuration({"xcode3 or xcode4", "x64"}) @@ -315,9 +330,11 @@ function NazaraBuild:Execute() libdirs("../lib/xcode/x64") if (toolTable.Kind == "library") then targetdir("../lib/xcode/x64") + elseif (toolTable.Kind == "plugin") then + targetdir("../plugins/" .. toolTable.Name .. "/lib/xcode/x64") end - if (toolTable.Kind == "library") then + if (toolTable.Kind == "library" or toolTable.Kind == "plugin") then configuration("*Static") kind("StaticLib") @@ -719,7 +736,7 @@ function NazaraBuild:RegisterTool(toolTable) end local lowerCaseKind = toolTable.Kind:lower() - if (lowerCaseKind == "library" or lowerCaseKind == "consoleapp" or lowerCaseKind == "windowapp") then + if (lowerCaseKind == "library" or lowerCaseKind == "plugin" or lowerCaseKind == "consoleapp" or lowerCaseKind == "windowapp") then toolTable.Kind = lowerCaseKind else return false, "Invalid tool type" diff --git a/build/scripts/tools/assimp.lua b/build/scripts/tools/assimp.lua new file mode 100644 index 000000000..0eecbcd54 --- /dev/null +++ b/build/scripts/tools/assimp.lua @@ -0,0 +1,21 @@ +TOOL.Name = "Assimp" + +TOOL.Directory = "../SDK/lib" +TOOL.Kind = "Plugin" + +TOOL.Includes = { + "../include", + "../plugins/Assimp" +} + +TOOL.Files = { + "../plugins/Assimp/**.hpp", + "../plugins/Assimp/**.inl", + "../plugins/Assimp/**.cpp" +} + +TOOL.Libraries = { + "NazaraCore", + "NazaraUtility", + "assimp" +} diff --git a/plugins/Assimp/CustomStream.cpp b/plugins/Assimp/CustomStream.cpp new file mode 100644 index 000000000..9a010b0b2 --- /dev/null +++ b/plugins/Assimp/CustomStream.cpp @@ -0,0 +1,140 @@ +// Copyright (C) 2016 Jérôme Leclercq +// This file is part of the "Nazara Engine - Assimp Plugin" +// For conditions of distribution and use, see copyright notice in Plugin.cpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Nz; + +void StreamFlush(aiFile* file) +{ + Stream* stream = reinterpret_cast(file->UserData); + stream->Flush(); +} + +size_t StreamRead(aiFile* file, char* buffer, size_t size, size_t count) +{ + Stream* stream = reinterpret_cast(file->UserData); + return stream->Read(buffer, size * count); +} + +aiReturn StreamSeek(aiFile* file, size_t offset, aiOrigin origin) +{ + Stream* stream = reinterpret_cast(file->UserData); + switch (origin) + { + case aiOrigin_CUR: + return (stream->SetCursorPos(stream->GetCursorPos() + offset)) ? aiReturn_SUCCESS : aiReturn_FAILURE; + + case aiOrigin_END: + return (stream->SetCursorPos(stream->GetSize() - offset)) ? aiReturn_SUCCESS : aiReturn_FAILURE; + + case aiOrigin_SET: + return (stream->SetCursorPos(offset)) ? aiReturn_SUCCESS : aiReturn_FAILURE; + } + + NazaraWarning("Unhandled aiOrigin enum (value: 0x" + String(origin, 16) + ')'); + return aiReturn_FAILURE; +} + +size_t StreamSize(aiFile* file) +{ + Stream* stream = reinterpret_cast(file->UserData); + return static_cast(stream->GetSize()); +} + +size_t StreamTell(aiFile* file) +{ + Stream* stream = reinterpret_cast(file->UserData); + return static_cast(stream->GetCursorPos()); +} + +size_t StreamWrite(aiFile* file, const char* buffer, size_t size, size_t count) +{ + Stream* stream = reinterpret_cast(file->UserData); + return stream->Write(buffer, size * count); +} + +aiFile* StreamOpener(aiFileIO* fileIO, const char* filePath, const char* openMode) +{ + FileIOUserdata* fileIOUserdata = reinterpret_cast(fileIO->UserData); + + bool isOriginalStream = (std::strcmp(filePath, fileIOUserdata->originalFilePath) == 0); + if (!isOriginalStream && strstr(filePath, StreamPath) != 0) + return nullptr; + + aiUserData stream; + if (isOriginalStream) + stream = reinterpret_cast(fileIOUserdata->originalStream); + else + { + ErrorFlags errFlags(ErrorFlag_ThrowExceptionDisabled, true); + + ///TODO: Move to File::DecodeOpenMode + UInt32 openModeEnum = 0; + + if (std::strchr(openMode, 'r')) + { + openModeEnum |= OpenMode_ReadOnly; + if (std::strchr(openMode, '+')) + openModeEnum |= OpenMode_ReadWrite | OpenMode_MustExit; + } + else if (std::strchr(openMode, 'w')) + { + openModeEnum |= OpenMode_WriteOnly | OpenMode_Truncate; + if (std::strchr(openMode, '+')) + openModeEnum |= OpenMode_ReadOnly; + } + else if (std::strchr(openMode, 'a')) + { + openModeEnum |= OpenMode_WriteOnly | OpenMode_Append; + if (std::strchr(openMode, '+')) + openModeEnum |= OpenMode_ReadOnly; + } + else + { + NazaraError(String("Unhandled/Invalid openmode: ") + openMode + String(" for file ") + filePath); + return nullptr; + } + + if (!std::strchr(openMode, 'b')) + openModeEnum |= OpenMode_Text; + + std::unique_ptr file = std::make_unique(); + if (!file->Open(filePath, openModeEnum)) + return nullptr; + + stream = reinterpret_cast(file.release()); + } + + std::unique_ptr file = std::make_unique(); + file->FileSizeProc = StreamSize; + file->FlushProc = StreamFlush; + file->ReadProc = StreamRead; + file->SeekProc = StreamSeek; + file->TellProc = StreamTell; + file->WriteProc = StreamWrite; + file->UserData = stream; + + return file.release(); +} + +void StreamCloser(aiFileIO* fileIO, aiFile* file) +{ + FileIOUserdata* fileIOUserdata = reinterpret_cast(fileIO->UserData); + Stream* fileUserdata = reinterpret_cast(file->UserData); + + if (fileUserdata != fileIOUserdata->originalStream) + delete reinterpret_cast(file->UserData); + + delete file; +} diff --git a/plugins/Assimp/CustomStream.hpp b/plugins/Assimp/CustomStream.hpp new file mode 100644 index 000000000..9cb155e7c --- /dev/null +++ b/plugins/Assimp/CustomStream.hpp @@ -0,0 +1,31 @@ +// Copyright (C) 2016 Jérôme Leclercq +// This file is part of the "Nazara Engine - Assimp Plugin" +// For conditions of distribution and use, see copyright notice in Plugin.cpp + +#pragma once + +#ifndef NAZARA_ASSIMP_CUSTOM_STREAM_HPP +#define NAZARA_ASSIMP_CUSTOM_STREAM_HPP + +#include +#include + +constexpr const char StreamPath[] = ""; + +void StreamFlush(aiFile* file); +size_t StreamRead(aiFile* file, char* buffer, size_t size, size_t count); +aiReturn StreamSeek(aiFile* file, size_t offset, aiOrigin origin); +size_t StreamSize(aiFile* file); +size_t StreamTell(aiFile* file); +size_t StreamWrite(aiFile* file, const char* buffer, size_t size, size_t count); + +struct FileIOUserdata +{ + Nz::Stream* originalStream; + const char* originalFilePath; +}; + +aiFile* StreamOpener(aiFileIO* fileIO, const char* filePath, const char* openMode); +void StreamCloser(aiFileIO* fileIO, aiFile* file); + +#endif // NAZARA_ASSIMP_CUSTOM_STREAM_HPP \ No newline at end of file diff --git a/plugins/Assimp/Plugin.cpp b/plugins/Assimp/Plugin.cpp new file mode 100644 index 000000000..0e754ea70 --- /dev/null +++ b/plugins/Assimp/Plugin.cpp @@ -0,0 +1,250 @@ +/* +Nazara Engine - Assimp Plugin + +Copyright (C) 2015 Jérôme "Lynix" Leclercq (lynix680@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Nz; + +void ProcessJoints(aiNode* node, Skeleton* skeleton, const std::set& joints) +{ + Nz::String jointName(node->mName.data, node->mName.length); + if (joints.count(jointName)) + { + Joint* joint = skeleton->GetJoint(jointName); + if (joint) + { + if (node->mParent) + joint->SetParent(skeleton->GetJoint(node->mParent->mName.C_Str())); + + Matrix4f transformMatrix(node->mTransformation.a1, node->mTransformation.a2, node->mTransformation.a3, node->mTransformation.a4, + node->mTransformation.b1, node->mTransformation.b2, node->mTransformation.b3, node->mTransformation.b4, + node->mTransformation.c1, node->mTransformation.c2, node->mTransformation.c3, node->mTransformation.c4, + node->mTransformation.d1, node->mTransformation.d2, node->mTransformation.d3, node->mTransformation.d4); + + transformMatrix.InverseAffine(); + + joint->SetInverseBindMatrix(transformMatrix); + } + } + + for (unsigned int i = 0; i < node->mNumChildren; ++i) + ProcessJoints(node->mChildren[i], skeleton, joints); +} + +bool IsSupported(const String& extension) +{ + String dotExt = '.' + extension; + return (aiIsExtensionSupported(dotExt.GetConstBuffer()) == AI_TRUE); +} + +Ternary Check(Stream& stream, const MeshParams& parameters) +{ + bool skip; + if (parameters.custom.GetBooleanParameter("SkipAssimpLoader", &skip) && skip) + return Ternary_False; + + return Ternary_Unknown; +} + +bool Load(Mesh* mesh, Stream& stream, const MeshParams& parameters) +{ + Nz::String streamPath = stream.GetPath(); + + FileIOUserdata userdata; + userdata.originalFilePath = (!streamPath.IsEmpty()) ? streamPath.GetConstBuffer() : StreamPath; + userdata.originalStream = &stream; + + aiFileIO fileIO; + fileIO.CloseProc = StreamCloser; + fileIO.OpenProc = StreamOpener; + fileIO.UserData = reinterpret_cast(&userdata); + + unsigned int postProcess = aiProcess_CalcTangentSpace | aiProcess_JoinIdenticalVertices + | aiProcess_MakeLeftHanded | aiProcess_Triangulate + | aiProcess_RemoveComponent | aiProcess_GenSmoothNormals + | aiProcess_SplitLargeMeshes | aiProcess_LimitBoneWeights + | aiProcess_ImproveCacheLocality | aiProcess_RemoveRedundantMaterials + | aiProcess_FixInfacingNormals | aiProcess_SortByPType + | aiProcess_FindInvalidData | aiProcess_GenUVCoords + | aiProcess_TransformUVCoords | aiProcess_OptimizeMeshes + | aiProcess_OptimizeGraph | aiProcess_FlipWindingOrder + | aiProcess_Debone; + + if (!parameters.flipUVs) + postProcess |= aiProcess_FlipUVs; + + if (parameters.optimizeIndexBuffers) + postProcess |= aiProcess_ImproveCacheLocality; + + float smoothingAngle = 80.f; + parameters.custom.GetFloatParameter("AssimpLoader_SmoothingAngle", &smoothingAngle); + + int triangleLimit = 1'000'000; + parameters.custom.GetIntegerParameter("AssimpLoader_TriangleLimit", &triangleLimit); + + int vertexLimit = 1'000'000; + parameters.custom.GetIntegerParameter("AssimpLoader_VertexLimit", &vertexLimit); + + aiPropertyStore* properties = aiCreatePropertyStore(); + aiSetImportPropertyFloat(properties, AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE, smoothingAngle); + aiSetImportPropertyInteger(properties, AI_CONFIG_PP_LBW_MAX_WEIGHTS, 4); + aiSetImportPropertyInteger(properties, AI_CONFIG_PP_SBP_REMOVE, ~aiPrimitiveType_TRIANGLE); //< We only want triangles + aiSetImportPropertyInteger(properties, AI_CONFIG_PP_SLM_TRIANGLE_LIMIT, triangleLimit); + aiSetImportPropertyInteger(properties, AI_CONFIG_PP_SLM_VERTEX_LIMIT, vertexLimit); + aiSetImportPropertyInteger(properties, AI_CONFIG_PP_RVC_FLAGS, aiComponent_COLORS); + + const aiScene* scene = aiImportFileExWithProperties(userdata.originalFilePath, postProcess, &fileIO, properties); + aiReleasePropertyStore(properties); + + std::set joints; + + bool animatedMesh = false; + if (parameters.animated) + { + for (unsigned int i = 0; i < scene->mNumMeshes; ++i) + { + aiMesh* mesh = scene->mMeshes[i]; + if (mesh->HasBones()) // Inline functions can be safely called + { + animatedMesh = true; + for (unsigned int j = 0; j < mesh->mNumBones; ++j) + joints.insert(mesh->mBones[j]->mName.C_Str()); + } + } + } + + if (animatedMesh) + { + mesh->CreateSkeletal(joints.size()); + + Skeleton* skeleton = mesh->GetSkeleton(); + + // First, assign names + unsigned int jointIndex = 0; + for (const Nz::String& jointName : joints) + skeleton->GetJoint(jointIndex++)->SetName(jointName); + + ProcessJoints(scene->mRootNode, skeleton, joints); + + return false; + } + else + { + mesh->CreateStatic(); + + for (unsigned int i = 0; i < scene->mNumMeshes; ++i) + { + aiMesh* iMesh = scene->mMeshes[i]; + if (!iMesh->HasBones()) // Don't process skeletal meshs + { + unsigned int indexCount = iMesh->mNumFaces * 3; + unsigned int vertexCount = iMesh->mNumVertices; + + // Index buffer + bool largeIndices = (vertexCount > std::numeric_limits::max()); + + IndexBufferRef indexBuffer = IndexBuffer::New(largeIndices, indexCount, parameters.storage); + + IndexMapper indexMapper(indexBuffer, BufferAccess_DiscardAndWrite); + IndexIterator index = indexMapper.begin(); + + for (unsigned int j = 0; j < iMesh->mNumFaces; ++j) + { + aiFace& face = iMesh->mFaces[j]; + if (face.mNumIndices != 3) + NazaraWarning("Assimp plugin: This face is not a triangle!"); + + *index++ = face.mIndices[0]; + *index++ = face.mIndices[1]; + *index++ = face.mIndices[2]; + } + indexMapper.Unmap(); + + // Vertex buffer + VertexBufferRef vertexBuffer = VertexBuffer::New(VertexDeclaration::Get(VertexLayout_XYZ_Normal_UV_Tangent), vertexCount, parameters.storage); + BufferMapper vertexMapper(vertexBuffer, BufferAccess_WriteOnly); + + MeshVertex* vertex = static_cast(vertexMapper.GetPointer()); + for (unsigned int j = 0; j < vertexCount; ++j) + { + aiVector3D position = iMesh->mVertices[j]; + aiVector3D normal = iMesh->mNormals[j]; + aiVector3D tangent = iMesh->mTangents[j]; + aiVector3D uv = iMesh->mTextureCoords[0][j]; + + vertex->position = parameters.scale * Vector3f(position.x, position.y, position.z); + vertex->normal.Set(normal.x, normal.y, normal.z); + vertex->tangent.Set(tangent.x, tangent.y, tangent.z); + vertex->uv.Set(uv.x, uv.y); + vertex++; + } + + vertexMapper.Unmap(); + + // Submesh + StaticMeshRef subMesh = StaticMesh::New(mesh); + subMesh->Create(vertexBuffer); + + subMesh->SetIndexBuffer(indexBuffer); + subMesh->GenerateAABB(); + subMesh->SetMaterialIndex(iMesh->mMaterialIndex); + + mesh->AddSubMesh(subMesh); + } + } + + if (parameters.center) + mesh->Recenter(); + } + + aiReleaseImport(scene); + + return true; +} + +extern "C" +{ + NAZARA_EXPORT int PluginLoad() + { + Nz::MeshLoader::RegisterLoader(IsSupported, Check, Load); + return 1; + } + + NAZARA_EXPORT void PluginUnload() + { + Nz::MeshLoader::UnregisterLoader(IsSupported, Check, Load); + } +}