VoxelInstancer supports VoxelTerrain, although Godot 4 multimeshes are buggy
See https://github.com/godotengine/godot/issues/56357master
parent
219c7bfda3
commit
19a69b059e
|
@ -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*.
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue