1 /*
2 Open Asset Import Library (assimp)
3 ----------------------------------------------------------------------
4 
5 Copyright (c) 2006-2019, assimp team
6 
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
12 following 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   X3DImporter_Geometry3D.cpp
43 /// \brief  Parsing data from nodes of "Geometry3D" set of X3D.
44 /// \date   2015-2016
45 /// \author smal.root@gmail.com
46 
47 #ifndef ASSIMP_BUILD_NO_X3D_IMPORTER
48 
49 #include "X3DImporter.hpp"
50 #include "X3DImporter_Macro.hpp"
51 
52 // Header files, Assimp.
53 #include <assimp/StandardShapes.h>
54 
55 namespace Assimp
56 {
57 
58 // <Box
59 // DEF=""       ID
60 // USE=""       IDREF
61 // size="2 2 2" SFVec3f [initializeOnly]
62 // solid="true" SFBool  [initializeOnly]
63 // />
64 // The Box node specifies a rectangular parallelepiped box centred at (0, 0, 0) in the local coordinate system and aligned with the local coordinate axes.
65 // By default, the box measures 2 units in each dimension, from -1 to +1. The size field specifies the extents of the box along the X-, Y-, and Z-axes
66 // respectively and each component value shall be greater than zero.
ParseNode_Geometry3D_Box()67 void X3DImporter::ParseNode_Geometry3D_Box()
68 {
69     std::string def, use;
70     bool solid = true;
71     aiVector3D size(2, 2, 2);
72     CX3DImporter_NodeElement* ne( nullptr );
73 
74 	MACRO_ATTRREAD_LOOPBEG;
75 		MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use);
76 		MACRO_ATTRREAD_CHECK_REF("size", size, XML_ReadNode_GetAttrVal_AsVec3f);
77 		MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool);
78 	MACRO_ATTRREAD_LOOPEND;
79 
80 	// if "USE" defined then find already defined element.
81 	if(!use.empty())
82 	{
83 		MACRO_USE_CHECKANDAPPLY(def, use, ENET_Box, ne);
84 	}
85 	else
86 	{
87 		// create and if needed - define new geometry object.
88 		ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Box, NodeElement_Cur);
89 		if(!def.empty()) ne->ID = def;
90 
91 		GeometryHelper_MakeQL_RectParallelepiped(size, ((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices);// get quad list
92 		((CX3DImporter_NodeElement_Geometry3D*)ne)->Solid = solid;
93 		((CX3DImporter_NodeElement_Geometry3D*)ne)->NumIndices = 4;
94 		// check for X3DMetadataObject childs.
95 		if(!mReader->isEmptyElement())
96 			ParseNode_Metadata(ne, "Box");
97 		else
98 			NodeElement_Cur->Child.push_back(ne);// add made object as child to current element
99 
100 		NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph
101 	}// if(!use.empty()) else
102 }
103 
104 // <Cone
105 // DEF=""           ID
106 // USE=""           IDREF
107 // bottom="true"    SFBool [initializeOnly]
108 // bottomRadius="1" SFloat [initializeOnly]
109 // height="2"       SFloat [initializeOnly]
110 // side="true"      SFBool [initializeOnly]
111 // solid="true"     SFBool [initializeOnly]
112 // />
ParseNode_Geometry3D_Cone()113 void X3DImporter::ParseNode_Geometry3D_Cone()
114 {
115     std::string use, def;
116     bool bottom = true;
117     float bottomRadius = 1;
118     float height = 2;
119     bool side = true;
120     bool solid = true;
121     CX3DImporter_NodeElement* ne( nullptr );
122 
123 	MACRO_ATTRREAD_LOOPBEG;
124 		MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use);
125 		MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool);
126 		MACRO_ATTRREAD_CHECK_RET("side", side, XML_ReadNode_GetAttrVal_AsBool);
127 		MACRO_ATTRREAD_CHECK_RET("bottom", bottom, XML_ReadNode_GetAttrVal_AsBool);
128 		MACRO_ATTRREAD_CHECK_RET("height", height, XML_ReadNode_GetAttrVal_AsFloat);
129 		MACRO_ATTRREAD_CHECK_RET("bottomRadius", bottomRadius, XML_ReadNode_GetAttrVal_AsFloat);
130 	MACRO_ATTRREAD_LOOPEND;
131 
132 	// if "USE" defined then find already defined element.
133 	if(!use.empty())
134 	{
135 		MACRO_USE_CHECKANDAPPLY(def, use, ENET_Cone, ne);
136 	}
137 	else
138 	{
139 		const unsigned int tess = 30;///TODO: IME tessellation factor through ai_property
140 
141 		std::vector<aiVector3D> tvec;// temp array for vertices.
142 
143 		// create and if needed - define new geometry object.
144 		ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Cone, NodeElement_Cur);
145 		if(!def.empty()) ne->ID = def;
146 
147 		// make cone or parts according to flags.
148 		if(side)
149 		{
150 			StandardShapes::MakeCone(height, 0, bottomRadius, tess, tvec, !bottom);
151 		}
152 		else if(bottom)
153 		{
154 			StandardShapes::MakeCircle(bottomRadius, tess, tvec);
155 			height = -(height / 2);
156 			for(std::vector<aiVector3D>::iterator it = tvec.begin(); it != tvec.end(); ++it) it->y = height;// y - because circle made in oXZ.
157 		}
158 
159 		// copy data from temp array
160 		for(std::vector<aiVector3D>::iterator it = tvec.begin(); it != tvec.end(); ++it) ((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices.push_back(*it);
161 
162 		((CX3DImporter_NodeElement_Geometry3D*)ne)->Solid = solid;
163 		((CX3DImporter_NodeElement_Geometry3D*)ne)->NumIndices = 3;
164 		// check for X3DMetadataObject childs.
165 		if(!mReader->isEmptyElement())
166 			ParseNode_Metadata(ne, "Cone");
167 		else
168 			NodeElement_Cur->Child.push_back(ne);// add made object as child to current element
169 
170 		NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph
171 	}// if(!use.empty()) else
172 }
173 
174 // <Cylinder
175 // DEF=""        ID
176 // USE=""        IDREF
177 // bottom="true" SFBool [initializeOnly]
178 // height="2"    SFloat [initializeOnly]
179 // radius="1"    SFloat [initializeOnly]
180 // side="true"   SFBool [initializeOnly]
181 // solid="true"  SFBool [initializeOnly]
182 // top="true"    SFBool [initializeOnly]
183 // />
ParseNode_Geometry3D_Cylinder()184 void X3DImporter::ParseNode_Geometry3D_Cylinder()
185 {
186     std::string use, def;
187     bool bottom = true;
188     float height = 2;
189     float radius = 1;
190     bool side = true;
191     bool solid = true;
192     bool top = true;
193     CX3DImporter_NodeElement* ne( nullptr );
194 
195 	MACRO_ATTRREAD_LOOPBEG;
196 		MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use);
197 		MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat);
198 		MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool);
199 		MACRO_ATTRREAD_CHECK_RET("bottom", bottom, XML_ReadNode_GetAttrVal_AsBool);
200 		MACRO_ATTRREAD_CHECK_RET("top", top, XML_ReadNode_GetAttrVal_AsBool);
201 		MACRO_ATTRREAD_CHECK_RET("side", side, XML_ReadNode_GetAttrVal_AsBool);
202 		MACRO_ATTRREAD_CHECK_RET("height", height, XML_ReadNode_GetAttrVal_AsFloat);
203 	MACRO_ATTRREAD_LOOPEND;
204 
205 	// if "USE" defined then find already defined element.
206 	if(!use.empty())
207 	{
208 		MACRO_USE_CHECKANDAPPLY(def, use, ENET_Cylinder, ne);
209 	}
210 	else
211 	{
212 		const unsigned int tess = 30;///TODO: IME tessellation factor through ai_property
213 
214 		std::vector<aiVector3D> tside;// temp array for vertices of side.
215 		std::vector<aiVector3D> tcir;// temp array for vertices of circle.
216 
217 		// create and if needed - define new geometry object.
218 		ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Cylinder, NodeElement_Cur);
219 		if(!def.empty()) ne->ID = def;
220 
221 		// make cilynder or parts according to flags.
222 		if(side) StandardShapes::MakeCone(height, radius, radius, tess, tside, true);
223 
224 		height /= 2;// height defined for whole cylinder, when creating top and bottom circle we are using just half of height.
225 		if(top || bottom) StandardShapes::MakeCircle(radius, tess, tcir);
226 		// copy data from temp arrays
227 		std::list<aiVector3D>& vlist = ((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices;// just short alias.
228 
229 		for(std::vector<aiVector3D>::iterator it = tside.begin(); it != tside.end(); ++it) vlist.push_back(*it);
230 
231 		if(top)
232 		{
233 			for(std::vector<aiVector3D>::iterator it = tcir.begin(); it != tcir.end(); ++it)
234 			{
235 				(*it).y = height;// y - because circle made in oXZ.
236 				vlist.push_back(*it);
237 			}
238 		}// if(top)
239 
240 		if(bottom)
241 		{
242 			for(std::vector<aiVector3D>::iterator it = tcir.begin(); it != tcir.end(); ++it)
243 			{
244 				(*it).y = -height;// y - because circle made in oXZ.
245 				vlist.push_back(*it);
246 			}
247 		}// if(top)
248 
249 		((CX3DImporter_NodeElement_Geometry3D*)ne)->Solid = solid;
250 		((CX3DImporter_NodeElement_Geometry3D*)ne)->NumIndices = 3;
251 		// check for X3DMetadataObject childs.
252 		if(!mReader->isEmptyElement())
253 			ParseNode_Metadata(ne, "Cylinder");
254 		else
255 			NodeElement_Cur->Child.push_back(ne);// add made object as child to current element
256 
257 		NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph
258 	}// if(!use.empty()) else
259 }
260 
261 // <ElevationGrid
262 // DEF=""                 ID
263 // USE=""                 IDREF
264 // ccw="true"             SFBool  [initializeOnly]
265 // colorPerVertex="true"  SFBool  [initializeOnly]
266 // creaseAngle="0"        SFloat  [initializeOnly]
267 // height=""              MFloat  [initializeOnly]
268 // normalPerVertex="true" SFBool  [initializeOnly]
269 // solid="true"           SFBool  [initializeOnly]
270 // xDimension="0"         SFInt32 [initializeOnly]
271 // xSpacing="1.0"         SFloat  [initializeOnly]
272 // zDimension="0"         SFInt32 [initializeOnly]
273 // zSpacing="1.0"         SFloat  [initializeOnly]
274 // >
275 //   <!-- ColorNormalTexCoordContentModel -->
276 // ColorNormalTexCoordContentModel can contain Color (or ColorRGBA), Normal and TextureCoordinate, in any order. No more than one instance of any single
277 // node type is allowed. A ProtoInstance node (with the proper node type) can be substituted for any node in this content model.
278 // </ElevationGrid>
279 // The ElevationGrid node specifies a uniform rectangular grid of varying height in the Y=0 plane of the local coordinate system. The geometry is described
280 // by a scalar array of height values that specify the height of a surface above each point of the grid. The xDimension and zDimension fields indicate
281 // the number of elements of the grid height array in the X and Z directions. Both xDimension and zDimension shall be greater than or equal to zero.
282 // If either the xDimension or the zDimension is less than two, the ElevationGrid contains no quadrilaterals.
ParseNode_Geometry3D_ElevationGrid()283 void X3DImporter::ParseNode_Geometry3D_ElevationGrid()
284 {
285     std::string use, def;
286     bool ccw = true;
287     bool colorPerVertex = true;
288     float creaseAngle = 0;
289     std::vector<float> height;
290     bool normalPerVertex = true;
291     bool solid = true;
292     int32_t xDimension = 0;
293     float xSpacing = 1;
294     int32_t zDimension = 0;
295     float zSpacing = 1;
296     CX3DImporter_NodeElement* ne( nullptr );
297 
298 	MACRO_ATTRREAD_LOOPBEG;
299 		MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use);
300 		MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool);
301 		MACRO_ATTRREAD_CHECK_RET("ccw", ccw, XML_ReadNode_GetAttrVal_AsBool);
302 		MACRO_ATTRREAD_CHECK_RET("colorPerVertex", colorPerVertex, XML_ReadNode_GetAttrVal_AsBool);
303 		MACRO_ATTRREAD_CHECK_RET("normalPerVertex", normalPerVertex, XML_ReadNode_GetAttrVal_AsBool);
304 		MACRO_ATTRREAD_CHECK_RET("creaseAngle", creaseAngle, XML_ReadNode_GetAttrVal_AsFloat);
305 		MACRO_ATTRREAD_CHECK_REF("height", height, XML_ReadNode_GetAttrVal_AsArrF);
306 		MACRO_ATTRREAD_CHECK_RET("xDimension", xDimension, XML_ReadNode_GetAttrVal_AsI32);
307 		MACRO_ATTRREAD_CHECK_RET("xSpacing", xSpacing, XML_ReadNode_GetAttrVal_AsFloat);
308 		MACRO_ATTRREAD_CHECK_RET("zDimension", zDimension, XML_ReadNode_GetAttrVal_AsI32);
309 		MACRO_ATTRREAD_CHECK_RET("zSpacing", zSpacing, XML_ReadNode_GetAttrVal_AsFloat);
310 	MACRO_ATTRREAD_LOOPEND;
311 
312 	// if "USE" defined then find already defined element.
313 	if(!use.empty())
314 	{
315 		MACRO_USE_CHECKANDAPPLY(def, use, ENET_ElevationGrid, ne);
316 	}
317 	else
318 	{
319 		if((xSpacing == 0.0f) || (zSpacing == 0.0f)) throw DeadlyImportError("Spacing in <ElevationGrid> must be grater than zero.");
320 		if((xDimension <= 0) || (zDimension <= 0)) throw DeadlyImportError("Dimension in <ElevationGrid> must be grater than zero.");
321 		if((size_t)(xDimension * zDimension) != height.size()) Throw_IncorrectAttrValue("Heights count must be equal to \"xDimension * zDimension\"");
322 
323 		// create and if needed - define new geometry object.
324 		ne = new CX3DImporter_NodeElement_ElevationGrid(CX3DImporter_NodeElement::ENET_ElevationGrid, NodeElement_Cur);
325 		if(!def.empty()) ne->ID = def;
326 
327 		CX3DImporter_NodeElement_ElevationGrid& grid_alias = *((CX3DImporter_NodeElement_ElevationGrid*)ne);// create alias for conveience
328 
329 		{// create grid vertices list
330 			std::vector<float>::const_iterator he_it = height.begin();
331 
332 			for(int32_t zi = 0; zi < zDimension; zi++)// rows
333 			{
334 				for(int32_t xi = 0; xi < xDimension; xi++)// columns
335 				{
336 					aiVector3D tvec(xSpacing * xi, *he_it, zSpacing * zi);
337 
338 					grid_alias.Vertices.push_back(tvec);
339 					++he_it;
340 				}
341 			}
342 		}// END: create grid vertices list
343 		//
344 		// create faces list. In "coordIdx" format
345 		//
346 		// check if we have quads
347 		if((xDimension < 2) || (zDimension < 2))// only one element in dimension is set, create line set.
348 		{
349 			((CX3DImporter_NodeElement_ElevationGrid*)ne)->NumIndices = 2;// will be holded as line set.
350 			for(size_t i = 0, i_e = (grid_alias.Vertices.size() - 1); i < i_e; i++)
351 			{
352 				grid_alias.CoordIdx.push_back(static_cast<int32_t>(i));
353 				grid_alias.CoordIdx.push_back(static_cast<int32_t>(i + 1));
354 				grid_alias.CoordIdx.push_back(-1);
355 			}
356 		}
357 		else// two or more elements in every dimension is set. create quad set.
358 		{
359 			((CX3DImporter_NodeElement_ElevationGrid*)ne)->NumIndices = 4;
360 			for(int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++)// rows
361 			{
362 				for(int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++)// columns
363 				{
364 					// points direction in face.
365 					if(ccw)
366 					{
367 						// CCW:
368 						//	3 2
369 						//	0 1
370 						grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi);
371 						grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1));
372 						grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1));
373 						grid_alias.CoordIdx.push_back(fzi * xDimension + fxi);
374 					}
375 					else
376 					{
377 						// CW:
378 						//	0 1
379 						//	3 2
380 						grid_alias.CoordIdx.push_back(fzi * xDimension + fxi);
381 						grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1));
382 						grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1));
383 						grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi);
384 					}// if(ccw) else
385 
386 					grid_alias.CoordIdx.push_back(-1);
387 				}// for(int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++)
388 			}// for(int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++)
389 		}// if((xDimension < 2) || (zDimension < 2)) else
390 
391 		grid_alias.ColorPerVertex = colorPerVertex;
392 		grid_alias.NormalPerVertex = normalPerVertex;
393 		grid_alias.CreaseAngle = creaseAngle;
394 		grid_alias.Solid = solid;
395         // check for child nodes
396         if(!mReader->isEmptyElement())
397         {
398 			ParseHelper_Node_Enter(ne);
399 			MACRO_NODECHECK_LOOPBEGIN("ElevationGrid");
400 				// check for X3DComposedGeometryNodes
401 				if(XML_CheckNode_NameEqual("Color")) { ParseNode_Rendering_Color(); continue; }
402 				if(XML_CheckNode_NameEqual("ColorRGBA")) { ParseNode_Rendering_ColorRGBA(); continue; }
403 				if(XML_CheckNode_NameEqual("Normal")) { ParseNode_Rendering_Normal(); continue; }
404 				if(XML_CheckNode_NameEqual("TextureCoordinate")) { ParseNode_Texturing_TextureCoordinate(); continue; }
405 				// check for X3DMetadataObject
406 				if(!ParseHelper_CheckRead_X3DMetadataObject()) XML_CheckNode_SkipUnsupported("ElevationGrid");
407 
408 			MACRO_NODECHECK_LOOPEND("ElevationGrid");
409 			ParseHelper_Node_Exit();
410 		}// if(!mReader->isEmptyElement())
411 		else
412 		{
413 			NodeElement_Cur->Child.push_back(ne);// add made object as child to current element
414 		}// if(!mReader->isEmptyElement()) else
415 
416 		NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph
417 	}// if(!use.empty()) else
418 }
419 
420 template<typename TVector>
GeometryHelper_Extrusion_CurveIsClosed(std::vector<TVector> & pCurve,const bool pDropTail,const bool pRemoveLastPoint,bool & pCurveIsClosed)421 static void GeometryHelper_Extrusion_CurveIsClosed(std::vector<TVector>& pCurve, const bool pDropTail, const bool pRemoveLastPoint, bool& pCurveIsClosed)
422 {
423     size_t cur_sz = pCurve.size();
424 
425 	pCurveIsClosed = false;
426 	// for curve with less than four points checking is have no sense,
427 	if(cur_sz < 4) return;
428 
429 	for(size_t s = 3, s_e = cur_sz; s < s_e; s++)
430 	{
431 		// search for first point of duplicated part.
432 		if(pCurve[0] == pCurve[s])
433 		{
434 			bool found = true;
435 
436 			// check if tail(indexed by b2) is duplicate of head(indexed by b1).
437 			for(size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++)
438 			{
439 				if(pCurve[b1] != pCurve[b2])
440 				{// points not match: clear flag and break loop.
441 					found = false;
442 
443 					break;
444 				}
445 			}// for(size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++)
446 
447 			// if duplicate tail is found then drop or not it depending on flags.
448 			if(found)
449 			{
450 				pCurveIsClosed = true;
451 				if(pDropTail)
452 				{
453 					if(!pRemoveLastPoint) s++;// prepare value for iterator's arithmetics.
454 
455 					pCurve.erase(pCurve.begin() + s, pCurve.end());// remove tail
456 				}
457 
458 				break;
459 			}// if(found)
460 		}// if(pCurve[0] == pCurve[s])
461 	}// for(size_t s = 3, s_e = (cur_sz - 1); s < s_e; s++)
462 }
463 
GeometryHelper_Extrusion_GetNextY(const size_t pSpine_PointIdx,const std::vector<aiVector3D> & pSpine,const bool pSpine_Closed)464 static aiVector3D GeometryHelper_Extrusion_GetNextY(const size_t pSpine_PointIdx, const std::vector<aiVector3D>& pSpine, const bool pSpine_Closed)
465 {
466     const size_t spine_idx_last = pSpine.size() - 1;
467     aiVector3D tvec;
468 
469 	if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last))// at first special cases
470 	{
471 		if(pSpine_Closed)
472 		{// If the spine curve is closed: The SCP for the first and last points is the same and is found using (spine[1] - spine[n - 2]) to compute the Y-axis.
473 			// As we even for closed spine curve last and first point in pSpine are not the same: duplicates(spine[n - 1] which are equivalent to spine[0])
474 			// in tail are removed.
475 			// So, last point in pSpine is a spine[n - 2]
476 			tvec = pSpine[1] - pSpine[spine_idx_last];
477 		}
478 		else if(pSpine_PointIdx == 0)
479 		{// The Y-axis used for the first point is the vector from spine[0] to spine[1]
480 			tvec = pSpine[1] - pSpine[0];
481 		}
482 		else
483 		{// The Y-axis used for the last point it is the vector from spine[n-2] to spine[n-1]. In our case(see above about dropping tail) spine[n - 1] is
484 			// the spine[0].
485 			tvec = pSpine[spine_idx_last] - pSpine[spine_idx_last - 1];
486 		}
487 	}// if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last))
488 	else
489 	{// For all points other than the first or last: The Y-axis for spine[i] is found by normalizing the vector defined by (spine[i+1] - spine[i-1]).
490 		tvec = pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx - 1];
491 	}// if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) else
492 
493 	return tvec.Normalize();
494 }
495 
GeometryHelper_Extrusion_GetNextZ(const size_t pSpine_PointIdx,const std::vector<aiVector3D> & pSpine,const bool pSpine_Closed,const aiVector3D pVecZ_Prev)496 static aiVector3D GeometryHelper_Extrusion_GetNextZ(const size_t pSpine_PointIdx, const std::vector<aiVector3D>& pSpine, const bool pSpine_Closed,
497 													const aiVector3D pVecZ_Prev)
498 {
499     const aiVector3D zero_vec(0);
500     const size_t spine_idx_last = pSpine.size() - 1;
501 
502     aiVector3D tvec;
503 
504 	// at first special cases
505 	if(pSpine.size() < 3)// spine have not enough points for vector calculations.
506 	{
507 		tvec.Set(0, 0, 1);
508 	}
509 	else if(pSpine_PointIdx == 0)// special case: first point
510 	{
511 		if(pSpine_Closed)// for calculating use previous point in curve s[n - 2]. In list it's a last point, because point s[n - 1] was removed as duplicate.
512 		{
513 			tvec = (pSpine[1] - pSpine[0]) ^ (pSpine[spine_idx_last] - pSpine[0]);
514 		}
515 		else // for not closed curve first and next point(s[0] and s[1]) has the same vector Z.
516 		{
517 			bool found = false;
518 
519 			// As said: "If the Z-axis of the first point is undefined (because the spine is not closed and the first two spine segments are collinear)
520 			// then the Z-axis for the first spine point with a defined Z-axis is used."
521 			// Walk through spine and find Z.
522 			for(size_t next_point = 2; (next_point <= spine_idx_last) && !found; next_point++)
523 			{
524 				// (pSpine[2] - pSpine[1]) ^ (pSpine[0] - pSpine[1])
525 				tvec = (pSpine[next_point] - pSpine[next_point - 1]) ^ (pSpine[next_point - 2] - pSpine[next_point - 1]);
526 				found = !tvec.Equal(zero_vec);
527 			}
528 
529 			// if entire spine are collinear then use OZ axis.
530 			if(!found) tvec.Set(0, 0, 1);
531 		}// if(pSpine_Closed) else
532 	}// else if(pSpine_PointIdx == 0)
533 	else if(pSpine_PointIdx == spine_idx_last)// special case: last point
534 	{
535 		if(pSpine_Closed)
536 		{// do not forget that real last point s[n - 1] is removed as duplicated. And in this case we are calculating vector Z for point s[n - 2].
537 			tvec = (pSpine[0] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]);
538 			// if taken spine vectors are collinear then use previous vector Z.
539 			if(tvec.Equal(zero_vec)) tvec = pVecZ_Prev;
540 		}
541 		else
542 		{// vector Z for last point of not closed curve is previous vector Z.
543 			tvec = pVecZ_Prev;
544 		}
545 	}
546 	else// regular point
547 	{
548 		tvec = (pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]);
549 		// if taken spine vectors are collinear then use previous vector Z.
550 		if(tvec.Equal(zero_vec)) tvec = pVecZ_Prev;
551 	}
552 
553 	// After determining the Z-axis, its dot product with the Z-axis of the previous spine point is computed. If this value is negative, the Z-axis
554 	// is flipped (multiplied by -1).
555 	if((tvec * pVecZ_Prev) < 0) tvec = -tvec;
556 
557 	return tvec.Normalize();
558 }
559 
560 // <Extrusion
561 // DEF=""                                 ID
562 // USE=""                                 IDREF
563 // beginCap="true"                        SFBool     [initializeOnly]
564 // ccw="true"                             SFBool     [initializeOnly]
565 // convex="true"                          SFBool     [initializeOnly]
566 // creaseAngle="0.0"                      SFloat     [initializeOnly]
567 // crossSection="1 1 1 -1 -1 -1 -1 1 1 1" MFVec2f    [initializeOnly]
568 // endCap="true"                          SFBool     [initializeOnly]
569 // orientation="0 0 1 0"                  MFRotation [initializeOnly]
570 // scale="1 1"                            MFVec2f    [initializeOnly]
571 // solid="true"                           SFBool     [initializeOnly]
572 // spine="0 0 0 0 1 0"                    MFVec3f    [initializeOnly]
573 // />
ParseNode_Geometry3D_Extrusion()574 void X3DImporter::ParseNode_Geometry3D_Extrusion()
575 {
576     std::string use, def;
577     bool beginCap = true;
578     bool ccw = true;
579     bool convex = true;
580     float creaseAngle = 0;
581     std::vector<aiVector2D> crossSection;
582     bool endCap = true;
583     std::vector<float> orientation;
584     std::vector<aiVector2D> scale;
585     bool solid = true;
586     std::vector<aiVector3D> spine;
587     CX3DImporter_NodeElement* ne( nullptr );
588 
589 	MACRO_ATTRREAD_LOOPBEG;
590 		MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use);
591 		MACRO_ATTRREAD_CHECK_RET("beginCap", beginCap, XML_ReadNode_GetAttrVal_AsBool);
592 		MACRO_ATTRREAD_CHECK_RET("ccw", ccw, XML_ReadNode_GetAttrVal_AsBool);
593 		MACRO_ATTRREAD_CHECK_RET("convex", convex, XML_ReadNode_GetAttrVal_AsBool);
594 		MACRO_ATTRREAD_CHECK_RET("creaseAngle", creaseAngle, XML_ReadNode_GetAttrVal_AsFloat);
595 		MACRO_ATTRREAD_CHECK_REF("crossSection", crossSection, XML_ReadNode_GetAttrVal_AsArrVec2f);
596 		MACRO_ATTRREAD_CHECK_RET("endCap", endCap, XML_ReadNode_GetAttrVal_AsBool);
597 		MACRO_ATTRREAD_CHECK_REF("orientation", orientation, XML_ReadNode_GetAttrVal_AsArrF);
598 		MACRO_ATTRREAD_CHECK_REF("scale", scale, XML_ReadNode_GetAttrVal_AsArrVec2f);
599 		MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool);
600 		MACRO_ATTRREAD_CHECK_REF("spine", spine, XML_ReadNode_GetAttrVal_AsArrVec3f);
601 	MACRO_ATTRREAD_LOOPEND;
602 
603 	// if "USE" defined then find already defined element.
604 	if(!use.empty())
605 	{
606 		MACRO_USE_CHECKANDAPPLY(def, use, ENET_Extrusion, ne);
607 	}
608 	else
609 	{
610 		//
611 		// check if default values must be assigned
612 		//
613 		if(spine.size() == 0)
614 		{
615 			spine.resize(2);
616 			spine[0].Set(0, 0, 0), spine[1].Set(0, 1, 0);
617 		}
618 		else if(spine.size() == 1)
619 		{
620 			throw DeadlyImportError("ParseNode_Geometry3D_Extrusion. Spine must have at least two points.");
621 		}
622 
623 		if(crossSection.size() == 0)
624 		{
625 			crossSection.resize(5);
626 			crossSection[0].Set(1, 1), crossSection[1].Set(1, -1), crossSection[2].Set(-1, -1), crossSection[3].Set(-1, 1), crossSection[4].Set(1, 1);
627 		}
628 
629 		{// orientation
630 			size_t ori_size = orientation.size() / 4;
631 
632 			if(ori_size < spine.size())
633 			{
634 				float add_ori[4];// values that will be added
635 
636 				if(ori_size == 1)// if "orientation" has one element(means one MFRotation with four components) then use it value for all spine points.
637 				{
638 					add_ori[0] = orientation[0], add_ori[1] = orientation[1], add_ori[2] = orientation[2], add_ori[3] = orientation[3];
639 				}
640 				else// else - use default values
641 				{
642 					add_ori[0] = 0, add_ori[1] = 0, add_ori[2] = 1, add_ori[3] = 0;
643 				}
644 
645 				orientation.reserve(spine.size() * 4);
646 				for(size_t i = 0, i_e = (spine.size() - ori_size); i < i_e; i++)
647 					orientation.push_back(add_ori[0]), orientation.push_back(add_ori[1]), orientation.push_back(add_ori[2]), orientation.push_back(add_ori[3]);
648 			}
649 
650 			if(orientation.size() % 4) throw DeadlyImportError("Attribute \"orientation\" in <Extrusion> must has multiple four quantity of numbers.");
651 		}// END: orientation
652 
653 		{// scale
654 			if(scale.size() < spine.size())
655 			{
656 				aiVector2D add_sc;
657 
658 				if(scale.size() == 1)// if "scale" has one element then use it value for all spine points.
659 					add_sc = scale[0];
660 				else// else - use default values
661 					add_sc.Set(1, 1);
662 
663 				scale.reserve(spine.size());
664 				for(size_t i = 0, i_e = (spine.size() - scale.size()); i < i_e; i++) scale.push_back(add_sc);
665 			}
666 		}// END: scale
667 		//
668 		// create and if needed - define new geometry object.
669 		//
670 		ne = new CX3DImporter_NodeElement_IndexedSet(CX3DImporter_NodeElement::ENET_Extrusion, NodeElement_Cur);
671 		if(!def.empty()) ne->ID = def;
672 
673 		CX3DImporter_NodeElement_IndexedSet& ext_alias = *((CX3DImporter_NodeElement_IndexedSet*)ne);// create alias for conveience
674 		// assign part of input data
675 		ext_alias.CCW = ccw;
676 		ext_alias.Convex = convex;
677 		ext_alias.CreaseAngle = creaseAngle;
678 		ext_alias.Solid = solid;
679 
680 		//
681 		// How we done it at all?
682 		// 1. At first we will calculate array of basises for every point in spine(look SCP in ISO-dic). Also "orientation" vector
683 		// are applied vor every basis.
684 		// 2. After that we can create array of point sets: which are scaled, transferred to basis of relative basis and at final translated to real position
685 		// using relative spine point.
686 		// 3. Next step is creating CoordIdx array(do not forget "-1" delimiter). While creating CoordIdx also created faces for begin and end caps, if
687 		// needed. While createing CootdIdx is taking in account CCW flag.
688 		// 4. The last step: create Vertices list.
689 		//
690         bool spine_closed;// flag: true if spine curve is closed.
691         bool cross_closed;// flag: true if cross curve is closed.
692         std::vector<aiMatrix3x3> basis_arr;// array of basises. ROW_a - X, ROW_b - Y, ROW_c - Z.
693         std::vector<std::vector<aiVector3D> > pointset_arr;// array of point sets: cross curves.
694 
695         // detect closed curves
696         GeometryHelper_Extrusion_CurveIsClosed(crossSection, true, true, cross_closed);// true - drop tail, true - remove duplicate end.
697         GeometryHelper_Extrusion_CurveIsClosed(spine, true, true, spine_closed);// true - drop tail, true - remove duplicate end.
698         // If both cap are requested and spine curve is closed then we can make only one cap. Because second cap will be the same surface.
699         if(spine_closed)
700         {
701 			beginCap |= endCap;
702 			endCap = false;
703 		}
704 
705         {// 1. Calculate array of basises.
706 			aiMatrix4x4 rotmat;
707 			aiVector3D vecX(0), vecY(0), vecZ(0);
708 
709 			basis_arr.resize(spine.size());
710 			for(size_t i = 0, i_e = spine.size(); i < i_e; i++)
711 			{
712 				aiVector3D tvec;
713 
714 				// get axises of basis.
715 				vecY = GeometryHelper_Extrusion_GetNextY(i, spine, spine_closed);
716 				vecZ = GeometryHelper_Extrusion_GetNextZ(i, spine, spine_closed, vecZ);
717 				vecX = (vecY ^ vecZ).Normalize();
718 				// get rotation matrix and apply "orientation" to basis
719 				aiMatrix4x4::Rotation(orientation[i * 4 + 3], aiVector3D(orientation[i * 4], orientation[i * 4 + 1], orientation[i * 4 + 2]), rotmat);
720 				tvec = vecX, tvec *= rotmat, basis_arr[i].a1 = tvec.x, basis_arr[i].a2 = tvec.y, basis_arr[i].a3 = tvec.z;
721 				tvec = vecY, tvec *= rotmat, basis_arr[i].b1 = tvec.x, basis_arr[i].b2 = tvec.y, basis_arr[i].b3 = tvec.z;
722 				tvec = vecZ, tvec *= rotmat, basis_arr[i].c1 = tvec.x, basis_arr[i].c2 = tvec.y, basis_arr[i].c3 = tvec.z;
723 			}// for(size_t i = 0, i_e = spine.size(); i < i_e; i++)
724 		}// END: 1. Calculate array of basises
725 
726 		{// 2. Create array of point sets.
727 			aiMatrix4x4 scmat;
728 			std::vector<aiVector3D> tcross(crossSection.size());
729 
730 			pointset_arr.resize(spine.size());
731 			for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++)
732 			{
733 				aiVector3D tc23vec;
734 
735 				tc23vec.Set(scale[spi].x, 0, scale[spi].y);
736 				aiMatrix4x4::Scaling(tc23vec, scmat);
737 				for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++)
738 				{
739 					aiVector3D tvecX, tvecY, tvecZ;
740 
741 					tc23vec.Set(crossSection[cri].x, 0, crossSection[cri].y);
742 					// apply scaling to point
743 					tcross[cri] = scmat * tc23vec;
744 					//
745 					// transfer point to new basis
746 					// calculate coordinate in new basis
747 					tvecX.Set(basis_arr[spi].a1, basis_arr[spi].a2, basis_arr[spi].a3), tvecX *= tcross[cri].x;
748 					tvecY.Set(basis_arr[spi].b1, basis_arr[spi].b2, basis_arr[spi].b3), tvecY *= tcross[cri].y;
749 					tvecZ.Set(basis_arr[spi].c1, basis_arr[spi].c2, basis_arr[spi].c3), tvecZ *= tcross[cri].z;
750 					// apply new coordinates and translate it to spine point.
751 					tcross[cri] = tvecX + tvecY + tvecZ + spine[spi];
752 				}// for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; i++)
753 
754 				pointset_arr[spi] = tcross;// store transferred point set
755 			}// for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; i++)
756 		}// END: 2. Create array of point sets.
757 
758 		{// 3. Create CoordIdx.
759 			// add caps if needed
760 			if(beginCap)
761 			{
762 				// add cap as polygon. vertices of cap are places at begin, so just add numbers from zero.
763 				for(size_t i = 0, i_e = crossSection.size(); i < i_e; i++) ext_alias.CoordIndex.push_back(static_cast<int32_t>(i));
764 
765 				// add delimiter
766 				ext_alias.CoordIndex.push_back(-1);
767 			}// if(beginCap)
768 
769 			if(endCap)
770 			{
771 				// add cap as polygon. vertices of cap are places at end, as for beginCap use just sequence of numbers but with offset.
772 				size_t beg = (pointset_arr.size() - 1) * crossSection.size();
773 
774 				for(size_t i = beg, i_e = (beg + crossSection.size()); i < i_e; i++) ext_alias.CoordIndex.push_back(static_cast<int32_t>(i));
775 
776 				// add delimiter
777 				ext_alias.CoordIndex.push_back(-1);
778 			}// if(beginCap)
779 
780 			// add quads
781 			for(size_t spi = 0, spi_e = (spine.size() - 1); spi <= spi_e; spi++)
782 			{
783 				const size_t cr_sz = crossSection.size();
784 				const size_t cr_last = crossSection.size() - 1;
785 
786 				size_t right_col;// hold index basis for points of quad placed in right column;
787 
788 				if(spi != spi_e)
789 					right_col = spi + 1;
790 				else if(spine_closed)// if spine curve is closed then one more quad is needed: between first and last points of curve.
791 					right_col = 0;
792 				else
793 					break;// if spine curve is not closed then break the loop, because spi is out of range for that type of spine.
794 
795 				for(size_t cri = 0; cri < cr_sz; cri++)
796 				{
797 					if(cri != cr_last)
798 					{
799 						MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex,
800 											static_cast<int32_t>(spi * cr_sz + cri),
801                                             static_cast<int32_t>(right_col * cr_sz + cri),
802                                             static_cast<int32_t>(right_col * cr_sz + cri + 1),
803                                             static_cast<int32_t>(spi * cr_sz + cri + 1));
804 						// add delimiter
805 						ext_alias.CoordIndex.push_back(-1);
806 					}
807 					else if(cross_closed)// if cross curve is closed then one more quad is needed: between first and last points of curve.
808 					{
809 						MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex,
810                                             static_cast<int32_t>(spi * cr_sz + cri),
811                                             static_cast<int32_t>(right_col * cr_sz + cri),
812                                             static_cast<int32_t>(right_col * cr_sz + 0),
813                                             static_cast<int32_t>(spi * cr_sz + 0));
814 						// add delimiter
815 						ext_alias.CoordIndex.push_back(-1);
816 					}
817 				}// for(size_t cri = 0; cri < cr_sz; cri++)
818 			}// for(size_t spi = 0, spi_e = (spine.size() - 2); spi < spi_e; spi++)
819 		}// END: 3. Create CoordIdx.
820 
821 		{// 4. Create vertices list.
822 			// just copy all vertices
823 			for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++)
824 			{
825 				for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++)
826 				{
827 					ext_alias.Vertices.push_back(pointset_arr[spi][cri]);
828 				}
829 			}
830 		}// END: 4. Create vertices list.
831 //PrintVectorSet("Ext. CoordIdx", ext_alias.CoordIndex);
832 //PrintVectorSet("Ext. Vertices", ext_alias.Vertices);
833 		// check for child nodes
834 		if(!mReader->isEmptyElement())
835 			ParseNode_Metadata(ne, "Extrusion");
836 		else
837 			NodeElement_Cur->Child.push_back(ne);// add made object as child to current element
838 
839 		NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph
840 	}// if(!use.empty()) else
841 }
842 
843 // <IndexedFaceSet
844 // DEF=""                         ID
845 // USE=""                         IDREF
846 // ccw="true"             SFBool  [initializeOnly]
847 // colorIndex=""          MFInt32 [initializeOnly]
848 // colorPerVertex="true"  SFBool  [initializeOnly]
849 // convex="true"          SFBool  [initializeOnly]
850 // coordIndex=""          MFInt32 [initializeOnly]
851 // creaseAngle="0"        SFFloat [initializeOnly]
852 // normalIndex=""         MFInt32 [initializeOnly]
853 // normalPerVertex="true" SFBool  [initializeOnly]
854 // solid="true"           SFBool  [initializeOnly]
855 // texCoordIndex=""       MFInt32 [initializeOnly]
856 // >
857 //    <!-- ComposedGeometryContentModel -->
858 // ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate,
859 // Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute,
860 // Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained.
861 // A ProtoInstance node (with the proper node type) can be substituted for any node in this content model.
862 // </IndexedFaceSet>
ParseNode_Geometry3D_IndexedFaceSet()863 void X3DImporter::ParseNode_Geometry3D_IndexedFaceSet()
864 {
865     std::string use, def;
866     bool ccw = true;
867     std::vector<int32_t> colorIndex;
868     bool colorPerVertex = true;
869     bool convex = true;
870     std::vector<int32_t> coordIndex;
871     float creaseAngle = 0;
872     std::vector<int32_t> normalIndex;
873     bool normalPerVertex = true;
874     bool solid = true;
875     std::vector<int32_t> texCoordIndex;
876     CX3DImporter_NodeElement* ne( nullptr );
877 
878 	MACRO_ATTRREAD_LOOPBEG;
879 		MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use);
880 		MACRO_ATTRREAD_CHECK_RET("ccw", ccw, XML_ReadNode_GetAttrVal_AsBool);
881 		MACRO_ATTRREAD_CHECK_REF("colorIndex", colorIndex, XML_ReadNode_GetAttrVal_AsArrI32);
882 		MACRO_ATTRREAD_CHECK_RET("colorPerVertex", colorPerVertex, XML_ReadNode_GetAttrVal_AsBool);
883 		MACRO_ATTRREAD_CHECK_RET("convex", convex, XML_ReadNode_GetAttrVal_AsBool);
884 		MACRO_ATTRREAD_CHECK_REF("coordIndex", coordIndex, XML_ReadNode_GetAttrVal_AsArrI32);
885 		MACRO_ATTRREAD_CHECK_RET("creaseAngle", creaseAngle, XML_ReadNode_GetAttrVal_AsFloat);
886 		MACRO_ATTRREAD_CHECK_REF("normalIndex", normalIndex, XML_ReadNode_GetAttrVal_AsArrI32);
887 		MACRO_ATTRREAD_CHECK_RET("normalPerVertex", normalPerVertex, XML_ReadNode_GetAttrVal_AsBool);
888 		MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool);
889 		MACRO_ATTRREAD_CHECK_REF("texCoordIndex", texCoordIndex, XML_ReadNode_GetAttrVal_AsArrI32);
890 	MACRO_ATTRREAD_LOOPEND;
891 
892 	// if "USE" defined then find already defined element.
893 	if(!use.empty())
894 	{
895 		MACRO_USE_CHECKANDAPPLY(def, use, ENET_IndexedFaceSet, ne);
896 	}
897 	else
898 	{
899 		// check data
900 		if(coordIndex.size() == 0) throw DeadlyImportError("IndexedFaceSet must contain not empty \"coordIndex\" attribute.");
901 
902 		// create and if needed - define new geometry object.
903 		ne = new CX3DImporter_NodeElement_IndexedSet(CX3DImporter_NodeElement::ENET_IndexedFaceSet, NodeElement_Cur);
904 		if(!def.empty()) ne->ID = def;
905 
906 		CX3DImporter_NodeElement_IndexedSet& ne_alias = *((CX3DImporter_NodeElement_IndexedSet*)ne);
907 
908 		ne_alias.CCW = ccw;
909 		ne_alias.ColorIndex = colorIndex;
910 		ne_alias.ColorPerVertex = colorPerVertex;
911 		ne_alias.Convex = convex;
912 		ne_alias.CoordIndex = coordIndex;
913 		ne_alias.CreaseAngle = creaseAngle;
914 		ne_alias.NormalIndex = normalIndex;
915 		ne_alias.NormalPerVertex = normalPerVertex;
916 		ne_alias.Solid = solid;
917 		ne_alias.TexCoordIndex = texCoordIndex;
918         // check for child nodes
919         if(!mReader->isEmptyElement())
920         {
921 			ParseHelper_Node_Enter(ne);
922 			MACRO_NODECHECK_LOOPBEGIN("IndexedFaceSet");
923 				// check for X3DComposedGeometryNodes
924 				if(XML_CheckNode_NameEqual("Color")) { ParseNode_Rendering_Color(); continue; }
925 				if(XML_CheckNode_NameEqual("ColorRGBA")) { ParseNode_Rendering_ColorRGBA(); continue; }
926 				if(XML_CheckNode_NameEqual("Coordinate")) { ParseNode_Rendering_Coordinate(); continue; }
927 				if(XML_CheckNode_NameEqual("Normal")) { ParseNode_Rendering_Normal(); continue; }
928 				if(XML_CheckNode_NameEqual("TextureCoordinate")) { ParseNode_Texturing_TextureCoordinate(); continue; }
929 				// check for X3DMetadataObject
930 				if(!ParseHelper_CheckRead_X3DMetadataObject()) XML_CheckNode_SkipUnsupported("IndexedFaceSet");
931 
932 			MACRO_NODECHECK_LOOPEND("IndexedFaceSet");
933 			ParseHelper_Node_Exit();
934 		}// if(!mReader->isEmptyElement())
935 		else
936 		{
937 			NodeElement_Cur->Child.push_back(ne);// add made object as child to current element
938 		}
939 
940 		NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph
941 	}// if(!use.empty()) else
942 }
943 
944 // <Sphere
945 // DEF=""       ID
946 // USE=""       IDREF
947 // radius="1"   SFloat [initializeOnly]
948 // solid="true" SFBool [initializeOnly]
949 // />
ParseNode_Geometry3D_Sphere()950 void X3DImporter::ParseNode_Geometry3D_Sphere()
951 {
952     std::string use, def;
953     ai_real radius = 1;
954     bool solid = true;
955     CX3DImporter_NodeElement* ne( nullptr );
956 
957 	MACRO_ATTRREAD_LOOPBEG;
958 		MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use);
959 		MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat);
960 		MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool);
961 	MACRO_ATTRREAD_LOOPEND;
962 
963 	// if "USE" defined then find already defined element.
964 	if(!use.empty())
965 	{
966 		MACRO_USE_CHECKANDAPPLY(def, use, ENET_Sphere, ne);
967 	}
968 	else
969 	{
970 		const unsigned int tess = 3;///TODO: IME tessellation factor through ai_property
971 
972 		std::vector<aiVector3D> tlist;
973 
974 		// create and if needed - define new geometry object.
975 		ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Sphere, NodeElement_Cur);
976 		if(!def.empty()) ne->ID = def;
977 
978 		StandardShapes::MakeSphere(tess, tlist);
979 		// copy data from temp array and apply scale
980 		for(std::vector<aiVector3D>::iterator it = tlist.begin(); it != tlist.end(); ++it)
981 		{
982 			((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices.push_back(*it * radius);
983 		}
984 
985 		((CX3DImporter_NodeElement_Geometry3D*)ne)->Solid = solid;
986 		((CX3DImporter_NodeElement_Geometry3D*)ne)->NumIndices = 3;
987 		// check for X3DMetadataObject childs.
988 		if(!mReader->isEmptyElement())
989 			ParseNode_Metadata(ne, "Sphere");
990 		else
991 			NodeElement_Cur->Child.push_back(ne);// add made object as child to current element
992 
993 		NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph
994 	}// if(!use.empty()) else
995 }
996 
997 }// namespace Assimp
998 
999 #endif // !ASSIMP_BUILD_NO_X3D_IMPORTER
1000