123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158 |
- /*
- ---------------------------------------------------------------------------
- Open Asset Import Library (assimp)
- ---------------------------------------------------------------------------
- Copyright (c) 2006-2025, 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>
- namespace Assimp {
- static constexpr aiImporterDesc desc = {
- "Neutral File Format Importer",
- "",
- "",
- "",
- aiImporterFlags_SupportBinaryFlavour,
- 0,
- 0,
- 0,
- 0,
- "enff nff"
- };
- // ------------------------------------------------------------------------------------------------
- // 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, lineEnd); \
- 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, lineEnd); \
- } 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) {
- 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, *lineEnd = &line[2095]+1;
- while (GetNextLine(buffer, line)) {
- SkipSpaces(line, &sz, lineEnd);
- // '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.emplace_back();
- 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 &file, aiScene *pScene, IOSystem *pIOHandler) {
- std::unique_ptr<IOStream> stream(pIOHandler->Open(file, "rb"));
- if (!stream) {
- throw DeadlyImportError("Failed to open NFF file ", file, ".");
- }
- // allocate storage and copy the contents of the file to a memory buffer
- // (terminate it with zero)
- std::vector<char> mBuffer2;
- TextFileToBuffer(stream.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 *lineEnd = &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, lineEnd);
- 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, lineEnd);
- 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 = file.find_last_of('\\');
- if (std::string::npos == sepPos) {
- sepPos = file.find_last_of('/');
- }
- if (std::string::npos != sepPos) {
- path = file.substr(0, sepPos + 1) + path;
- }
- }
- LoadNFF2MaterialTable(materialTable, path, pIOHandler);
- }
- } else
- break;
- }
- // read the number 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, lineEnd);
- 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, lineEnd);
- unsigned int numIdx = strtoul10(sz, &sz);
- // read all faces indices
- if (numIdx) {
- tempIdx.resize(numIdx);
- for (unsigned int a = 0; a < numIdx; ++a) {
- SkipSpaces(sz, &sz, lineEnd);
- unsigned int m = strtoul10(sz, &sz);
- if (m >= (unsigned int)tempPositions.size()) {
- ASSIMP_LOG_ERROR("NFF2: Vertex index overflow");
- m = 0;
- }
- 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, lineEnd);
- 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, lineEnd);
- 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, lineEnd);
- }
- // 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.emplace_back(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 scene-graph later
- if (objectName.length()) {
- ::strncpy(mesh->name, objectName.c_str(), objectName.size());
- 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.emplace_back(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.emplace_back(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.emplace_back(PatchType_Simple);
- currentMesh = &meshes.back();
- currentMesh->shader = s;
- }
- sz = &line[1];
- out = currentMesh;
- }
- SkipSpaces(sz, &sz, lineEnd);
- 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, lineEnd);
- if (IsLineEnd(*sz)) {
- GetNextLine(buffer, line);
- sz = line;
- }
- AI_NFF_PARSE_FLOAT(v.x);
- SkipSpaces(&sz, lineEnd);
- 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, lineEnd);
- 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
- }
- } else if (TokenMatch(sz, "shader", 6)) { // 'shader' - other way to specify a texture
- SkipSpaces(&sz, lineEnd);
- 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.emplace_back();
- 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.emplace_back(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.emplace_back(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.emplace_back(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.emplace_back(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.emplace_back(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.emplace_back(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, lineEnd);
- 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, lineEnd);
- 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;
- }
- } // namespace Assimp
- #endif // !! ASSIMP_BUILD_NO_NFF_IMPORTER
|