Jason McKesson avatar Jason McKesson committed bd93c24

Tut17: Scene system can now add textures.

Comments (0)

Files changed (4)

Documents/sceneFormat.rnc

         element scn:scene { sc.scene.content }
 
     sc.scene.content =
-        (sc.mesh | sc.prog | sc.node)+
+        (sc.mesh | sc.texture | sc.prog | sc.node)+
         
     sc.mesh =
-        ##A mesh to load. Meshes have names and filenames.
+        ##A mesh to load. Meshes have a name and a filename.
         element scn:mesh { sc.mesh.content }
         
     sc.mesh.content =
         sc.mesh.attlist
         
+    sc.texture =
+        ##A texture to load. Textures have a name and a filename.
+        element scn:texture { sc.texture.content }
+        
+    sc.texture.content =
+        sc.texture.attlist
+        
     sc.prog =
         ##A shader program. It represents a number of shaders, as well as uniforms and pre-4.20
         ##bindings.
         element scn:node { sc.node.content }
         
     sc.node.content =
-        sc.node.attlist, (sc.note)*
+        sc.node.attlist, (sc.note | sc.node.texture)*
         
     sc.note =
         ##User-defined strings that can be queried.
         
     sc.note.content =
         sc.note.attlist, text
+
+    sc.node.texture =
+        ##Associates a loaded texture with a sampler and texture unit for this node.
+        element scn:texture { sc.node.texture.content }
+        
+    sc.node.texture.content =
+        sc.node.texture.attlist
 }
 
 ##Attributes
 {
     sc.mesh.attlist =
         sc.xml.id.attribute, sc.mesh.file.attribute
+        
+    sc.texture.attlist =
+        sc.xml.id.attribute, sc.texture.file.attribute
     
     sc.prog.attlist =
         sc.xml.id.attribute,
     
     sc.note.attlist =
         sc.note.name.attribute
+        
+    sc.node.texture.attlist =
+        sc.node.texture.name.attribute,
+        sc.node.texture.unit.attribute,
+        sc.node.texture.sampler.attribute
     
     sc.xml.id.attribute =
         ##Uniquely named object
     sc.mesh.file.attribute =
         ##The mesh's filename.
         attribute file { acc.filename.type }
+        
+    sc.texture.file.attribute =
+        ##The texture's filename.
+        attribute file { acc.filename.type }
     
     sc.prog.vert.attribute =
         ##The vertex shader filename for this program
         attribute mesh { xsd:IDREF }
         
     sc.node.prog.attribute =
-        ##The name of a program. If no program is found, an error rsults.
+        ##The name of a program. If no program is found, an error results.
         attribute prog { xsd:IDREF }
         
     sc.node.pos.attribute =
     sc.note.name.attribute =
         ##The name of an annotation in a node. The note name must be unique within the node.
         attribute name { text }
+
+    sc.node.texture.name.attribute =
+        ##The name of a texture. If no program is found, an error results.
+        attribute name { xsd:IDREF }
+    
+    sc.node.texture.unit.attribute =
+        ##The texture unit to use when binding this texture.
+        attribute unit { acc.texture-unit.type }
+        
+    sc.node.texture.sampler.attribute =
+        ##The sample filtering to use for this texture.
+        attribute sampler { acc.samplers.type }
 }
 
 ## Accessories
 div
 {
+    acc.samplers.type =
+        "nearest" |
+        "linear" |
+        "mipmap nearest" |
+        "mipmap linear" |
+        "anisotropic" |
+        "half anisotropic"
+        
     acc.texture-unit.type = xsd:nonNegativeInteger { minInclusive = "0" maxExclusive = "80"}
     acc.block-binding.type = xsd:nonNegativeInteger { minInclusive = "0" maxExclusive = "80"}
     acc.uniform.type = text

Tut 17 Spotlight on Textures/Double Projection.cpp

 glutil::ViewData g_initialView =
 {
 	glm::vec3(0.0f, 0.0f, 0.0f),
-	glm::fquat(0.98481f, 0.173648f, 0.0f, 0.0f),
+	glm::fquat(0.909845f, 0.16043f, -0.376867f, -0.0664516f),
 	25.0f,
 	0.0f
 };
 
 glutil::ViewScale g_initialViewScale =
 {
-	5.0f, 40.0f,
+	5.0f, 70.0f,
 	2.0f, 0.5f,
-	4.0f, 1.0f,
+	2.0f, 0.5f,
 	90.0f/250.0f
 };
 
 	AssociateUniformWithNodes(nodes, g_lightNumBinder, "numberOfLights");
 	SetStateBinderWithNodes(nodes, g_lightNumBinder);
 
-	g_stoneTexBinder.SetTexture(0, GL_TEXTURE_2D, g_stoneTex, g_samplers[1]);
-	nodes[0].SetStateBinder(&g_stoneTexBinder);
-	nodes[1].SetStateBinder(&g_stoneTexBinder);
-	nodes[2].SetStateBinder(&g_stoneTexBinder);
-	nodes[3].SetStateBinder(&g_stoneTexBinder);
-
 	GLuint unlit = pScene->FindProgram("p_unlit");
 	Framework::Mesh *pSphereMesh = pScene->FindMesh("m_sphere");
 
 void BuildLights( const glm::mat4 &camMatrix )
 {
 	LightBlock lightData;
-	lightData.ambientIntensity = glm::vec4(0.2, 0.2, 0.2, 1.0);
+	lightData.ambientIntensity = glm::vec4(0.2f, 0.2f, 0.2f, 1.0f);
 	lightData.lightAttenuation = 1.0f / (5.0f * 5.0f);
 	lightData.maxIntensity = 3.0f;
-	lightData.lights[0].lightIntensity = glm::vec4(2.0, 2.0, 2.5, 1.0);
+	lightData.lights[0].lightIntensity = glm::vec4(2.0f, 2.0f, 2.5f, 1.0f);
 	lightData.lights[0].cameraSpaceLightPos = camMatrix *
 		glm::normalize(glm::vec4(-0.2f, 0.5f, 0.5f, 0.0f));
-	lightData.lights[1].lightIntensity = glm::vec4(3.5, 6.5, 3.0, 1.0);
+	lightData.lights[1].lightIntensity = glm::vec4(3.5f, 6.5f, 3.0f, 1.0f) * 1.2f;
 	lightData.lights[1].cameraSpaceLightPos = camMatrix *
 		glm::vec4(5.0f, 6.0f, 0.5f, 1.0f);
 

Tut 17 Spotlight on Textures/data/dp_scene.xml

     <mesh xml:id="m_cube" file="UnitCube.xml"/>
 	<mesh xml:id="m_shortBar" file="ShortBar.xml"/>
 	<mesh xml:id="m_longBar" file="LongBar.xml"/>
+    <texture xml:id="t_stone" file="seamless_rock1_small.dds"/>
     <prog
         xml:id="p_unlit"
         vert="Unlit.vert"
         prog="p_lit"
         pos="0 1 0"
         orient="0.0 0.0 0.0 1.0"
-        scale="3"/>
+        scale="3">
+        <texture name="t_stone" unit="0" sampler="anisotropic"/>
+    </node>
     <node
         name="rightBar"
         mesh="m_shortBar"
         prog="p_lit"
         pos="13 -2 0"
         orient="0.0 0.0 0.0 1.0"
-        scale="3"/>
+        scale="3">
+        <texture name="t_stone" unit="0" sampler="anisotropic"/>
+    </node>
     <node
         name="leaningBar"
         mesh="m_longBar"
         prog="p_lit"
         pos="3 -7 -10"
         orient="0.64278 0 0 0.76604"
-        scale="5"/>
+        scale="5">
+        <texture name="t_stone" unit="0" sampler="anisotropic"/>
+    </node>
     <node
         name="spinBar"
         mesh="m_longBar"
         prog="p_lit"
         pos="-7 0 8"
         orient="-0.148446 0.554035 0.212003 0.791242"
-        scale="4"/>
+        scale="4">
+        <texture name="t_stone" unit="0" sampler="anisotropic"/>
+    </node>
 </scene>

framework/Scene.cpp

 #include <stdexcept>
 #include <algorithm>
 #include <memory>
+#include <ctype.h>
 
 #include <istream>
 #include <fstream>
 #include <glm/gtc/quaternion.hpp>
 #include <glm/gtc/type_ptr.hpp>
 #include <glm/gtc/matrix_transform.hpp>
+#include <glimg/glimg.h>
 
 
 #define PARSE_THROW(cond, message)\
 			void operator()(const StateBinder *pState) const {pState->UnbindState(m_prog);}
 			GLuint m_prog;
 		};
+
+		std::string GetExtension(const std::string &filename)
+		{
+			size_t dotLoc = filename.rfind('.');
+			if(dotLoc == std::string::npos)
+				throw std::runtime_error("Texture must have an extension. " + filename + " does not.");
+
+			std::string ext = filename.substr(dotLoc + 1);
+
+			//Make lowercase.
+			std::transform(ext.begin(), ext.end(), ext.begin(), tolower);
+
+			return ext;
+		}
 	}
 
 	class SceneMesh
 		Mesh *m_pMesh;
 	};
 
+	class SceneTexture
+	{
+	public:
+		SceneTexture(const std::string &filename)
+		{
+			std::string pathname(Framework::FindFileOrThrow("seamless_rock1_small.dds"));
+
+			std::auto_ptr<glimg::ImageSet> pImageSet;
+			std::string ext = GetExtension(pathname);
+			if(ext == "dds")
+			{
+				pImageSet.reset(glimg::loaders::dds::LoadFromFile(pathname.c_str()));
+			}
+			else
+			{
+				pImageSet.reset(glimg::loaders::stb::LoadFromFile(pathname.c_str()));
+			}
+
+			m_texObj = glimg::CreateTexture(pImageSet.get(), 0);
+			//TODO: FIX THIS!!
+			m_texType = GL_TEXTURE_2D;
+		}
+
+		~SceneTexture()
+		{
+			glDeleteTextures(1, &m_texObj);
+		}
+
+		GLuint GetTexture() const {return m_texObj;}
+		GLenum GetType() const {return m_texType;}
+
+	private:
+		GLuint m_texObj;
+		GLenum m_texType;
+	};
+
 	class SceneProgram
 	{
 	public:
 		glm::vec3 m_trans;
 	};
 
+	enum SamplerTypes
+	{
+		SPL_NEAREST,
+		SPL_LINEAR,
+		SPL_MIPMAP_NEAREST,
+		SPL_MIPMAP_LINEAR,
+		SPL_ANISOTROPIC,
+		SPL_HALF_ANISOTROPIC,
+
+		MAX_SAMPLERS,
+	};
+
+	SamplerTypes GetTypeFromName(const std::string &name)
+	{
+		const char *samplerNames[MAX_SAMPLERS] =
+		{
+			"nearest",
+			"linear",
+			"mipmap nearest",
+			"mipmap linear",
+			"anisotropic",
+			"half anisotropic",
+		};
+
+		const char **theName = std::find(samplerNames, samplerNames + MAX_SAMPLERS, name);
+
+		for(int spl = 0; spl < MAX_SAMPLERS; ++spl)
+		{
+			if(name == samplerNames[spl])
+				return SamplerTypes(spl);
+		}
+
+		throw std::runtime_error("Unknown sampler name: " + name);
+	}
+
+	void MakeSamplerObjects(std::vector<GLuint> &samplers)
+	{
+		samplers.resize(MAX_SAMPLERS);
+		glGenSamplers(MAX_SAMPLERS, &samplers[0]);
+
+		//Always repeat.
+		for(int samplerIx = 0; samplerIx < MAX_SAMPLERS; samplerIx++)
+		{
+			glSamplerParameteri(samplers[samplerIx], GL_TEXTURE_WRAP_S, GL_REPEAT);
+			glSamplerParameteri(samplers[samplerIx], GL_TEXTURE_WRAP_T, GL_REPEAT);
+			glSamplerParameteri(samplers[samplerIx], GL_TEXTURE_WRAP_R, GL_REPEAT);
+		}
+
+		glSamplerParameteri(samplers[0], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		glSamplerParameteri(samplers[0], GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+		glSamplerParameteri(samplers[1], GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glSamplerParameteri(samplers[1], GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+		glSamplerParameteri(samplers[2], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		glSamplerParameteri(samplers[2], GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
+
+		glSamplerParameteri(samplers[3], GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glSamplerParameteri(samplers[3], GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+
+		GLfloat maxAniso = 0.0f;
+		glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso);
+
+		glSamplerParameteri(samplers[4], GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glSamplerParameteri(samplers[4], GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+		glSamplerParameterf(samplers[4], GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAniso / 2.0f);
+
+		glSamplerParameteri(samplers[5], GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glSamplerParameteri(samplers[5], GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+		glSamplerParameterf(samplers[5], GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAniso);
+	}
+
+	struct TextureBinding
+	{
+		SceneTexture *pTex;
+		GLuint texUnit;
+		SamplerTypes sampler;
+	};
+
 	class SceneNode
 	{
 	public:
-		SceneNode(SceneMesh *pMesh, SceneProgram *pProg, const glm::vec3 &nodePos)
+		SceneNode(SceneMesh *pMesh, SceneProgram *pProg, const glm::vec3 &nodePos,
+			const std::vector<TextureBinding> &texBindings)
 			: m_pMesh(pMesh)
 			, m_pProg(pProg)
+			, m_texBindings(texBindings)
 		{
 			m_nodeTm.m_trans = nodePos;
 		}
 			m_nodeTm.m_scale = nodeScale;
 		}
 
-		void Render(glm::mat4 baseMat) const
+		void Render(const std::vector<GLuint> &samplers, glm::mat4 baseMat) const
 		{
 			baseMat *= m_nodeTm.GetMatrix();
 			glm::mat4 objMat = baseMat * m_objTm.GetMatrix();
 			}
 
 			std::for_each(m_binders.begin(), m_binders.end(), BindBinder(m_pProg->GetProgram()));
+			for(size_t texIx = 0; texIx < m_texBindings.size(); ++texIx)
+			{
+				const TextureBinding &binding = m_texBindings[texIx];
+				glActiveTexture(GL_TEXTURE0 + binding.texUnit);
+				glBindTexture(binding.pTex->GetType(), binding.pTex->GetTexture());
+				glBindSampler(binding.texUnit, samplers[binding.sampler]);
+			}
+
 			m_pMesh->Render();
+
+			for(size_t texIx = 0; texIx < m_texBindings.size(); ++texIx)
+			{
+				const TextureBinding &binding = m_texBindings[texIx];
+				glActiveTexture(GL_TEXTURE0 + binding.texUnit);
+				glBindTexture(binding.pTex->GetType(), 0);
+				glBindSampler(binding.texUnit, 0);
+			}
 			std::for_each(m_binders.rbegin(), m_binders.rend(), UnbindBinder(m_pProg->GetProgram()));
 			glUseProgram(0);
 		}
 		SceneMesh *m_pMesh;		//Unmanaged. We are deleted first, so these should always be real values.
 		SceneProgram *m_pProg;	//Unmanaged. We are deleted first, so these should always be real values.
 
-		std::vector<StateBinder*> m_binders;
+		std::vector<StateBinder*> m_binders;	//Unmanaged. These live beyond us.
+		std::vector<TextureBinding> m_texBindings;
 
 		Transform m_nodeTm;
 		Transform m_objTm;
 	};
 
 	typedef std::map<std::string, SceneMesh*> MeshMap;
+	typedef std::map<std::string, SceneTexture*> TextureMap;
 	typedef std::map<std::string, SceneProgram*> ProgramMap;
 	typedef std::map<std::string, SceneNode*> NodeMap;
 
 	{
 	private:
 		MeshMap m_meshes;
+		TextureMap m_textures;
 		ProgramMap m_progs;
 		NodeMap m_nodes;
 
 		std::vector<SceneNode *> m_rootNodes;
 
+		std::vector<GLuint> m_samplers;
+
 	public:
 		SceneImpl(const std::string &filename)
 		{
 			try
 			{
 				ReadMeshes(*pSceneNode);
+				ReadTextures(*pSceneNode);
 				ReadPrograms(*pSceneNode);
 				ReadNodes(NULL, *pSceneNode);
 			}
 			catch(...)
 			{
+				std::for_each(m_nodes.begin(), m_nodes.end(), DeleteSecond<typename NodeMap::value_type>);
+				std::for_each(m_progs.begin(), m_progs.end(), DeleteSecond<typename ProgramMap::value_type>);
+				std::for_each(m_textures.begin(), m_textures.end(), DeleteSecond<typename TextureMap::value_type>);
 				std::for_each(m_meshes.begin(), m_meshes.end(), DeleteSecond<typename MeshMap::value_type>);
-				std::for_each(m_progs.begin(), m_progs.end(), DeleteSecond<typename ProgramMap::value_type>);
-				std::for_each(m_nodes.begin(), m_nodes.end(), DeleteSecond<typename NodeMap::value_type>);
 				throw;
 			}
+
+			MakeSamplerObjects(m_samplers);
 		}
 
 		~SceneImpl()
 		{
+			glDeleteSamplers(m_samplers.size(), &m_samplers[0]);
+			m_samplers.clear();
+
 			std::for_each(m_nodes.begin(), m_nodes.end(), DeleteSecond<typename NodeMap::value_type>);
 			std::for_each(m_progs.begin(), m_progs.end(), DeleteSecond<typename ProgramMap::value_type>);
+			std::for_each(m_textures.begin(), m_textures.end(), DeleteSecond<typename TextureMap::value_type>);
 			std::for_each(m_meshes.begin(), m_meshes.end(), DeleteSecond<typename MeshMap::value_type>);
 		}
 
 				theIt != m_nodes.end();
 				++theIt)
 			{
-				theIt->second->Render(cameraMatrix);
+				theIt->second->Render(m_samplers, cameraMatrix);
 			}
 		}
 
 			return theIt->second->GetMesh();
 		}
 
+		std::pair<GLuint, GLenum> FindTexture(const std::string &textureName)
+		{
+			TextureMap::iterator theIt = m_textures.find(textureName);
+			if(theIt == m_textures.end())
+				throw std::runtime_error("Could not find the texture named: " + textureName);
 
+			return std::make_pair(theIt->second->GetTexture(), theIt->second->GetType());
+		}
 
 	private:
 
 			m_meshes[name] = pMesh;
 		}
 
+		void ReadTextures(const xml_node<> &scene)
+		{
+			for(const xml_node<> *pTexNode = scene.first_node("texture");
+				pTexNode;
+				pTexNode = pTexNode->next_sibling("texture"))
+			{
+				ReadTexture(*pTexNode);
+			}
+		}
+
+		void ReadTexture(const xml_node<> &TexNode)
+		{
+			const xml_attribute<> *pNameNode = TexNode.first_attribute("xml:id");
+			const xml_attribute<> *pFilenameNode = TexNode.first_attribute("file");
+
+			PARSE_THROW(pNameNode, "Texture found with no `xml:id` name specified.");
+			PARSE_THROW(pFilenameNode, "Texture found with no `file` filename specified.");
+
+			std::string name = make_string(*pNameNode);
+			if(m_textures.find(name) != m_textures.end())
+				throw std::runtime_error("The texture named \"" + name + "\" already exists.");
+
+			m_textures[name] = NULL;
+
+			SceneTexture *pTexture = new SceneTexture(make_string(*pFilenameNode));
+
+			m_textures[name] = pTexture;
+		}
+
 		void ReadPrograms(const xml_node<> &scene)
 		{
 			for(const xml_node<> *pProgNode = scene.first_node("prog");
 
 			glm::vec3 nodePos = rapidxml::attrib_to_vec3(*pPositionNode, ThrowAttrib);
 
-			SceneNode *pNode = new SceneNode(meshIt->second, progIt->second, nodePos);
+			SceneNode *pNode = new SceneNode(meshIt->second, progIt->second, nodePos,
+				ReadNodeTextures(nodeNode));
 			m_nodes[name] = pNode;
 
 			//TODO: parent/child nodes.
 				PARSE_THROW(pNameNode, "Notations on nodes must have a `name` attribute.");
 			}
 		}
+
+		std::vector<TextureBinding> ReadNodeTextures(const xml_node<> &nodeNode)
+		{
+			std::vector<TextureBinding> texBindings;
+			std::set<GLuint> texUnits;
+
+			for(const xml_node<> *pTexNode = nodeNode.first_node("texture");
+				pTexNode;
+				pTexNode = pTexNode->next_sibling("texture"))
+			{
+				const xml_node<> &texNode = *pTexNode;
+				const xml_attribute<> *pNameNode = texNode.first_attribute("name");
+				const xml_attribute<> *pUnitName = texNode.first_attribute("unit");
+				const xml_attribute<> *pSamplerName = texNode.first_attribute("sampler");
+
+				PARSE_THROW(pNameNode, "Textures on nodes must have a `name` attribute.");
+				PARSE_THROW(pUnitName, "Textures on nodes must have a `unit` attribute.");
+				PARSE_THROW(pSamplerName, "Textures on nodes must have a `sampler` attribute.");
+
+				std::string textureName = make_string(*pNameNode);
+				TextureMap::iterator texIt = m_textures.find(textureName);
+				if(texIt == m_textures.end())
+				{
+					throw std::runtime_error("The node texture named \"" + textureName + 
+						"\" is a texture which does not exist.");
+				}
+
+				TextureBinding binding;
+
+				binding.pTex = texIt->second;
+				binding.texUnit = rapidxml::attrib_to_int(*pUnitName, ThrowAttrib);
+				binding.sampler = GetTypeFromName(make_string(*pSamplerName));
+
+				if(texUnits.find(binding.texUnit) != texUnits.end())
+					throw std::runtime_error("Multiply bound texture unit in node texture " + textureName);
+
+				texBindings.push_back(binding);
+
+				texUnits.insert(binding.texUnit);
+			}
+
+			return texBindings;
+		}
 	};
 
 	void NodeRef::NodeSetScale( const glm::vec3 &scale )
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.