Refactor vector/matrix2d code

This commit is contained in:
mniip 2023-02-19 19:21:07 +01:00
parent 369dadf81e
commit b2328c41b5
15 changed files with 377 additions and 427 deletions

View File

@ -5,72 +5,6 @@
#include <sys/types.h>
#include <cmath>
matrix2d m2d_multiply_m2d(matrix2d m1, matrix2d m2)
{
matrix2d result = {
m1.a*m2.a+m1.b*m2.c, m1.a*m2.b+m1.b*m2.d,
m1.c*m2.a+m1.d*m2.c, m1.c*m2.b+m1.d*m2.d
};
return result;
}
vector2d m2d_multiply_v2d(matrix2d m, vector2d v)
{
vector2d result = {
m.a*v.x+m.b*v.y,
m.c*v.x+m.d*v.y
};
return result;
}
matrix2d m2d_multiply_float(matrix2d m, float s)
{
matrix2d result = {
m.a*s, m.b*s,
m.c*s, m.d*s,
};
return result;
}
vector2d v2d_multiply_float(vector2d v, float s)
{
vector2d result = {
v.x*s,
v.y*s
};
return result;
}
vector2d v2d_add(vector2d v1, vector2d v2)
{
vector2d result = {
v1.x+v2.x,
v1.y+v2.y
};
return result;
}
vector2d v2d_sub(vector2d v1, vector2d v2)
{
vector2d result = {
v1.x-v2.x,
v1.y-v2.y
};
return result;
}
matrix2d m2d_new(float me0, float me1, float me2, float me3)
{
matrix2d result = {me0,me1,me2,me3};
return result;
}
vector2d v2d_new(float x, float y)
{
vector2d result = {x, y};
return result;
}
void HSV_to_RGB(int h,int s,int v,int *r,int *g,int *b)//convert 0-255(0-360 for H) HSV values to 0-255 RGB
{
float hh, ss, vv, c, x;
@ -152,9 +86,6 @@ void membwand(void * destv, void * srcv, size_t destsize, size_t srcsize)
}
}
vector2d v2d_zero = {0,0};
matrix2d m2d_identity = {1,0,0,1};
bool byteStringEqualsString(const ByteString &str, const char *data, size_t size)
{
return str.size() == size && !memcmp(str.data(), data, size);

View File

@ -1,4 +1,5 @@
#pragma once
#include "common/Geometry.h"
#include <cmath>
#include <cstdio>
#include <cstdlib>
@ -57,34 +58,6 @@ 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);
// a b
// c d
struct matrix2d {
float a,b,c,d;
};
typedef struct matrix2d matrix2d;
// column vector
struct vector2d {
float x,y;
};
typedef struct vector2d vector2d;
matrix2d m2d_multiply_m2d(matrix2d m1, matrix2d m2);
vector2d m2d_multiply_v2d(matrix2d m, vector2d v);
matrix2d m2d_multiply_float(matrix2d m, float s);
vector2d v2d_multiply_float(vector2d v, float s);
vector2d v2d_add(vector2d v1, vector2d v2);
vector2d v2d_sub(vector2d v1, vector2d v2);
matrix2d m2d_new(float me0, float me1, float me2, float me3);
vector2d v2d_new(float x, float y);
extern vector2d v2d_zero;
extern matrix2d m2d_identity;
class ByteString;
bool byteStringEqualsString(const ByteString &str, const char *data, size_t size);

View File

@ -1,4 +1,5 @@
#pragma once
#include "common/Geometry.h"
#include <cstdint>
constexpr int MENUSIZE = 40;
@ -11,15 +12,18 @@ constexpr int CELL = 4;
constexpr int XCELLS = 153;
constexpr int YCELLS = 96;
constexpr int NCELL = XCELLS * YCELLS;
constexpr Rect<int> CELLS = Rect<int>(Vec2<int>(0, 0), Vec2<int>(XCELLS - 1, YCELLS - 1));
constexpr int XRES = XCELLS * CELL;
constexpr int YRES = YCELLS * CELL;
constexpr int NPART = XRES * YRES;
constexpr Rect<int> RES = Rect<int>(Vec2<int>(0, 0), Vec2<int>(XRES - 1, YRES - 1));
constexpr int XCNTR = XRES / 2;
constexpr int YCNTR = YRES / 2;
constexpr int WINDOWW = XRES + BARSIZE;
constexpr int WINDOWH = YRES + MENUSIZE;
constexpr Rect<int> WINDOW = Rect<int>(Vec2<int>(0, 0), Vec2<int>(WINDOWW - 1, WINDOWH - 1));
constexpr int MAXSIGNS = 16;

View File

@ -22,16 +22,14 @@ static void CheckBsonFieldBool(bson_iterator iter, const char *field, bool *flag
static void CheckBsonFieldInt(bson_iterator iter, const char *field, int *setting);
static void CheckBsonFieldFloat(bson_iterator iter, const char *field, float *setting);
GameSave::GameSave(int width, int height)
GameSave::GameSave(Vec2<int> blockDimen)
{
setSize(width, height);
setSize(blockDimen);
}
GameSave::GameSave(const std::vector<char> &data, bool newWantAuthors)
{
wantAuthors = newWantAuthors;
blockWidth = 0;
blockHeight = 0;
try
{
@ -77,21 +75,20 @@ void GameSave::Expand(const std::vector<char> &data)
}
}
void GameSave::setSize(int newWidth, int newHeight)
void GameSave::setSize(Vec2<int> newDimen)
{
blockWidth = newWidth;
blockHeight = newHeight;
blockDimen = newDimen;
particlesCount = 0;
particles = std::vector<Particle>(NPART);
blockMap = Plane<unsigned char>(blockWidth, blockHeight, 0);
fanVelX = Plane<float>(blockWidth, blockHeight, 0.0f);
fanVelY = Plane<float>(blockWidth, blockHeight, 0.0f);
pressure = Plane<float>(blockWidth, blockHeight, 0.0f);
velocityX = Plane<float>(blockWidth, blockHeight, 0.0f);
velocityY = Plane<float>(blockWidth, blockHeight, 0.0f);
ambientHeat = Plane<float>(blockWidth, blockHeight, 0.0f);
blockMap = Plane<unsigned char>(blockDimen.X, blockDimen.Y, 0);
fanVelX = Plane<float>(blockDimen.X, blockDimen.Y, 0.0f);
fanVelY = Plane<float>(blockDimen.X, blockDimen.Y, 0.0f);
pressure = Plane<float>(blockDimen.X, blockDimen.Y, 0.0f);
velocityX = Plane<float>(blockDimen.X, blockDimen.Y, 0.0f);
velocityY = Plane<float>(blockDimen.X, blockDimen.Y, 0.0f);
ambientHeat = Plane<float>(blockDimen.X, blockDimen.Y, 0.0f);
}
std::pair<bool, std::vector<char>> GameSave::Serialise() const
@ -111,160 +108,130 @@ std::pair<bool, std::vector<char>> GameSave::Serialise() const
return { false, {} };
}
vector2d GameSave::Translate(vector2d translate)
// round to nearest integer, half-points towards -inf
inline Vec2<float> roundNegInf(Vec2<float> v)
{
float nx, ny;
vector2d pos;
vector2d translateReal = translate;
float minx = 0, miny = 0, maxx = 0, maxy = 0;
return (v + Vec2<float>(0.5f, 0.5f)).Floor();
}
// ditto, but return an int
inline Vec2<int> lroundNegInf(Vec2<float> v)
{
return Vec2<int>(roundNegInf(v));
}
Vec2<float> GameSave::Translate(Vec2<float> translate)
{
Vec2<float> translateReal = translate;
auto bounds = Rect<float>(Vec2<float>::Zero);
// determine minimum and maximum position of all particles / signs
for (size_t i = 0; i < signs.size(); i++)
{
pos = v2d_new(float(signs[i].x), float(signs[i].y));
pos = v2d_add(pos,translate);
nx = floor(pos.x+0.5f);
ny = floor(pos.y+0.5f);
if (nx < minx)
minx = nx;
if (ny < miny)
miny = ny;
if (nx > maxx)
maxx = nx;
if (ny > maxy)
maxy = ny;
}
for (auto &sign : signs)
bounds |= Rect<float>(roundNegInf(Vec2<float>(sign.x, sign.y) + translate));
for (int i = 0; i < particlesCount; i++)
{
if (!particles[i].type) continue;
pos = v2d_new(particles[i].x, particles[i].y);
pos = v2d_add(pos,translate);
nx = floor(pos.x+0.5f);
ny = floor(pos.y+0.5f);
if (nx < minx)
minx = nx;
if (ny < miny)
miny = ny;
if (nx > maxx)
maxx = nx;
if (ny > maxy)
maxy = ny;
bounds |= Rect<float>(roundNegInf(Vec2<float>(particles[i].x, particles[i].y) + translate));
}
// determine whether corrections are needed. If moving in this direction would delete stuff, expand the save
vector2d backCorrection = v2d_new(
(minx < 0) ? (-floor(minx / CELL)) : 0,
(miny < 0) ? (-floor(miny / CELL)) : 0
auto backCorrection = Vec2<float>(
std::max(0.0f, -std::floor(bounds.TopLeft.X / CELL)),
std::max(0.0f, -std::floor(bounds.TopLeft.Y / CELL))
);
int blockBoundsX = int(maxx / CELL) + 1, blockBoundsY = int(maxy / CELL) + 1;
vector2d frontCorrection = v2d_new(
float((blockBoundsX > blockWidth) ? (blockBoundsX - blockWidth) : 0),
float((blockBoundsY > blockHeight) ? (blockBoundsY - blockHeight) : 0)
auto frontCorrection = Vec2<float>(
std::max(0, int(bounds.BottomRight.X / CELL) + 1 - blockDimen.X), // TODO: sus. std::floor?
std::max(0, int(bounds.BottomRight.Y / CELL) + 1 - blockDimen.Y)
);
// get new width based on corrections
auto newWidth = int((blockWidth + backCorrection.x + frontCorrection.x) * CELL);
auto newHeight = int((blockHeight + backCorrection.y + frontCorrection.y) * CELL);
if (newWidth > XRES)
frontCorrection.x = backCorrection.x = 0;
if (newHeight > YRES)
frontCorrection.y = backCorrection.y = 0;
auto newDimen = [=]() {
return Vec2<int>((Vec2<float>(blockDimen) + backCorrection + frontCorrection) * CELL);
};
if (newDimen().X > XRES)
frontCorrection.X = backCorrection.X = 0;
if (newDimen().Y > YRES)
frontCorrection.Y = backCorrection.Y = 0;
// call Transform to do the transformation we wanted when calling this function
translate = v2d_add(translate, v2d_multiply_float(backCorrection, CELL));
Transform(m2d_identity, translate, translateReal,
int((blockWidth + backCorrection.x + frontCorrection.x) * CELL),
int((blockHeight + backCorrection.y + frontCorrection.y) * CELL)
);
translate += backCorrection * CELL;
Transform(Mat2<float>::Identity, translate + backCorrection * CELL, translateReal, newDimen());
// return how much we corrected. This is used to offset the position of the current stamp
// otherwise it would attempt to recenter it with the current size
return v2d_add(v2d_multiply_float(backCorrection, -CELL), v2d_multiply_float(frontCorrection, CELL));
return backCorrection * (-CELL) + frontCorrection * CELL;
}
void GameSave::Transform(matrix2d transform, vector2d translate)
void GameSave::Transform(Mat2<float> transform, Vec2<float> translate)
{
int width = blockWidth*CELL, height = blockHeight*CELL, newWidth, newHeight;
vector2d tmp, ctl, cbr;
vector2d cornerso[4];
vector2d translateReal = translate;
Vec2<float> cornerso[4] = {
Vec2<float>(0, 0),
Vec2<float>(blockDimen.X * CELL - 1, 0),
Vec2<float>(0, blockDimen.Y * CELL - 1),
Vec2<float>(blockDimen.X * CELL - 1, blockDimen.Y * CELL - 1),
};
// undo any translation caused by rotation
cornerso[0] = v2d_new(0,0);
cornerso[1] = v2d_new(float(width-1),0);
cornerso[2] = v2d_new(0,float(height-1));
cornerso[3] = v2d_new(float(width-1),float(height-1));
for (int i = 0; i < 4; i++)
{
tmp = m2d_multiply_v2d(transform,cornerso[i]);
if (i==0) ctl = cbr = tmp; // top left, bottom right corner
if (tmp.x<ctl.x) ctl.x = tmp.x;
if (tmp.y<ctl.y) ctl.y = tmp.y;
if (tmp.x>cbr.x) cbr.x = tmp.x;
if (tmp.y>cbr.y) cbr.y = tmp.y;
}
auto bounds = Rect<float>(transform * cornerso[0]);
for (int i = 1; i < 4; i++)
bounds |= Rect<float>(transform * cornerso[i]);
// casting as int doesn't quite do what we want with negative numbers, so use floor()
tmp = v2d_new(floor(ctl.x+0.5f),floor(ctl.y+0.5f));
translate = v2d_sub(translate,tmp);
newWidth = int(floor(cbr.x+0.5f))-int(floor(ctl.x+0.5f))+1;
newHeight = int(floor(cbr.y+0.5f))-int(floor(ctl.y+0.5f))+1;
Transform(transform, translate, translateReal, newWidth, newHeight);
Vec2<float> translateReal = translate - roundNegInf(bounds.TopLeft);
auto newBounds = lroundNegInf(bounds.BottomRight) - lroundNegInf(bounds.TopLeft) + Vec2<int>(1, 1);
Transform(transform, translate, translateReal, newBounds);
}
// transform is a matrix describing how we want to rotate this save
// translate can vary depending on whether the save is bring rotated, or if a normal translate caused it to expand
// translateReal is the original amount we tried to translate, used to calculate wall shifting
void GameSave::Transform(matrix2d transform, vector2d translate, vector2d translateReal, int newWidth, int newHeight)
void GameSave::Transform(Mat2<float> transform, Vec2<float> translate, Vec2<float> translateReal, Vec2<int> newDimen)
{
if (newWidth>XRES) newWidth = XRES;
if (newHeight>YRES) newHeight = YRES;
newDimen.X = std::min(newDimen.X, XRES);
newDimen.Y = std::min(newDimen.Y, YRES);
int x, y, nx, ny, newBlockWidth = newWidth / CELL, newBlockHeight = newHeight / CELL;
vector2d pos, vel;
Vec2<int> newBlockDimen = newDimen / CELL;
Plane<unsigned char> blockMapNew(newBlockWidth, newBlockHeight, 0);
Plane<float> fanVelXNew(newBlockWidth, newBlockHeight, 0.0f);
Plane<float> fanVelYNew(newBlockWidth, newBlockHeight, 0.0f);
Plane<float> pressureNew(newBlockWidth, newBlockHeight, 0.0f);
Plane<float> velocityXNew(newBlockWidth, newBlockHeight, 0.0f);
Plane<float> velocityYNew(newBlockWidth, newBlockHeight, 0.0f);
Plane<float> ambientHeatNew(newBlockWidth, newBlockHeight, 0.0f);
Plane<unsigned char> blockMapNew(newBlockDimen.X, newBlockDimen.Y, 0);
Plane<float> fanVelXNew(newBlockDimen.X, newBlockDimen.Y, 0.0f);
Plane<float> fanVelYNew(newBlockDimen.X, newBlockDimen.Y, 0.0f);
Plane<float> pressureNew(newBlockDimen.X, newBlockDimen.Y, 0.0f);
Plane<float> velocityXNew(newBlockDimen.X, newBlockDimen.Y, 0.0f);
Plane<float> velocityYNew(newBlockDimen.X, newBlockDimen.Y, 0.0f);
Plane<float> ambientHeatNew(newBlockDimen.X, newBlockDimen.Y, 0.0f);
// Match these up with the matrices provided in GameView::OnKeyPress.
bool patchPipeR = transform.a == 0 && transform.b == 1 && transform.c == -1 && transform.d == 0;
bool patchPipeH = transform.a == -1 && transform.b == 0 && transform.c == 0 && transform.d == 1;
bool patchPipeV = transform.a == 1 && transform.b == 0 && transform.c == 0 && transform.d == -1;
bool patchPipeR = transform == Mat2<float>::CW;
bool patchPipeH = transform == Mat2<float>::MirrorX;
bool patchPipeV = transform == Mat2<float>::MirrorY;
// rotate and translate signs, parts, walls
for (size_t i = 0; i < signs.size(); i++)
auto bounds = Rect<int>(Vec2<int>::Zero, newDimen - Vec2<int>(1, 1));
for (auto &sign : signs)
{
pos = v2d_new(float(signs[i].x), float(signs[i].y));
pos = v2d_add(m2d_multiply_v2d(transform,pos),translate);
nx = int(floor(pos.x+0.5f));
ny = int(floor(pos.y+0.5f));
if (nx<0 || nx>=newWidth || ny<0 || ny>=newHeight)
auto pos = lroundNegInf(transform * Vec2<float>(sign.x, sign.y) + translate);
if (!bounds.Contains(pos))
{
signs[i].text[0] = 0;
sign.text[0] = 0;
continue;
}
signs[i].x = nx;
signs[i].y = ny;
sign.x = pos.X;
sign.y = pos.Y;
}
for (int i = 0; i < particlesCount; i++)
{
if (!particles[i].type) continue;
pos = v2d_new(particles[i].x, particles[i].y);
pos = v2d_add(m2d_multiply_v2d(transform,pos),translate);
nx = int(floor(pos.x+0.5f));
ny = int(floor(pos.y+0.5f));
if (nx<0 || nx>=newWidth || ny<0 || ny>=newHeight)
auto pos = lroundNegInf(transform * Vec2<float>(particles[i].x, particles[i].y) + translate);
if (!bounds.Contains(pos))
{
particles[i].type = PT_NONE;
continue;
}
particles[i].x = float(nx);
particles[i].y = float(ny);
vel = v2d_new(particles[i].vx, particles[i].vy);
vel = m2d_multiply_v2d(transform, vel);
particles[i].vx = vel.x;
particles[i].vy = vel.y;
particles[i].x = pos.X;
particles[i].y = pos.Y;
auto vel = transform * Vec2<float>(particles[i].vx, particles[i].vy);
particles[i].vx = vel.X;
particles[i].vy = vel.Y;
if (particles[i].type == PT_PIPE || particles[i].type == PT_PPIP)
{
if (patchPipeR)
@ -286,49 +253,49 @@ void GameSave::Transform(matrix2d transform, vector2d translate, vector2d transl
}
// translate walls and other grid items when the stamp is shifted more than 4 pixels in any direction
int translateX = 0, translateY = 0;
if (translateReal.x > 0 && ((int)translated.x%CELL == 3
|| (translated.x < 0 && (int)translated.x%CELL == 0)))
translateX = CELL;
else if (translateReal.x < 0 && ((int)translated.x%CELL == -3
|| (translated.x > 0 && (int)translated.x%CELL == 0)))
translateX = -CELL;
if (translateReal.y > 0 && ((int)translated.y%CELL == 3
|| (translated.y < 0 && (int)translated.y%CELL == 0)))
translateY = CELL;
else if (translateReal.y < 0 && ((int)translated.y%CELL == -3
|| (translated.y > 0 && (int)translated.y%CELL == 0)))
translateY = -CELL;
Vec2<int> blockTranslate = Vec2<int>::Zero;
if (translateReal.X > 0 && ((int)translated.X%CELL == 3
|| (translated.X < 0 && (int)translated.X%CELL == 0)))
blockTranslate.X = CELL;
else if (translateReal.X < 0 && ((int)translated.X%CELL == -3
|| (translated.X > 0 && (int)translated.X%CELL == 0)))
blockTranslate.X = -CELL;
if (translateReal.Y > 0 && ((int)translated.Y%CELL == 3
|| (translated.Y < 0 && (int)translated.Y%CELL == 0)))
blockTranslate.Y = CELL;
else if (translateReal.Y < 0 && ((int)translated.Y%CELL == -3
|| (translated.Y > 0 && (int)translated.Y%CELL == 0)))
blockTranslate.Y = -CELL;
for (y=0; y<blockHeight; y++)
for (x=0; x<blockWidth; x++)
auto blockBounds = Rect<int>(Vec2<int>::Zero, newBlockDimen - Vec2<int>(1, 1));
for (int y=0; y<blockDimen.Y; y++)
for (int x=0; x<blockDimen.X; x++)
{
pos = v2d_new(x*CELL+CELL*0.4f+translateX, y*CELL+CELL*0.4f+translateY);
pos = v2d_add(m2d_multiply_v2d(transform,pos),translate);
nx = int(pos.x/CELL);
ny = int(pos.y/CELL);
if (pos.x<0 || nx>=newBlockWidth || pos.y<0 || ny>=newBlockHeight)
auto pos = Vec2<float>(x * CELL + CELL * 0.4f, y * CELL + CELL * 0.4f) + blockTranslate;
pos = transform * pos + translate;
auto ipos = Vec2<int>(pos / CELL);
if (!blockBounds.Contains(ipos))
continue;
int nx = ipos.X, ny = ipos.Y;
if (blockMap[y][x])
{
blockMapNew[ny][nx] = blockMap[y][x];
if (blockMap[y][x]==WL_FAN)
{
vel = v2d_new(fanVelX[y][x], fanVelY[y][x]);
vel = m2d_multiply_v2d(transform, vel);
fanVelXNew[ny][nx] = vel.x;
fanVelYNew[ny][nx] = vel.y;
auto vel = transform * Vec2<float>(fanVelX[y][x], fanVelY[y][x]);
fanVelXNew[ny][nx] = vel.X;
fanVelYNew[ny][nx] = vel.Y;
}
}
pressureNew[ny][nx] = pressure[y][x];
velocityXNew[ny][nx] = velocityX[y][x];
velocityXNew[ny][nx] = velocityX[y][x]; // TODO: shouldn't this be transformed?
velocityYNew[ny][nx] = velocityY[y][x];
ambientHeatNew[ny][nx] = ambientHeat[y][x];
}
translated = v2d_add(m2d_multiply_v2d(transform, translated), translateReal);
translated = transform * translated + translateReal;
blockWidth = newBlockWidth;
blockHeight = newBlockHeight;
blockDimen = newBlockDimen;
blockMap = blockMapNew;
fanVelX = fanVelXNew;
@ -451,7 +418,7 @@ void GameSave::readOPS(const std::vector<char> &data)
if (blockX+blockW > XCELLS || blockY+blockH > YCELLS)
throw ParseException(ParseException::InvalidDimensions, "Save too large");
setSize(blockW, blockH);
setSize(Vec2<int>(blockW, blockH));
bsonDataLen = ((unsigned)inputData[8]);
bsonDataLen |= ((unsigned)inputData[9]) << 8;
@ -1322,7 +1289,7 @@ void GameSave::readPSv(const std::vector<char> &dataVec)
default: throw ParseException(ParseException::Corrupt, String::Build("Cannot decompress: status ", int(status)));
}
setSize(bw, bh);
setSize(Vec2<int>(bw, bh));
const auto *data = reinterpret_cast<unsigned char *>(&bsonData[0]);
dataLength = bsonData.size();
@ -1920,20 +1887,20 @@ std::pair<bool, std::vector<char>> GameSave::serialiseOPS() const
fullY = blockY*CELL;
//Original size + offset of original corner from snapped corner, rounded up by adding CELL-1
blockW = blockWidth;//(blockWidth-fullX+CELL-1)/CELL;
blockH = blockHeight;//(blockHeight-fullY+CELL-1)/CELL;
blockW = blockDimen.X;//(blockDimen.X-fullX+CELL-1)/CELL;
blockH = blockDimen.Y;//(blockDimen.Y-fullY+CELL-1)/CELL;
fullW = blockW*CELL;
fullH = blockH*CELL;
// Copy fan and wall data
std::vector<unsigned char> wallData(blockWidth*blockHeight);
std::vector<unsigned char> wallData(blockDimen.X*blockDimen.Y);
bool hasWallData = false;
std::vector<unsigned char> fanData(blockWidth*blockHeight*2);
std::vector<unsigned char> pressData(blockWidth*blockHeight*2);
std::vector<unsigned char> vxData(blockWidth*blockHeight*2);
std::vector<unsigned char> vyData(blockWidth*blockHeight*2);
std::vector<unsigned char> ambientData(blockWidth*blockHeight*2, 0);
unsigned int wallDataLen = blockWidth*blockHeight, fanDataLen = 0, pressDataLen = 0, vxDataLen = 0, vyDataLen = 0, ambientDataLen = 0;
std::vector<unsigned char> fanData(blockDimen.X*blockDimen.Y*2);
std::vector<unsigned char> pressData(blockDimen.X*blockDimen.Y*2);
std::vector<unsigned char> vxData(blockDimen.X*blockDimen.Y*2);
std::vector<unsigned char> vyData(blockDimen.X*blockDimen.Y*2);
std::vector<unsigned char> ambientData(blockDimen.X*blockDimen.Y*2, 0);
unsigned int wallDataLen = blockDimen.X*blockDimen.Y, fanDataLen = 0, pressDataLen = 0, vxDataLen = 0, vyDataLen = 0, ambientDataLen = 0;
for (x = blockX; x < blockX+blockW; x++)
{

View File

@ -2,7 +2,6 @@
#include "common/String.h"
#include "simulation/Sign.h"
#include "simulation/Particle.h"
#include "Misc.h"
#include "SimulationConfig.h"
#include <vector>
#include <json/json.h>
@ -78,14 +77,13 @@ struct Plane
class GameSave
{
// number of pixels translated. When translating CELL pixels, shift all CELL grids
vector2d translated = { 0, 0 };
Vec2<float> translated = Vec2<float>::Zero;
void readOPS(const std::vector<char> &data);
void readPSv(const std::vector<char> &data);
std::pair<bool, std::vector<char>> serialiseOPS() const;
public:
int blockWidth = 0;
int blockHeight = 0;
Vec2<int> blockDimen = Vec2<int>::Zero;
bool fromNewerVersion = false;
int majorVersion = 0;
int minorVersion = 0;
@ -130,14 +128,14 @@ public:
int pmapbits = 8; // default to 8 bits for older saves
GameSave(int width, int height);
GameSave(Vec2<int> blockDimen);
GameSave(const std::vector<char> &data, bool newWantAuthors = true);
void setSize(int width, int height);
void setSize(Vec2<int> dimen);
// return value is [ fakeFromNewerVersion, gameData ]
std::pair<bool, std::vector<char>> Serialise() const;
vector2d Translate(vector2d translate);
void Transform(matrix2d transform, vector2d translate);
void Transform(matrix2d transform, vector2d translate, vector2d translateReal, int newWidth, int newHeight);
Vec2<float> Translate(Vec2<float> translate);
void Transform(Mat2<float> transform, Vec2<float> translate);
void Transform(Mat2<float> transform, Vec2<float> translate, Vec2<float> translateReal, Vec2<int> newDimen);
void Expand(const std::vector<char> &data);

212
src/common/Geometry.h Normal file
View File

@ -0,0 +1,212 @@
#pragma once
#include <algorithm>
#include <cmath>
#include <utility>
#include <type_traits>
template<typename T, typename = std::enable_if<std::is_arithmetic_v<T>, void>>
struct Rect;
template<typename T, typename = std::enable_if<std::is_arithmetic_v<T>, void>>
struct Vec2
{
T X, Y;
constexpr Vec2(T x, T y):
X(x),
Y(y)
{
}
template<typename S, typename = std::enable_if<std::is_constructible_v<T, S>, void>>
constexpr explicit Vec2(Vec2<S> other):
X(other.X),
Y(other.Y)
{
}
inline bool operator==(Vec2<T> other) const
{
return X == other.X && Y == other.Y;
}
inline bool operator!=(Vec2<T> other) const
{
return X != other.X || Y != other.Y;
}
template<typename S>
constexpr inline Vec2<decltype(std::declval<T>() + std::declval<S>())> operator+(Vec2<S> other) const
{
return Vec2(X + other.X, Y + other.Y);
}
inline Vec2<T> operator-() const
{
return Vec2(-X, -Y);
}
template<typename S>
inline Vec2<decltype(std::declval<T>() - std::declval<S>())> operator-(Vec2<S> other) const
{
return Vec2(X - other.X, Y - other.Y);
}
template<typename S, typename = std::enable_if<std::is_arithmetic_v<S>, void>>
constexpr inline Vec2<decltype(std::declval<T>() * std::declval<S>())> operator*(S other) const
{
return Vec2(X * other, Y * other);
}
template<typename S, typename = std::enable_if<std::is_arithmetic_v<S>, void>>
inline Vec2<decltype(std::declval<T>() / std::declval<S>())> operator/(S other) const
{
return Vec2(X / other, Y / other);
}
template<typename S>
inline Vec2<T> &operator+=(Vec2<S> other)
{
return *this = *this + other;
}
template<typename S>
inline Vec2<T> &operator-=(Vec2<S> other)
{
return *this = *this - other;
}
template<typename S>
inline Vec2<T> &operator*=(Vec2<S> other)
{
return *this = *this * other;
}
template<typename S>
inline Vec2<T> &operator/=(Vec2<S> other)
{
return *this = *this / other;
}
inline Vec2<T> Floor() const
{
return Vec2<T>(std::floor(X), std::floor(Y));
}
// Return a rectangle starting at origin, whose dimensions match this vector
template<typename = std::enable_if<std::is_integral_v<T>, void>>
inline Rect<T> OriginRect() const
{
return RectSized(Vec2<T>::Zero, *this);
}
constexpr static Vec2<T> Zero = Vec2<T>(0, 0);
};
template<typename T, typename = std::enable_if<std::is_arithmetic_v<T>, void>>
struct Mat2
{
// ⎛A B⎞
// ⎝C D⎠, acting on column vectors
T A, B, C, D;
constexpr Mat2(T a, T b, T c, T d):
A(a),
B(b),
C(c),
D(d)
{
}
inline bool operator==(Mat2<T> other) const
{
return A == other.A && B == other.B && C == other.C && D == other.D;
}
inline bool operator!=(Mat2<T> other) const
{
return A != other.A || B != other.B || C != other.C || D != other.D;
}
template<typename S>
inline Vec2<decltype(std::declval<T>() * std::declval<S>())> operator*(Vec2<S> vec) const
{
return Vec2<decltype(std::declval<T>() * std::declval<S>())>(A * vec.X + B * vec.Y, C * vec.X + D * vec.Y);
}
constexpr static Mat2<T> Identity = Mat2(1, 0, 0, 1);
constexpr static Mat2<T> MirrorX = Mat2(-1, 0, 0, 1);
constexpr static Mat2<T> MirrorY = Mat2(1, 0, 0, -1);
constexpr static Mat2<T> CCW = Mat2(0, 1, -1, 0); // reminder: the Y axis points down
};
template<typename T, typename = std::enable_if<std::is_arithmetic_v<T>, void>>
static inline Rect<T> RectBetween(Vec2<T>, Vec2<T>);
template<typename T, typename>
struct Rect
{
// Inclusive
Vec2<T> TopLeft, BottomRight;
private:
constexpr Rect(Vec2<T> topLeft, Vec2<T> bottomRight):
TopLeft(topLeft),
BottomRight(bottomRight)
{
}
friend Rect<T> RectBetween<T>(Vec2<T>, Vec2<T>);
public:
inline Rect<T> operator|(Rect<T> other) const
{
return Rect<T>(
Vec2<T>(std::min(TopLeft.X, other.TopLeft.X), std::min(TopLeft.Y, other.TopLeft.Y)),
Vec2<T>(std::max(BottomRight.X, other.BottomRight.X), std::max(BottomRight.Y, other.BottomRight.Y))
);
}
inline Rect<T> &operator|=(Rect<T> other)
{
return *this = *this | other;
}
inline bool Contains(Vec2<T> point) const
{
return point.X >= TopLeft.X && point.X <= BottomRight.X && point.Y >= TopLeft.Y && point.Y <= BottomRight.Y;
}
inline Vec2<T> Clamp(Vec2<T> point) const
{
return Vec2<T>(std::clamp(point.X, TopLeft.X, BottomRight.X), std::clamp(point.Y, TopLeft.Y, BottomRight.Y));
}
inline Rect<T> Clamp(Rect<T> other) const
{
return Rect<T>(Clamp(other.TopLeft), Clamp(other.BottomRight));
}
template<typename = std::enable_if<std::is_integral_v<T>, void>>
inline Vec2<T> Size() const
{
return BottomRight - TopLeft + Vec2<T>(1, 1);
}
};
template<typename T, typename>
static inline Rect<T> RectBetween(Vec2<T> topLeft, Vec2<T> bottomRight)
{
return Rect<T>(topLeft, bottomRight);
}
template<typename T, typename = std::enable_if<std::is_arithmetic_v<T>, void>>
static inline Rect<T> RectAt(Vec2<T> pos)
{
return RectBetween<T>(pos, pos);
}
template<typename T, typename = std::enable_if<std::is_integral_v<T>, void>>
static inline Rect<T> RectSized(Vec2<T> topLeft, Vec2<T> dimen)
{
return RectBetween<T>(topLeft, topLeft + dimen - Vec2<T>(1, 1));
}

View File

@ -422,18 +422,17 @@ void GameController::LoadStamp(GameSave *stamp)
void GameController::TranslateSave(ui::Point point)
{
vector2d translate = v2d_new(float(point.X), float(point.Y));
vector2d translated = gameModel->GetPlaceSave()->Translate(translate);
auto translate = Vec2<float>(point);
auto translated = gameModel->GetPlaceSave()->Translate(translate);
ui::Point currentPlaceSaveOffset = gameView->GetPlaceSaveOffset();
// resets placeSaveOffset to 0, which is why we back it up first
gameModel->SetPlaceSave(gameModel->GetPlaceSave());
gameView->SetPlaceSaveOffset(ui::Point(int(translated.x), int(translated.y)) + currentPlaceSaveOffset);
gameView->SetPlaceSaveOffset(ui::Point(translated) + currentPlaceSaveOffset);
}
void GameController::TransformSave(matrix2d transform)
void GameController::TransformSave(Mat2<float> transform)
{
vector2d translate = v2d_zero;
gameModel->GetPlaceSave()->Transform(transform, translate);
gameModel->GetPlaceSave()->Transform(transform, Vec2<float>::Zero);
gameModel->SetPlaceSave(gameModel->GetPlaceSave());
}
@ -928,7 +927,7 @@ void GameController::SetToolStrength(float value)
void GameController::SetZoomPosition(ui::Point position)
{
ui::Point zoomPosition = position-(gameModel->GetZoomSize()/2);
ui::Point zoomPosition = position - ui::Point(1, 1) * gameModel->GetZoomSize() / 2;
if(zoomPosition.X < 0)
zoomPosition.X = 0;
if(zoomPosition.Y < 0)

View File

@ -1,10 +1,10 @@
#pragma once
#include "client/ClientListener.h"
#include "common/Geometry.h"
#include "gui/interface/Point.h"
#include "gui/interface/Colour.h"
#include "simulation/Sign.h"
#include "simulation/Particle.h"
#include "Misc.h"
#include <vector>
#include <utility>
#include <memory>
@ -153,7 +153,7 @@ public:
void HideConsole();
void FrameStep();
void TranslateSave(ui::Point point);
void TransformSave(matrix2d transform);
void TransformSave(Mat2<float> transform);
bool MouseInZoom(ui::Point position);
ui::Point PointTranslate(ui::Point point);
ui::Point PointTranslateNoClamp(ui::Point point);

View File

@ -1364,17 +1364,17 @@ void GameView::OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl,
if (ctrl && shift)
{
//Vertical flip
c->TransformSave(m2d_new(1,0,0,-1));
c->TransformSave(Mat2<float>::MirrorY);
}
else if (!ctrl && shift)
{
//Horizontal flip
c->TransformSave(m2d_new(-1,0,0,1));
c->TransformSave(Mat2<float>::MirrorX);
}
else
{
//Rotate 90deg
c->TransformSave(m2d_new(0,1,-1,0));
c->TransformSave(Mat2<float>::CW);
}
return;
}

View File

@ -113,7 +113,7 @@ void DirectionSelector::Draw(const ui::Point& screenPos)
{
Graphics * g = GetGraphics();
auto handleTrackRadius = radius + handleRadius;
ui::Point center = screenPos + handleTrackRadius;
ui::Point center = screenPos + ui::Point(1, 1) * handleTrackRadius;
g->fillcircle(center.X, center.Y, handleTrackRadius, handleTrackRadius, backgroundColor.Red, backgroundColor.Green, backgroundColor.Blue, backgroundColor.Alpha);
g->drawcircle(center.X, center.Y, handleTrackRadius, handleTrackRadius, borderColor.Red, borderColor.Green, borderColor.Blue, borderColor.Alpha);

View File

@ -1,142 +1,8 @@
#pragma once
#include "common/Geometry.h"
namespace ui
{
//Lightweight 2D Int32/Float32 Point struct for UI
struct Point
{
using POINT_T = int;
POINT_T X;
POINT_T Y;
Point(POINT_T x, POINT_T y)
: X(x)
, Y(y)
{
}
inline Point operator - () const
{
return Point(-X, -Y);
}
inline Point operator + (const Point& v) const
{
return Point(X + v.X, Y + v.Y);
}
inline Point operator + (const int v) const
{
return Point(X + v, Y + v);
}
inline Point operator - (const Point& v) const
{
return Point(X - v.X, Y - v.Y);
}
inline Point operator - (const int v) const
{
return Point(X - v, Y - v);
}
inline Point operator * (const Point& v) const
{
return Point(X * v.X, Y * v.Y);
}
inline Point operator * (int v) const
{
return Point(X * static_cast<POINT_T>(v), Y * static_cast<POINT_T>(v));
}
inline Point operator * (float v) const
{
return Point(X * static_cast<POINT_T>(v), Y * static_cast<POINT_T>(v));
}
inline Point operator / (const Point& v) const
{
return Point(X / v.X, Y / v.Y);
}
inline Point operator / (int v) const
{
return Point(X / static_cast<POINT_T>(v), Y / static_cast<POINT_T>(v));
}
inline Point operator / (float v) const
{
return Point(X / static_cast<POINT_T>(v), Y / static_cast<POINT_T>(v));
}
inline void operator += (const Point& v)
{
X += v.X;
Y += v.Y;
}
inline void operator -= (const Point& v)
{
X -= v.X;
Y -= v.Y;
}
inline void operator *= (const Point& v)
{
X *= v.X;
Y *= v.Y;
}
inline void operator *= (int v)
{
X *= static_cast<POINT_T>(v);
Y *= static_cast<POINT_T>(v);
}
inline void operator *= (float v)
{
X *= static_cast<POINT_T>(v);
Y *= static_cast<POINT_T>(v);
}
inline void operator /= (const Point& v)
{
X /= v.X;
Y /= v.Y;
}
inline void operator /= (int v)
{
X /= static_cast<POINT_T>(v);
Y /= static_cast<POINT_T>(v);
}
inline void operator /= (float v)
{
X /= static_cast<POINT_T>(v);
Y /= static_cast<POINT_T>(v);
}
inline bool operator == (const Point& v) const
{
return (X == v.X && Y == v.Y);
}
inline bool operator != (const Point& v) const
{
return (X != v.X || Y != v.Y);
}
inline Point operator = (const Point& v)
{
X = v.X;
Y = v.Y;
return Point(X, Y);
}
};
using Point = Vec2<int>;
}

View File

@ -1,3 +1,4 @@
#include "Misc.h"
#include "Simulation.h"
#include "Sample.h"
#include "SimTool.h"

View File

@ -35,8 +35,8 @@ VideoBuffer * SaveRenderer::Render(GameSave * save, bool decorations, bool fire,
int width, height;
VideoBuffer * tempThumb = NULL;
width = save->blockWidth;
height = save->blockHeight;
width = save->blockDimen.X;
height = save->blockDimen.Y;
g->Clear();
sim->clear_sim();

View File

@ -10,6 +10,7 @@
#include "common/tpt-rand.h"
#include "common/tpt-thread-local.h"
#include "gui/game/Brush.h"
#include "Misc.h"
#include <iostream>
#include <set>
@ -305,9 +306,9 @@ int Simulation::Load(const GameSave * originalSave, bool includePressure, int fu
signs.push_back(tempSign);
}
}
for(int saveBlockX = 0; saveBlockX < save->blockWidth; saveBlockX++)
for(int saveBlockX = 0; saveBlockX < save->blockDimen.X; saveBlockX++)
{
for(int saveBlockY = 0; saveBlockY < save->blockHeight; saveBlockY++)
for(int saveBlockY = 0; saveBlockY < save->blockDimen.Y; saveBlockY++)
{
if(save->blockMap[saveBlockY][saveBlockX])
{
@ -368,7 +369,7 @@ GameSave * Simulation::Save(bool includePressure, int fullX, int fullY, int full
blockW = blockX2-blockX;
blockH = blockY2-blockY;
GameSave * newSave = new GameSave(blockW, blockH);
GameSave * newSave = new GameSave(Vec2<int>(blockW, blockH));
auto &possiblyCarriesType = Particle::PossiblyCarriesType();
auto &properties = Particle::GetProperties();
@ -457,9 +458,9 @@ GameSave * Simulation::Save(bool includePressure, int fullX, int fullY, int full
}
}
for(int saveBlockX = 0; saveBlockX < newSave->blockWidth; saveBlockX++)
for(int saveBlockX = 0; saveBlockX < newSave->blockDimen.X; saveBlockX++)
{
for(int saveBlockY = 0; saveBlockY < newSave->blockHeight; saveBlockY++)
for(int saveBlockY = 0; saveBlockY < newSave->blockDimen.Y; saveBlockY++)
{
if(bmap[saveBlockY+blockY][saveBlockX+blockX])
{

View File

@ -23,8 +23,6 @@ class Snapshot;
class SimTool;
class Brush;
class SimulationSample;
struct matrix2d;
struct vector2d;
class Simulation;
class Renderer;