From 0a18e5ffc6c753eedb59994bba3b318ca03d5f4c Mon Sep 17 00:00:00 2001 From: Liebranca Date: Wed, 19 Feb 2025 14:14:42 -0300 Subject: [PATCH] Add persistence and grid offset for isometric --- src/app/file/ase_format.cpp | 2 + src/app/ui/editor/editor.cpp | 243 +++++++++++++++++++++++------------ src/app/ui/editor/editor.h | 1 + src/dio/aseprite_common.h | 1 + src/dio/aseprite_decoder.cpp | 2 + 5 files changed, 170 insertions(+), 79 deletions(-) diff --git a/src/app/file/ase_format.cpp b/src/app/file/ase_format.cpp index 46d5c669b..a9c3c45a7 100644 --- a/src/app/file/ase_format.cpp +++ b/src/app/file/ase_format.cpp @@ -557,6 +557,7 @@ static void ase_file_prepare_header(FILE* f, header->grid_y = sprite->gridBounds().y; header->grid_width = sprite->gridBounds().w; header->grid_height = sprite->gridBounds().h; + header->grid_type = uint16_t(sprite->gridType()); } static void ase_file_write_header(FILE* f, dio::AsepriteHeader* header) @@ -584,6 +585,7 @@ static void ase_file_write_header(FILE* f, dio::AsepriteHeader* header) fputw(header->grid_y, f); fputw(header->grid_width, f); fputw(header->grid_height, f); + fputw(header->grid_type, f); fseek(f, header->pos + 128, SEEK_SET); } diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 1392601b1..a2f193b51 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -1147,7 +1147,7 @@ void Editor::drawGrid(Graphics* g, grid_color = gfx::rgba(gfx::getr(grid_color), gfx::getg(grid_color), gfx::getb(grid_color), alpha); - // Grid without rotation + // Orthogonal grid if (isPixelGrid || getSite().sprite()->gridType() == doc::Grid::Type::Orthogonal) { // Draw horizontal lines int x1 = spriteBounds.x; @@ -1167,95 +1167,180 @@ void Editor::drawGrid(Graphics* g, } // Isometric grid else { - Rect pix(editorToScreen(RectF(0, 0, 1, 1))); - int x1 = spriteBounds.x; - int y1 = spriteBounds.y; - int x2 = spriteBounds.x + spriteBounds.w; - int y2 = spriteBounds.y + spriteBounds.h; - int dx = int(std::round(grid.w * pix.w)); - int dy = int(std::round(grid.h * pix.h)); + const RectF pix(editorToScreenF(RectF(0, 0, 1, 1))); + int x1 = gridF.x; + int y1 = gridF.y; + const int x2 = spriteBounds.x + spriteBounds.w; + const int y2 = spriteBounds.y + spriteBounds.h; + int dx = std::round(grid.w * pix.w); + int dy = std::round(grid.h * pix.h); - // Make tile bitmap - doc::MaskBoundaries immask; - { - int x = 0; - int y = grid.h / 2; - int lx = grid.w; + if (dx < 2) + dx = 2; + if (dy < 2) + dy = 2; - // Draw pixel-precise isometric grid when zoomed in - // TODO: add support for different line angles - if (grid.w == grid.h * 2 && m_proj.zoom().scale() > 8.00) { - doc::ImageRef imref( - doc::Image::create(doc::PixelFormat::IMAGE_BITMAP, grid.w * pix.w, (grid.h + 1) * pix.h)); + while (x1 > spriteBounds.x) + x1 -= dx; + while (y1 > spriteBounds.y) + y1 -= dy; - doc::Image* im = imref.get(); - if (!im) - return; + // Draw pixel-precise isometric grid when zoomed in + if (m_proj.zoom().scale() > 8.00) { + ui::Paint paint; + paint.style(ui::Paint::Stroke); + paint.antialias(false); + paint.color(grid_color); - // Prepare bitmap - im->clear(0x00); - im->fillRect(0, y * pix.h, lx * pix.w, y * pix.h, 0x01); - y++; - x++; - for (; y < grid.h; y++, x += 2) - im->fillRect(x * pix.w, (y - (x + 1)) * pix.h, (lx - x) * pix.w, y * pix.h, 0x01); + // Move single cell across the screen + // to draw entire grid + Path& cell = getIsometricGridPath(grid); - im->fillRect(x * pix.w, 0, (x + 2) * pix.w, y * pix.h, 0x01); - immask.regen(im); - immask.createPathIfNeeeded(); - - // Draw entire grid from single cell - ui::Paint paint; - paint.style(ui::Paint::Stroke); - paint.antialias(false); - paint.color(grid_color); - - for (y = y1; y < y2; y += dy) { - for (x = x1; x < x2; x += dx) { - gfx::Path cell = immask.path(); - cell.offset(x, y); - g->drawPath(cell, paint); - } - } - } - // Draw straight isometric line grid - else { - // Single side of diamond is line (a, b) - Point a(x1 + x * pix.w, y1 + (y - x) * pix.h); - Point b(x1 + (grid.w / 2) * pix.w, y1 + grid.h * pix.h); - - // Calculate number of diamonds required to - // fill canvas horizontally - Point left(0, 0); - while (left.x < spriteBounds.w) - left.x += gridF.w; - - // Get length and direction of line (a, b) - // then calculate how much we need to stretch said - // line to cover the whole canvas - const Point vto = b - a; - const Point ivto = Point(-vto.x, vto.y); - double lenF = sqrt(vto.x * vto.x + vto.y * vto.y); - int len = int(std::round(left.x / lenF)) + 1; - - // Now displace point (b) to upper edge of canvas - b = a + Point(gridF.w / 2, -gridF.h / 2); - - // Move these two points across the screen in - // cell-sized steps to draw the entire grid - for (y = y1; y < y2; y += dy) { - g->drawLine(grid_color, a, a + vto * len); - g->drawLine(grid_color, a + left, (a + left) + ivto * len); - g->drawLine(grid_color, b, b + vto * len); - g->drawLine(grid_color, b, b + ivto * len); - a.y += gridF.h; - b.x += gridF.w; + for (int y = y1; y < y2; y += dy) { + for (int x = x1; x < x2; x += dx) { + cell.offset(x, y); + g->drawPath(cell, paint); + // Restore original position for later use + cell.offset(-x, -y); } } } + // Draw straight isometric line grid + else { + // Single side of diamond is line (a, b) + Point a(0, std::round(grid.h * 0.5 * pix.h)); + Point b(std::round(grid.w * 0.5 * pix.w), dy); + + // Get length and direction of line (a, b) + const Point vto = b - a; + const Point ivto = Point(-vto.x, vto.y); + const double lenF = sqrt(vto.x * vto.x + vto.y * vto.y); + + // Now displace point (b) to upper edge of canvas + b = a + Point(std::round(dx * 0.5), std::round(-dy * 0.5)); + + // Offset line (a, b) by screen coords + a += Point(x1, y1); + b += Point(x1, y1); + + // Calculate number of diamonds required to + // fill canvas horizontally + Point left(std::round(((x2 - x1) / dx) * dx), 0); + while (left.x < (x2 - x1)) + left.x += dx; + + // Calculate how much we need to stretch + // line (a, b) to cover the whole canvas + const int len = int(std::round(left.x / lenF)) + 1 + int(grid.x > 0) + int(grid.y > 0); + + // Move these two points across the screen in + // cell-sized steps to draw the entire grid + for (int y = y1; y < y2; y += dy) { + g->drawLine(grid_color, a, a + vto * len); + g->drawLine(grid_color, a + left, (a + left) + ivto * len); + a.y += dy; + } + for (int x = x1; x < x2; x += dx) { + g->drawLine(grid_color, b, b + vto * len); + g->drawLine(grid_color, b, b + ivto * len); + b.x += dx; + } + } } } +gfx::Path& Editor::getIsometricGridPath(Rect& grid) +{ + static Path path; + static Size prevSize(0, 0); + static double prevScale = 0.00; + + // Regenerate bitmap on zoom or grid size change + if (prevScale != m_proj.zoom().scale() || prevSize != grid.size()) { + const RectF pix(editorToScreenF(RectF(0, 0, 1, 1))); + doc::ImageRef imref(doc::Image::create(doc::PixelFormat::IMAGE_BITMAP, + std::round(grid.w * pix.w), + std::round((grid.h + 1) * pix.h))); + + // Return previous path if image generation fails + doc::Image* im = imref.get(); + if (!im) + return path; + + // Prepare bitmap + im->clear(0x00); + int x = 0; + int y = int(std::round(grid.h * 0.5)); + const int lx = grid.w; + int ly = y - 1; + + im->fillRect(0, std::round(y * pix.h), std::round(lx * pix.w), std::round(y * pix.h), 0x01); + + y++; + x++; + + // 2:1 + if (grid.w == grid.h * 2) { + for (; y < grid.h; y++, x += 2) + im->fillRect(std::round(x * pix.w), + std::round((y - (x + 1)) * pix.h), + std::round((lx - x) * pix.w), + std::round(y * pix.h), + 0x01); + + im->fillRect(std::round(x * pix.w), + 0, + std::round((x + 2) * pix.w), + std::round(y * pix.h), + 0x01); + } + // 1:1 + else if (grid.w == grid.h) { + for (; y < grid.h; y++, x++, ly--) + im->fillRect(std::round(x * pix.w), + std::round(ly * pix.h), + std::round((lx - x) * pix.w), + std::round(y * pix.h), + 0x01); + + im->fillRect(std::round(x * pix.w), 0, std::round(x * pix.w), std::round(y * pix.h), 0x01); + } + // Quick test for other ratios + else if (grid.w > grid.h * 2) { + float step = 0.00; + float rem = 0.00; + const float len = float(grid.w) / (grid.h + 1); + + for (; y < grid.h; y++, x += step, ly--) { + step = len + rem; + rem = (step - int(step)) * int(std::round(step) > std::floor(step)); + step = std::round(step); + im->fillRect(std::round(x * pix.w), + std::round(ly * pix.h), + std::round((lx - x) * pix.w), + std::round(y * pix.h), + 0x01); + } + + im->fillRect(std::round((grid.w * 0.5 - 1) * pix.w), + 0, + std::round((grid.w * 0.5 + 1) * pix.w), + std::round(y * pix.h), + 0x01); + } + + doc::MaskBoundaries immask; + immask.regen(im); + immask.createPathIfNeeeded(); + path = immask.path(); + } + // Remember scale used to generate the current path + prevScale = m_proj.zoom().scale(); + prevSize.w = grid.w; + prevSize.h = grid.h; + return path; +} + void Editor::drawSlices(ui::Graphics* g) { if ((m_flags & kShowSlices) == 0) diff --git a/src/app/ui/editor/editor.h b/src/app/ui/editor/editor.h index c07d2e322..afd9683ed 100644 --- a/src/app/ui/editor/editor.h +++ b/src/app/ui/editor/editor.h @@ -368,6 +368,7 @@ private: const app::Color& color, int alpha, bool isPixelGrid); + gfx::Path& getIsometricGridPath(gfx::Rect& grid); void drawSlices(ui::Graphics* g); void drawTileNumbers(ui::Graphics* g, const Cel* cel); void drawCelBounds(ui::Graphics* g, const Cel* cel, const gfx::Color color); diff --git a/src/dio/aseprite_common.h b/src/dio/aseprite_common.h index 2fd835fa0..7a2d0ec0c 100644 --- a/src/dio/aseprite_common.h +++ b/src/dio/aseprite_common.h @@ -100,6 +100,7 @@ struct AsepriteHeader { int16_t grid_y; uint16_t grid_width; uint16_t grid_height; + uint16_t grid_type; }; struct AsepriteFrameHeader { diff --git a/src/dio/aseprite_decoder.cpp b/src/dio/aseprite_decoder.cpp index 80f697328..e3422a0b1 100644 --- a/src/dio/aseprite_decoder.cpp +++ b/src/dio/aseprite_decoder.cpp @@ -74,6 +74,7 @@ bool AsepriteDecoder::decode() // Set grid bounds sprite->setGridBounds( gfx::Rect(header.grid_x, header.grid_y, header.grid_width, header.grid_height)); + sprite->setGridType(static_cast(header.grid_type)); // Prepare variables for layer chunks doc::Layer* last_layer = sprite->root(); @@ -336,6 +337,7 @@ bool AsepriteDecoder::readHeader(AsepriteHeader* header) header->grid_y = (int16_t)read16(); header->grid_width = read16(); header->grid_height = read16(); + header->grid_type = read16(); if (header->depth != 8) // Transparent index only valid for indexed images header->transparent_index = 0;