/*
-----------------------------------------------------------------------------
This source file is part of OGRE
    (Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright © 2000-2003 The OGRE Team
Also see acknowledgements in Readme.html

You may use this sample code for anything you like, it is not covered by the
LGPL like the rest of the engine.
-----------------------------------------------------------------------------
*/

/**
    \file 
        Dot3Bump.cpp
    \brief
        Specialisation of OGRE's framework application to show the
        dotproduct blending operation and normalization cube map usage
		for achieving bump mapping effect
	\par
		Tangent space computations made through the guide of the
		tutorial on bump mapping from http://users.ox.ac.uk/~univ1234
		author : paul.baker@univ.ox.ac.uk
**/

#include "ExampleApplication.h"

#if OGRE_PLATFORM == PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif

// entities we'll use
#define NUM_ENTITIES 3
Entity* mEntities[NUM_ENTITIES];
String mEntityMeshes[NUM_ENTITIES] = 
{
    "athene.mesh",
    "knot.mesh",
    "ogrehead.mesh"
};
size_t mCurrentEntity = 0;

// Lights
#define NUM_LIGHTS 3

// the light
Light *mLights[NUM_LIGHTS];
// billboards for lights
BillboardSet* mLightFlareSets[NUM_LIGHTS];
Billboard* mLightFlares[NUM_LIGHTS];
// Positions for lights
Vector3 mLightPositions[NUM_LIGHTS] = 
{
	Vector3(300,0,0),
	Vector3(-300,50,0),
	Vector3(0, -300, -100)
};
// Base orientations of the lights 
Radian mLightRotationAngles[NUM_LIGHTS] = { Degree(0), Degree(30), Degree(75) };
Vector3 mLightRotationAxes[NUM_LIGHTS] = {
    Vector3::UNIT_X, 
    Vector3::UNIT_Z,
    Vector3::UNIT_Y
};
// Rotation speed for lights, degrees per second
Real mLightSpeeds[NUM_LIGHTS] = { 30, 10, 50};

// Colours for the lights
ColourValue mDiffuseLightColours[NUM_LIGHTS] =
{
	ColourValue(1, 1, 1),
	ColourValue(1, 0, 0),
	ColourValue(1, 1, 0.5)
};
ColourValue mSpecularLightColours[NUM_LIGHTS] =
{
	ColourValue(1, 1, 1),
	ColourValue(1, 0.8, 0.8),
	ColourValue(1, 1, 0.8)
};
// Which lights are enabled
bool mLightState[NUM_LIGHTS] = 
{
	true,
	true,
	false
};
// The materials
#define NUM_MATERIALS 4
String mMaterialNames[NUM_ENTITIES][NUM_MATERIALS] = 
{
    // athene
    "Examples/Athene/Basic",
    "Examples/Athene/NormalMapped",
    "Examples/Athene/NormalMappedSpecular",
    "Examples/Athene/NormalMapped",
    // knot
	"Examples/BumpMapping/SingleLight",
	"Examples/BumpMapping/MultiLight",
	"Examples/BumpMapping/MultiLightSpecular",
    "Examples/OffsetMapping/Specular",
    // ogre head
    "Examples/BumpMapping/SingleLight",
    "Examples/BumpMapping/MultiLight",
    "Examples/BumpMapping/MultiLightSpecular",
    "Examples/OffsetMapping/Specular"
};
size_t mCurrentMaterial = 1;

// the scene node of the entity
SceneNode *mMainNode;
// the light nodes
SceneNode* mLightNodes[NUM_LIGHTS];
// the light node pivots
SceneNode* mLightPivots[NUM_LIGHTS];

GuiElement* mObjectInfo;
GuiElement* mMaterialInfo;
GuiElement* mInfo;

#define KEY_PRESSED(_key,_timeDelay, _macro) \
{ \
    if (mInputDevice->isKeyDown(_key) && timeDelay <= 0) \
    { \
		timeDelay = _timeDelay; \
        _macro ; \
    } \
}



// Event handler to add ability to change material
class Dp3_Listener : public ExampleFrameListener
{
public:
    Dp3_Listener(RenderWindow* win, Camera* cam)
        : ExampleFrameListener(win, cam)
    {
    }

    void flipLightState(size_t i)
    {
        mLightState[i] = !mLightState[i];
        mLights[i]->setVisible(mLightState[i]);
        mLightFlareSets[i]->setVisible(mLightState[i]);
    }
    bool frameStarted(const FrameEvent& evt)
    {
        if(!ExampleFrameListener::frameStarted(evt))
            return false;
        
        static Real timeDelay = 0;

        timeDelay -= evt.timeSinceLastFrame;

		// switch meshes
        KEY_PRESSED(KC_O, 1, 
            mEntities[mCurrentEntity]->setVisible(false); 
            mCurrentEntity = (++mCurrentEntity) % NUM_ENTITIES; 
            mEntities[mCurrentEntity]->setVisible(true);
            mEntities[mCurrentEntity]->setMaterialName(mMaterialNames[mCurrentEntity][mCurrentMaterial]);
            mObjectInfo->setCaption("Current: " + mEntityMeshes[mCurrentEntity]);
            mMaterialInfo->setCaption("Current: " + mMaterialNames[mCurrentEntity][mCurrentMaterial]);
        );

		// switch materials
		KEY_PRESSED(KC_M, 1, 
            mCurrentMaterial = (++mCurrentMaterial) % NUM_MATERIALS; 
            mEntities[mCurrentEntity]->setMaterialName(mMaterialNames[mCurrentEntity][mCurrentMaterial]);
            mMaterialInfo->setCaption("Current: " + mMaterialNames[mCurrentEntity][mCurrentMaterial]);
        );

		// enable / disable lights
		KEY_PRESSED(KC_1, 1, flipLightState(0));
		// switch materials
		KEY_PRESSED(KC_2, 1, flipLightState(1));
		// switch materials
		KEY_PRESSED(KC_3, 1, flipLightState(2));

        // animate the lights
        for (size_t i = 0; i < NUM_LIGHTS; ++i)
            mLightPivots[i]->rotate(Vector3::UNIT_Z, Degree(mLightSpeeds[i] * evt.timeSinceLastFrame));

		return true;
    }

};

class Dp3_Application : public ExampleApplication
{
public:
    Dp3_Application() {}
	
protected:
	SceneNode *mpObjsNode; // the node wich will hold our entities

	void createScene(void)
    {
        // First check that vertex programs and dot3 or fragment programs are supported
		const RenderSystemCapabilities* caps = Root::getSingleton().getRenderSystem()->getCapabilities();
        if (!caps->hasCapability(RSC_VERTEX_PROGRAM))
        {
            Except(1, "Your card does not support vertex programs, so cannot "
                "run this demo. Sorry!", 
                "Dot3Bump::createScene");
        }
        if (!(caps->hasCapability(RSC_FRAGMENT_PROGRAM) 
			|| caps->hasCapability(RSC_DOT3)) )
        {
            Except(1, "Your card does not support dot3 blending or fragment programs, so cannot "
                "run this demo. Sorry!", 
                "Dot3Bump::createScene");
        }

        // Set ambient light and fog
        mSceneMgr->setAmbientLight(ColourValue(0.0, 0.0, 0.0));
        /*
		// Define a floor plane mesh
        Plane p;
        p.normal = Vector3::UNIT_Y;
        p.d = 200;
        MeshManager::getSingleton().createPlane("FloorPlane",p,2000,2000,1,1,true,1,5,5,Vector3::UNIT_Z);
        // Create an entity (the floor)
        Entity *floorEnt = mSceneMgr->createEntity("floor", "FloorPlane");
        floorEnt->setMaterialName("Examples/DP3Terrain");
        mSceneMgr->getRootSceneNode()->attachObject(floorEnt);
        */

        mMainNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();

        // Load the meshes with non-default HBU options
		for(int mn = 0; mn < NUM_ENTITIES; mn++) {
			Mesh* pMesh = MeshManager::getSingleton().load(mEntityMeshes[mn],
				HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY, 
				HardwareBuffer::HBU_STATIC_WRITE_ONLY, 
				true, true); //so we can still read it
            // Build tangent vectors, all our meshes use only 1 texture coordset 
            unsigned short src, dest;
            if (!pMesh->suggestTangentVectorBuildParams(src, dest))
            {
                pMesh->buildTangentVectors(src, dest);
            }
            // Create entity
            mEntities[mn] = mSceneMgr->createEntity("Ent" + StringConverter::toString(mn), 
                mEntityMeshes[mn]);
            // Attach to child of root node
    		mMainNode->attachObject(mEntities[mn]);
            // Make invisible, except for index 0
            if (mn == 0)
                mEntities[mn]->setMaterialName(mMaterialNames[mCurrentEntity][mCurrentMaterial]);
            else
                mEntities[mn]->setVisible(false);
		}

        for (unsigned int i = 0; i < NUM_LIGHTS; ++i)
        {
            mLightPivots[i] = mSceneMgr->getRootSceneNode()->createChildSceneNode();
            mLightPivots[i]->rotate(mLightRotationAxes[i], mLightRotationAngles[i]);
            // Create a light, use default parameters
            mLights[i] = mSceneMgr->createLight("Light" + StringConverter::toString(i));
			mLights[i]->setPosition(mLightPositions[i]);
			mLights[i]->setDiffuseColour(mDiffuseLightColours[i]);
			mLights[i]->setSpecularColour(mSpecularLightColours[i]);
			mLights[i]->setVisible(mLightState[i]);
            // Attach light
            mLightPivots[i]->attachObject(mLights[i]);
			// Create billboard for light
			mLightFlareSets[i] = mSceneMgr->createBillboardSet("Flare" + StringConverter::toString(i));
			mLightFlareSets[i]->setMaterialName("Examples/Flare");
			mLightPivots[i]->attachObject(mLightFlareSets[i]);
			mLightFlares[i] = mLightFlareSets[i]->createBillboard(mLightPositions[i]);
			mLightFlares[i]->setColour(mDiffuseLightColours[i]);
			mLightFlareSets[i]->setVisible(mLightState[i]);
        }
        // move the camera a bit right and make it look at the knot
		mCamera->moveRelative(Vector3(50, 0, 20));
		mCamera->lookAt(0, 0, 0);
		// show overlay
		Overlay *pOver = (Overlay *)OverlayManager::getSingleton().getByName("Example/DP3Overlay");    
        mObjectInfo = GuiManager::getSingleton().getGuiElement("Example/DP3/ObjectInfo");
        mMaterialInfo = GuiManager::getSingleton().getGuiElement("Example/DP3/MaterialInfo");
        mInfo = GuiManager::getSingleton().getGuiElement("Example/DP3/Info");

        mObjectInfo->setCaption("Current: " + mEntityMeshes[mCurrentEntity]);
        mMaterialInfo->setCaption("Current: " + mMaterialNames[mCurrentEntity][mCurrentMaterial]);
        if (!caps->hasCapability(RSC_FRAGMENT_PROGRAM))
        {
            mInfo->setCaption("NOTE: Light colours and specular highlights are not supported by your card.");
        }
		pOver->show();
	}

    // Create new frame listener
    void createFrameListener(void)
    {
        mFrameListener= new Dp3_Listener(mWindow, mCamera);
        mRoot->addFrameListener(mFrameListener);
    }
};

#if OGRE_PLATFORM == PLATFORM_WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char **argv)
#endif
{
    // Create application object
    Dp3_Application app;

    try {
        app.go();
    } catch( Exception& e ) {
#if OGRE_PLATFORM == PLATFORM_WIN32
        MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
        fprintf(stderr, "An exception has occured: %s\n",
                e.getFullDescription().c_str());
#endif
    }

    return 0;
}
