diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 35448d4dc..e1ab13396 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1 @@ -patreon: assimp open_collective: assimp diff --git a/.gitignore b/.gitignore index f9c1a490f..0a999d3aa 100644 --- a/.gitignore +++ b/.gitignore @@ -116,3 +116,6 @@ tools/assimp_qt_viewer/moc_glview.cpp_parameters tools/assimp_qt_viewer/moc_mainwindow.cpp tools/assimp_qt_viewer/moc_mainwindow.cpp_parameters tools/assimp_qt_viewer/ui_mainwindow.h + +#Generated directory +generated/* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c6d9b3977..97a3641f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -454,7 +454,7 @@ IF(ASSIMP_HUNTER_ENABLED) ELSE() # If the zlib is already found outside, add an export in case assimpTargets can't find it. IF( ZLIB_FOUND ) - INSTALL( TARGETS zlib + INSTALL( TARGETS zlib zlibstatic EXPORT "${TARGETS_EXPORT_NAME}") ENDIF() diff --git a/INSTALL b/INSTALL index ecec2585b..2cfd38078 100644 --- a/INSTALL +++ b/INSTALL @@ -8,43 +8,10 @@ Getting the documentation ------------------------------ A regularly-updated copy is available at -http://assimp.sourceforge.net/lib_html/index.html - -A CHM file is included in the SVN repos: ./doc/AssimpDoc_Html/AssimpDoc.chm. -To build the doxygen documentation on your own, follow these steps: - -a) download & install latest doxygen -b) make sure doxygen is in the executable search path -c) navigate to ./doc -d) and run 'doxygen' - -Open the generated HTML (AssimpDoc_Html/index.html) in the browser of your choice. -Windows only: To generate the CHM doc, install 'Microsoft HTML Workshop' -and configure the path to it in the DOXYFILE first. +https://assimp-docs.readthedocs.io/en/latest/ ------------------------------ Building Assimp ------------------------------ -More detailed build instructions can be found in the documentation, -this section is just for the inpatient among you. - -CMake is the preferred build system for Assimp. The minimum required version -is 2.6. If you don't have it yet, downloads for CMake can be found on -http://www.cmake.org/. - -For Unix: - -1. mkdir build && cd build -2. cmake .. -G 'Unix Makefiles' -3. make -j4 - -For Windows: -1. Open a command prompt -2. mkdir build -3. cd build -4. cmake .. -5. cmake --build . - -For iOS: -Just check the following project, which deploys a compiler toolchain for different iOS-versions: https://github.com/assimp/assimp/tree/master/port/iOS +Just check the build-instaructions which you can find here: https://github.com/assimp/assimp/blob/master/Build.md diff --git a/LICENSE b/LICENSE index dc8e24706..acaaf016e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ Open Asset Import Library (assimp) -Copyright (c) 2006-2020, assimp team +Copyright (c) 2006-2021, assimp team All rights reserved. Redistribution and use of this software in source and binary forms, diff --git a/Readme.md b/Readme.md index 71b3c7f10..51684c4aa 100644 --- a/Readme.md +++ b/Readme.md @@ -76,9 +76,6 @@ For more information, visit [our website](http://assimp.org/). Or check out the If the docs don't solve your problem, ask on [StackOverflow with the assimp-tag](http://stackoverflow.com/questions/tagged/assimp?sort=newest). If you think you found a bug, please open an issue on Github. -For development discussions, there is also a (very low-volume) mailing list, _assimp-discussions_ - [(subscribe here)]( https://lists.sourceforge.net/lists/listinfo/assimp-discussions) - Open Asset Import Library is a library to load various 3d file formats into a shared, in-memory format. It supports more than __40 file formats__ for import and a growing selection of file formats for export. And we also have a Gitter-channel:Gitter [![Join the chat at https://gitter.im/assimp/assimp](https://badges.gitter.im/assimp/assimp.svg)](https://gitter.im/assimp/assimp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
@@ -103,9 +100,6 @@ Become a financial contributor and help us sustain our community. [[Contribute]( -Monthly donations via Patreon: -
[![Patreon](https://cloud.githubusercontent.com/assets/8225057/5990484/70413560-a9ab-11e4-8942-1a63607c0b00.png)](http://www.patreon.com/assimp) - #### Organizations diff --git a/code/AssetLib/3DS/3DSLoader.cpp b/code/AssetLib/3DS/3DSLoader.cpp index 3dabf9da1..0a64f6870 100644 --- a/code/AssetLib/3DS/3DSLoader.cpp +++ b/code/AssetLib/3DS/3DSLoader.cpp @@ -981,9 +981,9 @@ void Discreet3DSImporter::ParseMeshChunk() { mMesh.mMat.a3 = stream->GetF4(); mMesh.mMat.b3 = stream->GetF4(); mMesh.mMat.c3 = stream->GetF4(); - mMesh.mMat.a4 = stream->GetF4(); - mMesh.mMat.b4 = stream->GetF4(); - mMesh.mMat.c4 = stream->GetF4(); + mMesh.mMat.d1 = stream->GetF4(); + mMesh.mMat.d2 = stream->GetF4(); + mMesh.mMat.d3 = stream->GetF4(); } break; case Discreet3DS::CHUNK_MAPLIST: { diff --git a/code/AssetLib/AMF/AMFImporter.cpp b/code/AssetLib/AMF/AMFImporter.cpp index 7d6ef2fd0..37756aea0 100644 --- a/code/AssetLib/AMF/AMFImporter.cpp +++ b/code/AssetLib/AMF/AMFImporter.cpp @@ -39,16 +39,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ -/// \file AMFImporter.cpp -/// \brief AMF-format files importer for Assimp: main algorithm implementation. -/// \date 2016 -/// \author smal.root@gmail.com - #ifndef ASSIMP_BUILD_NO_AMF_IMPORTER // Header files, Assimp. #include "AMFImporter.hpp" -#include "AMFImporter_Macro.hpp" #include #include @@ -307,14 +301,14 @@ void AMFImporter::ParseNode_Root() { throw DeadlyImportError("Root node \"amf\" not found."); } XmlNode node = *root; - mUnit = ai_str_tolower(std::string(node.attribute("unit").as_string())); + mUnit = ai_tolower(std::string(node.attribute("unit").as_string())); mVersion = node.attribute("version").as_string(); // Read attributes for node . // Check attributes if (!mUnit.empty()) { - if ((mUnit != "inch") && (mUnit != "millimeter") && (mUnit != "meter") && (mUnit != "feet") && (mUnit != "micron")) { + if ((mUnit != "inch") && (mUnit != "millimeters") && (mUnit != "millimeter") && (mUnit != "meter") && (mUnit != "feet") && (mUnit != "micron")) { Throw_IncorrectAttrValue("unit", mUnit); } } @@ -409,20 +403,20 @@ void AMFImporter::ParseNode_Instance(XmlNode &node) { if (!node.empty()) { ParseHelper_Node_Enter(ne); - for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) { + for (auto ¤tNode : node.children()) { const std::string ¤tName = currentNode.name(); if (currentName == "deltax") { - als.Delta.x = (ai_real)std::atof(currentNode.value()); + XmlParser::getValueAsFloat(currentNode, als.Delta.x); } else if (currentName == "deltay") { - als.Delta.y = (ai_real)std::atof(currentNode.value()); + XmlParser::getValueAsFloat(currentNode, als.Delta.y); } else if (currentName == "deltaz") { - als.Delta.z = (ai_real)std::atof(currentNode.value()); + XmlParser::getValueAsFloat(currentNode, als.Delta.z); } else if (currentName == "rx") { - als.Delta.x = (ai_real)std::atof(currentNode.value()); + XmlParser::getValueAsFloat(currentNode, als.Delta.x); } else if (currentName == "ry") { - als.Delta.y = (ai_real)std::atof(currentNode.value()); + XmlParser::getValueAsFloat(currentNode, als.Delta.y); } else if (currentName == "rz") { - als.Delta.z = (ai_real)std::atof(currentNode.value()); + XmlParser::getValueAsFloat(currentNode, als.Delta.z); } } ParseHelper_Node_Exit(); @@ -458,7 +452,7 @@ void AMFImporter::ParseNode_Object(XmlNode &node) { // Check for child nodes if (!node.empty()) { ParseHelper_Node_Enter(ne); - for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) { + for (auto ¤tNode : node.children()) { const std::string ¤tName = currentNode.name(); if (currentName == "color") { ParseNode_Color(currentNode); diff --git a/code/AssetLib/AMF/AMFImporter_Geometry.cpp b/code/AssetLib/AMF/AMFImporter_Geometry.cpp index 3d50488a2..1fd2c49a4 100644 --- a/code/AssetLib/AMF/AMFImporter_Geometry.cpp +++ b/code/AssetLib/AMF/AMFImporter_Geometry.cpp @@ -39,16 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ -/// \file AMFImporter_Geometry.cpp -/// \brief Parsing data from geometry nodes. -/// \date 2016 -/// \author smal.root@gmail.com - #ifndef ASSIMP_BUILD_NO_AMF_IMPORTER #include "AMFImporter.hpp" -#include "AMFImporter_Macro.hpp" - #include namespace Assimp { @@ -103,18 +96,18 @@ void AMFImporter::ParseNode_Vertices(XmlNode &node) { // create new mesh object. ne = new AMFVertices(mNodeElement_Cur); // Check for child nodes - pugi::xml_node vertexNode = node.child("vertex"); - if (!vertexNode.empty()) { - ParseHelper_Node_Enter(ne); - - ParseNode_Vertex(vertexNode); - - ParseHelper_Node_Exit(); - - } else { + if (node.empty()) { mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element - } // if(!mReader->isEmptyElement()) else - + return; + } + ParseHelper_Node_Enter(ne); + for (XmlNode ¤tNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "vertex") { + ParseNode_Vertex(currentNode); + } + } + ParseHelper_Node_Exit(); mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph. } @@ -166,27 +159,25 @@ void AMFImporter::ParseNode_Vertex(XmlNode &node) { // X, Y, or Z coordinate, respectively, of a vertex position in space. void AMFImporter::ParseNode_Coordinates(XmlNode &node) { AMFNodeElementBase *ne = nullptr; - - // create new color object. - ne = new AMFCoordinates(mNodeElement_Cur); - - AMFCoordinates &als = *((AMFCoordinates *)ne); // alias for convenience if (!node.empty()) { + ne = new AMFCoordinates(mNodeElement_Cur); ParseHelper_Node_Enter(ne); for (XmlNode ¤tNode : node.children()) { - const std::string ¤tName = currentNode.name(); - if (currentName == "X") { + // create new color object. + AMFCoordinates &als = *((AMFCoordinates *)ne); // alias for convenience + const std::string ¤tName = ai_tolower(currentNode.name()); + if (currentName == "x") { XmlParser::getValueAsFloat(currentNode, als.Coordinate.x); - } else if (currentName == "Y") { + } else if (currentName == "y") { XmlParser::getValueAsFloat(currentNode, als.Coordinate.y); - } else if (currentName == "Z") { + } else if (currentName == "z") { XmlParser::getValueAsFloat(currentNode, als.Coordinate.z); } } - ParseHelper_Node_Exit(); + } else { - mNodeElement_Cur->Child.push_back(ne); + mNodeElement_Cur->Child.push_back(new AMFCoordinates(mNodeElement_Cur)); } mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph. @@ -216,7 +207,7 @@ void AMFImporter::ParseNode_Volume(XmlNode &node) { bool col_read = false; if (!node.empty()) { ParseHelper_Node_Enter(ne); - for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) { + for (auto ¤tNode : node.children()) { const std::string currentName = currentNode.name(); if (currentName == "color") { if (col_read) Throw_MoreThanOnceDefined(currentName, "color", "Only one color can be defined for ."); @@ -258,7 +249,8 @@ void AMFImporter::ParseNode_Triangle(XmlNode &node) { bool col_read = false; if (!node.empty()) { ParseHelper_Node_Enter(ne); - for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) { + std::string v; + for (auto ¤tNode : node.children()) { const std::string currentName = currentNode.name(); if (currentName == "color") { if (col_read) Throw_MoreThanOnceDefined(currentName, "color", "Only one color can be defined for ."); @@ -269,11 +261,14 @@ void AMFImporter::ParseNode_Triangle(XmlNode &node) { } else if (currentName == "map") { ParseNode_TexMap(currentNode, true); } else if (currentName == "v1") { - als.V[0] = std::atoi(currentNode.value()); + XmlParser::getValueAsString(currentNode, v); + als.V[0] = std::atoi(v.c_str()); } else if (currentName == "v2") { - als.V[1] = std::atoi(currentNode.value()); + XmlParser::getValueAsString(currentNode, v); + als.V[1] = std::atoi(v.c_str()); } else if (currentName == "v3") { - als.V[2] = std::atoi(currentNode.value()); + XmlParser::getValueAsString(currentNode, v); + als.V[2] = std::atoi(v.c_str()); } } ParseHelper_Node_Exit(); diff --git a/code/AssetLib/AMF/AMFImporter_Macro.hpp b/code/AssetLib/AMF/AMFImporter_Macro.hpp deleted file mode 100644 index 43dcf76ba..000000000 --- a/code/AssetLib/AMF/AMFImporter_Macro.hpp +++ /dev/null @@ -1,164 +0,0 @@ -/* ---------------------------------------------------------------------------- -Open Asset Import Library (assimp) ---------------------------------------------------------------------------- - -Copyright (c) 2006-2021, assimp team - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following -conditions are met: - -* Redistributions of source code must retain the above -copyright notice, this list of conditions and the -following disclaimer. - -* Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the -following disclaimer in the documentation and/or other -materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its -contributors may be used to endorse or promote products -derived from this software without specific prior -written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------- -*/ - -/// \file AMFImporter_Macro.hpp -/// \brief Useful macrodefines. -/// \date 2016 -/// \author smal.root@gmail.com - -#pragma once -#ifndef AMFIMPORTER_MACRO_HPP_INCLUDED -#define AMFIMPORTER_MACRO_HPP_INCLUDED - -/// \def MACRO_ATTRREAD_LOOPBEG -/// Begin of loop that read attributes values. -#define MACRO_ATTRREAD_LOOPBEG \ - for(int idx = 0, idx_end = mReader->getAttributeCount(); idx < idx_end; idx++) \ - { \ - std::string an(mReader->getAttributeName(idx)); - -/// \def MACRO_ATTRREAD_LOOPEND -/// End of loop that read attributes values. -#define MACRO_ATTRREAD_LOOPEND \ - Throw_IncorrectAttr(an); \ - } - -/// \def MACRO_ATTRREAD_LOOPEND_WSKIP -/// End of loop that read attributes values. Difference from \ref MACRO_ATTRREAD_LOOPEND in that: current macro skip unknown attributes, but -/// \ref MACRO_ATTRREAD_LOOPEND throw an exception. -#define MACRO_ATTRREAD_LOOPEND_WSKIP \ - continue; \ - } - -/// \def MACRO_ATTRREAD_CHECK_REF -/// Check current attribute name and if it equal to requested then read value. Result write to output variable by reference. If result was read then -/// "continue" will called. -/// \param [in] pAttrName - attribute name. -/// \param [out] pVarName - output variable name. -/// \param [in] pFunction - function which read attribute value and write it to pVarName. -#define MACRO_ATTRREAD_CHECK_REF(pAttrName, pVarName, pFunction) \ - if(an == pAttrName) \ - { \ - pFunction(idx, pVarName); \ - continue; \ - } - -/// \def MACRO_ATTRREAD_CHECK_RET -/// Check current attribute name and if it equal to requested then read value. Result write to output variable using return value of \ref pFunction. -/// If result was read then "continue" will called. -/// \param [in] pAttrName - attribute name. -/// \param [out] pVarName - output variable name. -/// \param [in] pFunction - function which read attribute value and write it to pVarName. -#define MACRO_ATTRREAD_CHECK_RET(pAttrName, pVarName, pFunction) \ - if(an == pAttrName) \ - { \ - pVarName = pFunction(idx); \ - continue; \ - } - -/// \def MACRO_NODECHECK_LOOPBEGIN(pNodeName) -/// Begin of loop of parsing child nodes. Do not add ';' at end. -/// \param [in] pNodeName - current node name. -#define MACRO_NODECHECK_LOOPBEGIN(pNodeName) \ - do { \ - bool close_found = false; \ - \ - while(mReader->read()) \ - { \ - if(mReader->getNodeType() == irr::io::EXN_ELEMENT) \ - { - -/// \def MACRO_NODECHECK_LOOPEND(pNodeName) -/// End of loop of parsing child nodes. -/// \param [in] pNodeName - current node name. -#define MACRO_NODECHECK_LOOPEND(pNodeName) \ - XML_CheckNode_SkipUnsupported(pNodeName); \ - }/* if(mReader->getNodeType() == irr::io::EXN_ELEMENT) */ \ - else if(mReader->getNodeType() == irr::io::EXN_ELEMENT_END) \ - { \ - if(XML_CheckNode_NameEqual(pNodeName)) \ - { \ - close_found = true; \ - \ - break; \ - } \ - }/* else if(mReader->getNodeType() == irr::io::EXN_ELEMENT_END) */ \ - }/* while(mReader->read()) */ \ - \ - if(!close_found) Throw_CloseNotFound(pNodeName); \ - \ - } while(false) - -/// \def MACRO_NODECHECK_READCOMP_F -/// Check current node name and if it equal to requested then read value. Result write to output variable of type "float". -/// If result was read then "continue" will called. Also check if node data already read then raise exception. -/// \param [in] pNodeName - node name. -/// \param [in, out] pReadFlag - read flag. -/// \param [out] pVarName - output variable name. -#define MACRO_NODECHECK_READCOMP_F(pNodeName, pReadFlag, pVarName) \ - if(XML_CheckNode_NameEqual(pNodeName)) \ - { \ - /* Check if field already read before. */ \ - if(pReadFlag) Throw_MoreThanOnceDefined(pNodeName, "Only one component can be defined."); \ - /* Read color component and assign it to object. */ \ - pVarName = XML_ReadNode_GetVal_AsFloat(); \ - pReadFlag = true; \ - continue; \ - } - -/// \def MACRO_NODECHECK_READCOMP_U32 -/// Check current node name and if it equal to requested then read value. Result write to output variable of type "uint32_t". -/// If result was read then "continue" will called. Also check if node data already read then raise exception. -/// \param [in] pNodeName - node name. -/// \param [in, out] pReadFlag - read flag. -/// \param [out] pVarName - output variable name. -#define MACRO_NODECHECK_READCOMP_U32(pNodeName, pReadFlag, pVarName) \ - if(XML_CheckNode_NameEqual(pNodeName)) \ - { \ - /* Check if field already read before. */ \ - if(pReadFlag) Throw_MoreThanOnceDefined(pNodeName, "Only one component can be defined."); \ - /* Read color component and assign it to object. */ \ - pVarName = XML_ReadNode_GetVal_AsU32(); \ - pReadFlag = true; \ - continue; \ - } - -#endif // AMFIMPORTER_MACRO_HPP_INCLUDED diff --git a/code/AssetLib/AMF/AMFImporter_Material.cpp b/code/AssetLib/AMF/AMFImporter_Material.cpp index 1fcc7eed9..6d93a43b6 100644 --- a/code/AssetLib/AMF/AMFImporter_Material.cpp +++ b/code/AssetLib/AMF/AMFImporter_Material.cpp @@ -65,48 +65,45 @@ namespace Assimp { // Red, Greed, Blue and Alpha (transparency) component of a color in sRGB space, values ranging from 0 to 1. The // values can be specified as constants, or as a formula depending on the coordinates. void AMFImporter::ParseNode_Color(XmlNode &node) { - std::string profile = node.attribute("profile").as_string(); - - // create new color object. - AMFNodeElementBase *ne = new AMFColor(mNodeElement_Cur); - AMFColor& als = *((AMFColor*)ne);// alias for convenience + if (node.empty()) { + return; + } - als.Profile = profile; - if (!node.empty()) { - ParseHelper_Node_Enter(ne); - bool read_flag[4] = { false, false, false, false }; - for (pugi::xml_node &child : node.children()) { - std::string name = child.name(); - if ( name == "r") { - read_flag[0] = true; - XmlParser::getValueAsFloat(child, als.Color.r); - } else if (name == "g") { - read_flag[1] = true; - XmlParser::getValueAsFloat(child, als.Color.g); - } else if (name == "b") { - read_flag[2] = true; - XmlParser::getValueAsFloat(child, als.Color.b); - } else if (name == "a") { - read_flag[3] = true; - XmlParser::getValueAsFloat(child, als.Color.a); - } - ParseHelper_Node_Exit(); + const std::string &profile = node.attribute("profile").as_string(); + bool read_flag[4] = { false, false, false, false }; + AMFNodeElementBase *ne = new AMFColor(mNodeElement_Cur); + AMFColor &als = *((AMFColor *)ne); // alias for convenience + ParseHelper_Node_Enter(ne); + for (pugi::xml_node &child : node.children()) { + // create new color object. + als.Profile = profile; + + const std::string &name = child.name(); + if ( name == "r") { + read_flag[0] = true; + XmlParser::getValueAsFloat(child, als.Color.r); + } else if (name == "g") { + read_flag[1] = true; + XmlParser::getValueAsFloat(child, als.Color.g); + } else if (name == "b") { + read_flag[2] = true; + XmlParser::getValueAsFloat(child, als.Color.b); + } else if (name == "a") { + read_flag[3] = true; + XmlParser::getValueAsFloat(child, als.Color.a); } - // check that all components was defined - if (!(read_flag[0] && read_flag[1] && read_flag[2])) { - throw DeadlyImportError("Not all color components are defined."); - } - - // check if is absent. Then manually add "a == 1". - if (!read_flag[3]) { - als.Color.a = 1; - } - } else { - mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + // check if is absent. Then manually add "a == 1". + if (!read_flag[3]) { + als.Color.a = 1; + } + } + als.Composed = false; + mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph. + ParseHelper_Node_Exit(); + // check that all components was defined + if (!(read_flag[0] && read_flag[1] && read_flag[2])) { + throw DeadlyImportError("Not all color components are defined."); } - - als.Composed = false; - mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. } // . void AMFImporter::ParseNode_Texture(XmlNode &node) { - std::string id = node.attribute("id").as_string(); - uint32_t width = node.attribute("width").as_uint(); - uint32_t height = node.attribute("height").as_uint(); - uint32_t depth = node.attribute("depth").as_uint(); - std::string type = node.attribute("type").as_string(); + const std::string id = node.attribute("id").as_string(); + const uint32_t width = node.attribute("width").as_uint(); + const uint32_t height = node.attribute("height").as_uint(); + uint32_t depth = node.attribute("depth").as_uint(); + const std::string type = node.attribute("type").as_string(); bool tiled = node.attribute("tiled").as_bool(); if (node.empty()) { @@ -174,22 +171,20 @@ void AMFImporter::ParseNode_Texture(XmlNode &node) { AMFTexture& als = *((AMFTexture*)ne);// alias for convenience - std::string enc64_data = node.value(); - // Check for child nodes + std::string enc64_data; + XmlParser::getValueAsString(node, enc64_data); + // Check for child nodes // check that all components was defined if (id.empty()) { throw DeadlyImportError("ID for texture must be defined."); } if (width < 1) { - throw DeadlyImportError("INvalid width for texture."); + throw DeadlyImportError("Invalid width for texture."); } if (height < 1) { throw DeadlyImportError("Invalid height for texture."); } - if (depth < 1) { - throw DeadlyImportError("Invalid depth for texture."); - } if (type != "grayscale") { throw DeadlyImportError("Invalid type for texture."); } @@ -203,7 +198,9 @@ void AMFImporter::ParseNode_Texture(XmlNode &node) { als.Depth = depth; als.Tiled = tiled; ParseHelper_Decode_Base64(enc64_data, als.Data); - + if (depth == 0) { + depth = (uint32_t)(als.Data.size() / (width * height)); + } // check data size if ((width * height * depth) != als.Data.size()) { throw DeadlyImportError("Texture has incorrect data size."); @@ -233,20 +230,18 @@ void AMFImporter::ParseNode_TexMap(XmlNode &node, const bool pUseOldName) { AMFTexMap &als = *((AMFTexMap *)ne); // std::string rtexid, gtexid, btexid, atexid; if (!node.empty()) { - ParseHelper_Node_Enter(ne); - for (XmlNode ¤tNode : node.children()) { - const std::string ¤tName = currentNode.name(); - if (currentName == "rtexid") { - XmlParser::getValueAsString(node, rtexid); - } else if (currentName == "gtexid") { - XmlParser::getValueAsString(node, gtexid); - } else if (currentName == "btexid") { - XmlParser::getValueAsString(node, btexid); - } else if (currentName == "atexid") { - XmlParser::getValueAsString(node, atexid); + for (pugi::xml_attribute &attr : node.attributes()) { + const std::string ¤tAttr = attr.name(); + if (currentAttr == "rtexid") { + rtexid = attr.as_string(); + } else if (currentAttr == "gtexid") { + gtexid = attr.as_string(); + } else if (currentAttr == "btexid") { + btexid = attr.as_string(); + } else if (currentAttr == "atexid") { + atexid = attr.as_string(); } } - ParseHelper_Node_Exit(); } // create new texture coordinates object, alias for convenience @@ -256,7 +251,6 @@ void AMFImporter::ParseNode_TexMap(XmlNode &node, const bool pUseOldName) { } // Check for children nodes - //XML_CheckNode_MustHaveChildren(); if (node.children().begin() == node.children().end()) { throw DeadlyImportError("Invalid children definition."); } @@ -264,28 +258,31 @@ void AMFImporter::ParseNode_TexMap(XmlNode &node, const bool pUseOldName) { bool read_flag[6] = { false, false, false, false, false, false }; if (!pUseOldName) { - for (pugi::xml_attribute &attr : node.attributes()) { - const std::string name = attr.name(); + ParseHelper_Node_Enter(ne); + for ( XmlNode ¤tNode : node.children()) { + const std::string &name = currentNode.name(); if (name == "utex1") { read_flag[0] = true; - als.TextureCoordinate[0].x = attr.as_float(); + XmlParser::getValueAsFloat(node, als.TextureCoordinate[0].x); } else if (name == "utex2") { read_flag[1] = true; - als.TextureCoordinate[1].x = attr.as_float(); + XmlParser::getValueAsFloat(node, als.TextureCoordinate[1].x); } else if (name == "utex3") { read_flag[2] = true; - als.TextureCoordinate[2].x = attr.as_float(); + XmlParser::getValueAsFloat(node, als.TextureCoordinate[2].x); } else if (name == "vtex1") { read_flag[3] = true; - als.TextureCoordinate[0].y = attr.as_float(); + XmlParser::getValueAsFloat(node, als.TextureCoordinate[0].y); } else if (name == "vtex2") { read_flag[4] = true; - als.TextureCoordinate[1].y = attr.as_float(); + XmlParser::getValueAsFloat(node, als.TextureCoordinate[1].y); } else if (name == "vtex3") { read_flag[5] = true; - als.TextureCoordinate[0].y = attr.as_float(); + XmlParser::getValueAsFloat(node, als.TextureCoordinate[2].y); } } + ParseHelper_Node_Exit(); + } else { for (pugi::xml_attribute &attr : node.attributes()) { const std::string name = attr.name(); diff --git a/code/AssetLib/AMF/AMFImporter_Postprocess.cpp b/code/AssetLib/AMF/AMFImporter_Postprocess.cpp index 9bbfc3249..036b647e8 100644 --- a/code/AssetLib/AMF/AMFImporter_Postprocess.cpp +++ b/code/AssetLib/AMF/AMFImporter_Postprocess.cpp @@ -62,12 +62,14 @@ aiColor4D AMFImporter::SPP_Material::GetColor(const float /*pX*/, const float /* // Check if stored data are supported. if (!Composition.empty()) { throw DeadlyImportError("IME. GetColor for composition"); - } else if (Color->Composed) { - throw DeadlyImportError("IME. GetColor, composed color"); - } else { - tcol = Color->Color; } + if (Color->Composed) { + throw DeadlyImportError("IME. GetColor, composed color"); + } + + tcol = Color->Color; + // Check if default color must be used if ((tcol.r == 0) && (tcol.g == 0) && (tcol.b == 0) && (tcol.a == 0)) { tcol.r = 0.5f; @@ -79,13 +81,13 @@ aiColor4D AMFImporter::SPP_Material::GetColor(const float /*pX*/, const float /* return tcol; } -void AMFImporter::PostprocessHelper_CreateMeshDataArray(const AMFMesh &pNodeElement, std::vector &pVertexCoordinateArray, +void AMFImporter::PostprocessHelper_CreateMeshDataArray(const AMFMesh &nodeElement, std::vector &vertexCoordinateArray, std::vector &pVertexColorArray) const { AMFVertices *vn = nullptr; size_t col_idx; // All data stored in "vertices", search for it. - for (AMFNodeElementBase *ne_child : pNodeElement.Child) { + for (AMFNodeElementBase *ne_child : nodeElement.Child) { if (ne_child->Type == AMFNodeElementBase::ENET_Vertices) { vn = (AMFVertices*)ne_child; } @@ -97,7 +99,7 @@ void AMFImporter::PostprocessHelper_CreateMeshDataArray(const AMFMesh &pNodeElem } // all coordinates stored as child and we need to reserve space for future push_back's. - pVertexCoordinateArray.reserve(vn->Child.size()); + vertexCoordinateArray.reserve(vn->Child.size()); // colors count equal vertices count. pVertexColorArray.resize(vn->Child.size()); @@ -112,7 +114,7 @@ void AMFImporter::PostprocessHelper_CreateMeshDataArray(const AMFMesh &pNodeElem for (AMFNodeElementBase *vtx : vn_child->Child) { if (vtx->Type == AMFNodeElementBase::ENET_Coordinates) { - pVertexCoordinateArray.push_back(((AMFCoordinates *)vtx)->Coordinate); + vertexCoordinateArray.push_back(((AMFCoordinates *)vtx)->Coordinate); continue; } diff --git a/code/AssetLib/FBX/FBXExporter.cpp b/code/AssetLib/FBX/FBXExporter.cpp index ae93cba02..322161411 100644 --- a/code/AssetLib/FBX/FBXExporter.cpp +++ b/code/AssetLib/FBX/FBXExporter.cpp @@ -812,6 +812,18 @@ void FBXExporter::WriteDefinitions () // Geometry / FbxMesh // <~~ aiMesh count = mScene->mNumMeshes; + + // Blendshapes are considered Geometry + int32_t bsDeformerCount=0; + for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) { + aiMesh* m = mScene->mMeshes[mi]; + if (m->mNumAnimMeshes > 0) { + count+=m->mNumAnimMeshes; + bsDeformerCount+=m->mNumAnimMeshes; // One deformer per blendshape + bsDeformerCount++; // Plus one master blendshape deformer + } + } + if (count) { n = FBX::Node("ObjectType", "Geometry"); n.AddChild("Count", count); @@ -978,7 +990,7 @@ void FBXExporter::WriteDefinitions () } // Deformer - count = int32_t(count_deformers(mScene)); + count = int32_t(count_deformers(mScene))+bsDeformerCount; if (count) { n = FBX::Node("ObjectType", "Deformer"); n.AddChild("Count", count); @@ -1363,6 +1375,7 @@ void FBXExporter::WriteObjects () n.End(outstream, binary, indent, true); } + // aiMaterial material_uids.clear(); for (size_t i = 0; i < mScene->mNumMaterials; ++i) { @@ -1697,6 +1710,100 @@ void FBXExporter::WriteObjects () } } + // Blendshapes, if any + for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) { + const aiMesh* m = mScene->mMeshes[mi]; + if (m->mNumAnimMeshes == 0) { + continue; + } + // make a deformer for this mesh + int64_t deformer_uid = generate_uid(); + FBX::Node dnode("Deformer"); + dnode.AddProperties(deformer_uid, m->mName.data + FBX::SEPARATOR + "Blendshapes", "BlendShape"); + dnode.AddChild("Version", int32_t(101)); + dnode.Dump(outstream, binary, indent); + // connect it + connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]); + std::vector vertex_indices = vVertexIndice[mi]; + + for (unsigned int am = 0; am < m->mNumAnimMeshes; ++am) { + aiAnimMesh *pAnimMesh = m->mAnimMeshes[am]; + std::string blendshape_name = pAnimMesh->mName.data; + + // start the node record + FBX::Node bsnode("Geometry"); + int64_t blendshape_uid = generate_uid(); + mesh_uids.push_back(blendshape_uid); + bsnode.AddProperty(blendshape_uid); + bsnode.AddProperty(blendshape_name + FBX::SEPARATOR + "Blendshape"); + bsnode.AddProperty("Shape"); + bsnode.AddChild("Version", int32_t(100)); + bsnode.Begin(outstream, binary, indent); + bsnode.DumpProperties(outstream, binary, indent); + bsnode.EndProperties(outstream, binary, indent); + bsnode.BeginChildren(outstream, binary, indent); + indent++; + if (pAnimMesh->HasPositions()) { + std::vectorshape_indices; + std::vectorpPositionDiff; + std::vectorpNormalDiff; + + for (unsigned int vt = 0; vt < vertex_indices.size(); ++vt) { + aiVector3D pDiff = (pAnimMesh->mVertices[vertex_indices[vt]] - m->mVertices[vertex_indices[vt]]); + if(pDiff.Length()>1e-8){ + shape_indices.push_back(vertex_indices[vt]); + pPositionDiff.push_back(pDiff[0]); + pPositionDiff.push_back(pDiff[1]); + pPositionDiff.push_back(pDiff[2]); + + if (pAnimMesh->HasNormals()) { + aiVector3D nDiff = (pAnimMesh->mNormals[vertex_indices[vt]] - m->mNormals[vertex_indices[vt]]); + pNormalDiff.push_back(nDiff[0]); + pNormalDiff.push_back(nDiff[1]); + pNormalDiff.push_back(nDiff[2]); + } + } + } + + FBX::Node::WritePropertyNode( + "Indexes", shape_indices, outstream, binary, indent + ); + + FBX::Node::WritePropertyNode( + "Vertices", pPositionDiff, outstream, binary, indent + ); + + if (pNormalDiff.size()>0) { + FBX::Node::WritePropertyNode( + "Normals", pNormalDiff, outstream, binary, indent + ); + } + } + indent--; + bsnode.End(outstream, binary, indent, true); + + // Add blendshape Channel Deformer + FBX::Node sdnode("Deformer"); + const int64_t blendchannel_uid = generate_uid(); + sdnode.AddProperties( + blendchannel_uid, blendshape_name + FBX::SEPARATOR + "SubDeformer", "BlendShapeChannel" + ); + sdnode.AddChild("Version", int32_t(100)); + sdnode.AddChild("DeformPercent", int32_t(100)); + FBX::Node p("Properties70"); + p.AddP70numberA("DeformPercent", 100.); + sdnode.AddChild(p); + // TODO: Normally just one weight per channel, adding stub for later development + std::vectorfFullWeights; + fFullWeights.push_back(0.); + sdnode.AddChild("FullWeights", fFullWeights); + sdnode.Dump(outstream, binary, indent); + + connections.emplace_back("C", "OO", blendchannel_uid, deformer_uid); + connections.emplace_back("C", "OO", blendshape_uid, blendchannel_uid); + } + } + // bones. // // output structure: diff --git a/code/AssetLib/HMP/HMPLoader.cpp b/code/AssetLib/HMP/HMPLoader.cpp index 9151e9d5d..56401f5c9 100644 --- a/code/AssetLib/HMP/HMPLoader.cpp +++ b/code/AssetLib/HMP/HMPLoader.cpp @@ -157,7 +157,10 @@ void HMPImporter::InternReadFile(const std::string &pFile, szBuffer[2] = ((char *)&iMagic)[2]; szBuffer[3] = ((char *)&iMagic)[3]; szBuffer[4] = '\0'; - + + delete[] mBuffer; + mBuffer = nullptr; + // We're definitely unable to load this file throw DeadlyImportError("Unknown HMP subformat ", pFile, ". Magic word (", szBuffer, ") is not known"); diff --git a/code/AssetLib/Ogre/OgreMaterial.cpp b/code/AssetLib/Ogre/OgreMaterial.cpp index 35a9bf4c5..eced651f6 100644 --- a/code/AssetLib/Ogre/OgreMaterial.cpp +++ b/code/AssetLib/Ogre/OgreMaterial.cpp @@ -419,7 +419,7 @@ bool OgreImporter::ReadTextureUnit(const std::string &textureUnitName, stringstr size_t posUnderscore = textureRef.find_last_of("_"); if (posSuffix != string::npos && posUnderscore != string::npos && posSuffix > posUnderscore) { - string identifier = ai_str_tolower(textureRef.substr(posUnderscore, posSuffix - posUnderscore)); + string identifier = ai_tolower(textureRef.substr(posUnderscore, posSuffix - posUnderscore)); ASSIMP_LOG_VERBOSE_DEBUG_F("Detecting texture type from filename postfix '", identifier, "'"); if (identifier == "_n" || identifier == "_nrm" || identifier == "_nrml" || identifier == "_normal" || identifier == "_normals" || identifier == "_normalmap") { @@ -440,7 +440,7 @@ bool OgreImporter::ReadTextureUnit(const std::string &textureUnitName, stringstr // Detect from texture unit name. This cannot be too broad as // authors might give names like "LightSaber" or "NormalNinja". else { - string unitNameLower = ai_str_tolower(textureUnitName); + string unitNameLower = ai_tolower(textureUnitName); if (unitNameLower.find("normalmap") != string::npos) { textureType = aiTextureType_NORMALS; } else if (unitNameLower.find("specularmap") != string::npos) { diff --git a/code/AssetLib/Ogre/OgreParsingUtils.h b/code/AssetLib/Ogre/OgreParsingUtils.h index 70797a237..0925c0305 100644 --- a/code/AssetLib/Ogre/OgreParsingUtils.h +++ b/code/AssetLib/Ogre/OgreParsingUtils.h @@ -64,7 +64,7 @@ static inline bool EndsWith(const std::string &s, const std::string &suffix, boo } if (!caseSensitive) { - return EndsWith(ai_str_tolower(s), ai_str_tolower(suffix), true); + return EndsWith(ai_tolower(s), ai_tolower(suffix), true); } size_t len = suffix.length(); diff --git a/code/AssetLib/Ogre/OgreXmlSerializer.cpp b/code/AssetLib/Ogre/OgreXmlSerializer.cpp index 99c05a522..938ae48f3 100644 --- a/code/AssetLib/Ogre/OgreXmlSerializer.cpp +++ b/code/AssetLib/Ogre/OgreXmlSerializer.cpp @@ -120,7 +120,7 @@ std::string OgreXmlSerializer::ReadAttribute(XmlNode &xmlNode, cons template <> bool OgreXmlSerializer::ReadAttribute(XmlNode &xmlNode, const char *name) const { - std::string value = ai_str_tolower(ReadAttribute(xmlNode, name)); + std::string value = ai_tolower(ReadAttribute(xmlNode, name)); if (ASSIMP_stricmp(value, "true") == 0) { return true; } else if (ASSIMP_stricmp(value, "false") == 0) { @@ -545,7 +545,7 @@ void OgreXmlSerializer::ReadSkeleton(XmlNode &node, Skeleton *skeleton) { // Optional blend mode from root node if (XmlParser::hasAttribute(node, "blendmode")) { - skeleton->blendMode = (ai_str_tolower(ReadAttribute(node, "blendmode")) == "cumulative" ? Skeleton::ANIMBLEND_CUMULATIVE : Skeleton::ANIMBLEND_AVERAGE); + skeleton->blendMode = (ai_tolower(ReadAttribute(node, "blendmode")) == "cumulative" ? Skeleton::ANIMBLEND_CUMULATIVE : Skeleton::ANIMBLEND_AVERAGE); } for (XmlNode ¤tNode : node.children()) { diff --git a/code/AssetLib/STEPParser/STEPFileReader.cpp b/code/AssetLib/STEPParser/STEPFileReader.cpp index 113b8dd6a..cbf8c07c2 100644 --- a/code/AssetLib/STEPParser/STEPFileReader.cpp +++ b/code/AssetLib/STEPParser/STEPFileReader.cpp @@ -278,7 +278,7 @@ void STEP::ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, --ne; } while (IsSpace(s.at(ne))); std::string type = s.substr(ns, ne - ns + 1); - type = ai_str_tolower(type); + type = ai_tolower(type); const char* sz = scheme.GetStaticStringForToken(type); if(sz) { const std::string::size_type szLen = n2-n1+1; diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index 0e4ba6eda..85eff4ac8 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -813,6 +813,11 @@ struct Mesh : public Object { AccessorList position, normal, tangent; }; std::vector targets; + + // extension: FB_ngon_encoding + bool ngonEncoded; + + Primitive(): ngonEncoded(false) {} }; std::vector primitives; @@ -1108,6 +1113,7 @@ public: bool KHR_materials_clearcoat; bool KHR_materials_transmission; bool KHR_draco_mesh_compression; + bool FB_ngon_encoding; } extensionsUsed; //! Keeps info about the required extensions diff --git a/code/AssetLib/glTF2/glTF2AssetWriter.inl b/code/AssetLib/glTF2/glTF2AssetWriter.inl index 166eada0f..01a28d4b7 100644 --- a/code/AssetLib/glTF2/glTF2AssetWriter.inl +++ b/code/AssetLib/glTF2/glTF2AssetWriter.inl @@ -507,6 +507,20 @@ namespace glTF2 { Mesh::Primitive& p = m.primitives[i]; Value prim; prim.SetObject(); + + // Extensions + if (p.ngonEncoded) + { + Value exts; + exts.SetObject(); + + Value FB_ngon_encoding; + FB_ngon_encoding.SetObject(); + + exts.AddMember(StringRef("FB_ngon_encoding"), FB_ngon_encoding, w.mAl); + prim.AddMember("extensions", exts, w.mAl); + } + { prim.AddMember("mode", Value(int(p.mode)).Move(), w.mAl); @@ -874,6 +888,10 @@ namespace glTF2 { if (this->mAsset.extensionsUsed.KHR_materials_transmission) { exts.PushBack(StringRef("KHR_materials_transmission"), mAl); } + + if (this->mAsset.extensionsUsed.FB_ngon_encoding) { + exts.PushBack(StringRef("FB_ngon_encoding"), mAl); + } } if (!exts.Empty()) diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index aa89e96da..51aef013d 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -97,6 +97,9 @@ glTF2Exporter::glTF2Exporter(const char* filename, IOSystem* pIOSystem, const ai mAsset.reset( new Asset( pIOSystem ) ); + // Always on as our triangulation process is aware of this type of encoding + mAsset->extensionsUsed.FB_ngon_encoding = true; + if (isBinary) { mAsset->SetAsBinary(); } @@ -955,6 +958,7 @@ void glTF2Exporter::ExportMeshes() m->name = name; p.material = mAsset->materials.Get(aim->mMaterialIndex); + p.ngonEncoded = (aim->mPrimitiveTypes & aiPrimitiveType_NGONEncodingFlag) != 0; /******************* Vertices ********************/ Ref v = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mVertices, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index 8442c4958..dca6fcb2d 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -527,8 +527,8 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { std::fill(aim->mAnimMeshes, aim->mAnimMeshes + aim->mNumAnimMeshes, nullptr); for (size_t i = 0; i < targets.size(); i++) { bool needPositions = targets[i].position.size() > 0; - bool needNormals = targets[i].normal.size() > 0; - bool needTangents = targets[i].tangent.size() > 0; + bool needNormals = (targets[i].normal.size() > 0) && aim->HasNormals(); + bool needTangents = (targets[i].tangent.size() > 0) && aim->HasTangentsAndBitangents(); // GLTF morph does not support colors and texCoords aim->mAnimMeshes[i] = aiCreateAnimMesh(aim, needPositions, needNormals, needTangents, false, false); @@ -536,35 +536,47 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { Mesh::Primitive::Target &target = targets[i]; if (needPositions) { - aiVector3D *positionDiff = nullptr; - target.position[0]->ExtractData(positionDiff); - for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { - aiAnimMesh.mVertices[vertexId] += positionDiff[vertexId]; + if (target.position[0]->count != aim->mNumVertices) { + ASSIMP_LOG_WARN_F("Positions of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count"); + } else { + aiVector3D *positionDiff = nullptr; + target.position[0]->ExtractData(positionDiff); + for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { + aiAnimMesh.mVertices[vertexId] += positionDiff[vertexId]; + } + delete[] positionDiff; } - delete[] positionDiff; } if (needNormals) { - aiVector3D *normalDiff = nullptr; - target.normal[0]->ExtractData(normalDiff); - for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { - aiAnimMesh.mNormals[vertexId] += normalDiff[vertexId]; + if (target.normal[0]->count != aim->mNumVertices) { + ASSIMP_LOG_WARN_F("Normals of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count"); + } else { + aiVector3D *normalDiff = nullptr; + target.normal[0]->ExtractData(normalDiff); + for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { + aiAnimMesh.mNormals[vertexId] += normalDiff[vertexId]; + } + delete[] normalDiff; } - delete[] normalDiff; } if (needTangents) { - Tangent *tangent = nullptr; - attr.tangent[0]->ExtractData(tangent); + if (target.tangent[0]->count != aim->mNumVertices) { + ASSIMP_LOG_WARN_F("Tangents of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count"); + } else { + Tangent *tangent = nullptr; + attr.tangent[0]->ExtractData(tangent); - aiVector3D *tangentDiff = nullptr; - target.tangent[0]->ExtractData(tangentDiff); + aiVector3D *tangentDiff = nullptr; + target.tangent[0]->ExtractData(tangentDiff); - for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; ++vertexId) { - tangent[vertexId].xyz += tangentDiff[vertexId]; - aiAnimMesh.mTangents[vertexId] = tangent[vertexId].xyz; - aiAnimMesh.mBitangents[vertexId] = (aiAnimMesh.mNormals[vertexId] ^ tangent[vertexId].xyz) * tangent[vertexId].w; + for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; ++vertexId) { + tangent[vertexId].xyz += tangentDiff[vertexId]; + aiAnimMesh.mTangents[vertexId] = tangent[vertexId].xyz; + aiAnimMesh.mBitangents[vertexId] = (aiAnimMesh.mNormals[vertexId] ^ tangent[vertexId].xyz) * tangent[vertexId].w; + } + delete[] tangent; + delete[] tangentDiff; } - delete[] tangent; - delete[] tangentDiff; } if (mesh.weights.size() > i) { aiAnimMesh.mWeight = mesh.weights[i]; diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 815d5dd6a..ebc3e0116 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -298,7 +298,6 @@ SET(ASSIMP_EXPORTERS_DISABLED "") # disabled exporters list (used to print) ADD_ASSIMP_IMPORTER( AMF AssetLib/AMF/AMFImporter.hpp - AssetLib/AMF/AMFImporter_Macro.hpp AssetLib/AMF/AMFImporter_Node.hpp AssetLib/AMF/AMFImporter.cpp AssetLib/AMF/AMFImporter_Geometry.cpp diff --git a/code/Common/BaseImporter.cpp b/code/Common/BaseImporter.cpp index cbecbf075..0657216cf 100644 --- a/code/Common/BaseImporter.cpp +++ b/code/Common/BaseImporter.cpp @@ -276,7 +276,7 @@ std::string BaseImporter::GetExtension(const std::string &file) { // thanks to Andy Maloney for the hint std::string ret = file.substr(pos + 1); - ret = ai_str_tolower(ret); + ret = ai_tolower(ret); return ret; } diff --git a/code/Common/Importer.cpp b/code/Common/Importer.cpp index 34953074c..db7fc9e1c 100644 --- a/code/Common/Importer.cpp +++ b/code/Common/Importer.cpp @@ -982,7 +982,7 @@ size_t Importer::GetImporterIndex (const char* szExtension) const { if (ext.empty()) { return static_cast(-1); } - ext = ai_str_tolower(ext); + ext = ai_tolower(ext); std::set str; for (std::vector::const_iterator i = pimpl->mImporter.begin();i != pimpl->mImporter.end();++i) { str.clear(); diff --git a/code/PostProcessing/EmbedTexturesProcess.cpp b/code/PostProcessing/EmbedTexturesProcess.cpp index b7aadcb23..dc304ff9c 100644 --- a/code/PostProcessing/EmbedTexturesProcess.cpp +++ b/code/PostProcessing/EmbedTexturesProcess.cpp @@ -137,7 +137,7 @@ bool EmbedTexturesProcess::addTexture(aiScene* pScene, std::string path) const { pTexture->pcData = imageContent; auto extension = path.substr(path.find_last_of('.') + 1u); - extension = ai_str_tolower(extension); + extension = ai_tolower(extension); if (extension == "jpeg") { extension = "jpg"; } diff --git a/code/PostProcessing/TriangulateProcess.cpp b/code/PostProcessing/TriangulateProcess.cpp index e971bf85f..0f71320b8 100644 --- a/code/PostProcessing/TriangulateProcess.cpp +++ b/code/PostProcessing/TriangulateProcess.cpp @@ -76,6 +76,87 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; +namespace { + + /** + * @brief Helper struct used to simplify NGON encoding functions. + */ + struct NGONEncoder { + NGONEncoder() : mLastNGONFirstIndex((unsigned int)-1) {} + + /** + * @brief Encode the current triangle, and make sure it is recognized as a triangle. + * + * This method will rotate indices in tri if needed in order to avoid tri to be considered + * part of the previous ngon. This method is to be used whenever you want to emit a real triangle, + * and make sure it is seen as a triangle. + * + * @param tri Triangle to encode. + */ + void ngonEncodeTriangle(aiFace * tri) { + ai_assert(tri->mNumIndices == 3); + + // Rotate indices in new triangle to avoid ngon encoding false ngons + // Otherwise, the new triangle would be considered part of the previous NGON. + if (isConsideredSameAsLastNgon(tri)) { + std::swap(tri->mIndices[0], tri->mIndices[2]); + std::swap(tri->mIndices[1], tri->mIndices[2]); + } + + mLastNGONFirstIndex = tri->mIndices[0]; + } + + /** + * @brief Encode a quad (2 triangles) in ngon encoding, and make sure they are seen as a single ngon. + * + * @param tri1 First quad triangle + * @param tri2 Second quad triangle + * + * @pre Triangles must be properly fanned from the most appropriate vertex. + */ + void ngonEncodeQuad(aiFace *tri1, aiFace *tri2) { + ai_assert(tri1->mNumIndices == 3); + ai_assert(tri2->mNumIndices == 3); + ai_assert(tri1->mIndices[0] == tri2->mIndices[0]); + + // If the selected fanning vertex is the same as the previously + // emitted ngon, we use the opposite vertex which also happens to work + // for tri-fanning a concave quad. + // ref: https://github.com/assimp/assimp/pull/3695#issuecomment-805999760 + if (isConsideredSameAsLastNgon(tri1)) { + // Right-rotate indices for tri1 (index 2 becomes the new fanning vertex) + std::swap(tri1->mIndices[0], tri1->mIndices[2]); + std::swap(tri1->mIndices[1], tri1->mIndices[2]); + + // Left-rotate indices for tri2 (index 2 becomes the new fanning vertex) + std::swap(tri2->mIndices[1], tri2->mIndices[2]); + std::swap(tri2->mIndices[0], tri2->mIndices[2]); + + ai_assert(tri1->mIndices[0] == tri2->mIndices[0]); + } + + mLastNGONFirstIndex = tri1->mIndices[0]; + } + + /** + * @brief Check whether this triangle would be considered part of the lastly emitted ngon or not. + * + * @param tri Current triangle. + * @return true If used as is, this triangle will be part of last ngon. + * @return false If used as is, this triangle is not considered part of the last ngon. + */ + bool isConsideredSameAsLastNgon(const aiFace * tri) const { + ai_assert(tri->mNumIndices == 3); + return tri->mIndices[0] == mLastNGONFirstIndex; + } + + private: + unsigned int mLastNGONFirstIndex; + }; + +} + + // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer TriangulateProcess::TriangulateProcess() @@ -175,10 +256,15 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) pMesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; pMesh->mPrimitiveTypes &= ~aiPrimitiveType_POLYGON; + // The mesh becomes NGON encoded now, during the triangulation process. + pMesh->mPrimitiveTypes |= aiPrimitiveType_NGONEncodingFlag; + aiFace* out = new aiFace[numOut](), *curOut = out; std::vector temp_verts3d(max_out+2); /* temporary storage for vertices */ std::vector temp_verts(max_out+2); + NGONEncoder ngonEncoder; + // Apply vertex colors to represent the face winding? #ifdef AI_BUILD_TRIANGULATE_COLOR_FACE_WINDING if (!pMesh->mColors[0]) @@ -220,8 +306,11 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) aiFace& nface = *curOut++; nface.mNumIndices = face.mNumIndices; nface.mIndices = face.mIndices; - face.mIndices = nullptr; + + // points and lines don't require ngon encoding (and are not supported either!) + if (nface.mNumIndices == 3) ngonEncoder.ngonEncodeTriangle(&nface); + continue; } // optimized code for quadrilaterals @@ -274,6 +363,9 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) // prevent double deletion of the indices field face.mIndices = nullptr; + + ngonEncoder.ngonEncodeQuad(&nface, &sface); + continue; } else @@ -284,11 +376,11 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) // modeling suite to make extensive use of highly concave, monster polygons ... // so we need to apply the full 'ear cutting' algorithm to get it right. - // RERQUIREMENT: polygon is expected to be simple and *nearly* planar. + // REQUIREMENT: polygon is expected to be simple and *nearly* planar. // We project it onto a plane to get a 2d triangle. // Collect all vertices of of the polygon. - for (tmp = 0; tmp < max; ++tmp) { + for (tmp = 0; tmp < max; ++tmp) { temp_verts3d[tmp] = verts[idx[tmp]]; } @@ -508,6 +600,11 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) i[0] = idx[i[0]]; i[1] = idx[i[1]]; i[2] = idx[i[2]]; + + // IMPROVEMENT: Polygons are not supported yet by this ngon encoding + triangulation step. + // So we encode polygons as regular triangles. No way to reconstruct the original + // polygon in this case. + ngonEncoder.ngonEncodeTriangle(f); ++f; } diff --git a/include/assimp/MathFunctions.h b/include/assimp/MathFunctions.h index 1031eab9d..2088c394c 100644 --- a/include/assimp/MathFunctions.h +++ b/include/assimp/MathFunctions.h @@ -87,7 +87,7 @@ inline IntegerType lcm( IntegerType a, IntegerType b ) { } return a / t * b; } -/// @brief Will return the smallest epsilon-value for the requested type. +/// @brief Will return the smallest epsilon-value for the requested type. /// @return The numercical limit epsilon depending on its type. template inline T getEpsilon() { @@ -97,7 +97,7 @@ inline T getEpsilon() { /// @brief Will return the constant PI for the requested type. /// @return Pi template -inline T PI() { +inline T aiPi() { return static_cast(3.14159265358979323846); } diff --git a/include/assimp/StringUtils.h b/include/assimp/StringUtils.h index b612a84cc..4afd717cf 100644 --- a/include/assimp/StringUtils.h +++ b/include/assimp/StringUtils.h @@ -225,7 +225,7 @@ AI_FORCE_INLINE char_t ai_tolower(char_t in) { /// @param in The incoming string. /// @return The string as lowercase. // --------------------------------------------------------------------------------- -AI_FORCE_INLINE std::string ai_str_tolower(const std::string &in) { +AI_FORCE_INLINE std::string ai_tolower(const std::string &in) { std::string out(in); ai_trim_left(out); ai_trim_right(out); diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index 8f17f541d..427dba008 100644 --- a/include/assimp/mesh.h +++ b/include/assimp/mesh.h @@ -398,6 +398,24 @@ enum aiPrimitiveType { */ aiPrimitiveType_POLYGON = 0x8, + /** + * A flag to determine whether this triangles only mesh is NGON encoded. + * + * NGON encoding is a special encoding that tells whether 2 or more consecutive triangles + * should be considered as a triangle fan. This is identified by looking at the first vertex index. + * 2 consecutive triangles with the same 1st vertex index are part of the same + * NGON. + * + * At the moment, only quads (concave or convex) are supported, meaning that polygons are 'seen' as + * triangles, as usual after a triangulation pass. + * + * To get an NGON encoded mesh, please use the aiProcess_Triangulate post process. + * + * @see aiProcess_Triangulate + * @link https://github.com/KhronosGroup/glTF/pull/1620 + */ + aiPrimitiveType_NGONEncodingFlag = 0x10, + /** This value is not used. It is just here to force the * compiler to map this enum to a 32 Bit integer. */ diff --git a/port/iOS/build.sh b/port/iOS/build.sh index f118c45c1..335b450d1 100755 --- a/port/iOS/build.sh +++ b/port/iOS/build.sh @@ -22,7 +22,7 @@ BUILD_TYPE=Release ################################################ # Minimum iOS deployment target version ################################################ -MIN_IOS_VERSION="6.0" +MIN_IOS_VERSION="10.0" IOS_SDK_TARGET=$MIN_IOS_VERSION XCODE_ROOT_DIR=$(xcode-select --print-path) @@ -60,8 +60,8 @@ build_arch() unset DEVROOT SDKROOT CFLAGS LDFLAGS CPPFLAGS CXXFLAGS CMAKE_CLI_INPUT - #export CC="$(xcrun -sdk iphoneos -find clang)" - #export CPP="$CC -E" + export CC="$(xcrun -sdk iphoneos -find clang)" + export CPP="$CC -E" export DEVROOT=$XCODE_ROOT_DIR/Platforms/$IOS_SDK_DEVICE.platform/Developer export SDKROOT=$DEVROOT/SDKs/$IOS_SDK_DEVICE$IOS_SDK_VERSION.sdk export CFLAGS="-arch $1 -pipe -no-cpp-precomp -stdlib=$CPP_STD_LIB -isysroot $SDKROOT -I$SDKROOT/usr/include/ -miphoneos-version-min=$IOS_SDK_TARGET" diff --git a/test/unit/AssimpAPITest_aiMatrix4x4.cpp b/test/unit/AssimpAPITest_aiMatrix4x4.cpp index c47da877d..d2f57b19d 100644 --- a/test/unit/AssimpAPITest_aiMatrix4x4.cpp +++ b/test/unit/AssimpAPITest_aiMatrix4x4.cpp @@ -57,7 +57,7 @@ protected: aiMatrix4x4 get_predetermined_transformation_matrix_for_decomposition() const { aiMatrix4x4 t, r; aiMatrix4x4::Translation(aiVector3D(14,-25,-8), t); - aiMatrix4x4::Rotation(Math::PI() / 4.0f, aiVector3D(1).Normalize(), r); + aiMatrix4x4::Rotation(Math::aiPi() / 4.0f, aiVector3D(1).Normalize(), r); return t * r; } diff --git a/test/unit/AssimpAPITest_aiQuaternion.cpp b/test/unit/AssimpAPITest_aiQuaternion.cpp index 93c93f674..79f89337e 100644 --- a/test/unit/AssimpAPITest_aiQuaternion.cpp +++ b/test/unit/AssimpAPITest_aiQuaternion.cpp @@ -59,7 +59,7 @@ TEST_F(AssimpAPITest_aiQuaternion, aiCreateQuaternionFromMatrixTest) { // to prevent running into division by zero. aiMatrix3x3 m, r; aiMatrix3x3::Translation(aiVector2D(14,-25), m); - aiMatrix3x3::RotationZ(Math::PI() / 4.0f, r); + aiMatrix3x3::RotationZ(Math::aiPi() / 4.0f, r); m = m * r; result_cpp = aiQuaternion(m); @@ -127,8 +127,8 @@ TEST_F(AssimpAPITest_aiQuaternion, aiQuaternionInterpolateTest) { // Use predetermined quaternions to prevent division by zero // during slerp calculations. const float INTERPOLATION(0.5f); - const auto q1 = aiQuaternion(aiVector3D(-1,1,1).Normalize(), Math::PI() / 4.0f); - const auto q2 = aiQuaternion(aiVector3D(1,2,1).Normalize(), Math::PI() / 2.0f); + const auto q1 = aiQuaternion(aiVector3D(-1,1,1).Normalize(), Math::aiPi() / 4.0f); + const auto q2 = aiQuaternion(aiVector3D(1,2,1).Normalize(), Math::aiPi() / 2.0f); aiQuaternion::Interpolate(result_cpp, q1, q2, INTERPOLATION); aiQuaternionInterpolate(&result_c, &q1, &q2, INTERPOLATION); EXPECT_EQ(result_cpp, result_c); diff --git a/test/unit/MathTest.cpp b/test/unit/MathTest.cpp index 88c95006c..ecc23545c 100644 --- a/test/unit/MathTest.cpp +++ b/test/unit/MathTest.cpp @@ -51,6 +51,6 @@ const float AssimpMathTest::Epsilon = Math::getEpsilon(); RandomUniformFloatGenerator AssimpMathTest::RandNonZero(1.0f, 100.0f); // Initialize with an interval of [-PI,PI] inclusively. -RandomUniformFloatGenerator AssimpMathTest::RandPI(-Math::PI(), Math::PI()); +RandomUniformFloatGenerator AssimpMathTest::RandPI(-Math::aiPi(), Math::aiPi()); } diff --git a/tools/assimp_view/LogWindow.cpp b/tools/assimp_view/LogWindow.cpp index 5397594dc..8a47b259f 100644 --- a/tools/assimp_view/LogWindow.cpp +++ b/tools/assimp_view/LogWindow.cpp @@ -105,7 +105,7 @@ void CLogWindow::Init() { // setup the log text this->szText = AI_VIEW_RTF_LOG_HEADER; - ; + this->szPlainText = ""; }