Combine regular and transition meshes together.

- Less draw calls
- Much faster rendering updates (about 5x),
  although colliders are still the biggest bottleneck so when collision is
  enabled this improvement isn't noticeable
- A shader is now always required to render Transvoxel properly.
  TODO: integrate a default one when no material is assigned?
master
Marc Gilleron 2022-06-25 16:47:09 +01:00
parent 13a9bf7deb
commit ce91b3890c
13 changed files with 282 additions and 121 deletions

View File

@ -19,6 +19,7 @@ enum Side {
SIDE_COUNT
};
// Alias to the above for clarity, fixing some interpretation problems regarding the side_normals table...
// TODO Legacy: I would like to fix the X axes, they are inverted compared to the others
enum SideAxis {
SIDE_POSITIVE_X = 0,
SIDE_NEGATIVE_X,

View File

@ -548,17 +548,16 @@ void build_regular_mesh(Span<const Sdf_T> sdf_data, TextureIndicesData texture_i
const Vector3f normal = normalized_not_null(cg0 * t0 + cg1 * t1);
Vector3f secondary;
uint16_t border_mask = cell_border_mask;
uint8_t vertex_border_mask = 0;
if (cell_border_mask > 0) {
secondary = get_secondary_position(primaryf, normal, 0, block_size_scaled);
border_mask |= (get_border_mask(p0, block_size_scaled) &
get_border_mask(p1, block_size_scaled))
<< 6;
vertex_border_mask = (get_border_mask(p0, block_size_scaled) &
get_border_mask(p1, block_size_scaled));
}
cell_vertex_indices[vertex_index] =
output.add_vertex(primaryf, normal, border_mask, secondary);
cell_vertex_indices[vertex_index] = output.add_vertex(
primaryf, normal, cell_border_mask, vertex_border_mask, 0, secondary);
if (texturing_mode == TEXTURES_BLEND_4_OVER_16) {
const FixedArray<uint8_t, MAX_TEXTURE_BLENDS> weights0 = cell_textures.weights[v0];
@ -589,14 +588,15 @@ void build_regular_mesh(Span<const Sdf_T> sdf_data, TextureIndicesData texture_i
const Vector3f normal = normalized_not_null(cg1);
Vector3f secondary;
uint16_t border_mask = cell_border_mask;
uint8_t vertex_border_mask = 0;
if (cell_border_mask > 0) {
secondary = get_secondary_position(primaryf, normal, 0, block_size_scaled);
border_mask |= get_border_mask(p1, block_size_scaled) << 6;
vertex_border_mask = get_border_mask(p1, block_size_scaled);
}
cell_vertex_indices[vertex_index] = output.add_vertex(primaryf, normal, border_mask, secondary);
cell_vertex_indices[vertex_index] =
output.add_vertex(primaryf, normal, cell_border_mask, vertex_border_mask, 0, secondary);
if (texturing_mode == TEXTURES_BLEND_4_OVER_16) {
const FixedArray<uint8_t, MAX_TEXTURE_BLENDS> weights1 = cell_textures.weights[v1];
@ -649,15 +649,15 @@ void build_regular_mesh(Span<const Sdf_T> sdf_data, TextureIndicesData texture_i
// TODO This bit of code is repeated several times, factor it?
Vector3f secondary;
uint16_t border_mask = cell_border_mask;
uint8_t vertex_border_mask = 0;
if (cell_border_mask > 0) {
secondary = get_secondary_position(primaryf, normal, 0, block_size_scaled);
border_mask |= get_border_mask(primary, block_size_scaled) << 6;
vertex_border_mask = get_border_mask(primary, block_size_scaled);
}
cell_vertex_indices[vertex_index] =
output.add_vertex(primaryf, normal, border_mask, secondary);
cell_vertex_indices[vertex_index] = output.add_vertex(
primaryf, normal, cell_border_mask, vertex_border_mask, 0, secondary);
if (texturing_mode == TEXTURES_BLEND_4_OVER_16) {
const FixedArray<uint8_t, MAX_TEXTURE_BLENDS> weights = cell_textures.weights[vi];
@ -758,6 +758,33 @@ inline void get_face_axes(int &ax, int &ay, int dir) {
}
}
// TODO Cube::Side has a legacy issue where Y axes are inverted compared to the others
inline uint8_t get_face_index(int cube_dir) {
switch (cube_dir) {
case Cube::SIDE_NEGATIVE_X:
return 0;
case Cube::SIDE_POSITIVE_X:
return 1;
case Cube::SIDE_NEGATIVE_Y:
return 2;
case Cube::SIDE_POSITIVE_Y:
return 3;
case Cube::SIDE_NEGATIVE_Z:
return 4;
case Cube::SIDE_POSITIVE_Z:
return 5;
default:
ZN_CRASH();
return 0;
}
}
template <typename Sdf_T, typename WeightSampler_T>
void build_transition_mesh(Span<const Sdf_T> sdf_data, TextureIndicesData texture_indices_data,
const WeightSampler_T &weights_sampler, const Vector3i block_size_with_padding, int direction, int lod_index,
@ -827,6 +854,8 @@ void build_transition_mesh(Span<const Sdf_T> sdf_data, TextureIndicesData textur
const Sdf_T isolevel = get_isolevel<Sdf_T>();
const uint8_t transition_hint_mask = 1 << get_face_index(direction);
// Iterating in face space
for (int fy = min_fpos_y; fy < max_fpos_y; fy += 2) {
for (int fx = min_fpos_x; fx < max_fpos_x; fx += 2) {
@ -1047,24 +1076,24 @@ void build_transition_mesh(Span<const Sdf_T> sdf_data, TextureIndicesData textur
const Vector3f normal = normalized_not_null(n0 * t0 + n1 * t1);
const bool fullres_side = (index_vertex_a < 9 || index_vertex_b < 9);
uint16_t border_mask = cell_border_mask;
Vector3f secondary;
uint8_t cell_border_mask2 = cell_border_mask;
uint8_t vertex_border_mask = 0;
if (fullres_side) {
secondary = get_secondary_position(primaryf, normal, 0, block_size_scaled);
border_mask |=
(get_border_mask(p0, block_size_scaled) & get_border_mask(p1, block_size_scaled))
<< 6;
vertex_border_mask =
(get_border_mask(p0, block_size_scaled) & get_border_mask(p1, block_size_scaled));
} else {
// If the vertex is on the half-res side (in our implementation,
// it's the side of the block), then we make the mask 0 so that the vertex is never moved.
// We only move the full-res side to connect with the regular mesh,
// which will also be moved by the same amount to fit the transition mesh.
border_mask = 0;
cell_border_mask2 = 0;
}
cell_vertex_indices[vertex_index] = output.add_vertex(primaryf, normal, border_mask, secondary);
cell_vertex_indices[vertex_index] = output.add_vertex(primaryf, normal, cell_border_mask2,
vertex_border_mask, transition_hint_mask, secondary);
if (texturing_mode == TEXTURES_BLEND_4_OVER_16) {
const FixedArray<uint8_t, MAX_TEXTURE_BLENDS> weights0 =
@ -1119,15 +1148,17 @@ void build_transition_mesh(Span<const Sdf_T> sdf_data, TextureIndicesData textur
uint16_t border_mask = cell_border_mask;
Vector3f secondary;
uint8_t vertex_border_mask = 0;
uint8_t cell_border_mask2 = cell_border_mask;
if (fullres_side) {
secondary = get_secondary_position(primaryf, normal, 0, block_size_scaled);
border_mask |= get_border_mask(primary, block_size_scaled) << 6;
vertex_border_mask = get_border_mask(primary, block_size_scaled);
} else {
border_mask = 0;
cell_border_mask2 = 0;
}
cell_vertex_indices[vertex_index] = output.add_vertex(primaryf, normal, border_mask, secondary);
cell_vertex_indices[vertex_index] = output.add_vertex(primaryf, normal, cell_border_mask2,
vertex_border_mask, transition_hint_mask, secondary);
if (texturing_mode == TEXTURES_BLEND_4_OVER_16) {
const FixedArray<uint8_t, MAX_TEXTURE_BLENDS> weights = cell_textures.weights[cell_index];

View File

@ -29,11 +29,41 @@ enum TexturingMode {
TEXTURES_BLEND_4_OVER_16
};
struct LodAttrib {
Vector3f secondary_position;
// Mask telling if a cell the vertex belongs to is on a side of the block.
// Each bit corresponds to a side.
// 0: -X
// 1: +X
// 2: -Y
// 3: +Y
// 4: -Z
// 5: +Z
uint8_t cell_border_mask;
// Mask telling if the vertex is on a side of the block. Same convention as above.
uint8_t vertex_border_mask;
// Flag telling if the vertex belongs to a transition mesh.
uint8_t transition;
// Unused. Necessary to align to 4*sizeof(float) so it can be memcpied to a rendering buffer.
uint8_t _pad;
};
// struct TextureAttrib {
// uint8_t index0;
// uint8_t index1;
// uint8_t index2;
// uint8_t index3;
// uint8_t weight0;
// uint8_t weight1;
// uint8_t weight2;
// uint8_t weight3;
// };
struct MeshArrays {
std::vector<Vector3f> vertices;
std::vector<Vector3f> normals;
std::vector<Color> lod_data;
std::vector<Vector2f> texturing_data;
std::vector<LodAttrib> lod_data;
std::vector<Vector2f> texturing_data; // TextureAttrib
std::vector<int> indices;
void clear() {
@ -44,12 +74,12 @@ struct MeshArrays {
indices.clear();
}
int add_vertex(Vector3f primary, Vector3f normal, uint16_t border_mask, Vector3f secondary) {
int add_vertex(Vector3f primary, Vector3f normal, uint8_t cell_border_mask, uint8_t vertex_border_mask,
uint8_t transition, Vector3f secondary) {
int vi = vertices.size();
vertices.push_back(primary);
normals.push_back(normal);
// TODO Use an explicit struct for this, and use floatToBits in shader so we can pack more data safely
lod_data.push_back(Color(secondary.x, secondary.y, secondary.z, border_mask));
lod_data.push_back({ secondary, cell_border_mask, vertex_border_mask, transition });
return vi;
}
};

View File

@ -27,7 +27,12 @@ int VoxelMesherTransvoxel::get_used_channels_mask() const {
return (1 << VoxelBufferInternal::CHANNEL_SDF);
}
void VoxelMesherTransvoxel::fill_surface_arrays(Array &arrays, const transvoxel::MeshArrays &src) {
bool VoxelMesherTransvoxel::is_generating_collision_surface() const {
// Via submesh indices
return true;
}
static void fill_surface_arrays(Array &arrays, const transvoxel::MeshArrays &src) {
PackedVector3Array vertices;
PackedVector3Array normals;
PackedFloat32Array lod_data; // 4*float32
@ -38,6 +43,8 @@ void VoxelMesherTransvoxel::fill_surface_arrays(Array &arrays, const transvoxel:
//raw_copy_to(lod_data, src.lod_data);
lod_data.resize(src.lod_data.size() * 4);
// Based on the layout, position is first 3 floats, and 4th float is actually a bitmask
static_assert(sizeof(transvoxel::LodAttrib) == 16);
memcpy(lod_data.ptrw(), src.lod_data.data(), lod_data.size() * sizeof(float));
raw_copy_to(indices, src.indices);
@ -167,6 +174,7 @@ void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher
static thread_local transvoxel::Cache tls_cache;
static thread_local transvoxel::MeshArrays tls_mesh_arrays;
// static thread_local FixedArray<transvoxel::MeshArrays, Cube::SIDE_COUNT> tls_transition_mesh_arrays;
static thread_local transvoxel::MeshArrays tls_simplified_mesh_arrays;
const VoxelBufferInternal::ChannelId sdf_channel = VoxelBufferInternal::CHANNEL_SDF;
@ -205,8 +213,7 @@ void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher
return;
}
Array regular_arrays;
transvoxel::MeshArrays *combined_mesh_arrays = &tls_mesh_arrays;
if (_mesh_optimization_params.enabled) {
// TODO When voxel texturing is enabled, this will decrease quality a lot.
// There is no support yet for taking textures into account when simplifying.
@ -214,31 +221,30 @@ void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher
simplify(tls_mesh_arrays, tls_simplified_mesh_arrays, _mesh_optimization_params.target_ratio,
_mesh_optimization_params.error_threshold);
fill_surface_arrays(regular_arrays, tls_simplified_mesh_arrays);
} else {
fill_surface_arrays(regular_arrays, tls_mesh_arrays);
combined_mesh_arrays = &tls_simplified_mesh_arrays;
}
output.surfaces.push_back({ regular_arrays });
output.collision_surface.submesh_vertex_end = combined_mesh_arrays->vertices.size();
output.collision_surface.submesh_index_end = combined_mesh_arrays->indices.size();
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
ZN_PROFILE_SCOPE();
tls_mesh_arrays.clear();
if (_transitions_enabled) {
// We combine transition meshes with the regular mesh, because it results in less draw calls than if they were
// separate. This only requires a vertex shader trick to discard them when neighbors change.
ZN_ASSERT(combined_mesh_arrays != nullptr);
transvoxel::build_transition_mesh(voxels, sdf_channel, dir, input.lod,
static_cast<transvoxel::TexturingMode>(_texture_mode), tls_cache, tls_mesh_arrays,
default_texture_indices_data);
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
ZN_PROFILE_SCOPE();
if (tls_mesh_arrays.vertices.size() == 0) {
continue;
transvoxel::build_transition_mesh(voxels, sdf_channel, dir, input.lod,
static_cast<transvoxel::TexturingMode>(_texture_mode), tls_cache, *combined_mesh_arrays,
default_texture_indices_data);
}
Array transition_arrays;
fill_surface_arrays(transition_arrays, tls_mesh_arrays);
output.transition_surfaces[dir].push_back({ transition_arrays });
}
Array gd_arrays;
fill_surface_arrays(gd_arrays, *combined_mesh_arrays);
output.surfaces.push_back({ gd_arrays });
// const uint64_t time_spent = Time::get_singleton()->get_ticks_usec() - time_before;
// print_line(String("VoxelMesherTransvoxel spent {0} us").format(varray(time_spent)));
@ -248,7 +254,7 @@ void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher
(RenderingServer::ARRAY_CUSTOM_RG_FLOAT << Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT);
}
// TODO For testing at the moment
// Only exists for testing
Ref<ArrayMesh> VoxelMesherTransvoxel::build_transition_mesh(Ref<gd::VoxelBuffer> voxels, int direction) {
static thread_local transvoxel::Cache s_cache;
static thread_local transvoxel::MeshArrays s_mesh_arrays;
@ -326,6 +332,14 @@ bool VoxelMesherTransvoxel::is_deep_sampling_enabled() const {
return _deep_sampling_enabled;
}
void VoxelMesherTransvoxel::set_transitions_enabled(bool enable) {
_transitions_enabled = enable;
}
bool VoxelMesherTransvoxel::get_transitions_enabled() const {
return _transitions_enabled;
}
void VoxelMesherTransvoxel::_bind_methods() {
ClassDB::bind_method(D_METHOD("build_transition_mesh", "voxel_buffer", "direction"),
&VoxelMesherTransvoxel::build_transition_mesh);
@ -352,6 +366,10 @@ void VoxelMesherTransvoxel::_bind_methods() {
D_METHOD("set_deep_sampling_enabled", "enabled"), &VoxelMesherTransvoxel::set_deep_sampling_enabled);
ClassDB::bind_method(D_METHOD("is_deep_sampling_enabled"), &VoxelMesherTransvoxel::is_deep_sampling_enabled);
ClassDB::bind_method(
D_METHOD("set_transitions_enabled", "enabled"), &VoxelMesherTransvoxel::set_transitions_enabled);
ClassDB::bind_method(D_METHOD("get_transitions_enabled"), &VoxelMesherTransvoxel::get_transitions_enabled);
ADD_PROPERTY(
PropertyInfo(Variant::INT, "texturing_mode", PROPERTY_HINT_ENUM, "None,4-blend over 16 textures (4 bits)"),
"set_texturing_mode", "get_texturing_mode");
@ -368,6 +386,9 @@ void VoxelMesherTransvoxel::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deep_sampling_enabled"), "set_deep_sampling_enabled",
"is_deep_sampling_enabled");
ADD_PROPERTY(
PropertyInfo(Variant::BOOL, "transitions_enabled"), "set_transitions_enabled", "get_transitions_enabled");
BIND_ENUM_CONSTANT(TEXTURES_NONE);
// TODO Rename MIXEL
BIND_ENUM_CONSTANT(TEXTURES_BLEND_4_OVER_16);

View File

@ -30,6 +30,8 @@ public:
Ref<Resource> duplicate(bool p_subresources = false) const override;
int get_used_channels_mask() const override;
bool is_generating_collision_surface() const;
void set_texturing_mode(TexturingMode mode);
TexturingMode get_texturing_mode() const;
@ -45,12 +47,23 @@ public:
void set_deep_sampling_enabled(bool enable);
bool is_deep_sampling_enabled() const;
void set_transitions_enabled(bool enable);
bool get_transitions_enabled() const;
// Not sure if that's necessary, currently transitions are either combined or not generated
// enum TransitionMode {
// // No transition meshes will be generated
// TRANSITION_NONE,
// // Generates transition meshes as separate meshes
// TRANSITION_SEPARATE,
// // Transition meshes will be part of the main mesh
// TRANSITION_COMBINED
// };
protected:
static void _bind_methods();
private:
void fill_surface_arrays(Array &arrays, const transvoxel::MeshArrays &src);
TexturingMode _texture_mode = TEXTURES_NONE;
struct MeshOptimizationParams {
@ -65,6 +78,8 @@ private:
// by querying the generator and edits. This can result in better quality meshes, but is also more expensive
// because voxel data shared between threads will have to be accessed randomly over denser data sets.
bool _deep_sampling_enabled = false;
bool _transitions_enabled = true;
};
} // namespace zylann::voxel

View File

@ -46,14 +46,22 @@ public:
std::vector<Surface> surfaces;
FixedArray<std::vector<Surface>, Cube::SIDE_COUNT> transition_surfaces;
Mesh::PrimitiveType primitive_type = Mesh::PRIMITIVE_TRIANGLES;
// Flags for creating the Godot mesh resource
uint32_t mesh_flags = 0;
struct CollisionSurface {
std::vector<Vector3f> positions;
std::vector<int> indices;
// If >= 0, the collision surface may actually be picked from a sub-section of arrays of the first surface
// in the render mesh (It may start from index 0).
// Used when transition meshes are combined with the main mesh.
int32_t submesh_vertex_end = -1;
int32_t submesh_index_end = -1;
};
CollisionSurface collision_surface;
// May be used to store extra information needed in shader to render the mesh properly
// (currently used only by the cubes mesher when baking colors)
Ref<Image> atlas_image;
};

View File

@ -22,6 +22,7 @@
#include <core/config/engine.h>
#include <core/core_string_names.h>
#include <scene/3d/mesh_instance_3d.h>
#include <scene/resources/concave_polygon_shape_3d.h>
namespace zylann::voxel {
@ -1609,16 +1610,9 @@ void VoxelTerrain::apply_mesh_update(const VoxelServer::BlockMeshOutput &ob) {
}
if (gen_collisions) {
const bool debug_collisions = get_tree()->is_debugging_collisions_hint();
const VoxelMesher::Output::CollisionSurface &collision_surface = ob.surfaces.collision_surface;
if (use_render_mesh_as_collider) {
block->set_collision_mesh(to_span(render_surfaces), debug_collisions, this, _collision_margin);
} else {
block->set_collision_mesh(to_span(collision_surface.positions), to_span(collision_surface.indices),
debug_collisions, this, _collision_margin);
}
Ref<Shape3D> collision_shape = make_collision_shape_from_mesher_output(ob.surfaces, **_mesher);
block->set_collision_shape(
collision_shape, get_tree()->is_debugging_collisions_hint(), this, _collision_margin);
block->set_collision_layer(_collision_layer);
block->set_collision_mask(_collision_mask);

View File

@ -25,6 +25,7 @@
#include <core/config/engine.h>
#include <core/core_string_names.h>
#include <scene/3d/mesh_instance_3d.h>
#include <scene/resources/concave_polygon_shape_3d.h>
#include <scene/resources/packed_scene.h>
namespace zylann::voxel {
@ -1417,6 +1418,11 @@ void VoxelLodTerrain::apply_mesh_update(VoxelServer::BlockMeshOutput &ob) {
++_stats.dropped_block_meshs;
return;
}
// There is a slim chance for some updates to come up just after setting the mesher to null. Avoids a crash.
if (_mesher.is_null()) {
++_stats.dropped_block_meshs;
return;
}
uint8_t transition_mask;
bool active;
@ -1527,7 +1533,7 @@ void VoxelLodTerrain::apply_mesh_update(VoxelServer::BlockMeshOutput &ob) {
block->set_mesh(mesh, DirectMeshInstance::GIMode(get_gi_mode()));
{
/*{
// Profiling has shown Godot takes as much time to build a transition mesh as the main mesh of a block, so
// because there are 6 transition meshes per block, we would spend about 80% of the time on these if we build
// them all. Which is counter-intuitive because transition meshes are tiny in comparison... (collision meshes
@ -1566,42 +1572,29 @@ void VoxelLodTerrain::apply_mesh_update(VoxelServer::BlockMeshOutput &ob) {
task, TimeSpreadTaskRunner::PRIORITY_LOW);
}
}
}
}*/
if (has_collision) {
const uint64_t now = get_ticks_msec();
if (_collision_update_delay == 0 ||
static_cast<int>(now - block->last_collider_update_time) > _collision_update_delay) {
static thread_local std::vector<Array> tls_collidable_surfaces;
std::vector<Array> &collidable_surfaces = tls_collidable_surfaces;
collidable_surfaces.clear();
for (unsigned int i = 0; i < mesh_data.surfaces.size(); ++i) {
collidable_surfaces.push_back(mesh_data.surfaces[i].arrays);
}
block->set_collision_mesh(
to_span(collidable_surfaces), get_tree()->is_debugging_collisions_hint(), this, _collision_margin);
ZN_ASSERT(_mesher.is_valid());
Ref<Shape3D> collision_shape = make_collision_shape_from_mesher_output(ob.surfaces, **_mesher);
block->set_collision_shape(
collision_shape, get_tree()->is_debugging_collisions_hint(), this, _collision_margin);
block->set_collision_layer(_collision_layer);
block->set_collision_mask(_collision_mask);
block->last_collider_update_time = now;
block->has_deferred_collider_update = false;
block->deferred_collider_data.clear();
block->deferred_collider_data.reset();
} else {
if (!block->has_deferred_collider_update) {
if (block->deferred_collider_data == nullptr) {
_deferred_collision_updates_per_lod[ob.lod].push_back(ob.position);
block->has_deferred_collider_update = true;
}
// TODO Optimization: could avoid the small allocation.
// It's usually a small vectors with a handful of elements.
// The caller providing `mesh_data` doesnt use `mesh_data` later so we could have moved the vector,
// but at the moment it's passed with `const` so that isn't possible. Indeed we are only going to read the
// data, but `const` also means the structure holding it is read-only as well.
block->deferred_collider_data.resize(mesh_data.surfaces.size());
for (size_t i = 0; i < mesh_data.surfaces.size(); ++i) {
block->deferred_collider_data[i] = mesh_data.surfaces[i].arrays;
block->deferred_collider_data = make_unique_instance<VoxelMesher::Output>();
}
*block->deferred_collider_data = std::move(ob.surfaces);
}
}
@ -1618,6 +1611,8 @@ void VoxelLodTerrain::process_deferred_collision_updates(uint32_t timeout_msec)
ZN_PROFILE_SCOPE();
const unsigned int lod_count = _update_data->settings.lod_count;
// TODO We may move this in a time spread task somehow, the timeout does not account for them so could take longer
const uint64_t then = get_ticks_msec();
for (unsigned int lod_index = 0; lod_index < lod_count; ++lod_index) {
VoxelMeshMap<VoxelMeshBlockVLT> &mesh_map = _mesh_maps_per_lod[lod_index];
@ -1627,30 +1622,35 @@ void VoxelLodTerrain::process_deferred_collision_updates(uint32_t timeout_msec)
const Vector3i block_pos = deferred_collision_updates[i];
VoxelMeshBlockVLT *block = mesh_map.get_block(block_pos);
if (block == nullptr || block->has_deferred_collider_update == false) {
if (block == nullptr || block->deferred_collider_data == nullptr) {
// Block was unloaded or no longer needs a collision update
unordered_remove(deferred_collision_updates, i);
--i;
continue;
}
const uint32_t now = get_ticks_msec();
const uint64_t now = get_ticks_msec();
if (static_cast<int>(now - block->last_collider_update_time) > _collision_update_delay) {
block->set_collision_mesh(to_span_const(block->deferred_collider_data),
get_tree()->is_debugging_collisions_hint(), this, _collision_margin);
Ref<Shape3D> collision_shape;
if (_mesher.is_valid()) {
collision_shape ==
make_collision_shape_from_mesher_output(*block->deferred_collider_data, **_mesher);
}
block->set_collision_shape(
collision_shape, get_tree()->is_debugging_collisions_hint(), this, _collision_margin);
block->set_collision_layer(_collision_layer);
block->set_collision_mask(_collision_mask);
block->last_collider_update_time = now;
block->has_deferred_collider_update = false;
block->deferred_collider_data.clear();
block->deferred_collider_data.reset();
unordered_remove(deferred_collision_updates, i);
--i;
}
// We always process at least one, then we to check the timeout
if (get_ticks_msec() >= timeout_msec) {
// We always process at least one, then we check the timeout
if (get_ticks_msec() - then >= timeout_msec) {
return;
}
}

View File

@ -1,6 +1,7 @@
#ifndef VOXEL_MESH_BLOCK_VLT_H
#define VOXEL_MESH_BLOCK_VLT_H
#include "../../util/memory.h"
#include "../../util/tasks/time_spread_task_runner.h"
#include "../voxel_mesh_block.h"
@ -33,8 +34,7 @@ public:
bool got_first_mesh_update = false;
uint64_t last_collider_update_time = 0;
bool has_deferred_collider_update = false;
std::vector<Array> deferred_collider_data;
UniquePtr<VoxelMesher::Output> deferred_collider_data;
VoxelMeshBlockVLT(const Vector3i bpos, unsigned int size, unsigned int p_lod_index);
~VoxelMeshBlockVLT();

View File

@ -128,26 +128,6 @@ void VoxelMeshBlock::set_parent_transform(const Transform3D &parent_transform) {
}
}
void VoxelMeshBlock::set_collision_mesh(
Span<const Array> surface_arrays, bool debug_collision, Node3D *node, float margin) {
if (surface_arrays.size() == 0) {
drop_collision();
return;
}
Ref<Shape3D> shape = create_concave_polygon_shape(surface_arrays);
set_collision_shape(shape, debug_collision, node, margin);
}
void VoxelMeshBlock::set_collision_mesh(
Span<const Vector3f> positions, Span<const int> indices, bool debug_collision, Node3D *node, float margin) {
if (positions.size() == 0) {
drop_collision();
return;
}
Ref<Shape3D> shape = create_concave_polygon_shape(positions, indices);
set_collision_shape(shape, debug_collision, node, margin);
}
void VoxelMeshBlock::set_collision_shape(Ref<Shape3D> shape, bool debug_collision, Node3D *node, float margin) {
ERR_FAIL_COND(node == nullptr);
ERR_FAIL_COND_MSG(node->get_world_3d() != _world, "Physics body and attached node must be from the same world");
@ -201,4 +181,39 @@ void VoxelMeshBlock::drop_collision() {
}
}
Ref<ConcavePolygonShape3D> make_collision_shape_from_mesher_output(
const VoxelMesher::Output &mesher_output, const VoxelMesher &mesher) {
Ref<ConcavePolygonShape3D> shape;
if (mesher.is_generating_collision_surface()) {
if (mesher_output.collision_surface.submesh_vertex_end == -1) {
// Use a sub-region of the render mesh
if (mesher_output.surfaces.size() > 0) {
shape = create_concave_polygon_shape(
mesher_output.surfaces[0].arrays, mesher_output.collision_surface.submesh_index_end);
}
} else {
// Use specialized collision mesh
shape = create_concave_polygon_shape(to_span(mesher_output.collision_surface.positions),
to_span(mesher_output.collision_surface.indices));
}
} else {
// Use render mesh
static thread_local std::vector<Array> tls_render_surfaces;
ZN_ASSERT(tls_render_surfaces.size() == 0);
for (unsigned int i = 0; i < mesher_output.surfaces.size(); ++i) {
tls_render_surfaces.push_back(mesher_output.surfaces[i].arrays);
}
shape = create_concave_polygon_shape(to_span(tls_render_surfaces));
tls_render_surfaces.clear();
}
return shape;
}
} // namespace zylann::voxel

View File

@ -14,6 +14,7 @@
#include <atomic>
class Node3D;
class ConcavePolygonShape3D;
namespace zylann::voxel {
@ -46,9 +47,6 @@ public:
// Collisions
void set_collision_mesh(Span<const Array> surface_arrays, bool debug_collision, Node3D *node, float margin);
void set_collision_mesh(
Span<const Vector3f> positions, Span<const int> indices, bool debug_collision, Node3D *node, float margin);
void set_collision_shape(Ref<Shape3D> shape, bool debug_collision, Node3D *node, float margin);
void set_collision_layer(int layer);
void set_collision_mask(int mask);
@ -87,6 +85,9 @@ protected:
bool _parent_visible = true;
};
Ref<ConcavePolygonShape3D> make_collision_shape_from_mesher_output(
const VoxelMesher::Output &mesher_output, const VoxelMesher &mesher);
} // namespace zylann::voxel
#endif // VOXEL_MESH_BLOCK_H

View File

@ -29,10 +29,10 @@ bool is_mesh_empty(const Mesh &mesh) {
return false;
}
// Faster version of Mesh::create_trimesh_shape()
// See https://github.com/Zylann/godot_voxel/issues/54
//
Ref<ConcavePolygonShape3D> create_concave_polygon_shape(Span<const Array> surfaces) {
// Faster version of Mesh::create_trimesh_shape()
// See https://github.com/Zylann/godot_voxel/issues/54
ZN_PROFILE_SCOPE();
PackedVector3Array face_points;
@ -135,6 +135,48 @@ Ref<ConcavePolygonShape3D> create_concave_polygon_shape(Span<const Vector3f> pos
return shape;
}
Ref<ConcavePolygonShape3D> create_concave_polygon_shape(const Array surface_arrays, unsigned int index_count) {
ZN_PROFILE_SCOPE();
Ref<ConcavePolygonShape3D> shape;
if (surface_arrays.size() == 0) {
return shape;
}
ZN_ASSERT(surface_arrays.size() == Mesh::ARRAY_MAX);
PackedInt32Array indices = surface_arrays[Mesh::ARRAY_INDEX];
ERR_FAIL_COND_V(index_count < 0 || index_count > static_cast<unsigned int>(indices.size()), shape);
if (indices.size() < 3) {
return shape;
}
PackedVector3Array positions = surface_arrays[Mesh::ARRAY_VERTEX];
ERR_FAIL_COND_V(positions.size() < 3, shape);
ERR_FAIL_COND_V(indices.size() < 3, shape);
ERR_FAIL_COND_V(indices.size() % 3 != 0, shape);
PackedVector3Array face_points;
face_points.resize(indices.size());
// Deindex mesh
{
Vector3 *w = face_points.ptrw();
for (unsigned int ii = 0; ii < index_count; ++ii) {
const int index = indices[ii];
w[ii] = to_vec3(positions[index]);
}
}
{
ZN_PROFILE_SCOPE_NAMED("Godot shape");
shape.instantiate();
shape->set_faces(face_points);
}
return shape;
}
int get_visible_instance_count(const MultiMesh &mm) {
int visible_count = mm.get_visible_instance_count();
if (visible_count == -1) {

View File

@ -21,8 +21,11 @@ namespace zylann {
bool is_surface_triangulated(Array surface);
bool is_mesh_empty(const Mesh &mesh);
// Combines all mesh surface arrays into one collider.
Ref<ConcavePolygonShape3D> create_concave_polygon_shape(Span<const Array> surfaces);
Ref<ConcavePolygonShape3D> create_concave_polygon_shape(Span<const Vector3f> positions, Span<const int> indices);
// Create shape from a sub-region of a mesh surface (starting at 0).
Ref<ConcavePolygonShape3D> create_concave_polygon_shape(const Array surface_arrays, unsigned int index_count);
// This API can be confusing so I made a wrapper
int get_visible_instance_count(const MultiMesh &mm);