148 lines
4.6 KiB
C++
148 lines
4.6 KiB
C++
#include "range_utility.h"
|
|
|
|
#include <core/io/image.h>
|
|
#include <scene/resources/curve.h>
|
|
|
|
namespace zylann {
|
|
|
|
using namespace math;
|
|
|
|
// Curve ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void get_curve_monotonic_sections(Curve &curve, std::vector<CurveMonotonicSection> §ions) {
|
|
const int res = curve.get_bake_resolution();
|
|
float prev_y = curve.interpolate_baked(0.f);
|
|
|
|
sections.clear();
|
|
CurveMonotonicSection section;
|
|
section.x_min = 0.f;
|
|
section.y_min = curve.interpolate_baked(0.f);
|
|
|
|
float prev_x = 0.f;
|
|
bool current_stationary = true;
|
|
bool current_increasing = false;
|
|
|
|
for (int i = 1; i < res; ++i) {
|
|
const float x = static_cast<float>(i) / res;
|
|
const float y = curve.interpolate_baked(x);
|
|
// Curve can sometimes appear flat but it still oscillates by very small amounts due to float imprecision
|
|
// which occurred during bake(). Attempting to workaround that by taking the error into account
|
|
const bool increasing = y > prev_y + CURVE_RANGE_MARGIN;
|
|
const bool decreasing = y < prev_y - CURVE_RANGE_MARGIN;
|
|
const bool stationary = increasing == false && decreasing == false;
|
|
|
|
if (current_stationary) {
|
|
current_stationary = stationary;
|
|
current_increasing = increasing;
|
|
|
|
} else if (i > 1 && !stationary && increasing != current_increasing) {
|
|
section.x_max = prev_x;
|
|
section.y_max = prev_y;
|
|
sections.push_back(section);
|
|
|
|
section.x_min = prev_x;
|
|
section.y_min = prev_y;
|
|
current_stationary = current_stationary;
|
|
current_increasing = increasing;
|
|
}
|
|
|
|
prev_x = x;
|
|
prev_y = y;
|
|
}
|
|
|
|
// Forcing 1 because the iteration doesn't go up to `res`
|
|
section.x_max = 1.f;
|
|
section.y_max = prev_y;
|
|
sections.push_back(section);
|
|
}
|
|
|
|
Interval get_curve_range(Curve &curve, const std::vector<CurveMonotonicSection> §ions, Interval x) {
|
|
// This implementation is linear. It assumes curves usually don't have many points.
|
|
// If a curve has too many points, we may consider dynamically choosing a different algorithm.
|
|
Interval y;
|
|
unsigned int i = 0;
|
|
if (x.min < sections[0].x_min) {
|
|
// X range starts before the curve's minimum X
|
|
y = Interval::from_single_value(curve.interpolate_baked(0.f));
|
|
} else {
|
|
// Find section from where the range starts
|
|
for (; i < sections.size(); ++i) {
|
|
const CurveMonotonicSection §ion = sections[i];
|
|
if (x.min >= section.x_min) {
|
|
const float begin_y = curve.interpolate_baked(x.min);
|
|
if (x.max < section.x_max) {
|
|
// X range starts and ends in that section
|
|
return Interval::from_unordered_values(begin_y, curve.interpolate_baked(x.max))
|
|
.padded(CURVE_RANGE_MARGIN);
|
|
} else {
|
|
// X range starts in that section, and continues after it.
|
|
// Will need to keep iterating, starting from here
|
|
y = Interval::from_unordered_values(begin_y, curve.interpolate_baked(section.x_max));
|
|
++i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (; i < sections.size(); ++i) {
|
|
const CurveMonotonicSection §ion = sections[i];
|
|
if (x.max >= section.x_max) {
|
|
// X range covers this whole section and maybe more after it
|
|
y.add_interval(Interval::from_unordered_values(section.y_min, section.y_max));
|
|
} else {
|
|
// X range ends in that section
|
|
y.add_interval(Interval::from_unordered_values(section.y_min, curve.interpolate_baked(x.max)));
|
|
break;
|
|
}
|
|
}
|
|
return y.padded(CURVE_RANGE_MARGIN);
|
|
}
|
|
|
|
Interval get_curve_range(Curve &curve, bool &is_monotonic_increasing) {
|
|
// TODO Would be nice to have the cache directly
|
|
const int res = curve.get_bake_resolution();
|
|
Interval range;
|
|
float prev_v = curve.interpolate_baked(0.f);
|
|
if (curve.interpolate_baked(1.f) > prev_v) {
|
|
is_monotonic_increasing = true;
|
|
}
|
|
for (int i = 0; i < res; ++i) {
|
|
const float v = curve.interpolate_baked(static_cast<float>(i) / res);
|
|
range.add_point(v);
|
|
if (v < prev_v) {
|
|
is_monotonic_increasing = false;
|
|
}
|
|
prev_v = v;
|
|
}
|
|
return range;
|
|
}
|
|
|
|
// Heightmaps //////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Interval get_heightmap_range(const Image &im) {
|
|
return get_heightmap_range(im, Rect2i(0, 0, im.get_width(), im.get_height()));
|
|
}
|
|
|
|
Interval get_heightmap_range(const Image &im, Rect2i rect) {
|
|
ERR_FAIL_COND_V_MSG(
|
|
im.is_compressed(), Interval(), String("Image format not supported: {0}").format(varray(im.get_format())));
|
|
|
|
Interval r;
|
|
|
|
r.min = im.get_pixel(0, 0).r;
|
|
r.max = r.min;
|
|
|
|
const int max_x = rect.position.x + rect.size.x;
|
|
const int max_y = rect.position.y + rect.size.y;
|
|
|
|
for (int y = rect.position.y; y < max_y; ++y) {
|
|
for (int x = rect.position.x; x < max_x; ++x) {
|
|
r.add_point(im.get_pixel(x, y).r);
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
} // namespace zylann
|