navigation_polygon_editor_plugin.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. /*************************************************************************/
  2. /* navigation_polygon_editor_plugin.cpp */
  3. /*************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /*************************************************************************/
  8. /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
  9. /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /*************************************************************************/
  30. #include "navigation_polygon_editor_plugin.h"
  31. #include "canvas_item_editor_plugin.h"
  32. #include "editor/editor_settings.h"
  33. #include "os/file_access.h"
  34. void NavigationPolygonEditor::_notification(int p_what) {
  35. switch (p_what) {
  36. case NOTIFICATION_READY: {
  37. button_create->set_icon(get_icon("Edit", "EditorIcons"));
  38. button_edit->set_icon(get_icon("MovePoint", "EditorIcons"));
  39. button_edit->set_pressed(true);
  40. get_tree()->connect("node_removed", this, "_node_removed");
  41. create_nav->connect("confirmed", this, "_create_nav");
  42. } break;
  43. case NOTIFICATION_FIXED_PROCESS: {
  44. } break;
  45. }
  46. }
  47. void NavigationPolygonEditor::_node_removed(Node *p_node) {
  48. if (p_node == node) {
  49. node = NULL;
  50. hide();
  51. canvas_item_editor->get_viewport_control()->update();
  52. }
  53. }
  54. void NavigationPolygonEditor::_create_nav() {
  55. if (!node)
  56. return;
  57. undo_redo->create_action(TTR("Create Navigation Polygon"));
  58. undo_redo->add_do_method(node, "set_navigation_polygon", Ref<NavigationPolygon>(memnew(NavigationPolygon)));
  59. undo_redo->add_undo_method(node, "set_navigation_polygon", Variant(REF()));
  60. undo_redo->commit_action();
  61. }
  62. void NavigationPolygonEditor::_menu_option(int p_option) {
  63. switch (p_option) {
  64. case MODE_CREATE: {
  65. mode = MODE_CREATE;
  66. button_create->set_pressed(true);
  67. button_edit->set_pressed(false);
  68. } break;
  69. case MODE_EDIT: {
  70. mode = MODE_EDIT;
  71. button_create->set_pressed(false);
  72. button_edit->set_pressed(true);
  73. } break;
  74. }
  75. }
  76. void NavigationPolygonEditor::_wip_close() {
  77. if (wip.size() >= 3) {
  78. undo_redo->create_action(TTR("Create Poly"));
  79. undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "remove_outline", node->get_navigation_polygon()->get_outline_count());
  80. undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "add_outline", wip);
  81. undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
  82. undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
  83. undo_redo->add_do_method(canvas_item_editor->get_viewport_control(), "update");
  84. undo_redo->add_undo_method(canvas_item_editor->get_viewport_control(), "update");
  85. undo_redo->commit_action();
  86. mode = MODE_EDIT;
  87. button_edit->set_pressed(true);
  88. button_create->set_pressed(false);
  89. }
  90. wip.clear();
  91. wip_active = false;
  92. edited_point = -1;
  93. }
  94. bool NavigationPolygonEditor::forward_input_event(const InputEvent &p_event) {
  95. if (!node)
  96. return false;
  97. if (node->get_navigation_polygon().is_null()) {
  98. if (p_event.type == InputEvent::MOUSE_BUTTON && p_event.mouse_button.button_index == 1 && p_event.mouse_button.pressed) {
  99. create_nav->set_text("No NavigationPolygon resource on this node.\nCreate and assign one?");
  100. create_nav->popup_centered_minsize();
  101. }
  102. return (p_event.type == InputEvent::MOUSE_BUTTON && p_event.mouse_button.button_index == 1);
  103. }
  104. switch (p_event.type) {
  105. case InputEvent::MOUSE_BUTTON: {
  106. const InputEventMouseButton &mb = p_event.mouse_button;
  107. Matrix32 xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform();
  108. Vector2 gpoint = Point2(mb.x, mb.y);
  109. Vector2 cpoint = canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint);
  110. cpoint = canvas_item_editor->snap_point(cpoint);
  111. cpoint = node->get_global_transform().affine_inverse().xform(cpoint);
  112. //first check if a point is to be added (segment split)
  113. real_t grab_treshold = EDITOR_DEF("poly_editor/point_grab_radius", 8);
  114. switch (mode) {
  115. case MODE_CREATE: {
  116. if (mb.button_index == BUTTON_LEFT && mb.pressed) {
  117. if (!wip_active) {
  118. wip.clear();
  119. wip.push_back(cpoint);
  120. wip_active = true;
  121. edited_point_pos = cpoint;
  122. edited_outline = -1;
  123. canvas_item_editor->get_viewport_control()->update();
  124. edited_point = 1;
  125. return true;
  126. } else {
  127. if (wip.size() > 1 && xform.xform(wip[0]).distance_to(gpoint) < grab_treshold) {
  128. //wip closed
  129. _wip_close();
  130. return true;
  131. } else {
  132. wip.push_back(cpoint);
  133. edited_point = wip.size();
  134. canvas_item_editor->get_viewport_control()->update();
  135. return true;
  136. //add wip point
  137. }
  138. }
  139. } else if (mb.button_index == BUTTON_RIGHT && mb.pressed && wip_active) {
  140. _wip_close();
  141. }
  142. } break;
  143. case MODE_EDIT: {
  144. if (mb.button_index == BUTTON_LEFT) {
  145. if (mb.pressed) {
  146. if (mb.mod.control) {
  147. //search edges
  148. int closest_outline = -1;
  149. int closest_idx = -1;
  150. Vector2 closest_pos;
  151. real_t closest_dist = 1e10;
  152. for (int j = 0; j < node->get_navigation_polygon()->get_outline_count(); j++) {
  153. DVector<Vector2> points = node->get_navigation_polygon()->get_outline(j);
  154. int pc = points.size();
  155. DVector<Vector2>::Read poly = points.read();
  156. for (int i = 0; i < pc; i++) {
  157. Vector2 points[2] = { xform.xform(poly[i]),
  158. xform.xform(poly[(i + 1) % pc]) };
  159. Vector2 cp = Geometry::get_closest_point_to_segment_2d(gpoint, points);
  160. if (cp.distance_squared_to(points[0]) < CMP_EPSILON2 || cp.distance_squared_to(points[1]) < CMP_EPSILON2)
  161. continue; //not valid to reuse point
  162. real_t d = cp.distance_to(gpoint);
  163. if (d < closest_dist && d < grab_treshold) {
  164. closest_dist = d;
  165. closest_outline = j;
  166. closest_pos = cp;
  167. closest_idx = i;
  168. }
  169. }
  170. }
  171. if (closest_idx >= 0) {
  172. pre_move_edit = node->get_navigation_polygon()->get_outline(closest_outline);
  173. DVector<Point2> poly = pre_move_edit;
  174. poly.insert(closest_idx + 1, xform.affine_inverse().xform(closest_pos));
  175. edited_point = closest_idx + 1;
  176. edited_outline = closest_outline;
  177. edited_point_pos = xform.affine_inverse().xform(closest_pos);
  178. node->get_navigation_polygon()->set_outline(closest_outline, poly);
  179. canvas_item_editor->get_viewport_control()->update();
  180. return true;
  181. }
  182. } else {
  183. //look for points to move
  184. int closest_outline = -1;
  185. int closest_idx = -1;
  186. Vector2 closest_pos;
  187. real_t closest_dist = 1e10;
  188. for (int j = 0; j < node->get_navigation_polygon()->get_outline_count(); j++) {
  189. DVector<Vector2> points = node->get_navigation_polygon()->get_outline(j);
  190. int pc = points.size();
  191. DVector<Vector2>::Read poly = points.read();
  192. for (int i = 0; i < pc; i++) {
  193. Vector2 cp = xform.xform(poly[i]);
  194. real_t d = cp.distance_to(gpoint);
  195. if (d < closest_dist && d < grab_treshold) {
  196. closest_dist = d;
  197. closest_pos = cp;
  198. closest_outline = j;
  199. closest_idx = i;
  200. }
  201. }
  202. }
  203. if (closest_idx >= 0) {
  204. pre_move_edit = node->get_navigation_polygon()->get_outline(closest_outline);
  205. edited_point = closest_idx;
  206. edited_outline = closest_outline;
  207. edited_point_pos = xform.affine_inverse().xform(closest_pos);
  208. canvas_item_editor->get_viewport_control()->update();
  209. return true;
  210. }
  211. }
  212. } else {
  213. if (edited_point != -1) {
  214. //apply
  215. DVector<Vector2> poly = node->get_navigation_polygon()->get_outline(edited_outline);
  216. ERR_FAIL_INDEX_V(edited_point, poly.size(), false);
  217. poly.set(edited_point, edited_point_pos);
  218. undo_redo->create_action(TTR("Edit Poly"));
  219. undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "set_outline", edited_outline, poly);
  220. undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "set_outline", edited_outline, pre_move_edit);
  221. undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
  222. undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
  223. undo_redo->add_do_method(canvas_item_editor->get_viewport_control(), "update");
  224. undo_redo->add_undo_method(canvas_item_editor->get_viewport_control(), "update");
  225. undo_redo->commit_action();
  226. edited_point = -1;
  227. return true;
  228. }
  229. }
  230. }
  231. if (mb.button_index == BUTTON_RIGHT && mb.pressed && edited_point == -1) {
  232. int closest_outline = -1;
  233. int closest_idx = -1;
  234. Vector2 closest_pos;
  235. real_t closest_dist = 1e10;
  236. for (int j = 0; j < node->get_navigation_polygon()->get_outline_count(); j++) {
  237. DVector<Vector2> points = node->get_navigation_polygon()->get_outline(j);
  238. int pc = points.size();
  239. DVector<Vector2>::Read poly = points.read();
  240. for (int i = 0; i < pc; i++) {
  241. Vector2 cp = xform.xform(poly[i]);
  242. real_t d = cp.distance_to(gpoint);
  243. if (d < closest_dist && d < grab_treshold) {
  244. closest_dist = d;
  245. closest_pos = cp;
  246. closest_outline = j;
  247. closest_idx = i;
  248. }
  249. }
  250. }
  251. if (closest_idx >= 0) {
  252. DVector<Vector2> poly = node->get_navigation_polygon()->get_outline(closest_outline);
  253. if (poly.size() > 3) {
  254. undo_redo->create_action(TTR("Edit Poly (Remove Point)"));
  255. undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "set_outline", closest_outline, poly);
  256. poly.remove(closest_idx);
  257. undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "set_outline", closest_outline, poly);
  258. undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
  259. undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
  260. undo_redo->add_do_method(canvas_item_editor->get_viewport_control(), "update");
  261. undo_redo->add_undo_method(canvas_item_editor->get_viewport_control(), "update");
  262. undo_redo->commit_action();
  263. } else {
  264. undo_redo->create_action(TTR("Remove Poly And Point"));
  265. undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "add_outline_at_index", poly, closest_outline);
  266. poly.remove(closest_idx);
  267. undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "remove_outline", closest_outline);
  268. undo_redo->add_do_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
  269. undo_redo->add_undo_method(node->get_navigation_polygon().ptr(), "make_polygons_from_outlines");
  270. undo_redo->add_do_method(canvas_item_editor->get_viewport_control(), "update");
  271. undo_redo->add_undo_method(canvas_item_editor->get_viewport_control(), "update");
  272. undo_redo->commit_action();
  273. }
  274. return true;
  275. }
  276. }
  277. } break;
  278. }
  279. } break;
  280. case InputEvent::MOUSE_MOTION: {
  281. const InputEventMouseMotion &mm = p_event.mouse_motion;
  282. if (edited_point != -1 && (wip_active || mm.button_mask & BUTTON_MASK_LEFT)) {
  283. Vector2 gpoint = Point2(mm.x, mm.y);
  284. Vector2 cpoint = canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint);
  285. cpoint = canvas_item_editor->snap_point(cpoint);
  286. edited_point_pos = node->get_global_transform().affine_inverse().xform(cpoint);
  287. canvas_item_editor->get_viewport_control()->update();
  288. }
  289. } break;
  290. }
  291. return false;
  292. }
  293. void NavigationPolygonEditor::_canvas_draw() {
  294. if (!node)
  295. return;
  296. Control *vpc = canvas_item_editor->get_viewport_control();
  297. if (node->get_navigation_polygon().is_null())
  298. return;
  299. Matrix32 xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform();
  300. Ref<Texture> handle = get_icon("EditorHandle", "EditorIcons");
  301. for (int j = -1; j < node->get_navigation_polygon()->get_outline_count(); j++) {
  302. Vector<Vector2> poly;
  303. if (wip_active && j == edited_outline) {
  304. poly = wip;
  305. } else {
  306. if (j == -1)
  307. continue;
  308. poly = Variant(node->get_navigation_polygon()->get_outline(j));
  309. }
  310. for (int i = 0; i < poly.size(); i++) {
  311. Vector2 p, p2;
  312. p = (j == edited_outline && i == edited_point) ? edited_point_pos : poly[i];
  313. if (j == edited_outline && ((wip_active && i == poly.size() - 1) || (((i + 1) % poly.size()) == edited_point)))
  314. p2 = edited_point_pos;
  315. else
  316. p2 = poly[(i + 1) % poly.size()];
  317. Vector2 point = xform.xform(p);
  318. Vector2 next_point = xform.xform(p2);
  319. Color col = Color(1, 0.3, 0.1, 0.8);
  320. vpc->draw_line(point, next_point, col, 2);
  321. vpc->draw_texture(handle, point - handle->get_size() * 0.5);
  322. }
  323. }
  324. }
  325. void NavigationPolygonEditor::edit(Node *p_collision_polygon) {
  326. if (!canvas_item_editor) {
  327. canvas_item_editor = CanvasItemEditor::get_singleton();
  328. }
  329. if (p_collision_polygon) {
  330. node = p_collision_polygon->cast_to<NavigationPolygonInstance>();
  331. if (!canvas_item_editor->get_viewport_control()->is_connected("draw", this, "_canvas_draw"))
  332. canvas_item_editor->get_viewport_control()->connect("draw", this, "_canvas_draw");
  333. wip.clear();
  334. wip_active = false;
  335. edited_point = -1;
  336. canvas_item_editor->get_viewport_control()->update();
  337. } else {
  338. node = NULL;
  339. if (canvas_item_editor->get_viewport_control()->is_connected("draw", this, "_canvas_draw"))
  340. canvas_item_editor->get_viewport_control()->disconnect("draw", this, "_canvas_draw");
  341. }
  342. }
  343. void NavigationPolygonEditor::_bind_methods() {
  344. ObjectTypeDB::bind_method(_MD("_menu_option"), &NavigationPolygonEditor::_menu_option);
  345. ObjectTypeDB::bind_method(_MD("_canvas_draw"), &NavigationPolygonEditor::_canvas_draw);
  346. ObjectTypeDB::bind_method(_MD("_node_removed"), &NavigationPolygonEditor::_node_removed);
  347. ObjectTypeDB::bind_method(_MD("_create_nav"), &NavigationPolygonEditor::_create_nav);
  348. }
  349. NavigationPolygonEditor::NavigationPolygonEditor(EditorNode *p_editor) {
  350. node = NULL;
  351. canvas_item_editor = NULL;
  352. editor = p_editor;
  353. undo_redo = editor->get_undo_redo();
  354. add_child(memnew(VSeparator));
  355. button_create = memnew(ToolButton);
  356. add_child(button_create);
  357. button_create->connect("pressed", this, "_menu_option", varray(MODE_CREATE));
  358. button_create->set_toggle_mode(true);
  359. button_create->set_tooltip(TTR("Create a new polygon from scratch."));
  360. button_edit = memnew(ToolButton);
  361. add_child(button_edit);
  362. button_edit->connect("pressed", this, "_menu_option", varray(MODE_EDIT));
  363. button_edit->set_toggle_mode(true);
  364. button_edit->set_tooltip(TTR("Edit existing polygon:") + "\n" + TTR("LMB: Move Point.") + "\n" + TTR("Ctrl+LMB: Split Segment.") + "\n" + TTR("RMB: Erase Point."));
  365. create_nav = memnew(ConfirmationDialog);
  366. add_child(create_nav);
  367. create_nav->get_ok()->set_text(TTR("Create"));
  368. //add_constant_override("separation",0);
  369. #if 0
  370. options = memnew( MenuButton );
  371. add_child(options);
  372. options->set_area_as_parent_rect();
  373. options->set_text("Polygon");
  374. //options->get_popup()->add_item("Parse BBCode",PARSE_BBCODE);
  375. options->get_popup()->connect("item_pressed", this,"_menu_option");
  376. #endif
  377. mode = MODE_EDIT;
  378. wip_active = false;
  379. edited_outline = -1;
  380. }
  381. void NavigationPolygonEditorPlugin::edit(Object *p_object) {
  382. collision_polygon_editor->edit(p_object->cast_to<Node>());
  383. }
  384. bool NavigationPolygonEditorPlugin::handles(Object *p_object) const {
  385. return p_object->is_type("NavigationPolygonInstance");
  386. }
  387. void NavigationPolygonEditorPlugin::make_visible(bool p_visible) {
  388. if (p_visible) {
  389. collision_polygon_editor->show();
  390. } else {
  391. collision_polygon_editor->hide();
  392. collision_polygon_editor->edit(NULL);
  393. }
  394. }
  395. NavigationPolygonEditorPlugin::NavigationPolygonEditorPlugin(EditorNode *p_node) {
  396. editor = p_node;
  397. collision_polygon_editor = memnew(NavigationPolygonEditor(p_node));
  398. CanvasItemEditor::get_singleton()->add_control_to_menu_panel(collision_polygon_editor);
  399. collision_polygon_editor->hide();
  400. }
  401. NavigationPolygonEditorPlugin::~NavigationPolygonEditorPlugin() {
  402. }