This repository has been archived on 2025-03-20. You can view files and clone it, but cannot push or open issues or pull requests.
The-Powder-Toy/src/simulation/Editing.cpp
Tamás Bálint Misius 0b82796ba4
Add Simulation::frameCount, save blockair/blockairh and rng state
Saving blockair/blockairh is nice because RecalculateBlockAirMaps uses the sim rng, which means the sim rng would get advanced in Simulation::Load. Also rename RecalculateBlockAirMaps to ApproximateBlockAirMaps because that's what it is, an approximation, and it's needed only if there are no block air maps in the save.

Simulation::frameCount keeps track of frames elapsed since the beginning of the simulation, zeroed at clear_sim. It overflows when it reaches the 64-bit limit, which means anything that depends on it should either handle this, or not fail catastrophically. sandcolour (the only thing that depends on it as of now) is a good example of the latter: sandcolour has a periodicity of 360 frames, which means that there is one sandcolour period that is cut short by the overflow. This is not "handled" (the period is cut short, which is detectable by users) but is not catastrophic either (it's not a big deal, and it won't ever happen unless someone hacks the save).

Also restrict saves with determinism data to 98.0.
2023-04-15 22:19:50 +02:00

1135 lines
27 KiB
C++

#include "Simulation.h"
#include "Sample.h"
#include "SimTool.h"
#include "Snapshot.h"
#include "Air.h"
#include "gravity/Gravity.h"
#include "common/tpt-rand.h"
#include "common/tpt-compat.h"
#include "client/GameSave.h"
#include "ElementClasses.h"
#include "graphics/Renderer.h"
#include "gui/game/Brush.h"
#include <iostream>
#include <cmath>
std::unique_ptr<Snapshot> Simulation::CreateSnapshot()
{
auto snap = std::make_unique<Snapshot>();
snap->AirPressure .insert (snap->AirPressure .begin(), &pv [0][0] , &pv [0][0] + NCELL);
snap->AirVelocityX .insert (snap->AirVelocityX .begin(), &vx [0][0] , &vx [0][0] + NCELL);
snap->AirVelocityY .insert (snap->AirVelocityY .begin(), &vy [0][0] , &vy [0][0] + NCELL);
snap->AmbientHeat .insert (snap->AmbientHeat .begin(), &hv [0][0] , &hv [0][0] + NCELL);
snap->BlockMap .insert (snap->BlockMap .begin(), &bmap[0][0] , &bmap[0][0] + NCELL);
snap->ElecMap .insert (snap->ElecMap .begin(), &emap[0][0] , &emap[0][0] + NCELL);
snap->BlockAir .insert (snap->BlockAir .begin(), &air->bmap_blockair[0][0] , &air->bmap_blockair[0][0] + NCELL);
snap->BlockAirH .insert (snap->BlockAirH .begin(), &air->bmap_blockairh[0][0], &air->bmap_blockairh[0][0] + NCELL);
snap->FanVelocityX .insert (snap->FanVelocityX .begin(), &fvx [0][0] , &fvx [0][0] + NCELL);
snap->FanVelocityY .insert (snap->FanVelocityY .begin(), &fvy [0][0] , &fvy [0][0] + NCELL);
snap->GravVelocityX .insert (snap->GravVelocityX .begin(), &gravx [0] , &gravx [0] + NCELL);
snap->GravVelocityY .insert (snap->GravVelocityY .begin(), &gravy [0] , &gravy [0] + NCELL);
snap->GravValue .insert (snap->GravValue .begin(), &gravp [0] , &gravp [0] + NCELL);
snap->GravMap .insert (snap->GravMap .begin(), &gravmap[0] , &gravmap[0] + NCELL);
snap->Particles .insert (snap->Particles .begin(), &parts [0] , &parts [0] + parts_lastActiveIndex + 1);
snap->PortalParticles.insert (snap->PortalParticles.begin(), &portalp[0][0][0], &portalp[0][0][0] + CHANNELS * 8 * 80);
snap->WirelessData .insert (snap->WirelessData .begin(), &wireless[0][0] , &wireless[0][0] + CHANNELS * 2);
snap->stickmen .insert (snap->stickmen .begin(), &fighters[0] , &fighters[0] + MAX_FIGHTERS);
snap->stickmen .push_back(player2);
snap->stickmen .push_back(player);
snap->signs = signs;
snap->FrameCount = frameCount;
snap->RngState = rng.state();
return snap;
}
void Simulation::Restore(const Snapshot &snap)
{
std::fill(elementCount, elementCount + PT_NUM, 0);
elementRecount = true;
force_stacking_check = true;
for (auto &part : parts)
{
part.type = 0;
}
std::copy(snap.AirPressure .begin(), snap.AirPressure .end(), &pv[0][0] );
std::copy(snap.AirVelocityX .begin(), snap.AirVelocityX .end(), &vx[0][0] );
std::copy(snap.AirVelocityY .begin(), snap.AirVelocityY .end(), &vy[0][0] );
std::copy(snap.AmbientHeat .begin(), snap.AmbientHeat .end(), &hv[0][0] );
std::copy(snap.BlockMap .begin(), snap.BlockMap .end(), &bmap[0][0] );
std::copy(snap.ElecMap .begin(), snap.ElecMap .end(), &emap[0][0] );
std::copy(snap.BlockAir .begin(), snap.BlockAir .end(), &air->bmap_blockair[0][0] );
std::copy(snap.BlockAirH .begin(), snap.BlockAirH .end(), &air->bmap_blockairh[0][0]);
std::copy(snap.FanVelocityX .begin(), snap.FanVelocityX .end(), &fvx[0][0] );
std::copy(snap.FanVelocityY .begin(), snap.FanVelocityY .end(), &fvy[0][0] );
if (grav->IsEnabled())
{
grav->Clear();
std::copy(snap.GravVelocityX.begin(), snap.GravVelocityX.end(), &gravx [0] );
std::copy(snap.GravVelocityY.begin(), snap.GravVelocityY.end(), &gravy [0] );
std::copy(snap.GravValue .begin(), snap.GravValue .end(), &gravp [0] );
std::copy(snap.GravMap .begin(), snap.GravMap .end(), &gravmap[0] );
}
std::copy(snap.Particles .begin(), snap.Particles .end(), &parts[0] );
std::copy(snap.PortalParticles.begin(), snap.PortalParticles.end(), &portalp[0][0][0]);
std::copy(snap.WirelessData .begin(), snap.WirelessData .end(), &wireless[0][0] );
std::copy(snap.stickmen .begin(), snap.stickmen.end() - 2 , &fighters[0] );
player = snap.stickmen[snap.stickmen.size() - 1];
player2 = snap.stickmen[snap.stickmen.size() - 2];
signs = snap.signs;
frameCount = snap.FrameCount;
rng.state(snap.RngState);
parts_lastActiveIndex = NPART - 1;
RecalcFreeParticles(false);
gravWallChanged = true;
}
void Simulation::clear_area(int area_x, int area_y, int area_w, int area_h)
{
float fx = area_x-.5f, fy = area_y-.5f;
for (int i = 0; i <= parts_lastActiveIndex; i++)
{
if (parts[i].type)
if (parts[i].x >= fx && parts[i].x <= fx+area_w+1 && parts[i].y >= fy && parts[i].y <= fy+area_h+1)
kill_part(i);
}
int cx1 = area_x/CELL, cy1 = area_y/CELL, cx2 = (area_x+area_w)/CELL, cy2 = (area_y+area_h)/CELL;
for (int y = cy1; y <= cy2; y++)
{
for (int x = cx1; x <= cx2; x++)
{
if (bmap[y][x] == WL_GRAV)
gravWallChanged = true;
bmap[y][x] = 0;
emap[y][x] = 0;
}
}
for( int i = signs.size()-1; i >= 0; i--)
{
if (signs[i].text.length() && signs[i].x >= area_x && signs[i].y >= area_y && signs[i].x <= area_x+area_w && signs[i].y <= area_y+area_h)
{
signs.erase(signs.begin()+i);
}
}
}
SimulationSample Simulation::GetSample(int x, int y)
{
SimulationSample sample;
sample.PositionX = x;
sample.PositionY = y;
if (x >= 0 && x < XRES && y >= 0 && y < YRES)
{
if (photons[y][x])
{
sample.particle = parts[ID(photons[y][x])];
sample.ParticleID = ID(photons[y][x]);
}
else if (pmap[y][x])
{
sample.particle = parts[ID(pmap[y][x])];
sample.ParticleID = ID(pmap[y][x]);
}
if (bmap[y/CELL][x/CELL])
{
sample.WallType = bmap[y/CELL][x/CELL];
}
sample.AirPressure = pv[y/CELL][x/CELL];
sample.AirTemperature = hv[y/CELL][x/CELL];
sample.AirVelocityX = vx[y/CELL][x/CELL];
sample.AirVelocityY = vy[y/CELL][x/CELL];
if(grav->IsEnabled())
{
sample.Gravity = gravp[(y/CELL)*XCELLS+(x/CELL)];
sample.GravityVelocityX = gravx[(y/CELL)*XCELLS+(x/CELL)];
sample.GravityVelocityY = gravy[(y/CELL)*XCELLS+(x/CELL)];
}
}
else
sample.isMouseInSim = false;
sample.NumParts = NUM_PARTS;
return sample;
}
void Simulation::SetDecoSpace(int newDecoSpace)
{
switch (newDecoSpace)
{
case 0: // sRGB
default: // anything stupid
deco_space = 0;
break;
case 1: // linear
deco_space = 1;
break;
case 2: // Gamma = 2.2
deco_space = 2;
break;
case 3: // Gamma = 1.8
deco_space = 3;
break;
}
}
int Simulation::Tool(int x, int y, int tool, int brushX, int brushY, float strength)
{
Particle * cpart = NULL;
int r;
if ((r = pmap[y][x]))
cpart = &(parts[ID(r)]);
else if ((r = photons[y][x]))
cpart = &(parts[ID(r)]);
return tools[tool].Perform(this, cpart, x, y, brushX, brushY, strength);
}
int Simulation::CreateWalls(int x, int y, int rx, int ry, int wall, Brush const *cBrush)
{
if(cBrush)
{
rx = cBrush->GetRadius().X;
ry = cBrush->GetRadius().Y;
}
ry = ry/CELL;
rx = rx/CELL;
x = x/CELL;
y = y/CELL;
x -= rx;
y -= ry;
for (int wallX = x; wallX <= x+rx+rx; wallX++)
{
for (int wallY = y; wallY <= y+ry+ry; wallY++)
{
if (wallX >= 0 && wallX < XCELLS && wallY >= 0 && wallY < YCELLS)
{
if (wall == WL_FAN)
{
fvx[wallY][wallX] = 0.0f;
fvy[wallY][wallX] = 0.0f;
}
else if (wall == WL_STREAM)
{
wallX = x + rx;
wallY = y + ry;
//streamlines can't be drawn next to each other
for (int tempY = wallY-1; tempY < wallY+2; tempY++)
for (int tempX = wallX-1; tempX < wallX+2; tempX++)
{
if (tempX >= 0 && tempX < XCELLS && tempY >= 0 && tempY < YCELLS && bmap[tempY][tempX] == WL_STREAM)
return 1;
}
}
if (wall == WL_GRAV || bmap[wallY][wallX] == WL_GRAV)
gravWallChanged = true;
if (wall == WL_ERASEALL)
{
for (int i = 0; i < CELL; i++)
for (int j = 0; j < CELL; j++)
{
delete_part(wallX*CELL+i, wallY*CELL+j);
}
for (int i = signs.size()-1; i >= 0; i--)
if (signs[i].x >= wallX*CELL && signs[i].y >= wallY*CELL && signs[i].x <= (wallX+1)*CELL && signs[i].y <= (wallY+1)*CELL)
signs.erase(signs.begin()+i);
bmap[wallY][wallX] = 0;
}
else
bmap[wallY][wallX] = wall;
}
}
}
return 1;
}
void Simulation::CreateWallLine(int x1, int y1, int x2, int y2, int rx, int ry, int wall, Brush const *cBrush)
{
int x, y, dx, dy, sy;
bool reverseXY = abs(y2-y1) > abs(x2-x1);
float e = 0.0f, de;
if (reverseXY)
{
y = x1;
x1 = y1;
y1 = y;
y = x2;
x2 = y2;
y2 = y;
}
if (x1 > x2)
{
y = x1;
x1 = x2;
x2 = y;
y = y1;
y1 = y2;
y2 = y;
}
dx = x2 - x1;
dy = abs(y2 - y1);
de = dx ? dy/(float)dx : 0.0f;
y = y1;
sy = (y1<y2) ? 1 : -1;
for (x=x1; x<=x2; x++)
{
if (reverseXY)
CreateWalls(y, x, rx, ry, wall, cBrush);
else
CreateWalls(x, y, rx, ry, wall, cBrush);
e += de;
if (e >= 0.5f)
{
y += sy;
if ((y1<y2) ? (y<=y2) : (y>=y2))
{
if (reverseXY)
CreateWalls(y, x, rx, ry, wall, cBrush);
else
CreateWalls(x, y, rx, ry, wall, cBrush);
}
e -= 1.0f;
}
}
}
void Simulation::CreateWallBox(int x1, int y1, int x2, int y2, int wall)
{
int i, j;
if (x1>x2)
{
i = x2;
x2 = x1;
x1 = i;
}
if (y1>y2)
{
j = y2;
y2 = y1;
y1 = j;
}
for (j=y1; j<=y2; j++)
for (i=x1; i<=x2; i++)
CreateWalls(i, j, 0, 0, wall, NULL);
}
int Simulation::FloodWalls(int x, int y, int wall, int bm)
{
int x1, x2, dy = CELL;
if (bm==-1)
{
if (wall==WL_ERASE || wall==WL_ERASEALL)
{
bm = bmap[y/CELL][x/CELL];
if (!bm)
return 0;
}
else
bm = 0;
}
if (bmap[y/CELL][x/CELL]!=bm)
return 1;
// go left as far as possible
x1 = x2 = x;
while (x1>=CELL)
{
if (bmap[y/CELL][(x1-1)/CELL]!=bm)
{
break;
}
x1--;
}
while (x2<XRES-CELL)
{
if (bmap[y/CELL][(x2+1)/CELL]!=bm)
{
break;
}
x2++;
}
// fill span
for (x=x1; x<=x2; x++)
{
if (!CreateWalls(x, y, 0, 0, wall, NULL))
return 0;
}
// fill children
if (y>=CELL)
for (x=x1; x<=x2; x++)
if (bmap[(y-dy)/CELL][x/CELL]==bm)
if (!FloodWalls(x, y-dy, wall, bm))
return 0;
if (y<YRES-CELL)
for (x=x1; x<=x2; x++)
if (bmap[(y+dy)/CELL][x/CELL]==bm)
if (!FloodWalls(x, y+dy, wall, bm))
return 0;
return 1;
}
int Simulation::CreatePartFlags(int x, int y, int c, int flags)
{
if (x < 0 || y < 0 || x >= XRES || y >= YRES)
{
return 0;
}
if (flags & REPLACE_MODE)
{
// if replace whatever and there's something to replace
// or replace X and there's a non-energy particle on top with type X
// or replace X and there's an energy particle on top with type X
if ((!replaceModeSelected && (photons[y][x] || pmap[y][x])) ||
(!photons[y][x] && pmap[y][x] && TYP(pmap[y][x]) == replaceModeSelected) ||
(photons[y][x] && TYP(photons[y][x]) == replaceModeSelected))
{
if (c)
create_part(photons[y][x] ? ID(photons[y][x]) : ID(pmap[y][x]), x, y, TYP(c), ID(c));
else
delete_part(x, y);
}
return 0;
}
else if (!c)
{
delete_part(x, y);
return 0;
}
else if (flags & SPECIFIC_DELETE)
{
// if delete whatever and there's something to delete
// or delete X and there's a non-energy particle on top with type X
// or delete X and there's an energy particle on top with type X
if ((!replaceModeSelected && (photons[y][x] || pmap[y][x])) ||
(!photons[y][x] && pmap[y][x] && TYP(pmap[y][x]) == replaceModeSelected) ||
(photons[y][x] && TYP(photons[y][x]) == replaceModeSelected))
{
delete_part(x, y);
}
return 0;
}
else
{
return (create_part(-2, x, y, TYP(c), ID(c)) == -1);
}
// I'm sure at least one compiler exists that would complain if this wasn't here
return 0;
}
int Simulation::GetParticleType(ByteString type)
{
type = type.ToUpper();
// alternative names for some elements
if (byteStringEqualsLiteral(type, "C4"))
return PT_PLEX;
else if (byteStringEqualsLiteral(type, "C5"))
return PT_C5;
else if (byteStringEqualsLiteral(type, "NONE"))
return PT_NONE;
for (int i = 1; i < PT_NUM; i++)
{
if (elements[i].Name.size() && elements[i].Enabled && type == elements[i].Name.ToUtf8().ToUpper())
{
return i;
}
}
return -1;
}
void Simulation::ApplyDecoration(int x, int y, int colR_, int colG_, int colB_, int colA_, int mode)
{
int rp;
float tr, tg, tb, ta, colR = float(colR_), colG = float(colG_), colB = float(colB_), colA = float(colA_);
float strength = 0.01f;
rp = pmap[y][x];
if (!rp)
rp = photons[y][x];
if (!rp)
return;
ta = float((parts[ID(rp)].dcolour>>24)&0xFF);
tr = float((parts[ID(rp)].dcolour>>16)&0xFF);
tg = float((parts[ID(rp)].dcolour>>8)&0xFF);
tb = float((parts[ID(rp)].dcolour)&0xFF);
ta /= 255.0f; tr /= 255.0f; tg /= 255.0f; tb /= 255.0f;
colR /= 255.0f; colG /= 255.0f; colB /= 255.0f; colA /= 255.0f;
if (mode == DECO_DRAW)
{
ta = colA;
tr = colR;
tg = colG;
tb = colB;
}
else if (mode == DECO_CLEAR)
{
ta = tr = tg = tb = 0.0f;
}
else if (mode == DECO_ADD)
{
//ta += (colA*strength)*colA;
tr += (colR*strength)*colA;
tg += (colG*strength)*colA;
tb += (colB*strength)*colA;
}
else if (mode == DECO_SUBTRACT)
{
//ta -= (colA*strength)*colA;
tr -= (colR*strength)*colA;
tg -= (colG*strength)*colA;
tb -= (colB*strength)*colA;
}
else if (mode == DECO_MULTIPLY)
{
tr *= 1.0f+(colR*strength)*colA;
tg *= 1.0f+(colG*strength)*colA;
tb *= 1.0f+(colB*strength)*colA;
}
else if (mode == DECO_DIVIDE)
{
tr /= 1.0f+(colR*strength)*colA;
tg /= 1.0f+(colG*strength)*colA;
tb /= 1.0f+(colB*strength)*colA;
}
else if (mode == DECO_SMUDGE)
{
if (x >= CELL && x < XRES-CELL && y >= CELL && y < YRES-CELL)
{
float tas = 0.0f, trs = 0.0f, tgs = 0.0f, tbs = 0.0f;
int rx, ry;
float num = 0;
for (rx=-2; rx<3; rx++)
for (ry=-2; ry<3; ry++)
{
if (abs(rx)+abs(ry) > 2 && TYP(pmap[y+ry][x+rx]) && parts[ID(pmap[y+ry][x+rx])].dcolour)
{
Particle part = parts[ID(pmap[y+ry][x+rx])];
num += 1.0f;
float pa = ((float)((part.dcolour>>24)&0xFF)) / 255.f;
float pr = ((float)((part.dcolour>>16)&0xFF)) / 255.f;
float pg = ((float)((part.dcolour>> 8)&0xFF)) / 255.f;
float pb = ((float)((part.dcolour )&0xFF)) / 255.f;
switch (deco_space)
{
case 0: // sRGB
pa = (pa <= 0.04045f) ? (pa / 12.92f) : pow((pa + 0.055f) / 1.055f, 2.4f);
pr = (pr <= 0.04045f) ? (pr / 12.92f) : pow((pr + 0.055f) / 1.055f, 2.4f);
pg = (pg <= 0.04045f) ? (pg / 12.92f) : pow((pg + 0.055f) / 1.055f, 2.4f);
pb = (pb <= 0.04045f) ? (pb / 12.92f) : pow((pb + 0.055f) / 1.055f, 2.4f);
break;
case 1: // linear
break;
case 2: // Gamma = 2.2
pa = pow(pa, 2.2f);
pr = pow(pr, 2.2f);
pg = pow(pg, 2.2f);
pb = pow(pb, 2.2f);
break;
case 3: // Gamma = 1.8
pa = pow(pa, 1.8f);
pr = pow(pr, 1.8f);
pg = pow(pg, 1.8f);
pb = pow(pb, 1.8f);
break;
}
tas += pa;
trs += pr;
tgs += pg;
tbs += pb;
}
}
if (num == 0)
return;
ta = tas / num;
tr = trs / num;
tg = tgs / num;
tb = tbs / num;
switch (deco_space)
{
case 0: // sRGB
ta = (ta <= 0.0031308f) ? (ta * 12.92f) : (1.055f * pow(ta, 1.f / 2.4f) - 0.055f);
tr = (tr <= 0.0031308f) ? (tr * 12.92f) : (1.055f * pow(tr, 1.f / 2.4f) - 0.055f);
tg = (tg <= 0.0031308f) ? (tg * 12.92f) : (1.055f * pow(tg, 1.f / 2.4f) - 0.055f);
tb = (tb <= 0.0031308f) ? (tb * 12.92f) : (1.055f * pow(tb, 1.f / 2.4f) - 0.055f);
break;
case 1: // linear
break;
case 2: // Gamma = 2.2
ta = pow(ta, 1.f / 2.2f);
tr = pow(tr, 1.f / 2.2f);
tg = pow(tg, 1.f / 2.2f);
tb = pow(tb, 1.f / 2.2f);
break;
case 3: // Gamma = 1.8
ta = pow(ta, 1.f / 1.8f);
tr = pow(tr, 1.f / 1.8f);
tg = pow(tg, 1.f / 1.8f);
tb = pow(tb, 1.f / 1.8f);
break;
}
if (!parts[ID(rp)].dcolour)
ta -= 3/255.0f;
}
}
ta *= 255.0f; tr *= 255.0f; tg *= 255.0f; tb *= 255.0f;
ta += .5f; tr += .5f; tg += .5f; tb += .5f;
colA_ = int(ta);
colR_ = int(tr);
colG_ = int(tg);
colB_ = int(tb);
if(colA_ > 255)
colA_ = 255;
else if(colA_ < 0)
colA_ = 0;
if(colR_ > 255)
colR_ = 255;
else if(colR_ < 0)
colR_ = 0;
if(colG_ > 255)
colG_ = 255;
else if(colG_ < 0)
colG_ = 0;
if(colB_ > 255)
colB_ = 255;
else if(colB_ < 0)
colB_ = 0;
parts[ID(rp)].dcolour = ((colA_<<24)|(colR_<<16)|(colG_<<8)|colB_);
}
void Simulation::ApplyDecorationPoint(int positionX, int positionY, int colR, int colG, int colB, int colA, int mode, Brush const &cBrush)
{
for (ui::Point off : cBrush)
{
ui::Point coords = ui::Point(positionX, positionY) + off;
if (coords.X >= 0 && coords.Y >= 0 && coords.X < XRES && coords.Y < YRES)
ApplyDecoration(coords.X, coords.Y, colR, colG, colB, colA, mode);
}
}
void Simulation::ApplyDecorationLine(int x1, int y1, int x2, int y2, int colR, int colG, int colB, int colA, int mode, Brush const &cBrush)
{
bool reverseXY = abs(y2-y1) > abs(x2-x1);
int x, y, dx, dy, sy, rx = 0, ry = 0;
float e = 0.0f, de;
rx = cBrush.GetRadius().X;
ry = cBrush.GetRadius().Y;
if (reverseXY)
{
y = x1;
x1 = y1;
y1 = y;
y = x2;
x2 = y2;
y2 = y;
}
if (x1 > x2)
{
y = x1;
x1 = x2;
x2 = y;
y = y1;
y1 = y2;
y2 = y;
}
dx = x2 - x1;
dy = abs(y2 - y1);
de = dx ? dy/(float)dx : 0.0f;
y = y1;
sy = (y1<y2) ? 1 : -1;
for (x=x1; x<=x2; x++)
{
if (reverseXY)
ApplyDecorationPoint(y, x, colR, colG, colB, colA, mode, cBrush);
else
ApplyDecorationPoint(x, y, colR, colG, colB, colA, mode, cBrush);
e += de;
if (e >= 0.5f)
{
y += sy;
if (!(rx+ry))
{
if (reverseXY)
ApplyDecorationPoint(y, x, colR, colG, colB, colA, mode, cBrush);
else
ApplyDecorationPoint(x, y, colR, colG, colB, colA, mode, cBrush);
}
e -= 1.0f;
}
}
}
void Simulation::ApplyDecorationBox(int x1, int y1, int x2, int y2, int colR, int colG, int colB, int colA, int mode)
{
int i, j;
if (x1>x2)
{
i = x2;
x2 = x1;
x1 = i;
}
if (y1>y2)
{
j = y2;
y2 = y1;
y1 = j;
}
for (j=y1; j<=y2; j++)
for (i=x1; i<=x2; i++)
ApplyDecoration(i, j, colR, colG, colB, colA, mode);
}
bool Simulation::ColorCompare(Renderer *ren, int x, int y, int replaceR, int replaceG, int replaceB)
{
pixel pix = ren->vid[x+y*WINDOWW];
int r = PIXR(pix);
int g = PIXG(pix);
int b = PIXB(pix);
int diff = std::abs(replaceR-r) + std::abs(replaceG-g) + std::abs(replaceB-b);
return diff < 15;
}
void Simulation::ApplyDecorationFill(Renderer *ren, int x, int y, int colR, int colG, int colB, int colA, int replaceR, int replaceG, int replaceB)
{
int x1, x2;
char *bitmap = (char*)malloc(XRES*YRES); //Bitmap for checking
if (!bitmap)
return;
memset(bitmap, 0, XRES*YRES);
if (!ColorCompare(ren, x, y, replaceR, replaceG, replaceB)) {
free(bitmap);
return;
}
try
{
CoordStack& cs = getCoordStackSingleton();
cs.clear();
cs.push(x, y);
do
{
cs.pop(x, y);
x1 = x2 = x;
// go left as far as possible
while (x1>0)
{
if (bitmap[(x1-1)+y*XRES] || !ColorCompare(ren, x1-1, y, replaceR, replaceG, replaceB))
{
break;
}
x1--;
}
// go right as far as possible
while (x2<XRES-1)
{
if (bitmap[(x1+1)+y*XRES] || !ColorCompare(ren, x2+1, y, replaceR, replaceG, replaceB))
{
break;
}
x2++;
}
// fill span
for (x=x1; x<=x2; x++)
{
ApplyDecoration(x, y, colR, colG, colB, colA, DECO_DRAW);
bitmap[x+y*XRES] = 1;
}
if (y >= 1)
for (x=x1; x<=x2; x++)
if (!bitmap[x+(y-1)*XRES] && ColorCompare(ren, x, y-1, replaceR, replaceG, replaceB))
cs.push(x, y-1);
if (y < YRES-1)
for (x=x1; x<=x2; x++)
if (!bitmap[x+(y+1)*XRES] && ColorCompare(ren, x, y+1, replaceR, replaceG, replaceB))
cs.push(x, y+1);
} while (cs.getSize() > 0);
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
free(bitmap);
return;
}
free(bitmap);
}
int Simulation::ToolBrush(int positionX, int positionY, int tool, Brush const &cBrush, float strength)
{
for (ui::Point off : cBrush)
{
ui::Point coords = ui::Point(positionX, positionY) + off;
if (coords.X >= 0 && coords.Y >= 0 && coords.X < XRES && coords.Y < YRES)
Tool(coords.X, coords.Y, tool, positionX, positionY, strength);
}
return 0;
}
void Simulation::ToolLine(int x1, int y1, int x2, int y2, int tool, Brush const &cBrush, float strength)
{
bool reverseXY = abs(y2-y1) > abs(x2-x1);
int x, y, dx, dy, sy, rx = cBrush.GetRadius().X, ry = cBrush.GetRadius().Y;
float e = 0.0f, de;
if (reverseXY)
{
y = x1;
x1 = y1;
y1 = y;
y = x2;
x2 = y2;
y2 = y;
}
if (x1 > x2)
{
y = x1;
x1 = x2;
x2 = y;
y = y1;
y1 = y2;
y2 = y;
}
dx = x2 - x1;
dy = abs(y2 - y1);
de = dx ? dy/(float)dx : 0.0f;
y = y1;
sy = (y1<y2) ? 1 : -1;
for (x=x1; x<=x2; x++)
{
if (reverseXY)
ToolBrush(y, x, tool, cBrush, strength);
else
ToolBrush(x, y, tool, cBrush, strength);
e += de;
if (e >= 0.5f)
{
y += sy;
if (!(rx+ry) && ((y1<y2) ? (y<=y2) : (y>=y2)))
{
if (reverseXY)
ToolBrush(y, x, tool, cBrush, strength);
else
ToolBrush(x, y, tool, cBrush, strength);
}
e -= 1.0f;
}
}
}
void Simulation::ToolBox(int x1, int y1, int x2, int y2, int tool, float strength)
{
int brushX, brushY;
brushX = ((x1 + x2) / 2);
brushY = ((y1 + y2) / 2);
int i, j;
if (x1>x2)
{
i = x2;
x2 = x1;
x1 = i;
}
if (y1>y2)
{
j = y2;
y2 = y1;
y1 = j;
}
for (j=y1; j<=y2; j++)
for (i=x1; i<=x2; i++)
Tool(i, j, tool, brushX, brushY, strength);
}
int Simulation::CreateParts(int positionX, int positionY, int c, Brush const &cBrush, int flags)
{
if (flags == -1)
flags = replaceModeFlags;
int radiusX = cBrush.GetRadius().X, radiusY = cBrush.GetRadius().Y;
// special case for LIGH
if (c == PT_LIGH)
{
if (currentTick < lightningRecreate)
return 1;
int newlife = radiusX + radiusY;
if (newlife > 55)
newlife = 55;
c = PMAP(newlife, c);
lightningRecreate = currentTick + std::max(newlife / 4, 1);
return CreatePartFlags(positionX, positionY, c, flags);
}
else if (c == PT_TESC)
{
int newtmp = (radiusX*4+radiusY*4+7);
if (newtmp > 300)
newtmp = 300;
c = PMAP(newtmp, c);
}
for (ui::Point off : cBrush)
{
ui::Point coords = ui::Point(positionX, positionY) + off;
if (coords.X >= 0 && coords.Y >= 0 && coords.X < XRES && coords.Y < YRES)
CreatePartFlags(coords.X, coords.Y, c, flags);
}
return 0;
}
int Simulation::CreateParts(int x, int y, int rx, int ry, int c, int flags)
{
bool created = false;
if (flags == -1)
flags = replaceModeFlags;
// special case for LIGH
if (c == PT_LIGH)
{
if (currentTick < lightningRecreate)
return 1;
int newlife = rx + ry;
if (newlife > 55)
newlife = 55;
c = PMAP(newlife, c);
lightningRecreate = currentTick + std::max(newlife / 4, 1);
rx = ry = 0;
}
else if (c == PT_TESC)
{
int newtmp = (rx*4+ry*4+7);
if (newtmp > 300)
newtmp = 300;
c = PMAP(newtmp, c);
}
for (int j = -ry; j <= ry; j++)
for (int i = -rx; i <= rx; i++)
if (CreatePartFlags(x+i, y+j, c, flags))
created = true;
return !created;
}
void Simulation::CreateLine(int x1, int y1, int x2, int y2, int c, Brush const &cBrush, int flags)
{
int x, y, dx, dy, sy, rx = cBrush.GetRadius().X, ry = cBrush.GetRadius().Y;
bool reverseXY = abs(y2-y1) > abs(x2-x1);
float e = 0.0f, de;
if (reverseXY)
{
y = x1;
x1 = y1;
y1 = y;
y = x2;
x2 = y2;
y2 = y;
}
if (x1 > x2)
{
y = x1;
x1 = x2;
x2 = y;
y = y1;
y1 = y2;
y2 = y;
}
dx = x2 - x1;
dy = abs(y2 - y1);
de = dx ? dy/(float)dx : 0.0f;
y = y1;
sy = (y1<y2) ? 1 : -1;
for (x=x1; x<=x2; x++)
{
if (reverseXY)
CreateParts(y, x, c, cBrush, flags);
else
CreateParts(x, y, c, cBrush, flags);
e += de;
if (e >= 0.5f)
{
y += sy;
if (!(rx+ry) && ((y1<y2) ? (y<=y2) : (y>=y2)))
{
if (reverseXY)
CreateParts(y, x, c, cBrush, flags);
else
CreateParts(x, y, c, cBrush, flags);
}
e -= 1.0f;
}
}
}
void Simulation::CreateBox(int x1, int y1, int x2, int y2, int c, int flags)
{
int i, j;
if (x1>x2)
{
i = x2;
x2 = x1;
x1 = i;
}
if (y1>y2)
{
j = y2;
y2 = y1;
y1 = j;
}
for (j=y2; j>=y1; j--)
for (i=x1; i<=x2; i++)
CreateParts(i, j, 0, 0, c, flags);
}
int Simulation::FloodParts(int x, int y, int fullc, int cm, int flags)
{
int c = TYP(fullc);
int x1, x2, dy = (c<PT_NUM)?1:CELL;
int coord_stack_limit = XRES*YRES;
unsigned short (*coord_stack)[2];
int coord_stack_size = 0;
int created_something = 0;
// Bitmap for checking where we've already looked
auto bitmapPtr = std::unique_ptr<char[]>(new char[XRES * YRES]);
char *bitmap = bitmapPtr.get();
std::fill(&bitmap[0], &bitmap[0] + XRES * YRES, 0);
if (cm==-1)
{
//if initial flood point is out of bounds, do nothing
if (c != 0 && (x < CELL || x >= XRES-CELL || y < CELL || y >= YRES-CELL || c == PT_SPRK))
return 1;
else if (x < 0 || x >= XRES || y < 0 || y >= YRES)
return 1;
if (c == 0)
{
cm = TYP(pmap[y][x]);
if (!cm)
{
cm = TYP(photons[y][x]);
if (!cm)
{
if (bmap[y/CELL][x/CELL])
return FloodWalls(x, y, WL_ERASE, -1);
else
return -1;
}
}
}
else
cm = 0;
}
if (c != 0 && IsWallBlocking(x, y, c))
return 1;
if (!FloodFillPmapCheck(x, y, cm))
return 1;
coord_stack = (short unsigned int (*)[2])malloc(sizeof(unsigned short)*2*coord_stack_limit);
coord_stack[coord_stack_size][0] = x;
coord_stack[coord_stack_size][1] = y;
coord_stack_size++;
do
{
coord_stack_size--;
x = coord_stack[coord_stack_size][0];
y = coord_stack[coord_stack_size][1];
x1 = x2 = x;
// go left as far as possible
while (c?x1>CELL:x1>0)
{
if (bitmap[(y * XRES) + x1 - 1] || !FloodFillPmapCheck(x1-1, y, cm) || (c != 0 && IsWallBlocking(x1-1, y, c)))
{
break;
}
x1--;
}
// go right as far as possible
while (c?x2<XRES-CELL-1:x2<XRES-1)
{
if (bitmap[(y * XRES) + x2 + 1] || !FloodFillPmapCheck(x2+1, y, cm) || (c != 0 && IsWallBlocking(x2+1, y, c)))
{
break;
}
x2++;
}
// fill span
for (x=x1; x<=x2; x++)
{
if (!fullc)
{
if (elements[cm].Properties&TYPE_ENERGY)
{
if (photons[y][x])
{
kill_part(ID(photons[y][x]));
created_something = 1;
}
}
else if (pmap[y][x])
{
kill_part(ID(pmap[y][x]));
created_something = 1;
}
}
else if (CreateParts(x, y, 0, 0, fullc, flags))
created_something = 1;
bitmap[(y * XRES) + x] = 1;
}
if (c?y>=CELL+dy:y>=dy)
for (x=x1; x<=x2; x++)
if (!bitmap[((y - dy) * XRES) + x] && FloodFillPmapCheck(x, y-dy, cm) && (c == 0 || !IsWallBlocking(x, y-dy, c)))
{
coord_stack[coord_stack_size][0] = x;
coord_stack[coord_stack_size][1] = y-dy;
coord_stack_size++;
if (coord_stack_size>=coord_stack_limit)
{
free(coord_stack);
return -1;
}
}
if (c?y<YRES-CELL-dy:y<YRES-dy)
for (x=x1; x<=x2; x++)
if (!bitmap[((y + dy) * XRES) + x] && FloodFillPmapCheck(x, y+dy, cm) && (c == 0 || !IsWallBlocking(x, y+dy, c)))
{
coord_stack[coord_stack_size][0] = x;
coord_stack[coord_stack_size][1] = y+dy;
coord_stack_size++;
if (coord_stack_size>=coord_stack_limit)
{
free(coord_stack);
return -1;
}
}
} while (coord_stack_size>0);
free(coord_stack);
return created_something;
}