1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2021, assimp team
7 
8 All rights reserved.
9 
10 Redistribution and use of this software in source and binary forms,
11 with or without modification, are permitted provided that the following
12 conditions are met:
13 
14 * Redistributions of source code must retain the above
15   copyright notice, this list of conditions and the
16   following disclaimer.
17 
18 * Redistributions in binary form must reproduce the above
19   copyright notice, this list of conditions and the
20   following disclaimer in the documentation and/or other
21   materials provided with the distribution.
22 
23 * Neither the name of the assimp team, nor the names of its
24   contributors may be used to endorse or promote products
25   derived from this software without specific prior
26   written permission of the assimp team.
27 
28 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 ---------------------------------------------------------------------------
40 */
41 
42 /** @file  Q3DLoader.cpp
43  *  @brief Implementation of the Q3D importer class
44  */
45 
46 #ifndef ASSIMP_BUILD_NO_Q3D_IMPORTER
47 
48 // internal headers
49 #include "Q3DLoader.h"
50 #include <assimp/StringUtils.h>
51 #include <assimp/StreamReader.h>
52 #include <assimp/fast_atof.h>
53 #include <assimp/importerdesc.h>
54 #include <assimp/scene.h>
55 #include <assimp/DefaultLogger.hpp>
56 #include <assimp/IOSystem.hpp>
57 
58 using namespace Assimp;
59 
60 static const aiImporterDesc desc = {
61     "Quick3D Importer",
62     "",
63     "",
64     "http://www.quick3d.com/",
65     aiImporterFlags_SupportBinaryFlavour,
66     0,
67     0,
68     0,
69     0,
70     "q3o q3s"
71 };
72 
73 // ------------------------------------------------------------------------------------------------
74 // Constructor to be privately used by Importer
Q3DImporter()75 Q3DImporter::Q3DImporter() {
76     // empty
77 }
78 
79 // ------------------------------------------------------------------------------------------------
80 // Destructor, private as well
~Q3DImporter()81 Q3DImporter::~Q3DImporter() {
82     // empty
83 }
84 
85 // ------------------------------------------------------------------------------------------------
86 // Returns whether the class can handle the format of the given file.
CanRead(const std::string & pFile,IOSystem * pIOHandler,bool checkSig) const87 bool Q3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
88     const std::string extension = GetExtension(pFile);
89 
90     if (extension == "q3s" || extension == "q3o")
91         return true;
92     else if (!extension.length() || checkSig) {
93         if (!pIOHandler)
94             return true;
95         static const char * const tokens[] = { "quick3Do", "quick3Ds" };
96         return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 2);
97     }
98     return false;
99 }
100 
101 // ------------------------------------------------------------------------------------------------
GetInfo() const102 const aiImporterDesc *Q3DImporter::GetInfo() const {
103     return &desc;
104 }
105 
106 // ------------------------------------------------------------------------------------------------
107 // Imports the given file into the given scene structure.
InternReadFile(const std::string & pFile,aiScene * pScene,IOSystem * pIOHandler)108 void Q3DImporter::InternReadFile(const std::string &pFile,
109         aiScene *pScene, IOSystem *pIOHandler) {
110 
111     auto file = pIOHandler->Open(pFile, "rb");
112     if (!file)
113         throw DeadlyImportError("Quick3D: Could not open ", pFile);
114 
115     StreamReaderLE stream(file);
116 
117     // The header is 22 bytes large
118     if (stream.GetRemainingSize() < 22)
119         throw DeadlyImportError("File is either empty or corrupt: ", pFile);
120 
121     // Check the file's signature
122     if (ASSIMP_strincmp((const char *)stream.GetPtr(), "quick3Do", 8) &&
123             ASSIMP_strincmp((const char *)stream.GetPtr(), "quick3Ds", 8)) {
124         throw DeadlyImportError("Not a Quick3D file. Signature string is: ", ai_str_toprintable((const char *)stream.GetPtr(), 8));
125     }
126 
127     // Print the file format version
128     ASSIMP_LOG_INFO("Quick3D File format version: ",
129             std::string(&((const char *)stream.GetPtr())[8], 2));
130 
131     // ... an store it
132     char major = ((const char *)stream.GetPtr())[8];
133     char minor = ((const char *)stream.GetPtr())[9];
134 
135     stream.IncPtr(10);
136     unsigned int numMeshes = (unsigned int)stream.GetI4();
137     unsigned int numMats = (unsigned int)stream.GetI4();
138     unsigned int numTextures = (unsigned int)stream.GetI4();
139 
140     std::vector<Material> materials;
141     materials.reserve(numMats);
142 
143     std::vector<Mesh> meshes;
144     meshes.reserve(numMeshes);
145 
146     // Allocate the scene root node
147     pScene->mRootNode = new aiNode();
148 
149     aiColor3D fgColor(0.6f, 0.6f, 0.6f);
150 
151     // Now read all file chunks
152     while (true) {
153         if (stream.GetRemainingSize() < 1) break;
154         char c = stream.GetI1();
155         switch (c) {
156             // Meshes chunk
157         case 'm': {
158             for (unsigned int quak = 0; quak < numMeshes; ++quak) {
159                 meshes.push_back(Mesh());
160                 Mesh &mesh = meshes.back();
161 
162                 // read all vertices
163                 unsigned int numVerts = (unsigned int)stream.GetI4();
164                 if (!numVerts)
165                     throw DeadlyImportError("Quick3D: Found mesh with zero vertices");
166 
167                 std::vector<aiVector3D> &verts = mesh.verts;
168                 verts.resize(numVerts);
169 
170                 for (unsigned int i = 0; i < numVerts; ++i) {
171                     verts[i].x = stream.GetF4();
172                     verts[i].y = stream.GetF4();
173                     verts[i].z = stream.GetF4();
174                 }
175 
176                 // read all faces
177                 numVerts = (unsigned int)stream.GetI4();
178                 if (!numVerts)
179                     throw DeadlyImportError("Quick3D: Found mesh with zero faces");
180 
181                 std::vector<Face> &faces = mesh.faces;
182                 faces.reserve(numVerts);
183 
184                 // number of indices
185                 for (unsigned int i = 0; i < numVerts; ++i) {
186                     faces.push_back(Face(stream.GetI2()));
187                     if (faces.back().indices.empty())
188                         throw DeadlyImportError("Quick3D: Found face with zero indices");
189                 }
190 
191                 // indices
192                 for (unsigned int i = 0; i < numVerts; ++i) {
193                     Face &vec = faces[i];
194                     for (unsigned int a = 0; a < (unsigned int)vec.indices.size(); ++a)
195                         vec.indices[a] = stream.GetI4();
196                 }
197 
198                 // material indices
199                 for (unsigned int i = 0; i < numVerts; ++i) {
200                     faces[i].mat = (unsigned int)stream.GetI4();
201                 }
202 
203                 // read all normals
204                 numVerts = (unsigned int)stream.GetI4();
205                 std::vector<aiVector3D> &normals = mesh.normals;
206                 normals.resize(numVerts);
207 
208                 for (unsigned int i = 0; i < numVerts; ++i) {
209                     normals[i].x = stream.GetF4();
210                     normals[i].y = stream.GetF4();
211                     normals[i].z = stream.GetF4();
212                 }
213 
214                 numVerts = (unsigned int)stream.GetI4();
215                 if (numTextures && numVerts) {
216                     // read all texture coordinates
217                     std::vector<aiVector3D> &uv = mesh.uv;
218                     uv.resize(numVerts);
219 
220                     for (unsigned int i = 0; i < numVerts; ++i) {
221                         uv[i].x = stream.GetF4();
222                         uv[i].y = stream.GetF4();
223                     }
224 
225                     // UV indices
226                     for (unsigned int i = 0; i < (unsigned int)faces.size(); ++i) {
227                         Face &vec = faces[i];
228                         for (unsigned int a = 0; a < (unsigned int)vec.indices.size(); ++a) {
229                             vec.uvindices[a] = stream.GetI4();
230                             if (!i && !a)
231                                 mesh.prevUVIdx = vec.uvindices[a];
232                             else if (vec.uvindices[a] != mesh.prevUVIdx)
233                                 mesh.prevUVIdx = UINT_MAX;
234                         }
235                     }
236                 }
237 
238                 // we don't need the rest, but we need to get to the next chunk
239                 stream.IncPtr(36);
240                 if (minor > '0' && major == '3')
241                     stream.IncPtr(mesh.faces.size());
242             }
243             // stream.IncPtr(4); // unknown value here
244         } break;
245 
246             // materials chunk
247         case 'c':
248 
249             for (unsigned int i = 0; i < numMats; ++i) {
250                 materials.push_back(Material());
251                 Material &mat = materials.back();
252 
253                 // read the material name
254                 c = stream.GetI1();
255                 while (c) {
256                     mat.name.data[mat.name.length++] = c;
257                     c = stream.GetI1();
258                 }
259 
260                 // add the terminal character
261                 mat.name.data[mat.name.length] = '\0';
262 
263                 // read the ambient color
264                 mat.ambient.r = stream.GetF4();
265                 mat.ambient.g = stream.GetF4();
266                 mat.ambient.b = stream.GetF4();
267 
268                 // read the diffuse color
269                 mat.diffuse.r = stream.GetF4();
270                 mat.diffuse.g = stream.GetF4();
271                 mat.diffuse.b = stream.GetF4();
272 
273                 // read the ambient color
274                 mat.specular.r = stream.GetF4();
275                 mat.specular.g = stream.GetF4();
276                 mat.specular.b = stream.GetF4();
277 
278                 // read the transparency
279                 mat.transparency = stream.GetF4();
280 
281                 // unknown value here
282                 // stream.IncPtr(4);
283                 // FIX: it could be the texture index ...
284                 mat.texIdx = (unsigned int)stream.GetI4();
285             }
286 
287             break;
288 
289             // texture chunk
290         case 't':
291 
292             pScene->mNumTextures = numTextures;
293             if (!numTextures) {
294                 break;
295             }
296             pScene->mTextures = new aiTexture *[pScene->mNumTextures];
297             // to make sure we won't crash if we leave through an exception
298             ::memset(pScene->mTextures, 0, sizeof(void *) * pScene->mNumTextures);
299             for (unsigned int i = 0; i < pScene->mNumTextures; ++i) {
300                 aiTexture *tex = pScene->mTextures[i] = new aiTexture;
301 
302                 // skip the texture name
303                 while (stream.GetI1())
304                     ;
305 
306                 // read texture width and height
307                 tex->mWidth = (unsigned int)stream.GetI4();
308                 tex->mHeight = (unsigned int)stream.GetI4();
309 
310                 if (!tex->mWidth || !tex->mHeight) {
311                     throw DeadlyImportError("Quick3D: Invalid texture. Width or height is zero");
312                 }
313 
314                 unsigned int mul = tex->mWidth * tex->mHeight;
315                 aiTexel *begin = tex->pcData = new aiTexel[mul];
316                 aiTexel *const end = &begin[mul - 1] + 1;
317 
318                 for (; begin != end; ++begin) {
319                     begin->r = stream.GetI1();
320                     begin->g = stream.GetI1();
321                     begin->b = stream.GetI1();
322                     begin->a = 0xff;
323                 }
324             }
325 
326             break;
327 
328             // scene chunk
329         case 's': {
330             // skip position and rotation
331             stream.IncPtr(12);
332 
333             for (unsigned int i = 0; i < 4; ++i)
334                 for (unsigned int a = 0; a < 4; ++a)
335                     pScene->mRootNode->mTransformation[i][a] = stream.GetF4();
336 
337             stream.IncPtr(16);
338 
339             // now setup a single camera
340             pScene->mNumCameras = 1;
341             pScene->mCameras = new aiCamera *[1];
342             aiCamera *cam = pScene->mCameras[0] = new aiCamera();
343             cam->mPosition.x = stream.GetF4();
344             cam->mPosition.y = stream.GetF4();
345             cam->mPosition.z = stream.GetF4();
346             cam->mName.Set("Q3DCamera");
347 
348             // skip eye rotation for the moment
349             stream.IncPtr(12);
350 
351             // read the default material color
352             fgColor.r = stream.GetF4();
353             fgColor.g = stream.GetF4();
354             fgColor.b = stream.GetF4();
355 
356             // skip some unimportant properties
357             stream.IncPtr(29);
358 
359             // setup a single point light with no attenuation
360             pScene->mNumLights = 1;
361             pScene->mLights = new aiLight *[1];
362             aiLight *light = pScene->mLights[0] = new aiLight();
363             light->mName.Set("Q3DLight");
364             light->mType = aiLightSource_POINT;
365 
366             light->mAttenuationConstant = 1;
367             light->mAttenuationLinear = 0;
368             light->mAttenuationQuadratic = 0;
369 
370             light->mColorDiffuse.r = stream.GetF4();
371             light->mColorDiffuse.g = stream.GetF4();
372             light->mColorDiffuse.b = stream.GetF4();
373 
374             light->mColorSpecular = light->mColorDiffuse;
375 
376             // We don't need the rest, but we need to know where this chunk ends.
377             unsigned int temp = (unsigned int)(stream.GetI4() * stream.GetI4());
378 
379             // skip the background file name
380             while (stream.GetI1())
381                 ;
382 
383             // skip background texture data + the remaining fields
384             stream.IncPtr(temp * 3 + 20); // 4 bytes of unknown data here
385 
386             // TODO
387             goto outer;
388         } break;
389 
390         default:
391             throw DeadlyImportError("Quick3D: Unknown chunk");
392             break;
393         };
394     }
395 outer:
396 
397     // If we have no mesh loaded - break here
398     if (meshes.empty())
399         throw DeadlyImportError("Quick3D: No meshes loaded");
400 
401     // If we have no materials loaded - generate a default mat
402     if (materials.empty()) {
403         ASSIMP_LOG_INFO("Quick3D: No material found, generating one");
404         materials.push_back(Material());
405         materials.back().diffuse = fgColor;
406     }
407 
408     // find out which materials we'll need
409     typedef std::pair<unsigned int, unsigned int> FaceIdx;
410     typedef std::vector<FaceIdx> FaceIdxArray;
411     FaceIdxArray *fidx = new FaceIdxArray[materials.size()];
412 
413     unsigned int p = 0;
414     for (std::vector<Mesh>::iterator it = meshes.begin(), end = meshes.end();
415             it != end; ++it, ++p) {
416         unsigned int q = 0;
417         for (std::vector<Face>::iterator fit = (*it).faces.begin(), fend = (*it).faces.end();
418                 fit != fend; ++fit, ++q) {
419             if ((*fit).mat >= materials.size()) {
420                 ASSIMP_LOG_WARN("Quick3D: Material index overflow");
421                 (*fit).mat = 0;
422             }
423             if (fidx[(*fit).mat].empty()) ++pScene->mNumMeshes;
424             fidx[(*fit).mat].push_back(FaceIdx(p, q));
425         }
426     }
427     pScene->mNumMaterials = pScene->mNumMeshes;
428     pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
429     pScene->mMeshes = new aiMesh *[pScene->mNumMaterials];
430 
431     for (unsigned int i = 0, real = 0; i < (unsigned int)materials.size(); ++i) {
432         if (fidx[i].empty()) continue;
433 
434         // Allocate a mesh and a material
435         aiMesh *mesh = pScene->mMeshes[real] = new aiMesh();
436         aiMaterial *mat = new aiMaterial();
437         pScene->mMaterials[real] = mat;
438 
439         mesh->mMaterialIndex = real;
440 
441         // Build the output material
442         Material &srcMat = materials[i];
443         mat->AddProperty(&srcMat.diffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
444         mat->AddProperty(&srcMat.specular, 1, AI_MATKEY_COLOR_SPECULAR);
445         mat->AddProperty(&srcMat.ambient, 1, AI_MATKEY_COLOR_AMBIENT);
446 
447         // NOTE: Ignore transparency for the moment - it seems
448         // unclear how to interpret the data
449 #if 0
450         if (!(minor > '0' && major == '3'))
451             srcMat.transparency = 1.0f - srcMat.transparency;
452         mat->AddProperty(&srcMat.transparency, 1, AI_MATKEY_OPACITY);
453 #endif
454 
455         // add shininess - Quick3D seems to use it ins its viewer
456         srcMat.transparency = 16.f;
457         mat->AddProperty(&srcMat.transparency, 1, AI_MATKEY_SHININESS);
458 
459         int m = (int)aiShadingMode_Phong;
460         mat->AddProperty(&m, 1, AI_MATKEY_SHADING_MODEL);
461 
462         if (srcMat.name.length)
463             mat->AddProperty(&srcMat.name, AI_MATKEY_NAME);
464 
465         // Add a texture
466         if (srcMat.texIdx < pScene->mNumTextures || real < pScene->mNumTextures) {
467             srcMat.name.data[0] = '*';
468             srcMat.name.length = ASSIMP_itoa10(&srcMat.name.data[1], 1000,
469                     (srcMat.texIdx < pScene->mNumTextures ? srcMat.texIdx : real));
470             mat->AddProperty(&srcMat.name, AI_MATKEY_TEXTURE_DIFFUSE(0));
471         }
472 
473         mesh->mNumFaces = (unsigned int)fidx[i].size();
474         aiFace *faces = mesh->mFaces = new aiFace[mesh->mNumFaces];
475 
476         // Now build the output mesh. First find out how many
477         // vertices we'll need
478         for (FaceIdxArray::const_iterator it = fidx[i].begin(), end = fidx[i].end();
479                 it != end; ++it) {
480             mesh->mNumVertices += (unsigned int)meshes[(*it).first].faces[(*it).second].indices.size();
481         }
482 
483         aiVector3D *verts = mesh->mVertices = new aiVector3D[mesh->mNumVertices];
484         aiVector3D *norms = mesh->mNormals = new aiVector3D[mesh->mNumVertices];
485         aiVector3D *uv = nullptr;
486         if (real < pScene->mNumTextures) {
487             uv = mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
488             mesh->mNumUVComponents[0] = 2;
489         }
490 
491         // Build the final array
492         unsigned int cnt = 0;
493         for (FaceIdxArray::const_iterator it = fidx[i].begin(), end = fidx[i].end();
494                 it != end; ++it, ++faces) {
495             Mesh &curMesh = meshes[(*it).first];
496             Face &face = curMesh.faces[(*it).second];
497             faces->mNumIndices = (unsigned int)face.indices.size();
498             faces->mIndices = new unsigned int[faces->mNumIndices];
499 
500             aiVector3D faceNormal;
501             bool fnOK = false;
502 
503             for (unsigned int n = 0; n < faces->mNumIndices; ++n, ++cnt, ++norms, ++verts) {
504                 if (face.indices[n] >= curMesh.verts.size()) {
505                     ASSIMP_LOG_WARN("Quick3D: Vertex index overflow");
506                     face.indices[n] = 0;
507                 }
508 
509                 // copy vertices
510                 *verts = curMesh.verts[face.indices[n]];
511 
512                 if (face.indices[n] >= curMesh.normals.size() && faces->mNumIndices >= 3) {
513                     // we have no normal here - assign the face normal
514                     if (!fnOK) {
515                         const aiVector3D &pV1 = curMesh.verts[face.indices[0]];
516                         const aiVector3D &pV2 = curMesh.verts[face.indices[1]];
517                         const aiVector3D &pV3 = curMesh.verts[face.indices.size() - 1];
518                         faceNormal = (pV2 - pV1) ^ (pV3 - pV1).Normalize();
519                         fnOK = true;
520                     }
521                     *norms = faceNormal;
522                 } else {
523                     *norms = curMesh.normals[face.indices[n]];
524                 }
525 
526                 // copy texture coordinates
527                 if (uv && curMesh.uv.size()) {
528                     if (curMesh.prevUVIdx != 0xffffffff && curMesh.uv.size() >= curMesh.verts.size()) // workaround
529                     {
530                         *uv = curMesh.uv[face.indices[n]];
531                     } else {
532                         if (face.uvindices[n] >= curMesh.uv.size()) {
533                             ASSIMP_LOG_WARN("Quick3D: Texture coordinate index overflow");
534                             face.uvindices[n] = 0;
535                         }
536                         *uv = curMesh.uv[face.uvindices[n]];
537                     }
538                     uv->y = 1.f - uv->y;
539                     ++uv;
540                 }
541 
542                 // setup the new vertex index
543                 faces->mIndices[n] = cnt;
544             }
545         }
546         ++real;
547     }
548 
549     // Delete our nice helper array
550     delete[] fidx;
551 
552     // Now we need to attach the meshes to the root node of the scene
553     pScene->mRootNode->mNumMeshes = pScene->mNumMeshes;
554     pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes];
555     for (unsigned int i = 0; i < pScene->mNumMeshes; ++i)
556         pScene->mRootNode->mMeshes[i] = i;
557 
558     /*pScene->mRootNode->mTransformation *= aiMatrix4x4(
559         1.f, 0.f, 0.f, 0.f,
560         0.f, -1.f,0.f, 0.f,
561         0.f, 0.f, 1.f, 0.f,
562         0.f, 0.f, 0.f, 1.f);*/
563 
564     // Add cameras and light sources to the scene root node
565     pScene->mRootNode->mNumChildren = pScene->mNumLights + pScene->mNumCameras;
566     if (pScene->mRootNode->mNumChildren) {
567         pScene->mRootNode->mChildren = new aiNode *[pScene->mRootNode->mNumChildren];
568 
569         // the light source
570         aiNode *nd = pScene->mRootNode->mChildren[0] = new aiNode();
571         nd->mParent = pScene->mRootNode;
572         nd->mName.Set("Q3DLight");
573         nd->mTransformation = pScene->mRootNode->mTransformation;
574         nd->mTransformation.Inverse();
575 
576         // camera
577         nd = pScene->mRootNode->mChildren[1] = new aiNode();
578         nd->mParent = pScene->mRootNode;
579         nd->mName.Set("Q3DCamera");
580         nd->mTransformation = pScene->mRootNode->mChildren[0]->mTransformation;
581     }
582 }
583 
584 #endif // !! ASSIMP_BUILD_NO_Q3D_IMPORTER
585