1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2019, assimp team
7 
8 
9 
10 All rights reserved.
11 
12 Redistribution and use of this software in source and binary forms,
13 with or without modification, are permitted provided that the following
14 conditions are met:
15 
16 * Redistributions of source code must retain the above
17 copyright notice, this list of conditions and the
18 following disclaimer.
19 
20 * Redistributions in binary form must reproduce the above
21 copyright notice, this list of conditions and the
22 following disclaimer in the documentation and/or other
23 materials provided with the distribution.
24 
25 * Neither the name of the assimp team, nor the names of its
26 contributors may be used to endorse or promote products
27 derived from this software without specific prior
28 written permission of the assimp team.
29 
30 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
31 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
32 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
33 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
34 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
37 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
38 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
40 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 ---------------------------------------------------------------------------
42 */
43 
44 /// \file AMFImporter.cpp
45 /// \brief AMF-format files importer for Assimp: main algorithm implementation.
46 /// \date 2016
47 /// \author smal.root@gmail.com
48 
49 #ifndef ASSIMP_BUILD_NO_AMF_IMPORTER
50 
51 // Header files, Assimp.
52 #include "AMFImporter.hpp"
53 #include "AMFImporter_Macro.hpp"
54 
55 #include <assimp/fast_atof.h>
56 #include <assimp/DefaultIOSystem.h>
57 
58 // Header files, stdlib.
59 #include <memory>
60 
61 namespace Assimp
62 {
63 
64 /// \var aiImporterDesc AMFImporter::Description
65 /// Conastant which hold importer description
66 const aiImporterDesc AMFImporter::Description = {
67 	"Additive manufacturing file format(AMF) Importer",
68 	"smalcom",
69 	"",
70 	"See documentation in source code. Chapter: Limitations.",
71 	aiImporterFlags_SupportTextFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental,
72 	0,
73 	0,
74 	0,
75 	0,
76 	"amf"
77 };
78 
Clear()79 void AMFImporter::Clear()
80 {
81 	mNodeElement_Cur = nullptr;
82 	mUnit.clear();
83 	mMaterial_Converted.clear();
84 	mTexture_Converted.clear();
85 	// Delete all elements
86 	if(!mNodeElement_List.empty())
87 	{
88 		for(CAMFImporter_NodeElement* ne: mNodeElement_List) { delete ne; }
89 
90 		mNodeElement_List.clear();
91 	}
92 }
93 
~AMFImporter()94 AMFImporter::~AMFImporter()
95 {
96 	if(mReader != nullptr) delete mReader;
97 	// Clear() is accounting if data already is deleted. So, just check again if all data is deleted.
98 	Clear();
99 }
100 
101 /*********************************************************************************************************************************************/
102 /************************************************************ Functions: find set ************************************************************/
103 /*********************************************************************************************************************************************/
104 
Find_NodeElement(const std::string & pID,const CAMFImporter_NodeElement::EType pType,CAMFImporter_NodeElement ** pNodeElement) const105 bool AMFImporter::Find_NodeElement(const std::string& pID, const CAMFImporter_NodeElement::EType pType, CAMFImporter_NodeElement** pNodeElement) const
106 {
107 	for(CAMFImporter_NodeElement* ne: mNodeElement_List)
108 	{
109 		if((ne->ID == pID) && (ne->Type == pType))
110 		{
111 			if(pNodeElement != nullptr) *pNodeElement = ne;
112 
113 			return true;
114 		}
115 	}// for(CAMFImporter_NodeElement* ne: mNodeElement_List)
116 
117 	return false;
118 }
119 
Find_ConvertedNode(const std::string & pID,std::list<aiNode * > & pNodeList,aiNode ** pNode) const120 bool AMFImporter::Find_ConvertedNode(const std::string& pID, std::list<aiNode*>& pNodeList, aiNode** pNode) const
121 {
122 aiString node_name(pID.c_str());
123 
124 	for(aiNode* node: pNodeList)
125 	{
126 		if(node->mName == node_name)
127 		{
128 			if(pNode != nullptr) *pNode = node;
129 
130 			return true;
131 		}
132 	}// for(aiNode* node: pNodeList)
133 
134 	return false;
135 }
136 
Find_ConvertedMaterial(const std::string & pID,const SPP_Material ** pConvertedMaterial) const137 bool AMFImporter::Find_ConvertedMaterial(const std::string& pID, const SPP_Material** pConvertedMaterial) const
138 {
139 	for(const SPP_Material& mat: mMaterial_Converted)
140 	{
141 		if(mat.ID == pID)
142 		{
143 			if(pConvertedMaterial != nullptr) *pConvertedMaterial = &mat;
144 
145 			return true;
146 		}
147 	}// for(const SPP_Material& mat: mMaterial_Converted)
148 
149 	return false;
150 }
151 
152 /*********************************************************************************************************************************************/
153 /************************************************************ Functions: throw set ***********************************************************/
154 /*********************************************************************************************************************************************/
155 
Throw_CloseNotFound(const std::string & pNode)156 void AMFImporter::Throw_CloseNotFound(const std::string& pNode)
157 {
158 	throw DeadlyImportError("Close tag for node <" + pNode + "> not found. Seems file is corrupt.");
159 }
160 
Throw_IncorrectAttr(const std::string & pAttrName)161 void AMFImporter::Throw_IncorrectAttr(const std::string& pAttrName)
162 {
163 	throw DeadlyImportError("Node <" + std::string(mReader->getNodeName()) + "> has incorrect attribute \"" + pAttrName + "\".");
164 }
165 
Throw_IncorrectAttrValue(const std::string & pAttrName)166 void AMFImporter::Throw_IncorrectAttrValue(const std::string& pAttrName)
167 {
168 	throw DeadlyImportError("Attribute \"" + pAttrName + "\" in node <" + std::string(mReader->getNodeName()) + "> has incorrect value.");
169 }
170 
Throw_MoreThanOnceDefined(const std::string & pNodeType,const std::string & pDescription)171 void AMFImporter::Throw_MoreThanOnceDefined(const std::string& pNodeType, const std::string& pDescription)
172 {
173 	throw DeadlyImportError("\"" + pNodeType + "\" node can be used only once in " + mReader->getNodeName() + ". Description: " + pDescription);
174 }
175 
Throw_ID_NotFound(const std::string & pID) const176 void AMFImporter::Throw_ID_NotFound(const std::string& pID) const
177 {
178 	throw DeadlyImportError("Not found node with name \"" + pID + "\".");
179 }
180 
181 /*********************************************************************************************************************************************/
182 /************************************************************* Functions: XML set ************************************************************/
183 /*********************************************************************************************************************************************/
184 
XML_CheckNode_MustHaveChildren()185 void AMFImporter::XML_CheckNode_MustHaveChildren()
186 {
187 	if(mReader->isEmptyElement()) throw DeadlyImportError(std::string("Node <") + mReader->getNodeName() + "> must have children.");
188 }
189 
XML_CheckNode_SkipUnsupported(const std::string & pParentNodeName)190 void AMFImporter::XML_CheckNode_SkipUnsupported(const std::string& pParentNodeName)
191 {
192     static const size_t Uns_Skip_Len = 3;
193     const char* Uns_Skip[Uns_Skip_Len] = { "composite", "edge", "normal" };
194 
195     static bool skipped_before[Uns_Skip_Len] = { false, false, false };
196 
197     std::string nn(mReader->getNodeName());
198     bool found = false;
199     bool close_found = false;
200     size_t sk_idx;
201 
202 	for(sk_idx = 0; sk_idx < Uns_Skip_Len; sk_idx++)
203 	{
204 		if(nn != Uns_Skip[sk_idx]) continue;
205 
206 		found = true;
207 		if(mReader->isEmptyElement())
208 		{
209 			close_found = true;
210 
211 			goto casu_cres;
212 		}
213 
214 		while(mReader->read())
215 		{
216 			if((mReader->getNodeType() == irr::io::EXN_ELEMENT_END) && (nn == mReader->getNodeName()))
217 			{
218 				close_found = true;
219 
220 				goto casu_cres;
221 			}
222 		}
223 	}// for(sk_idx = 0; sk_idx < Uns_Skip_Len; sk_idx++)
224 
225 casu_cres:
226 
227 	if(!found) throw DeadlyImportError("Unknown node \"" + nn + "\" in " + pParentNodeName + ".");
228 	if(!close_found) Throw_CloseNotFound(nn);
229 
230 	if(!skipped_before[sk_idx])
231 	{
232 		skipped_before[sk_idx] = true;
233         ASSIMP_LOG_WARN_F("Skipping node \"", nn, "\" in ", pParentNodeName, ".");
234 	}
235 }
236 
XML_SearchNode(const std::string & pNodeName)237 bool AMFImporter::XML_SearchNode(const std::string& pNodeName)
238 {
239 	while(mReader->read())
240 	{
241 		if((mReader->getNodeType() == irr::io::EXN_ELEMENT) && XML_CheckNode_NameEqual(pNodeName)) return true;
242 	}
243 
244 	return false;
245 }
246 
XML_ReadNode_GetAttrVal_AsBool(const int pAttrIdx)247 bool AMFImporter::XML_ReadNode_GetAttrVal_AsBool(const int pAttrIdx)
248 {
249     std::string val(mReader->getAttributeValue(pAttrIdx));
250 
251 	if((val == "false") || (val == "0"))
252 		return false;
253 	else if((val == "true") || (val == "1"))
254 		return true;
255 	else
256 		throw DeadlyImportError("Bool attribute value can contain \"false\"/\"0\" or \"true\"/\"1\" not the \"" + val + "\"");
257 }
258 
XML_ReadNode_GetAttrVal_AsFloat(const int pAttrIdx)259 float AMFImporter::XML_ReadNode_GetAttrVal_AsFloat(const int pAttrIdx)
260 {
261     std::string val;
262     float tvalf;
263 
264 	ParseHelper_FixTruncatedFloatString(mReader->getAttributeValue(pAttrIdx), val);
265 	fast_atoreal_move(val.c_str(), tvalf, false);
266 
267 	return tvalf;
268 }
269 
XML_ReadNode_GetAttrVal_AsU32(const int pAttrIdx)270 uint32_t AMFImporter::XML_ReadNode_GetAttrVal_AsU32(const int pAttrIdx)
271 {
272 	return strtoul10(mReader->getAttributeValue(pAttrIdx));
273 }
274 
XML_ReadNode_GetVal_AsFloat()275 float AMFImporter::XML_ReadNode_GetVal_AsFloat()
276 {
277     std::string val;
278     float tvalf;
279 
280 	if(!mReader->read()) throw DeadlyImportError("XML_ReadNode_GetVal_AsFloat. No data, seems file is corrupt.");
281 	if(mReader->getNodeType() != irr::io::EXN_TEXT) throw DeadlyImportError("XML_ReadNode_GetVal_AsFloat. Invalid type of XML element, seems file is corrupt.");
282 
283 	ParseHelper_FixTruncatedFloatString(mReader->getNodeData(), val);
284 	fast_atoreal_move(val.c_str(), tvalf, false);
285 
286 	return tvalf;
287 }
288 
XML_ReadNode_GetVal_AsU32()289 uint32_t AMFImporter::XML_ReadNode_GetVal_AsU32()
290 {
291 	if(!mReader->read()) throw DeadlyImportError("XML_ReadNode_GetVal_AsU32. No data, seems file is corrupt.");
292 	if(mReader->getNodeType() != irr::io::EXN_TEXT) throw DeadlyImportError("XML_ReadNode_GetVal_AsU32. Invalid type of XML element, seems file is corrupt.");
293 
294 	return strtoul10(mReader->getNodeData());
295 }
296 
XML_ReadNode_GetVal_AsString(std::string & pValue)297 void AMFImporter::XML_ReadNode_GetVal_AsString(std::string& pValue)
298 {
299 	if(!mReader->read()) throw DeadlyImportError("XML_ReadNode_GetVal_AsString. No data, seems file is corrupt.");
300 	if(mReader->getNodeType() != irr::io::EXN_TEXT)
301 		throw DeadlyImportError("XML_ReadNode_GetVal_AsString. Invalid type of XML element, seems file is corrupt.");
302 
303 	pValue = mReader->getNodeData();
304 }
305 
306 /*********************************************************************************************************************************************/
307 /************************************************************ Functions: parse set ***********************************************************/
308 /*********************************************************************************************************************************************/
309 
ParseHelper_Node_Enter(CAMFImporter_NodeElement * pNode)310 void AMFImporter::ParseHelper_Node_Enter(CAMFImporter_NodeElement* pNode)
311 {
312 	mNodeElement_Cur->Child.push_back(pNode);// add new element to current element child list.
313 	mNodeElement_Cur = pNode;// switch current element to new one.
314 }
315 
ParseHelper_Node_Exit()316 void AMFImporter::ParseHelper_Node_Exit()
317 {
318 	// check if we can walk up.
319 	if(mNodeElement_Cur != nullptr) mNodeElement_Cur = mNodeElement_Cur->Parent;
320 }
321 
ParseHelper_FixTruncatedFloatString(const char * pInStr,std::string & pOutString)322 void AMFImporter::ParseHelper_FixTruncatedFloatString(const char* pInStr, std::string& pOutString)
323 {
324     size_t instr_len;
325 
326 	pOutString.clear();
327 	instr_len = strlen(pInStr);
328 	if(!instr_len) return;
329 
330 	pOutString.reserve(instr_len * 3 / 2);
331 	// check and correct floats in format ".x". Must be "x.y".
332 	if(pInStr[0] == '.') pOutString.push_back('0');
333 
334 	pOutString.push_back(pInStr[0]);
335 	for(size_t ci = 1; ci < instr_len; ci++)
336 	{
337 		if((pInStr[ci] == '.') && ((pInStr[ci - 1] == ' ') || (pInStr[ci - 1] == '-') || (pInStr[ci - 1] == '+') || (pInStr[ci - 1] == '\t')))
338 		{
339 			pOutString.push_back('0');
340 			pOutString.push_back('.');
341 		}
342 		else
343 		{
344 			pOutString.push_back(pInStr[ci]);
345 		}
346 	}
347 }
348 
ParseHelper_Decode_Base64_IsBase64(const char pChar)349 static bool ParseHelper_Decode_Base64_IsBase64(const char pChar)
350 {
351 	return (isalnum(pChar) || (pChar == '+') || (pChar == '/'));
352 }
353 
ParseHelper_Decode_Base64(const std::string & pInputBase64,std::vector<uint8_t> & pOutputData) const354 void AMFImporter::ParseHelper_Decode_Base64(const std::string& pInputBase64, std::vector<uint8_t>& pOutputData) const
355 {
356     // With help from
357     // René Nyffenegger http://www.adp-gmbh.ch/cpp/common/base64.html
358     const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
359 
360     uint8_t tidx = 0;
361     uint8_t arr4[4], arr3[3];
362 
363 	// check input data
364 	if(pInputBase64.size() % 4) throw DeadlyImportError("Base64-encoded data must have size multiply of four.");
365 	// prepare output place
366 	pOutputData.clear();
367 	pOutputData.reserve(pInputBase64.size() / 4 * 3);
368 
369 	for(size_t in_len = pInputBase64.size(), in_idx = 0; (in_len > 0) && (pInputBase64[in_idx] != '='); in_len--)
370 	{
371 		if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx]))
372 		{
373 			arr4[tidx++] = pInputBase64[in_idx++];
374 			if(tidx == 4)
375 			{
376 				for(tidx = 0; tidx < 4; tidx++) arr4[tidx] = (uint8_t)base64_chars.find(arr4[tidx]);
377 
378 				arr3[0] = (arr4[0] << 2) + ((arr4[1] & 0x30) >> 4);
379 				arr3[1] = ((arr4[1] & 0x0F) << 4) + ((arr4[2] & 0x3C) >> 2);
380 				arr3[2] = ((arr4[2] & 0x03) << 6) + arr4[3];
381 				for(tidx = 0; tidx < 3; tidx++) pOutputData.push_back(arr3[tidx]);
382 
383 				tidx = 0;
384 			}// if(tidx == 4)
385 		}// if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx]))
386 		else
387 		{
388 			in_idx++;
389 		}// if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx])) else
390 	}
391 
392 	if(tidx)
393 	{
394 		for(uint8_t i = tidx; i < 4; i++) arr4[i] = 0;
395 		for(uint8_t i = 0; i < 4; i++) arr4[i] = (uint8_t)(base64_chars.find(arr4[i]));
396 
397 		arr3[0] = (arr4[0] << 2) + ((arr4[1] & 0x30) >> 4);
398 		arr3[1] = ((arr4[1] & 0x0F) << 4) + ((arr4[2] & 0x3C) >> 2);
399 		arr3[2] = ((arr4[2] & 0x03) << 6) + arr4[3];
400 		for(uint8_t i = 0; i < (tidx - 1); i++) pOutputData.push_back(arr3[i]);
401 	}
402 }
403 
ParseFile(const std::string & pFile,IOSystem * pIOHandler)404 void AMFImporter::ParseFile(const std::string& pFile, IOSystem* pIOHandler)
405 {
406     irr::io::IrrXMLReader* OldReader = mReader;// store current XMLreader.
407     std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb"));
408 
409 	// Check whether we can read from the file
410 	if(file.get() == NULL) throw DeadlyImportError("Failed to open AMF file " + pFile + ".");
411 
412 	// generate a XML reader for it
413 	std::unique_ptr<CIrrXML_IOStreamReader> mIOWrapper(new CIrrXML_IOStreamReader(file.get()));
414 	mReader = irr::io::createIrrXMLReader(mIOWrapper.get());
415 	if(!mReader) throw DeadlyImportError("Failed to create XML reader for file" + pFile + ".");
416 	//
417 	// start reading
418 	// search for root tag <amf>
419 	if(XML_SearchNode("amf"))
420 		ParseNode_Root();
421 	else
422 		throw DeadlyImportError("Root node \"amf\" not found.");
423 
424 	delete mReader;
425 	// restore old XMLreader
426 	mReader = OldReader;
427 }
428 
429 // <amf
430 // unit="" - The units to be used. May be "inch", "millimeter", "meter", "feet", or "micron".
431 // version="" - Version of file format.
432 // >
433 // </amf>
434 // Root XML element.
435 // Multi elements - No.
ParseNode_Root()436 void AMFImporter::ParseNode_Root()
437 {
438     std::string unit, version;
439     CAMFImporter_NodeElement *ne( nullptr );
440 
441 	// Read attributes for node <amf>.
442 	MACRO_ATTRREAD_LOOPBEG;
443 		MACRO_ATTRREAD_CHECK_RET("unit", unit, mReader->getAttributeValue);
444 		MACRO_ATTRREAD_CHECK_RET("version", version, mReader->getAttributeValue);
445 	MACRO_ATTRREAD_LOOPEND_WSKIP;
446 
447 	// Check attributes
448 	if(!mUnit.empty())
449 	{
450 		if((mUnit != "inch") && (mUnit != "millimeter") && (mUnit != "meter") && (mUnit != "feet") && (mUnit != "micron")) Throw_IncorrectAttrValue("unit");
451 	}
452 
453 	// create root node element.
454 	ne = new CAMFImporter_NodeElement_Root(nullptr);
455 	mNodeElement_Cur = ne;// set first "current" element
456 	// and assign attribute's values
457 	((CAMFImporter_NodeElement_Root*)ne)->Unit = unit;
458 	((CAMFImporter_NodeElement_Root*)ne)->Version = version;
459 
460 	// Check for child nodes
461 	if(!mReader->isEmptyElement())
462 	{
463 		MACRO_NODECHECK_LOOPBEGIN("amf");
464 			if(XML_CheckNode_NameEqual("object")) { ParseNode_Object(); continue; }
465 			if(XML_CheckNode_NameEqual("material")) { ParseNode_Material(); continue; }
466 			if(XML_CheckNode_NameEqual("texture")) { ParseNode_Texture(); continue; }
467 			if(XML_CheckNode_NameEqual("constellation")) { ParseNode_Constellation(); continue; }
468 			if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; }
469 		MACRO_NODECHECK_LOOPEND("amf");
470 		mNodeElement_Cur = ne;// force restore "current" element
471 	}// if(!mReader->isEmptyElement())
472 
473 	mNodeElement_List.push_back(ne);// add to node element list because its a new object in graph.
474 }
475 
476 // <constellation
477 // id="" - The Object ID of the new constellation being defined.
478 // >
479 // </constellation>
480 // A collection of objects or constellations with specific relative locations.
481 // Multi elements - Yes.
482 // Parent element - <amf>.
ParseNode_Constellation()483 void AMFImporter::ParseNode_Constellation()
484 {
485     std::string id;
486     CAMFImporter_NodeElement* ne( nullptr );
487 
488 	// Read attributes for node <constellation>.
489 	MACRO_ATTRREAD_LOOPBEG;
490 		MACRO_ATTRREAD_CHECK_RET("id", id, mReader->getAttributeValue);
491 	MACRO_ATTRREAD_LOOPEND;
492 
493 	// create and if needed - define new grouping object.
494 	ne = new CAMFImporter_NodeElement_Constellation(mNodeElement_Cur);
495 
496 	CAMFImporter_NodeElement_Constellation& als = *((CAMFImporter_NodeElement_Constellation*)ne);// alias for convenience
497 
498 	if(!id.empty()) als.ID = id;
499 	// Check for child nodes
500 	if(!mReader->isEmptyElement())
501 	{
502 		ParseHelper_Node_Enter(ne);
503 		MACRO_NODECHECK_LOOPBEGIN("constellation");
504 			if(XML_CheckNode_NameEqual("instance")) { ParseNode_Instance(); continue; }
505 			if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; }
506 		MACRO_NODECHECK_LOOPEND("constellation");
507 		ParseHelper_Node_Exit();
508 	}// if(!mReader->isEmptyElement())
509 	else
510 	{
511 		mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
512 	}// if(!mReader->isEmptyElement()) else
513 
514 	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
515 }
516 
517 // <instance
518 // objectid="" - The Object ID of the new constellation being defined.
519 // >
520 // </instance>
521 // A collection of objects or constellations with specific relative locations.
522 // Multi elements - Yes.
523 // Parent element - <amf>.
ParseNode_Instance()524 void AMFImporter::ParseNode_Instance()
525 {
526     std::string objectid;
527     CAMFImporter_NodeElement* ne( nullptr );
528 
529 	// Read attributes for node <constellation>.
530 	MACRO_ATTRREAD_LOOPBEG;
531 		MACRO_ATTRREAD_CHECK_RET("objectid", objectid, mReader->getAttributeValue);
532 	MACRO_ATTRREAD_LOOPEND;
533 
534 	// used object id must be defined, check that.
535 	if(objectid.empty()) throw DeadlyImportError("\"objectid\" in <instance> must be defined.");
536 	// create and define new grouping object.
537 	ne = new CAMFImporter_NodeElement_Instance(mNodeElement_Cur);
538 
539 	CAMFImporter_NodeElement_Instance& als = *((CAMFImporter_NodeElement_Instance*)ne);// alias for convenience
540 
541 	als.ObjectID = objectid;
542 	// Check for child nodes
543 	if(!mReader->isEmptyElement())
544 	{
545 		bool read_flag[6] = { false, false, false, false, false, false };
546 
547 		als.Delta.Set(0, 0, 0);
548 		als.Rotation.Set(0, 0, 0);
549 		ParseHelper_Node_Enter(ne);
550 		MACRO_NODECHECK_LOOPBEGIN("instance");
551 			MACRO_NODECHECK_READCOMP_F("deltax", read_flag[0], als.Delta.x);
552 			MACRO_NODECHECK_READCOMP_F("deltay", read_flag[1], als.Delta.y);
553 			MACRO_NODECHECK_READCOMP_F("deltaz", read_flag[2], als.Delta.z);
554 			MACRO_NODECHECK_READCOMP_F("rx", read_flag[3], als.Rotation.x);
555 			MACRO_NODECHECK_READCOMP_F("ry", read_flag[4], als.Rotation.y);
556 			MACRO_NODECHECK_READCOMP_F("rz", read_flag[5], als.Rotation.z);
557 		MACRO_NODECHECK_LOOPEND("instance");
558 		ParseHelper_Node_Exit();
559 		// also convert degrees to radians.
560 		als.Rotation.x = AI_MATH_PI_F * als.Rotation.x / 180.0f;
561 		als.Rotation.y = AI_MATH_PI_F * als.Rotation.y / 180.0f;
562 		als.Rotation.z = AI_MATH_PI_F * als.Rotation.z / 180.0f;
563 	}// if(!mReader->isEmptyElement())
564 	else
565 	{
566 		mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
567 	}// if(!mReader->isEmptyElement()) else
568 
569 	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
570 }
571 
572 // <object
573 // id="" - A unique ObjectID for the new object being defined.
574 // >
575 // </object>
576 // An object definition.
577 // Multi elements - Yes.
578 // Parent element - <amf>.
ParseNode_Object()579 void AMFImporter::ParseNode_Object()
580 {
581     std::string id;
582     CAMFImporter_NodeElement* ne( nullptr );
583 
584 	// Read attributes for node <object>.
585 	MACRO_ATTRREAD_LOOPBEG;
586 		MACRO_ATTRREAD_CHECK_RET("id", id, mReader->getAttributeValue);
587 	MACRO_ATTRREAD_LOOPEND;
588 
589 	// create and if needed - define new geometry object.
590 	ne = new CAMFImporter_NodeElement_Object(mNodeElement_Cur);
591 
592 	CAMFImporter_NodeElement_Object& als = *((CAMFImporter_NodeElement_Object*)ne);// alias for convenience
593 
594 	if(!id.empty()) als.ID = id;
595 	// Check for child nodes
596 	if(!mReader->isEmptyElement())
597 	{
598 		bool col_read = false;
599 
600 		ParseHelper_Node_Enter(ne);
601 		MACRO_NODECHECK_LOOPBEGIN("object");
602 			if(XML_CheckNode_NameEqual("color"))
603 			{
604 				// Check if color already defined for object.
605 				if(col_read) Throw_MoreThanOnceDefined("color", "Only one color can be defined for <object>.");
606 				// read data and set flag about it
607 				ParseNode_Color();
608 				col_read = true;
609 
610 				continue;
611 			}
612 
613 			if(XML_CheckNode_NameEqual("mesh")) { ParseNode_Mesh(); continue; }
614 			if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; }
615 		MACRO_NODECHECK_LOOPEND("object");
616 		ParseHelper_Node_Exit();
617 	}// if(!mReader->isEmptyElement())
618 	else
619 	{
620 		mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
621 	}// if(!mReader->isEmptyElement()) else
622 
623 	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
624 }
625 
626 // <metadata
627 // type="" - The type of the attribute.
628 // >
629 // </metadata>
630 // Specify additional information about an entity.
631 // Multi elements - Yes.
632 // Parent element - <amf>, <object>, <volume>, <material>, <vertex>.
633 //
634 // Reserved types are:
635 // "Name" - The alphanumeric label of the entity, to be used by the interpreter if interacting with the user.
636 // "Description" - A description of the content of the entity
637 // "URL" - A link to an external resource relating to the entity
638 // "Author" - Specifies the name(s) of the author(s) of the entity
639 // "Company" - Specifying the company generating the entity
640 // "CAD" - specifies the name of the originating CAD software and version
641 // "Revision" - specifies the revision of the entity
642 // "Tolerance" - specifies the desired manufacturing tolerance of the entity in entity's unit system
643 // "Volume" - specifies the total volume of the entity, in the entity's unit system, to be used for verification (object and volume only)
ParseNode_Metadata()644 void AMFImporter::ParseNode_Metadata()
645 {
646     std::string type, value;
647     CAMFImporter_NodeElement* ne( nullptr );
648 
649 	// read attribute
650 	MACRO_ATTRREAD_LOOPBEG;
651 		MACRO_ATTRREAD_CHECK_RET("type", type, mReader->getAttributeValue);
652 	MACRO_ATTRREAD_LOOPEND;
653 	// and value of node.
654 	value = mReader->getNodeData();
655 	// Create node element and assign read data.
656 	ne = new CAMFImporter_NodeElement_Metadata(mNodeElement_Cur);
657 	((CAMFImporter_NodeElement_Metadata*)ne)->Type = type;
658 	((CAMFImporter_NodeElement_Metadata*)ne)->Value = value;
659 	mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element
660 	mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph.
661 }
662 
663 /*********************************************************************************************************************************************/
664 /******************************************************** Functions: BaseImporter set ********************************************************/
665 /*********************************************************************************************************************************************/
666 
CanRead(const std::string & pFile,IOSystem * pIOHandler,bool pCheckSig) const667 bool AMFImporter::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool pCheckSig) const
668 {
669     const std::string extension = GetExtension(pFile);
670 
671     if ( extension == "amf" ) {
672         return true;
673     }
674 
675 	if(!extension.length() || pCheckSig)
676 	{
677 		const char* tokens[] = { "<amf" };
678 
679 		return SearchFileHeaderForToken( pIOHandler, pFile, tokens, 1 );
680 	}
681 
682 	return false;
683 }
684 
GetExtensionList(std::set<std::string> & pExtensionList)685 void AMFImporter::GetExtensionList(std::set<std::string>& pExtensionList)
686 {
687 	pExtensionList.insert("amf");
688 }
689 
GetInfo() const690 const aiImporterDesc* AMFImporter::GetInfo () const
691 {
692 	return &Description;
693 }
694 
InternReadFile(const std::string & pFile,aiScene * pScene,IOSystem * pIOHandler)695 void AMFImporter::InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
696 {
697 	Clear();// delete old graph.
698 	ParseFile(pFile, pIOHandler);
699 	Postprocess_BuildScene(pScene);
700 	// scene graph is ready, exit.
701 }
702 
703 }// namespace Assimp
704 
705 #endif // !ASSIMP_BUILD_NO_AMF_IMPORTER
706