Namespaced Interval

This commit is contained in:
Marc Gilleron 2022-01-03 22:29:39 +00:00
parent 570d870b0a
commit a9476227cb
15 changed files with 79 additions and 85 deletions

View File

@ -782,7 +782,7 @@ void VoxelGraphEditor::update_range_analysis_previews() {
if (!_graph->try_get_output_port_address(loc, address)) { if (!_graph->try_get_output_port_address(loc, address)) {
continue; continue;
} }
const Interval range = state.get_range(address); const math::Interval range = state.get_range(address);
Control *label = node_view->output_labels[port_index]; Control *label = node_view->output_labels[port_index];
label->set_tooltip(String("Min: {0}\nMax: {1}").format(varray(range.min, range.max))); label->set_tooltip(String("Min: {0}\nMax: {1}").format(varray(range.min, range.max)));
} }

View File

@ -5,6 +5,8 @@
namespace zylann { namespace zylann {
using namespace math;
ImageRangeGrid::~ImageRangeGrid() { ImageRangeGrid::~ImageRangeGrid() {
clear(); clear();
} }
@ -125,8 +127,8 @@ static void interval_to_pixels(Interval i, int &out_min, int &out_max, int len)
// Images are finite, intervals are not. // Images are finite, intervals are not.
// It's useless to let the range span a potentially infinite area. // It's useless to let the range span a potentially infinite area.
// The image can repeat, so we clamp to one repetition. // The image can repeat, so we clamp to one repetition.
out_min = clamp(imin, -len, len); out_min = ::clamp(imin, -len, len);
out_max = clamp(imax, -len, len); out_max = ::clamp(imax, -len, len);
} }
Interval ImageRangeGrid::get_range(Interval xr, Interval yr) const { Interval ImageRangeGrid::get_range(Interval xr, Interval yr) const {

View File

@ -15,16 +15,16 @@ public:
void clear(); void clear();
void generate(Image &im); void generate(Image &im);
inline Interval get_range() const { inline math::Interval get_range() const {
return _total_range; return _total_range;
} }
Interval get_range(Interval xr, Interval yr) const; math::Interval get_range(math::Interval xr, math::Interval yr) const;
private: private:
static const int MAX_LODS = 16; static const int MAX_LODS = 16;
struct Lod { struct Lod {
Interval *data = nullptr; math::Interval *data = nullptr;
int size_x = 0; int size_x = 0;
int size_y = 0; int size_y = 0;
}; };
@ -36,7 +36,7 @@ private:
int _lod_base = 0; int _lod_base = 0;
int _lod_count = 0; int _lod_count = 0;
Interval _total_range; math::Interval _total_range;
FixedArray<Lod, MAX_LODS> _lods; FixedArray<Lod, MAX_LODS> _lods;
}; };

View File

@ -8,6 +8,8 @@
namespace zylann { namespace zylann {
using namespace math;
// TODO We could skew max derivative estimation if the anchor is on a bump or a dip // 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 // because in these cases, it becomes impossible for noise to go further up or further down
@ -21,7 +23,7 @@ inline Interval get_noise_range_2d(Noise_F noise_func, const Interval &x, const
const float mid_y = 0.5 * (y.min + y.max); const float mid_y = 0.5 * (y.min + y.max);
const float mid_value = noise_func(mid_x, mid_y); const float mid_value = noise_func(mid_x, mid_y);
const float diag = Math::sqrt(squared(x.length()) + squared(y.length())); const float diag = Math::sqrt(::squared(x.length()) + ::squared(y.length()));
return Interval( // return Interval( //
::max(mid_value - max_derivative_half_diagonal * diag, -1.f), ::max(mid_value - max_derivative_half_diagonal * diag, -1.f),
@ -38,7 +40,7 @@ inline Interval get_noise_range_3d(
const float mid_z = 0.5 * (z.min + z.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 mid_value = noise_func(mid_x, mid_y, mid_z);
const float diag = Math::sqrt(squared(x.length()) + squared(y.length()) + squared(z.length())); const float diag = Math::sqrt(::squared(x.length()) + ::squared(y.length()) + ::squared(z.length()));
return Interval( // return Interval( //
::max(mid_value - max_derivative_half_diagonal * diag, -1.f), ::max(mid_value - max_derivative_half_diagonal * diag, -1.f),

View File

@ -13,8 +13,8 @@ class FastNoiseLiteGradient;
namespace zylann { namespace zylann {
Interval get_osn_range_2d(OpenSimplexNoise *noise, Interval x, Interval y); math::Interval get_osn_range_2d(OpenSimplexNoise *noise, math::Interval x, math::Interval y);
Interval get_osn_range_3d(OpenSimplexNoise *noise, Interval x, Interval y, Interval z); math::Interval get_osn_range_3d(OpenSimplexNoise *noise, math::Interval x, math::Interval y, math::Interval z);
struct CurveMonotonicSection { struct CurveMonotonicSection {
float x_min; float x_min;
@ -39,13 +39,13 @@ static const float CURVE_RANGE_MARGIN = CMP_EPSILON;
// we can quickly calculate an accurate range of output values by sampling the curve only at the two points. // we can quickly calculate an accurate range of output values by sampling the curve only at the two points.
void get_curve_monotonic_sections(Curve &curve, std::vector<CurveMonotonicSection> &sections); void get_curve_monotonic_sections(Curve &curve, std::vector<CurveMonotonicSection> &sections);
// Gets the range of Y values for a range of X values on a curve, using precalculated monotonic segments // Gets the range of Y values for a range of X values on a curve, using precalculated monotonic segments
Interval get_curve_range(Curve &curve, const std::vector<CurveMonotonicSection> &sections, Interval x); math::Interval get_curve_range(Curve &curve, const std::vector<CurveMonotonicSection> &sections, math::Interval x);
// Legacy // Legacy
Interval get_curve_range(Curve &curve, bool &is_monotonic_increasing); math::Interval get_curve_range(Curve &curve, bool &is_monotonic_increasing);
Interval get_heightmap_range(Image &im); math::Interval get_heightmap_range(Image &im);
Interval get_heightmap_range(Image &im, Rect2i rect); math::Interval get_heightmap_range(Image &im, Rect2i rect);
namespace math { namespace math {
@ -91,10 +91,11 @@ struct Interval3 {
} // namespace math } // namespace math
Interval get_fnl_range_2d(const FastNoiseLite *noise, Interval x, Interval y); math::Interval get_fnl_range_2d(const FastNoiseLite *noise, math::Interval x, math::Interval y);
Interval get_fnl_range_3d(const FastNoiseLite *noise, Interval x, Interval y, Interval z); 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, Interval x, Interval y); 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, Interval x, Interval y, Interval z); math::Interval3 get_fnl_gradient_range_3d(
const FastNoiseLiteGradient *noise, math::Interval x, math::Interval y, math::Interval z);
} // namespace zylann } // namespace zylann

View File

@ -532,7 +532,7 @@ VoxelGenerator::Result VoxelGeneratorGraph::generate_block(VoxelBlockRequest &in
const Vector3i gmax = origin + (rmax << input.lod); const Vector3i gmax = origin + (rmax << input.lod);
runtime.analyze_range(cache.state, gmin, gmax); runtime.analyze_range(cache.state, gmin, gmax);
const Interval sdf_range = cache.state.get_range(sdf_output_buffer_index) * sdf_scale; const math::Interval sdf_range = cache.state.get_range(sdf_output_buffer_index) * sdf_scale;
bool sdf_is_uniform = false; bool sdf_is_uniform = false;
if (sdf_range.min > clip_threshold && sdf_range.max > clip_threshold) { if (sdf_range.min > clip_threshold && sdf_range.max > clip_threshold) {
out_buffer.fill_area_f(air_sdf, rmin, rmax, channel); out_buffer.fill_area_f(air_sdf, rmin, rmax, channel);
@ -1123,14 +1123,14 @@ VoxelSingleValue VoxelGeneratorGraph::generate_single(Vector3i position, unsigne
// Note, this wrapper may not be used for main generation tasks. // Note, this wrapper may not be used for main generation tasks.
// It is mostly used as a debug tool. // It is mostly used as a debug tool.
Interval VoxelGeneratorGraph::debug_analyze_range( math::Interval VoxelGeneratorGraph::debug_analyze_range(
Vector3i min_pos, Vector3i max_pos, bool optimize_execution_map) const { Vector3i min_pos, Vector3i max_pos, bool optimize_execution_map) const {
std::shared_ptr<const Runtime> runtime_ptr; std::shared_ptr<const Runtime> runtime_ptr;
{ {
RWLockRead rlock(_runtime_lock); RWLockRead rlock(_runtime_lock);
runtime_ptr = _runtime; runtime_ptr = _runtime;
} }
ERR_FAIL_COND_V(runtime_ptr == nullptr, Interval::from_single_value(0.f)); ERR_FAIL_COND_V(runtime_ptr == nullptr, math::Interval::from_single_value(0.f));
Cache &cache = _cache; Cache &cache = _cache;
const VoxelGraphRuntime &runtime = runtime_ptr->runtime; const VoxelGraphRuntime &runtime = runtime_ptr->runtime;
// Note, buffer size is irrelevant here, because range analysis doesn't use buffers // Note, buffer size is irrelevant here, because range analysis doesn't use buffers
@ -1505,7 +1505,7 @@ Vector2 VoxelGeneratorGraph::_b_debug_analyze_range(Vector3 min_pos, Vector3 max
ERR_FAIL_COND_V(min_pos.x > max_pos.x, Vector2()); ERR_FAIL_COND_V(min_pos.x > max_pos.x, Vector2());
ERR_FAIL_COND_V(min_pos.y > max_pos.y, Vector2()); ERR_FAIL_COND_V(min_pos.y > max_pos.y, Vector2());
ERR_FAIL_COND_V(min_pos.z > max_pos.z, Vector2()); ERR_FAIL_COND_V(min_pos.z > max_pos.z, Vector2());
const Interval r = const math::Interval r =
debug_analyze_range(Vector3iUtil::from_floored(min_pos), Vector3iUtil::from_floored(max_pos), false); debug_analyze_range(Vector3iUtil::from_floored(min_pos), Vector3iUtil::from_floored(max_pos), false);
return Vector2(r.min, r.max); return Vector2(r.min, r.max);
} }

View File

@ -168,7 +168,7 @@ public:
// Debug // Debug
Interval debug_analyze_range(Vector3i min_pos, Vector3i max_pos, bool optimize_execution_map) const; zylann::math::Interval debug_analyze_range(Vector3i min_pos, Vector3i max_pos, bool optimize_execution_map) const;
float debug_measure_microseconds_per_voxel(bool singular); float debug_measure_microseconds_per_voxel(bool singular);
void debug_load_waves_preset(); void debug_load_waves_preset();

View File

@ -18,6 +18,8 @@ namespace {
VoxelGraphNodeDB *g_node_type_db = nullptr; VoxelGraphNodeDB *g_node_type_db = nullptr;
} }
using namespace math;
template <typename F> inline void do_monop(VoxelGraphRuntime::ProcessBufferContext &ctx, F f) { template <typename F> inline void do_monop(VoxelGraphRuntime::ProcessBufferContext &ctx, F f) {
const VoxelGraphRuntime::Buffer &a = ctx.get_input(0); const VoxelGraphRuntime::Buffer &a = ctx.get_input(0);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0); VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
@ -312,7 +314,7 @@ VoxelGraphNodeDB::VoxelGraphNodeDB() {
const VoxelGraphRuntime::Buffer &input = ctx.get_input(0); const VoxelGraphRuntime::Buffer &input = ctx.get_input(0);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0); VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
for (unsigned int i = 0; i < out.size; ++i) { for (unsigned int i = 0; i < out.size; ++i) {
out.data[i] = clamp(input.data[i], 0.f, 1.f); out.data[i] = ::clamp(input.data[i], 0.f, 1.f);
} }
}; };
t.range_analysis_func = [](RangeAnalysisContext &ctx) { t.range_analysis_func = [](RangeAnalysisContext &ctx) {
@ -476,7 +478,7 @@ VoxelGraphNodeDB::VoxelGraphNodeDB() {
t.inputs.push_back(Port("length")); t.inputs.push_back(Port("length"));
t.outputs.push_back(Port("out")); t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) { t.process_buffer_func = [](ProcessBufferContext &ctx) {
do_binop(ctx, [](float a, float b) { return wrapf(a, b); }); do_binop(ctx, [](float a, float b) { return ::wrapf(a, b); });
}; };
t.range_analysis_func = [](RangeAnalysisContext &ctx) { t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0); const Interval a = ctx.get_input(0);
@ -532,7 +534,7 @@ VoxelGraphNodeDB::VoxelGraphNodeDB() {
const VoxelGraphRuntime::Buffer &y1 = ctx.get_input(3); const VoxelGraphRuntime::Buffer &y1 = ctx.get_input(3);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0); VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
for (uint32_t i = 0; i < out.size; ++i) { for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = Math::sqrt(squared(x1.data[i] - x0.data[i]) + squared(y1.data[i] - y0.data[i])); out.data[i] = Math::sqrt(::squared(x1.data[i] - x0.data[i]) + ::squared(y1.data[i] - y0.data[i]));
} }
}; };
t.range_analysis_func = [](RangeAnalysisContext &ctx) { t.range_analysis_func = [](RangeAnalysisContext &ctx) {
@ -566,8 +568,8 @@ VoxelGraphNodeDB::VoxelGraphNodeDB() {
const VoxelGraphRuntime::Buffer &z1 = ctx.get_input(5); const VoxelGraphRuntime::Buffer &z1 = ctx.get_input(5);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0); VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
for (uint32_t i = 0; i < out.size; ++i) { for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = Math::sqrt(squared(x1.data[i] - x0.data[i]) + squared(y1.data[i] - y0.data[i]) + out.data[i] = Math::sqrt(::squared(x1.data[i] - x0.data[i]) + ::squared(y1.data[i] - y0.data[i]) +
squared(z1.data[i] - z0.data[i])); ::squared(z1.data[i] - z0.data[i]));
} }
}; };
t.range_analysis_func = [](RangeAnalysisContext &ctx) { t.range_analysis_func = [](RangeAnalysisContext &ctx) {
@ -607,7 +609,7 @@ VoxelGraphNodeDB::VoxelGraphNodeDB() {
VoxelGraphRuntime::Buffer &out = ctx.get_output(0); VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const Params p = ctx.get_params<Params>(); const Params p = ctx.get_params<Params>();
for (uint32_t i = 0; i < out.size; ++i) { for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = clamp(a.data[i], p.min, p.max); out.data[i] = ::clamp(a.data[i], p.min, p.max);
} }
}; };
t.range_analysis_func = [](RangeAnalysisContext &ctx) { t.range_analysis_func = [](RangeAnalysisContext &ctx) {
@ -771,7 +773,7 @@ VoxelGraphNodeDB::VoxelGraphNodeDB() {
VoxelGraphRuntime::Buffer &out = ctx.get_output(0); VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const Params p = ctx.get_params<Params>(); const Params p = ctx.get_params<Params>();
for (uint32_t i = 0; i < out.size; ++i) { for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = smoothstep(p.edge0, p.edge1, a.data[i]); out.data[i] = ::smoothstep(p.edge0, p.edge1, a.data[i]);
} }
}; };
t.range_analysis_func = [](RangeAnalysisContext &ctx) { t.range_analysis_func = [](RangeAnalysisContext &ctx) {
@ -1030,7 +1032,8 @@ VoxelGraphNodeDB::VoxelGraphNodeDB() {
const VoxelGraphRuntime::Buffer &r = ctx.get_input(3); const VoxelGraphRuntime::Buffer &r = ctx.get_input(3);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0); VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
for (uint32_t i = 0; i < out.size; ++i) { for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = Math::sqrt(squared(x.data[i]) + squared(y.data[i]) + squared(z.data[i])) - r.data[i]; out.data[i] =
Math::sqrt(::squared(x.data[i]) + ::squared(y.data[i]) + ::squared(z.data[i])) - r.data[i];
} }
}; };
t.range_analysis_func = [](RangeAnalysisContext &ctx) { t.range_analysis_func = [](RangeAnalysisContext &ctx) {
@ -1386,7 +1389,7 @@ VoxelGraphNodeDB::VoxelGraphNodeDB() {
const float x = xb.data[i]; const float x = xb.data[i];
const float y = yb.data[i]; const float y = yb.data[i];
const float z = zb.data[i]; const float z = zb.data[i];
const float len = Math::sqrt(squared(x) + squared(y) + squared(z)); const float len = Math::sqrt(::squared(x) + ::squared(y) + ::squared(z));
out_nx.data[i] = x / len; out_nx.data[i] = x / len;
out_ny.data[i] = y / len; out_ny.data[i] = y / len;
out_nz.data[i] = z / len; out_nz.data[i] = z / len;

View File

@ -536,7 +536,7 @@ void VoxelGraphRuntime::generate_optimized_execution_map(
// The node is considered skippable, which means its outputs are either locally constant or unused. // The node is considered skippable, which means its outputs are either locally constant or unused.
// Unused buffers can be left as-is, but local constants must be filled in. // Unused buffers can be left as-is, but local constants must be filled in.
if (buffer.local_users_count > 0) { if (buffer.local_users_count > 0) {
const Interval range = state.ranges[output_address]; const math::Interval range = state.ranges[output_address];
// If this interval is not a single value then the node should not have been skippable // If this interval is not a single value then the node should not have been skippable
CRASH_COND(!range.is_single_value()); CRASH_COND(!range.is_single_value());
const float v = range.min; const float v = range.min;
@ -651,7 +651,7 @@ void VoxelGraphRuntime::prepare_state(State &state, unsigned int buffer_size) co
buffer.data[j] = bs.constant_value; buffer.data[j] = bs.constant_value;
} }
CRASH_COND(bs.address >= state.ranges.size()); CRASH_COND(bs.address >= state.ranges.size());
state.ranges[bs.address] = Interval::from_single_value(bs.constant_value); state.ranges[bs.address] = math::Interval::from_single_value(bs.constant_value);
} }
} }
@ -806,7 +806,7 @@ void VoxelGraphRuntime::analyze_range(State &state, Vector3i min_pos, Vector3i m
ERR_FAIL_COND(state.ranges.size() != _program.buffer_count); ERR_FAIL_COND(state.ranges.size() != _program.buffer_count);
#endif #endif
Span<Interval> ranges(state.ranges, 0, state.ranges.size()); Span<math::Interval> ranges(state.ranges, 0, state.ranges.size());
Span<Buffer> buffers(state.buffers, 0, state.buffers.size()); Span<Buffer> buffers(state.buffers, 0, state.buffers.size());
// Reset users count, as they might be decreased during the analysis // Reset users count, as they might be decreased during the analysis
@ -816,9 +816,9 @@ void VoxelGraphRuntime::analyze_range(State &state, Vector3i min_pos, Vector3i m
b.local_users_count = bs.users_count; b.local_users_count = bs.users_count;
} }
ranges[_program.x_input_address] = Interval(min_pos.x, max_pos.x); ranges[_program.x_input_address] = math::Interval(min_pos.x, max_pos.x);
ranges[_program.y_input_address] = Interval(min_pos.y, max_pos.y); ranges[_program.y_input_address] = math::Interval(min_pos.y, max_pos.y);
ranges[_program.z_input_address] = Interval(min_pos.z, max_pos.z); ranges[_program.z_input_address] = math::Interval(min_pos.z, max_pos.z);
const Span<const uint16_t> operations(_program.operations.data(), 0, _program.operations.size()); const Span<const uint16_t> operations(_program.operations.data(), 0, _program.operations.size());

View File

@ -76,7 +76,7 @@ public:
return buffers[address]; return buffers[address];
} }
inline const Interval get_range(uint16_t address) const { inline const math::Interval get_range(uint16_t address) const {
// TODO Just for convenience because STL bound checks aren't working in Godot 3 // TODO Just for convenience because STL bound checks aren't working in Godot 3
CRASH_COND(address >= buffers.size()); CRASH_COND(address >= buffers.size());
return ranges[address]; return ranges[address];
@ -98,7 +98,7 @@ public:
private: private:
friend class VoxelGraphRuntime; friend class VoxelGraphRuntime;
std::vector<Interval> ranges; std::vector<math::Interval> ranges;
std::vector<Buffer> buffers; std::vector<Buffer> buffers;
unsigned int buffer_size = 0; unsigned int buffer_size = 0;
@ -316,15 +316,15 @@ public:
class RangeAnalysisContext : public _ProcessContext { class RangeAnalysisContext : public _ProcessContext {
public: public:
inline RangeAnalysisContext(const Span<const uint16_t> inputs, const Span<const uint16_t> outputs, inline RangeAnalysisContext(const Span<const uint16_t> inputs, const Span<const uint16_t> outputs,
const Span<const uint8_t> params, Span<Interval> ranges, Span<Buffer> buffers) : const Span<const uint8_t> params, Span<math::Interval> ranges, Span<Buffer> buffers) :
_ProcessContext(inputs, outputs, params), _ranges(ranges), _buffers(buffers) {} _ProcessContext(inputs, outputs, params), _ranges(ranges), _buffers(buffers) {}
inline const Interval get_input(uint32_t i) const { inline const math::Interval get_input(uint32_t i) const {
const uint32_t address = get_input_address(i); const uint32_t address = get_input_address(i);
return _ranges[address]; return _ranges[address];
} }
inline void set_output(uint32_t i, const Interval r) { inline void set_output(uint32_t i, const math::Interval r) {
const uint32_t address = get_output_address(i); const uint32_t address = get_output_address(i);
_ranges[address] = r; _ranges[address] = r;
} }
@ -336,7 +336,7 @@ public:
} }
private: private:
Span<Interval> _ranges; Span<math::Interval> _ranges;
Span<Buffer> _buffers; Span<Buffer> _buffers;
}; };

View File

@ -906,19 +906,19 @@ void test_get_curve_monotonic_sections() {
ERR_FAIL_COND(sections[0].y_min != 0.f); ERR_FAIL_COND(sections[0].y_min != 0.f);
ERR_FAIL_COND(sections[0].y_max != 1.f); ERR_FAIL_COND(sections[0].y_max != 1.f);
{ {
Interval yi = get_curve_range(**curve, sections, Interval(0.f, 1.f)); math::Interval yi = get_curve_range(**curve, sections, math::Interval(0.f, 1.f));
ERR_FAIL_COND(!L::is_equal_approx(yi.min, 0.f)); ERR_FAIL_COND(!L::is_equal_approx(yi.min, 0.f));
ERR_FAIL_COND(!L::is_equal_approx(yi.max, 1.f)); ERR_FAIL_COND(!L::is_equal_approx(yi.max, 1.f));
} }
{ {
Interval yi = get_curve_range(**curve, sections, Interval(-2.f, 2.f)); math::Interval yi = get_curve_range(**curve, sections, math::Interval(-2.f, 2.f));
ERR_FAIL_COND(!L::is_equal_approx(yi.min, 0.f)); ERR_FAIL_COND(!L::is_equal_approx(yi.min, 0.f));
ERR_FAIL_COND(!L::is_equal_approx(yi.max, 1.f)); ERR_FAIL_COND(!L::is_equal_approx(yi.max, 1.f));
} }
{ {
Interval xi(0.2f, 0.8f); math::Interval xi(0.2f, 0.8f);
Interval yi = get_curve_range(**curve, sections, xi); math::Interval yi = get_curve_range(**curve, sections, xi);
Interval yi_expected(curve->interpolate_baked(xi.min), curve->interpolate_baked(xi.max)); math::Interval yi_expected(curve->interpolate_baked(xi.min), curve->interpolate_baked(xi.max));
ERR_FAIL_COND(!L::is_equal_approx(yi.min, yi_expected.min)); ERR_FAIL_COND(!L::is_equal_approx(yi.min, yi_expected.min));
ERR_FAIL_COND(!L::is_equal_approx(yi.max, yi_expected.max)); ERR_FAIL_COND(!L::is_equal_approx(yi.max, yi_expected.max));
} }

View File

@ -4,27 +4,23 @@
#include "funcs.h" #include "funcs.h"
#include <limits> #include <limits>
namespace zylann::math {
// For interval arithmetic // For interval arithmetic
struct Interval { struct Interval {
// Both inclusive // Both inclusive
float min; float min;
float max; float max;
inline Interval() : inline Interval() : min(0), max(0) {}
min(0),
max(0) {}
inline Interval(float p_min, float p_max) : inline Interval(float p_min, float p_max) : min(p_min), max(p_max) {
min(p_min),
max(p_max) {
#if DEBUG_ENABLED #if DEBUG_ENABLED
CRASH_COND(p_min > p_max); CRASH_COND(p_min > p_max);
#endif #endif
} }
inline Interval(const Interval &other) : inline Interval(const Interval &other) : min(other.min), max(other.max) {}
min(other.min),
max(other.max) {}
inline static Interval from_single_value(float p_val) { inline static Interval from_single_value(float p_val) {
return Interval(p_val, p_val); return Interval(p_val, p_val);
@ -35,9 +31,7 @@ struct Interval {
} }
inline static Interval from_infinity() { inline static Interval from_infinity() {
return Interval( return Interval(-std::numeric_limits<float>::infinity(), std::numeric_limits<float>::infinity());
-std::numeric_limits<float>::infinity(),
std::numeric_limits<float>::infinity());
} }
inline bool contains(float v) const { inline bool contains(float v) const {
@ -178,25 +172,17 @@ inline Interval max_interval(const Interval &a, const float b) {
} }
inline Interval sqrt(const Interval &i) { inline Interval sqrt(const Interval &i) {
return Interval{ return Interval{ Math::sqrt(::max(0.f, i.min)), Math::sqrt(::max(0.f, i.max)) };
Math::sqrt(::max(0.f, i.min)),
Math::sqrt(::max(0.f, i.max))
};
} }
inline Interval abs(const Interval &i) { inline Interval abs(const Interval &i) {
return Interval{ return Interval{ i.contains(0) ? 0 : ::min(Math::abs(i.min), Math::abs(i.max)),
i.contains(0) ? 0 : ::min(Math::abs(i.min), Math::abs(i.max)), ::max(Math::abs(i.min), Math::abs(i.max)) };
::max(Math::abs(i.min), Math::abs(i.max))
};
} }
inline Interval clamp(const Interval &i, const Interval &p_min, const Interval &p_max) { inline Interval clamp(const Interval &i, const Interval &p_min, const Interval &p_max) {
if (p_min.is_single_value() && p_max.is_single_value()) { if (p_min.is_single_value() && p_max.is_single_value()) {
return { return { ::clamp(i.min, p_min.min, p_max.min), ::clamp(i.max, p_min.min, p_max.min) };
::clamp(i.min, p_min.min, p_max.min),
::clamp(i.max, p_min.min, p_max.min)
};
} }
if (i.min >= p_min.max && i.max <= p_max.min) { if (i.min >= p_min.max && i.max <= p_max.min) {
return i; return i;
@ -224,9 +210,7 @@ inline Interval lerp(const Interval &a, const Interval &b, const Interval &t) {
const float v6 = a.min + t.max * (b.max - a.min); const float v6 = a.min + t.max * (b.max - a.min);
const float v7 = a.max + t.max * (b.max - a.max); const float v7 = a.max + t.max * (b.max - a.max);
return Interval( return Interval(min(v0, v1, v2, v3, v4, v5, v6, v7), max(v0, v1, v2, v3, v4, v5, v6, v7));
min(v0, v1, v2, v3, v4, v5, v6, v7),
max(v0, v1, v2, v3, v4, v5, v6, v7));
} }
inline Interval sin(const Interval &i) { inline Interval sin(const Interval &i) {
@ -354,8 +338,8 @@ inline Interval smoothstep(float p_from, float p_to, Interval p_weight) {
return Interval::from_single_value(p_from); return Interval::from_single_value(p_from);
} }
// Smoothstep is monotonic // Smoothstep is monotonic
float v0 = smoothstep(p_from, p_to, p_weight.min); float v0 = ::smoothstep(p_from, p_to, p_weight.min);
float v1 = smoothstep(p_from, p_to, p_weight.max); float v1 = ::smoothstep(p_from, p_to, p_weight.max);
if (v0 <= v1) { if (v0 <= v1) {
return Interval(v0, v1); return Interval(v0, v1);
} else { } else {
@ -431,4 +415,6 @@ inline Interval get_length(const Interval &x, const Interval &y, const Interval
return sqrt(squared(x) + squared(y) + squared(z)); return sqrt(squared(x) + squared(y) + squared(z));
} }
} //namespace zylann::math
#endif // INTERVAL_H #endif // INTERVAL_H

View File

@ -48,13 +48,13 @@ inline float sdf_subtract(float a, float b) {
} }
inline float sdf_smooth_union(float a, float b, float s) { inline float sdf_smooth_union(float a, float b, float s) {
float h = clamp(0.5f + 0.5f * (b - a) / s, 0.0f, 1.0f); float h = ::clamp(0.5f + 0.5f * (b - a) / s, 0.0f, 1.0f);
return Math::lerp(b, a, h) - s * h * (1.0f - h); return Math::lerp(b, a, h) - s * h * (1.0f - h);
} }
// Inverted a and b because it subtracts SDF a from SDF b // Inverted a and b because it subtracts SDF a from SDF b
inline float sdf_smooth_subtract(float b, float a, float s) { inline float sdf_smooth_subtract(float b, float a, float s) {
float h = clamp(0.5f - 0.5f * (b + a) / s, 0.0f, 1.0f); float h = ::clamp(0.5f - 0.5f * (b + a) / s, 0.0f, 1.0f);
return Math::lerp(b, -a, h) + s * h * (1.0f - h); return Math::lerp(b, -a, h) + s * h * (1.0f - h);
} }

View File

@ -465,14 +465,14 @@ void FastNoise2::update_generator() {
_generator = generator_node; _generator = generator_node;
} }
Interval FastNoise2::get_estimated_output_range() const { zylann::math::Interval FastNoise2::get_estimated_output_range() const {
// TODO Optimize: better range analysis on FastNoise2 // TODO Optimize: better range analysis on FastNoise2
// Most noises should have known bounds like FastNoiseLite, but the node-graph nature of this library // Most noises should have known bounds like FastNoiseLite, but the node-graph nature of this library
// can make it difficult to calculate. Would be nice if the library could provide that out of the box. // can make it difficult to calculate. Would be nice if the library could provide that out of the box.
if (is_remap_enabled()) { if (is_remap_enabled()) {
return Interval(get_remap_output_min(), get_remap_output_max()); return zylann::math::Interval(get_remap_output_min(), get_remap_output_max());
} else { } else {
return Interval(-1.f, 1.f); return zylann::math::Interval(-1.f, 1.f);
} }
} }

View File

@ -167,7 +167,7 @@ public:
void generate_image(Ref<Image> image, bool tileable) const; void generate_image(Ref<Image> image, bool tileable) const;
Interval get_estimated_output_range() const; zylann::math::Interval get_estimated_output_range() const;
private: private:
// Non-static method for scripts because Godot4 does not support binding static methods (it's only implemented for // Non-static method for scripts because Godot4 does not support binding static methods (it's only implemented for