From 4626b4d7a3687c7cec17024e2e5215887bcbd1d3 Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Sun, 26 Jan 2020 20:08:25 +0000 Subject: [PATCH] Added iso_scale parameter to heightmap generators, which eliminates terracing. As a result, sdf_mode is now useless and was removed. --- streams/voxel_stream_heightmap.cpp | 43 ++--- streams/voxel_stream_heightmap.h | 72 ++++----- streams/voxel_stream_image.cpp | 29 +++- streams/voxel_stream_image.h | 5 + streams/voxel_stream_noise_2d.cpp | 4 +- util/heightmap_sdf.cpp | 42 ----- util/heightmap_sdf.h | 244 ----------------------------- 7 files changed, 79 insertions(+), 360 deletions(-) delete mode 100644 util/heightmap_sdf.cpp delete mode 100644 util/heightmap_sdf.h diff --git a/streams/voxel_stream_heightmap.cpp b/streams/voxel_stream_heightmap.cpp index 4dd10422..d0ea1546 100644 --- a/streams/voxel_stream_heightmap.cpp +++ b/streams/voxel_stream_heightmap.cpp @@ -3,46 +3,39 @@ #include "../util/fixed_array.h" VoxelStreamHeightmap::VoxelStreamHeightmap() { - _heightmap.settings.range.base = -50.0; - _heightmap.settings.range.span = 200.0; - _heightmap.settings.mode = HeightmapSdf::SDF_VERTICAL_AVERAGE; } void VoxelStreamHeightmap::set_channel(VoxelBuffer::ChannelId channel) { ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS); _channel = channel; - if (_channel != VoxelBuffer::CHANNEL_SDF) { - _heightmap.clear_cache(); - } } VoxelBuffer::ChannelId VoxelStreamHeightmap::get_channel() const { return _channel; } -void VoxelStreamHeightmap::set_sdf_mode(SdfMode mode) { - ERR_FAIL_INDEX(mode, SDF_MODE_COUNT); - _heightmap.settings.mode = (HeightmapSdf::Mode)mode; -} - -VoxelStreamHeightmap::SdfMode VoxelStreamHeightmap::get_sdf_mode() const { - return (VoxelStreamHeightmap::SdfMode)_heightmap.settings.mode; -} - void VoxelStreamHeightmap::set_height_start(float start) { - _heightmap.settings.range.base = start; + _range.start = start; } float VoxelStreamHeightmap::get_height_start() const { - return _heightmap.settings.range.base; + return _range.start; } void VoxelStreamHeightmap::set_height_range(float range) { - _heightmap.settings.range.span = range; + _range.height = range; } float VoxelStreamHeightmap::get_height_range() const { - return _heightmap.settings.range.span; + return _range.height; +} + +void VoxelStreamHeightmap::set_iso_scale(float iso_scale) { + _iso_scale = iso_scale; +} + +float VoxelStreamHeightmap::get_iso_scale() const { + return _iso_scale; } void VoxelStreamHeightmap::_bind_methods() { @@ -50,21 +43,17 @@ void VoxelStreamHeightmap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_channel", "channel"), &VoxelStreamHeightmap::set_channel); ClassDB::bind_method(D_METHOD("get_channel"), &VoxelStreamHeightmap::get_channel); - ClassDB::bind_method(D_METHOD("set_sdf_mode", "mode"), &VoxelStreamHeightmap::set_sdf_mode); - ClassDB::bind_method(D_METHOD("get_sdf_mode"), &VoxelStreamHeightmap::get_sdf_mode); - ClassDB::bind_method(D_METHOD("set_height_start", "start"), &VoxelStreamHeightmap::set_height_start); ClassDB::bind_method(D_METHOD("get_height_start"), &VoxelStreamHeightmap::get_height_start); ClassDB::bind_method(D_METHOD("set_height_range", "range"), &VoxelStreamHeightmap::set_height_range); ClassDB::bind_method(D_METHOD("get_height_range"), &VoxelStreamHeightmap::get_height_range); + ClassDB::bind_method(D_METHOD("set_iso_scale", "scale"), &VoxelStreamHeightmap::set_iso_scale); + ClassDB::bind_method(D_METHOD("get_iso_scale"), &VoxelStreamHeightmap::get_iso_scale); + ADD_PROPERTY(PropertyInfo(Variant::INT, "channel", PROPERTY_HINT_ENUM, VoxelBuffer::CHANNEL_ID_HINT_STRING), "set_channel", "get_channel"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "sdf_mode", PROPERTY_HINT_ENUM, HeightmapSdf::MODE_HINT_STRING), "set_sdf_mode", "get_sdf_mode"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "height_start"), "set_height_start", "get_height_start"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "height_range"), "set_height_range", "get_height_range"); - - BIND_ENUM_CONSTANT(SDF_VERTICAL); - BIND_ENUM_CONSTANT(SDF_VERTICAL_AVERAGE); - BIND_ENUM_CONSTANT(SDF_SEGMENT); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "iso_scale"), "set_iso_scale", "get_iso_scale"); } diff --git a/streams/voxel_stream_heightmap.h b/streams/voxel_stream_heightmap.h index cba3bd9e..254d71de 100644 --- a/streams/voxel_stream_heightmap.h +++ b/streams/voxel_stream_heightmap.h @@ -1,7 +1,6 @@ #ifndef VOXEL_STREAM_HEIGHTMAP_H #define VOXEL_STREAM_HEIGHTMAP_H -#include "../util/heightmap_sdf.h" #include "voxel_stream.h" #include @@ -10,38 +9,31 @@ class VoxelStreamHeightmap : public VoxelStream { public: VoxelStreamHeightmap(); - enum SdfMode { - SDF_VERTICAL = HeightmapSdf::SDF_VERTICAL, - SDF_VERTICAL_AVERAGE = HeightmapSdf::SDF_VERTICAL_AVERAGE, - SDF_SEGMENT = HeightmapSdf::SDF_SEGMENT, - SDF_MODE_COUNT = HeightmapSdf::SDF_MODE_COUNT - }; - void set_channel(VoxelBuffer::ChannelId channel); VoxelBuffer::ChannelId get_channel() const; - void set_sdf_mode(SdfMode mode); - SdfMode get_sdf_mode() const; - void set_height_start(float start); float get_height_start() const; void set_height_range(float range); float get_height_range() const; + void set_iso_scale(float iso_scale); + float get_iso_scale() const; + protected: template - void generate(VoxelBuffer &out_buffer, Height_F height_func, int ox, int oy, int oz, int lod) { + void generate(VoxelBuffer &out_buffer, Height_F height_func, Vector3i origin, int lod) { const int channel = _channel; const Vector3i bs = out_buffer.get_size(); bool use_sdf = channel == VoxelBuffer::CHANNEL_SDF; - if (oy > get_height_start() + get_height_range()) { + if (origin.y > get_height_start() + get_height_range()) { // The bottom of the block is above the highest ground can go (default is air) return; } - if (oy + (bs.y << lod) < get_height_start()) { + if (origin.y + (bs.y << lod) < get_height_start()) { // The top of the block is below the lowest ground can go out_buffer.clear_channel(_channel, use_sdf ? 0 : _matter_type); return; @@ -51,28 +43,17 @@ protected: if (use_sdf) { - if (lod == 0) { - // When sampling SDF, we may need to precompute values to speed it up depending on the chosen mode. - // Unfortunately, only LOD0 can use a cache. lower lods would require a much larger one, - // otherwise it would interpolate along higher stride, thus voxel values depend on LOD, and then cause discontinuities. - _heightmap.build_cache(height_func, bs.x, bs.z, ox, oz, stride); - } + int gz = origin.z; + for (int z = 0; z < bs.z; ++z, gz += stride) { - for (int z = 0; z < bs.z; ++z) { - for (int x = 0; x < bs.x; ++x) { + int gx = origin.x; + for (int x = 0; x < bs.x; ++x, gx += stride) { - // SDF may vary along the column so we use a helper for more precision - - if (lod == 0) { - _heightmap.get_column_from_cache( - [&out_buffer, x, z, channel](int ly, float v) { out_buffer.set_voxel_f(v, x, ly, z, channel); }, - x, oy, z, bs.y, stride); - } else { - HeightmapSdf::get_column_stateless( - [&out_buffer, x, z, channel](int ly, float v) { out_buffer.set_voxel_f(v, x, ly, z, channel); }, - [&height_func, this](int lx, int lz) { return _heightmap.settings.range.xform(height_func(lx, lz)); }, - _heightmap.settings.mode, - ox + (x << lod), oy, oz + (z << lod), stride, bs.y); + float h = _range.xform(height_func(gx, gz)); + int gy = origin.y; + for (int y = 0; y < bs.y; ++y, gy += stride) { + float sdf = _iso_scale * (gy - h); + out_buffer.set_voxel_f(sdf, x, y, z, channel); } } // for x @@ -81,15 +62,15 @@ protected: } else { // Blocky - int gz = oz; + int gz = origin.z; for (int z = 0; z < bs.z; ++z, gz += stride) { - int gx = ox; + int gx = origin.x; for (int x = 0; x < bs.x; ++x, gx += stride) { // Output is blocky, so we can go for just one sample - float h = _heightmap.settings.range.xform(height_func(gx, gz)); - h -= oy; + float h = _range.xform(height_func(gx, gz)); + h -= origin.y; int ih = int(h); if (ih > 0) { if (ih > bs.y) { @@ -106,12 +87,19 @@ protected: private: static void _bind_methods(); -private: - HeightmapSdf _heightmap; + struct Range { + float start = -50; + float height = 200; + + inline float xform(float x) const { + return x * height + start; + } + }; + VoxelBuffer::ChannelId _channel = VoxelBuffer::CHANNEL_TYPE; int _matter_type = 1; + Range _range; + float _iso_scale = 0.1; }; -VARIANT_ENUM_CAST(VoxelStreamHeightmap::SdfMode) - #endif // VOXEL_STREAM_HEIGHTMAP_H diff --git a/streams/voxel_stream_image.cpp b/streams/voxel_stream_image.cpp index d07811b1..bba2eb64 100644 --- a/streams/voxel_stream_image.cpp +++ b/streams/voxel_stream_image.cpp @@ -8,6 +8,15 @@ inline float get_height_repeat(Image &im, int x, int y) { return im.get_pixel(wrap(x, im.get_width()), wrap(y, im.get_height())).r; } +inline float get_height_blurred(Image &im, int x, int y) { + float h = get_height_repeat(im, x, y); + h += get_height_repeat(im, x + 1, y); + h += get_height_repeat(im, x - 1, y); + h += get_height_repeat(im, x, y + 1); + h += get_height_repeat(im, x, y - 1); + return h * 0.2f; +} + } // namespace VoxelStreamImage::VoxelStreamImage() { @@ -21,6 +30,14 @@ Ref VoxelStreamImage::get_image() const { return _image; } +void VoxelStreamImage::set_blur_enabled(bool enable) { + _blur_enabled = enable; +} + +bool VoxelStreamImage::is_blur_enabled() const { + return _blur_enabled; +} + void VoxelStreamImage::emerge_block(Ref p_out_buffer, Vector3i origin_in_voxels, int lod) { ERR_FAIL_COND(_image.is_null()); @@ -30,9 +47,15 @@ void VoxelStreamImage::emerge_block(Ref p_out_buffer, Vector3i orig image.lock(); - VoxelStreamHeightmap::generate(out_buffer, - [&image](int x, int z) { return get_height_repeat(image, x, z); }, - origin_in_voxels.x, origin_in_voxels.y, origin_in_voxels.z, lod); + if (_blur_enabled) { + VoxelStreamHeightmap::generate(out_buffer, + [&image](int x, int z) { return get_height_blurred(image, x, z); }, + origin_in_voxels, lod); + } else { + VoxelStreamHeightmap::generate(out_buffer, + [&image](int x, int z) { return get_height_repeat(image, x, z); }, + origin_in_voxels, lod); + } image.unlock(); diff --git a/streams/voxel_stream_image.h b/streams/voxel_stream_image.h index 3b0cdc66..6c7255d2 100644 --- a/streams/voxel_stream_image.h +++ b/streams/voxel_stream_image.h @@ -13,6 +13,9 @@ public: void set_image(Ref im); Ref get_image() const; + void set_blur_enabled(bool enable); + bool is_blur_enabled() const; + void emerge_block(Ref p_out_buffer, Vector3i origin_in_voxels, int lod); private: @@ -20,6 +23,8 @@ private: private: Ref _image; + // Mostly here as demo/tweak. It's better recommended to use an EXR/float image. + bool _blur_enabled = false; }; #endif // HEADER_VOXEL_STREAM_IMAGE diff --git a/streams/voxel_stream_noise_2d.cpp b/streams/voxel_stream_noise_2d.cpp index 247b3666..4cdee7ea 100644 --- a/streams/voxel_stream_noise_2d.cpp +++ b/streams/voxel_stream_noise_2d.cpp @@ -29,12 +29,12 @@ void VoxelStreamNoise2D::emerge_block(Ref p_out_buffer, Vector3i or if (_curve.is_null()) { VoxelStreamHeightmap::generate(out_buffer, [&noise](int x, int z) { return 0.5 + 0.5 * noise.get_noise_2d(x, z); }, - origin_in_voxels.x, origin_in_voxels.y, origin_in_voxels.z, lod); + origin_in_voxels, lod); } else { Curve &curve = **_curve; VoxelStreamHeightmap::generate(out_buffer, [&noise, &curve](int x, int z) { return curve.interpolate_baked(0.5 + 0.5 * noise.get_noise_2d(x, z)); }, - origin_in_voxels.x, origin_in_voxels.y, origin_in_voxels.z, lod); + origin_in_voxels, lod); } out_buffer.compress_uniform_channels(); diff --git a/util/heightmap_sdf.cpp b/util/heightmap_sdf.cpp deleted file mode 100644 index 8d0d7351..00000000 --- a/util/heightmap_sdf.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "heightmap_sdf.h" - -const char *HeightmapSdf::MODE_HINT_STRING = "Vertical,VerticalAverage,Segment"; - -float HeightmapSdf::get_constrained_segment_sdf(float p_yp, float p_ya, float p_yb, float p_xb) { - - // P - // . B - // . / - // . / y - // ./ | - // A o--x - - float s = p_yp >= p_ya ? 1 : -1; - - if (Math::absf(p_yp - p_ya) > 1.f && Math::absf(p_yp - p_yb) > 1.f) { - return s; - } - - Vector2 p(0, p_yp); - Vector2 a(0, p_ya); - Vector2 b(p_xb, p_yb); - Vector2 closest_point; - - // TODO Optimize given the particular case we are in - Vector2 n = b - a; - real_t l2 = n.length_squared(); - if (l2 < 1e-20) { - closest_point = a; // Both points are the same, just give any. - } else { - real_t d = n.dot(p - a) / l2; - if (d <= 0.0) { - closest_point = a; // Before first point. - } else if (d >= 1.0) { - closest_point = b; // After first point. - } else { - closest_point = a + n * d; // Inside. - } - } - - return s * closest_point.distance_to(p); -} diff --git a/util/heightmap_sdf.h b/util/heightmap_sdf.h deleted file mode 100644 index 37d9a7aa..00000000 --- a/util/heightmap_sdf.h +++ /dev/null @@ -1,244 +0,0 @@ -#ifndef HEIGHTMAP_SDF_H -#define HEIGHTMAP_SDF_H - -#include "array_slice.h" -#include -#include - -// Utility class to sample a heightmap as a 3D distance field. -// Provides a stateless function, or an accelerated area method using a cache. -// Note: this isn't general-purpose, it has been made for several use cases found in this module. -class HeightmapSdf { -public: - enum Mode { - SDF_VERTICAL = 0, // Lowest quality, fastest - SDF_VERTICAL_AVERAGE, - SDF_SEGMENT, - SDF_MODE_COUNT - }; - - static const char *MODE_HINT_STRING; - - struct Range { - float base = -50; - float span = 200; - - inline float xform(float x) const { - return x * span + base; - } - }; - - struct Settings { - Mode mode = SDF_VERTICAL; - Range range; - }; - - struct Cache { - std::vector heights; - int size_z = 0; - - inline float get_local(int x, int z) const { - const int i = x + z * size_z; -#ifdef TOOLS_ENABLED - CRASH_COND(i >= heights.size()); -#endif - return heights[i]; - } - }; - - Settings settings; - - // Precomputes data to accelerate the next area fetch. - // ox, oz and stride are in world space. - // Coordinates sent to the height function are in world space. - template - void build_cache(Height_F height_func, int cache_size_x, int cache_size_z, int ox, int oz, int stride) { - - CRASH_COND(cache_size_x < 0); - CRASH_COND(cache_size_z < 0); - - if (settings.mode == SDF_SEGMENT) { - // Pad - cache_size_x += 2; - cache_size_z += 2; - ox -= stride; - oz -= stride; - } - - unsigned int area = cache_size_x * cache_size_z; - if (area != _cache.heights.size()) { - _cache.heights.resize(area); - } - _cache.size_z = cache_size_z; - - int i = 0; - int gz = oz; - - for (int z = 0; z < cache_size_z; ++z, gz += stride) { - int gx = ox; - - for (int x = 0; x < cache_size_x; ++x, gx += stride) { - - switch (settings.mode) { - - case SDF_VERTICAL: - case SDF_SEGMENT: - _cache.heights[i++] = settings.range.xform(height_func(gx, gz)); - break; - - case SDF_VERTICAL_AVERAGE: - _cache.heights[i++] = settings.range.xform(get_height_blurred(height_func, gx, gz)); - break; - - default: - CRASH_NOW(); - break; - } - } - } - } - - void clear_cache() { - _cache.heights.clear(); - } - - // Core functionality is here. - // Slower than using a cache, but doesn't rely on heap memory. - // fx and fz use the same coordinate space as the height function. - // gy0 and stride are world space. - // Coordinates sent to the output function are in grid space. - template - static void get_column_stateless(Output_F output_func, Height_F height_func, Mode mode, int fx, int gy0, int fz, int stride, int size_y) { - - switch (mode) { - - case SDF_VERTICAL: { - float h = height_func(fx, fz); - int gy = gy0; - for (int y = 0; y < size_y; ++y, gy += stride) { - float sdf = gy - h; - output_func(y, sdf); - } - } break; - - case SDF_VERTICAL_AVERAGE: { - float h = get_height_blurred(height_func, fx, fz); - int gy = gy0; - for (int y = 0; y < size_y; ++y, gy += stride) { - float sdf = gy - h; - output_func(y, sdf); - } - } break; - - case SDF_SEGMENT: { - // Calculate distance to 8 segments going from the point at XZ to its neighbor points, - // and pick the smallest distance. - // Note: stride is intentionally not used for neighbor sampling. - // More than 1 isn't really supported, because it causes inconsistencies when nearest-neighbor LOD is used. - - float h0 = height_func(fx - 1, fz - 1); - float h1 = height_func(fx, fz - 1); - float h2 = height_func(fx + 1, fz - 1); - - float h3 = height_func(fx - 1, fz); - float h4 = height_func(fx, fz); - float h5 = height_func(fx + 1, fz); - - float h6 = height_func(fx - 1, fz + 1); - float h7 = height_func(fx, fz + 1); - float h8 = height_func(fx + 1, fz + 1); - - const float sqrt2 = 1.414213562373095; - - int gy = gy0; - for (int y = 0; y < size_y; ++y, gy += stride) { - - float sdf0 = get_constrained_segment_sdf(gy, h4, h0, sqrt2); - float sdf1 = get_constrained_segment_sdf(gy, h4, h1, 1); - float sdf2 = get_constrained_segment_sdf(gy, h4, h2, sqrt2); - - float sdf3 = get_constrained_segment_sdf(gy, h4, h3, 1); - float sdf4 = gy - h4; - float sdf5 = get_constrained_segment_sdf(gy, h4, h5, 1); - - float sdf6 = get_constrained_segment_sdf(gy, h4, h6, sqrt2); - float sdf7 = get_constrained_segment_sdf(gy, h4, h7, 1); - float sdf8 = get_constrained_segment_sdf(gy, h4, h8, sqrt2); - - float sdf = sdf4; - - if (Math::absf(sdf0) < Math::absf(sdf)) { - sdf = sdf0; - } - if (Math::absf(sdf1) < Math::absf(sdf)) { - sdf = sdf1; - } - if (Math::absf(sdf2) < Math::absf(sdf)) { - sdf = sdf2; - } - if (Math::absf(sdf3) < Math::absf(sdf)) { - sdf = sdf3; - } - if (Math::absf(sdf5) < Math::absf(sdf)) { - sdf = sdf5; - } - if (Math::absf(sdf6) < Math::absf(sdf)) { - sdf = sdf6; - } - if (Math::absf(sdf7) < Math::absf(sdf)) { - sdf = sdf7; - } - if (Math::absf(sdf8) < Math::absf(sdf)) { - sdf = sdf8; - } - - output_func(y, sdf); - } - } break; - - default: - CRASH_NOW(); - break; - - } // sdf mode - } - - // Fastest if a cache has been built before. Prefer this when fetching areas. - // Coordinates sent to the output function are in grid space. - template - inline void get_column_from_cache(Output_F output_func, int grid_x, int world_y0, int grid_z, int grid_size_y, int stride) { - - Mode mode = settings.mode; - - if (mode == SDF_VERTICAL_AVERAGE) { - // Precomputed in cache, sample directly - mode = SDF_VERTICAL; - - } else if (mode == SDF_SEGMENT) { - // Pad - ++grid_x; - ++grid_z; - } - - get_column_stateless(output_func, - [&](int x, int z) { return _cache.get_local(x, z); }, - mode, grid_x, world_y0, grid_z, stride, grid_size_y); - } - -private: - static float get_constrained_segment_sdf(float p_yp, float p_ya, float p_yb, float p_xb); - - template - static inline float get_height_blurred(Height_F height_func, int x, int y) { - float h = height_func(x, y); - h += height_func(x + 1, y); - h += height_func(x - 1, y); - h += height_func(x, y + 1); - h += height_func(x, y - 1); - return h * 0.2f; - } - - Cache _cache; -}; - -#endif // HEIGHTMAP_SDF_H