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