mirror of https://github.com/aseprite/aseprite.git
Adjust line tool for isometric 'snap to'
This commit is contained in:
parent
156cf0cfcf
commit
4d239f089f
|
@ -28,39 +28,77 @@ gfx::Point snap_to_isometric_grid(const gfx::Rect& grid,
|
|||
const PreferSnapTo prefer)
|
||||
{
|
||||
// Convert point to grid space
|
||||
gfx::PointF newPoint(int((point.x - grid.x) / double(grid.w)) * grid.w,
|
||||
int((point.y - grid.y) / double(grid.h)) * grid.h);
|
||||
const gfx::PointF newPoint(int((point.x - grid.x) / double(grid.w)) * grid.w,
|
||||
int((point.y - grid.y) / double(grid.h)) * grid.h);
|
||||
// And then make it relative to the center of a cell
|
||||
const gfx::PointF vto((newPoint + grid.center()) - point);
|
||||
|
||||
// Substract this from original point (also in grid space)
|
||||
// to obtain newPoint as an offset within the first grid cell
|
||||
const gfx::PointF diff((point - grid.origin()) - newPoint);
|
||||
|
||||
// We now find the closest corner to that offset
|
||||
const gfx::PointF candidates[] = { gfx::PointF(grid.w * 0.5, 0),
|
||||
gfx::PointF(grid.w * 0.5, grid.h),
|
||||
gfx::PointF(0, grid.h * 0.5),
|
||||
gfx::PointF(grid.w, grid.h * 0.5) };
|
||||
gfx::PointF near(grid.origin());
|
||||
double dist = (grid.w + grid.h) * 2;
|
||||
for (const auto& c : candidates) {
|
||||
gfx::PointF vto = diff - c;
|
||||
if (vto.x < 0)
|
||||
vto.x = -vto.x;
|
||||
if (vto.y < 0)
|
||||
vto.y = -vto.y;
|
||||
const double newDist = vto.x + vto.y;
|
||||
if (newDist < dist) {
|
||||
near = c;
|
||||
dist = newDist;
|
||||
}
|
||||
// The following happens here:
|
||||
//
|
||||
// /\ /\
|
||||
// /A \/B \
|
||||
// \ /\ /
|
||||
// \/ \/
|
||||
// /\ /\
|
||||
// /C \/D \
|
||||
//
|
||||
// Only the origin for diamonds (A,B,C,D) can be found by dividing
|
||||
// the original point by grid size.
|
||||
//
|
||||
// In order to snap to a position relative to the "in-between" diamonds,
|
||||
// we need to determine whether the cell coords are outside the
|
||||
// bounds of the current grid cell.
|
||||
bool outside;
|
||||
{
|
||||
// We use the pixel-precise grid for this bounds-check
|
||||
const auto& line = doc::Grid(grid).getIsometricLinePoints();
|
||||
const int index = int(ABS(vto.y) - int(vto.y > 0)) + 1;
|
||||
const gfx::Point co(-vto.x + int(grid.w / 2), -vto.y + int(grid.h / 2));
|
||||
const gfx::Point& p = line[index];
|
||||
outside = !(p.x <= co.x) || !(co.x < grid.w - p.x) || !(grid.h - p.y <= co.y) || !(co.y < p.y);
|
||||
}
|
||||
|
||||
// TODO: translate the use of the 'prefer' argument from
|
||||
// the orthogonal logic to this function
|
||||
// Find which of the four corners of the current diamond
|
||||
// should be picked
|
||||
gfx::Point near(0, 0);
|
||||
const gfx::Point candidates[] = { gfx::Point(grid.w / 2, 0),
|
||||
gfx::Point(grid.w / 2, grid.h),
|
||||
gfx::Point(0, grid.h / 2),
|
||||
gfx::Point(grid.w, grid.h / 2) };
|
||||
switch (prefer) {
|
||||
case PreferSnapTo::ClosestGridVertex:
|
||||
if (ABS(vto.x) > ABS(vto.y))
|
||||
near = (vto.x < 0 ? candidates[3] : candidates[2]);
|
||||
else
|
||||
near = (vto.y < 0 ? candidates[1] : candidates[0]);
|
||||
break;
|
||||
|
||||
// Convert cell offset to pixel space
|
||||
newPoint += near + grid.origin();
|
||||
return gfx::Point(std::round(newPoint.x), std::round(newPoint.y));
|
||||
// Pick topmost corner
|
||||
case PreferSnapTo::FloorGrid:
|
||||
case PreferSnapTo::BoxOrigin:
|
||||
if (outside) {
|
||||
near = (vto.x < 0 ? candidates[3] : candidates[2]);
|
||||
near.y -= (vto.y > 0 ? grid.h : 0);
|
||||
}
|
||||
else {
|
||||
near = candidates[0];
|
||||
}
|
||||
break;
|
||||
|
||||
// Pick bottom-most corner
|
||||
case PreferSnapTo::CeilGrid:
|
||||
case PreferSnapTo::BoxEnd:
|
||||
if (outside) {
|
||||
near = (vto.x < 0 ? candidates[3] : candidates[2]);
|
||||
near.y += (vto.y < 0 ? grid.h : 0);
|
||||
}
|
||||
else {
|
||||
near = candidates[1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Convert offset back to pixel space
|
||||
return gfx::Point(newPoint + near + grid.origin());
|
||||
}
|
||||
|
||||
gfx::Point snap_to_grid(const gfx::Rect& grid, const gfx::Point& point, const PreferSnapTo prefer)
|
||||
|
|
|
@ -17,6 +17,46 @@ namespace app { namespace tools {
|
|||
|
||||
using namespace gfx;
|
||||
|
||||
// Adjustment for snap to isometric grid
|
||||
static void snap_isometric_line(ToolLoop* loop, Stroke& stroke)
|
||||
{
|
||||
// Get line angle
|
||||
PointF vto(stroke[1].x - stroke[0].x, stroke[1].y - stroke[0].y);
|
||||
double len = ABS(vto.x) + ABS(vto.y);
|
||||
vto /= len;
|
||||
|
||||
// Skip on single point
|
||||
if (std::isnan(vto.x) && std::isnan(vto.y))
|
||||
return;
|
||||
|
||||
// Offset vertical lines one pixel left for line tool.
|
||||
// Because pressing the angle snap key will bypass this function,
|
||||
// this makes it so one can selectively apply the offset.
|
||||
const gfx::Rect& grid = loop->getGridBounds();
|
||||
if (int(vto.x) == 0 && int(vto.y) != 0) {
|
||||
bool lineTool = (string_id_to_brush_type(loop->getTool()->getId()) == kLineBrushType);
|
||||
stroke[0].x -= lineTool;
|
||||
stroke[1].x -= lineTool;
|
||||
}
|
||||
// Diagonal lines for width-to-height ratios greater than 1:1
|
||||
else if (grid.w / float(grid.h)) {
|
||||
// Skip horizontal or cross-cell diagonal lines
|
||||
PointF normal(grid.w * 0.5, grid.h * 0.5);
|
||||
normal /= normal.x + normal.y;
|
||||
const double eps = 0.15;
|
||||
if (ABS(vto.x) < normal.x - eps || ABS(vto.x) > normal.x + eps || ABS(vto.y) < normal.y - eps ||
|
||||
ABS(vto.y) > normal.y + eps)
|
||||
return;
|
||||
|
||||
// Adjust line start/end point based on direction
|
||||
stroke[0].y += (!(grid.h & 1) ? vto.y < 0 : 0);
|
||||
Point delta(std::round(SGN(vto.x) * normal.x * len), std::round(SGN(vto.y) * normal.y * len));
|
||||
stroke[1].x = stroke[0].x + delta.x;
|
||||
stroke[1].y = stroke[0].y + delta.y;
|
||||
stroke[1].y += (!(grid.h & 1) ? SGN(vto.y) : 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Shared logic between controllers that can move/displace all points
|
||||
// using the space bar.
|
||||
class MoveOriginCapability : public Controller {
|
||||
|
@ -197,6 +237,9 @@ public:
|
|||
stroke[1].y = m_first.y + SGN(dy) * minsize;
|
||||
}
|
||||
}
|
||||
else if (loop->getSnapToGrid() && loop->sprite()->gridType() == doc::Grid::Type::Isometric) {
|
||||
snap_isometric_line(loop, stroke);
|
||||
}
|
||||
|
||||
if (hasAngle()) {
|
||||
int rx = stroke[1].x - m_center.x;
|
||||
|
|
|
@ -145,8 +145,10 @@ doc::AlgoLineWithAlgoPixel Intertwine::getLineAlgo(ToolLoop* loop,
|
|||
|
||||
if ( // When "Snap Angle" in being used or...
|
||||
(int(loop->getModifiers()) & int(ToolLoopModifiers::kSquareAspect)) ||
|
||||
// "Snap to Grid" is enabled
|
||||
(loop->getController()->canSnapToGrid() && loop->getSnapToGrid())) {
|
||||
// "Snap to Grid" is enabled...
|
||||
(loop->getController()->canSnapToGrid() && loop->getSnapToGrid() &&
|
||||
// And we are not in isometric mode
|
||||
loop->sprite()->gridType() != doc::Grid::Type::Isometric)) {
|
||||
// We prefer the perfect pixel lines that matches grid tiles
|
||||
return (needsFixForLineBrush ? algo_line_perfect_with_fix_for_line_brush : algo_line_perfect);
|
||||
}
|
||||
|
|
|
@ -1213,12 +1213,12 @@ void Editor::drawGrid(Graphics* g,
|
|||
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);
|
||||
Point vto = b - a;
|
||||
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));
|
||||
// Now displace point (b) to right edge of canvas
|
||||
b = a + PointF(dx * 0.5, -dy * 0.5);
|
||||
|
||||
// Offset line (a, b) by screen coords
|
||||
a += Point(x1, y1);
|
||||
|
@ -1232,29 +1232,30 @@ void Editor::drawGrid(Graphics* g,
|
|||
|
||||
// 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);
|
||||
const double len = std::round(left.x / lenF) + std::round(dx / lenF) + 1 * int(grid.x > 0) +
|
||||
1 * int(grid.y > 0) + 2;
|
||||
|
||||
vto.x = std::round(vto.x * len);
|
||||
vto.y = std::round(vto.y * len);
|
||||
ivto.x = std::round(ivto.x * len);
|
||||
ivto.y = std::round(ivto.y * len);
|
||||
|
||||
// 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);
|
||||
g->drawLine(grid_color, Point(a), Point(a + vto));
|
||||
g->drawLine(grid_color, Point(a + left), Point((a + left) + ivto));
|
||||
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);
|
||||
g->drawLine(grid_color, Point(b), Point(b + vto));
|
||||
g->drawLine(grid_color, Point(b), Point(b + ivto));
|
||||
b.x += dx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void push_line_pixel(const int x, const int y, std::vector<Point>* const data)
|
||||
{
|
||||
data->push_back(Point(x, y));
|
||||
};
|
||||
|
||||
gfx::Path& Editor::getIsometricGridPath(Rect& grid)
|
||||
{
|
||||
static Path path;
|
||||
|
@ -1273,24 +1274,10 @@ gfx::Path& Editor::getIsometricGridPath(Rect& grid)
|
|||
if (!im)
|
||||
return path;
|
||||
|
||||
// Prepare bitmap
|
||||
// Prepare bitmap from points of pixel precise line.
|
||||
// A single grid cell is calculated from these
|
||||
im->clear(0x00);
|
||||
const Point a(0, std::round(grid.h * 0.5));
|
||||
const Point b(std::floor(grid.w * 0.5), grid.h);
|
||||
std::vector<Point> line;
|
||||
|
||||
// We use the line drawing algorithm to find the points
|
||||
// for a single pixel-precise line
|
||||
doc::algo_line_continuous_with_fix_for_line_brush(a.x,
|
||||
a.y,
|
||||
b.x,
|
||||
b.y,
|
||||
&line,
|
||||
(doc::AlgoPixel)&push_line_pixel);
|
||||
|
||||
// Iterating on said points, we fill in the bitmap
|
||||
// for a single cell grid
|
||||
for (auto p : line)
|
||||
for (const auto& p : getSite().grid().getIsometricLinePoints())
|
||||
im->fillRect(std::round(p.x * pix.w),
|
||||
std::round((grid.h - p.y) * pix.h),
|
||||
std::floor((grid.w - p.x) * pix.w),
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include "doc/grid.h"
|
||||
|
||||
#include "doc/algo.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/primitives.h"
|
||||
|
@ -177,4 +178,28 @@ std::vector<gfx::Point> Grid::tilesInCanvasRegion(const gfx::Region& rgn) const
|
|||
return result;
|
||||
}
|
||||
|
||||
static void push_isometric_line_point(int x, int y, std::vector<gfx::Point>* data)
|
||||
{
|
||||
if (data->empty() || data->back().y != y) {
|
||||
data->push_back(gfx::Point(x, y));
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<gfx::Point> Grid::getIsometricLinePoints(void) const
|
||||
{
|
||||
std::vector<gfx::Point> result;
|
||||
const gfx::Point a(0, std::round(m_tileSize.h * 0.5));
|
||||
const gfx::Point b(std::floor(m_tileSize.w * 0.5), m_tileSize.h);
|
||||
// We use the line drawing algorithm to find the points
|
||||
// for a single pixel-precise line
|
||||
doc::algo_line_continuous_with_fix_for_line_brush(a.x,
|
||||
a.y,
|
||||
b.x,
|
||||
b.y,
|
||||
&result,
|
||||
(doc::AlgoPixel)&push_isometric_line_point);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace doc
|
||||
|
|
|
@ -20,6 +20,7 @@ public:
|
|||
enum class Type {
|
||||
Orthogonal = 0x00,
|
||||
Isometric = 0x01,
|
||||
|
||||
};
|
||||
explicit Grid(const gfx::Size& sz = gfx::Size(16, 16))
|
||||
: m_tileSize(sz)
|
||||
|
@ -83,6 +84,10 @@ public:
|
|||
// Returns an array of tile positions that are touching the given region in the canvas
|
||||
std::vector<gfx::Point> tilesInCanvasRegion(const gfx::Region& rgn) const;
|
||||
|
||||
// Returns an array of coordinates used for calculating the
|
||||
// pixel-precise bounds of an isometric grid cell
|
||||
std::vector<gfx::Point> getIsometricLinePoints(void) const;
|
||||
|
||||
private:
|
||||
gfx::Size m_tileSize;
|
||||
gfx::Point m_origin;
|
||||
|
|
Loading…
Reference in New Issue