From a9ce5292de80afabb08c85e6fb2984b554dfa348 Mon Sep 17 00:00:00 2001 From: aidandavey <71520018+aidandavey@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:27:04 +0100 Subject: [PATCH] Add multiselect to asset dock --- project/addons/terrain_3d/src/asset_dock.gd | 87 ++++-- project/addons/terrain_3d/src/ui.gd | 10 +- src/terrain_3d_instancer.cpp | 329 +++++++++++--------- 3 files changed, 241 insertions(+), 185 deletions(-) diff --git a/project/addons/terrain_3d/src/asset_dock.gd b/project/addons/terrain_3d/src/asset_dock.gd index 58c67c16a..058d56088 100644 --- a/project/addons/terrain_3d/src/asset_dock.gd +++ b/project/addons/terrain_3d/src/asset_dock.gd @@ -270,7 +270,8 @@ func _on_textures_pressed() -> void: mesh_list.visible = false textures_btn.button_pressed = true meshes_btn.button_pressed = false - texture_list.set_selected_id(texture_list.selected_id) + #texture_list.update_selected_entries() + texture_list.set_selected_ids(texture_list.selected_ids) if plugin.is_terrain_valid(): EditorInterface.edit_node(plugin.terrain) save_editor_settings() @@ -283,7 +284,8 @@ func _on_meshes_pressed() -> void: texture_list.visible = false meshes_btn.button_pressed = true textures_btn.button_pressed = false - mesh_list.set_selected_id(mesh_list.selected_id) + #mesh_list.update_selected_entries() + mesh_list.set_selected_ids(mesh_list.selected_ids) if plugin.is_terrain_valid(): EditorInterface.edit_node(plugin.terrain) update_thumbnails() @@ -312,7 +314,7 @@ func update_assets() -> void: plugin.terrain.assets.meshes_changed.connect(mesh_list.update_asset_list) _current_list.update_asset_list() - + ## Window Management @@ -436,7 +438,7 @@ class ListContainer extends Container: var plugin: EditorPlugin var type := Terrain3DAssets.TYPE_TEXTURE var entries: Array[ListEntry] - var selected_id: int = 0 + var selected_ids: Dictionary = {} var height: float = 0 var width: float = 83 var focus_style: StyleBox @@ -445,6 +447,7 @@ class ListContainer extends Container: func _ready() -> void: set_v_size_flags(SIZE_EXPAND_FILL) set_h_size_flags(SIZE_EXPAND_FILL) + set_id_selected(0, true) func clear() -> void: @@ -471,22 +474,57 @@ class ListContainer extends Container: if type == Terrain3DAssets.TYPE_TEXTURE: var texture_count: int = t.assets.get_texture_count() - for i in texture_count: + for i: int in texture_count: var texture: Terrain3DTextureAsset = t.assets.get_texture(i) add_item(texture) if texture_count < Terrain3DAssets.MAX_TEXTURES: add_item() + if selected_ids.size() > texture_count or selected_ids.size() == 0: + set_id_selected(0, true) else: var mesh_count: int = t.assets.get_mesh_count() - for i in mesh_count: + for i: int in mesh_count: var mesh: Terrain3DMeshAsset = t.assets.get_mesh_asset(i) add_item(mesh, t.assets) if mesh_count < Terrain3DAssets.MAX_MESHES: add_item() - if selected_id >= mesh_count or selected_id < 0: - set_selected_id(0) - + if selected_ids.size() > mesh_count or selected_ids.size() == 0: + set_id_selected(0, true) + + update_selected_entries() + + + func update_selected_entries(): + for i: int in range(entries.size()): + var entry: ListEntry = entries[i] + entry.set_selected(i in selected_ids) + plugin.ui._on_setting_changed() + + func set_id_selected(p_id: int, p_clear_previous_selection: bool = false): + if type == Terrain3DAssets.TYPE_TEXTURE: + selected_ids.clear() + selected_ids[p_id] = p_id + update_selected_entries() + return + + if selected_ids.has(p_id): + set_id_deselected(p_id) + else: + if p_clear_previous_selection: + selected_ids.clear() + selected_ids[p_id] = p_id + update_selected_entries() + + + func set_id_deselected(p_id: int): + # Always keep at least one asset selected + if selected_ids.size() > 1: + if selected_ids.has(p_id): + selected_ids.erase(p_id) + update_selected_entries() + + func add_item(p_resource: Resource = null, p_assets: Terrain3DAssets = null) -> void: var entry: ListEntry = ListEntry.new() entry.focus_style = focus_style @@ -494,7 +532,7 @@ class ListContainer extends Container: entry.set_edited_resource(p_resource) entry.hovered.connect(_on_resource_hovered.bind(id)) - entry.selected.connect(set_selected_id.bind(id)) + entry.selected.connect(set_id_selected.bind(id)) entry.inspected.connect(_on_resource_inspected) entry.changed.connect(_on_resource_changed.bind(id)) entry.type = type @@ -502,28 +540,16 @@ class ListContainer extends Container: add_child(entry) entries.push_back(entry) - if p_resource: - entry.set_selected(id == selected_id) - if not p_resource.id_changed.is_connected(set_selected_after_swap): - p_resource.id_changed.connect(set_selected_after_swap) - func _on_resource_hovered(p_id: int): if type == Terrain3DAssets.TYPE_MESH: if plugin.terrain: plugin.terrain.assets.create_mesh_thumbnails(p_id) - - func set_selected_after_swap(p_type: Terrain3DAssets.AssetType, p_old_id: int, p_new_id: int) -> void: - set_selected_id(clamp(p_new_id, 0, entries.size() - 2)) - - func set_selected_id(p_id: int) -> void: - selected_id = p_id - - for i in entries.size(): - var entry: ListEntry = entries[i] - entry.set_selected(i == selected_id) + func set_selected_ids(p_selected_ids: Dictionary) -> void: + selected_ids = p_selected_ids + update_selected_entries() plugin.select_terrain() @@ -584,11 +610,11 @@ class ListContainer extends Container: var last_offset: int = 2 if p_id == entries.size()-2: last_offset = 3 - set_selected_id(clamp(selected_id, 0, entries.size() - last_offset)) + set_id_deselected(p_id) - func get_selected_id() -> int: - return selected_id + func get_selected_ids() -> Dictionary: + return selected_ids func set_entry_width(value: float) -> void: @@ -709,7 +735,7 @@ class ListEntry extends VBoxContainer: button_clear.set_custom_minimum_size(icon_size) button_clear.set_h_size_flags(Control.SIZE_SHRINK_END) button_clear.set_visible(resource != null) - button_clear.mouse_filter = Control.MOUSE_FILTER_PASS + button_clear.mouse_filter = Control.MOUSE_FILTER_STOP button_clear.pressed.connect(clear) button_row.add_child(button_clear) @@ -841,7 +867,6 @@ class ListEntry extends VBoxContainer: emit_signal("inspected", resource) - func set_edited_resource(p_res: Resource, p_no_signal: bool = true) -> void: resource = p_res if resource: @@ -866,7 +891,7 @@ class ListEntry extends VBoxContainer: func set_selected(value: bool) -> void: is_selected = value queue_redraw() - + func clear() -> void: if resource: diff --git a/project/addons/terrain_3d/src/ui.gd b/project/addons/terrain_3d/src/ui.gd index 7c4d74065..35328ef06 100644 --- a/project/addons/terrain_3d/src/ui.gd +++ b/project/addons/terrain_3d/src/ui.gd @@ -275,7 +275,15 @@ 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.get_current_list().get_selected_id() + + # Set asset_id: int for texture painting + + brush_data["asset_id"] = plugin.asset_dock.texture_list.get_selected_ids().values()[0] + + # Set asset_ids: Array for instancer + brush_data["asset_ids"] = plugin.asset_dock.mesh_list.get_selected_ids() + + if plugin.editor: plugin.editor.set_brush_data(brush_data) inverted_input = brush_data.get("invert", false) diff --git a/src/terrain_3d_instancer.cpp b/src/terrain_3d_instancer.cpp index 451858184..8361d87fe 100644 --- a/src/terrain_3d_instancer.cpp +++ b/src/terrain_3d_instancer.cpp @@ -435,28 +435,18 @@ void Terrain3DInstancer::clear_by_region(const Ref &p_region, c void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const Dictionary &p_params) { IS_DATA_INIT_MESG("Instancer isn't initialized.", VOID); - int mesh_id = p_params.get("asset_id", 0); - if (mesh_id < 0 || mesh_id >= _terrain->get_assets()->get_mesh_count()) { - LOG(ERROR, "Mesh ID out of range: ", mesh_id, ", valid: 0 to ", _terrain->get_assets()->get_mesh_count() - 1); + Dictionary mesh_ids = p_params.get("asset_ids", Dictionary()); + + if (mesh_ids.size() == 0) { + LOG(ERROR, "No mesh_ids received"); return; } - Ref mesh_asset = _terrain->get_assets()->get_mesh_asset(mesh_id); real_t brush_size = CLAMP(real_t(p_params.get("size", 10.f)), 0.1f, 4096.f); // Meters real_t radius = brush_size * .4f; // Ring1's inner radius real_t strength = CLAMP(real_t(p_params.get("strength", .1f)), .01f, 100.f); // (premul) 1-10k% real_t fixed_scale = CLAMP(real_t(p_params.get("fixed_scale", 100.f)) * .01f, .01f, 100.f); // 1-10k% real_t random_scale = CLAMP(real_t(p_params.get("random_scale", 0.f)) * .01f, 0.f, 10.f); // +/- 1000% - real_t density = CLAMP(.1f * brush_size * strength * mesh_asset->get_density() / - MAX(0.01f, fixed_scale + .5f * random_scale), - .001f, 1000.f); - - // Density based on strength, mesh AABB and input scale determines how many to place, even fractional - uint32_t count = _get_density_count(density); - if (count <= 0) { - return; - } - LOG(EXTREME, "Adding ", count, " instances at ", p_global_position); real_t fixed_spin = CLAMP(real_t(p_params.get("fixed_spin", 0.f)), .0f, 360.f); // degrees real_t random_spin = CLAMP(real_t(p_params.get("random_spin", 360.f)), 0.f, 360.f); // degrees @@ -474,81 +464,104 @@ void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const D Vector2 slope_range = p_params["slope"]; // 0-90 degrees already clamped in Editor bool invert = p_params["modifier_alt"]; Terrain3DData *data = _terrain->get_data(); - - TypedArray xforms; - PackedColorArray colors; - for (int i = 0; i < count; i++) { - Transform3D t; - - // Get random XZ position and height in a circle - real_t r_radius = radius * sqrt(UtilityFunctions::randf()); - real_t r_theta = UtilityFunctions::randf() * Math_TAU; - Vector3 rand_vec = Vector3(r_radius * cos(r_theta), 0.f, r_radius * sin(r_theta)); - Vector3 position = p_global_position + rand_vec; - // Get height, but skip holes - real_t height = data->get_height(position); - if (std::isnan(height)) { - continue; - } else { - position.y = height; + + Array mesh_id_keys = mesh_ids.values(); + mesh_id_keys.shuffle(); + + for (int m = 0; m < mesh_id_keys.size(); m++) { + int mesh_id = mesh_id_keys[m]; + + if (mesh_id < 0 || mesh_id >= _terrain->get_assets()->get_mesh_count()) { + LOG(ERROR, "Mesh ID out of range: ", mesh_id, ", valid: 0 to ", _terrain->get_assets()->get_mesh_count() - 1); + return; } - if (!data->is_in_slope(position, slope_range, invert)) { + Ref mesh_asset = _terrain->get_assets()->get_mesh_asset(mesh_id); + real_t density = CLAMP(.1f * brush_size * strength * mesh_asset->get_density() / + MAX(0.01f, fixed_scale + .5f * random_scale), + .001f, 1000.f); + + // Density based on strength, mesh AABB and input scale determines how many to place, even fractional + uint32_t count = _get_density_count(density); + if (count <= 0) { continue; } + LOG(EXTREME, "Adding ", count, " instances of ", mesh_asset->get_name(), "at ", p_global_position); - // Orientation - Vector3 normal = Vector3(0.f, 1.f, 0.f); - if (align_to_normal) { - normal = data->get_normal(position); - if (std::isnan(normal.x)) { - normal = Vector3(0.f, 1.f, 0.f); + TypedArray xforms; + PackedColorArray colors; + for (int i = 0; i < count; i++) { + Transform3D t; + + // Get random XZ position and height in a circle + real_t r_radius = radius * sqrt(UtilityFunctions::randf()); + real_t r_theta = UtilityFunctions::randf() * Math_TAU; + Vector3 rand_vec = Vector3(r_radius * cos(r_theta), 0.f, r_radius * sin(r_theta)); + Vector3 position = p_global_position + rand_vec; + // Get height, but skip holes + real_t height = data->get_height(position); + if (std::isnan(height)) { + continue; } else { - normal = normal.normalized(); - Vector3 z_axis = Vector3(0.f, 0.f, 1.f); - Vector3 x_axis = -z_axis.cross(normal); - t.basis = Basis(x_axis, normal, z_axis).orthonormalized(); + position.y = height; + } + if (!data->is_in_slope(position, slope_range, invert)) { + continue; } - } - real_t spin = (fixed_spin + random_spin * UtilityFunctions::randf()) * Math_PI / 180.f; - if (abs(spin) > 0.001f) { - t.basis = t.basis.rotated(normal, spin); - } - real_t tilt = (fixed_tilt + random_tilt * (2.f * UtilityFunctions::randf() - 1.f)) * Math_PI / 180.f; - if (abs(tilt) > 0.001f) { - t.basis = t.basis.rotated(t.basis.get_column(0), tilt); // Rotate pitch, X-axis - } - // Scale - real_t t_scale = CLAMP(fixed_scale + random_scale * (2.f * UtilityFunctions::randf() - 1.f), 0.01f, 10.f); - t = t.scaled(Vector3(t_scale, t_scale, t_scale)); + // Orientation + Vector3 normal = Vector3(0.f, 1.f, 0.f); + if (align_to_normal) { + normal = data->get_normal(position); + if (std::isnan(normal.x)) { + normal = Vector3(0.f, 1.f, 0.f); + } else { + normal = normal.normalized(); + Vector3 z_axis = Vector3(0.f, 0.f, 1.f); + Vector3 x_axis = -z_axis.cross(normal); + t.basis = Basis(x_axis, normal, z_axis).orthonormalized(); + } + } + real_t spin = (fixed_spin + random_spin * UtilityFunctions::randf()) * Math_PI / 180.f; + if (abs(spin) > 0.001f) { + t.basis = t.basis.rotated(normal, spin); + } + real_t tilt = (fixed_tilt + random_tilt * (2.f * UtilityFunctions::randf() - 1.f)) * Math_PI / 180.f; + if (abs(tilt) > 0.001f) { + t.basis = t.basis.rotated(t.basis.get_column(0), tilt); // Rotate pitch, X-axis + } - // Position. mesh_asset height offset added in add_transforms - real_t offset = height_offset + random_height * (2.f * UtilityFunctions::randf() - 1.f); - position += t.basis.get_column(1) * offset; // Offset along UP axis - t = t.translated(position); + // Scale + real_t t_scale = CLAMP(fixed_scale + random_scale * (2.f * UtilityFunctions::randf() - 1.f), 0.01f, 10.f); + t = t.scaled(Vector3(t_scale, t_scale, t_scale)); - // Color - Color col = vertex_color; - col.set_v(CLAMP(col.get_v() - random_darken * UtilityFunctions::randf(), 0.f, 1.f)); - col.set_h(fmod(col.get_h() + random_hue * (2.f * UtilityFunctions::randf() - 1.f), 1.f)); + // Position. mesh_asset height offset added in add_transforms + real_t offset = height_offset + random_height * (2.f * UtilityFunctions::randf() - 1.f); + position += t.basis.get_column(1) * offset; // Offset along UP axis + t = t.translated(position); - xforms.push_back(t); - colors.push_back(col); - } + // Color + Color col = vertex_color; + col.set_v(CLAMP(col.get_v() - random_darken * UtilityFunctions::randf(), 0.f, 1.f)); + col.set_h(fmod(col.get_h() + random_hue * (2.f * UtilityFunctions::randf() - 1.f), 1.f)); - // Append multimesh - if (xforms.size() > 0) { - add_transforms(mesh_id, xforms, colors); + xforms.push_back(t); + colors.push_back(col); + } + + // Append multimesh + if (xforms.size() > 0) { + add_transforms(mesh_id, xforms, colors); + } } } void Terrain3DInstancer::remove_instances(const Vector3 &p_global_position, const Dictionary &p_params) { IS_DATA_INIT_MESG("Instancer isn't initialized.", VOID); - int mesh_id = p_params.get("asset_id", 0); - int mesh_count = _terrain->get_assets()->get_mesh_count(); - if (mesh_id < 0 || mesh_id >= mesh_count) { - LOG(ERROR, "Mesh ID out of range: ", mesh_id, ", valid: 0 to ", _terrain->get_assets()->get_mesh_count() - 1); + Dictionary mesh_ids = p_params.get("asset_ids", Dictionary()); + + if (mesh_ids.size() == 0) { + LOG(ERROR, "No mesh_ids received"); return; } @@ -585,96 +598,106 @@ void Terrain3DInstancer::remove_instances(const Vector3 &p_global_position, cons return; } - for (int r = 0; r < region_queue.size(); r++) { - Vector2i region_loc = region_queue[r]; - Ref region = data->get_region(region_loc); - if (region.is_null()) { - LOG(WARN, "Errant null region found at: ", region_loc); - continue; + Array mesh_id_keys = mesh_ids.keys(); + for (int i = 0; i < mesh_id_keys.size(); i++) { + int mesh_id = mesh_id_keys[i]; + int mesh_count = _terrain->get_assets()->get_mesh_count(); + if (mesh_id < 0 || mesh_id >= mesh_count) { + LOG(ERROR, "Mesh ID out of range: ", mesh_id, ", valid: 0 to ", _terrain->get_assets()->get_mesh_count() - 1); + return; } - Dictionary mesh_inst_dict = region->get_instances(); - Array mesh_types = mesh_inst_dict.keys(); - if (mesh_types.size() == 0) { - continue; - } - Vector3 global_local_offset = Vector3(region_loc.x * region_size * vertex_spacing, 0.f, region_loc.y * region_size * vertex_spacing); - Vector2 localised_ring_center = Vector2(p_global_position.x - global_local_offset.x, p_global_position.z - global_local_offset.z); - // For this mesh id, or all mesh ids - for (int m = (modifier_shift ? 0 : mesh_id); m <= (modifier_shift ? mesh_count - 1 : mesh_id); m++) { - // Ensure this region has this mesh - if (!mesh_inst_dict.has(m)) { - continue; - } - Dictionary cell_inst_dict = mesh_inst_dict[m]; - Array cell_locations = cell_inst_dict.keys(); - // This shouldnt be empty - if (cell_locations.size() == 0) { - LOG(WARN, "Region at: ", region_loc, " has instance dictionary for mesh id: ", m, " but has no cells.") + for (int r = 0; r < region_queue.size(); r++) { + Vector2i region_loc = region_queue[r]; + Ref region = data->get_region(region_loc); + if (region.is_null()) { + LOG(WARN, "Errant null region found at: ", region_loc); continue; - } - // Check potential cells rather than searching the entire region, whilst marginally - // slower if there are very few cells for the given mesh present. It is significantly - // faster when a large number of cells are present. - Dictionary c_locs; - // Calculate step distance to ensure every cell is checked inside the bounds of brush size. - real_t cell_step = brush_size / ceil(brush_size / real_t(CELL_SIZE) / vertex_spacing); - for (real_t x = p_global_position.x - half_brush_size; x <= p_global_position.x + half_brush_size; x += cell_step) { - for (real_t z = p_global_position.z - half_brush_size; z <= p_global_position.z + half_brush_size; z += cell_step) { - Vector3 cell_pos = Vector3(x, 0.f, z) - global_local_offset; - // Manually calculate cell pos without modulus, locations not in the current region will not be found. - Vector2i cell_loc; - cell_loc.x = UtilityFunctions::floori(cell_pos.x / vertex_spacing) / CELL_SIZE; - cell_loc.y = UtilityFunctions::floori(cell_pos.z / vertex_spacing) / CELL_SIZE; - if (cell_locations.has(cell_loc)) { - c_locs[cell_loc] = 1; - } + + Dictionary mesh_inst_dict = region->get_instances(); + Array mesh_types = mesh_inst_dict.keys(); + if (mesh_types.size() == 0) { + continue; } - } - Array cell_queue = c_locs.keys(); - if (cell_queue.size() == 0) { - continue; - } - Ref mesh_asset = _terrain->get_assets()->get_mesh_asset(m); - real_t mesh_height_offset = mesh_asset->get_height_offset(); - for (int c = 0; c < cell_queue.size(); c++) { - Vector2i cell = cell_queue[c]; - Array triple = cell_inst_dict[cell]; - TypedArray xforms = triple[0]; - PackedColorArray colors = triple[1]; - TypedArray updated_xforms; - PackedColorArray updated_colors; - // Remove transforms if inside ring radius - for (int i = 0; i < xforms.size(); i++) { - Transform3D t = xforms[i]; - // Use localised ring center - real_t radial_distance = localised_ring_center.distance_to(Vector2(t.origin.x, t.origin.z)); - Vector3 height_offset = t.basis.get_column(1) * mesh_height_offset; - if (radial_distance < radius && - UtilityFunctions::randf() < CLAMP(0.175f * strength, 0.005f, 10.f) && - data->is_in_slope(t.origin + global_local_offset - height_offset, slope_range, invert)) { - _backup_region(region); + Vector3 global_local_offset = Vector3(region_loc.x * region_size * vertex_spacing, 0.f, region_loc.y * region_size * vertex_spacing); + Vector2 localised_ring_center = Vector2(p_global_position.x - global_local_offset.x, p_global_position.z - global_local_offset.z); + // For this mesh id, or all mesh ids + for (int m = (modifier_shift ? 0 : mesh_id); m <= (modifier_shift ? mesh_count - 1 : mesh_id); m++) { + // Ensure this region has this mesh + if (!mesh_inst_dict.has(m)) { continue; - } else { - updated_xforms.push_back(t); - updated_colors.push_back(colors[i]); + } + Dictionary cell_inst_dict = mesh_inst_dict[m]; + Array cell_locations = cell_inst_dict.keys(); + // This shouldnt be empty + if (cell_locations.size() == 0) { + LOG(WARN, "Region at: ", region_loc, " has instance dictionary for mesh id: ", m, " but has no cells.") + continue; + } + // Check potential cells rather than searching the entire region, whilst marginally + // slower if there are very few cells for the given mesh present. It is significantly + // faster when a large number of cells are present. + Dictionary c_locs; + // Calculate step distance to ensure every cell is checked inside the bounds of brush size. + real_t cell_step = brush_size / ceil(brush_size / real_t(CELL_SIZE) / vertex_spacing); + for (real_t x = p_global_position.x - half_brush_size; x <= p_global_position.x + half_brush_size; x += cell_step) { + for (real_t z = p_global_position.z - half_brush_size; z <= p_global_position.z + half_brush_size; z += cell_step) { + Vector3 cell_pos = Vector3(x, 0.f, z) - global_local_offset; + // Manually calculate cell pos without modulus, locations not in the current region will not be found. + Vector2i cell_loc; + cell_loc.x = UtilityFunctions::floori(cell_pos.x / vertex_spacing) / CELL_SIZE; + cell_loc.y = UtilityFunctions::floori(cell_pos.z / vertex_spacing) / CELL_SIZE; + if (cell_locations.has(cell_loc)) { + c_locs[cell_loc] = 1; + } + } + } + Array cell_queue = c_locs.keys(); + if (cell_queue.size() == 0) { + continue; + } + Ref mesh_asset = _terrain->get_assets()->get_mesh_asset(m); + real_t mesh_height_offset = mesh_asset->get_height_offset(); + for (int c = 0; c < cell_queue.size(); c++) { + Vector2i cell = cell_queue[c]; + Array triple = cell_inst_dict[cell]; + TypedArray xforms = triple[0]; + PackedColorArray colors = triple[1]; + TypedArray updated_xforms; + PackedColorArray updated_colors; + // Remove transforms if inside ring radius + for (int i = 0; i < xforms.size(); i++) { + Transform3D t = xforms[i]; + // Use localised ring center + real_t radial_distance = localised_ring_center.distance_to(Vector2(t.origin.x, t.origin.z)); + Vector3 height_offset = t.basis.get_column(1) * mesh_height_offset; + if (radial_distance < radius && + UtilityFunctions::randf() < CLAMP(0.175f * strength, 0.005f, 10.f) && + data->is_in_slope(t.origin + global_local_offset - height_offset, slope_range, invert)) { + _backup_region(region); + continue; + } else { + updated_xforms.push_back(t); + updated_colors.push_back(colors[i]); + } + } + if (updated_xforms.size() > 0) { + triple[0] = updated_xforms; + triple[1] = updated_colors; + triple[2] = true; + cell_inst_dict[cell] = triple; + } else { + cell_inst_dict.erase(cell); + _destroy_mmi_by_cell(region_loc, m, cell); + } + } + if (cell_inst_dict.is_empty()) { + mesh_inst_dict.erase(m); } } - if (updated_xforms.size() > 0) { - triple[0] = updated_xforms; - triple[1] = updated_colors; - triple[2] = true; - cell_inst_dict[cell] = triple; - } else { - cell_inst_dict.erase(cell); - _destroy_mmi_by_cell(region_loc, m, cell); - } - } - if (cell_inst_dict.is_empty()) { - mesh_inst_dict.erase(m); + _update_mmis(region_loc); } } - _update_mmis(region_loc); } }