commit 60bd8bee4e7a6b6a475defafbcf3c834c6fd3edb Author: Joshua Coles Date: Wed Mar 1 20:54:06 2023 +0000 Import of Initially Provided Code diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fcf1123 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Final executable +/run + +# IDE files +.idea + +# Data files +*.csv diff --git a/DLASystem.cpp b/DLASystem.cpp new file mode 100644 index 0000000..c174371 --- /dev/null +++ b/DLASystem.cpp @@ -0,0 +1,319 @@ +// +// DLASystem.cpp +// + +#include "DLASystem.h" + +// colors +namespace colours { + GLfloat blue[] = { 0.1, 0.3, 0.9, 1.0 }; // blue + GLfloat red[] = { 1.0, 0.2, 0.1, 0.2 }; // red + GLfloat green[] = { 0.3, 0.6, 0.3, 1.0 }; // green + GLfloat paleGrey[] = { 0.7, 0.7, 0.7, 1.0 }; // green + GLfloat darkGrey[] = { 0.2, 0.2, 0.2, 1.0 }; // green +} + + +// this function gets called every step, +// if there is an active particle then it gets moved, +// if not then add a particle +void DLASystem::Update() { + if (lastParticleIsActive == 1) + moveLastParticle(); + else if (numParticles < endNum) { + addParticleOnAddCircle(); + setParticleActive(); + } + if (lastParticleIsActive == 0 || slowNotFast == 1) + glutPostRedisplay(); //Tell GLUT that the display has changed +} + + +void DLASystem::clearParticles() { + // delete particles and the particle list + for (int i = 0; i < numParticles; i++) { + delete particleList[i]; + } + particleList.clear(); + numParticles = 0; +} + +// remove any existing particles and setup initial condition +void DLASystem::Reset() { + // stop running + running = 0; + + clearParticles(); + + lastParticleIsActive = 0; + + // set the grid to zero + for (int i = 0; i < gridSize; i++) { + for (int j = 0; j < gridSize; j++) { + grid[i][j] = 0; + } + } + + // setup initial condition and parameters + addCircle = 10; + killCircle = 2.0*addCircle; + clusterRadius = 0.0; + // add a single particle at the origin + double pos[] = { 0.0, 0.0 }; + addParticle(pos); + + // set the view + int InitialViewSize = 40; + setViewSize(InitialViewSize); + +} + +// set the value of a grid cell for a particular position +// note the position has the initial particle at (0,0) +// but this corresponds to the middle of the grid array ie grid[ halfGrid ][ halfGrid ] +void DLASystem::setGrid(double pos[], int val) { + int halfGrid = gridSize / 2; + grid[(int)(pos[0] + halfGrid)][(int)(pos[1] + halfGrid)] = val; +} + +// read the grid cell for a given position +int DLASystem::readGrid(double pos[]) { + int halfGrid = gridSize / 2; + return grid[(int)(pos[0] + halfGrid)][(int)(pos[1] + halfGrid)]; +} + +// check if the cluster is big enough and we should stop: +// to be safe, we need the killCircle to be at least 2 less than the edge of the grid +int DLASystem::checkStop() { + if (killCircle + 2 >= gridSize / 2) { + pauseRunning(); + cout << "STOP" << endl; + glutPostRedisplay(); // update display + return 1; + } + else return 0; +} + +// add a particle to the system at a specific position +void DLASystem::addParticle(double pos[]) { + // create a new particle + Particle * p = new Particle(pos); + // push_back means "add this to the end of the list" + particleList.push_back(p); + numParticles++; + + // pos coordinates should be -gridSize/2 < x < gridSize/2 + setGrid(pos, 1); +} + +// add a particle to the system at a random position on the addCircle +// if we hit an occupied site then we do nothing except print a message +// (this should never happen) +void DLASystem::addParticleOnAddCircle() { + double pos[2]; + double theta = rgen.random01() * 2 * M_PI; + pos[0] = ceil(addCircle * cos(theta)); + pos[1] = ceil(addCircle * sin(theta)); + if (readGrid(pos) == 0) + addParticle(pos); + else + cout << "FAIL " << pos[0] << " " << pos[1] << endl; +} + +// send back the position of a neighbour of a given grid cell +// NOTE: there is no check that the neighbour is inside the grid, +// this has to be done separately... +void DLASystem::setPosNeighbour(double setpos[], double pos[], int val) { + switch (val) { + case 0: + setpos[0] = pos[0] + 1.0; + setpos[1] = pos[1]; + break; + case 1: + setpos[0] = pos[0] - 1.0; + setpos[1] = pos[1]; + break; + case 2: + setpos[0] = pos[0]; + setpos[1] = pos[1] + 1.0; + break; + case 3: + setpos[0] = pos[0]; + setpos[1] = pos[1] - 1.0; + break; + } +} + +// if the view is smaller than the kill circle then increase the view area (zoom out) +void DLASystem::updateViewSize() { + double mult = 1.2; + if (viewSize < 2.0*killCircle) { + setViewSize(viewSize * mult); + } +} + +// set the view to be the size of the add circle (ie zoom in on the cluster) +void DLASystem::viewAddCircle() { + setViewSize(2.0*addCircle); // factor of 2 is to go from radius to diameter +} + +// when we add a particle to the cluster, we should update the cluster radius +// and the sizes of the addCircle and the killCircle +void DLASystem::updateClusterRadius(double pos[]) { + + double rr = distanceFromOrigin(pos); + if (rr > clusterRadius) { + clusterRadius = rr; + // this is how big addCircle is supposed to be: + // either 20% more than cluster radius, or at least 5 bigger. + double check = clusterRadius * addRatio; + if (check < clusterRadius + 5) + check = clusterRadius + 5; + // if it is smaller then update everything... + if (addCircle < check) { + addCircle = check; + killCircle = killRatio * addCircle; + updateViewSize(); + } + checkStop(); + } +} + +// make a random move of the last particle in the particleList +void DLASystem::moveLastParticle() { + int rr = rgen.randomInt(4); // pick a random number in the range 0-3, which direction do we hop? + double newpos[2]; + + Particle *lastP = particleList[numParticles - 1]; + + setPosNeighbour(newpos, lastP->pos, rr); + + if (distanceFromOrigin(newpos) > killCircle) { + //cout << "#deleting particle" << endl; + setGrid(lastP->pos, 0); + particleList.pop_back(); // remove particle from particleList + numParticles--; + setParticleInactive(); + } + // check if destination is empty + else if (readGrid(newpos) == 0) { + setGrid(lastP->pos, 0); // set the old grid site to empty + // update the position + particleList[numParticles - 1]->pos[0] = newpos[0]; + particleList[numParticles - 1]->pos[1] = newpos[1]; + setGrid(lastP->pos, 1); // set the new grid site to be occupied + + // check if we stick + if (checkStick()) { + //cout << "stick" << endl; + setParticleInactive(); // make the particle inactive (stuck) + updateClusterRadius(lastP->pos); // update the cluster radius, addCircle, etc. + + if (numParticles % 100 == 0 && logfile.is_open()) { + logfile << numParticles << " " << clusterRadius << endl; + } + } + } + else { + // if we get to here then we are trying to move to an occupied site + // (this should never happen as long as the sticking probability is 1.0) + cout << "reject " << rr << endl; + cout << lastP->pos[0] << " " << lastP->pos[1] << endl; + //cout << newpos[0] << " " << newpos[1] << " " << (int)newpos[0] << endl; + //printOccupied(); + } +} + +// check if the last particle should stick (to a neighbour) +int DLASystem::checkStick() { + Particle *lastP = particleList[numParticles - 1]; + int result = 0; + // loop over neighbours + for (int i = 0; i < 4; i++) { + double checkpos[2]; + setPosNeighbour(checkpos, lastP->pos, i); + // if the neighbour is occupied... + if (readGrid(checkpos) == 1) + result = 1; + } + return result; +} + + +// constructor +DLASystem::DLASystem(Window *set_win) { + cout << "creating system, gridSize " << gridSize << endl; + win = set_win; + numParticles = 0; + endNum = 1000; + + // allocate memory for the grid, remember to free the memory in destructor + grid = new int*[gridSize]; + for (int i = 0; i < gridSize; i++) { + grid[i] = new int[gridSize]; + } + slowNotFast = 1; + // reset initial parameters + Reset(); + + addRatio = 1.2; // how much bigger the addCircle should be, compared to cluster radius + killRatio = 1.7; // how much bigger is the killCircle, compared to the addCircle + + // this opens a logfile, if we want to... + //logfile.open("opfile.txt"); +} + +// destructor +DLASystem::~DLASystem() { + // strictly we should not print inside the destructor but never mind... + cout << "deleting system" << endl; + // delete the particles + clearParticles(); + // delete the grid + for (int i = 0; i < gridSize; i++) + delete[] grid[i]; + delete[] grid; + + if (logfile.is_open()) + logfile.close(); +} + + + +// this draws the system +void DLASystem::DrawSquares() { + + // draw the particles + double halfSize = 0.5; + for (int p = 0; p < numParticles; p++) { + double *vec = particleList[p]->pos; + glPushMatrix(); + if (p == numParticles - 1 && lastParticleIsActive == 1) + glColor4fv(colours::red); + else if (p == 0) + glColor4fv(colours::green); + else + glColor4fv(colours::blue); + glRectd(drawScale*(vec[0] - halfSize), + drawScale*(vec[1] - halfSize), + drawScale*(vec[0] + halfSize), + drawScale*(vec[1] + halfSize)); + glPopMatrix(); + } + + // print some information (at top left) + // this ostringstream is a way to create a string with numbers and words (similar to cout << ... ) + ostringstream str; + str << "num " << numParticles << " size " << clusterRadius; + + // print the string + win->displayString(str, -0.9, 0.9, colours::red); + + // if we are paused then print this (at bottom left) + if (running == 0) { + ostringstream pauseStr; + pauseStr << "paused"; + win->displayString(pauseStr, -0.9, -0.9, colours::red); + } + +} diff --git a/DLASystem.h b/DLASystem.h new file mode 100644 index 0000000..4b05fe6 --- /dev/null +++ b/DLASystem.h @@ -0,0 +1,154 @@ +#pragma once + +#include +#include +#include +#include +#include +#define _USE_MATH_DEFINES +#include +#include +#include +#include + +#include "Window.h" +#include "Particle.h" +#include "rnd.h" + +using namespace std; + + +class DLASystem { + private: + // these are private variables and functions that the user will not see + + Window *win; // window in which the system is running + + // list of particles + vector particleList; + int numParticles; + + // delete particles and clear the particle list + void clearParticles(); + + // size of cluster + double clusterRadius; + // these are related to the DLA algorithm + double addCircle; + double killCircle; + + // size of grid + static const int gridSize = 1600; + int **grid; // this will be a 2d array that stores whether each site is occupied + + // the window draws only part of the grid, viewSize controls how much... + double viewSize; + double drawScale; + + // random number generator, class name is rnd, instance is rgen + rnd rgen; + + // output file (not used at the moment) + ofstream logfile; + + // number of particles at which the simulation will stop + // (the value is set in constructor) + int endNum; + + // the values of these variables are set in the constructor + double addRatio; // how much bigger the addCircle should be, compared to cluster radius + double killRatio; // how much bigger is the killCircle, compared to the addCircle + + + public: + // these are public variables and functions + + // update the system: if there is an active particle then move it, + // else create a new particle (on the adding circle) + void Update(); + + // draw particles as squares + void DrawSquares(); + + // is the simulation running (1) or paused (0) ? + int running; + + // slowNotFast is +1 for slow running, 0 for fast + int slowNotFast; + + // lastParticleIsActive is +1 if there is an active particle in the system, otherwise 0 + int lastParticleIsActive; + + // constructor + DLASystem(Window *set_win); + // destructor + ~DLASystem(); + + // delete all particles and reset + void Reset(); + + // this sets the seed for the random numbers + void setSeed(int s) { rgen.setSeed(s); } + + // check whether we should stop (eg the cluster has reached the edge of the grid) + int checkStop(); + + // stop/start the algorithm + void setRunning() { if ( checkStop()==0 ) running = 1; } + void pauseRunning() { running = 0; } + // set whether it runs fast or slow + void setSlow() { slowNotFast = 1; } + void setFast() { slowNotFast = 0; } + void setSuperFast() { slowNotFast = -1; } + + // set which part of the grid is visible on the screen + // basically the screen shows co-ordinates -vv < x < vv + // where vv is the input value + void setViewSize(double vv) { viewSize = vv; drawScale = 2.0/viewSize; } + + // if the killcircle is almost as big as the view then increase the view + void updateViewSize(); + + // set the view to be the approx size of the addCircle + void viewAddCircle(); + + // if pos is outside the cluster radius then set clusterRadius to be the distance to pos. + void updateClusterRadius( double pos[] ); + + // set and read grid entries associated with a given position + void setGrid(double pos[], int val); + int readGrid(double pos[]); + + // return the distance of a given point from the origin + double distanceFromOrigin(double pos[]) { + return sqrt( pos[0]*pos[0] + pos[1]*pos[1] ); + } + + // set whether there is an active particle in the system or not + void setParticleActive() { lastParticleIsActive = 1; } + void setParticleInactive() { lastParticleIsActive = 0; } + + // add a particle at pos + void addParticle(double pos[]); + // add a particle at a random point on the addCircle + void addParticleOnAddCircle(); + + // assign setpos to the position of a neighbour of pos + // which neighbour we look at is determined by val (=0,1,2,3) + void setPosNeighbour(double setpos[], double pos[], int val); + + // this attempts to move the last particle in the List to a random neighbour + // if the neighbour is occupied then nothing happens + // the function also checks if the moving particle should stick. + void moveLastParticle(); + + // check whether the last particle should stick + // currently it sticks whenever it touches another particle + int checkStick(); + + // set the background colour for the window + // it would be better for an OOP philosophy to make these member functions for the Window class + // but we are being a bit lazy here + void setWinBackgroundWhite() { glClearColor(1.0, 1.0, 1.0, 1.0); } + void setWinBackgroundBlack() { glClearColor(0.0, 0.0, 0.0, 0.0); } +}; diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..354e5db --- /dev/null +++ b/Makefile @@ -0,0 +1,72 @@ +# ====================================================================================== # +# From the Author # +# ====================================================================================== # +# ! The purpose of this Makefile is to build the DLASystem project +# ! This makefile was adapted to work with any cpp project on OSX + +# ====================================================================================== # +# Variables of the Makefile # +# ====================================================================================== # + +CXX = clang++ + +CXXFLAGS = -Wall -Wextra -g -O0 + +IFLAGS = -I/usr/local/include -I/usr/include + +LFLAGS = -L/usr/local/lib -lm -framework OpenGL -framework GLUT + +# ------------------------------------------ +# FOR GENERIC MAKEFILE: +# 1 - Binary directory +# 2 - Source directory +# 3 - Executable name +# 4 - Sources names +# 5 - Dependencies names +# ------------------------------------------ +BIN = . +SOURCE = . +EXEC = ./run +SOURCES = $(wildcard $(SOURCE)/*.cpp) +OBJECTS = $(SOURCES:.cpp=.o) + +# ====================================================================================== # +# Targets of the Makefile # +# target_name : dependency # +# command # +# ====================================================================================== # + +# ------------------------------------------ +# ! - all : Compiles everything +# ! - help : Shows this help +# ! - clean : erases all object files *.o +# ! and all binary executables +# ------------------------------------------ +all : $(BIN)/run + +test: $(BIN)/hllc_test + +help : + @grep -E "^# !" Makefile | sed -e 's/# !/ /g' + +clean: + rm -f $(EXEC) $(OBJECTS) + +# ------------------------------------------ +# Executable +# ------------------------------------------ +$(EXEC): $(OBJECTS) + $(CXX) $(OBJECTS) -o $(EXEC) $(IFLAGS) $(LFLAGS) + +# ------------------------------------------ +# Temorary files (*.o) (IFLAGS should be added here) +# ------------------------------------------ +$(SOURCE)/%.o: $(SOURCE)/%.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ $(IFLAGS) $(LFLAGS) + + + + + + + diff --git a/Particle.h b/Particle.h new file mode 100644 index 0000000..d3a3d21 --- /dev/null +++ b/Particle.h @@ -0,0 +1,20 @@ +#pragma once + +class Particle { + public: + static const int dim = 2; // we are in two dimensions + double *pos; // pointer to an array of size dim, to store the position + + // default constructor + Particle() { + pos = new double[dim]; + } + // constructor, with a specified initial position + Particle(double set_pos[]) { + pos = new double[dim]; + for (int d=0;d +#include +#include +#include + +using namespace std; + +class Window { + public: + string title; + int size[2]; + int pos[2]; + + void locateOnScreen() { + // the fx sets where on the screen the window will appear + // (values should be between 0 and 1) + double fx[] = { 0.7,0.5 }; + pos[0] = (glutGet(GLUT_SCREEN_WIDTH) - size[0]) * fx[0]; + pos[1] = (glutGet(GLUT_SCREEN_HEIGHT) - size[1]) * fx[1]; + } + + // constructor, size is in pixels + Window(int set_size[], string &set_title); + + // function which prints a string to the screen, at a given position, with a given color + // note position is "absolute", not easy to get two strings spaced one above each other like this + void displayString( ostringstream &str, double x, double y, GLfloat col[]); + + +}; + + diff --git a/mainDLA.cpp b/mainDLA.cpp new file mode 100644 index 0000000..6d02350 --- /dev/null +++ b/mainDLA.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include +#include + +#include "DLASystem.h" +#include "Window.h" + +using namespace std; + +// functions which are needed for openGL go into a namespace so that we can identify them +namespace drawFuncs { + void handleKeypress(unsigned char key, int x, int y); + void display(void); + void update(int val); + void introMessage(); +} + +// this is a global pointer, which is how we access the system itself +DLASystem *sys; + +int main(int argc, char **argv) { + // turn on glut + glutInit(&argc, argv); + + int window_size[] = { 480,480 }; + string window_title("simple DLA simulation"); + + // create a window + Window *win = new Window(window_size,window_title); + + // create the system + sys = new DLASystem(win); + + // this is the seed for the random numbers + int seed = 6; + cout << "setting seed " << seed << endl; + sys->setSeed(seed); + + // print the "help" message to the console + drawFuncs::introMessage(); + + // tell openGL how to redraw the screen and respond to the keyboard + glutDisplayFunc( drawFuncs::display ); + glutKeyboardFunc( drawFuncs::handleKeypress ); + + // tell openGL to do its first update after waiting 10ms + int wait = 10; + int val = 0; + glutTimerFunc(wait, drawFuncs::update, val); + + // start the openGL stuff + glutMainLoop(); + + return 0; +} + +// this is just a help message +void drawFuncs::introMessage() { + cout << "Keys (while in graphics window):" << endl << " q or e to quit (or exit)" << endl; + cout << " h to print this message (help)" << endl; + cout << " u for a single update" << endl; + cout << " g to start running (go)" << endl; + cout << " p to pause running" << endl; + cout << " s to run in slow-mode" << endl; + cout << " f to run in fast-mode" << endl; + cout << " r to clear everything (reset)" << endl; + cout << " z to pause and zoom in" << endl; + cout << " w or b to change background colour to white or black" << endl; +} + +// openGL function deals with the keyboard +void drawFuncs::handleKeypress(unsigned char key, int x, int y) { + switch (key) { + case 'h': + drawFuncs::introMessage(); + break; + case 'q': + case 'e': + cout << "Exiting..." << endl; + // delete the system + delete sys; + exit(0); + break; + case 'p': + cout << "pause" << endl; + sys->pauseRunning(); + break; + case 'g': + cout << "go" << endl; + sys->setRunning(); + glutTimerFunc(0, drawFuncs::update, 0); + break; + case 's': + cout << "slow" << endl; + sys->setSlow(); + break; + case 'w': + cout << "white" << endl; + sys->setWinBackgroundWhite(); + break; + case 'b': + cout << "black" << endl; + sys->setWinBackgroundBlack(); + break; + case 'f': + cout << "fast" << endl; + sys->setFast(); + break; + case 'r': + cout << "reset" << endl; + sys->Reset(); + break; + case 'z': + cout << "zoom" << endl; + sys->pauseRunning(); + sys->viewAddCircle(); + break; + case 'u': + cout << "upd" << endl; + sys->Update(); + break; + } + // tell openGL to redraw the window + glutPostRedisplay(); +} + +// this function gets called whenever the algorithm should do its update +void drawFuncs::update(int val) { + int wait; // time to wait between updates (milliseconds) + + if ( sys->running ) { + if ( sys->slowNotFast == 1) + wait = 10; + else + wait = 0; + + sys->Update(); + + // tell openGL to call this funtion again after "wait" milliseconds + glutTimerFunc(wait, drawFuncs::update, 0); + } + +} + +// this function redraws the window when necessary +void drawFuncs::display() { + // Clear the window or more specifically the frame buffer... + // This happens by replacing all the contents of the frame + // buffer by the clear color (black in our case) + glClear(GL_COLOR_BUFFER_BIT); + + // this puts the camera at the origin (not sure why) with (I think) z axis out of page and y axis up + // there is also the question of the GL perspective which is not set up in any clear way at the moment + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt(0.0, 0.0, 1.0, /* camera position */ + 0.0, 0.0, -1.0, /* point to look at */ + 0.0, 1.0, 0.0); /* up direction */ + + //sys->DrawSpheres(); + sys->DrawSquares(); + + // Swap contents of backward and forward frame buffers + glutSwapBuffers(); +} + diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..fe83645 --- /dev/null +++ b/readme.txt @@ -0,0 +1,27 @@ +To run the code on a Mac through the command line: + +1. Download Makefile from this section + +2. Put Makefile in same directory as DLASystem.cpp (! Remove the .txt extension if present !) + +3. Relative to MS Visual Studio code, these files include the following changes: DLASystem.h Window.h mainDLA.cpp + +change #include + +to #include + +4. Open the terminal in the same folder as these files + +5. Type "make" to build project + +6. Run the program by typing "./run" + +7. To edit your files, run gedit, save the code, and recompile with "make -B" before running the program again. + + + +Thanks to Eliot Ayache for figuring this out! + + + +PS For Coursework 2: In addition, there needs to be a change in Line 72 of MAIN_Source.cpp file from "glutLeaveMainLoop();" to "exit(1);" \ No newline at end of file diff --git a/rnd.h b/rnd.h new file mode 100644 index 0000000..98e6c29 --- /dev/null +++ b/rnd.h @@ -0,0 +1,33 @@ +#pragma once +#include + +// ... don't worry how this all works +// ... member functions that you may want to use: +// random01() returns a random double between 0 and 1 +// randomInt(max) returns a random int between 0 and max-1 (inclusive) + +class rnd { +private: + // nuts and bolts.. should not need to touch this. + std::default_random_engine generator; + int genMax; + std::uniform_int_distribution *intmax; + std::uniform_real_distribution *real01; + +public: + // constructor + rnd() { + genMax = 0x7fffffff; + //cout << "genMax is " << generator.max() << endl; + intmax = new std::uniform_int_distribution(0, genMax); + real01 = new std::uniform_real_distribution(0.0, 1.0); + } + // destructor + ~rnd() { delete intmax; delete real01; } + + // set the random seed + void setSeed(int seed) { generator.seed(seed); } + // member functions for generating random double in [0,1] and random integer in [0,max-1] + double random01() { return (*real01)(generator); } + int randomInt(int max) { return (*intmax)(generator) % max; } +};