This repository has been archived on 2024-01-28. You can view files and clone it, but cannot push or open issues or pull requests.
ecg-prog-filtered/u02/src/util.cpp

142 lines
4.1 KiB
C++
Raw Normal View History

// SPDX-License-Identifier: LGPL-3.0-or-later
#include "util.h"
#include <cmath>
void transform_mut(Transformation transformation, int &x, int &y) {
if ((transformation & TRANSFORM_ROTATE_CW) != 0) {
std::swap(x, y);
x = -x;
}
if ((transformation & TRANSFORM_ROTATE_CCW) != 0) {
std::swap(x, y);
y = -y;
}
if ((transformation & TRANSFORM_MIRROR_X) != 0) {
y = -y;
}
if ((transformation & TRANSFORM_MIRROR_Y) != 0) {
x = -x;
}
}
std::pair<int, int> transform(Transformation transformation, int x, int y) {
transform_mut(transformation, x, y);
return std::make_pair(x, y);
}
void transform_inv_mut(Transformation transformation, int &x, int &y) {
if ((transformation & TRANSFORM_MIRROR_Y) != 0) {
x = -x;
}
if ((transformation & TRANSFORM_MIRROR_X) != 0) {
y = -y;
}
if (transformation & TRANSFORM_ROTATE_CCW) {
// does clockwise rotation
std::swap(x, y);
x = -x;
}
if (transformation & TRANSFORM_ROTATE_CW) {
// does counterclockwise rotation
std::swap(x, y);
y = -y;
}
}
std::pair<int, int> transform_inv(Transformation transformation, int x, int y) {
transform_inv_mut(transformation, x, y);
return std::make_pair(x, y);
}
/*
* After it took me many hours to get this right,
* I at least want to document how I got to it:
*
* N.B. In the following,
* Modulo is *not* the remainder of the euclidean division,
* but instead the remainder of truncated division
* (i.e., negative quotients produce negative results).
*
* There are two main cases:
* The simple one is where angle % 90° 45°.
* To transform this into the standard case,
* only mirrors are needed.
* The more complicated is when angle % 90° 45°.
* To transform this into the standard case,
* a rotation has to be done, followed by a mirror in some cases.
*
* The following matrices show what must be done when.
* The ASCII art arrows show the line as it should be drawn,
* the the column/row headings show how they can be identified in code,
* the capital letters in the field show what needs to be done
* to reach the standard case
* (X/Y: mirror X/Y; CW/CCW: rotate CW/CCW).
* Because there is no nice way to draw arrows with an angle < 45°,
* they are just differentiated by the heading.
*
* Let (x_0, y_0) be the starting point and (x_1, y_1) the end point.
* Let Δx = x_1 - x_0, Δy = y_1 - y_0.
* Let m = Δy/Δx, α = atan(m).
*
* α 45°:
*
* Δx>0 Δx<0
*
* A | A
* Δy<0 / | \
* / | \
* / - | Y \
* ------+------
* \ X | XY /
* \ | /
* Δy>0 \ | /
* V | V
*
* α 45°:
*
* Δx>0 Δx<0 Δx>0 Δx<0
*
* A | A \ | A
* Δy<0 / | \ Δy<0 \ | /
* / | \ \ | /
* / CW | CW \ X V | /
* -------+------- after rotation ------+-----
* \ CCW | CCW / A | \
* \ | / / | \
* Δy>0 \ | / Δy>0 / | \
* V | V / | X V
*/
Transformation transformation_to_standard_case(int x0, int y0, int x1, int y1) {
Transformation transformation = 0;
int delta_y = y1 - y0;
int delta_x = x1 - x0;
// checks if angle ∈ (-90°, 90°) is ≥ 45°
// this is a simplified version of atan(Δy/Δx) > π/4:
// atan(Δy/Δx) > π/4 | tan(…)
// ⇔ Δx/Δy > 1 | Δy
// ⇔ Δx > Δy
if (abs(delta_y) > abs(delta_x)) {
// if-else is needed, because of special case Δy = 0
if (delta_y < 0) {
transformation |= TRANSFORM_ROTATE_CW;
} else if (delta_y > 0) {
transformation |= TRANSFORM_ROTATE_CCW;
}
// the sign of Δx and Δy (pre-rotation!) differ,
// an additional mirror is needed
if (delta_x * delta_y < 0) {
transformation |= TRANSFORM_MIRROR_X;
}
} else {
if (delta_x < 0) {
transformation |= TRANSFORM_MIRROR_Y;
}
if (delta_y > 0) {
transformation |= TRANSFORM_MIRROR_X;
}
}
return transformation;
}