Added support for 'graphical' meshes - atm mostly for water surfaces

(and below water meshes), so that water splashes can be displayed at
the proper position (not used atm).


git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/trunk@7665 178a84e3-b1eb-0310-8ba1-8eac791a3b58
This commit is contained in:
hikerstk 2011-02-09 21:56:44 +00:00
parent 75d757d411
commit 240d39636c
11 changed files with 211 additions and 66 deletions

View File

@ -60,6 +60,8 @@ Material::Material(const XMLNode *node, int index)
node->get("light", &m_lighting );
node->get("sphere", &m_sphere_map );
node->get("friction", &m_friction );
node->get("below-surface", &m_below_surface );
node->get("surface", &m_surface );
node->get("ignore", &m_ignore );
node->get("reset", &m_resetter );
node->get("additive", &m_add );
@ -159,6 +161,8 @@ void Material::init(unsigned int index)
m_backface_culling = true;
m_sphere_map = false;
m_friction = 1.0f;
m_below_surface = false;
m_surface = false;
m_ignore = false;
m_resetter = false;
m_add = false;

View File

@ -55,8 +55,23 @@ private:
* "" if no special sfx exists. */
std::string m_sfx_name;
GraphicalEffect m_graphical_effect;
/** Set if being on this surface means being under some other mesh.
* This is used to simulate that a kart is in water: the ground under
* the water is marked as 'm_below_surface', which will then trigger a raycast
* up to find the position of the actual water surface. */
bool m_below_surface;
/** A material that is a surface only, i.e. the karts can fall through
* but the information is still needed (for GFX mostly). An example is
* a water surface: karts can drive while partly in water (so the water
* surface is not a physical object), but the location of the water
* effect is on the surface. */
bool m_surface;
/** If the material is a zipper, i.e. gives a speed boost. */
bool m_zipper;
/** If a kart is rescued when touching this surface. */
bool m_resetter;
/** If the property should be ignored in the physics. Example would be
* plants that a kart can just drive through. */
bool m_ignore;
bool m_add;
@ -151,6 +166,17 @@ public:
/** Returns true if this material should have smoke effect. */
//bool hasSmoke () const { return m_graphical_effect==GE_SMOKE;}
// ------------------------------------------------------------------------
/** Returns true if this material is under some other mesh and therefore
* requires another raycast to find the surface it is under (used for
* gfx, e.g. driving under water to find where the water splash should
* be shown at. */
bool isBelowSurface () const { return m_below_surface; }
// ------------------------------------------------------------------------
/** Returns true if this material is a surface, i.e. it is going to be
* ignored for the physics, but the information is needed e.g. for
* gfx. See m_below_surface for more details. */
bool isSurface () const { return m_surface; }
// ------------------------------------------------------------------------
/** Returns true if this material should have water splashes. */
bool hasWaterSplash () const { return m_graphical_effect==GE_WATER;}
// ------------------------------------------------------------------------

View File

@ -232,20 +232,21 @@ void Powerup::use()
case PowerupManager::POWERUP_BUBBLEGUM:
{
float up_coord = Track::NOHIT;
Vec3 hit_point;
Vec3 normal;
const Material* unused2;
const Material* material_hit;
btVector3 pos = m_owner->getXYZ();
world->getTrack()->getTerrainInfo(pos, &up_coord, &normal, &unused2);
world->getTrack()->getTerrainInfo(pos, &hit_point, &normal,
&material_hit);
// This can happen if the kart is 'over nothing' when dropping
// the bubble gum
if(up_coord==Track::NOHIT)
if(!material_hit)
return;
normal.normalize();
m_sound_use->position(m_owner->getXYZ());
m_sound_use->play();
pos.setY(up_coord-0.05f);
pos.setY(hit_point.getY()-0.05f);
item_manager->newItem(Item::ITEM_BUBBLEGUM, pos, normal, m_owner);
}

View File

@ -1678,7 +1678,19 @@ void Kart::updateGraphics(const Vec3& offset_xyz,
if (m->hasWaterSplash() && hot != Track::NOHIT && !m_flying)
{
const float hat = getXYZ().getY() - hot;
#ifdef NOT_TESTED_YET
if(m->isBelowSurface())
{
Vec3 pos;
if(getSurfacePosition(&pos))
{
m_water_splash_system->setPosition(pos.toIrrVector());
m_water_splash_system->setCreationRate(
(float)m_water_splash_system->getParticlesInfo()->getMaxRate()
);
}
} // m->isBelowSurface
#endif
// TODO: don't hardcode height, get from exporter
if (hat < 4.0f && hat > 2.0f)
{

View File

@ -441,8 +441,8 @@ void World::resetAllKarts()
{
if(!(*i)->isInRest())
{
float hot;
Vec3 normal;
Vec3 hit_point;
const Material *material;
// We can't use (*i)->getXYZ(), since this is only defined
// after update() was called. Instead we have to get the
@ -451,7 +451,8 @@ void World::resetAllKarts()
(*i)->getBody()->getMotionState()->getWorldTransform(t);
// This test can not be done only once before the loop, since
// it can happen that the kart falls through the track later!
m_track->getTerrainInfo(t.getOrigin(), &hot, &normal, &material);
m_track->getTerrainInfo(t.getOrigin(), &hit_point, &normal,
&material);
if(!material)
{
fprintf(stderr, "ERROR: no valid starting position for kart %d on track %s.\n",

View File

@ -34,6 +34,7 @@ TriangleMesh::TriangleMesh() : m_mesh()
// as m_mesh->m_use32bitIndices and m_use4componentVertices
// (and m_mesh->m_weldingThreshold at m_normals
m_collision_shape = NULL;
m_user_pointer.set(this);
} // TriangleMesh
// -----------------------------------------------------------------------------
@ -71,16 +72,10 @@ void TriangleMesh::addTriangle(const btVector3 &t1, const btVector3 &t2,
} // addTriangle
// -----------------------------------------------------------------------------
/** Creates the physics body for this triangle mesh. If the body already
* exists (because it was created by a previous call to createBody)
* it is first removed from the world. This is used by loading the track
* where a physics body is used to determine the height of terrain. To have
* an optimised rigid body including all static objects, the track is then
* removed and all objects together with the track is converted again into
* a single rigid body. This avoids using irrlicht (or the graphics engine)
* for height of terrain detection).
/** Creates a collision body only, which can be used for raycasting, but
* has no physical properties.
*/
void TriangleMesh::createBody(btCollisionObject::CollisionFlags flags)
void TriangleMesh::createCollisionShape()
{
if(m_triangleIndex2Material.size()==0)
{
@ -91,19 +86,36 @@ void TriangleMesh::createBody(btCollisionObject::CollisionFlags flags)
}
// Now convert the triangle mesh into a static rigid body
m_collision_shape = new btBvhTriangleMeshShape(&m_mesh, true);
m_collision_shape->setUserPointer(&m_user_pointer);
} // createCollisionShape
// -----------------------------------------------------------------------------
/** Creates the physics body for this triangle mesh. If the body already
* exists (because it was created by a previous call to createBody)
* it is first removed from the world. This is used by loading the track
* where a physics body is used to determine the height of terrain. To have
* an optimised rigid body including all static objects, the track is then
* removed and all objects together with the track is converted again into
* a single rigid body. This avoids using irrlicht (or the graphics engine)
* for height of terrain detection).
*/
void TriangleMesh::createPhysicalBody(btCollisionObject::CollisionFlags flags)
{
createCollisionShape();
btTransform startTransform;
startTransform.setIdentity();
m_motion_state = new btDefaultMotionState(startTransform);
btRigidBody::btRigidBodyConstructionInfo info(0.0f, m_motion_state, m_collision_shape);
btRigidBody::btRigidBodyConstructionInfo info(0.0f, m_motion_state,
m_collision_shape);
m_body=new btRigidBody(info);
World::getWorld()->getPhysics()->addBody(m_body);
m_user_pointer.set(this);
m_body->setUserPointer(&m_user_pointer);
m_body->setCollisionFlags(m_body->getCollisionFlags() |
flags |
btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK);
} // createBody
} // createPhysicalBody
// -----------------------------------------------------------------------------
/** Removes the created body from the physics world. This is used when creating

View File

@ -39,6 +39,7 @@ private:
std::vector<const Material*> m_triangleIndex2Material;
btRigidBody *m_body;
btTriangleMesh m_mesh;
btVector3 dummy1, dummy2;
btDefaultMotionState *m_motion_state;
btCollisionShape *m_collision_shape;
/** The three normals for each triangle. */
@ -50,8 +51,9 @@ public:
const btVector3 &t3, const btVector3 &n1,
const btVector3 &n2, const btVector3 &n3,
const Material* m);
void createBody(btCollisionObject::CollisionFlags flags=
(btCollisionObject::CollisionFlags)0);
void createCollisionShape();
void createPhysicalBody(btCollisionObject::CollisionFlags flags=
(btCollisionObject::CollisionFlags)0);
void removeBody();
btVector3 getInterpolatedNormal(unsigned int index,
const btVector3 &position) const;
@ -59,6 +61,12 @@ public:
const Material* getMaterial(int n) const
{return m_triangleIndex2Material[n];}
// ------------------------------------------------------------------------
const btCollisionShape &getCollisionShape() const
{return *m_collision_shape;}
// ------------------------------------------------------------------------
/** Returns the points of the 'indx' triangle.
* \param indx Index of the triangle to get.
* \param p1,p2,p3 On return the three points of the triangle. */
void getTriangle(unsigned int indx, btVector3 *p1, btVector3 *p2,
btVector3 *p3) const
{

View File

@ -22,44 +22,85 @@
#include <math.h>
#include "modes/world.hpp"
#include "physics/triangle_mesh.hpp"
#include "race/race_manager.hpp"
#include "tracks/track.hpp"
#include "utils/constants.hpp"
TerrainInfo::TerrainInfo(int frequency)
/** Constructor to initialise terrain data.
*/
TerrainInfo::TerrainInfo()
{
m_HoT_frequency = frequency;
m_HoT_counter = frequency;
m_last_material = NULL;
}
} // TerrainInfo
//-----------------------------------------------------------------------------
TerrainInfo::TerrainInfo(const Vec3 &pos, int frequency)
/** Constructor to initialise terrain data at a given position
* \param pos The position to get the data from.
*/
TerrainInfo::TerrainInfo(const Vec3 &pos)
{
m_HoT_frequency = frequency;
m_HoT_counter = frequency;
// initialise HoT
update(pos);
}
} // TerrainInfo
//-----------------------------------------------------------------------------
/** Update the terrain information based on the latest position.
* \param Position from which to start the rayast from.
*/
void TerrainInfo::update(const Vec3& pos)
{
m_HoT_counter++;
if(m_HoT_counter>=m_HoT_frequency)
{
m_last_material = m_material;
World::getWorld()->getTrack()->getTerrainInfo(pos, &m_HoT,
&m_normal, &m_material);
m_normal.normalize();
m_HoT_counter = 0;
}
m_last_material = m_material;
World::getWorld()->getTrack()->getTerrainInfo(pos, &m_hit_point,
&m_normal, &m_material);
m_normal.normalize();
} // update
// -----------------------------------------------------------------------------
/** If the raycast indicated that the kart is 'under something' (i.e. a
* specially marked terrain), to another raycast up to detect under whic
* mesh the kart is. This is using the special gfx effect mesh only.
* This is used e.g. to detect if a kart is under water, and then to
* get the proper position for water effects. Note that the TerrainInfo
* objects keeps track of the previous raycast position.
*/
bool TerrainInfo::getSurfacePosition(Vec3 *position)
{
if(m_material && m_material->isBelowSurface())
{
btTransform from;
from.setIdentity();
from.setOrigin(m_hit_point+btVector3(0,5,0));
btTransform to;
to.setIdentity();
to.setOrigin(m_hit_point+btVector3(0, 10000.0f, 0));
btTransform world_trans;
world_trans.setIdentity();
btCollisionWorld::ClosestRayResultCallback
result(from.getOrigin(), to.getOrigin());
const btCollisionShape &shape =
World::getWorld()->getTrack()->getGFXEffectMesh().getCollisionShape();
btCollisionObject col_obj;
btTransform bt;
bt.setIdentity();
col_obj.setWorldTransform(bt);
btCollisionWorld::rayTestSingle(from, to, &col_obj, &shape,
world_trans, result);
if(result.hasHit())
{
*position = result.m_hitPointWorld;
}
return result.hasHit();
}
return false;
} // getSurfacePosition
// -----------------------------------------------------------------------------
/** Returns the pitch of the terrain depending on the heading
*/
float TerrainInfo::getTerrainPitch(float heading) const {
if(m_HoT==Track::NOHIT) return 0.0f;
if(!m_material) return 0.0f;
const float X = sin(heading);
const float Z = cos(heading);

View File

@ -30,32 +30,45 @@
class TerrainInfo
{
private:
int m_HoT_frequency; // how often hight of terrain is computed
int m_HoT_counter; // compute HAT only every N timesteps
Vec3 m_normal; // normal of the triangle under the object
const Material *m_material; // material of the triangle under the object
const Material *m_last_material; // the previous material a kart was on
float m_HoT; // height of terrain
/** Normal of the triangle under the object. */
Vec3 m_normal;
/** Material of the triangle under the object. */
const Material *m_material;
/** The previous material a kart was on. */
const Material *m_last_material;
/** The point that was hit. */
Vec3 m_hit_point;
/** Position of last raycast. */
Vec3 m_last_pos;
public:
TerrainInfo(int frequency=1);
TerrainInfo(const Vec3 &pos, int frequency=1);
TerrainInfo();
TerrainInfo(const Vec3 &pos);
virtual ~TerrainInfo() {};
virtual void update(const Vec3 &pos);
bool getSurfacePosition(Vec3 *position);
// ------------------------------------------------------------------------
/** Returns the height of the terrain. we're currently above */
float getHoT() const {return m_HoT; }
float getHoT() const {return m_hit_point.getY(); }
// ------------------------------------------------------------------------
/** Returns the current material the kart is on. */
const Material *getMaterial() const {return m_material; }
// ------------------------------------------------------------------------
/** Returns the previous material the kart was one (which might be
* the same as getMaterial() ). */
const Material *getLastMaterial() const {return m_last_material;}
// ------------------------------------------------------------------------
/** Returns the normal of the terrain the kart is on. */
const Vec3 &getNormal() const {return m_normal; }
// ------------------------------------------------------------------------
/** Returns the pitch of the terrain depending on the heading. */
float getTerrainPitch(float heading) const;
// ------------------------------------------------------------------------
/** Returns the hit point of the raycast. */
const btVector3& getHitPoint() const { return m_hit_point; }
}; // TerrainInfo
#endif // HEADER_TERRAIN_INFO_HPP

View File

@ -69,6 +69,7 @@ Track::Track(std::string filename)
m_screenshot = "";
m_version = 0;
m_track_mesh = new TriangleMesh();
m_gfx_effect_mesh = new TriangleMesh();
m_all_nodes.clear();
m_all_meshes.clear();
m_is_arena = false;
@ -93,6 +94,7 @@ Track::~Track()
if(m_mini_map) irr_driver->removeTexture(m_mini_map);
if(m_sky_particles_emitter) delete m_sky_particles_emitter;
delete m_track_mesh;
delete m_gfx_effect_mesh;
} // ~Track
//-----------------------------------------------------------------------------
@ -164,6 +166,9 @@ void Track::cleanup()
delete m_track_mesh;
m_track_mesh = new TriangleMesh();
delete m_gfx_effect_mesh;
m_gfx_effect_mesh = new TriangleMesh();
// remove temporary materials loaded by the material manager
material_manager->popTempMaterial();
} // cleanup
@ -352,8 +357,8 @@ void Track::createPhysicsModel(unsigned int main_track_count)
{
convertTrackToBullet(m_all_nodes[i]);
}
m_track_mesh->createBody();
m_track_mesh->createPhysicalBody();
m_gfx_effect_mesh->createCollisionShape();
} // createPhysicsModel
// -----------------------------------------------------------------------------
@ -432,8 +437,11 @@ void Track::convertTrackToBullet(scene::ISceneNode *node)
{
std::string image = std::string(core::stringc(t->getName()).c_str());
material=material_manager->getMaterial(StringUtils::getBasename(image));
// Materials to be ignored are not converted into bullet meshes.
if(material->isIgnore()) continue;
// Special gfx meshes will not be stored as a normal physics body,
// but converted to a collision body only, so that ray tests
// against them can be done.
if(material->isSurface())
tmesh = m_gfx_effect_mesh;
}
u16 *mbIndices = mb->getIndices();
@ -567,8 +575,8 @@ bool Track::loadMainTrack(const XMLNode &root)
exit(-1);
}
m_track_mesh->createBody();
m_track_mesh->createPhysicalBody();
m_gfx_effect_mesh->createCollisionShape();
scene_node->setMaterialFlag(video::EMF_LIGHTING, true);
scene_node->setMaterialFlag(video::EMF_GOURAUD_SHADING, true);
@ -1115,7 +1123,16 @@ void Track::itemCommand(const Vec3 &xyz, Item::ItemType type,
} // itemCommand
// ----------------------------------------------------------------------------
void Track::getTerrainInfo(const Vec3 &pos, float *hot, Vec3 *normal,
/** Does a ray cast straight down from the given position, and returns
* the hit point, material, and normal at the given location (or material
* ==NULL if no triangle was hit).
* \param pos Positions from which to cast the ray.
* \param hit_point On return: the position which was hit.
* \param normal On return the normal of the triangle at that point.
* \param material The material at the hit point (or NULL if no material
* was found.
*/
void Track::getTerrainInfo(const Vec3 &pos, Vec3 *hit_point, Vec3 *normal,
const Material **material) const
{
btVector3 to_pos(pos);
@ -1152,14 +1169,13 @@ void Track::getTerrainInfo(const Vec3 &pos, float *hot, Vec3 *normal,
if(!rayCallback.hasHit())
{
*hot = NOHIT;
*material = NULL;
return;
}
*hot = rayCallback.m_hitPointWorld.getY();
*normal = rayCallback.m_hitNormalWorld;
*material = rayCallback.m_material;
*hit_point = rayCallback.m_hitPointWorld;
*normal = rayCallback.m_hitNormalWorld;
*material = rayCallback.m_material;
// Note: material might be NULL. This happens if the ray cast does not
// hit the track, but another rigid body (kart, moving_physics) - e.g.
// assume two karts falling down, one over the other. Bullet does not
@ -1174,9 +1190,9 @@ void Track::getTerrainInfo(const Vec3 &pos, float *hot, Vec3 *normal,
*/
float Track::getTerrainHeight(const Vec3 &pos) const
{
float hot;
Vec3 hit_point;
Vec3 normal;
const Material *m;
getTerrainInfo(pos, &hot, &normal, &m);
return hot;
getTerrainInfo(pos, &hit_point, &normal, &m);
return hit_point.getY();
} // getTerrainHeight

View File

@ -79,9 +79,16 @@ private:
std::vector<std::string> m_groups;
std::vector<scene::ISceneNode*> m_all_nodes;
std::vector<scene::IMesh*> m_all_meshes;
PtrVector<ParticleEmitter> m_all_emitters;
PtrVector<ParticleEmitter> m_all_emitters;
scene::ILightSceneNode *m_sun;
/** Used to collect the triangles for the bullet mesh. */
TriangleMesh* m_track_mesh;
/** Used to collect the triangles which do not have a physical
* representation, but are needed for some raycast effects. An
* example is a water surface: the karts ignore this (i.e.
* allowing the kart to drive in/partly under water), but the
* actual surface position is needed for the water splash effect. */
TriangleMesh* m_gfx_effect_mesh;
/** Minimum coordinates of this track. */
Vec3 m_aabb_min;
/** Maximum coordinates of this track. */
@ -229,7 +236,8 @@ public:
/** Starts the music for this track. */
void startMusic () const;
void getTerrainInfo(const Vec3 &pos, float *hot, Vec3* normal,
void getTerrainInfo(const Vec3 &pos, Vec3 *hit_point,
Vec3* normal,
const Material **material) const;
float getTerrainHeight(const Vec3 &pos) const;
void createPhysicsModel(unsigned int main_track_count);
@ -303,6 +311,9 @@ public:
/** Returns the triangle mesh for this track. */
const TriangleMesh& getTriangleMesh() const {return *m_track_mesh; }
// ------------------------------------------------------------------------
/** Returns the graphical effect mesh for this track. */
const TriangleMesh& getGFXEffectMesh() const {return *m_gfx_effect_mesh;}
// ------------------------------------------------------------------------
/** Get the number of start positions defined in the scene file. */
unsigned int getNumberOfStartPositions() const
{ return m_start_transforms.size(); }