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..f34c8f04 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()
@@ -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()
@@ -213,7 +212,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 ):
@@ -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/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/constants.h b/src/constants.h
index ab9e97eb..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() + \
@@ -68,15 +74,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/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/register_types.cpp b/src/register_types.cpp
index e86c70b7..a86addca 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..90e74b37 100644
--- a/src/terrain_3d.cpp
+++ b/src/terrain_3d.cpp
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -29,17 +30,16 @@
int Terrain3D::debug_level{ ERROR };
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()) {
LOG(DEBUG, "Creating blank material");
_material.instantiate();
}
- if (_storage.is_null()) {
+ if (_storage == nullptr) {
LOG(DEBUG, "Creating blank storage");
- _storage.instantiate();
- _storage->set_version(Terrain3DStorage::CURRENT_VERSION);
+ _storage = memnew(Terrain3DStorage);
}
if (_assets.is_null()) {
LOG(DEBUG, "Creating blank texture list");
@@ -51,40 +51,35 @@ 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()");
+ _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()");
_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));
}
+ // 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()");
_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));
- }
- // 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));
+ _assets->connect("meshes_changed", callable_mp(_instancer, &Terrain3DInstancer::_update_mmis).bind(V2I_MAX, -1));
}
// Initialize the system
@@ -125,6 +120,29 @@ 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();
+ LOG(DEBUG, "Destroying ", labels.size(), " region labels");
+ for (int i = 0; i < labels.size(); i++) {
+ Node *label = cast_to(labels[i]);
+ memdelete_safely(label);
+ }
+}
+
void Terrain3D::_setup_mouse_picking() {
if (!is_inside_tree()) {
LOG(ERROR, "Not inside the tree, skipping mouse setup");
@@ -185,7 +203,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);
}
@@ -211,7 +229,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;
}
@@ -270,7 +288,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;
}
/**
@@ -357,7 +375,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;
}
@@ -397,39 +415,45 @@ 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") {
- hole_const = __FLT_MAX__;
+ hole_const = FLT_MAX;
}
for (int i = 0; i < _storage->get_region_count(); i++) {
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_id(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;
+ }
+ 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_id = _storage->get_region_id(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 = _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_id(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++) {
@@ -442,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 {
@@ -470,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) {
@@ -541,11 +565,11 @@ 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()) {
- 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) {
@@ -654,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();
@@ -683,32 +723,44 @@ 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");
}
}
-void Terrain3D::set_material(const Ref &p_material) {
- if (_material != p_material) {
+void Terrain3D::set_storage_directory(String p_dir) {
+ LOG(INFO, "Setting storage directory to ", p_dir);
+ if (_storage_directory != p_dir) {
_clear_meshes();
- LOG(INFO, "Setting material");
- _material = p_material;
+ _destroy_collision();
+ _destroy_instancer();
+ memdelete_safely(_storage);
+ _storage_directory = p_dir;
_initialize();
- emit_signal("material_changed");
}
}
-// This is run after the object has loaded and initialized
-void Terrain3D::set_storage(const Ref &p_storage) {
- if (_storage != p_storage) {
+String Terrain3D::get_storage_directory() const {
+ if (_storage == nullptr) {
+ return "";
+ }
+ return _storage_directory;
+}
+
+void Terrain3D::set_save_16_bit(const bool p_enabled) {
+ LOG(INFO, p_enabled);
+ _save_16_bit = p_enabled;
+}
+
+void Terrain3D::set_material(const Ref &p_material) {
+ if (_material != p_material) {
_clear_meshes();
- _destroy_collision();
- _destroy_instancer();
- LOG(INFO, "Setting storage");
- _storage = p_storage;
+ LOG(INFO, "Setting material");
+ _material = p_material;
_initialize();
- emit_signal("storage_changed");
+ emit_signal("material_changed");
}
}
@@ -802,7 +854,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 +993,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;
}
@@ -984,11 +1036,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)) {
@@ -1029,7 +1080,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) {
@@ -1044,6 +1095,44 @@ 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() {
+ _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];
+ 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];
+ 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.
@@ -1056,7 +1145,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,11 +1187,8 @@ PackedVector3Array Terrain3D::generate_nav_mesh_source_geometry(const AABB &p_gl
PackedStringArray Terrain3D::_get_configuration_warnings() const {
PackedStringArray psa;
- 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.");
@@ -1137,6 +1223,7 @@ void Terrain3D::_notification(const int p_what) {
case NOTIFICATION_POSTINITIALIZE: {
// Object initialized, before script is attached
LOG(INFO, "NOTIFICATION_POSTINITIALIZE");
+ _build_containers();
break;
}
@@ -1157,7 +1244,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;
}
@@ -1200,10 +1287,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_directory(_storage_directory);
}
if (!_material.is_valid()) {
LOG(DEBUG, "Save requested, but no valid material. Skipping");
@@ -1238,9 +1325,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;
}
@@ -1255,6 +1340,11 @@ void Terrain3D::_notification(const int p_what) {
case NOTIFICATION_PREDELETE: {
// Object is about to be deleted
LOG(INFO, "NOTIFICATION_PREDELETE");
+ _destroy_collision();
+ _destroy_instancer();
+ _destroy_labels();
+ _destroy_containers();
+ memdelete_safely(_storage);
break;
}
@@ -1264,9 +1354,28 @@ 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);
+ 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("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);
@@ -1276,8 +1385,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);
ClassDB::bind_method(D_METHOD("get_instancer"), &Terrain3D::get_instancer);
@@ -1314,10 +1421,16 @@ 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::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::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");
+ 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_");
@@ -1340,9 +1453,9 @@ 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("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 12f21504..5db4c47b 100644
--- a/src/terrain_3d.h
+++ b/src/terrain_3d.h
@@ -24,22 +24,41 @@ 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;
+ String _storage_directory;
+ bool _save_16_bit = false;
+ bool _show_region_labels = false;
+ Terrain3DStorage *_storage = nullptr;
Ref _material;
- Ref _storage;
Ref _assets;
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.
@@ -47,7 +66,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;
@@ -82,6 +101,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();
@@ -111,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);
@@ -118,15 +143,20 @@ class Terrain3D : public Node3D {
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; }
+
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; }
// Instancer
Terrain3DInstancer *get_instancer() const { return _instancer; }
+ Node *get_mmi_parent() const { return _mmi_nodes; }
// Editor components
void set_editor(Terrain3DEditor *p_editor);
@@ -164,11 +194,15 @@ 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;
- // Misc
+ // Godot Callbacks
PackedStringArray _get_configuration_warnings() const override;
// DEPRECATED 0.9.2 - Remove 0.9.3+
@@ -180,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_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_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_editor.cpp b/src/terrain_3d_editor.cpp
index 57268fb9..a86f2f2c 100644
--- a/src/terrain_3d_editor.cpp
+++ b/src/terrain_3d_editor.cpp
@@ -2,99 +2,105 @@
#include
#include
-#include
#include "logger.h"
#include "terrain_3d_editor.h"
+#include "terrain_3d_storage.h"
#include "terrain_3d_util.h"
///////////////////////////
// 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);
- Terrain3DStorage::RegionSize region_size = _terrain->get_storage()->get_region_size();
-
+// Sends the whole region aabb to edited_area
+void Terrain3DEditor::_send_region_aabb(const Vector2i &p_region_loc, const Vector2 &p_height_range) {
+ Terrain3D::RegionSize region_size = _terrain->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_region(p_global_position);
- bool modified = false;
+// 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(p_global_position);
- modified = 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, "Location ", p_region_loc, " out of bounds. Max: ",
+ -Terrain3DStorage::REGION_MAP_SIZE / 2, " to ", Terrain3DStorage::REGION_MAP_SIZE / 2 - 1);
}
- } else {
- if (has_region) {
- int region_id = _terrain->get_storage()->get_region_id(p_global_position);
- Ref height_map = _terrain->get_storage()->get_map_region(TYPE_HEIGHT, region_id);
- height_range = Util::get_min_max(height_map);
+ 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());
+ }
+ }
- _terrain->get_storage()->remove_region(p_global_position);
- modified = true;
+ // 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 (modified) {
- _region_modified(p_global_position, height_range);
+ // 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) {
+ _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);
- Ref 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);
- 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_id(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 = _get_map_type();
+ if (map_type == TYPE_MAX) {
+ LOG(ERROR, "Invalid tool selected");
+ return;
}
- 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;
+ 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;
}
Ref brush_image = _brush_data["brush_image"];
@@ -158,12 +164,10 @@ 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;
}
// 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 +176,16 @@ 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_id(brush_global_position);
- if (new_region_id == -1) {
- if (!_brush_data["auto_regions"] || _tool != HEIGHT) {
- continue;
- }
- Error err = storage->add_region(brush_global_position);
- if (err) {
- continue;
- }
- new_region_id = storage->get_region_id(brush_global_position);
- _region_modified(brush_global_position);
+ // Get region for current brush pixel global position
+ Vector2i region_loc = storage->get_region_location(brush_global_position);
+ Ref region = _operate_region(region_loc);
+ // If no region and can't make one, skip
+ if (region.is_null()) {
+ continue;
}
- 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 +291,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);
@@ -447,132 +445,144 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_
break;
}
}
+ backup_region(region);
map->set_pixelv(map_pixel_position, dest);
}
}
}
- _modified = true;
+ // 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);
}
-Dictionary Terrain3DEditor::_get_undo_data() const {
- Dictionary data;
- if (_tool < 0 || _tool >= TOOL_MAX) {
- return data;
- }
- switch (_tool) {
- case REGION:
- LOG(DEBUG, "Storing region locations");
- data["region_locations"] = _terrain->get_storage()->get_region_locations().duplicate();
- 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["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);
- 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);
- break;
-
- case COLOR:
- case ROUGHNESS:
- LOG(DEBUG, "Storing color maps");
- data["color_map"] = _terrain->get_storage()->get_maps_copy(TYPE_COLOR);
- 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;
}
- 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);
+ 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;
+ }
+ }
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());
+ // 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);
- LOG(DEBUG, "Setting up redo snapshot...");
- Dictionary redo_set = _get_undo_data();
+ LOG(DEBUG, "Storing undo snapshot: ", _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);
-
- 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]);
+ LOG(INFO, "Applying Undo/Redo data");
+
+ Terrain3DStorage *storage = _terrain->get_storage();
+
+ 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_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);
+ LOG(DEBUG, "Edited: ", region->get_data());
+ }
+ }
+
+ 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");
+ }
+ }
+ 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");
+ }
+ }
+
+ // 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]);
+ //_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;
}
///////////////////////////
@@ -648,24 +658,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_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;
@@ -673,7 +683,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();
}
@@ -684,21 +694,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 (_pending_undo && _modified) {
+ // If undo was created and terrain actually modified, store it
+ 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;
}
///////////////////////////
@@ -735,9 +767,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 ab7e4c5b..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;
- bool _modified = false;
+ 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 _region_modified(const Vector3 &p_global_position, const Vector2 &p_height_range = Vector2());
- void _operate_region(const Vector3 &p_global_position);
+ void _send_region_aabb(const Vector2i &p_region_loc, const Vector2 &p_height_range = Vector2());
+ 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 c9a6011b..7b44e5ca 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");
@@ -34,14 +23,19 @@ 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)) {
- region_locations = region_dict.keys();
+ if (p_region_loc.x == INT32_MAX) {
+ 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,13 +100,13 @@ 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);
}
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);
}
@@ -128,14 +122,25 @@ 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
///////////////////////////
-Terrain3DInstancer::~Terrain3DInstancer() {
- destroy();
-}
-
void Terrain3DInstancer::initialize(Terrain3D *p_terrain) {
if (p_terrain) {
_terrain = p_terrain;
@@ -157,29 +162,29 @@ 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);
}
}
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);
}
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)) {
+ _backup_region(region);
+ mesh_dict.erase(p_mesh_id);
+ }
_destroy_mmi_by_location(p_region_loc, p_mesh_id);
}
@@ -266,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);
@@ -283,10 +287,8 @@ void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const D
// Append multimesh
if (xforms.size() > 0) {
- Vector2i region_loc = _terrain->get_storage()->get_region_location(p_global_position);
- append_multimesh(region_loc, mesh_id, xforms, colors);
+ add_transforms(mesh_id, xforms, colors);
}
- _terrain->get_storage()->set_modified();
}
void Terrain3DInstancer::remove_instances(const Vector3 &p_global_position, const Dictionary &p_params) {
@@ -344,7 +346,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_modified();
}
void Terrain3DInstancer::add_multimesh(const int p_mesh_id, const Ref &p_multimesh, const Transform3D &p_xform) {
@@ -408,8 +409,6 @@ void Terrain3DInstancer::add_transforms(const int p_mesh_id, const TypedArrayget_storage()->set_modified();
}
// Appends new transforms to existing multimeshes
@@ -456,14 +455,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;
+ }
+ _backup_region(region);
+ Dictionary mesh_dict = region->get_multimeshes();
mesh_dict[p_mesh_id] = mm;
- region_dict[p_region_loc] = mesh_dict;
-
_update_mmis(p_region_loc, p_mesh_id);
}
@@ -472,12 +473,16 @@ 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];
- int region_size = _terrain->get_storage()->get_region_size();
+ 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_region_size();
Rect2 region_rect;
region_rect.set_position(region_loc * region_size);
region_rect.set_size(Vector2(region_size, region_size));
@@ -485,7 +490,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++) {
@@ -528,29 +533,37 @@ 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;
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);
}
if (mesh_dict.has(p_dst_id)) {
+ _backup_region(region);
mm_dst = mesh_dict[p_dst_id];
mesh_dict.erase(p_dst_id);
}
// If src is ok, insert into dst slot
if (mm_src.is_valid()) {
+ _backup_region(region);
mesh_dict[p_dst_id] = mm_src;
}
// If dst is ok, insert into src slot
if (mm_dst.is_valid()) {
+ _backup_region(region);
mesh_dict[p_src_id] = mm_dst;
}
LOG(DEBUG, "Swapped multimesh ids at: ", region_loc);
@@ -588,7 +601,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;
@@ -620,6 +638,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;
@@ -658,4 +681,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 8da07877..7db0dc72 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,14 +29,15 @@ 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 = 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);
+ void _backup_regionl(const Vector2i &p_region_loc);
+ void _backup_region(const Ref &p_region);
public:
Terrain3DInstancer() {}
- ~Terrain3DInstancer();
+ ~Terrain3DInstancer() { destroy(); }
void initialize(Terrain3D *p_terrain);
void destroy();
@@ -60,6 +61,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_material.cpp b/src/terrain_3d_material.cpp
index e819a733..1f64bb91 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);
@@ -326,11 +320,18 @@ 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);
+ 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_region.cpp b/src/terrain_3d_region.cpp
new file mode 100644
index 00000000..341520f8
--- /dev/null
+++ b/src/terrain_3d_region.cpp
@@ -0,0 +1,376 @@
+// Copyright © 2024 Cory Petkovsek, Roope Palmroos, and Contributors.
+
+#include
+
+#include "logger.h"
+#include "terrain_3d_region.h"
+#include "terrain_3d_storage.h"
+#include "terrain_3d_util.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_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;
+ }
+ _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 {
+ 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) {
+ LOG(INFO, "Setting height map for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)");
+ 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)");
+ 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)");
+ 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();
+ }
+}
+
+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;
+ }
+ return true;
+}
+
+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);
+}
+
+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 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, "Provided ", type_str, " map wrong size: ", p_map->get_size(), ". Creating blank");
+ }
+ } 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;
+ }
+}
+
+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::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");
+ }
+}
+
+Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) {
+ // Initiate save to external file. The scene will save itself.
+ if (_location.x == INT32_MAX) {
+ LOG(ERROR, "Region has not been setup. Location is INT32_MAX. Skipping ", p_path);
+ }
+ if (!_modified) {
+ LOG(DEBUG, "Region ", _location, " 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;
+ }
+ 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);
+ 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, get_path(), ResourceSaver::FLAG_COMPRESS);
+ _height_map = original_map;
+ } else {
+ err = ResourceSaver::get_singleton()->save(this, get_path(), ResourceSaver::FLAG_COMPRESS);
+ }
+ if (err == OK) {
+ _modified = false;
+ LOG(INFO, "File saved successfully");
+ } else {
+ 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 (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) {
+#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
+/////////////////////
+
+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);
+ 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);
+ 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_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_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::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");
+ 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");
+
+ // 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, "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
new file mode 100644
index 00000000..bd92476f
--- /dev/null
+++ b/src/terrain_3d_region.h
@@ -0,0 +1,151 @@
+// Copyright © 2024 Cory Petkovsek, Roope Palmroos, and Contributors.
+
+#ifndef TERRAIN3D_REGION_CLASS_H
+#define TERRAIN3D_REGION_CLASS_H
+
+#include "constants.h"
+#include "terrain_3d_util.h"
+
+using namespace godot;
+
+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 = 0;
+ Vector2 _height_range = V2_ZERO;
+ // Maps
+ Ref _height_map;
+ Ref _control_map;
+ Ref _color_map;
+ // Instancer
+ 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;
+
+public:
+ Terrain3DRegion() {}
+ ~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; }
+ 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; }
+ 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; }
+ Dictionary get_multimeshes() const { return _multimeshes; }
+
+ // File I/O
+ Error save(const String &p_path = "", const bool p_16_bit = false);
+
+ // Working Data
+ 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();
+};
+
+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;
+
+/// 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 c24e80ee..57a5eb47 100644
--- a/src/terrain_3d_storage.cpp
+++ b/src/terrain_3d_storage.cpp
@@ -1,10 +1,11 @@
// Copyright © 2024 Cory Petkovsek, Roope Palmroos, and Contributors.
+#include
+#include
+#include
+#include
#include
-#include
#include
-#include
-#include
#include "logger.h"
#include "terrain_3d_storage.h"
@@ -17,10 +18,12 @@ 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();
- set_multimeshes(Dictionary());
}
///////////////////////////
@@ -28,76 +31,53 @@ 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();
-}
-
-// 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");
- _modified = true;
- }
-}
-
-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;
-}
+ _mesh_vertex_spacing = _terrain->get_mesh_vertex_spacing();
+ _region_size = _terrain->get_region_size();
+ _region_sizev = Vector2i(_region_size, _region_size);
-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);
+ if (!initialized && !_terrain->get_storage_directory().is_empty()) {
+ load_directory(_terrain->get_storage_directory());
}
}
-void Terrain3DStorage::update_heights(const Vector2 &p_heights) {
- if (p_heights.x < _height_range.x) {
- _height_range.x = p_heights.x;
- _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);
+// 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_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() {
@@ -113,14 +93,40 @@ void Terrain3DStorage::add_edited_area(const AABB &p_area) {
emit_signal("maps_edited", _edited_area);
}
-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_modified(const Vector2i &p_region_loc, const bool p_modified) {
+ Ref region = _regions[p_region_loc];
+ if (region.is_null()) {
+ LOG(ERROR, "Region not found at: ", p_region_loc);
+ return;
+ }
+ return region->set_modified(p_modified);
+}
+
+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;
+ }
+ 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_locations(const TypedArray &p_locations) {
@@ -130,274 +136,226 @@ void Terrain3DStorage::set_region_locations(const TypedArray &p_locati
update_maps();
}
-/** Returns a region location given a global position*/
-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();
- return Vector2i((Vector2(descaled_position.x, descaled_position.z) / real_t(_region_size)).floor());
+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);
}
-// 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];
+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);
}
-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);
+Ref Terrain3DStorage::add_region_blank(const Vector2i &p_region_loc, const bool p_update) {
+ 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;
+ }
+ return Ref();
}
-// 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;
+Ref Terrain3DStorage::add_region_blankp(const Vector3 &p_global_position, const bool p_update) {
+ return add_region_blank(get_region_location(p_global_position));
}
-/** 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
+/** 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) {
- 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");
-
- 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) * _terrain->get_mesh_vertex_spacing());
- }
+Error Terrain3DStorage::add_region(const Ref &p_region, const bool p_update) {
+ 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");
- if (has_region(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;
- } else {
- LOG(DEBUG, "Region at ", p_global_position, " already exists, overwriting");
- remove_region(p_global_position, false);
- }
- }
-
- TypedArray images = sanitize_maps(TYPE_MAX, p_images);
- if (images.is_empty()) {
- LOG(ERROR, "Sanitize_maps failed to accept images or produce blanks");
+ // Check bounds and slow report errors
+ if (get_region_map_index(region_loc) < 0) {
+ LOG(ERROR, "Location ", region_loc, " out of bounds. Max: ",
+ -REGION_MAP_SIZE / 2, " to ", REGION_MAP_SIZE / 2 - 1);
return FAILED;
}
-
- // If we're importing data into a region, check its heights for aabbs
- 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);
+ p_region->sanitize_maps();
+ 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);
}
-
- 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]);
- _region_locations.push_back(region_loc);
- LOG(DEBUG, "Total regions after pushback: ", _region_locations.size());
-
- // Region_map is used by get_region_id so must be updated every time
+ _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) {
- 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();
+ force_update_maps();
}
return OK;
}
-void Terrain3DStorage::remove_region(const Vector3 &p_global_position, const bool p_update) {
- 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.");
+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);
+}
- LOG(INFO, "Removing region at: ", get_region_location(p_global_position));
- _region_locations.remove_at(region_id);
- LOG(DEBUG, "Removed region_locations, new size: ", _region_locations.size());
- _height_maps.remove_at(region_id);
- LOG(DEBUG, "Removed heightmaps, new size: ", _height_maps.size());
- _control_maps.remove_at(region_id);
- LOG(DEBUG, "Removed control maps, new size: ", _control_maps.size());
- _color_maps.remove_at(region_id);
- LOG(DEBUG, "Removed colormaps, new size: ", _color_maps.size());
+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
+ 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 ", 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());
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();
+ force_update_maps();
}
}
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 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];
+ 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);
- _modified = true;
+ calc_height_range();
any_changed = true;
emit_signal("height_maps_changed");
}
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);
- _modified = true;
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);
- _modified = true;
any_changed = true;
emit_signal("color_maps_changed");
}
- if (_region_map_dirty) {
- LOG(DEBUG_CONT, "Regenerating ", REGION_MAP_VSIZE, " region map array");
- _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 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
- }
- }
- _modified = true;
- any_changed = true;
- emit_signal("region_map_changed");
- }
if (any_changed) {
emit_signal("maps_changed");
}
}
-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;
+void Terrain3DStorage::save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit) {
+ Ref region = _regions[p_region_loc];
+ if (region.is_null()) {
+ LOG(ERROR, "No region found at: ", p_region_loc);
+ return;
}
-}
-
-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;
+ String fname = Util::location_to_filename(p_region_loc);
+ 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(INFO, "File to delete ", path, " doesn't exist. (Maybe from add, undo, save)");
+ 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();
+ }
+ LOG(INFO, "File ", path, " deleted");
+ return;
}
- return Ref();
+ region->save(path, p_16_bit);
}
-void Terrain3DStorage::set_maps(const MapType p_map_type, const TypedArray &p_maps) {
- 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;
+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);
+ if (!FileAccess::file_exists(path)) {
+ LOG(ERROR, "File ", path, " doesn't exist");
+ return;
}
- force_update_maps(p_map_type);
+ Ref region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE);
+ if (region.is_null()) {
+ LOG(ERROR, "Cannot load region at ", path);
+ return;
+ }
+ 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);
}
TypedArray Terrain3DStorage::get_maps(const MapType p_map_type) const {
@@ -421,74 +379,80 @@ 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;
}
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;
}
- int region_id = get_region_id(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 / _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);
+ img_pos = img_pos.clamp(V2I_ZERO, Vector2i(_region_size - 1, _region_size - 1));
+ Ref map = region->get_map(p_map_type);
map->set_pixelv(img_pos, p_pixel);
+ region->set_modified(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;
}
- int region_id = get_region_id(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 / _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);
+ img_pos = img_pos.clamp(V2I_ZERO, Vector2i(_region_size - 1, _region_size - 1));
+ Ref map = region->get_map(p_map_type);
return map->get_pixelv(img_pos);
}
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;
@@ -517,41 +481,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););
- int region_id = get_region_id(p_global_position);
+ // Verify in a region
+ int region_id = get_region_idp(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);
}
@@ -562,7 +528,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 {
@@ -572,82 +538,11 @@ 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);
-}
-
-/**
- * Returns sanitized maps of either a region set or a uniform set
- * Verifies size, vailidity, and format of maps
- * Creates filled blanks if lacking
- * p_map_type:
- * TYPE_HEIGHT, TYPE_CONTROL, TYPE_COLOR: uniform set - p_maps are all the same type, size=N
- * TYPE_MAX = region set - p_maps is [ height, control, color ], size=3
- **/
-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;
+ 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:
_generated_height_maps.clear();
@@ -662,56 +557,25 @@ 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");
+ 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();
}
-void Terrain3DStorage::save() {
- if (!_modified) {
- LOG(INFO, "Save requested, but 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);
- }
- ERR_FAIL_COND(err);
- LOG(DEBUG, "ResourceSaver return error (0 is OK): ", err);
- if (err == OK) {
- _modified = false;
- }
- LOG(INFO, "Finished saving terrain data");
- }
- if (path.get_extension() != "res") {
- LOG(WARN, "Storage resource is not saved as an external, binary .res file");
+void Terrain3DStorage::save_directory(const String &p_dir) {
+ LOG(INFO, "Saving data files to ", p_dir);
+ Array locations = _regions.keys();
+ for (int i = 0; i < locations.size(); i++) {
+ save_region(locations[i], p_dir, _terrain->get_save_16_bit());
}
}
@@ -731,7 +595,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()) {
@@ -739,7 +603,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");
@@ -747,22 +611,21 @@ 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;
}
- 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;
}
@@ -816,26 +679,25 @@ 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->blit_rect(tmp_images[i], Rect2i(start_coords, size_to_copy), Vector2i(0, 0));
+ 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), V2I_ZERO);
+ } 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));
+ Ref region;
+ region.instantiate();
+ Vector3 position = Vector3(descaled_position.x + start_coords.x, 0.f, descaled_position.z + start_coords.y);
+ 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
}
@@ -888,10 +750,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();
@@ -899,7 +761,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;
}
@@ -942,8 +804,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];
@@ -968,12 +830,51 @@ 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);
- img->blit_rect(get_map_region(map_type, region_id), Rect2i(Vector2i(0, 0), _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;
}
+void Terrain3DStorage::load_directory(const String &p_dir) {
+ if (p_dir.is_empty()) {
+ LOG(ERROR, "Specified data directory is blank");
+ return;
+ }
+ Ref da = DirAccess::open(p_dir);
+ if (da.is_null()) {
+ LOG(ERROR, "Cannot read Terrain3D data directory: ", p_dir);
+ return;
+ }
+ _clear();
+
+ 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];
+ String path = p_dir + String("/") + fname;
+ if (!fname.begins_with("terrain3d") || !fname.ends_with(".res")) {
+ continue;
+ }
+ LOG(DEBUG, "Loading region from ", path);
+ Vector2i loc = Util::filename_to_location(fname);
+ if (loc.x == INT32_MAX) {
+ LOG(ERROR, "Cannot get region location from file name: ", fname);
+ continue;
+ }
+ 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
+ add_region(region, 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.
@@ -984,7 +885,6 @@ Ref Terrain3DStorage::layered_to_image(const MapType p_map_type) const {
* 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;
@@ -1001,7 +901,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;
@@ -1018,22 +918,19 @@ 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))) {
+ if (get_region_idp(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;
}
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++) {
@@ -1055,59 +952,60 @@ 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);
- //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);
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);
-
- 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("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_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);
+ 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_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("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("remove_region", "global_position", "update"), &Terrain3DStorage::remove_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_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("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", "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_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("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);
+
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);
@@ -1118,40 +1016,24 @@ 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("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_normal", "global_position"), &Terrain3DStorage::get_normal);
+ 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));
- ClassDB::bind_method(D_METHOD("save"), &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);
- 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::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");
- 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"));
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")));
- ADD_SIGNAL(MethodInfo("multimeshes_changed"));
}
diff --git a/src/terrain_3d_storage.h b/src/terrain_3d_storage.h
index 6a59bd32..d675d8fd 100644
--- a/src/terrain_3d_storage.h
+++ b/src/terrain_3d_storage.h
@@ -3,63 +3,24 @@
#ifndef TERRAIN3D_STORAGE_CLASS_H
#define TERRAIN3D_STORAGE_CLASS_H
-#include
-#include
-
#include "constants.h"
#include "generated_texture.h"
-#include "terrain_3d_util.h"
+#include "terrain_3d_region.h"
class Terrain3D;
using namespace godot;
-class Terrain3DStorage : public Resource {
- GDCLASS(Terrain3DStorage, Resource);
+class Terrain3DStorage : public Object {
+ GDCLASS(Terrain3DStorage, Object);
CLASS_NAME();
+ 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);
- 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,
- //SIZE_256 = 256,
- //SIZE_512 = 512,
- SIZE_1024 = 1024,
- //SIZE_2048 = 2048,
- };
-
enum HeightFilter {
HEIGHT_FILTER_NEAREST,
HEIGHT_FILTER_MINIMUM
@@ -68,97 +29,116 @@ class Terrain3DStorage : public Resource {
private:
Terrain3D *_terrain = nullptr;
- // 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
- // These contain the TextureLayered RID from the RenderingServer, no Image
- GeneratedTexture _generated_height_maps;
- GeneratedTexture _generated_control_maps;
- GeneratedTexture _generated_color_maps;
+ // 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;
- uint64_t _last_region_bounds_error = 0;
+ Vector2 _master_height_range = V2_ZERO;
+
+ /////////
+ // 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. 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 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.
- // 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
- * location is tracked by region_locations. The region data are combined into one large
- * texture in generated_*_maps.
- */
TypedArray _region_locations;
TypedArray _height_maps;
TypedArray _control_maps;
TypedArray _color_maps;
- // Foliage Instancer contains MultiMeshes saved to disk
- // Dictionary[region_location:Vector2i] -> Dictionary[mesh_id:int] -> MultiMesh
- Dictionary _multimeshes;
+ // Editing occurs on the Image arrays above, which are converted to Texture arrays
+ // below for the shader.
+
+ // 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;
+ GeneratedTexture _generated_color_maps;
// Functions
void _clear();
- int _get_region_map_index(const Vector2i &p_region_loc) const;
public:
Terrain3DStorage() {}
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; }
+ ~Terrain3DStorage() { _clear(); }
- 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(); }
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_count() const { return _region_locations.size(); }
+ 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; }
+ 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_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);
+ bool is_region_deleted(const Vector2i &p_region_loc) const;
+
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);
- void remove_region(const Vector3 &p_global_position, const bool p_update = true);
- void update_maps();
+ 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;
+
+ 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);
+
+ // 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);
// 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);
- TypedArray get_maps(const MapType p_map_type) const;
- TypedArray get_maps_copy(const MapType p_map_type) 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 update_maps();
+ 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;
+
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);
@@ -169,57 +149,84 @@ class Terrain3DStorage : public Resource {
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;
- TypedArray sanitize_maps(const MapType p_map_type, const TypedArray &p_maps) const;
- void force_update_maps(const MapType p_map = TYPE_MAX);
- // Instancer
- void set_multimeshes(const Dictionary &p_multimeshes);
- Dictionary get_multimeshes() const { return _multimeshes; }
+ 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;
- // File I/O
- void save();
- void clear_modified() { _modified = false; }
- void set_modified() { _modified = true; }
- void import_images(const TypedArray