//=========================================================================== // FLOCKING BALLS with WALLS - March 2007 // One can ask if an agent has entered a region in order to change its // characteristics. This is OK to test for out of bounds conditions for // a "bounce" or a "wrap." // // This approach does not work for a rectangular region within bounds, since // it does not tell us whether the agent crossed the vertical or horizontal // wall. In that case we want to know which wall she crossed, hence we must // know its LAST position. This code introduces new agent variables lastX // and lastY, and a new bounce() function. For clarity, it introduces two // new #defines as well as the variables x, y, lastX and lastY. //=========================================================================== //--------------------------------------------------------------------------- #include #include #pragma hdrstop #include "Unit1.h" // The following two lines are needed to enable the randomizer #include "stdlib.h" #include "time.h" #include //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { // Double buffering eliminates some screen flicker... DoubleBuffered = true; } //--------------------------------------------------------------------------- //=========================================================================== // GLOBAL VARIABLES //=========================================================================== // ------------------------------------------------- your program begins here #define LEFT 0 #define RIGHT 1 #define TOP 2 #define BOTTOM 3 #define offVERTICAL 0 #define offHORIZONTAL 1 int midiport = 0; HMIDIOUT device; union { public: unsigned long word; unsigned char data[4]; } message; int soundType = 0; int instrument; int note; TShape *shape[24] = {0}; // sets up an array of shapes class anAgent { public: float velocity; double direction; double newDirection; double newDistance; double lastDistance; float x; float lastX; float y; float lastY; } agent[24]; int increment = 5; float x, y; float lastX, lastY; bool stop = true; bool wasStopped = false; int iterations; int i; int chosenAgent; int shapeDownX, shapeDownY; bool agentWasJustChosen = false; //=========================================================================== // FUNCTIONS //=========================================================================== //---------------------------------------------------------------- color ramp 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)); } //------------------------------------------------------------------- scatter void scatter (void) { for (int i = 0; i < 24; i++) { agent[i].x = random(380) + 20; shape[i]->Left = agent[i].x - shape[i]->Width / 2; agent[i].y = random(380) + 20; shape[i]->Top = agent[i].y - shape[i]->Height / 2; } } //-------------------------------------------------------------------- circle void circle (void) { for (int i = 0; i < 24; i++) { agent[i].x = 180 + 180 * cos(i * (2 * M_PI / 24)) + 20; agent[i].y = 180 + 180 * sin(i * (2 * M_PI / 24)) + 20; shape[i]->Left = agent[i].x - shape[i]->Width / 2; shape[i]->Top = agent[i].y - shape[i]->Height / 2; } } //-------------------------------------------------------------------- center void center (void) { for (int i = 0; i < 24; i++) { agent[i].x = 180 + 20; agent[i].y = 180 + 20; shape[i]->Left = agent[i].x - shape[i]->Width / 2; shape[i]->Top = agent[i].y - shape[i]->Height / 2; } } //--------------------------------------------------------------------- reset void reset (void) { iterations = 0; Form1->EditIterations->Text = iterations; for (int i = 0; i < 24; i++) { agent[i].direction = (float(random(1000)) / 500) * M_PI; agent[i].velocity = float(random(1000)) / 1500 + .1; } } //------------------------------------------------------------- sound effects void soundFX(int wall) { switch (wall) { case LEFT: { if (soundType == 1) Beep(330, 50); if (soundType == 2) { PlaySound ("C:\\WINDOWS\\Media\\Windows XP Balloon.wav", "", SND_ASYNC); } if (soundType == 3) { PlaySound ("C:\\Program Files\\Windows NT\\Pinball\\SOUND713.wav", "", SND_ASYNC); } if (soundType == 4) { message.data[0] = 0x90; message.data[1] = 64; message.data[2] = 100; message.data[3] = 0; midiOutShortMsg(device, message.word); } break; } case RIGHT: { if (soundType == 1) Beep(196, 50); if (soundType == 2) { PlaySound ("C:\\WINDOWS\\Media\\Windows XP Balloon.wav", "", SND_ASYNC); } if (soundType == 3) { PlaySound ("C:\\Program Files\\Windows NT\\Pinball\\SOUND181.wav", "", SND_ASYNC); } if (soundType == 4) { message.data[0] = 0x90; message.data[1] = 55; message.data[2] = 100; message.data[3] = 0; midiOutShortMsg(device, message.word); } break; } case TOP: { if (soundType == 1) Beep(392, 50); if (soundType == 2) { PlaySound ("C:\\WINDOWS\\Media\\Windows XP pop-up blocked.wav", "", SND_ASYNC); } if (soundType == 3) { PlaySound ("C:\\Program Files\\Windows NT\\Pinball\\SOUND735.wav", "", SND_ASYNC); } if (soundType == 4) { message.data[0] = 0x90; message.data[1] = 67; message.data[2] = 100; message.data[3] = 0; midiOutShortMsg(device, message.word); } break; } case BOTTOM: { if (soundType == 1) Beep(261, 50); if (soundType == 2) { PlaySound ("C:\\WINDOWS\\Media\\Windows XP pop-up blocked.wav", "", SND_ASYNC); } if (soundType == 3) { PlaySound ("C:\\Program Files\\Windows NT\\Pinball\\SOUND243.wav", "", SND_ASYNC); } if (soundType == 4) { message.data[0] = 0x90; message.data[1] = 60; message.data[2] = 100; message.data[3] = 0; midiOutShortMsg(device, message.word); } break; } } } // ------------------------------------------------------------------- bounce void bounce (int xy) { if (xy == offVERTICAL) { agent[i].direction = M_PI - agent[i].direction; } if (xy == offHORIZONTAL) { agent[i].direction = 2 * M_PI - agent[i].direction; } // modular divide direction by one rotation if (agent[i].direction > 2 * M_PI) { agent[i].direction = agent[i].direction - 2 * M_PI; } if (agent[i].direction < 0) { agent[i].direction = agent[i].direction + 2 * M_PI; } } //---------------------------------------------------------- nearest neighbor int nearestNeighbor (int me) { int myNeighbor; double x, y, z; int dist = 10000; for (int i = 0; i < 24; i++) { if (i == me) continue; x = agent[i].x - agent[me].x; y = agent[i].y - agent[me].y; z = sqrt(x * x + y * y); if (z < dist) { dist = z; myNeighbor = i; } } return myNeighbor; } //---------------------------------------------------------------------- step void step (void) { iterations++; Form1->EditIterations->Text = iterations; increment = Form1->TrackBarIncrement->Position; for (i = 0; i < 24; i++) { agent[i].lastX = agent[i].x; agent[i].lastY = agent[i].y; // calculate agent's new position based on velocity and direction agent[i].x += agent[i].velocity * cos(agent[i].direction) * increment; agent[i].y += agent[i].velocity * sin(agent[i].direction) * increment; // move the visualization on the screen accordingly shape[i]->Left = agent[i].x - shape[i]->Width / 2; shape[i]->Top = agent[i].y - shape[i]->Height / 2; /////////////////////////////////////////////////////////////////////// ///////////////////// FLOCKING BEHAVIORS BELOW //////////////////////// /////////////////////////////////////////////////////////////////////// // adopt nearest neighbor's direction (problematic with BOUNCE) if (Form1->RadioGroupFlockingBehavior->ItemIndex == 1) { agent[i].direction = agent[nearestNeighbor(i)].direction; } // increment direction by 1/50 your nearest neighbor's if (Form1->RadioGroupFlockingBehavior->ItemIndex == 2) { agent[i].direction += agent[nearestNeighbor(i)].direction / 50; if (agent[i].direction > 2 * M_PI) { // modulo divide agent[i].direction = agent[i].direction - 2 * M_PI; } } // decrement direction by 1/100 your nearest neighbor's if (Form1->RadioGroupFlockingBehavior->ItemIndex == 3) { agent[i].direction -= agent[nearestNeighbor(i)].direction / 100; if (agent[i].direction > 2 * M_PI) { // modulo divide agent[i].direction = agent[i].direction - 2 * M_PI; } } // adopt nearest neighbor's velocity if (Form1->RadioGroupFlockingBehavior->ItemIndex == 4) { agent[i].velocity = agent[nearestNeighbor(i)].velocity; } // increment by half nearest neighbor's velocity if (Form1->RadioGroupFlockingBehavior->ItemIndex == 5) { agent[i].velocity += agent[nearestNeighbor(i)].velocity / 50; } // adopt nearest neighbor's direction and speed if (Form1->RadioGroupFlockingBehavior->ItemIndex == 6) { agent[i].velocity = agent[nearestNeighbor(i)].velocity; agent[i].direction = agent[nearestNeighbor(i)].direction; } /////////////////////////////////////////////////////////////////////// ///////////////////// FLOCKING BEHAVIORS ABOVE /////////////////////// /////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////// BOUNCE or WRAP x = agent[i].x; lastX = agent[i].lastX; y = agent[i].y; lastY = agent[i].lastY; if (x < 10) { soundFX(LEFT); agent[i].x = 390; //bounce(offVERTICAL); } else if (x > 390) { soundFX(RIGHT); agent[i].x = 10; //bounce(offVERTICAL); } else if (y < 10) { soundFX(TOP); agent[i].y = 390; //bounce(offHORIZONTAL); } else if (y > 390) { soundFX(BOTTOM); agent[i].y = 10; //bounce(offHORIZONTAL); } else { if ((x < 150 && lastX > 150) && (y < 150 || y > 250)) { bounce(offVERTICAL); } if ((x > 250 && lastX < 250) && (y < 150 || y > 250)) { bounce(offVERTICAL); } if ((y < 150 && lastY > 150) && (x < 150 || x > 250)) { bounce(offHORIZONTAL); } if ((y > 250 && lastY < 250) && (x < 150 || x > 250)) { bounce(offHORIZONTAL); } } } } //----------------------------------------------------------------------- run void run (void) { stop = false; message.data[0] = 0xC0; message.data[1] = 6; message.data[2] = 100; message.data[3] = 0; midiOutShortMsg(device, message.word); while (stop == false) { step(); Application->ProcessMessages(); } } //=========================================================================== // EVENT HANDLERS //=========================================================================== //------------------------------------------------------------ On Form Create void __fastcall TForm1::FormCreate(TObject *Sender) { midiOutOpen(&device, midiport, 0, 0, CALLBACK_NULL); randomize(); // defines an array of shapes with properties and events for (int i = 0; i < 24; i++) { shape[i] = new TShape(this); shape[i]->Parent = Form1; shape[i]->Visible = true; shape[i]->OnMouseDown = ShapeMouseDown; shape[i]->OnMouseUp = ShapeMouseUp; shape[i]->OnMouseMove = ShapeMouseMove; shape[i]->Height = 20; shape[i]->Width = 20; shape[i]->Left = 0; shape[i]->Top = 0; shape[i]->Shape = stCircle; shape[i]->Tag = i; shape[i]->Pen->Width = 2; shape[i]->Pen->Color = static_cast(colorRamp(25, 24 - shape[i]->Tag)); shape[i]->Brush->Color = static_cast(colorRamp(25, shape[i]->Tag)); } reset(); center(); } //------------------------------------------------------- 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 chosenAgent = shape->Tag; // remembers is we were stopped or running wasStopped = stop; stop = true; if (Button == 0) { // Begin drag object // Remembers that an shape was chosen for mouseMove and mouseUp agentWasJustChosen = true; // Remembers where on the shape the mouse was downed shapeDownX = X; shapeDownY = Y; } else { // Show nearest neighbor Form1->Canvas->MoveTo(agent[chosenAgent].x, agent[chosenAgent].y); Form1->Canvas->LineTo(agent[nearestNeighbor(chosenAgent)].x, agent[nearestNeighbor(chosenAgent)].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); chosenAgent = shape->Tag; if (agentWasJustChosen) { // this drags the shape along... shape->Left = shape->Left + X - shapeDownX; shape->Top = shape->Top + Y - shapeDownY; agent[chosenAgent].x = shape->Left + shape->Width / 2;; agent[chosenAgent].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[chosenAgent].x); EditY->Text = int(agent[chosenAgent].y); EditDirection->Text = agent[shape->Tag].direction; EditVelocity->Text = agent[shape->Tag].velocity; } //--------------------------------------------------------- On Shape Mouse Up // 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::ShapeMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { TShape *shape = dynamic_cast(Sender); // this is the end of the drag... agentWasJustChosen = false; EditAgent->Text = shape->Tag; // When the mouse moves over an shape its values are displayed EditX->Text = int(agent[chosenAgent].x); EditY->Text = int(agent[chosenAgent].y); EditDirection->Text = agent[shape->Tag].direction; EditVelocity->Text = agent[shape->Tag].velocity; // if we were running before the drag, then run... Form1->Refresh(); if (!wasStopped) { run(); } } //---------------------------------------------------------------- run button void __fastcall TForm1::ButtonRunClick(TObject *Sender) { run(); } //--------------------------------------------------------------- step button void __fastcall TForm1::ButtonStepClick(TObject *Sender) { stop = true; step(); } //--------------------------------------------------------------- stop button void __fastcall TForm1::ButtonStopClick(TObject *Sender) { stop = true; } //-------------------------------------------------------------- reset button void __fastcall TForm1::ButtonResetDirVelClick(TObject *Sender) { reset(); } //------------------------------------------------------------- circle button void __fastcall TForm1::ButtonCircleClick(TObject *Sender) { circle(); } //------------------------------------------------------------- center button void __fastcall TForm1::ButtonCenterClick(TObject *Sender) { center(); } //------------------------------------------------------------ scatter button void __fastcall TForm1::ButtonScatterClick(TObject *Sender) { scatter(); } //------------------------------------------------------ size change trackBar void __fastcall TForm1::TrackBarSizeChange(TObject *Sender) { int size = TrackBarSize->Position; for (int i = 0; i < 24; i++) { shape[i]->Height = size; shape[i]->Width = size; } } //----------------------------------------------------- sound type RadioGroup void __fastcall TForm1::RadioGroupSonificationClick(TObject *Sender) { soundType = RadioGroupSonification->ItemIndex; } //---------------------------------------------------------------------------