/*
								+----------------------------------+
								|                                  |
								| ***  ÜLame modelling script  *** |
								|                                  |
								|   Copyright © -tHE SWINe- 2008   |
								|                                  |
								|            Scene.cpp             |
								|                                  |
								+----------------------------------+
*/

/**
 *	@file dev/Scene.cpp
 *	@date 2008
 *	@author -tHE SWINe-
 *	@brief ÜLame modelling script
 *
 *	@date 2009-05-23
 *
 *	removed all instances of std::vector::reserve and replaced them by stl_ut::Reserve_*
 *
 *	@date 2009-10-11
 *
 *	replaced stl container ::resize() by stl_ut::Resize_*() to avoid unhandled
 *	std::bad_alloc
 *
 *	@date 2009-10-20
 *
 *	fixed some warnings when compiling under VC 2005, implemented "Security
 *	Enhancements in the CRT" for VC 2008. compare against MyProjects_2009-10-19_
 *
 */

#include "../NewFix.h"

#include "../CallStack.h"
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../Vector.h"
#include "PolyMesh.h"
#include "Scene.h"
#include "../StlUtils.h"

/*
 *								=== CScriptModeller::TMaterialNode ===
 */

/*
 *	CScriptModeller::TMaterialNode::TMaterialNode(int _n_id = -1)
 *		- default constructor; creates material with if _n_id
 *		  and default colors (defaults similar to OpenGL material
 *		  params defaults)
 */
CScriptModeller::TMaterialNode::TMaterialNode(int _n_id)
	:n_id(_n_id), v_emission(0, 0, 0, 1), v_ambient(.2f, .2f, .2f, 1),
	v_diffuse(.8f, .8f, .8f, 1), v_specular(0, 0, 0, 1),
	f_shininess(0), f_reflectance(0)
{}

/*
 *								=== ~CScriptModeller::TMaterialNode ===
 */

/*
 *								=== CScriptModeller::TGeometryNode ===
 */

/*
 *	CScriptModeller::TGeometryNode::TGeometryNode()
 *		- default constructor; sets t_transform to be identity matrix
 */
CScriptModeller::TGeometryNode::TGeometryNode()
{
	t_transform.Identity();
}

/*
 *	void CScriptModeller::TGeometryNode::Swap(TGeometryNode &r_node)
 *		- swaps contents of TGeometryNode and this
 */
void CScriptModeller::TGeometryNode::Swap(TGeometryNode &r_node)
{
	s_name.swap(r_node.s_name);
	t_mesh.Swap(r_node.t_mesh);
	Matrix4f t_tmp = t_transform;
	t_transform = r_node.t_transform;
	r_node.t_transform = t_tmp;
}

/*
 *								=== ~CScriptModeller::TGeometryNode ===
 */

/*
 *								=== CScriptModeller::CUtilParser ===
 */

/*
 *	static bool CScriptModeller::CUtilParser::ParseColor(CNodeInterface &r_parent_iface,
 *		const char *p_s_node_name, std::string &r_s_line, int n_type)
 *		- parses RGB(A) color, passes to r_parent_iface using SetFloatv with name n_type
 *		- returns true on success, false on failure
 */
bool CScriptModeller::CUtilParser::ParseColor(CNodeInterface &r_parent_iface,
	const char *p_s_node_name, std::string &r_s_line, int n_type)
{
	float p_color[4];
	if(!GetFloat(p_color[0], r_s_line) ||
	   !GetFloat(p_color[1], r_s_line) ||
	   !GetFloat(p_color[2], r_s_line))
		return false;
	// get RGB color

	if(!r_s_line.empty()) {
		if(!GetFloat(p_color[3], r_s_line) || !r_s_line.empty())
			return false; // should not contain additional params
	} else
		p_color[3] = 1;
	// get alpha

	return r_parent_iface.SetFloatv(n_type, 4, p_color);
}

/*
 *	static bool CScriptModeller::CUtilParser::ParseQuotedString(
 *		CNodeInterface &r_parent_iface, const char *p_s_node_name,
 *		std::string &r_s_line, int n_type)
 *		- parses quoted string, passes to r_parent_iface using SetStringv with name n_type
 *		- returns true on success, false on failure
 */
bool CScriptModeller::CUtilParser::ParseQuotedString(CNodeInterface &r_parent_iface,
	const char *p_s_node_name, std::string &r_s_line, int n_type)
{
	std::string s_texture;
	if(!GetQuotedString(s_texture, r_s_line) || !r_s_line.empty())
		return false; // should not contain additional params
	return r_parent_iface.SetStringv(n_type, s_texture);
}

/*
 *	static bool CScriptModeller::CUtilParser::ParseFloat(CNodeInterface &r_parent_iface,
 *		const char *p_s_node_name, std::string &r_s_line, int n_type)
 *		- parses float literal, passes to r_parent_iface using SetFloatv with name n_type
 *		- returns true on success, false on failure
 */
bool CScriptModeller::CUtilParser::ParseFloat(CNodeInterface &r_parent_iface,
	const char *p_s_node_name, std::string &r_s_line, int n_type)
{
	float f_value;
	if(!GetFloat(f_value, r_s_line) || !r_s_line.empty())
		return false; // should not contain additional params
	return r_parent_iface.SetFloatv(n_type, 1, &f_value);
}

/*
 *	static bool CScriptModeller::CUtilParser::ParseInteger(CNodeInterface &r_parent_iface,
 *		const char *p_s_node_name, std::string &r_s_line, int n_type)
 *		- parses int literal, passes to r_parent_iface using SetIntegerv with name n_type
 *		- returns true on success, false on failure
 */
bool CScriptModeller::CUtilParser::ParseInteger(CNodeInterface &r_parent_iface,
	const char *p_s_node_name, std::string &r_s_line, int n_type)
{
	int n_value;
	if(!GetInteger(n_value, r_s_line) || !r_s_line.empty())
		return false; // should not contain additional params
	return r_parent_iface.SetIntegerv(n_type, 1, &n_value);
}

/*
 *								=== ~CScriptModeller::CUtilParser ===
 */

/*
 *								=== CScriptModeller::CTransformParser ===
 */

/*
 *	static bool CScriptModeller::CTransformParser::ParseOffset(CNodeInterface &r_parent_iface,
 *		const char *p_s_node_name, std::string &r_s_line, int n_type)
 *		- parses offset transform node, creates transformation
 *		  matrix and pushes it to r_parent_iface transform
 *		- returns true on success, false on failure
 */
bool CScriptModeller::CTransformParser::ParseOffset(CNodeInterface &r_parent_iface,
	const char *p_s_node_name, std::string &r_s_line, int n_type)
{
	Vector3f v_offset;
	if(!GetFloat(v_offset.x, r_s_line) ||
	   !GetFloat(v_offset.y, r_s_line) ||
	   !GetFloat(v_offset.z, r_s_line) ||
	   !r_s_line.empty())
		return false;
	// offset vector

	Matrix4f t_matrix;
	t_matrix.Translation(v_offset);
	return r_parent_iface.PushTransform(t_matrix);
	// apply transformation
}

/*
 *	static bool CScriptModeller::CTransformParser::ParseScale(CNodeInterface &r_parent_iface,
 *		const char *p_s_node_name, std::string &r_s_line, int n_type)
 *		- parses scaling transform node, creates transformation
 *		  matrix and pushes it to r_parent_iface transform
 *		- returns true on success, false on failure
 */
bool CScriptModeller::CTransformParser::ParseScale(CNodeInterface &r_parent_iface,
	const char *p_s_node_name, std::string &r_s_line, int n_type)
{
	Vector3f v_scale;
	if(!GetFloat(v_scale.x, r_s_line))
		return false;
	if(!r_s_line.empty()) {
		if(!GetFloat(v_scale.y, r_s_line) ||
		   !GetFloat(v_scale.z, r_s_line) ||
		   !r_s_line.empty())
			return false;
	} else
		v_scale.y = v_scale.z = v_scale.x;
	// scale factor (uniform (1D), or generic (3D))

	Matrix4f t_matrix;
	t_matrix.Scaling(v_scale);
	return r_parent_iface.PushTransform(t_matrix);
	// apply transformation
}

/*
 *	static bool CScriptModeller::CTransformParser::ParseRotate(CNodeInterface &r_parent_iface,
 *		const char *p_s_node_name, std::string &r_s_line, int n_type)
 *		- parses rotation transform node, creates transformation
 *		  matrix and pushes it to r_parent_iface transform
 *		- returns true on success, false on failure
 */
bool CScriptModeller::CTransformParser::ParseRotate(CNodeInterface &r_parent_iface,
	const char *p_s_node_name, std::string &r_s_line, int n_type)
{
	float f_angle;
	Vector3f v_axis;
	if(!GetFloat(f_angle, r_s_line) ||
	   !GetFloat(v_axis.x, r_s_line) ||
	   !GetFloat(v_axis.y, r_s_line) ||
	   !GetFloat(v_axis.z, r_s_line) ||
	   !r_s_line.empty())
		return false;
	// offset vector

	f_angle = f_angle / 180 * f_pi;
	// convert from degrees to radians

	Matrix4f t_matrix;
	t_matrix.Rotation(f_angle, v_axis);
	return r_parent_iface.PushTransform(t_matrix);
	// apply transformation
}

/*
 *								=== ~CScriptModeller::CTransformParser ===
 */

/*
 *								=== CScriptModeller::CPolyMeshUtil ===
 */

class CScriptModeller::CPolyMeshUtil::CApplyTransform {
protected:
	Matrix4f m_transform, m_it_transform;

public:
	inline CApplyTransform(const Matrix4f &r_t_transform)
	{
		m_transform = r_t_transform;
		// copy transformation matrix

		m_it_transform = r_t_transform.t_FullInverse().t_Transpose();
		// normals transformed by inverse matrix
		// (not inverse-transpose, have to do extra transpose)

		Vector3f x = -m_it_transform.v_Right();
		x.Normalize();
		m_it_transform.Right(x);
		Vector3f y = -m_it_transform.v_Up();
		y.Normalize();
		m_it_transform.Up(y);
		Vector3f z = -m_it_transform.v_Dir();
		z.Normalize();
		m_it_transform.Dir(z);
		// make sure normal transformation doesn't affect normal length
	}

	inline void operator ()(CPolyMesh::TVertex *p_vertex) const
	{
		p_vertex->v_position = m_transform.v_Transform_Pos(p_vertex->v_position);
		p_vertex->v_normal = m_it_transform.v_Transform_Dir(p_vertex->v_normal);
	}
};

class CScriptModeller::CPolyMeshUtil::CSetVertexColor {
protected:
	Vector4f m_v_color;

public:
	CSetVertexColor(Vector4f v_color)
		:m_v_color(v_color)
	{}

	inline void operator ()(CPolyMesh::TVertex *p_vertex) const
	{
		p_vertex->v_color = m_v_color;
	}
};

/*
 *	static void CScriptModeller::CPolyMeshUtil::Apply_Transform(
 *		CPolyMesh &r_t_mesh, const Matrix4f &r_t_transform)
 *		- applies transformation described by matrix r_t_transform
 *		  to all vertices of r_t_mesh
 *		- note normals are transformed as well, using inverse-transpose of r_t_transform
 */
void CScriptModeller::CPolyMeshUtil::Apply_Transform(CPolyMesh &r_t_mesh,
	const Matrix4f &r_t_transform)
{
	r_t_mesh.Vertex_Pool().ForEach(0, r_t_mesh.n_Vertex_Num(), CApplyTransform(r_t_transform));
}

/*
 *	static void CScriptModeller::CPolyMeshUtil::Set_VertexColor(
 *		CPolyMesh &r_t_mesh, Vector4f v_color)
 *		- sets color of all vertices of r_t_mesh to v_color
 */
void CScriptModeller::CPolyMeshUtil::Set_VertexColor(CPolyMesh &r_t_mesh, Vector4f v_color)
{
	r_t_mesh.Vertex_Pool().ForEach(0, r_t_mesh.n_Vertex_Num(), CSetVertexColor(v_color));
}

/*
 *	static void CScriptModeller::CPolyMeshUtil::Set_MaterialId(
 *		CPolyMesh &r_t_mesh, int n_material_id)
 *		- sets material id of all polygons of r_t_mesh to n_material_id
 */
void CScriptModeller::CPolyMeshUtil::Set_MaterialId(CPolyMesh &r_t_mesh, int n_material_id)
{
	for(int i = 0, n = r_t_mesh.n_Poly_Num(); i < n; ++ i)
		r_t_mesh.r_Polygon(i).SetFlags(n_material_id);
}

/*
 *								=== ~CScriptModeller::CPolyMeshUtil ===
 */

/*
 *								=== CScriptModeller::CSceneIface ===
 */

class CScriptModeller::CSceneIface : public CScriptModeller::CNodeInterface {
protected:
	TScene *m_p_scene;

public:
	CSceneIface(TScene *p_scene)
		:m_p_scene(p_scene)
	{}

	virtual bool AddMaterial(const TMaterialNode &r_t_material)
	{
		std::vector<TMaterialNode> &r_list = m_p_scene->material_list;
		if(!stl_ut::Reserve_1More(r_list))
			return false;
		r_list.push_back(r_t_material);
		return true;
	}

	virtual bool AddGeometry(TGeometryNode &r_t_mesh_node)
	{
		std::vector<TGeometryNode> &r_list = m_p_scene->object_list;
		if(!stl_ut::Resize_Add_1More(r_list))
			return false;
		r_list.back().Swap(r_t_mesh_node);
		return true;
	}
};

/*
 *								=== ~CScriptModeller::CSceneIface ===
 */

/*
 *								=== CScriptModeller::CMatListIface ===
 */

class CScriptModeller::CMatListIface : public CScriptModeller::CNodeInterface {
protected:
	CNodeInterface &m_r_scene_iface;

public:
	CMatListIface(CNodeInterface &r_scene_iface)
		:m_r_scene_iface(r_scene_iface)
	{}

	static CNodeInterface *p_SpawnFunc(CNodeInterface &r_parent_iface,
		const char *p_s_node_name, std::string &r_s_line, int n_type)
	{
		if(!r_s_line.empty())
			return false; // should not contain additional params
		return new(std::nothrow) CMatListIface(r_parent_iface);
	}

	virtual bool AddMaterial(const TMaterialNode &r_t_material)
	{
		return m_r_scene_iface.AddMaterial(r_t_material);
	}
};

/*
 *								=== ~CScriptModeller::CMatListIface ===
 */

/*
 *								=== CScriptModeller::CMaterialIface ===
 */

class CScriptModeller::CMaterialIface : public CScriptModeller::CNodeInterface {
protected:
	CNodeInterface &m_r_matlist_iface;
	TMaterialNode m_t_material;

public:
	CMaterialIface(CNodeInterface &r_matlist_iface, int n_id)
		:m_r_matlist_iface(r_matlist_iface), m_t_material(n_id)
	{}

	static CNodeInterface *p_SpawnFunc(CNodeInterface &r_parent_iface,
		const char *p_s_node_name, std::string &r_s_line, int n_type)
	{
		int n_id;
		if(!GetInteger(n_id, r_s_line) || !r_s_line.empty())
			return false; // should not contain additional params
		return new(std::nothrow) CMaterialIface(r_parent_iface, n_id);
	}

	virtual bool SetFloatv(int n_name, int n_size, const float *p_value)
	{
		switch(n_name) {
		case type_Emission:
			_ASSERTE(n_size == 4);
			m_t_material.v_emission = Vector4f(p_value[0], p_value[1], p_value[2], p_value[3]);
			break;
		case type_Ambient:
			_ASSERTE(n_size == 4);
			m_t_material.v_ambient = Vector4f(p_value[0], p_value[1], p_value[2], p_value[3]);
			break;
		case type_Diffuse:
			_ASSERTE(n_size == 4);
			m_t_material.v_diffuse = Vector4f(p_value[0], p_value[1], p_value[2], p_value[3]);
			break;
		case type_Specular:
			_ASSERTE(n_size == 4);
			m_t_material.v_specular = Vector4f(p_value[0], p_value[1], p_value[2], p_value[3]);
			break;
		case type_Shininess:
			_ASSERTE(n_size == 1);
			m_t_material.f_shininess = *p_value;
			break;
		case type_Reflectance:
			_ASSERTE(n_size == 1);
			m_t_material.f_reflectance = *p_value;
			break;
		default:
			return false;
		};
		return true;
	}

	virtual bool SetStringv(int n_name, std::string &r_s_value)
	{
		if(n_name != type_Resource)
			return false;
		m_t_material.s_resource.swap(r_s_value);
		return true;
	}

	virtual bool FinishNode()
	{
		return m_r_matlist_iface.AddMaterial(m_t_material);
	}
};

/*
 *								=== ~CScriptModeller::CMaterialIface ===
 */

/*
 *								=== CScriptModeller::CGeometryIface ===
 */

class CScriptModeller::CGeometryIface : public CScriptModeller::CNodeInterface {
protected:
	CNodeInterface &m_r_scene_iface;
	TGeometryNode m_t_geometry;

public:
	CGeometryIface(CNodeInterface &r_scene_iface, std::string &r_s_name)
		:m_r_scene_iface(r_scene_iface)
	{
		m_t_geometry.s_name.swap(r_s_name);
	}

	static CNodeInterface *p_SpawnFunc(CNodeInterface &r_parent_iface,
		const char *p_s_node_name, std::string &r_s_line, int n_type)
	{
		std::string s_name;
		if(!GetQuotedString(s_name, r_s_line) || !r_s_line.empty())
			return false; // should not contain additional params
		return new(std::nothrow) CGeometryIface(r_parent_iface, s_name);
	}

	virtual bool FinishNode()
	{
		return m_r_scene_iface.AddGeometry(m_t_geometry);
	}

	virtual bool PushTransform(const Matrix4f &r_t_transform)
	{
		m_t_geometry.t_transform *= r_t_transform;
		return true;
	}

	virtual CPolyMesh *p_Geometry()
	{
		return &m_t_geometry.t_mesh;
	}

	virtual bool SetFloatv(int n_name, int n_size, const float *p_value)
	{
		if(n_name != type_Color)
			return false;
		_ASSERTE(n_size == 4);
		CPolyMeshUtil::Set_VertexColor(m_t_geometry.t_mesh,
			Vector4f(p_value[0], p_value[1], p_value[2], p_value[3]));
		return true;
	}

	virtual bool SetIntegerv(int n_name, int n_size, const int *p_value)
	{
		if(n_name != type_MaterialId)
			return false;
		_ASSERTE(n_size == 1);
		CPolyMeshUtil::Set_MaterialId(m_t_geometry.t_mesh, *p_value);
		return true;
	}
};

/*
 *								=== ~CScriptModeller::CGeometryIface ===
 */

/*
 *								=== CScriptModeller::CPlatPrimIface ===
 */

class CScriptModeller::CPlatPrimIface : public CScriptModeller::CNodeInterface {
protected:
	CNodeInterface &m_r_geometry_iface;
	CPolyMesh m_t_mesh;
	Matrix4f m_transform;
	bool m_b_transform_dirty;

public:
	CPlatPrimIface(CNodeInterface &r_geometry_iface, CPolyMesh &r_t_mesh)
		:m_r_geometry_iface(r_geometry_iface), m_b_transform_dirty(false)
	{
		m_t_mesh.Swap(r_t_mesh);
		m_transform.Identity();
	}

	static CNodeInterface *p_SpawnFunc(CNodeInterface &r_parent_iface,
		const char *p_s_node_name, std::string &r_s_line, int n_type)
	{
		if(n_type < type_GeoSphere || n_type > type_Torus)
			return 0;
		// make sure we've got the right geometry type

		int n_type_index = n_type - type_GeoSphere;
		// change type name to type index

		int n_additional_param_num = (n_type_index >= 6)? 1 : 0; // tube, torus
		// determine number of additional parameters

		float p_param[1];
		for(int i = 0; i < n_additional_param_num; ++ i) {
			if(!GetFloat(p_param[i], r_s_line))
				return 0;
		}
		// get parameters (mandatory)

		int p_tess[3];
		if(!r_s_line.empty()) {
			const int p_tess_dimension_list[] = {
				1, // geosphere (tesselation)
				2, // sphere (tess-axis, tess-radius)
				3, // box (tess-x, tess-y, tess-z)
				2, // plane (tess-x, tess-z)
				3, // cylinder (tess-axis, tess-radius, tess-cap)
				3, // cone (tess-axis, tess-radius, tess-cap)
				2, // tube (tess-axis, tess-radius)
				2 // torus (tess-major, tess-minor)
			};
			int n_tesselation_dimension = p_tess_dimension_list[n_type_index];
			// determine number of tesselation dimensions

			for(int i = 0; i < n_tesselation_dimension; ++ i) {
				if(!GetInteger(p_tess[i], r_s_line) || p_tess[i] < 0)
					return 0;
			}
			// parse tesselation

			if(!r_s_line.empty())
				return 0;
			// line is not supposed to contain more information
		} else {
			const int p_default_tess_list[][3] = {
				{3, 0, 0}, // geosphere
				{16, 32, 0}, // sphere
				{1, 1, 1}, // box
				{1, 1, 0}, // plane
				{1, 16, 1}, // cylinder
				{1, 16, 1}, // cone
				{1, 16, 0}, // tube
				{32, 16, 0} // torus
			};
			for(int i = 0; i < 3; ++ i)
				p_tess[i] = p_default_tess_list[n_type_index][i];
			// using 3 instead of n_tesselation_dimension (loop unroll)
		}
		// get tesselation (optional)

		CPolyMesh t_mesh;
		bool b_result = false;
		switch(n_type_index) {
		case 0: //type_GeoSphere:
			b_result = CMakeGeoSphere::MakeGeoSphere(t_mesh, p_tess[0]);
			break;
		case 1: //type_Sphere:
			b_result = CPlatonicPrimitives::MakeSphere(t_mesh, p_tess[0], p_tess[1]);
			break;
		case 2: //type_Box:
			b_result = CPlatonicPrimitives::MakeCube(t_mesh,
				p_tess[0], p_tess[1], p_tess[2]);
			break;
		case 3: //type_Plane:
			b_result = CPlatonicPrimitives::MakePlane(t_mesh, p_tess[0], p_tess[1]);
			break;
		case 4: //type_Cylinder:
			b_result = CPlatonicPrimitives::MakeCylinder(t_mesh,
				p_tess[0], p_tess[1], p_tess[2]);
			break;
		case 5: //type_Cone:
			b_result = CPlatonicPrimitives::MakeCone(t_mesh,
				p_tess[0], p_tess[1], p_tess[2]);
			break;
		case 6: //type_Tube:
			b_result = CPlatonicPrimitives::MakeTube(t_mesh,
				p_tess[0], p_tess[1], p_param[0]);
			break;
		case 7: //type_Torus:
			b_result = CPlatonicPrimitives::MakeTorus(t_mesh,
				p_tess[0], p_tess[1], p_param[0]);
			break;
		};
		if(!b_result)
			return 0;
		// create the primitive

		return new(std::nothrow) CPlatPrimIface(r_parent_iface, t_mesh);
	}

	virtual bool FinishNode()
	{
		ApplyTransform();
		// apply transform in case it wasn't done earlier

		CPolyMesh *p_mesh;
		if(!(p_mesh = m_r_geometry_iface.p_Geometry()))
			return false;
		return p_mesh->Merge(m_t_mesh);
		// merge mesh with geometry node mesh
	}

	virtual bool PushTransform(const Matrix4f &r_t_transform)
	{
		m_transform *= r_t_transform;
		m_b_transform_dirty = true;
		return true;
	}

	virtual CPolyMesh *p_Geometry()
	{
		ApplyTransform();
		return &m_t_mesh;
	}

	virtual bool SetFloatv(int n_name, int n_size, const float *p_value)
	{
		if(n_name != type_Color)
			return false;
		_ASSERTE(n_size == 4);
		CPolyMeshUtil::Set_VertexColor(m_t_mesh,
			Vector4f(p_value[0], p_value[1], p_value[2], p_value[3]));
		return true;
	}

	virtual bool SetIntegerv(int n_name, int n_size, const int *p_value)
	{
		if(n_name != type_MaterialId)
			return false;
		_ASSERTE(n_size == 1);
		CPolyMeshUtil::Set_MaterialId(m_t_mesh, *p_value);
		return true;
	}

protected:
	void ApplyTransform()
	{
		if(!m_b_transform_dirty)
			return;
		CPolyMeshUtil::Apply_Transform(m_t_mesh, m_transform);
		m_transform.Identity();
		m_b_transform_dirty = false;
	}
};

/*
 *								=== ~CScriptModeller::CPlatPrimIface ===
 */

/*
 *								=== CScriptModeller::CSurfaceIface ===
 */

class CScriptModeller::CSurfaceIface : public CScriptModeller::CNodeInterface {
public:
	typedef CSpline<Vector3f> CSpline3;

protected:
	CNodeInterface &m_r_geometry_iface;
	CPolyMesh m_t_mesh;

	Matrix4f m_transform;
	bool m_b_transform_dirty;

	std::vector<CSpline3*> m_spline_list;
	int m_n_spline_num;

	bool m_b_flip;

public:
	CSurfaceIface(CNodeInterface &r_geometry_iface, int n_spline_num, bool b_flip)
		:m_r_geometry_iface(r_geometry_iface), m_b_transform_dirty(false),
		m_n_spline_num(n_spline_num), m_b_flip(b_flip)
	{
		m_transform.Identity();
	}

	virtual ~CSurfaceIface()
	{
		std::for_each(m_spline_list.begin(), m_spline_list.end(), DeleteSpline);
	}

	virtual bool b_IsNodeComplete() const
	{
		return m_spline_list.size() == m_n_spline_num;
	}

	virtual bool FinishNode()
	{
		if(!b_IsNodeComplete())
			return false;
		// make sure surface was generated already

		ApplyTransform();
		// apply transform in case it wasn't done earlier

		CPolyMesh *p_mesh;
		if(!(p_mesh = m_r_geometry_iface.p_Geometry()))
			return false;
		return p_mesh->Merge(m_t_mesh);
		// merge mesh with geometry node mesh
	}

	virtual bool PushTransform(const Matrix4f &r_t_transform)
	{
		m_transform *= r_t_transform;
		m_b_transform_dirty = true;
		return true;
	}

	virtual CPolyMesh *p_Geometry()
	{
		if(!b_IsNodeComplete())
			return false;
		// make sure surface was generated already

		ApplyTransform();
		return &m_t_mesh;
	}

	virtual bool SetFloatv(int n_name, int n_size, const float *p_value)
	{
		if(!b_IsNodeComplete())
			return false;
		// make sure surface was generated already

		if(n_name != type_Color)
			return false;
		_ASSERTE(n_size == 4);
		CPolyMeshUtil::Set_VertexColor(m_t_mesh,
			Vector4f(p_value[0], p_value[1], p_value[2], p_value[3]));
		return true;
	}

	virtual bool SetIntegerv(int n_name, int n_size, const int *p_value)
	{
		if(!b_IsNodeComplete())
			return false;
		// make sure surface was generated already

		if(n_name != type_MaterialId)
			return false;
		_ASSERTE(n_size == 1);
		CPolyMeshUtil::Set_MaterialId(m_t_mesh, *p_value);
		return true;
	}

	virtual bool SetObject(int n_name, int n_size, void *p_pointer)
	{
		if(n_name != type_CBSpline || n_size != sizeof(CBezierCubicSpline<Vector3f>))
			return false; // check object type
		if(m_spline_list.size() > unsigned(m_n_spline_num))
			return false; // have had enough
		if(!stl_ut::Reserve_1More(m_spline_list))
			return false;
		m_spline_list.push_back((CSpline3*)p_pointer);
		return true;
	}

	static bool ParseCBSplineDef(CNodeInterface &r_parent_iface,
		const char *p_s_node_name, std::string &r_s_line, int n_type)
	{
		_ASSERTE(n_type == type_CBSpline);
		CBezierCubicSpline<Vector3f> *p_spline;
		if(!(p_spline = new(std::nothrow) CBezierCubicSpline<Vector3f>))
			return false;
		// create a new spline object

		while(!r_s_line.empty()) {
			Vector3f v_point;
			if(r_s_line[0] != '(') {
				delete p_spline;
				return false;
			}
			r_s_line.erase(0, 1);
			if(!GetFloat(v_point.x, r_s_line) ||
			   !GetFloat(v_point.y, r_s_line) ||
			   !GetFloat(v_point.z, r_s_line)) {
				delete p_spline;
				return false;
			}
			if(r_s_line[0] != ')') {
				delete p_spline;
				return false;
			}
			r_s_line.erase(0, 1);
			int b = 0, e = r_s_line.length();
			while(b < e && isspace(r_s_line[b]))
				++ b;
			r_s_line.erase(0, b);
			// get spline point

			if(!p_spline->PushBack(v_point)) {
				delete p_spline;
				return false;
			}
		}
		// parse spline points

		if(!r_parent_iface.SetObject(type_CBSpline,
		   sizeof(CBezierCubicSpline<Vector3f>), p_spline)) {
			delete p_spline;
			return false;
		}
		// pass spline to parent node (CSurfaceInterface)

		return true;
	}

protected:
	void ApplyTransform()
	{
		_ASSERTE(b_IsNodeComplete());
		if(!m_b_transform_dirty)
			return;
		CPolyMeshUtil::Apply_Transform(m_t_mesh, m_transform);
		m_transform.Identity();
		m_b_transform_dirty = false;
	}

	static inline void DeleteSpline(CSpline3 *p_spline)
	{
		delete p_spline;
	}
};

/*
 *								=== ~CScriptModeller::CSurfaceIface ===
 */

/*
 *								=== CScriptModeller::CExtrudeSurfIface ===
 */

class CScriptModeller::CExtrudeSurfIface : public CScriptModeller::CSurfaceIface {
protected:
	Vector3f m_v_extrude;
	int m_n_tess_spline, m_n_tess_extrusion;

public:
	CExtrudeSurfIface(CNodeInterface &r_geometry_iface, Vector3f v_extrude,
		int n_tess_spline, int n_tess_extrusion, bool b_flip)
		:CSurfaceIface(r_geometry_iface, 1, b_flip), m_v_extrude(v_extrude),
		m_n_tess_spline(n_tess_spline), m_n_tess_extrusion(n_tess_extrusion)
	{}

	virtual bool SetObject(int n_name, int n_size, void *p_pointer)
	{
		if(!CSurfaceIface::SetObject(n_name, n_size, p_pointer))
			return false;
		if(!CSurfaceIface::b_IsNodeComplete())
			return true;
		// add next spline, see if the node is complete

		_ASSERTE(m_spline_list.size() == 1);
		if(!CSplineSurf::MakeExtrudeSurf(m_t_mesh, *m_spline_list.front(),
		   m_v_extrude, m_n_tess_spline, m_n_tess_extrusion, m_b_flip)) {
			m_spline_list.erase(m_spline_list.end() - 1); // erase p_pointer from the list
			return false;
		}
		// create mesh

		return true;
	}

	static CNodeInterface *p_SpawnFunc(CNodeInterface &r_parent_iface,
		const char *p_s_node_name, std::string &r_s_line, int n_type)
	{
		if(n_type != type_ExtrudeSurf)
			return 0;
		// make sure we've got the right geometry type

		// extrude surface: spline, extrusion vector, 2D tesselation, flip

		Vector3f v_extrude;
		for(int i = 0; i < 3; ++ i) {
			if(!GetFloat(v_extrude[i], r_s_line))
				return 0;
		}
		// get extrusion vector (mandatory)

		int p_tess[2] = {16, 1};
		if(!r_s_line.empty() && isdigit(r_s_line[0])) {
			for(int i = 0; i < 2; ++ i) {
				if(!GetInteger(p_tess[i], r_s_line) || p_tess[i] < 0)
					return 0;
			}
			// parse tesselation
		}
		// get tesselation (optional)

		bool b_flip = false;
		if(!r_s_line.empty()) {
			if(!(r_s_line == "flip"))
				return 0;
			b_flip = true;
		}
		// flip? (that should be the last thing on the line)

		return new(std::nothrow) CExtrudeSurfIface(r_parent_iface, v_extrude, p_tess[0], p_tess[1], b_flip);
	}
};

/*
 *								=== ~CScriptModeller::CExtrudeSurfIface ===
 */

/*
 *								=== CScriptModeller::CRuledSurfIface ===
 */

class CScriptModeller::CRuledSurfIface : public CScriptModeller::CSurfaceIface {
protected:
	int m_n_tess_left_spline, m_n_tess_right_spline, m_n_tess_cross_section;

public:
	CRuledSurfIface(CNodeInterface &r_geometry_iface, int n_tess_left_spline,
		int n_tess_right_spline, int n_tess_cross_section, bool b_flip)
		:CSurfaceIface(r_geometry_iface, 2, b_flip),
		m_n_tess_left_spline(n_tess_left_spline),
		m_n_tess_right_spline(n_tess_right_spline),
		m_n_tess_cross_section(n_tess_cross_section)
	{}

	virtual bool SetObject(int n_name, int n_size, void *p_pointer)
	{
		if(!CSurfaceIface::SetObject(n_name, n_size, p_pointer))
			return false;
		if(!CSurfaceIface::b_IsNodeComplete())
			return true;
		// add next spline, see if the node is complete

		_ASSERTE(m_spline_list.size() == 2);
		if(!CSplineSurf::MakeRuledSurf(m_t_mesh, *m_spline_list[0], *m_spline_list[1],
		   m_n_tess_left_spline, m_n_tess_right_spline, m_n_tess_cross_section, m_b_flip)) {
			m_spline_list.erase(m_spline_list.end() - 1); // erase p_pointer from the list
			return false;
		}
		// create mesh

		return true;
	}

	static CNodeInterface *p_SpawnFunc(CNodeInterface &r_parent_iface,
		const char *p_s_node_name, std::string &r_s_line, int n_type)
	{
		if(n_type != type_RuledSurf)
			return 0;
		// make sure we've got the right geometry type

		// ruled surface: 2 splines, 3D tesselation, flip

		int p_tess[3] = {16, 16, 1};
		if(!r_s_line.empty() && isdigit(r_s_line[0])) {
			for(int i = 0; i < 3; ++ i) {
				if(!GetInteger(p_tess[i], r_s_line) || p_tess[i] < 0)
					return 0;
			}
			// parse tesselation
		}
		// get tesselation (optional)

		bool b_flip = false;
		if(!r_s_line.empty()) {
			if(!(r_s_line == "flip"))
				return 0;
			b_flip = true;
		}
		// flip? (that should be the last thing on the line)

		return new(std::nothrow) CRuledSurfIface(r_parent_iface, p_tess[0], p_tess[1], p_tess[2], b_flip);
	}
};

/*
 *								=== ~CScriptModeller::CRuledSurfIface ===
 */

/*
 *								=== CScriptModeller::CRevolSurfIface ===
 */

class CScriptModeller::CRevolSurfIface : public CScriptModeller::CSurfaceIface {
public:
	enum {
		cap_LeaveOpened,
		cap_Cap,
		cap_Weld
	};

protected:
	int m_n_tess_spline, m_n_tess_radius;
	int m_p_cap_mode[2];

public:
	CRevolSurfIface(CNodeInterface &r_geometry_iface, int n_tess_spline,
		int n_tess_radius, int n_bottom_cap_mode, int n_top_cap_mode, bool b_flip)
		:CSurfaceIface(r_geometry_iface, 1, b_flip),
		m_n_tess_spline(n_tess_spline), m_n_tess_radius(n_tess_radius)
	{
		m_p_cap_mode[0] = n_bottom_cap_mode;
		m_p_cap_mode[1] = n_top_cap_mode;
	}

	virtual bool SetObject(int n_name, int n_size, void *p_pointer)
	{
		if(!CSurfaceIface::SetObject(n_name, n_size, p_pointer))
			return false;
		if(!CSurfaceIface::b_IsNodeComplete())
			return true;
		// add next spline, see if the node is complete

		CBezierCubicSpline<Vector2f> t_2d_spline;
		const CSpline3 *p_3d_spline = m_spline_list.front();
		for(int i = 0, n = p_3d_spline->n_Point_Num(); i < n; ++ i) {
			if(!t_2d_spline.PushBack(p_3d_spline->t_Point(i).v_xy())) {
				m_spline_list.erase(m_spline_list.end() - 1); // erase p_pointer from the list
				return false;
			}
		}
		// convert 3d spline to 2d spline (think of more ellegant sollution?)

		_ASSERTE(m_spline_list.size() == 1);
		if(!CSplineSurf::MakeRevolutionSurf(m_t_mesh, t_2d_spline,
		   m_n_tess_spline, m_n_tess_radius, (m_p_cap_mode[0] == cap_Cap),
		   (m_p_cap_mode[1] == cap_Cap), (m_p_cap_mode[0] == cap_Weld),
		   (m_p_cap_mode[1] == cap_Weld), m_b_flip)) {
			m_spline_list.erase(m_spline_list.end() - 1); // erase p_pointer from the list
			return false;
		}
		// create mesh

		return true;
	}

	static CNodeInterface *p_SpawnFunc(CNodeInterface &r_parent_iface,
		const char *p_s_node_name, std::string &r_s_line, int n_type)
	{
		if(n_type != type_RevolutionSurf)
			return 0;
		// make sure we've got the right geometry type

		// revolution surf: spline, 2D tesselation, bottom cap, top cap, weld bottom, weld top, flip

		int p_tess[2] = {16, 16};
		if(!r_s_line.empty() && isdigit(r_s_line[0])) {
			for(int i = 0; i < 2; ++ i) {
				if(!GetInteger(p_tess[i], r_s_line) || p_tess[i] < 0)
					return 0;
			}
			// parse tesselation
		}
		// get tesselation (optional)

		int p_cap_mode[2] = {cap_LeaveOpened, cap_LeaveOpened};
		bool b_flip = false;
		for(int i = 0; i < 3; ++ i) {
			if(r_s_line.empty())
				break;
			std::string s_ident;
			if(!GetIdent(s_ident, r_s_line))
				return 0;
			if(s_ident == "flip") {
				b_flip = true;
				break;
			} else if(i == 2)
				return 0; // third must be flip
			else if(s_ident == "open")
				p_cap_mode[i] = cap_LeaveOpened;
			else if(s_ident == "cap")
				p_cap_mode[i] = cap_Cap;
			else if(s_ident == "weld")
				p_cap_mode[i] = cap_Weld;
			else
				return 0;
		}
		if(!r_s_line.empty())
			return 0;
		// parse flags

		return new(std::nothrow) CRevolSurfIface(r_parent_iface, p_tess[0], p_tess[1],
			p_cap_mode[0], p_cap_mode[1], b_flip);
	}
};

/*
 *								=== ~CScriptModeller::CRevolSurfIface ===
 */

/*
 *								=== CScriptModeller::CRailSweepSurfIface ===
 */

class CScriptModeller::CRailSweepSurfIface : public CScriptModeller::CSurfaceIface {
protected:
	int m_n_tess_rail, m_n_tess_connect_begin,
		m_n_tess_connect_end;
	bool m_b_unskew, m_b_scale;
	float m_f_cross_section_pos;

public:
	CRailSweepSurfIface(CNodeInterface &r_geometry_iface, int n_tess_rail,
		int n_tess_connect_begin, int n_tess_connect_end,
		bool b_unskew, bool b_scale, bool b_flip)
		:CSurfaceIface(r_geometry_iface, 4, b_flip),
		m_n_tess_rail(n_tess_rail), m_n_tess_connect_begin(n_tess_connect_begin),
		m_n_tess_connect_end(n_tess_connect_end), m_b_unskew(b_unskew), m_b_scale(b_scale)
	{}

	CRailSweepSurfIface(CNodeInterface &r_geometry_iface, int n_tess_rail,
		int n_tess_connect_begin, int n_tess_connect_end, float f_connect_pos,
		bool b_unskew, bool b_scale, bool b_flip)
		:CSurfaceIface(r_geometry_iface, 3, b_flip),
		m_n_tess_rail(n_tess_rail), m_n_tess_connect_begin(n_tess_connect_begin),
		m_n_tess_connect_end(n_tess_connect_end), m_f_cross_section_pos(f_connect_pos),
		m_b_unskew(b_unskew), m_b_scale(b_scale)
	{}

	virtual bool SetObject(int n_name, int n_size, void *p_pointer)
	{
		if(!CSurfaceIface::SetObject(n_name, n_size, p_pointer))
			return false;
		if(!CSurfaceIface::b_IsNodeComplete())
			return true;
		// add next spline, see if the node is complete

		if(m_spline_list.size() == 4) {
			if(!CSplineSurf::MakeRailSweep2(m_t_mesh, *m_spline_list[0], *m_spline_list[1],
			   *m_spline_list[2], *m_spline_list[3], m_n_tess_rail, m_n_tess_connect_begin,
			   m_n_tess_connect_end, m_b_flip, m_b_scale, m_b_unskew)) {
				m_spline_list.erase(m_spline_list.end() - 1); // erase p_pointer from the list
				return false;
			}
		} else {
			_ASSERTE(m_spline_list.size() == 3);
			if(!CSplineSurf::MakeRailSweep(m_t_mesh, *m_spline_list[0], *m_spline_list[1],
			   *m_spline_list[2], m_f_cross_section_pos, m_n_tess_rail, m_n_tess_connect_begin,
			   m_n_tess_connect_end, m_b_flip, m_b_scale, m_b_unskew)) {
				m_spline_list.erase(m_spline_list.end() - 1); // erase p_pointer from the list
				return false;
			}
		}
		m_t_mesh.CalcFaceNormals();
		m_t_mesh.CalcVertexNormals_Simple();
		// create mesh

		return true;
	}

	static CNodeInterface *p_SpawnFunc(CNodeInterface &r_parent_iface,
		const char *p_s_node_name, std::string &r_s_line, int n_type)
	{
		if(n_type != type_RailSweepSurf && n_type != type_RailSweep2Surf)
			return 0;
		// make sure we've got the right geometry type

		// rail sweep surf: 3 splines, float position, 3D tesselation, flip, scale, unskew
		// rail sweep 2 surf: 4 splines, 3D tesselation, flip, scale, unskew

		float f_position;
		if(n_type == type_RailSweepSurf && !GetFloat(f_position, r_s_line))
			return 0;
		// parse position parameter (type_RailSweepSurf only)

		int p_tess[3] = {16, 16, 16};
		if(!r_s_line.empty() && isdigit(r_s_line[0])) {
			for(int i = 0; i < 3; ++ i) {
				if(!GetInteger(p_tess[i], r_s_line) || p_tess[i] < 0)
					return 0;
			}
			// parse tesselation
		}
		// get tesselation (optional)

		bool b_scale = true, b_unskew = false;
		bool b_flip = false;
		while(!r_s_line.empty()) {
			std::string s_ident;
			if(r_s_line.empty())
				break;
			if(!GetIdent(s_ident, r_s_line))
				return 0;
			if(s_ident == "flip")
				b_flip = true;
			else if(s_ident == "unskew")
				b_unskew = true;
			else if(s_ident == "noscale")
				b_scale = false;
			else
				return 0;
		}
		// parse flags

		if(n_type == type_RailSweepSurf) {
			return new(std::nothrow) CRailSweepSurfIface(r_parent_iface, p_tess[0], p_tess[1], p_tess[2],
				f_position, b_unskew, b_scale, b_flip);
		} else {
			return new(std::nothrow) CRailSweepSurfIface(r_parent_iface, p_tess[0], p_tess[1], p_tess[2],
				b_unskew, b_scale, b_flip);
		}
	}
};

/*
 *								=== ~CScriptModeller::CRailSweepSurfIface ===
 */

/*
 *								=== CScriptModeller::CUVMapIface ===
 */

class CScriptModeller::CUVMapIface : public CScriptModeller::CNodeInterface {
protected:
	CPolyMesh *m_p_mesh;
	Matrix4f m_t_gizmo_transform;
	int m_n_gizmo_type;
	int m_n_tex_channel, m_n_s_index, m_n_t_index;

public:
	CUVMapIface(CPolyMesh *p_mesh, int n_gizmo_type,
		int n_tex_channel, int n_s_index, int n_t_index)
		:m_p_mesh(p_mesh), m_n_gizmo_type(n_gizmo_type),
		m_n_tex_channel(n_tex_channel), m_n_s_index(n_s_index),
		m_n_t_index(n_t_index)
	{
		m_t_gizmo_transform.Identity();
	}

	static CNodeInterface *p_SpawnFunc(CNodeInterface &r_parent_iface,
		const char *p_s_node_name, std::string &r_s_line, int n_type)
	{
		// uv_map gizmo [channel] [coord-s] [coord-t]

		std::string s_gizmo;
		if(!GetIdent(s_gizmo, r_s_line))
			return 0;
		// get gizmo name

		const struct {
			const char *p_s_name;
			int n_type;
		} p_gizmo_name_list[] = {
			{"planar", CTexCoordGen::map_Plane},
			{"box", CTexCoordGen::map_Box},
			{"spherical", CTexCoordGen::map_Sphere},
			{"cylindrical", CTexCoordGen::map_Cylinder},
			{"cylindrical_caps", CTexCoordGen::map_Cylinder_Caps}
		};
		const int n_gizmo_name_num = sizeof(p_gizmo_name_list) /
		   sizeof(p_gizmo_name_list[0]);
		_ASSERTE(n_gizmo_name_num);
		int n_gizmo_type;
		for(int i = 0;; ++ i) {
			if(s_gizmo == p_gizmo_name_list[i].p_s_name) {
				n_gizmo_type = p_gizmo_name_list[i].n_type;
				break;
			}
			if(i + 1 == n_gizmo_name_num)
				return 0; // not found
		}
		// translate gizmo name

		int n_channel = 0, n_s_coord = 0, n_t_coord = 1;
		if(!r_s_line.empty()) {
			if(!GetInteger(n_channel, r_s_line))
				return 0;
			if(!r_s_line.empty()) {
				if(!GetInteger(n_s_coord, r_s_line))
					return 0;
				if(!r_s_line.empty()) {
					if(!GetInteger(n_t_coord, r_s_line))
						return 0;
				} else
					n_t_coord = (n_s_coord + 1) % 4; // use next component
			}
		}
		if(n_channel < 0 || n_channel >= CPolyMesh::TVertex::n_Texcoord_Num ||
		   n_s_coord < 0 || n_s_coord >= 4 || n_t_coord < 0 || n_t_coord >= 4)
			return 0;
		// get texcoord channel and component indices (in case index for s is supplied
		// and index for t is not, it is just the next component)

		if(!r_s_line.empty())
			return 0;
		// there is not supposed to be more information

		CPolyMesh *p_mesh;
		if(!(p_mesh = r_parent_iface.p_Geometry()))
			return 0;
		// get mesh we're supposed to generate texcoords for

		if(n_gizmo_type == CTexCoordGen::map_Box ||
		   n_gizmo_type == CTexCoordGen::map_Cylinder_Caps)
			p_mesh->CalcFaceNormals();
		// this is required to tell box sides / caps

		return new(std::nothrow) CUVMapIface(p_mesh, n_gizmo_type,
			n_channel, n_s_coord, n_t_coord);
	}

	virtual bool PushTransform(const Matrix4f &r_t_transform)
	{
		m_t_gizmo_transform = r_t_transform * m_t_gizmo_transform;
		return true;
	}

	virtual bool FinishNode()
	{
		return CTexCoordGen::CalcTexcoords(*m_p_mesh,
			m_t_gizmo_transform.t_FullInverse() * CTexCoordGen::t_Gizmo(*m_p_mesh),
			m_n_gizmo_type, m_n_tex_channel, m_n_s_index, m_n_t_index);
	}
};

/*
 *								=== ~CScriptModeller::CUVMapIface ===
 */

/*
 *								=== CScriptModeller ===
 */

const CScriptModeller::TNodeHandler CScriptModeller::m_p_handler_list[] = {
	{"materials", 0, node_MatList, 0, CMatListIface::p_SpawnFunc},
		{"material", 0, node_Material, 0, CMaterialIface::p_SpawnFunc},
			{"resource", type_Resource, node_Material, CUtilParser::ParseQuotedString, 0},
			{"emission", type_Emission, node_Material, CUtilParser::ParseColor, 0},
			{"ambient", type_Ambient, node_Material, CUtilParser::ParseColor, 0},
			{"diffuse", type_Diffuse, node_Material, CUtilParser::ParseColor, 0},
			{"specular", type_Specular, node_Material, CUtilParser::ParseColor, 0},
			{"shininess", type_Shininess, node_Material, CUtilParser::ParseFloat, 0},
			{"reflectance", type_Reflectance, node_Material, CUtilParser::ParseFloat, 0},
	// materials

	{"node", 0, node_Geometry, 0, CGeometryIface::p_SpawnFunc},
		{"geosphere", type_GeoSphere, node_PlatPrim, 0, CPlatPrimIface::p_SpawnFunc},
		{"sphere", type_Sphere, node_PlatPrim, 0, CPlatPrimIface::p_SpawnFunc},
		{"box", type_Box, node_PlatPrim, 0, CPlatPrimIface::p_SpawnFunc},
		{"plane", type_Plane, node_PlatPrim, 0, CPlatPrimIface::p_SpawnFunc},
		{"cylinder", type_Cylinder, node_PlatPrim, 0, CPlatPrimIface::p_SpawnFunc},
		{"cone", type_Cone, node_PlatPrim, 0, CPlatPrimIface::p_SpawnFunc},
		{"tube", type_Tube, node_PlatPrim, 0, CPlatPrimIface::p_SpawnFunc},
		{"torus", type_Torus, node_PlatPrim, 0, CPlatPrimIface::p_SpawnFunc},
		// platonic primitives

		{"extrude_surf", type_ExtrudeSurf, node_Surface, 0, CExtrudeSurfIface::p_SpawnFunc},
		{"ruled_surf", type_RuledSurf, node_Surface, 0, CRuledSurfIface::p_SpawnFunc},
		{"revol_surf", type_RevolutionSurf, node_Surface, 0, CRevolSurfIface::p_SpawnFunc},
		{"rail_sweep_surf", type_RailSweepSurf, node_Surface, 0, CRailSweepSurfIface::p_SpawnFunc},
		{"rail_sweep2_surf", type_RailSweep2Surf, node_Surface, 0, CRailSweepSurfIface::p_SpawnFunc},
			{"cbspline", type_CBSpline, node_Surface, CSurfaceIface::ParseCBSplineDef, 0},
		// spline surfaces

		{"off", type_Offset, node_Transform, CTransformParser::ParseOffset, 0},
		{"scl", type_Scale, node_Transform, CTransformParser::ParseScale, 0},
		{"rot", type_Rotate, node_Transform, CTransformParser::ParseRotate, 0},
		// object transforms

		{"color", type_Color, node_Geometry, CUtilParser::ParseColor, 0},
		{"mat_id", type_MaterialId, node_Geometry, CUtilParser::ParseInteger, 0},
		// object properties

		{"uv_map", 0, node_UVMapping, 0, CUVMapIface::p_SpawnFunc},
/*
		{"uv_mod", 0, node_UVMapping, 0, CUVModIface::p_SpawnFunc}, // todo (channels; iface accepts transformations)
		{"uv_tangents", 0, node_UVMapping, CUVTangents::ParseGenerator, 0}, // todo (channels)
		// texgens

		{"optimize_verts", 0, node_Geometry, COptimizeVertices::ParseConfig, 0}, // todo (compare-*)
		{"vertex_normals", 0, node_Geometry, CVertexNormals::ParseConfig, 0}, // todo (separation angle)
		// vertex tools

		{"ffd", 0, node_Geometry, 0, CFFDIface::p_SpawnFunc}, // todo (ffd gets dimensions and inside itself, list of ctrl point positions)
		{"boolean", 0, node_Geometry, 0, CBooleanIface::p_SpawnFunc}, // todo (boolean has scene-like iface (accepts nodes), output is an object)
		// modifiers
*/
};

/*
 *	CScriptModeller::CScriptModeller()
 *		- default constructor; clears line counter
 */
CScriptModeller::CScriptModeller()
	:m_n_cur_line(0), m_p_scene(0)
{}

/*
 *	TScene *CScriptModeller::p_LoadScene(const char *p_s_file_name)
 *		- loads scene from file p_s_file_name
 *		- returns pointer to loaded scene on success or 0 on failure
 */
CScriptModeller::TScene *CScriptModeller::p_LoadScene(const char *p_s_file_name)
{
	m_n_cur_line = 0;
	if(!(m_p_reader = new(std::nothrow) CFileReader(p_s_file_name)))
		return 0;
	if(m_p_reader->b_Error()) {
		delete m_p_reader;
		m_p_reader = 0;
		return 0;
	}
	// open file

	_ASSERTE(m_node_stack.empty());

	if(!Parse()) {
		delete m_p_reader;
		m_p_reader = 0;
		if(m_p_scene) {
			delete m_p_scene;
			m_p_scene = 0;
		}
		std::for_each(m_node_stack.begin(),
			m_node_stack.end(), DeleteNodeIface);
		m_node_stack.clear();
		return 0;
	}
	delete m_p_reader;
	m_p_reader = 0;
	// parses script

	_ASSERTE(m_node_stack.empty());

	return m_p_scene;
}

/*
 *	TScene *CScriptModeller::p_LoadScene(const char *p_s_file_name)
 *		- loads scene from file p_s_file_name
 *		- returns pointer to loaded scene on success or 0 on failure
 */
CScriptModeller::TScene *CScriptModeller::p_LoadScene2(const char *p_s_scene_string)
{
	m_n_cur_line = 0;
	if(!(m_p_reader = new(std::nothrow) CStringReader(p_s_scene_string)))
		return 0;
	// create reader

	_ASSERTE(m_node_stack.empty());

	if(!Parse()) {
		delete m_p_reader;
		m_p_reader = 0;
		if(m_p_scene) {
			delete m_p_scene;
			m_p_scene = 0;
		}
		std::for_each(m_node_stack.begin(),
			m_node_stack.end(), DeleteNodeIface);
		m_node_stack.clear();
		return 0;
	}
	delete m_p_reader;
	m_p_reader = 0;
	// parses script

	_ASSERTE(m_node_stack.empty());

	return m_p_scene;
}

/*
 *	int CScriptModeller::n_Last_Line() const
 *		- returns number of line (zero-based index) where parser stopped,
 *		  useful for fixing errors in scene files
 */
int CScriptModeller::n_Last_Line() const
{
	return m_n_cur_line;
}

/*
 *	const std::string &CScriptModeller::s_Last_Line() const
 *		- returns contents of line where parser stopped
 *		- note whitespace from line beginning and end is stripped of
 */
const std::string &CScriptModeller::s_Last_Line() const
{
	return m_s_line;
}

/*
 *	static bool CScriptModeller::GetInteger(int &r_n_result, std::string &r_s_string)
 *		- utility function; parses integer value from r_s_string,
 *		  places it to r_n_result and erases it from the string
 *		  (including whitespace surrounding it)
 *		- returns true on success, false on failure
 */
bool CScriptModeller::GetInteger(int &r_n_result, std::string &r_s_string)
{
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
	if(sscanf_s(r_s_string.c_str(), "%d", &r_n_result) != 1)
#else // _MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
	if(sscanf(r_s_string.c_str(), "%d", &r_n_result) != 1)
#endif // _MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
		return false;
	// use library function to translate value

	int b = 0, e = r_s_string.length();
	while(b < e && isspace(r_s_string[b]))
		++ b; // skip any whitespace preceding data
	if(b < e && (r_s_string[b] == '-' || r_s_string[b] == '+'))
		++ b; // skip sign
	while(b < e && isdigit(r_s_string[b]))
		++ b; // skip integer number
	while(b < e && isspace(r_s_string[b]))
		++ b; // skip any whitespace follwoing data

	r_s_string.erase(0, b);
	// erase value from string

	return true;
}

/*
 *	static bool CScriptModeller::GetFloat(float &r_f_result, std::string &r_s_string)
 *		- utility function; parses float value from r_s_string,
 *		  places it to r_f_result and erases it from the string
 *		  (including whitespace surrounding it)
 *		- returns true on success, false on failure
 */
bool CScriptModeller::GetFloat(float &r_f_result, std::string &r_s_string)
{
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
	if(sscanf_s(r_s_string.c_str(), "%f", &r_f_result) != 1)
#else // _MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
	if(sscanf(r_s_string.c_str(), "%f", &r_f_result) != 1)
#endif // _MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
		return false;
	// use library function to translate value

	int b = 0, e = r_s_string.length();
	while(b < e && isspace(r_s_string[b]))
		++ b; // skip any whitespace preceding data
	if(b < e && (r_s_string[b] == '-' || r_s_string[b] == '+'))
		++ b; // skip sign
	while(b < e && isdigit(r_s_string[b]))
		++ b; // skip numbers
	if(b < e && r_s_string[b] == '.')
		++ b; // skip decimal point
	while(b < e && isdigit(r_s_string[b]))
		++ b; // skip numbers
	if(b < e && (r_s_string[b] == 'e' || r_s_string[b] == 'E')) {
		++ b; // skip 'e' letter
		if(b < e && (r_s_string[b] == '-' || r_s_string[b] == '+'))
			++ b; // skip sign
		while(b < e && isdigit(r_s_string[b]))
			++ b; // skip numbers
	} // skip exp part
	while(b < e && isspace(r_s_string[b]))
		++ b; // skip any whitespace follwoing data

	r_s_string.erase(0, b);
	// erase value from string

	return true;
}

/*
 *	static bool CScriptModeller::GetIdent(std::string &r_s_result, std::string &r_s_string)
 *		- utility function; parses c/c++ identifier from r_s_string,
 *		  places it to r_s_result and erases it from the string
 *		  (including whitespace surrounding it)
 *		- returns true on success, false on failure
 */
bool CScriptModeller::GetIdent(std::string &r_s_result, std::string &r_s_string)
{
	int b = 0, e = r_s_string.length();
	while(b < e && isspace(r_s_string[b]))
		++ b; // skip any whitespace preceding data
	int n_begin = b;
	if(b == e || (r_s_string[b] != '_' && !isalpha(r_s_string[b])))
		return false;
	++ b;
	while(b < e && (r_s_string[b] == '_' || isalpha(r_s_string[b]) || isdigit(r_s_string[b])))
		++ b;
	int n_end = b;
	while(b < e && isspace(r_s_string[b]))
		++ b; // skip any whitespace follwoing data

	if(!stl_ut::Reserve_N(r_s_result, n_end - n_begin))
		return false;
	r_s_result.assign(r_s_string.begin() + n_begin, r_s_string.begin() + n_end);
	// copy identifier to string

	r_s_string.erase(0, b);
	// erase value from string

	return true;
}

/*
 *	static bool CScriptModeller::GetQuotedString(std::string &r_s_result,
 *		std::string &r_s_string)
 *		- utility function; parses apostrophe quoted string literal
 *		  from r_s_string, places it to r_s_result and erases it from
 *		  the string (including whitespace surrounding it)
 *		- note this doesn't translate escape sequences, \' is illegal (it ends the string)
 *		- returns true on success, false on failure
 */
bool CScriptModeller::GetQuotedString(std::string &r_s_result, std::string &r_s_string)
{
	int b = 0, e = r_s_string.length();
	while(b < e && isspace(r_s_string[b]))
		++ b; // skip any whitespace preceding data
	if(b == e || r_s_string[b] != '\'')
		return false;
	int n_begin = ++ b;
	while(b < e && r_s_string[b] != '\'')
		++ b;
	if(b == e)
		return false; // must end with the other apostrophe
	int n_end = b;
	++ b;
	while(b < e && isspace(r_s_string[b]))
		++ b; // skip any whitespace follwoing data

	if(!stl_ut::Reserve_N(r_s_result, n_end - n_begin))
		return false;
	r_s_result.assign(r_s_string.begin() + n_begin, r_s_string.begin() + n_end);
	// copy identifier to string

	r_s_string.erase(0, b);
	// erase value from string

	return true;
}

/*
 *	static inline void CScriptModeller::DeleteNodeIface(CNodeInterface *p_iface)
 *		- helper cleanup function for std::for_each
 */
inline void CScriptModeller::DeleteNodeIface(CNodeInterface *p_iface)
{
	delete p_iface;
}

/*
 *	const TNodeHandler *CScriptModeller::p_NodeHandler(const std::string &r_s_node) const
 *		- returns const pointer to handler of node on
 *		  m_s_line or 0 if no such handler exists
 */
const CScriptModeller::TNodeHandler *CScriptModeller::p_NodeHandler(
	const std::string &r_s_node) const
{
	_ASSERTE(sizeof(m_p_handler_list));
	// make sure it's not empty, loop below counts on that

	for(int i = 0; i < sizeof(m_p_handler_list) / sizeof(m_p_handler_list[0]); ++ i) {
		if(r_s_node == m_p_handler_list[i].p_s_name)
			return &m_p_handler_list[i];
	}
	// find appropriate node handler

	return 0; // unknown node type
}

void CScriptModeller::TrimSpace(std::string &r_s_string)
{
	int b = 0, e = r_s_string.length();
	while(e > 0 && isspace(r_s_string[e - 1]))
		-- e;
	while(b < e && isspace(r_s_string[b]))
		++ b;
	r_s_string.erase(e);
	r_s_string.erase(0, b);
}

bool CScriptModeller::Parse()
{
	if(!(m_p_scene = new(std::nothrow) TScene))
		return false;
	// alloc new scene container

	_ASSERTE(m_node_stack.empty());
	stl_ut::Reserve_N(m_node_stack, 4); // stack height required for average scene
	if(m_node_stack.capacity() < 1)
		return false;
	CSceneIface *p_scene_i;
	if(!(p_scene_i = new(std::nothrow) CSceneIface(m_p_scene)))
		return false;
	m_node_stack.push_back(p_scene_i);
	// create base scene interface

	while(ReadLine()) {
		if(m_s_line == "}") {
			if(m_node_stack.empty() || !m_node_stack.back()->b_IsNodeComplete() ||
			   !m_node_stack.back()->FinishNode())
				return false;
			delete m_node_stack.back();
			m_node_stack.erase(m_node_stack.end() - 1);
			// finish node
		} else {
			std::string s_node_name;
			if(!GetIdent(s_node_name, m_s_line))
				return false;
			// get node name (and erase it from m_s_line)

			const TNodeHandler *p_handler;
			if(!(p_handler = p_NodeHandler(s_node_name)))
				return false;
			// find appropriate node handler

			bool b_contains_block_begin_char;
			if(b_contains_block_begin_char = m_s_line[m_s_line.length() - 1] == '{')
				m_s_line.erase(m_s_line.length() - 1);
			// see if it does contain block begin character (and erase it)

			TrimSpace(m_s_line);
			// trim whitespace

			if(p_handler->p_parse_func) {
				_ASSERTE(!p_handler->p_iface_spawn_func);
				// there shouldn't be both functions at the same time

				if(b_contains_block_begin_char)
					return false;
				// this node is not supposed to have subnodes

				if(!p_handler->p_parse_func(*m_node_stack.back(),
				   p_handler->p_s_name, m_s_line, p_handler->n_type))
					return false;
				// call parsing func (nodes like color, etc)
			} else /*if(p_handler->p_iface_spawn_func)*/ {
				_ASSERTE(p_handler->p_iface_spawn_func);

				if(b_contains_block_begin_char) {
					if(!stl_ut::Reserve_1More(m_node_stack))
						return false;
				}
				// make sure there's space in node stack

				CNodeInterface *p_iface;
				if(!(p_iface = p_handler->p_iface_spawn_func(*m_node_stack.back(),
				   p_handler->p_s_name, m_s_line, p_handler->n_type)))
					return false;
				// spawn node interface (nodes like material or object)

				if(b_contains_block_begin_char) {
					m_node_stack.push_back(p_iface);
					// add it to the stack
				} else {
					if(!p_iface->b_IsNodeComplete() ||
					   !p_iface->FinishNode())
						return false;
					delete p_iface;
					// finish node, delete interface
				}
			}
			// parse node definition (and maybe create node interface on top of the stack)
		}
	}
	if(m_p_reader->b_Error())
		return false; // i/o error

	if(m_node_stack.size() != 1)
		return false;
	// there sholud be scene interface node only

	delete m_node_stack.front();
	m_node_stack.clear();
	// delete scene interface node, clear the stack

	return true;
}

bool CScriptModeller::ReadLine()
{
	while(!m_p_reader->b_EOF()) {
		m_s_line.erase();
		for(int c = m_p_reader->n_GetChar(); c != '\n' && c != EOF; c = m_p_reader->n_GetChar()) {
			if(!stl_ut::Reserve_1More(m_s_line))
				return false;
			m_s_line += c;
		}
		// read line

		++ m_n_cur_line;
		// line counter for file debugging

		if(m_s_line.find('#') != std::string::npos)
			m_s_line.erase(m_s_line.find('#'));
		// throw away line comment

		TrimSpace(m_s_line);
		// throw away begin / end whitespace

		if(m_s_line.empty())
			continue;
		// skip empty lines

		return true;
	}

	return false;
}

/*
 *								=== ~CScriptModeller ===
 */

/*
 *								=== CScriptModeller::CNodeInterface ===
 */

/*
 *	virtual bool CScriptModeller::CNodeInterface::b_IsNodeComplete() const
 *		- returns true if node is complete, otherwise returns false
 *		- note by defaul all nodes are complete on line of their definition
 *		  so this always returns true
 */
bool CScriptModeller::CNodeInterface::b_IsNodeComplete() const
{
	return true;
}

/*
 *	virtual bool CScriptModeller::CNodeInterface::FinishNode()
 *		- finishes node; called upon '}' character
 *		- returns true on success, false on failuire
 *		- note by default no node needs finalization so this always returns true
 */
bool CScriptModeller::CNodeInterface::FinishNode()
{
	return true;
}

/*
 *	virtual bool CScriptModeller::CNodeInterface::AddMaterial(
 *		const TMaterialNode &r_t_material)
 *		- adds material definition r_t_material to the current node
 *		- returns true on success (node can accept material definition
 *		  and no error(s) occured), false on failuire
 *		- note default node can't have any subnodes so
 *		  this function always returns false
 */
bool CScriptModeller::CNodeInterface::AddMaterial(const TMaterialNode &r_t_material)
{
	return false;
}

/*
 *	virtual bool CScriptModeller::CNodeInterface::AddGeometry(TGeometryNode &r_t_mesh_node)
 *		- adds geometry node r_t_mesh_node to the current node
 *		  (use TGeometryNode::Swap() to avoid copying meshes)
 *		- returns true on success (node can accept polygonal object
 *		  and no error(s) occured), false on failuire
 *		- note default node can't have any subnodes so
 *		  this function always returns false
 */
bool CScriptModeller::CNodeInterface::AddGeometry(TGeometryNode &r_t_mesh_node)
{
	return false;
}

/*
 *	virtual bool CScriptModeller::CNodeInterface::PushTransform(
 *		const Matrix4f &r_t_transform)
 *		- adds polygonal object r_t_mesh to the current node (use CPolyMesh::Swap()
 *		  to avoid copying meshes)
 *		- returns true on success (node can accept transformation data
 *		  and no error(s) occured), false on failuire
 *		- note default node can't have any subnodes so
 *		  this function always returns false
 */
bool CScriptModeller::CNodeInterface::PushTransform(const Matrix4f &r_t_transform)
{
	return false;
}

/*
 *	virtual CPolyMesh *CScriptModeller::CNodeInterface::p_Geometry()
 *		- returns pointer to node geometry (this is intended to be used
 *		  for adding more geometry to mesh node and by texcoord generators
 *		  or mesh modifiers)
 *		- returns 0 if node doesn't contain geometry
 *		- note default node doesn't contain geometry so
 *		  this function always returns 0
 */
CPolyMesh *CScriptModeller::CNodeInterface::p_Geometry()
{
	return 0;
}

/*
 *	virtual bool CScriptModeller::CNodeInterface::SetFloatv(int n_name,
 *		int n_size, const float *p_value)
 *		- sets array of n_size float values p_value with name n_name
 *		- returns true on success (node can accept float data with name n_name
 *		  and no error(s) occured), false on failuire
 *		- note default node doesn't accept any data so
 *		  this function always returns false
 */
bool CScriptModeller::CNodeInterface::SetFloatv(int n_name, int n_size, const float *p_value)
{
	return false;
}

/*
 *	virtual bool CScriptModeller::CNodeInterface::SetIntegerv(int n_name,
 *		int n_size, const int *p_value)
 *		- sets array of n_size integer values p_value with name n_name
 *		- returns true on success (node can accept integer data with name n_name
 *		  and no error(s) occured), false on failuire
 *		- note default node doesn't accept any data so
 *		  this function always returns false
 */
bool CScriptModeller::CNodeInterface::SetIntegerv(int n_name, int n_size, const int *p_value)
{
	return false;
}

/*
 *	virtual bool CScriptModeller::CNodeInterface::SetObject(int n_name,
 *		int n_size, void *p_pointer)
 *		- sets object passed as pointer p_pointer with name n_name and size n_size
 *		- returns true on success (node can accept such and object
 *		  and no error(s) occured), false on failuire
 *		- note default node doesn't accept any objects so
 *		  this function always returns false
 *		- note in case this function fails, it's calleer's responsibility
 *		  to delete object (in case it's allocated on heap, that is)
 */
bool CScriptModeller::CNodeInterface::SetObject(int n_name, int n_size, void *p_pointer)
{
	return false;
}

/*
 *	virtual bool CScriptModeller::CNodeInterface::SetStringv(int n_name,
 *		std::string &r_s_value)
 *		- sets string value r_s_value (it's recommended to use std::string::swap()
 *		  instead of copying the string) with name n_name
 *		- returns true on success (node can accept string data with name n_name
 *		  and no error(s) occured), false on failuire
 *		- note default node doesn't accept any data so
 *		  this function always returns false
 */
bool CScriptModeller::CNodeInterface::SetStringv(int n_name, std::string &r_s_value)
{
	return false;
}

/*
 *								=== ~CScriptModeller::CNodeInterface ===
 */
