Merge branch 'master' into depth

This commit is contained in:
Marc Gilleron 2020-01-26 23:34:08 +00:00
commit aec06b214b
48 changed files with 723 additions and 1436 deletions

2
SCsub
View File

@ -11,9 +11,11 @@ files = [
"meshers/mc/*.cpp",
"meshers/*.cpp",
"streams/*.cpp",
"generators/*.cpp",
"util/*.cpp",
"terrain/*.cpp",
"math/*.cpp",
"edition/*.cpp",
"thirdparty/lz4/*.c"
]

View File

@ -1,6 +1,6 @@
#include "voxel_tool.h"
#include "terrain/voxel_lod_terrain.h"
#include "voxel_buffer.h"
#include "../terrain/voxel_lod_terrain.h"
#include "../voxel_buffer.h"
Vector3 VoxelRaycastResult::_b_get_position() const {
return position.to_vec3();

View File

@ -1,7 +1,7 @@
#ifndef VOXEL_TOOL_H
#define VOXEL_TOOL_H
#include "math/rect3i.h"
#include "../math/rect3i.h"
#include <core/reference.h>
class VoxelBuffer;

View File

@ -1,5 +1,5 @@
#include "voxel_tool_buffer.h"
#include "voxel_buffer.h"
#include "../voxel_buffer.h"
VoxelToolBuffer::VoxelToolBuffer(Ref<VoxelBuffer> vb) {
ERR_FAIL_COND(vb.is_null());

View File

@ -1,6 +1,6 @@
#include "voxel_tool_lod_terrain.h"
#include "terrain/voxel_lod_terrain.h"
#include "terrain/voxel_map.h"
#include "../terrain/voxel_lod_terrain.h"
#include "../terrain/voxel_map.h"
VoxelToolLodTerrain::VoxelToolLodTerrain(VoxelLodTerrain *terrain, Ref<VoxelMap> map) {
ERR_FAIL_COND(terrain == nullptr);

View File

@ -1,7 +1,7 @@
#include "voxel_tool_terrain.h"
#include "terrain/voxel_map.h"
#include "terrain/voxel_terrain.h"
#include "util/voxel_raycast.h"
#include "../terrain/voxel_map.h"
#include "../terrain/voxel_terrain.h"
#include "../util/voxel_raycast.h"
VoxelToolTerrain::VoxelToolTerrain(VoxelTerrain *terrain, Ref<VoxelMap> map) {
ERR_FAIL_COND(terrain == nullptr);
@ -15,53 +15,42 @@ bool VoxelToolTerrain::is_area_editable(const Rect3i &box) const {
return _map->is_area_fully_loaded(box.padded(1));
}
namespace {
struct _VoxelTerrainRaycastContext {
VoxelTerrain &terrain;
//unsigned int channel_mask;
};
} // namespace
static bool cb_raycast_predicate(Vector3i pos, void *context_ptr) {
// TODO This is really not the best way.
// This needs lambda goodness and made available to other volume types (generic?)
ERR_FAIL_COND_V(context_ptr == NULL, false);
_VoxelTerrainRaycastContext *context = (_VoxelTerrainRaycastContext *)context_ptr;
VoxelTerrain &terrain = context->terrain;
//unsigned int channel = context->channel;
Ref<VoxelMap> map = terrain.get_storage();
int v0 = map->get_voxel(pos, VoxelBuffer::CHANNEL_TYPE);
Ref<VoxelLibrary> lib_ref = terrain.get_voxel_library();
if (lib_ref.is_null())
return false;
const VoxelLibrary &lib = **lib_ref;
if (lib.has_voxel(v0) == false)
return false;
const Voxel &voxel = lib.get_voxel_const(v0);
if (voxel.is_transparent() == false)
return true;
float v1 = map->get_voxel_f(pos, VoxelBuffer::CHANNEL_SDF);
return v1 < 0;
}
Ref<VoxelRaycastResult> VoxelToolTerrain::raycast(Vector3 pos, Vector3 dir, float max_distance) {
// TODO Transform input if the terrain is rotated (in the future it can be made a Spatial node)
struct RaycastPredicate {
const VoxelTerrain &terrain;
bool operator()(Vector3i pos) {
//unsigned int channel = context->channel;
Ref<VoxelMap> map = terrain.get_storage();
int v0 = map->get_voxel(pos, VoxelBuffer::CHANNEL_TYPE);
Ref<VoxelLibrary> lib_ref = terrain.get_voxel_library();
if (lib_ref.is_null())
return false;
const VoxelLibrary &lib = **lib_ref;
if (lib.has_voxel(v0) == false)
return false;
const Voxel &voxel = lib.get_voxel_const(v0);
if (voxel.is_transparent() == false)
return true;
float v1 = map->get_voxel_f(pos, VoxelBuffer::CHANNEL_SDF);
return v1 < 0;
}
};
Vector3i hit_pos;
Vector3i prev_pos;
_VoxelTerrainRaycastContext context = { *_terrain };
Ref<VoxelRaycastResult> res;
if (voxel_raycast(pos, dir, cb_raycast_predicate, &context, max_distance, hit_pos, prev_pos)) {
RaycastPredicate predicate = { *_terrain };
if (voxel_raycast(pos, dir, predicate, max_distance, hit_pos, prev_pos)) {
res.instance();
res->position = hit_pos;

View File

@ -0,0 +1,55 @@
#include "voxel_generator.h"
#include "../voxel_string_names.h"
VoxelGenerator::VoxelGenerator() {
}
void VoxelGenerator::generate_block(VoxelBlockRequest &input) {
ERR_FAIL_COND(input.voxel_buffer.is_null());
ScriptInstance *script = get_script_instance();
if (script) {
// Call script to generate buffer
Variant arg1 = input.voxel_buffer;
Variant arg2 = input.origin_in_voxels.to_vec3();
Variant arg3 = input.lod;
const Variant *args[3] = { &arg1, &arg2, &arg3 };
Variant::CallError err;
script->call(VoxelStringNames::get_singleton()->generate_block, args, 3, err);
ERR_FAIL_COND_MSG(err.error != Variant::CallError::CALL_OK,
"voxel_generator.cpp:emerge_block gave an error: " + String::num(err.error) +
", Argument: " + String::num(err.argument) +
", Expected type: " + Variant::get_type_name(err.expected));
// This had to be explicitely logged due to the usual GD debugger not working with threads
}
}
//bool VoxelGenerator::is_thread_safe() const {
// return false;
//}
//bool VoxelGenerator::is_cloneable() const {
// return false;
//}
void VoxelGenerator::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) {
VoxelBlockRequest r = { out_buffer, Vector3i(origin_in_voxels), lod };
generate_block(r);
}
void VoxelGenerator::_b_generate_block(Ref<VoxelBuffer> out_buffer, Vector3 origin_in_voxels, int lod) {
ERR_FAIL_COND(lod < 0);
VoxelBlockRequest r = { out_buffer, Vector3i(origin_in_voxels), lod };
generate_block(r);
}
void VoxelGenerator::_bind_methods() {
// Note: C++ inheriting classes don't need to re-bind these, because they are bindings that call the actual virtual methods
ClassDB::bind_method(D_METHOD("generate_block", "out_buffer", "origin_in_voxels", "lod"), &VoxelGenerator::_b_generate_block);
}

View File

@ -0,0 +1,31 @@
#ifndef VOXEL_GENERATOR_H
#define VOXEL_GENERATOR_H
#include "../streams/voxel_stream.h"
// TODO I would like VoxelGenerator to not inherit VoxelStream
// because it gets members that make no sense with generators
// Provides access to read-only generated voxels.
// Must be implemented in a multi-thread-safe way.
class VoxelGenerator : public VoxelStream {
GDCLASS(VoxelGenerator, VoxelStream)
public:
VoxelGenerator();
virtual void generate_block(VoxelBlockRequest &input);
// TODO Single sample
// virtual bool is_thread_safe() const;
// virtual bool is_cloneable() const;
private:
void emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) override;
protected:
static void _bind_methods();
void _b_generate_block(Ref<VoxelBuffer> out_buffer, Vector3 origin_in_voxels, int lod);
};
#endif // VOXEL_GENERATOR_H

View File

@ -0,0 +1,59 @@
#include "voxel_generator_heightmap.h"
#include "../util/array_slice.h"
#include "../util/fixed_array.h"
VoxelGeneratorHeightmap::VoxelGeneratorHeightmap() {
}
void VoxelGeneratorHeightmap::set_channel(VoxelBuffer::ChannelId channel) {
ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS);
_channel = channel;
}
VoxelBuffer::ChannelId VoxelGeneratorHeightmap::get_channel() const {
return _channel;
}
void VoxelGeneratorHeightmap::set_height_start(float start) {
_range.start = start;
}
float VoxelGeneratorHeightmap::get_height_start() const {
return _range.start;
}
void VoxelGeneratorHeightmap::set_height_range(float range) {
_range.height = range;
}
float VoxelGeneratorHeightmap::get_height_range() const {
return _range.height;
}
void VoxelGeneratorHeightmap::set_iso_scale(float iso_scale) {
_iso_scale = iso_scale;
}
float VoxelGeneratorHeightmap::get_iso_scale() const {
return _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);
ClassDB::bind_method(D_METHOD("set_height_start", "start"), &VoxelGeneratorHeightmap::set_height_start);
ClassDB::bind_method(D_METHOD("get_height_start"), &VoxelGeneratorHeightmap::get_height_start);
ClassDB::bind_method(D_METHOD("set_height_range", "range"), &VoxelGeneratorHeightmap::set_height_range);
ClassDB::bind_method(D_METHOD("get_height_range"), &VoxelGeneratorHeightmap::get_height_range);
ClassDB::bind_method(D_METHOD("set_iso_scale", "scale"), &VoxelGeneratorHeightmap::set_iso_scale);
ClassDB::bind_method(D_METHOD("get_iso_scale"), &VoxelGeneratorHeightmap::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::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::REAL, "iso_scale"), "set_iso_scale", "get_iso_scale");
}

View File

@ -0,0 +1,106 @@
#ifndef VOXEL_GENERATOR_HEIGHTMAP_H
#define VOXEL_GENERATOR_HEIGHTMAP_H
#include "../voxel_buffer.h"
#include "voxel_generator.h"
#include <core/image.h>
class VoxelGeneratorHeightmap : public VoxelGenerator {
GDCLASS(VoxelGeneratorHeightmap, VoxelGenerator)
public:
VoxelGeneratorHeightmap();
void set_channel(VoxelBuffer::ChannelId channel);
VoxelBuffer::ChannelId get_channel() 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 <typename Height_F>
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 (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 (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;
}
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));
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
} // for z
} else {
// 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) {
// Output is blocky, so we can go for just one sample
float h = _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);
}
} // for x
} // for z
} // use_sdf
}
private:
static void _bind_methods();
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;
};
#endif // VOXEL_GENERATOR_HEIGHTMAP_H

View File

@ -0,0 +1,75 @@
#include "voxel_generator_image.h"
#include "../util/array_slice.h"
#include "../util/fixed_array.h"
namespace {
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
VoxelGeneratorImage::VoxelGeneratorImage() {
}
void VoxelGeneratorImage::set_image(Ref<Image> im) {
_image = im;
}
Ref<Image> VoxelGeneratorImage::get_image() const {
return _image;
}
void VoxelGeneratorImage::set_blur_enabled(bool enable) {
_blur_enabled = enable;
}
bool VoxelGeneratorImage::is_blur_enabled() const {
return _blur_enabled;
}
void VoxelGeneratorImage::generate_block(VoxelBlockRequest &input) {
ERR_FAIL_COND(_image.is_null());
VoxelBuffer &out_buffer = **input.voxel_buffer;
Image &image = **_image;
image.lock();
if (_blur_enabled) {
VoxelGeneratorHeightmap::generate(out_buffer,
[&image](int x, int z) { return get_height_blurred(image, x, z); },
input.origin_in_voxels, input.lod);
} else {
VoxelGeneratorHeightmap::generate(out_buffer,
[&image](int x, int z) { return get_height_repeat(image, x, z); },
input.origin_in_voxels, input.lod);
}
image.unlock();
out_buffer.compress_uniform_channels();
}
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);
ClassDB::bind_method(D_METHOD("blur_enabled", "enable"), &VoxelGeneratorImage::set_blur_enabled);
ClassDB::bind_method(D_METHOD("blur_enabled"), &VoxelGeneratorImage::is_blur_enabled);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "image", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_image", "get_image");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "blur_enabled"), "set_blur_enabled", "is_blur_enabled");
}

View File

@ -0,0 +1,30 @@
#ifndef HEADER_VOXEL_GENERATOR_IMAGE
#define HEADER_VOXEL_GENERATOR_IMAGE
#include "voxel_generator_heightmap.h"
#include <core/image.h>
// Provides infinite tiling heightmap based on an image
class VoxelGeneratorImage : public VoxelGeneratorHeightmap {
GDCLASS(VoxelGeneratorImage, VoxelGeneratorHeightmap)
public:
VoxelGeneratorImage();
void set_image(Ref<Image> im);
Ref<Image> get_image() const;
void set_blur_enabled(bool enable);
bool is_blur_enabled() const;
void generate_block(VoxelBlockRequest &input) override;
private:
static void _bind_methods();
private:
Ref<Image> _image;
// Mostly here as demo/tweak. It's better recommended to use an EXR/float image.
bool _blur_enabled = false;
};
#endif // HEADER_VOXEL_GENERATOR_IMAGE

View File

@ -1,38 +1,38 @@
#include "voxel_stream_noise.h"
#include "voxel_generator_noise.h"
void VoxelStreamNoise::set_channel(VoxelBuffer::ChannelId channel) {
void VoxelGeneratorNoise::set_channel(VoxelBuffer::ChannelId channel) {
ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS);
_channel = channel;
}
VoxelBuffer::ChannelId VoxelStreamNoise::get_channel() const {
VoxelBuffer::ChannelId VoxelGeneratorNoise::get_channel() const {
return _channel;
}
void VoxelStreamNoise::set_noise(Ref<OpenSimplexNoise> noise) {
void VoxelGeneratorNoise::set_noise(Ref<OpenSimplexNoise> noise) {
_noise = noise;
}
Ref<OpenSimplexNoise> VoxelStreamNoise::get_noise() const {
Ref<OpenSimplexNoise> VoxelGeneratorNoise::get_noise() const {
return _noise;
}
void VoxelStreamNoise::set_height_start(real_t y) {
void VoxelGeneratorNoise::set_height_start(real_t y) {
_height_start = y;
}
real_t VoxelStreamNoise::get_height_start() const {
real_t VoxelGeneratorNoise::get_height_start() const {
return _height_start;
}
void VoxelStreamNoise::set_height_range(real_t hrange) {
void VoxelGeneratorNoise::set_height_range(real_t hrange) {
if (hrange < 0.1f) {
hrange = 0.1f;
}
_height_range = hrange;
}
real_t VoxelStreamNoise::get_height_range() const {
real_t VoxelGeneratorNoise::get_height_range() const {
return _height_range;
}
@ -70,13 +70,15 @@ static inline float get_shaped_noise(OpenSimplexNoise &noise, float x, float y,
return sum / max;
}
void VoxelStreamNoise::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) {
void VoxelGeneratorNoise::generate_block(VoxelBlockRequest &input) {
ERR_FAIL_COND(out_buffer.is_null());
ERR_FAIL_COND(input.voxel_buffer.is_null());
ERR_FAIL_COND(_noise.is_null());
OpenSimplexNoise &noise = **_noise;
VoxelBuffer &buffer = **out_buffer;
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));
@ -157,19 +159,19 @@ void VoxelStreamNoise::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin
}
}
void VoxelStreamNoise::_bind_methods() {
void VoxelGeneratorNoise::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_noise", "noise"), &VoxelStreamNoise::set_noise);
ClassDB::bind_method(D_METHOD("get_noise"), &VoxelStreamNoise::get_noise);
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"), &VoxelStreamNoise::set_height_start);
ClassDB::bind_method(D_METHOD("get_height_start"), &VoxelStreamNoise::get_height_start);
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"), &VoxelStreamNoise::set_height_range);
ClassDB::bind_method(D_METHOD("get_height_range"), &VoxelStreamNoise::get_height_range);
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"), &VoxelStreamNoise::set_channel);
ClassDB::bind_method(D_METHOD("get_channel"), &VoxelStreamNoise::get_channel);
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");

View File

@ -1,12 +1,12 @@
#ifndef VOXEL_STREAM_NOISE_H
#define VOXEL_STREAM_NOISE_H
#ifndef VOXEL_GENERATOR_NOISE_H
#define VOXEL_GENERATOR_NOISE_H
#include "../util/float_buffer_3d.h"
#include "voxel_stream.h"
#include "voxel_generator.h"
#include <modules/opensimplex/open_simplex_noise.h>
class VoxelStreamNoise : public VoxelStream {
GDCLASS(VoxelStreamNoise, VoxelStream)
class VoxelGeneratorNoise : public VoxelGenerator {
GDCLASS(VoxelGeneratorNoise, VoxelGenerator)
public:
void set_channel(VoxelBuffer::ChannelId channel);
@ -21,7 +21,7 @@ public:
void set_height_range(real_t hrange);
real_t get_height_range() const;
void emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod);
void generate_block(VoxelBlockRequest &input) override;
protected:
static void _bind_methods();
@ -34,4 +34,4 @@ private:
float _height_range = 300;
};
#endif // VOXEL_STREAM_NOISE_H
#endif // VOXEL_GENERATOR_NOISE_H

View File

@ -0,0 +1,53 @@
#include "voxel_generator_noise_2d.h"
VoxelGeneratorNoise2D::VoxelGeneratorNoise2D() {
}
void VoxelGeneratorNoise2D::set_noise(Ref<OpenSimplexNoise> noise) {
_noise = noise;
}
Ref<OpenSimplexNoise> VoxelGeneratorNoise2D::get_noise() const {
return _noise;
}
void VoxelGeneratorNoise2D::set_curve(Ref<Curve> curve) {
_curve = curve;
}
Ref<Curve> VoxelGeneratorNoise2D::get_curve() const {
return _curve;
}
void VoxelGeneratorNoise2D::generate_block(VoxelBlockRequest &input) {
ERR_FAIL_COND(_noise.is_null());
VoxelBuffer &out_buffer = **input.voxel_buffer;
OpenSimplexNoise &noise = **_noise;
if (_curve.is_null()) {
VoxelGeneratorHeightmap::generate(out_buffer,
[&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;
VoxelGeneratorHeightmap::generate(out_buffer,
[&noise, &curve](int x, int z) { return curve.interpolate_baked(0.5 + 0.5 * noise.get_noise_2d(x, z)); },
input.origin_in_voxels, input.lod);
}
out_buffer.compress_uniform_channels();
}
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);
ClassDB::bind_method(D_METHOD("set_curve", "curve"), &VoxelGeneratorNoise2D::set_curve);
ClassDB::bind_method(D_METHOD("get_curve"), &VoxelGeneratorNoise2D::get_curve);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "noise", PROPERTY_HINT_RESOURCE_TYPE, "OpenSimplexNoise"), "set_noise", "get_noise");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve");
}

View File

@ -0,0 +1,28 @@
#ifndef VOXEL_GENERATOR_NOISE_2D_H
#define VOXEL_GENERATOR_NOISE_2D_H
#include "voxel_generator_heightmap.h"
#include <modules/opensimplex/open_simplex_noise.h>
class VoxelGeneratorNoise2D : public VoxelGeneratorHeightmap {
GDCLASS(VoxelGeneratorNoise2D, VoxelGeneratorHeightmap)
public:
VoxelGeneratorNoise2D();
void set_noise(Ref<OpenSimplexNoise> noise);
Ref<OpenSimplexNoise> get_noise() const;
void set_curve(Ref<Curve> curve);
Ref<Curve> get_curve() const;
void generate_block(VoxelBlockRequest &input) override;
private:
static void _bind_methods();
private:
Ref<OpenSimplexNoise> _noise;
Ref<Curve> _curve;
};
#endif // VOXEL_GENERATOR_NOISE_2D_H

View File

@ -1,38 +1,39 @@
#include "voxel_stream_test.h"
#include "voxel_generator_test.h"
VARIANT_ENUM_CAST(VoxelStreamTest::Mode)
VARIANT_ENUM_CAST(VoxelGeneratorTest::Mode)
VoxelStreamTest::VoxelStreamTest() {
VoxelGeneratorTest::VoxelGeneratorTest() {
_mode = MODE_WAVES;
_voxel_type = 1;
_pattern_size = Vector3i(10, 10, 10);
}
void VoxelStreamTest::set_mode(Mode mode) {
void VoxelGeneratorTest::set_mode(Mode mode) {
ERR_FAIL_INDEX(mode, MODE_COUNT)
_mode = mode;
}
void VoxelStreamTest::set_voxel_type(int t) {
void VoxelGeneratorTest::set_voxel_type(int t) {
_voxel_type = t;
}
int VoxelStreamTest::get_voxel_type() const {
int VoxelGeneratorTest::get_voxel_type() const {
return _voxel_type;
}
void VoxelStreamTest::set_pattern_size(Vector3i size) {
void VoxelGeneratorTest::set_pattern_size(Vector3i size) {
ERR_FAIL_COND(size.x < 1 || size.y < 1 || size.z < 1);
_pattern_size = size;
}
void VoxelStreamTest::set_pattern_offset(Vector3i offset) {
void VoxelGeneratorTest::set_pattern_offset(Vector3i offset) {
_pattern_offset = offset;
}
void VoxelStreamTest::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin, int lod) {
ERR_FAIL_COND(out_buffer.is_null());
void VoxelGeneratorTest::generate_block(VoxelBlockRequest &input) {
ERR_FAIL_COND(input.voxel_buffer.is_null());
if (lod != 0) {
if (input.lod != 0) {
// TODO Handle higher lods
return;
}
@ -40,16 +41,16 @@ void VoxelStreamTest::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin,
switch (_mode) {
case MODE_FLAT:
generate_block_flat(**out_buffer, origin, lod);
generate_block_flat(**input.voxel_buffer, input.origin_in_voxels, input.lod);
break;
case MODE_WAVES:
generate_block_waves(**out_buffer, origin, lod);
generate_block_waves(**input.voxel_buffer, input.origin_in_voxels, input.lod);
break;
}
}
void VoxelStreamTest::generate_block_flat(VoxelBuffer &out_buffer, Vector3i origin, int lod) {
void VoxelGeneratorTest::generate_block_flat(VoxelBuffer &out_buffer, Vector3i origin, int lod) {
// TODO Don't expect a block pos, but a voxel pos!
Vector3i size = out_buffer.get_size();
@ -67,7 +68,7 @@ void VoxelStreamTest::generate_block_flat(VoxelBuffer &out_buffer, Vector3i orig
}
}
void VoxelStreamTest::generate_block_waves(VoxelBuffer &out_buffer, Vector3i origin, int lod) {
void VoxelGeneratorTest::generate_block_waves(VoxelBuffer &out_buffer, Vector3i origin, int lod) {
// TODO Don't expect a block pos, but a voxel pos!
Vector3i size = out_buffer.get_size();
@ -108,19 +109,19 @@ void VoxelStreamTest::generate_block_waves(VoxelBuffer &out_buffer, Vector3i ori
}
}
void VoxelStreamTest::_bind_methods() {
void VoxelGeneratorTest::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mode", "mode"), &VoxelStreamTest::set_mode);
ClassDB::bind_method(D_METHOD("get_mode"), &VoxelStreamTest::get_mode);
ClassDB::bind_method(D_METHOD("set_mode", "mode"), &VoxelGeneratorTest::set_mode);
ClassDB::bind_method(D_METHOD("get_mode"), &VoxelGeneratorTest::get_mode);
ClassDB::bind_method(D_METHOD("set_voxel_type", "id"), &VoxelStreamTest::set_voxel_type);
ClassDB::bind_method(D_METHOD("get_voxel_type"), &VoxelStreamTest::get_voxel_type);
ClassDB::bind_method(D_METHOD("set_voxel_type", "id"), &VoxelGeneratorTest::set_voxel_type);
ClassDB::bind_method(D_METHOD("get_voxel_type"), &VoxelGeneratorTest::get_voxel_type);
ClassDB::bind_method(D_METHOD("set_pattern_size", "size"), &VoxelStreamTest::_set_pattern_size);
ClassDB::bind_method(D_METHOD("get_pattern_size"), &VoxelStreamTest::_get_pattern_size);
ClassDB::bind_method(D_METHOD("set_pattern_size", "size"), &VoxelGeneratorTest::_set_pattern_size);
ClassDB::bind_method(D_METHOD("get_pattern_size"), &VoxelGeneratorTest::_get_pattern_size);
ClassDB::bind_method(D_METHOD("set_pattern_offset", "offset"), &VoxelStreamTest::_set_pattern_offset);
ClassDB::bind_method(D_METHOD("get_pattern_offset"), &VoxelStreamTest::_get_pattern_offset);
ClassDB::bind_method(D_METHOD("set_pattern_offset", "offset"), &VoxelGeneratorTest::_set_pattern_offset);
ClassDB::bind_method(D_METHOD("get_pattern_offset"), &VoxelGeneratorTest::_get_pattern_offset);
ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Flat,Waves"), "set_mode", "get_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "voxel_type", PROPERTY_HINT_RANGE, "0,255,1"), "set_voxel_type", "get_voxel_type");

View File

@ -1,20 +1,21 @@
#ifndef VOXEL_STREAM_TEST_H
#define VOXEL_STREAM_TEST_H
#ifndef VOXEL_GENERATOR_TEST_H
#define VOXEL_GENERATOR_TEST_H
#include "voxel_stream.h"
#include "voxel_generator.h"
class VoxelStreamTest : public VoxelStream {
GDCLASS(VoxelStreamTest, VoxelStream)
class VoxelGeneratorTest : public VoxelGenerator {
GDCLASS(VoxelGeneratorTest, VoxelGenerator)
public:
enum Mode {
MODE_FLAT,
MODE_WAVES
MODE_WAVES,
MODE_COUNT
};
VoxelStreamTest();
VoxelGeneratorTest();
virtual void emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin, int lod);
void generate_block(VoxelBlockRequest &input) override;
void set_mode(Mode mode);
Mode get_mode() const { return _mode; }
@ -47,4 +48,4 @@ private:
Vector3i _pattern_size;
};
#endif // VOXEL_STREAM_TEST_H
#endif // VOXEL_GENERATOR_TEST_H

View File

@ -1,458 +0,0 @@
#include "voxel_mesher_mc.h"
#include "../transvoxel/transvoxel_tables.cpp"
#include <core/os/os.h>
namespace {
inline float tof(int8_t v) {
return static_cast<float>(v) / 256.f;
}
inline int8_t tos(uint8_t v) {
return v - 128;
}
// Values considered negative have a sign bit of 1
inline uint8_t sign(int8_t v) {
return (v >> 7) & 1;
}
//
// 6-------7
// /| /|
// / | / | Corners
// 4-------5 |
// | 2----|--3
// | / | / z y
// |/ |/ |/
// 0-------1 o--x
//
// The fact it follows a binary pattern is important
const Vector3i g_corner_dirs[8] = {
Vector3i(0, 0, 0),
Vector3i(1, 0, 0),
Vector3i(0, 1, 0),
Vector3i(1, 1, 0),
Vector3i(0, 0, 1),
Vector3i(1, 0, 1),
Vector3i(0, 1, 1),
Vector3i(1, 1, 1)
};
inline Vector3i dir_to_prev_vec(uint8_t dir) {
//return g_corner_dirs[mask] - Vector3(1,1,1);
return Vector3i(
-(dir & 1),
-((dir >> 1) & 1),
-((dir >> 2) & 1));
}
// Wrapped to invert SDF data, Transvoxel apparently works backwards?
inline uint8_t get_voxel(const VoxelBuffer &vb, int x, int y, int z, int channel) {
return 255 - vb.get_voxel(x, y, z, channel);
}
inline uint8_t get_voxel(const VoxelBuffer &vb, Vector3i pos, int channel) {
return get_voxel(vb, pos.x, pos.y, pos.z, channel);
}
inline int8_t increase(int8_t v, int8_t a) {
// Actually decreasing...
int8_t res = v - a;
if (res > v) {
// Underflowed, clamp to min
return -128;
}
return res;
}
} // namespace
VoxelMesherMC::VoxelMesherMC() {
set_padding(MIN_PADDING, MAX_PADDING);
}
void VoxelMesherMC::build(VoxelMesher::Output &output, const VoxelMesher::Input &input) {
int channel = VoxelBuffer::CHANNEL_SDF;
// Initialize dynamic memory:
// These vectors are re-used.
// We don't know in advance how much geometry we are going to produce.
// Once capacity is big enough, no more memory should be allocated
_output_vertices.clear();
_output_normals.clear();
_output_indices.clear();
const VoxelBuffer &voxels = input.voxels;
build_internal(voxels, channel);
// OS::get_singleton()->print("vertices: %i, normals: %i, indices: %i\n",
// m_output_vertices.size(),
// m_output_normals.size(),
// m_output_indices.size());
if (_output_vertices.size() == 0) {
// The mesh can be empty
return;
}
PoolVector<Vector3> vertices;
PoolVector<Vector3> normals;
PoolVector<int> indices;
raw_copy_to(vertices, _output_vertices);
raw_copy_to(normals, _output_normals);
raw_copy_to(indices, _output_indices);
Array arrays;
arrays.resize(Mesh::ARRAY_MAX);
arrays[Mesh::ARRAY_VERTEX] = vertices;
if (_output_normals.size() != 0) {
arrays[Mesh::ARRAY_NORMAL] = normals;
}
arrays[Mesh::ARRAY_INDEX] = indices;
output.surfaces.push_back(arrays);
output.primitive_type = Mesh::PRIMITIVE_TRIANGLES;
}
void VoxelMesherMC::set_seam_mode(SeamMode mode) {
_seam_mode = mode;
}
VoxelMesherMC::SeamMode VoxelMesherMC::get_seam_mode() const {
return _seam_mode;
}
void VoxelMesherMC::build_internal(const VoxelBuffer &voxels, unsigned int channel) {
// Each 2x2 voxel group is a "cell"
if (voxels.is_uniform(channel)) {
// Nothing to extract, because constant isolevels never cross the threshold and describe no surface
return;
}
Vector3i block_size = voxels.get_size();
// Iterate all cells, with expected padding.
// The algorithm works with a 2x2 kernel and needs extra neighbors for normals,
// so it looks 1 voxel away in negative axes, and 2 voxels away in positive axes.
Vector3i pos;
Vector3i min_pos(get_minimum_padding());
Vector3i max_pos(block_size - Vector3i(get_maximum_padding()));
if (_seam_mode == SEAM_OVERLAP) {
// When this is enabled, the algorithm may detect if it's on a border,
// and will avoid looking an extra neighbor for normals, while polygonizing an extra cell.
min_pos -= Vector3i(1);
max_pos += Vector3i(1);
block_size += Vector3i(2);
}
// Prepare vertex reuse cache:
// We'll iterate deck by deck in deterministic order, so we can link vertices together from the previous deck.
_block_size = block_size;
unsigned int deck_area = block_size.x * block_size.y;
for (int i = 0; i < 2; ++i) {
_cache[i].clear();
_cache[i].resize(deck_area);
}
bool overpoly_bx[2] = { false };
bool overpoly_by[2] = { false };
bool overpoly_bz[2] = { false };
for (pos.z = min_pos.z; pos.z < max_pos.z; ++pos.z) {
for (pos.y = min_pos.y; pos.y < max_pos.y; ++pos.y) {
for (pos.x = min_pos.x; pos.x < max_pos.x; ++pos.x) {
// Get the value of cells.
// Negative values are "solid" and positive are "air".
// Due to raw cells being unsigned 8-bit, they get converted to signed.
int8_t cell_samples[8] = {
tos(get_voxel(voxels, pos.x, pos.y, pos.z, channel)),
tos(get_voxel(voxels, pos.x + 1, pos.y, pos.z, channel)),
tos(get_voxel(voxels, pos.x, pos.y + 1, pos.z, channel)),
tos(get_voxel(voxels, pos.x + 1, pos.y + 1, pos.z, channel)),
tos(get_voxel(voxels, pos.x, pos.y, pos.z + 1, channel)),
tos(get_voxel(voxels, pos.x + 1, pos.y, pos.z + 1, channel)),
tos(get_voxel(voxels, pos.x, pos.y + 1, pos.z + 1, channel)),
tos(get_voxel(voxels, pos.x + 1, pos.y + 1, pos.z + 1, channel))
};
if (_seam_mode == SEAM_OVERLAP) {
// In overpoly, we extend the polygonized area, but the extended vertices will have increased distance samples.
// The intented effect is that the isosurface will be slightly contracted,
// so if we stitch two chunks of different LOD with overpolys,
// they will overlap and will fill cracks by crossing over each other.
overpoly_bx[0] = pos.x == min_pos.x;
overpoly_bx[1] = pos.x == max_pos.x - 1;
overpoly_by[0] = pos.y == min_pos.y;
overpoly_by[1] = pos.y == max_pos.y - 1;
overpoly_bz[0] = pos.z == min_pos.z;
overpoly_bz[1] = pos.z == max_pos.z - 1;
const int8_t inc = 32;
for (unsigned int i = 0; i < 8; ++i) {
if (overpoly_bx[i & 1] || overpoly_by[(i >> 1) & 1] || overpoly_bz[(i >> 2) & 1]) {
cell_samples[i] = increase(cell_samples[i], inc);
}
}
}
// Concatenate the sign of cell values to obtain the case code.
// Index 0 is the less significant bit, and index 7 is the most significant bit.
uint8_t case_code = sign(cell_samples[0]);
case_code |= (sign(cell_samples[1]) << 1);
case_code |= (sign(cell_samples[2]) << 2);
case_code |= (sign(cell_samples[3]) << 3);
case_code |= (sign(cell_samples[4]) << 4);
case_code |= (sign(cell_samples[5]) << 5);
case_code |= (sign(cell_samples[6]) << 6);
case_code |= (sign(cell_samples[7]) << 7);
{
ReuseCell &rc = get_reuse_cell(pos);
rc.case_index = case_code;
}
if (case_code == 0 || case_code == 255) {
// If the case_code is 0 or 255, there is no triangulation to do
continue;
}
// TODO We might not always need all of them
// Compute normals
Vector3 corner_normals[8];
for (unsigned int i = 0; i < 8; ++i) {
Vector3i p = pos + g_corner_dirs[i];
if (_seam_mode == SEAM_OVERLAP) {
// In case of overpoly, we keep the same normals as the connected vertex within the "normal" area
if (overpoly_bx[0]) {
++p.x;
}
if (overpoly_bx[1]) {
--p.x;
}
if (overpoly_by[0]) {
++p.y;
}
if (overpoly_by[1]) {
--p.y;
}
if (overpoly_bz[0]) {
++p.z;
}
if (overpoly_bz[1]) {
--p.z;
}
}
float nx = tof(tos(get_voxel(voxels, p - Vector3i(1, 0, 0), channel))) - tof(tos(get_voxel(voxels, p + Vector3i(1, 0, 0), channel)));
float ny = tof(tos(get_voxel(voxels, p - Vector3i(0, 1, 0), channel))) - tof(tos(get_voxel(voxels, p + Vector3i(0, 1, 0), channel)));
float nz = tof(tos(get_voxel(voxels, p - Vector3i(0, 0, 1), channel))) - tof(tos(get_voxel(voxels, p + Vector3i(0, 0, 1), channel)));
corner_normals[i] = Vector3(nx, ny, nz);
corner_normals[i].normalize();
}
// For cells occurring along the minimal boundaries of a block,
// the preceding cells needed for vertex reuse may not exist.
// In these cases, we allow new vertex creation on additional edges of a cell.
// While iterating through the cells in a block, a 3-bit mask is maintained whose bits indicate
// whether corresponding bits in a direction code are valid
uint8_t direction_validity_mask =
(pos.x > min_pos.x ? 1 : 0) |
((pos.y > min_pos.y ? 1 : 0) << 1) |
((pos.z > min_pos.z ? 1 : 0) << 2);
uint8_t regular_cell_class_index = Transvoxel::regularCellClass[case_code];
Transvoxel::RegularCellData regular_cell_class = Transvoxel::regularCellData[regular_cell_class_index];
uint8_t triangle_count = regular_cell_class.geometryCounts & 0x0f;
uint8_t vertex_count = (regular_cell_class.geometryCounts & 0xf0) >> 4;
int cell_mesh_indices[12];
// For each vertex in the case
for (unsigned int i = 0; i < vertex_count; ++i) {
// The case index maps to a list of 16-bit codes providing information about the edges on which the vertices lie.
// The low byte of each 16-bit code contains the corner indexes of the edges endpoints in one nibble each,
// and the high byte contains the mapping code shown in Figure 3.8(b)
unsigned short rvd = Transvoxel::regularVertexData[case_code][i];
unsigned short edge_code_low = rvd & 0xff;
unsigned short edge_code_high = (rvd >> 8) & 0xff;
// Get corner indexes in the low nibble (always ordered so the higher comes last)
uint8_t v0 = (edge_code_low >> 4) & 0xf;
uint8_t v1 = edge_code_low & 0xf;
ERR_FAIL_COND(v1 <= v0);
// Get voxel values at the corners
int sample0 = cell_samples[v0]; // called d0 in the paper
int sample1 = cell_samples[v1]; // called d1 in the paper
// TODO Zero-division is not mentionned in the paper??
ERR_FAIL_COND(sample1 == sample0);
ERR_FAIL_COND(sample1 == 0 && sample0 == 0);
// Get interpolation position
// We use an 8-bit fraction, allowing the new vertex to be located at one of 257 possible
// positions along the edge when both endpoints are included.
int t = (sample1 << 8) / (sample1 - sample0);
float t0 = static_cast<float>(t) / 256.f;
float t1 = static_cast<float>(0x0100 - t) / 256.f;
Vector3i p0 = pos + g_corner_dirs[v0];
Vector3i p1 = pos + g_corner_dirs[v1];
if (t & 0xff) {
// Vertex lies in the interior of the edge.
// Each edge of a cell is assigned an 8-bit code, as shown in Figure 3.8(b),
// that provides a mapping to a preceding cell and the coincident edge on that preceding cell
// for which new vertex creation was allowed.
// The high nibble of this code indicates which direction to go in order to reach the correct preceding cell.
// The bit values 1, 2, and 4 in this nibble indicate that we must subtract one
// from the x, y, and/or z coordinate, respectively.
uint8_t reuse_dir = (edge_code_high >> 4) & 0xf;
uint8_t reuse_vertex_index = edge_code_high & 0xf;
bool can_reuse = (reuse_dir & direction_validity_mask) == reuse_dir;
if (can_reuse) {
Vector3i cache_pos = pos + dir_to_prev_vec(reuse_dir);
ReuseCell &prev_cell = get_reuse_cell(cache_pos);
if (prev_cell.case_index == 0 || prev_cell.case_index == 255) {
// TODO I don't think this can happen for non-corner vertices.
cell_mesh_indices[i] = -1;
} else {
// Will reuse a previous vertice
cell_mesh_indices[i] = prev_cell.vertices[reuse_vertex_index];
}
}
if (!can_reuse || cell_mesh_indices[i] == -1) {
// Going to create a new vertice
cell_mesh_indices[i] = _output_vertices.size();
Vector3 pi = p0.to_vec3() * t0 + p1.to_vec3() * t1;
Vector3 primary = pi; //pos.to_vec3() + pi;
Vector3 normal = corner_normals[v0] * t0 + corner_normals[v1] * t1;
emit_vertex(primary, normal);
if (reuse_dir & 8) {
// Store the generated vertex so that other cells can reuse it.
ReuseCell &rc = get_reuse_cell(pos);
rc.vertices[reuse_vertex_index] = cell_mesh_indices[i];
}
}
} else if (t == 0 && v1 == 7) {
// This cell owns the vertex, so it should be created.
cell_mesh_indices[i] = _output_vertices.size();
Vector3 pi = p0.to_vec3() * t0 + p1.to_vec3() * t1;
Vector3 primary = pi; //pos.to_vec3() + pi;
Vector3 normal = corner_normals[v0] * t0 + corner_normals[v1] * t1;
emit_vertex(primary, normal);
ReuseCell &rc = get_reuse_cell(pos);
rc.vertices[0] = cell_mesh_indices[i];
} else {
// Always try to reuse previous vertices in these cases
// A 3-bit direction code leading to the proper cell can easily be obtained by
// inverting the 3-bit corner index (bitwise, by exclusive ORing with the number 7).
// The corner index depends on the value of t, t = 0 means that we're at the higher
// numbered endpoint.
uint8_t reuse_dir = (t == 0 ? v1 ^ 7 : v0 ^ 7);
bool can_reuse = (reuse_dir & direction_validity_mask) == reuse_dir;
// Note: the only difference with similar code above is that we take vertice 0 in the `else`
if (can_reuse) {
Vector3i cache_pos = pos + dir_to_prev_vec(reuse_dir);
ReuseCell prev_cell = get_reuse_cell(cache_pos);
// The previous cell might not have any geometry, and we
// might therefore have to create a new vertex anyway.
if (prev_cell.case_index == 0 || prev_cell.case_index == 255) {
cell_mesh_indices[i] = -1;
} else {
cell_mesh_indices[i] = prev_cell.vertices[0];
}
}
if (!can_reuse || cell_mesh_indices[i] < 0) {
cell_mesh_indices[i] = _output_vertices.size();
Vector3 pi = p0.to_vec3() * t0 + p1.to_vec3() * t1;
Vector3 primary = pi; //pos.to_vec3() + pi;
Vector3 normal = corner_normals[v0] * t0 + corner_normals[v1] * t1;
emit_vertex(primary, normal);
}
}
} // for each cell vertice
for (int t = 0; t < triangle_count; ++t) {
for (int i = 0; i < 3; ++i) {
int index = cell_mesh_indices[regular_cell_class.vertexIndex[t * 3 + i]];
_output_indices.push_back(index);
}
}
} // x
} // y
} // z
}
VoxelMesherMC::ReuseCell &VoxelMesherMC::get_reuse_cell(Vector3i pos) {
// CRASH_COND(pos.x < 0);
// CRASH_COND(pos.y < 0);
// CRASH_COND(pos.z < 0);
// CRASH_COND(pos.x >= _block_size.x);
// CRASH_COND(pos.y >= _block_size.y);
// CRASH_COND(pos.z >= _block_size.z);
int j = pos.z & 1;
int i = pos.y * _block_size.y + pos.x;
return _cache[j][i];
}
void VoxelMesherMC::emit_vertex(Vector3 primary, Vector3 normal) {
_output_vertices.push_back(primary - Vector3(MIN_PADDING, MIN_PADDING, MIN_PADDING));
_output_normals.push_back(normal);
}
VoxelMesher *VoxelMesherMC::clone() {
VoxelMesherMC *c = memnew(VoxelMesherMC);
c->_seam_mode = _seam_mode;
return c;
}
void VoxelMesherMC::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_seam_mode", "mode"), &VoxelMesherMC::set_seam_mode);
ClassDB::bind_method(D_METHOD("get_seam_mode"), &VoxelMesherMC::get_seam_mode);
BIND_ENUM_CONSTANT(SEAM_NONE);
BIND_ENUM_CONSTANT(SEAM_OVERLAP);
}

View File

@ -1,55 +0,0 @@
#ifndef VOXEL_MESHER_MC_H
#define VOXEL_MESHER_MC_H
#include "../voxel_mesher.h"
// TODO Remove it.
// Simple marching cubes.
// Implementation is simplified from old Transvoxel code.
class VoxelMesherMC : public VoxelMesher {
GDCLASS(VoxelMesherMC, VoxelMesher)
public:
static const int MIN_PADDING = 1;
static const int MAX_PADDING = 2;
enum SeamMode {
SEAM_NONE,
SEAM_OVERLAP
};
VoxelMesherMC();
void build(VoxelMesher::Output &output, const VoxelMesher::Input &input) override;
void set_seam_mode(SeamMode mode);
SeamMode get_seam_mode() const;
VoxelMesher *clone() override;
protected:
static void _bind_methods();
private:
struct ReuseCell {
int vertices[4] = { -1 };
int case_index = 0;
};
void build_internal(const VoxelBuffer &voxels, unsigned int channel);
ReuseCell &get_reuse_cell(Vector3i pos);
void emit_vertex(Vector3 primary, Vector3 normal);
private:
std::vector<ReuseCell> _cache[2];
Vector3i _block_size;
SeamMode _seam_mode = SEAM_NONE;
std::vector<Vector3> _output_vertices;
std::vector<Vector3> _output_normals;
std::vector<int> _output_indices;
};
VARIANT_ENUM_CAST(VoxelMesherMC::SeamMode)
#endif // VOXEL_MESHER_MC_H

View File

@ -1,16 +1,16 @@
#include "register_types.h"
#include "edition/voxel_tool.h"
#include "generators/voxel_generator_heightmap.h"
#include "generators/voxel_generator_image.h"
#include "generators/voxel_generator_noise.h"
#include "generators/voxel_generator_noise_2d.h"
#include "generators/voxel_generator_test.h"
#include "meshers/blocky/voxel_mesher_blocky.h"
#include "meshers/dmc/voxel_mesher_dmc.h"
#include "meshers/mc/voxel_mesher_mc.h"
#include "meshers/transvoxel/voxel_mesher_transvoxel.h"
#include "streams/voxel_stream_block_files.h"
#include "streams/voxel_stream_file.h"
#include "streams/voxel_stream_heightmap.h"
#include "streams/voxel_stream_image.h"
#include "streams/voxel_stream_noise.h"
#include "streams/voxel_stream_noise_2d.h"
#include "streams/voxel_stream_region_files.h"
#include "streams/voxel_stream_test.h"
#include "terrain/voxel_box_mover.h"
#include "terrain/voxel_lod_terrain.h"
#include "terrain/voxel_map.h"
@ -19,7 +19,6 @@
#include "voxel_library.h"
#include "voxel_memory_pool.h"
#include "voxel_string_names.h"
#include "voxel_tool.h"
void register_voxel_types() {
@ -37,15 +36,18 @@ void register_voxel_types() {
// Streams
ClassDB::register_class<VoxelStream>();
ClassDB::register_class<VoxelStreamTest>();
ClassDB::register_class<VoxelStreamHeightmap>();
ClassDB::register_class<VoxelStreamImage>();
ClassDB::register_class<VoxelStreamNoise>();
ClassDB::register_class<VoxelStreamNoise2D>();
ClassDB::register_class<VoxelStreamFile>();
ClassDB::register_class<VoxelStreamBlockFiles>();
ClassDB::register_class<VoxelStreamRegionFiles>();
// Generators
ClassDB::register_class<VoxelGenerator>();
ClassDB::register_class<VoxelGeneratorTest>();
ClassDB::register_class<VoxelGeneratorHeightmap>();
ClassDB::register_class<VoxelGeneratorImage>();
ClassDB::register_class<VoxelGeneratorNoise2D>();
ClassDB::register_class<VoxelGeneratorNoise>();
// Helpers
ClassDB::register_class<VoxelBoxMover>();
ClassDB::register_class<VoxelRaycastResult>();
@ -56,7 +58,6 @@ void register_voxel_types() {
ClassDB::register_class<VoxelMesherBlocky>();
ClassDB::register_class<VoxelMesherTransvoxel>();
ClassDB::register_class<VoxelMesherDMC>();
ClassDB::register_class<VoxelMesherMC>();
VoxelMemoryPool::create_singleton();
VoxelStringNames::create_singleton();

View File

@ -0,0 +1,15 @@
#ifndef VOXEL_BLOCK_REQUEST_H
#define VOXEL_BLOCK_REQUEST_H
#include "../math/vector3i.h"
#include "../voxel_buffer.h"
class VoxelBuffer;
struct VoxelBlockRequest {
Ref<VoxelBuffer> voxel_buffer;
Vector3i origin_in_voxels;
int lod;
};
#endif // VOXEL_BLOCK_REQUEST_H

View File

@ -43,17 +43,17 @@ void VoxelStream::immerge_block(Ref<VoxelBuffer> buffer, Vector3i origin_in_voxe
}
}
void VoxelStream::emerge_blocks(Vector<VoxelStream::BlockRequest> &p_blocks) {
void VoxelStream::emerge_blocks(Vector<VoxelBlockRequest> &p_blocks) {
// Default implementation. May matter for some stream types to optimize loading.
for (int i = 0; i < p_blocks.size(); ++i) {
BlockRequest &r = p_blocks.write[i];
VoxelBlockRequest &r = p_blocks.write[i];
emerge_block(r.voxel_buffer, r.origin_in_voxels, r.lod);
}
}
void VoxelStream::immerge_blocks(Vector<VoxelStream::BlockRequest> &p_blocks) {
void VoxelStream::immerge_blocks(Vector<VoxelBlockRequest> &p_blocks) {
for (int i = 0; i < p_blocks.size(); ++i) {
BlockRequest &r = p_blocks.write[i];
VoxelBlockRequest &r = p_blocks.write[i];
immerge_block(r.voxel_buffer, r.origin_in_voxels, r.lod);
}
}

View File

@ -2,20 +2,15 @@
#define VOXEL_STREAM_H
#include "../util/zprofiling.h"
#include "../voxel_buffer.h"
#include "voxel_block_request.h"
#include <core/resource.h>
// Provides access to a source of paged voxel data.
// Provides access to a source of paged voxel data, which may load and save.
// Must be implemented in a multi-thread-safe way.
// If you are looking for a more specialized API to generate voxels, use VoxelGenerator.
class VoxelStream : public Resource {
GDCLASS(VoxelStream, Resource)
public:
struct BlockRequest {
Ref<VoxelBuffer> voxel_buffer;
Vector3i origin_in_voxels;
int lod;
};
struct Stats {
int file_openings = 0;
int time_spent_opening_files = 0;
@ -31,12 +26,12 @@ public:
virtual void immerge_block(Ref<VoxelBuffer> buffer, Vector3i origin_in_voxels, int lod);
// Note: vector is passed by ref for performance. Don't reorder it.
virtual void emerge_blocks(Vector<BlockRequest> &p_blocks);
virtual void emerge_blocks(Vector<VoxelBlockRequest> &p_blocks);
// Returns multiple blocks of voxels to the stream.
// Generators usually don't implement it.
// This function is recommended if you save to files, because you can batch their access.
virtual void immerge_blocks(Vector<BlockRequest> &p_blocks);
virtual void immerge_blocks(Vector<VoxelBlockRequest> &p_blocks);
virtual bool is_thread_safe() const;
virtual bool is_cloneable() const;

View File

@ -23,18 +23,18 @@ void VoxelStreamFile::emerge_block_fallback(Ref<VoxelBuffer> out_buffer, Vector3
// This function is just a helper around the true thing, really. I might remove it in the future.
BlockRequest r;
VoxelBlockRequest r;
r.voxel_buffer = out_buffer;
r.origin_in_voxels = origin_in_voxels;
r.lod = lod;
Vector<BlockRequest> requests;
Vector<VoxelBlockRequest> requests;
requests.push_back(r);
emerge_blocks_fallback(requests);
}
void VoxelStreamFile::emerge_blocks_fallback(Vector<VoxelStreamFile::BlockRequest> &requests) {
void VoxelStreamFile::emerge_blocks_fallback(Vector<VoxelBlockRequest> &requests) {
VOXEL_PROFILE_SCOPE(profile_scope);
if (_fallback_stream.is_valid()) {

View File

@ -28,7 +28,7 @@ protected:
static void _bind_methods();
void emerge_block_fallback(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod);
void emerge_blocks_fallback(Vector<BlockRequest> &requests);
void emerge_blocks_fallback(Vector<VoxelBlockRequest> &requests);
FileAccess *open_file(const String &fpath, int mode_flags, Error *err);

View File

@ -1,70 +0,0 @@
#include "voxel_stream_heightmap.h"
#include "../util/array_slice.h"
#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;
}
float VoxelStreamHeightmap::get_height_start() const {
return _heightmap.settings.range.base;
}
void VoxelStreamHeightmap::set_height_range(float range) {
_heightmap.settings.range.span = range;
}
float VoxelStreamHeightmap::get_height_range() const {
return _heightmap.settings.range.span;
}
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);
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);
}

View File

@ -1,117 +0,0 @@
#ifndef VOXEL_STREAM_HEIGHTMAP_H
#define VOXEL_STREAM_HEIGHTMAP_H
#include "../util/heightmap_sdf.h"
#include "voxel_stream.h"
#include <core/image.h>
class VoxelStreamHeightmap : public VoxelStream {
GDCLASS(VoxelStreamHeightmap, 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;
protected:
template <typename Height_F>
void generate(VoxelBuffer &out_buffer, Height_F height_func, int ox, int oy, int oz, 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()) {
// The bottom of the block is above the highest ground can go (default is air)
return;
}
if (oy + (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;
}
const int stride = 1 << lod;
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);
}
for (int z = 0; z < bs.z; ++z) {
for (int x = 0; x < bs.x; ++x) {
// 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);
}
} // for x
} // for z
} else {
// Blocky
int gz = oz;
for (int z = 0; z < bs.z; ++z, gz += stride) {
int gx = ox;
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;
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);
}
} // for x
} // for z
} // use_sdf
}
private:
static void _bind_methods();
private:
HeightmapSdf _heightmap;
VoxelBuffer::ChannelId _channel = VoxelBuffer::CHANNEL_TYPE;
int _matter_type = 1;
};
VARIANT_ENUM_CAST(VoxelStreamHeightmap::SdfMode)
#endif // VOXEL_STREAM_HEIGHTMAP_H

View File

@ -1,48 +0,0 @@
#include "voxel_stream_image.h"
#include "../util/array_slice.h"
#include "../util/fixed_array.h"
namespace {
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;
}
} // namespace
VoxelStreamImage::VoxelStreamImage() {
}
void VoxelStreamImage::set_image(Ref<Image> im) {
_image = im;
}
Ref<Image> VoxelStreamImage::get_image() const {
return _image;
}
void VoxelStreamImage::emerge_block(Ref<VoxelBuffer> p_out_buffer, Vector3i origin_in_voxels, int lod) {
ERR_FAIL_COND(_image.is_null());
VoxelBuffer &out_buffer = **p_out_buffer;
Image &image = **_image;
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);
image.unlock();
out_buffer.compress_uniform_channels();
}
void VoxelStreamImage::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_image", "image"), &VoxelStreamImage::set_image);
ClassDB::bind_method(D_METHOD("get_image"), &VoxelStreamImage::get_image);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "image", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_image", "get_image");
}

View File

@ -1,25 +0,0 @@
#ifndef HEADER_VOXEL_STREAM_IMAGE
#define HEADER_VOXEL_STREAM_IMAGE
#include "voxel_stream_heightmap.h"
#include <core/image.h>
// Provides infinite tiling heightmap based on an image
class VoxelStreamImage : public VoxelStreamHeightmap {
GDCLASS(VoxelStreamImage, VoxelStreamHeightmap)
public:
VoxelStreamImage();
void set_image(Ref<Image> im);
Ref<Image> get_image() const;
void emerge_block(Ref<VoxelBuffer> p_out_buffer, Vector3i origin_in_voxels, int lod);
private:
static void _bind_methods();
private:
Ref<Image> _image;
};
#endif // HEADER_VOXEL_STREAM_IMAGE

View File

@ -1,53 +0,0 @@
#include "voxel_stream_noise_2d.h"
VoxelStreamNoise2D::VoxelStreamNoise2D() {
}
void VoxelStreamNoise2D::set_noise(Ref<OpenSimplexNoise> noise) {
_noise = noise;
}
Ref<OpenSimplexNoise> VoxelStreamNoise2D::get_noise() const {
return _noise;
}
void VoxelStreamNoise2D::set_curve(Ref<Curve> curve) {
_curve = curve;
}
Ref<Curve> VoxelStreamNoise2D::get_curve() const {
return _curve;
}
void VoxelStreamNoise2D::emerge_block(Ref<VoxelBuffer> p_out_buffer, Vector3i origin_in_voxels, int lod) {
ERR_FAIL_COND(_noise.is_null());
VoxelBuffer &out_buffer = **p_out_buffer;
OpenSimplexNoise &noise = **_noise;
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);
} 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);
}
out_buffer.compress_uniform_channels();
}
void VoxelStreamNoise2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_noise", "noise"), &VoxelStreamNoise2D::set_noise);
ClassDB::bind_method(D_METHOD("get_noise"), &VoxelStreamNoise2D::get_noise);
ClassDB::bind_method(D_METHOD("set_curve", "curve"), &VoxelStreamNoise2D::set_curve);
ClassDB::bind_method(D_METHOD("get_curve"), &VoxelStreamNoise2D::get_curve);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "noise", PROPERTY_HINT_RESOURCE_TYPE, "OpenSimplexNoise"), "set_noise", "get_noise");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve");
}

View File

@ -1,28 +0,0 @@
#ifndef VOXEL_STREAM_NOISE_2D_H
#define VOXEL_STREAM_NOISE_2D_H
#include "voxel_stream_heightmap.h"
#include <modules/opensimplex/open_simplex_noise.h>
class VoxelStreamNoise2D : public VoxelStreamHeightmap {
GDCLASS(VoxelStreamNoise2D, VoxelStreamHeightmap)
public:
VoxelStreamNoise2D();
void set_noise(Ref<OpenSimplexNoise> noise);
Ref<OpenSimplexNoise> get_noise() const;
void set_curve(Ref<Curve> curve);
Ref<Curve> get_curve() const;
void emerge_block(Ref<VoxelBuffer> p_out_buffer, Vector3i origin_in_voxels, int lod);
private:
static void _bind_methods();
private:
Ref<OpenSimplexNoise> _noise;
Ref<Curve> _curve;
};
#endif // VOXEL_STREAM_NOISE_2D_H

View File

@ -32,42 +32,42 @@ VoxelStreamRegionFiles::~VoxelStreamRegionFiles() {
}
void VoxelStreamRegionFiles::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) {
BlockRequest r;
VoxelBlockRequest r;
r.voxel_buffer = out_buffer;
r.origin_in_voxels = origin_in_voxels;
r.lod = lod;
Vector<BlockRequest> requests;
Vector<VoxelBlockRequest> requests;
requests.push_back(r);
emerge_blocks(requests);
}
void VoxelStreamRegionFiles::immerge_block(Ref<VoxelBuffer> buffer, Vector3i origin_in_voxels, int lod) {
BlockRequest r;
VoxelBlockRequest r;
r.voxel_buffer = buffer;
r.origin_in_voxels = origin_in_voxels;
r.lod = lod;
Vector<BlockRequest> requests;
Vector<VoxelBlockRequest> requests;
requests.push_back(r);
immerge_blocks(requests);
}
void VoxelStreamRegionFiles::emerge_blocks(Vector<BlockRequest> &p_blocks) {
void VoxelStreamRegionFiles::emerge_blocks(Vector<VoxelBlockRequest> &p_blocks) {
VOXEL_PROFILE_SCOPE(profile_scope);
// In order to minimize opening/closing files, requests are grouped according to their region.
// Had to copy input to sort it, as some areas in the module break if they get responses in different order
Vector<BlockRequest> sorted_blocks;
Vector<VoxelBlockRequest> sorted_blocks;
sorted_blocks.append_array(p_blocks);
SortArray<BlockRequest, BlockRequestComparator> sorter;
SortArray<VoxelBlockRequest, BlockRequestComparator> sorter;
sorter.compare.self = this;
sorter.sort(sorted_blocks.ptrw(), sorted_blocks.size());
Vector<BlockRequest> fallback_requests;
Vector<VoxelBlockRequest> fallback_requests;
for (int i = 0; i < sorted_blocks.size(); ++i) {
BlockRequest &r = sorted_blocks.write[i];
VoxelBlockRequest &r = sorted_blocks.write[i];
EmergeResult result = _emerge_block(r.voxel_buffer, r.origin_in_voxels, r.lod);
if (result == EMERGE_OK_FALLBACK) {
fallback_requests.push_back(r);
@ -77,19 +77,19 @@ void VoxelStreamRegionFiles::emerge_blocks(Vector<BlockRequest> &p_blocks) {
emerge_blocks_fallback(fallback_requests);
}
void VoxelStreamRegionFiles::immerge_blocks(Vector<BlockRequest> &p_blocks) {
void VoxelStreamRegionFiles::immerge_blocks(Vector<VoxelBlockRequest> &p_blocks) {
VOXEL_PROFILE_SCOPE(profile_scope);
// Had to copy input to sort it, as some areas in the module break if they get responses in different order
Vector<BlockRequest> sorted_blocks;
Vector<VoxelBlockRequest> sorted_blocks;
sorted_blocks.append_array(p_blocks);
SortArray<BlockRequest, BlockRequestComparator> sorter;
SortArray<VoxelBlockRequest, BlockRequestComparator> sorter;
sorter.compare.self = this;
sorter.sort(sorted_blocks.ptrw(), sorted_blocks.size());
for (int i = 0; i < sorted_blocks.size(); ++i) {
BlockRequest &r = sorted_blocks.write[i];
VoxelBlockRequest &r = sorted_blocks.write[i];
_immerge_block(r.voxel_buffer, r.origin_in_voxels, r.lod);
}
}

View File

@ -22,8 +22,8 @@ public:
void emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) override;
void immerge_block(Ref<VoxelBuffer> buffer, Vector3i origin_in_voxels, int lod) override;
void emerge_blocks(Vector<BlockRequest> &p_blocks) override;
void immerge_blocks(Vector<BlockRequest> &p_blocks) override;
void emerge_blocks(Vector<VoxelBlockRequest> &p_blocks) override;
void immerge_blocks(Vector<VoxelBlockRequest> &p_blocks) override;
String get_directory() const;
void set_directory(String dirpath);
@ -96,7 +96,7 @@ private:
VoxelStreamRegionFiles *self = nullptr;
// operator<
_FORCE_INLINE_ bool operator()(const VoxelStreamRegionFiles::BlockRequest &a, const VoxelStreamRegionFiles::BlockRequest &b) const {
_FORCE_INLINE_ bool operator()(const VoxelBlockRequest &a, const VoxelBlockRequest &b) const {
if (a.lod < b.lod) {
return true;
} else if (a.lod > b.lod) {

View File

@ -58,8 +58,8 @@ void VoxelDataLoader::process_blocks_thread_func(const ArraySlice<InputBlock> in
CRASH_COND(inputs.size() != outputs.size());
Vector<VoxelStream::BlockRequest> emerge_requests;
Vector<VoxelStream::BlockRequest> immerge_requests;
Vector<VoxelBlockRequest> emerge_requests;
Vector<VoxelBlockRequest> immerge_requests;
for (size_t i = 0; i < inputs.size(); ++i) {
@ -70,7 +70,7 @@ void VoxelDataLoader::process_blocks_thread_func(const ArraySlice<InputBlock> in
if (ib.data.voxels_to_save.is_null()) {
VoxelStream::BlockRequest r;
VoxelBlockRequest r;
r.voxel_buffer.instance();
r.voxel_buffer->create(bs, bs, bs);
r.origin_in_voxels = block_origin_in_voxels;
@ -79,7 +79,7 @@ void VoxelDataLoader::process_blocks_thread_func(const ArraySlice<InputBlock> in
} else {
VoxelStream::BlockRequest r;
VoxelBlockRequest r;
r.voxel_buffer = ib.data.voxels_to_save;
r.origin_in_voxels = block_origin_in_voxels;
r.lod = ib.lod;

View File

@ -1,9 +1,9 @@
#include "voxel_lod_terrain.h"
#include "../edition/voxel_tool_lod_terrain.h"
#include "../math/rect3i.h"
#include "../streams/voxel_stream_file.h"
#include "../util/profiling_clock.h"
#include "../voxel_string_names.h"
#include "../voxel_tool_lod_terrain.h"
#include "voxel_map.h"
#include <core/core_string_names.h>

View File

@ -1,9 +1,8 @@
#include "voxel_terrain.h"
#include "../edition/voxel_tool_terrain.h"
#include "../streams/voxel_stream_file.h"
#include "../util/profiling_clock.h"
#include "../util/utility.h"
#include "../util/voxel_raycast.h"
#include "../voxel_tool_terrain.h"
#include "voxel_block.h"
#include "voxel_map.h"

View File

@ -52,7 +52,7 @@ public:
bool is_smooth_meshing_enabled() const;
void set_smooth_meshing_enabled(bool enabled);
Ref<VoxelMap> get_storage() { return _map; }
Ref<VoxelMap> get_storage() const { return _map; }
Ref<VoxelTool> get_voxel_tool();
struct Stats {

View File

@ -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);
}

View File

@ -1,244 +0,0 @@
#ifndef HEIGHTMAP_SDF_H
#define HEIGHTMAP_SDF_H
#include "array_slice.h"
#include <core/math/vector2.h>
#include <vector>
// 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<float> 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 <typename Height_F>
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 <typename Height_F, typename Output_F>
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 <typename Output_F>
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 <typename Height_F>
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

View File

@ -1,123 +0,0 @@
#include "voxel_raycast.h"
#include <core/math/math_funcs.h>
bool voxel_raycast(
Vector3 ray_origin,
Vector3 ray_direction,
VoxelPredicate predicate,
void *predicate_context,
real_t max_distance,
Vector3i &out_hit_pos,
Vector3i &out_prev_pos) {
const float g_infinite = 9999999;
// Equation : p + v*t
// p : ray start position (ray.pos)
// v : ray orientation vector (ray.dir)
// t : parametric variable = a distance if v is normalized
// This raycasting technique is described here :
// http://www.cse.yorku.ca/~amana/research/grid.pdf
// Note : the grid is assumed to have 1-unit square cells.
ERR_FAIL_COND_V(predicate == 0, false);
ERR_FAIL_COND_V(ray_direction.is_normalized() == false, false); // Must be normalized
/* Initialisation */
// Voxel position
Vector3i hit_pos(
Math::floor(ray_origin.x),
Math::floor(ray_origin.y),
Math::floor(ray_origin.z));
Vector3i hit_prev_pos = hit_pos;
// Voxel step
const int xi_step = ray_direction.x > 0 ? 1 : ray_direction.x < 0 ? -1 : 0;
const int yi_step = ray_direction.y > 0 ? 1 : ray_direction.y < 0 ? -1 : 0;
const int zi_step = ray_direction.z > 0 ? 1 : ray_direction.z < 0 ? -1 : 0;
// Parametric voxel step
const real_t tdelta_x = xi_step != 0 ? 1.f / Math::abs(ray_direction.x) : g_infinite;
const real_t tdelta_y = yi_step != 0 ? 1.f / Math::abs(ray_direction.y) : g_infinite;
const real_t tdelta_z = zi_step != 0 ? 1.f / Math::abs(ray_direction.z) : g_infinite;
// Parametric grid-cross
real_t tcross_x; // At which value of T we will cross a vertical line?
real_t tcross_y; // At which value of T we will cross a horizontal line?
real_t tcross_z; // At which value of T we will cross a depth line?
// X initialization
if (xi_step != 0) {
if (xi_step == 1)
tcross_x = (Math::ceil(ray_origin.x) - ray_origin.x) * tdelta_x;
else
tcross_x = (ray_origin.x - Math::floor(ray_origin.x)) * tdelta_x;
} else
tcross_x = g_infinite; // Will never cross on X
// Y initialization
if (yi_step != 0) {
if (yi_step == 1)
tcross_y = (Math::ceil(ray_origin.y) - ray_origin.y) * tdelta_y;
else
tcross_y = (ray_origin.y - Math::floor(ray_origin.y)) * tdelta_y;
} else
tcross_y = g_infinite; // Will never cross on X
// Z initialization
if (zi_step != 0) {
if (zi_step == 1)
tcross_z = (Math::ceil(ray_origin.z) - ray_origin.z) * tdelta_z;
else
tcross_z = (ray_origin.z - Math::floor(ray_origin.z)) * tdelta_z;
} else
tcross_z = g_infinite; // Will never cross on X
/* Iteration */
do {
hit_prev_pos = hit_pos;
if (tcross_x < tcross_y) {
if (tcross_x < tcross_z) {
// X collision
//hit.prevPos.x = hit.pos.x;
hit_pos.x += xi_step;
if (tcross_x > max_distance)
return false;
tcross_x += tdelta_x;
} else {
// Z collision (duplicate code)
//hit.prevPos.z = hit.pos.z;
hit_pos.z += zi_step;
if (tcross_z > max_distance)
return false;
tcross_z += tdelta_z;
}
} else {
if (tcross_y < tcross_z) {
// Y collision
//hit.prevPos.y = hit.pos.y;
hit_pos.y += yi_step;
if (tcross_y > max_distance)
return false;
tcross_y += tdelta_y;
} else {
// Z collision (duplicate code)
//hit.prevPos.z = hit.pos.z;
hit_pos.z += zi_step;
if (tcross_z > max_distance)
return false;
tcross_z += tdelta_z;
}
}
} while (!predicate(hit_pos, predicate_context));
out_hit_pos = hit_pos;
out_prev_pos = hit_prev_pos;
return true;
}

View File

@ -1,16 +1,122 @@
#include "../math/vector3i.h"
#include <core/math/vector3.h>
// TODO that could be a template function
// pos: voxel position
// context: arguments to carry (as a lamdbda capture)
typedef bool (*VoxelPredicate)(Vector3i pos, void *context);
template <typename Predicate_F> // f(Vector3i position) -> bool
bool voxel_raycast(
Vector3 ray_origin,
Vector3 ray_direction,
VoxelPredicate predicate,
void *predicate_context, // Handle that one with care
Predicate_F predicate,
real_t max_distance,
Vector3i &out_hit_pos,
Vector3i &out_prev_pos);
Vector3i &out_prev_pos) {
const float g_infinite = 9999999;
// Equation : p + v*t
// p : ray start position (ray.pos)
// v : ray orientation vector (ray.dir)
// t : parametric variable = a distance if v is normalized
// This raycasting technique is described here :
// http://www.cse.yorku.ca/~amana/research/grid.pdf
// Note : the grid is assumed to have 1-unit square cells.
ERR_FAIL_COND_V(ray_direction.is_normalized() == false, false); // Must be normalized
/* Initialisation */
// Voxel position
Vector3i hit_pos(
Math::floor(ray_origin.x),
Math::floor(ray_origin.y),
Math::floor(ray_origin.z));
Vector3i hit_prev_pos = hit_pos;
// Voxel step
const int xi_step = ray_direction.x > 0 ? 1 : ray_direction.x < 0 ? -1 : 0;
const int yi_step = ray_direction.y > 0 ? 1 : ray_direction.y < 0 ? -1 : 0;
const int zi_step = ray_direction.z > 0 ? 1 : ray_direction.z < 0 ? -1 : 0;
// Parametric voxel step
const real_t tdelta_x = xi_step != 0 ? 1.f / Math::abs(ray_direction.x) : g_infinite;
const real_t tdelta_y = yi_step != 0 ? 1.f / Math::abs(ray_direction.y) : g_infinite;
const real_t tdelta_z = zi_step != 0 ? 1.f / Math::abs(ray_direction.z) : g_infinite;
// Parametric grid-cross
real_t tcross_x; // At which value of T we will cross a vertical line?
real_t tcross_y; // At which value of T we will cross a horizontal line?
real_t tcross_z; // At which value of T we will cross a depth line?
// X initialization
if (xi_step != 0) {
if (xi_step == 1)
tcross_x = (Math::ceil(ray_origin.x) - ray_origin.x) * tdelta_x;
else
tcross_x = (ray_origin.x - Math::floor(ray_origin.x)) * tdelta_x;
} else
tcross_x = g_infinite; // Will never cross on X
// Y initialization
if (yi_step != 0) {
if (yi_step == 1)
tcross_y = (Math::ceil(ray_origin.y) - ray_origin.y) * tdelta_y;
else
tcross_y = (ray_origin.y - Math::floor(ray_origin.y)) * tdelta_y;
} else
tcross_y = g_infinite; // Will never cross on X
// Z initialization
if (zi_step != 0) {
if (zi_step == 1)
tcross_z = (Math::ceil(ray_origin.z) - ray_origin.z) * tdelta_z;
else
tcross_z = (ray_origin.z - Math::floor(ray_origin.z)) * tdelta_z;
} else
tcross_z = g_infinite; // Will never cross on X
/* Iteration */
do {
hit_prev_pos = hit_pos;
if (tcross_x < tcross_y) {
if (tcross_x < tcross_z) {
// X collision
//hit.prevPos.x = hit.pos.x;
hit_pos.x += xi_step;
if (tcross_x > max_distance)
return false;
tcross_x += tdelta_x;
} else {
// Z collision (duplicate code)
//hit.prevPos.z = hit.pos.z;
hit_pos.z += zi_step;
if (tcross_z > max_distance)
return false;
tcross_z += tdelta_z;
}
} else {
if (tcross_y < tcross_z) {
// Y collision
//hit.prevPos.y = hit.pos.y;
hit_pos.y += yi_step;
if (tcross_y > max_distance)
return false;
tcross_y += tdelta_y;
} else {
// Z collision (duplicate code)
//hit.prevPos.z = hit.pos.z;
hit_pos.z += zi_step;
if (tcross_z > max_distance)
return false;
tcross_z += tdelta_z;
}
}
} while (!predicate(hit_pos));
out_hit_pos = hit_pos;
out_prev_pos = hit_prev_pos;
return true;
}

View File

@ -4,8 +4,8 @@
#include "voxel_memory_pool.h"
#endif
#include "edition/voxel_tool_buffer.h"
#include "voxel_buffer.h"
#include "voxel_tool_buffer.h"
#include <core/io/marshalls.h>
#include <core/math/math_funcs.h>

View File

@ -17,6 +17,7 @@ VoxelStringNames::VoxelStringNames() {
emerge_block = StaticCString::create("emerge_block");
immerge_block = StaticCString::create("immerge_block");
generate_block = StaticCString::create("generate_block");
u_transition_mask = StaticCString::create("u_transition_mask");
}

View File

@ -19,6 +19,7 @@ public:
StringName emerge_block;
StringName immerge_block;
StringName generate_block;
StringName u_transition_mask;
};