Commits

Jason McKesson  committed d045854

glscene: transform parsing works.

  • Participants
  • Parent commits 73614a0

Comments (0)

Files changed (6)

File glscene/Test/test.glscene

 	
 	node <foo>
 		layers 'foo' -'bar' +'bar' +-'foo'
+		
+		object_tm
+			translate (4)
+			scale (1)
+			orientation (0 0.707 0 0.707)
+			matrix (1)
+		end
+		
 		node <ident>
 			
 		end

File glscene/glscene_format.doxy

 	- `border_clamp`
 	- `repeat`
 	- `mirror_repeat`
-- `aniso number`: Specifies the anisotropic filtering value. An unparethesized number is provided; the number may be any form of integer or float. However, numbers less than 0 will be rejected.
+- `aniso (number)`: Specifies the anisotropic filtering value. An unparethesized number is provided; the number may be any form of integer or float. However, numbers less than 0 will be rejected.
 
 ## Camera Resource ## {#page_glscene_file_format_resources_camera}
 
 The contents of a node are rigidly ordered. The sub-commands must be provided in the following order:
 
 - `layers`: Optional. This provides a list of layers that the node is a member of. This list has some complexities to it, so there is [a section dedicated to them](\ref page_glscene_file_format_syntax_scene_node_layers).
+- `node_tm` and `object_tm`: Optional. The two commands can be in any order, but they can only be provided once and only before the next group of commands. These define the [node and object transforms](\ref module_glscene_node_transforms) for the node.
+- `node`: A child node of the current node. You may provide zero or more of them.
 
 ### Node Layers ### {#page_glscene_file_format_syntax_scene_node_layers}
 
 
 This will put the current node in 'shadow', but all child nodes will not be in 'shadow'.
 
+###Node Transforms### {#page_glscene_file_format_syntax_scene_node_transform}
+
+\verbatim
+node_tm
+//Transforms
+end
+object_tm
+//Transforms
+end
+\endverbatim
+
+These two transform commands appear after the `layers` (if present). They can appear in either order, but they must not be defined more than once.
+
+The contents of the commands are a sequence of transformations, to be applied in the order provided, from top to bottom. Each new transform is right-multiplied against the previous ones. You may specify as many transform commands in any order you wish.
+
+The transformation commands are:
+
+- `scale (vec3)`: Provides a scaling transform. This is a 3-element floating-point vector, so the elements must be in parentheses.
+- `translate (vec3)`: Provides a translation transform.
+- `orientation (quaternion)`: Provides an orientation transform as a quaternion.
+- `angle_axis (angle) (axis)`: Provides an angle/axis rotation transform as an angle/axis. `axis` is a vec3, while `angle` is a float. `angle` is in *degrees*.
+- `matrix (mat4)`: Provides an arbitrary matrix transform. This is a full 16-float, column-major matrix.
+
+#### Composed and Decomposed ####
+
+The glscene::TransformData class can exist either as a decomposed transform or a composed one. The decomposed state is especially useful, as it allows the user to adjust the rotation without impacting any existing scales or translation, and vice-versa.
+
+When creating transforms through the parsing system, they will only be in the decomposed state if you follow these rules. First, never use the `matrix` transform command; that will automatically compose your transform regardless of the contents of the matrix.
+
+Next, you must specify your transformations in this order:
+
+1. Zero or more `translate` commands.
+2. Zero or more `orientation` or `angle_axis` commands.
+3. Zero or more `scale` commands.
+
+If you follow these rules, the transform will remain decomposed.
+
+This results in a decomposed transform:
+
+~~~~
+node_tm
+  translate (4 5 3.2)
+  orientation (0 0.707 0 0.707)
+  scale (1)
+end
+~~~~
+
+This will be a composed transform:
+~~~~
+node_tm
+  translate (4 5 3.2)
+  scale (1)
+  orientation (0 0.707 0 0.707)
+end
+~~~~
+
+Even though the scale is an identity scale, it will still cause it to be composed.
+
 **/
 
 

File glscene/source/Parse.cpp

 #include "ParserUtils.h"
 #include "ParserExceptions.h"
 #include "ParserEnums.h"
+#include "ParsedData.h"
 
 namespace lex = boost::spirit::lex;
 namespace qi = boost::spirit::qi;
 		TOK_GRAPH_NAME,
 	};
 
+	const size_t g_nodeTransformTypesTokens[] =
+	{
+		TOK_NODE_TM,
+		TOK_OBJECT_TM,
+	};
+
+	const size_t g_transformTokens[] =
+	{
+		TOK_TRANSLATE,
+		TOK_SCALE,
+		TOK_ORIENTATION,
+		TOK_ANGLE_AXIS,
+		TOK_MATRIX,
+	};
+
 	//////////////////////////////////////////////////////////////////////////
 	// The lexer.
 	template <typename Lexer>
 			}
 
 			//Catch two keywords rammed together. Not a matched token, so it's bad.
-			this->self.add("[a-zA-Z]+", 0x0);
+			this->self.add("[a-zA-Z]+", 0x1);
 
 		}
 	};
 		{
 			if(!token_is_valid(tok))
 				throw BadTokenError(tok);
+			if((tok.id() & PREFIX_MASK) == 0)
+				throw BadTokenError(tok);
 			return true;
 		}
 	};
 	typedef simple_lexer<lexer_type>::iterator_type token_iterator;
 	typedef boost::iterator_range<token_iterator> token_range;
 
-	struct ParsedUniformDef
-	{
-		FilePosition pos;
-		UniformData data;
-	};
-
-	struct ParsedSamplerDef
-	{
-		FilePosition pos;
-		SamplerInfo sampler;
-	};
-
-	struct ParsedCameraDef
-	{
-		FilePosition pos;
-		glutil::ViewData view;
-		glutil::ViewScale scale;
-		glutil::MouseButtons actionButton;
-		bool bRightKeyboardCtrls;
-	};
-
-	struct ParsedBufferDef
-	{
-		FilePosition pos;
-		unsigned int size;
-		boost::optional<GLenum> creationUsage;
-	};
-
-	struct ParsedShader
-	{
-		FilePosition pos;
-		GLenum shaderType;
-		std::string filename;
-	};
-
-	struct ParsedProgramDef
-	{
-		FilePosition pos;
-		bool isSeparate;
-		std::vector<ParsedShader> shaders;
-		ProgramInfo info;
-	};
-
-	typedef boost::variant<boost::blank, int, std::string> MeshGenType;
-
-	struct ParsedMeshDef
-	{
-		FilePosition pos;
-		MeshGenType generator;
-		std::vector<int> params;
-	};
-
-	struct ParsedTextureDef
-	{
-		FilePosition pos;
-		boost::optional<std::string> filename;
-	};
-
-	template<typename Def>
-	FilePosition GetFilePosition(const Def &def) {return def.pos;}
-	FilePosition GetFilePosition(const FilePosition &pos) {return pos;}
-
-	template<typename Key, typename Def>
-	FilePosition GetPosFromDef(const std::pair<Key, Def> &pairDef) {return GetFilePosition(pairDef.second);}
-
-	typedef boost::container::flat_map<IdString, ParsedUniformDef> ParsedUniformMap;
-	typedef boost::container::flat_map<IdString, ParsedSamplerDef> ParsedSamplerMap;
-	typedef boost::container::flat_map<IdString, ParsedCameraDef> ParsedCameraMap;
-	typedef boost::container::flat_map<IdString, ParsedBufferDef> ParsedBufferMap;
-	typedef boost::container::flat_map<IdString, ParsedProgramDef> ParsedProgramMap;
-	typedef boost::container::flat_map<IdString, ParsedMeshDef> ParsedMeshMap;
-	typedef boost::container::flat_map<IdString, ParsedTextureDef> ParsedTextureMap;
-
-	struct ParsedResources
-	{
-		ParsedUniformMap uniforms;
-		ParsedSamplerMap samplers;
-		ParsedCameraMap cameras;
-		ParsedBufferMap uniformBuffers;
-		ParsedBufferMap storageBuffers;
-		ParsedProgramMap programs;
-		ParsedMeshMap meshes;
-		ParsedTextureMap textures;
-	};
-
-	typedef boost::container::flat_set<std::string> LayerSet;
-
-	struct ParsedNodeDef
-	{
-		FilePosition pos;
-		boost::optional<IdString> name;
-		LayerSet layers;
-		std::vector<int> childIndices;
-		int parentIx;
-
-		ParsedNodeDef& operator=(const ParsedNodeDef &other)
-		{
-			pos = other.pos;
-			name = other.name;
-			layers = other.layers;
-			return *this;
-		}
-	};
-
-	struct InheritedNodeData
-	{
-		int parentIx;
-		LayerSet layers;
-
-		InheritedNodeData() : parentIx(-1) {}
-	};
-
-	struct ParsedSceneGraphDef
-	{
-		FilePosition pos;
-		LayerSet layers;
-		std::vector<std::string> layerOrder;
-		boost::container::flat_set<IdString> variantChecks;
-		boost::container::flat_map<IdString, FilePosition> nodeNamePositions;
-		std::vector<ParsedNodeDef> nodes;
-	};
-
 	template<typename Range>
 	class SceneGraphParser
 	{
 			ExpectAndEatEndToken();
 		}
 
-		int ParseNode(const InheritedNodeData &inherit)
+		ParsedNodeDef *ParseNode(const InheritedNodeData &inherit)
 		{
 			ExpectToken(TOK_NODE);
 			PosStackPusher push(*this);
 			EatOneToken();
 
-			m_scene.nodes.push_back(ParsedNodeDef());
-			const int nodeIx = int(m_scene.nodes.size());
+			m_scene.nodes.emplace_back();
+
+			//Note: this is fine because `m_scene.nodes` is a stable_vector.
+			size_t size = m_scene.nodes.size();
+			ParsedNodeDef &node = m_scene.nodes[m_scene.nodes.size() - 1];	//stable_vector.back doesn't work.
+			node.pParent = inherit.pParent;
+			node.layers = inherit.layers;
+
 			InheritedNodeData myData = inherit;
-			myData.parentIx = nodeIx;
+			myData.pParent = &node;
 
+			if(IsCurrToken(TOK_IDENTIFIER))
 			{
-
-				ParsedNodeDef &node = m_scene.nodes.back();
-				node.layers = inherit.layers;
-				node.parentIx = inherit.parentIx;
-
-				if(IsCurrToken(TOK_IDENTIFIER))
-				{
-					IdString nodeId = ParseIdentifier(m_scene.nodeNamePositions, false, TOK_NODE);
-					m_scene.nodeNamePositions[nodeId] = m_posStack.top();
-					node.name = nodeId;
-				}
-
-				ExpectCategory(KEYWORD_ID_PREFIX);
-
-				if(IsCurrToken(TOK_LAYERS))
-				{
-					PosStackPusher push(*this);
-					EatOneToken();
-
-					while(!IsCurrTokenCategory(KEYWORD_ID_PREFIX))
-					{
-						bool layerInherit = false;
-						bool layerRemove = false;
-
-						if(IsCurrToken(TOK_PLUS_SIGN))
-						{
-							EatOneToken();
-							layerInherit = true;
-						}
-
-						if(IsCurrToken(TOK_MINUS_SIGN))
-						{
-							EatOneToken();
-							layerRemove = true;
-						}
-
-						try
-						{
-							ExpectToken(TOK_GRAPH_NAME);
-						}
-						catch(BaseParseError &)
-						{
-							if(IsCurrToken(TOK_PLUS_SIGN))
-								ThrowParseError("The `+` sign must always come before the `-` sign.", curr_throw);
-							else
-								ThrowParseError("Members of a `layers` list must be graph names, `+`, or `-`", curr_throw);
-						}
-						std::string layerName = GetStringTokenData();
-						if(m_scene.layers.find(layerName) == m_scene.layers.end())
-							ThrowParseError("The layer '" + layerName + "' was not listed in the scene graph `layer_defs`.", curr_throw);
-						EatOneToken();
-
-						if(layerRemove)
-							node.layers.erase(layerName);
-						else
-							node.layers.insert(layerName);
-
-						if(layerInherit)
-						{
-							if(layerRemove)
-								myData.layers.erase(layerName);
-							else
-								myData.layers.insert(layerName);
-						}
-					}
-				}
-
-				ExpectCategory(KEYWORD_ID_PREFIX);
+				IdString nodeId = ParseIdentifier(m_scene.nodeNamePositions, false, TOK_NODE);
+				m_scene.nodeNamePositions[nodeId] = m_posStack.top();
+				node.name = nodeId;
 			}
 
+			ExpectCategory(KEYWORD_ID_PREFIX);
+
+			if(IsCurrToken(TOK_LAYERS))
+			{
+				ParseNodeLayers(node, myData);
+			}
+
+			ExpectCategory(KEYWORD_ID_PREFIX);
+
+			TokenChecker hasBeenFound;
+			while(IsCurrTokenOneOf(g_nodeTransformTypesTokens))
+			{
+				CheckMultipleCommand(hasBeenFound, m_rng.front(), TOK_NODE);
+				if(IsCurrToken(TOK_NODE_TM))
+					node.nodeTM = ParseTransform();
+				else
+					node.objectTM = ParseTransform();
+			}
+
+			ExpectCategory(KEYWORD_ID_PREFIX);
+
 			while(IsCurrToken(TOK_NODE))
-			{
-				int childIx = ParseNode(myData);
-				m_scene.nodes[nodeIx].childIndices.push_back(childIx);
-			}
+				node.childNodes.push_back(ParseNode(myData));
 
 			try
 			{
 					throw;
 			}
 
-			return nodeIx;
+			return &node;
+		}
+
+		void ParseNodeLayers(ParsedNodeDef &node, InheritedNodeData &myData)
+		{
+			PosStackPusher push(*this);
+			EatOneToken();
+
+			while(!IsCurrTokenCategory(KEYWORD_ID_PREFIX))
+			{
+				bool layerInherit = false;
+				bool layerRemove = false;
+
+				if(IsCurrToken(TOK_PLUS_SIGN))
+				{
+					EatOneToken();
+					layerInherit = true;
+				}
+
+				if(IsCurrToken(TOK_MINUS_SIGN))
+				{
+					EatOneToken();
+					layerRemove = true;
+				}
+
+				try
+				{
+					ExpectToken(TOK_GRAPH_NAME);
+				}
+				catch(BaseParseError &)
+				{
+					if(IsCurrToken(TOK_PLUS_SIGN))
+						ThrowParseError("The `+` sign must always come before the `-` sign.", curr_throw);
+					else
+						ThrowParseError("Members of a `layers` list must be graph names, `+`, or `-`", curr_throw);
+				}
+				std::string layerName = GetStringTokenData();
+				if(m_scene.layers.find(layerName) == m_scene.layers.end())
+					ThrowParseError("The layer '" + layerName + "' was not listed in the scene graph `layer_defs`.", curr_throw);
+				EatOneToken();
+
+				if(layerRemove)
+					node.layers.erase(layerName);
+				else
+					node.layers.insert(layerName);
+
+				if(layerInherit)
+				{
+					if(layerRemove)
+						myData.layers.erase(layerName);
+					else
+						myData.layers.insert(layerName);
+				}
+			}
+		}
+
+		ParsedTransform ParseTransform()
+		{
+			PosStackPusher push(*this);
+			size_t tokId = m_rng.front().id();
+			EatOneToken();
+
+			//Start decomposed.
+			ParsedTransform ret = ParsedDecomposedTransform();
+
+			while(IsCurrTokenOneOf(g_transformTokens))
+			{
+				typename Range::value_type tok = m_rng.front();
+				EatOneToken();
+				switch(tok.id())
+				{
+				case TOK_TRANSLATE:
+					ApplyTranslation(ret, ParseVec3(tok, tokId));
+					break;
+				case TOK_SCALE:
+					ApplyScale(ret, ParseVec3(tok, tokId));
+					break;
+				case TOK_ORIENTATION:
+					ApplyOrientation(ret, ParseQuat(tok, tokId));
+					break;
+				case TOK_ANGLE_AXIS:
+					{
+						float angle = ParseSingleFloat(tok, tokId);
+						glm::vec3 axis = ParseVec3(tok, tokId);
+						ApplyOrientation(ret, glm::angleAxis(angle, axis));
+					}
+					break;
+				case TOK_MATRIX:
+					ApplyMatrix(ret, ParseMat4(tok, tokId));
+					break;
+				}
+			}
+
+			ExpectAndEatEndToken();
+
+			return ret;
 		}
 
 		template<typename MapType>
 			return std::string(rawToken.begin() + 1, rawToken.end() - 1);
 		}
 
+		glm::mat4 ParseMat4(const Token &owningTok, size_t owningId)
+		{
+			ExpectAndEatToken(TOK_OPEN_PAREN);
+
+			int count = 0;
+			glm::mat4 ret;
+
+			for(;
+				!m_rng.empty() && !HasTokenNoEmpty(TOK_CLOSE_PAREN);
+				EatOneToken(), ++count)
+			{
+				ExpectCategory(NUMBER_ID_PREFIX);
+				if(3 == count)
+				{
+					std::string msg = "The '" + GetTokenErrorName(owningTok.id()) +
+						"' mat4 only takes 16 numbers.";
+					ThrowParseError(msg, curr_throw);
+				}
+
+				ret[count / 4][count % 4] = boost::lexical_cast<float>(GetTokenText());
+			}
+
+			ExpectToken(TOK_CLOSE_PAREN);
+
+			if(3 != count && 1 != count)
+			{
+				std::string msg = "The '" + GetTokenErrorName(owningTok.id()) +
+					"' mat4 requires 16 numbers.";
+				ThrowParseError(msg, curr_throw);
+			}
+
+			if(1 == count)
+				ret = glm::mat4(ret[0][0]);
+
+			ExpectAndEatToken(TOK_CLOSE_PAREN);
+			return ret;
+		}
+
 		glm::vec3 ParseVec3(const Token &owningTok, size_t owningId)
 		{
 			ExpectAndEatToken(TOK_OPEN_PAREN);
 				ExpectCategory(NUMBER_ID_PREFIX);
 				if(3 == count)
 				{
-					std::string msg = "The '" + GetTokenErrorName(owningTok.id()) + "' only takes 3 numbers.";
+					std::string msg = "The '" + GetTokenErrorName(owningTok.id()) + "' vec3 only takes 3 numbers.";
 					ThrowParseError(msg, curr_throw);
 				}
 
 
 			if(3 != count && 1 != count)
 			{
-				std::string msg = "The '" + GetTokenErrorName(owningTok.id()) + "' requires 3 numbers.";
+				std::string msg = "The '" + GetTokenErrorName(owningTok.id()) + "' vec3 requires 3 numbers.";
 				ThrowParseError(msg, curr_throw);
 			}
 
 				return HasTokenNoEmpty(idExpected);
 		}
 
+		bool IsCurrTokenOneOf(array_ref<size_t> expecteds)
+		{
+			if(m_rng.empty())
+				throw UnexpectedDataError(expecteds[0]);
+
+			const Token &tok = m_rng.front();
+			BOOST_FOREACH(size_t idExpected, expecteds)
+			{
+				if(HasToken(tok, idExpected))
+					return true;
+			}
+
+			return false;
+		}
+
 		bool IsCurrTokenCategory(size_t prefix)
 		{
 			if(m_rng.empty())

File glscene/source/ParsedData.h

+#ifndef GLSDK_GLSCENE_PARSED_DATA_H
+#define GLSDK_GLSCENE_PARSED_DATA_H
+
+#include "ParserUtils.h"
+#include "ResourceData.h"
+#include <boost/container/flat_set.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/stable_vector.hpp>
+#include <boost/variant.hpp>
+#include <glutil/MousePoles.h>
+#include "TransformData.h"
+#include <glm/glm.hpp>
+#include <glm/gtc/quaternion.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+
+namespace glscene { namespace _detail {
+	struct ParsedUniformDef
+	{
+		FilePosition pos;
+		UniformData data;
+	};
+
+	struct ParsedSamplerDef
+	{
+		FilePosition pos;
+		SamplerInfo sampler;
+	};
+
+	struct ParsedCameraDef
+	{
+		FilePosition pos;
+		glutil::ViewData view;
+		glutil::ViewScale scale;
+		glutil::MouseButtons actionButton;
+		bool bRightKeyboardCtrls;
+	};
+
+	struct ParsedBufferDef
+	{
+		FilePosition pos;
+		unsigned int size;
+		boost::optional<GLenum> creationUsage;
+	};
+
+	struct ParsedShader
+	{
+		FilePosition pos;
+		GLenum shaderType;
+		std::string filename;
+	};
+
+	struct ParsedProgramDef
+	{
+		FilePosition pos;
+		bool isSeparate;
+		std::vector<ParsedShader> shaders;
+		ProgramInfo info;
+	};
+
+	typedef boost::variant<boost::blank, int, std::string> MeshGenType;
+
+	struct ParsedMeshDef
+	{
+		FilePosition pos;
+		MeshGenType generator;
+		std::vector<int> params;
+	};
+
+	struct ParsedTextureDef
+	{
+		FilePosition pos;
+		boost::optional<std::string> filename;
+	};
+
+	template<typename Def>
+	FilePosition GetFilePosition(const Def &def) {return def.pos;}
+	FilePosition GetFilePosition(const FilePosition &pos) {return pos;}
+
+	template<typename Key, typename Def>
+	FilePosition GetPosFromDef(const std::pair<Key, Def> &pairDef) {return GetFilePosition(pairDef.second);}
+
+	typedef boost::container::flat_map<IdString, ParsedUniformDef> ParsedUniformMap;
+	typedef boost::container::flat_map<IdString, ParsedSamplerDef> ParsedSamplerMap;
+	typedef boost::container::flat_map<IdString, ParsedCameraDef> ParsedCameraMap;
+	typedef boost::container::flat_map<IdString, ParsedBufferDef> ParsedBufferMap;
+	typedef boost::container::flat_map<IdString, ParsedProgramDef> ParsedProgramMap;
+	typedef boost::container::flat_map<IdString, ParsedMeshDef> ParsedMeshMap;
+	typedef boost::container::flat_map<IdString, ParsedTextureDef> ParsedTextureMap;
+
+	struct ParsedResources
+	{
+		ParsedUniformMap uniforms;
+		ParsedSamplerMap samplers;
+		ParsedCameraMap cameras;
+		ParsedBufferMap uniformBuffers;
+		ParsedBufferMap storageBuffers;
+		ParsedProgramMap programs;
+		ParsedMeshMap meshes;
+		ParsedTextureMap textures;
+	};
+
+	struct ParsedDecomposedTransform
+	{
+		DecomposedMatrix mat;
+		bool hasOrientation;
+		bool hasScale;
+
+		ParsedDecomposedTransform() : hasOrientation(false), hasScale(false) {}
+	};
+
+	typedef boost::variant<glm::mat4, ParsedDecomposedTransform> ParsedTransform;
+
+	inline void ApplyTranslation(ParsedTransform &tm, const glm::vec3 &translation)
+	{
+		if(tm.which() == 1)
+		{
+			ParsedDecomposedTransform &decomp = boost::get<ParsedDecomposedTransform>(tm);
+			if(!decomp.hasOrientation)
+			{
+				decomp.mat.translation += translation;
+				return;
+			}
+			
+			//Must compose.
+			tm = MatrixVisitor()(decomp.mat);
+		}
+
+		tm = glm::translate(boost::get<glm::mat4>(tm), translation);
+	}
+
+	inline void ApplyOrientation(ParsedTransform &tm, const glm::quat &orient)
+	{
+		if(tm.which() == 1)
+		{
+			ParsedDecomposedTransform &decomp = boost::get<ParsedDecomposedTransform>(tm);
+			decomp.hasOrientation = true;
+			if(!decomp.hasScale)
+			{
+				decomp.mat.orientation = decomp.mat.orientation * orient;
+				return;
+			}
+
+			//Must compose.
+			tm = MatrixVisitor()(decomp.mat);
+		}
+
+		tm = boost::get<glm::mat4>(tm) * glm::mat4_cast(orient);
+	}
+
+	inline void ApplyScale(ParsedTransform &tm, const glm::vec3 &scale)
+	{
+		if(tm.which() == 1)
+		{
+			ParsedDecomposedTransform &decomp = boost::get<ParsedDecomposedTransform>(tm);
+			decomp.hasOrientation = true;
+			decomp.hasScale = true;
+			decomp.mat.scale *= scale;
+			return;
+		}
+
+		tm = glm::scale(boost::get<glm::mat4>(tm), scale);
+	}
+
+	inline void ApplyMatrix(ParsedTransform &tm, const glm::mat4 &matrix)
+	{
+		if(tm.which() == 1)
+		{
+			//Must compose.
+			ParsedDecomposedTransform &decomp = boost::get<ParsedDecomposedTransform>(tm);
+			tm = MatrixVisitor()(decomp.mat);
+		}
+
+		tm = boost::get<glm::mat4>(tm) * matrix;
+	}
+
+	typedef boost::container::flat_set<std::string> LayerSet;
+
+	struct ParsedNodeDef
+	{
+		FilePosition pos;
+		boost::optional<IdString> name;
+		LayerSet layers;
+		std::vector<ParsedNodeDef*> childNodes;
+		ParsedNodeDef *pParent;
+		ParsedTransform nodeTM;
+		ParsedTransform objectTM;
+
+		ParsedNodeDef& operator=(const ParsedNodeDef &other)
+		{
+			pos = other.pos;
+			name = other.name;
+			layers = other.layers;
+			childNodes = other.childNodes;
+			pParent = other.pParent;
+			nodeTM = other.nodeTM;
+			objectTM = other.objectTM;
+			return *this;
+		}
+	};
+
+	struct InheritedNodeData
+	{
+		ParsedNodeDef *pParent;
+		LayerSet layers;
+
+		InheritedNodeData() : pParent() {}
+	};
+
+	struct ParsedSceneGraphDef
+	{
+		FilePosition pos;
+		LayerSet layers;
+		std::vector<std::string> layerOrder;
+		boost::container::flat_set<IdString> variantChecks;
+		boost::container::flat_map<IdString, FilePosition> nodeNamePositions;
+		boost::container::stable_vector<ParsedNodeDef> nodes;
+	};
+}}
+
+#endif //GLSDK_GLSCENE_PARSED_DATA_H

File glscene/source/ParserExceptions.h

 	public:
 		template<typename Token>
 		BadTokenError(const Token &t)
-			: BaseParseError(GetError(std::string(t.value().begin(), t.value().end())))
+			: BaseParseError(
+			GetError(std::string(t.value().begin(), t.value().end())),
+			std::distance(t.value().begin(), t.value().end()))
 		{}
 
 	private:

File glscene/source/keywords.incl

 	MAC(VARIANT_CHECK,	"variant_check",	"variant checking",		"variant checking")
 	MAC(NODE,			"node",			"scene graph node",			"scene graph node")
 	MAC(LAYERS,			"layers",		"layer declaration",		"layer declaration")
+	MAC(NODE_TM,		"node_tm",		"node transform",			"node transform")
+	MAC(OBJECT_TM,		"object_tm",	"object transform",			"object transform")
+	MAC(TRANSLATE,		"translate",	"translate transform",		"translate transform")
+	MAC(SCALE,			"scale",		"scale transform",			"scale transform")
+	MAC(ORIENTATION,	"orientation",	"orientation transform",	"orientation transform")
+	MAC(ANGLE_AXIS,		"angle_axis",	"angle/axis transform",		"angle/axis transform")
+	MAC(MATRIX,			"matrix",		"matrix transform",			"matrix transform")