diff --git a/src/Config.h b/src/Config.h index facb598a9..b1131436c 100644 --- a/src/Config.h +++ b/src/Config.h @@ -29,6 +29,7 @@ #define SERVER "powdertoy.co.uk" #define SCRIPTSERVER "powdertoy.co.uk" +#define STATICSERVER "static.powdertoy.co.uk" #define LOCAL_SAVE_DIR "Saves" diff --git a/src/client/Client.cpp b/src/client/Client.cpp index ad12da453..1d550fff4 100644 --- a/src/client/Client.cpp +++ b/src/client/Client.cpp @@ -42,7 +42,28 @@ Client::~Client() unsigned char * Client::GetSaveData(int saveID, int saveDate, int & dataLength) { + lastError = ""; + int dataStatus; + unsigned char * data; dataLength = 0; + std::stringstream urlStream; + if(saveDate) + { + urlStream << "http://" << STATICSERVER << "/" << saveID << "_" << saveDate << ".cps"; + } + else + { + urlStream << "http://" << STATICSERVER << "/" << saveID << ".cps"; + } + data = (unsigned char *)http_simple_get((char *)urlStream.str().c_str(), &dataStatus, &dataLength); + if(data && dataStatus == 200) + { + return data; + } + else if(data) + { + free(data); + } return NULL; } @@ -175,11 +196,12 @@ Save * Client::GetSave(int saveID, int saveDate) Thumbnail * Client::GetPreview(int saveID, int saveDate) { std::stringstream urlStream; - urlStream << "http://" << SERVER << "/Get.api?Op=thumblarge&ID=" << saveID; + urlStream << "http://" << STATICSERVER << "/" << saveID; if(saveDate) { - urlStream << "&Date=" << saveDate; + urlStream << "_" << saveDate; } + urlStream << "_large.pti"; pixel * thumbData; char * data; int status, data_size, imgw, imgh; @@ -322,11 +344,12 @@ Thumbnail * Client::GetThumbnail(int saveID, int saveDate) if(thumbnailCache[i] && thumbnailCache[i]->ID == saveID && thumbnailCache[i]->Datestamp == saveDate) return thumbnailCache[i]; } - urlStream << "http://" << SERVER << "/Get.api?Op=thumbsmall&ID=" << saveID; + urlStream << "http://" << STATICSERVER << "/" << saveID; if(saveDate) { - urlStream << "&Date=" << saveDate; + urlStream << "_" << saveDate; } + urlStream << "_small.pti"; idStream << saveID << ":" << saveDate; std::string idString = idStream.str(); bool found = false; diff --git a/src/game/GameController.cpp b/src/game/GameController.cpp index e8efe0b38..90ef70cda 100644 --- a/src/game/GameController.cpp +++ b/src/game/GameController.cpp @@ -148,7 +148,7 @@ void GameController::DrawPoints(queue & pointQueue) void GameController::Update() { - //gameModel->GetSimulation()->update_particles(); + gameModel->GetSimulation()->update_particles(); if(renderOptions && renderOptions->HasExited) { delete renderOptions; @@ -234,7 +234,8 @@ void GameController::ClearSim() void GameController::ReloadSim() { - //TODO: Implement + if(gameModel->GetSave() && gameModel->GetSave()->GetData()) + gameModel->GetSimulation()->Load(gameModel->GetSave()->GetData(), gameModel->GetSave()->GetDataLength()); } diff --git a/src/search/Save.cpp b/src/search/Save.cpp index 6a5925dbe..5bcb0e9d1 100644 --- a/src/search/Save.cpp +++ b/src/search/Save.cpp @@ -34,6 +34,14 @@ Save::Save(int _id, int date_, int _votesUp, int _votesDown, string _userName, published_), data(NULL) { } +Save::~Save() +{ + if(data) + { + free(data); + } +} + void Save::SetName(string name) { this->name = name; } diff --git a/src/search/Save.h b/src/search/Save.h index 3186e2abf..42cbb62d5 100644 --- a/src/search/Save.h +++ b/src/search/Save.h @@ -23,6 +23,8 @@ public: Save(int _id, int date_, int _votesUp, int _votesDown, string _userName, string _name, string description_, bool published_); + ~Save(); + string userName; string name; diff --git a/src/simulation/SaveLoader.cpp b/src/simulation/SaveLoader.cpp index 350b54a93..5d92300ac 100644 --- a/src/simulation/SaveLoader.cpp +++ b/src/simulation/SaveLoader.cpp @@ -5,16 +5,32 @@ * Author: Simon */ +#include #include "SaveLoader.h" -int SaveLoader::LoadSave(unsigned char * data, int dataLength, Simulation * sim) +//!TODO: enum for LoadSave return + +int SaveLoader::LoadSave(unsigned char * data, int dataLength, Simulation * sim, bool replace, int x, int y) { - return 0; + unsigned char * saveData = data; + if (dataLength<16) + { + return 1; + } + if(saveData[0] == 'O' && saveData[1] == 'P' && saveData[2] == 'S') + { + return OPSLoadSave(data, dataLength, sim); + } + else if((saveData[0]==0x66 && saveData[1]==0x75 && saveData[2]==0x43) || (saveData[0]==0x50 && saveData[1]==0x53 && saveData[2]==0x76)) + { + return PSVLoadSave(data, dataLength, sim, replace, x, y); + } + return 1; } unsigned char * SaveLoader::BuildSave(int & dataLength, Simulation * sim) { - return 0; + return OPSBuildSave(dataLength, sim); } int SaveLoader::OPSLoadSave(unsigned char * data, int dataLength, Simulation * sim) @@ -27,9 +43,598 @@ unsigned char * SaveLoader::OPSBuildSave(int & dataLength, Simulation * sim) return 0; } -int SaveLoader::PSVLoadSave(unsigned char * data, int dataLength, Simulation * sim) +int SaveLoader::PSVLoadSave(unsigned char * data, int dataLength, Simulation * sim, bool replace, int x0, int y0) { + unsigned char * d = NULL, * c = data; + int q,i,j,k,x,y,p=0,*m=NULL, ver, pty, ty, legacy_beta=0, tempGrav = 0; + int bx0=x0/CELL, by0=y0/CELL, bw, bh, w, h; + int nf=0, new_format = 0, ttv = 0; + Particle *parts = sim->parts; + int *fp = (int *)malloc(NPART*sizeof(int)); + + //New file header uses PSv, replacing fuC. This is to detect if the client uses a new save format for temperatures + //This creates a problem for old clients, that display and "corrupt" error instead of a "newer version" error + + if (dataLength<16) + return 1; + if (!(c[2]==0x43 && c[1]==0x75 && c[0]==0x66) && !(c[2]==0x76 && c[1]==0x53 && c[0]==0x50)) + return 1; + if (c[2]==0x76 && c[1]==0x53 && c[0]==0x50) { + new_format = 1; + } + if (c[4]>SAVE_VERSION) + return 2; + ver = c[4]; + + if (ver<34) + { + sim->legacy_enable = 1; + } + else + { + if (ver>=44) { + sim->legacy_enable = c[3]&0x01; + if (!sim->sys_pause) { + sim->sys_pause = (c[3]>>1)&0x01; + } + if (ver>=46 && replace) { + sim->gravityMode = ((c[3]>>2)&0x03);// | ((c[3]>>2)&0x01); + sim->airMode = ((c[3]>>4)&0x07);// | ((c[3]>>4)&0x02) | ((c[3]>>4)&0x01); + } + if (ver>=49 && replace) { + tempGrav = ((c[3]>>7)&0x01); + } + } else { + if (c[3]==1||c[3]==0) { + sim->legacy_enable = c[3]; + } else { + legacy_beta = 1; + } + } + } + + bw = c[6]; + bh = c[7]; + if (bx0+bw > XRES/CELL) + bx0 = XRES/CELL - bw; + if (by0+bh > YRES/CELL) + by0 = YRES/CELL - bh; + if (bx0 < 0) + bx0 = 0; + if (by0 < 0) + by0 = 0; + + if (c[5]!=CELL || bx0+bw>XRES/CELL || by0+bh>YRES/CELL) + return 3; + i = (unsigned)c[8]; + i |= ((unsigned)c[9])<<8; + i |= ((unsigned)c[10])<<16; + i |= ((unsigned)c[11])<<24; + d = (unsigned char *)malloc(i); + if (!d) + return 1; + + if (BZ2_bzBuffToBuffDecompress((char *)d, (unsigned *)&i, (char *)(c+12), dataLength-12, 0, 0)) + return 1; + dataLength = i; + + if (dataLength < bw*bh) + return 1; + + // normalize coordinates + x0 = bx0*CELL; + y0 = by0*CELL; + w = bw *CELL; + h = bh *CELL; + + if (replace) + { + if (ver<46) { + sim->gravityMode = 0; + sim->airMode = 0; + } + sim->clear_sim(); + } + sim->parts_lastActiveIndex = NPART-1; + m = (int *)calloc(XRES*YRES, sizeof(int)); + + // make a catalog of free parts + //memset(pmap, 0, sizeof(pmap)); "Using sizeof for array given as function argument returns the size of pointer." + memset(sim->pmap, 0, sizeof(unsigned)*(XRES*YRES)); + for (i=0; ipmap[y][x] = (i<<8)|1; + } + else + fp[nf++] = i; + + // load the required air state + for (y=by0; ybmap[y][x] = d[p]; + if (sim->bmap[y][x]==1) + sim->bmap[y][x]=WL_WALL; + if (sim->bmap[y][x]==2) + sim->bmap[y][x]=WL_DESTROYALL; + if (sim->bmap[y][x]==3) + sim->bmap[y][x]=WL_ALLOWLIQUID; + if (sim->bmap[y][x]==4) + sim->bmap[y][x]=WL_FAN; + if (sim->bmap[y][x]==5) + sim->bmap[y][x]=WL_STREAM; + if (sim->bmap[y][x]==6) + sim->bmap[y][x]=WL_DETECT; + if (sim->bmap[y][x]==7) + sim->bmap[y][x]=WL_EWALL; + if (sim->bmap[y][x]==8) + sim->bmap[y][x]=WL_WALLELEC; + if (sim->bmap[y][x]==9) + sim->bmap[y][x]=WL_ALLOWAIR; + if (sim->bmap[y][x]==10) + sim->bmap[y][x]=WL_ALLOWSOLID; + if (sim->bmap[y][x]==11) + sim->bmap[y][x]=WL_ALLOWALLELEC; + if (sim->bmap[y][x]==12) + sim->bmap[y][x]=WL_EHOLE; + if (sim->bmap[y][x]==13) + sim->bmap[y][x]=WL_ALLOWGAS; + } + + p++; + } + for (y=by0; y= dataLength) + goto corrupt; + sim->fvx[y][x] = (d[p++]-127.0f)/64.0f; + } + for (y=by0; y= dataLength) + goto corrupt; + sim->fvy[y][x] = (d[p++]-127.0f)/64.0f; + } + + // load the particle map + i = 0; + pty = p; + for (y=y0; y= dataLength) + goto corrupt; + j=d[p++]; + if (j >= PT_NUM) { + //TODO: Possibly some server side translation + j = PT_DUST;//goto corrupt; + } + sim->gol[x][y]=0; + if (j) + { + if (sim->pmap[y][x]) + { + k = sim->pmap[y][x]>>8; + } + else if (i= dataLength) + goto corrupt; + if (i < NPART) + { + parts[i].vx = (d[p++]-127.0f)/16.0f; + parts[i].vy = (d[p++]-127.0f)/16.0f; + } + else + p += 2; + } + } + for (j=0; j=44) { + if (p >= dataLength) { + goto corrupt; + } + if (i <= NPART) { + ttv = (d[p++])<<8; + ttv |= (d[p++]); + parts[i-1].life = ttv; + } else { + p+=2; + } + } else { + if (p >= dataLength) + goto corrupt; + if (i <= NPART) + parts[i-1].life = d[p++]*4; + else + p++; + } + } + } + if (ver>=44) { + for (j=0; j= dataLength) { + goto corrupt; + } + if (i <= NPART) { + ttv = (d[p++])<<8; + ttv |= (d[p++]); + parts[i-1].tmp = ttv; + if (ver<53 && !parts[i-1].tmp) + for (q = 1; q<=NGOLALT; q++) { + if (parts[i-1].type==sim->goltype[q-1] && sim->grule[q][9]==2) + parts[i-1].tmp = sim->grule[q][9]-1; + } + if (ver>=51 && ver<53 && parts[i-1].type==PT_PBCN) + { + parts[i-1].tmp2 = parts[i-1].tmp; + parts[i-1].tmp = 0; + } + } else { + p+=2; + } + } + } + } + if (ver>=53) { + for (j=0; j= dataLength) + goto corrupt; + if (i <= NPART) + parts[i-1].tmp2 = d[p++]; + else + p++; + } + } + } + //Read ALPHA component + for (j=0; j=49) { + if (p >= dataLength) { + goto corrupt; + } + if (i <= NPART) { + parts[i-1].dcolour = d[p++]<<24; + } else { + p++; + } + } + } + } + //Read RED component + for (j=0; j=49) { + if (p >= dataLength) { + goto corrupt; + } + if (i <= NPART) { + parts[i-1].dcolour |= d[p++]<<16; + } else { + p++; + } + } + } + } + //Read GREEN component + for (j=0; j=49) { + if (p >= dataLength) { + goto corrupt; + } + if (i <= NPART) { + parts[i-1].dcolour |= d[p++]<<8; + } else { + p++; + } + } + } + } + //Read BLUE component + for (j=0; j=49) { + if (p >= dataLength) { + goto corrupt; + } + if (i <= NPART) { + parts[i-1].dcolour |= d[p++]; + } else { + p++; + } + } + } + } + for (j=0; j=34&&legacy_beta==0) + { + if (p >= dataLength) + { + goto corrupt; + } + if (i <= NPART) + { + if (ver>=42) { + if (new_format) { + ttv = (d[p++])<<8; + ttv |= (d[p++]); + if (parts[i-1].type==PT_PUMP) { + parts[i-1].temp = ttv + 0.15;//fix PUMP saved at 0, so that it loads at 0. + } else { + parts[i-1].temp = ttv; + } + } else { + parts[i-1].temp = (d[p++]*((MAX_TEMP+(-MIN_TEMP))/255))+MIN_TEMP; + } + } else { + parts[i-1].temp = ((d[p++]*((O_MAX_TEMP+(-O_MIN_TEMP))/255))+O_MIN_TEMP)+273; + } + } + else + { + p++; + if (new_format) { + p++; + } + } + } + else + { + parts[i-1].temp = sim->ptypes[parts[i-1].type].heat; + } + } + } + for (j=0; j=43) || (ty==PT_BCLN && ver>=44) || (ty==PT_SPRK && ver>=21) || (ty==PT_LAVA && ver>=34) || (ty==PT_PIPE && ver>=43) || (ty==PT_LIFE && ver>=51) || (ty==PT_PBCN && ver>=52) || (ty==PT_WIRE && ver>=55) || (ty==PT_STOR && ver>=59) || (ty==PT_CONV && ver>=60))) + { + if (p >= dataLength) + goto corrupt; + if (i <= NPART) + parts[i-1].ctype = d[p++]; + else + p++; + } + //TODO: STKM_init_legs + // no more particle properties to load, so we can change type here without messing up loading + if (i && i<=NPART) + { + if ((sim->player.spwn == 1 && ty==PT_STKM) || (sim->player2.spwn == 1 && ty==PT_STKM2)) + { + parts[i-1].type = PT_NONE; + } + else if (parts[i-1].type == PT_STKM) + { + //STKM_init_legs(&player, i-1); + sim->player.spwn = 1; + sim->player.elem = PT_DUST; + } + else if (parts[i-1].type == PT_STKM2) + { + //STKM_init_legs(&player2, i-1); + sim->player2.spwn = 1; + sim->player2.elem = PT_DUST; + } + else if (parts[i-1].type == PT_FIGH) + { + unsigned char fcount = 0; + while (fcount < 100 && fcount < (sim->fighcount+1) && sim->fighters[fcount].spwn==1) fcount++; + if (fcount < 100 && sim->fighters[fcount].spwn==0) + { + parts[i-1].tmp = fcount; + sim->fighters[fcount].spwn = 1; + sim->fighters[fcount].elem = PT_DUST; + sim->fighcount++; + //STKM_init_legs(&(fighters[fcount]), i-1); + } + } + + if (ver<48 && (ty==OLD_PT_WIND || (ty==PT_BRAY&&parts[i-1].life==0))) + { + // Replace invisible particles with something sensible and add decoration to hide it + x = (int)(parts[i-1].x+0.5f); + y = (int)(parts[i-1].y+0.5f); + parts[i-1].dcolour = 0xFF000000; + parts[i-1].type = PT_DMND; + } + if(ver<51 && ((ty>=78 && ty<=89) || (ty>=134 && ty<=146 && ty!=141))){ + //Replace old GOL + parts[i-1].type = PT_LIFE; + for (gnum = 0; gnumgoltype[gnum]) + parts[i-1].ctype = gnum; + } + ty = PT_LIFE; + } + if(ver<52 && (ty==PT_CLNE || ty==PT_PCLN || ty==PT_BCLN)){ + //Replace old GOL ctypes in clone + for (gnum = 0; gnumgoltype[gnum]) + { + parts[i-1].ctype = PT_LIFE; + parts[i-1].tmp = gnum; + } + } + } + if(ty==PT_LCRY){ + if(ver<67) + { + //New LCRY uses TMP not life + if(parts[i-1].life>=10) + { + parts[i-1].life = 10; + parts[i-1].tmp2 = 10; + parts[i-1].tmp = 3; + } + else if(parts[i-1].life<=0) + { + parts[i-1].life = 0; + parts[i-1].tmp2 = 0; + parts[i-1].tmp = 0; + } + else if(parts[i-1].life < 10 && parts[i-1].life > 0) + { + parts[i-1].tmp = 1; + } + } + else + { + parts[i-1].tmp2 = parts[i-1].life; + } + } + if (!sim->ptypes[parts[i-1].type].enabled) + parts[i-1].type = PT_NONE; + } + } + + #ifndef RENDERER + //Change the gravity state + if(sim->ngrav_enable != tempGrav && replace) + { + if(tempGrav) + sim->grav->start_grav_async(); + else + sim->grav->stop_grav_async(); + } + #endif + + sim->grav->gravity_mask(); + + if (p >= dataLength) + goto version1; + j = d[p++]; + for (i=0; i dataLength) + goto corrupt; + for (k=0; ksigns[k].text[0]) + break; + x = d[p++]; + x |= ((unsigned)d[p++])<<8; + if (ksigns[k].x = x+x0; + x = d[p++]; + x |= ((unsigned)d[p++])<<8; + if (ksigns[k].y = x+y0; + x = d[p++]; + if (ksigns[k].ju = x; + x = d[p++]; + if (p+x > dataLength) + goto corrupt; + if (ksigns[k].text, d+p, x); + sim->signs[k].text[x] = 0; + //clean_text(signs[k].text, 158-14 /* Current max sign length */); //TODO: Text cleanup for signs + } + p += x; + } + +version1: + if (m) free(m); + if (d) free(d); + if (fp) free(fp); + return 0; + +corrupt: + if (m) free(m); + if (d) free(d); + if (fp) free(fp); + if (replace) + { + sim->legacy_enable = 0; + sim->clear_sim(); + } + return 1; } unsigned char * PSVBuildSave(int & dataLength, Simulation * sim) diff --git a/src/simulation/SaveLoader.h b/src/simulation/SaveLoader.h index e517a2b9e..50b1401d6 100644 --- a/src/simulation/SaveLoader.h +++ b/src/simulation/SaveLoader.h @@ -12,11 +12,11 @@ class SaveLoader { public: - static int LoadSave(unsigned char * data, int dataLength, Simulation * sim); + static int LoadSave(unsigned char * data, int dataLength, Simulation * sim, bool replace, int x, int y); static unsigned char * BuildSave(int & dataLength, Simulation * sim); static int OPSLoadSave(unsigned char * data, int dataLength, Simulation * sim); static unsigned char * OPSBuildSave(int & dataLength, Simulation * sim); - static int PSVLoadSave(unsigned char * data, int dataLength, Simulation * sim); + static int PSVLoadSave(unsigned char * data, int dataLength, Simulation * sim, bool replace, int x, int y); static unsigned char * PSVBuildSave(int & dataLength, Simulation * sim); }; diff --git a/src/simulation/Simulation.cpp b/src/simulation/Simulation.cpp index 89328631b..ca9e411f0 100644 --- a/src/simulation/Simulation.cpp +++ b/src/simulation/Simulation.cpp @@ -10,7 +10,7 @@ int Simulation::Load(unsigned char * data, int dataLength) { - return SaveLoader::LoadSave(data, dataLength, this); + return SaveLoader::LoadSave(data, dataLength, this, true, 0, 0); } unsigned char * Simulation::Save(int & dataLength) diff --git a/src/simulation/Simulation.h b/src/simulation/Simulation.h index 8a1ed16de..f03b18967 100644 --- a/src/simulation/Simulation.h +++ b/src/simulation/Simulation.h @@ -14,6 +14,7 @@ #include "Elements.h" #include "Misc.h" #include "game/Brush.h" +#include "Gravity.h" #include "SimulationData.h" //#include "ElementFunctions.h" @@ -204,36 +205,36 @@ public: int sandcolour_r; int sandcolour_g; int sandcolour_b; //TODO: Make a single variable - //TODO: Inlines for performance + int Load(unsigned char * data, int dataLength); unsigned char * Save(int & dataLength); - int is_blocking(int t, int x, int y); - int is_boundary(int pt, int x, int y); - int find_next_boundary(int pt, int *x, int *y, int dm, int *em); - int pn_junction_sprk(int x, int y, int pt); - void photoelectric_effect(int nx, int ny); - unsigned direction_to_map(float dx, float dy, int t); - int do_move(int i, int x, int y, float nxf, float nyf); - int try_move(int i, int x, int y, int nx, int ny); - int eval_move(int pt, int nx, int ny, unsigned *rr); + inline int is_blocking(int t, int x, int y); + inline int is_boundary(int pt, int x, int y); + inline int find_next_boundary(int pt, int *x, int *y, int dm, int *em); + inline int pn_junction_sprk(int x, int y, int pt); + inline void photoelectric_effect(int nx, int ny); + inline unsigned direction_to_map(float dx, float dy, int t); + inline int do_move(int i, int x, int y, float nxf, float nyf); + inline int try_move(int i, int x, int y, int nx, int ny); + inline int eval_move(int pt, int nx, int ny, unsigned *rr); void init_can_move(); void create_cherenkov_photon(int pp); void create_gain_photon(int pp); - void kill_part(int i); + inline void kill_part(int i); int flood_prop(int x, int y, size_t propoffset, void * propvalue, int proptype); int flood_prop_2(int x, int y, size_t propoffset, void * propvalue, int proptype, int parttype, char * bitmap); int flood_water(int x, int y, int i, int originaly, int check); - void detach(int i); - void part_change_type(int i, int x, int y, int t); - int create_part_add_props(int p, int x, int y, int tv, int rx, int ry); + inline void detach(int i); + inline void part_change_type(int i, int x, int y, int t); + inline int create_part_add_props(int p, int x, int y, int tv, int rx, int ry); //int InCurrentBrush(int i, int j, int rx, int ry); //int get_brush_flags(); - int create_part(int p, int x, int y, int t); - void delete_part(int x, int y, int flags); - int is_wire(int x, int y); - int is_wire_off(int x, int y); - void set_emap(int x, int y); - int parts_avg(int ci, int ni, int t); + inline int create_part(int p, int x, int y, int t); + inline void delete_part(int x, int y, int flags); + inline int is_wire(int x, int y); + inline int is_wire_off(int x, int y); + inline void set_emap(int x, int y); + inline int parts_avg(int ci, int ni, int t); void create_arc(int sx, int sy, int dx, int dy, int midpoints, int variance, int type, int flags); int nearest_part(int ci, int t, int max_d); void update_particles_i(int start, int inc); @@ -245,11 +246,11 @@ public: int create_parts(int x, int y, int rx, int ry, int c, int flags, Brush * cBrush = NULL); void create_line(int x1, int y1, int x2, int y2, int rx, int ry, int c, int flags, Brush * cBrush = NULL); void *transform_save(void *odata, int *size, matrix2d transform, vector2d translate); - void orbitalparts_get(int block1, int block2, int resblock1[], int resblock2[]); - void orbitalparts_set(int *block1, int *block2, int resblock1[], int resblock2[]); - int get_wavelength_bin(int *wm); - int get_normal(int pt, int x, int y, float dx, float dy, float *nx, float *ny); - int get_normal_interp(int pt, float x0, float y0, float dx, float dy, float *nx, float *ny); + inline void orbitalparts_get(int block1, int block2, int resblock1[], int resblock2[]); + inline void orbitalparts_set(int *block1, int *block2, int resblock1[], int resblock2[]); + inline int get_wavelength_bin(int *wm); + inline int get_normal(int pt, int x, int y, float dx, float dy, float *nx, float *ny); + inline int get_normal_interp(int pt, float x0, float y0, float dx, float dy, float *nx, float *ny); void clear_sim(); void UpdateParticles(); Simulation();