mirror of
https://github.com/Relintai/godot_voxel.git
synced 2024-11-11 20:35:08 +01:00
Added smooth voxels (untextured at the moment)
This commit is contained in:
parent
2e89ab0294
commit
a5a1ded2f3
@ -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
1035
transvoxel_tables.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
12
voxel.h
@ -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
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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 {
|
||||
|
@ -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,6 +178,12 @@ 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));
|
||||
|
||||
for(unsigned int channel = 0; channel < VoxelBuffer::MAX_CHANNELS; ++channel) {
|
||||
|
||||
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) {
|
||||
@ -204,6 +209,8 @@ void VoxelMap::get_buffer_copy(Vector3i min_pos, VoxelBuffer & dst_buffer, unsig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelMap::remove_blocks_not_in_area(Vector3i min, Vector3i max) {
|
||||
|
@ -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);
|
||||
|
@ -1,6 +1,6 @@
|
||||
#include "voxel_mesher.h"
|
||||
#include "voxel_library.h"
|
||||
|
||||
#include "voxel_mesher_smooth.h"
|
||||
|
||||
// The following tables respect the following conventions
|
||||
//
|
||||
@ -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);
|
||||
@ -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);
|
||||
|
@ -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
401
voxel_mesher_smooth.cpp
Normal 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 edge’s 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
42
voxel_mesher_smooth.h
Normal 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
|
@ -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,47 +318,63 @@ 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")
|
||||
|
||||
// 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);
|
||||
|
||||
MeshInstance * mesh_instance = block->get_mesh_instance(*this);
|
||||
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 {
|
||||
// The mesh exist and it has vertices
|
||||
|
||||
// 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);
|
||||
@ -381,7 +398,6 @@ void VoxelTerrain::update_block_mesh(Vector3i block_pos) {
|
||||
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);
|
||||
@ -397,6 +413,7 @@ void VoxelTerrain::update_block_mesh(Vector3i block_pos) {
|
||||
VOXEL_PROFILE_END("body_set_shape")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//void VoxelTerrain::block_removed(VoxelBlock & block) {
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user