Browse Source

GridMap editor fixes and improvements

This change fixes a few outstanding issues and greatly improves the usability
of the GridMap editor through the following changes:

- Copied mesh now gets displayed during pasting (also renamed the related
  identifiers accordingly)
- Duplication/paste indicator now gets rotated around the correct pivot point
  (duplication worked properly before, but the indicator was shown misplaced
  when rotated)
- Selected mesh library item cursor is no longer shown during selection and
  duplication/pasting
- Back rotate X/Y/Z is now working during duplication/pasting
- Added true cut operation thanks to now having a proper clipboard (clear
  operation got remapped to the DEL key)
- Got rid of some weird workarounds in the duplication code
- Fill and clear operations now correctly make the selection marker inactive
  as this was broken partly due to the workarounds mentioned above
  (duplication continues to keep the selection marker active to allow
  subsequent duplications)
- Clear current selection on RMB, but treat selection as an action so previous
  selection can be restored on undo
- Separated selection and paste indicator data as it's prone to error and
  confusion and it's anyway needed now that selection is treated as an action
- Added support for cancelling paste, selection, and even unselect the
  currently selected mesh library item with the ESC key (previously there
  wasn't a way to unselect)
- Changed the key binding of fill/clear/duplicate operations to use Ctrl as a
  modifier
- Changed erase to use RMB instead of Shift+RMB (free look is available
  through Shift+F anyway, so no need to occupy RMB for it during gridmap
  editing)
- Removed unused area, external connector, and configure menu items (there's
  also the non-functional clip mode menu items, but I'm not sure whether there
  are any plans with that, I suppose it's meant to be an editor aid)
- Renamed INPUT_COPY to INPUT_PICK to better reflect its purpose
- Added support for using Shift+Q and Shift+E to select multiple floors/planes
  without actually changing the current floor/plane as it happens when using
  e.g. the mouse wheel

Fixes #25373 and #15883
Daniel Rakos 6 years ago
parent
commit
07e2a86fc4
2 changed files with 286 additions and 144 deletions
  1. 255 132
      modules/gridmap/grid_map_editor_plugin.cpp
  2. 31 12
      modules/gridmap/grid_map_editor_plugin.h

+ 255 - 132
modules/gridmap/grid_map_editor_plugin.cpp

@@ -67,9 +67,6 @@ void GridMapEditor::_menu_option(int p_option) {
 			floor->set_value(floor->get_value() + 1);
 			floor->set_value(floor->get_value() + 1);
 		} break;
 		} break;
 
 
-		case MENU_OPTION_CONFIGURE: {
-
-		} break;
 		case MENU_OPTION_LOCK_VIEW: {
 		case MENU_OPTION_LOCK_VIEW: {
 
 
 			int index = options->get_popup()->get_item_index(MENU_OPTION_LOCK_VIEW);
 			int index = options->get_popup()->get_item_index(MENU_OPTION_LOCK_VIEW);
@@ -121,14 +118,15 @@ void GridMapEditor::_menu_option(int p_option) {
 		case MENU_OPTION_CURSOR_ROTATE_Y: {
 		case MENU_OPTION_CURSOR_ROTATE_Y: {
 
 
 			Basis r;
 			Basis r;
-			if (input_action == INPUT_DUPLICATE) {
+			if (input_action == INPUT_PASTE) {
 
 
-				r.set_orthogonal_index(selection.duplicate_rot);
+				r.set_orthogonal_index(paste_indicator.orientation);
 				r.rotate(Vector3(0, 1, 0), -Math_PI / 2.0);
 				r.rotate(Vector3(0, 1, 0), -Math_PI / 2.0);
-				selection.duplicate_rot = r.get_orthogonal_index();
-				_update_duplicate_indicator();
+				paste_indicator.orientation = r.get_orthogonal_index();
+				_update_paste_indicator();
 				break;
 				break;
 			}
 			}
+
 			r.set_orthogonal_index(cursor_rot);
 			r.set_orthogonal_index(cursor_rot);
 			r.rotate(Vector3(0, 1, 0), -Math_PI / 2.0);
 			r.rotate(Vector3(0, 1, 0), -Math_PI / 2.0);
 			cursor_rot = r.get_orthogonal_index();
 			cursor_rot = r.get_orthogonal_index();
@@ -137,12 +135,12 @@ void GridMapEditor::_menu_option(int p_option) {
 		case MENU_OPTION_CURSOR_ROTATE_X: {
 		case MENU_OPTION_CURSOR_ROTATE_X: {
 
 
 			Basis r;
 			Basis r;
-			if (input_action == INPUT_DUPLICATE) {
+			if (input_action == INPUT_PASTE) {
 
 
-				r.set_orthogonal_index(selection.duplicate_rot);
+				r.set_orthogonal_index(paste_indicator.orientation);
 				r.rotate(Vector3(1, 0, 0), -Math_PI / 2.0);
 				r.rotate(Vector3(1, 0, 0), -Math_PI / 2.0);
-				selection.duplicate_rot = r.get_orthogonal_index();
-				_update_duplicate_indicator();
+				paste_indicator.orientation = r.get_orthogonal_index();
+				_update_paste_indicator();
 				break;
 				break;
 			}
 			}
 
 
@@ -154,12 +152,12 @@ void GridMapEditor::_menu_option(int p_option) {
 		case MENU_OPTION_CURSOR_ROTATE_Z: {
 		case MENU_OPTION_CURSOR_ROTATE_Z: {
 
 
 			Basis r;
 			Basis r;
-			if (input_action == INPUT_DUPLICATE) {
+			if (input_action == INPUT_PASTE) {
 
 
-				r.set_orthogonal_index(selection.duplicate_rot);
+				r.set_orthogonal_index(paste_indicator.orientation);
 				r.rotate(Vector3(0, 0, 1), -Math_PI / 2.0);
 				r.rotate(Vector3(0, 0, 1), -Math_PI / 2.0);
-				selection.duplicate_rot = r.get_orthogonal_index();
-				_update_duplicate_indicator();
+				paste_indicator.orientation = r.get_orthogonal_index();
+				_update_paste_indicator();
 				break;
 				break;
 			}
 			}
 
 
@@ -171,6 +169,15 @@ void GridMapEditor::_menu_option(int p_option) {
 		case MENU_OPTION_CURSOR_BACK_ROTATE_Y: {
 		case MENU_OPTION_CURSOR_BACK_ROTATE_Y: {
 
 
 			Basis r;
 			Basis r;
+			if (input_action == INPUT_PASTE) {
+
+				r.set_orthogonal_index(paste_indicator.orientation);
+				r.rotate(Vector3(0, 1, 0), Math_PI / 2.0);
+				paste_indicator.orientation = r.get_orthogonal_index();
+				_update_paste_indicator();
+				break;
+			}
+
 			r.set_orthogonal_index(cursor_rot);
 			r.set_orthogonal_index(cursor_rot);
 			r.rotate(Vector3(0, 1, 0), Math_PI / 2.0);
 			r.rotate(Vector3(0, 1, 0), Math_PI / 2.0);
 			cursor_rot = r.get_orthogonal_index();
 			cursor_rot = r.get_orthogonal_index();
@@ -179,6 +186,15 @@ void GridMapEditor::_menu_option(int p_option) {
 		case MENU_OPTION_CURSOR_BACK_ROTATE_X: {
 		case MENU_OPTION_CURSOR_BACK_ROTATE_X: {
 
 
 			Basis r;
 			Basis r;
+			if (input_action == INPUT_PASTE) {
+
+				r.set_orthogonal_index(paste_indicator.orientation);
+				r.rotate(Vector3(1, 0, 0), Math_PI / 2.0);
+				paste_indicator.orientation = r.get_orthogonal_index();
+				_update_paste_indicator();
+				break;
+			}
+
 			r.set_orthogonal_index(cursor_rot);
 			r.set_orthogonal_index(cursor_rot);
 			r.rotate(Vector3(1, 0, 0), Math_PI / 2.0);
 			r.rotate(Vector3(1, 0, 0), Math_PI / 2.0);
 			cursor_rot = r.get_orthogonal_index();
 			cursor_rot = r.get_orthogonal_index();
@@ -187,6 +203,15 @@ void GridMapEditor::_menu_option(int p_option) {
 		case MENU_OPTION_CURSOR_BACK_ROTATE_Z: {
 		case MENU_OPTION_CURSOR_BACK_ROTATE_Z: {
 
 
 			Basis r;
 			Basis r;
+			if (input_action == INPUT_PASTE) {
+
+				r.set_orthogonal_index(paste_indicator.orientation);
+				r.rotate(Vector3(0, 0, 1), Math_PI / 2.0);
+				paste_indicator.orientation = r.get_orthogonal_index();
+				_update_paste_indicator();
+				break;
+			}
+
 			r.set_orthogonal_index(cursor_rot);
 			r.set_orthogonal_index(cursor_rot);
 			r.rotate(Vector3(0, 0, 1), Math_PI / 2.0);
 			r.rotate(Vector3(0, 0, 1), Math_PI / 2.0);
 			cursor_rot = r.get_orthogonal_index();
 			cursor_rot = r.get_orthogonal_index();
@@ -194,10 +219,10 @@ void GridMapEditor::_menu_option(int p_option) {
 		} break;
 		} break;
 		case MENU_OPTION_CURSOR_CLEAR_ROTATION: {
 		case MENU_OPTION_CURSOR_CLEAR_ROTATION: {
 
 
-			if (input_action == INPUT_DUPLICATE) {
+			if (input_action == INPUT_PASTE) {
 
 
-				selection.duplicate_rot = 0;
-				_update_duplicate_indicator();
+				paste_indicator.orientation = 0;
+				_update_paste_indicator();
 				break;
 				break;
 			}
 			}
 
 
@@ -205,28 +230,33 @@ void GridMapEditor::_menu_option(int p_option) {
 			_update_cursor_transform();
 			_update_cursor_transform();
 		} break;
 		} break;
 
 
-		case MENU_OPTION_DUPLICATE_SELECTS: {
-			int idx = options->get_popup()->get_item_index(MENU_OPTION_DUPLICATE_SELECTS);
+		case MENU_OPTION_PASTE_SELECTS: {
+			int idx = options->get_popup()->get_item_index(MENU_OPTION_PASTE_SELECTS);
 			options->get_popup()->set_item_checked(idx, !options->get_popup()->is_item_checked(idx));
 			options->get_popup()->set_item_checked(idx, !options->get_popup()->is_item_checked(idx));
 		} break;
 		} break;
-		case MENU_OPTION_SELECTION_DUPLICATE:
+
+		case MENU_OPTION_SELECTION_DUPLICATE: // fallthrough
+		case MENU_OPTION_SELECTION_CUT: {
 			if (!(selection.active && input_action == INPUT_NONE))
 			if (!(selection.active && input_action == INPUT_NONE))
-				return;
-			if (last_mouseover == Vector3(-1, -1, -1)) //nono mouseovering anythin
 				break;
 				break;
 
 
-			last_mouseover = selection.begin;
-			VS::get_singleton()->instance_set_transform(grid_instance[edit_axis], Transform(Basis(), grid_ofs));
+			_set_clipboard_data();
 
 
-			input_action = INPUT_DUPLICATE;
-			selection.click = last_mouseover;
-			selection.current = last_mouseover;
-			selection.duplicate_rot = 0;
-			_update_duplicate_indicator();
-			break;
+			if (p_option == MENU_OPTION_SELECTION_CUT) {
+				_delete_selection();
+			}
+
+			input_action = INPUT_PASTE;
+			paste_indicator.click = selection.begin;
+			paste_indicator.current = selection.begin;
+			paste_indicator.begin = selection.begin;
+			paste_indicator.end = selection.end;
+			paste_indicator.orientation = 0;
+			_update_paste_indicator();
+		} break;
 		case MENU_OPTION_SELECTION_CLEAR: {
 		case MENU_OPTION_SELECTION_CLEAR: {
 			if (!selection.active)
 			if (!selection.active)
-				return;
+				break;
 
 
 			_delete_selection();
 			_delete_selection();
 
 
@@ -315,17 +345,28 @@ void GridMapEditor::_validate_selection() {
 	_update_selection_transform();
 	_update_selection_transform();
 }
 }
 
 
+void GridMapEditor::_set_selection(bool p_active, const Vector3 p_begin, const Vector3 p_end) {
+
+	selection.active = p_active;
+	selection.begin = p_begin;
+	selection.end = p_end;
+	selection.click = p_begin;
+	selection.current = p_end;
+
+	_update_selection_transform();
+}
+
 bool GridMapEditor::do_input_action(Camera *p_camera, const Point2 &p_point, bool p_click) {
 bool GridMapEditor::do_input_action(Camera *p_camera, const Point2 &p_point, bool p_click) {
 
 
 	if (!spatial_editor)
 	if (!spatial_editor)
 		return false;
 		return false;
 
 
-	if (selected_palette < 0 && input_action != INPUT_COPY && input_action != INPUT_SELECT && input_action != INPUT_DUPLICATE)
+	if (selected_palette < 0 && input_action != INPUT_PICK && input_action != INPUT_SELECT && input_action != INPUT_PASTE)
 		return false;
 		return false;
 	Ref<MeshLibrary> mesh_library = node->get_mesh_library();
 	Ref<MeshLibrary> mesh_library = node->get_mesh_library();
 	if (mesh_library.is_null())
 	if (mesh_library.is_null())
 		return false;
 		return false;
-	if (input_action != INPUT_COPY && input_action != INPUT_SELECT && input_action != INPUT_DUPLICATE && !mesh_library->has_item(selected_palette))
+	if (input_action != INPUT_PICK && input_action != INPUT_SELECT && input_action != INPUT_PASTE && !mesh_library->has_item(selected_palette))
 		return false;
 		return false;
 
 
 	Camera *camera = p_camera;
 	Camera *camera = p_camera;
@@ -386,13 +427,17 @@ bool GridMapEditor::do_input_action(Camera *p_camera, const Point2 &p_point, boo
 		cursor_origin = (Vector3(cell[0], cell[1], cell[2]) + Vector3(0.5 * node->get_center_x(), 0.5 * node->get_center_y(), 0.5 * node->get_center_z())) * node->get_cell_size();
 		cursor_origin = (Vector3(cell[0], cell[1], cell[2]) + Vector3(0.5 * node->get_center_x(), 0.5 * node->get_center_y(), 0.5 * node->get_center_z())) * node->get_cell_size();
 		cursor_visible = true;
 		cursor_visible = true;
 
 
+		if (input_action == INPUT_SELECT || input_action == INPUT_PASTE) {
+			cursor_visible = false;
+		}
+
 		_update_cursor_transform();
 		_update_cursor_transform();
 	}
 	}
 
 
-	if (input_action == INPUT_DUPLICATE) {
+	if (input_action == INPUT_PASTE) {
 
 
-		selection.current = Vector3(cell[0], cell[1], cell[2]);
-		_update_duplicate_indicator();
+		paste_indicator.current = Vector3(cell[0], cell[1], cell[2]);
+		_update_paste_indicator();
 
 
 	} else if (input_action == INPUT_SELECT) {
 	} else if (input_action == INPUT_SELECT) {
 
 
@@ -403,7 +448,7 @@ bool GridMapEditor::do_input_action(Camera *p_camera, const Point2 &p_point, boo
 		_validate_selection();
 		_validate_selection();
 
 
 		return true;
 		return true;
-	} else if (input_action == INPUT_COPY) {
+	} else if (input_action == INPUT_PICK) {
 
 
 		int item = node->get_cell_item(cell[0], cell[1], cell[2]);
 		int item = node->get_cell_item(cell[0], cell[1], cell[2]);
 		if (item >= 0) {
 		if (item >= 0) {
@@ -456,10 +501,9 @@ void GridMapEditor::_delete_selection() {
 			}
 			}
 		}
 		}
 	}
 	}
+	undo_redo->add_do_method(this, "_set_selection", !selection.active, selection.begin, selection.end);
+	undo_redo->add_undo_method(this, "_set_selection", selection.active, selection.begin, selection.end);
 	undo_redo->commit_action();
 	undo_redo->commit_action();
-
-	selection.active = false;
-	_validate_selection();
 }
 }
 
 
 void GridMapEditor::_fill_selection() {
 void GridMapEditor::_fill_selection() {
@@ -479,97 +523,124 @@ void GridMapEditor::_fill_selection() {
 			}
 			}
 		}
 		}
 	}
 	}
+	undo_redo->add_do_method(this, "_set_selection", !selection.active, selection.begin, selection.end);
+	undo_redo->add_undo_method(this, "_set_selection", selection.active, selection.begin, selection.end);
 	undo_redo->commit_action();
 	undo_redo->commit_action();
+}
 
 
-	selection.active = false;
-	_validate_selection();
+void GridMapEditor::_clear_clipboard_data() {
+
+	for (List<ClipboardItem>::Element *E = clipboard_items.front(); E; E = E->next()) {
+
+		VisualServer::get_singleton()->free(E->get().instance);
+	}
+
+	clipboard_items.clear();
 }
 }
 
 
-void GridMapEditor::_update_duplicate_indicator() {
+void GridMapEditor::_set_clipboard_data() {
 
 
-	if (!selection.active || input_action != INPUT_DUPLICATE) {
+	_clear_clipboard_data();
+
+	Ref<MeshLibrary> meshLibrary = node->get_mesh_library();
+
+	for (int i = selection.begin.x; i <= selection.end.x; i++) {
+
+		for (int j = selection.begin.y; j <= selection.end.y; j++) {
+
+			for (int k = selection.begin.z; k <= selection.end.z; k++) {
+
+				int itm = node->get_cell_item(i, j, k);
+				if (itm == GridMap::INVALID_CELL_ITEM)
+					continue;
+
+				Ref<Mesh> mesh = meshLibrary->get_item_mesh(itm);
+
+				ClipboardItem item;
+				item.cell_item = itm;
+				item.grid_offset = Vector3(i, j, k) - selection.begin;
+				item.orientation = node->get_cell_item_orientation(i, j, k);
+				item.instance = VisualServer::get_singleton()->instance_create2(mesh->get_rid(), get_tree()->get_root()->get_world()->get_scenario());
+
+				clipboard_items.push_back(item);
+			}
+		}
+	}
+}
+
+void GridMapEditor::_update_paste_indicator() {
+
+	if (input_action != INPUT_PASTE) {
 
 
 		Transform xf;
 		Transform xf;
 		xf.basis.set_zero();
 		xf.basis.set_zero();
-		VisualServer::get_singleton()->instance_set_transform(duplicate_instance, xf);
+		VisualServer::get_singleton()->instance_set_transform(paste_instance, xf);
 		return;
 		return;
 	}
 	}
 
 
+	Vector3 center = 0.5 * Vector3(node->get_center_x(), node->get_center_y(), node->get_center_z());
+	Vector3 scale = (Vector3(1, 1, 1) + (paste_indicator.end - paste_indicator.begin)) * node->get_cell_size();
 	Transform xf;
 	Transform xf;
-	xf.scale(Vector3(1, 1, 1) * (Vector3(1, 1, 1) + (selection.end - selection.begin)) * node->get_cell_size());
-	xf.origin = (selection.begin + (selection.current - selection.click)) * node->get_cell_size();
+	xf.scale(scale);
+	xf.origin = (paste_indicator.begin + (paste_indicator.current - paste_indicator.click) + center) * node->get_cell_size();
 	Basis rot;
 	Basis rot;
-	rot.set_orthogonal_index(selection.duplicate_rot);
+	rot.set_orthogonal_index(paste_indicator.orientation);
 	xf.basis = rot * xf.basis;
 	xf.basis = rot * xf.basis;
+	xf.translate((-center * node->get_cell_size()) / scale);
 
 
-	VisualServer::get_singleton()->instance_set_transform(duplicate_instance, node->get_global_transform() * xf);
-}
+	VisualServer::get_singleton()->instance_set_transform(paste_instance, node->get_global_transform() * xf);
 
 
-struct __Item {
-	Vector3 pos;
-	int rot;
-	int item;
-};
-void GridMapEditor::_duplicate_paste() {
+	for (List<ClipboardItem>::Element *E = clipboard_items.front(); E; E = E->next()) {
 
 
-	if (!selection.active)
-		return;
+		ClipboardItem &item = E->get();
 
 
-	int idx = options->get_popup()->get_item_index(MENU_OPTION_DUPLICATE_SELECTS);
-	bool reselect = options->get_popup()->is_item_checked(idx);
+		xf = Transform();
+		xf.origin = (paste_indicator.begin + (paste_indicator.current - paste_indicator.click) + center) * node->get_cell_size();
+		xf.basis = rot * xf.basis;
+		xf.translate(item.grid_offset * node->get_cell_size());
+
+		Basis item_rot;
+		item_rot.set_orthogonal_index(item.orientation);
+		xf.basis = item_rot * xf.basis;
+
+		VisualServer::get_singleton()->instance_set_transform(item.instance, node->get_global_transform() * xf);
+	}
+}
 
 
-	List<__Item> items;
+void GridMapEditor::_do_paste() {
+
+	int idx = options->get_popup()->get_item_index(MENU_OPTION_PASTE_SELECTS);
+	bool reselect = options->get_popup()->is_item_checked(idx);
 
 
 	Basis rot;
 	Basis rot;
-	rot.set_orthogonal_index(selection.duplicate_rot);
+	rot.set_orthogonal_index(paste_indicator.orientation);
 
 
-	for (int i = selection.begin.x; i <= selection.end.x; i++) {
+	Vector3 ofs = paste_indicator.current - paste_indicator.click;
+	undo_redo->create_action(TTR("GridMap Paste Selection"));
 
 
-		for (int j = selection.begin.y; j <= selection.end.y; j++) {
+	for (List<ClipboardItem>::Element *E = clipboard_items.front(); E; E = E->next()) {
 
 
-			for (int k = selection.begin.z; k <= selection.end.z; k++) {
+		ClipboardItem &item = E->get();
 
 
-				int itm = node->get_cell_item(i, j, k);
-				if (itm == GridMap::INVALID_CELL_ITEM)
-					continue;
-				int orientation = node->get_cell_item_orientation(i, j, k);
-				__Item item;
-				Vector3 rel = Vector3(i, j, k) - selection.begin;
-				rel = rot.xform(rel);
-
-				Basis orm;
-				orm.set_orthogonal_index(orientation);
-				orm = rot * orm;
-
-				item.pos = selection.begin + rel;
-				item.item = itm;
-				item.rot = orm.get_orthogonal_index();
-				items.push_back(item);
-			}
-		}
-	}
+		Vector3 pos = rot.xform(item.grid_offset) + paste_indicator.begin + ofs;
 
 
-	Vector3 ofs = selection.current - selection.click;
-	if (items.size()) {
-		undo_redo->create_action(TTR("GridMap Duplicate Selection"));
-		for (List<__Item>::Element *E = items.front(); E; E = E->next()) {
-			__Item &it = E->get();
-			Vector3 pos = it.pos + ofs;
+		Basis orm;
+		orm.set_orthogonal_index(item.orientation);
+		orm = rot * orm;
 
 
-			undo_redo->add_do_method(node, "set_cell_item", pos.x, pos.y, pos.z, it.item, it.rot);
-			undo_redo->add_undo_method(node, "set_cell_item", pos.x, pos.y, pos.z, node->get_cell_item(pos.x, pos.y, pos.z), node->get_cell_item_orientation(pos.x, pos.y, pos.z));
-		}
-		undo_redo->commit_action();
+		undo_redo->add_do_method(node, "set_cell_item", pos.x, pos.y, pos.z, item.cell_item, orm.get_orthogonal_index());
+		undo_redo->add_undo_method(node, "set_cell_item", pos.x, pos.y, pos.z, node->get_cell_item(pos.x, pos.y, pos.z), node->get_cell_item_orientation(pos.x, pos.y, pos.z));
 	}
 	}
 
 
 	if (reselect) {
 	if (reselect) {
 
 
-		selection.begin += ofs;
-		selection.end += ofs;
-		selection.click = selection.begin;
-		selection.current = selection.end;
-		_validate_selection();
+		undo_redo->add_do_method(this, "_set_selection", true, paste_indicator.begin + ofs, paste_indicator.end + ofs);
+		undo_redo->add_undo_method(this, "_set_selection", selection.active, selection.begin, selection.end);
 	}
 	}
+
+	undo_redo->commit_action();
+
+	_clear_clipboard_data();
 }
 }
 
 
 bool GridMapEditor::forward_spatial_input_event(Camera *p_camera, const Ref<InputEvent> &p_event) {
 bool GridMapEditor::forward_spatial_input_event(Camera *p_camera, const Ref<InputEvent> &p_event) {
@@ -596,28 +667,31 @@ bool GridMapEditor::forward_spatial_input_event(Camera *p_camera, const Ref<Inpu
 
 
 			if (mb->get_button_index() == BUTTON_LEFT) {
 			if (mb->get_button_index() == BUTTON_LEFT) {
 
 
-				if (input_action == INPUT_DUPLICATE) {
-					//paste
-					_duplicate_paste();
+				if (input_action == INPUT_PASTE) {
+					_do_paste();
 					input_action = INPUT_NONE;
 					input_action = INPUT_NONE;
-					_update_duplicate_indicator();
+					_update_paste_indicator();
 				} else if (mb->get_shift()) {
 				} else if (mb->get_shift()) {
 					input_action = INPUT_SELECT;
 					input_action = INPUT_SELECT;
+					last_selection = selection;
 				} else if (mb->get_command()) {
 				} else if (mb->get_command()) {
-					input_action = INPUT_COPY;
+					input_action = INPUT_PICK;
 				} else {
 				} else {
 					input_action = INPUT_PAINT;
 					input_action = INPUT_PAINT;
 					set_items.clear();
 					set_items.clear();
 				}
 				}
 			} else if (mb->get_button_index() == BUTTON_RIGHT) {
 			} else if (mb->get_button_index() == BUTTON_RIGHT) {
-				if (input_action == INPUT_DUPLICATE) {
+				if (input_action == INPUT_PASTE) {
+					_clear_clipboard_data();
 					input_action = INPUT_NONE;
 					input_action = INPUT_NONE;
-					_update_duplicate_indicator();
-				} else if (mb->get_shift()) {
+					_update_paste_indicator();
+					return true;
+				} else if (selection.active) {
+					_set_selection(false);
+					return true;
+				} else {
 					input_action = INPUT_ERASE;
 					input_action = INPUT_ERASE;
 					set_items.clear();
 					set_items.clear();
-				} else {
-					return false;
 				}
 				}
 			} else {
 			} else {
 				return false;
 				return false;
@@ -650,13 +724,21 @@ bool GridMapEditor::forward_spatial_input_event(Camera *p_camera, const Ref<Inpu
 				return set_items.size() > 0;
 				return set_items.size() > 0;
 			}
 			}
 
 
+			if (mb->get_button_index() == BUTTON_LEFT && input_action == INPUT_SELECT) {
+
+				undo_redo->create_action("GridMap Selection");
+				undo_redo->add_do_method(this, "_set_selection", selection.active, selection.begin, selection.end);
+				undo_redo->add_undo_method(this, "_set_selection", last_selection.active, last_selection.begin, last_selection.end);
+				undo_redo->commit_action();
+			}
+
 			if (mb->get_button_index() == BUTTON_LEFT && input_action != INPUT_NONE) {
 			if (mb->get_button_index() == BUTTON_LEFT && input_action != INPUT_NONE) {
 
 
 				set_items.clear();
 				set_items.clear();
 				input_action = INPUT_NONE;
 				input_action = INPUT_NONE;
 				return true;
 				return true;
 			}
 			}
-			if (mb->get_button_index() == BUTTON_RIGHT && (input_action == INPUT_ERASE || input_action == INPUT_DUPLICATE)) {
+			if (mb->get_button_index() == BUTTON_RIGHT && (input_action == INPUT_ERASE || input_action == INPUT_PASTE)) {
 				input_action = INPUT_NONE;
 				input_action = INPUT_NONE;
 				return true;
 				return true;
 			}
 			}
@@ -670,6 +752,45 @@ bool GridMapEditor::forward_spatial_input_event(Camera *p_camera, const Ref<Inpu
 		return do_input_action(p_camera, mm->get_position(), false);
 		return do_input_action(p_camera, mm->get_position(), false);
 	}
 	}
 
 
+	Ref<InputEventKey> k = p_event;
+
+	if (k.is_valid()) {
+		if (k->is_pressed()) {
+			if (k->get_scancode() == KEY_ESCAPE) {
+
+				if (input_action == INPUT_PASTE) {
+					_clear_clipboard_data();
+					input_action = INPUT_NONE;
+					_update_paste_indicator();
+					return true;
+				} else if (selection.active) {
+					_set_selection(false);
+					return true;
+				} else {
+					selected_palette = -1;
+					mesh_library_palette->unselect_all();
+					update_palette();
+					_update_cursor_instance();
+					return true;
+				}
+			}
+
+			if (k->get_shift() && selection.active && input_action != INPUT_PASTE) {
+
+				if (k->get_scancode() == options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_PREV_LEVEL))) {
+					selection.click[edit_axis]--;
+					_validate_selection();
+					return true;
+				}
+				if (k->get_scancode() == options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_NEXT_LEVEL))) {
+					selection.click[edit_axis]++;
+					_validate_selection();
+					return true;
+				}
+			}
+		}
+	}
+
 	Ref<InputEventPanGesture> pan_gesture = p_event;
 	Ref<InputEventPanGesture> pan_gesture = p_event;
 	if (pan_gesture.is_valid()) {
 	if (pan_gesture.is_valid()) {
 
 
@@ -818,7 +939,7 @@ void GridMapEditor::edit(GridMap *p_gridmap) {
 	input_action = INPUT_NONE;
 	input_action = INPUT_NONE;
 	selection.active = false;
 	selection.active = false;
 	_update_selection_transform();
 	_update_selection_transform();
-	_update_duplicate_indicator();
+	_update_paste_indicator();
 
 
 	spatial_editor = Object::cast_to<SpatialEditorPlugin>(editor->get_editor_plugin_screen());
 	spatial_editor = Object::cast_to<SpatialEditorPlugin>(editor->get_editor_plugin_screen());
 
 
@@ -953,13 +1074,15 @@ void GridMapEditor::_notification(int p_what) {
 			}
 			}
 
 
 			selection_instance = VisualServer::get_singleton()->instance_create2(selection_mesh, get_tree()->get_root()->get_world()->get_scenario());
 			selection_instance = VisualServer::get_singleton()->instance_create2(selection_mesh, get_tree()->get_root()->get_world()->get_scenario());
-			duplicate_instance = VisualServer::get_singleton()->instance_create2(duplicate_mesh, get_tree()->get_root()->get_world()->get_scenario());
+			paste_instance = VisualServer::get_singleton()->instance_create2(paste_mesh, get_tree()->get_root()->get_world()->get_scenario());
 
 
 			_update_selection_transform();
 			_update_selection_transform();
-			_update_duplicate_indicator();
+			_update_paste_indicator();
 		} break;
 		} break;
 
 
 		case NOTIFICATION_EXIT_TREE: {
 		case NOTIFICATION_EXIT_TREE: {
+			_clear_clipboard_data();
+
 			for (int i = 0; i < 3; i++) {
 			for (int i = 0; i < 3; i++) {
 
 
 				VS::get_singleton()->free(grid_instance[i]);
 				VS::get_singleton()->free(grid_instance[i]);
@@ -970,9 +1093,9 @@ void GridMapEditor::_notification(int p_what) {
 			}
 			}
 
 
 			VisualServer::get_singleton()->free(selection_instance);
 			VisualServer::get_singleton()->free(selection_instance);
-			VisualServer::get_singleton()->free(duplicate_instance);
+			VisualServer::get_singleton()->free(paste_instance);
 			selection_instance = RID();
 			selection_instance = RID();
-			duplicate_instance = RID();
+			paste_instance = RID();
 		} break;
 		} break;
 
 
 		case NOTIFICATION_PROCESS: {
 		case NOTIFICATION_PROCESS: {
@@ -1065,6 +1188,7 @@ void GridMapEditor::_bind_methods() {
 	ClassDB::bind_method("_configure", &GridMapEditor::_configure);
 	ClassDB::bind_method("_configure", &GridMapEditor::_configure);
 	ClassDB::bind_method("_item_selected_cbk", &GridMapEditor::_item_selected_cbk);
 	ClassDB::bind_method("_item_selected_cbk", &GridMapEditor::_item_selected_cbk);
 	ClassDB::bind_method("_floor_changed", &GridMapEditor::_floor_changed);
 	ClassDB::bind_method("_floor_changed", &GridMapEditor::_floor_changed);
+	ClassDB::bind_method("_set_selection", &GridMapEditor::_set_selection);
 
 
 	ClassDB::bind_method(D_METHOD("_set_display_mode", "mode"), &GridMapEditor::_set_display_mode);
 	ClassDB::bind_method(D_METHOD("_set_display_mode", "mode"), &GridMapEditor::_set_display_mode);
 }
 }
@@ -1128,15 +1252,12 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) {
 	options->get_popup()->add_item(TTR("Cursor Back Rotate Z"), MENU_OPTION_CURSOR_BACK_ROTATE_Z, KEY_MASK_SHIFT + KEY_D);
 	options->get_popup()->add_item(TTR("Cursor Back Rotate Z"), MENU_OPTION_CURSOR_BACK_ROTATE_Z, KEY_MASK_SHIFT + KEY_D);
 	options->get_popup()->add_item(TTR("Cursor Clear Rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION, KEY_W);
 	options->get_popup()->add_item(TTR("Cursor Clear Rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION, KEY_W);
 	options->get_popup()->add_separator();
 	options->get_popup()->add_separator();
-	options->get_popup()->add_check_item("Duplicate Selects", MENU_OPTION_DUPLICATE_SELECTS);
-	options->get_popup()->add_separator();
-	options->get_popup()->add_item(TTR("Create Area"), MENU_OPTION_SELECTION_MAKE_AREA, KEY_CONTROL + KEY_C);
-	options->get_popup()->add_item(TTR("Create Exterior Connector"), MENU_OPTION_SELECTION_MAKE_EXTERIOR_CONNECTOR);
-	options->get_popup()->add_item(TTR("Erase Area"), MENU_OPTION_REMOVE_AREA);
+	options->get_popup()->add_check_item("Paste Selects", MENU_OPTION_PASTE_SELECTS);
 	options->get_popup()->add_separator();
 	options->get_popup()->add_separator();
-	options->get_popup()->add_item(TTR("Duplicate Selection"), MENU_OPTION_SELECTION_DUPLICATE, KEY_MASK_SHIFT + KEY_C);
-	options->get_popup()->add_item(TTR("Clear Selection"), MENU_OPTION_SELECTION_CLEAR, KEY_MASK_SHIFT + KEY_X);
-	options->get_popup()->add_item(TTR("Fill Selection"), MENU_OPTION_SELECTION_FILL, KEY_MASK_SHIFT + KEY_F);
+	options->get_popup()->add_item(TTR("Duplicate Selection"), MENU_OPTION_SELECTION_DUPLICATE, KEY_MASK_CTRL + KEY_C);
+	options->get_popup()->add_item(TTR("Cut Selection"), MENU_OPTION_SELECTION_CUT, KEY_MASK_CTRL + KEY_X);
+	options->get_popup()->add_item(TTR("Clear Selection"), MENU_OPTION_SELECTION_CLEAR, KEY_DELETE);
+	options->get_popup()->add_item(TTR("Fill Selection"), MENU_OPTION_SELECTION_FILL, KEY_MASK_CTRL + KEY_F);
 
 
 	options->get_popup()->add_separator();
 	options->get_popup()->add_separator();
 	options->get_popup()->add_item(TTR("Settings"), MENU_OPTION_GRIDMAP_SETTINGS);
 	options->get_popup()->add_item(TTR("Settings"), MENU_OPTION_GRIDMAP_SETTINGS);
@@ -1211,7 +1332,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) {
 	last_mouseover = Vector3(-1, -1, -1);
 	last_mouseover = Vector3(-1, -1, -1);
 
 
 	selection_mesh = VisualServer::get_singleton()->mesh_create();
 	selection_mesh = VisualServer::get_singleton()->mesh_create();
-	duplicate_mesh = VisualServer::get_singleton()->mesh_create();
+	paste_mesh = VisualServer::get_singleton()->mesh_create();
 
 
 	{
 	{
 		//selection mesh create
 		//selection mesh create
@@ -1319,12 +1440,12 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) {
 		VisualServer::get_singleton()->mesh_surface_set_material(selection_mesh, 1, outer_mat->get_rid());
 		VisualServer::get_singleton()->mesh_surface_set_material(selection_mesh, 1, outer_mat->get_rid());
 
 
 		d[VS::ARRAY_VERTEX] = triangles;
 		d[VS::ARRAY_VERTEX] = triangles;
-		VisualServer::get_singleton()->mesh_add_surface_from_arrays(duplicate_mesh, VS::PRIMITIVE_TRIANGLES, d);
-		VisualServer::get_singleton()->mesh_surface_set_material(duplicate_mesh, 0, inner_mat->get_rid());
+		VisualServer::get_singleton()->mesh_add_surface_from_arrays(paste_mesh, VS::PRIMITIVE_TRIANGLES, d);
+		VisualServer::get_singleton()->mesh_surface_set_material(paste_mesh, 0, inner_mat->get_rid());
 
 
 		d[VS::ARRAY_VERTEX] = lines;
 		d[VS::ARRAY_VERTEX] = lines;
-		VisualServer::get_singleton()->mesh_add_surface_from_arrays(duplicate_mesh, VS::PRIMITIVE_LINES, d);
-		VisualServer::get_singleton()->mesh_surface_set_material(duplicate_mesh, 1, outer_mat->get_rid());
+		VisualServer::get_singleton()->mesh_add_surface_from_arrays(paste_mesh, VS::PRIMITIVE_LINES, d);
+		VisualServer::get_singleton()->mesh_surface_set_material(paste_mesh, 1, outer_mat->get_rid());
 
 
 		for (int i = 0; i < 3; i++) {
 		for (int i = 0; i < 3; i++) {
 			d[VS::ARRAY_VERTEX] = square[i];
 			d[VS::ARRAY_VERTEX] = square[i];
@@ -1341,6 +1462,8 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) {
 
 
 GridMapEditor::~GridMapEditor() {
 GridMapEditor::~GridMapEditor() {
 
 
+	_clear_clipboard_data();
+
 	for (int i = 0; i < 3; i++) {
 	for (int i = 0; i < 3; i++) {
 
 
 		if (grid[i].is_valid())
 		if (grid[i].is_valid())
@@ -1359,9 +1482,9 @@ GridMapEditor::~GridMapEditor() {
 	if (selection_instance.is_valid())
 	if (selection_instance.is_valid())
 		VisualServer::get_singleton()->free(selection_instance);
 		VisualServer::get_singleton()->free(selection_instance);
 
 
-	VisualServer::get_singleton()->free(duplicate_mesh);
-	if (duplicate_instance.is_valid())
-		VisualServer::get_singleton()->free(duplicate_instance);
+	VisualServer::get_singleton()->free(paste_mesh);
+	if (paste_instance.is_valid())
+		VisualServer::get_singleton()->free(paste_instance);
 }
 }
 
 
 void GridMapEditorPlugin::_notification(int p_what) {
 void GridMapEditorPlugin::_notification(int p_what) {

+ 31 - 12
modules/gridmap/grid_map_editor_plugin.h

@@ -54,9 +54,9 @@ class GridMapEditor : public VBoxContainer {
 		INPUT_NONE,
 		INPUT_NONE,
 		INPUT_PAINT,
 		INPUT_PAINT,
 		INPUT_ERASE,
 		INPUT_ERASE,
-		INPUT_COPY,
+		INPUT_PICK,
 		INPUT_SELECT,
 		INPUT_SELECT,
-		INPUT_DUPLICATE,
+		INPUT_PASTE,
 	};
 	};
 
 
 	enum ClipMode {
 	enum ClipMode {
@@ -116,8 +116,17 @@ class GridMapEditor : public VBoxContainer {
 	RID selection_instance;
 	RID selection_instance;
 	RID selection_level_mesh[3];
 	RID selection_level_mesh[3];
 	RID selection_level_instance[3];
 	RID selection_level_instance[3];
-	RID duplicate_mesh;
-	RID duplicate_instance;
+	RID paste_mesh;
+	RID paste_instance;
+
+	struct ClipboardItem {
+		int cell_item;
+		Vector3 grid_offset;
+		int orientation;
+		RID instance;
+	};
+
+	List<ClipboardItem> clipboard_items;
 
 
 	Ref<SpatialMaterial> indicator_mat;
 	Ref<SpatialMaterial> indicator_mat;
 	Ref<SpatialMaterial> inner_mat;
 	Ref<SpatialMaterial> inner_mat;
@@ -132,9 +141,19 @@ class GridMapEditor : public VBoxContainer {
 		Vector3 current;
 		Vector3 current;
 		Vector3 begin;
 		Vector3 begin;
 		Vector3 end;
 		Vector3 end;
-		int duplicate_rot;
 		bool active;
 		bool active;
 	} selection;
 	} selection;
+	Selection last_selection;
+
+	struct PasteIndicator {
+
+		Vector3 click;
+		Vector3 current;
+		Vector3 begin;
+		Vector3 end;
+		int orientation;
+	};
+	PasteIndicator paste_indicator;
 
 
 	bool cursor_visible;
 	bool cursor_visible;
 	Transform cursor_transform;
 	Transform cursor_transform;
@@ -148,7 +167,6 @@ class GridMapEditor : public VBoxContainer {
 
 
 	enum Menu {
 	enum Menu {
 
 
-		MENU_OPTION_CONFIGURE,
 		MENU_OPTION_NEXT_LEVEL,
 		MENU_OPTION_NEXT_LEVEL,
 		MENU_OPTION_PREV_LEVEL,
 		MENU_OPTION_PREV_LEVEL,
 		MENU_OPTION_LOCK_VIEW,
 		MENU_OPTION_LOCK_VIEW,
@@ -165,13 +183,11 @@ class GridMapEditor : public VBoxContainer {
 		MENU_OPTION_CURSOR_BACK_ROTATE_X,
 		MENU_OPTION_CURSOR_BACK_ROTATE_X,
 		MENU_OPTION_CURSOR_BACK_ROTATE_Z,
 		MENU_OPTION_CURSOR_BACK_ROTATE_Z,
 		MENU_OPTION_CURSOR_CLEAR_ROTATION,
 		MENU_OPTION_CURSOR_CLEAR_ROTATION,
-		MENU_OPTION_DUPLICATE_SELECTS,
-		MENU_OPTION_SELECTION_MAKE_AREA,
-		MENU_OPTION_SELECTION_MAKE_EXTERIOR_CONNECTOR,
+		MENU_OPTION_PASTE_SELECTS,
 		MENU_OPTION_SELECTION_DUPLICATE,
 		MENU_OPTION_SELECTION_DUPLICATE,
+		MENU_OPTION_SELECTION_CUT,
 		MENU_OPTION_SELECTION_CLEAR,
 		MENU_OPTION_SELECTION_CLEAR,
 		MENU_OPTION_SELECTION_FILL,
 		MENU_OPTION_SELECTION_FILL,
-		MENU_OPTION_REMOVE_AREA,
 		MENU_OPTION_GRIDMAP_SETTINGS
 		MENU_OPTION_GRIDMAP_SETTINGS
 
 
 	};
 	};
@@ -200,10 +216,13 @@ class GridMapEditor : public VBoxContainer {
 
 
 	void _icon_size_changed(float p_value);
 	void _icon_size_changed(float p_value);
 
 
-	void _update_duplicate_indicator();
-	void _duplicate_paste();
+	void _clear_clipboard_data();
+	void _set_clipboard_data();
+	void _update_paste_indicator();
+	void _do_paste();
 	void _update_selection_transform();
 	void _update_selection_transform();
 	void _validate_selection();
 	void _validate_selection();
+	void _set_selection(bool p_active, const Vector3 p_begin = Vector3(), const Vector3 p_end = Vector3());
 
 
 	void _floor_changed(float p_value);
 	void _floor_changed(float p_value);