diff --git a/Terrain3D.vcxproj b/Terrain3D.vcxproj
index abec7f2d8..973731d5f 100644
--- a/Terrain3D.vcxproj
+++ b/Terrain3D.vcxproj
@@ -221,6 +221,7 @@
+
diff --git a/Terrain3D.vcxproj.filters b/Terrain3D.vcxproj.filters
index 026e09887..d956c7385 100644
--- a/Terrain3D.vcxproj.filters
+++ b/Terrain3D.vcxproj.filters
@@ -283,6 +283,9 @@
2. Docs
+
+ 4. Shaders
+
diff --git a/doc/doc_classes/Terrain3D.xml b/doc/doc_classes/Terrain3D.xml
index 19f90b3e0..804be8fd1 100644
--- a/doc/doc_classes/Terrain3D.xml
+++ b/doc/doc_classes/Terrain3D.xml
@@ -252,6 +252,12 @@
If enabled, heightmaps are saved as 16-bit half-precision to reduce file size. Files are always loaded in 32-bit for editing. Upon save, a copy of the heightmap is converted to 16-bit for writing. It does not change what is currently in memory.
This process is lossy. 16-bit precision gets increasingly worse with every power of 2. At a height of 256m, the precision interval is .25m. At 512m it is .5m. At 1024m it is 1m. Saving a height of 1024.4m will be rounded down to 1024m.
+
+ The selected compression mode will be used to compress the color maps, to be used during runtime. The uncompressed color map will be used during editing. Upon save, a copy of the color map is compressed to the selected compression mode. During runtime, the compressed color map will be used instead of the uncompressed color map.
+
+
+ Frees the uncompressed color maps for the regions, if the application is not running in the editor, and [member color_compression_mode] is set to something other than None. The uncompressed color maps are also freed upon exporting the game.
+
Displays the area designated for use by the autoshader, which shows materials based upon slope.
Alias for [member Terrain3DMaterial.show_autoshader].
diff --git a/doc/doc_classes/Terrain3DData.xml b/doc/doc_classes/Terrain3DData.xml
index 1259779bd..d643332e2 100644
--- a/doc/doc_classes/Terrain3DData.xml
+++ b/doc/doc_classes/Terrain3DData.xml
@@ -89,7 +89,7 @@
- Returns the resource ID of the generated height map Texture Array sent to the shader. You can use this RID with the RenderingServer to set it as a shader parameter for a sampler2DArray uniform in your own shader. See [url=https://terrain3d.readthedocs.io/en/stable/docs/tips.html#using-the-generated-height-map-in-other-shaders]Tips[/url] for an example.
+ Returns the resource ID of the generated color map Texture Array sent to the shader. You can use this RID with the RenderingServer to set it as a shader parameter for a sampler2DArray uniform in your own shader.
@@ -431,10 +431,12 @@
+
Saves the specified active region to the directory. See [method Terrain3DRegion.save].
- region_location - the region to save.
- 16_bit - converts the edited 32-bit heightmap to 16-bit. This is a lossy operation.
+ - color_compression_mode - compresses the color map to selected compression mode.
diff --git a/doc/doc_classes/Terrain3DRegion.xml b/doc/doc_classes/Terrain3DRegion.xml
index eb4c1f705..9cb1c2a23 100644
--- a/doc/doc_classes/Terrain3DRegion.xml
+++ b/doc/doc_classes/Terrain3DRegion.xml
@@ -39,7 +39,7 @@
- Returns the specified image map.
+ Returns the specified image map. Returns the compressed color map if a compression mode is selected and not in editor mode.
@@ -58,18 +58,22 @@
+
Sanitizes all map types. See [method sanitize_map].
+ - free_uncompressed_color_maps - if true, the uncompressed color map will be freed instead of sanitized.
+
Saves this region to the current file name.
- path - specifies a directory and file name to use from now on.
- - 16-bit - save this region with 16-bit height map instead of 32-bit. This process is lossy. Does not change the bit depth in memory.
+ - 16-bit - saves this region with 16-bit height map instead of 32-bit. This process is lossy. Does not change the bit depth in memory.
+ - color_compression_mode - saves a compressed color map for this region if a compression mode is set.
@@ -123,6 +127,9 @@
[b]RGB[/b] is used for color, which is multiplied by albedo in the shader. Multiply is a blend mode that only darkens.
[b]A[/b] is used for a roughness modifier. A value of 0.5 means no change to the existing texture roughness. Higher than this value increases roughness, lower decreases it.
+
+ A compressed version of the color map. Empty if [member Terrain3D.color_compression_mode] is set to None. Otherwise, this texture will be used instead of the uncompressed color map when not in editor mode.
+
This map tells the shader which textures to use where, how to blend, where to place holes, etc.
Image format: FORMAT_RF, 32-bit per pixel as full-precision floating-point.
diff --git a/doc/docs/data_format.md b/doc/docs/data_format.md
index ae4f9723a..fd116b8e9 100644
--- a/doc/docs/data_format.md
+++ b/doc/docs/data_format.md
@@ -6,6 +6,7 @@ The data format version is found as [Terrain3DData.version](../api/class_terrain
| Version | Description |
|---------|-------------------|
+| 1.10 | Added a compressed color map [#842](https://github.com/TokisanGames/Terrain3D/pull/842)
| 0.93 | The monolithic storage file was split into one file per region [#374](https://github.com/TokisanGames/Terrain3D/pull/374), [#476](https://github.com/TokisanGames/Terrain3D/pull/476)
| 0.92 | Add `Terrain3DInstancer` data [#340](https://github.com/TokisanGames/Terrain3D/pull/340)
| 0.842 | Control map changed from FORMAT_RGB to 32-bit packed integer (encoded in FORMAT_RF) [#234](https://github.com/TokisanGames/Terrain3D/pull/234/)
diff --git a/doc/docs/installation.md b/doc/docs/installation.md
index 810d5f4bc..a15ffd5b3 100644
--- a/doc/docs/installation.md
+++ b/doc/docs/installation.md
@@ -80,3 +80,5 @@ If upgrading from a very old version, you may need to go through multiple steps
| 0.8.0 | 0.8.4 - 0.9.0 |
* 0.9.3 - Data storage changed from a single .res file to one file per region saved in a directory.
+
+Also see [Data Format Changelog](data_format.md).
\ No newline at end of file
diff --git a/project/addons/terrain_3d/src/editor_plugin.gd b/project/addons/terrain_3d/src/editor_plugin.gd
index 51196ca7b..365ab0ca9 100644
--- a/project/addons/terrain_3d/src/editor_plugin.gd
+++ b/project/addons/terrain_3d/src/editor_plugin.gd
@@ -7,6 +7,7 @@ extends EditorPlugin
# Includes
const Terrain3DUI: Script = preload("res://addons/terrain_3d/src/ui.gd")
const RegionGizmo: Script = preload("res://addons/terrain_3d/src/region_gizmo.gd")
+const ExportPlugin: Script = preload("res://addons/terrain_3d/src/export_plugin.gd")
const ASSET_DOCK: String = "res://addons/terrain_3d/src/asset_dock.tscn"
# Editor Plugin
@@ -21,6 +22,7 @@ var mouse_global_position: Vector3 = Vector3.ZERO
var godot_editor_window: Window # The Godot Editor window
var viewport: SubViewport # Viewport the mouse was last in
var mouse_in_main: bool = false # Helper to track when mouse is in the editor vp
+var export_plugin = ExportPlugin.new()
# Terrain
var terrain: Terrain3D
@@ -64,6 +66,7 @@ func _enter_tree() -> void:
asset_dock = load(ASSET_DOCK).instantiate()
asset_dock.initialize(self)
+ add_export_plugin(export_plugin)
func _exit_tree() -> void:
@@ -76,6 +79,7 @@ func _exit_tree() -> void:
scene_changed.disconnect(_on_scene_changed)
godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered)
+ remove_export_plugin(export_plugin)
func _on_godot_focus_entered() -> void:
diff --git a/project/addons/terrain_3d/src/export_plugin.gd b/project/addons/terrain_3d/src/export_plugin.gd
new file mode 100644
index 000000000..efefca1b3
--- /dev/null
+++ b/project/addons/terrain_3d/src/export_plugin.gd
@@ -0,0 +1,46 @@
+# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+# Editor Export Plugin for Terrain3D
+@tool
+extends EditorExportPlugin
+
+
+var _hash: String
+var _free_uncompressed_color_maps: bool
+var _color_compress_mode: Image.CompressMode
+
+
+func _get_name() -> String:
+ return "Terrain3DExportPlugin"
+
+
+func _begin_customize_scenes(platform: EditorExportPlatform, features: PackedStringArray) -> bool:
+ return true
+
+
+func _customize_scene(scene: Node, path: String) -> Node:
+ var terrain: Terrain3D = scene.find_child("Terrain3D", true)
+ if terrain:
+ _free_uncompressed_color_maps = terrain.get_free_uncompressed_color_maps()
+ _color_compress_mode = terrain.get_color_image_compress_mode()
+ return null
+
+
+func _begin_customize_resources(platform: EditorExportPlatform, features: PackedStringArray) -> bool:
+ _hash = ""
+ for feat: String in features:
+ _hash += feat
+ _hash += platform.to_string()
+ return true
+
+
+func _customize_resource(resource: Resource, path: String) -> Resource:
+ if resource is Terrain3DRegion:
+ var region: Terrain3DRegion = resource
+ if _color_compress_mode != Image.COMPRESS_MAX and _free_uncompressed_color_maps and region.compressed_color_map != null:
+ region.free_uncompressed_color_map()
+ return region
+ return null
+
+
+func _get_customization_configuration_hash() -> int:
+ return hash(_hash)
diff --git a/project/addons/terrain_3d/src/export_plugin.gd.uid b/project/addons/terrain_3d/src/export_plugin.gd.uid
new file mode 100644
index 000000000..f073a5db4
--- /dev/null
+++ b/project/addons/terrain_3d/src/export_plugin.gd.uid
@@ -0,0 +1 @@
+uid://bfl6jfwv15i0f
diff --git a/src/shaders/color_map.glsl b/src/shaders/color_map.glsl
new file mode 100644
index 000000000..9ad86a0b0
--- /dev/null
+++ b/src/shaders/color_map.glsl
@@ -0,0 +1,24 @@
+// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+
+R"(
+//INSERT: COLOR_MAP_BASE
+color_map = region_uv.z > -1.0 ? textureLod(_color_maps, region_uv, region_mip) : COLOR_MAP_DEF;
+
+//INSERT: COLOR_MAP_BILERP
+ // 4 lookups if linear filtering, else 1 lookup.
+ vec4 col_map[4];
+ col_map[3] = index[3].z > -1 ? texelFetch(_color_maps, index[3], 0) : COLOR_MAP_DEF;
+ #ifdef FILTER_LINEAR
+ col_map[0] = index[0].z > -1 ? texelFetch(_color_maps, index[0], 0) : COLOR_MAP_DEF;
+ col_map[1] = index[1].z > -1 ? texelFetch(_color_maps, index[1], 0) : COLOR_MAP_DEF;
+ col_map[2] = index[2].z > -1 ? texelFetch(_color_maps, index[2], 0) : COLOR_MAP_DEF;
+
+ color_map =
+ col_map[0] * weights[0] +
+ col_map[1] * weights[1] +
+ col_map[2] * weights[2] +
+ col_map[3] * weights[3] ;
+ #else
+ color_map = col_map[3];
+ #endif
+)"
\ No newline at end of file
diff --git a/src/shaders/main.glsl b/src/shaders/main.glsl
index 65f5d2de8..929e3575b 100644
--- a/src/shaders/main.glsl
+++ b/src/shaders/main.glsl
@@ -481,26 +481,12 @@ void fragment() {
base_ddy *= bias;
// Color map
- vec4 color_map = region_uv.z > -1.0 ? textureLod(_color_maps, region_uv, region_mip) : COLOR_MAP_DEF;
+ vec4 color_map = COLOR_MAP_DEF;
+//INSERT: COLOR_MAP_BASE
// Branching smooth normals and manually interpolated color map - fixes cross region artifacts
if (bilerp) {
- // 4 lookups if linear filtering, else 1 lookup.
- vec4 col_map[4];
- col_map[3] = index[3].z > -1 ? texelFetch(_color_maps, index[3], 0) : COLOR_MAP_DEF;
- #ifdef FILTER_LINEAR
- col_map[0] = index[0].z > -1 ? texelFetch(_color_maps, index[0], 0) : COLOR_MAP_DEF;
- col_map[1] = index[1].z > -1 ? texelFetch(_color_maps, index[1], 0) : COLOR_MAP_DEF;
- col_map[2] = index[2].z > -1 ? texelFetch(_color_maps, index[2], 0) : COLOR_MAP_DEF;
-
- color_map =
- col_map[0] * weights[0] +
- col_map[1] * weights[1] +
- col_map[2] * weights[2] +
- col_map[3] * weights[3] ;
- #else
- color_map = col_map[3];
- #endif
+//INSERT: COLOR_MAP_BILERP
// 5 lookups
// Fetch the additional required height values for smooth normals
diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp
index 17c5bcf20..d78bc0887 100644
--- a/src/terrain_3d.cpp
+++ b/src/terrain_3d.cpp
@@ -610,6 +610,30 @@ void Terrain3D::set_save_16_bit(const bool p_enabled) {
}
}
+void Terrain3D::set_color_map_enabled(const bool p_enabled) {
+ if (_material.is_valid()) {
+ _material->set_color_map_enabled(p_enabled);
+ }
+ if (_data) {
+ _data->set_color_map_enabled(p_enabled);
+ }
+}
+
+void Terrain3D::set_color_map_mode(const ColorMapMode p_mode) {
+ SET_IF_DIFF(_color_map_mode, p_mode);
+ LOG(INFO, "Setting color map mode: ", _color_map_mode);
+ notify_property_list_changed();
+}
+
+void Terrain3D::set_color_compress_mode(const CompressMode p_mode) {
+ SET_IF_DIFF(_color_compress_mode, p_mode);
+ LOG(INFO, "Setting compression mode for color maps: ", _color_compress_mode);
+ TypedArray regions = _data->get_regions_active();
+ for (Ref region : regions) {
+ region->set_modified(true);
+ }
+}
+
void Terrain3D::set_label_distance(const real_t p_distance) {
SET_IF_DIFF(_label_distance, CLAMP(p_distance, 0.f, 100000.f));
LOG(INFO, "Setting region label distance: ", _label_distance);
@@ -1141,7 +1165,12 @@ void Terrain3D::_validate_property(PropertyInfo &p_property) const {
p_property.name == StringName("displacement_sharpness") ||
p_property.name == StringName("buffer_shader_override_enabled") ||
p_property.name == StringName("buffer_shader_override")) {
- p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ p_property.usage &= ~PROPERTY_USAGE_EDITOR;
+ }
+ }
+ if (_color_map_mode != COLOR_COMPRESSED) {
+ if (p_property.name == StringName("color_compress_mode")) {
+ p_property.usage &= ~PROPERTY_USAGE_EDITOR;
}
}
}
@@ -1159,6 +1188,11 @@ void Terrain3D::_bind_methods() {
BIND_ENUM_CONSTANT(SIZE_1024);
BIND_ENUM_CONSTANT(SIZE_2048);
+ BIND_ENUM_CONSTANT(COLOR_DISABLED);
+ BIND_ENUM_CONSTANT(COLOR_CLEARED);
+ BIND_ENUM_CONSTANT(COLOR_EDITABLE);
+ BIND_ENUM_CONSTANT(COLOR_COMPRESSED);
+
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);
@@ -1194,6 +1228,15 @@ void Terrain3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_region_size"), &Terrain3D::get_region_size);
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_free_color_map"), &Terrain3D::set_free_color_map);
+ ClassDB::bind_method(D_METHOD("get_free_color_map"), &Terrain3D::get_free_color_map);
+ ClassDB::bind_method(D_METHOD("set_color_map_enabled", "enabled"), &Terrain3D::set_color_map_enabled);
+ ClassDB::bind_method(D_METHOD("get_color_map_enabled"), &Terrain3D::get_color_map_enabled);
+ ClassDB::bind_method(D_METHOD("set_color_map_mode", "mode"), &Terrain3D::set_color_map_mode);
+ ClassDB::bind_method(D_METHOD("get_color_map_mode"), &Terrain3D::get_color_map_mode);
+ ClassDB::bind_method(D_METHOD("set_color_compress_mode", "mode"), &Terrain3D::set_color_compress_mode);
+ ClassDB::bind_method(D_METHOD("get_color_compress_mode"), &Terrain3D::get_color_compress_mode);
+ ClassDB::bind_method(D_METHOD("get_color_image_compress_mode"), &Terrain3D::get_color_image_compress_mode);
ClassDB::bind_method(D_METHOD("set_label_distance", "distance"), &Terrain3D::set_label_distance);
ClassDB::bind_method(D_METHOD("get_label_distance"), &Terrain3D::get_label_distance);
ClassDB::bind_method(D_METHOD("set_label_size", "size"), &Terrain3D::set_label_size);
@@ -1244,7 +1287,7 @@ void Terrain3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_gi_mode"), &Terrain3D::get_gi_mode);
ClassDB::bind_method(D_METHOD("set_cull_margin", "margin"), &Terrain3D::set_cull_margin);
ClassDB::bind_method(D_METHOD("get_cull_margin"), &Terrain3D::get_cull_margin);
- ClassDB::bind_method(D_METHOD("set_free_editor_textures"), &Terrain3D::set_free_editor_textures);
+ ClassDB::bind_method(D_METHOD("set_free_editor_textures", "enabled"), &Terrain3D::set_free_editor_textures);
ClassDB::bind_method(D_METHOD("get_free_editor_textures"), &Terrain3D::get_free_editor_textures);
ClassDB::bind_method(D_METHOD("set_instancer_mode", "mode"), &Terrain3D::set_instancer_mode);
ClassDB::bind_method(D_METHOD("get_instancer_mode"), &Terrain3D::get_instancer_mode);
@@ -1319,10 +1362,16 @@ void Terrain3D::_bind_methods() {
ADD_GROUP("Regions", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "64:64,128:128,256:256,512:512,1024:1024,2048:2048", PROPERTY_USAGE_EDITOR), "change_region_size", "get_region_size");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "save_16_bit"), "set_save_16_bit", "get_save_16_bit");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "label_distance", PROPERTY_HINT_RANGE, "0.0,10000.0,0.5,or_greater"), "set_label_distance", "get_label_distance");
ADD_PROPERTY(PropertyInfo(Variant::INT, "label_size", PROPERTY_HINT_RANGE, "24,128,1"), "set_label_size", "get_label_size");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_grid"), "set_show_region_grid", "get_show_region_grid");
+ ADD_SUBGROUP("Advanced", "");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "save_16_bit"), "set_save_16_bit", "get_save_16_bit");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "free_color_map"), "set_free_color_map", "get_free_color_map");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "color_map_enabled"), "set_color_map_enabled", "get_color_map_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "color_map_mode", PROPERTY_HINT_ENUM, "Disabled,Cleared,Editable,Compressed"), "set_color_map_mode", "get_color_map_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "color_compress_mode", PROPERTY_HINT_ENUM, "None,S3TC (LQ Desktop),BPTC (HQ Desktop),ETC1 (LQ Mobile),ETC2 (Mobile),ASTC (Mobile)"),
+ "set_color_compress_mode", "get_color_compress_mode");
ADD_GROUP("Collision", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mode", PROPERTY_HINT_ENUM, "Disabled,Dynamic / Game,Dynamic / Editor,Full / Game,Full / Editor"), "set_collision_mode", "get_collision_mode");
diff --git a/src/terrain_3d.h b/src/terrain_3d.h
index 664468377..0f14ea088 100644
--- a/src/terrain_3d.h
+++ b/src/terrain_3d.h
@@ -45,6 +45,13 @@ class Terrain3D : public Node3D {
SIZE_2048 = 2048,
};
+ enum ColorMapMode {
+ COLOR_DISABLED, // maps freed from memory in editor and game; editing is disabled; saved data is unchanged
+ COLOR_CLEARED, // disabled plus color maps are cleared on save
+ COLOR_EDITABLE, // current - no compressed map saved or used editable in game
+ COLOR_COMPRESSED, // compressed freed in editor, generated on save; uncompressed freed in game and stripped on export.
+ };
+
private:
String _version = "1.1.0-dev";
String _data_directory;
@@ -70,6 +77,9 @@ class Terrain3D : public Node3D {
// Regions
RegionSize _region_size = SIZE_256;
bool _save_16_bit = false;
+ ColorMapMode _color_map_mode = COLOR_EDITABLE;
+ CompressMode _color_compress_mode = Terrain3DRegion::COMPRESS_NONE;
+ bool _free_color_map = false;
real_t _label_distance = 0.f;
int _label_size = 48;
@@ -169,6 +179,15 @@ class Terrain3D : public Node3D {
void change_region_size(const RegionSize p_size) { _data ? _data->change_region_size(p_size) : void(); }
void set_save_16_bit(const bool p_enabled);
bool get_save_16_bit() const { return _save_16_bit; }
+ void set_free_color_map(const bool p_free_color_map) { _free_color_map = p_free_color_map; }
+ bool get_free_color_map() const { return _free_color_map; };
+ void set_color_map_enabled(const bool p_enabled);
+ bool get_color_map_enabled() const { return _data ? _data->get_color_map_enabled() : true; }
+ void set_color_map_mode(const ColorMapMode p_mode);
+ ColorMapMode get_color_map_mode() const { return _color_map_mode; }
+ void set_color_compress_mode(const CompressMode p_mode = Terrain3DRegion::COMPRESS_NONE);
+ Terrain3DRegion::CompressMode get_color_compress_mode() const { return _color_compress_mode; }
+ Image::CompressMode get_color_image_compress_mode() const { return Terrain3DRegion::get_image_compress_mode(_color_compress_mode); }
void set_label_distance(const real_t p_distance);
real_t get_label_distance() const { return _label_distance; }
void set_label_size(const int p_size);
@@ -296,8 +315,9 @@ class Terrain3D : public Node3D {
static void _bind_methods();
};
-VARIANT_ENUM_CAST(Terrain3D::RegionSize);
VARIANT_ENUM_CAST(Terrain3D::DebugLevel);
+VARIANT_ENUM_CAST(Terrain3D::RegionSize);
+VARIANT_ENUM_CAST(Terrain3D::ColorMapMode);
constexpr Terrain3D::DebugLevel MESG = Terrain3D::DebugLevel::MESG;
constexpr Terrain3D::DebugLevel WARN = Terrain3D::DebugLevel::WARN;
diff --git a/src/terrain_3d_data.cpp b/src/terrain_3d_data.cpp
index fbf793ca1..64c2b3807 100644
--- a/src/terrain_3d_data.cpp
+++ b/src/terrain_3d_data.cpp
@@ -36,7 +36,7 @@ void Terrain3DData::_copy_paste_dfr(const Terrain3DRegion *p_src_region, const R
TypedArray dst_maps = p_dst_region->get_maps();
for (int i = 0; i < dst_maps.size(); i++) {
Image *img = cast_to(dst_maps[i]);
- if (img) {
+ if (img && cast_to(src_maps[i])) {
img->blit_rect(src_maps[i], p_src_rect, p_dst_rect.position);
}
}
@@ -248,6 +248,14 @@ Error Terrain3DData::add_region(const Ref &p_region, const bool
return FAILED;
}
p_region->sanitize_maps();
+ // Free compressed color map in editor
+ if (IS_EDITOR) {
+ p_region->clear_compressed_color_map();
+ } else if (_terrain && _terrain->get_free_color_map() &&
+ p_region->get_compressed_color_map().is_valid()) {
+ // Free uncompressed color map in game if valid and desired
+ p_region->clear_color_map();
+ }
p_region->set_deleted(false);
if (!_region_locations.has(region_loc)) {
_region_locations.push_back(region_loc);
@@ -304,7 +312,7 @@ void Terrain3DData::save_directory(const String &p_dir) {
LOG(INFO, "Saving data files to ", p_dir);
Array locations = _regions.keys();
for (const Vector2i ®ion_loc : locations) {
- save_region(region_loc, p_dir, _terrain->get_save_16_bit());
+ save_region(region_loc, p_dir, _terrain->get_save_16_bit(), _terrain->get_color_compress_mode());
}
if (IS_EDITOR && !EditorInterface::get_singleton()->get_resource_filesystem()->is_scanning()) {
EditorInterface::get_singleton()->get_resource_filesystem()->scan();
@@ -312,7 +320,7 @@ void Terrain3DData::save_directory(const String &p_dir) {
}
// You may need to do a file system scan to update FileSystem panel
-void Terrain3DData::save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit) {
+void Terrain3DData::save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit, const CompressMode p_color_compress_mode) {
Ref region = get_region(p_region_loc);
if (region.is_null()) {
LOG(ERROR, "No region found at: ", p_region_loc);
@@ -341,7 +349,7 @@ void Terrain3DData::save_region(const Vector2i &p_region_loc, const String &p_di
LOG(INFO, "File ", path, " deleted");
return;
}
- Error err = region->save(path, p_16_bit);
+ Error err = region->save(path, p_16_bit, p_color_compress_mode);
if (!(err == OK || err == ERR_SKIP)) {
LOG(ERROR, "Could not save file: ", path, ", error: ", UtilityFunctions::error_string(err), " (", err, ")");
}
@@ -369,7 +377,7 @@ void Terrain3DData::load_directory(const String &p_dir) {
LOG(ERROR, "Cannot get region location from file name: ", fname);
continue;
}
- Ref region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE);
+ Ref region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_REPLACE);
if (region.is_null()) {
LOG(ERROR, "Cannot load region at ", path);
continue;
@@ -389,6 +397,30 @@ void Terrain3DData::load_directory(const String &p_dir) {
region->set_version(CURRENT_VERSION); // Sends upgrade warning if old version
add_region(region, false);
}
+
+ LOG(MESG, "Verifying region color maps:");
+ for (const Vector2i ®ion_loc : _region_locations) {
+ const Terrain3DRegion *region = get_region_ptr(region_loc);
+ if (region) {
+ Vector2i region_loc = region->get_location();
+ Ref map = region->get_color_map();
+ if (map.is_valid()) {
+ LOG(MESG, "Region ", region_loc, " color map size: ",
+ map->get_size(), " format: ", map->get_format());
+ } else {
+ LOG(MESG, "Region ", region_loc, " colormap: null");
+ }
+
+ map = region->get_compressed_color_map();
+ if (map.is_valid()) {
+ LOG(MESG, "Region ", region_loc, " compressed color map size: ",
+ map->get_size(), " format: ", map->get_format());
+ } else {
+ LOG(MESG, "Region ", region_loc, " compressed colormap: null");
+ }
+ }
+ }
+
update_maps(TYPE_MAX, true, false);
}
@@ -400,7 +432,7 @@ void Terrain3DData::load_region(const Vector2i &p_region_loc, const String &p_di
LOG(ERROR, "File ", path, " doesn't exist");
return;
}
- Ref region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE);
+ Ref region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_REPLACE);
if (region.is_null()) {
LOG(ERROR, "Cannot load region at ", path);
return;
@@ -441,14 +473,23 @@ TypedArray Terrain3DData::get_maps(const MapType p_map_type) const {
return TypedArray();
}
+void Terrain3DData::set_color_map_enabled(const bool p_enabled) {
+ LOG(INFO, "Setting color maps enabled: ", p_enabled);
+ if (_color_map_enabled != p_enabled) {
+ _color_map_enabled = p_enabled;
+ update_maps(TYPE_COLOR);
+ }
+}
+
void Terrain3DData::update_maps(const MapType p_map_type, const bool p_all_regions, const bool p_generate_mipmaps) {
// Generate region color mipmaps
- if (p_generate_mipmaps && (p_map_type == TYPE_COLOR || p_map_type == TYPE_MAX)) {
+ if (_color_map_enabled && p_generate_mipmaps &&
+ (p_map_type == TYPE_COLOR || p_map_type == TYPE_MAX)) {
LOG(EXTREME, "Regenerating color mipmaps");
for (const Vector2i ®ion_loc : _region_locations) {
Terrain3DRegion *region = get_region_ptr(region_loc);
// Generate all or only those marked edited
- if (region && (p_all_regions || region->is_edited())) {
+ if (region && (p_all_regions || region->is_edited() && region->get_color_map().is_valid())) {
region->get_color_map()->generate_mipmaps();
}
}
@@ -524,7 +565,7 @@ void Terrain3DData::update_maps(const MapType p_map_type, const bool p_all_regio
emit_signal("height_maps_changed");
}
- // Rebulid control maps if dirty
+ // Rebuild control maps if dirty
if (_generated_control_maps.is_dirty()) {
LOG(EXTREME, "Regenerating control texture array from regions");
_control_maps.clear();
@@ -540,17 +581,19 @@ void Terrain3DData::update_maps(const MapType p_map_type, const bool p_all_regio
emit_signal("control_maps_changed");
}
- // Rebulid color maps if dirty
+ // Rebuild color maps if dirty
if (_generated_color_maps.is_dirty()) {
LOG(EXTREME, "Regenerating color texture array from regions");
_color_maps.clear();
- for (const Vector2i ®ion_loc : _region_locations) {
- const Terrain3DRegion *region = get_region_ptr(region_loc);
- if (region) {
- _color_maps.push_back(region->get_color_map());
+ if (_color_map_enabled) {
+ for (const Vector2i ®ion_loc : _region_locations) {
+ const Terrain3DRegion *region = get_region_ptr(region_loc);
+ if (region) {
+ _color_maps.push_back(region->get_active_color_map());
+ }
}
+ _generated_color_maps.create(_color_maps);
}
- _generated_color_maps.create(_color_maps);
any_changed = true;
LOG(DEBUG, "Emitting color_maps_changed");
emit_signal("color_maps_changed");
@@ -575,14 +618,18 @@ void Terrain3DData::update_maps(const MapType p_map_type, const bool p_all_regio
emit_signal("control_maps_changed");
break;
case TYPE_COLOR:
- _generated_color_maps.update(region->get_color_map(), region_id);
+ if (_color_map_enabled) {
+ _generated_color_maps.update(region->get_active_color_map(), region_id);
+ }
LOG(DEBUG, "Emitting color_maps_changed");
emit_signal("color_maps_changed");
break;
default:
_generated_height_maps.update(region->get_height_map(), region_id);
_generated_control_maps.update(region->get_control_map(), region_id);
- _generated_color_maps.update(region->get_color_map(), region_id);
+ if (_color_map_enabled) {
+ _generated_color_maps.update(region->get_active_color_map(), region_id);
+ }
LOG(DEBUG, "Emitting height_maps_changed");
emit_signal("height_maps_changed");
LOG(DEBUG, "Emitting control_maps_changed");
@@ -597,7 +644,9 @@ void Terrain3DData::update_maps(const MapType p_map_type, const bool p_all_regio
if (any_changed) {
LOG(DEBUG, "Emitting maps_changed");
emit_signal("maps_changed");
- _terrain->snap();
+ if (_terrain) {
+ _terrain->snap();
+ }
}
}
@@ -754,7 +803,7 @@ Vector3 Terrain3DData::get_texture_id(const Vector3 &p_global_position) const {
// If material available, autoshader enabled, and pixel set to auto
if (_terrain) {
Ref t_material = _terrain->get_material();
- bool auto_enabled = t_material->get_auto_shader();
+ bool auto_enabled = t_material->get_auto_shader_enabled();
bool control_auto = is_auto(src);
if (auto_enabled && control_auto) {
real_t auto_slope = real_t(t_material->get_shader_param("auto_slope"));
@@ -1178,7 +1227,7 @@ void Terrain3DData::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_region", "region", "update"), &Terrain3DData::remove_region, DEFVAL(true));
ClassDB::bind_method(D_METHOD("save_directory", "directory"), &Terrain3DData::save_directory);
- ClassDB::bind_method(D_METHOD("save_region", "region_location", "directory", "save_16_bit"), &Terrain3DData::save_region, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("save_region", "region_location", "directory", "save_16_bit", "color_compress_mode"), &Terrain3DData::save_region, DEFVAL(""), DEFVAL(false), DEFVAL(Terrain3DRegion::COMPRESS_NONE));
ClassDB::bind_method(D_METHOD("load_directory", "directory"), &Terrain3DData::load_directory);
ClassDB::bind_method(D_METHOD("load_region", "region_location", "directory", "update"), &Terrain3DData::load_region, DEFVAL(true));
@@ -1186,6 +1235,8 @@ void Terrain3DData::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_control_maps"), &Terrain3DData::get_control_maps);
ClassDB::bind_method(D_METHOD("get_color_maps"), &Terrain3DData::get_color_maps);
ClassDB::bind_method(D_METHOD("get_maps", "map_type"), &Terrain3DData::get_maps);
+ ClassDB::bind_method(D_METHOD("set_color_map_enabled", "enabled"), &Terrain3DData::set_color_map_enabled);
+ ClassDB::bind_method(D_METHOD("get_color_map_enabled"), &Terrain3DData::get_color_map_enabled);
ClassDB::bind_method(D_METHOD("update_maps", "map_type", "all_regions", "generate_mipmaps"), &Terrain3DData::update_maps, DEFVAL(TYPE_MAX), DEFVAL(true), DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_height_maps_rid"), &Terrain3DData::get_height_maps_rid);
ClassDB::bind_method(D_METHOD("get_control_maps_rid"), &Terrain3DData::get_control_maps_rid);
@@ -1238,6 +1289,8 @@ void Terrain3DData::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "control_maps", PROPERTY_HINT_ARRAY_TYPE, "Image", ro_flags), "", "get_control_maps");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "color_maps", PROPERTY_HINT_ARRAY_TYPE, "Image", ro_flags), "", "get_color_maps");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "color_map_enabled"), "set_color_map_enabled", "get_color_map_enabled");
+
ADD_SIGNAL(MethodInfo("maps_changed"));
ADD_SIGNAL(MethodInfo("region_map_changed"));
ADD_SIGNAL(MethodInfo("height_maps_changed"));
diff --git a/src/terrain_3d_data.h b/src/terrain_3d_data.h
index c9347b9df..a053124fd 100644
--- a/src/terrain_3d_data.h
+++ b/src/terrain_3d_data.h
@@ -16,7 +16,7 @@ class Terrain3DData : public Object {
friend Terrain3D;
public: // Constants
- static inline const real_t CURRENT_VERSION = 0.93f;
+ static inline const real_t CURRENT_VERSION = 1.099f; // Current Terrain3DRegion format version
static inline const int REGION_MAP_SIZE = 32;
static inline const Vector2i REGION_MAP_VSIZE = V2I(REGION_MAP_SIZE);
@@ -61,6 +61,7 @@ class Terrain3DData : public Object {
TypedArray _height_maps;
TypedArray _control_maps;
TypedArray _color_maps;
+ bool _color_map_enabled = true;
// Editing occurs on the Image arrays above, which are converted to Texture arrays
// below for the shader.
@@ -122,7 +123,7 @@ class Terrain3DData : public Object {
// File I/O
void save_directory(const String &p_dir);
- void save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit = false);
+ void save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit = false, const CompressMode p_color_compress_mode = Terrain3DRegion::COMPRESS_NONE);
void load_directory(const String &p_dir);
void load_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_update = true);
@@ -131,6 +132,8 @@ class Terrain3DData : public Object {
TypedArray get_control_maps() const { return _control_maps; }
TypedArray get_color_maps() const { return _color_maps; }
TypedArray get_maps(const MapType p_map_type) const;
+ void set_color_map_enabled(const bool p_enabled);
+ bool get_color_map_enabled() const { return _color_map_enabled; }
void update_maps(const MapType p_map_type = TYPE_MAX, const bool p_all_regions = true, const bool p_generate_mipmaps = false);
RID get_height_maps_rid() const { return _generated_height_maps.get_rid(); }
RID get_control_maps_rid() const { return _generated_control_maps.get_rid(); }
diff --git a/src/terrain_3d_material.cpp b/src/terrain_3d_material.cpp
index e16b0697e..4e347d989 100644
--- a/src/terrain_3d_material.cpp
+++ b/src/terrain_3d_material.cpp
@@ -33,6 +33,9 @@ void Terrain3DMaterial::_preload_shaders() {
#include "shaders/dual_scaling.glsl"
, "dual_scaling");
_parse_shader(
+#include "shaders/color_map.glsl"
+ , "color_map");
+ _parse_shader(
#include "shaders/overlays.glsl"
, "overlays");
_parse_shader(
@@ -152,11 +155,15 @@ String Terrain3DMaterial::_generate_shader_code() const {
excludes.push_back("TEXTURE_SAMPLERS_LINEAR");
excludes.push_back("NOISE_SAMPLER_LINEAR");
}
- if (!_auto_shader) {
+ if (!_auto_shader_enabled) {
excludes.push_back("AUTO_SHADER_UNIFORMS");
excludes.push_back("AUTO_SHADER");
}
- if (!_dual_scaling) {
+ if (!_color_map_enabled) {
+ excludes.push_back("COLOR_MAP_BASE");
+ excludes.push_back("COLOR_MAP_BILERP");
+ }
+ if (!_dual_scaling_enabled) {
excludes.push_back("DUAL_SCALING_UNIFORMS");
excludes.push_back("DUAL_SCALING");
excludes.push_back("DUAL_SCALING_CONDITION_0");
@@ -273,7 +280,7 @@ String Terrain3DMaterial::_generate_buffer_shader_code() {
excludes.push_back("TEXTURE_SAMPLERS_LINEAR");
excludes.push_back("NOISE_SAMPLER_LINEAR");
}
- if (!_auto_shader) {
+ if (!_auto_shader_enabled) {
excludes.push_back("AUTO_SHADER_UNIFORMS");
excludes.push_back("AUTO_SHADER");
}
@@ -731,14 +738,20 @@ void Terrain3DMaterial::set_texture_filtering(const TextureFiltering p_filtering
_update_shader();
}
-void Terrain3DMaterial::set_auto_shader(const bool p_enabled) {
- SET_IF_DIFF(_auto_shader, p_enabled);
+void Terrain3DMaterial::set_auto_shader_enabled(const bool p_enabled) {
+ SET_IF_DIFF(_auto_shader_enabled, p_enabled);
LOG(INFO, "Enable auto shader: ", p_enabled);
_update_shader();
}
-void Terrain3DMaterial::set_dual_scaling(const bool p_enabled) {
- SET_IF_DIFF(_dual_scaling, p_enabled);
+void Terrain3DMaterial::set_color_map_enabled(const bool p_enabled) {
+ SET_IF_DIFF(_color_map_enabled, p_enabled);
+ LOG(INFO, "Enable color map: ", p_enabled);
+ _update_shader();
+}
+
+void Terrain3DMaterial::set_dual_scaling_enabled(const bool p_enabled) {
+ SET_IF_DIFF(_dual_scaling_enabled, p_enabled);
LOG(INFO, "Enable dual scaling: ", p_enabled);
_update_shader();
}
@@ -1169,10 +1182,12 @@ void Terrain3DMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_world_background"), &Terrain3DMaterial::get_world_background);
ClassDB::bind_method(D_METHOD("set_texture_filtering", "filtering"), &Terrain3DMaterial::set_texture_filtering);
ClassDB::bind_method(D_METHOD("get_texture_filtering"), &Terrain3DMaterial::get_texture_filtering);
- ClassDB::bind_method(D_METHOD("set_auto_shader", "enabled"), &Terrain3DMaterial::set_auto_shader);
- ClassDB::bind_method(D_METHOD("get_auto_shader"), &Terrain3DMaterial::get_auto_shader);
- ClassDB::bind_method(D_METHOD("set_dual_scaling", "enabled"), &Terrain3DMaterial::set_dual_scaling);
- ClassDB::bind_method(D_METHOD("get_dual_scaling"), &Terrain3DMaterial::get_dual_scaling);
+ ClassDB::bind_method(D_METHOD("set_auto_shader_enabled", "enabled"), &Terrain3DMaterial::set_auto_shader_enabled);
+ ClassDB::bind_method(D_METHOD("get_auto_shader_enabled"), &Terrain3DMaterial::get_auto_shader_enabled);
+ ClassDB::bind_method(D_METHOD("set_color_map_enabled", "enabled"), &Terrain3DMaterial::set_color_map_enabled);
+ ClassDB::bind_method(D_METHOD("get_color_map_enabled"), &Terrain3DMaterial::get_color_map_enabled);
+ ClassDB::bind_method(D_METHOD("set_dual_scaling_enabled", "enabled"), &Terrain3DMaterial::set_dual_scaling_enabled);
+ ClassDB::bind_method(D_METHOD("get_dual_scaling_enabled"), &Terrain3DMaterial::get_dual_scaling_enabled);
ClassDB::bind_method(D_METHOD("set_shader_override_enabled", "enabled"), &Terrain3DMaterial::set_shader_override_enabled);
ClassDB::bind_method(D_METHOD("is_shader_override_enabled"), &Terrain3DMaterial::is_shader_override_enabled);
@@ -1243,11 +1258,13 @@ void Terrain3DMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("save", "path"), &Terrain3DMaterial::save, DEFVAL(""));
+ ADD_GROUP("Shader Code", "");
// These must be different from the names of uniform groups
ADD_PROPERTY(PropertyInfo(Variant::INT, "world_background", PROPERTY_HINT_ENUM, "None,Flat,Noise"), "set_world_background", "get_world_background");
ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filtering", PROPERTY_HINT_ENUM, "Linear,Nearest"), "set_texture_filtering", "get_texture_filtering");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_shader_enabled"), "set_auto_shader", "get_auto_shader");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dual_scaling_enabled"), "set_dual_scaling", "get_dual_scaling");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_shader_enabled"), "set_auto_shader_enabled", "get_auto_shader_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "color_map_enabled"), "set_color_map_enabled", "get_color_map_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dual_scaling_enabled"), "set_dual_scaling_enabled", "get_dual_scaling_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shader_override_enabled"), "set_shader_override_enabled", "is_shader_override_enabled");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shader_override", PROPERTY_HINT_RESOURCE_TYPE, "Shader"), "set_shader_override", "get_shader_override");
diff --git a/src/terrain_3d_material.h b/src/terrain_3d_material.h
index 3ea4d824b..a67798c8d 100644
--- a/src/terrain_3d_material.h
+++ b/src/terrain_3d_material.h
@@ -47,8 +47,9 @@ class Terrain3DMaterial : public Resource {
// Material Features
WorldBackground _world_background = FLAT;
TextureFiltering _texture_filtering = LINEAR;
- bool _dual_scaling = false;
- bool _auto_shader = false;
+ bool _dual_scaling_enabled = false;
+ bool _color_map_enabled = true;
+ bool _auto_shader_enabled = false;
// Overlays
bool _show_region_grid = false;
@@ -116,10 +117,12 @@ class Terrain3DMaterial : public Resource {
WorldBackground get_world_background() const { return _world_background; }
void set_texture_filtering(const TextureFiltering p_filtering);
TextureFiltering get_texture_filtering() const { return _texture_filtering; }
- void set_auto_shader(const bool p_enabled);
- bool get_auto_shader() const { return _auto_shader; }
- void set_dual_scaling(const bool p_enabled);
- bool get_dual_scaling() const { return _dual_scaling; }
+ void set_auto_shader_enabled(const bool p_enabled);
+ bool get_auto_shader_enabled() const { return _auto_shader_enabled; }
+ void set_color_map_enabled(const bool p_enabled);
+ bool get_color_map_enabled() const { return _color_map_enabled; }
+ void set_dual_scaling_enabled(const bool p_enabled);
+ bool get_dual_scaling_enabled() const { return _dual_scaling_enabled; }
void set_shader_override_enabled(const bool p_enabled);
bool is_shader_override_enabled() const { return _shader_override_enabled; }
diff --git a/src/terrain_3d_region.cpp b/src/terrain_3d_region.cpp
index 5d7ca8ff4..6fd35d473 100644
--- a/src/terrain_3d_region.cpp
+++ b/src/terrain_3d_region.cpp
@@ -61,11 +61,11 @@ void Terrain3DRegion::set_map(const MapType p_map_type, const Ref &p_imag
Ref Terrain3DRegion::get_map(const MapType p_map_type) const {
switch (p_map_type) {
case TYPE_HEIGHT:
- return get_height_map();
+ return _height_map;
case TYPE_CONTROL:
- return get_control_map();
+ return _control_map;
case TYPE_COLOR:
- return get_color_map();
+ return _color_map;
default:
LOG(ERROR, "Requested map type ", p_map_type, ", is invalid");
return Ref();
@@ -149,6 +149,47 @@ void Terrain3DRegion::set_color_map(const Ref &p_map) {
_color_map = map;
}
+void Terrain3DRegion::clear_color_map() {
+ LOG(WARN, "Freeing color map for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)");
+ _color_map.unref();
+}
+
+void Terrain3DRegion::set_compressed_color_map(const Ref &p_map) {
+ SET_IF_DIFF(_compressed_color_map, p_map);
+ LOG(INFO, "Setting compressed color map for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)");
+ if (_region_size == 0 && p_map.is_valid()) {
+ set_region_size(p_map->get_width());
+ }
+ // If already initialized and receiving a new map
+ if (_compressed_color_map.is_valid() && _compressed_color_map != p_map) {
+ _modified = true;
+ }
+ _compressed_color_map = p_map;
+}
+
+void Terrain3DRegion::compress_color_map(const CompressMode p_compress_mode) {
+ if (_color_map.is_null() || _color_map->is_empty()) {
+ LOG(ERROR, "Color map is null or empty");
+ }
+ if (!IS_EDITOR) {
+ LOG(ERROR, "Cannot compress maps in export builds");
+ return;
+ }
+ if (p_compress_mode > COMPRESS_NONE && p_compress_mode <= COMPRESS_ASTC) {
+ LOG(MESG, "Compressing color map with ", COMPRESS_STR[p_compress_mode]);
+ _compressed_color_map = Image::create_from_data(_color_map->get_width(), _color_map->get_height(),
+ _color_map->has_mipmaps(), _color_map->get_format(), _color_map->get_data());
+ _compressed_color_map->copy_from(_color_map);
+ _compressed_color_map->compress_from_channels(get_image_compress_mode(p_compress_mode), Image::USED_CHANNELS_RGBA);
+ _modified = true;
+ }
+}
+
+void Terrain3DRegion::clear_compressed_color_map() {
+ LOG(WARN, "Freeing compressed color map for region: ", (_location.x != INT32_MAX) ? String(_location) : "(new)");
+ _compressed_color_map.unref();
+}
+
void Terrain3DRegion::sanitize_maps() {
if (_region_size == 0) { // blank region, no set_*_map has been called
LOG(ERROR, "Set region_size first");
@@ -250,7 +291,8 @@ void Terrain3DRegion::calc_height_range() {
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");
+ LOG(DEBUG, "Recalculated new height range: ", _height_range, " for region: ",
+ (_location.x != INT32_MAX) ? String(_location) : "(new)", ". Marking modified");
}
}
@@ -278,7 +320,7 @@ void Terrain3DRegion::set_location(const Vector2i &p_location) {
LOG(INFO, "Set location: ", p_location);
}
-Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) {
+Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit, const CompressMode p_color_compress_mode) {
// 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);
@@ -300,6 +342,10 @@ Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) {
LOG(MESG, "Writing", (p_16_bit) ? " 16-bit" : "", " region ", _location, " to ", get_path());
set_version(Terrain3DData::CURRENT_VERSION);
Error err = OK;
+ _compressed_color_map.unref();
+ if (IS_EDITOR && p_color_compress_mode != COMPRESS_NONE) {
+ compress_color_map(p_color_compress_mode);
+ }
if (p_16_bit) {
Ref original_map;
original_map.instantiate();
@@ -335,6 +381,7 @@ void Terrain3DRegion::set_data(const Dictionary &p_data) {
SET_IF_HAS(_height_map, "height_map");
SET_IF_HAS(_control_map, "control_map");
SET_IF_HAS(_color_map, "color_map");
+ SET_IF_HAS(_compressed_color_map, "compressed_color_map");
SET_IF_HAS(_instances, "instances");
}
@@ -351,6 +398,7 @@ Dictionary Terrain3DRegion::get_data() const {
dict["height_map"] = _height_map;
dict["control_map"] = _control_map;
dict["color_map"] = _color_map;
+ dict["compressed_color_map"] = _compressed_color_map;
dict["instances"] = _instances;
return dict;
}
@@ -380,6 +428,23 @@ Ref Terrain3DRegion::duplicate(const bool p_deep) {
return region;
}
+Image::CompressMode Terrain3DRegion::get_image_compress_mode(const CompressMode p_compress_mode) {
+ switch (p_compress_mode) {
+ case COMPRESS_S3TC:
+ return Image::COMPRESS_S3TC;
+ case COMPRESS_BPTC:
+ return Image::COMPRESS_BPTC;
+ case COMPRESS_ETC:
+ return Image::COMPRESS_ETC;
+ case COMPRESS_ETC2:
+ return Image::COMPRESS_ETC2;
+ case COMPRESS_ASTC:
+ return Image::COMPRESS_ASTC;
+ default:
+ return Image::COMPRESS_MAX;
+ }
+}
+
void Terrain3DRegion::dump(const bool verbose) const {
LOG(MESG, "Region: ", _location, ", version: ", vformat("%.2f", _version), ", size: ", _region_size,
", spacing: ", vformat("%.1f", _vertex_spacing), ", range: ", vformat("%.2v", _height_range),
@@ -423,6 +488,13 @@ void Terrain3DRegion::_bind_methods() {
BIND_ENUM_CONSTANT(TYPE_COLOR);
BIND_ENUM_CONSTANT(TYPE_MAX);
+ BIND_ENUM_CONSTANT(COMPRESS_NONE);
+ BIND_ENUM_CONSTANT(COMPRESS_S3TC);
+ BIND_ENUM_CONSTANT(COMPRESS_BPTC);
+ BIND_ENUM_CONSTANT(COMPRESS_ETC);
+ BIND_ENUM_CONSTANT(COMPRESS_ETC2);
+ BIND_ENUM_CONSTANT(COMPRESS_ASTC);
+
ClassDB::bind_method(D_METHOD("set_version", "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);
@@ -440,6 +512,12 @@ void Terrain3DRegion::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_control_map"), &Terrain3DRegion::get_control_map);
ClassDB::bind_method(D_METHOD("set_color_map", "map"), &Terrain3DRegion::set_color_map);
ClassDB::bind_method(D_METHOD("get_color_map"), &Terrain3DRegion::get_color_map);
+ ClassDB::bind_method(D_METHOD("clear_color_map"), &Terrain3DRegion::clear_color_map);
+ ClassDB::bind_method(D_METHOD("get_active_color_map"), &Terrain3DRegion::get_active_color_map);
+ ClassDB::bind_method(D_METHOD("set_compressed_color_map", "map"), &Terrain3DRegion::set_compressed_color_map);
+ ClassDB::bind_method(D_METHOD("get_compressed_color_map"), &Terrain3DRegion::get_compressed_color_map);
+ ClassDB::bind_method(D_METHOD("compress_color_map"), &Terrain3DRegion::compress_color_map);
+ ClassDB::bind_method(D_METHOD("clear_compressed_color_map"), &Terrain3DRegion::clear_compressed_color_map);
ClassDB::bind_method(D_METHOD("sanitize_maps"), &Terrain3DRegion::sanitize_maps);
ClassDB::bind_method(D_METHOD("sanitize_map", "map_type", "map"), &Terrain3DRegion::sanitize_map);
ClassDB::bind_method(D_METHOD("validate_map_size", "map"), &Terrain3DRegion::validate_map_size);
@@ -453,7 +531,7 @@ void Terrain3DRegion::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_instances", "instances"), &Terrain3DRegion::set_instances);
ClassDB::bind_method(D_METHOD("get_instances"), &Terrain3DRegion::get_instances);
- ClassDB::bind_method(D_METHOD("save", "path", "save_16_bit"), &Terrain3DRegion::save, DEFVAL(""), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("save", "path", "save_16_bit", "color_compress_mode"), &Terrain3DRegion::save, DEFVAL(""), DEFVAL(false), DEFVAL(COMPRESS_NONE));
ClassDB::bind_method(D_METHOD("set_deleted", "deleted"), &Terrain3DRegion::set_deleted);
ClassDB::bind_method(D_METHOD("is_deleted"), &Terrain3DRegion::is_deleted);
@@ -467,6 +545,7 @@ void Terrain3DRegion::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_data", "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));
+ ClassDB::bind_static_method("Terrain3DRegion", D_METHOD("get_image_compress_mode", "compress_mode"), &Terrain3DRegion::get_image_compress_mode);
ClassDB::bind_method(D_METHOD("dump", "verbose"), &Terrain3DRegion::dump, DEFVAL(false));
int ro_flags = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY;
@@ -477,6 +556,7 @@ void Terrain3DRegion::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "height_map", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_height_map", "get_height_map");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "control_map", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_control_map", "get_control_map");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_map", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_color_map", "get_color_map");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "compressed_color_map", PROPERTY_HINT_RESOURCE_TYPE, "Image", ro_flags), "set_compressed_color_map", "get_compressed_color_map");
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "instances", PROPERTY_HINT_NONE, "", ro_flags), "set_instances", "get_instances");
// Double-clicking a region .res file shows what's on disk, the defaults, not in memory. So these are hidden
diff --git a/src/terrain_3d_region.h b/src/terrain_3d_region.h
index 2b233480f..1b7a20dbf 100644
--- a/src/terrain_3d_region.h
+++ b/src/terrain_3d_region.h
@@ -3,6 +3,8 @@
#ifndef TERRAIN3D_REGION_CLASS_H
#define TERRAIN3D_REGION_CLASS_H
+#include
+
#include "constants.h"
#include "terrain_3d_util.h"
@@ -18,13 +20,6 @@ class Terrain3DRegion : public Resource {
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",
@@ -32,6 +27,13 @@ class Terrain3DRegion : public Resource {
"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 Color COLOR[] = {
COLOR_BLACK, // TYPE_HEIGHT
COLOR_CONTROL, // TYPE_CONTROL
@@ -39,6 +41,24 @@ class Terrain3DRegion : public Resource {
COLOR_NAN, // TYPE_MAX, unused just in case someone indexes the array
};
+ enum CompressMode {
+ COMPRESS_NONE,
+ COMPRESS_S3TC,
+ COMPRESS_BPTC,
+ COMPRESS_ETC,
+ COMPRESS_ETC2,
+ COMPRESS_ASTC,
+ };
+
+ static inline const char *COMPRESS_STR[] = {
+ "COMPRESS_NONE",
+ "COMPRESS_S3TC",
+ "COMPRESS_BPTC",
+ "COMPRESS_ETC",
+ "COMPRESS_ETC2",
+ "COMPRESS_ASTC",
+ };
+
private:
// Saved data
real_t _version = 0.8f; // Set to first version to ensure we always upgrades this
@@ -48,6 +68,8 @@ class Terrain3DRegion : public Resource {
Ref _height_map;
Ref _control_map;
Ref _color_map;
+ Ref _compressed_color_map;
+
// Instancer
Dictionary _instances; // Meshes{int} -> Cells{v2i} -> [ Transform3D, Color, Modified ]
real_t _vertex_spacing = 1.f; // Spacing that instancer transforms are currently scaled by.
@@ -79,6 +101,12 @@ class Terrain3DRegion : public Resource {
Ref get_control_map() const { return _control_map; }
void set_color_map(const Ref &p_map);
Ref get_color_map() const { return _color_map; }
+ void clear_color_map();
+ Ref get_active_color_map() const;
+ void set_compressed_color_map(const Ref &p_map);
+ Ref get_compressed_color_map() const { return _compressed_color_map; }
+ void compress_color_map(const CompressMode p_compress_mode);
+ void clear_compressed_color_map();
void sanitize_maps();
Ref sanitize_map(const MapType p_map_type, const Ref &p_map) const;
bool validate_map_size(const Ref &p_map) const;
@@ -106,18 +134,22 @@ class Terrain3DRegion : public Resource {
Vector2i get_location() const { return _location; }
// File I/O
- Error save(const String &p_path = "", const bool p_16_bit = false);
+ Error save(const String &p_path = "", const bool p_16_bit = false, const CompressMode p_color_compress_mode = COMPRESS_NONE);
// Utility
void set_data(const Dictionary &p_data);
Dictionary get_data() const;
Ref duplicate(const bool p_deep = false);
+ static Image::CompressMode get_image_compress_mode(const CompressMode p_compress_mode);
+
void dump(const bool verbose = false) const;
protected:
static void _bind_methods();
};
+using CompressMode = Terrain3DRegion::CompressMode;
+VARIANT_ENUM_CAST(Terrain3DRegion::CompressMode);
using MapType = Terrain3DRegion::MapType;
VARIANT_ENUM_CAST(Terrain3DRegion::MapType);
constexpr Terrain3DRegion::MapType TYPE_HEIGHT = Terrain3DRegion::MapType::TYPE_HEIGHT;
@@ -151,4 +183,11 @@ inline void Terrain3DRegion::update_heights(const Vector2 &p_low_high) {
}
}
+inline Ref Terrain3DRegion::get_active_color_map() const {
+ if (!IS_EDITOR && _compressed_color_map.is_valid()) {
+ return _compressed_color_map;
+ }
+ return _color_map;
+}
+
#endif // TERRAIN3D_REGION_CLASS_H