Add base class to all meshers, gather common things in it

This commit is contained in:
Marc Gilleron 2019-04-28 20:48:59 +01:00
parent 12a97cca5a
commit faefde721a
14 changed files with 167 additions and 157 deletions

1
SCsub
View File

@ -4,6 +4,7 @@ env.add_source_files(env.modules_sources, "*.cpp")
env.add_source_files(env.modules_sources, "meshers/blocky/*.cpp")
env.add_source_files(env.modules_sources, "meshers/transvoxel/*.cpp")
env.add_source_files(env.modules_sources, "meshers/dmc/*.cpp")
env.add_source_files(env.modules_sources, "meshers/*.cpp")
env.add_source_files(env.modules_sources, "providers/*.cpp")
env.add_source_files(env.modules_sources, "util/*.cpp")
env.add_source_files(env.modules_sources, "terrain/*.cpp")

View File

@ -4,6 +4,8 @@
#include "../../voxel_library.h"
#include <core/os/os.h>
namespace {
template <typename T>
void raw_copy_to(PoolVector<T> &to, const Vector<T> &from) {
to.resize(from.size());
@ -11,26 +13,6 @@ void raw_copy_to(PoolVector<T> &to, const Vector<T> &from) {
memcpy(w.ptr(), from.ptr(), from.size() * sizeof(T));
}
VoxelMesherBlocky::VoxelMesherBlocky() :
_baked_occlusion_darkness(0.8),
_bake_occlusion(true) {}
void VoxelMesherBlocky::set_library(Ref<VoxelLibrary> library) {
_library = library;
}
void VoxelMesherBlocky::set_occlusion_darkness(float darkness) {
_baked_occlusion_darkness = darkness;
if (_baked_occlusion_darkness < 0.0)
_baked_occlusion_darkness = 0.0;
else if (_baked_occlusion_darkness >= 1.0)
_baked_occlusion_darkness = 1.0;
}
void VoxelMesherBlocky::set_occlusion_enabled(bool enable) {
_bake_occlusion = enable;
}
inline Color Color_greyscale(float c) {
return Color(c, c, c);
}
@ -51,36 +33,35 @@ inline bool is_transparent(const VoxelLibrary &lib, int voxel_id) {
return true;
}
Ref<ArrayMesh> VoxelMesherBlocky::build_mesh(Ref<VoxelBuffer> buffer_ref, unsigned int channel, Array materials, Ref<ArrayMesh> mesh) {
ERR_FAIL_COND_V(buffer_ref.is_null(), Ref<ArrayMesh>());
} // namespace
VoxelBuffer &buffer = **buffer_ref;
Array surfaces = build(buffer, channel, MINIMUM_PADDING);
VoxelMesherBlocky::VoxelMesherBlocky() :
_baked_occlusion_darkness(0.8),
_bake_occlusion(true) {}
if (mesh.is_null())
mesh.instance();
int surface = mesh->get_surface_count();
for (int i = 0; i < surfaces.size(); ++i) {
Array arrays = surfaces[i];
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
Ref<Material> material = materials[i];
if (material.is_valid()) {
mesh->surface_set_material(surface, material);
}
}
return mesh;
void VoxelMesherBlocky::set_library(Ref<VoxelLibrary> library) {
_library = library;
}
Array VoxelMesherBlocky::build(const VoxelBuffer &buffer, unsigned int channel, int padding) {
void VoxelMesherBlocky::set_occlusion_darkness(float darkness) {
_baked_occlusion_darkness = darkness;
if (_baked_occlusion_darkness < 0.0)
_baked_occlusion_darkness = 0.0;
else if (_baked_occlusion_darkness >= 1.0)
_baked_occlusion_darkness = 1.0;
}
void VoxelMesherBlocky::set_occlusion_enabled(bool enable) {
_bake_occlusion = enable;
}
void VoxelMesherBlocky::build(VoxelMesher::Output &output, const VoxelBuffer &buffer, int padding) {
//uint64_t time_before = OS::get_singleton()->get_ticks_usec();
ERR_FAIL_COND_V(_library.is_null(), Array());
ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Array());
ERR_FAIL_COND_V(padding < MINIMUM_PADDING, Array());
ERR_FAIL_COND(_library.is_null());
ERR_FAIL_COND(padding < MINIMUM_PADDING);
const int channel = VoxelBuffer::CHANNEL_TYPE;
const VoxelLibrary &library = **_library;
@ -118,7 +99,7 @@ Array VoxelMesherBlocky::build(const VoxelBuffer &buffer, unsigned int channel,
// 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.
uint8_t *type_buffer = buffer.get_channel_raw(Voxel::CHANNEL_TYPE);
uint8_t *type_buffer = buffer.get_channel_raw(channel);
// _
// | \
// /\ \\
@ -132,7 +113,7 @@ Array VoxelMesherBlocky::build(const VoxelBuffer &buffer, unsigned int channel,
// No data to read, the channel is probably uniform
// TODO This is an invalid behavior IF sending a full block of uniformly opaque cubes,
// however not likely for terrains because with neighbor padding, such a case means no face would be generated anyways
return Array();
return;
}
//CRASH_COND(memarr_len(type_buffer) != buffer.get_volume() * sizeof(uint8_t));
@ -367,8 +348,6 @@ Array VoxelMesherBlocky::build(const VoxelBuffer &buffer, unsigned int channel,
// print_line(String("Made mesh v: ") + String::num(_arrays[0].positions.size())
// + String(", i: ") + String::num(_arrays[0].indices.size()));
Array surfaces;
// TODO We could return a single byte array and use Mesh::add_surface down the line?
for (int i = 0; i < MAX_MATERIALS; ++i) {
@ -407,15 +386,19 @@ Array VoxelMesherBlocky::build(const VoxelBuffer &buffer, unsigned int channel,
mesh_arrays[Mesh::ARRAY_INDEX] = indices;
}
surfaces.append(mesh_arrays);
output.surfaces.push_back(mesh_arrays);
}
}
output.primitive_type = Mesh::PRIMITIVE_TRIANGLES;
//uint64_t time_commit = OS::get_singleton()->get_ticks_usec() - time_before;
//print_line(String("P: {0}, M: {1}, C: {2}").format(varray(time_prep, time_meshing, time_commit)));
}
return surfaces;
int VoxelMesherBlocky::get_minimum_padding() const {
return MINIMUM_PADDING;
}
void VoxelMesherBlocky::_bind_methods() {
@ -429,8 +412,6 @@ void VoxelMesherBlocky::_bind_methods() {
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);
ClassDB::bind_method(D_METHOD("build_mesh", "voxel_buffer", "channel", "materials", "existing_mesh"), &VoxelMesherBlocky::build_mesh);
#ifdef VOXEL_PROFILING
ClassDB::bind_method(D_METHOD("get_profiling_info"), &VoxelMesherBlocky::get_profiling_info);
#endif

View File

@ -3,14 +3,13 @@
#include "../../util/zprofiling.h"
#include "../../voxel.h"
#include "../../voxel_buffer.h"
#include "../../voxel_library.h"
#include "../voxel_mesher.h"
#include <core/reference.h>
#include <scene/resources/mesh.h>
// TODO Should be renamed VoxelMesherBlocky or something like that
class VoxelMesherBlocky : public Reference {
GDCLASS(VoxelMesherBlocky, Reference)
class VoxelMesherBlocky : public VoxelMesher {
GDCLASS(VoxelMesherBlocky, VoxelMesher)
public:
static const unsigned int MAX_MATERIALS = 8; // Arbitrary. Tweak if needed.
@ -27,13 +26,14 @@ public:
void set_occlusion_enabled(bool enable);
bool get_occlusion_enabled() const { return _bake_occlusion; }
Array build(const VoxelBuffer &buffer_ref, unsigned int channel, int padding);
Ref<ArrayMesh> build_mesh(Ref<VoxelBuffer> buffer_ref, unsigned int channel, Array materials, Ref<ArrayMesh> mesh = Ref<ArrayMesh>());
void build(VoxelMesher::Output &output, const VoxelBuffer &voxels, int padding) override;
int get_minimum_padding() const override;
protected:
static void _bind_methods();
private:
// TODO Replace those with std::vector, it's faster
struct Arrays {
Vector<Vector3> positions;
Vector<Vector3> normals;

View File

@ -1317,7 +1317,7 @@ float VoxelMesherDMC::get_geometric_error() const {
return _geometric_error;
}
Array VoxelMesherDMC::build(const VoxelBuffer &voxels) {
void VoxelMesherDMC::build(VoxelMesher::Output &output, const VoxelBuffer &voxels, int padding) {
// Requirements:
// - Voxel data must be padded
@ -1328,17 +1328,18 @@ Array VoxelMesherDMC::build(const VoxelBuffer &voxels) {
if (voxels.is_uniform(VoxelBuffer::CHANNEL_ISOLEVEL)) {
// That won't produce any polygon
return Array();
return;
}
int padding = 2;
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_V(voxels.get_size().x < chunk_size + padding * 2, Array());
ERR_FAIL_COND_V(voxels.get_size().y < chunk_size + padding * 2, Array());
ERR_FAIL_COND_V(voxels.get_size().z < chunk_size + padding * 2, Array());
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);
// Construct an intermediate to handle padding transparently
dmc::VoxelAccess voxels_access(voxels, Vector3i(padding));
@ -1427,33 +1428,17 @@ Array VoxelMesherDMC::build(const VoxelBuffer &voxels) {
// TODO Marching squares skirts
// surfaces[material][array_type], for now single material
Array surfaces;
surfaces.append(surface);
return surfaces;
output.surfaces.push_back(surface);
if (_mesh_mode == MESH_NORMAL) {
output.primitive_type = Mesh::PRIMITIVE_TRIANGLES;
} else {
output.primitive_type = Mesh::PRIMITIVE_LINES;
}
}
Ref<ArrayMesh> VoxelMesherDMC::build_mesh(Ref<VoxelBuffer> voxels) {
ERR_FAIL_COND_V(voxels.is_null(), Ref<ArrayMesh>());
Array surfaces = build(**voxels);
if (surfaces.empty()) {
return Ref<ArrayMesh>();
}
Ref<ArrayMesh> mesh;
mesh.instance();
for (int i = 0; i < surfaces.size(); ++i) {
if (_mesh_mode == MESH_NORMAL) {
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, surfaces[i]);
} else {
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, surfaces[i]);
}
}
return mesh;
int VoxelMesherDMC::get_minimum_padding() const {
return MINIMUM_PADDING;
}
Dictionary VoxelMesherDMC::get_stats() const {
@ -1476,7 +1461,6 @@ void VoxelMesherDMC::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_geometric_error", "error"), &VoxelMesherDMC::set_geometric_error);
ClassDB::bind_method(D_METHOD("get_geometric_error"), &VoxelMesherDMC::get_geometric_error);
ClassDB::bind_method(D_METHOD("build_mesh", "voxel_buffer"), &VoxelMesherDMC::build_mesh);
ClassDB::bind_method(D_METHOD("get_stats"), &VoxelMesherDMC::get_stats);
BIND_ENUM_CONSTANT(MESH_NORMAL);

View File

@ -1,7 +1,7 @@
#ifndef VOXEL_MESHER_DMC_H
#define VOXEL_MESHER_DMC_H
#include "../../voxel_buffer.h"
#include "../voxel_mesher.h"
#include "hermite_value.h"
#include "mesh_builder.h"
#include "object_pool.h"
@ -64,9 +64,11 @@ struct DualGrid {
} // namespace dmc
class VoxelMesherDMC : public Reference {
GDCLASS(VoxelMesherDMC, Reference)
class VoxelMesherDMC : public VoxelMesher {
GDCLASS(VoxelMesherDMC, VoxelMesher)
public:
static const int MINIMUM_PADDING = 2;
enum MeshMode {
MESH_NORMAL,
MESH_WIREFRAME,
@ -91,8 +93,8 @@ public:
void set_geometric_error(real_t geometric_error);
float get_geometric_error() const;
Array build(const VoxelBuffer &voxels);
Ref<ArrayMesh> build_mesh(Ref<VoxelBuffer> voxels);
void build(VoxelMesher::Output &output, const VoxelBuffer &voxels, int padding) override;
int get_minimum_padding() const override;
Dictionary get_stats() const;

View File

@ -3,6 +3,8 @@
#include "transvoxel_tables.cpp"
#include <core/os/os.h>
namespace {
inline float tof(int8_t v) {
return static_cast<float>(v) / 256.f;
}
@ -58,6 +60,8 @@ void copy_to(PoolVector<T> &to, Vector<T> &from) {
}
}
} // namespace
VoxelMesherTransvoxel::ReuseCell::ReuseCell() {
case_index = 0;
for (unsigned int i = 0; i < 4; ++i) {
@ -65,32 +69,15 @@ VoxelMesherTransvoxel::ReuseCell::ReuseCell() {
}
}
VoxelMesherTransvoxel::VoxelMesherTransvoxel() {
int VoxelMesherTransvoxel::get_minimum_padding() const {
return MINIMUM_PADDING;
}
Ref<ArrayMesh> VoxelMesherTransvoxel::build_mesh(Ref<VoxelBuffer> voxels_ref, unsigned int channel, Ref<ArrayMesh> mesh) {
void VoxelMesherTransvoxel::build(VoxelMesher::Output &output, const VoxelBuffer &voxels, int padding) {
ERR_FAIL_COND_V(voxels_ref.is_null(), Ref<ArrayMesh>());
ERR_FAIL_COND(padding < MINIMUM_PADDING);
VoxelBuffer &buffer = **voxels_ref;
Array surfaces = build(buffer, channel);
if (mesh.is_null())
mesh.instance();
//int surface = mesh->get_surface_count();
for (int i = 0; i < surfaces.size(); ++i) {
Array arrays = surfaces[i];
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
//mesh->surface_set_material(surface, _materials[i]);
}
return mesh;
}
Array VoxelMesherTransvoxel::build(const VoxelBuffer &voxels, unsigned int channel) {
ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Array());
int channel = VoxelBuffer::CHANNEL_ISOLEVEL;
// Initialize dynamic memory:
// These vectors are re-used.
@ -109,7 +96,7 @@ Array VoxelMesherTransvoxel::build(const VoxelBuffer &voxels, unsigned int chann
if (m_output_vertices.size() == 0) {
// The mesh can be empty
return Array();
return;
}
PoolVector<Vector3> vertices;
@ -128,10 +115,8 @@ Array VoxelMesherTransvoxel::build(const VoxelBuffer &voxels, unsigned int chann
}
arrays[Mesh::ARRAY_INDEX] = indices;
Array surfaces;
surfaces.append(arrays);
return surfaces;
output.surfaces.push_back(arrays);
output.primitive_type = Mesh::PRIMITIVE_TRIANGLES;
}
void VoxelMesherTransvoxel::build_internal(const VoxelBuffer &voxels, unsigned int channel) {
@ -393,6 +378,4 @@ void VoxelMesherTransvoxel::emit_vertex(Vector3 primary, Vector3 normal) {
}
void VoxelMesherTransvoxel::_bind_methods() {
ClassDB::bind_method(D_METHOD("build", "voxels", "channel", "existing_mesh"), &VoxelMesherTransvoxel::build_mesh, DEFVAL(Variant()));
}

View File

@ -1,17 +1,17 @@
#ifndef VOXEL_MESHER_SMOOTH_H
#define VOXEL_MESHER_SMOOTH_H
#include "../../voxel_buffer.h"
#include "../voxel_mesher.h"
#include <scene/resources/mesh.h>
class VoxelMesherTransvoxel : public Reference {
GDCLASS(VoxelMesherTransvoxel, Reference)
class VoxelMesherTransvoxel : public VoxelMesher {
GDCLASS(VoxelMesherTransvoxel, VoxelMesher)
public:
VoxelMesherTransvoxel();
static const int MINIMUM_PADDING = 2;
Ref<ArrayMesh> build_mesh(Ref<VoxelBuffer> voxels_ref, unsigned int channel, Ref<ArrayMesh> mesh = Ref<ArrayMesh>());
Array build(const VoxelBuffer &voxels, unsigned int channel);
void build(VoxelMesher::Output &output, const VoxelBuffer &voxels, int padding) override;
int get_minimum_padding() const override;
protected:
static void _bind_methods();

36
meshers/voxel_mesher.cpp Normal file
View File

@ -0,0 +1,36 @@
#include "voxel_mesher.h"
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());
if (output.surfaces.empty()) {
return Ref<ArrayMesh>();
}
Ref<ArrayMesh> mesh;
mesh.instance();
for (int i = 0; i < output.surfaces.size(); ++i) {
mesh->add_surface_from_arrays(output.primitive_type, output.surfaces[i]);
}
return mesh;
}
void VoxelMesher::build(Output &output, const VoxelBuffer &voxels, int padding) {
}
int VoxelMesher::get_minimum_padding() const {
return 0;
}
void VoxelMesher::_bind_methods() {
// Shortcut if you want to generate a mesh directly from a fixed grid of voxels.
// Useful for testing the different meshers.
ClassDB::bind_method(D_METHOD("build_mesh", "voxel_buffer"), &VoxelMesher::build_mesh);
}

24
meshers/voxel_mesher.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef VOXEL_MESHER_H
#define VOXEL_MESHER_H
#include "../voxel_buffer.h"
#include <scene/resources/mesh.h>
class VoxelMesher : public Reference {
GDCLASS(VoxelMesher, Reference)
public:
struct Output {
Vector<Array> surfaces;
Mesh::PrimitiveType primitive_type;
};
virtual void build(Output &output, const VoxelBuffer &voxels, int padding);
virtual int get_minimum_padding() const;
Ref<Mesh> build_mesh(Ref<VoxelBuffer> voxels);
protected:
static void _bind_methods();
};
#endif // VOXEL_MESHER_H

View File

@ -34,6 +34,7 @@ void register_voxel_types() {
ClassDB::register_class<VoxelIsoSurfaceTool>();
// Meshers
ClassDB::register_class<VoxelMesher>();
ClassDB::register_class<VoxelMesherBlocky>();
ClassDB::register_class<VoxelMesherTransvoxel>();
ClassDB::register_class<VoxelMesherDMC>();

View File

@ -107,6 +107,17 @@ void VoxelMeshUpdater::pop(Output &output) {
_shared_output.blocks.clear();
}
int VoxelMeshUpdater::get_required_padding() const {
int padding = _blocky_mesher->get_minimum_padding();
if (_dmc_mesher.is_valid()) {
padding = max(padding, _dmc_mesher->get_minimum_padding());
}
return padding;
}
void VoxelMeshUpdater::_thread_func(void *p_self) {
VoxelMeshUpdater *self = reinterpret_cast<VoxelMeshUpdater *>(p_self);
self->thread_func();
@ -181,17 +192,12 @@ void VoxelMeshUpdater::process_block(const InputBlock &block, OutputBlock &outpu
CRASH_COND(block.voxels.is_null());
int padding = 1;
if (_dmc_mesher.is_valid()) {
padding = 2;
}
int padding = get_required_padding();
// Build cubic parts of the mesh
output.model_surfaces = _blocky_mesher->build(**block.voxels, Voxel::CHANNEL_TYPE, padding);
_blocky_mesher->build(output.blocky_surfaces, **block.voxels, padding);
if (_dmc_mesher.is_valid()) {
// Build smooth parts of the mesh
output.smooth_surfaces = _dmc_mesher->build(**block.voxels);
_dmc_mesher->build(output.smooth_surfaces, **block.voxels, padding);
}
output.position = block.position;

View File

@ -26,8 +26,8 @@ public:
};
struct OutputBlock {
Array model_surfaces;
Array smooth_surfaces;
VoxelMesher::Output blocky_surfaces;
VoxelMesher::Output smooth_surfaces;
Vector3i position;
};
@ -66,6 +66,8 @@ public:
void push(const Input &input);
void pop(Output &output);
int get_required_padding() const;
private:
static void _thread_func(void *p_self);
void thread_func();

View File

@ -286,15 +286,6 @@ void VoxelTerrain::reset_updater() {
_block_updater = memnew(VoxelMeshUpdater(_library, params));
}
int VoxelTerrain::get_block_padding() const {
// How many neighbor voxels we should pad for mesh updates to be seamless
// TODO Generalize padding retrieval, or split terrain systems because blocky and smooth are two different beasts
// - Blocky needs padding of 1
// - Transvoxel needs padding of 2
// - DMC needs padding of 2
return _smooth_meshing_enabled ? 2 : 1;
}
inline int get_border_index(int x, int max) {
return x == 0 ? 0 : x != max ? 1 : 2;
}
@ -757,7 +748,7 @@ void VoxelTerrain::_process() {
// TODO Make the buffer re-usable
unsigned int block_size = _map->get_block_size();
unsigned int padding = get_block_padding();
unsigned int padding = _block_updater->get_required_padding();
nbuffer->create(block_size + 2 * padding, block_size + 2 * padding, block_size + 2 * padding);
unsigned int channels_mask = (1 << VoxelBuffer::CHANNEL_TYPE) | (1 << VoxelBuffer::CHANNEL_ISOLEVEL);
@ -820,30 +811,30 @@ void VoxelTerrain::_process() {
mesh.instance();
int surface_index = 0;
for (int i = 0; i < ob.model_surfaces.size(); ++i) {
for (int i = 0; i < ob.blocky_surfaces.surfaces.size(); ++i) {
Array surface = ob.model_surfaces[i];
Array surface = ob.blocky_surfaces.surfaces[i];
if (surface.empty()) {
continue;
}
CRASH_COND(surface.size() != Mesh::ARRAY_MAX);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, surface);
mesh->add_surface_from_arrays(ob.blocky_surfaces.primitive_type, surface);
mesh->surface_set_material(surface_index, _materials[i]);
++surface_index;
}
for (int i = 0; i < ob.smooth_surfaces.size(); ++i) {
for (int i = 0; i < ob.smooth_surfaces.surfaces.size(); ++i) {
Array surface = ob.smooth_surfaces[i];
Array surface = ob.smooth_surfaces.surfaces[i];
if (surface.empty()) {
continue;
}
CRASH_COND(surface.size() != Mesh::ARRAY_MAX);
// TODO Problem here, the mesher could be configured to output wireframe! Need to output some MeshData struct instead
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, surface);
mesh->add_surface_from_arrays(ob.smooth_surfaces.primitive_type, surface);
mesh->surface_set_material(surface_index, _materials[i]);
// No material supported yet
++surface_index;

View File

@ -98,7 +98,6 @@ private:
void make_all_view_dirty_deferred();
void reset_updater();
int get_block_padding() const;
Spatial *get_viewer(NodePath path) const;