diff --git a/vector3i.h b/vector3i.h index 9eaf1c4..84fe49c 100644 --- a/vector3i.h +++ b/vector3i.h @@ -68,6 +68,10 @@ struct Vector3i { z -= other.z; } + _FORCE_INLINE_ Vector3i operator-() const { + return Vector3i(-x, -y, -z); + } + _FORCE_INLINE_ int &operator[](unsigned int i) { return coords[i]; } diff --git a/voxel.cpp b/voxel.cpp index ebbb905..45aff1d 100644 --- a/voxel.cpp +++ b/voxel.cpp @@ -2,15 +2,96 @@ #include "voxel_library.h" #include "voxel_mesher.h" +#define STRLEN(x) (sizeof(x) / sizeof(x[0])) + Voxel::Voxel() - : Reference(), + : Resource(), _id(-1), _material_id(0), _is_transparent(false), - _library(NULL), - _color(1.f, 1.f, 1.f) {} + _color(1.f, 1.f, 1.f), + _geometry_type(GEOMETRY_NONE), + _cube_geometry_padding_y(0) {} -Ref Voxel::set_name(String name) { +static Voxel::Side name_to_side(const String &s) { + if (s == "left") + return Voxel::SIDE_LEFT; + if (s == "right") + return Voxel::SIDE_RIGHT; + if (s == "top") + return Voxel::SIDE_TOP; + if (s == "bottom") + return Voxel::SIDE_BOTTOM; + if (s == "front") + return Voxel::SIDE_FRONT; + if (s == "back") + return Voxel::SIDE_BACK; + return Voxel::SIDE_COUNT; // Invalid +} + +bool Voxel::_set(const StringName &p_name, const Variant &p_value) { + + String name = p_name; + + // TODO Eventualy these could be Rect2 for maximum flexibility? + if (name.begins_with("cube_tiles/")) { + + String s = name.substr(STRLEN("cube_tiles/"), name.length()); + Voxel::Side side = name_to_side(s); + if (side != Voxel::SIDE_COUNT) { + Vector2 v = p_value; + set_cube_uv_side(side, v); + return true; + } + + } else if (name == "cube_geometry/padding_y") { + + _cube_geometry_padding_y = p_value; + set_cube_geometry(_cube_geometry_padding_y); + return true; + } + + return false; +} + +bool Voxel::_get(const StringName &p_name, Variant &r_ret) const { + + String name = p_name; + + if (name.begins_with("cube_tiles/")) { + + String s = name.substr(STRLEN("cube_tiles/"), name.length()); + Voxel::Side side = name_to_side(s); + if (side != Voxel::SIDE_COUNT) { + r_ret = _cube_tiles[side]; + return true; + } + + } else if (name == "cube_geometry/padding_y") { + + r_ret = _cube_geometry_padding_y; + return true; + } + + return false; +} + +void Voxel::_get_property_list(List *p_list) const { + + if (_geometry_type == GEOMETRY_CUBE) { + + p_list->push_back(PropertyInfo(Variant::REAL, "cube_geometry/padding_y")); + + p_list->push_back(PropertyInfo(Variant::VECTOR2, "cube_tiles/left")); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "cube_tiles/right")); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "cube_tiles/bottom")); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "cube_tiles/top")); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "cube_tiles/back")); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "cube_tiles/front")); + } +} + +Ref Voxel::set_voxel_name(String name) { _name = name; return Ref(this); } @@ -39,7 +120,53 @@ Ref Voxel::set_transparent(bool t) { return Ref(this); } +void Voxel::set_geometry_type(GeometryType type) { + + _geometry_type = type; + + switch (_geometry_type) { + + case GEOMETRY_NONE: { + // Clear all geometry + _model_vertices.resize(0); + _model_normals.resize(0); + _model_uv.resize(0); + for (int side = 0; side < SIDE_COUNT; ++side) { + _model_side_vertices[side].resize(0); + _model_side_uv[side].resize(0); + } + } break; + + case GEOMETRY_CUBE: + set_cube_geometry(_cube_geometry_padding_y); + update_cube_uv_sides(); + break; + + default: + print_line("Wtf? Unknown geometry type"); + break; + } +} + +Voxel::GeometryType Voxel::get_geometry_type() const { + return _geometry_type; +} + +void Voxel::set_library(Ref lib) { + _library.set_ref(lib); + // Update model UVs because atlas size is defined by the library + update_cube_uv_sides(); +} + +VoxelLibrary *Voxel::get_library() const { + Object *v = _library.get_ref(); + if (v) + return v->cast_to(); + return NULL; +} + Ref Voxel::set_cube_geometry(float sy) { + sy = 1.0 + sy; const Vector3 vertices[SIDE_COUNT][6] = { { // LEFT Vector3(0, 0, 0), @@ -96,8 +223,20 @@ Ref Voxel::set_cube_geometry(float sy) { return Ref(this); } -Ref Voxel::_set_cube_uv_sides(const Vector2 atlas_pos[6]) { - ERR_FAIL_COND_V(_library == NULL, Ref()); +void Voxel::set_cube_uv_side(int side, Vector2 tile_pos) { + _cube_tiles[side] = tile_pos; + // TODO Better have a dirty flag, otherwise UVs will be needlessly updated at least 6 times everytime a Voxel resource is loaded! + update_cube_uv_sides(); +} + +void Voxel::update_cube_uv_sides() { + VoxelLibrary *library = get_library(); + //ERR_FAIL_COND(library == NULL); + if(library == NULL) { + // Not an error, the Voxel might have been created before the library, and can't be used without anyways + print_line("VoxelLibrary not set yet"); + return; + } float e = 0.001; const Vector2 uv[4] = { @@ -107,6 +246,8 @@ Ref Voxel::_set_cube_uv_sides(const Vector2 atlas_pos[6]) { Vector2(1.f - e, 1.f - e), }; + // TODO The only reason why there are 6 entries per array is because SurfaceTool is used to access them. + // in the near future, there should be only 4, to account for one quad! const int uv6[SIDE_COUNT][6] = { // LEFT { 2, 0, 1, 2, 1, 3 }, @@ -122,41 +263,15 @@ Ref Voxel::_set_cube_uv_sides(const Vector2 atlas_pos[6]) { { 3, 2, 1, 2, 0, 1 } }; - float s = 1.0 / (float)_library->get_atlas_size(); + float s = 1.0 / (float)library->get_atlas_size(); for (unsigned int side = 0; side < SIDE_COUNT; ++side) { _model_side_uv[side].resize(6); PoolVector::Write w = _model_side_uv[side].write(); for (unsigned int i = 0; i < 6; ++i) { - w[i] = (atlas_pos[side] + uv[uv6[side][i]]) * s; + w[i] = (_cube_tiles[side] + uv[uv6[side][i]]) * s; } } - - return Ref(this); -} - -Ref Voxel::set_cube_uv_all_sides(Vector2 atlas_pos) { - const Vector2 positions[6] = { - atlas_pos, - atlas_pos, - atlas_pos, - atlas_pos, - atlas_pos, - atlas_pos - }; - return _set_cube_uv_sides(positions); -} - -Ref Voxel::set_cube_uv_tbs_sides(Vector2 top_atlas_pos, Vector2 side_atlas_pos, Vector2 bottom_atlas_pos) { - const Vector2 positions[6] = { - side_atlas_pos, - side_atlas_pos, - bottom_atlas_pos, - top_atlas_pos, - side_atlas_pos, - side_atlas_pos, - }; - return _set_cube_uv_sides(positions); } //Ref Voxel::set_xquad_geometry(Vector2 atlas_pos) { @@ -166,8 +281,8 @@ Ref Voxel::set_cube_uv_tbs_sides(Vector2 top_atlas_pos, Vector2 side_atla void Voxel::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_name", "name"), &Voxel::set_name); - ClassDB::bind_method(D_METHOD("get_name"), &Voxel::get_name); + ClassDB::bind_method(D_METHOD("set_voxel_name", "name"), &Voxel::set_voxel_name); + ClassDB::bind_method(D_METHOD("get_voxel_name"), &Voxel::get_voxel_name); ClassDB::bind_method(D_METHOD("set_id", "id"), &Voxel::set_id); ClassDB::bind_method(D_METHOD("get_id"), &Voxel::get_id); @@ -175,15 +290,24 @@ void Voxel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_color", "color"), &Voxel::set_color); ClassDB::bind_method(D_METHOD("get_color"), &Voxel::get_color); - ClassDB::bind_method(D_METHOD("set_transparent", "color"), &Voxel::set_transparent, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("set_transparent", "transparent"), &Voxel::set_transparent, DEFVAL(true)); ClassDB::bind_method(D_METHOD("is_transparent"), &Voxel::is_transparent); ClassDB::bind_method(D_METHOD("set_material_id", "id"), &Voxel::set_material_id); ClassDB::bind_method(D_METHOD("get_material_id"), &Voxel::get_material_id); - ClassDB::bind_method(D_METHOD("set_cube_geometry", "height"), &Voxel::set_cube_geometry, DEFVAL(1.f)); - ClassDB::bind_method(D_METHOD("set_cube_uv_all_sides", "atlas_pos"), &Voxel::set_cube_uv_all_sides); - ClassDB::bind_method(D_METHOD("set_cube_uv_tbs_sides", "top_atlas_pos", "side_atlas_pos", "bottom_atlas_pos"), &Voxel::set_cube_uv_tbs_sides); + ClassDB::bind_method(D_METHOD("set_geometry_type", "type"), &Voxel::set_geometry_type); + ClassDB::bind_method(D_METHOD("get_geometry_type"), &Voxel::get_geometry_type); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "voxel_name"), "set_name", "get_name"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "transparent"), "set_transparent", "is_transparent"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "material_id"), "set_material_id", "get_material_id"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry_type", PROPERTY_HINT_ENUM, "None,Cube"), "set_geometry_type", "get_geometry_type"); + + BIND_CONSTANT(GEOMETRY_NONE); + BIND_CONSTANT(GEOMETRY_CUBE); + BIND_CONSTANT(GEOMETRY_MAX); BIND_CONSTANT(CHANNEL_TYPE) BIND_CONSTANT(CHANNEL_ISOLEVEL) diff --git a/voxel.h b/voxel.h index 710b7bc..157677e 100644 --- a/voxel.h +++ b/voxel.h @@ -1,15 +1,15 @@ #ifndef VOXEL_TYPE_H #define VOXEL_TYPE_H -#include +#include class VoxelLibrary; // Definition of one type of voxel. // A voxel can be a simple coloured cube, or a more complex model. // Important: it is recommended that you create voxels from a library rather than using new(). -class Voxel : public Reference { - GDCLASS(Voxel, Reference) +class Voxel : public Resource { + GDCLASS(Voxel, Resource) public: enum Side { @@ -36,8 +36,8 @@ public: // Properties - Ref set_name(String name); - _FORCE_INLINE_ String get_name() const { return _name; } + Ref set_voxel_name(String name); + _FORCE_INLINE_ String get_voxel_name() const { return _name; } Ref set_id(int id); _FORCE_INLINE_ int get_id() const { return _id; } @@ -51,12 +51,17 @@ public: Ref set_transparent(bool t = true); _FORCE_INLINE_ bool is_transparent() const { return _is_transparent; } + //------------------------------------------- // Built-in geometry generators - Ref set_cube_geometry(float sy = 1); - Ref set_cube_uv_all_sides(Vector2 atlas_pos); - Ref set_cube_uv_tbs_sides(Vector2 top_atlas_pos, Vector2 side_atlas_pos, Vector2 bottom_atlas_pos); - //Ref set_xquad_geometry(Vector2 atlas_pos); + enum GeometryType { + GEOMETRY_NONE = 0, + GEOMETRY_CUBE = 1, + GEOMETRY_MAX + }; + + void set_geometry_type(GeometryType type); + GeometryType get_geometry_type() const; // Getters for native usage only @@ -66,15 +71,25 @@ public: const PoolVector &get_model_side_vertices(unsigned int side) const { return _model_side_vertices[side]; } const PoolVector &get_model_side_uv(unsigned int side) const { return _model_side_uv[side]; } - void set_library_ptr(VoxelLibrary *lib) { _library = lib; } + void set_library(Ref lib); protected: - Ref _set_cube_uv_sides(const Vector2 atlas_pos[6]); + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + + void set_cube_uv_side(int side, Vector2 tile_pos); + void update_cube_uv_sides(); + + VoxelLibrary *get_library() const; static void _bind_methods(); + Ref set_cube_geometry(float sy = 1); + //Ref set_xquad_geometry(Vector2 atlas_pos); + private: - VoxelLibrary *_library; + WeakRef _library; // Identifiers int _id; @@ -83,9 +98,12 @@ private: // Properties int _material_id; bool _is_transparent; + Color _color; + GeometryType _geometry_type; + float _cube_geometry_padding_y; + Vector2 _cube_tiles[SIDE_COUNT]; // Model - Color _color; PoolVector _model_vertices; PoolVector _model_normals; PoolVector _model_uv; @@ -96,5 +114,6 @@ private: }; VARIANT_ENUM_CAST(Voxel::ChannelMode) +VARIANT_ENUM_CAST(Voxel::GeometryType) #endif // VOXEL_TYPE_H diff --git a/voxel_library.cpp b/voxel_library.cpp index e0afb5b..fdb4849 100644 --- a/voxel_library.cpp +++ b/voxel_library.cpp @@ -1,18 +1,96 @@ #include "voxel_library.h" -VoxelLibrary::VoxelLibrary() - : Reference(), _atlas_size(1) { - // Defaults - create_voxel(0, "air")->set_transparent(true); - create_voxel(1, "solid")->set_transparent(false)->set_cube_geometry(); +VoxelLibrary::VoxelLibrary() : + Resource(), _atlas_size(1) { } VoxelLibrary::~VoxelLibrary() { - for (unsigned int i = 0; i < MAX_VOXEL_TYPES; ++i) { - if (_voxel_types[i].is_valid()) { - _voxel_types[i]->set_library_ptr(NULL); + // Handled with a WeakRef + // for (unsigned int i = 0; i < MAX_VOXEL_TYPES; ++i) { + // if (_voxel_types[i].is_valid()) { + // _voxel_types[i]->set_library(NULL); + // } + // } +} + +int VoxelLibrary::get_voxel_count() const { + int count = 0; + for(int i = 0; i < MAX_VOXEL_TYPES; ++i) { + if(_voxel_types[i].is_valid()) + ++count; + } + return count; +} + +void VoxelLibrary::load_default() { + create_voxel(0, "air")->set_transparent(true); + create_voxel(1, "solid") + ->set_transparent(false) + ->set_geometry_type(Voxel::GEOMETRY_CUBE); + _max_count = 2; +} + +// TODO Add a way to add voxels + +bool VoxelLibrary::_set(const StringName &p_name, const Variant &p_value) { + +// if(p_name == "voxels/max") { + +// int v = p_value; +// _max_count = CLAMP(v, 0, MAX_VOXEL_TYPES); +// for(int i = _max_count; i < MAX_VOXEL_TYPES; ++i) { +// _voxel_types[i] = Ref(); +// return true; +// } + +// } else + if (p_name.operator String().begins_with("voxels/")) { + + int idx = p_name.operator String().get_slicec('/', 1).to_int(); + if (idx >= 0 && idx < MAX_VOXEL_TYPES) { + Ref voxel = p_value; + _voxel_types[idx] = voxel; + if(voxel.is_valid()) { + voxel->set_library(Ref(this)); + } + // Note: if the voxel is set to null, we could set the previous one's library reference to null. + // however it Voxels use a weak reference, so it's not really needed + return true; } } + + return false; +} + +bool VoxelLibrary::_get(const StringName &p_name, Variant &r_ret) const { + +// if(p_name == "voxels/max") { + +// r_ret = _max_count; +// return true; + +// } else + if (p_name.operator String().begins_with("voxels/")) { + + int idx = p_name.operator String().get_slicec('/', 1).to_int(); + if (idx >= 0 && idx < MAX_VOXEL_TYPES) { + r_ret = _voxel_types[idx]; + return true; + } + } + + return false; +} + +void VoxelLibrary::_get_property_list(List *p_list) const { + + //p_list->push_back(PropertyInfo(Variant::INT, "voxels/max")); + + //for(int i = 0; i < _max_count; ++i) { + for(int i = 0; i < MAX_VOXEL_TYPES; ++i) { + p_list->push_back(PropertyInfo(Variant::OBJECT, "voxels/" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, "Voxel")); + } + } void VoxelLibrary::set_atlas_size(int s) { @@ -23,10 +101,13 @@ void VoxelLibrary::set_atlas_size(int s) { Ref VoxelLibrary::create_voxel(int id, String name) { ERR_FAIL_COND_V(id < 0 || id >= MAX_VOXEL_TYPES, Ref()); Ref voxel(memnew(Voxel)); - voxel->set_library_ptr(this); + voxel->set_library(Ref(this)); voxel->set_id(id); - voxel->set_name(name); + voxel->set_voxel_name(name); _voxel_types[id] = voxel; + if(id >= _max_count) { + _max_count = id + 1; + } return voxel; } @@ -41,4 +122,9 @@ void VoxelLibrary::_bind_methods() { ClassDB::bind_method(D_METHOD("get_voxel", "id"), &VoxelLibrary::_get_voxel_bind); ClassDB::bind_method(D_METHOD("set_atlas_size", "square_size"), &VoxelLibrary::set_atlas_size); + ClassDB::bind_method(D_METHOD("get_atlas_size"), &VoxelLibrary::get_atlas_size); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "atlas_size"), "set_atlas_size", "get_atlas_size"); } + + diff --git a/voxel_library.h b/voxel_library.h index 6e61719..0b02d51 100644 --- a/voxel_library.h +++ b/voxel_library.h @@ -2,10 +2,10 @@ #define VOXEL_LIBRARY_H #include "voxel.h" -#include +#include -class VoxelLibrary : public Reference { - GDCLASS(VoxelLibrary, Reference) +class VoxelLibrary : public Resource { + GDCLASS(VoxelLibrary, Resource) public: static const unsigned int MAX_VOXEL_TYPES = 256; // Required limit because voxel types are stored in 8 bits @@ -19,6 +19,10 @@ public: // Use this factory rather than creating voxels from scratch Ref create_voxel(int id, String name); + int get_voxel_count() const; + + void load_default(); + // Internal getters _FORCE_INLINE_ bool has_voxel(int id) const { return _voxel_types[id].is_valid(); } @@ -27,10 +31,15 @@ public: protected: static void _bind_methods(); + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + Ref _get_voxel_bind(int id); private: Ref _voxel_types[MAX_VOXEL_TYPES]; + int _max_count; int _atlas_size; }; diff --git a/voxel_mesher.cpp b/voxel_mesher.cpp index bf9e1c8..50ed99c 100644 --- a/voxel_mesher.cpp +++ b/voxel_mesher.cpp @@ -123,7 +123,6 @@ VoxelMesher::VoxelMesher() _bake_occlusion(true) {} void VoxelMesher::set_library(Ref library) { - ERR_FAIL_COND(library.is_null()); _library = library; } @@ -339,21 +338,19 @@ Ref VoxelMesher::build(const VoxelBuffer &buffer, unsigned int channe mesh_ref = Ref(memnew(ArrayMesh)); for (unsigned int i = 0; i < MAX_MATERIALS; ++i) { - if (_materials[i].is_valid()) { - SurfaceTool &st = _surface_tool[i]; + 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") + // 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_commit") - mesh_ref = st.commit(mesh_ref); - VOXEL_PROFILE_END("mesher_surfacetool_commit") + VOXEL_PROFILE_BEGIN("mesher_surfacetool_commit") + mesh_ref = st.commit(mesh_ref); + VOXEL_PROFILE_END("mesher_surfacetool_commit") - st.clear(); - } + st.clear(); } return mesh_ref; diff --git a/voxel_provider.h b/voxel_provider.h index 9385061..afa23d4 100644 --- a/voxel_provider.h +++ b/voxel_provider.h @@ -1,11 +1,11 @@ #ifndef VOXEL_PROVIDER_H #define VOXEL_PROVIDER_H -#include "reference.h" +#include #include "voxel_buffer.h" -class VoxelProvider : public Reference { - GDCLASS(VoxelProvider, Reference) +class VoxelProvider : public Resource { + GDCLASS(VoxelProvider, Resource) public: virtual void emerge_block(Ref out_buffer, Vector3i block_pos); virtual void immerge_block(Ref buffer, Vector3i block_pos); diff --git a/voxel_provider_test.cpp b/voxel_provider_test.cpp index 121ddbf..070bf8a 100644 --- a/voxel_provider_test.cpp +++ b/voxel_provider_test.cpp @@ -4,7 +4,7 @@ VARIANT_ENUM_CAST(VoxelProviderTest::Mode) VoxelProviderTest::VoxelProviderTest() { - _mode = MODE_FLAT; + _mode = MODE_WAVES; _voxel_type = 1; _pattern_size = Vector3i(10, 10, 10); } @@ -72,19 +72,32 @@ void VoxelProviderTest::generate_block_waves(VoxelBuffer &out_buffer, Vector3i b float period_x = 1.f / static_cast(_pattern_size.x); float period_z = 1.f / static_cast(_pattern_size.z); - for (int rz = 0; rz < size.z; ++rz) { - for (int rx = 0; rx < size.x; ++rx) { + //out_buffer.fill(0, 1); // TRANSVOXEL TEST - float x = origin.x + rx; - float z = origin.z + rz; + if(origin.y + size.y < Math::floor(_pattern_offset.y - 1.5*amplitude)) { + // Everything is ground + out_buffer.fill(_voxel_type); - int h = _pattern_offset.y + amplitude * (Math::cos(x * period_x) + Math::sin(z * period_z)); - int rh = h - origin.y; - if (rh > size.y) - rh = size.y; + } else if(origin.y > Math::ceil(_pattern_offset.y + 1.5*amplitude)) { + // Everything is air + return; - for (int ry = 0; ry < rh; ++ry) { - out_buffer.set_voxel(_voxel_type, rx, ry, rz, 0); + } else { + for (int rz = 0; rz < size.z; ++rz) { + for (int rx = 0; rx < size.x; ++rx) { + + float x = origin.x + rx; + float z = origin.z + rz; + + int h = _pattern_offset.y + amplitude * (Math::cos(x * period_x) + Math::sin(z * period_z)); + int rh = h - origin.y; + if (rh > size.y) + rh = size.y; + + for (int ry = 0; ry < rh; ++ry) { + out_buffer.set_voxel(_voxel_type, rx, ry, rz, 0); + //out_buffer.set_voxel(255, rx, ry, rz, 1); // TRANSVOXEL TEST + } } } } @@ -104,6 +117,13 @@ void VoxelProviderTest::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pattern_offset", "offset"), &VoxelProviderTest::_set_pattern_offset); ClassDB::bind_method(D_METHOD("get_pattern_offset"), &VoxelProviderTest::_get_pattern_offset); + ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Flat,Waves"), "set_mode", "get_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "voxel_type", PROPERTY_HINT_RANGE, "0,255,1"), "set_voxel_type", "get_voxel_type"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "pattern_size"), "set_pattern_size", "get_pattern_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "pattern_offset"), "set_pattern_offset", "get_pattern_offset"); + BIND_CONSTANT(MODE_FLAT); BIND_CONSTANT(MODE_WAVES); } + + diff --git a/voxel_terrain.cpp b/voxel_terrain.cpp index d1ef2d4..e5eb96c 100644 --- a/voxel_terrain.cpp +++ b/voxel_terrain.cpp @@ -9,6 +9,8 @@ VoxelTerrain::VoxelTerrain() _map = Ref(memnew(VoxelMap)); _mesher = Ref(memnew(VoxelMesher)); _mesher_smooth = Ref(memnew(VoxelMesherSmooth)); + + _view_distance_blocks = 8; } Vector3i g_viewer_block_pos; // TODO UGLY! Lambdas or pointers needed... @@ -20,33 +22,98 @@ struct BlockUpdateComparator { } }; -void VoxelTerrain::set_provider(Ref provider) { - _provider = provider; +// TODO See if there is a way to specify materials in voxels directly? + +bool VoxelTerrain::_set(const StringName &p_name, const Variant &p_value) { + + if (p_name.operator String().begins_with("material/")) { + int idx = p_name.operator String().get_slicec('/', 1).to_int(); + if (idx >= VoxelMesher::MAX_MATERIALS || idx < 0) + return false; + set_material(idx, p_value); + return true; + } + + return false; } -Ref VoxelTerrain::get_provider() { +bool VoxelTerrain::_get(const StringName &p_name, Variant &r_ret) const { + + if (p_name.operator String().begins_with("material/")) { + int idx = p_name.operator String().get_slicec('/', 1).to_int(); + if (idx >= VoxelMesher::MAX_MATERIALS || idx < 0) + return false; + r_ret = get_material(idx); + return true; + } + + return false; +} + +void VoxelTerrain::_get_property_list(List *p_list) const { + + for (int i = 0; i < VoxelMesher::MAX_MATERIALS; ++i) { + p_list->push_back(PropertyInfo(Variant::OBJECT, "material/" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,SpatialMaterial")); + } +} + +void VoxelTerrain::set_provider(Ref provider) { + if(provider != _provider) { + _provider = provider; + make_all_view_dirty(); + } +} + +Ref VoxelTerrain::get_provider() const { return _provider; } -Ref VoxelTerrain::get_voxel_library() { +Ref VoxelTerrain::get_voxel_library() const { return _mesher->get_library(); } +void VoxelTerrain::set_voxel_library(Ref library) { + if(library != _mesher->get_library()) { + +#ifdef TOOLS_ENABLED + if(library->get_voxel_count() == 0) { + library->load_default(); + } +#endif + _mesher->set_library(library); + make_all_view_dirty(); + } +} + void VoxelTerrain::set_generate_collisions(bool enabled) { _generate_collisions = enabled; } +int VoxelTerrain::get_view_distance() const { + return _view_distance_blocks * VoxelBlock::SIZE; +} + +void VoxelTerrain::set_view_distance(int distance_in_voxels) { + ERR_FAIL_COND(distance_in_voxels < 0) + int d = distance_in_voxels / VoxelBlock::SIZE; + if(d != _view_distance_blocks) { + _view_distance_blocks = d; + make_all_view_dirty(); + // TODO Immerge blocks too far away + } +} + void VoxelTerrain::set_viewer_path(NodePath path) { if (!path.is_empty()) ERR_FAIL_COND(get_viewer(path) == NULL); _viewer_path = path; } -NodePath VoxelTerrain::get_viewer_path() { +NodePath VoxelTerrain::get_viewer_path() const { return _viewer_path; } -Spatial *VoxelTerrain::get_viewer(NodePath path) { +Spatial *VoxelTerrain::get_viewer(NodePath path) const { if (path.is_empty()) return NULL; Node *node = get_node(path); @@ -55,6 +122,15 @@ Spatial *VoxelTerrain::get_viewer(NodePath path) { return node->cast_to(); } +void VoxelTerrain::set_material(int id, Ref material) { + // TODO Update existing block surfaces + _mesher->set_material(material, id); +} + +Ref VoxelTerrain::get_material(int id) const { + return _mesher->get_material(id); +} + //void VoxelTerrain::clear_update_queue() { // _block_update_queue.clear(); // _dirty_blocks.clear(); @@ -85,6 +161,12 @@ void VoxelTerrain::make_blocks_dirty(Vector3i min, Vector3i size) { } } +void VoxelTerrain::make_all_view_dirty() { + Vector3i radius(_view_distance_blocks, _view_distance_blocks, _view_distance_blocks); + // TODO Take viewer and fixed range into account + make_blocks_dirty(-radius, 2*radius); +} + inline int get_border_index(int x, int max) { return x == 0 ? 0 : x != max ? 1 : 2; } @@ -217,6 +299,11 @@ void VoxelTerrain::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: break; + case NOTIFICATION_READY: + // TODO This should also react to viewer movement + make_all_view_dirty(); + break; + default: break; } @@ -258,6 +345,7 @@ void VoxelTerrain::update_blocks() { bool entire_block_changed = false; if (!_map->has_block(block_pos)) { + // The block's data isn't loaded yet // Create buffer if (!_provider.is_null()) { @@ -274,6 +362,7 @@ void VoxelTerrain::update_blocks() { _provider->emerge_block(buffer_ref, block_pos); // Check script return + // TODO Shouldn't halt execution though, as it can bring the map in an invalid state! ERR_FAIL_COND(buffer_ref->get_size() != block_size); VOXEL_PROFILE_END("block_generation") @@ -316,11 +405,10 @@ void VoxelTerrain::update_blocks() { static inline bool is_mesh_empty(Ref mesh_ref) { if (mesh_ref.is_null()) return true; - Mesh &mesh = **mesh_ref; + const Mesh &mesh = **mesh_ref; if (mesh.get_surface_count() == 0) return true; - // TODO Shouldn't it have an index to the surface rather than just the type? Oo - if (mesh.surface_get_array_len(Mesh::ARRAY_VERTEX) == 0) + if (mesh.surface_get_array_len(0) == 0) return true; return false; } @@ -450,14 +538,20 @@ void VoxelTerrain::_bind_methods() { ClassDB::bind_method(D_METHOD("set_provider", "provider"), &VoxelTerrain::set_provider); ClassDB::bind_method(D_METHOD("get_provider"), &VoxelTerrain::get_provider); + ClassDB::bind_method(D_METHOD("set_voxel_library", "library"), &VoxelTerrain::set_voxel_library); + ClassDB::bind_method(D_METHOD("get_voxel_library"), &VoxelTerrain::get_voxel_library); + + ClassDB::bind_method(D_METHOD("set_view_distance", "distance_in_voxels"), &VoxelTerrain::set_view_distance); + ClassDB::bind_method(D_METHOD("get_view_distance"), &VoxelTerrain::get_view_distance); + ClassDB::bind_method(D_METHOD("get_block_update_count"), &VoxelTerrain::get_block_update_count); ClassDB::bind_method(D_METHOD("get_mesher"), &VoxelTerrain::get_mesher); ClassDB::bind_method(D_METHOD("get_generate_collisions"), &VoxelTerrain::get_generate_collisions); ClassDB::bind_method(D_METHOD("set_generate_collisions", "enabled"), &VoxelTerrain::set_generate_collisions); - ClassDB::bind_method(D_METHOD("get_viewer"), &VoxelTerrain::get_viewer_path); - ClassDB::bind_method(D_METHOD("set_viewer", "path"), &VoxelTerrain::set_viewer_path); + ClassDB::bind_method(D_METHOD("get_viewer_path"), &VoxelTerrain::get_viewer_path); + ClassDB::bind_method(D_METHOD("set_viewer_path", "path"), &VoxelTerrain::set_viewer_path); ClassDB::bind_method(D_METHOD("get_storage"), &VoxelTerrain::get_map); @@ -474,4 +568,10 @@ void VoxelTerrain::_bind_methods() { #ifdef VOXEL_PROFILING ClassDB::bind_method(D_METHOD("get_profiling_info"), &VoxelTerrain::get_profiling_info); #endif + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "provider", PROPERTY_HINT_RESOURCE_TYPE, "VoxelProvider"), "set_provider", "get_provider"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "voxel_library", PROPERTY_HINT_RESOURCE_TYPE, "VoxelLibrary"), "set_voxel_library", "get_voxel_library"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "view_distance"), "set_view_distance", "get_view_distance"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewer_path"), "set_viewer_path", "get_viewer_path"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_collisions"), "set_generate_collisions", "get_generate_collisions"); } diff --git a/voxel_terrain.h b/voxel_terrain.h index 2527f89..c30d176 100644 --- a/voxel_terrain.h +++ b/voxel_terrain.h @@ -16,11 +16,13 @@ public: VoxelTerrain(); void set_provider(Ref provider); - Ref get_provider(); + Ref get_provider() const; + + void set_voxel_library(Ref library); + Ref get_voxel_library() const; void force_load_blocks(Vector3i center, Vector3i extents); int get_block_update_count(); - //void clear_update_queue(); void make_block_dirty(Vector3i bpos); void make_blocks_dirty(Vector3i min, Vector3i size); @@ -28,25 +30,36 @@ public: bool is_block_dirty(Vector3i bpos); void set_generate_collisions(bool enabled); - bool get_generate_collisions() { return _generate_collisions; } + bool get_generate_collisions() const { return _generate_collisions; } + + int get_view_distance() const; + void set_view_distance(int distance_in_voxels); void set_viewer_path(NodePath path); - NodePath get_viewer_path(); + NodePath get_viewer_path() const; + + void set_material(int id, Ref material); + Ref get_material(int id) const; Ref get_mesher() { return _mesher; } Ref get_map() { return _map; } - Ref get_voxel_library(); protected: void _notification(int p_what); private: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + void _process(); void update_blocks(); void update_block_mesh(Vector3i block_pos); - Spatial *get_viewer(NodePath path); + void make_all_view_dirty(); + + Spatial *get_viewer(NodePath path) const; // Observer events //void block_removed(VoxelBlock & block); @@ -70,6 +83,9 @@ private: // Voxel storage Ref _map; + // How many blocks to load around the viewer + int _view_distance_blocks; + // TODO Terrains only need to handle the visible portion of voxels, which reduces the bounds blocks to handle. // Therefore, could a simple grid be better to use than a hashmap?