diff --git a/src/Misc.cpp b/src/Misc.cpp index a0d6ffe73..668f94bbc 100644 --- a/src/Misc.cpp +++ b/src/Misc.cpp @@ -5,72 +5,6 @@ #include #include -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); diff --git a/src/Misc.h b/src/Misc.h index 5d097b859..c7696c909 100644 --- a/src/Misc.h +++ b/src/Misc.h @@ -57,45 +57,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 { - [[deprecated("Use Mat2")]] - float a,b,c,d; -}; -[[deprecated("Use Mat2")]] -typedef struct matrix2d matrix2d; - -// column vector -struct vector2d { - [[deprecated("Use Vec2")]] - float x,y; -}; -[[deprecated("Use Vec2")]] -typedef struct vector2d vector2d; - -//matrix2d m2d_multiply_m2d(matrix2d m1, matrix2d m2); -[[deprecated("Use Mat2::operator*(Vec2)")]] -vector2d m2d_multiply_v2d(matrix2d m, vector2d v); -//matrix2d m2d_multiply_float(matrix2d m, float s); -[[deprecated("Use Vec2::operator*(float)")]] -vector2d v2d_multiply_float(vector2d v, float s); - -[[deprecated("Use Vec2::operator+")]] -vector2d v2d_add(vector2d v1, vector2d v2); -[[deprecated("Use Vec2::operator-")]] -vector2d v2d_sub(vector2d v1, vector2d v2); - -[[deprecated("Use Mat2")]] -matrix2d m2d_new(float me0, float me1, float me2, float me3); -[[deprecated("Use Vec2")]] -vector2d v2d_new(float x, float y); - -[[deprecated("Use Vec2::Zero")]] -extern vector2d v2d_zero; -[[deprecated("Use Mat2::Identity")]] -extern matrix2d m2d_identity; - class ByteString; bool byteStringEqualsString(const ByteString &str, const char *data, size_t size); diff --git a/src/client/GameSave.cpp b/src/client/GameSave.cpp index 92f635e3c..b1b4da836 100644 --- a/src/client/GameSave.cpp +++ b/src/client/GameSave.cpp @@ -111,232 +111,141 @@ std::pair> GameSave::Serialise() const return { false, {} }; } -vector2d GameSave::Translate(vector2d translate) +extern const std::array, 8> Element_PIPE_offsets; +void Element_PIPE_transformPatchOffsets(Particle &part, const std::array &offsetMap); + +void GameSave::Transform(Mat2 transform, Vec2 nudge) { - float nx, ny; - vector2d pos; - vector2d translateReal = translate; - float minx = 0, miny = 0, maxx = 0, maxy = 0; - // determine minimum and maximum position of all particles / signs - for (size_t i = 0; i < signs.size(); i++) + // undo translation by rotation + auto br = transform * (blockSize * CELL - Vec2{ 1, 1 }); + auto bbr = transform * (blockSize - Vec2{ 1, 1 }); + auto translate = Vec2{ std::max(0, -br.X), std::max(0, -br.Y) }; + auto btranslate = Vec2{ std::max(0, -bbr.X), std::max(0, -bbr.Y) }; + auto newBlockS = transform * blockSize; + newBlockS.X = std::abs(newBlockS.X); + newBlockS.Y = std::abs(newBlockS.Y); + translate += nudge; + + // Grow as needed. + assert((Vec2{ CELL, CELL }.OriginRect().Contains(nudge))); + if (nudge.X) newBlockS.X += 1; + if (nudge.Y) newBlockS.Y += 1; + + // TODO: allow transforms to yield bigger saves. For this we'd need SaveRenderer (the singleton, not Renderer) + // to fully render them (possible with stitching) and Simulation::Load to be able to take only the part that fits. + newBlockS = newBlockS.Clamp(RectBetween({ 0, 0 }, CELLS)); + auto newPartS = newBlockS * CELL; + + // Prepare to patch pipes. + std::array pipeOffsetMap; { - 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; + std::transform(Element_PIPE_offsets.begin(), Element_PIPE_offsets.end(), pipeOffsetMap.begin(), [transform](auto offset) { + auto it = std::find(Element_PIPE_offsets.begin(), Element_PIPE_offsets.end(), transform * offset); + assert(it != Element_PIPE_offsets.end()); + return int(it - Element_PIPE_offsets.begin()); + }); } - for (int i = 0; i < particlesCount; i++) + + // Translate signs. + for (auto i = 0U; i < signs.size(); 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; - } - // 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 - ); - int blockBoundsX = int(maxx / CELL) + 1, blockBoundsY = int(maxy / CELL) + 1; - vector2d frontCorrection = v2d_new( - float((blockBoundsX > blockSize.X) ? (blockBoundsX - blockSize.X) : 0), - float((blockBoundsY > blockSize.Y) ? (blockBoundsY - blockSize.Y) : 0) - ); - - // get new width based on corrections - auto newWidth = int((blockSize.X + backCorrection.x + frontCorrection.x) * CELL); - auto newHeight = int((blockSize.Y + backCorrection.y + frontCorrection.y) * CELL); - if (newWidth > XRES) - frontCorrection.x = backCorrection.x = 0; - if (newHeight > 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((blockSize.X + backCorrection.x + frontCorrection.x) * CELL), - int((blockSize.Y + backCorrection.y + frontCorrection.y) * CELL) - }); - - // 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)); -} - -void GameSave::Transform(matrix2d transform, vector2d translate) -{ - int width = blockSize.X*CELL, height = blockSize.Y*CELL; - vector2d tmp, ctl, cbr; - vector2d cornerso[4]; - vector2d translateReal = translate; - // 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.xcbr.x) cbr.x = tmp.x; - if (tmp.y>cbr.y) cbr.y = tmp.y; - } - // 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); - auto newWidth = int(floor(cbr.x+0.5f))-int(floor(ctl.x+0.5f))+1; - auto newHeight = int(floor(cbr.y+0.5f))-int(floor(ctl.y+0.5f))+1; - Transform(transform, translate, translateReal, { newWidth, newHeight }); -} - -// 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, Vec2 newPartSize) -{ - if (newPartSize.X>XRES) newPartSize.X = XRES; - if (newPartSize.Y>YRES) newPartSize.Y = YRES; - - auto newBlockSize = newPartSize / CELL; - - PlaneAdapter> blockMapNew(newBlockSize, 0); - PlaneAdapter> fanVelXNew(newBlockSize, 0.0f); - PlaneAdapter> fanVelYNew(newBlockSize, 0.0f); - PlaneAdapter> pressureNew(newBlockSize, 0.0f); - PlaneAdapter> velocityXNew(newBlockSize, 0.0f); - PlaneAdapter> velocityYNew(newBlockSize, 0.0f); - PlaneAdapter> ambientHeatNew(newBlockSize, 0.0f); - PlaneAdapter> blockAirNew(newBlockSize, 0); - PlaneAdapter> blockAirhNew(newBlockSize, 0); - - // 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; - - // rotate and translate signs, parts, walls - for (size_t i = 0; i < signs.size(); i++) - { - auto pos = v2d_new(float(signs[i].x), float(signs[i].y)); - pos = v2d_add(m2d_multiply_v2d(transform,pos),translate); - auto newPos = Vec2{ int(floor(pos.x+0.5f)), int(floor(pos.y+0.5f)) }; - if (!newPartSize.OriginRect().Contains(newPos)) + auto newPos = transform * Vec2{ signs[i].x, signs[i].y } + translate; + if (!newPartS.OriginRect().Contains(newPos)) { - signs[i].text[0] = 0; + signs[i].text.clear(); continue; } signs[i].x = newPos.X; signs[i].y = newPos.Y; } + + // Translate particles. for (int i = 0; i < particlesCount; i++) { - if (!particles[i].type) continue; - auto pos = v2d_new(particles[i].x, particles[i].y); - pos = v2d_add(m2d_multiply_v2d(transform,pos),translate); - auto newPos = Vec2{ int(floor(pos.x+0.5f)), int(floor(pos.y+0.5f)) }; - if (!newPartSize.OriginRect().Contains(newPos)) + if (!particles[i].type) + { + continue; + } + { + // * We want particles to retain their distance from the centre of the particle grid cell + // they are in, but more importantly we also don't want them to change grid cells, + // so we just get rid of the edge cases. + constexpr auto threshold = 0.001f; + auto boundaryDiffX = particles[i].x - (floor(particles[i].x) + 0.5f); + if (fabs(boundaryDiffX) < threshold) + { + particles[i].x += copysign(threshold, boundaryDiffX); + } + auto boundaryDiffY = particles[i].y - (floor(particles[i].y) + 0.5f); + if (fabs(boundaryDiffY) < threshold) + { + particles[i].y += copysign(threshold, boundaryDiffY); + } + } + if (particles[i].x - floor(particles[i].x) == 0.5f) particles[i].x += 0.001f; + if (particles[i].y - floor(particles[i].y) == 0.5f) particles[i].y += 0.001f; + auto newPos = transform * Vec2{ particles[i].x, particles[i].y } + translate; + if (!newPartS.OriginRect().Contains(Vec2{ int(floor(newPos.X + 0.5f)), int(floor(newPos.Y + 0.5f)) })) { particles[i].type = PT_NONE; continue; } - particles[i].x = float(newPos.X); - particles[i].y = float(newPos.Y); - auto 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 = newPos.X; + particles[i].y = newPos.Y; + auto newVel = transform * Vec2{ particles[i].vx, particles[i].vy }; + particles[i].vx = newVel.X; + particles[i].vy = newVel.Y; if (particles[i].type == PT_PIPE || particles[i].type == PT_PPIP) { - if (patchPipeR) - { - void Element_PIPE_patchR(Particle &part); - Element_PIPE_patchR(particles[i]); - } - if (patchPipeH) - { - void Element_PIPE_patchH(Particle &part); - Element_PIPE_patchH(particles[i]); - } - if (patchPipeV) - { - void Element_PIPE_patchV(Particle &part); - Element_PIPE_patchV(particles[i]); - } + Element_PIPE_transformPatchOffsets(particles[i], pipeOffsetMap); } } - // 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; - + // Translate blocky stuff. + PlaneAdapter> newBlockMap(newBlockS, 0); + PlaneAdapter> newFanVelX(newBlockS, 0.0f); + PlaneAdapter> newFanVelY(newBlockS, 0.0f); + PlaneAdapter> newPressure(newBlockS, 0.0f); + PlaneAdapter> newVelocityX(newBlockS, 0.0f); + PlaneAdapter> newVelocityY(newBlockS, 0.0f); + PlaneAdapter> newAmbientHeat(newBlockS, 0.0f); + PlaneAdapter> newBlockAir(newBlockS, 0); + PlaneAdapter> newBlockAirh(newBlockS, 0); for (auto bpos : blockSize.OriginRect()) { - auto pos = v2d_new(bpos.X*CELL+CELL*0.4f+translateX, bpos.Y*CELL+CELL*0.4f+translateY); - pos = v2d_add(m2d_multiply_v2d(transform,pos),translate); - auto newBpos = Vec2{ int(pos.x/CELL), int(pos.y/CELL) }; - if (!newBlockSize.OriginRect().Contains(newBpos)) + auto newBpos = transform * bpos + btranslate; + if (!newBlockS.OriginRect().Contains(newBpos)) + { continue; + } if (blockMap[bpos]) { - blockMapNew[newBpos] = blockMap[bpos]; - if (blockMap[bpos]==WL_FAN) + newBlockMap[newBpos] = blockMap[bpos]; + if (blockMap[bpos] == WL_FAN) { - auto vel = v2d_new(fanVelX[bpos], fanVelY[bpos]); - vel = m2d_multiply_v2d(transform, vel); - fanVelXNew[newBpos] = vel.x; - fanVelYNew[newBpos] = vel.y; + auto newVel = transform * Vec2{ fanVelX[bpos], fanVelY[bpos] }; + newFanVelX[newBpos] = newVel.X; + newFanVelY[newBpos] = newVel.Y; } } - pressureNew[newBpos] = pressure[bpos]; - velocityXNew[newBpos] = velocityX[bpos]; - velocityYNew[newBpos] = velocityY[bpos]; - ambientHeatNew[newBpos] = ambientHeat[bpos]; - blockAirNew[newBpos] = blockAir[bpos]; - blockAirhNew[newBpos] = blockAirh[bpos]; + newPressure[newBpos] = pressure[bpos]; + newVelocityX[newBpos] = velocityX[bpos]; + newVelocityY[newBpos] = velocityY[bpos]; + newAmbientHeat[newBpos] = ambientHeat[bpos]; + newBlockAir[newBpos] = blockAir[bpos]; + newBlockAirh[newBpos] = blockAirh[bpos]; } - translated = v2d_add(m2d_multiply_v2d(transform, translated), translateReal); + blockMap = std::move(newBlockMap); + fanVelX = std::move(newFanVelX); + fanVelY = std::move(newFanVelY); + pressure = std::move(newPressure); + velocityX = std::move(newVelocityX); + velocityY = std::move(newVelocityY); + ambientHeat = std::move(newAmbientHeat); + blockAir = std::move(newBlockAir); + blockAirh = std::move(newBlockAirh); - blockSize = newBlockSize; - - blockMap = blockMapNew; - fanVelX = fanVelXNew; - fanVelY = fanVelYNew; - pressure = pressureNew; - velocityX = velocityXNew; - velocityY = velocityYNew; - ambientHeat = ambientHeatNew; - blockAir = blockAirNew; - blockAirh = blockAirhNew; + blockSize = newBlockS; } static void CheckBsonFieldUser(bson_iterator iter, const char *field, unsigned char **data, unsigned int *fieldLen) diff --git a/src/client/GameSave.h b/src/client/GameSave.h index cbe472081..2490372f0 100644 --- a/src/client/GameSave.h +++ b/src/client/GameSave.h @@ -57,7 +57,6 @@ public: class GameSave { // number of pixels translated. When translating CELL pixels, shift all CELL grids - vector2d translated = { 0, 0 }; void readOPS(const std::vector &data); void readPSv(const std::vector &data); std::pair> serialiseOPS() const; @@ -120,9 +119,7 @@ public: void setSize(Vec2 newBlockSize); // return value is [ fakeFromNewerVersion, gameData ] std::pair> Serialise() const; - vector2d Translate(vector2d translate); - void Transform(matrix2d transform, vector2d translate); - void Transform(matrix2d transform, vector2d translate, vector2d translateReal, Vec2 newPartSize); + void Transform(Mat2 transform, Vec2 nudge); void Expand(const std::vector &data); diff --git a/src/common/Vec2.h b/src/common/Vec2.h index d438d11fa..7bbf896d1 100644 --- a/src/common/Vec2.h +++ b/src/common/Vec2.h @@ -156,6 +156,15 @@ struct Mat2 return Vec2() * std::declval())>(A * vec.X + B * vec.Y, C * vec.X + D * vec.Y); } + template + constexpr Mat2() * std::declval())> operator*(Mat2 mat) const + { + return Mat2() * std::declval())>( + A * mat.A + B * mat.C, A * mat.B + B * mat.D, + C * mat.A + D * mat.C, C * mat.B + D * mat.D + ); + } + static Mat2 const Identity, MirrorX, MirrorY, CCW; }; diff --git a/src/gui/game/GameController.cpp b/src/gui/game/GameController.cpp index 189d80860..bdc1028c9 100644 --- a/src/gui/game/GameController.cpp +++ b/src/gui/game/GameController.cpp @@ -238,7 +238,7 @@ std::pair GameController::GetSignSplit(int signID) void GameController::PlaceSave(ui::Point position) { - auto *placeSave = gameModel->GetPlaceSave(); + auto *placeSave = gameModel->GetTransformedPlaceSave(); if (placeSave) { HistorySnapshot(); @@ -248,6 +248,7 @@ void GameController::PlaceSave(ui::Point position) Client::Ref().MergeStampAuthorInfo(placeSave->authors); } } + gameModel->SetPlaceSave(nullptr); } void GameController::Install() @@ -420,23 +421,9 @@ void GameController::LoadStamp(std::unique_ptr stamp) gameModel->SetPlaceSave(std::move(stamp)); } -void GameController::TranslateSave(ui::Point point) +void GameController::TransformPlaceSave(Mat2 transform, Vec2 nudge) { - vector2d translate = v2d_new(float(point.X), float(point.Y)); - auto save = gameModel->TakePlaceSave(); - vector2d translated = save->Translate(translate); - ui::Point currentPlaceSaveOffset = gameView->GetPlaceSaveOffset(); - // resets placeSaveOffset to 0, which is why we back it up first - gameModel->SetPlaceSave(std::move(save)); - gameView->SetPlaceSaveOffset(ui::Point(int(translated.x), int(translated.y)) + currentPlaceSaveOffset); -} - -void GameController::TransformSave(matrix2d transform) -{ - vector2d translate = v2d_zero; - auto save = gameModel->TakePlaceSave(); - save->Transform(transform, translate); - gameModel->SetPlaceSave(std::move(save)); + gameModel->TransformPlaceSave(transform, nudge); } void GameController::ToolClick(int toolSelection, ui::Point point) diff --git a/src/gui/game/GameController.h b/src/gui/game/GameController.h index 04753eaeb..447cf85b7 100644 --- a/src/gui/game/GameController.h +++ b/src/gui/game/GameController.h @@ -152,8 +152,7 @@ public: void ShowConsole(); void HideConsole(); void FrameStep(); - void TranslateSave(ui::Point point); - void TransformSave(matrix2d transform); + void TransformPlaceSave(Mat2 transform, Vec2 nudge); bool MouseInZoom(ui::Point position); ui::Point PointTranslate(ui::Point point); ui::Point PointTranslateNoClamp(ui::Point point); diff --git a/src/gui/game/GameModel.cpp b/src/gui/game/GameModel.cpp index facc4151b..7ce13c4f5 100644 --- a/src/gui/game/GameModel.cpp +++ b/src/gui/game/GameModel.cpp @@ -1336,10 +1336,21 @@ void GameModel::ClearSimulation() void GameModel::SetPlaceSave(std::unique_ptr save) { + transformedPlaceSave.reset(); placeSave = std::move(save); notifyPlaceSaveChanged(); } +void GameModel::TransformPlaceSave(Mat2 transform, Vec2 nudge) +{ + if (placeSave) + { + transformedPlaceSave = std::make_unique(*placeSave); + transformedPlaceSave->Transform(transform, nudge); + } + notifyTransformedPlaceSaveChanged(); +} + void GameModel::SetClipboard(std::unique_ptr save) { clipboard = std::move(save); @@ -1350,15 +1361,9 @@ const GameSave *GameModel::GetClipboard() const return clipboard.get(); } -const GameSave *GameModel::GetPlaceSave() const +const GameSave *GameModel::GetTransformedPlaceSave() const { - return placeSave.get(); -} - -std::unique_ptr GameModel::TakePlaceSave() -{ - // we don't notify listeners because we'll get a new save soon anyway - return std::move(placeSave); + return transformedPlaceSave.get(); } void GameModel::Log(String message, bool printToFile) @@ -1559,6 +1564,14 @@ void GameModel::notifyPlaceSaveChanged() } } +void GameModel::notifyTransformedPlaceSaveChanged() +{ + for (size_t i = 0; i < observers.size(); i++) + { + observers[i]->NotifyTransformedPlaceSaveChanged(this); + } +} + void GameModel::notifyLogChanged(String entry) { for (size_t i = 0; i < observers.size(); i++) diff --git a/src/gui/game/GameModel.h b/src/gui/game/GameModel.h index d5b49598b..0a02df0ad 100644 --- a/src/gui/game/GameModel.h +++ b/src/gui/game/GameModel.h @@ -46,6 +46,7 @@ private: //unsigned char * clipboardData; std::unique_ptr clipboard; std::unique_ptr placeSave; + std::unique_ptr transformedPlaceSave; std::deque consoleLog; std::vector observers; std::vector toolList; @@ -104,6 +105,7 @@ private: void notifyZoomChanged(); void notifyClipboardChanged(); void notifyPlaceSaveChanged(); + void notifyTransformedPlaceSaveChanged(); void notifyColourSelectorColourChanged(); void notifyColourSelectorVisibilityChanged(); void notifyColourPresetsChanged(); @@ -227,11 +229,12 @@ public: ui::Point GetZoomWindowPosition(); void SetClipboard(std::unique_ptr save); void SetPlaceSave(std::unique_ptr save); + void TransformPlaceSave(Mat2 transform, Vec2 nudge); void Log(String message, bool printToFile); std::deque GetLog(); const GameSave *GetClipboard() const; const GameSave *GetPlaceSave() const; - std::unique_ptr TakePlaceSave(); + const GameSave *GetTransformedPlaceSave() const; bool GetMouseClickRequired(); void SetMouseClickRequired(bool mouseClickRequired); bool GetIncludePressure(); diff --git a/src/gui/game/GameView.cpp b/src/gui/game/GameView.cpp index da9ca1caa..d3f324c77 100644 --- a/src/gui/game/GameView.cpp +++ b/src/gui/game/GameView.cpp @@ -36,6 +36,7 @@ #include "Config.h" #include +#include #include #include @@ -1198,7 +1199,7 @@ void GameView::OnMouseUp(int x, int y, unsigned button) { if (placeSaveThumb && y <= WINDOWH-BARSIZE) { - auto thumb = selectPoint2 - (placeSaveThumb->Size() - placeSaveOffset) / 2; + auto thumb = selectPoint2 + placeSaveOffset; thumb = thumb.Clamp(RectBetween(Vec2::Zero, RES - placeSaveThumb->Size())); c->PlaceSave(thumb); } @@ -1334,16 +1335,16 @@ void GameView::OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, switch (key) { case SDLK_RIGHT: - c->TranslateSave(ui::Point(1, 0)); + TranslateSave({ 1, 0 }); return; case SDLK_LEFT: - c->TranslateSave(ui::Point(-1, 0)); + TranslateSave({ -1, 0 }); return; case SDLK_UP: - c->TranslateSave(ui::Point(0, -1)); + TranslateSave({ 0, -1 }); return; case SDLK_DOWN: - c->TranslateSave(ui::Point(0, 1)); + TranslateSave({ 0, 1 }); return; } if (scan == SDL_SCANCODE_R && !repeat) @@ -1351,17 +1352,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)); + TransformSave(Mat2::MirrorY); } else if (!ctrl && shift) { //Horizontal flip - c->TransformSave(m2d_new(-1,0,0,1)); + TransformSave(Mat2::MirrorX); } else { //Rotate 90deg - c->TransformSave(m2d_new(0,1,-1,0)); + TransformSave(Mat2::CCW); } return; } @@ -1908,16 +1909,62 @@ void GameView::NotifyLogChanged(GameModel * sender, String entry) void GameView::NotifyPlaceSaveChanged(GameModel * sender) { - placeSaveOffset = ui::Point(0, 0); - if(sender->GetPlaceSave()) + placeSaveTransform = Mat2::Identity; + placeSaveTranslate = Vec2::Zero; + ApplyTransformPlaceSave(); +} + +void GameView::TranslateSave(Vec2 addToTranslate) +{ + placeSaveTranslate += addToTranslate; + ApplyTransformPlaceSave(); +} + +void GameView::TransformSave(Mat2 mulToTransform) +{ + placeSaveTranslate = Vec2::Zero; // reset offset + placeSaveTransform = mulToTransform * placeSaveTransform; + ApplyTransformPlaceSave(); +} + +template +static std::pair floorDiv(Signed a, Signed b) +{ + auto quo = a / b; + auto rem = a % b; + if (a < Signed(0) && rem) { - placeSaveThumb = SaveRenderer::Ref().Render(sender->GetPlaceSave(), true, true, sender->GetRenderer()); + quo -= Signed(1); + rem += b; + } + return { quo, rem }; +} + +void GameView::ApplyTransformPlaceSave() +{ + auto remX = floorDiv(placeSaveTranslate.X, CELL).second; + auto remY = floorDiv(placeSaveTranslate.Y, CELL).second; + c->TransformPlaceSave(placeSaveTransform, { remX, remY }); +} + +void GameView::NotifyTransformedPlaceSaveChanged(GameModel *sender) +{ + if (sender->GetTransformedPlaceSave()) + { + placeSaveThumb = SaveRenderer::Ref().Render(sender->GetTransformedPlaceSave(), true, true, sender->GetRenderer()); + auto [ quoX, remX ] = floorDiv(placeSaveTranslate.X, CELL); + auto [ quoY, remY ] = floorDiv(placeSaveTranslate.Y, CELL); + placeSaveOffset = Vec2{ quoX, quoY } * CELL; + auto usefulSize = placeSaveThumb->Size(); + if (remX) usefulSize.X -= CELL; + if (remY) usefulSize.Y -= CELL; + placeSaveOffset -= usefulSize / 2; selectMode = PlaceSave; selectPoint2 = mousePosition; } else { - placeSaveThumb = nullptr; + placeSaveThumb.reset(); selectMode = SelectNone; } } @@ -2127,7 +2174,7 @@ void GameView::OnDraw() { if(placeSaveThumb && selectPoint2.X!=-1) { - auto thumb = selectPoint2 - (placeSaveThumb->Size() - placeSaveOffset) / 2 + Vec2(1, 1) * CELL / 2; + auto thumb = selectPoint2 + placeSaveOffset + Vec2(1, 1) * CELL / 2; thumb = c->NormaliseBlockCoord(thumb).Clamp(RectBetween(Vec2::Zero, RES - placeSaveThumb->Size())); auto rect = RectSized(thumb, placeSaveThumb->Size()); ren->BlendImage(placeSaveThumb->Data(), 0x80, rect); diff --git a/src/gui/game/GameView.h b/src/gui/game/GameView.h index b94bbafc6..311a2cff1 100644 --- a/src/gui/game/GameView.h +++ b/src/gui/game/GameView.h @@ -119,6 +119,11 @@ private: std::unique_ptr placeSaveThumb; ui::Point placeSaveOffset; + Mat2 placeSaveTransform = Mat2::Identity; + Vec2 placeSaveTranslate = Vec2::Zero; + void TranslateSave(Vec2 addToTranslate); + void TransformSave(Mat2 mulToTransform); + void ApplyTransformPlaceSave(); SimulationSample sample; @@ -185,6 +190,7 @@ public: void NotifyColourPresetsChanged(GameModel * sender); void NotifyColourActivePresetChanged(GameModel * sender); void NotifyPlaceSaveChanged(GameModel * sender); + void NotifyTransformedPlaceSaveChanged(GameModel *sender); void NotifyNotificationsChanged(GameModel * sender); void NotifyLogChanged(GameModel * sender, String entry); void NotifyToolTipChanged(GameModel * sender); diff --git a/src/simulation/elements/PIPE.cpp b/src/simulation/elements/PIPE.cpp index aa9b6e113..4e6446814 100644 --- a/src/simulation/elements/PIPE.cpp +++ b/src/simulation/elements/PIPE.cpp @@ -1,5 +1,7 @@ #include "simulation/ElementCommon.h" +extern const std::array, 8> Element_PIPE_offsets; +void Element_PIPE_transformPatchOffsets(Particle &part, const std::array &offsetMap); int Element_PIPE_update(UPDATE_FUNC_ARGS); int Element_PIPE_graphics(GRAPHICS_FUNC_ARGS); void Element_PIPE_transfer_pipe_to_part(Simulation * sim, Particle *pipe, Particle *part, bool STOR); @@ -78,40 +80,21 @@ constexpr int PPIP_TMPFLAG_TRIGGER_OFF = 0x08000000; constexpr int PPIP_TMPFLAG_TRIGGER_ON = 0x10000000; constexpr int PPIP_TMPFLAG_TRIGGERS = 0x1C000000; -signed char pos_1_rx[] = { -1,-1,-1, 0, 0, 1, 1, 1 }; -signed char pos_1_ry[] = { -1, 0, 1,-1, 1,-1, 0, 1 }; +const std::array, 8> Element_PIPE_offsets = {{ + { -1, -1 }, + { -1, 0 }, + { -1, 1 }, + { 0, -1 }, + { 0, 1 }, + { 1, -1 }, + { 1, 0 }, + { 1, 1 }, +}}; -static void transformPatch(Particle &part, const int (&patch)[8]) +void Element_PIPE_transformPatchOffsets(Particle &part, const std::array &offsetMap) { - if (part.tmp & 0x00000200) part.tmp = (part.tmp & 0xFFFFE3FF) | (patch[(part.tmp & 0x00001C00) >> 10] << 10); - if (part.tmp & 0x00002000) part.tmp = (part.tmp & 0xFFFE3FFF) | (patch[(part.tmp & 0x0001C000) >> 14] << 14); -} - -void Element_PIPE_patchR(Particle &part) -{ - // 035 -> 210 - // 1 6 -> 4 3 - // 247 -> 765 - const int patchR[] = { 2, 4, 7, 1, 6, 0, 3, 5 }; - transformPatch(part, patchR); -} - -void Element_PIPE_patchH(Particle &part) -{ - // 035 -> 530 - // 1 6 -> 6 1 - // 247 -> 742 - const int patchH[] = { 5, 6, 7, 3, 4, 0, 1, 2 }; - transformPatch(part, patchH); -} - -void Element_PIPE_patchV(Particle &part) -{ - // 035 -> 247 - // 1 6 -> 1 6 - // 247 -> 035 - const int patchV[] = { 2, 1, 0, 4, 3, 7, 6, 5 }; - transformPatch(part, patchV); + if (part.tmp & 0x00000200) part.tmp = (part.tmp & 0xFFFFE3FF) | (offsetMap[(part.tmp & 0x00001C00) >> 10] << 10); + if (part.tmp & 0x00002000) part.tmp = (part.tmp & 0xFFFE3FFF) | (offsetMap[(part.tmp & 0x0001C000) >> 14] << 14); } static unsigned int prevColor(unsigned int flags) @@ -260,8 +243,8 @@ int Element_PIPE_update(UPDATE_FUNC_ARGS) rndstore = sim->rng.gen(); rnd = rndstore&7; //rndstore = rndstore>>3; - rx = pos_1_rx[rnd]; - ry = pos_1_ry[rnd]; + rx = Element_PIPE_offsets[rnd].X; + ry = Element_PIPE_offsets[rnd].Y; if (BOUNDS_CHECK) { r = pmap[y+ry][x+rx]; @@ -501,8 +484,8 @@ static void pushParticle(Simulation * sim, int i, int count, int original) { rnd = rndstore&7; rndstore = rndstore>>3; - rx = pos_1_rx[rnd]; - ry = pos_1_ry[rnd]; + rx = Element_PIPE_offsets[rnd].X; + ry = Element_PIPE_offsets[rnd].Y; if (BOUNDS_CHECK) { r = sim->pmap[y+ry][x+rx]; @@ -537,7 +520,7 @@ static void pushParticle(Simulation * sim, int i, int count, int original) else //predefined 1 pixel thick pipe movement { int coords = 7 - ((sim->parts[i].tmp>>10)&7); - r = sim->pmap[y+ pos_1_ry[coords]][x+ pos_1_rx[coords]]; + r = sim->pmap[y+ Element_PIPE_offsets[coords].Y][x+ Element_PIPE_offsets[coords].X]; if ((TYP(r)==PT_PIPE || TYP(r) == PT_PPIP) && (sim->parts[ID(r)].tmp&PFLAG_COLORS) != notctype && !TYP(sim->parts[ID(r)].ctype)) { transfer_pipe_to_pipe(sim->parts+i, sim->parts+(ID(r)), false); @@ -563,8 +546,8 @@ static void pushParticle(Simulation * sim, int i, int count, int original) } else if (!r) //Move particles out of pipe automatically, much faster at ends { - rx = pos_1_rx[coords]; - ry = pos_1_ry[coords]; + rx = Element_PIPE_offsets[coords].X; + ry = Element_PIPE_offsets[coords].Y; np = sim->create_part(-1,x+rx,y+ry,TYP(sim->parts[i].ctype)); if (np!=-1) {