Moved noise and SDF range utilities to util/
This commit is contained in:
parent
b8e8ba84b0
commit
239a87a6e9
7
SCsub
7
SCsub
@ -32,10 +32,17 @@ voxel_files = [
|
||||
"generators/simple/*.cpp",
|
||||
|
||||
"util/*.cpp",
|
||||
|
||||
"util/math/*.cpp",
|
||||
|
||||
"util/godot/*.cpp",
|
||||
|
||||
#"util/noise/*.cpp",
|
||||
"util/noise/fast_noise_lite.cpp",
|
||||
"util/noise/fast_noise_lite_gradient.cpp",
|
||||
"util/noise/fast_noise_lite_range.cpp",
|
||||
"util/noise/osn_noise_range.cpp",
|
||||
|
||||
"util/tasks/*.cpp",
|
||||
|
||||
"terrain/*.cpp",
|
||||
|
@ -1,117 +1,13 @@
|
||||
#include "range_utility.h"
|
||||
#include "../../util/math/sdf.h"
|
||||
#include "../../util/noise/fast_noise_lite.h"
|
||||
|
||||
#include <core/io/image.h>
|
||||
#include <modules/opensimplex/open_simplex_noise.h>
|
||||
#include <scene/resources/curve.h>
|
||||
|
||||
namespace zylann {
|
||||
|
||||
using namespace math;
|
||||
|
||||
// TODO We could skew max derivative estimation if the anchor is on a bump or a dip
|
||||
// because in these cases, it becomes impossible for noise to go further up or further down
|
||||
|
||||
template <typename Noise_F>
|
||||
inline Interval get_noise_range_2d(Noise_F noise_func, const Interval &x, const Interval &y, float max_derivative) {
|
||||
// Any unit vector away from a given evaluation point, the maximum difference is a fixed number.
|
||||
// We can use that number to find a bounding range within our rectangular interval.
|
||||
const float max_derivative_half_diagonal = 0.5f * max_derivative * Math_SQRT2;
|
||||
|
||||
const float mid_x = 0.5 * (x.min + x.max);
|
||||
const float mid_y = 0.5 * (y.min + y.max);
|
||||
const float mid_value = noise_func(mid_x, mid_y);
|
||||
|
||||
const float diag = Math::sqrt(squared(x.length()) + squared(y.length()));
|
||||
|
||||
return Interval( //
|
||||
max(mid_value - max_derivative_half_diagonal * diag, -1.f),
|
||||
min(mid_value + max_derivative_half_diagonal * diag, 1.f));
|
||||
}
|
||||
|
||||
template <typename Noise_F>
|
||||
inline Interval get_noise_range_3d(
|
||||
Noise_F noise_func, const Interval &x, const Interval &y, const Interval &z, float max_derivative) {
|
||||
const float max_derivative_half_diagonal = 0.5f * max_derivative * Math_SQRT2;
|
||||
|
||||
const float mid_x = 0.5 * (x.min + x.max);
|
||||
const float mid_y = 0.5 * (y.min + y.max);
|
||||
const float mid_z = 0.5 * (z.min + z.max);
|
||||
const float mid_value = noise_func(mid_x, mid_y, mid_z);
|
||||
|
||||
const float diag = Math::sqrt(squared(x.length()) + squared(y.length()) + squared(z.length()));
|
||||
|
||||
return Interval( //
|
||||
max(mid_value - max_derivative_half_diagonal * diag, -1.f),
|
||||
min(mid_value + max_derivative_half_diagonal * diag, 1.f));
|
||||
}
|
||||
|
||||
Interval get_osn_octave_range_2d(OpenSimplexNoise *noise, const Interval &p_x, const Interval &p_y, int octave) {
|
||||
return get_noise_range_2d(
|
||||
[octave, noise](float x, float y) { return noise->_get_octave_noise_2d(octave, x, y); }, p_x, p_y, 2.35f);
|
||||
}
|
||||
|
||||
Interval get_osn_octave_range_3d(
|
||||
OpenSimplexNoise *noise, const Interval &p_x, const Interval &p_y, const Interval &p_z, int octave) {
|
||||
return get_noise_range_3d(
|
||||
[octave, noise](float x, float y, float z) { return noise->_get_octave_noise_3d(octave, x, y, z); }, p_x,
|
||||
p_y, p_z, 2.5f);
|
||||
}
|
||||
|
||||
Interval get_osn_range_2d(OpenSimplexNoise *noise, Interval x, Interval y) {
|
||||
// Same implementation as `get_noise_2d`
|
||||
|
||||
if (x.is_single_value() && y.is_single_value()) {
|
||||
return Interval::from_single_value(noise->get_noise_2d(x.min, y.min));
|
||||
}
|
||||
|
||||
x /= noise->get_period();
|
||||
y /= noise->get_period();
|
||||
|
||||
float amp = 1.0;
|
||||
float max = 1.0;
|
||||
Interval sum = get_osn_octave_range_2d(noise, x, y, 0);
|
||||
|
||||
int i = 0;
|
||||
while (++i < noise->get_octaves()) {
|
||||
x *= noise->get_lacunarity();
|
||||
y *= noise->get_lacunarity();
|
||||
amp *= noise->get_persistence();
|
||||
max += amp;
|
||||
sum += get_osn_octave_range_2d(noise, x, y, i) * amp;
|
||||
}
|
||||
|
||||
return sum / max;
|
||||
}
|
||||
|
||||
Interval get_osn_range_3d(OpenSimplexNoise *noise, Interval x, Interval y, Interval z) {
|
||||
// Same implementation as `get_noise_3d`
|
||||
|
||||
if (x.is_single_value() && y.is_single_value()) {
|
||||
return Interval::from_single_value(noise->get_noise_2d(x.min, y.min));
|
||||
}
|
||||
|
||||
x /= noise->get_period();
|
||||
y /= noise->get_period();
|
||||
z /= noise->get_period();
|
||||
|
||||
float amp = 1.0;
|
||||
float max = 1.0;
|
||||
Interval sum = get_osn_octave_range_3d(noise, x, y, z, 0);
|
||||
|
||||
int i = 0;
|
||||
while (++i < noise->get_octaves()) {
|
||||
x *= noise->get_lacunarity();
|
||||
y *= noise->get_lacunarity();
|
||||
z *= noise->get_lacunarity();
|
||||
amp *= noise->get_persistence();
|
||||
max += amp;
|
||||
sum += get_osn_octave_range_3d(noise, x, y, z, i) * amp;
|
||||
}
|
||||
|
||||
return sum / max;
|
||||
}
|
||||
// Curve ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void get_curve_monotonic_sections(Curve &curve, std::vector<CurveMonotonicSection> §ions) {
|
||||
const int res = curve.get_bake_resolution();
|
||||
@ -221,6 +117,8 @@ Interval get_curve_range(Curve &curve, bool &is_monotonic_increasing) {
|
||||
return range;
|
||||
}
|
||||
|
||||
// Heightmaps //////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Interval get_heightmap_range(const Image &im) {
|
||||
return get_heightmap_range(im, Rect2i(0, 0, im.get_width(), im.get_height()));
|
||||
}
|
||||
@ -246,448 +144,4 @@ Interval get_heightmap_range(const Image &im, Rect2i rect) {
|
||||
return r;
|
||||
}
|
||||
|
||||
namespace math {
|
||||
|
||||
SdfAffectingArguments sdf_subtract_side(Interval a, Interval b) {
|
||||
if (b.min > -a.min) {
|
||||
return SDF_ONLY_A;
|
||||
}
|
||||
if (b.max < -a.max) {
|
||||
return SDF_ONLY_B;
|
||||
}
|
||||
return SDF_BOTH;
|
||||
}
|
||||
|
||||
SdfAffectingArguments sdf_polynomial_smooth_subtract_side(Interval a, Interval b, float s) {
|
||||
// | \ \ \ |
|
||||
// ---1---x--x--x-------3--- b.max
|
||||
// | \ \ \ |
|
||||
// | \ \ \ | (b)
|
||||
// | \ \ \ | y
|
||||
// | \ \ \ | |
|
||||
// ---0--------x--x--x--2--- b.min o---x (a)
|
||||
// | \s \ s\ |
|
||||
// a.min a.max
|
||||
|
||||
if (b.min > -a.min + s) {
|
||||
return SDF_ONLY_B;
|
||||
}
|
||||
if (b.max < -a.max - s) {
|
||||
return SDF_ONLY_A;
|
||||
}
|
||||
return SDF_BOTH;
|
||||
}
|
||||
|
||||
SdfAffectingArguments sdf_union_side(Interval a, Interval b) {
|
||||
if (a.max < b.min) {
|
||||
return SDF_ONLY_A;
|
||||
}
|
||||
if (b.max < a.min) {
|
||||
return SDF_ONLY_B;
|
||||
}
|
||||
return SDF_BOTH;
|
||||
}
|
||||
|
||||
SdfAffectingArguments sdf_polynomial_smooth_union_side(Interval a, Interval b, float s) {
|
||||
if (a.max + s < b.min) {
|
||||
return SDF_ONLY_A;
|
||||
}
|
||||
if (b.max + s < a.min) {
|
||||
return SDF_ONLY_B;
|
||||
}
|
||||
return SDF_BOTH;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
inline Interval sdf_smooth_op(Interval b, Interval a, float s, F smooth_op_func) {
|
||||
// Smooth union and subtract are a generalization of `min(a, b)` and `max(-a, b)`, with a smooth junction.
|
||||
// That junction runs in a diagonal crossing zero (with equation `y = -x`).
|
||||
// Areas on the two sides of the junction are monotonic, i.e their derivatives should never cross zero,
|
||||
// because they are linear functions modified by a "smoothing" polynomial for which the tip is on diagonal.
|
||||
// So to find the output range, we can evaluate and sort the 4 corners,
|
||||
// and diagonal intersections if it crosses the area.
|
||||
|
||||
// | \ |
|
||||
// ---1---x-------------3--- b.max
|
||||
// | \ |
|
||||
// | \ | (b)
|
||||
// | \ | y
|
||||
// | \ | |
|
||||
// ---0--------x--------2--- b.min o---x (a)
|
||||
// | \ |
|
||||
// a.min a.max
|
||||
|
||||
const float v0 = smooth_op_func(b.min, a.min, s);
|
||||
const float v1 = smooth_op_func(b.max, a.min, s);
|
||||
const float v2 = smooth_op_func(b.min, a.max, s);
|
||||
const float v3 = smooth_op_func(b.max, a.max, s);
|
||||
|
||||
const Vector2 diag_b_min(-b.min, b.min);
|
||||
const Vector2 diag_b_max(-b.max, b.max);
|
||||
const Vector2 diag_a_min(a.min, -a.min);
|
||||
const Vector2 diag_a_max(a.max, -a.max);
|
||||
|
||||
const bool crossing_top = (diag_b_max.x > a.min && diag_b_max.x < a.max);
|
||||
const bool crossing_left = (diag_a_min.y > b.min && diag_a_min.y < b.max);
|
||||
|
||||
if (crossing_left || crossing_top) {
|
||||
const bool crossing_right = (diag_a_max.y > b.min && diag_a_max.y < b.max);
|
||||
|
||||
float v4;
|
||||
if (crossing_left) {
|
||||
v4 = smooth_op_func(diag_a_min.y, diag_a_min.x, s);
|
||||
} else {
|
||||
v4 = smooth_op_func(diag_b_max.y, diag_b_max.x, s);
|
||||
}
|
||||
|
||||
float v5;
|
||||
if (crossing_right) {
|
||||
v5 = smooth_op_func(diag_a_max.y, diag_a_max.x, s);
|
||||
} else {
|
||||
v5 = smooth_op_func(diag_b_min.y, diag_b_min.x, s);
|
||||
}
|
||||
|
||||
return Interval(min(v0, v1, v2, v3, v4, v5), max(v0, v1, v2, v3, v4, v5));
|
||||
}
|
||||
|
||||
// The diagonal does not cross the area
|
||||
return Interval(min(v0, v1, v2, v3), max(v0, v1, v2, v3));
|
||||
}
|
||||
|
||||
Interval sdf_smooth_union(Interval p_b, Interval p_a, float p_s) {
|
||||
// TODO Not tested
|
||||
// Had to use a lambda because otherwise it's ambiguous
|
||||
return sdf_smooth_op(
|
||||
p_b, p_a, p_s, [](float b, float a, float s) { return zylann::math::sdf_smooth_union(b, a, s); });
|
||||
}
|
||||
|
||||
Interval sdf_smooth_subtract(Interval p_b, Interval p_a, float p_s) {
|
||||
return sdf_smooth_op(
|
||||
p_b, p_a, p_s, [](float b, float a, float s) { return zylann::math::sdf_smooth_subtract(b, a, s); });
|
||||
}
|
||||
|
||||
} // namespace math
|
||||
|
||||
static Interval get_fnl_cellular_value_range_2d(const FastNoiseLite *noise, Interval x, Interval y) {
|
||||
const float c0 = noise->get_noise_2d(x.min, y.min);
|
||||
const float c1 = noise->get_noise_2d(x.max, y.min);
|
||||
const float c2 = noise->get_noise_2d(x.min, y.max);
|
||||
const float c3 = noise->get_noise_2d(x.max, y.max);
|
||||
if (c0 == c1 && c1 == c2 && c2 == c3) {
|
||||
return Interval::from_single_value(c0);
|
||||
}
|
||||
return Interval{ -1, 1 };
|
||||
}
|
||||
|
||||
static Interval get_fnl_cellular_value_range_3d(
|
||||
const fast_noise_lite::FastNoiseLite &fn, Interval x, Interval y, Interval z) {
|
||||
const float c0 = fn.GetNoise(x.min, y.min, z.min);
|
||||
const float c1 = fn.GetNoise(x.max, y.min, z.min);
|
||||
const float c2 = fn.GetNoise(x.min, y.max, z.min);
|
||||
const float c3 = fn.GetNoise(x.max, y.max, z.min);
|
||||
const float c4 = fn.GetNoise(x.max, y.max, z.max);
|
||||
const float c5 = fn.GetNoise(x.max, y.max, z.max);
|
||||
const float c6 = fn.GetNoise(x.max, y.max, z.max);
|
||||
const float c7 = fn.GetNoise(x.max, y.max, z.max);
|
||||
if (c0 == c1 && c1 == c2 && c2 == c3 && c3 == c4 && c4 == c5 && c5 == c6 && c6 == c7) {
|
||||
return Interval::from_single_value(c0);
|
||||
}
|
||||
return Interval{ -1, 1 };
|
||||
}
|
||||
|
||||
static Interval get_fnl_cellular_range(const FastNoiseLite *noise) {
|
||||
// There are many combinations with Cellular noise so instead of implementing them with intervals,
|
||||
// I used empiric tests to figure out some bounds.
|
||||
|
||||
// Value mode must be handled separately.
|
||||
|
||||
switch (noise->get_cellular_distance_function()) {
|
||||
case FastNoiseLite::CELLULAR_DISTANCE_EUCLIDEAN:
|
||||
switch (noise->get_cellular_return_type()) {
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE:
|
||||
return Interval{ -1.f, 0.08f };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2:
|
||||
return Interval{ -0.92f, 0.35 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_ADD:
|
||||
return Interval{ -0.92f, 0.1 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_SUB:
|
||||
return Interval{ -1, 0.15 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_MUL:
|
||||
return Interval{ -1, 0 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_DIV:
|
||||
return Interval{ -1, 0 };
|
||||
default:
|
||||
ERR_FAIL_V(Interval(-1, 1));
|
||||
}
|
||||
break;
|
||||
|
||||
case FastNoiseLite::CELLULAR_DISTANCE_EUCLIDEAN_SQ:
|
||||
switch (noise->get_cellular_return_type()) {
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE:
|
||||
return Interval{ -1, 0.2 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2:
|
||||
return Interval{ -1, 0.8 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_ADD:
|
||||
return Interval{ -1, 0.2 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_SUB:
|
||||
return Interval{ -1, 0.7 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_MUL:
|
||||
return Interval{ -1, 0 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_DIV:
|
||||
return Interval{ -1, 0 };
|
||||
default:
|
||||
ERR_FAIL_V(Interval(-1, 1));
|
||||
}
|
||||
|
||||
case FastNoiseLite::CELLULAR_DISTANCE_MANHATTAN:
|
||||
switch (noise->get_cellular_return_type()) {
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE:
|
||||
return Interval{ -1, 0.75 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2:
|
||||
return Interval{ -0.9, 0.8 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_ADD:
|
||||
return Interval{ -0.8, 0.8 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_SUB:
|
||||
return Interval{ -1.0, 0.5 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_MUL:
|
||||
return Interval{ -1.0, 0.7 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_DIV:
|
||||
return Interval{ -1.0, 0.0 };
|
||||
default:
|
||||
ERR_FAIL_V(Interval(-1, 1));
|
||||
}
|
||||
|
||||
case FastNoiseLite::CELLULAR_DISTANCE_HYBRID:
|
||||
switch (noise->get_cellular_return_type()) {
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE:
|
||||
return Interval{ -1, 1.75 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2:
|
||||
return Interval{ -0.9, 2.3 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_ADD:
|
||||
return Interval{ -0.9, 1.9 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_SUB:
|
||||
return Interval{ -1.0, 1.85 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_MUL:
|
||||
return Interval{ -1.0, 3.4 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_DIV:
|
||||
return Interval{ -1.0, 0.0 };
|
||||
default:
|
||||
ERR_FAIL_V(Interval(-1, 1));
|
||||
}
|
||||
}
|
||||
return Interval{ -1.f, 1.f };
|
||||
}
|
||||
|
||||
void fnl_transform_noise_coordinate(const fast_noise_lite::FastNoiseLite &fn, Interval &x, Interval &y, Interval &z) {
|
||||
// Same logic as in the FastNoiseLite internal function
|
||||
|
||||
x *= fn.mFrequency;
|
||||
y *= fn.mFrequency;
|
||||
z *= fn.mFrequency;
|
||||
|
||||
switch (fn.mTransformType3D) {
|
||||
case fast_noise_lite::FastNoiseLite::TransformType3D_ImproveXYPlanes: {
|
||||
Interval xy = x + y;
|
||||
Interval s2 = xy * (-0.211324865405187);
|
||||
z *= 0.577350269189626;
|
||||
x += s2 - z;
|
||||
y = y + s2 - z;
|
||||
z += xy * 0.577350269189626;
|
||||
} break;
|
||||
case fast_noise_lite::FastNoiseLite::TransformType3D_ImproveXZPlanes: {
|
||||
Interval xz = x + z;
|
||||
Interval s2 = xz * (-0.211324865405187);
|
||||
y *= 0.577350269189626;
|
||||
x += s2 - y;
|
||||
z += s2 - y;
|
||||
y += xz * 0.577350269189626;
|
||||
} break;
|
||||
case fast_noise_lite::FastNoiseLite::TransformType3D_DefaultOpenSimplex2: {
|
||||
const float R3 = (2.0 / 3.0);
|
||||
Interval r = (x + y + z) * R3; // Rotation, not skew
|
||||
x = r - x;
|
||||
y = r - y;
|
||||
z = r - z;
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Interval fnl_single_opensimplex2(
|
||||
const fast_noise_lite::FastNoiseLite &fn, int seed, Interval p_x, Interval p_y, Interval p_z) {
|
||||
// According to OpenSimplex2 author, the 3D version is supposed to have a max derivative around 4.23718
|
||||
// https://www.wolframalpha.com/input/?i=max+d%2Fdx+32.69428253173828125+*+x+*+%28%280.6-x%5E2%29%5E4%29+from+-0.6+to+0.6
|
||||
// But empiric measures have shown it around 8. Discontinuities do exist in this noise though,
|
||||
// which makes this measuring harder
|
||||
return get_noise_range_3d([&fn, seed](float x, float y, float z) { return fn.SingleOpenSimplex2(seed, x, y, z); },
|
||||
p_x, p_y, p_z, 4.23718f);
|
||||
}
|
||||
|
||||
Interval fnl_single_opensimplex2s(
|
||||
const fast_noise_lite::FastNoiseLite &fn, int seed, Interval p_x, Interval p_y, Interval p_z) {
|
||||
return get_noise_range_3d([&fn, seed](float x, float y, float z) { return fn.SingleOpenSimplex2(seed, x, y, z); },
|
||||
// Max derivative found from empiric tests
|
||||
p_x, p_y, p_z, 2.5f);
|
||||
}
|
||||
|
||||
Interval fnl_single_cellular(const FastNoiseLite *noise, Interval x, Interval y, Interval z) {
|
||||
const fast_noise_lite::FastNoiseLite &fn = noise->get_noise_internal();
|
||||
if (fn.mCellularReturnType == fast_noise_lite::FastNoiseLite::CellularReturnType_CellValue) {
|
||||
return get_fnl_cellular_value_range_3d(fn, x, y, z);
|
||||
}
|
||||
return get_fnl_cellular_range(noise);
|
||||
}
|
||||
|
||||
Interval fnl_single_perlin(
|
||||
const fast_noise_lite::FastNoiseLite &fn, int seed, Interval p_x, Interval p_y, Interval p_z) {
|
||||
return get_noise_range_3d([&fn, seed](float x, float y, float z) { return fn.SinglePerlin(seed, x, y, z); },
|
||||
// Max derivative found from empiric tests
|
||||
p_x, p_y, p_z, 3.2f);
|
||||
}
|
||||
|
||||
Interval fnl_single_value_cubic(
|
||||
const fast_noise_lite::FastNoiseLite &fn, int seed, Interval p_x, Interval p_y, Interval p_z) {
|
||||
return get_noise_range_3d([&fn, seed](float x, float y, float z) { return fn.SingleValueCubic(seed, x, y, z); },
|
||||
// Max derivative found from empiric tests
|
||||
p_x, p_y, p_z, 1.2f);
|
||||
}
|
||||
|
||||
Interval fnl_single_value(
|
||||
const fast_noise_lite::FastNoiseLite &fn, int seed, Interval p_x, Interval p_y, Interval p_z) {
|
||||
return get_noise_range_3d([&fn, seed](float x, float y, float z) { return fn.SingleValue(seed, x, y, z); },
|
||||
// Max derivative found from empiric tests
|
||||
p_x, p_y, p_z, 3.0f);
|
||||
}
|
||||
|
||||
Interval fnl_gen_noise_single(const FastNoiseLite *noise, int seed, Interval x, Interval y, Interval z) {
|
||||
// Same logic as in the FastNoiseLite internal function
|
||||
const fast_noise_lite::FastNoiseLite &fn = noise->get_noise_internal();
|
||||
|
||||
switch (fn.mNoiseType) {
|
||||
case fast_noise_lite::FastNoiseLite::NoiseType_OpenSimplex2:
|
||||
return fnl_single_opensimplex2(fn, seed, x, y, z);
|
||||
case fast_noise_lite::FastNoiseLite::NoiseType_OpenSimplex2S:
|
||||
return fnl_single_opensimplex2s(fn, seed, x, y, z);
|
||||
case fast_noise_lite::FastNoiseLite::NoiseType_Cellular:
|
||||
return fnl_single_cellular(noise, x, y, z);
|
||||
case fast_noise_lite::FastNoiseLite::NoiseType_Perlin:
|
||||
return fnl_single_perlin(fn, seed, x, y, z);
|
||||
case fast_noise_lite::FastNoiseLite::NoiseType_ValueCubic:
|
||||
return fnl_single_value_cubic(fn, seed, x, y, z);
|
||||
case fast_noise_lite::FastNoiseLite::NoiseType_Value:
|
||||
return fnl_single_value(fn, seed, x, y, z);
|
||||
default:
|
||||
return Interval::from_single_value(0);
|
||||
}
|
||||
}
|
||||
|
||||
Interval fnl_gen_fractal_fbm(const FastNoiseLite *p_noise, Interval x, Interval y, Interval z) {
|
||||
// Same logic as in the FastNoiseLite internal function
|
||||
const fast_noise_lite::FastNoiseLite &fn = p_noise->get_noise_internal();
|
||||
|
||||
int seed = fn.mSeed;
|
||||
Interval sum;
|
||||
Interval amp = Interval::from_single_value(fn.mFractalBounding);
|
||||
|
||||
for (int i = 0; i < fn.mOctaves; i++) {
|
||||
Interval noise = fnl_gen_noise_single(p_noise, seed++, x, y, z);
|
||||
sum += noise * amp;
|
||||
amp *= lerp(Interval::from_single_value(1.0f), (noise + Interval::from_single_value(1.0f)) * 0.5f,
|
||||
Interval::from_single_value(fn.mWeightedStrength));
|
||||
|
||||
x *= fn.mLacunarity;
|
||||
y *= fn.mLacunarity;
|
||||
z *= fn.mLacunarity;
|
||||
amp *= fn.mGain;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
Interval fnl_gen_fractal_ridged(const FastNoiseLite *p_noise, Interval x, Interval y, Interval z) {
|
||||
// Same logic as in the FastNoiseLite internal function
|
||||
const fast_noise_lite::FastNoiseLite &fn = p_noise->get_noise_internal();
|
||||
|
||||
int seed = fn.mSeed;
|
||||
Interval sum;
|
||||
Interval amp = Interval::from_single_value(fn.mFractalBounding);
|
||||
|
||||
for (int i = 0; i < fn.mOctaves; i++) {
|
||||
Interval noise = abs(fnl_gen_noise_single(p_noise, seed++, x, y, z));
|
||||
sum += (noise * -2 + 1) * amp;
|
||||
amp *= lerp(Interval::from_single_value(1.0f), Interval::from_single_value(1.0f) - noise,
|
||||
Interval::from_single_value(fn.mWeightedStrength));
|
||||
|
||||
x *= fn.mLacunarity;
|
||||
y *= fn.mLacunarity;
|
||||
z *= fn.mLacunarity;
|
||||
amp *= fn.mGain;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
Interval fnl_get_noise(const FastNoiseLite *noise, Interval x, Interval y, Interval z) {
|
||||
// Same logic as in the FastNoiseLite internal function
|
||||
const fast_noise_lite::FastNoiseLite &fn = noise->get_noise_internal();
|
||||
|
||||
fnl_transform_noise_coordinate(fn, x, y, z);
|
||||
|
||||
switch (noise->get_fractal_type()) {
|
||||
default:
|
||||
return fnl_gen_noise_single(noise, noise->get_seed(), x, y, z);
|
||||
case FastNoiseLite::FRACTAL_FBM:
|
||||
return fnl_gen_fractal_fbm(noise, x, y, z);
|
||||
case FastNoiseLite::FRACTAL_RIDGED:
|
||||
return fnl_gen_fractal_ridged(noise, x, y, z);
|
||||
case FastNoiseLite::FRACTAL_PING_PONG:
|
||||
// TODO Ping pong
|
||||
return Interval(-1.f, 1.f);
|
||||
}
|
||||
}
|
||||
|
||||
Interval get_fnl_range_2d(const FastNoiseLite *noise, Interval x, Interval y) {
|
||||
// TODO More precise analysis using derivatives
|
||||
// TODO Take warp noise into account
|
||||
switch (noise->get_noise_type()) {
|
||||
case FastNoiseLite::TYPE_CELLULAR:
|
||||
if (noise->get_cellular_return_type() == FastNoiseLite::CELLULAR_RETURN_CELL_VALUE) {
|
||||
return get_fnl_cellular_value_range_2d(noise, x, y);
|
||||
}
|
||||
return get_fnl_cellular_range(noise);
|
||||
default:
|
||||
return Interval{ -1.f, 1.f };
|
||||
}
|
||||
}
|
||||
|
||||
Interval get_fnl_range_3d(const FastNoiseLite *noise, Interval x, Interval y, Interval z) {
|
||||
if (noise->get_warp_noise().is_null()) {
|
||||
return fnl_get_noise(noise, x, y, z);
|
||||
}
|
||||
// TODO Take warp noise into account
|
||||
switch (noise->get_noise_type()) {
|
||||
case FastNoiseLite::TYPE_CELLULAR:
|
||||
if (noise->get_cellular_return_type() == FastNoiseLite::CELLULAR_RETURN_CELL_VALUE) {
|
||||
return get_fnl_cellular_value_range_3d(noise->get_noise_internal(), x, y, z);
|
||||
}
|
||||
return get_fnl_cellular_range(noise);
|
||||
default:
|
||||
return Interval{ -1.f, 1.f };
|
||||
}
|
||||
}
|
||||
|
||||
math::Interval2 get_fnl_gradient_range_2d(const FastNoiseLiteGradient *noise, Interval x, Interval y) {
|
||||
// TODO More precise analysis
|
||||
const float amp = Math::abs(noise->get_amplitude());
|
||||
return math::Interval2{ Interval{ x.min - amp, x.max + amp }, Interval{ y.min - amp, y.max + amp } };
|
||||
}
|
||||
|
||||
math::Interval3 get_fnl_gradient_range_3d(const FastNoiseLiteGradient *noise, Interval x, Interval y, Interval z) {
|
||||
// TODO More precise analysis
|
||||
const float amp = Math::abs(noise->get_amplitude());
|
||||
return math::Interval3{ Interval{ x.min - amp, x.max + amp }, Interval{ y.min - amp, y.max + amp },
|
||||
Interval{ z.min - amp, z.max + amp } };
|
||||
}
|
||||
|
||||
} // namespace zylann
|
||||
|
@ -6,15 +6,11 @@
|
||||
#include <vector>
|
||||
|
||||
class Curve;
|
||||
class OpenSimplexNoise;
|
||||
class Image;
|
||||
class FastNoiseLite;
|
||||
class FastNoiseLiteGradient;
|
||||
|
||||
namespace zylann {
|
||||
|
||||
math::Interval get_osn_range_2d(OpenSimplexNoise *noise, math::Interval x, math::Interval y);
|
||||
math::Interval get_osn_range_3d(OpenSimplexNoise *noise, math::Interval x, math::Interval y, math::Interval z);
|
||||
// Curve ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct CurveMonotonicSection {
|
||||
float x_min;
|
||||
@ -44,59 +40,11 @@ math::Interval get_curve_range(Curve &curve, const std::vector<CurveMonotonicSec
|
||||
// Legacy
|
||||
math::Interval get_curve_range(Curve &curve, bool &is_monotonic_increasing);
|
||||
|
||||
// Heightmaps //////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
math::Interval get_heightmap_range(const Image &im);
|
||||
math::Interval get_heightmap_range(const Image &im, Rect2i rect);
|
||||
|
||||
namespace math {
|
||||
|
||||
inline Interval sdf_union(Interval a, Interval b) {
|
||||
return min_interval(a, b);
|
||||
}
|
||||
|
||||
// Does a - b
|
||||
inline Interval sdf_subtract(Interval a, Interval b) {
|
||||
return max_interval(a, -b);
|
||||
}
|
||||
|
||||
Interval sdf_smooth_union(Interval p_b, Interval p_a, float p_s);
|
||||
|
||||
// Does b - a
|
||||
Interval sdf_smooth_subtract(Interval p_b, Interval p_a, float p_s);
|
||||
|
||||
enum SdfAffectingArguments { //
|
||||
SDF_ONLY_A,
|
||||
SDF_ONLY_B,
|
||||
SDF_BOTH
|
||||
};
|
||||
|
||||
// Tests which argument can affect the result.
|
||||
// for a - b
|
||||
SdfAffectingArguments sdf_subtract_side(Interval a, Interval b);
|
||||
// for a - b
|
||||
SdfAffectingArguments sdf_polynomial_smooth_subtract_side(Interval a, Interval b, float s);
|
||||
|
||||
SdfAffectingArguments sdf_union_side(Interval a, Interval b);
|
||||
SdfAffectingArguments sdf_polynomial_smooth_union_side(Interval a, Interval b, float s);
|
||||
|
||||
struct Interval2 {
|
||||
Interval x;
|
||||
Interval y;
|
||||
};
|
||||
|
||||
struct Interval3 {
|
||||
Interval x;
|
||||
Interval y;
|
||||
Interval z;
|
||||
};
|
||||
|
||||
} // namespace math
|
||||
|
||||
math::Interval get_fnl_range_2d(const FastNoiseLite *noise, math::Interval x, math::Interval y);
|
||||
math::Interval get_fnl_range_3d(const FastNoiseLite *noise, math::Interval x, math::Interval y, math::Interval z);
|
||||
math::Interval2 get_fnl_gradient_range_2d(const FastNoiseLiteGradient *noise, math::Interval x, math::Interval y);
|
||||
math::Interval3 get_fnl_gradient_range_3d(
|
||||
const FastNoiseLiteGradient *noise, math::Interval x, math::Interval y, math::Interval z);
|
||||
|
||||
} // namespace zylann
|
||||
|
||||
#endif // RANGE_UTILITY_H
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include "voxel_graph_node_db.h"
|
||||
#include "../../util/math/sdf.h"
|
||||
#include "../../util/noise/fast_noise_lite.h"
|
||||
#include "../../util/noise/fast_noise_lite_range.h"
|
||||
#include "../../util/noise/osn_noise_range.h"
|
||||
#include "../../util/profiling.h"
|
||||
#include "image_range_grid.h"
|
||||
#include "range_utility.h"
|
||||
|
@ -10,7 +10,8 @@ int VoxelDataBlockEnterInfo::_b_get_network_peer_id() const {
|
||||
|
||||
Ref<VoxelBuffer> VoxelDataBlockEnterInfo::_b_get_voxels() const {
|
||||
ERR_FAIL_COND_V(voxel_block == nullptr, Ref<VoxelBuffer>());
|
||||
Ref<VoxelBuffer> vb = VoxelBuffer::create_shared(voxel_block->get_voxels_shared());
|
||||
std::shared_ptr<VoxelBufferInternal> vbi = voxel_block->get_voxels_shared();
|
||||
Ref<VoxelBuffer> vb = VoxelBuffer::create_shared(vbi);
|
||||
return vb;
|
||||
}
|
||||
|
||||
|
@ -153,6 +153,17 @@ struct Interval {
|
||||
}
|
||||
};
|
||||
|
||||
struct Interval2 {
|
||||
Interval x;
|
||||
Interval y;
|
||||
};
|
||||
|
||||
struct Interval3 {
|
||||
Interval x;
|
||||
Interval y;
|
||||
Interval z;
|
||||
};
|
||||
|
||||
inline Interval operator*(float b, const Interval &a) {
|
||||
return a * b;
|
||||
}
|
||||
|
123
util/math/sdf.cpp
Normal file
123
util/math/sdf.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
#include "sdf.h"
|
||||
|
||||
namespace zylann::math {
|
||||
|
||||
SdfAffectingArguments sdf_subtract_side(Interval a, Interval b) {
|
||||
if (b.min > -a.min) {
|
||||
return SDF_ONLY_A;
|
||||
}
|
||||
if (b.max < -a.max) {
|
||||
return SDF_ONLY_B;
|
||||
}
|
||||
return SDF_BOTH;
|
||||
}
|
||||
|
||||
SdfAffectingArguments sdf_polynomial_smooth_subtract_side(Interval a, Interval b, float s) {
|
||||
// | \ \ \ |
|
||||
// ---1---x--x--x-------3--- b.max
|
||||
// | \ \ \ |
|
||||
// | \ \ \ | (b)
|
||||
// | \ \ \ | y
|
||||
// | \ \ \ | |
|
||||
// ---0--------x--x--x--2--- b.min o---x (a)
|
||||
// | \s \ s\ |
|
||||
// a.min a.max
|
||||
|
||||
if (b.min > -a.min + s) {
|
||||
return SDF_ONLY_B;
|
||||
}
|
||||
if (b.max < -a.max - s) {
|
||||
return SDF_ONLY_A;
|
||||
}
|
||||
return SDF_BOTH;
|
||||
}
|
||||
|
||||
SdfAffectingArguments sdf_union_side(Interval a, Interval b) {
|
||||
if (a.max < b.min) {
|
||||
return SDF_ONLY_A;
|
||||
}
|
||||
if (b.max < a.min) {
|
||||
return SDF_ONLY_B;
|
||||
}
|
||||
return SDF_BOTH;
|
||||
}
|
||||
|
||||
SdfAffectingArguments sdf_polynomial_smooth_union_side(Interval a, Interval b, float s) {
|
||||
if (a.max + s < b.min) {
|
||||
return SDF_ONLY_A;
|
||||
}
|
||||
if (b.max + s < a.min) {
|
||||
return SDF_ONLY_B;
|
||||
}
|
||||
return SDF_BOTH;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
inline Interval sdf_smooth_op(Interval b, Interval a, float s, F smooth_op_func) {
|
||||
// Smooth union and subtract are a generalization of `min(a, b)` and `max(-a, b)`, with a smooth junction.
|
||||
// That junction runs in a diagonal crossing zero (with equation `y = -x`).
|
||||
// Areas on the two sides of the junction are monotonic, i.e their derivatives should never cross zero,
|
||||
// because they are linear functions modified by a "smoothing" polynomial for which the tip is on diagonal.
|
||||
// So to find the output range, we can evaluate and sort the 4 corners,
|
||||
// and diagonal intersections if it crosses the area.
|
||||
|
||||
// | \ |
|
||||
// ---1---x-------------3--- b.max
|
||||
// | \ |
|
||||
// | \ | (b)
|
||||
// | \ | y
|
||||
// | \ | |
|
||||
// ---0--------x--------2--- b.min o---x (a)
|
||||
// | \ |
|
||||
// a.min a.max
|
||||
|
||||
const float v0 = smooth_op_func(b.min, a.min, s);
|
||||
const float v1 = smooth_op_func(b.max, a.min, s);
|
||||
const float v2 = smooth_op_func(b.min, a.max, s);
|
||||
const float v3 = smooth_op_func(b.max, a.max, s);
|
||||
|
||||
const Vector2 diag_b_min(-b.min, b.min);
|
||||
const Vector2 diag_b_max(-b.max, b.max);
|
||||
const Vector2 diag_a_min(a.min, -a.min);
|
||||
const Vector2 diag_a_max(a.max, -a.max);
|
||||
|
||||
const bool crossing_top = (diag_b_max.x > a.min && diag_b_max.x < a.max);
|
||||
const bool crossing_left = (diag_a_min.y > b.min && diag_a_min.y < b.max);
|
||||
|
||||
if (crossing_left || crossing_top) {
|
||||
const bool crossing_right = (diag_a_max.y > b.min && diag_a_max.y < b.max);
|
||||
|
||||
float v4;
|
||||
if (crossing_left) {
|
||||
v4 = smooth_op_func(diag_a_min.y, diag_a_min.x, s);
|
||||
} else {
|
||||
v4 = smooth_op_func(diag_b_max.y, diag_b_max.x, s);
|
||||
}
|
||||
|
||||
float v5;
|
||||
if (crossing_right) {
|
||||
v5 = smooth_op_func(diag_a_max.y, diag_a_max.x, s);
|
||||
} else {
|
||||
v5 = smooth_op_func(diag_b_min.y, diag_b_min.x, s);
|
||||
}
|
||||
|
||||
return Interval(min(v0, v1, v2, v3, v4, v5), max(v0, v1, v2, v3, v4, v5));
|
||||
}
|
||||
|
||||
// The diagonal does not cross the area
|
||||
return Interval(min(v0, v1, v2, v3), max(v0, v1, v2, v3));
|
||||
}
|
||||
|
||||
Interval sdf_smooth_union(Interval p_b, Interval p_a, float p_s) {
|
||||
// TODO Not tested
|
||||
// Had to use a lambda because otherwise it's ambiguous
|
||||
return sdf_smooth_op(
|
||||
p_b, p_a, p_s, [](float b, float a, float s) { return zylann::math::sdf_smooth_union(b, a, s); });
|
||||
}
|
||||
|
||||
Interval sdf_smooth_subtract(Interval p_b, Interval p_a, float p_s) {
|
||||
return sdf_smooth_op(
|
||||
p_b, p_a, p_s, [](float b, float a, float s) { return zylann::math::sdf_smooth_subtract(b, a, s); });
|
||||
}
|
||||
|
||||
} //namespace zylann::math
|
@ -58,6 +58,35 @@ inline float sdf_smooth_subtract(float b, float a, float s) {
|
||||
return Math::lerp(b, -a, h) + s * h * (1.0f - h);
|
||||
}
|
||||
|
||||
inline Interval sdf_union(Interval a, Interval b) {
|
||||
return min_interval(a, b);
|
||||
}
|
||||
|
||||
// Does a - b
|
||||
inline Interval sdf_subtract(Interval a, Interval b) {
|
||||
return max_interval(a, -b);
|
||||
}
|
||||
|
||||
Interval sdf_smooth_union(Interval p_b, Interval p_a, float p_s);
|
||||
|
||||
// Does b - a
|
||||
Interval sdf_smooth_subtract(Interval p_b, Interval p_a, float p_s);
|
||||
|
||||
enum SdfAffectingArguments { //
|
||||
SDF_ONLY_A,
|
||||
SDF_ONLY_B,
|
||||
SDF_BOTH
|
||||
};
|
||||
|
||||
// Tests which argument can affect the result.
|
||||
// for a - b
|
||||
SdfAffectingArguments sdf_subtract_side(Interval a, Interval b);
|
||||
// for a - b
|
||||
SdfAffectingArguments sdf_polynomial_smooth_subtract_side(Interval a, Interval b, float s);
|
||||
|
||||
SdfAffectingArguments sdf_union_side(Interval a, Interval b);
|
||||
SdfAffectingArguments sdf_polynomial_smooth_union_side(Interval a, Interval b, float s);
|
||||
|
||||
} // namespace zylann::math
|
||||
|
||||
#endif // VOXEL_MATH_SDF_H
|
||||
|
331
util/noise/fast_noise_lite_range.cpp
Normal file
331
util/noise/fast_noise_lite_range.cpp
Normal file
@ -0,0 +1,331 @@
|
||||
#include "fast_noise_lite_range.h"
|
||||
#include "fast_noise_lite.h"
|
||||
#include "noise_range_utility.h"
|
||||
|
||||
namespace zylann {
|
||||
|
||||
using namespace math;
|
||||
|
||||
static Interval get_fnl_cellular_value_range_2d(const FastNoiseLite *noise, Interval x, Interval y) {
|
||||
const float c0 = noise->get_noise_2d(x.min, y.min);
|
||||
const float c1 = noise->get_noise_2d(x.max, y.min);
|
||||
const float c2 = noise->get_noise_2d(x.min, y.max);
|
||||
const float c3 = noise->get_noise_2d(x.max, y.max);
|
||||
if (c0 == c1 && c1 == c2 && c2 == c3) {
|
||||
return Interval::from_single_value(c0);
|
||||
}
|
||||
return Interval{ -1, 1 };
|
||||
}
|
||||
|
||||
static Interval get_fnl_cellular_value_range_3d(
|
||||
const fast_noise_lite::FastNoiseLite &fn, Interval x, Interval y, Interval z) {
|
||||
const float c0 = fn.GetNoise(x.min, y.min, z.min);
|
||||
const float c1 = fn.GetNoise(x.max, y.min, z.min);
|
||||
const float c2 = fn.GetNoise(x.min, y.max, z.min);
|
||||
const float c3 = fn.GetNoise(x.max, y.max, z.min);
|
||||
const float c4 = fn.GetNoise(x.max, y.max, z.max);
|
||||
const float c5 = fn.GetNoise(x.max, y.max, z.max);
|
||||
const float c6 = fn.GetNoise(x.max, y.max, z.max);
|
||||
const float c7 = fn.GetNoise(x.max, y.max, z.max);
|
||||
if (c0 == c1 && c1 == c2 && c2 == c3 && c3 == c4 && c4 == c5 && c5 == c6 && c6 == c7) {
|
||||
return Interval::from_single_value(c0);
|
||||
}
|
||||
return Interval{ -1, 1 };
|
||||
}
|
||||
|
||||
static Interval get_fnl_cellular_range(const FastNoiseLite *noise) {
|
||||
// There are many combinations with Cellular noise so instead of implementing them with intervals,
|
||||
// I used empiric tests to figure out some bounds.
|
||||
|
||||
// Value mode must be handled separately.
|
||||
|
||||
switch (noise->get_cellular_distance_function()) {
|
||||
case FastNoiseLite::CELLULAR_DISTANCE_EUCLIDEAN:
|
||||
switch (noise->get_cellular_return_type()) {
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE:
|
||||
return Interval{ -1.f, 0.08f };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2:
|
||||
return Interval{ -0.92f, 0.35 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_ADD:
|
||||
return Interval{ -0.92f, 0.1 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_SUB:
|
||||
return Interval{ -1, 0.15 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_MUL:
|
||||
return Interval{ -1, 0 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_DIV:
|
||||
return Interval{ -1, 0 };
|
||||
default:
|
||||
ERR_FAIL_V(Interval(-1, 1));
|
||||
}
|
||||
break;
|
||||
|
||||
case FastNoiseLite::CELLULAR_DISTANCE_EUCLIDEAN_SQ:
|
||||
switch (noise->get_cellular_return_type()) {
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE:
|
||||
return Interval{ -1, 0.2 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2:
|
||||
return Interval{ -1, 0.8 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_ADD:
|
||||
return Interval{ -1, 0.2 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_SUB:
|
||||
return Interval{ -1, 0.7 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_MUL:
|
||||
return Interval{ -1, 0 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_DIV:
|
||||
return Interval{ -1, 0 };
|
||||
default:
|
||||
ERR_FAIL_V(Interval(-1, 1));
|
||||
}
|
||||
|
||||
case FastNoiseLite::CELLULAR_DISTANCE_MANHATTAN:
|
||||
switch (noise->get_cellular_return_type()) {
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE:
|
||||
return Interval{ -1, 0.75 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2:
|
||||
return Interval{ -0.9, 0.8 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_ADD:
|
||||
return Interval{ -0.8, 0.8 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_SUB:
|
||||
return Interval{ -1.0, 0.5 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_MUL:
|
||||
return Interval{ -1.0, 0.7 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_DIV:
|
||||
return Interval{ -1.0, 0.0 };
|
||||
default:
|
||||
ERR_FAIL_V(Interval(-1, 1));
|
||||
}
|
||||
|
||||
case FastNoiseLite::CELLULAR_DISTANCE_HYBRID:
|
||||
switch (noise->get_cellular_return_type()) {
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE:
|
||||
return Interval{ -1, 1.75 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2:
|
||||
return Interval{ -0.9, 2.3 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_ADD:
|
||||
return Interval{ -0.9, 1.9 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_SUB:
|
||||
return Interval{ -1.0, 1.85 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_MUL:
|
||||
return Interval{ -1.0, 3.4 };
|
||||
case FastNoiseLite::CELLULAR_RETURN_DISTANCE_2_DIV:
|
||||
return Interval{ -1.0, 0.0 };
|
||||
default:
|
||||
ERR_FAIL_V(Interval(-1, 1));
|
||||
}
|
||||
}
|
||||
return Interval{ -1.f, 1.f };
|
||||
}
|
||||
|
||||
void fnl_transform_noise_coordinate(const fast_noise_lite::FastNoiseLite &fn, Interval &x, Interval &y, Interval &z) {
|
||||
// Same logic as in the FastNoiseLite internal function
|
||||
|
||||
x *= fn.mFrequency;
|
||||
y *= fn.mFrequency;
|
||||
z *= fn.mFrequency;
|
||||
|
||||
switch (fn.mTransformType3D) {
|
||||
case fast_noise_lite::FastNoiseLite::TransformType3D_ImproveXYPlanes: {
|
||||
Interval xy = x + y;
|
||||
Interval s2 = xy * (-0.211324865405187);
|
||||
z *= 0.577350269189626;
|
||||
x += s2 - z;
|
||||
y = y + s2 - z;
|
||||
z += xy * 0.577350269189626;
|
||||
} break;
|
||||
case fast_noise_lite::FastNoiseLite::TransformType3D_ImproveXZPlanes: {
|
||||
Interval xz = x + z;
|
||||
Interval s2 = xz * (-0.211324865405187);
|
||||
y *= 0.577350269189626;
|
||||
x += s2 - y;
|
||||
z += s2 - y;
|
||||
y += xz * 0.577350269189626;
|
||||
} break;
|
||||
case fast_noise_lite::FastNoiseLite::TransformType3D_DefaultOpenSimplex2: {
|
||||
const float R3 = (2.0 / 3.0);
|
||||
Interval r = (x + y + z) * R3; // Rotation, not skew
|
||||
x = r - x;
|
||||
y = r - y;
|
||||
z = r - z;
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Interval fnl_single_opensimplex2(
|
||||
const fast_noise_lite::FastNoiseLite &fn, int seed, Interval p_x, Interval p_y, Interval p_z) {
|
||||
// According to OpenSimplex2 author, the 3D version is supposed to have a max derivative around 4.23718
|
||||
// https://www.wolframalpha.com/input/?i=max+d%2Fdx+32.69428253173828125+*+x+*+%28%280.6-x%5E2%29%5E4%29+from+-0.6+to+0.6
|
||||
// But empiric measures have shown it around 8. Discontinuities do exist in this noise though,
|
||||
// which makes this measuring harder
|
||||
return get_noise_range_3d([&fn, seed](float x, float y, float z) { return fn.SingleOpenSimplex2(seed, x, y, z); },
|
||||
p_x, p_y, p_z, 4.23718f);
|
||||
}
|
||||
|
||||
Interval fnl_single_opensimplex2s(
|
||||
const fast_noise_lite::FastNoiseLite &fn, int seed, Interval p_x, Interval p_y, Interval p_z) {
|
||||
return get_noise_range_3d([&fn, seed](float x, float y, float z) { return fn.SingleOpenSimplex2(seed, x, y, z); },
|
||||
// Max derivative found from empiric tests
|
||||
p_x, p_y, p_z, 2.5f);
|
||||
}
|
||||
|
||||
Interval fnl_single_cellular(const FastNoiseLite *noise, Interval x, Interval y, Interval z) {
|
||||
const fast_noise_lite::FastNoiseLite &fn = noise->get_noise_internal();
|
||||
if (fn.mCellularReturnType == fast_noise_lite::FastNoiseLite::CellularReturnType_CellValue) {
|
||||
return get_fnl_cellular_value_range_3d(fn, x, y, z);
|
||||
}
|
||||
return get_fnl_cellular_range(noise);
|
||||
}
|
||||
|
||||
Interval fnl_single_perlin(
|
||||
const fast_noise_lite::FastNoiseLite &fn, int seed, Interval p_x, Interval p_y, Interval p_z) {
|
||||
return get_noise_range_3d([&fn, seed](float x, float y, float z) { return fn.SinglePerlin(seed, x, y, z); },
|
||||
// Max derivative found from empiric tests
|
||||
p_x, p_y, p_z, 3.2f);
|
||||
}
|
||||
|
||||
Interval fnl_single_value_cubic(
|
||||
const fast_noise_lite::FastNoiseLite &fn, int seed, Interval p_x, Interval p_y, Interval p_z) {
|
||||
return get_noise_range_3d([&fn, seed](float x, float y, float z) { return fn.SingleValueCubic(seed, x, y, z); },
|
||||
// Max derivative found from empiric tests
|
||||
p_x, p_y, p_z, 1.2f);
|
||||
}
|
||||
|
||||
Interval fnl_single_value(
|
||||
const fast_noise_lite::FastNoiseLite &fn, int seed, Interval p_x, Interval p_y, Interval p_z) {
|
||||
return get_noise_range_3d([&fn, seed](float x, float y, float z) { return fn.SingleValue(seed, x, y, z); },
|
||||
// Max derivative found from empiric tests
|
||||
p_x, p_y, p_z, 3.0f);
|
||||
}
|
||||
|
||||
Interval fnl_gen_noise_single(const FastNoiseLite *noise, int seed, Interval x, Interval y, Interval z) {
|
||||
// Same logic as in the FastNoiseLite internal function
|
||||
const fast_noise_lite::FastNoiseLite &fn = noise->get_noise_internal();
|
||||
|
||||
switch (fn.mNoiseType) {
|
||||
case fast_noise_lite::FastNoiseLite::NoiseType_OpenSimplex2:
|
||||
return fnl_single_opensimplex2(fn, seed, x, y, z);
|
||||
case fast_noise_lite::FastNoiseLite::NoiseType_OpenSimplex2S:
|
||||
return fnl_single_opensimplex2s(fn, seed, x, y, z);
|
||||
case fast_noise_lite::FastNoiseLite::NoiseType_Cellular:
|
||||
return fnl_single_cellular(noise, x, y, z);
|
||||
case fast_noise_lite::FastNoiseLite::NoiseType_Perlin:
|
||||
return fnl_single_perlin(fn, seed, x, y, z);
|
||||
case fast_noise_lite::FastNoiseLite::NoiseType_ValueCubic:
|
||||
return fnl_single_value_cubic(fn, seed, x, y, z);
|
||||
case fast_noise_lite::FastNoiseLite::NoiseType_Value:
|
||||
return fnl_single_value(fn, seed, x, y, z);
|
||||
default:
|
||||
return Interval::from_single_value(0);
|
||||
}
|
||||
}
|
||||
|
||||
Interval fnl_gen_fractal_fbm(const FastNoiseLite *p_noise, Interval x, Interval y, Interval z) {
|
||||
// Same logic as in the FastNoiseLite internal function
|
||||
const fast_noise_lite::FastNoiseLite &fn = p_noise->get_noise_internal();
|
||||
|
||||
int seed = fn.mSeed;
|
||||
Interval sum;
|
||||
Interval amp = Interval::from_single_value(fn.mFractalBounding);
|
||||
|
||||
for (int i = 0; i < fn.mOctaves; i++) {
|
||||
Interval noise = fnl_gen_noise_single(p_noise, seed++, x, y, z);
|
||||
sum += noise * amp;
|
||||
amp *= lerp(Interval::from_single_value(1.0f), (noise + Interval::from_single_value(1.0f)) * 0.5f,
|
||||
Interval::from_single_value(fn.mWeightedStrength));
|
||||
|
||||
x *= fn.mLacunarity;
|
||||
y *= fn.mLacunarity;
|
||||
z *= fn.mLacunarity;
|
||||
amp *= fn.mGain;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
Interval fnl_gen_fractal_ridged(const FastNoiseLite *p_noise, Interval x, Interval y, Interval z) {
|
||||
// Same logic as in the FastNoiseLite internal function
|
||||
const fast_noise_lite::FastNoiseLite &fn = p_noise->get_noise_internal();
|
||||
|
||||
int seed = fn.mSeed;
|
||||
Interval sum;
|
||||
Interval amp = Interval::from_single_value(fn.mFractalBounding);
|
||||
|
||||
for (int i = 0; i < fn.mOctaves; i++) {
|
||||
Interval noise = abs(fnl_gen_noise_single(p_noise, seed++, x, y, z));
|
||||
sum += (noise * -2 + 1) * amp;
|
||||
amp *= lerp(Interval::from_single_value(1.0f), Interval::from_single_value(1.0f) - noise,
|
||||
Interval::from_single_value(fn.mWeightedStrength));
|
||||
|
||||
x *= fn.mLacunarity;
|
||||
y *= fn.mLacunarity;
|
||||
z *= fn.mLacunarity;
|
||||
amp *= fn.mGain;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
Interval fnl_get_noise(const FastNoiseLite *noise, Interval x, Interval y, Interval z) {
|
||||
// Same logic as in the FastNoiseLite internal function
|
||||
const fast_noise_lite::FastNoiseLite &fn = noise->get_noise_internal();
|
||||
|
||||
fnl_transform_noise_coordinate(fn, x, y, z);
|
||||
|
||||
switch (noise->get_fractal_type()) {
|
||||
default:
|
||||
return fnl_gen_noise_single(noise, noise->get_seed(), x, y, z);
|
||||
case FastNoiseLite::FRACTAL_FBM:
|
||||
return fnl_gen_fractal_fbm(noise, x, y, z);
|
||||
case FastNoiseLite::FRACTAL_RIDGED:
|
||||
return fnl_gen_fractal_ridged(noise, x, y, z);
|
||||
case FastNoiseLite::FRACTAL_PING_PONG:
|
||||
// TODO Ping pong
|
||||
return Interval(-1.f, 1.f);
|
||||
}
|
||||
}
|
||||
|
||||
Interval get_fnl_range_2d(const FastNoiseLite *noise, Interval x, Interval y) {
|
||||
// TODO More precise analysis using derivatives
|
||||
// TODO Take warp noise into account
|
||||
switch (noise->get_noise_type()) {
|
||||
case FastNoiseLite::TYPE_CELLULAR:
|
||||
if (noise->get_cellular_return_type() == FastNoiseLite::CELLULAR_RETURN_CELL_VALUE) {
|
||||
return get_fnl_cellular_value_range_2d(noise, x, y);
|
||||
}
|
||||
return get_fnl_cellular_range(noise);
|
||||
default:
|
||||
return Interval{ -1.f, 1.f };
|
||||
}
|
||||
}
|
||||
|
||||
Interval get_fnl_range_3d(const FastNoiseLite *noise, Interval x, Interval y, Interval z) {
|
||||
if (noise->get_warp_noise().is_null()) {
|
||||
return fnl_get_noise(noise, x, y, z);
|
||||
}
|
||||
// TODO Take warp noise into account
|
||||
switch (noise->get_noise_type()) {
|
||||
case FastNoiseLite::TYPE_CELLULAR:
|
||||
if (noise->get_cellular_return_type() == FastNoiseLite::CELLULAR_RETURN_CELL_VALUE) {
|
||||
return get_fnl_cellular_value_range_3d(noise->get_noise_internal(), x, y, z);
|
||||
}
|
||||
return get_fnl_cellular_range(noise);
|
||||
default:
|
||||
return Interval{ -1.f, 1.f };
|
||||
}
|
||||
}
|
||||
|
||||
math::Interval2 get_fnl_gradient_range_2d(const FastNoiseLiteGradient *noise, Interval x, Interval y) {
|
||||
// TODO More precise analysis
|
||||
const float amp = Math::abs(noise->get_amplitude());
|
||||
return math::Interval2{ Interval{ x.min - amp, x.max + amp }, Interval{ y.min - amp, y.max + amp } };
|
||||
}
|
||||
|
||||
math::Interval3 get_fnl_gradient_range_3d(const FastNoiseLiteGradient *noise, Interval x, Interval y, Interval z) {
|
||||
// TODO More precise analysis
|
||||
const float amp = Math::abs(noise->get_amplitude());
|
||||
return math::Interval3{ Interval{ x.min - amp, x.max + amp }, Interval{ y.min - amp, y.max + amp },
|
||||
Interval{ z.min - amp, z.max + amp } };
|
||||
}
|
||||
|
||||
} // namespace zylann
|
21
util/noise/fast_noise_lite_range.h
Normal file
21
util/noise/fast_noise_lite_range.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef FAST_NOISE_LITE_RANGE_H
|
||||
#define FAST_NOISE_LITE_RANGE_H
|
||||
|
||||
#include "../math/interval.h"
|
||||
|
||||
// Interval estimation for FastNoiseLite
|
||||
|
||||
namespace zylann {
|
||||
|
||||
class FastNoiseLite;
|
||||
class FastNoiseLiteGradient;
|
||||
|
||||
math::Interval get_fnl_range_2d(const FastNoiseLite *noise, math::Interval x, math::Interval y);
|
||||
math::Interval get_fnl_range_3d(const FastNoiseLite *noise, math::Interval x, math::Interval y, math::Interval z);
|
||||
math::Interval2 get_fnl_gradient_range_2d(const FastNoiseLiteGradient *noise, math::Interval x, math::Interval y);
|
||||
math::Interval3 get_fnl_gradient_range_3d(
|
||||
const FastNoiseLiteGradient *noise, math::Interval x, math::Interval y, math::Interval z);
|
||||
|
||||
} // namespace zylann
|
||||
|
||||
#endif // FAST_NOISE_LITE_RANGE_H
|
51
util/noise/noise_range_utility.h
Normal file
51
util/noise/noise_range_utility.h
Normal file
@ -0,0 +1,51 @@
|
||||
#ifndef NOISE_RANGE_UTILITY_H
|
||||
#define NOISE_RANGE_UTILITY_H
|
||||
|
||||
#include "../math/interval.h"
|
||||
|
||||
// Common utilities to obtain interval estimation for noise.
|
||||
// The main technique is to sample a middle point and use derivatives to find maximum variation.
|
||||
|
||||
// TODO We could skew max derivative estimation if the anchor is on a bump or a dip
|
||||
// because in these cases, it becomes impossible for noise to go further up or further down
|
||||
|
||||
namespace zylann {
|
||||
|
||||
template <typename Noise_F>
|
||||
inline math::Interval get_noise_range_2d(
|
||||
Noise_F noise_func, const math::Interval &x, const math::Interval &y, float max_derivative) {
|
||||
// Any unit vector away from a given evaluation point, the maximum difference is a fixed number.
|
||||
// We can use that number to find a bounding range within our rectangular interval.
|
||||
const float max_derivative_half_diagonal = 0.5f * max_derivative * Math_SQRT2;
|
||||
|
||||
const float mid_x = 0.5 * (x.min + x.max);
|
||||
const float mid_y = 0.5 * (y.min + y.max);
|
||||
const float mid_value = noise_func(mid_x, mid_y);
|
||||
|
||||
const float diag = Math::sqrt(squared(x.length()) + squared(y.length()));
|
||||
|
||||
return math::Interval( //
|
||||
max(mid_value - max_derivative_half_diagonal * diag, -1.f),
|
||||
min(mid_value + max_derivative_half_diagonal * diag, 1.f));
|
||||
}
|
||||
|
||||
template <typename Noise_F>
|
||||
inline math::Interval get_noise_range_3d(Noise_F noise_func, const math::Interval &x, const math::Interval &y,
|
||||
const math::Interval &z, float max_derivative) {
|
||||
const float max_derivative_half_diagonal = 0.5f * max_derivative * Math_SQRT2;
|
||||
|
||||
const float mid_x = 0.5 * (x.min + x.max);
|
||||
const float mid_y = 0.5 * (y.min + y.max);
|
||||
const float mid_z = 0.5 * (z.min + z.max);
|
||||
const float mid_value = noise_func(mid_x, mid_y, mid_z);
|
||||
|
||||
const float diag = Math::sqrt(squared(x.length()) + squared(y.length()) + squared(z.length()));
|
||||
|
||||
return math::Interval( //
|
||||
max(mid_value - max_derivative_half_diagonal * diag, -1.f),
|
||||
min(mid_value + max_derivative_half_diagonal * diag, 1.f));
|
||||
}
|
||||
|
||||
} // namespace zylann
|
||||
|
||||
#endif // NOISE_RANGE_UTILITY_H
|
75
util/noise/osn_noise_range.cpp
Normal file
75
util/noise/osn_noise_range.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#include "osn_noise_range.h"
|
||||
#include "noise_range_utility.h"
|
||||
#include <modules/opensimplex/open_simplex_noise.h>
|
||||
|
||||
namespace zylann {
|
||||
|
||||
using namespace math;
|
||||
|
||||
Interval get_osn_octave_range_2d(OpenSimplexNoise *noise, const Interval &p_x, const Interval &p_y, int octave) {
|
||||
return get_noise_range_2d(
|
||||
[octave, noise](float x, float y) { return noise->_get_octave_noise_2d(octave, x, y); }, p_x, p_y, 2.35f);
|
||||
}
|
||||
|
||||
Interval get_osn_octave_range_3d(
|
||||
OpenSimplexNoise *noise, const Interval &p_x, const Interval &p_y, const Interval &p_z, int octave) {
|
||||
return get_noise_range_3d(
|
||||
[octave, noise](float x, float y, float z) { return noise->_get_octave_noise_3d(octave, x, y, z); }, p_x,
|
||||
p_y, p_z, 2.5f);
|
||||
}
|
||||
|
||||
Interval get_osn_range_2d(OpenSimplexNoise *noise, Interval x, Interval y) {
|
||||
// Same implementation as `get_noise_2d`
|
||||
|
||||
if (x.is_single_value() && y.is_single_value()) {
|
||||
return Interval::from_single_value(noise->get_noise_2d(x.min, y.min));
|
||||
}
|
||||
|
||||
x /= noise->get_period();
|
||||
y /= noise->get_period();
|
||||
|
||||
float amp = 1.0;
|
||||
float max = 1.0;
|
||||
Interval sum = get_osn_octave_range_2d(noise, x, y, 0);
|
||||
|
||||
int i = 0;
|
||||
while (++i < noise->get_octaves()) {
|
||||
x *= noise->get_lacunarity();
|
||||
y *= noise->get_lacunarity();
|
||||
amp *= noise->get_persistence();
|
||||
max += amp;
|
||||
sum += get_osn_octave_range_2d(noise, x, y, i) * amp;
|
||||
}
|
||||
|
||||
return sum / max;
|
||||
}
|
||||
|
||||
Interval get_osn_range_3d(OpenSimplexNoise *noise, Interval x, Interval y, Interval z) {
|
||||
// Same implementation as `get_noise_3d`
|
||||
|
||||
if (x.is_single_value() && y.is_single_value()) {
|
||||
return Interval::from_single_value(noise->get_noise_2d(x.min, y.min));
|
||||
}
|
||||
|
||||
x /= noise->get_period();
|
||||
y /= noise->get_period();
|
||||
z /= noise->get_period();
|
||||
|
||||
float amp = 1.0;
|
||||
float max = 1.0;
|
||||
Interval sum = get_osn_octave_range_3d(noise, x, y, z, 0);
|
||||
|
||||
int i = 0;
|
||||
while (++i < noise->get_octaves()) {
|
||||
x *= noise->get_lacunarity();
|
||||
y *= noise->get_lacunarity();
|
||||
z *= noise->get_lacunarity();
|
||||
amp *= noise->get_persistence();
|
||||
max += amp;
|
||||
sum += get_osn_octave_range_3d(noise, x, y, z, i) * amp;
|
||||
}
|
||||
|
||||
return sum / max;
|
||||
}
|
||||
|
||||
} // namespace zylann
|
17
util/noise/osn_noise_range.h
Normal file
17
util/noise/osn_noise_range.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef OSN_NOISE_RANGE_H
|
||||
#define OSN_NOISE_RANGE_H
|
||||
|
||||
#include "../math/interval.h"
|
||||
|
||||
// Interval estimation for Godot's OpenSimplexNoise
|
||||
|
||||
class OpenSimplexNoise;
|
||||
|
||||
namespace zylann {
|
||||
|
||||
math::Interval get_osn_range_2d(OpenSimplexNoise *noise, math::Interval x, math::Interval y);
|
||||
math::Interval get_osn_range_3d(OpenSimplexNoise *noise, math::Interval x, math::Interval y, math::Interval z);
|
||||
|
||||
} // namespace zylann
|
||||
|
||||
#endif // OSN_NOISE_RANGE_H
|
Loading…
x
Reference in New Issue
Block a user