Add a faster bounded version of pcg::lazy_perlin

This commit is contained in:
Nikita Lisitsa 2021-07-19 08:40:40 +03:00
parent ec6552d1a4
commit 45eaab6a9d

View file

@ -1,53 +1,30 @@
#pragma once #pragma once
#include <psemek/util/function.hpp> #include <psemek/util/function.hpp>
#include <psemek/util/array.hpp>
#include <psemek/geom/vector.hpp> #include <psemek/geom/vector.hpp>
#include <psemek/geom/box.hpp>
#include <unordered_map> #include <unordered_map>
namespace psemek::pcg namespace psemek::pcg
{ {
template <typename T, std::size_t N> namespace detail
struct lazy_perlin
{ {
using generator_func = util::function<geom::vector<T, N>(geom::vector<int, N> const &)>;
using value_type = T; template <typename T, std::size_t N, typename F, typename G>
static constexpr auto dimension = N; T perlin_impl(geom::vector<T, N> p, int grid_size, F && grid_at, G && bound)
lazy_perlin (std::size_t grid_size, generator_func generator)
: grid_size_(grid_size)
, gen_(std::move(generator))
{}
T operator()(geom::vector<T, N> p) const;
private:
int const grid_size_;
mutable std::unordered_map<geom::vector<int, N>, geom::vector<T, N>> grid_;
generator_func gen_;
geom::vector<T, N> grid_at(geom::vector<int, N> const & c) const
{
auto it = grid_.find(c);
if (it == grid_.end())
it = grid_.insert({c, gen_(c)}).first;
return it->second;
}
};
template <typename T, std::size_t N>
T lazy_perlin<T, N>::operator()(geom::vector<T, N> p) const
{ {
geom::vector<int, N> ip; geom::vector<int, N> ip;
for (std::size_t i = 0; i < N; ++i) for (std::size_t i = 0; i < N; ++i)
{ {
ip[i] = (p[i] >= 0) ? (p[i] / grid_size_) : -((- p[i] + grid_size_ - 1) / grid_size_); ip[i] = (p[i] >= 0) ? (p[i] / grid_size) : -((- p[i] + grid_size - 1) / grid_size);
} }
ip = bound(ip);
geom::vector<T, N> t = (p - geom::cast<T>(ip) * T(grid_size_)) / T(grid_size_); geom::vector<T, N> t = (p - geom::cast<T>(ip) * T(grid_size)) / T(grid_size);
T values[1 << N]; T values[1 << N];
@ -79,4 +56,99 @@ namespace psemek::pcg
return 0.5 * (1. + std::sqrt(2.) * values[0]); return 0.5 * (1. + std::sqrt(2.) * values[0]);
} }
}
template <typename T, std::size_t N>
struct lazy_perlin
{
using generator_func = util::function<geom::vector<T, N>(geom::vector<int, N> const &)>;
using value_type = T;
static constexpr auto dimension = N;
lazy_perlin(std::size_t grid_size, generator_func generator)
: grid_size_(grid_size)
, gen_(std::move(generator))
{}
T operator()(geom::vector<T, N> p) const;
private:
int const grid_size_;
mutable std::unordered_map<geom::vector<int, N>, geom::vector<T, N>> grid_;
generator_func gen_;
geom::vector<T, N> grid_at(geom::vector<int, N> const & c) const
{
auto it = grid_.find(c);
if (it == grid_.end())
it = grid_.insert({c, gen_(c)}).first;
return it->second;
}
};
template <typename T, std::size_t N>
struct bounded_lazy_perlin
{
using generator_func = util::function<geom::vector<T, N>(geom::vector<int, N> const &)>;
using value_type = T;
static constexpr auto dimension = N;
bounded_lazy_perlin(std::size_t grid_size, generator_func generator, geom::box<int, N> const & bounds)
: grid_size_(grid_size)
, gen_(std::move(generator))
{
std::array<std::size_t, N> dims;
for (std::size_t i = 0; i < N; ++i)
{
origin_[i] = bounds[i].min / grid_size;
dims[i] = bounds[i].length() / grid_size + 1;
corner_[i] = origin_[i] + dims[i] - 2;
}
grid_.resize(dims);
}
T operator()(geom::vector<T, N> p) const;
private:
int const grid_size_;
geom::vector<int, N> origin_;
geom::vector<int, N> corner_;
mutable util::array<std::optional<geom::vector<T, N>>, N> grid_;
generator_func gen_;
geom::vector<T, N> grid_at(geom::vector<int, N> const & c) const
{
std::array<std::size_t, N> idx;
for (std::size_t i = 0; i < N; ++i)
idx[i] = c[i] - origin_[i];
auto & result = grid_(idx);
if (!result)
result = gen_(c);
return *result;
}
};
template <typename T, std::size_t N>
T lazy_perlin<T, N>::operator()(geom::vector<T, N> p) const
{
return detail::perlin_impl(p, grid_size_, [this](auto const & c){ return grid_at(c); }, [](auto const & p){ return p; });
}
template <typename T, std::size_t N>
T bounded_lazy_perlin<T, N>::operator()(geom::vector<T, N> p) const
{
return detail::perlin_impl(p, grid_size_, [this](auto const & c){ return grid_at(c); }, [this](auto p){
for (std::size_t i = 0; i < N; ++i)
{
p[i] = std::max(p[i], origin_[i]);
p[i] = std::min(p[i], corner_[i]);
}
return p;
});
}
} }