// Copyright (c) 1999 Philip A. Hardin (pahardin@cs.utexas.edu) // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License v2 or later. #include "gob.h" // Notes: // gob is an abbreviation for Game OBject // STL is an abbreviation for the Standard Template Library // static data members FriendlyFire gob::ffire= dangerous; bool gob::fancy= true; bool gob::classSolid= true; coord gob::gravity= -1./32; // gravitational acceleration gobList* gob::gobs= NULL; /*=========================================================================*/ // gobList class gob* gobList::Update(gob* gobPtr, gobList::iterator gi) { if (gobPtr != *gi) // if the new gob ptr is different from the orig one, delete *gi; // delete the orig gob if (gobPtr) // if new gob ptr is non-NULL, *gi= gobPtr; // replace orig gob ptr w/ new gob ptr else // else erase(gi); // erase the orig gob ptr from the gobList return gobPtr; } /*=========================================================================*/ // the newVel.Scale() is overriden to be zero. gob::gob(const tcomp& newPos, const tcomp& newVel, gob *newParent) : pos(newPos), vel(newVel), parent(newParent), solid(classSolid), lastPosAng(pos.Ang()+ang3d(1,1,1)) { vel.Scale()= pt3d(0,0,0); UpdateWorldPos(); } //--------------------------------------------------------------------------- /* Make this gob "act" for one game iteration. "Acting" typically includes one or more of the following: o morphing the gob (changing its shape or class) o setting the gob's velocity o moving the gob o interacting with other gobs Act() returns: o NULL, if the gob is destroyed, o a ptr to a new gob, if the gob becomes a different object (as in the tank to helicopter transformation), or o the "this" ptr, otherwise */ gob* gob::Act() { vel.Scale()= pt3d(0,0,0); // for right now... -PAH pos += vel; return Interact(); } //--------------------------------------------------------------------------- // Returns the center of the bounding box around this gob, in world coords pt3d gob::Center() const { return Shape()->Box().Center() * WorldPos(); } //--------------------------------------------------------------------------- // Returns true if globalPt lies inside of this gob. // Implemented to test against the gob's bounding box, but can be overridden // in derived classes with a more accurate test if necessary. // In: inclusive = true -> consider a pt on the surface contained // = false -> consider a pt on the surface not contained bool gob::Contains(const pt3d& globalPt, bool inclusive) { if (inclusive) return Shape()->Box().ContainsInclusively(globalPt << WorldPos()); else return Shape()->Box().ContainsExclusively(globalPt << WorldPos()); } //--------------------------------------------------------------------------- // Returns the distance from a viewer's eye position to this gob. // In: eyePos = viewer's eye position, in world coords coord gob::DistFromViewer(const pt3d& eyePos) const { pt3d center= Shape()->Box().Center() >> WorldPos(); // in world coords return (eyePos - center).Dist(); // or use FastDist()? } //--------------------------------------------------------------------------- // Draw this gob. // In: worldToView = a world-to-view coordinates transformation matrix // viewNum = which view cache to use (helps performance) // eyePos = viewer's eye position, in world coords // colors = the color table to use when drawing void gob::Draw(bbGfxTarget& gt, tmtrx& worldToView, int viewNum, const pt3d& eyePos, const ulong colors[]) { while (views.size() <=viewNum) views.push_back(view()); views[viewNum].Update(Shape(),WorldPos()*worldToView); gt.SetForeground(gt.Color(Color())); Shape()->Draw(views[viewNum].pts, gt, eyePos << WorldPos(), //pt3d(0,0,0) << views[viewNum].modelToView, gt.gfxSize, Solid(), colors ? colors : gt.Colors()); gt.SetForeground(gt.Black()); } //--------------------------------------------------------------------------- /* GetViewOrder() This function adds this gob's ptr to a list of gobs to be drawn. If this gob has any child gobs (i.e. if this is a multi-part gob), the childrens' ptrs are added to the list too. The list of gobs will be drawn beginning with the last gob first. The purpose of this function is to allow multi-part gobs to be drawn correctly. The eye position can be checked to decide whether this gob should be drawn first, or whether its part(s) should be drawn first. This essentially allows a BSP tree to implicitly embedded into multi-part gobs... */ //--------------------------------------------------------------------------- /* HitByBall(ballGob*) HitByShll(shllGob*) HitByVhcl(vhclGob*) These functions detect and handle collisions between this gob and the ball gob, a shell gob, and a vehicle gob. By default, the base gob class does not detect collisions with balls/ shells/vehicles. To make a derived gob class detect and handle these collisions, override the appropriate HitBy*() function(s). The HitBy*() functions return two gob ptrs, one for this gob and one for the "hitter" gob. These returned ptrs have the same semantics as the ptr returned by Act(). */ //--------------------------------------------------------------------------- /* Interact after moving. This function is called by Act() after a gob is moved, so that the gob may react appropriately to the results of its motion. Typically, Interact() will: o Adjust the gob's position if it passes some intrinsic limit. For example, the angular position of the tank's gun barrel is intrinsically limited to be between 0 and 25 degrees vertically. o Update the gob's world position (i.e. its model-to-world transformation matrix) o Test for interaction with other gobs. This step requires that the gob's world position is up-to-date. Upon entrance to Interact(), the gob's world position (worldPos) is typically *not* up-to-date. Upon exit, Interact() must ensure that worldPos consistent with pos and the worldPos of this gob's parent. Interact()'s return value has the same semantics as Act()'s return value. */ gob* gob::Interact() { UpdateWorldPos(); return this; } //--------------------------------------------------------------------------- // Tests if this gob and another gob have collided, and applies a "kick" if // they have. The other gob is usually the ball gob. // Returns true if a kick was applied. bool gob::KickIfHit(gob *ball, coord maxVel, coord zvel, coord acc, bool kickBoth) { pt3d ballPos= ball->WorldPos().Cart(); ballPos.z += ball->Shape()->Box().Center().z; if (Contains(ballPos)) { pt3d selfToBall= ball->WorldPos().Cart() -WorldPos().Cart(); pt3d kick= selfToBall; if (zvel==0) kick.z= zvel; kick.Normalize(acc); if (zvel != 1 and Altitude() <0.001) kick.z= zvel; if (kick.Dot(ball->vel.Cart()) vel.Cart() += kick; if (kickBoth and (-kick).Dot(vel.Cart()) FarthestDist(); // approx. radius of self //assert(R >0); // compute distance from shell's (infinite) path to center of self pt2d shellToSelf= pos.Cart() -shell->pos.Cart(); pt2d shellVelPerp= shell->vel.Cart().pt2d::Left90().Normalized(); coord d= shellToSelf.Dot( shellVelPerp ); if (d >R) d= R; // use assert here? if (d <-R) d= -R; double torque= d/R; pt3d push= shell->vel.Cart().Normalized((1 -ABS(torque)) *pushMult); if (groundBounce != 0 and Altitude() <0.001) push.z= groundBounce; ang2d xyspin= (ang2d) ((MA_PI/64) *torque *spinMult); vel.Ang().xy += xyspin; vel.Cart() += push; } //--------------------------------------------------------------------------- // TeamNum() returns the number of the team associated with this gob, or -1 // if this gob isn't associated with any team. //--------------------------------------------------------------------------- // Bounce this gob against the ground, if its altitude drops below zero. void gob::TestForGroundBounce(const pt3d& bounceFactor, bool wantUpdate) { coord altitude= Altitude(); if (altitude <0) { pos.Cart().z -= 2*altitude; // bounce off ground vel.Cart().x *= bounceFactor.x; vel.Cart().y *= bounceFactor.y; vel.Cart().z *= bounceFactor.z; if (vel.Cart().z +2*gravity <= 0) { vel.Cart().z= 0; pos.Cart().z= -Shape()->Box().low.z; } if (wantUpdate) UpdateWorldPos(); } } //--------------------------------------------------------------------------- // Update this gob's world position, i.e. its model-to-world transformation // matrix. void gob::UpdateWorldPos() { if (parent) worldPos= pos * parent->WorldPos(); else { // this test saves computation time, but is a bit of a kludge... -PAH if (pos.Ang()==lastPosAng) worldPos.Cart()= pos.Cart(); else { worldPos= pos; lastPosAng= pos.Ang(); } } } //--------------------------------------------------------------------------- // Returns a gob's view position & direction in world coordinates tmtrx gob::ViewPos() const { return ModelViewPos() * WorldPos(); } //--------------------------------------------------------------------------- // Returns true if this gob is hit by a ray which starts at "startPos" and // points in direction "dir". Not too exact, and only considers 2-D. // Used to determine when a vehicle has the ball lined up with a building. bool gob::WillBeHitBy(const pt3d& startPos, const pt3d& dir) const { pt3d low= Shape()->Box().low; pt3d high= Shape()->Box().high; pt2d p1= pt3d(high.x,low.y,0) * WorldPos() -startPos; pt2d p2= pt3d(high.x,high.y,0) * WorldPos() -startPos; // This gob won't be hit if it isn't in "front" of the projectile if (dir.pt2d::Dot(p1) <0 and dir.pt2d::Dot(p2) <0) return false; // This gob will be hit if the projectile's path passes between the two // corners on this gob's front pt2d left= dir.Left90(); return (left.Dot(p1) >0) != (left.Dot(p2) >0); } //=========================================================================== // Draw a region2d. // off is in screen coords. void Draw(rgn2 *rgn, gfxTarget& gt, const pt2d& off, ang2d ang, const pt2d& sc, bool solid) { #ifdef __GNUC__ pt2d pts[100]; XPoint xpts[100]; #else pt2d *pts= new pt2d[rgn->numSides]; XPoint *xpts= new XPoint[rgn->numSides+1]; #endif rgn->TransformTo(off,ang,sc,pts); forii(rgn->numSides) { xpts[i].x= (short) pts[i].x; xpts[i].y= (short) pts[i].y; } xpts[rgn->numSides].x= (short) pts[0].x; xpts[rgn->numSides].y= (short) pts[0].y; if (solid) XFillPolygon(gt.disp,gt.win,gt.gc,xpts,rgn->numSides+1,Nonconvex, CoordModeOrigin); else XDrawLines(gt.disp,gt.win,gt.gc,xpts,rgn->numSides+1,CoordModeOrigin); #ifndef __GNUC__ delete [] xpts; delete [] pts; #endif } //--------------------------------------------------------------------------- // Simulates the motion of a shell and a target, and returns info about the // closest point of approach. The shell is influenced by gravity, but the // target is not. void GetClosestApproach(const pt3d& shellInitPos, const pt3d& shellInitVel, const pt3d& tgtInitPos, const pt3d& tgtInitVel, coord grav, int maxShotTime, coord& closestDist, double& closestTime, pt3d& closestShellPos, pt3d& closestTgtPos) { closestDist= 1000; // one kilometer! closestTime= 0; pt3d tgtPos= tgtInitPos; pt3d shellPos= shellInitPos; pt3d shellVel= shellInitVel; forii(maxShotTime) { coord d= (tgtPos-shellPos).Dist(); if (d