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