Show terrain in editor, with option to turn it off. A scripted stream will turn it off by default.

This commit is contained in:
Marc Gilleron 2020-08-14 20:33:09 +01:00
parent 27180e11e6
commit 23ce65d789
15 changed files with 260 additions and 112 deletions

1
SCsub
View File

@ -19,6 +19,7 @@ files = [
"math/*.cpp", "math/*.cpp",
"edition/*.cpp", "edition/*.cpp",
"editor/graph/*.cpp", "editor/graph/*.cpp",
"editor/terrain/*.cpp",
"thirdparty/lz4/*.c" "thirdparty/lz4/*.c"
] ]

View File

@ -0,0 +1,49 @@
#include "voxel_terrain_editor_plugin.h"
#include "../../terrain/voxel_lod_terrain.h"
#include "../../terrain/voxel_terrain.h"
VoxelTerrainEditorPlugin::VoxelTerrainEditorPlugin(EditorNode *p_node) {
_restart_stream_button = memnew(Button);
_restart_stream_button->set_text(TTR("Re-generate"));
_restart_stream_button->connect("pressed", this, "_on_restart_stream_button_pressed");
_restart_stream_button->hide();
add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, _restart_stream_button);
}
bool VoxelTerrainEditorPlugin::handles(Object *p_object) const {
VoxelTerrain *terrain = Object::cast_to<VoxelTerrain>(p_object);
if (terrain != nullptr) {
return true;
}
VoxelLodTerrain *terrain2 = Object::cast_to<VoxelLodTerrain>(p_object);
return terrain2 != nullptr;
}
void VoxelTerrainEditorPlugin::edit(Object *p_object) {
_node = Object::cast_to<Node>(p_object);
}
void VoxelTerrainEditorPlugin::make_visible(bool visible) {
_restart_stream_button->set_visible(visible);
if (!visible) {
_node = nullptr;
}
}
void VoxelTerrainEditorPlugin::_on_restart_stream_button_pressed() {
ERR_FAIL_COND(_node == nullptr);
VoxelTerrain *terrain = Object::cast_to<VoxelTerrain>(_node);
if (terrain != nullptr) {
terrain->restart_stream();
return;
}
VoxelLodTerrain *terrain2 = Object::cast_to<VoxelLodTerrain>(_node);
ERR_FAIL_COND(terrain2 == nullptr);
terrain2->restart_stream();
}
void VoxelTerrainEditorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("_on_restart_stream_button_pressed"),
&VoxelTerrainEditorPlugin::_on_restart_stream_button_pressed);
}

View File

@ -0,0 +1,26 @@
#ifndef VOXEL_TERRAIN_EDITOR_PLUGIN_H
#define VOXEL_TERRAIN_EDITOR_PLUGIN_H
#include <editor/editor_plugin.h>
class Button;
class VoxelTerrainEditorPlugin : public EditorPlugin {
GDCLASS(VoxelTerrainEditorPlugin, EditorPlugin)
public:
VoxelTerrainEditorPlugin(EditorNode *p_node);
bool handles(Object *p_object) const override;
void edit(Object *p_object) override;
void make_visible(bool visible) override;
private:
void _on_restart_stream_button_pressed();
static void _bind_methods();
Button *_restart_stream_button = nullptr;
Node *_node = nullptr;
};
#endif // VOXEL_TERRAIN_EDITOR_PLUGIN_H

View File

@ -340,6 +340,8 @@ Ref<Resource> VoxelGeneratorGraph::duplicate(bool p_subresources) const {
d->_graph.copy_from(_graph, p_subresources); d->_graph.copy_from(_graph, p_subresources);
// Program not copied, as it may contain pointers to the resources we are duplicating // Program not copied, as it may contain pointers to the resources we are duplicating
d->compile();
return d; return d;
} }
@ -590,7 +592,6 @@ bool VoxelGeneratorGraph::_set(const StringName &p_name, const Variant &p_value)
} }
bool VoxelGeneratorGraph::_get(const StringName &p_name, Variant &r_ret) const { bool VoxelGeneratorGraph::_get(const StringName &p_name, Variant &r_ret) const {
const String name = p_name; const String name = p_name;
struct L { struct L {
@ -637,7 +638,6 @@ bool VoxelGeneratorGraph::_get(const StringName &p_name, Variant &r_ret) const {
} }
void VoxelGeneratorGraph::_get_property_list(List<PropertyInfo> *p_list) const { void VoxelGeneratorGraph::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::INT, "bounds/type", PROPERTY_HINT_ENUM, "None,Vertical,Box")); p_list->push_back(PropertyInfo(Variant::INT, "bounds/type", PROPERTY_HINT_ENUM, "None,Vertical,Box"));
switch (_bounds.type) { switch (_bounds.type) {

View File

@ -744,6 +744,10 @@ float VoxelGraphRuntime::generate_single(const Vector3i &position) {
} }
Interval VoxelGraphRuntime::analyze_range(Vector3i min_pos, Vector3i max_pos) { Interval VoxelGraphRuntime::analyze_range(Vector3i min_pos, Vector3i max_pos) {
#ifdef TOOLS_ENABLED
ERR_FAIL_COND_V_MSG(_sdf_output_address == -1, Interval(), "The graph has no SDF output");
#endif
ArraySlice<float> min_memory(_memory, 0, _memory.size() / 2); ArraySlice<float> min_memory(_memory, 0, _memory.size() / 2);
ArraySlice<float> max_memory(_memory, _memory.size() / 2, _memory.size()); ArraySlice<float> max_memory(_memory, _memory.size() / 2, _memory.size());
min_memory[0] = min_pos.x; min_memory[0] = min_pos.x;
@ -755,7 +759,6 @@ Interval VoxelGraphRuntime::analyze_range(Vector3i min_pos, Vector3i max_pos) {
uint32_t pc = 0; uint32_t pc = 0;
while (pc < _program.size()) { while (pc < _program.size()) {
const uint8_t opid = _program[pc++]; const uint8_t opid = _program[pc++];
switch (opid) { switch (opid) {

View File

@ -3,6 +3,7 @@
#include "edition/voxel_tool_terrain.h" #include "edition/voxel_tool_terrain.h"
#include "editor/editor_plugin.h" #include "editor/editor_plugin.h"
#include "editor/graph/voxel_graph_editor_plugin.h" #include "editor/graph/voxel_graph_editor_plugin.h"
#include "editor/terrain/voxel_terrain_editor_plugin.h"
#include "generators/graph/voxel_generator_graph.h" #include "generators/graph/voxel_generator_graph.h"
#include "generators/graph/voxel_graph_node_db.h" #include "generators/graph/voxel_graph_node_db.h"
#include "generators/voxel_generator_flat.h" #include "generators/voxel_generator_flat.h"
@ -79,6 +80,7 @@ void register_voxel_types() {
VoxelDebug::create_debug_box_mesh(); VoxelDebug::create_debug_box_mesh();
EditorPlugins::add_by_type<VoxelGraphEditorPlugin>(); EditorPlugins::add_by_type<VoxelGraphEditorPlugin>();
EditorPlugins::add_by_type<VoxelTerrainEditorPlugin>();
#endif #endif
} }

View File

@ -1,5 +1,6 @@
#include "voxel_stream.h" #include "voxel_stream.h"
#include "../voxel_string_names.h" #include "../voxel_string_names.h"
#include <core/script_language.h>
VoxelStream::VoxelStream() { VoxelStream::VoxelStream() {
} }
@ -63,6 +64,11 @@ VoxelStream::Stats VoxelStream::get_statistics() const {
return _stats; return _stats;
} }
bool VoxelStream::has_script() const {
Ref<Script> s = get_script();
return s.is_valid();
}
void VoxelStream::_bind_methods() { void VoxelStream::_bind_methods() {
// TODO Make these proper virtual, it confuses C# bindings // TODO Make these proper virtual, it confuses C# bindings
// Note: C++ inheriting classes don't need to re-bind these, because they are bindings that call the actual virtual methods // Note: C++ inheriting classes don't need to re-bind these, because they are bindings that call the actual virtual methods

View File

@ -47,6 +47,8 @@ public:
Stats get_statistics() const; Stats get_statistics() const;
virtual bool has_script() const;
protected: protected:
static void _bind_methods(); static void _bind_methods();

View File

@ -74,8 +74,14 @@ Vector3 VoxelStreamFile::_get_block_size() const {
return Vector3i(1 << get_block_size_po2()).to_vec3(); return Vector3i(1 << get_block_size_po2()).to_vec3();
} }
void VoxelStreamFile::_bind_methods() { bool VoxelStreamFile::has_script() const {
if (_fallback_stream.is_valid()) {
return _fallback_stream->has_script();
}
return VoxelStream::has_script();
}
void VoxelStreamFile::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_save_fallback_output", "enabled"), &VoxelStreamFile::set_save_fallback_output); ClassDB::bind_method(D_METHOD("set_save_fallback_output", "enabled"), &VoxelStreamFile::set_save_fallback_output);
ClassDB::bind_method(D_METHOD("get_save_fallback_output"), &VoxelStreamFile::get_save_fallback_output); ClassDB::bind_method(D_METHOD("get_save_fallback_output"), &VoxelStreamFile::get_save_fallback_output);

View File

@ -26,6 +26,8 @@ public:
int get_used_channels_mask() const override; int get_used_channels_mask() const override;
bool has_script() const override;
protected: protected:
static void _bind_methods(); static void _bind_methods();

View File

@ -7,6 +7,12 @@ VoxelDataLoader::VoxelDataLoader(unsigned int thread_count, Ref<VoxelStream> str
PRINT_VERBOSE("Constructing VoxelDataLoader"); PRINT_VERBOSE("Constructing VoxelDataLoader");
CRASH_COND(stream.is_null()); CRASH_COND(stream.is_null());
if (Engine::get_singleton()->is_editor_hint()) {
// In the editor, we want extra safety.
// Duplicate all data so modifications done by the user won't impact running threads.
stream = stream->duplicate(true);
}
// TODO I'm not sure it's worth to configure more than one thread for voxel streams // TODO I'm not sure it's worth to configure more than one thread for voxel streams
FixedArray<Mgr::BlockProcessingFunc, Mgr::MAX_JOBS> processors; FixedArray<Mgr::BlockProcessingFunc, Mgr::MAX_JOBS> processors;

View File

@ -82,11 +82,18 @@ VoxelLodTerrain::~VoxelLodTerrain() {
String VoxelLodTerrain::get_configuration_warning() const { String VoxelLodTerrain::get_configuration_warning() const {
if (_stream.is_valid()) { if (_stream.is_valid()) {
Ref<Script> script = _stream->get_script(); Ref<Script> script = _stream->get_script();
if (script.is_valid() && !script->is_tool()) { if (script.is_valid()) {
return TTR("The custom stream is not tool, the editor won't be able to use it."); if (script->is_tool()) {
// TODO This is very annoying. Probably needs an issue or proposal in Godot so we can handle this properly?
return TTR("Be careful! Don't edit your custom stream while it's running, it can cause crashes. "
"Turn off `run_stream_in_editor` before doing so.");
} else {
return TTR("The custom stream is not tool, the editor won't be able to use it.");
}
} }
if (!(_stream->get_used_channels_mask() & (1 << VoxelBuffer::CHANNEL_SDF))) { if (!(_stream->get_used_channels_mask() & (1 << VoxelBuffer::CHANNEL_SDF))) {
return TTR("VoxelLodTerrain supports only stream channel \"Sdf\" (smooth)."); return TTR("VoxelLodTerrain supports only stream channel \"Sdf\" (smooth), "
"but `get_used_channels_mask()` tells it's providing none of these.");
} }
} }
return String(); return String();
@ -113,44 +120,46 @@ unsigned int VoxelLodTerrain::get_block_size_pow2() const {
} }
void VoxelLodTerrain::set_stream(Ref<VoxelStream> p_stream) { void VoxelLodTerrain::set_stream(Ref<VoxelStream> p_stream) {
if (p_stream == _stream) { if (p_stream == _stream) {
return; return;
} }
if (_stream.is_valid()) {
if (_stream->is_connected(CoreStringNames::get_singleton()->changed, this, "_on_stream_params_changed")) {
_stream->disconnect(CoreStringNames::get_singleton()->changed, this, "_on_stream_params_changed");
}
}
_stream = p_stream; _stream = p_stream;
#ifdef TOOLS_ENABLED
if (_stream.is_valid()) { if (_stream.is_valid()) {
_stream->connect(CoreStringNames::get_singleton()->changed, this, "_on_stream_params_changed"); if (Engine::get_singleton()->is_editor_hint()) {
if (_stream->has_script()) {
// Safety check. It's too easy to break threads by making a script reload.
// You can turn it back on, but be careful.
_run_stream_in_editor = false;
_change_notify();
}
}
} }
#endif
_on_stream_params_changed(); _on_stream_params_changed();
} }
void VoxelLodTerrain::_on_stream_params_changed() { void VoxelLodTerrain::_on_stream_params_changed() {
stop_streamer(); stop_streamer();
bool was_updater_running = _block_updater != nullptr; const bool was_updater_running = _block_updater != nullptr;
stop_updater(); stop_updater();
Ref<VoxelStreamFile> file_stream = _stream; Ref<VoxelStreamFile> file_stream = _stream;
if (file_stream.is_valid()) { if (file_stream.is_valid()) {
const int stream_block_size_po2 = file_stream->get_block_size_po2();
int stream_block_size_po2 = file_stream->get_block_size_po2();
_set_block_size_po2(stream_block_size_po2); _set_block_size_po2(stream_block_size_po2);
int stream_lod_count = file_stream->get_lod_count(); const int stream_lod_count = file_stream->get_lod_count();
_set_lod_count(min(stream_lod_count, get_lod_count())); _set_lod_count(min(stream_lod_count, get_lod_count()));
} }
if (_stream.is_valid()) { reset_maps();
if (_stream.is_valid() && (!Engine::get_singleton()->is_editor_hint() || _run_stream_in_editor)) {
start_streamer(); start_streamer();
} }
if (was_updater_running) { if (was_updater_running) {
@ -168,7 +177,6 @@ void VoxelLodTerrain::_on_stream_params_changed() {
} }
void VoxelLodTerrain::set_block_size_po2(unsigned int p_block_size_po2) { void VoxelLodTerrain::set_block_size_po2(unsigned int p_block_size_po2) {
ERR_FAIL_COND(p_block_size_po2 < 1); ERR_FAIL_COND(p_block_size_po2 < 1);
ERR_FAIL_COND(p_block_size_po2 > 32); ERR_FAIL_COND(p_block_size_po2 > 32);
@ -182,20 +190,7 @@ void VoxelLodTerrain::set_block_size_po2(unsigned int p_block_size_po2) {
return; return;
} }
bool updater_was_running = _block_updater != nullptr; _on_stream_params_changed();
stop_streamer();
stop_updater();
_set_block_size_po2(p_block_size_po2);
reset_maps();
if (_stream.is_valid()) {
start_streamer();
}
if (updater_was_running) {
start_updater();
}
} }
void VoxelLodTerrain::_set_block_size_po2(int p_block_size_po2) { void VoxelLodTerrain::_set_block_size_po2(int p_block_size_po2) {
@ -240,10 +235,8 @@ int VoxelLodTerrain::get_view_distance() const {
} }
void VoxelLodTerrain::set_view_distance(int p_distance_in_voxels) { void VoxelLodTerrain::set_view_distance(int p_distance_in_voxels) {
ERR_FAIL_COND(p_distance_in_voxels <= 0); ERR_FAIL_COND(p_distance_in_voxels <= 0);
ERR_FAIL_COND(p_distance_in_voxels > 8192); ERR_FAIL_COND(p_distance_in_voxels > 8192);
// Note: this is a hint distance, the terrain will attempt to have this radius filled with loaded voxels. // Note: this is a hint distance, the terrain will attempt to have this radius filled with loaded voxels.
// It is possible for blocks to still load beyond that distance. // It is possible for blocks to still load beyond that distance.
_view_distance_voxels = p_distance_in_voxels; _view_distance_voxels = p_distance_in_voxels;
@ -264,7 +257,6 @@ Spatial *VoxelLodTerrain::get_viewer() const {
} }
void VoxelLodTerrain::start_updater() { void VoxelLodTerrain::start_updater() {
ERR_FAIL_COND(_block_updater != nullptr); ERR_FAIL_COND(_block_updater != nullptr);
// TODO Thread-safe way to change those parameters // TODO Thread-safe way to change those parameters
@ -275,7 +267,6 @@ void VoxelLodTerrain::start_updater() {
} }
void VoxelLodTerrain::stop_updater() { void VoxelLodTerrain::stop_updater() {
struct ResetMeshStateAction { struct ResetMeshStateAction {
void operator()(VoxelBlock *block) { void operator()(VoxelBlock *block) {
if (block->get_mesh_state() == VoxelBlock::MESH_UPDATE_SENT) { if (block->get_mesh_state() == VoxelBlock::MESH_UPDATE_SENT) {
@ -292,7 +283,6 @@ void VoxelLodTerrain::stop_updater() {
_blocks_pending_main_thread_update.clear(); _blocks_pending_main_thread_update.clear();
for (unsigned int i = 0; i < _lods.size(); ++i) { for (unsigned int i = 0; i < _lods.size(); ++i) {
Lod &lod = _lods[i]; Lod &lod = _lods[i];
lod.blocks_pending_update.clear(); lod.blocks_pending_update.clear();
@ -304,7 +294,6 @@ void VoxelLodTerrain::stop_updater() {
} }
void VoxelLodTerrain::start_streamer() { void VoxelLodTerrain::start_streamer() {
ERR_FAIL_COND(_stream_thread != nullptr); ERR_FAIL_COND(_stream_thread != nullptr);
ERR_FAIL_COND(_stream.is_null()); ERR_FAIL_COND(_stream.is_null());
@ -312,7 +301,6 @@ void VoxelLodTerrain::start_streamer() {
} }
void VoxelLodTerrain::stop_streamer() { void VoxelLodTerrain::stop_streamer() {
if (_stream_thread) { if (_stream_thread) {
memdelete(_stream_thread); memdelete(_stream_thread);
_stream_thread = nullptr; _stream_thread = nullptr;
@ -325,7 +313,6 @@ void VoxelLodTerrain::stop_streamer() {
} }
void VoxelLodTerrain::set_lod_split_scale(float p_lod_split_scale) { void VoxelLodTerrain::set_lod_split_scale(float p_lod_split_scale) {
if (p_lod_split_scale == _lod_split_scale) { if (p_lod_split_scale == _lod_split_scale) {
return; return;
} }
@ -333,7 +320,6 @@ void VoxelLodTerrain::set_lod_split_scale(float p_lod_split_scale) {
_lod_split_scale = CLAMP(p_lod_split_scale, VoxelConstants::MINIMUM_LOD_SPLIT_SCALE, VoxelConstants::MAXIMUM_LOD_SPLIT_SCALE); _lod_split_scale = CLAMP(p_lod_split_scale, VoxelConstants::MINIMUM_LOD_SPLIT_SCALE, VoxelConstants::MAXIMUM_LOD_SPLIT_SCALE);
for (Map<Vector3i, OctreeItem>::Element *E = _lod_octrees.front(); E; E = E->next()) { for (Map<Vector3i, OctreeItem>::Element *E = _lod_octrees.front(); E; E = E->next()) {
OctreeItem &item = E->value(); OctreeItem &item = E->value();
item.octree.set_split_scale(_lod_split_scale); item.octree.set_split_scale(_lod_split_scale);
@ -347,7 +333,6 @@ float VoxelLodTerrain::get_lod_split_scale() const {
} }
void VoxelLodTerrain::set_lod_count(int p_lod_count) { void VoxelLodTerrain::set_lod_count(int p_lod_count) {
ERR_FAIL_COND(p_lod_count >= (int)VoxelConstants::MAX_LOD); ERR_FAIL_COND(p_lod_count >= (int)VoxelConstants::MAX_LOD);
ERR_FAIL_COND(p_lod_count < 1); ERR_FAIL_COND(p_lod_count < 1);
@ -357,7 +342,6 @@ void VoxelLodTerrain::set_lod_count(int p_lod_count) {
} }
void VoxelLodTerrain::_set_lod_count(int p_lod_count) { void VoxelLodTerrain::_set_lod_count(int p_lod_count) {
CRASH_COND(p_lod_count >= (int)VoxelConstants::MAX_LOD); CRASH_COND(p_lod_count >= (int)VoxelConstants::MAX_LOD);
CRASH_COND(p_lod_count < 1); CRASH_COND(p_lod_count < 1);
@ -376,25 +360,31 @@ void VoxelLodTerrain::_set_lod_count(int p_lod_count) {
void VoxelLodTerrain::reset_maps() { void VoxelLodTerrain::reset_maps() {
// Clears all blocks and reconfigures maps to account for new LOD count and block sizes // Clears all blocks and reconfigures maps to account for new LOD count and block sizes
for (int lod_index = 0; lod_index < (int)_lods.size(); ++lod_index) { // Don't reset while streaming, the result can be dirty?
//CRASH_COND(_stream_thread != nullptr);
for (int lod_index = 0; lod_index < (int)_lods.size(); ++lod_index) {
Lod &lod = _lods[lod_index]; Lod &lod = _lods[lod_index];
// Instance new maps if we have more lods, or clear them otherwise // Instance new maps if we have more lods, or clear them otherwise
if (lod_index < get_lod_count()) { if (lod_index < get_lod_count()) {
if (lod.map.is_null()) { if (lod.map.is_null()) {
lod.map.instance(); lod.map.instance();
} }
lod.map->create(get_block_size_pow2(), lod_index); lod.map->create(get_block_size_pow2(), lod_index);
} else { lod.last_view_distance_blocks = 0;
} else {
if (lod.map.is_valid()) { if (lod.map.is_valid()) {
lod.map.unref(); lod.map.unref();
} }
} }
} }
// Reset previous state caches to force rebuilding the view area
_last_octree_region_box = Rect3i();
_lod_octrees.clear();
} }
int VoxelLodTerrain::get_lod_count() const { int VoxelLodTerrain::get_lod_count() const {
@ -437,9 +427,7 @@ Vector3 VoxelLodTerrain::voxel_to_block_position(Vector3 vpos, int lod_index) co
} }
void VoxelLodTerrain::_notification(int p_what) { void VoxelLodTerrain::_notification(int p_what) {
switch (p_what) { switch (p_what) {
case NOTIFICATION_ENTER_TREE: case NOTIFICATION_ENTER_TREE:
if (_block_updater == nullptr) { if (_block_updater == nullptr) {
start_updater(); start_updater();
@ -448,9 +436,7 @@ void VoxelLodTerrain::_notification(int p_what) {
break; break;
case NOTIFICATION_PROCESS: case NOTIFICATION_PROCESS:
if (!Engine::get_singleton()->is_editor_hint()) { _process();
_process();
}
break; break;
case NOTIFICATION_EXIT_TREE: case NOTIFICATION_EXIT_TREE:
@ -496,9 +482,7 @@ void VoxelLodTerrain::_notification(int p_what) {
} }
void VoxelLodTerrain::get_viewer_pos_and_direction(Vector3 &out_pos, Vector3 &out_direction) const { void VoxelLodTerrain::get_viewer_pos_and_direction(Vector3 &out_pos, Vector3 &out_direction) const {
if (Engine::get_singleton()->is_editor_hint()) { if (Engine::get_singleton()->is_editor_hint()) {
// TODO Use editor's camera here // TODO Use editor's camera here
out_pos = Vector3(); out_pos = Vector3();
out_direction = Vector3(0, -1, 0); out_direction = Vector3(0, -1, 0);
@ -507,13 +491,11 @@ void VoxelLodTerrain::get_viewer_pos_and_direction(Vector3 &out_pos, Vector3 &ou
// TODO Have option to use viewport camera // TODO Have option to use viewport camera
Spatial *viewer = get_viewer(); Spatial *viewer = get_viewer();
if (viewer) { if (viewer) {
Transform gt = viewer->get_global_transform(); Transform gt = viewer->get_global_transform();
out_pos = gt.origin; out_pos = gt.origin;
out_direction = -gt.basis.get_axis(Vector3::AXIS_Z); out_direction = -gt.basis.get_axis(Vector3::AXIS_Z);
} else { } else {
// TODO Just remember last viewer pos // TODO Just remember last viewer pos
out_pos = (_lods[0].last_viewer_block_pos << _lods[0].map->get_block_size_pow2()).to_vec3(); out_pos = (_lods[0].last_viewer_block_pos << _lods[0].map->get_block_size_pow2()).to_vec3();
out_direction = Vector3(0, -1, 0); out_direction = Vector3(0, -1, 0);
@ -591,7 +573,6 @@ bool VoxelLodTerrain::check_block_mesh_updated(VoxelBlock *block) {
} }
void VoxelLodTerrain::send_block_data_requests() { void VoxelLodTerrain::send_block_data_requests() {
VoxelDataLoader::Input input; VoxelDataLoader::Input input;
Vector3 viewer_pos; Vector3 viewer_pos;
@ -665,7 +646,7 @@ void VoxelLodTerrain::_process() {
// TODO Could it actually be enough to have a rolling update on all blocks? // TODO Could it actually be enough to have a rolling update on all blocks?
// This should be the same distance relatively to each LOD // This should be the same distance relatively to each LOD
int block_region_extent = get_block_region_extent(); const int block_region_extent = get_block_region_extent();
// Ignore last lod because it can extend a little beyond due to the view distance setting. // Ignore last lod because it can extend a little beyond due to the view distance setting.
// Instead, those blocks are unloaded by the octree forest management. // Instead, those blocks are unloaded by the octree forest management.
@ -828,7 +809,6 @@ void VoxelLodTerrain::_process() {
// TODO Maintain a vector to make iteration faster? // TODO Maintain a vector to make iteration faster?
for (Map<Vector3i, OctreeItem>::Element *E = _lod_octrees.front(); E; E = E->next()) { for (Map<Vector3i, OctreeItem>::Element *E = _lod_octrees.front(); E; E = E->next()) {
VOXEL_PROFILE_SCOPE(profile_process_update_octrees_item); VOXEL_PROFILE_SCOPE(profile_process_update_octrees_item);
OctreeItem &item = E->value(); OctreeItem &item = E->value();
@ -836,7 +816,6 @@ void VoxelLodTerrain::_process() {
Vector3i block_offset_lod0 = block_pos_maxlod << (get_lod_count() - 1); Vector3i block_offset_lod0 = block_pos_maxlod << (get_lod_count() - 1);
struct OctreeActions { struct OctreeActions {
VoxelLodTerrain *self = nullptr; VoxelLodTerrain *self = nullptr;
Vector3i block_offset_lod0; Vector3i block_offset_lod0;
unsigned int blocked_count = 0; unsigned int blocked_count = 0;
@ -1076,12 +1055,10 @@ void VoxelLodTerrain::_process() {
input.exclusive_region_extent = get_block_region_extent(); input.exclusive_region_extent = get_block_region_extent();
for (int lod_index = 0; lod_index < get_lod_count(); ++lod_index) { for (int lod_index = 0; lod_index < get_lod_count(); ++lod_index) {
VOXEL_PROFILE_SCOPE(profile_process_send_mesh_updates_lod); VOXEL_PROFILE_SCOPE(profile_process_send_mesh_updates_lod);
Lod &lod = _lods[lod_index]; Lod &lod = _lods[lod_index];
for (unsigned int i = 0; i < lod.blocks_pending_update.size(); ++i) { for (unsigned int i = 0; i < lod.blocks_pending_update.size(); ++i) {
VOXEL_PROFILE_SCOPE(profile_process_send_mesh_updates_block); VOXEL_PROFILE_SCOPE(profile_process_send_mesh_updates_block);
Vector3i block_pos = lod.blocks_pending_update[i]; Vector3i block_pos = lod.blocks_pending_update[i];
@ -1140,7 +1117,6 @@ void VoxelLodTerrain::_process() {
_stats.updated_blocks = output.blocks.size(); _stats.updated_blocks = output.blocks.size();
for (int i = 0; i < output.blocks.size(); ++i) { for (int i = 0; i < output.blocks.size(); ++i) {
VOXEL_PROFILE_SCOPE(profile_process_receive_mesh_updates_block_schedule); VOXEL_PROFILE_SCOPE(profile_process_receive_mesh_updates_block_schedule);
const VoxelMeshUpdater::OutputBlock &ob = output.blocks[i]; const VoxelMeshUpdater::OutputBlock &ob = output.blocks[i];
@ -1162,7 +1138,6 @@ void VoxelLodTerrain::_process() {
// hopefully Vulkan will allow us to upload graphical resources without stalling rendering as they upload? // hopefully Vulkan will allow us to upload graphical resources without stalling rendering as they upload?
for (; queue_index < _blocks_pending_main_thread_update.size() && os.get_ticks_msec() < timeout; ++queue_index) { for (; queue_index < _blocks_pending_main_thread_update.size() && os.get_ticks_msec() < timeout; ++queue_index) {
VOXEL_PROFILE_SCOPE(profile_process_receive_mesh_updates_block_update); VOXEL_PROFILE_SCOPE(profile_process_receive_mesh_updates_block_update);
const VoxelMeshUpdater::OutputBlock &ob = _blocks_pending_main_thread_update[queue_index]; const VoxelMeshUpdater::OutputBlock &ob = _blocks_pending_main_thread_update[queue_index];
@ -1319,13 +1294,11 @@ void VoxelLodTerrain::flush_pending_lod_edits() {
namespace { namespace {
struct ScheduleSaveAction { struct ScheduleSaveAction {
std::vector<VoxelDataLoader::InputBlock> &blocks_to_save; std::vector<VoxelDataLoader::InputBlock> &blocks_to_save;
std::vector<Ref<ShaderMaterial> > &shader_materials; std::vector<Ref<ShaderMaterial> > &shader_materials;
bool with_copy; bool with_copy;
void operator()(VoxelBlock *block) { void operator()(VoxelBlock *block) {
Ref<ShaderMaterial> sm = block->get_shader_material(); Ref<ShaderMaterial> sm = block->get_shader_material();
if (sm.is_valid()) { if (sm.is_valid()) {
shader_materials.push_back(sm); shader_materials.push_back(sm);
@ -1524,6 +1497,32 @@ Dictionary VoxelLodTerrain::get_statistics() const {
return d; return d;
} }
void VoxelLodTerrain::set_run_stream_in_editor(bool enable) {
if (enable == _run_stream_in_editor) {
return;
}
_run_stream_in_editor = enable;
if (Engine::get_singleton()->is_editor_hint()) {
if (_run_stream_in_editor) {
_on_stream_params_changed();
} else {
// This is expected to block the main thread until the streaming thread is done.
stop_streamer();
}
}
}
bool VoxelLodTerrain::is_stream_running_in_editor() const {
return _run_stream_in_editor;
}
void VoxelLodTerrain::restart_stream() {
_on_stream_params_changed();
}
void VoxelLodTerrain::_b_save_modified_blocks() { void VoxelLodTerrain::_b_save_modified_blocks() {
save_all_modified_blocks(true); save_all_modified_blocks(true);
} }
@ -1662,12 +1661,15 @@ void VoxelLodTerrain::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_voxel_tool"), &VoxelLodTerrain::get_voxel_tool); ClassDB::bind_method(D_METHOD("get_voxel_tool"), &VoxelLodTerrain::get_voxel_tool);
ClassDB::bind_method(D_METHOD("save_modified_blocks"), &VoxelLodTerrain::_b_save_modified_blocks); ClassDB::bind_method(D_METHOD("save_modified_blocks"), &VoxelLodTerrain::_b_save_modified_blocks);
ClassDB::bind_method(D_METHOD("set_run_stream_in_editor"), &VoxelLodTerrain::set_run_stream_in_editor);
ClassDB::bind_method(D_METHOD("is_stream_running_in_editor"), &VoxelLodTerrain::is_stream_running_in_editor);
ClassDB::bind_method(D_METHOD("debug_raycast_block", "origin", "dir"), &VoxelLodTerrain::debug_raycast_block); ClassDB::bind_method(D_METHOD("debug_raycast_block", "origin", "dir"), &VoxelLodTerrain::debug_raycast_block);
ClassDB::bind_method(D_METHOD("debug_get_block_info", "block_pos", "lod"), &VoxelLodTerrain::debug_get_block_info); ClassDB::bind_method(D_METHOD("debug_get_block_info", "block_pos", "lod"), &VoxelLodTerrain::debug_get_block_info);
ClassDB::bind_method(D_METHOD("debug_get_octrees"), &VoxelLodTerrain::debug_get_octrees); ClassDB::bind_method(D_METHOD("debug_get_octrees"), &VoxelLodTerrain::debug_get_octrees);
ClassDB::bind_method(D_METHOD("debug_print_sdf_top_down", "center", "extents"), &VoxelLodTerrain::_b_debug_print_sdf_top_down); ClassDB::bind_method(D_METHOD("debug_print_sdf_top_down", "center", "extents"), &VoxelLodTerrain::_b_debug_print_sdf_top_down);
ClassDB::bind_method(D_METHOD("_on_stream_params_changed"), &VoxelLodTerrain::_on_stream_params_changed); //ClassDB::bind_method(D_METHOD("_on_stream_params_changed"), &VoxelLodTerrain::_on_stream_params_changed);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "VoxelStream"), "set_stream", "get_stream"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "VoxelStream"), "set_stream", "get_stream");
ADD_PROPERTY(PropertyInfo(Variant::INT, "view_distance"), "set_view_distance", "get_view_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "view_distance"), "set_view_distance", "get_view_distance");
@ -1677,4 +1679,6 @@ void VoxelLodTerrain::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_material", "get_material"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_material", "get_material");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_collisions"), "set_generate_collisions", "get_generate_collisions"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_collisions"), "set_generate_collisions", "get_generate_collisions");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_lod_count"), "set_collision_lod_count", "get_collision_lod_count"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_lod_count"), "set_collision_lod_count", "get_collision_lod_count");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "run_stream_in_editor"),
"set_run_stream_in_editor", "is_stream_running_in_editor");
} }

View File

@ -77,6 +77,11 @@ public:
Dictionary get_statistics() const; Dictionary get_statistics() const;
void set_run_stream_in_editor(bool enable);
bool is_stream_running_in_editor() const;
void restart_stream();
Array debug_raycast_block(Vector3 world_origin, Vector3 world_direction) const; Array debug_raycast_block(Vector3 world_origin, Vector3 world_direction) const;
Dictionary debug_get_block_info(Vector3 fbpos, int lod_index) const; Dictionary debug_get_block_info(Vector3 fbpos, int lod_index) const;
Array debug_get_octrees() const; Array debug_get_octrees() const;
@ -119,13 +124,14 @@ private:
void _b_save_modified_blocks(); void _b_save_modified_blocks();
Array _b_debug_print_sdf_top_down(Vector3 center, Vector3 extents) const; Array _b_debug_print_sdf_top_down(Vector3 center, Vector3 extents) const;
private:
struct OctreeItem { struct OctreeItem {
LodOctree octree; LodOctree octree;
}; };
// This terrain type is a sparse grid of octrees. // This terrain type is a sparse grid of octrees.
// Indexed by a grid coordinate whose step is the size of the highest-LOD block // Indexed by a grid coordinate whose step is the size of the highest-LOD block.
// This octree doesn't hold any data... hence bool. // Not using a pointer because Map storage is stable.
Map<Vector3i, OctreeItem> _lod_octrees; Map<Vector3i, OctreeItem> _lod_octrees;
Rect3i _last_octree_region_box; Rect3i _last_octree_region_box;
@ -168,6 +174,8 @@ private:
float _lod_split_scale = 0.f; float _lod_split_scale = 0.f;
unsigned int _view_distance_voxels = 512; unsigned int _view_distance_voxels = 512;
bool _run_stream_in_editor = true;
Stats _stats; Stats _stats;
}; };

View File

@ -27,8 +27,6 @@ VoxelTerrain::VoxelTerrain() {
_stream_thread = nullptr; _stream_thread = nullptr;
_block_updater = nullptr; _block_updater = nullptr;
_run_in_editor = false;
Ref<VoxelLibrary> library; Ref<VoxelLibrary> library;
library.instance(); library.instance();
set_voxel_library(library); set_voxel_library(library);
@ -53,11 +51,18 @@ VoxelTerrain::~VoxelTerrain() {
String VoxelTerrain::get_configuration_warning() const { String VoxelTerrain::get_configuration_warning() const {
if (_stream.is_valid()) { if (_stream.is_valid()) {
Ref<Script> script = _stream->get_script(); Ref<Script> script = _stream->get_script();
if (script.is_valid() && !script->is_tool()) { if (script.is_valid()) {
return TTR("The custom stream is not tool, the editor won't be able to use it."); if (script->is_tool()) {
// TODO This is very annoying. Probably needs an issue or proposal in Godot so we can handle this properly?
return TTR("Be careful! Don't edit your custom stream while it's running, "
"it can cause crashes. Turn off `run_stream_in_editor` before doing so.");
} else {
return TTR("The custom stream is not tool, the editor won't be able to use it.");
}
} }
if (!(_stream->get_used_channels_mask() & ((1 << VoxelBuffer::CHANNEL_TYPE) | (1 << VoxelBuffer::CHANNEL_SDF)))) { if (!(_stream->get_used_channels_mask() & ((1 << VoxelBuffer::CHANNEL_TYPE) | (1 << VoxelBuffer::CHANNEL_SDF)))) {
return TTR("VoxelTerrain supports only stream channels \"Type\" or \"Sdf\"."); return TTR("VoxelTerrain supports only stream channels \"Type\" or \"Sdf\", "
"but `get_used_channels_mask()` tells it's providing none of these.");
} }
} }
return String(); return String();
@ -98,17 +103,20 @@ void VoxelTerrain::set_stream(Ref<VoxelStream> p_stream) {
return; return;
} }
if (_stream.is_valid()) {
if (_stream->is_connected(CoreStringNames::get_singleton()->changed, this, "_on_stream_params_changed")) {
_stream->disconnect(CoreStringNames::get_singleton()->changed, this, "_on_stream_params_changed");
}
}
_stream = p_stream; _stream = p_stream;
#ifdef TOOLS_ENABLED
if (_stream.is_valid()) { if (_stream.is_valid()) {
_stream->connect(CoreStringNames::get_singleton()->changed, this, "_on_stream_params_changed"); if (Engine::get_singleton()->is_editor_hint()) {
if (_stream->has_script()) {
// Safety check. It's too easy to break threads by making a script reload.
// You can turn it back on, but be careful.
_run_stream_in_editor = false;
_change_notify();
}
}
} }
#endif
_on_stream_params_changed(); _on_stream_params_changed();
} }
@ -131,20 +139,7 @@ void VoxelTerrain::set_block_size_po2(unsigned int p_block_size_po2) {
return; return;
} }
bool updater_was_running = _block_updater != nullptr; _on_stream_params_changed();
stop_streamer();
stop_updater();
reset_map();
_set_block_size_po2(p_block_size_po2);
if (_stream.is_valid()) {
start_streamer();
}
if (updater_was_running) {
start_updater();
}
} }
void VoxelTerrain::_set_block_size_po2(int p_block_size_po2) { void VoxelTerrain::_set_block_size_po2(int p_block_size_po2) {
@ -155,6 +150,10 @@ unsigned int VoxelTerrain::get_block_size_pow2() const {
return _map->get_block_size_pow2(); return _map->get_block_size_pow2();
} }
void VoxelTerrain::restart_stream() {
_on_stream_params_changed();
}
void VoxelTerrain::_on_stream_params_changed() { void VoxelTerrain::_on_stream_params_changed() {
stop_streamer(); stop_streamer();
@ -163,12 +162,14 @@ void VoxelTerrain::_on_stream_params_changed() {
Ref<VoxelStreamFile> file_stream = _stream; Ref<VoxelStreamFile> file_stream = _stream;
if (file_stream.is_valid()) { if (file_stream.is_valid()) {
int stream_block_size_po2 = file_stream->get_block_size_po2(); int stream_block_size_po2 = file_stream->get_block_size_po2();
_set_block_size_po2(stream_block_size_po2); _set_block_size_po2(stream_block_size_po2);
} }
if (_stream.is_valid()) { // The whole map might change, so regenerate it
reset_map();
if (_stream.is_valid() && (!Engine::get_singleton()->is_editor_hint() || _run_stream_in_editor)) {
start_streamer(); start_streamer();
} }
if (was_updater_running) { if (was_updater_running) {
@ -176,10 +177,6 @@ void VoxelTerrain::_on_stream_params_changed() {
} }
update_configuration_warning(); update_configuration_warning();
// The whole map might change, so make all area dirty
// TODO Actually, we should regenerate the whole map, not just update all its blocks
make_all_view_dirty_deferred();
} }
Ref<VoxelLibrary> VoxelTerrain::get_voxel_library() const { Ref<VoxelLibrary> VoxelTerrain::get_voxel_library() const {
@ -370,6 +367,7 @@ Dictionary VoxelTerrain::get_statistics() const {
//} //}
void VoxelTerrain::make_all_view_dirty_deferred() { void VoxelTerrain::make_all_view_dirty_deferred() {
// We do this because we query blocks only when the viewer area changes.
// This trick will regenerate all chunks in view, according to the view distance found during block updates. // 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 // 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. // always use an up-to-date view distance, which is not necessarily loaded yet on initialization.
@ -439,10 +437,16 @@ void VoxelTerrain::stop_streamer() {
} }
void VoxelTerrain::reset_map() { void VoxelTerrain::reset_map() {
// Don't reset while streaming, the result can be dirty
CRASH_COND(_stream_thread != nullptr);
_map->for_all_blocks([this](VoxelBlock *block) { _map->for_all_blocks([this](VoxelBlock *block) {
emit_block_unloaded(block); emit_block_unloaded(block);
}); });
_map->create(get_block_size_pow2(), 0); _map->create(get_block_size_pow2(), 0);
// To force queries to happen again, because we only listen for viewer position changes
make_all_view_dirty_deferred();
} }
inline int get_border_index(int x, int max) { inline int get_border_index(int x, int max) {
@ -639,9 +643,7 @@ void VoxelTerrain::_notification(int p_what) {
break; break;
case NOTIFICATION_PROCESS: case NOTIFICATION_PROCESS:
if (!Engine::get_singleton()->is_editor_hint() || _run_in_editor) { _process();
_process();
}
break; break;
case NOTIFICATION_EXIT_TREE: case NOTIFICATION_EXIT_TREE:
@ -806,7 +808,7 @@ void VoxelTerrain::_process() {
_last_view_distance_blocks = _view_distance_blocks; _last_view_distance_blocks = _view_distance_blocks;
_last_viewer_block_pos = viewer_block_pos; _last_viewer_block_pos = viewer_block_pos;
// It's possible the user didn't set a stream yet // It's possible the user didn't set a stream yet, or it is turned off
if (_stream_thread != nullptr) { if (_stream_thread != nullptr) {
send_block_data_requests(); send_block_data_requests();
} }
@ -1097,6 +1099,28 @@ Ref<VoxelTool> VoxelTerrain::get_voxel_tool() {
return vt; return vt;
} }
void VoxelTerrain::set_run_stream_in_editor(bool enable) {
if (enable == _run_stream_in_editor) {
return;
}
_run_stream_in_editor = enable;
if (Engine::get_singleton()->is_editor_hint()) {
if (_run_stream_in_editor) {
_on_stream_params_changed();
} else {
// This is expected to block the main thread until the streaming thread is done.
stop_streamer();
}
}
}
bool VoxelTerrain::is_stream_running_in_editor() const {
return _run_stream_in_editor;
}
Vector3 VoxelTerrain::_b_voxel_to_block(Vector3 pos) { Vector3 VoxelTerrain::_b_voxel_to_block(Vector3 pos) {
return Vector3i(_map->voxel_to_block(pos)).to_vec3(); return Vector3i(_map->voxel_to_block(pos)).to_vec3();
} }
@ -1153,7 +1177,10 @@ void VoxelTerrain::_bind_methods() {
ClassDB::bind_method(D_METHOD("save_modified_blocks"), &VoxelTerrain::_b_save_modified_blocks); ClassDB::bind_method(D_METHOD("save_modified_blocks"), &VoxelTerrain::_b_save_modified_blocks);
ClassDB::bind_method(D_METHOD("save_block"), &VoxelTerrain::_b_save_block); ClassDB::bind_method(D_METHOD("save_block"), &VoxelTerrain::_b_save_block);
ClassDB::bind_method(D_METHOD("_on_stream_params_changed"), &VoxelTerrain::_on_stream_params_changed); ClassDB::bind_method(D_METHOD("set_run_stream_in_editor"), &VoxelTerrain::set_run_stream_in_editor);
ClassDB::bind_method(D_METHOD("is_stream_running_in_editor"), &VoxelTerrain::is_stream_running_in_editor);
//ClassDB::bind_method(D_METHOD("_on_stream_params_changed"), &VoxelTerrain::_on_stream_params_changed);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "VoxelStream"), ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "VoxelStream"),
"set_stream", "get_stream"); "set_stream", "get_stream");
@ -1163,6 +1190,8 @@ void VoxelTerrain::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewer_path"), "set_viewer_path", "get_viewer_path"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewer_path"), "set_viewer_path", "get_viewer_path");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_collisions"), ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_collisions"),
"set_generate_collisions", "get_generate_collisions"); "set_generate_collisions", "get_generate_collisions");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "run_stream_in_editor"),
"set_run_stream_in_editor", "is_stream_running_in_editor");
ADD_SIGNAL(MethodInfo(VoxelStringNames::get_singleton()->block_loaded, ADD_SIGNAL(MethodInfo(VoxelStringNames::get_singleton()->block_loaded,
PropertyInfo(Variant::VECTOR3, "position"), PropertyInfo(Variant::VECTOR3, "position"),

View File

@ -1,10 +1,9 @@
#ifndef VOXEL_TERRAIN_H #ifndef VOXEL_TERRAIN_H
#define VOXEL_TERRAIN_H #define VOXEL_TERRAIN_H
#include "../math/rect3i.h"
#include "../math/vector3i.h"
#include "../util/zprofiling.h" #include "../util/zprofiling.h"
#include "voxel_data_loader.h" #include "voxel_data_loader.h"
#include "voxel_map.h"
#include "voxel_mesh_updater.h" #include "voxel_mesh_updater.h"
#include <scene/3d/spatial.h> #include <scene/3d/spatial.h>
@ -55,6 +54,11 @@ public:
Ref<VoxelMap> get_storage() const { return _map; } Ref<VoxelMap> get_storage() const { return _map; }
Ref<VoxelTool> get_voxel_tool(); Ref<VoxelTool> get_voxel_tool();
void set_run_stream_in_editor(bool enable);
bool is_stream_running_in_editor() const;
void restart_stream();
struct Stats { struct Stats {
VoxelMeshUpdater::Stats updater; VoxelMeshUpdater::Stats updater;
VoxelDataLoader::Stats stream; VoxelDataLoader::Stats stream;
@ -135,7 +139,7 @@ private:
int _last_view_distance_blocks; int _last_view_distance_blocks;
bool _generate_collisions = true; bool _generate_collisions = true;
bool _run_in_editor; bool _run_stream_in_editor = true;
Ref<Material> _materials[VoxelMesherBlocky::MAX_MATERIALS]; Ref<Material> _materials[VoxelMesherBlocky::MAX_MATERIALS];