/// \file   X3DImporter.cpp
/// \brief  X3D-format files importer for Assimp: main algorithm implementation.
/// \date   2015-2016
/// \author smal.root@gmail.com

#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER

#include "X3DImporter.hpp"
#include "X3DImporter_Macro.hpp"

// Header files, Assimp.
#include "DefaultIOSystem.h"
#include "fast_atof.h"

// Header files, stdlib.
#include <memory>
#include <string>

namespace Assimp
{

/// \var aiImporterDesc X3DImporter::Description
/// Conastant which hold importer description
const aiImporterDesc X3DImporter::Description = {
	"Extensible 3D(X3D) Importer",
	"smalcom",
	"",
	"See documentation in source code. Chapter: Limitations.",
	aiImporterFlags_SupportTextFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental,
	0,
	0,
	0,
	0,
	"x3d"
};

void X3DImporter::Clear()
{
	NodeElement_Cur = nullptr;
	// Delete all elements
	if(NodeElement_List.size())
	{
		for(std::list<CX3DImporter_NodeElement*>::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); it++) delete *it;

		NodeElement_List.clear();
	}
}

X3DImporter::~X3DImporter()
{
	if(mReader != nullptr) delete mReader;
	// Clear() is accounting if data already is deleted. So, just check again if all data is deleted.
	Clear();
}

/*********************************************************************************************************************************************/
/************************************************************ Functions: find set ************************************************************/
/*********************************************************************************************************************************************/

bool X3DImporter::FindNodeElement_FromRoot(const std::string& pID, const CX3DImporter_NodeElement::EType pType, CX3DImporter_NodeElement** pElement)
{
	for(std::list<CX3DImporter_NodeElement*>::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); it++)
	{
		if(((*it)->Type == pType) && ((*it)->ID == pID))
		{
			if(pElement != nullptr) *pElement = *it;

			return true;
		}
	}// for(std::list<CX3DImporter_NodeElement*>::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); it++)

	return false;
}

bool X3DImporter::FindNodeElement_FromNode(CX3DImporter_NodeElement* pStartNode, const std::string& pID,
													const CX3DImporter_NodeElement::EType pType, CX3DImporter_NodeElement** pElement)
{
bool found = false;// flag: true - if requested element is found.

	// Check if pStartNode - this is the element, we are looking for.
	if((pStartNode->Type == pType) && (pStartNode->ID == pID))
	{
		found = true;
		if(pElement != nullptr) *pElement = pStartNode;

		goto fne_fn_end;
	}// if((pStartNode->Type() == pType) && (pStartNode->ID() == pID))

	// Check childs of pStartNode.
	for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = pStartNode->Child.begin(); ch_it != pStartNode->Child.end(); ch_it++)
	{
		found = FindNodeElement_FromNode(*ch_it, pID, pType, pElement);
		if(found) break;
	}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = it->Child.begin(); ch_it != it->Child.end(); ch_it++)

fne_fn_end:

	return found;
}

bool X3DImporter::FindNodeElement(const std::string& pID, const CX3DImporter_NodeElement::EType pType, CX3DImporter_NodeElement** pElement)
{
CX3DImporter_NodeElement* tnd = NodeElement_Cur;// temporary pointer to node.
bool static_search = false;// flag: true if searching in static node.

    // At first check if we have deal with static node. Go up thru parent nodes and check flag.
    while(tnd != nullptr)
    {
		if(tnd->Type == CX3DImporter_NodeElement::ENET_Group)
		{
			if(((CX3DImporter_NodeElement_Group*)tnd)->Static)
			{
				static_search = true;// Flag found, stop walking up. Node with static flag will holded in tnd variable.

				break;
			}
		}

		tnd = tnd->Parent;// go up in graph.
    }// while(tnd != nullptr)

    // at now call appropriate search function.
    if(static_search)
		return FindNodeElement_FromNode(tnd, pID, pType, pElement);
	else
		return FindNodeElement_FromRoot(pID, pType, pElement);
}

/*********************************************************************************************************************************************/
/************************************************************ Functions: throw set ***********************************************************/
/*********************************************************************************************************************************************/

void X3DImporter::Throw_ArgOutOfRange(const std::string& pArgument)
{
	throw DeadlyImportError("Argument value is out of range for: \"" + pArgument + "\".");
}

void X3DImporter::Throw_CloseNotFound(const std::string& pNode)
{
	throw DeadlyImportError("Close tag for node <" + pNode + "> not found. Seems file is corrupt.");
}

void X3DImporter::Throw_ConvertFail_Str2ArrF(const std::string& pAttrValue)
{
	throw DeadlyImportError("In <" + std::string(mReader->getNodeName()) + "> failed to convert attribute value \"" + pAttrValue +
							"\" from string to array of floats.");
}

void X3DImporter::Throw_DEF_And_USE()
{
	throw DeadlyImportError("\"DEF\" and \"USE\" can not be defined both in <" + std::string(mReader->getNodeName()) + ">.");
}

void X3DImporter::Throw_IncorrectAttr(const std::string& pAttrName)
{
	throw DeadlyImportError("Node <" + std::string(mReader->getNodeName()) + "> has incorrect attribute \"" + pAttrName + "\".");
}

void X3DImporter::Throw_IncorrectAttrValue(const std::string& pAttrName)
{
	throw DeadlyImportError("Attribute \"" + pAttrName + "\" in node <" + std::string(mReader->getNodeName()) + "> has incorrect value.");
}

void X3DImporter::Throw_MoreThanOnceDefined(const std::string& pNodeType, const std::string& pDescription)
{
	throw DeadlyImportError("\"" + pNodeType + "\" node can be used only once in " + mReader->getNodeName() + ". Description: " + pDescription);
}

void X3DImporter::Throw_TagCountIncorrect(const std::string& pNode)
{
	throw DeadlyImportError("Count of open and close tags for node <" + pNode + "> are not equivalent. Seems file is corrupt.");
}

void X3DImporter::Throw_USE_NotFound(const std::string& pAttrValue)
{
	throw DeadlyImportError("Not found node with name \"" + pAttrValue + "\" in <" + std::string(mReader->getNodeName()) + ">.");
}

/*********************************************************************************************************************************************/
/************************************************************* Functions: XML set ************************************************************/
/*********************************************************************************************************************************************/

void X3DImporter::XML_CheckNode_MustBeEmpty()
{
	if(!mReader->isEmptyElement()) throw DeadlyImportError(std::string("Node <") + mReader->getNodeName() + "> must be empty.");
}

void X3DImporter::XML_CheckNode_SkipUnsupported(const std::string& pParentNodeName)
{
const size_t Uns_Skip_Len = 189;
const char* Uns_Skip[Uns_Skip_Len] = {
	// CAD geometry component
	"CADAssembly", "CADFace", "CADLayer", "CADPart", "IndexedQuadSet", "QuadSet",
	// Core
	"ROUTE", "ExternProtoDeclare", "ProtoDeclare", "ProtoInstance", "ProtoInterface", "WorldInfo",
	// Distributed interactive simulation (DIS) component
	"DISEntityManager", "DISEntityTypeMapping", "EspduTransform", "ReceiverPdu", "SignalPdu", "TransmitterPdu",
	// Cube map environmental texturing component
	"ComposedCubeMapTexture", "GeneratedCubeMapTexture", "ImageCubeMapTexture",
	// Environmental effects component
	"Background", "Fog", "FogCoordinate", "LocalFog", "TextureBackground",
	// Environmental sensor component
	"ProximitySensor", "TransformSensor", "VisibilitySensor",
	// Followers component
	"ColorChaser", "ColorDamper", "CoordinateChaser", "CoordinateDamper", "OrientationChaser", "OrientationDamper", "PositionChaser", "PositionChaser2D",
	"PositionDamper", "PositionDamper2D", "ScalarChaser", "ScalarDamper", "TexCoordChaser2D", "TexCoordDamper2D",
	// Geospatial component
	"GeoCoordinate", "GeoElevationGrid", "GeoLocation", "GeoLOD", "GeoMetadata", "GeoOrigin", "GeoPositionInterpolator", "GeoProximitySensor",
	"GeoTouchSensor", "GeoTransform", "GeoViewpoint",
	// Humanoid Animation (H-Anim) component
	"HAnimDisplacer", "HAnimHumanoid", "HAnimJoint", "HAnimSegment", "HAnimSite",
	// Interpolation component
	"ColorInterpolator", "CoordinateInterpolator", "CoordinateInterpolator2D", "EaseInEaseOut", "NormalInterpolator", "OrientationInterpolator",
	"PositionInterpolator", "PositionInterpolator2D", "ScalarInterpolator", "SplinePositionInterpolator", "SplinePositionInterpolator2D",
	"SplineScalarInterpolator", "SquadOrientationInterpolator",
	// Key device sensor component
	"KeySensor", "StringSensor"
	// Layering component
	"Layer", "LayerSet", "Viewport",
	// Layout component
	"Layout", "LayoutGroup", "LayoutLayer", "ScreenFontStyle", "ScreenGroup",
	// Navigation component
	"Billboard", "Collision", "LOD", "NavigationInfo", "OrthoViewpoint", "Viewpoint", "ViewpointGroup",
	// Networking component
	"Anchor", "LoadSensor",
	// NURBS component
	"Contour2D", "ContourPolyline2D", "CoordinateDouble", "NurbsCurve", "NurbsCurve2D", "NurbsOrientationInterpolator", "NurbsPatchSurface",
	"NurbsPositionInterpolator", "NurbsSet", "NurbsSurfaceInterpolator", "NurbsSweptSurface", "NurbsSwungSurface", "NurbsTextureCoordinate",
	"NurbsTrimmedSurface",
	// Particle systems component
	"BoundedPhysicsModel", "ConeEmitter", "ExplosionEmitter", "ForcePhysicsModel", "ParticleSystem", "PointEmitter", "PolylineEmitter", "SurfaceEmitter",
	"VolumeEmitter", "WindPhysicsModel",
	// Picking component
	"LinePickSensor", "PickableGroup", "PointPickSensor", "PrimitivePickSensor", "VolumePickSensor",
	// Pointing device sensor component
	"CylinderSensor", "PlaneSensor", "SphereSensor", "TouchSensor",
	// Rendering component
	"ClipPlane",
	// Rigid body physics
	"BallJoint", "CollidableOffset", "CollidableShape", "CollisionCollection", "CollisionSensor", "CollisionSpace", "Contact", "DoubleAxisHingeJoint",
	"MotorJoint", "RigidBody", "RigidBodyCollection", "SingleAxisHingeJoint", "SliderJoint", "UniversalJoint",
	// Scripting component
	"Script",
	// Programmable shaders component
	"ComposedShader", "FloatVertexAttribute", "Matrix3VertexAttribute", "Matrix4VertexAttribute", "PackagedShader", "ProgramShader", "ShaderPart",
	"ShaderProgram",
	// Shape component
	"FillProperties", "LineProperties", "TwoSidedMaterial",
	// Sound component
	"AudioClip", "Sound",
	// Text component
	"FontStyle", "Text",
	// Texturing3D Component
	"ComposedTexture3D", "ImageTexture3D", "PixelTexture3D", "TextureCoordinate3D", "TextureCoordinate4D", "TextureTransformMatrix3D", "TextureTransform3D",
	// Texturing component
	"MovieTexture", "MultiTexture", "MultiTextureCoordinate", "MultiTextureTransform", "PixelTexture", "TextureCoordinateGenerator", "TextureProperties",
	// Time component
	"TimeSensor",
	// Event Utilities component
	"BooleanFilter", "BooleanSequencer", "BooleanToggle", "BooleanTrigger", "IntegerSequencer", "IntegerTrigger", "TimeTrigger",
	// Volume rendering component
	"BlendedVolumeStyle", "BoundaryEnhancementVolumeStyle", "CartoonVolumeStyle", "ComposedVolumeStyle", "EdgeEnhancementVolumeStyle", "IsoSurfaceVolumeData",
	"OpacityMapVolumeStyle", "ProjectionVolumeStyle", "SegmentedVolumeData", "ShadedVolumeStyle", "SilhouetteEnhancementVolumeStyle", "ToneMappedVolumeStyle",
	"VolumeData"
};

std::string nn(mReader->getNodeName());
bool found = false;
bool close_found = false;

	for(size_t i = 0; i < Uns_Skip_Len; i++)
	{
		if(nn == Uns_Skip[i])
		{
			found = true;
			if(mReader->isEmptyElement())
			{
				close_found = true;

				goto casu_cres;
			}

			while(mReader->read())
			{
				if((mReader->getNodeType() == irr::io::EXN_ELEMENT_END) && (nn == mReader->getNodeName()))
				{
					close_found = true;

					goto casu_cres;
				}
			}
		}
	}

casu_cres:

	if(!found) throw DeadlyImportError("Unknown node \"" + nn + "\" in " + pParentNodeName + ".");

	if(close_found)
		LogInfo("Skipping node \"" + nn + "\" in " + pParentNodeName + ".");
	else
		Throw_CloseNotFound(nn);
}

bool X3DImporter::XML_SearchNode(const std::string& pNodeName)
{
	while(mReader->read())
	{
		if((mReader->getNodeType() == irr::io::EXN_ELEMENT) && XML_CheckNode_NameEqual(pNodeName)) return true;
	}

	return false;
}

bool X3DImporter::XML_ReadNode_GetAttrVal_AsBool(const int pAttrIdx)
{
std::string val(mReader->getAttributeValue(pAttrIdx));

	if(val == "false")
		return false;
	else if(val == "true")
		return true;
	else
		throw DeadlyImportError("Bool attribute value can contain \"false\" or \"true\" not the \"" + val + "\"");
}

float X3DImporter::XML_ReadNode_GetAttrVal_AsFloat(const int pAttrIdx)
{
std::string val;
float tvalf;

	ParseHelper_FixTruncatedFloatString(mReader->getAttributeValue(pAttrIdx), val);
	fast_atoreal_move(val.c_str(), tvalf, false);

	return tvalf;
}

int32_t X3DImporter::XML_ReadNode_GetAttrVal_AsI32(const int pAttrIdx)
{
	return strtol10(mReader->getAttributeValue(pAttrIdx));
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsCol3f(const int pAttrIdx, aiColor3D& pValue)
{
std::list<float> tlist;
std::list<float>::iterator it;

	XML_ReadNode_GetAttrVal_AsListF(pAttrIdx, tlist);
	if(tlist.size() != 3) Throw_ConvertFail_Str2ArrF(mReader->getAttributeValue(pAttrIdx));

	it = tlist.begin();
	pValue.r = *it++;
	pValue.g = *it++;
	pValue.b = *it;
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsVec2f(const int pAttrIdx, aiVector2D& pValue)
{
std::list<float> tlist;
std::list<float>::iterator it;

	XML_ReadNode_GetAttrVal_AsListF(pAttrIdx, tlist);
	if(tlist.size() != 2) Throw_ConvertFail_Str2ArrF(mReader->getAttributeValue(pAttrIdx));

	it = tlist.begin();
	pValue.x = *it++;
	pValue.y = *it;
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsVec3f(const int pAttrIdx, aiVector3D& pValue)
{
std::list<float> tlist;
std::list<float>::iterator it;

	XML_ReadNode_GetAttrVal_AsListF(pAttrIdx, tlist);
	if(tlist.size() != 3) Throw_ConvertFail_Str2ArrF(mReader->getAttributeValue(pAttrIdx));

	it = tlist.begin();
	pValue.x = *it++;
	pValue.y = *it++;
	pValue.z = *it;
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsListB(const int pAttrIdx, std::list<bool>& pValue)
{
char* tok_str;
size_t tok_str_len;

	// make copy of attribute value - string with list of bool values. Also all bool values is strings.
	tok_str_len = strlen(mReader->getAttributeValue(pAttrIdx));
	if(!tok_str_len) Throw_IncorrectAttrValue(mReader->getAttributeName(pAttrIdx));

	tok_str_len++;// take in account terminating '\0'.
	tok_str = new char[tok_str_len];

	strcpy(tok_str, mReader->getAttributeValue(pAttrIdx));
	// change all spacebars to symbol '\0'. That is needed for parsing.
	for(size_t i = 0; i < tok_str_len; i++)
	{
		if(tok_str[i] == ' ') tok_str[i] = 0;
	}

	// at now check what current token is
	for(char *tok_cur = tok_str, *tok_end = (tok_str + tok_str_len); tok_cur < tok_end;)
	{
		if(strncmp(tok_cur, "true", 4) == 0)
		{
			pValue.push_back(true);
			tok_cur += 5;// five, not four. Because '\0' must be skipped too.
		}
		else if(strncmp(tok_cur, "false", 5) == 0)
		{
			pValue.push_back(true);
			tok_cur += 6;// six, not five. Because '\0' must be skipped too.
		}
		else
		{
			Throw_IncorrectAttrValue(mReader->getAttributeName(pAttrIdx));
		}
	}// for(char* tok_cur = tok_str, tok_end = (tok_str + tok_str_len); tok_cur < tok_end;)

	// delete temporary string
	delete [] tok_str;
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsArrB(const int pAttrIdx, std::vector<bool>& pValue)
{
std::list<bool> tlist;

	XML_ReadNode_GetAttrVal_AsListB(pAttrIdx, tlist);// read as list
	// and copy to array
	if(tlist.size() > 0)
	{
		pValue.reserve(tlist.size());
		for(std::list<bool>::iterator it = tlist.begin(); it != tlist.end(); it++) pValue.push_back(*it);
	}
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsListI32(const int pAttrIdx, std::list<int32_t>& pValue)
{
const char* tstr = mReader->getAttributeValue(pAttrIdx);
const char* tstr_end = tstr + strlen(tstr);

	do
	{
		const char* ostr;

		int32_t tval32;

		tval32 = strtol10(tstr, &ostr);
		if(ostr == tstr) break;

		while((ostr < tstr_end) && (*ostr == ' ')) ostr++;// skip spaces between values.

		tstr = ostr;
		pValue.push_back(tval32);
	} while(tstr < tstr_end);
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsArrI32(const int pAttrIdx, std::vector<int32_t>& pValue)
{
std::list<int32_t> tlist;

	XML_ReadNode_GetAttrVal_AsListI32(pAttrIdx, tlist);// read as list
	// and copy to array
	if(tlist.size() > 0)
	{
		pValue.reserve(tlist.size());
		for(std::list<int32_t>::iterator it = tlist.begin(); it != tlist.end(); it++) pValue.push_back(*it);
	}
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsListF(const int pAttrIdx, std::list<float>& pValue)
{
std::string str_fixed;

	// at first check string values like '.xxx'.
	ParseHelper_FixTruncatedFloatString(mReader->getAttributeValue(pAttrIdx), str_fixed);
	if(!str_fixed.size()) Throw_ConvertFail_Str2ArrF(mReader->getAttributeValue(pAttrIdx));

	// and convert all values and place it in list.
	const char* pstr = str_fixed.c_str();
	const char* pstr_end = pstr + str_fixed.size();

	do
	{
		float tvalf;

		while((*pstr == ' ') && (pstr < pstr_end)) pstr++;// skip spaces between values.

		if(pstr < pstr_end)// additional check, because attribute value can be ended with spaces.
		{
			pstr = fast_atoreal_move(pstr, tvalf, false);
			pValue.push_back(tvalf);
		}
	} while(pstr < pstr_end);
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsArrF(const int pAttrIdx, std::vector<float>& pValue)
{
std::list<float> tlist;

	XML_ReadNode_GetAttrVal_AsListF(pAttrIdx, tlist);// read as list
	// and copy to array
	if(tlist.size() > 0)
	{
		pValue.reserve(tlist.size());
		for(std::list<float>::iterator it = tlist.begin(); it != tlist.end(); it++) pValue.push_back(*it);
	}
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsListD(const int pAttrIdx, std::list<double>& pValue)
{
std::string str_fixed;

	// at first check string values like '.xxx'.
	ParseHelper_FixTruncatedFloatString(mReader->getAttributeValue(pAttrIdx), str_fixed);
	if(!str_fixed.size()) Throw_ConvertFail_Str2ArrF(mReader->getAttributeValue(pAttrIdx));

	// and convert all values and place it in list.
	const char* pstr = str_fixed.c_str();
	const char* pstr_end = pstr + str_fixed.size();

	do
	{
		double tvald;

		while((*pstr == ' ') && (pstr < pstr_end)) pstr++;// skip spaces between values.

		if(pstr < pstr_end)// additional check, because attribute value can be ended with spaces.
		{
			pstr = fast_atoreal_move(pstr, tvald, false);
			pValue.push_back(tvald);
		}
	} while(pstr < pstr_end);
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsArrD(const int pAttrIdx, std::vector<double>& pValue)
{
std::list<double> tlist;

	XML_ReadNode_GetAttrVal_AsListD(pAttrIdx, tlist);// read as list
	// and copy to array
	if(tlist.size() > 0)
	{
		pValue.reserve(tlist.size());
		for(std::list<double>::iterator it = tlist.begin(); it != tlist.end(); it++) pValue.push_back(*it);
	}
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsListCol3f(const int pAttrIdx, std::list<aiColor3D>& pValue)
{
std::list<float> tlist;

	XML_ReadNode_GetAttrVal_AsListF(pAttrIdx, tlist);// read as list
	if(tlist.size() % 3) Throw_ConvertFail_Str2ArrF(mReader->getAttributeValue(pAttrIdx));

	// copy data to array
	for(std::list<float>::iterator it = tlist.begin(); it != tlist.end();)
	{
		aiColor3D tcol;

		tcol.r = *it++;
		tcol.g = *it++;
		tcol.b = *it++;
		pValue.push_back(tcol);
	}
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsArrCol3f(const int pAttrIdx, std::vector<aiColor3D>& pValue)
{
std::list<aiColor3D> tlist;

	XML_ReadNode_GetAttrVal_AsListCol3f(pAttrIdx, tlist);// read as list
	// and copy to array
	if(tlist.size() > 0)
	{
		pValue.reserve(tlist.size());
		for(std::list<aiColor3D>::iterator it = tlist.begin(); it != tlist.end(); it++) pValue.push_back(*it);
	}
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsListCol4f(const int pAttrIdx, std::list<aiColor4D>& pValue)
{
std::list<float> tlist;

	XML_ReadNode_GetAttrVal_AsListF(pAttrIdx, tlist);// read as list
	if(tlist.size() % 4) Throw_ConvertFail_Str2ArrF(mReader->getAttributeValue(pAttrIdx));

	// copy data to array
	for(std::list<float>::iterator it = tlist.begin(); it != tlist.end();)
	{
		aiColor4D tcol;

		tcol.r = *it++;
		tcol.g = *it++;
		tcol.b = *it++;
		tcol.a = *it++;
		pValue.push_back(tcol);
	}
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsArrCol4f(const int pAttrIdx, std::vector<aiColor4D>& pValue)
{
std::list<aiColor4D> tlist;

	XML_ReadNode_GetAttrVal_AsListCol4f(pAttrIdx, tlist);// read as list
	// and copy to array
	if(tlist.size() > 0)
	{
		pValue.reserve(tlist.size());
		for(std::list<aiColor4D>::iterator it = tlist.begin(); it != tlist.end(); it++) pValue.push_back(*it);
	}
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsListVec2f(const int pAttrIdx, std::list<aiVector2D>& pValue)
{
std::list<float> tlist;

	XML_ReadNode_GetAttrVal_AsListF(pAttrIdx, tlist);// read as list
	if(tlist.size() % 2) Throw_ConvertFail_Str2ArrF(mReader->getAttributeValue(pAttrIdx));

	// copy data to array
	for(std::list<float>::iterator it = tlist.begin(); it != tlist.end();)
	{
		aiVector2D tvec;

		tvec.x = *it++;
		tvec.y = *it++;
		pValue.push_back(tvec);
	}
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsArrVec2f(const int pAttrIdx, std::vector<aiVector2D>& pValue)
{
std::list<aiVector2D> tlist;

	XML_ReadNode_GetAttrVal_AsListVec2f(pAttrIdx, tlist);// read as list
	// and copy to array
	if(tlist.size() > 0)
	{
		pValue.reserve(tlist.size());
		for(std::list<aiVector2D>::iterator it = tlist.begin(); it != tlist.end(); it++) pValue.push_back(*it);
	}
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsListVec3f(const int pAttrIdx, std::list<aiVector3D>& pValue)
{
std::list<float> tlist;

	XML_ReadNode_GetAttrVal_AsListF(pAttrIdx, tlist);// read as list
	if(tlist.size() % 3) Throw_ConvertFail_Str2ArrF(mReader->getAttributeValue(pAttrIdx));

	// copy data to array
	for(std::list<float>::iterator it = tlist.begin(); it != tlist.end();)
	{
		aiVector3D tvec;

		tvec.x = *it++;
		tvec.y = *it++;
		tvec.z = *it++;
		pValue.push_back(tvec);
	}
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsArrVec3f(const int pAttrIdx, std::vector<aiVector3D>& pValue)
{
std::list<aiVector3D> tlist;

	XML_ReadNode_GetAttrVal_AsListVec3f(pAttrIdx, tlist);// read as list
	// and copy to array
	if(tlist.size() > 0)
	{
		pValue.reserve(tlist.size());
		for(std::list<aiVector3D>::iterator it = tlist.begin(); it != tlist.end(); it++) pValue.push_back(*it);
	}
}

void X3DImporter::XML_ReadNode_GetAttrVal_AsListS(const int pAttrIdx, std::list<std::string>& pValue)
{
char* tok_str;
char* tok_str_end;
size_t tok_str_len;

	// make copy of attribute value - strings list.
	tok_str_len = strlen(mReader->getAttributeValue(pAttrIdx));
	if(!tok_str_len) Throw_IncorrectAttrValue(mReader->getAttributeName(pAttrIdx));

	// get pointer to begin of value.
	tok_str = const_cast<char*>(mReader->getAttributeValue(pAttrIdx));
	tok_str_end = tok_str + tok_str_len;
	// string list has following format: attr_name='"s1" "s2" "sn"'.
	do
	{
		char* tbeg;
		char* tend;
		size_t tlen;
		std::string tstr;

		// find begin of string(element of string list): "sn".
		tbeg = strstr(tok_str, "\"");
		if(tbeg == nullptr) Throw_IncorrectAttrValue(mReader->getAttributeName(pAttrIdx));

		tbeg++;// forward pointer from '\"' symbol to next after it.
		tok_str = tbeg;
		// find end of string(element of string list): "sn".
		tend = strstr(tok_str, "\"");
		if(tend == nullptr) Throw_IncorrectAttrValue(mReader->getAttributeName(pAttrIdx));

		tok_str = tend + 1;
		// create storage for new string
		tlen = tend - tbeg;
		tstr.resize(tlen);// reserve enough space and copy data
		memcpy((void*)tstr.data(), tbeg, tlen);// not strcpy because end of copied string from tok_str has no terminator.
		// and store string in output list.
		pValue.push_back(tstr);
	} while(tok_str < tok_str_end);
}

/*********************************************************************************************************************************************/
/****************************************************** Functions: geometry helper set  ******************************************************/
/*********************************************************************************************************************************************/

aiVector3D X3DImporter::GeometryHelper_Make_Point2D(const float pAngle, const float pRadius)
{
	return aiVector3D(pRadius * cosf(pAngle), pRadius * sinf(pAngle), 0);
}

void X3DImporter::GeometryHelper_Make_Arc2D(const float pStartAngle, const float pEndAngle, const float pRadius, size_t pNumSegments,
												std::list<aiVector3D>& pVertices)
{
float angle_full, angle_step;

	// check argument values ranges.
	if((pStartAngle < -AI_MATH_TWO_PI_F) || (pStartAngle > AI_MATH_TWO_PI_F)) Throw_ArgOutOfRange("GeometryHelper_Make_Arc2D.pStartAngle");
	if((pEndAngle < -AI_MATH_TWO_PI_F) || (pEndAngle > AI_MATH_TWO_PI_F)) Throw_ArgOutOfRange("GeometryHelper_Make_Arc2D.pEndAngle");
	if(pRadius <= 0) Throw_ArgOutOfRange("GeometryHelper_Make_Arc2D.pRadius");

	// calculate arc angle and check type of arc
	angle_full = fabs(pEndAngle - pStartAngle);
	if((angle_full > AI_MATH_TWO_PI_F) || (angle_full == 0.0f)) angle_full = AI_MATH_TWO_PI_F;

	// calculate angle for one step - angle to next point of line.
	angle_step = angle_full / (float)pNumSegments;
	// make points
	for(size_t pi = 0; pi <= pNumSegments; pi++)
	{
		float tangle;

		tangle = pStartAngle + pi * angle_step;
		pVertices.push_back(GeometryHelper_Make_Point2D(tangle, pRadius));
	}// for(size_t pi = 0; pi <= pNumSegments; pi++)

	// if we making full circle then add last vertex equal to first vertex
	if(angle_full == AI_MATH_TWO_PI_F) pVertices.push_back(*pVertices.begin());
}

void X3DImporter::GeometryHelper_Extend_PointToLine(const std::list<aiVector3D>& pPoint, std::list<aiVector3D>& pLine)
{
std::list<aiVector3D>::const_iterator pit = pPoint.begin();
std::list<aiVector3D>::const_iterator pit_last = pPoint.end();

	pit_last--;

	if(pPoint.size() < 2) Throw_ArgOutOfRange("GeometryHelper_Extend_PointToLine.pPoint.size() can not be less than 2.");

	// add first point of first line.
	pLine.push_back(*pit++);
	// add internal points
	while(pit != pit_last)
	{
		pLine.push_back(*pit);// second point of previous line
		pLine.push_back(*pit);// first point of next line
		pit++;
	}
	// add last point of last line
	pLine.push_back(*pit);
}

void X3DImporter::GeometryHelper_Extend_PolylineIdxToLineIdx(const std::list<int32_t>& pPolylineCoordIdx, std::list<int32_t>& pLineCoordIdx)
{
std::list<int32_t>::const_iterator plit = pPolylineCoordIdx.begin();

	while(plit != pPolylineCoordIdx.end())
	{
		// add first point of polyline
		pLineCoordIdx.push_back(*plit++);
		while((*plit != (-1)) && (plit != pPolylineCoordIdx.end()))
		{
			std::list<int32_t>::const_iterator plit_next;

			plit_next = plit, plit_next++;
			pLineCoordIdx.push_back(*plit);// second point of previous line.
			pLineCoordIdx.push_back(-1);// delimiter
			if((*plit_next == (-1)) || (plit_next == pPolylineCoordIdx.end())) break;// current polyline is finished

			pLineCoordIdx.push_back(*plit);// first point of next line.
			plit = plit_next;
		}// while((*plit != (-1)) && (plit != pPolylineCoordIdx.end()))
	}// while(plit != pPolylineCoordIdx.end())
}

#define MESH_RectParallelepiped_CREATE_VERT \
aiVector3D vert_set[8]; \
float x1, x2, y1, y2, z1, z2, hs; \
 \
	hs = pSize.x / 2, x1 = -hs, x2 = hs; \
	hs = pSize.y / 2, y1 = -hs, y2 = hs; \
	hs = pSize.z / 2, z1 = -hs, z2 = hs; \
	vert_set[0].Set(x2, y1, z2); \
	vert_set[1].Set(x2, y2, z2); \
	vert_set[2].Set(x2, y2, z1); \
	vert_set[3].Set(x2, y1, z1); \
	vert_set[4].Set(x1, y1, z2); \
	vert_set[5].Set(x1, y2, z2); \
	vert_set[6].Set(x1, y2, z1); \
	vert_set[7].Set(x1, y1, z1)

void X3DImporter::GeometryHelper_MakeQL_RectParallelepiped(const aiVector3D& pSize, std::list<aiVector3D>& pVertices)
{
	MESH_RectParallelepiped_CREATE_VERT;
	MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 3, 2, 1, 0);// front
	MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 6, 7, 4, 5);// back
	MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 7, 3, 0, 4);// left
	MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 2, 6, 5, 1);// right
	MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 0, 1, 5, 4);// top
	MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 7, 6, 2, 3);// bottom
}

#undef MESH_RectParallelepiped_CREATE_VERT

void X3DImporter::GeometryHelper_CoordIdxStr2FacesArr(const std::list<int32_t>& pCoordIdx, std::vector<aiFace>& pFaces, unsigned int& pPrimitiveTypes) const
{
std::list<int32_t> f_data(pCoordIdx);
std::vector<unsigned int> inds;
unsigned int prim_type = 0;

	if(f_data.back() != (-1)) f_data.push_back(-1);

	// reserve average size.
	pFaces.reserve(f_data.size() / 3);
	inds.reserve(4);
//PrintVectorSet("build. ci", pCoordIdx);
	for(std::list<int32_t>::iterator it = f_data.begin(); it != f_data.end(); it++)
	{
		// when face is got count how many indices in it.
		if(*it == (-1))
		{
			aiFace tface;
			size_t ts;

			ts = inds.size();
			switch(ts)
			{
				case 0: goto mg_m_err;
				case 1: prim_type |= aiPrimitiveType_POINT; break;
				case 2: prim_type |= aiPrimitiveType_LINE; break;
				case 3: prim_type |= aiPrimitiveType_TRIANGLE; break;
				default: prim_type |= aiPrimitiveType_POLYGON; break;
			}

			tface.mNumIndices = ts;
			tface.mIndices = new unsigned int[ts];
			memcpy(tface.mIndices, inds.data(), ts * sizeof(unsigned int));
			pFaces.push_back(tface);
			inds.clear();
		}// if(*it == (-1))
		else
		{
			inds.push_back(*it);
		}// if(*it == (-1)) else
	}// for(std::list<int32_t>::iterator it = f_data.begin(); it != f_data.end(); it++)
//PrintVectorSet("build. faces", pCoordIdx);

	pPrimitiveTypes = prim_type;

	return;

mg_m_err:

	for(size_t i = 0, i_e = pFaces.size(); i < i_e; i++) delete [] pFaces.at(i).mIndices;

	pFaces.clear();
}

void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::list<aiColor3D>& pColors, const bool pColorPerVertex) const
{
std::list<aiColor4D> tcol;

	// create RGBA array from RGB.
	for(std::list<aiColor3D>::const_iterator it = pColors.begin(); it != pColors.end(); it++) tcol.push_back(aiColor4D((*it).r, (*it).g, (*it).b, 1));

	// call existing function for adding RGBA colors
	MeshGeometry_AddColor(pMesh, tcol, pColorPerVertex);
}

void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::list<aiColor4D>& pColors, const bool pColorPerVertex) const
{
std::list<aiColor4D>::const_iterator col_it = pColors.begin();

	if(pColorPerVertex)
	{
		if(pColors.size() < pMesh.mNumVertices)
		{
			throw DeadlyImportError("MeshGeometry_AddColor1. Colors count(" + std::to_string(pColors.size()) + ") can not be less than Vertices count(" +
									std::to_string(pMesh.mNumVertices) +  ").");
		}

		// copy colors to mesh
		pMesh.mColors[0] = new aiColor4D[pMesh.mNumVertices];
		for(size_t i = 0; i < pMesh.mNumVertices; i++) pMesh.mColors[0][i] = *col_it++;
	}// if(pColorPerVertex)
	else
	{
		if(pColors.size() < pMesh.mNumFaces)
		{
			throw DeadlyImportError("MeshGeometry_AddColor1. Colors count(" + std::to_string(pColors.size()) + ") can not be less than Faces count(" +
									std::to_string(pMesh.mNumFaces) +  ").");
		}

		// copy colors to mesh
		pMesh.mColors[0] = new aiColor4D[pMesh.mNumVertices];
		for(size_t fi = 0; fi < pMesh.mNumFaces; fi++)
		{
			// apply color to all vertices of face
			for(size_t vi = 0, vi_e = pMesh.mFaces[fi].mNumIndices; vi < vi_e; vi++) pMesh.mColors[0][pMesh.mFaces[fi].mIndices[vi]] = *col_it;

			col_it++;
		}
	}// if(pColorPerVertex) else
}

void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::list<int32_t>& pCoordIdx, const std::list<int32_t>& pColorIdx,
										const std::list<aiColor3D>& pColors, const bool pColorPerVertex) const
{
std::list<aiColor4D> tcol;

	// create RGBA array from RGB.
	for(std::list<aiColor3D>::const_iterator it = pColors.begin(); it != pColors.end(); it++) tcol.push_back(aiColor4D((*it).r, (*it).g, (*it).b, 1));

	// call existing function for adding RGBA colors
	MeshGeometry_AddColor(pMesh, pCoordIdx, pColorIdx, tcol, pColorPerVertex);
}

void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::list<int32_t>& pCoordIdx, const std::list<int32_t>& pColorIdx,
										const std::list<aiColor4D>& pColors, const bool pColorPerVertex) const
{
std::vector<aiColor4D> col_tgt_arr;
std::list<aiColor4D> col_tgt_list;
std::vector<aiColor4D> col_arr_copy;

	if(pCoordIdx.size() == 0) throw DeadlyImportError("MeshGeometry_AddColor2. pCoordIdx can not be empty.");

	// copy list to array because we are need indexed access to colors.
	col_arr_copy.reserve(pColors.size());
	for(std::list<aiColor4D>::const_iterator it = pColors.begin(); it != pColors.end(); it++) col_arr_copy.push_back(*it);

	if(pColorPerVertex)
	{
		if(pColorIdx.size() > 0)
		{
			// check indices array count.
			if(pColorIdx.size() < pCoordIdx.size())
			{
				throw DeadlyImportError("MeshGeometry_AddColor2. Colors indices count(" + std::to_string(pColorIdx.size()) +
										") can not be less than Coords inidces count(" + std::to_string(pCoordIdx.size()) +  ").");
			}
			// create list with colors for every vertex.
			col_tgt_arr.resize(pMesh.mNumVertices);
			for(std::list<int32_t>::const_iterator colidx_it = pColorIdx.begin(), coordidx_it = pCoordIdx.begin(); colidx_it != pColorIdx.end(); colidx_it++, coordidx_it++)
			{
				if(*colidx_it == (-1)) continue;// skip faces delimiter
				if((unsigned int)(*coordidx_it) > pMesh.mNumVertices) throw DeadlyImportError("MeshGeometry_AddColor2. Coordinate idx is out of range.");
				if((unsigned int)*colidx_it > pMesh.mNumVertices) throw DeadlyImportError("MeshGeometry_AddColor2. Color idx is out of range.");

				col_tgt_arr[*coordidx_it] = col_arr_copy[*colidx_it];
			}
		}// if(pColorIdx.size() > 0)
		else
		{
			// when color indices list is absent use CoordIdx.
			// check indices array count.
			if(pColors.size() < pMesh.mNumVertices)
			{
				throw DeadlyImportError("MeshGeometry_AddColor2. Colors count(" + std::to_string(pColors.size()) + ") can not be less than Vertices count(" +
										std::to_string(pMesh.mNumVertices) +  ").");
			}
			// create list with colors for every vertex.
			col_tgt_arr.resize(pMesh.mNumVertices);
			for(size_t i = 0; i < pMesh.mNumVertices; i++) col_tgt_arr[i] = col_arr_copy[i];
		}// if(pColorIdx.size() > 0) else
	}// if(pColorPerVertex)
	else
	{
		if(pColorIdx.size() > 0)
		{
			// check indices array count.
			if(pColorIdx.size() < pMesh.mNumFaces)
			{
				throw DeadlyImportError("MeshGeometry_AddColor2. Colors indices count(" + std::to_string(pColorIdx.size()) +
										") can not be less than Faces count(" + std::to_string(pMesh.mNumFaces) +  ").");
			}
			// create list with colors for every vertex using faces indices.
			col_tgt_arr.resize(pMesh.mNumFaces);

			std::list<int32_t>::const_iterator colidx_it = pColorIdx.begin();
			for(size_t fi = 0; fi < pMesh.mNumFaces; fi++)
			{
				if((unsigned int)*colidx_it > pMesh.mNumFaces) throw DeadlyImportError("MeshGeometry_AddColor2. Face idx is out of range.");

				col_tgt_arr[fi] = col_arr_copy[*colidx_it++];
			}
		}// if(pColorIdx.size() > 0)
		else
		{
			// when color indices list is absent use CoordIdx.
			// check indices array count.
			if(pColors.size() < pMesh.mNumFaces)
			{
				throw DeadlyImportError("MeshGeometry_AddColor2. Colors count(" + std::to_string(pColors.size()) + ") can not be less than Faces count(" +
										std::to_string(pMesh.mNumFaces) +  ").");
			}
			// create list with colors for every vertex using faces indices.
			col_tgt_arr.resize(pMesh.mNumFaces);
			for(size_t fi = 0; fi < pMesh.mNumFaces; fi++) col_tgt_arr[fi] = col_arr_copy[fi];

		}// if(pColorIdx.size() > 0) else
	}// if(pColorPerVertex) else

	// copy array to list for calling function that add colors.
	for(std::vector<aiColor4D>::const_iterator it = col_tgt_arr.begin(); it != col_tgt_arr.end(); it++) col_tgt_list.push_back(*it);
	// add prepared colors list to mesh.
	MeshGeometry_AddColor(pMesh, col_tgt_list, pColorPerVertex);
}

void X3DImporter::MeshGeometry_AddNormal(aiMesh& pMesh, const std::list<int32_t>& pCoordIdx, const std::list<int32_t>& pNormalIdx,
								const std::list<aiVector3D>& pNormals, const bool pNormalPerVertex) const
{
std::vector<size_t> tind;
std::vector<aiVector3D> norm_arr_copy;

	// copy list to array because we are need indexed access to normals.
	norm_arr_copy.reserve(pNormals.size());
	for(std::list<aiVector3D>::const_iterator it = pNormals.begin(); it != pNormals.end(); it++) norm_arr_copy.push_back(*it);

	if(pNormalPerVertex)
	{
		const std::list<int32_t>* srcidx;

		if(pNormalIdx.size() > 0)
		{
			// check indices array count.
			if(pNormalIdx.size() != pCoordIdx.size()) throw DeadlyImportError("Normals and Coords inidces count must be equal.");

			srcidx = &pNormalIdx;
		}
		else
		{
			srcidx = &pCoordIdx;
		}

		tind.reserve(srcidx->size());
		for(std::list<int32_t>::const_iterator it = srcidx->begin(); it != srcidx->end(); it++)
		{
			if(*it != (-1)) tind.push_back(*it);
		}

		// copy normals to mesh
		pMesh.mNormals = new aiVector3D[pMesh.mNumVertices];
		for(size_t i = 0; (i < pMesh.mNumVertices) && (i < tind.size()); i++)
		{
			if(tind[i] >= norm_arr_copy.size())
				throw DeadlyImportError("MeshGeometry_AddNormal. Normal index(" + std::to_string(tind[i]) +
										") is out of range. Normals count: " + std::to_string(norm_arr_copy.size()) + ".");

			pMesh.mNormals[i] = norm_arr_copy[tind[i]];
		}
	}// if(pNormalPerVertex)
	else
	{
		if(pNormalIdx.size() > 0)
		{
			if(pMesh.mNumFaces != pNormalIdx.size()) throw DeadlyImportError("Normals faces count must be equal to mesh faces count.");

			std::list<int32_t>::const_iterator normidx_it = pNormalIdx.begin();

			tind.reserve(pNormalIdx.size());
			for(size_t i = 0, i_e = pNormalIdx.size(); i < i_e; i++) tind.push_back(*normidx_it++);

		}
		else
		{
			tind.reserve(pMesh.mNumFaces);
			for(size_t i = 0; i < pMesh.mNumFaces; i++) tind.push_back(i);

		}

		// copy normals to mesh
		pMesh.mNormals = new aiVector3D[pMesh.mNumVertices];
		for(size_t fi = 0; fi < pMesh.mNumFaces; fi++)
		{
			aiVector3D tnorm;

			tnorm = norm_arr_copy[tind[fi]];
			for(size_t vi = 0, vi_e = pMesh.mFaces[fi].mNumIndices; vi < vi_e; vi++) pMesh.mNormals[pMesh.mFaces[fi].mIndices[vi]] = tnorm;
		}
	}// if(pNormalPerVertex) else
}

void X3DImporter::MeshGeometry_AddNormal(aiMesh& pMesh, const std::list<aiVector3D>& pNormals, const bool pNormalPerVertex) const
{
std::list<aiVector3D>::const_iterator norm_it = pNormals.begin();

	if(pNormalPerVertex)
	{
		if(pNormals.size() != pMesh.mNumVertices) throw DeadlyImportError("MeshGeometry_AddNormal. Normals and vertices count must be equal.");

		// copy normals to mesh
		pMesh.mNormals = new aiVector3D[pMesh.mNumVertices];
		for(size_t i = 0; i < pMesh.mNumVertices; i++) pMesh.mNormals[i] = *norm_it++;
	}// if(pNormalPerVertex)
	else
	{
		if(pNormals.size() != pMesh.mNumFaces) throw DeadlyImportError("MeshGeometry_AddNormal. Normals and faces count must be equal.");

		// copy normals to mesh
		pMesh.mNormals = new aiVector3D[pMesh.mNumVertices];
		for(size_t fi = 0; fi < pMesh.mNumFaces; fi++)
		{
			// apply color to all vertices of face
			for(size_t vi = 0, vi_e = pMesh.mFaces[fi].mNumIndices; vi < vi_e; vi++) pMesh.mNormals[pMesh.mFaces[fi].mIndices[vi]] = *norm_it;

			norm_it++;
		}
	}// if(pNormalPerVertex) else
}

void X3DImporter::MeshGeometry_AddTexCoord(aiMesh& pMesh, const std::list<int32_t>& pCoordIdx, const std::list<int32_t>& pTexCoordIdx,
								const std::list<aiVector2D>& pTexCoords) const
{
std::vector<aiVector3D> texcoord_arr_copy;
std::vector<aiFace> faces;
unsigned int prim_type;

	// copy list to array because we are need indexed access to normals.
	texcoord_arr_copy.reserve(pTexCoords.size());
	for(std::list<aiVector2D>::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); it++)
	{
		texcoord_arr_copy.push_back(aiVector3D((*it).x, (*it).y, 0));
	}

	if(pTexCoordIdx.size() > 0)
	{
		GeometryHelper_CoordIdxStr2FacesArr(pTexCoordIdx, faces, prim_type);
		if(!faces.size()) throw DeadlyImportError("Failed to add texture coordinates to mesh, faces list is empty.");
		if(faces.size() != pMesh.mNumFaces) throw DeadlyImportError("Texture coordinates faces count must be equal to mesh faces count.");
	}
	else
	{
		GeometryHelper_CoordIdxStr2FacesArr(pCoordIdx, faces, prim_type);
	}

	pMesh.mTextureCoords[0] = new aiVector3D[pMesh.mNumVertices];
	pMesh.mNumUVComponents[0] = 2;
	for(size_t fi = 0, fi_e = faces.size(); fi < fi_e; fi++)
	{
		if(pMesh.mFaces[fi].mNumIndices != faces.at(fi).mNumIndices)
			throw DeadlyImportError("Number of indices in texture face and mesh face must be equal. Invalid face index: " + std::to_string(fi) + ".");

		for(size_t ii = 0; ii < pMesh.mFaces[fi].mNumIndices; ii++)
		{
			size_t vert_idx = pMesh.mFaces[fi].mIndices[ii];
			size_t tc_idx = faces.at(fi).mIndices[ii];

			pMesh.mTextureCoords[0][vert_idx] = texcoord_arr_copy.at(tc_idx);
		}
	}// for(size_t fi = 0, fi_e = faces.size(); fi < fi_e; fi++)
}

void X3DImporter::MeshGeometry_AddTexCoord(aiMesh& pMesh, const std::list<aiVector2D>& pTexCoords) const
{
std::vector<aiVector3D> tc_arr_copy;

	if(pTexCoords.size() != pMesh.mNumVertices) throw DeadlyImportError("MeshGeometry_AddTexCoord. Texture coordinates and vertices count must be equal.");

	// copy list to array because we are need convert aiVector2D to aiVector3D and also get indexed access as a bonus.
	tc_arr_copy.reserve(pTexCoords.size());
	for(std::list<aiVector2D>::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); it++) tc_arr_copy.push_back(aiVector3D((*it).x, (*it).y, 0));

	// copy texture coordinates to mesh
	pMesh.mTextureCoords[0] = new aiVector3D[pMesh.mNumVertices];
	pMesh.mNumUVComponents[0] = 2;
	for(size_t i = 0; i < pMesh.mNumVertices; i++) pMesh.mTextureCoords[0][i] = tc_arr_copy[i];
}

aiMesh* X3DImporter::GeometryHelper_MakeMesh(const std::list<int32_t>& pCoordIdx, const std::list<aiVector3D>& pVertices) const
{
aiMesh* tmesh;
std::vector<aiFace> faces;
unsigned int prim_type = 0;
size_t ts;

	// create faces array from input string with vertices indices.
	GeometryHelper_CoordIdxStr2FacesArr(pCoordIdx, faces, prim_type);
	if(!faces.size()) throw DeadlyImportError("Failed to create mesh, faces list is empty.");

	//
	// Create new mesh and copy geometry data.
	//
	tmesh = new aiMesh;
	ts = faces.size();
	// faces
	tmesh->mFaces = new aiFace[ts];
	tmesh->mNumFaces = ts;
	for(size_t i = 0; i < ts; i++) tmesh->mFaces[i] = faces.at(i);

	// vertices
	std::list<aiVector3D>::const_iterator vit = pVertices.begin();

	ts = pVertices.size();
	tmesh->mVertices = new aiVector3D[ts];
	tmesh->mNumVertices = ts;
	for(size_t i = 0; i < ts; i++) tmesh->mVertices[i] = *vit++;

	// set primitives type and return result.
	tmesh->mPrimitiveTypes = prim_type;

	return tmesh;
}

/*********************************************************************************************************************************************/
/************************************************************ Functions: parse set ***********************************************************/
/*********************************************************************************************************************************************/

void X3DImporter::ParseHelper_Group_Begin(const bool pStatic)
{
CX3DImporter_NodeElement_Group* new_group = new CX3DImporter_NodeElement_Group(NodeElement_Cur, pStatic);// create new node with current node as parent.

	// if we are adding not the root element then add new element to current element child list.
	if(NodeElement_Cur != nullptr) NodeElement_Cur->Child.push_back(new_group);

	NodeElement_List.push_back(new_group);// it's a new element - add it to list.
	NodeElement_Cur = new_group;// switch current element to new one.
}

void X3DImporter::ParseHelper_Node_Enter(CX3DImporter_NodeElement* pNode)
{
	NodeElement_Cur->Child.push_back(pNode);// add new element to current element child list.
	NodeElement_Cur = pNode;// switch current element to new one.
}

void X3DImporter::ParseHelper_Node_Exit()
{
	// check if we can walk up.
	if(NodeElement_Cur != nullptr) NodeElement_Cur = NodeElement_Cur->Parent;
}

void X3DImporter::ParseHelper_FixTruncatedFloatString(const char* pInStr, std::string& pOutString)
{
size_t instr_len;

	pOutString.clear();
	instr_len = strlen(pInStr);
	if(!instr_len) return;

	pOutString.reserve(instr_len * 3 / 2);
	// check and correct floats in format ".x". Must be "x.y".
	if(pInStr[0] == '.') pOutString.push_back('0');

	pOutString.push_back(pInStr[0]);
	for(size_t ci = 1; ci < instr_len; ci++)
	{
		if((pInStr[ci] == '.') && ((pInStr[ci - 1] == ' ') || (pInStr[ci - 1] == '-') || (pInStr[ci - 1] == '+') || (pInStr[ci - 1] == '\t')))
		{
			pOutString.push_back('0');
			pOutString.push_back('.');
		}
		else
		{
			pOutString.push_back(pInStr[ci]);
		}
	}
}

void X3DImporter::ParseFile(const std::string& pFile, IOSystem* pIOHandler)
{
irr::io::IrrXMLReader* OldReader = mReader;// store current XMLreader.
std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb"));

	// Check whether we can read from the file
	if(file.get() == nullptr) throw DeadlyImportError("Failed to open X3D file " + pFile + ".");
	// generate a XML reader for it
	std::unique_ptr<CIrrXML_IOStreamReader> mIOWrapper(new CIrrXML_IOStreamReader(file.get()));
	mReader = irr::io::createIrrXMLReader(mIOWrapper.get());
	if(!mReader) throw DeadlyImportError("Failed to create XML reader for file" + pFile + ".");
	// start reading
	ParseNode_Root();

	delete mReader;
	// restore old XMLreader
	mReader = OldReader;
}

void X3DImporter::ParseNode_Root()
{
LogInfo("ParseNode_Root b");
	// search for root tag <X3D>
	if(!XML_SearchNode("X3D")) throw DeadlyImportError("Root node \"X3D\" not found.");

	ParseHelper_Group_Begin();// create root node element.
	// parse other contents
LogInfo("ParseNode_Root. read loop");
	while(mReader->read())
	{
		if(mReader->getNodeType() != irr::io::EXN_ELEMENT) continue;

		if(XML_CheckNode_NameEqual("head"))
			ParseNode_Head();
		else if(XML_CheckNode_NameEqual("Scene"))
			ParseNode_Scene();
		else
			XML_CheckNode_SkipUnsupported("Root");
	}
LogInfo("ParseNode_Root. end loop");

	// exit from root node element.
	ParseHelper_Node_Exit();
LogInfo("ParseNode_Root e");
}

void X3DImporter::ParseNode_Head()
{
bool close_found = false;// flag: true if close tag of node are found.

	while(mReader->read())
	{
		if(mReader->getNodeType() == irr::io::EXN_ELEMENT)
		{
			if(XML_CheckNode_NameEqual("meta"))
			{
				XML_CheckNode_MustBeEmpty();

				// adding metada from <head> as MetaString from <Scene>
				CX3DImporter_NodeElement_MetaString* ms = new CX3DImporter_NodeElement_MetaString(NodeElement_Cur);

				ms->Name = mReader->getAttributeValueSafe("name");
				// name can not be empty
				if(!ms->Name.empty())
				{
					ms->Value.push_back(mReader->getAttributeValueSafe("content"));
					NodeElement_List.push_back(ms);
					if(NodeElement_Cur != nullptr) NodeElement_Cur->Child.push_back(ms);

				}
			}// if(XML_CheckNode_NameEqual("meta"))
		}// if(mReader->getNodeType() == irr::io::EXN_ELEMENT)
		else if(mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
		{
			if(XML_CheckNode_NameEqual("head"))
			{
				close_found = true;

				break;
			}
		}// if(mReader->getNodeType() == irr::io::EXN_ELEMENT) else
	}// while(mReader->read())

	if(!close_found) Throw_CloseNotFound("head");

}

void X3DImporter::ParseNode_Scene()
{
auto GroupCounter_Increase = [](size_t& pCounter, const char* pGroupName) -> void
{
	pCounter++;
	if(pCounter == 0) throw DeadlyImportError("Group counter overflow. Too much groups with type: " + std::string(pGroupName) + ".");
};

auto GroupCounter_Decrease = [&](size_t& pCounter, const char* pGroupName) -> void
{
	if(pCounter == 0) Throw_TagCountIncorrect(pGroupName);

	pCounter--;
};

const char* GroupName_Group = "Group";
const char* GroupName_StaticGroup = "StaticGroup";
const char* GroupName_Transform = "Transform";
const char* GroupName_Switch = "Switch";

bool close_found = false;
size_t counter_group = 0;
size_t counter_transform = 0;
size_t counter_switch = 0;

	// while create static node? Because objects name used deeper in "USE" attribute can be equal to some meta in <head> node.
	ParseHelper_Group_Begin(true);
	while(mReader->read())
	{
		if(mReader->getNodeType() == irr::io::EXN_ELEMENT)
		{
			if(XML_CheckNode_NameEqual("Shape"))
			{
				ParseNode_Shape_Shape();
			}
			else if(XML_CheckNode_NameEqual(GroupName_Group))
			{
				GroupCounter_Increase(counter_group, GroupName_Group);
				ParseNode_Grouping_Group();
				// if node is empty then decrease group counter at this place.
				if(mReader->isEmptyElement()) GroupCounter_Decrease(counter_group, GroupName_Group);
			}
			else if(XML_CheckNode_NameEqual(GroupName_StaticGroup))
			{
				GroupCounter_Increase(counter_group, GroupName_StaticGroup);
				ParseNode_Grouping_StaticGroup();
				// if node is empty then decrease group counter at this place.
				if(mReader->isEmptyElement()) GroupCounter_Decrease(counter_group, GroupName_StaticGroup);
			}
			else if(XML_CheckNode_NameEqual(GroupName_Transform))
			{
				GroupCounter_Increase(counter_transform, GroupName_Transform);
				ParseNode_Grouping_Transform();
				// if node is empty then decrease group counter at this place.
				if(mReader->isEmptyElement()) GroupCounter_Decrease(counter_transform, GroupName_Transform);
			}
			else if(XML_CheckNode_NameEqual(GroupName_Switch))
			{
				GroupCounter_Increase(counter_switch, GroupName_Switch);
				ParseNode_Grouping_Switch();
				// if node is empty then decrease group counter at this place.
				if(mReader->isEmptyElement()) GroupCounter_Decrease(counter_switch, GroupName_Switch);
			}
			else if(XML_CheckNode_NameEqual("DirectionalLight"))
			{
				ParseNode_Lighting_DirectionalLight();
			}
			else if(XML_CheckNode_NameEqual("PointLight"))
			{
				ParseNode_Lighting_PointLight();
			}
			else if(XML_CheckNode_NameEqual("SpotLight"))
			{
				ParseNode_Lighting_SpotLight();
			}
			else if(XML_CheckNode_NameEqual("Inline"))
			{
				ParseNode_Networking_Inline();
			}
			else if(!ParseHelper_CheckRead_X3DMetadataObject())
			{
				XML_CheckNode_SkipUnsupported("Scene");
			}
		}// if(mReader->getNodeType() == irr::io::EXN_ELEMENT)
		else if(mReader->getNodeType() == irr::io::EXN_ELEMENT_END)
		{
			if(XML_CheckNode_NameEqual("Scene"))
			{
				close_found = true;

				break;
			}
			else if(XML_CheckNode_NameEqual(GroupName_Group))
			{
				GroupCounter_Decrease(counter_group, GroupName_Group);
				ParseNode_Grouping_GroupEnd();
			}
			else if(XML_CheckNode_NameEqual(GroupName_StaticGroup))
			{
				GroupCounter_Decrease(counter_group, GroupName_StaticGroup);
				ParseNode_Grouping_StaticGroupEnd();
			}
			else if(XML_CheckNode_NameEqual(GroupName_Transform))
			{
				GroupCounter_Decrease(counter_transform, GroupName_Transform);
				ParseNode_Grouping_TransformEnd();
			}
			else if(XML_CheckNode_NameEqual(GroupName_Switch))
			{
				GroupCounter_Decrease(counter_switch, GroupName_Switch);
				ParseNode_Grouping_SwitchEnd();
			}
		}// if(mReader->getNodeType() == irr::io::EXN_ELEMENT) else
	}// while(mReader->read())

	ParseHelper_Node_Exit();

	if(counter_group) Throw_TagCountIncorrect("Group");
	if(counter_transform) Throw_TagCountIncorrect("Transform");
	if(counter_switch) Throw_TagCountIncorrect("Switch");
	if(!close_found) Throw_CloseNotFound("Scene");

}

/*********************************************************************************************************************************************/
/******************************************************** Functions: BaseImporter set ********************************************************/
/*********************************************************************************************************************************************/

bool X3DImporter::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool pCheckSig) const
{
const std::string extension = GetExtension(pFile);

	if(extension == "x3d") return true;

	if(!extension.length() || pCheckSig)
	{
		const char* tokens[] = { "DOCTYPE X3D PUBLIC", "http://www.web3d.org/specifications/x3d" };

		return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 2);
	}

	return false;
}

void X3DImporter::GetExtensionList(std::set<std::string>& pExtensionList)
{
	pExtensionList.insert("x3d");
}

const aiImporterDesc* X3DImporter::GetInfo () const
{
	return &Description;
}

void X3DImporter::InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
{
	Clear();// delete old graph.
	mFileDir = DefaultIOSystem::absolutePath(pFile);
	ParseFile(pFile, pIOHandler);
	//
	// Assimp use static arrays of objects for fast speed of rendering. That's good, but need some additional operations/
	// We know that geometry objects(meshes) are stored in <Shape>, also in <Shape>-><Appearance> materials(in Assimp logical view)
	// are stored. So at first we need to count how meshes and materials are stored in scene graph.
	//
	// at first creating root node for aiScene.
	pScene->mRootNode = new aiNode;
	pScene->mRootNode->mParent = nullptr;
	pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED;
	//search for root node element
	NodeElement_Cur = NodeElement_List.front();
	while(NodeElement_Cur->Parent != nullptr) NodeElement_Cur = NodeElement_Cur->Parent;

	{// fill aiScene with objects.
		std::list<aiMesh*> mesh_list;
		std::list<aiMaterial*> mat_list;
		std::list<aiLight*> light_list;

		// create nodes tree
		Postprocess_BuildNode(*NodeElement_Cur, *pScene->mRootNode, mesh_list, mat_list, light_list);
		// copy needed data to scene
		if(mesh_list.size() > 0)
		{
			std::list<aiMesh*>::const_iterator it = mesh_list.begin();

			pScene->mNumMeshes = mesh_list.size();
			pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
			for(size_t i = 0; i < pScene->mNumMeshes; i++) pScene->mMeshes[i] = *it++;
		}

		if(mat_list.size() > 0)
		{
			std::list<aiMaterial*>::const_iterator it = mat_list.begin();

			pScene->mNumMaterials = mat_list.size();
			pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials];
			for(size_t i = 0; i < pScene->mNumMaterials; i++) pScene->mMaterials[i] = *it++;
		}

		if(light_list.size() > 0)
		{
			std::list<aiLight*>::const_iterator it = light_list.begin();

			pScene->mNumLights = light_list.size();
			pScene->mLights = new aiLight*[pScene->mNumLights];
			for(size_t i = 0; i < pScene->mNumLights; i++) pScene->mLights[i] = *it++;
		}
	}// END: fill aiScene with objects.

	///TODO: IME optimize tree
}

}// namespace Assimp

#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER