Implement LOD transitions in VoxelLodTerrain
- VoxelBlock contains 6 optional transition meshes - VoxelLodTerrain calculates transition masks when block visibility changes - VoxelMesher can now specify different min and max paddings - Fix Cube::SIDE_POSITIVE/NEGATIVE_X/Y/Z enum not matching Cube::g_side_normals - Convert transition masks to make up for the Cube:: inconsistency for now
This commit is contained in:
parent
44f84a9422
commit
ebc6be35d1
@ -54,10 +54,14 @@ const int g_side_quad_triangles[SIDE_COUNT][6] = {
|
||||
//const unsigned int g_side_sign[SIDE_COUNT] = { 0, 1, 0, 1, 0, 1 };
|
||||
|
||||
const Vector3i g_side_normals[SIDE_COUNT] = {
|
||||
// TODO Wrong! Left should be -X
|
||||
Vector3i(1, 0, 0), // LEFT
|
||||
Vector3i(-1, 0, 0), // RIGHT
|
||||
|
||||
Vector3i(0, -1, 0), // BOTTOM
|
||||
Vector3i(0, 1, 0), // TOP
|
||||
|
||||
// TODO Wrong! Front should be -Z
|
||||
Vector3i(0, 0, -1), // BACK
|
||||
Vector3i(0, 0, 1), // FRONT
|
||||
};
|
||||
@ -161,5 +165,4 @@ const Vector3i g_moore_neighboring_3d[MOORE_NEIGHBORING_3D_COUNT] = {
|
||||
Vector3i(1, 1, 1),
|
||||
};
|
||||
|
||||
} // namespace CubeTables
|
||||
|
||||
} // namespace Cube
|
||||
|
@ -18,14 +18,14 @@ enum Side {
|
||||
|
||||
SIDE_COUNT
|
||||
};
|
||||
// Alias to the above for clarity, using OpenGL axis convention
|
||||
// Alias to the above for clarity, fixing some interpretation problems regarding the side_normals table...
|
||||
enum SideAxis {
|
||||
SIDE_NEGATIVE_X = 0,
|
||||
SIDE_POSITIVE_X,
|
||||
SIDE_POSITIVE_X = 0,
|
||||
SIDE_NEGATIVE_X,
|
||||
SIDE_NEGATIVE_Y,
|
||||
SIDE_POSITIVE_Y,
|
||||
SIDE_POSITIVE_Z,
|
||||
SIDE_NEGATIVE_Z
|
||||
SIDE_NEGATIVE_Z,
|
||||
SIDE_POSITIVE_Z
|
||||
};
|
||||
|
||||
// Index convention used in some lookup tables
|
||||
|
@ -35,7 +35,9 @@ inline bool is_transparent(const VoxelLibrary &lib, int voxel_id) {
|
||||
|
||||
VoxelMesherBlocky::VoxelMesherBlocky() :
|
||||
_baked_occlusion_darkness(0.8),
|
||||
_bake_occlusion(true) {}
|
||||
_bake_occlusion(true) {
|
||||
set_padding(PADDING, PADDING);
|
||||
}
|
||||
|
||||
void VoxelMesherBlocky::set_library(Ref<VoxelLibrary> library) {
|
||||
_library = library;
|
||||
@ -54,14 +56,12 @@ void VoxelMesherBlocky::set_occlusion_enabled(bool enable) {
|
||||
_bake_occlusion = enable;
|
||||
}
|
||||
|
||||
void VoxelMesherBlocky::build(VoxelMesher::Output &output, const VoxelBuffer &buffer, int padding) {
|
||||
void VoxelMesherBlocky::build(VoxelMesher::Output &output, const VoxelBuffer &buffer) {
|
||||
//uint64_t time_before = OS::get_singleton()->get_ticks_usec();
|
||||
|
||||
ERR_FAIL_COND(_library.is_null());
|
||||
ERR_FAIL_COND(padding < MINIMUM_PADDING);
|
||||
|
||||
const int channel = VoxelBuffer::CHANNEL_TYPE;
|
||||
|
||||
ERR_FAIL_COND(_library.is_null());
|
||||
const VoxelLibrary &library = **_library;
|
||||
|
||||
for (unsigned int i = 0; i < MAX_MATERIALS; ++i) {
|
||||
@ -87,8 +87,8 @@ void VoxelMesherBlocky::build(VoxelMesher::Output &output, const VoxelBuffer &bu
|
||||
// => Could be implemented in a separate class?
|
||||
|
||||
// Data must be padded, hence the off-by-one
|
||||
Vector3i min = Vector3i(padding);
|
||||
Vector3i max = buffer.get_size() - Vector3i(padding);
|
||||
Vector3i min = Vector3i(get_minimum_padding());
|
||||
Vector3i max = buffer.get_size() - Vector3i(get_maximum_padding());
|
||||
|
||||
int index_offsets[MAX_MATERIALS] = { 0 };
|
||||
|
||||
@ -408,10 +408,6 @@ void VoxelMesherBlocky::build(VoxelMesher::Output &output, const VoxelBuffer &bu
|
||||
//print_line(String("P: {0}, M: {1}, C: {2}").format(varray(time_prep, time_meshing, time_commit)));
|
||||
}
|
||||
|
||||
int VoxelMesherBlocky::get_minimum_padding() const {
|
||||
return MINIMUM_PADDING;
|
||||
}
|
||||
|
||||
VoxelMesher *VoxelMesherBlocky::clone() {
|
||||
VoxelMesherBlocky *c = memnew(VoxelMesherBlocky);
|
||||
c->set_library(_library);
|
||||
|
@ -14,7 +14,7 @@ class VoxelMesherBlocky : public VoxelMesher {
|
||||
|
||||
public:
|
||||
static const unsigned int MAX_MATERIALS = 8; // Arbitrary. Tweak if needed.
|
||||
static const int MINIMUM_PADDING = 1;
|
||||
static const int PADDING = 1;
|
||||
|
||||
VoxelMesherBlocky();
|
||||
|
||||
@ -27,8 +27,7 @@ public:
|
||||
void set_occlusion_enabled(bool enable);
|
||||
bool get_occlusion_enabled() const { return _bake_occlusion; }
|
||||
|
||||
void build(VoxelMesher::Output &output, const VoxelBuffer &voxels, int padding) override;
|
||||
int get_minimum_padding() const override;
|
||||
void build(VoxelMesher::Output &output, const VoxelBuffer &voxels) override;
|
||||
|
||||
VoxelMesher *clone() override;
|
||||
|
||||
|
@ -1429,6 +1429,7 @@ void polygonize_volume_directly(const VoxelBuffer &voxels, Vector3i min, Vector3
|
||||
#define BUILD_OCTREE_BOTTOM_UP
|
||||
|
||||
VoxelMesherDMC::VoxelMesherDMC() {
|
||||
set_padding(PADDING, PADDING);
|
||||
}
|
||||
|
||||
void VoxelMesherDMC::set_mesh_mode(MeshMode mode) {
|
||||
@ -1463,7 +1464,7 @@ VoxelMesherDMC::SeamMode VoxelMesherDMC::get_seam_mode() const {
|
||||
return _seam_mode;
|
||||
}
|
||||
|
||||
void VoxelMesherDMC::build(VoxelMesher::Output &output, const VoxelBuffer &voxels, int padding) {
|
||||
void VoxelMesherDMC::build(VoxelMesher::Output &output, const VoxelBuffer &voxels) {
|
||||
|
||||
// Requirements:
|
||||
// - Voxel data must be padded
|
||||
@ -1476,15 +1477,13 @@ void VoxelMesherDMC::build(VoxelMesher::Output &output, const VoxelBuffer &voxel
|
||||
return;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(padding < MINIMUM_PADDING);
|
||||
|
||||
const Vector3i buffer_size = voxels.get_size();
|
||||
// Taking previous power of two because the algorithm uses an integer cubic octree, and data should be padded
|
||||
int chunk_size = previous_power_of_2(MIN(MIN(buffer_size.x, buffer_size.y), buffer_size.z));
|
||||
|
||||
ERR_FAIL_COND(voxels.get_size().x < chunk_size + padding * 2);
|
||||
ERR_FAIL_COND(voxels.get_size().y < chunk_size + padding * 2);
|
||||
ERR_FAIL_COND(voxels.get_size().z < chunk_size + padding * 2);
|
||||
ERR_FAIL_COND(voxels.get_size().x < chunk_size + PADDING * 2);
|
||||
ERR_FAIL_COND(voxels.get_size().y < chunk_size + PADDING * 2);
|
||||
ERR_FAIL_COND(voxels.get_size().z < chunk_size + PADDING * 2);
|
||||
|
||||
// TODO Option for this in case LOD is not used
|
||||
bool skirts_enabled = _seam_mode == SEAM_MARCHING_SQUARE_SKIRTS;
|
||||
@ -1498,7 +1497,7 @@ void VoxelMesherDMC::build(VoxelMesher::Output &output, const VoxelBuffer &voxel
|
||||
// So we can't improve this further until Godot's API gives us that possibility, or other approaches like skirts need to be taken.
|
||||
|
||||
// Construct an intermediate to handle padding transparently
|
||||
dmc::VoxelAccess voxels_access(voxels, Vector3i(padding));
|
||||
dmc::VoxelAccess voxels_access(voxels, Vector3i(PADDING));
|
||||
|
||||
real_t time_before = OS::get_singleton()->get_ticks_usec();
|
||||
|
||||
@ -1571,7 +1570,7 @@ void VoxelMesherDMC::build(VoxelMesher::Output &output, const VoxelBuffer &voxel
|
||||
// This is essentially regular marching cubes.
|
||||
|
||||
time_before = OS::get_singleton()->get_ticks_usec();
|
||||
dmc::polygonize_volume_directly(voxels, Vector3i(padding), Vector3i(chunk_size), _mesh_builder, skirts_enabled);
|
||||
dmc::polygonize_volume_directly(voxels, Vector3i(PADDING), Vector3i(chunk_size), _mesh_builder, skirts_enabled);
|
||||
_stats.meshing_time = OS::get_singleton()->get_ticks_usec() - time_before;
|
||||
}
|
||||
|
||||
@ -1591,10 +1590,6 @@ void VoxelMesherDMC::build(VoxelMesher::Output &output, const VoxelBuffer &voxel
|
||||
}
|
||||
}
|
||||
|
||||
int VoxelMesherDMC::get_minimum_padding() const {
|
||||
return MINIMUM_PADDING;
|
||||
}
|
||||
|
||||
VoxelMesher *VoxelMesherDMC::clone() {
|
||||
VoxelMesherDMC *c = memnew(VoxelMesherDMC);
|
||||
c->set_mesh_mode(_mesh_mode);
|
||||
|
@ -65,7 +65,7 @@ struct DualGrid {
|
||||
class VoxelMesherDMC : public VoxelMesher {
|
||||
GDCLASS(VoxelMesherDMC, VoxelMesher)
|
||||
public:
|
||||
static const int MINIMUM_PADDING = 2;
|
||||
static const int PADDING = 2;
|
||||
|
||||
enum MeshMode {
|
||||
MESH_NORMAL,
|
||||
@ -101,8 +101,7 @@ public:
|
||||
void set_seam_mode(SeamMode mode);
|
||||
SeamMode get_seam_mode() const;
|
||||
|
||||
void build(VoxelMesher::Output &output, const VoxelBuffer &voxels, int padding) override;
|
||||
int get_minimum_padding() const override;
|
||||
void build(VoxelMesher::Output &output, const VoxelBuffer &voxels) override;
|
||||
|
||||
Dictionary get_statistics() const;
|
||||
|
||||
|
@ -69,13 +69,11 @@ inline int8_t increase(int8_t v, int8_t a) {
|
||||
|
||||
} // namespace
|
||||
|
||||
int VoxelMesherMC::get_minimum_padding() const {
|
||||
return MINIMUM_PADDING;
|
||||
VoxelMesherMC::VoxelMesherMC() {
|
||||
set_padding(MIN_PADDING, MAX_PADDING);
|
||||
}
|
||||
|
||||
void VoxelMesherMC::build(VoxelMesher::Output &output, const VoxelBuffer &voxels, int padding) {
|
||||
|
||||
ERR_FAIL_COND(padding < MINIMUM_PADDING);
|
||||
void VoxelMesherMC::build(VoxelMesher::Output &output, const VoxelBuffer &voxels) {
|
||||
|
||||
int channel = VoxelBuffer::CHANNEL_ISOLEVEL;
|
||||
|
||||
@ -141,9 +139,8 @@ void VoxelMesherMC::build_internal(const VoxelBuffer &voxels, unsigned int chann
|
||||
// The algorithm works with a 2x2 kernel and needs extra neighbors for normals,
|
||||
// so it looks 1 voxel away in negative axes, and 2 voxels away in positive axes.
|
||||
Vector3i pos;
|
||||
// Could have been offset by 1, but using 2 instead because the VoxelMesher API expects a symetric padding at the moment
|
||||
Vector3i min_pos(2);
|
||||
Vector3i max_pos(block_size - Vector3i(2));
|
||||
Vector3i min_pos(get_minimum_padding());
|
||||
Vector3i max_pos(block_size - Vector3i(get_maximum_padding()));
|
||||
|
||||
if (_seam_mode == SEAM_OVERLAP) {
|
||||
// When this is enabled, the algorithm may detect if it's on a border,
|
||||
@ -440,8 +437,7 @@ VoxelMesherMC::ReuseCell &VoxelMesherMC::get_reuse_cell(Vector3i pos) {
|
||||
}
|
||||
|
||||
void VoxelMesherMC::emit_vertex(Vector3 primary, Vector3 normal) {
|
||||
// Could have been offset by 1, but using 2 instead because the VoxelMesher API expects a symetric padding at the moment
|
||||
_output_vertices.push_back(primary - Vector3(MINIMUM_PADDING, MINIMUM_PADDING, MINIMUM_PADDING));
|
||||
_output_vertices.push_back(primary - Vector3(MIN_PADDING, MIN_PADDING, MIN_PADDING));
|
||||
_output_normals.push_back(normal);
|
||||
}
|
||||
|
||||
|
@ -4,20 +4,22 @@
|
||||
#include "../voxel_mesher.h"
|
||||
|
||||
// Simple marching cubes.
|
||||
// Implementation is simplified from Transvoxel.
|
||||
// Implementation is simplified from old Transvoxel code.
|
||||
class VoxelMesherMC : public VoxelMesher {
|
||||
GDCLASS(VoxelMesherMC, VoxelMesher)
|
||||
|
||||
public:
|
||||
static const int MINIMUM_PADDING = 2;
|
||||
static const int MIN_PADDING = 1;
|
||||
static const int MAX_PADDING = 2;
|
||||
|
||||
enum SeamMode {
|
||||
SEAM_NONE,
|
||||
SEAM_OVERLAP
|
||||
};
|
||||
|
||||
void build(VoxelMesher::Output &output, const VoxelBuffer &voxels, int padding) override;
|
||||
int get_minimum_padding() const override;
|
||||
VoxelMesherMC();
|
||||
|
||||
void build(VoxelMesher::Output &output, const VoxelBuffer &voxels) override;
|
||||
|
||||
void set_seam_mode(SeamMode mode);
|
||||
SeamMode get_seam_mode() const;
|
||||
|
@ -94,28 +94,6 @@ inline Vector3 get_secondary_position(Vector3 primary, Vector3 normal, int lod,
|
||||
return primary + delta;
|
||||
}
|
||||
|
||||
// TODO Use this to prevent some cases of null normals
|
||||
inline Vector3 get_gradient_normal(uint8_t left, uint8_t right, uint8_t bottom, uint8_t top, uint8_t back, uint8_t front, uint8_t middle) {
|
||||
|
||||
float gx, gy, gz;
|
||||
|
||||
if (left == right && bottom == top && back == front) {
|
||||
// Sided gradient, but can't be zero
|
||||
float m = tof(tos(middle));
|
||||
gx = tof(tos(left)) - m;
|
||||
gy = tof(tos(bottom)) - m;
|
||||
gz = tof(tos(back)) - m;
|
||||
|
||||
} else {
|
||||
// Symetric gradient
|
||||
gx = tof(tos(left)) - tof(tos(right));
|
||||
gy = tof(tos(bottom)) - tof(tos(top));
|
||||
gz = tof(tos(back)) - tof(tos(front));
|
||||
}
|
||||
|
||||
return Vector3(gx, gy, gz).normalized();
|
||||
}
|
||||
|
||||
inline uint8_t get_border_mask(const Vector3i &pos, const Vector3i &min_pos, const Vector3i &max_pos) {
|
||||
|
||||
uint8_t mask = 0;
|
||||
@ -143,8 +121,8 @@ inline uint8_t get_border_mask(const Vector3i &pos, const Vector3i &min_pos, con
|
||||
|
||||
} // namespace
|
||||
|
||||
int VoxelMesherTransvoxel::get_minimum_padding() const {
|
||||
return MINIMUM_PADDING;
|
||||
VoxelMesherTransvoxel::VoxelMesherTransvoxel() {
|
||||
set_padding(MIN_PADDING, MAX_PADDING);
|
||||
}
|
||||
|
||||
void VoxelMesherTransvoxel::clear_output() {
|
||||
@ -177,7 +155,7 @@ void VoxelMesherTransvoxel::fill_surface_arrays(Array &arrays) {
|
||||
arrays[Mesh::ARRAY_INDEX] = indices;
|
||||
}
|
||||
|
||||
void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelBuffer &voxels, int padding) {
|
||||
void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelBuffer &voxels) {
|
||||
|
||||
int channel = VoxelBuffer::CHANNEL_ISOLEVEL;
|
||||
|
||||
@ -187,23 +165,32 @@ void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelBuffer
|
||||
// Once capacity is big enough, no more memory should be allocated
|
||||
clear_output();
|
||||
|
||||
ERR_FAIL_COND(padding < MINIMUM_PADDING);
|
||||
|
||||
build_internal(voxels, channel);
|
||||
// OS::get_singleton()->print("vertices: %i, normals: %i, indices: %i\n",
|
||||
// m_output_vertices.size(),
|
||||
// m_output_normals.size(),
|
||||
// m_output_indices.size());
|
||||
|
||||
if (_output_vertices.size() == 0) {
|
||||
// The mesh can be empty
|
||||
return;
|
||||
}
|
||||
|
||||
Array arrays;
|
||||
fill_surface_arrays(arrays);
|
||||
Array regular_arrays;
|
||||
fill_surface_arrays(regular_arrays);
|
||||
output.surfaces.push_back(regular_arrays);
|
||||
|
||||
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
|
||||
|
||||
clear_output();
|
||||
|
||||
build_transition(voxels, channel, dir);
|
||||
|
||||
if (_output_vertices.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Array transition_arrays;
|
||||
fill_surface_arrays(transition_arrays);
|
||||
output.transition_surfaces[dir].push_back(transition_arrays);
|
||||
}
|
||||
|
||||
output.surfaces.push_back(arrays);
|
||||
output.primitive_type = Mesh::PRIMITIVE_TRIANGLES;
|
||||
output.compression_flags = MESH_COMPRESSION_FLAGS;
|
||||
}
|
||||
@ -270,15 +257,15 @@ void VoxelMesherTransvoxel::build_internal(const VoxelBuffer &voxels, unsigned i
|
||||
}
|
||||
|
||||
const Vector3i block_size = voxels.get_size();
|
||||
const Vector3i block_size_without_padding = block_size - 3 * PAD;
|
||||
const Vector3i block_size_without_padding = block_size - 3 * Vector3i(MIN_PADDING);
|
||||
|
||||
// Prepare vertex reuse cache
|
||||
reset_reuse_cells(block_size);
|
||||
|
||||
// We iterate 2x2 voxel groups, which the paper calls "cells".
|
||||
// We also reach one voxel further to compute normals, so we adjust the iterated area
|
||||
const Vector3i min_pos = PAD;
|
||||
const Vector3i max_pos = block_size - Vector3i(1) - PAD;
|
||||
const Vector3i min_pos = Vector3i(MIN_PADDING);
|
||||
const Vector3i max_pos = block_size - Vector3i(MAX_PADDING);
|
||||
const Vector3i max_pos_c = max_pos - Vector3i(1);
|
||||
// TODO Change the Mesher API to allow different min and max paddings. Here 1 on min, 2 on max
|
||||
// TODO Also abstract positions with padding, it can get quite confusing when one is used but not the other...
|
||||
@ -433,10 +420,11 @@ void VoxelMesherTransvoxel::build_internal(const VoxelBuffer &voxels, unsigned i
|
||||
// TODO Implement surface shifting interpolation (see other places we interpolate too).
|
||||
// See issue https://github.com/Zylann/godot_voxel/issues/60
|
||||
// Seen in the paper, it fixes "steps" between LODs on flat surfaces.
|
||||
// It is using a binary search through higher lods to find the zero-crossing edge.
|
||||
// I did not do it here, because our data model is such that when we have low-resolution voxels,
|
||||
// we cannot just have a look at the high-res ones, because they are not in memory.
|
||||
// However, it might be possible on low-res blocks bordering high-res ones due to neighboring rules,
|
||||
// or with a different storage strategy.
|
||||
// or by falling back on the generator that was used to produce the volume.
|
||||
|
||||
Vector3 primary = p0.to_vec3() * t0 + p1.to_vec3() * t1;
|
||||
Vector3 normal = corner_normals[v0] * t0 + corner_normals[v1] * t1;
|
||||
@ -655,7 +643,7 @@ void VoxelMesherTransvoxel::build_transition(const VoxelBuffer &p_voxels, unsign
|
||||
}
|
||||
|
||||
const Vector3i block_size = p_voxels.get_size();
|
||||
const Vector3i block_size_without_padding = block_size - 3 * PAD;
|
||||
const Vector3i block_size_without_padding = block_size - Vector3i(MIN_PADDING + MAX_PADDING);
|
||||
//const Vector3i half_block_size = block_size / 2;
|
||||
|
||||
ERR_FAIL_COND(block_size.x < 3);
|
||||
@ -676,8 +664,8 @@ void VoxelMesherTransvoxel::build_transition(const VoxelBuffer &p_voxels, unsign
|
||||
// This represents the actual box of voxels we are working on.
|
||||
// It also represents positions of the minimum and maximum vertices that can be generated.
|
||||
// Padding is present to allow reaching 1 voxel further for calculating normals
|
||||
const Vector3i min_pos = PAD;
|
||||
const Vector3i max_pos = block_size - Vector3i(1) - PAD;
|
||||
const Vector3i min_pos = Vector3i(MIN_PADDING);
|
||||
const Vector3i max_pos = block_size - Vector3i(MAX_PADDING);
|
||||
|
||||
int axis_x, axis_y;
|
||||
L::get_face_axes(axis_x, axis_y, direction);
|
||||
@ -694,7 +682,7 @@ void VoxelMesherTransvoxel::build_transition(const VoxelBuffer &p_voxels, unsign
|
||||
for (int fy = min_fpos_y; fy < max_fpos_y; fy += 2) {
|
||||
for (int fx = min_fpos_x; fx < max_fpos_x; fx += 2) {
|
||||
|
||||
const int fz = PAD.x;
|
||||
const int fz = MIN_PADDING;
|
||||
|
||||
const VoxelBuffer &fvoxels = p_voxels;
|
||||
|
||||
@ -719,8 +707,6 @@ void VoxelMesherTransvoxel::build_transition(const VoxelBuffer &p_voxels, unsign
|
||||
// | | |
|
||||
// 0---1---2
|
||||
|
||||
// TODO Double-check positions and transforms. I understand what's needed (contrary to what's next ._.) but it's boring to do, so I botched it
|
||||
|
||||
// Full-resolution samples 0..8
|
||||
for (unsigned int i = 0; i < 9; ++i) {
|
||||
cell_samples[i] = tos(get_voxel(fvoxels, cell_positions[i], channel));
|
||||
@ -991,8 +977,8 @@ int VoxelMesherTransvoxel::emit_vertex(Vector3 primary, Vector3 normal, uint16_t
|
||||
int vi = _output_vertices.size();
|
||||
|
||||
// TODO Unpad positions in calling code, as it may simplify border offset
|
||||
primary -= PAD.to_vec3();
|
||||
secondary -= PAD.to_vec3();
|
||||
primary -= Vector3(MIN_PADDING, MIN_PADDING, MIN_PADDING);
|
||||
secondary -= Vector3(MIN_PADDING, MIN_PADDING, MIN_PADDING);
|
||||
|
||||
_output_vertices.push_back(primary);
|
||||
_output_normals.push_back(normal);
|
||||
|
@ -10,10 +10,12 @@ class VoxelMesherTransvoxel : public VoxelMesher {
|
||||
GDCLASS(VoxelMesherTransvoxel, VoxelMesher)
|
||||
|
||||
public:
|
||||
static const int MINIMUM_PADDING = 2;
|
||||
static const int MIN_PADDING = 1;
|
||||
static const int MAX_PADDING = 2;
|
||||
|
||||
void build(VoxelMesher::Output &output, const VoxelBuffer &voxels, int padding) override;
|
||||
int get_minimum_padding() const override;
|
||||
VoxelMesherTransvoxel();
|
||||
|
||||
void build(VoxelMesher::Output &output, const VoxelBuffer &voxels) override;
|
||||
|
||||
VoxelMesher *clone() override;
|
||||
|
||||
@ -54,8 +56,6 @@ private:
|
||||
void fill_surface_arrays(Array &arrays);
|
||||
|
||||
private:
|
||||
const Vector3i PAD = Vector3i(1, 1, 1);
|
||||
|
||||
FixedArray<std::vector<ReuseCell>, 2> _cache;
|
||||
FixedArray<std::vector<ReuseTransitionCell>, 2> _cache_2d;
|
||||
Vector3i _block_size;
|
||||
|
@ -5,7 +5,7 @@ Ref<Mesh> VoxelMesher::build_mesh(Ref<VoxelBuffer> voxels) {
|
||||
ERR_FAIL_COND_V(voxels.is_null(), Ref<ArrayMesh>());
|
||||
|
||||
Output output;
|
||||
build(output, **voxels, get_minimum_padding());
|
||||
build(output, **voxels);
|
||||
|
||||
if (output.surfaces.empty()) {
|
||||
return Ref<ArrayMesh>();
|
||||
@ -21,11 +21,24 @@ Ref<Mesh> VoxelMesher::build_mesh(Ref<VoxelBuffer> voxels) {
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void VoxelMesher::build(Output &output, const VoxelBuffer &voxels, int padding) {
|
||||
void VoxelMesher::build(Output &output, const VoxelBuffer &voxels) {
|
||||
|
||||
ERR_PRINT("Not implemented");
|
||||
}
|
||||
|
||||
int VoxelMesher::get_minimum_padding() const {
|
||||
return 0;
|
||||
return _minimum_padding;
|
||||
}
|
||||
|
||||
int VoxelMesher::get_maximum_padding() const {
|
||||
return _maximum_padding;
|
||||
}
|
||||
|
||||
void VoxelMesher::set_padding(int minimum, int maximum) {
|
||||
CRASH_COND(minimum < 0);
|
||||
CRASH_COND(maximum < 0);
|
||||
_minimum_padding = minimum;
|
||||
_maximum_padding = maximum;
|
||||
}
|
||||
|
||||
VoxelMesher *VoxelMesher::clone() {
|
||||
@ -38,4 +51,5 @@ void VoxelMesher::_bind_methods() {
|
||||
// Useful for testing the different meshers.
|
||||
ClassDB::bind_method(D_METHOD("build_mesh", "voxel_buffer"), &VoxelMesher::build_mesh);
|
||||
ClassDB::bind_method(D_METHOD("get_minimum_padding"), &VoxelMesher::get_minimum_padding);
|
||||
ClassDB::bind_method(D_METHOD("get_maximum_padding"), &VoxelMesher::get_maximum_padding);
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
#ifndef VOXEL_MESHER_H
|
||||
#define VOXEL_MESHER_H
|
||||
|
||||
#include "../cube_tables.h"
|
||||
#include "../util/fixed_array.h"
|
||||
#include "../voxel_buffer.h"
|
||||
#include <scene/resources/mesh.h>
|
||||
|
||||
@ -10,20 +12,31 @@ public:
|
||||
struct Output {
|
||||
// Each surface correspond to a different material
|
||||
Vector<Array> surfaces;
|
||||
FixedArray<Vector<Array>, Cube::SIDE_COUNT> transition_surfaces;
|
||||
Mesh::PrimitiveType primitive_type = Mesh::PRIMITIVE_TRIANGLES;
|
||||
unsigned int compression_flags = Mesh::ARRAY_COMPRESS_DEFAULT;
|
||||
};
|
||||
|
||||
virtual void build(Output &output, const VoxelBuffer &voxels, int padding);
|
||||
virtual int get_minimum_padding() const;
|
||||
virtual void build(Output &output, const VoxelBuffer &voxels);
|
||||
|
||||
// Must be cloneable so can be used by more than one thread
|
||||
// Get how many neighbor voxels need to be accessed around the meshed area.
|
||||
// If this is not respected, the mesher might produce seams at the edges, or an error
|
||||
int get_minimum_padding() const;
|
||||
int get_maximum_padding() const;
|
||||
|
||||
// Must be cloneable so can be duplicated for use by more than one thread
|
||||
virtual VoxelMesher *clone();
|
||||
|
||||
Ref<Mesh> build_mesh(Ref<VoxelBuffer> voxels);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
void set_padding(int minimum, int maximum);
|
||||
|
||||
private:
|
||||
int _minimum_padding = 0;
|
||||
int _maximum_padding = 0;
|
||||
};
|
||||
|
||||
#endif // VOXEL_MESHER_H
|
||||
|
@ -47,6 +47,21 @@ VoxelBlock *VoxelBlock::create(Vector3i bpos, Ref<VoxelBuffer> buffer, unsigned
|
||||
block->_position_in_voxels = bpos * (size << p_lod_index);
|
||||
block->voxels = buffer;
|
||||
|
||||
#ifdef VOXEL_DEBUG_LOD_MATERIALS
|
||||
Ref<SpatialMaterial> debug_material;
|
||||
debug_material.instance();
|
||||
int checker = (bpos.x + bpos.y + bpos.z) & 1;
|
||||
Color debug_color = Color(0.8, 0.4, 0.8).linear_interpolate(Color(0.0, 0.0, 0.5), static_cast<float>(p_lod_index) / 8.f);
|
||||
debug_color = debug_color.lightened(checker * 0.1f);
|
||||
debug_material->set_albedo(debug_color);
|
||||
block->_debug_material = debug_material;
|
||||
|
||||
Ref<SpatialMaterial> debug_transition_material;
|
||||
debug_transition_material.instance();
|
||||
debug_transition_material->set_albedo(Color(1, 1, 0));
|
||||
block->_debug_transition_material = debug_transition_material;
|
||||
#endif
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
@ -68,6 +83,8 @@ void VoxelBlock::set_mesh(Ref<Mesh> mesh, Spatial *node, bool generate_collision
|
||||
Ref<World> world = node->get_world();
|
||||
ERR_FAIL_COND(world.is_null());
|
||||
|
||||
Transform transform(Basis(), _position_in_voxels.to_vec3());
|
||||
|
||||
if (!_mesh_instance.is_valid()) {
|
||||
// Create instance if it doesn't exist
|
||||
_mesh_instance.create();
|
||||
@ -75,12 +92,17 @@ void VoxelBlock::set_mesh(Ref<Mesh> mesh, Spatial *node, bool generate_collision
|
||||
_mesh_instance.set_visible(_visible);
|
||||
}
|
||||
|
||||
Transform transform(Basis(), _position_in_voxels.to_vec3());
|
||||
|
||||
_mesh_instance.set_mesh(mesh);
|
||||
_mesh_instance.set_transform(transform);
|
||||
// TODO The day VoxelTerrain becomes a Spatial, this transform will need to be updatable separately
|
||||
|
||||
if (_shader_material.is_valid()) {
|
||||
_mesh_instance.set_material_override(_shader_material);
|
||||
}
|
||||
#ifdef VOXEL_DEBUG_LOD_MATERIALS
|
||||
_mesh_instance.set_material_override(_debug_material);
|
||||
#endif
|
||||
|
||||
if (generate_collision) {
|
||||
Ref<Shape> shape = create_concave_polygon_shape(surface_arrays);
|
||||
|
||||
@ -116,6 +138,42 @@ void VoxelBlock::set_mesh(Ref<Mesh> mesh, Spatial *node, bool generate_collision
|
||||
// }
|
||||
}
|
||||
|
||||
void VoxelBlock::set_transition_mesh(Ref<Mesh> mesh, int side, Ref<World> world) {
|
||||
|
||||
DirectMeshInstance &mesh_instance = _transition_mesh_instances[side];
|
||||
|
||||
if (mesh.is_valid()) {
|
||||
|
||||
ERR_FAIL_COND(world.is_null());
|
||||
|
||||
if (!mesh_instance.is_valid()) {
|
||||
// Create instance if it doesn't exist
|
||||
mesh_instance.create();
|
||||
mesh_instance.set_world(*world);
|
||||
mesh_instance.set_visible(_visible && _is_transition_visible(side));
|
||||
}
|
||||
|
||||
Transform transform(Basis(), _position_in_voxels.to_vec3());
|
||||
|
||||
mesh_instance.set_mesh(mesh);
|
||||
mesh_instance.set_transform(transform);
|
||||
|
||||
if (_shader_material.is_valid()) {
|
||||
mesh_instance.set_material_override(_shader_material);
|
||||
}
|
||||
#ifdef VOXEL_DEBUG_LOD_MATERIALS
|
||||
mesh_instance.set_material_override(_debug_transition_material);
|
||||
#endif
|
||||
|
||||
} else {
|
||||
|
||||
if (mesh_instance.is_valid()) {
|
||||
// Delete instance if it exists
|
||||
mesh_instance.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VoxelBlock::has_mesh() const {
|
||||
return _mesh_instance.get_mesh().is_valid();
|
||||
}
|
||||
@ -132,6 +190,11 @@ void VoxelBlock::set_world(World *world) {
|
||||
if (_mesh_instance.is_valid()) {
|
||||
_mesh_instance.set_world(world);
|
||||
}
|
||||
for (int i = 0; i < _transition_mesh_instances.size(); ++i) {
|
||||
if (_transition_mesh_instances[i].is_valid()) {
|
||||
_transition_mesh_instances[i].set_world(world);
|
||||
}
|
||||
}
|
||||
if (_static_body.is_valid()) {
|
||||
_static_body.set_world(world);
|
||||
}
|
||||
@ -153,11 +216,63 @@ void VoxelBlock::_set_visible(bool visible) {
|
||||
if (_mesh_instance.is_valid()) {
|
||||
_mesh_instance.set_visible(visible);
|
||||
}
|
||||
for (int dir = 0; dir < _transition_mesh_instances.size(); ++dir) {
|
||||
if (_transition_mesh_instances[dir].is_valid()) {
|
||||
_transition_mesh_instances[dir].set_visible(visible & _is_transition_visible(dir));
|
||||
}
|
||||
}
|
||||
if (_static_body.is_valid()) {
|
||||
_static_body.set_shape_enabled(0, visible);
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelBlock::set_shader_material(Ref<ShaderMaterial> material) {
|
||||
_shader_material = material;
|
||||
}
|
||||
|
||||
//void VoxelBlock::set_transition_bit(uint8_t side, bool value) {
|
||||
// CRASH_COND(side >= Cube::SIDE_COUNT);
|
||||
// uint32_t m = _transition_mask;
|
||||
// if (value) {
|
||||
// m |= (1 << side);
|
||||
// } else {
|
||||
// m &= ~(1 << side);
|
||||
// }
|
||||
// set_transition_mask(m);
|
||||
//}
|
||||
|
||||
void VoxelBlock::set_transition_mask(uint8_t m) {
|
||||
CRASH_COND(m >= (1 << Cube::SIDE_COUNT));
|
||||
uint8_t diff = _transition_mask ^ m;
|
||||
if (diff == 0) {
|
||||
return;
|
||||
}
|
||||
_transition_mask = m;
|
||||
if (_shader_material.is_valid()) {
|
||||
|
||||
// TODO Needs translation here, because Cube:: tables use slightly different order...
|
||||
// We may get rid of this once cube tables respects -x+x-y+y-z+z order
|
||||
uint8_t bits[Cube::SIDE_COUNT];
|
||||
for (unsigned int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
|
||||
bits[dir] = (m >> dir) & 1;
|
||||
}
|
||||
uint8_t tm = bits[Cube::SIDE_NEGATIVE_X];
|
||||
tm |= bits[Cube::SIDE_POSITIVE_X] << 1;
|
||||
tm |= bits[Cube::SIDE_NEGATIVE_Y] << 2;
|
||||
tm |= bits[Cube::SIDE_POSITIVE_Y] << 3;
|
||||
tm |= bits[Cube::SIDE_NEGATIVE_Z] << 4;
|
||||
tm |= bits[Cube::SIDE_POSITIVE_Z] << 5;
|
||||
|
||||
_shader_material->set_shader_param("u_transition_mask", tm);
|
||||
}
|
||||
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
|
||||
DirectMeshInstance &mi = _transition_mesh_instances[dir];
|
||||
if (diff & (1 << dir) && mi.is_valid()) {
|
||||
mi.set_visible(_visible & _is_transition_visible(dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelBlock::set_parent_visible(bool parent_visible) {
|
||||
if (_parent_visible && parent_visible) {
|
||||
return;
|
||||
|
@ -1,10 +1,14 @@
|
||||
#ifndef VOXEL_BLOCK_H
|
||||
#define VOXEL_BLOCK_H
|
||||
|
||||
#include "../cube_tables.h"
|
||||
#include "../util/direct_mesh_instance.h"
|
||||
#include "../util/direct_static_body.h"
|
||||
#include "../util/fixed_array.h"
|
||||
#include "../voxel_buffer.h"
|
||||
|
||||
//#define VOXEL_DEBUG_LOD_MATERIALS
|
||||
|
||||
class Spatial;
|
||||
|
||||
// Internal structure holding a reference to mesh visuals, physics and a block of voxel data.
|
||||
@ -21,20 +25,24 @@ public:
|
||||
Ref<VoxelBuffer> voxels;
|
||||
Vector3i position;
|
||||
unsigned int lod_index = 0;
|
||||
bool pending_transition_update = false;
|
||||
|
||||
static VoxelBlock *create(Vector3i bpos, Ref<VoxelBuffer> buffer, unsigned int size, unsigned int p_lod_index);
|
||||
|
||||
~VoxelBlock();
|
||||
|
||||
// Visuals and physics
|
||||
|
||||
void set_mesh(Ref<Mesh> mesh, Spatial *node, bool generate_collision, Array surface_arrays, bool debug_collision);
|
||||
void set_transition_mesh(Ref<Mesh> mesh, int side, Ref<World> world);
|
||||
bool has_mesh() const;
|
||||
|
||||
void set_shader_material(Ref<ShaderMaterial> material);
|
||||
inline Ref<ShaderMaterial> get_shader_material() const { return _shader_material; }
|
||||
|
||||
void set_mesh_state(MeshState ms);
|
||||
MeshState get_mesh_state() const;
|
||||
|
||||
void set_needs_lodding(bool need_lodding);
|
||||
inline bool get_needs_lodding() const { return _needs_lodding; }
|
||||
|
||||
void set_world(World *world);
|
||||
|
||||
void set_visible(bool visible);
|
||||
@ -42,6 +50,15 @@ public:
|
||||
|
||||
void set_parent_visible(bool parent_visible);
|
||||
|
||||
void set_transition_mask(uint8_t m);
|
||||
//void set_transition_bit(uint8_t side, bool value);
|
||||
inline uint8_t get_transition_mask() const { return _transition_mask; }
|
||||
|
||||
// Voxel data
|
||||
|
||||
void set_needs_lodding(bool need_lodding);
|
||||
inline bool get_needs_lodding() const { return _needs_lodding; }
|
||||
|
||||
bool is_modified() const;
|
||||
void set_modified(bool modified);
|
||||
|
||||
@ -49,17 +66,27 @@ private:
|
||||
VoxelBlock();
|
||||
|
||||
void _set_visible(bool visible);
|
||||
void _update_transition_visibility();
|
||||
inline bool _is_transition_visible(int side) const { return _transition_mask & (1 << side); }
|
||||
|
||||
private:
|
||||
Vector3i _position_in_voxels;
|
||||
|
||||
Ref<ShaderMaterial> _shader_material;
|
||||
DirectMeshInstance _mesh_instance;
|
||||
FixedArray<DirectMeshInstance, Cube::SIDE_COUNT> _transition_mesh_instances;
|
||||
DirectStaticBody _static_body;
|
||||
|
||||
#ifdef VOXEL_DEBUG_LOD_MATERIALS
|
||||
Ref<Material> _debug_material;
|
||||
Ref<Material> _debug_transition_material;
|
||||
#endif
|
||||
|
||||
int _mesh_update_count = 0;
|
||||
bool _visible = true;
|
||||
bool _parent_visible = true;
|
||||
MeshState _mesh_state = MESH_NEVER_UPDATED;
|
||||
uint8_t _transition_mask = 0;
|
||||
|
||||
// The block was edited, which requires its LOD counterparts to be recomputed
|
||||
bool _needs_lodding = false;
|
||||
|
@ -13,6 +13,46 @@
|
||||
|
||||
const uint32_t MAIN_THREAD_MESHING_BUDGET_MS = 8;
|
||||
|
||||
namespace {
|
||||
|
||||
Ref<ArrayMesh> build_mesh(const Vector<Array> surfaces, Mesh::PrimitiveType primitive, int compression_flags,
|
||||
Ref<Material> material, Array *collidable_surface) {
|
||||
|
||||
Ref<ArrayMesh> mesh;
|
||||
mesh.instance();
|
||||
|
||||
unsigned int surface_index = 0;
|
||||
for (int i = 0; i < surfaces.size(); ++i) {
|
||||
|
||||
Array surface = surfaces[i];
|
||||
if (surface.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CRASH_COND(surface.size() != Mesh::ARRAY_MAX);
|
||||
if (!is_surface_triangulated(surface)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (collidable_surface != nullptr && collidable_surface->empty()) {
|
||||
*collidable_surface = surface;
|
||||
}
|
||||
|
||||
mesh->add_surface_from_arrays(primitive, surface, Array(), compression_flags);
|
||||
mesh->surface_set_material(surface_index, material);
|
||||
// No multi-material supported yet
|
||||
++surface_index;
|
||||
}
|
||||
|
||||
if (is_mesh_empty(mesh)) {
|
||||
mesh = Ref<Mesh>();
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
VoxelLodTerrain::VoxelLodTerrain() {
|
||||
// Note: don't do anything heavy in the constructor.
|
||||
// Godot may create and destroy dozens of instances of all node types on startup,
|
||||
@ -817,6 +857,8 @@ void VoxelLodTerrain::_process() {
|
||||
_last_octree_region_box = new_box;
|
||||
}
|
||||
|
||||
CRASH_COND(_blocks_pending_transition_update.size() != 0);
|
||||
|
||||
// Find which blocks we need to load and see, within each octree
|
||||
{
|
||||
// TODO Maintain a vector to make iteration faster?
|
||||
@ -874,6 +916,8 @@ void VoxelLodTerrain::_process() {
|
||||
CRASH_COND(block->get_mesh_state() != VoxelBlock::MESH_UP_TO_DATE);
|
||||
|
||||
block->set_visible(true);
|
||||
self->add_transition_update(block);
|
||||
self->add_transition_updates_around(bpos, lod_index);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@ -906,6 +950,7 @@ void VoxelLodTerrain::_process() {
|
||||
VoxelBlock *block = lod.map->get_block(bpos);
|
||||
if (block) {
|
||||
block->set_visible(false);
|
||||
self->add_transition_updates_around(bpos, lod_index);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -925,8 +970,12 @@ void VoxelLodTerrain::_process() {
|
||||
// If not, something in block management prevents LODs to properly show up and should be fixed.
|
||||
_stats.blocked_lods += subdivide_action.blocked_count + unsubdivide_action.blocked_count;
|
||||
}
|
||||
|
||||
process_transition_updates();
|
||||
}
|
||||
|
||||
CRASH_COND(_blocks_pending_transition_update.size() != 0);
|
||||
|
||||
_stats.time_detect_required_blocks = profiling_clock.restart();
|
||||
|
||||
send_block_data_requests();
|
||||
@ -992,6 +1041,12 @@ void VoxelLodTerrain::_process() {
|
||||
// The block will be made visible and meshed only by LodOctree
|
||||
block->set_visible(false);
|
||||
block->set_parent_visible(is_visible());
|
||||
|
||||
Ref<ShaderMaterial> shader_material = _material;
|
||||
if (shader_material.is_valid() && block->get_shader_material().is_null()) {
|
||||
Ref<ShaderMaterial> sm = shader_material->duplicate(false);
|
||||
block->set_shader_material(sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1023,16 +1078,14 @@ void VoxelLodTerrain::_process() {
|
||||
Ref<VoxelBuffer> nbuffer;
|
||||
nbuffer.instance();
|
||||
|
||||
// TODO Make the buffer re-usable
|
||||
// TODO Make the buffer re-usable, or pool memory
|
||||
unsigned int block_size = lod.map->get_block_size();
|
||||
unsigned int padding = _block_updater->get_required_padding();
|
||||
nbuffer->create(
|
||||
block_size + 2 * padding,
|
||||
block_size + 2 * padding,
|
||||
block_size + 2 * padding);
|
||||
unsigned int min_padding = _block_updater->get_minimum_padding();
|
||||
unsigned int max_padding = _block_updater->get_maximum_padding();
|
||||
nbuffer->create(Vector3i(block_size + min_padding + max_padding));
|
||||
|
||||
unsigned int channels_mask = (1 << VoxelBuffer::CHANNEL_ISOLEVEL);
|
||||
lod.map->get_buffer_copy(lod.map->block_to_voxel(block_pos) - Vector3i(padding), **nbuffer, channels_mask);
|
||||
lod.map->get_buffer_copy(lod.map->block_to_voxel(block_pos) - Vector3i(min_padding), **nbuffer, channels_mask);
|
||||
|
||||
VoxelMeshUpdater::InputBlock iblock;
|
||||
iblock.data.voxels = nbuffer;
|
||||
@ -1112,39 +1165,15 @@ void VoxelLodTerrain::_process() {
|
||||
block->set_mesh_state(VoxelBlock::MESH_UP_TO_DATE);
|
||||
}
|
||||
|
||||
Ref<ArrayMesh> mesh;
|
||||
mesh.instance();
|
||||
const VoxelMesher::Output mesh_data = ob.data.smooth_surfaces;
|
||||
|
||||
// TODO Allow multiple collision surfaces
|
||||
Array collidable_surface;
|
||||
|
||||
unsigned int surface_index = 0;
|
||||
const VoxelMeshUpdater::OutputBlockData &data = ob.data;
|
||||
for (int i = 0; i < data.smooth_surfaces.surfaces.size(); ++i) {
|
||||
|
||||
Array surface = data.smooth_surfaces.surfaces[i];
|
||||
if (surface.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CRASH_COND(surface.size() != Mesh::ARRAY_MAX);
|
||||
if (!is_surface_triangulated(surface)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (collidable_surface.empty()) {
|
||||
collidable_surface = surface;
|
||||
}
|
||||
|
||||
mesh->add_surface_from_arrays(data.smooth_surfaces.primitive_type, surface, Array(), data.smooth_surfaces.compression_flags);
|
||||
mesh->surface_set_material(surface_index, _material);
|
||||
// No multi-material supported yet
|
||||
++surface_index;
|
||||
}
|
||||
|
||||
if (is_mesh_empty(mesh)) {
|
||||
mesh = Ref<Mesh>();
|
||||
}
|
||||
Ref<ArrayMesh> mesh = build_mesh(
|
||||
mesh_data.surfaces,
|
||||
mesh_data.primitive_type,
|
||||
mesh_data.compression_flags,
|
||||
_material, &collidable_surface);
|
||||
|
||||
bool has_collision = _generate_collisions;
|
||||
if (has_collision && _collision_lod_count != -1) {
|
||||
@ -1152,6 +1181,17 @@ void VoxelLodTerrain::_process() {
|
||||
}
|
||||
|
||||
block->set_mesh(mesh, this, has_collision, collidable_surface, get_tree()->is_debugging_collisions_hint());
|
||||
|
||||
for (int dir = 0; dir < mesh_data.transition_surfaces.size(); ++dir) {
|
||||
|
||||
Ref<ArrayMesh> transition_mesh = build_mesh(
|
||||
mesh_data.transition_surfaces[dir],
|
||||
mesh_data.primitive_type,
|
||||
mesh_data.compression_flags,
|
||||
_material, nullptr);
|
||||
|
||||
block->set_transition_mesh(transition_mesh, dir, get_world());
|
||||
}
|
||||
}
|
||||
|
||||
shift_up(_blocks_pending_main_thread_update, queue_index);
|
||||
@ -1299,6 +1339,79 @@ void VoxelLodTerrain::save_all_modified_blocks(bool with_copy) {
|
||||
send_block_data_requests();
|
||||
}
|
||||
|
||||
void VoxelLodTerrain::add_transition_update(VoxelBlock *block) {
|
||||
if (!block->pending_transition_update) {
|
||||
_blocks_pending_transition_update.push_back(block);
|
||||
block->pending_transition_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelLodTerrain::add_transition_updates_around(Vector3i block_pos, int lod_index) {
|
||||
|
||||
Lod &lod = _lods[lod_index];
|
||||
CRASH_COND(lod.map.is_null());
|
||||
|
||||
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
|
||||
|
||||
Vector3i npos = block_pos + Cube::g_side_normals[dir];
|
||||
VoxelBlock *nblock = lod.map->get_block(npos);
|
||||
|
||||
if (nblock != nullptr && nblock->is_visible()) {
|
||||
add_transition_update(nblock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelLodTerrain::process_transition_updates() {
|
||||
|
||||
for (unsigned int i = 0; i < _blocks_pending_transition_update.size(); ++i) {
|
||||
|
||||
VoxelBlock *block = _blocks_pending_transition_update[i];
|
||||
CRASH_COND(block == nullptr);
|
||||
|
||||
if (block->is_visible()) {
|
||||
block->set_transition_mask(get_transition_mask(block->position, block->lod_index));
|
||||
}
|
||||
|
||||
block->pending_transition_update = false;
|
||||
}
|
||||
|
||||
_blocks_pending_transition_update.clear();
|
||||
}
|
||||
|
||||
uint8_t VoxelLodTerrain::get_transition_mask(Vector3i block_pos, int lod_index) {
|
||||
|
||||
uint8_t transition_mask = 0;
|
||||
|
||||
if (lod_index + 1 >= _lods.size()) {
|
||||
return transition_mask;
|
||||
}
|
||||
|
||||
Lod &lower_lod = _lods[lod_index + 1];
|
||||
|
||||
if (!lower_lod.map.is_valid()) {
|
||||
return transition_mask;
|
||||
}
|
||||
|
||||
Vector3i lpos = block_pos >> 1;
|
||||
|
||||
// Check for lower-LOD blocks around
|
||||
for (int dir = 0; dir < Cube::SIDE_COUNT; ++dir) {
|
||||
Vector3i lnpos = (block_pos + Cube::g_side_normals[dir]) >> 1;
|
||||
|
||||
if (lnpos != lpos) {
|
||||
VoxelBlock *nblock = lower_lod.map->get_block(lnpos);
|
||||
|
||||
if (nblock != nullptr && nblock->is_visible()) {
|
||||
// The block has a visible neighbor of lower LOD
|
||||
transition_mask |= (1 << dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return transition_mask;
|
||||
}
|
||||
|
||||
Dictionary VoxelLodTerrain::get_statistics() const {
|
||||
|
||||
Dictionary d;
|
||||
|
@ -111,6 +111,11 @@ private:
|
||||
void save_all_modified_blocks(bool with_copy);
|
||||
void send_block_data_requests();
|
||||
|
||||
void add_transition_update(VoxelBlock *block);
|
||||
void add_transition_updates_around(Vector3i block_pos, int lod_index);
|
||||
void process_transition_updates();
|
||||
uint8_t get_transition_mask(Vector3i block_pos, int lod_index);
|
||||
|
||||
struct OctreeItem {
|
||||
LodOctree<bool> octree;
|
||||
#ifdef VOXEL_DEBUG_BOXES
|
||||
@ -137,6 +142,9 @@ private:
|
||||
std::vector<VoxelMeshUpdater::OutputBlock> _blocks_pending_main_thread_update;
|
||||
std::vector<VoxelDataLoader::InputBlock> _blocks_to_save;
|
||||
|
||||
// Only populated and then cleared inside _process, so lifetime of pointers should be valid
|
||||
std::vector<VoxelBlock *> _blocks_pending_transition_update;
|
||||
|
||||
Ref<Material> _material;
|
||||
|
||||
bool _generate_collisions = true;
|
||||
@ -164,7 +172,7 @@ private:
|
||||
#endif
|
||||
};
|
||||
|
||||
Lod _lods[MAX_LOD];
|
||||
FixedArray<Lod, MAX_LOD> _lods;
|
||||
int _lod_count = 0;
|
||||
float _lod_split_scale = 0.f;
|
||||
unsigned int _view_distance_voxels = 512;
|
||||
|
@ -1,13 +1,13 @@
|
||||
#include "voxel_mesh_updater.h"
|
||||
#include "../meshers/dmc/voxel_mesher_dmc.h"
|
||||
#include "../meshers/transvoxel/voxel_mesher_transvoxel.h"
|
||||
#include "../util/utility.h"
|
||||
#include "voxel_lod_terrain.h"
|
||||
#include <core/os/os.h>
|
||||
|
||||
static void scale_mesh_data(VoxelMesher::Output &data, float factor) {
|
||||
static void scale_mesh_data(Vector<Array> &surfaces, float factor) {
|
||||
|
||||
for (int i = 0; i < data.surfaces.size(); ++i) {
|
||||
Array &surface = data.surfaces.write[i]; // There is COW here too but should not happen, hopefully
|
||||
for (int i = 0; i < surfaces.size(); ++i) {
|
||||
Array &surface = surfaces.write[i]; // There is COW here too but should not happen, hopefully
|
||||
|
||||
if (surface.empty()) {
|
||||
continue;
|
||||
@ -37,24 +37,24 @@ VoxelMeshUpdater::VoxelMeshUpdater(unsigned int thread_count, MeshingParams para
|
||||
print_line("Constructing VoxelMeshUpdater");
|
||||
|
||||
Ref<VoxelMesherBlocky> blocky_mesher;
|
||||
Ref<VoxelMesherDMC> smooth_mesher;
|
||||
Ref<VoxelMesherTransvoxel> smooth_mesher;
|
||||
|
||||
_required_padding = 0;
|
||||
_minimum_padding = 0;
|
||||
_maximum_padding = 0;
|
||||
|
||||
if (params.library.is_valid()) {
|
||||
blocky_mesher.instance();
|
||||
blocky_mesher->set_library(params.library);
|
||||
blocky_mesher->set_occlusion_enabled(params.baked_ao);
|
||||
blocky_mesher->set_occlusion_darkness(params.baked_ao_darkness);
|
||||
_required_padding = max(_required_padding, blocky_mesher->get_minimum_padding());
|
||||
_minimum_padding = max(_minimum_padding, blocky_mesher->get_minimum_padding());
|
||||
_maximum_padding = max(_maximum_padding, blocky_mesher->get_maximum_padding());
|
||||
}
|
||||
|
||||
if (params.smooth_surface) {
|
||||
smooth_mesher.instance();
|
||||
smooth_mesher->set_geometric_error(0.05);
|
||||
smooth_mesher->set_simplify_mode(VoxelMesherDMC::SIMPLIFY_NONE);
|
||||
smooth_mesher->set_seam_mode(VoxelMesherDMC::SEAM_MARCHING_SQUARE_SKIRTS);
|
||||
_required_padding = max(_required_padding, smooth_mesher->get_minimum_padding());
|
||||
_minimum_padding = max(_minimum_padding, smooth_mesher->get_minimum_padding());
|
||||
_maximum_padding = max(_maximum_padding, smooth_mesher->get_maximum_padding());
|
||||
}
|
||||
|
||||
Mgr::BlockProcessingFunc processors[Mgr::MAX_LOD];
|
||||
@ -73,7 +73,7 @@ VoxelMeshUpdater::VoxelMeshUpdater(unsigned int thread_count, MeshingParams para
|
||||
}
|
||||
|
||||
processors[i] = [this, blocky_mesher, smooth_mesher](const ArraySlice<InputBlock> inputs, ArraySlice<OutputBlock> outputs, Mgr::ProcessorStats &_) {
|
||||
this->process_blocks_thread_func(inputs, outputs, blocky_mesher, smooth_mesher, this->_required_padding);
|
||||
this->process_blocks_thread_func(inputs, outputs, blocky_mesher, smooth_mesher);
|
||||
};
|
||||
}
|
||||
|
||||
@ -91,8 +91,7 @@ void VoxelMeshUpdater::process_blocks_thread_func(
|
||||
const ArraySlice<InputBlock> inputs,
|
||||
ArraySlice<OutputBlock> outputs,
|
||||
Ref<VoxelMesher> blocky_mesher,
|
||||
Ref<VoxelMesher> smooth_mesher,
|
||||
int padding) {
|
||||
Ref<VoxelMesher> smooth_mesher) {
|
||||
|
||||
CRASH_COND(inputs.size() != outputs.size());
|
||||
|
||||
@ -105,16 +104,20 @@ void VoxelMeshUpdater::process_blocks_thread_func(
|
||||
CRASH_COND(block.voxels.is_null());
|
||||
|
||||
if (blocky_mesher.is_valid()) {
|
||||
blocky_mesher->build(output.blocky_surfaces, **block.voxels, padding);
|
||||
blocky_mesher->build(output.blocky_surfaces, **block.voxels);
|
||||
}
|
||||
if (smooth_mesher.is_valid()) {
|
||||
smooth_mesher->build(output.smooth_surfaces, **block.voxels, padding);
|
||||
smooth_mesher->build(output.smooth_surfaces, **block.voxels);
|
||||
}
|
||||
|
||||
if (ib.lod > 0) {
|
||||
// TODO Make this optional if the mesher can factor in the upscale already
|
||||
float factor = 1 << ib.lod;
|
||||
scale_mesh_data(output.blocky_surfaces, factor);
|
||||
scale_mesh_data(output.smooth_surfaces, factor);
|
||||
scale_mesh_data(output.blocky_surfaces.surfaces, factor);
|
||||
scale_mesh_data(output.smooth_surfaces.surfaces, factor);
|
||||
for (int i = 0; i < output.smooth_surfaces.transition_surfaces.size(); ++i) {
|
||||
scale_mesh_data(output.smooth_surfaces.transition_surfaces[i], factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,17 +41,18 @@ public:
|
||||
void push(const Input &input) { _mgr->push(input); }
|
||||
void pop(Output &output) { _mgr->pop(output); }
|
||||
|
||||
int get_required_padding() const { return _required_padding; }
|
||||
int get_minimum_padding() const { return _minimum_padding; }
|
||||
int get_maximum_padding() const { return _maximum_padding; }
|
||||
|
||||
private:
|
||||
void process_blocks_thread_func(const ArraySlice<InputBlock> inputs,
|
||||
ArraySlice<OutputBlock> outputs,
|
||||
Ref<VoxelMesher> blocky_mesher,
|
||||
Ref<VoxelMesher> smooth_mesher,
|
||||
int padding);
|
||||
Ref<VoxelMesher> smooth_mesher);
|
||||
|
||||
Mgr *_mgr = nullptr;
|
||||
int _required_padding = 0;
|
||||
int _minimum_padding = 0;
|
||||
int _maximum_padding = 0;
|
||||
};
|
||||
|
||||
#endif // VOXEL_MESH_UPDATER_H
|
||||
|
@ -934,11 +934,12 @@ void VoxelTerrain::_process() {
|
||||
|
||||
// TODO Make the buffer re-usable
|
||||
unsigned int block_size = _map->get_block_size();
|
||||
unsigned int padding = _block_updater->get_required_padding();
|
||||
nbuffer->create(Vector3i(block_size + 2 * padding));
|
||||
unsigned int min_padding = _block_updater->get_minimum_padding();
|
||||
unsigned int max_padding = _block_updater->get_maximum_padding();
|
||||
nbuffer->create(Vector3i(block_size + min_padding + max_padding));
|
||||
|
||||
unsigned int channels_mask = (1 << VoxelBuffer::CHANNEL_TYPE) | (1 << VoxelBuffer::CHANNEL_ISOLEVEL);
|
||||
_map->get_buffer_copy(_map->block_to_voxel(block_pos) - Vector3i(padding), **nbuffer, channels_mask);
|
||||
_map->get_buffer_copy(_map->block_to_voxel(block_pos) - Vector3i(min_padding), **nbuffer, channels_mask);
|
||||
|
||||
VoxelMeshUpdater::InputBlock iblock;
|
||||
iblock.data.voxels = nbuffer;
|
||||
|
Loading…
x
Reference in New Issue
Block a user