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 AMFImporter_Postprocess.cpp
43 /// \brief Convert built scenegraph and objects to Assimp scenegraph.
44 /// \date 2016
45 /// \author smal.root@gmail.com
46
47 #ifndef ASSIMP_BUILD_NO_AMF_IMPORTER
48
49 #include "AMFImporter.hpp"
50
51 #include <assimp/SceneCombiner.h>
52 #include <assimp/StandardShapes.h>
53 #include <assimp/StringUtils.h>
54
55 #include <iterator>
56
57 namespace Assimp {
58
GetColor(const float,const float,const float) const59 aiColor4D AMFImporter::SPP_Material::GetColor(const float /*pX*/, const float /*pY*/, const float /*pZ*/) const {
60 aiColor4D tcol;
61
62 // Check if stored data are supported.
63 if (!Composition.empty()) {
64 throw DeadlyImportError("IME. GetColor for composition");
65 }
66
67 if (Color->Composed) {
68 throw DeadlyImportError("IME. GetColor, composed color");
69 }
70
71 tcol = Color->Color;
72
73 // Check if default color must be used
74 if ((tcol.r == 0) && (tcol.g == 0) && (tcol.b == 0) && (tcol.a == 0)) {
75 tcol.r = 0.5f;
76 tcol.g = 0.5f;
77 tcol.b = 0.5f;
78 tcol.a = 1;
79 }
80
81 return tcol;
82 }
83
PostprocessHelper_CreateMeshDataArray(const AMFMesh & nodeElement,std::vector<aiVector3D> & vertexCoordinateArray,std::vector<AMFColor * > & pVertexColorArray) const84 void AMFImporter::PostprocessHelper_CreateMeshDataArray(const AMFMesh &nodeElement, std::vector<aiVector3D> &vertexCoordinateArray,
85 std::vector<AMFColor *> &pVertexColorArray) const {
86 AMFVertices *vn = nullptr;
87 size_t col_idx;
88
89 // All data stored in "vertices", search for it.
90 for (AMFNodeElementBase *ne_child : nodeElement.Child) {
91 if (ne_child->Type == AMFNodeElementBase::ENET_Vertices) {
92 vn = (AMFVertices*)ne_child;
93 }
94 }
95
96 // If "vertices" not found then no work for us.
97 if (vn == nullptr) {
98 return;
99 }
100
101 // all coordinates stored as child and we need to reserve space for future push_back's.
102 vertexCoordinateArray.reserve(vn->Child.size());
103
104 // colors count equal vertices count.
105 pVertexColorArray.resize(vn->Child.size());
106 col_idx = 0;
107
108 // Inside vertices collect all data and place to arrays
109 for (AMFNodeElementBase *vn_child : vn->Child) {
110 // vertices, colors
111 if (vn_child->Type == AMFNodeElementBase::ENET_Vertex) {
112 // by default clear color for current vertex
113 pVertexColorArray[col_idx] = nullptr;
114
115 for (AMFNodeElementBase *vtx : vn_child->Child) {
116 if (vtx->Type == AMFNodeElementBase::ENET_Coordinates) {
117 vertexCoordinateArray.push_back(((AMFCoordinates *)vtx)->Coordinate);
118 continue;
119 }
120
121 if (vtx->Type == AMFNodeElementBase::ENET_Color) {
122 pVertexColorArray[col_idx] = (AMFColor *)vtx;
123 continue;
124 }
125 }
126
127 ++col_idx;
128 }
129 }
130 }
131
PostprocessHelper_GetTextureID_Or_Create(const std::string & r,const std::string & g,const std::string & b,const std::string & a)132 size_t AMFImporter::PostprocessHelper_GetTextureID_Or_Create(const std::string &r, const std::string &g, const std::string &b, const std::string &a) {
133 if (r.empty() && g.empty() && b.empty() && a.empty()) {
134 throw DeadlyImportError("PostprocessHelper_GetTextureID_Or_Create. At least one texture ID must be defined.");
135 }
136
137 std::string TextureConverted_ID = r + "_" + g + "_" + b + "_" + a;
138 size_t TextureConverted_Index = 0;
139 for (const SPP_Texture &tex_convd : mTexture_Converted) {
140 if (tex_convd.ID == TextureConverted_ID) {
141 return TextureConverted_Index;
142 } else {
143 ++TextureConverted_Index;
144 }
145 }
146
147 // Converted texture not found, create it.
148 AMFTexture *src_texture[4] {
149 nullptr
150 };
151 std::vector<AMFTexture *> src_texture_4check;
152 SPP_Texture converted_texture;
153
154 { // find all specified source textures
155 AMFNodeElementBase *t_tex = nullptr;
156
157 // R
158 if (!r.empty()) {
159 if (!Find_NodeElement(r, AMFNodeElementBase::EType::ENET_Texture, &t_tex)) {
160 Throw_ID_NotFound(r);
161 }
162
163 src_texture[0] = (AMFTexture *)t_tex;
164 src_texture_4check.push_back((AMFTexture *)t_tex);
165 } else {
166 src_texture[0] = nullptr;
167 }
168
169 // G
170 if (!g.empty()) {
171 if (!Find_NodeElement(g, AMFNodeElementBase::ENET_Texture, &t_tex)) {
172 Throw_ID_NotFound(g);
173 }
174
175 src_texture[1] = (AMFTexture *)t_tex;
176 src_texture_4check.push_back((AMFTexture *)t_tex);
177 } else {
178 src_texture[1] = nullptr;
179 }
180
181 // B
182 if (!b.empty()) {
183 if (!Find_NodeElement(b, AMFNodeElementBase::ENET_Texture, &t_tex)) {
184 Throw_ID_NotFound(b);
185 }
186
187 src_texture[2] = (AMFTexture *)t_tex;
188 src_texture_4check.push_back((AMFTexture *)t_tex);
189 } else {
190 src_texture[2] = nullptr;
191 }
192
193 // A
194 if (!a.empty()) {
195 if (!Find_NodeElement(a, AMFNodeElementBase::ENET_Texture, &t_tex)) {
196 Throw_ID_NotFound(a);
197 }
198
199 src_texture[3] = (AMFTexture *)t_tex;
200 src_texture_4check.push_back((AMFTexture *)t_tex);
201 } else {
202 src_texture[3] = nullptr;
203 }
204 } // END: find all specified source textures
205
206 // check that all textures has same size
207 if (src_texture_4check.size() > 1) {
208 for (size_t i = 0, i_e = (src_texture_4check.size() - 1); i < i_e; i++) {
209 if ((src_texture_4check[i]->Width != src_texture_4check[i + 1]->Width) || (src_texture_4check[i]->Height != src_texture_4check[i + 1]->Height) ||
210 (src_texture_4check[i]->Depth != src_texture_4check[i + 1]->Depth)) {
211 throw DeadlyImportError("PostprocessHelper_GetTextureID_Or_Create. Source texture must has the same size.");
212 }
213 }
214 } // if(src_texture_4check.size() > 1)
215
216 // set texture attributes
217 converted_texture.Width = src_texture_4check[0]->Width;
218 converted_texture.Height = src_texture_4check[0]->Height;
219 converted_texture.Depth = src_texture_4check[0]->Depth;
220 // if one of source texture is tiled then converted texture is tiled too.
221 converted_texture.Tiled = false;
222 for (uint8_t i = 0; i < src_texture_4check.size(); ++i) {
223 converted_texture.Tiled |= src_texture_4check[i]->Tiled;
224 }
225
226 // Create format hint.
227 strcpy(converted_texture.FormatHint, "rgba0000"); // copy initial string.
228 if (!r.empty()) converted_texture.FormatHint[4] = '8';
229 if (!g.empty()) converted_texture.FormatHint[5] = '8';
230 if (!b.empty()) converted_texture.FormatHint[6] = '8';
231 if (!a.empty()) converted_texture.FormatHint[7] = '8';
232
233 // Сopy data of textures.
234 size_t tex_size = 0;
235 size_t step = 0;
236 size_t off_g = 0;
237 size_t off_b = 0;
238
239 // Calculate size of the target array and rule how data will be copied.
240 if (!r.empty() && nullptr != src_texture[0]) {
241 tex_size += src_texture[0]->Data.size();
242 step++, off_g++, off_b++;
243 }
244 if (!g.empty() && nullptr != src_texture[1]) {
245 tex_size += src_texture[1]->Data.size();
246 step++, off_b++;
247 }
248 if (!b.empty() && nullptr != src_texture[2]) {
249 tex_size += src_texture[2]->Data.size();
250 step++;
251 }
252 if (!a.empty() && nullptr != src_texture[3]) {
253 tex_size += src_texture[3]->Data.size();
254 step++;
255 }
256
257 // Create target array.
258 converted_texture.Data = new uint8_t[tex_size];
259 // And copy data
260 auto CopyTextureData = [&](const std::string &pID, const size_t pOffset, const size_t pStep, const uint8_t pSrcTexNum) -> void {
261 if (!pID.empty()) {
262 for (size_t idx_target = pOffset, idx_src = 0; idx_target < tex_size; idx_target += pStep, idx_src++) {
263 AMFTexture *tex = src_texture[pSrcTexNum];
264 ai_assert(tex);
265 converted_texture.Data[idx_target] = tex->Data.at(idx_src);
266 }
267 }
268 }; // auto CopyTextureData = [&](const size_t pOffset, const size_t pStep, const uint8_t pSrcTexNum) -> void
269
270 CopyTextureData(r, 0, step, 0);
271 CopyTextureData(g, off_g, step, 1);
272 CopyTextureData(b, off_b, step, 2);
273 CopyTextureData(a, step - 1, step, 3);
274
275 // Store new converted texture ID
276 converted_texture.ID = TextureConverted_ID;
277 // Store new converted texture
278 mTexture_Converted.push_back(converted_texture);
279
280 return TextureConverted_Index;
281 }
282
PostprocessHelper_SplitFacesByTextureID(std::list<SComplexFace> & pInputList,std::list<std::list<SComplexFace>> & pOutputList_Separated)283 void AMFImporter::PostprocessHelper_SplitFacesByTextureID(std::list<SComplexFace> &pInputList, std::list<std::list<SComplexFace>> &pOutputList_Separated) {
284 auto texmap_is_equal = [](const AMFTexMap *pTexMap1, const AMFTexMap *pTexMap2) -> bool {
285 if ((pTexMap1 == nullptr) && (pTexMap2 == nullptr)) return true;
286 if (pTexMap1 == nullptr) return false;
287 if (pTexMap2 == nullptr) return false;
288
289 if (pTexMap1->TextureID_R != pTexMap2->TextureID_R) return false;
290 if (pTexMap1->TextureID_G != pTexMap2->TextureID_G) return false;
291 if (pTexMap1->TextureID_B != pTexMap2->TextureID_B) return false;
292 if (pTexMap1->TextureID_A != pTexMap2->TextureID_A) return false;
293
294 return true;
295 };
296
297 pOutputList_Separated.clear();
298 if (pInputList.empty()) return;
299
300 do {
301 SComplexFace face_start = pInputList.front();
302 std::list<SComplexFace> face_list_cur;
303
304 for (std::list<SComplexFace>::iterator it = pInputList.begin(), it_end = pInputList.end(); it != it_end;) {
305 if (texmap_is_equal(face_start.TexMap, it->TexMap)) {
306 auto it_old = it;
307
308 ++it;
309 face_list_cur.push_back(*it_old);
310 pInputList.erase(it_old);
311 } else {
312 ++it;
313 }
314 }
315
316 if (!face_list_cur.empty()) pOutputList_Separated.push_back(face_list_cur);
317
318 } while (!pInputList.empty());
319 }
320
Postprocess_AddMetadata(const AMFMetaDataArray & metadataList,aiNode & sceneNode) const321 void AMFImporter::Postprocess_AddMetadata(const AMFMetaDataArray &metadataList, aiNode &sceneNode) const {
322 if (metadataList.empty()) {
323 return;
324 }
325
326 if (sceneNode.mMetaData != nullptr) {
327 throw DeadlyImportError("Postprocess. MetaData member in node are not nullptr. Something went wrong.");
328 }
329
330 // copy collected metadata to output node.
331 sceneNode.mMetaData = aiMetadata::Alloc(static_cast<unsigned int>(metadataList.size()));
332 size_t meta_idx(0);
333
334 for (const AMFMetadata *metadata : metadataList) {
335 sceneNode.mMetaData->Set(static_cast<unsigned int>(meta_idx++), metadata->Type, aiString(metadata->Value));
336 }
337 }
338
Postprocess_BuildNodeAndObject(const AMFObject & pNodeElement,MeshArray & meshList,aiNode ** pSceneNode)339 void AMFImporter::Postprocess_BuildNodeAndObject(const AMFObject &pNodeElement, MeshArray &meshList, aiNode **pSceneNode) {
340 AMFColor *object_color = nullptr;
341
342 // create new aiNode and set name as <object> has.
343 *pSceneNode = new aiNode;
344 (*pSceneNode)->mName = pNodeElement.ID;
345 // read mesh and color
346 for (const AMFNodeElementBase *ne_child : pNodeElement.Child) {
347 std::vector<aiVector3D> vertex_arr;
348 std::vector<AMFColor *> color_arr;
349
350 // color for object
351 if (ne_child->Type == AMFNodeElementBase::ENET_Color) {
352 object_color = (AMFColor *) ne_child;
353 }
354
355 if (ne_child->Type == AMFNodeElementBase::ENET_Mesh) {
356 // Create arrays from children of mesh: vertices.
357 PostprocessHelper_CreateMeshDataArray(*((AMFMesh *)ne_child), vertex_arr, color_arr);
358 // Use this arrays as a source when creating every aiMesh
359 Postprocess_BuildMeshSet(*((AMFMesh *)ne_child), vertex_arr, color_arr, object_color, meshList, **pSceneNode);
360 }
361 } // for(const CAMFImporter_NodeElement* ne_child: pNodeElement)
362 }
363
Postprocess_BuildMeshSet(const AMFMesh & pNodeElement,const std::vector<aiVector3D> & pVertexCoordinateArray,const std::vector<AMFColor * > & pVertexColorArray,const AMFColor * pObjectColor,MeshArray & pMeshList,aiNode & pSceneNode)364 void AMFImporter::Postprocess_BuildMeshSet(const AMFMesh &pNodeElement, const std::vector<aiVector3D> &pVertexCoordinateArray,
365 const std::vector<AMFColor *> &pVertexColorArray, const AMFColor *pObjectColor, MeshArray &pMeshList, aiNode &pSceneNode) {
366 std::list<unsigned int> mesh_idx;
367
368 // all data stored in "volume", search for it.
369 for (const AMFNodeElementBase *ne_child : pNodeElement.Child) {
370 const AMFColor *ne_volume_color = nullptr;
371 const SPP_Material *cur_mat = nullptr;
372
373 if (ne_child->Type == AMFNodeElementBase::ENET_Volume) {
374 /******************* Get faces *******************/
375 const AMFVolume *ne_volume = reinterpret_cast<const AMFVolume *>(ne_child);
376
377 std::list<SComplexFace> complex_faces_list; // List of the faces of the volume.
378 std::list<std::list<SComplexFace>> complex_faces_toplist; // List of the face list for every mesh.
379
380 // check if volume use material
381 if (!ne_volume->MaterialID.empty()) {
382 if (!Find_ConvertedMaterial(ne_volume->MaterialID, &cur_mat)) {
383 Throw_ID_NotFound(ne_volume->MaterialID);
384 }
385 }
386
387 // inside "volume" collect all data and place to arrays or create new objects
388 for (const AMFNodeElementBase *ne_volume_child : ne_volume->Child) {
389 // color for volume
390 if (ne_volume_child->Type == AMFNodeElementBase::ENET_Color) {
391 ne_volume_color = reinterpret_cast<const AMFColor *>(ne_volume_child);
392 } else if (ne_volume_child->Type == AMFNodeElementBase::ENET_Triangle) // triangles, triangles colors
393 {
394 const AMFTriangle &tri_al = *reinterpret_cast<const AMFTriangle *>(ne_volume_child);
395
396 SComplexFace complex_face;
397
398 // initialize pointers
399 complex_face.Color = nullptr;
400 complex_face.TexMap = nullptr;
401 // get data from triangle children: color, texture coordinates.
402 if (tri_al.Child.size()) {
403 for (const AMFNodeElementBase *ne_triangle_child : tri_al.Child) {
404 if (ne_triangle_child->Type == AMFNodeElementBase::ENET_Color)
405 complex_face.Color = reinterpret_cast<const AMFColor *>(ne_triangle_child);
406 else if (ne_triangle_child->Type == AMFNodeElementBase::ENET_TexMap)
407 complex_face.TexMap = reinterpret_cast<const AMFTexMap *>(ne_triangle_child);
408 }
409 } // if(tri_al.Child.size())
410
411 // create new face and store it.
412 complex_face.Face.mNumIndices = 3;
413 complex_face.Face.mIndices = new unsigned int[3];
414 complex_face.Face.mIndices[0] = static_cast<unsigned int>(tri_al.V[0]);
415 complex_face.Face.mIndices[1] = static_cast<unsigned int>(tri_al.V[1]);
416 complex_face.Face.mIndices[2] = static_cast<unsigned int>(tri_al.V[2]);
417 complex_faces_list.push_back(complex_face);
418 }
419 } // for(const CAMFImporter_NodeElement* ne_volume_child: ne_volume->Child)
420
421 /**** Split faces list: one list per mesh ****/
422 PostprocessHelper_SplitFacesByTextureID(complex_faces_list, complex_faces_toplist);
423
424 /***** Create mesh for every faces list ******/
425 for (std::list<SComplexFace> &face_list_cur : complex_faces_toplist) {
426 auto VertexIndex_GetMinimal = [](const std::list<SComplexFace> &pFaceList, const size_t *pBiggerThan) -> size_t {
427 size_t rv = 0;
428
429 if (pBiggerThan != nullptr) {
430 bool found = false;
431 const size_t biggerThan = *pBiggerThan;
432 for (const SComplexFace &face : pFaceList) {
433 for (size_t idx_vert = 0; idx_vert < face.Face.mNumIndices; idx_vert++) {
434 if (face.Face.mIndices[idx_vert] > biggerThan) {
435 rv = face.Face.mIndices[idx_vert];
436 found = true;
437 break;
438 }
439 }
440
441 if (found) {
442 break;
443 }
444 }
445
446 if (!found) {
447 return *pBiggerThan;
448 }
449 } else {
450 rv = pFaceList.front().Face.mIndices[0];
451 } // if(pBiggerThan != nullptr) else
452
453 for (const SComplexFace &face : pFaceList) {
454 for (size_t vi = 0; vi < face.Face.mNumIndices; vi++) {
455 if (face.Face.mIndices[vi] < rv) {
456 if (pBiggerThan != nullptr) {
457 if (face.Face.mIndices[vi] > *pBiggerThan) rv = face.Face.mIndices[vi];
458 } else {
459 rv = face.Face.mIndices[vi];
460 }
461 }
462 }
463 } // for(const SComplexFace& face: pFaceList)
464
465 return rv;
466 }; // auto VertexIndex_GetMinimal = [](const std::list<SComplexFace>& pFaceList, const size_t* pBiggerThan) -> size_t
467
468 auto VertexIndex_Replace = [](std::list<SComplexFace> &pFaceList, const size_t pIdx_From, const size_t pIdx_To) -> void {
469 for (const SComplexFace &face : pFaceList) {
470 for (size_t vi = 0; vi < face.Face.mNumIndices; vi++) {
471 if (face.Face.mIndices[vi] == pIdx_From) face.Face.mIndices[vi] = static_cast<unsigned int>(pIdx_To);
472 }
473 }
474 }; // auto VertexIndex_Replace = [](std::list<SComplexFace>& pFaceList, const size_t pIdx_From, const size_t pIdx_To) -> void
475
476 auto Vertex_CalculateColor = [&](const size_t pIdx) -> aiColor4D {
477 // Color priorities(In descending order):
478 // 1. triangle color;
479 // 2. vertex color;
480 // 3. volume color;
481 // 4. object color;
482 // 5. material;
483 // 6. default - invisible coat.
484 //
485 // Fill vertices colors in color priority list above that's points from 1 to 6.
486 if ((pIdx < pVertexColorArray.size()) && (pVertexColorArray[pIdx] != nullptr)) // check for vertex color
487 {
488 if (pVertexColorArray[pIdx]->Composed)
489 throw DeadlyImportError("IME: vertex color composed");
490 else
491 return pVertexColorArray[pIdx]->Color;
492 } else if (ne_volume_color != nullptr) // check for volume color
493 {
494 if (ne_volume_color->Composed)
495 throw DeadlyImportError("IME: volume color composed");
496 else
497 return ne_volume_color->Color;
498 } else if (pObjectColor != nullptr) // check for object color
499 {
500 if (pObjectColor->Composed)
501 throw DeadlyImportError("IME: object color composed");
502 else
503 return pObjectColor->Color;
504 } else if (cur_mat != nullptr) // check for material
505 {
506 return cur_mat->GetColor(pVertexCoordinateArray.at(pIdx).x, pVertexCoordinateArray.at(pIdx).y, pVertexCoordinateArray.at(pIdx).z);
507 } else // set default color.
508 {
509 return { 0, 0, 0, 0 };
510 } // if((vi < pVertexColorArray.size()) && (pVertexColorArray[vi] != nullptr)) else
511 }; // auto Vertex_CalculateColor = [&](const size_t pIdx) -> aiColor4D
512
513 aiMesh *tmesh = new aiMesh;
514
515 tmesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; // Only triangles is supported by AMF.
516 //
517 // set geometry and colors (vertices)
518 //
519 // copy faces/triangles
520 tmesh->mNumFaces = static_cast<unsigned int>(face_list_cur.size());
521 tmesh->mFaces = new aiFace[tmesh->mNumFaces];
522
523 // Create vertices list and optimize indices. Optimization mean following.In AMF all volumes use one big list of vertices. And one volume
524 // can use only part of vertices list, for example: vertices list contain few thousands of vertices and volume use vertices 1, 3, 10.
525 // Do you need all this thousands of garbage? Of course no. So, optimization step transform sparse indices set to continuous.
526 size_t VertexCount_Max = tmesh->mNumFaces * 3; // 3 - triangles.
527 std::vector<aiVector3D> vert_arr, texcoord_arr;
528 std::vector<aiColor4D> col_arr;
529
530 vert_arr.reserve(VertexCount_Max * 2); // "* 2" - see below TODO.
531 col_arr.reserve(VertexCount_Max * 2);
532
533 { // fill arrays
534 size_t vert_idx_from, vert_idx_to;
535
536 // first iteration.
537 vert_idx_to = 0;
538 vert_idx_from = VertexIndex_GetMinimal(face_list_cur, nullptr);
539 vert_arr.push_back(pVertexCoordinateArray.at(vert_idx_from));
540 col_arr.push_back(Vertex_CalculateColor(vert_idx_from));
541 if (vert_idx_from != vert_idx_to) VertexIndex_Replace(face_list_cur, vert_idx_from, vert_idx_to);
542
543 // rest iterations
544 do {
545 vert_idx_from = VertexIndex_GetMinimal(face_list_cur, &vert_idx_to);
546 if (vert_idx_from == vert_idx_to) break; // all indices are transferred,
547
548 vert_arr.push_back(pVertexCoordinateArray.at(vert_idx_from));
549 col_arr.push_back(Vertex_CalculateColor(vert_idx_from));
550 vert_idx_to++;
551 if (vert_idx_from != vert_idx_to) VertexIndex_Replace(face_list_cur, vert_idx_from, vert_idx_to);
552
553 } while (true);
554 } // fill arrays. END.
555
556 //
557 // check if triangle colors are used and create additional faces if needed.
558 //
559 for (const SComplexFace &face_cur : face_list_cur) {
560 if (face_cur.Color != nullptr) {
561 aiColor4D face_color;
562 size_t vert_idx_new = vert_arr.size();
563
564 if (face_cur.Color->Composed)
565 throw DeadlyImportError("IME: face color composed");
566 else
567 face_color = face_cur.Color->Color;
568
569 for (size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++) {
570 vert_arr.push_back(vert_arr.at(face_cur.Face.mIndices[idx_ind]));
571 col_arr.push_back(face_color);
572 face_cur.Face.mIndices[idx_ind] = static_cast<unsigned int>(vert_idx_new++);
573 }
574 } // if(face_cur.Color != nullptr)
575 } // for(const SComplexFace& face_cur: face_list_cur)
576
577 //
578 // if texture is used then copy texture coordinates too.
579 //
580 if (face_list_cur.front().TexMap != nullptr) {
581 size_t idx_vert_new = vert_arr.size();
582 ///TODO: clean unused vertices. "* 2": in certain cases - mesh full of triangle colors - vert_arr will contain duplicated vertices for
583 /// colored triangles and initial vertices (for colored vertices) which in real became unused. This part need more thinking about
584 /// optimization.
585 bool *idx_vert_used;
586
587 idx_vert_used = new bool[VertexCount_Max * 2];
588 for (size_t i = 0, i_e = VertexCount_Max * 2; i < i_e; i++)
589 idx_vert_used[i] = false;
590
591 // This ID's will be used when set materials ID in scene.
592 tmesh->mMaterialIndex = static_cast<unsigned int>(PostprocessHelper_GetTextureID_Or_Create(face_list_cur.front().TexMap->TextureID_R,
593 face_list_cur.front().TexMap->TextureID_G,
594 face_list_cur.front().TexMap->TextureID_B,
595 face_list_cur.front().TexMap->TextureID_A));
596 texcoord_arr.resize(VertexCount_Max * 2);
597 for (const SComplexFace &face_cur : face_list_cur) {
598 for (size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++) {
599 const size_t idx_vert = face_cur.Face.mIndices[idx_ind];
600
601 if (!idx_vert_used[idx_vert]) {
602 texcoord_arr.at(idx_vert) = face_cur.TexMap->TextureCoordinate[idx_ind];
603 idx_vert_used[idx_vert] = true;
604 } else if (texcoord_arr.at(idx_vert) != face_cur.TexMap->TextureCoordinate[idx_ind]) {
605 // in that case one vertex is shared with many texture coordinates. We need to duplicate vertex with another texture
606 // coordinates.
607 vert_arr.push_back(vert_arr.at(idx_vert));
608 col_arr.push_back(col_arr.at(idx_vert));
609 texcoord_arr.at(idx_vert_new) = face_cur.TexMap->TextureCoordinate[idx_ind];
610 face_cur.Face.mIndices[idx_ind] = static_cast<unsigned int>(idx_vert_new++);
611 }
612 } // for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++)
613 } // for(const SComplexFace& face_cur: face_list_cur)
614
615 delete[] idx_vert_used;
616 // shrink array
617 texcoord_arr.resize(idx_vert_new);
618 } // if(face_list_cur.front().TexMap != nullptr)
619
620 //
621 // copy collected data to mesh
622 //
623 tmesh->mNumVertices = static_cast<unsigned int>(vert_arr.size());
624 tmesh->mVertices = new aiVector3D[tmesh->mNumVertices];
625 tmesh->mColors[0] = new aiColor4D[tmesh->mNumVertices];
626
627 memcpy(tmesh->mVertices, vert_arr.data(), tmesh->mNumVertices * sizeof(aiVector3D));
628 memcpy(tmesh->mColors[0], col_arr.data(), tmesh->mNumVertices * sizeof(aiColor4D));
629 if (texcoord_arr.size() > 0) {
630 tmesh->mTextureCoords[0] = new aiVector3D[tmesh->mNumVertices];
631 memcpy(tmesh->mTextureCoords[0], texcoord_arr.data(), tmesh->mNumVertices * sizeof(aiVector3D));
632 tmesh->mNumUVComponents[0] = 2; // U and V stored in "x", "y" of aiVector3D.
633 }
634
635 size_t idx_face = 0;
636 for (const SComplexFace &face_cur : face_list_cur)
637 tmesh->mFaces[idx_face++] = face_cur.Face;
638
639 // store new aiMesh
640 mesh_idx.push_back(static_cast<unsigned int>(pMeshList.size()));
641 pMeshList.push_back(tmesh);
642 } // for(const std::list<SComplexFace>& face_list_cur: complex_faces_toplist)
643 } // if(ne_child->Type == CAMFImporter_NodeElement::ENET_Volume)
644 } // for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child)
645
646 // if meshes was created then assign new indices with current aiNode
647 if (!mesh_idx.empty()) {
648 std::list<unsigned int>::const_iterator mit = mesh_idx.begin();
649
650 pSceneNode.mNumMeshes = static_cast<unsigned int>(mesh_idx.size());
651 pSceneNode.mMeshes = new unsigned int[pSceneNode.mNumMeshes];
652 for (size_t i = 0; i < pSceneNode.mNumMeshes; i++)
653 pSceneNode.mMeshes[i] = *mit++;
654 } // if(mesh_idx.size() > 0)
655 }
656
Postprocess_BuildMaterial(const AMFMaterial & pMaterial)657 void AMFImporter::Postprocess_BuildMaterial(const AMFMaterial &pMaterial) {
658 SPP_Material new_mat;
659
660 new_mat.ID = pMaterial.ID;
661 for (const AMFNodeElementBase *mat_child : pMaterial.Child) {
662 if (mat_child->Type == AMFNodeElementBase::ENET_Color) {
663 new_mat.Color = (AMFColor*)mat_child;
664 } else if (mat_child->Type == AMFNodeElementBase::ENET_Metadata) {
665 new_mat.Metadata.push_back((AMFMetadata *)mat_child);
666 }
667 } // for(const CAMFImporter_NodeElement* mat_child; pMaterial.Child)
668
669 // place converted material to special list
670 mMaterial_Converted.push_back(new_mat);
671 }
672
Postprocess_BuildConstellation(AMFConstellation & pConstellation,NodeArray & nodeArray) const673 void AMFImporter::Postprocess_BuildConstellation(AMFConstellation &pConstellation, NodeArray &nodeArray) const {
674 aiNode *con_node;
675 std::list<aiNode *> ch_node;
676
677 // We will build next hierarchy:
678 // aiNode as parent (<constellation>) for set of nodes as a children
679 // |- aiNode for transformation (<instance> -> <delta...>, <r...>) - aiNode for pointing to object ("objectid")
680 // ...
681 // \_ aiNode for transformation (<instance> -> <delta...>, <r...>) - aiNode for pointing to object ("objectid")
682 con_node = new aiNode;
683 con_node->mName = pConstellation.ID;
684 // Walk through children and search for instances of another objects, constellations.
685 for (const AMFNodeElementBase *ne : pConstellation.Child) {
686 aiMatrix4x4 tmat;
687 aiNode *t_node;
688 aiNode *found_node;
689
690 if (ne->Type == AMFNodeElementBase::ENET_Metadata) continue;
691 if (ne->Type != AMFNodeElementBase::ENET_Instance) throw DeadlyImportError("Only <instance> nodes can be in <constellation>.");
692
693 // create alias for convenience
694 AMFInstance &als = *((AMFInstance *)ne);
695 // find referenced object
696 if (!Find_ConvertedNode(als.ObjectID, nodeArray, &found_node)) Throw_ID_NotFound(als.ObjectID);
697
698 // create node for applying transformation
699 t_node = new aiNode;
700 t_node->mParent = con_node;
701 // apply transformation
702 aiMatrix4x4::Translation(als.Delta, tmat), t_node->mTransformation *= tmat;
703 aiMatrix4x4::RotationX(als.Rotation.x, tmat), t_node->mTransformation *= tmat;
704 aiMatrix4x4::RotationY(als.Rotation.y, tmat), t_node->mTransformation *= tmat;
705 aiMatrix4x4::RotationZ(als.Rotation.z, tmat), t_node->mTransformation *= tmat;
706 // create array for one child node
707 t_node->mNumChildren = 1;
708 t_node->mChildren = new aiNode *[t_node->mNumChildren];
709 SceneCombiner::Copy(&t_node->mChildren[0], found_node);
710 t_node->mChildren[0]->mParent = t_node;
711 ch_node.push_back(t_node);
712 } // for(const CAMFImporter_NodeElement* ne: pConstellation.Child)
713
714 // copy found aiNode's as children
715 if (ch_node.empty()) throw DeadlyImportError("<constellation> must have at least one <instance>.");
716
717 size_t ch_idx = 0;
718
719 con_node->mNumChildren = static_cast<unsigned int>(ch_node.size());
720 con_node->mChildren = new aiNode *[con_node->mNumChildren];
721 for (aiNode *node : ch_node)
722 con_node->mChildren[ch_idx++] = node;
723
724 // and place "root" of <constellation> node to node list
725 nodeArray.push_back(con_node);
726 }
727
Postprocess_BuildScene(aiScene * pScene)728 void AMFImporter::Postprocess_BuildScene(aiScene *pScene) {
729 NodeArray nodeArray;
730 MeshArray mesh_list;
731 AMFMetaDataArray meta_list;
732
733 //
734 // Because for AMF "material" is just complex colors mixing so aiMaterial will not be used.
735 // For building aiScene we are must to do few steps:
736 // at first creating root node for aiScene.
737 pScene->mRootNode = new aiNode;
738 pScene->mRootNode->mParent = nullptr;
739 pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED;
740 // search for root(<amf>) element
741 AMFNodeElementBase *root_el = nullptr;
742
743 for (AMFNodeElementBase *ne : mNodeElement_List) {
744 if (ne->Type != AMFNodeElementBase::ENET_Root) {
745 continue;
746 }
747
748 root_el = ne;
749 break;
750 } // for(const CAMFImporter_NodeElement* ne: mNodeElement_List)
751
752 // Check if root element are found.
753 if (root_el == nullptr) {
754 throw DeadlyImportError("Root(<amf>) element not found.");
755 }
756
757 // after that walk through children of root and collect data. Five types of nodes can be placed at top level - in <amf>: <object>, <material>, <texture>,
758 // <constellation> and <metadata>. But at first we must read <material> and <texture> because they will be used in <object>. <metadata> can be read
759 // at any moment.
760 //
761 // 1. <material>
762 // 2. <texture> will be converted later when processing triangles list. \sa Postprocess_BuildMeshSet
763 for (const AMFNodeElementBase *root_child : root_el->Child) {
764 if (root_child->Type == AMFNodeElementBase::ENET_Material) {
765 Postprocess_BuildMaterial(*((AMFMaterial *)root_child));
766 }
767 }
768
769 // After "appearance" nodes we must read <object> because it will be used in <constellation> -> <instance>.
770 //
771 // 3. <object>
772 for (const AMFNodeElementBase *root_child : root_el->Child) {
773 if (root_child->Type == AMFNodeElementBase::ENET_Object) {
774 aiNode *tnode = nullptr;
775
776 // for <object> mesh and node must be built: object ID assigned to aiNode name and will be used in future for <instance>
777 Postprocess_BuildNodeAndObject(*((AMFObject *)root_child), mesh_list, &tnode);
778 if (tnode != nullptr) {
779 nodeArray.push_back(tnode);
780 }
781 }
782 } // for(const CAMFImporter_NodeElement* root_child: root_el->Child)
783
784 // And finally read rest of nodes.
785 //
786 for (const AMFNodeElementBase *root_child : root_el->Child) {
787 // 4. <constellation>
788 if (root_child->Type == AMFNodeElementBase::ENET_Constellation) {
789 // <object> and <constellation> at top of self abstraction use aiNode. So we can use only aiNode list for creating new aiNode's.
790 Postprocess_BuildConstellation(*((AMFConstellation *)root_child), nodeArray);
791 }
792
793 // 5, <metadata>
794 if (root_child->Type == AMFNodeElementBase::ENET_Metadata) meta_list.push_back((AMFMetadata *)root_child);
795 } // for(const CAMFImporter_NodeElement* root_child: root_el->Child)
796
797 // at now we can add collected metadata to root node
798 Postprocess_AddMetadata(meta_list, *pScene->mRootNode);
799 //
800 // Check constellation children
801 //
802 // As said in specification:
803 // "When multiple objects and constellations are defined in a single file, only the top level objects and constellations are available for printing."
804 // What that means? For example: if some object is used in constellation then you must show only constellation but not original object.
805 // And at this step we are checking that relations.
806 nl_clean_loop:
807
808 if (nodeArray.size() > 1) {
809 // walk through all nodes
810 for (NodeArray::iterator nl_it = nodeArray.begin(); nl_it != nodeArray.end(); ++nl_it) {
811 // and try to find them in another top nodes.
812 NodeArray::const_iterator next_it = nl_it;
813
814 ++next_it;
815 for (; next_it != nodeArray.end(); ++next_it) {
816 if ((*next_it)->FindNode((*nl_it)->mName) != nullptr) {
817 // if current top node(nl_it) found in another top node then erase it from node_list and restart search loop.
818 nodeArray.erase(nl_it);
819
820 goto nl_clean_loop;
821 }
822 } // for(; next_it != node_list.end(); next_it++)
823 } // for(std::list<aiNode*>::const_iterator nl_it = node_list.begin(); nl_it != node_list.end(); nl_it++)
824 }
825
826 //
827 // move created objects to aiScene
828 //
829 //
830 // Nodes
831 if (!nodeArray.empty()) {
832 NodeArray::const_iterator nl_it = nodeArray.begin();
833
834 pScene->mRootNode->mNumChildren = static_cast<unsigned int>(nodeArray.size());
835 pScene->mRootNode->mChildren = new aiNode *[pScene->mRootNode->mNumChildren];
836 for (size_t i = 0; i < pScene->mRootNode->mNumChildren; i++) {
837 // Objects and constellation that must be showed placed at top of hierarchy in <amf> node. So all aiNode's in node_list must have
838 // mRootNode only as parent.
839 (*nl_it)->mParent = pScene->mRootNode;
840 pScene->mRootNode->mChildren[i] = *nl_it++;
841 }
842 } // if(node_list.size() > 0)
843
844 //
845 // Meshes
846 if (!mesh_list.empty()) {
847 MeshArray::const_iterator ml_it = mesh_list.begin();
848
849 pScene->mNumMeshes = static_cast<unsigned int>(mesh_list.size());
850 pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
851 for (size_t i = 0; i < pScene->mNumMeshes; i++)
852 pScene->mMeshes[i] = *ml_it++;
853 } // if(mesh_list.size() > 0)
854
855 //
856 // Textures
857 pScene->mNumTextures = static_cast<unsigned int>(mTexture_Converted.size());
858 if (pScene->mNumTextures > 0) {
859 size_t idx;
860
861 idx = 0;
862 pScene->mTextures = new aiTexture *[pScene->mNumTextures];
863 for (const SPP_Texture &tex_convd : mTexture_Converted) {
864 pScene->mTextures[idx] = new aiTexture;
865 pScene->mTextures[idx]->mWidth = static_cast<unsigned int>(tex_convd.Width);
866 pScene->mTextures[idx]->mHeight = static_cast<unsigned int>(tex_convd.Height);
867 pScene->mTextures[idx]->pcData = (aiTexel *)tex_convd.Data;
868 // texture format description.
869 strcpy(pScene->mTextures[idx]->achFormatHint, tex_convd.FormatHint);
870 idx++;
871 } // for(const SPP_Texture& tex_convd: mTexture_Converted)
872
873 // Create materials for embedded textures.
874 idx = 0;
875 pScene->mNumMaterials = static_cast<unsigned int>(mTexture_Converted.size());
876 pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
877 for (const SPP_Texture &tex_convd : mTexture_Converted) {
878 const aiString texture_id(AI_EMBEDDED_TEXNAME_PREFIX + ai_to_string(idx));
879 const int mode = aiTextureOp_Multiply;
880 const int repeat = tex_convd.Tiled ? 1 : 0;
881
882 pScene->mMaterials[idx] = new aiMaterial;
883 pScene->mMaterials[idx]->AddProperty(&texture_id, AI_MATKEY_TEXTURE_DIFFUSE(0));
884 pScene->mMaterials[idx]->AddProperty(&mode, 1, AI_MATKEY_TEXOP_DIFFUSE(0));
885 pScene->mMaterials[idx]->AddProperty(&repeat, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0));
886 pScene->mMaterials[idx]->AddProperty(&repeat, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0));
887 idx++;
888 }
889 } // if(pScene->mNumTextures > 0)
890 } // END: after that walk through children of root and collect data
891
892 } // namespace Assimp
893
894 #endif // !ASSIMP_BUILD_NO_AMF_IMPORTER
895