1) First version of rubber band (to do: rubber bands are sometimes not visible)

2) Code cleanup.
3) Initial support for special water nodes.


git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/branches/irrlicht@3299 178a84e3-b1eb-0310-8ba1-8eac791a3b58
This commit is contained in:
hikerstk 2009-03-23 23:50:50 +00:00
parent c8e05cf0dd
commit c14c06e830
9 changed files with 268 additions and 152 deletions

View File

@ -20,10 +20,6 @@
#include "graphics/camera.hpp"
#ifndef HAVE_IRRLICHT
#define _WINSOCKAPI_
#include <plib/ssg.h>
#endif
#include "user_config.hpp"
#include "audio/sound_manager.hpp"
#include "graphics/irr_driver.hpp"
@ -38,11 +34,7 @@ Camera::Camera(int camera_index, const Kart* kart)
{
m_mode = CM_NORMAL;
m_index = camera_index;
#ifdef HAVE_IRRLICHT
m_camera = irr_driver->addCamera();
#else
m_context = new ssgContext ;
#endif
m_distance = kart->getKartProperties()->getCameraDistance();
m_kart = kart;
m_xyz = kart->getXYZ();
@ -238,11 +230,7 @@ void Camera::update(float dt)
// The reverse mode and the cam used in follow the leader mode (when a
// kart has been eliminated) are facing backwards:
bool reverse = m_kart->getControls().m_look_back || m_mode==CM_LEADER_MODE;
#ifdef HAVE_IRRLICHT
Vec3 cam_rel_pos(0.f, reverse ? m_distance : -m_distance, 1.5f);
#else
Vec3 cam_rel_pos(0.f, reverse ? m_distance : -m_distance, 1.5f);
#endif
// Set the camera rotation
// -----------------------
@ -254,7 +242,7 @@ void Camera::update(float dt)
else
pitch = sign * DEGREE_TO_RAD(25.0f);
btQuaternion cam_rot(0.0f, pitch, reverse ? M_PI : 0.0f);
btQuaternion cam_rot(0.0f, -pitch, reverse ? M_PI : 0.0f);
// Camera position relative to the kart
btTransform relative_to_kart(cam_rot, cam_rel_pos);
@ -266,12 +254,8 @@ void Camera::update(float dt)
Coord c(result);
m_xyz = c.getXYZ();
m_hpr = c.getHPR();
#ifdef HAVE_IRRLICHT
m_camera->setPosition(m_xyz.toIrrVector());
m_camera->setTarget(kart_xyz.toIrrVector());
#else
m_context -> setCamera(&c.toSgCoord());
#endif
if(race_manager->getNumLocalPlayers() < 2)
sound_manager->positionListener(m_xyz, kart_xyz - m_xyz);
} // update
@ -285,11 +269,9 @@ void Camera::finalCamera(float dt)
{
m_xyz += m_velocity*dt;
m_hpr += m_angular_velocity*dt;
Coord coord(m_xyz, m_hpr);
#ifdef HAVE_IRRLICHT
#else
m_context->setCamera(&coord.toSgCoord());
#endif
m_camera->setPosition(m_xyz.toIrrVector());
// FIXME: target position needs to be computed
// m_camera->setTarget(target_xyz.toIrrVector());
}
#undef TEST_END_CAMERA_POSITION
#ifdef TEST_END_CAMERA_POSITION
@ -317,12 +299,12 @@ void Camera::apply ()
int width = user_config->m_width ;
int height = user_config->m_height;
#ifdef HAVE_IRRLICHT
#else
glViewport ( (int)((float)width * m_x),
(int)((float)height * m_y),
(int)((float)width * m_w),
(int)((float)height * m_h) ) ;
#ifdef HAVE_IRRLICHT
#else
m_context -> makeCurrent () ;
#endif
} // apply

View File

@ -93,6 +93,23 @@ scene::IMesh *IrrDriver::getMesh(const std::string &filename)
return m->getMesh(0);
} // getMesh
// ----------------------------------------------------------------------------
/** Converts the mesh into a water scene node.
* \param mesh The mesh which is converted into a water scene node.
* \param wave_height Height of the water waves.
* \param wave_speed Speed of the water waves.
* \param wave_length Lenght of a water wave.
*/
scene::ISceneNode* IrrDriver::addWaterNode(scene::IMesh *mesh,
float wave_height,
float wave_speed,
float wave_length)
{
return m_scene_manager->addWaterSurfaceSceneNode(mesh,
wave_height, wave_speed,
wave_length);
} // addWaterNode
// ----------------------------------------------------------------------------
/** Adds a mesh that will be optimised using an oct tree.
* \param mesh Mesh to add.
@ -120,6 +137,52 @@ scene::ISceneNode *IrrDriver::addMesh(scene::IMesh *mesh)
return m_scene_manager->addMeshSceneNode(mesh);
} // addMesh
// ----------------------------------------------------------------------------
/** Creates a quad mesh buffer and adds it to the scene graph.
*/
scene::IMesh *IrrDriver::createQuadMesh(const video::SColor &color)
{
scene::SMeshBuffer *buffer = new scene::SMeshBuffer();
video::S3DVertex v;
v.Pos = core::vector3df(0,0,0);
// Add the vertices
// ----------------
buffer->Vertices.push_back(v);
buffer->Vertices.push_back(v);
buffer->Vertices.push_back(v);
buffer->Vertices.push_back(v);
// Define the indices for the triangles
// ------------------------------------
buffer->Indices.push_back(0);
buffer->Indices.push_back(1);
buffer->Indices.push_back(2);
buffer->Indices.push_back(0);
buffer->Indices.push_back(2);
buffer->Indices.push_back(3);
// Set the normals
// ---------------
core::vector3df n(1/sqrt(2.0f), 1/sqrt(2.0f), 0);
buffer->Vertices[0].Normal = n;
buffer->Vertices[1].Normal = n;
buffer->Vertices[2].Normal = n;
buffer->Vertices[3].Normal = n;
video::SMaterial m;
m.AmbientColor = color;
m.DiffuseColor = color;
m.EmissiveColor = color;
m.BackfaceCulling = false;
buffer->Material = m;
SMesh *mesh = new SMesh();
mesh->addMeshBuffer(buffer);
mesh->recalculateBoundingBox();
buffer->drop();
return mesh;
} // createQuadMesh
// ----------------------------------------------------------------------------
/** Removes a scene node from the scene.
* \param node The scene node to remove.
@ -171,6 +234,27 @@ scene::ISceneNode *IrrDriver::addSkyDome(const std::string &texture_name,
sphere_percent);
} // addSkyDome
// ----------------------------------------------------------------------------
/** Adds a skybox using. Irrlicht documentation:
* A skybox is a big cube with 6 textures on it and is drawn around the camera
* position.
* \param top: Texture for the top plane of the box.
* \param bottom: Texture for the bottom plane of the box.
* \param left: Texture for the left plane of the box.
* \param right: Texture for the right plane of the box.
* \param front: Texture for the front plane of the box.
* \param back: Texture for the back plane of the box.
*/
scene::ISceneNode *IrrDriver::addSkyBox(const std::vector<std::string> &texture_names)
{
std::vector<video::ITexture*> t;
for(unsigned int i=0; i<texture_names.size(); i++)
{
t.push_back(getTexture(texture_names[i]));
}
return m_scene_manager->addSkyBoxSceneNode(t[0], t[1], t[2], t[3], t[4], t[5]);
} // addSkyBox
// ----------------------------------------------------------------------------
/** Adds a camera to the scene.
*/

View File

@ -21,6 +21,7 @@
#define HEADER_IRR_DRIVER_HPP
#include <string>
#include <vector>
#include "irrlicht.h"
using namespace irr;
@ -50,6 +51,9 @@ public:
bool OnEvent(const irr::SEvent &event);
void setAmbientLight(const video::SColor &light);
video::ITexture *getTexture(const std::string &filename);
scene::IMesh *createQuadMesh(const video::SColor &c=video::SColor(77, 179, 0, 0));
scene::ISceneNode *addWaterNode(scene::IMesh *mesh, float wave_height,
float wave_speed, float wave_length);
scene::ISceneNode *addOctTree(scene::IMesh *mesh);
scene::ISceneNode *addMesh(scene::IMesh *mesh);
scene::IParticleSystemSceneNode
@ -57,6 +61,7 @@ public:
scene::ISceneNode *addSkyDome(const std::string &texture, int hori_res,
int vert_res, float texture_percent,
float sphere_percent);
scene::ISceneNode *addSkyBox(const std::vector<std::string> &texture_names);
void removeNode(scene::ISceneNode *node);
void removeMesh(scene::IMesh *mesh);
scene::ISceneNode *addAnimatedMesh(scene::IAnimatedMesh *mesh);

View File

@ -94,7 +94,6 @@ Plunger::Plunger(Kart *kart) : Flyable(kart, POWERUP_PLUNGER)
else
{
m_rubber_band = new RubberBand(this, *kart);
m_rubber_band->ref();
}
m_keep_alive = -1;
} // Plunger
@ -103,7 +102,6 @@ Plunger::Plunger(Kart *kart) : Flyable(kart, POWERUP_PLUNGER)
Plunger::~Plunger()
{
m_rubber_band->removeFromScene();
ssgDeRefDelete(m_rubber_band);
} // ~Plunger
// -----------------------------------------------------------------------------

View File

@ -21,6 +21,7 @@
#include "material_manager.hpp"
#include "race_manager.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/scene.hpp"
#include "items/plunger.hpp"
#include "items/projectile_manager.hpp"
@ -36,38 +37,24 @@
* can trigger an explosion)
* \param kart Reference to the kart.
*/
RubberBand::RubberBand(Plunger *plunger, const Kart &kart)
: ssgVtxTable(GL_QUADS, new ssgVertexArray,
new ssgNormalArray,
new ssgTexCoordArray,
new ssgColourArray ),
m_plunger(plunger), m_owner(kart)
RubberBand::RubberBand(Plunger *plunger, const Kart &kart) :
m_plunger(plunger), m_owner(kart)
{
#ifdef DEBUG
setName("rubber_band");
#endif
// The call to update defines the actual coordinates, only the entries are added for now.
vertices->add(0, 0, 0); vertices->add(0, 0, 0);
vertices->add(0, 0, 0); vertices->add(0, 0, 0);
m_mesh = irr_driver->createQuadMesh();
m_buffer = m_mesh->getMeshBuffer(0);
m_attached_state = RB_TO_PLUNGER;
update(0);
assert(m_buffer->getVertexType()==video::EVT_STANDARD);
sgVec3 norm;
sgSetVec3(norm, 1/sqrt(2.0f), 0, 1/sqrt(2.0f));
normals->add(norm);normals->add(norm);
normals->add(norm);normals->add(norm);
irr::video::S3DVertex* vertices=(video::S3DVertex*)m_buffer->getVertices();
sgVec4 colour;
sgSetVec4(colour, 0.7f, 0.0f, 0.0f, 0.3f);
colours->add(colour);colours->add(colour);
colours->add(colour);colours->add(colour);
m_state = new ssgSimpleState();
m_state->disable(GL_CULL_FACE);
setState(m_state);
//setState(material_manager->getMaterial("chrome.rgb")->getState());
video::SColor c(77, 179, 0, 0);
vertices[0].Color = c;
vertices[1].Color = c;
vertices[2].Color = c;
vertices[3].Color = c;
stk_scene->add(this);
updatePosition();
m_node = irr_driver->addMesh(m_mesh);
} // RubberBand
// ----------------------------------------------------------------------------
@ -76,9 +63,43 @@ RubberBand::RubberBand(Plunger *plunger, const Kart &kart)
*/
void RubberBand::removeFromScene()
{
stk_scene->remove(this);
irr_driver->removeNode(m_node);
irr_driver->removeMesh(m_mesh);
} // removeFromScene
// ----------------------------------------------------------------------------
/** Updates the position of the rubber band. It especially sets the
* end position of the rubber band, i.e. the side attached to the plunger,
* track, or kart hit.
*/
void RubberBand::updatePosition()
{
const Vec3 &k = m_owner.getXYZ();
// Get the position to which the band is attached
// ----------------------------------------------
switch(m_attached_state)
{
case RB_TO_KART: m_end_position = m_hit_kart->getXYZ(); break;
case RB_TO_TRACK: m_end_position = m_hit_position; break;
case RB_TO_PLUNGER: m_end_position = m_plunger->getXYZ();
checkForHit(k, m_end_position); break;
} // switch(m_attached_state);
// Update the rubber band positions
// --------------------------------
// Todo: make height dependent on length (i.e. rubber band gets
// thinner). And call explosion if the band is too long.
const float hh=.1f; // half height of the band
const Vec3 &p=m_end_position; // for shorter typing
irr::video::S3DVertex* v=(video::S3DVertex*)m_buffer->getVertices();
v[0].Pos.X = p.getX()-hh; v[0].Pos.Z=p.getY(); v[0].Pos.Y = p.getZ()-hh;
v[1].Pos.X = p.getX()+hh; v[1].Pos.Z=p.getY(); v[1].Pos.Y = p.getZ()+hh;
v[2].Pos.X = k.getX()+hh; v[2].Pos.Z=k.getY(); v[2].Pos.Y = k.getZ()+hh;
v[3].Pos.X = k.getX()-hh; v[3].Pos.Z=k.getY(); v[3].Pos.Y = k.getZ()-hh;
m_buffer->recalculateBoundingBox();
} // updatePosition
// ----------------------------------------------------------------------------
/** Updates the rubber band. It takes the new position of the kart and the
* plunger, and sets the quad representing the rubber band appropriately.
@ -97,38 +118,12 @@ void RubberBand::update(float dt)
return;
}
Vec3 p;
updatePosition();
const Vec3 &k = m_owner.getXYZ();
// Get the position to which the band is attached
// ----------------------------------------------
switch(m_attached_state)
{
case RB_TO_KART: p = m_hit_kart->getXYZ(); break;
case RB_TO_TRACK: p = m_hit_position; break;
case RB_TO_PLUNGER: p = m_plunger->getXYZ();
checkForHit(k, p); break;
} // switch(m_attached_state);
// Draw the rubber band
// --------------------
// Todo: make height dependent on length (i.e. rubber band gets
// thinner). And call explosion if the band is too long.
const float hh=.1f; // half height of the band
float *f = vertices->get(0);
f[0] = p.getX()-hh; f[1] = p.getY(); f[2] = p.getZ()-hh;
f = vertices->get(1);
f[0] = p.getX()+hh; f[1] = p.getY(); f[2] = p.getZ()+hh;
f = vertices->get(2);
f[0] = k.getX()+hh; f[1] = k.getY(); f[2] = k.getZ()+hh;
f = vertices->get(3);
f[0] = k.getX()-hh; f[1] = k.getY(); f[2] = k.getZ()-hh;
dirtyBSphere();
// Check for rubber band snapping
// ------------------------------
float l = (p-k).length2();
float l = (m_end_position-k).length2();
float max_len = m_owner.getKartProperties()->getRubberBandMaxLength();
if(l>max_len*max_len)
{
@ -143,7 +138,7 @@ void RubberBand::update(float dt)
if(m_attached_state!=RB_TO_PLUNGER)
{
float force = m_owner.getKartProperties()->getRubberBandForce();
Vec3 diff = p-k;
Vec3 diff = m_end_position-k;
// detach rubber band if kart gets very close to hit point
if(m_attached_state==RB_TO_TRACK && diff.length2() < 10*10)

View File

@ -23,34 +23,42 @@
/** This class is used together with the pluger to display a rubber band from
* the shooting kart to the plunger.
*/
#define _WINSOCKAPI_
#include <plib/ssg.h>
#include "utils/vec3.hpp"
class Kart;
class Plunger;
class RubberBand : public ssgVtxTable
class RubberBand
{
private:
enum {RB_TO_PLUNGER, /**< Rubber band is attached to plunger. */
RB_TO_KART, /**< Rubber band is attached to a kart hit. */
RB_TO_TRACK} /**< Rubber band is attached to track. */
m_attached_state;
m_attached_state;
/** True if plunger was fired backwards. */
bool m_is_backward;
bool m_is_backward;
/** If rubber band is attached to track, the coordinates. */
Vec3 m_hit_position;
Vec3 m_hit_position;
/** The plunger the rubber band is attached to. */
Plunger *m_plunger;
Plunger *m_plunger;
/** The kart who shot this plunger. */
const Kart &m_owner;
const Kart &m_owner;
/** The scene node for the rubber band. */
scene::ISceneNode *m_node;
/** The mesh of the rubber band. */
scene::IMesh *m_mesh;
/** The mesh buffer containing the actual vertices of the rubber band. */
scene::IMeshBuffer *m_buffer;
/** The kart a plunger might have hit. */
Kart *m_hit_kart;
/** State for rubber band. */
ssgSimpleState *m_state;
Kart *m_hit_kart;
/** Stores the end of the rubber band (i.e. the side attached to the
* plunger. */
Vec3 m_end_position;
void checkForHit(const Vec3 &k, const Vec3 &p);
void updatePosition();
public:
RubberBand(Plunger *plunger, const Kart &kart);

View File

@ -112,65 +112,17 @@ void Material::init(unsigned int index)
//-----------------------------------------------------------------------------
void Material::install(bool is_full_path)
{
#ifdef HAVE_IRRLICHT
if(m_texname!="")
{
// Avoid irrlicht warning about not being able to load texture.
m_texture = irr_driver->getTexture(file_manager->getTextureFile(m_texname));
}
#else
if ( isSphereMap () )
{
m_predraw = setSpheremap ;
m_postdraw = clearSpheremap ;
}
m_state = new ssgSimpleState ;
m_state -> ref () ;
m_state -> setExternalPropertyIndex ( m_index ) ;
if ( m_texname.size()>0 )
{
std::string fn=is_full_path ? m_texname
: file_manager->getTextureFile(m_texname);
if(fn=="")
{
fprintf(stderr, "WARNING: texture '%s' not found.\n",
m_texname.c_str());
}
m_state -> setTexture ( fn.c_str(), !(m_clamp_tex & UCLAMP),
!(m_clamp_tex & VCLAMP) );
m_state -> enable ( GL_TEXTURE_2D ) ;
}
else
m_state -> disable ( GL_TEXTURE_2D ) ;
if ( m_lighting )
m_state -> enable ( GL_LIGHTING ) ;
else
m_state -> disable ( GL_LIGHTING ) ;
m_state -> setShadeModel ( GL_SMOOTH ) ;
m_state -> enable ( GL_COLOR_MATERIAL ) ;
m_state -> enable ( GL_CULL_FACE ) ;
m_state -> setColourMaterial ( GL_AMBIENT_AND_DIFFUSE ) ;
m_state -> setMaterial ( GL_EMISSION, 0, 0, 0, 1 ) ;
m_state -> setMaterial ( GL_SPECULAR, 0, 0, 0, 1 ) ;
m_state -> setShininess ( 0 ) ;
if ( m_transparency )
{
m_state -> setTranslucent () ;
m_state -> enable ( GL_ALPHA_TEST ) ;
m_state -> setAlphaClamp ( m_alpha_ref ) ;
m_state -> enable ( GL_BLEND ) ;
//m_texture->MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
}
else
{
m_state -> setOpaque () ;
m_state -> disable ( GL_BLEND ) ;
}
#endif
// FIXME: more parameters need to be set!
// now set the name to the basename, so that all tests work as expected
m_texname = StringUtils::basename(m_texname);

View File

@ -929,11 +929,22 @@ void Track::loadTrack(const std::string &filename)
node = xml->getNode("sky-box");
if(node)
{
m_sky_type = SKY_BOX;
std::string s;
node->get("texture", &s);
m_sky_textures = StringUtils::split(s, ' ');
if(m_sky_textures.size()!=6)
{
fprintf(stderr, "A skybox needs 6 textures, but %d are specified\n",
m_sky_textures.size());
fprintf(stderr, "in '%s'.\n", filename.c_str());
}
else
{
m_sky_type = SKY_BOX;
}
} // if sky-box
// Set the correct paths
m_screenshot = file_manager->getTrackFile(m_screenshot, getIdent());
} // loadTrack
@ -1144,7 +1155,7 @@ void Track::convertTrackToBullet(const scene::IMesh *mesh)
{
for(unsigned int i=0; i<mesh->getMeshBufferCount(); i++) {
scene::IMeshBuffer *mb = mesh->getMeshBuffer(i);
// FIXME: take translation/rotation into accou
// FIXME: take translation/rotation into account
if(mb->getVertexType()!=video::EVT_STANDARD) {
fprintf(stderr, "WARNING: Physics::convertTrack: Ignoring type '%d'!",
mb->getVertexType());
@ -1214,6 +1225,55 @@ bool Track::loadMainTrack(const XMLNode &node)
return true;
} // loadMainTrack
// ----------------------------------------------------------------------------
/** Creates a water node.
* \param node The XML node containing the specifications for the water node.
*/
void Track::createWater(const XMLNode &node)
{
std::string model_name;
node.get("model", &model_name);
std::string full_path = file_manager->getTrackFile(model_name,
getIdent());
//scene::IMesh *mesh = irr_driver->getMesh(full_path);
scene::IMesh *mesh = irr_driver->getSceneManager()->getMesh(full_path.c_str());
// scene::IAnimatedMesh *mesh = irr_driver->getSceneManager()->addHillPlaneMesh("myHill",
// core::dimension2d<f32>(20,20),
// core::dimension2d<u32>(40,40), 0, 0,
// core::dimension2d<f32>(0,0),
// core::dimension2d<f32>(10,10));
scene::SMeshBuffer b(*(scene::SMeshBuffer*)(mesh->getMeshBuffer(0)));
scene::SMeshBuffer* buffer = new scene::SMeshBuffer(*(scene::SMeshBuffer*)(mesh->getMeshBuffer(0)));
float wave_height = 2.0f;
float wave_speed = 300.0f;
float wave_length = 10.0f;
node.get("height", &wave_height);
node.get("speed", &wave_speed);
node.get("length", &wave_length);
scene::ISceneNode* scene_node = irr_driver->addWaterNode(mesh,
wave_height,
wave_speed,
wave_length);
if(!mesh || !scene_node)
{
fprintf(stderr, "Warning: Water model '%s' in '%s' not found, ignored.\n",
node.getName().c_str(), model_name.c_str());
return;
}
m_all_meshes.push_back(mesh);
core::vector3df xyz(0,0,0);
node.getXYZ(&xyz);
core::vector3df hpr(0,0,0);
node.getHPR(&hpr);
scene_node->setPosition(xyz);
scene_node->setRotation(hpr);
m_all_nodes.push_back(scene_node);
} // createWater
// ----------------------------------------------------------------------------
void Track::loadTrackModel()
{
@ -1259,6 +1319,34 @@ void Track::loadTrackModel()
MovingPhysics *mp = new MovingPhysics(node);
callback_manager->addCallback(mp, CB_TRACK);
}
else if(name=="water")
{
createWater(*node);
}
else if(name=="model")
{
std::string model_name;
node->get("model", &model_name);
std::string full_path = file_manager->getTrackFile(model_name,
getIdent());
scene::IMesh *mesh = irr_driver->getAnimatedMesh(full_path);
if(!mesh)
{
fprintf(stderr, "Warning: Main track model '%s' in '%s' not found, aborting.\n",
node->getName().c_str(), model_name.c_str());
exit(-1);
}
m_all_meshes.push_back(mesh);
scene::ISceneNode *scene_node = irr_driver->addOctTree(mesh);
core::vector3df xyz(0,0,0);
node->getXYZ(&xyz);
core::vector3df hpr(0,0,0);
node->getHPR(&hpr);
scene_node->setPosition(xyz);
scene_node->setRotation(hpr);
m_all_nodes.push_back(scene_node);
scene_node->setMaterialFlag(video::EMF_LIGHTING, false);
}
else if(name=="banana" || name=="item" ||
name=="small-nitro" || name=="big-nitro")
{
@ -1507,12 +1595,15 @@ void Track::loadTrackModel()
if(m_sky_type==SKY_DOME)
{
irr_driver->addSkyDome(m_sky_textures[0],
m_sky_hori_segments, m_sky_vert_segments,
m_sky_texture_percent, m_sky_sphere_percent);
m_all_nodes.push_back(irr_driver->addSkyDome(m_sky_textures[0],
m_sky_hori_segments,
m_sky_vert_segments,
m_sky_texture_percent,
m_sky_sphere_percent) );
}
else if(m_sky_type==SKY_BOX)
{
m_all_nodes.push_back(irr_driver->addSkyBox(m_sky_textures));
}
file_manager->popTextureSearchPath();
file_manager->popModelSearchPath ();

View File

@ -66,6 +66,7 @@ private:
bool m_is_arena;
int m_version;
bool loadMainTrack(const XMLNode &node);
void createWater(const XMLNode &node);
/** The type of sky to be used for the track. */
enum {SKY_NONE, SKY_BOX,
SKY_DOME} m_sky_type;