252 lines
8.7 KiB
C++
252 lines
8.7 KiB
C++
#include "voxel_generator_noise.h"
|
|
#include <core/config/engine.h>
|
|
#include <core/core_string_names.h>
|
|
|
|
VoxelGeneratorNoise::VoxelGeneratorNoise() {}
|
|
|
|
VoxelGeneratorNoise::~VoxelGeneratorNoise() {}
|
|
|
|
void VoxelGeneratorNoise::set_noise(Ref<OpenSimplexNoise> noise) {
|
|
if (_noise == noise) {
|
|
return;
|
|
}
|
|
if (_noise.is_valid()) {
|
|
_noise->disconnect(
|
|
CoreStringNames::get_singleton()->changed, callable_mp(this, &VoxelGeneratorNoise::_on_noise_changed));
|
|
}
|
|
_noise = noise;
|
|
Ref<OpenSimplexNoise> copy;
|
|
if (_noise.is_valid()) {
|
|
_noise->connect(
|
|
CoreStringNames::get_singleton()->changed, callable_mp(this, &VoxelGeneratorNoise::_on_noise_changed));
|
|
// The OpenSimplexNoise resource is not thread-safe so we make a copy of it for use in threads
|
|
copy = _noise->duplicate();
|
|
}
|
|
// The OpenSimplexNoise resource is not thread-safe so we make a copy of it for use in threads
|
|
RWLockWrite wlock(_parameters_lock);
|
|
_parameters.noise = copy;
|
|
}
|
|
|
|
void VoxelGeneratorNoise::_on_noise_changed() {
|
|
ERR_FAIL_COND(_noise.is_null());
|
|
RWLockWrite wlock(_parameters_lock);
|
|
_parameters.noise = _noise->duplicate();
|
|
}
|
|
|
|
void VoxelGeneratorNoise::set_channel(VoxelBuffer::ChannelId p_channel) {
|
|
VoxelBufferInternal::ChannelId channel = VoxelBufferInternal::ChannelId(p_channel);
|
|
ERR_FAIL_INDEX(channel, VoxelBufferInternal::MAX_CHANNELS);
|
|
bool changed = false;
|
|
{
|
|
RWLockWrite wlock(_parameters_lock);
|
|
if (_parameters.channel != channel) {
|
|
_parameters.channel = channel;
|
|
changed = true;
|
|
}
|
|
}
|
|
if (changed) {
|
|
emit_changed();
|
|
}
|
|
}
|
|
|
|
VoxelBuffer::ChannelId VoxelGeneratorNoise::get_channel() const {
|
|
RWLockRead rlock(_parameters_lock);
|
|
return VoxelBuffer::ChannelId(_parameters.channel);
|
|
}
|
|
|
|
int VoxelGeneratorNoise::get_used_channels_mask() const {
|
|
RWLockRead rlock(_parameters_lock);
|
|
return (1 << _parameters.channel);
|
|
}
|
|
|
|
Ref<OpenSimplexNoise> VoxelGeneratorNoise::get_noise() const {
|
|
return _noise;
|
|
}
|
|
|
|
void VoxelGeneratorNoise::set_height_start(real_t y) {
|
|
RWLockWrite wlock(_parameters_lock);
|
|
_parameters.height_start = y;
|
|
}
|
|
|
|
real_t VoxelGeneratorNoise::get_height_start() const {
|
|
RWLockRead rlock(_parameters_lock);
|
|
return _parameters.height_start;
|
|
}
|
|
|
|
void VoxelGeneratorNoise::set_height_range(real_t hrange) {
|
|
if (hrange < 0.1f) {
|
|
hrange = 0.1f;
|
|
}
|
|
RWLockWrite wlock(_parameters_lock);
|
|
_parameters.height_range = hrange;
|
|
}
|
|
|
|
real_t VoxelGeneratorNoise::get_height_range() const {
|
|
RWLockRead rlock(_parameters_lock);
|
|
return _parameters.height_range;
|
|
}
|
|
|
|
// For isosurface use cases, noise can be "shaped" by calculating only the first octave,
|
|
// and discarding the next ones if beyond some distance away from the isosurface,
|
|
// 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();
|
|
|
|
float sum = noise._get_octave_noise_3d(0, x, y, z);
|
|
|
|
// A default value for `threshold` would be `persistence`
|
|
if (sum + bias > threshold || sum + bias < -threshold) {
|
|
// Assume next octaves will not change sign of noise
|
|
return sum;
|
|
}
|
|
|
|
float amp = 1.0;
|
|
float max = 1.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 += noise._get_octave_noise_3d(i, x, y, z) * amp;
|
|
}
|
|
|
|
return sum / max;
|
|
}
|
|
|
|
VoxelGenerator::Result VoxelGeneratorNoise::generate_block(VoxelBlockRequest &input) {
|
|
Parameters params;
|
|
{
|
|
RWLockRead rlock(_parameters_lock);
|
|
params = _parameters;
|
|
}
|
|
|
|
ERR_FAIL_COND_V(params.noise.is_null(), Result());
|
|
|
|
OpenSimplexNoise &noise = **params.noise;
|
|
VoxelBufferInternal &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(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;
|
|
const int air_color = 0;
|
|
const int matter_color = 1;
|
|
|
|
Result result;
|
|
|
|
if (origin_in_voxels.y >= isosurface_upper_bound) {
|
|
// Fill with air
|
|
if (params.channel == VoxelBufferInternal::CHANNEL_SDF) {
|
|
buffer.clear_channel_f(params.channel, 100.0);
|
|
} else if (params.channel == VoxelBufferInternal::CHANNEL_TYPE) {
|
|
buffer.clear_channel(params.channel, air_type);
|
|
} else if (params.channel == VoxelBufferInternal::CHANNEL_COLOR) {
|
|
buffer.clear_channel(params.channel, air_color);
|
|
}
|
|
result.max_lod_hint = true;
|
|
|
|
} else if (origin_in_voxels.y + (buffer.get_size().y << lod) < isosurface_lower_bound) {
|
|
// Fill with matter
|
|
if (params.channel == VoxelBufferInternal::CHANNEL_SDF) {
|
|
buffer.clear_channel_f(params.channel, -100.0);
|
|
} else if (params.channel == VoxelBufferInternal::CHANNEL_TYPE) {
|
|
buffer.clear_channel(params.channel, matter_type);
|
|
} else if (params.channel == VoxelBufferInternal::CHANNEL_COLOR) {
|
|
buffer.clear_channel(params.channel, matter_color);
|
|
}
|
|
result.max_lod_hint = true;
|
|
|
|
} else {
|
|
const float iso_scale = noise.get_period() * 0.1;
|
|
const Vector3i size = buffer.get_size();
|
|
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) {
|
|
int lz = origin_in_voxels.z + (z << lod);
|
|
|
|
for (int x = 0; x < size.x; ++x) {
|
|
int lx = origin_in_voxels.x + (x << lod);
|
|
|
|
for (int y = 0; y < size.y; ++y) {
|
|
const int ly = origin_in_voxels.y + (y << lod);
|
|
|
|
if (ly < isosurface_lower_bound) {
|
|
// Below is only matter
|
|
if (params.channel == VoxelBufferInternal::CHANNEL_SDF) {
|
|
buffer.set_voxel_f(-1, x, y, z, params.channel);
|
|
} else if (params.channel == VoxelBufferInternal::CHANNEL_TYPE) {
|
|
buffer.set_voxel(matter_type, x, y, z, params.channel);
|
|
} else if (params.channel == VoxelBufferInternal::CHANNEL_COLOR) {
|
|
buffer.set_voxel(matter_color, x, y, z, params.channel);
|
|
}
|
|
continue;
|
|
|
|
} else if (ly >= isosurface_upper_bound) {
|
|
// Above is only air
|
|
if (params.channel == VoxelBufferInternal::CHANNEL_SDF) {
|
|
buffer.set_voxel_f(1, x, y, z, params.channel);
|
|
} else if (params.channel == VoxelBufferInternal::CHANNEL_TYPE) {
|
|
buffer.set_voxel(air_type, x, y, z, params.channel);
|
|
} else if (params.channel == VoxelBufferInternal::CHANNEL_COLOR) {
|
|
buffer.set_voxel(air_color, 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 - 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 (params.channel == VoxelBufferInternal::CHANNEL_SDF) {
|
|
buffer.set_voxel_f(d, x, y, z, params.channel);
|
|
} else if (params.channel == VoxelBufferInternal::CHANNEL_TYPE && d < 0) {
|
|
buffer.set_voxel(matter_type, x, y, z, params.channel);
|
|
} else if (params.channel == VoxelBufferInternal::CHANNEL_COLOR && d < 0) {
|
|
buffer.set_voxel(matter_color, x, y, z, params.channel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
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);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_noise", "noise"), &VoxelGeneratorNoise::set_noise);
|
|
ClassDB::bind_method(D_METHOD("get_noise"), &VoxelGeneratorNoise::get_noise);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_height_start", "hstart"), &VoxelGeneratorNoise::set_height_start);
|
|
ClassDB::bind_method(D_METHOD("get_height_start"), &VoxelGeneratorNoise::get_height_start);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_height_range", "hrange"), &VoxelGeneratorNoise::set_height_range);
|
|
ClassDB::bind_method(D_METHOD("get_height_range"), &VoxelGeneratorNoise::get_height_range);
|
|
|
|
ClassDB::bind_method(D_METHOD("_on_noise_changed"), &VoxelGeneratorNoise::_on_noise_changed);
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "channel", PROPERTY_HINT_ENUM, VoxelBuffer::CHANNEL_ID_HINT_STRING),
|
|
"set_channel", "get_channel");
|
|
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "noise", PROPERTY_HINT_RESOURCE_TYPE, "OpenSimplexNoise",
|
|
PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT),
|
|
"set_noise", "get_noise");
|
|
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height_start"), "set_height_start", "get_height_start");
|
|
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height_range"), "set_height_range", "get_height_range");
|
|
}
|