From bb7b46c966274ceee44e1c46e56b5c2dbf121eee Mon Sep 17 00:00:00 2001 From: Slashscreen Date: Wed, 8 May 2024 00:02:12 -0700 Subject: [PATCH 01/19] =?UTF-8?q?=EF=BB=BFSeparate=20region=20data=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants.h | 14 +- src/register_types.cpp | 1 + src/terrain_3d.cpp | 73 ++++-- src/terrain_3d.h | 9 +- src/terrain_3d_editor.cpp | 137 ++++++++--- src/terrain_3d_instancer.cpp | 8 +- src/terrain_3d_material.cpp | 17 +- src/terrain_3d_storage.cpp | 463 +++++++++++++++++++++++++++-------- src/terrain_3d_storage.h | 89 +++++-- 9 files changed, 621 insertions(+), 190 deletions(-) diff --git a/src/constants.h b/src/constants.h index ab9e97eb..e3b003f2 100644 --- a/src/constants.h +++ b/src/constants.h @@ -68,15 +68,15 @@ using namespace godot; return ret; \ } -#define IS_STORAGE_INIT(ret) \ - if (_terrain == nullptr || _terrain->get_storage().is_null()) { \ - return ret; \ +#define IS_STORAGE_INIT(ret) \ + if (_terrain == nullptr || _terrain->get_storage() == nullptr) { \ + return ret; \ } -#define IS_STORAGE_INIT_MESG(mesg, ret) \ - if (_terrain == nullptr || _terrain->get_storage().is_null()) { \ - LOG(ERROR, mesg); \ - return ret; \ +#define IS_STORAGE_INIT_MESG(mesg, ret) \ + if (_terrain == nullptr || _terrain->get_storage() == nullptr) { \ + LOG(ERROR, mesg); \ + return ret; \ } #endif // CONSTANTS_CLASS_H \ No newline at end of file diff --git a/src/register_types.cpp b/src/register_types.cpp index e86c70b7..f13995c3 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -20,6 +20,7 @@ void initialize_terrain_3d(ModuleInitializationLevel p_level) { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); // Deprecated 0.9.2 - Remove 0.9.3+ diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index 8f44c267..f368d071 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -36,9 +36,9 @@ void Terrain3D::_initialize() { LOG(DEBUG, "Creating blank material"); _material.instantiate(); } - if (_storage.is_null()) { + if (_storage == nullptr) { LOG(DEBUG, "Creating blank storage"); - _storage.instantiate(); + _storage = memnew(Terrain3DStorage); _storage->set_version(Terrain3DStorage::CURRENT_VERSION); } if (_assets.is_null()) { @@ -211,7 +211,7 @@ void Terrain3D::_grab_camera() { } void Terrain3D::_build_meshes(const int p_mesh_lods, const int p_mesh_size) { - if (!is_inside_tree() || !_storage.is_valid()) { + if (!is_inside_tree() || _storage == nullptr) { LOG(DEBUG, "Not inside the tree or no valid storage, skipping build"); return; } @@ -357,7 +357,7 @@ void Terrain3D::_build_collision() { if (IS_EDITOR && !_show_debug_collision) { return; } - if (_storage.is_null()) { + if (_storage == nullptr) { LOG(ERROR, "Storage missing, cannot create collision"); return; } @@ -541,7 +541,7 @@ void Terrain3D::_destroy_instancer() { void Terrain3D::_generate_triangles(PackedVector3Array &p_vertices, PackedVector2Array *p_uvs, const int32_t p_lod, const Terrain3DStorage::HeightFilter p_filter, const bool p_require_nav, const AABB &p_global_aabb) const { - ERR_FAIL_COND(!_storage.is_valid()); + ERR_FAIL_COND(_storage == nullptr); int32_t step = 1 << CLAMP(p_lod, 0, 8); if (!p_global_aabb.has_volume()) { @@ -689,29 +689,56 @@ void Terrain3D::set_mesh_vertex_spacing(const real_t p_spacing) { } } -void Terrain3D::set_material(const Ref &p_material) { - if (_material != p_material) { - _clear_meshes(); - LOG(INFO, "Setting material"); - _material = p_material; - _initialize(); - emit_signal("material_changed"); +void Terrain3D::set_storage_directory(String p_dir) { + LOG(INFO, "Setting storage directory to ", p_dir); + if (_storage == nullptr) { + LOG(DEBUG, "Creating blank storage"); + _storage = memnew(Terrain3DStorage); + _storage->set_version(Terrain3DStorage::CURRENT_VERSION); + _storage->initialize(this); + } + if (_storage_directory != p_dir) { + _storage_directory = p_dir; + if (!p_dir.is_empty()) { + _storage->load_directory(p_dir); + } + emit_signal("storage_changed"); + } +} + +String Terrain3D::get_storage_directory() const { + if (_storage == nullptr) { + return ""; } + return _storage_directory; } // This is run after the object has loaded and initialized -void Terrain3D::set_storage(const Ref &p_storage) { +void Terrain3D::set_storage(Terrain3DStorage *p_storage) { if (_storage != p_storage) { _clear_meshes(); _destroy_collision(); _destroy_instancer(); LOG(INFO, "Setting storage"); _storage = p_storage; + if (_storage == nullptr) { + LOG(INFO, "Clearing storage"); + } _initialize(); emit_signal("storage_changed"); } } +void Terrain3D::set_material(const Ref &p_material) { + if (_material != p_material) { + _clear_meshes(); + LOG(INFO, "Setting material"); + _material = p_material; + _initialize(); + emit_signal("material_changed"); + } +} + void Terrain3D::set_assets(const Ref &p_assets) { if (_assets != p_assets) { _clear_meshes(); @@ -802,7 +829,7 @@ void Terrain3D::set_show_debug_collision(const bool p_enabled) { LOG(INFO, "Setting show collision: ", p_enabled); _show_debug_collision = p_enabled; _destroy_collision(); - if (_storage.is_valid() && _show_debug_collision) { + if (_storage != nullptr && _show_debug_collision) { _build_collision(); } } @@ -941,7 +968,7 @@ void Terrain3D::snap(const Vector3 &p_cam_pos) { } void Terrain3D::update_aabbs() { - if (_meshes.is_empty() || _storage.is_null()) { + if (_meshes.is_empty() || _storage == nullptr) { LOG(DEBUG, "Update AABB called before terrain meshes built. Returning."); return; } @@ -1056,7 +1083,7 @@ Vector3 Terrain3D::get_intersection(const Vector3 &p_src_pos, const Vector3 &p_d Ref Terrain3D::bake_mesh(const int p_lod, const Terrain3DStorage::HeightFilter p_filter) const { LOG(INFO, "Baking mesh at lod: ", p_lod, " with filter: ", p_filter); Ref result; - ERR_FAIL_COND_V(!_storage.is_valid(), result); + ERR_FAIL_COND_V(_storage == nullptr, result); Ref st; st.instantiate(); @@ -1098,12 +1125,13 @@ PackedVector3Array Terrain3D::generate_nav_mesh_source_geometry(const AABB &p_gl PackedStringArray Terrain3D::_get_configuration_warnings() const { PackedStringArray psa; - if (_storage.is_valid()) { + //! FIXME + /* if (_storage.is_valid()) { String ext = _storage->get_path().get_extension(); if (ext != "res") { psa.push_back("Storage resource is not saved as a binary resource file. Click the arrow to the right of `Storage`, then `Save As...` a `*.res` file."); } - } + } */ if (!psa.is_empty()) { psa.push_back("To update this message, deselect and reselect Terrain3D in the Scene panel."); } @@ -1200,10 +1228,10 @@ void Terrain3D::_notification(const int p_what) { case NOTIFICATION_EDITOR_PRE_SAVE: { // Editor Node is about to the current scene LOG(INFO, "NOTIFICATION_EDITOR_PRE_SAVE"); - if (!_storage.is_valid()) { + if (_storage == nullptr) { LOG(DEBUG, "Save requested, but no valid storage. Skipping"); } else { - _storage->save(); + _storage->save(_storage_directory); } if (!_material.is_valid()) { LOG(DEBUG, "Save requested, but no valid material. Skipping"); @@ -1265,6 +1293,8 @@ void Terrain3D::_notification(const int p_what) { void Terrain3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_version"), &Terrain3D::get_version); + ClassDB::bind_method(D_METHOD("set_storage_directory", "directory"), &Terrain3D::set_storage_directory); + ClassDB::bind_method(D_METHOD("get_storage_directory"), &Terrain3D::get_storage_directory); ClassDB::bind_method(D_METHOD("set_debug_level", "level"), &Terrain3D::set_debug_level); ClassDB::bind_method(D_METHOD("get_debug_level"), &Terrain3D::get_debug_level); ClassDB::bind_method(D_METHOD("set_mesh_lods", "count"), &Terrain3D::set_mesh_lods); @@ -1276,7 +1306,6 @@ void Terrain3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_material", "material"), &Terrain3D::set_material); ClassDB::bind_method(D_METHOD("get_material"), &Terrain3D::get_material); - ClassDB::bind_method(D_METHOD("set_storage", "storage"), &Terrain3D::set_storage); ClassDB::bind_method(D_METHOD("get_storage"), &Terrain3D::get_storage); ClassDB::bind_method(D_METHOD("set_assets", "assets"), &Terrain3D::set_assets); ClassDB::bind_method(D_METHOD("get_assets"), &Terrain3D::get_assets); @@ -1315,7 +1344,7 @@ void Terrain3D::_bind_methods() { ClassDB::bind_method(D_METHOD("generate_nav_mesh_source_geometry", "global_aabb", "require_nav"), &Terrain3D::generate_nav_mesh_source_geometry, DEFVAL(true)); ADD_PROPERTY(PropertyInfo(Variant::STRING, "version", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY), "", "get_version"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "storage", PROPERTY_HINT_RESOURCE_TYPE, "Terrain3DStorage"), "set_storage", "get_storage"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "storage_directory", PROPERTY_HINT_DIR), "set_storage_directory", "get_storage_directory"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "Terrain3DMaterial"), "set_material", "get_material"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "assets", PROPERTY_HINT_RESOURCE_TYPE, "Terrain3DAssets"), "set_assets", "get_assets"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "instancer", PROPERTY_HINT_NONE, "Terrain3DInstancer", PROPERTY_USAGE_NONE), "", "get_instancer"); diff --git a/src/terrain_3d.h b/src/terrain_3d.h index 12f21504..64c15311 100644 --- a/src/terrain_3d.h +++ b/src/terrain_3d.h @@ -33,9 +33,10 @@ class Terrain3D : public Node3D { int _mesh_size = 48; int _mesh_lods = 7; real_t _mesh_vertex_spacing = 1.0f; + String _storage_directory; + Terrain3DStorage *_storage = nullptr; Ref _material; - Ref _storage; Ref _assets; Terrain3DInstancer *_instancer = nullptr; Terrain3DEditor *_editor = nullptr; @@ -117,11 +118,13 @@ class Terrain3D : public Node3D { int get_mesh_size() const { return _mesh_size; } void set_mesh_vertex_spacing(const real_t p_spacing); real_t get_mesh_vertex_spacing() const { return _mesh_vertex_spacing; } + void set_storage_directory(String p_dir); + String get_storage_directory() const; + void set_storage(Terrain3DStorage *p_storage); + Terrain3DStorage *get_storage() const { return _storage; } void set_material(const Ref &p_material); Ref get_material() const { return _material; } - void set_storage(const Ref &p_storage); - Ref get_storage() const { return _storage; } void set_assets(const Ref &p_assets); Ref get_assets() const { return _assets; } diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp index 57268fb9..57bf6941 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -6,6 +6,7 @@ #include "logger.h" #include "terrain_3d_editor.h" +#include "terrain_3d_storage.h" #include "terrain_3d_util.h" /////////////////////////// @@ -42,7 +43,7 @@ void Terrain3DEditor::_operate_region(const Vector3 &p_global_position) { Ref height_map = _terrain->get_storage()->get_map_region(TYPE_HEIGHT, region_id); height_range = Util::get_min_max(height_map); - _terrain->get_storage()->remove_region(p_global_position); + _terrain->get_storage()->remove_region(p_global_position, true, _terrain->get_storage_directory()); modified = true; } } @@ -54,7 +55,7 @@ void Terrain3DEditor::_operate_region(const Vector3 &p_global_position) { void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_t p_camera_direction) { LOG(DEBUG_CONT, "Operating at ", p_global_position, " tool type ", _tool, " op ", _operation); - Ref storage = _terrain->get_storage(); + Terrain3DStorage *storage = _terrain->get_storage(); int region_size = storage->get_region_size(); Vector2i region_vsize = Vector2i(region_size, region_size); int region_id = storage->get_region_id(p_global_position); @@ -461,14 +462,27 @@ Dictionary Terrain3DEditor::_get_undo_data() const { if (_tool < 0 || _tool >= TOOL_MAX) { return data; } + + TypedArray e_regions; + for (size_t i = 0; i < _terrain->get_storage()->get_region_count(); i++) { + bool is_modified = _terrain->get_storage()->get_modified()[i]; + if (is_modified) { + e_regions.append(i); + } + } + + //= _terrain->get_storage()->get_regions_under_aabb(_terrain->get_storage()->get_edited_area()); + data["edited_regions"] = e_regions; + LOG(DEBUG, "E Regions ", data["edited_regions"]); switch (_tool) { case REGION: LOG(DEBUG, "Storing region locations"); - data["region_locations"] = _terrain->get_storage()->get_region_locations().duplicate(); + data["is_add"] = _operation == ADD; if (_operation == SUBTRACT) { - data["height_map"] = _terrain->get_storage()->get_maps_copy(TYPE_HEIGHT); - data["control_map"] = _terrain->get_storage()->get_maps_copy(TYPE_CONTROL); - data["color_map"] = _terrain->get_storage()->get_maps_copy(TYPE_COLOR); + data["region_locations"] = _terrain->get_storage()->get_region_locations().duplicate(); + data["height_map"] = _terrain->get_storage()->get_maps_copy(TYPE_HEIGHT, e_regions); + data["control_map"] = _terrain->get_storage()->get_maps_copy(TYPE_CONTROL, e_regions); + data["color_map"] = _terrain->get_storage()->get_maps_copy(TYPE_COLOR, e_regions); data["height_range"] = _terrain->get_storage()->get_height_range(); data["edited_area"] = _terrain->get_storage()->get_edited_area(); } @@ -477,7 +491,7 @@ Dictionary Terrain3DEditor::_get_undo_data() const { case HEIGHT: LOG(DEBUG, "Storing height maps and range"); data["region_locations"] = _terrain->get_storage()->get_region_locations().duplicate(); - data["height_map"] = _terrain->get_storage()->get_maps_copy(TYPE_HEIGHT); + data["height_map"] = _terrain->get_storage()->get_maps_copy(TYPE_HEIGHT, e_regions); data["height_range"] = _terrain->get_storage()->get_height_range(); data["edited_area"] = _terrain->get_storage()->get_edited_area(); break; @@ -490,13 +504,13 @@ Dictionary Terrain3DEditor::_get_undo_data() const { case AUTOSHADER: case NAVIGATION: LOG(DEBUG, "Storing control maps"); - data["control_map"] = _terrain->get_storage()->get_maps_copy(TYPE_CONTROL); + data["control_map"] = _terrain->get_storage()->get_maps_copy(TYPE_CONTROL, e_regions); break; case COLOR: case ROUGHNESS: LOG(DEBUG, "Storing color maps"); - data["color_map"] = _terrain->get_storage()->get_maps_copy(TYPE_COLOR); + data["color_map"] = _terrain->get_storage()->get_maps_copy(TYPE_COLOR, e_regions); break; case INSTANCER: @@ -545,32 +559,99 @@ void Terrain3DEditor::_apply_undo(const Dictionary &p_set) { LOG(INFO, "Applying Undo/Redo set. Array size: ", p_set.size()); LOG(DEBUG, "Apply undo received: ", p_set); - Array keys = p_set.keys(); - for (int i = 0; i < keys.size(); i++) { - String key = keys[i]; - if (key == "region_offsets") { - _terrain->get_storage()->set_region_locations(p_set[key]); - } else if (key == "height_map") { - _terrain->get_storage()->set_maps(TYPE_HEIGHT, p_set[key]); - } else if (key == "control_map") { - _terrain->get_storage()->set_maps(TYPE_CONTROL, p_set[key]); - } else if (key == "color_map") { - _terrain->get_storage()->set_maps(TYPE_COLOR, p_set[key]); - } else if (key == "height_range") { - _terrain->get_storage()->set_height_range(p_set[key]); - } else if (key == "edited_area") { - _terrain->get_storage()->clear_edited_area(); - _terrain->get_storage()->add_edited_area(p_set[key]); - } else if (key == "multimeshes") { - _terrain->get_storage()->set_multimeshes(p_set[key]); + TypedArray regions = p_set["edited_regions"]; + + if (p_set.has("is_add")) { + // FIXME: This block assumes a lot about the structure of the dictionary and the number of elements + bool is_add = p_set["is_add"]; + if (is_add) { + // Remove + LOG(DEBUG, "Removing region..."); + + TypedArray current; + for (size_t i = 0; i < _terrain->get_storage()->get_region_count(); i++) { + bool is_modified = _terrain->get_storage()->get_modified()[i]; + if (is_modified) { + current.append(i); + } + } + + TypedArray diff; + + for (size_t i = 0; i < current.size(); i++) { + if (!regions.has(current[i])) { + diff.append(current[i]); + } + } + + for (size_t i = 0; i < diff.size(); i++) { + _terrain->get_storage()->remove_region(diff[i]); + } + } else { + // Add region + TypedArray current; + for (size_t i = 0; i < _terrain->get_storage()->get_region_count(); i++) { + bool is_modified = _terrain->get_storage()->get_modified()[i]; + if (is_modified) { + current.append(i); + } + } + + TypedArray diff; + + for (size_t i = 0; i < current.size(); i++) { + if (!regions.has(current[i])) { + diff.append(current[i]); + } + } + + LOG(DEBUG, "Re-adding regions..."); + for (size_t i = 0; i < diff.size(); i++) { + Vector2i new_region = ((TypedArray)p_set["region_locations"])[regions[0]]; + int size = _terrain->get_storage()->get_region_size(); + Vector3 new_region_position = Vector3(new_region.x * size, 0, new_region.y * size); + + TypedArray map_info = TypedArray(); + map_info.resize(3); + map_info[TYPE_HEIGHT] = ((TypedArray)p_set["height_map"])[0]; + map_info[TYPE_CONTROL] = ((TypedArray)p_set["control_map"])[0]; + map_info[TYPE_COLOR] = ((TypedArray)p_set["color_map"])[0]; + _terrain->get_storage()->add_region(new_region_position, map_info); + } + } + } else { + Array keys = p_set.keys(); + for (int i = 0; i < keys.size(); i++) { + String key = keys[i]; + if (key == "region_locations") { + _terrain->get_storage()->set_region_locations(p_set[key]); + } else if (key == "height_map") { + _terrain->get_storage()->set_maps(TYPE_HEIGHT, p_set[key], regions); + } else if (key == "control_map") { + _terrain->get_storage()->set_maps(TYPE_CONTROL, p_set[key], regions); + } else if (key == "color_map") { + _terrain->get_storage()->set_maps(TYPE_COLOR, p_set[key], regions); + } else if (key == "height_range") { + _terrain->get_storage()->set_height_range(p_set[key]); + } else if (key == "edited_area") { + _terrain->get_storage()->clear_edited_area(); + _terrain->get_storage()->add_edited_area(p_set[key]); + } else if (key == "multimeshes") { + _terrain->get_storage()->set_multimeshes(p_set[key], regions); + } } } + _terrain->get_storage()->clear_modified(); + // Roll back to previous modified state + for (size_t i = 0; i < regions.size(); i++) { + _terrain->get_storage()->set_modified(i); + } + if (_terrain->get_plugin()->has_method("update_grid")) { LOG(DEBUG, "Calling GDScript update_grid()"); _terrain->get_plugin()->call("update_grid"); } - _pending_undo = false; _modified = false; } diff --git a/src/terrain_3d_instancer.cpp b/src/terrain_3d_instancer.cpp index c9a6011b..1c010360 100644 --- a/src/terrain_3d_instancer.cpp +++ b/src/terrain_3d_instancer.cpp @@ -286,7 +286,7 @@ void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const D Vector2i region_loc = _terrain->get_storage()->get_region_location(p_global_position); append_multimesh(region_loc, mesh_id, xforms, colors); } - _terrain->get_storage()->set_modified(); + _terrain->get_storage()->set_modified(_terrain->get_storage()->get_region_id(p_global_position)); } void Terrain3DInstancer::remove_instances(const Vector3 &p_global_position, const Dictionary &p_params) { @@ -344,7 +344,7 @@ void Terrain3DInstancer::remove_instances(const Vector3 &p_global_position, cons } else { append_multimesh(region_loc, mesh_id, xforms, colors, true); } - _terrain->get_storage()->set_modified(); + _terrain->get_storage()->set_modified(_terrain->get_storage()->get_region_id(p_global_position)); } void Terrain3DInstancer::add_multimesh(const int p_mesh_id, const Ref &p_multimesh, const Transform3D &p_xform) { @@ -397,6 +397,8 @@ void Terrain3DInstancer::add_transforms(const int p_mesh_id, const TypedArray colors = colors_dict[region_loc]; xforms.push_back(trns); colors.push_back(col); + + _terrain->get_storage()->set_modified(_terrain->get_storage()->get_region_id(trns.origin)); // might be stupid } // Merge incoming transforms with existing transforms @@ -408,8 +410,6 @@ void Terrain3DInstancer::add_transforms(const int p_mesh_id, const TypedArrayget_storage()->set_modified(); } // Appends new transforms to existing multimeshes diff --git a/src/terrain_3d_material.cpp b/src/terrain_3d_material.cpp index e819a733..f6b104c8 100644 --- a/src/terrain_3d_material.cpp +++ b/src/terrain_3d_material.cpp @@ -298,18 +298,12 @@ void Terrain3DMaterial::_update_maps() { IS_STORAGE_INIT(VOID); LOG(DEBUG_CONT, "Updating maps in shader"); - Ref storage = _terrain->get_storage(); - RS->material_set_param(_material, "_height_maps", storage->get_height_maps_rid()); - RS->material_set_param(_material, "_control_maps", storage->get_control_maps_rid()); - RS->material_set_param(_material, "_color_maps", storage->get_color_maps_rid()); - LOG(DEBUG_CONT, "Height map RID: ", storage->get_height_maps_rid()); - LOG(DEBUG_CONT, "Control map RID: ", storage->get_control_maps_rid()); - LOG(DEBUG_CONT, "Color map RID: ", storage->get_color_maps_rid()); - + Terrain3DStorage *storage = _terrain->get_storage(); PackedInt32Array region_map = storage->get_region_map(); LOG(DEBUG_CONT, "region_map.size(): ", region_map.size()); if (region_map.size() != Terrain3DStorage::REGION_MAP_SIZE * Terrain3DStorage::REGION_MAP_SIZE) { LOG(ERROR, "Expected region_map.size() of ", Terrain3DStorage::REGION_MAP_SIZE * Terrain3DStorage::REGION_MAP_SIZE); + return; } RS->material_set_param(_material, "_region_map", region_map); RS->material_set_param(_material, "_region_map_size", Terrain3DStorage::REGION_MAP_SIZE); @@ -331,6 +325,13 @@ void Terrain3DMaterial::_update_maps() { RS->material_set_param(_material, "_region_size", region_size); RS->material_set_param(_material, "_region_texel_size", 1.0f / region_size); + RS->material_set_param(_material, "_height_maps", storage->get_height_maps_rid()); + RS->material_set_param(_material, "_control_maps", storage->get_control_maps_rid()); + RS->material_set_param(_material, "_color_maps", storage->get_color_maps_rid()); + LOG(DEBUG_CONT, "Height map RID: ", storage->get_height_maps_rid()); + LOG(DEBUG_CONT, "Control map RID: ", storage->get_control_maps_rid()); + LOG(DEBUG_CONT, "Color map RID: ", storage->get_color_maps_rid()); + real_t spacing = _terrain->get_mesh_vertex_spacing(); LOG(DEBUG_CONT, "Setting mesh vertex spacing in material: ", spacing); RS->material_set_param(_material, "_mesh_vertex_spacing", spacing); diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index c24e80ee..fbb62211 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -1,5 +1,10 @@ // Copyright © 2024 Cory Petkovsek, Roope Palmroos, and Contributors. +#include +#include +#include +#include + #include #include #include @@ -17,6 +22,7 @@ void Terrain3DStorage::_clear() { LOG(INFO, "Clearing storage"); _region_map_dirty = true; _region_map.clear(); + _modified.clear(); _generated_height_maps.clear(); _generated_control_maps.clear(); _generated_color_maps.clear(); @@ -51,7 +57,6 @@ void Terrain3DStorage::set_version(const real_t p_version) { _version = p_version; if (_version < CURRENT_VERSION) { LOG(WARN, "Storage version ", vformat("%.3f", _version), " will be updated to ", vformat("%.3f", CURRENT_VERSION), " upon save"); - _modified = true; } } @@ -65,31 +70,33 @@ void Terrain3DStorage::set_height_range(const Vector2 &p_range) { _height_range = p_range; } +// TODO: _height_range should move to Terrain3D, or the next 3 functions should move +// to individual Regions and AABBs are updated from loaded regions. void Terrain3DStorage::update_heights(const real_t p_height) { if (p_height < _height_range.x) { _height_range.x = p_height; - _modified = true; + //_modified = true; } else if (p_height > _height_range.y) { _height_range.y = p_height; - _modified = true; - } - if (_modified) { - LOG(DEBUG_CONT, "Expanded height range: ", _height_range); + //_modified = true; } + //if (_modified) { + // LOG(DEBUG_CONT, "Expanded height range: ", _height_range); + //} } void Terrain3DStorage::update_heights(const Vector2 &p_heights) { if (p_heights.x < _height_range.x) { _height_range.x = p_heights.x; - _modified = true; + //_modified = true; } if (p_heights.y > _height_range.y) { _height_range.y = p_heights.y; - _modified = true; - } - if (_modified) { - LOG(DEBUG_CONT, "Expanded height range: ", _height_range); + //_modified = true; } + //if (_modified) { + // LOG(DEBUG_CONT, "Expanded height range: ", _height_range); + //} } void Terrain3DStorage::update_height_range() { @@ -145,6 +152,20 @@ Vector2i Terrain3DStorage::get_region_location_from_id(const int p_region_id) co return _region_locations[p_region_id]; } +Vector2i Terrain3DStorage::get_region_location_from_string(const String &p_filename) const { + String working_string = p_filename.trim_suffix(".res"); + String y_str = working_string.right(3).replace("_", ""); + working_string = working_string.erase(working_string.length() - 3, 3); + String x_str = working_string.right(3).replace("_", ""); + if (!x_str.is_valid_int() || !y_str.is_valid_int()) { + LOG(ERROR, "Malformed filename at ", p_filename, ": got x ", x_str, " y ", y_str); + return Vector2i(INT32_MIN, INT32_MIN); + } + int x = x_str.to_int(); + int y = y_str.to_int(); + return Vector2i(x, y); +} + int Terrain3DStorage::get_region_id(const Vector3 &p_global_position) const { Vector2i region_loc = get_region_location(p_global_position); return get_region_id_from_location(region_loc); @@ -162,6 +183,27 @@ int Terrain3DStorage::get_region_id_from_location(const Vector2i &p_region_loc) return -1; } +String Terrain3DStorage::get_region_filename(const Vector2i &p_region_loc) { + const String POS_REGION_FORMAT = "_%02d"; + const String NEG_REGION_FORMAT = "%03d"; + String x_str, y_str; + if (p_region_loc.x >= 0) { + x_str = vformat(POS_REGION_FORMAT, p_region_loc.x); + } else { + x_str = vformat(NEG_REGION_FORMAT, p_region_loc.x); + } + if (p_region_loc.y >= 0) { + y_str = vformat(POS_REGION_FORMAT, p_region_loc.y); + } else { + y_str = vformat(NEG_REGION_FORMAT, p_region_loc.y); + } + return "terrain3d" + x_str + y_str + ".res"; +} + +String Terrain3DStorage::get_region_filename_from_id(const int p_region_id) const { + return get_region_filename(_region_locations[p_region_id]); +} + /** Adds a region to the terrain * Option to include an array of Images to use for maps * Map types are Height:0, Control:1, Color:2, defined in MapType @@ -171,7 +213,7 @@ int Terrain3DStorage::get_region_id_from_location(const Vector2i &p_region_loc) * p_images - Optional array of [ Height, Control, Color ... ] w/ region_sized images * p_update - rebuild the maps if true. Set to false if bulk adding many regions. */ -Error Terrain3DStorage::add_region(const Vector3 &p_global_position, const TypedArray &p_images, const bool p_update) { +Error Terrain3DStorage::add_region(const Vector3 &p_global_position, const TypedArray &p_images, const bool p_update, const String &p_path) { IS_INIT_MESG("Storage not initialized", FAILED); Vector2i region_loc = get_region_location(p_global_position); LOG(INFO, "Adding region at ", p_global_position, ", region_loc ", region_loc, @@ -194,7 +236,7 @@ Error Terrain3DStorage::add_region(const Vector3 &p_global_position, const Typed return OK; } else { LOG(DEBUG, "Region at ", p_global_position, " already exists, overwriting"); - remove_region(p_global_position, false); + remove_region(p_global_position, false, p_path); } } @@ -221,35 +263,49 @@ Error Terrain3DStorage::add_region(const Vector3 &p_global_position, const Typed // Region_map is used by get_region_id so must be updated every time _region_map_dirty = true; - if (p_update) { - LOG(DEBUG, "Updating generated maps"); - _generated_height_maps.clear(); - _generated_control_maps.clear(); - _generated_color_maps.clear(); - update_maps(); - notify_property_list_changed(); - emit_changed(); - } else { - update_maps(); + _modified.push_back(!_loading); // If we're loading, these shouldn't be dirty + if (!_loading) { + if (p_update) { + //notify_property_list_changed(); + //emit_changed(); + force_update_maps(); + } else { + update_maps(); + _modified[_modified.size() - 1] = false; // Is my logic right here?... + } } return OK; } -void Terrain3DStorage::remove_region(const Vector3 &p_global_position, const bool p_update) { +void Terrain3DStorage::remove_region(const Vector3 &p_global_position, const bool p_update, const String &p_path) { LOG(INFO, "Removing region at ", p_global_position, " Updating: ", p_update ? "yes" : "no"); int region_id = get_region_id(p_global_position); - ERR_FAIL_COND_MSG(region_id == -1, "Map does not exist."); + ERR_FAIL_COND_MSG(region_id == -1, "Position out of bounds"); + remove_region_by_id(region_id, p_update, p_path); +} - LOG(INFO, "Removing region at: ", get_region_location(p_global_position)); - _region_locations.remove_at(region_id); +void Terrain3DStorage::remove_region_by_id(const int p_region_id, const bool p_update, const String &p_path) { + ERR_FAIL_COND_MSG(p_region_id == -1 || p_region_id >= _region_locations.size(), "Region id out of bounds."); + String fname = get_region_filename_from_id(p_region_id); + LOG(INFO, "Removing region at: ", p_region_id); + _region_locations.remove_at(p_region_id); LOG(DEBUG, "Removed region_locations, new size: ", _region_locations.size()); - _height_maps.remove_at(region_id); + _height_maps.remove_at(p_region_id); LOG(DEBUG, "Removed heightmaps, new size: ", _height_maps.size()); - _control_maps.remove_at(region_id); + _control_maps.remove_at(p_region_id); LOG(DEBUG, "Removed control maps, new size: ", _control_maps.size()); - _color_maps.remove_at(region_id); + _color_maps.remove_at(p_region_id); LOG(DEBUG, "Removed colormaps, new size: ", _color_maps.size()); + _modified.remove_at(p_region_id); + if (p_path != "") { + Ref da = DirAccess::open(p_path); + da->remove(fname); + if (Engine::get_singleton()->is_editor_hint()) { + EditorInterface::get_singleton()->get_resource_filesystem()->scan(); + } + } + if (_height_maps.size() == 0) { _height_range = Vector2(0.f, 0.f); } @@ -263,7 +319,7 @@ void Terrain3DStorage::remove_region(const Vector3 &p_global_position, const boo _generated_color_maps.clear(); update_maps(); notify_property_list_changed(); - emit_changed(); + //emit_changed(); //! FIXME - (needs to be consistent w/ add_regions } else { update_maps(); } @@ -274,7 +330,7 @@ void Terrain3DStorage::update_maps() { if (_generated_height_maps.is_dirty()) { LOG(DEBUG_CONT, "Regenerating height layered texture from ", _height_maps.size(), " maps"); _generated_height_maps.create(_height_maps); - _modified = true; + _modified.fill(true); any_changed = true; emit_signal("height_maps_changed"); } @@ -282,7 +338,7 @@ void Terrain3DStorage::update_maps() { if (_generated_control_maps.is_dirty()) { LOG(DEBUG_CONT, "Regenerating control layered texture from ", _control_maps.size(), " maps"); _generated_control_maps.create(_control_maps); - _modified = true; + _modified.fill(true); any_changed = true; emit_signal("control_maps_changed"); } @@ -294,7 +350,7 @@ void Terrain3DStorage::update_maps() { map->generate_mipmaps(); } _generated_color_maps.create(_color_maps); - _modified = true; + _modified.fill(true); any_changed = true; emit_signal("color_maps_changed"); } @@ -310,7 +366,8 @@ void Terrain3DStorage::update_maps() { _region_map[map_index] = i + 1; // Begin at 1 since 0 = no region } } - _modified = true; + _modified.resize(_region_locations.size()); + _modified.fill(true); any_changed = true; emit_signal("region_map_changed"); } @@ -319,12 +376,101 @@ void Terrain3DStorage::update_maps() { } } +void Terrain3DStorage::save_region(const String &p_path, const int p_region_id) { + LOG(INFO, "Saving region at index ", p_region_id); + Ref region; + region.instantiate(); + region->set_version(_version); + region->set_height_map(_height_maps[p_region_id]); + region->set_control_map(_control_maps[p_region_id]); + region->set_color_map(_color_maps[p_region_id]); + // TODO: Instances + // region.set_instances(cast_to(_instances[i])); + // Format filename + String fname = get_region_filename_from_id(p_region_id); + String path = p_path + String("/") + fname; + Error err = ResourceSaver::get_singleton()->save(region, path, ResourceSaver::FLAG_COMPRESS); + if (err != OK) { + LOG(ERROR, "Can't save region file: ", fname, ". Error code: ", ERROR, ". Look up @GlobalScope Error enum in the Godot docs"); + } +} + +void Terrain3DStorage::load_region(const String &p_path, const int p_region_id) { + if (p_region_id < 0 || p_region_id >= _region_locations.size()) { + LOG(DEBUG, "Attempted to load a region with an invalid id: ", p_region_id); + return; + } + load_region_by_location(p_path, _region_locations[p_region_id]); +} + +void Terrain3DStorage::load_region_by_location(const String &p_path, const Vector2i &p_region_loc) { + LOG(INFO, "Loading region from location ", p_region_loc); + String path = p_path + String("/") + get_region_filename(p_region_loc); + Ref region = ResourceLoader::get_singleton()->load(path); + if (region.is_null()) { + LOG(ERROR, "Could not load region at ", path, " at location", p_region_loc); + return; + } + register_region(region, p_region_loc); +} + +void Terrain3DStorage::register_region(const Ref &p_region, const Vector2i &p_region_loc) { + Vector3 global_position = Vector3(_region_size * p_region_loc.x, 0.0f, _region_size * p_region_loc.y); + TypedArray maps = TypedArray(); + maps.resize(3); + if (p_region->get_height_map().is_valid()) { + maps[0] = p_region->get_height_map()->duplicate(); + } + if (p_region->get_control_map().is_valid()) { + maps[1] = p_region->get_control_map()->duplicate(); + } + if (p_region->get_color_map().is_valid()) { + maps[2] = p_region->get_color_map()->duplicate(); + } + // Region will get freed afterwards + // 3 - Add to region map + add_region(global_position, maps); +} + +TypedArray Terrain3DStorage::get_regions_under_aabb(const AABB &p_aabb) { + TypedArray found = TypedArray(); + // Step 1: Calculate how many region tiles are under the AABB + Vector2 start = Vector2(p_aabb.get_position().x, p_aabb.get_position().y); + real_t size_x = p_aabb.get_size().x; + real_t size_y = p_aabb.get_size().y; + LOG(INFO, "AABB: ", p_aabb); + real_t rcx = size_x / _region_size; + real_t rcy = size_y / _region_size; + LOG(INFO, "rcx ", rcx, " rcy ", rcy); + // This ternary avoids the edge case of the AABB being exactly on the region boundaries + int region_count_x = godot::Math::is_equal_approx(godot::Math::fract(rcx), 0.0f) ? (int)(rcx) : (int)ceilf(rcx); + int region_count_y = godot::Math::is_equal_approx(godot::Math::fract(rcy), 0.0f) ? (int)(rcy) : (int)ceilf(rcy); + LOG(INFO, "Region counts: x ", region_count_x, " y ", region_count_y); + // Step 2: Check under every region point + // Using ceil and min may seem like an odd choice, but it will snap the checking bounds to the edges of the AABB, + // ensuring that it also hits regions partially covered by the AABB + for (int i = 0; i < region_count_x; i++) { + real_t x = start.x + godot::Math::min((real_t)(i * _region_size), size_x); + for (int j = 0; j < region_count_y; j++) { + real_t y = start.y + godot::Math::min((real_t)(j * _region_size), size_y); + int region_id = get_region_id(Vector3(x, 0, y)); + LOG(INFO, "Region id ", region_id, " location ", Vector3(x, 0, y)); + // If found, append to array + if (region_id != -1) { + found.append(region_id); + } + } + } + return found; +} + void Terrain3DStorage::set_map_region(const MapType p_map_type, const int p_region_id, const Ref &p_image) { switch (p_map_type) { case TYPE_HEIGHT: if (p_region_id >= 0 && p_region_id < _height_maps.size()) { _height_maps[p_region_id] = p_image; force_update_maps(TYPE_HEIGHT); + _modified[p_region_id] = true; } else { LOG(ERROR, "Requested region id is out of bounds. height_maps size: ", _height_maps.size()); } @@ -333,6 +479,7 @@ void Terrain3DStorage::set_map_region(const MapType p_map_type, const int p_regi if (p_region_id >= 0 && p_region_id < _control_maps.size()) { _control_maps[p_region_id] = p_image; force_update_maps(TYPE_CONTROL); + _modified[p_region_id] = true; } else { LOG(ERROR, "Requested region id is out of bounds. control_maps size: ", _control_maps.size()); } @@ -341,6 +488,7 @@ void Terrain3DStorage::set_map_region(const MapType p_map_type, const int p_regi if (p_region_id >= 0 && p_region_id < _color_maps.size()) { _color_maps[p_region_id] = p_image; force_update_maps(TYPE_COLOR); + _modified[p_region_id] = true; } else { LOG(ERROR, "Requested region id is out of bounds. color_maps size: ", _color_maps.size()); } @@ -381,21 +529,40 @@ Ref Terrain3DStorage::get_map_region(const MapType p_map_type, const int return Ref(); } -void Terrain3DStorage::set_maps(const MapType p_map_type, const TypedArray &p_maps) { +void Terrain3DStorage::set_maps(const MapType p_map_type, const TypedArray &p_maps, const TypedArray &p_region_ids) { ERR_FAIL_COND_MSG(p_map_type < 0 || p_map_type >= TYPE_MAX, "Specified map type out of range"); - LOG(INFO, "Setting ", TYPESTR[p_map_type], " maps: ", p_maps.size()); - switch (p_map_type) { - case TYPE_HEIGHT: - _height_maps = sanitize_maps(TYPE_HEIGHT, p_maps); - break; - case TYPE_CONTROL: - _control_maps = sanitize_maps(TYPE_CONTROL, p_maps); - break; - case TYPE_COLOR: - _color_maps = sanitize_maps(TYPE_COLOR, p_maps); - break; - default: - break; + if (p_region_ids.is_empty()) { + LOG(INFO, "Setting ", TYPESTR[p_map_type], " maps: ", p_maps.size()); + switch (p_map_type) { + case TYPE_HEIGHT: + _height_maps = sanitize_maps(TYPE_HEIGHT, p_maps); + break; + case TYPE_CONTROL: + _control_maps = sanitize_maps(TYPE_CONTROL, p_maps); + break; + case TYPE_COLOR: + _color_maps = sanitize_maps(TYPE_COLOR, p_maps); + break; + default: + break; + } + } else { + TypedArray sanitized = sanitize_maps(p_map_type, p_maps); + for (int i = 0; i < p_region_ids.size(); i++) { + switch (p_map_type) { + case TYPE_HEIGHT: + _height_maps[p_region_ids[i]] = sanitized[i]; + break; + case TYPE_CONTROL: + _control_maps[p_region_ids[i]] = sanitized[i]; + break; + case TYPE_COLOR: + _color_maps[p_region_ids[i]] = sanitized[i]; + break; + default: + break; + } + } } force_update_maps(p_map_type); } @@ -421,19 +588,29 @@ TypedArray Terrain3DStorage::get_maps(const MapType p_map_type) const { return TypedArray(); } -TypedArray Terrain3DStorage::get_maps_copy(const MapType p_map_type) const { +TypedArray Terrain3DStorage::get_maps_copy(const MapType p_map_type, const TypedArray &p_region_ids) const { if (p_map_type < 0 || p_map_type >= TYPE_MAX) { LOG(ERROR, "Specified map type out of range"); return TypedArray(); } TypedArray maps = get_maps(p_map_type); TypedArray newmaps; - newmaps.resize(maps.size()); - for (int i = 0; i < maps.size(); i++) { - Ref img; - img.instantiate(); - img->copy_from(maps[i]); - newmaps[i] = img; + if (p_region_ids.is_empty()) { + newmaps.resize(maps.size()); + for (int i = 0; i < maps.size(); i++) { + Ref img; + img.instantiate(); + img->copy_from(maps[i]); + newmaps[i] = img; + } + } else { + newmaps.resize(p_region_ids.size()); + for (int i = 0; i < p_region_ids.size(); i++) { + Ref img; + img.instantiate(); + img->copy_from(maps[p_region_ids[i]]); + newmaps[i] = img; + } } return newmaps; } @@ -455,6 +632,7 @@ void Terrain3DStorage::set_pixel(const MapType p_map_type, const Vector3 &p_glob img_pos = img_pos.clamp(Vector2i(), Vector2i(_region_size - 1, _region_size - 1)); Ref map = get_map_region(p_map_type, region_id); map->set_pixelv(img_pos, p_pixel); + _modified[region_id] = true; } Color Terrain3DStorage::get_pixel(const MapType p_map_type, const Vector3 &p_global_position) const { @@ -648,6 +826,7 @@ TypedArray Terrain3DStorage::sanitize_maps(const MapType p_map_type, cons } void Terrain3DStorage::force_update_maps(const MapType p_map_type) { + LOG(INFO, "Regenerating maps of type: ", p_map_type); switch (p_map_type) { case TYPE_HEIGHT: _generated_height_maps.clear(); @@ -675,43 +854,70 @@ void Terrain3DStorage::set_multimeshes(const Dictionary &p_multimeshes) { } } -void Terrain3DStorage::save() { - if (!_modified) { - LOG(INFO, "Save requested, but not modified. Skipping"); +void Terrain3DStorage::set_multimeshes(const Dictionary &p_multimeshes, const TypedArray &p_region_ids) { + if (p_region_ids.is_empty()) { + _multimeshes = p_multimeshes; + } else { + for (int i = 0; i < p_region_ids.size(); i++) { + _multimeshes[p_region_ids[i]] = p_multimeshes[i]; + } + } +} + +Dictionary Terrain3DStorage::get_multimeshes(const TypedArray &p_region_ids) const { + if (p_region_ids.is_empty()) { + return _multimeshes; + } else { + Dictionary output = Dictionary(); + for (int i = 0; i < p_region_ids.size(); i++) { + Vector2i region_loc = get_region_location_from_id(i); + output[region_loc] = _multimeshes[p_region_ids[i]]; + } + return output; + } +} + +void Terrain3DStorage::save(const String &p_path) { + if (!_modified.has(true)) { + LOG(INFO, "Save requested, but terrain not modified. Skipping"); return; } - String path = get_path(); - // Initiate save to external file. The scene will save itself. - if (path.get_extension() == "tres" || path.get_extension() == "res") { - LOG(DEBUG, "Attempting to save terrain data to external file: " + path); - LOG(DEBUG, "Saving storage version: ", vformat("%.3f", CURRENT_VERSION)); - set_version(CURRENT_VERSION); - Error err; - if (_save_16_bit) { - LOG(DEBUG, "16-bit save requested, converting heightmaps"); - TypedArray original_maps; - original_maps = get_maps_copy(TYPE_HEIGHT); - for (int i = 0; i < _height_maps.size(); i++) { - Ref img = _height_maps[i]; - img->convert(Image::FORMAT_RH); - } - LOG(DEBUG, "Images converted, saving"); - err = ResourceSaver::get_singleton()->save(this, path, ResourceSaver::FLAG_COMPRESS); - LOG(DEBUG, "Restoring 32-bit maps"); - _height_maps = original_maps; - } else { - err = ResourceSaver::get_singleton()->save(this, path, ResourceSaver::FLAG_COMPRESS); + if (_save_16_bit) { + LOG(DEBUG, "16-bit save requested, converting heightmaps"); + TypedArray original_maps; + original_maps = get_maps_copy(TYPE_HEIGHT); + for (int i = 0; i < _height_maps.size(); i++) { + Ref img = _height_maps[i]; + img->convert(Image::FORMAT_RH); } - ERR_FAIL_COND(err); - LOG(DEBUG, "ResourceSaver return error (0 is OK): ", err); - if (err == OK) { - _modified = false; + LOG(DEBUG, "Images converted, saving"); + for (int i = 0; i < _height_maps.size(); i++) { + // Here we do not do a modified check. + save_region(p_path, i); + } + + LOG(DEBUG, "Restoring 32-bit maps"); + _height_maps = original_maps; + + } else { + for (int i = 0; i < _height_maps.size(); i++) { + if (!_modified[i]) { + LOG(INFO, "Save requested, but region not modified. Skipping"); + continue; + } + save_region(p_path, i); } - LOG(INFO, "Finished saving terrain data"); } - if (path.get_extension() != "res") { - LOG(WARN, "Storage resource is not saved as an external, binary .res file"); + _modified.fill(false); // Reset modified flags +} + +void Terrain3DStorage::set_modified(const int p_index) { + if (p_index < _modified.size() && p_index >= 0) { + _modified[p_index] = true; + } else { + // Seems inconsequential but silent failures are always a PITA to hunt down + LOG(ERROR, "Attempted to mark a region as modified that doesn't exist: index ", p_index, " of region count ", _modified.size()); } } @@ -816,26 +1022,20 @@ void Terrain3DStorage::import_images(const TypedArray &p_images, const Ve LOG(DEBUG, "Copying ", size_to_copy, " sized segment"); TypedArray images; images.resize(TYPE_MAX); - Vector3 position = Vector3(descaled_position.x + start_coords.x, 0.f, descaled_position.z + start_coords.y) * vertex_spacing; - int region_id = get_region_id(position); for (int i = 0; i < TYPE_MAX; i++) { Ref img = tmp_images[i]; Ref img_slice; - // If not in a region, generate a new empty map. - if (region_id == -1) { - img_slice = Util::get_filled_image(_region_sizev, COLOR[i], false, FORMAT[i]); - // Otherwise Get the current map. - } else { - img_slice = get_map_region(MapType(i), region_id); - } if (img.is_valid() && !img->is_empty()) { - img_slice->convert(img->get_format()); + img_slice = Util::get_filled_image(_region_sizev, COLOR[i], false, img->get_format()); img_slice->blit_rect(tmp_images[i], Rect2i(start_coords, size_to_copy), Vector2i(0, 0)); + } else { + img_slice = Util::get_filled_image(_region_sizev, COLOR[i], false, FORMAT[i]); } images[i] = img_slice; } // Add the heightmap slice and only regenerate on the last one - add_region(position, images, (x == slices_width - 1 && y == slices_height - 1)); + Vector3 position = Vector3(descaled_position.x + start_coords.x, 0.f, descaled_position.z + start_coords.y); + add_region(position * vertex_spacing, images, (x == slices_width - 1 && y == slices_height - 1)); } } // for y < slices_height, x < slices_width } @@ -974,6 +1174,39 @@ Ref Terrain3DStorage::layered_to_image(const MapType p_map_type) const { return img; } +void Terrain3DStorage::load_directory(const String &p_dir) { + Ref da = DirAccess::open(p_dir); + if (da.is_null()) { + LOG(ERROR, "Error reading Terrain3D save directory."); + return; + } else { + _clear(); + _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); + } + if (p_dir.is_empty()) { + return; + } + _loading = true; + PackedStringArray files = da->get_files(); + for (int i = 0; i < files.size(); i++) { + LOG(INFO, "Loading region from ", p_dir, "/", files[i]); + Ref region = ResourceLoader::get_singleton()->load(p_dir + String("/") + files[i]); + // 1 - if nil, throw error + handle error + if (region.is_null()) { + LOG(ERROR, "Region at path ", p_dir, "/", files[i], " failed to load."); + continue; + } + // 2 - parse coordinates + Vector2i coords = get_region_location_from_string(files[i]); + if (coords == Vector2i(INT32_MIN, INT32_MIN)) { + continue; + } + register_region(region, coords); + } + _loading = false; + force_update_maps(); +} + /** * Returns the location of a terrain vertex at a certain LOD. If there is a hole at the position, it returns * NAN in the vector's Y coordinate. @@ -1094,6 +1327,15 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("add_region", "global_position", "images", "update"), &Terrain3DStorage::add_region, DEFVAL(TypedArray()), DEFVAL(true)); ClassDB::bind_method(D_METHOD("remove_region", "global_position", "update"), &Terrain3DStorage::remove_region, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("save_region", "path", "region_id"), &Terrain3DStorage::save_region); + ClassDB::bind_method(D_METHOD("load_region", "path", "region_id"), &Terrain3DStorage::load_region); + ClassDB::bind_method(D_METHOD("load_region_by_location", "path", "region_location"), &Terrain3DStorage::load_region_by_location); + ClassDB::bind_method(D_METHOD("register_region", "region", "region_location"), &Terrain3DStorage::register_region); + ClassDB::bind_method(D_METHOD("get_region_location_from_string", "filename"), &Terrain3DStorage::get_region_location_from_string); + ClassDB::bind_static_method("Terrain3DStorage", D_METHOD("get_region_filename", "region_location"), &Terrain3DStorage::get_region_filename); + ClassDB::bind_method(D_METHOD("get_region_filename_from_id", "region_id"), &Terrain3DStorage::get_region_filename_from_id); + ClassDB::bind_method(D_METHOD("load_directory", "directory"), &Terrain3DStorage::load_directory); + ClassDB::bind_method(D_METHOD("set_map_region", "map_type", "region_id", "image"), &Terrain3DStorage::set_map_region); ClassDB::bind_method(D_METHOD("get_map_region", "map_type", "region_id"), &Terrain3DStorage::get_map_region); ClassDB::bind_method(D_METHOD("set_maps", "map_type", "maps"), &Terrain3DStorage::set_maps); @@ -1123,10 +1365,10 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("get_scale", "global_position"), &Terrain3DStorage::get_scale); ClassDB::bind_method(D_METHOD("force_update_maps", "map_type"), &Terrain3DStorage::force_update_maps, DEFVAL(TYPE_MAX)); - ClassDB::bind_method(D_METHOD("set_multimeshes", "multimeshes"), &Terrain3DStorage::set_multimeshes); - ClassDB::bind_method(D_METHOD("get_multimeshes"), &Terrain3DStorage::get_multimeshes); + //ClassDB::bind_method(D_METHOD("set_multimeshes", "multimeshes"), &Terrain3DStorage::set_multimeshes); + //ClassDB::bind_method(D_METHOD("get_multimeshes"), &Terrain3DStorage::get_multimeshes); - ClassDB::bind_method(D_METHOD("save"), &Terrain3DStorage::save); + ClassDB::bind_method(D_METHOD("save", "path"), &Terrain3DStorage::save); ClassDB::bind_method(D_METHOD("import_images", "images", "global_position", "offset", "scale"), &Terrain3DStorage::import_images, DEFVAL(Vector3(0, 0, 0)), DEFVAL(0.0), DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("export_image", "file_name", "map_type"), &Terrain3DStorage::export_image); ClassDB::bind_method(D_METHOD("layered_to_image", "map_type"), &Terrain3DStorage::layered_to_image); @@ -1155,3 +1397,22 @@ void Terrain3DStorage::_bind_methods() { ADD_SIGNAL(MethodInfo("maps_edited", PropertyInfo(Variant::AABB, "edited_area"))); ADD_SIGNAL(MethodInfo("multimeshes_changed")); } + +/////////////////////////////// +// Terrain3DRegion Protected +/////////////////////////////// + +void Terrain3DStorage::Terrain3DRegion::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_height_map", "map"), &Terrain3DStorage::Terrain3DRegion::set_height_map); + ClassDB::bind_method(D_METHOD("get_height_map"), &Terrain3DStorage::Terrain3DRegion::get_height_map); + ClassDB::bind_method(D_METHOD("set_control_map", "map"), &Terrain3DStorage::Terrain3DRegion::set_control_map); + ClassDB::bind_method(D_METHOD("get_control_map"), &Terrain3DStorage::Terrain3DRegion::get_control_map); + ClassDB::bind_method(D_METHOD("set_color_map", "map"), &Terrain3DStorage::Terrain3DRegion::set_color_map); + ClassDB::bind_method(D_METHOD("get_color_map"), &Terrain3DStorage::Terrain3DRegion::get_color_map); + ClassDB::bind_method(D_METHOD("set_instances", "instances"), &Terrain3DStorage::Terrain3DRegion::set_multimeshes); + ClassDB::bind_method(D_METHOD("get_instances"), &Terrain3DStorage::Terrain3DRegion::get_multimeshes); + // Note: Modified is only for C++, don't expose it. + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "heightmap", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_height_map", "get_height_map"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "controlmap", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_control_map", "get_control_map"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "colormap", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_color_map", "get_color_map"); +} diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index 6a59bd32..029cbd94 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -14,8 +14,8 @@ class Terrain3D; using namespace godot; -class Terrain3DStorage : public Resource { - GDCLASS(Terrain3DStorage, Resource); +class Terrain3DStorage : public Object { + GDCLASS(Terrain3DStorage, Object); CLASS_NAME(); public: // Constants @@ -65,11 +65,55 @@ class Terrain3DStorage : public Resource { HEIGHT_FILTER_MINIMUM }; + class Terrain3DRegion : public Resource { + GDCLASS(Terrain3DRegion, Resource); + CLASS_NAME(); + + private: + // Map info + Ref _height_map; + Ref _control_map; + Ref _color_map; + // Foliage Instancer contains MultiMeshes saved to disk + // Dictionary[mesh_id:int] -> MultiMesh + Dictionary _multimeshes; + real_t _version = 0.8f; // Set to ensure Godot always saves this + + public: + // Map Info + void set_height_map(Ref p_map) { _height_map = p_map; } + Ref get_height_map() const { return _height_map; } + void set_control_map(Ref p_map) { _control_map = p_map; } + Ref get_control_map() const { return _control_map; } + void set_color_map(Ref p_map) { _color_map = p_map; } + Ref get_color_map() const { return _color_map; } + + // Foliage Instancer + void set_multimeshes(Dictionary p_instances) { _multimeshes = p_instances; } + Dictionary get_multimeshes() const { return _multimeshes; } + + void set_version(real_t p_version) { _version = p_version; } + real_t get_version() { return _version; } + + protected: + static void _bind_methods(); + }; + private: Terrain3D *_terrain = nullptr; + // Storage Settings & flags + real_t _version = 0.8f; // Set to ensure Godot always saves this + TypedArray _modified = TypedArray(); // TODO: Make sure this is the right size + bool _save_16_bit = false; + RegionSize _region_size = SIZE_1024; + Vector2i _region_sizev = Vector2i(_region_size, _region_size); + bool _loading = false; // I am a little hesitant to include state like this. + + // TODO: Should be in Terrain3D so its saved + Vector2 _height_range = Vector2(0.f, 0.f); + // Work data - bool _modified = false; bool _region_map_dirty = true; PackedInt32Array _region_map; // 16x16 Region grid with index into region_locations (1 based array) // Generated Texture RIDs @@ -81,13 +125,6 @@ class Terrain3DStorage : public Resource { AABB _edited_area; uint64_t _last_region_bounds_error = 0; - // Stored Data - real_t _version = 0.8f; // Set to ensure Godot always saves this - RegionSize _region_size = SIZE_1024; - Vector2i _region_sizev = Vector2i(_region_size, _region_size); - bool _save_16_bit = false; - Vector2 _height_range = Vector2(0.f, 0.f); - /** * These arrays house all of the map data. * The Image arrays are region_sized slices of all heightmap data. Their world @@ -137,19 +174,31 @@ class Terrain3DStorage : public Resource { int get_region_count() const { return _region_locations.size(); } Vector2i get_region_location(const Vector3 &p_global_position) const; Vector2i get_region_location_from_id(const int p_region_id) const; + Vector2i get_region_location_from_string(const String &p_filename) const; int get_region_id(const Vector3 &p_global_position) const; int get_region_id_from_location(const Vector2i &p_region_loc) const; + static String get_region_filename(const Vector2i &p_region_loc); + String get_region_filename_from_id(const int p_region_id) const; bool has_region(const Vector3 &p_global_position) const { return get_region_id(p_global_position) != -1; } - Error add_region(const Vector3 &p_global_position, const TypedArray &p_images = TypedArray(), const bool p_update = true); - void remove_region(const Vector3 &p_global_position, const bool p_update = true); + Error add_region(const Vector3 &p_global_position, + const TypedArray &p_images = TypedArray(), const bool p_update = true, + const String &p_path = ""); + void remove_region(const Vector3 &p_global_position, const bool p_update = true, const String &p_path = ""); + void remove_region_by_id(const int p_region_id, const bool p_update = true, const String &p_path = ""); void update_maps(); + void save_region(const String &p_path, const int p_region_id); + void load_region(const String &p_path, const int p_region_id); + void load_region_by_location(const String &p_path, const Vector2i &p_region_loc); + void register_region(const Ref &p_region, const Vector2i &p_region_loc); + TypedArray get_regions_under_aabb(const AABB &p_aabb); + // Maps void set_map_region(const MapType p_map_type, const int p_region_id, const Ref &p_image); Ref get_map_region(const MapType p_map_type, const int p_region_id) const; - void set_maps(const MapType p_map_type, const TypedArray &p_maps); + void set_maps(const MapType p_map_type, const TypedArray &p_maps, const TypedArray &p_region_ids = TypedArray()); TypedArray get_maps(const MapType p_map_type) const; - TypedArray get_maps_copy(const MapType p_map_type) const; + TypedArray get_maps_copy(const MapType p_map_type, const TypedArray &p_region_ids = TypedArray()) const; void set_height_maps(const TypedArray &p_maps) { set_maps(TYPE_HEIGHT, p_maps); } TypedArray get_height_maps() const { return _height_maps; } RID get_height_maps_rid() const { return _generated_height_maps.get_rid(); } @@ -177,12 +226,18 @@ class Terrain3DStorage : public Resource { // Instancer void set_multimeshes(const Dictionary &p_multimeshes); + void set_multimeshes(const Dictionary &p_multimeshes, const TypedArray &p_region_ids); Dictionary get_multimeshes() const { return _multimeshes; } + Dictionary get_multimeshes(const TypedArray &p_region_ids) const; // File I/O - void save(); - void clear_modified() { _modified = false; } - void set_modified() { _modified = true; } + void save(const String &p_path); + void clear_modified() { _modified.fill(false); } + void set_all_regions_modified() { _modified.fill(true); } + void set_modified(int p_index); + TypedArray get_modified() const { return _modified; } + void load_directory(const String &p_dir); + void import_images(const TypedArray &p_images, const Vector3 &p_global_position = Vector3(0.f, 0.f, 0.f), const real_t p_offset = 0.f, const real_t p_scale = 1.f); Error export_image(const String &p_file_name, const MapType p_map_type = TYPE_HEIGHT) const; From 2556bf56cb0284d6f764b89ae9754db8994a3cae Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Tue, 23 Jul 2024 19:03:55 +0700 Subject: [PATCH 02/19] =?UTF-8?q?=EF=BB=BFFix=20apple=20clang=20errors:=20?= =?UTF-8?q?size=5Ft=20and=20override=20warnings;=20change=20append?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/terrain_3d_asset_resource.h | 4 ++-- src/terrain_3d_editor.cpp | 28 ++++++++++++++-------------- src/terrain_3d_storage.cpp | 2 +- src/terrain_3d_texture_asset.cpp | 3 --- src/terrain_3d_texture_asset.h | 2 +- 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/terrain_3d_asset_resource.h b/src/terrain_3d_asset_resource.h index 1376335d..681ac2f5 100644 --- a/src/terrain_3d_asset_resource.h +++ b/src/terrain_3d_asset_resource.h @@ -15,8 +15,8 @@ class Terrain3DAssetResource : public Resource { friend class Terrain3DAssets; public: - Terrain3DAssetResource(){}; - ~Terrain3DAssetResource(){}; + Terrain3DAssetResource() {} + ~Terrain3DAssetResource() {} virtual void clear() = 0; virtual void set_name(const String &p_name) = 0; diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp index 57bf6941..cac37e0c 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -464,10 +464,10 @@ Dictionary Terrain3DEditor::_get_undo_data() const { } TypedArray e_regions; - for (size_t i = 0; i < _terrain->get_storage()->get_region_count(); i++) { + for (int i = 0; i < _terrain->get_storage()->get_region_count(); i++) { bool is_modified = _terrain->get_storage()->get_modified()[i]; if (is_modified) { - e_regions.append(i); + e_regions.push_back(i); } } @@ -569,44 +569,44 @@ void Terrain3DEditor::_apply_undo(const Dictionary &p_set) { LOG(DEBUG, "Removing region..."); TypedArray current; - for (size_t i = 0; i < _terrain->get_storage()->get_region_count(); i++) { + for (int i = 0; i < _terrain->get_storage()->get_region_count(); i++) { bool is_modified = _terrain->get_storage()->get_modified()[i]; if (is_modified) { - current.append(i); + current.push_back(i); } } TypedArray diff; - for (size_t i = 0; i < current.size(); i++) { + for (int i = 0; i < current.size(); i++) { if (!regions.has(current[i])) { - diff.append(current[i]); + diff.push_back(current[i]); } } - for (size_t i = 0; i < diff.size(); i++) { + for (int i = 0; i < diff.size(); i++) { _terrain->get_storage()->remove_region(diff[i]); } } else { // Add region TypedArray current; - for (size_t i = 0; i < _terrain->get_storage()->get_region_count(); i++) { + for (int i = 0; i < _terrain->get_storage()->get_region_count(); i++) { bool is_modified = _terrain->get_storage()->get_modified()[i]; if (is_modified) { - current.append(i); + current.push_back(i); } } TypedArray diff; - for (size_t i = 0; i < current.size(); i++) { + for (int i = 0; i < current.size(); i++) { if (!regions.has(current[i])) { - diff.append(current[i]); + diff.push_back(current[i]); } } LOG(DEBUG, "Re-adding regions..."); - for (size_t i = 0; i < diff.size(); i++) { + for (int i = 0; i < diff.size(); i++) { Vector2i new_region = ((TypedArray)p_set["region_locations"])[regions[0]]; int size = _terrain->get_storage()->get_region_size(); Vector3 new_region_position = Vector3(new_region.x * size, 0, new_region.y * size); @@ -644,7 +644,7 @@ void Terrain3DEditor::_apply_undo(const Dictionary &p_set) { _terrain->get_storage()->clear_modified(); // Roll back to previous modified state - for (size_t i = 0; i < regions.size(); i++) { + for (int i = 0; i < regions.size(); i++) { _terrain->get_storage()->set_modified(i); } @@ -754,7 +754,7 @@ void Terrain3DEditor::operate(const Vector3 &p_global_position, const real_t p_c // Convolve the last 8 movement events, we dont clear on mouse release // so as to make repeated mouse strokes in the same direction consistent - _operation_movement_history.append(_operation_movement); + _operation_movement_history.push_back(_operation_movement); if (_operation_movement_history.size() > 8) { _operation_movement_history.pop_front(); } diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index fbb62211..57d0f813 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -457,7 +457,7 @@ TypedArray Terrain3DStorage::get_regions_under_aabb(const AABB &p_aabb) { LOG(INFO, "Region id ", region_id, " location ", Vector3(x, 0, y)); // If found, append to array if (region_id != -1) { - found.append(region_id); + found.push_back(region_id); } } } diff --git a/src/terrain_3d_texture_asset.cpp b/src/terrain_3d_texture_asset.cpp index 53ad2111..cb2acb58 100644 --- a/src/terrain_3d_texture_asset.cpp +++ b/src/terrain_3d_texture_asset.cpp @@ -37,9 +37,6 @@ Terrain3DTextureAsset::Terrain3DTextureAsset() { clear(); } -Terrain3DTextureAsset::~Terrain3DTextureAsset() { -} - void Terrain3DTextureAsset::clear() { _name = "New Texture"; _id = 0; diff --git a/src/terrain_3d_texture_asset.h b/src/terrain_3d_texture_asset.h index 9573978d..1f68b6cb 100644 --- a/src/terrain_3d_texture_asset.h +++ b/src/terrain_3d_texture_asset.h @@ -25,7 +25,7 @@ class Terrain3DTextureAsset : public Terrain3DAssetResource { public: Terrain3DTextureAsset(); - ~Terrain3DTextureAsset(); + ~Terrain3DTextureAsset() {} void clear() override; From 37e113799c3295214e6ed6e1ef054b999fb84749 Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Thu, 25 Jul 2024 09:56:10 +0700 Subject: [PATCH 03/19] Load only terrain3d*.res files, ignoring cache --- src/terrain_3d_storage.cpp | 41 +++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index 57d0f813..50f41a2c 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -406,7 +406,8 @@ void Terrain3DStorage::load_region(const String &p_path, const int p_region_id) void Terrain3DStorage::load_region_by_location(const String &p_path, const Vector2i &p_region_loc) { LOG(INFO, "Loading region from location ", p_region_loc); String path = p_path + String("/") + get_region_filename(p_region_loc); - Ref region = ResourceLoader::get_singleton()->load(path); + Ref region = ResourceLoader::get_singleton()->load(path, + "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE); if (region.is_null()) { LOG(ERROR, "Could not load region at ", path, " at location", p_region_loc); return; @@ -1175,33 +1176,41 @@ Ref Terrain3DStorage::layered_to_image(const MapType p_map_type) const { } void Terrain3DStorage::load_directory(const String &p_dir) { - Ref da = DirAccess::open(p_dir); - if (da.is_null()) { - LOG(ERROR, "Error reading Terrain3D save directory."); + if (p_dir.is_empty()) { + LOG(ERROR, "Specified data directory is blank"); return; - } else { - _clear(); - _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); } - if (p_dir.is_empty()) { + Ref da = DirAccess::open(p_dir); + if (da.is_null()) { + LOG(ERROR, "Cannot read Terrain3D data directory: ", p_dir); return; } + LOG(INFO, "Loading region files from ", p_dir); + _clear(); + _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); _loading = true; PackedStringArray files = da->get_files(); for (int i = 0; i < files.size(); i++) { - LOG(INFO, "Loading region from ", p_dir, "/", files[i]); - Ref region = ResourceLoader::get_singleton()->load(p_dir + String("/") + files[i]); - // 1 - if nil, throw error + handle error + String fname = files[i]; + String path = p_dir + String("/") + fname; + if (!fname.begins_with("terrain3d") || !fname.ends_with(".res")) { + continue; + } + LOG(DEBUG, "Loading region from ", path); + Ref region = ResourceLoader::get_singleton()->load( + path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE); if (region.is_null()) { - LOG(ERROR, "Region at path ", p_dir, "/", files[i], " failed to load."); + LOG(ERROR, "Region file ", path, " failed to load"); continue; } - // 2 - parse coordinates - Vector2i coords = get_region_location_from_string(files[i]); - if (coords == Vector2i(INT32_MIN, INT32_MIN)) { + Vector2i loc = get_region_location_from_string(fname); + if (loc == Vector2i(INT32_MIN, INT32_MIN)) { + LOG(ERROR, "Cannot get region location from file name: ", fname); continue; } - register_region(region, coords); + LOG(DEBUG, "Region version: ", region->get_version(), " location: ", loc); + region->set_version(CURRENT_VERSION); // Sends upgrade warning if old version + register_region(region, loc); } _loading = false; force_update_maps(); From 00f4fc44c5efdc1923cc730cc4651f474977e5a0 Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Wed, 24 Jul 2024 18:50:29 +0700 Subject: [PATCH 04/19] Add version/modified to Region resource, viewable & read only in inspector --- src/terrain_3d.cpp | 2 -- src/terrain_3d_storage.cpp | 50 ++++++++++++++++++++------------------ src/terrain_3d_storage.h | 37 ++++++++++++++++------------ 3 files changed, 48 insertions(+), 41 deletions(-) diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index f368d071..8b418102 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -39,7 +39,6 @@ void Terrain3D::_initialize() { if (_storage == nullptr) { LOG(DEBUG, "Creating blank storage"); _storage = memnew(Terrain3DStorage); - _storage->set_version(Terrain3DStorage::CURRENT_VERSION); } if (_assets.is_null()) { LOG(DEBUG, "Creating blank texture list"); @@ -694,7 +693,6 @@ void Terrain3D::set_storage_directory(String p_dir) { if (_storage == nullptr) { LOG(DEBUG, "Creating blank storage"); _storage = memnew(Terrain3DStorage); - _storage->set_version(Terrain3DStorage::CURRENT_VERSION); _storage->initialize(this); } if (_storage_directory != p_dir) { diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index 50f41a2c..67f3c2c9 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -49,17 +49,6 @@ Terrain3DStorage::~Terrain3DStorage() { _clear(); } -// Lots of the upgrade process requires this to run first -// It only runs if the version is saved in the file, which only happens if it was -// different from the in the file is different from _version -void Terrain3DStorage::set_version(const real_t p_version) { - LOG(INFO, vformat("%.3f", p_version)); - _version = p_version; - if (_version < CURRENT_VERSION) { - LOG(WARN, "Storage version ", vformat("%.3f", _version), " will be updated to ", vformat("%.3f", CURRENT_VERSION), " upon save"); - } -} - void Terrain3DStorage::set_save_16_bit(const bool p_enabled) { LOG(INFO, p_enabled); _save_16_bit = p_enabled; @@ -380,7 +369,7 @@ void Terrain3DStorage::save_region(const String &p_path, const int p_region_id) LOG(INFO, "Saving region at index ", p_region_id); Ref region; region.instantiate(); - region->set_version(_version); + region->set_version(CURRENT_VERSION); region->set_height_map(_height_maps[p_region_id]); region->set_control_map(_control_maps[p_region_id]); region->set_color_map(_color_maps[p_region_id]); @@ -1314,8 +1303,6 @@ void Terrain3DStorage::_bind_methods() { BIND_CONSTANT(REGION_MAP_SIZE); - ClassDB::bind_method(D_METHOD("set_version", "version"), &Terrain3DStorage::set_version); - ClassDB::bind_method(D_METHOD("get_version"), &Terrain3DStorage::get_version); ClassDB::bind_method(D_METHOD("set_save_16_bit", "enabled"), &Terrain3DStorage::set_save_16_bit); ClassDB::bind_method(D_METHOD("get_save_16_bit"), &Terrain3DStorage::get_save_16_bit); @@ -1386,7 +1373,6 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("get_normal", "global_position"), &Terrain3DStorage::get_normal); int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "version", PROPERTY_HINT_NONE, "", ro_flags), "set_version", "get_version"); //ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "64:64, 128:128, 256:256, 512:512, 1024:1024, 2048:2048"), "set_region_size", "get_region_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "1024:1024"), "set_region_size", "get_region_size"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "save_16_bit", PROPERTY_HINT_NONE), "set_save_16_bit", "get_save_16_bit"); @@ -1407,21 +1393,37 @@ void Terrain3DStorage::_bind_methods() { ADD_SIGNAL(MethodInfo("multimeshes_changed")); } -/////////////////////////////// -// Terrain3DRegion Protected -/////////////////////////////// +///////////////////// +// Terrain3DRegion +///////////////////// + +void Terrain3DStorage::Terrain3DRegion::set_version(const real_t p_version) { + LOG(INFO, vformat("%.3f", p_version)); + _version = p_version; + if (_version < CURRENT_VERSION) { + LOG(WARN, "Region ", get_path(), " version ", vformat("%.3f", _version), " will be updated to ", vformat("%.3f", CURRENT_VERSION), " upon save"); + } +} void Terrain3DStorage::Terrain3DRegion::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_modified"), &Terrain3DStorage::Terrain3DRegion::get_modified); + ClassDB::bind_method(D_METHOD("is_modified"), &Terrain3DStorage::Terrain3DRegion::is_modified); + ClassDB::bind_method(D_METHOD("get_version"), &Terrain3DStorage::Terrain3DRegion::get_version); + ClassDB::bind_method(D_METHOD("set_height_map", "map"), &Terrain3DStorage::Terrain3DRegion::set_height_map); ClassDB::bind_method(D_METHOD("get_height_map"), &Terrain3DStorage::Terrain3DRegion::get_height_map); ClassDB::bind_method(D_METHOD("set_control_map", "map"), &Terrain3DStorage::Terrain3DRegion::set_control_map); ClassDB::bind_method(D_METHOD("get_control_map"), &Terrain3DStorage::Terrain3DRegion::get_control_map); ClassDB::bind_method(D_METHOD("set_color_map", "map"), &Terrain3DStorage::Terrain3DRegion::set_color_map); ClassDB::bind_method(D_METHOD("get_color_map"), &Terrain3DStorage::Terrain3DRegion::get_color_map); - ClassDB::bind_method(D_METHOD("set_instances", "instances"), &Terrain3DStorage::Terrain3DRegion::set_multimeshes); - ClassDB::bind_method(D_METHOD("get_instances"), &Terrain3DStorage::Terrain3DRegion::get_multimeshes); - // Note: Modified is only for C++, don't expose it. - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "heightmap", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_height_map", "get_height_map"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "controlmap", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_control_map", "get_control_map"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "colormap", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_color_map", "get_color_map"); + ClassDB::bind_method(D_METHOD("set_multimeshes", "multimeshes"), &Terrain3DStorage::Terrain3DRegion::set_multimeshes); + ClassDB::bind_method(D_METHOD("get_multimeshes"), &Terrain3DStorage::Terrain3DRegion::get_multimeshes); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "modified", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY), "", "get_modified"); + int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "version", PROPERTY_HINT_NONE, "", ro_flags), "", "get_version"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "heightmap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_height_map", "get_height_map"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "controlmap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_control_map", "get_control_map"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "colormap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_color_map", "get_color_map"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "multimeshes", PROPERTY_HINT_NONE, "", ro_flags), "set_multimeshes", "get_multimeshes"); } diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index 029cbd94..7b0363c3 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -65,45 +65,54 @@ class Terrain3DStorage : public Object { HEIGHT_FILTER_MINIMUM }; + /////////////////////////// + // Terrain3DRegion + /////////////////////////// + class Terrain3DRegion : public Resource { GDCLASS(Terrain3DRegion, Resource); CLASS_NAME(); private: - // Map info + bool _modified = false; + real_t _version = 0.8f; // Set to first version to ensure Godot always upgrades this + // Maps Ref _height_map; Ref _control_map; Ref _color_map; - // Foliage Instancer contains MultiMeshes saved to disk + // Instancer MultiMeshes saved to disk // Dictionary[mesh_id:int] -> MultiMesh Dictionary _multimeshes; - real_t _version = 0.8f; // Set to ensure Godot always saves this public: - // Map Info - void set_height_map(Ref p_map) { _height_map = p_map; } + void set_modified(const bool p_modified) { _modified = p_modified; } + bool get_modified() const { return _modified; } + bool is_modified() const { return _modified == true; } + void set_version(const real_t p_version); + real_t get_version() const { return _version; } + + // Maps + void set_height_map(const Ref &p_map) { _height_map = p_map; } Ref get_height_map() const { return _height_map; } - void set_control_map(Ref p_map) { _control_map = p_map; } + void set_control_map(const Ref &p_map) { _control_map = p_map; } Ref get_control_map() const { return _control_map; } - void set_color_map(Ref p_map) { _color_map = p_map; } + void set_color_map(const Ref &p_map) { _color_map = p_map; } Ref get_color_map() const { return _color_map; } - // Foliage Instancer - void set_multimeshes(Dictionary p_instances) { _multimeshes = p_instances; } + // Instancer + void set_multimeshes(const Dictionary &p_multimeshes) { _multimeshes = p_multimeshes; } Dictionary get_multimeshes() const { return _multimeshes; } - void set_version(real_t p_version) { _version = p_version; } - real_t get_version() { return _version; } - protected: static void _bind_methods(); }; + /////////////////////////// + private: Terrain3D *_terrain = nullptr; // Storage Settings & flags - real_t _version = 0.8f; // Set to ensure Godot always saves this TypedArray _modified = TypedArray(); // TODO: Make sure this is the right size bool _save_16_bit = false; RegionSize _region_size = SIZE_1024; @@ -149,8 +158,6 @@ class Terrain3DStorage : public Object { void initialize(Terrain3D *p_terrain); ~Terrain3DStorage(); - void set_version(const real_t p_version); - real_t get_version() const { return _version; } void set_save_16_bit(const bool p_enabled); bool get_save_16_bit() const { return _save_16_bit; } From 76ce54f707d096660c47ea629de842242c584852 Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Thu, 25 Jul 2024 11:37:00 +0700 Subject: [PATCH 05/19] =?UTF-8?q?=EF=BB=BFMove=20save=2016-bit=20to=20terr?= =?UTF-8?q?ain3d,=20handle=20it=20in=20region?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/terrain_3d.cpp | 16 ++++- src/terrain_3d.h | 3 + src/terrain_3d_storage.cpp | 135 +++++++++++++++++++++---------------- src/terrain_3d_storage.h | 27 +++++--- 4 files changed, 109 insertions(+), 72 deletions(-) diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index 8b418102..878d72f9 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -711,6 +711,11 @@ String Terrain3D::get_storage_directory() const { return _storage_directory; } +void Terrain3D::set_save_16_bit(const bool p_enabled) { + LOG(INFO, p_enabled); + _save_16_bit = p_enabled; +} + // This is run after the object has loaded and initialized void Terrain3D::set_storage(Terrain3DStorage *p_storage) { if (_storage != p_storage) { @@ -1291,10 +1296,15 @@ void Terrain3D::_notification(const int p_what) { void Terrain3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_version"), &Terrain3D::get_version); - ClassDB::bind_method(D_METHOD("set_storage_directory", "directory"), &Terrain3D::set_storage_directory); - ClassDB::bind_method(D_METHOD("get_storage_directory"), &Terrain3D::get_storage_directory); ClassDB::bind_method(D_METHOD("set_debug_level", "level"), &Terrain3D::set_debug_level); ClassDB::bind_method(D_METHOD("get_debug_level"), &Terrain3D::get_debug_level); + + ClassDB::bind_method(D_METHOD("set_storage_directory", "directory"), &Terrain3D::set_storage_directory); + ClassDB::bind_method(D_METHOD("get_storage_directory"), &Terrain3D::get_storage_directory); + ClassDB::bind_method(D_METHOD("set_save_16_bit", "enabled"), &Terrain3D::set_save_16_bit); + ClassDB::bind_method(D_METHOD("get_save_16_bit"), &Terrain3D::get_save_16_bit); + ClassDB::bind_method(D_METHOD("get_storage"), &Terrain3D::get_storage); + ClassDB::bind_method(D_METHOD("set_mesh_lods", "count"), &Terrain3D::set_mesh_lods); ClassDB::bind_method(D_METHOD("get_mesh_lods"), &Terrain3D::get_mesh_lods); ClassDB::bind_method(D_METHOD("set_mesh_size", "size"), &Terrain3D::set_mesh_size); @@ -1304,7 +1314,6 @@ void Terrain3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_material", "material"), &Terrain3D::set_material); ClassDB::bind_method(D_METHOD("get_material"), &Terrain3D::get_material); - ClassDB::bind_method(D_METHOD("get_storage"), &Terrain3D::get_storage); ClassDB::bind_method(D_METHOD("set_assets", "assets"), &Terrain3D::set_assets); ClassDB::bind_method(D_METHOD("get_assets"), &Terrain3D::get_assets); ClassDB::bind_method(D_METHOD("get_instancer"), &Terrain3D::get_instancer); @@ -1343,6 +1352,7 @@ void Terrain3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "version", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY), "", "get_version"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "storage_directory", PROPERTY_HINT_DIR), "set_storage_directory", "get_storage_directory"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "save_16_bit", PROPERTY_HINT_NONE), "set_save_16_bit", "get_save_16_bit"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "Terrain3DMaterial"), "set_material", "get_material"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "assets", PROPERTY_HINT_RESOURCE_TYPE, "Terrain3DAssets"), "set_assets", "get_assets"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "instancer", PROPERTY_HINT_NONE, "Terrain3DInstancer", PROPERTY_USAGE_NONE), "", "get_instancer"); diff --git a/src/terrain_3d.h b/src/terrain_3d.h index 64c15311..0f964e95 100644 --- a/src/terrain_3d.h +++ b/src/terrain_3d.h @@ -34,6 +34,7 @@ class Terrain3D : public Node3D { int _mesh_lods = 7; real_t _mesh_vertex_spacing = 1.0f; String _storage_directory; + bool _save_16_bit = false; Terrain3DStorage *_storage = nullptr; Ref _material; @@ -120,6 +121,8 @@ class Terrain3D : public Node3D { real_t get_mesh_vertex_spacing() const { return _mesh_vertex_spacing; } void set_storage_directory(String p_dir); String get_storage_directory() const; + void set_save_16_bit(const bool p_enabled); + bool get_save_16_bit() const { return _save_16_bit; } void set_storage(Terrain3DStorage *p_storage); Terrain3DStorage *get_storage() const { return _storage; } diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index 67f3c2c9..8f714464 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -49,11 +49,6 @@ Terrain3DStorage::~Terrain3DStorage() { _clear(); } -void Terrain3DStorage::set_save_16_bit(const bool p_enabled) { - LOG(INFO, p_enabled); - _save_16_bit = p_enabled; -} - void Terrain3DStorage::set_height_range(const Vector2 &p_range) { LOG(INFO, vformat("%.2v", p_range)); _height_range = p_range; @@ -365,23 +360,13 @@ void Terrain3DStorage::update_maps() { } } -void Terrain3DStorage::save_region(const String &p_path, const int p_region_id) { - LOG(INFO, "Saving region at index ", p_region_id); - Ref region; - region.instantiate(); - region->set_version(CURRENT_VERSION); - region->set_height_map(_height_maps[p_region_id]); - region->set_control_map(_control_maps[p_region_id]); - region->set_color_map(_color_maps[p_region_id]); - // TODO: Instances - // region.set_instances(cast_to(_instances[i])); - // Format filename - String fname = get_region_filename_from_id(p_region_id); - String path = p_path + String("/") + fname; - Error err = ResourceSaver::get_singleton()->save(region, path, ResourceSaver::FLAG_COMPRESS); - if (err != OK) { - LOG(ERROR, "Can't save region file: ", fname, ". Error code: ", ERROR, ". Look up @GlobalScope Error enum in the Godot docs"); - } +void Terrain3DStorage::save_region(const String &p_dir, const int p_region_id, const bool p_16_bit) { + Vector2i region_loc = _region_locations[p_region_id]; + Ref region = _regions[region_loc]; + String fname = get_region_filename(region_loc); + String path = p_dir + String("/") + fname; + LOG(INFO, "Saving file: ", fname, " region id: ", p_region_id, " loc: ", region_loc); + region->save(path, p_16_bit); } void Terrain3DStorage::load_region(const String &p_path, const int p_region_id) { @@ -420,6 +405,11 @@ void Terrain3DStorage::register_region(const Ref &p_region, con // Region will get freed afterwards // 3 - Add to region map add_region(global_position, maps); + + // Store region + p_region->set_region_loc(p_region_loc); + LOG(INFO, "Registered region ", p_region->get_path(), " at ", p_region->get_region_loc(), " - ", p_region_loc); + _regions[p_region_loc] = p_region; } TypedArray Terrain3DStorage::get_regions_under_aabb(const AABB &p_aabb) { @@ -867,37 +857,18 @@ Dictionary Terrain3DStorage::get_multimeshes(const TypedArray &p_region_ids } } -void Terrain3DStorage::save(const String &p_path) { - if (!_modified.has(true)) { - LOG(INFO, "Save requested, but terrain not modified. Skipping"); - return; - } - - if (_save_16_bit) { - LOG(DEBUG, "16-bit save requested, converting heightmaps"); - TypedArray original_maps; - original_maps = get_maps_copy(TYPE_HEIGHT); - for (int i = 0; i < _height_maps.size(); i++) { - Ref img = _height_maps[i]; - img->convert(Image::FORMAT_RH); - } - LOG(DEBUG, "Images converted, saving"); - for (int i = 0; i < _height_maps.size(); i++) { - // Here we do not do a modified check. - save_region(p_path, i); - } - - LOG(DEBUG, "Restoring 32-bit maps"); - _height_maps = original_maps; - - } else { - for (int i = 0; i < _height_maps.size(); i++) { - if (!_modified[i]) { - LOG(INFO, "Save requested, but region not modified. Skipping"); - continue; - } - save_region(p_path, i); - } +void Terrain3DStorage::save(const String &p_dir) { + LOG(INFO, "Saving data files to ", p_dir); + //if (!_modified.has(true)) { + // LOG(INFO, "Save requested, but terrain not modified. Skipping"); + // return; + //} + for (int i = 0; i < _height_maps.size(); i++) { + //if (!_modified[i]) { + // LOG(INFO, "Save requested, but region not modified. Skipping"); + // continue; + //} + save_region(p_dir, i, _terrain->get_save_16_bit()); } _modified.fill(false); // Reset modified flags } @@ -1303,9 +1274,6 @@ void Terrain3DStorage::_bind_methods() { BIND_CONSTANT(REGION_MAP_SIZE); - ClassDB::bind_method(D_METHOD("set_save_16_bit", "enabled"), &Terrain3DStorage::set_save_16_bit); - ClassDB::bind_method(D_METHOD("get_save_16_bit"), &Terrain3DStorage::get_save_16_bit); - ClassDB::bind_method(D_METHOD("set_height_range", "range"), &Terrain3DStorage::set_height_range); ClassDB::bind_method(D_METHOD("get_height_range"), &Terrain3DStorage::get_height_range); ClassDB::bind_method(D_METHOD("update_height_range"), &Terrain3DStorage::update_height_range); @@ -1375,7 +1343,6 @@ void Terrain3DStorage::_bind_methods() { int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; //ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "64:64, 128:128, 256:256, 512:512, 1024:1024, 2048:2048"), "set_region_size", "get_region_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "1024:1024"), "set_region_size", "get_region_size"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "save_16_bit", PROPERTY_HINT_NONE), "set_save_16_bit", "get_save_16_bit"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "height_range", PROPERTY_HINT_NONE, "", ro_flags), "set_height_range", "get_height_range"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "region_locations", PROPERTY_HINT_ARRAY_TYPE, vformat("%tex_size/%tex_size:%tex_size", Variant::VECTOR2, PROPERTY_HINT_NONE), ro_flags), "set_region_locations", "get_region_locations"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "height_maps", PROPERTY_HINT_ARRAY_TYPE, vformat("%tex_size/%tex_size:%tex_size", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Image"), ro_flags), "set_height_maps", "get_height_maps"); @@ -1405,6 +1372,56 @@ void Terrain3DStorage::Terrain3DRegion::set_version(const real_t p_version) { } } +void Terrain3DStorage::Terrain3DRegion::set_height_map(const Ref &p_map) { + if (p_map.is_valid() && p_map->get_format() != FORMAT[TYPE_HEIGHT]) { + LOG(DEBUG, "Converting file format: ", p_map->get_format(), " to ", FORMAT[TYPE_HEIGHT]); + if (_height_map.is_null()) { + _height_map.instantiate(); + } + _height_map->copy_from(p_map); + _height_map->convert(FORMAT[TYPE_HEIGHT]); + } else { + _height_map = p_map; + } +} + +Error Terrain3DStorage::Terrain3DRegion::save(const String &p_path, const bool p_16_bit) { + // Initiate save to external file. The scene will save itself. + if (!_modified) { + LOG(INFO, "Save requested, but not modified. Skipping - but not really rn"); + //return; + } + if (p_path.is_empty() && get_path().is_empty()) { + LOG(ERROR, "No valid path provided"); + return ERR_FILE_NOT_FOUND; + } + String path = p_path; + if (path.is_empty()) { + path = get_path(); + } + set_version(CURRENT_VERSION); + LOG(INFO, "Writing", (p_16_bit) ? " 16-bit" : "", " region ", _region_loc, " to ", path); + + Error err; + if (p_16_bit) { + Ref original_map; + original_map.instantiate(); + original_map->copy_from(_height_map); + _height_map->convert(Image::FORMAT_RH); + err = ResourceSaver::get_singleton()->save(this, path, ResourceSaver::FLAG_COMPRESS); + _height_map = original_map; + } else { + err = ResourceSaver::get_singleton()->save(this, path, ResourceSaver::FLAG_COMPRESS); + } + if (err == OK) { + _modified = false; + LOG(INFO, "File saved successfully"); + } else { + LOG(ERROR, "Can't save region file: ", path, ". Error code: ", ERROR, ". Look up @GlobalScope Error enum in the Godot docs"); + } + return err; +} + void Terrain3DStorage::Terrain3DRegion::_bind_methods() { ClassDB::bind_method(D_METHOD("get_modified"), &Terrain3DStorage::Terrain3DRegion::get_modified); ClassDB::bind_method(D_METHOD("is_modified"), &Terrain3DStorage::Terrain3DRegion::is_modified); @@ -1419,7 +1436,9 @@ void Terrain3DStorage::Terrain3DRegion::_bind_methods() { ClassDB::bind_method(D_METHOD("set_multimeshes", "multimeshes"), &Terrain3DStorage::Terrain3DRegion::set_multimeshes); ClassDB::bind_method(D_METHOD("get_multimeshes"), &Terrain3DStorage::Terrain3DRegion::get_multimeshes); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "modified", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY), "", "get_modified"); + ClassDB::bind_method(D_METHOD("save", "path", "16-bit"), &Terrain3DStorage::Terrain3DRegion::save, DEFVAL(""), DEFVAL(false)); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "modified", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY), "", "is_modified"); int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "version", PROPERTY_HINT_NONE, "", ro_flags), "", "get_version"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "heightmap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_height_map", "get_height_map"); diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index 7b0363c3..e93f337b 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -76,6 +76,8 @@ class Terrain3DStorage : public Object { private: bool _modified = false; real_t _version = 0.8f; // Set to first version to ensure Godot always upgrades this + Vector2i _region_loc = Vector2i(INT32_MIN, INT32_MIN); + // Maps Ref _height_map; Ref _control_map; @@ -90,9 +92,11 @@ class Terrain3DStorage : public Object { bool is_modified() const { return _modified == true; } void set_version(const real_t p_version); real_t get_version() const { return _version; } + void set_region_loc(const Vector2i &p_region_loc) { _region_loc = p_region_loc; } + Vector2i get_region_loc() const { return _region_loc; } // Maps - void set_height_map(const Ref &p_map) { _height_map = p_map; } + void set_height_map(const Ref &p_map); Ref get_height_map() const { return _height_map; } void set_control_map(const Ref &p_map) { _control_map = p_map; } Ref get_control_map() const { return _control_map; } @@ -103,6 +107,9 @@ class Terrain3DStorage : public Object { void set_multimeshes(const Dictionary &p_multimeshes) { _multimeshes = p_multimeshes; } Dictionary get_multimeshes() const { return _multimeshes; } + // File I/O + Error save(const String &p_path = "", const bool p_16_bit = false); + protected: static void _bind_methods(); }; @@ -114,7 +121,6 @@ class Terrain3DStorage : public Object { // Storage Settings & flags TypedArray _modified = TypedArray(); // TODO: Make sure this is the right size - bool _save_16_bit = false; RegionSize _region_size = SIZE_1024; Vector2i _region_sizev = Vector2i(_region_size, _region_size); bool _loading = false; // I am a little hesitant to include state like this. @@ -145,6 +151,8 @@ class Terrain3DStorage : public Object { TypedArray _control_maps; TypedArray _color_maps; + Dictionary _regions; + // Foliage Instancer contains MultiMeshes saved to disk // Dictionary[region_location:Vector2i] -> Dictionary[mesh_id:int] -> MultiMesh Dictionary _multimeshes; @@ -158,9 +166,6 @@ class Terrain3DStorage : public Object { void initialize(Terrain3D *p_terrain); ~Terrain3DStorage(); - void set_save_16_bit(const bool p_enabled); - bool get_save_16_bit() const { return _save_16_bit; } - void set_height_range(const Vector2 &p_range); Vector2 get_height_range() const { return _height_range; } void update_heights(const real_t p_height); @@ -194,12 +199,6 @@ class Terrain3DStorage : public Object { void remove_region_by_id(const int p_region_id, const bool p_update = true, const String &p_path = ""); void update_maps(); - void save_region(const String &p_path, const int p_region_id); - void load_region(const String &p_path, const int p_region_id); - void load_region_by_location(const String &p_path, const Vector2i &p_region_loc); - void register_region(const Ref &p_region, const Vector2i &p_region_loc); - TypedArray get_regions_under_aabb(const AABB &p_aabb); - // Maps void set_map_region(const MapType p_map_type, const int p_region_id, const Ref &p_image); Ref get_map_region(const MapType p_map_type, const int p_region_id) const; @@ -245,6 +244,12 @@ class Terrain3DStorage : public Object { TypedArray get_modified() const { return _modified; } void load_directory(const String &p_dir); + void save_region(const String &p_dir, const int p_region_id, const bool p_16_bit = false); + void load_region(const String &p_dir, const int p_region_id); + void load_region_by_location(const String &p_path, const Vector2i &p_region_loc); + void register_region(const Ref &p_region, const Vector2i &p_region_loc); + TypedArray get_regions_under_aabb(const AABB &p_aabb); + void import_images(const TypedArray &p_images, const Vector3 &p_global_position = Vector3(0.f, 0.f, 0.f), const real_t p_offset = 0.f, const real_t p_scale = 1.f); Error export_image(const String &p_file_name, const MapType p_map_type = TYPE_HEIGHT) const; From 5a14b11093cb4c8275758c3983c5d748c3b12e2e Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:45:17 +0700 Subject: [PATCH 06/19] =?UTF-8?q?=EF=BB=BFRemove=20Storage::=5Fmodified?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/terrain_3d_editor.cpp | 41 +++++++++++++++++---------------- src/terrain_3d_instancer.cpp | 8 +++---- src/terrain_3d_storage.cpp | 44 +++++++++++++++--------------------- src/terrain_3d_storage.h | 14 ++++++------ 4 files changed, 51 insertions(+), 56 deletions(-) diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp index cac37e0c..45d2487d 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -2,7 +2,6 @@ #include #include -#include #include "logger.h" #include "terrain_3d_editor.h" @@ -29,13 +28,13 @@ void Terrain3DEditor::_region_modified(const Vector3 &p_global_position, const V void Terrain3DEditor::_operate_region(const Vector3 &p_global_position) { bool has_region = _terrain->get_storage()->has_region(p_global_position); - bool modified = false; + bool changed = false; Vector2 height_range; if (_operation == ADD) { if (!has_region) { _terrain->get_storage()->add_region(p_global_position); - modified = true; + changed = true; } } else { if (has_region) { @@ -44,11 +43,11 @@ void Terrain3DEditor::_operate_region(const Vector3 &p_global_position) { height_range = Util::get_min_max(height_map); _terrain->get_storage()->remove_region(p_global_position, true, _terrain->get_storage_directory()); - modified = true; + changed = true; } } - if (modified) { + if (changed) { _region_modified(p_global_position, height_range); } } @@ -464,8 +463,9 @@ Dictionary Terrain3DEditor::_get_undo_data() const { } TypedArray e_regions; - for (int i = 0; i < _terrain->get_storage()->get_region_count(); i++) { - bool is_modified = _terrain->get_storage()->get_modified()[i]; + Array regions = _terrain->get_storage()->get_regions().values(); + for (int i = 0; i < regions.size(); i++) { + bool is_modified = static_cast>(regions[i])->is_modified(); if (is_modified) { e_regions.push_back(i); } @@ -559,7 +559,7 @@ void Terrain3DEditor::_apply_undo(const Dictionary &p_set) { LOG(INFO, "Applying Undo/Redo set. Array size: ", p_set.size()); LOG(DEBUG, "Apply undo received: ", p_set); - TypedArray regions = p_set["edited_regions"]; + TypedArray e_regions = p_set["edited_regions"]; if (p_set.has("is_add")) { // FIXME: This block assumes a lot about the structure of the dictionary and the number of elements @@ -569,8 +569,9 @@ void Terrain3DEditor::_apply_undo(const Dictionary &p_set) { LOG(DEBUG, "Removing region..."); TypedArray current; - for (int i = 0; i < _terrain->get_storage()->get_region_count(); i++) { - bool is_modified = _terrain->get_storage()->get_modified()[i]; + Array regions = _terrain->get_storage()->get_regions().values(); + for (int i = 0; i < regions.size(); i++) { + bool is_modified = static_cast>(regions[i])->is_modified(); if (is_modified) { current.push_back(i); } @@ -590,8 +591,9 @@ void Terrain3DEditor::_apply_undo(const Dictionary &p_set) { } else { // Add region TypedArray current; - for (int i = 0; i < _terrain->get_storage()->get_region_count(); i++) { - bool is_modified = _terrain->get_storage()->get_modified()[i]; + Array regions = _terrain->get_storage()->get_regions().values(); + for (int i = 0; i < regions.size(); i++) { + bool is_modified = static_cast>(regions[i])->is_modified(); if (is_modified) { current.push_back(i); } @@ -626,27 +628,28 @@ void Terrain3DEditor::_apply_undo(const Dictionary &p_set) { if (key == "region_locations") { _terrain->get_storage()->set_region_locations(p_set[key]); } else if (key == "height_map") { - _terrain->get_storage()->set_maps(TYPE_HEIGHT, p_set[key], regions); + _terrain->get_storage()->set_maps(TYPE_HEIGHT, p_set[key], e_regions); } else if (key == "control_map") { - _terrain->get_storage()->set_maps(TYPE_CONTROL, p_set[key], regions); + _terrain->get_storage()->set_maps(TYPE_CONTROL, p_set[key], e_regions); } else if (key == "color_map") { - _terrain->get_storage()->set_maps(TYPE_COLOR, p_set[key], regions); + _terrain->get_storage()->set_maps(TYPE_COLOR, p_set[key], e_regions); } else if (key == "height_range") { _terrain->get_storage()->set_height_range(p_set[key]); } else if (key == "edited_area") { _terrain->get_storage()->clear_edited_area(); _terrain->get_storage()->add_edited_area(p_set[key]); } else if (key == "multimeshes") { - _terrain->get_storage()->set_multimeshes(p_set[key], regions); + _terrain->get_storage()->set_multimeshes(p_set[key], e_regions); } } } - _terrain->get_storage()->clear_modified(); + /* Rework all of undo + _terrain->get_storage()->set_all_regions_modified(false); // Roll back to previous modified state - for (int i = 0; i < regions.size(); i++) { + for (int i = 0; i < e_regions.size(); i++) { _terrain->get_storage()->set_modified(i); - } + }*/ if (_terrain->get_plugin()->has_method("update_grid")) { LOG(DEBUG, "Calling GDScript update_grid()"); diff --git a/src/terrain_3d_instancer.cpp b/src/terrain_3d_instancer.cpp index 1c010360..aa74a7c4 100644 --- a/src/terrain_3d_instancer.cpp +++ b/src/terrain_3d_instancer.cpp @@ -282,11 +282,11 @@ void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const D } // Append multimesh + Vector2i region_loc = _terrain->get_storage()->get_region_location(p_global_position); if (xforms.size() > 0) { - Vector2i region_loc = _terrain->get_storage()->get_region_location(p_global_position); append_multimesh(region_loc, mesh_id, xforms, colors); } - _terrain->get_storage()->set_modified(_terrain->get_storage()->get_region_id(p_global_position)); + _terrain->get_storage()->set_region_modified(region_loc, true); } void Terrain3DInstancer::remove_instances(const Vector3 &p_global_position, const Dictionary &p_params) { @@ -344,7 +344,7 @@ void Terrain3DInstancer::remove_instances(const Vector3 &p_global_position, cons } else { append_multimesh(region_loc, mesh_id, xforms, colors, true); } - _terrain->get_storage()->set_modified(_terrain->get_storage()->get_region_id(p_global_position)); + _terrain->get_storage()->set_region_modified(region_loc, true); } void Terrain3DInstancer::add_multimesh(const int p_mesh_id, const Ref &p_multimesh, const Transform3D &p_xform) { @@ -398,7 +398,7 @@ void Terrain3DInstancer::add_transforms(const int p_mesh_id, const TypedArrayget_storage()->set_modified(_terrain->get_storage()->get_region_id(trns.origin)); // might be stupid + _terrain->get_storage()->set_region_modified(region_loc, true); } // Merge incoming transforms with existing transforms diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index 8f714464..12cdeab4 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -6,10 +6,8 @@ #include #include -#include #include #include -#include #include "logger.h" #include "terrain_3d_storage.h" @@ -22,7 +20,6 @@ void Terrain3DStorage::_clear() { LOG(INFO, "Clearing storage"); _region_map_dirty = true; _region_map.clear(); - _modified.clear(); _generated_height_maps.clear(); _generated_control_maps.clear(); _generated_color_maps.clear(); @@ -104,6 +101,24 @@ void Terrain3DStorage::add_edited_area(const AABB &p_area) { emit_signal("maps_edited", _edited_area); } +void Terrain3DStorage::set_region_modified(const Vector2i &p_region_loc, const bool p_modified) { + Ref region = _regions.get(p_region_loc, Ref()); + if (region.is_null()) { + LOG(ERROR, "Region not found at: ", p_region_loc); + return; + } + return region->set_modified(p_modified); +} + +bool Terrain3DStorage::get_region_modified(const Vector2i &p_region_loc) const { + Ref region = _regions.get(p_region_loc, Ref()); + if (region.is_null()) { + LOG(ERROR, "Region not found at: ", p_region_loc); + return false; + } + return region->is_modified(); +} + void Terrain3DStorage::set_region_size(const RegionSize p_size) { LOG(INFO, p_size); //ERR_FAIL_COND(p_size < SIZE_64); @@ -247,7 +262,6 @@ Error Terrain3DStorage::add_region(const Vector3 &p_global_position, const Typed // Region_map is used by get_region_id so must be updated every time _region_map_dirty = true; - _modified.push_back(!_loading); // If we're loading, these shouldn't be dirty if (!_loading) { if (p_update) { //notify_property_list_changed(); @@ -255,7 +269,6 @@ Error Terrain3DStorage::add_region(const Vector3 &p_global_position, const Typed force_update_maps(); } else { update_maps(); - _modified[_modified.size() - 1] = false; // Is my logic right here?... } } return OK; @@ -281,7 +294,6 @@ void Terrain3DStorage::remove_region_by_id(const int p_region_id, const bool p_u _color_maps.remove_at(p_region_id); LOG(DEBUG, "Removed colormaps, new size: ", _color_maps.size()); - _modified.remove_at(p_region_id); if (p_path != "") { Ref da = DirAccess::open(p_path); da->remove(fname); @@ -314,7 +326,6 @@ void Terrain3DStorage::update_maps() { if (_generated_height_maps.is_dirty()) { LOG(DEBUG_CONT, "Regenerating height layered texture from ", _height_maps.size(), " maps"); _generated_height_maps.create(_height_maps); - _modified.fill(true); any_changed = true; emit_signal("height_maps_changed"); } @@ -322,7 +333,6 @@ void Terrain3DStorage::update_maps() { if (_generated_control_maps.is_dirty()) { LOG(DEBUG_CONT, "Regenerating control layered texture from ", _control_maps.size(), " maps"); _generated_control_maps.create(_control_maps); - _modified.fill(true); any_changed = true; emit_signal("control_maps_changed"); } @@ -334,7 +344,6 @@ void Terrain3DStorage::update_maps() { map->generate_mipmaps(); } _generated_color_maps.create(_color_maps); - _modified.fill(true); any_changed = true; emit_signal("color_maps_changed"); } @@ -350,8 +359,6 @@ void Terrain3DStorage::update_maps() { _region_map[map_index] = i + 1; // Begin at 1 since 0 = no region } } - _modified.resize(_region_locations.size()); - _modified.fill(true); any_changed = true; emit_signal("region_map_changed"); } @@ -450,7 +457,6 @@ void Terrain3DStorage::set_map_region(const MapType p_map_type, const int p_regi if (p_region_id >= 0 && p_region_id < _height_maps.size()) { _height_maps[p_region_id] = p_image; force_update_maps(TYPE_HEIGHT); - _modified[p_region_id] = true; } else { LOG(ERROR, "Requested region id is out of bounds. height_maps size: ", _height_maps.size()); } @@ -459,7 +465,6 @@ void Terrain3DStorage::set_map_region(const MapType p_map_type, const int p_regi if (p_region_id >= 0 && p_region_id < _control_maps.size()) { _control_maps[p_region_id] = p_image; force_update_maps(TYPE_CONTROL); - _modified[p_region_id] = true; } else { LOG(ERROR, "Requested region id is out of bounds. control_maps size: ", _control_maps.size()); } @@ -468,7 +473,6 @@ void Terrain3DStorage::set_map_region(const MapType p_map_type, const int p_regi if (p_region_id >= 0 && p_region_id < _color_maps.size()) { _color_maps[p_region_id] = p_image; force_update_maps(TYPE_COLOR); - _modified[p_region_id] = true; } else { LOG(ERROR, "Requested region id is out of bounds. color_maps size: ", _color_maps.size()); } @@ -612,7 +616,6 @@ void Terrain3DStorage::set_pixel(const MapType p_map_type, const Vector3 &p_glob img_pos = img_pos.clamp(Vector2i(), Vector2i(_region_size - 1, _region_size - 1)); Ref map = get_map_region(p_map_type, region_id); map->set_pixelv(img_pos, p_pixel); - _modified[region_id] = true; } Color Terrain3DStorage::get_pixel(const MapType p_map_type, const Vector3 &p_global_position) const { @@ -870,16 +873,6 @@ void Terrain3DStorage::save(const String &p_dir) { //} save_region(p_dir, i, _terrain->get_save_16_bit()); } - _modified.fill(false); // Reset modified flags -} - -void Terrain3DStorage::set_modified(const int p_index) { - if (p_index < _modified.size() && p_index >= 0) { - _modified[p_index] = true; - } else { - // Seems inconsequential but silent failures are always a PITA to hunt down - LOG(ERROR, "Attempted to mark a region as modified that doesn't exist: index ", p_index, " of region count ", _modified.size()); - } } /** @@ -1235,7 +1228,6 @@ Vector3 Terrain3DStorage::get_normal(const Vector3 &p_global_position) const { void Terrain3DStorage::print_audit_data() const { LOG(INFO, "Dumping storage data"); - LOG(INFO, "_modified: ", _modified); LOG(INFO, "Region_locations size: ", _region_locations.size(), " ", _region_locations); LOG(INFO, "Region map"); for (int i = 0; i < _region_map.size(); i++) { diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index e93f337b..4d7e493f 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -120,7 +120,6 @@ class Terrain3DStorage : public Object { Terrain3D *_terrain = nullptr; // Storage Settings & flags - TypedArray _modified = TypedArray(); // TODO: Make sure this is the right size RegionSize _region_size = SIZE_1024; Vector2i _region_sizev = Vector2i(_region_size, _region_size); bool _loading = false; // I am a little hesitant to include state like this. @@ -177,6 +176,10 @@ class Terrain3DStorage : public Object { AABB get_edited_area() const { return _edited_area; } // Regions + Dictionary get_regions() { return _regions; } + void set_region_modified(const Vector2i &p_region_loc, const bool p_modified = true); + bool get_region_modified(const Vector2i &p_region_loc) const; + void set_region_size(const RegionSize p_size); RegionSize get_region_size() const { return _region_size; } Vector2i get_region_sizev() const { return _region_sizev; } @@ -193,7 +196,8 @@ class Terrain3DStorage : public Object { String get_region_filename_from_id(const int p_region_id) const; bool has_region(const Vector3 &p_global_position) const { return get_region_id(p_global_position) != -1; } Error add_region(const Vector3 &p_global_position, - const TypedArray &p_images = TypedArray(), const bool p_update = true, + const TypedArray &p_images = TypedArray(), + const bool p_update = true, const String &p_path = ""); void remove_region(const Vector3 &p_global_position, const bool p_update = true, const String &p_path = ""); void remove_region_by_id(const int p_region_id, const bool p_update = true, const String &p_path = ""); @@ -237,11 +241,7 @@ class Terrain3DStorage : public Object { Dictionary get_multimeshes(const TypedArray &p_region_ids) const; // File I/O - void save(const String &p_path); - void clear_modified() { _modified.fill(false); } - void set_all_regions_modified() { _modified.fill(true); } - void set_modified(int p_index); - TypedArray get_modified() const { return _modified; } + void save(const String &p_dir); void load_directory(const String &p_dir); void save_region(const String &p_dir, const int p_region_id, const bool p_16_bit = false); From 5b87c7cf6f76fca8593da1abcb7fcb8c27e4652f Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:15:51 +0700 Subject: [PATCH 07/19] =?UTF-8?q?=EF=BB=BFMove=20Terrain3DRegion=20to=20it?= =?UTF-8?q?s=20own=20class=20and=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Terrain3D.vcxproj | 2 + Terrain3D.vcxproj.filters | 6 + project/addons/terrain_3d/editor.gd | 4 +- src/register_types.cpp | 2 +- src/terrain_3d.cpp | 57 ++--- src/terrain_3d.h | 1 - src/terrain_3d_editor.cpp | 6 +- src/terrain_3d_instancer.cpp | 4 - src/terrain_3d_instancer.h | 2 +- src/terrain_3d_region.cpp | 103 +++++++++ src/terrain_3d_region.h | 53 +++++ src/terrain_3d_storage.cpp | 314 ++++++++-------------------- src/terrain_3d_storage.h | 73 +------ src/terrain_3d_util.cpp | 26 +++ src/terrain_3d_util.h | 11 + 15 files changed, 322 insertions(+), 342 deletions(-) create mode 100644 src/terrain_3d_region.cpp create mode 100644 src/terrain_3d_region.h diff --git a/Terrain3D.vcxproj b/Terrain3D.vcxproj index ed9989a3..18f927f5 100644 --- a/Terrain3D.vcxproj +++ b/Terrain3D.vcxproj @@ -149,6 +149,7 @@ + @@ -164,6 +165,7 @@ + diff --git a/Terrain3D.vcxproj.filters b/Terrain3D.vcxproj.filters index ac8c334c..95927250 100644 --- a/Terrain3D.vcxproj.filters +++ b/Terrain3D.vcxproj.filters @@ -69,6 +69,9 @@ 4. Headers + + 4. Headers + @@ -107,6 +110,9 @@ 5. C++ + + 5. C++ + diff --git a/project/addons/terrain_3d/editor.gd b/project/addons/terrain_3d/editor.gd index 13b9805d..facee9ab 100644 --- a/project/addons/terrain_3d/editor.gd +++ b/project/addons/terrain_3d/editor.gd @@ -103,11 +103,11 @@ func _edit(p_object: Object) -> void: ui.set_visible(true) terrain.set_meta("_edit_lock_", true) - # Get alerted when a new asset list is loaded + # Get alerted when a new asset list is loaded if not terrain.assets_changed.is_connected(asset_dock.update_assets): terrain.assets_changed.connect(asset_dock.update_assets) asset_dock.update_assets() - # Get alerted when the region map changes + # Get alerted when the region map changes if not terrain.get_storage().region_map_changed.is_connected(update_region_grid): terrain.get_storage().region_map_changed.connect(update_region_grid) update_region_grid() diff --git a/src/register_types.cpp b/src/register_types.cpp index f13995c3..a86addca 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -20,7 +20,7 @@ void initialize_terrain_3d(ModuleInitializationLevel p_level) { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); - ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); // Deprecated 0.9.2 - Remove 0.9.3+ diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index 878d72f9..6be6a2f9 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -26,10 +26,10 @@ /////////////////////////// // Initialize static member variable -int Terrain3D::debug_level{ ERROR }; +int Terrain3D::debug_level{ DEBUG }; void Terrain3D::_initialize() { - LOG(INFO, "Checking instancer, material, storage, assets, signal, and mesh initialization"); + LOG(INFO, "Checking initialization of main subsystems"); // Make blank objects if needed if (_material.is_null()) { @@ -184,7 +184,7 @@ void Terrain3D::_destroy_mouse_picking() { memdelete_safely(_mouse_quad); LOG(DEBUG, "Freeing mouse_cam"); memdelete_safely(_mouse_cam); - LOG(DEBUG, "memdelete mouse_vp"); + LOG(DEBUG, "Freeing mouse_vp"); memdelete_safely(_mouse_vp); } @@ -682,6 +682,7 @@ void Terrain3D::set_mesh_vertex_spacing(const real_t p_spacing) { _destroy_collision(); _destroy_instancer(); _initialize(); + _storage->_mesh_vertex_spacing = spacing; } if (IS_EDITOR && _plugin != nullptr) { _plugin->call("update_region_grid"); @@ -690,17 +691,13 @@ void Terrain3D::set_mesh_vertex_spacing(const real_t p_spacing) { void Terrain3D::set_storage_directory(String p_dir) { LOG(INFO, "Setting storage directory to ", p_dir); - if (_storage == nullptr) { - LOG(DEBUG, "Creating blank storage"); - _storage = memnew(Terrain3DStorage); - _storage->initialize(this); - } if (_storage_directory != p_dir) { + _clear_meshes(); + _destroy_collision(); + _destroy_instancer(); + memdelete_safely(_storage); _storage_directory = p_dir; - if (!p_dir.is_empty()) { - _storage->load_directory(p_dir); - } - emit_signal("storage_changed"); + _initialize(); } } @@ -716,22 +713,6 @@ void Terrain3D::set_save_16_bit(const bool p_enabled) { _save_16_bit = p_enabled; } -// This is run after the object has loaded and initialized -void Terrain3D::set_storage(Terrain3DStorage *p_storage) { - if (_storage != p_storage) { - _clear_meshes(); - _destroy_collision(); - _destroy_instancer(); - LOG(INFO, "Setting storage"); - _storage = p_storage; - if (_storage == nullptr) { - LOG(INFO, "Clearing storage"); - } - _initialize(); - emit_signal("storage_changed"); - } -} - void Terrain3D::set_material(const Ref &p_material) { if (_material != p_material) { _clear_meshes(); @@ -1128,13 +1109,9 @@ PackedVector3Array Terrain3D::generate_nav_mesh_source_geometry(const AABB &p_gl PackedStringArray Terrain3D::_get_configuration_warnings() const { PackedStringArray psa; - //! FIXME - /* if (_storage.is_valid()) { - String ext = _storage->get_path().get_extension(); - if (ext != "res") { - psa.push_back("Storage resource is not saved as a binary resource file. Click the arrow to the right of `Storage`, then `Save As...` a `*.res` file."); - } - } */ + if (_storage_directory.is_empty()) { + psa.push_back("No storage directory specified. Select a directory then save the scene to write data."); + } if (!psa.is_empty()) { psa.push_back("To update this message, deselect and reselect Terrain3D in the Scene panel."); } @@ -1188,7 +1165,7 @@ void Terrain3D::_notification(const int p_what) { set_notify_transform(true); set_meta("_edit_lock_", true); _setup_mouse_picking(); - _initialize(); + _initialize(); // Rebuild anything freed: meshes, collision, instancer set_process(true); break; } @@ -1234,7 +1211,7 @@ void Terrain3D::_notification(const int p_what) { if (_storage == nullptr) { LOG(DEBUG, "Save requested, but no valid storage. Skipping"); } else { - _storage->save(_storage_directory); + _storage->save_directory(_storage_directory); } if (!_material.is_valid()) { LOG(DEBUG, "Save requested, but no valid material. Skipping"); @@ -1269,9 +1246,7 @@ void Terrain3D::_notification(const int p_what) { LOG(INFO, "NOTIFICATION_EXIT_TREE"); set_process(false); _clear_meshes(); - _destroy_collision(); _destroy_mouse_picking(); - _destroy_instancer(); break; } @@ -1286,6 +1261,9 @@ void Terrain3D::_notification(const int p_what) { case NOTIFICATION_PREDELETE: { // Object is about to be deleted LOG(INFO, "NOTIFICATION_PREDELETE"); + _destroy_collision(); + _destroy_instancer(); + memdelete_safely(_storage); break; } @@ -1379,7 +1357,6 @@ void Terrain3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug_show_collision"), "set_show_debug_collision", "get_show_debug_collision"); ADD_SIGNAL(MethodInfo("material_changed")); - ADD_SIGNAL(MethodInfo("storage_changed")); ADD_SIGNAL(MethodInfo("assets_changed")); // DEPRECATED 0.9.2 - Remove 0.9.3+ diff --git a/src/terrain_3d.h b/src/terrain_3d.h index 0f964e95..af9f345b 100644 --- a/src/terrain_3d.h +++ b/src/terrain_3d.h @@ -124,7 +124,6 @@ class Terrain3D : public Node3D { void set_save_16_bit(const bool p_enabled); bool get_save_16_bit() const { return _save_16_bit; } - void set_storage(Terrain3DStorage *p_storage); Terrain3DStorage *get_storage() const { return _storage; } void set_material(const Ref &p_material); Ref get_material() const { return _material; } diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp index 45d2487d..f2df2fea 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -465,7 +465,7 @@ Dictionary Terrain3DEditor::_get_undo_data() const { TypedArray e_regions; Array regions = _terrain->get_storage()->get_regions().values(); for (int i = 0; i < regions.size(); i++) { - bool is_modified = static_cast>(regions[i])->is_modified(); + bool is_modified = static_cast>(regions[i])->is_modified(); if (is_modified) { e_regions.push_back(i); } @@ -571,7 +571,7 @@ void Terrain3DEditor::_apply_undo(const Dictionary &p_set) { TypedArray current; Array regions = _terrain->get_storage()->get_regions().values(); for (int i = 0; i < regions.size(); i++) { - bool is_modified = static_cast>(regions[i])->is_modified(); + bool is_modified = static_cast>(regions[i])->is_modified(); if (is_modified) { current.push_back(i); } @@ -593,7 +593,7 @@ void Terrain3DEditor::_apply_undo(const Dictionary &p_set) { TypedArray current; Array regions = _terrain->get_storage()->get_regions().values(); for (int i = 0; i < regions.size(); i++) { - bool is_modified = static_cast>(regions[i])->is_modified(); + bool is_modified = static_cast>(regions[i])->is_modified(); if (is_modified) { current.push_back(i); } diff --git a/src/terrain_3d_instancer.cpp b/src/terrain_3d_instancer.cpp index aa74a7c4..9151626f 100644 --- a/src/terrain_3d_instancer.cpp +++ b/src/terrain_3d_instancer.cpp @@ -132,10 +132,6 @@ void Terrain3DInstancer::_destroy_mmi_by_location(const Vector2i &p_region_loc, // Public Functions /////////////////////////// -Terrain3DInstancer::~Terrain3DInstancer() { - destroy(); -} - void Terrain3DInstancer::initialize(Terrain3D *p_terrain) { if (p_terrain) { _terrain = p_terrain; diff --git a/src/terrain_3d_instancer.h b/src/terrain_3d_instancer.h index 8da07877..38445dad 100644 --- a/src/terrain_3d_instancer.h +++ b/src/terrain_3d_instancer.h @@ -36,7 +36,7 @@ class Terrain3DInstancer : public Object { public: Terrain3DInstancer() {} - ~Terrain3DInstancer(); + ~Terrain3DInstancer() { destroy(); } void initialize(Terrain3D *p_terrain); void destroy(); diff --git a/src/terrain_3d_region.cpp b/src/terrain_3d_region.cpp new file mode 100644 index 00000000..26ca95ac --- /dev/null +++ b/src/terrain_3d_region.cpp @@ -0,0 +1,103 @@ +// Copyright © 2024 Cory Petkovsek, Roope Palmroos, and Contributors. + +#include + +#include "logger.h" +#include "terrain_3d_region.h" + +///////////////////// +// Public Functions +///////////////////// + +void Terrain3DRegion::set_version(const real_t p_version) { + LOG(INFO, vformat("%.3f", p_version)); + _version = p_version; + if (_version < Terrain3DStorage::CURRENT_VERSION) { + LOG(WARN, "Region ", get_path(), " version ", vformat("%.3f", _version), + " will be updated to ", vformat("%.3f", Terrain3DStorage::CURRENT_VERSION), " upon save"); + } +} + +void Terrain3DRegion::set_height_map(const Ref &p_map) { + Image::Format format = Terrain3DStorage::FORMAT[Terrain3DStorage::TYPE_HEIGHT]; + if (p_map.is_valid() && p_map->get_format() != format) { + LOG(DEBUG, "Converting file format: ", p_map->get_format(), " to ", format); + if (_height_map.is_null()) { + _height_map.instantiate(); + } + _height_map->copy_from(p_map); + _height_map->convert(format); + } else { + _height_map = p_map; + } +} + +Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) { + // Initiate save to external file. The scene will save itself. + if (!_modified) { + LOG(INFO, "Save requested, but not modified. Skipping"); + return ERR_SKIP; + } + if (p_path.is_empty() && get_path().is_empty()) { + LOG(ERROR, "No valid path provided"); + return ERR_FILE_NOT_FOUND; + } + String path = p_path; + if (path.is_empty()) { + path = get_path(); + } + set_version(Terrain3DStorage::CURRENT_VERSION); + LOG(INFO, "Writing", (p_16_bit) ? " 16-bit" : "", " region ", _region_loc, " to ", path); + + Error err; + if (p_16_bit) { + Ref original_map; + original_map.instantiate(); + original_map->copy_from(_height_map); + _height_map->convert(Image::FORMAT_RH); + err = ResourceSaver::get_singleton()->save(this, path, ResourceSaver::FLAG_COMPRESS); + _height_map = original_map; + } else { + err = ResourceSaver::get_singleton()->save(this, path, ResourceSaver::FLAG_COMPRESS); + } + if (err == OK) { + _modified = false; + LOG(INFO, "File saved successfully"); + } else { + LOG(ERROR, "Can't save region file: ", path, ". Error code: ", ERROR, ". Look up @GlobalScope Error enum in the Godot docs"); + } + return err; +} + +///////////////////// +// Protected Functions +///////////////////// + +void Terrain3DRegion::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_version"), &Terrain3DRegion::set_version); + ClassDB::bind_method(D_METHOD("get_version"), &Terrain3DRegion::get_version); + ClassDB::bind_method(D_METHOD("is_modified"), &Terrain3DRegion::is_modified); + ClassDB::bind_method(D_METHOD("get_region_loc"), &Terrain3DRegion::get_region_loc); + + ClassDB::bind_method(D_METHOD("set_height_map", "map"), &Terrain3DRegion::set_height_map); + ClassDB::bind_method(D_METHOD("get_height_map"), &Terrain3DRegion::get_height_map); + ClassDB::bind_method(D_METHOD("set_control_map", "map"), &Terrain3DRegion::set_control_map); + ClassDB::bind_method(D_METHOD("get_control_map"), &Terrain3DRegion::get_control_map); + ClassDB::bind_method(D_METHOD("set_color_map", "map"), &Terrain3DRegion::set_color_map); + ClassDB::bind_method(D_METHOD("get_color_map"), &Terrain3DRegion::get_color_map); + ClassDB::bind_method(D_METHOD("set_multimeshes", "multimeshes"), &Terrain3DRegion::set_multimeshes); + ClassDB::bind_method(D_METHOD("get_multimeshes"), &Terrain3DRegion::get_multimeshes); + + ClassDB::bind_method(D_METHOD("save", "path", "16-bit"), &Terrain3DRegion::save, DEFVAL(""), DEFVAL(false)); + + // These two show what's on the disk (defaults) not what is in memory, so hide them + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "modified", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "is_modified"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "location", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_region_loc"); + + int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "version", PROPERTY_HINT_NONE, "", ro_flags), "set_version", "get_version"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "heightmap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_height_map", "get_height_map"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "controlmap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_control_map", "get_control_map"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "colormap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_color_map", "get_color_map"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "multimeshes", PROPERTY_HINT_NONE, "", ro_flags), "set_multimeshes", "get_multimeshes"); +} diff --git a/src/terrain_3d_region.h b/src/terrain_3d_region.h new file mode 100644 index 00000000..557dae16 --- /dev/null +++ b/src/terrain_3d_region.h @@ -0,0 +1,53 @@ +// Copyright © 2024 Cory Petkovsek, Roope Palmroos, and Contributors. + +#ifndef TERRAIN3D_REGION_CLASS_H +#define TERRAIN3D_REGION_CLASS_H + +#include "constants.h" + +using namespace godot; + +class Terrain3DRegion : public Resource { + GDCLASS(Terrain3DRegion, Resource); + CLASS_NAME(); + +private: + real_t _version = 0.8f; // Set to first version to ensure Godot always upgrades this + bool _modified = false; + Vector2i _region_loc = Vector2i(INT32_MIN, INT32_MIN); + // Maps + Ref _height_map; + Ref _control_map; + Ref _color_map; + // Instancer MultiMeshes saved to disk + // Dictionary[mesh_id:int] -> MultiMesh + Dictionary _multimeshes; + +public: + void set_version(const real_t p_version); + real_t get_version() const { return _version; } + void set_modified(const bool p_modified) { _modified = p_modified; } + bool is_modified() const { return _modified; } + void set_region_loc(const Vector2i &p_region_loc) { _region_loc = p_region_loc; } + Vector2i get_region_loc() const { return _region_loc; } + + // Maps + void set_height_map(const Ref &p_map); + Ref get_height_map() const { return _height_map; } + void set_control_map(const Ref &p_map) { _control_map = p_map; } + Ref get_control_map() const { return _control_map; } + void set_color_map(const Ref &p_map) { _color_map = p_map; } + Ref get_color_map() const { return _color_map; } + + // Instancer + void set_multimeshes(const Dictionary &p_multimeshes) { _multimeshes = p_multimeshes; } + Dictionary get_multimeshes() const { return _multimeshes; } + + // File I/O + Error save(const String &p_path = "", const bool p_16_bit = false); + +protected: + static void _bind_methods(); +}; + +#endif // TERRAIN3D_REGION_CLASS_H diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index 12cdeab4..814c0287 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -4,7 +4,6 @@ #include #include #include - #include #include #include @@ -20,10 +19,11 @@ void Terrain3DStorage::_clear() { LOG(INFO, "Clearing storage"); _region_map_dirty = true; _region_map.clear(); + _regions.clear(); _generated_height_maps.clear(); _generated_control_maps.clear(); _generated_color_maps.clear(); - set_multimeshes(Dictionary()); + set_multimeshes(Dictionary()); // Sends a signal to the instancer } /////////////////////////// @@ -31,19 +31,18 @@ void Terrain3DStorage::_clear() { /////////////////////////// void Terrain3DStorage::initialize(Terrain3D *p_terrain) { - if (p_terrain != nullptr) { - _terrain = p_terrain; - } else { + if (p_terrain == nullptr) { LOG(ERROR, "Initialization failed, p_terrain is null"); return; } LOG(INFO, "Initializing storage"); + bool initialized = _terrain != nullptr; + _terrain = p_terrain; _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); - update_maps(); // generate map arrays -} - -Terrain3DStorage::~Terrain3DStorage() { - _clear(); + _mesh_vertex_spacing = _terrain->get_mesh_vertex_spacing(); + if (!initialized && !_terrain->get_storage_directory().is_empty()) { + load_directory(_terrain->get_storage_directory()); + } } void Terrain3DStorage::set_height_range(const Vector2 &p_range) { @@ -53,6 +52,7 @@ void Terrain3DStorage::set_height_range(const Vector2 &p_range) { // TODO: _height_range should move to Terrain3D, or the next 3 functions should move // to individual Regions and AABBs are updated from loaded regions. +// modified might be edited only by the editor void Terrain3DStorage::update_heights(const real_t p_height) { if (p_height < _height_range.x) { _height_range.x = p_height; @@ -101,8 +101,12 @@ void Terrain3DStorage::add_edited_area(const AABB &p_area) { emit_signal("maps_edited", _edited_area); } +Ref Terrain3DStorage::get_region(const Vector2i &p_region_loc) { + return _regions.get(p_region_loc, Ref()); +} + void Terrain3DStorage::set_region_modified(const Vector2i &p_region_loc, const bool p_modified) { - Ref region = _regions.get(p_region_loc, Ref()); + Ref region = _regions.get(p_region_loc, Ref()); if (region.is_null()) { LOG(ERROR, "Region not found at: ", p_region_loc); return; @@ -111,7 +115,7 @@ void Terrain3DStorage::set_region_modified(const Vector2i &p_region_loc, const b } bool Terrain3DStorage::get_region_modified(const Vector2i &p_region_loc) const { - Ref region = _regions.get(p_region_loc, Ref()); + Ref region = _regions.get(p_region_loc, Ref()); if (region.is_null()) { LOG(ERROR, "Region not found at: ", p_region_loc); return false; @@ -136,10 +140,9 @@ void Terrain3DStorage::set_region_locations(const TypedArray &p_locati update_maps(); } -/** Returns a region location given a global position*/ +/** Returns a region location given a global position. No bounds checking*/ Vector2i Terrain3DStorage::get_region_location(const Vector3 &p_global_position) const { - IS_INIT_MESG("Storage not initialized", Vector2i()); - Vector3 descaled_position = p_global_position / _terrain->get_mesh_vertex_spacing(); + Vector3 descaled_position = p_global_position / _mesh_vertex_spacing; return Vector2i((Vector2(descaled_position.x, descaled_position.z) / real_t(_region_size)).floor()); } @@ -151,20 +154,6 @@ Vector2i Terrain3DStorage::get_region_location_from_id(const int p_region_id) co return _region_locations[p_region_id]; } -Vector2i Terrain3DStorage::get_region_location_from_string(const String &p_filename) const { - String working_string = p_filename.trim_suffix(".res"); - String y_str = working_string.right(3).replace("_", ""); - working_string = working_string.erase(working_string.length() - 3, 3); - String x_str = working_string.right(3).replace("_", ""); - if (!x_str.is_valid_int() || !y_str.is_valid_int()) { - LOG(ERROR, "Malformed filename at ", p_filename, ": got x ", x_str, " y ", y_str); - return Vector2i(INT32_MIN, INT32_MIN); - } - int x = x_str.to_int(); - int y = y_str.to_int(); - return Vector2i(x, y); -} - int Terrain3DStorage::get_region_id(const Vector3 &p_global_position) const { Vector2i region_loc = get_region_location(p_global_position); return get_region_id_from_location(region_loc); @@ -182,27 +171,6 @@ int Terrain3DStorage::get_region_id_from_location(const Vector2i &p_region_loc) return -1; } -String Terrain3DStorage::get_region_filename(const Vector2i &p_region_loc) { - const String POS_REGION_FORMAT = "_%02d"; - const String NEG_REGION_FORMAT = "%03d"; - String x_str, y_str; - if (p_region_loc.x >= 0) { - x_str = vformat(POS_REGION_FORMAT, p_region_loc.x); - } else { - x_str = vformat(NEG_REGION_FORMAT, p_region_loc.x); - } - if (p_region_loc.y >= 0) { - y_str = vformat(POS_REGION_FORMAT, p_region_loc.y); - } else { - y_str = vformat(NEG_REGION_FORMAT, p_region_loc.y); - } - return "terrain3d" + x_str + y_str + ".res"; -} - -String Terrain3DStorage::get_region_filename_from_id(const int p_region_id) const { - return get_region_filename(_region_locations[p_region_id]); -} - /** Adds a region to the terrain * Option to include an array of Images to use for maps * Map types are Height:0, Control:1, Color:2, defined in MapType @@ -224,7 +192,7 @@ Error Terrain3DStorage::add_region(const Vector3 &p_global_position, const Typed if (time - _last_region_bounds_error > 1000) { _last_region_bounds_error = time; LOG(ERROR, "Specified position outside of maximum region map size: +/-", - real_t((REGION_MAP_SIZE / 2) * _region_size) * _terrain->get_mesh_vertex_spacing()); + real_t((REGION_MAP_SIZE / 2) * _region_size) * _mesh_vertex_spacing); } return FAILED; } @@ -283,7 +251,7 @@ void Terrain3DStorage::remove_region(const Vector3 &p_global_position, const boo void Terrain3DStorage::remove_region_by_id(const int p_region_id, const bool p_update, const String &p_path) { ERR_FAIL_COND_MSG(p_region_id == -1 || p_region_id >= _region_locations.size(), "Region id out of bounds."); - String fname = get_region_filename_from_id(p_region_id); + String fname = Util::location_to_filename(_region_locations[p_region_id]); LOG(INFO, "Removing region at: ", p_region_id); _region_locations.remove_at(p_region_id); LOG(DEBUG, "Removed region_locations, new size: ", _region_locations.size()); @@ -367,26 +335,23 @@ void Terrain3DStorage::update_maps() { } } -void Terrain3DStorage::save_region(const String &p_dir, const int p_region_id, const bool p_16_bit) { - Vector2i region_loc = _region_locations[p_region_id]; - Ref region = _regions[region_loc]; - String fname = get_region_filename(region_loc); - String path = p_dir + String("/") + fname; - LOG(INFO, "Saving file: ", fname, " region id: ", p_region_id, " loc: ", region_loc); - region->save(path, p_16_bit); -} - -void Terrain3DStorage::load_region(const String &p_path, const int p_region_id) { - if (p_region_id < 0 || p_region_id >= _region_locations.size()) { - LOG(DEBUG, "Attempted to load a region with an invalid id: ", p_region_id); +void Terrain3DStorage::save_region(const String &p_dir, const Vector2i &p_region_loc, const bool p_16_bit) { + Ref region = _regions[p_region_loc]; + // looks like get isn't required + if (region.is_null()) { + LOG(ERROR, "No region at: ", p_region_loc); + // need to create it return; } - load_region_by_location(p_path, _region_locations[p_region_id]); + String fname = Util::location_to_filename(p_region_loc); + String path = p_dir + String("/") + fname; + LOG(INFO, "Saving file: ", fname, " location: ", p_region_loc); + region->save(path, p_16_bit); } -void Terrain3DStorage::load_region_by_location(const String &p_path, const Vector2i &p_region_loc) { +void Terrain3DStorage::load_region(const String &p_dir, const Vector2i &p_region_loc) { LOG(INFO, "Loading region from location ", p_region_loc); - String path = p_path + String("/") + get_region_filename(p_region_loc); + String path = p_dir + String("/") + Util::location_to_filename(p_region_loc); Ref region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE); if (region.is_null()) { @@ -409,7 +374,6 @@ void Terrain3DStorage::register_region(const Ref &p_region, con if (p_region->get_color_map().is_valid()) { maps[2] = p_region->get_color_map()->duplicate(); } - // Region will get freed afterwards // 3 - Add to region map add_region(global_position, maps); @@ -442,7 +406,7 @@ TypedArray Terrain3DStorage::get_regions_under_aabb(const AABB &p_aabb) { real_t y = start.y + godot::Math::min((real_t)(j * _region_size), size_y); int region_id = get_region_id(Vector3(x, 0, y)); LOG(INFO, "Region id ", region_id, " location ", Vector3(x, 0, y)); - // If found, append to array + // If found, push_back to array if (region_id != -1) { found.push_back(region_id); } @@ -600,7 +564,6 @@ TypedArray Terrain3DStorage::get_maps_copy(const MapType p_map_type, cons } void Terrain3DStorage::set_pixel(const MapType p_map_type, const Vector3 &p_global_position, const Color &p_pixel) { - IS_INIT_MESG("Storage not initialized", VOID); if (p_map_type < 0 || p_map_type >= TYPE_MAX) { LOG(ERROR, "Specified map type out of range"); return; @@ -611,15 +574,15 @@ void Terrain3DStorage::set_pixel(const MapType p_map_type, const Vector3 &p_glob } Vector2i region_loc = _region_locations[region_id]; Vector2i global_offset = region_loc * _region_size; - Vector3 descaled_pos = p_global_position / _terrain->get_mesh_vertex_spacing(); + Vector3 descaled_pos = p_global_position / _mesh_vertex_spacing; Vector2i img_pos = Vector2i(descaled_pos.x - global_offset.x, descaled_pos.z - global_offset.y); img_pos = img_pos.clamp(Vector2i(), Vector2i(_region_size - 1, _region_size - 1)); Ref map = get_map_region(p_map_type, region_id); map->set_pixelv(img_pos, p_pixel); + set_region_modified(region_loc, true); } Color Terrain3DStorage::get_pixel(const MapType p_map_type, const Vector3 &p_global_position) const { - IS_INIT_MESG("Storage not initialized", COLOR_NAN); if (p_map_type < 0 || p_map_type >= TYPE_MAX) { LOG(ERROR, "Specified map type out of range"); return COLOR_NAN; @@ -630,7 +593,7 @@ Color Terrain3DStorage::get_pixel(const MapType p_map_type, const Vector3 &p_glo } Vector2i region_loc = _region_locations[region_id]; Vector2i global_offset = region_loc * _region_size; - Vector3 descaled_pos = p_global_position / _terrain->get_mesh_vertex_spacing(); + Vector3 descaled_pos = p_global_position / _mesh_vertex_spacing; Vector2i img_pos = Vector2i(descaled_pos.x - global_offset.x, descaled_pos.z - global_offset.y); img_pos = img_pos.clamp(Vector2i(), Vector2i(_region_size - 1, _region_size - 1)); Ref map = get_map_region(p_map_type, region_id); @@ -638,18 +601,14 @@ Color Terrain3DStorage::get_pixel(const MapType p_map_type, const Vector3 &p_glo } real_t Terrain3DStorage::get_height(const Vector3 &p_global_position) const { - IS_INIT_MESG("Storage not initialized", NAN); if (is_hole(get_control(p_global_position))) { return NAN; } Vector3 pos = p_global_position; - real_t step = _terrain->get_mesh_vertex_spacing(); + const real_t &step = _mesh_vertex_spacing; pos.y = 0.f; // Round to nearest vertex - Vector3 pos_round = Vector3( - round_multiple(pos.x, step), - 0.f, - round_multiple(pos.z, step)); + Vector3 pos_round = Vector3(round_multiple(pos.x, step), 0.f, round_multiple(pos.z, step)); // If requested position is close to a vertex, return its height if ((pos - pos_round).length() < 0.01f) { return get_pixel(TYPE_HEIGHT, pos).r; @@ -678,41 +637,43 @@ real_t Terrain3DStorage::get_height(const Vector3 &p_global_position) const { * value of .3-.5, otherwise it's the base texture. **/ Vector3 Terrain3DStorage::get_texture_id(const Vector3 &p_global_position) const { - IS_INIT_MESG("Storage not initialized", Vector3(NAN, NAN, NAN);); + // Verify in a region int region_id = get_region_id(p_global_position); if (region_id < 0) { - // Not in a region return Vector3(NAN, NAN, NAN); } + + // Verify not in a hole float src = get_pixel(TYPE_CONTROL, p_global_position).r; // 32-bit float, not double/real if (is_hole(src)) { return Vector3(NAN, NAN, NAN); } - Ref t_material = _terrain->get_material(); - bool auto_enabled = t_material->get_auto_shader(); - bool control_auto = is_auto(src); - uint32_t base_id; - uint32_t overlay_id; - real_t blend; - // Autoshader is enabled, and is enabled at the current location. - if (auto_enabled && control_auto) { - real_t auto_slope = real_t(t_material->get_shader_param("auto_slope")) * 2.f - 1.f; - real_t auto_height_reduction = real_t(t_material->get_shader_param("auto_height_reduction")); - real_t height = get_height(p_global_position); - Vector3 normal = get_normal(p_global_position); - base_id = t_material->get_shader_param("auto_base_texture"); - overlay_id = t_material->get_shader_param("auto_overlay_texture"); - blend = CLAMP( - vec3_dot(Vector3(0.f, 1.f, 0.f), - normal * auto_slope * 2.f - Vector3(auto_slope, auto_slope, auto_slope)) - - auto_height_reduction * .01f * height, - 0.f, 1.f); - // Return control map values. - } else { - base_id = get_base(src); - overlay_id = get_overlay(src); - blend = real_t(get_blend(src)) / 255.0f; + + // If material available, autoshader enabled, and pixel set to auto + if (_terrain != nullptr) { + Ref t_material = _terrain->get_material(); + bool auto_enabled = t_material->get_auto_shader(); + bool control_auto = is_auto(src); + if (auto_enabled && control_auto) { + real_t auto_slope = real_t(t_material->get_shader_param("auto_slope")) * 2.f - 1.f; + real_t auto_height_reduction = real_t(t_material->get_shader_param("auto_height_reduction")); + real_t height = get_height(p_global_position); + Vector3 normal = get_normal(p_global_position); + uint32_t base_id = t_material->get_shader_param("auto_base_texture"); + uint32_t overlay_id = t_material->get_shader_param("auto_overlay_texture"); + real_t blend = CLAMP( + vec3_dot(Vector3(0.f, 1.f, 0.f), + normal * auto_slope * 2.f - Vector3(auto_slope, auto_slope, auto_slope)) - + auto_height_reduction * .01f * height, + 0.f, 1.f); + return Vector3(real_t(base_id), real_t(overlay_id), blend); + } } + + // Else, just get textures from control map + uint32_t base_id = get_base(src); + uint32_t overlay_id = get_overlay(src); + real_t blend = real_t(get_blend(src)) / 255.0f; return Vector3(real_t(base_id), real_t(overlay_id), blend); } @@ -809,7 +770,7 @@ TypedArray Terrain3DStorage::sanitize_maps(const MapType p_map_type, cons } void Terrain3DStorage::force_update_maps(const MapType p_map_type) { - LOG(INFO, "Regenerating maps of type: ", p_map_type); + LOG(DEBUG_CONT, "Regenerating maps of type: ", p_map_type); switch (p_map_type) { case TYPE_HEIGHT: _generated_height_maps.clear(); @@ -860,18 +821,10 @@ Dictionary Terrain3DStorage::get_multimeshes(const TypedArray &p_region_ids } } -void Terrain3DStorage::save(const String &p_dir) { +void Terrain3DStorage::save_directory(const String &p_dir) { LOG(INFO, "Saving data files to ", p_dir); - //if (!_modified.has(true)) { - // LOG(INFO, "Save requested, but terrain not modified. Skipping"); - // return; - //} - for (int i = 0; i < _height_maps.size(); i++) { - //if (!_modified[i]) { - // LOG(INFO, "Save requested, but region not modified. Skipping"); - // continue; - //} - save_region(p_dir, i, _terrain->get_save_16_bit()); + for (int i = 0; i < _region_locations.size(); i++) { + save_region(p_dir, _region_locations[i], _terrain->get_save_16_bit()); } } @@ -912,17 +865,16 @@ void Terrain3DStorage::import_images(const TypedArray &p_images, const Ve return; } - real_t vertex_spacing = _terrain->get_mesh_vertex_spacing(); - Vector3 descaled_position = p_global_position / vertex_spacing; + Vector3 descaled_position = p_global_position / _mesh_vertex_spacing; int max_dimension = _region_size * REGION_MAP_SIZE / 2; if ((abs(descaled_position.x) > max_dimension) || (abs(descaled_position.z) > max_dimension)) { - LOG(ERROR, "Specify a position within +/-", Vector3(max_dimension, 0.f, max_dimension) * vertex_spacing); + LOG(ERROR, "Specify a position within +/-", Vector3(max_dimension, 0.f, max_dimension) * _mesh_vertex_spacing); return; } if ((descaled_position.x + img_size.x > max_dimension) || (descaled_position.z + img_size.y > max_dimension)) { LOG(ERROR, img_size, " image will not fit at ", p_global_position, - ". Try ", -(img_size * vertex_spacing) / 2.f, " to center"); + ". Try ", -(img_size * _mesh_vertex_spacing) / 2.f, " to center"); return; } @@ -989,7 +941,7 @@ void Terrain3DStorage::import_images(const TypedArray &p_images, const Ve } // Add the heightmap slice and only regenerate on the last one Vector3 position = Vector3(descaled_position.x + start_coords.x, 0.f, descaled_position.z + start_coords.y); - add_region(position * vertex_spacing, images, (x == slices_width - 1 && y == slices_height - 1)); + add_region(position * _mesh_vertex_spacing, images, (x == slices_width - 1 && y == slices_height - 1)); } } // for y < slices_height, x < slices_width } @@ -1156,12 +1108,13 @@ void Terrain3DStorage::load_directory(const String &p_dir) { LOG(ERROR, "Region file ", path, " failed to load"); continue; } - Vector2i loc = get_region_location_from_string(fname); - if (loc == Vector2i(INT32_MIN, INT32_MIN)) { + Vector2i loc = Util::filename_to_location(fname); + if (loc.x == INT32_MAX) { LOG(ERROR, "Cannot get region location from file name: ", fname); continue; } LOG(DEBUG, "Region version: ", region->get_version(), " location: ", loc); + region->set_region_loc(loc); region->set_version(CURRENT_VERSION); // Sends upgrade warning if old version register_region(region, loc); } @@ -1179,7 +1132,6 @@ void Terrain3DStorage::load_directory(const String &p_dir) { * p_global_position: X and Z coordinates of the vertex. Heights will be sampled around these coordinates. */ Vector3 Terrain3DStorage::get_mesh_vertex(const int32_t p_lod, const HeightFilter p_filter, const Vector3 &p_global_position) const { - IS_INIT_MESG("Storage not initialized", Vector3()); LOG(INFO, "Calculating vertex location"); int32_t step = 1 << CLAMP(p_lod, 0, 8); real_t height = 0.0f; @@ -1196,7 +1148,7 @@ Vector3 Terrain3DStorage::get_mesh_vertex(const int32_t p_lod, const HeightFilte height = get_height(p_global_position); for (int32_t dx = -step / 2; dx < step / 2; dx += 1) { for (int32_t dz = -step / 2; dz < step / 2; dz += 1) { - Vector3 position = p_global_position + Vector3(dx, 0.f, dz) * _terrain->get_mesh_vertex_spacing(); + Vector3 position = p_global_position + Vector3(dx, 0.f, dz) * _mesh_vertex_spacing; if (is_hole(get_control(position))) { height = NAN; break; @@ -1213,15 +1165,13 @@ Vector3 Terrain3DStorage::get_mesh_vertex(const int32_t p_lod, const HeightFilte } Vector3 Terrain3DStorage::get_normal(const Vector3 &p_global_position) const { - IS_INIT_MESG("Storage not initialized", Vector3()); if (get_region_id(p_global_position) < 0 || is_hole(get_control(p_global_position))) { return Vector3(NAN, NAN, NAN); } - real_t vertex_spacing = _terrain->get_mesh_vertex_spacing(); real_t height = get_height(p_global_position); - real_t u = height - get_height(p_global_position + Vector3(vertex_spacing, 0.0f, 0.0f)); - real_t v = height - get_height(p_global_position + Vector3(0.f, 0.f, vertex_spacing)); - Vector3 normal = Vector3(u, vertex_spacing, v); + real_t u = height - get_height(p_global_position + Vector3(_mesh_vertex_spacing, 0.0f, 0.0f)); + real_t v = height - get_height(p_global_position + Vector3(0.f, 0.f, _mesh_vertex_spacing)); + Vector3 normal = Vector3(u, _mesh_vertex_spacing, v); normal.normalize(); return normal; } @@ -1280,17 +1230,15 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("get_region_id", "global_position"), &Terrain3DStorage::get_region_id); ClassDB::bind_method(D_METHOD("get_region_id_from_location", "region_location"), &Terrain3DStorage::get_region_id_from_location); ClassDB::bind_method(D_METHOD("has_region", "global_position"), &Terrain3DStorage::has_region); - ClassDB::bind_method(D_METHOD("add_region", "global_position", "images", "update"), &Terrain3DStorage::add_region, DEFVAL(TypedArray()), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("add_region", "global_position", "images", "update", "path"), &Terrain3DStorage::add_region, + DEFVAL(TypedArray()), DEFVAL(true), DEFVAL("")); ClassDB::bind_method(D_METHOD("remove_region", "global_position", "update"), &Terrain3DStorage::remove_region, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("save_region", "path", "region_id"), &Terrain3DStorage::save_region); - ClassDB::bind_method(D_METHOD("load_region", "path", "region_id"), &Terrain3DStorage::load_region); - ClassDB::bind_method(D_METHOD("load_region_by_location", "path", "region_location"), &Terrain3DStorage::load_region_by_location); - ClassDB::bind_method(D_METHOD("register_region", "region", "region_location"), &Terrain3DStorage::register_region); - ClassDB::bind_method(D_METHOD("get_region_location_from_string", "filename"), &Terrain3DStorage::get_region_location_from_string); - ClassDB::bind_static_method("Terrain3DStorage", D_METHOD("get_region_filename", "region_location"), &Terrain3DStorage::get_region_filename); - ClassDB::bind_method(D_METHOD("get_region_filename_from_id", "region_id"), &Terrain3DStorage::get_region_filename_from_id); + ClassDB::bind_method(D_METHOD("save_directory", "directory"), &Terrain3DStorage::save_directory); ClassDB::bind_method(D_METHOD("load_directory", "directory"), &Terrain3DStorage::load_directory); + ClassDB::bind_method(D_METHOD("save_region", "directory", "region_location"), &Terrain3DStorage::save_region); + ClassDB::bind_method(D_METHOD("load_region", "directory", "region_location"), &Terrain3DStorage::load_region); + ClassDB::bind_method(D_METHOD("register_region", "region", "region_location"), &Terrain3DStorage::register_region); ClassDB::bind_method(D_METHOD("set_map_region", "map_type", "region_id", "image"), &Terrain3DStorage::set_map_region); ClassDB::bind_method(D_METHOD("get_map_region", "map_type", "region_id"), &Terrain3DStorage::get_map_region); @@ -1324,7 +1272,6 @@ void Terrain3DStorage::_bind_methods() { //ClassDB::bind_method(D_METHOD("set_multimeshes", "multimeshes"), &Terrain3DStorage::set_multimeshes); //ClassDB::bind_method(D_METHOD("get_multimeshes"), &Terrain3DStorage::get_multimeshes); - ClassDB::bind_method(D_METHOD("save", "path"), &Terrain3DStorage::save); ClassDB::bind_method(D_METHOD("import_images", "images", "global_position", "offset", "scale"), &Terrain3DStorage::import_images, DEFVAL(Vector3(0, 0, 0)), DEFVAL(0.0), DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("export_image", "file_name", "map_type"), &Terrain3DStorage::export_image); ClassDB::bind_method(D_METHOD("layered_to_image", "map_type"), &Terrain3DStorage::layered_to_image); @@ -1351,90 +1298,3 @@ void Terrain3DStorage::_bind_methods() { ADD_SIGNAL(MethodInfo("maps_edited", PropertyInfo(Variant::AABB, "edited_area"))); ADD_SIGNAL(MethodInfo("multimeshes_changed")); } - -///////////////////// -// Terrain3DRegion -///////////////////// - -void Terrain3DStorage::Terrain3DRegion::set_version(const real_t p_version) { - LOG(INFO, vformat("%.3f", p_version)); - _version = p_version; - if (_version < CURRENT_VERSION) { - LOG(WARN, "Region ", get_path(), " version ", vformat("%.3f", _version), " will be updated to ", vformat("%.3f", CURRENT_VERSION), " upon save"); - } -} - -void Terrain3DStorage::Terrain3DRegion::set_height_map(const Ref &p_map) { - if (p_map.is_valid() && p_map->get_format() != FORMAT[TYPE_HEIGHT]) { - LOG(DEBUG, "Converting file format: ", p_map->get_format(), " to ", FORMAT[TYPE_HEIGHT]); - if (_height_map.is_null()) { - _height_map.instantiate(); - } - _height_map->copy_from(p_map); - _height_map->convert(FORMAT[TYPE_HEIGHT]); - } else { - _height_map = p_map; - } -} - -Error Terrain3DStorage::Terrain3DRegion::save(const String &p_path, const bool p_16_bit) { - // Initiate save to external file. The scene will save itself. - if (!_modified) { - LOG(INFO, "Save requested, but not modified. Skipping - but not really rn"); - //return; - } - if (p_path.is_empty() && get_path().is_empty()) { - LOG(ERROR, "No valid path provided"); - return ERR_FILE_NOT_FOUND; - } - String path = p_path; - if (path.is_empty()) { - path = get_path(); - } - set_version(CURRENT_VERSION); - LOG(INFO, "Writing", (p_16_bit) ? " 16-bit" : "", " region ", _region_loc, " to ", path); - - Error err; - if (p_16_bit) { - Ref original_map; - original_map.instantiate(); - original_map->copy_from(_height_map); - _height_map->convert(Image::FORMAT_RH); - err = ResourceSaver::get_singleton()->save(this, path, ResourceSaver::FLAG_COMPRESS); - _height_map = original_map; - } else { - err = ResourceSaver::get_singleton()->save(this, path, ResourceSaver::FLAG_COMPRESS); - } - if (err == OK) { - _modified = false; - LOG(INFO, "File saved successfully"); - } else { - LOG(ERROR, "Can't save region file: ", path, ". Error code: ", ERROR, ". Look up @GlobalScope Error enum in the Godot docs"); - } - return err; -} - -void Terrain3DStorage::Terrain3DRegion::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_modified"), &Terrain3DStorage::Terrain3DRegion::get_modified); - ClassDB::bind_method(D_METHOD("is_modified"), &Terrain3DStorage::Terrain3DRegion::is_modified); - ClassDB::bind_method(D_METHOD("get_version"), &Terrain3DStorage::Terrain3DRegion::get_version); - - ClassDB::bind_method(D_METHOD("set_height_map", "map"), &Terrain3DStorage::Terrain3DRegion::set_height_map); - ClassDB::bind_method(D_METHOD("get_height_map"), &Terrain3DStorage::Terrain3DRegion::get_height_map); - ClassDB::bind_method(D_METHOD("set_control_map", "map"), &Terrain3DStorage::Terrain3DRegion::set_control_map); - ClassDB::bind_method(D_METHOD("get_control_map"), &Terrain3DStorage::Terrain3DRegion::get_control_map); - ClassDB::bind_method(D_METHOD("set_color_map", "map"), &Terrain3DStorage::Terrain3DRegion::set_color_map); - ClassDB::bind_method(D_METHOD("get_color_map"), &Terrain3DStorage::Terrain3DRegion::get_color_map); - ClassDB::bind_method(D_METHOD("set_multimeshes", "multimeshes"), &Terrain3DStorage::Terrain3DRegion::set_multimeshes); - ClassDB::bind_method(D_METHOD("get_multimeshes"), &Terrain3DStorage::Terrain3DRegion::get_multimeshes); - - ClassDB::bind_method(D_METHOD("save", "path", "16-bit"), &Terrain3DStorage::Terrain3DRegion::save, DEFVAL(""), DEFVAL(false)); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "modified", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY), "", "is_modified"); - int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "version", PROPERTY_HINT_NONE, "", ro_flags), "", "get_version"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "heightmap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_height_map", "get_height_map"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "controlmap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_control_map", "get_control_map"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "colormap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_color_map", "get_color_map"); - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "multimeshes", PROPERTY_HINT_NONE, "", ro_flags), "set_multimeshes", "get_multimeshes"); -} diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index 4d7e493f..f6220d69 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -3,11 +3,9 @@ #ifndef TERRAIN3D_STORAGE_CLASS_H #define TERRAIN3D_STORAGE_CLASS_H -#include -#include - #include "constants.h" #include "generated_texture.h" +#include "terrain_3d_region.h" #include "terrain_3d_util.h" class Terrain3D; @@ -17,6 +15,7 @@ using namespace godot; class Terrain3DStorage : public Object { GDCLASS(Terrain3DStorage, Object); CLASS_NAME(); + friend Terrain3D; public: // Constants static inline const real_t CURRENT_VERSION = 0.92f; @@ -65,64 +64,14 @@ class Terrain3DStorage : public Object { HEIGHT_FILTER_MINIMUM }; - /////////////////////////// - // Terrain3DRegion - /////////////////////////// - - class Terrain3DRegion : public Resource { - GDCLASS(Terrain3DRegion, Resource); - CLASS_NAME(); - - private: - bool _modified = false; - real_t _version = 0.8f; // Set to first version to ensure Godot always upgrades this - Vector2i _region_loc = Vector2i(INT32_MIN, INT32_MIN); - - // Maps - Ref _height_map; - Ref _control_map; - Ref _color_map; - // Instancer MultiMeshes saved to disk - // Dictionary[mesh_id:int] -> MultiMesh - Dictionary _multimeshes; - - public: - void set_modified(const bool p_modified) { _modified = p_modified; } - bool get_modified() const { return _modified; } - bool is_modified() const { return _modified == true; } - void set_version(const real_t p_version); - real_t get_version() const { return _version; } - void set_region_loc(const Vector2i &p_region_loc) { _region_loc = p_region_loc; } - Vector2i get_region_loc() const { return _region_loc; } - - // Maps - void set_height_map(const Ref &p_map); - Ref get_height_map() const { return _height_map; } - void set_control_map(const Ref &p_map) { _control_map = p_map; } - Ref get_control_map() const { return _control_map; } - void set_color_map(const Ref &p_map) { _color_map = p_map; } - Ref get_color_map() const { return _color_map; } - - // Instancer - void set_multimeshes(const Dictionary &p_multimeshes) { _multimeshes = p_multimeshes; } - Dictionary get_multimeshes() const { return _multimeshes; } - - // File I/O - Error save(const String &p_path = "", const bool p_16_bit = false); - - protected: - static void _bind_methods(); - }; - - /////////////////////////// - private: Terrain3D *_terrain = nullptr; // Storage Settings & flags RegionSize _region_size = SIZE_1024; Vector2i _region_sizev = Vector2i(_region_size, _region_size); - bool _loading = false; // I am a little hesitant to include state like this. + real_t _mesh_vertex_spacing = 1.f; // Set by Terrain3D::set_mesh_vertex_spacing + bool _loading = false; // tracking when we're loading so we don't add_region w/ update // TODO: Should be in Terrain3D so its saved Vector2 _height_range = Vector2(0.f, 0.f); @@ -150,6 +99,7 @@ class Terrain3DStorage : public Object { TypedArray _control_maps; TypedArray _color_maps; + // Dictionary[region_location:Vector2i] -> Terrain3DRegion Dictionary _regions; // Foliage Instancer contains MultiMeshes saved to disk @@ -163,7 +113,7 @@ class Terrain3DStorage : public Object { public: Terrain3DStorage() {} void initialize(Terrain3D *p_terrain); - ~Terrain3DStorage(); + ~Terrain3DStorage() { _clear(); } void set_height_range(const Vector2 &p_range); Vector2 get_height_range() const { return _height_range; } @@ -177,6 +127,7 @@ class Terrain3DStorage : public Object { // Regions Dictionary get_regions() { return _regions; } + Ref get_region(const Vector2i &p_region_loc); void set_region_modified(const Vector2i &p_region_loc, const bool p_modified = true); bool get_region_modified(const Vector2i &p_region_loc) const; @@ -189,11 +140,8 @@ class Terrain3DStorage : public Object { int get_region_count() const { return _region_locations.size(); } Vector2i get_region_location(const Vector3 &p_global_position) const; Vector2i get_region_location_from_id(const int p_region_id) const; - Vector2i get_region_location_from_string(const String &p_filename) const; int get_region_id(const Vector3 &p_global_position) const; int get_region_id_from_location(const Vector2i &p_region_loc) const; - static String get_region_filename(const Vector2i &p_region_loc); - String get_region_filename_from_id(const int p_region_id) const; bool has_region(const Vector3 &p_global_position) const { return get_region_id(p_global_position) != -1; } Error add_region(const Vector3 &p_global_position, const TypedArray &p_images = TypedArray(), @@ -241,12 +189,11 @@ class Terrain3DStorage : public Object { Dictionary get_multimeshes(const TypedArray &p_region_ids) const; // File I/O - void save(const String &p_dir); + void save_directory(const String &p_dir); void load_directory(const String &p_dir); - void save_region(const String &p_dir, const int p_region_id, const bool p_16_bit = false); - void load_region(const String &p_dir, const int p_region_id); - void load_region_by_location(const String &p_path, const Vector2i &p_region_loc); + void save_region(const String &p_dir, const Vector2i &p_region_loc, const bool p_16_bit = false); + void load_region(const String &p_dir, const Vector2i &p_region_loc); void register_region(const Ref &p_region, const Vector2i &p_region_loc); TypedArray get_regions_under_aabb(const AABB &p_aabb); diff --git a/src/terrain_3d_util.cpp b/src/terrain_3d_util.cpp index 85d5599a..f31fed42 100644 --- a/src/terrain_3d_util.cpp +++ b/src/terrain_3d_util.cpp @@ -30,6 +30,28 @@ void Terrain3DUtil::dump_maps(const TypedArray &p_maps, const String &p_n } } +// Expects a filename in a String like: "terrain3d-01_02.res" which returns (-1, 2) +Vector2i Terrain3DUtil::filename_to_location(const String &p_filename) { + String working_string = p_filename.trim_suffix(".res"); + String y_str = working_string.right(3).replace("_", ""); + working_string = working_string.erase(working_string.length() - 3, 3); + String x_str = working_string.right(3).replace("_", ""); + if (!x_str.is_valid_int() || !y_str.is_valid_int()) { + LOG(ERROR, "Malformed filename at ", p_filename, ": got x ", x_str, " y ", y_str); + return Vector2i(INT32_MAX, INT32_MAX); + } + return Vector2i(x_str.to_int(), y_str.to_int()); +} + +String Terrain3DUtil::location_to_filename(const Vector2i &p_region_loc) { + const String POS_REGION_FORMAT = "_%02d"; + const String NEG_REGION_FORMAT = "%03d"; + String x_str, y_str; + x_str = vformat((p_region_loc.x >= 0) ? POS_REGION_FORMAT : NEG_REGION_FORMAT, p_region_loc.x); + y_str = vformat((p_region_loc.y >= 0) ? POS_REGION_FORMAT : NEG_REGION_FORMAT, p_region_loc.y); + return "terrain3d" + x_str + y_str + ".res"; +} + Ref Terrain3DUtil::black_to_alpha(const Ref &p_image) { if (p_image.is_null()) { return Ref(); @@ -321,6 +343,10 @@ void Terrain3DUtil::_bind_methods() { ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("get_uv_scale", "pixel"), &gd_get_uv_scale); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("enc_uv_scale", "scale"), &gd_enc_uv_scale); + // String functions + ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("filename_to_location", "filename"), &Terrain3DUtil::filename_to_location); + ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("location_to_filename", "region_location"), &Terrain3DUtil::location_to_filename); + // Image handling ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("black_to_alpha", "image"), &Terrain3DUtil::black_to_alpha); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("get_min_max", "image"), &Terrain3DUtil::get_min_max); diff --git a/src/terrain_3d_util.h b/src/terrain_3d_util.h index 9e32f65b..22b0645f 100644 --- a/src/terrain_3d_util.h +++ b/src/terrain_3d_util.h @@ -4,12 +4,18 @@ #define TERRAIN3D_UTIL_CLASS_H #include +#include #include "constants.h" #include "generated_texture.h" using namespace godot; +// This file holds stateless utility functions for both C++ and GDScript +// The class exposes static member and inline functions to GDscript +// The inline functions below are not part of the class eg bilerp +// However some of these inline functions are also exposed to GDScript + class Terrain3DUtil : public Object { GDCLASS(Terrain3DUtil, Object); CLASS_NAME_STATIC("Terrain3DUtil"); @@ -20,6 +26,10 @@ class Terrain3DUtil : public Object { static void dump_gentex(const GeneratedTexture p_gen, const String &name = "", const int p_level = 2); static void dump_maps(const TypedArray &p_maps, const String &p_name = ""); + // String functions + static Vector2i filename_to_location(const String &p_filename); + static String location_to_filename(const Vector2i &p_region_loc); + // Image operations static Ref black_to_alpha(const Ref &p_image); static Vector2 get_min_max(const Ref &p_image); @@ -44,6 +54,7 @@ typedef Terrain3DUtil Util; // Math /////////////////////////// +// Rounds a decimal to the nearest multiple eg round_multiple(2.7, 4) -> 4 template T round_multiple(const T p_value, const T p_multiple) { if (p_multiple == 0) { From e2b7d1ae53eef355dcd60955b9f16aeb8bccf7b8 Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Sun, 28 Jul 2024 20:52:34 +0700 Subject: [PATCH 08/19] Organize region --- src/terrain_3d.h | 2 +- src/terrain_3d_region.cpp | 14 ++++++++------ src/terrain_3d_region.h | 18 +++++++++++------- src/terrain_3d_storage.cpp | 26 +++++++++++++------------- src/terrain_3d_storage.h | 6 +++--- 5 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/terrain_3d.h b/src/terrain_3d.h index af9f345b..f7e956f8 100644 --- a/src/terrain_3d.h +++ b/src/terrain_3d.h @@ -173,7 +173,7 @@ class Terrain3D : public Node3D { Ref bake_mesh(const int p_lod, const Terrain3DStorage::HeightFilter p_filter = Terrain3DStorage::HEIGHT_FILTER_NEAREST) const; PackedVector3Array generate_nav_mesh_source_geometry(const AABB &p_global_aabb, const bool p_require_nav = true) const; - // Misc + // Godot Callbacks PackedStringArray _get_configuration_warnings() const override; // DEPRECATED 0.9.2 - Remove 0.9.3+ diff --git a/src/terrain_3d_region.cpp b/src/terrain_3d_region.cpp index 26ca95ac..14ecca88 100644 --- a/src/terrain_3d_region.cpp +++ b/src/terrain_3d_region.cpp @@ -47,7 +47,7 @@ Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) { path = get_path(); } set_version(Terrain3DStorage::CURRENT_VERSION); - LOG(INFO, "Writing", (p_16_bit) ? " 16-bit" : "", " region ", _region_loc, " to ", path); + LOG(INFO, "Writing", (p_16_bit) ? " 16-bit" : "", " region ", _location, " to ", path); Error err; if (p_16_bit) { @@ -76,8 +76,6 @@ Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) { void Terrain3DRegion::_bind_methods() { ClassDB::bind_method(D_METHOD("set_version"), &Terrain3DRegion::set_version); ClassDB::bind_method(D_METHOD("get_version"), &Terrain3DRegion::get_version); - ClassDB::bind_method(D_METHOD("is_modified"), &Terrain3DRegion::is_modified); - ClassDB::bind_method(D_METHOD("get_region_loc"), &Terrain3DRegion::get_region_loc); ClassDB::bind_method(D_METHOD("set_height_map", "map"), &Terrain3DRegion::set_height_map); ClassDB::bind_method(D_METHOD("get_height_map"), &Terrain3DRegion::get_height_map); @@ -90,9 +88,9 @@ void Terrain3DRegion::_bind_methods() { ClassDB::bind_method(D_METHOD("save", "path", "16-bit"), &Terrain3DRegion::save, DEFVAL(""), DEFVAL(false)); - // These two show what's on the disk (defaults) not what is in memory, so hide them - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "modified", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "is_modified"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "location", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_region_loc"); + ClassDB::bind_method(D_METHOD("is_modified"), &Terrain3DRegion::is_modified); + ClassDB::bind_method(D_METHOD("set_location"), &Terrain3DRegion::set_location); + ClassDB::bind_method(D_METHOD("get_location"), &Terrain3DRegion::get_location); int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "version", PROPERTY_HINT_NONE, "", ro_flags), "set_version", "get_version"); @@ -100,4 +98,8 @@ void Terrain3DRegion::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "controlmap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_control_map", "get_control_map"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "colormap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_color_map", "get_color_map"); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "multimeshes", PROPERTY_HINT_NONE, "", ro_flags), "set_multimeshes", "get_multimeshes"); + + // The inspector only shows what's on disk, not what is in memory, so hide them. Being generated, they only show defaults + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "modified", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "is_modified"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "location", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_location", "get_location"); } diff --git a/src/terrain_3d_region.h b/src/terrain_3d_region.h index 557dae16..644c9d27 100644 --- a/src/terrain_3d_region.h +++ b/src/terrain_3d_region.h @@ -12,24 +12,22 @@ class Terrain3DRegion : public Resource { CLASS_NAME(); private: + // Saved data real_t _version = 0.8f; // Set to first version to ensure Godot always upgrades this - bool _modified = false; - Vector2i _region_loc = Vector2i(INT32_MIN, INT32_MIN); // Maps Ref _height_map; Ref _control_map; Ref _color_map; - // Instancer MultiMeshes saved to disk // Dictionary[mesh_id:int] -> MultiMesh Dictionary _multimeshes; + // Workind data not saved to disk + Vector2i _location = Vector2i(INT32_MAX, INT32_MAX); + bool _modified = false; + public: void set_version(const real_t p_version); real_t get_version() const { return _version; } - void set_modified(const bool p_modified) { _modified = p_modified; } - bool is_modified() const { return _modified; } - void set_region_loc(const Vector2i &p_region_loc) { _region_loc = p_region_loc; } - Vector2i get_region_loc() const { return _region_loc; } // Maps void set_height_map(const Ref &p_map); @@ -46,6 +44,12 @@ class Terrain3DRegion : public Resource { // File I/O Error save(const String &p_path = "", const bool p_16_bit = false); + // Working + void set_modified(const bool p_modified) { _modified = p_modified; } + bool is_modified() const { return _modified; } + void set_location(const Vector2i &p_location) { _location = p_location; } + Vector2i get_location() const { return _location; } + protected: static void _bind_methods(); }; diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index 814c0287..cfee2c3c 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -358,29 +358,28 @@ void Terrain3DStorage::load_region(const String &p_dir, const Vector2i &p_region LOG(ERROR, "Could not load region at ", path, " at location", p_region_loc); return; } - register_region(region, p_region_loc); + register_region(region); } -void Terrain3DStorage::register_region(const Ref &p_region, const Vector2i &p_region_loc) { - Vector3 global_position = Vector3(_region_size * p_region_loc.x, 0.0f, _region_size * p_region_loc.y); +void Terrain3DStorage::register_region(const Ref &p_region) { + Vector3 global_position = Vector3(p_region->get_location().x, 0.0f, p_region->get_location().y) * _region_size; TypedArray maps = TypedArray(); maps.resize(3); if (p_region->get_height_map().is_valid()) { - maps[0] = p_region->get_height_map()->duplicate(); + maps[0] = p_region->get_height_map(); } if (p_region->get_control_map().is_valid()) { - maps[1] = p_region->get_control_map()->duplicate(); + maps[1] = p_region->get_control_map(); } if (p_region->get_color_map().is_valid()) { - maps[2] = p_region->get_color_map()->duplicate(); + maps[2] = p_region->get_color_map(); } // 3 - Add to region map add_region(global_position, maps); // Store region - p_region->set_region_loc(p_region_loc); - LOG(INFO, "Registered region ", p_region->get_path(), " at ", p_region->get_region_loc(), " - ", p_region_loc); - _regions[p_region_loc] = p_region; + LOG(INFO, "Registered region ", p_region->get_path(), " at ", p_region->get_location()); + _regions[p_region->get_location()] = p_region; } TypedArray Terrain3DStorage::get_regions_under_aabb(const AABB &p_aabb) { @@ -1090,10 +1089,11 @@ void Terrain3DStorage::load_directory(const String &p_dir) { LOG(ERROR, "Cannot read Terrain3D data directory: ", p_dir); return; } - LOG(INFO, "Loading region files from ", p_dir); _clear(); _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); - _loading = true; + _loading = true; // don't update until done + + LOG(INFO, "Loading region files from ", p_dir); PackedStringArray files = da->get_files(); for (int i = 0; i < files.size(); i++) { String fname = files[i]; @@ -1114,9 +1114,9 @@ void Terrain3DStorage::load_directory(const String &p_dir) { continue; } LOG(DEBUG, "Region version: ", region->get_version(), " location: ", loc); - region->set_region_loc(loc); + region->set_location(loc); region->set_version(CURRENT_VERSION); // Sends upgrade warning if old version - register_region(region, loc); + register_region(region); } _loading = false; force_update_maps(); diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index f6220d69..9bbe75ba 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -73,7 +73,7 @@ class Terrain3DStorage : public Object { real_t _mesh_vertex_spacing = 1.f; // Set by Terrain3D::set_mesh_vertex_spacing bool _loading = false; // tracking when we're loading so we don't add_region w/ update - // TODO: Should be in Terrain3D so its saved + // TODO: Should be in Terrain3D or per region so its saved Vector2 _height_range = Vector2(0.f, 0.f); // Work data @@ -191,10 +191,10 @@ class Terrain3DStorage : public Object { // File I/O void save_directory(const String &p_dir); void load_directory(const String &p_dir); - void save_region(const String &p_dir, const Vector2i &p_region_loc, const bool p_16_bit = false); void load_region(const String &p_dir, const Vector2i &p_region_loc); - void register_region(const Ref &p_region, const Vector2i &p_region_loc); + + void register_region(const Ref &p_region); TypedArray get_regions_under_aabb(const AABB &p_aabb); void import_images(const TypedArray &p_images, const Vector3 &p_global_position = Vector3(0.f, 0.f, 0.f), From 2ecbf99986db13c4bc1ade1aa9243438a25bbe3c Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Mon, 29 Jul 2024 12:26:50 +0700 Subject: [PATCH 09/19] Inline get_region_location/id, API changes --- project/addons/terrain_3d/editor.gd | 2 +- src/terrain_3d.cpp | 6 +-- src/terrain_3d_editor.cpp | 12 +++--- src/terrain_3d_instancer.cpp | 4 +- src/terrain_3d_storage.cpp | 62 ++++++++--------------------- src/terrain_3d_storage.h | 49 +++++++++++++++++++---- 6 files changed, 69 insertions(+), 66 deletions(-) diff --git a/project/addons/terrain_3d/editor.gd b/project/addons/terrain_3d/editor.gd index facee9ab..ec32da2e 100644 --- a/project/addons/terrain_3d/editor.gd +++ b/project/addons/terrain_3d/editor.gd @@ -213,7 +213,7 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) -> # If adjusting regions if editor.get_tool() == Terrain3DEditor.REGION: # Skip regions that already exist or don't - var has_region: bool = terrain.get_storage().has_region(mouse_global_position) + var has_region: bool = terrain.get_storage().has_regionp(mouse_global_position) var op: int = editor.get_operation() if ( has_region and op == Terrain3DEditor.ADD) or \ ( not has_region and op == Terrain3DEditor.SUBTRACT ): diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index 6be6a2f9..035ef16a 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -415,17 +415,17 @@ void Terrain3D::_update_collision() { Ref cmap, cmap_x, cmap_z, cmap_xz; map = _storage->get_map_region(TYPE_HEIGHT, i); cmap = _storage->get_map_region(TYPE_CONTROL, i); - int region_id = _storage->get_region_id(Vector3(global_pos.x + region_size, 0.f, global_pos.z) * _mesh_vertex_spacing); + int region_id = _storage->get_region_idp(Vector3(global_pos.x + region_size, 0.f, global_pos.z) * _mesh_vertex_spacing); if (region_id >= 0) { map_x = _storage->get_map_region(TYPE_HEIGHT, region_id); cmap_x = _storage->get_map_region(TYPE_CONTROL, region_id); } - region_id = _storage->get_region_id(Vector3(global_pos.x, 0.f, global_pos.z + region_size) * _mesh_vertex_spacing); + region_id = _storage->get_region_idp(Vector3(global_pos.x, 0.f, global_pos.z + region_size) * _mesh_vertex_spacing); if (region_id >= 0) { map_z = _storage->get_map_region(TYPE_HEIGHT, region_id); cmap_z = _storage->get_map_region(TYPE_CONTROL, region_id); } - region_id = _storage->get_region_id(Vector3(global_pos.x + region_size, 0.f, global_pos.z + region_size) * _mesh_vertex_spacing); + region_id = _storage->get_region_idp(Vector3(global_pos.x + region_size, 0.f, global_pos.z + region_size) * _mesh_vertex_spacing); if (region_id >= 0) { map_xz = _storage->get_map_region(TYPE_HEIGHT, region_id); cmap_xz = _storage->get_map_region(TYPE_CONTROL, region_id); diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp index f2df2fea..1902b794 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -27,7 +27,7 @@ void Terrain3DEditor::_region_modified(const Vector3 &p_global_position, const V } void Terrain3DEditor::_operate_region(const Vector3 &p_global_position) { - bool has_region = _terrain->get_storage()->has_region(p_global_position); + bool has_region = _terrain->get_storage()->has_regionp(p_global_position); bool changed = false; Vector2 height_range; @@ -38,7 +38,7 @@ void Terrain3DEditor::_operate_region(const Vector3 &p_global_position) { } } else { if (has_region) { - int region_id = _terrain->get_storage()->get_region_id(p_global_position); + int region_id = _terrain->get_storage()->get_region_idp(p_global_position); Ref height_map = _terrain->get_storage()->get_map_region(TYPE_HEIGHT, region_id); height_range = Util::get_min_max(height_map); @@ -57,7 +57,7 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ Terrain3DStorage *storage = _terrain->get_storage(); int region_size = storage->get_region_size(); Vector2i region_vsize = Vector2i(region_size, region_size); - int region_id = storage->get_region_id(p_global_position); + int region_id = storage->get_region_idp(p_global_position); if (region_id == -1) { if (!_brush_data["auto_regions"] || _tool != HEIGHT) { return; @@ -65,7 +65,7 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ LOG(DEBUG, "No region to operate on, attempting to add"); storage->add_region(p_global_position); region_size = storage->get_region_size(); - region_id = storage->get_region_id(p_global_position); + region_id = storage->get_region_idp(p_global_position); if (region_id == -1) { LOG(ERROR, "Failed to add region, no region to operate on"); return; @@ -173,7 +173,7 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ p_global_position.z + brush_offset.y + .5f); // If we're brushing across a region boundary, possibly add a region, and get the other map - int new_region_id = storage->get_region_id(brush_global_position); + int new_region_id = storage->get_region_idp(brush_global_position); if (new_region_id == -1) { if (!_brush_data["auto_regions"] || _tool != HEIGHT) { continue; @@ -182,7 +182,7 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ if (err) { continue; } - new_region_id = storage->get_region_id(brush_global_position); + new_region_id = storage->get_region_idp(brush_global_position); _region_modified(brush_global_position); } diff --git a/src/terrain_3d_instancer.cpp b/src/terrain_3d_instancer.cpp index 9151626f..debd3cd2 100644 --- a/src/terrain_3d_instancer.cpp +++ b/src/terrain_3d_instancer.cpp @@ -112,7 +112,7 @@ void Terrain3DInstancer::_update_mmis(const Vector2i &p_region_loc, const int p_ } void Terrain3DInstancer::_destroy_mmi_by_region_id(const int p_region_id, const int p_mesh_id) { - Vector2i region_loc = _terrain->get_storage()->get_region_location_from_id(p_region_id); + Vector2i region_loc = _terrain->get_storage()->get_region_locationi(p_region_id); _destroy_mmi_by_location(region_loc, p_mesh_id); } @@ -161,7 +161,7 @@ void Terrain3DInstancer::clear_by_mesh(const int p_mesh_id) { } void Terrain3DInstancer::clear_by_region_id(const int p_region_id, const int p_mesh_id) { - Vector2i region_loc = _terrain->get_storage()->get_region_location_from_id(p_region_id); + Vector2i region_loc = _terrain->get_storage()->get_region_locationi(p_region_id); clear_by_location(region_loc, p_mesh_id); } diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index cfee2c3c..5b914215 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -140,37 +140,6 @@ void Terrain3DStorage::set_region_locations(const TypedArray &p_locati update_maps(); } -/** Returns a region location given a global position. No bounds checking*/ -Vector2i Terrain3DStorage::get_region_location(const Vector3 &p_global_position) const { - Vector3 descaled_position = p_global_position / _mesh_vertex_spacing; - return Vector2i((Vector2(descaled_position.x, descaled_position.z) / real_t(_region_size)).floor()); -} - -// Returns Vector2i(2147483647) if out of range -Vector2i Terrain3DStorage::get_region_location_from_id(const int p_region_id) const { - if (p_region_id < 0 || p_region_id >= _region_locations.size()) { - return Vector2i(INT32_MAX, INT32_MAX); - } - return _region_locations[p_region_id]; -} - -int Terrain3DStorage::get_region_id(const Vector3 &p_global_position) const { - Vector2i region_loc = get_region_location(p_global_position); - return get_region_id_from_location(region_loc); -} - -// Returns -1 if out of bounds, 0 if no region, or region id -int Terrain3DStorage::get_region_id_from_location(const Vector2i &p_region_loc) const { - int map_index = _get_region_map_index(p_region_loc); - if (map_index >= 0) { - int region_id = _region_map[map_index] - 1; // 0 = no region - if (region_id >= 0 && region_id < _region_locations.size()) { - return region_id; - } - } - return -1; -} - /** Adds a region to the terrain * Option to include an array of Images to use for maps * Map types are Height:0, Control:1, Color:2, defined in MapType @@ -197,7 +166,7 @@ Error Terrain3DStorage::add_region(const Vector3 &p_global_position, const Typed return FAILED; } - if (has_region(p_global_position)) { + if (has_regionp(p_global_position)) { if (p_images.is_empty()) { LOG(DEBUG, "Region at ", p_global_position, " already exists and nothing to overwrite. Doing nothing"); return OK; @@ -244,7 +213,7 @@ Error Terrain3DStorage::add_region(const Vector3 &p_global_position, const Typed void Terrain3DStorage::remove_region(const Vector3 &p_global_position, const bool p_update, const String &p_path) { LOG(INFO, "Removing region at ", p_global_position, " Updating: ", p_update ? "yes" : "no"); - int region_id = get_region_id(p_global_position); + int region_id = get_region_idp(p_global_position); ERR_FAIL_COND_MSG(region_id == -1, "Position out of bounds"); remove_region_by_id(region_id, p_update, p_path); } @@ -322,9 +291,10 @@ void Terrain3DStorage::update_maps() { _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); _region_map_dirty = false; for (int i = 0; i < _region_locations.size(); i++) { + int region_id = i + 1; // Begin at 1 since 0 = no region int map_index = _get_region_map_index(_region_locations[i]); if (map_index >= 0) { - _region_map[map_index] = i + 1; // Begin at 1 since 0 = no region + _region_map[map_index] = region_id; } } any_changed = true; @@ -403,7 +373,7 @@ TypedArray Terrain3DStorage::get_regions_under_aabb(const AABB &p_aabb) { real_t x = start.x + godot::Math::min((real_t)(i * _region_size), size_x); for (int j = 0; j < region_count_y; j++) { real_t y = start.y + godot::Math::min((real_t)(j * _region_size), size_y); - int region_id = get_region_id(Vector3(x, 0, y)); + int region_id = get_region_idp(Vector3(x, 0, y)); LOG(INFO, "Region id ", region_id, " location ", Vector3(x, 0, y)); // If found, push_back to array if (region_id != -1) { @@ -567,7 +537,7 @@ void Terrain3DStorage::set_pixel(const MapType p_map_type, const Vector3 &p_glob LOG(ERROR, "Specified map type out of range"); return; } - int region_id = get_region_id(p_global_position); + int region_id = get_region_idp(p_global_position); if (region_id < 0) { return; } @@ -586,7 +556,7 @@ Color Terrain3DStorage::get_pixel(const MapType p_map_type, const Vector3 &p_glo LOG(ERROR, "Specified map type out of range"); return COLOR_NAN; } - int region_id = get_region_id(p_global_position); + int region_id = get_region_idp(p_global_position); if (region_id < 0) { return COLOR_NAN; } @@ -637,7 +607,7 @@ real_t Terrain3DStorage::get_height(const Vector3 &p_global_position) const { **/ Vector3 Terrain3DStorage::get_texture_id(const Vector3 &p_global_position) const { // Verify in a region - int region_id = get_region_id(p_global_position); + int region_id = get_region_idp(p_global_position); if (region_id < 0) { return Vector3(NAN, NAN, NAN); } @@ -683,7 +653,7 @@ real_t Terrain3DStorage::get_angle(const Vector3 &p_global_position) const { } real_t angle = real_t(get_uv_rotation(src)); angle *= 22.5; // Return value in degrees. - return real_t(angle); + return angle; } real_t Terrain3DStorage::get_scale(const Vector3 &p_global_position) const { @@ -693,7 +663,7 @@ real_t Terrain3DStorage::get_scale(const Vector3 &p_global_position) const { } std::array scale_values = { 0.0f, 20.0f, 40.0f, 60.0f, 80.0f, -60.0f, -40.0f, -20.0f }; real_t scale = scale_values[get_uv_scale(src)]; //select from array UI return values - return real_t(scale); + return scale; } /** @@ -813,7 +783,7 @@ Dictionary Terrain3DStorage::get_multimeshes(const TypedArray &p_region_ids } else { Dictionary output = Dictionary(); for (int i = 0; i < p_region_ids.size(); i++) { - Vector2i region_loc = get_region_location_from_id(i); + Vector2i region_loc = get_region_locationi(i); output[region_loc] = _multimeshes[p_region_ids[i]]; } return output; @@ -1073,7 +1043,7 @@ Ref Terrain3DStorage::layered_to_image(const MapType p_map_type) const { Vector2i region_loc = _region_locations[i]; Vector2i img_location = (region_loc - top_left) * _region_size; LOG(DEBUG, "Region to blit: ", region_loc, " Export image coords: ", img_location); - int region_id = get_region_id(Vector3(region_loc.x, 0, region_loc.y) * _region_size); + int region_id = get_region_idp(Vector3(region_loc.x, 0, region_loc.y) * _region_size); img->blit_rect(get_map_region(map_type, region_id), Rect2i(Vector2i(0, 0), _region_sizev), img_location); } return img; @@ -1165,7 +1135,7 @@ Vector3 Terrain3DStorage::get_mesh_vertex(const int32_t p_lod, const HeightFilte } Vector3 Terrain3DStorage::get_normal(const Vector3 &p_global_position) const { - if (get_region_id(p_global_position) < 0 || is_hole(get_control(p_global_position))) { + if (get_region_idp(p_global_position) < 0 || is_hole(get_control(p_global_position))) { return Vector3(NAN, NAN, NAN); } real_t height = get_height(p_global_position); @@ -1226,9 +1196,9 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("get_region_locations"), &Terrain3DStorage::get_region_locations); ClassDB::bind_method(D_METHOD("get_region_count"), &Terrain3DStorage::get_region_count); ClassDB::bind_method(D_METHOD("get_region_location", "global_position"), &Terrain3DStorage::get_region_location); - ClassDB::bind_method(D_METHOD("get_region_location_from_id", "region_id"), &Terrain3DStorage::get_region_location_from_id); - ClassDB::bind_method(D_METHOD("get_region_id", "global_position"), &Terrain3DStorage::get_region_id); - ClassDB::bind_method(D_METHOD("get_region_id_from_location", "region_location"), &Terrain3DStorage::get_region_id_from_location); + ClassDB::bind_method(D_METHOD("get_region_locationi", "region_id"), &Terrain3DStorage::get_region_locationi); + ClassDB::bind_method(D_METHOD("get_region_id", "region_location"), &Terrain3DStorage::get_region_id); + ClassDB::bind_method(D_METHOD("get_region_idp", "global_position"), &Terrain3DStorage::get_region_idp); ClassDB::bind_method(D_METHOD("has_region", "global_position"), &Terrain3DStorage::has_region); ClassDB::bind_method(D_METHOD("add_region", "global_position", "images", "update", "path"), &Terrain3DStorage::add_region, DEFVAL(TypedArray()), DEFVAL(true), DEFVAL("")); diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index 9bbe75ba..2cc469ae 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -115,6 +115,12 @@ class Terrain3DStorage : public Object { void initialize(Terrain3D *p_terrain); ~Terrain3DStorage() { _clear(); } + Vector2i get_region_location(const Vector3 &p_global_position) const; + Vector2i get_region_locationi(const int p_region_id) const; + int get_region_id(const Vector2i &p_region_loc) const; + int get_region_idp(const Vector3 &p_global_position) const; + bool has_region(const Vector2i &p_region_loc) const { return get_region_id(p_region_loc) != -1; } + bool has_regionp(const Vector3 &p_global_position) const { return get_region_idp(p_global_position) != -1; } void set_height_range(const Vector2 &p_range); Vector2 get_height_range() const { return _height_range; } void update_heights(const real_t p_height); @@ -138,11 +144,6 @@ class Terrain3DStorage : public Object { TypedArray get_region_locations() const { return _region_locations; } PackedInt32Array get_region_map() const { return _region_map; } int get_region_count() const { return _region_locations.size(); } - Vector2i get_region_location(const Vector3 &p_global_position) const; - Vector2i get_region_location_from_id(const int p_region_id) const; - int get_region_id(const Vector3 &p_global_position) const; - int get_region_id_from_location(const Vector2i &p_region_loc) const; - bool has_region(const Vector3 &p_global_position) const { return get_region_id(p_global_position) != -1; } Error add_region(const Vector3 &p_global_position, const TypedArray &p_images = TypedArray(), const bool p_update = true, @@ -220,10 +221,10 @@ constexpr Terrain3DStorage::MapType TYPE_MAX = Terrain3DStorage::MapType::TYPE_M VARIANT_ENUM_CAST(Terrain3DStorage::RegionSize); VARIANT_ENUM_CAST(Terrain3DStorage::HeightFilter); -/// Inline Functions +/// Inline Region Functions -// This function verifies the location is within the bounds of the -// _region_map array and returns the map index if valid, -1 if not +// This function verifies the location is within the bounds of the _region_map array and +// thus the world. It returns the _region_map index if valid, -1 if not inline int Terrain3DStorage::_get_region_map_index(const Vector2i &p_region_loc) const { // Offset locations centered on (0,0) to positive only Vector2i loc = Vector2i(p_region_loc + (REGION_MAP_VSIZE / 2)); @@ -234,6 +235,38 @@ inline int Terrain3DStorage::_get_region_map_index(const Vector2i &p_region_loc) return map_index; } +// Returns a region location given a global position. No bounds checking nor data access. +inline Vector2i Terrain3DStorage::get_region_location(const Vector3 &p_global_position) const { + Vector2 descaled_position = Vector2(p_global_position.x, p_global_position.z); + return Vector2i((descaled_position / (_mesh_vertex_spacing * real_t(_region_size))).floor()); +} + +// Returns Vector2i(2147483647) if out of range +inline Vector2i Terrain3DStorage::get_region_locationi(const int p_region_id) const { + if (p_region_id < 0 || p_region_id >= _region_locations.size()) { + return Vector2i(INT32_MAX, INT32_MAX); + } + return _region_locations[p_region_id]; +} + +// Returns id of any active region. -1 if out of bounds, 0 if no region, or region id +inline int Terrain3DStorage::get_region_id(const Vector2i &p_region_loc) const { + int map_index = _get_region_map_index(p_region_loc); + if (map_index >= 0) { + int region_id = _region_map[map_index] - 1; // 0 = no region + if (region_id >= 0 && region_id < _region_locations.size()) { + return region_id; + } + } + return -1; +} + +inline int Terrain3DStorage::get_region_idp(const Vector3 &p_global_position) const { + return get_region_id(get_region_location(p_global_position)); +} + +/// Inline Map Functions + inline void Terrain3DStorage::set_height(const Vector3 &p_global_position, const real_t p_height) { set_pixel(TYPE_HEIGHT, p_global_position, Color(p_height, 0.f, 0.f, 1.f)); } From 804ca55718d5adb06265ca790078b2e327b41f6e Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:29:06 +0700 Subject: [PATCH 10/19] Move MapType/format enums, sanitize_maps to Terrain3DRegion --- project/addons/terrain_3d/src/ui.gd | 4 +- project/addons/terrain_3d/tools/importer.gd | 8 +- src/terrain_3d_region.cpp | 81 +++++++++++++- src/terrain_3d_region.h | 53 ++++++++- src/terrain_3d_storage.cpp | 117 +++++--------------- src/terrain_3d_storage.h | 36 ------ src/terrain_3d_util.cpp | 2 +- 7 files changed, 165 insertions(+), 136 deletions(-) diff --git a/project/addons/terrain_3d/src/ui.gd b/project/addons/terrain_3d/src/ui.gd index b8b3fa89..73a06c7c 100644 --- a/project/addons/terrain_3d/src/ui.gd +++ b/project/addons/terrain_3d/src/ui.gd @@ -412,9 +412,9 @@ func pick(p_global_position: Vector3) -> void: var color: Color match picking: Terrain3DEditor.HEIGHT: - color = plugin.terrain.get_storage().get_pixel(Terrain3DStorage.TYPE_HEIGHT, p_global_position) + color = plugin.terrain.get_storage().get_pixel(Terrain3DRegion.TYPE_HEIGHT, p_global_position) Terrain3DEditor.ROUGHNESS: - color = plugin.terrain.get_storage().get_pixel(Terrain3DStorage.TYPE_COLOR, p_global_position) + color = plugin.terrain.get_storage().get_pixel(Terrain3DRegion.TYPE_COLOR, p_global_position) Terrain3DEditor.COLOR: color = plugin.terrain.get_storage().get_color(p_global_position) Terrain3DEditor.ANGLE: diff --git a/project/addons/terrain_3d/tools/importer.gd b/project/addons/terrain_3d/tools/importer.gd index 8b5aba8e..1309ea90 100644 --- a/project/addons/terrain_3d/tools/importer.gd +++ b/project/addons/terrain_3d/tools/importer.gd @@ -61,19 +61,19 @@ func start_import(p_value: bool) -> void: storage = Terrain3DStorage.new() var imported_images: Array[Image] - imported_images.resize(Terrain3DStorage.TYPE_MAX) + imported_images.resize(Terrain3DRegion.TYPE_MAX) var min_max := Vector2(0, 1) var img: Image if height_file_name: img = Terrain3DUtil.load_image(height_file_name, ResourceLoader.CACHE_MODE_IGNORE, r16_range, r16_size) min_max = Terrain3DUtil.get_min_max(img) - imported_images[Terrain3DStorage.TYPE_HEIGHT] = img + imported_images[Terrain3DRegion.TYPE_HEIGHT] = img if control_file_name: img = Terrain3DUtil.load_image(control_file_name, ResourceLoader.CACHE_MODE_IGNORE) - imported_images[Terrain3DStorage.TYPE_CONTROL] = img + imported_images[Terrain3DRegion.TYPE_CONTROL] = img if color_file_name: img = Terrain3DUtil.load_image(color_file_name, ResourceLoader.CACHE_MODE_IGNORE) - imported_images[Terrain3DStorage.TYPE_COLOR] = img + imported_images[Terrain3DRegion.TYPE_COLOR] = img if assets.get_texture_count() == 0: material.show_checkered = false material.show_colormap = true diff --git a/src/terrain_3d_region.cpp b/src/terrain_3d_region.cpp index 14ecca88..0293af15 100644 --- a/src/terrain_3d_region.cpp +++ b/src/terrain_3d_region.cpp @@ -4,11 +4,22 @@ #include "logger.h" #include "terrain_3d_region.h" +#include "terrain_3d_storage.h" +#include "terrain_3d_util.h" ///////////////////// // Public Functions ///////////////////// +Terrain3DRegion::Terrain3DRegion(const Ref &p_height_map, const Ref &p_control_map, + const Ref &p_color_map, const int p_region_size) { + _height_map = p_height_map; + _control_map = p_control_map; + _color_map = p_color_map; + _region_size = p_region_size; + sanitize_maps(); +} + void Terrain3DRegion::set_version(const real_t p_version) { LOG(INFO, vformat("%.3f", p_version)); _version = p_version; @@ -19,7 +30,9 @@ void Terrain3DRegion::set_version(const real_t p_version) { } void Terrain3DRegion::set_height_map(const Ref &p_map) { - Image::Format format = Terrain3DStorage::FORMAT[Terrain3DStorage::TYPE_HEIGHT]; + Image::Format format = FORMAT[TYPE_HEIGHT]; + // This is to convert format_RH to RF, but probably unnecessary w/ sanitize + /*if (p_map.is_valid() && p_map->get_format() != format) { if (p_map.is_valid() && p_map->get_format() != format) { LOG(DEBUG, "Converting file format: ", p_map->get_format(), " to ", format); if (_height_map.is_null()) { @@ -29,9 +42,70 @@ void Terrain3DRegion::set_height_map(const Ref &p_map) { _height_map->convert(format); } else { _height_map = p_map; + } */ + _height_map = sanitize_map(TYPE_HEIGHT, p_map); +} + +void Terrain3DRegion::set_control_map(const Ref &p_map) { + _control_map = sanitize_map(TYPE_CONTROL, p_map); +} + +void Terrain3DRegion::set_color_map(const Ref &p_map) { + _color_map = sanitize_map(TYPE_COLOR, p_map); +} + +void Terrain3DRegion::sanitize_maps(const MapType p_map_type) { + LOG(INFO, "Verifying images maps are valid"); + if (p_map_type == TYPE_HEIGHT || p_map_type == TYPE_MAX) { + _height_map = sanitize_map(TYPE_HEIGHT, _height_map); + } + if (p_map_type == TYPE_CONTROL || p_map_type == TYPE_MAX) { + _control_map = sanitize_map(TYPE_CONTROL, _control_map); + } + if (p_map_type == TYPE_COLOR || p_map_type == TYPE_MAX) { + _color_map = sanitize_map(TYPE_COLOR, _color_map); } } +// Verifies region map is a valid size and format +// Creates filled blanks if lacking +Ref Terrain3DRegion::sanitize_map(const MapType p_map_type, const Ref &p_img) const { + LOG(INFO, "Verifying images maps are valid"); + if (p_map_type < 0 || p_map_type >= TYPE_MAX) { + LOG(ERROR, "Invalid map type: ", p_map_type); + return Ref(); + } + + Image::Format format = FORMAT[p_map_type]; + const char *type_str = TYPESTR[p_map_type]; + Color color = COLOR[p_map_type]; + + if (p_img.is_valid()) { + if (p_img->get_size() == Vector2i(_region_size, _region_size)) { + if (p_img->get_format() == format) { + LOG(DEBUG, "Map type ", type_str, " correct format, size. Using image"); + return p_img; + } else { + LOG(DEBUG, "Provided ", type_str, " map wrong format: ", p_img->get_format(), ". Converting copy to: ", format); + Ref newimg; + newimg.instantiate(); + newimg->copy_from(p_img); + newimg->convert(format); + if (newimg->get_format() == format) { + return newimg; + } else { + LOG(DEBUG, "Couldn't convert image to format: ", format, ". Creating blank "); + } + } + } else { + LOG(DEBUG, "Provided ", type_str, " map wrong size: ", p_img->get_size(), ". Creating blank"); + } + } else { + LOG(DEBUG, "No provided ", type_str, " map. Creating blank"); + } + return Util::get_filled_image(Vector2i(_region_size, _region_size), color, false, format); +} + Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) { // Initiate save to external file. The scene will save itself. if (!_modified) { @@ -74,6 +148,11 @@ Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) { ///////////////////// void Terrain3DRegion::_bind_methods() { + BIND_ENUM_CONSTANT(TYPE_HEIGHT); + BIND_ENUM_CONSTANT(TYPE_CONTROL); + BIND_ENUM_CONSTANT(TYPE_COLOR); + BIND_ENUM_CONSTANT(TYPE_MAX); + ClassDB::bind_method(D_METHOD("set_version"), &Terrain3DRegion::set_version); ClassDB::bind_method(D_METHOD("get_version"), &Terrain3DRegion::get_version); diff --git a/src/terrain_3d_region.h b/src/terrain_3d_region.h index 644c9d27..485ae631 100644 --- a/src/terrain_3d_region.h +++ b/src/terrain_3d_region.h @@ -4,6 +4,7 @@ #define TERRAIN3D_REGION_CLASS_H #include "constants.h" +#include "terrain_3d_util.h" using namespace godot; @@ -11,9 +12,39 @@ class Terrain3DRegion : public Resource { GDCLASS(Terrain3DRegion, Resource); CLASS_NAME(); +public: // Constants + enum MapType { + TYPE_HEIGHT, + TYPE_CONTROL, + TYPE_COLOR, + TYPE_MAX, + }; + + static inline const Image::Format FORMAT[] = { + Image::FORMAT_RF, // TYPE_HEIGHT + Image::FORMAT_RF, // TYPE_CONTROL + Image::FORMAT_RGBA8, // TYPE_COLOR + Image::Format(TYPE_MAX), // Proper size of array instead of FORMAT_MAX + }; + + static inline const char *TYPESTR[] = { + "TYPE_HEIGHT", + "TYPE_CONTROL", + "TYPE_COLOR", + "TYPE_MAX", + }; + + static inline const Color COLOR[] = { + COLOR_BLACK, // TYPE_HEIGHT + COLOR_CONTROL, // TYPE_CONTROL + COLOR_ROUGHNESS, // TYPE_COLOR + COLOR_NAN, // TYPE_MAX, unused just in case someone indexes the array + }; + private: // Saved data real_t _version = 0.8f; // Set to first version to ensure Godot always upgrades this + int _region_size = 1024; // Maps Ref _height_map; Ref _control_map; @@ -26,16 +57,22 @@ class Terrain3DRegion : public Resource { bool _modified = false; public: + Terrain3DRegion() {} + Terrain3DRegion(const Ref &p_height_map, const Ref &p_control_map, const Ref &p_color_map, const int p_region_size = 1024); + ~Terrain3DRegion() {} + void set_version(const real_t p_version); real_t get_version() const { return _version; } // Maps void set_height_map(const Ref &p_map); Ref get_height_map() const { return _height_map; } - void set_control_map(const Ref &p_map) { _control_map = p_map; } + void set_control_map(const Ref &p_map); Ref get_control_map() const { return _control_map; } - void set_color_map(const Ref &p_map) { _color_map = p_map; } + void set_color_map(const Ref &p_map); Ref get_color_map() const { return _color_map; } + void sanitize_maps(const MapType p_map_type = TYPE_MAX); + Ref sanitize_map(const MapType p_map_type, const Ref &p_img) const; // Instancer void set_multimeshes(const Dictionary &p_multimeshes) { _multimeshes = p_multimeshes; } @@ -49,9 +86,21 @@ class Terrain3DRegion : public Resource { bool is_modified() const { return _modified; } void set_location(const Vector2i &p_location) { _location = p_location; } Vector2i get_location() const { return _location; } + void set_region_size(const int p_region_size) { _region_size = p_region_size; } + int get_region_size() const { return _region_size; } protected: static void _bind_methods(); }; +typedef Terrain3DRegion::MapType MapType; +VARIANT_ENUM_CAST(Terrain3DRegion::MapType); +constexpr Terrain3DRegion::MapType TYPE_HEIGHT = Terrain3DRegion::MapType::TYPE_HEIGHT; +constexpr Terrain3DRegion::MapType TYPE_CONTROL = Terrain3DRegion::MapType::TYPE_CONTROL; +constexpr Terrain3DRegion::MapType TYPE_COLOR = Terrain3DRegion::MapType::TYPE_COLOR; +constexpr Terrain3DRegion::MapType TYPE_MAX = Terrain3DRegion::MapType::TYPE_MAX; +constexpr inline const Image::Format *FORMAT = Terrain3DRegion::FORMAT; +constexpr inline const char **TYPESTR = Terrain3DRegion::TYPESTR; +constexpr inline const Color *COLOR = Terrain3DRegion::COLOR; + #endif // TERRAIN3D_REGION_CLASS_H diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index 5b914215..86f67636 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -176,24 +176,36 @@ Error Terrain3DStorage::add_region(const Vector3 &p_global_position, const Typed } } - TypedArray images = sanitize_maps(TYPE_MAX, p_images); - if (images.is_empty()) { - LOG(ERROR, "Sanitize_maps failed to accept images or produce blanks"); - return FAILED; - } + Ref region; + region.instantiate(); + region->set_location(region_loc); + region->set_height_map((p_images.size() > 0) ? Ref(p_images[TYPE_HEIGHT]) : Ref()); + region->set_control_map((p_images.size() > 0) ? Ref(p_images[TYPE_CONTROL]) : Ref()); + region->set_color_map((p_images.size() > 0) ? Ref(p_images[TYPE_COLOR]) : Ref()); + + //TypedArray images = sanitize_maps(TYPE_MAX, p_images); + //if (images.is_empty()) { + // LOG(ERROR, "Sanitize_maps failed to accept images or produce blanks"); + // return FAILED; + //} + // Check this later, per region // If we're importing data into a region, check its heights for aabbs - Vector2 min_max = Vector2(0.f, 0.f); + /*Vector2 min_max = Vector2(0.f, 0.f); if (p_images.size() > TYPE_HEIGHT) { min_max = Util::get_min_max(images[TYPE_HEIGHT]); LOG(DEBUG, "Checking imported height range: ", min_max); update_heights(min_max); } + */ - LOG(DEBUG, "Pushing back ", images.size(), " images"); - _height_maps.push_back(images[TYPE_HEIGHT]); - _control_maps.push_back(images[TYPE_CONTROL]); - _color_maps.push_back(images[TYPE_COLOR]); + //LOG(DEBUG, "Pushing back ", images.size(), " images"); + //_height_maps.push_back(images[TYPE_HEIGHT]); + //_control_maps.push_back(images[TYPE_CONTROL]); + //_color_maps.push_back(images[TYPE_COLOR]); + _height_maps.push_back(region->get_height_map()); + _control_maps.push_back(region->get_control_map()); + _color_maps.push_back(region->get_color_map()); _region_locations.push_back(region_loc); LOG(DEBUG, "Total regions after pushback: ", _region_locations.size()); @@ -448,6 +460,9 @@ Ref Terrain3DStorage::get_map_region(const MapType p_map_type, const int void Terrain3DStorage::set_maps(const MapType p_map_type, const TypedArray &p_maps, const TypedArray &p_region_ids) { ERR_FAIL_COND_MSG(p_map_type < 0 || p_map_type >= TYPE_MAX, "Specified map type out of range"); + return; + // Rewrite or move to Terrain3DRegion + /* if (p_region_ids.is_empty()) { LOG(INFO, "Setting ", TYPESTR[p_map_type], " maps: ", p_maps.size()); switch (p_map_type) { @@ -480,7 +495,7 @@ void Terrain3DStorage::set_maps(const MapType p_map_type, const TypedArray Terrain3DStorage::sanitize_maps(const MapType p_map_type, const TypedArray &p_maps) const { - LOG(INFO, "Verifying image set is valid: ", p_maps.size(), " maps of type: ", TYPESTR[TYPE_MAX]); - - TypedArray images; - int iterations; - - if (p_map_type == TYPE_MAX) { - images.resize(TYPE_MAX); - iterations = TYPE_MAX; - } else { - images.resize(p_maps.size()); - iterations = p_maps.size(); - if (iterations <= 0) { - LOG(DEBUG, "Empty Image set. Nothing to sanitize"); - return images; - } - } - - Image::Format format; - const char *type_str; - Color color; - for (int i = 0; i < iterations; i++) { - if (p_map_type == TYPE_MAX) { - format = FORMAT[i]; - type_str = TYPESTR[i]; - color = COLOR[i]; - } else { - format = FORMAT[p_map_type]; - type_str = TYPESTR[p_map_type]; - color = COLOR[p_map_type]; - } - - if (i < p_maps.size()) { - Ref img; - img = p_maps[i]; - if (img.is_valid()) { - if (img->get_size() == _region_sizev) { - if (img->get_format() == format) { - LOG(DEBUG, "Map type ", type_str, " correct format, size. Using image"); - images[i] = img; - } else { - LOG(DEBUG, "Provided ", type_str, " map wrong format: ", img->get_format(), ". Converting copy to: ", format); - Ref newimg; - newimg.instantiate(); - newimg->copy_from(img); - newimg->convert(format); - images[i] = newimg; - } - continue; // Continue for loop - } else { - LOG(DEBUG, "Provided ", type_str, " map wrong size: ", img->get_size(), ". Creating blank"); - } - } else { - LOG(DEBUG, "No provided ", type_str, " map. Creating blank"); - } - } else { - LOG(DEBUG, "p_images.size() < ", i, ". Creating blank"); - } - images[i] = Util::get_filled_image(_region_sizev, color, false, format); - } - - return images; -} - void Terrain3DStorage::force_update_maps(const MapType p_map_type) { LOG(DEBUG_CONT, "Regenerating maps of type: ", p_map_type); switch (p_map_type) { @@ -1072,8 +1015,7 @@ void Terrain3DStorage::load_directory(const String &p_dir) { continue; } LOG(DEBUG, "Loading region from ", path); - Ref region = ResourceLoader::get_singleton()->load( - path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE); + Ref region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE); if (region.is_null()) { LOG(ERROR, "Region file ", path, " failed to load"); continue; @@ -1169,11 +1111,6 @@ void Terrain3DStorage::print_audit_data() const { /////////////////////////// void Terrain3DStorage::_bind_methods() { - BIND_ENUM_CONSTANT(TYPE_HEIGHT); - BIND_ENUM_CONSTANT(TYPE_CONTROL); - BIND_ENUM_CONSTANT(TYPE_COLOR); - BIND_ENUM_CONSTANT(TYPE_MAX); - //BIND_ENUM_CONSTANT(SIZE_64); //BIND_ENUM_CONSTANT(SIZE_128); //BIND_ENUM_CONSTANT(SIZE_256); diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index 2cc469ae..0d08ebc1 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -6,7 +6,6 @@ #include "constants.h" #include "generated_texture.h" #include "terrain_3d_region.h" -#include "terrain_3d_util.h" class Terrain3D; @@ -22,34 +21,6 @@ class Terrain3DStorage : public Object { static inline const int REGION_MAP_SIZE = 16; static inline const Vector2i REGION_MAP_VSIZE = Vector2i(REGION_MAP_SIZE, REGION_MAP_SIZE); - enum MapType { - TYPE_HEIGHT, - TYPE_CONTROL, - TYPE_COLOR, - TYPE_MAX, - }; - - static inline const Image::Format FORMAT[] = { - Image::FORMAT_RF, // TYPE_HEIGHT - Image::FORMAT_RF, // TYPE_CONTROL - Image::FORMAT_RGBA8, // TYPE_COLOR - Image::Format(TYPE_MAX), // Proper size of array instead of FORMAT_MAX - }; - - static inline const char *TYPESTR[] = { - "TYPE_HEIGHT", - "TYPE_CONTROL", - "TYPE_COLOR", - "TYPE_MAX", - }; - - static inline const Color COLOR[] = { - COLOR_BLACK, // TYPE_HEIGHT - COLOR_CONTROL, // TYPE_CONTROL - COLOR_ROUGHNESS, // TYPE_COLOR - COLOR_NAN, // TYPE_MAX, unused just in case someone indexes the array - }; - enum RegionSize { //SIZE_64 = 64, //SIZE_128 = 128, @@ -180,7 +151,6 @@ class Terrain3DStorage : public Object { Vector3 get_texture_id(const Vector3 &p_global_position) const; real_t get_angle(const Vector3 &p_global_position) const; real_t get_scale(const Vector3 &p_global_position) const; - TypedArray sanitize_maps(const MapType p_map_type, const TypedArray &p_maps) const; void force_update_maps(const MapType p_map = TYPE_MAX); // Instancer @@ -212,12 +182,6 @@ class Terrain3DStorage : public Object { static void _bind_methods(); }; -typedef Terrain3DStorage::MapType MapType; -VARIANT_ENUM_CAST(Terrain3DStorage::MapType); -constexpr Terrain3DStorage::MapType TYPE_HEIGHT = Terrain3DStorage::MapType::TYPE_HEIGHT; -constexpr Terrain3DStorage::MapType TYPE_CONTROL = Terrain3DStorage::MapType::TYPE_CONTROL; -constexpr Terrain3DStorage::MapType TYPE_COLOR = Terrain3DStorage::MapType::TYPE_COLOR; -constexpr Terrain3DStorage::MapType TYPE_MAX = Terrain3DStorage::MapType::TYPE_MAX; VARIANT_ENUM_CAST(Terrain3DStorage::RegionSize); VARIANT_ENUM_CAST(Terrain3DStorage::HeightFilter); diff --git a/src/terrain_3d_util.cpp b/src/terrain_3d_util.cpp index f31fed42..a19fe43c 100644 --- a/src/terrain_3d_util.cpp +++ b/src/terrain_3d_util.cpp @@ -255,7 +255,7 @@ Ref Terrain3DUtil::load_image(const String &p_file_name, const int p_cach LOG(DEBUG, "Total file size is: ", fsize, " calculated width: ", fwidth, " dimensions: ", r16_size); file->seek(0); } - img = Image::create(r16_size.x, r16_size.y, false, Terrain3DStorage::FORMAT[TYPE_HEIGHT]); + img = Image::create(r16_size.x, r16_size.y, false, FORMAT[TYPE_HEIGHT]); for (int y = 0; y < r16_size.y; y++) { for (int x = 0; x < r16_size.x; x++) { real_t h = real_t(file->get_16()) / 65535.0f; From b2bb277f4ffba72d2819d608c21a5080ed017db8 Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:13:07 +0700 Subject: [PATCH 11/19] Add Vector constants --- src/constants.h | 14 ++++++++++---- src/geoclipmap.cpp | 2 +- src/terrain_3d.cpp | 10 +++++----- src/terrain_3d.h | 2 +- src/terrain_3d_assets.cpp | 14 +++++++------- src/terrain_3d_instancer.cpp | 2 +- src/terrain_3d_instancer.h | 2 +- src/terrain_3d_region.h | 2 +- src/terrain_3d_storage.cpp | 20 ++++++++++---------- src/terrain_3d_storage.h | 4 ++-- src/terrain_3d_util.cpp | 10 +++++----- src/terrain_3d_util.h | 2 +- 12 files changed, 45 insertions(+), 39 deletions(-) diff --git a/src/constants.h b/src/constants.h index e3b003f2..695fa6f2 100644 --- a/src/constants.h +++ b/src/constants.h @@ -18,12 +18,18 @@ using namespace godot; #define COLOR_NORMAL Color(0.5f, 0.5f, 1.0f, 1.0f) #define COLOR_CONTROL Color(as_float(enc_auto(true)), 0.f, 0.f, 1.0f) -// For consistency between msvc, gcc, clang - -#ifndef __FLT_MAX__ -#define __FLT_MAX__ FLT_MAX +#ifndef FLT_MAX +// For consistency between MSVC, gcc, clang +#define FLT_MAX __FLT_MAX__ #endif +#define V2_ZERO Vector2(0.f, 0.f) +#define V2_MAX Vector2(FLT_MAX, FLT_MAX) +#define V3_ZERO Vector3(0.f, 0.f, 0.f) +#define V3_MAX Vector3(FLT_MAX, FLT_MAX, FLT_MAX) +#define V2I_ZERO Vector2i(0, 0) +#define V2I_MAX Vector2i(INT32_MAX, INT32_MAX) + // Set class name for logger.h #define CLASS_NAME() const String __class__ = get_class_static() + \ diff --git a/src/geoclipmap.cpp b/src/geoclipmap.cpp index 5d4bdcce..ef6de753 100644 --- a/src/geoclipmap.cpp +++ b/src/geoclipmap.cpp @@ -95,7 +95,7 @@ Vector GeoClipMap::generate(const int p_size, const int p_levels) { } } - aabb = AABB(Vector3(0.f, 0.f, 0.f), Vector3(PATCH_VERT_RESOLUTION, 0.1f, PATCH_VERT_RESOLUTION)); + aabb = AABB(V3_ZERO, Vector3(PATCH_VERT_RESOLUTION, 0.1f, PATCH_VERT_RESOLUTION)); tile_mesh = _create_mesh(vertices, indices, aabb); } diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index 035ef16a..8e64057e 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -71,9 +71,9 @@ void Terrain3D::_initialize() { _assets->connect("textures_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::_update_texture_arrays)); } // MeshAssets changed, update instancer - if (!_assets->is_connected("meshes_changed", callable_mp(_instancer, &Terrain3DInstancer::_update_mmis).bind(Vector2i(INT32_MAX, INT32_MAX), -1))) { + if (!_assets->is_connected("meshes_changed", callable_mp(_instancer, &Terrain3DInstancer::_update_mmis).bind(V2I_MAX, -1))) { LOG(DEBUG, "Connecting _assets.meshes_changed to _instancer->_update_mmis()"); - _assets->connect("meshes_changed", callable_mp(_instancer, &Terrain3DInstancer::_update_mmis).bind(Vector2i(INT32_MAX, INT32_MAX), -1)); + _assets->connect("meshes_changed", callable_mp(_instancer, &Terrain3DInstancer::_update_mmis).bind(V2I_MAX, -1)); } // New multimesh added to storage, rebuild instancer if (!_storage->is_connected("multimeshes_changed", callable_mp(_instancer, &Terrain3DInstancer::_rebuild_mmis))) { @@ -269,7 +269,7 @@ void Terrain3D::_build_meshes(const int p_mesh_lods, const int p_mesh_size) { update_aabbs(); // Force a snap update - _camera_last_position = Vector2(__FLT_MAX__, __FLT_MAX__); + _camera_last_position = V2_MAX; } /** @@ -401,7 +401,7 @@ void Terrain3D::_update_collision() { float hole_const = NAN; // DEPRECATED - Jolt v0.12 supports NAN. Remove check when it's old. if (ProjectSettings::get_singleton()->get_setting("physics/3d/physics_engine") == "JoltPhysics3D") { - hole_const = __FLT_MAX__; + hole_const = FLT_MAX; } for (int i = 0; i < _storage->get_region_count(); i++) { @@ -1040,7 +1040,7 @@ Vector3 Terrain3D::get_intersection(const Vector3 &p_src_pos, const Vector3 &p_d Vector2 screen_rg = Vector2(screen_depth.r, screen_depth.g); real_t normalized_distance = screen_rg.dot(Vector2(1.f, 1.f / 255.f)); if (normalized_distance < 0.00001f) { - return Vector3(__FLT_MAX__, __FLT_MAX__, __FLT_MAX__); + return V3_MAX; } // Necessary for a correct value depth = 1 if (normalized_distance > 0.9999f) { diff --git a/src/terrain_3d.h b/src/terrain_3d.h index f7e956f8..073e9886 100644 --- a/src/terrain_3d.h +++ b/src/terrain_3d.h @@ -49,7 +49,7 @@ class Terrain3D : public Node3D { uint64_t _camera_instance_id = 0; // X,Z Position of the camera during the previous snapping. Set to max real_t value to force a snap update. - Vector2 _camera_last_position = Vector2(__FLT_MAX__, __FLT_MAX__); + Vector2 _camera_last_position = V2_MAX; // Meshes and Mesh instances Vector _meshes; diff --git a/src/terrain_3d_assets.cpp b/src/terrain_3d_assets.cpp index fbd380d7..d7592c55 100644 --- a/src/terrain_3d_assets.cpp +++ b/src/terrain_3d_assets.cpp @@ -166,8 +166,8 @@ void Terrain3DAssets::_update_texture_files() { // Detect image sizes and formats LOG(INFO, "Validating texture sizes"); - Vector2i albedo_size = Vector2i(0, 0); - Vector2i normal_size = Vector2i(0, 0); + Vector2i albedo_size = V2I_ZERO; + Vector2i normal_size = V2I_ZERO; Image::Format albedo_format = Image::FORMAT_MAX; Image::Format normal_format = Image::FORMAT_MAX; @@ -221,19 +221,19 @@ void Terrain3DAssets::_update_texture_files() { } } - if (normal_size == Vector2i(0, 0)) { + if (normal_size == V2I_ZERO) { normal_size = albedo_size; - } else if (albedo_size == Vector2i(0, 0)) { + } else if (albedo_size == V2I_ZERO) { albedo_size = normal_size; } - if (albedo_size == Vector2i(0, 0)) { + if (albedo_size == V2I_ZERO) { albedo_size = Vector2i(1024, 1024); normal_size = Vector2i(1024, 1024); } // Generate TextureArrays and replace nulls with a empty image - if (_generated_albedo_textures.is_dirty() && albedo_size != Vector2i(0, 0)) { + if (_generated_albedo_textures.is_dirty() && albedo_size != V2I_ZERO) { LOG(INFO, "Regenerating albedo texture array"); Array albedo_texture_array; for (int i = 0; i < _texture_list.size(); i++) { @@ -259,7 +259,7 @@ void Terrain3DAssets::_update_texture_files() { } } - if (_generated_normal_textures.is_dirty() && normal_size != Vector2i(0, 0)) { + if (_generated_normal_textures.is_dirty() && normal_size != V2I_ZERO) { LOG(INFO, "Regenerating normal texture arrays"); Array normal_texture_array; diff --git a/src/terrain_3d_instancer.cpp b/src/terrain_3d_instancer.cpp index debd3cd2..877df95e 100644 --- a/src/terrain_3d_instancer.cpp +++ b/src/terrain_3d_instancer.cpp @@ -34,7 +34,7 @@ void Terrain3DInstancer::_update_mmis(const Vector2i &p_region_loc, const int p_ // For specified region_location, or max for all Array region_locations; - if (p_region_loc == Vector2i(INT32_MAX, INT32_MAX)) { + if (p_region_loc.x == INT32_MAX) { region_locations = region_dict.keys(); } else { region_locations.push_back(p_region_loc); diff --git a/src/terrain_3d_instancer.h b/src/terrain_3d_instancer.h index 38445dad..da4e25dc 100644 --- a/src/terrain_3d_instancer.h +++ b/src/terrain_3d_instancer.h @@ -30,7 +30,7 @@ class Terrain3DInstancer : public Object { int _get_instace_count(const real_t p_density); void _rebuild_mmis(); - void _update_mmis(const Vector2i &p_region_loc = Vector2i(INT32_MAX, INT32_MAX), const int p_mesh_id = -1); + void _update_mmis(const Vector2i &p_region_loc = V2I_MAX, const int p_mesh_id = -1); void _destroy_mmi_by_region_id(const int p_region, const int p_mesh_id); void _destroy_mmi_by_location(const Vector2i &p_region_loc, const int p_mesh_id); diff --git a/src/terrain_3d_region.h b/src/terrain_3d_region.h index 485ae631..88f0eead 100644 --- a/src/terrain_3d_region.h +++ b/src/terrain_3d_region.h @@ -53,7 +53,7 @@ class Terrain3DRegion : public Resource { Dictionary _multimeshes; // Workind data not saved to disk - Vector2i _location = Vector2i(INT32_MAX, INT32_MAX); + Vector2i _location = V2I_MAX; bool _modified = false; public: diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index 86f67636..433849c3 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -191,7 +191,7 @@ Error Terrain3DStorage::add_region(const Vector3 &p_global_position, const Typed // Check this later, per region // If we're importing data into a region, check its heights for aabbs - /*Vector2 min_max = Vector2(0.f, 0.f); + /*Vector2 min_max = V2_ZERO; if (p_images.size() > TYPE_HEIGHT) { min_max = Util::get_min_max(images[TYPE_HEIGHT]); LOG(DEBUG, "Checking imported height range: ", min_max); @@ -560,7 +560,7 @@ void Terrain3DStorage::set_pixel(const MapType p_map_type, const Vector3 &p_glob Vector2i global_offset = region_loc * _region_size; Vector3 descaled_pos = p_global_position / _mesh_vertex_spacing; Vector2i img_pos = Vector2i(descaled_pos.x - global_offset.x, descaled_pos.z - global_offset.y); - img_pos = img_pos.clamp(Vector2i(), Vector2i(_region_size - 1, _region_size - 1)); + img_pos = img_pos.clamp(V2I_ZERO, Vector2i(_region_size - 1, _region_size - 1)); Ref map = get_map_region(p_map_type, region_id); map->set_pixelv(img_pos, p_pixel); set_region_modified(region_loc, true); @@ -579,7 +579,7 @@ Color Terrain3DStorage::get_pixel(const MapType p_map_type, const Vector3 &p_glo Vector2i global_offset = region_loc * _region_size; Vector3 descaled_pos = p_global_position / _mesh_vertex_spacing; Vector2i img_pos = Vector2i(descaled_pos.x - global_offset.x, descaled_pos.z - global_offset.y); - img_pos = img_pos.clamp(Vector2i(), Vector2i(_region_size - 1, _region_size - 1)); + img_pos = img_pos.clamp(V2I_ZERO, Vector2i(_region_size - 1, _region_size - 1)); Ref map = get_map_region(p_map_type, region_id); return map->get_pixelv(img_pos); } @@ -756,7 +756,7 @@ void Terrain3DStorage::import_images(const TypedArray &p_images, const Ve return; } - Vector2i img_size = Vector2i(0, 0); + Vector2i img_size = V2I_ZERO; for (int i = 0; i < TYPE_MAX; i++) { Ref img = p_images[i]; if (img.is_valid() && !img->is_empty()) { @@ -764,7 +764,7 @@ void Terrain3DStorage::import_images(const TypedArray &p_images, const Ve if (i == TYPE_HEIGHT) { LOG(INFO, "Applying offset: ", p_offset, ", scale: ", p_scale); } - if (img_size == Vector2i(0, 0)) { + if (img_size == V2I_ZERO) { img_size = img->get_size(); } else if (img_size != img->get_size()) { LOG(ERROR, "Included Images in p_images have different dimensions. Aborting import"); @@ -772,7 +772,7 @@ void Terrain3DStorage::import_images(const TypedArray &p_images, const Ve } } } - if (img_size == Vector2i(0, 0)) { + if (img_size == V2I_ZERO) { LOG(ERROR, "All images are empty. Nothing to import"); return; } @@ -845,7 +845,7 @@ void Terrain3DStorage::import_images(const TypedArray &p_images, const Ve Ref img_slice; if (img.is_valid() && !img->is_empty()) { img_slice = Util::get_filled_image(_region_sizev, COLOR[i], false, img->get_format()); - img_slice->blit_rect(tmp_images[i], Rect2i(start_coords, size_to_copy), Vector2i(0, 0)); + img_slice->blit_rect(tmp_images[i], Rect2i(start_coords, size_to_copy), V2I_ZERO); } else { img_slice = Util::get_filled_image(_region_sizev, COLOR[i], false, FORMAT[i]); } @@ -960,8 +960,8 @@ Ref Terrain3DStorage::layered_to_image(const MapType p_map_type) const { if (map_type >= TYPE_MAX) { map_type = TYPE_HEIGHT; } - Vector2i top_left = Vector2i(0, 0); - Vector2i bottom_right = Vector2i(0, 0); + Vector2i top_left = V2I_ZERO; + Vector2i bottom_right = V2I_ZERO; for (int i = 0; i < _region_locations.size(); i++) { LOG(DEBUG, "Region locations[", i, "]: ", _region_locations[i]); Vector2i region_loc = _region_locations[i]; @@ -987,7 +987,7 @@ Ref Terrain3DStorage::layered_to_image(const MapType p_map_type) const { Vector2i img_location = (region_loc - top_left) * _region_size; LOG(DEBUG, "Region to blit: ", region_loc, " Export image coords: ", img_location); int region_id = get_region_idp(Vector3(region_loc.x, 0, region_loc.y) * _region_size); - img->blit_rect(get_map_region(map_type, region_id), Rect2i(Vector2i(0, 0), _region_sizev), img_location); + img->blit_rect(get_map_region(map_type, region_id), Rect2i(V2I_ZERO, _region_sizev), img_location); } return img; } diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index 0d08ebc1..6dd0da36 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -168,7 +168,7 @@ class Terrain3DStorage : public Object { void register_region(const Ref &p_region); TypedArray get_regions_under_aabb(const AABB &p_aabb); - void import_images(const TypedArray &p_images, const Vector3 &p_global_position = Vector3(0.f, 0.f, 0.f), + void import_images(const TypedArray &p_images, const Vector3 &p_global_position = V3_ZERO, const real_t p_offset = 0.f, const real_t p_scale = 1.f); Error export_image(const String &p_file_name, const MapType p_map_type = TYPE_HEIGHT) const; Ref layered_to_image(const MapType p_map_type) const; @@ -208,7 +208,7 @@ inline Vector2i Terrain3DStorage::get_region_location(const Vector3 &p_global_po // Returns Vector2i(2147483647) if out of range inline Vector2i Terrain3DStorage::get_region_locationi(const int p_region_id) const { if (p_region_id < 0 || p_region_id >= _region_locations.size()) { - return Vector2i(INT32_MAX, INT32_MAX); + return V2I_MAX; } return _region_locations[p_region_id]; } diff --git a/src/terrain_3d_util.cpp b/src/terrain_3d_util.cpp index a19fe43c..12bbd7c3 100644 --- a/src/terrain_3d_util.cpp +++ b/src/terrain_3d_util.cpp @@ -38,7 +38,7 @@ Vector2i Terrain3DUtil::filename_to_location(const String &p_filename) { String x_str = working_string.right(3).replace("_", ""); if (!x_str.is_valid_int() || !y_str.is_valid_int()) { LOG(ERROR, "Malformed filename at ", p_filename, ": got x ", x_str, " y ", y_str); - return Vector2i(INT32_MAX, INT32_MAX); + return V2I_MAX; } return Vector2i(x_str.to_int(), y_str.to_int()); } @@ -79,7 +79,7 @@ Vector2 Terrain3DUtil::get_min_max(const Ref &p_image) { return Vector2(INFINITY, INFINITY); } - Vector2 min_max = Vector2(0.f, 0.f); + Vector2 min_max = V2_ZERO; for (int y = 0; y < p_image->get_height(); y++) { for (int x = 0; x < p_image->get_width(); x++) { @@ -200,7 +200,7 @@ Ref Terrain3DUtil::get_filled_image(const Vector2i &p_size, const Color & color.a = 1.0f; Color col_a = Color(0.8f, 0.8f, 0.8f, 1.0) * color; Color col_b = Color(0.5f, 0.5f, 0.5f, 1.0) * color; - img->fill_rect(Rect2i(Vector2i(0, 0), p_size / 2), col_a); + img->fill_rect(Rect2i(V2I_ZERO, p_size / 2), col_a); img->fill_rect(Rect2i(p_size / 2, p_size / 2), col_a); img->fill_rect(Rect2i(Vector2(p_size.x, 0) / 2, p_size / 2), col_b); img->fill_rect(Rect2i(Vector2(0, p_size.y) / 2, p_size / 2), col_b); @@ -247,7 +247,7 @@ Ref Terrain3DUtil::load_image(const String &p_file_name, const int p_cach Ref file = FileAccess::open(p_file_name, FileAccess::READ); // If p_size is zero, assume square and try to auto detect size Vector2i r16_size = p_r16_size; - if (r16_size <= Vector2i(0, 0)) { + if (r16_size <= V2I_ZERO) { file->seek_end(); int fsize = file->get_position(); int fwidth = sqrt(fsize / 2); @@ -352,6 +352,6 @@ void Terrain3DUtil::_bind_methods() { ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("get_min_max", "image"), &Terrain3DUtil::get_min_max); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("get_thumbnail", "image", "size"), &Terrain3DUtil::get_thumbnail, DEFVAL(Vector2i(256, 256))); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("get_filled_image", "size", "color", "create_mipmaps", "format"), &Terrain3DUtil::get_filled_image); - ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("load_image", "file_name", "cache_mode", "r16_height_range", "r16_size"), &Terrain3DUtil::load_image, DEFVAL(ResourceLoader::CACHE_MODE_IGNORE), DEFVAL(Vector2(0, 255)), DEFVAL(Vector2i(0, 0))); + ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("load_image", "file_name", "cache_mode", "r16_height_range", "r16_size"), &Terrain3DUtil::load_image, DEFVAL(ResourceLoader::CACHE_MODE_IGNORE), DEFVAL(Vector2(0, 255)), DEFVAL(V2I_ZERO)); ClassDB::bind_static_method("Terrain3DUtil", D_METHOD("pack_image", "src_rgb", "src_r", "invert_green_channel"), &Terrain3DUtil::pack_image, DEFVAL(false)); } diff --git a/src/terrain_3d_util.h b/src/terrain_3d_util.h index 22b0645f..2638bde2 100644 --- a/src/terrain_3d_util.h +++ b/src/terrain_3d_util.h @@ -39,7 +39,7 @@ class Terrain3DUtil : public Object { const bool p_create_mipmaps = true, const Image::Format p_format = Image::FORMAT_MAX); static Ref load_image(const String &p_file_name, const int p_cache_mode = ResourceLoader::CACHE_MODE_IGNORE, - const Vector2 &p_r16_height_range = Vector2(0.f, 255.f), const Vector2i &p_r16_size = Vector2i(0, 0)); + const Vector2 &p_r16_height_range = Vector2(0.f, 255.f), const Vector2i &p_r16_size = V2I_ZERO); static Ref pack_image(const Ref &p_src_rgb, const Ref &p_src_r, const bool p_invert_green_channel = false); protected: From d987426b27b3304c85741e37948655f1026ac834 Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:33:43 +0700 Subject: [PATCH 12/19] Organize storage header --- src/terrain_3d_storage.cpp | 10 +-- src/terrain_3d_storage.h | 127 ++++++++++++++++++++----------------- 2 files changed, 75 insertions(+), 62 deletions(-) diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index 433849c3..d1f1bbfd 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -317,7 +317,7 @@ void Terrain3DStorage::update_maps() { } } -void Terrain3DStorage::save_region(const String &p_dir, const Vector2i &p_region_loc, const bool p_16_bit) { +void Terrain3DStorage::save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit) { Ref region = _regions[p_region_loc]; // looks like get isn't required if (region.is_null()) { @@ -331,7 +331,7 @@ void Terrain3DStorage::save_region(const String &p_dir, const Vector2i &p_region region->save(path, p_16_bit); } -void Terrain3DStorage::load_region(const String &p_dir, const Vector2i &p_region_loc) { +void Terrain3DStorage::load_region(const Vector2i &p_region_loc, const String &p_dir) { LOG(INFO, "Loading region from location ", p_region_loc); String path = p_dir + String("/") + Util::location_to_filename(p_region_loc); Ref region = ResourceLoader::get_singleton()->load(path, @@ -736,7 +736,7 @@ Dictionary Terrain3DStorage::get_multimeshes(const TypedArray &p_region_ids void Terrain3DStorage::save_directory(const String &p_dir) { LOG(INFO, "Saving data files to ", p_dir); for (int i = 0; i < _region_locations.size(); i++) { - save_region(p_dir, _region_locations[i], _terrain->get_save_16_bit()); + save_region(_region_locations[i], p_dir, _terrain->get_save_16_bit()); } } @@ -1174,6 +1174,8 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("get_texture_id", "global_position"), &Terrain3DStorage::get_texture_id); ClassDB::bind_method(D_METHOD("get_angle", "global_position"), &Terrain3DStorage::get_angle); ClassDB::bind_method(D_METHOD("get_scale", "global_position"), &Terrain3DStorage::get_scale); + ClassDB::bind_method(D_METHOD("get_mesh_vertex", "lod", "filter", "global_position"), &Terrain3DStorage::get_mesh_vertex); + ClassDB::bind_method(D_METHOD("get_normal", "global_position"), &Terrain3DStorage::get_normal); ClassDB::bind_method(D_METHOD("force_update_maps", "map_type"), &Terrain3DStorage::force_update_maps, DEFVAL(TYPE_MAX)); //ClassDB::bind_method(D_METHOD("set_multimeshes", "multimeshes"), &Terrain3DStorage::set_multimeshes); @@ -1183,8 +1185,6 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("export_image", "file_name", "map_type"), &Terrain3DStorage::export_image); ClassDB::bind_method(D_METHOD("layered_to_image", "map_type"), &Terrain3DStorage::layered_to_image); - ClassDB::bind_method(D_METHOD("get_mesh_vertex", "lod", "filter", "global_position"), &Terrain3DStorage::get_mesh_vertex); - ClassDB::bind_method(D_METHOD("get_normal", "global_position"), &Terrain3DStorage::get_normal); int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; //ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "64:64, 128:128, 256:256, 512:512, 1024:1024, 2048:2048"), "set_region_size", "get_region_size"); diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index 6dd0da36..93df7142 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -17,7 +17,7 @@ class Terrain3DStorage : public Object { friend Terrain3D; public: // Constants - static inline const real_t CURRENT_VERSION = 0.92f; + static inline const real_t CURRENT_VERSION = 0.93f; static inline const int REGION_MAP_SIZE = 16; static inline const Vector2i REGION_MAP_VSIZE = Vector2i(REGION_MAP_SIZE, REGION_MAP_SIZE); @@ -39,43 +39,50 @@ class Terrain3DStorage : public Object { Terrain3D *_terrain = nullptr; // Storage Settings & flags - RegionSize _region_size = SIZE_1024; - Vector2i _region_sizev = Vector2i(_region_size, _region_size); real_t _mesh_vertex_spacing = 1.f; // Set by Terrain3D::set_mesh_vertex_spacing bool _loading = false; // tracking when we're loading so we don't add_region w/ update - // TODO: Should be in Terrain3D or per region so its saved - Vector2 _height_range = Vector2(0.f, 0.f); - - // Work data - bool _region_map_dirty = true; - PackedInt32Array _region_map; // 16x16 Region grid with index into region_locations (1 based array) - // Generated Texture RIDs - // These contain the TextureLayered RID from the RenderingServer, no Image - GeneratedTexture _generated_height_maps; - GeneratedTexture _generated_control_maps; - GeneratedTexture _generated_color_maps; - AABB _edited_area; uint64_t _last_region_bounds_error = 0; - /** - * These arrays house all of the map data. - * The Image arrays are region_sized slices of all heightmap data. Their world - * location is tracked by region_locations. The region data are combined into one large - * texture in generated_*_maps. - */ + ///////// + // Region data are dual indexed. 1) By `region_location:Vector2i` as the primary key, + // stored in the `_regions` Dictionary. 2) By a mutable `region_id:int` stored in the + // arrays below. + // + // `_regions` stores all loaded Terrain3DRegions, indexed by region_location. This is + // the only stable index so should be the main index for users. Terrain3DRegion + // houses the maps, instances, and other data for the region. + + Dictionary _regions; // Dict[region_location:Vector2i] -> Terrain3DRegion + + // All region maps are maintained in secondary indices. These arrays provide direct + // access to maps in the regions, indexed by a region_id. This index changes on + // every add/remove, depends on load order and is not stable, so should not be + // relied on by users. These arrays are converted to TextureArrays for the shader. + TypedArray _region_locations; TypedArray _height_maps; TypedArray _control_maps; TypedArray _color_maps; - // Dictionary[region_location:Vector2i] -> Terrain3DRegion - Dictionary _regions; + // Editing occurs on the arrays above, then get converted to the generated arrays + // below for the shader. + + // 16x16 grid with region_id at its location, no region = 0, region_ids >= 1 + PackedInt32Array _region_map; + bool _region_map_dirty = true; + // These contain the TextureArray RIDs from the RenderingServer + GeneratedTexture _generated_height_maps; + GeneratedTexture _generated_control_maps; + GeneratedTexture _generated_color_maps; // Foliage Instancer contains MultiMeshes saved to disk // Dictionary[region_location:Vector2i] -> Dictionary[mesh_id:int] -> MultiMesh Dictionary _multimeshes; + Vector2 _height_range = Vector2(0.f, 0.f); + RegionSize _region_size = SIZE_1024; + Vector2i _region_sizev = Vector2i(_region_size, _region_size); // Functions void _clear(); @@ -86,42 +93,44 @@ class Terrain3DStorage : public Object { void initialize(Terrain3D *p_terrain); ~Terrain3DStorage() { _clear(); } - Vector2i get_region_location(const Vector3 &p_global_position) const; - Vector2i get_region_locationi(const int p_region_id) const; - int get_region_id(const Vector2i &p_region_loc) const; - int get_region_idp(const Vector3 &p_global_position) const; - bool has_region(const Vector2i &p_region_loc) const { return get_region_id(p_region_loc) != -1; } - bool has_regionp(const Vector3 &p_global_position) const { return get_region_idp(p_global_position) != -1; } - void set_height_range(const Vector2 &p_range); - Vector2 get_height_range() const { return _height_range; } - void update_heights(const real_t p_height); - void update_heights(const Vector2 &p_heights); - void update_height_range(); + /// Internal functions should be by region_id or region_location + /// External functions by region_location or global_position + /// look at godot's naming conventions + /// Conversion functions, many can probably be inline, static or in util - void clear_edited_area(); - void add_edited_area(const AABB &p_area); - AABB get_edited_area() const { return _edited_area; } + /// Regions - // Regions + void set_region_size(const RegionSize p_size); + RegionSize get_region_size() const { return _region_size; } + Vector2i get_region_sizev() const { return _region_sizev; } + int get_region_count() const { return _region_locations.size(); } Dictionary get_regions() { return _regions; } Ref get_region(const Vector2i &p_region_loc); void set_region_modified(const Vector2i &p_region_loc, const bool p_modified = true); bool get_region_modified(const Vector2i &p_region_loc) const; - void set_region_size(const RegionSize p_size); - RegionSize get_region_size() const { return _region_size; } - Vector2i get_region_sizev() const { return _region_sizev; } - void set_region_locations(const TypedArray &p_locations); + Vector2i get_region_location(const Vector3 &p_global_position) const; + Vector2i get_region_locationi(const int p_region_id) const; + int get_region_id(const Vector2i &p_region_loc) const; + int get_region_idp(const Vector3 &p_global_position) const; + bool has_region(const Vector2i &p_region_loc) const { return get_region_id(p_region_loc) != -1; } + bool has_regionp(const Vector3 &p_global_position) const { return get_region_idp(p_global_position) != -1; } TypedArray get_region_locations() const { return _region_locations; } PackedInt32Array get_region_map() const { return _region_map; } - int get_region_count() const { return _region_locations.size(); } Error add_region(const Vector3 &p_global_position, const TypedArray &p_images = TypedArray(), const bool p_update = true, const String &p_path = ""); void remove_region(const Vector3 &p_global_position, const bool p_update = true, const String &p_path = ""); void remove_region_by_id(const int p_region_id, const bool p_update = true, const String &p_path = ""); - void update_maps(); + + // File I/O + void save_directory(const String &p_dir); + void load_directory(const String &p_dir); + void save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit = false); + void load_region(const Vector2i &p_region_loc, const String &p_dir); + + void set_region_locations(const TypedArray &p_locations); // Maps void set_map_region(const MapType p_map_type, const int p_region_id, const Ref &p_image); @@ -151,31 +160,35 @@ class Terrain3DStorage : public Object { Vector3 get_texture_id(const Vector3 &p_global_position) const; real_t get_angle(const Vector3 &p_global_position) const; real_t get_scale(const Vector3 &p_global_position) const; + Vector3 get_mesh_vertex(const int32_t p_lod, const HeightFilter p_filter, const Vector3 &p_global_position) const; + Vector3 get_normal(const Vector3 &global_position) const; + void update_maps(); void force_update_maps(const MapType p_map = TYPE_MAX); - // Instancer - void set_multimeshes(const Dictionary &p_multimeshes); - void set_multimeshes(const Dictionary &p_multimeshes, const TypedArray &p_region_ids); - Dictionary get_multimeshes() const { return _multimeshes; } - Dictionary get_multimeshes(const TypedArray &p_region_ids) const; - - // File I/O - void save_directory(const String &p_dir); - void load_directory(const String &p_dir); - void save_region(const String &p_dir, const Vector2i &p_region_loc, const bool p_16_bit = false); - void load_region(const String &p_dir, const Vector2i &p_region_loc); + void clear_edited_area(); + void add_edited_area(const AABB &p_area); + AABB get_edited_area() const { return _edited_area; } void register_region(const Ref &p_region); TypedArray get_regions_under_aabb(const AABB &p_aabb); + void set_height_range(const Vector2 &p_range); + Vector2 get_height_range() const { return _height_range; } + void update_heights(const real_t p_height); + void update_heights(const Vector2 &p_heights); + void update_height_range(); void import_images(const TypedArray &p_images, const Vector3 &p_global_position = V3_ZERO, const real_t p_offset = 0.f, const real_t p_scale = 1.f); Error export_image(const String &p_file_name, const MapType p_map_type = TYPE_HEIGHT) const; Ref layered_to_image(const MapType p_map_type) const; + // Instancer + void set_multimeshes(const Dictionary &p_multimeshes); + void set_multimeshes(const Dictionary &p_multimeshes, const TypedArray &p_region_ids); + Dictionary get_multimeshes() const { return _multimeshes; } + Dictionary get_multimeshes(const TypedArray &p_region_ids) const; + // Utility - Vector3 get_mesh_vertex(const int32_t p_lod, const HeightFilter p_filter, const Vector3 &p_global_position) const; - Vector3 get_normal(const Vector3 &global_position) const; void print_audit_data() const; protected: From 2b6e2fa919321ee6b0f2eeb4cdda86830382d9e6 Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Sat, 10 Aug 2024 17:21:17 +0700 Subject: [PATCH 13/19] Move individual height range tracking to Region, w/ master calculated in Storage; Fix sanitize_map for 16-bit --- src/terrain_3d_editor.cpp | 99 ++++----- src/terrain_3d_editor.h | 6 +- src/terrain_3d_region.cpp | 270 ++++++++++++++++------- src/terrain_3d_region.h | 32 ++- src/terrain_3d_storage.cpp | 437 ++++++++++++++++++------------------- src/terrain_3d_storage.h | 67 +++--- src/terrain_3d_util.cpp | 2 +- 7 files changed, 511 insertions(+), 402 deletions(-) diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp index 1902b794..5df097a6 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -12,67 +12,45 @@ // Private Functions /////////////////////////// -void Terrain3DEditor::_region_modified(const Vector3 &p_global_position, const Vector2 &p_height_range) { - Vector2i region_loc = _terrain->get_storage()->get_region_location(p_global_position); +// Sends the whole region aabb to edited_area +void Terrain3DEditor::_send_region_aabb(const Vector2i &p_region_loc, const Vector2 &p_height_range) { Terrain3DStorage::RegionSize region_size = _terrain->get_storage()->get_region_size(); - AABB edited_area; - edited_area.position = Vector3(region_loc.x * region_size, p_height_range.x, region_loc.y * region_size); + edited_area.position = Vector3(p_region_loc.x * region_size, p_height_range.x, p_region_loc.y * region_size); edited_area.size = Vector3(region_size, p_height_range.y - p_height_range.x, region_size); edited_area.position *= _terrain->get_mesh_vertex_spacing(); edited_area.size *= _terrain->get_mesh_vertex_spacing(); - - _modified = true; _terrain->get_storage()->add_edited_area(edited_area); } void Terrain3DEditor::_operate_region(const Vector3 &p_global_position) { - bool has_region = _terrain->get_storage()->has_regionp(p_global_position); + Vector2i region_loc = _terrain->get_storage()->get_region_location(p_global_position); + bool has_region = _terrain->get_storage()->has_region(region_loc); bool changed = false; Vector2 height_range; if (_operation == ADD) { if (!has_region) { - _terrain->get_storage()->add_region(p_global_position); + _terrain->get_storage()->add_region_blank(region_loc); + _terrain->get_storage()->set_region_modified(region_loc, true); changed = true; } - } else { + } else { // Remove region if (has_region) { - int region_id = _terrain->get_storage()->get_region_idp(p_global_position); - Ref height_map = _terrain->get_storage()->get_map_region(TYPE_HEIGHT, region_id); - height_range = Util::get_min_max(height_map); - - _terrain->get_storage()->remove_region(p_global_position, true, _terrain->get_storage_directory()); + Ref region = _terrain->get_storage()->get_region(region_loc); + height_range = region->get_height_range(); + _terrain->get_storage()->remove_region(region); changed = true; } } - if (changed) { - _region_modified(p_global_position, height_range); + _modified = true; + _send_region_aabb(region_loc, height_range); } } void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_t p_camera_direction) { LOG(DEBUG_CONT, "Operating at ", p_global_position, " tool type ", _tool, " op ", _operation); - Terrain3DStorage *storage = _terrain->get_storage(); - int region_size = storage->get_region_size(); - Vector2i region_vsize = Vector2i(region_size, region_size); - int region_id = storage->get_region_idp(p_global_position); - if (region_id == -1) { - if (!_brush_data["auto_regions"] || _tool != HEIGHT) { - return; - } else { - LOG(DEBUG, "No region to operate on, attempting to add"); - storage->add_region(p_global_position); - region_size = storage->get_region_size(); - region_id = storage->get_region_idp(p_global_position); - if (region_id == -1) { - LOG(ERROR, "Failed to add region, no region to operate on"); - return; - } - _region_modified(p_global_position); - } - } MapType map_type; switch (_tool) { @@ -97,6 +75,15 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ return; } + Terrain3DStorage *storage = _terrain->get_storage(); + int region_size = storage->get_region_size(); + Vector2i region_vsize = Vector2i(region_size, region_size); + + // If no region and not height, skip whole function + if (storage->has_regionp(p_global_position) && (!_brush_data["auto_regions"] || _tool != HEIGHT)) { + return; + } + Ref brush_image = _brush_data["brush_image"]; if (brush_image.is_null()) { LOG(ERROR, "Invalid brush image. Returning"); @@ -163,7 +150,6 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ } // MAP Operations - Ref map = storage->get_map_region(map_type, region_id); real_t vertex_spacing = _terrain->get_mesh_vertex_spacing(); for (real_t x = 0.f; x < brush_size; x += vertex_spacing) { for (real_t y = 0.f; y < brush_size; y += vertex_spacing) { @@ -172,24 +158,26 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ Vector3(p_global_position.x + brush_offset.x + .5f, p_global_position.y, p_global_position.z + brush_offset.y + .5f); - // If we're brushing across a region boundary, possibly add a region, and get the other map - int new_region_id = storage->get_region_idp(brush_global_position); - if (new_region_id == -1) { + // Get region for current brush pixel global position + Vector2i region_loc = storage->get_region_location(brush_global_position); + Ref region = storage->get_region(region_loc); + // If no region, create one + if (region.is_null()) { + // Except if outside of regions and we can't add one if (!_brush_data["auto_regions"] || _tool != HEIGHT) { continue; } - Error err = storage->add_region(brush_global_position); - if (err) { + region = storage->add_region_blank(region_loc); + if (region.is_null()) { + // A new region can't be made continue; } - new_region_id = storage->get_region_idp(brush_global_position); - _region_modified(brush_global_position); + _modified = true; + _send_region_aabb(region_loc); } - if (new_region_id != region_id) { - region_id = new_region_id; - map = storage->get_map_region(map_type, region_id); - } + // Get map for this region and tool + Ref map = region->get_map(map_type); // Identify position on map image Vector2 uv_position = _get_uv_position(brush_global_position, region_size, vertex_spacing); @@ -295,7 +283,9 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ break; } dest = Color(destf, 0.f, 0.f, 1.f); - storage->update_heights(destf); + region->update_height(destf); + // TODO Move this line to a signal sent from above line + storage->update_master_height(destf); edited_position.y = destf; edited_area = edited_area.expand(edited_position); @@ -448,10 +438,11 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ } } map->set_pixelv(map_pixel_position, dest); + _modified = true; + region->set_modified(true); } } } - _modified = true; storage->force_update_maps(map_type); storage->add_edited_area(edited_area); } @@ -618,7 +609,10 @@ void Terrain3DEditor::_apply_undo(const Dictionary &p_set) { map_info[TYPE_HEIGHT] = ((TypedArray)p_set["height_map"])[0]; map_info[TYPE_CONTROL] = ((TypedArray)p_set["control_map"])[0]; map_info[TYPE_COLOR] = ((TypedArray)p_set["color_map"])[0]; - _terrain->get_storage()->add_region(new_region_position, map_info); + Ref region; + region.instantiate(); + region->set_maps(map_info); + _terrain->get_storage()->add_region(region); } } } else { @@ -634,7 +628,7 @@ void Terrain3DEditor::_apply_undo(const Dictionary &p_set) { } else if (key == "color_map") { _terrain->get_storage()->set_maps(TYPE_COLOR, p_set[key], e_regions); } else if (key == "height_range") { - _terrain->get_storage()->set_height_range(p_set[key]); + //_terrain->get_storage()->set_height_range(p_set[key]); } else if (key == "edited_area") { _terrain->get_storage()->clear_edited_area(); _terrain->get_storage()->add_edited_area(p_set[key]); @@ -735,7 +729,7 @@ void Terrain3DEditor::start_operation(const Vector3 &p_global_position) { _undo_data.clear(); _undo_data = _get_undo_data(); _pending_undo = true; - _modified = false; + _modified = false; // Undo created, but don't save unless terrain modified // Reset counter at start to ensure first click places an instance _terrain->get_instancer()->reset_instance_counter(); _terrain->get_storage()->clear_edited_area(); @@ -777,6 +771,7 @@ void Terrain3DEditor::operate(const Vector3 &p_global_position, const real_t p_c // Called on left mouse button released void Terrain3DEditor::stop_operation() { IS_STORAGE_INIT_MESG("Terrain isn't initialized", VOID); + // If undo was created and terrain actually modified, store it if (_pending_undo && _modified) { _store_undo(); _pending_undo = false; diff --git a/src/terrain_3d_editor.h b/src/terrain_3d_editor.h index ab7e4c5b..0975f175 100644 --- a/src/terrain_3d_editor.h +++ b/src/terrain_3d_editor.h @@ -73,13 +73,13 @@ class Terrain3DEditor : public Object { Vector3 _operation_position = Vector3(); Vector3 _operation_movement = Vector3(); Array _operation_movement_history; - bool _pending_undo = false; - bool _modified = false; + bool _pending_undo = false; // Undo created on each click + bool _modified = false; // Tracks if any region is actually modified AABB _modified_area; Dictionary _undo_data; // See _get_undo_data for definition uint64_t _last_pen_tick = 0; - void _region_modified(const Vector3 &p_global_position, const Vector2 &p_height_range = Vector2()); + void _send_region_aabb(const Vector2i &p_region_loc, const Vector2 &p_height_range = Vector2()); void _operate_region(const Vector3 &p_global_position); void _operate_map(const Vector3 &p_global_position, const real_t p_camera_direction); bool _is_in_bounds(const Vector2i &p_position, const Vector2i &p_max_position) const; diff --git a/src/terrain_3d_region.cpp b/src/terrain_3d_region.cpp index 0293af15..61221361 100644 --- a/src/terrain_3d_region.cpp +++ b/src/terrain_3d_region.cpp @@ -11,15 +11,6 @@ // Public Functions ///////////////////// -Terrain3DRegion::Terrain3DRegion(const Ref &p_height_map, const Ref &p_control_map, - const Ref &p_color_map, const int p_region_size) { - _height_map = p_height_map; - _control_map = p_control_map; - _color_map = p_color_map; - _region_size = p_region_size; - sanitize_maps(); -} - void Terrain3DRegion::set_version(const real_t p_version) { LOG(INFO, vformat("%.3f", p_version)); _version = p_version; @@ -29,120 +20,239 @@ void Terrain3DRegion::set_version(const real_t p_version) { } } +void Terrain3DRegion::set_map(const MapType p_map_type, const Ref &p_image) { + switch (p_map_type) { + case TYPE_HEIGHT: + set_height_map(p_image); + break; + case TYPE_CONTROL: + set_control_map(p_image); + break; + case TYPE_COLOR: + set_color_map(p_image); + break; + default: + LOG(ERROR, "Requested map type is invalid"); + break; + } +} + +Ref Terrain3DRegion::get_map(const MapType p_map_type) const { + switch (p_map_type) { + case TYPE_HEIGHT: + return get_height_map(); + break; + case TYPE_CONTROL: + return get_control_map(); + break; + case TYPE_COLOR: + return get_color_map(); + break; + default: + LOG(ERROR, "Requested map type is invalid"); + return Ref(); + } +} + +void Terrain3DRegion::set_maps(const TypedArray &p_maps) { + if (p_maps.size() != TYPE_MAX) { + LOG(ERROR, "Expected ", TYPE_MAX - 1, " maps. Received ", p_maps.size()); + return; + } + LOG(INFO, "Setting maps for region: ", _location); + _height_map = p_maps[TYPE_HEIGHT]; + _control_map = p_maps[TYPE_CONTROL]; + _color_map = p_maps[TYPE_COLOR]; + sanitize_map(TYPE_MAX); +} + +TypedArray Terrain3DRegion::get_maps() const { + LOG(INFO, "Retrieving maps from region: ", _location); + TypedArray maps; + maps.push_back(_height_map); + maps.push_back(_control_map); + maps.push_back(_color_map); + return maps; +} + void Terrain3DRegion::set_height_map(const Ref &p_map) { - Image::Format format = FORMAT[TYPE_HEIGHT]; - // This is to convert format_RH to RF, but probably unnecessary w/ sanitize - /*if (p_map.is_valid() && p_map->get_format() != format) { - if (p_map.is_valid() && p_map->get_format() != format) { - LOG(DEBUG, "Converting file format: ", p_map->get_format(), " to ", format); - if (_height_map.is_null()) { - _height_map.instantiate(); - } - _height_map->copy_from(p_map); - _height_map->convert(format); - } else { - _height_map = p_map; - } */ - _height_map = sanitize_map(TYPE_HEIGHT, p_map); + LOG(INFO, "Setting height map for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)"); + _height_map = p_map; + sanitize_map(TYPE_HEIGHT); } void Terrain3DRegion::set_control_map(const Ref &p_map) { - _control_map = sanitize_map(TYPE_CONTROL, p_map); + LOG(INFO, "Setting control map for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)"); + _control_map = p_map; + sanitize_map(TYPE_CONTROL); } void Terrain3DRegion::set_color_map(const Ref &p_map) { - _color_map = sanitize_map(TYPE_COLOR, p_map); -} - -void Terrain3DRegion::sanitize_maps(const MapType p_map_type) { - LOG(INFO, "Verifying images maps are valid"); - if (p_map_type == TYPE_HEIGHT || p_map_type == TYPE_MAX) { - _height_map = sanitize_map(TYPE_HEIGHT, _height_map); - } - if (p_map_type == TYPE_CONTROL || p_map_type == TYPE_MAX) { - _control_map = sanitize_map(TYPE_CONTROL, _control_map); - } - if (p_map_type == TYPE_COLOR || p_map_type == TYPE_MAX) { - _color_map = sanitize_map(TYPE_COLOR, _color_map); - } + LOG(INFO, "Setting color map for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)"); + _color_map = p_map; + sanitize_map(TYPE_COLOR); } // Verifies region map is a valid size and format // Creates filled blanks if lacking -Ref Terrain3DRegion::sanitize_map(const MapType p_map_type, const Ref &p_img) const { - LOG(INFO, "Verifying images maps are valid"); - if (p_map_type < 0 || p_map_type >= TYPE_MAX) { +void Terrain3DRegion::sanitize_map(const MapType p_map_type) { + if (p_map_type < 0 || p_map_type > TYPE_MAX) { LOG(ERROR, "Invalid map type: ", p_map_type); - return Ref(); + return; } + LOG(INFO, "Verifying image maps type: ", TYPESTR[p_map_type], " are valid for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)"); - Image::Format format = FORMAT[p_map_type]; - const char *type_str = TYPESTR[p_map_type]; - Color color = COLOR[p_map_type]; + TypedArray queued_map_types; + if (p_map_type == TYPE_MAX) { + queued_map_types.push_back(TYPE_HEIGHT); + queued_map_types.push_back(TYPE_CONTROL); + queued_map_types.push_back(TYPE_COLOR); + } else { + queued_map_types.push_back(p_map_type); + } - if (p_img.is_valid()) { - if (p_img->get_size() == Vector2i(_region_size, _region_size)) { - if (p_img->get_format() == format) { - LOG(DEBUG, "Map type ", type_str, " correct format, size. Using image"); - return p_img; - } else { - LOG(DEBUG, "Provided ", type_str, " map wrong format: ", p_img->get_format(), ". Converting copy to: ", format); - Ref newimg; - newimg.instantiate(); - newimg->copy_from(p_img); - newimg->convert(format); - if (newimg->get_format() == format) { - return newimg; + for (int i = 0; i < queued_map_types.size(); i++) { + MapType type = (MapType)(int)queued_map_types[i]; + const char *type_str = TYPESTR[type]; + Image::Format format = FORMAT[type]; + Color color = COLOR[type]; + + Ref map = get_map(type); + + if (map.is_valid()) { + if (map->get_size() == Vector2i(_region_size, _region_size)) { + if (map->get_format() == format) { + LOG(DEBUG, "Map type ", type_str, " correct format, size. Using image"); + if (type == TYPE_HEIGHT) { + calc_height_range(); + } + continue; } else { - LOG(DEBUG, "Couldn't convert image to format: ", format, ". Creating blank "); + LOG(DEBUG, "Provided ", type_str, " map wrong format: ", map->get_format(), ". Converting copy to: ", format); + Ref newimg; + newimg.instantiate(); + newimg->copy_from(map); + newimg->convert(format); + if (newimg->get_format() == format) { + set_map(type, newimg); + continue; + } else { + LOG(DEBUG, "Cannot convert image to format: ", format, ". Creating blank "); + } } + } else { + LOG(DEBUG, "Provided ", type_str, " map wrong size: ", map->get_size(), ". Creating blank"); } } else { - LOG(DEBUG, "Provided ", type_str, " map wrong size: ", p_img->get_size(), ". Creating blank"); + LOG(DEBUG, "No provided ", type_str, " map. Creating blank"); } - } else { - LOG(DEBUG, "No provided ", type_str, " map. Creating blank"); + set_map(type, Util::get_filled_image(Vector2i(_region_size, _region_size), color, false, format)); + if (type == TYPE_HEIGHT) { + set_height_range(V2_ZERO); + } + } +} + +void Terrain3DRegion::set_height_range(const Vector2 &p_range) { + LOG(INFO, vformat("%.2v", p_range)); + if (_height_range != p_range) { + // If initial value, we're loading it from disk, else mark modified + if (_height_range != V2_ZERO) { + _modified = true; + } + _height_range = p_range; + } +} + +void Terrain3DRegion::update_height(const real_t p_height) { + if (p_height < _height_range.x) { + _height_range.x = p_height; + _modified = true; + } else if (p_height > _height_range.y) { + _height_range.y = p_height; + _modified = true; + } + if (_modified) { + LOG(DEBUG_CONT, "Expanded range: ", _height_range); + } +} + +void Terrain3DRegion::update_heights(const Vector2 &p_low_high) { + if (p_low_high.x < _height_range.x) { + _height_range.x = p_low_high.x; + _modified = true; + } + if (p_low_high.y > _height_range.y) { + _height_range.y = p_low_high.y; + _modified = true; + } + if (_modified) { + LOG(DEBUG_CONT, "Expanded range: ", _height_range); + } +} + +void Terrain3DRegion::calc_height_range() { + Vector2 range = Util::get_min_max(_height_map); + if (_height_range != range) { + _height_range = range; + _modified = true; + LOG(DEBUG, "Recalculated new height range: ", _height_range, " for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)", ". Marking modified"); } - return Util::get_filled_image(Vector2i(_region_size, _region_size), color, false, format); } Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) { // Initiate save to external file. The scene will save itself. + //LOG(WARN, "Saving region: ", _location, " modified: ", _modified, " path: ", get_path(), ", to : ", p_path); + if (_location.x == INT32_MAX) { + LOG(ERROR, "Region has not been setup. Location is INT32_MAX. Skipping ", p_path); + } if (!_modified) { - LOG(INFO, "Save requested, but not modified. Skipping"); + LOG(DEBUG, "Save requested for region ", _location, ", but not modified. Skipping ", p_path); return ERR_SKIP; } if (p_path.is_empty() && get_path().is_empty()) { LOG(ERROR, "No valid path provided"); return ERR_FILE_NOT_FOUND; } - String path = p_path; - if (path.is_empty()) { - path = get_path(); + if (get_path().is_empty() && !p_path.is_empty()) { + LOG(DEBUG, "Setting file path for region ", _location, " to ", p_path); + take_over_path(p_path); + // Set region path and take over the path from any other cached resources, + // incuding those in the undo queue } + LOG(INFO, "Writing", (p_16_bit) ? " 16-bit" : "", " region ", _location, " to ", get_path()); set_version(Terrain3DStorage::CURRENT_VERSION); - LOG(INFO, "Writing", (p_16_bit) ? " 16-bit" : "", " region ", _location, " to ", path); - Error err; if (p_16_bit) { Ref original_map; original_map.instantiate(); original_map->copy_from(_height_map); _height_map->convert(Image::FORMAT_RH); - err = ResourceSaver::get_singleton()->save(this, path, ResourceSaver::FLAG_COMPRESS); + err = ResourceSaver::get_singleton()->save(this, get_path(), ResourceSaver::FLAG_COMPRESS); _height_map = original_map; } else { - err = ResourceSaver::get_singleton()->save(this, path, ResourceSaver::FLAG_COMPRESS); + err = ResourceSaver::get_singleton()->save(this, get_path(), ResourceSaver::FLAG_COMPRESS); } if (err == OK) { _modified = false; LOG(INFO, "File saved successfully"); } else { - LOG(ERROR, "Can't save region file: ", path, ". Error code: ", ERROR, ". Look up @GlobalScope Error enum in the Godot docs"); + LOG(ERROR, "Cannot save region file: ", get_path(), ". Error code: ", ERROR, ". Look up @GlobalScope Error enum in the Godot docs"); } return err; } +void Terrain3DRegion::set_location(const Vector2i &p_location) { + // In the future anywhere they want to put the location might be fine, but because of region_map + // We have a limitation of 16x16 and eventually 45x45. + if (abs(p_location.x) < Terrain3DStorage::REGION_MAP_SIZE / 2 && abs(p_location.y) < Terrain3DStorage::REGION_MAP_SIZE / 2) { + LOG(INFO, "Set location: ", p_location); + _location = p_location; + } else { + LOG(ERROR, "Location out of bounds: ", p_location); + } +} + ///////////////////// // Protected Functions ///////////////////// @@ -162,23 +272,35 @@ void Terrain3DRegion::_bind_methods() { ClassDB::bind_method(D_METHOD("get_control_map"), &Terrain3DRegion::get_control_map); ClassDB::bind_method(D_METHOD("set_color_map", "map"), &Terrain3DRegion::set_color_map); ClassDB::bind_method(D_METHOD("get_color_map"), &Terrain3DRegion::get_color_map); + + ClassDB::bind_method(D_METHOD("set_height_range", "range"), &Terrain3DRegion::set_height_range); + ClassDB::bind_method(D_METHOD("get_height_range"), &Terrain3DRegion::get_height_range); + ClassDB::bind_method(D_METHOD("update_height", "height"), &Terrain3DRegion::update_height); + ClassDB::bind_method(D_METHOD("update_heights", "low_high"), &Terrain3DRegion::update_heights); + ClassDB::bind_method(D_METHOD("calc_height_range"), &Terrain3DRegion::calc_height_range); + ClassDB::bind_method(D_METHOD("set_multimeshes", "multimeshes"), &Terrain3DRegion::set_multimeshes); ClassDB::bind_method(D_METHOD("get_multimeshes"), &Terrain3DRegion::get_multimeshes); ClassDB::bind_method(D_METHOD("save", "path", "16-bit"), &Terrain3DRegion::save, DEFVAL(""), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("set_modified"), &Terrain3DRegion::set_modified); ClassDB::bind_method(D_METHOD("is_modified"), &Terrain3DRegion::is_modified); + ClassDB::bind_method(D_METHOD("set_deleted"), &Terrain3DRegion::set_deleted); + ClassDB::bind_method(D_METHOD("is_deleted"), &Terrain3DRegion::is_deleted); ClassDB::bind_method(D_METHOD("set_location"), &Terrain3DRegion::set_location); ClassDB::bind_method(D_METHOD("get_location"), &Terrain3DRegion::get_location); int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "version", PROPERTY_HINT_NONE, "", ro_flags), "set_version", "get_version"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "height_range", PROPERTY_HINT_NONE, "", ro_flags), "set_height_range", "get_height_range"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "heightmap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_height_map", "get_height_map"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "controlmap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_control_map", "get_control_map"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "colormap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_color_map", "get_color_map"); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "multimeshes", PROPERTY_HINT_NONE, "", ro_flags), "set_multimeshes", "get_multimeshes"); - // The inspector only shows what's on disk, not what is in memory, so hide them. Being generated, they only show defaults - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "modified", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "is_modified"); + // Double-clicking a region .res file shows what's on disk, the defaults, not in memory. So these are hidden + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "modified", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_modified", "is_modified"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deleted", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_deleted", "is_deleted"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "location", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_location", "get_location"); } diff --git a/src/terrain_3d_region.h b/src/terrain_3d_region.h index 88f0eead..b640c6c9 100644 --- a/src/terrain_3d_region.h +++ b/src/terrain_3d_region.h @@ -42,37 +42,47 @@ class Terrain3DRegion : public Resource { }; private: - // Saved data + /// Saved data real_t _version = 0.8f; // Set to first version to ensure Godot always upgrades this int _region_size = 1024; + Vector2 _height_range = V2_ZERO; // Maps Ref _height_map; Ref _control_map; Ref _color_map; - // Dictionary[mesh_id:int] -> MultiMesh - Dictionary _multimeshes; + // Instancer + Dictionary _multimeshes; // Dictionary[mesh_id:int] -> MultiMesh - // Workind data not saved to disk + // Working data not saved to disk Vector2i _location = V2I_MAX; bool _modified = false; + bool _deleted = false; public: Terrain3DRegion() {} - Terrain3DRegion(const Ref &p_height_map, const Ref &p_control_map, const Ref &p_color_map, const int p_region_size = 1024); ~Terrain3DRegion() {} void set_version(const real_t p_version); real_t get_version() const { return _version; } // Maps + void set_map(const MapType p_map_type, const Ref &p_image); + Ref get_map(const MapType p_map_type) const; + void set_maps(const TypedArray &p_maps); + TypedArray get_maps() const; void set_height_map(const Ref &p_map); Ref get_height_map() const { return _height_map; } void set_control_map(const Ref &p_map); Ref get_control_map() const { return _control_map; } void set_color_map(const Ref &p_map); Ref get_color_map() const { return _color_map; } - void sanitize_maps(const MapType p_map_type = TYPE_MAX); - Ref sanitize_map(const MapType p_map_type, const Ref &p_img) const; + void sanitize_map(const MapType p_map_type = TYPE_MAX); + + void set_height_range(const Vector2 &p_range); + Vector2 get_height_range() const { return _height_range; } + void update_height(const real_t p_height); + void update_heights(const Vector2 &p_low_high); + void calc_height_range(); // Instancer void set_multimeshes(const Dictionary &p_multimeshes) { _multimeshes = p_multimeshes; } @@ -81,12 +91,14 @@ class Terrain3DRegion : public Resource { // File I/O Error save(const String &p_path = "", const bool p_16_bit = false); - // Working + // Working Data void set_modified(const bool p_modified) { _modified = p_modified; } bool is_modified() const { return _modified; } - void set_location(const Vector2i &p_location) { _location = p_location; } + void set_deleted(const bool p_deleted) { _deleted = p_deleted; } + bool is_deleted() const { return _deleted; } + void set_location(const Vector2i &p_location); Vector2i get_location() const { return _location; } - void set_region_size(const int p_region_size) { _region_size = p_region_size; } + void set_region_size(const int p_region_size) { _region_size = CLAMP(p_region_size, 1024, 1024); } int get_region_size() const { return _region_size; } protected: diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index d1f1bbfd..fa15fc85 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -19,7 +19,9 @@ void Terrain3DStorage::_clear() { LOG(INFO, "Clearing storage"); _region_map_dirty = true; _region_map.clear(); + _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); _regions.clear(); + _master_height_range = V2_ZERO; _generated_height_maps.clear(); _generated_control_maps.clear(); _generated_color_maps.clear(); @@ -45,47 +47,40 @@ void Terrain3DStorage::initialize(Terrain3D *p_terrain) { } } -void Terrain3DStorage::set_height_range(const Vector2 &p_range) { - LOG(INFO, vformat("%.2v", p_range)); - _height_range = p_range; -} - -// TODO: _height_range should move to Terrain3D, or the next 3 functions should move -// to individual Regions and AABBs are updated from loaded regions. -// modified might be edited only by the editor -void Terrain3DStorage::update_heights(const real_t p_height) { - if (p_height < _height_range.x) { - _height_range.x = p_height; - //_modified = true; - } else if (p_height > _height_range.y) { - _height_range.y = p_height; - //_modified = true; - } - //if (_modified) { - // LOG(DEBUG_CONT, "Expanded height range: ", _height_range); - //} +void Terrain3DStorage::update_master_height(const real_t p_height) { + if (p_height < _master_height_range.x) { + _master_height_range.x = p_height; + } else if (p_height > _master_height_range.y) { + _master_height_range.y = p_height; + } } -void Terrain3DStorage::update_heights(const Vector2 &p_heights) { - if (p_heights.x < _height_range.x) { - _height_range.x = p_heights.x; - //_modified = true; +void Terrain3DStorage::update_master_heights(const Vector2 &p_low_high) { + if (p_low_high.x < _master_height_range.x) { + _master_height_range.x = p_low_high.x; } - if (p_heights.y > _height_range.y) { - _height_range.y = p_heights.y; - //_modified = true; + if (p_low_high.y > _master_height_range.y) { + _master_height_range.y = p_low_high.y; } - //if (_modified) { - // LOG(DEBUG_CONT, "Expanded height range: ", _height_range); - //} } -void Terrain3DStorage::update_height_range() { - _height_range = Vector2(0.f, 0.f); - for (int i = 0; i < _height_maps.size(); i++) { - update_heights(Util::get_min_max(_height_maps[i])); +// Recalculates master height range from all active regions current height ranges +// Recursive mode has all regions to recalculate from each heightmap pixel +void Terrain3DStorage::calc_height_range(const bool p_recursive) { + _master_height_range = V2_ZERO; + for (int i = 0; i < _region_locations.size(); i++) { + Vector2i region_loc = _region_locations[i]; + Ref region = _regions[region_loc]; + if (region.is_null()) { + LOG(ERROR, "Region not found at: ", region_loc); + return; + } + if (p_recursive) { + region->calc_height_range(); + } + update_master_heights(region->get_height_range()); } - LOG(INFO, "Recalculated terrain height range: ", _height_range); + LOG(DEBUG_CONT, "Accumulated height range for all regions: ", _master_height_range); } void Terrain3DStorage::clear_edited_area() { @@ -101,12 +96,8 @@ void Terrain3DStorage::add_edited_area(const AABB &p_area) { emit_signal("maps_edited", _edited_area); } -Ref Terrain3DStorage::get_region(const Vector2i &p_region_loc) { - return _regions.get(p_region_loc, Ref()); -} - void Terrain3DStorage::set_region_modified(const Vector2i &p_region_loc, const bool p_modified) { - Ref region = _regions.get(p_region_loc, Ref()); + Ref region = _regions[p_region_loc]; if (region.is_null()) { LOG(ERROR, "Region not found at: ", p_region_loc); return; @@ -114,8 +105,8 @@ void Terrain3DStorage::set_region_modified(const Vector2i &p_region_loc, const b return region->set_modified(p_modified); } -bool Terrain3DStorage::get_region_modified(const Vector2i &p_region_loc) const { - Ref region = _regions.get(p_region_loc, Ref()); +bool Terrain3DStorage::is_region_modified(const Vector2i &p_region_loc) const { + Ref region = _regions[p_region_loc]; if (region.is_null()) { LOG(ERROR, "Region not found at: ", p_region_loc); return false; @@ -123,6 +114,24 @@ bool Terrain3DStorage::get_region_modified(const Vector2i &p_region_loc) const { return region->is_modified(); } +void Terrain3DStorage::set_region_deleted(const Vector2i &p_region_loc, const bool p_deleted) { + Ref region = _regions[p_region_loc]; + if (region.is_null()) { + LOG(ERROR, "Region not found at: ", p_region_loc); + return; + } + return region->set_deleted(p_deleted); +} + +bool Terrain3DStorage::is_region_deleted(const Vector2i &p_region_loc) const { + Ref region = _regions[p_region_loc]; + if (region.is_null()) { + LOG(ERROR, "Region not found at: ", p_region_loc); + return true; + } + return region->is_deleted(); +} + void Terrain3DStorage::set_region_size(const RegionSize p_size) { LOG(INFO, p_size); //ERR_FAIL_COND(p_size < SIZE_64); @@ -140,22 +149,50 @@ void Terrain3DStorage::set_region_locations(const TypedArray &p_locati update_maps(); } -/** Adds a region to the terrain - * Option to include an array of Images to use for maps - * Map types are Height:0, Control:1, Color:2, defined in MapType - * If the region already exists and maps are included, the current maps will be overwritten - * Parameters: - * p_global_position - the world location to place the region, rounded down to the nearest region_size multiple - * p_images - Optional array of [ Height, Control, Color ... ] w/ region_sized images +Error Terrain3DStorage::add_regionl(const Vector2i &p_region_loc, const Ref &p_region, const bool p_update) { + p_region->set_location(p_region_loc); + return add_region(p_region, p_update); +} + +Error Terrain3DStorage::add_regionp(const Vector3 &p_global_position, const Ref &p_region, const bool p_update) { + p_region->set_location(get_region_location(p_global_position)); + return add_region(p_region, p_update); +} + +Ref Terrain3DStorage::add_region_blank(const Vector2i &p_region_loc, const bool p_update) { + Ref region; + region.instantiate(); + region->set_location(p_region_loc); + if (add_region(region, p_update)) { + return region; + } + return Ref(); +} + +Ref Terrain3DStorage::add_region_blankp(const Vector3 &p_global_position, const bool p_update) { + Ref region; + region.instantiate(); + region->set_location(get_region_location(p_global_position)); + if (add_region(region, p_update)) { + return region; + } + return Ref(); +} + +/** Adds a Terrain3DRegion to the terrain + * Marks region as modified * p_update - rebuild the maps if true. Set to false if bulk adding many regions. */ -Error Terrain3DStorage::add_region(const Vector3 &p_global_position, const TypedArray &p_images, const bool p_update, const String &p_path) { - IS_INIT_MESG("Storage not initialized", FAILED); - Vector2i region_loc = get_region_location(p_global_position); - LOG(INFO, "Adding region at ", p_global_position, ", region_loc ", region_loc, - ", array size: ", p_images.size(), - ", update maps: ", p_update ? "yes" : "no"); +Error Terrain3DStorage::add_region(const Ref &p_region, const bool p_update) { + IS_INIT_MESG("Storage not initialized", FAILED); // needed? + if (p_region.is_null()) { + LOG(ERROR, "Provided region is null. Returning"); + return FAILED; + } + Vector2i region_loc = p_region->get_location(); + LOG(INFO, "Adding region at location ", region_loc, ", update maps: ", p_update ? "yes" : "no"); + // Check bounds and slow report errors if (_get_region_map_index(region_loc) < 0) { uint64_t time = Time::get_singleton()->get_ticks_msec(); if (time - _last_region_bounds_error > 1000) { @@ -166,107 +203,73 @@ Error Terrain3DStorage::add_region(const Vector3 &p_global_position, const Typed return FAILED; } - if (has_regionp(p_global_position)) { - if (p_images.is_empty()) { + // do something here to overwrite existing regions + // Some regions might be in a deleted state and has_region won't see them + + if (has_region(region_loc)) { + LOG(DEBUG, "Overwriting existing region at ", region_loc); + + /*if (p_images.is_empty()) { LOG(DEBUG, "Region at ", p_global_position, " already exists and nothing to overwrite. Doing nothing"); return OK; } else { LOG(DEBUG, "Region at ", p_global_position, " already exists, overwriting"); remove_region(p_global_position, false, p_path); - } + }*/ } - Ref region; - region.instantiate(); - region->set_location(region_loc); - region->set_height_map((p_images.size() > 0) ? Ref(p_images[TYPE_HEIGHT]) : Ref()); - region->set_control_map((p_images.size() > 0) ? Ref(p_images[TYPE_CONTROL]) : Ref()); - region->set_color_map((p_images.size() > 0) ? Ref(p_images[TYPE_COLOR]) : Ref()); - - //TypedArray images = sanitize_maps(TYPE_MAX, p_images); - //if (images.is_empty()) { - // LOG(ERROR, "Sanitize_maps failed to accept images or produce blanks"); - // return FAILED; - //} - - // Check this later, per region - // If we're importing data into a region, check its heights for aabbs - /*Vector2 min_max = V2_ZERO; - if (p_images.size() > TYPE_HEIGHT) { - min_max = Util::get_min_max(images[TYPE_HEIGHT]); - LOG(DEBUG, "Checking imported height range: ", min_max); - update_heights(min_max); - } - */ - - //LOG(DEBUG, "Pushing back ", images.size(), " images"); - //_height_maps.push_back(images[TYPE_HEIGHT]); - //_control_maps.push_back(images[TYPE_CONTROL]); - //_color_maps.push_back(images[TYPE_COLOR]); - _height_maps.push_back(region->get_height_map()); - _control_maps.push_back(region->get_control_map()); - _color_maps.push_back(region->get_color_map()); - _region_locations.push_back(region_loc); - LOG(DEBUG, "Total regions after pushback: ", _region_locations.size()); + LOG(DEBUG, "Region version: ", vformat("%.3f", p_region->get_version()), " location: ", region_loc); - // Region_map is used by get_region_id so must be updated every time + LOG(DEBUG, "Storing region in indices at id: ", _region_locations.size()); + _region_locations.push_back(region_loc); + _regions[region_loc] = p_region; _region_map_dirty = true; - if (!_loading) { - if (p_update) { - //notify_property_list_changed(); - //emit_changed(); - force_update_maps(); - } else { - update_maps(); - } + _height_maps.push_back(p_region->get_height_map()); + _control_maps.push_back(p_region->get_control_map()); + _color_maps.push_back(p_region->get_color_map()); + if (p_update) { + force_update_maps(); } return OK; } -void Terrain3DStorage::remove_region(const Vector3 &p_global_position, const bool p_update, const String &p_path) { - LOG(INFO, "Removing region at ", p_global_position, " Updating: ", p_update ? "yes" : "no"); - int region_id = get_region_idp(p_global_position); - ERR_FAIL_COND_MSG(region_id == -1, "Position out of bounds"); - remove_region_by_id(region_id, p_update, p_path); +void Terrain3DStorage::remove_regionp(const Vector3 &p_global_position, const bool p_update) { + Ref region = get_region(get_region_location(p_global_position)); + remove_region(region, p_update); } -void Terrain3DStorage::remove_region_by_id(const int p_region_id, const bool p_update, const String &p_path) { - ERR_FAIL_COND_MSG(p_region_id == -1 || p_region_id >= _region_locations.size(), "Region id out of bounds."); - String fname = Util::location_to_filename(_region_locations[p_region_id]); - LOG(INFO, "Removing region at: ", p_region_id); - _region_locations.remove_at(p_region_id); - LOG(DEBUG, "Removed region_locations, new size: ", _region_locations.size()); - _height_maps.remove_at(p_region_id); - LOG(DEBUG, "Removed heightmaps, new size: ", _height_maps.size()); - _control_maps.remove_at(p_region_id); - LOG(DEBUG, "Removed control maps, new size: ", _control_maps.size()); - _color_maps.remove_at(p_region_id); - LOG(DEBUG, "Removed colormaps, new size: ", _color_maps.size()); - - if (p_path != "") { - Ref da = DirAccess::open(p_path); - da->remove(fname); - if (Engine::get_singleton()->is_editor_hint()) { - EditorInterface::get_singleton()->get_resource_filesystem()->scan(); - } - } +void Terrain3DStorage::remove_regionl(const Vector2i &p_region_loc, const bool p_update) { + Ref region = get_region(p_region_loc); + remove_region(region, p_update); +} - if (_height_maps.size() == 0) { - _height_range = Vector2(0.f, 0.f); +// Remove region marks the region for deletion, and removes it from the active arrays indexed by ID +// It remains stored in _regions and the file remains on disk until saved, when both are removed +void Terrain3DStorage::remove_region(const Ref &p_region, const bool p_update) { + if (p_region.is_null()) { + LOG(ERROR, "Region not found or is null. Returning"); + return; } - - // Region_map is used by get_region_id so must be updated + LOG(INFO, "Removing region at ", p_region->get_location(), " Updating: ", p_update ? "yes" : "no"); + LOG(DEBUG, "Marking region for deletion"); + p_region->set_deleted(true); + int region_id = get_region_id(p_region->get_location()); + if (region_id < 0) { + LOG(ERROR, "Region already removed from region_locations. Returning"); + return; + } + _region_locations.remove_at(region_id); _region_map_dirty = true; + LOG(DEBUG, "Removing from region_locations, new size: ", _region_locations.size()); + _height_maps.remove_at(region_id); + LOG(DEBUG, "Removed from heightmaps, new size: ", _height_maps.size()); + _control_maps.remove_at(region_id); + LOG(DEBUG, "Removed from control maps, new size: ", _control_maps.size()); + _color_maps.remove_at(region_id); + LOG(DEBUG, "Removed from colormaps, new size: ", _color_maps.size()); if (p_update) { LOG(DEBUG, "Updating generated maps"); - _generated_height_maps.clear(); - _generated_control_maps.clear(); - _generated_color_maps.clear(); - update_maps(); - notify_property_list_changed(); - //emit_changed(); //! FIXME - (needs to be consistent w/ add_regions - } else { - update_maps(); + force_update_maps(); } } @@ -275,6 +278,7 @@ void Terrain3DStorage::update_maps() { if (_generated_height_maps.is_dirty()) { LOG(DEBUG_CONT, "Regenerating height layered texture from ", _height_maps.size(), " maps"); _generated_height_maps.create(_height_maps); + calc_height_range(); any_changed = true; emit_signal("height_maps_changed"); } @@ -298,7 +302,7 @@ void Terrain3DStorage::update_maps() { } if (_region_map_dirty) { - LOG(DEBUG_CONT, "Regenerating ", REGION_MAP_VSIZE, " region map array"); + LOG(DEBUG_CONT, "Regenerating ", REGION_MAP_VSIZE, " region map array from active regions"); _region_map.clear(); _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); _region_map_dirty = false; @@ -319,81 +323,51 @@ void Terrain3DStorage::update_maps() { void Terrain3DStorage::save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit) { Ref region = _regions[p_region_loc]; - // looks like get isn't required if (region.is_null()) { - LOG(ERROR, "No region at: ", p_region_loc); - // need to create it + LOG(ERROR, "No region found at: ", p_region_loc); return; } String fname = Util::location_to_filename(p_region_loc); String path = p_dir + String("/") + fname; - LOG(INFO, "Saving file: ", fname, " location: ", p_region_loc); + // If region marked for deletion, remove from disk and from _regions, but don't free in case stored in undo + if (region->is_deleted()) { + LOG(DEBUG, "File to be deleted: ", path); + if (!FileAccess::file_exists(path)) { + LOG(WARN, "File ", path, " doesn't exist"); + return; + } + Ref da = DirAccess::open(p_dir); + if (da.is_null()) { + LOG(ERROR, "Cannot open directory for writing: ", p_dir, " error: ", DirAccess::get_open_error()); + return; + } + da->remove(fname); + if (Engine::get_singleton()->is_editor_hint()) { + EditorInterface::get_singleton()->get_resource_filesystem()->scan(); + } + _regions.erase(p_region_loc); + LOG(INFO, "File ", path, " deleted and ", p_region_loc, " removed from _regions"); + return; + } region->save(path, p_16_bit); } -void Terrain3DStorage::load_region(const Vector2i &p_region_loc, const String &p_dir) { +void Terrain3DStorage::load_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_update) { LOG(INFO, "Loading region from location ", p_region_loc); String path = p_dir + String("/") + Util::location_to_filename(p_region_loc); - Ref region = ResourceLoader::get_singleton()->load(path, - "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE); - if (region.is_null()) { - LOG(ERROR, "Could not load region at ", path, " at location", p_region_loc); + if (!FileAccess::file_exists(path)) { + LOG(ERROR, "File ", path, " doesn't exist"); return; } - register_region(region); -} - -void Terrain3DStorage::register_region(const Ref &p_region) { - Vector3 global_position = Vector3(p_region->get_location().x, 0.0f, p_region->get_location().y) * _region_size; - TypedArray maps = TypedArray(); - maps.resize(3); - if (p_region->get_height_map().is_valid()) { - maps[0] = p_region->get_height_map(); - } - if (p_region->get_control_map().is_valid()) { - maps[1] = p_region->get_control_map(); - } - if (p_region->get_color_map().is_valid()) { - maps[2] = p_region->get_color_map(); - } - // 3 - Add to region map - add_region(global_position, maps); - - // Store region - LOG(INFO, "Registered region ", p_region->get_path(), " at ", p_region->get_location()); - _regions[p_region->get_location()] = p_region; -} - -TypedArray Terrain3DStorage::get_regions_under_aabb(const AABB &p_aabb) { - TypedArray found = TypedArray(); - // Step 1: Calculate how many region tiles are under the AABB - Vector2 start = Vector2(p_aabb.get_position().x, p_aabb.get_position().y); - real_t size_x = p_aabb.get_size().x; - real_t size_y = p_aabb.get_size().y; - LOG(INFO, "AABB: ", p_aabb); - real_t rcx = size_x / _region_size; - real_t rcy = size_y / _region_size; - LOG(INFO, "rcx ", rcx, " rcy ", rcy); - // This ternary avoids the edge case of the AABB being exactly on the region boundaries - int region_count_x = godot::Math::is_equal_approx(godot::Math::fract(rcx), 0.0f) ? (int)(rcx) : (int)ceilf(rcx); - int region_count_y = godot::Math::is_equal_approx(godot::Math::fract(rcy), 0.0f) ? (int)(rcy) : (int)ceilf(rcy); - LOG(INFO, "Region counts: x ", region_count_x, " y ", region_count_y); - // Step 2: Check under every region point - // Using ceil and min may seem like an odd choice, but it will snap the checking bounds to the edges of the AABB, - // ensuring that it also hits regions partially covered by the AABB - for (int i = 0; i < region_count_x; i++) { - real_t x = start.x + godot::Math::min((real_t)(i * _region_size), size_x); - for (int j = 0; j < region_count_y; j++) { - real_t y = start.y + godot::Math::min((real_t)(j * _region_size), size_y); - int region_id = get_region_idp(Vector3(x, 0, y)); - LOG(INFO, "Region id ", region_id, " location ", Vector3(x, 0, y)); - // If found, push_back to array - if (region_id != -1) { - found.push_back(region_id); - } - } + Ref region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE); + if (region.is_null()) { + LOG(ERROR, "Cannot load region at ", path); + return; } - return found; + region->take_over_path(path); + region->set_location(p_region_loc); + region->set_version(CURRENT_VERSION); // Sends upgrade warning if old version + add_region(region, p_update); } void Terrain3DStorage::set_map_region(const MapType p_map_type, const int p_region_id, const Ref &p_image) { @@ -735,8 +709,9 @@ Dictionary Terrain3DStorage::get_multimeshes(const TypedArray &p_region_ids void Terrain3DStorage::save_directory(const String &p_dir) { LOG(INFO, "Saving data files to ", p_dir); - for (int i = 0; i < _region_locations.size(); i++) { - save_region(_region_locations[i], p_dir, _terrain->get_save_16_bit()); + Array locations = _regions.keys(); + for (int i = 0; i < locations.size(); i++) { + save_region(locations[i], p_dir, _terrain->get_save_16_bit()); } } @@ -852,8 +827,13 @@ void Terrain3DStorage::import_images(const TypedArray &p_images, const Ve images[i] = img_slice; } // Add the heightmap slice and only regenerate on the last one + Ref region; + region.instantiate(); Vector3 position = Vector3(descaled_position.x + start_coords.x, 0.f, descaled_position.z + start_coords.y); - add_region(position * _mesh_vertex_spacing, images, (x == slices_width - 1 && y == slices_height - 1)); + position *= _mesh_vertex_spacing; + region->set_location(get_region_location(position)); + region->set_maps(images); + add_region(region, (x == slices_width - 1 && y == slices_height - 1)); } } // for y < slices_height, x < slices_width } @@ -906,10 +886,10 @@ Error Terrain3DStorage::export_image(const String &p_file_name, const MapType p_ file_name = "res://" + file_name; } - // Check if the file could be opened for writing + // Check if the file can be opened for writing Ref file_ref = FileAccess::open(file_name, FileAccess::ModeFlags::WRITE); if (file_ref.is_null()) { - LOG(ERROR, "Could not open file '" + file_name + "' for writing"); + LOG(ERROR, "Cannot open file '" + file_name + "' for writing"); return FAILED; } file_ref->close(); @@ -917,7 +897,7 @@ Error Terrain3DStorage::export_image(const String &p_file_name, const MapType p_ // Filename is validated. Begin export image generation Ref img = layered_to_image(p_map_type); if (img.is_null() || img->is_empty()) { - LOG(ERROR, "Could not create an export image for map type: ", TYPESTR[p_map_type]); + LOG(ERROR, "Cannot create an export image for map type: ", TYPESTR[p_map_type]); return FAILED; } @@ -1003,8 +983,6 @@ void Terrain3DStorage::load_directory(const String &p_dir) { return; } _clear(); - _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); - _loading = true; // don't update until done LOG(INFO, "Loading region files from ", p_dir); PackedStringArray files = da->get_files(); @@ -1015,22 +993,21 @@ void Terrain3DStorage::load_directory(const String &p_dir) { continue; } LOG(DEBUG, "Loading region from ", path); - Ref region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE); - if (region.is_null()) { - LOG(ERROR, "Region file ", path, " failed to load"); - continue; - } Vector2i loc = Util::filename_to_location(fname); if (loc.x == INT32_MAX) { LOG(ERROR, "Cannot get region location from file name: ", fname); continue; } - LOG(DEBUG, "Region version: ", region->get_version(), " location: ", loc); + Ref region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE); + if (region.is_null()) { + LOG(ERROR, "Cannot load region at ", path); + continue; + } + region->take_over_path(path); region->set_location(loc); region->set_version(CURRENT_VERSION); // Sends upgrade warning if old version - register_region(region); + add_region(region, false); } - _loading = false; force_update_maps(); } @@ -1123,10 +1100,6 @@ void Terrain3DStorage::_bind_methods() { BIND_CONSTANT(REGION_MAP_SIZE); - ClassDB::bind_method(D_METHOD("set_height_range", "range"), &Terrain3DStorage::set_height_range); - ClassDB::bind_method(D_METHOD("get_height_range"), &Terrain3DStorage::get_height_range); - ClassDB::bind_method(D_METHOD("update_height_range"), &Terrain3DStorage::update_height_range); - ClassDB::bind_method(D_METHOD("set_region_size", "size"), &Terrain3DStorage::set_region_size); ClassDB::bind_method(D_METHOD("get_region_size"), &Terrain3DStorage::get_region_size); ClassDB::bind_method(D_METHOD("set_region_locations", "region_locations"), &Terrain3DStorage::set_region_locations); @@ -1136,16 +1109,21 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("get_region_locationi", "region_id"), &Terrain3DStorage::get_region_locationi); ClassDB::bind_method(D_METHOD("get_region_id", "region_location"), &Terrain3DStorage::get_region_id); ClassDB::bind_method(D_METHOD("get_region_idp", "global_position"), &Terrain3DStorage::get_region_idp); - ClassDB::bind_method(D_METHOD("has_region", "global_position"), &Terrain3DStorage::has_region); - ClassDB::bind_method(D_METHOD("add_region", "global_position", "images", "update", "path"), &Terrain3DStorage::add_region, - DEFVAL(TypedArray()), DEFVAL(true), DEFVAL("")); - ClassDB::bind_method(D_METHOD("remove_region", "global_position", "update"), &Terrain3DStorage::remove_region, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("has_region", "region_location"), &Terrain3DStorage::has_region); + ClassDB::bind_method(D_METHOD("has_regionp", "global_position"), &Terrain3DStorage::has_regionp); + ClassDB::bind_method(D_METHOD("add_region", "region", "update"), &Terrain3DStorage::add_region, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("add_regionl", "region_location", "update"), &Terrain3DStorage::add_regionl, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("add_regionp", "global_position", "update"), &Terrain3DStorage::add_regionp, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("add_region_blank", "region_location", "update"), &Terrain3DStorage::add_region, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("add_region_blankp", "global_position", "update"), &Terrain3DStorage::add_region, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("remove_region", "region", "update"), &Terrain3DStorage::remove_region, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("remove_regionl", "region_location", "update"), &Terrain3DStorage::remove_regionl, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("remove_regionp", "global_position", "update"), &Terrain3DStorage::remove_regionp, DEFVAL(true)); ClassDB::bind_method(D_METHOD("save_directory", "directory"), &Terrain3DStorage::save_directory); ClassDB::bind_method(D_METHOD("load_directory", "directory"), &Terrain3DStorage::load_directory); - ClassDB::bind_method(D_METHOD("save_region", "directory", "region_location"), &Terrain3DStorage::save_region); - ClassDB::bind_method(D_METHOD("load_region", "directory", "region_location"), &Terrain3DStorage::load_region); - ClassDB::bind_method(D_METHOD("register_region", "region", "region_location"), &Terrain3DStorage::register_region); + ClassDB::bind_method(D_METHOD("save_region", "directory", "region_location", "16_bit"), &Terrain3DStorage::save_region, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("load_region", "directory", "region_location", "update"), &Terrain3DStorage::load_region, DEFVAL(true)); ClassDB::bind_method(D_METHOD("set_map_region", "map_type", "region_id", "image"), &Terrain3DStorage::set_map_region); ClassDB::bind_method(D_METHOD("get_map_region", "map_type", "region_id"), &Terrain3DStorage::get_map_region); @@ -1178,23 +1156,24 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("get_normal", "global_position"), &Terrain3DStorage::get_normal); ClassDB::bind_method(D_METHOD("force_update_maps", "map_type"), &Terrain3DStorage::force_update_maps, DEFVAL(TYPE_MAX)); - //ClassDB::bind_method(D_METHOD("set_multimeshes", "multimeshes"), &Terrain3DStorage::set_multimeshes); - //ClassDB::bind_method(D_METHOD("get_multimeshes"), &Terrain3DStorage::get_multimeshes); + ClassDB::bind_method(D_METHOD("get_height_range"), &Terrain3DStorage::get_height_range); + ClassDB::bind_method(D_METHOD("calc_height_range", "recursive"), &Terrain3DStorage::calc_height_range, DEFVAL(false)); ClassDB::bind_method(D_METHOD("import_images", "images", "global_position", "offset", "scale"), &Terrain3DStorage::import_images, DEFVAL(Vector3(0, 0, 0)), DEFVAL(0.0), DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("export_image", "file_name", "map_type"), &Terrain3DStorage::export_image); ClassDB::bind_method(D_METHOD("layered_to_image", "map_type"), &Terrain3DStorage::layered_to_image); + //ClassDB::bind_method(D_METHOD("set_multimeshes", "multimeshes"), &Terrain3DStorage::set_multimeshes); + //ClassDB::bind_method(D_METHOD("get_multimeshes"), &Terrain3DStorage::get_multimeshes); + //ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "multimeshes", PROPERTY_HINT_NONE, "", ro_flags), "set_multimeshes", "get_multimeshes"); int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; //ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "64:64, 128:128, 256:256, 512:512, 1024:1024, 2048:2048"), "set_region_size", "get_region_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "1024:1024"), "set_region_size", "get_region_size"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "height_range", PROPERTY_HINT_NONE, "", ro_flags), "set_height_range", "get_height_range"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "region_locations", PROPERTY_HINT_ARRAY_TYPE, vformat("%tex_size/%tex_size:%tex_size", Variant::VECTOR2, PROPERTY_HINT_NONE), ro_flags), "set_region_locations", "get_region_locations"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "height_maps", PROPERTY_HINT_ARRAY_TYPE, vformat("%tex_size/%tex_size:%tex_size", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Image"), ro_flags), "set_height_maps", "get_height_maps"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "control_maps", PROPERTY_HINT_ARRAY_TYPE, vformat("%tex_size/%tex_size:%tex_size", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Image"), ro_flags), "set_control_maps", "get_control_maps"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "color_maps", PROPERTY_HINT_ARRAY_TYPE, vformat("%tex_size/%tex_size:%tex_size", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Image"), ro_flags), "set_color_maps", "get_color_maps"); - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "multimeshes", PROPERTY_HINT_NONE, "", ro_flags), "set_multimeshes", "get_multimeshes"); ADD_SIGNAL(MethodInfo("maps_changed")); ADD_SIGNAL(MethodInfo("region_map_changed")); diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index 93df7142..658174c1 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -40,38 +40,39 @@ class Terrain3DStorage : public Object { // Storage Settings & flags real_t _mesh_vertex_spacing = 1.f; // Set by Terrain3D::set_mesh_vertex_spacing - bool _loading = false; // tracking when we're loading so we don't add_region w/ update AABB _edited_area; + Vector2 _master_height_range = V2_ZERO; uint64_t _last_region_bounds_error = 0; ///////// - // Region data are dual indexed. 1) By `region_location:Vector2i` as the primary key, - // stored in the `_regions` Dictionary. 2) By a mutable `region_id:int` stored in the - // arrays below. - // - // `_regions` stores all loaded Terrain3DRegions, indexed by region_location. This is - // the only stable index so should be the main index for users. Terrain3DRegion - // houses the maps, instances, and other data for the region. - + // Terrain3DRegions house the maps, instances, and other data for each region. + // Regions are dual indexed: + // 1) By `region_location:Vector2i` as the primary key. This is the only stable index + // so should be the main index for users. + // 2) By `region_id:int`. This index changes on every add/remove, depends on load order, + // and is not stable. It should not be relied on by users and is primarily for internal use. + + // `_regions` stores all loaded Terrain3DRegions, indexed by region_location. + // Inactive regions are those marked for deletion. Dictionary _regions; // Dict[region_location:Vector2i] -> Terrain3DRegion - // All region maps are maintained in secondary indices. These arrays provide direct - // access to maps in the regions, indexed by a region_id. This index changes on - // every add/remove, depends on load order and is not stable, so should not be - // relied on by users. These arrays are converted to TextureArrays for the shader. + // All _active_ region maps are maintained in secondary indices. These arrays provide + // direct access to maps in the regions, indexed by region_id. These arrays are converted + // to TextureArrays for the shader. TypedArray _region_locations; TypedArray _height_maps; TypedArray _control_maps; TypedArray _color_maps; - // Editing occurs on the arrays above, then get converted to the generated arrays + // Editing occurs on the Image arrays above, which are converted to Texture arrays // below for the shader. - // 16x16 grid with region_id at its location, no region = 0, region_ids >= 1 + // 16x16 grid with region_id:int at its location, no region = 0, region_ids >= 1 PackedInt32Array _region_map; bool _region_map_dirty = true; + // These contain the TextureArray RIDs from the RenderingServer GeneratedTexture _generated_height_maps; GeneratedTexture _generated_control_maps; @@ -80,7 +81,6 @@ class Terrain3DStorage : public Object { // Foliage Instancer contains MultiMeshes saved to disk // Dictionary[region_location:Vector2i] -> Dictionary[mesh_id:int] -> MultiMesh Dictionary _multimeshes; - Vector2 _height_range = Vector2(0.f, 0.f); RegionSize _region_size = SIZE_1024; Vector2i _region_sizev = Vector2i(_region_size, _region_size); @@ -105,9 +105,11 @@ class Terrain3DStorage : public Object { Vector2i get_region_sizev() const { return _region_sizev; } int get_region_count() const { return _region_locations.size(); } Dictionary get_regions() { return _regions; } - Ref get_region(const Vector2i &p_region_loc); + Ref get_region(const Vector2i &p_region_loc) { return _regions[p_region_loc]; } void set_region_modified(const Vector2i &p_region_loc, const bool p_modified = true); - bool get_region_modified(const Vector2i &p_region_loc) const; + bool is_region_modified(const Vector2i &p_region_loc) const; + void set_region_deleted(const Vector2i &p_region_loc, const bool p_deleted = true); + bool is_region_deleted(const Vector2i &p_region_loc) const; Vector2i get_region_location(const Vector3 &p_global_position) const; Vector2i get_region_locationi(const int p_region_id) const; @@ -115,22 +117,24 @@ class Terrain3DStorage : public Object { int get_region_idp(const Vector3 &p_global_position) const; bool has_region(const Vector2i &p_region_loc) const { return get_region_id(p_region_loc) != -1; } bool has_regionp(const Vector3 &p_global_position) const { return get_region_idp(p_global_position) != -1; } + void set_region_locations(const TypedArray &p_locations); TypedArray get_region_locations() const { return _region_locations; } PackedInt32Array get_region_map() const { return _region_map; } - Error add_region(const Vector3 &p_global_position, - const TypedArray &p_images = TypedArray(), - const bool p_update = true, - const String &p_path = ""); - void remove_region(const Vector3 &p_global_position, const bool p_update = true, const String &p_path = ""); - void remove_region_by_id(const int p_region_id, const bool p_update = true, const String &p_path = ""); // File I/O void save_directory(const String &p_dir); void load_directory(const String &p_dir); void save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit = false); - void load_region(const Vector2i &p_region_loc, const String &p_dir); + void load_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_update = true); - void set_region_locations(const TypedArray &p_locations); + Error add_region(const Ref &p_region, const bool p_update = true); + Error add_regionl(const Vector2i &p_region_loc, const Ref &p_region, const bool p_update = true); + Error add_regionp(const Vector3 &p_global_position, const Ref &p_region, const bool p_update = true); + Ref add_region_blank(const Vector2i &p_region_loc, const bool p_update = true); + Ref add_region_blankp(const Vector3 &p_global_position, const bool p_update = true); + void remove_region(const Ref &p_region, const bool p_update = true); + void remove_regionl(const Vector2i &p_region_loc, const bool p_update = true); + void remove_regionp(const Vector3 &p_global_position, const bool p_update = true); // Maps void set_map_region(const MapType p_map_type, const int p_region_id, const Ref &p_image); @@ -169,13 +173,10 @@ class Terrain3DStorage : public Object { void add_edited_area(const AABB &p_area); AABB get_edited_area() const { return _edited_area; } - void register_region(const Ref &p_region); - TypedArray get_regions_under_aabb(const AABB &p_aabb); - void set_height_range(const Vector2 &p_range); - Vector2 get_height_range() const { return _height_range; } - void update_heights(const real_t p_height); - void update_heights(const Vector2 &p_heights); - void update_height_range(); + Vector2 get_height_range() const { return _master_height_range; } + void update_master_height(const real_t p_height); + void update_master_heights(const Vector2 &p_low_high); + void calc_height_range(const bool p_recursive = false); void import_images(const TypedArray &p_images, const Vector3 &p_global_position = V3_ZERO, const real_t p_offset = 0.f, const real_t p_scale = 1.f); diff --git a/src/terrain_3d_util.cpp b/src/terrain_3d_util.cpp index 12bbd7c3..7f25b8b3 100644 --- a/src/terrain_3d_util.cpp +++ b/src/terrain_3d_util.cpp @@ -276,7 +276,7 @@ Ref Terrain3DUtil::load_image(const String &p_file_name, const int p_cach } if (!img.is_valid()) { - LOG(ERROR, "File", p_file_name, " could not be loaded."); + LOG(ERROR, "File", p_file_name, " cannot be loaded."); return Ref(); } if (img->is_empty()) { From 3964cb9e701beb3dfaac8915aef2411b294373ef Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Sun, 18 Aug 2024 13:41:27 +0700 Subject: [PATCH 14/19] Move instancer storage to Terrain3DRegion; Add region labels --- src/terrain_3d.cpp | 130 ++++++++++++++++---- src/terrain_3d.h | 17 ++- src/terrain_3d_editor.cpp | 17 +-- src/terrain_3d_instancer.cpp | 113 ++++++++++-------- src/terrain_3d_instancer.h | 6 +- src/terrain_3d_region.cpp | 10 +- src/terrain_3d_storage.cpp | 223 +++++++---------------------------- src/terrain_3d_storage.h | 39 +++--- 8 files changed, 258 insertions(+), 297 deletions(-) diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index 8e64057e..54450ead 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,11 @@ void Terrain3D::_initialize() { LOG(DEBUG, "Connecting region_size_changed signal to _material->_update_regions()"); _storage->connect("region_size_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::_update_maps)); } + // Any region was changed, update region labels + if (!_storage->is_connected("region_map_changed", callable_mp(this, &Terrain3D::update_region_labels))) { + LOG(DEBUG, "Connecting _storage::region_map_changed signal to set_show_region_locations()"); + _storage->connect("region_map_changed", callable_mp(this, &Terrain3D::update_region_labels)); + } // Any map was regenerated or regions changed, update material if (!_storage->is_connected("maps_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::_update_maps))) { LOG(DEBUG, "Connecting _storage::maps_changed signal to _material->_update_maps()"); @@ -65,6 +71,11 @@ void Terrain3D::_initialize() { LOG(DEBUG, "Connecting _storage::height_maps_changed signal to update_aabbs()"); _storage->connect("height_maps_changed", callable_mp(this, &Terrain3D::update_aabbs)); } + // Connect height changes to update instances + if (!_storage->is_connected("maps_edited", callable_mp(_instancer, &Terrain3DInstancer::update_transforms))) { + LOG(DEBUG, "Connecting maps_edited signal to update_transforms()"); + _storage->connect("maps_edited", callable_mp(_instancer, &Terrain3DInstancer::update_transforms)); + } // Texture assets changed, update material if (!_assets->is_connected("textures_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::_update_texture_arrays))) { LOG(DEBUG, "Connecting _assets.textures_changed to _material->_update_texture_arrays()"); @@ -75,16 +86,6 @@ void Terrain3D::_initialize() { LOG(DEBUG, "Connecting _assets.meshes_changed to _instancer->_update_mmis()"); _assets->connect("meshes_changed", callable_mp(_instancer, &Terrain3DInstancer::_update_mmis).bind(V2I_MAX, -1)); } - // New multimesh added to storage, rebuild instancer - if (!_storage->is_connected("multimeshes_changed", callable_mp(_instancer, &Terrain3DInstancer::_rebuild_mmis))) { - LOG(DEBUG, "Connecting _storage::multimeshes_changed signal to _rebuild_mmis()"); - _storage->connect("multimeshes_changed", callable_mp(_instancer, &Terrain3DInstancer::_rebuild_mmis)); - } - // Connect height changes to update instances - if (!_storage->is_connected("maps_edited", callable_mp(_instancer, &Terrain3DInstancer::update_transforms))) { - LOG(DEBUG, "Connecting maps_edited signal to update_transforms()"); - _storage->connect("maps_edited", callable_mp(_instancer, &Terrain3DInstancer::update_transforms)); - } // Initialize the system if (!_initialized && _is_inside_world && is_inside_tree()) { @@ -124,6 +125,31 @@ void Terrain3D::__process(const double p_delta) { } } +void Terrain3D::_build_containers() { + _label_nodes = memnew(Node); + _label_nodes->set_name("Labels"); + add_child(_label_nodes, true); + _mmi_nodes = memnew(Node); + _mmi_nodes->set_name("MMIs"); + add_child(_mmi_nodes, true); +} + +void Terrain3D::_destroy_containers() { + memdelete_safely(_label_nodes); + memdelete_safely(_mmi_nodes); +} + +void Terrain3D::_destroy_labels() { + Array labels = _label_nodes->get_children(); + for (int i = 0; i < labels.size(); i++) { + Node *label = cast_to(labels[i]); + if (label != nullptr) { + LOG(DEBUG, "Destroying label: ", label->get_name()); + } + memdelete_safely(label); + } +} + void Terrain3D::_setup_mouse_picking() { if (!is_inside_tree()) { LOG(ERROR, "Not inside the tree, skipping mouse setup"); @@ -408,27 +434,34 @@ void Terrain3D::_update_collision() { PackedRealArray map_data = PackedRealArray(); map_data.resize(shape_size * shape_size); - Vector2i global_loc = Vector2i(_storage->get_region_locations()[i]) * region_size; + Vector2i region_loc = _storage->get_region_locations()[i]; + Vector2i global_loc = region_loc * region_size; Vector3 global_pos = Vector3(global_loc.x, 0.f, global_loc.y); Ref map, map_x, map_z, map_xz; Ref cmap, cmap_x, cmap_z, cmap_xz; - map = _storage->get_map_region(TYPE_HEIGHT, i); - cmap = _storage->get_map_region(TYPE_CONTROL, i); - int region_id = _storage->get_region_idp(Vector3(global_pos.x + region_size, 0.f, global_pos.z) * _mesh_vertex_spacing); - if (region_id >= 0) { - map_x = _storage->get_map_region(TYPE_HEIGHT, region_id); - cmap_x = _storage->get_map_region(TYPE_CONTROL, region_id); + Ref region = _storage->get_region(region_loc); + if (region.is_null()) { + LOG(ERROR, "Region ", region_loc, " not found"); + continue; } - region_id = _storage->get_region_idp(Vector3(global_pos.x, 0.f, global_pos.z + region_size) * _mesh_vertex_spacing); - if (region_id >= 0) { - map_z = _storage->get_map_region(TYPE_HEIGHT, region_id); - cmap_z = _storage->get_map_region(TYPE_CONTROL, region_id); + map = region->get_map(TYPE_HEIGHT); + cmap = region->get_map(TYPE_CONTROL); + + region = _storage->get_regionp(Vector3(global_pos.x + region_size, 0.f, global_pos.z) * _mesh_vertex_spacing); + if (region.is_valid()) { + map_x = region->get_map(TYPE_HEIGHT); + cmap_x = region->get_map(TYPE_CONTROL); + } + region = _storage->get_regionp(Vector3(global_pos.x, 0.f, global_pos.z + region_size) * _mesh_vertex_spacing); + if (region.is_valid()) { + map_z = region->get_map(TYPE_HEIGHT); + cmap_z = region->get_map(TYPE_CONTROL); } - region_id = _storage->get_region_idp(Vector3(global_pos.x + region_size, 0.f, global_pos.z + region_size) * _mesh_vertex_spacing); - if (region_id >= 0) { - map_xz = _storage->get_map_region(TYPE_HEIGHT, region_id); - cmap_xz = _storage->get_map_region(TYPE_CONTROL, region_id); + region = _storage->get_regionp(Vector3(global_pos.x + region_size, 0.f, global_pos.z + region_size) * _mesh_vertex_spacing); + if (region.is_valid()) { + map_xz = region->get_map(TYPE_HEIGHT); + cmap_xz = region->get_map(TYPE_CONTROL); } for (int z = 0; z < shape_size; z++) { @@ -1055,6 +1088,45 @@ Vector3 Terrain3D::get_intersection(const Vector3 &p_src_pos, const Vector3 &p_d return point; } +void Terrain3D::set_show_region_labels(const bool p_enabled) { + LOG(INFO, "Setting show region labels: ", p_enabled); + if (_show_region_labels != p_enabled) { + _show_region_labels = p_enabled; + update_region_labels(); + } +} + +void Terrain3D::update_region_labels() { + LOG(DEBUG, "Updating region labels"); + _destroy_labels(); + if (_show_region_labels && _storage != nullptr) { + Array region_locations = _storage->get_region_locations(); + for (int i = 0; i < region_locations.size(); i++) { + Label3D *label = memnew(Label3D); + String text = region_locations[i]; + label->set_name("Label3D" + text.replace(" ", "")); + label->set_pixel_size(.001f); + label->set_billboard_mode(BaseMaterial3D::BILLBOARD_ENABLED); + label->set_draw_flag(Label3D::FLAG_DOUBLE_SIDED, true); + label->set_draw_flag(Label3D::FLAG_DISABLE_DEPTH_TEST, true); + label->set_draw_flag(Label3D::FLAG_FIXED_SIZE, true); + label->set_text(text); + label->set_modulate(Color(1.f, 1.f, 1.f, .5f)); + label->set_outline_modulate(Color(0.f, 0.f, 0.f, .5f)); + label->set_font_size(64); + label->set_outline_size(10); + label->set_visibility_range_end(3072.f * _mesh_vertex_spacing); + label->set_visibility_range_end_margin(256.f); + label->set_visibility_range_fade_mode(GeometryInstance3D::VISIBILITY_RANGE_FADE_SELF); + _label_nodes->add_child(label, true); + Vector2i loc = region_locations[i]; + int region_size = _storage->get_region_size(); + Vector3 pos = Vector3(real_t(loc.x) + .5f, 0.f, real_t(loc.y) + .5f) * region_size * _mesh_vertex_spacing; + label->set_position(pos); + } + } +} + /** * Generates a static ArrayMesh for the terrain. * p_lod (0-8): Determines the granularity of the generated mesh. @@ -1145,6 +1217,7 @@ void Terrain3D::_notification(const int p_what) { case NOTIFICATION_POSTINITIALIZE: { // Object initialized, before script is attached LOG(INFO, "NOTIFICATION_POSTINITIALIZE"); + _build_containers(); break; } @@ -1263,6 +1336,8 @@ void Terrain3D::_notification(const int p_what) { LOG(INFO, "NOTIFICATION_PREDELETE"); _destroy_collision(); _destroy_instancer(); + _destroy_labels(); + _destroy_containers(); memdelete_safely(_storage); break; } @@ -1276,12 +1351,14 @@ void Terrain3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_version"), &Terrain3D::get_version); ClassDB::bind_method(D_METHOD("set_debug_level", "level"), &Terrain3D::set_debug_level); ClassDB::bind_method(D_METHOD("get_debug_level"), &Terrain3D::get_debug_level); + ClassDB::bind_method(D_METHOD("set_show_region_labels", "enabled"), &Terrain3D::set_show_region_labels); + ClassDB::bind_method(D_METHOD("get_show_region_labels"), &Terrain3D::get_show_region_labels); + ClassDB::bind_method(D_METHOD("get_storage"), &Terrain3D::get_storage); ClassDB::bind_method(D_METHOD("set_storage_directory", "directory"), &Terrain3D::set_storage_directory); ClassDB::bind_method(D_METHOD("get_storage_directory"), &Terrain3D::get_storage_directory); ClassDB::bind_method(D_METHOD("set_save_16_bit", "enabled"), &Terrain3D::set_save_16_bit); ClassDB::bind_method(D_METHOD("get_save_16_bit"), &Terrain3D::get_save_16_bit); - ClassDB::bind_method(D_METHOD("get_storage"), &Terrain3D::get_storage); ClassDB::bind_method(D_METHOD("set_mesh_lods", "count"), &Terrain3D::set_mesh_lods); ClassDB::bind_method(D_METHOD("get_mesh_lods"), &Terrain3D::get_mesh_lods); @@ -1355,6 +1432,7 @@ void Terrain3D::_bind_methods() { ADD_GROUP("Debug", "debug_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "debug_level", PROPERTY_HINT_ENUM, "Errors,Info,Debug,Debug Continuous"), "set_debug_level", "get_debug_level"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug_show_collision"), "set_show_debug_collision", "get_show_debug_collision"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug_show_region_labels"), "set_show_region_labels", "get_show_region_labels"); ADD_SIGNAL(MethodInfo("material_changed")); ADD_SIGNAL(MethodInfo("assets_changed")); diff --git a/src/terrain_3d.h b/src/terrain_3d.h index 073e9886..cfdf1d95 100644 --- a/src/terrain_3d.h +++ b/src/terrain_3d.h @@ -35,6 +35,7 @@ class Terrain3D : public Node3D { real_t _mesh_vertex_spacing = 1.0f; String _storage_directory; bool _save_16_bit = false; + bool _show_region_labels = false; Terrain3DStorage *_storage = nullptr; Ref _material; @@ -42,6 +43,10 @@ class Terrain3D : public Node3D { Terrain3DInstancer *_instancer = nullptr; Terrain3DEditor *_editor = nullptr; + // Parent containers for child nodes + Node *_label_nodes; + Node *_mmi_nodes; + // Editor components EditorPlugin *_plugin = nullptr; // Current editor or gameplay camera we are centering the terrain on. @@ -84,6 +89,10 @@ class Terrain3D : public Node3D { void _initialize(); void __process(const double p_delta); + void _build_containers(); + void _destroy_containers(); + void _destroy_labels(); + void _setup_mouse_picking(); void _destroy_mouse_picking(); void _grab_camera(); @@ -119,12 +128,13 @@ class Terrain3D : public Node3D { int get_mesh_size() const { return _mesh_size; } void set_mesh_vertex_spacing(const real_t p_spacing); real_t get_mesh_vertex_spacing() const { return _mesh_vertex_spacing; } + + Terrain3DStorage *get_storage() const { return _storage; } void set_storage_directory(String p_dir); String get_storage_directory() const; void set_save_16_bit(const bool p_enabled); bool get_save_16_bit() const { return _save_16_bit; } - Terrain3DStorage *get_storage() const { return _storage; } void set_material(const Ref &p_material); Ref get_material() const { return _material; } void set_assets(const Ref &p_assets); @@ -132,6 +142,7 @@ class Terrain3D : public Node3D { // Instancer Terrain3DInstancer *get_instancer() const { return _instancer; } + Node *get_mmi_parent() const { return _mmi_nodes; } // Editor components void set_editor(Terrain3DEditor *p_editor); @@ -169,6 +180,10 @@ class Terrain3D : public Node3D { void update_aabbs(); Vector3 get_intersection(const Vector3 &p_src_pos, const Vector3 &p_direction); + void set_show_region_labels(const bool p_enabled); + bool get_show_region_labels() const { return _show_region_labels; } + void update_region_labels(); + // Baking methods Ref bake_mesh(const int p_lod, const Terrain3DStorage::HeightFilter p_filter = Terrain3DStorage::HEIGHT_FILTER_NEAREST) const; PackedVector3Array generate_nav_mesh_source_geometry(const AABB &p_global_aabb, const bool p_require_nav = true) const; diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp index 5df097a6..78311bc7 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -79,8 +79,8 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ int region_size = storage->get_region_size(); Vector2i region_vsize = Vector2i(region_size, region_size); - // If no region and not height, skip whole function - if (storage->has_regionp(p_global_position) && (!_brush_data["auto_regions"] || _tool != HEIGHT)) { + // If no region and not height, skip whole function. Checked again later + if (!storage->has_regionp(p_global_position) && (!_brush_data["auto_regions"] || _tool != HEIGHT)) { return; } @@ -449,6 +449,7 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ Dictionary Terrain3DEditor::_get_undo_data() const { Dictionary data; + return data; // ignore undo for now if (_tool < 0 || _tool >= TOOL_MAX) { return data; } @@ -489,7 +490,7 @@ Dictionary Terrain3DEditor::_get_undo_data() const { case HOLES: // Holes can remove instances - data["multimeshes"] = _terrain->get_storage()->get_multimeshes().duplicate(true); + //data["multimeshes"] = _terrain->get_storage()->get_multimeshes().duplicate(true); LOG(DEBUG, "Storing Multimesh: ", data["multimeshes"]); case TEXTURE: case AUTOSHADER: @@ -505,7 +506,7 @@ Dictionary Terrain3DEditor::_get_undo_data() const { break; case INSTANCER: - data["multimeshes"] = _terrain->get_storage()->get_multimeshes().duplicate(true); + //data["multimeshes"] = _terrain->get_storage()->get_multimeshes().duplicate(true); LOG(DEBUG, "Storing Multimesh: ", data["multimeshes"]); break; @@ -622,18 +623,18 @@ void Terrain3DEditor::_apply_undo(const Dictionary &p_set) { if (key == "region_locations") { _terrain->get_storage()->set_region_locations(p_set[key]); } else if (key == "height_map") { - _terrain->get_storage()->set_maps(TYPE_HEIGHT, p_set[key], e_regions); + //_terrain->get_storage()->set_maps(TYPE_HEIGHT, p_set[key], e_regions); } else if (key == "control_map") { - _terrain->get_storage()->set_maps(TYPE_CONTROL, p_set[key], e_regions); + //_terrain->get_storage()->set_maps(TYPE_CONTROL, p_set[key], e_regions); } else if (key == "color_map") { - _terrain->get_storage()->set_maps(TYPE_COLOR, p_set[key], e_regions); + //_terrain->get_storage()->set_maps(TYPE_COLOR, p_set[key], e_regions); } else if (key == "height_range") { //_terrain->get_storage()->set_height_range(p_set[key]); } else if (key == "edited_area") { _terrain->get_storage()->clear_edited_area(); _terrain->get_storage()->add_edited_area(p_set[key]); } else if (key == "multimeshes") { - _terrain->get_storage()->set_multimeshes(p_set[key], e_regions); + //_terrain->get_storage()->set_multimeshes(p_set[key], e_regions); } } } diff --git a/src/terrain_3d_instancer.cpp b/src/terrain_3d_instancer.cpp index 877df95e..b12d068c 100644 --- a/src/terrain_3d_instancer.cpp +++ b/src/terrain_3d_instancer.cpp @@ -4,29 +4,18 @@ #include "logger.h" #include "terrain_3d_instancer.h" +#include "terrain_3d_region.h" #include "terrain_3d_util.h" /////////////////////////// // Private Functions /////////////////////////// -void Terrain3DInstancer::_rebuild_mmis() { - destroy(); - _update_mmis(); -} - // Creates MMIs based on stored Multimesh data void Terrain3DInstancer::_update_mmis(const Vector2i &p_region_loc, const int p_mesh_id) { IS_STORAGE_INIT(VOID); LOG(INFO, "Updating MMIs for ", (p_region_loc.x == INT32_MAX) ? "all regions" : "region " + String(p_region_loc), (p_mesh_id == -1) ? ", all meshes" : ", mesh " + String::num_int64(p_mesh_id)); - // Get region multimeshes dictionary - Dictionary region_dict = _terrain->get_storage()->get_multimeshes(); - LOG(DEBUG, "Multimeshes: ", region_dict); - if (region_dict.has(Variant())) { - region_dict.erase(Variant()); - LOG(WARN, "Removed errant null in MM dictionary"); - } if (_mmis.has(Variant())) { _mmis.erase(Variant()); LOG(WARN, "Removed errant null in MMI dictionary"); @@ -35,13 +24,18 @@ void Terrain3DInstancer::_update_mmis(const Vector2i &p_region_loc, const int p_ // For specified region_location, or max for all Array region_locations; if (p_region_loc.x == INT32_MAX) { - region_locations = region_dict.keys(); + region_locations = _terrain->get_storage()->get_region_locations(); } else { region_locations.push_back(p_region_loc); } for (int r = 0; r < region_locations.size(); r++) { Vector2i region_loc = region_locations[r]; - Dictionary mesh_dict = region_dict.get(region_loc, Dictionary()); + Ref region = _terrain->get_storage()->get_region(region_loc); + if (region.is_null()) { + LOG(WARN, "Errant null region found at: ", region_loc); + continue; + } + Dictionary mesh_dict = region->get_multimeshes(); LOG(DEBUG, "Updating MMIs from: ", region_loc); // For specified mesh id in that region, or -1 for all @@ -94,7 +88,7 @@ void Terrain3DInstancer::_update_mmis(const Vector2i &p_region_loc, const int p_ LOG(DEBUG, "No MMI found, creating new MultiMeshInstance3D, attaching to tree"); mmi = memnew(MultiMeshInstance3D); mmi->set_as_top_level(true); - _terrain->add_child(mmi, true); + _terrain->get_mmi_parent()->add_child(mmi, true); _mmis[mmi_key] = mmi; LOG(DEBUG, _mmis); } @@ -106,8 +100,8 @@ void Terrain3DInstancer::_update_mmis(const Vector2i &p_region_loc, const int p_ mmi->set_global_transform(Transform3D()); } } + LOG(DEBUG, "mm: ", mesh_dict); } - LOG(DEBUG, "mm: ", _terrain->get_storage()->get_multimeshes()); LOG(DEBUG, "_mmis: ", _mmis); } @@ -153,10 +147,9 @@ void Terrain3DInstancer::destroy() { void Terrain3DInstancer::clear_by_mesh(const int p_mesh_id) { LOG(INFO, "Deleting Multimeshes in all regions with mesh_id: ", p_mesh_id); - Dictionary region_dict = _terrain->get_storage()->get_multimeshes(); - Array locations = region_dict.keys(); - for (int i = 0; i < locations.size(); i++) { - clear_by_location(locations[i], p_mesh_id); + Array region_locations = _terrain->get_storage()->get_region_locations(); + for (int i = 0; i < region_locations.size(); i++) { + clear_by_location(region_locations[i], p_mesh_id); } } @@ -167,15 +160,16 @@ void Terrain3DInstancer::clear_by_region_id(const int p_region_id, const int p_m void Terrain3DInstancer::clear_by_location(const Vector2i &p_region_loc, const int p_mesh_id) { LOG(INFO, "Deleting Multimeshes w/ mesh_id: ", p_mesh_id, " in region: ", p_region_loc); - Dictionary region_dict = _terrain->get_storage()->get_multimeshes(); - LOG(DEBUG, "Original region_dict: ", region_dict); - Dictionary mesh_dict = region_dict[p_region_loc]; - mesh_dict.erase(p_mesh_id); - if (mesh_dict.is_empty()) { - LOG(DEBUG, "No more multimeshes in region, removing region dictionary"); - region_dict.erase(p_region_loc); - } - LOG(DEBUG, "Final region_dict: ", region_dict); + Ref region = _terrain->get_storage()->get_region(p_region_loc); + if (region.is_null()) { + LOG(WARN, "No region found at: ", p_region_loc); + return; + } + Dictionary mesh_dict = region->get_multimeshes(); + if (mesh_dict.has(p_mesh_id)) { + mesh_dict.erase(p_mesh_id); + region->set_modified(true); + } _destroy_mmi_by_location(p_region_loc, p_mesh_id); } @@ -282,7 +276,6 @@ void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const D if (xforms.size() > 0) { append_multimesh(region_loc, mesh_id, xforms, colors); } - _terrain->get_storage()->set_region_modified(region_loc, true); } void Terrain3DInstancer::remove_instances(const Vector3 &p_global_position, const Dictionary &p_params) { @@ -340,7 +333,6 @@ void Terrain3DInstancer::remove_instances(const Vector3 &p_global_position, cons } else { append_multimesh(region_loc, mesh_id, xforms, colors, true); } - _terrain->get_storage()->set_region_modified(region_loc, true); } void Terrain3DInstancer::add_multimesh(const int p_mesh_id, const Ref &p_multimesh, const Transform3D &p_xform) { @@ -452,14 +444,16 @@ void Terrain3DInstancer::append_multimesh(const Vector2i &p_region_loc, const in mm->set_instance_transform(i + old_count, p_xforms[i]); mm->set_instance_color(i + old_count, p_colors[i]); } - LOG(DEBUG_CONT, "Setting multimesh in region: ", p_region_loc, ", mesh_id: ", p_mesh_id, " instance count: ", mm->get_instance_count(), " mm: ", mm); - Dictionary region_dict = _terrain->get_storage()->get_multimeshes(); - Dictionary mesh_dict = region_dict.get(p_region_loc, Dictionary()); - // Assign into dictionaries in case these are new resources + LOG(DEBUG_CONT, "Setting multimesh in region: ", p_region_loc, ", mesh_id: ", p_mesh_id, " instance count: ", mm->get_instance_count(), " mm: ", mm); + Ref region = _terrain->get_storage()->get_region(p_region_loc); + if (region.is_null()) { + LOG(WARN, "No region found at: ", p_region_loc); + return; + } + Dictionary mesh_dict = region->get_multimeshes(); mesh_dict[p_mesh_id] = mm; - region_dict[p_region_loc] = mesh_dict; - + region->set_modified(true); _update_mmis(p_region_loc, p_mesh_id); } @@ -468,11 +462,15 @@ void Terrain3DInstancer::update_transforms(const AABB &p_aabb) { IS_STORAGE_INIT_MESG("Instancer isn't initialized.", VOID); LOG(DEBUG_CONT, "Updating transforms for all meshes within ", p_aabb); - Dictionary region_dict = _terrain->get_storage()->get_multimeshes(); - Array regions = region_dict.keys(); + Array region_locations = _terrain->get_storage()->get_region_locations(); Rect2 brush_rect = aabb2rect(p_aabb); - for (int r = 0; r < regions.size(); r++) { - Vector2i region_loc = regions[r]; + for (int r = 0; r < region_locations.size(); r++) { + Vector2i region_loc = region_locations[r]; + Ref region = _terrain->get_storage()->get_region(region_loc); + if (region.is_null()) { + LOG(WARN, "No region found at: ", region_loc); + continue; + } int region_size = _terrain->get_storage()->get_region_size(); Rect2 region_rect; region_rect.set_position(region_loc * region_size); @@ -481,7 +479,7 @@ void Terrain3DInstancer::update_transforms(const AABB &p_aabb) { // If specified area includes this region, update all MMs within if (brush_rect.intersects(region_rect)) { - Dictionary mesh_dict = region_dict.get(region_loc, Dictionary()); + Dictionary mesh_dict = region->get_multimeshes(); LOG(DEBUG_CONT, "Region ", region_loc, " intersect AABB and contains ", mesh_dict.size(), " mesh types"); // For all mesh ids for (int m = 0; m < mesh_dict.keys().size(); m++) { @@ -524,11 +522,15 @@ void Terrain3DInstancer::swap_ids(const int p_src_id, const int p_dst_id) { LOG(INFO, "Swapping IDs of multimeshes: ", p_src_id, " and ", p_dst_id); if (p_src_id >= 0 && p_src_id < asset_count && p_dst_id >= 0 && p_dst_id < asset_count) { // Change id keys in storage mm dictionary - Dictionary multimeshes = _terrain->get_storage()->get_multimeshes(); - Array mm_keys = multimeshes.keys(); - for (int i = 0; i < mm_keys.size(); i++) { - Vector2i region_loc = mm_keys[i]; - Dictionary mesh_dict = multimeshes[region_loc]; + Array region_locations = _terrain->get_storage()->get_region_locations(); + for (int i = 0; i < region_locations.size(); i++) { + Vector2i region_loc = region_locations[i]; + Ref region = _terrain->get_storage()->get_region(region_loc); + if (region.is_null()) { + LOG(WARN, "No region found at: ", region_loc); + return; + } + Dictionary mesh_dict = region->get_multimeshes(); // mesh_dict could have src, src&dst, dst or nothing. All 4 must be considered // Pop out any existing MMs Ref mm_src; @@ -536,18 +538,22 @@ void Terrain3DInstancer::swap_ids(const int p_src_id, const int p_dst_id) { if (mesh_dict.has(p_src_id)) { mm_src = mesh_dict[p_src_id]; mesh_dict.erase(p_src_id); + region->set_modified(true); } if (mesh_dict.has(p_dst_id)) { mm_dst = mesh_dict[p_dst_id]; mesh_dict.erase(p_dst_id); + region->set_modified(true); } // If src is ok, insert into dst slot if (mm_src.is_valid()) { mesh_dict[p_dst_id] = mm_src; + region->set_modified(true); } // If dst is ok, insert into src slot if (mm_dst.is_valid()) { mesh_dict[p_src_id] = mm_dst; + region->set_modified(true); } LOG(DEBUG, "Swapped multimesh ids at: ", region_loc); } @@ -584,7 +590,12 @@ Ref Terrain3DInstancer::get_multimesh(const Vector3 &p_global_positio Ref Terrain3DInstancer::get_multimesh(const Vector2i &p_region_loc, const int p_mesh_id) const { IS_STORAGE_INIT(Ref()); - Dictionary mesh_dict = _terrain->get_storage()->get_multimeshes().get(p_region_loc, Dictionary()); + Ref region = _terrain->get_storage()->get_region(p_region_loc); + if (region.is_null()) { + LOG(WARN, "No region found at: ", p_region_loc); + return Ref(); + } + Dictionary mesh_dict = region->get_multimeshes(); Ref mm = mesh_dict.get(p_mesh_id, Ref()); LOG(DEBUG_CONT, "Retrieving MultiMesh at region: ", p_region_loc, " mesh_id: ", p_mesh_id, " : ", mm); return mm; @@ -616,6 +627,11 @@ void Terrain3DInstancer::set_cast_shadows(const int p_mesh_id, const GeometryIns } } +void Terrain3DInstancer::force_update_mmis() { + destroy(); + _update_mmis(); +} + void Terrain3DInstancer::print_multimesh_buffer(MultiMeshInstance3D *p_mmi) const { if (p_mmi == nullptr) { return; @@ -654,4 +670,5 @@ void Terrain3DInstancer::_bind_methods() { ClassDB::bind_method(D_METHOD("update_transforms", "aabb"), &Terrain3DInstancer::update_transforms); ClassDB::bind_method(D_METHOD("get_mmis"), &Terrain3DInstancer::get_mmis); ClassDB::bind_method(D_METHOD("set_cast_shadows", "mesh_id", "mode"), &Terrain3DInstancer::set_cast_shadows); + ClassDB::bind_method(D_METHOD("force_update_mmis"), &Terrain3DInstancer::force_update_mmis); } diff --git a/src/terrain_3d_instancer.h b/src/terrain_3d_instancer.h index da4e25dc..08bd021f 100644 --- a/src/terrain_3d_instancer.h +++ b/src/terrain_3d_instancer.h @@ -20,8 +20,8 @@ class Terrain3DInstancer : public Object { Terrain3D *_terrain = nullptr; - // MM Resources stored in Terrain3DStorage::_multimeshes as - // Dictionary[region_location:Vector2i] -> Dictionary[mesh_id:int] -> MultiMesh + // MM Resources stored in Terrain3DRegion::_multimeshes as + // Dictionary[mesh_id:int] -> MultiMesh // MMI Objects attached to tree, freed in destructor, stored as // Dictionary[Vector3i(region_location.x, region_location.y, mesh_id)] -> MultiMeshInstance3D Dictionary _mmis; @@ -29,7 +29,6 @@ class Terrain3DInstancer : public Object { uint32_t _instance_counter = 0; int _get_instace_count(const real_t p_density); - void _rebuild_mmis(); void _update_mmis(const Vector2i &p_region_loc = V2I_MAX, const int p_mesh_id = -1); void _destroy_mmi_by_region_id(const int p_region, const int p_mesh_id); void _destroy_mmi_by_location(const Vector2i &p_region_loc, const int p_mesh_id); @@ -60,6 +59,7 @@ class Terrain3DInstancer : public Object { void set_cast_shadows(const int p_mesh_id, const GeometryInstance3D::ShadowCastingSetting p_cast_shadows); void reset_instance_counter() { _instance_counter = 0; } + void force_update_mmis(); void print_multimesh_buffer(MultiMeshInstance3D *p_mmi) const; protected: diff --git a/src/terrain_3d_region.cpp b/src/terrain_3d_region.cpp index 61221361..0369faad 100644 --- a/src/terrain_3d_region.cpp +++ b/src/terrain_3d_region.cpp @@ -126,6 +126,9 @@ void Terrain3DRegion::sanitize_map(const MapType p_map_type) { if (type == TYPE_HEIGHT) { calc_height_range(); } + if (type == TYPE_COLOR && !map->has_mipmaps()) { + map->generate_mipmaps(); + } continue; } else { LOG(DEBUG, "Provided ", type_str, " map wrong format: ", map->get_format(), ". Converting copy to: ", format); @@ -146,10 +149,7 @@ void Terrain3DRegion::sanitize_map(const MapType p_map_type) { } else { LOG(DEBUG, "No provided ", type_str, " map. Creating blank"); } - set_map(type, Util::get_filled_image(Vector2i(_region_size, _region_size), color, false, format)); - if (type == TYPE_HEIGHT) { - set_height_range(V2_ZERO); - } + set_map(type, Util::get_filled_image(Vector2i(_region_size, _region_size), color, type == TYPE_COLOR, format)); } } @@ -207,7 +207,7 @@ Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) { LOG(ERROR, "Region has not been setup. Location is INT32_MAX. Skipping ", p_path); } if (!_modified) { - LOG(DEBUG, "Save requested for region ", _location, ", but not modified. Skipping ", p_path); + LOG(DEBUG, "Region ", _location, " not modified. Skipping ", p_path); return ERR_SKIP; } if (p_path.is_empty() && get_path().is_empty()) { diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index fa15fc85..16c40c97 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -25,7 +25,6 @@ void Terrain3DStorage::_clear() { _generated_height_maps.clear(); _generated_control_maps.clear(); _generated_color_maps.clear(); - set_multimeshes(Dictionary()); // Sends a signal to the instancer } /////////////////////////// @@ -218,15 +217,11 @@ Error Terrain3DStorage::add_region(const Ref &p_region, const b }*/ } - LOG(DEBUG, "Region version: ", vformat("%.3f", p_region->get_version()), " location: ", region_loc); - - LOG(DEBUG, "Storing region in indices at id: ", _region_locations.size()); + p_region->sanitize_map(); + LOG(DEBUG, "Storing region ", region_loc, " version ", vformat("%.3f", p_region->get_version()), " id: ", _region_locations.size()); _region_locations.push_back(region_loc); _regions[region_loc] = p_region; _region_map_dirty = true; - _height_maps.push_back(p_region->get_height_map()); - _control_maps.push_back(p_region->get_control_map()); - _color_maps.push_back(p_region->get_color_map()); if (p_update) { force_update_maps(); } @@ -261,12 +256,6 @@ void Terrain3DStorage::remove_region(const Ref &p_region, const _region_locations.remove_at(region_id); _region_map_dirty = true; LOG(DEBUG, "Removing from region_locations, new size: ", _region_locations.size()); - _height_maps.remove_at(region_id); - LOG(DEBUG, "Removed from heightmaps, new size: ", _height_maps.size()); - _control_maps.remove_at(region_id); - LOG(DEBUG, "Removed from control maps, new size: ", _control_maps.size()); - _color_maps.remove_at(region_id); - LOG(DEBUG, "Removed from colormaps, new size: ", _color_maps.size()); if (p_update) { LOG(DEBUG, "Updating generated maps"); force_update_maps(); @@ -276,7 +265,13 @@ void Terrain3DStorage::remove_region(const Ref &p_region, const void Terrain3DStorage::update_maps() { bool any_changed = false; if (_generated_height_maps.is_dirty()) { - LOG(DEBUG_CONT, "Regenerating height layered texture from ", _height_maps.size(), " maps"); + LOG(DEBUG_CONT, "Regenerating height texture array from regions"); + _height_maps.clear(); + for (int i = 0; i < _region_locations.size(); i++) { + Vector2i region_loc = _region_locations[i]; + Ref region = _regions[region_loc]; + _height_maps.push_back(region->get_height_map()); + } _generated_height_maps.create(_height_maps); calc_height_range(); any_changed = true; @@ -284,17 +279,25 @@ void Terrain3DStorage::update_maps() { } if (_generated_control_maps.is_dirty()) { - LOG(DEBUG_CONT, "Regenerating control layered texture from ", _control_maps.size(), " maps"); + LOG(DEBUG_CONT, "Regenerating control texture array from regions"); + _control_maps.clear(); + for (int i = 0; i < _region_locations.size(); i++) { + Vector2i region_loc = _region_locations[i]; + Ref region = _regions[region_loc]; + _control_maps.push_back(region->get_control_map()); + } _generated_control_maps.create(_control_maps); any_changed = true; emit_signal("control_maps_changed"); } if (_generated_color_maps.is_dirty()) { - LOG(DEBUG_CONT, "Regenerating color layered texture from ", _color_maps.size(), " maps"); - for (int i = 0; i < _color_maps.size(); i++) { - Ref map = _color_maps[i]; - map->generate_mipmaps(); + LOG(DEBUG_CONT, "Regenerating color texture array from regions"); + _color_maps.clear(); + for (int i = 0; i < _region_locations.size(); i++) { + Vector2i region_loc = _region_locations[i]; + Ref region = _regions[region_loc]; + _color_maps.push_back(region->get_color_map()); } _generated_color_maps.create(_color_maps); any_changed = true; @@ -333,7 +336,7 @@ void Terrain3DStorage::save_region(const Vector2i &p_region_loc, const String &p if (region->is_deleted()) { LOG(DEBUG, "File to be deleted: ", path); if (!FileAccess::file_exists(path)) { - LOG(WARN, "File ", path, " doesn't exist"); + LOG(ERROR, "File ", path, " doesn't exist"); return; } Ref da = DirAccess::open(p_dir); @@ -370,109 +373,6 @@ void Terrain3DStorage::load_region(const Vector2i &p_region_loc, const String &p add_region(region, p_update); } -void Terrain3DStorage::set_map_region(const MapType p_map_type, const int p_region_id, const Ref &p_image) { - switch (p_map_type) { - case TYPE_HEIGHT: - if (p_region_id >= 0 && p_region_id < _height_maps.size()) { - _height_maps[p_region_id] = p_image; - force_update_maps(TYPE_HEIGHT); - } else { - LOG(ERROR, "Requested region id is out of bounds. height_maps size: ", _height_maps.size()); - } - break; - case TYPE_CONTROL: - if (p_region_id >= 0 && p_region_id < _control_maps.size()) { - _control_maps[p_region_id] = p_image; - force_update_maps(TYPE_CONTROL); - } else { - LOG(ERROR, "Requested region id is out of bounds. control_maps size: ", _control_maps.size()); - } - break; - case TYPE_COLOR: - if (p_region_id >= 0 && p_region_id < _color_maps.size()) { - _color_maps[p_region_id] = p_image; - force_update_maps(TYPE_COLOR); - } else { - LOG(ERROR, "Requested region id is out of bounds. color_maps size: ", _color_maps.size()); - } - break; - default: - LOG(ERROR, "Requested map type is invalid"); - break; - } -} - -Ref Terrain3DStorage::get_map_region(const MapType p_map_type, const int p_region_id) const { - switch (p_map_type) { - case TYPE_HEIGHT: - if (p_region_id >= 0 && p_region_id < _height_maps.size()) { - return _height_maps[p_region_id]; - } else { - LOG(ERROR, "Requested region id is out of bounds. height_maps size: ", _height_maps.size()); - } - break; - case TYPE_CONTROL: - if (p_region_id >= 0 && p_region_id < _control_maps.size()) { - return _control_maps[p_region_id]; - } else { - LOG(ERROR, "Requested region id is out of bounds. control_maps size: ", _control_maps.size()); - } - break; - case TYPE_COLOR: - if (p_region_id >= 0 && p_region_id < _color_maps.size()) { - return _color_maps[p_region_id]; - } else { - LOG(ERROR, "Requested region id is out of bounds. color_maps size: ", _color_maps.size()); - } - break; - default: - LOG(ERROR, "Requested map type is invalid"); - break; - } - return Ref(); -} - -void Terrain3DStorage::set_maps(const MapType p_map_type, const TypedArray &p_maps, const TypedArray &p_region_ids) { - ERR_FAIL_COND_MSG(p_map_type < 0 || p_map_type >= TYPE_MAX, "Specified map type out of range"); - return; - // Rewrite or move to Terrain3DRegion - /* - if (p_region_ids.is_empty()) { - LOG(INFO, "Setting ", TYPESTR[p_map_type], " maps: ", p_maps.size()); - switch (p_map_type) { - case TYPE_HEIGHT: - _height_maps = sanitize_maps(TYPE_HEIGHT, p_maps); - break; - case TYPE_CONTROL: - _control_maps = sanitize_maps(TYPE_CONTROL, p_maps); - break; - case TYPE_COLOR: - _color_maps = sanitize_maps(TYPE_COLOR, p_maps); - break; - default: - break; - } - } else { - TypedArray sanitized = sanitize_maps(p_map_type, p_maps); - for (int i = 0; i < p_region_ids.size(); i++) { - switch (p_map_type) { - case TYPE_HEIGHT: - _height_maps[p_region_ids[i]] = sanitized[i]; - break; - case TYPE_CONTROL: - _control_maps[p_region_ids[i]] = sanitized[i]; - break; - case TYPE_COLOR: - _color_maps[p_region_ids[i]] = sanitized[i]; - break; - default: - break; - } - } - }*/ - force_update_maps(p_map_type); -} - TypedArray Terrain3DStorage::get_maps(const MapType p_map_type) const { if (p_map_type < 0 || p_map_type >= TYPE_MAX) { LOG(ERROR, "Specified map type out of range"); @@ -526,18 +426,19 @@ void Terrain3DStorage::set_pixel(const MapType p_map_type, const Vector3 &p_glob LOG(ERROR, "Specified map type out of range"); return; } - int region_id = get_region_idp(p_global_position); - if (region_id < 0) { + Vector2i region_loc = get_region_location(p_global_position); + Ref region = _regions[region_loc]; + if (region.is_null()) { + LOG(ERROR, "No region found at: ", p_global_position); return; } - Vector2i region_loc = _region_locations[region_id]; Vector2i global_offset = region_loc * _region_size; Vector3 descaled_pos = p_global_position / _mesh_vertex_spacing; Vector2i img_pos = Vector2i(descaled_pos.x - global_offset.x, descaled_pos.z - global_offset.y); img_pos = img_pos.clamp(V2I_ZERO, Vector2i(_region_size - 1, _region_size - 1)); - Ref map = get_map_region(p_map_type, region_id); + Ref map = region->get_map(p_map_type); map->set_pixelv(img_pos, p_pixel); - set_region_modified(region_loc, true); + region->set_modified(true); } Color Terrain3DStorage::get_pixel(const MapType p_map_type, const Vector3 &p_global_position) const { @@ -545,16 +446,16 @@ Color Terrain3DStorage::get_pixel(const MapType p_map_type, const Vector3 &p_glo LOG(ERROR, "Specified map type out of range"); return COLOR_NAN; } - int region_id = get_region_idp(p_global_position); - if (region_id < 0) { + Vector2i region_loc = get_region_location(p_global_position); + Ref region = _regions[region_loc]; + if (region.is_null()) { return COLOR_NAN; } - Vector2i region_loc = _region_locations[region_id]; Vector2i global_offset = region_loc * _region_size; Vector3 descaled_pos = p_global_position / _mesh_vertex_spacing; Vector2i img_pos = Vector2i(descaled_pos.x - global_offset.x, descaled_pos.z - global_offset.y); img_pos = img_pos.clamp(V2I_ZERO, Vector2i(_region_size - 1, _region_size - 1)); - Ref map = get_map_region(p_map_type, region_id); + Ref map = region->get_map(p_map_type); return map->get_pixelv(img_pos); } @@ -671,42 +572,12 @@ void Terrain3DStorage::force_update_maps(const MapType p_map_type) { _generated_height_maps.clear(); _generated_control_maps.clear(); _generated_color_maps.clear(); + _region_map_dirty = true; break; } update_maps(); } -void Terrain3DStorage::set_multimeshes(const Dictionary &p_multimeshes) { - LOG(INFO, "Loading multimeshes: ", p_multimeshes); - if (_multimeshes != p_multimeshes) { - _multimeshes = p_multimeshes; - emit_signal("multimeshes_changed"); - } -} - -void Terrain3DStorage::set_multimeshes(const Dictionary &p_multimeshes, const TypedArray &p_region_ids) { - if (p_region_ids.is_empty()) { - _multimeshes = p_multimeshes; - } else { - for (int i = 0; i < p_region_ids.size(); i++) { - _multimeshes[p_region_ids[i]] = p_multimeshes[i]; - } - } -} - -Dictionary Terrain3DStorage::get_multimeshes(const TypedArray &p_region_ids) const { - if (p_region_ids.is_empty()) { - return _multimeshes; - } else { - Dictionary output = Dictionary(); - for (int i = 0; i < p_region_ids.size(); i++) { - Vector2i region_loc = get_region_locationi(i); - output[region_loc] = _multimeshes[p_region_ids[i]]; - } - return output; - } -} - void Terrain3DStorage::save_directory(const String &p_dir) { LOG(INFO, "Saving data files to ", p_dir); Array locations = _regions.keys(); @@ -966,8 +837,8 @@ Ref Terrain3DStorage::layered_to_image(const MapType p_map_type) const { Vector2i region_loc = _region_locations[i]; Vector2i img_location = (region_loc - top_left) * _region_size; LOG(DEBUG, "Region to blit: ", region_loc, " Export image coords: ", img_location); - int region_id = get_region_idp(Vector3(region_loc.x, 0, region_loc.y) * _region_size); - img->blit_rect(get_map_region(map_type, region_id), Rect2i(V2I_ZERO, _region_sizev), img_location); + Ref region = _regions[region_loc]; + img->blit_rect(region->get_map(map_type), Rect2i(V2I_ZERO, _region_sizev), img_location); } return img; } @@ -1100,6 +971,10 @@ void Terrain3DStorage::_bind_methods() { BIND_CONSTANT(REGION_MAP_SIZE); + ClassDB::bind_method(D_METHOD("get_region", "region_location"), &Terrain3DStorage::get_region); + ClassDB::bind_method(D_METHOD("get_regionp", "global_position"), &Terrain3DStorage::get_regionp); + ClassDB::bind_method(D_METHOD("has_region", "region_location"), &Terrain3DStorage::has_region); + ClassDB::bind_method(D_METHOD("has_regionp", "global_position"), &Terrain3DStorage::has_regionp); ClassDB::bind_method(D_METHOD("set_region_size", "size"), &Terrain3DStorage::set_region_size); ClassDB::bind_method(D_METHOD("get_region_size"), &Terrain3DStorage::get_region_size); ClassDB::bind_method(D_METHOD("set_region_locations", "region_locations"), &Terrain3DStorage::set_region_locations); @@ -1109,8 +984,6 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("get_region_locationi", "region_id"), &Terrain3DStorage::get_region_locationi); ClassDB::bind_method(D_METHOD("get_region_id", "region_location"), &Terrain3DStorage::get_region_id); ClassDB::bind_method(D_METHOD("get_region_idp", "global_position"), &Terrain3DStorage::get_region_idp); - ClassDB::bind_method(D_METHOD("has_region", "region_location"), &Terrain3DStorage::has_region); - ClassDB::bind_method(D_METHOD("has_regionp", "global_position"), &Terrain3DStorage::has_regionp); ClassDB::bind_method(D_METHOD("add_region", "region", "update"), &Terrain3DStorage::add_region, DEFVAL(true)); ClassDB::bind_method(D_METHOD("add_regionl", "region_location", "update"), &Terrain3DStorage::add_regionl, DEFVAL(true)); ClassDB::bind_method(D_METHOD("add_regionp", "global_position", "update"), &Terrain3DStorage::add_regionp, DEFVAL(true)); @@ -1125,20 +998,15 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("save_region", "directory", "region_location", "16_bit"), &Terrain3DStorage::save_region, DEFVAL(false)); ClassDB::bind_method(D_METHOD("load_region", "directory", "region_location", "update"), &Terrain3DStorage::load_region, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("set_map_region", "map_type", "region_id", "image"), &Terrain3DStorage::set_map_region); - ClassDB::bind_method(D_METHOD("get_map_region", "map_type", "region_id"), &Terrain3DStorage::get_map_region); - ClassDB::bind_method(D_METHOD("set_maps", "map_type", "maps"), &Terrain3DStorage::set_maps); ClassDB::bind_method(D_METHOD("get_maps", "map_type"), &Terrain3DStorage::get_maps); ClassDB::bind_method(D_METHOD("get_maps_copy", "map_type"), &Terrain3DStorage::get_maps_copy); - ClassDB::bind_method(D_METHOD("set_height_maps", "maps"), &Terrain3DStorage::set_height_maps); ClassDB::bind_method(D_METHOD("get_height_maps"), &Terrain3DStorage::get_height_maps); - ClassDB::bind_method(D_METHOD("set_control_maps", "maps"), &Terrain3DStorage::set_control_maps); ClassDB::bind_method(D_METHOD("get_control_maps"), &Terrain3DStorage::get_control_maps); - ClassDB::bind_method(D_METHOD("set_color_maps", "maps"), &Terrain3DStorage::set_color_maps); ClassDB::bind_method(D_METHOD("get_color_maps"), &Terrain3DStorage::get_color_maps); ClassDB::bind_method(D_METHOD("get_height_maps_rid"), &Terrain3DStorage::get_height_maps_rid); ClassDB::bind_method(D_METHOD("get_control_maps_rid"), &Terrain3DStorage::get_control_maps_rid); ClassDB::bind_method(D_METHOD("get_color_maps_rid"), &Terrain3DStorage::get_color_maps_rid); + ClassDB::bind_method(D_METHOD("set_pixel", "map_type", "global_position", "pixel"), &Terrain3DStorage::set_pixel); ClassDB::bind_method(D_METHOD("get_pixel", "map_type", "global_position"), &Terrain3DStorage::get_pixel); ClassDB::bind_method(D_METHOD("set_height", "global_position", "height"), &Terrain3DStorage::set_height); @@ -1163,17 +1031,9 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("export_image", "file_name", "map_type"), &Terrain3DStorage::export_image); ClassDB::bind_method(D_METHOD("layered_to_image", "map_type"), &Terrain3DStorage::layered_to_image); - //ClassDB::bind_method(D_METHOD("set_multimeshes", "multimeshes"), &Terrain3DStorage::set_multimeshes); - //ClassDB::bind_method(D_METHOD("get_multimeshes"), &Terrain3DStorage::get_multimeshes); - //ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "multimeshes", PROPERTY_HINT_NONE, "", ro_flags), "set_multimeshes", "get_multimeshes"); - int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; //ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "64:64, 128:128, 256:256, 512:512, 1024:1024, 2048:2048"), "set_region_size", "get_region_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "1024:1024"), "set_region_size", "get_region_size"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "region_locations", PROPERTY_HINT_ARRAY_TYPE, vformat("%tex_size/%tex_size:%tex_size", Variant::VECTOR2, PROPERTY_HINT_NONE), ro_flags), "set_region_locations", "get_region_locations"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "height_maps", PROPERTY_HINT_ARRAY_TYPE, vformat("%tex_size/%tex_size:%tex_size", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Image"), ro_flags), "set_height_maps", "get_height_maps"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "control_maps", PROPERTY_HINT_ARRAY_TYPE, vformat("%tex_size/%tex_size:%tex_size", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Image"), ro_flags), "set_control_maps", "get_control_maps"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "color_maps", PROPERTY_HINT_ARRAY_TYPE, vformat("%tex_size/%tex_size:%tex_size", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Image"), ro_flags), "set_color_maps", "get_color_maps"); ADD_SIGNAL(MethodInfo("maps_changed")); ADD_SIGNAL(MethodInfo("region_map_changed")); @@ -1182,5 +1042,4 @@ void Terrain3DStorage::_bind_methods() { ADD_SIGNAL(MethodInfo("color_maps_changed")); ADD_SIGNAL(MethodInfo("region_size_changed")); ADD_SIGNAL(MethodInfo("maps_edited", PropertyInfo(Variant::AABB, "edited_area"))); - ADD_SIGNAL(MethodInfo("multimeshes_changed")); } diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index 658174c1..d2b9d39c 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -78,9 +78,7 @@ class Terrain3DStorage : public Object { GeneratedTexture _generated_control_maps; GeneratedTexture _generated_color_maps; - // Foliage Instancer contains MultiMeshes saved to disk - // Dictionary[region_location:Vector2i] -> Dictionary[mesh_id:int] -> MultiMesh - Dictionary _multimeshes; + /// Move to Region? RegionSize _region_size = SIZE_1024; Vector2i _region_sizev = Vector2i(_region_size, _region_size); @@ -100,12 +98,16 @@ class Terrain3DStorage : public Object { /// Regions + Ref get_region(const Vector2i &p_region_loc) const { return _regions[p_region_loc]; } + Ref get_regionp(const Vector3 &p_global_position) const { return _regions[get_region_location(p_global_position)]; } + bool has_region(const Vector2i &p_region_loc) const { return get_region_id(p_region_loc) != -1; } + bool has_regionp(const Vector3 &p_global_position) const { return get_region_idp(p_global_position) != -1; } + void set_region_size(const RegionSize p_size); RegionSize get_region_size() const { return _region_size; } Vector2i get_region_sizev() const { return _region_sizev; } int get_region_count() const { return _region_locations.size(); } - Dictionary get_regions() { return _regions; } - Ref get_region(const Vector2i &p_region_loc) { return _regions[p_region_loc]; } + Dictionary get_regions() const { return _regions; } void set_region_modified(const Vector2i &p_region_loc, const bool p_modified = true); bool is_region_modified(const Vector2i &p_region_loc) const; void set_region_deleted(const Vector2i &p_region_loc, const bool p_deleted = true); @@ -115,8 +117,6 @@ class Terrain3DStorage : public Object { Vector2i get_region_locationi(const int p_region_id) const; int get_region_id(const Vector2i &p_region_loc) const; int get_region_idp(const Vector3 &p_global_position) const; - bool has_region(const Vector2i &p_region_loc) const { return get_region_id(p_region_loc) != -1; } - bool has_regionp(const Vector3 &p_global_position) const { return get_region_idp(p_global_position) != -1; } void set_region_locations(const TypedArray &p_locations); TypedArray get_region_locations() const { return _region_locations; } PackedInt32Array get_region_map() const { return _region_map; } @@ -137,24 +137,24 @@ class Terrain3DStorage : public Object { void remove_regionp(const Vector3 &p_global_position, const bool p_update = true); // Maps - void set_map_region(const MapType p_map_type, const int p_region_id, const Ref &p_image); - Ref get_map_region(const MapType p_map_type, const int p_region_id) const; - void set_maps(const MapType p_map_type, const TypedArray &p_maps, const TypedArray &p_region_ids = TypedArray()); + + void update_maps(); + void force_update_maps(const MapType p_map = TYPE_MAX); + TypedArray get_maps(const MapType p_map_type) const; TypedArray get_maps_copy(const MapType p_map_type, const TypedArray &p_region_ids = TypedArray()) const; - void set_height_maps(const TypedArray &p_maps) { set_maps(TYPE_HEIGHT, p_maps); } TypedArray get_height_maps() const { return _height_maps; } - RID get_height_maps_rid() const { return _generated_height_maps.get_rid(); } - void set_control_maps(const TypedArray &p_maps) { set_maps(TYPE_CONTROL, p_maps); } TypedArray get_control_maps() const { return _control_maps; } - RID get_control_maps_rid() const { return _generated_control_maps.get_rid(); } - void set_color_maps(const TypedArray &p_maps) { set_maps(TYPE_COLOR, p_maps); } TypedArray get_color_maps() const { return _color_maps; } + RID get_height_maps_rid() const { return _generated_height_maps.get_rid(); } + RID get_control_maps_rid() const { return _generated_control_maps.get_rid(); } RID get_color_maps_rid() const { return _generated_color_maps.get_rid(); } + void set_pixel(const MapType p_map_type, const Vector3 &p_global_position, const Color &p_pixel); Color get_pixel(const MapType p_map_type, const Vector3 &p_global_position) const; void set_height(const Vector3 &p_global_position, const real_t p_height); real_t get_height(const Vector3 &p_global_position) const; + Vector3 get_normal(const Vector3 &global_position) const; void set_color(const Vector3 &p_global_position, const Color &p_color); Color get_color(const Vector3 &p_global_position) const; void set_control(const Vector3 &p_global_position, const uint32_t p_control); @@ -165,9 +165,6 @@ class Terrain3DStorage : public Object { real_t get_angle(const Vector3 &p_global_position) const; real_t get_scale(const Vector3 &p_global_position) const; Vector3 get_mesh_vertex(const int32_t p_lod, const HeightFilter p_filter, const Vector3 &p_global_position) const; - Vector3 get_normal(const Vector3 &global_position) const; - void update_maps(); - void force_update_maps(const MapType p_map = TYPE_MAX); void clear_edited_area(); void add_edited_area(const AABB &p_area); @@ -183,12 +180,6 @@ class Terrain3DStorage : public Object { Error export_image(const String &p_file_name, const MapType p_map_type = TYPE_HEIGHT) const; Ref layered_to_image(const MapType p_map_type) const; - // Instancer - void set_multimeshes(const Dictionary &p_multimeshes); - void set_multimeshes(const Dictionary &p_multimeshes, const TypedArray &p_region_ids); - Dictionary get_multimeshes() const { return _multimeshes; } - Dictionary get_multimeshes(const TypedArray &p_region_ids) const; - // Utility void print_audit_data() const; From 5bbc693458ad1b0865d6193eaf5af2fb3c648e5b Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:22:43 +0700 Subject: [PATCH 15/19] Implement Undo/redo for add/remove region --- src/terrain_3d.cpp | 15 +- src/terrain_3d_editor.cpp | 425 +++++++++++++++-------------------- src/terrain_3d_editor.h | 42 +++- src/terrain_3d_instancer.cpp | 37 +-- src/terrain_3d_instancer.h | 2 + src/terrain_3d_region.cpp | 80 ++++++- src/terrain_3d_region.h | 16 +- src/terrain_3d_storage.cpp | 120 +++++----- src/terrain_3d_storage.h | 69 +++--- src/terrain_3d_util.cpp | 49 +++- src/terrain_3d_util.h | 3 +- 11 files changed, 492 insertions(+), 366 deletions(-) diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index 54450ead..1dacbb10 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -27,7 +27,7 @@ /////////////////////////// // Initialize static member variable -int Terrain3D::debug_level{ DEBUG }; +int Terrain3D::debug_level{ ERROR }; void Terrain3D::_initialize() { LOG(INFO, "Checking initialization of main subsystems"); @@ -66,7 +66,7 @@ void Terrain3D::_initialize() { LOG(DEBUG, "Connecting _storage::maps_changed signal to _material->_update_maps()"); _storage->connect("maps_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::_update_maps)); } - // Height map was regenerated - update aabbs - Probably remove and just use maps_edited + // Height map was regenerated, update aabbs if (!_storage->is_connected("height_maps_changed", callable_mp(this, &Terrain3D::update_aabbs))) { LOG(DEBUG, "Connecting _storage::height_maps_changed signal to update_aabbs()"); _storage->connect("height_maps_changed", callable_mp(this, &Terrain3D::update_aabbs)); @@ -141,11 +141,9 @@ void Terrain3D::_destroy_containers() { void Terrain3D::_destroy_labels() { Array labels = _label_nodes->get_children(); + LOG(DEBUG, "Destroying ", labels.size(), " region labels"); for (int i = 0; i < labels.size(); i++) { Node *label = cast_to(labels[i]); - if (label != nullptr) { - LOG(DEBUG, "Destroying label: ", label->get_name()); - } memdelete_safely(label); } } @@ -1028,11 +1026,10 @@ void Terrain3D::update_aabbs() { } } -/* Iterate over ground to find intersection point between two rays: +/* Returns the point a ray intersects the ground using the GPU depth texture * p_src_pos (camera position) * p_direction (camera direction looking at the terrain) - * test_dir (camera direction 0 Y, traversing terrain along height - * Returns vec3(Double max 3.402823466e+38F) on no intersection. Test w/ if (var.x < 3.4e38) + * Returns Vec3(NAN) on error or vec3(3.402823466e+38F) on no intersection. Test w/ if (var.x < 3.4e38) */ Vector3 Terrain3D::get_intersection(const Vector3 &p_src_pos, const Vector3 &p_direction) { if (!is_instance_valid(_camera_instance_id)) { @@ -1097,10 +1094,10 @@ void Terrain3D::set_show_region_labels(const bool p_enabled) { } void Terrain3D::update_region_labels() { - LOG(DEBUG, "Updating region labels"); _destroy_labels(); if (_show_region_labels && _storage != nullptr) { Array region_locations = _storage->get_region_locations(); + LOG(DEBUG, "Creating ", region_locations.size(), " region labels"); for (int i = 0; i < region_locations.size(); i++) { Label3D *label = memnew(Label3D); String text = region_locations[i]; diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp index 78311bc7..53347aae 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -23,63 +23,81 @@ void Terrain3DEditor::_send_region_aabb(const Vector2i &p_region_loc, const Vect _terrain->get_storage()->add_edited_area(edited_area); } -void Terrain3DEditor::_operate_region(const Vector3 &p_global_position) { - Vector2i region_loc = _terrain->get_storage()->get_region_location(p_global_position); - bool has_region = _terrain->get_storage()->has_region(region_loc); +// Process location to add new region, mark as deleted, or just retrieve +Ref Terrain3DEditor::_operate_region(const Vector2i &p_region_loc) { bool changed = false; Vector2 height_range; + Terrain3DStorage *storage = _terrain->get_storage(); - if (_operation == ADD) { - if (!has_region) { - _terrain->get_storage()->add_region_blank(region_loc); - _terrain->get_storage()->set_region_modified(region_loc, true); - changed = true; + // Check if in bounds, limiting errors + bool can_print = false; + uint64_t ticks = Time::get_singleton()->get_ticks_msec(); + if (ticks - _last_region_bounds_error > 1000) { + _last_region_bounds_error = ticks; + can_print = true; + } + if (storage->get_region_map_index(p_region_loc) < 0) { + if (can_print) { + LOG(ERROR, "Specified position outside of maximum bounds"); + } + return Ref(); + } + + // Get Region & dump data if debug + Ref region = storage->get_region(p_region_loc); + if (can_print) { + LOG(DEBUG, "Tool: ", _tool, " Op: ", _operation, " processing region ", p_region_loc, ": ", + region.is_valid() ? String::num_uint64(region->get_instance_id()) : "Null"); + if (region.is_valid()) { + LOG(DEBUG, region->get_data()); } - } else { // Remove region - if (has_region) { - Ref region = _terrain->get_storage()->get_region(region_loc); - height_range = region->get_height_range(); - _terrain->get_storage()->remove_region(region); + } + + // Create new region if location is null or deleted + if (region.is_null() || (region.is_valid() && region->is_deleted())) { + // And tool is Add Region, or Height + auto_regions + if ((_tool == REGION && _operation == ADD) || (_tool == HEIGHT && _brush_data["auto_regions"])) { + region = storage->add_region_blank(p_region_loc); changed = true; + if (region.is_null()) { + // A new region can't be made + LOG(ERROR, "A new region cannot be created"); + return region; + } } } + + // If removing region + else if (region.is_valid() && _tool == REGION && _operation == SUBTRACT) { + _original_regions.push_back(region); + height_range = region->get_height_range(); + _terrain->get_storage()->remove_region(region); + _terrain->get_instancer()->force_update_mmis(); + changed = true; + } + if (changed) { - _modified = true; - _send_region_aabb(region_loc, height_range); + _added_removed_locations.push_back(p_region_loc); + region->set_modified(true); + _send_region_aabb(p_region_loc, height_range); } + return region; } void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_t p_camera_direction) { LOG(DEBUG_CONT, "Operating at ", p_global_position, " tool type ", _tool, " op ", _operation); - MapType map_type; - switch (_tool) { - case HEIGHT: - case INSTANCER: - map_type = TYPE_HEIGHT; - break; - case TEXTURE: - case AUTOSHADER: - case HOLES: - case NAVIGATION: - case ANGLE: - case SCALE: - map_type = TYPE_CONTROL; - break; - case COLOR: - case ROUGHNESS: - map_type = TYPE_COLOR; - break; - default: - LOG(ERROR, "Invalid tool selected"); - return; + MapType map_type = _get_map_type(); + if (map_type == TYPE_MAX) { + LOG(ERROR, "Invalid tool selected"); + return; } Terrain3DStorage *storage = _terrain->get_storage(); int region_size = storage->get_region_size(); Vector2i region_vsize = Vector2i(region_size, region_size); - // If no region and not height, skip whole function. Checked again later + // If no region and can't add one, skip whole function. Checked again later if (!storage->has_regionp(p_global_position) && (!_brush_data["auto_regions"] || _tool != HEIGHT)) { return; } @@ -145,7 +163,6 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ } else { _terrain->get_instancer()->remove_instances(p_global_position, _brush_data); } - _modified = true; return; } @@ -160,20 +177,10 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ // Get region for current brush pixel global position Vector2i region_loc = storage->get_region_location(brush_global_position); - Ref region = storage->get_region(region_loc); - // If no region, create one + Ref region = _operate_region(region_loc); + // If no region and can't make one, skip if (region.is_null()) { - // Except if outside of regions and we can't add one - if (!_brush_data["auto_regions"] || _tool != HEIGHT) { - continue; - } - region = storage->add_region_blank(region_loc); - if (region.is_null()) { - // A new region can't be made - continue; - } - _modified = true; - _send_region_aabb(region_loc); + continue; } // Get map for this region and tool @@ -437,9 +444,8 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ break; } } + backup_region(region); map->set_pixelv(map_pixel_position, dest); - _modified = true; - region->set_modified(true); } } } @@ -447,211 +453,128 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ storage->add_edited_area(edited_area); } -Dictionary Terrain3DEditor::_get_undo_data() const { - Dictionary data; - return data; // ignore undo for now +void Terrain3DEditor::_store_undo() { + IS_INIT_COND_MESG(_terrain->get_plugin() == nullptr, "_terrain isn't initialized, returning", VOID); if (_tool < 0 || _tool >= TOOL_MAX) { - return data; + return; } - - TypedArray e_regions; - Array regions = _terrain->get_storage()->get_regions().values(); - for (int i = 0; i < regions.size(); i++) { - bool is_modified = static_cast>(regions[i])->is_modified(); - if (is_modified) { - e_regions.push_back(i); + LOG(DEBUG, "Finalize undo & redo snapshots"); + Dictionary redo_data; + // Store current locations; Original backed up in start_operation() + redo_data["region_locations"] = _terrain->get_storage()->get_region_locations().duplicate(); + // Store original and current backups of edited regions + _undo_data["edited_regions"] = _original_regions; + redo_data["edited_regions"] = _edited_regions; + + // Store regions that were added or removed + if (_added_removed_locations.size() > 0) { + if (_tool == REGION && _operation == SUBTRACT) { + _undo_data["removed_regions"] = _added_removed_locations; + redo_data["added_regions"] = _added_removed_locations; + } else { + _undo_data["added_regions"] = _added_removed_locations; + redo_data["removed_regions"] = _added_removed_locations; } } - //= _terrain->get_storage()->get_regions_under_aabb(_terrain->get_storage()->get_edited_area()); - data["edited_regions"] = e_regions; - LOG(DEBUG, "E Regions ", data["edited_regions"]); - switch (_tool) { - case REGION: - LOG(DEBUG, "Storing region locations"); - data["is_add"] = _operation == ADD; - if (_operation == SUBTRACT) { - data["region_locations"] = _terrain->get_storage()->get_region_locations().duplicate(); - data["height_map"] = _terrain->get_storage()->get_maps_copy(TYPE_HEIGHT, e_regions); - data["control_map"] = _terrain->get_storage()->get_maps_copy(TYPE_CONTROL, e_regions); - data["color_map"] = _terrain->get_storage()->get_maps_copy(TYPE_COLOR, e_regions); - data["height_range"] = _terrain->get_storage()->get_height_range(); - data["edited_area"] = _terrain->get_storage()->get_edited_area(); - } - break; - - case HEIGHT: - LOG(DEBUG, "Storing height maps and range"); - data["region_locations"] = _terrain->get_storage()->get_region_locations().duplicate(); - data["height_map"] = _terrain->get_storage()->get_maps_copy(TYPE_HEIGHT, e_regions); - data["height_range"] = _terrain->get_storage()->get_height_range(); - data["edited_area"] = _terrain->get_storage()->get_edited_area(); - break; - - case HOLES: - // Holes can remove instances - //data["multimeshes"] = _terrain->get_storage()->get_multimeshes().duplicate(true); - LOG(DEBUG, "Storing Multimesh: ", data["multimeshes"]); - case TEXTURE: - case AUTOSHADER: - case NAVIGATION: - LOG(DEBUG, "Storing control maps"); - data["control_map"] = _terrain->get_storage()->get_maps_copy(TYPE_CONTROL, e_regions); - break; - - case COLOR: - case ROUGHNESS: - LOG(DEBUG, "Storing color maps"); - data["color_map"] = _terrain->get_storage()->get_maps_copy(TYPE_COLOR, e_regions); - break; - - case INSTANCER: - //data["multimeshes"] = _terrain->get_storage()->get_multimeshes().duplicate(true); - LOG(DEBUG, "Storing Multimesh: ", data["multimeshes"]); - break; - - default: - return data; - } - return data; -} - -void Terrain3DEditor::_store_undo() { - IS_INIT_COND_MESG(_terrain->get_plugin() == nullptr, "_terrain isn't initialized, returning", VOID); - if (_tool < 0 || _tool >= TOOL_MAX) { - return; + if (_undo_data.has("edited_area")) { + _undo_data["edited_area"] = _terrain->get_storage()->get_edited_area(); + LOG(DEBUG, "Updating undo snapshot edited area: ", _undo_data["edited_area"]); } + + // Store data in Godot's Undo/Redo Manager LOG(INFO, "Storing undo snapshot..."); EditorUndoRedoManager *undo_redo = _terrain->get_plugin()->get_undo_redo(); - String action_name = String("Terrain3D ") + OPNAME[_operation] + String(" ") + TOOLNAME[_tool]; LOG(DEBUG, "Creating undo action: '", action_name, "'"); undo_redo->create_action(action_name); - if (_undo_data.has("edited_area")) { - _undo_data["edited_area"] = _terrain->get_storage()->get_edited_area(); - LOG(DEBUG, "Updating undo snapshot edited area: ", _undo_data["edited_area"]); - } - LOG(DEBUG, "Storing undo snapshot: ", _undo_data); - undo_redo->add_undo_method(this, "apply_undo", _undo_data.duplicate()); - - LOG(DEBUG, "Setting up redo snapshot..."); - Dictionary redo_set = _get_undo_data(); + undo_redo->add_undo_method(this, "apply_undo", _undo_data); + for (int i = 0; i < _original_regions.size(); i++) { + Ref region = _original_regions[i]; + LOG(DEBUG, "Original Region: ", region->get_data()); + } - LOG(DEBUG, "Storing redo snapshot: ", redo_set); - undo_redo->add_do_method(this, "apply_undo", redo_set); + LOG(DEBUG, "Storing redo snapshot: ", redo_data); + undo_redo->add_do_method(this, "apply_undo", redo_data); + for (int i = 0; i < _edited_regions.size(); i++) { + Ref region = _edited_regions[i]; + LOG(DEBUG, "Edited Region: ", region->get_data()); + } LOG(DEBUG, "Committing undo action"); undo_redo->commit_action(false); } -void Terrain3DEditor::_apply_undo(const Dictionary &p_set) { +void Terrain3DEditor::_apply_undo(const Dictionary &p_data) { IS_INIT_COND_MESG(_terrain->get_plugin() == nullptr, "_terrain isn't initialized, returning", VOID); - LOG(INFO, "Applying Undo/Redo set. Array size: ", p_set.size()); - LOG(DEBUG, "Apply undo received: ", p_set); - - TypedArray e_regions = p_set["edited_regions"]; - - if (p_set.has("is_add")) { - // FIXME: This block assumes a lot about the structure of the dictionary and the number of elements - bool is_add = p_set["is_add"]; - if (is_add) { - // Remove - LOG(DEBUG, "Removing region..."); - - TypedArray current; - Array regions = _terrain->get_storage()->get_regions().values(); - for (int i = 0; i < regions.size(); i++) { - bool is_modified = static_cast>(regions[i])->is_modified(); - if (is_modified) { - current.push_back(i); - } - } - - TypedArray diff; + LOG(INFO, "Applying Undo/Redo data"); - for (int i = 0; i < current.size(); i++) { - if (!regions.has(current[i])) { - diff.push_back(current[i]); - } - } - - for (int i = 0; i < diff.size(); i++) { - _terrain->get_storage()->remove_region(diff[i]); - } - } else { - // Add region - TypedArray current; - Array regions = _terrain->get_storage()->get_regions().values(); - for (int i = 0; i < regions.size(); i++) { - bool is_modified = static_cast>(regions[i])->is_modified(); - if (is_modified) { - current.push_back(i); - } - } - - TypedArray diff; + Terrain3DStorage *storage = _terrain->get_storage(); - for (int i = 0; i < current.size(); i++) { - if (!regions.has(current[i])) { - diff.push_back(current[i]); - } + if (p_data.has("edited_regions")) { + Util::print_arr("Edited regions", p_data["edited_regions"]); + TypedArray undo_regions = p_data["edited_regions"]; + LOG(DEBUG, "Backup has ", undo_regions.size(), " edited regions"); + for (int i = 0; i < undo_regions.size(); i++) { + Ref region = undo_regions[i]; + if (region.is_null()) { + LOG(ERROR, "Null region saved in undo data. Please report this error."); + continue; } + region->sanitize_map(); // Live data may not have some maps so must be sanitized + Dictionary regions = storage->get_regions_all(); + regions[region->get_location()] = region; + region->set_modified(true); + LOG(DEBUG, "Edited: ", region->get_data()); + } + } - LOG(DEBUG, "Re-adding regions..."); - for (int i = 0; i < diff.size(); i++) { - Vector2i new_region = ((TypedArray)p_set["region_locations"])[regions[0]]; - int size = _terrain->get_storage()->get_region_size(); - Vector3 new_region_position = Vector3(new_region.x * size, 0, new_region.y * size); - - TypedArray map_info = TypedArray(); - map_info.resize(3); - map_info[TYPE_HEIGHT] = ((TypedArray)p_set["height_map"])[0]; - map_info[TYPE_CONTROL] = ((TypedArray)p_set["control_map"])[0]; - map_info[TYPE_COLOR] = ((TypedArray)p_set["color_map"])[0]; - Ref region; - region.instantiate(); - region->set_maps(map_info); - _terrain->get_storage()->add_region(region); - } + if (p_data.has("added_regions")) { + LOG(DEBUG, "Added regions: ", p_data["added_regions"]); + TypedArray region_locs = p_data["added_regions"]; + for (int i = 0; i < region_locs.size(); i++) { + storage->set_region_deleted(region_locs[i], true); + storage->set_region_modified(region_locs[i], true); + LOG(DEBUG, "Marking region: ", region_locs[i], " +deleted, +modified"); } - } else { - Array keys = p_set.keys(); - for (int i = 0; i < keys.size(); i++) { - String key = keys[i]; - if (key == "region_locations") { - _terrain->get_storage()->set_region_locations(p_set[key]); - } else if (key == "height_map") { - //_terrain->get_storage()->set_maps(TYPE_HEIGHT, p_set[key], e_regions); - } else if (key == "control_map") { - //_terrain->get_storage()->set_maps(TYPE_CONTROL, p_set[key], e_regions); - } else if (key == "color_map") { - //_terrain->get_storage()->set_maps(TYPE_COLOR, p_set[key], e_regions); - } else if (key == "height_range") { - //_terrain->get_storage()->set_height_range(p_set[key]); - } else if (key == "edited_area") { - _terrain->get_storage()->clear_edited_area(); - _terrain->get_storage()->add_edited_area(p_set[key]); - } else if (key == "multimeshes") { - //_terrain->get_storage()->set_multimeshes(p_set[key], e_regions); - } + } + if (p_data.has("removed_regions")) { + LOG(DEBUG, "Removed regions: ", p_data["removed_regions"]); + TypedArray region_locs = p_data["removed_regions"]; + for (int i = 0; i < region_locs.size(); i++) { + storage->set_region_deleted(region_locs[i], false); + storage->set_region_modified(region_locs[i], true); + LOG(DEBUG, "Marking region: ", region_locs[i], " -deleted, +modified"); } } - /* Rework all of undo - _terrain->get_storage()->set_all_regions_modified(false); - // Roll back to previous modified state - for (int i = 0; i < e_regions.size(); i++) { - _terrain->get_storage()->set_modified(i); - }*/ + // After all regions are in place, reset the region map, which also calls update_maps + if (p_data.has("region_locations")) { + // Load w/ duplicate or it gets a bit wonky undoing removed regions w/ saves + _terrain->get_storage()->set_region_locations(p_data["region_locations"].duplicate()); + Array locations = storage->get_region_locations(); + LOG(DEBUG, "Locations(", locations.size(), "): ", locations); + } + + /* else if (key == "height_range") { + //_terrain->get_storage()->set_height_range(p_set[key]); + } else if (key == "edited_area") { + _terrain->get_storage()->clear_edited_area(); + _terrain->get_storage()->add_edited_area(p_set[key]); + } else if (key == "multimeshes") { + //_terrain->get_storage()->set_multimeshes(p_set[key]); + } + } + */ + storage->force_update_maps(); + _terrain->get_instancer()->force_update_mmis(); if (_terrain->get_plugin()->has_method("update_grid")) { LOG(DEBUG, "Calling GDScript update_grid()"); _terrain->get_plugin()->call("update_grid"); } - _pending_undo = false; - _modified = false; } /////////////////////////// @@ -727,24 +650,24 @@ void Terrain3DEditor::set_tool(const Tool p_tool) { void Terrain3DEditor::start_operation(const Vector3 &p_global_position) { IS_STORAGE_INIT_MESG("Terrain isn't initialized", VOID); LOG(INFO, "Setting up undo snapshot..."); - _undo_data.clear(); - _undo_data = _get_undo_data(); - _pending_undo = true; - _modified = false; // Undo created, but don't save unless terrain modified + _undo_data = Dictionary(); // New pointer instead of clear + _undo_data["region_locations"] = _terrain->get_storage()->get_region_locations().duplicate(); + _is_operating = true; + _original_regions = TypedArray(); // New pointers instead of clear + _edited_regions = TypedArray(); + _added_removed_locations = TypedArray(); // Reset counter at start to ensure first click places an instance _terrain->get_instancer()->reset_instance_counter(); _terrain->get_storage()->clear_edited_area(); _operation_position = p_global_position; _operation_movement = Vector3(); - if (_tool == REGION) { - _operate_region(p_global_position); - } } // Called on mouse movement with left mouse button down void Terrain3DEditor::operate(const Vector3 &p_global_position, const real_t p_camera_direction) { IS_STORAGE_INIT_MESG("Terrain isn't initialized", VOID); - if (!_pending_undo) { + if (!_is_operating) { + LOG(ERROR, "Run start_operation() before operating"); return; } _operation_movement = p_global_position - _operation_position; @@ -763,22 +686,43 @@ void Terrain3DEditor::operate(const Vector3 &p_global_position, const real_t p_c _operation_movement *= 0.125; // 1/8th if (_tool == REGION) { - _operate_region(p_global_position); + _operate_region(_terrain->get_storage()->get_region_location(p_global_position)); } else if (_tool >= 0 && _tool < TOOL_MAX) { _operate_map(p_global_position, p_camera_direction); } } +void Terrain3DEditor::backup_region(const Ref &p_region) { + if (_is_operating && p_region.is_valid() && !p_region->is_edited()) { + LOG(DEBUG, "Storing original copy of region: ", p_region->get_location()); + _original_regions.push_back(p_region->duplicate(true)); + _edited_regions.push_back(p_region); + p_region->set_edited(true); + p_region->set_modified(true); + } +} + // Called on left mouse button released void Terrain3DEditor::stop_operation() { IS_STORAGE_INIT_MESG("Terrain isn't initialized", VOID); // If undo was created and terrain actually modified, store it - if (_pending_undo && _modified) { + LOG(DEBUG, "Backed up regions: ", _original_regions.size(), ", Edited regions: ", _edited_regions.size(), + ", Added/Removed regions: ", _added_removed_locations.size()); + if (_is_operating && (!_added_removed_locations.is_empty() || !_edited_regions.is_empty())) { + for (int i = 0; i < _edited_regions.size(); i++) { + Ref region = _edited_regions[i]; + region->set_edited(false); + LOG(DEBUG, "Edited region: ", region->get_data()); + // Make duplicate for redo backup + _edited_regions[i] = region->duplicate(true); + } _store_undo(); - _pending_undo = false; - _modified = false; - _terrain->get_storage()->clear_edited_area(); } + _original_regions = TypedArray(); //New pointers instead of clear + _edited_regions = TypedArray(); + _added_removed_locations = TypedArray(); + _terrain->get_storage()->clear_edited_area(); + _is_operating = false; } /////////////////////////// @@ -815,9 +759,10 @@ void Terrain3DEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("set_operation", "operation"), &Terrain3DEditor::set_operation); ClassDB::bind_method(D_METHOD("get_operation"), &Terrain3DEditor::get_operation); ClassDB::bind_method(D_METHOD("start_operation", "position"), &Terrain3DEditor::start_operation); + ClassDB::bind_method(D_METHOD("is_operating"), &Terrain3DEditor::is_operating); ClassDB::bind_method(D_METHOD("operate", "position", "camera_direction"), &Terrain3DEditor::operate); + ClassDB::bind_method(D_METHOD("backup_region", "region"), &Terrain3DEditor::backup_region); ClassDB::bind_method(D_METHOD("stop_operation"), &Terrain3DEditor::stop_operation); - ClassDB::bind_method(D_METHOD("is_operating"), &Terrain3DEditor::is_operating); - ClassDB::bind_method(D_METHOD("apply_undo", "maps"), &Terrain3DEditor::_apply_undo); + ClassDB::bind_method(D_METHOD("apply_undo", "data"), &Terrain3DEditor::_apply_undo); } diff --git a/src/terrain_3d_editor.h b/src/terrain_3d_editor.h index 0975f175..a590cd50 100644 --- a/src/terrain_3d_editor.h +++ b/src/terrain_3d_editor.h @@ -7,6 +7,7 @@ #include #include "terrain_3d.h" +#include "terrain_3d_region.h" using namespace godot; @@ -73,22 +74,25 @@ class Terrain3DEditor : public Object { Vector3 _operation_position = Vector3(); Vector3 _operation_movement = Vector3(); Array _operation_movement_history; - bool _pending_undo = false; // Undo created on each click - bool _modified = false; // Tracks if any region is actually modified + bool _is_operating = false; + uint64_t _last_region_bounds_error = 0; + TypedArray _original_regions; // Queue for undo + TypedArray _edited_regions; // Queue for redo + TypedArray _added_removed_locations; // Queue for added/removed locations AABB _modified_area; Dictionary _undo_data; // See _get_undo_data for definition uint64_t _last_pen_tick = 0; void _send_region_aabb(const Vector2i &p_region_loc, const Vector2 &p_height_range = Vector2()); - void _operate_region(const Vector3 &p_global_position); + Ref _operate_region(const Vector2i &p_region_loc); void _operate_map(const Vector3 &p_global_position, const real_t p_camera_direction); + MapType _get_map_type() const; bool _is_in_bounds(const Vector2i &p_position, const Vector2i &p_max_position) const; Vector2 _get_uv_position(const Vector3 &p_global_position, const int p_region_size, const real_t p_vertex_spacing) const; Vector2 _get_rotated_uv(const Vector2 &p_uv, const real_t p_angle) const; - Dictionary _get_undo_data() const; void _store_undo(); - void _apply_undo(const Dictionary &p_set); + void _apply_undo(const Dictionary &p_data); public: Terrain3DEditor() {} @@ -104,9 +108,10 @@ class Terrain3DEditor : public Object { Operation get_operation() const { return _operation; } void start_operation(const Vector3 &p_global_position); + bool is_operating() const { return _is_operating; } void operate(const Vector3 &p_global_position, const real_t p_camera_direction); + void backup_region(const Ref &p_region); void stop_operation(); - bool is_operating() const { return _pending_undo; } protected: static void _bind_methods(); @@ -117,6 +122,29 @@ VARIANT_ENUM_CAST(Terrain3DEditor::Tool); /// Inline functions +inline MapType Terrain3DEditor::_get_map_type() const { + switch (_tool) { + case HEIGHT: + case INSTANCER: + return TYPE_HEIGHT; + break; + case TEXTURE: + case AUTOSHADER: + case HOLES: + case NAVIGATION: + case ANGLE: + case SCALE: + return TYPE_CONTROL; + break; + case COLOR: + case ROUGHNESS: + return TYPE_COLOR; + break; + default: + return TYPE_MAX; + } +} + inline bool Terrain3DEditor::_is_in_bounds(const Vector2i &p_position, const Vector2i &p_max_position) const { bool more_than_min = p_position.x >= 0 && p_position.y >= 0; bool less_than_max = p_position.x < p_max_position.x && p_position.y < p_max_position.y; @@ -134,7 +162,7 @@ inline Vector2 Terrain3DEditor::_get_uv_position(const Vector3 &p_global_positio inline Vector2 Terrain3DEditor::_get_rotated_uv(const Vector2 &p_uv, const real_t p_angle) const { Vector2 rotation_offset = Vector2(0.5f, 0.5f); Vector2 uv = (p_uv - rotation_offset).rotated(p_angle) + rotation_offset; - return uv.clamp(Vector2(0.f, 0.f), Vector2(1.f, 1.f)); + return uv.clamp(V2_ZERO, Vector2(1.f, 1.f)); } #endif // TERRAIN3D_EDITOR_CLASS_H diff --git a/src/terrain_3d_instancer.cpp b/src/terrain_3d_instancer.cpp index b12d068c..aa835cb0 100644 --- a/src/terrain_3d_instancer.cpp +++ b/src/terrain_3d_instancer.cpp @@ -122,6 +122,21 @@ void Terrain3DInstancer::_destroy_mmi_by_location(const Vector2i &p_region_loc, LOG(DEBUG, "Deleting MMI, success: ", result); } +void Terrain3DInstancer::_backup_regionl(const Vector2i &p_region_loc) { + if (_terrain->get_storage() != nullptr) { + Ref region = _terrain->get_storage()->get_region(p_region_loc); + _backup_region(region); + } +} + +void Terrain3DInstancer::_backup_region(const Ref &p_region) { + if (_terrain->get_editor() != nullptr) { + _terrain->get_editor()->backup_region(p_region); + } else { + p_region->set_modified(true); + } +} + /////////////////////////// // Public Functions /////////////////////////// @@ -167,8 +182,8 @@ void Terrain3DInstancer::clear_by_location(const Vector2i &p_region_loc, const i } Dictionary mesh_dict = region->get_multimeshes(); if (mesh_dict.has(p_mesh_id)) { + _backup_region(region); mesh_dict.erase(p_mesh_id); - region->set_modified(true); } _destroy_mmi_by_location(p_region_loc, p_mesh_id); } @@ -256,9 +271,8 @@ void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const D real_t t_scale = CLAMP(fixed_scale + random_scale * (2.f * UtilityFunctions::randf() - 1.f), 0.01f, 10.f); t = t.scaled(Vector3(t_scale, t_scale, t_scale)); - // Position - real_t offset = height_offset + mesh_asset->get_height_offset() + - random_height * (2.f * UtilityFunctions::randf() - 1.f); + // Position. mesh_asset height offset added in add_transforms + real_t offset = height_offset + random_height * (2.f * UtilityFunctions::randf() - 1.f); position += t.basis.get_column(1) * offset; // Offset along UP axis t = t.translated(position); @@ -272,9 +286,8 @@ void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const D } // Append multimesh - Vector2i region_loc = _terrain->get_storage()->get_region_location(p_global_position); if (xforms.size() > 0) { - append_multimesh(region_loc, mesh_id, xforms, colors); + add_transforms(mesh_id, xforms, colors); } } @@ -385,8 +398,6 @@ void Terrain3DInstancer::add_transforms(const int p_mesh_id, const TypedArray colors = colors_dict[region_loc]; xforms.push_back(trns); colors.push_back(col); - - _terrain->get_storage()->set_region_modified(region_loc, true); } // Merge incoming transforms with existing transforms @@ -451,9 +462,9 @@ void Terrain3DInstancer::append_multimesh(const Vector2i &p_region_loc, const in LOG(WARN, "No region found at: ", p_region_loc); return; } + _backup_region(region); Dictionary mesh_dict = region->get_multimeshes(); mesh_dict[p_mesh_id] = mm; - region->set_modified(true); _update_mmis(p_region_loc, p_mesh_id); } @@ -536,24 +547,24 @@ void Terrain3DInstancer::swap_ids(const int p_src_id, const int p_dst_id) { Ref mm_src; Ref mm_dst; if (mesh_dict.has(p_src_id)) { + _backup_region(region); mm_src = mesh_dict[p_src_id]; mesh_dict.erase(p_src_id); - region->set_modified(true); } if (mesh_dict.has(p_dst_id)) { + _backup_region(region); mm_dst = mesh_dict[p_dst_id]; mesh_dict.erase(p_dst_id); - region->set_modified(true); } // If src is ok, insert into dst slot if (mm_src.is_valid()) { + _backup_region(region); mesh_dict[p_dst_id] = mm_src; - region->set_modified(true); } // If dst is ok, insert into src slot if (mm_dst.is_valid()) { + _backup_region(region); mesh_dict[p_src_id] = mm_dst; - region->set_modified(true); } LOG(DEBUG, "Swapped multimesh ids at: ", region_loc); } diff --git a/src/terrain_3d_instancer.h b/src/terrain_3d_instancer.h index 08bd021f..7db0dc72 100644 --- a/src/terrain_3d_instancer.h +++ b/src/terrain_3d_instancer.h @@ -32,6 +32,8 @@ class Terrain3DInstancer : public Object { void _update_mmis(const Vector2i &p_region_loc = V2I_MAX, const int p_mesh_id = -1); void _destroy_mmi_by_region_id(const int p_region, const int p_mesh_id); void _destroy_mmi_by_location(const Vector2i &p_region_loc, const int p_mesh_id); + void _backup_regionl(const Vector2i &p_region_loc); + void _backup_region(const Ref &p_region); public: Terrain3DInstancer() {} diff --git a/src/terrain_3d_region.cpp b/src/terrain_3d_region.cpp index 0369faad..22af4c84 100644 --- a/src/terrain_3d_region.cpp +++ b/src/terrain_3d_region.cpp @@ -253,6 +253,73 @@ void Terrain3DRegion::set_location(const Vector2i &p_location) { } } +void Terrain3DRegion::set_data(const Dictionary &p_data) { +#define SET_IF_HAS(var, str) \ + if (p_data.has(str)) { \ + var = p_data[str]; \ + } + SET_IF_HAS(_location, "location"); + SET_IF_HAS(_deleted, "deleted"); + SET_IF_HAS(_edited, "edited"); + SET_IF_HAS(_modified, "modified"); + SET_IF_HAS(_version, "version"); + SET_IF_HAS(_region_size, "region_size"); + SET_IF_HAS(_height_range, "height_range"); + SET_IF_HAS(_height_map, "height_map"); + SET_IF_HAS(_control_map, "control_map"); + SET_IF_HAS(_color_map, "color_map"); + SET_IF_HAS(_multimeshes, "multimeshes"); +} + +Dictionary Terrain3DRegion::get_data() const { + Dictionary dict; + dict["location"] = _location; + dict["deleted"] = _deleted; + dict["edited"] = _edited; + dict["modified"] = _modified; + dict["instance_id"] = String::num_uint64(get_instance_id()); // don't commit + dict["version"] = _version; + dict["region_size"] = _region_size; + dict["height_range"] = _height_range; + dict["height_map"] = _height_map; + dict["control_map"] = _control_map; + dict["color_map"] = _color_map; + dict["multimeshes"] = _multimeshes; + return dict; +} + +Ref Terrain3DRegion::duplicate(const bool p_deep) { + Ref region; + region.instantiate(); + if (!p_deep) { + region->set_data(get_data()); + } else { + Dictionary dict; + // Native type copies + dict["version"] = _version; + dict["region_size"] = _region_size; + dict["height_range"] = _height_range; + dict["modified"] = _modified; + dict["deleted"] = _deleted; + dict["location"] = _location; + // Resource duplicates + dict["height_map"] = _height_map->duplicate(); + dict["control_map"] = _control_map->duplicate(); + dict["color_map"] = _color_map->duplicate(); + Dictionary mms; + Array keys = _multimeshes.keys(); + for (int i = 0; i < keys.size(); i++) { + int mesh_id = keys[i]; + Ref mm = _multimeshes[mesh_id]; + mm->duplicate(); + mms[mesh_id] = mm; + } + dict["multimeshes"] = mms; + region->set_data(dict); + } + return region; +} + ///////////////////// // Protected Functions ///////////////////// @@ -284,13 +351,19 @@ void Terrain3DRegion::_bind_methods() { ClassDB::bind_method(D_METHOD("save", "path", "16-bit"), &Terrain3DRegion::save, DEFVAL(""), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("set_modified"), &Terrain3DRegion::set_modified); - ClassDB::bind_method(D_METHOD("is_modified"), &Terrain3DRegion::is_modified); ClassDB::bind_method(D_METHOD("set_deleted"), &Terrain3DRegion::set_deleted); ClassDB::bind_method(D_METHOD("is_deleted"), &Terrain3DRegion::is_deleted); + ClassDB::bind_method(D_METHOD("set_edited"), &Terrain3DRegion::set_edited); + ClassDB::bind_method(D_METHOD("is_edited"), &Terrain3DRegion::is_edited); + ClassDB::bind_method(D_METHOD("set_modified"), &Terrain3DRegion::set_modified); + ClassDB::bind_method(D_METHOD("is_modified"), &Terrain3DRegion::is_modified); ClassDB::bind_method(D_METHOD("set_location"), &Terrain3DRegion::set_location); ClassDB::bind_method(D_METHOD("get_location"), &Terrain3DRegion::get_location); + ClassDB::bind_method(D_METHOD("set_data"), &Terrain3DRegion::set_data); + ClassDB::bind_method(D_METHOD("get_data"), &Terrain3DRegion::get_data); + ClassDB::bind_method(D_METHOD("duplicate", "deep"), &Terrain3DRegion::duplicate, DEFVAL(false)); + int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "version", PROPERTY_HINT_NONE, "", ro_flags), "set_version", "get_version"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "height_range", PROPERTY_HINT_NONE, "", ro_flags), "set_height_range", "get_height_range"); @@ -300,7 +373,8 @@ void Terrain3DRegion::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "multimeshes", PROPERTY_HINT_NONE, "", ro_flags), "set_multimeshes", "get_multimeshes"); // Double-clicking a region .res file shows what's on disk, the defaults, not in memory. So these are hidden - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "modified", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_modified", "is_modified"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "edited", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_edited", "is_edited"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deleted", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_deleted", "is_deleted"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "modified", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_modified", "is_modified"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "location", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_location", "get_location"); } diff --git a/src/terrain_3d_region.h b/src/terrain_3d_region.h index b640c6c9..920566f3 100644 --- a/src/terrain_3d_region.h +++ b/src/terrain_3d_region.h @@ -54,9 +54,10 @@ class Terrain3DRegion : public Resource { Dictionary _multimeshes; // Dictionary[mesh_id:int] -> MultiMesh // Working data not saved to disk + bool _deleted = false; // Marked for deletion on save + bool _edited = false; // Marked for undo/redo storage + bool _modified = false; // Marked for saving Vector2i _location = V2I_MAX; - bool _modified = false; - bool _deleted = false; public: Terrain3DRegion() {} @@ -92,15 +93,22 @@ class Terrain3DRegion : public Resource { Error save(const String &p_path = "", const bool p_16_bit = false); // Working Data - void set_modified(const bool p_modified) { _modified = p_modified; } - bool is_modified() const { return _modified; } void set_deleted(const bool p_deleted) { _deleted = p_deleted; } bool is_deleted() const { return _deleted; } + void set_edited(const bool p_edited) { _edited = p_edited; } + bool is_edited() const { return _edited; } + void set_modified(const bool p_modified) { _modified = p_modified; } + bool is_modified() const { return _modified; } void set_location(const Vector2i &p_location); Vector2i get_location() const { return _location; } void set_region_size(const int p_region_size) { _region_size = CLAMP(p_region_size, 1024, 1024); } int get_region_size() const { return _region_size; } + // Utility + void set_data(const Dictionary &p_data); + Dictionary get_data() const; + Ref duplicate(const bool p_deep = false); + protected: static void _bind_methods(); }; diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index 16c40c97..d9415a41 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "logger.h" #include "terrain_3d_storage.h" @@ -46,6 +45,19 @@ void Terrain3DStorage::initialize(Terrain3D *p_terrain) { } } +// Returns an array of active regions, optionally a shallow or deep copy +TypedArray Terrain3DStorage::get_regions_active(const bool p_copy, const bool p_deep) const { + TypedArray region_arr; + for (int i = 0; i < _region_locations.size(); i++) { + Vector2i region_loc = _region_locations[i]; + Ref region = _regions[region_loc]; + if (region.is_valid()) { + region_arr.push_back((p_copy) ? region->duplicate(p_deep) : region); + } + } + return region_arr; +} + void Terrain3DStorage::update_master_height(const real_t p_height) { if (p_height < _master_height_range.x) { _master_height_range.x = p_height; @@ -162,20 +174,15 @@ Ref Terrain3DStorage::add_region_blank(const Vector2i &p_region Ref region; region.instantiate(); region->set_location(p_region_loc); - if (add_region(region, p_update)) { + if (add_region(region, p_update) == OK) { + region->set_modified(true); return region; } return Ref(); } Ref Terrain3DStorage::add_region_blankp(const Vector3 &p_global_position, const bool p_update) { - Ref region; - region.instantiate(); - region->set_location(get_region_location(p_global_position)); - if (add_region(region, p_update)) { - return region; - } - return Ref(); + return add_region_blank(get_region_location(p_global_position)); } /** Adds a Terrain3DRegion to the terrain @@ -183,7 +190,6 @@ Ref Terrain3DStorage::add_region_blankp(const Vector3 &p_global * p_update - rebuild the maps if true. Set to false if bulk adding many regions. */ Error Terrain3DStorage::add_region(const Ref &p_region, const bool p_update) { - IS_INIT_MESG("Storage not initialized", FAILED); // needed? if (p_region.is_null()) { LOG(ERROR, "Provided region is null. Returning"); return FAILED; @@ -192,36 +198,22 @@ Error Terrain3DStorage::add_region(const Ref &p_region, const b LOG(INFO, "Adding region at location ", region_loc, ", update maps: ", p_update ? "yes" : "no"); // Check bounds and slow report errors - if (_get_region_map_index(region_loc) < 0) { - uint64_t time = Time::get_singleton()->get_ticks_msec(); - if (time - _last_region_bounds_error > 1000) { - _last_region_bounds_error = time; - LOG(ERROR, "Specified position outside of maximum region map size: +/-", - real_t((REGION_MAP_SIZE / 2) * _region_size) * _mesh_vertex_spacing); - } + if (get_region_map_index(region_loc) < 0) { + LOG(ERROR, "Specified position outside of maximum region map size: +/-", + real_t((REGION_MAP_SIZE / 2) * _region_size) * _mesh_vertex_spacing); return FAILED; } - // do something here to overwrite existing regions - // Some regions might be in a deleted state and has_region won't see them - - if (has_region(region_loc)) { - LOG(DEBUG, "Overwriting existing region at ", region_loc); - - /*if (p_images.is_empty()) { - LOG(DEBUG, "Region at ", p_global_position, " already exists and nothing to overwrite. Doing nothing"); - return OK; - } else { - LOG(DEBUG, "Region at ", p_global_position, " already exists, overwriting"); - remove_region(p_global_position, false, p_path); - }*/ - } - p_region->sanitize_map(); - LOG(DEBUG, "Storing region ", region_loc, " version ", vformat("%.3f", p_region->get_version()), " id: ", _region_locations.size()); - _region_locations.push_back(region_loc); + p_region->set_deleted(false); + if (!_region_locations.has(region_loc)) { + _region_locations.push_back(region_loc); + } else { + LOG(INFO, "Overwriting ", (_regions.has(region_loc)) ? "deleted" : "existing", " region at ", region_loc); + } _regions[region_loc] = p_region; _region_map_dirty = true; + LOG(DEBUG, "Storing region ", region_loc, " version ", vformat("%.3f", p_region->get_version()), " id: ", _region_locations.size()); if (p_update) { force_update_maps(); } @@ -245,14 +237,15 @@ void Terrain3DStorage::remove_region(const Ref &p_region, const LOG(ERROR, "Region not found or is null. Returning"); return; } - LOG(INFO, "Removing region at ", p_region->get_location(), " Updating: ", p_update ? "yes" : "no"); - LOG(DEBUG, "Marking region for deletion"); - p_region->set_deleted(true); - int region_id = get_region_id(p_region->get_location()); + + Vector2i region_loc = p_region->get_location(); + int region_id = _region_locations.find(region_loc); + LOG(INFO, "Marking region ", region_loc, " for deletion. update_maps: ", p_update ? "yes" : "no"); if (region_id < 0) { - LOG(ERROR, "Region already removed from region_locations. Returning"); + LOG(ERROR, "Region ", region_loc, " not found in region_locations. Returning"); return; } + p_region->set_deleted(true); _region_locations.remove_at(region_id); _region_map_dirty = true; LOG(DEBUG, "Removing from region_locations, new size: ", _region_locations.size()); @@ -311,7 +304,7 @@ void Terrain3DStorage::update_maps() { _region_map_dirty = false; for (int i = 0; i < _region_locations.size(); i++) { int region_id = i + 1; // Begin at 1 since 0 = no region - int map_index = _get_region_map_index(_region_locations[i]); + int map_index = get_region_map_index(_region_locations[i]); if (map_index >= 0) { _region_map[map_index] = region_id; } @@ -334,9 +327,11 @@ void Terrain3DStorage::save_region(const Vector2i &p_region_loc, const String &p String path = p_dir + String("/") + fname; // If region marked for deletion, remove from disk and from _regions, but don't free in case stored in undo if (region->is_deleted()) { + LOG(DEBUG, "Removing ", p_region_loc, " from _regions"); + _regions.erase(p_region_loc); LOG(DEBUG, "File to be deleted: ", path); if (!FileAccess::file_exists(path)) { - LOG(ERROR, "File ", path, " doesn't exist"); + LOG(INFO, "File to delete ", path, " doesn't exist. (Maybe from add, undo, save)"); return; } Ref da = DirAccess::open(p_dir); @@ -348,8 +343,7 @@ void Terrain3DStorage::save_region(const Vector2i &p_region_loc, const String &p if (Engine::get_singleton()->is_editor_hint()) { EditorInterface::get_singleton()->get_resource_filesystem()->scan(); } - _regions.erase(p_region_loc); - LOG(INFO, "File ", path, " deleted and ", p_region_loc, " removed from _regions"); + LOG(INFO, "File ", path, " deleted"); return; } region->save(path, p_16_bit); @@ -971,24 +965,38 @@ void Terrain3DStorage::_bind_methods() { BIND_CONSTANT(REGION_MAP_SIZE); - ClassDB::bind_method(D_METHOD("get_region", "region_location"), &Terrain3DStorage::get_region); - ClassDB::bind_method(D_METHOD("get_regionp", "global_position"), &Terrain3DStorage::get_regionp); - ClassDB::bind_method(D_METHOD("has_region", "region_location"), &Terrain3DStorage::has_region); - ClassDB::bind_method(D_METHOD("has_regionp", "global_position"), &Terrain3DStorage::has_regionp); - ClassDB::bind_method(D_METHOD("set_region_size", "size"), &Terrain3DStorage::set_region_size); - ClassDB::bind_method(D_METHOD("get_region_size"), &Terrain3DStorage::get_region_size); + ClassDB::bind_method(D_METHOD("get_region_count"), &Terrain3DStorage::get_region_count); ClassDB::bind_method(D_METHOD("set_region_locations", "region_locations"), &Terrain3DStorage::set_region_locations); ClassDB::bind_method(D_METHOD("get_region_locations"), &Terrain3DStorage::get_region_locations); - ClassDB::bind_method(D_METHOD("get_region_count"), &Terrain3DStorage::get_region_count); + ClassDB::bind_method(D_METHOD("get_regions_active", "copy", "deep"), &Terrain3DStorage::get_regions_active, DEFVAL(false), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_regions_all"), &Terrain3DStorage::get_regions_all); + ClassDB::bind_method(D_METHOD("get_region_map"), &Terrain3DStorage::get_region_map); + + ClassDB::bind_method(D_METHOD("has_region", "region_location"), &Terrain3DStorage::has_region); + ClassDB::bind_method(D_METHOD("has_regionp", "global_position"), &Terrain3DStorage::has_regionp); + ClassDB::bind_method(D_METHOD("get_region", "region_location"), &Terrain3DStorage::get_region); + ClassDB::bind_method(D_METHOD("get_regionp", "global_position"), &Terrain3DStorage::get_regionp); + + ClassDB::bind_method(D_METHOD("set_region_modified", "region_location", "modified"), &Terrain3DStorage::set_region_modified); + ClassDB::bind_method(D_METHOD("is_region_modified", "region_location"), &Terrain3DStorage::is_region_modified); + ClassDB::bind_method(D_METHOD("set_region_deleted", "region_location", "deleted"), &Terrain3DStorage::set_region_deleted); + ClassDB::bind_method(D_METHOD("is_region_deleted", "region_location"), &Terrain3DStorage::is_region_deleted); + ClassDB::bind_method(D_METHOD("get_region_location", "global_position"), &Terrain3DStorage::get_region_location); ClassDB::bind_method(D_METHOD("get_region_locationi", "region_id"), &Terrain3DStorage::get_region_locationi); ClassDB::bind_method(D_METHOD("get_region_id", "region_location"), &Terrain3DStorage::get_region_id); ClassDB::bind_method(D_METHOD("get_region_idp", "global_position"), &Terrain3DStorage::get_region_idp); + + ClassDB::bind_method(D_METHOD("set_region_size", "size"), &Terrain3DStorage::set_region_size); + ClassDB::bind_method(D_METHOD("get_region_size"), &Terrain3DStorage::get_region_size); + ClassDB::bind_method(D_METHOD("get_region_sizev"), &Terrain3DStorage::get_region_sizev); + ClassDB::bind_method(D_METHOD("add_region", "region", "update"), &Terrain3DStorage::add_region, DEFVAL(true)); ClassDB::bind_method(D_METHOD("add_regionl", "region_location", "update"), &Terrain3DStorage::add_regionl, DEFVAL(true)); ClassDB::bind_method(D_METHOD("add_regionp", "global_position", "update"), &Terrain3DStorage::add_regionp, DEFVAL(true)); ClassDB::bind_method(D_METHOD("add_region_blank", "region_location", "update"), &Terrain3DStorage::add_region, DEFVAL(true)); ClassDB::bind_method(D_METHOD("add_region_blankp", "global_position", "update"), &Terrain3DStorage::add_region, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("remove_region", "region", "update"), &Terrain3DStorage::remove_region, DEFVAL(true)); ClassDB::bind_method(D_METHOD("remove_regionl", "region_location", "update"), &Terrain3DStorage::remove_regionl, DEFVAL(true)); ClassDB::bind_method(D_METHOD("remove_regionp", "global_position", "update"), &Terrain3DStorage::remove_regionp, DEFVAL(true)); @@ -998,14 +1006,16 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("save_region", "directory", "region_location", "16_bit"), &Terrain3DStorage::save_region, DEFVAL(false)); ClassDB::bind_method(D_METHOD("load_region", "directory", "region_location", "update"), &Terrain3DStorage::load_region, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("get_maps", "map_type"), &Terrain3DStorage::get_maps); - ClassDB::bind_method(D_METHOD("get_maps_copy", "map_type"), &Terrain3DStorage::get_maps_copy); ClassDB::bind_method(D_METHOD("get_height_maps"), &Terrain3DStorage::get_height_maps); ClassDB::bind_method(D_METHOD("get_control_maps"), &Terrain3DStorage::get_control_maps); ClassDB::bind_method(D_METHOD("get_color_maps"), &Terrain3DStorage::get_color_maps); ClassDB::bind_method(D_METHOD("get_height_maps_rid"), &Terrain3DStorage::get_height_maps_rid); ClassDB::bind_method(D_METHOD("get_control_maps_rid"), &Terrain3DStorage::get_control_maps_rid); ClassDB::bind_method(D_METHOD("get_color_maps_rid"), &Terrain3DStorage::get_color_maps_rid); + ClassDB::bind_method(D_METHOD("force_update_maps", "map_type"), &Terrain3DStorage::force_update_maps, DEFVAL(TYPE_MAX)); + + ClassDB::bind_method(D_METHOD("get_maps", "map_type"), &Terrain3DStorage::get_maps); + ClassDB::bind_method(D_METHOD("get_maps_copy", "map_type"), &Terrain3DStorage::get_maps_copy); ClassDB::bind_method(D_METHOD("set_pixel", "map_type", "global_position", "pixel"), &Terrain3DStorage::set_pixel); ClassDB::bind_method(D_METHOD("get_pixel", "map_type", "global_position"), &Terrain3DStorage::get_pixel); @@ -1017,12 +1027,12 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("get_control", "global_position"), &Terrain3DStorage::get_control); ClassDB::bind_method(D_METHOD("set_roughness", "global_position", "roughness"), &Terrain3DStorage::set_roughness); ClassDB::bind_method(D_METHOD("get_roughness", "global_position"), &Terrain3DStorage::get_roughness); - ClassDB::bind_method(D_METHOD("get_texture_id", "global_position"), &Terrain3DStorage::get_texture_id); ClassDB::bind_method(D_METHOD("get_angle", "global_position"), &Terrain3DStorage::get_angle); ClassDB::bind_method(D_METHOD("get_scale", "global_position"), &Terrain3DStorage::get_scale); - ClassDB::bind_method(D_METHOD("get_mesh_vertex", "lod", "filter", "global_position"), &Terrain3DStorage::get_mesh_vertex); + ClassDB::bind_method(D_METHOD("get_normal", "global_position"), &Terrain3DStorage::get_normal); - ClassDB::bind_method(D_METHOD("force_update_maps", "map_type"), &Terrain3DStorage::force_update_maps, DEFVAL(TYPE_MAX)); + ClassDB::bind_method(D_METHOD("get_texture_id", "global_position"), &Terrain3DStorage::get_texture_id); + ClassDB::bind_method(D_METHOD("get_mesh_vertex", "lod", "filter", "global_position"), &Terrain3DStorage::get_mesh_vertex); ClassDB::bind_method(D_METHOD("get_height_range"), &Terrain3DStorage::get_height_range); ClassDB::bind_method(D_METHOD("calc_height_range", "recursive"), &Terrain3DStorage::calc_height_range, DEFVAL(false)); diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index d2b9d39c..4d1553f0 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -43,7 +43,6 @@ class Terrain3DStorage : public Object { AABB _edited_area; Vector2 _master_height_range = V2_ZERO; - uint64_t _last_region_bounds_error = 0; ///////// // Terrain3DRegions house the maps, instances, and other data for each region. @@ -53,13 +52,15 @@ class Terrain3DStorage : public Object { // 2) By `region_id:int`. This index changes on every add/remove, depends on load order, // and is not stable. It should not be relied on by users and is primarily for internal use. - // `_regions` stores all loaded Terrain3DRegions, indexed by region_location. - // Inactive regions are those marked for deletion. + // `_regions` stores all loaded Terrain3DRegions, indexed by region_location. If marked for + // deletion they are removed from here upon saving, however they may stay in memory if tracked + // by the Undo system. Dictionary _regions; // Dict[region_location:Vector2i] -> Terrain3DRegion - // All _active_ region maps are maintained in secondary indices. These arrays provide - // direct access to maps in the regions, indexed by region_id. These arrays are converted - // to TextureArrays for the shader. + // All _active_ region maps are maintained in these secondary indices. + // Regions are considered active if and only if they exist in `_region_locations`. The other + // arrays are built off of this index; its order defines region_id. + // The image arrays are converted to TextureArrays for the shader. TypedArray _region_locations; TypedArray _height_maps; @@ -84,7 +85,6 @@ class Terrain3DStorage : public Object { // Functions void _clear(); - int _get_region_map_index(const Vector2i &p_region_loc) const; public: Terrain3DStorage() {} @@ -98,16 +98,19 @@ class Terrain3DStorage : public Object { /// Regions - Ref get_region(const Vector2i &p_region_loc) const { return _regions[p_region_loc]; } - Ref get_regionp(const Vector3 &p_global_position) const { return _regions[get_region_location(p_global_position)]; } + int get_region_count() const { return _region_locations.size(); } + void set_region_locations(const TypedArray &p_locations); + TypedArray get_region_locations() const { return _region_locations; } + TypedArray get_regions_active(const bool p_copy = false, const bool p_deep = false) const; + Dictionary get_regions_all() const { return _regions; } + PackedInt32Array get_region_map() const { return _region_map; } + int get_region_map_index(const Vector2i &p_region_loc) const; + bool has_region(const Vector2i &p_region_loc) const { return get_region_id(p_region_loc) != -1; } bool has_regionp(const Vector3 &p_global_position) const { return get_region_idp(p_global_position) != -1; } + Ref get_region(const Vector2i &p_region_loc) const { return _regions[p_region_loc]; } + Ref get_regionp(const Vector3 &p_global_position) const { return _regions[get_region_location(p_global_position)]; } - void set_region_size(const RegionSize p_size); - RegionSize get_region_size() const { return _region_size; } - Vector2i get_region_sizev() const { return _region_sizev; } - int get_region_count() const { return _region_locations.size(); } - Dictionary get_regions() const { return _regions; } void set_region_modified(const Vector2i &p_region_loc, const bool p_modified = true); bool is_region_modified(const Vector2i &p_region_loc) const; void set_region_deleted(const Vector2i &p_region_loc, const bool p_deleted = true); @@ -117,15 +120,10 @@ class Terrain3DStorage : public Object { Vector2i get_region_locationi(const int p_region_id) const; int get_region_id(const Vector2i &p_region_loc) const; int get_region_idp(const Vector3 &p_global_position) const; - void set_region_locations(const TypedArray &p_locations); - TypedArray get_region_locations() const { return _region_locations; } - PackedInt32Array get_region_map() const { return _region_map; } - // File I/O - void save_directory(const String &p_dir); - void load_directory(const String &p_dir); - void save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit = false); - void load_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_update = true); + void set_region_size(const RegionSize p_size); + RegionSize get_region_size() const { return _region_size; } + Vector2i get_region_sizev() const { return _region_sizev; } Error add_region(const Ref &p_region, const bool p_update = true); Error add_regionl(const Vector2i &p_region_loc, const Ref &p_region, const bool p_update = true); @@ -136,13 +134,13 @@ class Terrain3DStorage : public Object { void remove_regionl(const Vector2i &p_region_loc, const bool p_update = true); void remove_regionp(const Vector3 &p_global_position, const bool p_update = true); - // Maps - - void update_maps(); - void force_update_maps(const MapType p_map = TYPE_MAX); + // File I/O + void save_directory(const String &p_dir); + void load_directory(const String &p_dir); + void save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit = false); + void load_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_update = true); - TypedArray get_maps(const MapType p_map_type) const; - TypedArray get_maps_copy(const MapType p_map_type, const TypedArray &p_region_ids = TypedArray()) const; + // Maps TypedArray get_height_maps() const { return _height_maps; } TypedArray get_control_maps() const { return _control_maps; } TypedArray get_color_maps() const { return _color_maps; } @@ -150,20 +148,27 @@ class Terrain3DStorage : public Object { RID get_control_maps_rid() const { return _generated_control_maps.get_rid(); } RID get_color_maps_rid() const { return _generated_color_maps.get_rid(); } + void update_maps(); + void force_update_maps(const MapType p_map = TYPE_MAX); + + TypedArray get_maps(const MapType p_map_type) const; + TypedArray get_maps_copy(const MapType p_map_type, const TypedArray &p_region_ids = TypedArray()) const; + void set_pixel(const MapType p_map_type, const Vector3 &p_global_position, const Color &p_pixel); Color get_pixel(const MapType p_map_type, const Vector3 &p_global_position) const; void set_height(const Vector3 &p_global_position, const real_t p_height); real_t get_height(const Vector3 &p_global_position) const; - Vector3 get_normal(const Vector3 &global_position) const; void set_color(const Vector3 &p_global_position, const Color &p_color); Color get_color(const Vector3 &p_global_position) const; void set_control(const Vector3 &p_global_position, const uint32_t p_control); uint32_t get_control(const Vector3 &p_global_position) const; void set_roughness(const Vector3 &p_global_position, const real_t p_roughness); real_t get_roughness(const Vector3 &p_global_position) const; - Vector3 get_texture_id(const Vector3 &p_global_position) const; real_t get_angle(const Vector3 &p_global_position) const; real_t get_scale(const Vector3 &p_global_position) const; + + Vector3 get_normal(const Vector3 &global_position) const; + Vector3 get_texture_id(const Vector3 &p_global_position) const; Vector3 get_mesh_vertex(const int32_t p_lod, const HeightFilter p_filter, const Vector3 &p_global_position) const; void clear_edited_area(); @@ -194,7 +199,7 @@ VARIANT_ENUM_CAST(Terrain3DStorage::HeightFilter); // This function verifies the location is within the bounds of the _region_map array and // thus the world. It returns the _region_map index if valid, -1 if not -inline int Terrain3DStorage::_get_region_map_index(const Vector2i &p_region_loc) const { +inline int Terrain3DStorage::get_region_map_index(const Vector2i &p_region_loc) const { // Offset locations centered on (0,0) to positive only Vector2i loc = Vector2i(p_region_loc + (REGION_MAP_VSIZE / 2)); int map_index = loc.y * REGION_MAP_SIZE + loc.x; @@ -220,7 +225,7 @@ inline Vector2i Terrain3DStorage::get_region_locationi(const int p_region_id) co // Returns id of any active region. -1 if out of bounds, 0 if no region, or region id inline int Terrain3DStorage::get_region_id(const Vector2i &p_region_loc) const { - int map_index = _get_region_map_index(p_region_loc); + int map_index = get_region_map_index(p_region_loc); if (map_index >= 0) { int region_id = _region_map[map_index] - 1; // 0 = no region if (region_id >= 0 && region_id < _region_locations.size()) { diff --git a/src/terrain_3d_util.cpp b/src/terrain_3d_util.cpp index 7f25b8b3..3122c6a4 100644 --- a/src/terrain_3d_util.cpp +++ b/src/terrain_3d_util.cpp @@ -10,11 +10,56 @@ // Public Functions /////////////////////////// +void Terrain3DUtil::print_arr(const String &p_name, const Array &p_arr, const int p_level) { + LOG(p_level, "Array[", p_arr.size(), "]: ", p_name); + for (int i = 0; i < p_arr.size(); i++) { + Variant var = p_arr[i]; + switch (var.get_type()) { + case Variant::ARRAY: { + print_arr(p_name + String::num_int64(i), var, p_level); + break; + } + case Variant::DICTIONARY: { + print_dict(p_name + String::num_int64(i), var, p_level); + break; + } + case Variant::OBJECT: { + String inst = "Object#" + String::num_uint64(cast_to(var)->get_instance_id()); + LOG(p_level, i, ": ", inst); + break; + } + default: { + LOG(p_level, i, ": ", p_arr[i]); + break; + } + } + } +} + void Terrain3DUtil::print_dict(const String &p_name, const Dictionary &p_dict, const int p_level) { - LOG(p_level, "Printing Dictionary: ", p_name); + LOG(p_level, "Dictionary: ", p_name); Array keys = p_dict.keys(); for (int i = 0; i < keys.size(); i++) { - LOG(p_level, "Key: ", keys[i], ", Value: ", p_dict[keys[i]]); + Variant var = p_dict[keys[i]]; + switch (var.get_type()) { + case Variant::ARRAY: { + print_arr(p_name + String::num_int64(i), var, p_level); + break; + } + case Variant::DICTIONARY: { + print_dict(p_name + String::num_int64(i), var, p_level); + break; + } + case Variant::OBJECT: { + String inst = "Object#" + String::num_uint64(cast_to(var)->get_instance_id()); + LOG(p_level, "\"", keys[i], "\": ", inst); + break; + } + default: { + LOG(p_level, "\"", keys[i], "\": Value: ", var); + break; + } + } } } diff --git a/src/terrain_3d_util.h b/src/terrain_3d_util.h index 2638bde2..b6acd351 100644 --- a/src/terrain_3d_util.h +++ b/src/terrain_3d_util.h @@ -22,7 +22,8 @@ class Terrain3DUtil : public Object { public: // Print info to the console - static void print_dict(const String &name, const Dictionary &p_dict, const int p_level = 2); // Level 2: DEBUG + static void print_arr(const String &p_name, const Array &p_arr, const int p_level = 2); // Level 2: DEBUG + static void print_dict(const String &p_name, const Dictionary &p_dict, const int p_level = 2); // Level 2: DEBUG static void dump_gentex(const GeneratedTexture p_gen, const String &name = "", const int p_level = 2); static void dump_maps(const TypedArray &p_maps, const String &p_name = ""); From 820d72b76223d169fcc5f3b7650853f6649a07f5 Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:34:06 +0700 Subject: [PATCH 16/19] Fix get_region_map_index() checking bounds Rebuild _region_locations when region_map dirty Generate colormap mipmaps for edited regions only on edit (regression Drop storage map api) --- src/terrain_3d_editor.cpp | 10 +++++- src/terrain_3d_region.cpp | 19 +++++++---- src/terrain_3d_storage.cpp | 64 ++++++++++++++++++++++++++------------ src/terrain_3d_storage.h | 22 +++++++------ 4 files changed, 78 insertions(+), 37 deletions(-) diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp index 53347aae..93d054e8 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -38,7 +38,8 @@ Ref Terrain3DEditor::_operate_region(const Vector2i &p_region_l } if (storage->get_region_map_index(p_region_loc) < 0) { if (can_print) { - LOG(ERROR, "Specified position outside of maximum bounds"); + LOG(ERROR, "Location ", p_region_loc, " out of bounds. Max: ", + -Terrain3DStorage::REGION_MAP_SIZE / 2, " to ", Terrain3DStorage::REGION_MAP_SIZE / 2 - 1); } return Ref(); } @@ -449,6 +450,13 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ } } } + // Regenerate color mipmaps for edited regions + if (map_type == TYPE_COLOR) { + for (int i = 0; i < _edited_regions.size(); i++) { + Ref region = _edited_regions[i]; + region->get_map(map_type)->generate_mipmaps(); + } + } storage->force_update_maps(map_type); storage->add_edited_area(edited_area); } diff --git a/src/terrain_3d_region.cpp b/src/terrain_3d_region.cpp index 22af4c84..b83a57f1 100644 --- a/src/terrain_3d_region.cpp +++ b/src/terrain_3d_region.cpp @@ -122,11 +122,12 @@ void Terrain3DRegion::sanitize_map(const MapType p_map_type) { if (map.is_valid()) { if (map->get_size() == Vector2i(_region_size, _region_size)) { if (map->get_format() == format) { - LOG(DEBUG, "Map type ", type_str, " correct format, size. Using image"); + LOG(DEBUG, "Map type ", type_str, " correct format, size. Mipmaps: ", map->has_mipmaps()); if (type == TYPE_HEIGHT) { calc_height_range(); } if (type == TYPE_COLOR && !map->has_mipmaps()) { + LOG(DEBUG, "Color map does not have mipmaps. Generating"); map->generate_mipmaps(); } continue; @@ -136,6 +137,10 @@ void Terrain3DRegion::sanitize_map(const MapType p_map_type) { newimg.instantiate(); newimg->copy_from(map); newimg->convert(format); + if (type == TYPE_COLOR && !map->has_mipmaps()) { + LOG(DEBUG, "Color map does not have mipmaps. Generating"); + newimg->generate_mipmaps(); + } if (newimg->get_format() == format) { set_map(type, newimg); continue; @@ -149,6 +154,7 @@ void Terrain3DRegion::sanitize_map(const MapType p_map_type) { } else { LOG(DEBUG, "No provided ", type_str, " map. Creating blank"); } + LOG(DEBUG, "Making new image of type: ", type_str, " and generating mipmaps: ", type == TYPE_COLOR); set_map(type, Util::get_filled_image(Vector2i(_region_size, _region_size), color, type == TYPE_COLOR, format)); } } @@ -245,12 +251,13 @@ Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) { void Terrain3DRegion::set_location(const Vector2i &p_location) { // In the future anywhere they want to put the location might be fine, but because of region_map // We have a limitation of 16x16 and eventually 45x45. - if (abs(p_location.x) < Terrain3DStorage::REGION_MAP_SIZE / 2 && abs(p_location.y) < Terrain3DStorage::REGION_MAP_SIZE / 2) { - LOG(INFO, "Set location: ", p_location); - _location = p_location; - } else { - LOG(ERROR, "Location out of bounds: ", p_location); + if (Terrain3DStorage::get_region_map_index(p_location) < 0) { + LOG(ERROR, "Location ", p_location, " out of bounds. Max: ", + -Terrain3DStorage::REGION_MAP_SIZE / 2, " to ", Terrain3DStorage::REGION_MAP_SIZE / 2 - 1); + return; } + LOG(INFO, "Set location: ", p_location); + _location = p_location; } void Terrain3DRegion::set_data(const Dictionary &p_data) { diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index d9415a41..c7425c47 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -199,8 +199,8 @@ Error Terrain3DStorage::add_region(const Ref &p_region, const b // Check bounds and slow report errors if (get_region_map_index(region_loc) < 0) { - LOG(ERROR, "Specified position outside of maximum region map size: +/-", - real_t((REGION_MAP_SIZE / 2) * _region_size) * _mesh_vertex_spacing); + LOG(ERROR, "Location ", region_loc, " out of bounds. Max: ", + -REGION_MAP_SIZE / 2, " to ", REGION_MAP_SIZE / 2 - 1); return FAILED; } @@ -257,13 +257,43 @@ void Terrain3DStorage::remove_region(const Ref &p_region, const void Terrain3DStorage::update_maps() { bool any_changed = false; + + if (_region_map_dirty) { + LOG(DEBUG_CONT, "Regenerating ", REGION_MAP_VSIZE, " region map array from active regions"); + _region_map.clear(); + _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); + _region_map_dirty = false; + _region_locations = TypedArray(); // enforce new pointer + Array locs = _regions.keys(); + int region_id = 0; + for (int i = 0; i < locs.size(); i++) { + Ref region = _regions[locs[i]]; + if (region.is_valid() && !region->is_deleted()) { + region_id += 1; // Begin at 1 since 0 = no region + int map_index = get_region_map_index(region->get_location()); + if (map_index >= 0) { + _region_map[map_index] = region_id; + _region_locations.push_back(region->get_location()); + } + } + } + any_changed = true; + emit_signal("region_map_changed"); + } + if (_generated_height_maps.is_dirty()) { LOG(DEBUG_CONT, "Regenerating height texture array from regions"); _height_maps.clear(); for (int i = 0; i < _region_locations.size(); i++) { Vector2i region_loc = _region_locations[i]; Ref region = _regions[region_loc]; - _height_maps.push_back(region->get_height_map()); + if (region.is_valid()) { + _height_maps.push_back(region->get_height_map()); + } else { + LOG(ERROR, "Can't find region ", region_loc, ", _regions: ", _regions.size(), + ", locations: ", _region_locations.size(), ". Please report this error."); + _region_map_dirty = true; + } } _generated_height_maps.create(_height_maps); calc_height_range(); @@ -297,21 +327,6 @@ void Terrain3DStorage::update_maps() { emit_signal("color_maps_changed"); } - if (_region_map_dirty) { - LOG(DEBUG_CONT, "Regenerating ", REGION_MAP_VSIZE, " region map array from active regions"); - _region_map.clear(); - _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); - _region_map_dirty = false; - for (int i = 0; i < _region_locations.size(); i++) { - int region_id = i + 1; // Begin at 1 since 0 = no region - int map_index = get_region_map_index(_region_locations[i]); - if (map_index >= 0) { - _region_map[map_index] = region_id; - } - } - any_changed = true; - emit_signal("region_map_changed"); - } if (any_changed) { emit_signal("maps_changed"); } @@ -550,7 +565,7 @@ real_t Terrain3DStorage::get_scale(const Vector3 &p_global_position) const { return scale; } -void Terrain3DStorage::force_update_maps(const MapType p_map_type) { +void Terrain3DStorage::force_update_maps(const MapType p_map_type, const bool p_generate_mipmaps) { LOG(DEBUG_CONT, "Regenerating maps of type: ", p_map_type); switch (p_map_type) { case TYPE_HEIGHT: @@ -569,6 +584,14 @@ void Terrain3DStorage::force_update_maps(const MapType p_map_type) { _region_map_dirty = true; break; } + if (p_generate_mipmaps && (p_map_type == TYPE_COLOR || p_map_type == TYPE_MAX)) { + LOG(DEBUG_CONT, "Regenerating color mipmaps"); + for (int i = 0; i < _region_locations.size(); i++) { + Vector2i region_loc = _region_locations[i]; + Ref region = _regions[region_loc]; + region->get_color_map()->generate_mipmaps(); + } + } update_maps(); } @@ -971,6 +994,7 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("get_regions_active", "copy", "deep"), &Terrain3DStorage::get_regions_active, DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_regions_all"), &Terrain3DStorage::get_regions_all); ClassDB::bind_method(D_METHOD("get_region_map"), &Terrain3DStorage::get_region_map); + ClassDB::bind_static_method("Terrain3DStorage", D_METHOD("get_region_map_index"), &Terrain3DStorage::get_region_map_index); ClassDB::bind_method(D_METHOD("has_region", "region_location"), &Terrain3DStorage::has_region); ClassDB::bind_method(D_METHOD("has_regionp", "global_position"), &Terrain3DStorage::has_regionp); @@ -1012,7 +1036,7 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("get_height_maps_rid"), &Terrain3DStorage::get_height_maps_rid); ClassDB::bind_method(D_METHOD("get_control_maps_rid"), &Terrain3DStorage::get_control_maps_rid); ClassDB::bind_method(D_METHOD("get_color_maps_rid"), &Terrain3DStorage::get_color_maps_rid); - ClassDB::bind_method(D_METHOD("force_update_maps", "map_type"), &Terrain3DStorage::force_update_maps, DEFVAL(TYPE_MAX)); + ClassDB::bind_method(D_METHOD("force_update_maps", "map_type", "generate_mipmaps"), &Terrain3DStorage::force_update_maps, DEFVAL(TYPE_MAX), DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_maps", "map_type"), &Terrain3DStorage::get_maps); ClassDB::bind_method(D_METHOD("get_maps_copy", "map_type"), &Terrain3DStorage::get_maps_copy); diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index 4d1553f0..cb97b59a 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -104,7 +104,7 @@ class Terrain3DStorage : public Object { TypedArray get_regions_active(const bool p_copy = false, const bool p_deep = false) const; Dictionary get_regions_all() const { return _regions; } PackedInt32Array get_region_map() const { return _region_map; } - int get_region_map_index(const Vector2i &p_region_loc) const; + static int get_region_map_index(const Vector2i &p_region_loc); bool has_region(const Vector2i &p_region_loc) const { return get_region_id(p_region_loc) != -1; } bool has_regionp(const Vector3 &p_global_position) const { return get_region_idp(p_global_position) != -1; } @@ -149,7 +149,7 @@ class Terrain3DStorage : public Object { RID get_color_maps_rid() const { return _generated_color_maps.get_rid(); } void update_maps(); - void force_update_maps(const MapType p_map = TYPE_MAX); + void force_update_maps(const MapType p_map = TYPE_MAX, const bool p_generate_mipmaps = false); TypedArray get_maps(const MapType p_map_type) const; TypedArray get_maps_copy(const MapType p_map_type, const TypedArray &p_region_ids = TypedArray()) const; @@ -197,16 +197,18 @@ VARIANT_ENUM_CAST(Terrain3DStorage::HeightFilter); /// Inline Region Functions -// This function verifies the location is within the bounds of the _region_map array and -// thus the world. It returns the _region_map index if valid, -1 if not -inline int Terrain3DStorage::get_region_map_index(const Vector2i &p_region_loc) const { - // Offset locations centered on (0,0) to positive only - Vector2i loc = Vector2i(p_region_loc + (REGION_MAP_VSIZE / 2)); - int map_index = loc.y * REGION_MAP_SIZE + loc.x; - if (map_index < 0 || map_index >= REGION_MAP_SIZE * REGION_MAP_SIZE) { +// Verifies the location is within the bounds of the _region_map array and +// the world, returning the _region_map index, which contains the region_id. +// Valid region locations are -8, -8 to 7, 7, or when offset: 0, 0 to 15, 15 +// If any bits other than 0xF are set, it's out of bounds and returns -1 +inline int Terrain3DStorage::get_region_map_index(const Vector2i &p_region_loc) { + // Offset world to positive values only + Vector2i loc = p_region_loc + (REGION_MAP_VSIZE / 2); + // Catch values > 15 + if ((uint32_t(loc.x | loc.y) & uint32_t(~0xF)) > 0) { return -1; } - return map_index; + return loc.y * REGION_MAP_SIZE + loc.x; } // Returns a region location given a global position. No bounds checking nor data access. From 07e4c098dabe526cd09c8b9dd966b4cf456263dd Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Wed, 4 Sep 2024 21:00:28 +0700 Subject: [PATCH 17/19] Inline update_height functions --- src/terrain_3d.cpp | 1 + src/terrain_3d_region.cpp | 27 --------------------------- src/terrain_3d_region.h | 23 +++++++++++++++++++++++ src/terrain_3d_storage.cpp | 17 ----------------- src/terrain_3d_storage.h | 17 +++++++++++++++++ 5 files changed, 41 insertions(+), 44 deletions(-) diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index 1dacbb10..35091416 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -1407,6 +1407,7 @@ void Terrain3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "save_16_bit", PROPERTY_HINT_NONE), "set_save_16_bit", "get_save_16_bit"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "Terrain3DMaterial"), "set_material", "get_material"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "assets", PROPERTY_HINT_RESOURCE_TYPE, "Terrain3DAssets"), "set_assets", "get_assets"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "storage", PROPERTY_HINT_NONE, "Terrain3DStorage", PROPERTY_USAGE_NONE), "", "get_storage"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "instancer", PROPERTY_HINT_NONE, "Terrain3DInstancer", PROPERTY_USAGE_NONE), "", "get_instancer"); ADD_GROUP("Renderer", "render_"); diff --git a/src/terrain_3d_region.cpp b/src/terrain_3d_region.cpp index b83a57f1..9463a53e 100644 --- a/src/terrain_3d_region.cpp +++ b/src/terrain_3d_region.cpp @@ -170,33 +170,6 @@ void Terrain3DRegion::set_height_range(const Vector2 &p_range) { } } -void Terrain3DRegion::update_height(const real_t p_height) { - if (p_height < _height_range.x) { - _height_range.x = p_height; - _modified = true; - } else if (p_height > _height_range.y) { - _height_range.y = p_height; - _modified = true; - } - if (_modified) { - LOG(DEBUG_CONT, "Expanded range: ", _height_range); - } -} - -void Terrain3DRegion::update_heights(const Vector2 &p_low_high) { - if (p_low_high.x < _height_range.x) { - _height_range.x = p_low_high.x; - _modified = true; - } - if (p_low_high.y > _height_range.y) { - _height_range.y = p_low_high.y; - _modified = true; - } - if (_modified) { - LOG(DEBUG_CONT, "Expanded range: ", _height_range); - } -} - void Terrain3DRegion::calc_height_range() { Vector2 range = Util::get_min_max(_height_map); if (_height_range != range) { diff --git a/src/terrain_3d_region.h b/src/terrain_3d_region.h index 920566f3..745be947 100644 --- a/src/terrain_3d_region.h +++ b/src/terrain_3d_region.h @@ -123,4 +123,27 @@ constexpr inline const Image::Format *FORMAT = Terrain3DRegion::FORMAT; constexpr inline const char **TYPESTR = Terrain3DRegion::TYPESTR; constexpr inline const Color *COLOR = Terrain3DRegion::COLOR; +/// Inline functions + +inline void Terrain3DRegion::update_height(const real_t p_height) { + if (p_height < _height_range.x) { + _height_range.x = p_height; + _modified = true; + } else if (p_height > _height_range.y) { + _height_range.y = p_height; + _modified = true; + } +} + +inline void Terrain3DRegion::update_heights(const Vector2 &p_low_high) { + if (p_low_high.x < _height_range.x) { + _height_range.x = p_low_high.x; + _modified = true; + } + if (p_low_high.y > _height_range.y) { + _height_range.y = p_low_high.y; + _modified = true; + } +} + #endif // TERRAIN3D_REGION_CLASS_H diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index c7425c47..8ef52116 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -58,23 +58,6 @@ TypedArray Terrain3DStorage::get_regions_active(const bool p_co return region_arr; } -void Terrain3DStorage::update_master_height(const real_t p_height) { - if (p_height < _master_height_range.x) { - _master_height_range.x = p_height; - } else if (p_height > _master_height_range.y) { - _master_height_range.y = p_height; - } -} - -void Terrain3DStorage::update_master_heights(const Vector2 &p_low_high) { - if (p_low_high.x < _master_height_range.x) { - _master_height_range.x = p_low_high.x; - } - if (p_low_high.y > _master_height_range.y) { - _master_height_range.y = p_low_high.y; - } -} - // Recalculates master height range from all active regions current height ranges // Recursive mode has all regions to recalculate from each heightmap pixel void Terrain3DStorage::calc_height_range(const bool p_recursive) { diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index cb97b59a..75a7982b 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -278,4 +278,21 @@ inline real_t Terrain3DStorage::get_roughness(const Vector3 &p_global_position) return get_pixel(TYPE_COLOR, p_global_position).a; } +inline void Terrain3DStorage::update_master_height(const real_t p_height) { + if (p_height < _master_height_range.x) { + _master_height_range.x = p_height; + } else if (p_height > _master_height_range.y) { + _master_height_range.y = p_height; + } +} + +inline void Terrain3DStorage::update_master_heights(const Vector2 &p_low_high) { + if (p_low_high.x < _master_height_range.x) { + _master_height_range.x = p_low_high.x; + } + if (p_low_high.y > _master_height_range.y) { + _master_height_range.y = p_low_high.y; + } +} + #endif // TERRAIN3D_STORAGE_CLASS_H From 234639fc0041556e2f9ece2cd307fcb2da96d4cd Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Sat, 7 Sep 2024 14:34:08 +0700 Subject: [PATCH 18/19] Regions validate region size, expose it --- src/terrain_3d_editor.cpp | 2 +- src/terrain_3d_region.cpp | 150 ++++++++++++++++++++----------------- src/terrain_3d_region.h | 6 +- src/terrain_3d_storage.cpp | 4 +- src/terrain_3d_util.h | 4 + 5 files changed, 94 insertions(+), 72 deletions(-) diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp index 93d054e8..e9d2f8fe 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -531,7 +531,7 @@ void Terrain3DEditor::_apply_undo(const Dictionary &p_data) { LOG(ERROR, "Null region saved in undo data. Please report this error."); continue; } - region->sanitize_map(); // Live data may not have some maps so must be sanitized + region->sanitize_maps(); // Live data may not have some maps so must be sanitized Dictionary regions = storage->get_regions_all(); regions[region->get_location()] = region; region->set_modified(true); diff --git a/src/terrain_3d_region.cpp b/src/terrain_3d_region.cpp index 9463a53e..341520f8 100644 --- a/src/terrain_3d_region.cpp +++ b/src/terrain_3d_region.cpp @@ -59,11 +59,10 @@ void Terrain3DRegion::set_maps(const TypedArray &p_maps) { LOG(ERROR, "Expected ", TYPE_MAX - 1, " maps. Received ", p_maps.size()); return; } - LOG(INFO, "Setting maps for region: ", _location); - _height_map = p_maps[TYPE_HEIGHT]; - _control_map = p_maps[TYPE_CONTROL]; - _color_map = p_maps[TYPE_COLOR]; - sanitize_map(TYPE_MAX); + _region_size = 0; + set_height_map(p_maps[TYPE_HEIGHT]); + set_control_map(p_maps[TYPE_CONTROL]); + set_color_map(p_maps[TYPE_COLOR]); } TypedArray Terrain3DRegion::get_maps() const { @@ -77,85 +76,100 @@ TypedArray Terrain3DRegion::get_maps() const { void Terrain3DRegion::set_height_map(const Ref &p_map) { LOG(INFO, "Setting height map for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)"); - _height_map = p_map; - sanitize_map(TYPE_HEIGHT); + if (_region_size == 0) { + set_region_size((p_map.is_valid()) ? p_map->get_width() : 0); + } + _height_map = sanitize_map(TYPE_HEIGHT, p_map); + calc_height_range(); } void Terrain3DRegion::set_control_map(const Ref &p_map) { LOG(INFO, "Setting control map for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)"); - _control_map = p_map; - sanitize_map(TYPE_CONTROL); + if (_region_size == 0) { + set_region_size((p_map.is_valid()) ? p_map->get_width() : 0); + } + _control_map = sanitize_map(TYPE_CONTROL, p_map); } void Terrain3DRegion::set_color_map(const Ref &p_map) { LOG(INFO, "Setting color map for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)"); - _color_map = p_map; - sanitize_map(TYPE_COLOR); + if (_region_size == 0) { + set_region_size((p_map.is_valid()) ? p_map->get_width() : 0); + } + _color_map = sanitize_map(TYPE_COLOR, p_map); + if (!_color_map->has_mipmaps()) { + LOG(DEBUG, "Color map does not have mipmaps. Generating"); + _color_map->generate_mipmaps(); + } } -// Verifies region map is a valid size and format -// Creates filled blanks if lacking -void Terrain3DRegion::sanitize_map(const MapType p_map_type) { - if (p_map_type < 0 || p_map_type > TYPE_MAX) { - LOG(ERROR, "Invalid map type: ", p_map_type); - return; +bool Terrain3DRegion::validate_map_size(const Ref &p_map) const { + Vector2i region_sizev = p_map->get_size(); + if (region_sizev.x != region_sizev.y) { + LOG(ERROR, "Image width doesn't match height: ", region_sizev); + return false; + } + if (!is_power_of_2(region_sizev.x) || !is_power_of_2(region_sizev.y)) { + LOG(ERROR, "Image dimensions are not a power of 2: ", region_sizev); + return false; + } + if (region_sizev.x < 64 || region_sizev.y > 4096) { + LOG(ERROR, "Image size out of bounds (64-4096): ", region_sizev); + return false; + } + if (_region_size == 0) { + LOG(ERROR, "Region size is 0, set it or set a map first"); + return false; + } + if (_region_size != region_sizev.x || _region_size != region_sizev.y) { + LOG(ERROR, "Image size doesn't match existing images in this region", region_sizev); + return false; } - LOG(INFO, "Verifying image maps type: ", TYPESTR[p_map_type], " are valid for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)"); + return true; +} - TypedArray queued_map_types; - if (p_map_type == TYPE_MAX) { - queued_map_types.push_back(TYPE_HEIGHT); - queued_map_types.push_back(TYPE_CONTROL); - queued_map_types.push_back(TYPE_COLOR); - } else { - queued_map_types.push_back(p_map_type); +void Terrain3DRegion::sanitize_maps() { + if (_region_size == 0) { // blank region, no set_*_map has been called + LOG(ERROR, "Set region_size first"); + return; } + _height_map = sanitize_map(TYPE_HEIGHT, _height_map); + _control_map = sanitize_map(TYPE_CONTROL, _control_map); + _color_map = sanitize_map(TYPE_COLOR, _color_map); +} - for (int i = 0; i < queued_map_types.size(); i++) { - MapType type = (MapType)(int)queued_map_types[i]; - const char *type_str = TYPESTR[type]; - Image::Format format = FORMAT[type]; - Color color = COLOR[type]; - - Ref map = get_map(type); - - if (map.is_valid()) { - if (map->get_size() == Vector2i(_region_size, _region_size)) { - if (map->get_format() == format) { - LOG(DEBUG, "Map type ", type_str, " correct format, size. Mipmaps: ", map->has_mipmaps()); - if (type == TYPE_HEIGHT) { - calc_height_range(); - } - if (type == TYPE_COLOR && !map->has_mipmaps()) { - LOG(DEBUG, "Color map does not have mipmaps. Generating"); - map->generate_mipmaps(); - } - continue; - } else { - LOG(DEBUG, "Provided ", type_str, " map wrong format: ", map->get_format(), ". Converting copy to: ", format); - Ref newimg; - newimg.instantiate(); - newimg->copy_from(map); - newimg->convert(format); - if (type == TYPE_COLOR && !map->has_mipmaps()) { - LOG(DEBUG, "Color map does not have mipmaps. Generating"); - newimg->generate_mipmaps(); - } - if (newimg->get_format() == format) { - set_map(type, newimg); - continue; - } else { - LOG(DEBUG, "Cannot convert image to format: ", format, ". Creating blank "); - } - } +Ref Terrain3DRegion::sanitize_map(const MapType p_map_type, const Ref &p_map) const { + const char *type_str = TYPESTR[p_map_type]; + Image::Format format = FORMAT[p_map_type]; + Color color = COLOR[p_map_type]; + Ref map; + + if (p_map.is_valid()) { + if (validate_map_size(p_map)) { + if (p_map->get_format() == format) { + LOG(DEBUG, "Map type ", type_str, " correct format, size. Mipmaps: ", p_map->has_mipmaps()); + map = p_map; } else { - LOG(DEBUG, "Provided ", type_str, " map wrong size: ", map->get_size(), ". Creating blank"); + LOG(DEBUG, "Provided ", type_str, " map wrong format: ", p_map->get_format(), ". Converting copy to: ", format); + map.instantiate(); + map->copy_from(p_map); + map->convert(format); + if (map->get_format() != format) { + LOG(DEBUG, "Cannot convert image to format: ", format, ". Creating blank "); + map.unref(); + } } } else { - LOG(DEBUG, "No provided ", type_str, " map. Creating blank"); + LOG(DEBUG, "Provided ", type_str, " map wrong size: ", p_map->get_size(), ". Creating blank"); } - LOG(DEBUG, "Making new image of type: ", type_str, " and generating mipmaps: ", type == TYPE_COLOR); - set_map(type, Util::get_filled_image(Vector2i(_region_size, _region_size), color, type == TYPE_COLOR, format)); + } else { + LOG(DEBUG, "No provided ", type_str, " map. Creating blank"); + } + if (map.is_null()) { + LOG(DEBUG, "Making new image of type: ", type_str, " and generating mipmaps: ", p_map_type == TYPE_COLOR); + return Util::get_filled_image(Vector2i(_region_size, _region_size), color, p_map_type == TYPE_COLOR, format); + } else { + return map; } } @@ -181,7 +195,6 @@ void Terrain3DRegion::calc_height_range() { Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) { // Initiate save to external file. The scene will save itself. - //LOG(WARN, "Saving region: ", _location, " modified: ", _modified, " path: ", get_path(), ", to : ", p_path); if (_location.x == INT32_MAX) { LOG(ERROR, "Region has not been setup. Location is INT32_MAX. Skipping ", p_path); } @@ -312,6 +325,8 @@ void Terrain3DRegion::_bind_methods() { ClassDB::bind_method(D_METHOD("set_version"), &Terrain3DRegion::set_version); ClassDB::bind_method(D_METHOD("get_version"), &Terrain3DRegion::get_version); + ClassDB::bind_method(D_METHOD("set_region_size", "region_size"), &Terrain3DRegion::set_region_size); + ClassDB::bind_method(D_METHOD("get_region_size"), &Terrain3DRegion::get_region_size); ClassDB::bind_method(D_METHOD("set_height_map", "map"), &Terrain3DRegion::set_height_map); ClassDB::bind_method(D_METHOD("get_height_map"), &Terrain3DRegion::get_height_map); @@ -346,6 +361,7 @@ void Terrain3DRegion::_bind_methods() { int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "version", PROPERTY_HINT_NONE, "", ro_flags), "set_version", "get_version"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_NONE, "", ro_flags), "set_region_size", "get_region_size"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "height_range", PROPERTY_HINT_NONE, "", ro_flags), "set_height_range", "get_height_range"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "heightmap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_height_map", "get_height_map"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "controlmap", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_control_map", "get_control_map"); diff --git a/src/terrain_3d_region.h b/src/terrain_3d_region.h index 745be947..bd92476f 100644 --- a/src/terrain_3d_region.h +++ b/src/terrain_3d_region.h @@ -44,7 +44,7 @@ class Terrain3DRegion : public Resource { private: /// Saved data real_t _version = 0.8f; // Set to first version to ensure Godot always upgrades this - int _region_size = 1024; + int _region_size = 0; Vector2 _height_range = V2_ZERO; // Maps Ref _height_map; @@ -77,7 +77,9 @@ class Terrain3DRegion : public Resource { Ref get_control_map() const { return _control_map; } void set_color_map(const Ref &p_map); Ref get_color_map() const { return _color_map; } - void sanitize_map(const MapType p_map_type = TYPE_MAX); + bool validate_map_size(const Ref &p_map) const; + void sanitize_maps(); + Ref sanitize_map(const MapType p_map_type, const Ref &p_map) const; void set_height_range(const Vector2 &p_range); Vector2 get_height_range() const { return _height_range; } diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index 8ef52116..670aa46a 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -157,6 +157,7 @@ Ref Terrain3DStorage::add_region_blank(const Vector2i &p_region Ref region; region.instantiate(); region->set_location(p_region_loc); + region->set_region_size(_region_size); if (add_region(region, p_update) == OK) { region->set_modified(true); return region; @@ -186,8 +187,7 @@ Error Terrain3DStorage::add_region(const Ref &p_region, const b -REGION_MAP_SIZE / 2, " to ", REGION_MAP_SIZE / 2 - 1); return FAILED; } - - p_region->sanitize_map(); + p_region->sanitize_maps(); p_region->set_deleted(false); if (!_region_locations.has(region_loc)) { _region_locations.push_back(region_loc); diff --git a/src/terrain_3d_util.h b/src/terrain_3d_util.h index b6acd351..e7a43b5c 100644 --- a/src/terrain_3d_util.h +++ b/src/terrain_3d_util.h @@ -64,6 +64,10 @@ T round_multiple(const T p_value, const T p_multiple) { return static_cast(std::round(static_cast(p_value) / static_cast(p_multiple)) * static_cast(p_multiple)); } +inline bool is_power_of_2(const int p_n) { + return p_n && !(p_n & (p_n - 1)); +} + // Returns the bilinearly interpolated value derived from parameters: // * 4 values to be interpolated // * Positioned at the 4 corners of the p_pos00 - p_pos11 rectangle From 82f313373bd8324dac2127b76275dd8cc58c929a Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Sat, 7 Sep 2024 14:37:38 +0700 Subject: [PATCH 19/19] Move region_size to Terrain3D --- project/addons/terrain_3d/editor.gd | 5 +-- src/terrain_3d.cpp | 61 ++++++++++++++++++++--------- src/terrain_3d.h | 16 ++++++++ src/terrain_3d_editor.cpp | 6 +-- src/terrain_3d_instancer.cpp | 2 +- src/terrain_3d_material.cpp | 2 +- src/terrain_3d_storage.cpp | 29 ++------------ src/terrain_3d_storage.h | 20 +--------- 8 files changed, 70 insertions(+), 71 deletions(-) diff --git a/project/addons/terrain_3d/editor.gd b/project/addons/terrain_3d/editor.gd index ec32da2e..f34c8f04 100644 --- a/project/addons/terrain_3d/editor.gd +++ b/project/addons/terrain_3d/editor.gd @@ -180,9 +180,8 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) -> ui.update_decal() ## Update region highlight - var region_size = terrain.get_storage().get_region_size() var region_position: Vector2 = ( Vector2(mouse_global_position.x, mouse_global_position.z) \ - / (region_size * terrain.get_mesh_vertex_spacing()) ).floor() + / (terrain.get_region_size() * terrain.get_mesh_vertex_spacing()) ).floor() if current_region_position != region_position: current_region_position = region_position update_region_grid() @@ -246,7 +245,7 @@ func update_region_grid() -> void: region_gizmo.show_rect = editor.get_tool() == Terrain3DEditor.REGION region_gizmo.use_secondary_color = editor.get_operation() == Terrain3DEditor.SUBTRACT region_gizmo.region_position = current_region_position - region_gizmo.region_size = terrain.get_storage().get_region_size() * terrain.get_mesh_vertex_spacing() + region_gizmo.region_size = terrain.get_region_size() * terrain.get_mesh_vertex_spacing() region_gizmo.grid = terrain.get_storage().get_region_locations() terrain.update_gizmos() diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index 35091416..90e74b37 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -51,11 +51,6 @@ void Terrain3D::_initialize() { } // Connect signals - // Region size changed, update material - if (!_storage->is_connected("region_size_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::_update_maps))) { - LOG(DEBUG, "Connecting region_size_changed signal to _material->_update_regions()"); - _storage->connect("region_size_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::_update_maps)); - } // Any region was changed, update region labels if (!_storage->is_connected("region_map_changed", callable_mp(this, &Terrain3D::update_region_labels))) { LOG(DEBUG, "Connecting _storage::region_map_changed signal to set_show_region_locations()"); @@ -420,8 +415,7 @@ void Terrain3D::_update_collision() { } int time = Time::get_singleton()->get_ticks_msec(); - int region_size = _storage->get_region_size(); - int shape_size = region_size + 1; + int shape_size = _region_size + 1; float hole_const = NAN; // DEPRECATED - Jolt v0.12 supports NAN. Remove check when it's old. if (ProjectSettings::get_singleton()->get_setting("physics/3d/physics_engine") == "JoltPhysics3D") { @@ -433,7 +427,7 @@ void Terrain3D::_update_collision() { map_data.resize(shape_size * shape_size); Vector2i region_loc = _storage->get_region_locations()[i]; - Vector2i global_loc = region_loc * region_size; + Vector2i global_loc = region_loc * _region_size; Vector3 global_pos = Vector3(global_loc.x, 0.f, global_loc.y); Ref map, map_x, map_z, map_xz; @@ -446,17 +440,17 @@ void Terrain3D::_update_collision() { map = region->get_map(TYPE_HEIGHT); cmap = region->get_map(TYPE_CONTROL); - region = _storage->get_regionp(Vector3(global_pos.x + region_size, 0.f, global_pos.z) * _mesh_vertex_spacing); + region = _storage->get_regionp(Vector3(global_pos.x + _region_size, 0.f, global_pos.z) * _mesh_vertex_spacing); if (region.is_valid()) { map_x = region->get_map(TYPE_HEIGHT); cmap_x = region->get_map(TYPE_CONTROL); } - region = _storage->get_regionp(Vector3(global_pos.x, 0.f, global_pos.z + region_size) * _mesh_vertex_spacing); + region = _storage->get_regionp(Vector3(global_pos.x, 0.f, global_pos.z + _region_size) * _mesh_vertex_spacing); if (region.is_valid()) { map_z = region->get_map(TYPE_HEIGHT); cmap_z = region->get_map(TYPE_CONTROL); } - region = _storage->get_regionp(Vector3(global_pos.x + region_size, 0.f, global_pos.z + region_size) * _mesh_vertex_spacing); + region = _storage->get_regionp(Vector3(global_pos.x + _region_size, 0.f, global_pos.z + _region_size) * _mesh_vertex_spacing); if (region.is_valid()) { map_xz = region->get_map(TYPE_HEIGHT); cmap_xz = region->get_map(TYPE_CONTROL); @@ -472,21 +466,21 @@ void Terrain3D::_update_collision() { int index = shape_size - 1 - z + x * shape_size; // Set heights on local map, or adjacent maps if on the last row/col - if (x < region_size && z < region_size) { + if (x < _region_size && z < _region_size) { map_data[index] = (is_hole(cmap->get_pixel(x, z).r)) ? hole_const : map->get_pixel(x, z).r; - } else if (x == region_size && z < region_size) { + } else if (x == _region_size && z < _region_size) { if (map_x.is_valid()) { map_data[index] = (is_hole(cmap_x->get_pixel(0, z).r)) ? hole_const : map_x->get_pixel(0, z).r; } else { map_data[index] = 0.0f; } - } else if (z == region_size && x < region_size) { + } else if (z == _region_size && x < _region_size) { if (map_z.is_valid()) { map_data[index] = (is_hole(cmap_z->get_pixel(x, 0).r)) ? hole_const : map_z->get_pixel(x, 0).r; } else { map_data[index] = 0.0f; } - } else if (x == region_size && z == region_size) { + } else if (x == _region_size && z == _region_size) { if (map_xz.is_valid()) { map_data[index] = (is_hole(cmap_xz->get_pixel(0, 0).r)) ? hole_const : map_xz->get_pixel(0, 0).r; } else { @@ -500,7 +494,7 @@ void Terrain3D::_update_collision() { //Transform3D xform = Transform3D(Basis(), global_pos); // Rotated shape Y=90 for -90 rotated array index Transform3D xform = Transform3D(Basis(Vector3(0.f, 1.f, 0.f), Math_PI * .5f), - global_pos + Vector3(region_size, 0.f, region_size) * .5f); + global_pos + Vector3(_region_size, 0.f, _region_size) * .5f); xform.scale(Vector3(_mesh_vertex_spacing, 1.f, _mesh_vertex_spacing)); if (!_show_debug_collision) { @@ -575,7 +569,7 @@ void Terrain3D::_generate_triangles(PackedVector3Array &p_vertices, PackedVector int32_t step = 1 << CLAMP(p_lod, 0, 8); if (!p_global_aabb.has_volume()) { - int32_t region_size = (int)_storage->get_region_size(); + int32_t region_size = (int32_t)_region_size; TypedArray region_locations = _storage->get_region_locations(); for (int r = 0; r < region_locations.size(); ++r) { @@ -684,6 +678,22 @@ void Terrain3D::set_debug_level(const int p_level) { debug_level = CLAMP(p_level, 0, DEBUG_MAX); } +void Terrain3D::set_region_size(const RegionSize p_size) { + LOG(INFO, p_size); + //ERR_FAIL_COND(p_size < SIZE_64); + //ERR_FAIL_COND(p_size > SIZE_2048); + ERR_FAIL_COND(p_size != SIZE_1024); + _region_size = p_size; + // Region size changed, update downstream + if (_storage) { + _storage->_region_size = _region_size; + _storage->_region_sizev = Vector2i(_region_size, _region_size); + } + if (_material.is_valid()) { + _material->_update_maps(); + } +} + void Terrain3D::set_mesh_lods(const int p_count) { if (_mesh_lods != p_count) { _clear_meshes(); @@ -1117,8 +1127,7 @@ void Terrain3D::update_region_labels() { label->set_visibility_range_fade_mode(GeometryInstance3D::VISIBILITY_RANGE_FADE_SELF); _label_nodes->add_child(label, true); Vector2i loc = region_locations[i]; - int region_size = _storage->get_region_size(); - Vector3 pos = Vector3(real_t(loc.x) + .5f, 0.f, real_t(loc.y) + .5f) * region_size * _mesh_vertex_spacing; + Vector3 pos = Vector3(real_t(loc.x) + .5f, 0.f, real_t(loc.y) + .5f) * _region_size * _mesh_vertex_spacing; label->set_position(pos); } } @@ -1345,6 +1354,13 @@ void Terrain3D::_notification(const int p_what) { } void Terrain3D::_bind_methods() { + //BIND_ENUM_CONSTANT(SIZE_64); + //BIND_ENUM_CONSTANT(SIZE_128); + //BIND_ENUM_CONSTANT(SIZE_256); + //BIND_ENUM_CONSTANT(SIZE_512); + BIND_ENUM_CONSTANT(SIZE_1024); + //BIND_ENUM_CONSTANT(SIZE_2048); + ClassDB::bind_method(D_METHOD("get_version"), &Terrain3D::get_version); ClassDB::bind_method(D_METHOD("set_debug_level", "level"), &Terrain3D::set_debug_level); ClassDB::bind_method(D_METHOD("get_debug_level"), &Terrain3D::get_debug_level); @@ -1357,6 +1373,9 @@ void Terrain3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_save_16_bit", "enabled"), &Terrain3D::set_save_16_bit); ClassDB::bind_method(D_METHOD("get_save_16_bit"), &Terrain3D::get_save_16_bit); + ClassDB::bind_method(D_METHOD("set_region_size", "size"), &Terrain3D::set_region_size); + ClassDB::bind_method(D_METHOD("get_region_size"), &Terrain3D::get_region_size); + ClassDB::bind_method(D_METHOD("set_mesh_lods", "count"), &Terrain3D::set_mesh_lods); ClassDB::bind_method(D_METHOD("get_mesh_lods"), &Terrain3D::get_mesh_lods); ClassDB::bind_method(D_METHOD("set_mesh_size", "size"), &Terrain3D::set_mesh_size); @@ -1402,8 +1421,12 @@ void Terrain3D::_bind_methods() { ClassDB::bind_method(D_METHOD("bake_mesh", "lod", "filter"), &Terrain3D::bake_mesh); ClassDB::bind_method(D_METHOD("generate_nav_mesh_source_geometry", "global_aabb", "require_nav"), &Terrain3D::generate_nav_mesh_source_geometry, DEFVAL(true)); + int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; + ADD_PROPERTY(PropertyInfo(Variant::STRING, "version", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY), "", "get_version"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "storage_directory", PROPERTY_HINT_DIR), "set_storage_directory", "get_storage_directory"); + //ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "64:64, 128:128, 256:256, 512:512, 1024:1024, 2048:2048"), "set_region_size", "get_region_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "1024:1024"), "set_region_size", "get_region_size"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "save_16_bit", PROPERTY_HINT_NONE), "set_save_16_bit", "get_save_16_bit"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "Terrain3DMaterial"), "set_material", "get_material"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "assets", PROPERTY_HINT_RESOURCE_TYPE, "Terrain3DAssets"), "set_assets", "get_assets"); diff --git a/src/terrain_3d.h b/src/terrain_3d.h index cfdf1d95..5db4c47b 100644 --- a/src/terrain_3d.h +++ b/src/terrain_3d.h @@ -24,12 +24,24 @@ class Terrain3D : public Node3D { GDCLASS(Terrain3D, Node3D); CLASS_NAME(); +public: // Constants + enum RegionSize { + //SIZE_64 = 64, + //SIZE_128 = 128, + //SIZE_256 = 256, + //SIZE_512 = 512, + SIZE_1024 = 1024, + //SIZE_2048 = 2048, + }; + +private: // Terrain state String _version = "0.9.3-dev"; bool _is_inside_world = false; bool _initialized = false; // Terrain settings + RegionSize _region_size = SIZE_1024; int _mesh_size = 48; int _mesh_lods = 7; real_t _mesh_vertex_spacing = 1.0f; @@ -122,6 +134,8 @@ class Terrain3D : public Node3D { String get_version() const { return _version; } void set_debug_level(const int p_level); int get_debug_level() const { return debug_level; } + void set_region_size(const RegionSize p_size); + RegionSize get_region_size() const { return _region_size; } void set_mesh_lods(const int p_count); int get_mesh_lods() const { return _mesh_lods; } void set_mesh_size(const int p_size); @@ -200,4 +214,6 @@ class Terrain3D : public Node3D { static void _bind_methods(); }; +VARIANT_ENUM_CAST(Terrain3D::RegionSize); + #endif // TERRAIN3D_CLASS_H diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp index e9d2f8fe..a86f2f2c 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -14,7 +14,7 @@ // Sends the whole region aabb to edited_area void Terrain3DEditor::_send_region_aabb(const Vector2i &p_region_loc, const Vector2 &p_height_range) { - Terrain3DStorage::RegionSize region_size = _terrain->get_storage()->get_region_size(); + Terrain3D::RegionSize region_size = _terrain->get_region_size(); AABB edited_area; edited_area.position = Vector3(p_region_loc.x * region_size, p_height_range.x, p_region_loc.y * region_size); edited_area.size = Vector3(region_size, p_height_range.y - p_height_range.x, region_size); @@ -94,11 +94,11 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_ return; } - Terrain3DStorage *storage = _terrain->get_storage(); - int region_size = storage->get_region_size(); + int region_size = _terrain->get_region_size(); Vector2i region_vsize = Vector2i(region_size, region_size); // If no region and can't add one, skip whole function. Checked again later + Terrain3DStorage *storage = _terrain->get_storage(); if (!storage->has_regionp(p_global_position) && (!_brush_data["auto_regions"] || _tool != HEIGHT)) { return; } diff --git a/src/terrain_3d_instancer.cpp b/src/terrain_3d_instancer.cpp index aa835cb0..7b44e5ca 100644 --- a/src/terrain_3d_instancer.cpp +++ b/src/terrain_3d_instancer.cpp @@ -482,7 +482,7 @@ void Terrain3DInstancer::update_transforms(const AABB &p_aabb) { LOG(WARN, "No region found at: ", region_loc); continue; } - int region_size = _terrain->get_storage()->get_region_size(); + int region_size = _terrain->get_region_size(); Rect2 region_rect; region_rect.set_position(region_loc * region_size); region_rect.set_size(Vector2(region_size, region_size)); diff --git a/src/terrain_3d_material.cpp b/src/terrain_3d_material.cpp index f6b104c8..1f64bb91 100644 --- a/src/terrain_3d_material.cpp +++ b/src/terrain_3d_material.cpp @@ -320,7 +320,7 @@ void Terrain3DMaterial::_update_maps() { LOG(DEBUG_CONT, "Region_locations size: ", region_locations.size(), " ", region_locations); RS->material_set_param(_material, "_region_locations", region_locations); - real_t region_size = real_t(storage->get_region_size()); + real_t region_size = real_t(_terrain->get_region_size()); LOG(DEBUG_CONT, "Setting region size in material: ", region_size); RS->material_set_param(_material, "_region_size", region_size); RS->material_set_param(_material, "_region_texel_size", 1.0f / region_size); diff --git a/src/terrain_3d_storage.cpp b/src/terrain_3d_storage.cpp index 670aa46a..57a5eb47 100644 --- a/src/terrain_3d_storage.cpp +++ b/src/terrain_3d_storage.cpp @@ -40,6 +40,9 @@ void Terrain3DStorage::initialize(Terrain3D *p_terrain) { _terrain = p_terrain; _region_map.resize(REGION_MAP_SIZE * REGION_MAP_SIZE); _mesh_vertex_spacing = _terrain->get_mesh_vertex_spacing(); + _region_size = _terrain->get_region_size(); + _region_sizev = Vector2i(_region_size, _region_size); + if (!initialized && !_terrain->get_storage_directory().is_empty()) { load_directory(_terrain->get_storage_directory()); } @@ -126,16 +129,6 @@ bool Terrain3DStorage::is_region_deleted(const Vector2i &p_region_loc) const { return region->is_deleted(); } -void Terrain3DStorage::set_region_size(const RegionSize p_size) { - LOG(INFO, p_size); - //ERR_FAIL_COND(p_size < SIZE_64); - //ERR_FAIL_COND(p_size > SIZE_2048); - ERR_FAIL_COND(p_size != SIZE_1024); - _region_size = p_size; - _region_sizev = Vector2i(_region_size, _region_size); - emit_signal("region_size_changed", _region_size); -} - void Terrain3DStorage::set_region_locations(const TypedArray &p_locations) { LOG(INFO, "Setting _region_locations with array sized: ", p_locations.size()); _region_locations = p_locations; @@ -959,13 +952,6 @@ void Terrain3DStorage::print_audit_data() const { /////////////////////////// void Terrain3DStorage::_bind_methods() { - //BIND_ENUM_CONSTANT(SIZE_64); - //BIND_ENUM_CONSTANT(SIZE_128); - //BIND_ENUM_CONSTANT(SIZE_256); - //BIND_ENUM_CONSTANT(SIZE_512); - BIND_ENUM_CONSTANT(SIZE_1024); - //BIND_ENUM_CONSTANT(SIZE_2048); - BIND_ENUM_CONSTANT(HEIGHT_FILTER_NEAREST); BIND_ENUM_CONSTANT(HEIGHT_FILTER_MINIMUM); @@ -994,10 +980,6 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("get_region_id", "region_location"), &Terrain3DStorage::get_region_id); ClassDB::bind_method(D_METHOD("get_region_idp", "global_position"), &Terrain3DStorage::get_region_idp); - ClassDB::bind_method(D_METHOD("set_region_size", "size"), &Terrain3DStorage::set_region_size); - ClassDB::bind_method(D_METHOD("get_region_size"), &Terrain3DStorage::get_region_size); - ClassDB::bind_method(D_METHOD("get_region_sizev"), &Terrain3DStorage::get_region_sizev); - ClassDB::bind_method(D_METHOD("add_region", "region", "update"), &Terrain3DStorage::add_region, DEFVAL(true)); ClassDB::bind_method(D_METHOD("add_regionl", "region_location", "update"), &Terrain3DStorage::add_regionl, DEFVAL(true)); ClassDB::bind_method(D_METHOD("add_regionp", "global_position", "update"), &Terrain3DStorage::add_regionp, DEFVAL(true)); @@ -1048,15 +1030,10 @@ void Terrain3DStorage::_bind_methods() { ClassDB::bind_method(D_METHOD("export_image", "file_name", "map_type"), &Terrain3DStorage::export_image); ClassDB::bind_method(D_METHOD("layered_to_image", "map_type"), &Terrain3DStorage::layered_to_image); - int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; - //ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "64:64, 128:128, 256:256, 512:512, 1024:1024, 2048:2048"), "set_region_size", "get_region_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "1024:1024"), "set_region_size", "get_region_size"); - ADD_SIGNAL(MethodInfo("maps_changed")); ADD_SIGNAL(MethodInfo("region_map_changed")); ADD_SIGNAL(MethodInfo("height_maps_changed")); ADD_SIGNAL(MethodInfo("control_maps_changed")); ADD_SIGNAL(MethodInfo("color_maps_changed")); - ADD_SIGNAL(MethodInfo("region_size_changed")); ADD_SIGNAL(MethodInfo("maps_edited", PropertyInfo(Variant::AABB, "edited_area"))); } diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h index 75a7982b..d675d8fd 100644 --- a/src/terrain_3d_storage.h +++ b/src/terrain_3d_storage.h @@ -21,15 +21,6 @@ class Terrain3DStorage : public Object { static inline const int REGION_MAP_SIZE = 16; static inline const Vector2i REGION_MAP_VSIZE = Vector2i(REGION_MAP_SIZE, REGION_MAP_SIZE); - enum RegionSize { - //SIZE_64 = 64, - //SIZE_128 = 128, - //SIZE_256 = 256, - //SIZE_512 = 512, - SIZE_1024 = 1024, - //SIZE_2048 = 2048, - }; - enum HeightFilter { HEIGHT_FILTER_NEAREST, HEIGHT_FILTER_MINIMUM @@ -39,6 +30,8 @@ class Terrain3DStorage : public Object { Terrain3D *_terrain = nullptr; // Storage Settings & flags + int _region_size = 0; + Vector2i _region_sizev = Vector2i(_region_size, _region_size); real_t _mesh_vertex_spacing = 1.f; // Set by Terrain3D::set_mesh_vertex_spacing AABB _edited_area; @@ -79,10 +72,6 @@ class Terrain3DStorage : public Object { GeneratedTexture _generated_control_maps; GeneratedTexture _generated_color_maps; - /// Move to Region? - RegionSize _region_size = SIZE_1024; - Vector2i _region_sizev = Vector2i(_region_size, _region_size); - // Functions void _clear(); @@ -121,10 +110,6 @@ class Terrain3DStorage : public Object { int get_region_id(const Vector2i &p_region_loc) const; int get_region_idp(const Vector3 &p_global_position) const; - void set_region_size(const RegionSize p_size); - RegionSize get_region_size() const { return _region_size; } - Vector2i get_region_sizev() const { return _region_sizev; } - Error add_region(const Ref &p_region, const bool p_update = true); Error add_regionl(const Vector2i &p_region_loc, const Ref &p_region, const bool p_update = true); Error add_regionp(const Vector3 &p_global_position, const Ref &p_region, const bool p_update = true); @@ -192,7 +177,6 @@ class Terrain3DStorage : public Object { static void _bind_methods(); }; -VARIANT_ENUM_CAST(Terrain3DStorage::RegionSize); VARIANT_ENUM_CAST(Terrain3DStorage::HeightFilter); /// Inline Region Functions