/*
    Copyright (C) 2001 by Jorrit Tyberghein

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "cssysdef.h"
#include "isotest.h"
#include "cstool/csview.h"
#include "cstool/initapp.h"
#include "csutil/cmdhelp.h"
#include "csutil/cscolor.h"
#include "csutil/event.h"
#include "csutil/sysfunc.h"
#include "iengine/camera.h"
#include "iengine/campos.h"
#include "iengine/engine.h"
#include "iengine/light.h"
#include "iengine/material.h"
#include "iengine/mesh.h"
#include "iengine/movable.h"
#include "iengine/sector.h"
#include "iengine/texture.h"
#include "igraphic/imageio.h"
#include "imap/loader.h"
#include "imesh/object.h"
#include "imesh/sprite3d.h"
#include "iutil/csinput.h"
#include "iutil/event.h"
#include "iutil/eventq.h"
#include "iutil/objreg.h"
#include "iutil/vfs.h"
#include "iutil/virtclk.h"
#include "ivaria/reporter.h"
#include "ivaria/stdrep.h"
#include "ivideo/fontserv.h"
#include "ivideo/graph2d.h"
#include "ivideo/graph3d.h"
#include "ivideo/material.h"
#include "ivideo/texture.h"
#include "ivideo/txtmgr.h"
#include "imesh/genmesh.h"
#include "imesh/gmeshskel.h"

CS_IMPLEMENT_APPLICATION















//------------------------------------------------------------------------

// The global pointer to isotest
IsoTest *isotest;

IsoTest::IsoTest (iObjectRegistry* object_reg)
{
  IsoTest::object_reg = object_reg;

  current_view = 0;
  views[0].SetOrigOffset (csVector3 (-4, 4, -4)); // true isometric perspective.
  views[1].SetOrigOffset (csVector3 (-9, 9, -9)); // zoomed out.
  views[2].SetOrigOffset (csVector3 (4, 3, -4)); // diablo style perspective.
  views[3].SetOrigOffset (csVector3 (0, 4, -4)); // zelda style perspective.

  actor_is_walking = false;
}

IsoTest::~IsoTest ()
{
}

void IsoTest::SetupFrame ()
{
  // First get elapsed time from the virtual clock.
  csTicks elapsed_time = vc->GetElapsedTicks ();

  // Now rotate the camera according to keyboard state
  float speed = (elapsed_time / 1000.0) * (0.03 * 90);

  if (kbd->GetModifierState (CSKEY_SHIFT_LEFT)
    || kbd->GetModifierState (CSKEY_SHIFT_RIGHT))
  {
    if (kbd->GetKeyState (CSKEY_RIGHT))
      views[current_view].angle += speed*15.f;
    if (kbd->GetKeyState (CSKEY_LEFT))
      views[current_view].angle -= speed*15.f;
    if (kbd->GetKeyState (CSKEY_UP))
      views[current_view].distance -= 0.25f*speed;
    if (kbd->GetKeyState (CSKEY_DOWN))
      views[current_view].distance += 0.25f*speed;
    SetupIsoView(views[current_view]);
  }
  else
  {
    float facing = 0.f; // in degrees
    bool moved = false;
    if (kbd->GetKeyState (CSKEY_RIGHT))
    {
      moved = true;
      actor->GetMovable ()->MovePosition (csVector3 (speed, 0, 0));
      facing = 270.f;
    }
    if (kbd->GetKeyState (CSKEY_LEFT))
    {
      moved = true;
      actor->GetMovable ()->MovePosition (csVector3 (-speed, 0, 0));
      facing = 90.f;
    }
    if (kbd->GetKeyState (CSKEY_UP))
    {
      moved = true;
      actor->GetMovable ()->MovePosition (csVector3 (0, 0, speed));
      facing = 0.f;
    }
    if (kbd->GetKeyState (CSKEY_DOWN))
    {
      moved = true;
      actor->GetMovable ()->MovePosition (csVector3 (0, 0, -speed));
      facing = 180.f;
    }

    if(kbd->GetKeyState (CSKEY_DOWN) && kbd->GetKeyState (CSKEY_LEFT))
      facing = 135;
    if(kbd->GetKeyState (CSKEY_DOWN) && kbd->GetKeyState (CSKEY_RIGHT))
      facing = 225;
    if(kbd->GetKeyState (CSKEY_UP) && kbd->GetKeyState (CSKEY_LEFT))
      facing = 45;
    if(kbd->GetKeyState (CSKEY_UP) && kbd->GetKeyState (CSKEY_RIGHT))
      facing = 315;

    if(moved)
    {
      csYRotMatrix3 r(facing*PI/180.0);
      actor->GetMovable ()->SetTransform(r);
    }
    // update animation state
    csRef<iGeneralMeshState> spstate (
      SCF_QUERY_INTERFACE (actor->GetMeshObject (), iGeneralMeshState));
    csRef<iGenMeshSkeletonControlState> animcontrol (
      SCF_QUERY_INTERFACE (spstate->GetAnimationControl (),
      iGenMeshSkeletonControlState));
    if(actor_is_walking && !moved)
    {
      animcontrol->StopAll();
      animcontrol->Execute("idle");
    }
    if(!actor_is_walking && moved)
    {
      animcontrol->StopAll();
      animcontrol->Execute("walk");
    }
    actor_is_walking = moved;
  }

  // Make sure actor is constant distance above plane.
  csVector3 actor_pos = actor->GetMovable ()->GetPosition ();
  actor_pos.y += 10.0;    // Make sure we start beam high enough.
  csVector3 end_pos, isect;
  end_pos = actor_pos; end_pos.y -= 100.0;
  float r;
  plane->HitBeamObject (actor_pos, end_pos, isect, &r);
  actor_pos.y = isect.y + .8;

  actor->GetMovable ()->SetPosition (actor_pos);
  actor->GetMovable ()->UpdateMove ();

  // Move the light.
  actor_light->SetCenter (actor_pos+csVector3 (0, 2, -1));

  CameraIsoLookat(view->GetCamera(), views[current_view], actor_pos);
 
  // Tell 3D driver we're going to display 3D things.
  if (!g3d->BeginDraw (engine->GetBeginDrawFlags () | CSDRAW_3DGRAPHICS))
    return;

  // Tell the camera to render into the frame buffer.
  view->Draw ();

  if (!g3d->BeginDraw (CSDRAW_2DGRAPHICS))
    return;

  csVector2 lpos(0,0);
  view->GetCamera()->Perspective(
    view->GetCamera()->GetTransform ().Other2This(csVector3 (-4.7f, 1.0f, 5.5f)),
    lpos);
  // display a helpful little text.
  int txtw=0, txth=0;
  font->GetMaxSize(txtw, txth);
  if(txth == -1) txth = 20;
  int white = g3d->GetDriver2D ()->FindRGB (255, 255, 255);
  g3d->GetDriver2D ()->DrawBox((int)lpos.x-2,
    g3d->GetDriver2D ()->GetHeight()-(int)lpos.y-2,4,4,white);
  int ypos = g3d->GetDriver2D ()->GetHeight () - txth*4 - 1;
  g3d->GetDriver2D ()->Write (font, 1, ypos, white, -1,
    "Isometric demo keys (esc to exit):");
  ypos += txth;
  g3d->GetDriver2D ()->Write (font, 1, ypos, white, -1,
    "   arrow keys: move around");
  ypos += txth;
  g3d->GetDriver2D ()->Write (font, 1, ypos, white, -1,
    "   shift+arrow keys: rotate/zoom camera");
  ypos += txth;
  g3d->GetDriver2D ()->Write (font, 1, ypos, white, -1,
    "   tab key: cycle through camera presets");
}

void IsoTest::FinishFrame ()
{
  g3d->FinishDraw ();
  g3d->Print (0);
}

bool IsoTest::HandleEvent (iEvent& ev)
{
  if (ev.Type == csevBroadcast && csCommandEventHelper::GetCode(&ev) == cscmdProcess)
  {
    isotest->SetupFrame ();
    return true;
  }
  else if (ev.Type == csevBroadcast && csCommandEventHelper::GetCode(&ev) == cscmdFinalProcess)
  {
    isotest->FinishFrame ();
    return true;
  }
  else if ((ev.Type == csevKeyboard) &&
    (csKeyEventHelper::GetEventType (&ev) == csKeyEventTypeDown))
  {
    utf32_char c = csKeyEventHelper::GetCookedCode (&ev);
    if (c == CSKEY_ESC)
    {
      csRef<iEventQueue> q (CS_QUERY_REGISTRY (object_reg, iEventQueue));
      if (q)
    q->GetEventOutlet()->Broadcast (cscmdQuit);
      return true;
    }
    else if (c == CSKEY_TAB)
    {
      current_view++;
      if (current_view >= 4) current_view = 0;
    }
  }

  return false;
}

void IsoTest::CameraIsoLookat(csRef<iCamera> cam, const IsoView& isoview,
    const csVector3& lookat)
{
  // Let the camera look at the actor.
  // so the camera is set to look at 'actor_pos'
  //int isofactor = 50; // 98.3% isometric (=GetFovAngle()/180.0)
  //int isofactor = 100; // 99.2% isometric (=GetFovAngle()/180.0)
  int isofactor = 200; // 99.6% isometric (=GetFovAngle()/180.0)

  // set center and lookat
  csOrthoTransform& cam_trans = cam->GetTransform ();
  cam_trans.SetOrigin (lookat + float(isofactor)*isoview.camera_offset);
  cam_trans.LookAt (lookat-cam_trans.GetOrigin (), csVector3 (0, 1, 0));
  // set fov more isometric, could be done in initialisation once.
  cam->SetFOV (g3d->GetHeight()*isofactor, g3d->GetWidth());

  // due to moving the camera so far away, depth buffer accuracy is
  // impaired, repair that by using smaller coordinate system
  csOrthoTransform repair_trans = cam->GetTransform();
  repair_trans.SetT2O (repair_trans.GetT2O()/repair_trans.GetOrigin().Norm());
  cam->SetTransform (repair_trans);
}

void IsoTest::SetupIsoView(IsoView& isoview)
{
  // clamp
  if(isoview.angle < 0.f) isoview.angle += 360.f;
  if(isoview.angle > 360.f) isoview.angle -= 360.f;
  if(isoview.distance < 0.05f) isoview.distance = 0.05f;
  if(views[current_view].distance > 10.f) isoview.distance = 10.f;
  // setup
  csYRotMatrix3 r(isoview.angle * PI / 180.0);
  isoview.camera_offset = (r*isoview.original_offset)*isoview.distance;
}

bool IsoTest::IsoTestEventHandler (iEvent& ev)
{
  return isotest->HandleEvent (ev);
}

bool IsoTest::LoadMap ()
{
  // First disable the lighting cache. Our map uses stencil
  // lighting.
  engine->SetLightingCacheMode (0);

  // Set VFS current directory to the level we want to load.
  csRef<iVFS> VFS (CS_QUERY_REGISTRY (object_reg, iVFS));
  VFS->ChDir ("/lev/isomap");
  // Load the level file which is called 'world'.
  if (!loader->LoadMapFile ("world"))
  {
    csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
        "crystalspace.application.isotest",
        "Couldn't load level!");
    return false;
  }

  // Find the starting position in this level.
  csVector3 pos (0, 0, 0);
  if (engine->GetCameraPositions ()->GetCount () > 0)
  {
    // There is a valid starting position defined in the level file.
    iCameraPosition* campos = engine->GetCameraPositions ()->Get (0);
    room = engine->GetSectors ()->FindByName (campos->GetSector ());
    pos = campos->GetPosition ();
  }
  else
  {
    // We didn't find a valid starting position. So we default
    // to going to room called 'room' at position (0,0,0).
    room = engine->GetSectors ()->FindByName ("room");
  }
  if (!room)
  {
    csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
        "crystalspace.application.isotest",
          "Can't find a valid starting position!");
    return false;
  }

  view->GetCamera ()->SetSector (room);
  view->GetCamera ()->GetTransform ().SetOrigin (pos);

  iLightList* ll = room->GetLights ();
  actor_light = engine->CreateLight (0, csVector3 (-3, 5, 0), 5,
    csColor (1, 1, 1));
  ll->Add (actor_light);

  csRef<iLight> statuelight = engine->CreateLight ("statuelight",
    csVector3 (-4.7f, 1.0f, 5.5f), 4, csColor(1.2f,0.2f,0.2f));
  statuelight->CreateNovaHalo (1278, 15, 0.3f);
  ll->Add (statuelight);

  plane = engine->FindMeshObject ("Plane");

  return true;
}

bool IsoTest::CreateActor ()
{
  // Load a texture for our sprite.
  iTextureManager* txtmgr = g3d->GetTextureManager ();
  iTextureWrapper* txt = loader->LoadTexture ("vedette_fashion",
    "/lib/std/ugly_woman.jpg", CS_TEXTURE_3D, txtmgr, false, false);
  if (txt == 0)
  {
    csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
        "crystalspace.application.isotest",
        "Error loading texture!");
    return false;
  }
  csRef<iStringSet> strings = CS_QUERY_REGISTRY_TAG_INTERFACE (
    object_reg, "crystalspace.shared.stringset", iStringSet);
  csRef<iShaderManager> shader_mgr = CS_QUERY_REGISTRY (object_reg,
      iShaderManager);
  if (shader_mgr == 0)
  {
    csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
        "crystalspace.application.isotest",
        "Couldn't find shader manager! This application requires new renderer!"
    );
    return false;
  }
  iShader* ambient_shader = shader_mgr->GetShader ("ambient");
  iShader* light_shader = shader_mgr->GetShader ("light");
  if (ambient_shader == 0 || light_shader == 0)
  {
    csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
        "crystalspace.application.isotest",
        "Couldn't find shaders!");
    return false;
  }
  csRef<iMaterial> fash_material = engine->CreateBaseMaterial (txt);
  fash_material->SetShader (strings->Request ("ambient"), ambient_shader);
  fash_material->SetShader (strings->Request ("diffuse"), light_shader);
  engine->GetMaterialList ()->NewMaterial (fash_material, "vedette_fashion");

  // Load a sprite template from disk.
  csRef<iMeshFactoryWrapper> imeshfact (
    loader->LoadMeshObjectFactory ("/lev/isomap/vedette.spr"));
  if (imeshfact == 0)
  {
    csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
        "crystalspace.application.isotest",
        "Error loading mesh object factory!");
    return false;
  }
  csMatrix3 m; m.Identity (); m *= 1.10f; // scaling factor
  imeshfact->HardTransform (csReversibleTransform (m, csVector3 (0)));

  // Create the sprite and add it to the engine.
  actor = engine->CreateMeshWrapper (
    imeshfact, "MySprite", room, csVector3 (-3, 2, 3));
  actor->GetMovable ()->UpdateMove ();
  csRef<iGeneralMeshState> spstate (
    SCF_QUERY_INTERFACE (actor->GetMeshObject (), iGeneralMeshState));
  csRef<iGenMeshSkeletonControlState> animcontrol (
    SCF_QUERY_INTERFACE (spstate->GetAnimationControl (),
    iGenMeshSkeletonControlState));
  animcontrol->StopAll();
  animcontrol->Execute("idle");

  // The following two calls are not needed since CS_ZBUF_USE and
  // Object render priority are the default but they show how you
  // can do this.
  actor->SetZBufMode (CS_ZBUF_USE);
  actor->SetRenderPriority (engine->GetObjectRenderPriority ());
  return true;
}

bool IsoTest::Initialize ()
{
  if (!csInitializer::RequestPlugins (object_reg,
      CS_REQUEST_VFS,
    CS_REQUEST_OPENGL3D,
    CS_REQUEST_ENGINE,
    CS_REQUEST_PLUGIN("crystalspace.font.server.multiplexer", iFontServer),
    "crystalspace.font.server.freetype2", "iFontServer.1",
      scfInterfaceTraits<iFontServer>::GetID(),
      scfInterfaceTraits<iFontServer>::GetVersion(),
    "crystalspace.font.server.default", "iFontServer.2",
      scfInterfaceTraits<iFontServer>::GetID(),
      scfInterfaceTraits<iFontServer>::GetVersion(),
    CS_REQUEST_FONTSERVER,
    CS_REQUEST_IMAGELOADER,
    CS_REQUEST_LEVELLOADER,
    CS_REQUEST_REPORTER,
    CS_REQUEST_REPORTERLISTENER,
    CS_REQUEST_END))
  {
    csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
        "crystalspace.application.isotest",
    "Can't initialize plugins!");
    return false;
  }

  if (!csInitializer::SetupEventHandler (object_reg, IsoTestEventHandler))
  {
    csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
        "crystalspace.application.isotest",
    "Can't initialize event handler!");
    return false;
  }

  // Check for commandline help.
  if (csCommandLineHelper::CheckHelp (object_reg))
  {
    csCommandLineHelper::Help (object_reg);
    return false;
  }

  // The virtual clock.
  vc = CS_QUERY_REGISTRY (object_reg, iVirtualClock);
  if (!vc)
  {
    csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
        "crystalspace.application.isotest",
    "Can't find the virtual clock!");
    return false;
  }

  // Find the pointer to engine plugin
  engine = CS_QUERY_REGISTRY (object_reg, iEngine);
  if (!engine)
  {
    csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
        "crystalspace.application.isotest",
    "No iEngine plugin!");
    return false;
  }

  loader = CS_QUERY_REGISTRY (object_reg, iLoader);
  if (!loader)
  {
    csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
        "crystalspace.application.isotest",
        "No iLoader plugin!");
    return false;
  }

  g3d = CS_QUERY_REGISTRY (object_reg, iGraphics3D);
  if (!g3d)
  {
    csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
        "crystalspace.application.isotest",
        "No iGraphics3D plugin!");
    return false;
  }

  kbd = CS_QUERY_REGISTRY (object_reg, iKeyboardDriver);
  if (!kbd)
  {
    csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
        "crystalspace.application.isotest",
        "No iKeyboardDriver plugin!");
    return false;
  }

  // Open the main system. This will open all the previously loaded plug-ins.
  if (!csInitializer::OpenApplication (object_reg))
  {
    csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
        "crystalspace.application.isotest",
        "Error opening system!");
    return false;
  }

  view = csPtr<iView> (new csView (engine, g3d));
  iGraphics2D* g2d = g3d->GetDriver2D ();
  view->SetRectangle (0, 0, g2d->GetWidth (), g2d->GetHeight ());

  font = g3d->GetDriver2D ()->GetFontServer()->LoadFont
    ("/fonts/ttf/Vera.ttf", 10);
  if(!font) // fallback
    font = g3d->GetDriver2D ()->GetFontServer()->LoadFont(CSFONT_LARGE);

  if (!LoadMap ()) return false;
  if (!CreateActor ()) return false;
  engine->Prepare ();

  return true;
}

void IsoTest::Start ()
{
  csDefaultRunLoop (object_reg);
}

/*---------------------------------------------------------------------*
 * Main function
 *---------------------------------------------------------------------*/
int main (int argc, char* argv[])
{
  iObjectRegistry* object_reg = csInitializer::CreateEnvironment (argc, argv);
  if (!object_reg) return -1;
  isotest = new IsoTest (object_reg);

  if (isotest->Initialize ())
    isotest->Start ();

  delete isotest;

  csInitializer::DestroyApplication (object_reg);
  return 0;
}

"""
    isotest.py
   
    The intent of this script is to remake isotest.cpp line-for-line in Python.
    The purpose is to improve and expand the use of Python in Crystal Space
    projects. My hope is that others will do the same with some of the other
    tutorial scripts, eventually forming a PyCrystal (CrystalPy?) community.

    Scott Holliday
"""








############################## Module Imports
# import Python modules
import types, string, re, sys, math, traceback

# import CrystalSpace module
try:
  from cspace import *
except:
  print "WARNING: Failed to import module cspace"
  sys.exit(1)
 
############################## rewriting isotest.h
class IsoView:
  def __init__(self,v):
    # initialize with original offset of camera from the spot you look at.
    self.camera_offset = v      # offset to apply to the camera.
    self.original_offset = v    # original camera offset.
    self.angle = 0.0            # angle of rotation, in degrees, 0.0 is original.
    self.distance = 1.0         # distance from the lookat spot. 1.0 is original distance.    

############################## Event Handler is Global Instead
def IsoTestEventHandler(ev):
  try:
    if ev.Type == csevBroadcast and csCommandEventHelper.GetCode(ev) == cscmdProcess:
      isotest.SetupFrame()
      return 1
    elif ev.Type == csevBroadcast and csCommandEventHelper.GetCode(ev) == cscmdFinalProcess:
      try:
        isotest.FinishFrame()
      except:
        msg=traceback.format_exc()
        csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,"crystalspace.application.isotest",    msg) 
      return 1
    elif ev.Type == csevKeyboard and csKeyEventHelper.GetEventType(ev) == csKeyEventTypeDown:
      c = csKeyEventHelper.GetCookedCode (ev)
      if c == CSKEY_ESC:
        q = CS_QUERY_REGISTRY (object_reg, iEventQueue)
        if q:   
          csReport (object_reg, CS_REPORTER_SEVERITY_ERROR, "crystalspace.application.isotest",    "Done! "+str(framecount))
          q.GetEventOutlet().Broadcast(cscmdQuit)
  except:
    traceback.print_exc()   # prints the usual error messages
    sys.exit(1)             # stop dead
  return 1



############################## Rewriting isotest.cpp

# The global pointer to isotest
isotest=IsoTest() 
   
class IsoTest:



########## Variables are instead initialized in the Initialize Method below












  def SetupFrame (self):
    global framecount
    # First get elapsed time from the virtual clock.
    elapsed_time = self.vc.GetElapsedTicks ()
    # Now rotate the camera according to keyboard state
    speed = elapsed_time / 1000.0 * 0.03 * 90
    ################# WARNING: SHIFT CSKEYS ARE UNDEFINED!
    # if self.kbd.GetModifierState(CSKEY_SHIFT):   
    if 1:
      angle=self.views[self.current_view].angle
      distance=self.views[self.current_view].distance
      if self.kbd.GetKeyState (CSKEY_DEL):
        self.views[self.current_view].angle -= speed*15.0
      if self.kbd.GetKeyState (CSKEY_END):
        self.views[self.current_view].angle += speed*15.0
      if self.kbd.GetKeyState (CSKEY_PGUP):
        self.views[self.current_view].distance -= speed/2
      if self.kbd.GetKeyState (CSKEY_PGDN):
        self.views[self.current_view].distance += speed/2
      if angle!=self.views[self.current_view].angle or distance!=self.views[self.current_view].distance:
        self.SetupIsoView(self.views[self.current_view])      
      moved = 0
      if self.kbd.GetKeyState (CSKEY_RIGHT):
        moved = 1
        self.actor.GetMovable ().MovePosition (csVector3 (speed, 0, 0))
        facing = 270.0     
      if self.kbd.GetKeyState (CSKEY_LEFT):
        moved = 1
        self.actor.GetMovable ().MovePosition (csVector3 (-speed, 0, 0))
        facing = 90.0
      if self.kbd.GetKeyState (CSKEY_UP):
        moved = 1
        self.actor.GetMovable ().MovePosition (csVector3 (0, 0, speed))
        facing = 0.0
      if self.kbd.GetKeyState (CSKEY_DOWN):
        moved = 1
        self.actor.GetMovable ().MovePosition (csVector3 (0, 0, -speed))
        facing = 180.0
      if self.kbd.GetKeyState (CSKEY_DOWN) and self.kbd.GetKeyState (CSKEY_LEFT):
        facing = 135.0
      if self.kbd.GetKeyState (CSKEY_DOWN) and self.kbd.GetKeyState (CSKEY_RIGHT):
        facing = 225.0
      if self.kbd.GetKeyState (CSKEY_UP) and self.kbd.GetKeyState (CSKEY_LEFT):
        facing = 45.0
      if self.kbd.GetKeyState (CSKEY_UP) and self.kbd.GetKeyState (CSKEY_RIGHT):
        facing = 315.0
      if self.kbd.GetKeyState (CSKEY_TAB):
        self.current_view=(self.current_view+1)%4
      if moved:
        r = csYRotMatrix3(facing*math.pi/180.0)
        self.actor.GetMovable ().SetTransform(r)           
      # update animation state
      self.spstate = SCF_QUERY_INTERFACE (self.actor.GetMeshObject (), iGeneralMeshState)
      ################# WARNING: iGenMeshSkeletonControlState is not defined!
      #self.animcontrol (SCF_QUERY_INTERFACE (spstate.GetAnimationControl (),iGenMeshSkeletonControlState))
      #  if self.actor_is_walking and moved==0:
      #    self.animcontrol->StopAll()
      #    self.animcontrol->Execute("idle")
      #  if self.actor_is_walking==0 and moved:
      #    self.animcontrol->StopAll()
      #    self.animcontrol->Execute("walk")
      self.actor_is_walking = moved
    # Make sure actor is constant distance above plane.
    self.actor_pos = self.actor.GetMovable ().GetPosition ()
    startpos=self.actor_pos+csVector3(0,10,0)  # Make sure we start beam high enough.
    endpos=self.actor_pos-csVector3(0,100,0)
    hbo=self.plane.HitBeamObject(startpos,endpos) ## HitBeamObject returns csHitBeamResult
    self.actor_pos.y = hbo.isect.y + 0.85
    self.actor.GetMovable ().SetPosition (self.actor_pos)
    self.actor.GetMovable ().UpdateMove ()
    # Move the light.
    self.actor_light.SetCenter (self.actor_pos+csVector3 (0, 2, -1))
    self.CameraIsoLookat(self.view.GetCamera(), self.views[self.current_view], self.actor_pos)
    # Tell 3D driver we're going to display 3D things.
    if not self.g3d.BeginDraw(self.engine.GetBeginDrawFlags() | CSDRAW_3DGRAPHICS):
      return
    # Tell the camera to render into the frame buffer.
    self.view.Draw ()
    if not self.g3d.BeginDraw (CSDRAW_2DGRAPHICS):
      return
    lpos = self.view.GetCamera().Perspective(self.view.GetCamera(). GetTransform().Other2This(csVector3(-4.7, 1.0, 5.5)))
    # display a helpful little text.
    txtw,txth = self.font.GetMaxSize()   
    if txth == -1: txth = 20
    white = self.g3d.GetDriver2D ().FindRGB (255, 255, 255)
    self.g3d.GetDriver2D().DrawBox(int(lpos.x-2),int(self.g3d.GetDriver2D(). GetHeight()-lpos.y-2),4,4,white)
    ypos = self.g3d.GetDriver2D ().GetHeight () - txth*8 - 1
    self.g3d.GetDriver2D ().Write (self.font, 1, ypos, white, -1,str(hbo.hit)+" : "+str(self.current_view)+" : "+str(speed))
    ypos += txth
    self.g3d.GetDriver2D ().Write (self.font, 1, ypos, white, -1,str(int(hbo.isect.x))+" : "+str(int(self.actor_pos.x)))
    ypos += txth
    self.g3d.GetDriver2D ().Write (self.font, 1, ypos, white, -1,str(int(hbo.isect.y))+" : "+str(int(self.actor_pos.y)))
    ypos += txth
    self.g3d.GetDriver2D ().Write (self.font, 1, ypos, white, -1,str(int(hbo.isect.z))+" : "+str(int(self.actor_pos.z)))
    ypos += txth
    self.g3d.GetDriver2D ().Write (self.font, 1, ypos, white, -1,"Isometric demo keys (esc to exit):")
    ypos += txth
    self.g3d.GetDriver2D ().Write (self.font, 1, ypos, white, -1,"   arrow keys: move around")
    ypos += txth
    self.g3d.GetDriver2D ().Write (self.font, 1, ypos, white, -1,"   DEL/END and PGUP/PGDN: rotate/zoom camera")
    ypos += txth
    self.g3d.GetDriver2D ().Write (self.font, 1, ypos, white, -1,"   tab key: cycle through camera presets")















  def FinishFrame (self):
    global framecount
    self.g3d.FinishDraw ()
    self.g3d.Print(None)


########## This method is handled above globally


































  def CameraIsoLookat(self, cam, isoview, lookat):
    # Let the camera look at the actor. So the camera is set to look at 'actor_pos'
    # isofactor = 50 # 98.3% isometric (=GetFovAngle()/180.0)
    # isofactor = 100 # 99.2% isometric (=GetFovAngle()/180.0)
    isofactor = 200 # 99.6% isometric (=GetFovAngle()/180.0)
    # set center and lookat
    cam_trans = cam.GetTransform ();
    cam_trans.SetOrigin (lookat + isofactor*isoview.camera_offset)
    cam_trans.LookAt (lookat - cam_trans.GetOrigin (), csVector3 (0, 1, 0))
    # set fov more isometric, could be done in initialisation once.
    cam.SetFOV (self.g3d.GetHeight()*isofactor, self.g3d.GetWidth())
    # due to moving the camera so far away, depth buffer accuracy is impaired, repair that by using smaller coordinate system
    repair_trans = cam.GetTransform()
    repair_trans.SetT2O(repair_trans.GetT2O()/repair_trans.GetOrigin().Norm())
    cam.SetTransform (repair_trans)






  def SetupIsoView(self,isoview):
    # clamp
    if isoview.angle < 0: isoview.angle += 360.0
    if isoview.angle > 360: isoview.angle -= 360.0
    if isoview.distance < 0.05: isoview.distance = 0.05
    if self.views[self.current_view].distance > 5.0: isoview.distance = 5.0
    # setup
    r= csYRotMatrix3(isoview.angle * math.pi / 180.0)
    isoview.camera_offset = r * isoview.original_offset * isoview.distance



######## This method is handled above globally




  def LoadMap (self):
    # First disable the lighting cache. Our map uses stencil lighting.
    self.engine.SetLightingCacheMode(0)
    # Set VFS current directory to the level we want to load.
    VFS=CS_QUERY_REGISTRY (object_reg, iVFS)
    VFS.ChDir ("/lev/isomap")
    # Load the level file which is called 'world'.
    if not self.loader.LoadMapFile ("world"):
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR, "crystalspace.application.isotest",    "Couldn't load level!")
      return
    # Find the starting position in this level.
    pos=(0, 0, 0)
    if (self.engine.GetCameraPositions().GetCount() > 0):
      # There is a valid starting position defined in the level file.
      self.campos = self.engine.GetCameraPositions().Get (0)
      self.room = self.engine.GetSectors().FindByName(self.campos.GetSector ())
      self.pos = self.campos.GetPosition ()
    else:
      # We didn't find a valid starting position? So we default to room called 'room' at position (0,0,0).
      self.room = self.engine.GetSectors().FindByName("room")    
    if not self.room:
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,    "crystalspace.application.isotest",    "Can't find a valid starting position!")
      return
    self.view.GetCamera().SetSector (self.room)
    self.view.GetCamera().GetTransform ().SetOrigin (self.pos)
    self.ll=self.room.GetLights ()
    self.actor_light=self.engine.CreateLight("actor_light", csVector3(-3,5,0), 5, csColor(1,1,1))
    self.ll.Add (self.actor_light)
    statuelight = self.engine.CreateLight ("statuelight", csVector3 (-4.7, 1.0, 5.5), 4, csColor(2.2,0.5,0.5))
    statuelight.CreateNovaHalo (1278, 15, 0.3)
    self.ll.Add (statuelight)
    self.plane = self.engine.FindMeshObject ("Plane")
    return 1





















  def CreateActor(self):
    # Load a texture for our sprite.
    self.txtmgr = self.g3d.GetTextureManager ()
    self.txt = self.loader.LoadTexture ("vedette_fashion","/lib/std/ugly_woman.jpg", CS_TEXTURE_3D, self.txtmgr, 0, 0)
    if self.txt==0:
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,"crystalspace.application.isotest","Error loading texture!")
      return
    self.strings = CS_QUERY_REGISTRY_TAG_INTERFACE (object_reg, "crystalspace.shared.stringset", iStringSet)
    self.shader_mgr = CS_QUERY_REGISTRY (object_reg,iShaderManager)
    if self.shader_mgr == 0:
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,"crystalspace.application.isotest","Couldn't find shader manager! This application requires new renderer!")
      return
    self.ambient_shader = self.shader_mgr.GetShader ("ambient")
    self.light_shader = self.shader_mgr.GetShader ("light")
    if self.ambient_shader == 0 or self.light_shader == 0:
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,"crystalspace.application.isotest","Couldn't find shaders!")
      return false
    self.fash_material = self.engine.CreateBaseMaterial (self.txt)
    self.fash_material.SetShader (self.strings.Request ("ambient"), self.ambient_shader)
    self.fash_material.SetShader (self.strings.Request ("diffuse"), self.light_shader)
    self.engine.GetMaterialList ().NewMaterial (self.fash_material, "vedette_fashion")
    # Load a sprite template from disk.
    self.imeshfact=self.loader.LoadMeshObjectFactory ("/lev/isomap/vedette.spr")
    if self.imeshfact == 0:
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,"crystalspace.application.isotest","Error loading mesh object factory!")
      return
    self.m=csMatrix3(0,0,0,0)
    self.m.Identity()
    self.m *= 1.10 # scaling factor
    self.imeshfact.HardTransform (csReversibleTransform (self.m, csVector3 (0)))
    # Create the sprite and add it to the engine.
    self.actor = self.engine.CreateMeshWrapper (self.imeshfact, "MySprite", self.room, csVector3 (-3, 2, 3))
    self.actor.GetMovable ().UpdateMove ()
    self.spstate = SCF_QUERY_INTERFACE (self.actor.GetMeshObject (), iGeneralMeshState)
    ############################### WARNING! iGenMeshSkeletonControlState is undefined!
    try:
      self.animcontrol = SCF_QUERY_INTERFACE (self.spstate.GetAnimationControl (), iGenMeshSkeletonControlState)
      self.animcontrol.StopAll()
      self.animcontrol.Execute("idle")
    except:
      msg=traceback.format_exc()
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,    "crystalspace.application.isotest",    msg)  
    # The following two calls are not needed since CS_ZBUF_USE and Object render priority are the default but they show how you can do this.
    self.actor.SetZBufMode (CS_ZBUF_USE)
    self.actor.SetRenderPriority (self.engine.GetObjectRenderPriority ())
    return 1







  def Initialize(self):
    self.r=0
    self.views=[0,0,0,0]
    self.object_reg = object_reg;
    self.current_view=0
    self.views[0]=IsoView(csVector3 (-4, 4, -4)) # true isometric perspective.
    self.views[1]=IsoView(csVector3 (-9, 9, -9)) # zoomed out.
    self.views[2]=IsoView(csVector3 (4, 3, -4))  # diablo style perspective.
    self.views[3]=IsoView(csVector3 (0, 4, -4))  # zelda style perspective.
    self.actor_is_walking = 0;
    if not csInitializer.RequestPlugins(object_reg,[CS_REQUEST_VFS, CS_REQUEST_OPENGL3D, CS_REQUEST_ENGINE,     CS_REQUEST_FONTSERVER, CS_REQUEST_IMAGELOADER, CS_REQUEST_LEVELLOADER,
CS_REQUEST_REPORTER, CS_REQUEST_REPORTERLISTENER]):
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,    "crystalspace.application.isotest",    "Can't initialize plugins!")
      return
    if not csInitializer.SetupEventHandler(object_reg, IsoTestEventHandler):
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,    "crystalspace.application.isotest",    "Can't initialize event handler!")
      return
    # Check for commandline help.
    if csCommandLineHelper.CheckHelp(object_reg):
      csCommandLineHelper.Help(object_reg)
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,    "crystalspace.application.isotest",    "Command line help!")
      return     
    # The virtual clock.
    self.vc = CS_QUERY_REGISTRY (object_reg, iVirtualClock)
    if not self.vc:
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,    "crystalspace.application.isotest", "Can't find the virtual clock!")
      return
    # Find the pointer to engine plugin
    self.engine = CS_QUERY_REGISTRY (object_reg, iEngine)
    if not self.engine:
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,    "crystalspace.application.isotest",    "No iEngine plugin!")
      return
    self.loader = CS_QUERY_REGISTRY (object_reg, iLoader)
    if not self.loader:
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,    "crystalspace.application.isotest",    "No iLoader plugin!")
      return
    self.g3d = CS_QUERY_REGISTRY (object_reg, iGraphics3D)
    if not self.g3d:
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,    "crystalspace.application.isotest",    "No iGraphics3D plugin!")
      return
    self.kbd = CS_QUERY_REGISTRY (object_reg, iKeyboardDriver)
    if not self.kbd:
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,    "crystalspace.application.isotest",    "No iKeyboardDriver plugin!")
      return
    # Open the main system. This will open all the previously loaded plug-ins.
    if not csInitializer.OpenApplication (object_reg):
      csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,    "crystalspace.application.isotest",    "Error opening system!")
      return
    self.view = csView (self.engine, self.g3d)
    self.g2d = self.g3d.GetDriver2D ()
    self.view.SetRectangle (0, 0, self.g2d.GetWidth (), self.g2d.GetHeight ())
    self.font = self.g3d.GetDriver2D().GetFontServer().LoadFont("/fonts/ttf/Vera.ttf", 10)
    if not self.font:  # fallback
      self.font = self.g3d.GetDriver2D().GetFontServer().LoadFont(CSFONT_LARGE)
    if not self.LoadMap(): return
    if not self.CreateActor(): return
    self.engine.Prepare()
    return 1 





























  def Start(self):
    csDefaultRunLoop (object_reg)


   
############################## Main
framecount=0
object_reg = csInitializer.CreateEnvironment(sys.argv)
if object_reg is None: sys.exit()

if isotest.Initialize():
  isotest.Start ()
       
isotest=None
csInitializer.DestroyApplication (object_reg)
object_reg=None