diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..3fd6967c7
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,19 @@
+; Top-most EditorConfig file
+root = true
+
+; 4-column tab indentation
+[*.cpp]
+indent_style = tab
+indent_size = 4
+
+[*.c]
+indent_style = tab
+indent_size = 4
+
+[*.h]
+indent_style = tab
+indent_size = 4
+
+[*.py]
+indent_style = tab
+indent_size = 4
diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt
index 22686bba0..a1f3df8aa 100644
--- a/code/CMakeLists.txt
+++ b/code/CMakeLists.txt
@@ -147,6 +147,10 @@ IF(WITH_QT)
FIND_PACKAGE(Qt4 COMPONENTS QtCore QtGui QtXml QtOpenGL REQUIRED)
ENDIF(WITH_QT)
+IF(WITH_ASSIMP)
+ FIND_PACKAGE(assimp REQUIRED)
+ENDIF(WITH_ASSIMP)
+
IF(WITH_NEL)
IF(WITH_NEL_TESTS)
FIND_PACKAGE(CppTest)
diff --git a/code/CMakeModules/Findassimp.cmake b/code/CMakeModules/Findassimp.cmake
new file mode 100644
index 000000000..6748cd221
--- /dev/null
+++ b/code/CMakeModules/Findassimp.cmake
@@ -0,0 +1,25 @@
+FIND_PATH(
+ assimp_INCLUDE_DIRS
+ NAMES assimp/postprocess.h assimp/scene.h assimp/version.h assimp/config.h assimp/cimport.h
+ PATHS /usr/local/include/
+)
+
+FIND_LIBRARY(
+ assimp_LIBRARIES
+ NAMES assimp
+ PATHS /usr/local/lib/
+)
+
+IF (assimp_INCLUDE_DIRS AND assimp_LIBRARIES)
+ SET(assimp_FOUND TRUE)
+ENDIF (assimp_INCLUDE_DIRS AND assimp_LIBRARIES)
+
+IF (assimp_FOUND)
+ IF (NOT assimp_FIND_QUIETLY)
+ MESSAGE(STATUS "Found asset importer library: ${assimp_LIBRARIES}")
+ ENDIF (NOT assimp_FIND_QUIETLY)
+ELSE (assimp_FOUND)
+ IF (assimp_FIND_REQUIRED)
+ MESSAGE(FATAL_ERROR "Could not find asset importer library")
+ ENDIF (assimp_FIND_REQUIRED)
+ENDIF (assimp_FOUND)
diff --git a/code/CMakeModules/nel.cmake b/code/CMakeModules/nel.cmake
index d02db8bd5..802458ca3 100644
--- a/code/CMakeModules/nel.cmake
+++ b/code/CMakeModules/nel.cmake
@@ -274,6 +274,8 @@ MACRO(NL_SETUP_DEFAULT_OPTIONS)
OPTION(WITH_STATIC_EXTERNAL "With static external libraries" OFF)
OPTION(WITH_INSTALL_LIBRARIES "Install development files." ON )
+ OPTION(WITH_ASSIMP "Use assimp exporter" OFF)
+
###
# GUI toolkits
###
diff --git a/code/nel/include/nel/misc/tool_logger.h b/code/nel/include/nel/misc/tool_logger.h
new file mode 100644
index 000000000..3ef547045
--- /dev/null
+++ b/code/nel/include/nel/misc/tool_logger.h
@@ -0,0 +1,210 @@
+/**
+ * \file tool_logger.h
+ * \brief CToolLogger
+ * \date 2012-02-19 10:33GMT
+ * \author Jan Boon (Kaetemi)
+ * Tool logger is fully implemented in header so small tools do not
+ * need to link to this library unnecessarily.
+ * NOTE: Needs to be changed not to use time_nl and string_common.
+ */
+
+/*
+ * Copyright (C) 2012 by authors
+ *
+ * This file is part of RYZOM CORE PIPELINE.
+ * RYZOM CORE PIPELINE is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * RYZOM CORE PIPELINE is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with RYZOM CORE PIPELINE; see the file COPYING. If not, see
+ * .
+ */
+
+#ifndef NLMISC_TOOL_LOGGER_H
+#define NLMISC_TOOL_LOGGER_H
+#include
+
+// STL includes
+#include
+#include
+#include
+
+// NeL includes
+#include
+#include
+
+// Project includes
+
+#ifdef ERROR
+#undef ERROR
+#endif
+
+#ifdef NL_DEBUG_H
+#define tlerror(toolLogger, path, error, ...) nlwarning(error, __VA_ARGS__), toolLogger.writeError(NLMISC::ERROR, path, error, __VA_ARGS__)
+#define tlwarning(toolLogger, path, error, ...) nlwarning(error, __VA_ARGS__), toolLogger.writeError(NLMISC::WARNING, path, error, __VA_ARGS__)
+#define tlmessage(toolLogger, path, error, ...) nlinfo(error, __VA_ARGS__), toolLogger.writeError(NLMISC::MESSAGE, path, error, __VA_ARGS__)
+#else
+#define tlerror(toolLogger, path, error, ...) toolLogger.writeError(NLMISC::ERROR, path, error, __VA_ARGS__)
+#define tlwarning(toolLogger, path, error, ...) toolLogger.writeError(NLMISC::WARNING, path, error, __VA_ARGS__)
+#define tlmessage(toolLogger, path, error, ...) toolLogger.writeError(NLMISC::MESSAGE, path, error, __VA_ARGS__)
+#endif
+
+namespace NLMISC {
+
+enum TError
+{
+ ERROR,
+ WARNING,
+ MESSAGE,
+};
+
+enum TDepend
+{
+ BUILD,
+ DIRECTORY,
+ RUNTIME,
+};
+
+const std::string s_ErrorHeader = "type\tpath\ttime\terror";
+const std::string s_DependHeader = "type\toutput_file\tinput_file";
+
+/**
+ * \brief CToolLogger
+ * \date 2012-02-19 10:33GMT
+ * \author Jan Boon (Kaetemi)
+ * CToolLogger
+ */
+class CToolLogger
+{
+private:
+ FILE *m_ErrorLog;
+ FILE *m_DependLog;
+
+public:
+ inline CToolLogger() : m_ErrorLog(NULL), m_DependLog(NULL)
+ {
+
+ }
+
+ inline ~CToolLogger()
+ {
+ release();
+ }
+
+ inline void initError(const std::string &errorLog)
+ {
+ releaseError();
+
+ m_ErrorLog = fopen(errorLog.c_str(), "wt");
+ fwrite(s_ErrorHeader.c_str(), 1, s_ErrorHeader.length(), m_ErrorLog);
+ fwrite("\n", 1, 1, m_ErrorLog);
+ fflush(m_ErrorLog);
+
+ }
+
+ inline void initDepend(const std::string &dependLog)
+ {
+ releaseDepend();
+
+ m_DependLog = fopen(dependLog.c_str(), "wt");
+ fwrite(s_DependHeader.c_str(), 1, s_DependHeader.length(), m_DependLog);
+ fwrite("\n", 1, 1, m_DependLog);
+ // fflush(m_DependLog);
+ }
+
+ inline void writeError(TError type, const char *path, const char *error, ...)
+ {
+ if (m_ErrorLog)
+ {
+ switch (type)
+ {
+ case ERROR:
+ fwrite("ERROR", 1, 5, m_ErrorLog);
+ break;
+ case WARNING:
+ fwrite("WARNING", 1, 7, m_ErrorLog);
+ break;
+ case MESSAGE:
+ fwrite("MESSAGE", 1, 7, m_ErrorLog);
+ break;
+ }
+ fwrite("\t", 1, 1, m_ErrorLog);
+ fprintf(m_ErrorLog, "%s", path);
+ fwrite("\t", 1, 1, m_ErrorLog);
+ std::string time = NLMISC::toString(NLMISC::CTime::getSecondsSince1970());
+ fwrite(time.c_str(), 1, time.length(), m_ErrorLog);
+ fwrite("\t", 1, 1, m_ErrorLog);
+ va_list args;
+ va_start(args, error);
+ vfprintf(m_ErrorLog, error, args);
+ va_end(args);
+ fwrite("\n", 1, 1, m_ErrorLog);
+ fflush(m_ErrorLog);
+ }
+ }
+
+ /// inputFile can only be file. [? May be not-yet-existing file for expected input for future build runs. ?] Directories are handled on process level. [? You should call this before calling writeError on inputFile, so the error is also linked from the outputFile. ?]
+ inline void writeDepend(TDepend type, const char *outputFile, const char *inputFile)
+ {
+ if (m_DependLog)
+ {
+ switch (type)
+ {
+ case BUILD:
+ fwrite("BUILD", 1, 5, m_DependLog);
+ break;
+ case DIRECTORY:
+ fwrite("DIRECTORY", 1, 9, m_DependLog);
+ break;
+ case RUNTIME:
+ fwrite("RUNTIME", 1, 7, m_DependLog);
+ break;
+ }
+ fwrite("\t", 1, 1, m_DependLog);
+ fprintf(m_DependLog, "%s", outputFile);
+ fwrite("\t", 1, 1, m_DependLog);
+ fprintf(m_DependLog, "%s", inputFile);
+ fwrite("\n", 1, 1, m_DependLog);
+ // fflush(m_DependLog);
+ }
+ }
+
+ inline void releaseError()
+ {
+ if (m_ErrorLog)
+ {
+ fflush(m_ErrorLog);
+ fclose(m_ErrorLog);
+ m_ErrorLog = NULL;
+ }
+ }
+
+ inline void releaseDepend()
+ {
+ if (m_DependLog)
+ {
+ fflush(m_DependLog);
+ fclose(m_DependLog);
+ m_DependLog = NULL;
+ }
+ }
+
+ inline void release()
+ {
+ releaseError();
+ releaseDepend();
+ }
+}; /* class CToolLogger */
+
+} /* namespace NLMISC */
+
+#endif /* #ifndef NLMISC_TOOL_LOGGER_H */
+
+/* end of file */
diff --git a/code/nel/src/misc/tool_logger.cpp b/code/nel/src/misc/tool_logger.cpp
new file mode 100644
index 000000000..e6a9fbf36
--- /dev/null
+++ b/code/nel/src/misc/tool_logger.cpp
@@ -0,0 +1,45 @@
+/**
+ * \file tool_logger.cpp
+ * \brief CToolLogger
+ * \date 2012-02-19 10:33GMT
+ * \author Jan Boon (Kaetemi)
+ * CToolLogger
+ */
+
+/*
+ * Copyright (C) 2012 by authors
+ *
+ * This file is part of RYZOM CORE PIPELINE.
+ * RYZOM CORE PIPELINE is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * RYZOM CORE PIPELINE is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with RYZOM CORE PIPELINE; see the file COPYING. If not, see
+ * .
+ */
+
+#include "stdmisc.h"
+#include "nel/misc/tool_logger.h"
+
+// STL includes
+
+// NeL includes
+// #include
+
+// Project includes
+
+namespace NLMISC {
+
+// Tool logger is fully implemented in header so small tools do not need to link to this library unnecessarily.
+void dummy_tool_logger_cpp() { }
+
+} /* namespace NLMISC */
+
+/* end of file */
diff --git a/code/nel/tools/3d/CMakeLists.txt b/code/nel/tools/3d/CMakeLists.txt
index cb709ffaa..37773e9ee 100644
--- a/code/nel/tools/3d/CMakeLists.txt
+++ b/code/nel/tools/3d/CMakeLists.txt
@@ -1,6 +1,11 @@
IF(WITH_NEL_TOOLS)
IF(WITH_3D)
+ IF(WITH_ASSIMP)
+ SUBDIRS(
+ mesh_utils
+ mesh_export)
+ ENDIF()
SUBDIRS(
anim_builder
animation_set_builder
@@ -27,7 +32,6 @@ IF(WITH_NEL_TOOLS)
zone_dump
zviewer)
ENDIF()
-
SUBDIRS(
build_interface
get_neighbors
diff --git a/code/nel/tools/3d/mesh_export/CMakeLists.txt b/code/nel/tools/3d/mesh_export/CMakeLists.txt
new file mode 100644
index 000000000..838ce49c2
--- /dev/null
+++ b/code/nel/tools/3d/mesh_export/CMakeLists.txt
@@ -0,0 +1,12 @@
+FILE(GLOB SRCS *.cpp)
+FILE(GLOB HDRS *.h)
+
+SOURCE_GROUP("" FILES ${SRCS} ${HDRS})
+
+ADD_EXECUTABLE(mesh_export ${SRCS} ${HDRS})
+
+TARGET_LINK_LIBRARIES(mesh_export mesh_utils nel3d nelmisc)
+NL_DEFAULT_PROPS(mesh_export "NeL, Tools, 3D: Mesh Export")
+NL_ADD_RUNTIME_FLAGS(mesh_export)
+
+INSTALL(TARGETS mesh_export RUNTIME DESTINATION ${NL_BIN_PREFIX} COMPONENT tools3d)
diff --git a/code/nel/tools/3d/mesh_export/main.cpp b/code/nel/tools/3d/mesh_export/main.cpp
new file mode 100644
index 000000000..1503a4b16
--- /dev/null
+++ b/code/nel/tools/3d/mesh_export/main.cpp
@@ -0,0 +1,77 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2015 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include
+#include "../mesh_utils/mesh_utils.h"
+
+#include
+#include
+
+#include
+#include
+
+int printHelp(const NLMISC::CCmdArgs &args)
+{
+ printf("NeL Mesh Export\n");
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char *argv[])
+{
+ NLMISC::CApplicationContext app;
+
+ NLMISC::CCmdArgs args;
+ args.setArgs(argc, (const char **)argv);
+
+ if (args.getArgs().size() == 1
+ || args.haveArg('h')
+ || args.haveLongArg("help"))
+ return printHelp(args);
+
+ const NLMISC::CSString &filePath = args.getArgs().back();
+ if (!NLMISC::CFile::fileExists(filePath))
+ {
+ printHelp(args);
+ nlerror("File '%s' does not exist", filePath);
+ return EXIT_FAILURE;
+ }
+
+ CMeshUtilsSettings settings;
+ settings.SourceFilePath = filePath;
+
+ if (args.haveArg('d'))
+ settings.DestinationDirectoryPath = args.getArg('d');
+ if (settings.DestinationDirectoryPath.empty())
+ settings.DestinationDirectoryPath = args.getLongArg("dst");
+ if (settings.DestinationDirectoryPath.empty())
+ settings.DestinationDirectoryPath = filePath + "_export";
+ settings.DestinationDirectoryPath += "/";
+
+ settings.ToolDependLog = args.getLongArg("dependlog");
+ if (settings.ToolDependLog.empty())
+ settings.ToolDependLog = settings.DestinationDirectoryPath + "depend.log";
+ settings.ToolErrorLog = args.getLongArg("errorlog");
+ if (settings.ToolErrorLog.empty())
+ settings.ToolErrorLog = settings.DestinationDirectoryPath + "error.log";
+
+ NL3D::CScene::registerBasics();
+ NL3D::registerSerial3d();
+
+ return exportScene(settings);
+}
+
+/* end of file */
diff --git a/code/nel/tools/3d/mesh_utils/CMakeLists.txt b/code/nel/tools/3d/mesh_utils/CMakeLists.txt
new file mode 100644
index 000000000..a5a9fa952
--- /dev/null
+++ b/code/nel/tools/3d/mesh_utils/CMakeLists.txt
@@ -0,0 +1,16 @@
+FILE(GLOB SRCS *.cpp)
+FILE(GLOB HDRS *.h)
+
+SOURCE_GROUP("" FILES ${SRCS} ${HDRS})
+
+INCLUDE_DIRECTORIES(${assimp_INCLUDE_DIRS})
+
+NL_TARGET_LIB(mesh_utils ${SRCS} ${HDRS})
+
+TARGET_LINK_LIBRARIES(mesh_utils ${assimp_LIBRARIES} nelmisc nel3d)
+NL_DEFAULT_PROPS(mesh_utils "NeL, Tools, 3D: Mesh Utils")
+NL_ADD_RUNTIME_FLAGS(mesh_utils)
+
+IF((WITH_INSTALL_LIBRARIES AND WITH_STATIC) OR NOT WITH_STATIC)
+ INSTALL(TARGETS mesh_utils LIBRARY DESTINATION ${NL_LIB_PREFIX} ARCHIVE DESTINATION ${NL_LIB_PREFIX} COMPONENT tools3d)
+ENDIF((WITH_INSTALL_LIBRARIES AND WITH_STATIC) OR NOT WITH_STATIC)
diff --git a/code/nel/tools/3d/mesh_utils/assimp_material.cpp b/code/nel/tools/3d/mesh_utils/assimp_material.cpp
new file mode 100644
index 000000000..5dbe8643c
--- /dev/null
+++ b/code/nel/tools/3d/mesh_utils/assimp_material.cpp
@@ -0,0 +1,228 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2015 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include
+#include "assimp_shape.h"
+
+#include
+#include
+#include
+
+#define NL_NODE_INTERNAL_TYPE aiNode
+#define NL_SCENE_INTERNAL_TYPE aiScene
+#include "scene_context.h"
+
+#include
+#include
+#include
+
+#include
+#include
+
+using namespace std;
+using namespace NLMISC;
+using namespace NL3D;
+
+// http://assimp.sourceforge.net/lib_html/materials.html
+
+inline CRGBA convColor(const aiColor3D &ac, uint8 a = 255)
+{
+ return CRGBA(ac.r * 255.99f, ac.g * 255.99f, ac.b * 255.99f);
+}
+
+inline CRGBA convColor(const aiColor4D &ac)
+{
+ return CRGBA(ac.r * 255.99f, ac.g * 255.99f, ac.b * 255.99f, ac.a * 255.99f);
+}
+
+void assimpMaterial(NL3D::CMaterial &mat, CMeshUtilsContext &context, const aiMaterial *am)
+{
+ aiString amname;
+ if (am->Get(AI_MATKEY_NAME, amname) != aiReturn_SUCCESS)
+ amname = "";
+
+ mat.initLighted();
+ mat.setShader(CMaterial::Normal);
+
+ int i;
+ float f;
+ aiColor3D c3;
+ aiColor4D c4;
+
+ if (am->Get(AI_MATKEY_TWOSIDED, i) == aiReturn_SUCCESS)
+ mat.setDoubleSided(i != 0);
+
+ if (am->Get(AI_MATKEY_BLEND_FUNC, i) == aiReturn_SUCCESS) switch ((aiBlendMode)i)
+ {
+ case aiBlendMode_Default:
+ mat.setSrcBlend(CMaterial::srcalpha);
+ mat.setDstBlend(CMaterial::invsrcalpha);
+ break;
+ case aiBlendMode_Additive:
+ mat.setSrcBlend(CMaterial::one);
+ mat.setDstBlend(CMaterial::one);
+ break;
+ }
+
+ // Colors follow GL convention
+ // "While the ambient, diffuse, specular and emission
+ // "material parameters all have alpha components, only the diffuse"
+ // "alpha component is used in the lighting computation."
+ if (am->Get(AI_MATKEY_COLOR_DIFFUSE, c3) == aiReturn_SUCCESS)
+ mat.setDiffuse(convColor(c3));
+
+ if (am->Get(AI_MATKEY_OPACITY, f) == aiReturn_SUCCESS)
+ mat.setOpacity(f * 255.99f);
+
+ if (am->Get(AI_MATKEY_COLOR_AMBIENT, c3) == aiReturn_SUCCESS)
+ mat.setAmbient(convColor(c3));
+
+ if (am->Get(AI_MATKEY_SHININESS, f) == aiReturn_SUCCESS)
+ mat.setShininess(f); // (float)pow(2.0, f * 10.0) * 4.f;
+
+ if (am->Get(AI_MATKEY_COLOR_SPECULAR, c3) == aiReturn_SUCCESS)
+ mat.setSpecular(convColor(c3));
+
+ if (am->Get(AI_MATKEY_SHININESS_STRENGTH, f) == aiReturn_SUCCESS)
+ mat.setSpecular(CRGBAF(mat.getSpecular()) * f);
+ else
+ mat.setSpecular(NLMISC::CRGBA::Black);
+
+ if (am->Get(AI_MATKEY_COLOR_EMISSIVE, c3) == aiReturn_SUCCESS)
+ mat.setEmissive(convColor(c3));
+
+ // Textures
+ unsigned int texCount = am->GetTextureCount(aiTextureType_DIFFUSE);
+ if (texCount > IDRV_MAT_MAXTEXTURES)
+ {
+ tlwarning(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Material '%s' has more than %i textures (%i textures found)", amname.C_Str(), IDRV_MAT_MAXTEXTURES, texCount);
+ texCount = IDRV_MAT_MAXTEXTURES;
+ }
+
+ for (unsigned int ti = 0; ti < texCount; ++ti)
+ {
+ aiString path;
+ aiTextureMapping mapping;
+ unsigned int uvindex;
+ float blend; // Partially supported
+ aiTextureOp op;
+ aiTextureMapMode mapmode;
+ if (am->GetTexture(aiTextureType_DIFFUSE, ti, &path, &mapping, &uvindex, &blend, &op, &mapmode) != aiReturn_SUCCESS)
+ {
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Failed to get texture %i in material '%s'", ti, amname.C_Str());
+ break;
+ }
+
+ std::string fileName = CFile::getFilename(CPath::standardizePath(path.C_Str(), false));
+ std::string knownPath = CPath::lookup(fileName, false, false, false);
+ if (knownPath.empty())
+ {
+ tlwarning(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Texture '%s' referenced in material '%s' but not found in the database search paths", fileName.c_str(), amname.C_Str());
+ }
+
+ // NeL supports bitmap and cubemap, but we import only basic bitmap here. Cubemap can be inserted from the mesh editor tool
+ // NeL also has fancy multi-bitmap thing to switch between summer and winter and so on. Same story
+ CSmartPtr tex = new CTextureFile();
+ tex->setFileName(fileName);
+ tex->setWrapS(mapmode == aiTextureMapMode_Clamp ? ITexture::Clamp : ITexture::Repeat);
+ tex->setWrapT(mapmode == aiTextureMapMode_Clamp ? ITexture::Clamp : ITexture::Repeat);
+ mat.setTexture(ti, tex);
+
+ // TODO uvindex for uv routing (probably necessary during shape import - if so also need to also ask the uv channel in the editor and store in meta)
+
+ // TODO aiTextureMapping texcoordgen if useful to import
+
+ mat.texEnvArg0Alpha(ti, CMaterial::Texture, CMaterial::SrcAlpha);
+ mat.texEnvArg0RGB(ti, CMaterial::Texture, CMaterial::SrcColor);
+ mat.texEnvArg1Alpha(ti, ti == 0 ? CMaterial::Diffuse : CMaterial::Previous, CMaterial::SrcAlpha);
+ mat.texEnvArg1RGB(ti, ti == 0 ? CMaterial::Diffuse : CMaterial::Previous, CMaterial::SrcColor);
+ switch (op)
+ {
+ case aiTextureOp_Multiply:
+ default:
+ mat.texEnvOpAlpha(ti, CMaterial::Modulate);
+ mat.texEnvOpRGB(ti, CMaterial::Modulate);
+ break;
+ case aiTextureOp_Add:
+ mat.texEnvOpAlpha(ti, CMaterial::Add);
+ mat.texEnvOpRGB(ti, CMaterial::Add);
+ break;
+ case aiTextureOp_Subtract:
+ mat.texEnvArg0Alpha(ti, CMaterial::Texture, CMaterial::InvSrcAlpha);
+ mat.texEnvArg0RGB(ti, CMaterial::Texture, CMaterial::InvSrcColor);
+ mat.texEnvOpAlpha(ti, CMaterial::Add);
+ mat.texEnvOpRGB(ti, CMaterial::Add);
+ break;
+ case aiTextureOp_SignedAdd:
+ mat.texEnvOpAlpha(ti, CMaterial::AddSigned);
+ mat.texEnvOpRGB(ti, CMaterial::AddSigned);
+ break;
+ }
+ }
+}
+
+CSmartPtr assimpMaterial(CMeshUtilsContext &context, const aiMaterial *am)
+{
+ CSmartPtr matp = new CMaterial();
+ CMaterial &mat = *matp;
+ assimpMaterial(mat, context, am);
+ return matp;
+}
+
+void assimpMaterials(CMeshUtilsContext &context)
+{
+ set materialNames;
+
+ const aiScene *scene = context.InternalScene;
+ for (unsigned int mi = 0; mi < scene->mNumMaterials; ++mi)
+ {
+ const aiMaterial *am = scene->mMaterials[mi];
+
+ for (unsigned int pi = 0; pi < am->mNumProperties; ++pi) // DEBUG
+ { // DEBUG
+ const aiMaterialProperty *amp = am->mProperties[pi];
+ printf("%s\n", amp->mKey.C_Str());
+ } // DEBUG
+
+ aiString amname;
+ if (am->Get(AI_MATKEY_NAME, amname) != aiReturn_SUCCESS)
+ {
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Material has no name");
+ continue;
+ }
+
+ if (materialNames.find(amname.C_Str()) != materialNames.end())
+ {
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Material name '%s' used more than once", amname.C_Str());
+ continue;
+ }
+
+ if (context.SceneMeta.Materials.find(amname.C_Str())
+ == context.SceneMeta.Materials.end())
+ {
+ materialNames.insert(amname.C_Str());
+ context.SceneMeta.Materials[amname.C_Str()] = assimpMaterial(context, am);
+ }
+ }
+}
+
+/* end of file */
diff --git a/code/nel/tools/3d/mesh_utils/assimp_material.h b/code/nel/tools/3d/mesh_utils/assimp_material.h
new file mode 100644
index 000000000..062ca95bb
--- /dev/null
+++ b/code/nel/tools/3d/mesh_utils/assimp_material.h
@@ -0,0 +1,28 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2015 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include
+
+namespace NL3D {
+ class CMaterial;
+}
+
+struct CMeshUtilsContext;
+void assimpMaterial(NL3D::CMaterial &mat, CMeshUtilsContext &context, const aiMaterial *am);
+void assimpMaterials(CMeshUtilsContext &context);
+
+/* end of file */
diff --git a/code/nel/tools/3d/mesh_utils/assimp_shape.cpp b/code/nel/tools/3d/mesh_utils/assimp_shape.cpp
new file mode 100644
index 000000000..7b404d1e2
--- /dev/null
+++ b/code/nel/tools/3d/mesh_utils/assimp_shape.cpp
@@ -0,0 +1,379 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2015 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include
+#include "assimp_shape.h"
+
+#include
+#include
+#include
+
+#define NL_NODE_INTERNAL_TYPE aiNode
+#define NL_SCENE_INTERNAL_TYPE aiScene
+#include "scene_context.h"
+
+#include
+#include
+#include
+
+#include
+
+#include "assimp_material.h"
+
+using namespace std;
+using namespace NLMISC;
+using namespace NL3D;
+
+// TODO: buildParticleSystem ??
+// TODO: buildWaveMakerShape ??
+// TODO: buildRemanence ??
+// TODO: buildFlare ??
+// Probably specific settings we can only do in meta editor on a dummy node..
+// TODO: pacs prim
+
+// TODO: buildWaterShape specifics when node has water material
+
+// TODO: CMeshMultiLod::CMeshMultiLodBuild multiLodBuild; export_mesh.cpp ln 228
+// TODO: LOD MRM
+
+// TODO: Skinned - reverse transform by skeleton root bone to align?
+
+/*inline CMatrix convMatrix(const aiMatrix4x4 &tf)
+{
+ CMatrix m;
+ for (int i = 0; i < 16; ++i)
+ m.set(&tf.a1);
+ return m;
+}*/
+
+inline CVector convVector(const aiVector3D &av)
+{
+ return CVector(av.x, av.y, av.z); // COORDINATE CONVERSION
+}
+
+inline CRGBA convColor(const aiColor4D &ac)
+{
+ return CRGBA(ac.r * 255.99f, ac.g * 255.99f, ac.b * 255.99f, ac.a * 255.99f);
+}
+
+inline CUVW convUvw(const aiVector3D &av)
+{
+ return CUVW(av.x, -av.y, av.z); // UH OH COORDINATE CONVERSION ?! ONLY FOR TEXTURES !!
+}
+
+inline CQuat convQuat(const aiQuaternion &aq)
+{
+ return CQuat(aq.x, aq.y, aq.z, aq.w);
+}
+
+void assimpBuildBaseMesh(CMeshBase::CMeshBaseBuild &buildBaseMesh, CMeshUtilsContext &context, CNodeContext &nodeContext)
+{
+ const aiNode *node = nodeContext.InternalNode;
+ // Reference CExportNel::buildBaseMeshInterface
+
+ // Load materials
+ buildBaseMesh.Materials.resize(node->mNumMeshes);
+
+ for (unsigned int mi = 0; mi < node->mNumMeshes; ++mi)
+ {
+ const aiMesh *mesh = context.InternalScene->mMeshes[node->mMeshes[mi]];
+ const aiMaterial *am = context.InternalScene->mMaterials[mesh->mMaterialIndex];
+
+ aiString amname;
+ if (am->Get(AI_MATKEY_NAME, amname) != aiReturn_SUCCESS)
+ {
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Material used by node '%s' has no name", node->mName.C_Str()); // TODO: Maybe autogen names by index in mesh or node if this is actually a thing
+ assimpMaterial(buildBaseMesh.Materials[mi], context, am);
+ }
+ else
+ {
+ buildBaseMesh.Materials[mi] = *context.SceneMeta.Materials[amname.C_Str()];
+ }
+ }
+
+ // Positioning
+ const aiMatrix4x4 &root = context.InternalScene->mRootNode->mTransformation;
+ const aiMatrix4x4 &tf = nodeContext.InternalNode->mTransformation; // COORDINATE CONVERSION HERE INSTEAD OF PER VERTEX ??
+ aiVector3D scaling;
+ aiQuaternion rotation;
+ aiVector3D position;
+ tf.Decompose(scaling, rotation, position);
+ buildBaseMesh.DefaultScale = convVector(scaling);
+ buildBaseMesh.DefaultRotQuat = convQuat(rotation);
+ buildBaseMesh.DefaultRotEuler = CVector(0, 0, 0);
+ buildBaseMesh.DefaultPivot = CVector(0, 0, 0);
+ buildBaseMesh.DefaultPos = convVector(position);
+ if (buildBaseMesh.DefaultScale.x != 1.0f || buildBaseMesh.DefaultScale.y != 1.0f || buildBaseMesh.DefaultScale.z != 1.0f)
+ {
+ tlmessage(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Node '%s' has a scaled transformation. This may be a mistake", node->mName.C_Str());
+ }
+
+ // Meta
+ // dst.CollisionMeshGeneration = src.CollisionMeshGeneration;
+
+ // TODO: Morph
+}
+
+bool assimpBuildMesh(CMesh::CMeshBuild &buildMesh, CMeshBase::CMeshBaseBuild &buildBaseMesh, CMeshUtilsContext &context, CNodeContext &nodeContext)
+{
+ // TODO
+ // *** If the mesh is skined, vertices will be exported in world space.
+ // *** If the mesh is not skined, vertices will be exported in offset space.
+
+ // TODO Support skinning
+
+ const aiNode *node = nodeContext.InternalNode;
+ nlassert(node->mNumMeshes);
+
+ // Basic validations before processing starts
+ for (unsigned int mi = 0; mi < node->mNumMeshes; ++mi)
+ {
+ // TODO: Maybe needs to be the same count too for all meshes, so compare with mesh 0
+ const aiMesh *mesh = context.InternalScene->mMeshes[node->mMeshes[mi]];
+ if (mesh->GetNumColorChannels() > 2)
+ {
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "(%s) mesh->GetNumColorChannels() > 2", node->mName.C_Str());
+ return false;
+ }
+ if (mesh->GetNumUVChannels() > CVertexBuffer::MaxStage)
+ {
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "(%s) mesh->GetNumUVChannels() > CVertexBuffer::MaxStage", node->mName.C_Str());
+ return false;
+ }
+ if (!mesh->HasNormals())
+ {
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "(%s) !mesh->HasNormals()", node->mName.C_Str());
+ return false;
+ }
+ }
+
+ // Default vertex flags
+ buildMesh.VertexFlags = CVertexBuffer::PositionFlag | CVertexBuffer::NormalFlag;
+
+ // TODO: UV Channels routing to correct texture stage
+ for (uint i = 0; i < CVertexBuffer::MaxStage; ++i)
+ buildMesh.UVRouting[i] = i;
+
+ // Meshes in assimp are separated per material, so we need to re-merge them for the mesh build process
+ // This process also deduplicates vertices
+ bool cleanupMesh = true;
+ sint32 numVertices = 0;
+ for (unsigned int mi = 0; mi < node->mNumMeshes; ++mi)
+ numVertices += context.InternalScene->mMeshes[node->mMeshes[mi]]->mNumVertices;
+ buildMesh.Vertices.resize(numVertices);
+ numVertices = 0;
+ map vertexIdentifiers;
+ vector > vertexRemapping;
+ vertexRemapping.resize(node->mNumMeshes);
+ for (unsigned int mi = 0; mi < node->mNumMeshes; ++mi)
+ {
+ const aiMesh *mesh = context.InternalScene->mMeshes[node->mMeshes[mi]];
+ vertexRemapping[mi].resize(mesh->mNumVertices);
+ for (unsigned int vi = 0; vi < mesh->mNumVertices; ++vi)
+ {
+ CVector vec = convVector(mesh->mVertices[vi]);
+ map::iterator vecit = vertexIdentifiers.find(vec);
+ if (vecit == vertexIdentifiers.end())
+ {
+ buildMesh.Vertices[numVertices] = vec;
+ if (cleanupMesh) vertexIdentifiers[vec] = numVertices; // Don't remap if we don't wan't to lose vertex indices
+ vertexRemapping[mi][vi] = numVertices;
+ ++numVertices;
+ }
+ else
+ {
+ vertexRemapping[mi][vi] = vecit->second;
+ }
+ }
+ }
+ buildMesh.Vertices.resize(numVertices);
+
+ // Process all faces
+ // WONT IMPLEMENT: Radial faces generation... is linked to smoothing group...
+ // leave radial normals generation to modeling tool for now...
+ sint32 numFaces = 0;
+ for (unsigned int mi = 0; mi < node->mNumMeshes; ++mi)
+ numFaces += context.InternalScene->mMeshes[node->mMeshes[mi]]->mNumFaces;
+ buildMesh.Faces.resize(numFaces);
+ numFaces = 0;
+ unsigned int refNumColorChannels = context.InternalScene->mMeshes[node->mMeshes[0]]->GetNumColorChannels();
+ unsigned int refNumUVChannels = context.InternalScene->mMeshes[node->mMeshes[0]]->GetNumUVChannels();
+ for (unsigned int mi = 0; mi < node->mNumMeshes; ++mi)
+ {
+ const aiMesh *mesh = context.InternalScene->mMeshes[node->mMeshes[mi]];
+
+ // Get channel numbers
+ unsigned int numColorChannels = mesh->GetNumColorChannels();
+ if (numColorChannels > 2)
+ {
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Shape '%s' has too many color channels in mesh %i (%i channels found)", node->mName.C_Str(), mi, numColorChannels);
+ }
+ if (numColorChannels > 0)
+ {
+ buildMesh.VertexFlags |= CVertexBuffer::PrimaryColorFlag;
+ if (numColorChannels > 1)
+ {
+ buildMesh.VertexFlags |= CVertexBuffer::SecondaryColorFlag;
+ }
+ }
+ unsigned int numUVChannels = mesh->GetNumUVChannels();
+ if (numUVChannels > CVertexBuffer::MaxStage)
+ {
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Shape '%s' has too many uv channels in mesh %i (%i channels found)", node->mName.C_Str(), mi, numUVChannels);
+ numUVChannels = CVertexBuffer::MaxStage;
+ }
+ for (unsigned int ui = 0; ui < numUVChannels; ++ui)
+ buildMesh.VertexFlags |= (CVertexBuffer::TexCoord0Flag << ui); // TODO: Coord UV tex stage rerouting
+
+ // TODO: Channels do in fact differ between submeshes, so we need to correctly recount and reroute the materials properly
+ if (numColorChannels != refNumColorChannels)
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Shape '%s' mismatch of nb color channel in mesh '%i', please contact developer", node->mName.C_Str(), mi);
+ if (numUVChannels != refNumUVChannels)
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Shape '%s' mismatch of nb uv channel in mesh '%i', please contact developer", node->mName.C_Str(), mi);
+
+ for (unsigned int fi = 0; fi < mesh->mNumFaces; ++fi)
+ {
+ const aiFace &af = mesh->mFaces[fi];
+ if (af.mNumIndices != 3)
+ {
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "(%s) Face %i on mesh %i has %i faces", node->mName.C_Str(), fi, mi, af.mNumIndices);
+ continue; // return false; Keep going, just drop the face for better user experience
+ }
+ if (cleanupMesh)
+ {
+ if (vertexRemapping[mi][af.mIndices[0]] == vertexRemapping[mi][af.mIndices[1]]
+ || vertexRemapping[mi][af.mIndices[1]] == vertexRemapping[mi][af.mIndices[2]]
+ || vertexRemapping[mi][af.mIndices[2]] == vertexRemapping[mi][af.mIndices[0]])
+ continue; // Not a triangle
+ }
+ CMesh::CFace &face = buildMesh.Faces[numFaces];
+ face.MaterialId = mi;
+ face.SmoothGroup = 0; // No smoothing groups (bitfield)
+ face.Corner[0].Vertex = vertexRemapping[mi][af.mIndices[0]];
+ face.Corner[1].Vertex = vertexRemapping[mi][af.mIndices[1]];
+ face.Corner[2].Vertex = vertexRemapping[mi][af.mIndices[2]];
+ face.Corner[0].Normal = convVector(mesh->mNormals[af.mIndices[0]]);
+ face.Corner[1].Normal = convVector(mesh->mNormals[af.mIndices[1]]);
+ face.Corner[2].Normal = convVector(mesh->mNormals[af.mIndices[2]]);
+ // TODO: If we want normal maps, we need to add tangent vectors to CFace and build process
+ // UV channels
+ for (unsigned int ui = 0; ui < numUVChannels; ++ui) // TODO: UV Rerouting
+ {
+ face.Corner[0].Uvws[ui] = convUvw(mesh->mTextureCoords[ui][af.mIndices[0]]);
+ face.Corner[1].Uvws[ui] = convUvw(mesh->mTextureCoords[ui][af.mIndices[1]]);
+ face.Corner[2].Uvws[ui] = convUvw(mesh->mTextureCoords[ui][af.mIndices[2]]);
+ }
+ for (unsigned int ui = numUVChannels; ui < CVertexBuffer::MaxStage; ++ui)
+ {
+ face.Corner[0].Uvws[ui] = CUVW(0, 0, 0);
+ face.Corner[1].Uvws[ui] = CUVW(0, 0, 0);
+ face.Corner[2].Uvws[ui] = CUVW(0, 0, 0);
+ }
+ // Primary and secondary color channels
+ if (numColorChannels > 0) // TODO: Verify
+ {
+ face.Corner[0].Color = convColor(mesh->mColors[0][af.mIndices[0]]);
+ face.Corner[1].Color = convColor(mesh->mColors[0][af.mIndices[1]]);
+ face.Corner[2].Color = convColor(mesh->mColors[0][af.mIndices[2]]);
+ }
+ else
+ {
+ face.Corner[0].Color = CRGBA(255, 255, 255, 255);
+ face.Corner[1].Color = CRGBA(255, 255, 255, 255);
+ face.Corner[2].Color = CRGBA(255, 255, 255, 255);
+ }
+ if (numColorChannels > 1) // TODO: Verify
+ {
+ face.Corner[0].Specular = convColor(mesh->mColors[1][af.mIndices[0]]);
+ face.Corner[1].Specular = convColor(mesh->mColors[1][af.mIndices[1]]);
+ face.Corner[2].Specular = convColor(mesh->mColors[1][af.mIndices[2]]);
+ }
+ else
+ {
+ face.Corner[0].Specular = CRGBA(255, 255, 255, 255);
+ face.Corner[1].Specular = CRGBA(255, 255, 255, 255);
+ face.Corner[2].Specular = CRGBA(255, 255, 255, 255);
+ }
+ // TODO: Color modulate, alpha, use color alpha for vp tree, etc
+ ++numFaces;
+ }
+ }
+ if (numFaces != buildMesh.Faces.size())
+ {
+ tlmessage(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Removed %u degenerate faces in shape '%s'", (uint32)(buildMesh.Faces.size() - numFaces), node->mName.C_Str());
+ buildMesh.Faces.resize(numFaces);
+ }
+
+ // clear for MRM info
+ buildMesh.Interfaces.clear();
+ buildMesh.InterfaceLinks.clear();
+
+ // TODO: Export VP
+ buildMesh.MeshVertexProgram = NULL;
+
+ return true;
+}
+
+bool assimpShape(CMeshUtilsContext &context, CNodeContext &nodeContext)
+{
+ // Reference: export_mesh.cpp, buildShape
+ nodeContext.Shape = NULL;
+
+ const aiNode *node = nodeContext.InternalNode;
+ nlassert(node->mNumMeshes);
+
+ // Fill the build interface of CMesh
+ CMeshBase::CMeshBaseBuild buildBaseMesh;
+ assimpBuildBaseMesh(buildBaseMesh, context, nodeContext);
+
+ CMesh::CMeshBuild buildMesh;
+ if (!assimpBuildMesh(buildMesh, buildBaseMesh, context, nodeContext))
+ return false;
+
+ // Make a CMesh object
+ CMesh *mesh = new CMesh();
+
+ // Build the mesh with the build interface
+ mesh->build(buildBaseMesh, buildMesh);
+
+ // TODO
+ // Reference: export_mesh.cpp, buildShape
+ // Must be done after the build to update vertex links
+ // Pass to buildMeshMorph if the original mesh is skinned or not
+ // buildMeshMorph(buildMesh, node, time, nodeMap != NULL);
+ // mesh->setBlendShapes(buildMesh.BlendShapes);
+
+ // optimize number of material
+ // mesh->optimizeMaterialUsage(materialRemap);
+
+ // Store mesh in context
+ nodeContext.Shape = mesh;
+ return true;
+}
+
+/* end of file */
diff --git a/code/nel/tools/3d/mesh_utils/assimp_shape.h b/code/nel/tools/3d/mesh_utils/assimp_shape.h
new file mode 100644
index 000000000..b4bdc2490
--- /dev/null
+++ b/code/nel/tools/3d/mesh_utils/assimp_shape.h
@@ -0,0 +1,25 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2015 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include
+
+struct CMeshUtilsContext;
+struct CNodeContext;
+
+bool assimpShape(CMeshUtilsContext &context, CNodeContext &nodeContext);
+
+/* end of file */
diff --git a/code/nel/tools/3d/mesh_utils/database_config.cpp b/code/nel/tools/3d/mesh_utils/database_config.cpp
new file mode 100644
index 000000000..7e6ea5b37
--- /dev/null
+++ b/code/nel/tools/3d/mesh_utils/database_config.cpp
@@ -0,0 +1,107 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2015 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include
+#include "database_config.h"
+
+#include
+#include
+#include
+
+using namespace std;
+using namespace NLMISC;
+
+TPathString CDatabaseConfig::s_RootPath;
+NLMISC::CConfigFile *CDatabaseConfig::s_ConfigFile = NULL;
+CDatabaseConfig CDatabaseConfig::s_Instance;
+uint32 CDatabaseConfig::s_ConfigFileModification;
+
+static std::set s_SearchPaths;
+
+void CDatabaseConfig::cleanup()
+{
+ delete CDatabaseConfig::s_ConfigFile;
+ CDatabaseConfig::s_ConfigFile = NULL;
+}
+
+CDatabaseConfig::~CDatabaseConfig()
+{
+ cleanup();
+}
+
+bool CDatabaseConfig::init(const std::string &asset)
+{
+ // release();
+
+ TPathString rootPath = NLMISC::CPath::standardizePath(asset, false);
+ TPathString configPath = rootPath + "/database.cfg";
+ while (!CFile::fileExists(configPath))
+ {
+ int sep = CFile::getLastSeparator(rootPath);
+ if (sep == string::npos)
+ return false;
+
+ rootPath = rootPath.substr(0, sep);
+ if (rootPath.empty())
+ return false;
+
+ configPath = rootPath + "/database.cfg";
+ }
+
+ rootPath += "/";
+ uint32 configFileModification = CFile::getFileModificationDate(configPath);
+ if (rootPath == s_RootPath && s_ConfigFileModification == configFileModification)
+ return true; // Do not reload
+
+ nldebug("Initializing database config '%s'", configPath.c_str());
+ release();
+
+ s_RootPath = rootPath;
+ s_ConfigFileModification = configFileModification;
+
+ s_ConfigFile = new CConfigFile();
+ s_ConfigFile->load(configPath);
+ return true;
+}
+
+void CDatabaseConfig::initTextureSearchDirectories()
+{
+ searchDirectories("TextureSearchDirectories");
+}
+
+void CDatabaseConfig::searchDirectories(const char *var)
+{
+ CConfigFile::CVar &paths = s_ConfigFile->getVar(var);
+ for (uint i = 0; i < paths.size(); i++)
+ {
+ TPathString path = paths.asString(i);
+ if (s_SearchPaths.find(path) == s_SearchPaths.end())
+ {
+ CPath::addSearchPath(s_RootPath + path);
+ s_SearchPaths.insert(path);
+ }
+ }
+}
+
+void CDatabaseConfig::release()
+{
+ s_SearchPaths.clear();
+ CPath::clearMap();
+ cleanup();
+}
+
+/* end of file */
diff --git a/code/nel/tools/3d/mesh_utils/database_config.h b/code/nel/tools/3d/mesh_utils/database_config.h
new file mode 100644
index 000000000..25bcc2a00
--- /dev/null
+++ b/code/nel/tools/3d/mesh_utils/database_config.h
@@ -0,0 +1,58 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2015 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include
+
+namespace NLMISC {
+ class CConfigFile;
+}
+
+#ifdef NL_OS_WINDOWS
+#include
+typedef NLMISC::CSString TPathString;
+#else
+typedef std::string TPathString;
+#endif
+
+/// Asset database configuration
+class CDatabaseConfig
+{
+public:
+ ~CDatabaseConfig();
+
+ /// Searches for the configuration for the specified asset path by recursively going through all parent directories looking for 'database.cfg', initializes and applies the configuration.
+ static bool init(const std::string &asset);
+ static void release();
+
+ static void initTextureSearchDirectories();
+
+ static inline const TPathString &rootPath() { return s_RootPath; }
+ static inline TPathString configPath() { return s_RootPath + "/database.cfg"; }
+
+private:
+ static void cleanup();
+ static void searchDirectories(const char *var);
+
+ static CDatabaseConfig s_Instance;
+ static uint32 s_ConfigFileModification;
+
+ static TPathString s_RootPath;
+ static NLMISC::CConfigFile *s_ConfigFile;
+
+};
+
+/* end of file */
diff --git a/code/nel/tools/3d/mesh_utils/mesh_utils.cpp b/code/nel/tools/3d/mesh_utils/mesh_utils.cpp
new file mode 100644
index 000000000..2ea3800a7
--- /dev/null
+++ b/code/nel/tools/3d/mesh_utils/mesh_utils.cpp
@@ -0,0 +1,348 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2015 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include
+#include "mesh_utils.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "database_config.h"
+#include "scene_meta.h"
+
+#include
+#include
+#include
+
+#define NL_NODE_INTERNAL_TYPE aiNode
+#define NL_SCENE_INTERNAL_TYPE aiScene
+#include "scene_context.h"
+
+#include "assimp_material.h"
+#include "assimp_shape.h"
+
+CMeshUtilsSettings::CMeshUtilsSettings()
+{
+ /*ShapeDirectory = "shape";
+ IGDirectory = "ig";
+ SkelDirectory = "skel";*/
+}
+
+void importShapes(CMeshUtilsContext &context, const aiNode *node)
+{
+ if (node != context.InternalScene->mRootNode)
+ {
+ CNodeContext &nodeContext = context.Nodes[node->mName.C_Str()];
+ CNodeMeta &nodeMeta = context.SceneMeta.Nodes[node->mName.C_Str()];
+ if (nodeMeta.ExportMesh == TMeshShape && nodeMeta.InstanceName.empty())
+ {
+ if (node->mNumMeshes)
+ {
+ nldebug("Shape '%s' found containing '%u' meshes", node->mName.C_Str(), node->mNumMeshes);
+ assimpShape(context, nodeContext);
+ }
+ }
+ }
+
+ for (unsigned int i = 0; i < node->mNumChildren; ++i)
+ importShapes(context, node->mChildren[i]);
+}
+
+void validateInternalNodeNames(CMeshUtilsContext &context, const aiNode *node)
+{
+ if (!node->mParent || node == context.InternalScene->mRootNode)
+ {
+ // do nothing
+ }
+ else if (node->mName.length == 0)
+ {
+ tlwarning(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Node has no name");
+ }
+ else
+ {
+ CNodeContext &nodeContext = context.Nodes[node->mName.C_Str()];
+
+ if (nodeContext.InternalNode && nodeContext.InternalNode != node)
+ {
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Node name '%s' appears multiple times", node->mName.C_Str());
+ }
+ else
+ {
+ nodeContext.InternalNode = node;
+ }
+ }
+
+ for (unsigned int i = 0; i < node->mNumChildren; ++i)
+ validateInternalNodeNames(context, node->mChildren[i]);
+}
+
+void flagAssimpBones(CMeshUtilsContext &context)
+{
+ // Find out which nodes are bones by checking the mesh meta info
+ const aiScene *scene = context.InternalScene;
+ for (unsigned int i = 0; i < scene->mNumMeshes; ++i)
+ {
+ // nldebug("FOUND MESH '%s'\n", scene->mMeshes[i]->mName.C_Str());
+ const aiMesh *mesh = scene->mMeshes[i];
+ for (unsigned int j = 0; j < mesh->mNumBones; ++j)
+ {
+ CNodeContext &nodeContext = context.Nodes[mesh->mBones[j]->mName.C_Str()];
+ if (!nodeContext.InternalNode)
+ {
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Bone '%s' has no associated node", mesh->mBones[j]->mName.C_Str());
+ }
+ else
+ {
+ // Flag as bone
+ nodeContext.IsBone = true;
+
+ // Flag all parents as bones
+ /*const aiNode *parent = nodeContext.InternalNode;
+ while (parent = parent->mParent) if (parent->mName.length)
+ {
+ context.Nodes[parent->mName.C_Str()].IsBone = true;
+ }*/
+ }
+ }
+ }
+
+ // Find out which nodes are bones by checking the animation info
+ // TODO
+}
+
+void flagRecursiveBones(CMeshUtilsContext &context, CNodeContext &nodeContext, bool autoStop = false)
+{
+ nodeContext.IsBone = true;
+ const aiNode *node = nodeContext.InternalNode;
+ nlassert(node);
+ for (unsigned int i = 0; i < node->mNumChildren; ++i)
+ {
+ CNodeContext &ctx = context.Nodes[node->mName.C_Str()];
+ if (autoStop && ctx.IsBone)
+ continue;
+ flagRecursiveBones(context, ctx);
+ }
+}
+
+void flagMetaBones(CMeshUtilsContext &context)
+{
+ for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it)
+ {
+ CNodeContext &ctx = it->second;
+ CNodeMeta &meta = context.SceneMeta.Nodes[it->first];
+ if (meta.ExportBone == TBoneForce)
+ ctx.IsBone = true;
+ else if (meta.ExportBone == TBoneRoot)
+ flagRecursiveBones(context, ctx);
+ }
+}
+
+void flagLocalParentBones(CMeshUtilsContext &context, CNodeContext &nodeContext)
+{
+ const aiNode *node = nodeContext.InternalNode;
+}
+
+void flagAllParentBones(CMeshUtilsContext &context, CNodeContext &nodeContext, bool autoStop = false)
+{
+ const aiNode *parent = nodeContext.InternalNode;
+ while (parent = parent->mParent) if (parent->mName.length && parent != context.InternalScene->mRootNode)
+ {
+ CNodeContext &ctx = context.Nodes[parent->mName.C_Str()];
+ if (autoStop && ctx.IsBone)
+ break;
+ ctx.IsBone = true;
+ }
+}
+
+bool hasIndirectParentBone(CMeshUtilsContext &context, CNodeContext &nodeContext)
+{
+ const aiNode *parent = nodeContext.InternalNode;
+ while (parent = parent->mParent) if (parent->mName.length && parent != context.InternalScene->mRootNode)
+ if (context.Nodes[parent->mName.C_Str()].IsBone) return true;
+ return false;
+}
+
+void flagExpandedBones(CMeshUtilsContext &context)
+{
+ switch (context.SceneMeta.SkeletonMode)
+ {
+ case TSkelLocal:
+ for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it)
+ {
+ CNodeContext &nodeContext = it->second;
+ if (nodeContext.IsBone && hasIndirectParentBone(context, nodeContext))
+ flagAllParentBones(context, nodeContext, true);
+ }
+ break;
+ case TSkelRoot:
+ for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it)
+ {
+ CNodeContext &nodeContext = it->second;
+ if (nodeContext.IsBone)
+ flagAllParentBones(context, nodeContext, true);
+ }
+ break;
+ case TSkelFull:
+ for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it)
+ {
+ CNodeContext &nodeContext = it->second;
+ if (nodeContext.IsBone)
+ flagAllParentBones(context, nodeContext, true);
+ }
+ for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it)
+ {
+ CNodeContext &nodeContext = it->second;
+ if (nodeContext.IsBone)
+ flagRecursiveBones(context, nodeContext, true);
+ }
+ break;
+ }
+}
+
+void exportShapes(CMeshUtilsContext &context)
+{
+ for (TNodeContextMap::iterator it(context.Nodes.begin()), end(context.Nodes.end()); it != end; ++it)
+ {
+ CNodeContext &nodeContext = it->second;
+ if (nodeContext.Shape)
+ {
+ std::string shapePath = NLMISC::CPath::standardizePath(context.Settings.DestinationDirectoryPath, true) + it->first + ".shape";
+ context.ToolLogger.writeDepend(NLMISC::BUILD, shapePath.c_str(), "*");
+ NLMISC::COFile f;
+ if (f.open(shapePath, false, false, true))
+ {
+ try
+ {
+ NL3D::CShapeStream shapeStream(nodeContext.Shape);
+ shapeStream.serial(f);
+ f.close();
+ }
+ catch (...)
+ {
+ tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Shape '%s' serialization failed!", it->first.c_str());
+ }
+ }
+ if (NL3D::CMeshBase *mesh = dynamic_cast(nodeContext.Shape.getPtr()))
+ {
+ for (uint mi = 0; mi < mesh->getNbMaterial(); ++mi)
+ {
+ NL3D::CMaterial &mat = mesh->getMaterial(mi);
+ for (uint ti = 0; ti < NL3D::IDRV_MAT_MAXTEXTURES; ++ti)
+ {
+ if (NL3D::ITexture *itex = mat.getTexture(ti))
+ {
+ if (NL3D::CTextureFile *tex = dynamic_cast(itex))
+ {
+ std::string fileName = tex->getFileName();
+ std::string knownPath = NLMISC::CPath::lookup(fileName, false, false, false);
+ if (!knownPath.empty())
+ {
+ context.ToolLogger.writeDepend(NLMISC::RUNTIME, shapePath.c_str(), knownPath.c_str());
+ }
+ else
+ {
+ // TODO: Move this warning into nelmeta serialization so it's shown before export
+ tlwarning(context.ToolLogger, context.Settings.SourceFilePath.c_str(),
+ "Texture '%s' referenced in material but not found in the database search paths", fileName.c_str());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+// TODO: Separate load scene and save scene functions
+int exportScene(const CMeshUtilsSettings &settings)
+{
+ CMeshUtilsContext context(settings);
+ NLMISC::CFile::createDirectoryTree(settings.DestinationDirectoryPath);
+
+ if (!settings.ToolDependLog.empty())
+ context.ToolLogger.initDepend(settings.ToolDependLog);
+ if (!settings.ToolErrorLog.empty())
+ context.ToolLogger.initError(settings.ToolErrorLog);
+ context.ToolLogger.writeDepend(NLMISC::BUILD, "*", NLMISC::CPath::standardizePath(context.Settings.SourceFilePath, false).c_str()); // Base input file
+
+ // Apply database configuration
+ CDatabaseConfig::init(settings.SourceFilePath);
+ CDatabaseConfig::initTextureSearchDirectories();
+
+ Assimp::Importer importer;
+ const aiScene *scene = importer.ReadFile(settings.SourceFilePath, 0
+ | aiProcess_Triangulate
+ | aiProcess_ValidateDataStructure
+ | aiProcess_GenNormals // Or GenSmoothNormals? TODO: Validate smoothness between material boundaries!
+ ); // aiProcess_SplitLargeMeshes | aiProcess_LimitBoneWeights
+ if (!scene)
+ {
+ const char *errs = importer.GetErrorString();
+ if (errs) tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(), "Assimp failed to load the scene: '%s'", errs);
+ else tlerror(context.ToolLogger, context.Settings.SourceFilePath.c_str(), "Unable to load scene");
+ return EXIT_FAILURE;
+ }
+ // aiProcess_Triangulate
+ // aiProcess_ValidateDataStructure: TODO: Catch Assimp error output stream
+ // aiProcess_RemoveRedundantMaterials: Not used because we may override materials with NeL Material from meta
+ // aiProcess_ImproveCacheLocality: TODO: Verify this does not modify vertex indices
+ //scene->mRootNode->mMetaData
+
+ context.InternalScene = scene;
+ if (context.SceneMeta.load(context.Settings.SourceFilePath))
+ context.ToolLogger.writeDepend(NLMISC::BUILD, "*", context.SceneMeta.metaFilePath().c_str()); // Meta input file
+
+ validateInternalNodeNames(context, context.InternalScene->mRootNode);
+
+ // -- SKEL FLAG --
+ flagAssimpBones(context);
+ flagMetaBones(context);
+ flagExpandedBones(context);
+ // TODO
+ // [
+ // Only necessary in TSkelLocal
+ // For each shape test if all the bones have the same root bones for their skeleton
+ // 1) Iterate each until a different is found
+ // 2) When a different root is found, connect the two to the nearest common bone
+ // ]
+ // -- SKEL FLAG --
+
+ // First import materials
+ assimpMaterials(context);
+
+ // Import shapes
+ importShapes(context, context.InternalScene->mRootNode);
+
+ // Export shapes
+ exportShapes(context);
+
+ return EXIT_SUCCESS;
+}
+
+/* end of file */
diff --git a/code/nel/tools/3d/mesh_utils/mesh_utils.h b/code/nel/tools/3d/mesh_utils/mesh_utils.h
new file mode 100644
index 000000000..18be11e60
--- /dev/null
+++ b/code/nel/tools/3d/mesh_utils/mesh_utils.h
@@ -0,0 +1,44 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2015 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef NL_MESH_UTILS_H
+#define NL_MESH_UTILS_H
+#include
+
+#include
+
+struct CMeshUtilsSettings
+{
+ CMeshUtilsSettings();
+
+ // Absolute Paths
+ std::string SourceFilePath;
+ std::string DestinationDirectoryPath;
+ std::string ToolDependLog;
+ std::string ToolErrorLog;
+
+ // Relative Directories
+ /*std::string ShapeDirectory;
+ std::string IGDirectory;
+ std::string SkelDirectory;*/
+};
+
+int exportScene(const CMeshUtilsSettings &settings);
+
+#endif /* NL_MESH_UTILS_H */
+
+/* end of file */
diff --git a/code/nel/tools/3d/mesh_utils/scene_context.cpp b/code/nel/tools/3d/mesh_utils/scene_context.cpp
new file mode 100644
index 000000000..6812a312a
--- /dev/null
+++ b/code/nel/tools/3d/mesh_utils/scene_context.cpp
@@ -0,0 +1,30 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2015 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include
+#include "scene_context.h"
+
+#include
+#include
+#include
+
+using namespace std;
+using namespace NLMISC;
+
+void dummy_scene_context_cpp();
+
+/* end of file */
diff --git a/code/nel/tools/3d/mesh_utils/scene_context.h b/code/nel/tools/3d/mesh_utils/scene_context.h
new file mode 100644
index 000000000..714e3ff54
--- /dev/null
+++ b/code/nel/tools/3d/mesh_utils/scene_context.h
@@ -0,0 +1,81 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2015 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef NL_SCENE_CONTEXT_H
+#define NL_SCENE_CONTEXT_H
+#include
+
+#include "mesh_utils.h"
+#include "scene_meta.h"
+
+#include
+#include
+#include
+#include
+
+#include
+
+#ifndef NL_NODE_INTERNAL_TYPE
+#define NL_NODE_INTERNAL_TYPE void
+#endif
+#ifndef NL_SCENE_INTERNAL_TYPE
+#define NL_SCENE_INTERNAL_TYPE void
+#endif
+
+namespace NL3D {
+ class IShape;
+ class CMaterial;
+}
+
+struct CNodeContext
+{
+ CNodeContext() :
+ InternalNode(NULL),
+ IsBone(false)
+ {
+
+ }
+
+ const NL_NODE_INTERNAL_TYPE *InternalNode;
+ bool IsBone;
+
+ // NLMISC::CMatrix Transform; // TODO
+ NLMISC::CSmartPtr Shape;
+};
+
+typedef std::map TNodeContextMap;
+struct CMeshUtilsContext
+{
+ CMeshUtilsContext(const CMeshUtilsSettings &settings) : Settings(settings), InternalScene(NULL)
+ {
+
+ }
+
+ const CMeshUtilsSettings &Settings;
+
+ NLMISC::CToolLogger ToolLogger;
+
+ const NL_SCENE_INTERNAL_TYPE *InternalScene;
+ CSceneMeta SceneMeta;
+
+ TNodeContextMap Nodes; // Impl note: Should never end up containing the scene root node.
+ // std::map MeshNames; // Maps meshes to a node name ********************* todo ***************
+};
+
+#endif /* NL_SCENE_CONTEXT_H */
+
+/* end of file */
diff --git a/code/nel/tools/3d/mesh_utils/scene_meta.cpp b/code/nel/tools/3d/mesh_utils/scene_meta.cpp
new file mode 100644
index 000000000..dd7ea0686
--- /dev/null
+++ b/code/nel/tools/3d/mesh_utils/scene_meta.cpp
@@ -0,0 +1,100 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2015 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include
+#include "scene_meta.h"
+
+#include
+#include
+#include
+
+#include
+
+using namespace std;
+using namespace NLMISC;
+
+CNodeMeta::CNodeMeta() :
+ AddToIG(true),
+ ExportMesh(TMeshShape),
+ ExportBone(TBoneAuto),
+ AutoAnim(false)
+{
+
+}
+
+void CNodeMeta::serial(NLMISC::IStream &s)
+{
+ uint version = s.serialVersion(1);
+ s.serial(AddToIG);
+ s.serial((uint32 &)ExportMesh);
+ s.serial((uint32 &)ExportBone);
+ s.serial(InstanceShape);
+ s.serial(InstanceName);
+ s.serial(InstanceGroupName);
+ s.serial(AutoAnim);
+}
+
+CSceneMeta::CSceneMeta() :
+ ImportShape(true),
+ ImportSkel(true),
+ ImportAnim(true),
+ ImportCmb(true),
+ ImportIG(true),
+ ExportDefaultIG(false),
+ SkeletonMode(TSkelRoot)
+{
+
+}
+
+bool CSceneMeta::load(const std::string &filePath)
+{
+ m_MetaFilePath = NLMISC::CPath::standardizePath(filePath + ".nelmeta", false);
+ if (CFile::fileExists(m_MetaFilePath))
+ {
+ CIFile f(m_MetaFilePath);
+ serial(f);
+ f.close();
+ return true;
+ }
+ return false;
+}
+
+void CSceneMeta::save()
+{
+ COFile f(m_MetaFilePath, false, false, true);
+ serial(f);
+ f.close();
+}
+
+void CSceneMeta::serial(NLMISC::IStream &s)
+{
+ uint version = s.serialVersion(1);
+
+ s.serial(ImportShape);
+ s.serial(ImportSkel);
+ s.serial(ImportAnim);
+ s.serial(ImportCmb);
+ s.serial(ImportIG);
+
+ s.serial(ExportDefaultIG);
+ s.serial((uint32 &)SkeletonMode);
+
+ s.serialCont(Nodes);
+ s.serialPtrCont(Materials);
+}
+
+/* end of file */
diff --git a/code/nel/tools/3d/mesh_utils/scene_meta.h b/code/nel/tools/3d/mesh_utils/scene_meta.h
new file mode 100644
index 000000000..62c185ee4
--- /dev/null
+++ b/code/nel/tools/3d/mesh_utils/scene_meta.h
@@ -0,0 +1,108 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2015 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef NL_SCENE_META_H
+#define NL_SCENE_META_H
+#include
+
+#include
+#include
+
+#include
+
+namespace NLMISC {
+ class IStream;
+}
+
+namespace NL3D {
+ class CMaterial;
+}
+
+enum TMesh
+{
+ TMeshDisabled = 0,
+ TMeshShape = 1,
+ TMeshCollisionInt = 2,
+ TMeshCollisionExt = 3,
+ TMeshZone = 4,
+ TMeshPortal = 5,
+ TMeshCluster = 6,
+};
+
+enum TBone
+{
+ TBoneAuto = 0,
+ TBoneForce = 1, // Force this node to be part of a skeleton
+ TBoneRoot = 2, // Make this node the skeleton root, it will be exported using the scene name. There can only be one (editor should keep track and disable)
+};
+
+struct CNodeMeta
+{
+ CNodeMeta();
+
+ bool AddToIG; // Add this node to an instance group
+ TMesh ExportMesh;
+ TBone ExportBone;
+
+ std::string InstanceShape;
+ std::string InstanceName;
+ std::string InstanceGroupName;
+
+ bool AutoAnim;
+ // std::vector Materials; // In case there's an issue with nameless materials in some format... Map to material entirely in the meta editor.
+
+ void serial(NLMISC::IStream &s);
+};
+
+enum TSkel
+{
+ TSkelLocal = 0, // Export smallest skeleton possible from connected bones
+ TSkelRoot = 1, // Export skeleton from a direct child node in the scene root node
+ TSkelFull = 2, // Include all connected child nodes in the skeleton
+};
+
+typedef std::map > TMaterialMap;
+struct CSceneMeta
+{
+ CSceneMeta();
+
+ bool ImportShape;
+ bool ImportSkel;
+ bool ImportAnim;
+ bool ImportCmb;
+ bool ImportIG;
+
+ bool ExportDefaultIG; // Export a default instance group from nodes the scene that do not have an instance group set
+ TSkel SkeletonMode;
+
+ std::map Nodes;
+ TMaterialMap Materials;
+
+ const std::string &metaFilePath() const { return m_MetaFilePath; }
+
+ bool load(const std::string &filePath);
+ void save();
+ void serial(NLMISC::IStream &s);
+
+private:
+ std::string m_MetaFilePath;
+
+};
+
+#endif NL_SCENE_META_H
+
+/* end of file */