/// \file X3DImporter_Geometry3D.cpp
/// \brief Parsing data from nodes of "Geometry3D" set of X3D.
/// \date 2015-2016
/// \author nevorek@gmail.com
#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER
#include "X3DImporter.hpp"
#include "X3DImporter_Macro.hpp"
#include "StandardShapes.h"
namespace Assimp
{
//
// 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.
// 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
// respectively and each component value shall be greater than zero.
void X3DImporter::ParseNode_Geometry3D_Box()
{
std::string def, use;
bool solid = true;
aiVector3D size(2, 2, 2);
CX3DImporter_NodeElement* ne;
MACRO_ATTRREAD_LOOPBEG;
MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use);
MACRO_ATTRREAD_CHECK_REF("size", size, XML_ReadNode_GetAttrVal_AsVec3f);
MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_LOOPEND;
// if "USE" defined then find already defined element.
if(!use.empty())
{
MACRO_USE_CHECKANDAPPLY(def, use, ENET_Box, ne);
}
else
{
// create and if needed - define new geometry object.
ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Box, NodeElement_Cur);
if(!def.empty()) ne->ID = def;
GeometryHelper_MakeQL_RectParallelepiped(size, ((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices);// get quad list
((CX3DImporter_NodeElement_Geometry3D*)ne)->Solid = solid;
((CX3DImporter_NodeElement_Geometry3D*)ne)->NumIndices = 4;
// check for X3DMetadataObject childs.
if(!mReader->isEmptyElement())
ParseNode_Metadata(ne, "Box");
else
NodeElement_Cur->Child.push_back(ne);// add made object as child to current element
NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph
}// if(!use.empty()) else
}
//
void X3DImporter::ParseNode_Geometry3D_Cone()
{
std::string use, def;
bool bottom = true;
float bottomRadius = 1;
float height = 2;
bool side = true;
bool solid = true;
CX3DImporter_NodeElement* ne;
MACRO_ATTRREAD_LOOPBEG;
MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use);
MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("side", side, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("bottom", bottom, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("height", height, XML_ReadNode_GetAttrVal_AsFloat);
MACRO_ATTRREAD_CHECK_RET("bottomRadius", bottomRadius, XML_ReadNode_GetAttrVal_AsFloat);
MACRO_ATTRREAD_LOOPEND;
// if "USE" defined then find already defined element.
if(!use.empty())
{
MACRO_USE_CHECKANDAPPLY(def, use, ENET_Cone, ne);
}
else
{
const unsigned int tess = 30;///TODO: IME tesselation factor thru ai_property
std::vector tvec;// temp array for vertices.
// create and if needed - define new geometry object.
ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Cone, NodeElement_Cur);
if(!def.empty()) ne->ID = def;
// make cone or parts according to flags.
if(side)
{
StandardShapes::MakeCone(height, 0, bottomRadius, tess, tvec, !bottom);
}
else if(bottom)
{
StandardShapes::MakeCircle(bottomRadius, tess, tvec);
height = -(height / 2);
for(std::vector::iterator it = tvec.begin(); it != tvec.end(); it++) it->y = height;// y - because circle made in oXZ.
}
// copy data from temp array
for(std::vector::iterator it = tvec.begin(); it != tvec.end(); it++) ((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices.push_back(*it);
((CX3DImporter_NodeElement_Geometry3D*)ne)->Solid = solid;
((CX3DImporter_NodeElement_Geometry3D*)ne)->NumIndices = 3;
// check for X3DMetadataObject childs.
if(!mReader->isEmptyElement())
ParseNode_Metadata(ne, "Cone");
else
NodeElement_Cur->Child.push_back(ne);// add made object as child to current element
NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph
}// if(!use.empty()) else
}
//
void X3DImporter::ParseNode_Geometry3D_Cylinder()
{
std::string use, def;
bool bottom = true;
float height = 2;
float radius = 1;
bool side = true;
bool solid = true;
bool top = true;
CX3DImporter_NodeElement* ne;
MACRO_ATTRREAD_LOOPBEG;
MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use);
MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat);
MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("bottom", bottom, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("top", top, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("side", side, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("height", height, XML_ReadNode_GetAttrVal_AsFloat);
MACRO_ATTRREAD_LOOPEND;
// if "USE" defined then find already defined element.
if(!use.empty())
{
MACRO_USE_CHECKANDAPPLY(def, use, ENET_Cylinder, ne);
}
else
{
const unsigned int tess = 30;///TODO: IME tesselation factor thru ai_property
std::vector tside;// temp array for vertices of side.
std::vector tcir;// temp array for vertices of circle.
// create and if needed - define new geometry object.
ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Cylinder, NodeElement_Cur);
if(!def.empty()) ne->ID = def;
// make cilynder or parts according to flags.
if(side) StandardShapes::MakeCone(height, radius, radius, tess, tside, true);
height /= 2;// height defined for whole cylinder, when creating top and bottom circle we are using just half of height.
if(top || bottom) StandardShapes::MakeCircle(radius, tess, tcir);
// copy data from temp arrays
std::list& vlist = ((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices;// just short alias.
for(std::vector::iterator it = tside.begin(); it != tside.end(); it++) vlist.push_back(*it);
if(top)
{
for(std::vector::iterator it = tcir.begin(); it != tcir.end(); it++)
{
(*it).y = height;// y - because circle made in oXZ.
vlist.push_back(*it);
}
}// if(top)
if(bottom)
{
for(std::vector::iterator it = tcir.begin(); it != tcir.end(); it++)
{
(*it).y = -height;// y - because circle made in oXZ.
vlist.push_back(*it);
}
}// if(top)
((CX3DImporter_NodeElement_Geometry3D*)ne)->Solid = solid;
((CX3DImporter_NodeElement_Geometry3D*)ne)->NumIndices = 3;
// check for X3DMetadataObject childs.
if(!mReader->isEmptyElement())
ParseNode_Metadata(ne, "Cylinder");
else
NodeElement_Cur->Child.push_back(ne);// add made object as child to current element
NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph
}// if(!use.empty()) else
}
//
//
// ColorNormalTexCoordContentModel can contain Color (or ColorRGBA), Normal and TextureCoordinate, in any order. No more than one instance of any single
// node type is allowed. A ProtoInstance node (with the proper node type) can be substituted for any node in this content model.
//
// 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
// 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
// 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.
// If either the xDimension or the zDimension is less than two, the ElevationGrid contains no quadrilaterals.
void X3DImporter::ParseNode_Geometry3D_ElevationGrid()
{
std::string use, def;
bool ccw = true;
bool colorPerVertex = true;
float creaseAngle = 0;
std::list height;
bool normalPerVertex = true;
bool solid = true;
int32_t xDimension = 0;
float xSpacing = 1;
int32_t zDimension = 0;
float zSpacing = 1;
CX3DImporter_NodeElement* ne;
MACRO_ATTRREAD_LOOPBEG;
MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use);
MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("ccw", ccw, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("colorPerVertex", colorPerVertex, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("normalPerVertex", normalPerVertex, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("creaseAngle", creaseAngle, XML_ReadNode_GetAttrVal_AsFloat);
MACRO_ATTRREAD_CHECK_REF("height", height, XML_ReadNode_GetAttrVal_AsListF);
MACRO_ATTRREAD_CHECK_RET("xDimension", xDimension, XML_ReadNode_GetAttrVal_AsI32);
MACRO_ATTRREAD_CHECK_RET("xSpacing", xSpacing, XML_ReadNode_GetAttrVal_AsFloat);
MACRO_ATTRREAD_CHECK_RET("zDimension", zDimension, XML_ReadNode_GetAttrVal_AsI32);
MACRO_ATTRREAD_CHECK_RET("zSpacing", zSpacing, XML_ReadNode_GetAttrVal_AsFloat);
MACRO_ATTRREAD_LOOPEND;
// if "USE" defined then find already defined element.
if(!use.empty())
{
MACRO_USE_CHECKANDAPPLY(def, use, ENET_ElevationGrid, ne);
}
else
{
if((xSpacing == 0.0f) || (zSpacing == 0.0f)) throw DeadlyImportError("Spacing in must be grater than zero.");
if((xDimension <= 0) || (zDimension <= 0)) throw DeadlyImportError("Dimension in must be grater than zero.");
if((size_t)(xDimension * zDimension) != height.size()) Throw_IncorrectAttrValue("Heights count must be equal to \"xDimension * zDimension\"");
// create and if needed - define new geometry object.
ne = new CX3DImporter_NodeElement_ElevationGrid(CX3DImporter_NodeElement::ENET_ElevationGrid, NodeElement_Cur);
if(!def.empty()) ne->ID = def;
CX3DImporter_NodeElement_ElevationGrid& grid_alias = *((CX3DImporter_NodeElement_ElevationGrid*)ne);// create alias for conveience
{// create grid vertices list
std::list::const_iterator he_it = height.begin();
for(int32_t zi = 0; zi < zDimension; zi++)// rows
{
for(int32_t xi = 0; xi < xDimension; xi++)// columns
{
aiVector3D tvec(xSpacing * xi, *he_it, zSpacing * zi);
grid_alias.Vertices.push_back(tvec);
he_it++;
}
}
}// END: create grid vertices list
//
// create faces list. In "coordIdx" format
//
// check if we have quads
if((xDimension < 2) || (zDimension < 2))// only one element in dimension is set, create line set.
{
((CX3DImporter_NodeElement_ElevationGrid*)ne)->NumIndices = 2;// will be holded as line set.
for(size_t i = 0, i_e = (grid_alias.Vertices.size() - 1); i < i_e; i++)
{
grid_alias.CoordIdx.push_back(i);
grid_alias.CoordIdx.push_back(i + 1);
grid_alias.CoordIdx.push_back(-1);
}
}
else// two or more elements in every dimension is set. create quad set.
{
((CX3DImporter_NodeElement_ElevationGrid*)ne)->NumIndices = 4;
for(int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++)// rows
{
for(int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++)// columns
{
// points direction in face.
if(ccw)
{
// CCW:
// 3 2
// 0 1
grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi);
grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1));
grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1));
grid_alias.CoordIdx.push_back(fzi * xDimension + fxi);
}
else
{
// CW:
// 0 1
// 3 2
grid_alias.CoordIdx.push_back(fzi * xDimension + fxi);
grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1));
grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1));
grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi);
}// if(ccw) else
grid_alias.CoordIdx.push_back(-1);
}// for(int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++)
}// for(int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++)
}// if((xDimension < 2) || (zDimension < 2)) else
grid_alias.ColorPerVertex = colorPerVertex;
grid_alias.NormalPerVertex = normalPerVertex;
grid_alias.CreaseAngle = creaseAngle;
grid_alias.Solid = solid;
// check for child nodes
if(!mReader->isEmptyElement())
{
ParseHelper_Node_Enter(ne);
MACRO_NODECHECK_LOOPBEGIN("ElevationGrid");
// check for X3DComposedGeometryNodes
if(XML_CheckNode_NameEqual("Color")) { ParseNode_Rendering_Color(); continue; }
if(XML_CheckNode_NameEqual("ColorRGBA")) { ParseNode_Rendering_ColorRGBA(); continue; }
if(XML_CheckNode_NameEqual("Normal")) { ParseNode_Rendering_Normal(); continue; }
if(XML_CheckNode_NameEqual("TextureCoordinate")) { ParseNode_Texturing_TextureCoordinate(); continue; }
// check for X3DMetadataObject
if(!ParseHelper_CheckRead_X3DMetadataObject()) XML_CheckNode_SkipUnsupported("ElevationGrid");
MACRO_NODECHECK_LOOPEND("ElevationGrid");
ParseHelper_Node_Exit();
}// if(!mReader->isEmptyElement())
else
{
NodeElement_Cur->Child.push_back(ne);// add made object as child to current element
}// if(!mReader->isEmptyElement()) else
NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph
}// if(!use.empty()) else
}
template
static void GeometryHelper_Extrusion_CurveIsClosed(std::vector& pCurve, const bool pDropTail, const bool pRemoveLastPoint, bool& pCurveIsClosed)
{
size_t cur_sz = pCurve.size();
pCurveIsClosed = false;
// for curve with less than four points checking is have no sense,
if(cur_sz < 4) return;
for(size_t s = 3, s_e = cur_sz; s < s_e; s++)
{
// search for first point of duplicated part.
if(pCurve[0] == pCurve[s])
{
bool found = true;
// check if tail(indexed by b2) is duplicate of head(indexed by b1).
for(size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++)
{
if(pCurve[b1] != pCurve[b2])
{// points not match: clear flag and break loop.
found = false;
break;
}
}// for(size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++)
// if duplicate tail is found then drop or not it depending on flags.
if(found)
{
pCurveIsClosed = true;
if(pDropTail)
{
if(!pRemoveLastPoint) s++;// prepare value for iterator's arithmetics.
pCurve.erase(pCurve.begin() + s, pCurve.end());// remove tail
}
break;
}// if(found)
}// if(pCurve[0] == pCurve[s])
}// for(size_t s = 3, s_e = (cur_sz - 1); s < s_e; s++)
}
static aiVector3D GeometryHelper_Extrusion_GetNextY(const size_t pSpine_PointIdx, const std::vector& pSpine, const bool pSpine_Closed)
{
const size_t spine_idx_last = pSpine.size() - 1;
aiVector3D tvec;
if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last))// at first special cases
{
if(pSpine_Closed)
{// 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.
// 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])
// in tail are removed.
// So, last point in pSpine is a spine[n - 2]
tvec = pSpine[1] - pSpine[spine_idx_last];
}
else if(pSpine_PointIdx == 0)
{// The Y-axis used for the first point is the vector from spine[0] to spine[1]
tvec = pSpine[1] - pSpine[0];
}
else
{// 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
// the spine[0].
tvec = pSpine[spine_idx_last] - pSpine[spine_idx_last - 1];
}
}// if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last))
else
{// 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]).
tvec = pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx - 1];
}// if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) else
return tvec.Normalize();
}
static aiVector3D GeometryHelper_Extrusion_GetNextZ(const size_t pSpine_PointIdx, const std::vector& pSpine, const bool pSpine_Closed,
const aiVector3D pVecZ_Prev)
{
const aiVector3D zero_vec(0);
const size_t spine_idx_last = pSpine.size() - 1;
aiVector3D tvec;
// at first special cases
if(pSpine.size() < 3)// spine have not enough points for vector calculations.
{
tvec.Set(0, 0, 1);
}
else if(pSpine_PointIdx == 0)// special case: first point
{
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.
{
tvec = (pSpine[1] - pSpine[0]) ^ (pSpine[spine_idx_last] - pSpine[0]);
}
else // for not closed curve first and next point(s[0] and s[1]) has the same vector Z.
{
bool found = false;
// 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)
// then the Z-axis for the first spine point with a defined Z-axis is used."
// Walk thru spine and find Z.
for(size_t next_point = 2; (next_point <= spine_idx_last) && !found; next_point++)
{
// (pSpine[2] - pSpine[1]) ^ (pSpine[0] - pSpine[1])
tvec = (pSpine[next_point] - pSpine[next_point - 1]) ^ (pSpine[next_point - 2] - pSpine[next_point - 1]);
found = !tvec.Equal(zero_vec);
}
// if entire spine are collinear then use OZ axis.
if(!found) tvec.Set(0, 0, 1);
}// if(pSpine_Closed) else
}// else if(pSpine_PointIdx == 0)
else if(pSpine_PointIdx == spine_idx_last)// special case: last point
{
if(pSpine_Closed)
{// 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].
tvec = (pSpine[0] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]);
// if taken spine vectors are collinear then use previous vector Z.
if(tvec.Equal(zero_vec)) tvec = pVecZ_Prev;
}
else
{// vector Z for last point of not closed curve is previous vector Z.
tvec = pVecZ_Prev;
}
}
else// regular point
{
tvec = (pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]);
// if taken spine vectors are collinear then use previous vector Z.
if(tvec.Equal(zero_vec)) tvec = pVecZ_Prev;
}
// 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
// is flipped (multiplied by −1).
if((tvec * pVecZ_Prev) < 0) tvec = -tvec;
return tvec.Normalize();
}
//
void X3DImporter::ParseNode_Geometry3D_Extrusion()
{
std::string use, def;
bool beginCap = true;
bool ccw = true;
bool convex = true;
float creaseAngle = 0;
std::vector crossSection;
bool endCap = true;
std::vector orientation;
std::vector scale;
bool solid = true;
std::vector spine;
CX3DImporter_NodeElement* ne;
MACRO_ATTRREAD_LOOPBEG;
MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use);
MACRO_ATTRREAD_CHECK_RET("beginCap", beginCap, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("ccw", ccw, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("convex", convex, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("creaseAngle", creaseAngle, XML_ReadNode_GetAttrVal_AsFloat);
MACRO_ATTRREAD_CHECK_REF("crossSection", crossSection, XML_ReadNode_GetAttrVal_AsArrVec2f);
MACRO_ATTRREAD_CHECK_RET("endCap", endCap, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_REF("orientation", orientation, XML_ReadNode_GetAttrVal_AsArrF);
MACRO_ATTRREAD_CHECK_REF("scale", scale, XML_ReadNode_GetAttrVal_AsArrVec2f);
MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_REF("spine", spine, XML_ReadNode_GetAttrVal_AsArrVec3f);
MACRO_ATTRREAD_LOOPEND;
// if "USE" defined then find already defined element.
if(!use.empty())
{
MACRO_USE_CHECKANDAPPLY(def, use, ENET_Extrusion, ne);
}
else
{
//
// check if default values must be assigned
//
if(spine.size() == 0)
{
spine.resize(2);
spine[0].Set(0, 0, 0), spine[1].Set(0, 1, 0);
}
else if(spine.size() == 1)
{
throw DeadlyImportError("ParseNode_Geometry3D_Extrusion. Spine must have at least two points.");
}
if(crossSection.size() == 0)
{
crossSection.resize(5);
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);
}
{// orientation
size_t ori_size = orientation.size() / 4;
if(ori_size < spine.size())
{
float add_ori[4];// values that will be added
if(ori_size == 1)// if "orientation" has one element(means one MFRotation with four components) then use it value for all spine points.
{
add_ori[0] = orientation[0], add_ori[1] = orientation[1], add_ori[2] = orientation[2], add_ori[3] = orientation[3];
}
else// else - use default values
{
add_ori[0] = 0, add_ori[1] = 0, add_ori[2] = 1, add_ori[3] = 0;
}
orientation.reserve(spine.size() * 4);
for(size_t i = 0, i_e = (spine.size() - ori_size); i < i_e; i++)
orientation.push_back(add_ori[0]), orientation.push_back(add_ori[1]), orientation.push_back(add_ori[2]), orientation.push_back(add_ori[3]);
}
if(orientation.size() % 4) throw DeadlyImportError("Attribute \"orientation\" in must has multiple four quantity of numbers.");
}// END: orientation
{// scale
if(scale.size() < spine.size())
{
aiVector2D add_sc;
if(scale.size() == 1)// if "scale" has one element then use it value for all spine points.
add_sc = scale[0];
else// else - use default values
add_sc.Set(1, 1);
scale.reserve(spine.size());
for(size_t i = 0, i_e = (spine.size() - scale.size()); i < i_e; i++) scale.push_back(add_sc);
}
}// END: scale
//
// create and if needed - define new geometry object.
//
ne = new CX3DImporter_NodeElement_IndexedSet(CX3DImporter_NodeElement::ENET_Extrusion, NodeElement_Cur);
if(!def.empty()) ne->ID = def;
CX3DImporter_NodeElement_IndexedSet& ext_alias = *((CX3DImporter_NodeElement_IndexedSet*)ne);// create alias for conveience
// assign part of input data
ext_alias.CCW = ccw;
ext_alias.Convex = convex;
ext_alias.CreaseAngle = creaseAngle;
ext_alias.Solid = solid;
//
// How we done it at all?
// 1. At first we will calculate array of basises for every point in spine(look SCP in ISO-dic). Also "orientation" vector
// are applied vor every basis.
// 2. After that we can create array of point sets: which are scaled, transfered to basis of relative basis and at final translated to real position
// using relative spine point.
// 3. Next step is creating CoordIdx array(do not forget "-1" delimiter). While creating CoordIdx also created faces for begin and end caps, if
// needed. While createing CootdIdx is taking in account CCW flag.
// 4. The last step: create Vertices list.
//
bool spine_closed;// flag: true if spine curve is closed.
bool cross_closed;// flag: true if cross curve is closed.
std::vector basis_arr;// array of basises. ROW_a - X, ROW_b - Y, ROW_c - Z.
std::vector > pointset_arr;// array of point sets: cross curves.
// detect closed curves
GeometryHelper_Extrusion_CurveIsClosed(crossSection, true, true, cross_closed);// true - drop tail, true - remove duplicate end.
GeometryHelper_Extrusion_CurveIsClosed(spine, true, true, spine_closed);// true - drop tail, true - remove duplicate end.
// 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.
if(spine_closed)
{
beginCap |= endCap;
endCap = false;
}
{// 1. Calculate array of basises.
aiMatrix4x4 rotmat;
aiVector3D vecX(0), vecY(0), vecZ(0);
basis_arr.resize(spine.size());
for(size_t i = 0, i_e = spine.size(); i < i_e; i++)
{
aiVector3D tvec;
// get axises of basis.
vecY = GeometryHelper_Extrusion_GetNextY(i, spine, spine_closed);
vecZ = GeometryHelper_Extrusion_GetNextZ(i, spine, spine_closed, vecZ);
vecX = (vecY ^ vecZ).Normalize();
// get rotation matrix and apply "orientation" to basis
aiMatrix4x4::Rotation(orientation[i * 4 + 3], aiVector3D(orientation[i * 4], orientation[i * 4 + 1], orientation[i * 4 + 2]), rotmat);
tvec = vecX, tvec *= rotmat, basis_arr[i].a1 = tvec.x, basis_arr[i].a2 = tvec.y, basis_arr[i].a3 = tvec.z;
tvec = vecY, tvec *= rotmat, basis_arr[i].b1 = tvec.x, basis_arr[i].b2 = tvec.y, basis_arr[i].b3 = tvec.z;
tvec = vecZ, tvec *= rotmat, basis_arr[i].c1 = tvec.x, basis_arr[i].c2 = tvec.y, basis_arr[i].c3 = tvec.z;
}// for(size_t i = 0, i_e = spine.size(); i < i_e; i++)
}// END: 1. Calculate array of basises
{// 2. Create array of point sets.
aiMatrix4x4 scmat;
std::vector tcross(crossSection.size());
pointset_arr.resize(spine.size());
for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++)
{
aiVector3D tc23vec;
tc23vec.Set(scale[spi].x, 0, scale[spi].y);
aiMatrix4x4::Scaling(tc23vec, scmat);
for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++)
{
aiVector3D tvecX, tvecY, tvecZ;
tc23vec.Set(crossSection[cri].x, 0, crossSection[cri].y);
// apply scaling to point
tcross[cri] = scmat * tc23vec;
//
// transfer point to new basis
// calculate coordinate in new basis
tvecX.Set(basis_arr[spi].a1, basis_arr[spi].a2, basis_arr[spi].a3), tvecX *= tcross[cri].x;
tvecY.Set(basis_arr[spi].b1, basis_arr[spi].b2, basis_arr[spi].b3), tvecY *= tcross[cri].y;
tvecZ.Set(basis_arr[spi].c1, basis_arr[spi].c2, basis_arr[spi].c3), tvecZ *= tcross[cri].z;
// apply new coordinates and translate it to spine point.
tcross[cri] = tvecX + tvecY + tvecZ + spine[spi];
}// for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; i++)
pointset_arr[spi] = tcross;// store transfered point set
}// for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; i++)
}// END: 2. Create array of point sets.
{// 3. Create CoordIdx.
// add caps if needed
if(beginCap)
{
// add cap as polygon. vertices of cap are places at begin, so just add numbers from zero.
for(size_t i = 0, i_e = crossSection.size(); i < i_e; i++) ext_alias.CoordIndex.push_back(i);
// add delimiter
ext_alias.CoordIndex.push_back(-1);
}// if(beginCap)
if(endCap)
{
// add cap as polygon. vertices of cap are places at end, as for beginCap use just sequence of numbers but with offset.
size_t beg = (pointset_arr.size() - 1) * crossSection.size();
for(size_t i = beg, i_e = (beg + crossSection.size()); i < i_e; i++) ext_alias.CoordIndex.push_back(i);
// add delimiter
ext_alias.CoordIndex.push_back(-1);
}// if(beginCap)
// add quads
for(size_t spi = 0, spi_e = (spine.size() - 1); spi <= spi_e; spi++)
{
const size_t cr_sz = crossSection.size();
const size_t cr_last = crossSection.size() - 1;
size_t right_col;// hold index basis for points of quad placed in right column;
if(spi != spi_e)
right_col = spi + 1;
else if(spine_closed)// if spine curve is closed then one more quad is needed: between first and last points of curve.
right_col = 0;
else
break;// if spine curve is not closed then break the loop, because spi is out of range for that type of spine.
for(size_t cri = 0; cri < cr_sz; cri++)
{
if(cri != cr_last)
{
MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex,
spi * cr_sz + cri, right_col * cr_sz + cri, right_col * cr_sz + cri + 1, spi * cr_sz + cri + 1);
// add delimiter
ext_alias.CoordIndex.push_back(-1);
}
else if(cross_closed)// if cross curve is closed then one more quad is needed: between first and last points of curve.
{
MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex,
spi * cr_sz + cri, right_col * cr_sz + cri, right_col * cr_sz + 0, spi * cr_sz + 0);
// add delimiter
ext_alias.CoordIndex.push_back(-1);
}
}// for(size_t cri = 0; cri < cr_sz; cri++)
}// for(size_t spi = 0, spi_e = (spine.size() - 2); spi < spi_e; spi++)
}// END: 3. Create CoordIdx.
{// 4. Create vertices list.
// just copy all vertices
for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++)
{
for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++)
{
ext_alias.Vertices.push_back(pointset_arr[spi][cri]);
}
}
}// END: 4. Create vertices list.
//PrintVectorSet("Ext. CoordIdx", ext_alias.CoordIndex);
//PrintVectorSet("Ext. Vertices", ext_alias.Vertices);
// check for child nodes
if(!mReader->isEmptyElement())
ParseNode_Metadata(ne, "Extrusion");
else
NodeElement_Cur->Child.push_back(ne);// add made object as child to current element
NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph
}// if(!use.empty()) else
}
//
//
// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate,
// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute,
// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained.
// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model.
//
void X3DImporter::ParseNode_Geometry3D_IndexedFaceSet()
{
std::string use, def;
bool ccw = true;
std::list colorIndex;
bool colorPerVertex = true;
bool convex = true;
std::list coordIndex;
float creaseAngle = 0;
std::list normalIndex;
bool normalPerVertex = true;
bool solid = true;
std::list texCoordIndex;
CX3DImporter_NodeElement* ne;
MACRO_ATTRREAD_LOOPBEG;
MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use);
MACRO_ATTRREAD_CHECK_RET("ccw", ccw, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_REF("colorIndex", colorIndex, XML_ReadNode_GetAttrVal_AsListI32);
MACRO_ATTRREAD_CHECK_RET("colorPerVertex", colorPerVertex, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("convex", convex, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_REF("coordIndex", coordIndex, XML_ReadNode_GetAttrVal_AsListI32);
MACRO_ATTRREAD_CHECK_RET("creaseAngle", creaseAngle, XML_ReadNode_GetAttrVal_AsFloat);
MACRO_ATTRREAD_CHECK_REF("normalIndex", normalIndex, XML_ReadNode_GetAttrVal_AsListI32);
MACRO_ATTRREAD_CHECK_RET("normalPerVertex", normalPerVertex, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_CHECK_REF("texCoordIndex", texCoordIndex, XML_ReadNode_GetAttrVal_AsListI32);
MACRO_ATTRREAD_LOOPEND;
// if "USE" defined then find already defined element.
if(!use.empty())
{
MACRO_USE_CHECKANDAPPLY(def, use, ENET_IndexedFaceSet, ne);
}
else
{
// check data
if(coordIndex.size() == 0) throw DeadlyImportError("IndexedFaceSet must contain not empty \"coordIndex\" attribute.");
// create and if needed - define new geometry object.
ne = new CX3DImporter_NodeElement_IndexedSet(CX3DImporter_NodeElement::ENET_IndexedFaceSet, NodeElement_Cur);
if(!def.empty()) ne->ID = def;
CX3DImporter_NodeElement_IndexedSet& ne_alias = *((CX3DImporter_NodeElement_IndexedSet*)ne);
ne_alias.CCW = ccw;
ne_alias.ColorIndex = colorIndex;
ne_alias.ColorPerVertex = colorPerVertex;
ne_alias.Convex = convex;
ne_alias.CoordIndex = coordIndex;
ne_alias.CreaseAngle = creaseAngle;
ne_alias.NormalIndex = normalIndex;
ne_alias.NormalPerVertex = normalPerVertex;
ne_alias.Solid = solid;
ne_alias.TexCoordIndex = texCoordIndex;
// check for child nodes
if(!mReader->isEmptyElement())
{
ParseHelper_Node_Enter(ne);
MACRO_NODECHECK_LOOPBEGIN("IndexedFaceSet");
// check for X3DComposedGeometryNodes
if(XML_CheckNode_NameEqual("Color")) { ParseNode_Rendering_Color(); continue; }
if(XML_CheckNode_NameEqual("ColorRGBA")) { ParseNode_Rendering_ColorRGBA(); continue; }
if(XML_CheckNode_NameEqual("Coordinate")) { ParseNode_Rendering_Coordinate(); continue; }
if(XML_CheckNode_NameEqual("Normal")) { ParseNode_Rendering_Normal(); continue; }
if(XML_CheckNode_NameEqual("TextureCoordinate")) { ParseNode_Texturing_TextureCoordinate(); continue; }
// check for X3DMetadataObject
if(!ParseHelper_CheckRead_X3DMetadataObject()) XML_CheckNode_SkipUnsupported("IndexedFaceSet");
MACRO_NODECHECK_LOOPEND("IndexedFaceSet");
ParseHelper_Node_Exit();
}// if(!mReader->isEmptyElement())
else
{
NodeElement_Cur->Child.push_back(ne);// add made object as child to current element
}
NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph
}// if(!use.empty()) else
}
//
void X3DImporter::ParseNode_Geometry3D_Sphere()
{
std::string use, def;
float radius = 1;
bool solid = true;
CX3DImporter_NodeElement* ne;
MACRO_ATTRREAD_LOOPBEG;
MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use);
MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat);
MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool);
MACRO_ATTRREAD_LOOPEND;
// if "USE" defined then find already defined element.
if(!use.empty())
{
MACRO_USE_CHECKANDAPPLY(def, use, ENET_Sphere, ne);
}
else
{
const unsigned int tess = 3;///TODO: IME tesselation factor thru ai_property
std::vector tlist;
// create and if needed - define new geometry object.
ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Sphere, NodeElement_Cur);
if(!def.empty()) ne->ID = def;
StandardShapes::MakeSphere(tess, tlist);
// copy data from temp array and apply scale
for(std::vector::iterator it = tlist.begin(); it != tlist.end(); it++)
{
((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices.push_back(*it * radius);
}
((CX3DImporter_NodeElement_Geometry3D*)ne)->Solid = solid;
((CX3DImporter_NodeElement_Geometry3D*)ne)->NumIndices = 3;
// check for X3DMetadataObject childs.
if(!mReader->isEmptyElement())
ParseNode_Metadata(ne, "Sphere");
else
NodeElement_Cur->Child.push_back(ne);// add made object as child to current element
NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph
}// if(!use.empty()) else
}
}// namespace Assimp
#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER