From 60bd8bee4e7a6b6a475defafbcf3c834c6fd3edb Mon Sep 17 00:00:00 2001 From: Joshua Coles Date: Wed, 1 Mar 2023 20:54:06 +0000 Subject: [PATCH] Import of Initially Provided Code --- .gitignore | 8 ++ DLASystem.cpp | 319 ++++++++++++++++++++++++++++++++++++++++++++++++++ DLASystem.h | 154 ++++++++++++++++++++++++ Makefile | 72 ++++++++++++ Particle.h | 20 ++++ Window.cpp | 26 ++++ Window.h | 34 ++++++ mainDLA.cpp | 169 ++++++++++++++++++++++++++ readme.txt | 27 +++++ rnd.h | 33 ++++++ 10 files changed, 862 insertions(+) create mode 100644 .gitignore create mode 100644 DLASystem.cpp create mode 100644 DLASystem.h create mode 100644 Makefile create mode 100644 Particle.h create mode 100644 Window.cpp create mode 100644 Window.h create mode 100644 mainDLA.cpp create mode 100644 readme.txt create mode 100644 rnd.h 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; } +};