//=========================================================================== //=========================================================================== // CONSTRAINED VECTOR FLOCKING // v. 0.9.1 // by Rafael DeAquino Villar // 12/1/2007 rev. 12/6/2007 // HNRS 69 / Dr. Gessler // Upgraded to Embarcadero 29 March 2011 by NG // // Description: Models simplified physics and particle interactions based on // vector math. I've observed several complex emergent behaviors that // resemble the action of cellular automata -- try "Add, then Subtract Eigen- // vector at Range / 2" with low range of action and kinetic energy. // // // This code was built for modularity, and should be broadly applicable. Feel // free to modify, tweak, steal, distribute or otherwise make use of it. // //=========================================================================== //=========================================================================== //$$---- Form CPP ---- //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #include #include #pragma hdrstop #include "Unit1.h" #include "stdlib.h" #include "time.h" #include #include "vector.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { DoubleBuffered = true; } //=========================================================================== // GLOBAL CONSTANTS //=========================================================================== #define LEFT 0 #define RIGHT 1 #define TOP 2 #define BOTTOM 3 #define OFF_VERTICAL 0 #define OFF_HORIZONTAL 1 #define NUMBER_OF_AGENTS 10 // Can/should be defined dynamically by user. #define AGENT_DIAMETER 30 //=========================================================================== // DATA STRUCTURES & CLASSES //=========================================================================== //-----------------------------------------------------------------------Vect // Vector structure: used for positions, velocities, forces, etc. // Basic unit of data manipulation. typedef struct vect { float x, y; } Vect; // This approach has several advantages: for one, it almost completely // eliminates the need for trigonometry and angles. Rather, we can think of // velocities, forces, positions, accelerations, and so forth, as components // along the x and y axes. //----------------------------------------------------------------Coordinator // Coordinator class: coordinates actions, stores global variables class coordinator { public: coordinator(): increment(.5), stop(true), wasStopped(false), agentWasJustChosen(false), behaviorRange(30), sysEnergy(2000), iterations(0) {} //No argument contructor ~coordinator() {} // Destructor: deallocates memory after use. int iterations, chosenAgent, shapeDownX, shapeDownY, behaviorRange, sysEnergy, tempInt; float increment, tempFloat, maxMagVel, totalKE; bool stop, wasStopped, agentWasJustChosen; // Get rid of temps Vect pointA, pointB, ctrOfMass, agentPosition, agentLastPosition, ctrPoint, boundsCtr, tlCorner, ctrSpace, posRelCtr; }; //---------------------------------------------------------------------Mobile // Agent class: self-contained mobile agent class mobile { public: mobile(): currentLapComplete(false), alive(true), startAtPtA(true), age(0), laps(0), utility(0.0), work(0.0), fitness(0.0), energy(0.0), odometer(0.0), mass(1.0) {} // No argument constructor ~mobile() {} // Destructor bool currentLapComplete, alive, startAtPtA; int affinity, laps, age, forceRange; float mass, energy, work, fitness, utility, odometer; Vect position, velocity, acceleration, force, positionLast, velocityLast, positionNext, velocityNext, positionObjective, velocityObjective; // Classes used in this way are essentially glorified structures. // To increase modularity and data protection, functions should be incorporated // into the classes, and variables should be made private. I would have done so, // and still may, but didn't have the time to deal with the associated // complications. }; //=========================================================================== // GLOBAL OBJECTS & VARIABLES //=========================================================================== // Should be moved to "On Form Create" ; no time to implement randomize(); // Initializes random number gen vector shape(NUMBER_OF_AGENTS); // Initializes vector of shape pointers vector agent(NUMBER_OF_AGENTS); // Initializes vector of agents coordinator daemon; // Initializes coordinator //Above: I used a C++ standard library container "vector". While sharing a sim. //name, this has nothing to do with the "Vect" structure I defined previously-- //it is merely a flexible data container that uses dynamic memory allocation, //and to which several useful standard library algorithms can be applied. //Since allocation is dynamic, this allows for runtime definition of the number of //agents, and deletion based on specified criteria (meets some value, etc) //one need simply move the initialization to "On Form Create", adjust the rest //of the code accordingly, and use the STL appropriate algorithms. //=========================================================================== // OVERLOADED OPERATORS //=========================================================================== //Below are useful operators that I overloaded for use with the Vect data //structure. //--------------------------------------------------------------------------- // Vector addition Vect operator +(const Vect& vector1, const Vect& vector2) { Vect tmpDump; tmpDump.x = vector1.x + vector2.x; tmpDump.y = vector1.y + vector2.y; return tmpDump; } Vect resultant(Vect& vectA, Vect& vectB) //temporary { Vect tmpDump; tmpDump.x=vectB.x - vectA.x; tmpDump.y=vectB.y - vectA.y; return tmpDump; } //--------------------------------------------------------------------------- // Vector subtraction Vect operator -(const Vect& vector1, const Vect& vector2) { //causing access violations; momentarily deprecated Vect tmpDump; tmpDump.x = vector1.x - vector2.x; //ugly fix to AccsViol tmpDump.y = vector1.y - vector2.y; return tmpDump; } //--------------------------------------------------------------------------- // Scalar Multiplication Vect operator *(const Vect& vector1, float factor) { Vect tmpDump; tmpDump.x = vector1.x * factor; tmpDump.y = vector1.y * factor; return tmpDump; } Vect operator *(float factor, const Vect& vector1) { Vect tmpDump; tmpDump.x = vector1.x * factor; tmpDump.y = vector1.y * factor; return tmpDump; } //--------------------------------------------------------------------------- // Dot Product float operator *(const Vect& vector1, const Vect& vector2) { float temp; temp = vector1.x * vector2.x + vector1.y * vector2.y; return temp; } //--------------------------------------------------------------------------- // Scalar Division Vect operator /(const Vect& vector1, float divisor) { Vect tmpDump; tmpDump.x = vector1.x / divisor; tmpDump.y = vector1.y / divisor; return tmpDump; } //=========================================================================== // FUNCTIONS //=========================================================================== //----------------------------------------------------------------Color Ramp // Color Ramp function: creates color gradient corresponding to a range of // values int colorRamp(int range, int value) { int pixelDistanceAlongPath = (value * 1792) / range; int red, green, blue; // Which edge of the color cube are we on? if (pixelDistanceAlongPath < 256) { // Edge 1 from BLACK to BLUE red=0; green=0; blue=pixelDistanceAlongPath; } else if (pixelDistanceAlongPath < 512) { // Edge 2 from BLUE to CYAN red =0; green=pixelDistanceAlongPath-256; blue=255; } else if (pixelDistanceAlongPath < 768) { // Edge 3 from CYAN to GREEN red =0; green =255; blue= 255-(pixelDistanceAlongPath-512); } else if (pixelDistanceAlongPath < 1024) { // Edge 4 from GREEN to YELLOW red= (pixelDistanceAlongPath-768); green =255; blue =0; } else if (pixelDistanceAlongPath <1280) { // Edge 5 from YELLOW to RED red =255; green=255-(pixelDistanceAlongPath-1024); blue =0; } else if (pixelDistanceAlongPath < 1536) { // Edge 6 from RED to MAGENTA red =255; green=0; blue=pixelDistanceAlongPath -1280; } else { // Edge 7 from MAGENTA to WHITE red =255; green=pixelDistanceAlongPath-1536; blue =255; } return (RGB(red, green, blue)); } //------------------------------------Plus or Minus for Double Precision Float float floatPlusOrMinus() // Generates either a -1.0 or 1.0 { // for various uses. bool plus=(bool)random(2); float temp = plus ? 1.0 : -1.0; return temp; } //-------------------------------------------------------------Random Velocity Vect randVelocity() // Assigns random velocity vectors to agents { Vect vel; vel.x=floatPlusOrMinus()* ((float)random(daemon.sysEnergy))/3000; vel.y=floatPlusOrMinus()* ((float)random(daemon.sysEnergy))/3000; return vel; } //-------------------------------------------------------Center on Coordinates void spawnOnCoordinate(Vect ctr1, Vect ctr2) //Sets Up Agent Spawnpoints { for (int i = 0; i < NUMBER_OF_AGENTS; i=i+2) { agent[i].position = ctr1; shape[i]->Left = agent[i].position.x - shape[i]->Width / 2; shape[i]->Top = agent[i].position.y - shape[i]->Height / 2; } for (int i = 1; i < NUMBER_OF_AGENTS; i=i+2) { agent[i].position = ctr2; shape[i]->Left = agent[i].position.x - shape[i]->Width / 2; shape[i]->Top = agent[i].position.y - shape[i]->Height / 2; } } // -----------------------------------------------------Bounce Off Boundraries void bounce(int xy, mobile& anAgent) // Bounces off vertical or horizontal walls { if (xy == OFF_VERTICAL) anAgent.velocity.y = anAgent.velocity.y * -1.0; else if (xy == OFF_HORIZONTAL) anAgent.velocity.x = anAgent.velocity.x * -1.0; } // Vector Tools: //------------------------------------------------------------------Set Vector void stV(Vect& vector1, float xCmp, float yCmp) // Initialzes vect { vector1.x=xCmp; vector1.y=yCmp; } // -----------------------------------------------------------Vector Magnitude float magnitudeOfVect(const Vect& a) // Calculates vector a's magnitude: { // i.e. distance or speed. float temp; temp = sqrt(pow(a.x,2) + pow(a.y,2)); return temp; } // ------------------------------------------------------------Get Eigenvector Vect getEigenVect(const Vect& a) // Calculates directional eigenvector of a { Vect temp = a / magnitudeOfVect(a); return temp; } // ------------------------------------------------------------Add Eigenvector void addEigen(Vect& a, const Vect& b) { Vect temp = a; a = a + getEigenVect(b); a = a * (magnitudeOfVect(temp)/magnitudeOfVect(a));// normalizes magnitude } // -------------------------------------------------------Subtract Eigenvector void subEigen(Vect& a, const Vect& b) { Vect temp = a; a = a - getEigenVect(b); a = a * (magnitudeOfVect(temp)/magnitudeOfVect(a)); } // ---------------------------------------Randomly Add or Subtract Eigenvector void randEigen(Vect& a, const Vect& b) { Vect temp = a; a = a + getEigenVect(b)* floatPlusOrMinus(); a = a * (magnitudeOfVect(temp)/magnitudeOfVect(a)); } // --------------------------------------------------------------Cross Product Vect crossProd(const Vect& a) // Not strictly a crossp. Calculates orthogonal { // vector to a. Vect temp; temp.x=a.y; temp.y=-a.x; return temp; } // ------------------------------------------------------------Cross Product 2 float crossProd(const Vect& a, const Vect& b) // Not strictly a crossp. Calc { // area of parallelogram between a and b. float temp=(a.x * b.y) - (a.y * b.x); return temp; } // -----------------------------------------------------------Scalar Projection float compBontoA(const Vect& b, const Vect& a) // Finds component of b along a { float temp=(a * b) / magnitudeOfVect(a); return temp; } // ----------------------------------------------------------Raise Vect to Pow Vect powVect(const Vect& baseVect, float exp) { Vect temp; temp.x=pow(baseVect.x,exp); temp.y=pow(baseVect.y,exp); return temp; } // ---------------------------------------------------------Set Vector to Null void nullVect(Vect& vector1) { stV(vector1, 0.0, 0.0); } //---------------------------------------------------------------Pure Pursuit Vect purePursuitDir(const Vect& positionA, const Vect& positionB) { // returns an Eigen direction vector to nearest neighbor Vect temp = positionB - positionA; return getEigenVect(temp); } // ------------------------------------------------------------------Collide void collide(mobile& agentA, const mobile& agentB)// Collisions between agents { Vect temp = agentA.velocity; agentA.velocity = agentA.velocity - purePursuitDir(agentA.position,agentB.position); agentA.velocity = agentA.velocity * (magnitudeOfVect(temp)/magnitudeOfVect(agentA.velocity)); } //Simple collisions; improved version in the works. /*void collideProper(mobile& agentA, mobile& agentB,int i, int iterations) { if(*agentA.collided<1 && !(agentA.collided==&agentB.collided)) { Vect linearMomentum, normalI, normalJ, los; los=purePursuitDir(agentA.position,agentB.position); linearMomentum=(agentA.mass*agentA.velocity)+(agentB.mass*agentB.velocity); normalI=agentA.velocity-(compBontoA(agentA.velocity, los)*los); normalJ=agentB.velocity-(compBontoA(agentB.velocity, los)*los); agentA.velocity=normalI+linearMomentum; agentB.velocity=normalJ+linearMomentum; ++(*agentA.collided); agentA.collided=agentB.collided; } } // Not working yet. The problem it seems, is that due to cinematic scheduling, // the agents collide multiple times, and initiate a positive feedback loops. // My implementation of flags didn't work, nor did my first try with pointers. // I'm confident this could eventually be resolved. I believe the mathematics // and physics for the colision algorithm above is correct, however. */ // --------------------------------------------------------------Radial Force Vect radialForce(const Vect& positionA, const Vect& positionB, float coefficient, float power) { Vect force; force=purePursuitDir(positionA,positionB)* (coefficient/pow(magnitudeOfVect(positionB-positionA),power)); return force; } // --------------------------------------------------------------Radial Force inline void updateCtrOfMass() { Form1->ShapeCtrOfMass->Left = daemon.ctrOfMass.x - Form1->ShapeCtrOfMass->Width / 2; Form1->ShapeCtrOfMass->Top = daemon.ctrOfMass.y - Form1->ShapeCtrOfMass->Height / 2; } // --------------------------------------------------------Updates Ctr of Mass inline void updateShape(bool render, int i) { if(render) { shape[i]->Left = agent[i].position.x - shape[i]->Width / 2; shape[i]->Top = agent[i].position.y - shape[i]->Height / 2; shape[i]->Brush->Color = static_cast(colorRamp(100, static_cast(agent[i].energy*100))); } } inline bool toRender() { bool temp; ((daemon.iterations % 10) == 0) ? temp=true : temp=false; return temp; } // --------------------------------------------------------------Check Bounds void checkBounds() //no argument version for updating walls { switch(Form1->RadioGroupBounds->ItemIndex) { case 0: { Form1->LeftWall->Visible=true; Form1->RightWall->Visible=true; Form1->UpperWall->Visible=false; Form1->LowerWall->Visible=false; } break; case 1: { Form1->LeftWall->Visible=true; Form1->RightWall->Visible=true; Form1->UpperWall->Visible=true; Form1->LowerWall->Visible=true; } break; case 2: { Form1->LeftWall->Visible=false; Form1->RightWall->Visible=false; Form1->UpperWall->Visible=false; Form1->LowerWall->Visible=false; } break; default: break; } } // --------------------------------------------------------------Check Bounds void checkBounds(mobile& anAgent) { daemon.posRelCtr=anAgent.position-daemon.boundsCtr; switch(Form1->RadioGroupBounds->ItemIndex) { case 0: { Form1->LeftWall->Visible=true; Form1->RightWall->Visible=true; Form1->UpperWall->Visible=false; Form1->LowerWall->Visible=false; if(abs(long(daemon.posRelCtr.x)) > (long(daemon.ctrSpace.x-AGENT_DIAMETER)/2)) bounce(OFF_HORIZONTAL, anAgent); if(anAgent.position.y < 0) anAgent.position.y = 536; else if(anAgent.position.y > 536) anAgent.position.y = 0; } break; case 1: { Form1->LeftWall->Visible=true; Form1->RightWall->Visible=true; Form1->UpperWall->Visible=true; Form1->LowerWall->Visible=true; if(abs(long(daemon.posRelCtr.x)) > (long(daemon.ctrSpace.x-AGENT_DIAMETER)/2)) bounce(OFF_HORIZONTAL, anAgent); if(abs(long(daemon.posRelCtr.y)) > (long(daemon.ctrSpace.y-AGENT_DIAMETER)/2)) bounce(OFF_VERTICAL, anAgent); } break; case 2: { Form1->LeftWall->Visible=false; Form1->RightWall->Visible=false; Form1->UpperWall->Visible=false; Form1->LowerWall->Visible=false; if(anAgent.position.x < 0) anAgent.position.x = 530; else if(anAgent.position.x > 530) anAgent.position.x = 0; if(anAgent.position.y < 0) anAgent.position.y = 536; else if(anAgent.position.y > 536) anAgent.position.y = 0; } break; default: break; } } // Enforces boundraries; still needs considerable work to prevent "leakage". //------------------------------------------------------------Nearest Neighbor int nearestNeighbor (int me) // Finds nearest neighbor, returns index { int myNeighbor; Vect temp; float dist = 10000,z; for (int k = 0; k < NUMBER_OF_AGENTS; k++) { if (k == me) continue; temp = resultant(agent[me].position, agent[k].position); if ((z=(magnitudeOfVect(temp))) < dist) { dist = z; myNeighbor = k; } } return myNeighbor; } //----------------------------------------------------------------------Reset void reset (void) // Resets simulation { daemon.iterations = 0; Form1->EditIterations->Text = daemon.iterations; daemon.sysEnergy=Form1->TrackBarInitialSystemEnergy->Position; daemon.totalKE = 0; stV(daemon.tlCorner, Form1->LeftWall->Width, (Form1->UpperWall->Height) - (Form1->LeftWall->Top)); stV(daemon.ctrSpace, (Form1->RightWall->Left) - ((Form1->LeftWall->Left) + (Form1->LeftWall->Width)), (Form1->LowerWall->Top)-(Form1->UpperWall->Top+Form1->UpperWall->Height)); daemon.boundsCtr = daemon.tlCorner + daemon.ctrSpace/2; for (int i = 0; i < NUMBER_OF_AGENTS; i++) { agent[i].velocity = randVelocity(); nullVect(agent[i].force); nullVect(agent[i].acceleration); agent[i].energy = .5*agent[i].mass*(agent[i].velocity)*(agent[i].velocity); daemon.totalKE += agent[i].energy; agent[i].affinity = (int)random(5); shape[i]->Brush->Color = static_cast(colorRamp(100, static_cast(agent[i].energy*100))); updateShape(toRender, i); } spawnOnCoordinate(daemon.pointA, daemon.pointB); daemon.ctrOfMass=(daemon.pointA+daemon.pointB)/2; updateCtrOfMass(); checkBounds(); Form1->EditTotalKE->Text=daemon.totalKE; } //-----------------------------------------------------------------------Step void step() // The "engine". Updates positions and performs calculations { // using cinematic scheduling. daemon.iterations++; Form1->EditIterations->Text = daemon.iterations; Vect nearResultant, aggregate; nullVect(aggregate); int j, k; daemon.behaviorRange = Form1->TrackBarBehaviorRange->Position; float newKE = 0; for (int i=0;iCheckBoxCollisions->Checked) if ((magnitudeOfVect(nearResultant)) < AGENT_DIAMETER) collide(agent[i],agent[j]); // gravity toggle if (Form1->CheckBoxGravity->Checked) if (Form1->CheckBoxRepulsive->Checked) agent[i].force=-1*radialForce(agent[i].position, daemon.ctrOfMass, .5*(Form1->TrackBarGravityStrength->Position),2.0); else agent[i].force=radialForce(agent[i].position, daemon.ctrOfMass, .5*(Form1->TrackBarGravityStrength->Position),2.0); // Flocking Algorithms switch(Form1->RadioGroupBehavior->ItemIndex) { case 0: break; case 1: if(magnitudeOfVect(nearResultant) < daemon.behaviorRange) addEigen(agent[i].velocity,agent[j].velocity); // Add nearest neighbor's eigenvector, and normalize magnitude. break; case 2: if(magnitudeOfVect(nearResultant) < daemon.behaviorRange) subEigen(agent[i].velocity,agent[j].velocity); // Subtract nearest neighbor's eigenvector, and normalize mag. break; case 3: if(magnitudeOfVect(nearResultant) < daemon.behaviorRange) randEigen(agent[i].velocity,agent[j].velocity); break; // Randomly add or subtract NN's eigenvector. case 4: if(magnitudeOfVect(nearResultant) < daemon.behaviorRange) { addEigen(agent[i].velocity,agent[j].velocity); if(magnitudeOfVect(nearResultant) < (daemon.behaviorRange/2)) subEigen(agent[i].velocity,agent[j].velocity); } // Add or Subtract nearest neighbor's eigenvector depending // on range, and normalize magnitude. break; case 5: if(magnitudeOfVect(nearResultant) < daemon.behaviorRange) addEigen(agent[i].velocity,crossProd(agent[j].velocity)); break; // Add NN's normal eigenvector case 6: if(magnitudeOfVect(nearResultant) < daemon.behaviorRange) addEigen(agent[i].velocity,crossProd(agent[j].velocity)); if(magnitudeOfVect(nearResultant) < (daemon.behaviorRange/3)) agent[i].force=radialForce(agent[i].position, daemon.ctrOfMass, 1.0,1.0); break; // Normal eigenvector + attraction case 7: if(magnitudeOfVect(nearResultant) < daemon.behaviorRange) agent[i].force=radialForce(agent[i].position, daemon.ctrOfMass, .0001,-1.0); if(magnitudeOfVect(nearResultant) < (daemon.behaviorRange/4)) agent[i].force=radialForce(agent[i].position, daemon.ctrOfMass,-1.0,3.0); break; case 8: agent[i].force=radialForce(agent[i].position, daemon.ctrOfMass, .0001,-1.0); break; default: break; } // Improved Bounds Checking checkBounds(agent[i]); // Calculate KE agent[i].energy = .5*agent[i].mass*(agent[i].velocity)*(agent[i].velocity); newKE += agent[i].energy; } daemon.ctrOfMass=aggregate/NUMBER_OF_AGENTS; updateCtrOfMass(); daemon.totalKE -= (daemon.totalKE)-(newKE); Form1->EditTotalKE->Text= daemon.totalKE; } //--------------------------------------------------------------------------- void run() { daemon.stop = false; while (daemon.stop == false) { step(); Application->ProcessMessages(); } } //=========================================================================== // EVENT HANDLERS //=========================================================================== //-------------------------------------------------------------On Form Create void __fastcall TForm1::FormCreate(TObject *Sender) { stV(daemon.ctrPoint,265,268); stV(daemon.pointA, 275.0,190.0); // spawnpoints stV(daemon.pointB, 275.0,310.0); daemon.ctrOfMass=(daemon.pointA+daemon.pointB)/2; ShapeCtrOfMass->Left = daemon.ctrOfMass.x - ShapeCtrOfMass->Width / 2; ShapeCtrOfMass->Top = daemon.ctrOfMass.y - ShapeCtrOfMass->Height / 2; for (int i=0;iParent = Form1; // coordinators to run an arbitrary number of shape[i]->Visible = true; // simulations, which might be useful for shape[i]->OnMouseDown = ShapeMouseDown; // injecting fit individuals from shape[i]->OnMouseUp = ShapeMouseUp; // one population into another. shape[i]->OnMouseMove = ShapeMouseMove; shape[i]->Width = AGENT_DIAMETER; shape[i]->Height = AGENT_DIAMETER; shape[i]->Left = 0; shape[i]->Top = 0; shape[i]->Shape = stCircle; shape[i]->Tag = i; shape[i]->Pen->Width = 2; shape[i]->Brush->Color = clBlack; shape[i]->Pen->Color = clBlack; //static_cast(colorRamp(5, agent[i].affinity)); } reset(); } //------------------------------------------------------- On Shape Mouse Down // An exception to the rule which says "Let Borland write event handlers." // This one you must type in yourself in addition to a reference to it // in Unit1.h (look at the source code in that unit). void __fastcall TForm1::ShapeMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { TShape *shape = dynamic_cast(Sender); // The following will be used for mouseMove and mouseUp events... // Captures the index number of the shape that was clicked daemon.chosenAgent = shape->Tag; // remembers is we were stopped or running daemon.wasStopped = daemon.stop; daemon.stop = true; if (Button == 0) { // Begin drag object // Remembers that an shape was chosen for mouseMove and mouseUp daemon.agentWasJustChosen = true; // Remembers where on the shape the mouse was downed daemon.shapeDownX = X; daemon.shapeDownY = Y; } else { // Show nearest neighbor Form1->Canvas->MoveTo(agent[daemon.chosenAgent].position.x, agent[daemon.chosenAgent].position.y); Form1->Canvas->LineTo(agent[nearestNeighbor(daemon.chosenAgent)].position.x, agent[nearestNeighbor(daemon.chosenAgent)].position.y); } } //------------------------------------------------------- On Shape Mouse Move // An exception to the rule which says "Let Borland write event handlers." // This one you must type in yourself in addition to a reference to it // in Unit1.h (look at the source code in that unit). void __fastcall TForm1::ShapeMouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { TShape *shape = dynamic_cast(Sender); daemon.chosenAgent = shape->Tag; if (daemon.agentWasJustChosen) { // this drags the shape along... shape->Left = shape->Left + X - daemon.shapeDownX; shape->Top = shape->Top + Y - daemon.shapeDownY; agent[daemon.chosenAgent].position.x = shape->Left + shape->Width / 2;; agent[daemon.chosenAgent].position.y = shape->Top + shape->Height / 2;; } // When the mouse moves over an shape its values are displayed EditAgent->Text = shape->Tag; EditX->Text = int(agent[daemon.chosenAgent].position.x); EditY->Text = int(agent[daemon.chosenAgent].position.y); EditVx->Text = agent[shape->Tag].velocity.x; EditVy->Text = agent[shape->Tag].velocity.y; EditAgentKE->Text = agent[shape->Tag].energy; } //--------------------------------------------------------- On Shape Mouse Up void __fastcall TForm1::ShapeMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { TShape *shape = dynamic_cast(Sender); // this is the end of the drag... daemon.agentWasJustChosen = false; EditAgent->Text = shape->Tag; // When the mouse moves over an shape its values are displayed EditX->Text = int(agent[daemon.chosenAgent].position.x); EditY->Text = int(agent[daemon.chosenAgent].position.y); EditVx->Text = agent[shape->Tag].velocity.x; EditVy->Text = agent[shape->Tag].velocity.y; EditAgentKE->Text = agent[shape->Tag].energy; // if we were running before the drag, then run... Form1->Refresh(); if (!daemon.wasStopped) { run(); } } //-----------------------------------------------------------------Run Button void __fastcall TForm1::ButtonRunClick(TObject *Sender) { run(); } //----------------------------------------------------------------Stop Button void __fastcall TForm1::ButtonStopClick(TObject *Sender) { daemon.stop=true; } //---------------------------------------------------------------Reset Button void __fastcall TForm1::ButtonResetClick(TObject *Sender) { reset(); } //----------------------------------------------------------------Step Button void __fastcall TForm1::ButtonStepClick(TObject *Sender) { daemon.stop = true; step(); } //////////////////////////////////////////////////////////////////END PROGRAM