Added Path2D and Polygon2D Nodes and their editord to the Paint module.

This commit is contained in:
Relintai 2023-08-25 18:13:54 +02:00
parent 8bf5c70f50
commit 3424e44400
8 changed files with 3881 additions and 0 deletions

View File

@ -0,0 +1,637 @@
/*************************************************************************/
/* path_2d_editor_plugin.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "path_2d_editor_plugin.h"
#include "canvas_item_editor_plugin.h"
#include "core/os/keyboard.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "core/object/class_db.h"
#include "core/math/color.h"
#include "core/math/math_defs.h"
#include "core/math/math_funcs.h"
#include "core/math/rect2.h"
#include "core/math/transform_2d.h"
#include "core/input/input_event.h"
#include "core/os/memory.h"
#include "core/object/undo_redo.h"
#include "core/variant/variant.h"
#include "editor/editor_node.h"
#include "scene/2d/path_2d.h"
#include "scene/gui/control.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/popup_menu.h"
#include "scene/gui/separator.h"
#include "scene/gui/tool_button.h"
#include "scene/main/node.h"
#include "scene/resources/curve.h"
#include "scene/resources/texture.h"
void Path2DEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
//button_create->set_icon( get_theme_icon("Edit","EditorIcons"));
//button_edit->set_icon( get_theme_icon("MovePoint","EditorIcons"));
//set_pressed_button(button_edit);
//button_edit->set_pressed(true);
} break;
case NOTIFICATION_PHYSICS_PROCESS: {
} break;
}
}
void Path2DEditor::_node_removed(Node *p_node) {
if (p_node == node) {
node = nullptr;
hide();
}
}
bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
if (!node) {
return false;
}
if (!node->is_visible_in_tree()) {
return false;
}
if (!node->get_curve().is_valid()) {
return false;
}
real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius");
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform();
Vector2 gpoint = mb->get_position();
Vector2 cpoint = node->get_global_transform().affine_inverse().xform(canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mb->get_position())));
if (mb->is_pressed() && action == ACTION_NONE) {
Ref<Curve2D> curve = node->get_curve();
for (int i = 0; i < curve->get_point_count(); i++) {
real_t dist_to_p = gpoint.distance_to(xform.xform(curve->get_point_position(i)));
real_t dist_to_p_out = gpoint.distance_to(xform.xform(curve->get_point_position(i) + curve->get_point_out(i)));
real_t dist_to_p_in = gpoint.distance_to(xform.xform(curve->get_point_position(i) + curve->get_point_in(i)));
// Check for point movement start (for point + in/out controls).
if (mb->get_button_index() == BUTTON_LEFT) {
if (mode == MODE_EDIT && !mb->get_shift() && dist_to_p < grab_threshold) {
// Points can only be moved in edit mode.
action = ACTION_MOVING_POINT;
action_point = i;
moving_from = curve->get_point_position(i);
moving_screen_from = gpoint;
return true;
} else if (mode == MODE_EDIT || mode == MODE_EDIT_CURVE) {
// In/out controls can be moved in multiple modes.
if (dist_to_p_out < grab_threshold && i < (curve->get_point_count() - 1)) {
action = ACTION_MOVING_OUT;
action_point = i;
moving_from = curve->get_point_out(i);
moving_screen_from = gpoint;
orig_in_length = curve->get_point_in(action_point).length();
return true;
} else if (dist_to_p_in < grab_threshold && i > 0) {
action = ACTION_MOVING_IN;
action_point = i;
moving_from = curve->get_point_in(i);
moving_screen_from = gpoint;
orig_out_length = curve->get_point_out(action_point).length();
return true;
}
}
}
// Check for point deletion.
if ((mb->get_button_index() == BUTTON_RIGHT && mode == MODE_EDIT) || (mb->get_button_index() == BUTTON_LEFT && mode == MODE_DELETE)) {
if (dist_to_p < grab_threshold) {
undo_redo->create_action(TTR("Remove Point from Curve"));
undo_redo->add_do_method(curve.ptr(), "remove_point", i);
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);
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
undo_redo->commit_action();
return true;
} else if (dist_to_p_out < grab_threshold) {
undo_redo->create_action(TTR("Remove Out-Control from Curve"));
undo_redo->add_do_method(curve.ptr(), "set_point_out", i, Vector2());
undo_redo->add_undo_method(curve.ptr(), "set_point_out", i, curve->get_point_out(i));
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
undo_redo->commit_action();
return true;
} else if (dist_to_p_in < grab_threshold) {
undo_redo->create_action(TTR("Remove In-Control from Curve"));
undo_redo->add_do_method(curve.ptr(), "set_point_in", i, Vector2());
undo_redo->add_undo_method(curve.ptr(), "set_point_in", i, curve->get_point_in(i));
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
undo_redo->commit_action();
return true;
}
}
}
}
// Check for point creation.
if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && ((mb->get_command() && mode == MODE_EDIT) || mode == MODE_CREATE)) {
Ref<Curve2D> curve = node->get_curve();
undo_redo->create_action(TTR("Add Point to Curve"));
undo_redo->add_do_method(curve.ptr(), "add_point", cpoint);
undo_redo->add_undo_method(curve.ptr(), "remove_point", curve->get_point_count());
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
undo_redo->commit_action();
action = ACTION_MOVING_POINT;
action_point = curve->get_point_count() - 1;
moving_from = curve->get_point_position(action_point);
moving_screen_from = gpoint;
canvas_item_editor->update_viewport();
return true;
}
// Check for segment split.
if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && mode == MODE_EDIT && on_edge) {
Vector2 gpoint2 = mb->get_position();
Ref<Curve2D> curve = node->get_curve();
int insertion_point = -1;
float mbLength = curve->get_closest_offset(xform.affine_inverse().xform(gpoint2));
int len = curve->get_point_count();
for (int i = 0; i < len - 1; i++) {
float compareLength = curve->get_closest_offset(curve->get_point_position(i + 1));
if (mbLength >= curve->get_closest_offset(curve->get_point_position(i)) && mbLength <= compareLength) {
insertion_point = i;
}
}
if (insertion_point == -1) {
insertion_point = curve->get_point_count() - 2;
}
undo_redo->create_action(TTR("Split Curve"));
undo_redo->add_do_method(curve.ptr(), "add_point", xform.affine_inverse().xform(gpoint2), Vector2(0, 0), Vector2(0, 0), insertion_point + 1);
undo_redo->add_undo_method(curve.ptr(), "remove_point", insertion_point + 1);
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
undo_redo->commit_action();
action = ACTION_MOVING_POINT;
action_point = insertion_point + 1;
moving_from = curve->get_point_position(action_point);
moving_screen_from = gpoint2;
canvas_item_editor->update_viewport();
on_edge = false;
return true;
}
// Check for point movement completion.
if (!mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && action != ACTION_NONE) {
Ref<Curve2D> curve = node->get_curve();
Vector2 new_pos = moving_from + xform.affine_inverse().basis_xform(gpoint - moving_screen_from);
switch (action) {
case ACTION_NONE:
// N/A, handled in above condition.
break;
case ACTION_MOVING_POINT: {
undo_redo->create_action(TTR("Move Point in Curve"));
undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint);
undo_redo->add_undo_method(curve.ptr(), "set_point_position", action_point, moving_from);
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
undo_redo->commit_action();
} break;
case ACTION_MOVING_IN: {
undo_redo->create_action(TTR("Move In-Control in Curve"));
undo_redo->add_do_method(curve.ptr(), "set_point_in", action_point, new_pos);
undo_redo->add_undo_method(curve.ptr(), "set_point_in", action_point, moving_from);
if (mirror_handle_angle) {
undo_redo->add_do_method(curve.ptr(), "set_point_out", action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_out_length));
undo_redo->add_undo_method(curve.ptr(), "set_point_out", action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_out_length));
}
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
undo_redo->commit_action();
} break;
case ACTION_MOVING_OUT: {
undo_redo->create_action(TTR("Move Out-Control in Curve"));
undo_redo->add_do_method(curve.ptr(), "set_point_out", action_point, new_pos);
undo_redo->add_undo_method(curve.ptr(), "set_point_out", action_point, moving_from);
if (mirror_handle_angle) {
undo_redo->add_do_method(curve.ptr(), "set_point_in", action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_in_length));
undo_redo->add_undo_method(curve.ptr(), "set_point_in", action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_in_length));
}
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
undo_redo->commit_action();
} break;
}
action = ACTION_NONE;
return true;
}
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
if (action == ACTION_NONE && mode == MODE_EDIT) {
// Handle Edge Follow
bool old_edge = on_edge;
Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform();
Vector2 gpoint = mm->get_position();
Ref<Curve2D> curve = node->get_curve();
if (curve == nullptr) {
return true;
}
if (curve->get_point_count() < 2) {
return true;
}
// Find edge
edge_point = xform.xform(curve->get_closest_point(xform.affine_inverse().xform(mm->get_position())));
on_edge = false;
if (edge_point.distance_to(gpoint) <= grab_threshold) {
on_edge = true;
}
// However, if near a control point or its in-out handles then not on edge
int len = curve->get_point_count();
for (int i = 0; i < len; i++) {
Vector2 pp = curve->get_point_position(i);
Vector2 p = xform.xform(pp);
if (p.distance_to(gpoint) <= grab_threshold) {
on_edge = false;
break;
}
p = xform.xform(pp + curve->get_point_in(i));
if (p.distance_to(gpoint) <= grab_threshold) {
on_edge = false;
break;
}
p = xform.xform(pp + curve->get_point_out(i));
if (p.distance_to(gpoint) <= grab_threshold) {
on_edge = false;
break;
}
}
if (on_edge || old_edge != on_edge) {
canvas_item_editor->update_viewport();
return true;
}
}
if (action != ACTION_NONE) {
// Handle point/control movement.
Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform();
Vector2 gpoint = mm->get_position();
Vector2 cpoint = node->get_global_transform().affine_inverse().xform(canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mm->get_position())));
Ref<Curve2D> curve = node->get_curve();
Vector2 new_pos = moving_from + xform.affine_inverse().basis_xform(gpoint - moving_screen_from);
switch (action) {
case ACTION_NONE:
// N/A, handled in above condition.
break;
case ACTION_MOVING_POINT: {
curve->set_point_position(action_point, cpoint);
} break;
case ACTION_MOVING_IN: {
curve->set_point_in(action_point, new_pos);
if (mirror_handle_angle) {
curve->set_point_out(action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_out_length));
}
} break;
case ACTION_MOVING_OUT: {
curve->set_point_out(action_point, new_pos);
if (mirror_handle_angle) {
curve->set_point_in(action_point, mirror_handle_length ? -new_pos : (-new_pos.normalized() * orig_in_length));
}
} break;
}
canvas_item_editor->update_viewport();
return true;
}
}
return false;
}
void Path2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
if (!node || !node->is_visible_in_tree() || !node->get_curve().is_valid()) {
return;
}
Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform();
const Ref<Texture> path_sharp_handle = get_theme_icon("EditorPathSharpHandle", "EditorIcons");
const Ref<Texture> path_smooth_handle = get_theme_icon("EditorPathSmoothHandle", "EditorIcons");
// Both handle icons must be of the same size
const Size2 handle_size = path_sharp_handle->get_size();
const Ref<Texture> curve_handle = get_theme_icon("EditorCurveHandle", "EditorIcons");
const Size2 curve_handle_size = curve_handle->get_size();
Ref<Curve2D> curve = node->get_curve();
int len = curve->get_point_count();
Control *vpc = canvas_item_editor->get_viewport_control();
for (int i = 0; i < len; i++) {
Vector2 point = xform.xform(curve->get_point_position(i));
// Determines the point icon to be used
bool smooth = false;
if (i < len - 1) {
Vector2 pointout = xform.xform(curve->get_point_position(i) + curve->get_point_out(i));
if (point != pointout) {
smooth = true;
// Draw the line with a dark and light color to be visible on all backgrounds
vpc->draw_line(point, pointout, Color(0, 0, 0, 0.5), Math::round(EDSCALE), true);
vpc->draw_line(point, pointout, Color(1, 1, 1, 0.5), Math::round(EDSCALE), true);
vpc->draw_texture_rect(curve_handle, Rect2(pointout - curve_handle_size * 0.5, curve_handle_size), false, Color(1, 1, 1, 0.75));
}
}
if (i > 0) {
Vector2 pointin = xform.xform(curve->get_point_position(i) + curve->get_point_in(i));
if (point != pointin) {
smooth = true;
// Draw the line with a dark and light color to be visible on all backgrounds
vpc->draw_line(point, pointin, Color(0, 0, 0, 0.5), Math::round(EDSCALE), true);
vpc->draw_line(point, pointin, Color(1, 1, 1, 0.5), Math::round(EDSCALE), true);
vpc->draw_texture_rect(curve_handle, Rect2(pointin - curve_handle_size * 0.5, curve_handle_size), false, Color(1, 1, 1, 0.75));
}
}
vpc->draw_texture_rect(
smooth ? path_smooth_handle : path_sharp_handle,
Rect2(point - handle_size * 0.5, handle_size),
false);
}
if (on_edge) {
Ref<Texture> add_handle = get_theme_icon("EditorHandleAdd", "EditorIcons");
p_overlay->draw_texture(add_handle, edge_point - add_handle->get_size() * 0.5);
}
}
void Path2DEditor::_node_visibility_changed() {
if (!node) {
return;
}
canvas_item_editor->update_viewport();
}
void Path2DEditor::edit(Node *p_path2d) {
if (!canvas_item_editor) {
canvas_item_editor = CanvasItemEditor::get_singleton();
}
if (p_path2d) {
node = Object::cast_to<Path2D>(p_path2d);
if (!node->is_connected("visibility_changed", this, "_node_visibility_changed")) {
node->connect("visibility_changed", this, "_node_visibility_changed");
}
} else {
// node may have been deleted at this point
if (node && node->is_connected("visibility_changed", this, "_node_visibility_changed")) {
node->disconnect("visibility_changed", this, "_node_visibility_changed");
}
node = nullptr;
}
}
void Path2DEditor::_bind_methods() {
//ClassDB::bind_method(D_METHOD("_menu_option"),&Path2DEditor::_menu_option);
ClassDB::bind_method(D_METHOD("_node_visibility_changed"), &Path2DEditor::_node_visibility_changed);
ClassDB::bind_method(D_METHOD("_mode_selected"), &Path2DEditor::_mode_selected);
ClassDB::bind_method(D_METHOD("_handle_option_pressed"), &Path2DEditor::_handle_option_pressed);
}
void Path2DEditor::_mode_selected(int p_mode) {
if (p_mode == MODE_CREATE) {
curve_create->set_pressed(true);
curve_edit->set_pressed(false);
curve_edit_curve->set_pressed(false);
curve_del->set_pressed(false);
} else if (p_mode == MODE_EDIT) {
curve_create->set_pressed(false);
curve_edit->set_pressed(true);
curve_edit_curve->set_pressed(false);
curve_del->set_pressed(false);
} else if (p_mode == MODE_EDIT_CURVE) {
curve_create->set_pressed(false);
curve_edit->set_pressed(false);
curve_edit_curve->set_pressed(true);
curve_del->set_pressed(false);
} else if (p_mode == MODE_DELETE) {
curve_create->set_pressed(false);
curve_edit->set_pressed(false);
curve_edit_curve->set_pressed(false);
curve_del->set_pressed(true);
} else if (p_mode == ACTION_CLOSE) {
//?
if (!node->get_curve().is_valid()) {
return;
}
if (node->get_curve()->get_point_count() < 3) {
return;
}
Vector2 begin = node->get_curve()->get_point_position(0);
Vector2 end = node->get_curve()->get_point_position(node->get_curve()->get_point_count() - 1);
if (begin.distance_to(end) < CMP_EPSILON) {
return;
}
undo_redo->create_action(TTR("Remove Point from Curve"));
undo_redo->add_do_method(node->get_curve().ptr(), "add_point", begin);
undo_redo->add_undo_method(node->get_curve().ptr(), "remove_point", node->get_curve()->get_point_count());
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
undo_redo->commit_action();
return;
}
mode = Mode(p_mode);
}
void Path2DEditor::_handle_option_pressed(int p_option) {
PopupMenu *pm;
pm = handle_menu->get_popup();
switch (p_option) {
case HANDLE_OPTION_ANGLE: {
bool is_checked = pm->is_item_checked(HANDLE_OPTION_ANGLE);
mirror_handle_angle = !is_checked;
pm->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle);
pm->set_item_disabled(HANDLE_OPTION_LENGTH, !mirror_handle_angle);
} break;
case HANDLE_OPTION_LENGTH: {
bool is_checked = pm->is_item_checked(HANDLE_OPTION_LENGTH);
mirror_handle_length = !is_checked;
pm->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length);
} break;
}
}
Path2DEditor::Path2DEditor(EditorNode *p_editor) {
canvas_item_editor = nullptr;
editor = p_editor;
undo_redo = editor->get_undo_redo();
mirror_handle_angle = true;
mirror_handle_length = true;
on_edge = false;
mode = MODE_EDIT;
action = ACTION_NONE;
base_hb = memnew(HBoxContainer);
CanvasItemEditor::get_singleton()->add_control_to_menu_panel(base_hb);
sep = memnew(VSeparator);
base_hb->add_child(sep);
curve_edit = memnew(ToolButton);
curve_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveEdit", "EditorIcons"));
curve_edit->set_toggle_mode(true);
curve_edit->set_focus_mode(Control::FOCUS_NONE);
curve_edit->set_tooltip(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string(KEY_MASK_CMD) + TTR("Click: Add Point") + "\n" + TTR("Left Click: Split Segment (in curve)") + "\n" + TTR("Right Click: Delete Point"));
curve_edit->connect("pressed", this, "_mode_selected", varray(MODE_EDIT));
base_hb->add_child(curve_edit);
curve_edit_curve = memnew(ToolButton);
curve_edit_curve->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveCurve", "EditorIcons"));
curve_edit_curve->set_toggle_mode(true);
curve_edit_curve->set_focus_mode(Control::FOCUS_NONE);
curve_edit_curve->set_tooltip(TTR("Select Control Points (Shift+Drag)"));
curve_edit_curve->connect("pressed", this, "_mode_selected", varray(MODE_EDIT_CURVE));
base_hb->add_child(curve_edit_curve);
curve_create = memnew(ToolButton);
curve_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveCreate", "EditorIcons"));
curve_create->set_toggle_mode(true);
curve_create->set_focus_mode(Control::FOCUS_NONE);
curve_create->set_tooltip(TTR("Add Point (in empty space)"));
curve_create->connect("pressed", this, "_mode_selected", varray(MODE_CREATE));
base_hb->add_child(curve_create);
curve_del = memnew(ToolButton);
curve_del->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveDelete", "EditorIcons"));
curve_del->set_toggle_mode(true);
curve_del->set_focus_mode(Control::FOCUS_NONE);
curve_del->set_tooltip(TTR("Delete Point"));
curve_del->connect("pressed", this, "_mode_selected", varray(MODE_DELETE));
base_hb->add_child(curve_del);
curve_close = memnew(ToolButton);
curve_close->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveClose", "EditorIcons"));
curve_close->set_focus_mode(Control::FOCUS_NONE);
curve_close->set_tooltip(TTR("Close Curve"));
curve_close->connect("pressed", this, "_mode_selected", varray(ACTION_CLOSE));
base_hb->add_child(curve_close);
PopupMenu *menu;
handle_menu = memnew(MenuButton);
handle_menu->set_text(TTR("Options"));
base_hb->add_child(handle_menu);
menu = handle_menu->get_popup();
menu->add_check_item(TTR("Mirror Handle Angles"));
menu->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle);
menu->add_check_item(TTR("Mirror Handle Lengths"));
menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length);
menu->connect("id_pressed", this, "_handle_option_pressed");
base_hb->hide();
curve_edit->set_pressed(true);
}
void Path2DEditorPlugin::edit(Object *p_object) {
path2d_editor->edit(Object::cast_to<Node>(p_object));
}
bool Path2DEditorPlugin::handles(Object *p_object) const {
return p_object->is_class("Path2D");
}
void Path2DEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
path2d_editor->show();
path2d_editor->base_hb->show();
} else {
path2d_editor->hide();
path2d_editor->base_hb->hide();
path2d_editor->edit(nullptr);
}
}
Path2DEditorPlugin::Path2DEditorPlugin(EditorNode *p_node) {
editor = p_node;
path2d_editor = memnew(Path2DEditor(p_node));
CanvasItemEditor::get_singleton()->add_control_to_menu_panel(path2d_editor);
path2d_editor->hide();
}
Path2DEditorPlugin::~Path2DEditorPlugin() {
}

View File

@ -0,0 +1,146 @@
#ifndef PATH_2D_EDITOR_PLUGIN_H
#define PATH_2D_EDITOR_PLUGIN_H
/*************************************************************************/
/* path_2d_editor_plugin.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "scene/gui/box_container.h"
#include "editor/editor_plugin.h"
#include "core/math/vector2.h"
#include "core/object/object.h"
#include "core/object/reference.h"
#include "core/string/ustring.h"
class CanvasItemEditor;
class Control;
class EditorNode;
class InputEvent;
class MenuButton;
class Node;
class Panel;
class Path2D;
class Separator;
class ToolButton;
class UndoRedo;
class Path2DEditor : public HBoxContainer {
GDCLASS(Path2DEditor, HBoxContainer);
UndoRedo *undo_redo;
CanvasItemEditor *canvas_item_editor;
EditorNode *editor;
Panel *panel;
Path2D *node;
HBoxContainer *base_hb;
Separator *sep;
enum Mode {
MODE_CREATE,
MODE_EDIT,
MODE_EDIT_CURVE,
MODE_DELETE,
ACTION_CLOSE
};
Mode mode;
ToolButton *curve_create;
ToolButton *curve_edit;
ToolButton *curve_edit_curve;
ToolButton *curve_del;
ToolButton *curve_close;
MenuButton *handle_menu;
bool mirror_handle_angle;
bool mirror_handle_length;
bool on_edge;
enum HandleOption {
HANDLE_OPTION_ANGLE,
HANDLE_OPTION_LENGTH
};
enum Action {
ACTION_NONE,
ACTION_MOVING_POINT,
ACTION_MOVING_IN,
ACTION_MOVING_OUT,
};
Action action;
int action_point;
Point2 moving_from;
Point2 moving_screen_from;
float orig_in_length;
float orig_out_length;
Vector2 edge_point;
void _mode_selected(int p_mode);
void _handle_option_pressed(int p_option);
void _node_visibility_changed();
friend class Path2DEditorPlugin;
protected:
void _notification(int p_what);
void _node_removed(Node *p_node);
static void _bind_methods();
public:
bool forward_gui_input(const Ref<InputEvent> &p_event);
void forward_canvas_draw_over_viewport(Control *p_overlay);
void edit(Node *p_path2d);
Path2DEditor(EditorNode *p_editor);
};
class Path2DEditorPlugin : public EditorPlugin {
GDCLASS(Path2DEditorPlugin, EditorPlugin);
Path2DEditor *path2d_editor;
EditorNode *editor;
public:
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return path2d_editor->forward_gui_input(p_event); }
virtual void forward_canvas_draw_over_viewport(Control *p_overlay) { path2d_editor->forward_canvas_draw_over_viewport(p_overlay); }
virtual String get_name() const { return "Path2D"; }
bool has_main_screen() const { return false; }
virtual void edit(Object *p_object);
virtual bool handles(Object *p_object) const;
virtual void make_visible(bool p_visible);
Path2DEditorPlugin(EditorNode *p_node);
~Path2DEditorPlugin();
};
#endif // PATH_2D_EDITOR_PLUGIN_H

View File

@ -0,0 +1,412 @@
/*************************************************************************/
/* path_2d.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "path_2d.h"
#include "core/config/engine.h"
#include "scene/resources/curve.h"
#include "scene/scene_string_names.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_scale.h"
#endif
#ifdef TOOLS_ENABLED
Rect2 Path2D::_edit_get_rect() const {
if (!curve.is_valid() || curve->get_point_count() == 0) {
return Rect2(0, 0, 0, 0);
}
Rect2 aabb = Rect2(curve->get_point_position(0), Vector2(0, 0));
for (int i = 0; i < curve->get_point_count(); i++) {
for (int j = 0; j <= 8; j++) {
real_t frac = j / 8.0;
Vector2 p = curve->interpolate(i, frac);
aabb.expand_to(p);
}
}
return aabb;
}
bool Path2D::_edit_use_rect() const {
return curve.is_valid() && curve->get_point_count() != 0;
}
bool Path2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
if (curve.is_null()) {
return false;
}
for (int i = 0; i < curve->get_point_count(); i++) {
Vector2 s[2];
s[0] = curve->get_point_position(i);
for (int j = 1; j <= 8; j++) {
real_t frac = j / 8.0;
s[1] = curve->interpolate(i, frac);
Vector2 p = Geometry::get_closest_point_to_segment_2d(p_point, s);
if (p.distance_to(p_point) <= p_tolerance) {
return true;
}
s[0] = s[1];
}
}
return false;
}
#endif
void Path2D::_notification(int p_what) {
if (p_what == NOTIFICATION_DRAW && curve.is_valid()) {
//draw the curve!!
if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_navigation_hint()) {
return;
}
if (curve->get_point_count() < 2) {
return;
}
#ifdef TOOLS_ENABLED
const float line_width = 2 * EDSCALE;
#else
const float line_width = 2;
#endif
const Color color = Color(1.0, 1.0, 1.0, 1.0);
_cached_draw_pts.resize(curve->get_point_count() * 8);
int count = 0;
for (int i = 0; i < curve->get_point_count(); i++) {
for (int j = 0; j < 8; j++) {
real_t frac = j * (1.0 / 8.0);
Vector2 p = curve->interpolate(i, frac);
_cached_draw_pts.set(count++, p);
}
}
draw_polyline(_cached_draw_pts, color, line_width, true);
}
}
void Path2D::_curve_changed() {
if (!is_inside_tree()) {
return;
}
if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_navigation_hint()) {
return;
}
update();
}
void Path2D::set_curve(const Ref<Curve2D> &p_curve) {
if (curve.is_valid()) {
curve->disconnect("changed", this, "_curve_changed");
}
curve = p_curve;
if (curve.is_valid()) {
curve->connect("changed", this, "_curve_changed");
}
_curve_changed();
}
Ref<Curve2D> Path2D::get_curve() const {
return curve;
}
void Path2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_curve", "curve"), &Path2D::set_curve);
ClassDB::bind_method(D_METHOD("get_curve"), &Path2D::get_curve);
ClassDB::bind_method(D_METHOD("_curve_changed"), &Path2D::_curve_changed);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve2D"), "set_curve", "get_curve");
}
Path2D::Path2D() {
set_curve(Ref<Curve2D>(memnew(Curve2D))); //create one by default
set_self_modulate(Color(0.5, 0.6, 1.0, 0.7));
}
/////////////////////////////////////////////////////////////////////////////////
void PathFollow2D::_update_transform() {
if (!path) {
return;
}
Ref<Curve2D> c = path->get_curve();
if (!c.is_valid()) {
return;
}
float path_length = c->get_baked_length();
if (path_length == 0) {
return;
}
Vector2 pos = c->interpolate_baked(offset, cubic);
if (rotate) {
float ahead = offset + lookahead;
if (loop && ahead >= path_length) {
// If our lookahead will loop, we need to check if the path is closed.
int point_count = c->get_point_count();
if (point_count > 0) {
Vector2 start_point = c->get_point_position(0);
Vector2 end_point = c->get_point_position(point_count - 1);
if (start_point == end_point) {
// Since the path is closed we want to 'smooth off'
// the corner at the start/end.
// So we wrap the lookahead back round.
ahead = Math::fmod(ahead, path_length);
}
}
}
Vector2 ahead_pos = c->interpolate_baked(ahead, cubic);
Vector2 tangent_to_curve;
if (ahead_pos == pos) {
// This will happen at the end of non-looping or non-closed paths.
// We'll try a look behind instead, in order to get a meaningful angle.
tangent_to_curve =
(pos - c->interpolate_baked(offset - lookahead, cubic)).normalized();
} else {
tangent_to_curve = (ahead_pos - pos).normalized();
}
Vector2 normal_of_curve = -tangent_to_curve.tangent();
pos += tangent_to_curve * h_offset;
pos += normal_of_curve * v_offset;
set_rotation(tangent_to_curve.angle());
} else {
pos.x += h_offset;
pos.y += v_offset;
}
set_position(pos);
}
void PathFollow2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
path = Object::cast_to<Path2D>(get_parent());
if (path) {
_update_transform();
}
} break;
case NOTIFICATION_EXIT_TREE: {
path = nullptr;
} break;
}
}
void PathFollow2D::set_cubic_interpolation(bool p_enable) {
cubic = p_enable;
}
bool PathFollow2D::get_cubic_interpolation() const {
return cubic;
}
void PathFollow2D::_validate_property(PropertyInfo &property) const {
if (property.name == "offset") {
float max = 10000;
if (path && path->get_curve().is_valid()) {
max = path->get_curve()->get_baked_length();
}
property.hint_string = "0," + rtos(max) + ",0.01,or_lesser,or_greater";
}
}
String PathFollow2D::get_configuration_warning() const {
if (!is_visible_in_tree() || !is_inside_tree()) {
return String();
}
String warning = Node2D::get_configuration_warning();
if (!Object::cast_to<Path2D>(get_parent())) {
if (warning != String()) {
warning += "\n\n";
}
warning += TTR("PathFollow2D only works when set as a child of a Path2D node.");
}
return warning;
}
void PathFollow2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_offset", "offset"), &PathFollow2D::set_offset);
ClassDB::bind_method(D_METHOD("get_offset"), &PathFollow2D::get_offset);
ClassDB::bind_method(D_METHOD("set_h_offset", "h_offset"), &PathFollow2D::set_h_offset);
ClassDB::bind_method(D_METHOD("get_h_offset"), &PathFollow2D::get_h_offset);
ClassDB::bind_method(D_METHOD("set_v_offset", "v_offset"), &PathFollow2D::set_v_offset);
ClassDB::bind_method(D_METHOD("get_v_offset"), &PathFollow2D::get_v_offset);
ClassDB::bind_method(D_METHOD("set_unit_offset", "unit_offset"), &PathFollow2D::set_unit_offset);
ClassDB::bind_method(D_METHOD("get_unit_offset"), &PathFollow2D::get_unit_offset);
ClassDB::bind_method(D_METHOD("set_rotate", "enable"), &PathFollow2D::set_rotate);
ClassDB::bind_method(D_METHOD("is_rotating"), &PathFollow2D::is_rotating);
ClassDB::bind_method(D_METHOD("set_cubic_interpolation", "enable"), &PathFollow2D::set_cubic_interpolation);
ClassDB::bind_method(D_METHOD("get_cubic_interpolation"), &PathFollow2D::get_cubic_interpolation);
ClassDB::bind_method(D_METHOD("set_loop", "loop"), &PathFollow2D::set_loop);
ClassDB::bind_method(D_METHOD("has_loop"), &PathFollow2D::has_loop);
ClassDB::bind_method(D_METHOD("set_lookahead", "lookahead"), &PathFollow2D::set_lookahead);
ClassDB::bind_method(D_METHOD("get_lookahead"), &PathFollow2D::get_lookahead);
ADD_PROPERTY(PropertyInfo(Variant::REAL, "offset", PROPERTY_HINT_RANGE, "0,10000,0.01,or_lesser,or_greater"), "set_offset", "get_offset");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "unit_offset", PROPERTY_HINT_RANGE, "0,1,0.0001,or_lesser,or_greater", PROPERTY_USAGE_EDITOR), "set_unit_offset", "get_unit_offset");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "h_offset"), "set_h_offset", "get_h_offset");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "v_offset"), "set_v_offset", "get_v_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rotate"), "set_rotate", "is_rotating");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cubic_interp"), "set_cubic_interpolation", "get_cubic_interpolation");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "lookahead", PROPERTY_HINT_RANGE, "0.001,1024.0,0.001"), "set_lookahead", "get_lookahead");
}
void PathFollow2D::set_offset(float p_offset) {
ERR_FAIL_COND(!isfinite(p_offset));
offset = p_offset;
if (path) {
if (path->get_curve().is_valid()) {
float path_length = path->get_curve()->get_baked_length();
if (loop) {
offset = Math::fposmod(offset, path_length);
if (!Math::is_zero_approx(p_offset) && Math::is_zero_approx(offset)) {
offset = path_length;
}
} else {
offset = CLAMP(offset, 0, path_length);
}
}
_update_transform();
}
_change_notify("offset");
_change_notify("unit_offset");
}
void PathFollow2D::set_h_offset(float p_h_offset) {
h_offset = p_h_offset;
if (path) {
_update_transform();
}
}
float PathFollow2D::get_h_offset() const {
return h_offset;
}
void PathFollow2D::set_v_offset(float p_v_offset) {
v_offset = p_v_offset;
if (path) {
_update_transform();
}
}
float PathFollow2D::get_v_offset() const {
return v_offset;
}
float PathFollow2D::get_offset() const {
return offset;
}
void PathFollow2D::set_unit_offset(float p_unit_offset) {
if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) {
set_offset(p_unit_offset * path->get_curve()->get_baked_length());
}
}
float PathFollow2D::get_unit_offset() const {
if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) {
return get_offset() / path->get_curve()->get_baked_length();
} else {
return 0;
}
}
void PathFollow2D::set_lookahead(float p_lookahead) {
lookahead = p_lookahead;
}
float PathFollow2D::get_lookahead() const {
return lookahead;
}
void PathFollow2D::set_rotate(bool p_rotate) {
rotate = p_rotate;
_update_transform();
}
bool PathFollow2D::is_rotating() const {
return rotate;
}
void PathFollow2D::set_loop(bool p_loop) {
loop = p_loop;
}
bool PathFollow2D::has_loop() const {
return loop;
}
PathFollow2D::PathFollow2D() {
offset = 0;
h_offset = 0;
v_offset = 0;
path = nullptr;
rotate = true;
cubic = true;
loop = true;
lookahead = 4;
}

View File

@ -0,0 +1,114 @@
#ifndef PATH_2D_H
#define PATH_2D_H
/*************************************************************************/
/* path_2d.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "scene/2d/node_2d.h"
class Curve2D;
class Path2D : public Node2D {
GDCLASS(Path2D, Node2D);
Ref<Curve2D> curve;
Vector<Vector2> _cached_draw_pts;
void _curve_changed();
protected:
void _notification(int p_what);
static void _bind_methods();
public:
#ifdef TOOLS_ENABLED
virtual Rect2 _edit_get_rect() const;
virtual bool _edit_use_rect() const;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
#endif
void set_curve(const Ref<Curve2D> &p_curve);
Ref<Curve2D> get_curve() const;
Path2D();
};
class PathFollow2D : public Node2D {
GDCLASS(PathFollow2D, Node2D);
public:
private:
Path2D *path;
real_t offset;
real_t h_offset;
real_t v_offset;
real_t lookahead;
bool cubic;
bool loop;
bool rotate;
void _update_transform();
protected:
virtual void _validate_property(PropertyInfo &property) const;
void _notification(int p_what);
static void _bind_methods();
public:
void set_offset(float p_offset);
float get_offset() const;
void set_h_offset(float p_h_offset);
float get_h_offset() const;
void set_v_offset(float p_v_offset);
float get_v_offset() const;
void set_unit_offset(float p_unit_offset);
float get_unit_offset() const;
void set_lookahead(float p_lookahead);
float get_lookahead() const;
void set_loop(bool p_loop);
bool has_loop() const;
void set_rotate(bool p_rotate);
bool is_rotating() const;
void set_cubic_interpolation(bool p_enable);
bool get_cubic_interpolation() const;
String get_configuration_warning() const;
PathFollow2D();
};
#endif // PATH_2D_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,198 @@
#ifndef POLYGON_2D_EDITOR_PLUGIN_H
#define POLYGON_2D_EDITOR_PLUGIN_H
/*************************************************************************/
/* polygon_2d_editor_plugin.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "editor/plugins/abstract_polygon_2d_editor.h"
#include "core/variant/array.h"
#include "core/math/color.h"
#include "core/math/vector2.h"
#include "core/object/object.h"
#include "core/containers/pool_vector.h"
#include "core/object/reference.h"
#include "core/containers/vector.h"
class AcceptDialog;
class Button;
class EditorNode;
class HScrollBar;
class HSlider;
class InputEvent;
class Label;
class MenuButton;
class Node2D;
class Node;
class Panel;
class Polygon2D;
class ScrollContainer;
class SpinBox;
class TextureRect;
class ToolButton;
class VBoxContainer;
class VScrollBar;
class Polygon2DEditor : public AbstractPolygon2DEditor {
GDCLASS(Polygon2DEditor, AbstractPolygon2DEditor);
enum Mode {
MODE_EDIT_UV = MODE_CONT,
UVEDIT_POLYGON_TO_UV,
UVEDIT_UV_TO_POLYGON,
UVEDIT_UV_CLEAR,
UVEDIT_GRID_SETTINGS
};
enum UVMode {
UV_MODE_CREATE,
UV_MODE_CREATE_INTERNAL,
UV_MODE_REMOVE_INTERNAL,
UV_MODE_EDIT_POINT,
UV_MODE_MOVE,
UV_MODE_ROTATE,
UV_MODE_SCALE,
UV_MODE_ADD_POLYGON,
UV_MODE_REMOVE_POLYGON,
UV_MODE_PAINT_WEIGHT,
UV_MODE_CLEAR_WEIGHT,
UV_MODE_MAX
};
ToolButton *uv_edit_mode[4];
Ref<ButtonGroup> uv_edit_group;
Polygon2D *node;
UVMode uv_mode;
AcceptDialog *uv_edit;
ToolButton *uv_button[UV_MODE_MAX];
ToolButton *b_snap_enable;
ToolButton *b_snap_grid;
Panel *uv_edit_draw;
HSlider *uv_zoom;
SpinBox *uv_zoom_value;
HScrollBar *uv_hscroll;
VScrollBar *uv_vscroll;
MenuButton *uv_menu;
TextureRect *uv_icon_zoom;
VBoxContainer *bone_scroll_main_vb;
ScrollContainer *bone_scroll;
VBoxContainer *bone_scroll_vb;
Button *sync_bones;
HSlider *bone_paint_strength;
SpinBox *bone_paint_radius;
Label *bone_paint_radius_label;
bool bone_painting;
int bone_painting_bone;
PoolVector<float> prev_weights;
Vector2 bone_paint_pos;
AcceptDialog *grid_settings;
void _sync_bones();
void _update_bone_list();
Vector2 uv_draw_ofs;
float uv_draw_zoom;
PoolVector<Vector2> points_prev;
PoolVector<Vector2> uv_create_uv_prev;
PoolVector<Vector2> uv_create_poly_prev;
PoolVector<Color> uv_create_colors_prev;
int uv_create_prev_internal_vertices;
Array uv_create_bones_prev;
Array polygons_prev;
Vector2 uv_create_to;
int point_drag_index;
bool uv_drag;
bool uv_create;
Vector<int> polygon_create;
UVMode uv_move_current;
Vector2 uv_drag_from;
bool updating_uv_scroll;
AcceptDialog *error;
ToolButton *button_uv;
bool use_snap;
bool snap_show_grid;
Vector2 snap_offset;
Vector2 snap_step;
virtual void _menu_option(int p_option);
void _cancel_editing();
void _update_polygon_editing_state();
void _uv_scroll_changed(float);
void _uv_input(const Ref<InputEvent> &p_input);
void _uv_draw();
void _uv_mode(int p_mode);
void _set_use_snap(bool p_use);
void _set_show_grid(bool p_show);
void _set_snap_off_x(float p_val);
void _set_snap_off_y(float p_val);
void _set_snap_step_x(float p_val);
void _set_snap_step_y(float p_val);
void _uv_edit_mode_select(int p_mode);
void _uv_edit_popup_hide();
void _bone_paint_selected(int p_index);
int _get_polygon_count() const;
protected:
virtual Node2D *_get_node() const;
virtual void _set_node(Node *p_polygon);
virtual Vector2 _get_offset(int p_idx) const;
virtual bool _has_uv() const { return true; };
virtual void _commit_action();
void _notification(int p_what);
static void _bind_methods();
Vector2 snap_point(Vector2 p_target) const;
public:
Polygon2DEditor(EditorNode *p_editor);
};
class Polygon2DEditorPlugin : public AbstractPolygon2DEditorPlugin {
GDCLASS(Polygon2DEditorPlugin, AbstractPolygon2DEditorPlugin);
public:
Polygon2DEditorPlugin(EditorNode *p_node);
};
#endif // POLYGON_2D_EDITOR_PLUGIN_H

View File

@ -0,0 +1,690 @@
/*************************************************************************/
/* polygon_2d.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "polygon_2d.h"
#include "core/math/geometry.h"
#include "modules/modules_enabled.gen.h"
#ifdef MODULE_SKELETON_2D_ENABLED
#include "modules/skeleton_2d/nodes/skeleton_2d.h"
#endif
#ifdef TOOLS_ENABLED
Dictionary Polygon2D::_edit_get_state() const {
Dictionary state = Node2D::_edit_get_state();
state["offset"] = offset;
return state;
}
void Polygon2D::_edit_set_state(const Dictionary &p_state) {
Node2D::_edit_set_state(p_state);
set_offset(p_state["offset"]);
}
void Polygon2D::_edit_set_pivot(const Point2 &p_pivot) {
set_position(get_transform().xform(p_pivot));
set_offset(get_offset() - p_pivot);
}
Point2 Polygon2D::_edit_get_pivot() const {
return Vector2();
}
bool Polygon2D::_edit_use_pivot() const {
return true;
}
Rect2 Polygon2D::_edit_get_rect() const {
if (rect_cache_dirty) {
int l = polygon.size();
PoolVector<Vector2>::Read r = polygon.read();
item_rect = Rect2();
for (int i = 0; i < l; i++) {
Vector2 pos = r[i] + offset;
if (i == 0) {
item_rect.position = pos;
} else {
item_rect.expand_to(pos);
}
}
rect_cache_dirty = false;
}
return item_rect;
}
bool Polygon2D::_edit_use_rect() const {
return polygon.size() > 0;
}
bool Polygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
Vector<Vector2> polygon2d = Variant(polygon);
if (internal_vertices > 0) {
polygon2d.resize(polygon2d.size() - internal_vertices);
}
return Geometry::is_point_in_polygon(p_point - get_offset(), polygon2d);
}
#endif
void Polygon2D::_skeleton_bone_setup_changed() {
update();
}
void Polygon2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
// Must re-establish any existing links with skeletons on re-entering the tree.
update();
} break;
case NOTIFICATION_EXIT_TREE: {
// Always detach skeleton when exiting the tree, so skeletons don't inform
// Polygon2Ds outside the tree that they have moved (this would be useless work).
RS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), RID());
} break;
case NOTIFICATION_DRAW: {
if (polygon.size() < 3) {
return;
}
#ifdef MODULE_SKELETON_2D_ENABLED
Skeleton2D *skeleton_node = nullptr;
if (has_node(skeleton)) {
skeleton_node = Object::cast_to<Skeleton2D>(get_node(skeleton));
}
ObjectID new_skeleton_id = 0;
if (skeleton_node) {
RS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), skeleton_node->get_skeleton());
new_skeleton_id = skeleton_node->get_instance_id();
// Sync the offset transform between the Polygon2D and the skeleton.
// This is needed for accurate culling in VisualServer.
Transform2D global_xform_skel = skeleton_node->get_global_transform();
Transform2D global_xform_poly = get_global_transform();
// find the difference
Transform2D global_xform_offset = global_xform_skel.affine_inverse() * global_xform_poly;
RS::get_singleton()->canvas_item_set_skeleton_relative_xform(get_canvas_item(), global_xform_offset);
} else {
RS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), RID());
}
if (new_skeleton_id != current_skeleton_id) {
Object *old_skeleton = ObjectDB::get_instance(current_skeleton_id);
if (old_skeleton) {
old_skeleton->disconnect("bone_setup_changed", this, "_skeleton_bone_setup_changed");
}
if (skeleton_node) {
skeleton_node->connect("bone_setup_changed", this, "_skeleton_bone_setup_changed");
}
current_skeleton_id = new_skeleton_id;
}
#endif
Vector<Vector2> points;
Vector<Vector2> uvs;
Vector<int> bones;
Vector<float> weights;
int len = polygon.size();
if ((invert || polygons.size() == 0) && internal_vertices > 0) {
//if no polygons are around, internal vertices must not be drawn, else let them be
len -= internal_vertices;
}
if (len <= 0) {
return;
}
points.resize(len);
{
PoolVector<Vector2>::Read polyr = polygon.read();
for (int i = 0; i < len; i++) {
points.write[i] = polyr[i] + offset;
}
}
if (invert) {
Rect2 bounds;
int highest_idx = -1;
float highest_y = -1e20;
float sum = 0;
for (int i = 0; i < len; i++) {
if (i == 0) {
bounds.position = points[i];
} else {
bounds.expand_to(points[i]);
}
if (points[i].y > highest_y) {
highest_idx = i;
highest_y = points[i].y;
}
int ni = (i + 1) % len;
sum += (points[ni].x - points[i].x) * (points[ni].y + points[i].y);
}
bounds = bounds.grow(invert_border);
Vector2 ep[7] = {
Vector2(points[highest_idx].x, points[highest_idx].y + invert_border),
Vector2(bounds.position + bounds.size),
Vector2(bounds.position + Vector2(bounds.size.x, 0)),
Vector2(bounds.position),
Vector2(bounds.position + Vector2(0, bounds.size.y)),
Vector2(points[highest_idx].x - CMP_EPSILON, points[highest_idx].y + invert_border),
Vector2(points[highest_idx].x - CMP_EPSILON, points[highest_idx].y),
};
if (sum > 0) {
SWAP(ep[1], ep[4]);
SWAP(ep[2], ep[3]);
SWAP(ep[5], ep[0]);
SWAP(ep[6], points.write[highest_idx]);
}
points.resize(points.size() + 7);
for (int i = points.size() - 1; i >= highest_idx + 7; i--) {
points.write[i] = points[i - 7];
}
for (int i = 0; i < 7; i++) {
points.write[highest_idx + i + 1] = ep[i];
}
len = points.size();
}
if (texture.is_valid()) {
Transform2D texmat(tex_rot, tex_ofs);
texmat.scale(tex_scale);
Size2 tex_size = texture->get_size();
uvs.resize(len);
if (points.size() == uv.size()) {
PoolVector<Vector2>::Read uvr = uv.read();
for (int i = 0; i < len; i++) {
uvs.write[i] = texmat.xform(uvr[i]) / tex_size;
}
} else {
for (int i = 0; i < len; i++) {
uvs.write[i] = texmat.xform(points[i]) / tex_size;
}
}
}
#ifdef MODULE_SKELETON_2D_ENABLED
if (skeleton_node && !invert && bone_weights.size()) {
//a skeleton is set! fill indices and weights
int vc = len;
bones.resize(vc * 4);
weights.resize(vc * 4);
int *bonesw = bones.ptrw();
float *weightsw = weights.ptrw();
for (int i = 0; i < vc * 4; i++) {
bonesw[i] = 0;
weightsw[i] = 0;
}
for (int i = 0; i < bone_weights.size(); i++) {
if (bone_weights[i].weights.size() != points.size()) {
continue; //different number of vertices, sorry not using.
}
if (!skeleton_node->has_node(bone_weights[i].path)) {
continue; //node does not exist
}
Bone2D *bone = Object::cast_to<Bone2D>(skeleton_node->get_node(bone_weights[i].path));
if (!bone) {
continue;
}
int bone_index = bone->get_index_in_skeleton();
PoolVector<float>::Read r = bone_weights[i].weights.read();
for (int j = 0; j < vc; j++) {
if (r[j] == 0.0) {
continue; //weight is unpainted, skip
}
//find an index with a weight
for (int k = 0; k < 4; k++) {
if (weightsw[j * 4 + k] < r[j]) {
//this is less than this weight, insert weight!
for (int l = 3; l > k; l--) {
weightsw[j * 4 + l] = weightsw[j * 4 + l - 1];
bonesw[j * 4 + l] = bonesw[j * 4 + l - 1];
}
weightsw[j * 4 + k] = r[j];
bonesw[j * 4 + k] = bone_index;
break;
}
}
}
}
//normalize the weights
for (int i = 0; i < vc; i++) {
float tw = 0;
for (int j = 0; j < 4; j++) {
tw += weightsw[i * 4 + j];
}
if (tw == 0) {
continue; //unpainted, do nothing
}
//normalize
for (int j = 0; j < 4; j++) {
weightsw[i * 4 + j] /= tw;
}
}
}
#endif
Vector<Color> colors;
if (vertex_colors.size() == points.size()) {
colors.resize(len);
PoolVector<Color>::Read color_r = vertex_colors.read();
for (int i = 0; i < len; i++) {
colors.write[i] = color_r[i];
}
} else {
colors.push_back(color);
}
// Vector<int> indices = Geometry::triangulate_polygon(points);
// RS::get_singleton()->canvas_item_add_triangle_array(get_canvas_item(), indices, points, colors, uvs, texture.is_valid() ? texture->get_rid() : RID());
if (invert || polygons.size() == 0) {
Vector<int> indices = Geometry::triangulate_polygon(points);
if (indices.size()) {
RS::get_singleton()->canvas_item_add_triangle_array(get_canvas_item(), indices, points, colors, uvs, bones, weights, texture.is_valid() ? texture->get_rid() : RID(), -1, RID(), antialiased);
}
} else {
//draw individual polygons
Vector<int> total_indices;
for (int i = 0; i < polygons.size(); i++) {
PoolVector<int> src_indices = polygons[i];
int ic = src_indices.size();
if (ic < 3) {
continue;
}
PoolVector<int>::Read r = src_indices.read();
Vector<Vector2> tmp_points;
tmp_points.resize(ic);
for (int j = 0; j < ic; j++) {
int idx = r[j];
ERR_CONTINUE(idx < 0 || idx >= points.size());
tmp_points.write[j] = points[r[j]];
}
Vector<int> indices = Geometry::triangulate_polygon(tmp_points);
int ic2 = indices.size();
const int *r2 = indices.ptr();
int bic = total_indices.size();
total_indices.resize(bic + ic2);
int *w2 = total_indices.ptrw();
for (int j = 0; j < ic2; j++) {
w2[j + bic] = r[r2[j]];
}
}
if (total_indices.size()) {
RS::get_singleton()->canvas_item_add_triangle_array(get_canvas_item(), total_indices, points, colors, uvs, bones, weights, texture.is_valid() ? texture->get_rid() : RID(), -1, RID(), antialiased);
}
}
} break;
}
}
void Polygon2D::set_polygon(const PoolVector<Vector2> &p_polygon) {
polygon = p_polygon;
rect_cache_dirty = true;
update();
}
PoolVector<Vector2> Polygon2D::get_polygon() const {
return polygon;
}
void Polygon2D::set_internal_vertex_count(int p_count) {
internal_vertices = p_count;
}
int Polygon2D::get_internal_vertex_count() const {
return internal_vertices;
}
void Polygon2D::set_uv(const PoolVector<Vector2> &p_uv) {
uv = p_uv;
update();
}
PoolVector<Vector2> Polygon2D::get_uv() const {
return uv;
}
void Polygon2D::set_polygons(const Array &p_polygons) {
polygons = p_polygons;
update();
}
Array Polygon2D::get_polygons() const {
return polygons;
}
void Polygon2D::set_color(const Color &p_color) {
color = p_color;
update();
}
Color Polygon2D::get_color() const {
return color;
}
void Polygon2D::set_vertex_colors(const PoolVector<Color> &p_colors) {
vertex_colors = p_colors;
update();
}
PoolVector<Color> Polygon2D::get_vertex_colors() const {
return vertex_colors;
}
void Polygon2D::set_texture(const Ref<Texture> &p_texture) {
texture = p_texture;
/*if (texture.is_valid()) {
uint32_t flags=texture->get_flags();
flags&=~Texture::FLAG_REPEAT;
if (tex_tile)
flags|=Texture::FLAG_REPEAT;
texture->set_flags(flags);
}*/
update();
}
Ref<Texture> Polygon2D::get_texture() const {
return texture;
}
void Polygon2D::set_texture_offset(const Vector2 &p_offset) {
tex_ofs = p_offset;
update();
}
Vector2 Polygon2D::get_texture_offset() const {
return tex_ofs;
}
void Polygon2D::set_texture_rotation(float p_rot) {
tex_rot = p_rot;
update();
}
float Polygon2D::get_texture_rotation() const {
return tex_rot;
}
void Polygon2D::set_texture_rotation_degrees(float p_rot) {
set_texture_rotation(Math::deg2rad(p_rot));
}
float Polygon2D::get_texture_rotation_degrees() const {
return Math::rad2deg(get_texture_rotation());
}
void Polygon2D::set_texture_scale(const Size2 &p_scale) {
tex_scale = p_scale;
update();
}
Size2 Polygon2D::get_texture_scale() const {
return tex_scale;
}
void Polygon2D::set_invert(bool p_invert) {
invert = p_invert;
update();
}
bool Polygon2D::get_invert() const {
return invert;
}
void Polygon2D::set_antialiased(bool p_antialiased) {
antialiased = p_antialiased;
update();
}
bool Polygon2D::get_antialiased() const {
return antialiased;
}
void Polygon2D::set_invert_border(float p_invert_border) {
invert_border = p_invert_border;
update();
}
float Polygon2D::get_invert_border() const {
return invert_border;
}
void Polygon2D::set_offset(const Vector2 &p_offset) {
offset = p_offset;
rect_cache_dirty = true;
update();
_change_notify("offset");
}
Vector2 Polygon2D::get_offset() const {
return offset;
}
void Polygon2D::add_bone(const NodePath &p_path, const PoolVector<float> &p_weights) {
Bone bone;
bone.path = p_path;
bone.weights = p_weights;
bone_weights.push_back(bone);
}
int Polygon2D::get_bone_count() const {
return bone_weights.size();
}
NodePath Polygon2D::get_bone_path(int p_index) const {
ERR_FAIL_INDEX_V(p_index, bone_weights.size(), NodePath());
return bone_weights[p_index].path;
}
PoolVector<float> Polygon2D::get_bone_weights(int p_index) const {
ERR_FAIL_INDEX_V(p_index, bone_weights.size(), PoolVector<float>());
return bone_weights[p_index].weights;
}
void Polygon2D::erase_bone(int p_idx) {
ERR_FAIL_INDEX(p_idx, bone_weights.size());
bone_weights.remove(p_idx);
}
void Polygon2D::clear_bones() {
bone_weights.clear();
}
void Polygon2D::set_bone_weights(int p_index, const PoolVector<float> &p_weights) {
ERR_FAIL_INDEX(p_index, bone_weights.size());
bone_weights.write[p_index].weights = p_weights;
update();
}
void Polygon2D::set_bone_path(int p_index, const NodePath &p_path) {
ERR_FAIL_INDEX(p_index, bone_weights.size());
bone_weights.write[p_index].path = p_path;
update();
}
Array Polygon2D::_get_bones() const {
Array bones;
for (int i = 0; i < get_bone_count(); i++) {
// Convert path property to String to avoid errors due to invalid node path in editor,
// because it's relative to the Skeleton2D node and not Polygon2D.
bones.push_back(String(get_bone_path(i)));
bones.push_back(get_bone_weights(i));
}
return bones;
}
void Polygon2D::_set_bones(const Array &p_bones) {
ERR_FAIL_COND(p_bones.size() & 1);
clear_bones();
for (int i = 0; i < p_bones.size(); i += 2) {
// Convert back from String to NodePath.
add_bone(NodePath(p_bones[i].operator String()), p_bones[i + 1]);
}
}
void Polygon2D::set_skeleton(const NodePath &p_skeleton) {
if (skeleton == p_skeleton) {
return;
}
skeleton = p_skeleton;
update();
}
NodePath Polygon2D::get_skeleton() const {
return skeleton;
}
void Polygon2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &Polygon2D::set_polygon);
ClassDB::bind_method(D_METHOD("get_polygon"), &Polygon2D::get_polygon);
ClassDB::bind_method(D_METHOD("set_uv", "uv"), &Polygon2D::set_uv);
ClassDB::bind_method(D_METHOD("get_uv"), &Polygon2D::get_uv);
ClassDB::bind_method(D_METHOD("set_color", "color"), &Polygon2D::set_color);
ClassDB::bind_method(D_METHOD("get_color"), &Polygon2D::get_color);
ClassDB::bind_method(D_METHOD("set_polygons", "polygons"), &Polygon2D::set_polygons);
ClassDB::bind_method(D_METHOD("get_polygons"), &Polygon2D::get_polygons);
ClassDB::bind_method(D_METHOD("set_vertex_colors", "vertex_colors"), &Polygon2D::set_vertex_colors);
ClassDB::bind_method(D_METHOD("get_vertex_colors"), &Polygon2D::get_vertex_colors);
ClassDB::bind_method(D_METHOD("set_texture", "texture"), &Polygon2D::set_texture);
ClassDB::bind_method(D_METHOD("get_texture"), &Polygon2D::get_texture);
ClassDB::bind_method(D_METHOD("set_texture_offset", "texture_offset"), &Polygon2D::set_texture_offset);
ClassDB::bind_method(D_METHOD("get_texture_offset"), &Polygon2D::get_texture_offset);
ClassDB::bind_method(D_METHOD("set_texture_rotation", "texture_rotation"), &Polygon2D::set_texture_rotation);
ClassDB::bind_method(D_METHOD("get_texture_rotation"), &Polygon2D::get_texture_rotation);
ClassDB::bind_method(D_METHOD("set_texture_rotation_degrees", "texture_rotation"), &Polygon2D::set_texture_rotation_degrees);
ClassDB::bind_method(D_METHOD("get_texture_rotation_degrees"), &Polygon2D::get_texture_rotation_degrees);
ClassDB::bind_method(D_METHOD("set_texture_scale", "texture_scale"), &Polygon2D::set_texture_scale);
ClassDB::bind_method(D_METHOD("get_texture_scale"), &Polygon2D::get_texture_scale);
ClassDB::bind_method(D_METHOD("set_invert", "invert"), &Polygon2D::set_invert);
ClassDB::bind_method(D_METHOD("get_invert"), &Polygon2D::get_invert);
ClassDB::bind_method(D_METHOD("set_antialiased", "antialiased"), &Polygon2D::set_antialiased);
ClassDB::bind_method(D_METHOD("get_antialiased"), &Polygon2D::get_antialiased);
ClassDB::bind_method(D_METHOD("set_invert_border", "invert_border"), &Polygon2D::set_invert_border);
ClassDB::bind_method(D_METHOD("get_invert_border"), &Polygon2D::get_invert_border);
ClassDB::bind_method(D_METHOD("set_offset", "offset"), &Polygon2D::set_offset);
ClassDB::bind_method(D_METHOD("get_offset"), &Polygon2D::get_offset);
ClassDB::bind_method(D_METHOD("add_bone", "path", "weights"), &Polygon2D::add_bone);
ClassDB::bind_method(D_METHOD("get_bone_count"), &Polygon2D::get_bone_count);
ClassDB::bind_method(D_METHOD("get_bone_path", "index"), &Polygon2D::get_bone_path);
ClassDB::bind_method(D_METHOD("get_bone_weights", "index"), &Polygon2D::get_bone_weights);
ClassDB::bind_method(D_METHOD("erase_bone", "index"), &Polygon2D::erase_bone);
ClassDB::bind_method(D_METHOD("clear_bones"), &Polygon2D::clear_bones);
ClassDB::bind_method(D_METHOD("set_bone_path", "index", "path"), &Polygon2D::set_bone_path);
ClassDB::bind_method(D_METHOD("set_bone_weights", "index", "weights"), &Polygon2D::set_bone_weights);
ClassDB::bind_method(D_METHOD("set_skeleton", "skeleton"), &Polygon2D::set_skeleton);
ClassDB::bind_method(D_METHOD("get_skeleton"), &Polygon2D::get_skeleton);
ClassDB::bind_method(D_METHOD("set_internal_vertex_count", "internal_vertex_count"), &Polygon2D::set_internal_vertex_count);
ClassDB::bind_method(D_METHOD("get_internal_vertex_count"), &Polygon2D::get_internal_vertex_count);
ClassDB::bind_method(D_METHOD("_set_bones", "bones"), &Polygon2D::_set_bones);
ClassDB::bind_method(D_METHOD("_get_bones"), &Polygon2D::_get_bones);
ClassDB::bind_method(D_METHOD("_skeleton_bone_setup_changed"), &Polygon2D::_skeleton_bone_setup_changed);
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "antialiased"), "set_antialiased", "get_antialiased");
ADD_GROUP("Texture", "");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture");
ADD_GROUP("Texture", "texture_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_offset"), "set_texture_offset", "get_texture_offset");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_scale", PROPERTY_HINT_LINK), "set_texture_scale", "get_texture_scale");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "texture_rotation_degrees", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater"), "set_texture_rotation_degrees", "get_texture_rotation_degrees");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "texture_rotation", PROPERTY_HINT_NONE, "", 0), "set_texture_rotation", "get_texture_rotation");
ADD_GROUP("Skeleton", "");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton2D"), "set_skeleton", "get_skeleton");
ADD_GROUP("Invert", "invert_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "invert_enable"), "set_invert", "get_invert");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "invert_border", PROPERTY_HINT_RANGE, "0.1,16384,0.1"), "set_invert_border", "get_invert_border");
ADD_GROUP("Data", "");
ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon");
ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "uv"), "set_uv", "get_uv");
ADD_PROPERTY(PropertyInfo(Variant::POOL_COLOR_ARRAY, "vertex_colors"), "set_vertex_colors", "get_vertex_colors");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons"), "set_polygons", "get_polygons");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_bones", "_get_bones");
ADD_PROPERTY(PropertyInfo(Variant::INT, "internal_vertex_count", PROPERTY_HINT_RANGE, "0,1000"), "set_internal_vertex_count", "get_internal_vertex_count");
}
Polygon2D::Polygon2D() {
invert = false;
invert_border = 100;
antialiased = false;
tex_rot = 0;
tex_tile = true;
tex_scale = Vector2(1, 1);
color = Color(1, 1, 1);
rect_cache_dirty = true;
internal_vertices = 0;
current_skeleton_id = 0;
}
Polygon2D::~Polygon2D() {
// Most definitely don't want to leave references to this deleted canvas item
// in the skeleton.
if (get_canvas_item().is_valid()) {
RS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), RID());
}
}

View File

@ -0,0 +1,153 @@
#ifndef POLYGON_2D_H
#define POLYGON_2D_H
/*************************************************************************/
/* polygon_2d.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "scene/2d/node_2d.h"
class Polygon2D : public Node2D {
GDCLASS(Polygon2D, Node2D);
PoolVector<Vector2> polygon;
PoolVector<Vector2> uv;
PoolVector<Color> vertex_colors;
Array polygons;
int internal_vertices;
struct Bone {
NodePath path;
PoolVector<float> weights;
};
Vector<Bone> bone_weights;
Color color;
Ref<Texture> texture;
Size2 tex_scale;
Vector2 tex_ofs;
bool tex_tile;
float tex_rot;
bool invert;
float invert_border;
bool antialiased;
Vector2 offset;
mutable bool rect_cache_dirty;
mutable Rect2 item_rect;
NodePath skeleton;
ObjectID current_skeleton_id;
Array _get_bones() const;
void _set_bones(const Array &p_bones);
void _skeleton_bone_setup_changed();
protected:
void _notification(int p_what);
static void _bind_methods();
public:
#ifdef TOOLS_ENABLED
virtual Dictionary _edit_get_state() const;
virtual void _edit_set_state(const Dictionary &p_state);
virtual void _edit_set_pivot(const Point2 &p_pivot);
virtual Point2 _edit_get_pivot() const;
virtual bool _edit_use_pivot() const;
virtual Rect2 _edit_get_rect() const;
virtual bool _edit_use_rect() const;
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
#endif
void set_polygon(const PoolVector<Vector2> &p_polygon);
PoolVector<Vector2> get_polygon() const;
void set_internal_vertex_count(int p_count);
int get_internal_vertex_count() const;
void set_uv(const PoolVector<Vector2> &p_uv);
PoolVector<Vector2> get_uv() const;
void set_polygons(const Array &p_polygons);
Array get_polygons() const;
void set_color(const Color &p_color);
Color get_color() const;
void set_vertex_colors(const PoolVector<Color> &p_colors);
PoolVector<Color> get_vertex_colors() const;
void set_texture(const Ref<Texture> &p_texture);
Ref<Texture> get_texture() const;
void set_texture_offset(const Vector2 &p_offset);
Vector2 get_texture_offset() const;
void set_texture_rotation(float p_rot);
float get_texture_rotation() const;
void set_texture_rotation_degrees(float p_rot);
float get_texture_rotation_degrees() const;
void set_texture_scale(const Size2 &p_scale);
Size2 get_texture_scale() const;
void set_invert(bool p_invert);
bool get_invert() const;
void set_antialiased(bool p_antialiased);
bool get_antialiased() const;
void set_invert_border(float p_invert_border);
float get_invert_border() const;
void set_offset(const Vector2 &p_offset);
Vector2 get_offset() const;
void add_bone(const NodePath &p_path = NodePath(), const PoolVector<float> &p_weights = PoolVector<float>());
int get_bone_count() const;
NodePath get_bone_path(int p_index) const;
PoolVector<float> get_bone_weights(int p_index) const;
void erase_bone(int p_idx);
void clear_bones();
void set_bone_weights(int p_index, const PoolVector<float> &p_weights);
void set_bone_path(int p_index, const NodePath &p_path);
void set_skeleton(const NodePath &p_skeleton);
NodePath get_skeleton() const;
Polygon2D();
virtual ~Polygon2D();
};
#endif // POLYGON_2D_H