Index: /GTP/trunk/App/Demos/Vis/KdTreeDemo/OGRE/include/TestKdTree.h
===================================================================
--- /GTP/trunk/App/Demos/Vis/KdTreeDemo/OGRE/include/TestKdTree.h	(revision 1195)
+++ /GTP/trunk/App/Demos/Vis/KdTreeDemo/OGRE/include/TestKdTree.h	(revision 1200)
@@ -74,4 +74,5 @@
 	bool loadSceneIV(const String &filename, SceneNode *root, const int index);
 private:
+	void createMaterials(void);
 	std::string cat(std::string name, int x, int z)
 	{
Index: /GTP/trunk/App/Demos/Vis/KdTreeDemo/OGRE/include/TestKdTreeAppListener.h
===================================================================
--- /GTP/trunk/App/Demos/Vis/KdTreeDemo/OGRE/include/TestKdTreeAppListener.h	(revision 1195)
+++ /GTP/trunk/App/Demos/Vis/KdTreeDemo/OGRE/include/TestKdTreeAppListener.h	(revision 1200)
@@ -131,11 +131,22 @@
 
 	FrameList mFrameList;
+	FrameList::iterator mCurrFrame;
+	Real mTimeRemaining;
 
 	AppState mAppState;
+
+	Overlay * mDemoOverlay;
+	OverlayElement * mDemoStatus;
 
 	void toggleRecord();
 	void togglePlayback();
+//	void resetPlayback();
+
 	void saveFrameInfo(Real elapsedTime);
 
+	void setDemoOverlay();
+
+	void saveFramesBinary(const String& filename);
+	void loadFramesBinary(const String& filename);
 public:
 	void loadFrames(const String& filename);
Index: /GTP/trunk/App/Demos/Vis/KdTreeDemo/OGRE/src/TestKdTree.cpp
===================================================================
--- /GTP/trunk/App/Demos/Vis/KdTreeDemo/OGRE/src/TestKdTree.cpp	(revision 1195)
+++ /GTP/trunk/App/Demos/Vis/KdTreeDemo/OGRE/src/TestKdTree.cpp	(revision 1200)
@@ -101,4 +101,6 @@
 void KdTreeApp::createScene(void)
 {
+	createMaterials();
+
 	Entity *deaths;
 	SceneNode *nodes;
@@ -370,4 +372,32 @@
 }
 
+void KdTreeApp::createMaterials()
+{
+	MaterialPtr mat;
+	Technique * tech;
+	Pass * pass;
+	TextureUnitState * tex;
+
+	// create play button
+	mat = MaterialManager::getSingleton().create("KdTree/DemoPlayButton", "General");
+	mat->setLightingEnabled(false);
+	tech = mat->getTechnique(0);
+	tech->setSceneBlending(SBT_TRANSPARENT_ALPHA);
+	tech->setDepthCheckEnabled(false);
+	pass = tech->getPass(0);
+	tex = pass->createTextureUnitState();
+	tex->setTextureName("DemoPlayButton.png");
+
+	// create record button
+	mat = MaterialManager::getSingleton().create("KdTree/DemoRecordButton", "General");
+	mat->setLightingEnabled(false);
+	tech = mat->getTechnique(0);
+	tech->setSceneBlending(SBT_TRANSPARENT_ALPHA);
+	tech->setDepthCheckEnabled(false);
+	pass = tech->getPass(0);
+	tex = pass->createTextureUnitState();
+	tex->setTextureName("DemoRecordButton.png");
+}
+
 
 //-----------------------------------------------------------------------
Index: /GTP/trunk/App/Demos/Vis/KdTreeDemo/OGRE/src/TestKdTreeAppListener.cpp
===================================================================
--- /GTP/trunk/App/Demos/Vis/KdTreeDemo/OGRE/src/TestKdTreeAppListener.cpp	(revision 1195)
+++ /GTP/trunk/App/Demos/Vis/KdTreeDemo/OGRE/src/TestKdTreeAppListener.cpp	(revision 1200)
@@ -2,4 +2,5 @@
 #include <cstdlib>
 
+#include <OgrePanelOverlayElement.h>
 #include "TestKdTreeAppListener.h"
 
@@ -129,4 +130,7 @@
 	MaterialManager::getSingleton().setDefaultAnisotropy(mAniso);
 
+	mDemoOverlay = 0;
+	mDemoStatus = 0;
+
 	initKdTreeOverlay();
 	initStatsOverlay();
@@ -134,5 +138,8 @@
 	showDebugOverlay(true);
 
-	loadFrames("demo-in.txt");
+	setDemoOverlay();
+
+	//loadFrames("demo-in.txt");
+	loadFramesBinary("demo-in.bin");
 }
 //-----------------------------------------------------------------------
@@ -150,5 +157,8 @@
 	// save demo
 	if (!mFrameList.empty())
-		saveFrames("demo-out.txt");
+	{
+		//saveFrames("demo-out.txt");
+		saveFramesBinary("demo-out.bin");
+	}
 }
 //-----------------------------------------------------------------------
@@ -942,12 +952,69 @@
 	}
 
-	if ( !mUseBufferedInputMouse || !mUseBufferedInputKeys)
-	{
-		// one of the input modes is immediate, so update the movement vector
-
-		moveCamera();
-
-	}
-
+	// demo playback - replace position with stored one
+	if (mAppState == AS_PLAYBACK)
+	{
+		// update time
+		mTimeRemaining -= evt.timeSinceLastFrame;
+
+		static FrameList::iterator lastFrame;
+		if (mCurrFrame == mFrameList.begin())
+			lastFrame = mCurrFrame;
+
+		// advance to next stored frame
+		while (mTimeRemaining <= 0.0 && mCurrFrame != mFrameList.end())
+		{
+			lastFrame = mCurrFrame;
+			++ mCurrFrame;
+			mTimeRemaining += mCurrFrame->mElapsedTime;
+		}
+
+		// when reached last frame, reset and stop playback
+		if (mCurrFrame == mFrameList.end())
+		{
+			togglePlayback();
+		}
+		// interpolate position & orientation and modify move vectors
+		else
+		{
+			
+			// interpolation factor
+			Real factor = 1.0 - mTimeRemaining / mCurrFrame->mElapsedTime;
+
+			// interpolate position and orientation
+			Vector3 pos = lastFrame->mPosition + 
+				factor * (mCurrFrame->mPosition - lastFrame->mPosition);
+
+			Quaternion or = Quaternion::Slerp(factor, lastFrame->mOrientation,
+				mCurrFrame->mOrientation);
+
+			// update camera
+			mCamNode->setPosition(pos);
+			mCamNode->setOrientation(or);
+
+			// update viz camera
+			mTopCam->moveRelative(mVizCamTransVect);
+			if (mTopCamFollow)
+			{
+				Vector3 toppos = mTopCam->getPosition();
+				Vector3 campos = mCamNode->getPosition();
+				mTopCam->setPosition(Vector3::ZERO);
+				mTopCam->setOrientation(Quaternion::IDENTITY);
+				mTopCam->setPosition(campos.x, toppos.y, campos.z);
+				mTopCam->pitch(Radian(-Math::HALF_PI));
+			}
+
+		}
+	}
+	else
+	{
+		if ( !mUseBufferedInputMouse || !mUseBufferedInputKeys)
+		{
+			// one of the input modes is immediate, so update the movement vector
+
+			moveCamera();
+
+		}
+	}
 
 	//saveFrameInfo(evt.timeSinceLastFrame);
@@ -1024,13 +1091,22 @@
 void KdTreeAppListener::toggleRecord()
 {
-	// TODO: overlay
+	// start recording
 	if (mAppState == AS_NORMAL)
 	{
+		// change state
 		mAppState = AS_RECORD;
-	}
+
+		// clear old recording
+		mFrameList.clear();
+	}
+	// stop recording
 	else if (mAppState == AS_RECORD)
 	{
+		// change state
 		mAppState = AS_NORMAL;
-	}
+
+	}
+	// update display
+	setDemoOverlay();
 }
 
@@ -1038,14 +1114,45 @@
 void KdTreeAppListener::togglePlayback()
 {
-	// TODO: overlay
-	if (mAppState == AS_NORMAL)
-	{
+	static Vector3 pos;
+	static Quaternion or;
+
+	// start playback
+	if (mAppState == AS_NORMAL && !mFrameList.empty())
+	{
+		// change state
 		mAppState = AS_PLAYBACK;
-	}
+
+		// save old position
+		pos = mCamNode->getPosition();
+		or = mCamNode->getOrientation();
+
+		// reset demo
+		mCurrFrame = mFrameList.begin();
+		mTimeRemaining = mCurrFrame->mElapsedTime;
+	}
+	// stop playback
 	else if (mAppState == AS_PLAYBACK)
 	{
+		// change state
 		mAppState = AS_NORMAL;
-	}
-}
+
+		// restore old position
+		mCamNode->setPosition(pos);
+		mCamNode->setOrientation(or);
+	}
+
+	// update display
+	setDemoOverlay();
+}
+
+//-----------------------------------------------------------------------------
+//void KdTreeAppListener::resetPlayback()
+//{
+//	if (!mFrameList.empty())
+//	{
+//		mCurrFrame = mFrameList.begin();
+//		mTimeRemaining = mCurrFrame->mElapsedTime;
+//	}
+//}
 
 //-----------------------------------------------------------------------------
@@ -1083,4 +1190,37 @@
 
 //-----------------------------------------------------------------------------
+void KdTreeAppListener::saveFramesBinary(const String& filename)
+{
+	std::ofstream dest(filename.c_str(), std::ios_base::out | std::ios_base::binary);
+
+	if (!dest)
+	{
+		LogManager::getSingleton().logMessage("Failed to open file for saving demo: " + filename);
+		return;
+	}
+
+	FrameList::iterator it = mFrameList.begin();
+	FrameList::iterator end = mFrameList.end();
+
+	size_t size = sizeof(Real);
+
+	// dump values
+	while (it != end)
+	{
+		dest.write((char *)&it->mPosition.x, size);
+		dest.write((char *)&it->mPosition.y, size);
+		dest.write((char *)&it->mPosition.z, size);
+		dest.write((char *)&it->mOrientation.w, size);
+		dest.write((char *)&it->mOrientation.x, size);
+		dest.write((char *)&it->mOrientation.y, size);
+		dest.write((char *)&it->mOrientation.z, size);
+		dest.write((char *)&it->mElapsedTime, size);
+		++ it;
+	}
+
+	dest.close();
+}
+
+//-----------------------------------------------------------------------------
 void KdTreeAppListener::loadFrames(const String& filename)
 {
@@ -1121,2 +1261,82 @@
 	src.close();
 }
+
+//-----------------------------------------------------------------------------
+void KdTreeAppListener::loadFramesBinary(const String& filename)
+{
+	// open file
+	std::ifstream src(filename.c_str(), std::ios_base::out | std::ios_base::binary);
+	if (!src)
+	{
+		LogManager::getSingleton().logMessage("Failed to open file for reading demo: " + filename);
+		return;
+	}
+
+	// clear the list
+	mFrameList.clear();
+
+	size_t size = sizeof(Real);
+
+	Vector3 pos;
+	Quaternion or;
+	Real time;
+
+	while (!src.eof())
+	{
+		src.read((char *)&pos.x, size);
+		src.read((char *)&pos.y, size);
+		src.read((char *)&pos.z, size);
+		src.read((char *)&or.w, size);
+		src.read((char *)&or.x, size);
+		src.read((char *)&or.y, size);
+		src.read((char *)&or.z, size);
+		src.read((char *)&time, size);
+
+		mFrameList.push_back(FrameInfo(pos, or, time));
+	}
+
+	// HACK pop last frame, was doubled while reading
+	mFrameList.pop_back();
+
+	src.close();
+}
+//-----------------------------------------------------------------------------
+void KdTreeAppListener::setDemoOverlay()
+{
+	// init overlay if not present
+	if (!mDemoOverlay || !mDemoStatus)
+	{
+		OGRE_DELETE(mDemoOverlay);
+		OGRE_DELETE(mDemoStatus);
+
+		mDemoStatus = OverlayManager::getSingleton().createOverlayElement("Panel", "KdTree/DemoStatus");
+		mDemoStatus->setMetricsMode(GMM_PIXELS);
+		mDemoStatus->setHorizontalAlignment(GHA_CENTER);
+		mDemoStatus->setVerticalAlignment(GVA_TOP);
+		mDemoStatus->setTop(5);
+		mDemoStatus->setLeft(-16);
+		mDemoStatus->setDimensions(32, 32);
+
+		mDemoOverlay = OverlayManager::getSingleton().create("KdTree/DemoOverlay");
+		mDemoOverlay->setZOrder(500);
+		mDemoOverlay->add2D(static_cast<PanelOverlayElement *>(mDemoStatus));
+	}
+
+	switch(mAppState)
+	{
+	case AS_NORMAL:
+		mDemoOverlay->hide();
+		break;
+	case AS_PLAYBACK:
+		mDemoStatus->setMaterialName("KdTree/DemoPlayButton");
+		mDemoOverlay->show();
+		break;
+	case AS_RECORD:
+		mDemoStatus->setMaterialName("KdTree/DemoRecordButton");
+		mDemoOverlay->show();
+		break;
+	default:
+	    break;
+	}
+
+}
