Show bounds and octrees of VoxelLodTerrain with debug drawing

master
Marc Gilleron 2020-10-24 00:08:14 +01:00
parent e70f87e4a7
commit d158a92f57
13 changed files with 312 additions and 75 deletions

1
SCsub
View File

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

View File

@ -69,6 +69,11 @@ void VoxelTerrainEditorPlugin::set_node(Node *node) {
// Also moving the node around in the tree triggers exit/enter so have to listen for both.
_node->disconnect("tree_entered", this, "_on_terrain_tree_entered");
_node->disconnect("tree_exited", this, "_on_terrain_tree_exited");
VoxelLodTerrain *vlt = Object::cast_to<VoxelLodTerrain>(_node);
if (vlt != nullptr) {
vlt->set_show_gizmos(false);
}
}
_node = node;
@ -76,12 +81,27 @@ void VoxelTerrainEditorPlugin::set_node(Node *node) {
if (_node != nullptr) {
_node->connect("tree_entered", this, "_on_terrain_tree_entered", varray(_node));
_node->connect("tree_exited", this, "_on_terrain_tree_exited", varray(_node));
VoxelLodTerrain *vlt = Object::cast_to<VoxelLodTerrain>(_node);
if (vlt != nullptr) {
vlt->set_show_gizmos(true);
}
}
}
void VoxelTerrainEditorPlugin::make_visible(bool visible) {
_restart_stream_button->set_visible(visible);
// Can't use `make_visible(false)` to reset our reference to the node,
if (_node != nullptr) {
VoxelLodTerrain *vlt = Object::cast_to<VoxelLodTerrain>(_node);
if (vlt != nullptr) {
vlt->set_show_gizmos(visible);
}
}
// TODO There are deselection problems I cannot fix cleanly!
// Can't use `make_visible(false)` to reset our reference to the node or reset gizmos,
// because of https://github.com/godotengine/godot/issues/40166
// So we'll need to check if _node is null all over the place
}
@ -99,11 +119,11 @@ void VoxelTerrainEditorPlugin::_on_restart_stream_button_pressed() {
}
void VoxelTerrainEditorPlugin::_on_terrain_tree_entered(Node *node) {
// If the node exited the tree because it was deleted, signals we connected should automatically disconnect.
_node = node;
}
void VoxelTerrainEditorPlugin::_on_terrain_tree_exited(Node *node) {
// If the node exited the tree because it was deleted, signals we connected should automatically disconnect.
_node = nullptr;
}

157
editor/voxel_debug.cpp Normal file
View File

@ -0,0 +1,157 @@
#include "voxel_debug.h"
#include "../util/direct_mesh_instance.h"
#include "../util/fixed_array.h"
#include "../util/utility.h"
#include <scene/resources/mesh.h>
namespace VoxelDebug {
FixedArray<Ref<Mesh>, ID_COUNT> g_wirecubes;
bool g_finalized = false;
template <typename T>
void raw_copy_to(PoolVector<T> &dst, const T *src, unsigned int count) {
dst.resize(count);
PoolVector<T>::Write w = dst.write();
memcpy(w.ptr(), src, count * sizeof(T));
}
static Color get_color(ColorID id) {
switch (id) {
case ID_VOXEL_BOUNDS:
return Color(1, 1, 1);
case ID_OCTREE_BOUNDS:
return Color(0.5, 0.5, 0.5);
default:
CRASH_NOW_MSG("Unexpected index");
}
return Color();
}
Ref<Mesh> get_wirecube(ColorID id) {
CRASH_COND(g_finalized);
Ref<Mesh> &wirecube = g_wirecubes[id];
if (wirecube.is_null()) {
const Vector3 positions_raw[] = {
Vector3(0, 0, 0),
Vector3(1, 0, 0),
Vector3(1, 0, 1),
Vector3(0, 0, 1),
Vector3(0, 1, 0),
Vector3(1, 1, 0),
Vector3(1, 1, 1),
Vector3(0, 1, 1)
};
PoolVector3Array positions;
raw_copy_to(positions, positions_raw, 8);
Color white(1.0, 1.0, 1.0);
PoolColorArray colors;
colors.resize(positions.size());
{
PoolColorArray::Write w = colors.write();
for (int i = 0; i < colors.size(); ++i) {
w[i] = white;
}
}
const int indices_raw[] = {
0, 1,
1, 2,
2, 3,
3, 0,
4, 5,
5, 6,
6, 7,
7, 4,
0, 4,
1, 5,
2, 6,
3, 7
};
PoolIntArray indices;
raw_copy_to(indices, indices_raw, 24);
Array arrays;
arrays.resize(Mesh::ARRAY_MAX);
arrays[Mesh::ARRAY_VERTEX] = positions;
arrays[Mesh::ARRAY_COLOR] = colors;
arrays[Mesh::ARRAY_INDEX] = indices;
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, arrays);
Ref<SpatialMaterial> mat;
mat.instance();
mat->set_albedo(get_color(id));
mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
mesh->surface_set_material(0, mat);
wirecube = mesh;
}
return wirecube;
}
void free_resources() {
for (unsigned int i = 0; i < g_wirecubes.size(); ++i) {
g_wirecubes[i].unref();
}
g_finalized = true;
}
DebugRenderer::~DebugRenderer() {
clear();
}
void DebugRenderer::clear() {
for (auto it = _mesh_instances.begin(); it != _mesh_instances.end(); ++it) {
memdelete(*it);
}
_mesh_instances.clear();
}
void DebugRenderer::set_world(World *world) {
_world = world;
for (auto it = _mesh_instances.begin(); it != _mesh_instances.end(); ++it) {
(*it)->set_world(world);
}
}
void DebugRenderer::begin() {
CRASH_COND(_inside_block);
CRASH_COND(_world == nullptr);
_current = 0;
_inside_block = true;
}
void DebugRenderer::draw_box(Transform t, ColorID color) {
DirectMeshInstance *mi;
if (_current >= _mesh_instances.size()) {
mi = memnew(DirectMeshInstance);
mi->create();
mi->set_world(_world);
_mesh_instances.push_back(mi);
} else {
mi = _mesh_instances[_current];
}
mi->set_mesh(get_wirecube(color));
mi->set_transform(t);
++_current;
}
void DebugRenderer::end() {
CRASH_COND(!_inside_block);
for (unsigned int i = _current; i < _mesh_instances.size(); ++i) {
DirectMeshInstance *mi = _mesh_instances[i];
mi->set_visible(false);
}
_inside_block = false;
}
} // namespace VoxelDebug

42
editor/voxel_debug.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef VOXEL_DEBUG_H
#define VOXEL_DEBUG_H
#include <core/reference.h>
#include <vector>
class Mesh;
class DirectMeshInstance;
class World;
namespace VoxelDebug {
enum ColorID {
ID_VOXEL_BOUNDS = 0,
ID_OCTREE_BOUNDS,
ID_COUNT
};
Ref<Mesh> get_wirecube(ColorID id);
void free_resources();
class DebugRenderer {
public:
~DebugRenderer();
void set_world(World *world);
void begin();
void draw_box(Transform t, ColorID color);
void end();
void clear();
private:
std::vector<DirectMeshInstance *> _mesh_instances;
unsigned int _current = 0;
bool _inside_block = false;
World *_world = nullptr;
};
} // namespace VoxelDebug
#endif // VOXEL_DEBUG_H

View File

@ -695,7 +695,8 @@ float VoxelGraphRuntime::generate_single(const Vector3i &position) {
case VoxelGeneratorGraph::NODE_IMAGE_2D: {
const PNodeImage2D &n = read<PNodeImage2D>(_program, pc);
// TODO Not great, but in Godot 4.0 we won't need to lock anymore. Otherwise, need to do it in a pre-run and post-run
// TODO Not great, but in Godot 4.0 we won't need to lock anymore.
// Otherwise, need to do it in a pre-run and post-run
n.p_image->lock();
memory[n.a_out] = get_pixel_repeat(*n.p_image, memory[n.a_x], memory[n.a_y]);
n.p_image->unlock();

View File

@ -200,8 +200,8 @@ public:
inline Rect3i downscaled(int step_size) const {
Rect3i o;
o.pos = pos.udiv(step_size);
Vector3i max_pos = (pos + size - Vector3i(1)).udiv(step_size);
o.pos = pos.floordiv(step_size);
Vector3i max_pos = (pos + size - Vector3i(1)).floordiv(step_size);
o.size = max_pos - o.pos + Vector3i(1);
return o;
}

View File

@ -134,10 +134,6 @@ struct Vector3i {
::sort_min_max(a.z, b.z);
}
inline Vector3i udiv(int d) const {
return Vector3i(::udiv(x, d), ::udiv(y, d), ::udiv(z, d));
}
inline Vector3i udiv(const Vector3i d) const {
return Vector3i(::udiv(x, d.x), ::udiv(y, d.y), ::udiv(z, d.z));
}

View File

@ -32,6 +32,10 @@
#include "voxel_string_names.h"
#include <core/engine.h>
#ifdef TOOLS_ENABLED
#include "editor/voxel_debug.h"
#endif
void register_voxel_types() {
VoxelMemoryPool::create_singleton();
VoxelStringNames::create_singleton();
@ -94,8 +98,6 @@ void register_voxel_types() {
PRINT_VERBOSE(String("Size of VoxelBlock: {0}").format(varray((int)sizeof(VoxelBlock))));
#ifdef TOOLS_ENABLED
VoxelDebug::create_debug_box_mesh();
EditorPlugins::add_by_type<VoxelGraphEditorPlugin>();
EditorPlugins::add_by_type<VoxelTerrainEditorPlugin>();
#endif
@ -122,7 +124,7 @@ void unregister_voxel_types() {
// TODO No remove?
#ifdef TOOLS_ENABLED
VoxelDebug::free_debug_box_mesh();
VoxelDebug::free_resources();
// TODO Seriously, no remove?
//EditorPlugins::remove_by_type<VoxelGraphEditorPlugin>();

View File

@ -440,6 +440,11 @@ void VoxelLodTerrain::_notification(int p_what) {
});
}
}
#ifdef TOOLS_ENABLED
if (is_showing_gizmos()) {
_debug_renderer.set_world(is_visible_in_tree() ? world : nullptr);
}
#endif
} break;
case NOTIFICATION_EXIT_WORLD: {
@ -450,6 +455,9 @@ void VoxelLodTerrain::_notification(int p_what) {
});
}
}
#ifdef TOOLS_ENABLED
_debug_renderer.set_world(nullptr);
#endif
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
@ -461,6 +469,11 @@ void VoxelLodTerrain::_notification(int p_what) {
});
}
}
#ifdef TOOLS_ENABLED
if (is_showing_gizmos()) {
_debug_renderer.set_world(is_visible_in_tree() ? *get_world() : nullptr);
}
#endif
} break;
// TODO Listen for transform changes
@ -1154,6 +1167,12 @@ void VoxelLodTerrain::_process() {
}
_stats.time_process_update_responses = profiling_clock.restart();
#ifdef TOOLS_ENABLED
if (is_showing_gizmos()) {
update_gizmos();
}
#endif
}
void VoxelLodTerrain::flush_pending_lod_edits() {
@ -1576,6 +1595,45 @@ Array VoxelLodTerrain::debug_get_octrees() const {
return positions;
}
#ifdef TOOLS_ENABLED
void VoxelLodTerrain::update_gizmos() {
VOXEL_PROFILE_SCOPE();
VoxelDebug::DebugRenderer &dr = _debug_renderer;
dr.begin();
const int octree_size = get_block_size() << (get_lod_count() - 1);
for (Map<Vector3i, OctreeItem>::Element *E = _lod_octrees.front(); E; E = E->next()) {
Transform t = get_global_transform();
t.scale(Vector3(octree_size, octree_size, octree_size));
t.translate(E->key().to_vec3());
dr.draw_box(t, VoxelDebug::ID_OCTREE_BOUNDS);
}
const float bounds_in_voxels_len = _bounds_in_voxels.size.length();
if (bounds_in_voxels_len < 10000) {
Transform t = get_global_transform();
Vector3 margin = Vector3(1, 1, 1) * bounds_in_voxels_len * 0.0025f;
t.scale(_bounds_in_voxels.size.to_vec3() + margin * 2.f);
t.origin = _bounds_in_voxels.pos.to_vec3() - margin;
dr.draw_box(t, VoxelDebug::ID_VOXEL_BOUNDS);
}
dr.end();
}
void VoxelLodTerrain::set_show_gizmos(bool enable) {
_show_gizmos_enabled = enable;
if (_show_gizmos_enabled) {
_debug_renderer.set_world(is_visible_in_tree() ? *get_world() : nullptr);
} else {
_debug_renderer.clear();
}
}
#endif
Array VoxelLodTerrain::_b_debug_print_sdf_top_down(Vector3 center, Vector3 extents) const {
Array image_array;
image_array.resize(get_lod_count());

View File

@ -2,10 +2,15 @@
#define VOXEL_LOD_TERRAIN_HPP
#include "../server/voxel_server.h"
#include "../util/direct_mesh_instance.h"
#include "lod_octree.h"
#include <core/set.h>
#include <scene/3d/spatial.h>
#ifdef TOOLS_ENABLED
#include "../editor/voxel_debug.h"
#endif
class VoxelMap;
class VoxelTool;
class VoxelStream;
@ -53,6 +58,7 @@ public:
unsigned int get_block_size_pow2() const;
void set_block_size_po2(unsigned int p_block_size_po2);
unsigned int get_block_size() const;
// These must be called after an edit
void post_edit_area(Rect3i p_box);
@ -92,6 +98,11 @@ public:
Dictionary debug_get_block_info(Vector3 fbpos, int lod_index) const;
Array debug_get_octrees() const;
#ifdef TOOLS_ENABLED
void set_show_gizmos(bool enable);
bool is_showing_gizmos() const { return _show_gizmos_enabled; }
#endif
protected:
static void _bind_methods();
@ -99,7 +110,6 @@ protected:
void _process();
private:
unsigned int get_block_size() const;
Spatial *get_viewer() const;
void immerge_block(Vector3i block_pos, int lod_index);
@ -132,11 +142,15 @@ private:
AABB _b_get_voxel_bounds() const;
Array _b_debug_print_sdf_top_down(Vector3 center, Vector3 extents) const;
private:
struct OctreeItem {
LodOctree octree;
};
#ifdef TOOLS_ENABLED
void update_gizmos();
#endif
private:
// This terrain type is a sparse grid of octrees.
// Indexed by a grid coordinate whose step is the size of the highest-LOD block.
// Not using a pointer because Map storage is stable.
@ -188,6 +202,10 @@ private:
unsigned int _view_distance_voxels = 512;
bool _run_stream_in_editor = true;
#ifdef TOOLS_ENABLED
bool _show_gizmos_enabled = false;
VoxelDebug::DebugRenderer _debug_renderer;
#endif
Stats _stats;
};

View File

@ -48,7 +48,9 @@ void DirectMeshInstance::set_mesh(Ref<Mesh> mesh) {
ERR_FAIL_COND(!_mesh_instance.is_valid());
VisualServer &vs = *VisualServer::get_singleton();
if (mesh.is_valid()) {
vs.instance_set_base(_mesh_instance, mesh->get_rid());
if (_mesh != mesh) {
vs.instance_set_base(_mesh_instance, mesh->get_rid());
}
} else {
vs.instance_set_base(_mesh_instance, RID());
}

View File

@ -51,55 +51,3 @@ bool try_call_script(const Object *obj, StringName method_name, const Variant **
return true;
}
#if TOOLS_ENABLED
namespace VoxelDebug {
Ref<Mesh> g_debug_box_mesh;
void create_debug_box_mesh() {
PoolVector3Array positions;
positions.resize(8);
{
PoolVector3Array::Write w = positions.write();
for (int i = 0; i < positions.size(); ++i) {
w[i] = Cube::g_corner_position[i];
}
}
PoolIntArray indices;
indices.resize(Cube::EDGE_COUNT * 2);
{
PoolIntArray::Write w = indices.write();
int j = 0;
for (int i = 0; i < Cube::EDGE_COUNT; ++i) {
w[j++] = Cube::g_edge_corners[i][0];
w[j++] = Cube::g_edge_corners[i][1];
}
}
Array arrays;
arrays.resize(Mesh::ARRAY_MAX);
arrays[Mesh::ARRAY_VERTEX] = positions;
arrays[Mesh::ARRAY_INDEX] = indices;
Ref<ArrayMesh> mesh;
mesh.instance();
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, arrays);
Ref<SpatialMaterial> mat;
mat.instance();
mat->set_albedo(Color(0, 1, 0));
mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
mesh->surface_set_material(0, mat);
g_debug_box_mesh = mesh;
}
void free_debug_box_mesh() {
g_debug_box_mesh.unref();
}
Ref<Mesh> get_debug_box_mesh() {
return g_debug_box_mesh;
}
} // namespace VoxelDebug
#endif

View File

@ -229,12 +229,4 @@ inline bool try_call_script(const Object *obj, StringName method_name, Variant a
return try_call_script(obj, method_name, args, 3, out_ret);
}
#if TOOLS_ENABLED
namespace VoxelDebug {
void create_debug_box_mesh();
void free_debug_box_mesh();
Ref<Mesh> get_debug_box_mesh();
} // namespace VoxelDebug
#endif
#endif // HEADER_VOXEL_UTILITY_H