Commits

Jason McKesson committed 2aa4061

Tut12: Lighting model fully operational.

Comments (0)

Files changed (12)

Tut 12 Dynamic Range/Lights.cpp

 #include <math.h>
 #include "Lights.h"
 #include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
 
 static float g_fLightHeight = 10.5f;
 static float g_fLightRadius = 70.0f;
 }
 
 LightManager::LightManager()
-	: m_keyLightTimer(Framework::Timer::TT_LOOP, 5.0f)
-	, m_ambientInterpolator(true)
+	: m_sunTimer(Framework::Timer::TT_LOOP, 5.0f)
+	, m_ambientInterpolator()
 {
-	std::vector<glm::vec4> ambientValues;
-	ambientValues.reserve(4);
-	
-	ambientValues.push_back(glm::vec4(0.1f, 0.1f, 0.1f, 1.0f));
+	m_lightTimers.reserve(NUMBER_OF_POINT_LIGHTS);
+	m_lightPos.reserve(NUMBER_OF_POINT_LIGHTS);
+	m_lightPos.push_back(LightInterpolator());
+	m_lightPos.push_back(LightInterpolator());
+	m_lightPos.push_back(LightInterpolator());
 
-	m_ambientInterpolator.SetValues(ambientValues);
-
-	m_lightTimers.reserve(NUMBER_OF_LIGHTS - 1);
-	m_lightPos.reserve(NUMBER_OF_LIGHTS - 1);
-	m_lightPos.push_back(LightInterpolator(true));
-	m_lightPos.push_back(LightInterpolator(true));
-	m_lightPos.push_back(LightInterpolator(true));
+	m_lightIntensity.resize(NUMBER_OF_POINT_LIGHTS, glm::vec4(0.2f, 0.2f, 0.2f, 1.0f));
 
 	std::vector<glm::vec3> posValues;
 	posValues.reserve(20);
 	m_lightTimers.push_back(Framework::Timer(Framework::Timer::TT_LOOP, 15.0f));
 }
 
+typedef std::pair<glm::vec4, float> LightVectorData;
+typedef std::vector<LightVectorData> LightVectors;
+
+glm::vec4 GetValue(const LightVectorData &data) {return data.first;}
+float GetTime(const LightVectorData &data) {return data.second;}
+
+void LightManager::SetSunlightValues( const std::vector<SunlightValue> &values )
+{
+	LightVectors ambient;
+	LightVectors light;
+	LightVectors background;
+
+	for(size_t valIx = 0; valIx < values.size(); ++valIx)
+	{
+		ambient.push_back(LightVectorData(values[valIx].ambient, values[valIx].normTime));
+		light.push_back(LightVectorData(values[valIx].sunlightIntensity, values[valIx].normTime));
+		background.push_back(LightVectorData(values[valIx].backgroundColor, values[valIx].normTime));
+	}
+
+	m_ambientInterpolator.SetValues(ambient);
+	m_sunlightInterpolator.SetValues(light);
+	m_backgroundInterpolator.SetValues(background);
+}
+
+void LightManager::SetSunlightValues( SunlightValue *pValues, int iSize )
+{
+	LightVectors ambient;
+	LightVectors light;
+	LightVectors background;
+
+	for(int valIx = 0; valIx < iSize; ++valIx)
+	{
+		ambient.push_back(LightVectorData(pValues[valIx].ambient, pValues[valIx].normTime));
+		light.push_back(LightVectorData(pValues[valIx].sunlightIntensity, pValues[valIx].normTime));
+		background.push_back(LightVectorData(pValues[valIx].backgroundColor, pValues[valIx].normTime));
+	}
+
+	m_ambientInterpolator.SetValues(ambient);
+	m_sunlightInterpolator.SetValues(light);
+	m_backgroundInterpolator.SetValues(background);
+}
+
 struct UpdateTimer
 {
 	void operator()(Framework::Timer &timer) {timer.Update();}
 	float secRewind;
 };
 
+struct FFTimer
+{
+	FFTimer(float _secFF) : secFF(_secFF) {}
+
+	void operator()(Framework::Timer &timer) {timer.Fastforward(secFF);}
+	void operator()(std::pair<const std::string, Framework::Timer> &timeData)
+	{timeData.second.Fastforward(secFF);}
+
+	float secFF;
+};
+
 void LightManager::UpdateTime()
 {
-	m_keyLightTimer.Update();
+	m_sunTimer.Update();
 	std::for_each(m_lightTimers.begin(), m_lightTimers.end(), UpdateTimer());
 	std::for_each(m_extraTimers.begin(), m_extraTimers.end(), UpdateTimer());
 }
 	std::for_each(m_lightTimers.begin(), m_lightTimers.end(), PauseTimer());
 	std::for_each(m_extraTimers.begin(), m_extraTimers.end(), PauseTimer());
 
-	return m_keyLightTimer.TogglePause();
+	return m_sunTimer.TogglePause();
+}
+
+bool LightManager::ToggleSunPause()
+{
+	return m_sunTimer.TogglePause();
 }
 
 void LightManager::RewindTime( float secRewind )
 {
-	m_keyLightTimer.Rewind(secRewind);
+	m_sunTimer.Rewind(secRewind);
 	std::for_each(m_lightTimers.begin(), m_lightTimers.end(), RewindTimer(secRewind));
 	std::for_each(m_extraTimers.begin(), m_extraTimers.end(), RewindTimer(secRewind));
 }
 
+void LightManager::FastForwardTime( float secFF )
+{
+	m_sunTimer.Fastforward(secFF);
+	std::for_each(m_lightTimers.begin(), m_lightTimers.end(), FFTimer(secFF));
+	std::for_each(m_extraTimers.begin(), m_extraTimers.end(), FFTimer(secFF));
+}
+
 LightBlock LightManager::GetLightPositions( const glm::mat4 &worldToCameraMat ) const
 {
 	LightBlock lightData;
 
-	lightData.ambientIntensity = m_ambientInterpolator.Interpolate(m_keyLightTimer.GetAlpha());
+	lightData.ambientIntensity = m_ambientInterpolator.Interpolate(m_sunTimer.GetAlpha());
 	lightData.lightAttenuation = g_fLightAttenuation;
 
+	float angle = 2.0f * 3.14159f * m_sunTimer.GetAlpha();
+	glm::vec4 sunDirection(0.0f);
+	sunDirection[0] = sinf(angle);
+	sunDirection[1] = cosf(angle);
+
+	//Keep the sun from being perfectly centered overhead.
+	sunDirection = glm::rotate(glm::mat4(1.0f), 5.0f, glm::vec3(0.0f, 1.0f, 0.0f)) * sunDirection;
+
 	lightData.lights[0].cameraSpaceLightPos =
-		worldToCameraMat * glm::vec4(0.0f, 0.981f, 0.196f, 0.0f);
+		worldToCameraMat * sunDirection;
 
-	lightData.lights[0].lightIntensity = glm::vec4(0.1f, 0.1f, 0.1f, 1.0f);
+	lightData.lights[0].lightIntensity = m_sunlightInterpolator.Interpolate(m_sunTimer.GetAlpha());
 
-	for(int light = 1; light < NUMBER_OF_LIGHTS; light++)
+	for(int light = 0; light < NUMBER_OF_POINT_LIGHTS; light++)
 	{
-		int lightIx = light - 1;
-		glm::vec4 worldLightPos = glm::vec4(m_lightPos[lightIx].Interpolate(m_lightTimers[lightIx].GetAlpha()), 1.0f);
+		glm::vec4 worldLightPos =
+			glm::vec4(m_lightPos[light].Interpolate(m_lightTimers[light].GetAlpha()), 1.0f);
 		glm::vec4 lightPosCameraSpace = worldToCameraMat * worldLightPos;
 
-		lightData.lights[light].cameraSpaceLightPos = lightPosCameraSpace;
-		lightData.lights[light].lightIntensity = glm::vec4(0.2f, 0.2f, 0.2f, 1.0f);
+		lightData.lights[light + 1].cameraSpaceLightPos = lightPosCameraSpace;
+		lightData.lights[light + 1].lightIntensity = m_lightIntensity[light];
 	}
 
 	return lightData;
 	return m_lightPos[lightIx].Interpolate(m_lightTimers[lightIx].GetAlpha());
 }
 
+void LightManager::SetPointLightIntensity( int iLightIx, const glm::vec4 &intensity )
+{
+	m_lightIntensity[iLightIx] = intensity;
+}
+
+glm::vec4 LightManager::GetPointLightIntensity( int iLightIx ) const
+{
+	return m_lightIntensity[iLightIx];
+}
+
 void LightManager::CreateTimer( const std::string &timerName,
 							   Framework::Timer::Type eType, float fDuration )
 {
 	return loc->second.GetAlpha();
 }
 
+glm::vec4 LightManager::GetBackgroundColor() const
+{
+	return m_backgroundInterpolator.Interpolate(m_sunTimer.GetAlpha());
+}

Tut 12 Dynamic Range/Lights.h

 };
 
 const int NUMBER_OF_LIGHTS = 4;
+const int NUMBER_OF_POINT_LIGHTS = NUMBER_OF_LIGHTS - 1;
 
 struct LightBlock
 {
 	PerLight lights[NUMBER_OF_LIGHTS];
 };
 
+struct SunlightValue
+{
+	float normTime;
+	glm::vec4 ambient;
+	glm::vec4 sunlightIntensity;
+	glm::vec4 backgroundColor;
+};
+
 class LightManager
 {
 public:
 	LightManager();
 
+	void SetSunlightValues(const std::vector<SunlightValue> &values);
+	void SetSunlightValues(SunlightValue *pValues, int iSize);
+
 	void UpdateTime();
 	bool TogglePause();
+	bool ToggleSunPause();
+
 	void RewindTime(float secRewind);
+	void FastForwardTime(float secRewind);
 
 	LightBlock GetLightPositions(const glm::mat4 &worldToCameraMat) const;
+	glm::vec4 GetBackgroundColor() const;
 
 	int GetNumberOfPointLights() const;
 	glm::vec3 GetWorldLightPosition(int iLightIx) const;
+	void SetPointLightIntensity(int iLightIx, const glm::vec4 &intensity);
+	glm::vec4 GetPointLightIntensity(int iLightIx) const;
 
 	void CreateTimer(const std::string &timerName, Framework::Timer::Type eType, float fDuration);
 	float GetTimerValue(const std::string &timerName) const;
 	typedef Framework::ConstVelLinearInterpolator<glm::vec3> LightInterpolator;
 	typedef std::map<std::string, Framework::Timer> ExtraTimerMap;
 
-	Framework::Timer m_keyLightTimer;
-	Framework::LinearInterpolator<glm::vec4> m_ambientInterpolator;
+	Framework::Timer m_sunTimer;
+	Framework::TimedLinearInterpolator<glm::vec4> m_ambientInterpolator;
+	Framework::TimedLinearInterpolator<glm::vec4> m_backgroundInterpolator;
+	Framework::TimedLinearInterpolator<glm::vec4> m_sunlightInterpolator;
 
 	std::vector<LightInterpolator> m_lightPos;
+	std::vector<glm::vec4> m_lightIntensity;
 	std::vector<Framework::Timer> m_lightTimers;
 	ExtraTimerMap m_extraTimers;
 };

Tut 12 Dynamic Range/Scene Lighting.cpp

 	glBufferData(GL_UNIFORM_BUFFER, sizeMaterialUniformBuffer, bufferPtr, GL_STATIC_DRAW);
 }
 
+void SetupLighting()
+{
+	SunlightValue values[] =
+	{
+		{0.0f, glm::vec4(0.2f, 0.2f, 0.2f, 1.0f), glm::vec4(0.4f, 0.4f, 0.4f, 1.0f), glm::vec4(0.9f, 0.9f, 1.0f, 1.0f)},
+		{0.25f, glm::vec4(0.1f, 0.05f, 0.05f, 1.0f), glm::vec4(0.2f, 0.0f, 0.0f, 1.0f), glm::vec4(0.5f, 0.1f, 0.1f, 1.0f)},
+		{0.5f, glm::vec4(0.0f, 0.0f, 0.0f, 1.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)},
+		{0.75f, glm::vec4(0.1f, 0.05f, 0.05f, 1.0f), glm::vec4(0.2f, 0.0f, 0.0f, 1.0f), glm::vec4(0.5f, 0.1f, 0.1f, 1.0f)},
+	};
+
+	g_lights.SetSunlightValues(values, 4);
+
+	g_lights.SetPointLightIntensity(0, glm::vec4(0.2f, 0.2f, 0.2f, 1.0f));
+	g_lights.SetPointLightIntensity(1, glm::vec4(0.0f, 0.0f, 0.3f, 1.0f));
+	g_lights.SetPointLightIntensity(2, glm::vec4(0.3f, 0.0f, 0.0f, 1.0f));
+}
+
 Framework::Mesh *g_pTerrainMesh = NULL;
 Framework::Mesh *g_pCubeMesh = NULL;
 Framework::Mesh *g_pTetraMesh = NULL;
 		throw;
 	}
 
+	SetupLighting();
+
 	g_lights.CreateTimer("tetra", Framework::Timer::TT_LOOP, 2.5f);
 
- 	glutMouseFunc(MouseButton);
+	glutMouseFunc(MouseButton);
  	glutMotionFunc(MouseMotion);
 	glutMouseWheelFunc(MouseWheel);
 
 {
 	g_lights.UpdateTime();
 
-	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	glm::vec4 bkg = g_lights.GetBackgroundColor();
+
+	glClearColor(bkg[0], bkg[1], bkg[2], bkg[3]);
 	glClearDepth(1.0f);
 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
 			glUseProgram(g_Unlit.theProgram);
 			glUniformMatrix4fv(g_Unlit.modelToCameraMatrixUnif, 1, GL_FALSE,
 				glm::value_ptr(modelMatrix.Top()));
-			glUniform4f(g_Unlit.objectColorUnif, 0.8078f, 0.8706f, 0.9922f, 1.0f);
+
+			glm::vec4 lightColor = g_lights.GetPointLightIntensity(light);
+			glUniform4fv(g_Unlit.objectColorUnif, 1, glm::value_ptr(lightColor));
 			g_pCubeMesh->Render("flat");
 		}
 
 		break;
 		
 	case 'b': g_lights.TogglePause(); break;
+	case 'B': g_lights.ToggleSunPause(); break;
 	case 'g': g_lights.RewindTime(1.0f); break;
+	case 'G': g_lights.FastForwardTime(1.0f); break;
 	case 't': g_bDrawCameraPos = !g_bDrawCameraPos; break;
 
-	case 'w': g_mousePole.OffsetTargetPos(Framework::MousePole::DIR_FORWARD, 5.0f); break;
-	case 's': g_mousePole.OffsetTargetPos(Framework::MousePole::DIR_BACKWARD, 5.0f); break;
-	case 'd': g_mousePole.OffsetTargetPos(Framework::MousePole::DIR_RIGHT, 5.0f); break;
-	case 'a': g_mousePole.OffsetTargetPos(Framework::MousePole::DIR_LEFT, 5.0f); break;
-	case 'e': g_mousePole.OffsetTargetPos(Framework::MousePole::DIR_UP, 5.0f); break;
-	case 'q': g_mousePole.OffsetTargetPos(Framework::MousePole::DIR_DOWN, 5.0f); break;
-
 	case 32:
 		printf("%f\n", 360.0f * g_lights.GetTimerValue("tetra"));
 		break;
 	}
 
+	g_mousePole.GLUTKeyOffset(key, 5.0f, 1.0f);
+
 	glutPostRedisplay();
 }
 

Tut 12 Dynamic Range/data/DiffuseOnly.frag

 	
 	vec3 surfaceNormal = normalize(vertexNormal);
 	float cosAngIncidence = dot(surfaceNormal, lightDir);
-	cosAngIncidence = clamp(cosAngIncidence, 0, 1);
+	cosAngIncidence = cosAngIncidence < 0.0001 ? 0.0 : cosAngIncidence;
 	
 	vec4 lighting = diffuseColor * lightIntensity * cosAngIncidence;
 	

Tut 12 Dynamic Range/data/DiffuseOnlyMtl.frag

 	
 	vec3 surfaceNormal = normalize(vertexNormal);
 	float cosAngIncidence = dot(surfaceNormal, lightDir);
-	cosAngIncidence = clamp(cosAngIncidence, 0, 1);
+	cosAngIncidence = cosAngIncidence < 0.0001 ? 0.0 : cosAngIncidence;
 	
 	vec4 lighting = Mtl.diffuseColor * lightIntensity * cosAngIncidence;
 	

Tut 12 Dynamic Range/data/DiffuseSpecular.frag

 	
 	vec3 surfaceNormal = normalize(vertexNormal);
 	float cosAngIncidence = dot(surfaceNormal, lightDir);
-	cosAngIncidence = clamp(cosAngIncidence, 0, 1);
+	cosAngIncidence = cosAngIncidence < 0.0001 ? 0.0 : cosAngIncidence;
 	
 	vec3 viewDirection = normalize(-cameraSpacePosition);
 	

Tut 12 Dynamic Range/data/DiffuseSpecularMtl.frag

 	
 	vec3 surfaceNormal = normalize(vertexNormal);
 	float cosAngIncidence = dot(surfaceNormal, lightDir);
-	cosAngIncidence = clamp(cosAngIncidence, 0, 1);
+	cosAngIncidence = cosAngIncidence < 0.0001 ? 0.0 : cosAngIncidence;
 	
 	vec3 viewDirection = normalize(-cameraSpacePosition);
 	
 	exponent = -(exponent * exponent);
 	float gaussianTerm = exp(exponent);
 
-	gaussianTerm = cosAngIncidence != 0.0 ? gaussianTerm : 0.0;
+	gaussianTerm = cosAngIncidence != 0.0001 ? gaussianTerm : 0.0;
 	
 	vec4 lighting = Mtl.diffuseColor * lightIntensity * cosAngIncidence;
 	lighting += Mtl.specularColor * lightIntensity * gaussianTerm;

framework/Interpolators.h

 namespace Framework
 {
 	template<typename ValueType>
-	class LinearInterpolator
+	class WeightedLinearInterpolator
 	{
 	public:
 		typedef ValueType value_type;
 
-		explicit LinearInterpolator(bool isLoop)
-			: m_isLoop(isLoop)
-		{}
-
-		template<typename BidirectionalRange>
-		void SetValues(const BidirectionalRange &data)
-		{
-			m_values.clear();
-			std::copy(data.begin(), data.end(), std::back_inserter(m_values));
-			if(m_isLoop)
-				m_values.push_back(*data.begin());
-		}
-
-		size_t NumSegments() const {return m_values.empty() ? 0 : m_values.size() - 1;}
-
-		ValueType Interpolate(float fAlpha) const
-		{
-			if(m_values.empty())
-				return ValueType();
-			if(m_values.size() == 1)
-				return m_values[0];
-
-			fAlpha *= m_values.size() - 1;
-
-			float intPart = 0.0f;
-			float sectionAlpha = modf(fAlpha, &intPart);
-
-			size_t iStartIx = (size_t)intPart;
-
-			size_t iNextIx = iStartIx + 1;
-
-			if(iNextIx > NumSegments())
-				return m_values.back();
-
-			float invSecAlpha = 1.0f - sectionAlpha;
-
-			return m_values[iStartIx] * invSecAlpha + m_values[iNextIx] * sectionAlpha;
-		}
-
-	private:
-		std::vector<ValueType> m_values;
-		bool m_isLoop;
-	};
-
-	/**
-	\brief Interpolates with a constant velocity between positions.
-
-	This interpolator maps a range of [0, 1) onto a set of values. However, it takes the distance
-	between these values. There must be a free function called "distance" which takes two ValueType's and
-	returns a float distance between them.
-
-	The idea is that, if you add 0.1 to your alpha value, you will always get a movement of the same distance.
-	Not necessarily between the initial and final points, but the object will have moved at the same
-	speed along the path.
-	**/
-	template<typename ValueType>
-	class ConstVelLinearInterpolator
-	{
-	public:
-		typedef ValueType value_type;
-
-		explicit ConstVelLinearInterpolator(bool isLoop)
-			: m_isLoop(isLoop)
-			, m_totalDist(0.0f)
-		{}
-
-		template<typename BidirectionalRange>
-		void SetValues(const BidirectionalRange &data)
-		{
-			m_values.clear();
-
-			typename BidirectionalRange::const_iterator curr = data.begin();
-			typename BidirectionalRange::const_iterator last = data.end();
-			for(; curr != last; ++curr)
-			{
-				Data currData;
-				currData.data = *curr;
-				currData.weight = 0.0f;
-				m_values.push_back(currData);
-			}
-
-			if(m_isLoop)
-			{
-				Data currData;
-				currData.data = *data.begin();
-				currData.weight = 0.0f;
-				m_values.push_back(currData);
-			}
-
-			//Compute the distances of each segment.
-			m_totalDist = 0.0f;
-			for(size_t iLoop = 1; iLoop < m_values.size(); ++iLoop)
-			{
-				m_totalDist += distance(m_values[iLoop - 1].data, m_values[iLoop].data);
-				m_values[iLoop].weight = m_totalDist;
-			}
-
-			//Compute the alpha value that represents when to use this segment.
-			for(size_t iLoop = 1; iLoop < m_values.size(); ++iLoop)
-			{
-				m_values[iLoop].weight /= m_totalDist;
-			}
-		}
-
-		float Distance() const {return m_totalDist;}
-
 		size_t NumSegments() const {return m_values.empty() ? 0 : m_values.size() - 1;}
 
 		ValueType Interpolate(float fAlpha) const
 			return m_values[segment - 1].data * invSecAlpha + m_values[segment].data * sectionAlpha;
 		}
 
-	private:
+	protected:
+		WeightedLinearInterpolator() {}
+
 		struct Data
 		{
 			ValueType data;
 		};
 
 		std::vector<Data> m_values;
-		bool m_isLoop;
+	};
+
+	template<typename ValueType>
+	class TimedLinearInterpolator : public WeightedLinearInterpolator<ValueType>
+	{
+	public:
+
+		template<typename BidirectionalRange>
+		void SetValues(const BidirectionalRange &data, bool isLooping = true)
+		{
+			m_values.clear();
+			typename BidirectionalRange::const_iterator curr = data.begin();
+			typename BidirectionalRange::const_iterator final = data.end();
+			for(; curr != final; ++curr)
+			{
+				typename WeightedLinearInterpolator<ValueType>::Data currData;
+				currData.data = GetValue(*curr);
+				currData.weight = GetTime(*curr);
+
+				assert(0.0f <= currData.weight && currData.weight <= 1.0f);
+				m_values.push_back(currData);
+			}
+
+			if(isLooping && !m_values.empty())
+				m_values.push_back(m_values[0]);
+
+			//Ensure first is weight 0, and last is weight 1.
+			if(!m_values.empty())
+			{
+				m_values.front().weight = 0.0f;
+				m_values.back().weight = 1.0f;
+			}
+		}
+	protected:
+	};
+
+
+	template<typename ValueType>
+	class LinearInterpolator : public WeightedLinearInterpolator<ValueType>
+	{
+	public:
+		typedef ValueType value_type;
+
+		template<typename BidirectionalRange>
+		void SetValues(const BidirectionalRange &data, bool isLooping = true)
+		{
+			m_values.clear();
+			int iNumValues = 0;
+			typename BidirectionalRange::const_iterator curr = data.begin();
+			typename BidirectionalRange::const_iterator final = data.end();
+			for(; curr != final; ++curr)
+			{
+				typename WeightedLinearInterpolator<ValueType>::Data currData;
+				currData.data = *curr;
+				currData.weight = 0.0f;
+				m_values.push_back(currData);
+
+				iNumValues++;
+			}
+
+			if(isLooping && !m_values.empty())
+			{
+				m_values.push_back(m_values.back());
+				++iNumValues;
+			}
+
+			//Compute weights.
+			for(size_t valIx = 0; valIx < m_values.size(); ++valIx)
+			{
+				m_values[valIx].weight = valIx / (float)(iNumValues - 1);
+			}
+		}
+	private:
+	};
+
+	/**
+	\brief Interpolates with a constant velocity between positions.
+
+	This interpolator maps a range of [0, 1) onto a set of values. However, it takes the distance
+	between these values. There must be a free function called "distance" which takes two ValueType's and
+	returns a float distance between them.
+
+	The idea is that, if you add 0.1 to your alpha value, you will always get a movement of the same distance.
+	Not necessarily between the initial and final points, but the object will have moved at the same
+	speed along the path.
+	**/
+	template<typename ValueType>
+	class ConstVelLinearInterpolator : public WeightedLinearInterpolator<ValueType>
+	{
+	public:
+		typedef ValueType value_type;
+
+		explicit ConstVelLinearInterpolator()
+			: m_totalDist(0.0f)
+		{}
+
+		template<typename BidirectionalRange>
+		void SetValues(const BidirectionalRange &data, bool isLoop = true)
+		{
+			m_values.clear();
+
+			typename BidirectionalRange::const_iterator curr = data.begin();
+			typename BidirectionalRange::const_iterator last = data.end();
+			for(; curr != last; ++curr)
+			{
+				Data currData;
+				currData.data = *curr;
+				currData.weight = 0.0f;
+				m_values.push_back(currData);
+			}
+
+			if(isLoop)
+			{
+				Data currData;
+				currData.data = *data.begin();
+				currData.weight = 0.0f;
+				m_values.push_back(currData);
+			}
+
+			//Compute the distances of each segment.
+			m_totalDist = 0.0f;
+			for(size_t iLoop = 1; iLoop < m_values.size(); ++iLoop)
+			{
+				m_totalDist += distance(m_values[iLoop - 1].data, m_values[iLoop].data);
+				m_values[iLoop].weight = m_totalDist;
+			}
+
+			//Compute the alpha value that represents when to use this segment.
+			for(size_t iLoop = 1; iLoop < m_values.size(); ++iLoop)
+			{
+				m_values[iLoop].weight /= m_totalDist;
+			}
+		}
+
+		float Distance() const {return m_totalDist;}
+
+	private:
 		float m_totalDist;
 	};
 }

framework/MousePole.cpp

 			this->MoveAway(!(glutGetModifiers() & GLUT_ACTIVE_SHIFT));
 	}
 
+	void MousePole::GLUTKeyOffset( int key, float largeIncrement, float smallIncrement )
+	{
+		switch(key)
+		{
+		case 'w': OffsetTargetPos(Framework::MousePole::DIR_FORWARD, largeIncrement); break;
+		case 's': OffsetTargetPos(Framework::MousePole::DIR_BACKWARD, largeIncrement); break;
+		case 'd': OffsetTargetPos(Framework::MousePole::DIR_RIGHT, largeIncrement); break;
+		case 'a': OffsetTargetPos(Framework::MousePole::DIR_LEFT, largeIncrement); break;
+		case 'e': OffsetTargetPos(Framework::MousePole::DIR_UP, largeIncrement); break;
+		case 'q': OffsetTargetPos(Framework::MousePole::DIR_DOWN, largeIncrement); break;
+
+		case 'W': OffsetTargetPos(Framework::MousePole::DIR_FORWARD, smallIncrement); break;
+		case 'S': OffsetTargetPos(Framework::MousePole::DIR_BACKWARD, smallIncrement); break;
+		case 'D': OffsetTargetPos(Framework::MousePole::DIR_RIGHT, smallIncrement); break;
+		case 'A': OffsetTargetPos(Framework::MousePole::DIR_LEFT, smallIncrement); break;
+		case 'E': OffsetTargetPos(Framework::MousePole::DIR_UP, smallIncrement); break;
+		case 'Q': OffsetTargetPos(Framework::MousePole::DIR_DOWN, smallIncrement); break;
+		}
+	}
+
 	namespace
 	{
 		glm::vec3 g_offsets[] =

framework/MousePole.h

 		void GLUTMouseMove(const glm::ivec2 &position);
 		void GLUTMouseButton(int button, int btnState, const glm::ivec2 &position);
 		void GLUTMouseWheel(int direction, const glm::ivec2 &position);
+		void GLUTKeyOffset(int key, float largeIncrement, float smallIncrement);
 
 		bool IsDragging() const {return m_bIsDragging;}
 

framework/Timer.cpp

 	void Timer::Rewind( float secRewind )
 	{
 		m_secAccumTime -= secRewind;
+		if(m_secAccumTime < 0.0f)
+			m_secAccumTime = 0.0f;
+	}
+
+	void Timer::Fastforward( float secFF )
+	{
+		m_secAccumTime += secFF;
 	}
 
 	float Timer::GetAlpha() const

framework/Timer.h

 		//Subtracts secRewind from the current time and continues from there.
 		void Rewind(float secRewind);
 
+		//Adds secRewind to the current time and continues from there.
+		void Fastforward(float secFF);
+
 		//Returns a number [0, 1], representing progress through the duration. Only used
 		//for SINGLE and LOOP timers.
 		float GetAlpha() const;