Add persistence and grid offset for isometric

This commit is contained in:
Liebranca 2025-02-19 14:14:42 -03:00
parent 94ddd406af
commit 0a18e5ffc6
5 changed files with 170 additions and 79 deletions

View File

@ -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);
}

View File

@ -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)

View File

@ -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);

View File

@ -100,6 +100,7 @@ struct AsepriteHeader {
int16_t grid_y;
uint16_t grid_width;
uint16_t grid_height;
uint16_t grid_type;
};
struct AsepriteFrameHeader {

View File

@ -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<doc::Grid::Type>(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;