2016-05-10 01:59:54 +02:00
|
|
|
#include "voxel_terrain.h"
|
2019-05-25 16:16:03 +01:00
|
|
|
#include "../streams/voxel_stream_test.h"
|
2019-06-25 20:51:35 +01:00
|
|
|
#include "../util/profiling_clock.h"
|
2019-04-28 17:58:29 +01:00
|
|
|
#include "../util/utility.h"
|
|
|
|
#include "../util/voxel_raycast.h"
|
2018-09-25 00:54:07 +01:00
|
|
|
#include "voxel_block.h"
|
2019-04-24 01:29:47 +01:00
|
|
|
#include "voxel_map.h"
|
2018-09-25 00:54:07 +01:00
|
|
|
|
2019-04-24 01:29:47 +01:00
|
|
|
#include <core/engine.h>
|
2018-09-19 20:25:04 +01:00
|
|
|
#include <core/os/os.h>
|
2017-08-13 01:19:39 +02:00
|
|
|
#include <scene/3d/mesh_instance.h>
|
2017-08-20 15:17:54 +02:00
|
|
|
|
2019-04-28 01:32:23 +01:00
|
|
|
VoxelTerrain::VoxelTerrain() {
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2017-01-01 04:40:16 +01:00
|
|
|
_map = Ref<VoxelMap>(memnew(VoxelMap));
|
2017-08-15 02:24:52 +02:00
|
|
|
|
|
|
|
_view_distance_blocks = 8;
|
2017-08-28 01:47:38 +02:00
|
|
|
_last_view_distance_blocks = 0;
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2019-05-25 16:07:38 +01:00
|
|
|
_stream_thread = NULL;
|
2018-09-25 00:54:07 +01:00
|
|
|
_block_updater = NULL;
|
2018-09-28 01:10:35 +01:00
|
|
|
|
|
|
|
_generate_collisions = false;
|
|
|
|
_run_in_editor = false;
|
2019-04-28 01:32:23 +01:00
|
|
|
_smooth_meshing_enabled = false;
|
2018-09-25 00:54:07 +01:00
|
|
|
}
|
2017-03-28 00:56:01 +02:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
VoxelTerrain::~VoxelTerrain() {
|
2018-09-29 18:59:19 +01:00
|
|
|
print_line("Destroying VoxelTerrain");
|
2019-05-28 00:40:09 +01:00
|
|
|
|
2019-05-25 16:07:38 +01:00
|
|
|
if (_stream_thread) {
|
2019-06-02 02:30:22 +01:00
|
|
|
// Schedule saving of all modified blocks,
|
|
|
|
// without copy because we are destroying the map anyways
|
|
|
|
save_all_modified_blocks(false);
|
|
|
|
|
2019-05-25 16:07:38 +01:00
|
|
|
memdelete(_stream_thread);
|
2017-01-01 04:40:16 +01:00
|
|
|
}
|
2019-06-02 02:30:22 +01:00
|
|
|
|
2019-04-24 01:29:47 +01:00
|
|
|
if (_block_updater) {
|
2018-09-25 00:54:07 +01:00
|
|
|
memdelete(_block_updater);
|
|
|
|
}
|
|
|
|
}
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2017-08-15 02:24:52 +02:00
|
|
|
// TODO See if there is a way to specify materials in voxels directly?
|
|
|
|
|
|
|
|
bool VoxelTerrain::_set(const StringName &p_name, const Variant &p_value) {
|
|
|
|
|
|
|
|
if (p_name.operator String().begins_with("material/")) {
|
2019-06-18 14:24:56 +09:00
|
|
|
unsigned int idx = p_name.operator String().get_slicec('/', 1).to_int();
|
2019-04-28 17:58:29 +01:00
|
|
|
ERR_FAIL_COND_V(idx >= VoxelMesherBlocky::MAX_MATERIALS || idx < 0, false);
|
2017-08-15 02:24:52 +02:00
|
|
|
set_material(idx, p_value);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool VoxelTerrain::_get(const StringName &p_name, Variant &r_ret) const {
|
|
|
|
|
|
|
|
if (p_name.operator String().begins_with("material/")) {
|
2019-06-18 14:24:56 +09:00
|
|
|
unsigned int idx = p_name.operator String().get_slicec('/', 1).to_int();
|
2019-04-28 17:58:29 +01:00
|
|
|
ERR_FAIL_COND_V(idx >= VoxelMesherBlocky::MAX_MATERIALS || idx < 0, false);
|
2017-08-15 02:24:52 +02:00
|
|
|
r_ret = get_material(idx);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelTerrain::_get_property_list(List<PropertyInfo> *p_list) const {
|
|
|
|
|
2019-06-18 14:24:56 +09:00
|
|
|
for (unsigned int i = 0; i < VoxelMesherBlocky::MAX_MATERIALS; ++i) {
|
2017-08-15 02:24:52 +02:00
|
|
|
p_list->push_back(PropertyInfo(Variant::OBJECT, "material/" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,SpatialMaterial"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-28 01:10:50 +01:00
|
|
|
void VoxelTerrain::set_stream(Ref<VoxelStream> stream) {
|
|
|
|
if (stream != _stream) {
|
2018-09-25 00:54:07 +01:00
|
|
|
|
2019-05-25 16:07:38 +01:00
|
|
|
if (_stream_thread) {
|
|
|
|
memdelete(_stream_thread);
|
|
|
|
_stream_thread = NULL;
|
2018-09-25 00:54:07 +01:00
|
|
|
}
|
|
|
|
|
2019-05-28 01:10:50 +01:00
|
|
|
_stream = stream;
|
2019-05-25 16:07:38 +01:00
|
|
|
_stream_thread = memnew(VoxelDataLoader(1, _stream, _map->get_block_size_pow2()));
|
2018-09-25 00:54:07 +01:00
|
|
|
|
2017-08-28 01:47:38 +02:00
|
|
|
// The whole map might change, so make all area dirty
|
2018-09-25 00:54:07 +01:00
|
|
|
// TODO Actually, we should regenerate the whole map, not just update all its blocks
|
2017-08-28 01:47:38 +02:00
|
|
|
make_all_view_dirty_deferred();
|
2017-08-15 02:24:52 +02:00
|
|
|
}
|
2017-01-02 02:15:57 +01:00
|
|
|
}
|
|
|
|
|
2019-05-25 16:07:38 +01:00
|
|
|
Ref<VoxelStream> VoxelTerrain::get_stream() const {
|
|
|
|
return _stream;
|
2017-01-02 02:15:57 +01:00
|
|
|
}
|
|
|
|
|
2017-08-15 02:24:52 +02:00
|
|
|
Ref<VoxelLibrary> VoxelTerrain::get_voxel_library() const {
|
2018-09-25 00:54:07 +01:00
|
|
|
return _library;
|
2017-03-26 20:07:01 +02:00
|
|
|
}
|
|
|
|
|
2017-08-15 02:24:52 +02:00
|
|
|
void VoxelTerrain::set_voxel_library(Ref<VoxelLibrary> library) {
|
2018-09-25 00:54:07 +01:00
|
|
|
|
|
|
|
if (library != _library) {
|
2017-08-15 02:24:52 +02:00
|
|
|
|
|
|
|
#ifdef TOOLS_ENABLED
|
2018-09-25 00:54:07 +01:00
|
|
|
if (library->get_voxel_count() == 0) {
|
2017-08-15 02:24:52 +02:00
|
|
|
library->load_default();
|
|
|
|
}
|
|
|
|
#endif
|
2018-09-25 00:54:07 +01:00
|
|
|
_library = library;
|
|
|
|
|
2019-04-28 01:32:23 +01:00
|
|
|
reset_updater();
|
2017-08-28 01:47:38 +02:00
|
|
|
|
|
|
|
// Voxel appearance might completely change
|
|
|
|
make_all_view_dirty_deferred();
|
2017-08-15 02:24:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-26 18:09:37 +02:00
|
|
|
void VoxelTerrain::set_generate_collisions(bool enabled) {
|
|
|
|
_generate_collisions = enabled;
|
|
|
|
}
|
|
|
|
|
2017-08-15 02:24:52 +02:00
|
|
|
int VoxelTerrain::get_view_distance() const {
|
2017-08-20 15:17:54 +02:00
|
|
|
return _view_distance_blocks * _map->get_block_size();
|
2017-08-15 02:24:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelTerrain::set_view_distance(int distance_in_voxels) {
|
|
|
|
ERR_FAIL_COND(distance_in_voxels < 0)
|
2017-08-20 15:17:54 +02:00
|
|
|
int d = distance_in_voxels / _map->get_block_size();
|
2019-04-24 01:29:47 +01:00
|
|
|
if (d != _view_distance_blocks) {
|
2017-08-28 01:47:38 +02:00
|
|
|
print_line(String("View distance changed from ") + String::num(_view_distance_blocks) + String(" blocks to ") + String::num(d));
|
2017-08-15 02:24:52 +02:00
|
|
|
_view_distance_blocks = d;
|
2017-08-28 01:47:38 +02:00
|
|
|
// Blocks too far away will be removed in _process, same for blocks to load
|
2017-08-15 02:24:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-28 00:56:01 +02:00
|
|
|
void VoxelTerrain::set_viewer_path(NodePath path) {
|
|
|
|
_viewer_path = path;
|
|
|
|
}
|
|
|
|
|
2017-08-15 02:24:52 +02:00
|
|
|
NodePath VoxelTerrain::get_viewer_path() const {
|
2017-03-28 00:56:01 +02:00
|
|
|
return _viewer_path;
|
|
|
|
}
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2017-08-15 02:24:52 +02:00
|
|
|
Spatial *VoxelTerrain::get_viewer(NodePath path) const {
|
2019-06-02 01:59:39 +01:00
|
|
|
if (path.is_empty()) {
|
2017-03-28 00:56:01 +02:00
|
|
|
return NULL;
|
2019-06-02 01:59:39 +01:00
|
|
|
}
|
|
|
|
if (!is_inside_tree()) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2017-08-13 01:19:39 +02:00
|
|
|
Node *node = get_node(path);
|
2019-06-02 01:59:39 +01:00
|
|
|
if (node == NULL) {
|
2017-03-28 00:56:01 +02:00
|
|
|
return NULL;
|
2019-06-02 01:59:39 +01:00
|
|
|
}
|
2017-08-27 15:52:14 +02:00
|
|
|
return Object::cast_to<Spatial>(node);
|
2017-03-28 00:56:01 +02:00
|
|
|
}
|
|
|
|
|
2019-06-18 14:24:56 +09:00
|
|
|
void VoxelTerrain::set_material(unsigned int id, Ref<Material> material) {
|
2017-08-15 02:24:52 +02:00
|
|
|
// TODO Update existing block surfaces
|
2019-04-28 17:58:29 +01:00
|
|
|
ERR_FAIL_COND(id < 0 || id >= VoxelMesherBlocky::MAX_MATERIALS);
|
2018-09-25 00:54:07 +01:00
|
|
|
_materials[id] = material;
|
2017-08-15 02:24:52 +02:00
|
|
|
}
|
|
|
|
|
2019-06-18 14:24:56 +09:00
|
|
|
Ref<Material> VoxelTerrain::get_material(unsigned int id) const {
|
2019-04-28 17:58:29 +01:00
|
|
|
ERR_FAIL_COND_V(id < 0 || id >= VoxelMesherBlocky::MAX_MATERIALS, Ref<Material>());
|
2018-09-25 00:54:07 +01:00
|
|
|
return _materials[id];
|
2017-08-15 02:24:52 +02:00
|
|
|
}
|
|
|
|
|
2019-04-28 01:32:23 +01:00
|
|
|
bool VoxelTerrain::is_smooth_meshing_enabled() const {
|
|
|
|
return _smooth_meshing_enabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelTerrain::set_smooth_meshing_enabled(bool enabled) {
|
|
|
|
if (_smooth_meshing_enabled != enabled) {
|
|
|
|
_smooth_meshing_enabled = enabled;
|
|
|
|
reset_updater();
|
|
|
|
make_all_view_dirty_deferred();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-28 00:56:01 +02:00
|
|
|
void VoxelTerrain::make_block_dirty(Vector3i bpos) {
|
2018-09-25 00:54:07 +01:00
|
|
|
// TODO Immediate update viewer distance?
|
|
|
|
|
2018-09-26 01:22:23 +01:00
|
|
|
VoxelTerrain::BlockDirtyState *state = _dirty_blocks.getptr(bpos);
|
|
|
|
|
2019-04-24 01:29:47 +01:00
|
|
|
if (state == NULL) {
|
2018-09-26 01:22:23 +01:00
|
|
|
// The block is not dirty, so it will either be loaded or updated
|
2018-09-25 00:54:07 +01:00
|
|
|
|
2019-05-28 00:40:09 +01:00
|
|
|
VoxelBlock *block = _map->get_block(bpos);
|
|
|
|
|
|
|
|
if (block != nullptr) {
|
2018-09-25 00:54:07 +01:00
|
|
|
|
|
|
|
_blocks_pending_update.push_back(bpos);
|
2018-09-26 01:22:23 +01:00
|
|
|
_dirty_blocks[bpos] = BLOCK_UPDATE_NOT_SENT;
|
2019-06-02 01:59:39 +01:00
|
|
|
|
|
|
|
if (!block->modified) {
|
|
|
|
print_line(String("Marking block {0} as modified").format(varray(bpos.to_vec3())));
|
|
|
|
block->modified = true;
|
|
|
|
}
|
2018-09-25 00:54:07 +01:00
|
|
|
|
|
|
|
} else {
|
2019-04-29 21:57:39 +01:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
_blocks_pending_load.push_back(bpos);
|
|
|
|
_dirty_blocks[bpos] = BLOCK_LOAD;
|
|
|
|
}
|
|
|
|
|
2019-04-24 01:29:47 +01:00
|
|
|
} else if (*state == BLOCK_UPDATE_SENT) {
|
2018-09-26 01:22:23 +01:00
|
|
|
// The updater is already processing the block,
|
|
|
|
// but the block was modified again so we schedule another update
|
|
|
|
*state = BLOCK_UPDATE_NOT_SENT;
|
|
|
|
_blocks_pending_update.push_back(bpos);
|
2017-03-28 00:56:01 +02:00
|
|
|
}
|
2018-09-26 01:22:23 +01:00
|
|
|
|
|
|
|
//OS::get_singleton()->print("Dirty (%i, %i, %i)", bpos.x, bpos.y, bpos.z);
|
|
|
|
|
|
|
|
// TODO What if a block is made dirty, goes through threaded update, then gets changed again before it gets updated?
|
|
|
|
// this will make the second change ignored, which is not correct!
|
2017-03-28 00:56:01 +02:00
|
|
|
}
|
|
|
|
|
2019-05-28 00:40:09 +01:00
|
|
|
namespace {
|
|
|
|
struct ScheduleSaveAction {
|
|
|
|
|
|
|
|
std::vector<VoxelDataLoader::InputBlock> &blocks_to_save;
|
|
|
|
bool with_copy;
|
|
|
|
|
|
|
|
void operator()(VoxelBlock *block) {
|
|
|
|
if (block->modified) {
|
2019-08-16 20:56:07 +01:00
|
|
|
//print_line(String("Scheduling save for block {0}").format(varray(block->position.to_vec3())));
|
2019-05-28 00:40:09 +01:00
|
|
|
VoxelDataLoader::InputBlock b;
|
|
|
|
b.data.voxels_to_save = with_copy ? block->voxels->duplicate() : block->voxels;
|
|
|
|
b.position = block->position;
|
2019-06-02 01:52:46 +01:00
|
|
|
b.can_be_discarded = false;
|
2019-05-28 00:40:09 +01:00
|
|
|
blocks_to_save.push_back(b);
|
|
|
|
block->modified = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
2017-08-28 01:47:38 +02:00
|
|
|
void VoxelTerrain::immerge_block(Vector3i bpos) {
|
|
|
|
|
|
|
|
ERR_FAIL_COND(_map.is_null());
|
|
|
|
|
2019-05-28 00:40:09 +01:00
|
|
|
// Note: no need to copy the block because it gets removed from the map anyways
|
|
|
|
_map->remove_block(bpos, ScheduleSaveAction{ _blocks_to_save, false });
|
2017-08-28 01:47:38 +02:00
|
|
|
|
|
|
|
_dirty_blocks.erase(bpos);
|
|
|
|
// Blocks in the update queue will be cancelled in _process,
|
|
|
|
// because it's too expensive to linear-search all blocks for each block
|
|
|
|
}
|
|
|
|
|
2019-05-28 00:40:09 +01:00
|
|
|
void VoxelTerrain::save_all_modified_blocks(bool with_copy) {
|
|
|
|
|
2019-06-02 02:30:22 +01:00
|
|
|
ERR_FAIL_COND(_stream_thread == nullptr);
|
|
|
|
|
2019-05-28 00:40:09 +01:00
|
|
|
// That may cause a stutter, so should be used when the player won't notice
|
|
|
|
_map->for_all_blocks(ScheduleSaveAction{ _blocks_to_save, with_copy });
|
2019-06-02 01:59:39 +01:00
|
|
|
|
|
|
|
// And flush immediately
|
|
|
|
send_block_data_requests();
|
2019-05-28 00:40:09 +01:00
|
|
|
}
|
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
Dictionary VoxelTerrain::get_statistics() const {
|
|
|
|
|
2019-05-28 01:10:50 +01:00
|
|
|
Dictionary stream = VoxelDataLoader::Mgr::to_dictionary(_stats.stream);
|
|
|
|
stream["dropped_blocks"] = _stats.dropped_stream_blocks;
|
2018-09-25 00:54:07 +01:00
|
|
|
|
2019-05-19 18:27:49 +01:00
|
|
|
Dictionary updater = VoxelMeshUpdater::Mgr::to_dictionary(_stats.updater);
|
2018-09-25 00:54:07 +01:00
|
|
|
updater["updated_blocks"] = _stats.updated_blocks;
|
|
|
|
updater["mesh_alloc_time"] = _stats.mesh_alloc_time;
|
2018-09-26 01:22:23 +01:00
|
|
|
updater["dropped_blocks"] = _stats.dropped_updater_blocks;
|
|
|
|
updater["remaining_main_thread_blocks"] = _stats.remaining_main_thread_blocks;
|
2018-09-25 00:54:07 +01:00
|
|
|
|
|
|
|
Dictionary d;
|
2019-05-28 01:10:50 +01:00
|
|
|
d["stream"] = stream;
|
2018-09-25 00:54:07 +01:00
|
|
|
d["updater"] = updater;
|
|
|
|
|
2018-09-29 18:59:19 +01:00
|
|
|
// Breakdown of time spent in _process
|
|
|
|
d["time_detect_required_blocks"] = _stats.time_detect_required_blocks;
|
|
|
|
d["time_send_load_requests"] = _stats.time_send_load_requests;
|
|
|
|
d["time_process_load_responses"] = _stats.time_process_load_responses;
|
|
|
|
d["time_send_update_requests"] = _stats.time_send_update_requests;
|
|
|
|
d["time_process_update_responses"] = _stats.time_process_update_responses;
|
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
return d;
|
2017-03-29 22:36:42 +02:00
|
|
|
}
|
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
bool VoxelTerrain::is_block_dirty(Vector3i bpos) const {
|
|
|
|
return _dirty_blocks.has(bpos);
|
2016-05-10 01:59:54 +02:00
|
|
|
}
|
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
//void VoxelTerrain::make_blocks_dirty(Vector3i min, Vector3i size) {
|
|
|
|
// Vector3i max = min + size;
|
|
|
|
// Vector3i pos;
|
|
|
|
// for (pos.z = min.z; pos.z < max.z; ++pos.z) {
|
|
|
|
// for (pos.y = min.y; pos.y < max.y; ++pos.y) {
|
|
|
|
// for (pos.x = min.x; pos.x < max.x; ++pos.x) {
|
|
|
|
// make_block_dirty(pos);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
|
2017-08-28 01:47:38 +02:00
|
|
|
void VoxelTerrain::make_all_view_dirty_deferred() {
|
|
|
|
// This trick will regenerate all chunks in view, according to the view distance found during block updates.
|
|
|
|
// The point of doing this instead of immediately scheduling updates is that it will
|
|
|
|
// always use an up-to-date view distance, which is not necessarily loaded yet on initialization.
|
|
|
|
_last_view_distance_blocks = 0;
|
|
|
|
|
2019-04-24 01:29:47 +01:00
|
|
|
// Vector3i radius(_view_distance_blocks, _view_distance_blocks, _view_distance_blocks);
|
|
|
|
// make_blocks_dirty(-radius, 2*radius);
|
2017-08-15 02:24:52 +02:00
|
|
|
}
|
|
|
|
|
2019-04-28 01:32:23 +01:00
|
|
|
void VoxelTerrain::reset_updater() {
|
|
|
|
|
|
|
|
if (_block_updater) {
|
|
|
|
memdelete(_block_updater);
|
|
|
|
_block_updater = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO Thread-safe way to change those parameters
|
|
|
|
VoxelMeshUpdater::MeshingParams params;
|
|
|
|
params.smooth_surface = _smooth_meshing_enabled;
|
2019-05-19 18:27:49 +01:00
|
|
|
params.library = _library;
|
2019-04-28 01:32:23 +01:00
|
|
|
|
2019-05-19 18:27:49 +01:00
|
|
|
_block_updater = memnew(VoxelMeshUpdater(1, params));
|
2019-05-04 00:00:50 +01:00
|
|
|
|
|
|
|
// TODO Revert any pending update states!
|
2019-04-28 01:32:23 +01:00
|
|
|
}
|
|
|
|
|
2017-03-31 01:45:06 +02:00
|
|
|
inline int get_border_index(int x, int max) {
|
|
|
|
return x == 0 ? 0 : x != max ? 1 : 2;
|
|
|
|
}
|
|
|
|
|
2017-03-29 22:36:42 +02:00
|
|
|
void VoxelTerrain::make_voxel_dirty(Vector3i pos) {
|
|
|
|
|
|
|
|
// Update the block in which the voxel is
|
2017-08-20 15:17:54 +02:00
|
|
|
Vector3i bpos = _map->voxel_to_block(pos);
|
2017-03-29 22:36:42 +02:00
|
|
|
make_block_dirty(bpos);
|
2017-03-31 01:45:06 +02:00
|
|
|
//OS::get_singleton()->print("Dirty (%i, %i, %i)\n", bpos.x, bpos.y, bpos.z);
|
2017-03-29 22:36:42 +02:00
|
|
|
|
|
|
|
// Update neighbor blocks if the voxel is touching a boundary
|
|
|
|
|
2017-08-20 15:17:54 +02:00
|
|
|
Vector3i rpos = _map->to_local(pos);
|
2017-03-29 22:36:42 +02:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
// TODO Thread-safe way of getting this parameter
|
2019-04-24 01:29:47 +01:00
|
|
|
bool check_corners = true; //_mesher->get_occlusion_enabled();
|
2017-03-31 01:45:06 +02:00
|
|
|
|
2017-08-20 15:17:54 +02:00
|
|
|
const int max = _map->get_block_size() - 1;
|
2017-03-31 01:45:06 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
if (rpos.x == 0)
|
|
|
|
make_block_dirty(bpos - Vector3i(1, 0, 0));
|
|
|
|
else if (rpos.x == max)
|
|
|
|
make_block_dirty(bpos + Vector3i(1, 0, 0));
|
2017-03-31 01:45:06 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
if (rpos.y == 0)
|
|
|
|
make_block_dirty(bpos - Vector3i(0, 1, 0));
|
|
|
|
else if (rpos.y == max)
|
|
|
|
make_block_dirty(bpos + Vector3i(0, 1, 0));
|
2017-03-31 01:45:06 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
if (rpos.z == 0)
|
|
|
|
make_block_dirty(bpos - Vector3i(0, 0, 1));
|
|
|
|
else if (rpos.z == max)
|
|
|
|
make_block_dirty(bpos + Vector3i(0, 0, 1));
|
2017-03-31 01:45:06 +02:00
|
|
|
|
|
|
|
// We might want to update blocks in corners in order to update ambient occlusion
|
2017-08-13 01:19:39 +02:00
|
|
|
if (check_corners) {
|
2017-03-31 01:45:06 +02:00
|
|
|
|
|
|
|
// 24------25------26
|
|
|
|
// /| /|
|
|
|
|
// / | / |
|
|
|
|
// 21 | 23 |
|
|
|
|
// / 15 / 17
|
|
|
|
// / | / |
|
|
|
|
// 18------19------20 |
|
|
|
|
// | | | |
|
|
|
|
// | 6-------7-|-----8
|
|
|
|
// | / | /
|
|
|
|
// 9 / 11 /
|
|
|
|
// | 3 | 5
|
|
|
|
// | / | / y z
|
|
|
|
// |/ |/ |/
|
|
|
|
// 0-------1-------2 o--x
|
|
|
|
|
|
|
|
// I'm not good at writing piles of ifs
|
|
|
|
|
|
|
|
static const int normals[27][3] = {
|
2017-08-13 01:19:39 +02:00
|
|
|
{ -1, -1, -1 }, { 0, -1, -1 }, { 1, -1, -1 },
|
|
|
|
{ -1, -1, 0 }, { 0, -1, 0 }, { 1, -1, 0 },
|
|
|
|
{ -1, -1, 1 }, { 0, -1, 1 }, { 1, -1, 1 },
|
2017-03-31 01:45:06 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
{ -1, 0, -1 }, { 0, 0, -1 }, { 1, 0, -1 },
|
|
|
|
{ -1, 0, 0 }, { 0, 0, 0 }, { 1, 0, 0 },
|
|
|
|
{ -1, 0, 1 }, { 0, 0, 1 }, { 1, 0, 1 },
|
2017-03-31 01:45:06 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
{ -1, 1, -1 }, { 0, 1, -1 }, { 1, 1, -1 },
|
|
|
|
{ -1, 1, 0 }, { 0, 1, 0 }, { 1, 1, 0 },
|
|
|
|
{ -1, 1, 1 }, { 0, 1, 1 }, { 1, 1, 1 }
|
2017-03-31 01:45:06 +02:00
|
|
|
};
|
|
|
|
static const int ce_counts[27] = {
|
|
|
|
4, 1, 4,
|
|
|
|
1, 0, 1,
|
|
|
|
4, 1, 4,
|
|
|
|
|
|
|
|
1, 0, 1,
|
|
|
|
0, 0, 0,
|
|
|
|
1, 0, 1,
|
|
|
|
|
|
|
|
4, 1, 4,
|
|
|
|
1, 0, 1,
|
|
|
|
4, 1, 4
|
|
|
|
};
|
|
|
|
static const int ce_indexes_lut[27][4] = {
|
2017-08-13 01:19:39 +02:00
|
|
|
{ 0, 1, 3, 9 }, { 1 }, { 2, 1, 5, 11 },
|
|
|
|
{ 3 }, {}, { 5 },
|
|
|
|
{ 6, 3, 7, 15 }, { 7 }, { 8, 7, 5, 17 },
|
2017-03-31 01:45:06 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
{ 9 }, {}, { 11 },
|
2017-03-31 01:45:06 +02:00
|
|
|
{}, {}, {},
|
2017-08-13 01:19:39 +02:00
|
|
|
{ 15 }, {}, { 17 },
|
2017-03-31 01:45:06 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
{ 18, 9, 19, 21 }, { 19 }, { 20, 11, 19, 23 },
|
|
|
|
{ 21 }, {}, { 23 },
|
|
|
|
{ 24, 15, 21, 25 }, { 25 }, { 26, 17, 23, 25 }
|
2017-03-31 01:45:06 +02:00
|
|
|
};
|
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
int m = get_border_index(rpos.x, max) + 3 * get_border_index(rpos.z, max) + 9 * get_border_index(rpos.y, max);
|
2017-03-31 01:45:06 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
const int *ce_indexes = ce_indexes_lut[m];
|
2017-03-31 01:45:06 +02:00
|
|
|
int ce_count = ce_counts[m];
|
|
|
|
//OS::get_singleton()->print("m=%i, rpos=(%i, %i, %i)\n", m, rpos.x, rpos.y, rpos.z);
|
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
for (int i = 0; i < ce_count; ++i) {
|
2017-03-31 01:45:06 +02:00
|
|
|
// TODO Because it's about ambient occlusion across 1 voxel only,
|
|
|
|
// we could optimize it even more by looking at neighbor voxels,
|
|
|
|
// and discard the update if we know it won't change anything
|
2017-08-13 01:19:39 +02:00
|
|
|
const int *normal = normals[ce_indexes[i]];
|
2017-03-31 01:45:06 +02:00
|
|
|
Vector3i nbpos(bpos.x + normal[0], bpos.y + normal[1], bpos.z + normal[2]);
|
|
|
|
//OS::get_singleton()->print("Corner dirty (%i, %i, %i)\n", nbpos.x, nbpos.y, nbpos.z);
|
|
|
|
make_block_dirty(nbpos);
|
|
|
|
}
|
|
|
|
}
|
2017-03-29 22:36:42 +02:00
|
|
|
}
|
|
|
|
|
2018-10-01 20:48:47 +01:00
|
|
|
void VoxelTerrain::make_area_dirty(Rect3i box) {
|
|
|
|
|
|
|
|
Vector3i min_pos = box.pos;
|
|
|
|
Vector3i max_pos = box.pos + box.size - Vector3(1, 1, 1);
|
|
|
|
|
|
|
|
// TODO Thread-safe way of getting this parameter
|
2019-04-24 01:29:47 +01:00
|
|
|
bool check_corners = true; //_mesher->get_occlusion_enabled();
|
2018-10-01 20:48:47 +01:00
|
|
|
if (check_corners) {
|
|
|
|
|
|
|
|
min_pos -= Vector3i(1, 1, 1);
|
|
|
|
max_pos += Vector3i(1, 1, 1);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
Vector3i min_rpos = _map->to_local(min_pos);
|
|
|
|
if (min_rpos.x == 0)
|
|
|
|
--min_pos.x;
|
|
|
|
if (min_rpos.y == 0)
|
|
|
|
--min_pos.y;
|
|
|
|
if (min_rpos.z == 0)
|
|
|
|
--min_pos.z;
|
|
|
|
|
|
|
|
const int max = _map->get_block_size() - 1;
|
|
|
|
Vector3i max_rpos = _map->to_local(max_pos);
|
|
|
|
if (max_rpos.x == max)
|
|
|
|
++max_pos.x;
|
|
|
|
if (max_rpos.y == max)
|
|
|
|
++max_pos.y;
|
|
|
|
if (max_rpos.z == max)
|
|
|
|
++max_pos.z;
|
|
|
|
}
|
|
|
|
|
|
|
|
Vector3i min_block_pos = _map->voxel_to_block(min_pos);
|
|
|
|
Vector3i max_block_pos = _map->voxel_to_block(max_pos);
|
|
|
|
|
|
|
|
Vector3i bpos;
|
|
|
|
for (bpos.z = min_block_pos.z; bpos.z <= max_block_pos.z; ++bpos.z) {
|
|
|
|
for (bpos.x = min_block_pos.x; bpos.x <= max_block_pos.x; ++bpos.x) {
|
|
|
|
for (bpos.y = min_block_pos.y; bpos.y <= max_block_pos.y; ++bpos.y) {
|
|
|
|
|
|
|
|
make_block_dirty(bpos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-04 00:00:50 +01:00
|
|
|
namespace {
|
|
|
|
|
2017-08-28 02:32:23 +02:00
|
|
|
struct EnterWorldAction {
|
|
|
|
World *world;
|
2019-04-24 01:29:47 +01:00
|
|
|
EnterWorldAction(World *w) :
|
|
|
|
world(w) {}
|
2017-08-28 02:32:23 +02:00
|
|
|
void operator()(VoxelBlock *block) {
|
|
|
|
block->enter_world(world);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ExitWorldAction {
|
|
|
|
void operator()(VoxelBlock *block) {
|
|
|
|
block->exit_world();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct SetVisibilityAction {
|
|
|
|
bool visible;
|
2019-04-24 01:29:47 +01:00
|
|
|
SetVisibilityAction(bool v) :
|
|
|
|
visible(v) {}
|
2017-08-28 02:32:23 +02:00
|
|
|
void operator()(VoxelBlock *block) {
|
|
|
|
block->set_visible(visible);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-05-04 00:00:50 +01:00
|
|
|
} // namespace
|
|
|
|
|
2016-05-10 01:59:54 +02:00
|
|
|
void VoxelTerrain::_notification(int p_what) {
|
|
|
|
|
2017-01-01 04:40:16 +01:00
|
|
|
switch (p_what) {
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
case NOTIFICATION_ENTER_TREE:
|
|
|
|
set_process(true);
|
|
|
|
break;
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
case NOTIFICATION_PROCESS:
|
2018-09-28 01:10:35 +01:00
|
|
|
if (!Engine::get_singleton()->is_editor_hint() || _run_in_editor)
|
|
|
|
_process();
|
2017-08-13 01:19:39 +02:00
|
|
|
break;
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
case NOTIFICATION_EXIT_TREE:
|
|
|
|
break;
|
2017-01-01 04:40:16 +01:00
|
|
|
|
2017-08-28 02:32:23 +02:00
|
|
|
case NOTIFICATION_ENTER_WORLD: {
|
|
|
|
ERR_FAIL_COND(_map.is_null());
|
|
|
|
_map->for_all_blocks(EnterWorldAction(*get_world()));
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case NOTIFICATION_EXIT_WORLD:
|
|
|
|
ERR_FAIL_COND(_map.is_null());
|
|
|
|
_map->for_all_blocks(ExitWorldAction());
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NOTIFICATION_VISIBILITY_CHANGED:
|
|
|
|
ERR_FAIL_COND(_map.is_null());
|
|
|
|
_map->for_all_blocks(SetVisibilityAction(is_visible()));
|
|
|
|
break;
|
2017-08-15 02:24:52 +02:00
|
|
|
|
2019-04-24 01:29:47 +01:00
|
|
|
// TODO Listen for transform changes
|
2017-08-20 18:37:08 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
default:
|
|
|
|
break;
|
2017-01-01 04:40:16 +01:00
|
|
|
}
|
2016-05-10 01:59:54 +02:00
|
|
|
}
|
|
|
|
|
2019-04-29 21:31:52 +01:00
|
|
|
static void remove_positions_outside_box(
|
|
|
|
Vector<Vector3i> &positions,
|
|
|
|
Rect3i box,
|
|
|
|
HashMap<Vector3i, VoxelTerrain::BlockDirtyState, Vector3iHasher> &state_map) {
|
|
|
|
|
2019-04-24 01:29:47 +01:00
|
|
|
for (int i = 0; i < positions.size(); ++i) {
|
2018-09-25 00:54:07 +01:00
|
|
|
const Vector3i bpos = positions[i];
|
2019-04-24 01:29:47 +01:00
|
|
|
if (!box.contains(bpos)) {
|
2018-09-25 00:54:07 +01:00
|
|
|
int last = positions.size() - 1;
|
|
|
|
positions.write[i] = positions[last];
|
|
|
|
positions.resize(last);
|
|
|
|
state_map.erase(bpos);
|
|
|
|
--i;
|
|
|
|
}
|
|
|
|
}
|
2016-05-10 01:59:54 +02:00
|
|
|
}
|
|
|
|
|
2019-06-02 01:59:39 +01:00
|
|
|
void VoxelTerrain::get_viewer_block_pos_and_direction(Vector3i &out_block_pos, Vector3 &out_direction) {
|
|
|
|
|
|
|
|
if (Engine::get_singleton()->is_editor_hint()) {
|
|
|
|
|
|
|
|
// TODO Use editor's camera here
|
|
|
|
out_block_pos = Vector3i();
|
|
|
|
out_direction = Vector3(0, -1, 0);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// TODO Use viewport camera, much easier
|
|
|
|
Spatial *viewer = get_viewer(_viewer_path);
|
|
|
|
if (viewer) {
|
|
|
|
|
|
|
|
out_block_pos = _map->voxel_to_block(viewer->get_translation());
|
|
|
|
out_direction = -viewer->get_global_transform().basis.get_axis(Vector3::AXIS_Z);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
out_block_pos = _last_viewer_block_pos;
|
|
|
|
out_direction = Vector3(0, -1, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void VoxelTerrain::send_block_data_requests() {
|
|
|
|
|
|
|
|
VoxelDataLoader::Input input;
|
|
|
|
|
|
|
|
get_viewer_block_pos_and_direction(input.priority_position, input.priority_direction);
|
|
|
|
|
|
|
|
for (int i = 0; i < _blocks_pending_load.size(); ++i) {
|
|
|
|
VoxelDataLoader::InputBlock input_block;
|
|
|
|
input_block.position = _blocks_pending_load[i];
|
|
|
|
input_block.lod = 0;
|
|
|
|
input.blocks.push_back(input_block);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < _blocks_to_save.size(); ++i) {
|
|
|
|
print_line(String("Requesting save of block {0}").format(varray(_blocks_to_save[i].position.to_vec3())));
|
|
|
|
input.blocks.push_back(_blocks_to_save[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
//print_line(String("Sending {0} block requests").format(varray(input.blocks_to_emerge.size())));
|
|
|
|
_blocks_pending_load.clear();
|
|
|
|
_blocks_to_save.clear();
|
|
|
|
|
|
|
|
_stream_thread->push(input);
|
|
|
|
}
|
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
void VoxelTerrain::_process() {
|
2017-08-28 01:47:38 +02:00
|
|
|
|
2019-04-25 00:58:23 +01:00
|
|
|
// TODO Should be able to run without library, tho!
|
|
|
|
if (_library.is_null()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
OS &os = *OS::get_singleton();
|
2017-01-01 04:40:16 +01:00
|
|
|
|
2017-08-20 15:17:54 +02:00
|
|
|
ERR_FAIL_COND(_map.is_null());
|
|
|
|
|
2019-06-22 23:24:34 +09:00
|
|
|
ProfilingClock profiling_clock;
|
2018-09-29 18:59:19 +01:00
|
|
|
|
2017-03-28 00:56:01 +02:00
|
|
|
// Get viewer location
|
2017-08-28 01:47:38 +02:00
|
|
|
// TODO Transform to local (Spatial Transform)
|
|
|
|
Vector3i viewer_block_pos;
|
2019-05-19 21:04:26 +01:00
|
|
|
Vector3 viewer_direction;
|
2019-06-02 01:59:39 +01:00
|
|
|
get_viewer_block_pos_and_direction(viewer_block_pos, viewer_direction);
|
2017-08-28 01:47:38 +02:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
// Find out which blocks need to appear and which need to be unloaded
|
2017-08-28 01:47:38 +02:00
|
|
|
{
|
|
|
|
//Vector3i viewer_block_pos_delta = _last_viewer_block_pos - viewer_block_pos;
|
|
|
|
Rect3i new_box = Rect3i::from_center_extents(viewer_block_pos, Vector3i(_view_distance_blocks));
|
|
|
|
Rect3i prev_box = Rect3i::from_center_extents(_last_viewer_block_pos, Vector3i(_last_view_distance_blocks));
|
|
|
|
|
2019-04-24 01:29:47 +01:00
|
|
|
if (prev_box != new_box) {
|
2017-08-28 01:47:38 +02:00
|
|
|
//print_line(String("Loaded area changed: from ") + prev_box.to_string() + String(" to ") + new_box.to_string());
|
|
|
|
|
|
|
|
Rect3i bounds = Rect3i::get_bounding_box(prev_box, new_box);
|
|
|
|
Vector3i max = bounds.pos + bounds.size;
|
|
|
|
|
|
|
|
// TODO There should be a way to only iterate relevant blocks
|
|
|
|
Vector3i pos;
|
2019-04-24 01:29:47 +01:00
|
|
|
for (pos.z = bounds.pos.z; pos.z < max.z; ++pos.z) {
|
|
|
|
for (pos.y = bounds.pos.y; pos.y < max.y; ++pos.y) {
|
|
|
|
for (pos.x = bounds.pos.x; pos.x < max.x; ++pos.x) {
|
2017-08-28 01:47:38 +02:00
|
|
|
|
|
|
|
bool prev_contains = prev_box.contains(pos);
|
|
|
|
bool new_contains = new_box.contains(pos);
|
|
|
|
|
2019-04-24 01:29:47 +01:00
|
|
|
if (prev_contains && !new_contains) {
|
2017-08-28 01:47:38 +02:00
|
|
|
// Unload block
|
|
|
|
immerge_block(pos);
|
|
|
|
|
2019-04-24 01:29:47 +01:00
|
|
|
} else if (!prev_contains && new_contains) {
|
2017-08-28 01:47:38 +02:00
|
|
|
// Load or update block
|
|
|
|
make_block_dirty(pos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
// Eliminate pending blocks that aren't needed
|
|
|
|
remove_positions_outside_box(_blocks_pending_load, new_box, _dirty_blocks);
|
|
|
|
remove_positions_outside_box(_blocks_pending_update, new_box, _dirty_blocks);
|
2017-08-28 01:47:38 +02:00
|
|
|
}
|
|
|
|
|
2019-06-25 20:51:35 +01:00
|
|
|
_stats.time_detect_required_blocks = profiling_clock.restart();
|
2018-09-29 18:59:19 +01:00
|
|
|
|
2017-08-28 01:47:38 +02:00
|
|
|
_last_view_distance_blocks = _view_distance_blocks;
|
|
|
|
_last_viewer_block_pos = viewer_block_pos;
|
2017-03-28 00:56:01 +02:00
|
|
|
|
2019-06-02 01:59:39 +01:00
|
|
|
send_block_data_requests();
|
2017-01-01 04:40:16 +01:00
|
|
|
|
2019-06-22 23:24:34 +09:00
|
|
|
_stats.time_send_load_requests = profiling_clock.restart();
|
2018-09-29 18:59:19 +01:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
// Get block loading responses
|
2018-09-30 23:32:01 +01:00
|
|
|
// Note: if block loading is too fast, this can cause stutters. It should only happen on first load, though.
|
2018-09-25 00:54:07 +01:00
|
|
|
{
|
|
|
|
const unsigned int bs = _map->get_block_size();
|
|
|
|
const Vector3i block_size(bs, bs, bs);
|
|
|
|
|
2019-05-20 20:48:58 +01:00
|
|
|
VoxelDataLoader::Output output;
|
2019-05-25 16:07:38 +01:00
|
|
|
_stream_thread->pop(output);
|
2018-09-25 00:54:07 +01:00
|
|
|
//print_line(String("Receiving {0} blocks").format(varray(output.emerged_blocks.size())));
|
|
|
|
|
2019-05-28 01:10:50 +01:00
|
|
|
_stats.stream = output.stats;
|
2019-05-25 16:07:38 +01:00
|
|
|
_stats.dropped_stream_blocks = 0;
|
2018-09-25 00:54:07 +01:00
|
|
|
|
2019-05-20 20:48:58 +01:00
|
|
|
for (int i = 0; i < output.blocks.size(); ++i) {
|
2018-09-25 00:54:07 +01:00
|
|
|
|
2019-05-20 20:48:58 +01:00
|
|
|
const VoxelDataLoader::OutputBlock &ob = output.blocks[i];
|
2019-06-02 01:59:39 +01:00
|
|
|
|
|
|
|
if (ob.data.type != VoxelDataLoader::TYPE_LOAD) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-05-20 20:48:58 +01:00
|
|
|
Vector3i block_pos = ob.position;
|
2018-09-26 01:22:23 +01:00
|
|
|
|
2018-09-30 23:32:01 +01:00
|
|
|
{
|
|
|
|
VoxelTerrain::BlockDirtyState *state = _dirty_blocks.getptr(block_pos);
|
2019-04-24 01:29:47 +01:00
|
|
|
if (state == NULL || *state != BLOCK_LOAD) {
|
2018-09-30 23:32:01 +01:00
|
|
|
// That block was not requested, drop it
|
2019-05-25 16:07:38 +01:00
|
|
|
++_stats.dropped_stream_blocks;
|
2018-09-30 23:32:01 +01:00
|
|
|
continue;
|
|
|
|
}
|
2018-09-26 01:22:23 +01:00
|
|
|
}
|
2018-09-25 00:54:07 +01:00
|
|
|
|
2019-05-20 20:48:58 +01:00
|
|
|
if (ob.drop_hint) {
|
|
|
|
// That block was dropped by the data loader thread, but we were still expecting it...
|
|
|
|
// This is not good, because it means the loader is out of sync due to a bug.
|
|
|
|
// TODO Implement recovery like `VoxelLodTerrain`?
|
|
|
|
print_line(String("Received a block loading drop while we were still expecting it: lod{0} ({1}, {2}, {3})")
|
|
|
|
.format(varray(ob.lod, ob.position.x, ob.position.y, ob.position.z)));
|
2019-05-25 16:07:38 +01:00
|
|
|
++_stats.dropped_stream_blocks;
|
2019-05-20 20:48:58 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
// Check return
|
|
|
|
// TODO Shouldn't halt execution though, as it can bring the map in an invalid state!
|
2019-05-20 20:48:58 +01:00
|
|
|
ERR_FAIL_COND(ob.data.voxels_loaded->get_size() != block_size);
|
2018-09-25 00:54:07 +01:00
|
|
|
|
|
|
|
// TODO Discard blocks out of range
|
|
|
|
|
|
|
|
// Store buffer
|
|
|
|
bool update_neighbors = !_map->has_block(block_pos);
|
2019-05-20 20:48:58 +01:00
|
|
|
_map->set_block_buffer(block_pos, ob.data.voxels_loaded);
|
2018-09-25 00:54:07 +01:00
|
|
|
|
2019-08-16 20:56:07 +01:00
|
|
|
// TODO The following code appears to have order-dependency with block loading.
|
|
|
|
// i.e if block loading responses arrive in a different order they were requested in,
|
|
|
|
// some blocks will be stuck in LOAD. For now I made it so no re-ordering happens,
|
|
|
|
// but it needs to be made more robust
|
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
// Trigger mesh updates
|
|
|
|
if (update_neighbors) {
|
2018-09-30 23:32:01 +01:00
|
|
|
// All neighbors have to be checked. If they are now surrounded, they can be updated
|
2018-09-25 00:54:07 +01:00
|
|
|
Vector3i ndir;
|
|
|
|
for (ndir.z = -1; ndir.z < 2; ++ndir.z) {
|
|
|
|
for (ndir.x = -1; ndir.x < 2; ++ndir.x) {
|
|
|
|
for (ndir.y = -1; ndir.y < 2; ++ndir.y) {
|
|
|
|
Vector3i npos = block_pos + ndir;
|
|
|
|
// TODO What if the map is really composed of empty blocks?
|
|
|
|
if (_map->is_block_surrounded(npos)) {
|
|
|
|
|
2018-09-30 23:32:01 +01:00
|
|
|
VoxelTerrain::BlockDirtyState *state = _dirty_blocks.getptr(npos);
|
2018-09-27 00:31:09 +01:00
|
|
|
if (state && *state == BLOCK_UPDATE_NOT_SENT) {
|
2018-09-25 00:54:07 +01:00
|
|
|
// Assuming it is scheduled to be updated already.
|
2018-09-27 00:31:09 +01:00
|
|
|
// In case of BLOCK_UPDATE_SENT, we'll have to resend it.
|
2018-09-25 00:54:07 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-09-26 01:22:23 +01:00
|
|
|
_dirty_blocks[npos] = BLOCK_UPDATE_NOT_SENT;
|
2018-09-25 00:54:07 +01:00
|
|
|
_blocks_pending_update.push_back(npos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-09-30 23:32:01 +01:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
} else {
|
|
|
|
// Only update the block, neighbors will probably follow if needed
|
2018-09-26 01:22:23 +01:00
|
|
|
_dirty_blocks[block_pos] = BLOCK_UPDATE_NOT_SENT;
|
2018-09-25 00:54:07 +01:00
|
|
|
_blocks_pending_update.push_back(block_pos);
|
|
|
|
//OS::get_singleton()->print("Update (%i, %i, %i)\n", block_pos.x, block_pos.y, block_pos.z);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-01-01 04:40:16 +01:00
|
|
|
|
2019-06-22 23:24:34 +09:00
|
|
|
_stats.time_process_load_responses = profiling_clock.restart();
|
2018-09-29 18:59:19 +01:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
// Send mesh updates
|
|
|
|
{
|
|
|
|
VoxelMeshUpdater::Input input;
|
2019-05-19 21:04:26 +01:00
|
|
|
input.priority_position = viewer_block_pos;
|
|
|
|
input.priority_direction = viewer_direction;
|
2017-01-01 04:40:16 +01:00
|
|
|
|
2019-04-24 01:29:47 +01:00
|
|
|
for (int i = 0; i < _blocks_pending_update.size(); ++i) {
|
2018-09-25 00:54:07 +01:00
|
|
|
Vector3i block_pos = _blocks_pending_update[i];
|
2017-03-29 22:36:42 +02:00
|
|
|
|
2019-04-28 01:32:23 +01:00
|
|
|
// Check if the block is worth meshing
|
|
|
|
// Smooth meshing works on more neighbors, so checking a single block isn't enough to ignore it,
|
|
|
|
// but that will slow down meshing a lot.
|
|
|
|
// TODO This is one reason to separate terrain systems between blocky and smooth (other reason is LOD)
|
|
|
|
if (!_smooth_meshing_enabled) {
|
|
|
|
VoxelBlock *block = _map->get_block(block_pos);
|
|
|
|
if (block == NULL) {
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
CRASH_COND(block->voxels.is_null());
|
2018-09-29 21:47:53 +01:00
|
|
|
|
2019-04-28 01:32:23 +01:00
|
|
|
int air_type = 0;
|
|
|
|
if (
|
|
|
|
block->voxels->is_uniform(Voxel::CHANNEL_TYPE) &&
|
|
|
|
block->voxels->is_uniform(Voxel::CHANNEL_ISOLEVEL) &&
|
|
|
|
block->voxels->get_voxel(0, 0, 0, Voxel::CHANNEL_TYPE) == air_type) {
|
2018-09-29 21:47:53 +01:00
|
|
|
|
2019-04-28 01:32:23 +01:00
|
|
|
VoxelTerrain::BlockDirtyState *block_state = _dirty_blocks.getptr(block_pos);
|
|
|
|
CRASH_COND(block_state == NULL);
|
|
|
|
CRASH_COND(*block_state != BLOCK_UPDATE_NOT_SENT);
|
2018-09-29 21:47:53 +01:00
|
|
|
|
2019-04-28 01:32:23 +01:00
|
|
|
// The block contains empty voxels
|
|
|
|
block->set_mesh(Ref<Mesh>(), Ref<World>());
|
|
|
|
_dirty_blocks.erase(block_pos);
|
2018-09-29 21:47:53 +01:00
|
|
|
|
2019-04-28 01:32:23 +01:00
|
|
|
// Optional, but I guess it might spare some memory
|
|
|
|
block->voxels->clear_channel(Voxel::CHANNEL_TYPE, air_type);
|
2018-09-29 21:47:53 +01:00
|
|
|
|
2019-04-28 01:32:23 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2018-09-29 21:47:53 +01:00
|
|
|
}
|
|
|
|
|
2019-04-28 01:32:23 +01:00
|
|
|
VoxelTerrain::BlockDirtyState *block_state = _dirty_blocks.getptr(block_pos);
|
|
|
|
CRASH_COND(block_state == NULL);
|
|
|
|
CRASH_COND(*block_state != BLOCK_UPDATE_NOT_SENT);
|
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
// Create buffer padded with neighbor voxels
|
|
|
|
Ref<VoxelBuffer> nbuffer;
|
|
|
|
nbuffer.instance();
|
2019-04-28 01:32:23 +01:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
// TODO Make the buffer re-usable
|
|
|
|
unsigned int block_size = _map->get_block_size();
|
2019-04-28 20:48:59 +01:00
|
|
|
unsigned int padding = _block_updater->get_required_padding();
|
2019-04-28 01:32:23 +01:00
|
|
|
nbuffer->create(block_size + 2 * padding, block_size + 2 * padding, block_size + 2 * padding);
|
2018-09-25 00:54:07 +01:00
|
|
|
|
2019-04-28 01:32:23 +01:00
|
|
|
unsigned int channels_mask = (1 << VoxelBuffer::CHANNEL_TYPE) | (1 << VoxelBuffer::CHANNEL_ISOLEVEL);
|
|
|
|
_map->get_buffer_copy(_map->block_to_voxel(block_pos) - Vector3i(padding), **nbuffer, channels_mask);
|
2018-09-25 00:54:07 +01:00
|
|
|
|
|
|
|
VoxelMeshUpdater::InputBlock iblock;
|
2019-05-19 18:27:49 +01:00
|
|
|
iblock.data.voxels = nbuffer;
|
2018-09-25 00:54:07 +01:00
|
|
|
iblock.position = block_pos;
|
|
|
|
input.blocks.push_back(iblock);
|
2018-09-26 01:22:23 +01:00
|
|
|
|
2018-09-29 21:47:53 +01:00
|
|
|
*block_state = BLOCK_UPDATE_SENT;
|
2018-09-25 00:54:07 +01:00
|
|
|
}
|
2017-04-01 20:10:38 +02:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
_block_updater->push(input);
|
|
|
|
_blocks_pending_update.clear();
|
|
|
|
}
|
2017-01-05 02:39:40 +01:00
|
|
|
|
2019-06-22 23:24:34 +09:00
|
|
|
_stats.time_send_update_requests = profiling_clock.restart();
|
2018-09-29 18:59:19 +01:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
// Get mesh updates
|
|
|
|
{
|
2018-09-26 01:22:23 +01:00
|
|
|
{
|
|
|
|
VoxelMeshUpdater::Output output;
|
|
|
|
_block_updater->pop(output);
|
2017-04-01 20:10:38 +02:00
|
|
|
|
2018-09-26 01:22:23 +01:00
|
|
|
_stats.updater = output.stats;
|
|
|
|
_stats.updated_blocks = output.blocks.size();
|
|
|
|
_stats.dropped_updater_blocks = 0;
|
2017-01-05 02:39:40 +01:00
|
|
|
|
2018-09-26 01:22:23 +01:00
|
|
|
_blocks_pending_main_thread_update.append_array(output.blocks);
|
|
|
|
}
|
2017-01-05 02:39:40 +01:00
|
|
|
|
2018-09-26 01:22:23 +01:00
|
|
|
Ref<World> world = get_world();
|
2019-06-22 23:24:34 +09:00
|
|
|
ProfilingClock profiling_mesh_clock;
|
2018-09-26 01:22:23 +01:00
|
|
|
uint32_t timeout = os.get_ticks_msec() + 10;
|
|
|
|
int queue_index = 0;
|
|
|
|
|
|
|
|
// The following is done on the main thread because Godot doesn't really support multithreaded Mesh allocation.
|
2018-09-28 01:10:35 +01:00
|
|
|
// This also proved to be very slow compared to the meshing process itself...
|
|
|
|
// hopefully Vulkan will allow us to upload graphical resources without stalling rendering as they upload?
|
2018-09-26 01:22:23 +01:00
|
|
|
|
|
|
|
for (; queue_index < _blocks_pending_main_thread_update.size() && os.get_ticks_msec() < timeout; ++queue_index) {
|
2017-04-01 20:10:38 +02:00
|
|
|
|
2018-09-26 01:22:23 +01:00
|
|
|
const VoxelMeshUpdater::OutputBlock &ob = _blocks_pending_main_thread_update[queue_index];
|
|
|
|
|
|
|
|
VoxelTerrain::BlockDirtyState *state = _dirty_blocks.getptr(ob.position);
|
|
|
|
if (state && *state == BLOCK_UPDATE_SENT) {
|
|
|
|
_dirty_blocks.erase(ob.position);
|
|
|
|
}
|
2017-03-29 22:36:42 +02:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
VoxelBlock *block = _map->get_block(ob.position);
|
|
|
|
if (block == NULL) {
|
2018-09-26 01:22:23 +01:00
|
|
|
// That block is no longer loaded, drop the result
|
|
|
|
++_stats.dropped_updater_blocks;
|
2018-09-25 00:54:07 +01:00
|
|
|
continue;
|
2017-01-02 02:15:57 +01:00
|
|
|
}
|
2017-01-01 04:40:16 +01:00
|
|
|
|
2019-05-20 20:48:58 +01:00
|
|
|
if (ob.drop_hint) {
|
|
|
|
// That block is loaded, but its meshing request was dropped.
|
|
|
|
// TODO Not sure what to do in this case, the code sending update queries has to be tweaked
|
|
|
|
print_line("Received a block mesh drop while we were still expecting it");
|
|
|
|
++_stats.dropped_updater_blocks;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
Ref<ArrayMesh> mesh;
|
|
|
|
mesh.instance();
|
2017-08-28 01:47:38 +02:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
int surface_index = 0;
|
2019-05-19 18:27:49 +01:00
|
|
|
const VoxelMeshUpdater::OutputBlockData &data = ob.data;
|
|
|
|
for (int i = 0; i < data.blocky_surfaces.surfaces.size(); ++i) {
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2019-05-19 18:27:49 +01:00
|
|
|
Array surface = data.blocky_surfaces.surfaces[i];
|
2019-04-25 01:00:58 +01:00
|
|
|
if (surface.empty()) {
|
2018-09-25 00:54:07 +01:00
|
|
|
continue;
|
2019-04-25 01:00:58 +01:00
|
|
|
}
|
2017-03-29 22:36:42 +02:00
|
|
|
|
2019-04-25 01:00:58 +01:00
|
|
|
CRASH_COND(surface.size() != Mesh::ARRAY_MAX);
|
2019-05-19 18:27:49 +01:00
|
|
|
mesh->add_surface_from_arrays(data.blocky_surfaces.primitive_type, surface);
|
2018-09-25 00:54:07 +01:00
|
|
|
mesh->surface_set_material(surface_index, _materials[i]);
|
|
|
|
++surface_index;
|
|
|
|
}
|
|
|
|
|
2019-05-19 18:27:49 +01:00
|
|
|
for (int i = 0; i < data.smooth_surfaces.surfaces.size(); ++i) {
|
2017-04-01 20:10:38 +02:00
|
|
|
|
2019-05-19 18:27:49 +01:00
|
|
|
Array surface = data.smooth_surfaces.surfaces[i];
|
2019-04-25 01:00:58 +01:00
|
|
|
if (surface.empty()) {
|
2018-09-25 00:54:07 +01:00
|
|
|
continue;
|
2019-04-25 01:00:58 +01:00
|
|
|
}
|
2017-01-01 04:40:16 +01:00
|
|
|
|
2019-04-25 01:00:58 +01:00
|
|
|
CRASH_COND(surface.size() != Mesh::ARRAY_MAX);
|
2019-05-19 18:27:49 +01:00
|
|
|
mesh->add_surface_from_arrays(data.smooth_surfaces.primitive_type, surface);
|
2019-04-25 01:00:58 +01:00
|
|
|
mesh->surface_set_material(surface_index, _materials[i]);
|
2018-09-25 00:54:07 +01:00
|
|
|
++surface_index;
|
|
|
|
}
|
2017-04-06 23:44:11 +02:00
|
|
|
|
2019-04-25 01:00:58 +01:00
|
|
|
if (is_mesh_empty(mesh)) {
|
2018-09-25 00:54:07 +01:00
|
|
|
mesh = Ref<Mesh>();
|
2019-04-25 01:00:58 +01:00
|
|
|
}
|
2017-01-01 04:40:16 +01:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
block->set_mesh(mesh, world);
|
|
|
|
}
|
2017-04-06 23:44:11 +02:00
|
|
|
|
2018-09-26 01:22:23 +01:00
|
|
|
shift_up(_blocks_pending_main_thread_update, queue_index);
|
|
|
|
|
2019-06-22 23:24:34 +09:00
|
|
|
_stats.mesh_alloc_time = profiling_mesh_clock.restart();
|
2018-09-25 00:54:07 +01:00
|
|
|
}
|
|
|
|
|
2019-06-22 23:24:34 +09:00
|
|
|
_stats.time_process_update_responses = profiling_clock.restart();
|
2018-09-29 18:59:19 +01:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
//print_line(String("d:") + String::num(_dirty_blocks.size()) + String(", q:") + String::num(_block_update_queue.size()));
|
|
|
|
}
|
|
|
|
|
2016-05-10 01:59:54 +02:00
|
|
|
//void VoxelTerrain::block_removed(VoxelBlock & block) {
|
|
|
|
// MeshInstance * mesh_instance = block.get_mesh_instance(*this);
|
|
|
|
// if (mesh_instance) {
|
|
|
|
// mesh_instance->queue_delete();
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
|
2017-04-09 20:52:39 +02:00
|
|
|
struct _VoxelTerrainRaycastContext {
|
2017-08-13 01:19:39 +02:00
|
|
|
VoxelTerrain &terrain;
|
2017-04-09 20:52:39 +02:00
|
|
|
//unsigned int channel_mask;
|
|
|
|
};
|
|
|
|
|
|
|
|
static bool _raycast_binding_predicate(Vector3i pos, void *context_ptr) {
|
|
|
|
|
|
|
|
ERR_FAIL_COND_V(context_ptr == NULL, false);
|
2017-08-13 01:19:39 +02:00
|
|
|
_VoxelTerrainRaycastContext *context = (_VoxelTerrainRaycastContext *)context_ptr;
|
|
|
|
VoxelTerrain &terrain = context->terrain;
|
2017-04-09 20:52:39 +02:00
|
|
|
|
|
|
|
//unsigned int channel = context->channel;
|
2017-03-26 20:07:01 +02:00
|
|
|
|
2017-04-09 20:52:39 +02:00
|
|
|
Ref<VoxelMap> map = terrain.get_map();
|
|
|
|
int v0 = map->get_voxel(pos, Voxel::CHANNEL_TYPE);
|
2017-03-26 20:07:01 +02:00
|
|
|
|
2017-04-09 20:52:39 +02:00
|
|
|
Ref<VoxelLibrary> lib_ref = terrain.get_voxel_library();
|
2017-08-13 01:19:39 +02:00
|
|
|
if (lib_ref.is_null())
|
2017-03-26 20:07:01 +02:00
|
|
|
return false;
|
2017-08-13 01:19:39 +02:00
|
|
|
const VoxelLibrary &lib = **lib_ref;
|
2017-03-26 20:07:01 +02:00
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
if (lib.has_voxel(v0) == false)
|
2017-03-26 20:07:01 +02:00
|
|
|
return false;
|
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
const Voxel &voxel = lib.get_voxel_const(v0);
|
|
|
|
if (voxel.is_transparent() == false)
|
2017-04-09 20:52:39 +02:00
|
|
|
return true;
|
|
|
|
|
2019-04-28 01:29:52 +01:00
|
|
|
float v1 = map->get_voxel_f(pos.x, pos.y, pos.z, Voxel::CHANNEL_ISOLEVEL);
|
|
|
|
return v1 < 0;
|
2017-03-26 20:07:01 +02:00
|
|
|
}
|
|
|
|
|
2018-10-01 20:48:47 +01:00
|
|
|
void VoxelTerrain::_make_area_dirty_binding(AABB aabb) {
|
|
|
|
make_area_dirty(Rect3i(aabb.position, aabb.size));
|
|
|
|
}
|
|
|
|
|
2017-03-26 20:07:01 +02:00
|
|
|
Variant VoxelTerrain::_raycast_binding(Vector3 origin, Vector3 direction, real_t max_distance) {
|
|
|
|
|
|
|
|
// TODO Transform input if the terrain is rotated (in the future it can be made a Spatial node)
|
|
|
|
|
|
|
|
Vector3i hit_pos;
|
|
|
|
Vector3i prev_pos;
|
|
|
|
|
2017-04-09 20:52:39 +02:00
|
|
|
_VoxelTerrainRaycastContext context = { *this };
|
|
|
|
|
2017-08-13 01:19:39 +02:00
|
|
|
if (voxel_raycast(origin, direction, _raycast_binding_predicate, &context, max_distance, hit_pos, prev_pos)) {
|
2017-03-26 20:07:01 +02:00
|
|
|
|
|
|
|
Dictionary hit = Dictionary();
|
|
|
|
hit["position"] = hit_pos.to_vec3();
|
|
|
|
hit["prev_position"] = prev_pos.to_vec3();
|
|
|
|
return hit;
|
2017-08-13 01:19:39 +02:00
|
|
|
} else {
|
2017-03-26 20:07:01 +02:00
|
|
|
return Variant(); // Null dictionary, no alloc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-20 15:17:54 +02:00
|
|
|
Vector3 VoxelTerrain::_voxel_to_block_binding(Vector3 pos) {
|
|
|
|
return Vector3i(_map->voxel_to_block(pos)).to_vec3();
|
|
|
|
}
|
|
|
|
|
|
|
|
Vector3 VoxelTerrain::_block_to_voxel_binding(Vector3 pos) {
|
|
|
|
return Vector3i(_map->block_to_voxel(pos)).to_vec3();
|
|
|
|
}
|
|
|
|
|
2018-09-26 01:22:23 +01:00
|
|
|
// For debugging purpose
|
|
|
|
VoxelTerrain::BlockDirtyState VoxelTerrain::get_block_state(Vector3 p_bpos) const {
|
|
|
|
Vector3i bpos = p_bpos;
|
|
|
|
const VoxelTerrain::BlockDirtyState *state = _dirty_blocks.getptr(bpos);
|
2019-04-24 01:29:47 +01:00
|
|
|
if (state) {
|
2018-09-26 01:22:23 +01:00
|
|
|
return *state;
|
|
|
|
} else {
|
2019-04-24 01:29:47 +01:00
|
|
|
if (!_map->has_block(bpos))
|
2018-09-26 01:22:23 +01:00
|
|
|
return BLOCK_NONE;
|
|
|
|
return BLOCK_IDLE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-10 01:59:54 +02:00
|
|
|
void VoxelTerrain::_bind_methods() {
|
|
|
|
|
2019-05-28 01:10:50 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("set_stream", "stream"), &VoxelTerrain::set_stream);
|
2019-05-25 16:07:38 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("get_stream"), &VoxelTerrain::get_stream);
|
2017-01-02 02:15:57 +01:00
|
|
|
|
2017-08-15 02:24:52 +02:00
|
|
|
ClassDB::bind_method(D_METHOD("set_voxel_library", "library"), &VoxelTerrain::set_voxel_library);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_voxel_library"), &VoxelTerrain::get_voxel_library);
|
|
|
|
|
2019-07-04 00:58:39 +09:00
|
|
|
ClassDB::bind_method(D_METHOD("set_material", "id", "material"), &VoxelTerrain::set_material);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_material", "id"), &VoxelTerrain::get_material);
|
|
|
|
|
2017-08-15 02:24:52 +02:00
|
|
|
ClassDB::bind_method(D_METHOD("set_view_distance", "distance_in_voxels"), &VoxelTerrain::set_view_distance);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_view_distance"), &VoxelTerrain::get_view_distance);
|
|
|
|
|
2017-03-26 18:09:37 +02:00
|
|
|
ClassDB::bind_method(D_METHOD("get_generate_collisions"), &VoxelTerrain::get_generate_collisions);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_generate_collisions", "enabled"), &VoxelTerrain::set_generate_collisions);
|
|
|
|
|
2017-08-15 02:24:52 +02:00
|
|
|
ClassDB::bind_method(D_METHOD("get_viewer_path"), &VoxelTerrain::get_viewer_path);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_viewer_path", "path"), &VoxelTerrain::set_viewer_path);
|
2017-03-28 00:56:01 +02:00
|
|
|
|
2019-04-28 01:32:23 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("is_smooth_meshing_enabled"), &VoxelTerrain::is_smooth_meshing_enabled);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_smooth_meshing_enabled", "enabled"), &VoxelTerrain::set_smooth_meshing_enabled);
|
|
|
|
|
2017-08-13 00:08:53 +02:00
|
|
|
ClassDB::bind_method(D_METHOD("get_storage"), &VoxelTerrain::get_map);
|
2017-01-05 02:39:40 +01:00
|
|
|
|
2019-05-10 00:57:09 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("voxel_to_block", "voxel_pos"), &VoxelTerrain::_voxel_to_block_binding);
|
|
|
|
ClassDB::bind_method(D_METHOD("block_to_voxel", "block_pos"), &VoxelTerrain::_block_to_voxel_binding);
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2017-03-29 22:36:42 +02:00
|
|
|
ClassDB::bind_method(D_METHOD("make_voxel_dirty", "pos"), &VoxelTerrain::_make_voxel_dirty_binding);
|
2018-10-01 20:48:47 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("make_area_dirty", "aabb"), &VoxelTerrain::_make_area_dirty_binding);
|
2016-05-10 01:59:54 +02:00
|
|
|
|
2017-08-13 00:08:53 +02:00
|
|
|
ClassDB::bind_method(D_METHOD("raycast", "origin", "direction", "max_distance"), &VoxelTerrain::_raycast_binding, DEFVAL(100));
|
2017-03-26 20:07:01 +02:00
|
|
|
|
2018-09-25 00:54:07 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("get_statistics"), &VoxelTerrain::get_statistics);
|
2018-09-26 01:22:23 +01:00
|
|
|
ClassDB::bind_method(D_METHOD("get_block_state", "block_pos"), &VoxelTerrain::get_block_state);
|
2017-08-15 02:24:52 +02:00
|
|
|
|
2019-05-28 01:10:50 +01:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "VoxelStream"), "set_stream", "get_stream");
|
2017-08-15 02:24:52 +02:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "voxel_library", PROPERTY_HINT_RESOURCE_TYPE, "VoxelLibrary"), "set_voxel_library", "get_voxel_library");
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "view_distance"), "set_view_distance", "get_view_distance");
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewer_path"), "set_viewer_path", "get_viewer_path");
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_collisions"), "set_generate_collisions", "get_generate_collisions");
|
2019-04-28 01:32:23 +01:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_meshing_enabled"), "set_smooth_meshing_enabled", "is_smooth_meshing_enabled");
|
2018-09-26 01:22:23 +01:00
|
|
|
|
|
|
|
BIND_ENUM_CONSTANT(BLOCK_NONE);
|
|
|
|
BIND_ENUM_CONSTANT(BLOCK_LOAD);
|
|
|
|
BIND_ENUM_CONSTANT(BLOCK_UPDATE_NOT_SENT);
|
|
|
|
BIND_ENUM_CONSTANT(BLOCK_UPDATE_SENT);
|
|
|
|
BIND_ENUM_CONSTANT(BLOCK_IDLE);
|
2016-05-10 01:59:54 +02:00
|
|
|
}
|