//////////////////////////////////////////////////////////////////////////////// // Scorched3D (c) 2000-2004 // // 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 //////////////////////////////////////////////////////////////////////////////// /* * Copyright (C) 1996, Christopher John Kline * Electronic mail: ckline@acm.org * * This software may be freely copied, modified, and redistributed * for academic purposes and by not-for-profit organizations, provided that * this copyright notice is preserved on all copies, and that the source * code is included or notice is given informing the end-user that the source * code is publicly available under the terms described here. * * Persons or organizations wishing to use this code or any modified version * of this code in a commercial and/or for-profit manner must contact the * author via electronic mail (preferred) or other method to arrange the terms * of usage. These terms may be as simple as giving the author visible credit * in the final product. * * There is no warranty or other guarantee of fitness for this software, * it is provided solely "as is". Bug reports or fixes may be sent * to the author, who may or may not act on them as he desires. * * If you use this software the author politely requests that you inform him via * electronic mail. * */ // Boid.c++ // // CODE: Class for objects that move with A-Life boid-type behavior. // // (c) 1996 Christopher Kline // #ifndef __BOID_C #define __BOID_C #include "Boid.h" #include // Returns the value in the visibility matrix which determines if this boid // can see the "otherBoid". A value of -1 means that otherBoid cannot be // seen; any other value indicates that otherBoid _can_ be seen, and the // value is equal to the otherBoid's boidType. #define visMatrix(otherBoid) \ (world->getVisibilityMatrix()[boidNumber-1][(otherBoid)->boidNumber-1]) void Boid::calculateVisibilityMatrix(void) { int val; // Foreach boid, if it's not visible to us, put a a -1 in the // matrix. Otherwise put the boidType in the matrix. for (int i=0; igetBoidCount(); i++) { Boid *n = world->getBoids()[i]; // Can't see self! if (n == this) { val = -1; } else { val = (visibleToSelf(n) ? n->boidType : -1); } visMatrix(n) = val; } } double Boid::accumulate(BoidVector &accumulator, BoidVector valueToAdd) { double magnitudeRemaining = 1.0 - Magnitude(accumulator); double vMag = Magnitude(valueToAdd); double newMag = vMag < magnitudeRemaining ? vMag : magnitudeRemaining; accumulator += (Direction(valueToAdd) * newMag); return newMag; } BoidVector Boid::navigator(void) { // Calculate the visibility matrix so that visibility computations are // much more efficient. calculateVisibilityMatrix(); BoidVector vacc(0, 0, 0); // BoidVector accumulator if (accumulate(vacc, collisionAvoidance()) >= 1.0) goto MAXACCEL_ATTAINED; if (world->getBoidCount() > 1) { if (accumulate(vacc, flockCentering()) >= 1.0) goto MAXACCEL_ATTAINED; if (accumulate(vacc, maintainingCruisingDistance()) >= 1.0) goto MAXACCEL_ATTAINED; if (accumulate(vacc, velocityMatching()) >= 1.0) goto MAXACCEL_ATTAINED; } if (accumulate(vacc, wander()) >= 1.0) goto MAXACCEL_ATTAINED; if (accumulate(vacc, levelFlight(vacc)) >= 1.0) goto MAXACCEL_ATTAINED; MAXACCEL_ATTAINED: /* label */ // IMPORTANT: // Since the FlockCentering, CollisionAvoidance, and VelocityMatching modules // return a BoidVector whose magnitude is a percentage of the maximum acceleration, // we need to convert this to a real magnitude before we hand it off to the Flight // module. // Remember, maxAcceleration is in terms of a fraction (0.0 to 1.0) of maxVelocity vacc *= world->getMaxAcceleration() * world->getMaxVelocity(); return(vacc); } void Boid::calculateRollPitchYaw(BoidVector appliedAcceleration, BoidVector currentVelocity, BoidVector /*currentPosition*/) { // NOTES: // // 1) currentPosition (the third argument) is unused, though in a more // advanced flight model it could make a difference (some flight dynamics // change depending on altitude, for example) // // 2) Dr. Craig Wanke of the Mitre Corporation in McLean, Virginia helped me // tremendously when working out these equations. Thanks, Craig! // In our simple flight model, roll only a function of lateral // (centripedal) acceleration and gravitational acceleration. We assume // that gravitational acceleration will NOT be zero. // Determine the direction of the lateral acceleration. BoidVector lateralDir = Direction((currentVelocity % appliedAcceleration) % currentVelocity); // Set the lateral acceleration's magnitude. The magnitude is the BoidVector // projection of the appliedAcceleration BoidVector onto the direction of the // lateral acceleration). double lateralMag = appliedAcceleration * lateralDir; if (lateralMag == 0) roll = 0; else roll = -atan2(getGravAcceleration(), lateralMag) + 3.14/2; pitch = -atan(currentVelocity.y / sqrt(currentVelocity.z*currentVelocity.z + currentVelocity.x*currentVelocity.x)); yaw = atan2(currentVelocity.x, currentVelocity.z); } void Boid::updateslow() { dampedroll = (dampedroll + roll) / 2.0; } BoidVector Boid::wander(void) { double distanceFromCruiseSpeed = (Magnitude(velocity) - desiredCruisingSpeed()) / world->getMaxVelocity(); double urgency = fabs(distanceFromCruiseSpeed); if (urgency > 0.25) urgency = 0.25; return Direction(velocity) * urgency * (distanceFromCruiseSpeed > 0 ? -1 : 1); } BoidVector Boid::collisionAvoidance(void) { ISectData d; BoidVector normalToObject(0, 0, 0); int objectSeen = 0; BoidVector pointOnObject; // Ignore obstacles that are out of the range of our probe. double distanceToObject = getProbeLength(); // Find closest imminent collision with non-boid object for (unsigned int o=0; ogetObstacles().size(); o++) { Obstacle *obs = world->getObstacles()[o]; d = obs->DoesRayIntersect(Direction(velocity), position); if (d.intersectionflag == 1) { // Velocity BoidVector intersects an obstacle if (Magnitude(d.point-position) <= distanceToObject) { // found a closer object objectSeen = 1; distanceToObject = Magnitude(d.point-position); normalToObject = d.normal; pointOnObject = d.point; } } } if (!objectSeen) { return BoidVector(0,0,0); } return resolveCollision(pointOnObject, normalToObject); } BoidVector Boid::resolveCollision(BoidVector pointOnObject, BoidVector normalToObject) { double distanceToObject = Magnitude(pointOnObject - position); // Make sure the object's normal is pointing towards the boid. // The boid wants to head in the direction of the normal, which // should push it away from the obstacle if the normal is pointing // towards the boid. if (AngleBetween(velocity, normalToObject) < 3.14/2) normalToObject = - normalToObject; double mag = 1 - distanceToObject/getProbeLength(); // Ignore objects that are farther away than our probe. if (mag < 0) mag = 0; return Direction(normalToObject) * mag; } BoidVector Boid::maintainingCruisingDistance(void) { double distanceToClosestNeighbor = DBL_MAX; // DBL_MAX defined in int foundClosestNeighbor = 0; Boid *n, *closestNeighbor; double tempDistance; for (int i=0; igetBoidCount(); i++) { n = world->getBoids()[i]; // Skip boids that we don't need to consider if (visMatrix(n) == -1) continue; if (visMatrix(n) != boidType && flockSelectively) continue; // Find distance from the current boid to self tempDistance = Magnitude(n->position - position); // remember distance to closest boid if (tempDistance < distanceToClosestNeighbor) { distanceToClosestNeighbor = tempDistance; foundClosestNeighbor = 1; closestNeighbor = n; } } BoidVector speedAdjustmentBoidVector(0, 0, 0); if (foundClosestNeighbor) { // Have the boid try to remain at least cruiseDistance away from its // nearest neighbor at all times in all directions (i.e., don't violate // your neighbor's "personal space" bounding sphere of radius // cruiseDistance, but stay as close to the neighbor as possible). BoidVector separationBoidVector = closestNeighbor->getPosition() - position; float separateFactor = 0.09f; float approachFactor = 0.05f; float cruiseDistance = world->getCruiseDistance(); if (separationBoidVector.y < cruiseDistance) { speedAdjustmentBoidVector.y -= separateFactor; } else if (separationBoidVector.y > cruiseDistance) { speedAdjustmentBoidVector.y += approachFactor; } if (separationBoidVector.x < cruiseDistance) { speedAdjustmentBoidVector.x -= separateFactor; } else if (separationBoidVector.x > cruiseDistance) { speedAdjustmentBoidVector.x += approachFactor; } if (separationBoidVector.z < cruiseDistance) { speedAdjustmentBoidVector.z -= separateFactor; } else if (separationBoidVector.z > cruiseDistance) { speedAdjustmentBoidVector.z += approachFactor; } } // -- Otherwise, if we couldn't find a closest boid, speedAdjustmentBoidVector // will have a magnitude of 0 and thus have no effect on navigation. return(speedAdjustmentBoidVector); } BoidVector Boid::velocityMatching(void) { BoidVector velocityOfClosestNeighbor(0,0,0); double tempDistance; double distanceToClosestNeighbor = DBL_MAX; Boid *n; for (int i=0; igetBoidCount(); i++) { n = world->getBoids()[i]; // Skip boids that we don't need to consider if (visMatrix(n) == -1) continue; if (visMatrix(n) != boidType && flockSelectively) continue; // Find distance from the current boid to self tempDistance = Magnitude(n->position - position); // remember velocity BoidVector of closest boid if (tempDistance < distanceToClosestNeighbor) { distanceToClosestNeighbor = tempDistance; velocityOfClosestNeighbor = n->velocity; } } // If we found a close boid, set the percentage of our acceleration that // we want to use in order to begin flying parallel to its velocity BoidVector. // -- Otherwise, if we couldn't find a closest boid, velocityOfClosestNeighbor // will have a magnitude of 0 and thus have no effect on navigation. if (distanceToClosestNeighbor != DBL_MAX) { // return velocity BoidVector of closest boid so we can try to match it velocityOfClosestNeighbor.SetMagnitude(0.05); } return(velocityOfClosestNeighbor); } BoidVector Boid::flockCentering(void) { BoidVector t; double boids_observed = 0; // number of boids that were checked BoidVector flockcenter(0,0,0); // approximate center of flock Boid *n; // Calculate approximate center of flock by averaging the positions of all // visible boids that we are flocking with. for (int i=0; igetBoidCount(); i++) { n = world->getBoids()[i]; if (visMatrix(n) == -1) continue; if (visMatrix(n) != boidType && flockSelectively) continue; flockcenter += n->position; boids_observed++; } if (boids_observed != 0) { flockcenter /= boids_observed; // now calculate a BoidVector to head towards center of flock t = flockcenter - position; // and the percentage of maximum acceleration (in decimal) to use when yaw toward center t.SetMagnitude(0.1); } else { // Don't see any other birds. t.SetMagnitude(0); } return(t); } bool Boid::update(const double &elapsedSeconds) { bool ok = true; if (flightflag == false) { flightflag = true; } else { double dt = elapsedSeconds - lastUpdate; // Step 1: Calculate new position and velocity by integrating // the acceleration BoidVector. // Update position based on where we wanted to go, and how long it has // been since we made the decision to go there position += (oldVelocity*dt + 0.5*acceleration*dt*dt); // Set new velocity, which will be in the direction boid is traveling. velocity += (acceleration*dt); // Cap off velocity at maximum allowed value if (Magnitude(velocity) > world->getMaxVelocity()) { velocity.SetMagnitude(world->getMaxVelocity()); } // Step 2: Calculate new roll, pitch, and yaw of the boid // which correspond to the changes in position and velocity. // Remember: the boid isn't necessarily oriented in the // direction of the velocity! // (assuming +y is up, +z is through the nose, +x is through the left wing) calculateRollPitchYaw(acceleration, oldVelocity, position); } // remember current velocity oldVelocity = velocity; // remember desired acceleration (the acceleration BoidVector that the // Navigator() module specified acceleration = navigator(); ok &= SimObject::update(elapsedSeconds); // Do generic object updating return ok; } Boid::Boid(BoidWorld *w, int boidNum, BoidVector bPosition, BoidVector bVelocity) { flightflag = false; // haven't flown yet world = w; position = bPosition; velocity = bVelocity; mass = 9.07; // 9.07 kg is approx 20 lbs. roll = pitch = yaw = dampedroll = 0; boidType = NORMAL; flockSelectively = false; boidNumber = boidNum; } #endif /* __BOID_C */