From e94e26ae1eb0dd8a3838e66db3abe4006d377ab2 Mon Sep 17 00:00:00 2001 From: Piotr Dziwinski Date: Fri, 31 Aug 2012 20:55:16 +0200 Subject: New model file format - added new binary and text formats for models - fixes and improvements in CModelFile - tool for converting model files - minor additions and fixes --- src/CMakeLists.txt | 5 +- src/app/app.cpp | 29 +- src/common/ioutils.h | 80 ++- src/common/singleton.h | 10 +- src/graphics/engine/modelfile.cpp | 1037 ++++++++++++++++++++++++++----------- src/graphics/engine/modelfile.h | 62 ++- src/graphics/engine/terrain.cpp | 2 +- src/graphics/engine/terrain.h | 2 +- src/tools/CMakeLists.txt | 13 + src/tools/README.txt | 4 + src/tools/convert_model.cpp | 285 ++++++++++ 11 files changed, 1179 insertions(+), 350 deletions(-) create mode 100644 src/tools/CMakeLists.txt create mode 100644 src/tools/README.txt create mode 100644 src/tools/convert_model.cpp (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b998d19..ff271fd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,8 @@ # CBot shared library is built separately -add_subdirectory(CBot) +add_subdirectory(CBot) + +# Tools directory is built separately +add_subdirectory(tools) # Configure options diff --git a/src/app/app.cpp b/src/app/app.cpp index 182e0fd..00cd13d 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -113,6 +113,7 @@ CApplication::~CApplication() bool CApplication::ParseArguments(int argc, char *argv[]) { bool waitDataDir = false; + bool waitLogLevel = false; for (int i = 1; i < argc; ++i) { @@ -125,10 +126,34 @@ bool CApplication::ParseArguments(int argc, char *argv[]) continue; } + if (waitLogLevel) + { + waitLogLevel = false; + if (arg == "trace") + GetLogger()->SetLogLevel(LOG_TRACE); + else if (arg == "debug") + GetLogger()->SetLogLevel(LOG_DEBUG); + else if (arg == "info") + GetLogger()->SetLogLevel(LOG_INFO); + else if (arg == "warn") + GetLogger()->SetLogLevel(LOG_WARN); + else if (arg == "error") + GetLogger()->SetLogLevel(LOG_ERROR); + else if (arg == "none") + GetLogger()->SetLogLevel(LOG_NONE); + else + return false; + continue; + } + if (arg == "-debug") { SetDebugMode(true); } + else if (arg == "-loglevel") + { + waitLogLevel = true; + } else if (arg == "-datadir") { waitDataDir = true; @@ -140,8 +165,8 @@ bool CApplication::ParseArguments(int argc, char *argv[]) } } - // Data dir not given? - if (waitDataDir) + // Args not given? + if (waitDataDir || waitLogLevel) return false; return true; diff --git a/src/common/ioutils.h b/src/common/ioutils.h index a7bd044..2a542c6 100644 --- a/src/common/ioutils.h +++ b/src/common/ioutils.h @@ -27,9 +27,10 @@ namespace IOUtils { //! Writes a binary number to output stream /** - \c T is a numeric type (int, unsigned int, etc.) - \c N is number of bytes - Write order is little-endian */ + * \c T is a numeric type (int, unsigned int, etc.) + * \c N is number of bytes + * Write order is little-endian + */ template void WriteBinary(T value, std::ostream &ostr) { @@ -42,9 +43,10 @@ void WriteBinary(T value, std::ostream &ostr) //! Reads a binary number from input stream /** - \c T is a numeric type (int, unsigned int, etc.) - \c N is number of bytes - Read order is little-endian */ + * \c T is a numeric type (int, unsigned int, etc.) + * \c N is number of bytes + * Read order is little-endian + */ template T ReadBinary(std::istream &istr) { @@ -58,10 +60,31 @@ T ReadBinary(std::istream &istr) return value; } +//! Writes a binary 1-byte boolean +/** + * false is 0; true is 1. + */ +void WriteBinaryBool(float value, std::ostream &ostr) +{ + unsigned char v = value ? 1 : 0; + IOUtils::WriteBinary<1, unsigned char>(v, ostr); +} + +//! Reads a binary 1-byte boolean +/** + * 0 is false; other values are true. + */ +bool ReadBinaryBool(std::istream &istr) +{ + int v = IOUtils::ReadBinary<1, unsigned char>(istr); + return v != 0; +} + //! Writes a binary 32-bit float to output stream /** - Write order is little-endian - NOTE: code is probably not portable as there are platforms with other float representations. */ + * Write order is little-endian + * NOTE: code is probably not portable as there are platforms with other float representations. + */ void WriteBinaryFloat(float value, std::ostream &ostr) { union { float fValue; unsigned int iValue; } u; @@ -72,8 +95,9 @@ void WriteBinaryFloat(float value, std::ostream &ostr) //! Reads a binary 32-bit float from input stream /** - Read order is little-endian - NOTE: code is probably not portable as there are platforms with other float representations. */ + * Read order is little-endian + * NOTE: code is probably not portable as there are platforms with other float representations. + */ float ReadBinaryFloat(std::istream &istr) { union { float fValue; unsigned int iValue; } u; @@ -82,4 +106,40 @@ float ReadBinaryFloat(std::istream &istr) return u.fValue; } +//! Writes a variable binary string to output stream +/** + * The string is written by first writing string length + * in \c N byte binary number and then the string bytes. + */ +template +void WriteBinaryString(const std::string &value, std::ostream &ostr) +{ + int length = value.size(); + WriteBinary(length, ostr); + + for (int i = 0; i < length; ++i) + ostr.put(value[i]); +} + +//! Reads a variable binary string from output stream +/** + * The string is read by first reading string length + * in \c N byte binary number and then the string bytes. + */ +template +std::string ReadBinaryString(std::istream &istr) +{ + int length = ReadBinary(istr); + + std::string str; + char c = 0; + for (int i = 0; i < length; ++i) + { + istr.read(&c, 1); + str += c; + } + + return str; +} + }; // namespace IOUtils diff --git a/src/common/singleton.h b/src/common/singleton.h index 4df7878..f631ed4 100644 --- a/src/common/singleton.h +++ b/src/common/singleton.h @@ -29,26 +29,26 @@ template class CSingleton public: static T& GetInstance() { - assert(mInstance != NULL); + assert(mInstance != nullptr); return *mInstance; } static T* GetInstancePointer() { - assert(mInstance != NULL); + assert(mInstance != nullptr); return mInstance; } static bool IsCreated() { - return mInstance != NULL; + return mInstance != nullptr; } CSingleton() { - assert(mInstance == NULL); + assert(mInstance == nullptr); mInstance = static_cast(this); } virtual ~CSingleton() { - mInstance = NULL; + mInstance = nullptr; } private: diff --git a/src/graphics/engine/modelfile.cpp b/src/graphics/engine/modelfile.cpp index 9d7a389..20a0932 100644 --- a/src/graphics/engine/modelfile.cpp +++ b/src/graphics/engine/modelfile.cpp @@ -23,104 +23,28 @@ #include "common/ioutils.h" #include "common/logger.h" #include "common/stringutils.h" +#include "graphics/engine/engine.h" #include "math/geometry.h" #include #include +#include -//! How big the triangle vector is by default -const int TRIANGLE_PREALLOCATE_COUNT = 2000; - -/** - \struct ModelHeader - \brief Header info for model file +/* + * NOTE: #ifndef checking for MODELFILE_NO_ENGINE + * is provided in this module to conditionally + * disable dependence on CEngine. */ -struct ModelHeader -{ - //! Revision number - int revision; - //! Version number - int version; - //! Total number of vertices - int totalVertices; - //! Reserved area - int reserved[10]; - - ModelHeader() - { - memset(this, 0, sizeof(*this)); - } -}; - - -struct OldModelTriangle1 -{ - char used; - char selected; - Gfx::Vertex p1; - Gfx::Vertex p2; - Gfx::Vertex p3; - Gfx::Material material; - char texName[20]; - float min; - float max; - - OldModelTriangle1() - { - memset(this, 0, sizeof(*this)); - } -}; - -struct OldModelTriangle2 -{ - char used; - char selected; - Gfx::Vertex p1; - Gfx::Vertex p2; - Gfx::Vertex p3; - Gfx::Material material; - char texName[20]; - float min; - float max; - long state; - short reserved1; - short reserved2; - short reserved3; - short reserved4; - OldModelTriangle2() - { - memset(this, 0, sizeof(*this)); - } -}; -struct NewModelTriangle -{ - char used; - char selected; - Gfx::VertexTex2 p1; - Gfx::VertexTex2 p2; - Gfx::VertexTex2 p3; - Gfx::Material material; - char texName[20]; - float min; - float max; - long state; - short texNum2; - short reserved2; - short reserved3; - short reserved4; +//! How big the triangle vector is by default +const int TRIANGLE_PREALLOCATE_COUNT = 2000; - NewModelTriangle() - { - memset(this, 0, sizeof(*this)); - } -}; -Gfx::Vertex ReadBinaryVertex(std::istream &stream) +Gfx::Vertex ReadBinaryVertex(std::istream& stream) { Gfx::Vertex result; @@ -136,7 +60,7 @@ Gfx::Vertex ReadBinaryVertex(std::istream &stream) return result; } -void WriteBinaryVertex(Gfx::Vertex vertex, std::ostream &stream) +void WriteBinaryVertex(Gfx::Vertex vertex, std::ostream& stream) { IOUtils::WriteBinaryFloat(vertex.coord.x, stream); IOUtils::WriteBinaryFloat(vertex.coord.y, stream); @@ -148,7 +72,7 @@ void WriteBinaryVertex(Gfx::Vertex vertex, std::ostream &stream) IOUtils::WriteBinaryFloat(vertex.texCoord.y, stream); } -Gfx::VertexTex2 ReadBinaryVertexTex2(std::istream &stream) +Gfx::VertexTex2 ReadBinaryVertexTex2(std::istream& stream) { Gfx::VertexTex2 result; @@ -166,7 +90,7 @@ Gfx::VertexTex2 ReadBinaryVertexTex2(std::istream &stream) return result; } -void WriteBinaryVertexTex2(Gfx::VertexTex2 vertex, std::ostream &stream) +void WriteBinaryVertexTex2(Gfx::VertexTex2 vertex, std::ostream& stream) { IOUtils::WriteBinaryFloat(vertex.coord.x, stream); IOUtils::WriteBinaryFloat(vertex.coord.y, stream); @@ -180,7 +104,63 @@ void WriteBinaryVertexTex2(Gfx::VertexTex2 vertex, std::ostream &stream) IOUtils::WriteBinaryFloat(vertex.texCoord2.y, stream); } -Gfx::Material ReadBinaryMaterial(std::istream &stream) +Gfx::VertexTex2 ReadTextVertexTex2(const std::string& text) +{ + std::stringstream stream; + stream.str(text); + + Gfx::VertexTex2 result; + std::string what; + + stream >> what; + if (what != "c") + { + GetLogger()->Error("c\n"); + return Gfx::VertexTex2(); + } + + stream >> result.coord.x >> result.coord.y >> result.coord.z; + + stream >> what; + if (what != "n") + { + GetLogger()->Error("n\n"); + return Gfx::VertexTex2(); + } + + stream >> result.normal.x >> result.normal.y >> result.normal.z; + + stream >> what; + if (what != "t1") + { + GetLogger()->Error("t1\n"); + return Gfx::VertexTex2(); + } + + stream >> result.texCoord.x >> result.texCoord.y; + + stream >> what; + if (what != "t2") + { + GetLogger()->Error("t2\n"); + return Gfx::VertexTex2(); + } + + stream >> result.texCoord2.x >> result.texCoord2.y; + + return result; +} + +void WriteTextVertexTex2(const Gfx::VertexTex2& vertex, std::ostream& stream) +{ + stream << "c " << vertex.coord.x << " " << vertex.coord.y << " " << vertex.coord.z; + stream << " n " << vertex.normal.x << " " << vertex.normal.y << " " << vertex.normal.z; + stream << " t1 " << vertex.texCoord.x << " " << vertex.texCoord.y; + stream << " t2 " << vertex.texCoord.x << " " << vertex.texCoord.y; + stream << std::endl; +} + +Gfx::Material ReadBinaryMaterial(std::istream& stream) { Gfx::Material result; @@ -209,7 +189,7 @@ Gfx::Material ReadBinaryMaterial(std::istream &stream) return result; } -void WriteBinaryMaterial(Gfx::Material material, std::ostream &stream) +void WriteBinaryMaterial(const Gfx::Material& material, std::ostream& stream) { IOUtils::WriteBinaryFloat(material.diffuse.r, stream); IOUtils::WriteBinaryFloat(material.diffuse.g, stream); @@ -234,11 +214,87 @@ void WriteBinaryMaterial(Gfx::Material material, std::ostream &stream) /* power */ IOUtils::WriteBinaryFloat(0.0f, stream); } -Gfx::ModelTriangle::ModelTriangle() +Gfx::Material ReadTextMaterial(const std::string& text) +{ + std::stringstream stream; + stream.str(text); + + Gfx::Material result; + std::string what; + + stream >> what; + if (what != "dif") + return Gfx::Material(); + + stream >> result.diffuse.r >> result.diffuse.g >> result.diffuse.b >> result.diffuse.a; + + stream >> what; + if (what != "amb") + return Gfx::Material(); + + stream >> result.ambient.r >> result.ambient.g >> result.ambient.b >> result.ambient.a; + + stream >> what; + if (what != "spc") + return Gfx::Material(); + + stream >> result.specular.r >> result.specular.g >> result.specular.b >> result.specular.a; + + return result; +} + +void WriteTextMaterial(const Gfx::Material& material, std::ostream& stream) +{ + stream << "dif " << material.diffuse.r << " " << material.diffuse.g << " " << material.diffuse.b << " " << material.diffuse.a; + stream << " amb " << material.ambient.r << " " << material.ambient.g << " " << material.ambient.b << " " << material.ambient.a; + stream << " spc " << material.specular.r << " " << material.specular.g << " " << material.specular.b << " " << material.specular.a << std::endl; +} + +template +bool ReadLineValue(std::istream& stream, const std::string& prefix, T& value) +{ + std::string line; + while (! stream.eof() ) + { + std::getline(stream, line); + if (!line.empty() && line[0] != '#') + break; + } + + std::stringstream s; + s.str(line); + + std::string what; + s >> what; + if (what != prefix) + return false; + + s >> value; + + return true; +} + +bool ReadLineString(std::istream& stream, const std::string& prefix, std::string& value) { - min = 0.0f; - max = 0.0f; - state = 0L; + std::string line; + while (! stream.eof() ) + { + std::getline(stream, line); + if (!line.empty() && line[0] != '#') + break; + } + + std::stringstream s; + s.str(line); + + std::string what; + s >> what; + if (what != prefix) + return false; + + getline(s, value); + + return true; } @@ -246,7 +302,9 @@ Gfx::CModelFile::CModelFile(CInstanceManager* iMan) { m_iMan = iMan; +#ifndef MODELFILE_NO_ENGINE m_engine = static_cast(m_iMan->SearchInstance(CLASS_ENGINE)); +#endif m_triangles.reserve(TRIANGLE_PREALLOCATE_COUNT); } @@ -255,54 +313,154 @@ Gfx::CModelFile::~CModelFile() { } -std::string Gfx::CModelFile::GetError() + +/******************************************************* + Deprecated formats + *******************************************************/ + +/** + * \struct OldModelHeader + * \brief Colobot binary model header info + * + * @deprecated + */ +struct OldModelHeader { - return m_error; -} + //! Revision number + int revision; + //! Version number + int version; + //! Total number of triangles + int totalTriangles; + //! Reserved area + int reserved[10]; + + OldModelHeader() + { + memset(this, 0, sizeof(*this)); + } +}; -bool Gfx::CModelFile::ReadModel(const std::string &filename, bool edit, bool meta) +/** + * \struct OldModelTriangle1 + * \brief Colobot binary model file version 1 + * + * @deprecated + */ +struct OldModelTriangle1 +{ + char used; + char selected; + Gfx::Vertex p1; + Gfx::Vertex p2; + Gfx::Vertex p3; + Gfx::Material material; + char texName[20]; + float min; + float max; + + OldModelTriangle1() + { + memset(this, 0, sizeof(*this)); + } +}; + +/** + * \struct OldModelTriangle2 + * \brief Colobot binary model file version 2 + * + * @deprecated + */ +struct OldModelTriangle2 +{ + char used; + char selected; + Gfx::Vertex p1; + Gfx::Vertex p2; + Gfx::Vertex p3; + Gfx::Material material; + char texName[20]; + float min; + float max; + long state; + short reserved1; + short reserved2; + short reserved3; + short reserved4; + OldModelTriangle2() + { + memset(this, 0, sizeof(*this)); + } +}; + +/** + * \struct OldModelTriangle3 + * \brief Colobot binary model file version 3 + * + * @deprecated + */ +struct OldModelTriangle3 +{ + char used; + char selected; + Gfx::VertexTex2 p1; + Gfx::VertexTex2 p2; + Gfx::VertexTex2 p3; + Gfx::Material material; + char texName[20]; + float min; + float max; + long state; + short texNum2; + short reserved2; + short reserved3; + short reserved4; + + OldModelTriangle3() + { + memset(this, 0, sizeof(*this)); + } +}; + +bool Gfx::CModelFile::ReadModel(const std::string& fileName) { m_triangles.clear(); - m_error = ""; std::ifstream stream; - stream.open(filename.c_str(), std::ios_base::in | std::ios_base::binary); + stream.open(fileName.c_str(), std::ios_base::in | std::ios_base::binary); if (! stream.good()) { - m_error = std::string("Could not open file '") + filename + std::string("'"); + GetLogger()->Error("Could not open file '%s'\n", fileName.c_str()); return false; } - return ReadModel(stream, edit, meta); + return ReadModel(stream); } -bool Gfx::CModelFile::ReadModel(std::istream &stream, bool edit, bool meta) +bool Gfx::CModelFile::ReadModel(std::istream& stream) { m_triangles.clear(); - m_error = ""; - // FIXME: for now, reading models only from files, not metafile - - ModelHeader header; + OldModelHeader header; header.revision = IOUtils::ReadBinary<4, int>(stream); header.version = IOUtils::ReadBinary<4, int>(stream); - header.totalVertices = IOUtils::ReadBinary<4, int>(stream); + header.totalTriangles = IOUtils::ReadBinary<4, int>(stream); for (int i = 0; i < 10; ++i) header.reserved[i] = IOUtils::ReadBinary<4, int>(stream); if (! stream.good()) { - m_error = "Error reading model file header"; + GetLogger()->Error("Error reading model file header\n"); return false; } // Old model version #1 if ( (header.revision == 1) && (header.version == 0) ) { - for (int i = 0; i < header.totalVertices; ++i) + for (int i = 0; i < header.totalTriangles; ++i) { OldModelTriangle1 t; t.used = IOUtils::ReadBinary<1, char>(stream); @@ -319,7 +477,7 @@ bool Gfx::CModelFile::ReadModel(std::istream &stream, bool edit, bool meta) if (! stream.good()) { - m_error = "Error reading model data"; + GetLogger()->Error("Error reading model data\n"); return false; } @@ -338,7 +496,7 @@ bool Gfx::CModelFile::ReadModel(std::istream &stream, bool edit, bool meta) } else if ( header.revision == 1 && header.version == 1 ) { - for (int i = 0; i < header.totalVertices; ++i) + for (int i = 0; i < header.totalTriangles; ++i) { OldModelTriangle2 t; t.used = IOUtils::ReadBinary<1, char>(stream); @@ -361,7 +519,7 @@ bool Gfx::CModelFile::ReadModel(std::istream &stream, bool edit, bool meta) if (! stream.good()) { - m_error = "Error reading model data"; + GetLogger()->Error("Error reading model data\n"); return false; } @@ -381,9 +539,9 @@ bool Gfx::CModelFile::ReadModel(std::istream &stream, bool edit, bool meta) } else { - for (int i = 0; i < header.totalVertices; ++i) + for (int i = 0; i < header.totalTriangles; ++i) { - NewModelTriangle t; + OldModelTriangle3 t; t.used = IOUtils::ReadBinary<1, char>(stream); t.selected = IOUtils::ReadBinary<1, char>(stream); @@ -406,7 +564,7 @@ bool Gfx::CModelFile::ReadModel(std::istream &stream, bool edit, bool meta) if (! stream.good()) { - m_error = "Error reading model data"; + GetLogger()->Error("Error reading model data\n"); return false; } @@ -417,15 +575,26 @@ bool Gfx::CModelFile::ReadModel(std::istream &stream, bool edit, bool meta) triangle.material = t.material; triangle.tex1Name = std::string(t.texName); - char tex2Name[20] = { 0 }; triangle.min = t.min; triangle.max = t.max; triangle.state = t.state; + triangle.variableTex2 = t.texNum2 == 1; + + if (triangle.tex1Name == "plant.png") + triangle.state |= Gfx::ENG_RSTATE_ALPHA; + + if (!triangle.variableTex2 && t.texNum2 != 0) + { + if (t.texNum2 >= 1 && t.texNum2 <= 10) + triangle.state |= Gfx::ENG_RSTATE_DUAL_BLACK; - if (t.texNum2 != 0) - sprintf(tex2Name, "dirty%.2d.tga", t.texNum2); // hardcoded as in the original code + if (t.texNum2 >= 11 && t.texNum2 <= 20) + triangle.state |= Gfx::ENG_RSTATE_DUAL_WHITE; - triangle.tex2Name = std::string(tex2Name); + char tex2Name[20] = { 0 }; + sprintf(tex2Name, "dirty%.2d.png", t.texNum2); // hardcoded as in original code + triangle.tex2Name = tex2Name; + } m_triangles.push_back(triangle); } @@ -433,7 +602,12 @@ bool Gfx::CModelFile::ReadModel(std::istream &stream, bool edit, bool meta) for (int i = 0; i < static_cast( m_triangles.size() ); ++i) { - m_triangles[i].tex1Name = StrUtils::Replace(m_triangles[i].tex1Name, "bmp", "tga"); + // All extensions are now png + m_triangles[i].tex1Name = StrUtils::Replace(m_triangles[i].tex1Name, "bmp", "png"); + m_triangles[i].tex1Name = StrUtils::Replace(m_triangles[i].tex1Name, "tga", "png"); + + m_triangles[i].tex2Name = StrUtils::Replace(m_triangles[i].tex2Name, "bmp", "png"); + m_triangles[i].tex2Name = StrUtils::Replace(m_triangles[i].tex2Name, "tga", "png"); GetLogger()->Info("ModelTriangle %d\n", i+1); std::string s1 = m_triangles[i].p1.ToString(); @@ -448,82 +622,50 @@ bool Gfx::CModelFile::ReadModel(std::istream &stream, bool edit, bool meta) std::string s = m_triangles[i].material.specular.ToString(); GetLogger()->Info(" mat: d: %s a: %s s: %s\n", d.c_str(), a.c_str(), s.c_str()); - GetLogger()->Info(" tex1: %s tex2: %s\n", m_triangles[i].tex1Name.c_str(), m_triangles[i].tex2Name.c_str()); + GetLogger()->Info(" tex1: %s tex2: %s\n", m_triangles[i].tex1Name.c_str(), + m_triangles[i].variableTex2 ? "(variable)" : m_triangles[i].tex2Name.c_str()); GetLogger()->Info(" min: %.2f max: %.2f\n", m_triangles[i].min, m_triangles[i].max); GetLogger()->Info(" state: %ld\n", m_triangles[i].state); } - /* - if (! edit) - { - float limit[2]; - limit[0] = m_engine->RetLimitLOD(0); // frontier AB as config - limit[1] = m_engine->RetLimitLOD(1); // frontier BC as config - - // Standard frontiers -> config. - for (int i = 0; i < m_triangles.size(); ++i) - { - if ( m_triangles[i].min == 0.0f && - m_triangles[i].max == 100.0f ) // resolution A ? - { - m_triangles[i].max = limit[0]; - } - else if ( m_triangles[i].min == 100.0f && - m_triangles[i].max == 200.0f ) // resolution B ? - { - m_triangles[i].min = limit[0]; - m_triangles[i].max = limit[1]; - } - else if ( m_triangles[i].min == 200.0f && - m_triangles[i].max == 1000000.0f ) // resolution C ? - { - m_triangles[i].min = limit[1]; - } - } - }*/ - return true; } -bool Gfx::CModelFile::WriteModel(const std::string &filename) +bool Gfx::CModelFile::WriteModel(const std::string& fileName) { - m_error = ""; - std::ofstream stream; - stream.open(filename.c_str(), std::ios_base::out | std::ios_base::binary); + stream.open(fileName.c_str(), std::ios_base::out | std::ios_base::binary); if (! stream.good()) { - m_error = std::string("Could not open file '") + filename + std::string("'"); + GetLogger()->Error("Could not open file '%s'\n", fileName.c_str()); return false; } return WriteModel(stream); } -bool Gfx::CModelFile::WriteModel(std::ostream &stream) +bool Gfx::CModelFile::WriteModel(std::ostream& stream) { - m_error = ""; - if (m_triangles.size() == 0) { - m_error = "Empty model"; + GetLogger()->Error("Empty model\n"); return false; } - ModelHeader header; + OldModelHeader header; header.revision = 1; header.version = 2; - header.totalVertices = m_triangles.size(); + header.totalTriangles = m_triangles.size(); IOUtils::WriteBinary<4, int>(header.revision, stream); IOUtils::WriteBinary<4, int>(header.version, stream); - IOUtils::WriteBinary<4, int>(header.totalVertices, stream); + IOUtils::WriteBinary<4, int>(header.totalTriangles, stream); for (int i = 0; i < 10; ++i) IOUtils::WriteBinary<4, int>(header.reserved[i], stream); for (int i = 0; i < static_cast( m_triangles.size() ); ++i) { - NewModelTriangle t; + OldModelTriangle3 t; t.used = true; @@ -536,14 +678,21 @@ bool Gfx::CModelFile::WriteModel(std::ostream &stream) t.min = m_triangles[i].min; t.max = m_triangles[i].max; t.state = m_triangles[i].state; + int no = 0; - sscanf(m_triangles[i].tex2Name.c_str(), "dirty%d.tga", &no); // hardcoded as in the original code + if (m_triangles[i].variableTex2) + no = 1; + else + sscanf(m_triangles[i].tex2Name.c_str(), "dirty%d.png", &no); // hardcoded as in the original code + t.texNum2 = no; IOUtils::WriteBinary<1, char>(t.used, stream); IOUtils::WriteBinary<1, char>(t.selected, stream); + /* padding */ IOUtils::WriteBinary<2, unsigned int>(0, stream); + WriteBinaryVertexTex2(t.p1, stream); WriteBinaryVertexTex2(t.p2, stream); WriteBinaryVertexTex2(t.p3, stream); @@ -563,215 +712,481 @@ bool Gfx::CModelFile::WriteModel(std::ostream &stream) return true; } -bool Gfx::CModelFile::ReadDXF(const std::string &filename, float min, float max) + +/******************************************************* + New formats + *******************************************************/ + +/** + * \struct NewModelHeader + * \brief Header for new binary model file + */ +struct NewModelHeader { - m_triangles.clear(); - m_error = ""; + //! File version (1, 2, ...) + int version; + //! Total number of triangles + int totalTriangles; + + NewModelHeader() + { + version = 0; + totalTriangles = 0; + } +}; +/** + * \struct NewModelTriangle1 + * \brief Triangle of new binary model file + * + * NOTE: at this time, it is identical to ModelTriangle struct, but it may change + * independently in the future. + */ +struct NewModelTriangle1 +{ + //! 1st vertex + Gfx::VertexTex2 p1; + //! 2nd vertex + Gfx::VertexTex2 p2; + //! 3rd vertex + Gfx::VertexTex2 p3; + //! Material + Gfx::Material material; + //! Name of 1st texture + std::string tex1Name; + //! Name of 2nd texture + std::string tex2Name; + //! If true, 2nd texture will be taken from current engine setting + bool variableTex2; + //! Min LOD threshold + float min; + //! Max LOD threshold + float max; + //! Rendering state to be set + int state; + + NewModelTriangle1() + { + variableTex2 = true; + min = max = 0.0f; + state = 0; + } +}; + + +bool Gfx::CModelFile::ReadTextModel(const std::string& fileName) +{ std::ifstream stream; - stream.open(filename.c_str(), std::ios_base::in); + stream.open(fileName.c_str(), std::ios_base::in); if (! stream.good()) { - m_error = std::string("Couldn't open file '") + filename + std::string("'"); + GetLogger()->Error("Could not open file '%s'\n", fileName.c_str()); return false; } - return ReadDXF(stream, min, max); + return ReadTextModel(stream); } -bool Gfx::CModelFile::ReadDXF(std::istream &stream, float min, float max) +bool Gfx::CModelFile::ReadTextModel(std::istream& stream) { m_triangles.clear(); - m_error = ""; - if (! stream.good()) + NewModelHeader header; + + bool headOk = ReadLineValue(stream, "version", header.version) && + ReadLineValue(stream, "total_triangles", header.totalTriangles); + + if (!headOk || !stream.good()) { - m_error = "Invalid stream"; + GetLogger()->Error("Error reading model file header\n"); return false; } - // Input state - bool waitNumVertex = false; - bool waitNumFace = false; - bool waitVertexX = false; - bool waitVertexY = false; - bool waitVertexZ = false; - bool waitFaceX = false; - bool waitFaceY = false; - bool waitFaceZ = false; - - // Vertex array - std::vector vertices; - vertices.reserve(TRIANGLE_PREALLOCATE_COUNT); - - // Number of vertices & faces of the primitive to be read - int vertexNum = 0, faceNum = 0; - // Vertex coords - Math::Vector coords; - // Indexes of face (triangle) points - int p1 = 0, p2 = 0, p3 = 0; - - // Input line - std::string line; - while (! stream.eof() ) + // New model version 1 + if (header.version == 1) { - // Read line with command - std::getline(stream, line); - int command = StrUtils::FromString(line); + for (int i = 0; i < header.totalTriangles; ++i) + { + NewModelTriangle1 t; + + std::string p1Text, p2Text, p3Text; + std::string matText; + char varTex2Ch = 0; + + bool triOk = ReadLineString(stream, "p1", p1Text) && + ReadLineString(stream, "p2", p2Text) && + ReadLineString(stream, "p3", p3Text) && + ReadLineString(stream, "mat", matText) && + ReadLineValue(stream, "tex1", t.tex1Name) && + ReadLineValue(stream, "tex2", t.tex2Name) && + ReadLineValue(stream, "var_tex2", varTex2Ch) && + ReadLineValue(stream, "min", t.min) && + ReadLineValue(stream, "max", t.max); + + if (!triOk || !stream.good()) + { + GetLogger()->Error("Error reading model file header\n"); + return false; + } - // Read line with param - std::getline(stream, line); + t.p1 = ReadTextVertexTex2(p1Text); + t.p2 = ReadTextVertexTex2(p2Text); + t.p3 = ReadTextVertexTex2(p3Text); + t.material = ReadTextMaterial(matText); + t.variableTex2 = varTex2Ch == 'Y'; - bool ok = true; + Gfx::ModelTriangle triangle; + triangle.p1 = t.p1; + triangle.p2 = t.p2; + triangle.p3 = t.p3; + triangle.material = t.material; + triangle.tex1Name = t.tex1Name; + triangle.tex2Name = t.tex2Name; + triangle.variableTex2 = t.variableTex2; + triangle.min = t.min; + triangle.max = t.max; - if (command == 66) - { - waitNumVertex = true; - } + m_triangles.push_back(triangle); - if ( command == 71 && waitNumVertex ) - { - waitNumVertex = false; - vertexNum = StrUtils::FromString(line, &ok); - waitNumFace = true; + continue; } + } + else + { + GetLogger()->Error("Unknown model file version\n"); + return false; + } - if ( command == 72 && waitNumFace ) - { - waitNumFace = false; - faceNum = StrUtils::FromString(line, &ok); - waitVertexX = true; - } + for (int i = 0; i < static_cast( m_triangles.size() ); ++i) + { + GetLogger()->Info("ModelTriangle %d\n", i+1); + std::string s1 = m_triangles[i].p1.ToString(); + GetLogger()->Info(" p1: %s\n", s1.c_str()); + std::string s2 = m_triangles[i].p2.ToString(); + GetLogger()->Info(" p2: %s\n", s2.c_str()); + std::string s3 = m_triangles[i].p3.ToString(); + GetLogger()->Info(" p3: %s\n", s3.c_str()); - if ( command == 10 && waitVertexX ) - { - waitVertexX = false; - coords.x = StrUtils::FromString(line, &ok); - waitVertexY = true; - } + std::string d = m_triangles[i].material.diffuse.ToString(); + std::string a = m_triangles[i].material.ambient.ToString(); + std::string s = m_triangles[i].material.specular.ToString(); + GetLogger()->Info(" mat: d: %s a: %s s: %s\n", d.c_str(), a.c_str(), s.c_str()); + + GetLogger()->Info(" tex1: %s tex2: %s\n", m_triangles[i].tex1Name.c_str(), m_triangles[i].tex2Name.c_str()); + GetLogger()->Info(" min: %.2f max: %.2f\n", m_triangles[i].min, m_triangles[i].max); + GetLogger()->Info(" state: %ld\n", m_triangles[i].state); + } + + return true; +} + +bool Gfx::CModelFile::WriteTextModel(const std::string &fileName) +{ + std::ofstream stream; + stream.open(fileName.c_str(), std::ios_base::out); + if (! stream.good()) + { + GetLogger()->Error("Could not open file '%s'\n", fileName.c_str()); + return false; + } + + return WriteTextModel(stream); +} + +bool Gfx::CModelFile::WriteTextModel(std::ostream& stream) +{ + if (m_triangles.size() == 0) + { + GetLogger()->Error("Empty model\n"); + return false; + } + + NewModelHeader header; + + header.version = 1; + header.totalTriangles = m_triangles.size(); + + stream << "# Colobot text model" << std::endl; + stream << std::endl; + stream << "### HEAD" << std::endl; + stream << "version " << header.version << std::endl; + stream << "total_triangles " << header.totalTriangles << std::endl; + stream << std::endl; + stream << "### TRIANGLES" << std::endl; - if ( command == 20 && waitVertexY ) + for (int i = 0; i < static_cast( m_triangles.size() ); ++i) + { + NewModelTriangle1 t; + + t.p1 = m_triangles[i].p1; + t.p2 = m_triangles[i].p2; + t.p3 = m_triangles[i].p3; + t.material = m_triangles[i].material; + t.tex1Name = m_triangles[i].tex1Name; + t.tex2Name = m_triangles[i].tex2Name; + t.variableTex2 = m_triangles[i].variableTex2; + t.min = m_triangles[i].min; + t.max = m_triangles[i].max; + + stream << "p1 "; + WriteTextVertexTex2(t.p1, stream); + stream << "p2 "; + WriteTextVertexTex2(t.p2, stream); + stream << "p3 "; + WriteTextVertexTex2(t.p3, stream); + stream << "mat "; + WriteTextMaterial(t.material, stream); + + stream << "tex1 " << t.tex1Name << std::endl; + stream << "tex2 " << t.tex2Name << std::endl; + stream << "var_tex2 " << (t.variableTex2 ? 'Y' : 'N') << std::endl; + stream << "min " << t.min << std::endl; + stream << "max " << t.max << std::endl; + + stream << std::endl; + + if (! stream.good()) { - waitVertexY = false; - coords.y = StrUtils::FromString(line, &ok); - waitVertexZ = true; + GetLogger()->Error("Error writing model file\n"); + return false; } + } + + return true; +} + +bool Gfx::CModelFile::ReadBinaryModel(const std::string& fileName) +{ + std::ifstream stream; + stream.open(fileName.c_str(), std::ios_base::in | std::ios_base::binary); + if (! stream.good()) + { + GetLogger()->Error("Could not open file '%s'\n", fileName.c_str()); + return false; + } + + return ReadBinaryModel(stream); +} + +bool Gfx::CModelFile::ReadBinaryModel(std::istream& stream) +{ + m_triangles.clear(); - if ( command == 30 && waitVertexZ ) + NewModelHeader header; + + header.version = IOUtils::ReadBinary<4, int>(stream); + header.totalTriangles = IOUtils::ReadBinary<4, int>(stream); + + if (! stream.good()) + { + GetLogger()->Error("Error reading model file header\n"); + return false; + } + + // New model version 1 + if (header.version == 1) + { + for (int i = 0; i < header.totalTriangles; ++i) { - waitVertexZ = false; - coords.z = StrUtils::FromString(line, &ok); + NewModelTriangle1 t; - vertexNum --; - if ( vertexNum >= 0 ) - { - Math::Vector p(coords.x, coords.z, coords.y); // permutation of Y and Z! - vertices.push_back(p); - waitVertexX = true; - } - else + t.p1 = ReadBinaryVertexTex2(stream); + t.p2 = ReadBinaryVertexTex2(stream); + t.p3 = ReadBinaryVertexTex2(stream); + t.material = ReadBinaryMaterial(stream); + t.tex1Name = IOUtils::ReadBinaryString<1>(stream); + t.tex2Name = IOUtils::ReadBinaryString<1>(stream); + t.variableTex2 = IOUtils::ReadBinaryBool(stream); + t.min = IOUtils::ReadBinaryFloat(stream); + t.max = IOUtils::ReadBinaryFloat(stream); + + if (! stream.good()) { - waitFaceX = true; + GetLogger()->Error("Error reading model data\n"); + return false; } - } - if ( command == 71 && waitFaceX ) - { - waitFaceX = false; - p1 = StrUtils::FromString(line, &ok); - if ( p1 < 0 ) p1 = -p1; - waitFaceY = true; - } + Gfx::ModelTriangle triangle; + triangle.p1 = t.p1; + triangle.p2 = t.p2; + triangle.p3 = t.p3; + triangle.material = t.material; + triangle.tex1Name = t.tex1Name; + triangle.tex2Name = t.tex2Name; + triangle.variableTex2 = t.variableTex2; + triangle.min = t.min; + triangle.max = t.max; - if ( command == 72 && waitFaceY ) - { - waitFaceY = false; - p2 = StrUtils::FromString(line, &ok); - if ( p2 < 0 ) p2 = -p2; - waitFaceZ = true; + m_triangles.push_back(triangle); } + } + else + { + GetLogger()->Error("Unknown model file version\n"); + return false; + } - if ( command == 73 && waitFaceZ ) - { - waitFaceZ = false; - p3 = StrUtils::FromString(line, &ok); - if ( p3 < 0 ) p3 = -p3; + for (int i = 0; i < static_cast( m_triangles.size() ); ++i) + { + GetLogger()->Info("ModelTriangle %d\n", i+1); + std::string s1 = m_triangles[i].p1.ToString(); + GetLogger()->Info(" p1: %s\n", s1.c_str()); + std::string s2 = m_triangles[i].p2.ToString(); + GetLogger()->Info(" p2: %s\n", s2.c_str()); + std::string s3 = m_triangles[i].p3.ToString(); + GetLogger()->Info(" p3: %s\n", s3.c_str()); - faceNum --; - if ( faceNum >= 0 ) - { - assert( (p1-1 >= 0) && (p1-1 < static_cast(vertices.size())) ); - assert( (p2-1 >= 0) && (p2-1 < static_cast(vertices.size())) ); - assert( (p3-1 >= 0) && (p3-1 < static_cast(vertices.size())) ); + std::string d = m_triangles[i].material.diffuse.ToString(); + std::string a = m_triangles[i].material.ambient.ToString(); + std::string s = m_triangles[i].material.specular.ToString(); + GetLogger()->Info(" mat: d: %s a: %s s: %s\n", d.c_str(), a.c_str(), s.c_str()); - CreateTriangle(vertices[p3-1], vertices[p2-1], vertices[p1-1], min, max); - waitFaceX = true; - } - } + GetLogger()->Info(" tex1: %s tex2: %s\n", m_triangles[i].tex1Name.c_str(), m_triangles[i].tex2Name.c_str()); + GetLogger()->Info(" min: %.2f max: %.2f\n", m_triangles[i].min, m_triangles[i].max); + GetLogger()->Info(" state: %ld\n", m_triangles[i].state); + } + + return true; +} + +bool Gfx::CModelFile::WriteBinaryModel(const std::string& fileName) +{ + std::ofstream stream; + stream.open(fileName.c_str(), std::ios_base::out | std::ios_base::binary); + if (! stream.good()) + { + GetLogger()->Error("Could not open file '%s'\n", fileName.c_str()); + return false; + } + + return WriteBinaryModel(stream); +} + +bool Gfx::CModelFile::WriteBinaryModel(std::ostream& stream) +{ + if (m_triangles.size() == 0) + { + GetLogger()->Error("Empty model\n"); + return false; + } + + NewModelHeader header; + + header.version = 1; + header.totalTriangles = m_triangles.size(); - if (! ok) + IOUtils::WriteBinary<4, int>(header.version, stream); + IOUtils::WriteBinary<4, int>(header.totalTriangles, stream); + + for (int i = 0; i < static_cast( m_triangles.size() ); ++i) + { + NewModelTriangle1 t; + + t.p1 = m_triangles[i].p1; + t.p2 = m_triangles[i].p2; + t.p3 = m_triangles[i].p3; + t.material = m_triangles[i].material; + t.tex1Name = m_triangles[i].tex1Name; + t.tex2Name = m_triangles[i].tex2Name; + t.variableTex2 = m_triangles[i].variableTex2; + t.min = m_triangles[i].min; + t.max = m_triangles[i].max; + + WriteBinaryVertexTex2(t.p1, stream); + WriteBinaryVertexTex2(t.p2, stream); + WriteBinaryVertexTex2(t.p3, stream); + WriteBinaryMaterial(t.material, stream); + IOUtils::WriteBinaryString<1>(t.tex1Name, stream); + IOUtils::WriteBinaryString<1>(t.tex2Name, stream); + IOUtils::WriteBinaryBool(t.variableTex2, stream); + IOUtils::WriteBinaryFloat(t.min, stream); + IOUtils::WriteBinaryFloat(t.max, stream); + + if (! stream.good()) { - m_error = "Error reading data"; + GetLogger()->Error("Error writing model file\n"); return false; } - } return true; } -bool Gfx::CModelFile::CreateEngineObject(int objRank, int addState) + +/******************************************************* + Other stuff + *******************************************************/ + +#ifndef MODELFILE_NO_ENGINE + +bool Gfx::CModelFile::CreateEngineObject(int objRank) { + std::vector vs(3, Gfx::VertexTex2()); + + float limit[2]; + limit[0] = m_engine->GetLimitLOD(0); // frontier AB as config + limit[1] = m_engine->GetLimitLOD(1); // frontier BC as config + for (int i = 0; i < static_cast( m_triangles.size() ); i++) { - int state = m_triangles[i].state; + // TODO move this to CEngine + + float min = m_triangles[i].min; + float max = m_triangles[i].max; + + // Standard frontiers -> config + if (min == 0.0f && max == 100.0f) // resolution A ? + { + max = limit[0]; + } + else if (min == 100.0f && max == 200.0f) // resolution B ? + { + min = limit[0]; + max = limit[1]; + } + else if (min == 200.0f && max == 1000000.0f) // resolution C ? + { + min = limit[1]; + } - /* TODO ??? - if (texName1 == "plant.png") - state |= Gfx::ENG_RSTATE_ALPHA; + int state = m_triangles[i].state; + std::string tex2Name = m_triangles[i].tex2Name; - if (m_triangles[i].tex2Name.empty()) + if (m_triangles[i].variableTex2) { - int texNum = 0; + int texNum = m_engine->GetSecondTexture(); - if ( m_triangles[i].texNum2 == 1 ) - { - texNum = m_engine->RetSecondTexture(); - } - else - { - texNum = m_triangles[i].texNum2; - } + if (texNum >= 1 && texNum <= 10) + state |= Gfx::ENG_RSTATE_DUAL_BLACK; - if ( texNum >= 1 && texNum <= 10 ) - { - state |= D3DSTATEDUALb; - } - if ( texNum >= 11 && texNum <= 20 ) - { - state |= D3DSTATEDUALw; - } - sprintf(texName2, "dirty%.2d.tga", texNum); // ??? - }*/ + if (texNum >= 11 && texNum <= 20) + state |= Gfx::ENG_RSTATE_DUAL_WHITE; - std::vector vs; - vs.push_back(m_triangles[i].p1); - vs.push_back(m_triangles[i].p2); - vs.push_back(m_triangles[i].p3); + char name[20] = { 0 }; + sprintf(name, "dirty%.2d.png", texNum); + tex2Name = name; + } - m_engine->AddTriangles(objRank, vs, - m_triangles[i].material, - state + addState, - m_triangles[i].tex1Name, - m_triangles[i].tex2Name, - m_triangles[i].min, - m_triangles[i].max, false); + vs[0] = m_triangles[i].p1; + vs[1] = m_triangles[i].p2; + vs[2] = m_triangles[i].p3; + + bool ok = m_engine->AddTriangles(objRank, vs, + m_triangles[i].material, + state, + m_triangles[i].tex1Name, + tex2Name, + min, max, false); + if (!ok) + return false; } return true; } +#endif + void Gfx::CModelFile::Mirror() { for (int i = 0; i < static_cast( m_triangles.size() ); i++) @@ -790,7 +1205,7 @@ void Gfx::CModelFile::Mirror() } } -std::vector& Gfx::CModelFile::GetTriangles() +const std::vector& Gfx::CModelFile::GetTriangles() { return m_triangles; } diff --git a/src/graphics/engine/modelfile.h b/src/graphics/engine/modelfile.h index fab190f..833cdf6 100644 --- a/src/graphics/engine/modelfile.h +++ b/src/graphics/engine/modelfile.h @@ -20,7 +20,6 @@ * \brief Model loading - Gfx::CModelFile class (aka modfile) */ -#include "graphics/engine/engine.h" #include "graphics/core/vertex.h" #include "graphics/core/material.h" #include "math/vector.h" @@ -35,6 +34,9 @@ class CInstanceManager; namespace Gfx { +class CEngine; + + /** \struct ModelTriangle \brief Triangle of a 3D model @@ -53,14 +55,21 @@ struct ModelTriangle std::string tex1Name; //! Name of 2nd texture std::string tex2Name; + //! If true, 2nd texture will be taken from current engine setting + bool variableTex2; //! Min LOD threshold float min; //! Max LOD threshold float max; //! Rendering state to be set - long state; - - ModelTriangle(); + int state; + + ModelTriangle() + { + variableTex2 = true; + min = max = 0.0f; + state = 0; + } }; @@ -75,27 +84,45 @@ public: CModelFile(CInstanceManager* iMan); ~CModelFile(); - //! Returns the last error encountered - std::string GetError(); + //! Reads a model in text format from file + bool ReadTextModel(const std::string &fileName); + //! Reads a model in text format from stream + bool ReadTextModel(std::istream &stream); + + //! Writes the model in text format to a file + bool WriteTextModel(const std::string &fileName); + //! Writes the model in text format to a stream + bool WriteTextModel(std::ostream &stream); + + //! Reads a model in new binary format from file + bool ReadBinaryModel(const std::string &fileName); + //! Reads a model in new binary format from stream + bool ReadBinaryModel(std::istream &stream); + + //! Writes the model in binary format to a file + bool WriteBinaryModel(const std::string &fileName); + //! Writes the model in binary format to a stream + bool WriteBinaryModel(std::ostream &stream); //! Reads a binary Colobot model from file - bool ReadModel(const std::string &filename, bool edit = false, bool meta = true); + //! @deprecated + bool ReadModel(const std::string &fileName); //! Reads a binary Colobot model from stream - bool ReadModel(std::istream &stream, bool edit = false, bool meta = true); + //! @deprecated + bool ReadModel(std::istream &stream); //! Writes the model to Colobot binary model file - bool WriteModel(const std::string &filename); + //! @deprecated + bool WriteModel(const std::string &fileName); //! Writes the model to Colobot binary model file + //! @deprecated bool WriteModel(std::ostream &stream); - //! Reads a DXF model from file - bool ReadDXF(const std::string &filename, float min, float max); - //! Reads a DXF model from stream - bool ReadDXF(std::istream &stream, float min, float max); - //! Returns the number of triangles in model int GetTriangleCount(); + //! Returns the triangle vector - std::vector& GetTriangles(); + const std::vector& GetTriangles(); + //! Returns the height of model -- closest point to X and Z coords of \a pos float GetHeight(Math::Vector pos); @@ -103,7 +130,7 @@ public: void Mirror(); //! Creates an object in the graphics engine from the model - bool CreateEngineObject(int objRank, int addState = 0); + bool CreateEngineObject(int objRank); protected: //! Adds a triangle to the list @@ -113,9 +140,6 @@ protected: CInstanceManager* m_iMan; Gfx::CEngine* m_engine; - //! Last error - std::string m_error; - //! Model triangles std::vector m_triangles; }; diff --git a/src/graphics/engine/terrain.cpp b/src/graphics/engine/terrain.cpp index 368796a..971eed1 100644 --- a/src/graphics/engine/terrain.cpp +++ b/src/graphics/engine/terrain.cpp @@ -180,7 +180,7 @@ void Gfx::CTerrain::LevelFlush() LevelCloseTable(); } -void Gfx::CTerrain::LevelMaterial(int id, std::string& baseName, float u, float v, +void Gfx::CTerrain::LevelMaterial(int id, const std::string& baseName, float u, float v, int up, int right, int down, int left, float hardness) { diff --git a/src/graphics/engine/terrain.h b/src/graphics/engine/terrain.h index 24bd1f9..0c3fa63 100644 --- a/src/graphics/engine/terrain.h +++ b/src/graphics/engine/terrain.h @@ -157,7 +157,7 @@ public: //! Empties level void LevelFlush(); //! Initializes the names of textures to use for the land - void LevelMaterial(int id, std::string& baseName, float u, float v, int up, int right, int down, int left, float hardness); + void LevelMaterial(int id, const std::string& baseName, float u, float v, int up, int right, int down, int left, float hardness); //! Initializes all the ground with a material bool LevelInit(int id); //! Generates a level in the terrain diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt new file mode 100644 index 0000000..f6c6112 --- /dev/null +++ b/src/tools/CMakeLists.txt @@ -0,0 +1,13 @@ +set(CONVERT_MODEL_SOURCES +../common/iman.cpp +../common/logger.cpp +../common/stringutils.cpp +../graphics/engine/modelfile.cpp +convert_model.cpp +) + +include_directories(. ..) + +add_definitions(-DMODELFILE_NO_ENGINE) + +add_executable(convert_model ${CONVERT_MODEL_SOURCES}) diff --git a/src/tools/README.txt b/src/tools/README.txt new file mode 100644 index 0000000..de2f087 --- /dev/null +++ b/src/tools/README.txt @@ -0,0 +1,4 @@ +/** + * \dir tools + * \brief Various tools (separate programs) + */ diff --git a/src/tools/convert_model.cpp b/src/tools/convert_model.cpp new file mode 100644 index 0000000..472f1c6 --- /dev/null +++ b/src/tools/convert_model.cpp @@ -0,0 +1,285 @@ +#include "common/iman.h" +#include "common/logger.h" +#include "graphics/engine/modelfile.h" + +#include +#include + + +bool EndsWith(std::string const &fullString, std::string const &ending) +{ + if (fullString.length() >= ending.length()) { + return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending)); + } else { + return false; + } +} + + +struct Args +{ + bool usage; + bool dumpInfo; + bool mirror; + std::string inputFile; + std::string outputFile; + std::string inputFormat; + std::string outputFormat; + + Args() + { + usage = false; + dumpInfo = false; + mirror = false; + } +}; + +Args ARGS; + +void PrintUsage(const std::string& program) +{ + std::cerr << "Colobot model converter" << std::endl; + std::cerr << std::endl; + std::cerr << "Usage:" << std::endl; + std::cerr << std::endl; + std::cerr << " Convert files:" << std::endl; + std::cerr << " " << program << " -i input_file -if input_format -o output_file -of output_format [-m]" << std::endl; + std::cerr << " -m => mirror" << std::endl; + std::cerr << std::endl; + std::cerr << " Dump info:" << std::endl; + std::cerr << " " << program << " -d -i input_file -if input_format" << std::endl; + std::cerr << std::endl; + std::cerr << " Help:" << std::endl; + std::cerr << " " << program << " -h" << std::endl; + std::cerr << std::endl; + + std::cerr << "Model formats:" << std::endl; + std::cerr << " old => old binary format" << std::endl; + std::cerr << " new_bin => new binary format" << std::endl; + std::cerr << " new_txt => new text format" << std::endl; +} + +bool ParseArgs(int argc, char *argv[]) +{ + bool waitI = false, waitO = false; + bool waitIf = false, waitOf = false; + for (int i = 1; i < argc; ++i) + { + std::string arg = std::string(argv[i]); + + if (arg == "-i") + { + waitI = true; + continue; + } + if (arg == "-o") + { + waitO = true; + continue; + } + if (arg == "-if") + { + waitIf = true; + continue; + } + if (arg == "-of") + { + waitOf = true; + continue; + } + + if (waitI) + { + waitI = false; + ARGS.inputFile = arg; + } + else if (waitO) + { + waitO = false; + ARGS.outputFile = arg; + } + else if (waitIf) + { + waitIf = false; + ARGS.inputFormat = arg; + } + else if (waitOf) + { + waitOf = false; + ARGS.outputFormat = arg; + } + else if (arg == "-h") + { + PrintUsage(argv[0]); + ARGS.usage = true; + } + else if (arg == "-d") + { + ARGS.dumpInfo = true; + } + else if (arg == "-m") + { + ARGS.mirror = true; + } + else + { + return false; + } + } + + if (waitI || waitO || waitIf || waitOf) + return false; + + if (ARGS.usage) + return true; + + if (ARGS.inputFile.empty() || (!ARGS.dumpInfo && ARGS.outputFile.empty() )) + return false; + + if (ARGS.inputFormat.empty() || (!ARGS.dumpInfo && ARGS.outputFormat.empty() )) + return false; + + return true; +} + +template +void PrintStats(const std::map& stats, int total) +{ + for (auto it = stats.begin(); it != stats.end(); ++it) + { + std::cerr << " " << (*it).first << " : " << (*it).second << " / " << total << std::endl; + } +} + +int main(int argc, char *argv[]) +{ + CLogger logger; + + if (!ParseArgs(argc, argv)) + { + std::cerr << "Invalid arguments! Run with -h for usage info." << std::endl; + return 1; + } + + if (ARGS.usage) + return 0; + + CInstanceManager iMan; + Gfx::CModelFile model(&iMan); + + bool ok = true; + + if (ARGS.inputFormat == "old") + { + ok = model.ReadModel(ARGS.inputFile); + } + else if (ARGS.inputFormat == "new_bin") + { + ok = model.ReadBinaryModel(ARGS.inputFile); + } + else if (ARGS.inputFormat == "new_txt") + { + ok = model.ReadTextModel(ARGS.inputFile); + } + else + { + std::cerr << "Invalid input format" << std::endl; + return 1; + } + + if (!ok) + { + std::cerr << "Reading input model failed" << std::endl; + return 1; + } + + if (ARGS.dumpInfo) + { + const std::vector& triangles = model.GetTriangles(); + + Math::Vector min( Math::HUGE_NUM, Math::HUGE_NUM, Math::HUGE_NUM); + Math::Vector max(-Math::HUGE_NUM, -Math::HUGE_NUM, -Math::HUGE_NUM); + + std::map texs1, texs2; + std::map states; + std::map mins, maxs; + int variableTexs2 = 0; + + for (int i = 0; i < static_cast( triangles.size() ); ++i) + { + const Gfx::ModelTriangle& t = triangles[i]; + + min.x = Math::Min(t.p1.coord.x, t.p2.coord.x, t.p3.coord.x, min.x); + min.y = Math::Min(t.p1.coord.y, t.p2.coord.y, t.p3.coord.y, min.y); + min.z = Math::Min(t.p1.coord.z, t.p2.coord.z, t.p3.coord.z, min.z); + + max.x = Math::Max(t.p1.coord.x, t.p2.coord.x, t.p3.coord.x, max.x); + max.y = Math::Max(t.p1.coord.y, t.p2.coord.y, t.p3.coord.y, max.y); + max.z = Math::Max(t.p1.coord.z, t.p2.coord.z, t.p3.coord.z, max.z); + + texs1[t.tex1Name] += 1; + if (! t.tex2Name.empty()) + texs2[t.tex2Name] += 1; + if (t.variableTex2) + variableTexs2 += 1; + states[t.state] += 1; + + mins[t.min] += 1; + maxs[t.max] += 1; + } + + std::cerr << "---- Info ----" << std::endl; + std::cerr << "Total triangles: " << triangles.size(); + std::cerr << std::endl; + std::cerr << "Bounding box:" << std::endl; + std::cerr << " min: [" << min.x << ", " << min.y << ", " << min.z << "]" << std::endl; + std::cerr << " max: [" << max.x << ", " << max.y << ", " << max.z << "]" << std::endl; + std::cerr << std::endl; + std::cerr << "Textures:" << std::endl; + std::cerr << " tex1:" << std::endl; + PrintStats(texs1, triangles.size()); + std::cerr << " tex2:" << std::endl; + PrintStats(texs2, triangles.size()); + std::cerr << " variable tex2: " << variableTexs2 << " / " << triangles.size() << std::endl; + std::cerr << std::endl; + std::cerr << "States:" << std::endl; + PrintStats(states, triangles.size()); + std::cerr << std::endl; + std::cerr << "LOD:" << std::endl; + std::cerr << " min:" << std::endl; + PrintStats(mins, triangles.size()); + std::cerr << " max:" << std::endl; + PrintStats(maxs, triangles.size()); + + return 0; + } + + if (ARGS.mirror) + model.Mirror(); + + if (ARGS.outputFormat == "old") + { + ok = model.WriteModel(ARGS.outputFile); + } + else if (ARGS.outputFormat == "new_bin") + { + ok = model.WriteBinaryModel(ARGS.outputFile); + } + else if (ARGS.outputFormat == "new_txt") + { + ok = model.WriteTextModel(ARGS.outputFile); + } + else + { + std::cerr << "Invalid output format" << std::endl; + return 1; + } + + if (!ok) + { + std::cerr << "Writing output model failed" << std::endl; + return 1; + } + + return 0; +} -- cgit v1.2.3-1-g7c22