Merge with develop

This commit is contained in:
kervala 2015-11-21 18:09:38 +01:00
commit 29d8d751a8
24 changed files with 1996 additions and 17 deletions

19
.editorconfig Normal file
View file

@ -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

View file

@ -147,6 +147,10 @@ IF(WITH_QT)
FIND_PACKAGE(Qt4 COMPONENTS QtCore QtGui QtXml QtOpenGL REQUIRED) FIND_PACKAGE(Qt4 COMPONENTS QtCore QtGui QtXml QtOpenGL REQUIRED)
ENDIF(WITH_QT) ENDIF(WITH_QT)
IF(WITH_ASSIMP)
FIND_PACKAGE(assimp REQUIRED)
ENDIF(WITH_ASSIMP)
IF(WITH_NEL) IF(WITH_NEL)
IF(WITH_NEL_TESTS) IF(WITH_NEL_TESTS)
FIND_PACKAGE(CppTest) FIND_PACKAGE(CppTest)

View file

@ -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)

View file

@ -274,6 +274,8 @@ MACRO(NL_SETUP_DEFAULT_OPTIONS)
OPTION(WITH_STATIC_EXTERNAL "With static external libraries" OFF) OPTION(WITH_STATIC_EXTERNAL "With static external libraries" OFF)
OPTION(WITH_INSTALL_LIBRARIES "Install development files." ON ) OPTION(WITH_INSTALL_LIBRARIES "Install development files." ON )
OPTION(WITH_ASSIMP "Use assimp exporter" OFF)
### ###
# GUI toolkits # GUI toolkits
### ###

View file

@ -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
* <http://www.gnu.org/licenses/>.
*/
#ifndef NLMISC_TOOL_LOGGER_H
#define NLMISC_TOOL_LOGGER_H
#include <nel/misc/types_nl.h>
// STL includes
#include <string>
#include <stdio.h>
#include <stdlib.h>
// NeL includes
#include <nel/misc/time_nl.h>
#include <nel/misc/string_common.h>
// 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 */

View file

@ -769,9 +769,15 @@ bool launchProgram(const std::string &programName, const std::string &arguments,
} }
argv[i+1] = NULL; argv[i+1] = NULL;
// save LD_LIBRARY_PATH
const char *previousEnv = getenv("LD_LIBRARY_PATH");
// clear LD_LIBRARY_PATH to avoid problems with Steam Runtime
setenv("LD_LIBRARY_PATH", "", 1);
int status = vfork (); int status = vfork ();
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
/// WARNING : NO MORE INSTRCUTION AFTER VFORK ! /// WARNING : NO MORE INSTRUCTION AFTER VFORK !
/// READ VFORK manual /// READ VFORK manual
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
if (status == -1) if (status == -1)
@ -779,10 +785,12 @@ bool launchProgram(const std::string &programName, const std::string &arguments,
char *err = strerror (errno); char *err = strerror (errno);
if (log) if (log)
nlwarning("LAUNCH: Failed launched '%s' with arg '%s' err %d: '%s'", programName.c_str(), arguments.c_str(), errno, err); nlwarning("LAUNCH: Failed launched '%s' with arg '%s' err %d: '%s'", programName.c_str(), arguments.c_str(), errno, err);
// restore previous LD_LIBRARY_PATH
setenv("LD_LIBRARY_PATH", previousEnv, 1);
} }
else if (status == 0) else if (status == 0)
{ {
// Exec (the only allowed instruction after vfork) // Exec (the only allowed instruction after vfork)
status = execvp(programName.c_str(), &argv.front()); status = execvp(programName.c_str(), &argv.front());
@ -795,6 +803,10 @@ bool launchProgram(const std::string &programName, const std::string &arguments,
else else
{ {
//nldebug("LAUNCH: Successful launch '%s' with arg '%s'", programName.c_str(), arguments.c_str()); //nldebug("LAUNCH: Successful launch '%s' with arg '%s'", programName.c_str(), arguments.c_str());
// restore previous LD_LIBRARY_PATH
setenv("LD_LIBRARY_PATH", previousEnv, 1);
return true; return true;
} }
#else #else
@ -1078,9 +1090,36 @@ static bool openDocWithExtension (const char *document, const char *ext)
{ {
return true; return true;
} }
#elif defined(NL_OS_MAC)
return launchProgram("open", document);
#else #else
// TODO: implement for Linux and Mac OS X std::string command = "/usr/bin/xdg-open";
nlunreferenced(document);
if (!CFile::fileExists(command))
{
if (strcmp(ext, "htm") == 0)
{
command = "/etc/alternatives/x-www-browser";
if (!CFile::fileExists(command))
{
command.clear();
}
}
else
{
command.clear();
}
}
if (command.empty())
{
nlwarning("Unable to open %s", document);
return false;
}
return launchProgram(command, document);
#endif // NL_OS_WINDOWS #endif // NL_OS_WINDOWS
return false; return false;
@ -1088,17 +1127,7 @@ static bool openDocWithExtension (const char *document, const char *ext)
bool openURL (const char *url) bool openURL (const char *url)
{ {
#ifdef NL_OS_WINDOWS
return openDocWithExtension(url, "htm"); return openDocWithExtension(url, "htm");
#elif defined(NL_OS_MAC)
return launchProgram("open", url);
#elif defined(NL_OS_UNIX)
return launchProgram("/etc/alternatives/x-www-browser", url);
#else
nlwarning("openURL() is not implemented for this OS");
#endif // NL_OS_WINDOWS
return false;
} }
bool openDoc (const char *document) bool openDoc (const char *document)

View file

@ -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
* <http://www.gnu.org/licenses/>.
*/
#include "stdmisc.h"
#include "nel/misc/tool_logger.h"
// STL includes
// NeL includes
// #include <nel/misc/debug.h>
// 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 */

View file

@ -1,6 +1,11 @@
IF(WITH_NEL_TOOLS) IF(WITH_NEL_TOOLS)
IF(WITH_3D) IF(WITH_3D)
IF(WITH_ASSIMP)
SUBDIRS(
mesh_utils
mesh_export)
ENDIF()
SUBDIRS( SUBDIRS(
anim_builder anim_builder
animation_set_builder animation_set_builder
@ -27,7 +32,6 @@ IF(WITH_NEL_TOOLS)
zone_dump zone_dump
zviewer) zviewer)
ENDIF() ENDIF()
SUBDIRS( SUBDIRS(
build_interface build_interface
get_neighbors get_neighbors

View file

@ -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)

View file

@ -0,0 +1,77 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2015 Winch Gate Property Limited
// Author: Jan Boon <jan.boon@kaetemi.be>
//
// 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 <http://www.gnu.org/licenses/>.
#include <nel/misc/types_nl.h>
#include "../mesh_utils/mesh_utils.h"
#include <nel/misc/cmd_args.h>
#include <nel/misc/path.h>
#include <nel/3d/register_3d.h>
#include <nel/3d/scene.h>
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 */

View file

@ -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)

View file

@ -0,0 +1,228 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2015 Winch Gate Property Limited
// Author: Jan Boon <jan.boon@kaetemi.be>
//
// 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 <http://www.gnu.org/licenses/>.
#include <nel/misc/types_nl.h>
#include "assimp_shape.h"
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#include <assimp/Importer.hpp>
#define NL_NODE_INTERNAL_TYPE aiNode
#define NL_SCENE_INTERNAL_TYPE aiScene
#include "scene_context.h"
#include <nel/misc/debug.h>
#include <nel/misc/path.h>
#include <nel/misc/tool_logger.h>
#include <nel/3d/mesh.h>
#include <nel/3d/texture_file.h>
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<CTextureFile> 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<CMaterial> assimpMaterial(CMeshUtilsContext &context, const aiMaterial *am)
{
CSmartPtr<CMaterial> matp = new CMaterial();
CMaterial &mat = *matp;
assimpMaterial(mat, context, am);
return matp;
}
void assimpMaterials(CMeshUtilsContext &context)
{
set<CSString> 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 */

View file

@ -0,0 +1,28 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2015 Winch Gate Property Limited
// Author: Jan Boon <jan.boon@kaetemi.be>
//
// 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 <http://www.gnu.org/licenses/>.
#include <nel/misc/types_nl.h>
namespace NL3D {
class CMaterial;
}
struct CMeshUtilsContext;
void assimpMaterial(NL3D::CMaterial &mat, CMeshUtilsContext &context, const aiMaterial *am);
void assimpMaterials(CMeshUtilsContext &context);
/* end of file */

View file

@ -0,0 +1,379 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2015 Winch Gate Property Limited
// Author: Jan Boon <jan.boon@kaetemi.be>
//
// 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 <http://www.gnu.org/licenses/>.
#include <nel/misc/types_nl.h>
#include "assimp_shape.h"
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#include <assimp/Importer.hpp>
#define NL_NODE_INTERNAL_TYPE aiNode
#define NL_SCENE_INTERNAL_TYPE aiScene
#include "scene_context.h"
#include <nel/misc/debug.h>
#include <nel/misc/path.h>
#include <nel/misc/tool_logger.h>
#include <nel/3d/mesh.h>
#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<CVector, sint32> vertexIdentifiers;
vector<vector<sint32> > 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<CVector, sint32>::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 */

View file

@ -0,0 +1,25 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2015 Winch Gate Property Limited
// Author: Jan Boon <jan.boon@kaetemi.be>
//
// 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 <http://www.gnu.org/licenses/>.
#include <nel/misc/types_nl.h>
struct CMeshUtilsContext;
struct CNodeContext;
bool assimpShape(CMeshUtilsContext &context, CNodeContext &nodeContext);
/* end of file */

View file

@ -0,0 +1,107 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2015 Winch Gate Property Limited
// Author: Jan Boon <jan.boon@kaetemi.be>
//
// 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 <http://www.gnu.org/licenses/>.
#include <nel/misc/types_nl.h>
#include "database_config.h"
#include <nel/misc/debug.h>
#include <nel/misc/path.h>
#include <nel/misc/config_file.h>
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<TPathString> 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 */

View file

@ -0,0 +1,58 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2015 Winch Gate Property Limited
// Author: Jan Boon <jan.boon@kaetemi.be>
//
// 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 <http://www.gnu.org/licenses/>.
#include <nel/misc/types_nl.h>
namespace NLMISC {
class CConfigFile;
}
#ifdef NL_OS_WINDOWS
#include <nel/misc/sstring.h>
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 */

View file

@ -0,0 +1,348 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2015 Winch Gate Property Limited
// Author: Jan Boon <jan.boon@kaetemi.be>
//
// 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 <http://www.gnu.org/licenses/>.
#include <nel/misc/types_nl.h>
#include "mesh_utils.h"
#include <nel/misc/debug.h>
#include <nel/misc/tool_logger.h>
#include <nel/misc/sstring.h>
#include <nel/misc/file.h>
#include <nel/misc/path.h>
#include <nel/3d/shape.h>
#include <nel/3d/mesh.h>
#include <nel/3d/texture_file.h>
#include "database_config.h"
#include "scene_meta.h"
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#include <assimp/Importer.hpp>
#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<NL3D::CMeshBase *>(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<NL3D::CTextureFile *>(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 */

View file

@ -0,0 +1,44 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2015 Winch Gate Property Limited
// Author: Jan Boon <jan.boon@kaetemi.be>
//
// 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 <http://www.gnu.org/licenses/>.
#ifndef NL_MESH_UTILS_H
#define NL_MESH_UTILS_H
#include <nel/misc/types_nl.h>
#include <string>
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 */

View file

@ -0,0 +1,30 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2015 Winch Gate Property Limited
// Author: Jan Boon <jan.boon@kaetemi.be>
//
// 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 <http://www.gnu.org/licenses/>.
#include <nel/misc/types_nl.h>
#include "scene_context.h"
#include <nel/misc/debug.h>
#include <nel/misc/path.h>
#include <nel/misc/tool_logger.h>
using namespace std;
using namespace NLMISC;
void dummy_scene_context_cpp();
/* end of file */

View file

@ -0,0 +1,81 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2015 Winch Gate Property Limited
// Author: Jan Boon <jan.boon@kaetemi.be>
//
// 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 <http://www.gnu.org/licenses/>.
#ifndef NL_SCENE_CONTEXT_H
#define NL_SCENE_CONTEXT_H
#include <nel/misc/types_nl.h>
#include "mesh_utils.h"
#include "scene_meta.h"
#include <nel/misc/sstring.h>
#include <nel/misc/tool_logger.h>
#include <nel/misc/smart_ptr.h>
#include <nel/misc/matrix.h>
#include <nel/3d/shape.h>
#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<NL3D::IShape> Shape;
};
typedef std::map<NLMISC::CSString, CNodeContext> 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<const aiMesh *, NLMISC::CSString> MeshNames; // Maps meshes to a node name ********************* todo ***************
};
#endif /* NL_SCENE_CONTEXT_H */
/* end of file */

View file

@ -0,0 +1,100 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2015 Winch Gate Property Limited
// Author: Jan Boon <jan.boon@kaetemi.be>
//
// 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 <http://www.gnu.org/licenses/>.
#include <nel/misc/types_nl.h>
#include "scene_meta.h"
#include <nel/misc/debug.h>
#include <nel/misc/stream.h>
#include <nel/misc/file.h>
#include <nel/3d/material.h>
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 */

View file

@ -0,0 +1,108 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2015 Winch Gate Property Limited
// Author: Jan Boon <jan.boon@kaetemi.be>
//
// 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 <http://www.gnu.org/licenses/>.
#ifndef NL_SCENE_META_H
#define NL_SCENE_META_H
#include <nel/misc/types_nl.h>
#include <nel/misc/sstring.h>
#include <nel/misc/smart_ptr.h>
#include <nel/3d/material.h>
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<NLMISC::CSString> 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<NLMISC::CSString, NLMISC::CSmartPtr<NL3D::CMaterial> > 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<NLMISC::CSString, CNodeMeta> 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 */

View file

@ -1000,7 +1000,7 @@ void CPatchManager::executeBatchFile()
if (execl(strCmdLine.c_str(), strCmdLine.c_str(), LoginLogin.c_str(), LoginPassword.c_str(), (char *) NULL) == -1) if (execl(strCmdLine.c_str(), strCmdLine.c_str(), LoginLogin.c_str(), LoginPassword.c_str(), (char *) NULL) == -1)
{ {
int errsv = errno; int errsv = errno;
nlerror("Execl Error: %d %s", errsv, strCmdLine.c_str(), (char *) NULL); nlerror("Execl Error: %d %s", errsv, strCmdLine.c_str());
} }
else else
{ {