u02: Implement transformation to standard case
This commit is contained in:
parent
18456541aa
commit
fab356c308
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Reference in a new issue