diff --git a/src/graphics/material.cpp b/src/graphics/material.cpp index 536dc2983..e64cd296d 100644 --- a/src/graphics/material.cpp +++ b/src/graphics/material.cpp @@ -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; diff --git a/src/graphics/material.hpp b/src/graphics/material.hpp index f6682c27b..010e23ee1 100644 --- a/src/graphics/material.hpp +++ b/src/graphics/material.hpp @@ -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;} // ------------------------------------------------------------------------ diff --git a/src/items/powerup.cpp b/src/items/powerup.cpp index a76685b48..0eb8319e4 100644 --- a/src/items/powerup.cpp +++ b/src/items/powerup.cpp @@ -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); } diff --git a/src/karts/kart.cpp b/src/karts/kart.cpp index d472ba53e..9471bd678 100644 --- a/src/karts/kart.cpp +++ b/src/karts/kart.cpp @@ -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) { diff --git a/src/modes/world.cpp b/src/modes/world.cpp index bd9c5c20a..a7e737b7f 100644 --- a/src/modes/world.cpp +++ b/src/modes/world.cpp @@ -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", diff --git a/src/physics/triangle_mesh.cpp b/src/physics/triangle_mesh.cpp index 53e788ecd..f26e949c6 100644 --- a/src/physics/triangle_mesh.cpp +++ b/src/physics/triangle_mesh.cpp @@ -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 diff --git a/src/physics/triangle_mesh.hpp b/src/physics/triangle_mesh.hpp index 5fdec8f1e..c696d1baf 100644 --- a/src/physics/triangle_mesh.hpp +++ b/src/physics/triangle_mesh.hpp @@ -39,6 +39,7 @@ private: std::vector 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 { diff --git a/src/tracks/terrain_info.cpp b/src/tracks/terrain_info.cpp index 846ff604a..f9968e0e1 100644 --- a/src/tracks/terrain_info.cpp +++ b/src/tracks/terrain_info.cpp @@ -22,44 +22,85 @@ #include #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); diff --git a/src/tracks/terrain_info.hpp b/src/tracks/terrain_info.hpp index 20615a17f..552779bca 100644 --- a/src/tracks/terrain_info.hpp +++ b/src/tracks/terrain_info.hpp @@ -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 diff --git a/src/tracks/track.cpp b/src/tracks/track.cpp index 1b7600126..3cc2b88d8 100644 --- a/src/tracks/track.cpp +++ b/src/tracks/track.cpp @@ -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 diff --git a/src/tracks/track.hpp b/src/tracks/track.hpp index 80ab3d201..9bd572dd9 100644 --- a/src/tracks/track.hpp +++ b/src/tracks/track.hpp @@ -79,9 +79,16 @@ private: std::vector m_groups; std::vector m_all_nodes; std::vector m_all_meshes; - PtrVector m_all_emitters; + PtrVector 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(); }