noise: use points instead of scalars in eval

This commit is contained in:
Danny Robson 2015-05-28 10:17:19 +10:00
parent b96ad81d32
commit c9812f7c78
6 changed files with 84 additions and 117 deletions

View File

@ -34,7 +34,7 @@ util::noise::fill (image::buffer<T> &pixels,
for (size_t y = 0; y < h; ++y)
for (size_t x = 0; x < w; ++x)
data[y * s + x] = gen (x, y);
data[y * s + x] = gen ({T(x), T(y)});
}
template void util::noise::fill (image::buffer<float>&, const util::noise::fractal<float>&);
@ -49,7 +49,7 @@ util::noise::image2d (uint8_t *restrict pixels,
const util::noise::fractal<float> &p) {
for (size_t y = 0; y < height; ++y)
for (size_t x = 0; x < width; ++x) {
double v = p (x, y);
float v = p ({float(x), float(y)});
pixels[x + y * width] = static_cast<uint8_t> (v * std::numeric_limits<uint8_t>::max ());
}
}

View File

@ -35,11 +35,11 @@ using util::noise::cellular;
// Generate a type from [-UNIT..UNIT]
template <typename T>
T
generate (intmax_t x, intmax_t y, uint64_t seed)
gen_scalar (util::point<2,T> p, uint64_t seed)
{
using util::hash::murmur2::mix;
T v = mix (seed, mix (uint64_t (y), uint64_t (x))) & 0xffff;
T v = mix (seed, mix (uint64_t (p.y), uint64_t (p.x))) & 0xffff;
v = v / T{0xffff} * 2 - 1;
CHECK_GE (v, T{0});
@ -50,43 +50,22 @@ generate (intmax_t x, intmax_t y, uint64_t seed)
//-----------------------------------------------------------------------------
template <>
util::vector2d
generate (intmax_t x, intmax_t y, uint64_t seed)
template <typename T>
util::vector<2,T>
gen_vector (util::point<2,T> p, uint64_t seed)
{
using util::hash::murmur2::mix;
auto u = mix (seed, mix (uint64_t (x), uint64_t (y)));
auto u = mix (seed, mix (uint64_t (p.x), uint64_t (p.y)));
auto v = mix (u, seed);
auto r = util::vector2d {
(u & 0xffff) / double{0xffff},
(v & 0xffff) / double{0xffff}
} * 2.0 - 1.0;
auto r = util::vector<2,T> {
(u & 0xffff) / T{0xffff},
(v & 0xffff) / T{0xffff}
} * 2 - 1;
CHECK_GE (r, double{-1});
CHECK_LE (r, double{ 1});
return r;
}
//-----------------------------------------------------------------------------
template <>
util::vector2f
generate (intmax_t x, intmax_t y, uint64_t seed)
{
using util::hash::murmur2::mix;
auto u = mix (seed, mix (uint64_t (x), uint64_t (y)));
auto v = mix (u, seed);
auto r = util::vector2f {
(u & 0xffff) / float{0xffff},
(v & 0xffff) / float{0xffff}
} * 2.f - 1.f;
CHECK_GE (r, float (-1));
CHECK_LE (r, float ( 1));
CHECK_GE (r, T{-1});
CHECK_LE (r, T{ 1});
return r;
}
@ -115,7 +94,7 @@ basis<T>::~basis ()
//-----------------------------------------------------------------------------
template <typename T>
T
basis<T>::operator() (T, T) const
basis<T>::operator() (util::point<2,T>) const
{
unreachable ();
}
@ -153,29 +132,27 @@ value<T,L>::bounds (void) const
//-----------------------------------------------------------------------------
template <typename T, util::noise::lerp_t<T> L>
T
value<T,L>::operator() (T x, T y) const
value<T,L>::operator() (util::point<2,T> p) const
{
intmax_t x_int = static_cast<intmax_t> (x);
intmax_t y_int = static_cast<intmax_t> (y);
T x_fac = x - x_int;
T y_fac = y - y_int;
auto p_int = p.template cast<intmax_t> ();
auto p_rem = p - p_int;
// Shift the coordinate system down a little to ensure we get unit weights
// for the lerp. It's better to do this than abs the fractional portion so
// we don't get reflections along the origin.
if (x < 0) { x_fac = 1 + x_fac; x_int -= 1; }
if (y < 0) { y_fac = 1 + y_fac; y_int -= 1; }
if (p.x < 0) { p_rem.x = 1 + p_rem.x; p_int.x -= 1; }
if (p.y < 0) { p_rem.y = 1 + p_rem.y; p_int.y -= 1; }
// Generate the four corner values
T p0 = generate<T> (x_int, y_int, this->seed);
T p1 = generate<T> (x_int + 1, y_int, this->seed);
T p2 = generate<T> (x_int, y_int + 1, this->seed);
T p3 = generate<T> (x_int + 1, y_int + 1, this->seed);
T p0 = gen_scalar<T> (p_int + util::vector<2,T>{ 0, 0 }, this->seed);
T p1 = gen_scalar<T> (p_int + util::vector<2,T>{ 1, 0 }, this->seed);
T p2 = gen_scalar<T> (p_int + util::vector<2,T>{ 0, 0 }, this->seed);
T p3 = gen_scalar<T> (p_int + util::vector<2,T>{ 1, 1 }, this->seed);
// Interpolate on one dimension, then the other.
return L (L (p0, p1, x_fac),
L (p2, p3, x_fac),
y_fac);
return L (L (p0, p1, p_rem.x),
L (p2, p3, p_rem.x),
p_rem.y);
}
@ -216,35 +193,33 @@ gradient<T,L>::bounds (void) const
//-----------------------------------------------------------------------------
template <typename T, util::noise::lerp_t<T> L>
T
gradient<T,L>::operator() (T x, T y) const
gradient<T,L>::operator() (util::point<2,T> p) const
{
intmax_t x_int = static_cast<intmax_t> (x);
intmax_t y_int = static_cast<intmax_t> (y);
T x_fac = x - x_int;
T y_fac = y - y_int;
auto p_int = p.template cast<intmax_t> ();
auto p_rem = p - p_int;
// Shift the coordinate system down a little to ensure we get unit weights
// for the lerp. It's better to do this than abs the fractional portion so
// we don't get reflections along the origin.
if (x < 0) { x_fac = 1 + x_fac; x_int -= 1; }
if (y < 0) { y_fac = 1 + y_fac; y_int -= 1; }
if (p.x < 0) { p_rem.x = 1 + p_rem.x; p_int.x -= 1; }
if (p.y < 0) { p_rem.y = 1 + p_rem.y; p_int.y -= 1; }
// Generate the four corner values. It's not strictly necessary to
// normalise the values, but we get a more consistent and visually
// appealing range of outputs with normalised values.
auto p0 = generate<vector<2,T>> (x_int, y_int, this->seed).normalise ();
auto p1 = generate<vector<2,T>> (x_int + 1, y_int, this->seed).normalise ();
auto p2 = generate<vector<2,T>> (x_int, y_int + 1, this->seed).normalise ();
auto p3 = generate<vector<2,T>> (x_int + 1, y_int + 1, this->seed).normalise ();
auto p0 = gen_vector<T> (p_int + util::vector<2,T> { 0, 0 }, this->seed).normalise ();
auto p1 = gen_vector<T> (p_int + util::vector<2,T> { 1, 0 }, this->seed).normalise ();
auto p2 = gen_vector<T> (p_int + util::vector<2,T> { 0, 1 }, this->seed).normalise ();
auto p3 = gen_vector<T> (p_int + util::vector<2,T> { 1, 1 }, this->seed).normalise ();
T v0 = p0.x * x_fac + p0.y * y_fac;
T v1 = p1.x * (x_fac - 1) + p1.y * y_fac;
T v2 = p2.x * x_fac + p2.y * (y_fac - 1);
T v3 = p3.x * (x_fac - 1) + p3.y * (y_fac - 1);
T v0 = p0.x * p_rem.x + p0.y * p_rem.y;
T v1 = p1.x * (p_rem.x - 1) + p1.y * p_rem.y;
T v2 = p2.x * p_rem.x + p2.y * (p_rem.y - 1);
T v3 = p3.x * (p_rem.x - 1) + p3.y * (p_rem.y - 1);
auto L0 = L (v0, v1, x_fac);
auto L1 = L (v2, v3, x_fac);
auto L_ = L (L0, L1, y_fac);
auto L0 = L (v0, v1, p_rem.x);
auto L1 = L (v2, v3, p_rem.x);
auto L_ = L (L0, L1, p_rem.y);
return L_;
}
@ -286,18 +261,16 @@ cellular<T>::bounds (void) const
//-----------------------------------------------------------------------------
template <typename T>
T
cellular<T>::operator() (T x, T y) const
cellular<T>::operator() (util::point<2,T> p) const
{
intmax_t x_int = static_cast<intmax_t> (x);
intmax_t y_int = static_cast<intmax_t> (y);
T x_fac = x - x_int;
T y_fac = y - y_int;
auto p_int = p.template cast<intmax_t> ();
auto p_rem = p - p_int;
// Generate the four corner values. It's not strictly necessary to
// normalise the values, but we get a more consistent and visually
// appealing range of outputs with normalised values.
if (x < 0) { x_fac = 1 + x_fac; x_int -= 1; }
if (y < 0) { y_fac = 1 + y_fac; y_int -= 1; }
if (p.x < 0) { p_rem.x = 1 + p_rem.x; p_int.x -= 1; }
if (p.y < 0) { p_rem.y = 1 + p_rem.y; p_int.y -= 1; }
// +---+---+---+
// | 0 | 1 | 2 |
@ -307,14 +280,13 @@ cellular<T>::operator() (T x, T y) const
// | 6 | 7 | 8 |
// +---+---+---+
point<2,T> centre = { x_fac, y_fac };
T distances[9] = { std::numeric_limits<T>::quiet_NaN () };
T *cursor = distances;
for (signed y_off = -1; y_off <= 1 ; ++y_off)
for (signed x_off = -1; x_off <= 1; ++x_off) {
auto pos = point<2,T> (T (x_off), T (y_off));
auto off = generate<vector<2,T>> (x_int + x_off, y_int + y_off, this->seed);
auto pos = vector<2,T> (T (x_off), T (y_off));
auto off = gen_vector<T> (p_int + pos, this->seed);
off += T{1};
off /= T{2};
@ -322,7 +294,7 @@ cellular<T>::operator() (T x, T y) const
CHECK (off.y >= 0 && off.y <= 1);
pos += off;
*cursor++ = pos.distance2 (centre);
*cursor++ = pos.difference2 (p_rem);
}
std::sort (std::begin (distances), std::end (distances));

View File

@ -19,6 +19,7 @@
#include <cstdlib>
#include "lerp.hpp"
#include "../point.hpp"
#include "../range.hpp"
namespace util { namespace noise {
@ -36,7 +37,7 @@ namespace util { namespace noise {
seed_t seed;
virtual range<T> bounds (void) const = 0;
virtual T operator() (T x, T y) const = 0;
virtual T operator() (util::point<2,T>) const = 0;
};
@ -49,7 +50,7 @@ namespace util { namespace noise {
value ();
virtual range<T> bounds (void) const final;
virtual T operator() (T x, T y) const final;
virtual T operator() (util::point<2,T>) const final;
};
@ -62,7 +63,7 @@ namespace util { namespace noise {
gradient ();
virtual range<T> bounds (void) const final;
virtual T operator() (T x, T y) const final;
virtual T operator() (util::point<2,T>) const final;
};
@ -75,7 +76,7 @@ namespace util { namespace noise {
cellular ();
virtual range<T> bounds (void) const final;
virtual T operator() (T x, T y) const final;
virtual T operator() (util::point<2,T>) const final;
};
} }

View File

@ -52,7 +52,7 @@ fractal<T>::~fractal ()
//-----------------------------------------------------------------------------
template <typename T>
T
fractal<T>::operator() (T, T) const
fractal<T>::operator() (util::point<2,T>) const
{
unreachable ();
}
@ -102,13 +102,13 @@ fbm<T,B>::fbm ():
//-----------------------------------------------------------------------------
template <typename T, typename B>
T
fbm<T,B>::operator() (T x, T y) const {
fbm<T,B>::operator() (util::point<2,T> p) const {
T total = 0;
T f = frequency;
T a = amplitude;
for (size_t i = 0; i < octaves; ++i) {
total += basis (x * f, y * f) * a;
total += basis (p * f) * a;
f *= lacunarity;
a *= gain;
@ -167,7 +167,7 @@ rmf<T,B>::rmf ():
//-----------------------------------------------------------------------------
template <typename T, typename B>
T
rmf<T,B>::operator() (T x, T y) const {
rmf<T,B>::operator() (util::point<2,T> p) const {
const T offset = 1;
const T H = 1.f;
@ -179,12 +179,11 @@ rmf<T,B>::operator() (T x, T y) const {
T result = 0;
T weight = 1;
x *= frequency;
y *= frequency;
p *= frequency;
for (size_t i = 0; i < octaves; ++i) {
// generates ridged noise
signal = basis (x, y);
signal = basis (p);
signal = std::fabs (signal);
signal = offset - signal;
@ -201,8 +200,7 @@ rmf<T,B>::operator() (T x, T y) const {
// record and continue
result += signal * exponents[i];
x *= lacunarity;
y *= lacunarity;
p *= lacunarity;
}
return result;
@ -239,7 +237,7 @@ hmf<T,B>::hmf ():
//-----------------------------------------------------------------------------
template <typename T, typename B>
T
hmf<T,B>::operator() (T x, T y) const
hmf<T,B>::operator() (util::point<2,T> p) const
{
T exponents[octaves];
for (size_t i = 0; i < octaves; ++i)
@ -249,19 +247,17 @@ hmf<T,B>::operator() (T x, T y) const
T signal = 0;
T weight = 1;
x *= frequency;
y *= frequency;
p *= frequency;
for (size_t i = 0; i < octaves; ++i) {
signal = (basis (x, y) + offset) * exponents[i];
signal = (basis (p) + offset) * exponents[i];
result += weight * signal;
weight *= gain * signal;
if (weight > 1)
weight = 1;
x *= lacunarity;
y *= lacunarity;
p *= lacunarity;
}
return result;
@ -291,7 +287,7 @@ hetero<T,B>::hetero():
//-----------------------------------------------------------------------------
template <typename T, typename B>
T
hetero<T,B>::operator() (T x, T y) const
hetero<T,B>::operator() (util::point<2,T> p) const
{
T exponents[octaves];
for (size_t i = 0; i < octaves; ++i)
@ -300,23 +296,18 @@ hetero<T,B>::operator() (T x, T y) const
T result = 0;
T increment = 0;
x *= frequency;
y *= frequency;
result = basis (x, y) + offset;
x *= lacunarity;
y *= lacunarity;
p *= frequency;
result = basis (p) + offset;
p *= lacunarity;
for (size_t i = 0; i < octaves; ++i) {
increment = basis (x, y) + offset;
increment = basis (p) + offset;
increment *= exponents[i];
increment *= result;
result += increment;
x *= lacunarity;
y *= lacunarity;
p *= lacunarity;
}
return result;

View File

@ -30,7 +30,7 @@ namespace util {
fractal ();
virtual ~fractal ();
virtual T operator() (T x, T y) const = 0;
virtual T operator() (util::point<2,T>) const = 0;
seed_t seed;
};
@ -73,7 +73,7 @@ namespace util {
T gain;
B basis;
virtual T operator() (T x, T y) const override;
virtual T operator() (util::point<2,T>) const override;
};
@ -109,7 +109,7 @@ namespace util {
T gain;
B basis;
virtual T operator() (T x, T y) const override;
virtual T operator() (util::point<2,T>) const override;
};
@ -133,7 +133,7 @@ namespace util {
B basis;
virtual T operator() (T x, T y) const override;
virtual T operator() (util::point<2,T>) const override;
};
@ -158,7 +158,7 @@ namespace util {
B basis;
virtual T operator() (T x, T y) const override;
virtual T operator() (util::point<2,T>) const override;
};
}
}

View File

@ -12,15 +12,18 @@ main (void)
// setup the noise generator
//util::noise::fbm<float, util::noise::cellular<float>> b;
util::noise::rmf<float, util::noise::gradient<float,util::lerp::quintic>> b;
b.octaves = 3;
//util::noise::rmf<float, util::noise::cellular<float>> b;
//util::noise::hmf<float, util::noise::gradient<float,util::lerp::quintic>> b;
util::noise::hetero<float, util::noise::gradient<float,util::lerp::quintic>> b;
//b.octaves = 3;
b.frequency = 10.f / size.w;
b.lacunarity = 2;
b.basis.seed = time (NULL);
// generate the values. offset positions slightly to avoid simple axis issues with perlin basis
for (size_t y = 0; y < size.h; ++y)
for (size_t x = 0; x < size.w; ++x) {
auto v = b (x + 20, y + 20);
auto v = b ({float(x + 20), float(y + 20)});
img.data ()[y * size.w + x] = v;
}