Change gravity timing and add support for saving it

Timing is now such that the input to the gravity process (the point masses and the mask derived from gravity walls) is passed in BeforeSim and the output of the gravity process (the 2D force field) is taken also in BeforeSim, at the same time. This means that input generated by particles in frame n is passed to the gravity process at the beginning of frame n+1, the corresponding output is available by the beginning of frame n+2, and so it is visible to particles in frame n+2. Thus, gravity is now properly and predictably one frame late, though, sadly, it's displayed two frames late.

This is in contrast with the case of not being late at all, which would be the case if input generated in frame n produced visible output by frame n+1, and also in contrast with what we've had until now, which was that gravity was *at least* one frame late, but it could be more out of sync than that if the gravity process for some reason took more time to produce output.

Further, both input and output now make it into snapshots and saves, which fixes one half of what causes the phenomenon wherein beams of PHOT under the effect of gravity wiggle for a few frames before stabilizing after a save is loaded. The other half is that velocities are saved at very a low precision, so overall, the effect of this change on this phenomenon is negligible.

It is not crucial that a client understands this new piece of info, as clients have been fine not getting gravity data from saves for years. Thus, its presence does not restrict the save to client versions newer than the one that adds this feature.

An interesting question this brings up is whether settings, crucially, the enabled state of Newtonian gravity, should be considered simulation state and be also included in saves.

Gravity data is now also included in the output of sim.hash, so Newtonian gravity no longer has to be disabled in order to ensure local determinism.

The diff is large because I gave up on being non-invasive and renamed important structures that lots of elements use. gravp, which was functionally just hypot(gravx, gravy), was removed, because it was only ever used to display a single value in the HUD. I've also taken a few detours and made changes that really should have been in separate commits, see below, but oh well.

This commit also includes a complete rework of the gravity wall area of effect discovery algorithm. The old implementation was extremely broken even beyond the usual C99-isms.

Also fix Simulation.NewtonianGravity being read as an integer from powder.pref, even though it's a boolean and is in fact saved as a boolean. It's pure luck that it's worked fine until now.

Also introduce PlaneBase for use with PlaneAdapter. PlaneBase is a very budget version of std::span from C++20, so budget in fact that it doesn't even store span size; this is fine because PlaneAdapter knows the span size anyway. std::basic_string_view worked for char types but this new code deals with floats.

Also include blockAir data in saves unconditionally. It used to be included only if ensureDeterminism was enabled, like rngState and frameCount, but those last two are conditionally included only because some assumptions are broken if they are included, such as that of expecting a saved simulation to take different paths of evolution after each reload. blockAir has no such effect.

Also remove sim.resetGravityField aka tpt.reset_gravity_field because it was agreed that it's really weird that the output of the gravity process can be changed at all from Lua, not to mention that it can only be reset to zero. No scripts to my knowledge ever used these functions.
This commit is contained in:
Tamás Bálint Misius 2024-07-01 20:47:18 +02:00
parent 0345dcf508
commit 7e9d9686dd
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
36 changed files with 597 additions and 749 deletions

View File

@ -76,16 +76,6 @@ void RGB_to_HSV(int r,int g,int b,int *h,int *s,int *v)//convert 0-255 RGB value
}
}
void membwand(void * destv, void * srcv, size_t destsize, size_t srcsize)
{
size_t i;
unsigned char * dest = (unsigned char*)destv;
unsigned char * src = (unsigned char*)srcv;
for(i = 0; i < destsize; i++){
dest[i] = dest[i] & src[i%srcsize];
}
}
bool byteStringEqualsString(const ByteString &str, const char *data, size_t size)
{
return str.size() == size && !memcmp(str.data(), data, size);

View File

@ -79,7 +79,6 @@ inline float restrict_flt(float f, float min, float max)
void HSV_to_RGB(int h,int s,int v,int *r,int *g,int *b);
void RGB_to_HSV(int r,int g,int b,int *h,int *s,int *v);
void membwand(void * dest, void * src, size_t destsize, size_t srcsize);
class ByteString;

View File

@ -189,6 +189,10 @@ void GameSave::setSize(Vec2<int> newBlockSize)
ambientHeat = PlaneAdapter<std::vector<float>>(blockSize, 0.0f);
blockAir = PlaneAdapter<std::vector<unsigned char>>(blockSize, 0);
blockAirh = PlaneAdapter<std::vector<unsigned char>>(blockSize, 0);
gravMass = PlaneAdapter<std::vector<float>>(blockSize, 0.f);
gravMask = PlaneAdapter<std::vector<uint32_t>>(blockSize, UINT32_C(0xFFFFFFFF));
gravForceX = PlaneAdapter<std::vector<float>>(blockSize, 0.f);
gravForceY = PlaneAdapter<std::vector<float>>(blockSize, 0.f);
}
std::pair<bool, std::vector<char>> GameSave::Serialise() const
@ -305,6 +309,10 @@ void GameSave::Transform(Mat2<int> transform, Vec2<int> nudge)
PlaneAdapter<std::vector<float>> newAmbientHeat(newBlockS, 0.0f);
PlaneAdapter<std::vector<unsigned char>> newBlockAir(newBlockS, 0);
PlaneAdapter<std::vector<unsigned char>> newBlockAirh(newBlockS, 0);
PlaneAdapter<std::vector<float>> newGravMass(newBlockS, 0.f);
PlaneAdapter<std::vector<uint32_t>> newGravMask(newBlockS, UINT32_C(0xFFFFFFFF));
PlaneAdapter<std::vector<float>> newGravForceX(newBlockS, 0.f);
PlaneAdapter<std::vector<float>> newGravForceY(newBlockS, 0.f);
for (auto bpos : blockSize.OriginRect())
{
auto newBpos = transform * bpos + btranslate;
@ -328,6 +336,10 @@ void GameSave::Transform(Mat2<int> transform, Vec2<int> nudge)
newAmbientHeat[newBpos] = ambientHeat[bpos];
newBlockAir[newBpos] = blockAir[bpos];
newBlockAirh[newBpos] = blockAirh[bpos];
newGravMass[newBpos] = gravMass[bpos];
newGravMask[newBpos] = gravMask[bpos];
newGravForceX[newBpos] = gravForceX[bpos];
newGravForceY[newBpos] = gravForceY[bpos];
}
blockMap = std::move(newBlockMap);
fanVelX = std::move(newFanVelX);
@ -338,6 +350,10 @@ void GameSave::Transform(Mat2<int> transform, Vec2<int> nudge)
ambientHeat = std::move(newAmbientHeat);
blockAir = std::move(newBlockAir);
blockAirh = std::move(newBlockAirh);
gravMass = std::move(newGravMass);
gravMask = std::move(newGravMask);
gravForceX = std::move(newGravForceX);
gravForceY = std::move(newGravForceY);
blockSize = newBlockS;
}
@ -424,9 +440,9 @@ void GameSave::readOPS(const std::vector<char> &data)
Renderer::PopulateTables();
unsigned char *inputData = (unsigned char*)&data[0], *partsData = NULL, *partsPosData = NULL, *fanData = NULL, *wallData = NULL, *soapLinkData = NULL;
unsigned char *pressData = NULL, *vxData = NULL, *vyData = NULL, *ambientData = NULL, *blockAirData = nullptr;
unsigned char *pressData = NULL, *vxData = NULL, *vyData = NULL, *ambientData = NULL, *blockAirData = nullptr, *gravityData = nullptr;
unsigned int inputDataLen = data.size(), bsonDataLen = 0, partsDataLen, partsPosDataLen, fanDataLen, wallDataLen, soapLinkDataLen;
unsigned int pressDataLen, vxDataLen, vyDataLen, ambientDataLen, blockAirDataLen;
unsigned int pressDataLen, vxDataLen, vyDataLen, ambientDataLen, blockAirDataLen, gravityDataLen;
unsigned partsCount = 0;
unsigned int savedVersion = inputData[4];
version = { savedVersion, 0 };
@ -537,6 +553,7 @@ void GameSave::readOPS(const std::vector<char> &data)
CheckBsonFieldUser(iter, "vyMap", &vyData, &vyDataLen);
CheckBsonFieldUser(iter, "ambientMap", &ambientData, &ambientDataLen);
CheckBsonFieldUser(iter, "blockAir", &blockAirData, &blockAirDataLen);
CheckBsonFieldUser(iter, "gravity", &gravityData, &gravityDataLen);
CheckBsonFieldUser(iter, "fanMap", &fanData, &fanDataLen);
CheckBsonFieldUser(iter, "soapLinks", &soapLinkData, &soapLinkDataLen);
CheckBsonFieldBool(iter, "legacyEnable", &legacyEnable);
@ -762,8 +779,7 @@ void GameSave::readOPS(const std::vector<char> &data)
//Read wall and fan data
if(wallData)
{
// TODO: use PlaneAdapter<std::span<unsigned char>> once we're C++20
auto wallDataPlane = PlaneAdapter<const std::basic_string_view<unsigned char>>(blockS, std::in_place, wallData, blockS.X * blockS.Y);
auto wallDataPlane = PlaneAdapter<PlaneBase<const unsigned char>>(blockS, std::in_place, wallData);
unsigned int j = 0;
if (blockS.X * blockS.Y > int(wallDataLen))
throw ParseException(ParseException::Corrupt, "Not enough wall data");
@ -875,17 +891,36 @@ void GameSave::readOPS(const std::vector<char> &data)
{
if (blockS.X * blockS.Y * 2 > int(blockAirDataLen))
throw ParseException(ParseException::Corrupt, "Not enough block air data");
// TODO: use PlaneAdapter<std::span<unsigned char>> once we're C++20
auto blockAirDataPlane = PlaneAdapter<const std::basic_string_view<unsigned char>>(blockS, std::in_place, blockAirData, blockS.X * blockS.Y);
auto blockAirhDataPlane = PlaneAdapter<const std::basic_string_view<unsigned char>>(blockS, std::in_place, blockAirData + blockS.X * blockS.Y, blockS.X * blockS.Y);
auto blockAirDataPlane = PlaneAdapter<PlaneBase<const unsigned char>>(blockS, std::in_place, blockAirData);
auto blockAirhDataPlane = PlaneAdapter<PlaneBase<const unsigned char>>(blockS, std::in_place, blockAirData + blockS.X * blockS.Y);
for (auto bpos : blockS.OriginRect().Range<LEFT_TO_RIGHT, TOP_TO_BOTTOM>())
{
blockAir[blockP + bpos] = blockAirDataPlane[bpos];
blockAir [blockP + bpos] = blockAirDataPlane [bpos];
blockAirh[blockP + bpos] = blockAirhDataPlane[bpos];
}
hasBlockAirMaps = true;
}
if (gravityData)
{
if (blockS.X * blockS.Y * 4 > int(gravityDataLen))
{
throw ParseException(ParseException::Corrupt, "Not enough gravity data");
}
auto massDataPlane = PlaneAdapter<PlaneBase<const float >>(blockS, std::in_place, reinterpret_cast<const float *>(gravityData ));
auto maskDataPlane = PlaneAdapter<PlaneBase<const uint32_t>>(blockS, std::in_place, reinterpret_cast<const uint32_t *>(gravityData + blockS.X * blockS.Y * sizeof(float)));
auto forceXDataPlane = PlaneAdapter<PlaneBase<const float >>(blockS, std::in_place, reinterpret_cast<const float *>(gravityData + 2 * blockS.X * blockS.Y * sizeof(float)));
auto forceYDataPlane = PlaneAdapter<PlaneBase<const float >>(blockS, std::in_place, reinterpret_cast<const float *>(gravityData + 3 * blockS.X * blockS.Y * sizeof(float)));
for (auto bpos : blockS.OriginRect().Range<LEFT_TO_RIGHT, TOP_TO_BOTTOM>())
{
gravMass [blockP + bpos] = massDataPlane [bpos];
gravMask [blockP + bpos] = maskDataPlane [bpos];
gravForceX[blockP + bpos] = forceXDataPlane[bpos];
gravForceY[blockP + bpos] = forceYDataPlane[bpos];
}
hasGravityMaps = true;
}
//Read particle data
if (partsData && partsPosData)
{
@ -1441,8 +1476,7 @@ void GameSave::readPSv(const std::vector<char> &dataVec)
}
for (auto bpos : RectSized(blockP, blockS).Range<TOP_TO_BOTTOM, LEFT_TO_RIGHT>())
{
// TODO: use PlaneAdapter<std::span<unsigned char>> once we're C++20
auto dataPlane = PlaneAdapter<const std::basic_string_view<unsigned char>>(blockS, std::in_place, data, blockS.X * blockS.Y);
auto dataPlane = PlaneAdapter<PlaneBase<const unsigned char>>(blockS, std::in_place, data);
if (dataPlane[bpos - blockP]==4||(ver>=44 && dataPlane[bpos - blockP]==O_WL_FAN))
{
if (p >= dataLength)
@ -1452,8 +1486,7 @@ void GameSave::readPSv(const std::vector<char> &dataVec)
}
for (auto bpos : RectSized(blockP, blockS).Range<TOP_TO_BOTTOM, LEFT_TO_RIGHT>())
{
// TODO: use PlaneAdapter<std::span<unsigned char>> once we're C++20
auto dataPlane = PlaneAdapter<const std::basic_string_view<unsigned char>>(blockS, std::in_place, data, blockS.X * blockS.Y);
auto dataPlane = PlaneAdapter<PlaneBase<const unsigned char>>(blockS, std::in_place, data);
if (dataPlane[bpos - blockP]==4||(ver>=44 && dataPlane[bpos - blockP]==O_WL_FAN))
{
if (p >= dataLength)
@ -1569,8 +1602,7 @@ void GameSave::readPSv(const std::vector<char> &dataVec)
}
}
}
// TODO: use PlaneAdapter<std::span<unsigned char>> once we're C++20
auto dataPlanePty = PlaneAdapter<const std::basic_string_view<unsigned char>>(partS, std::in_place, data + pty, partS.X * partS.Y);
auto dataPlanePty = PlaneAdapter<PlaneBase<const unsigned char>>(partS, std::in_place, data + pty);
if (ver>=53) {
for (auto pos : partS.OriginRect().Range<TOP_TO_BOTTOM, LEFT_TO_RIGHT>())
{
@ -1947,8 +1979,17 @@ std::pair<bool, std::vector<char>> GameSave::serialiseOPS() const
std::vector<unsigned char> vxData(blockSize.X*blockSize.Y*2);
std::vector<unsigned char> vyData(blockSize.X*blockSize.Y*2);
std::vector<unsigned char> ambientData(blockSize.X*blockSize.Y*2, 0);
// TODO: have a separate vector with two PlaneAdapter<std::span<unsigned char>>s over it once we're C++20
PlaneAdapter<std::vector<unsigned char>> blockAirData({ blockSize.X, blockSize.Y * 2 });
std::vector<unsigned char> blockAirData(blockSize.X * blockSize.Y * 2);
PlaneAdapter<PlaneBase<unsigned char>> blockAirDataPlane (blockSize, std::in_place, &blockAirData[0] );
PlaneAdapter<PlaneBase<unsigned char>> blockAirhDataPlane(blockSize, std::in_place, &blockAirData[0] + blockSize.X * blockSize.Y);
std::vector<unsigned char> gravityData(blockSize.X * blockSize.Y * 4 * sizeof(float));
PlaneAdapter<PlaneBase<float >> massDataPlane (blockSize, std::in_place, reinterpret_cast<float *>(&gravityData[0] ));
PlaneAdapter<PlaneBase<uint32_t>> maskDataPlane (blockSize, std::in_place, reinterpret_cast<uint32_t *>(&gravityData[0] + blockSize.X * blockSize.Y * sizeof(float)));
PlaneAdapter<PlaneBase<float >> forceXDataPlane(blockSize, std::in_place, reinterpret_cast<float *>(&gravityData[0] + 2 * blockSize.X * blockSize.Y * sizeof(float)));
PlaneAdapter<PlaneBase<float >> forceYDataPlane(blockSize, std::in_place, reinterpret_cast<float *>(&gravityData[0] + 3 * blockSize.X * blockSize.Y * sizeof(float)));
unsigned int wallDataLen = blockSize.X*blockSize.Y, fanDataLen = 0, pressDataLen = 0, vxDataLen = 0, vyDataLen = 0, ambientDataLen = 0;
for (auto bpos : RectSized(blockP, blockS).Range<LEFT_TO_RIGHT, TOP_TO_BOTTOM>())
@ -1972,8 +2013,13 @@ std::pair<bool, std::vector<char>> GameSave::serialiseOPS() const
vyData[vyDataLen++] = (unsigned char)((int)(velY*128)&0xFF);
vyData[vyDataLen++] = (unsigned char)((int)(velY*128)>>8);
blockAirData[bpos - blockP] = blockAir[bpos];
blockAirData[(bpos - blockP) + Vec2{ 0, blockS.Y }] = blockAirh[bpos];
blockAirDataPlane [bpos - blockP] = blockAir [bpos];
blockAirhDataPlane[bpos - blockP] = blockAirh[bpos];
massDataPlane [bpos - blockP] = gravMass [bpos];
maskDataPlane [bpos - blockP] = gravMask [bpos];
forceXDataPlane[bpos - blockP] = gravForceX[bpos];
forceYDataPlane[bpos - blockP] = gravForceY[bpos];
}
if (hasAmbientHeat)
@ -2522,14 +2568,18 @@ std::pair<bool, std::vector<char>> GameSave::serialiseOPS() const
bson_append_binary(&b, "ambientMap", (char)BSON_BIN_USER, (const char*)&ambientData[0], ambientDataLen);
if (soapLinkDataLen)
bson_append_binary(&b, "soapLinks", (char)BSON_BIN_USER, (const char *)&soapLinkData[0], soapLinkDataLen);
bson_append_binary(&b, "blockAir", (char)BSON_BIN_USER, (const char *)blockAirData.data(), blockAirData.size());
if (ensureDeterminism)
{
bson_append_bool(&b, "ensureDeterminism", ensureDeterminism);
bson_append_binary(&b, "blockAir", (char)BSON_BIN_USER, (const char *)blockAirData.data(), blockAirData.Size().X * blockAirData.Size().Y);
bson_append_long(&b, "frameCount", int64_t(frameCount));
bson_append_binary(&b, "rngState", (char)BSON_BIN_USER, (const char *)&rngState, sizeof(rngState));
RESTRICTVERSION(98, 0);
}
if (gravityEnable)
{
bson_append_binary(&b, "gravity", (char)BSON_BIN_USER, (const char *)gravityData.data(), gravityData.size());
}
unsigned int signsCount = 0;
for (size_t i = 0; i < signs.size(); i++)
{

View File

@ -6,6 +6,7 @@
#include "simulation/Sign.h"
#include "simulation/Particle.h"
#include "simulation/MissingElements.h"
#include "simulation/gravity/GravityData.h"
#include "Misc.h"
#include "SimulationConfig.h"
#include <vector>
@ -71,7 +72,8 @@ public:
Version<2> version{};
bool hasPressure = false;
bool hasAmbientHeat = false;
bool hasBlockAirMaps = false; // only written by readOPS, never read
bool hasBlockAirMaps = false;
bool hasGravityMaps = false;
bool ensureDeterminism = false; // only taken seriously by serializeOPS; readOPS may set this even if the save does not have everything required for determinism
bool hasRngState = false; // only written by readOPS, never read
RNG::State rngState;
@ -89,6 +91,10 @@ public:
PlaneAdapter<std::vector<float>> ambientHeat;
PlaneAdapter<std::vector<unsigned char>> blockAir;
PlaneAdapter<std::vector<unsigned char>> blockAirh;
PlaneAdapter<std::vector<float>> gravMass;
PlaneAdapter<std::vector<uint32_t>> gravMask;
PlaneAdapter<std::vector<float>> gravForceX;
PlaneAdapter<std::vector<float>> gravForceY;
//Simulation Options
bool waterEEnabled = false;

View File

@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <cstddef>
#include <functional>
#include <limits>
#include <type_traits>
@ -8,6 +9,42 @@
#include "common/Vec2.h"
// TODO: std::span once we're C++20
template<class Item>
struct PlaneBase
{
Item *base;
PlaneBase(Item *newBase) : base(newBase)
{
}
Item *begin()
{
return base;
}
const Item *begin() const
{
return base;
}
Item &operator [](size_t index)
{
return *(base + index);
}
const Item &operator [](size_t index) const
{
return *(base + index);
}
const Item *data() const
{
return base;
}
};
constexpr size_t DynamicExtent = std::numeric_limits<size_t>::max();
template<size_t Extent>

View File

@ -814,22 +814,18 @@ void Renderer::draw_other() // EMP effect
void Renderer::draw_grav_zones()
{
if(!gravityZonesEnabled)
return;
int x, y, i, j;
for (y=0; y<YCELLS; y++)
if (!gravityZonesEnabled)
{
for (x=0; x<XCELLS; x++)
return;
}
for (auto p : CELLS.OriginRect())
{
if (sim->gravIn.mask[p])
{
if(sim->grav->gravmask[y*XCELLS+x])
auto np = p * CELL;
for (auto o : Vec2{ CELL, CELL }.OriginRect())
{
for (j=0; j<CELL; j++)//draws the colors
for (i=0; i<CELL; i++)
if(i == j)
BlendPixel({ x*CELL+i, y*CELL+j }, 0xFFC800_rgb .WithAlpha(120));
else
BlendPixel({ x*CELL+i, y*CELL+j }, 0x202020_rgb .WithAlpha(120));
BlendPixel(np + o, (o.X == o.Y ? 0xFFC800_rgb : 0x202020_rgb).WithAlpha(120));
}
}
}
@ -837,28 +833,26 @@ void Renderer::draw_grav_zones()
void Renderer::draw_grav()
{
int x, y, i, ca;
float nx, ny, dist;
if(!gravityFieldEnabled)
return;
for (y=0; y<YCELLS; y++)
if (!gravityFieldEnabled)
{
for (x=0; x<XCELLS; x++)
return;
}
for (auto p : CELLS.OriginRect())
{
auto gx = sim->gravOut.forceX[p];
auto gy = sim->gravOut.forceY[p];
auto agx = std::abs(gx);
auto agy = std::abs(gy);
if (agx <= 0.001f && agy <= 0.001f)
{
ca = y*XCELLS+x;
if(fabsf(sim->gravx[ca]) <= 0.001f && fabsf(sim->gravy[ca]) <= 0.001f)
continue;
nx = float(x*CELL);
ny = float(y*CELL);
dist = fabsf(sim->gravy[ca])+fabsf(sim->gravx[ca]);
for(i = 0; i < 4; i++)
{
nx -= sim->gravx[ca]*0.5f;
ny -= sim->gravy[ca]*0.5f;
AddPixel({ int(nx+0.5f), int(ny+0.5f) }, 0xFFFFFF_rgb .WithAlpha(int(dist*20.0f)));
}
continue;
}
auto np = Vec2<float>(p.X * CELL, p.Y * CELL);
auto dist = agx + agy;
for (auto i = 0; i < 4; ++i)
{
np -= Vec2{ gx * 0.5f, gy * 0.5f };
AddPixel({ int(np.X + 0.5f), int(np.Y + 0.5f) }, 0xFFFFFF_rgb .WithAlpha(int(dist * 20.0f)));
}
}
}

View File

@ -96,6 +96,30 @@ void Renderer::RenderZoom()
}
}
void Renderer::render_gravlensing(const Video &source)
{
for (auto p : RES.OriginRect())
{
auto cp = p / CELL;
auto rp = Vec2{ int(p.X - sim->gravOut.forceX[cp] * 0.75f + 0.5f), int(p.Y - sim->gravOut.forceY[cp] * 0.75f + 0.5f) };
auto gp = Vec2{ int(p.X - sim->gravOut.forceX[cp] * 0.875f + 0.5f), int(p.Y - sim->gravOut.forceY[cp] * 0.875f + 0.5f) };
auto bp = Vec2{ int(p.X - sim->gravOut.forceX[cp] + 0.5f), int(p.Y - sim->gravOut.forceY[cp] + 0.5f) };
if (RES.OriginRect().Contains(rp) &&
RES.OriginRect().Contains(gp) &&
RES.OriginRect().Contains(bp))
{
auto v = RGB<uint8_t>::Unpack(video[p]);
auto s = RGB<uint8_t>::Unpack(source[rp]);
video[p] = RGB<uint8_t>(
std::min(0xFF, s.Red + v.Red ),
std::min(0xFF, s.Green + v.Green),
std::min(0xFF, s.Blue + v.Blue )
).Pack();
}
}
}
void Renderer::DrawBlob(Vec2<int> pos, RGB<uint8_t> colour)
{
BlendPixel(pos + Vec2{ +1, 0 }, colour.WithAlpha(112));
@ -108,33 +132,6 @@ void Renderer::DrawBlob(Vec2<int> pos, RGB<uint8_t> colour)
BlendPixel(pos + Vec2{ -1, +1 }, colour.WithAlpha(64));
}
void Renderer::render_gravlensing(const Video &source)
{
int nx, ny, rx, ry, gx, gy, bx, by, co;
for(nx = 0; nx < XRES; nx++)
{
for(ny = 0; ny < YRES; ny++)
{
co = (ny/CELL)*XCELLS+(nx/CELL);
rx = (int)(nx-sim->gravx[co]*0.75f+0.5f);
ry = (int)(ny-sim->gravy[co]*0.75f+0.5f);
gx = (int)(nx-sim->gravx[co]*0.875f+0.5f);
gy = (int)(ny-sim->gravy[co]*0.875f+0.5f);
bx = (int)(nx-sim->gravx[co]+0.5f);
by = (int)(ny-sim->gravy[co]+0.5f);
if(rx >= 0 && rx < XRES && ry >= 0 && ry < YRES && gx >= 0 && gx < XRES && gy >= 0 && gy < YRES && bx >= 0 && bx < XRES && by >= 0 && by < YRES)
{
auto t = RGB<uint8_t>::Unpack(video[{ nx, ny }]);
t.Red = std::min(0xFF, (int)RGB<uint8_t>::Unpack(source[{ rx, ry }]).Red + t.Red);
t.Green = std::min(0xFF, (int)RGB<uint8_t>::Unpack(source[{ gx, gy }]).Green + t.Green);
t.Blue = std::min(0xFF, (int)RGB<uint8_t>::Unpack(source[{ bx, by }]).Blue + t.Blue);
video[{ nx, ny }] = t.Pack();
}
}
}
}
float temp[CELL*3][CELL*3];
float fire_alphaf[CELL*3][CELL*3];
float glow_alphaf[11][11];

View File

@ -1,3 +1,4 @@
#include "Config.h"
#include "GameModel.h"
#include "BitmapBrush.h"
#include "EllipseBrush.h"
@ -106,9 +107,10 @@ GameModel::GameModel():
sim->air->ambientAirTemp = ambientAirTemp;
decoSpace = prefs.Get("Simulation.DecoSpace", NUM_DECOSPACES, DECOSPACE_SRGB);
sim->SetDecoSpace(decoSpace);
int ngrav_enable = prefs.Get("Simulation.NewtonianGravity", NUM_GRAVMODES, GRAV_VERTICAL);
if (ngrav_enable)
sim->grav->start_grav_async();
if (prefs.Get("Simulation.NewtonianGravity", false))
{
sim->EnableNewtonianGravity(true);
}
sim->aheat_enable = prefs.Get("Simulation.AmbientHeat", 0); // TODO: AmbientHeat enum
sim->pretty_powder = prefs.Get("Simulation.PrettyPowder", 0); // TODO: PrettyPowder enum
@ -166,7 +168,7 @@ GameModel::~GameModel()
prefs.Set("Renderer.GravityField", (bool)ren->gravityFieldEnabled);
prefs.Set("Renderer.Decorations", (bool)ren->decorations_enable);
prefs.Set("Renderer.DebugMode", ren->debugLines); //These two should always be equivalent, even though they are different things
prefs.Set("Simulation.NewtonianGravity", sim->grav->IsEnabled());
prefs.Set("Simulation.NewtonianGravity", bool(sim->grav));
prefs.Set("Simulation.AmbientHeat", sim->aheat_enable);
prefs.Set("Simulation.PrettyPowder", sim->pretty_powder);
prefs.Set("Decoration.Red", (int)colour.Red);
@ -967,14 +969,7 @@ void GameModel::SaveToSimParameters(const GameSave &saveData)
sim->legacy_enable = saveData.legacyEnable;
sim->water_equal_test = saveData.waterEEnabled;
sim->aheat_enable = saveData.aheatEnable;
if (saveData.gravityEnable && !sim->grav->IsEnabled())
{
sim->grav->start_grav_async();
}
else if (!saveData.gravityEnable && sim->grav->IsEnabled())
{
sim->grav->stop_grav_async();
}
sim->EnableNewtonianGravity(saveData.gravityEnable);
sim->frameCount = saveData.frameCount;
if (saveData.hasRngState)
{
@ -1296,14 +1291,13 @@ void GameModel::ResetAHeat()
void GameModel::SetNewtonianGravity(bool newtonainGravity)
{
sim->EnableNewtonianGravity(newtonainGravity);
if (newtonainGravity)
{
sim->grav->start_grav_async();
SetInfoTip("Newtonian Gravity: On");
}
else
{
sim->grav->stop_grav_async();
SetInfoTip("Newtonian Gravity: Off");
}
UpdateQuickOptions();
@ -1311,7 +1305,7 @@ void GameModel::SetNewtonianGravity(bool newtonainGravity)
bool GameModel::GetNewtonianGrvity()
{
return sim->grav->IsEnabled();
return bool(sim->grav);
}
void GameModel::ShowGravityGrid(bool showGrid)

View File

@ -2457,7 +2457,9 @@ void GameView::OnDraw()
sampleInfo << "X:" << sample.PositionX << " Y:" << sample.PositionY;
if (sample.Gravity)
auto gravtot = std::abs(sample.GravityVelocityX) +
std::abs(sample.GravityVelocityY);
if (gravtot)
sampleInfo << ", GX: " << sample.GravityVelocityX << " GY: " << sample.GravityVelocityY;
if (c->GetAHeatEnable())

View File

@ -43,15 +43,12 @@ void OptionsModel::SetAmbientHeatSimulation(bool state)
bool OptionsModel::GetNewtonianGravity()
{
return sim->grav->IsEnabled();
return bool(sim->grav);
}
void OptionsModel::SetNewtonianGravity(bool state)
{
if(state)
sim->grav->start_grav_async();
else
sim->grav->stop_grav_async();
sim->EnableNewtonianGravity(state);
notifySettingsChanged();
}

View File

@ -52,14 +52,10 @@ static int newtonianGravity(lua_State *L)
int acount = lua_gettop(L);
if (acount == 0)
{
lua_pushboolean(L, lsi->sim->grav->IsEnabled());
lua_pushboolean(L, bool(lsi->sim->grav));
return 1;
}
int gravstate = lua_toboolean(L, 1);
if(gravstate)
lsi->sim->grav->start_grav_async();
else
lsi->sim->grav->stop_grav_async();
lsi->sim->EnableNewtonianGravity(lua_toboolean(L, 1));
lsi->gameModel->UpdateQuickOptions();
return 0;
}
@ -205,7 +201,7 @@ static int gravityMass(lua_State *L)
{
auto *lsi = GetLSI();
return LuaBlockMap(L, [lsi](Vec2<int> p) -> float & {
return lsi->sim->gravmap[p.Y * XCELLS + p.X];
return lsi->sim->gravIn.mass[p];
});
}
@ -217,8 +213,8 @@ static int gravityField(lua_State *L)
{
return luaL_error(L, "Coordinates (%i, %i) out of range", pos.X, pos.Y);
}
lua_pushnumber(L, lsi->sim->gravx[pos.Y * XCELLS + pos.X]);
lua_pushnumber(L, lsi->sim->gravy[pos.Y * XCELLS + pos.X]);
lua_pushnumber(L, lsi->sim->gravOut.forceX[pos]);
lua_pushnumber(L, lsi->sim->gravOut.forceY[pos]);
return 2;
}
@ -1798,34 +1794,6 @@ static int resetSpark(lua_State *L)
return 0;
}
static int resetGravityField(lua_State *L)
{
int nx, ny;
int x1, y1, width, height;
x1 = abs(luaL_optint(L, 1, 0));
y1 = abs(luaL_optint(L, 2, 0));
width = abs(luaL_optint(L, 3, XCELLS));
height = abs(luaL_optint(L, 4, YCELLS));
if(x1 > XCELLS-1)
x1 = XCELLS-1;
if(y1 > YCELLS-1)
y1 = YCELLS-1;
if(x1+width > XCELLS-1)
width = XCELLS-x1;
if(y1+height > YCELLS-1)
height = YCELLS-y1;
auto *lsi = GetLSI();
auto *sim = lsi->sim;
for (nx = x1; nx<x1+width; nx++)
for (ny = y1; ny<y1+height; ny++)
{
sim->gravx[ny*XCELLS+nx] = 0;
sim->gravy[ny*XCELLS+nx] = 0;
sim->gravp[ny*XCELLS+nx] = 0;
}
return 0;
}
static int randomSeed(lua_State *L)
{
auto *lsi = GetLSI();
@ -1946,7 +1914,6 @@ void LuaSimulation::Open(lua_State *L)
LFUNC(paused),
LFUNC(gravityMass),
LFUNC(gravityField),
LFUNC(resetGravityField),
LFUNC(resetSpark),
LFUNC(resetVelocity),
LFUNC(wallMap),

View File

@ -47,7 +47,6 @@ tpt.get_name = tpt.getUserName
tpt.menu_enabled = ui.menuEnabled
tpt.num_menus = ui.numMenus
tpt.perfectCircleBrush = ui.perfectCircleBrush
tpt.reset_gravity_field = sim.resetGravityField
tpt.reset_spark = sim.resetSpark
tpt.reset_velocity = sim.resetVelocity
tpt.set_clipboard = plat.clipboardPaste

View File

@ -26,16 +26,15 @@ std::unique_ptr<Snapshot> Simulation::CreateSnapshot() const
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->GravMass .insert(snap->GravMass .begin(), &gravIn.mass[{ 0, 0 }] , &gravIn.mass[{ 0, 0 }] + NCELL);
snap->GravForceX.insert(snap->GravForceX.begin(), &gravOut.forceX[{ 0, 0 }], &gravOut.forceX[{ 0, 0 }] + NCELL);
snap->GravForceY.insert(snap->GravForceY.begin(), &gravOut.forceY[{ 0, 0 }], &gravOut.forceY[{ 0, 0 }] + NCELL);
snap->signs = signs;
snap->FrameCount = frameCount;
snap->RngState = rng.state();
@ -61,26 +60,27 @@ void Simulation::Restore(const Snapshot &snap)
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];
{
GravityInput newGravIn;
GravityOutput newGravOut;
std::copy(snap.GravMass .begin(), snap.GravMass .end(), &newGravIn.mass[{ 0, 0 }] );
std::copy(snap.GravForceX.begin(), snap.GravForceX.end(), &newGravOut.forceX[{ 0, 0 }]);
std::copy(snap.GravForceY.begin(), snap.GravForceY.end(), &newGravOut.forceY[{ 0, 0 }]);
// we apply the old grav values but Newtonian gravity enable state is not part of the snapshot so this may be pointless
// TODO: maybe track settings like Newtonian gravity enable state in the history
ResetNewtonianGravity(newGravIn, newGravOut);
}
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)
@ -120,6 +120,7 @@ void Simulation::clear_area(int area_x, int area_y, int area_w, int area_h)
SimulationSample Simulation::GetSample(int x, int y)
{
SimulationSample sample;
sample.particle.type = 0;
sample.PositionX = x;
sample.PositionY = y;
if (x >= 0 && x < XRES && y >= 0 && y < YRES)
@ -143,11 +144,10 @@ SimulationSample Simulation::GetSample(int x, int y)
sample.AirVelocityX = vx[y/CELL][x/CELL];
sample.AirVelocityY = vy[y/CELL][x/CELL];
if(grav->IsEnabled())
if (grav)
{
sample.Gravity = gravp[(y/CELL)*XCELLS+(x/CELL)];
sample.GravityVelocityX = gravx[(y/CELL)*XCELLS+(x/CELL)];
sample.GravityVelocityY = gravy[(y/CELL)*XCELLS+(x/CELL)];
sample.GravityVelocityX = gravOut.forceX[Vec2{ x, y } / CELL];
sample.GravityVelocityY = gravOut.forceY[Vec2{ x, y } / CELL];
}
}
else

View File

@ -1,24 +1,21 @@
#pragma once
#include "Particle.h"
class SimulationSample
struct SimulationSample
{
public:
Particle particle;
int ParticleID;
int PositionX, PositionY;
float AirPressure;
float AirTemperature;
float AirVelocityX;
float AirVelocityY;
int ParticleID = 0;
int PositionX = 0;
int PositionY = 0;
float AirPressure = 0;
float AirTemperature = 0;
float AirVelocityX = 0;
float AirVelocityY = 0;
int WallType;
float Gravity;
float GravityVelocityX;
float GravityVelocityY;
int WallType = 0;
float GravityVelocityX = 0;
float GravityVelocityY = 0;
int NumParts;
bool isMouseInSim;
SimulationSample() : particle(), ParticleID(0), PositionX(0), PositionY(0), AirPressure(0), AirTemperature(0), AirVelocityX(0), AirVelocityY(0), WallType(0), Gravity(0), GravityVelocityX(0), GravityVelocityY(0), NumParts(0), isMouseInSim(true) {}
int NumParts = 0;
bool isMouseInSim = true;
};

View File

@ -261,6 +261,7 @@ void Simulation::Load(const GameSave *save, bool includePressure, Vec2<int> bloc
signs.push_back(tempSign);
}
}
auto useGravityMaps = save->hasGravityMaps && grav;
for (auto bpos : RectSized(blockP, save->blockSize) & CELLS.OriginRect())
{
auto spos = bpos - blockP;
@ -284,10 +285,21 @@ void Simulation::Load(const GameSave *save, bool includePressure, Vec2<int> bloc
}
if (save->hasBlockAirMaps)
{
air->bmap_blockair[bpos.Y][bpos.X] = save->blockAir[spos];
air->bmap_blockair [bpos.Y][bpos.X] = save->blockAir [spos];
air->bmap_blockairh[bpos.Y][bpos.X] = save->blockAirh[spos];
}
}
if (useGravityMaps)
{
gravIn.mass [bpos] = save->gravMass [spos];
gravIn.mask [bpos] = save->gravMask [spos];
gravOut.forceX[bpos] = save->gravForceX[spos];
gravOut.forceY[bpos] = save->gravForceY[spos];
}
}
if (useGravityMaps)
{
ResetNewtonianGravity(gravIn, gravOut);
}
gravWallChanged = true;
@ -398,6 +410,21 @@ std::unique_ptr<GameSave> Simulation::Save(bool includePressure, Rect<int> partR
newSave->blockAir[bpos] = air->bmap_blockair[bpos.Y + blockP.Y][bpos.X + blockP.X];
newSave->blockAirh[bpos] = air->bmap_blockairh[bpos.Y + blockP.Y][bpos.X + blockP.X];
}
if (grav)
{
newSave->gravMass [bpos] = gravIn.mass [bpos + blockP];
newSave->gravMask [bpos] = gravIn.mask [bpos + blockP];
newSave->gravForceX[bpos] = gravOut.forceX[bpos + blockP];
newSave->gravForceY[bpos] = gravOut.forceY[bpos + blockP];
}
}
if (includePressure)
{
newSave->hasBlockAirMaps = true;
}
if (grav)
{
newSave->hasGravityMaps = true;
}
if (includePressure || ensureDeterminism)
{
@ -433,7 +460,7 @@ void Simulation::SaveSimOptions(GameSave &gameSave)
gameSave.edgeMode = edgeMode;
gameSave.legacyEnable = legacy_enable;
gameSave.waterEEnabled = water_equal_test;
gameSave.gravityEnable = grav->IsEnabled();
gameSave.gravityEnable = bool(grav);
gameSave.aheatEnable = aheat_enable;
}
@ -1005,8 +1032,7 @@ void Simulation::clear_sim(void)
//memset(fire_b, 0, sizeof(fire_b));
//if(gravmask)
//memset(gravmask, 0xFFFFFFFF, NCELL*sizeof(unsigned));
if(grav)
grav->Clear();
ResetNewtonianGravity({}, {});
if(air)
{
air->Clear();
@ -1959,8 +1985,8 @@ void Simulation::GetGravityField(int x, int y, float particleGrav, float newtonG
}
if (newtonGrav)
{
pGravX += newtonGrav*gravx[(y/CELL)*XCELLS+(x/CELL)];
pGravY += newtonGrav*gravy[(y/CELL)*XCELLS+(x/CELL)];
pGravX += newtonGrav * gravOut.forceX[Vec2{ x, y } / CELL];
pGravY += newtonGrav * gravOut.forceY[Vec2{ x, y } / CELL];
}
}
@ -2791,7 +2817,8 @@ void Simulation::UpdateParticles(int start, int end)
{
auto s = 1;
auto gravtot = fabs(gravy[(y/CELL)*XCELLS+(x/CELL)])+fabs(gravx[(y/CELL)*XCELLS+(x/CELL)]);
auto gravtot = std::abs(gravOut.forceX[Vec2{ x, y } / CELL]) +
std::abs(gravOut.forceY[Vec2{ x, y } / CELL]);
if (elements[t].HighPressureTransition != NT && pv[y/CELL][x/CELL]>elements[t].HighPressure) {
// particle type change due to high pressure
if (elements[t].HighPressureTransition != ST)
@ -3171,7 +3198,7 @@ killed:
goto movedone;
}
}
if (elements[t].Falldown>1 && !grav->IsEnabled() && gravityMode==GRAV_VERTICAL && parts[i].vy>fabsf(parts[i].vx))
if (elements[t].Falldown>1 && !grav && gravityMode==GRAV_VERTICAL && parts[i].vy>fabsf(parts[i].vx))
{
auto s = 0;
// stagnant is true if FLAG_STAGNANT was set for this particle in previous frame
@ -3675,6 +3702,48 @@ void Simulation::CheckStacking()
}
}
void Simulation::UpdateGravityMask()
{
for (auto p : CELLS.OriginRect())
{
gravIn.mask[p] = 0;
}
std::stack<Vec2<int>> toCheck;
auto check = [this, &toCheck](Vec2<int> p) {
if (!(bmap[p.Y][p.X] == WL_GRAV || gravIn.mask[p]))
{
gravIn.mask[p] = UINT32_C(0xFFFFFFFF);
for (auto o : RectSized<int>({ -1, -1 }, { 3, 3 }))
{
if ((o.X + o.Y) & 1) // i.e. immediate neighbours but not diagonal ones
{
auto q = p + o;
if (CELLS.OriginRect().Contains(q))
{
toCheck.push(q);
}
}
}
}
};
for (auto x = 0; x < CELLS.X; ++x)
{
check({ x, 0 });
check({ x, CELLS.Y - 1 });
}
for (auto y = 1; y < CELLS.Y - 1; ++y) // corners already checked in the previous loop
{
check({ 0 , y });
check({ CELLS.X - 1, y });
}
while (!toCheck.empty())
{
auto p = toCheck.top();
toCheck.pop();
check(p);
}
}
//updates pmap, gol, and some other simulation stuff (but not particles)
void Simulation::BeforeSim()
{
@ -3685,16 +3754,9 @@ void Simulation::BeforeSim()
if(aheat_enable)
air->update_airh();
if(grav->IsEnabled())
{
grav->gravity_update_async();
DispatchNewtonianGravity();
gravIn = {};
//Get updated buffer pointers for gravity
gravx = &grav->gravx[0];
gravy = &grav->gravy[0];
gravp = &grav->gravp[0];
gravmap = &grav->gravmap[0];
}
if(emp_decor>0)
emp_decor -= emp_decor/25+2;
if(emp_decor < 0)
@ -3714,7 +3776,7 @@ void Simulation::BeforeSim()
if (gravWallChanged)
{
grav->gravity_mask();
UpdateGravityMask();
gravWallChanged = false;
}
@ -3923,16 +3985,6 @@ Simulation::Simulation():
std::fill(elementCount, elementCount+PT_NUM, 0);
elementRecount = true;
//Create and attach gravity simulation
grav = Gravity::Create();
//Give air sim references to our data
grav->bmap = bmap;
//Gravity sim gives us maps to use
gravx = &grav->gravx[0];
gravy = &grav->gravy[0];
gravp = &grav->gravp[0];
gravmap = &grav->gravmap[0];
//Create and attach air simulation
air = std::make_unique<Air>(*this);
//Give air sim references to our data
@ -3951,7 +4003,40 @@ Simulation::Simulation():
clear_sim();
grav->gravity_mask();
UpdateGravityMask();
}
void Simulation::DispatchNewtonianGravity()
{
if (grav)
{
grav->Exchange(gravOut, gravIn);
}
}
void Simulation::ResetNewtonianGravity(GravityInput newGravIn, GravityOutput newGravOut)
{
gravIn = newGravIn;
DispatchNewtonianGravity();
if (grav)
{
gravOut = newGravOut;
gravWallChanged = true;
}
}
void Simulation::EnableNewtonianGravity(bool enable)
{
if (grav && !enable)
{
grav.reset();
gravOut = {}; // reset as per the invariant
}
if (!grav && enable)
{
grav = Gravity::Create();
DispatchNewtonianGravity();
}
}
constexpr size_t ce_log2(size_t n)

View File

@ -7,8 +7,8 @@
#include "BuiltinGOL.h"
#include "MenuSection.h"
#include "CoordStack.h"
#include "gravity/GravityPtr.h"
#include "common/tpt-rand.h"
#include "gravity/Gravity.h"
#include "Element.h"
#include "SimulationConfig.h"
#include <cstring>
@ -22,13 +22,12 @@ constexpr int CHANNELS = int(MAX_TEMP - 73) / 100 + 2;
class Snapshot;
class Brush;
class SimulationSample;
struct SimulationSample;
struct matrix2d;
struct vector2d;
class Simulation;
class Renderer;
class Gravity;
class Air;
class GameSave;
@ -36,6 +35,9 @@ class Simulation
{
public:
GravityPtr grav;
GravityInput gravIn;
GravityOutput gravOut; // invariant: when grav is empty, this is in its default-constructed state
std::unique_ptr<Air> air;
RNG rng;
@ -80,11 +82,6 @@ public:
float (*vy)[XCELLS];
float (*pv)[XCELLS];
float (*hv)[XCELLS];
//Gravity sim
float *gravx;//gravx[YCELLS * XCELLS];
float *gravy;//gravy[YCELLS * XCELLS];
float *gravp;//gravp[YCELLS * XCELLS];
float *gravmap;//gravmap[YCELLS * XCELLS];
//Walls
unsigned char bmap[YCELLS][XCELLS];
unsigned char emap[YCELLS][XCELLS];
@ -216,6 +213,11 @@ public:
bool useLuaCallbacks = false;
void EnableNewtonianGravity(bool enable);
void ResetNewtonianGravity(GravityInput newGravIn, GravityOutput newGravOut);
void DispatchNewtonianGravity();
void UpdateGravityMask();
private:
CoordStack& getCoordStackSingleton();
};

View File

@ -22,10 +22,9 @@ uint32_t Snapshot::Hash() const
takeVector(AirVelocityY);
takeVector(AmbientHeat);
takeVector(Particles);
takeVector(GravVelocityX);
takeVector(GravVelocityY);
takeVector(GravValue);
takeVector(GravMap);
takeVector(GravMass);
takeVector(GravForceX);
takeVector(GravForceY);
takeVector(BlockMap);
takeVector(ElecMap);
takeVector(BlockAir);

View File

@ -17,10 +17,9 @@ public:
std::vector<Particle> Particles;
std::vector<float> GravVelocityX;
std::vector<float> GravVelocityY;
std::vector<float> GravValue;
std::vector<float> GravMap;
std::vector<float> GravForceX;
std::vector<float> GravForceY;
std::vector<float> GravMass;
std::vector<unsigned char> BlockMap;
std::vector<unsigned char> ElecMap;

View File

@ -207,10 +207,9 @@ std::unique_ptr<SnapshotDelta> SnapshotDelta::FromSnapshots(const Snapshot &oldS
FillHunkVector(oldSnap.AirVelocityX , newSnap.AirVelocityX , delta.AirVelocityX );
FillHunkVector(oldSnap.AirVelocityY , newSnap.AirVelocityY , delta.AirVelocityY );
FillHunkVector(oldSnap.AmbientHeat , newSnap.AmbientHeat , delta.AmbientHeat );
FillHunkVector(oldSnap.GravVelocityX , newSnap.GravVelocityX , delta.GravVelocityX );
FillHunkVector(oldSnap.GravVelocityY , newSnap.GravVelocityY , delta.GravVelocityY );
FillHunkVector(oldSnap.GravValue , newSnap.GravValue , delta.GravValue );
FillHunkVector(oldSnap.GravMap , newSnap.GravMap , delta.GravMap );
FillHunkVector(oldSnap.GravMass , newSnap.GravMass , delta.GravMass );
FillHunkVector(oldSnap.GravForceX , newSnap.GravForceX , delta.GravForceX );
FillHunkVector(oldSnap.GravForceY , newSnap.GravForceY , delta.GravForceY );
FillHunkVector(oldSnap.BlockMap , newSnap.BlockMap , delta.BlockMap );
FillHunkVector(oldSnap.ElecMap , newSnap.ElecMap , delta.ElecMap );
FillHunkVector(oldSnap.BlockAir , newSnap.BlockAir , delta.BlockAir );
@ -244,10 +243,9 @@ std::unique_ptr<Snapshot> SnapshotDelta::Forward(const Snapshot &oldSnap)
ApplyHunkVector<false>(AirVelocityX , newSnap.AirVelocityX );
ApplyHunkVector<false>(AirVelocityY , newSnap.AirVelocityY );
ApplyHunkVector<false>(AmbientHeat , newSnap.AmbientHeat );
ApplyHunkVector<false>(GravVelocityX , newSnap.GravVelocityX );
ApplyHunkVector<false>(GravVelocityY , newSnap.GravVelocityY );
ApplyHunkVector<false>(GravValue , newSnap.GravValue );
ApplyHunkVector<false>(GravMap , newSnap.GravMap );
ApplyHunkVector<false>(GravMass , newSnap.GravMass );
ApplyHunkVector<false>(GravForceX , newSnap.GravForceX );
ApplyHunkVector<false>(GravForceY , newSnap.GravForceY );
ApplyHunkVector<false>(BlockMap , newSnap.BlockMap );
ApplyHunkVector<false>(ElecMap , newSnap.ElecMap );
ApplyHunkVector<false>(BlockAir , newSnap.BlockAir );
@ -279,10 +277,9 @@ std::unique_ptr<Snapshot> SnapshotDelta::Restore(const Snapshot &newSnap)
ApplyHunkVector<true>(AirVelocityX , oldSnap.AirVelocityX );
ApplyHunkVector<true>(AirVelocityY , oldSnap.AirVelocityY );
ApplyHunkVector<true>(AmbientHeat , oldSnap.AmbientHeat );
ApplyHunkVector<true>(GravVelocityX , oldSnap.GravVelocityX );
ApplyHunkVector<true>(GravVelocityY , oldSnap.GravVelocityY );
ApplyHunkVector<true>(GravValue , oldSnap.GravValue );
ApplyHunkVector<true>(GravMap , oldSnap.GravMap );
ApplyHunkVector<true>(GravMass , oldSnap.GravMass );
ApplyHunkVector<true>(GravForceX , oldSnap.GravForceX );
ApplyHunkVector<true>(GravForceY , oldSnap.GravForceY );
ApplyHunkVector<true>(BlockMap , oldSnap.BlockMap );
ApplyHunkVector<true>(ElecMap , oldSnap.ElecMap );
ApplyHunkVector<true>(BlockAir , oldSnap.BlockAir );

View File

@ -49,10 +49,9 @@ struct SnapshotDelta
HunkVector<uint32_t> commonParticles;
std::vector<Particle> extraPartsOld, extraPartsNew;
HunkVector<float> GravVelocityX;
HunkVector<float> GravVelocityY;
HunkVector<float> GravValue;
HunkVector<float> GravMap;
HunkVector<float> GravMass;
HunkVector<float> GravForceX;
HunkVector<float> GravForceY;
HunkVector<unsigned char> BlockMap;
HunkVector<unsigned char> ElecMap;

View File

@ -53,7 +53,8 @@ void Element::Element_DEUT()
static int update(UPDATE_FUNC_ARGS)
{
float gravtot = fabs(sim->gravy[(y/CELL)*XCELLS+(x/CELL)])+fabs(sim->gravx[(y/CELL)*XCELLS+(x/CELL)]);
auto gravtot = std::abs(sim->gravOut.forceX[Vec2{ x, y } / CELL]) +
std::abs(sim->gravOut.forceY[Vec2{ x, y } / CELL]);
// Prevent division by 0
float temp = std::max(1.0f, (parts[i].temp + 1));
auto maxlife = int(((10000/(temp + 1))-1));

View File

@ -69,10 +69,14 @@ static int update(UPDATE_FUNC_ARGS)
}
}
}
if (parts[i].life>20)
sim->gravmap[(y/CELL)*XCELLS+(x/CELL)] = 20;
else if (parts[i].life>=1)
sim->gravmap[(y/CELL)*XCELLS+(x/CELL)] = -80;
if (parts[i].life > 20)
{
sim->gravIn.mass[Vec2{ x, y } / CELL] = 20;
}
else if (parts[i].life >= 1)
{
sim->gravIn.mass[Vec2{ x, y } / CELL] = -80;
}
return 0;
}

View File

@ -63,7 +63,7 @@ static int update(UPDATE_FUNC_ARGS)
if (parts[i].temp<= -256.0f+273.15f)
parts[i].temp = -256.0f+273.15f;
sim->gravmap[(y/CELL)*XCELLS+(x/CELL)] = 0.2f*(parts[i].temp-273.15);
sim->gravIn.mass[Vec2{ x, y } / CELL] = 0.2f * (parts[i].temp - 273.15);
for (auto rx = -2; rx <= 2; rx++)
{
for (auto ry = -2; ry <= 2; ry++)

View File

@ -65,11 +65,11 @@ static int update(UPDATE_FUNC_ARGS)
//Randomly kill GRVT inside RSSS
if((utype == PT_RSSS) && sim->rng.chance(1, 5))
{
sim->kill_part(i);
return 1;
}
sim->gravmap[(y/CELL)*XCELLS+(x/CELL)] = 0.2f*parts[i].tmp;
sim->gravIn.mass[Vec2{ x, y } / CELL] = 0.2f * parts[i].tmp;
return 0;
}

View File

@ -48,8 +48,12 @@ void Element::Element_NBHL()
static int update(UPDATE_FUNC_ARGS)
{
if (parts[i].tmp)
sim->gravmap[(y/CELL)*XCELLS+(x/CELL)] += restrict_flt(0.001f*parts[i].tmp, 0.1f, 51.2f);
{
sim->gravIn.mass[Vec2{ x, y } / CELL] += restrict_flt(0.001f * parts[i].tmp, 0.1f, 51.2f);
}
else
sim->gravmap[(y/CELL)*XCELLS+(x/CELL)] += 0.1f;
{
sim->gravIn.mass[Vec2{ x, y } / CELL] += 0.1f;
}
return 0;
}

View File

@ -48,8 +48,12 @@ void Element::Element_NWHL()
static int update(UPDATE_FUNC_ARGS)
{
if (parts[i].tmp)
sim->gravmap[(y/CELL)*XCELLS+(x/CELL)] -= restrict_flt(0.001f*parts[i].tmp, 0.1f, 51.2f);
{
sim->gravIn.mass[Vec2{ x, y } / CELL] -= restrict_flt(0.001f * parts[i].tmp, 0.1f, 51.2f);
}
else
sim->gravmap[(y/CELL)*XCELLS+(x/CELL)] -= 0.1f;
{
sim->gravIn.mass[Vec2{ x, y } / CELL] -= 0.1f;
}
return 0;
}

View File

@ -81,9 +81,8 @@ static int update(UPDATE_FUNC_ARGS)
}
if (parts[i].temp > 9973.15 && sim->pv[y/CELL][x/CELL] > 250.0f)
{
int gravPos = ((y/CELL)*XCELLS)+(x/CELL);
float gravx = sim->gravx[gravPos];
float gravy = sim->gravy[gravPos];
auto gravx = sim->gravOut.forceX[Vec2{ x, y } / CELL];
auto gravy = sim->gravOut.forceY[Vec2{ x, y } / CELL];
if (gravx*gravx + gravy*gravy > 400)
{
if (sim->rng.chance(1, 5))

View File

@ -165,8 +165,8 @@ int Element_STKM_run_stickman(playerst *playerp, UPDATE_FUNC_ARGS)
break;
}
gvx += sim->gravx[((int)parts[i].y/CELL)*XCELLS+((int)parts[i].x/CELL)];
gvy += sim->gravy[((int)parts[i].y/CELL)*XCELLS+((int)parts[i].x/CELL)];
gvx += sim->gravOut.forceX[Vec2{ int(parts[i].x), int(parts[i].y) } / CELL];
gvy += sim->gravOut.forceY[Vec2{ int(parts[i].x), int(parts[i].y) } / CELL];
float mvx = gvx;
float mvy = gvy;

View File

@ -1,287 +0,0 @@
#include "Gravity.h"
#include "simulation/CoordStack.h"
#include "simulation/Simulation.h"
#include "simulation/SimulationData.h"
#include "Misc.h"
#include <cmath>
#include <iostream>
#include <sys/types.h>
Gravity::Gravity(CtorTag)
{
th_ogravmap.resize(NCELL);
th_gravmap.resize(NCELL);
th_gravy.resize(NCELL);
th_gravx.resize(NCELL);
th_gravp.resize(NCELL);
gravmap.resize(NCELL);
gravy.resize(NCELL);
gravx.resize(NCELL);
gravp.resize(NCELL);
gravmask.resize(NCELL);
}
Gravity::~Gravity()
{
stop_grav_async();
}
void Gravity::Clear()
{
std::fill(&gravy[0], &gravy[0] + NCELL, 0.0f);
std::fill(&gravx[0], &gravx[0] + NCELL, 0.0f);
std::fill(&gravp[0], &gravp[0] + NCELL, 0.0f);
std::fill(&gravmap[0], &gravmap[0] + NCELL, 0.0f);
std::fill(&gravmask[0], &gravmask[0] + NCELL, UINT32_C(0xFFFFFFFF));
ignoreNextResult = true;
}
void Gravity::gravity_update_async()
{
int result;
if (!enabled)
return;
bool signal_grav = false;
{
std::unique_lock<std::mutex> l(gravmutex, std::defer_lock);
if (l.try_lock())
{
result = grav_ready;
if (result) //Did the gravity thread finish?
{
if (th_gravchanged && !ignoreNextResult)
{
// Copy thread gravity maps into this one
get_result();
}
ignoreNextResult = false;
std::swap(gravmap, th_gravmap);
grav_ready = 0; //Tell the other thread that we're ready for it to continue
signal_grav = true;
}
}
}
if (signal_grav)
{
gravcv.notify_one();
}
unsigned int size = NCELL;
membwand(&gravy[0], &gravmask[0], size * sizeof(float), size * sizeof(uint32_t));
membwand(&gravx[0], &gravmask[0], size * sizeof(float), size * sizeof(uint32_t));
std::fill(&gravmap[0], &gravmap[0] + size, 0.0f);
}
void Gravity::update_grav_async()
{
int done = 0;
int thread_done = 0;
std::fill(&th_ogravmap[0], &th_ogravmap[0] + NCELL, 0.0f);
std::fill(&th_gravmap[0], &th_gravmap[0] + NCELL, 0.0f);
std::fill(&th_gravy[0], &th_gravy[0] + NCELL, 0.0f);
std::fill(&th_gravx[0], &th_gravx[0] + NCELL, 0.0f);
std::fill(&th_gravp[0], &th_gravp[0] + NCELL, 0.0f);
std::unique_lock<std::mutex> l(gravmutex);
while (!thread_done)
{
if (!done)
{
// run gravity update
update_grav();
done = 1;
grav_ready = 1;
thread_done = gravthread_done;
}
else
{
// wait for main thread
gravcv.wait(l);
done = grav_ready;
thread_done = gravthread_done;
}
}
}
void Gravity::start_grav_async()
{
if (enabled) //If it's already enabled, restart it
stop_grav_async();
gravthread_done = 0;
grav_ready = 0;
gravthread = std::thread([this]() { update_grav_async(); }); //Start asynchronous gravity simulation
enabled = true;
std::fill(&gravy[0], &gravy[0] + NCELL, 0.0f);
std::fill(&gravx[0], &gravx[0] + NCELL, 0.0f);
std::fill(&gravp[0], &gravp[0] + NCELL, 0.0f);
std::fill(&gravmap[0], &gravmap[0] + NCELL, 0.0f);
}
void Gravity::stop_grav_async()
{
if (enabled)
{
{
std::lock_guard<std::mutex> g(gravmutex);
gravthread_done = 1;
}
gravcv.notify_one();
gravthread.join();
enabled = false;
}
// Clear the grav velocities
std::fill(&gravy[0], &gravy[0] + NCELL, 0.0f);
std::fill(&gravx[0], &gravx[0] + NCELL, 0.0f);
std::fill(&gravp[0], &gravp[0] + NCELL, 0.0f);
std::fill(&gravmap[0], &gravmap[0] + NCELL, 0.0f);
}
bool Gravity::grav_mask_r(int x, int y, char checkmap[YCELLS][XCELLS], char shape[YCELLS][XCELLS])
{
int x1, x2;
bool ret = false;
try
{
CoordStack cs;
cs.push(x, y);
do
{
cs.pop(x, y);
x1 = x2 = x;
while (x1 >= 0)
{
if (x1 == 0)
{
ret = true;
break;
}
else if (checkmap[y][x1-1] || bmap[y][x1-1] == WL_GRAV)
break;
x1--;
}
while (x2 <= XCELLS-1)
{
if (x2 == XCELLS-1)
{
ret = true;
break;
}
else if (checkmap[y][x2+1] || bmap[y][x2+1] == WL_GRAV)
break;
x2++;
}
for (x = x1; x <= x2; x++)
{
shape[y][x] = 1;
checkmap[y][x] = 1;
}
if (y == 0)
{
for (x = x1; x <= x2; x++)
if (bmap[y][x] != WL_GRAV)
ret = true;
}
else if (y >= 1)
{
for (x = x1; x <= x2; x++)
if (!checkmap[y-1][x] && bmap[y-1][x] != WL_GRAV)
{
if (y-1 == 0)
ret = true;
cs.push(x, y-1);
}
}
if (y < YCELLS-1)
for (x=x1; x<=x2; x++)
if (!checkmap[y+1][x] && bmap[y+1][x] != WL_GRAV)
{
if (y+1 == YCELLS-1)
ret = true;
cs.push(x, y+1);
}
} while (cs.getSize()>0);
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
ret = false;
}
return ret;
}
void Gravity::mask_free(mask_el *c_mask_el)
{
if (c_mask_el == nullptr)
return;
delete[] c_mask_el->next;
delete[] c_mask_el->shape;
delete[] c_mask_el;
}
void Gravity::gravity_mask()
{
char checkmap[YCELLS][XCELLS];
unsigned maskvalue;
mask_el *t_mask_el = nullptr;
mask_el *c_mask_el = nullptr;
memset(checkmap, 0, sizeof(checkmap));
for (int x = 0; x < XCELLS; x++)
{
for(int y = 0; y < YCELLS; y++)
{
if (bmap[y][x] != WL_GRAV && checkmap[y][x] == 0)
{
// Create a new shape
if (t_mask_el == nullptr)
{
t_mask_el = new mask_el[sizeof(mask_el)];
t_mask_el->shape = new char[NCELL];
std::fill(&t_mask_el->shape[0], &t_mask_el->shape[0] + NCELL, 0);
t_mask_el->shapeout = 0;
t_mask_el->next = nullptr;
c_mask_el = t_mask_el;
}
else
{
c_mask_el->next = new mask_el[sizeof(mask_el)];
c_mask_el = c_mask_el->next;
c_mask_el->shape = new char[NCELL];
std::fill(&c_mask_el->shape[0], &c_mask_el->shape[0] + NCELL, 0);
c_mask_el->shapeout = 0;
c_mask_el->next = nullptr;
}
// Fill the shape
if (grav_mask_r(x, y, checkmap, reinterpret_cast<char(*)[XCELLS]>(c_mask_el->shape)))
c_mask_el->shapeout = 1;
}
}
}
c_mask_el = t_mask_el;
std::fill(&gravmask[0], &gravmask[0] + NCELL, 0);
while (c_mask_el != nullptr)
{
char *cshape = c_mask_el->shape;
for (int x = 0; x < XCELLS; x++)
{
for (int y = 0; y < YCELLS; y++)
{
if (cshape[y * XCELLS + x])
{
if (c_mask_el->shapeout)
maskvalue = 0xFFFFFFFF;
else
maskvalue = 0x00000000;
gravmask[y * XCELLS + x] = maskvalue;
}
}
}
c_mask_el = c_mask_el->next;
}
mask_free(t_mask_el);
}

View File

@ -1,14 +1,23 @@
#include "Gravity.h"
#include "Misc.h"
#include "Config.h"
#include "SimulationConfig.h"
#include <cstring>
#include <cmath>
#include <complex>
#include <memory>
#include <fftw3.h>
#include <thread>
#include <mutex>
#include <condition_variable>
constexpr auto xblock2 = XCELLS * 2;
constexpr auto yblock2 = YCELLS * 2;
constexpr auto fft_tsize = (xblock2 / 2 + 1) * yblock2;
//NCELL*4 is size of data array, scaling needed because FFTW calculates an unnormalized DFT
// DFT is cyclic in nature; gravity would wrap around sort of like in loop mode without the 2x here;
// in fact it still does, it's just not as visible. the arrays are 2x as big along all dimensions as normal cell maps
constexpr auto blocks = CELLS * 2;
// https://www.fftw.org/fftw3_doc/Multi_002dDimensional-DFTs-of-Real-Data.html#Multi_002dDimensional-DFTs-of-Real-Data
constexpr auto transSize = (blocks.X / 2 + 1) * blocks.Y;
// NCELL * 4 is size of data array, scaling needed because FFTW calculates an unnormalized DFT
constexpr auto scaleFactor = -float(M_GRAV) / (NCELL * 4);
static_assert(sizeof(std::complex<float>) == sizeof(fftwf_complex));
@ -29,149 +38,203 @@ FftwComplexArrayPtr FftwComplexArray(size_t size)
struct GravityImpl : public Gravity
{
bool grav_fft_status = false;
FftwArrayPtr th_gravmapbig , th_gravxbig , th_gravybig ;
FftwComplexArrayPtr th_ptgravxt, th_ptgravyt, th_gravmapbigt, th_gravxbigt, th_gravybigt;
FftwPlanPtr plan_gravmap, plan_gravx_inverse, plan_gravy_inverse;
FftwArrayPtr massBig , forceXBig , forceYBig ;
FftwComplexArrayPtr kernelXT, kernelYT, massBigT, forceXBigT, forceYBigT;
FftwPlanPtr massForward, forceXInverse, forceYInverse;
bool initDone = false;
void grav_fft_init();
void grav_fft_cleanup();
std::thread thr;
bool working = false;
bool shouldStop = false;
std::mutex stateMx;
std::condition_variable stateCv;
GravityImpl() : Gravity(CtorTag{})
{
}
GravityInput gravIn;
GravityOutput gravOut;
bool copyGravOut = false;
~GravityImpl();
void Init();
void Work();
void Wait();
void Stop();
void Dispatch();
};
GravityImpl::~GravityImpl()
{
stop_grav_async();
grav_fft_cleanup();
if (initDone)
{
Wait();
Stop();
}
}
void GravityImpl::grav_fft_init()
void GravityImpl::Dispatch()
{
if (grav_fft_status) return;
FftwPlanPtr plan_ptgravx, plan_ptgravy;
{
std::unique_lock lk(stateMx);
working = true;
}
stateCv.notify_one();
}
//use fftw malloc function to ensure arrays are aligned, to get better performance
FftwArrayPtr th_ptgravx = FftwArray(xblock2 * yblock2);
FftwArrayPtr th_ptgravy = FftwArray(xblock2 * yblock2);
th_ptgravxt = FftwComplexArray(fft_tsize);
th_ptgravyt = FftwComplexArray(fft_tsize);
th_gravmapbig = FftwArray(xblock2 * yblock2);
th_gravmapbigt = FftwComplexArray(fft_tsize);
th_gravxbig = FftwArray(xblock2 * yblock2);
th_gravybig = FftwArray(xblock2 * yblock2);
th_gravxbigt = FftwComplexArray(fft_tsize);
th_gravybigt = FftwComplexArray(fft_tsize);
void GravityImpl::Stop()
{
{
std::unique_lock lk(stateMx);
shouldStop = true;
}
stateCv.notify_one();
thr.join();
}
void GravityImpl::Wait()
{
std::unique_lock lk(stateMx);
stateCv.wait(lk, [this]() {
return !working;
});
}
void GravityImpl::Work()
{
{
PlaneAdapter<PlaneBase<float>, blocks.X, blocks.Y> massBigP(blocks, std::in_place, massBig.get());
for (auto p : CELLS.OriginRect())
{
// used to be a membwand but we'd need a new buffer for this,
// not worth it just to make this unalinged copy faster
massBigP[p + CELLS] = gravIn.mask[p] ? gravIn.mass[p] : 0.f;
}
}
fftwf_execute(massForward.get());
{
// https://en.wikipedia.org/wiki/Convolution_theorem
for (int i = 0; i < transSize; ++i)
{
forceXBigT[i] = massBigT[i] * kernelXT[i];
forceYBigT[i] = massBigT[i] * kernelYT[i];
}
}
fftwf_execute(forceXInverse.get());
fftwf_execute(forceYInverse.get());
{
PlaneAdapter<PlaneBase<float>, blocks.X, blocks.Y> forceXBigP(blocks, std::in_place, forceXBig.get());
PlaneAdapter<PlaneBase<float>, blocks.X, blocks.Y> forceYBigP(blocks, std::in_place, forceYBig.get());
for (auto p : CELLS.OriginRect())
{
// similarly
gravOut.forceX[p] = gravIn.mask[p] ? forceXBigP[p] : 0;
gravOut.forceY[p] = gravIn.mask[p] ? forceYBigP[p] : 0;
}
}
}
void GravityImpl::Init()
{
//select best algorithm, could use FFTW_PATIENT or FFTW_EXHAUSTIVE but that increases the time taken to plan, and I don't see much increase in execution speed
auto fftwPlanFlags = FFTW_PLAN_MEASURE ? FFTW_MEASURE : FFTW_ESTIMATE;
plan_ptgravx = FftwPlanPtr(fftwf_plan_dft_r2c_2d(yblock2, xblock2, th_ptgravx.get(), reinterpret_cast<fftwf_complex *>(th_ptgravxt.get()), fftwPlanFlags));
plan_ptgravy = FftwPlanPtr(fftwf_plan_dft_r2c_2d(yblock2, xblock2, th_ptgravy.get(), reinterpret_cast<fftwf_complex *>(th_ptgravyt.get()), fftwPlanFlags));
plan_gravmap = FftwPlanPtr(fftwf_plan_dft_r2c_2d(yblock2, xblock2, th_gravmapbig.get(), reinterpret_cast<fftwf_complex *>(th_gravmapbigt.get()), fftwPlanFlags));
plan_gravx_inverse = FftwPlanPtr(fftwf_plan_dft_c2r_2d(yblock2, xblock2, reinterpret_cast<fftwf_complex *>(th_gravxbigt.get()), th_gravxbig.get(), fftwPlanFlags));
plan_gravy_inverse = FftwPlanPtr(fftwf_plan_dft_c2r_2d(yblock2, xblock2, reinterpret_cast<fftwf_complex *>(th_gravybigt.get()), th_gravybig.get(), fftwPlanFlags));
//use fftw malloc function to ensure arrays are aligned, to get better performance
kernelXT = FftwComplexArray(transSize);
kernelYT = FftwComplexArray(transSize);
massBig = FftwArray(blocks.X * blocks.Y);
massBigT = FftwComplexArray(transSize);
forceXBig = FftwArray(blocks.X * blocks.Y);
forceYBig = FftwArray(blocks.X * blocks.Y);
forceXBigT = FftwComplexArray(transSize);
forceYBigT = FftwComplexArray(transSize);
massForward = FftwPlanPtr(fftwf_plan_dft_r2c_2d(blocks.Y, blocks.X, massBig.get(), reinterpret_cast<fftwf_complex *>(massBigT.get()), fftwPlanFlags));
forceXInverse = FftwPlanPtr(fftwf_plan_dft_c2r_2d(blocks.Y, blocks.X, reinterpret_cast<fftwf_complex *>(forceXBigT.get()), forceXBig.get(), fftwPlanFlags));
forceYInverse = FftwPlanPtr(fftwf_plan_dft_c2r_2d(blocks.Y, blocks.X, reinterpret_cast<fftwf_complex *>(forceYBigT.get()), forceYBig.get(), fftwPlanFlags));
auto kernelXRaw = FftwArray(blocks.X * blocks.Y);
auto kernelYRaw = FftwArray(blocks.X * blocks.Y);
auto kernelXForward = FftwPlanPtr(fftwf_plan_dft_r2c_2d(blocks.Y, blocks.X, kernelXRaw.get(), reinterpret_cast<fftwf_complex *>(kernelXT.get()), fftwPlanFlags));
auto kernelYForward = FftwPlanPtr(fftwf_plan_dft_r2c_2d(blocks.Y, blocks.X, kernelYRaw.get(), reinterpret_cast<fftwf_complex *>(kernelYT.get()), fftwPlanFlags));
PlaneAdapter<PlaneBase<float>, blocks.X, blocks.Y> kernelX(blocks, std::in_place, kernelXRaw.get());
PlaneAdapter<PlaneBase<float>, blocks.X, blocks.Y> kernelY(blocks, std::in_place, kernelYRaw.get());
//calculate velocity map caused by a point mass
for (int y = 0; y < yblock2; y++)
for (auto p : blocks.OriginRect())
{
for (int x = 0; x < xblock2; x++)
auto d = p - CELLS;
if (d == Vec2{ 0, 0 })
{
if (x == XCELLS && y == YCELLS)
continue;
auto distance = hypotf(float(x-XCELLS), float(y-YCELLS));
th_ptgravx[y * xblock2 + x] = scaleFactor * (x - XCELLS) / powf(distance, 3);
th_ptgravy[y * xblock2 + x] = scaleFactor * (y - YCELLS) / powf(distance, 3);
kernelX[p] = 0.f;
kernelY[p] = 0.f;
}
else
{
auto distance = std::hypot(float(d.X), float(d.Y));
kernelX[p] = scaleFactor * d.X / std::pow(distance, 3.f);
kernelY[p] = scaleFactor * d.Y / std::pow(distance, 3.f);
}
}
th_ptgravx[yblock2 * xblock2 / 2 + xblock2 / 2] = 0.0f;
th_ptgravy[yblock2 * xblock2 / 2 + xblock2 / 2] = 0.0f;
//transform point mass velocity maps
fftwf_execute(plan_ptgravx.get());
fftwf_execute(plan_ptgravy.get());
fftwf_execute(kernelXForward.get());
fftwf_execute(kernelYForward.get());
//clear padded gravmap
memset(th_gravmapbig.get(), 0, xblock2 * yblock2 * sizeof(float));
std::memset(massBig.get(), 0.f, blocks.X * blocks.Y * sizeof(float));
grav_fft_status = true;
thr = std::thread([this]() {
while (true)
{
{
std::unique_lock lk(stateMx);
stateCv.wait(lk, [this]() {
return working || shouldStop;
});
if (shouldStop)
{
break;
}
}
Work();
{
std::unique_lock lk(stateMx);
working = false;
}
stateCv.notify_one();
}
});
}
void GravityImpl::grav_fft_cleanup()
{
if (!grav_fft_status) return;
grav_fft_status = false;
}
void Gravity::get_result()
{
std::swap(gravy, th_gravy);
std::swap(gravx, th_gravx);
std::swap(gravp, th_gravp);
}
void Gravity::update_grav()
void Gravity::Exchange(GravityOutput &gravOut, const GravityInput &gravIn)
{
auto *fftGravity = static_cast<GravityImpl *>(this);
if (!fftGravity->grav_fft_status)
fftGravity->grav_fft_init();
auto *th_gravmapbig = fftGravity->th_gravmapbig.get();
auto *th_gravxbig = fftGravity->th_gravxbig.get();
auto *th_gravybig = fftGravity->th_gravybig.get();
auto *th_ptgravxt = fftGravity->th_ptgravxt.get();
auto *th_ptgravyt = fftGravity->th_ptgravyt.get();
auto *th_gravmapbigt = fftGravity->th_gravmapbigt.get();
auto *th_gravxbigt = fftGravity->th_gravxbigt.get();
auto *th_gravybigt = fftGravity->th_gravybigt.get();
auto &plan_gravmap = fftGravity->plan_gravmap;
auto &plan_gravx_inverse = fftGravity->plan_gravx_inverse;
auto &plan_gravy_inverse = fftGravity->plan_gravy_inverse;
if (memcmp(&th_ogravmap[0], &th_gravmap[0], sizeof(float) * NCELL) != 0)
// lazy init
if (!fftGravity->initDone)
{
th_gravchanged = 1;
membwand(&th_gravmap[0], &gravmask[0], NCELL * sizeof(float), NCELL * sizeof(uint32_t));
//copy gravmap into padded gravmap array
for (int y = 0; y < YCELLS; y++)
{
for (int x = 0; x < XCELLS; x++)
{
th_gravmapbig[(y+YCELLS)*xblock2+XCELLS+x] = th_gravmap[y*XCELLS+x];
}
}
//transform gravmap
fftwf_execute(plan_gravmap.get());
//do convolution (multiply the complex numbers)
for (int i = 0; i < fft_tsize; i++)
{
th_gravxbigt[i] = th_gravmapbigt[i] * th_ptgravxt[i];
th_gravybigt[i] = th_gravmapbigt[i] * th_ptgravyt[i];
}
//inverse transform, and copy from padded arrays into normal velocity maps
fftwf_execute(plan_gravx_inverse.get());
fftwf_execute(plan_gravy_inverse.get());
for (int y = 0; y < YCELLS; y++)
{
for (int x = 0; x < XCELLS; x++)
{
th_gravx[y*XCELLS+x] = th_gravxbig[y*xblock2+x];
th_gravy[y*XCELLS+x] = th_gravybig[y*xblock2+x];
th_gravp[y*XCELLS+x] = hypotf(th_gravxbig[y*xblock2+x], th_gravybig[y*xblock2+x]);
}
}
}
else
{
th_gravchanged = 0;
// this takes a noticeable amount of time
// TODO: hide the wait somehow
fftGravity->Init();
fftGravity->initDone = true;
}
// Copy th_ogravmap into th_gravmap (doesn't matter what th_ogravmap is afterwards)
std::swap(th_gravmap, th_ogravmap);
fftGravity->Wait();
// take output
if (fftGravity->copyGravOut)
{
fftGravity->copyGravOut = false;
std::swap(gravOut, fftGravity->gravOut);
}
// pass input (but same input => same output)
if (std::memcmp(&fftGravity->gravIn.mass[{ 0, 0 }], &gravIn.mass[{ 0, 0 }], NCELL * sizeof(float)) ||
std::memcmp(&fftGravity->gravIn.mask[{ 0, 0 }], &gravIn.mask[{ 0, 0 }], NCELL * sizeof(float)))
{
fftGravity->copyGravOut = true;
fftGravity->gravIn = gravIn;
}
fftGravity->Dispatch();
}
GravityPtr Gravity::Create()

View File

@ -1,78 +1,14 @@
#pragma once
#include "Config.h"
#include "GravityData.h"
#include "GravityPtr.h"
#include "SimulationConfig.h"
#include <thread>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <vector>
#include <cstdint>
class Simulation;
class Gravity
{
protected:
bool enabled = false;
// Maps to be processed by the gravity thread
std::vector<float> th_ogravmap;
std::vector<float> th_gravmap;
std::vector<float> th_gravx;
std::vector<float> th_gravy;
std::vector<float> th_gravp;
int th_gravchanged = 0;
std::thread gravthread;
std::mutex gravmutex;
std::condition_variable gravcv;
int grav_ready = 0;
int gravthread_done = 0;
bool ignoreNextResult = false;
struct mask_el {
char *shape;
char shapeout;
mask_el *next;
};
using mask_el = struct mask_el;
bool grav_mask_r(int x, int y, char checkmap[YCELLS][XCELLS], char shape[YCELLS][XCELLS]);
void mask_free(mask_el *c_mask_el);
void update_grav();
void get_result();
void update_grav_async();
struct CtorTag // Please use Gravity::Create().
{
};
Gravity() = default;
public:
Gravity(CtorTag);
~Gravity();
//Maps to be used by the main thread
std::vector<float> gravmap;
std::vector<float> gravp;
std::vector<float> gravy;
std::vector<float> gravx;
std::vector<uint32_t> gravmask;
static_assert(sizeof(float) == sizeof(uint32_t));
unsigned char (*bmap)[XCELLS];
bool IsEnabled() { return enabled; }
void Clear();
void gravity_update_async();
void start_grav_async();
void stop_grav_async();
void gravity_mask();
void Exchange(GravityOutput &gravOut, const GravityInput &gravIn);
static GravityPtr Create();
};

View File

@ -0,0 +1,29 @@
#pragma once
#include "common/Plane.h"
#include "SimulationConfig.h"
#include <cstdint>
#include <vector>
template<class Item>
using GravityPlane = PlaneAdapter<std::vector<Item>, CELLS.X, CELLS.Y>;
struct GravityInput
{
GravityPlane<float> mass;
GravityPlane<uint32_t> mask;
static_assert(sizeof(float) == sizeof(uint32_t));
GravityInput() : mass(CELLS, 0.f), mask(CELLS, UINT32_C(0xFFFFFFFF))
{
}
};
struct GravityOutput
{
GravityPlane<float> forceX;
GravityPlane<float> forceY;
GravityOutput() : forceX(CELLS, 0.f), forceY(CELLS, 0.f)
{
}
};

View File

@ -1,23 +1,12 @@
#include "Gravity.h"
#include "Misc.h"
#include <cstring>
// gravity without fast Fourier transforms
void Gravity::get_result()
{
memcpy(&gravy[0], &th_gravy[0], NCELL * sizeof(float));
memcpy(&gravx[0], &th_gravx[0], NCELL * sizeof(float));
memcpy(&gravp[0], &th_gravp[0], NCELL * sizeof(float));
}
void Gravity::update_grav(void)
void Gravity::Exchange(GravityOutput &gravOut, const GravityInput &gravIn)
{
}
GravityPtr Gravity::Create()
{
return GravityPtr(new Gravity(CtorTag{}));
return GravityPtr(new Gravity());
}
void GravityDeleter::operator ()(Gravity *ptr) const

View File

@ -1,7 +1,3 @@
simulation_files += files(
'Common.cpp',
)
if host_platform == 'emscripten'
# FFTW_MEASURE fails on emscripten for some reason, probably a timing issue
# FFTW_ESTIMATE is 20% slower

View File

@ -13,6 +13,6 @@ void SimTool::Tool_NGRV()
static int perform(Simulation * sim, Particle * cpart, int x, int y, int brushX, int brushYy, float strength)
{
sim->gravmap[((y/CELL)*XCELLS)+(x/CELL)] = strength*-5.0f;
sim->gravIn.mass[Vec2{ x, y } / CELL] = strength * -5.0f;
return 1;
}

View File

@ -13,6 +13,6 @@ void SimTool::Tool_PGRV()
static int perform(Simulation * sim, Particle * cpart, int x, int y, int brushX, int brushY, float strength)
{
sim->gravmap[((y/CELL)*XCELLS)+(x/CELL)] = strength*5.0f;
sim->gravIn.mass[Vec2{ x, y } / CELL] = strength * 5.0f;
return 1;
}