godot_voxel/meshers/blocky/voxel_mesher_blocky.cpp
2022-05-17 23:10:11 +01:00

701 lines
25 KiB
C++

#include "voxel_mesher_blocky.h"
#include "../../constants/cube_tables.h"
#include "../../storage/voxel_buffer_internal.h"
#include "../../util/godot/funcs.h"
#include "../../util/math/conv.h"
#include "../../util/span.h"
#include <core/os/os.h>
namespace zylann::voxel {
// Utility functions
namespace {
const int g_opposite_side[6] = {
Cube::SIDE_NEGATIVE_X, //
Cube::SIDE_POSITIVE_X, //
Cube::SIDE_POSITIVE_Y, //
Cube::SIDE_NEGATIVE_Y, //
Cube::SIDE_POSITIVE_Z, //
Cube::SIDE_NEGATIVE_Z //
};
inline bool is_face_visible(const VoxelBlockyLibrary::BakedData &lib, const VoxelBlockyModel::BakedData &vt,
uint32_t other_voxel_id, int side) {
if (other_voxel_id < lib.models.size()) {
const VoxelBlockyModel::BakedData &other_vt = lib.models[other_voxel_id];
if (other_vt.empty || (other_vt.transparency_index > vt.transparency_index)) {
return true;
} else {
const unsigned int ai = vt.model.side_pattern_indices[side];
const unsigned int bi = other_vt.model.side_pattern_indices[g_opposite_side[side]];
// Patterns are not the same, and B does not occlude A
return (ai != bi) && !lib.get_side_pattern_occlusion(bi, ai);
}
}
return true;
}
inline bool contributes_to_ao(const VoxelBlockyLibrary::BakedData &lib, uint32_t voxel_id) {
if (voxel_id < lib.models.size()) {
const VoxelBlockyModel::BakedData &t = lib.models[voxel_id];
return t.contributes_to_ao;
}
return true;
}
static thread_local std::vector<int> tls_index_offsets;
} // namespace
template <typename Type_T>
void generate_blocky_mesh(std::vector<VoxelMesherBlocky::Arrays> &out_arrays_per_material,
VoxelMesher::Output::CollisionSurface *collision_surface, const Span<Type_T> type_buffer,
const Vector3i block_size, const VoxelBlockyLibrary::BakedData &library, bool bake_occlusion,
float baked_occlusion_darkness) {
// TODO Optimization: not sure if this mandates a template function. There is so much more happening in this
// function other than reading voxels, although reading is on the hottest path. It needs to be profiled. If
// changing makes no difference, we could use a function pointer or switch inside instead to reduce executable size.
ERR_FAIL_COND(block_size.x < static_cast<int>(2 * VoxelMesherBlocky::PADDING) ||
block_size.y < static_cast<int>(2 * VoxelMesherBlocky::PADDING) ||
block_size.z < static_cast<int>(2 * VoxelMesherBlocky::PADDING));
// Build lookup tables so to speed up voxel access.
// These are values to add to an address in order to get given neighbor.
const int row_size = block_size.y;
const int deck_size = block_size.x * row_size;
// Data must be padded, hence the off-by-one
const Vector3i min = Vector3iUtil::create(VoxelMesherBlocky::PADDING);
const Vector3i max = block_size - Vector3iUtil::create(VoxelMesherBlocky::PADDING);
std::vector<int> &index_offsets = tls_index_offsets;
index_offsets.clear();
index_offsets.resize(out_arrays_per_material.size(), 0);
int collision_surface_index_offset = 0;
FixedArray<int, Cube::SIDE_COUNT> side_neighbor_lut;
side_neighbor_lut[Cube::SIDE_LEFT] = row_size;
side_neighbor_lut[Cube::SIDE_RIGHT] = -row_size;
side_neighbor_lut[Cube::SIDE_BACK] = -deck_size;
side_neighbor_lut[Cube::SIDE_FRONT] = deck_size;
side_neighbor_lut[Cube::SIDE_BOTTOM] = -1;
side_neighbor_lut[Cube::SIDE_TOP] = 1;
FixedArray<int, Cube::EDGE_COUNT> edge_neighbor_lut;
edge_neighbor_lut[Cube::EDGE_BOTTOM_BACK] =
side_neighbor_lut[Cube::SIDE_BOTTOM] + side_neighbor_lut[Cube::SIDE_BACK];
edge_neighbor_lut[Cube::EDGE_BOTTOM_FRONT] =
side_neighbor_lut[Cube::SIDE_BOTTOM] + side_neighbor_lut[Cube::SIDE_FRONT];
edge_neighbor_lut[Cube::EDGE_BOTTOM_LEFT] =
side_neighbor_lut[Cube::SIDE_BOTTOM] + side_neighbor_lut[Cube::SIDE_LEFT];
edge_neighbor_lut[Cube::EDGE_BOTTOM_RIGHT] =
side_neighbor_lut[Cube::SIDE_BOTTOM] + side_neighbor_lut[Cube::SIDE_RIGHT];
edge_neighbor_lut[Cube::EDGE_BACK_LEFT] = side_neighbor_lut[Cube::SIDE_BACK] + side_neighbor_lut[Cube::SIDE_LEFT];
edge_neighbor_lut[Cube::EDGE_BACK_RIGHT] = side_neighbor_lut[Cube::SIDE_BACK] + side_neighbor_lut[Cube::SIDE_RIGHT];
edge_neighbor_lut[Cube::EDGE_FRONT_LEFT] = side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_LEFT];
edge_neighbor_lut[Cube::EDGE_FRONT_RIGHT] =
side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_RIGHT];
edge_neighbor_lut[Cube::EDGE_TOP_BACK] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_BACK];
edge_neighbor_lut[Cube::EDGE_TOP_FRONT] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_FRONT];
edge_neighbor_lut[Cube::EDGE_TOP_LEFT] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_LEFT];
edge_neighbor_lut[Cube::EDGE_TOP_RIGHT] = side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_RIGHT];
FixedArray<int, Cube::CORNER_COUNT> corner_neighbor_lut;
corner_neighbor_lut[Cube::CORNER_BOTTOM_BACK_LEFT] = side_neighbor_lut[Cube::SIDE_BOTTOM] +
side_neighbor_lut[Cube::SIDE_BACK] + side_neighbor_lut[Cube::SIDE_LEFT];
corner_neighbor_lut[Cube::CORNER_BOTTOM_BACK_RIGHT] = side_neighbor_lut[Cube::SIDE_BOTTOM] +
side_neighbor_lut[Cube::SIDE_BACK] + side_neighbor_lut[Cube::SIDE_RIGHT];
corner_neighbor_lut[Cube::CORNER_BOTTOM_FRONT_RIGHT] = side_neighbor_lut[Cube::SIDE_BOTTOM] +
side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_RIGHT];
corner_neighbor_lut[Cube::CORNER_BOTTOM_FRONT_LEFT] = side_neighbor_lut[Cube::SIDE_BOTTOM] +
side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_LEFT];
corner_neighbor_lut[Cube::CORNER_TOP_BACK_LEFT] =
side_neighbor_lut[Cube::SIDE_TOP] + side_neighbor_lut[Cube::SIDE_BACK] + side_neighbor_lut[Cube::SIDE_LEFT];
corner_neighbor_lut[Cube::CORNER_TOP_BACK_RIGHT] = side_neighbor_lut[Cube::SIDE_TOP] +
side_neighbor_lut[Cube::SIDE_BACK] + side_neighbor_lut[Cube::SIDE_RIGHT];
corner_neighbor_lut[Cube::CORNER_TOP_FRONT_RIGHT] = side_neighbor_lut[Cube::SIDE_TOP] +
side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_RIGHT];
corner_neighbor_lut[Cube::CORNER_TOP_FRONT_LEFT] = side_neighbor_lut[Cube::SIDE_TOP] +
side_neighbor_lut[Cube::SIDE_FRONT] + side_neighbor_lut[Cube::SIDE_LEFT];
//uint64_t time_prep = Time::get_singleton()->get_ticks_usec() - time_before;
//time_before = Time::get_singleton()->get_ticks_usec();
for (unsigned int z = min.z; z < (unsigned int)max.z; ++z) {
for (unsigned int x = min.x; x < (unsigned int)max.x; ++x) {
for (unsigned int y = min.y; y < (unsigned int)max.y; ++y) {
// min and max are chosen such that you can visit 1 neighbor away from the current voxel without size
// check
const int voxel_index = y + x * row_size + z * deck_size;
const int voxel_id = type_buffer[voxel_index];
if (voxel_id == VoxelBlockyModel::AIR_ID || !library.has_model(voxel_id)) {
continue;
}
const VoxelBlockyModel::BakedData &voxel = library.models[voxel_id];
const VoxelBlockyModel::BakedData::Model &model = voxel.model;
// Hybrid approach: extract cube faces and decimate those that aren't visible,
// and still allow voxels to have geometry that is not a cube.
// Sides
for (unsigned int side = 0; side < Cube::SIDE_COUNT; ++side) {
if ((model.empty_sides_mask & (1 << side)) != 0) {
// This side is empty
continue;
}
const uint32_t neighbor_voxel_id = type_buffer[voxel_index + side_neighbor_lut[side]];
if (!is_face_visible(library, voxel, neighbor_voxel_id, side)) {
continue;
}
// The face is visible
int shaded_corner[8] = { 0 };
if (bake_occlusion) {
// Combinatory solution for
// https://0fps.net/2013/07/03/ambient-occlusion-for-minecraft-like-worlds/ (inverted)
// function vertexAO(side1, side2, corner) {
// if(side1 && side2) {
// return 0
// }
// return 3 - (side1 + side2 + corner)
// }
for (unsigned int j = 0; j < 4; ++j) {
const unsigned int edge = Cube::g_side_edges[side][j];
const int edge_neighbor_id = type_buffer[voxel_index + edge_neighbor_lut[edge]];
if (contributes_to_ao(library, edge_neighbor_id)) {
++shaded_corner[Cube::g_edge_corners[edge][0]];
++shaded_corner[Cube::g_edge_corners[edge][1]];
}
}
for (unsigned int j = 0; j < 4; ++j) {
const unsigned int corner = Cube::g_side_corners[side][j];
if (shaded_corner[corner] == 2) {
shaded_corner[corner] = 3;
} else {
const int corner_neigbor_id = type_buffer[voxel_index + corner_neighbor_lut[corner]];
if (contributes_to_ao(library, corner_neigbor_id)) {
++shaded_corner[corner];
}
}
}
}
// Subtracting 1 because the data is padded
Vector3f pos(x - 1, y - 1, z - 1);
for (unsigned int surface_index = 0; surface_index < model.surface_count; ++surface_index) {
const VoxelBlockyModel::BakedData::Surface &surface = model.surfaces[surface_index];
VoxelMesherBlocky::Arrays &arrays = out_arrays_per_material[surface.material_id];
ZN_ASSERT(surface.material_id >= 0 && surface.material_id < index_offsets.size());
int &index_offset = index_offsets[surface.material_id];
const std::vector<Vector3f> &side_positions = surface.side_positions[side];
const unsigned int vertex_count = side_positions.size();
const std::vector<Vector2f> &side_uvs = surface.side_uvs[side];
const std::vector<float> &side_tangents = surface.side_tangents[side];
// Append vertices of the faces in one go, don't use push_back
{
const int append_index = arrays.positions.size();
arrays.positions.resize(arrays.positions.size() + vertex_count);
Vector3f *w = arrays.positions.data() + append_index;
for (unsigned int i = 0; i < vertex_count; ++i) {
w[i] = side_positions[i] + pos;
}
}
{
const int append_index = arrays.uvs.size();
arrays.uvs.resize(arrays.uvs.size() + vertex_count);
memcpy(arrays.uvs.data() + append_index, side_uvs.data(), vertex_count * sizeof(Vector2f));
}
if (side_tangents.size() > 0) {
const int append_index = arrays.tangents.size();
arrays.tangents.resize(arrays.tangents.size() + vertex_count * 4);
memcpy(arrays.tangents.data() + append_index, side_tangents.data(),
(vertex_count * 4) * sizeof(float));
}
{
const int append_index = arrays.normals.size();
arrays.normals.resize(arrays.normals.size() + vertex_count);
Vector3f *w = arrays.normals.data() + append_index;
for (unsigned int i = 0; i < vertex_count; ++i) {
w[i] = to_vec3f(Cube::g_side_normals[side]);
}
}
{
const int append_index = arrays.colors.size();
arrays.colors.resize(arrays.colors.size() + vertex_count);
Color *w = arrays.colors.data() + append_index;
const Color modulate_color = voxel.color;
if (bake_occlusion) {
for (unsigned int i = 0; i < vertex_count; ++i) {
Vector3f vertex_pos = side_positions[i];
// General purpose occlusion colouring.
// TODO Optimize for cubes
// TODO Fix occlusion inconsistency caused by triangles orientation? Not sure if
// worth it
float shade = 0;
for (unsigned int j = 0; j < 4; ++j) {
unsigned int corner = Cube::g_side_corners[side][j];
if (shaded_corner[corner]) {
float s = baked_occlusion_darkness *
static_cast<float>(shaded_corner[corner]);
//float k = 1.f - Cube::g_corner_position[corner].distance_to(v);
float k = 1.f -
Cube::g_corner_position[corner].distance_squared_to(vertex_pos);
if (k < 0.0) {
k = 0.0;
}
s *= k;
if (s > shade) {
shade = s;
}
}
}
const float gs = 1.0 - shade;
w[i] = Color(gs, gs, gs) * modulate_color;
}
} else {
for (unsigned int i = 0; i < vertex_count; ++i) {
w[i] = modulate_color;
}
}
}
const std::vector<int> &side_indices = surface.side_indices[side];
const unsigned int index_count = side_indices.size();
{
int i = arrays.indices.size();
arrays.indices.resize(arrays.indices.size() + index_count);
int *w = arrays.indices.data();
for (unsigned int j = 0; j < index_count; ++j) {
w[i++] = index_offset + side_indices[j];
}
}
if (collision_surface != nullptr && surface.collision_enabled) {
std::vector<Vector3f> &dst_positions = collision_surface->positions;
std::vector<int> &dst_indices = collision_surface->indices;
{
const unsigned int append_index = dst_positions.size();
dst_positions.resize(dst_positions.size() + vertex_count);
Vector3f *w = dst_positions.data() + append_index;
for (unsigned int i = 0; i < vertex_count; ++i) {
w[i] = side_positions[i] + pos;
}
}
{
int i = dst_indices.size();
dst_indices.resize(dst_indices.size() + index_count);
int *w = dst_indices.data();
for (unsigned int j = 0; j < index_count; ++j) {
w[i++] = collision_surface_index_offset + side_indices[j];
}
}
collision_surface_index_offset += vertex_count;
}
index_offset += vertex_count;
}
}
// Inside
for (unsigned int surface_index = 0; surface_index < model.surface_count; ++surface_index) {
const VoxelBlockyModel::BakedData::Surface &surface = model.surfaces[surface_index];
if (surface.positions.size() == 0) {
continue;
}
// TODO Get rid of push_backs
VoxelMesherBlocky::Arrays &arrays = out_arrays_per_material[surface.material_id];
ZN_ASSERT(surface.material_id >= 0 && surface.material_id < index_offsets.size());
int &index_offset = index_offsets[surface.material_id];
const std::vector<Vector3f> &positions = surface.positions;
const unsigned int vertex_count = positions.size();
const Color modulate_color = voxel.color;
const std::vector<Vector3f> &normals = surface.normals;
const std::vector<Vector2f> &uvs = surface.uvs;
const std::vector<float> &tangents = surface.tangents;
const Vector3f pos(x - 1, y - 1, z - 1);
if (tangents.size() > 0) {
const int append_index = arrays.tangents.size();
arrays.tangents.resize(arrays.tangents.size() + vertex_count * 4);
memcpy(arrays.tangents.data() + append_index, tangents.data(),
(vertex_count * 4) * sizeof(float));
}
for (unsigned int i = 0; i < vertex_count; ++i) {
arrays.normals.push_back(normals[i]);
arrays.uvs.push_back(uvs[i]);
arrays.positions.push_back(positions[i] + pos);
// TODO handle ambient occlusion on inner parts
arrays.colors.push_back(modulate_color);
}
const std::vector<int> &indices = surface.indices;
const unsigned int index_count = indices.size();
for (unsigned int i = 0; i < index_count; ++i) {
arrays.indices.push_back(index_offset + indices[i]);
}
if (collision_surface != nullptr && surface.collision_enabled) {
std::vector<Vector3f> &dst_positions = collision_surface->positions;
std::vector<int> &dst_indices = collision_surface->indices;
for (unsigned int i = 0; i < vertex_count; ++i) {
dst_positions.push_back(positions[i] + pos);
}
for (unsigned int i = 0; i < index_count; ++i) {
dst_indices.push_back(collision_surface_index_offset + indices[i]);
}
collision_surface_index_offset += vertex_count;
}
index_offset += vertex_count;
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
thread_local VoxelMesherBlocky::Cache VoxelMesherBlocky::_cache;
VoxelMesherBlocky::VoxelMesherBlocky() {
set_padding(PADDING, PADDING);
}
VoxelMesherBlocky::~VoxelMesherBlocky() {}
void VoxelMesherBlocky::set_library(Ref<VoxelBlockyLibrary> library) {
RWLockWrite wlock(_parameters_lock);
_parameters.library = library;
}
Ref<VoxelBlockyLibrary> VoxelMesherBlocky::get_library() const {
RWLockRead rlock(_parameters_lock);
return _parameters.library;
}
void VoxelMesherBlocky::set_occlusion_darkness(float darkness) {
RWLockWrite wlock(_parameters_lock);
_parameters.baked_occlusion_darkness = math::clamp(darkness, 0.0f, 1.0f);
}
float VoxelMesherBlocky::get_occlusion_darkness() const {
RWLockRead rlock(_parameters_lock);
return _parameters.baked_occlusion_darkness;
}
void VoxelMesherBlocky::set_occlusion_enabled(bool enable) {
RWLockWrite wlock(_parameters_lock);
_parameters.bake_occlusion = enable;
}
bool VoxelMesherBlocky::get_occlusion_enabled() const {
RWLockRead rlock(_parameters_lock);
return _parameters.bake_occlusion;
}
void VoxelMesherBlocky::build(VoxelMesher::Output &output, const VoxelMesher::Input &input) {
const int channel = VoxelBufferInternal::CHANNEL_TYPE;
Parameters params;
{
RWLockRead rlock(_parameters_lock);
params = _parameters;
}
ERR_FAIL_COND(params.library.is_null());
Cache &cache = _cache;
std::vector<Arrays> &arrays_per_material = cache.arrays_per_material;
for (unsigned int i = 0; i < arrays_per_material.size(); ++i) {
Arrays &a = arrays_per_material[i];
a.clear();
}
float baked_occlusion_darkness = 0;
if (params.bake_occlusion) {
baked_occlusion_darkness = params.baked_occlusion_darkness / 3.0f;
}
// The technique is Culled faces.
// Could be improved with greedy meshing: https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/
// However I don't feel it's worth it yet:
// - Not so much gain for organic worlds with lots of texture variations
// - Works well with cubes but not with any shape
// - Slower
// => Could be implemented in a separate class?
const VoxelBufferInternal &voxels = input.voxels;
#ifdef TOOLS_ENABLED
if (input.lod != 0) {
WARN_PRINT("VoxelMesherBlocky received lod != 0, it is not supported");
}
#endif
// Iterate 3D padded data to extract voxel faces.
// This is the most intensive job in this class, so all required data should be as fit as possible.
// The buffer we receive MUST be dense (i.e not compressed, and channels allocated).
// That means we can use raw pointers to voxel data inside instead of using the higher-level getters,
// and then save a lot of time.
if (voxels.get_channel_compression(channel) == VoxelBufferInternal::COMPRESSION_UNIFORM) {
// All voxels have the same type.
// If it's all air, nothing to do. If it's all cubes, nothing to do either.
// TODO Handle edge case of uniform block with non-cubic voxels!
// If the type of voxel still produces geometry in this situation (which is an absurd use case but not an
// error), decompress into a backing array to still allow the use of the same algorithm.
return;
} else if (voxels.get_channel_compression(channel) != VoxelBufferInternal::COMPRESSION_NONE) {
// No other form of compression is allowed
ERR_PRINT("VoxelMesherBlocky received unsupported voxel compression");
return;
}
Span<uint8_t> raw_channel;
if (!voxels.get_channel_raw(channel, raw_channel)) {
/* _
// | \
// /\ \\
// / /|\\\
// | |\ \\\
// | \_\ \\|
// | | )
// \ | |
// \ /
*/
// Case supposedly handled before...
ERR_PRINT("Something wrong happened");
return;
}
const Vector3i block_size = voxels.get_size();
const VoxelBufferInternal::Depth channel_depth = voxels.get_channel_depth(channel);
VoxelMesher::Output::CollisionSurface *collision_surface = nullptr;
if (input.collision_hint) {
collision_surface = &output.collision_surface;
}
unsigned int material_count = 0;
{
// We can only access baked data. Only this data is made for multithreaded access.
RWLockRead lock(params.library->get_baked_data_rw_lock());
const VoxelBlockyLibrary::BakedData &library_baked_data = params.library->get_baked_data();
// There must be at least one "material" in case none of them are assigned.
// This will produce a single surface, which will be rendered with a default material.
material_count = math::max(library_baked_data.indexed_materials_count, 1u);
if (arrays_per_material.size() < material_count) {
arrays_per_material.resize(material_count);
}
switch (channel_depth) {
case VoxelBufferInternal::DEPTH_8_BIT:
generate_blocky_mesh(arrays_per_material, collision_surface, raw_channel, block_size,
library_baked_data, params.bake_occlusion, baked_occlusion_darkness);
break;
case VoxelBufferInternal::DEPTH_16_BIT:
generate_blocky_mesh(arrays_per_material, collision_surface,
raw_channel.reinterpret_cast_to<uint16_t>(), block_size, library_baked_data,
params.bake_occlusion, baked_occlusion_darkness);
break;
default:
ERR_PRINT("Unsupported voxel depth");
return;
}
output.surfaces.resize(material_count);
}
// TODO Optimization: we could return a single byte array and use Mesh::add_surface down the line?
// That API does not seem to exist yet though.
for (unsigned int i = 0; i < material_count; ++i) {
const Arrays &arrays = arrays_per_material[i];
if (arrays.positions.size() != 0) {
Array mesh_arrays;
mesh_arrays.resize(Mesh::ARRAY_MAX);
{
PackedVector3Array positions;
PackedVector2Array uvs;
PackedVector3Array normals;
PackedColorArray colors;
PackedInt32Array indices;
copy_to(positions, arrays.positions);
copy_to(uvs, arrays.uvs);
copy_to(normals, arrays.normals);
raw_copy_to(colors, arrays.colors);
raw_copy_to(indices, arrays.indices);
mesh_arrays[Mesh::ARRAY_VERTEX] = positions;
mesh_arrays[Mesh::ARRAY_TEX_UV] = uvs;
mesh_arrays[Mesh::ARRAY_NORMAL] = normals;
mesh_arrays[Mesh::ARRAY_COLOR] = colors;
mesh_arrays[Mesh::ARRAY_INDEX] = indices;
if (arrays.tangents.size() > 0) {
PackedFloat32Array tangents;
raw_copy_to(tangents, arrays.tangents);
mesh_arrays[Mesh::ARRAY_TANGENT] = tangents;
}
}
ZN_ASSERT(i < output.surfaces.size());
Output::Surface &surface = output.surfaces[i];
surface.arrays = mesh_arrays;
}
// else {
// // Empty
// output.surfaces.push_back(Output::Surface());
// }
}
output.primitive_type = Mesh::PRIMITIVE_TRIANGLES;
}
Ref<Resource> VoxelMesherBlocky::duplicate(bool p_subresources) const {
Parameters params;
{
RWLockRead rlock(_parameters_lock);
params = _parameters;
}
if (p_subresources && params.library.is_valid()) {
params.library = params.library->duplicate(true);
}
VoxelMesherBlocky *c = memnew(VoxelMesherBlocky);
c->_parameters = params;
return c;
}
int VoxelMesherBlocky::get_used_channels_mask() const {
return (1 << VoxelBufferInternal::CHANNEL_TYPE);
}
Ref<Material> VoxelMesherBlocky::get_material_by_index(unsigned int index) const {
Ref<VoxelBlockyLibrary> lib = get_library();
if (lib.is_null()) {
return Ref<Material>();
}
return lib->get_material_by_index(index);
}
#ifdef TOOLS_ENABLED
void VoxelMesherBlocky::get_configuration_warnings(TypedArray<String> &out_warnings) const {
Ref<VoxelBlockyLibrary> library = get_library();
if (library.is_null()) {
out_warnings.append(TTR(VoxelMesherBlocky::get_class_static() + " has no " +
VoxelBlockyLibrary::get_class_static() + " assigned."));
return;
}
if (library->get_voxel_count() == 0) {
out_warnings.append(TTR("The " + VoxelBlockyLibrary::get_class_static() + " assigned to " +
VoxelMesherBlocky::get_class_static() + " has an empty list of " +
VoxelBlockyModel::get_class_static() + "s."));
return;
}
bool has_solid_model = false;
for (unsigned int i = 0; i < library->get_voxel_count() && !has_solid_model; ++i) {
const VoxelBlockyModel &model = library->get_voxel_const(i);
switch (model.get_geometry_type()) {
case VoxelBlockyModel::GEOMETRY_NONE:
break;
case VoxelBlockyModel::GEOMETRY_CUBE:
has_solid_model = true;
break;
case VoxelBlockyModel::GEOMETRY_CUSTOM_MESH:
has_solid_model = model.get_custom_mesh().is_valid();
break;
default:
ERR_PRINT("Unknown enum value");
break;
}
}
if (!has_solid_model) {
out_warnings.append(TTR("The " + VoxelBlockyLibrary::get_class_static() + " assigned to " +
VoxelMesherBlocky::get_class_static() + " only has empty " + VoxelBlockyModel::get_class_static() +
"s."));
}
}
#endif // TOOLS_ENABLED
void VoxelMesherBlocky::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_library", "voxel_library"), &VoxelMesherBlocky::set_library);
ClassDB::bind_method(D_METHOD("get_library"), &VoxelMesherBlocky::get_library);
ClassDB::bind_method(D_METHOD("set_occlusion_enabled", "enable"), &VoxelMesherBlocky::set_occlusion_enabled);
ClassDB::bind_method(D_METHOD("get_occlusion_enabled"), &VoxelMesherBlocky::get_occlusion_enabled);
ClassDB::bind_method(D_METHOD("set_occlusion_darkness", "value"), &VoxelMesherBlocky::set_occlusion_darkness);
ClassDB::bind_method(D_METHOD("get_occlusion_darkness"), &VoxelMesherBlocky::get_occlusion_darkness);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE,
VoxelBlockyLibrary::get_class_static(),
PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT),
"set_library", "get_library");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "occlusion_enabled"), "set_occlusion_enabled", "get_occlusion_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "occlusion_darkness", PROPERTY_HINT_RANGE, "0,1,0.01"),
"set_occlusion_darkness", "get_occlusion_darkness");
}
} // namespace zylann::voxel