geom/sample: add edge sampling routines for extent2i
This commit is contained in:
parent
df1f5fe8fd
commit
b6d1b74bc4
@ -355,6 +355,7 @@ list (
|
|||||||
geom/rect.cpp
|
geom/rect.cpp
|
||||||
geom/rect.hpp
|
geom/rect.hpp
|
||||||
geom/sample/fwd.hpp
|
geom/sample/fwd.hpp
|
||||||
|
geom/sample/edge.hpp
|
||||||
geom/sample/surface.hpp
|
geom/sample/surface.hpp
|
||||||
geom/sample/volume.hpp
|
geom/sample/volume.hpp
|
||||||
geom/segment.cpp
|
geom/segment.cpp
|
||||||
@ -651,6 +652,7 @@ if (TESTS)
|
|||||||
geom/line
|
geom/line
|
||||||
geom/plane
|
geom/plane
|
||||||
geom/ray
|
geom/ray
|
||||||
|
geom/sample/edge
|
||||||
geom/segment
|
geom/segment
|
||||||
geom/sphere
|
geom/sphere
|
||||||
hash/buzhash
|
hash/buzhash
|
||||||
|
90
geom/sample/edge.hpp
Normal file
90
geom/sample/edge.hpp
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*
|
||||||
|
* Copyright 2019 Danny Robson <danny@nerdcruft.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fwd.hpp"
|
||||||
|
|
||||||
|
#include "../../extent.hpp"
|
||||||
|
#include "../../region.hpp"
|
||||||
|
|
||||||
|
namespace cruft::geom::sample {
|
||||||
|
template <typename T>
|
||||||
|
struct edge<cruft::extent2<T>> {
|
||||||
|
using shape_type = cruft::extent2<T>;
|
||||||
|
|
||||||
|
edge (shape_type _shape)
|
||||||
|
: m_shape (_shape)
|
||||||
|
{ ; }
|
||||||
|
|
||||||
|
template <typename GeneratorT>
|
||||||
|
point2<T>
|
||||||
|
eval (GeneratorT &&gen)
|
||||||
|
{
|
||||||
|
// Generate a point at some point along the perimeter.
|
||||||
|
//
|
||||||
|
// Unwind half the perimeter with length `w + h - 1`.
|
||||||
|
// This gives us the full length of the width, and subtract one of
|
||||||
|
// the extreme axis on the height side that is covered by the full
|
||||||
|
// width case.
|
||||||
|
//
|
||||||
|
// If we're in the first portion of the perimeter then choose if
|
||||||
|
// we're on the top/bottom randomly. And use the position as the x
|
||||||
|
// coordinate.
|
||||||
|
//
|
||||||
|
// Otherwise bump the coordinate above the baseline which was
|
||||||
|
// already covered and choose the left/right side randomly.
|
||||||
|
//
|
||||||
|
// TODO: avoid repeated calls into uniform. We can do this by
|
||||||
|
// doubling the extent we're testing and using the extra bit for
|
||||||
|
// the side test. But I'm lacking time right now.
|
||||||
|
auto const [w, h] = m_shape;
|
||||||
|
auto const len = w + h - 1;
|
||||||
|
|
||||||
|
auto pos = cruft::random::uniform (T{0}, len, gen);
|
||||||
|
|
||||||
|
if (pos <= w) {
|
||||||
|
auto const _w = pos;
|
||||||
|
auto const _h = cruft::random::uniform (1) ? h : 0;
|
||||||
|
return { _w, _h };
|
||||||
|
}
|
||||||
|
|
||||||
|
pos -= w;
|
||||||
|
|
||||||
|
auto const _w = cruft::random::uniform (1) ? w : 0;
|
||||||
|
auto const _h = pos;
|
||||||
|
|
||||||
|
return { _w, _h };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
shape_type m_shape;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct edge<cruft::region2<T>> {
|
||||||
|
using shape_type = cruft::region2<T>;
|
||||||
|
|
||||||
|
edge (shape_type _shape)
|
||||||
|
: m_shape (_shape)
|
||||||
|
{ ; }
|
||||||
|
|
||||||
|
template <typename GeneratorT>
|
||||||
|
decltype(auto)
|
||||||
|
eval (GeneratorT &&gen)
|
||||||
|
{
|
||||||
|
return edge<cruft::extent2i> (m_shape.e).eval (
|
||||||
|
std::forward<GeneratorT> (gen)
|
||||||
|
) + m_shape.p.template as<vector> ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
shape_type m_shape;
|
||||||
|
};
|
||||||
|
}
|
@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
|
||||||
namespace cruft::geom::sample {
|
namespace cruft::geom::sample {
|
||||||
/// Provides an interface to randomly sample points within the volume of
|
/// Provides an interface to randomly sample points within the volume of
|
||||||
/// a supplied shape.
|
/// a supplied shape.
|
||||||
@ -30,4 +33,14 @@ namespace cruft::geom::sample {
|
|||||||
|
|
||||||
template <typename ShapeT>
|
template <typename ShapeT>
|
||||||
surface (ShapeT const&) -> surface<std::decay_t<ShapeT>>;
|
surface (ShapeT const&) -> surface<std::decay_t<ShapeT>>;
|
||||||
|
|
||||||
|
|
||||||
|
/// Provides an interface to randomly sample points across the edges of a
|
||||||
|
/// shape; eg, the perimeter of an extent or region.
|
||||||
|
template <typename ShapeT>
|
||||||
|
struct edge;
|
||||||
|
|
||||||
|
|
||||||
|
template <typename ShapeT>
|
||||||
|
edge (ShapeT const &) -> edge<std::decay_t<ShapeT>>;
|
||||||
}
|
}
|
||||||
|
62
test/geom/sample/edge.cpp
Normal file
62
test/geom/sample/edge.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#include <cruft/util/geom/sample/edge.hpp>
|
||||||
|
#include <cruft/util/tap.hpp>
|
||||||
|
#include <cruft/util/coord/iostream.hpp>
|
||||||
|
#include <cruft/util/coord/comparator.hpp>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main ()
|
||||||
|
{
|
||||||
|
static constexpr int ITERATIONS = 1'000'000;
|
||||||
|
static const cruft::extent2i AREA { 3, 7 };
|
||||||
|
|
||||||
|
std::map<cruft::point2i, int, cruft::coord::ordering<>> counts;
|
||||||
|
|
||||||
|
auto sampler = cruft::geom::sample::edge (AREA);
|
||||||
|
std::mt19937_64 gen;
|
||||||
|
|
||||||
|
cruft::TAP::logger tap;
|
||||||
|
|
||||||
|
for (int i = 0; i < ITERATIONS; ++i) {
|
||||||
|
auto const pos = sampler.eval (gen);
|
||||||
|
|
||||||
|
bool const x_perim = pos.x == 0 || pos.x == AREA.w;
|
||||||
|
bool const y_perim = pos.y == 0 || pos.y == AREA.h;
|
||||||
|
|
||||||
|
if (!x_perim && !y_perim) {
|
||||||
|
tap.fail ("position %! falls outside perimeter: %!", i, pos);
|
||||||
|
return tap.status ();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const [val, success] = counts.try_emplace (pos, 0);
|
||||||
|
val->second++;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const expected_count = 2 * (AREA[0] + 1) + 2 * (AREA[1] - 1);
|
||||||
|
tap.expect_eq (
|
||||||
|
counts.size (),
|
||||||
|
std::size_t (expected_count),
|
||||||
|
"all perimeter options are covered"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto const [lo,hi] = std::minmax_element (
|
||||||
|
std::begin (counts),
|
||||||
|
std::end (counts),
|
||||||
|
[] (auto const &a, auto const &b)
|
||||||
|
{
|
||||||
|
return a.second < b.second;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto const abs_diff = hi->second - lo->second;
|
||||||
|
auto const rel_diff = float (abs_diff) / lo->second;
|
||||||
|
|
||||||
|
tap.expect_lt (
|
||||||
|
std::abs (rel_diff), 0.05f,
|
||||||
|
"occurence counts are evenly spaced: %!:%! vs %!:%!",
|
||||||
|
hi->first, hi->second,
|
||||||
|
lo->first, lo->second
|
||||||
|
);
|
||||||
|
|
||||||
|
return tap.status ();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user