VoxelInstancer supports VoxelTerrain, although Godot 4 multimeshes are buggy

See https://github.com/godotengine/godot/issues/56357
master
Marc Gilleron 2022-04-09 20:40:03 +01:00
parent 219c7bfda3
commit 19a69b059e
9 changed files with 236 additions and 52 deletions

View File

@ -27,6 +27,7 @@ Godot 4 is required from this version.
- `VoxelGeneratorGraph`: added math expression node
- `VoxelGeneratorGraph`: added Pow and Powi nodes
- `VoxelGeneratorGraph`: Clamp now accepts min and max as inputs. For the version with constant parameters, use ClampC (might be faster in the current state of things).
- `VoxelInstancer`: Added support for `VoxelTerrain`. This means only LOD0 works, but mesh-LODs should work.
- Smooth voxels
- SDF data is now encoded with `inorm8` and `inorm16`, instead of an arbitrary version of `unorm8` and `unorm16`. Migration code is in place to load old save files, but *do a backup before running your project with the new version*.

View File

@ -9,6 +9,7 @@
#include "../../util/macros.h"
#include "../../util/profiling.h"
#include "../../util/profiling_clock.h"
#include "../instancing/voxel_instancer.h"
#include "../voxel_data_block_enter_info.h"
#include <core/config/engine.h>
@ -217,6 +218,13 @@ void VoxelTerrain::set_mesh_block_size(unsigned int mesh_block_size) {
_mesh_block_size_po2 = po2;
if (_instancer != nullptr) {
VoxelInstancer &instancer = *_instancer;
_mesh_map.for_each_block([&instancer](VoxelMeshBlockVT &block) { //
instancer.on_mesh_block_exit(block.position, 0);
});
}
// Unload all mesh blocks regardless of refcount
_mesh_map.clear();
@ -356,6 +364,10 @@ unsigned int VoxelTerrain::get_max_view_distance() const {
void VoxelTerrain::set_max_view_distance(unsigned int distance_in_voxels) {
ERR_FAIL_COND(distance_in_voxels < 0);
_max_view_distance_voxels = distance_in_voxels;
if (_instancer != nullptr) {
_instancer->set_mesh_lod_distance(_max_view_distance_voxels);
}
}
void VoxelTerrain::set_block_enter_notification_enabled(bool enable) {
@ -401,8 +413,9 @@ void VoxelTerrain::set_material(unsigned int id, Ref<Material> material) {
_mesh_map.for_each_block([material, old_material](VoxelMeshBlockVT &block) {
Ref<Mesh> mesh = block.get_mesh();
if (mesh.is_valid()) {
// We can't just assign by material index because some meshes don't use all materials of the terrain,
// therefore they don't have as many surfaces. So we have to find which surfaces use the old material.
// We can't just assign by material index because some meshes don't use all materials of the
// terrain, therefore they don't have as many surfaces. So we have to find which surfaces use the
// old material.
for (int surface_index = 0; surface_index < mesh->get_surface_count(); ++surface_index) {
if (mesh->surface_get_material(surface_index) == old_material) {
mesh->surface_set_material(surface_index, material);
@ -640,16 +653,25 @@ void VoxelTerrain::unload_mesh_block(Vector3i bpos) {
_mesh_map.remove_block(bpos, [&blocks_pending_update](const VoxelMeshBlockVT &block) {
if (block.get_mesh_state() == VoxelMeshBlockVT::MESH_UPDATE_NOT_SENT) {
// That block was in the list of blocks to update later in the process loop, we'll need to unregister it.
// We expect that block to be in that list. If it isn't, something wrong happened with its state.
// That block was in the list of blocks to update later in the process loop, we'll need to unregister
// it. We expect that block to be in that list. If it isn't, something wrong happened with its state.
ERR_FAIL_COND(!unordered_remove_value(blocks_pending_update, block.position));
}
});
if (_instancer != nullptr) {
_instancer->on_mesh_block_exit(bpos, 0);
}
}
void VoxelTerrain::save_all_modified_blocks(bool with_copy) {
// That may cause a stutter, so should be used when the player won't notice
_data_map.for_each_block(ScheduleSaveAction{ _blocks_to_save, with_copy });
if (_stream.is_valid() && _instancer != nullptr && _stream->supports_instance_blocks()) {
_instancer->save_all_modified_blocks();
}
// And flush immediately
send_block_data_requests();
}
@ -658,6 +680,42 @@ const VoxelTerrain::Stats &VoxelTerrain::get_stats() const {
return _stats;
}
void VoxelTerrain::set_instancer(VoxelInstancer *instancer) {
if (_instancer != nullptr && instancer != nullptr) {
ERR_FAIL_COND_MSG(_instancer != nullptr, "No more than one VoxelInstancer per terrain");
}
_instancer = instancer;
}
void VoxelTerrain::get_meshed_block_positions(std::vector<Vector3i> &out_positions) const {
_mesh_map.for_each_block([&out_positions](const VoxelMeshBlock &mesh_block) {
if (mesh_block.has_mesh()) {
out_positions.push_back(mesh_block.position);
}
});
}
// This function is primarily intented for editor use cases at the moment.
// It will be slower than using the instancing generation events,
// because it has to query VisualServer, which then allocates and decodes vertex buffers (assuming they are cached).
Array VoxelTerrain::get_mesh_block_surface(Vector3i block_pos) const {
ZN_PROFILE_SCOPE();
Ref<Mesh> mesh;
{
const VoxelMeshBlockVT *block = _mesh_map.get_block(block_pos);
if (block != nullptr) {
mesh = block->get_mesh();
}
}
if (mesh.is_valid()) {
return mesh->surface_get_arrays(0);
}
return Array();
}
Dictionary VoxelTerrain::_b_get_statistics() const {
Dictionary d;
@ -811,6 +869,10 @@ void VoxelTerrain::post_edit_area(Box3i box_in_voxels) {
}
try_schedule_mesh_update_from_data(box_in_voxels);
if (_instancer != nullptr) {
_instancer->on_area_edited(box_in_voxels);
}
}
void VoxelTerrain::_notification(int p_what) {
@ -887,7 +949,7 @@ void VoxelTerrain::send_block_data_requests() {
for (size_t i = 0; i < _blocks_pending_load.size(); ++i) {
const Vector3i block_pos = _blocks_pending_load[i];
// TODO Batch request
VoxelServer::get_singleton().request_block_load(_volume_id, block_pos, 0, false);
VoxelServer::get_singleton().request_block_load(_volume_id, block_pos, 0, _instancer != nullptr);
}
// Blocks to save
@ -1275,11 +1337,15 @@ void VoxelTerrain::apply_data_block_response(VoxelServer::BlockDataOutput &ob) {
// if (stream_enabled) {
// send_block_data_requests();
// }
if (_instancer != nullptr && ob.instances != nullptr) {
_instancer->on_data_block_loaded(ob.position, ob.lod, std::move(ob.instances));
}
}
// Sets voxel data of a block, discarding existing data if any.
// If the given block coordinates are not inside any viewer's area, this function won't do anything and return false.
// If a block is already loading or generating at this position, it will be cancelled.
// If the given block coordinates are not inside any viewer's area, this function won't do anything and return
// false. If a block is already loading or generating at this position, it will be cancelled.
bool VoxelTerrain::try_set_block_data(Vector3i position, std::shared_ptr<VoxelBufferInternal> &voxel_data) {
ZN_PROFILE_SCOPE();
ERR_FAIL_COND_V(voxel_data == nullptr, false);
@ -1301,8 +1367,8 @@ bool VoxelTerrain::try_set_block_data(Vector3i position, std::shared_ptr<VoxelBu
if (refcount.get() == 0) {
// Actually, this block is not even in range. So we may ignore it.
// If we don't want this behavior, we could introduce a fake viewer that adds a reference to all blocks in this
// volume as long as it is enabled?
// If we don't want this behavior, we could introduce a fake viewer that adds a reference to all blocks in
// this volume as long as it is enabled?
return false;
}
@ -1423,7 +1489,6 @@ void VoxelTerrain::apply_mesh_update(const VoxelServer::BlockMeshOutput &ob) {
}
Ref<ArrayMesh> mesh;
mesh.instantiate();
//need to put both blocky and smooth surfaces into one list
std::vector<Array> collidable_surfaces;
@ -1442,17 +1507,33 @@ void VoxelTerrain::apply_mesh_update(const VoxelServer::BlockMeshOutput &ob) {
collidable_surfaces.push_back(surface);
if (mesh.is_null()) {
mesh.instantiate();
}
mesh->add_surface_from_arrays(
ob.surfaces.primitive_type, surface, Array(), Dictionary(), ob.surfaces.mesh_flags);
mesh->surface_set_material(surface_index, _materials[i]);
++surface_index;
}
if (is_mesh_empty(**mesh)) {
if (mesh.is_valid() && is_mesh_empty(**mesh)) {
mesh = Ref<Mesh>();
collidable_surfaces.clear();
}
if (_instancer != nullptr) {
if (mesh.is_null() && block != nullptr) {
// No surface anymore in this block
_instancer->on_mesh_block_exit(ob.position, ob.lod);
}
if (ob.surfaces.surfaces.size() > 0 && mesh.is_valid() && !block->has_mesh()) {
// TODO The mesh could come from an edited region!
// We would have to know if specific voxels got edited, or different from the generator
_instancer->on_mesh_block_enter(ob.position, ob.lod, ob.surfaces.surfaces[0]);
}
}
const bool gen_collisions = _generate_collisions && block->collision_viewers.get() > 0;
block->set_mesh(mesh, DirectMeshInstance::GIMode(get_gi_mode()));

View File

@ -14,6 +14,7 @@
namespace zylann::voxel {
class VoxelTool;
class VoxelInstancer;
// Infinite paged terrain made of voxel blocks all with the same level of detail.
// Voxels are polygonized around the viewer by distance in a large cubic space.
@ -86,7 +87,7 @@ public:
return _data_map;
}
Ref<VoxelTool> get_voxel_tool();
Ref<VoxelTool> get_voxel_tool() override;
// Creates or overrides whatever block data there is at the given position.
// The use case is multiplayer, client-side.
@ -129,6 +130,16 @@ public:
Vector3i position;
};
// Internal
void set_instancer(VoxelInstancer *instancer);
void get_meshed_block_positions(std::vector<Vector3i> &out_positions) const;
Array get_mesh_block_surface(Vector3i block_pos) const;
uint32_t get_volume_id() const override {
return _volume_id;
}
protected:
void _notification(int p_what);
@ -272,6 +283,8 @@ private:
GodotUniqueObjectPtr<VoxelDataBlockEnterInfo> _data_block_enter_info_obj;
VoxelInstancer *_instancer = nullptr;
Stats _stats;
};

View File

@ -3,6 +3,7 @@
#include "../../util/godot/funcs.h"
#include "../../util/macros.h"
#include "../../util/profiling.h"
#include "../fixed_lod/voxel_terrain.h"
#include "../variable_lod/voxel_lod_terrain.h"
#include "voxel_instance_component.h"
#include "voxel_instance_library_scene_item.h"
@ -104,20 +105,41 @@ void VoxelInstancer::_notification(int p_what) {
#endif
break;
case NOTIFICATION_PARENTED:
_parent = Object::cast_to<VoxelLodTerrain>(get_parent());
if (_parent != nullptr) {
_parent->set_instancer(this);
case NOTIFICATION_PARENTED: {
VoxelLodTerrain *vlt = Object::cast_to<VoxelLodTerrain>(get_parent());
if (vlt != nullptr) {
_parent = vlt;
_parent_data_block_size_po2 = vlt->get_data_block_size_pow2();
_parent_mesh_block_size_po2 = vlt->get_mesh_block_size_pow2();
_mesh_lod_distance = vlt->get_lod_distance();
vlt->set_instancer(this);
} else {
VoxelTerrain *vt = Object::cast_to<VoxelTerrain>(get_parent());
if (vt != nullptr) {
_parent = vt;
_parent_data_block_size_po2 = vt->get_data_block_size_pow2();
_parent_mesh_block_size_po2 = vt->get_mesh_block_size_pow2();
_mesh_lod_distance = vt->get_max_view_distance();
vt->set_instancer(this);
}
}
// TODO may want to reload all instances? Not sure if worth implementing that use case
break;
} break;
case NOTIFICATION_UNPARENTED:
clear_blocks();
if (_parent != nullptr) {
_parent->set_instancer(nullptr);
VoxelLodTerrain *vlt = Object::cast_to<VoxelLodTerrain>(_parent);
if (vlt != nullptr) {
vlt->set_instancer(nullptr);
} else {
VoxelTerrain *vt = Object::cast_to<VoxelTerrain>(get_parent());
if (vt != nullptr) {
vt->set_instancer(nullptr);
}
}
_parent = nullptr;
}
break;
case NOTIFICATION_TRANSFORM_CHANGED: {
@ -130,7 +152,7 @@ void VoxelInstancer::_notification(int p_what) {
}
const Transform3D parent_transform = get_global_transform();
const int base_block_size_po2 = _parent->get_mesh_block_size_pow2();
const int base_block_size_po2 = _parent_mesh_block_size_po2;
//print_line(String("IP: {0}").format(varray(parent_transform.origin)));
for (auto it = _blocks.begin(); it != _blocks.end(); ++it) {
@ -157,7 +179,7 @@ void VoxelInstancer::_notification(int p_what) {
break;
case NOTIFICATION_INTERNAL_PROCESS:
if (_parent != nullptr && _library.is_valid()) {
if (_parent != nullptr && _library.is_valid() && _mesh_lod_distance > 0.f) {
process_mesh_lods();
}
#ifdef TOOLS_ENABLED
@ -183,7 +205,7 @@ void VoxelInstancer::set_show_gizmos(bool enable) {
void VoxelInstancer::process_gizmos() {
ERR_FAIL_COND(_parent == nullptr);
const Transform3D parent_transform = get_global_transform();
const int base_block_size_po2 = _parent->get_mesh_block_size_pow2();
const int base_block_size_po2 = _parent_mesh_block_size_po2;
_debug_renderer.begin();
@ -246,13 +268,11 @@ void VoxelInstancer::process_mesh_lods() {
const Vector3 cam_pos_local = (gtrans.affine_inverse() * camera->get_global_transform()).origin;
ERR_FAIL_COND(_parent == nullptr);
const int block_size = _parent->get_mesh_block_size();
const int block_size = 1 << _parent_mesh_block_size_po2;
{
// Hardcoded LOD thresholds for now.
// Can't really use pixel density because view distances are controlled by the main surface LOD octree
{
const int block_region_extent = _parent->get_mesh_block_region_extent();
FixedArray<float, 4> coeffs;
coeffs[0] = 0;
coeffs[1] = 0.1;
@ -260,13 +280,15 @@ void VoxelInstancer::process_mesh_lods() {
coeffs[3] = 0.5;
const float hysteresis = 1.05;
const float max_distance = _mesh_lod_distance;
for (unsigned int lod_index = 0; lod_index < _lods.size(); ++lod_index) {
Lod &lod = _lods[lod_index];
const float max_distance = block_size * (block_region_extent << lod_index);
for (unsigned int j = 0; j < lod.mesh_lod_distances.size(); ++j) {
MeshLodDistances &mld = lod.mesh_lod_distances[j];
mld.exit_distance_squared = max_distance * max_distance * coeffs[j];
const float lod_max_distance = (1 << j) * max_distance;
mld.exit_distance_squared = lod_max_distance * lod_max_distance * coeffs[j];
mld.enter_distance_squared = hysteresis * mld.exit_distance_squared;
}
}
@ -415,10 +437,19 @@ void VoxelInstancer::regenerate_layer(uint16_t layer_id, bool regenerate_blocks)
const Transform3D parent_transform = get_global_transform();
const VoxelLodTerrain *parent_vlt = Object::cast_to<VoxelLodTerrain>(_parent);
const VoxelTerrain *parent_vt = Object::cast_to<VoxelTerrain>(_parent);
if (regenerate_blocks) {
// Create blocks
std::vector<Vector3i> positions;
_parent->get_meshed_block_positions_at_lod(layer->lod_index, positions);
if (parent_vlt != nullptr) {
parent_vlt->get_meshed_block_positions_at_lod(layer->lod_index, positions);
} else if (parent_vt != nullptr) {
parent_vt->get_meshed_block_positions(positions);
}
for (unsigned int i = 0; i < positions.size(); ++i) {
const Vector3i pos = positions[i];
@ -431,7 +462,7 @@ void VoxelInstancer::regenerate_layer(uint16_t layer_id, bool regenerate_blocks)
}
}
const int render_to_data_factor = _parent->get_mesh_block_size() / _parent->get_data_block_size();
const int render_to_data_factor = 1 << (_parent_mesh_block_size_po2 - _parent_mesh_block_size_po2);
ERR_FAIL_COND(render_to_data_factor <= 0 || render_to_data_factor > 2);
struct L {
@ -500,9 +531,15 @@ void VoxelInstancer::regenerate_layer(uint16_t layer_id, bool regenerate_blocks)
_transform_cache.clear();
Array surface_arrays = _parent->get_mesh_block_surface(block->grid_position, lod_index);
Array surface_arrays;
if (parent_vlt != nullptr) {
surface_arrays = parent_vlt->get_mesh_block_surface(block->grid_position, lod_index);
} else if (parent_vt != nullptr) {
surface_arrays = parent_vt->get_mesh_block_surface(block->grid_position);
}
const int lod_block_size = _parent->get_mesh_block_size() << lod_index;
const int mesh_block_size = 1 << _parent_mesh_block_size_po2;
const int lod_block_size = mesh_block_size << lod_index;
const Transform3D block_local_transform(Basis(), Vector3(block->grid_position * lod_block_size));
const Transform3D block_transform = parent_transform * block_local_transform;
@ -512,7 +549,7 @@ void VoxelInstancer::regenerate_layer(uint16_t layer_id, bool regenerate_blocks)
if (render_to_data_factor == 2 && octant_mask != 0xff) {
// Complete transforms with edited ones
L::extract_octant_transforms(*block, _transform_cache, ~octant_mask, _parent->get_mesh_block_size());
L::extract_octant_transforms(*block, _transform_cache, ~octant_mask, mesh_block_size);
// TODO What if these blocks had loaded data which wasn't yet uploaded for render?
// We may setup a local transform list as well since it's expensive to get it from VisualServer
}
@ -553,7 +590,7 @@ void VoxelInstancer::update_layer_scenes(int layer_id) {
ERR_FAIL_COND(item_base.is_null());
VoxelInstanceLibrarySceneItem *item = Object::cast_to<VoxelInstanceLibrarySceneItem>(*item_base);
ERR_FAIL_COND(item == nullptr);
const int data_block_size_po2 = _parent->get_data_block_size_pow2();
const int data_block_size_po2 = _parent_data_block_size_po2;
for (unsigned int block_index = 0; block_index < _blocks.size(); ++block_index) {
Block *block = _blocks[block_index];
@ -728,7 +765,8 @@ void VoxelInstancer::on_mesh_block_exit(Vector3i render_grid_position, unsigned
Lod &lod = _lods[lod_index];
// Remove data blocks
const int render_to_data_factor = _parent->get_mesh_block_size() / _parent->get_data_block_size();
const int render_to_data_factor = 1 << (_parent_mesh_block_size_po2 - _parent_data_block_size_po2);
ERR_FAIL_COND(render_to_data_factor <= 0 || render_to_data_factor > 2);
const Vector3i data_min_pos = render_grid_position * render_to_data_factor;
const Vector3i data_max_pos = data_min_pos + Vector3iUtil::create(render_to_data_factor);
Vector3i data_grid_pos;
@ -878,7 +916,7 @@ void VoxelInstancer::update_block_from_transforms(int block_index, Span<const Tr
if (collision_shapes.size() > 0) {
ZN_PROFILE_SCOPE_NAMED("Update multimesh bodies");
const int data_block_size_po2 = _parent->get_data_block_size_pow2();
const int data_block_size_po2 = _parent_data_block_size_po2;
// Add new bodies
for (unsigned int instance_index = 0; instance_index < transforms.size(); ++instance_index) {
@ -930,7 +968,7 @@ void VoxelInstancer::update_block_from_transforms(int block_index, Span<const Tr
ZN_PROFILE_SCOPE_NAMED("Update scene instances");
ERR_FAIL_COND(scene_item->get_scene().is_null());
const int data_block_size_po2 = _parent->get_data_block_size_pow2();
const int data_block_size_po2 = _parent_data_block_size_po2;
// Add new instances
for (unsigned int instance_index = 0; instance_index < transforms.size(); ++instance_index) {
@ -989,15 +1027,16 @@ void VoxelInstancer::create_render_blocks(Vector3i render_grid_position, int lod
ERR_FAIL_COND(world_ref.is_null());
World3D *world = *world_ref;
const int lod_block_size = _parent->get_mesh_block_size() << lod_index;
const int mesh_block_size = (1 << _parent_mesh_block_size_po2);
const int lod_block_size = mesh_block_size << lod_index;
const Transform3D block_local_transform = Transform3D(Basis(), render_grid_position * lod_block_size);
const Transform3D block_transform = parent_transform * block_local_transform;
const int render_to_data_factor = _parent->get_mesh_block_size() / _parent->get_data_block_size();
const int render_to_data_factor = mesh_block_size / (1 << _parent_data_block_size_po2);
const Vector3i data_min_pos = render_grid_position * render_to_data_factor;
const Vector3i data_max_pos = data_min_pos + Vector3iUtil::create(render_to_data_factor);
const int lod_render_block_size = _parent->get_mesh_block_size() << lod_index;
const int lod_render_block_size = mesh_block_size << lod_index;
for (auto layer_it = lod.layers.begin(); layer_it != lod.layers.end(); ++layer_it) {
const int layer_id = *layer_it;
@ -1083,19 +1122,20 @@ void VoxelInstancer::create_render_blocks(Vector3i render_grid_position, int lod
void VoxelInstancer::save_block(Vector3i data_grid_pos, int lod_index) const {
ZN_PROFILE_SCOPE();
ERR_FAIL_COND(_library.is_null());
ERR_FAIL_COND(_parent == nullptr);
ZN_PRINT_VERBOSE(format("Requesting save of instance block {} lod {}", data_grid_pos, lod_index));
const Lod &lod = _lods[lod_index];
std::unique_ptr<InstanceBlockData> data = std::make_unique<InstanceBlockData>();
const int data_block_size = _parent->get_data_block_size() << lod_index;
const int data_block_size = (1 << _parent_data_block_size_po2) << lod_index;
data->position_range = data_block_size;
const int render_to_data_factor = _parent->get_mesh_block_size() / _parent->get_data_block_size();
const int render_to_data_factor = (1 << _parent_mesh_block_size_po2) / (1 << _parent_data_block_size_po2);
ERR_FAIL_COND_MSG(render_to_data_factor < 1 || render_to_data_factor > 2, "Unsupported block size");
const int half_render_block_size = _parent->get_mesh_block_size() / 2;
const int half_render_block_size = (1 << _parent_mesh_block_size_po2) / 2;
const Vector3i render_block_pos = Vector3iUtil::floordiv(data_grid_pos, render_to_data_factor);
const int octant_index = (data_grid_pos.x & 1) | ((data_grid_pos.y & 1) << 1) | ((data_grid_pos.z & 1) << 1);
@ -1363,15 +1403,15 @@ void VoxelInstancer::remove_floating_scene_instances(Block &block, const Transfo
void VoxelInstancer::on_area_edited(Box3i p_voxel_box) {
ZN_PROFILE_SCOPE();
ERR_FAIL_COND(_parent == nullptr);
const int render_block_size = _parent->get_mesh_block_size();
const int data_block_size = _parent->get_data_block_size();
const int render_block_size = 1 << _parent_mesh_block_size_po2;
const int data_block_size = 1 << _parent_data_block_size_po2;
Ref<VoxelTool> voxel_tool = _parent->get_voxel_tool();
ERR_FAIL_COND(voxel_tool.is_null());
voxel_tool->set_channel(VoxelBufferInternal::CHANNEL_SDF);
const Transform3D parent_transform = get_global_transform();
const int base_block_size_po2 = _parent->get_mesh_block_size_pow2();
const int base_block_size_po2 = 1 << _parent_mesh_block_size_po2;
for (unsigned int lod_index = 0; lod_index < _lods.size(); ++lod_index) {
Lod &lod = _lods[lod_index];
@ -1492,6 +1532,18 @@ void VoxelInstancer::on_scene_instance_modified(Vector3i data_block_position, un
lod.modified_blocks.set(data_block_position, true);
}
void VoxelInstancer::set_mesh_block_size_po2(unsigned int p_mesh_block_size_po2) {
_parent_mesh_block_size_po2 = p_mesh_block_size_po2;
}
void VoxelInstancer::set_data_block_size_po2(unsigned int p_data_block_size_po2) {
_parent_data_block_size_po2 = p_data_block_size_po2;
}
void VoxelInstancer::set_mesh_lod_distance(float p_lod_distance) {
_mesh_lod_distance = p_lod_distance;
}
int VoxelInstancer::debug_get_block_count() const {
return _blocks.size();
}
@ -1540,7 +1592,7 @@ void VoxelInstancer::debug_dump_as_scene(String fpath) const {
Node *VoxelInstancer::debug_dump_as_nodes() const {
ERR_FAIL_COND_V(_parent == nullptr, nullptr);
const unsigned int mesh_block_size = _parent->get_mesh_block_size();
const unsigned int mesh_block_size = 1 << _parent_mesh_block_size_po2;
Node3D *root = memnew(Node3D);
root->set_transform(get_transform());

View File

@ -1,6 +1,7 @@
#ifndef VOXEL_INSTANCER_H
#define VOXEL_INSTANCER_H
#include "../../constants/voxel_constants.h"
#include "../../streams/instance_data.h"
#include "../../util/fixed_array.h"
#include "../../util/godot/direct_multimesh_instance.h"
@ -24,7 +25,7 @@ class PhysicsBody3D;
namespace zylann::voxel {
class VoxelLodTerrain;
class VoxelNode;
class VoxelInstancerRigidBody;
class VoxelInstanceComponent;
class VoxelInstanceLibrarySceneItem;
@ -73,6 +74,12 @@ public:
Vector3i data_block_position, unsigned int render_block_index, unsigned int instance_index);
void on_scene_instance_modified(Vector3i data_block_position, unsigned int render_block_index);
// Internal properties
void set_mesh_block_size_po2(unsigned int p_mesh_block_size_po2);
void set_data_block_size_po2(unsigned int p_data_block_size_po2);
void set_mesh_lod_distance(float p_lod_distance);
// Debug
int debug_get_block_count() const;
@ -203,7 +210,10 @@ private:
std::vector<Transform3D> _transform_cache;
VoxelLodTerrain *_parent;
VoxelNode *_parent = nullptr;
unsigned int _parent_data_block_size_po2 = constants::DEFAULT_BLOCK_SIZE_PO2;
unsigned int _parent_mesh_block_size_po2 = constants::DEFAULT_BLOCK_SIZE_PO2;
float _mesh_lod_distance = 0.f;
#ifdef TOOLS_ENABLED
DebugRenderer _debug_renderer;

View File

@ -420,6 +420,11 @@ void VoxelLodTerrain::set_mesh_block_size(unsigned int mesh_block_size) {
lod.last_view_distance_mesh_blocks = 0;
}
// Doing this after because `on_mesh_block_exit` may use the old size
if (_instancer != nullptr) {
_instancer->set_mesh_block_size_po2(mesh_block_size);
}
// Reset LOD octrees
LodOctree::NoDestroyAction nda;
for (Map<Vector3i, VoxelLodTerrainUpdateData::OctreeItem>::Element *E = state.lod_octrees.front(); E;
@ -766,10 +771,15 @@ void VoxelLodTerrain::set_lod_distance(float p_lod_distance) {
// Distance must be greater than a threshold,
// otherwise lods will decimate too fast and it will look messy
_update_data->settings.lod_distance =
const float lod_distance =
math::clamp(p_lod_distance, constants::MINIMUM_LOD_DISTANCE, constants::MAXIMUM_LOD_DISTANCE);
_update_data->settings.lod_distance = lod_distance;
_update_data->state.force_update_octrees_next_update = true;
VoxelServer::get_singleton().set_volume_octree_lod_distance(_volume_id, get_lod_distance());
if (_instancer != nullptr) {
_instancer->set_mesh_lod_distance(lod_distance);
}
}
float VoxelLodTerrain::get_lod_distance() const {

View File

@ -232,7 +232,7 @@ public:
// Internal
void set_instancer(VoxelInstancer *instancer);
uint32_t get_volume_id() const {
uint32_t get_volume_id() const override {
return _volume_id;
}

View File

@ -1,4 +1,5 @@
#include "voxel_node.h"
#include "../edition/voxel_tool.h"
#include "../generators/voxel_generator.h"
#include "../meshers/voxel_mesher.h"
#include "../streams/voxel_stream.h"
@ -40,6 +41,16 @@ void VoxelNode::remesh_all_blocks() {
// Not implemented
}
uint32_t VoxelNode::get_volume_id() const {
CRASH_NOW_MSG("Not implemented");
return 0;
}
Ref<VoxelTool> VoxelNode::get_voxel_tool() {
CRASH_NOW_MSG("Not implemented");
return Ref<VoxelTool>();
}
#ifdef TOOLS_ENABLED
TypedArray<String> VoxelNode::get_configuration_warnings() const {

View File

@ -9,6 +9,8 @@
namespace zylann::voxel {
class VoxelTool;
// Base class for voxel volumes
class VoxelNode : public Node3D {
GDCLASS(VoxelNode, Node3D)
@ -35,6 +37,10 @@ public:
virtual void restart_stream();
virtual void remesh_all_blocks();
virtual uint32_t get_volume_id() const;
virtual Ref<VoxelTool> get_voxel_tool();
#ifdef TOOLS_ENABLED
virtual TypedArray<String> get_configuration_warnings() const override;
#endif