diff --git a/project/addons/terrain_3d/src/asset_dock.gd b/project/addons/terrain_3d/src/asset_dock.gd index 6edac088d..90c554a1e 100644 --- a/project/addons/terrain_3d/src/asset_dock.gd +++ b/project/addons/terrain_3d/src/asset_dock.gd @@ -537,7 +537,7 @@ class ListContainer extends Container: func update_asset_list() -> void: if plugin.debug: - print("Terrain3DListContainer: ", name, " update_asset_list") + print("Terrain3DListContainer ", name, ": update_asset_list") clear() # Grab terrain @@ -573,7 +573,7 @@ class ListContainer extends Container: var res_id: int = p_resource.id if p_resource else entries.size() entry.hovered.connect(_on_resource_hovered.bind(res_id)) - entry.selected.connect(set_selected_id.bind(entries.size())) + entry.clicked.connect(clicked_id.bind(res_id)) entry.inspected.connect(_on_resource_inspected) entry.changed.connect(_on_resource_changed.bind(res_id)) entry.type = type @@ -597,25 +597,26 @@ class ListContainer extends Container: set_selected_id(clamp(p_new_id, 0, entries.size() - 2)) + func clicked_id(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) + + 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: set_selected_id: ", selected_id, " to ", clamp(p_id, 0, max_id)) + print("Terrain3DListContainer ", name, ": set_selected_id: ", selected_id, " to ", clamp(p_id, 0, max_id)) selected_id = clamp(p_id, 0, max_id) for i in entries.size(): var entry: ListEntry = entries[i] entry.set_selected(i == selected_id) - - # Select Paint tool if clicking a texture - 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") - - # Update editor with selected brush plugin.ui._on_setting_changed() @@ -624,7 +625,7 @@ class ListContainer extends Container: 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: get_selected_asset_id: selected_id: ", selected_id, ", clamped: ", id, ", entries: ", entries.size()) + 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 @@ -637,7 +638,7 @@ class ListContainer extends Container: func _on_resource_inspected(p_resource: Resource) -> void: - await get_tree().create_timer(.01).timeout + await get_tree().process_frame EditorInterface.edit_resource(p_resource) @@ -646,7 +647,7 @@ class ListContainer extends Container: return if not p_resource: if plugin.debug: - print("Terrain3DAssetDock: _on_resource_changed: removing asset ID: ", p_id) + print("Terrain3DListContainer ", name, ": _on_resource_changed: removing asset ID: ", p_id) _clearing_resource = true var asset_dock: Control = get_parent().get_parent().get_parent() if type == Terrain3DAssets.TYPE_TEXTURE: @@ -662,7 +663,7 @@ class ListContainer extends Container: if not plugin.is_terrain_valid(): plugin.select_terrain() - await get_tree().create_timer(.01).timeout + await get_tree().process_frame if plugin.is_terrain_valid(): if type == Terrain3DAssets.TYPE_TEXTURE: @@ -723,7 +724,7 @@ class ListContainer extends Container: class ListEntry extends MarginContainer: signal hovered() - signal selected() + signal clicked() signal changed(resource: Resource) signal inspected(resource: Resource) @@ -986,7 +987,7 @@ class ListEntry extends MarginContainer: set_edited_resource(Terrain3DMeshAsset.new(), false) _on_edit() else: - emit_signal("selected") + emit_signal("clicked") MOUSE_BUTTON_RIGHT: if resource: _on_edit() @@ -1029,7 +1030,7 @@ class ListEntry extends MarginContainer: if resource is Terrain3DMeshAsset: res.id = resource.id set_edited_resource(res, false) - emit_signal("selected") + emit_signal("clicked") emit_signal("inspected", resource) @@ -1059,7 +1060,7 @@ class ListEntry extends MarginContainer: is_selected = value if is_selected: # Handle scrolling to show the selected item - await RenderingServer.frame_pre_draw + await get_tree().process_frame if is_inside_tree(): get_parent().get_parent().get_v_scroll_bar().ratio = position.y / get_parent().size.y queue_redraw() @@ -1073,7 +1074,7 @@ class ListEntry extends MarginContainer: func _on_edit() -> void: - emit_signal("selected") + emit_signal("clicked") emit_signal("inspected", resource) diff --git a/project/addons/terrain_3d/src/editor_plugin.gd b/project/addons/terrain_3d/src/editor_plugin.gd index 5ebe00555..229fb6e07 100644 --- a/project/addons/terrain_3d/src/editor_plugin.gd +++ b/project/addons/terrain_3d/src/editor_plugin.gd @@ -10,7 +10,7 @@ const RegionGizmo: Script = preload("res://addons/terrain_3d/src/region_gizmo.gd const ASSET_DOCK: String = "res://addons/terrain_3d/src/asset_dock.tscn" # Editor Plugin -var debug: int = 0 # Set 1 normal, 2 verbose, or = terrain.debug in _edit() +var debug: int = 0 # Set in _edit() var editor: Terrain3DEditor var editor_settings: EditorSettings var ui: Node # Terrain3DUI see Godot #75388 @@ -19,6 +19,8 @@ var region_gizmo: RegionGizmo var current_region_position: Vector2 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 # Terrain var terrain: Terrain3D @@ -36,15 +38,20 @@ var _use_meta: bool = false func _init() -> void: + if debug: + print("Terrain3DEditorPlugin: _init") if OS.get_name() == "macOS": _use_meta = true # Get the Godot Editor window. Structure is root:Window/EditorNode/Base Control godot_editor_window = EditorInterface.get_base_control().get_parent().get_parent() godot_editor_window.focus_entered.connect(_on_godot_focus_entered) + EditorInterface.get_inspector().mouse_entered.connect(func(): mouse_in_main = false) + - func _enter_tree() -> void: + if debug: + print("Terrain3DEditorPlugin: _enter_tree") editor = Terrain3DEditor.new() setup_editor_settings() ui = Terrain3DUI.new() @@ -60,6 +67,8 @@ func _enter_tree() -> void: func _exit_tree() -> void: + if debug: + print("Terrain3DEditorPlugin: _exit_tree") asset_dock.remove_dock(true) asset_dock.queue_free() ui.queue_free() @@ -73,7 +82,6 @@ func _on_godot_focus_entered() -> void: if debug > 1: print("Terrain3DEditorPlugin: _on_godot_focus_entered") _read_input() - ui.update_decal() ## EditorPlugin selection function call chain isn't consistent. Here's the map of calls: @@ -101,6 +109,8 @@ func _handles(p_object: Object) -> bool: func _make_visible(p_visible: bool, p_redraw: bool = false) -> void: + if debug: + print("Terrain3DEditorPlugin: _make_visible(%s, %s)" % [ p_visible, p_redraw ]) if p_visible and is_selected(): ui.set_visible(true) asset_dock.update_dock() @@ -119,6 +129,7 @@ func _edit(p_object: Object) -> void: _last_terrain = terrain terrain.set_plugin(self) terrain.set_editor(editor) + debug = terrain.debug_level editor.set_terrain(terrain) region_gizmo.set_node_3d(terrain) terrain.add_gizmo(region_gizmo) @@ -159,13 +170,15 @@ func _clear() -> void: func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) -> AfterGUIInput: + mouse_in_main = true if not is_terrain_valid(): return AFTER_GUI_INPUT_PASS var continue_input: AfterGUIInput = _read_input(p_event) if continue_input != AFTER_GUI_INPUT_CUSTOM: return continue_input - ui.update_decal() + if terrain.dev_mode == Terrain3D.READ_ONLY: + return AFTER_GUI_INPUT_CUSTOM ## Setup active camera & viewport # Always update this for all inputs, as the mouse position can move without @@ -177,16 +190,18 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) -> # Detect if viewport is set to half_resolution # Structure is: Node3DEditorViewportContainer/Node3DEditorViewport(4)/SubViewportContainer/SubViewport/Camera3D - var editor_vpc: SubViewportContainer = p_viewport_camera.get_parent().get_parent() - var full_resolution: bool = false if editor_vpc.stretch_shrink == 2 else true + viewport = p_viewport_camera.get_parent() + var full_resolution: bool = false if viewport.get_parent().stretch_shrink == 2 else true ## Get mouse location on terrain # Project 2D mouse position to 3D position and direction - var vp_mouse_pos: Vector2 = editor_vpc.get_local_mouse_position() + var vp_mouse_pos: Vector2 = viewport.get_mouse_position() var mouse_pos: Vector2 = vp_mouse_pos if full_resolution else vp_mouse_pos / 2 var camera_pos: Vector3 = p_viewport_camera.project_ray_origin(mouse_pos) var camera_dir: Vector3 = p_viewport_camera.project_ray_normal(mouse_pos) + ui.update_decal() + # If region tool, grab mouse position without considering height if editor.get_tool() == Terrain3DEditor.REGION: var t = -Vector3(0, 1, 0).dot(camera_pos) / Vector3(0, 1, 0).dot(camera_dir) diff --git a/project/addons/terrain_3d/src/tool_settings.gd b/project/addons/terrain_3d/src/tool_settings.gd index e54a9ff25..0daf68a09 100644 --- a/project/addons/terrain_3d/src/tool_settings.gd +++ b/project/addons/terrain_3d/src/tool_settings.gd @@ -351,6 +351,8 @@ func _on_brush_hover(p_hovering: bool, p_button: Button) -> void: func _on_pick(p_type: Terrain3DEditor.Tool) -> void: + if plugin.debug: + print("Terrain3DToolSettings: _on_pick: emitting picking: ", p_type, ", ", _on_picked) emit_signal("picking", p_type, _on_picked) @@ -381,6 +383,8 @@ func _on_picked(p_type: Terrain3DEditor.Tool, p_color: Color, p_global_position: func _on_point_pick(p_type: Terrain3DEditor.Tool, p_name: String) -> void: assert(p_type == Terrain3DEditor.SCULPT) + if plugin.debug: + print("Terrain3DToolSettings: _on_pick: emitting picking: ", p_type, ", ", _on_point_picked) emit_signal("picking", p_type, _on_point_picked.bind(p_name)) @@ -410,6 +414,7 @@ func add_setting(p_args: Dictionary) -> void: return var container: HBoxContainer = HBoxContainer.new() + container.custom_minimum_size.y = 36 container.set_v_size_flags(SIZE_EXPAND_FILL) var control: Control # Houses the setting to be saved var pending_children: Array[Control] @@ -654,6 +659,8 @@ func _on_setting_changed(p_setting: Variant = null) -> void: # Hide label if p_setting.get_child_count() > 0: p_setting.get_child(0).visible = false + if plugin.debug: + print("Terrain3DToolSettings: _on_setting_changed: emitting setting_changed: ", p_setting) emit_signal("setting_changed", p_setting) diff --git a/project/addons/terrain_3d/src/toolbar.gd b/project/addons/terrain_3d/src/toolbar.gd index a3f0c675a..edf198d93 100644 --- a/project/addons/terrain_3d/src/toolbar.gd +++ b/project/addons/terrain_3d/src/toolbar.gd @@ -166,6 +166,5 @@ func change_tool(p_name: String) -> void: if plugin.debug: print("Terrain3DToolbar: change_tool: ", p_name, ", pressed: ", btn and btn.button_pressed) if btn and not btn.button_pressed: - if plugin.debug: - print("Terrain3DToolbar: change_tool: pressing button") + await get_tree().process_frame btn.set_pressed(true) diff --git a/project/addons/terrain_3d/src/ui.gd b/project/addons/terrain_3d/src/ui.gd index f8e582c3f..e11437b50 100644 --- a/project/addons/terrain_3d/src/ui.gd +++ b/project/addons/terrain_3d/src/ui.gd @@ -51,10 +51,10 @@ var picking: int = Terrain3DEditor.TOOL_MAX var picking_callback: Callable var brush_data: Dictionary var operation_builder: OperationBuilder -var active_tool: Terrain3DEditor.Tool -var _selected_tool: Terrain3DEditor.Tool -var active_operation: Terrain3DEditor.Operation -var _selected_operation: Terrain3DEditor.Operation +var active_tool: Terrain3DEditor.Tool = Terrain3DEditor.TOOL_MAX +var _selected_tool: Terrain3DEditor.Tool = Terrain3DEditor.TOOL_MAX +var active_operation: Terrain3DEditor.Operation = Terrain3DEditor.OP_MAX +var _selected_operation: Terrain3DEditor.Operation = Terrain3DEditor.OP_MAX var inverted_input: bool = false # 3 Editor decals: 0 = cursor, 1 = slope point1, 2 = slope point2 @@ -80,6 +80,9 @@ var editor_decal_fade: float : func _enter_tree() -> void: + if plugin.debug: + print("Terrain3DUI: _enter_tree()") + toolbar = TerrainToolbar.new() toolbar.plugin = plugin toolbar.hide() @@ -110,6 +113,8 @@ func _enter_tree() -> void: func _exit_tree() -> void: + if plugin.debug: + print("Terrain3DUI: _exit_tree()") plugin.remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, toolbar) plugin.remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, tool_settings) toolbar.queue_free() @@ -119,6 +124,9 @@ func _exit_tree() -> void: func set_visible(p_visible: bool, p_menu_only: bool = false) -> void: + if plugin.debug: + print("Terrain3DUI: set_visible(%s, %s)" % [ p_visible, p_menu_only ]) + terrain_menu.set_visible(p_visible) if p_menu_only: @@ -128,11 +136,12 @@ func set_visible(p_visible: bool, p_menu_only: bool = false) -> void: visible = p_visible toolbar.set_visible(p_visible) tool_settings.set_visible(p_visible) - update_decal() if plugin.editor: if p_visible: - await get_tree().create_timer(.01).timeout # Won't work, otherwise + await get_tree().process_frame # Won't work, otherwise + if plugin.debug: + print("Terrain3DUI: set_visible: calling _on_tool_changed()") _on_tool_changed(_selected_tool, _selected_operation) else: plugin.editor.set_tool(Terrain3DEditor.TOOL_MAX) @@ -147,6 +156,8 @@ func set_menu_visibility(p_list: Control, p_visible: bool) -> void: func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) -> void: if plugin.debug: print("Terrain3DUI: _on_tool_changed: ", p_tool, ", ", p_operation) + if active_tool == p_tool and active_operation == p_operation: + return _selected_tool = p_tool _selected_operation = p_operation clear_picking() @@ -265,6 +276,8 @@ func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor to_show.push_back("brush_spin_speed") tool_settings.show_settings(to_show) + if plugin.debug: + print("Terrain3DUI: _on_tool_changed: calling _on_setting_changed()") _on_setting_changed() plugin.update_region_grid() @@ -339,12 +352,11 @@ func set_active_operation() -> void: func update_decal() -> void: - if not plugin.terrain or brush_data.size() <= 3: + if not plugin.terrain or not plugin.viewport or brush_data.size() <= 3: return - mat_rid = plugin.terrain.material.get_material_rid() - editor_decal_timer.start() # If not a state that should show the decal, hide everything and return + mat_rid = plugin.terrain.material.get_material_rid() # Used in hide_decal() and below if not visible or \ plugin._input_mode == -1 or \ # After moving camera, wait for mouse cursor to update before revealing @@ -353,10 +365,18 @@ func update_decal() -> void: hide_decal() return + # Only show decal if in viewport or toolbars + var main: Control = EditorInterface.get_editor_main_screen() + var main_rect := Rect2(main.position, main.size) + main_rect.size.y += tool_settings.size.y + if not ( main_rect.has_point(plugin.viewport.get_mouse_position()) && plugin.mouse_in_main ): + return + reset_decal_arrays() editor_decal_position[0] = Vector2(plugin.mouse_global_position.x, plugin.mouse_global_position.z) editor_decal_visible = [true, false, false] # Show cursor by default editor_decal_part = [true, true] # Show brush and reticle by default + editor_decal_timer.start() ## Region Operations var r_map: PackedInt32Array = plugin.terrain.data.get_region_map() @@ -574,7 +594,6 @@ func set_decal_rotation(p_rot: float) -> void: func _on_picking(p_type: Terrain3DEditor.Tool, p_callback: Callable) -> void: picking = p_type picking_callback = p_callback - update_decal() func clear_picking() -> void: diff --git a/project/demo/data/assets.tres b/project/demo/data/assets.tres index 1970b28e9..a87b886ae 100644 --- a/project/demo/data/assets.tres +++ b/project/demo/data/assets.tres @@ -18,7 +18,6 @@ distance_fade_max_distance = 96.0 [sub_resource type="Terrain3DMeshAsset" id="Terrain3DMeshAsset_2qf8x"] name = "TextureCard" -generated_type = 1 height_offset = 0.5 material_override = SubResource("StandardMaterial3D_b2vqk") last_lod = 0 diff --git a/src/register_types.cpp b/src/register_types.cpp index 40d252d15..41cb61910 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -34,7 +34,6 @@ void uninitialize_terrain_3d_module(ModuleInitializationLevel p_level) { } #ifdef GDEXTENSION - extern "C" { // Initialization. GDExtensionBool GDE_EXPORT terrain_3d_init( @@ -50,5 +49,4 @@ GDExtensionBool GDE_EXPORT terrain_3d_init( return init_obj.init(); } } - #endif /* GDEXTENSION */ diff --git a/src/shaders/main.glsl b/src/shaders/main.glsl index 13f7ef470..bbac7e6cf 100644 --- a/src/shaders/main.glsl +++ b/src/shaders/main.glsl @@ -69,6 +69,7 @@ uniform highp sampler2DArray _height_maps : repeat_disable; uniform highp sampler2DArray _control_maps : repeat_disable; //INSERT: TEXTURE_SAMPLERS_NEAREST //INSERT: TEXTURE_SAMPLERS_LINEAR +uniform bool _color_map_enabled = true; // Public uniforms group_uniforms general; @@ -428,17 +429,17 @@ 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_enabled && region_uv.z > -1.0 ? textureLod(_color_maps, region_uv, region_mip) : COLOR_MAP_DEF; // 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; + col_map[3] = _color_map_enabled && 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; + col_map[0] = _color_map_enabled && index[0].z > -1 ? texelFetch(_color_maps, index[0], 0) : COLOR_MAP_DEF; + col_map[1] = _color_map_enabled && index[1].z > -1 ? texelFetch(_color_maps, index[1], 0) : COLOR_MAP_DEF; + col_map[2] = _color_map_enabled && index[2].z > -1 ? texelFetch(_color_maps, index[2], 0) : COLOR_MAP_DEF; color_map = col_map[0] * weights[0] + diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index f09162276..54497d6d3 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -201,6 +201,7 @@ void Terrain3D::_setup_mouse_picking() { _mouse_vp->set_handle_input_locally(false); _mouse_vp->set_canvas_cull_mask(0); _mouse_vp->set_use_hdr_2d(true); + _mouse_vp->set_anisotropic_filtering_level(Viewport::ANISOTROPY_DISABLED); _mouse_vp->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST); _mouse_vp->set_positional_shadow_atlas_size(0); _mouse_vp->set_positional_shadow_atlas_quadrant_subdiv(0, Viewport::SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED); @@ -242,7 +243,9 @@ void Terrain3D::_setup_mouse_picking() { _mouse_quad->set_position(Vector3(0.f, 0.f, -0.5f)); // Set terrain, terrain shader, mouse camera, and screen quad to mouse layer - set_mouse_layer(_mouse_layer); + uint32_t force_update_layer = _mouse_layer; + _mouse_layer = 0u; + set_mouse_layer(force_update_layer); } void Terrain3D::_destroy_mouse_picking() { @@ -400,51 +403,51 @@ Terrain3D::Terrain3D() { } void Terrain3D::set_debug_level(const DebugLevel p_level) { - LOG(INFO, "Setting debug level: ", p_level); - debug_level = CLAMP(p_level, ERROR, EXTREME); + SET_IF_DIFF(debug_level, CLAMP(p_level, ERROR, EXTREME)); + LOG(INFO, "Setting debug level: ", debug_level); } -void Terrain3D::set_data_directory(String p_dir) { - LOG(INFO, "Setting data directory to ", p_dir); - if (_data_directory != p_dir) { - if (_data_directory.is_empty() && Util::get_files(p_dir, "terrain3d*.res").size() == 0) { - // If _data_directory was empty and now specified, and has no data - // assume we want to retain the current data - _data_directory = p_dir; - } else { - // Else clear data and if not null, load - _initialized = false; - _destroy_labels(); - _destroy_collision(); - _destroy_instancer(); - memdelete_safely(_data); - _data_directory = p_dir; - _initialize(); - } +void Terrain3D::set_dev_mode(const DevMode p_mode) { + LOG(INFO, "Setting development mode: ", p_mode); + if (p_mode != _dev_mode) { + _dev_mode = p_mode; } - update_configuration_warnings(); } -void Terrain3D::set_material(const Ref &p_material) { - if (_material != p_material) { +void Terrain3D::set_data_directory(String p_dir) { + String old_dir = _data_directory; + SET_IF_DIFF(_data_directory, p_dir); + LOG(INFO, "Setting data directory to ", _data_directory); + // If _data_directory was empty and now specified, and has no data + // assume we want to retain the current data. + // Otherwise, clear data and reload dir + if (!old_dir.is_empty() || Util::get_files(p_dir, "terrain3d*.res").size() > 0) { _initialized = false; - LOG(INFO, "Setting material"); - _material = p_material; + _destroy_labels(); + _destroy_collision(); + _destroy_instancer(); + memdelete_safely(_data); _initialize(); - LOG(DEBUG, "Emitting material_changed"); - emit_signal("material_changed"); } + update_configuration_warnings(); +} + +void Terrain3D::set_material(const Ref &p_material) { + SET_IF_DIFF(_material, p_material); + LOG(INFO, "Setting material"); + _initialized = false; + _initialize(); + LOG(DEBUG, "Emitting material_changed"); + emit_signal("material_changed"); } void Terrain3D::set_assets(const Ref &p_assets) { - if (_assets != p_assets) { - _initialized = false; - LOG(INFO, "Setting asset list"); - _assets = p_assets; - _initialize(); - LOG(DEBUG, "Emitting assets_changed"); - emit_signal("assets_changed"); - } + SET_IF_DIFF(_assets, p_assets); + LOG(INFO, "Setting asset list"); + _initialized = false; + _initialize(); + LOG(DEBUG, "Emitting assets_changed"); + emit_signal("assets_changed"); } void Terrain3D::set_editor(Terrain3DEditor *p_editor) { @@ -452,8 +455,8 @@ void Terrain3D::set_editor(Terrain3DEditor *p_editor) { LOG(ERROR, "Attempted to set a node queued for deletion"); return; } - LOG(INFO, "Setting Terrain3DEditor: ", p_editor); - _editor = p_editor; + SET_IF_DIFF(_editor, p_editor); + LOG(INFO, "Setting Terrain3DEditor: ", _editor); if (_material.is_valid()) { _material->update(); } @@ -464,8 +467,8 @@ void Terrain3D::set_plugin(Object *p_plugin) { LOG(ERROR, "Attempted to set a node queued for deletion"); return; } - LOG(INFO, "Setting Editor Plugin: ", p_plugin); - _editor_plugin = p_plugin; + SET_IF_DIFF(_editor_plugin, p_plugin); + LOG(INFO, "Setting Editor Plugin: ", _editor_plugin); } void Terrain3D::set_camera(Camera3D *p_camera) { @@ -474,9 +477,11 @@ void Terrain3D::set_camera(Camera3D *p_camera) { _camera.clear(); return; } - LOG(EXTREME, "Setting camera: ", p_camera); - _camera.set_target(p_camera); - set_physics_process(true); + if (_camera.ptr() != p_camera) { + _camera.set_target(p_camera); + LOG(EXTREME, "Setting camera: ", p_camera); + set_physics_process(true); + }; } void Terrain3D::set_clipmap_target(Node3D *p_node) { @@ -485,9 +490,11 @@ void Terrain3D::set_clipmap_target(Node3D *p_node) { _clipmap_target.clear(); return; } - LOG(INFO, "Setting clipmap target: ", p_node); - _clipmap_target.set_target(p_node); - set_physics_process(true); + if (_clipmap_target.ptr() != p_node) { + _clipmap_target.set_target(p_node); + LOG(INFO, "Setting clipmap target: ", p_node); + set_physics_process(true); + } } Vector3 Terrain3D::get_clipmap_target_position() const { @@ -506,8 +513,11 @@ void Terrain3D::set_collision_target(Node3D *p_node) { _collision_target.clear(); return; } - LOG(INFO, "Setting collision target: ", p_node); - _collision_target.set_target(p_node); + if (_collision_target.ptr() != p_node) { + LOG(INFO, "Setting collision target: ", p_node); + _collision_target.set_target(p_node); + set_physics_process(true); + } } Vector3 Terrain3D::get_collision_target_position() const { @@ -527,12 +537,12 @@ void Terrain3D::snap() { } void Terrain3D::set_region_size(const RegionSize p_size) { - LOG(INFO, "Setting region size: ", p_size); if (!is_valid_region_size(p_size)) { LOG(ERROR, "Invalid region size: ", p_size, ". Must be power of 2, 64-2048"); return; } - _region_size = p_size; + SET_IF_DIFF(_region_size, p_size); + LOG(INFO, "Setting region size: ", _region_size); if (_data) { _data->_region_size = _region_size; _data->_region_sizev = V2I(_region_size); @@ -543,8 +553,8 @@ void Terrain3D::set_region_size(const RegionSize p_size) { } void Terrain3D::set_save_16_bit(const bool p_enabled) { - LOG(INFO, p_enabled); - _save_16_bit = p_enabled; + SET_IF_DIFF(_save_16_bit, p_enabled); + LOG(INFO, "Save heightmaps as 16-bit: ", _save_16_bit); TypedArray regions = _data->get_regions_active(); for (int i = 0; i < regions.size(); i++) { Ref region = regions[i]; @@ -553,21 +563,15 @@ void Terrain3D::set_save_16_bit(const bool p_enabled) { } void Terrain3D::set_label_distance(const real_t p_distance) { - real_t distance = CLAMP(p_distance, 0.f, 100000.f); - LOG(INFO, "Setting region label distance: ", distance); - if (_label_distance != distance) { - _label_distance = distance; - update_region_labels(); - } + SET_IF_DIFF(_label_distance, CLAMP(p_distance, 0.f, 100000.f)); + LOG(INFO, "Setting region label distance: ", _label_distance); + update_region_labels(); } void Terrain3D::set_label_size(const int p_size) { - int size = CLAMP(p_size, 24, 128); - LOG(INFO, "Setting region label size: ", size); - if (_label_size != size) { - _label_size = size; - update_region_labels(); - } + SET_IF_DIFF(_label_size, CLAMP(p_size, 24, 128)); + LOG(INFO, "Setting region label size: ", _label_size); + update_region_labels(); } void Terrain3D::update_region_labels() { @@ -602,41 +606,34 @@ void Terrain3D::update_region_labels() { } } -void Terrain3D::set_mesh_lods(const int p_count) { - if (_mesh_lods != p_count) { - LOG(INFO, "Setting mesh levels: ", p_count); - _mesh_lods = p_count; - if (_mesher) { - _mesher->initialize(this); - } +void Terrain3D::set_mesh_size(const int p_size) { + SET_IF_DIFF(_mesh_size, CLAMP(p_size & ~1, 8, 256)); // Ensure even + LOG(INFO, "Setting mesh size: ", _mesh_size); + if (_mesher && _material.is_valid()) { + _material->update(); + _mesher->initialize(this); } } -void Terrain3D::set_mesh_size(const int p_size) { - if (_mesh_size != p_size) { - LOG(INFO, "Setting mesh size: ", p_size); - _mesh_size = p_size; - if (_mesher && _material.is_valid()) { - _material->_update_maps(); - _mesher->initialize(this); - } +void Terrain3D::set_mesh_lods(const int p_count) { + SET_IF_DIFF(_mesh_lods, CLAMP(p_count, 1, 10)); + LOG(INFO, "Setting mesh levels: ", _mesh_lods); + if (_mesher) { + _mesher->initialize(this); } } void Terrain3D::set_vertex_spacing(const real_t p_spacing) { - real_t spacing = CLAMP(p_spacing, 0.25f, 100.0f); - if (_vertex_spacing != spacing) { - _vertex_spacing = spacing; - LOG(INFO, "Setting vertex spacing: ", _vertex_spacing); - if (_collision && _data && _instancer && _material.is_valid()) { - _data->_vertex_spacing = _vertex_spacing; - update_region_labels(); - _instancer->_update_vertex_spacing(_vertex_spacing); - _mesher->reset_target_position(); - _material->_update_maps(); - _collision->destroy(); - _collision->build(); - } + SET_IF_DIFF(_vertex_spacing, CLAMP(p_spacing, 0.25f, 100.0f)); + LOG(INFO, "Setting vertex spacing: ", _vertex_spacing); + if (_collision && _data && _instancer && _material.is_valid()) { + _data->_vertex_spacing = _vertex_spacing; + update_region_labels(); + _instancer->_update_vertex_spacing(_vertex_spacing); + _mesher->reset_target_position(); + _material->_update_maps(); + _collision->destroy(); + _collision->build(); } if (IS_EDITOR && _editor_plugin) { _editor_plugin->call("update_region_grid"); @@ -644,18 +641,18 @@ void Terrain3D::set_vertex_spacing(const real_t p_spacing) { } void Terrain3D::set_render_layers(const uint32_t p_layers) { + SET_IF_DIFF(_render_layers, p_layers); LOG(INFO, "Setting terrain render layers to: ", p_layers); - _render_layers = p_layers; if (_mesher) { _mesher->update(); } } void Terrain3D::set_mouse_layer(const uint32_t p_layer) { - uint32_t layer = CLAMP(p_layer, 21, 32); - _mouse_layer = layer; + SET_IF_DIFF(_mouse_layer, CLAMP(p_layer, 21, 32)); uint32_t mouse_mask = 1 << (_mouse_layer - 1); - LOG(INFO, "Setting mouse layer: ", layer, " (", mouse_mask, ") on terrain mesh, material, mouse camera, mouse quad"); + LOG(INFO, "Setting mouse layer: ", _mouse_layer, " (", mouse_mask, + ") on terrain mesh, material, mouse camera, mouse quad"); // Set terrain meshes to mouse layer // Mask off editor render layers by ORing user layers 1-20 and current mouse layer @@ -675,22 +672,22 @@ void Terrain3D::set_mouse_layer(const uint32_t p_layer) { } void Terrain3D::set_cast_shadows(const RenderingServer::ShadowCastingSetting p_cast_shadows) { - _cast_shadows = p_cast_shadows; + SET_IF_DIFF(_cast_shadows, p_cast_shadows); if (_mesher) { _mesher->update(); } } void Terrain3D::set_gi_mode(const GeometryInstance3D::GIMode p_gi_mode) { - _gi_mode = p_gi_mode; + SET_IF_DIFF(_gi_mode, p_gi_mode); if (_mesher) { _mesher->update(); } } void Terrain3D::set_cull_margin(const real_t p_margin) { - LOG(INFO, "Setting extra cull margin: ", p_margin); - _cull_margin = p_margin; + SET_IF_DIFF(_cull_margin, CLAMP(p_margin, 0.f, 100000.f)); + LOG(INFO, "Setting extra cull margin: ", _cull_margin); if (_mesher) { _mesher->update_aabbs(); } @@ -740,7 +737,6 @@ Vector3 Terrain3D::get_intersection(const Vector3 &p_src_pos, const Vector3 &p_d return V3_MAX; } else { - // Else use GPU mode, which requires multiple calls // Get depth from perspective camera snapshot if (!_mouse_cam) { LOG(ERROR, "Invalid mouse camera"); @@ -1062,6 +1058,9 @@ void Terrain3D::_notification(const int p_what) { } void Terrain3D::_bind_methods() { + BIND_ENUM_CONSTANT(NORMAL); + BIND_ENUM_CONSTANT(READ_ONLY); + BIND_ENUM_CONSTANT(ERROR); BIND_ENUM_CONSTANT(INFO); BIND_ENUM_CONSTANT(DEBUG); @@ -1077,6 +1076,8 @@ void Terrain3D::_bind_methods() { 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); + ClassDB::bind_method(D_METHOD("set_dev_mode", "mode"), &Terrain3D::set_dev_mode); + ClassDB::bind_method(D_METHOD("get_dev_mode"), &Terrain3D::get_dev_mode); ClassDB::bind_method(D_METHOD("set_data_directory", "directory"), &Terrain3D::set_data_directory); ClassDB::bind_method(D_METHOD("get_data_directory"), &Terrain3D::get_data_directory); @@ -1130,7 +1131,7 @@ void Terrain3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_physics_material", "material"), &Terrain3D::set_physics_material); ClassDB::bind_method(D_METHOD("get_physics_material"), &Terrain3D::get_physics_material); - // Meshes + // Mesh ClassDB::bind_method(D_METHOD("set_mesh_lods", "count"), &Terrain3D::set_mesh_lods); ClassDB::bind_method(D_METHOD("get_mesh_lods"), &Terrain3D::get_mesh_lods); ClassDB::bind_method(D_METHOD("set_mesh_size", "size"), &Terrain3D::set_mesh_size); @@ -1138,6 +1139,12 @@ void Terrain3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_vertex_spacing", "scale"), &Terrain3D::set_vertex_spacing); ClassDB::bind_method(D_METHOD("get_vertex_spacing"), &Terrain3D::get_vertex_spacing); + // Instancer + 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); + ClassDB::bind_method(D_METHOD("set_show_instances", "visible"), &Terrain3D::set_show_instances); + ClassDB::bind_method(D_METHOD("get_show_instances"), &Terrain3D::get_show_instances); + // Rendering ClassDB::bind_method(D_METHOD("set_render_layers", "layers"), &Terrain3D::set_render_layers); ClassDB::bind_method(D_METHOD("get_render_layers"), &Terrain3D::get_render_layers); @@ -1149,10 +1156,10 @@ 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_show_instances", "visible"), &Terrain3D::set_show_instances); - ClassDB::bind_method(D_METHOD("get_show_instances"), &Terrain3D::get_show_instances); + 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); // Overlays ClassDB::bind_method(D_METHOD("set_show_region_grid", "enabled"), &Terrain3D::set_show_region_grid); @@ -1205,6 +1212,7 @@ void Terrain3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "version", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY), "", "get_version"); ADD_PROPERTY(PropertyInfo(Variant::INT, "debug_level", PROPERTY_HINT_ENUM, "Errors,Info,Debug,Extreme"), "set_debug_level", "get_debug_level"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "dev_mode", PROPERTY_HINT_ENUM, "Normal,Read Only"), "set_dev_mode", "get_dev_mode"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "data_directory", PROPERTY_HINT_DIR), "set_data_directory", "get_data_directory"); // Object references @@ -1231,12 +1239,16 @@ void Terrain3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "collision_target", PROPERTY_HINT_NODE_TYPE, "Node3D"), "set_collision_target", "get_collision_target"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material", "get_physics_material"); - ADD_GROUP("Mesh", ""); + ADD_GROUP("Clipmap Mesh", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_lods", PROPERTY_HINT_RANGE, "1,10,1"), "set_mesh_lods", "get_mesh_lods"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_size", PROPERTY_HINT_RANGE, "8,64,2"), "set_mesh_size", "get_mesh_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_size", PROPERTY_HINT_RANGE, "8,256,2"), "set_mesh_size", "get_mesh_size"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "vertex_spacing", PROPERTY_HINT_RANGE, "0.25,10.0,0.05,or_greater"), "set_vertex_spacing", "get_vertex_spacing"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "clipmap_target", PROPERTY_HINT_NODE_TYPE, "Node3D"), "set_clipmap_target", "get_clipmap_target"); + ADD_GROUP("Instancer", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "instancer_mode", PROPERTY_HINT_ENUM, "Disabled,Normal"), "set_instancer_mode", "get_instancer_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_instances"), "set_show_instances", "get_show_instances"); + ADD_GROUP("Rendering", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "render_layers", PROPERTY_HINT_LAYERS_3D_RENDER), "set_render_layers", "get_render_layers"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_layer", PROPERTY_HINT_RANGE, "21, 32"), "set_mouse_layer", "get_mouse_layer"); @@ -1244,7 +1256,7 @@ void Terrain3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_mode", PROPERTY_HINT_ENUM, "Disabled,Static,Dynamic"), "set_gi_mode", "get_gi_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cull_margin", PROPERTY_HINT_RANGE, "0.0,10000.0,.5,or_greater"), "set_cull_margin", "get_cull_margin"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "free_editor_textures"), "set_free_editor_textures", "get_free_editor_textures"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_instances"), "set_show_instances", "get_show_instances"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "color_map_enabled"), "set_color_map_enabled", "get_color_map_enabled"); ADD_GROUP("Overlays", "show_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_region_grid"), "set_show_region_grid", "get_show_region_grid"); diff --git a/src/terrain_3d.h b/src/terrain_3d.h index 1bb9ab689..516434a55 100644 --- a/src/terrain_3d.h +++ b/src/terrain_3d.h @@ -36,6 +36,11 @@ class Terrain3D : public Node3D { EXTREME = 3, // Continuous operations like snapping }; + enum DevMode { + NORMAL, + READ_ONLY, + }; + enum RegionSize { SIZE_64 = 64, SIZE_128 = 128, @@ -47,10 +52,11 @@ class Terrain3D : public Node3D { private: String _version = "1.1.0-dev"; + DevMode _dev_mode = NORMAL; String _data_directory; bool _is_inside_world = false; bool _initialized = false; - uint8_t _warnings = 0; + uint8_t _warnings = 0u; // Object references Terrain3DData *_data = nullptr; @@ -79,7 +85,7 @@ class Terrain3D : public Node3D { real_t _vertex_spacing = 1.0f; // Rendering - uint32_t _render_layers = 1 | (1 << 31); // Bit 1 and 32 for the cursor + uint32_t _render_layers = 1u | (1u << 31u); // Bit 1 and 32 for the cursor RenderingServer::ShadowCastingSetting _cast_shadows = RenderingServer::SHADOW_CASTING_SETTING_ON; GeometryInstance3D::GIMode _gi_mode = GeometryInstance3D::GI_MODE_STATIC; real_t _cull_margin = 0.0f; @@ -89,7 +95,7 @@ class Terrain3D : public Node3D { SubViewport *_mouse_vp = nullptr; Camera3D *_mouse_cam = nullptr; MeshInstance3D *_mouse_quad = nullptr; - uint32_t _mouse_layer = 32; + uint32_t _mouse_layer = 32u; // Parent containers for child nodes Node3D *_label_parent; @@ -127,6 +133,8 @@ class Terrain3D : public Node3D { String get_version() const { return _version; } void set_debug_level(const DebugLevel p_level); DebugLevel get_debug_level() const { return debug_level; } + void set_dev_mode(const DevMode p_mode); + DevMode get_dev_mode() const { return _dev_mode; } void set_data_directory(String p_dir); String get_data_directory() const { return _data ? _data_directory : ""; } @@ -175,6 +183,12 @@ class Terrain3D : public Node3D { void set_vertex_spacing(const real_t p_spacing); real_t get_vertex_spacing() const { return _vertex_spacing; } + // Instancer + void set_instancer_mode(const InstancerMode p_mode) { _instancer ? _instancer->set_mode(p_mode) : void(); } + InstancerMode get_instancer_mode() const { return _instancer ? _instancer->get_mode() : InstancerMode::NORMAL; } + void set_show_instances(const bool p_visible) { _mmi_parent ? _mmi_parent->set_visible(p_visible) : void(); } + bool get_show_instances() const { return _mmi_parent ? _mmi_parent->is_visible() : false; } + // Rendering void set_render_layers(const uint32_t p_layers); uint32_t get_render_layers() const { return _render_layers; }; @@ -188,8 +202,8 @@ class Terrain3D : public Node3D { real_t get_cull_margin() const { return _cull_margin; }; void set_free_editor_textures(const bool p_free_textures) { _free_editor_textures = p_free_textures; } bool get_free_editor_textures() const { return _free_editor_textures; }; - void set_show_instances(const bool p_visible) { _mmi_parent ? _mmi_parent->set_visible(p_visible) : void(); } - bool get_show_instances() const { return _mmi_parent ? _mmi_parent->is_visible() : false; } + void set_color_map_enabled(const bool p_enabled) { _data ? _data->set_color_map_enabled(p_enabled) : void(); } + bool get_color_map_enabled() const { return _data ? _data->get_color_map_enabled() : true; } // Utility Vector3 get_intersection(const Vector3 &p_src_pos, const Vector3 &p_direction, const bool p_gpu_mode = false); @@ -265,8 +279,9 @@ class Terrain3D : public Node3D { static void _bind_methods(); }; -VARIANT_ENUM_CAST(Terrain3D::RegionSize); +VARIANT_ENUM_CAST(Terrain3D::DevMode); VARIANT_ENUM_CAST(Terrain3D::DebugLevel); +VARIANT_ENUM_CAST(Terrain3D::RegionSize); constexpr Terrain3D::DebugLevel MESG = Terrain3D::DebugLevel::MESG; constexpr Terrain3D::DebugLevel WARN = Terrain3D::DebugLevel::WARN; diff --git a/src/terrain_3d_assets.cpp b/src/terrain_3d_assets.cpp index 17a353af3..da681b587 100644 --- a/src/terrain_3d_assets.cpp +++ b/src/terrain_3d_assets.cpp @@ -105,7 +105,9 @@ void Terrain3DAssets::_set_asset_list(const AssetType p_type, const TypedArrayget_class(), " ID ", id, " already exists. Setting '", res->get_name(), "' to ID ", j, ". Textures/Meshes on the ground may be wrong. Review Asset list to ensure each have unique IDs."); + LOG(ERROR, res->get_class(), " ID ", id, " already exists. Setting '", + res->get_name(), "' to ID ", j, + ". Textures/Meshes on the ground may be wrong. Review Asset list to ensure each have unique IDs."); res->set_id(j); list[j] = res; filled_id = j; @@ -122,7 +124,8 @@ void Terrain3DAssets::_set_asset_list(const AssetType p_type, const TypedArray res = list[i]; int id = res.is_valid() ? res->get_id() : -1; - LOG(DEBUG, "Asset ", i, ": ", res, ", stored ID: ", id); + String name = res.is_valid() ? res->get_name() : ""; + LOG(DEBUG, "Asset ", i, ": ", name, ", ", res, ", stored ID: ", id); } } } @@ -148,22 +151,23 @@ void Terrain3DAssets::_set_asset(const AssetType p_type, const int p_id, const R LOG(ERROR, "Invalid asset id: ", p_id, " range is 0-", max_size); return; } + int id = CLAMP(p_id, 0, list.size()); // Delete asset if null if (p_asset.is_null()) { // If final asset, remove it - if (p_id == list.size() - 1) { - LOG(DEBUG, "Deleting asset id: ", p_id); + if (id == list.size() - 1) { + LOG(DEBUG, "Deleting asset id: ", id); list.pop_back(); - } else if (p_id < list.size()) { + } else { // Else just clear it - Ref res = list[p_id]; + Ref res = list[id]; res->clear(); - res->_id = p_id; + res->_id = id; } } else { // Else Insert/Add Asset at end if a high number - if (p_id >= list.size()) { - p_asset->_id = list.size(); + if (id == list.size()) { + p_asset->_id = id; list.push_back(p_asset); if (!p_asset->is_connected("id_changed", callable_mp(this, &Terrain3DAssets::_swap_ids))) { LOG(DEBUG, "Connecting to id_changed"); @@ -171,7 +175,7 @@ void Terrain3DAssets::_set_asset(const AssetType p_type, const int p_id, const R } } else { // Else overwrite an existing slot - list[p_id] = p_asset; + list[id] = p_asset; } } } @@ -293,7 +297,7 @@ void Terrain3DAssets::_update_texture_files() { texture_set->_albedo_texture = ImageTexture::create_from_image(img); } else { img = tex->get_image(); - LOG(DEBUG, "Texture ID ", i, " albedo is valid. Format: ", img->get_format()); + LOG(EXTREME, "Texture ID ", i, " albedo is valid. Format: ", img->get_format()); if (!IS_EDITOR && tex->get_path().contains("ImageTexture")) { LOG(WARN, "Texture ID ", i, " albedo is saved in the scene. Save it as a file and link it."); } @@ -324,7 +328,7 @@ void Terrain3DAssets::_update_texture_files() { texture_set->_normal_texture = ImageTexture::create_from_image(img); } else { img = tex->get_image(); - LOG(DEBUG, "Texture ID ", i, " normal is valid. Format: ", img->get_format()); + LOG(EXTREME, "Texture ID ", i, " normal is valid. Format: ", img->get_format()); if (!IS_EDITOR && tex->get_path().contains("ImageTexture")) { LOG(WARN, "Texture ID ", i, " normal is saved in the scene. Save it as a file and link it."); } @@ -481,15 +485,23 @@ void Terrain3DAssets::destroy() { } void Terrain3DAssets::set_texture(const int p_id, const Ref &p_texture) { - if (_texture_list.size() <= p_id || p_texture != _texture_list[p_id]) { - LOG(INFO, "Setting texture id: ", p_id); - _set_asset(TYPE_TEXTURE, p_id, p_texture); - update_texture_list(); + if (p_id < 0 || p_id >= MAX_TEXTURES) { + LOG(ERROR, "Invalid texture id: ", p_id, " range is 0-", MAX_TEXTURES - 1); + return; } + if (p_id < _texture_list.size() && _texture_list[p_id] == p_texture) { + return; + } + LOG(INFO, "Setting texture id: ", p_id); + _set_asset(TYPE_TEXTURE, p_id, p_texture); + update_texture_list(); } void Terrain3DAssets::set_texture_list(const TypedArray &p_texture_list) { LOG(INFO, "Setting texture list with ", p_texture_list.size(), " entries"); + if (!differs(_texture_list, p_texture_list)) { + return; + } _set_asset_list(TYPE_TEXTURE, p_texture_list); update_texture_list(); } @@ -524,11 +536,14 @@ void Terrain3DAssets::update_texture_list() { } void Terrain3DAssets::set_mesh_asset(const int p_id, const Ref &p_mesh_asset) { - LOG(INFO, "Setting mesh id: ", p_id, ", ", p_mesh_asset); - if (p_id >= 0 && p_id < _mesh_list.size() && _mesh_list[p_id] == p_mesh_asset) { - LOG(DEBUG, "Setting same mesh asset, returning"); + if (p_id < 0 || p_id >= MAX_MESHES) { + LOG(ERROR, "Invalid mesh id: ", p_id, " range is 0-", MAX_MESHES - 1); + return; + } + if (p_id < _mesh_list.size() && _mesh_list[p_id] == p_mesh_asset) { return; } + LOG(INFO, "Setting mesh id: ", p_id, ", ", p_mesh_asset); _set_asset(TYPE_MESH, p_id, p_mesh_asset); if (p_mesh_asset.is_null()) { IS_INSTANCER_INIT(VOID); @@ -546,6 +561,9 @@ Ref Terrain3DAssets::get_mesh_asset(const int p_id) const { void Terrain3DAssets::set_mesh_list(const TypedArray &p_mesh_list) { LOG(INFO, "Setting mesh list with ", p_mesh_list.size(), " entries"); + if (!differs(_mesh_list, p_mesh_list)) { + return; + } _set_asset_list(TYPE_MESH, p_mesh_list); update_mesh_list(); } @@ -571,18 +589,18 @@ void Terrain3DAssets::create_mesh_thumbnails(const int p_id, const Vector2i &p_s LOG(DEBUG, "Creating thumbnails for ids: ", start, " through ", end - 1); for (int i = start; i < end; i++) { Ref ma = get_mesh_asset(i); - LOG(DEBUG, i, ": Getting Terrain3DMeshAsset: ", ptr_to_str(*ma)); + LOG(EXTREME, i, ": Getting Terrain3DMeshAsset: ", ptr_to_str(*ma)); if (ma.is_null()) { LOG(ERROR, i, ": Terrain3DMeshAsset is null, skipping"); continue; } if (!p_force && ma->get_thumbnail().is_valid()) { - LOG(DEBUG, "Thumbnail already generated, skipping"); + LOG(EXTREME, "Thumbnail already generated, skipping"); continue; } // Setup mesh Ref mesh = ma->get_mesh(0); - LOG(DEBUG, i, ": Getting Mesh 0: ", mesh); + LOG(EXTREME, i, ": Getting Mesh 0: ", mesh); if (mesh.is_null()) { LOG(ERROR, i, ": Mesh is null, skipping"); continue; @@ -623,10 +641,10 @@ void Terrain3DAssets::create_mesh_thumbnails(const int p_id, const Vector2i &p_s Ref img = RS->texture_2d_get(_viewport_texture); RS->instance_set_base(_mesh_instance, RID()); // Clear mesh if (img.is_valid()) { - LOG(DEBUG, i, ": Retrieving image: ", img, " size: ", img->get_size(), " format: ", img->get_format()); + LOG(EXTREME, i, ": Retrieving image: ", img, " size: ", img->get_size(), " format: ", img->get_format()); ma->set_thumbnail(ImageTexture::create_from_image(img)); } else { - LOG(DEBUG, "_viewport_texture is null"); + LOG(ERROR, "_viewport_texture is null. Couldn't create thumbnail picture"); } } return; diff --git a/src/terrain_3d_collision.cpp b/src/terrain_3d_collision.cpp index 9372a6557..2d2bb2093 100644 --- a/src/terrain_3d_collision.cpp +++ b/src/terrain_3d_collision.cpp @@ -449,22 +449,20 @@ void Terrain3DCollision::destroy() { } void Terrain3DCollision::set_mode(const CollisionMode p_mode) { + SET_IF_DIFF(_mode, p_mode); LOG(INFO, "Setting collision mode: ", p_mode); - if (p_mode != _mode) { - _mode = p_mode; - if (is_enabled()) { - build(); - } else { - destroy(); - } + if (is_enabled()) { + build(); + } else { + destroy(); } } void Terrain3DCollision::set_shape_size(const uint16_t p_size) { - int size = CLAMP(p_size, 8, 64); - size = int_round_mult(size, 8); - LOG(INFO, "Setting collision dynamic shape size: ", size); - _shape_size = size; + uint16_t size = CLAMP(p_size, 8, 64); + size = int_round_mult(size, uint16_t(8)); + SET_IF_DIFF(_shape_size, size); + LOG(INFO, "Setting collision dynamic shape size: ", _shape_size); // Ensure size:radius always results in at least one valid shape if (_shape_size > _radius - 8) { set_radius(_shape_size + 16); @@ -474,10 +472,10 @@ void Terrain3DCollision::set_shape_size(const uint16_t p_size) { } void Terrain3DCollision::set_radius(const uint16_t p_radius) { - int radius = CLAMP(p_radius, 16, 256); - radius = int_ceil_pow2(radius, 16); - LOG(INFO, "Setting collision dynamic radius: ", radius); - _radius = radius; + uint16_t radius = CLAMP(p_radius, 16, 256); + radius = int_ceil_pow2(radius, uint16_t(16)); + SET_IF_DIFF(_radius, radius); + LOG(INFO, "Setting collision dynamic radius: ", _radius); // Ensure size:radius always results in at least one valid shape if (_radius < _shape_size + 8) { set_shape_size(_radius - 8); @@ -489,8 +487,8 @@ void Terrain3DCollision::set_radius(const uint16_t p_radius) { } void Terrain3DCollision::set_layer(const uint32_t p_layers) { + SET_IF_DIFF(_layer, p_layers); LOG(INFO, "Setting collision layers: ", p_layers); - _layer = p_layers; if (is_editor_mode()) { if (_static_body) { _static_body->set_collision_layer(_layer); @@ -503,8 +501,8 @@ void Terrain3DCollision::set_layer(const uint32_t p_layers) { } void Terrain3DCollision::set_mask(const uint32_t p_mask) { + SET_IF_DIFF(_mask, p_mask); LOG(INFO, "Setting collision mask: ", p_mask); - _mask = p_mask; if (is_editor_mode()) { if (_static_body) { _static_body->set_collision_mask(_mask); @@ -517,8 +515,8 @@ void Terrain3DCollision::set_mask(const uint32_t p_mask) { } void Terrain3DCollision::set_priority(const real_t p_priority) { + SET_IF_DIFF(_priority, p_priority); LOG(INFO, "Setting collision priority: ", p_priority); - _priority = p_priority; if (is_editor_mode()) { if (_static_body) { _static_body->set_collision_priority(_priority); @@ -531,7 +529,9 @@ void Terrain3DCollision::set_priority(const real_t p_priority) { } void Terrain3DCollision::set_physics_material(const Ref &p_mat) { - LOG(INFO, "Setting physics material: ", p_mat); + if (_physics_material == p_mat) { + return; + } if (_physics_material.is_valid()) { if (_physics_material->is_connected("changed", callable_mp(this, &Terrain3DCollision::_reload_physics_material))) { LOG(DEBUG, "Disconnecting _physics_material::changed signal to _reload_physics_material()"); @@ -539,6 +539,7 @@ void Terrain3DCollision::set_physics_material(const Ref &p_mat) } } _physics_material = p_mat; + LOG(INFO, "Setting physics material: ", p_mat); if (_physics_material.is_valid()) { LOG(DEBUG, "Connecting _physics_material::changed signal to _reload_physics_material()"); _physics_material->connect("changed", callable_mp(this, &Terrain3DCollision::_reload_physics_material)); diff --git a/src/terrain_3d_data.cpp b/src/terrain_3d_data.cpp index 40e717cfd..81c85f28b 100644 --- a/src/terrain_3d_data.cpp +++ b/src/terrain_3d_data.cpp @@ -65,8 +65,8 @@ void Terrain3DData::initialize(Terrain3D *p_terrain) { } void Terrain3DData::set_region_locations(const TypedArray &p_locations) { + SET_IF_DIFF(_region_locations, p_locations); LOG(INFO, "Setting _region_locations with array sized: ", p_locations.size()); - _region_locations = p_locations; _region_map_dirty = true; update_maps(TYPE_MAX, false, false); // only rebuild region map } @@ -444,9 +444,18 @@ 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 (int i = 0; i < _region_locations.size(); i++) { Vector2i region_loc = _region_locations[i]; @@ -551,14 +560,16 @@ void Terrain3DData::update_maps(const MapType p_map_type, const bool p_all_regio if (_generated_color_maps.is_dirty()) { LOG(EXTREME, "Regenerating color texture array from regions"); _color_maps.clear(); - for (int i = 0; i < _region_locations.size(); i++) { - Vector2i region_loc = _region_locations[i]; - const Terrain3DRegion *region = get_region_ptr(region_loc); - if (region) { - _color_maps.push_back(region->get_color_map()); + if (_color_map_enabled) { + for (int i = 0; i < _region_locations.size(); i++) { + Vector2i region_loc = _region_locations[i]; + const Terrain3DRegion *region = get_region_ptr(region_loc); + if (region) { + _color_maps.push_back(region->get_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"); @@ -586,7 +597,9 @@ void Terrain3DData::update_maps(const MapType p_map_type, const bool p_all_regio any_changed = true; break; case TYPE_COLOR: - _generated_color_maps.update(region->get_color_map(), region_id); + if (_color_map_enabled) { + _generated_color_maps.update(region->get_color_map(), region_id); + } LOG(DEBUG, "Emitting color_maps_changed"); emit_signal("color_maps_changed"); any_changed = true; @@ -594,7 +607,9 @@ void Terrain3DData::update_maps(const MapType p_map_type, const bool p_all_regio 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_color_map(), region_id); + } LOG(DEBUG, "Emitting height_maps_changed"); emit_signal("height_maps_changed"); LOG(DEBUG, "Emitting control_maps_changed"); @@ -610,7 +625,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(); + } } } @@ -1203,6 +1220,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); @@ -1255,6 +1274,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..124e23610 100644 --- a/src/terrain_3d_data.h +++ b/src/terrain_3d_data.h @@ -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. @@ -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_editor.cpp b/src/terrain_3d_editor.cpp index ca8388553..3e948a121 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -898,14 +898,17 @@ void Terrain3DEditor::set_brush_data(const Dictionary &p_data) { } void Terrain3DEditor::set_tool(const Tool p_tool) { - if (_terrain && _tool != p_tool && (_tool == Tool::NAVIGATION || p_tool == Tool::NAVIGATION)) { - _tool = CLAMP(p_tool, Tool(0), TOOL_MAX); + Tool old_tool = _tool; + SET_IF_DIFF(_tool, CLAMP(p_tool, Tool(0), TOOL_MAX)); + if (_terrain && (_tool == Tool::NAVIGATION || old_tool == Tool::NAVIGATION)) { _terrain->get_material()->update(); - } else { - _tool = p_tool; } } +void Terrain3DEditor::set_operation(const Operation p_operation) { + SET_IF_DIFF(_operation, CLAMP(p_operation, Operation(0), OP_MAX)); +} + // Called on mouse click void Terrain3DEditor::start_operation(const Vector3 &p_global_position) { IS_DATA_INIT_MESG("Terrain isn't initialized", VOID); diff --git a/src/terrain_3d_editor.h b/src/terrain_3d_editor.h index 720d83099..89ec4a383 100644 --- a/src/terrain_3d_editor.h +++ b/src/terrain_3d_editor.h @@ -112,7 +112,7 @@ class Terrain3DEditor : public Object { Dictionary get_brush_data() const { return _brush_data; }; void set_tool(const Tool p_tool); Tool get_tool() const { return _tool; } - void set_operation(const Operation p_operation) { _operation = CLAMP(p_operation, Operation(0), OP_MAX); } + void set_operation(const Operation p_operation); Operation get_operation() const { return _operation; } void start_operation(const Vector3 &p_global_position); diff --git a/src/terrain_3d_instancer.cpp b/src/terrain_3d_instancer.cpp index 99751d7bc..f4bffee47 100644 --- a/src/terrain_3d_instancer.cpp +++ b/src/terrain_3d_instancer.cpp @@ -129,7 +129,7 @@ void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region, return; } if (!ma->is_enabled()) { - LOG(INFO, "Disabling mesh ", p_mesh_id, " in region ", region_loc, ": destroying MMIs"); + LOG(DEBUG, "Disabling mesh ", p_mesh_id, " in region ", region_loc, ": destroying MMIs"); _destroy_mmi_by_location(region_loc, p_mesh_id); return; } @@ -156,7 +156,7 @@ void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region, // Clean MMIs if xforms have been removed if (xforms.size() == 0) { - LOG(DEBUG, "Empty cell in region ", region_loc, " cell ", cell, ": destroying MMIs"); + LOG(EXTREME, "Empty cell in region ", region_loc, " mesh ", p_mesh_id, " cell ", cell, ": destroying MMIs"); _destroy_mmi_by_cell(region_loc, p_mesh_id, cell); continue; } @@ -164,7 +164,7 @@ void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region, for (int lod = 0; lod < Terrain3DMeshAsset::MAX_LOD_COUNT; lod++) { if (lod > ma->get_last_lod() || (ma->get_cast_shadows() == SHADOWS_ONLY && (lod > ma->get_last_shadow_lod() || lod < ma->get_shadow_impostor()))) { _destroy_mmi_by_cell(region_loc, p_mesh_id, cell, lod); - LOG(DEBUG, "Destroyed old MMIs, LOD ", lod, " in cell ", cell); + LOG(EXTREME, "Destroyed old MMIs mesh ", p_mesh_id, " cell ", cell, ", LOD ", lod); } } // Clean Shadow MMIs @@ -172,7 +172,7 @@ void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region, ma->get_cast_shadows() == SHADOWS_OFF); if (shadow_lod_disabled) { _destroy_mmi_by_cell(region_loc, p_mesh_id, cell, Terrain3DMeshAsset::SHADOW_LOD_ID); - LOG(DEBUG, "Destroyed stale shadow MMI for disabled impostor in cell ", cell); + LOG(EXTREME, "Destroyed stale shadow MMI mesh ", p_mesh_id, " for disabled impostor in cell ", cell); } // Setup MMIs for each LOD + shadows @@ -198,7 +198,7 @@ void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region, MultiMeshInstance3D *mmi = cell_mmi_dict[cell]; // null if missing if (!mmi) { mmi = memnew(MultiMeshInstance3D); - LOG(DEBUG, "No MMI found, Created new MultiMeshInstance3D for cell ", cell, ": ", ptr_to_str(mmi)); + LOG(EXTREME, "No MMI found, Created new MultiMeshInstance3D for cell ", cell, ": ", ptr_to_str(mmi)); // Node name is MMI3D_Cell##_##_Mesh#_LOD# String cstring = "_C" + Util::location_to_string(cell).trim_prefix("_"); String mstring = "_M" + String::num_int64(p_mesh_id); @@ -619,6 +619,18 @@ void Terrain3DInstancer::clear_by_region(const Ref &p_region, c _destroy_mmi_by_location(region_loc, p_mesh_id); } +void Terrain3DInstancer::set_mode(const InstancerMode p_mode) { + LOG(INFO, "Setting instancer mode: ", p_mode); + if (p_mode != _mode) { + _mode = p_mode; + if (is_enabled()) { + update_mmis(-1, V2I_MAX, true); + } else { + destroy(); + } + } +} + 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); @@ -626,13 +638,13 @@ void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const D LOG(ERROR, "Mesh ID out of range: ", mesh_id, ", valid: 0 to ", _terrain->get_assets()->get_mesh_count() - 1); 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 * .5f; 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% + 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); @@ -900,13 +912,14 @@ void Terrain3DInstancer::add_transforms(const int p_mesh_id, const TypedArray mesh_asset = _terrain->get_assets()->get_mesh_asset(p_mesh_id); + real_t height_offset = mesh_asset->get_height_offset(); // Separate incoming transforms/colors by region Dict{ region_loc => Array() } LOG(INFO, "Separating ", p_xforms.size(), " transforms and ", p_colors.size(), " colors into regions"); for (int i = 0; i < p_xforms.size(); i++) { // Get adjusted xform/color Transform3D trns = p_xforms[i]; - trns.origin += trns.basis.get_column(1) * mesh_asset->get_height_offset(); // Offset along UP axis + trns.origin += trns.basis.get_column(1) * height_offset; // Offset along UP axis Color col = COLOR_WHITE; if (p_colors.size() > i) { col = p_colors[i]; @@ -1018,7 +1031,7 @@ void Terrain3DInstancer::update_transforms(const AABB &p_aabb) { Vector2 global_position = rect.get_center(); Vector2 size = rect.get_size(); Vector2 half_size = size * 0.5f + V2(1.f); // 1m margin - if (size == V2_ZERO) { + if (size.is_zero_approx()) { return; } @@ -1297,6 +1310,10 @@ void Terrain3DInstancer::swap_ids(const int p_src_id, const int p_dst_id) { // If mesh_id < 0, will do all meshes in the specified region // You safely can call multiple times per frame, and select any combo of options without fillling up the queue. void Terrain3DInstancer::update_mmis(const int p_mesh_id, const Vector2i &p_region_loc, const bool p_rebuild) { + if (_mode == DISABLED) { + LOG(INFO, "Instancer is disabled"); + return; + } LOG(INFO, "Queueing MMI update for mesh id: ", p_mesh_id < 0 ? "all" : String::num_int64(p_mesh_id), ", region: ", p_region_loc == V2I_MAX ? "all" : String(p_region_loc), p_rebuild ? ", destroying first" : ""); @@ -1360,9 +1377,15 @@ void Terrain3DInstancer::dump_mmis() { /////////////////////////// void Terrain3DInstancer::_bind_methods() { + BIND_ENUM_CONSTANT(DISABLED); + BIND_ENUM_CONSTANT(NORMAL); + ClassDB::bind_method(D_METHOD("clear_by_mesh", "mesh_id"), &Terrain3DInstancer::clear_by_mesh); ClassDB::bind_method(D_METHOD("clear_by_location", "region_location", "mesh_id"), &Terrain3DInstancer::clear_by_location); ClassDB::bind_method(D_METHOD("clear_by_region", "region", "mesh_id"), &Terrain3DInstancer::clear_by_region); + ClassDB::bind_method(D_METHOD("set_mode", "mode"), &Terrain3DInstancer::set_mode); + ClassDB::bind_method(D_METHOD("get_mode"), &Terrain3DInstancer::get_mode); + ClassDB::bind_method(D_METHOD("is_enabled"), &Terrain3DInstancer::is_enabled); ClassDB::bind_method(D_METHOD("add_instances", "global_position", "params"), &Terrain3DInstancer::add_instances); ClassDB::bind_method(D_METHOD("remove_instances", "global_position", "params"), &Terrain3DInstancer::remove_instances); ClassDB::bind_method(D_METHOD("add_multimesh", "mesh_id", "multimesh", "transform", "update"), &Terrain3DInstancer::add_multimesh, DEFVAL(Transform3D()), DEFVAL(true)); @@ -1374,4 +1397,6 @@ void Terrain3DInstancer::_bind_methods() { ClassDB::bind_method(D_METHOD("update_mmis", "mesh_id", "region_location", "rebuild_all"), &Terrain3DInstancer::update_mmis, DEFVAL(-1), DEFVAL(V2I_MAX), DEFVAL(false)); ClassDB::bind_method(D_METHOD("swap_ids", "src_id", "dest_id"), &Terrain3DInstancer::swap_ids); ClassDB::bind_method(D_METHOD("dump_mmis"), &Terrain3DInstancer::dump_mmis); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Disabled,Normal"), "set_mode", "get_mode"); } diff --git a/src/terrain_3d_instancer.h b/src/terrain_3d_instancer.h index a7f13e952..b7b86298f 100644 --- a/src/terrain_3d_instancer.h +++ b/src/terrain_3d_instancer.h @@ -22,6 +22,11 @@ class Terrain3DInstancer : public Object { public: // Constants static inline const int CELL_SIZE = 32; + enum InstancerMode { + DISABLED, + NORMAL, + }; + private: Terrain3D *_terrain = nullptr; @@ -46,6 +51,7 @@ class Terrain3DInstancer : public Object { using V2IIntPair = std::unordered_set, PairVector2iIntHash>; V2IIntPair _queued_updates; + InstancerMode _mode = NORMAL; uint32_t _density_counter = 0; uint32_t _get_density_count(const real_t p_density); @@ -72,6 +78,10 @@ class Terrain3DInstancer : public Object { void clear_by_location(const Vector2i &p_region_loc, const int p_mesh_id); void clear_by_region(const Ref &p_region, const int p_mesh_id); + void set_mode(const InstancerMode p_mode); + InstancerMode get_mode() const { return _mode; } + bool is_enabled() const { return _mode > DISABLED; } + void add_instances(const Vector3 &p_global_position, const Dictionary &p_params); void remove_instances(const Vector3 &p_global_position, const Dictionary &p_params); void add_multimesh(const int p_mesh_id, const Ref &p_multimesh, const Transform3D &p_xform = Transform3D(), const bool p_update = true); @@ -94,6 +104,9 @@ class Terrain3DInstancer : public Object { static void _bind_methods(); }; +using InstancerMode = Terrain3DInstancer::InstancerMode; +VARIANT_ENUM_CAST(Terrain3DInstancer::InstancerMode); + // Allows us to instance every X function calls for sparse placement // Modifies _density_counter, not const! inline uint32_t Terrain3DInstancer::_get_density_count(const real_t p_density) { diff --git a/src/terrain_3d_material.cpp b/src/terrain_3d_material.cpp index 00e8e1619..efd9edba8 100644 --- a/src/terrain_3d_material.cpp +++ b/src/terrain_3d_material.cpp @@ -37,7 +37,7 @@ void Terrain3DMaterial::_preload_shaders() { , "debug_views"); _parse_shader( #include "shaders/overlays.glsl" - , "debug_views"); + , "overlays"); _parse_shader( #include "shaders/editor_functions.glsl" , "editor_functions"); @@ -513,6 +513,7 @@ void Terrain3DMaterial::_update_maps() { RS->material_set_param(_material, "_height_maps", data->get_height_maps_rid()); RS->material_set_param(_material, "_control_maps", data->get_control_maps_rid()); RS->material_set_param(_material, "_color_maps", data->get_color_maps_rid()); + RS->material_set_param(_material, "_color_map_enabled", data->get_color_map_enabled()); LOG(EXTREME, "Height map RID: ", data->get_height_maps_rid()); LOG(EXTREME, "Control map RID: ", data->get_control_maps_rid()); LOG(EXTREME, "Color map RID: ", data->get_color_maps_rid()); @@ -559,8 +560,8 @@ void Terrain3DMaterial::_update_texture_arrays() { } void Terrain3DMaterial::_set_shader_parameters(const Dictionary &p_dict) { + SET_IF_DIFF(_shader_params, p_dict); LOG(INFO, "Setting shader params dictionary: ", p_dict.size()); - _shader_params = p_dict; } /////////////////////////// @@ -611,32 +612,32 @@ void Terrain3DMaterial::update() { } void Terrain3DMaterial::set_world_background(const WorldBackground p_background) { + SET_IF_DIFF(_world_background, p_background); LOG(INFO, "Enable world background: ", p_background); - _world_background = p_background; _update_shader(); } void Terrain3DMaterial::set_texture_filtering(const TextureFiltering p_filtering) { + SET_IF_DIFF(_texture_filtering, p_filtering); LOG(INFO, "Setting texture filtering: ", p_filtering); - _texture_filtering = p_filtering; _update_shader(); } void Terrain3DMaterial::set_auto_shader(const bool p_enabled) { + SET_IF_DIFF(_auto_shader, p_enabled); LOG(INFO, "Enable auto shader: ", p_enabled); - _auto_shader = p_enabled; _update_shader(); } void Terrain3DMaterial::set_dual_scaling(const bool p_enabled) { + SET_IF_DIFF(_dual_scaling, p_enabled); LOG(INFO, "Enable dual scaling: ", p_enabled); - _dual_scaling = p_enabled; _update_shader(); } -void Terrain3DMaterial::enable_shader_override(const bool p_enabled) { +void Terrain3DMaterial::set_shader_override_enabled(const bool p_enabled) { + SET_IF_DIFF(_shader_override_enabled, p_enabled); LOG(INFO, "Enable shader override: ", p_enabled); - _shader_override_enabled = p_enabled; if (_shader_override_enabled && _shader_override.is_null()) { LOG(DEBUG, "Instantiating new _shader_override"); _shader_override.instantiate(); @@ -646,7 +647,7 @@ void Terrain3DMaterial::enable_shader_override(const bool p_enabled) { void Terrain3DMaterial::set_shader_override(const Ref &p_shader) { LOG(INFO, "Setting override shader"); - _shader_override = p_shader; + SET_IF_DIFF(_shader_override, p_shader); _update_shader(); } @@ -663,116 +664,116 @@ Variant Terrain3DMaterial::get_shader_param(const StringName &p_name) const { } void Terrain3DMaterial::set_show_region_grid(const bool p_enabled) { + SET_IF_DIFF(_show_region_grid, p_enabled); LOG(INFO, "Enable show_region_grid: ", p_enabled); - _show_region_grid = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_instancer_grid(const bool p_enabled) { + SET_IF_DIFF(_show_instancer_grid, p_enabled); LOG(INFO, "Enable show_instancer_grid: ", p_enabled); - _show_instancer_grid = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_vertex_grid(const bool p_enabled) { + SET_IF_DIFF(_show_vertex_grid, p_enabled); LOG(INFO, "Enable show_vertex_grid: ", p_enabled); - _show_vertex_grid = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_contours(const bool p_enabled) { + SET_IF_DIFF(_show_contours, p_enabled); LOG(INFO, "Enable show_contours: ", p_enabled); - _show_contours = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_navigation(const bool p_enabled) { + SET_IF_DIFF(_show_navigation, p_enabled); LOG(INFO, "Enable show_navigation: ", p_enabled); - _show_navigation = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_checkered(const bool p_enabled) { + SET_IF_DIFF(_debug_view_checkered, p_enabled); LOG(INFO, "Enable set_show_checkered: ", p_enabled); - _debug_view_checkered = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_grey(const bool p_enabled) { + SET_IF_DIFF(_debug_view_grey, p_enabled); LOG(INFO, "Enable show_grey: ", p_enabled); - _debug_view_grey = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_heightmap(const bool p_enabled) { + SET_IF_DIFF(_debug_view_heightmap, p_enabled); LOG(INFO, "Enable show_heightmap: ", p_enabled); - _debug_view_heightmap = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_jaggedness(const bool p_enabled) { + SET_IF_DIFF(_debug_view_jaggedness, p_enabled); LOG(INFO, "Enable show_jaggedness: ", p_enabled); - _debug_view_jaggedness = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_autoshader(const bool p_enabled) { + SET_IF_DIFF(_debug_view_autoshader, p_enabled); LOG(INFO, "Enable show_autoshader: ", p_enabled); - _debug_view_autoshader = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_control_texture(const bool p_enabled) { + SET_IF_DIFF(_debug_view_control_texture, p_enabled); LOG(INFO, "Enable show_control_texture: ", p_enabled); - _debug_view_control_texture = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_control_blend(const bool p_enabled) { + SET_IF_DIFF(_debug_view_control_blend, p_enabled); LOG(INFO, "Enable show_control_blend: ", p_enabled); - _debug_view_control_blend = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_control_angle(const bool p_enabled) { + SET_IF_DIFF(_debug_view_control_angle, p_enabled); LOG(INFO, "Enable show_control_angle: ", p_enabled); - _debug_view_control_angle = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_control_scale(const bool p_enabled) { + SET_IF_DIFF(_debug_view_control_scale, p_enabled); LOG(INFO, "Enable show_control_scale: ", p_enabled); - _debug_view_control_scale = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_colormap(const bool p_enabled) { + SET_IF_DIFF(_debug_view_colormap, p_enabled); LOG(INFO, "Enable show_colormap: ", p_enabled); - _debug_view_colormap = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_roughmap(const bool p_enabled) { + SET_IF_DIFF(_debug_view_roughmap, p_enabled); LOG(INFO, "Enable show_roughmap: ", p_enabled); - _debug_view_roughmap = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_texture_height(const bool p_enabled) { + SET_IF_DIFF(_debug_view_tex_height, p_enabled); LOG(INFO, "Enable show_texture_height: ", p_enabled); - _debug_view_tex_height = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_texture_normal(const bool p_enabled) { + SET_IF_DIFF(_debug_view_tex_normal, p_enabled); LOG(INFO, "Enable show_texture_normal: ", p_enabled); - _debug_view_tex_normal = p_enabled; _update_shader(); } void Terrain3DMaterial::set_show_texture_rough(const bool p_enabled) { + SET_IF_DIFF(_debug_view_tex_rough, p_enabled); LOG(INFO, "Enable show_texture_rough: ", p_enabled); - _debug_view_tex_rough = p_enabled; _update_shader(); } @@ -970,7 +971,7 @@ void Terrain3DMaterial::_bind_methods() { 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("enable_shader_override", "enabled"), &Terrain3DMaterial::enable_shader_override); + 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); ClassDB::bind_method(D_METHOD("set_shader_override", "shader"), &Terrain3DMaterial::set_shader_override); ClassDB::bind_method(D_METHOD("get_shader_override"), &Terrain3DMaterial::get_shader_override); @@ -1027,7 +1028,7 @@ void Terrain3DMaterial::_bind_methods() { 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, "shader_override_enabled"), "enable_shader_override", "is_shader_override_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"); // Hidden in Material, aliased in Terrain3D diff --git a/src/terrain_3d_material.h b/src/terrain_3d_material.h index 382653abc..070ea5764 100644 --- a/src/terrain_3d_material.h +++ b/src/terrain_3d_material.h @@ -103,7 +103,7 @@ class Terrain3DMaterial : public Resource { void set_dual_scaling(const bool p_enabled); bool get_dual_scaling() const { return _dual_scaling; } - void enable_shader_override(const bool p_enabled); + void set_shader_override_enabled(const bool p_enabled); bool is_shader_override_enabled() const { return _shader_override_enabled; } void set_shader_override(const Ref &p_shader); Ref get_shader_override() const { return _shader_override; } diff --git a/src/terrain_3d_mesh_asset.cpp b/src/terrain_3d_mesh_asset.cpp index cc32ebf89..85d28c18e 100644 --- a/src/terrain_3d_mesh_asset.cpp +++ b/src/terrain_3d_mesh_asset.cpp @@ -31,7 +31,7 @@ bool Terrain3DMeshAsset::_sort_lod_nodes(const Node *a, const Node *b) { } Ref Terrain3DMeshAsset::_get_generated_mesh() const { - LOG(EXTREME, "Regeneratingn new mesh"); + LOG(EXTREME, "Regenerating new mesh"); Ref array_mesh; array_mesh.instantiate(); PackedVector3Array vertices; @@ -40,7 +40,7 @@ Ref Terrain3DMeshAsset::_get_generated_mesh() const { PackedVector2Array uvs; PackedInt32Array indices; - int i, j, prevrow, thisrow, point = 0; + int prevrow, thisrow, point = 0; float x, z; Size2 start_pos = Vector2(_generated_size.x * -0.5f, -0.5f); Vector3 normal = Vector3(0.f, 0.f, 1.f); @@ -126,46 +126,43 @@ void Terrain3DMeshAsset::clear() { _highlight_mat = Ref(); _enabled = true; _packed_scene.unref(); - _meshes.clear(); - _thumbnail.unref(); + _generated_type = TYPE_TEXTURE_CARD; + _generated_faces = 2.f; + _generated_size = V2(1.f); _height_offset = 0.f; _density = 10.f; _cast_shadows = SHADOWS_ON; _material_override.unref(); _material_overlay.unref(); - _generated_type = TYPE_TEXTURE_CARD; - _generated_faces = 2.f; - _generated_size = V2(1.f); _last_lod = MAX_LOD_COUNT - 1; _last_shadow_lod = MAX_LOD_COUNT - 1; _shadow_impostor = 0; - _fade_margin = 0.f; _clear_lod_ranges(); + _fade_margin = 0.f; + _meshes.clear(); + _thumbnail.unref(); } void Terrain3DMeshAsset::set_name(const String &p_name) { if (p_name.length() > 96) { LOG(WARN, "Name too long, truncating to 96 characters"); } - LOG(INFO, "Setting name: ", p_name.left(96)); - _name = p_name.left(96); + SET_IF_DIFF(_name, p_name.left(96)); + LOG(INFO, "Setting name: ", _name); LOG(DEBUG, "Emitting setting_changed, ID: ", _id); emit_signal("setting_changed", _id); } void Terrain3DMeshAsset::set_id(const int p_new_id) { int old_id = _id; - _id = CLAMP(p_new_id, 0, Terrain3DAssets::MAX_MESHES - 1); + SET_IF_DIFF(_id, CLAMP(p_new_id, 0, Terrain3DAssets::MAX_MESHES - 1)); LOG(INFO, "Setting mesh ID: ", _id); LOG(DEBUG, "Emitting id_changed, TYPE_MESH, ", old_id, ", ", p_new_id); emit_signal("id_changed", Terrain3DAssets::TYPE_MESH, old_id, p_new_id); } void Terrain3DMeshAsset::set_highlighted(const bool p_highlighted) { - if (p_highlighted == _highlighted) { - return; // No change - } - _highlighted = p_highlighted; + SET_IF_DIFF(_highlighted, p_highlighted); LOG(INFO, "Set mesh ID ", _id, " highlight: ", p_highlighted); if (_highlighted && _highlight_mat.is_null()) { Ref mat; @@ -190,13 +187,16 @@ Color Terrain3DMeshAsset::get_highlight_color() const { } void Terrain3DMeshAsset::set_enabled(const bool p_enabled) { - _enabled = p_enabled; + SET_IF_DIFF(_enabled, p_enabled); LOG(INFO, "Setting enabled: ", _enabled); LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } void Terrain3DMeshAsset::update_instance_count(const int p_amount) { + if (p_amount == 0) { + return; + } int new_count = _instance_count + p_amount; _instance_count = CLAMP(new_count, 0, UINT32_MAX); LOG(EXTREME, "Emitting instance_count_changed, ID: ", _id, ", count: ", _instance_count); @@ -204,14 +204,14 @@ void Terrain3DMeshAsset::update_instance_count(const int p_amount) { } void Terrain3DMeshAsset::set_instance_count(const uint32_t p_amount) { - _instance_count = CLAMP(p_amount, 0, UINT32_MAX); + SET_IF_DIFF(_instance_count, CLAMP(p_amount, 0, UINT32_MAX)); LOG(DEBUG, "Emitting instance_count_changed, ID: ", _id, ", count: ", _instance_count); emit_signal("instance_count_changed"); } void Terrain3DMeshAsset::set_scene_file(const Ref &p_scene_file) { + SET_IF_DIFF(_packed_scene, p_scene_file); LOG(INFO, "Setting scene file and instantiating node: ", p_scene_file); - _packed_scene = p_scene_file; _meshes.clear(); if (_packed_scene.is_valid()) { Node *node = _packed_scene->instantiate(); @@ -290,15 +290,21 @@ void Terrain3DMeshAsset::set_scene_file(const Ref &p_scene_file) { } void Terrain3DMeshAsset::set_generated_type(const GenType p_type) { - _generated_type = p_type; - LOG(INFO, "Setting is_generated: ", p_type); - if (p_type == TYPE_NONE && _packed_scene.is_null()) { + if (p_type < TYPE_NONE || p_type >= TYPE_MAX) { + LOG(ERROR, "Invalid generated type: ", p_type); + return; + } + bool changed = _generated_type != p_type; + _generated_type = p_type; // Always do this setup + if (_generated_type == TYPE_NONE && _packed_scene.is_null()) { _generated_type = TYPE_TEXTURE_CARD; + changed = true; } - if (p_type > TYPE_NONE && p_type < TYPE_MAX) { + LOG(INFO, "Setting generated type: ", _generated_type); + if (_generated_type > TYPE_NONE) { _packed_scene.unref(); _meshes.clear(); - LOG(DEBUG, "Generating card mesh"); + LOG(DEBUG, "Generating texture card mesh"); _meshes.push_back(_get_generated_mesh()); if (_material_override.is_null()) { _material_override = _get_material(); @@ -309,10 +315,13 @@ void Terrain3DMeshAsset::set_generated_type(const GenType p_type) { _last_shadow_lod = 0; _shadow_impostor = 0; _clear_lod_ranges(); + changed = true; } notify_property_list_changed(); // Call _validate_property to update inspector - LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); - emit_signal("instancer_setting_changed", _id); + if (changed) { + LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); + emit_signal("instancer_setting_changed", _id); + } } Ref Terrain3DMeshAsset::get_mesh(const int p_lod) const { @@ -323,21 +332,21 @@ Ref Terrain3DMeshAsset::get_mesh(const int p_lod) const { } void Terrain3DMeshAsset::set_height_offset(const real_t p_offset) { - _height_offset = CLAMP(p_offset, -50.f, 50.f); + SET_IF_DIFF(_height_offset, CLAMP(p_offset, -50.f, 50.f)); LOG(INFO, "Setting height offset: ", _height_offset); LOG(DEBUG, "Emitting setting_changed, ID: ", _id); emit_signal("setting_changed", _id); } void Terrain3DMeshAsset::set_density(const real_t p_density) { + SET_IF_DIFF(_density, CLAMP(p_density, 0.01f, 10.f)); LOG(INFO, "Setting mesh density: ", p_density); - _density = CLAMP(p_density, 0.01f, 10.f); LOG(DEBUG, "Emitting setting_changed, ID: ", _id); emit_signal("setting_changed", _id); } void Terrain3DMeshAsset::set_cast_shadows(const ShadowCasting p_cast_shadows) { - _cast_shadows = p_cast_shadows; + SET_IF_DIFF(_cast_shadows, p_cast_shadows); LOG(INFO, "Setting shadow casting mode: ", _cast_shadows); LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); @@ -369,52 +378,48 @@ ShadowCasting Terrain3DMeshAsset::get_lod_cast_shadows(const int p_lod_id) const } void Terrain3DMeshAsset::set_material_override(const Ref &p_material) { + SET_IF_DIFF(_material_override, p_material); LOG(INFO, _name, ": Setting material override: ", p_material); - _material_override = p_material; LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } void Terrain3DMeshAsset::set_material_overlay(const Ref &p_material) { + SET_IF_DIFF(_material_overlay, p_material); LOG(INFO, _name, ": Setting material overlay: ", p_material); - _material_overlay = p_material; LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); } void Terrain3DMeshAsset::set_generated_faces(const int p_count) { - if (_generated_faces != p_count) { - _generated_faces = CLAMP(p_count, 1, 3); - LOG(INFO, "Setting generated face count: ", _generated_faces); - if (_generated_type > TYPE_NONE && _generated_type < TYPE_MAX && _meshes.size() == 1) { - _meshes[0] = _get_generated_mesh(); - if (_material_override.is_null()) { - _material_override = _get_material(); - } - LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); - emit_signal("instancer_setting_changed", _id); + SET_IF_DIFF(_generated_faces, CLAMP(p_count, 1, 3)); + LOG(INFO, "Setting generated face count: ", _generated_faces); + if (_generated_type > TYPE_NONE && _generated_type < TYPE_MAX && _meshes.size() == 1) { + _meshes[0] = _get_generated_mesh(); + if (_material_override.is_null()) { + _material_override = _get_material(); } + LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); + emit_signal("instancer_setting_changed", _id); } } void Terrain3DMeshAsset::set_generated_size(const Vector2 &p_size) { - if (_generated_size != p_size) { - _generated_size = p_size; - LOG(INFO, "Setting generated size: ", _generated_faces); - if (_generated_type > TYPE_NONE && _generated_type < TYPE_MAX && _meshes.size() == 1) { - _meshes[0] = _get_generated_mesh(); - if (_material_override.is_null()) { - _material_override = _get_material(); - } - LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); - emit_signal("instancer_setting_changed", _id); + SET_IF_DIFF(_generated_size, p_size); + LOG(INFO, "Setting generated size: ", _generated_size); + if (_generated_type > TYPE_NONE && _generated_type < TYPE_MAX && _meshes.size() == 1) { + _meshes[0] = _get_generated_mesh(); + if (_material_override.is_null()) { + _material_override = _get_material(); } + LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); + emit_signal("instancer_setting_changed", _id); } } void Terrain3DMeshAsset::set_last_lod(const int p_lod) { int max_lod = _generated_type != TYPE_NONE ? 0 : CLAMP(_meshes.size(), 1, MAX_LOD_COUNT) - 1; - _last_lod = CLAMP(p_lod, 0, max_lod); + SET_IF_DIFF(_last_lod, CLAMP(p_lod, 0, max_lod)); if (_last_shadow_lod > _last_lod) { _last_shadow_lod = _last_lod; } @@ -428,7 +433,7 @@ void Terrain3DMeshAsset::set_last_lod(const int p_lod) { } void Terrain3DMeshAsset::set_last_shadow_lod(const int p_lod) { - _last_shadow_lod = CLAMP(p_lod, 0, _last_lod); + SET_IF_DIFF(_last_shadow_lod, CLAMP(p_lod, 0, _last_lod)); if (_shadow_impostor > _last_shadow_lod) { _shadow_impostor = _last_shadow_lod; } @@ -438,7 +443,7 @@ void Terrain3DMeshAsset::set_last_shadow_lod(const int p_lod) { } void Terrain3DMeshAsset::set_shadow_impostor(const int p_lod) { - _shadow_impostor = CLAMP(p_lod, 0, MIN(_last_lod, _last_shadow_lod)); + SET_IF_DIFF(_shadow_impostor, CLAMP(p_lod, 0, MIN(_last_lod, _last_shadow_lod))); LOG(INFO, "Setting shadow imposter LOD: ", _shadow_impostor); LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); @@ -446,9 +451,10 @@ void Terrain3DMeshAsset::set_shadow_impostor(const int p_lod) { void Terrain3DMeshAsset::set_lod_range(const int p_lod, const real_t p_distance) { if (p_lod < 0 || p_lod >= _lod_ranges.size()) { + LOG(ERROR, "p_lod out of range. Valid range is 0 - ", _lod_ranges.size() - 1); return; } - _lod_ranges[p_lod] = CLAMP(p_distance, 0.f, 100000.f); + SET_IF_DIFF(_lod_ranges[p_lod], CLAMP(p_distance, 0.f, 100000.f)); LOG(INFO, "Setting LOD ", p_lod, " visibility range: ", _lod_ranges[p_lod]); LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); @@ -479,7 +485,7 @@ real_t Terrain3DMeshAsset::get_lod_range_end(const int p_lod) const { void Terrain3DMeshAsset::set_fade_margin(const real_t p_fade_margin) { int max_range = CLAMP(_lod_ranges[1] - _lod_ranges[0], 0.f, 64.f); - _fade_margin = CLAMP(p_fade_margin, 0.f, max_range); + SET_IF_DIFF(_fade_margin, CLAMP(p_fade_margin, 0.f, max_range)); LOG(INFO, "Setting visibility margin: ", _fade_margin); LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id); emit_signal("instancer_setting_changed", _id); diff --git a/src/terrain_3d_mesh_asset.h b/src/terrain_3d_mesh_asset.h index 6890f0a27..68dc48b71 100644 --- a/src/terrain_3d_mesh_asset.h +++ b/src/terrain_3d_mesh_asset.h @@ -37,14 +37,13 @@ class Terrain3DMeshAsset : public Terrain3DAssetResource { bool _enabled = true; Ref _packed_scene; GenType _generated_type = TYPE_TEXTURE_CARD; - + int _generated_faces = 2; + Vector2 _generated_size = V2(1.f); real_t _height_offset = 0.f; real_t _density = 10.f; ShadowCasting _cast_shadows = SHADOWS_ON; Ref _material_override; Ref _material_overlay; - int _generated_faces = 2; - Vector2 _generated_size = V2(1.f); int _last_lod = MAX_LOD_COUNT - 1; int _last_shadow_lod = MAX_LOD_COUNT - 1; int _shadow_impostor = 0; diff --git a/src/terrain_3d_mesher.cpp b/src/terrain_3d_mesher.cpp index 498a35356..509579f11 100644 --- a/src/terrain_3d_mesher.cpp +++ b/src/terrain_3d_mesher.cpp @@ -298,6 +298,7 @@ void Terrain3DMesher::initialize(Terrain3D *p_terrain) { _generate_clipmap(size, lods, _terrain->get_world_3d()->get_scenario()); update(); update_aabbs(); + reset_target_position(); snap(); } diff --git a/src/terrain_3d_region.cpp b/src/terrain_3d_region.cpp index abb6d6679..736eed5fb 100644 --- a/src/terrain_3d_region.cpp +++ b/src/terrain_3d_region.cpp @@ -12,11 +12,16 @@ ///////////////////// void Terrain3DRegion::set_version(const real_t p_version) { - LOG(INFO, vformat("%.3f", p_version)); - if (_version > 0.8f && _version != p_version) { + real_t version = CLAMP(p_version, 0.8f, 100.f); + if (_version == version) { + return; + } + // Mark modified if already initialized and we get a new value + if (_version > 0.8f) { _modified = true; } - _version = p_version; + _version = version; + LOG(INFO, vformat("%.3f", _version)); if (_version < Terrain3DData::CURRENT_VERSION) { LOG(WARN, "Region ", get_path(), " version ", vformat("%.3f", _version), " will be updated to ", vformat("%.3f", Terrain3DData::CURRENT_VERSION), " upon save"); @@ -24,16 +29,16 @@ void Terrain3DRegion::set_version(const real_t p_version) { } void Terrain3DRegion::set_region_size(const int p_region_size) { - LOG(INFO, "Setting region ", _location, " size: ", p_region_size); if (!is_valid_region_size(p_region_size)) { LOG(ERROR, "Invalid region size: ", p_region_size, ". Must be power of 2, 64-2048"); return; } - // If already initialized and we get a new value + // Mark modified if already initialized and we get a new value if (_region_size > 0 && _region_size != p_region_size) { _modified = true; } - _region_size = p_region_size; + SET_IF_DIFF(_region_size, p_region_size); + LOG(INFO, "Setting region ", _location, " size: ", p_region_size); } void Terrain3DRegion::set_map(const MapType p_map_type, const Ref &p_image) { @@ -102,6 +107,7 @@ TypedArray Terrain3DRegion::get_maps() const { } void Terrain3DRegion::set_height_map(const Ref &p_map) { + SET_IF_DIFF(_height_map, p_map); LOG(INFO, "Setting height 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()); @@ -116,6 +122,7 @@ void Terrain3DRegion::set_height_map(const Ref &p_map) { } void Terrain3DRegion::set_control_map(const Ref &p_map) { + SET_IF_DIFF(_control_map, p_map); LOG(INFO, "Setting control 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()); @@ -129,6 +136,7 @@ void Terrain3DRegion::set_control_map(const Ref &p_map) { } void Terrain3DRegion::set_color_map(const Ref &p_map) { + SET_IF_DIFF(_color_map, p_map); LOG(INFO, "Setting 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()); @@ -225,14 +233,16 @@ bool Terrain3DRegion::validate_map_size(const Ref &p_map) const { } void Terrain3DRegion::set_height_range(const Vector2 &p_range) { - LOG(INFO, vformat("%.2v", p_range)); - if (_height_range != p_range) { - // If initial value, we're loading it from disk, else mark modified - if (_height_range != V2_ZERO) { + if (differs(_height_range, p_range)) { + // Mark modified if setting after initialization + if (!_height_range.is_zero_approx()) { _modified = true; } _height_range = p_range; - } + LOG(INFO, vformat("%.2v", p_range)); + } else { + return; + }; } void Terrain3DRegion::calc_height_range() { @@ -245,11 +255,27 @@ void Terrain3DRegion::calc_height_range() { } void Terrain3DRegion::set_instances(const Dictionary &p_instances) { + if (!_instances.is_empty() && !shares_ptr(_instances, p_instances)) { + _modified = true; + } + SET_IF_DIFF(_instances, p_instances); LOG(INFO, "Region ", _location, " setting instances ptr: ", ptr_to_str(p_instances._native_ptr())); - if (!_instances.is_empty() && _instances._native_ptr() != p_instances._native_ptr()) { +} + +void Terrain3DRegion::set_location(const Vector2i &p_location) { + // In the future anywhere they want to put the location might be fine, but because of region_map + // We have a limitation of 32x32. + if (Terrain3DData::get_region_map_index(p_location) < 0) { + LOG(ERROR, "Location ", p_location, " out of bounds. Max: ", + -Terrain3DData::REGION_MAP_SIZE / 2, " to ", Terrain3DData::REGION_MAP_SIZE / 2 - 1); + return; + } + // Marked modified if setting after initialized + if (_location < V2I_MAX && _location != p_location) { _modified = true; } - _instances = p_instances; + SET_IF_DIFF(_location, p_location); + LOG(INFO, "Set location: ", p_location); } Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) { @@ -293,21 +319,6 @@ Error Terrain3DRegion::save(const String &p_path, const bool p_16_bit) { return err; } -void Terrain3DRegion::set_location(const Vector2i &p_location) { - // In the future anywhere they want to put the location might be fine, but because of region_map - // We have a limitation of 16x16 and eventually 45x45. - if (Terrain3DData::get_region_map_index(p_location) < 0) { - LOG(ERROR, "Location ", p_location, " out of bounds. Max: ", - -Terrain3DData::REGION_MAP_SIZE / 2, " to ", Terrain3DData::REGION_MAP_SIZE / 2 - 1); - return; - } - LOG(INFO, "Set location: ", p_location); - if (_location < V2I_MAX && _location != p_location) { - _modified = true; - } - _location = p_location; -} - void Terrain3DRegion::set_data(const Dictionary &p_data) { #define SET_IF_HAS(var, str) \ if (p_data.has(str)) { \ diff --git a/src/terrain_3d_region.h b/src/terrain_3d_region.h index 6f9976bf8..2b233480f 100644 --- a/src/terrain_3d_region.h +++ b/src/terrain_3d_region.h @@ -95,9 +95,6 @@ class Terrain3DRegion : public Resource { void set_vertex_spacing(const real_t p_vertex_spacing) { _vertex_spacing = CLAMP(p_vertex_spacing, 0.25f, 100.f); } real_t get_vertex_spacing() const { return _vertex_spacing; } - // File I/O - Error save(const String &p_path = "", const bool p_16_bit = false); - // Working Data void set_deleted(const bool p_deleted) { _deleted = p_deleted; } bool is_deleted() const { return _deleted; } @@ -108,6 +105,9 @@ class Terrain3DRegion : public Resource { void set_location(const Vector2i &p_location); Vector2i get_location() const { return _location; } + // File I/O + Error save(const String &p_path = "", const bool p_16_bit = false); + // Utility void set_data(const Dictionary &p_data); Dictionary get_data() const; diff --git a/src/terrain_3d_texture_asset.cpp b/src/terrain_3d_texture_asset.cpp index 55ff58286..4983b5d5f 100644 --- a/src/terrain_3d_texture_asset.cpp +++ b/src/terrain_3d_texture_asset.cpp @@ -35,6 +35,7 @@ bool Terrain3DTextureAsset::_is_valid_format(const Ref &p_texture) co /////////////////////////// void Terrain3DTextureAsset::clear() { + LOG(INFO, "Clearing TextureAsset"); _name = "New Texture"; _id = 0; _highlighted = false; @@ -52,7 +53,7 @@ void Terrain3DTextureAsset::set_name(const String &p_name) { if (p_name.length() > 96) { LOG(WARN, "Name too long, truncating to 96 characters"); } - _name = p_name.left(96); + SET_IF_DIFF(_name, p_name.left(96)); LOG(INFO, "Setting name: ", _name); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); @@ -60,17 +61,14 @@ void Terrain3DTextureAsset::set_name(const String &p_name) { void Terrain3DTextureAsset::set_id(const int p_new_id) { int old_id = _id; - _id = CLAMP(p_new_id, 0, Terrain3DAssets::MAX_TEXTURES - 1); + SET_IF_DIFF(_id, CLAMP(p_new_id, 0, Terrain3DAssets::MAX_TEXTURES - 1)); LOG(INFO, "Setting texture id: ", _id); LOG(DEBUG, "Emitting id_changed, TYPE_TEXTURE, ", old_id, ", ", _id); emit_signal("id_changed", Terrain3DAssets::TYPE_TEXTURE, old_id, _id); } void Terrain3DTextureAsset::set_highlighted(const bool p_highlighted) { - if (p_highlighted == _highlighted) { - return; // No change - } - _highlighted = p_highlighted; + SET_IF_DIFF(_highlighted, p_highlighted); LOG(INFO, "Set mesh ID ", _id, " highlight: ", p_highlighted); real_t random_float = real_t(rand()) / real_t(RAND_MAX); _highlight_color.set_hsv(random_float, 1.f, 1.f, 1.f); @@ -79,21 +77,21 @@ void Terrain3DTextureAsset::set_highlighted(const bool p_highlighted) { } void Terrain3DTextureAsset::set_albedo_color(const Color &p_color) { + SET_IF_DIFF(_albedo_color, p_color); LOG(INFO, "Setting color: ", p_color); - _albedo_color = p_color; LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_albedo_texture(const Ref &p_texture) { - LOG(INFO, "Setting albedo texture: ", p_texture); if (_is_valid_format(p_texture)) { - _albedo_texture = p_texture; + SET_IF_DIFF(_albedo_texture, p_texture); + LOG(INFO, "Setting albedo texture: ", p_texture); if (p_texture.is_valid()) { String filename = p_texture->get_path().get_file().get_basename(); if (_name == "New Texture" && !p_texture->get_path().contains("::")) { _name = filename; - LOG(INFO, "Naming texture based on filename: ", _name); + LOG(INFO, "Setting name based on filename: ", _name); } Ref img = p_texture->get_image(); if (!img->has_mipmaps()) { @@ -112,9 +110,9 @@ void Terrain3DTextureAsset::set_albedo_texture(const Ref &p_texture) } void Terrain3DTextureAsset::set_normal_texture(const Ref &p_texture) { - LOG(INFO, "Setting normal texture: ", p_texture); if (_is_valid_format(p_texture)) { - _normal_texture = p_texture; + SET_IF_DIFF(_normal_texture, p_texture); + LOG(INFO, "Setting normal texture: ", p_texture); if (p_texture.is_valid()) { String filename = p_texture->get_path().get_file().get_basename(); Ref img = p_texture->get_image(); @@ -134,49 +132,49 @@ void Terrain3DTextureAsset::set_normal_texture(const Ref &p_texture) } void Terrain3DTextureAsset::set_normal_depth(const real_t p_normal_depth) { - _normal_depth = CLAMP(p_normal_depth, 0.0f, 2.0f); + SET_IF_DIFF(_normal_depth, CLAMP(p_normal_depth, 0.0f, 2.0f)); LOG(INFO, "Setting normal_depth: ", _normal_depth); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } -void Terrain3DTextureAsset::set_ao_strength(const real_t p_ao_strength) { - _ao_strength = CLAMP(p_ao_strength, 0.0f, 2.0f); - LOG(INFO, "Setting ao_strength: ", _ao_strength); +void Terrain3DTextureAsset::set_roughness(const real_t p_roughness) { + SET_IF_DIFF(_roughness, CLAMP(p_roughness, -1.0f, 1.0f)); + LOG(INFO, "Setting roughness modifier: ", _roughness); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } -void Terrain3DTextureAsset::set_roughness(const real_t p_roughness) { - _roughness = CLAMP(p_roughness, -1.0f, 1.0f); - LOG(INFO, "Setting roughness modifier: ", _roughness); +void Terrain3DTextureAsset::set_ao_strength(const real_t p_ao_strength) { + SET_IF_DIFF(_ao_strength, CLAMP(p_ao_strength, 0.0f, 2.0f)); + LOG(INFO, "Setting ao_strength: ", _ao_strength); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_uv_scale(const real_t p_scale) { - _uv_scale = CLAMP(p_scale, 0.001f, 100.0f); + SET_IF_DIFF(_uv_scale, CLAMP(p_scale, 0.001f, 100.0f)); LOG(INFO, "Setting uv_scale: ", _uv_scale); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_vertical_projection(const bool p_projection) { - _vertical_projection = p_projection; + SET_IF_DIFF(_vertical_projection, p_projection); LOG(INFO, "Setting uv projection: ", _vertical_projection); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_detiling_rotation(const real_t p_detiling_rotation) { - _detiling_rotation = CLAMP(p_detiling_rotation, 0.0f, 1.0f); + SET_IF_DIFF(_detiling_rotation, CLAMP(p_detiling_rotation, 0.0f, 1.0f)); LOG(INFO, "Setting detiling_rotation: ", _detiling_rotation); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); } void Terrain3DTextureAsset::set_detiling_shift(const real_t p_detiling_shift) { - _detiling_shift = CLAMP(p_detiling_shift, 0.0f, 1.0f); + SET_IF_DIFF(_detiling_shift, CLAMP(p_detiling_shift, 0.0f, 1.0f)); LOG(INFO, "Setting detiling_shift: ", _detiling_shift); LOG(DEBUG, "Emitting setting_changed"); emit_signal("setting_changed"); diff --git a/src/terrain_3d_util.h b/src/terrain_3d_util.h index 5a68c777f..0f7769fa1 100644 --- a/src/terrain_3d_util.h +++ b/src/terrain_3d_util.h @@ -273,4 +273,45 @@ _FORCE_INLINE_ String ptr_to_str(const void *p_ptr) { return "0x" + String::num_uint64(uint64_t(p_ptr), 16, true); } +// Trait to detect types with _native_ptr(): Dictionary, Array, String, etc +template +struct has_native_ptr : std::false_type {}; + +template +struct has_native_ptr()._native_ptr())>> + : std::true_type {}; + +// Returns true if Variants share an internal pointer +template +_FORCE_INLINE_ bool shares_ptr(const T &a, const T &b) { + static_assert(has_native_ptr::value); // Enforce type check via trait + static_assert(sizeof(godot::Variant) == 24); + auto pa = static_cast(a._native_ptr()); + auto pb = static_cast(b._native_ptr()); + return *reinterpret_cast(pa + 8) == + *reinterpret_cast(pb + 8); +} + +// Returns if A is different from B +// O(1) pointer compare for Array/TypedArray/Dictionary +// Operator==() otherwise +// Could be extended for PackedArray and other special types +template +_FORCE_INLINE_ bool differs(T &a, const T &b) { + if constexpr (std::is_base_of_v || + std::is_base_of_v) { + return !shares_ptr(a, b); + } else { + return !(a == b); + } +} + +// Sets A if different from B, otherwise returns +#define SET_IF_DIFF(a, b) \ + if (differs(a, b)) { \ + a = b; \ + } else { \ + return; \ + } + #endif // TERRAIN3D_UTIL_CLASS_H