Home - Forums - Documentation - Gallery - Bugs

Working with halos

Crystal Space supports three type of halo lights, nova halos, cross halos and flare halos. We deal with novas and cross halos only in this tutorial. The result will be something, like this:

Image:halo1.jpg


Nova halos are ideal for you, when you want to render star-like lights or suns. You see this kind of halo light on the right side of the screenshot.


Nova halos have the following attributes:

Seed: The size of seed of the halo

Number of spokes: How many spokes are around the seed.

Roundness: The roundness of the halo.


Cross halos have the following attributes:

Cross: How “cross-like” is the light

Intensity: the intensity of the halo


The simple1 example class is a good starting point for us, the Halotest class is just an extension of this class. In our test application first we create a cross and a nova halo. We want to change the properties of the halos (cross, intensity, spokes, etc) runtime. So we need the following variables in the Halotest class:

  int seed;

  int num_spokes;

  float roundness;

  float intensity;

  float cross;

Let’s go to initialize this variables in the constructor:

Halotest::Halotest ()
{
  SetApplicationName ("CrystalSpace.Halotest1");
  //some starting value
  seed=5;
  num_spokes=50;
  roundness=0.5f;
  intensity=5.0f;
  cross=0.5f;
}

In the CreateRoom() function, we create the halos:

light = engine->CreateLight(0, csVector3(-3, 3, 0), 10, csColor(0.5, 0.5, 0.5), CS_LIGHT_DYNAMICTYPE_DYNAMIC);
  light->CreateCrossHalo(5.0,0.5);
  ll->Add (light);
  crosslight=light;

  light = engine->CreateLight(0, csVector3(3, 3,  0), 10, csColor(0.5, 0.5,       0.5),CS_LIGHT_DYNAMICTYPE_DYNAMIC);
  light->CreateNovaHalo(4,100,1.0);
  novalight=light;	
  ll->Add (light);

Important note, that the type of the lights will be dynamic. Why? When the engie created the halos, we can’t change runtime such properties, like number of spokes or roundness. But when we delete the light, and create a new one with the wished properties, we can simulate a runtime changing halo.

The next two function handle this:

void Halotest::CreateCrossHalo()
{
	 csRef<iLight> light;

	 iLightList* ll = room->GetLights ();

	engine->RemoveLight(crosslight);

	light = engine->CreateLight(0, csVector3(-3, 3, 0), 10, csColor(0.5, 

0.5, 0.5), CS_LIGHT_DYNAMICTYPE_DYNAMIC);

	light->CreateCrossHalo(intensity,cross);

  ll->Add (light);

  crosslight=light;
	
}

/* When we regenerate our halos, first we delete the old halo light from the engine
Next we create a new halo with the wished properties, and add to the engine.
*/
void Halotest::CreateNovaHalo()
{
	 csRef<iLight> light;

	 iLightList* ll = room->GetLights ();

	engine->RemoveLight(novalight);

	light = engine->CreateLight(0, csVector3(3, 3, 0), 10, csColor(0.5, 0.5,
 0.5), CS_LIGHT_DYNAMICTYPE_DYNAMIC);

	light->CreateNovaHalo(seed,num_spokes,roundness);

  ll->Add (light);

  novalight=light;

}

Now we set up some key binding to change the wished properties:

For the cross halo:

a: decrease intensity

s: increase intensity

g: decrease cross

h: increase cross


For the nova halo:

w: decrease seed

e: increase seed

z: decrease spokes

e: increase spokes

1: decreasing roundness

2: decreasing roundness


//here ve step our halo related values. Try different stepping numbers to fine the code.
	if (code == 'a')
	{
		if(intensity>0.1f)
		{
			intensity-=0.1f;
			CreateCrossHalo();
		}
		return true;
	}
	
	if (code == 's')
	{
		intensity+=0.1f;
		CreateCrossHalo();
		return true;
	}

	if (code =='g')
	{
		cross-= 0.1f;
		CreateCrossHalo();
		return true;
	}

	//more key handling

  }

So when the user presses a key, we regenereate the halo with the new values.

That’s it. Try it. Here is another example shot after various key pressing:

Image:halo2.jpg

That's it. Enjoy.

And finally, here is the full source: halotest.h




#ifndef __Halotest_H__
#define __Halotest_H__

#include <crystalspace.h>


class Halotest : public csApplicationFramework, public csBaseEventHandler
{

 private:
  
  csRef<iEngine> engine;
   
  
  csRef<iLoader> loader;
     
  
  csRef<iGraphics3D> g3d;
	
  
  csRef<iKeyboardDriver> kbd;
	  
 
  csRef<iVirtualClock> vc;

 
  csRef<iView> view;

  
  iSector* room;

  
  float rotX, rotY;

  csRef<FrameBegin3DDraw> drawer;

  csRef<FramePrinter> printer;

  csRef<iFont> font;

  csRef<iLight> crosslight;

  csRef<iLight> novalight;

  int seed;

  int num_spokes;

  float roundness;

  float intensity;

  float cross;

  void CreateNovaHalo();

  void CreateCrossHalo();

 public:

  bool SetupModules ();

  bool OnKeyboard (iEvent&);
  
  
  void Frame ();
  
  void FinishFrame();

  
  void CreateRoom ();
    
 
  Halotest ();

  ~Halotest ();

 
  void OnExit ();

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

  bool Application ();
  
  CS_EVENTHANDLER_PHASE_LOGIC("application.Halotest1")
};

#endif 

halotest.cpp


#include "halotest.h"

CS_IMPLEMENT_APPLICATION

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

Halotest::Halotest ()
{
  SetApplicationName ("CrystalSpace.Halotest1");
  //some starting value
  seed=5;
  num_spokes=50;
  roundness=0.5f;
  intensity=5.0f;
  cross=0.5f;
}

Halotest::~Halotest ()
{

}

void Halotest::Frame ()
{
  // 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.06 * 20);

  iCamera* c = view->GetCamera();

  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))
      c->Move (CS_VEC_RIGHT * 4 * speed);
    if (kbd->GetKeyState (CSKEY_LEFT))
      c->Move (CS_VEC_LEFT * 4 * speed);
    if (kbd->GetKeyState (CSKEY_UP))
      c->Move (CS_VEC_UP * 4 * speed);
    if (kbd->GetKeyState (CSKEY_DOWN))
      c->Move (CS_VEC_DOWN * 4 * speed);
  }
  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))
      rotY += speed;
    if (kbd->GetKeyState (CSKEY_LEFT))
      rotY -= speed;
    if (kbd->GetKeyState (CSKEY_PGUP))
      rotX += speed;
    if (kbd->GetKeyState (CSKEY_PGDN))
      rotX -= speed;
    if (kbd->GetKeyState (CSKEY_UP))
      c->Move (CS_VEC_FORWARD * 4 * speed);
    if (kbd->GetKeyState (CSKEY_DOWN))
      c->Move (CS_VEC_BACKWARD * 4 * speed);
  }

  // We now assign a new rotation transformation to the camera.  You
  // can think of the rotation this way: starting from the zero
  // position, you first rotate "rotY" radians on your Y axis to get
  // the first rotation.  From there you rotate "rotX" radians on the
  // your X axis to get the final rotation.  We multiply the
  // individual rotations on each axis together to get a single
  // rotation matrix.  The rotations are applied in right to left
  // order .
  csMatrix3 rot = csXRotMatrix3 (rotX) * csYRotMatrix3 (rotY);
  csOrthoTransform ot (rot, c->GetTransform().GetOrigin ());
  c->SetTransform (ot);

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

  //display halo data
  int color=g3d->GetDriver2D()->FindRGB (255, 150, 100);
  g3d->GetDriver2D()->Write(font,10,420,color,-1,"seed:%d");

}

bool Halotest::OnKeyboard(iEvent& ev)
{
  // We got a keyboard event.
  csKeyEventType eventtype = csKeyEventHelper::GetEventType(&ev);
  if (eventtype == csKeyEventTypeDown)
  {
    // The user pressed a key (as opposed to releasing it).
    utf32_char code = csKeyEventHelper::GetCookedCode(&ev);
    if (code == CSKEY_ESC)
    {
      // The user pressed escape to exit the application.
      // The proper way to quit a Crystal Space application
      // is by broadcasting a csevQuit event. That will cause the
      // main runloop to stop. To do that we get the event queue from
      // the object registry and then post the event.
      csRef<iEventQueue> q = 
        csQueryRegistry<iEventQueue> (GetObjectRegistry());
      if (q.IsValid()) q->GetEventOutlet()->Broadcast(
      	csevQuit(GetObjectRegistry()));
    }
	//here ve step our halo related values. Try different stepping numbers to fine the code.
	if (code == 'a')
	{
		if(intensity>0.1f)
		{
			intensity-=0.1f;
			CreateCrossHalo();
		}
		return true;
	}
	
	if (code == 's')
	{
		intensity+=0.1f;
		CreateCrossHalo();
		return true;
	}

	if (code =='g')
	{
		cross-= 0.1f;
		CreateCrossHalo();
		return true;
	}

	if (code =='h')
	{
		cross+= 0.1f;
		CreateCrossHalo();
		return true;
	}

	if (code =='w')
	{
		if(seed>0)
		{
		seed--;
		CreateNovaHalo();
		}
		return true;
	}

	if(code =='e')
	{
		seed++;
		CreateNovaHalo();
		return true;
	}

	if (code =='z')
	{
		if(num_spokes>0)
		{
		num_spokes--;
		CreateNovaHalo();
		}
		return true;
	}

	if (code =='u')
	{
		num_spokes++;
		CreateNovaHalo();
		return true;
	}

	if (code =='1')
	{
		if(roundness>0.1f)
		{
		roundness-=0.1f;
		CreateNovaHalo();
		}
		return true;
	}

	if (code=='2')
	{
		roundness+=0.1f;
		CreateNovaHalo();
		return true;
	}

  }

  return false;
}

bool Halotest::OnInitialize(int /*argc*/, char* /*argv*/ [])
{
  // RequestPlugins() will load all plugins we specify. In addition
  // it will also check if there are plugins that need to be loaded
  // from the config system (both the application config and CS or
  // global configs). In addition it also supports specifying plugins
  // on the commandline.
  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_FONTSERVER,
    CS_REQUEST_END))
    return ReportError("Failed to initialize plugins!");

  // "Warm up" the event handler so it can interact with the world
  csBaseEventHandler::Initialize(GetObjectRegistry());

  // Now we need to register the event handler for our application.
  // Crystal Space is fully event-driven. Everything (except for this
  // initialization) happens in an event.
  // Rather than simply handling all events, we subscribe to the
  // particular events we're interested in.
  csEventID events[] = {
    csevFrame (GetObjectRegistry()),
    csevKeyboardEvent (GetObjectRegistry()),
    CS_EVENTLIST_END
  };
  if (!RegisterQueue(GetObjectRegistry(), events))
    return ReportError("Failed to set up event handler!");

  // Report success
  return true;
}

void Halotest::OnExit()
{
  // Shut down the event handlers we spawned earlier.
  drawer.Invalidate();
  printer.Invalidate();
}

bool Halotest::Application()
{
  // Open the main system. This will open all the previously loaded plug-ins.
  // i.e. all windows will be opened.
  if (!OpenApplication(GetObjectRegistry()))
    return ReportError("Error opening system!");

  if (SetupModules()) {
    // This calls the default runloop. This will basically just keep
    // broadcasting process events to keep the game going.
    Run();
  }

  return true;
}

bool Halotest::SetupModules ()
{
  // Now get the pointer to various modules we need. We fetch them
  // from the object registry. The RequestPlugins() call we did earlier
  // registered all loaded plugins with the object registry.
  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!");

  csRef<iFontServer> fs= g3d->GetDriver2D()->GetFontServer();
  if(!fs) return ReportError("Failed to locate font server!");

  font=fs->LoadFont(CSFONT_COURIER);


  // We need a View to the virtual world.
  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 ());

  // First disable the lighting cache. Our app is Halotest enough
  // not to need this.
  engine->SetLightingCacheMode (0);

  // Here we create our world.
  CreateRoom();

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

  // these are used store the current orientation of the camera
  rotY = rotX = 0;
  
  // Now we need to position the camera in our world.
  view->GetCamera ()->SetSector (room);
  view->GetCamera ()->GetTransform ().SetOrigin (csVector3 (0, 5, -4));

  // We use some other "helper" event handlers to handle 
  // pushing our work into the 3D engine and rendering it
  // to the screen.
  drawer.AttachNew(new FrameBegin3DDraw (GetObjectRegistry (), view));
  printer.AttachNew(new FramePrinter (GetObjectRegistry ()));

  return true;
}

void Halotest::CreateRoom ()
{
  // Load the texture from the standard library.  This is located in
  // CS/data/standard.zip and mounted as /lib/std using the Virtual
  // File System (VFS) plugin.
  if (!loader->LoadTexture ("stone", "/lib/std/blue.jpg"))
    ReportError("Error loading 'stone4' texture!");

  iMaterialWrapper* tm = engine->GetMaterialList ()->FindByName ("stone");

  // We create a new sector called "room".
  room = engine->CreateSector ("room");

  // Creating the walls for our room.
  csRef<iMeshWrapper> walls (engine->CreateSectorWallsMesh (room, "walls"));
  iMeshObject* walls_object = walls->GetMeshObject ();
  iMeshObjectFactory* walls_factory = walls_object->GetFactory();
  csRef<iThingFactoryState> walls_state = 
    scfQueryInterface<iThingFactoryState> (walls_factory);
  walls_state->AddInsideBox (csVector3 (-5, 0, -5), csVector3 (5, 20, 5));
  walls_state->SetPolygonMaterial (CS_POLYRANGE_LAST, tm);
  walls_state->SetPolygonTextureMapping (CS_POLYRANGE_LAST, 3);

  csRef<iLight> light;
  iLightList* ll = room->GetLights ();

  light = engine->CreateLight(0, csVector3(-3, 3, 0), 10, csColor(0.5, 0.5, 0.5), CS_LIGHT_DYNAMICTYPE_DYNAMIC);
  light->CreateCrossHalo(5.0,0.5);
  ll->Add (light);
  crosslight=light;

  light = engine->CreateLight(0, csVector3(3, 3,  0), 10, csColor(0.5, 0.5, 0.5),CS_LIGHT_DYNAMICTYPE_DYNAMIC);
  light->CreateNovaHalo(4,100,1.0);
  novalight=light;	
  ll->Add (light);


}

void Halotest::CreateCrossHalo()
{
	 csRef<iLight> light;
	 iLightList* ll = room->GetLights ();
	engine->RemoveLight(crosslight);
	light = engine->CreateLight(0, csVector3(-3, 3, 0), 10, csColor(0.5, 0.5, 0.5), CS_LIGHT_DYNAMICTYPE_DYNAMIC);
	light->CreateCrossHalo(intensity,cross);
  ll->Add (light);
  crosslight=light;
	
}

/* When we regenerate our halos, first we delete the old halo light from the engine
   Next we create a new halo with the wished properties, and add to the engine.
*/
void Halotest::CreateNovaHalo()
{
	 csRef<iLight> light;
	 iLightList* ll = room->GetLights ();
	engine->RemoveLight(novalight);
	light = engine->CreateLight(0, csVector3(3, 3, 0), 10, csColor(0.5, 0.5, 0.5), CS_LIGHT_DYNAMICTYPE_DYNAMIC);
	light->CreateNovaHalo(seed,num_spokes,roundness);
  ll->Add (light);
  novalight=light;
}

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

/*-------------------------------------------------------------------------*
 * Main function
 *-------------------------------------------------------------------------*/
int main (int argc, char* argv[])
{
  /* Runs the application. 
   *
   * csApplicationRunner<> is a small wrapper to support "restartable" 
   * applications (ie where CS needs to be completely shut down and loaded 
   * again). Halotest1 does not use that functionality itself, however, it
   * allows you to later use "Halotest.Restart();" and it'll just work.
   */
  return csApplicationRunner<Halotest>::Run (argc, argv);
}

| Article | Discussion | View source | History |