godot_voxel/meshers/transvoxel/voxel_mesher_transvoxel.cpp

309 lines
11 KiB
C++

#include "voxel_mesher_transvoxel.h"
#include "../../storage/voxel_buffer.h"
#include "../../thirdparty/meshoptimizer/meshoptimizer.h"
#include "../../util/funcs.h"
#include "../../util/profiling.h"
#include "transvoxel_tables.cpp"
namespace {
static const unsigned int MESH_COMPRESSION_FLAGS =
Mesh::ARRAY_COMPRESS_NORMAL |
Mesh::ARRAY_COMPRESS_TANGENT |
//Mesh::ARRAY_COMPRESS_COLOR | // Using color as 4 full floats to transfer extra attributes for now...
//Mesh::ARRAY_COMPRESS_TEX_UV | // Not compressing UV, we use it for different texturing information
Mesh::ARRAY_COMPRESS_TEX_UV2 |
Mesh::ARRAY_COMPRESS_WEIGHTS;
}
VoxelMesherTransvoxel::VoxelMesherTransvoxel() {
set_padding(Transvoxel::MIN_PADDING, Transvoxel::MAX_PADDING);
}
VoxelMesherTransvoxel::~VoxelMesherTransvoxel() {
}
Ref<Resource> VoxelMesherTransvoxel::duplicate(bool p_subresources) const {
return memnew(VoxelMesherTransvoxel);
}
int VoxelMesherTransvoxel::get_used_channels_mask() const {
if (_texture_mode == TEXTURES_BLEND_4_OVER_16) {
return (1 << VoxelBuffer::CHANNEL_SDF) |
(1 << VoxelBuffer::CHANNEL_INDICES) |
(1 << VoxelBuffer::CHANNEL_WEIGHTS);
}
return (1 << VoxelBuffer::CHANNEL_SDF);
}
void VoxelMesherTransvoxel::fill_surface_arrays(Array &arrays, const Transvoxel::MeshArrays &src) {
PoolVector<Vector3> vertices;
PoolVector<Vector3> normals;
PoolVector<Color> extra;
PoolVector<Vector2> uv;
PoolVector<int> indices;
raw_copy_to(vertices, src.vertices);
raw_copy_to(extra, src.extra);
raw_copy_to(indices, src.indices);
arrays.resize(Mesh::ARRAY_MAX);
arrays[Mesh::ARRAY_VERTEX] = vertices;
if (src.normals.size() != 0) {
raw_copy_to(normals, src.normals);
arrays[Mesh::ARRAY_NORMAL] = normals;
}
if (src.uv.size() != 0) {
raw_copy_to(uv, src.uv);
arrays[Mesh::ARRAY_TEX_UV] = uv;
}
arrays[Mesh::ARRAY_COLOR] = extra;
arrays[Mesh::ARRAY_INDEX] = indices;
}
template <typename T>
static void remap_vertex_array(const std::vector<T> &src_data, std::vector<T> &dst_data,
const std::vector<unsigned int> &remap_indices, unsigned int unique_vertex_count) {
if (src_data.size() == 0) {
dst_data.clear();
return;
}
dst_data.resize(unique_vertex_count);
meshopt_remapVertexBuffer(&dst_data[0], &src_data[0], src_data.size(), sizeof(T), remap_indices.data());
}
static void simplify(const Transvoxel::MeshArrays &src_mesh, Transvoxel::MeshArrays &dst_mesh,
float p_target_ratio, float p_error_threshold) {
VOXEL_PROFILE_SCOPE();
// Gather and check input
ERR_FAIL_COND(p_target_ratio < 0.f || p_target_ratio > 1.f);
ERR_FAIL_COND(p_error_threshold < 0.f || p_error_threshold > 1.f);
ERR_FAIL_COND(src_mesh.vertices.size() < 3);
ERR_FAIL_COND(src_mesh.indices.size() < 3);
const unsigned int target_index_count = p_target_ratio * src_mesh.indices.size();
static thread_local std::vector<unsigned int> lod_indices;
lod_indices.clear();
lod_indices.resize(src_mesh.indices.size());
float lod_error = 0.f;
// Simplify
{
VOXEL_PROFILE_SCOPE_NAMED("meshopt_simplify");
const unsigned int lod_index_count = meshopt_simplify(
&lod_indices[0], reinterpret_cast<const unsigned int *>(src_mesh.indices.data()), src_mesh.indices.size(),
&src_mesh.vertices[0].x, src_mesh.vertices.size(),
sizeof(Vector3), target_index_count, p_error_threshold, &lod_error);
lod_indices.resize(lod_index_count);
}
// Produce output
Array surface;
surface.resize(Mesh::ARRAY_MAX);
static thread_local std::vector<unsigned int> remap_indices;
remap_indices.clear();
remap_indices.resize(src_mesh.vertices.size());
const unsigned int unique_vertex_count = meshopt_optimizeVertexFetchRemap(
&remap_indices[0], lod_indices.data(), lod_indices.size(), src_mesh.vertices.size());
remap_vertex_array(src_mesh.vertices, dst_mesh.vertices, remap_indices, unique_vertex_count);
remap_vertex_array(src_mesh.normals, dst_mesh.normals, remap_indices, unique_vertex_count);
remap_vertex_array(src_mesh.extra, dst_mesh.extra, remap_indices, unique_vertex_count);
remap_vertex_array(src_mesh.uv, dst_mesh.uv, remap_indices, unique_vertex_count);
dst_mesh.indices.resize(lod_indices.size());
// TODO Not sure if arguments are correct
meshopt_remapIndexBuffer(reinterpret_cast<unsigned int *>(dst_mesh.indices.data()),
lod_indices.data(), lod_indices.size(), remap_indices.data());
}
void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelMesher::Input &input) {
VOXEL_PROFILE_SCOPE();
static thread_local Transvoxel::Cache s_cache;
static thread_local Transvoxel::MeshArrays s_mesh_arrays;
static thread_local Transvoxel::MeshArrays s_simplified_mesh_arrays;
const int sdf_channel = VoxelBuffer::CHANNEL_SDF;
// Initialize dynamic memory:
// These vectors are re-used.
// We don't know in advance how much geometry we are going to produce.
// Once capacity is big enough, no more memory should be allocated
s_mesh_arrays.clear();
const VoxelBuffer &voxels = input.voxels;
if (voxels.is_uniform(sdf_channel)) {
// There won't be anything to polygonize since the SDF has no variations, so it can't cross the isolevel
return;
}
// const uint64_t time_before = OS::get_singleton()->get_ticks_usec();
Transvoxel::DefaultTextureIndicesData default_texture_indices_data = Transvoxel::build_regular_mesh(
voxels, sdf_channel, input.lod, static_cast<Transvoxel::TexturingMode>(_texture_mode), s_cache,
s_mesh_arrays);
if (s_mesh_arrays.vertices.size() == 0) {
// The mesh can be empty
return;
}
Array regular_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.
// See https://github.com/zeux/meshoptimizer/issues/158
simplify(s_mesh_arrays, s_simplified_mesh_arrays,
_mesh_optimization_params.target_ratio, _mesh_optimization_params.error_threshold);
fill_surface_arrays(regular_arrays, s_simplified_mesh_arrays);
} else {
fill_surface_arrays(regular_arrays, s_mesh_arrays);
}
output.surfaces.push_back(regular_arrays);
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
VOXEL_PROFILE_SCOPE();
s_mesh_arrays.clear();
Transvoxel::build_transition_mesh(voxels, sdf_channel, dir, input.lod,
static_cast<Transvoxel::TexturingMode>(_texture_mode), s_cache, s_mesh_arrays,
default_texture_indices_data);
if (s_mesh_arrays.vertices.size() == 0) {
continue;
}
Array transition_arrays;
fill_surface_arrays(transition_arrays, s_mesh_arrays);
output.transition_surfaces[dir].push_back(transition_arrays);
}
// const uint64_t time_spent = OS::get_singleton()->get_ticks_usec() - time_before;
// print_line(String("VoxelMesherTransvoxel spent {0} us").format(varray(time_spent)));
output.primitive_type = Mesh::PRIMITIVE_TRIANGLES;
output.compression_flags = MESH_COMPRESSION_FLAGS;
}
// TODO For testing at the moment
Ref<ArrayMesh> VoxelMesherTransvoxel::build_transition_mesh(Ref<VoxelBuffer> voxels, int direction) {
static thread_local Transvoxel::Cache s_cache;
static thread_local Transvoxel::MeshArrays s_mesh_arrays;
s_mesh_arrays.clear();
ERR_FAIL_COND_V(voxels.is_null(), Ref<ArrayMesh>());
if (voxels->is_uniform(VoxelBuffer::CHANNEL_SDF)) {
// Uniform SDF won't produce any surface
return Ref<ArrayMesh>();
}
// TODO We need to output transition meshes through the generic interface, they are part of the result
// For now we can't support proper texture indices in this specific case
Transvoxel::DefaultTextureIndicesData default_texture_indices_data;
default_texture_indices_data.use = false;
Transvoxel::build_transition_mesh(**voxels, VoxelBuffer::CHANNEL_SDF, direction, 0,
static_cast<Transvoxel::TexturingMode>(_texture_mode), s_cache, s_mesh_arrays,
default_texture_indices_data);
Ref<ArrayMesh> mesh;
if (s_mesh_arrays.vertices.size() == 0) {
return mesh;
}
Array arrays;
fill_surface_arrays(arrays, s_mesh_arrays);
mesh.instance();
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays, Array(), MESH_COMPRESSION_FLAGS);
return mesh;
}
void VoxelMesherTransvoxel::set_texturing_mode(TexturingMode mode) {
if (mode != _texture_mode) {
_texture_mode = mode;
emit_changed();
}
}
VoxelMesherTransvoxel::TexturingMode VoxelMesherTransvoxel::get_texturing_mode() const {
return _texture_mode;
}
void VoxelMesherTransvoxel::set_mesh_optimization_enabled(bool enabled) {
_mesh_optimization_params.enabled = enabled;
}
bool VoxelMesherTransvoxel::is_mesh_optimization_enabled() const {
return _mesh_optimization_params.enabled;
}
void VoxelMesherTransvoxel::set_mesh_optimization_error_threshold(float threshold) {
_mesh_optimization_params.error_threshold = clamp(threshold, 0.f, 1.f);
}
float VoxelMesherTransvoxel::get_mesh_optimization_error_threshold() const {
return _mesh_optimization_params.error_threshold;
}
void VoxelMesherTransvoxel::set_mesh_optimization_target_ratio(float ratio) {
_mesh_optimization_params.target_ratio = clamp(ratio, 0.f, 1.f);
}
float VoxelMesherTransvoxel::get_mesh_optimization_target_ratio() const {
return _mesh_optimization_params.target_ratio;
}
void VoxelMesherTransvoxel::_bind_methods() {
ClassDB::bind_method(D_METHOD("build_transition_mesh", "voxel_buffer", "direction"),
&VoxelMesherTransvoxel::build_transition_mesh);
ClassDB::bind_method(D_METHOD("set_texturing_mode", "mode"), &VoxelMesherTransvoxel::set_texturing_mode);
ClassDB::bind_method(D_METHOD("get_texturing_mode"), &VoxelMesherTransvoxel::get_texturing_mode);
ClassDB::bind_method(D_METHOD("set_mesh_optimization_enabled", "enabled"),
&VoxelMesherTransvoxel::set_mesh_optimization_enabled);
ClassDB::bind_method(D_METHOD("is_mesh_optimization_enabled"),
&VoxelMesherTransvoxel::is_mesh_optimization_enabled);
ClassDB::bind_method(D_METHOD("set_mesh_optimization_error_threshold", "threshold"),
&VoxelMesherTransvoxel::set_mesh_optimization_error_threshold);
ClassDB::bind_method(D_METHOD("get_mesh_optimization_error_threshold"),
&VoxelMesherTransvoxel::get_mesh_optimization_error_threshold);
ClassDB::bind_method(D_METHOD("set_mesh_optimization_target_ratio", "ratio"),
&VoxelMesherTransvoxel::set_mesh_optimization_target_ratio);
ClassDB::bind_method(D_METHOD("get_mesh_optimization_target_ratio"),
&VoxelMesherTransvoxel::get_mesh_optimization_target_ratio);
ADD_PROPERTY(PropertyInfo(
Variant::INT, "texturing_mode", PROPERTY_HINT_ENUM, "None,4-blend over 16 textures (4 bits)"),
"set_texturing_mode", "get_texturing_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mesh_optimization_enabled"),
"set_mesh_optimization_enabled", "is_mesh_optimization_enabled");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "mesh_optimization_error_threshold"),
"set_mesh_optimization_error_threshold", "get_mesh_optimization_error_threshold");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "mesh_optimization_target_ratio"),
"set_mesh_optimization_target_ratio", "get_mesh_optimization_target_ratio");
BIND_ENUM_CONSTANT(TEXTURES_NONE);
BIND_ENUM_CONSTANT(TEXTURES_BLEND_4_OVER_16);
}