//////////////////////////////////////////////////////////////////////////////// // Scorched3D (c) 2000-2003 // // This file is part of Scorched3D. // // Scorched3D is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // Scorched3D is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Scorched3D; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include static ScorchedContext *staticContext_ = 0; HeightMapCollision::HeightMapCollision(ScorchedContext *context) : landscapeClass_(0), context_(context), groundInfo_(CollisionIdGround), wallInfoTop_(CollisionIdWallTop), wallInfoBottom_(CollisionIdWallBottom), wallInfoLeft_(CollisionIdWallLeft), wallInfoRight_(CollisionIdWallRight), info_(CollisionIdLandscape), sidea_(0), sideb_(0), sidec_(0), sided_(0) { create(); } HeightMapCollision::~HeightMapCollision() { } void HeightMapCollision::setContext(ScorchedContext *context) { staticContext_ = context; } void HeightMapCollision::create() { PhysicsEngine &engine = context_->actionController->getPhysics(); // Create the walls and ground // Create the ground { dGeomID ground = dCreatePlane(engine.getGroundSpace(),0,0,1,0); dGeomSetData(ground, (void *) &groundInfo_); } // Create the land scape stuff dGeomClass c; c.bytes = 0; c.collider = &dLandscapeGetColliderFn; c.aabb = &dInfiniteAABB; c.aabb_test = &dCollideLAABB; c.dtor = 0; landscapeClass_ = dCreateGeomClass(&c); dGeomID geom = dCreateGeom(landscapeClass_); dGeomSetData(geom, (void *) &info_); dSpaceAdd(engine.getGroundSpace(), geom); } void HeightMapCollision::generate() { if (sidea_) { dGeomDestroy(sidea_); dGeomDestroy(sideb_); dGeomDestroy(sidec_); dGeomDestroy(sided_); sidea_ = 0; sideb_ = 0; sidec_ = 0; sided_ = 0; } if (context_->optionsGame->getWallType() != OptionsGame::WallNone) { PhysicsEngine &engine = context_->actionController->getPhysics(); // Create the side walls { sidea_ = dCreatePlane(engine.getGroundSpace(),0,1,0,5); dGeomSetData(sidea_, (void *) &wallInfoTop_); } { int mapHeight = context_->landscapeMaps->getGroundMaps().getMapHeight(); sideb_ = dCreatePlane(engine.getGroundSpace(),0,-1,0,-mapHeight + 5); dGeomSetData(sideb_, (void *) &wallInfoBottom_); } { sidec_ = dCreatePlane(engine.getGroundSpace(),1,0,0,5); dGeomSetData(sidec_, (void *) &wallInfoLeft_); } { int mapWidth = context_->landscapeMaps->getGroundMaps().getMapWidth(); sided_ = dCreatePlane(engine.getGroundSpace(),-1,0,0,-mapWidth + 5); dGeomSetData(sided_, (void *) &wallInfoRight_); } } } dColliderFn * HeightMapCollision::dLandscapeGetColliderFn(int num) { if (num == dSphereClass) return (dColliderFn *) &dCollideLS; //if (num == dBoxClass) return (dColliderFn *) &dCollideLB; return 0; } // Checks for collision between a Landscape and an AABB. int HeightMapCollision::dCollideLAABB(dGeomID o1, dGeomID o2, dReal aabb2[6]) { // This function checks for contact between the Landscape and an AABB. // It checks a larger region of terrain than strictly necessary and // will sometimes return true even if there is no collision, but will never // return false if there is a collision. // If the AABB is below the terrain it will also return true. // Check what we have collided with // We are only interested in Shots, FallingTanks and Sprites // as all other objects are static to the landscape and should // not be checked for collisions ScorchedCollisionInfo *info1 = (ScorchedCollisionInfo *) dGeomGetData(o1); ScorchedCollisionInfo *info2 = (ScorchedCollisionInfo *) dGeomGetData(o2); DIALOG_ASSERT(info1 && info2); if (info2->id != CollisionIdShot && info2->id != CollisionIdFallingTank && info2->id != CollisionIdBounce && info2->id != CollisionIdSprite && info1->id != CollisionIdShot && info1->id != CollisionIdFallingTank && info1->id != CollisionIdBounce && info1->id != CollisionIdSprite) { return 0; } // Now we have something we should actualy be checking the collision // for get the extents of the bounding box int minX = (int)floor(aabb2[0]); int maxX = (int)ceil(aabb2[1]); int minY = (int)floor(aabb2[2]); int maxY = (int)ceil(aabb2[3]); // See if any heights in this area are in the bounding box for (int x=minX; x<=maxX; x++) { for (int y=minY; y<=maxY; y++) { if (info2->collisionOnSurface && info1->collisionOnSurface) { if (staticContext_->landscapeMaps->getGroundMaps(). getHeight(x, y) > aabb2[4]) return 1; } else { if (staticContext_->landscapeMaps->getGroundMaps(). getHeight(x, y) < aabb2[5]) return 1; } } } return 0; } // Checks for collision between a Landscape and a Sphere int HeightMapCollision::dCollideLS (dGeomID o1, dGeomID o2, int flags, dContactGeom *basecontact, int skip) { ScorchedCollisionInfo *info2 = (ScorchedCollisionInfo *) dGeomGetData(o2); DIALOG_ASSERT(info2); // Only use the sphere collision for shots and falling tanks int num_contacts = 0; if (info2->id == CollisionIdShot || info2->id == CollisionIdBounce || info2->id == CollisionIdFallingTank) { // Do we actually need to do this?? // This is very intensive for the actual precision we need // we could probably get away with the sphere center check! num_contacts = dCollideLSSphere(o1, o2, flags, basecontact, skip); } // Now check the actual center of the sphere is not in the landscape if (!num_contacts) { // Get the centre and radius of the sphere const dReal *centre = dGeomGetPosition(o2); float radius = (float) dGeomSphereGetRadius(o2); Vector sphereCentre; sphereCentre[0] = (float) centre[0]; sphereCentre[1] = (float) centre[1]; sphereCentre[2] = (float) centre[2]; // Check if the center of the spehere is in the landscape float dist = sphereCentre[2] - staticContext_->landscapeMaps->getGroundMaps(). getInterpHeight(sphereCentre[0], sphereCentre[1]); bool landscapeCollision = false; if (info2->collisionOnSurface) { landscapeCollision = (dist < 0.0f); } else { landscapeCollision = (dist > 0.0f); } if (landscapeCollision) { Vector interN; staticContext_->landscapeMaps->getGroundMaps(). getInterpNormal(sphereCentre[0], sphereCentre[1], interN); char *ptr = (char *) basecontact; ptr += (num_contacts * skip); dContactGeom *contact = (dContactGeom *) ptr; // This is probably not correct, but it is close // enough + nothing currently uses the collision info contact->depth = -dist; contact->normal[0] = -interN[0]; contact->normal[1] = -interN[1]; contact->normal[2] = -interN[2]; contact->pos[0] = sphereCentre[0] - (interN[0] * radius); contact->pos[1] = sphereCentre[1] - (interN[1] * radius); contact->pos[2] = sphereCentre[2] - (interN[2] * radius); num_contacts++; } } return num_contacts; } int HeightMapCollision::dCollideLSSphere (dGeomID o1, dGeomID o2, int flags, dContactGeom *basecontact, int skip) { // Get the centre & radius of the sphere const dReal *centre = dGeomGetPosition(o2); Vector sphereCentre; sphereCentre[0] = (float) centre[0]; sphereCentre[1] = (float) centre[1]; sphereCentre[2] = (float) centre[2]; float radius = (float) dGeomSphereGetRadius(o2); // Get the bounding box of the sphere dReal aabb2[6]; dGeomGetAABB (o2, aabb2); // Get the extents of the bounding box int minX = (int)floor(aabb2[0]); int maxX = (int)ceil(aabb2[1]); int minY = (int)floor(aabb2[2]); int maxY = (int)ceil(aabb2[3]); // Check all squares for contacts int num_contacts = 0; for (int x=minX; x<=maxX; x++) { for (int y=minY; y<=maxY; y++) { static Triangle triA; triA.setPointComponents( float(x), float(y), staticContext_->landscapeMaps->getGroundMaps().getHeight(x, y), staticContext_->landscapeMaps->getGroundMaps().getNormal(x, y), float(x + 1), float(y), staticContext_->landscapeMaps->getGroundMaps().getHeight(x + 1, y), staticContext_->landscapeMaps->getGroundMaps().getNormal(x+1, y), float(x + 1), float(y + 1), staticContext_->landscapeMaps->getGroundMaps().getHeight(x + 1, y + 1), staticContext_->landscapeMaps->getGroundMaps().getNormal(x+1, y+1)); static Triangle triB; triB.setPointComponents( float(x + 1), float(y + 1), staticContext_->landscapeMaps->getGroundMaps().getHeight(x + 1, y + 1), staticContext_->landscapeMaps->getGroundMaps().getNormal(x+1, y+1), float(x), float(y + 1), staticContext_->landscapeMaps->getGroundMaps().getHeight(x, y + 1), staticContext_->landscapeMaps->getGroundMaps().getNormal(x, y+1), float(x), float(y), staticContext_->landscapeMaps->getGroundMaps().getHeight(x, y), staticContext_->landscapeMaps->getGroundMaps().getNormal(x, y)); static Vector newInter, interN; float dist; if (triA.sphereIntersect(sphereCentre, radius, newInter, interN, dist)) { char *ptr = (char *) basecontact; ptr += (num_contacts * skip); dContactGeom *contact = (dContactGeom *) ptr; contact->depth = dist; contact->normal[0] = -interN[0]; contact->normal[1] = -interN[1]; contact->normal[2] = -interN[2]; contact->pos[0] = sphereCentre[0] - (interN[0] * radius); contact->pos[1] = sphereCentre[1] - (interN[1] * radius); contact->pos[2] = sphereCentre[2] - (interN[2] * radius); num_contacts++; } if (triB.sphereIntersect(sphereCentre, radius, newInter, interN, dist)) { char *ptr = (char *) basecontact; ptr += (num_contacts * skip); dContactGeom *contact = (dContactGeom *) ptr; contact->depth = dist; contact->normal[0] = -interN[0]; contact->normal[1] = -interN[1]; contact->normal[2] = -interN[2]; contact->pos[0] = sphereCentre[0] - (interN[0] * radius); contact->pos[1] = sphereCentre[1] - (interN[1] * radius); contact->pos[2] = sphereCentre[2] - (interN[2] * radius); num_contacts++; } if (num_contacts > 3) break; } } return num_contacts; }