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,14 +1,63 @@
#pragma once
#include <psemek/util/function.hpp>
#include <psemek/util/array.hpp>
#include <psemek/geom/vector.hpp>
#include <psemek/geom/box.hpp>
#include <unordered_map>
namespace psemek::pcg
{
namespace detail
{
template <typename T, std::size_t N, typename F, typename G>
T perlin_impl(geom::vector<T, N> p, int grid_size, F && grid_at, G && bound)
{
geom::vector<int, N> ip;
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 = bound(ip);
geom::vector<T, N> t = (p - geom::cast<T>(ip) * T(grid_size)) / T(grid_size);
T values[1 << N];
for (std::size_t mask = 0; mask < (1 << N); ++mask)
{
geom::vector<T, N> tt;
geom::vector<int, N> ii;
for (std::size_t i = 0; i < N; ++i)
{
bool m = (mask & (1 << i));
tt[i] = m ? t[i] - 1 : t[i];
ii[i] = m ? ip[i] + 1 : ip[i];
}
values[mask] = geom::dot(grid_at(ii), tt);
}
auto smootherstep = [](float x0, float x1, float t)
{
auto const s = t * t * t * (10. + t * (-15. + 6. * t));
return x0 * (1 - s) + x1 * s;
};
for (std::size_t i = N; i --> 0;)
{
for (std::size_t mask = 0; mask < (1 << i); ++mask)
values[mask] = smootherstep(values[mask], values[mask | (1 << i)], t[i]);
}
return 0.5 * (1. + std::sqrt(2.) * values[0]);
}
}
template <typename T, std::size_t N>
struct lazy_perlin
{
@ -17,7 +66,7 @@ namespace psemek::pcg
using value_type = T;
static constexpr auto dimension = N;
lazy_perlin (std::size_t grid_size, generator_func generator)
lazy_perlin(std::size_t grid_size, generator_func generator)
: grid_size_(grid_size)
, gen_(std::move(generator))
{}
@ -39,44 +88,67 @@ namespace psemek::pcg
};
template <typename T, std::size_t N>
T lazy_perlin<T, N>::operator()(geom::vector<T, N> p) const
struct bounded_lazy_perlin
{
geom::vector<int, N> ip;
for (std::size_t i = 0; i < N; ++i)
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))
{
ip[i] = (p[i] >= 0) ? (p[i] / grid_size_) : -((- p[i] + grid_size_ - 1) / grid_size_);
}
std::array<std::size_t, N> dims;
geom::vector<T, N> t = (p - geom::cast<T>(ip) * T(grid_size_)) / T(grid_size_);
T values[1 << N];
for (std::size_t mask = 0; mask < (1 << N); ++mask)
{
geom::vector<T, N> tt;
geom::vector<int, N> ii;
for (std::size_t i = 0; i < N; ++i)
{
bool m = (mask & (1 << i));
tt[i] = m ? t[i] - 1 : t[i];
ii[i] = m ? ip[i] + 1 : ip[i];
origin_[i] = bounds[i].min / grid_size;
dims[i] = bounds[i].length() / grid_size + 1;
corner_[i] = origin_[i] + dims[i] - 2;
}
values[mask] = geom::dot(grid_at(ii), tt);
grid_.resize(dims);
}
auto smootherstep = [](float x0, float x1, float t)
{
auto const s = t * t * t * (10. + t * (-15. + 6. * t));
return x0 * (1 - s) + x1 * s;
};
T operator()(geom::vector<T, N> p) const;
for (std::size_t i = N; i --> 0;)
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
{
for (std::size_t mask = 0; mask < (1 << i); ++mask)
values[mask] = smootherstep(values[mask], values[mask | (1 << i)], t[i]);
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;
}
};
return 0.5 * (1. + std::sqrt(2.) * values[0]);
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;
});
}
}