summaryrefslogtreecommitdiff
path: root/src/mesh/assimp-master/code/AssetLib/NFF/NFFLoader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mesh/assimp-master/code/AssetLib/NFF/NFFLoader.cpp')
-rw-r--r--src/mesh/assimp-master/code/AssetLib/NFF/NFFLoader.cpp1168
1 files changed, 1168 insertions, 0 deletions
diff --git a/src/mesh/assimp-master/code/AssetLib/NFF/NFFLoader.cpp b/src/mesh/assimp-master/code/AssetLib/NFF/NFFLoader.cpp
new file mode 100644
index 0000000..48271d1
--- /dev/null
+++ b/src/mesh/assimp-master/code/AssetLib/NFF/NFFLoader.cpp
@@ -0,0 +1,1168 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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 Implementation of the STL importer class */
+
+#ifndef ASSIMP_BUILD_NO_NFF_IMPORTER
+
+// internal headers
+#include "NFFLoader.h"
+#include <assimp/ParsingUtils.h>
+#include <assimp/RemoveComments.h>
+#include <assimp/StandardShapes.h>
+#include <assimp/fast_atof.h>
+#include <assimp/importerdesc.h>
+#include <assimp/qnan.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/IOSystem.hpp>
+#include <memory>
+
+using namespace Assimp;
+
+static const aiImporterDesc desc = {
+ "Neutral File Format Importer",
+ "",
+ "",
+ "",
+ aiImporterFlags_SupportBinaryFlavour,
+ 0,
+ 0,
+ 0,
+ 0,
+ "enff nff"
+};
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+NFFImporter::NFFImporter() {}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+NFFImporter::~NFFImporter() {}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file.
+bool NFFImporter::CanRead(const std::string & pFile, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const {
+ return SimpleExtensionCheck(pFile, "nff", "enff");
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get the list of all supported file extensions
+const aiImporterDesc *NFFImporter::GetInfo() const {
+ return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+#define AI_NFF_PARSE_FLOAT(f) \
+ SkipSpaces(&sz); \
+ if (!::IsLineEnd(*sz)) sz = fast_atoreal_move<ai_real>(sz, (ai_real &)f);
+
+// ------------------------------------------------------------------------------------------------
+#define AI_NFF_PARSE_TRIPLE(v) \
+ AI_NFF_PARSE_FLOAT(v[0]) \
+ AI_NFF_PARSE_FLOAT(v[1]) \
+ AI_NFF_PARSE_FLOAT(v[2])
+
+// ------------------------------------------------------------------------------------------------
+#define AI_NFF_PARSE_SHAPE_INFORMATION() \
+ aiVector3D center, radius(1.0f, get_qnan(), get_qnan()); \
+ AI_NFF_PARSE_TRIPLE(center); \
+ AI_NFF_PARSE_TRIPLE(radius); \
+ if (is_qnan(radius.z)) radius.z = radius.x; \
+ if (is_qnan(radius.y)) radius.y = radius.x; \
+ curMesh.radius = radius; \
+ curMesh.center = center;
+
+// ------------------------------------------------------------------------------------------------
+#define AI_NFF2_GET_NEXT_TOKEN() \
+ do { \
+ if (!GetNextLine(buffer, line)) { \
+ ASSIMP_LOG_WARN("NFF2: Unexpected EOF, can't read next token"); \
+ break; \
+ } \
+ SkipSpaces(line, &sz); \
+ } while (IsLineEnd(*sz))
+
+// ------------------------------------------------------------------------------------------------
+// Loads the material table for the NFF2 file format from an external file
+void NFFImporter::LoadNFF2MaterialTable(std::vector<ShadingInfo> &output,
+ const std::string &path, IOSystem *pIOHandler) {
+ std::unique_ptr<IOStream> file(pIOHandler->Open(path, "rb"));
+
+ // Check whether we can read from the file
+ if (!file.get()) {
+ ASSIMP_LOG_ERROR("NFF2: Unable to open material library ", path, ".");
+ return;
+ }
+
+ // get the size of the file
+ const unsigned int m = (unsigned int)file->FileSize();
+
+ // allocate storage and copy the contents of the file to a memory buffer
+ // (terminate it with zero)
+ std::vector<char> mBuffer2(m + 1);
+ TextFileToBuffer(file.get(), mBuffer2);
+ const char *buffer = &mBuffer2[0];
+
+ // First of all: remove all comments from the file
+ CommentRemover::RemoveLineComments("//", &mBuffer2[0]);
+
+ // The file should start with the magic sequence "mat"
+ if (!TokenMatch(buffer, "mat", 3)) {
+ ASSIMP_LOG_ERROR("NFF2: Not a valid material library ", path, ".");
+ return;
+ }
+
+ ShadingInfo *curShader = nullptr;
+
+ // No read the file line per line
+ char line[4096];
+ const char *sz;
+ while (GetNextLine(buffer, line)) {
+ SkipSpaces(line, &sz);
+
+ // 'version' defines the version of the file format
+ if (TokenMatch(sz, "version", 7)) {
+ ASSIMP_LOG_INFO("NFF (Sense8) material library file format: ", std::string(sz));
+ }
+ // 'matdef' starts a new material in the file
+ else if (TokenMatch(sz, "matdef", 6)) {
+ // add a new material to the list
+ output.push_back(ShadingInfo());
+ curShader = &output.back();
+
+ // parse the name of the material
+ } else if (!TokenMatch(sz, "valid", 5)) {
+ // check whether we have an active material at the moment
+ if (!IsLineEnd(*sz)) {
+ if (!curShader) {
+ ASSIMP_LOG_ERROR("NFF2 material library: Found element ", sz, "but there is no active material");
+ continue;
+ }
+ } else
+ continue;
+
+ // now read the material property and determine its type
+ aiColor3D c;
+ if (TokenMatch(sz, "ambient", 7)) {
+ AI_NFF_PARSE_TRIPLE(c);
+ curShader->ambient = c;
+ } else if (TokenMatch(sz, "diffuse", 7) || TokenMatch(sz, "ambientdiffuse", 14) /* correct? */) {
+ AI_NFF_PARSE_TRIPLE(c);
+ curShader->diffuse = curShader->ambient = c;
+ } else if (TokenMatch(sz, "specular", 8)) {
+ AI_NFF_PARSE_TRIPLE(c);
+ curShader->specular = c;
+ } else if (TokenMatch(sz, "emission", 8)) {
+ AI_NFF_PARSE_TRIPLE(c);
+ curShader->emissive = c;
+ } else if (TokenMatch(sz, "shininess", 9)) {
+ AI_NFF_PARSE_FLOAT(curShader->shininess);
+ } else if (TokenMatch(sz, "opacity", 7)) {
+ AI_NFF_PARSE_FLOAT(curShader->opacity);
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file into the given scene structure.
+void NFFImporter::InternReadFile(const std::string &pFile,
+ aiScene *pScene, IOSystem *pIOHandler) {
+ std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb"));
+
+ // Check whether we can read from the file
+ if (!file.get())
+ throw DeadlyImportError("Failed to open NFF file ", pFile, ".");
+
+ // allocate storage and copy the contents of the file to a memory buffer
+ // (terminate it with zero)
+ std::vector<char> mBuffer2;
+ TextFileToBuffer(file.get(), mBuffer2);
+ const char *buffer = &mBuffer2[0];
+
+ // mesh arrays - separate here to make the handling of the pointers below easier.
+ std::vector<MeshInfo> meshes;
+ std::vector<MeshInfo> meshesWithNormals;
+ std::vector<MeshInfo> meshesWithUVCoords;
+ std::vector<MeshInfo> meshesLocked;
+
+ char line[4096];
+ const char *sz;
+
+ // camera parameters
+ aiVector3D camPos, camUp(0.f, 1.f, 0.f), camLookAt(0.f, 0.f, 1.f);
+ ai_real angle = 45.f;
+ aiVector2D resolution;
+
+ bool hasCam = false;
+
+ MeshInfo *currentMeshWithNormals = nullptr;
+ MeshInfo *currentMesh = nullptr;
+ MeshInfo *currentMeshWithUVCoords = nullptr;
+
+ ShadingInfo s; // current material info
+
+ // degree of tessellation
+ unsigned int iTesselation = 4;
+
+ // some temporary variables we need to parse the file
+ unsigned int sphere = 0,
+ cylinder = 0,
+ cone = 0,
+ numNamed = 0,
+ dodecahedron = 0,
+ octahedron = 0,
+ tetrahedron = 0,
+ hexahedron = 0;
+
+ // lights imported from the file
+ std::vector<Light> lights;
+
+ // check whether this is the NFF2 file format
+ if (TokenMatch(buffer, "nff", 3)) {
+ const ai_real qnan = get_qnan();
+ const aiColor4D cQNAN = aiColor4D(qnan, 0.f, 0.f, 1.f);
+ const aiVector3D vQNAN = aiVector3D(qnan, 0.f, 0.f);
+
+ // another NFF file format ... just a raw parser has been implemented
+ // no support for further details, I don't think it is worth the effort
+ // http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/nff/nff2.html
+ // http://www.netghost.narod.ru/gff/graphics/summary/sense8.htm
+
+ // First of all: remove all comments from the file
+ CommentRemover::RemoveLineComments("//", &mBuffer2[0]);
+
+ while (GetNextLine(buffer, line)) {
+ SkipSpaces(line, &sz);
+ if (TokenMatch(sz, "version", 7)) {
+ ASSIMP_LOG_INFO("NFF (Sense8) file format: ", sz);
+ } else if (TokenMatch(sz, "viewpos", 7)) {
+ AI_NFF_PARSE_TRIPLE(camPos);
+ hasCam = true;
+ } else if (TokenMatch(sz, "viewdir", 7)) {
+ AI_NFF_PARSE_TRIPLE(camLookAt);
+ hasCam = true;
+ }
+ // This starts a new object section
+ else if (!IsSpaceOrNewLine(*sz)) {
+ unsigned int subMeshIdx = 0;
+
+ // read the name of the object, skip all spaces
+ // at the end of it.
+ const char *sz3 = sz;
+ while (!IsSpaceOrNewLine(*sz))
+ ++sz;
+ std::string objectName = std::string(sz3, (unsigned int)(sz - sz3));
+
+ const unsigned int objStart = (unsigned int)meshes.size();
+
+ // There could be a material table in a separate file
+ std::vector<ShadingInfo> materialTable;
+ while (true) {
+ AI_NFF2_GET_NEXT_TOKEN();
+
+ // material table - an external file
+ if (TokenMatch(sz, "mtable", 6)) {
+ SkipSpaces(&sz);
+ sz3 = sz;
+ while (!IsSpaceOrNewLine(*sz))
+ ++sz;
+ const unsigned int diff = (unsigned int)(sz - sz3);
+ if (!diff)
+ ASSIMP_LOG_WARN("NFF2: Found empty mtable token");
+ else {
+ // The material table has the file extension .mat.
+ // If it is not there, we need to append it
+ std::string path = std::string(sz3, diff);
+ if (std::string::npos == path.find_last_of(".mat")) {
+ path.append(".mat");
+ }
+
+ // Now extract the working directory from the path to
+ // this file and append the material library filename
+ // to it.
+ std::string::size_type sepPos;
+ if ((std::string::npos == (sepPos = path.find_last_of('\\')) || !sepPos) &&
+ (std::string::npos == (sepPos = path.find_last_of('/')) || !sepPos)) {
+ sepPos = pFile.find_last_of('\\');
+ if (std::string::npos == sepPos) {
+ sepPos = pFile.find_last_of('/');
+ }
+ if (std::string::npos != sepPos) {
+ path = pFile.substr(0, sepPos + 1) + path;
+ }
+ }
+ LoadNFF2MaterialTable(materialTable, path, pIOHandler);
+ }
+ } else
+ break;
+ }
+
+ // read the numbr of vertices
+ unsigned int num = ::strtoul10(sz, &sz);
+
+ // temporary storage
+ std::vector<aiColor4D> tempColors;
+ std::vector<aiVector3D> tempPositions, tempTextureCoords, tempNormals;
+
+ bool hasNormals = false, hasUVs = false, hasColor = false;
+
+ tempPositions.reserve(num);
+ tempColors.reserve(num);
+ tempNormals.reserve(num);
+ tempTextureCoords.reserve(num);
+ for (unsigned int i = 0; i < num; ++i) {
+ AI_NFF2_GET_NEXT_TOKEN();
+ aiVector3D v;
+ AI_NFF_PARSE_TRIPLE(v);
+ tempPositions.push_back(v);
+
+ // parse all other attributes in the line
+ while (true) {
+ SkipSpaces(&sz);
+ if (IsLineEnd(*sz)) break;
+
+ // color definition
+ if (TokenMatch(sz, "0x", 2)) {
+ hasColor = true;
+ unsigned int numIdx = ::strtoul16(sz, &sz);
+ aiColor4D clr;
+ clr.a = 1.f;
+
+ // 0xRRGGBB
+ clr.r = ((numIdx >> 16u) & 0xff) / 255.f;
+ clr.g = ((numIdx >> 8u) & 0xff) / 255.f;
+ clr.b = ((numIdx)&0xff) / 255.f;
+ tempColors.push_back(clr);
+ }
+ // normal vector
+ else if (TokenMatch(sz, "norm", 4)) {
+ hasNormals = true;
+ AI_NFF_PARSE_TRIPLE(v);
+ tempNormals.push_back(v);
+ }
+ // UV coordinate
+ else if (TokenMatch(sz, "uv", 2)) {
+ hasUVs = true;
+ AI_NFF_PARSE_FLOAT(v.x);
+ AI_NFF_PARSE_FLOAT(v.y);
+ v.z = 0.f;
+ tempTextureCoords.push_back(v);
+ }
+ }
+
+ // fill in dummies for all attributes that have not been set
+ if (tempNormals.size() != tempPositions.size())
+ tempNormals.push_back(vQNAN);
+
+ if (tempTextureCoords.size() != tempPositions.size())
+ tempTextureCoords.push_back(vQNAN);
+
+ if (tempColors.size() != tempPositions.size())
+ tempColors.push_back(cQNAN);
+ }
+
+ AI_NFF2_GET_NEXT_TOKEN();
+ if (!num) throw DeadlyImportError("NFF2: There are zero vertices");
+ num = ::strtoul10(sz, &sz);
+
+ std::vector<unsigned int> tempIdx;
+ tempIdx.reserve(10);
+ for (unsigned int i = 0; i < num; ++i) {
+ AI_NFF2_GET_NEXT_TOKEN();
+ SkipSpaces(line, &sz);
+ unsigned int numIdx = ::strtoul10(sz, &sz);
+
+ // read all faces indices
+ if (numIdx) {
+ // mesh.faces.push_back(numIdx);
+ // tempIdx.erase(tempIdx.begin(),tempIdx.end());
+ tempIdx.resize(numIdx);
+
+ for (unsigned int a = 0; a < numIdx; ++a) {
+ SkipSpaces(sz, &sz);
+ unsigned int m = ::strtoul10(sz, &sz);
+ if (m >= (unsigned int)tempPositions.size()) {
+ ASSIMP_LOG_ERROR("NFF2: Vertex index overflow");
+ m = 0;
+ }
+ // mesh.vertices.push_back (tempPositions[idx]);
+ tempIdx[a] = m;
+ }
+ }
+
+ // build a temporary shader object for the face.
+ ShadingInfo shader;
+ unsigned int matIdx = 0;
+
+ // white material color - we have vertex colors
+ shader.color = aiColor3D(1.f, 1.f, 1.f);
+ aiColor4D c = aiColor4D(1.f, 1.f, 1.f, 1.f);
+ while (true) {
+ SkipSpaces(sz, &sz);
+ if (IsLineEnd(*sz)) break;
+
+ // per-polygon colors
+ if (TokenMatch(sz, "0x", 2)) {
+ hasColor = true;
+ const char *sz2 = sz;
+ numIdx = ::strtoul16(sz, &sz);
+ const unsigned int diff = (unsigned int)(sz - sz2);
+
+ // 0xRRGGBB
+ if (diff > 3) {
+ c.r = ((numIdx >> 16u) & 0xff) / 255.f;
+ c.g = ((numIdx >> 8u) & 0xff) / 255.f;
+ c.b = ((numIdx)&0xff) / 255.f;
+ }
+ // 0xRGB
+ else {
+ c.r = ((numIdx >> 8u) & 0xf) / 16.f;
+ c.g = ((numIdx >> 4u) & 0xf) / 16.f;
+ c.b = ((numIdx)&0xf) / 16.f;
+ }
+ }
+ // TODO - implement texture mapping here
+#if 0
+ // mirror vertex texture coordinate?
+ else if (TokenMatch(sz,"mirror",6))
+ {
+ }
+ // texture coordinate scaling
+ else if (TokenMatch(sz,"scale",5))
+ {
+ }
+ // texture coordinate translation
+ else if (TokenMatch(sz,"trans",5))
+ {
+ }
+ // texture coordinate rotation angle
+ else if (TokenMatch(sz,"rot",3))
+ {
+ }
+#endif
+
+ // texture file name for this polygon + mapping information
+ else if ('_' == sz[0]) {
+ // get mapping information
+ switch (sz[1]) {
+ case 'v':
+ case 'V':
+
+ shader.shaded = false;
+ break;
+
+ case 't':
+ case 'T':
+ case 'u':
+ case 'U':
+
+ ASSIMP_LOG_WARN("Unsupported NFF2 texture attribute: trans");
+ };
+ if (!sz[1] || '_' != sz[2]) {
+ ASSIMP_LOG_WARN("NFF2: Expected underscore after texture attributes");
+ continue;
+ }
+ const char *sz2 = sz + 3;
+ while (!IsSpaceOrNewLine(*sz))
+ ++sz;
+ const unsigned int diff = (unsigned int)(sz - sz2);
+ if (diff) shader.texFile = std::string(sz2, diff);
+ }
+
+ // Two-sided material?
+ else if (TokenMatch(sz, "both", 4)) {
+ shader.twoSided = true;
+ }
+
+ // Material ID?
+ else if (!materialTable.empty() && TokenMatch(sz, "matid", 5)) {
+ SkipSpaces(&sz);
+ matIdx = ::strtoul10(sz, &sz);
+ if (matIdx >= materialTable.size()) {
+ ASSIMP_LOG_ERROR("NFF2: Material index overflow.");
+ matIdx = 0;
+ }
+
+ // now combine our current shader with the shader we
+ // read from the material table.
+ ShadingInfo &mat = materialTable[matIdx];
+ shader.ambient = mat.ambient;
+ shader.diffuse = mat.diffuse;
+ shader.emissive = mat.emissive;
+ shader.opacity = mat.opacity;
+ shader.specular = mat.specular;
+ shader.shininess = mat.shininess;
+ } else
+ SkipToken(sz);
+ }
+
+ // search the list of all shaders we have for this object whether
+ // there is an identical one. In this case, we append our mesh
+ // data to it.
+ MeshInfo *mesh = nullptr;
+ for (std::vector<MeshInfo>::iterator it = meshes.begin() + objStart, end = meshes.end();
+ it != end; ++it) {
+ if ((*it).shader == shader && (*it).matIndex == matIdx) {
+ // we have one, we can append our data to it
+ mesh = &(*it);
+ }
+ }
+ if (!mesh) {
+ meshes.push_back(MeshInfo(PatchType_Simple, false));
+ mesh = &meshes.back();
+ mesh->matIndex = matIdx;
+
+ // We need to add a new mesh to the list. We assign
+ // an unique name to it to make sure the scene will
+ // pass the validation step for the moment.
+ // TODO: fix naming of objects in the scenegraph later
+ if (objectName.length()) {
+ ::strcpy(mesh->name, objectName.c_str());
+ ASSIMP_itoa10(&mesh->name[objectName.length()], 30, subMeshIdx++);
+ }
+
+ // copy the shader to the mesh.
+ mesh->shader = shader;
+ }
+
+ // fill the mesh with data
+ if (!tempIdx.empty()) {
+ mesh->faces.push_back((unsigned int)tempIdx.size());
+ for (std::vector<unsigned int>::const_iterator it = tempIdx.begin(), end = tempIdx.end();
+ it != end; ++it) {
+ unsigned int m = *it;
+
+ // copy colors -vertex color specifications override polygon color specifications
+ if (hasColor) {
+ const aiColor4D &clr = tempColors[m];
+ mesh->colors.push_back((is_qnan(clr.r) ? c : clr));
+ }
+
+ // positions should always be there
+ mesh->vertices.push_back(tempPositions[m]);
+
+ // copy normal vectors
+ if (hasNormals)
+ mesh->normals.push_back(tempNormals[m]);
+
+ // copy texture coordinates
+ if (hasUVs)
+ mesh->uvs.push_back(tempTextureCoords[m]);
+ }
+ }
+ }
+ if (!num) throw DeadlyImportError("NFF2: There are zero faces");
+ }
+ }
+ camLookAt = camLookAt + camPos;
+ } else // "Normal" Neutral file format that is quite more common
+ {
+ while (GetNextLine(buffer, line)) {
+ sz = line;
+ if ('p' == line[0] || TokenMatch(sz, "tpp", 3)) {
+ MeshInfo *out = nullptr;
+
+ // 'tpp' - texture polygon patch primitive
+ if ('t' == line[0]) {
+ currentMeshWithUVCoords = nullptr;
+ for (auto &mesh : meshesWithUVCoords) {
+ if (mesh.shader == s) {
+ currentMeshWithUVCoords = &mesh;
+ break;
+ }
+ }
+
+ if (!currentMeshWithUVCoords) {
+ meshesWithUVCoords.push_back(MeshInfo(PatchType_UVAndNormals));
+ currentMeshWithUVCoords = &meshesWithUVCoords.back();
+ currentMeshWithUVCoords->shader = s;
+ }
+ out = currentMeshWithUVCoords;
+ }
+ // 'pp' - polygon patch primitive
+ else if ('p' == line[1]) {
+ currentMeshWithNormals = nullptr;
+ for (auto &mesh : meshesWithNormals) {
+ if (mesh.shader == s) {
+ currentMeshWithNormals = &mesh;
+ break;
+ }
+ }
+
+ if (!currentMeshWithNormals) {
+ meshesWithNormals.push_back(MeshInfo(PatchType_Normals));
+ currentMeshWithNormals = &meshesWithNormals.back();
+ currentMeshWithNormals->shader = s;
+ }
+ sz = &line[2];
+ out = currentMeshWithNormals;
+ }
+ // 'p' - polygon primitive
+ else {
+ currentMesh = nullptr;
+ for (auto &mesh : meshes) {
+ if (mesh.shader == s) {
+ currentMesh = &mesh;
+ break;
+ }
+ }
+
+ if (!currentMesh) {
+ meshes.push_back(MeshInfo(PatchType_Simple));
+ currentMesh = &meshes.back();
+ currentMesh->shader = s;
+ }
+ sz = &line[1];
+ out = currentMesh;
+ }
+ SkipSpaces(sz, &sz);
+ unsigned int m = strtoul10(sz);
+
+ // ---- flip the face order
+ out->vertices.resize(out->vertices.size() + m);
+ if (out != currentMesh) {
+ out->normals.resize(out->vertices.size());
+ }
+ if (out == currentMeshWithUVCoords) {
+ out->uvs.resize(out->vertices.size());
+ }
+ for (unsigned int n = 0; n < m; ++n) {
+ if (!GetNextLine(buffer, line)) {
+ ASSIMP_LOG_ERROR("NFF: Unexpected EOF was encountered. Patch definition incomplete");
+ continue;
+ }
+
+ aiVector3D v;
+ sz = &line[0];
+ AI_NFF_PARSE_TRIPLE(v);
+ out->vertices[out->vertices.size() - n - 1] = v;
+
+ if (out != currentMesh) {
+ AI_NFF_PARSE_TRIPLE(v);
+ out->normals[out->vertices.size() - n - 1] = v;
+ }
+ if (out == currentMeshWithUVCoords) {
+ // FIX: in one test file this wraps over multiple lines
+ SkipSpaces(&sz);
+ if (IsLineEnd(*sz)) {
+ GetNextLine(buffer, line);
+ sz = line;
+ }
+ AI_NFF_PARSE_FLOAT(v.x);
+ SkipSpaces(&sz);
+ if (IsLineEnd(*sz)) {
+ GetNextLine(buffer, line);
+ sz = line;
+ }
+ AI_NFF_PARSE_FLOAT(v.y);
+ v.y = 1.f - v.y;
+ out->uvs[out->vertices.size() - n - 1] = v;
+ }
+ }
+ out->faces.push_back(m);
+ }
+ // 'f' - shading information block
+ else if (TokenMatch(sz, "f", 1)) {
+ ai_real d;
+
+ // read the RGB colors
+ AI_NFF_PARSE_TRIPLE(s.color);
+
+ // read the other properties
+ AI_NFF_PARSE_FLOAT(s.diffuse.r);
+ AI_NFF_PARSE_FLOAT(s.specular.r);
+ AI_NFF_PARSE_FLOAT(d); // skip shininess and transmittance
+ AI_NFF_PARSE_FLOAT(d);
+ AI_NFF_PARSE_FLOAT(s.refracti);
+
+ // NFF2 uses full colors here so we need to use them too
+ // although NFF uses simple scaling factors
+ s.diffuse.g = s.diffuse.b = s.diffuse.r;
+ s.specular.g = s.specular.b = s.specular.r;
+
+ // if the next one is NOT a number we assume it is a texture file name
+ // this feature is used by some NFF files on the internet and it has
+ // been implemented as it can be really useful
+ SkipSpaces(&sz);
+ if (!IsNumeric(*sz)) {
+ // TODO: Support full file names with spaces and quotation marks ...
+ const char *p = sz;
+ while (!IsSpaceOrNewLine(*sz))
+ ++sz;
+
+ unsigned int diff = (unsigned int)(sz - p);
+ if (diff) {
+ s.texFile = std::string(p, diff);
+ }
+ } else {
+ AI_NFF_PARSE_FLOAT(s.ambient); // optional
+ }
+ }
+ // 'shader' - other way to specify a texture
+ else if (TokenMatch(sz, "shader", 6)) {
+ SkipSpaces(&sz);
+ const char *old = sz;
+ while (!IsSpaceOrNewLine(*sz))
+ ++sz;
+ s.texFile = std::string(old, (uintptr_t)sz - (uintptr_t)old);
+ }
+ // 'l' - light source
+ else if (TokenMatch(sz, "l", 1)) {
+ lights.push_back(Light());
+ Light &light = lights.back();
+
+ AI_NFF_PARSE_TRIPLE(light.position);
+ AI_NFF_PARSE_FLOAT(light.intensity);
+ AI_NFF_PARSE_TRIPLE(light.color);
+ }
+ // 's' - sphere
+ else if (TokenMatch(sz, "s", 1)) {
+ meshesLocked.push_back(MeshInfo(PatchType_Simple, true));
+ MeshInfo &curMesh = meshesLocked.back();
+ curMesh.shader = s;
+ curMesh.shader.mapping = aiTextureMapping_SPHERE;
+
+ AI_NFF_PARSE_SHAPE_INFORMATION();
+
+ // we don't need scaling or translation here - we do it in the node's transform
+ StandardShapes::MakeSphere(iTesselation, curMesh.vertices);
+ curMesh.faces.resize(curMesh.vertices.size() / 3, 3);
+
+ // generate a name for the mesh
+ ::ai_snprintf(curMesh.name, MeshInfo::MaxNameLen, "sphere_%i", sphere++);
+ }
+ // 'dod' - dodecahedron
+ else if (TokenMatch(sz, "dod", 3)) {
+ meshesLocked.push_back(MeshInfo(PatchType_Simple, true));
+ MeshInfo &curMesh = meshesLocked.back();
+ curMesh.shader = s;
+ curMesh.shader.mapping = aiTextureMapping_SPHERE;
+
+ AI_NFF_PARSE_SHAPE_INFORMATION();
+
+ // we don't need scaling or translation here - we do it in the node's transform
+ StandardShapes::MakeDodecahedron(curMesh.vertices);
+ curMesh.faces.resize(curMesh.vertices.size() / 3, 3);
+
+ // generate a name for the mesh
+ ::ai_snprintf(curMesh.name, 128, "dodecahedron_%i", dodecahedron++);
+ }
+
+ // 'oct' - octahedron
+ else if (TokenMatch(sz, "oct", 3)) {
+ meshesLocked.push_back(MeshInfo(PatchType_Simple, true));
+ MeshInfo &curMesh = meshesLocked.back();
+ curMesh.shader = s;
+ curMesh.shader.mapping = aiTextureMapping_SPHERE;
+
+ AI_NFF_PARSE_SHAPE_INFORMATION();
+
+ // we don't need scaling or translation here - we do it in the node's transform
+ StandardShapes::MakeOctahedron(curMesh.vertices);
+ curMesh.faces.resize(curMesh.vertices.size() / 3, 3);
+
+ // generate a name for the mesh
+ ::ai_snprintf(curMesh.name, MeshInfo::MaxNameLen, "octahedron_%i", octahedron++);
+ }
+
+ // 'tet' - tetrahedron
+ else if (TokenMatch(sz, "tet", 3)) {
+ meshesLocked.push_back(MeshInfo(PatchType_Simple, true));
+ MeshInfo &curMesh = meshesLocked.back();
+ curMesh.shader = s;
+ curMesh.shader.mapping = aiTextureMapping_SPHERE;
+
+ AI_NFF_PARSE_SHAPE_INFORMATION();
+
+ // we don't need scaling or translation here - we do it in the node's transform
+ StandardShapes::MakeTetrahedron(curMesh.vertices);
+ curMesh.faces.resize(curMesh.vertices.size() / 3, 3);
+
+ // generate a name for the mesh
+ ::ai_snprintf(curMesh.name, MeshInfo::MaxNameLen, "tetrahedron_%i", tetrahedron++);
+ }
+
+ // 'hex' - hexahedron
+ else if (TokenMatch(sz, "hex", 3)) {
+ meshesLocked.push_back(MeshInfo(PatchType_Simple, true));
+ MeshInfo &curMesh = meshesLocked.back();
+ curMesh.shader = s;
+ curMesh.shader.mapping = aiTextureMapping_BOX;
+
+ AI_NFF_PARSE_SHAPE_INFORMATION();
+
+ // we don't need scaling or translation here - we do it in the node's transform
+ StandardShapes::MakeHexahedron(curMesh.vertices);
+ curMesh.faces.resize(curMesh.vertices.size() / 3, 3);
+
+ // generate a name for the mesh
+ ::ai_snprintf(curMesh.name, MeshInfo::MaxNameLen, "hexahedron_%i", hexahedron++);
+ }
+ // 'c' - cone
+ else if (TokenMatch(sz, "c", 1)) {
+ meshesLocked.push_back(MeshInfo(PatchType_Simple, true));
+ MeshInfo &curMesh = meshesLocked.back();
+ curMesh.shader = s;
+ curMesh.shader.mapping = aiTextureMapping_CYLINDER;
+
+ if (!GetNextLine(buffer, line)) {
+ ASSIMP_LOG_ERROR("NFF: Unexpected end of file (cone definition not complete)");
+ break;
+ }
+ sz = line;
+
+ // read the two center points and the respective radii
+ aiVector3D center1, center2;
+ ai_real radius1 = 0.f, radius2 = 0.f;
+ AI_NFF_PARSE_TRIPLE(center1);
+ AI_NFF_PARSE_FLOAT(radius1);
+
+ if (!GetNextLine(buffer, line)) {
+ ASSIMP_LOG_ERROR("NFF: Unexpected end of file (cone definition not complete)");
+ break;
+ }
+ sz = line;
+
+ AI_NFF_PARSE_TRIPLE(center2);
+ AI_NFF_PARSE_FLOAT(radius2);
+
+ // compute the center point of the cone/cylinder -
+ // it is its local transformation origin
+ curMesh.dir = center2 - center1;
+ curMesh.center = center1 + curMesh.dir / (ai_real)2.0;
+
+ ai_real f;
+ if ((f = curMesh.dir.Length()) < 10e-3f) {
+ ASSIMP_LOG_ERROR("NFF: Cone height is close to zero");
+ continue;
+ }
+ curMesh.dir /= f; // normalize
+
+ // generate the cone - it consists of simple triangles
+ StandardShapes::MakeCone(f, radius1, radius2,
+ integer_pow(4, iTesselation), curMesh.vertices);
+
+ // MakeCone() returns tris
+ curMesh.faces.resize(curMesh.vertices.size() / 3, 3);
+
+ // generate a name for the mesh. 'cone' if it a cone,
+ // 'cylinder' if it is a cylinder. Funny, isn't it?
+ if (radius1 != radius2) {
+ ::ai_snprintf(curMesh.name, MeshInfo::MaxNameLen, "cone_%i", cone++);
+ } else {
+ ::ai_snprintf(curMesh.name, MeshInfo::MaxNameLen, "cylinder_%i", cylinder++);
+ }
+ }
+ // 'tess' - tessellation
+ else if (TokenMatch(sz, "tess", 4)) {
+ SkipSpaces(&sz);
+ iTesselation = strtoul10(sz);
+ }
+ // 'from' - camera position
+ else if (TokenMatch(sz, "from", 4)) {
+ AI_NFF_PARSE_TRIPLE(camPos);
+ hasCam = true;
+ }
+ // 'at' - camera look-at vector
+ else if (TokenMatch(sz, "at", 2)) {
+ AI_NFF_PARSE_TRIPLE(camLookAt);
+ hasCam = true;
+ }
+ // 'up' - camera up vector
+ else if (TokenMatch(sz, "up", 2)) {
+ AI_NFF_PARSE_TRIPLE(camUp);
+ hasCam = true;
+ }
+ // 'angle' - (half?) camera field of view
+ else if (TokenMatch(sz, "angle", 5)) {
+ AI_NFF_PARSE_FLOAT(angle);
+ hasCam = true;
+ }
+ // 'resolution' - used to compute the screen aspect
+ else if (TokenMatch(sz, "resolution", 10)) {
+ AI_NFF_PARSE_FLOAT(resolution.x);
+ AI_NFF_PARSE_FLOAT(resolution.y);
+ hasCam = true;
+ }
+ // 'pb' - bezier patch. Not supported yet
+ else if (TokenMatch(sz, "pb", 2)) {
+ ASSIMP_LOG_ERROR("NFF: Encountered unsupported ID: bezier patch");
+ }
+ // 'pn' - NURBS. Not supported yet
+ else if (TokenMatch(sz, "pn", 2) || TokenMatch(sz, "pnn", 3)) {
+ ASSIMP_LOG_ERROR("NFF: Encountered unsupported ID: NURBS");
+ }
+ // '' - comment
+ else if ('#' == line[0]) {
+ const char *space;
+ SkipSpaces(&line[1], &space);
+ if (!IsLineEnd(*space)) {
+ ASSIMP_LOG_INFO(space);
+ }
+ }
+ }
+ }
+
+ // copy all arrays into one large
+ meshes.reserve(meshes.size() + meshesLocked.size() + meshesWithNormals.size() + meshesWithUVCoords.size());
+ meshes.insert(meshes.end(), meshesLocked.begin(), meshesLocked.end());
+ meshes.insert(meshes.end(), meshesWithNormals.begin(), meshesWithNormals.end());
+ meshes.insert(meshes.end(), meshesWithUVCoords.begin(), meshesWithUVCoords.end());
+
+ // now generate output meshes. first find out how many meshes we'll need
+ std::vector<MeshInfo>::const_iterator it = meshes.begin(), end = meshes.end();
+ for (; it != end; ++it) {
+ if (!(*it).faces.empty()) {
+ ++pScene->mNumMeshes;
+ if ((*it).name[0]) ++numNamed;
+ }
+ }
+
+ // generate a dummy root node - assign all unnamed elements such
+ // as polygons and polygon patches to the root node and generate
+ // sub nodes for named objects such as spheres and cones.
+ aiNode *const root = new aiNode();
+ root->mName.Set("<NFF_Root>");
+ root->mNumChildren = numNamed + (hasCam ? 1 : 0) + (unsigned int)lights.size();
+ root->mNumMeshes = pScene->mNumMeshes - numNamed;
+
+ aiNode **ppcChildren = nullptr;
+ unsigned int *pMeshes = nullptr;
+ if (root->mNumMeshes)
+ pMeshes = root->mMeshes = new unsigned int[root->mNumMeshes];
+ if (root->mNumChildren)
+ ppcChildren = root->mChildren = new aiNode *[root->mNumChildren];
+
+ // generate the camera
+ if (hasCam) {
+ ai_assert(ppcChildren);
+ aiNode *nd = new aiNode();
+ *ppcChildren = nd;
+ nd->mName.Set("<NFF_Camera>");
+ nd->mParent = root;
+
+ // allocate the camera in the scene
+ pScene->mNumCameras = 1;
+ pScene->mCameras = new aiCamera *[1];
+ aiCamera *c = pScene->mCameras[0] = new aiCamera;
+
+ c->mName = nd->mName; // make sure the names are identical
+ c->mHorizontalFOV = AI_DEG_TO_RAD(angle);
+ c->mLookAt = camLookAt - camPos;
+ c->mPosition = camPos;
+ c->mUp = camUp;
+
+ // If the resolution is not specified in the file, we
+ // need to set 1.0 as aspect.
+ c->mAspect = (!resolution.y ? 0.f : resolution.x / resolution.y);
+ ++ppcChildren;
+ }
+
+ // generate light sources
+ if (!lights.empty()) {
+ ai_assert(ppcChildren);
+ pScene->mNumLights = (unsigned int)lights.size();
+ pScene->mLights = new aiLight *[pScene->mNumLights];
+ for (unsigned int i = 0; i < pScene->mNumLights; ++i, ++ppcChildren) {
+ const Light &l = lights[i];
+
+ aiNode *nd = new aiNode();
+ *ppcChildren = nd;
+ nd->mParent = root;
+
+ nd->mName.length = ::ai_snprintf(nd->mName.data, 1024, "<NFF_Light%u>", i);
+
+ // allocate the light in the scene data structure
+ aiLight *out = pScene->mLights[i] = new aiLight();
+ out->mName = nd->mName; // make sure the names are identical
+ out->mType = aiLightSource_POINT;
+ out->mColorDiffuse = out->mColorSpecular = l.color * l.intensity;
+ out->mPosition = l.position;
+ }
+ }
+
+ if (!pScene->mNumMeshes) throw DeadlyImportError("NFF: No meshes loaded");
+ pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
+ pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials = pScene->mNumMeshes];
+ unsigned int m = 0;
+ for (it = meshes.begin(); it != end; ++it) {
+ if ((*it).faces.empty()) continue;
+
+ const MeshInfo &src = *it;
+ aiMesh *const mesh = pScene->mMeshes[m] = new aiMesh();
+ mesh->mNumVertices = (unsigned int)src.vertices.size();
+ mesh->mNumFaces = (unsigned int)src.faces.size();
+
+ // Generate sub nodes for named meshes
+ if (src.name[0] && nullptr != ppcChildren) {
+ aiNode *const node = *ppcChildren = new aiNode();
+ node->mParent = root;
+ node->mNumMeshes = 1;
+ node->mMeshes = new unsigned int[1];
+ node->mMeshes[0] = m;
+ node->mName.Set(src.name);
+
+ // setup the transformation matrix of the node
+ aiMatrix4x4::FromToMatrix(aiVector3D(0.f, 1.f, 0.f),
+ src.dir, node->mTransformation);
+
+ aiMatrix4x4 &mat = node->mTransformation;
+ mat.a1 *= src.radius.x;
+ mat.b1 *= src.radius.x;
+ mat.c1 *= src.radius.x;
+ mat.a2 *= src.radius.y;
+ mat.b2 *= src.radius.y;
+ mat.c2 *= src.radius.y;
+ mat.a3 *= src.radius.z;
+ mat.b3 *= src.radius.z;
+ mat.c3 *= src.radius.z;
+ mat.a4 = src.center.x;
+ mat.b4 = src.center.y;
+ mat.c4 = src.center.z;
+
+ ++ppcChildren;
+ } else {
+ *pMeshes++ = m;
+ }
+
+ // copy vertex positions
+ mesh->mVertices = new aiVector3D[mesh->mNumVertices];
+ ::memcpy(mesh->mVertices, &src.vertices[0],
+ sizeof(aiVector3D) * mesh->mNumVertices);
+
+ // NFF2: there could be vertex colors
+ if (!src.colors.empty()) {
+ ai_assert(src.colors.size() == src.vertices.size());
+
+ // copy vertex colors
+ mesh->mColors[0] = new aiColor4D[mesh->mNumVertices];
+ ::memcpy(mesh->mColors[0], &src.colors[0],
+ sizeof(aiColor4D) * mesh->mNumVertices);
+ }
+
+ if (!src.normals.empty()) {
+ ai_assert(src.normals.size() == src.vertices.size());
+
+ // copy normal vectors
+ mesh->mNormals = new aiVector3D[mesh->mNumVertices];
+ ::memcpy(mesh->mNormals, &src.normals[0],
+ sizeof(aiVector3D) * mesh->mNumVertices);
+ }
+
+ if (!src.uvs.empty()) {
+ ai_assert(src.uvs.size() == src.vertices.size());
+
+ // copy texture coordinates
+ mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
+ ::memcpy(mesh->mTextureCoords[0], &src.uvs[0],
+ sizeof(aiVector3D) * mesh->mNumVertices);
+ }
+
+ // generate faces
+ unsigned int p = 0;
+ aiFace *pFace = mesh->mFaces = new aiFace[mesh->mNumFaces];
+ for (std::vector<unsigned int>::const_iterator it2 = src.faces.begin(),
+ end2 = src.faces.end();
+ it2 != end2; ++it2, ++pFace) {
+ pFace->mIndices = new unsigned int[pFace->mNumIndices = *it2];
+ for (unsigned int o = 0; o < pFace->mNumIndices; ++o)
+ pFace->mIndices[o] = p++;
+ }
+
+ // generate a material for the mesh
+ aiMaterial *pcMat = (aiMaterial *)(pScene->mMaterials[m] = new aiMaterial());
+
+ mesh->mMaterialIndex = m++;
+
+ aiString matName;
+ matName.Set(AI_DEFAULT_MATERIAL_NAME);
+ pcMat->AddProperty(&matName, AI_MATKEY_NAME);
+
+ // FIX: Ignore diffuse == 0
+ aiColor3D c = src.shader.color * (src.shader.diffuse.r ? src.shader.diffuse : aiColor3D(1.f, 1.f, 1.f));
+ pcMat->AddProperty(&c, 1, AI_MATKEY_COLOR_DIFFUSE);
+ c = src.shader.color * src.shader.specular;
+ pcMat->AddProperty(&c, 1, AI_MATKEY_COLOR_SPECULAR);
+
+ // NFF2 - default values for NFF
+ pcMat->AddProperty(&src.shader.ambient, 1, AI_MATKEY_COLOR_AMBIENT);
+ pcMat->AddProperty(&src.shader.emissive, 1, AI_MATKEY_COLOR_EMISSIVE);
+ pcMat->AddProperty(&src.shader.opacity, 1, AI_MATKEY_OPACITY);
+
+ // setup the first texture layer, if existing
+ if (src.shader.texFile.length()) {
+ matName.Set(src.shader.texFile);
+ pcMat->AddProperty(&matName, AI_MATKEY_TEXTURE_DIFFUSE(0));
+
+ if (aiTextureMapping_UV != src.shader.mapping) {
+
+ aiVector3D v(0.f, -1.f, 0.f);
+ pcMat->AddProperty(&v, 1, AI_MATKEY_TEXMAP_AXIS_DIFFUSE(0));
+ pcMat->AddProperty((int *)&src.shader.mapping, 1, AI_MATKEY_MAPPING_DIFFUSE(0));
+ }
+ }
+
+ // setup the name of the material
+ if (src.shader.name.length()) {
+ matName.Set(src.shader.texFile);
+ pcMat->AddProperty(&matName, AI_MATKEY_NAME);
+ }
+
+ // setup some more material properties that are specific to NFF2
+ int i;
+ if (src.shader.twoSided) {
+ i = 1;
+ pcMat->AddProperty(&i, 1, AI_MATKEY_TWOSIDED);
+ }
+ i = (src.shader.shaded ? aiShadingMode_Gouraud : aiShadingMode_NoShading);
+ if (src.shader.shininess) {
+ i = aiShadingMode_Phong;
+ pcMat->AddProperty(&src.shader.shininess, 1, AI_MATKEY_SHININESS);
+ }
+ pcMat->AddProperty(&i, 1, AI_MATKEY_SHADING_MODEL);
+ }
+ pScene->mRootNode = root;
+}
+
+#endif // !! ASSIMP_BUILD_NO_NFF_IMPORTER