/* ---- This file is part of SECONDO. Copyright (C) 2004, University in Hagen, Department of Computer Science, Database Systems for New Applications. SECONDO 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. SECONDO 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 SECONDO; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ---- This file was originally written by Christopher John Kline, under the copy right statement below. Mahomud Sakr, September 2011, has made the necessary changes to make it available as a SECONDO operator. 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++ Class for objects that move with A-Life boid-type behavior. (c) 1996 Christopher Kline September 2011 Mahmoud Sakr: The Boids simulator/data-generator is now available as a SECONDO operator. */ #ifndef __BOID_C #define __BOID_C #include "Boid.h" #include #include using namespace std; /* 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. */ int **Boid::visibilityMatrix; #define visMatrix(otherBoid) \ (Boid::visibilityMatrix[boidNumber-1][(otherBoid)->boidNumber-1]) double Boid::accumulate(MathVector &accumulator, MathVector valueToAdd) { double magnitudeRemaining = 1.0 - Magnitude(accumulator); double vMag = Magnitude(valueToAdd); double newMag = vMag < magnitudeRemaining ? vMag : magnitudeRemaining; accumulator += (Direction(valueToAdd) * newMag); return newMag; } void Boid::calculateVisibilityMatrix(void) { Boid *n; int val; unsigned int cur= 0; /* Foreach boid, if it's not visible to us, put a a -1 in the matrix. Otherwise put the boidType in the matrix. */ while (cur != this->Generator->boids.size()) { n= this->Generator->boids[cur]; ++cur; // Can't see self! if (n == this) { val = -1; } else { val = (visibleToSelf(n) ? n->boidType : -1); } visMatrix(n)= val; } } MathVector Boid::navigator(const double sampleIntervalSeconds) { /* Calculate the visibility matrix so that visibility computations are much more efficient. */ calculateVisibilityMatrix(); MathVector vacc(0, 0, 0); // vector accumulator if(this->boidType == FREEMOVER) { if (accumulate(vacc, wander()) >= 1.0) goto MAXACCEL_ATTAINED; if (accumulate(vacc, collisionAvoidance(sampleIntervalSeconds)) >= 1.0) goto MAXACCEL_ATTAINED; if (accumulate(vacc, levelFlight(vacc)) >= 1.0) goto MAXACCEL_ATTAINED; } else { if (accumulate(vacc, collisionAvoidance(sampleIntervalSeconds)) >= 1.0) goto MAXACCEL_ATTAINED; 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 vector 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 *= maxAcceleration * maxVelocity; return(vacc); } void Boid::calculateRollPitchYaw(MathVector appliedAcceleration, MathVector currentVelocity, MathVector 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. */ MathVector lateralDir = Direction((currentVelocity % appliedAcceleration) % currentVelocity); /* Set the lateral acceleration's magnitude. The magnitude is the vector projection of the appliedAcceleration vector onto the direction of the lateral acceleration). */ double lateralMag = appliedAcceleration * lateralDir; if (lateralMag == 0) roll = 0; else roll = -atan2(getGravAcceleration(), lateralMag) + M_PI/2; pitch = -atan(currentVelocity.y / sqrt(currentVelocity.z*currentVelocity.z + currentVelocity.x*currentVelocity.x)); yaw = atan2(currentVelocity.x, currentVelocity.z); } MathVector Boid::wander(void) { double distanceFromCruiseSpeed = (Magnitude(velocity) - desiredCruisingSpeed()) / maxVelocity; double urgency = fabs(distanceFromCruiseSpeed); if (urgency > 0.25) urgency = 0.25; return Direction(velocity) * urgency * (distanceFromCruiseSpeed > 0? -1 : 1); } MathVector Boid::collisionAvoidance(const double sampleIntervalSeconds) { Obstacle *obs; ISectData d; MathVector normalToObject(0, 0, 0); int objectSeen = 0; MathVector pointOnObject; // Ignore obstacles that are out of the range of our probe. double distanceToObject = getProbeLength(sampleIntervalSeconds); double dist=0; // Find closest imminent collision with non-boid object unsigned int cur=0; while (cur != this->Generator->obstacles.size()) { obs= this->Generator->obstacles[cur]; ++cur; d = obs->DoesRayIntersect(Direction(velocity), position); if (d.intersectionflag == 1) { // Velocity vector intersects an obstacle dist= Magnitude(d.point-position); if (dist <= distanceToObject) { // found a closer object objectSeen = 1; distanceToObject = Magnitude(d.point-position); normalToObject = d.normal; pointOnObject = d.point; } } } if (!objectSeen) { return MathVector(0,0,0); } return resolveCollision(pointOnObject, normalToObject, sampleIntervalSeconds); } MathVector Boid::resolveCollision( MathVector pointOnObject, MathVector normalToObject, const double sampleIntervalSeconds) { 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) < M_PI/2) normalToObject = - normalToObject; double mag = 1 - distanceToObject/getProbeLength(sampleIntervalSeconds); // Ignore objects that are farther away than our probe. if (mag < 0) mag = 0; /* Mahmoud Sakr has changed this. Instead of yielding a vector directed in the direction of the normal, now it returns a vector in the direction of (-velocity + normal). This should make the Boids perform a sharper turn when they reach an obstacle, in an effect which is similar to a ball hitting an obstacle. return Direction(normalToObject ) * mag; */ // return Direction( normalToObject - velocity ) * mag; return Direction( normalToObject ) * mag; } MathVector Boid::maintainingCruisingDistance(void) { double distanceToClosestNeighbor = std::numeric_limits::max(); // DBL_MAX defined in int foundClosestNeighbor = 0; Boid *n, *closestNeighbor; double tempDistance; unsigned int cur= 0; while (cur != this->Generator->boids.size()) { n= this->Generator->boids[cur]; ++cur; // 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; } } MathVector speedAdjustmentVector(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). */ MathVector separationVector = closestNeighbor->getPosition() - position; float separateFactor = 0.09; float approachFactor = 0.05; if (separationVector.y < cruiseDistance) { speedAdjustmentVector.y -= separateFactor; } else if (separationVector.y > cruiseDistance) { speedAdjustmentVector.y += approachFactor; } if (separationVector.x < cruiseDistance) { speedAdjustmentVector.x -= separateFactor; } else if (separationVector.x > cruiseDistance) { speedAdjustmentVector.x += approachFactor; } if (separationVector.z < cruiseDistance) { speedAdjustmentVector.z -= separateFactor; } else if (separationVector.z > cruiseDistance) { speedAdjustmentVector.z += approachFactor; } } /* Otherwise, if we couldn't find a closest boid, speedAdjustmentVector will have a magnitude of 0 and thus have no effect on navigation. */ return(speedAdjustmentVector); } MathVector Boid::velocityMatching(void) { MathVector velocityOfClosestNeighbor(0,0,0); double tempDistance; double distanceToClosestNeighbor = DBL_MAX; Boid *n; unsigned int cur= 0; while (cur != this->Generator->boids.size()) { n= this->Generator->boids[cur]; ++cur; // 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 vector 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 vector. 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 vector of closest boid so we can try to match it velocityOfClosestNeighbor.SetMagnitude(0.05); } return(velocityOfClosestNeighbor); } MathVector Boid::flockCentering(void) { MathVector t; double boids_observed = 0; // number of boids that were checked MathVector flockcenter(0,0,0); // approximate center of flock Boid *n; unsigned int cur= 0; // Calculate approximate center of flock by averaging the positions of all // visible boids that we are flocking with. while (cur != this->Generator->boids.size()) { n= this->Generator->boids[cur]; ++cur; 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 vector 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, const double sampleIntervalSeconds) { bool ok = false; if (flightflag == false) { flightflag = true; } else { double dt = elapsedSeconds - lastUpdate; /* Step 1: Calculate new position and velocity by integrating the acceleration vector. 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) > maxVelocity) { velocity.SetMagnitude(maxVelocity); } /* 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 vector that the // Navigator() module specified acceleration = navigator(sampleIntervalSeconds); ok &= SimObject::update(elapsedSeconds); // Do generic object updating return ok; } void Boid::SetGenerator(BoidGenerator* BG) { this->Generator= BG; } void Boid::InitBoid( MathVector bPosition, MathVector bVelocity, MathVector bDimensions, int _boidNumber) { flightflag = false; // haven't flown yet position = bPosition; velocity = bVelocity; dimensions = bDimensions; // width, height, length mass = 9.07; // 9.07 kg is approx 20 lbs. lastUpdate=0; oldVelocity= velocity; acceleration= MathVector(0,0,0); Generator=0; maxVelocity = 10.0; maxAcceleration = 0.5; roll = pitch = yaw = 0; boidType = 0; cruiseDistance = 2*dimensions.z; // Try to stay at least one bodywidth apart flockSelectively = false; bodyLength = bDimensions.z; boidNumber = _boidNumber; } Boid::Boid( MathVector bPosition, MathVector bVelocity, MathVector bDimensions, int bType, int boidNumber) { InitBoid(bPosition, bVelocity, bDimensions, boidNumber); boidType = bType; flockSelectively = true; } /* Class BoidGenerator. This class controls and runs the whole simulation. */ BoidGenerator::BoidGenerator(){} BoidGenerator::BoidGenerator(vector& BoidSizes, vector& Obstacles, Instant* SimulationStartTime, Instant* SimulationDuration): boidSizes(BoidSizes), freeBoidCount(BoidSizes[0]), simulationStart(*SimulationStartTime), sampleInterval(0, 7000, datetime::durationtype), remainingSamples(*SimulationDuration / sampleInterval), iterator(0), elapsedTime(0) { makeObstacles(Obstacles); initSimulation(); } BoidGenerator::~BoidGenerator() { for(unsigned int i=0; i< boids.size(); ++i) delete[] Boid::visibilityMatrix[i]; delete[] Boid::visibilityMatrix; for(unsigned int i=0; i< boids.size(); ++i) delete boids[i]; for(unsigned int i=0; i< obstacles.size(); ++i) delete obstacles[i]; //World is already included in obstacles. The previous statement deletes it. //delete world; } void BoidGenerator::makeObstacles(vector& Obstacles) { MathVector origin(mirror(Obstacles[0]), 0, Obstacles[1]); double radius= Obstacles[2]; this->world= new Sphere(origin, radius); for(unsigned int i=3; iobstacles.push_back(obstacle); } obstacles.push_back(this->world); } int BoidGenerator::GetNext( int& BoidID, Instant& SampleTime, double& X,double& Y) { if(this->iterator == boids.size()) { if(! updateBoids()) return -1; //Simulation ended (i.e., the simulation period is completed). iterator= 0; } BoidID= iterator; SampleTime.ReadFrom((int64_t)elapsedTime * 1000); SampleTime.SetType(datetime::durationtype); SampleTime.Add( &simulationStart ); GetCurrent(X, Y); X= mirror(X); ++iterator; return 0; } bool BoidGenerator::GetCurrent(double& X,double& Y) { MathVector pos= boids[iterator]->getPosition(); X= pos.x; Y= pos.z; return true; } bool BoidGenerator::updateBoids() { if(remainingSamples <= 0) return false; // increment elapsed time elapsedTime += sampleInterval.millisecondsToNull()/ 1000; for(unsigned int i = 0; i < boids.size(); i++) boids[i]->update(elapsedTime, sampleInterval.millisecondsToNull()/ 1000); --remainingSamples; return true; } bool BoidGenerator::makeBoids() { bool debugme= false; MathVector d(1, .2, .75); // dimensions of boid (RAD, height, length) double mv = 10;// maximum magnitude of velocity m/s. This value means 36 km/h Boid* myBoid=0; int groupSize=0; double X, Y; for(unsigned int i=1; i< boidSizes.size(); ++i) { groupSize= boidSizes[i]; X= (random() % int(world->Radius() * 0.75)) * pow(-1, random()); Y= (random() % int(world->Radius() * 0.75)) * pow(-1, random()); MathVector origin= MathVector(mirror(X), 0.0, Y) + world->Origin(); X= (random() % int(world->Radius() )) * pow(-1, random()); Y= (random() % int(world->Radius() )) * pow(-1, random()); MathVector MainAttitude= MathVector(X, 0.0, Y); for(int j=0; jRadius() * 0.05)); Y= (random() % int(world->Radius() * 0.05)); MathVector attitude= MathVector(X, 0.0, Y) + MainAttitude; MathVector v = Direction(attitude) * (mv)/4.0; X= (random() % int(min(world->Radius() / 100, 200.0))); Y= (random() % int(min(world->Radius() / 100, 200.0))); MathVector pos= MathVector(mirror(X), 0.0, Y) + origin; myBoid = new Boid(pos, v, d, i, boids.size() + 1); if(debugme) cerr << "\nConstructing boid " << boids.size() + 1 << endl; // Set mac acceleration, and max velocity. myBoid->maxAcceleration = 0.65; //allowing more maneuver than the default 0.5 myBoid->maxVelocity = mv; myBoid->SetGenerator(this); boids.push_back(myBoid); } } for(int i=0; i< this->freeBoidCount; ++i) { X= (random() % int(world->Radius() )) * pow(-1, random()); Y= (random() % int(world->Radius() )) * pow(-1, random()); MathVector attitude= MathVector(X, 0.0, Y); MathVector v = Direction(attitude) * (mv)/4.0; X= (random() % int(world->Radius() * 0.75)) * pow(-1, random()); Y= (random() % int(world->Radius() * 0.75)) * pow(-1, random()); MathVector pos= MathVector(mirror(X), 0.0, Y) + world->Origin(); myBoid = new Boid(pos, v, d, -1, boids.size() + 1); if(debugme) cerr << "\nConstructing boid " << boids.size() + 1 << endl; // Set mac acceleration, and max velocity. myBoid->maxAcceleration = 0.65; //allowing more maneuver than the default 0.5 myBoid->maxVelocity = mv; myBoid->SetGenerator(this); boids.push_back(myBoid); } return true; } bool BoidGenerator::initSimulation() { if (! makeBoids()) { cerr << "\nmakeBoids() returned an error\n"; return false; } // Allocate the visibility matrix. unsigned int boidCount= this->boids.size(); Boid::visibilityMatrix = new int*[boidCount]; for (unsigned int i = 0; i < boidCount; i++) Boid::visibilityMatrix[i] = new int[boidCount]; return true; } #endif // __BOID_C