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