//=========================================================================== // 1000 CANVAS RENDERED AGENTS // Version 8 - Multiple Timers // 4 November 2010 // // EVERYTHING RUNS MORE SLOWLY IN WINDOWS 7 // This is the beginning of a new foundation. // Illustrating class construction, a great deal of code // has been moved to the class declaration and definition. // // Note: The Canvas->Rectangle method runs ABOUT 10 times faster // than the Canvas->Ellipse method and about 100 times faster // than moving TImages around the screen. // Use Canvas->Polyline or Canvas->Polygon to vary the visualization. // Trials runs 5-10 times faster than no trails. //=========================================================================== #include #include #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; #define POP 1000 // maximum population // ====================================================== VARIABLES & CLASSES bool stop = true; bool move = false; bool anAgentHasBeenChosen = false; bool fixCursorXY = false; bool timers = false; // for throwing an agent double downX, downY; double upX, upY; double downUpX, downUpY; double timeUp, timeDown; double timeDownUp; double weightedAveDirection; double tangent; double sumXdistancesFromMe, sumYdistancesFromMe, weightedAveSumXdistances, weightedAveSumYdistances; double xDistanceFromMe, yDistanceFromMe; double ave_abs_dX, ave_abs_dY; double weightedAveVelocity, sum_dH, my_H; double noiseX, noiseY; int variance; double directionVariance; int iterations; int delay = 0; TColor back = clGray; TColor fore = clLime; int i; int dist, chosen; int pop = 100; // running population <= POP int lastPop = 100; int rule; int radius = 20; int neighbors; int independence = 20; int cursorX, cursorY; TColor userColor = clLime; // user chosen color TColor dynaColor = clLime; // dynamically chosen color TColor defaultColor = clLime; //--------------------------------------------------- Color Ramp Version 2008 TColor colorRamp(int part, int whole) { if (whole == 0) whole++; // prevent divide by zero int pixelDistanceAlongEdges = (part * 1792) / whole; int red, green, blue; // Which edge of the color cube are we on? if (pixelDistanceAlongEdges < 256) { // from BLACK to BLUE red = 0; green = 0; blue = pixelDistanceAlongEdges; } else if (pixelDistanceAlongEdges < 512) { // from BLUE to CYAN red = 0; green = pixelDistanceAlongEdges - 256; blue = 255; } else if (pixelDistanceAlongEdges < 768) { // from CYAN to GREEN red = 0; green = 255; blue = 255 - (pixelDistanceAlongEdges - 512); } else if (pixelDistanceAlongEdges < 1024) { // from GREEN to YELLOW red = (pixelDistanceAlongEdges - 768); green = 255; blue = 0; } else if (pixelDistanceAlongEdges < 1280) { // from YELLOW to RED red = 255; green= 255-(pixelDistanceAlongEdges - 1024); blue = 0; } else if (pixelDistanceAlongEdges < 1536) { // from RED to MAGENTA red = 255; green= 0; blue = pixelDistanceAlongEdges - 1280; } else { // from MAGENTA to WHITE red = 255; green = pixelDistanceAlongEdges - 1537; blue = 255; } return static_cast(RGB(red, green, blue)); } // this is a description of an abstract class called "agents" class agentClass { public: double x, y ; double dx, dy; double dxx, dyy; TPoint points[6]; TColor color; float height, width; void erase (void) { // size is dependent upon speed points[0].x = x + dx * 10; points[0].y = y + dy * 10; points[1].x = x - dx * 10 + 4 * dy; points[1].y = y - dy * 10 - 4 * dx; points[2].x = x - dx * 10 - 4 * dy; points[2].y = y - dy * 10 + 4 * dx; Form1->PaintBox1->Canvas->Pen->Color = back; Form1->PaintBox1->Canvas->Brush->Color = back; Form1->PaintBox1->Canvas->Polygon(points, 2); } void draw (void) { // size is dependent upon speed points[0].x = x + dx * 10; points[0].y = y + dy * 10; points[1].x = x - dx * 10 + 4 * dy; points[1].y = y - dy * 10 - 4 * dx; points[2].x = x - dx * 10 - 4 * dy; points[2].y = y - dy * 10 + 4 * dx; if (Form1->RadioGroupColor->ItemIndex == 0) { // your color dynaColor = color; } if (Form1->RadioGroupColor->ItemIndex == 1) { // by speed dynaColor = colorRamp(velocity() * 300, 1000); } if (Form1->RadioGroupColor->ItemIndex == 2) { // by location dynaColor = colorRamp(x + y, 1800); } if (Form1->RadioGroupColor->ItemIndex == 3) { // by neighbors in radius dynaColor = colorRamp(neighbors + 1, 20); } Form1->PaintBox1->Canvas->Brush->Color = dynaColor; if (Form1->RadioGroupTrail->ItemIndex == 2) { Form1->PaintBox1->Canvas->Pen->Color = dynaColor; Application->ProcessMessages(); } else Form1->PaintBox1->Canvas->Pen->Color = clBlack; Form1->PaintBox1->Canvas->Polygon(points, 2); } void shrink (void) { erase(); // needs code for Polygon draw(); } void grow (void) { erase(); // needs code for Polygon draw(); } void move (void) { x += dx; y += dy; if (Form1->RadioGroupSpace->ItemIndex == 0) { // Bounce if (x > 885 || x < 15) { dx = -dx; } if (y > 885 || y < 15) { dy = -dy; } } else { // Wrap if (x > 895 ) x = 5; if (x < 5) x = 895; if (y > 895) y = 5; if (y < 5) y = 895; } draw(); } double velocity (void) { // is a function of dy, dx return sqrt(pow(dx, 2) + pow(dy, 2)); } double direction (void) { if (dx == 0) dx = .00001; return atan2(dy, dx); } }; agentClass agent[POP]; // creates an array "agent" of type "agentClass" // ================================================================ FUNCTIONS void initialize (void) { for (int i = 0; i < POP; i++) { agent[i].erase(); agent[i].height = 10; agent[i].width = 10; agent[i].color = defaultColor; //agent[i].direction = double(i) / 3; agent[i].x = random(890) + 5; // in a 900 x 900 world agent[i].y = random(890) + 5; // in a 900 x 900 world agent[i].dy = double(random(100) - 50) / 25.00; // -2 to +2 agent[i].dx = double(random(100) - 50) / 25.00; // -2 to +2 agent[i].draw(); } Form1->TrackBarSpeed->Position = Form1->TrackBarSpeed->Max; } void reset (void) { Form1->PaintBox1->Refresh(); iterations = 0; for (int i = 0; i < pop; i++) { agent[i].erase(); agent[i].height = 10; agent[i].width = 10; agent[i].color = defaultColor; // agent[i].direction = double(i) / 3; agent[i].x = random(890) + 5; // in a 900 x 900 world agent[i].y = random(890) + 5; // in a 900 x 900 world agent[i].dy = double(random(100) - 50) / 25.00; // -2 to +2 agent[i].dx = double(random(100) - 50) / 25.00; // -2 to +2 agent[i].draw(); } } int nneighbor (int me) { int is; int minDist = 1000; for (int i = 0; i < pop; i++) { if (i == me) continue; dist = (sqrt(pow((agent[i].x - agent[me].x), 2)) + sqrt(pow((agent[i].y - agent[me].y), 2))); if (dist < minDist) { minDist = dist; is = i; } } return is; } void averageInSquareRadius (int me) { neighbors = 0; sumXdistancesFromMe = 0; sumYdistancesFromMe = 0; sum_dH = 0; xDistanceFromMe = 0; yDistanceFromMe = 0; for (int i = 0; i < pop; i++) { // look at everyone else if (i == me) continue; // but skip me xDistanceFromMe = abs(agent[i].x - agent[me].x); yDistanceFromMe = abs(agent[i].y - agent[me].y); if ((xDistanceFromMe < radius) && (yDistanceFromMe < radius)) { neighbors++; sumXdistancesFromMe += agent[i].dx; sumYdistancesFromMe += agent[i].dy; } } } void step (void) { for (int i = 0; i < pop; i++) { // draw trails or not if (Form1->RadioGroupTrail->ItemIndex == 0) { agent[i].erase(); } // adopt nearest neighbors direction and speed if (rule == 1) { agent[i].dx = agent[nneighbor(i)].dx; agent[i].dy = agent[nneighbor(i)].dy; } // adopt weighted average direction and speed of those // within a square radius if (rule == 2) { averageInSquareRadius(i); // look around // add all x & y components weightedAveSumXdistances = (independence * agent[i].dx + sumXdistancesFromMe) / (neighbors + independence); weightedAveSumYdistances = (independence * agent[i].dy + sumYdistancesFromMe) / (neighbors + independence); if (weightedAveSumXdistances == 0) weightedAveSumXdistances = .00001; if (weightedAveSumYdistances == 0) weightedAveSumYdistances = .00001; // compute noise directionVariance = 0.001 * ((random(variance + 1)) - 0.5 * variance); weightedAveDirection = atan2(weightedAveSumYdistances, weightedAveSumXdistances) + directionVariance; my_H = agent[i].velocity(); weightedAveVelocity = (independence * my_H + sum_dH) / (neighbors + independence); agent[i].dx = my_H * cos(weightedAveDirection); agent[i].dy = my_H * sin(weightedAveDirection); } // flock to cursor (flee from cursor is less interesting) if (rule == 3) { sumXdistancesFromMe = cursorX - agent[i].x; sumYdistancesFromMe = cursorY - agent[i].y; weightedAveSumXdistances = (independence * agent[i].dx + sumXdistancesFromMe) / (independence); weightedAveSumYdistances = (independence * agent[i].dy + sumYdistancesFromMe) / (independence); if (weightedAveSumXdistances == 0) weightedAveSumXdistances = .00001; if (weightedAveSumYdistances == 0) weightedAveSumYdistances = .00001; // compute noise directionVariance = 0.001 * ((random(variance + 1)) - 0.5 * variance); weightedAveDirection = atan2(weightedAveSumYdistances, weightedAveSumXdistances) + directionVariance; my_H = agent[i].velocity(); weightedAveVelocity = (independence * my_H + sum_dH) / (neighbors + independence); agent[i].dx = my_H * cos(weightedAveDirection); agent[i].dy = my_H * sin(weightedAveDirection); } agent[i].move(); } Sleep(delay); } void refresh (void) { Form1->PaintBox1->Refresh(); for (int i = 0; i < pop; i++) { agent[i].draw(); } } // =========================================================== EVENT HANDLERS //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { DoubleBuffered = true; randomize(); initialize(); } //--------------------------------------------------------------------------- void __fastcall TForm1::ButtonShrinkClick(TObject *Sender) { for (int i = 0; i < pop; i++) { agent[i].shrink(); } } //--------------------------------------------------------------------------- void __fastcall TForm1::ButtonGrowClick(TObject *Sender) { for (int i = 0; i < pop; i++) { agent[i].grow(); } } //--------------------------------------------------------------------------- void __fastcall TForm1::RadioGroupTrailClick(TObject *Sender) { Form1->Refresh(); } //--------------------------------------------------------------------------- void __fastcall TForm1::ButtonStepClick(TObject *Sender) { stop = true; step(); } //--------------------------------------------------------------------------- void __fastcall TForm1::ButtonRunClick(TObject *Sender) { stop = false; while (stop == false) { Application->ProcessMessages(); iterations++; step(); } } //--------------------------------------------------------------------------- void __fastcall TForm1::ButtonStopClick(TObject *Sender) { stop = true; } //--------------------------------------------------------------------------- void __fastcall TForm1::ButtonResetClick(TObject *Sender) { reset(); Form1->Paint(); } //--------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { if (Button == 0) { // set, unset, cursor X and Y fixCursorXY = !fixCursorXY; if (fixCursorXY == true) { cursorX = X; cursorY = Y; } } if (Button == 1) { // allow user to color an agent // a chosen agent is the nearest agent no farther away from its radius int shortestDistance = 1000; for (i = 0; i < POP; i++) { int distance = abs(X - agent[i].x) + abs(Y - agent[i].y); if (distance < shortestDistance) { shortestDistance = distance; chosen = i; } } if( shortestDistance < agent[chosen].width / 2) { agent[chosen].color = userColor; agent[chosen].draw(); } } } //--------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1MouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { if (fixCursorXY == false) { cursorX = X; // useful for some rules cursorY = Y; } } //--------------------------------------------------------------------------- void __fastcall TForm1::FormShow(TObject *Sender) { refresh(); } //--------------------------------------------------------------------------- void __fastcall TForm1::ButtonSelectColorClick(TObject *Sender) { if(Form1->ColorDialog1->Execute()) { userColor = ColorDialog1->Color; Form1->ShapeColor->Brush->Color = userColor; } } //--------------------------------------------------------------------------- void __fastcall TForm1::TrackBarSpeedChange(TObject *Sender) { delay = TrackBarSpeed->Max - TrackBarSpeed->Position; } //--------------------------------------------------------------------------- void __fastcall TForm1::TrackBarPopulationChange(TObject *Sender) { lastPop = pop; pop = TrackBarPopulation->Position; Form1->EditPop->Text = pop; if (pop < lastPop) Form1->PaintBox1->Refresh(); reset(); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormActivate(TObject *Sender) { refresh(); } //--------------------------------------------------------------------------- void __fastcall TForm1::RadioGroupRulesClick(TObject *Sender) { rule = RadioGroupRules->ItemIndex; } //--------------------------------------------------------------------------- void __fastcall TForm1::TrackBarRadiusChange(TObject *Sender) { radius = TrackBarRadius->Position; Form1->EditRadius->Text = radius; } //--------------------------------------------------------------------------- void __fastcall TForm1::TrackBarIndependenceChange(TObject *Sender) { independence = TrackBarIndependence->Position; Form1->EditStubbornness->Text = independence; } //--------------------------------------------------------------------------- void __fastcall TForm1::TrackBarVarianceChange(TObject *Sender) { variance = TrackBarVariance->Position; Form1->EditVariance->Text = variance; } //--------------------------------------------------------------------------- void __fastcall TForm1::TimerColorTimer(TObject *Sender) { Form1->RadioGroupColor->ItemIndex = random(3) + 1; } //--------------------------------------------------------------------------- void __fastcall TForm1::TimerRulesTimer(TObject *Sender) { Form1->RadioGroupRules->ItemIndex = random(4); } //--------------------------------------------------------------------------- void __fastcall TForm1::TimerRadiusTimer(TObject *Sender) { Form1->TrackBarRadius->Position = random(100); } //--------------------------------------------------------------------------- void __fastcall TForm1::TimerIndependenceTimer(TObject *Sender) { Form1->TrackBarIndependence->Position = random(2000) + 1; } //--------------------------------------------------------------------------- void __fastcall TForm1::TimerVarianceTimer(TObject *Sender) { Form1->TrackBarVariance->Position = random(200); } //--------------------------------------------------------------------------- void __fastcall TForm1::TimerResetTimer(TObject *Sender) { reset(); } //--------------------------------------------------------------------------- void __fastcall TForm1::TimerTrailsTimer(TObject *Sender) { Form1->RadioGroupTrail->ItemIndex = random(3); } //--------------------------------------------------------------------------- void __fastcall TForm1::TimerCursorTimer(TObject *Sender) { cursorX = random(900); cursorY = random(900); } //--------------------------------------------------------------------------- void __fastcall TForm1::RadioGroupTimersClick(TObject *Sender) { timers = !timers; cursorX = random(900); cursorY = random(900); if (timers == true) { Form1->TimerColor->Enabled = true; Form1->TimerRules->Enabled = true; Form1->TimerRadius->Enabled = true; Form1->TimerIndependence->Enabled = true; Form1->TimerVariance->Enabled = true; Form1->TimerReset->Enabled = true; Form1->TimerTrails->Enabled = true; Form1->TimerCursor->Enabled = true; } else { Form1->TimerColor->Enabled = false; Form1->TimerRules->Enabled = false; Form1->TimerRadius->Enabled = false; Form1->TimerIndependence->Enabled = false; Form1->TimerVariance->Enabled = false; Form1->TimerReset->Enabled = false; Form1->TimerTrails->Enabled = false; Form1->TimerCursor->Enabled = false; } } //---------------------------------------------------------------------------