Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/doc_classes/Terrain3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
</member>
<member name="color_compression_mode" type="ColorCompressMode" setter="set_color_compression_mode" getter="get_color_compression_mode" default="None">
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.
</member>
<member name="free_uncompressed_color_maps" type="bool" setter="set_free_uncompressed_color_maps" getter="get_free_uncompressed_color_maps" default="true">
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.
</member>
<member name="show_autoshader" type="bool" setter="set_show_autoshader" getter="get_show_autoshader" default="false">
Displays the area designated for use by the autoshader, which shows materials based upon slope.
Alias for [member Terrain3DMaterial.show_autoshader].
Expand Down
4 changes: 3 additions & 1 deletion doc/doc_classes/Terrain3DData.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
<method name="get_color_maps_rid" qualifiers="const">
<return type="RID" />
<description>
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.
</description>
</method>
<method name="get_control" qualifiers="const">
Expand Down Expand Up @@ -431,10 +431,12 @@
<param index="0" name="region_location" type="Vector2i" />
<param index="1" name="directory" type="String" />
<param index="2" name="save_16_bit" type="bool" default="false" />
<param index="3" name="color_compression_mode" type="Image::CompressMode" default="Image::COMPRESS_MAX" />
<description>
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.
</description>
</method>
<method name="set_color">
Expand Down
11 changes: 9 additions & 2 deletions doc/doc_classes/Terrain3DRegion.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<return type="Image" />
<param index="0" name="map_type" type="int" enum="Terrain3DRegion.MapType" />
<description>
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.
</description>
</method>
<method name="get_maps" qualifiers="const">
Expand All @@ -58,18 +58,22 @@
</method>
<method name="sanitize_maps">
<return type="void" />
<param index="0" name="free_uncompressed_color_maps" type="bool" />
<description>
Sanitizes all map types. See [method sanitize_map].
- free_uncompressed_color_maps - if true, the uncompressed color map will be freed instead of sanitized.
</description>
</method>
<method name="save">
<return type="int" enum="Error" />
<param index="0" name="path" type="String" default="&quot;&quot;" />
<param index="1" name="save_16_bit" type="bool" default="false" />
<param index="2" name="color_compression_mode" type="Image::CompressMode" default="Image::COMPRESS_MAX" />
<description>
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.
</description>
</method>
<method name="set_data">
Expand Down Expand Up @@ -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.
</member>
<member name="compressed_color_map" type="Image" setter="set_compressed_color_map" getter="get_compressed_color_map">
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.
</member>
<member name="control_map" type="Image" setter="set_control_map" getter="get_control_map">
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.
Expand Down
4 changes: 4 additions & 0 deletions project/addons/terrain_3d/src/editor_plugin.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
46 changes: 46 additions & 0 deletions project/addons/terrain_3d/src/export_plugin.gd
Original file line number Diff line number Diff line change
@@ -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
Comment thread
stan4dbunny marked this conversation as resolved.


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


Comment thread
stan4dbunny marked this conversation as resolved.
func _get_customization_configuration_hash() -> int:
return hash(_hash)
1 change: 1 addition & 0 deletions project/addons/terrain_3d/src/export_plugin.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://bfl6jfwv15i0f
19 changes: 18 additions & 1 deletion src/terrain_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,15 @@ void Terrain3D::set_save_16_bit(const bool p_enabled) {
}
}

void Terrain3D::set_color_compress_mode(const CompressMode p_color_compress_mode) {
SET_IF_DIFF(_color_compress_mode, p_color_compress_mode);
LOG(INFO, "Setting compression mode for color maps: ", _color_compress_mode);
TypedArray<Terrain3DRegion> regions = _data->get_regions_active();
for (Ref<Terrain3DRegion> 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);
Expand Down Expand Up @@ -1194,6 +1203,11 @@ 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_color_compress_mode", "compress_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_free_uncompressed_color_maps"), &Terrain3D::set_free_uncompressed_color_maps);
ClassDB::bind_method(D_METHOD("get_free_uncompressed_color_maps"), &Terrain3D::get_free_uncompressed_color_maps);
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);
Expand Down Expand Up @@ -1319,10 +1333,13 @@ 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::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_PROPERTY(PropertyInfo(Variant::BOOL, "free_uncompressed_color_maps"), "set_free_uncompressed_color_maps", "get_free_uncompressed_color_maps");

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");
Expand Down
7 changes: 7 additions & 0 deletions src/terrain_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ class Terrain3D : public Node3D {
// Regions
RegionSize _region_size = SIZE_256;
bool _save_16_bit = false;
CompressMode _color_compress_mode = Terrain3DRegion::COMPRESS_NONE;
bool _free_uncompressed_color_maps = true;
real_t _label_distance = 0.f;
int _label_size = 48;

Expand Down Expand Up @@ -169,6 +171,11 @@ 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_color_compress_mode(const CompressMode p_compress_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_free_uncompressed_color_maps(const bool p_free_uncompressed_color_maps) { _free_uncompressed_color_maps = p_free_uncompressed_color_maps; }
bool get_free_uncompressed_color_maps() const { return _free_uncompressed_color_maps; };
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);
Expand Down
34 changes: 19 additions & 15 deletions src/terrain_3d_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ void Terrain3DData::change_region_size(int p_new_size) {
new_region->set_region_size(p_new_size);
new_region->set_vertex_spacing(_vertex_spacing);
new_region->set_modified(true);
new_region->sanitize_maps();
new_region->sanitize_maps(false);

// Copy current data from current into new region, up to new region size
Rect2i area;
Expand Down Expand Up @@ -247,7 +247,11 @@ Error Terrain3DData::add_region(const Ref<Terrain3DRegion> &p_region, const bool
-REGION_MAP_SIZE / 2, " to ", REGION_MAP_SIZE / 2 - 1);
return FAILED;
}
p_region->sanitize_maps();
if (!IS_EDITOR && _terrain->get_color_compress_mode() != Terrain3DRegion::COMPRESS_NONE) {
p_region->sanitize_maps(_terrain->get_free_uncompressed_color_maps());
} else {
p_region->sanitize_maps(false);
}
p_region->set_deleted(false);
if (!_region_locations.has(region_loc)) {
_region_locations.push_back(region_loc);
Expand Down Expand Up @@ -304,15 +308,15 @@ void Terrain3DData::save_directory(const String &p_dir) {
LOG(INFO, "Saving data files to ", p_dir);
Array locations = _regions.keys();
for (const Vector2i &region_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();
}
}

// 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<Terrain3DRegion> region = get_region(p_region_loc);
if (region.is_null()) {
LOG(ERROR, "No region found at: ", p_region_loc);
Expand Down Expand Up @@ -341,7 +345,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, ")");
}
Expand Down Expand Up @@ -369,7 +373,7 @@ void Terrain3DData::load_directory(const String &p_dir) {
LOG(ERROR, "Cannot get region location from file name: ", fname);
continue;
}
Ref<Terrain3DRegion> region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE);
Ref<Terrain3DRegion> region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_REPLACE);
Comment thread
stan4dbunny marked this conversation as resolved.
if (region.is_null()) {
LOG(ERROR, "Cannot load region at ", path);
continue;
Expand Down Expand Up @@ -400,7 +404,7 @@ void Terrain3DData::load_region(const Vector2i &p_region_loc, const String &p_di
LOG(ERROR, "File ", path, " doesn't exist");
return;
}
Ref<Terrain3DRegion> region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_IGNORE);
Ref<Terrain3DRegion> region = ResourceLoader::get_singleton()->load(path, "Terrain3DRegion", ResourceLoader::CACHE_MODE_REPLACE);
if (region.is_null()) {
LOG(ERROR, "Cannot load region at ", path);
return;
Expand Down Expand Up @@ -448,7 +452,7 @@ void Terrain3DData::update_maps(const MapType p_map_type, const bool p_all_regio
for (const Vector2i &region_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();
}
}
Expand Down Expand Up @@ -524,7 +528,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();
Expand All @@ -540,14 +544,14 @@ 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 &region_loc : _region_locations) {
const Terrain3DRegion *region = get_region_ptr(region_loc);
if (region) {
_color_maps.push_back(region->get_color_map());
_color_maps.push_back(region->get_map(TYPE_COLOR));
}
}
_generated_color_maps.create(_color_maps);
Expand Down Expand Up @@ -575,14 +579,14 @@ 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);
_generated_color_maps.update(region->get_map(TYPE_COLOR), 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);
_generated_color_maps.update(region->get_map(TYPE_COLOR), region_id);
LOG(DEBUG, "Emitting height_maps_changed");
emit_signal("height_maps_changed");
LOG(DEBUG, "Emitting control_maps_changed");
Expand Down Expand Up @@ -967,7 +971,7 @@ void Terrain3DData::import_images(const TypedArray<Image> &p_images, const Vecto
region->set_map(static_cast<MapType>(i), img_slice);
}
}
region->sanitize_maps();
region->sanitize_maps(false);
} // for x < slices_width
} // for y < slices_height
update_maps(TYPE_MAX, true, false);
Expand Down Expand Up @@ -1178,7 +1182,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));

Expand Down
Loading