Fix some issues with persistent instance positions

- Scene instances were not made relative to their block
- Saving a block while mesh size is 16 and loading it back while mesh size
  is increased to 32 should now work. Before, positions were saved
  relative to render block instead of data block, which forced to
  keep mesh and data sizes the same
- Fixed incorrect octant_index in save_block, Z shift was 1 instead of 2
master
Marc Gilleron 2022-07-24 01:00:02 +01:00
parent 0e7fe5f4e3
commit 1f128a7803
4 changed files with 61 additions and 22 deletions

View File

@ -48,6 +48,7 @@ Godot 4 is required from this version.
- `VoxelToolLodTerrain`: added `stamp_sdf` function to place a baked mesh SDF on the terrain
- `VoxelInstancer`: Allow to dump VoxelInstancer as scene for debug inspection
- `VoxelInstancer`: Editor: instance chunks are shown when the node is selected
- `VoxelInstancer`: Changing mesh block size should no longer make saved instances invalid if they were saved with a different mesh block size
- `VoxelInstanceLibraryMultiMeshItem`: Support setting up mesh LODs from a scene with name `LODx` suffixes
- `VoxelMesherTransvoxel`: initial support for deep SDF sampling, to affine vertex positions at low levels of details (slow and limited proof of concept for now).
- `VoxelMesherTransvoxel`: Variable LOD: regular and transition meshes are now combined in one single mesh per chunk. A shader is required to render it, but creates far less mesh resources and reduces the amount of draw calls.
@ -69,6 +70,8 @@ Godot 4 is required from this version.
- `VoxelInstancer`: fix instances not refreshing when an item is modified and the mesh block size is 32
- `VoxelInstancer`: fix crash when removing an item from the library while an instancer node is using it
- `VoxelInstancer`: fix errors when removing scene instances
- `VoxelInstancer`: fix position issues when scene instances are saved
- `VoxelInstancer`: fix position issues when instances are saved while mesh block size is set to 32
- `VoxelLodTerrain`: fix `lod_fade_duration` property was not accepting decimal numbers
- `VoxelLodTerrain`: Cracks no longer appear at seams when LOD fading is enabled
- `VoxelMesherCubes`: editor: color mode is now a proper dropdown

View File

@ -9,6 +9,7 @@ namespace zylann::voxel {
// Stores data to pass around until it either gets saved or turned into actual instances
struct InstanceBlockData {
struct InstanceData {
// Transform of the instance, relative to the origin of the data block.
Transform3D transform;
};

View File

@ -112,7 +112,11 @@ public:
float get_noise_on_scale() const;
static inline int get_octant_index(const Vector3 pos, float half_block_size) {
return (pos.x > half_block_size) | ((pos.y > half_block_size) << 1) | ((pos.z > half_block_size) << 2);
return get_octant_index(pos.x > half_block_size, pos.y > half_block_size, pos.z > half_block_size);
}
static inline int get_octant_index(bool x, bool y, bool z) {
return int(x) | (int(y) << 1) | (int(z) << 2);
}
private:

View File

@ -791,7 +791,8 @@ void VoxelInstancer::on_mesh_block_exit(Vector3i render_grid_position, unsigned
for (data_grid_pos.z = data_min_pos.z; data_grid_pos.z < data_max_pos.z; ++data_grid_pos.z) {
for (data_grid_pos.y = data_min_pos.y; data_grid_pos.y < data_max_pos.y; ++data_grid_pos.y) {
for (data_grid_pos.x = data_min_pos.x; data_grid_pos.x < data_max_pos.x; ++data_grid_pos.x) {
// If we loaded data there but it was never used, we'll unload it either way
// If we loaded data there but it was never used, we'll unload it either way.
// Note, this data is what we loaded initially, it doesnt contain modifications.
lod.loaded_instances_data.erase(data_grid_pos);
auto modified_block_it = lod.modified_blocks.find(data_grid_pos);
@ -852,7 +853,7 @@ VoxelInstancer::SceneInstance VoxelInstancer::create_scene_instance(const VoxelI
instance.root->set_transform(transform);
// This is the SLOWEST part
// This is the SLOWEST part because Godot triggers all sorts of callbacks
add_child(instance.root);
return instance;
@ -1049,17 +1050,18 @@ void VoxelInstancer::create_render_blocks(Vector3i render_grid_position, int lod
ERR_FAIL_COND(maybe_world.is_null());
World3D &world = **maybe_world;
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 int mesh_block_size_base = (1 << _parent_mesh_block_size_po2);
const int data_block_size_base = (1 << _parent_data_block_size_po2);
const int mesh_block_size = mesh_block_size_base << lod_index;
const int data_block_size = data_block_size_base << lod_index;
const Transform3D block_local_transform = Transform3D(Basis(), render_grid_position * mesh_block_size);
const Transform3D block_transform = parent_transform * block_local_transform;
const int render_to_data_factor = mesh_block_size / (1 << _parent_data_block_size_po2);
const int render_to_data_factor = mesh_block_size_base / data_block_size_base;
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 = 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;
@ -1095,8 +1097,19 @@ void VoxelInstancer::create_render_blocks(Vector3i render_grid_position, int lod
}
for (auto it = layer_data->instances.begin(); it != layer_data->instances.end(); ++it) {
const InstanceBlockData::InstanceData &d = *it;
_transform_cache.push_back(d.transform);
_transform_cache.push_back(it->transform);
}
if (render_to_data_factor != 1) {
// Data blocks store instances relative to a smaller grid than render blocks.
// So we need to adjust their relative position.
const Vector3 rel = (data_grid_pos - data_min_pos) * data_block_size;
const size_t cache_begin_index = _transform_cache.size() - layer_data->instances.size();
ZN_ASSERT(cache_begin_index <= _transform_cache.size());
for (auto it = _transform_cache.begin() + cache_begin_index; it != _transform_cache.end();
++it) {
it->origin += rel;
}
}
// Unset bit for this octant so it won't be generated
@ -1121,14 +1134,14 @@ void VoxelInstancer::create_render_blocks(Vector3i render_grid_position, int lod
ZN_PROFILE_SCOPE();
static thread_local std::vector<Transform3D> s_generated_transforms;
s_generated_transforms.clear();
static thread_local std::vector<Transform3D> tls_generated_transforms;
tls_generated_transforms.clear();
item->get_generator()->generate_transforms(s_generated_transforms, render_grid_position, lod_index,
item->get_generator()->generate_transforms(tls_generated_transforms, render_grid_position, lod_index,
layer_id, surface_arrays, block_local_transform,
static_cast<VoxelInstanceGenerator::UpMode>(_up_mode), gen_octant_mask, lod_render_block_size);
static_cast<VoxelInstanceGenerator::UpMode>(_up_mode), gen_octant_mask, mesh_block_size);
for (auto it = s_generated_transforms.begin(); it != s_generated_transforms.end(); ++it) {
for (auto it = tls_generated_transforms.begin(); it != tls_generated_transforms.end(); ++it) {
_transform_cache.push_back(*it);
}
}
@ -1155,10 +1168,13 @@ void VoxelInstancer::save_block(Vector3i data_grid_pos, int lod_index) const {
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 = (1 << _parent_mesh_block_size_po2) / 2;
const int render_block_size_base = (1 << _parent_mesh_block_size_po2);
const int render_block_size = render_block_size_base << lod_index;
const int half_render_block_size = render_block_size / 2;
const Vector3i render_block_pos = math::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);
const int octant_index =
VoxelInstanceGenerator::get_octant_index(data_grid_pos.x & 1, data_grid_pos.y & 1, data_grid_pos.z & 1);
for (auto it = lod.layers.begin(); it != lod.layers.end(); ++it) {
const int layer_id = *it;
@ -1221,12 +1237,12 @@ void VoxelInstancer::save_block(Vector3i data_grid_pos, int lod_index) const {
} else if (render_to_data_factor == 2) {
for (int instance_index = 0; instance_index < instance_count; ++instance_index) {
// TODO Optimize: This is terrible in MT mode! Think about keeping a local copy...
const Transform3D t = multimesh->get_instance_transform(instance_index);
const int instance_octant_index =
VoxelInstanceGenerator::get_octant_index(t.origin, half_render_block_size);
const Transform3D rendered_instance_transform = multimesh->get_instance_transform(instance_index);
const int instance_octant_index = VoxelInstanceGenerator::get_octant_index(
rendered_instance_transform.origin, half_render_block_size);
if (instance_octant_index == octant_index) {
InstanceBlockData::InstanceData d;
d.transform = t;
d.transform = rendered_instance_transform;
layer_data.instances.push_back(d);
}
}
@ -1262,6 +1278,21 @@ void VoxelInstancer::save_block(Vector3i data_grid_pos, int lod_index) const {
// TODO Serialize state
}
}
// Make scene transforms relative to render block
const Vector3 render_block_origin = render_block_pos * render_block_size;
for (InstanceBlockData::InstanceData &d : layer_data.instances) {
d.transform.origin -= render_block_origin;
}
}
if (render_to_data_factor == 2) {
// Data blocks are on a smaller grid than render blocks so we may convert the relative position
// of the instances
const Vector3 rel = to_vec3(data_block_size * (data_grid_pos - render_block_pos * render_to_data_factor));
for (InstanceBlockData::InstanceData &d : layer_data.instances) {
d.transform.origin -= rel;
}
}
}