Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/scene/2d/path_2d_editor_plugin.cpp
9913 views
1
/**************************************************************************/
2
/* path_2d_editor_plugin.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
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
31
#include "path_2d_editor_plugin.h"
32
33
#include "core/os/keyboard.h"
34
#include "editor/editor_node.h"
35
#include "editor/editor_undo_redo_manager.h"
36
#include "editor/scene/canvas_item_editor_plugin.h"
37
#include "editor/settings/editor_settings.h"
38
#include "editor/themes/editor_scale.h"
39
#include "scene/gui/dialogs.h"
40
#include "scene/gui/menu_button.h"
41
42
void Path2DEditor::_notification(int p_what) {
43
switch (p_what) {
44
case NOTIFICATION_THEME_CHANGED: {
45
curve_edit->set_button_icon(get_editor_theme_icon(SNAME("CurveEdit")));
46
curve_edit_curve->set_button_icon(get_editor_theme_icon(SNAME("CurveCurve")));
47
curve_create->set_button_icon(get_editor_theme_icon(SNAME("CurveCreate")));
48
curve_del->set_button_icon(get_editor_theme_icon(SNAME("CurveDelete")));
49
curve_close->set_button_icon(get_editor_theme_icon(SNAME("CurveClose")));
50
curve_clear_points->set_button_icon(get_editor_theme_icon(SNAME("Clear")));
51
52
create_curve_button->set_button_icon(get_editor_theme_icon(SNAME("Curve2D")));
53
} break;
54
}
55
}
56
57
void Path2DEditor::_node_removed(Node *p_node) {
58
if (p_node == node) {
59
node = nullptr;
60
hide();
61
}
62
}
63
64
bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
65
if (!node) {
66
return false;
67
}
68
69
if (!node->is_visible_in_tree()) {
70
return false;
71
}
72
73
Viewport *vp = node->get_viewport();
74
if (vp && !vp->is_visible_subviewport()) {
75
return false;
76
}
77
78
if (node->get_curve().is_null()) {
79
return false;
80
}
81
82
real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
83
84
Ref<InputEventMouseButton> mb = p_event;
85
if (mb.is_valid()) {
86
Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_screen_transform();
87
88
Vector2 gpoint = mb->get_position();
89
Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint));
90
cpoint = node->to_local(node->get_viewport()->get_popup_base_transform().affine_inverse().xform(cpoint));
91
92
if (mb->is_pressed() && action == ACTION_NONE) {
93
Ref<Curve2D> curve = node->get_curve();
94
95
original_mouse_pos = gpoint;
96
97
for (int i = 0; i < curve->get_point_count(); i++) {
98
real_t dist_to_p = gpoint.distance_to(xform.xform(curve->get_point_position(i)));
99
real_t dist_to_p_out = gpoint.distance_to(xform.xform(curve->get_point_position(i) + curve->get_point_out(i)));
100
real_t dist_to_p_in = gpoint.distance_to(xform.xform(curve->get_point_position(i) + curve->get_point_in(i)));
101
102
// Check for point movement start (for point + in/out controls).
103
if (mb->get_button_index() == MouseButton::LEFT) {
104
if (mode == MODE_EDIT && !mb->is_shift_pressed() && dist_to_p < grab_threshold) {
105
// Points can only be moved in edit mode.
106
107
action = ACTION_MOVING_POINT;
108
action_point = i;
109
moving_from = curve->get_point_position(i);
110
moving_screen_from = gpoint;
111
return true;
112
} else if (mode == MODE_EDIT || mode == MODE_EDIT_CURVE) {
113
control_points_in_range = 0;
114
// In/out controls can be moved in multiple modes.
115
if (dist_to_p_out < grab_threshold && i < (curve->get_point_count() - 1)) {
116
action = ACTION_MOVING_OUT;
117
action_point = i;
118
moving_from = curve->get_point_out(i);
119
moving_screen_from = gpoint;
120
orig_in_length = curve->get_point_in(action_point).length();
121
control_points_in_range += 1;
122
}
123
if (dist_to_p_in < grab_threshold && i > 0) {
124
action = ACTION_MOVING_IN;
125
action_point = i;
126
moving_from = curve->get_point_in(i);
127
moving_screen_from = gpoint;
128
orig_out_length = curve->get_point_out(action_point).length();
129
control_points_in_range += 1;
130
}
131
if (control_points_in_range > 0) {
132
return true;
133
}
134
}
135
}
136
137
// Check for point deletion.
138
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
139
if ((mb->get_button_index() == MouseButton::RIGHT && (mode == MODE_EDIT || mode == MODE_CREATE)) || (mb->get_button_index() == MouseButton::LEFT && mode == MODE_DELETE)) {
140
if (dist_to_p < grab_threshold) {
141
undo_redo->create_action(TTR("Remove Point from Curve"));
142
undo_redo->add_do_method(curve.ptr(), "remove_point", i);
143
undo_redo->add_undo_method(curve.ptr(), "add_point", curve->get_point_position(i), curve->get_point_in(i), curve->get_point_out(i), i);
144
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
145
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
146
undo_redo->commit_action();
147
return true;
148
} else if (dist_to_p_out < grab_threshold) {
149
undo_redo->create_action(TTR("Remove Out-Control from Curve"));
150
undo_redo->add_do_method(curve.ptr(), "set_point_out", i, Vector2());
151
undo_redo->add_undo_method(curve.ptr(), "set_point_out", i, curve->get_point_out(i));
152
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
153
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
154
undo_redo->commit_action();
155
return true;
156
} else if (dist_to_p_in < grab_threshold) {
157
undo_redo->create_action(TTR("Remove In-Control from Curve"));
158
undo_redo->add_do_method(curve.ptr(), "set_point_in", i, Vector2());
159
undo_redo->add_undo_method(curve.ptr(), "set_point_in", i, curve->get_point_in(i));
160
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
161
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
162
undo_redo->commit_action();
163
return true;
164
}
165
}
166
}
167
}
168
169
if (action != ACTION_NONE && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
170
_cancel_current_action();
171
return true;
172
}
173
174
// Check for point creation.
175
if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && ((mb->is_command_or_control_pressed() && mode == MODE_EDIT) || mode == MODE_CREATE)) {
176
Ref<Curve2D> curve = node->get_curve();
177
curve->add_point(cpoint);
178
moving_from = cpoint;
179
180
action = ACTION_MOVING_NEW_POINT;
181
action_point = curve->get_point_count() - 1;
182
moving_from = curve->get_point_position(action_point);
183
moving_screen_from = gpoint;
184
185
canvas_item_editor->update_viewport();
186
187
return true;
188
}
189
190
// Check for segment split.
191
if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && mode == MODE_EDIT && on_edge) {
192
Vector2 gpoint2 = mb->get_position();
193
Ref<Curve2D> curve = node->get_curve();
194
195
int insertion_point = -1;
196
float mbLength = curve->get_closest_offset(xform.affine_inverse().xform(gpoint2));
197
int len = curve->get_point_count();
198
for (int i = 0; i < len - 1; i++) {
199
float compareLength = curve->get_closest_offset(curve->get_point_position(i + 1));
200
if (mbLength >= curve->get_closest_offset(curve->get_point_position(i)) && mbLength <= compareLength) {
201
insertion_point = i;
202
}
203
}
204
if (insertion_point == -1) {
205
insertion_point = curve->get_point_count() - 2;
206
}
207
208
const Vector2 new_point = xform.affine_inverse().xform(gpoint2);
209
curve->add_point(new_point, Vector2(0, 0), Vector2(0, 0), insertion_point + 1);
210
211
action = ACTION_MOVING_NEW_POINT_FROM_SPLIT;
212
action_point = insertion_point + 1;
213
moving_from = curve->get_point_position(action_point);
214
moving_screen_from = gpoint2;
215
216
canvas_item_editor->update_viewport();
217
218
on_edge = false;
219
220
return true;
221
}
222
223
// Check for point movement completion.
224
if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && action != ACTION_NONE) {
225
Ref<Curve2D> curve = node->get_curve();
226
227
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
228
Vector2 new_pos = moving_from + xform.affine_inverse().basis_xform(gpoint - moving_screen_from);
229
switch (action) {
230
case ACTION_NONE:
231
// N/A, handled in above condition.
232
break;
233
234
case ACTION_MOVING_POINT:
235
if (original_mouse_pos != gpoint) {
236
undo_redo->create_action(TTR("Move Point in Curve"));
237
undo_redo->add_undo_method(curve.ptr(), "set_point_position", action_point, moving_from);
238
undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint);
239
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
240
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
241
undo_redo->commit_action(false);
242
}
243
break;
244
case ACTION_MOVING_NEW_POINT: {
245
undo_redo->create_action(TTR("Add Point to Curve"));
246
undo_redo->add_do_method(curve.ptr(), "add_point", cpoint);
247
undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint);
248
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
249
undo_redo->add_undo_method(curve.ptr(), "remove_point", curve->get_point_count() - 1);
250
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
251
undo_redo->commit_action(false);
252
} break;
253
254
case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
255
undo_redo->create_action(TTR("Split Curve"));
256
undo_redo->add_do_method(curve.ptr(), "add_point", Vector2(), Vector2(), Vector2(), action_point);
257
undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint);
258
undo_redo->add_undo_method(curve.ptr(), "remove_point", action_point);
259
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
260
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
261
undo_redo->commit_action(false);
262
} break;
263
264
case ACTION_MOVING_IN: {
265
if (original_mouse_pos != gpoint) {
266
undo_redo->create_action(TTR("Move In-Control in Curve"));
267
undo_redo->add_do_method(curve.ptr(), "set_point_in", action_point, new_pos);
268
undo_redo->add_undo_method(curve.ptr(), "set_point_in", action_point, moving_from);
269
270
if (mirror_handle_angle) {
271
undo_redo->add_do_method(curve.ptr(), "set_point_out", action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_out_length));
272
undo_redo->add_undo_method(curve.ptr(), "set_point_out", action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_out_length));
273
}
274
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
275
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
276
undo_redo->commit_action();
277
}
278
} break;
279
280
case ACTION_MOVING_OUT: {
281
if (original_mouse_pos != gpoint) {
282
undo_redo->create_action(TTR("Move Out-Control in Curve"));
283
undo_redo->add_do_method(curve.ptr(), "set_point_out", action_point, new_pos);
284
undo_redo->add_undo_method(curve.ptr(), "set_point_out", action_point, moving_from);
285
286
if (mirror_handle_angle) {
287
undo_redo->add_do_method(curve.ptr(), "set_point_in", action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_in_length));
288
undo_redo->add_undo_method(curve.ptr(), "set_point_in", action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_in_length));
289
}
290
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
291
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
292
undo_redo->commit_action();
293
}
294
} break;
295
}
296
297
action = ACTION_NONE;
298
299
return true;
300
}
301
}
302
303
Ref<InputEventMouseMotion> mm = p_event;
304
305
if (mm.is_valid()) {
306
// When both control points were in range of click,
307
// pick the point that drags the curve outwards.
308
if (control_points_in_range == 2) {
309
control_points_in_range = 0;
310
Ref<Curve2D> curve = node->get_curve();
311
Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_screen_transform();
312
Point2 relative = xform.affine_inverse().basis_xform(mm->get_relative());
313
real_t angle_in = relative.angle_to(curve->get_point_position(action_point - 1) - curve->get_point_position(action_point));
314
real_t angle_out = relative.angle_to(curve->get_point_position(action_point + 1) - curve->get_point_position(action_point));
315
316
if (Math::abs(angle_in) < Math::abs(angle_out)) {
317
action = ACTION_MOVING_IN;
318
moving_from = curve->get_point_in(action_point);
319
orig_out_length = curve->get_point_out(action_point).length();
320
} else {
321
action = ACTION_MOVING_OUT;
322
moving_from = curve->get_point_out(action_point);
323
orig_in_length = curve->get_point_in(action_point).length();
324
}
325
}
326
327
if (action == ACTION_NONE && mode == MODE_EDIT) {
328
// Handle Edge Follow
329
bool old_edge = on_edge;
330
331
Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_screen_transform();
332
Vector2 gpoint = mm->get_position();
333
334
Ref<Curve2D> curve = node->get_curve();
335
if (curve.is_null()) {
336
return true;
337
}
338
if (curve->get_point_count() < 2) {
339
return true;
340
}
341
342
// Find edge
343
edge_point = xform.xform(curve->get_closest_point(xform.affine_inverse().xform(mm->get_position())));
344
on_edge = false;
345
if (edge_point.distance_to(gpoint) <= grab_threshold) {
346
on_edge = true;
347
}
348
// However, if near a control point or its in-out handles then not on edge
349
int len = curve->get_point_count();
350
for (int i = 0; i < len; i++) {
351
Vector2 pp = curve->get_point_position(i);
352
Vector2 p = xform.xform(pp);
353
if (p.distance_to(gpoint) <= grab_threshold) {
354
on_edge = false;
355
break;
356
}
357
p = xform.xform(pp + curve->get_point_in(i));
358
if (p.distance_to(gpoint) <= grab_threshold) {
359
on_edge = false;
360
break;
361
}
362
p = xform.xform(pp + curve->get_point_out(i));
363
if (p.distance_to(gpoint) <= grab_threshold) {
364
on_edge = false;
365
break;
366
}
367
}
368
if (on_edge || old_edge != on_edge) {
369
canvas_item_editor->update_viewport();
370
return true;
371
}
372
}
373
374
if (action != ACTION_NONE) {
375
// Handle point/control movement.
376
Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_screen_transform();
377
Vector2 gpoint = mm->get_position();
378
Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint));
379
cpoint = node->to_local(node->get_viewport()->get_popup_base_transform().affine_inverse().xform(cpoint));
380
381
Ref<Curve2D> curve = node->get_curve();
382
383
Vector2 new_pos = moving_from + xform.affine_inverse().basis_xform(gpoint - moving_screen_from);
384
385
switch (action) {
386
case ACTION_NONE:
387
// N/A, handled in above condition.
388
break;
389
390
case ACTION_MOVING_POINT:
391
case ACTION_MOVING_NEW_POINT:
392
case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
393
curve->set_point_position(action_point, cpoint);
394
} break;
395
396
case ACTION_MOVING_IN: {
397
curve->set_point_in(action_point, new_pos);
398
399
if (mirror_handle_angle) {
400
curve->set_point_out(action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_out_length));
401
}
402
} break;
403
404
case ACTION_MOVING_OUT: {
405
curve->set_point_out(action_point, new_pos);
406
407
if (mirror_handle_angle) {
408
curve->set_point_in(action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_in_length));
409
}
410
} break;
411
}
412
413
canvas_item_editor->update_viewport();
414
return true;
415
}
416
}
417
418
return false;
419
}
420
421
void Path2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
422
if (!node || !node->is_visible_in_tree() || node->get_curve().is_null()) {
423
return;
424
}
425
426
Viewport *vp = node->get_viewport();
427
if (vp && !vp->is_visible_subviewport()) {
428
return;
429
}
430
431
Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_screen_transform();
432
433
const Ref<Texture2D> path_sharp_handle = get_editor_theme_icon(SNAME("EditorPathSharpHandle"));
434
const Ref<Texture2D> path_smooth_handle = get_editor_theme_icon(SNAME("EditorPathSmoothHandle"));
435
// Both handle icons must be of the same size
436
const Size2 handle_size = path_sharp_handle->get_size();
437
438
const Ref<Texture2D> curve_handle = get_editor_theme_icon(SNAME("EditorCurveHandle"));
439
const Size2 curve_handle_size = curve_handle->get_size();
440
441
Ref<Curve2D> curve = node->get_curve();
442
443
int len = curve->get_point_count();
444
Control *vpc = canvas_item_editor->get_viewport_control();
445
446
debug_handle_lines.clear();
447
debug_handle_curve_transforms.clear();
448
debug_handle_sharp_transforms.clear();
449
debug_handle_smooth_transforms.clear();
450
451
Transform2D handle_curve_transform = Transform2D().scaled(curve_handle_size * 0.5);
452
Transform2D handle_point_transform = Transform2D().scaled(handle_size * 0.5);
453
454
for (int i = 0; i < len; i++) {
455
Vector2 point = xform.xform(curve->get_point_position(i));
456
// Determines the point icon to be used
457
bool smooth = false;
458
459
if (i < len - 1) {
460
Vector2 point_out = xform.xform(curve->get_point_position(i) + curve->get_point_out(i));
461
if (point != point_out) {
462
smooth = true;
463
debug_handle_lines.push_back(point);
464
debug_handle_lines.push_back(point_out);
465
handle_curve_transform.set_origin(point_out);
466
debug_handle_curve_transforms.push_back(handle_curve_transform);
467
}
468
}
469
470
if (i > 0) {
471
Vector2 point_in = xform.xform(curve->get_point_position(i) + curve->get_point_in(i));
472
if (point != point_in) {
473
smooth = true;
474
debug_handle_lines.push_back(point);
475
debug_handle_lines.push_back(point_in);
476
handle_curve_transform.set_origin(point_in);
477
debug_handle_curve_transforms.push_back(handle_curve_transform);
478
}
479
}
480
481
handle_point_transform.set_origin(point);
482
if (smooth) {
483
debug_handle_smooth_transforms.push_back(handle_point_transform);
484
} else {
485
debug_handle_sharp_transforms.push_back(handle_point_transform);
486
}
487
}
488
489
if (on_edge) {
490
Ref<Texture2D> add_handle = get_editor_theme_icon(SNAME("EditorHandleAdd"));
491
p_overlay->draw_texture(add_handle, edge_point - add_handle->get_size() * 0.5);
492
}
493
494
RenderingServer *rs = RS::get_singleton();
495
rs->mesh_clear(debug_mesh_rid);
496
497
if (!debug_handle_lines.is_empty()) {
498
Array handles_array;
499
handles_array.resize(Mesh::ARRAY_MAX);
500
501
handles_array[Mesh::ARRAY_VERTEX] = Vector<Vector2>(debug_handle_lines);
502
503
rs->mesh_add_surface_from_arrays(debug_mesh_rid, RS::PRIMITIVE_LINES, handles_array, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
504
rs->canvas_item_add_mesh(vpc->get_canvas_item(), debug_mesh_rid, Transform2D(), Color(0.5, 0.5, 0.5, 1.0));
505
}
506
507
// Add texture rects multimeshes for handle vertices.
508
509
uint32_t handle_curve_count = debug_handle_curve_transforms.size();
510
uint32_t handle_sharp_count = debug_handle_sharp_transforms.size();
511
uint32_t handle_smooth_count = debug_handle_smooth_transforms.size();
512
513
// Add texture rects for curve handle vertices.
514
515
rs->multimesh_set_visible_instances(debug_handle_curve_multimesh_rid, 0);
516
if (handle_curve_count > 0) {
517
if (rs->multimesh_get_instance_count(debug_handle_curve_multimesh_rid) != int(handle_curve_count)) {
518
rs->multimesh_allocate_data(debug_handle_curve_multimesh_rid, handle_curve_count, RS::MULTIMESH_TRANSFORM_2D);
519
}
520
521
Vector<float> multimesh_buffer;
522
multimesh_buffer.resize(8 * handle_curve_count);
523
float *multimesh_buffer_ptrw = multimesh_buffer.ptrw();
524
525
const Transform2D *debug_handle_transforms_ptr = debug_handle_curve_transforms.ptr();
526
527
for (uint32_t i = 0; i < handle_curve_count; i++) {
528
const Transform2D &handle_transform = debug_handle_transforms_ptr[i];
529
530
multimesh_buffer_ptrw[i * 8 + 0] = handle_transform[0][0];
531
multimesh_buffer_ptrw[i * 8 + 1] = handle_transform[1][0];
532
multimesh_buffer_ptrw[i * 8 + 2] = 0;
533
multimesh_buffer_ptrw[i * 8 + 3] = handle_transform[2][0];
534
multimesh_buffer_ptrw[i * 8 + 4] = handle_transform[0][1];
535
multimesh_buffer_ptrw[i * 8 + 5] = handle_transform[1][1];
536
multimesh_buffer_ptrw[i * 8 + 6] = 0;
537
multimesh_buffer_ptrw[i * 8 + 7] = handle_transform[2][1];
538
}
539
540
rs->multimesh_set_buffer(debug_handle_curve_multimesh_rid, multimesh_buffer);
541
rs->multimesh_set_visible_instances(debug_handle_curve_multimesh_rid, handle_curve_count);
542
543
rs->canvas_item_add_multimesh(vpc->get_canvas_item(), debug_handle_curve_multimesh_rid, curve_handle->get_rid());
544
}
545
546
// Add texture rects for sharp handle vertices.
547
548
rs->multimesh_set_visible_instances(debug_handle_sharp_multimesh_rid, 0);
549
if (handle_sharp_count > 0) {
550
if (rs->multimesh_get_instance_count(debug_handle_sharp_multimesh_rid) != int(handle_sharp_count)) {
551
rs->multimesh_allocate_data(debug_handle_sharp_multimesh_rid, handle_sharp_count, RS::MULTIMESH_TRANSFORM_2D);
552
}
553
554
Vector<float> multimesh_buffer;
555
multimesh_buffer.resize(8 * handle_sharp_count);
556
float *multimesh_buffer_ptrw = multimesh_buffer.ptrw();
557
558
const Transform2D *debug_handle_transforms_ptr = debug_handle_sharp_transforms.ptr();
559
560
for (uint32_t i = 0; i < handle_sharp_count; i++) {
561
const Transform2D &handle_transform = debug_handle_transforms_ptr[i];
562
563
multimesh_buffer_ptrw[i * 8 + 0] = handle_transform[0][0];
564
multimesh_buffer_ptrw[i * 8 + 1] = handle_transform[1][0];
565
multimesh_buffer_ptrw[i * 8 + 2] = 0;
566
multimesh_buffer_ptrw[i * 8 + 3] = handle_transform[2][0];
567
multimesh_buffer_ptrw[i * 8 + 4] = handle_transform[0][1];
568
multimesh_buffer_ptrw[i * 8 + 5] = handle_transform[1][1];
569
multimesh_buffer_ptrw[i * 8 + 6] = 0;
570
multimesh_buffer_ptrw[i * 8 + 7] = handle_transform[2][1];
571
}
572
573
rs->multimesh_set_buffer(debug_handle_sharp_multimesh_rid, multimesh_buffer);
574
rs->multimesh_set_visible_instances(debug_handle_sharp_multimesh_rid, handle_sharp_count);
575
576
rs->canvas_item_add_multimesh(vpc->get_canvas_item(), debug_handle_sharp_multimesh_rid, curve_handle->get_rid());
577
}
578
579
// Add texture rects for smooth handle vertices.
580
581
rs->multimesh_set_visible_instances(debug_handle_smooth_multimesh_rid, 0);
582
if (handle_smooth_count > 0) {
583
if (rs->multimesh_get_instance_count(debug_handle_smooth_multimesh_rid) != int(handle_smooth_count)) {
584
rs->multimesh_allocate_data(debug_handle_smooth_multimesh_rid, handle_smooth_count, RS::MULTIMESH_TRANSFORM_2D);
585
}
586
587
Vector<float> multimesh_buffer;
588
multimesh_buffer.resize(8 * handle_smooth_count);
589
float *multimesh_buffer_ptrw = multimesh_buffer.ptrw();
590
591
const Transform2D *debug_handle_transforms_ptr = debug_handle_smooth_transforms.ptr();
592
593
for (uint32_t i = 0; i < handle_smooth_count; i++) {
594
const Transform2D &handle_transform = debug_handle_transforms_ptr[i];
595
596
multimesh_buffer_ptrw[i * 8 + 0] = handle_transform[0][0];
597
multimesh_buffer_ptrw[i * 8 + 1] = handle_transform[1][0];
598
multimesh_buffer_ptrw[i * 8 + 2] = 0;
599
multimesh_buffer_ptrw[i * 8 + 3] = handle_transform[2][0];
600
multimesh_buffer_ptrw[i * 8 + 4] = handle_transform[0][1];
601
multimesh_buffer_ptrw[i * 8 + 5] = handle_transform[1][1];
602
multimesh_buffer_ptrw[i * 8 + 6] = 0;
603
multimesh_buffer_ptrw[i * 8 + 7] = handle_transform[2][1];
604
}
605
606
rs->multimesh_set_buffer(debug_handle_smooth_multimesh_rid, multimesh_buffer);
607
rs->multimesh_set_visible_instances(debug_handle_smooth_multimesh_rid, handle_smooth_count);
608
609
rs->canvas_item_add_multimesh(vpc->get_canvas_item(), debug_handle_smooth_multimesh_rid, curve_handle->get_rid());
610
}
611
}
612
613
void Path2DEditor::_node_visibility_changed() {
614
if (!node) {
615
return;
616
}
617
618
canvas_item_editor->update_viewport();
619
_update_toolbar();
620
}
621
622
void Path2DEditor::_update_toolbar() {
623
if (!node) {
624
return;
625
}
626
bool has_curve = node->get_curve().is_valid();
627
toolbar->set_visible(has_curve);
628
create_curve_button->set_visible(!has_curve);
629
}
630
631
void Path2DEditor::edit(Node *p_path2d) {
632
if (!canvas_item_editor) {
633
canvas_item_editor = CanvasItemEditor::get_singleton();
634
}
635
636
if (action != ACTION_NONE) {
637
_cancel_current_action();
638
}
639
640
if (p_path2d) {
641
node = Object::cast_to<Path2D>(p_path2d);
642
_update_toolbar();
643
644
if (!node->is_connected(SceneStringName(visibility_changed), callable_mp(this, &Path2DEditor::_node_visibility_changed))) {
645
node->connect(SceneStringName(visibility_changed), callable_mp(this, &Path2DEditor::_node_visibility_changed));
646
}
647
} else {
648
// The node may have been deleted at this point.
649
if (node && node->is_connected(SceneStringName(visibility_changed), callable_mp(this, &Path2DEditor::_node_visibility_changed))) {
650
node->disconnect(SceneStringName(visibility_changed), callable_mp(this, &Path2DEditor::_node_visibility_changed));
651
}
652
node = nullptr;
653
}
654
655
canvas_item_editor->update_viewport();
656
}
657
658
void Path2DEditor::_bind_methods() {
659
ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path2DEditor::_update_toolbar);
660
ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path2DEditor::_clear_curve_points);
661
ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path2DEditor::_restore_curve_points);
662
}
663
664
void Path2DEditor::_mode_selected(int p_mode) {
665
if (p_mode == MODE_CREATE) {
666
curve_create->set_pressed(true);
667
curve_edit->set_pressed(false);
668
curve_edit_curve->set_pressed(false);
669
curve_del->set_pressed(false);
670
} else if (p_mode == MODE_EDIT) {
671
curve_create->set_pressed(false);
672
curve_edit->set_pressed(true);
673
curve_edit_curve->set_pressed(false);
674
curve_del->set_pressed(false);
675
} else if (p_mode == MODE_EDIT_CURVE) {
676
curve_create->set_pressed(false);
677
curve_edit->set_pressed(false);
678
curve_edit_curve->set_pressed(true);
679
curve_del->set_pressed(false);
680
} else if (p_mode == MODE_DELETE) {
681
curve_create->set_pressed(false);
682
curve_edit->set_pressed(false);
683
curve_edit_curve->set_pressed(false);
684
curve_del->set_pressed(true);
685
} else if (p_mode == MODE_CLOSE) {
686
if (node->get_curve().is_null()) {
687
return;
688
}
689
if (node->get_curve()->get_point_count() < 3) {
690
return;
691
}
692
Vector2 begin = node->get_curve()->get_point_position(0);
693
Vector2 end = node->get_curve()->get_point_position(node->get_curve()->get_point_count() - 1);
694
695
if (begin.is_equal_approx(end)) {
696
return;
697
}
698
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
699
700
undo_redo->create_action(TTR("Close the Curve"));
701
undo_redo->add_do_method(node->get_curve().ptr(), "add_point", begin);
702
undo_redo->add_undo_method(node->get_curve().ptr(), "remove_point", node->get_curve()->get_point_count());
703
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
704
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
705
undo_redo->commit_action();
706
return;
707
} else if (p_mode == MODE_CLEAR_POINTS) {
708
if (node->get_curve().is_null()) {
709
return;
710
}
711
if (node->get_curve()->get_point_count() == 0) {
712
return;
713
}
714
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
715
PackedVector2Array points = node->get_curve()->get_points().duplicate();
716
717
undo_redo->create_action(TTR("Clear Curve Points"), UndoRedo::MERGE_DISABLE, node);
718
undo_redo->add_do_method(this, "_clear_curve_points", node);
719
undo_redo->add_undo_method(this, "_restore_curve_points", node, points);
720
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
721
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
722
undo_redo->commit_action();
723
return;
724
}
725
mode = Mode(p_mode);
726
}
727
728
void Path2DEditor::_handle_option_pressed(int p_option) {
729
PopupMenu *pm;
730
pm = handle_menu->get_popup();
731
732
switch (p_option) {
733
case HANDLE_OPTION_ANGLE: {
734
bool is_checked = pm->is_item_checked(HANDLE_OPTION_ANGLE);
735
mirror_handle_angle = !is_checked;
736
pm->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle);
737
pm->set_item_disabled(HANDLE_OPTION_LENGTH, !mirror_handle_angle);
738
} break;
739
case HANDLE_OPTION_LENGTH: {
740
bool is_checked = pm->is_item_checked(HANDLE_OPTION_LENGTH);
741
mirror_handle_length = !is_checked;
742
pm->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length);
743
} break;
744
}
745
}
746
747
void Path2DEditor::_cancel_current_action() {
748
ERR_FAIL_NULL(node);
749
Ref<Curve2D> curve = node->get_curve();
750
ERR_FAIL_COND(curve.is_null());
751
752
switch (action) {
753
case ACTION_MOVING_POINT: {
754
curve->set_point_position(action_point, moving_from);
755
} break;
756
757
case ACTION_MOVING_NEW_POINT: {
758
curve->remove_point(curve->get_point_count() - 1);
759
} break;
760
761
case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
762
curve->remove_point(action_point);
763
} break;
764
765
case ACTION_MOVING_IN: {
766
curve->set_point_in(action_point, moving_from);
767
curve->set_point_out(action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_out_length));
768
} break;
769
770
case ACTION_MOVING_OUT: {
771
curve->set_point_out(action_point, moving_from);
772
curve->set_point_in(action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_in_length));
773
} break;
774
775
default: {
776
}
777
}
778
779
canvas_item_editor->update_viewport();
780
action = ACTION_NONE;
781
}
782
783
void Path2DEditor::_create_curve() {
784
ERR_FAIL_NULL(node);
785
786
Ref<Curve2D> new_curve;
787
new_curve.instantiate();
788
789
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
790
undo_redo->create_action(TTR("Create Curve in Path2D"));
791
undo_redo->add_do_property(node, "curve", new_curve);
792
undo_redo->add_undo_property(node, "curve", Ref<Curve2D>());
793
undo_redo->add_do_method(this, "_update_toolbar");
794
undo_redo->add_undo_method(this, "_update_toolbar");
795
undo_redo->commit_action();
796
}
797
798
void Path2DEditor::_confirm_clear_points() {
799
if (!node || node->get_curve().is_null()) {
800
return;
801
}
802
if (node->get_curve()->get_point_count() == 0) {
803
return;
804
}
805
clear_points_dialog->reset_size();
806
clear_points_dialog->popup_centered();
807
}
808
809
void Path2DEditor::_clear_curve_points(Path2D *p_path2d) {
810
if (!p_path2d || p_path2d->get_curve().is_null()) {
811
return;
812
}
813
Ref<Curve2D> curve = p_path2d->get_curve();
814
815
if (curve->get_point_count() == 0) {
816
return;
817
}
818
curve->clear_points();
819
820
if (node == p_path2d) {
821
_mode_selected(MODE_CREATE);
822
}
823
}
824
825
void Path2DEditor::_restore_curve_points(Path2D *p_path2d, const PackedVector2Array &p_points) {
826
if (!p_path2d || p_path2d->get_curve().is_null()) {
827
return;
828
}
829
Ref<Curve2D> curve = p_path2d->get_curve();
830
831
if (curve->get_point_count() > 0) {
832
curve->clear_points();
833
}
834
835
for (int i = 0; i < p_points.size(); i += 3) {
836
curve->add_point(p_points[i + 2], p_points[i], p_points[i + 1]); // The Curve2D::points pattern is [point_in, point_out, point_position].
837
}
838
839
if (node == p_path2d) {
840
_mode_selected(MODE_EDIT);
841
}
842
}
843
844
Path2DEditor::Path2DEditor() {
845
toolbar = memnew(HBoxContainer);
846
847
curve_edit = memnew(Button);
848
curve_edit->set_theme_type_variation(SceneStringName(FlatButton));
849
curve_edit->set_toggle_mode(true);
850
curve_edit->set_pressed(true);
851
curve_edit->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
852
curve_edit->set_tooltip_text(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Click: Add Point") + "\n" + TTR("Left Click: Split Segment (in curve)") + "\n" + TTR("Right Click: Delete Point"));
853
curve_edit->set_accessibility_name(TTRC("Select Points"));
854
curve_edit->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT));
855
toolbar->add_child(curve_edit);
856
857
curve_edit_curve = memnew(Button);
858
curve_edit_curve->set_theme_type_variation(SceneStringName(FlatButton));
859
curve_edit_curve->set_toggle_mode(true);
860
curve_edit_curve->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
861
curve_edit_curve->set_tooltip_text(TTR("Select Control Points (Shift+Drag)"));
862
curve_edit_curve->set_accessibility_name(TTRC("Select Control Points"));
863
curve_edit_curve->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT_CURVE));
864
toolbar->add_child(curve_edit_curve);
865
866
curve_create = memnew(Button);
867
curve_create->set_theme_type_variation(SceneStringName(FlatButton));
868
curve_create->set_toggle_mode(true);
869
curve_create->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
870
curve_create->set_tooltip_text(TTR("Add Point (in empty space)") + "\n" + TTR("Right Click: Delete Point"));
871
curve_create->set_accessibility_name(TTRC("Add Point (in empty space)"));
872
curve_create->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CREATE));
873
toolbar->add_child(curve_create);
874
875
curve_del = memnew(Button);
876
curve_del->set_theme_type_variation(SceneStringName(FlatButton));
877
curve_del->set_toggle_mode(true);
878
curve_del->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
879
curve_del->set_tooltip_text(TTR("Delete Point"));
880
curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_DELETE));
881
toolbar->add_child(curve_del);
882
883
curve_close = memnew(Button);
884
curve_close->set_theme_type_variation(SceneStringName(FlatButton));
885
curve_close->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
886
curve_close->set_tooltip_text(TTR("Close Curve"));
887
curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLOSE));
888
toolbar->add_child(curve_close);
889
890
curve_clear_points = memnew(Button);
891
curve_clear_points->set_theme_type_variation(SceneStringName(FlatButton));
892
curve_clear_points->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
893
curve_clear_points->set_tooltip_text(TTR("Clear Points"));
894
curve_clear_points->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_confirm_clear_points));
895
toolbar->add_child(curve_clear_points);
896
897
clear_points_dialog = memnew(ConfirmationDialog);
898
clear_points_dialog->set_title(TTR("Please Confirm..."));
899
clear_points_dialog->set_text(TTR("Remove all curve points?"));
900
clear_points_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLEAR_POINTS));
901
toolbar->add_child(clear_points_dialog);
902
903
handle_menu = memnew(MenuButton);
904
handle_menu->set_flat(false);
905
handle_menu->set_theme_type_variation("FlatMenuButton");
906
handle_menu->set_text(TTR("Options"));
907
toolbar->add_child(handle_menu);
908
909
PopupMenu *menu = handle_menu->get_popup();
910
menu->add_check_item(TTR("Mirror Handle Angles"));
911
menu->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle);
912
menu->add_check_item(TTR("Mirror Handle Lengths"));
913
menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length);
914
menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path2DEditor::_handle_option_pressed));
915
916
add_child(toolbar);
917
918
create_curve_button = memnew(Button);
919
create_curve_button->set_text(TTR("Create Curve"));
920
create_curve_button->hide();
921
add_child(create_curve_button);
922
create_curve_button->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_create_curve));
923
924
ERR_FAIL_NULL(RS::get_singleton());
925
RenderingServer *rs = RS::get_singleton();
926
927
debug_mesh_rid = rs->mesh_create();
928
929
{
930
debug_handle_mesh_rid = rs->mesh_create();
931
932
Vector<Vector2> vertex_array;
933
vertex_array.resize(4);
934
Vector2 *vertex_array_ptrw = vertex_array.ptrw();
935
vertex_array_ptrw[0] = Vector2(-1.0, -1.0);
936
vertex_array_ptrw[1] = Vector2(1.0, -1.0);
937
vertex_array_ptrw[2] = Vector2(1.0, 1.0);
938
vertex_array_ptrw[3] = Vector2(-1.0, 1.0);
939
940
Vector<Vector2> uv_array;
941
uv_array.resize(4);
942
Vector2 *uv_array_ptrw = uv_array.ptrw();
943
uv_array_ptrw[0] = Vector2(0.0, 0.0);
944
uv_array_ptrw[1] = Vector2(1.0, 0.0);
945
uv_array_ptrw[2] = Vector2(1.0, 1.0);
946
uv_array_ptrw[3] = Vector2(0.0, 1.0);
947
948
Vector<int> index_array;
949
index_array.resize(6);
950
int *index_array_ptrw = index_array.ptrw();
951
index_array_ptrw[0] = 0;
952
index_array_ptrw[1] = 1;
953
index_array_ptrw[2] = 3;
954
index_array_ptrw[3] = 1;
955
index_array_ptrw[4] = 2;
956
index_array_ptrw[5] = 3;
957
958
Array mesh_arrays;
959
mesh_arrays.resize(RS::ARRAY_MAX);
960
mesh_arrays[RS::ARRAY_VERTEX] = vertex_array;
961
mesh_arrays[RS::ARRAY_TEX_UV] = uv_array;
962
mesh_arrays[RS::ARRAY_INDEX] = index_array;
963
964
rs->mesh_add_surface_from_arrays(debug_handle_mesh_rid, RS::PRIMITIVE_TRIANGLES, mesh_arrays, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
965
966
debug_handle_curve_multimesh_rid = rs->multimesh_create();
967
debug_handle_sharp_multimesh_rid = rs->multimesh_create();
968
debug_handle_smooth_multimesh_rid = rs->multimesh_create();
969
970
rs->multimesh_set_mesh(debug_handle_curve_multimesh_rid, debug_handle_mesh_rid);
971
rs->multimesh_set_mesh(debug_handle_sharp_multimesh_rid, debug_handle_mesh_rid);
972
rs->multimesh_set_mesh(debug_handle_smooth_multimesh_rid, debug_handle_mesh_rid);
973
}
974
}
975
976
Path2DEditor::~Path2DEditor() {
977
ERR_FAIL_NULL(RS::get_singleton());
978
RS::get_singleton()->free(debug_mesh_rid);
979
RS::get_singleton()->free(debug_handle_curve_multimesh_rid);
980
RS::get_singleton()->free(debug_handle_sharp_multimesh_rid);
981
RS::get_singleton()->free(debug_handle_smooth_multimesh_rid);
982
RS::get_singleton()->free(debug_handle_mesh_rid);
983
}
984
985
void Path2DEditorPlugin::edit(Object *p_object) {
986
path2d_editor->edit(Object::cast_to<Node>(p_object));
987
}
988
989
bool Path2DEditorPlugin::handles(Object *p_object) const {
990
return p_object->is_class("Path2D");
991
}
992
993
void Path2DEditorPlugin::make_visible(bool p_visible) {
994
if (p_visible) {
995
path2d_editor->show();
996
} else {
997
path2d_editor->hide();
998
path2d_editor->edit(nullptr);
999
}
1000
}
1001
1002
Path2DEditorPlugin::Path2DEditorPlugin() {
1003
path2d_editor = memnew(Path2DEditor);
1004
CanvasItemEditor::get_singleton()->add_control_to_menu_panel(path2d_editor);
1005
path2d_editor->hide();
1006
}
1007
1008