On 8 September 2017 at 16:11, Nikolay Sivov <nsivov(a)codeweavers.com> wrote:
> +static BOOL d2d_point_on_segment(const D2D1_POINT_2F *q, const D2D1_POINT_2F *p0,
> + const D2D1_POINT_2F *p1, float tolerance)
> +{
> + float dot, edge_length, distance;
> + D2D1_POINT_2F v_p, v_q, n;
> +
> + d2d_point_subtract(&v_p, p1, p0);
> + if ((edge_length = d2d_point_length(&v_p)) == 0.0f)
> + return FALSE;
> +
> + /* Edge distance. */
> + d2d_point_subtract(&v_q, q, p0);
> +
> + n.x = -v_p.y;
> + n.y = v_p.x;
Unfortunately it's not that easy when "transform" is a world
transformation instead of a geometry transformation. I.e., when you
have for example a shear transformation, the "normal" is no longer
necessarily perpendicular to the line segment. Put a different way,
you're potentially testing against a parallelogram instead of a
rectangle. In a sense you account for that with stroke_widths[], but
see below.
> + distance = fabsf(d2d_point_dot(&v_q, &n)) / edge_length;
> + if (distance >= tolerance)
> + return FALSE;
> +
> + /* Test if normal and edge intersect. */
> + dot = d2d_point_dot(&v_q, &v_p);
> + return dot >= 0.0f && dot <= edge_length * edge_length;
> +}
Likewise, this doesn't do the right thing under all transformations.
> + d2d_point_transform(&v_s[0], transform, stroke_width, 0.0f);
> + d2d_point_transform(&v_s[1], transform, stroke_width, 0.0f);
> + d2d_point_transform(&origin, transform, 0.0f, 0.0f);
> +
> + d2d_point_subtract(&v_s[0], &v_s[0], &origin);
> + d2d_point_subtract(&v_s[1], &v_s[1], &origin);
> +
> + stroke_widths[0] = d2d_point_length(&v_s[0]);
> + stroke_widths[1] = d2d_point_length(&v_s[1]);
This ends up with the same value for stroke_widths[0] and stroke_widths[1].
> + d2d_point_subtract(&v, &point, &vertices[i]);
> +
> + if (d2d_point_dot(&v, &v) < tolerance * tolerance)
> + {
> + *contains = TRUE;
> + break;
> + }
This tests around the corner of the joins, but note that there's
potentially a space between that corner and the start of the line
segment that this would miss.
> + else
> + {
> + D2D1_POINT_2F d, s;
> +
> + s.x = rect->right - rect->left;
> + s.y = rect->bottom - rect->top;
> + d.x = fabsf((rect->right + rect->left) * 0.5f - point.x);
> + d.y = fabsf((rect->bottom + rect->top) * 0.5f - point.y);
> +
> + /* Inside test. */
> + if (d.x <= (s.x - stroke_width) * 0.5f - tolerance && d.y <= (s.y - stroke_width) * 0.5f - tolerance)
> + {
> + *contains = FALSE;
> + return S_OK;
> + }
> +
> + if (tolerance == 0.0f)
> + {
> + *contains = d.x < (s.x + stroke_width) * 0.5f && d.y < (s.y + stroke_width) * 0.5f;
> + }
> + else
> + {
> + d.x = max(d.x - (s.x + stroke_width) * 0.5f, 0.0f);
> + d.y = max(d.y - (s.y + stroke_width) * 0.5f, 0.0f);
> +
> + *contains = d2d_point_dot(&d, &d) < tolerance * tolerance;
> + }
> + }
Ultimately it's not that important, but I think there's something to
be said for handling the "!transform" case first as a special case.
I.e.,
if (!transform)
{
...
return S_OK;
}
/* General case. */
> diff --git a/dlls/d2d1/tests/d2d1.c b/dlls/d2d1/tests/d2d1.c
> index f6261295b6..95a010b13e 100644
> --- a/dlls/d2d1/tests/d2d1.c
> +++ b/dlls/d2d1/tests/d2d1.c
> @@ -76,6 +76,15 @@ struct expected_geometry_figure
> const struct geometry_segment *segments;
> };
>
> +struct contains_point_test
> +{
> + D2D1_MATRIX_3X2_F transform;
> + D2D1_POINT_2F point;
> + float tolerance;
> + float stroke_width;
> + BOOL contains;
> +};
Unless these are reused between tests, we tend to keep them local to
the function.
> + static const struct contains_point_test stroke_contains[] =
> + {
> + {{0.0f, 0.0f, 0.0f, 1.0f}, {0.1f, 10.0f}, 0.0f, 1.0f, FALSE},
> + {{0.0f, 0.0f, 0.0f, 1.0f}, {5.0f, 10.0f}, 5.0f, 1.0f, FALSE},
> + {{0.0f, 0.0f, 0.0f, 1.0f}, {4.9f, 10.0f}, 5.0f, 1.0f, TRUE},
> + {{0.0f, 0.0f, 0.0f, 1.0f}, {5.0f, 10.0f}, -5.0f, 1.0f, FALSE},
> + {{0.0f, 0.0f, 0.0f, 1.0f}, {4.9f, 10.0f}, -5.0f, 1.0f, TRUE},
> +
> + {{1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 10.0f}, 0.0f, 1.0f, TRUE},
> + {{1.0f, 0.0f, 0.0f, 1.0f}, {0.1f, 10.0f}, 0.0f, 1.0f, TRUE},
> + {{1.0f, 0.0f, 0.0f, 1.0f}, {0.5f, 10.0f}, 0.0f, 1.0f, FALSE},
> + {{1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 10.0f}, 1.0f, 1.0f, TRUE},
> + {{1.0f, 0.0f, 0.0f, 1.0f}, {0.59f, 10.0f}, 1.0f, 1.0f, TRUE},
> + {{1.0f, 0.0f, 0.0f, 1.0f}, {-0.59f, 10.0f}, 1.0f, 1.0f, TRUE},
> + {{1.0f, 0.0f, 0.0f, 1.0f}, {0.59f, 10.0f}, -1.0f, 1.0f, TRUE},
> + {{1.0f, 0.0f, 0.0f, 1.0f}, {-0.59f, 10.0f}, -1.0f, 1.0f, TRUE},
> + };
Rotations and shears are interesting.
> @@ -2766,6 +2793,108 @@ static void test_rectangle_geometry(void)
> ok(SUCCEEDED(hr), "FillContainsPoint() failed, hr %#x.\n", hr);
> ok(!!contains, "Got wrong hit test result %d.\n", contains);
>
> + /* Stroked area hittesting. Edge. */
> + contains = FALSE;
> + set_point(&point, 0.4f, 10.0f);
> + hr = ID2D1RectangleGeometry_StrokeContainsPoint(geometry, point, 1.0f, NULL, NULL, 0.0f, &contains);
> + ok(SUCCEEDED(hr), "StrokeContainsPoint() failed, hr %#x.\n", hr);
> + ok(!!contains, "Got wrong hit test result %d.\n", contains);
We tend to use %#x for BOOLs. And well, you might as well make these
array based as well.