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