Add snap line to more angles (fix #1641)

This commit is contained in:
tsone 2023-12-22 20:09:35 +01:00
parent e87fdbb3af
commit b6026ad433
4 changed files with 111 additions and 46 deletions

View File

@ -153,45 +153,16 @@ public:
stroke[0] = m_first;
stroke[1] = pt;
bool isoAngle = false;
int snapM = 0;
if ((int(loop->getModifiers()) & int(ToolLoopModifiers::kSquareAspect))) {
int dx = stroke[1].x - m_first.x;
int dy = stroke[1].y - m_first.y;
int minsize = std::min(ABS(dx), ABS(dy));
int maxsize = std::max(ABS(dx), ABS(dy));
const int dx = stroke[1].x - m_first.x;
const int dy = stroke[1].y - m_first.y;
const int minsize = std::min(ABS(dx), ABS(dy));
// Lines
if (loop->getIntertwine()->snapByAngle()) {
double angle = 180.0 * std::atan(static_cast<double>(-dy) /
static_cast<double>(dx)) / PI;
angle = ABS(angle);
// Snap horizontally
if (angle < 18.0) {
stroke[1].y = m_first.y;
}
// Snap at 26.565
else if (angle < 36.0) {
stroke[1].x = m_first.x + SGN(dx)*maxsize;
stroke[1].y = m_first.y + SGN(dy)*maxsize/2;
isoAngle = true;
}
// Snap at 45
else if (angle < 54.0) {
stroke[1].x = m_first.x + SGN(dx)*minsize;
stroke[1].y = m_first.y + SGN(dy)*minsize;
}
// Snap at 63.435
else if (angle < 72.0) {
stroke[1].x = m_first.x + SGN(dx)*maxsize/2;
stroke[1].y = m_first.y + SGN(dy)*maxsize;
isoAngle = true;
}
// Snap vertically
else {
stroke[1].x = m_first.x;
}
snapM = doc::algo_line_snap_endpoint(&stroke[1].x, &stroke[1].y, m_first.x, m_first.y, stroke[1].x, stroke[1].y);
}
// Rectangles and ellipses
else {
@ -209,12 +180,24 @@ public:
stroke[1].y = m_center.y + ry;
}
else if ((int(loop->getModifiers()) & int(ToolLoopModifiers::kFromCenter))) {
int rx = stroke[1].x - m_first.x;
int ry = stroke[1].y - m_first.y;
stroke[0].x = m_first.x - rx + (isoAngle && ABS(rx) > ABS(ry) ? SGN(rx)*(rx & 1): 0);
stroke[0].y = m_first.y - ry + (isoAngle && ABS(rx) < ABS(ry) ? SGN(ry)*(ry & 1): 0);
stroke[1].x = m_first.x + rx;
stroke[1].y = m_first.y + ry;
const int rx = stroke[1].x - stroke[0].x;
const int ry = stroke[1].y - stroke[0].y;
if (snapM == 0 || snapM == INT_MAX) {
stroke[0].x -= rx;
stroke[0].y -= ry;
}
else {
if (std::abs(rx) >= std::abs(ry)) {
const int c = std::abs(rx) / snapM;
stroke[0].x -= SGN(rx) * c * snapM;
stroke[0].y -= SGN(ry) * c;
}
else {
const int c = std::abs(ry) / snapM;
stroke[0].x -= SGN(rx) * c;
stroke[0].y -= SGN(ry) * c * snapM;
}
}
}
// Adjust points for selection like tools (so we can select tiles)

View File

@ -151,16 +151,18 @@ 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())) {
// We prefer the perfect pixel lines that matches grid tiles
if (loop->getController()->canSnapToGrid() && loop->getSnapToGrid()) {
// "Snap to Grid" is enabled. Has precedence over other modifiers.
return (needsFixForLineBrush ? algo_line_perfect_with_fix_for_line_brush:
algo_line_perfect);
}
else if (int(loop->getModifiers()) & int(ToolLoopModifiers::kSquareAspect)) {
// When "Snap Angle" in being used.
return (needsFixForLineBrush ? algo_line_perfect_with_fix_for_line_brush:
algo_line_snap);
}
else {
// In other case we use the regular algorithm that is useful to
// Otherwise use the regular algorithm that is useful to
// draw continuous lines/strokes.
return (needsFixForLineBrush ? algo_line_continuous_with_fix_for_line_brush:
algo_line_continuous);

View File

@ -20,6 +20,82 @@
namespace doc {
int algo_line_snap_endpoint(int* x_out, int* y_out, int x1, int y1, int x2, int y2)
{
constexpr int MAX_M = 8;
int dx = x2 - x1;
int dy = y2 - y1;
const bool swapxy = std::abs(dy) > std::abs(dx);
if (swapxy) {
std::swap(dx, dy);
}
const int m_limit = std::min(MAX_M, std::max(1, std::abs(dx) / 2));
int m = dy != 0 ? (std::abs(dx) + std::abs(dy)/2) / std::abs(dy) : INT_MAX;
if (m > 2 * m_limit) m = INT_MAX;
else if (m > m_limit) m = m_limit;
if (!x_out || !y_out) {
return m;
}
if (m != INT_MAX) {
const int v2 = m*m + 1;
dx = SGN(dx) * (m * (std::abs(dx*m) + std::abs(dy)) + v2/2) / v2;
dy = SGN(dy) * std::abs(dx) / m;
if (swapxy) {
std::swap(dx, dy);
}
*x_out = x1 + dx;
*y_out = y1 + dy;
}
else {
if (swapxy) {
*x_out = x1;
*y_out = y2;
}
else {
*x_out = x2;
*y_out = y1;
}
}
return m;
}
void algo_line_snap(int x1, int y1, int x2, int y2, void* data, AlgoPixel proc)
{
const int m = algo_line_snap_endpoint(nullptr, nullptr, x1, y1, x2, y2);
const bool swapxy = std::abs(y2-y1) > std::abs(x2-x1);
if (swapxy) {
std::swap(x1, y1);
std::swap(x2, y2);
}
const int dx = SGN(x2-x1);
const int dy = SGN(y2-y1);
x2 += dx;
int e = m;
int y = y1;
for (int x=x1; x!=x2; x+=dx) {
if (swapxy)
proc(y, x, data);
else
proc(x, y, data);
e--;
if (!e) {
y += dy;
e = m;
}
}
}
void algo_line_perfect(int x1, int y1, int x2, int y2, void* data, AlgoPixel proc)
{
bool yaxis;

View File

@ -28,6 +28,10 @@ namespace doc {
void algo_line_perfect(int x1, int y1, int x2, int y2, void* data, AlgoPixel proc);
void algo_line_perfect_with_fix_for_line_brush(int x1, int y1, int x2, int y2, void *data, AlgoPixel proc);
// For lines with constant integer pixel runs throughout the line.
void algo_line_snap(int x1, int y1, int x2, int y2, void* data, AlgoPixel proc);
int algo_line_snap_endpoint(int* x_out, int* y_out, int x1, int y1, int x2, int y2);
// Useful to create continuous lines (you can draw from one point to
// another, and continue from that point to another in the same
// angle and the line will look continous).