@c -*-texinfo-*-
@node Tutorial Simple Map, Tutorial Mazing, Tutorial Simple 2, Tutorials
@subsection Simple Tutorial 3: Map Loading, Collision Detection
@cindex Tutorial, Simple
@cindex Simple Tutorial

This tutorial continues on tutorial one. Here we will learn how to load a map
from a file instead of creating the geometry programmatically. You can find a
fully-commented version of this tutorial's source code in the Crystal Space
source directory at @file{CS/apps/tutorial/simpmap}.

This tutorial features (in addition to what is explained in simple1 tutorial):
@itemize @bullet
@item
Creating geometry by loading a map file using @code{iLoader::LoadMap()}.
@item
Setting up collision detection for the loaded map using
@code{csColliderHelper::InitializeCollisionWrappers()}.
@item
Doing movement and collision detection using the @code{csColliderActor} class.
@item
Finding a starting position in the map file using
@code{iEngine::GetCameraPositions()}.
@end itemize

@menu
* Simple Base Code:: Minimal Base Code
* Simple Loading The Map:: Loading The Map
* Simple Locating the Camera:: Locating the Camera
@end menu

@node Simple Base Code, Simple Loading The Map, Tutorial Simple Map, Tutorial Simple Map
@subsubsection Minimal Base Code

To start, we first take the code of tutorial one
and remove the code that creates geometry and initializes
the default camera position. In addition we add a new @code{LoadMap()}
function. This results in the following for @file{simpmap.h}:

@example
#ifndef __SIMPMAP_H__
#define __SIMPMAP_H__

#include <crystalspace.h>

class Simple
  : public csApplicationFramework, public csBaseEventHandler
@{
private:
  csRef<iEngine> engine;
  csRef<iLoader> loader;
  csRef<iGraphics3D> g3d;
  csRef<iKeyboardDriver> kbd;
  csRef<iVirtualClock> vc;
  csRef<iCollideSystem> cdsys;
  csRef<iView> view;
  iSector* room;
  csColliderActor collider_actor;
 
  bool OnKeyboard (iEvent&);
  void ProcessFrame ();
  void FinishFrame ();
  bool LoadMap ();

public:
  Simple ();
  ~Simple ();

  void OnExit ();
  bool OnInitialize (int argc, char* argv[]);
  bool Application ();

  CS_EVENTHANDLER_NAMES("application.simpmap")
  CS_EVENTHANDLER_NIL_CONSTRAINTS
@};

#endif // __SIMPMAP_H__
@end example

Similarly, for @file{simpmap.cpp}:

@example
#include "simpmap.h"

CS_IMPLEMENT_APPLICATION

Simple::Simple ()
@{
  SetApplicationName ("CrystalSpace.SimpleMap");
@}

Simple::~Simple ()
@{
@}

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

  csVector3 obj_move (0);
  csVector3 obj_rotate (0);

  if (kbd->GetKeyState (CSKEY_SHIFT))
  @{
    // If the user is holding down shift, the arrow keys will cause
    // the camera to strafe up, down, left or right from it's
    // current position.
    if (kbd->GetKeyState (CSKEY_RIGHT))
      obj_move = CS_VEC_RIGHT * 3.0f;
    if (kbd->GetKeyState (CSKEY_LEFT))
      obj_move = CS_VEC_LEFT * 3.0f;
    if (kbd->GetKeyState (CSKEY_UP))
      obj_move = CS_VEC_UP * 3.0f;
    if (kbd->GetKeyState (CSKEY_DOWN))
      obj_move = CS_VEC_DOWN * 3.0f;
  @}
  else
  @{
    // left and right cause the camera to rotate on the global Y
    // axis; page up and page down cause the camera to rotate on the
    // _camera's_ X axis (more on this in a second) and up and down
    // arrows cause the camera to go forwards and backwards.
    if (kbd->GetKeyState (CSKEY_RIGHT))
      obj_rotate.Set (0, 1, 0);
    if (kbd->GetKeyState (CSKEY_LEFT))
      obj_rotate.Set (0, -1, 0);
    if (kbd->GetKeyState (CSKEY_PGUP))
      obj_rotate.Set (1, 0, 0);
    if (kbd->GetKeyState (CSKEY_PGDN))
      obj_rotate.Set (-1, 0, 0);
    if (kbd->GetKeyState (CSKEY_UP))
      obj_move = CS_VEC_FORWARD * 3.0f;
    if (kbd->GetKeyState (CSKEY_DOWN))
      obj_move = CS_VEC_BACKWARD * 3.0f;
  @}

  collider_actor.Move (float (elapsed_time) / 1000.0f, 1.0f,
    	obj_move, obj_rotate);

  // 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 ();
@}

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

bool Simple::OnKeyboard(iEvent& ev)
@{
  csKeyEventType eventtype = csKeyEventHelper::GetEventType(&ev);
  if (eventtype == csKeyEventTypeDown)
  @{
    utf32_char code = csKeyEventHelper::GetCookedCode(&ev);
    if (code == CSKEY_ESC)
    @{
      csRef<iEventQueue> q = 
        csQueryRegistry<iEventQueue> (GetObjectRegistry());
      if (q.IsValid()) q->GetEventOutlet()->Broadcast(cscmdQuit);
    @}
  @}
  return false;
@}

bool Simple::OnInitialize(int argc, char* argv[])
@{
  if (!csInitializer::RequestPlugins(GetObjectRegistry(),
    CS_REQUEST_VFS,
    CS_REQUEST_OPENGL3D,
    CS_REQUEST_ENGINE,
    CS_REQUEST_FONTSERVER,
    CS_REQUEST_IMAGELOADER,
    CS_REQUEST_LEVELLOADER,
    CS_REQUEST_REPORTER,
    CS_REQUEST_REPORTERLISTENER,
    CS_REQUEST_PLUGIN("crystalspace.collisiondetection.opcode",
		iCollideSystem),
    CS_REQUEST_END))
    return ReportError("Failed to initialize plugins!");

  csBaseEventHandler::Initialize(GetObjectRegistry());
  if (!RegisterQueue(GetObjectRegistry(), csevAllEvents(GetObjectRegistry())))
    return ReportError("Failed to set up event handler!");

  return true;
@}

void Simple::OnExit()
@{
@}

bool Simple::Application()
@{
  if (!OpenApplication(GetObjectRegistry()))
    return ReportError("Error opening system!");

  g3d = csQueryRegistry<iGraphics3D> (GetObjectRegistry());
  if (!g3d) return ReportError("Failed to locate 3D renderer!");

  engine = csQueryRegistry<iEngine> (GetObjectRegistry());
  if (!engine) return ReportError("Failed to locate 3D engine!");

  vc = csQueryRegistry<iVirtualClock> (GetObjectRegistry());
  if (!vc) return ReportError("Failed to locate Virtual Clock!");

  kbd = csQueryRegistry<iKeyboardDriver> (GetObjectRegistry());
  if (!kbd) return ReportError("Failed to locate Keyboard Driver!");

  loader = csQueryRegistry<iLoader> (GetObjectRegistry());
  if (!loader) return ReportError("Failed to locate Loader!");

  cdsys = csQueryRegistry<iCollideSystem> (GetObjectRegistry());
  if (!cdsys) return ReportError ("Failed to locate CD system!");

  view.AttachNew(new csView (engine, g3d));
  iGraphics2D* g2d = g3d->GetDriver2D ();
  // We use the full window to draw the world.
  view->SetRectangle (0, 0, g2d->GetWidth (), g2d->GetHeight ());

  Run();

  return true;
@}

/*---------------*
 * Main function
 *---------------*/
int main (int argc, char* argv[])
@{
  return csApplicationRunner<Simple>::Run (argc, argv);
@}
@end example

One important note in the code above which differs from the earlier tutorials
is that we removed the following line:

@example
engine->SetLightingCacheMode (0);
@end example

This is because, when we are loading from map file, we actually want
the lighting information to be read from the cache (if present).
We do so because relighting a map can be a time-consuming process.

@node Simple Loading The Map, Simple Locating the Camera, Simple Base Code, Tutorial Simple Map
@subsubsection Loading The Map
@cindex loading a map
@cindex vfs
@cindex mount point

Here we add the code to load a map. In this example we will load the
@samp{flarge} map which is included with Crystal Space.

In the second tutorial we already mentioned @sc{vfs} (@pxref{VFS}). This
is important in this case too since we are going to load the map from
the virtual filesystem. To do this we first add the new @code{LoadMap()}
routine just before the @code{OnInitialize()} function:

@example
bool Simple::LoadMap ()
@{
  // Set VFS current directory to the level we want to load.
  csRef<iVFS> VFS (csQueryRegistry<iVFS> (object_reg));
  VFS->ChDir ("/lev/flarge");
  // Load the level file which is called 'world'.
  if (!loader->LoadMapFile ("world"))
    ReportError("Error couldn't load level!");
  return true;
@}
@end example

This code first uses @code{iVFS::ChDir()} to set the current
directory in the virtual file system to @file{/lev/flarge}. In the
case of @samp{flarge} this @dfn{mount point} exists already in the
configuration file @file{vfs.cfg}. If this is not the case for your own levels,
you can either modify @file{vfs.cfg}, provide your own @file{vfs.cfg}, or
else call @code{iVFS::Mount()}
to map a physical file path (can be a @sc{zip} archive file as well)
to a virtual directory.

The call to @code{iLoader::LoadMapFile()} will take the given filename
(in this case @file{world}) and open it from the current @sc{vfs}
directory. Then, it will parse that file and create the geometry which
is specified there.

If loading is successful, then you must call @code{iEngine::Prepare()} to
make sure that all lightmaps are correctly loaded from the cache
and other necessary setup work is done (i.e. textures are registered
and so on). This is demonstrated in the code below, which you add to the
@code{Application()} method, following the @code{SetRectangle()} invocation.
We also add code to initialize collision detection for geometry in the map
which was just loaded.

@example
bool Simple::Application ()
@{
  @dots{}
  view->SetRectangle (0, 0, g2d->GetWidth (), g2d->GetHeight ());

  // Here we load our world from a map file.
  if (!LoadMap ()) return false;

  // Initialize collision objects for all loaded objects.
  csColliderHelper::InitializeCollisionWrappers (cdsys, engine);

  // Let the engine prepare all lightmaps for use and also free all
  // images that were loaded for the texture manager.
  engine->Prepare ();

  Run();
  @dots{}
@}
@end example

So, we now have geometry loaded into the world, but we haven't set the current
sector or the camera's position in the world. Often, maps contain information
specifying where the camera should be placed when the map is first loaded, so
we will use this information if present. This is the topic of the next section.

@node Simple Locating the Camera, , Simple Loading The Map, Tutorial Simple Map
@subsubsection Locating the Camera

@samp{flarge} contains a number of sectors and various objects, and those
are now all loaded in memory. However, this is not enough. We also have
to set the camera to some sector and position in that world. In the
previous tutorials we simply used the sector that we just created
and a fixed position in that sector. (Keep in mind that in Crystal
Space a position in space is always defined as a sector in combination
with a position.) When loading a map we can't work that way because
we don't know which sectors are in the map (unless we make an application
that can only read one map file, but that's not very flexible) and we
also don't know where we can safely put our camera. In the map files
it is possible to specify one or more starting positions. We will
query the engine for such a starting position and initialize our
view (camera) to that. This happens with the following code which you should
add just after @code{engine->Prepare()} in the @code{Application()} method:

@example
  @dots{}
  engine->Prepare ();

  // 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");
    pos = csVector3 (0, 0, 0);
  @}
  if (!room)
    ReportError("Can't find a valid starting position!");

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

  // Initialize our collider actor.
  collider_actor.SetCollideSystem (cdsys);
  collider_actor.SetEngine (engine);
  csVector3 legs (.2f, .3f, .2f);
  csVector3 body (.2f, 1.2f, .2f);
  csVector3 shift (0, -1, 0);
  collider_actor.InitializeColliders (view->GetCamera (),
  	legs, body, shift);

  Run ();
  @dots{}
@}
@end example

First, we see how many camera positions there were defined in the
map by using @code{iEngine::GetCameraPositionCount()}. If there are none,
then the map didn't define a starting position. In that case we will
assume there is a sector called 'room' and we will start the camera
at (0,0,0) in that sector. Otherwise we will use the first
starting position defined in the map.

Once we know our position in the sector, we are also able to initialize the
camera's collision data, as shown in the code above.

That's it. After adding this code this application will now load
the @samp{flarge} map and display it so you can explore the map.
