Added smooth voxels (untextured at the moment)

This commit is contained in:
Marc Gilleron 2017-04-06 23:44:11 +02:00
parent 2e89ab0294
commit a5a1ded2f3
14 changed files with 1661 additions and 107 deletions

View File

@ -5,6 +5,7 @@
#include "voxel_map.h"
#include "voxel_terrain.h"
#include "voxel_provider_test.h"
#include "voxel_mesher_smooth.h"
void register_voxel_types() {
@ -16,6 +17,7 @@ void register_voxel_types() {
ClassDB::register_class<VoxelTerrain>();
ClassDB::register_class<VoxelProvider>();
ClassDB::register_class<VoxelProviderTest>();
ClassDB::register_class<VoxelMesherSmooth>();
}

1035
transvoxel_tables.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -197,5 +197,9 @@ void Voxel::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_cube_uv_all_sides:Voxel", "atlas_pos"), &Voxel::set_cube_uv_all_sides);
ClassDB::bind_method(D_METHOD("set_cube_uv_tbs_sides:Voxel", "top_atlas_pos", "side_atlas_pos", "bottom_atlas_pos"), &Voxel::set_cube_uv_tbs_sides);
BIND_CONSTANT( CHANNEL_TYPE )
BIND_CONSTANT( CHANNEL_ISOLEVEL )
BIND_CONSTANT( CHANNEL_DATA )
}

12
voxel.h
View File

@ -23,6 +23,15 @@ public:
SIDE_COUNT
};
enum ChannelMode {
// For mapping to a Voxel type
CHANNEL_TYPE = 0,
// Distance to surface for smooth voxels
CHANNEL_ISOLEVEL,
// Arbitrary data not used by the module
CHANNEL_DATA
};
Voxel();
// Properties
@ -87,5 +96,8 @@ private:
};
VARIANT_ENUM_CAST(Voxel::ChannelMode)
#endif // VOXEL_TYPE_H

View File

@ -1,5 +1,6 @@
#include "voxel_buffer.h"
#include <string.h>
#include <math_funcs.h>
VoxelBuffer::VoxelBuffer() {
@ -123,10 +124,10 @@ void VoxelBuffer::fill_area(int defval, Vector3i min, Vector3i max, unsigned int
}
}
bool VoxelBuffer::is_uniform(unsigned int channel_index) {
bool VoxelBuffer::is_uniform(unsigned int channel_index) const {
ERR_FAIL_INDEX_V(channel_index, MAX_CHANNELS, true);
Channel & channel = _channels[channel_index];
const Channel & channel = _channels[channel_index];
if (channel.data == NULL)
return true;
@ -248,8 +249,10 @@ void VoxelBuffer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_size_z"), &VoxelBuffer::get_size_z);
ClassDB::bind_method(D_METHOD("set_voxel", "value", "x", "y", "z", "channel"), &VoxelBuffer::_set_voxel_binding, DEFVAL(0));
ClassDB::bind_method(D_METHOD("set_voxel_iso", "value", "x", "y", "z", "channel"), &VoxelBuffer::_set_voxel_iso_binding, DEFVAL(0));
ClassDB::bind_method(D_METHOD("set_voxel_v", "value", "pos", "channel"), &VoxelBuffer::set_voxel_v, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_voxel", "x", "y", "z", "channel"), &VoxelBuffer::_get_voxel_binding, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_voxel_iso", "x", "y", "z", "channel"), &VoxelBuffer::get_voxel_iso, DEFVAL(0));
ClassDB::bind_method(D_METHOD("fill", "value", "channel"), &VoxelBuffer::fill, DEFVAL(0));
ClassDB::bind_method(D_METHOD("fill_area", "value", "min", "max", "channel"), &VoxelBuffer::_fill_area_binding, DEFVAL(0));

View File

@ -16,6 +16,21 @@ public:
// Arbitrary value, 8 should be enough. Tweak for your needs.
static const int MAX_CHANNELS = 8;
// Converts -1..1 float into 0..255 integer
static inline int iso_to_byte(real_t iso) {
int v = static_cast<int>(128.f * iso + 128.f);
if(v > 255)
return 255;
else if(v < 0)
return 0;
return v;
}
// Converts 0..255 integer into -1..1 float
static inline real_t byte_to_iso(int b) {
return static_cast<float>(b - 128) / 128.f;
}
VoxelBuffer();
~VoxelBuffer();
@ -30,13 +45,15 @@ public:
int get_voxel(int x, int y, int z, unsigned int channel_index=0) const;
void set_voxel(int value, int x, int y, int z, unsigned int channel_index=0);
void set_voxel_v(int value, Vector3 pos, unsigned int channel_index = 0);
_FORCE_INLINE_ void set_voxel_iso(real_t value, int x, int y, int z, unsigned int channel_index=0) { set_voxel(iso_to_byte(value), x,y,z, channel_index); }
_FORCE_INLINE_ real_t get_voxel_iso(int x, int y, int z, unsigned int channel_index=0) const { return byte_to_iso(get_voxel(x,y,z,channel_index)); }
_FORCE_INLINE_ int get_voxel(const Vector3i pos, unsigned int channel_index = 0) const { return get_voxel(pos.x, pos.y, pos.z, channel_index); }
_FORCE_INLINE_ void set_voxel(int value, const Vector3i pos, unsigned int channel_index = 0) { set_voxel(value, pos.x, pos.y, pos.z, channel_index); }
void fill(int defval, unsigned int channel_index = 0);
void fill_area(int defval, Vector3i min, Vector3i max, unsigned int channel_index = 0);
bool is_uniform(unsigned int channel_index = 0);
bool is_uniform(unsigned int channel_index = 0) const;
void optimize();
@ -57,7 +74,7 @@ public:
return (z * _size.z + x) * _size.x;
}
_FORCE_INLINE_ unsigned int get_volume() {
_FORCE_INLINE_ unsigned int get_volume() const {
return _size.x * _size.y * _size.z;
}
@ -78,6 +95,7 @@ protected:
void _copy_from_binding(Ref<VoxelBuffer> other, unsigned int channel);
void _copy_from_area_binding(Ref<VoxelBuffer> other, Vector3 src_min, Vector3 src_max, Vector3 dst_min, unsigned int channel);
_FORCE_INLINE_ void _fill_area_binding(int defval, Vector3 min, Vector3 max, unsigned int channel_index) { fill_area(defval, Vector3i(min), Vector3i(max), channel_index); }
_FORCE_INLINE_ void _set_voxel_iso_binding(real_t value, int x, int y, int z, unsigned int channel) { set_voxel_iso(value, x,y,z, channel); }
private:
struct Channel {

View File

@ -170,8 +170,7 @@ bool VoxelMap::is_block_surrounded(Vector3i pos) const {
return true;
}
void VoxelMap::get_buffer_copy(Vector3i min_pos, VoxelBuffer & dst_buffer, unsigned int channel) {
ERR_FAIL_INDEX(channel, VoxelBuffer::MAX_CHANNELS);
void VoxelMap::get_buffer_copy(Vector3i min_pos, VoxelBuffer & dst_buffer, unsigned int channels_mask) {
Vector3i max_pos = min_pos + dst_buffer.get_size();
@ -179,30 +178,38 @@ void VoxelMap::get_buffer_copy(Vector3i min_pos, VoxelBuffer & dst_buffer, unsig
Vector3i max_block_pos = voxel_to_block(max_pos - Vector3i(1,1,1)) + Vector3i(1,1,1);
ERR_FAIL_COND((max_block_pos - min_block_pos) != Vector3(3, 3, 3));
Vector3i bpos;
for (bpos.z = min_block_pos.z; bpos.z < max_block_pos.z; ++bpos.z) {
for (bpos.x = min_block_pos.x; bpos.x < max_block_pos.x; ++bpos.x) {
for (bpos.y = min_block_pos.y; bpos.y < max_block_pos.y; ++bpos.y) {
for(unsigned int channel = 0; channel < VoxelBuffer::MAX_CHANNELS; ++channel) {
VoxelBlock * block = get_block(bpos);
if (block) {
if(((1 << channel) & channels_mask) == 0) {
continue;
}
Vector3i bpos;
for (bpos.z = min_block_pos.z; bpos.z < max_block_pos.z; ++bpos.z) {
for (bpos.x = min_block_pos.x; bpos.x < max_block_pos.x; ++bpos.x) {
for (bpos.y = min_block_pos.y; bpos.y < max_block_pos.y; ++bpos.y) {
VoxelBlock * block = get_block(bpos);
if (block) {
VoxelBuffer & src_buffer = **block->voxels;
Vector3i offset = block_to_voxel(bpos);
// Note: copy_from takes care of clamping the area if it's on an edge
dst_buffer.copy_from(src_buffer, min_pos - offset, max_pos - offset, offset - min_pos, channel);
}
else {
Vector3i offset = block_to_voxel(bpos);
dst_buffer.fill_area(
_default_voxel[channel],
offset - min_pos,
offset - min_pos + Vector3i(VoxelBlock::SIZE,VoxelBlock::SIZE, VoxelBlock::SIZE)
);
}
VoxelBuffer & src_buffer = **block->voxels;
Vector3i offset = block_to_voxel(bpos);
// Note: copy_from takes care of clamping the area if it's on an edge
dst_buffer.copy_from(src_buffer, min_pos - offset, max_pos - offset, offset - min_pos, channel);
}
else {
Vector3i offset = block_to_voxel(bpos);
dst_buffer.fill_area(
_default_voxel[channel],
offset - min_pos,
offset - min_pos + Vector3i(VoxelBlock::SIZE,VoxelBlock::SIZE, VoxelBlock::SIZE)
);
}
}
}
}
}

View File

@ -67,7 +67,7 @@ public:
int get_default_voxel(unsigned int channel=0);
// Gets a copy of all voxels in the area starting at min_pos having the same size as dst_buffer.
void get_buffer_copy(Vector3i min_pos, VoxelBuffer & dst_buffer, unsigned int channel = 0);
void get_buffer_copy(Vector3i min_pos, VoxelBuffer & dst_buffer, unsigned int channels_mask = 1);
// Moves the given buffer into a block of the map. The buffer is referenced, no copy is made.
void set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer);

View File

@ -1,6 +1,6 @@
#include "voxel_mesher.h"
#include "voxel_library.h"
#include "voxel_mesher_smooth.h"
// The following tables respect the following conventions
//
@ -148,7 +148,7 @@ void VoxelMesher::set_occlusion_darkness(float darkness) {
else if (_baked_occlusion_darkness >= 1.0)
_baked_occlusion_darkness = 1.0;
}
void VoxelMesher::set_occlusion_enabled(bool enable) {
_bake_occlusion = enable;
}
@ -171,13 +171,16 @@ inline bool is_transparent(const VoxelLibrary & lib, int voxel_id) {
return true;
}
Ref<Mesh> VoxelMesher::build_ref(Ref<VoxelBuffer> buffer_ref) {
Ref<Mesh> VoxelMesher::build_ref(Ref<VoxelBuffer> buffer_ref, unsigned int channel, Ref<Mesh> mesh) {
ERR_FAIL_COND_V(buffer_ref.is_null(), Ref<Mesh>());
return build(**buffer_ref);
VoxelBuffer & buffer = **buffer_ref;
mesh = build(buffer, channel, Vector3i(), buffer.get_size(), mesh);
return mesh;
}
Ref<Mesh> VoxelMesher::build(const VoxelBuffer & buffer) {
Ref<Mesh> VoxelMesher::build(const VoxelBuffer & buffer, unsigned int channel, Vector3i min, Vector3i max, Ref<Mesh> mesh) {
ERR_FAIL_COND_V(_library.is_null(), Ref<Mesh>());
ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Ref<Mesh>());
const VoxelLibrary & library = **_library;
@ -199,12 +202,17 @@ Ref<Mesh> VoxelMesher::build(const VoxelBuffer & buffer) {
VOXEL_PROFILE_BEGIN("mesher_face_extraction")
// Data must be padded, hence the off-by-one
Vector3i::sort_min_max(min, max);
const Vector3i pad(1,1,1);
min.clamp_to(pad, max);
max.clamp_to(min, buffer.get_size()-pad);
// 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.
const Vector3i buffer_size = buffer.get_size();
for (unsigned int z = 1; z < buffer_size.z-1; ++z) {
for (unsigned int x = 1; x < buffer_size.x-1; ++x) {
for (unsigned int y = 1; y < buffer_size.y-1; ++y) {
for (unsigned int z = min.z; z < max.z; ++z) {
for (unsigned int x = min.x; x < max.x; ++x) {
for (unsigned int y = min.y; y < max.y; ++y) {
int voxel_id = buffer.get_voxel(x, y, z, 0);
@ -228,7 +236,7 @@ Ref<Mesh> VoxelMesher::build(const VoxelBuffer & buffer) {
unsigned ny = y + normal.y;
unsigned nz = z + normal.z;
int neighbor_voxel_id = buffer.get_voxel(nx, ny, nz, 0);
int neighbor_voxel_id = buffer.get_voxel(nx, ny, nz, channel);
// TODO Better face visibility test
if (is_face_visible(library, voxel, neighbor_voxel_id)) {
@ -329,16 +337,20 @@ Ref<Mesh> VoxelMesher::build(const VoxelBuffer & buffer) {
VOXEL_PROFILE_END("mesher_face_extraction")
// Commit mesh
Ref<Mesh> mesh_ref;
Ref<Mesh> mesh_ref = mesh;
if(mesh.is_null())
mesh_ref = Ref<Mesh>(memnew(Mesh));
for (unsigned int i = 0; i < MAX_MATERIALS; ++i) {
if (_materials[i].is_valid()) {
SurfaceTool & st = _surface_tool[i];
// Index mesh to reduce memory usage and make upload to VRAM faster
// TODO actually, we could make it indexed from the ground up without using SurfaceTool, so we also save time!
VOXEL_PROFILE_BEGIN("mesher_surfacetool_index")
st.index();
VOXEL_PROFILE_END("mesher_surfacetool_index")
//VOXEL_PROFILE_BEGIN("mesher_surfacetool_index")
//st.index();
//VOXEL_PROFILE_END("mesher_surfacetool_index")
VOXEL_PROFILE_BEGIN("mesher_surfacetool_commit")
mesh_ref = st.commit(mesh_ref);
@ -348,7 +360,7 @@ Ref<Mesh> VoxelMesher::build(const VoxelBuffer & buffer) {
}
}
return mesh_ref;
return mesh_ref;
}
void VoxelMesher::_bind_methods() {
@ -365,7 +377,7 @@ void VoxelMesher::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_occlusion_darkness", "value"), &VoxelMesher::set_occlusion_darkness);
ClassDB::bind_method(D_METHOD("get_occlusion_darkness"), &VoxelMesher::get_occlusion_darkness);
ClassDB::bind_method(D_METHOD("build:Mesh", "voxel_buffer:VoxelBuffer"), &VoxelMesher::build_ref);
ClassDB::bind_method(D_METHOD("build:Mesh", "voxel_buffer:VoxelBuffer", "channel", "existing_mesh:Mesh"), &VoxelMesher::build_ref);
#ifdef VOXEL_PROFILING
ClassDB::bind_method(D_METHOD("get_profiling_info"), &VoxelMesher::get_profiling_info);

View File

@ -9,6 +9,8 @@
#include "voxel_library.h"
#include "zprofiling.h"
// TODO Should be renamed VoxelMesherCubic or something like that
class VoxelMesher : public Reference {
GDCLASS(VoxelMesher, Reference)
@ -29,8 +31,8 @@ public:
void set_occlusion_enabled(bool enable);
bool get_occlusion_enabled() const { return _bake_occlusion; }
Ref<Mesh> build(const VoxelBuffer & buffer_ref);
Ref<Mesh> build_ref(Ref<VoxelBuffer> buffer_ref);
Ref<Mesh> build(const VoxelBuffer & buffer_ref, unsigned int channel, Vector3i min, Vector3i max, Ref<Mesh> mesh=Ref<Mesh>());
Ref<Mesh> build_ref(Ref<VoxelBuffer> buffer_ref, unsigned int channel, Ref<Mesh> mesh=Ref<Mesh>());
protected:
static void _bind_methods();

401
voxel_mesher_smooth.cpp Normal file
View File

@ -0,0 +1,401 @@
#include "voxel_mesher_smooth.h"
#include "transvoxel_tables.cpp"
#include <os/os.h>
inline float tof(int8_t v) {
return static_cast<float>(v) / 256.f;
}
inline int8_t tos(uint8_t v) {
return v - 128;
}
// Values considered negative have a sign bit of 1
inline uint8_t sign(int8_t v) {
return (v >> 7) & 1;
}
//
// 6-------7
// /| /|
// / | / | Corners
// 4-------5 |
// | 2----|--3
// | / | / z y
// |/ |/ |/
// 0-------1 o--x
//
const Vector3i g_corner_dirs[8] = {
Vector3i(0, 0, 0),
Vector3i(1, 0, 0),
Vector3i(0, 1, 0),
Vector3i(1, 1, 0),
Vector3i(0, 0, 1),
Vector3i(1, 0, 1),
Vector3i(0, 1, 1),
Vector3i(1, 1, 1)
};
inline Vector3i dir_to_prev_vec(uint8_t dir) {
//return g_corner_dirs[mask] - Vector3(1,1,1);
return Vector3i(
-(dir & 1),
-((dir >> 1) & 1),
-((dir >> 2) & 1)
);
}
template<typename T>
void copy_to(PoolVector<T> & to, Vector<T> & from) {
to.resize(from.size());
PoolVector<T>::Write w = to.write();
for(unsigned int i = 0; i < from.size(); ++i) {
w[i] = from[i];
}
}
VoxelMesherSmooth::ReuseCell::ReuseCell() {
case_index = 0;
for(unsigned int i = 0; i < 4; ++i) {
vertices[i] = -1;
}
}
VoxelMesherSmooth::VoxelMesherSmooth() {
}
Ref<Mesh> VoxelMesherSmooth::build_ref(Ref<VoxelBuffer> voxels_ref, unsigned int channel, Ref<Mesh> mesh) {
ERR_FAIL_COND_V(voxels_ref.is_null(), Ref<Mesh>());
VoxelBuffer & voxels = **voxels_ref;
return build(voxels, channel, mesh);
}
Ref<Mesh> VoxelMesherSmooth::build(const VoxelBuffer & voxels, unsigned int channel, Ref<Mesh> mesh) {
ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Ref<Mesh>());
// Initialize dynamic memory:
// These vectors are re-used.
// We don't know in advance how much geometry we are going to produce.
// Once capacity is big enough, no more memory should be allocated
m_output_vertices.clear();
//m_output_vertices_secondary.clear();
m_output_normals.clear();
m_output_indices.clear();
build_mesh(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(m_output_vertices.size() == 0) {
// The mesh can be empty
return Ref<Mesh>();
}
PoolVector<Vector3> vertices;
PoolVector<Vector3> normals;
PoolVector<int> indices;
copy_to(vertices, m_output_vertices);
copy_to(normals, m_output_normals);
copy_to(indices, m_output_indices);
Array arrays;
arrays.resize(Mesh::ARRAY_MAX);
arrays[Mesh::ARRAY_VERTEX] = vertices;
if(m_output_normals.size() != 0) {
arrays[Mesh::ARRAY_NORMAL] = normals;
}
arrays[Mesh::ARRAY_INDEX] = indices;
if(mesh.is_null())
mesh = Ref<Mesh>(memnew(Mesh));
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
return mesh;
}
void VoxelMesherSmooth::build_mesh(const VoxelBuffer & voxels, unsigned int channel) {
// Each 2x2 voxel group is a "cell"
const Vector3i block_size = voxels.get_size();
// TODO No lod yet, but it's planned
const int lod_index = 0;
const int lod_scale = 1 << lod_index;
// Prepare vertex reuse cache
m_block_size = block_size;
unsigned int deck_area = block_size.x * block_size.y;
for(int i = 0; i < 2; ++i) {
if(m_cache[i].size() != deck_area) {
m_cache[i].clear(); // Clear any previous data
m_cache[i].resize(deck_area);
}
}
// Iterate all cells with padding (expected to be neighbors)
Vector3i pos;
for(pos.z = PAD.z; pos.z < block_size.z - 2; ++pos.z) {
for(pos.y = PAD.y; pos.y < block_size.y - 2; ++pos.y) {
for(pos.x = PAD.x; pos.x < block_size.x - 2; ++pos.x) {
// Get the value of cells.
// Negative values are "solid" and positive are "air".
// Due to raw cells being unsigned 8-bit, they get converted to signed.
int8_t cell_samples[8] = {
tos(voxels.get_voxel( pos.x, pos.y, pos.z, channel )),
tos(voxels.get_voxel( pos.x+1, pos.y, pos.z, channel )),
tos(voxels.get_voxel( pos.x, pos.y+1, pos.z, channel )),
tos(voxels.get_voxel( pos.x+1, pos.y+1, pos.z, channel )),
tos(voxels.get_voxel( pos.x, pos.y, pos.z+1, channel )),
tos(voxels.get_voxel( pos.x+1, pos.y, pos.z+1, channel )),
tos(voxels.get_voxel( pos.x, pos.y+1, pos.z+1, channel )),
tos(voxels.get_voxel( pos.x+1, pos.y+1, pos.z+1, channel ))
};
// Concatenate the sign of cell values to obtain the case code.
// Index 0 is the less significant bit, and index 7 is the most significant bit.
uint8_t case_code =
sign(cell_samples[0])
| (sign(cell_samples[1]) << 1)
| (sign(cell_samples[2]) << 2)
| (sign(cell_samples[3]) << 3)
| (sign(cell_samples[4]) << 4)
| (sign(cell_samples[5]) << 5)
| (sign(cell_samples[6]) << 6)
| (sign(cell_samples[7]) << 7);
{
ReuseCell & rc = get_reuse_cell(pos);
rc.case_index = case_code;
}
if(case_code == 0 || case_code == 255) {
// If the case_code is 0 or 255, there is no triangulation to do
continue;
}
// TODO We might not always need all of them
// Compute normals
Vector3 corner_normals[8];
for(unsigned int i = 0; i < 8; ++i) {
Vector3i p = pos + g_corner_dirs[i];
float nx = tof(tos(voxels.get_voxel(p - Vector3i(1,0,0), channel)))
- tof(tos(voxels.get_voxel(p + Vector3i(1,0,0), channel)));
float ny = tof(tos(voxels.get_voxel(p - Vector3i(0,1,0), channel)))
- tof(tos(voxels.get_voxel(p + Vector3i(0,1,0), channel)));
float nz = tof(tos(voxels.get_voxel(p - Vector3i(0,0,1), channel)))
- tof(tos(voxels.get_voxel(p + Vector3i(0,0,1), channel)));
corner_normals[i] = Vector3(nx, ny, nz);
corner_normals[i].normalize();
}
// For cells occurring along the minimal boundaries of a block,
// the preceding cells needed for vertex reuse may not exist.
// In these cases, we allow new vertex creation on additional edges of a cell.
// While iterating through the cells in a block, a 3-bit mask is maintained whose bits indicate
// whether corresponding bits in a direction code are valid
uint8_t direction_validity_mask =
(pos.x > 1 ? 1 : 0)
| ((pos.y > 1 ? 1 : 0) << 1)
| ((pos.z > 1 ? 1 : 0) << 2);
uint8_t regular_cell_class_index = Transvoxel::regularCellClass[case_code];
Transvoxel::RegularCellData regular_cell_class = Transvoxel::regularCellData[regular_cell_class_index];
uint8_t triangle_count = regular_cell_class.geometryCounts & 0x0f;
uint8_t vertex_count = (regular_cell_class.geometryCounts & 0xf0) >> 4;
int cell_mesh_indices[12];
// For each vertex in the case
for(unsigned int i = 0; i < vertex_count; ++i) {
// The case index maps to a list of 16-bit codes providing information about the edges on which the vertices lie.
// The low byte of each 16-bit code contains the corner indexes of the edges endpoints in one nibble each,
// and the high byte contains the mapping code shown in Figure 3.8(b)
unsigned short rvd = Transvoxel::regularVertexData[case_code][i];
unsigned short edge_code_low = rvd & 0xff;
unsigned short edge_code_high = (rvd >> 8) & 0xff;
// Get corner indexes in the low nibble (always ordered so the higher comes last)
uint8_t v0 = (edge_code_low >> 4) & 0xf;
uint8_t v1 = edge_code_low & 0xf;
ERR_FAIL_COND(v1 <= v0);
// Get voxel values at the corners
int sample0 = cell_samples[v0]; // called d0 in the paper
int sample1 = cell_samples[v1]; // called d1 in the paper
// TODO Zero-division is not mentionned in the paper??
ERR_FAIL_COND(sample1 == sample0);
ERR_FAIL_COND(sample1 == 0 && sample0 == 0);
// Get interpolation position
// We use an 8-bit fraction, allowing the new vertex to be located at one of 257 possible
// positions along the edge when both endpoints are included.
int t = (sample1 << 8) / (sample1 - sample0);
float t0 = static_cast<float>(t) / 256.f;
float t1 = static_cast<float>(0x0100 - t) / 256.f;
Vector3i p0 = pos + g_corner_dirs[v0];
Vector3i p1 = pos + g_corner_dirs[v1];
if(t & 0xff) {
//OS::get_singleton()->print("A");
// Vertex lies in the interior of the edge.
// Each edge of a cell is assigned an 8-bit code, as shown in Figure 3.8(b),
// that provides a mapping to a preceding cell and the coincident edge on that preceding cell
// for which new vertex creation was allowed.
// The high nibble of this code indicates which direction to go in order to reach the correct preceding cell.
// The bit values 1, 2, and 4 in this nibble indicate that we must subtract one
// from the x, y, and/or z coordinate, respectively.
uint8_t reuse_dir = (edge_code_high >> 4) & 0xf;
uint8_t reuse_vertex_index = edge_code_high & 0xf;
bool can_reuse = (reuse_dir & direction_validity_mask) == reuse_dir;
if(can_reuse) {
Vector3i cache_pos = pos + dir_to_prev_vec(reuse_dir);
ReuseCell & prev_cell = get_reuse_cell(cache_pos);
if(prev_cell.case_index == 0 || prev_cell.case_index == 255) {
// TODO I don't think this can happen for non-corner vertices.
cell_mesh_indices[i] = -1;
} else {
// Will reuse a previous vertice
cell_mesh_indices[i] = prev_cell.vertices[reuse_vertex_index];
}
}
if(!can_reuse || cell_mesh_indices[i] == -1) {
// Going to create a new vertice
cell_mesh_indices[i] = m_output_vertices.size();
Vector3 pi = p0.to_vec3() * t0 + p1.to_vec3() * t1;
Vector3 primary = pi;//pos.to_vec3() + pi;
Vector3 normal = corner_normals[v0] * t0 + corner_normals[v1] * t1;
emit_vertex(primary, normal);
if (reuse_dir & 8) {
// Store the generated vertex so that other cells can reuse it.
ReuseCell & rc = get_reuse_cell(pos);
rc.vertices[reuse_vertex_index] = cell_mesh_indices[i];
}
}
} else if(t == 0 && v1 == 7) {
//OS::get_singleton()->print("B");
// This cell owns the vertex, so it should be created.
cell_mesh_indices[i] = m_output_vertices.size();
Vector3 pi = p0.to_vec3() * t0 + p1.to_vec3() * t1;
Vector3 primary = pi;//pos.to_vec3() + pi;
Vector3 normal = corner_normals[v0] * t0 + corner_normals[v1] * t1;
emit_vertex(primary, normal);
ReuseCell & rc = get_reuse_cell(pos);
rc.vertices[0] = cell_mesh_indices[i];
} else {
// Always try to reuse previous vertices in these cases
//OS::get_singleton()->print("C");
// A 3-bit direction code leading to the proper cell can easily be obtained by
// inverting the 3-bit corner index (bitwise, by exclusive ORing with the number 7).
// The corner index depends on the value of t, t = 0 means that we're at the higher
// numbered endpoint.
uint8_t reuse_dir = (t == 0 ? v1 ^ 7 : v0 ^ 7);
bool can_reuse = (reuse_dir & direction_validity_mask) == reuse_dir;
// Note: the only difference with similar code above is that we take vertice 0 in the `else`
if(can_reuse) {
Vector3i cache_pos = pos + dir_to_prev_vec(reuse_dir);
ReuseCell prev_cell = get_reuse_cell(cache_pos);
// The previous cell might not have any geometry, and we
// might therefore have to create a new vertex anyway.
if (prev_cell.case_index == 0 || prev_cell.case_index == 255) {
cell_mesh_indices[i] = -1;
} else {
cell_mesh_indices[i] = prev_cell.vertices[0];
}
}
if(!can_reuse || cell_mesh_indices[i] < 0) {
cell_mesh_indices[i] = m_output_vertices.size();
Vector3 pi = p0.to_vec3() * t0 + p1.to_vec3() * t1;
Vector3 primary = pi;//pos.to_vec3() + pi;
Vector3 normal = corner_normals[v0] * t0 + corner_normals[v1] * t1;
emit_vertex(primary, normal);
}
}
} // for each cell vertice
//OS::get_singleton()->print("_");
for (int t = 0; t < triangle_count; ++t) {
for (int i = 0; i < 3; ++i) {
int index = cell_mesh_indices[regular_cell_class.vertexIndex[t * 3 + i]];
m_output_indices.push_back(index);
}
}
} // x
} // y
} // z
//OS::get_singleton()->print("\n");
}
VoxelMesherSmooth::ReuseCell & VoxelMesherSmooth::get_reuse_cell(Vector3i pos) {
int j = pos.z & 1;
int i = pos.y * m_block_size.y + pos.x;
return m_cache[j][i];
}
void VoxelMesherSmooth::emit_vertex(Vector3 primary, Vector3 normal) {
m_output_vertices.push_back(primary - PAD.to_vec3());
m_output_normals.push_back(normal);
}
void VoxelMesherSmooth::_bind_methods() {
ClassDB::bind_method(
D_METHOD("build:Mesh", "voxels:VoxelBuffer", "channel", "existing_mesh:Mesh"),
&VoxelMesherSmooth::build_ref, DEFVAL(Variant()));
}

42
voxel_mesher_smooth.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef VOXEL_MESHER_SMOOTH_H
#define VOXEL_MESHER_SMOOTH_H
#include "voxel_buffer.h"
#include <scene/resources/mesh.h>
class VoxelMesherSmooth : public Reference {
GDCLASS(VoxelMesherSmooth, Reference)
public:
VoxelMesherSmooth();
Ref<Mesh> build_ref(Ref<VoxelBuffer> voxels_ref, unsigned int channel, Ref<Mesh> mesh=Ref<Mesh>());
Ref<Mesh> build(const VoxelBuffer & voxels, unsigned int channel, Ref<Mesh> mesh=Ref<Mesh>());
protected:
static void _bind_methods();
private:
struct ReuseCell {
int vertices[4];
int case_index;
ReuseCell();
};
void build_mesh(const VoxelBuffer & voxels, unsigned int channel);
ReuseCell & get_reuse_cell(Vector3i pos);
void emit_vertex(Vector3 primary, Vector3 normal);
private:
const Vector3i PAD = Vector3i(1,1,1);
Vector<ReuseCell> m_cache[2];
Vector3i m_block_size;
Vector<Vector3> m_output_vertices;
//Vector<Vector3> m_output_vertices_secondary;
Vector<Vector3> m_output_normals;
Vector<int> m_output_indices;
};
#endif // VOXEL_MESHER_SMOOTH_H

View File

@ -3,10 +3,11 @@
#include <os/os.h>
#include "voxel_raycast.h"
VoxelTerrain::VoxelTerrain(): Node(), _min_y(-4), _max_y(4), _generate_collisions(true) {
VoxelTerrain::VoxelTerrain(): Node(), _generate_collisions(true) {
_map = Ref<VoxelMap>(memnew(VoxelMap));
_mesher = Ref<VoxelMesher>(memnew(VoxelMesher));
_mesher_smooth = Ref<VoxelMesherSmooth>(memnew(VoxelMesherSmooth));
}
Vector3i g_viewer_block_pos; // TODO UGLY! Lambdas or pointers needed...
@ -232,9 +233,6 @@ void VoxelTerrain::_process() {
void VoxelTerrain::update_blocks() {
OS & os = *OS::get_singleton();
uint32_t time_before = os.get_ticks_msec();
uint32_t max_time = 1000 / 60;
// Get viewer location
Spatial * viewer = get_viewer(_viewer_path);
if(viewer)
@ -247,6 +245,9 @@ void VoxelTerrain::update_blocks() {
_block_update_queue.sort_custom<BlockUpdateComparator>();
VOXEL_PROFILE_END("block_update_sorting")
uint32_t time_before = os.get_ticks_msec();
uint32_t max_time = 1000 / 60;
// Update a bunch of blocks until none are left or too much time elapsed
while (!_block_update_queue.empty() && (os.get_ticks_msec() - time_before) < max_time) {
@ -317,84 +318,100 @@ void VoxelTerrain::update_blocks() {
}
}
static inline bool is_mesh_empty(Ref<Mesh> mesh_ref) {
if(mesh_ref.is_null())
return true;
Mesh & mesh = **mesh_ref;
if(mesh.get_surface_count() == 0)
return true;
if(mesh.surface_get_array_len(Mesh::ARRAY_VERTEX) == 0)
return true;
return false;
}
void VoxelTerrain::update_block_mesh(Vector3i block_pos) {
VoxelBlock * block = _map->get_block(block_pos);
if (block == NULL) {
return;
}
if (block->voxels->is_uniform(0) && block->voxels->get_voxel(0, 0, 0, 0) == 0) {
// Optimization: the block contains nothing
MeshInstance * mesh_instance = block->get_mesh_instance(*this);
if(mesh_instance) {
mesh_instance->set_mesh(Ref<Mesh>());
}
StaticBody * body = block->get_physics_body(*this);
if(body) {
body->set_shape(0, Ref<Mesh>());
}
return;
}
VOXEL_PROFILE_BEGIN("voxel_buffer_creation_extract")
// Create buffer padded with neighbor voxels
VoxelBuffer nbuffer;
nbuffer.create(VoxelBlock::SIZE + 2, VoxelBlock::SIZE + 2, VoxelBlock::SIZE + 2);
// TODO Make the buffer re-usable
// TODO Padding set to 3 at the moment because Transvoxel works on 2x2 cells.
// It should change for a smarter padding (if smooth isn't used for example).
nbuffer.create(VoxelBlock::SIZE + 3, VoxelBlock::SIZE + 3, VoxelBlock::SIZE + 3);
VOXEL_PROFILE_END("voxel_buffer_creation_extract")
VOXEL_PROFILE_BEGIN("block_extraction")
_map->get_buffer_copy(VoxelMap::block_to_voxel(block_pos) - Vector3i(1, 1, 1), nbuffer);
_map->get_buffer_copy(VoxelMap::block_to_voxel(block_pos) - Vector3i(1, 1, 1), nbuffer, 0x3);
VOXEL_PROFILE_END("block_extraction")
Vector3 block_node_pos = VoxelMap::block_to_voxel(block_pos).to_vec3();
// Build mesh (that part is the most CPU-intensive)
// TODO Re-use existing meshes to optimize memory cost
//VOXEL_PROFILE_BEGIN("meshing")
Ref<Mesh> mesh = _mesher->build(nbuffer);
//VOXEL_PROFILE_END("meshing")
// TODO Re-use existing meshes to optimize memory cost
// Build cubic parts of the mesh
Ref<Mesh> mesh = _mesher->build(nbuffer, Voxel::CHANNEL_TYPE, Vector3i(0,0,0), nbuffer.get_size()-Vector3(1,1,1));
// Build smooth parts of the mesh
_mesher_smooth->build(nbuffer, Voxel::CHANNEL_ISOLEVEL, mesh);
// TODO Don't use nodes! Use servers directly, it's faster
MeshInstance * mesh_instance = block->get_mesh_instance(*this);
if (mesh_instance == NULL) {
// Create and spawn mesh
mesh_instance = memnew(MeshInstance);
mesh_instance->set_mesh(mesh);
mesh_instance->set_translation(block_node_pos);
add_child(mesh_instance);
block->mesh_instance_path = mesh_instance->get_path();
StaticBody * body = block->get_physics_body(*this);
if(is_mesh_empty(mesh)) {
if(mesh_instance) {
mesh_instance->set_mesh(Ref<Mesh>());
}
if(body) {
body->set_shape(0, Ref<Shape>());
}
}
else {
// Update mesh
VOXEL_PROFILE_BEGIN("mesh_instance_set_mesh")
mesh_instance->set_mesh(mesh);
VOXEL_PROFILE_END("mesh_instance_set_mesh")
}
// The mesh exist and it has vertices
if(get_tree()->is_editor_hint() == false && _generate_collisions) {
// Generate collisions
// TODO Need to select only specific surfaces because some may not have collisions
VOXEL_PROFILE_BEGIN("create_trimesh_shape")
Ref<Shape> shape = mesh->create_trimesh_shape();
VOXEL_PROFILE_END("create_trimesh_shape")
StaticBody * body = block->get_physics_body(*this);
if(body == NULL) {
// Create body
body = memnew(StaticBody);
body->set_translation(block_node_pos);
body->add_shape(shape);
add_child(body);
block->body_path = body->get_path();
// TODO Don't use nodes! Use servers directly, it's faster
if (mesh_instance == NULL) {
// Create and spawn mesh
mesh_instance = memnew(MeshInstance);
mesh_instance->set_mesh(mesh);
mesh_instance->set_translation(block_node_pos);
add_child(mesh_instance);
block->mesh_instance_path = mesh_instance->get_path();
}
else {
// Update body
VOXEL_PROFILE_BEGIN("body_set_shape")
body->set_shape(0, shape);
VOXEL_PROFILE_END("body_set_shape")
// Update mesh
VOXEL_PROFILE_BEGIN("mesh_instance_set_mesh")
mesh_instance->set_mesh(mesh);
VOXEL_PROFILE_END("mesh_instance_set_mesh")
}
if(get_tree()->is_editor_hint() == false && _generate_collisions) {
// Generate collisions
// TODO Need to select only specific surfaces because some may not have collisions
VOXEL_PROFILE_BEGIN("create_trimesh_shape")
Ref<Shape> shape = mesh->create_trimesh_shape();
VOXEL_PROFILE_END("create_trimesh_shape")
if(body == NULL) {
// Create body
body = memnew(StaticBody);
body->set_translation(block_node_pos);
body->add_shape(shape);
add_child(body);
block->body_path = body->get_path();
}
else {
// Update body
VOXEL_PROFILE_BEGIN("body_set_shape")
body->set_shape(0, shape);
VOXEL_PROFILE_END("body_set_shape")
}
}
}
}

View File

@ -66,10 +66,6 @@ private:
int get_voxel(Vector3 pos, int c);
private:
// Parameters
int _min_y; // In blocks, not voxels
int _max_y;
// Voxel storage
Ref<VoxelMap> _map;
@ -80,6 +76,9 @@ private:
HashMap<Vector3i, bool, Vector3iHasher> _dirty_blocks; // only the key is relevant
Ref<VoxelMesher> _mesher;
// TODO I'm not sure it will stay here... refactoring ahead
Ref<VoxelMesherSmooth> _mesher_smooth;
Ref<VoxelProvider> _provider;
NodePath _viewer_path;