181 lines
5.9 KiB
C++
181 lines
5.9 KiB
C++
#include "voxel_generator_noise.h"
|
|
|
|
void VoxelGeneratorNoise::set_channel(VoxelBuffer::ChannelId channel) {
|
|
ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS);
|
|
_channel = channel;
|
|
}
|
|
|
|
VoxelBuffer::ChannelId VoxelGeneratorNoise::get_channel() const {
|
|
return _channel;
|
|
}
|
|
|
|
void VoxelGeneratorNoise::set_noise(Ref<OpenSimplexNoise> noise) {
|
|
_noise = noise;
|
|
}
|
|
|
|
Ref<OpenSimplexNoise> VoxelGeneratorNoise::get_noise() const {
|
|
return _noise;
|
|
}
|
|
|
|
void VoxelGeneratorNoise::set_height_start(real_t y) {
|
|
_height_start = y;
|
|
}
|
|
|
|
real_t VoxelGeneratorNoise::get_height_start() const {
|
|
return _height_start;
|
|
}
|
|
|
|
void VoxelGeneratorNoise::set_height_range(real_t hrange) {
|
|
if (hrange < 0.1f) {
|
|
hrange = 0.1f;
|
|
}
|
|
_height_range = hrange;
|
|
}
|
|
|
|
real_t VoxelGeneratorNoise::get_height_range() const {
|
|
return _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;
|
|
}
|
|
|
|
void VoxelGeneratorNoise::generate_block(VoxelBlockRequest &input) {
|
|
|
|
ERR_FAIL_COND(input.voxel_buffer.is_null());
|
|
ERR_FAIL_COND(_noise.is_null());
|
|
|
|
OpenSimplexNoise &noise = **_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));
|
|
|
|
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);
|
|
}
|
|
|
|
} 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);
|
|
}
|
|
|
|
} 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 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) {
|
|
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
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 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void VoxelGeneratorNoise::_bind_methods() {
|
|
|
|
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("set_channel", "channel"), &VoxelGeneratorNoise::set_channel);
|
|
ClassDB::bind_method(D_METHOD("get_channel"), &VoxelGeneratorNoise::get_channel);
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "noise", PROPERTY_HINT_RESOURCE_TYPE, "OpenSimplexNoise"), "set_noise", "get_noise");
|
|
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");
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "channel", PROPERTY_HINT_ENUM, VoxelBuffer::CHANNEL_ID_HINT_STRING), "set_channel", "get_channel");
|
|
}
|