Made all simple generators thread-safe

This commit is contained in:
Marc Gilleron 2021-01-16 13:41:46 +00:00
parent 082cdf3e16
commit a0941cea83
14 changed files with 308 additions and 121 deletions

View File

@ -1,54 +1,81 @@
#include "voxel_generator_flat.h"
VoxelGeneratorFlat::VoxelGeneratorFlat() {
_parameters_lock = RWLock::create();
}
VoxelGeneratorFlat::~VoxelGeneratorFlat() {
memdelete(_parameters_lock);
}
void VoxelGeneratorFlat::set_channel(VoxelBuffer::ChannelId channel) {
ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS);
if (_channel != channel) {
_channel = channel;
bool changed = false;
{
RWLockWrite wlock(_parameters_lock);
if (_parameters.channel != channel) {
_parameters.channel = channel;
}
}
if (changed) {
emit_changed();
}
}
VoxelBuffer::ChannelId VoxelGeneratorFlat::get_channel() const {
return _channel;
RWLockRead rlock(_parameters_lock);
return _parameters.channel;
}
int VoxelGeneratorFlat::get_used_channels_mask() const {
return (1 << _channel);
RWLockRead rlock(_parameters_lock);
return (1 << _parameters.channel);
}
void VoxelGeneratorFlat::set_voxel_type(int t) {
_voxel_type = t;
RWLockWrite wlock(_parameters_lock);
_parameters.voxel_type = t;
}
int VoxelGeneratorFlat::get_voxel_type() const {
return _voxel_type;
RWLockRead rlock(_parameters_lock);
return _parameters.voxel_type;
}
void VoxelGeneratorFlat::set_height(float h) {
_height = h;
RWLockWrite wlock(_parameters_lock);
_parameters.height = h;
}
float VoxelGeneratorFlat::get_height() const {
RWLockRead rlock(_parameters_lock);
return _parameters.height;
}
void VoxelGeneratorFlat::generate_block(VoxelBlockRequest &input) {
ERR_FAIL_COND(input.voxel_buffer.is_null());
Parameters params;
{
RWLockRead rlock(_parameters_lock);
params = _parameters;
}
VoxelBuffer &out_buffer = **input.voxel_buffer;
const Vector3i origin = input.origin_in_voxels;
const int channel = _channel;
const int channel = params.channel;
const Vector3i bs = out_buffer.get_size();
const bool use_sdf = channel == VoxelBuffer::CHANNEL_SDF;
const float margin = 1 << input.lod;
const int lod = input.lod;
if (origin.y > _height + margin) {
if (origin.y > params.height + margin) {
// The bottom of the block is above the highest ground can go (default is air)
return;
}
if (origin.y + (bs.y << lod) < _height - margin) {
if (origin.y + (bs.y << lod) < params.height - margin) {
// The top of the block is below the lowest ground can go
out_buffer.clear_channel(_channel, use_sdf ? 0 : _voxel_type);
out_buffer.clear_channel(params.channel, use_sdf ? 0 : params.voxel_type);
return;
}
@ -64,7 +91,7 @@ void VoxelGeneratorFlat::generate_block(VoxelBlockRequest &input) {
int gy = origin.y;
for (int y = 0; y < bs.y; ++y, gy += stride) {
float sdf = _iso_scale * (gy - _height);
float sdf = params.iso_scale * (gy - params.height);
out_buffer.set_voxel_f(sdf, x, y, z, channel);
}
@ -80,13 +107,13 @@ void VoxelGeneratorFlat::generate_block(VoxelBlockRequest &input) {
int gx = origin.x;
for (int x = 0; x < bs.x; ++x, gx += stride) {
float h = _height - origin.y;
float h = params.height - origin.y;
int ih = int(h);
if (ih > 0) {
if (ih > bs.y) {
ih = bs.y;
}
out_buffer.fill_area(_voxel_type, Vector3i(x, 0, z), Vector3i(x + 1, ih, z + 1), channel);
out_buffer.fill_area(params.voxel_type, Vector3i(x, 0, z), Vector3i(x + 1, ih, z + 1), channel);
}
} // for x
@ -95,7 +122,6 @@ void VoxelGeneratorFlat::generate_block(VoxelBlockRequest &input) {
}
void VoxelGeneratorFlat::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_channel", "channel"), &VoxelGeneratorFlat::set_channel);
ClassDB::bind_method(D_METHOD("get_channel"), &VoxelGeneratorFlat::get_channel);

View File

@ -8,6 +8,7 @@ class VoxelGeneratorFlat : public VoxelGenerator {
public:
VoxelGeneratorFlat();
~VoxelGeneratorFlat();
void set_channel(VoxelBuffer::ChannelId channel);
VoxelBuffer::ChannelId get_channel() const;
@ -19,16 +20,21 @@ public:
int get_voxel_type() const;
void set_height(float h);
float get_height() const { return _height; }
float get_height() const;
protected:
static void _bind_methods();
private:
VoxelBuffer::ChannelId _channel = VoxelBuffer::CHANNEL_SDF;
int _voxel_type = 1;
float _height = 0;
float _iso_scale = 0.1;
struct Parameters {
VoxelBuffer::ChannelId channel = VoxelBuffer::CHANNEL_SDF;
int voxel_type = 1;
float height = 0;
float iso_scale = 0.1;
};
Parameters _parameters;
RWLock *_parameters_lock;
};
#endif // VOXEL_GENERATOR_FLAT_H

View File

@ -3,50 +3,68 @@
#include "../../util/fixed_array.h"
VoxelGeneratorHeightmap::VoxelGeneratorHeightmap() {
_parameters_lock = RWLock::create();
}
VoxelGeneratorHeightmap::~VoxelGeneratorHeightmap() {
memdelete(_parameters_lock);
}
void VoxelGeneratorHeightmap::set_channel(VoxelBuffer::ChannelId channel) {
ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS);
if (_channel != channel) {
_channel = channel;
bool changed = false;
{
RWLockWrite wlock(_parameters_lock);
if (_parameters.channel != channel) {
_parameters.channel = channel;
}
}
if (changed) {
emit_changed();
}
}
VoxelBuffer::ChannelId VoxelGeneratorHeightmap::get_channel() const {
return _channel;
RWLockRead rlock(_parameters_lock);
return _parameters.channel;
}
int VoxelGeneratorHeightmap::get_used_channels_mask() const {
return (1 << _channel);
RWLockRead rlock(_parameters_lock);
return (1 << _parameters.channel);
}
void VoxelGeneratorHeightmap::set_height_start(float start) {
_range.start = start;
RWLockWrite wlock(_parameters_lock);
_parameters.range.start = start;
}
float VoxelGeneratorHeightmap::get_height_start() const {
return _range.start;
RWLockRead rlock(_parameters_lock);
return _parameters.range.start;
}
void VoxelGeneratorHeightmap::set_height_range(float range) {
_range.height = range;
RWLockWrite wlock(_parameters_lock);
_parameters.range.height = range;
}
float VoxelGeneratorHeightmap::get_height_range() const {
return _range.height;
RWLockRead rlock(_parameters_lock);
return _parameters.range.height;
}
void VoxelGeneratorHeightmap::set_iso_scale(float iso_scale) {
_iso_scale = iso_scale;
RWLockWrite wlock(_parameters_lock);
_parameters.iso_scale = iso_scale;
}
float VoxelGeneratorHeightmap::get_iso_scale() const {
return _iso_scale;
RWLockRead rlock(_parameters_lock);
return _parameters.iso_scale;
}
void VoxelGeneratorHeightmap::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_channel", "channel"), &VoxelGeneratorHeightmap::set_channel);
ClassDB::bind_method(D_METHOD("get_channel"), &VoxelGeneratorHeightmap::get_channel);

View File

@ -9,6 +9,7 @@ class VoxelGeneratorHeightmap : public VoxelGenerator {
GDCLASS(VoxelGeneratorHeightmap, VoxelGenerator)
public:
VoxelGeneratorHeightmap();
~VoxelGeneratorHeightmap();
void set_channel(VoxelBuffer::ChannelId channel);
VoxelBuffer::ChannelId get_channel() const;
@ -26,10 +27,15 @@ public:
protected:
template <typename Height_F>
void generate(VoxelBuffer &out_buffer, Height_F height_func, Vector3i origin, int lod) {
Parameters params;
{
RWLockRead rlock(_parameters_lock);
params = _parameters;
}
const int channel = _channel;
const int channel = params.channel;
const Vector3i bs = out_buffer.get_size();
bool use_sdf = channel == VoxelBuffer::CHANNEL_SDF;
const bool use_sdf = channel == VoxelBuffer::CHANNEL_SDF;
if (origin.y > get_height_start() + get_height_range()) {
// The bottom of the block is above the highest ground can go (default is air)
@ -37,24 +43,23 @@ protected:
}
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);
out_buffer.clear_channel(params.channel, use_sdf ? 0 : params.matter_type);
return;
}
const int stride = 1 << lod;
if (use_sdf) {
int gz = origin.z;
for (int z = 0; z < bs.z; ++z, gz += stride) {
int gx = origin.x;
for (int x = 0; x < bs.x; ++x, gx += stride) {
float h = _range.xform(height_func(gx, gz));
for (int x = 0; x < bs.x; ++x, gx += stride) {
float h = params.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);
float sdf = params.iso_scale * (gy - h);
out_buffer.set_voxel_f(sdf, x, y, z, channel);
}
@ -65,20 +70,21 @@ protected:
// Blocky
int gz = origin.z;
for (int z = 0; z < bs.z; ++z, gz += stride) {
int gx = origin.x;
for (int x = 0; x < bs.x; ++x, gx += stride) {
for (int x = 0; x < bs.x; ++x, gx += stride) {
// Output is blocky, so we can go for just one sample
float h = _range.xform(height_func(gx, gz));
float h = params.range.xform(height_func(gx, gz));
h -= origin.y;
int ih = int(h);
if (ih > 0) {
if (ih > bs.y) {
ih = bs.y;
}
out_buffer.fill_area(_matter_type, Vector3i(x, 0, z), Vector3i(x + 1, ih, z + 1), channel);
out_buffer.fill_area(
params.matter_type, Vector3i(x, 0, z), Vector3i(x + 1, ih, z + 1), channel);
}
} // for x
@ -98,10 +104,15 @@ private:
}
};
VoxelBuffer::ChannelId _channel = VoxelBuffer::CHANNEL_SDF;
int _matter_type = 1;
Range _range;
float _iso_scale = 0.1;
struct Parameters {
VoxelBuffer::ChannelId channel = VoxelBuffer::CHANNEL_SDF;
int matter_type = 1;
Range range;
float iso_scale = 0.1;
};
RWLock *_parameters_lock;
Parameters _parameters;
};
#endif // VOXEL_GENERATOR_HEIGHTMAP_H

View File

@ -20,10 +20,21 @@ inline float get_height_blurred(Image &im, int x, int y) {
} // namespace
VoxelGeneratorImage::VoxelGeneratorImage() {
_parameters_lock = RWLock::create();
}
VoxelGeneratorImage::~VoxelGeneratorImage() {
memdelete(_parameters_lock);
}
void VoxelGeneratorImage::set_image(Ref<Image> im) {
if (im == _image) {
return;
}
_image = im;
Ref<Image> copy = im.is_valid() ? im->duplicate() : Ref<Image>();
RWLockWrite wlock(_parameters_lock);
_parameters.image = copy;
}
Ref<Image> VoxelGeneratorImage::get_image() const {
@ -31,23 +42,30 @@ Ref<Image> VoxelGeneratorImage::get_image() const {
}
void VoxelGeneratorImage::set_blur_enabled(bool enable) {
_blur_enabled = enable;
RWLockWrite wlock(_parameters_lock);
_parameters.blur_enabled = enable;
}
bool VoxelGeneratorImage::is_blur_enabled() const {
return _blur_enabled;
RWLockRead rlock(_parameters_lock);
return _parameters.blur_enabled;
}
void VoxelGeneratorImage::generate_block(VoxelBlockRequest &input) {
ERR_FAIL_COND(_image.is_null());
VoxelBuffer &out_buffer = **input.voxel_buffer;
Image &image = **_image;
Parameters params;
{
RWLockRead rlock(_parameters_lock);
params = _parameters;
}
ERR_FAIL_COND(params.image.is_null());
Image &image = **params.image;
image.lock();
if (_blur_enabled) {
if (params.blur_enabled) {
VoxelGeneratorHeightmap::generate(
out_buffer,
[&image](int x, int z) { return get_height_blurred(image, x, z); },
@ -65,7 +83,6 @@ void VoxelGeneratorImage::generate_block(VoxelBlockRequest &input) {
}
void VoxelGeneratorImage::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_image", "image"), &VoxelGeneratorImage::set_image);
ClassDB::bind_method(D_METHOD("get_image"), &VoxelGeneratorImage::get_image);

View File

@ -10,6 +10,7 @@ class VoxelGeneratorImage : public VoxelGeneratorHeightmap {
public:
VoxelGeneratorImage();
~VoxelGeneratorImage();
void set_image(Ref<Image> im);
Ref<Image> get_image() const;
@ -23,9 +24,20 @@ private:
static void _bind_methods();
private:
// Proper reference used for external access.
Ref<Image> _image;
// Mostly here as demo/tweak. It's better recommended to use an EXR/float image.
bool _blur_enabled = false;
struct Parameters {
// This is a read-only copy of the image.
// It wastes memory for sure, but Godot does not offer any way to secure this better.
// If this is a problem one day, we could add an option to dereference the external image in game.
Ref<Image> image;
// Mostly here as demo/tweak. It's better recommended to use an EXR/float image.
bool blur_enabled = false;
};
Parameters _parameters;
RWLock *_parameters_lock;
};
#endif // HEADER_VOXEL_GENERATOR_IMAGE

View File

@ -2,32 +2,56 @@
#include <core/engine.h>
VoxelGeneratorNoise::VoxelGeneratorNoise() {
_parameters_lock = RWLock::create();
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
// Have one by default in editor
_noise.instance();
Ref<OpenSimplexNoise> noise;
noise.instance();
set_noise(noise);
}
#endif
}
VoxelGeneratorNoise::~VoxelGeneratorNoise() {
memdelete(_parameters_lock);
}
void VoxelGeneratorNoise::set_noise(Ref<OpenSimplexNoise> noise) {
if (_noise == noise) {
return;
}
_noise = noise;
Ref<OpenSimplexNoise> copy;
if (noise.is_valid()) {
copy = noise->duplicate();
}
RWLockWrite wlock(_parameters_lock);
_parameters.noise = copy;
}
void VoxelGeneratorNoise::set_channel(VoxelBuffer::ChannelId channel) {
ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS);
if (_channel != channel) {
_channel = channel;
bool changed = false;
{
RWLockWrite wlock(_parameters_lock);
if (_parameters.channel != channel) {
_parameters.channel = channel;
}
}
if (changed) {
emit_changed();
}
}
VoxelBuffer::ChannelId VoxelGeneratorNoise::get_channel() const {
return _channel;
RWLockRead rlock(_parameters_lock);
return _parameters.channel;
}
int VoxelGeneratorNoise::get_used_channels_mask() const {
return (1 << _channel);
RWLockRead rlock(_parameters_lock);
return (1 << _parameters.channel);
}
Ref<OpenSimplexNoise> VoxelGeneratorNoise::get_noise() const {
@ -35,22 +59,26 @@ Ref<OpenSimplexNoise> VoxelGeneratorNoise::get_noise() const {
}
void VoxelGeneratorNoise::set_height_start(real_t y) {
_height_start = y;
RWLockWrite wlock(_parameters_lock);
_parameters.height_start = y;
}
real_t VoxelGeneratorNoise::get_height_start() const {
return _height_start;
RWLockRead rlock(_parameters_lock);
return _parameters.height_start;
}
void VoxelGeneratorNoise::set_height_range(real_t hrange) {
if (hrange < 0.1f) {
hrange = 0.1f;
}
_height_range = hrange;
RWLockWrite wlock(_parameters_lock);
_parameters.height_range = hrange;
}
real_t VoxelGeneratorNoise::get_height_range() const {
return _height_range;
RWLockRead rlock(_parameters_lock);
return _parameters.height_range;
}
// For isosurface use cases, noise can be "shaped" by calculating only the first octave,
@ -58,7 +86,6 @@ real_t VoxelGeneratorNoise::get_height_range() const {
// because then we assume next octaves won't change the sign (which crosses the surface).
// This might reduce accuracy in some areas, but it speeds up the results.
static inline float get_shaped_noise(OpenSimplexNoise &noise, float x, float y, float z, float threshold, float bias) {
x /= noise.get_period();
y /= noise.get_period();
z /= noise.get_period();
@ -89,42 +116,46 @@ static inline float get_shaped_noise(OpenSimplexNoise &noise, float x, float y,
void VoxelGeneratorNoise::generate_block(VoxelBlockRequest &input) {
ERR_FAIL_COND(input.voxel_buffer.is_null());
ERR_FAIL_COND(_noise.is_null());
OpenSimplexNoise &noise = **_noise;
Parameters params;
{
RWLockRead rlock(_parameters_lock);
params = _parameters;
}
ERR_FAIL_COND(params.noise.is_null());
OpenSimplexNoise &noise = **params.noise;
VoxelBuffer &buffer = **input.voxel_buffer;
Vector3i origin_in_voxels = input.origin_in_voxels;
int lod = input.lod;
int isosurface_lower_bound = static_cast<int>(Math::floor(_height_start));
int isosurface_upper_bound = static_cast<int>(Math::ceil(_height_start + _height_range));
int isosurface_lower_bound = static_cast<int>(Math::floor(params.height_start));
int isosurface_upper_bound = static_cast<int>(Math::ceil(params.height_start + params.height_range));
const int air_type = 0;
const int matter_type = 1;
if (origin_in_voxels.y >= isosurface_upper_bound) {
// Fill with air
if (_channel == VoxelBuffer::CHANNEL_SDF) {
buffer.clear_channel_f(_channel, 100.0);
} else if (_channel == VoxelBuffer::CHANNEL_TYPE) {
buffer.clear_channel(_channel, air_type);
if (params.channel == VoxelBuffer::CHANNEL_SDF) {
buffer.clear_channel_f(params.channel, 100.0);
} else if (params.channel == VoxelBuffer::CHANNEL_TYPE) {
buffer.clear_channel(params.channel, air_type);
}
} else if (origin_in_voxels.y + (buffer.get_size().y << lod) < isosurface_lower_bound) {
// Fill with matter
if (_channel == VoxelBuffer::CHANNEL_SDF) {
buffer.clear_channel_f(_channel, -100.0);
} else if (_channel == VoxelBuffer::CHANNEL_TYPE) {
buffer.clear_channel(_channel, matter_type);
if (params.channel == VoxelBuffer::CHANNEL_SDF) {
buffer.clear_channel_f(params.channel, -100.0);
} else if (params.channel == VoxelBuffer::CHANNEL_TYPE) {
buffer.clear_channel(params.channel, matter_type);
}
} else {
const float iso_scale = noise.get_period() * 0.1;
const Vector3i size = buffer.get_size();
const float height_range_inv = 1.f / _height_range;
const float height_range_inv = 1.f / params.height_range;
const float one_minus_persistence = 1.f - noise.get_persistence();
for (int z = 0; z < size.z; ++z) {
@ -134,40 +165,39 @@ void VoxelGeneratorNoise::generate_block(VoxelBlockRequest &input) {
int lx = origin_in_voxels.x + (x << lod);
for (int y = 0; y < size.y; ++y) {
int ly = origin_in_voxels.y + (y << lod);
const int ly = origin_in_voxels.y + (y << lod);
if (ly < isosurface_lower_bound) {
// Below is only matter
if (_channel == VoxelBuffer::CHANNEL_SDF) {
buffer.set_voxel_f(-1, x, y, z, _channel);
} else if (_channel == VoxelBuffer::CHANNEL_TYPE) {
buffer.set_voxel(matter_type, x, y, z, _channel);
if (params.channel == VoxelBuffer::CHANNEL_SDF) {
buffer.set_voxel_f(-1, x, y, z, params.channel);
} else if (params.channel == VoxelBuffer::CHANNEL_TYPE) {
buffer.set_voxel(matter_type, x, y, z, params.channel);
}
continue;
} else if (ly >= isosurface_upper_bound) {
// Above is only air
if (_channel == VoxelBuffer::CHANNEL_SDF) {
buffer.set_voxel_f(1, x, y, z, _channel);
} else if (_channel == VoxelBuffer::CHANNEL_TYPE) {
buffer.set_voxel(air_type, x, y, z, _channel);
if (params.channel == VoxelBuffer::CHANNEL_SDF) {
buffer.set_voxel_f(1, x, y, z, params.channel);
} else if (params.channel == VoxelBuffer::CHANNEL_TYPE) {
buffer.set_voxel(air_type, x, y, z, params.channel);
}
continue;
}
// Bias is what makes noise become "matter" the lower we go, and "air" the higher we go
float t = (ly - _height_start) * height_range_inv;
float t = (ly - params.height_start) * height_range_inv;
float bias = 2.0 * t - 1.0;
// We are near the isosurface, need to calculate noise value
float n = get_shaped_noise(noise, lx, ly, lz, one_minus_persistence, bias);
float d = (n + bias) * iso_scale;
if (_channel == VoxelBuffer::CHANNEL_SDF) {
buffer.set_voxel_f(d, x, y, z, _channel);
} else if (_channel == VoxelBuffer::CHANNEL_TYPE && d < 0) {
buffer.set_voxel(matter_type, x, y, z, _channel);
if (params.channel == VoxelBuffer::CHANNEL_SDF) {
buffer.set_voxel_f(d, x, y, z, params.channel);
} else if (params.channel == VoxelBuffer::CHANNEL_TYPE && d < 0) {
buffer.set_voxel(matter_type, x, y, z, params.channel);
}
}
}
@ -176,7 +206,6 @@ void VoxelGeneratorNoise::generate_block(VoxelBlockRequest &input) {
}
void VoxelGeneratorNoise::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_channel", "channel"), &VoxelGeneratorNoise::set_channel);
ClassDB::bind_method(D_METHOD("get_channel"), &VoxelGeneratorNoise::get_channel);

View File

@ -9,6 +9,7 @@ class VoxelGeneratorNoise : public VoxelGenerator {
public:
VoxelGeneratorNoise();
~VoxelGeneratorNoise();
void set_channel(VoxelBuffer::ChannelId channel);
VoxelBuffer::ChannelId get_channel() const;
@ -29,10 +30,17 @@ protected:
static void _bind_methods();
private:
VoxelBuffer::ChannelId _channel = VoxelBuffer::CHANNEL_SDF;
Ref<OpenSimplexNoise> _noise;
float _height_start = 0;
float _height_range = 300;
struct Parameters {
VoxelBuffer::ChannelId channel = VoxelBuffer::CHANNEL_SDF;
Ref<OpenSimplexNoise> noise;
float height_start = 0;
float height_range = 300;
};
Parameters _parameters;
RWLock *_parameters_lock;
};
#endif // VOXEL_GENERATOR_NOISE_H

View File

@ -2,16 +2,28 @@
#include <core/engine.h>
VoxelGeneratorNoise2D::VoxelGeneratorNoise2D() {
_parameters_lock = RWLock::create();
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
// Have one by default in editor
_noise.instance();
Ref<OpenSimplexNoise> noise;
noise.instance();
set_noise(noise);
}
#endif
}
VoxelGeneratorNoise2D::~VoxelGeneratorNoise2D() {
memdelete(_parameters_lock);
}
void VoxelGeneratorNoise2D::set_noise(Ref<OpenSimplexNoise> noise) {
if (_noise == noise) {
return;
}
_noise = noise;
RWLockWrite wlock(_parameters_lock);
_parameters.noise = _noise.is_valid() ? _noise->duplicate() : Ref<OpenSimplexNoise>();
}
Ref<OpenSimplexNoise> VoxelGeneratorNoise2D::get_noise() const {
@ -19,7 +31,17 @@ Ref<OpenSimplexNoise> VoxelGeneratorNoise2D::get_noise() const {
}
void VoxelGeneratorNoise2D::set_curve(Ref<Curve> curve) {
if (_curve == curve) {
return;
}
_curve = curve;
RWLockWrite wlock(_parameters_lock);
if (_curve.is_valid()) {
_parameters.curve = _curve->duplicate();
_parameters.curve->bake();
} else {
_parameters.curve.unref();
}
}
Ref<Curve> VoxelGeneratorNoise2D::get_curve() const {
@ -27,11 +49,16 @@ Ref<Curve> VoxelGeneratorNoise2D::get_curve() const {
}
void VoxelGeneratorNoise2D::generate_block(VoxelBlockRequest &input) {
Parameters params;
{
RWLockRead rlock(_parameters_lock);
params = _parameters;
}
ERR_FAIL_COND(_noise.is_null());
ERR_FAIL_COND(params.noise.is_null());
OpenSimplexNoise &noise = **params.noise;
VoxelBuffer &out_buffer = **input.voxel_buffer;
OpenSimplexNoise &noise = **_noise;
if (_curve.is_null()) {
VoxelGeneratorHeightmap::generate(
@ -39,7 +66,7 @@ void VoxelGeneratorNoise2D::generate_block(VoxelBlockRequest &input) {
[&noise](int x, int z) { return 0.5 + 0.5 * noise.get_noise_2d(x, z); },
input.origin_in_voxels, input.lod);
} else {
Curve &curve = **_curve;
Curve &curve = **params.curve;
VoxelGeneratorHeightmap::generate(
out_buffer,
[&noise, &curve](int x, int z) { return curve.interpolate_baked(0.5 + 0.5 * noise.get_noise_2d(x, z)); },
@ -50,7 +77,6 @@ void VoxelGeneratorNoise2D::generate_block(VoxelBlockRequest &input) {
}
void VoxelGeneratorNoise2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_noise", "noise"), &VoxelGeneratorNoise2D::set_noise);
ClassDB::bind_method(D_METHOD("get_noise"), &VoxelGeneratorNoise2D::get_noise);

View File

@ -6,9 +6,10 @@
class VoxelGeneratorNoise2D : public VoxelGeneratorHeightmap {
GDCLASS(VoxelGeneratorNoise2D, VoxelGeneratorHeightmap)
public:
VoxelGeneratorNoise2D();
~VoxelGeneratorNoise2D();
void set_noise(Ref<OpenSimplexNoise> noise);
Ref<OpenSimplexNoise> get_noise() const;
@ -24,6 +25,14 @@ private:
private:
Ref<OpenSimplexNoise> _noise;
Ref<Curve> _curve;
struct Parameters {
Ref<OpenSimplexNoise> noise;
Ref<Curve> curve;
};
Parameters _parameters;
RWLock *_parameters_lock;
};
#endif // VOXEL_GENERATOR_NOISE_2D_H

View File

@ -3,16 +3,22 @@
#include <cmath>
VoxelGeneratorWaves::VoxelGeneratorWaves() {
_pattern_size = Vector2(30, 30);
_parameters.pattern_size = Vector2(30, 30);
set_height_range(30);
}
void VoxelGeneratorWaves::generate_block(VoxelBlockRequest &input) {
Parameters params;
{
RWLockRead rlock(_parameters_lock);
params = _parameters;
}
VoxelBuffer &out_buffer = **input.voxel_buffer;
const Vector2 freq(
Math_PI / static_cast<float>(_pattern_size.x),
Math_PI / static_cast<float>(_pattern_size.y));
const Vector2 offset = _pattern_offset;
Math_PI / static_cast<float>(params.pattern_size.x),
Math_PI / static_cast<float>(params.pattern_size.y));
const Vector2 offset = params.pattern_offset;
VoxelGeneratorHeightmap::generate(
out_buffer,
@ -22,18 +28,29 @@ void VoxelGeneratorWaves::generate_block(VoxelBlockRequest &input) {
input.origin_in_voxels, input.lod);
}
Vector2 VoxelGeneratorWaves::get_pattern_size() const {
RWLockRead rlock(_parameters_lock);
return _parameters.pattern_size;
}
void VoxelGeneratorWaves::set_pattern_size(Vector2 size) {
RWLockWrite wlock(_parameters_lock);
size.x = max(size.x, 0.1f);
size.y = max(size.y, 0.1f);
_pattern_size = size;
_parameters.pattern_size = size;
}
Vector2 VoxelGeneratorWaves::get_pattern_offset() const {
RWLockRead rlock(_parameters_lock);
return _parameters.pattern_offset;
}
void VoxelGeneratorWaves::set_pattern_offset(Vector2 offset) {
_pattern_offset = offset;
RWLockWrite wlock(_parameters_lock);
_parameters.pattern_offset = offset;
}
void VoxelGeneratorWaves::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_pattern_size", "size"), &VoxelGeneratorWaves::set_pattern_size);
ClassDB::bind_method(D_METHOD("get_pattern_size"), &VoxelGeneratorWaves::get_pattern_size);

View File

@ -14,17 +14,22 @@ public:
void generate_block(VoxelBlockRequest &input) override;
Vector2 get_pattern_size() const { return _pattern_size; }
Vector2 get_pattern_size() const;
void set_pattern_size(Vector2 size);
Vector2 get_pattern_offset() const { return _pattern_offset; }
Vector2 get_pattern_offset() const;
void set_pattern_offset(Vector2 offset);
private:
static void _bind_methods();
Vector2 _pattern_size;
Vector2 _pattern_offset;
struct Parameters {
Vector2 pattern_size;
Vector2 pattern_offset;
};
Parameters _parameters;
RWLock *_parameters_lock;
};
#endif // VOXEL_GENERATOR_WAVES_H

View File

@ -3,7 +3,8 @@
#include "voxel_generator.h"
// Generator based on a script, like GDScript, C# or NativeScript
// Generator based on a script, like GDScript, C# or NativeScript.
// The script is expected to properly handle multithreading.
class VoxelGeneratorScript : public VoxelGenerator {
GDCLASS(VoxelGeneratorScript, VoxelGenerator)
public:

View File

@ -61,6 +61,7 @@ public:
void enqueue(IVoxelTask *task);
void enqueue(ArraySlice<IVoxelTask *> tasks);
// TODO Lambda might not be the best API. memcpying to a vector would ensure we lock for a shorter time.
template <typename F>
void dequeue_completed_tasks(F f) {
MutexLock lock(_completed_tasks_mutex);
@ -102,8 +103,9 @@ private:
FixedArray<ThreadData, MAX_THREADS> _threads;
uint32_t _thread_count = 0;
// TODO Optimize this with a less naive design? Maybe moodycamel
std::vector<TaskItem> _tasks;
Mutex *_tasks_mutex = nullptr; // TODO Optimize this with a less naive design?
Mutex *_tasks_mutex = nullptr;
Semaphore *_tasks_semaphore = nullptr;
std::vector<IVoxelTask *> _completed_tasks;