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