diff --git a/u02/include/util.h b/u02/include/util.h index af19c3d..ed64009 100644 --- a/u02/include/util.h +++ b/u02/include/util.h @@ -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 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); diff --git a/u02/src/tests.cpp b/u02/src/tests.cpp index aafc054..a2f7209 100644 --- a/u02/src/tests.cpp +++ b/u02/src/tests.cpp @@ -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); +} diff --git a/u02/src/util.cpp b/u02/src/util.cpp index 234fffc..8a2e439 100644 --- a/u02/src/util.cpp +++ b/u02/src/util.cpp @@ -47,3 +47,95 @@ std::pair 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; +}