diff options
author | Raffaele Picca <picster@pixelgod.net> | 2021-02-18 20:31:25 +0100 |
---|---|---|
committer | Raffaele Picca <picster@pixelgod.net> | 2021-02-18 20:31:25 +0100 |
commit | 6406cd4708aeec04c23ffdf098e3319a404e7ed9 (patch) | |
tree | 8faedb9b3f733cc6ee901407e3ac4d4ed8ae6012 /src/addons/rmsmartshape/plugin.gd | |
parent | c9b3470e43aed98e2a93a332cfc92c3b9ca05163 (diff) |
Initial Commit
Diffstat (limited to 'src/addons/rmsmartshape/plugin.gd')
-rw-r--r-- | src/addons/rmsmartshape/plugin.gd | 1503 |
1 files changed, 1503 insertions, 0 deletions
diff --git a/src/addons/rmsmartshape/plugin.gd b/src/addons/rmsmartshape/plugin.gd new file mode 100644 index 0000000..7c554d4 --- /dev/null +++ b/src/addons/rmsmartshape/plugin.gd @@ -0,0 +1,1503 @@ +tool +extends EditorPlugin + +""" +Common Abbreviations +et = editor transform (viewport's canvas transform) + +- Snapping using the build in functionality isn't going to happen + - https://github.com/godotengine/godot/issues/11180 + - https://godotengine.org/qa/18051/tool-script-in-3-0 +""" + +# Icons +const ICON_HANDLE = preload("assets/icon_editor_handle.svg") +const ICON_HANDLE_SELECTED = preload("assets/icon_editor_handle_selected.svg") +const ICON_HANDLE_BEZIER = preload("assets/icon_editor_handle_bezier.svg") +const ICON_HANDLE_CONTROL = preload("assets/icon_editor_handle_control.svg") +const ICON_ADD_HANDLE = preload("assets/icon_editor_handle_add.svg") +const ICON_CURVE_EDIT = preload("assets/icon_curve_edit.svg") +const ICON_CURVE_CREATE = preload("assets/icon_curve_create.svg") +const ICON_CURVE_DELETE = preload("assets/icon_curve_delete.svg") +const ICON_PIVOT_POINT = preload("assets/icon_editor_position.svg") +const ICON_COLLISION = preload("assets/icon_collision_polygon_2d.svg") +const ICON_INTERP_LINEAR = preload("assets/InterpLinear.svg") +const ICON_SNAP = preload("assets/icon_editor_snap.svg") +const ICON_IMPORT_CLOSED = preload("assets/closed_shape.png") +const ICON_IMPORT_OPEN = preload("assets/open_shape.png") +const FUNC = preload("plugin-functionality.gd") + +enum MODE { EDIT_VERT, EDIT_EDGE, SET_PIVOT, CREATE_VERT } + +enum ACTION_VERT { + NONE = 0, + MOVE_VERT = 1, + MOVE_CONTROL = 2, + MOVE_CONTROL_IN = 3, + MOVE_CONTROL_OUT = 4, + MOVE_WIDTH_HANDLE = 5 +} + + +# Data related to an action being taken on points +class ActionDataVert: + #Type of Action from the ACTION_VERT enum + var type: int = ACTION_VERT.NONE + # The affected Verticies and their initial positions + var keys = [] + var starting_width = [] + var starting_positions = [] + var starting_positions_control_in = [] + var starting_positions_control_out = [] + + func _init( + _keys: Array, + positions: Array, + positions_in: Array, + positions_out: Array, + width: Array, + t: int + ): + type = t + keys = _keys + starting_positions = positions + starting_positions_control_in = positions_in + starting_positions_control_out = positions_out + starting_width = width + + func are_verts_selected() -> bool: + return keys.size() > 0 + + func to_string() -> String: + var s = "%s: %s = %s" + return s % [type, keys, starting_positions] + + func is_single_vert_selected() -> bool: + if keys.size() == 1: + return true + return false + + func current_point_key() -> int: + if not is_single_vert_selected(): + return -1 + return keys[0] + + func current_point_index(s: SS2D_Shape_Base) -> int: + if not is_single_vert_selected(): + return -1 + return s.get_point_index(keys[0]) + + +# PRELOADS +var GUI_SNAP_POPUP = preload("scenes/SnapPopup.tscn") +var GUI_POINT_INFO_PANEL = preload("scenes/GUI_InfoPanel.tscn") +var GUI_EDGE_INFO_PANEL = preload("scenes/GUI_Edge_InfoPanel.tscn") +var gui_point_info_panel = GUI_POINT_INFO_PANEL.instance() +var gui_edge_info_panel = GUI_EDGE_INFO_PANEL.instance() +var gui_snap_settings = GUI_SNAP_POPUP.instance() + +# This is the shape node being edited +var shape = null +# For when a legacy shape is selected +var legacy_shape = null + +# Toolbar Stuff +var tb_hb: HBoxContainer = null +var tb_hb_legacy_import: HBoxContainer = null +var tb_import: ToolButton = null +var tb_vert_create: ToolButton = null +var tb_vert_edit: ToolButton = null +var tb_edge_edit: ToolButton = null +var tb_pivot: ToolButton = null +var tb_collision: ToolButton = null +var tb_snap: MenuButton = null +# The PopupMenu that belongs to tb_snap +var tb_snap_popup: PopupMenu = null + +# Edge Stuff +var on_edge: bool = false +var edge_point: Vector2 +var edge_data: SS2D_Edge = null + +# Width Handle Stuff +var on_width_handle: bool = false +const WIDTH_HANDLE_OFFSET: float = 60.0 +var closest_key: int +var closest_edge_keys: Array = [-1, -1] +var width_scaling: float + +# Track our mode of operation +var current_mode: int = MODE.CREATE_VERT +var previous_mode: int = MODE.CREATE_VERT + +# Undo stuff +var undo: UndoRedo = null +var undo_version: int = 0 + +var current_action = ActionDataVert.new([], [], [], [], [], ACTION_VERT.NONE) +var cached_shape_global_transform: Transform2D + +# Action Move Variables +var _mouse_motion_delta_starting_pos = Vector2(0, 0) + +####### +# GUI # +####### + + +func gui_display_snap_settings(): + var win_size = OS.get_window_size() + gui_snap_settings.popup_centered_ratio(0.5) + gui_snap_settings.set_as_minsize() + # Get Centered + gui_snap_settings.rect_position = (win_size / 2.0) - gui_snap_settings.rect_size / 2.0 + # Move up + gui_snap_settings.rect_position.y = (win_size.y / 8.0) + + +func _snapping_item_selected(id: int): + if id == 0: + tb_snap_popup.set_item_checked(id, not tb_snap_popup.is_item_checked(id)) + if id == 1: + tb_snap_popup.set_item_checked(id, not tb_snap_popup.is_item_checked(id)) + elif id == 3: + gui_display_snap_settings() + + +func _gui_build_toolbar(): + tb_hb = HBoxContainer.new() + add_control_to_container(EditorPlugin.CONTAINER_CANVAS_EDITOR_MENU, tb_hb) + tb_hb_legacy_import = HBoxContainer.new() + add_control_to_container(EditorPlugin.CONTAINER_CANVAS_EDITOR_MENU, tb_hb_legacy_import) + tb_import = ToolButton.new() + tb_import.icon = ICON_IMPORT_CLOSED + tb_import.toggle_mode = false + tb_import.pressed = false + tb_import.hint_tooltip = SS2D_Strings.EN_TOOLTIP_IMPORT + tb_import.connect("pressed", self, "_import_legacy") + tb_hb_legacy_import.add_child(tb_import) + + var sep = VSeparator.new() + tb_hb.add_child(sep) + + tb_vert_create = create_tool_button(ICON_CURVE_CREATE, SS2D_Strings.EN_TOOLTIP_CREATE_VERT) + tb_vert_create.connect("pressed", self, "_enter_mode", [MODE.CREATE_VERT]) + tb_vert_create.pressed = true + + tb_vert_edit = create_tool_button(ICON_CURVE_EDIT, SS2D_Strings.EN_TOOLTIP_EDIT_VERT) + tb_vert_edit.connect("pressed", self, "_enter_mode", [MODE.EDIT_VERT]) + + tb_edge_edit = create_tool_button(ICON_INTERP_LINEAR, SS2D_Strings.EN_TOOLTIP_EDIT_EDGE) + tb_edge_edit.connect("pressed", self, "_enter_mode", [MODE.EDIT_EDGE]) + + tb_pivot = create_tool_button(ICON_PIVOT_POINT, SS2D_Strings.EN_TOOLTIP_EDIT_VERT) + tb_pivot.connect("pressed", self, "_enter_mode", [MODE.SET_PIVOT]) + + tb_collision = create_tool_button(ICON_COLLISION, SS2D_Strings.EN_TOOLTIP_COLLISION) + tb_collision.connect("pressed", self, "_add_collision") + + tb_snap = MenuButton.new() + tb_snap.hint_tooltip = SS2D_Strings.EN_TOOLTIP_SNAP + tb_snap_popup = tb_snap.get_popup() + tb_snap.icon = ICON_SNAP + tb_snap_popup.add_check_item("Use Grid Snap") + tb_snap_popup.add_check_item("Snap Relative") + tb_snap_popup.add_separator() + tb_snap_popup.add_item("Configure Snap...") + tb_snap_popup.hide_on_checkable_item_selection = false + tb_hb.add_child(tb_snap) + tb_snap_popup.connect("id_pressed", self, "_snapping_item_selected") + + tb_hb.hide() + + +func create_tool_button(icon, tooltip): + var tb = ToolButton.new() + tb.toggle_mode = true + tb.icon = icon + tb.hint_tooltip = tooltip + tb_hb.add_child(tb) + return tb + + +func _gui_update_vert_info_panel(): + var idx = current_action.current_point_index(shape) + var key = current_action.current_point_key() + if not is_key_valid(shape, key): + gui_point_info_panel.visible = false + return + gui_point_info_panel.visible = true + # Shrink panel + gui_point_info_panel.rect_size = Vector2(1, 1) + + var properties = shape.get_point_properties(key) + gui_point_info_panel.set_idx(idx) + gui_point_info_panel.set_texture_idx(properties.texture_idx) + gui_point_info_panel.set_width(properties.width) + gui_point_info_panel.set_flip(properties.flip) + + +func _gui_update_edge_info_panel(): + # Don't update if already visible + if gui_edge_info_panel.visible: + return + var indicies = [-1, -1] + var override = null + if on_edge: + var t: Transform2D = get_et() * shape.get_global_transform() + var offset = shape.get_closest_offset_straight_edge(t.affine_inverse().xform(edge_point)) + var keys = _get_edge_point_keys_from_offset(offset, true) + indicies = [shape.get_point_index(keys[0]), shape.get_point_index(keys[1])] + if shape.has_material_override(keys): + override = shape.get_material_override(keys) + gui_edge_info_panel.set_indicies(indicies) + if override != null: + gui_edge_info_panel.set_material_override(true) + gui_edge_info_panel.load_values_from_meta_material(override) + else: + gui_edge_info_panel.set_material_override(false) + + # Shrink panel to minimum size + gui_edge_info_panel.rect_size = Vector2(1, 1) + + +func _gui_update_info_panels(): + match current_mode: + MODE.EDIT_VERT: + _gui_update_vert_info_panel() + gui_edge_info_panel.visible = false + MODE.EDIT_EDGE: + _gui_update_edge_info_panel() + gui_point_info_panel.visible = false + + +######### +# GODOT # +######### + + +# Called when saving +# https://docs.godotengine.org/en/3.2/classes/class_editorplugin.html?highlight=switch%20scene%20tab +func apply_changes(): + gui_point_info_panel.visible = false + gui_edge_info_panel.visible = false + + +func _init(): + pass + + +func _ready(): + undo = get_undo_redo() + # Support the undo-redo actions + _gui_build_toolbar() + add_child(gui_point_info_panel) + gui_point_info_panel.visible = false + add_child(gui_edge_info_panel) + gui_edge_info_panel.visible = false + gui_edge_info_panel.connect("set_material_override", self, "_on_set_edge_material_override") + gui_edge_info_panel.connect("set_render", self, "_on_set_edge_material_override_render") + gui_edge_info_panel.connect("set_weld", self, "_on_set_edge_material_override_weld") + gui_edge_info_panel.connect("set_z_index", self, "_on_set_edge_material_override_z_index") + gui_edge_info_panel.connect("set_edge_material", self, "_on_set_edge_material") + add_child(gui_snap_settings) + + +func _enter_tree(): + pass + + +func _exit_tree(): + gui_point_info_panel.visible = false + gui_edge_info_panel.visible = false + remove_control_from_container(EditorPlugin.CONTAINER_CANVAS_EDITOR_MENU, tb_hb) + + +func forward_canvas_gui_input(event): + if not is_shape_valid(shape): + return false + + var et = get_et() + var grab_threshold = get_editor_interface().get_editor_settings().get( + "editors/poly_editor/point_grab_radius" + ) + var return_value = false + + if event is InputEventKey: + return_value = _input_handle_keyboard_event(event) + + elif event is InputEventMouseButton: + return_value = _input_handle_mouse_button_event(event, et, grab_threshold) + + elif event is InputEventMouseMotion: + return_value = _input_handle_mouse_motion_event(event, et, grab_threshold) + + _gui_update_info_panels() + return return_value + + +func _process(delta): + if not Engine.editor_hint: + return + + if not is_shape_valid(shape): + gui_point_info_panel.visible = false + gui_edge_info_panel.visible = false + shape = null + update_overlays() + return + # Force update if global transforma has been changed + if cached_shape_global_transform != shape.get_global_transform(): + shape.set_as_dirty() + cached_shape_global_transform = shape.get_global_transform() + + +func handles(object): + tb_hb.hide() + tb_hb_legacy_import.hide() + update_overlays() + gui_point_info_panel.visible = false + gui_edge_info_panel.visible = false + + if object is Resource: + return false + + var rslt: bool = object is SS2D_Shape_Base or object is RMSmartShape2D + return rslt + + +func edit(object): + on_edge = false + deselect_verts() + if is_shape_valid(shape): + disconnect_shape(shape) + if is_shape_valid(legacy_shape): + disconnect_shape(legacy_shape) + + shape = null + legacy_shape = null + + if object is RMSmartShape2D: + tb_hb.hide() + tb_hb_legacy_import.show() + if object.closed_shape: + tb_import.icon = ICON_IMPORT_CLOSED + else: + tb_import.icon = ICON_IMPORT_OPEN + + legacy_shape = object + connect_shape(legacy_shape) + else: + tb_hb.show() + tb_hb_legacy_import.hide() + + shape = object + connect_shape(shape) + + update_overlays() + + +func make_visible(visible): + pass + + +############ +# SNAPPING # +############ +func use_global_snap() -> bool: + return ! tb_snap_popup.is_item_checked(1) + + +func use_snap() -> bool: + return tb_snap_popup.is_item_checked(0) + + +func get_snap_offset() -> Vector2: + return gui_snap_settings.get_snap_offset() + + +func get_snap_step() -> Vector2: + return gui_snap_settings.get_snap_step() + + +func snap(v: Vector2, force: bool = false) -> Vector2: + if not use_snap() and not force: + return v + var step = get_snap_step() + var offset = get_snap_offset() + var t = Transform2D.IDENTITY + if use_global_snap(): + t = shape.get_global_transform() + return snap_position(v, offset, step, t) + + +static func snap_position( + pos_global: Vector2, snap_offset: Vector2, snap_step: Vector2, local_t: Transform2D +) -> Vector2: + # Move global position to local position to snap in local space + var pos_local = local_t * pos_global + + # Snap in local space + var x = pos_local.x + if snap_step.x != 0: + var delta = fmod(pos_local.x, snap_step.x) + # Round up + if delta >= (snap_step.x / 2.0): + x = pos_local.x + (snap_step.x - delta) + # Round down + else: + x = pos_local.x - delta + var y = pos_local.y + if snap_step.y != 0: + var delta = fmod(pos_local.y, snap_step.y) + # Round up + if delta >= (snap_step.y / 2.0): + y = pos_local.y + (snap_step.y - delta) + # Round down + else: + y = pos_local.y - delta + + # Transform local position to global position + var pos_global_snapped = (local_t.affine_inverse() * Vector2(x, y)) + snap_offset + #print ("%s | %s | %s | %s" % [pos_global, pos_local, Vector2(x,y), pos_global_snapped]) + return pos_global_snapped + + +########## +# PLUGIN # +########## +func _import_legacy(): + call_deferred("_import_legacy_impl") + + +func _import_legacy_impl(): + if legacy_shape == null: + push_error("LEGACY SHAPE IS NULL") + return + if not legacy_shape is RMSmartShape2D: + push_error("LEGACY SHAPE NOT VALID") + return + var par = legacy_shape.get_parent() + if par == null: + push_error("LEGACY SHAPE PARENT IS NULL") + return + + # Make new shape and set values + var new_shape = null + if legacy_shape.closed_shape: + new_shape = SS2D_Shape_Closed.new() + new_shape.name = "SS2D_Shape_Closed" + else: + new_shape = SS2D_Shape_Open.new() + new_shape.name = "SS2D_Shape_Open" + new_shape.import_from_legacy(legacy_shape) + new_shape.transform = legacy_shape.transform + + # Add new to scene tree + par.add_child(new_shape) + new_shape.owner = get_editor_interface().get_edited_scene_root() + + # Remove Legacy from scene tree + disconnect_shape(legacy_shape) + par.remove_child(legacy_shape) + legacy_shape.queue_free() + legacy_shape = null + + # Edit the new shape + #edit(new_shape) + + +func _on_legacy_closed_changed(): + if is_shape_valid(legacy_shape): + if legacy_shape is RMSmartShape2D: + if legacy_shape.closed_shape: + tb_import.icon = ICON_IMPORT_CLOSED + else: + tb_import.icon = ICON_IMPORT_OPEN + + +func disconnect_shape(s): + if s.is_connected("points_modified", self, "_on_shape_point_modified"): + s.disconnect("points_modified", self, "_on_shape_point_modified") + # Legacy + if s is RMSmartShape2D: + if s.is_connected("on_closed_change", self, "_on_legacy_closed_changed"): + s.disconnect("on_closed_change", self, "_on_legacy_closed_changed") + + +func connect_shape(s): + if not s.is_connected("points_modified", self, "_on_shape_point_modified"): + s.connect("points_modified", self, "_on_shape_point_modified") + if s is RMSmartShape2D: + if not s.is_connected("on_closed_change", self, "_on_legacy_closed_changed"): + s.connect("on_closed_change", self, "_on_legacy_closed_changed") + + +static func get_material_override_from_indicies(shape: SS2D_Shape_Base, indicies: Array): + var keys = [] + for i in indicies: + keys.push_back(shape.get_point_key_at_index(i)) + if not shape.has_material_override(keys): + return null + return shape.get_material_override(keys) + + +func _on_set_edge_material_override_render(enabled: bool): + var override = get_material_override_from_indicies(shape, gui_edge_info_panel.indicies) + if override != null: + override.render = enabled + + +func _on_set_edge_material_override_weld(enabled: bool): + var override = get_material_override_from_indicies(shape, gui_edge_info_panel.indicies) + if override != null: + override.weld = enabled + + +func _on_set_edge_material_override_z_index(z: int): + var override = get_material_override_from_indicies(shape, gui_edge_info_panel.indicies) + if override != null: + override.z_index = z + + +func _on_set_edge_material(m: SS2D_Material_Edge): + var override = get_material_override_from_indicies(shape, gui_edge_info_panel.indicies) + if override != null: + override.edge_material = m + + +func _on_set_edge_material_override(enabled: bool): + var indicies = gui_edge_info_panel.indicies + if indicies.has(-1) or indicies.size() != 2: + return + var keys = [] + for i in indicies: + keys.push_back(shape.get_point_key_at_index(i)) + + # Get the relevant Override data if any exists + var override = null + if shape.has_material_override(keys): + override = shape.get_material_override(keys) + + if enabled: + if override == null: + override = SS2D_Material_Edge_Metadata.new() + override.edge_material = null + shape.set_material_override(keys, override) + + # Load override data into the info panel + gui_edge_info_panel.load_values_from_meta_material(override) + else: + if override != null: + shape.remove_material_override(keys) + + +static func is_shape_valid(s) -> bool: + if s == null: + return false + if not is_instance_valid(s): + return false + if not s.is_inside_tree(): + return false + return true + + +func _on_shape_point_modified(): + FUNC.action_invert_orientation(self, "update_overlays", undo, shape) + + +func get_et() -> Transform2D: + return get_editor_interface().get_edited_scene_root().get_viewport().global_canvas_transform + + +static func is_key_valid(s: SS2D_Shape_Base, key: int) -> bool: + if not is_shape_valid(s): + return false + return s.has_point(key) + + +func _enter_mode(mode: int): + if current_mode == mode: + return + for tb in [tb_vert_edit, tb_edge_edit, tb_pivot, tb_vert_create]: + tb.pressed = false + + previous_mode = current_mode + current_mode = mode + match mode: + MODE.CREATE_VERT: + tb_vert_create.pressed = true + MODE.EDIT_VERT: + tb_vert_edit.pressed = true + MODE.EDIT_EDGE: + tb_edge_edit.pressed = true + MODE.SET_PIVOT: + tb_pivot.pressed = true + _: + tb_vert_edit.pressed = true + update_overlays() + + +func _set_pivot(point: Vector2): + var et = get_et() + + var np: Vector2 = point + var ct: Transform2D = shape.get_global_transform() + ct.origin = np + + shape.disable_constraints() + for i in shape.get_point_count(): + var key = shape.get_point_key_at_index(i) + var pt = shape.get_global_transform().xform(shape.get_point_position(key)) + shape.set_point_position(key, ct.affine_inverse().xform(pt)) + shape.enable_constraints() + + shape.position = shape.get_parent().get_global_transform().affine_inverse().xform(np) + _enter_mode(current_mode) + update_overlays() + + +func _add_collision(): + call_deferred("_add_deferred_collision") + + +func _add_deferred_collision(): + if not shape.get_parent() is StaticBody2D: + var static_body: StaticBody2D = StaticBody2D.new() + var t: Transform2D = shape.transform + static_body.position = shape.position + shape.position = Vector2.ZERO + + shape.get_parent().add_child(static_body) + static_body.owner = get_editor_interface().get_edited_scene_root() + + shape.get_parent().remove_child(shape) + static_body.add_child(shape) + shape.owner = get_editor_interface().get_edited_scene_root() + + var poly: CollisionPolygon2D = CollisionPolygon2D.new() + static_body.add_child(poly) + poly.owner = get_editor_interface().get_edited_scene_root() + # TODO: Make this a option at some point + poly.modulate.a = 0.3 + poly.visible = false + shape.collision_polygon_node_path = shape.get_path_to(poly) + shape.set_as_dirty() + + +############# +# RENDERING # +############# +func forward_canvas_draw_over_viewport(overlay: Control): + # Something might force a draw which we had no control over, + # in this case do some updating to be sure + if not is_shape_valid(shape) or not is_inside_tree(): + return + + if undo_version != undo.get_version(): + if ( + undo.get_current_action_name() == "Move CanvasItem" + or undo.get_current_action_name() == "Rotate CanvasItem" + or undo.get_current_action_name() == "Scale CanvasItem" + ): + shape.set_as_dirty() + undo_version = undo.get_version() + + match current_mode: + MODE.CREATE_VERT: + draw_mode_edit_vert(overlay) + if Input.is_key_pressed(KEY_ALT) and Input.is_key_pressed(KEY_SHIFT): + draw_new_shape_preview(overlay) + elif Input.is_key_pressed(KEY_ALT): + draw_new_point_close_preview(overlay) + else: + draw_new_point_preview(overlay) + MODE.EDIT_VERT: + draw_mode_edit_vert(overlay) + if Input.is_key_pressed(KEY_ALT): + if Input.is_key_pressed(KEY_SHIFT): + draw_new_shape_preview(overlay) + elif not on_edge: + draw_new_point_close_preview(overlay) + MODE.EDIT_EDGE: + draw_mode_edit_edge(overlay) + + shape.update() + + +func draw_mode_edit_edge(overlay: Control): + var t: Transform2D = get_et() * shape.get_global_transform() + var verts = shape.get_vertices() + var edges = shape.get_edges() + + var color_highlight = Color(1.0, 0.75, 0.75, 1.0) + var color_normal = Color(1.0, 0.25, 0.25, 0.8) + + draw_shape_outline(overlay, t, verts, color_normal, 3.0) + draw_vert_handles(overlay, t, verts, false) + + if current_action.type == ACTION_VERT.MOVE_VERT: + var edge_point_keys = current_action.keys + var p1 = shape.get_point_position(edge_point_keys[0]) + var p2 = shape.get_point_position(edge_point_keys[1]) + overlay.draw_line(t.xform(p1), t.xform(p2), color_highlight, 5.0) + elif on_edge: + var offset = shape.get_closest_offset_straight_edge(t.affine_inverse().xform(edge_point)) + var edge_point_keys = _get_edge_point_keys_from_offset(offset, true) + var p1 = shape.get_point_position(edge_point_keys[0]) + var p2 = shape.get_point_position(edge_point_keys[1]) + overlay.draw_line(t.xform(p1), t.xform(p2), color_highlight, 5.0) + + +func draw_mode_edit_vert(overlay: Control): + var t: Transform2D = get_et() * shape.get_global_transform() + var verts = shape.get_vertices() + var points = shape.get_tessellated_points() + draw_shape_outline(overlay, t, points) + draw_vert_handles(overlay, t, verts, true) + if on_edge: + overlay.draw_texture(ICON_ADD_HANDLE, edge_point - ICON_ADD_HANDLE.get_size() * 0.5) + + # Draw Highlighted Handle + if current_action.is_single_vert_selected(): + var tex = ICON_HANDLE_SELECTED + overlay.draw_texture( + tex, t.xform(verts[current_action.current_point_index(shape)]) - tex.get_size() * 0.5 + ) + + +func draw_shape_outline(overlay: Control, t: Transform2D, points, color = null, width = 2.0): + # Draw Outline + var prev_pt = null + if color == null: + color = shape.modulate + for i in range(0, points.size(), 1): + var pt = points[i] + if prev_pt != null: + overlay.draw_line(prev_pt, t.xform(pt), color, width, true) + prev_pt = t.xform(pt) + + +func draw_vert_handles(overlay: Control, t: Transform2D, verts, control_points: bool): + for i in range(0, verts.size(), 1): + # Draw Vert handles + var key: int = shape.get_point_key_at_index(i) + var hp: Vector2 = t.xform(verts[i]) + var icon = ICON_HANDLE_BEZIER if Input.is_key_pressed(KEY_SHIFT) else ICON_HANDLE + overlay.draw_texture(icon, hp - icon.get_size() * 0.5) + + # Draw Width handles +# var normal = _get_vert_normal(t, verts, i) +# var width_handle_icon = WIDTH_HANDLES[int((normal.angle() + PI / 8 + TAU) / PI * 4) % 4] +# overlay.draw_texture(width_handle_icon, hp - width_handle_icon.get_size() * 0.5 + normal * WIDTH_HANDLE_OFFSET) + + # Draw Width handle + var offset = WIDTH_HANDLE_OFFSET + var width_handle_key = closest_key + if ( + Input.is_mouse_button_pressed(BUTTON_LEFT) + and current_action.type == ACTION_VERT.MOVE_WIDTH_HANDLE + ): + offset *= width_scaling + width_handle_key = current_action.keys[0] + var width_handle_normal = _get_vert_normal(t, verts, shape.get_point_index(width_handle_key)) + var vertex_position: Vector2 = t.xform(shape.get_point_position(width_handle_key)) + var icon_position: Vector2 = vertex_position + width_handle_normal * offset + var rect_size: Vector2 = Vector2.ONE * 10.0 + var width_handle_color = Color("f53351") + overlay.draw_line(vertex_position, icon_position, width_handle_color, 1.0, true) + overlay.draw_set_transform(icon_position, width_handle_normal.angle(), Vector2.ONE) + overlay.draw_rect(Rect2(-rect_size / 2.0, rect_size), width_handle_color, true, 1.0) + overlay.draw_set_transform(Vector2.ZERO, 0, Vector2.ONE) + + # Draw Control point handles + if control_points: + for i in range(0, verts.size(), 1): + var normal = _get_vert_normal(t, verts, i) + var key = shape.get_point_key_at_index(i) + var hp = t.xform(verts[i]) + + # Drawing the point-out for the last point makes no sense, as there's no point ahead of it + if i < verts.size() - 1: + var pointout = t.xform(verts[i] + shape.get_point_out(key)) + if hp != pointout: + _draw_control_point_line(overlay, hp, pointout, ICON_HANDLE_CONTROL) + # Drawing the point-in for point 0 makes no sense, as there's no point behind it + if i > 0: + var pointin = t.xform(verts[i] + shape.get_point_in(key)) + if hp != pointin: + _draw_control_point_line(overlay, hp, pointin, ICON_HANDLE_CONTROL) + + +func _draw_control_point_line(c: Control, vert: Vector2, cp: Vector2, tex: Texture): + # Draw the line with a dark and light color to be visible on all backgrounds + var color_dark = Color(0, 0, 0, 0.3) + var color_light = Color(1, 1, 1, .5) + var width = 2.0 + var normal = (cp - vert).normalized() + c.draw_line(vert + normal * 4 + Vector2.DOWN, cp + Vector2.DOWN, color_dark, width, true) + c.draw_line(vert + normal * 4, cp, color_light, width, true) + c.draw_texture(tex, cp - tex.get_size() * 0.5) + + +func draw_new_point_preview(overlay: Control): + # Draw lines to where a new point will be added + var verts = shape.get_vertices() + var t: Transform2D = get_et() * shape.get_global_transform() + var color = Color(1, 1, 1, .5) + var width = 2 + + var a + var mouse = overlay.get_local_mouse_position() + if is_shape_closed(shape): + a = t.xform(verts[verts.size() - 2]) + var b = t.xform(verts[0]) + overlay.draw_line(mouse, b, color, width * .5, true) + else: + a = t.xform(verts[verts.size() - 1]) + overlay.draw_line(mouse, a, color, width, true) + overlay.draw_texture(ICON_ADD_HANDLE, mouse - ICON_ADD_HANDLE.get_size() * 0.5) + + +func draw_new_point_close_preview(overlay: Control): + # Draw lines to where a new point will be added + var verts = shape.get_vertices() + var t: Transform2D = get_et() * shape.get_global_transform() + var color = Color(1, 1, 1, .5) + var width = 2 + + var mouse = overlay.get_local_mouse_position() + var a = t.xform(shape.get_point_position(closest_edge_keys[0])) + var b = t.xform(shape.get_point_position(closest_edge_keys[1])) +# var a = +# var b = t.xform() + overlay.draw_line(mouse, b, color, width, true) + overlay.draw_line(mouse, a, color, width, true) + overlay.draw_texture(ICON_ADD_HANDLE, mouse - ICON_ADD_HANDLE.get_size() * 0.5) + + +func draw_new_shape_preview(overlay: Control): + # Draw a plus where a new shape will be added + var mouse = overlay.get_local_mouse_position() + overlay.draw_texture(ICON_ADD_HANDLE, mouse - ICON_ADD_HANDLE.get_size() * 0.5) + + +########## +# PLUGIN # +########## +func deselect_verts(): + current_action = ActionDataVert.new([], [], [], [], [], ACTION_VERT.NONE) + + +func select_verticies(keys: Array, action: int) -> ActionDataVert: + var from_positions = [] + var from_positions_c_in = [] + var from_positions_c_out = [] + var from_widths = [] + for key in keys: + from_positions.push_back(shape.get_point_position(key)) + from_positions_c_in.push_back(shape.get_point_in(key)) + from_positions_c_out.push_back(shape.get_point_out(key)) + from_widths.push_back(shape.get_point_width(key)) + return ActionDataVert.new( + keys, from_positions, from_positions_c_in, from_positions_c_out, from_widths, action + ) + + +func select_vertices_to_move(keys: Array, _mouse_starting_pos_viewport: Vector2): + _mouse_motion_delta_starting_pos = _mouse_starting_pos_viewport + current_action = select_verticies(keys, ACTION_VERT.MOVE_VERT) + + +func select_control_points_to_move( + keys: Array, _mouse_starting_pos_viewport: Vector2, action = ACTION_VERT.MOVE_CONTROL +): + current_action = select_verticies(keys, action) + _mouse_motion_delta_starting_pos = _mouse_starting_pos_viewport + + +func select_width_handle_to_move(keys: Array, _mouse_starting_pos_viewport: Vector2): + _mouse_motion_delta_starting_pos = _mouse_starting_pos_viewport + current_action = select_verticies(keys, ACTION_VERT.MOVE_WIDTH_HANDLE) + + +######### +# INPUT # +######### +func _input_handle_right_click_press(mb_position: Vector2, grab_threshold: float) -> bool: + if not shape.can_edit: + return false + if current_mode == MODE.EDIT_VERT or current_mode == MODE.CREATE_VERT: + # Mouse over a single vertex? + if current_action.is_single_vert_selected(): + FUNC.action_delete_point(self, "update_overlays", undo, shape, current_action.keys[0]) + undo_version = undo.get_version() + deselect_verts() + return true + else: + # Mouse over a control point? + var et = get_et() + var points_in = FUNC.get_intersecting_control_point_in( + shape, et, mb_position, grab_threshold + ) + var points_out = FUNC.get_intersecting_control_point_out( + shape, et, mb_position, grab_threshold + ) + if not points_in.empty(): + FUNC.action_delete_point_in(self, "update_overlays", undo, shape, points_in[0]) + undo_version = undo.get_version() + return true + elif not points_out.empty(): + FUNC.action_delete_point_out(self, "update_overlays", undo, shape, points_out[0]) + undo_version = undo.get_version() + return true + elif current_mode == MODE.EDIT_EDGE: + if on_edge: + gui_edge_info_panel.visible = not gui_edge_info_panel.visible + gui_edge_info_panel.rect_position = mb_position + Vector2(256, -24) + return false + + +func _input_handle_left_click( + mb: InputEventMouseButton, + vp_m_pos: Vector2, + t: Transform2D, + et: Transform2D, + grab_threshold: float +) -> bool: + # Set Pivot? + if current_mode == MODE.SET_PIVOT: + var local_position = et.affine_inverse().xform(mb.position) + if use_snap(): + local_position = snap(local_position) + FUNC.action_set_pivot(self, "_set_pivot", undo, shape, et, local_position) + undo_version = undo.get_version() + return true + if current_mode == MODE.EDIT_VERT or current_mode == MODE.CREATE_VERT: + gui_edge_info_panel.visible = false + + # Any nearby control points to move? + if not Input.is_key_pressed(KEY_ALT): + if _input_move_control_points(mb, vp_m_pos, grab_threshold): + return true + + # Highlighting a vert to move or add control points to + if current_action.is_single_vert_selected(): + if on_width_handle: + select_width_handle_to_move([current_action.current_point_key()], vp_m_pos) + elif Input.is_key_pressed(KEY_SHIFT): + select_control_points_to_move([current_action.current_point_key()], vp_m_pos) + return true + else: + select_vertices_to_move([current_action.current_point_key()], vp_m_pos) + return true + + # Split the Edge? + if _input_split_edge(mb, vp_m_pos, t): + return true + + if not on_edge: + # Create new point + if Input.is_key_pressed(KEY_ALT) or current_mode == MODE.CREATE_VERT: + var local_position = t.affine_inverse().xform(mb.position) + if use_snap(): + local_position = snap(local_position) + if Input.is_key_pressed(KEY_SHIFT) and Input.is_key_pressed(KEY_ALT): + # Copy shape with a new single point + var copy = copy_shape(shape) + + copy.set_point_array(SS2D_Point_Array.new()) + copy.clear_all_material_overrides() + var new_key = FUNC.action_add_point( + self, "update_overlays", undo, copy, local_position + ) + select_vertices_to_move([new_key], vp_m_pos) + + _enter_mode(MODE.CREATE_VERT) + + var selection := get_editor_interface().get_selection() + selection.clear() + selection.add_node(copy) + elif Input.is_key_pressed(KEY_ALT): + var new_key = FUNC.action_add_point( + self, + "update_overlays", + undo, + shape, + local_position, + shape.get_point_index(closest_edge_keys[1]) + ) + else: + var new_key = FUNC.action_add_point( + self, "update_overlays", undo, shape, local_position + ) + select_vertices_to_move([new_key], vp_m_pos) + undo_version = undo.get_version() + return true + elif current_mode == MODE.EDIT_EDGE: + if gui_edge_info_panel.visible: + gui_edge_info_panel.visible = false + return true + if on_edge: + # Grab Edge (2 points) + var offset = shape.get_closest_offset_straight_edge( + t.affine_inverse().xform(edge_point) + ) + var edge_point_keys = _get_edge_point_keys_from_offset(offset, true) + select_vertices_to_move([edge_point_keys[0], edge_point_keys[1]], vp_m_pos) + return true + return false + + +func _input_handle_mouse_wheel(btn: int) -> bool: + if not shape.can_edit: + return false + var key = current_action.current_point_key() + if Input.is_key_pressed(KEY_SHIFT): + var width = shape.get_point_width(key) + var width_step = 0.1 + if btn == BUTTON_WHEEL_DOWN: + width_step *= -1 + var new_width = width + width_step + shape.set_point_width(key, new_width) + + else: + var texture_idx_step = 1 + if btn == BUTTON_WHEEL_DOWN: + texture_idx_step *= -1 + + var tex_idx: int = shape.get_point_texture_index(key) + texture_idx_step + shape.set_point_texture_index(key, tex_idx) + + shape.set_as_dirty() + update_overlays() + _gui_update_info_panels() + + return true + + +func _input_handle_keyboard_event(event: InputEventKey) -> bool: + if not shape.can_edit: + return false + var kb: InputEventKey = event + if _is_valid_keyboard_scancode(kb): + if current_action.is_single_vert_selected(): + if kb.pressed and kb.scancode == KEY_SPACE: + var key = current_action.current_point_key() + shape.set_point_texture_flip(key, not shape.get_point_texture_flip(key)) + shape.set_as_dirty() + shape.update() + _gui_update_info_panels() + + if kb.pressed and kb.scancode == KEY_ESCAPE: + # Hide edge_info_panel + if gui_edge_info_panel.visible: + gui_edge_info_panel.visible = false + + if current_mode == MODE.CREATE_VERT: + _enter_mode(MODE.EDIT_VERT) + + if kb.scancode == KEY_CONTROL: + if kb.pressed and not kb.echo: + on_edge = false + current_action = select_verticies([closest_key], ACTION_VERT.NONE) + else: + deselect_verts() + update_overlays() + + if kb.scancode == KEY_ALT: + update_overlays() + + return true + return false + + +func _is_valid_keyboard_scancode(kb: InputEventKey) -> bool: + match kb.scancode: + KEY_ESCAPE: + return true + KEY_ENTER: + return true + KEY_SPACE: + return true + KEY_SHIFT: + return true + KEY_ALT: + return true + KEY_CONTROL: + return true + return false + + +func _input_handle_mouse_button_event( + event: InputEventMouseButton, et: Transform2D, grab_threshold: float +) -> bool: + if not shape.can_edit: + return false + var t: Transform2D = et * shape.get_global_transform() + var mb: InputEventMouseButton = event + var viewport_mouse_position = et.affine_inverse().xform(mb.position) + var mouse_wheel_spun = ( + mb.pressed + and (mb.button_index == BUTTON_WHEEL_DOWN or mb.button_index == BUTTON_WHEEL_UP) + ) + + ####################################### + # Mouse Button released + if not mb.pressed and mb.button_index == BUTTON_LEFT: + var rslt: bool = false + var type = current_action.type + var _in = type == ACTION_VERT.MOVE_CONTROL or type == ACTION_VERT.MOVE_CONTROL_IN + var _out = type == ACTION_VERT.MOVE_CONTROL or type == ACTION_VERT.MOVE_CONTROL_OUT + if type == ACTION_VERT.MOVE_VERT: + FUNC.action_move_verticies(self, "update_overlays", undo, shape, current_action) + undo_version = undo.get_version() + rslt = true + elif _in or _out: + FUNC.action_move_control_points( + self, "update_overlays", undo, shape, current_action, _in, _out + ) + undo_version = undo.get_version() + rslt = true + deselect_verts() + return rslt + + ######################################### + # Mouse Wheel on valid point + elif mouse_wheel_spun and current_action.is_single_vert_selected(): + return _input_handle_mouse_wheel(mb.button_index) + + ######################################### + # Mouse left click + elif mb.pressed and mb.button_index == BUTTON_LEFT: + return _input_handle_left_click(mb, viewport_mouse_position, t, et, grab_threshold) + + ######################################### + # Mouse right click + elif mb.pressed and mb.button_index == BUTTON_RIGHT: + return _input_handle_right_click_press(mb.position, grab_threshold) + + return false + + +func _input_split_edge(mb: InputEventMouseButton, vp_m_pos: Vector2, t: Transform2D) -> bool: + if not on_edge: + return false + var gpoint: Vector2 = mb.position + var insertion_point: int = -1 + var mb_offset = shape.get_closest_offset(t.affine_inverse().xform(gpoint)) + + insertion_point = shape.get_point_index(_get_edge_point_keys_from_offset(mb_offset)[1]) + + if insertion_point == -1: + insertion_point = shape.get_point_count() - 1 + + var key = FUNC.action_split_curve( + self, "update_overlays", undo, shape, insertion_point, gpoint, t + ) + undo_version = undo.get_version() + select_vertices_to_move([key], vp_m_pos) + on_edge = false + + return true + + +func _input_move_control_points(mb: InputEventMouseButton, vp_m_pos: Vector2, grab_threshold: float) -> bool: + var points_in = FUNC.get_intersecting_control_point_in( + shape, get_et(), mb.position, grab_threshold + ) + var points_out = FUNC.get_intersecting_control_point_out( + shape, get_et(), mb.position, grab_threshold + ) + if not points_in.empty(): + select_control_points_to_move([points_in[0]], vp_m_pos, ACTION_VERT.MOVE_CONTROL_IN) + return true + elif not points_out.empty(): + select_control_points_to_move([points_out[0]], vp_m_pos, ACTION_VERT.MOVE_CONTROL_OUT) + return true + return false + + +func _get_edge_point_keys_from_offset(offset: float, straight: bool = false): + for i in range(0, shape.get_point_count() - 1, 1): + var key = shape.get_point_key_at_index(i) + var key_next = shape.get_point_key_at_index(i + 1) + var this_offset = 0 + var next_offset = 0 + if straight: + this_offset = shape.get_closest_offset_straight_edge(shape.get_point_position(key)) + next_offset = shape.get_closest_offset_straight_edge(shape.get_point_position(key_next)) + else: + this_offset = shape.get_closest_offset(shape.get_point_position(key)) + next_offset = shape.get_closest_offset(shape.get_point_position(key_next)) + if offset >= this_offset and offset <= next_offset: + return [key, key_next] + # for when the shape is closed and the final point has an offset of 0 + if next_offset == 0 and offset >= this_offset: + return [key, key_next] + return [-1, -1] + + +func _input_motion_is_on_edge(mm: InputEventMouseMotion, grab_threshold: float) -> bool: + var xform: Transform2D = get_et() * shape.get_global_transform() + if shape.get_point_count() < 2: + return false + + # Find edge + var closest_point = null + if current_mode == MODE.EDIT_EDGE: + closest_point = shape.get_closest_point_straight_edge( + xform.affine_inverse().xform(mm.position) + ) + else: + closest_point = shape.get_closest_point(xform.affine_inverse().xform(mm.position)) + if closest_point != null: + edge_point = xform.xform(closest_point) + if edge_point.distance_to(mm.position) <= grab_threshold: + return true + return false + + +func _input_find_closest_edge_keys(mm: InputEventMouseMotion): + var xform: Transform2D = get_et() * shape.get_global_transform() + if shape.get_point_count() < 2: + return false + + # Find edge + var closest_point = null + + closest_point = shape.get_closest_point_straight_edge(xform.affine_inverse().xform(mm.position)) + var edge_point = xform.xform(closest_point) + var offset = shape.get_closest_offset_straight_edge(xform.affine_inverse().xform(edge_point)) + closest_edge_keys = _get_edge_point_keys_from_offset(offset, true) + + +func get_mouse_over_vert_key(mm: InputEventMouseMotion, grab_threshold: float) -> int: + var xform: Transform2D = get_et() * shape.get_global_transform() + # However, if near a control point or one of its handles then we are not on the edge + for k in shape.get_all_point_keys(): + var pp: Vector2 = shape.get_point_position(k) + var p: Vector2 = xform.xform(pp) + if p.distance_to(mm.position) <= grab_threshold: + return k + return -1 + + +func get_mouse_over_width_handle(mm: InputEventMouseMotion, grab_threshold: float) -> int: + var xform: Transform2D = get_et() * shape.get_global_transform() + for k in shape.get_all_point_keys(): + var pp: Vector2 = shape.get_point_position(k) + var normal: Vector2 = _get_vert_normal( + xform, shape.get_vertices(), shape.get_point_index(k) + ) + var p: Vector2 = xform.xform(pp) + normal * WIDTH_HANDLE_OFFSET + if p.distance_to(mm.position) <= grab_threshold: + return k + return -1 + + +func _input_motion_move_control_points(delta: Vector2, _in: bool, _out: bool) -> bool: + var rslt = false + for i in range(0, current_action.keys.size(), 1): + var key = current_action.keys[i] + var from = current_action.starting_positions[i] + var out_multiplier = 1 + # Invert the delta for position_out if moving both at once + if _out and _in: + out_multiplier = -1 + var new_position_in = delta + current_action.starting_positions_control_in[i] + var new_position_out = ( + (delta * out_multiplier) + + current_action.starting_positions_control_out[i] + ) + if use_snap(): + new_position_in = snap(new_position_in) + new_position_out = snap(new_position_out) + if _in: + shape.set_point_in(key, new_position_in) + rslt = true + if _out: + shape.set_point_out(key, new_position_out) + rslt = true + shape.set_as_dirty() + update_overlays() + return false + + +func _input_motion_move_verts(delta: Vector2) -> bool: + for i in range(0, current_action.keys.size(), 1): + var key = current_action.keys[i] + var from = current_action.starting_positions[i] + var new_position = from + delta + if use_snap(): + new_position = snap(new_position) + shape.set_point_position(key, new_position) + update_overlays() + return true + + +func _input_motion_move_width_handle(mouse_position: Vector2, scale: Vector2) -> bool: + for i in range(0, current_action.keys.size(), 1): + var key = current_action.keys[i] + var from_width = current_action.starting_width[i] + var from_position = current_action.starting_positions[i] + width_scaling = from_position.distance_to(mouse_position) / WIDTH_HANDLE_OFFSET * scale.x + shape.set_point_width(key, round(from_width * width_scaling * 10) / 10) + update_overlays() + return true + + +func get_closest_vert_to_point(s: SS2D_Shape_Base, p: Vector2) -> int: + """ + Will Return index of closest vert to point + """ + var gt = shape.get_global_transform() + var verts = s.get_vertices() + var transformed_point = gt.affine_inverse() * p + var idx = -1 + var closest_distance = -1 + for i in verts.size(): + var distance = verts[i].distance_to(transformed_point) + if distance < closest_distance or closest_distance == -1: + idx = s.get_point_key_at_index(i) + closest_distance = distance + return idx + + +func _input_handle_mouse_motion_event( + event: InputEventMouseMotion, et: Transform2D, grab_threshold: float +) -> bool: + var t: Transform2D = et * shape.get_global_transform() + var mm: InputEventMouseMotion = event + var delta_current_pos = et.affine_inverse().xform(mm.position) + gui_point_info_panel.rect_position = mm.position + Vector2(256, -24) + var delta = delta_current_pos - _mouse_motion_delta_starting_pos + + closest_key = get_closest_vert_to_point(shape, delta_current_pos) + + if current_mode == MODE.EDIT_VERT or current_mode == MODE.CREATE_VERT: + var type = current_action.type + var _in = type == ACTION_VERT.MOVE_CONTROL or type == ACTION_VERT.MOVE_CONTROL_IN + var _out = type == ACTION_VERT.MOVE_CONTROL or type == ACTION_VERT.MOVE_CONTROL_OUT + + if type == ACTION_VERT.MOVE_VERT: + return _input_motion_move_verts(delta) + elif _in or _out: + return _input_motion_move_control_points(delta, _in, _out) + elif type == ACTION_VERT.MOVE_WIDTH_HANDLE: + return _input_motion_move_width_handle( + et.affine_inverse().xform(mm.position), et.get_scale() + ) + var mouse_over_key = get_mouse_over_vert_key(event, grab_threshold) + var mouse_over_width_handle = get_mouse_over_width_handle(event, grab_threshold) + + # Make the closest key grabable while holding down Control + if ( + Input.is_key_pressed(KEY_CONTROL) + and not Input.is_key_pressed(KEY_ALT) + and mouse_over_width_handle == -1 + and mouse_over_key == -1 + ): + mouse_over_key = closest_key + + on_width_handle = false + if mouse_over_key != -1: + on_edge = false + current_action = select_verticies([mouse_over_key], ACTION_VERT.NONE) + elif mouse_over_width_handle != -1: + on_edge = false + on_width_handle = true + current_action = select_verticies([mouse_over_width_handle], ACTION_VERT.NONE) + elif Input.is_key_pressed(KEY_ALT): + _input_find_closest_edge_keys(mm) + else: + deselect_verts() + on_edge = _input_motion_is_on_edge(mm, grab_threshold) + + elif current_mode == MODE.EDIT_EDGE: + # Don't update if edge panel is visible + if gui_edge_info_panel.visible: + return false + var type = current_action.type + if type == ACTION_VERT.MOVE_VERT: + return _input_motion_move_verts(delta) + else: + deselect_verts() + on_edge = _input_motion_is_on_edge(mm, grab_threshold) + + update_overlays() + return false + + +func _get_vert_normal(t: Transform2D, verts, i: int): + var point: Vector2 = t.xform(verts[i]) + var prev_point: Vector2 = t.xform(verts[(i - 1) % verts.size()]) + var next_point: Vector2 = t.xform(verts[(i + 1) % verts.size()]) + return ((prev_point - point).normalized().rotated(PI / 2) + (point - next_point).normalized().rotated(PI / 2)).normalized() + + +func copy_shape(shape): + var copy: SS2D_Shape_Base + if shape is SS2D_Shape_Closed: + copy = SS2D_Shape_Closed.new() + if shape is SS2D_Shape_Open: + copy = SS2D_Shape_Open.new() + if shape is SS2D_Shape_Meta: + copy = SS2D_Shape_Meta.new() + copy.position = shape.position + copy.scale = shape.scale + copy.modulate = shape.modulate + copy.shape_material = shape.shape_material + copy.editor_debug = shape.editor_debug + copy.flip_edges = shape.flip_edges + copy.editor_debug = shape.editor_debug + copy.collision_size = shape.collision_size + copy.collision_offset = shape.collision_offset + copy.tessellation_stages = shape.tessellation_stages + copy.tessellation_tolerence = shape.tessellation_tolerence + copy.curve_bake_interval = shape.curve_bake_interval + copy.material_overrides = shape.material_overrides + + shape.get_parent().add_child(copy) + copy.set_owner(get_tree().get_edited_scene_root()) + + if ( + shape.collision_polygon_node_path != "" + and shape.has_node(shape.collision_polygon_node_path) + ): + var collision_polygon_original = shape.get_node(shape.collision_polygon_node_path) + var collision_polygon_new = CollisionPolygon2D.new() + collision_polygon_new.visible = collision_polygon_original.visible + + collision_polygon_original.get_parent().add_child(collision_polygon_new) + collision_polygon_new.set_owner(get_tree().get_edited_scene_root()) + + copy.collision_polygon_node_path = copy.get_path_to(collision_polygon_new) + + return copy + + +func is_shape_closed(shape): + if shape is SS2D_Shape_Open: + return false + if shape is SS2D_Shape_Meta: + return shape.treat_as_closed() + return true + + +######### +# DEBUG # +######### +func _debug_mouse_positions(mm, t): + print("========================================") + print("MouseDelta:%s" % str(_mouse_motion_delta_starting_pos)) + print("= MousePositions =") + print("Position: %s" % str(mm.position)) + print("Relative: %s" % str(mm.relative)) + print("= Transforms =") + print("Transform: %s" % str(t)) + print("Inverse: %s" % str(t.affine_inverse())) + print("= Transformed Mouse positions =") + print("Position: %s" % str(t.affine_inverse().xform(mm.position))) + print("Relative: %s" % str(t.affine_inverse().xform(mm.relative))) + print("MouseDelta:%s" % str(t.affine_inverse().xform(_mouse_motion_delta_starting_pos))) |