From 48d6a007a2cbf658aa6125ff49556cb66f945525 Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Tue, 28 Jul 2015 18:14:10 +1000 Subject: [PATCH] n/b/patch: add blur width parameter --- noise/basis/patch.hpp | 12 ++- noise/basis/patch.ipp | 226 +++++++++++++++++++++++++++--------------- tools/noise.cpp | 27 ++--- 3 files changed, 173 insertions(+), 92 deletions(-) diff --git a/noise/basis/patch.hpp b/noise/basis/patch.hpp index fb62cf33..995113cc 100644 --- a/noise/basis/patch.hpp +++ b/noise/basis/patch.hpp @@ -17,12 +17,13 @@ #ifndef __UTIL_NOISE_BASIS_PATCH_HPP #define __UTIL_NOISE_BASIS_PATCH_HPP +#include "../basis.hpp" #include "../../point.hpp" namespace util { namespace noise { namespace basis { template struct patch { - patch (seed_t); + patch (seed_t, T width = 0); range bounds (void) const; T operator() (point2) const; @@ -30,11 +31,18 @@ namespace util { namespace noise { namespace basis { seed_t seed (void) const; seed_t seed (seed_t); + T width (void) const; + T width (T); + private: point2 centroid (util::point2i) const; T generate (util::point2i) const; - seed_t m_seed; + static constexpr T THRESHOLD = 1 - T(0.999); + + T m_width; + T m_power; + seed_t m_seed; }; } } } diff --git a/noise/basis/patch.ipp b/noise/basis/patch.ipp index a63d6a2d..2d11712d 100644 --- a/noise/basis/patch.ipp +++ b/noise/basis/patch.ipp @@ -19,99 +19,169 @@ #endif #define __UTIL_NOISE_BASIS_PATCH_IPP - -/////////////////////////////////////////////////////////////////////////////// -template -util::noise::basis::patch::patch (seed_t _seed): - m_seed (_seed) -{ ; } +#include "../../types.hpp" +#include "../../ray.hpp" +#include "../../vector.hpp" -/////////////////////////////////////////////////////////////////////////////// -template -util::range -util::noise::basis::patch::bounds (void) const -{ - return { T{0}, T{1} }; -} +namespace util { namespace noise { namespace basis { + /////////////////////////////////////////////////////////////////////////// + template + patch::patch (seed_t _seed, T _width): + m_width (_width), + m_power (exactly_zero (_width) + ? std::numeric_limits::infinity () + : std::log (THRESHOLD) / std::log (1 - _width)), + m_seed (_seed) + { ; } -/////////////////////////////////////////////////////////////////////////////// -template -T -util::noise::basis::patch::operator () (point2 p) const -{ - // extract integer and fractional parts. be careful to always round down - // (particularly with negatives) and avoid rounding errors. - auto p_int = p.template cast (); - if (p.x < 0) p_int.x -= 1; - if (p.y < 0) p_int.y -= 1; - auto p_rem = (p - p_int).template as (); + /////////////////////////////////////////////////////////////////////////// + template + range + patch::bounds (void) const + { + return { T{0}, T{1} }; + } - T closest = std::numeric_limits::infinity (); - T value; - for (signed y = -1; y <= 1; ++y) - for (signed x = -1; x <= 1; ++x) { - util::vector2i offset {x, y}; - auto c = centroid (p_int + offset); - auto d = util::distance (p_rem, c + offset); + /////////////////////////////////////////////////////////////////////////// + template + T + patch::operator () (point2 p) const + { + // extract integer and fractional parts. be careful to always round down + // (particularly with negatives) and avoid rounding errors. + auto p_int = p.template cast (); + if (p.x < 0) p_int.x -= 1; + if (p.y < 0) p_int.y -= 1; + auto p_rem = (p - p_int).template as (); - if (d < closest) { - closest = d; - value = generate (p_int + offset); - } + static const util::vector2i OFFSETS[] = { + { 0, -2 }, + { -1, -1 }, { 0, -1 }, { 1, -1 }, + { -2, 0 }, { -1, 0 }, { 0, 0 }, { 1, 0 }, { 2, 0 }, + { -1, 1 }, { 0, 1 }, { 1, 1 }, + { 0, 2 }, + }; + + static const size_t COUNT = elems (OFFSETS); + + // find the distances to each neighbour's centroid + util::point2 centres[COUNT]; + for (size_t i = 0; i < COUNT; ++i) + centres[i] = centroid (p_int + OFFSETS[i]) + OFFSETS[i]; + + T distances[COUNT]; + for (size_t i = 0; i < COUNT; ++i) + distances[i] = util::distance (p_rem, centres[i]); + + // sort the distances, using indices so we can use 'offsets' to generate values + unsigned indices[COUNT]; + std::iota (std::begin (indices), std::end (indices), 0); + + std::sort (std::begin (indices), + std::end (indices), + [&] (auto a, auto b) { + return distances[a] < distances[b]; + }); + + // calculate normalisation constants for the 9 nearest points. the + // neighbourhood size is implicitly specified by the 1.5 unit maximum + // distance. + constexpr auto MAX_DISTANCE = std::hypot (1.5f, 1.5f); + const auto lo = distances[indices[0]]; + const auto hi = std::min (distances[indices[COUNT-1]], MAX_DISTANCE); + + T out = 0.f; + T sumw = 0.f; + + // sum the weight values of each neighbour. weight by a function of + // the distance. we use an power function which allows a known width + // to blend. + for (size_t i = 0; i < COUNT && distances[indices[i]] <= MAX_DISTANCE; ++i) + { + auto v = generate (p_int + OFFSETS[indices[i]]); + auto d = (distances[indices[i]] - lo) / (hi - lo); + auto w = std::pow (1 - d, m_power); + + sumw += w; + out += v * w; } - return value; -} + return out / sumw; + } -/////////////////////////////////////////////////////////////////////////////// -template -util::noise::seed_t -util::noise::basis::patch::seed (void) const -{ - return m_seed; -} + /////////////////////////////////////////////////////////////////////////// + template + seed_t + patch::seed (void) const + { + return m_seed; + } -//----------------------------------------------------------------------------- -template -util::noise::seed_t -util::noise::basis::patch::seed (util::noise::seed_t _seed) -{ - return m_seed = _seed; -} + //------------------------------------------------------------------------- + template + seed_t + patch::seed (util::noise::seed_t _seed) + { + return m_seed = _seed; + } -/////////////////////////////////////////////////////////////////////////////// -template -util::point2 -util::noise::basis::patch::centroid (util::point2i p) const -{ - using util::hash::murmur2::mix; - - auto u = mix (m_seed, mix (uint64_t (p.x), uint64_t (p.y))); - auto v = mix (u, m_seed); - - auto r = util::point<2,T> { - (u & 0xffff) / T{0xffff}, - (v & 0xffff) / T{0xffff} - }; - - CHECK_LIMIT (r, T{0}, T{1}); - return r; -} + /////////////////////////////////////////////////////////////////////////// + template + T + patch::width (void) const + { + return m_width; + } -//----------------------------------------------------------------------------- -template -T -util::noise::basis::patch::generate (util::point2i p) const -{ - using util::hash::murmur2::mix; + //------------------------------------------------------------------------- + template + T + patch::width (T _width) + { + m_width = _width; + m_power = exactly_zero (_width) + ? std::numeric_limits::infinity () + : std::log (THRESHOLD) / std::log (1 - _width); - auto u = mix (m_seed, mix (uint64_t (p.x), uint64_t (p.y))); - return (u & 0xffff) / T{0xffff}; -} + return m_width; + } + + + /////////////////////////////////////////////////////////////////////////// + template + util::point2 + patch::centroid (util::point2i p) const + { + using util::hash::murmur2::mix; + + auto u = mix (m_seed, mix (uint64_t (p.x), uint64_t (p.y))); + auto v = mix (u, m_seed); + + auto r = util::point<2,T> { + (u & 0xffff) / T{0xffff}, + (v & 0xffff) / T{0xffff} + }; + + CHECK_LIMIT (r, T{0}, T{1}); + return r; + } + + + //------------------------------------------------------------------------- + template + T + patch::generate (util::point2i p) const + { + using util::hash::murmur2::mix; + + auto u = mix (m_seed, mix (uint64_t (p.x), uint64_t (p.y))); + return (u & 0xffff) / T{0xffff}; + } +} } } diff --git a/tools/noise.cpp b/tools/noise.cpp index 72eeb21a..d7e21dbd 100644 --- a/tools/noise.cpp +++ b/tools/noise.cpp @@ -209,20 +209,22 @@ main (int argc, char **argv) float scale = 1.f; float turbulence = 0.f; unsigned single = 0; + float width = 0; // fill variables from arguments util::cmdopt::parser args; - args.add> ('w', "width", "output image width", res.w); - args.add> ('h', "height", "output image height", res.h); - args.add> ('s', "seed", "random seed", seed); - args.add> ('b', "basis", "primary basis function", basis); - args.add> ('f', "fractal", "primary fractal function", fractal); - args.add> ('l', "lerp", "interpolation algorithm", lerp); - args.add> ('o', "octaves", "total fractal iterations", octaves); - args.add> ('1', "single", "single octave", single); - args.add> ('H', "hurst", "Hurst exponent", H); - args.add> ('x', "scale", "frequency multiplier", scale); - args.add> ('t', "turbulence","turbulence scale", turbulence); + args.add> ('w', "width", "output image width", res.w); + args.add> ('h', "height", "output image height", res.h); + args.add> ('s', "seed", "random seed", seed); + args.add> ('b', "basis", "primary basis function", basis); + args.add> ('f', "fractal", "primary fractal function", fractal); + args.add> ('l', "lerp", "interpolation algorithm", lerp); + args.add> ('o', "octaves", "total fractal iterations", octaves); + args.add> ('1', "single", "single octave", single); + args.add> ('H', "hurst", "Hurst exponent", H); + args.add> ('x', "scale", "frequency multiplier", scale); + args.add> ('t', "turbulence", "turbulence scale", turbulence); + args.add> ('W', "patch-width", "patch blur width", width); args.scan (argc, argv); @@ -293,7 +295,7 @@ main (int argc, char **argv) } case PATCH: { - b.reset> (seed); + b.reset> (seed, width); break; } @@ -343,6 +345,7 @@ main (int argc, char **argv) auto offset = *range.first; auto div = *range.second - *range.first; + std::cerr << '[' << *range.first << ',' << *range.second << "]\n"; std::transform (img.begin (), img.end (), img.begin (), [offset,div] (auto i) { return (i - offset) / div; }); // write the images to disk