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