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