// This is a Demo of the Irrlicht Engine (c) 2005-2009 by N.Gebhardt.
// This file is not documented.

#include <irrlicht.h>
#include "extantz.h"
#include "CDemo.h"

CDemo::CDemo(GLData *gld, bool a)
  : additive(a),
  device(gld->device),
  currentScene(0),
  quakeLevelMesh(0), quakeLevelNode(0), skyboxNode(0), model1(0), model2(0),
  campFire(0), metaSelector(0), mapSelector(0), sceneStartTime(0),
  timeForThisScene(0)
{
}

CDemo::~CDemo()
{
  if (mapSelector) mapSelector->drop();
  if (metaSelector) metaSelector->drop();
}

void CDemo::setup(GLData *gld)
{
  device = gld->device;
  IrrlichtDevice *device = gld->device;
// IVideoDriver	*driver = gld->driver;
// ISceneManager	*smgr   = gld->smgr;

  if (device->getFileSystem()->existFile("irrlicht.dat")) device->getFileSystem()->addFileArchive("irrlicht.dat");
  else device->getFileSystem()->addFileArchive("media/Irrlicht/irrlicht.dat");
  if (device->getFileSystem()->existFile("map-20kdm2.pk3")) device->getFileSystem()->addFileArchive("map-20kdm2.pk3");
  else device->getFileSystem()->addFileArchive("media/Irrlicht/map-20kdm2.pk3");

  sceneStartTime   = device->getTimer()->getTime();
  timeForThisScene = 0;
  loadSceneData();
}

void CDemo::preDraw(GLData *gld, u32 now)
{
  if (((now - sceneStartTime) > timeForThisScene) && (timeForThisScene != -1)) switchToNextScene(gld);
  createParticleImpacts();
}

bool CDemo::OnEvent(const SEvent& event)
{
  if (!device) return false;

  if ((((event.EventType == EET_KEY_INPUT_EVENT) && (event.KeyInput.Key == KEY_SPACE) &&
	(event.KeyInput.PressedDown == false)) ||
       ((event.EventType == EET_MOUSE_INPUT_EVENT) && (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP))) && (currentScene == 3))
  {
    shoot();
  }
  else if (device->getSceneManager()->getActiveCamera())
  {
    device->getSceneManager()->getActiveCamera()->OnEvent(event);
    return true;
  }

  return false;
}

void CDemo::switchToNextScene(GLData *gld)
{
  currentScene++;
  if (currentScene > 3) currentScene = 1;

  scene::ISceneManager	    *sm	    = device->getSceneManager();
  scene::ISceneNodeAnimator *sa	    = 0;
  scene::ICameraSceneNode   *camera = 0;

  camera = sm->getActiveCamera();

  switch (currentScene)
  {
    case 1 :    // panorama camera
    {
      core::array<core::vector3df> points, points2;

      points.push_back(core::vector3df(-931.473755f, 900.0f, 2000.0f));                   // -49873
      points.push_back(core::vector3df(-931.473755f, 900.0f, 2000.0f));                   // -49873
      points.push_back(core::vector3df(-931.473755f, 700.0f, 1750.0f));                   // -49873
      points.push_back(core::vector3df(-931.473755f, 500.0f, 1500.0f));                   // -49873
      points.push_back(core::vector3df(-931.473755f, 300.0f, 1250.0f));                   // -49873
      points.push_back(core::vector3df(-931.473755f, 200.0f, 1000.0f));                   // -49873
      points.push_back(core::vector3df(-931.473755f, 138.300003f, 987.279114f));          // -49873
      points.push_back(core::vector3df(-847.902222f, 136.757553f, 915.792725f));          // -50559
      points.push_back(core::vector3df(-748.680420f, 152.254501f, 826.418945f));          // -51964
      points.push_back(core::vector3df(-708.428406f, 213.569580f, 784.466675f));          // -53251
      points.push_back(core::vector3df(-686.217651f, 288.141174f, 762.965576f));          // -54015
      points.push_back(core::vector3df(-679.685059f, 365.095612f, 756.551453f));          // -54733
      points.push_back(core::vector3df(-671.317871f, 447.360107f, 749.394592f));          // -55588
      points.push_back(core::vector3df(-669.468445f, 583.335632f, 747.711853f));          // -56178
      points.push_back(core::vector3df(-667.611267f, 727.313232f, 746.018250f));          // -56757
      points.push_back(core::vector3df(-665.853210f, 862.791931f, 744.436096f));          // -57859
      points.push_back(core::vector3df(-642.649597f, 1026.047607f, 724.259827f));         // -59705
      points.push_back(core::vector3df(-517.793884f, 838.396790f, 490.326050f));          // -60983
      points.push_back(core::vector3df(-474.387299f, 715.691467f, 344.639984f));          // -61629
      points.push_back(core::vector3df(-444.600250f, 601.155701f, 180.938095f));          // -62319
      points.push_back(core::vector3df(-414.808899f, 479.691406f, 4.866660f));            // -63048
      points.push_back(core::vector3df(-410.418945f, 429.642242f, -134.332687f));         // -63757
      points.push_back(core::vector3df(-399.837585f, 411.498383f, -349.350983f));         // -64418
      points.push_back(core::vector3df(-390.756653f, 403.970093f, -524.454407f));         // -65005
      points.push_back(core::vector3df(-334.864227f, 350.065491f, -732.397400f));         // -65701
      points.push_back(core::vector3df(-195.253387f, 349.577209f, -812.475891f));         // -66335
      points.push_back(core::vector3df(16.255573f, 363.743134f, -833.800415f));           // -67170
      points.push_back(core::vector3df(234.940964f, 352.957825f, -820.150696f));          // -67939
      points.push_back(core::vector3df(436.797668f, 349.236450f, -816.914185f));          // -68596
      points.push_back(core::vector3df(575.236206f, 356.244812f, -719.788513f));          // -69166
      points.push_back(core::vector3df(594.131042f, 387.173828f, -609.675598f));          // -69744
      points.push_back(core::vector3df(617.615234f, 412.002899f, -326.174072f));          // -70640
      points.push_back(core::vector3df(606.456848f, 403.221954f, -104.179291f));          // -71390
      points.push_back(core::vector3df(610.958252f, 407.037750f, 117.209778f));           // -72085
      points.push_back(core::vector3df(597.956909f, 395.167877f, 345.942200f));           // -72817
      points.push_back(core::vector3df(587.383118f, 391.444519f, 566.098633f));           // -73477
      points.push_back(core::vector3df(559.572449f, 371.991333f, 777.689453f));           // -74124
      points.push_back(core::vector3df(423.753204f, 329.990051f, 925.859741f));           // -74941
      points.push_back(core::vector3df(247.520050f, 252.818954f, 935.311829f));           // -75651
      points.push_back(core::vector3df(114.756012f, 199.799759f, 805.014160f));
      points.push_back(core::vector3df(96.783348f, 181.639481f, 648.188110f));
      points.push_back(core::vector3df(97.865623f, 138.905975f, 484.812561f));
      points.push_back(core::vector3df(99.612457f, 102.463669f, 347.603210f));
      points.push_back(core::vector3df(99.0f, 95.0f, 347.0f));
      points.push_back(core::vector3df(99.0f, 90.0f, 347.0f));
      points.push_back(core::vector3df(99.0f, 85.0f, 347.0f));
      points.push_back(core::vector3df(99.0f, 80.0f, 347.0f));
      points.push_back(core::vector3df(99.0f, 75.0f, 347.0f));
      points.push_back(core::vector3df(99.0f, 75.0f, 347.0f));
      points.push_back(core::vector3df(99.0f, 75.0f, 347.0f));
      timeForThisScene = (points.size() - 2) * 1000;
      camera	       = sm->addCameraSceneNode(0, points[0], core::vector3df(0, 400, 0));
      sa	       = sm->createFollowSplineAnimator(device->getTimer()->getTime(), points, 1.0f, 0.6f, false, false);
      camera->addAnimator(sa);
      sa->drop();
      break;
    }

    case 2 :    // panorama camera
    {
      core::array<core::vector3df> points;

      camera->setTarget(core::vector3df(100, 145, -80));

      points.push_back(core::vector3df(99.0f, 75.0f, 347.0f));
      points.push_back(core::vector3df(100.0f, 75.0f, 347.0f));
      points.push_back(core::vector3df(105.0f, 75.0f, 347.0f));
      points.push_back(core::vector3df(110.0f, 70.0f, 347.0f));
      points.push_back(core::vector3df(115.0f, 70.0f, -160.0f));
      points.push_back(core::vector3df(120.0f, 70.0f, -160.0f));
      points.push_back(core::vector3df(125.0f, 65.0f, -160.0f));
      points.push_back(core::vector3df(130.0f, 65.0f, -160.0f));
      points.push_back(core::vector3df(135.0f, 65.0f, -160.0f));
      points.push_back(core::vector3df(150.0f, 170.0f, -160.0f));
      points.push_back(core::vector3df(150.0f, 170.0f, -160.0f));
      points.push_back(core::vector3df(150.0f, 170.0f, -160.0f));
      timeForThisScene = (points.size() - 2) * 1000;
      sa	       = sm->createFollowSplineAnimator(device->getTimer()->getTime(), points, 1.0f, 0.6f, false, false);
      camera->addAnimator(sa);
      sa->drop();
      break;
    }

    case 3 :    // interactive, go around
    {
      if (camera)
      {
	sm->setActiveCamera(0);
	camera->remove();
	camera = 0;
      }
      timeForThisScene = -1;

      gld->camera = addExtantzCamera(sm, NULL, -1);
      camera	  = gld->camera;
      camera->setPosition(core::vector3df(108, 140, -140));
      camera->setFarValue(5000.0f);
      gld->move = getCameraMove(gld->camera);

      scene::ISceneNodeAnimatorCollisionResponse *collider =
	sm->createCollisionResponseAnimator(metaSelector, camera,
	  core::vector3df(25, 50, 25), core::vector3df(0, quakeLevelMesh ? -10.f : 0.0f, 0), core::vector3df(0, 45, 0), 0.005f);
      camera->addAnimator(collider);
      collider->drop();
      break;
    }
  } // switch

  sceneStartTime = device->getTimer()->getTime();
}

void CDemo::loadSceneData()
{
  // load quake level

  video::IVideoDriver  *driver = device->getVideoDriver();
  scene::ISceneManager *sm     = device->getSceneManager();

  // Quake3 Shader controls Z-Writing
  sm->getParameters()->setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true);

  quakeLevelMesh = (scene::IQ3LevelMesh *)sm->getMesh("maps/20kdm2.bsp");

  if (quakeLevelMesh)
  {
    u32 i;

    // move all quake level meshes (non-realtime)
    core::matrix4 m;
    m.setTranslation(core::vector3df(-1300, -70, -1249));

    for (i = 0; i != scene::quake3::E_Q3_MESH_SIZE; ++i) sm->getMeshManipulator()->transform(quakeLevelMesh->getMesh(i), m);

    quakeLevelNode = sm->addOctreeSceneNode(quakeLevelMesh->getMesh(scene::quake3::E_Q3_MESH_GEOMETRY));
    if (quakeLevelNode)
    {
      // quakeLevelNode->setPosition(core::vector3df(-1300, -70, -1249));
      quakeLevelNode->setVisible(true);

      // create map triangle selector
      mapSelector = sm->createOctreeTriangleSelector(quakeLevelMesh->getMesh(0), quakeLevelNode, 128);

      // if not using shader and no gamma it's better to use more lighting, because
      // quake3 level are usually dark
      quakeLevelNode->setMaterialType(video::EMT_LIGHTMAP_M4);

      // set additive blending if wanted
      if (additive) quakeLevelNode->setMaterialType(video::EMT_LIGHTMAP_ADD);
    }

    // the additional mesh can be quite huge and is unoptimized
    scene::IMesh *additional_mesh = quakeLevelMesh->getMesh(scene::quake3::E_Q3_MESH_ITEMS);

    for (i = 0; i != additional_mesh->getMeshBufferCount(); ++i)
    {
      scene::IMeshBuffer     *meshBuffer = additional_mesh->getMeshBuffer(i);
      const video::SMaterial &material	 = meshBuffer->getMaterial();

      // ! The ShaderIndex is stored in the material parameter
      s32 shaderIndex = (s32)material.MaterialTypeParam2;

      // the meshbuffer can be rendered without additional support, or it has no shader
      const scene::quake3::IShader *shader = quakeLevelMesh->getShader(shaderIndex);
      if (0 == shader) continue;
      // Now add the MeshBuffer(s) with the current Shader to the Manager
      sm->addQuake3SceneNode(meshBuffer, shader);
    }
  }

  // load sydney model and create 2 instances

  scene::IAnimatedMesh *mesh = 0;
  mesh = sm->getMesh("media/Irrlicht/sydney.md2");
  if (mesh)
  {
    model1 = sm->addAnimatedMeshSceneNode(mesh);
    if (model1)
    {
      model1->setMaterialTexture(0, driver->getTexture("media/Irrlicht/sydney.bmp"));
      model1->setPosition(core::vector3df(100, 40, -80));
      model1->setScale(core::vector3df(2, 2, 2));
      model1->setMD2Animation(scene::EMAT_STAND);
      model1->setMaterialFlag(video::EMF_LIGHTING, true);
      model1->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);
      model1->addShadowVolumeSceneNode();
    }

    model2 = sm->addAnimatedMeshSceneNode(mesh);
    if (model2)
    {
      model2->setMaterialTexture(0, driver->getTexture("media/Irrlicht/spheremap.jpg"));
      model2->setPosition(core::vector3df(180, 15, -60));
      model2->setScale(core::vector3df(2, 2, 2));
      model2->setMD2Animation(scene::EMAT_RUN);
      model2->setMaterialFlag(video::EMF_LIGHTING, false);
      model2->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);
      model2->setMaterialType(video::EMT_SPHERE_MAP);
      model2->addShadowVolumeSceneNode();
    }
  }

  scene::ISceneNodeAnimator *anim = 0;

  // create sky box
  driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
  skyboxNode = sm->addSkyBoxSceneNode(
      driver->getTexture("media/Irrlicht/irrlicht2_up.jpg"),
      driver->getTexture("media/Irrlicht/irrlicht2_dn.jpg"),
      driver->getTexture("media/Irrlicht/irrlicht2_lf.jpg"),
      driver->getTexture("media/Irrlicht/irrlicht2_rt.jpg"),
      driver->getTexture("media/Irrlicht/irrlicht2_ft.jpg"),
      driver->getTexture("media/Irrlicht/irrlicht2_bk.jpg"));
  driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);

  // create walk-between-portals animation
  core::vector3df waypoint[2];
  waypoint[0].set(-150, 40, 100);
  waypoint[1].set(350, 40, 100);

  if (model2)
  {
    anim = device->getSceneManager()->createFlyStraightAnimator(waypoint[0], waypoint[1], 2000, true);
    model2->addAnimator(anim);
    anim->drop();
  }

  // create animation for portals;
  core::array<video::ITexture *> textures;

  for (s32 g = 1; g < 8; ++g)
  {
    core::stringc tmp("media/Irrlicht/portal");
    tmp += g;
    tmp += ".bmp";
    video::ITexture *t = driver->getTexture(tmp);
    textures.push_back(t);
  }

  anim = sm->createTextureAnimator(textures, 100);

  // create portals
  scene::IBillboardSceneNode *bill = 0;

  for (int r = 0; r < 2; ++r)
  {
    bill = sm->addBillboardSceneNode(0, core::dimension2d<f32>(100, 100), waypoint[r] + core::vector3df(0, 20, 0));
    bill->setMaterialFlag(video::EMF_LIGHTING, false);
    bill->setMaterialTexture(0, driver->getTexture("media/Irrlicht/portal1.bmp"));
    bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
    bill->addAnimator(anim);
  }

  anim->drop();

  // create circle flying dynamic light with transparent billboard attached
  scene::ILightSceneNode *light = 0;

  light = sm->addLightSceneNode(0, core::vector3df(0, 0, 0), video::SColorf(1.0f, 1.0f, 1.f, 1.0f), 500.f);
  anim	= sm->createFlyCircleAnimator(core::vector3df(100, 150, 80), 80.0f, 0.0005f);

  light->addAnimator(anim);
  anim->drop();

  bill = device->getSceneManager()->addBillboardSceneNode(light, core::dimension2d<f32>(40, 40));
  bill->setMaterialFlag(video::EMF_LIGHTING, false);
  bill->setMaterialTexture(0, driver->getTexture("media/Irrlicht/particlewhite.bmp"));
  bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);

  // create meta triangle selector with all triangles selectors in it.
  metaSelector = sm->createMetaTriangleSelector();
  metaSelector->addTriangleSelector(mapSelector);

  // create camp fire
  campFire = sm->addParticleSystemSceneNode(false);
  campFire->setPosition(core::vector3df(100, 120, 600));
  campFire->setScale(core::vector3df(2, 2, 2));

  scene::IParticleEmitter *em =
    campFire->createBoxEmitter(core::aabbox3d<f32>(-7, 0, -7, 7, 1, 7),
      core::vector3df(0.0f, 0.06f, 0.0f), 80, 100,
      video::SColor(1, 255, 255, 255),
      video::SColor(1, 255, 255, 255), 800, 2000);
  em->setMinStartSize(core::dimension2d<f32>(20.0f, 10.0f));
  em->setMaxStartSize(core::dimension2d<f32>(20.0f, 10.0f));
  campFire->setEmitter(em);
  em->drop();

  scene::IParticleAffector *paf = campFire->createFadeOutParticleAffector();
  campFire->addAffector(paf);
  paf->drop();

  campFire->setMaterialFlag(video::EMF_LIGHTING, false);
  campFire->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
  campFire->setMaterialTexture(0, driver->getTexture("media/Irrlicht/fireball.bmp"));
  campFire->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
}

void CDemo::shoot()
{
  scene::ISceneManager	  *sm	  = device->getSceneManager();
  scene::ICameraSceneNode *camera = sm->getActiveCamera();

  if ((!camera) || (!mapSelector)) return;

  SParticleImpact imp;
  imp.when = 0;

  // get line of camera
  core::vector3df start = camera->getPosition();
  core::vector3df end	= (camera->getTarget() - start);
  end.normalize();
  start += end * 8.0f;
  end	 = start + (end * camera->getFarValue());

  core::triangle3df triangle;

  core::line3d<f32> line(start, end);

  // get intersection point with map
  scene::ISceneNode *hitNode;
  if (sm->getSceneCollisionManager()->getCollisionPoint(line, mapSelector, end, triangle, hitNode))
  {
    // collides with wall
    core::vector3df out = triangle.getNormal();
    out.setLength(0.03f);

    imp.when	  = 1;
    imp.outVector = out;
    imp.pos	  = end;
  }
  else
  {
    // doesnt collide with wall
    core::vector3df start = camera->getPosition();
    core::vector3df end	  = (camera->getTarget() - start);
    end.normalize();
    start += end * 8.0f;
    end	   = start + (end * camera->getFarValue());
  }

  // create fire ball
  scene::ISceneNode *node = 0;
  node = sm->addBillboardSceneNode(0, core::dimension2d<f32>(25, 25), start);

  node->setMaterialFlag(video::EMF_LIGHTING, false);
  node->setMaterialTexture(0, device->getVideoDriver()->getTexture("media/Irrlicht/fireball.bmp"));
  node->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);

  f32	    length = (f32)(end - start).getLength();
  const f32 speed  = 0.6f;
  u32	    time   = (u32)(length / speed);

  scene::ISceneNodeAnimator *anim = 0;

  // set flight line
  anim = sm->createFlyStraightAnimator(start, end, time);
  node->addAnimator(anim);
  anim->drop();

  anim = sm->createDeleteAnimator(time);
  node->addAnimator(anim);
  anim->drop();

  if (imp.when)
  {
    // create impact note
    imp.when = device->getTimer()->getTime() + (time - 100);
    Impacts.push_back(imp);
  }
}

void CDemo::createParticleImpacts()
{
  u32		       now = device->getTimer()->getTime();
  scene::ISceneManager *sm = device->getSceneManager();

  for (s32 i = 0; i < (s32)Impacts.size(); ++i)
    if (now > Impacts[i].when)
    {
      // create smoke particle system
      scene::IParticleSystemSceneNode *pas = 0;

      pas = sm->addParticleSystemSceneNode(false, 0, -1, Impacts[i].pos);

      pas->setParticleSize(core::dimension2d<f32>(10.0f, 10.0f));

      scene::IParticleEmitter *em =
        pas->createBoxEmitter(core::aabbox3d<f32>(-5, -5, -5, 5, 5, 5),
	  Impacts[i].outVector, 20, 40,
	  video::SColor(50, 255, 255, 255), 
	  video::SColor(50, 255, 255, 255), 1200, 1600, 20);
      pas->setEmitter(em);
      em->drop();

      scene::IParticleAffector *paf = campFire->createFadeOutParticleAffector();
      pas->addAffector(paf);
      paf->drop();

      pas->setMaterialFlag(video::EMF_LIGHTING, false);
      pas->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
      pas->setMaterialTexture(0, device->getVideoDriver()->getTexture("media/Irrlicht/smoke.bmp"));
      pas->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);

      scene::ISceneNodeAnimator *anim = sm->createDeleteAnimator(2000);
      pas->addAnimator(anim);
      anim->drop();

      // delete entry
      Impacts.erase(i);
      i--;
    }

}