u02: Implement transformation to standard case

This commit is contained in:
Simon Bruder 2023-05-06 16:40:17 +02:00
parent 18456541aa
commit fab356c308
3 changed files with 129 additions and 0 deletions

View file

@ -25,3 +25,8 @@ void transform_inv_mut(Transformation transformation, int &x, int &y);
// returning the transformed point.
// Composition of this with the transformation is the identity function.
std::pair<int, int> transform_inv(Transformation transformation, int x, int y);
// Returns the transformation required
// to make the given endpoints of a line conform
// to the standard case for rasterization.
Transformation transformation_to_standard_case(int x0, int y0, int x1, int y1);

View file

@ -70,3 +70,35 @@ TEST_CASE("Transform = Inverse Transform ○ Transform") {
REQUIRE(y == yti);
}
}
TEST_CASE("Transformation to standard case") {
REQUIRE(transformation_to_standard_case(5, 20, 20, 10) == 0);
REQUIRE(transformation_to_standard_case(5, 5, 20, 15) == TRANSFORM_MIRROR_X);
REQUIRE(transformation_to_standard_case(20, 15, 5, 5) == TRANSFORM_MIRROR_Y);
REQUIRE(transformation_to_standard_case(20, 10, 5, 20) ==
(TRANSFORM_MIRROR_X | TRANSFORM_MIRROR_Y));
REQUIRE(transformation_to_standard_case(5, 20, 15, 5) ==
(TRANSFORM_ROTATE_CW | TRANSFORM_MIRROR_X));
REQUIRE(transformation_to_standard_case(5, 5, 15, 20) ==
TRANSFORM_ROTATE_CCW);
REQUIRE(transformation_to_standard_case(15, 5, 5, 20) ==
(TRANSFORM_ROTATE_CCW | TRANSFORM_MIRROR_X));
REQUIRE(transformation_to_standard_case(15, 20, 5, 5) == TRANSFORM_ROTATE_CW);
}
TEST_CASE("Transformation to standard case (prop)") {
int x0 = GENERATE(take(10, random(-100, 100)));
int y0 = GENERATE(take(10, random(-100, 100)));
int x1 = GENERATE(take(10, random(-100, 100)));
int y1 = GENERATE(take(10, random(-100, 100)));
Transformation transformation =
transformation_to_standard_case(x0, y0, x1, y1);
transform_mut(transformation, x0, y0);
transform_mut(transformation, x1, y1);
REQUIRE(x0 <= x1);
REQUIRE(y0 >= y1);
}

View file

@ -47,3 +47,95 @@ 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;
}