Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
81 changes: 39 additions & 42 deletions project/addons/terrain_3d/src/asset_dock.gd
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,8 @@ func _on_godot_window_entered() -> void:
func set_selected_by_asset_id(p_id: int) -> void:
search_box.text = ""
_on_search_text_changed()
current_list.set_selected_id(p_id)
current_list.selected_asset_ids = [p_id]
current_list.update_selection()


func _on_search_text_changed() -> void:
Expand All @@ -221,7 +222,7 @@ func _on_search_text_changed() -> void:
mesh_list.search_text = search_box.text
texture_list.search_text = search_box.text
current_list.update_asset_list()
current_list.set_selected_id(0)
current_list.update_selection()


func _on_search_button_pressed() -> void:
Expand Down Expand Up @@ -369,7 +370,7 @@ class ListContainer extends Container:
var plugin: EditorPlugin
var type := Terrain3DAssets.TYPE_TEXTURE
var entries: Array[ListEntry]
var selected_id: int = 0
var selected_asset_ids: Array[int] = [0]
var height: float = 0.
var width: float = 90.
var focus_style: StyleBox
Expand Down Expand Up @@ -416,10 +417,11 @@ class ListContainer extends Container:
var mesh_count: int = t.assets.get_mesh_count()
for i in mesh_count:
var mesh: Terrain3DMeshAsset = t.assets.get_mesh_asset(i)
add_item(mesh)
if mesh:
add_item(mesh)
if mesh_count < Terrain3DAssets.MAX_MESHES:
add_item()
set_selected_id(selected_id)
update_selection()


func add_item(p_resource: Resource = null) -> void:
Expand Down Expand Up @@ -451,49 +453,44 @@ class ListContainer extends Container:

func set_selected_after_swap(p_type: Terrain3DAssets.AssetType, p_old_id: int, p_new_id: int) -> void:
EditorInterface.mark_scene_as_unsaved()
set_selected_id(clamp(p_new_id, 0, entries.size() - 2))
# Select the new id
clicked_id(MOUSE_BUTTON_LEFT, p_new_id)
# deselect the old id
clicked_id(MOUSE_BUTTON_LEFT, p_old_id)
update_selection()


func clicked_id(p_id: int) -> void:
func clicked_id(p_mouse_button: MouseButton, p_id: int) -> void:
# Select Tool if clicking an asset
plugin.select_terrain()
if type == Terrain3DAssets.TYPE_TEXTURE and \
not plugin.editor.get_tool() in [ Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS ]:
plugin.ui.toolbar.change_tool("PaintTexture")
elif type == Terrain3DAssets.TYPE_MESH and plugin.editor.get_tool() != Terrain3DEditor.INSTANCER:
plugin.ui.toolbar.change_tool("InstanceMeshes")
set_selected_id(p_id)

if type == Terrain3DAssets.TYPE_TEXTURE:
if not plugin.editor.get_tool() in [ Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS ]:
plugin.ui.toolbar.change_tool("PaintTexture")
var res: Terrain3DTextureAsset = entries[p_id].resource
selected_asset_ids = [ res.get_id() ]
elif type == Terrain3DAssets.TYPE_MESH:
if plugin.editor.get_tool() != Terrain3DEditor.INSTANCER:
plugin.ui.toolbar.change_tool("InstanceMeshes")
var res: Terrain3DMeshAsset = entries[p_id].resource

if selected_asset_ids.has(res.get_id()):
if not p_mouse_button == MOUSE_BUTTON_RIGHT:
selected_asset_ids.erase(res.get_id())
else:
selected_asset_ids.push_back(res.get_id())
if selected_asset_ids.is_empty():
selected_asset_ids = [0]
update_selection()

func set_selected_id(p_id: int) -> void:
# "Add new" is the final entry only when search box is blank
var max_id: int = max(0, entries.size() - (1 if search_text else 2))
if plugin.debug:
print("Terrain3DListContainer ", name, ": set_selected_id: ", selected_id, " to ", clamp(p_id, 0, max_id))
selected_id = clamp(p_id, 0, max_id)

func update_selection() -> void:
for i in entries.size():
var entry: ListEntry = entries[i]
entry.set_selected(i == selected_id)
if entry.resource:
entry.set_selected(entry.resource.get_id() in selected_asset_ids)
plugin.ui._on_setting_changed()


func get_selected_asset_id() -> int:
# "Add new" is the final entry only when search box is blank
var max_id: int = max(0, entries.size() - (1 if search_text else 2))
var id: int = clamp(selected_id, 0, max_id)
if plugin.debug:
print("Terrain3DListContainer ", name, ": get_selected_asset_id: selected_id: ", selected_id, ", clamped: ", id, ", entries: ", entries.size())
if id >= entries.size():
return 0
var res: Resource = entries[id].resource
if not res:
return 0
if type == Terrain3DAssets.AssetType.TYPE_MESH:
return (res as Terrain3DMeshAsset).id
else:
return (res as Terrain3DTextureAsset).id


func _on_resource_inspected(p_resource: Resource) -> void:
await get_tree().process_frame
EditorInterface.edit_resource(p_resource)
Expand Down Expand Up @@ -582,7 +579,7 @@ class ListContainer extends Container:

class ListEntry extends MarginContainer:
signal hovered()
signal clicked()
signal clicked(mouse_button: MouseButton)
signal changed(resource: Resource)
signal inspected(resource: Resource)

Expand Down Expand Up @@ -839,7 +836,7 @@ class ListEntry extends MarginContainer:
set_edited_resource(Terrain3DMeshAsset.new(), false)
_on_edit()
else:
emit_signal("clicked")
clicked.emit(MOUSE_BUTTON_LEFT)
MOUSE_BUTTON_RIGHT:
if resource:
_on_edit()
Expand Down Expand Up @@ -900,7 +897,7 @@ class ListEntry extends MarginContainer:
button_clear.set_visible(resource != null)

queue_redraw()
if not p_no_signal:
if resource and not p_no_signal:
emit_signal("changed", resource)


Expand Down Expand Up @@ -930,7 +927,7 @@ class ListEntry extends MarginContainer:


func _on_edit() -> void:
emit_signal("clicked")
clicked.emit(MOUSE_BUTTON_RIGHT)
emit_signal("inspected", resource)


Expand Down
77 changes: 37 additions & 40 deletions project/addons/terrain_3d/src/asset_dock_45.gd
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,8 @@ func update_layout() -> void:
func set_selected_by_asset_id(p_id: int) -> void:
search_box.text = ""
_on_search_text_changed()
current_list.set_selected_id(p_id)
current_list.selected_asset_ids = [p_id]
current_list.update_selection()


func _on_search_text_changed() -> void:
Expand All @@ -272,7 +273,8 @@ func _on_search_text_changed() -> void:
mesh_list.search_text = search_box.text
texture_list.search_text = search_box.text
current_list.update_asset_list()
current_list.set_selected_id(0)
current_list.update_selection()



func _on_search_button_pressed() -> void:
Expand Down Expand Up @@ -515,7 +517,7 @@ class ListContainer extends Container:
var plugin: EditorPlugin
var type := Terrain3DAssets.TYPE_TEXTURE
var entries: Array[ListEntry]
var selected_id: int = 0
var selected_asset_ids: Array[int] = [0]
var height: float = 0.
var width: float = 90.
var focus_style: StyleBox
Expand Down Expand Up @@ -565,7 +567,7 @@ class ListContainer extends Container:
add_item(mesh)
if mesh_count < Terrain3DAssets.MAX_MESHES:
add_item()
set_selected_id(selected_id)
update_selection()


func add_item(p_resource: Resource = null) -> void:
Expand Down Expand Up @@ -597,49 +599,44 @@ class ListContainer extends Container:

func set_selected_after_swap(p_type: Terrain3DAssets.AssetType, p_old_id: int, p_new_id: int) -> void:
EditorInterface.mark_scene_as_unsaved()
set_selected_id(clamp(p_new_id, 0, entries.size() - 2))
# Select the new id
clicked_id(MOUSE_BUTTON_LEFT, p_new_id)
# deselect the old id
clicked_id(MOUSE_BUTTON_LEFT, p_old_id)
update_selection()


func clicked_id(p_id: int) -> void:
func clicked_id(p_mouse_button: MouseButton, p_id: int) -> void:
# Select Tool if clicking an asset
plugin.select_terrain()
if type == Terrain3DAssets.TYPE_TEXTURE and \
not plugin.editor.get_tool() in [ Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS ]:
plugin.ui.toolbar.change_tool("PaintTexture")
elif type == Terrain3DAssets.TYPE_MESH and plugin.editor.get_tool() != Terrain3DEditor.INSTANCER:
plugin.ui.toolbar.change_tool("InstanceMeshes")
set_selected_id(p_id)
if type == Terrain3DAssets.TYPE_TEXTURE:
if not plugin.editor.get_tool() in [ Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS ]:
plugin.ui.toolbar.change_tool("PaintTexture")
var res: Terrain3DTextureAsset = entries[p_id].resource
selected_asset_ids = [ res.get_id() ]
elif type == Terrain3DAssets.TYPE_MESH:
if plugin.editor.get_tool() != Terrain3DEditor.INSTANCER:
plugin.ui.toolbar.change_tool("InstanceMeshes")
var res: Terrain3DMeshAsset = entries[p_id].resource

if selected_asset_ids.has(res.get_id()):
if not p_mouse_button == MOUSE_BUTTON_RIGHT:
selected_asset_ids.erase(res.get_id())
else:
selected_asset_ids.push_back(res.get_id())
if selected_asset_ids.is_empty():
selected_asset_ids = [0]
update_selection()


func set_selected_id(p_id: int) -> void:
# "Add new" is the final entry only when search box is blank
var max_id: int = max(0, entries.size() - (1 if search_text else 2))
if plugin.debug:
print("Terrain3DListContainer ", name, ": set_selected_id: ", selected_id, " to ", clamp(p_id, 0, max_id))
selected_id = clamp(p_id, 0, max_id)
func update_selection() -> void:
for i in entries.size():
var entry: ListEntry = entries[i]
entry.set_selected(i == selected_id)
if entry.resource:
entry.set_selected(entry.resource.get_id() in selected_asset_ids)
plugin.ui._on_setting_changed()


func get_selected_asset_id() -> int:
# "Add new" is the final entry only when search box is blank
var max_id: int = max(0, entries.size() - (1 if search_text else 2))
var id: int = clamp(selected_id, 0, max_id)
if plugin.debug:
print("Terrain3DListContainer ", name, ": get_selected_asset_id: selected_id: ", selected_id, ", clamped: ", id, ", entries: ", entries.size())
if id >= entries.size():
return 0
var res: Resource = entries[id].resource
if not res:
return 0
if type == Terrain3DAssets.AssetType.TYPE_MESH:
return (res as Terrain3DMeshAsset).id
else:
return (res as Terrain3DTextureAsset).id


func _on_resource_inspected(p_resource: Resource) -> void:
await get_tree().process_frame
EditorInterface.edit_resource(p_resource)
Expand Down Expand Up @@ -727,7 +724,7 @@ class ListContainer extends Container:

class ListEntry extends MarginContainer:
signal hovered()
signal clicked()
signal clicked(mouse_button: MouseButton)
signal changed(resource: Resource)
signal inspected(resource: Resource)

Expand Down Expand Up @@ -984,7 +981,7 @@ class ListEntry extends MarginContainer:
set_edited_resource(Terrain3DMeshAsset.new(), false)
_on_edit()
else:
emit_signal("clicked")
clicked.emit(MOUSE_BUTTON_LEFT)
MOUSE_BUTTON_RIGHT:
if resource:
_on_edit()
Expand Down Expand Up @@ -1025,7 +1022,7 @@ class ListEntry extends MarginContainer:
if resource is Terrain3DMeshAsset:
res.id = resource.id
set_edited_resource(res, false)
emit_signal("clicked")
clicked.emit(MOUSE_BUTTON_RIGHT)
emit_signal("inspected", resource)


Expand Down Expand Up @@ -1072,7 +1069,7 @@ class ListEntry extends MarginContainer:


func _on_edit() -> void:
emit_signal("clicked")
clicked.emit(MOUSE_BUTTON_RIGHT)
emit_signal("inspected", resource)


Expand Down
4 changes: 2 additions & 2 deletions project/addons/terrain_3d/src/ui.gd
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,9 @@ func _on_setting_changed(p_setting: Variant = null) -> void:
if not plugin.asset_dock: # Skip function if not _ready()
return
brush_data = tool_settings.get_settings()
brush_data["asset_id"] = plugin.asset_dock.current_list.get_selected_asset_id()
brush_data["asset_ids"] = plugin.asset_dock.current_list.selected_asset_ids
if plugin.debug:
print("Terrain3DUI: _on_setting_changed: selected resource ID: ", brush_data["asset_id"])
print("Terrain3DUI: _on_setting_changed: selected resource ID: ", brush_data["asset_ids"])
if plugin.editor:
plugin.editor.set_brush_data(brush_data)
inverted_input = brush_data.get("invert", false)
Expand Down
37 changes: 35 additions & 2 deletions src/terrain_3d_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_
bool enable_texture = _brush_data["enable_texture"];
bool texture_filter = _brush_data["texture_filter"];
int margin = _brush_data["margin"];
int asset_id = _brush_data["asset_id"];
Array asset_ids = _brush_data["asset_ids"];

Vector2 slope_range = _brush_data["slope"];
bool enable_angle = _brush_data["enable_angle"];
Expand Down Expand Up @@ -326,6 +326,10 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_
if (!data->is_in_slope(brush_global_position, slope_range)) {
continue;
}
if (!asset_ids.size()) {
continue;
}
const int asset_id = asset_ids[0];
switch (_operation) {
// Base Paint
case REPLACE: {
Expand Down Expand Up @@ -487,6 +491,10 @@ void Terrain3DEditor::_operate_map(const Vector3 &p_global_position, const real_
if (!cmap) {
continue;
}
if (!asset_ids.size()) {
continue;
}
const int asset_id = asset_ids[0];
float src_ctrl = cmap->get_pixelv(map_pixel_position).r; // Must be float
int tex_id = (get_blend(src_ctrl) > 110 - margin) ? get_overlay(src_ctrl) : get_base(src_ctrl);
if (tex_id != asset_id) {
Expand Down Expand Up @@ -872,7 +880,6 @@ void Terrain3DEditor::set_brush_data(const Dictionary &p_data) {

_brush_data["enable_texture"] = p_data.get("enable_texture", true);
_brush_data["texture_filter"] = p_data.get("texture_filter", false);
_brush_data["asset_id"] = CLAMP(int(p_data.get("asset_id", 0)), 0, ((_tool == INSTANCER) ? Terrain3DAssets::MAX_MESHES : Terrain3DAssets::MAX_TEXTURES) - 1);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This defaulted to id 0. Now texture painting is ignored if there is no asset_ids. Perhaps we should insert [0] if it doesn't exist.

Also it doesn't sanitize the brush data. Sanitization was limited only to 0-32/256 before instead of the actual count. We could check _terrain->get_assets()->get_texture_count() and _terrain->get_assets()->get_mesh_count().

@aidandavey aidandavey Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have implemented sanitation and need to test it

_brush_data["margin"] = CLAMP(int(p_data.get("margin", 0)), -100, 100);

_brush_data["enable_angle"] = p_data.get("enable_angle", true);
Expand All @@ -888,6 +895,32 @@ void Terrain3DEditor::set_brush_data(const Dictionary &p_data) {
_brush_data["brush_spin_speed"] = CLAMP(real_t(p_data.get("brush_spin_speed", 0.f)), 0.f, 1.f);
_brush_data["gradient_points"] = p_data.get("gradient_points", PackedVector3Array());

// Get and sanitize asset ids. If invalid, remove from list and log error
TypedArray<int> asset_ids = p_data.get("asset_ids", TypedArray<int>());

if (_tool == Tool::TEXTURE) {
TypedArray<int> valid_ids;
int max_asset_id = -1;
if (_tool == Tool::TEXTURE) {
max_asset_id = _terrain->get_assets()->get_texture_count();
} else if (_tool == Tool::INSTANCER) {
max_asset_id = _terrain->get_assets()->get_mesh_count();
}
for (const int id : asset_ids) {
if (id < max_asset_id) {
valid_ids.push_back(id);
} else {
LOG(ERROR, "Brush data contains invalid asset id: ", id, ". Max asset id is: ", max_asset_id - 1);
}
}
asset_ids = valid_ids;
}
// Default to first asset id if none provided
if (asset_ids.is_empty()) {
asset_ids.push_back(0);
}
_brush_data["asset_ids"] = asset_ids;

Util::print_dict("set_brush_data() Santized brush data:", _brush_data, EXTREME);
}

Expand Down
Loading
Loading