291 lines
9.1 KiB
C++
291 lines
9.1 KiB
C++
#include <cruft/util/region.hpp>
|
|
|
|
#include <cruft/util/point.hpp>
|
|
#include <cruft/util/tap.hpp>
|
|
|
|
#include <vector>
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int
|
|
main (int, char **)
|
|
{
|
|
cruft::TAP::logger tap;
|
|
|
|
// check that two overlapping regions successfully test for intersection
|
|
{
|
|
const cruft::point2d ap { 32.7, -6.09703 };
|
|
const cruft::extent2d ae { 0.8, 2. };
|
|
const cruft::region2d a (ap, ae);
|
|
|
|
const cruft::point2d bp {33.5, -4.5};
|
|
const cruft::extent2d be { 0.5, 0.5 };
|
|
const cruft::region2d b (bp, be);
|
|
|
|
tap.expect (!a.intersects (b), "simple 2d intersection");
|
|
}
|
|
|
|
{
|
|
using p_t = cruft::point2i;
|
|
using e_t = cruft::extent2i;
|
|
|
|
struct {
|
|
cruft::region2i a;
|
|
cruft::region2i b;
|
|
bool intersection;
|
|
char const *message;
|
|
} const TESTS[] = {
|
|
{
|
|
.a = { p_t { 58, 22 }, e_t { 13, 8 } },
|
|
.b = { p_t { 58, 22 }, e_t { 7, 7 } },
|
|
.intersection = true,
|
|
.message = "Coaligned base",
|
|
},
|
|
{
|
|
.a = { p_t { 21, 32 }, e_t { 13, 8 } },
|
|
.b = { p_t { 27, 33 }, e_t { 24, 24 } },
|
|
.intersection = true,
|
|
.message = "corner overlap",
|
|
}
|
|
};
|
|
|
|
for (auto const &[a, b, res, msg]: TESTS) {
|
|
tap.expect_eq (a.intersects (b), res, "intersection a-on-b: {}", msg);
|
|
tap.expect_eq (b.intersects (a), res, "intersection b-on-a: {}", msg);
|
|
}
|
|
}
|
|
|
|
// check various floating point maximums successfully test for
|
|
// intersection. the concern is that infinities are incorrectly handled
|
|
// in some comparisons.
|
|
tap.expect (
|
|
cruft::region2d::max ().intersects (
|
|
cruft::region2d::unit ()
|
|
),
|
|
"maximal region2d intersection"
|
|
);
|
|
|
|
tap.expect (
|
|
cruft::region2f::max ().intersects (
|
|
cruft::region2f::unit ()
|
|
),
|
|
"maximal region2f intersection"
|
|
);
|
|
|
|
// ensure unit regions are... unit sized...
|
|
tap.expect_eq (cruft::region2d::unit ().area (), 1.0, "unit region2d area");
|
|
tap.expect_eq (cruft::region2f::unit ().area (), 1.0f, "unit region2f area");
|
|
|
|
// test boundary cases of includes and contains
|
|
{
|
|
const cruft::point2f p0 { 0 };
|
|
const cruft::extent2f e0 { 2 };
|
|
const cruft::region2f r {p0, e0};
|
|
|
|
tap.expect (!r.inclusive (cruft::point2f {-1, 1}), "region/point inclusive, invalid x");
|
|
tap.expect (!r.inclusive (cruft::point2f { 1, 3}), "region/point inclusive, invalid y ");
|
|
|
|
tap.expect (r.inclusive (cruft::point2f {1, 1}), "region/point inclusive, centre");
|
|
tap.expect (r.inclusive (cruft::point2f {0, 0}), "region/point inclusive, base");
|
|
tap.expect (r.inclusive (cruft::point2f {2, 2}), "region/point inclusive, corner");
|
|
}
|
|
|
|
// ensure union operator behaves as expected
|
|
{
|
|
const cruft::point2f p { -1 };
|
|
const cruft::extent2f e { 2 };
|
|
const cruft::region2f r { p, e };
|
|
|
|
tap.expect_eq (r | cruft::point2f { 0, 0 }, r, "identity union");
|
|
tap.expect_eq (
|
|
r | cruft::point2f { 2, 3 },
|
|
cruft::region2f { p, cruft::extent2f { 3, 4 } },
|
|
"positive expanding union"
|
|
);
|
|
tap.expect_eq (
|
|
r | cruft::point2f { -3, -2 },
|
|
cruft::region2f {
|
|
cruft::point2f { -3, -2 },
|
|
cruft::extent2f { 4, 3 }
|
|
},
|
|
"negative expanding union"
|
|
);
|
|
};
|
|
|
|
// Check that 'covers' behaves as expected. ie, a covers b.
|
|
{
|
|
using p = cruft::point2i;
|
|
using e = cruft::extent2i;
|
|
|
|
struct {
|
|
cruft::region2i a;
|
|
cruft::region2i b;
|
|
bool expected;
|
|
char const *message;
|
|
} TESTS[] = {
|
|
{
|
|
.a = { p { 0, 0, }, e { 32, 32, } },
|
|
.b = { p { 27, 4, }, e { 8, 13, } },
|
|
.expected = false,
|
|
.message = "a exceeds +x margin",
|
|
}
|
|
};
|
|
|
|
for (auto const &[a, b, expected, msg]: TESTS)
|
|
tap.expect_eq (a.covers (b), expected, "covers: {}", msg);
|
|
}
|
|
|
|
// ensure make_region covers the expected values
|
|
{
|
|
const cruft::region2i REGION {
|
|
cruft::point2i { 0, 0 },
|
|
cruft::point2i { 3, 2 }
|
|
};
|
|
|
|
const cruft::point2i EXPECTED[] = {
|
|
{ 0, 0 }, { 1, 0 }, { 2, 0 },
|
|
{ 0, 1 }, { 1, 1 }, { 2, 1 },
|
|
};
|
|
|
|
std::vector<cruft::point2i> values;
|
|
std::copy (std::cbegin (REGION.step ()), std::cend (REGION.step ()), std::back_inserter (values));
|
|
|
|
bool success = values.size () == std::size (EXPECTED)
|
|
&& std::equal (std::cbegin (values), std::cend (values),
|
|
std::cbegin (EXPECTED), std::cend (EXPECTED));
|
|
tap.expect (success, "make_range(region2i)");
|
|
};
|
|
|
|
// test distance2 for points
|
|
{
|
|
struct {
|
|
cruft::region2f r;
|
|
cruft::point2f p;
|
|
float distance2;
|
|
char const *message;
|
|
} const TESTS[] = {
|
|
{
|
|
{ cruft::point2f { 1, 1 }, cruft::point2f { 3, 5 } },
|
|
{ 0, 0 },
|
|
2.f,
|
|
"origin to offset rect"
|
|
},
|
|
{
|
|
{ cruft::point2f { 1, 1 }, cruft::point2f { 3, 5 } },
|
|
{ 2, 2 },
|
|
0.f,
|
|
"point inside region"
|
|
},
|
|
{
|
|
{ cruft::point2f { 1, 1 }, cruft::point2f { 3, 5 } },
|
|
{ 4, 7 },
|
|
1 + 4,
|
|
"offset point to offset rect"
|
|
},
|
|
};
|
|
|
|
for (auto const &t: TESTS) {
|
|
auto const d2 = distance2 (t.r, t.p);
|
|
tap.expect_eq (d2, t.distance2, "region-point distance2: {}", t.message);
|
|
}
|
|
};
|
|
|
|
|
|
// Test expected results of 2 region interesection
|
|
{
|
|
using p_t = cruft::point2i;
|
|
|
|
struct {
|
|
cruft::region2i a;
|
|
cruft::region2i b;
|
|
cruft::region2i res;
|
|
char const *msg;
|
|
} const TESTS[] = {
|
|
{
|
|
.a = { p_t { 0, 0 }, p_t { 1, 1 } },
|
|
.b = { p_t { 0, 0 }, p_t { 1, 1 } },
|
|
.res = { p_t { 0, 0 }, p_t { 1, 1 } },
|
|
.msg = "identical, unit sizes",
|
|
},
|
|
{
|
|
.a = { p_t { 0, 0 }, p_t { 1, 1 } },
|
|
.b = { p_t { 1, 1 }, p_t { 2, 2 } },
|
|
.res = { p_t { 1, 1 }, p_t { 1, 1 } },
|
|
.msg = "unit sized, shared corner",
|
|
},
|
|
{
|
|
.a = { p_t { -4, -5 }, p_t { 5, 3 } },
|
|
.b = { p_t { -1, -2 }, p_t { 2, 1 } },
|
|
.res = { p_t { -1, -2 }, p_t { 2, 1 } },
|
|
.msg = "a contains b",
|
|
},
|
|
{
|
|
.a = { p_t { -1, -2 }, p_t { 2, 1 } },
|
|
.b = { p_t { -4, -5 }, p_t { 5, 3 } },
|
|
.res = { p_t { -1, -2 }, p_t { 2, 1 } },
|
|
.msg = "b contains a",
|
|
},
|
|
{
|
|
.a = { p_t { -3, -1 }, p_t { 4, 8 } },
|
|
.b = { p_t { -1, -4 }, p_t { 6, 2 } },
|
|
.res = { p_t { -1, -1 }, p_t { 4, 2 } },
|
|
.msg = "partial overlap",
|
|
},
|
|
};
|
|
|
|
for (auto const &[a, b, res, message]: TESTS)
|
|
tap.expect_eq (intersection (a, b), res, "region-region interesection: {}", message);
|
|
}
|
|
|
|
// Test rotate behaves as expected.
|
|
//
|
|
// It's important that the following constraints apply to the test data:
|
|
// * no point lies at the origin because we want to catch
|
|
// rotate-about-point errors that need a translation.
|
|
// * the size is non-square so we can differentiate between rotated
|
|
// values
|
|
// * use floating point values so we can test for loss of precision.
|
|
{
|
|
cruft::point2f const base { 1, 2 };
|
|
cruft::extent2f const size { 3, 4 };
|
|
cruft::region2f const orig { base, size };
|
|
|
|
struct {
|
|
int rotation;
|
|
cruft::region2f res;
|
|
} const TESTS[] {
|
|
{
|
|
.rotation = 0,
|
|
.res = orig,
|
|
},
|
|
{
|
|
.rotation = 1,
|
|
.res = {
|
|
cruft::point2f { -3, 2 },
|
|
cruft::extent2f { 4, 3 },
|
|
},
|
|
},
|
|
{
|
|
.rotation = 2,
|
|
.res = {
|
|
cruft::point2f { 1, -2 },
|
|
cruft::extent2f { 3, 4 },
|
|
},
|
|
},
|
|
{
|
|
.rotation = 3,
|
|
.res = {
|
|
cruft::point2f { 1, 2 },
|
|
cruft::extent2f { 4, 3 },
|
|
},
|
|
}
|
|
};
|
|
|
|
for (auto const &obj: TESTS) {
|
|
auto const computed = rotate90 (orig, obj.rotation);
|
|
tap.expect_eq (computed, obj.res, "{} rotation", obj.rotation);
|
|
}
|
|
}
|
|
|
|
return tap.status ();
|
|
}
|