feat: updated engine version to 4.4-rc1

This commit is contained in:
Sara 2025-02-23 14:38:14 +01:00
parent ee00efde1f
commit 21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")

View file

@ -40,7 +40,6 @@
#include "editor/themes/editor_scale.h"
#include "scene/gui/button.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/separator.h"
bool AbstractPolygon2DEditor::Vertex::operator==(const AbstractPolygon2DEditor::Vertex &p_vertex) const {
return polygon == p_vertex.polygon && vertex == p_vertex.vertex;
@ -158,9 +157,9 @@ void AbstractPolygon2DEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
button_create->set_icon(get_editor_theme_icon(SNAME("CurveCreate")));
button_edit->set_icon(get_editor_theme_icon(SNAME("CurveEdit")));
button_delete->set_icon(get_editor_theme_icon(SNAME("CurveDelete")));
button_create->set_button_icon(get_editor_theme_icon(SNAME("CurveCreate")));
button_edit->set_button_icon(get_editor_theme_icon(SNAME("CurveEdit")));
button_delete->set_button_icon(get_editor_theme_icon(SNAME("CurveDelete")));
} break;
case NOTIFICATION_READY: {
@ -260,6 +259,11 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event)
return false;
}
Viewport *vp = _get_node()->get_viewport();
if (vp && !vp->is_visible_subviewport()) {
return false;
}
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
Ref<InputEventMouseButton> mb = p_event;
@ -277,10 +281,11 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event)
}
if (mb.is_valid()) {
Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_global_transform();
Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_screen_transform();
Vector2 gpoint = mb->get_position();
Vector2 cpoint = _get_node()->to_local(canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mb->get_position())));
Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint));
cpoint = _get_node()->get_screen_transform().affine_inverse().xform(cpoint);
if (mode == MODE_EDIT || (_is_line() && mode == MODE_CREATE)) {
if (mb->get_button_index() == MouseButton::LEFT) {
@ -412,7 +417,8 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event)
Vector2 gpoint = mm->get_position();
if (edited_point.valid() && (wip_active || mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
Vector2 cpoint = _get_node()->to_local(canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint)));
Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint));
cpoint = _get_node()->get_screen_transform().affine_inverse().xform(cpoint);
//Move the point in a single axis. Should only work when editing a polygon and while holding shift.
if (mode == MODE_EDIT && mm->is_shift_pressed()) {
@ -499,7 +505,12 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl
return;
}
Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_global_transform();
Viewport *vp = _get_node()->get_viewport();
if (vp && !vp->is_visible_subviewport()) {
return;
}
Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_screen_transform();
// All polygon points are sharp, so use the sharp handle icon
const Ref<Texture2D> handle = get_editor_theme_icon(SNAME("EditorPathSharpHandle"));
@ -576,7 +587,7 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl
if (vertex == hover_point) {
Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
String num = String::num(vertex.vertex);
String num = String::num_int64(vertex.vertex);
Size2 num_size = font->get_string_size(num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
const float outline_size = 4;
Color font_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
@ -620,9 +631,6 @@ void AbstractPolygon2DEditor::edit(Node *p_polygon) {
canvas_item_editor->update_viewport();
}
void AbstractPolygon2DEditor::_bind_methods() {
}
void AbstractPolygon2DEditor::remove_point(const Vertex &p_vertex) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
Vector<Vector2> vertices = _get_polygon(p_vertex.polygon);
@ -657,7 +665,7 @@ AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_point(const
const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
const int n_polygons = _get_polygon_count();
const Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_global_transform();
const Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_screen_transform();
PosVertex closest;
real_t closest_dist = 1e10;
@ -687,7 +695,7 @@ AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_edge_point(c
const real_t eps2 = eps * eps;
const int n_polygons = _get_polygon_count();
const Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_global_transform();
const Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_screen_transform();
PosVertex closest;
real_t closest_dist = 1e10;
@ -728,19 +736,19 @@ AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) {
edge_point = PosVertex();
button_create = memnew(Button);
button_create->set_theme_type_variation("FlatButton");
button_create->set_theme_type_variation(SceneStringName(FlatButton));
add_child(button_create);
button_create->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_CREATE));
button_create->set_toggle_mode(true);
button_edit = memnew(Button);
button_edit->set_theme_type_variation("FlatButton");
button_edit->set_theme_type_variation(SceneStringName(FlatButton));
add_child(button_edit);
button_edit->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_EDIT));
button_edit->set_toggle_mode(true);
button_delete = memnew(Button);
button_delete->set_theme_type_variation("FlatButton");
button_delete->set_theme_type_variation(SceneStringName(FlatButton));
add_child(button_delete);
button_delete->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_DELETE));
button_delete->set_toggle_mode(true);
@ -751,7 +759,9 @@ AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) {
}
void AbstractPolygon2DEditorPlugin::edit(Object *p_object) {
polygon_editor->edit(Object::cast_to<Node>(p_object));
Node *polygon_node = Object::cast_to<Node>(p_object);
polygon_editor->edit(polygon_node);
make_visible(polygon_node != nullptr);
}
bool AbstractPolygon2DEditorPlugin::handles(Object *p_object) const {

View file

@ -32,7 +32,7 @@
#define ABSTRACT_POLYGON_2D_EDITOR_H
#include "editor/plugins/editor_plugin.h"
#include "scene/2d/polygon_2d.h"
#include "scene/2d/node_2d.h"
#include "scene/gui/box_container.h"
class Button;
@ -109,7 +109,6 @@ protected:
void _notification(int p_what);
void _node_removed(Node *p_node);
static void _bind_methods();
void remove_point(const Vertex &p_vertex);
Vertex get_active_point() const;
@ -158,7 +157,7 @@ public:
virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { polygon_editor->forward_canvas_draw_over_viewport(p_overlay); }
bool has_main_screen() const override { return false; }
virtual String get_name() const override { return klass; }
virtual String get_plugin_name() const override { return klass; }
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;
virtual void make_visible(bool p_visible) override;

View file

@ -428,7 +428,7 @@ void AnimationNodeBlendSpace1DEditor::_add_menu_type(int p_index) {
node = Ref<AnimationNode>(an);
}
if (!node.is_valid()) {
if (node.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("This type of node can't be used. Only root nodes are allowed."));
return;
}
@ -576,12 +576,12 @@ void AnimationNodeBlendSpace1DEditor::_notification(int p_what) {
error_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
error_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
tool_blend->set_icon(get_editor_theme_icon(SNAME("EditPivot")));
tool_select->set_icon(get_editor_theme_icon(SNAME("ToolSelect")));
tool_create->set_icon(get_editor_theme_icon(SNAME("EditKey")));
tool_erase->set_icon(get_editor_theme_icon(SNAME("Remove")));
snap->set_icon(get_editor_theme_icon(SNAME("SnapGrid")));
open_editor->set_icon(get_editor_theme_icon(SNAME("Edit")));
tool_blend->set_button_icon(get_editor_theme_icon(SNAME("EditPivot")));
tool_select->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));
tool_create->set_button_icon(get_editor_theme_icon(SNAME("EditKey")));
tool_erase->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
snap->set_button_icon(get_editor_theme_icon(SNAME("SnapGrid")));
open_editor->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
interpolation->clear();
interpolation->add_icon_item(get_editor_theme_icon(SNAME("TrackContinuous")), TTR("Continuous"), 0);
interpolation->add_icon_item(get_editor_theme_icon(SNAME("TrackDiscrete")), TTR("Discrete"), 1);
@ -634,7 +634,7 @@ void AnimationNodeBlendSpace1DEditor::edit(const Ref<AnimationNode> &p_node) {
blend_space = p_node;
read_only = false;
if (!blend_space.is_null()) {
if (blend_space.is_valid()) {
read_only = EditorNode::get_singleton()->is_resource_read_only(blend_space);
_update_space();
@ -661,7 +661,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
bg.instantiate();
tool_blend = memnew(Button);
tool_blend->set_theme_type_variation("FlatButton");
tool_blend->set_theme_type_variation(SceneStringName(FlatButton));
tool_blend->set_toggle_mode(true);
tool_blend->set_button_group(bg);
top_hb->add_child(tool_blend);
@ -670,7 +670,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
tool_blend->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch).bind(3));
tool_select = memnew(Button);
tool_select->set_theme_type_variation("FlatButton");
tool_select->set_theme_type_variation(SceneStringName(FlatButton));
tool_select->set_toggle_mode(true);
tool_select->set_button_group(bg);
top_hb->add_child(tool_select);
@ -678,7 +678,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
tool_select->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch).bind(0));
tool_create = memnew(Button);
tool_create->set_theme_type_variation("FlatButton");
tool_create->set_theme_type_variation(SceneStringName(FlatButton));
tool_create->set_toggle_mode(true);
tool_create->set_button_group(bg);
top_hb->add_child(tool_create);
@ -688,7 +688,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
tool_erase_sep = memnew(VSeparator);
top_hb->add_child(tool_erase_sep);
tool_erase = memnew(Button);
tool_erase->set_theme_type_variation("FlatButton");
tool_erase->set_theme_type_variation(SceneStringName(FlatButton));
top_hb->add_child(tool_erase);
tool_erase->set_tooltip_text(TTR("Erase points."));
tool_erase->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_erase_selected));
@ -696,7 +696,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
top_hb->add_child(memnew(VSeparator));
snap = memnew(Button);
snap->set_theme_type_variation("FlatButton");
snap->set_theme_type_variation(SceneStringName(FlatButton));
snap->set_toggle_mode(true);
top_hb->add_child(snap);
snap->set_pressed(true);
@ -713,7 +713,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
top_hb->add_child(memnew(Label(TTR("Sync:"))));
sync = memnew(CheckBox);
top_hb->add_child(sync);
sync->connect("toggled", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
sync->connect(SceneStringName(toggled), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
top_hb->add_child(memnew(VSeparator));

View file

@ -35,7 +35,6 @@
#include "editor/plugins/editor_plugin.h"
#include "scene/animation/animation_blend_space_1d.h"
#include "scene/gui/graph_edit.h"
#include "scene/gui/popup.h"
class Button;
class CheckBox;

View file

@ -30,8 +30,6 @@
#include "animation_blend_space_2d_editor.h"
#include "core/config/project_settings.h"
#include "core/input/input.h"
#include "core/io/resource_loader.h"
#include "core/math/geometry_2d.h"
#include "core/os/keyboard.h"
@ -71,7 +69,7 @@ void AnimationNodeBlendSpace2DEditor::edit(const Ref<AnimationNode> &p_node) {
blend_space = p_node;
read_only = false;
if (!blend_space.is_null()) {
if (blend_space.is_valid()) {
read_only = EditorNode::get_singleton()->is_resource_read_only(blend_space);
blend_space->connect("triangles_updated", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_blend_space_changed));
@ -355,7 +353,7 @@ void AnimationNodeBlendSpace2DEditor::_add_menu_type(int p_index) {
node = Ref<AnimationNode>(an);
}
if (!node.is_valid()) {
if (node.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("This type of node can't be used. Only root nodes are allowed."));
return;
}
@ -798,14 +796,14 @@ void AnimationNodeBlendSpace2DEditor::_notification(int p_what) {
error_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
error_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
tool_blend->set_icon(get_editor_theme_icon(SNAME("EditPivot")));
tool_select->set_icon(get_editor_theme_icon(SNAME("ToolSelect")));
tool_create->set_icon(get_editor_theme_icon(SNAME("EditKey")));
tool_triangle->set_icon(get_editor_theme_icon(SNAME("ToolTriangle")));
tool_erase->set_icon(get_editor_theme_icon(SNAME("Remove")));
snap->set_icon(get_editor_theme_icon(SNAME("SnapGrid")));
open_editor->set_icon(get_editor_theme_icon(SNAME("Edit")));
auto_triangles->set_icon(get_editor_theme_icon(SNAME("AutoTriangle")));
tool_blend->set_button_icon(get_editor_theme_icon(SNAME("EditPivot")));
tool_select->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));
tool_create->set_button_icon(get_editor_theme_icon(SNAME("EditKey")));
tool_triangle->set_button_icon(get_editor_theme_icon(SNAME("ToolTriangle")));
tool_erase->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
snap->set_button_icon(get_editor_theme_icon(SNAME("SnapGrid")));
open_editor->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
auto_triangles->set_button_icon(get_editor_theme_icon(SNAME("AutoTriangle")));
interpolation->clear();
interpolation->add_icon_item(get_editor_theme_icon(SNAME("TrackContinuous")), TTR("Continuous"), 0);
interpolation->add_icon_item(get_editor_theme_icon(SNAME("TrackDiscrete")), TTR("Discrete"), 1);
@ -882,7 +880,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
bg.instantiate();
tool_blend = memnew(Button);
tool_blend->set_theme_type_variation("FlatButton");
tool_blend->set_theme_type_variation(SceneStringName(FlatButton));
tool_blend->set_toggle_mode(true);
tool_blend->set_button_group(bg);
top_hb->add_child(tool_blend);
@ -891,7 +889,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
tool_blend->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch).bind(3));
tool_select = memnew(Button);
tool_select->set_theme_type_variation("FlatButton");
tool_select->set_theme_type_variation(SceneStringName(FlatButton));
tool_select->set_toggle_mode(true);
tool_select->set_button_group(bg);
top_hb->add_child(tool_select);
@ -899,7 +897,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
tool_select->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch).bind(0));
tool_create = memnew(Button);
tool_create->set_theme_type_variation("FlatButton");
tool_create->set_theme_type_variation(SceneStringName(FlatButton));
tool_create->set_toggle_mode(true);
tool_create->set_button_group(bg);
top_hb->add_child(tool_create);
@ -907,7 +905,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
tool_create->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch).bind(1));
tool_triangle = memnew(Button);
tool_triangle->set_theme_type_variation("FlatButton");
tool_triangle->set_theme_type_variation(SceneStringName(FlatButton));
tool_triangle->set_toggle_mode(true);
tool_triangle->set_button_group(bg);
top_hb->add_child(tool_triangle);
@ -917,7 +915,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
tool_erase_sep = memnew(VSeparator);
top_hb->add_child(tool_erase_sep);
tool_erase = memnew(Button);
tool_erase->set_theme_type_variation("FlatButton");
tool_erase->set_theme_type_variation(SceneStringName(FlatButton));
top_hb->add_child(tool_erase);
tool_erase->set_tooltip_text(TTR("Erase points and triangles."));
tool_erase->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_erase_selected));
@ -926,7 +924,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
top_hb->add_child(memnew(VSeparator));
auto_triangles = memnew(Button);
auto_triangles->set_theme_type_variation("FlatButton");
auto_triangles->set_theme_type_variation(SceneStringName(FlatButton));
top_hb->add_child(auto_triangles);
auto_triangles->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_auto_triangles_toggled));
auto_triangles->set_toggle_mode(true);
@ -935,7 +933,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
top_hb->add_child(memnew(VSeparator));
snap = memnew(Button);
snap->set_theme_type_variation("FlatButton");
snap->set_theme_type_variation(SceneStringName(FlatButton));
snap->set_toggle_mode(true);
top_hb->add_child(snap);
snap->set_pressed(true);
@ -961,7 +959,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
top_hb->add_child(memnew(Label(TTR("Sync:"))));
sync = memnew(CheckBox);
top_hb->add_child(sync);
sync->connect("toggled", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_config_changed));
sync->connect(SceneStringName(toggled), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_config_changed));
top_hb->add_child(memnew(VSeparator));

View file

@ -35,7 +35,6 @@
#include "editor/plugins/editor_plugin.h"
#include "scene/animation/animation_blend_space_2d.h"
#include "scene/gui/graph_edit.h"
#include "scene/gui/popup.h"
class Button;
class CheckBox;

View file

@ -31,26 +31,24 @@
#include "animation_blend_tree_editor_plugin.h"
#include "core/config/project_settings.h"
#include "core/input/input.h"
#include "core/io/resource_loader.h"
#include "core/os/keyboard.h"
#include "editor/editor_inspector.h"
#include "editor/editor_node.h"
#include "editor/editor_properties.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/themes/editor_scale.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/animation/animation_player.h"
#include "scene/gui/check_box.h"
#include "scene/gui/grid_container.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/panel.h"
#include "scene/gui/option_button.h"
#include "scene/gui/progress_bar.h"
#include "scene/gui/separator.h"
#include "scene/gui/view_panner.h"
#include "scene/main/window.h"
#include "scene/resources/style_box_flat.h"
void AnimationNodeBlendTreeEditor::add_custom_type(const String &p_name, const Ref<Script> &p_script) {
for (int i = 0; i < add_options.size(); i++) {
@ -151,7 +149,7 @@ void AnimationNodeBlendTreeEditor::update_graph() {
node->set_draggable(!read_only);
Ref<AnimationNode> agnode = blend_tree->get_node(E);
ERR_CONTINUE(!agnode.is_valid());
ERR_CONTINUE(agnode.is_null());
node->set_position_offset(blend_tree->get_node_position(E) * EDSCALE);
@ -159,14 +157,15 @@ void AnimationNodeBlendTreeEditor::update_graph() {
node->set_name(E);
int base = 0;
if (String(E) != "output") {
if (E != SceneStringName(output)) {
LineEdit *name = memnew(LineEdit);
name->set_text(E);
name->set_editable(!read_only);
name->set_expand_to_text_length_enabled(true);
name->set_custom_minimum_size(Vector2(100, 0) * EDSCALE);
node->add_child(name);
node->set_slot(0, false, 0, Color(), true, read_only ? -1 : 0, get_theme_color(SceneStringName(font_color), SNAME("Label")));
name->connect("text_submitted", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed).bind(agnode), CONNECT_DEFERRED);
name->connect(SceneStringName(text_submitted), callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed).bind(agnode), CONNECT_DEFERRED);
name->connect(SceneStringName(focus_exited), callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed_focus_out).bind(agnode), CONNECT_DEFERRED);
name->connect(SceneStringName(text_changed), callable_mp(this, &AnimationNodeBlendTreeEditor::_node_rename_lineedit_changed), CONNECT_DEFERRED);
base = 1;
@ -176,7 +175,7 @@ void AnimationNodeBlendTreeEditor::update_graph() {
Button *delete_button = memnew(Button);
delete_button->set_flat(true);
delete_button->set_focus_mode(FOCUS_NONE);
delete_button->set_icon(get_editor_theme_icon(SNAME("Close")));
delete_button->set_button_icon(get_editor_theme_icon(SNAME("Close")));
delete_button->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendTreeEditor::_delete_node_request).bind(E), CONNECT_DEFERRED);
node->get_titlebar_hbox()->add_child(delete_button);
}
@ -203,6 +202,14 @@ void AnimationNodeBlendTreeEditor::update_graph() {
prop->update_property();
prop->set_name_split_ratio(0);
prop->connect("property_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_property_changed));
if (F.hint == PROPERTY_HINT_RESOURCE_TYPE) {
// Give the resource editor some more space to make the inside readable.
prop->set_custom_minimum_size(Vector2(180, 0) * EDSCALE);
// Align the size of the node with the resource editor, its un-expanding does not trigger a resize.
prop->connect(SceneStringName(resized), Callable(node, "reset_size"));
}
node->add_child(prop);
visible_properties.push_back(prop);
}
@ -214,7 +221,7 @@ void AnimationNodeBlendTreeEditor::update_graph() {
node->add_child(memnew(HSeparator));
Button *open_in_editor = memnew(Button);
open_in_editor->set_text(TTR("Open Editor"));
open_in_editor->set_icon(get_editor_theme_icon(SNAME("Edit")));
open_in_editor->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
node->add_child(open_in_editor);
open_in_editor->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendTreeEditor::_open_in_editor).bind(E), CONNECT_DEFERRED);
open_in_editor->set_h_size_flags(SIZE_SHRINK_CENTER);
@ -228,7 +235,7 @@ void AnimationNodeBlendTreeEditor::update_graph() {
} else {
inspect_filters->set_text(TTR("Edit Filters"));
}
inspect_filters->set_icon(get_editor_theme_icon(SNAME("AnimationFilter")));
inspect_filters->set_button_icon(get_editor_theme_icon(SNAME("AnimationFilter")));
node->add_child(inspect_filters);
inspect_filters->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendTreeEditor::_inspect_filters).bind(E), CONNECT_DEFERRED);
inspect_filters->set_h_size_flags(SIZE_SHRINK_CENTER);
@ -238,7 +245,7 @@ void AnimationNodeBlendTreeEditor::update_graph() {
if (anim.is_valid()) {
MenuButton *mb = memnew(MenuButton);
mb->set_text(anim->get_animation());
mb->set_icon(get_editor_theme_icon(SNAME("Animation")));
mb->set_button_icon(get_editor_theme_icon(SNAME("Animation")));
mb->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
mb->set_disabled(read_only);
Array options;
@ -264,17 +271,14 @@ void AnimationNodeBlendTreeEditor::update_graph() {
mb->get_popup()->connect("index_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_anim_selected).bind(options, E), CONNECT_DEFERRED);
}
// TODO: Avoid using strings, expose a method on GraphNode instead.
Ref<StyleBoxFlat> sb = node->get_theme_stylebox(SceneStringName(panel));
Color c = sb->get_border_color();
Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0) : Color(0.0, 0.0, 0.0);
mono_color.a = 0.85;
c = mono_color;
Ref<StyleBox> sb_panel = node->get_theme_stylebox(SceneStringName(panel), "GraphNode")->duplicate();
if (sb_panel.is_valid()) {
sb_panel->set_content_margin(SIDE_TOP, 12 * EDSCALE);
sb_panel->set_content_margin(SIDE_BOTTOM, 12 * EDSCALE);
node->add_theme_style_override(SceneStringName(panel), sb_panel);
}
node->add_theme_color_override("title_color", c);
c.a = 0.7;
node->add_theme_color_override("close_color", c);
node->add_theme_color_override("resizer_color", c);
node->add_theme_constant_override("separation", 4 * EDSCALE);
}
List<AnimationNodeBlendTree::NodeConnection> node_connections;
@ -323,7 +327,7 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) {
base_name = anode->get_class();
} else if (p_idx == MENU_PASTE) {
anode = EditorSettings::get_singleton()->get_resource_clipboard();
ERR_FAIL_COND(!anode.is_valid());
ERR_FAIL_COND(anode.is_null());
base_name = anode->get_class();
} else if (!add_options[p_idx].type.is_empty()) {
AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instantiate(add_options[p_idx].type));
@ -494,7 +498,7 @@ void AnimationNodeBlendTreeEditor::_anim_selected(int p_index, const Array &p_op
String option = p_options[p_index];
Ref<AnimationNodeAnimation> anim = blend_tree->get_node(p_node);
ERR_FAIL_COND(!anim.is_valid());
ERR_FAIL_COND(anim.is_null());
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Set Animation"));
@ -580,14 +584,14 @@ void AnimationNodeBlendTreeEditor::_node_selected(Object *p_node) {
String name = gn->get_name();
Ref<AnimationNode> anode = blend_tree->get_node(name);
ERR_FAIL_COND(!anode.is_valid());
ERR_FAIL_COND(anode.is_null());
EditorNode::get_singleton()->push_item(anode.ptr(), "", true);
}
void AnimationNodeBlendTreeEditor::_open_in_editor(const String &p_which) {
Ref<AnimationNode> an = blend_tree->get_node(p_which);
ERR_FAIL_COND(!an.is_valid());
ERR_FAIL_COND(an.is_null());
AnimationTreeEditor::get_singleton()->enter_editor(p_which);
}
@ -914,7 +918,7 @@ void AnimationNodeBlendTreeEditor::_inspect_filters(const String &p_which) {
filter_enabled->set_disabled(read_only);
Ref<AnimationNode> anode = blend_tree->get_node(p_which);
ERR_FAIL_COND(!anode.is_valid());
ERR_FAIL_COND(anode.is_null());
_filter_edit = anode;
if (!_update_filters(anode)) {
@ -926,7 +930,7 @@ void AnimationNodeBlendTreeEditor::_inspect_filters(const String &p_which) {
void AnimationNodeBlendTreeEditor::_update_editor_settings() {
graph->get_panner()->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));
graph->set_warped_panning(bool(EDITOR_GET("editors/panning/warped_mouse_panning")));
graph->set_warped_panning(EDITOR_GET("editors/panning/warped_mouse_panning"));
}
void AnimationNodeBlendTreeEditor::_notification(int p_what) {
@ -1059,7 +1063,7 @@ void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref<Anima
const String &new_name = p_text;
ERR_FAIL_COND(new_name.is_empty() || new_name.contains(".") || new_name.contains("/"));
ERR_FAIL_COND(new_name.is_empty() || new_name.contains_char('.') || new_name.contains_char('/'));
if (new_name == prev_name) {
return; //nothing to do
@ -1262,4 +1266,166 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() {
open_file->set_title(TTR("Open Animation Node"));
open_file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
open_file->connect("file_selected", callable_mp(this, &AnimationNodeBlendTreeEditor::_file_opened));
animation_node_inspector_plugin.instantiate();
EditorInspector::add_inspector_plugin(animation_node_inspector_plugin);
}
AnimationNodeBlendTreeEditor::~AnimationNodeBlendTreeEditor() {
}
// EditorPluginAnimationNodeAnimation
void AnimationNodeAnimationEditor::_open_set_custom_timeline_from_marker_dialog() {
AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
StringName anim_name = animation_node_animation->get_animation();
PackedStringArray markers = tree->has_animation(anim_name) ? tree->get_animation(anim_name)->get_marker_names() : PackedStringArray();
dialog->select_start->clear();
dialog->select_start->add_icon_item(get_editor_theme_icon(SNAME("PlayStart")), TTR("Start of Animation"));
dialog->select_start->add_separator();
dialog->select_end->clear();
dialog->select_end->add_icon_item(get_editor_theme_icon(SNAME("PlayStartBackwards")), TTR("End of Animation"));
dialog->select_end->add_separator();
for (const String &marker : markers) {
dialog->select_start->add_item(marker);
dialog->select_end->add_item(marker);
}
// Because the default selections are always valid, and marker times won't change during the dialog, we can ensure that the user can only select valid markers.
// This invariant is maintained by _validate_markers.
dialog->select_start->select(0);
dialog->select_end->select(0);
dialog->popup_centered(Size2(200, 0) * EDSCALE);
}
void AnimationNodeAnimationEditor::_validate_markers(int p_id) {
// Note: p_id is ignored. It is included because OptionButton's item_changed signal always passes it.
int start_id = dialog->select_start->get_selected_id();
int end_id = dialog->select_end->get_selected_id();
StringName anim_name = animation_node_animation->get_animation();
Ref<Animation> animation = AnimationTreeEditor::get_singleton()->get_animation_tree()->get_animation(anim_name);
ERR_FAIL_COND(animation.is_null());
double start_time = start_id < 2 ? 0 : animation->get_marker_time(dialog->select_start->get_item_text(start_id));
double end_time = end_id < 2 ? animation->get_length() : animation->get_marker_time(dialog->select_end->get_item_text(end_id));
// p_start and p_end have the same item count.
for (int i = 2; i < dialog->select_start->get_item_count(); i++) {
String start_marker = dialog->select_start->get_item_text(i);
String end_marker = dialog->select_end->get_item_text(i);
dialog->select_start->set_item_disabled(i, end_id >= 2 && (i == end_id || animation->get_marker_time(start_marker) > end_time));
dialog->select_end->set_item_disabled(i, start_id >= 2 && (i == start_id || start_time > animation->get_marker_time(end_marker)));
}
}
void AnimationNodeAnimationEditor::_confirm_set_custom_timeline_from_marker_dialog() {
int start_id = dialog->select_start->get_selected_id();
int end_id = dialog->select_end->get_selected_id();
Ref<Animation> animation = AnimationTreeEditor::get_singleton()->get_animation_tree()->get_animation(animation_node_animation->get_animation());
ERR_FAIL_COND(animation.is_null());
double start_time = start_id < 2 ? 0 : animation->get_marker_time(dialog->select_start->get_item_text(start_id));
double end_time = end_id < 2 ? animation->get_length() : animation->get_marker_time(dialog->select_end->get_item_text(end_id));
double length = end_time - start_time;
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Set Custom Timeline from Marker"));
undo_redo->add_do_method(*animation_node_animation, "set_start_offset", start_time);
undo_redo->add_undo_method(*animation_node_animation, "set_start_offset", animation_node_animation->get_start_offset());
undo_redo->add_do_method(*animation_node_animation, "set_stretch_time_scale", false);
undo_redo->add_undo_method(*animation_node_animation, "set_stretch_time_scale", animation_node_animation->is_stretching_time_scale());
undo_redo->add_do_method(*animation_node_animation, "set_timeline_length", length);
undo_redo->add_undo_method(*animation_node_animation, "set_timeline_length", animation_node_animation->get_timeline_length());
undo_redo->add_do_method(*animation_node_animation, "notify_property_list_changed");
undo_redo->add_undo_method(*animation_node_animation, "notify_property_list_changed");
undo_redo->commit_action();
}
AnimationNodeAnimationEditor::AnimationNodeAnimationEditor(Ref<AnimationNodeAnimation> p_animation_node_animation) {
animation_node_animation = p_animation_node_animation;
dialog = memnew(AnimationNodeAnimationEditorDialog);
add_child(dialog);
dialog->set_hide_on_ok(false);
dialog->select_start->connect(SceneStringName(item_selected), callable_mp(this, &AnimationNodeAnimationEditor::_validate_markers));
dialog->select_end->connect(SceneStringName(item_selected), callable_mp(this, &AnimationNodeAnimationEditor::_validate_markers));
dialog->connect(SceneStringName(confirmed), callable_mp(this, &AnimationNodeAnimationEditor::_confirm_set_custom_timeline_from_marker_dialog));
Control *top_spacer = memnew(Control);
add_child(top_spacer);
top_spacer->set_custom_minimum_size(Size2(0, 2) * EDSCALE);
button = memnew(Button);
add_child(button);
button->set_text(TTR("Set Custom Timeline from Marker"));
button->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
button->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeAnimationEditor::_open_set_custom_timeline_from_marker_dialog));
Control *bottom_spacer = memnew(Control);
add_child(bottom_spacer);
bottom_spacer->set_custom_minimum_size(Size2(0, 2) * EDSCALE);
}
AnimationNodeAnimationEditor::~AnimationNodeAnimationEditor() {
}
void AnimationNodeAnimationEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
button->set_theme_type_variation(SNAME("InspectorActionButton"));
button->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
} break;
}
}
bool EditorInspectorPluginAnimationNodeAnimation::can_handle(Object *p_object) {
Ref<AnimationNodeAnimation> ana(Object::cast_to<AnimationNodeAnimation>(p_object));
return ana.is_valid() && ana->is_using_custom_timeline();
}
bool EditorInspectorPluginAnimationNodeAnimation::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
Ref<AnimationNodeAnimation> ana(Object::cast_to<AnimationNodeAnimation>(p_object));
ERR_FAIL_COND_V(ana.is_null(), false);
if (p_path == "timeline_length") {
add_custom_control(memnew(AnimationNodeAnimationEditor(ana)));
}
return false;
}
AnimationNodeAnimationEditorDialog::AnimationNodeAnimationEditorDialog() {
set_title(TTR("Select Markers"));
GridContainer *grid = memnew(GridContainer);
grid->set_columns(2);
grid->set_offsets_preset(Control::PRESET_FULL_RECT);
add_child(grid);
Label *label_start = memnew(Label(TTR("Start Marker")));
grid->add_child(label_start);
label_start->set_h_size_flags(Control::SIZE_EXPAND_FILL);
label_start->set_stretch_ratio(1);
select_start = memnew(OptionButton);
select_start->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
grid->add_child(select_start);
select_start->set_h_size_flags(Control::SIZE_EXPAND_FILL);
select_start->set_stretch_ratio(2);
Label *label_end = memnew(Label(TTR("End Marker")));
grid->add_child(label_end);
label_end->set_h_size_flags(Control::SIZE_EXPAND_FILL);
label_end->set_stretch_ratio(1);
select_end = memnew(OptionButton);
select_end->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
grid->add_child(select_end);
select_end->set_h_size_flags(Control::SIZE_EXPAND_FILL);
select_end->set_stretch_ratio(2);
}
AnimationNodeAnimationEditorDialog::~AnimationNodeAnimationEditorDialog() {
}

View file

@ -32,12 +32,13 @@
#define ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H
#include "core/object/script_language.h"
#include "editor/editor_inspector.h"
#include "editor/plugins/animation_tree_editor_plugin.h"
#include "scene/animation/animation_blend_tree.h"
#include "scene/gui/button.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/graph_edit.h"
#include "scene/gui/panel_container.h"
#include "scene/gui/popup.h"
#include "scene/gui/tree.h"
class AcceptDialog;
@ -47,6 +48,7 @@ class EditorFileDialog;
class EditorProperty;
class MenuButton;
class PanelContainer;
class EditorInspectorPluginAnimationNodeAnimation;
class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin {
GDCLASS(AnimationNodeBlendTreeEditor, AnimationTreeNodeEditorPlugin);
@ -147,6 +149,8 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin {
MENU_LOAD_FILE_CONFIRM = 1002
};
Ref<EditorInspectorPluginAnimationNodeAnimation> animation_node_inspector_plugin;
protected:
void _notification(int p_what);
static void _bind_methods();
@ -165,6 +169,48 @@ public:
void update_graph();
AnimationNodeBlendTreeEditor();
~AnimationNodeBlendTreeEditor();
};
// EditorPluginAnimationNodeAnimation
class EditorInspectorPluginAnimationNodeAnimation : public EditorInspectorPlugin {
GDCLASS(EditorInspectorPluginAnimationNodeAnimation, EditorInspectorPlugin);
public:
virtual bool can_handle(Object *p_object) override;
virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) override;
};
class AnimationNodeAnimationEditorDialog : public ConfirmationDialog {
GDCLASS(AnimationNodeAnimationEditorDialog, ConfirmationDialog);
friend class AnimationNodeAnimationEditor;
OptionButton *select_start = nullptr;
OptionButton *select_end = nullptr;
public:
AnimationNodeAnimationEditorDialog();
~AnimationNodeAnimationEditorDialog();
};
class AnimationNodeAnimationEditor : public VBoxContainer {
GDCLASS(AnimationNodeAnimationEditor, VBoxContainer);
Ref<AnimationNodeAnimation> animation_node_animation;
Button *button = nullptr;
AnimationNodeAnimationEditorDialog *dialog = nullptr;
void _open_set_custom_timeline_from_marker_dialog();
void _validate_markers(int p_id);
void _confirm_set_custom_timeline_from_marker_dialog();
public:
AnimationNodeAnimationEditor(Ref<AnimationNodeAnimation> p_animation_node_animation);
~AnimationNodeAnimationEditor();
protected:
void _notification(int p_what);
};
#endif // ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H

View file

@ -30,7 +30,12 @@
#include "animation_library_editor.h"
#include "core/string/print_string.h"
#include "core/string/ustring.h"
#include "core/templates/vector.h"
#include "core/variant/variant.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
@ -100,7 +105,7 @@ void AnimationLibraryEditor::_add_library_confirm() {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
Ref<AnimationLibrary> al = mixer->get_animation_library(adding_animation_to_library);
ERR_FAIL_COND(!al.is_valid());
ERR_FAIL_COND(al.is_null());
Ref<Animation> anim;
anim.instantiate();
@ -465,7 +470,7 @@ void AnimationLibraryEditor::_item_renamed() {
bool restore_text = false;
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
if (String(text).contains("/") || String(text).contains(":") || String(text).contains(",") || String(text).contains("[")) {
if (String(text).contains_char('/') || String(text).contains_char(':') || String(text).contains_char(',') || String(text).contains_char('[')) {
restore_text = true;
} else {
if (ti->get_parent() == tree->get_root()) {
@ -518,6 +523,8 @@ void AnimationLibraryEditor::_item_renamed() {
if (restore_text) {
ti->set_text(0, old_text);
}
_save_mixer_lib_folding(ti);
}
void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button) {
@ -555,13 +562,15 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int
} break;
case LIB_BUTTON_PASTE: {
Ref<Animation> anim = EditorSettings::get_singleton()->get_resource_clipboard();
if (!anim.is_valid()) {
if (anim.is_null()) {
error_dialog->set_text(TTR("No animation resource in clipboard!"));
error_dialog->popup_centered();
return;
}
anim = anim->duplicate(); // Users simply dont care about referencing, so making a copy works better here.
if (!anim->get_path().is_resource_file()) {
anim = anim->duplicate(); // Users simply dont care about referencing, so making a copy works better here.
}
String base_name;
if (anim->get_name() != "") {
@ -619,7 +628,7 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int
StringName anim_name = p_item->get_metadata(0);
Ref<AnimationLibrary> al = mixer->get_animation_library(lib_name);
Ref<Animation> anim = al->get_animation(anim_name);
ERR_FAIL_COND(!anim.is_valid());
ERR_FAIL_COND(anim.is_null());
switch (p_id) {
case ANIM_BUTTON_COPY: {
if (anim->get_name() == "") {
@ -668,6 +677,8 @@ void AnimationLibraryEditor::update_tree() {
TreeItem *root = tree->create_item();
List<StringName> libs;
Vector<uint64_t> collapsed_lib_ids = _load_mixer_libs_folding();
mixer->get_animation_library_list(&libs);
for (const StringName &K : libs) {
@ -757,12 +768,203 @@ void AnimationLibraryEditor::update_tree() {
anitem->set_text(1, anim_path.get_file());
}
}
anitem->add_button(1, get_editor_theme_icon("Save"), ANIM_BUTTON_FILE, animation_library_is_foreign, TTR("Save animation to resource on disk."));
anitem->add_button(1, get_editor_theme_icon("Remove"), ANIM_BUTTON_DELETE, animation_library_is_foreign, TTR("Remove animation from Library."));
for (const uint64_t &lib_id : collapsed_lib_ids) {
Object *lib_obj = ObjectDB::get_instance(ObjectID(lib_id));
AnimationLibrary *cur_lib = Object::cast_to<AnimationLibrary>(lib_obj);
StringName M = mixer->get_animation_library_name(cur_lib);
if (M == K) {
libitem->set_collapsed_recursive(true);
}
}
}
}
}
void AnimationLibraryEditor::_save_mixer_lib_folding(TreeItem *p_item) {
//Check if ti is a library or animation
if (p_item->get_parent()->get_parent() != nullptr) {
return;
}
Ref<ConfigFile> config;
config.instantiate();
String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join("lib_folding.cfg");
Error err = config->load(path);
if (err != OK && err != ERR_FILE_NOT_FOUND) {
ERR_PRINT("Error loading lib_folding.cfg: " + itos(err));
}
// Get unique identifier for this scene+mixer combination
String md = (mixer->get_tree()->get_edited_scene_root()->get_scene_file_path() + mixer->get_path()).md5_text();
PackedStringArray collapsed_lib_names;
PackedStringArray collapsed_lib_ids;
if (config->has_section(md)) {
collapsed_lib_names = String(config->get_value(md, "folding")).split("\n");
collapsed_lib_ids = String(config->get_value(md, "id")).split("\n");
}
String lib_name = p_item->get_text(0);
// Get library reference and check validity
Ref<AnimationLibrary> al;
uint64_t lib_id = 0;
if (mixer->has_animation_library(lib_name)) {
al = mixer->get_animation_library(lib_name);
ERR_FAIL_COND(al.is_null());
lib_id = uint64_t(al->get_instance_id());
} else {
ERR_PRINT("Library not found: " + lib_name);
}
int at = collapsed_lib_names.find(lib_name);
if (p_item->is_collapsed()) {
if (at != -1) {
//Entry exists and needs updating
collapsed_lib_ids.set(at, String::num_int64(lib_id + INT64_MIN));
} else {
//Check if it's a rename
int id_at = collapsed_lib_ids.find(String::num_int64(lib_id + INT64_MIN));
if (id_at != -1) {
//It's actually a rename
collapsed_lib_names.set(id_at, lib_name);
} else {
//It's a new entry
collapsed_lib_names.append(lib_name);
collapsed_lib_ids.append(String::num_int64(lib_id + INT64_MIN));
}
}
} else {
if (at != -1) {
collapsed_lib_names.remove_at(at);
collapsed_lib_ids.remove_at(at);
}
}
//Runtime IDs
config->set_value(md, "root", uint64_t(mixer->get_tree()->get_edited_scene_root()->get_instance_id()));
config->set_value(md, "mixer", uint64_t(mixer->get_instance_id()));
//Plan B recovery mechanism
config->set_value(md, "mixer_signature", _get_mixer_signature());
//Save folding state as text and runtime ID
config->set_value(md, "folding", String("\n").join(collapsed_lib_names));
config->set_value(md, "id", String("\n").join(collapsed_lib_ids));
err = config->save(path);
if (err != OK) {
ERR_PRINT("Error saving lib_folding.cfg: " + itos(err));
}
}
Vector<uint64_t> AnimationLibraryEditor::_load_mixer_libs_folding() {
Ref<ConfigFile> config;
config.instantiate();
String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join("lib_folding.cfg");
Error err = config->load(path);
if (err != OK && err != ERR_FILE_NOT_FOUND) {
ERR_PRINT("Error loading lib_folding.cfg: " + itos(err));
return Vector<uint64_t>();
}
// Get unique identifier for this scene+mixer combination
String md = (mixer->get_tree()->get_edited_scene_root()->get_scene_file_path() + mixer->get_path()).md5_text();
Vector<uint64_t> collapsed_lib_ids;
if (config->has_section(md)) {
_load_config_libs_folding(collapsed_lib_ids, config.ptr(), md);
} else {
//The scene/mixer combination is no longer valid and we'll try to recover
uint64_t current_mixer_id = uint64_t(mixer->get_instance_id());
String current_mixer_signature = _get_mixer_signature();
List<String> sections;
config->get_sections(&sections);
for (const String &section : sections) {
Variant mixer_id = config->get_value(section, "mixer");
if ((mixer_id.get_type() == Variant::INT && uint64_t(mixer_id) == current_mixer_id) || config->get_value(section, "mixer_signature") == current_mixer_signature) { // Ensure value exists and is correct type
// Found the mixer in a different section!
_load_config_libs_folding(collapsed_lib_ids, config.ptr(), section);
//Cleanup old entry and copy fold data into new one!
String collapsed_lib_names_str = String(config->get_value(section, "folding"));
String collapsed_lib_ids_str = String(config->get_value(section, "id"));
config->erase_section(section);
config->set_value(md, "root", uint64_t(mixer->get_tree()->get_edited_scene_root()->get_instance_id()));
config->set_value(md, "mixer", uint64_t(mixer->get_instance_id()));
config->set_value(md, "mixer_signature", _get_mixer_signature());
config->set_value(md, "folding", collapsed_lib_names_str);
config->set_value(md, "id", collapsed_lib_ids_str);
err = config->save(path);
if (err != OK) {
ERR_PRINT("Error saving lib_folding.cfg: " + itos(err));
}
break;
}
}
}
return collapsed_lib_ids;
}
void AnimationLibraryEditor::_load_config_libs_folding(Vector<uint64_t> &p_lib_ids, ConfigFile *p_config, String p_section) {
if (uint64_t(p_config->get_value(p_section, "root", 0)) != uint64_t(mixer->get_tree()->get_edited_scene_root()->get_instance_id())) {
// Root changed - tries to match by library names
PackedStringArray collapsed_lib_names = String(p_config->get_value(p_section, "folding", "")).split("\n");
for (const String &lib_name : collapsed_lib_names) {
if (mixer->has_animation_library(lib_name)) {
p_lib_ids.append(mixer->get_animation_library(lib_name)->get_instance_id());
} else {
print_line("Can't find ", lib_name, " in mixer");
}
}
} else {
// Root same - uses saved instance IDs
for (const String &saved_id : String(p_config->get_value(p_section, "id")).split("\n")) {
p_lib_ids.append(uint64_t(saved_id.to_int() - INT64_MIN));
}
}
}
String AnimationLibraryEditor::_get_mixer_signature() const {
String signature = String();
// Get all libraries sorted for consistency
List<StringName> libs;
mixer->get_animation_library_list(&libs);
libs.sort_custom<StringName::AlphCompare>();
// Add libraries and their animations to signature
for (const StringName &lib_name : libs) {
signature += "::" + String(lib_name);
Ref<AnimationLibrary> lib = mixer->get_animation_library(lib_name);
if (lib.is_valid()) {
List<StringName> anims;
lib->get_animation_list(&anims);
anims.sort_custom<StringName::AlphCompare>();
for (const StringName &anim_name : anims) {
signature += "," + String(anim_name);
}
}
}
return signature.md5_text();
}
void AnimationLibraryEditor::show_dialog() {
update_tree();
popup_centered_ratio(0.5);
@ -771,8 +973,8 @@ void AnimationLibraryEditor::show_dialog() {
void AnimationLibraryEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
new_library_button->set_icon(get_editor_theme_icon(SNAME("Add")));
load_library_button->set_icon(get_editor_theme_icon(SNAME("Load")));
new_library_button->set_button_icon(get_editor_theme_icon(SNAME("Add")));
load_library_button->set_button_icon(get_editor_theme_icon(SNAME("Load")));
}
}
}
@ -853,11 +1055,12 @@ AnimationLibraryEditor::AnimationLibraryEditor() {
tree->set_column_custom_minimum_width(1, EDSCALE * 250);
tree->set_column_expand(1, false);
tree->set_hide_root(true);
tree->set_hide_folding(true);
tree->set_hide_folding(false);
tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
tree->connect("item_edited", callable_mp(this, &AnimationLibraryEditor::_item_renamed));
tree->connect("button_clicked", callable_mp(this, &AnimationLibraryEditor::_button_pressed));
tree->connect("item_collapsed", callable_mp(this, &AnimationLibraryEditor::_save_mixer_lib_folding));
file_popup = memnew(PopupMenu);
add_child(file_popup);

View file

@ -31,7 +31,8 @@
#ifndef ANIMATION_LIBRARY_EDITOR_H
#define ANIMATION_LIBRARY_EDITOR_H
#include "editor/animation_track_editor.h"
#include "core/io/config_file.h"
#include "core/templates/vector.h"
#include "editor/plugins/editor_plugin.h"
#include "scene/animation/animation_mixer.h"
#include "scene/gui/dialogs.h"
@ -103,6 +104,11 @@ class AnimationLibraryEditor : public AcceptDialog {
void _load_file(const String &p_path);
void _load_files(const PackedStringArray &p_paths);
void _save_mixer_lib_folding(TreeItem *p_item);
Vector<uint64_t> _load_mixer_libs_folding();
void _load_config_libs_folding(Vector<uint64_t> &p_lib_ids, ConfigFile *p_config, String p_section);
String _get_mixer_signature() const;
void _item_renamed();
void _button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button);

View file

@ -32,8 +32,6 @@
#include "core/config/project_settings.h"
#include "core/input/input.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/os/keyboard.h"
#include "editor/editor_command_palette.h"
#include "editor/editor_node.h"
@ -41,6 +39,7 @@
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/gui/editor_validation_panel.h"
#include "editor/inspector_dock.h"
#include "editor/plugins/canvas_item_editor_plugin.h" // For onion skinning.
#include "editor/plugins/node_3d_editor_plugin.h" // For onion skinning.
@ -98,7 +97,7 @@ void AnimationPlayerEditor::_notification(int p_what) {
if (player->has_animation(animname)) {
Ref<Animation> anim = player->get_animation(animname);
if (!anim.is_null()) {
if (anim.is_valid()) {
frame->set_max((double)anim->get_length());
}
}
@ -112,7 +111,7 @@ void AnimationPlayerEditor::_notification(int p_what) {
// Need the last frame after it stopped.
frame->set_value(player->get_current_animation_position());
track_editor->set_anim_pos(player->get_current_animation_position());
stop->set_icon(stop_icon);
stop->set_button_icon(stop_icon);
}
last_active = player->is_playing();
@ -144,16 +143,16 @@ void AnimationPlayerEditor::_notification(int p_what) {
stop_icon = get_editor_theme_icon(SNAME("Stop"));
pause_icon = get_editor_theme_icon(SNAME("Pause"));
if (player && player->is_playing()) {
stop->set_icon(pause_icon);
stop->set_button_icon(pause_icon);
} else {
stop->set_icon(stop_icon);
stop->set_button_icon(stop_icon);
}
autoplay->set_icon(get_editor_theme_icon(SNAME("AutoPlay")));
play->set_icon(get_editor_theme_icon(SNAME("PlayStart")));
play_from->set_icon(get_editor_theme_icon(SNAME("Play")));
play_bw->set_icon(get_editor_theme_icon(SNAME("PlayStartBackwards")));
play_bw_from->set_icon(get_editor_theme_icon(SNAME("PlayBackwards")));
autoplay->set_button_icon(get_editor_theme_icon(SNAME("AutoPlay")));
play->set_button_icon(get_editor_theme_icon(SNAME("PlayStart")));
play_from->set_button_icon(get_editor_theme_icon(SNAME("Play")));
play_bw->set_button_icon(get_editor_theme_icon(SNAME("PlayStartBackwards")));
play_bw_from->set_button_icon(get_editor_theme_icon(SNAME("PlayBackwards")));
autoplay_icon = get_editor_theme_icon(SNAME("AutoPlay"));
reset_icon = get_editor_theme_icon(SNAME("Reload"));
@ -167,10 +166,10 @@ void AnimationPlayerEditor::_notification(int p_what) {
autoplay_reset_icon = ImageTexture::create_from_image(autoplay_reset_img);
}
onion_toggle->set_icon(get_editor_theme_icon(SNAME("Onion")));
onion_skinning->set_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
onion_toggle->set_button_icon(get_editor_theme_icon(SNAME("Onion")));
onion_skinning->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
pin->set_icon(get_editor_theme_icon(SNAME("Pin")));
pin->set_button_icon(get_editor_theme_icon(SNAME("Pin")));
tool_anim->add_theme_style_override(CoreStringName(normal), get_theme_stylebox(CoreStringName(normal), SNAME("Button")));
track_editor->get_edit_menu()->add_theme_style_override(CoreStringName(normal), get_theme_stylebox(CoreStringName(normal), SNAME("Button")));
@ -224,6 +223,69 @@ void AnimationPlayerEditor::_autoplay_pressed() {
}
}
void AnimationPlayerEditor::_go_to_nearest_keyframe(bool p_backward) {
if (_get_current().is_empty()) {
return;
}
Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
double current_time = player->get_current_animation_position();
// Offset the time to avoid finding the same keyframe with Animation::track_find_key().
double time_offset = MAX(CMP_EPSILON * 2, current_time * CMP_EPSILON * 2);
double current_time_offset = current_time + (p_backward ? -time_offset : time_offset);
float nearest_key_time = p_backward ? 0 : anim->get_length();
int track_count = anim->get_track_count();
bool bezier_active = track_editor->is_bezier_editor_active();
Node *root = get_tree()->get_edited_scene_root();
EditorSelection *selection = EditorNode::get_singleton()->get_editor_selection();
Vector<int> selected_tracks;
for (int i = 0; i < track_count; ++i) {
if (selection->is_selected(root->get_node_or_null(anim->track_get_path(i)))) {
selected_tracks.push_back(i);
}
}
// Find the nearest keyframe in selection if the scene has selected nodes
// or the nearest keyframe in the entire animation otherwise.
if (selected_tracks.size() > 0) {
for (int track : selected_tracks) {
if (bezier_active && anim->track_get_type(track) != Animation::TYPE_BEZIER) {
continue;
}
int key = anim->track_find_key(track, current_time_offset, Animation::FIND_MODE_NEAREST, false, !p_backward);
if (key == -1) {
continue;
}
double key_time = anim->track_get_key_time(track, key);
if ((p_backward && key_time > nearest_key_time) || (!p_backward && key_time < nearest_key_time)) {
nearest_key_time = key_time;
}
}
} else {
for (int track = 0; track < track_count; ++track) {
if (bezier_active && anim->track_get_type(track) != Animation::TYPE_BEZIER) {
continue;
}
int key = anim->track_find_key(track, current_time_offset, Animation::FIND_MODE_NEAREST, false, !p_backward);
if (key == -1) {
continue;
}
double key_time = anim->track_get_key_time(track, key);
if ((p_backward && key_time > nearest_key_time) || (!p_backward && key_time < nearest_key_time)) {
nearest_key_time = key_time;
}
}
}
player->seek_internal(nearest_key_time, true, true, true);
frame->set_value(nearest_key_time);
track_editor->set_anim_pos(nearest_key_time);
}
void AnimationPlayerEditor::_play_pressed() {
String current = _get_current();
@ -232,11 +294,18 @@ void AnimationPlayerEditor::_play_pressed() {
player->stop(); //so it won't blend with itself
}
ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
player->play(current);
PackedStringArray markers = track_editor->get_selected_section();
if (markers.size() == 2) {
StringName start_marker = markers[0];
StringName end_marker = markers[1];
player->play_section_with_markers(current, start_marker, end_marker);
} else {
player->play(current);
}
}
//unstop
stop->set_icon(pause_icon);
stop->set_button_icon(pause_icon);
}
void AnimationPlayerEditor::_play_from_pressed() {
@ -249,11 +318,18 @@ void AnimationPlayerEditor::_play_from_pressed() {
}
ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
player->seek_internal(time, true, true, true);
player->play(current);
PackedStringArray markers = track_editor->get_selected_section();
if (markers.size() == 2) {
StringName start_marker = markers[0];
StringName end_marker = markers[1];
player->play_section_with_markers(current, start_marker, end_marker);
} else {
player->play(current);
}
}
//unstop
stop->set_icon(pause_icon);
stop->set_button_icon(pause_icon);
}
String AnimationPlayerEditor::_get_current() const {
@ -270,11 +346,18 @@ void AnimationPlayerEditor::_play_bw_pressed() {
player->stop(); //so it won't blend with itself
}
ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
player->play_backwards(current);
PackedStringArray markers = track_editor->get_selected_section();
if (markers.size() == 2) {
StringName start_marker = markers[0];
StringName end_marker = markers[1];
player->play_section_with_markers_backwards(current, start_marker, end_marker);
} else {
player->play_backwards(current);
}
}
//unstop
stop->set_icon(pause_icon);
stop->set_button_icon(pause_icon);
}
void AnimationPlayerEditor::_play_bw_from_pressed() {
@ -287,11 +370,18 @@ void AnimationPlayerEditor::_play_bw_from_pressed() {
}
ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
player->seek_internal(time, true, true, true);
player->play_backwards(current);
PackedStringArray markers = track_editor->get_selected_section();
if (markers.size() == 2) {
StringName start_marker = markers[0];
StringName end_marker = markers[1];
player->play_section_with_markers_backwards(current, start_marker, end_marker);
} else {
player->play_backwards(current);
}
}
//unstop
stop->set_icon(pause_icon);
stop->set_button_icon(pause_icon);
}
void AnimationPlayerEditor::_stop_pressed() {
@ -308,7 +398,7 @@ void AnimationPlayerEditor::_stop_pressed() {
frame->set_value(0);
track_editor->set_anim_pos(0);
}
stop->set_icon(stop_icon);
stop->set_button_icon(stop_icon);
}
void AnimationPlayerEditor::_animation_selected(int p_which) {
@ -339,7 +429,17 @@ void AnimationPlayerEditor::_animation_selected(int p_which) {
track_editor->set_animation(anim, animation_is_readonly);
Node *root = player->get_node_or_null(player->get_root_node());
if (root) {
// Player shouldn't access parent if it's the scene root.
if (!root || (player == get_tree()->get_edited_scene_root() && player->get_root_node() == SceneStringName(path_pp))) {
NodePath cached_root_path = player->get_path_to(get_cached_root_node());
if (player->get_node_or_null(cached_root_path) != nullptr) {
player->set_root_node(cached_root_path);
} else {
player->set_root_node(SceneStringName(path_pp)); // No other choice, preventing crash.
}
} else {
cached_root_node_id = root->get_instance_id(); // Caching as `track_editor` can lose track of player's root node.
track_editor->set_root(root);
}
}
@ -402,7 +502,7 @@ void AnimationPlayerEditor::_animation_rename() {
String selected_name = animation->get_item_text(selected);
// Remove library prefix if present.
if (selected_name.contains("/")) {
if (selected_name.contains_char('/')) {
selected_name = selected_name.get_slice("/", 1);
}
@ -435,7 +535,7 @@ void AnimationPlayerEditor::_animation_remove_confirmed() {
ERR_FAIL_COND(al.is_null());
// For names of form lib_name/anim_name, remove library name prefix.
if (current.contains("/")) {
if (current.contains_char('/')) {
current = current.get_slice("/", 1);
}
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
@ -474,17 +574,14 @@ void AnimationPlayerEditor::_select_anim_by_name(const String &p_anim) {
}
float AnimationPlayerEditor::_get_editor_step() const {
// Returns the effective snapping value depending on snapping modifiers, or 0 if snapping is disabled.
if (track_editor->is_snap_enabled()) {
const String current = player->get_assigned_animation();
const Ref<Animation> anim = player->get_animation(current);
ERR_FAIL_COND_V(!anim.is_valid(), 0.0);
const String current = player->get_assigned_animation();
const Ref<Animation> anim = player->get_animation(current);
ERR_FAIL_COND_V(anim.is_null(), 0.0);
// Use more precise snapping when holding Shift
return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? anim->get_step() * 0.25 : anim->get_step();
}
float step = track_editor->get_snap_unit();
return 0.0f;
// Use more precise snapping when holding Shift
return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? step * 0.25 : step;
}
void AnimationPlayerEditor::_animation_name_edited() {
@ -527,7 +624,7 @@ void AnimationPlayerEditor::_animation_name_edited() {
// Extract library prefix if present.
String new_library_prefix = "";
if (current.contains("/")) {
if (current.contains_char('/')) {
new_library_prefix = current.get_slice("/", 0) + "/";
current = current.get_slice("/", 1);
}
@ -802,7 +899,7 @@ void AnimationPlayerEditor::set_state(const Dictionary &p_state) {
player->connect(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated), CONNECT_DEFERRED);
}
if (!player->is_connected(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed))) {
player->connect(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed), CONNECT_DEFERRED);
player->connect(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed));
}
}
@ -862,9 +959,9 @@ void AnimationPlayerEditor::_update_animation() {
updating = true;
if (player->is_playing()) {
stop->set_icon(pause_icon);
stop->set_button_icon(pause_icon);
} else {
stop->set_icon(stop_icon);
stop->set_button_icon(stop_icon);
}
scale->set_text(String::num(player->get_speed_scale(), 2));
@ -1217,7 +1314,7 @@ void AnimationPlayerEditor::_animation_duplicate() {
String current = animation->get_item_text(animation->get_selected());
Ref<Animation> anim = player->get_animation(current);
if (!anim.is_valid()) {
if (anim.is_null()) {
return;
}
@ -1241,7 +1338,7 @@ void AnimationPlayerEditor::_animation_duplicate() {
break;
}
if (new_name.contains("/")) {
if (new_name.contains_char('/')) {
// Discard library prefix.
new_name = new_name.get_slice("/", 1);
}
@ -1290,7 +1387,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_timeline_o
anim = player->get_animation(current);
double pos = CLAMP((double)anim->get_length() * (p_value / frame->get_max()), 0, (double)anim->get_length());
if (track_editor->is_snap_enabled()) {
if (track_editor->is_snap_timeline_enabled()) {
pos = Math::snapped(pos, _get_editor_step());
}
pos = CLAMP(pos, 0, (double)anim->get_length() - CMP_EPSILON2); // Hack: Avoid fposmod with LOOP_LINEAR.
@ -1300,7 +1397,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_timeline_o
}
track_editor->set_anim_pos(pos);
};
}
void AnimationPlayerEditor::_animation_player_changed(Object *p_pl) {
_update_player();
@ -1408,11 +1505,17 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_timel
}
updating = true;
frame->set_value(Math::snapped(p_pos, _get_editor_step()));
frame->set_value(track_editor->is_snap_timeline_enabled() ? Math::snapped(p_pos, _get_editor_step()) : p_pos);
updating = false;
_seek_value_changed(p_pos, p_timeline_only);
}
void AnimationPlayerEditor::_animation_update_key_frame() {
if (player) {
player->advance(0);
}
}
void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
String current = _get_current();
@ -1505,30 +1608,28 @@ void AnimationPlayerEditor::shortcut_input(const Ref<InputEvent> &p_ev) {
ERR_FAIL_COND(p_ev.is_null());
Ref<InputEventKey> k = p_ev;
if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo() && !k->is_alt_pressed() && !k->is_ctrl_pressed() && !k->is_meta_pressed()) {
switch (k->get_keycode()) {
case Key::A: {
if (!k->is_shift_pressed()) {
_play_bw_from_pressed();
} else {
_play_bw_pressed();
}
accept_event();
} break;
case Key::S: {
_stop_pressed();
accept_event();
} break;
case Key::D: {
if (!k->is_shift_pressed()) {
_play_from_pressed();
} else {
_play_pressed();
}
accept_event();
} break;
default:
break;
if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo()) {
if (ED_IS_SHORTCUT("animation_editor/stop_animation", p_ev)) {
_stop_pressed();
accept_event();
} else if (ED_IS_SHORTCUT("animation_editor/play_animation", p_ev)) {
_play_from_pressed();
accept_event();
} else if (ED_IS_SHORTCUT("animation_editor/play_animation_backwards", p_ev)) {
_play_bw_from_pressed();
accept_event();
} else if (ED_IS_SHORTCUT("animation_editor/play_animation_from_start", p_ev)) {
_play_pressed();
accept_event();
} else if (ED_IS_SHORTCUT("animation_editor/play_animation_from_end", p_ev)) {
_play_bw_pressed();
accept_event();
} else if (ED_IS_SHORTCUT("animation_editor/go_to_next_keyframe", p_ev)) {
_go_to_nearest_keyframe(false);
accept_event();
} else if (ED_IS_SHORTCUT("animation_editor/go_to_previous_keyframe", p_ev)) {
_go_to_nearest_keyframe(true);
accept_event();
}
}
}
@ -1612,7 +1713,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_1() {
void AnimationPlayerEditor::_prepare_onion_layers_2_prolog() {
Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
if (!anim.is_valid()) {
if (anim.is_null()) {
return;
}
@ -1824,9 +1925,13 @@ AnimationMixer *AnimationPlayerEditor::fetch_mixer_for_library() const {
return original_node;
}
Node *AnimationPlayerEditor::get_cached_root_node() const {
return Object::cast_to<Node>(ObjectDB::get_instance(cached_root_node_id));
}
bool AnimationPlayerEditor::_validate_tracks(const Ref<Animation> p_anim) {
bool is_valid = true;
if (!p_anim.is_valid()) {
if (p_anim.is_null()) {
return true; // There is a problem outside of the animation track.
}
int len = p_anim->get_track_count();
@ -1874,6 +1979,7 @@ bool AnimationPlayerEditor::_validate_tracks(const Ref<Animation> p_anim) {
void AnimationPlayerEditor::_bind_methods() {
// Needed for UndoRedo.
ClassDB::bind_method(D_METHOD("_animation_player_changed"), &AnimationPlayerEditor::_animation_player_changed);
ClassDB::bind_method(D_METHOD("_animation_update_key_frame"), &AnimationPlayerEditor::_animation_update_key_frame);
ClassDB::bind_method(D_METHOD("_start_onion_skinning"), &AnimationPlayerEditor::_start_onion_skinning);
ClassDB::bind_method(D_METHOD("_stop_onion_skinning"), &AnimationPlayerEditor::_stop_onion_skinning);
@ -1900,30 +2006,34 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
HBoxContainer *hb = memnew(HBoxContainer);
add_child(hb);
HBoxContainer *playback_container = memnew(HBoxContainer);
playback_container->set_layout_direction(LAYOUT_DIRECTION_LTR);
hb->add_child(playback_container);
play_bw_from = memnew(Button);
play_bw_from->set_theme_type_variation("FlatButton");
play_bw_from->set_tooltip_text(TTR("Play selected animation backwards from current pos. (A)"));
hb->add_child(play_bw_from);
play_bw_from->set_theme_type_variation(SceneStringName(FlatButton));
play_bw_from->set_tooltip_text(TTR("Play Animation Backwards"));
playback_container->add_child(play_bw_from);
play_bw = memnew(Button);
play_bw->set_theme_type_variation("FlatButton");
play_bw->set_tooltip_text(TTR("Play selected animation backwards from end. (Shift+A)"));
hb->add_child(play_bw);
play_bw->set_theme_type_variation(SceneStringName(FlatButton));
play_bw->set_tooltip_text(TTR("Play Animation Backwards from End"));
playback_container->add_child(play_bw);
stop = memnew(Button);
stop->set_theme_type_variation("FlatButton");
hb->add_child(stop);
stop->set_tooltip_text(TTR("Pause/stop animation playback. (S)"));
stop->set_theme_type_variation(SceneStringName(FlatButton));
stop->set_tooltip_text(TTR("Pause/Stop Animation"));
playback_container->add_child(stop);
play = memnew(Button);
play->set_theme_type_variation("FlatButton");
play->set_tooltip_text(TTR("Play selected animation from start. (Shift+D)"));
hb->add_child(play);
play->set_theme_type_variation(SceneStringName(FlatButton));
play->set_tooltip_text(TTR("Play Animation from Start"));
playback_container->add_child(play);
play_from = memnew(Button);
play_from->set_theme_type_variation("FlatButton");
play_from->set_tooltip_text(TTR("Play selected animation from current pos. (D)"));
hb->add_child(play_from);
play_from->set_theme_type_variation(SceneStringName(FlatButton));
play_from->set_tooltip_text(TTR("Play Animation"));
playback_container->add_child(play_from);
frame = memnew(SpinBox);
hb->add_child(frame);
@ -1950,17 +2060,17 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
tool_anim->set_flat(false);
tool_anim->set_tooltip_text(TTR("Animation Tools"));
tool_anim->set_text(TTR("Animation"));
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/new_animation", TTR("New...")), TOOL_NEW_ANIM);
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/new_animation", TTRC("New...")), TOOL_NEW_ANIM);
tool_anim->get_popup()->add_separator();
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/animation_libraries", TTR("Manage Animations...")), TOOL_ANIM_LIBRARY);
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/animation_libraries", TTRC("Manage Animations...")), TOOL_ANIM_LIBRARY);
tool_anim->get_popup()->add_separator();
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/duplicate_animation", TTR("Duplicate...")), TOOL_DUPLICATE_ANIM);
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/duplicate_animation", TTRC("Duplicate...")), TOOL_DUPLICATE_ANIM);
tool_anim->get_popup()->add_separator();
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/rename_animation", TTR("Rename...")), TOOL_RENAME_ANIM);
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/edit_transitions", TTR("Edit Transitions...")), TOOL_EDIT_TRANSITIONS);
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation_in_inspector", TTR("Open in Inspector")), TOOL_EDIT_RESOURCE);
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/rename_animation", TTRC("Rename...")), TOOL_RENAME_ANIM);
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/edit_transitions", TTRC("Edit Transitions...")), TOOL_EDIT_TRANSITIONS);
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation_in_inspector", TTRC("Open in Inspector")), TOOL_EDIT_RESOURCE);
tool_anim->get_popup()->add_separator();
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/remove_animation", TTR("Remove")), TOOL_REMOVE_ANIM);
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/remove_animation", TTRC("Remove")), TOOL_REMOVE_ANIM);
tool_anim->set_disabled(true);
hb->add_child(tool_anim);
@ -1972,7 +2082,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
animation->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
autoplay = memnew(Button);
autoplay->set_theme_type_variation("FlatButton");
autoplay->set_theme_type_variation(SceneStringName(FlatButton));
hb->add_child(autoplay);
autoplay->set_tooltip_text(TTR("Autoplay on Load"));
@ -1984,7 +2094,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
hb->add_child(memnew(VSeparator));
onion_toggle = memnew(Button);
onion_toggle->set_theme_type_variation("FlatButton");
onion_toggle->set_theme_type_variation(SceneStringName(FlatButton));
onion_toggle->set_toggle_mode(true);
onion_toggle->set_tooltip_text(TTR("Enable Onion Skinning"));
onion_toggle->connect(SceneStringName(pressed), callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu).bind(ONION_SKINNING_ENABLE));
@ -2014,7 +2124,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
hb->add_child(memnew(VSeparator));
pin = memnew(Button);
pin->set_theme_type_variation("FlatButton");
pin->set_theme_type_variation(SceneStringName(FlatButton));
pin->set_toggle_mode(true);
pin->set_tooltip_text(TTR("Pin AnimationPlayer"));
hb->add_child(pin);
@ -2085,7 +2195,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
animation->connect(SceneStringName(item_selected), callable_mp(this, &AnimationPlayerEditor::_animation_selected));
frame->connect(SceneStringName(value_changed), callable_mp(this, &AnimationPlayerEditor::_seek_value_changed).bind(false));
scale->connect(SNAME("text_submitted"), callable_mp(this, &AnimationPlayerEditor::_scale_changed));
scale->connect(SceneStringName(text_submitted), callable_mp(this, &AnimationPlayerEditor::_scale_changed));
add_child(track_editor);
track_editor->set_v_size_flags(SIZE_EXPAND_FILL);
@ -2138,6 +2248,14 @@ void fragment() {
}
)");
RS::get_singleton()->material_set_shader(onion.capture.material->get_rid(), onion.capture.shader->get_rid());
ED_SHORTCUT("animation_editor/stop_animation", TTRC("Pause/Stop Animation"), Key::S);
ED_SHORTCUT("animation_editor/play_animation", TTRC("Play Animation"), Key::D);
ED_SHORTCUT("animation_editor/play_animation_backwards", TTRC("Play Animation Backwards"), Key::A);
ED_SHORTCUT("animation_editor/play_animation_from_start", TTRC("Play Animation from Start"), KeyModifierMask::SHIFT + Key::D);
ED_SHORTCUT("animation_editor/play_animation_from_end", TTRC("Play Animation Backwards from End"), KeyModifierMask::SHIFT + Key::A);
ED_SHORTCUT("animation_editor/go_to_next_keyframe", TTRC("Go to Next Keyframe"), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::D);
ED_SHORTCUT("animation_editor/go_to_previous_keyframe", TTRC("Go to Previous Keyframe"), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::A);
}
AnimationPlayerEditor::~AnimationPlayerEditor() {
@ -2165,7 +2283,7 @@ void AnimationPlayerEditorPlugin::_property_keyed(const String &p_keyed, const V
return;
}
te->_clear_selection();
te->insert_value_key(p_keyed, p_value, p_advance);
te->insert_value_key(p_keyed, p_advance);
}
void AnimationPlayerEditorPlugin::_transform_key_request(Object *sp, const String &p_sub, const Transform3D &p_key) {
@ -2281,7 +2399,7 @@ void AnimationPlayerEditorPlugin::make_visible(bool p_visible) {
AnimationPlayerEditorPlugin::AnimationPlayerEditorPlugin() {
anim_editor = memnew(AnimationPlayerEditor(this));
EditorNode::get_bottom_panel()->add_item(TTR("Animation"), anim_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_animation_bottom_panel", TTR("Toggle Animation Bottom Panel"), KeyModifierMask::ALT | Key::N));
EditorNode::get_bottom_panel()->add_item(TTR("Animation"), anim_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_animation_bottom_panel", TTRC("Toggle Animation Bottom Panel"), KeyModifierMask::ALT | Key::N));
}
AnimationPlayerEditorPlugin::~AnimationPlayerEditorPlugin() {
@ -2312,3 +2430,24 @@ AnimationTrackKeyEditEditorPlugin::AnimationTrackKeyEditEditorPlugin() {
bool AnimationTrackKeyEditEditorPlugin::handles(Object *p_object) const {
return p_object->is_class("AnimationTrackKeyEdit");
}
bool EditorInspectorPluginAnimationMarkerKeyEdit::can_handle(Object *p_object) {
return Object::cast_to<AnimationMarkerKeyEdit>(p_object) != nullptr;
}
void EditorInspectorPluginAnimationMarkerKeyEdit::parse_begin(Object *p_object) {
AnimationMarkerKeyEdit *amk = Object::cast_to<AnimationMarkerKeyEdit>(p_object);
ERR_FAIL_NULL(amk);
amk_editor = memnew(AnimationMarkerKeyEditEditor(amk->animation, amk->marker_name, amk->use_fps));
add_custom_control(amk_editor);
}
AnimationMarkerKeyEditEditorPlugin::AnimationMarkerKeyEditEditorPlugin() {
amk_plugin = memnew(EditorInspectorPluginAnimationMarkerKeyEdit);
EditorInspector::add_inspector_plugin(amk_plugin);
}
bool AnimationMarkerKeyEditEditorPlugin::handles(Object *p_object) const {
return p_object->is_class("AnimationMarkerKeyEdit");
}

View file

@ -52,6 +52,7 @@ class AnimationPlayerEditor : public VBoxContainer {
AnimationPlayerEditorPlugin *plugin = nullptr;
AnimationMixer *original_node = nullptr; // For pinned mark in SceneTree.
AnimationPlayer *player = nullptr; // For AnimationPlayerEditor, could be dummy.
ObjectID cached_root_node_id;
bool is_dummy = false;
enum {
@ -178,6 +179,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _select_anim_by_name(const String &p_anim);
float _get_editor_step() const;
void _go_to_nearest_keyframe(bool p_backward);
void _play_pressed();
void _play_from_pressed();
void _play_bw_pressed();
@ -216,6 +218,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _animation_key_editor_seek(float p_pos, bool p_timeline_only = false, bool p_update_position_only = false);
void _animation_key_editor_anim_len_changed(float p_len);
void _animation_update_key_frame();
virtual void shortcut_input(const Ref<InputEvent> &p_ev) override;
void _animation_tool_menu(int p_option);
@ -251,6 +254,7 @@ public:
AnimationMixer *get_editing_node() const;
AnimationPlayer *get_player() const;
AnimationMixer *fetch_mixer_for_library() const;
Node *get_cached_root_node() const;
static AnimationPlayerEditor *get_singleton() { return singleton; }
@ -295,7 +299,7 @@ public:
virtual Dictionary get_state() const override { return anim_editor->get_state(); }
virtual void set_state(const Dictionary &p_state) override { anim_editor->set_state(p_state); }
virtual String get_name() const override { return "Anim"; }
virtual String get_plugin_name() const override { return "Anim"; }
bool has_main_screen() const override { return false; }
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;
@ -329,9 +333,35 @@ public:
bool has_main_screen() const override { return false; }
virtual bool handles(Object *p_object) const override;
virtual String get_name() const override { return "AnimationTrackKeyEdit"; }
virtual String get_plugin_name() const override { return "AnimationTrackKeyEdit"; }
AnimationTrackKeyEditEditorPlugin();
};
// AnimationMarkerKeyEditEditorPlugin
class EditorInspectorPluginAnimationMarkerKeyEdit : public EditorInspectorPlugin {
GDCLASS(EditorInspectorPluginAnimationMarkerKeyEdit, EditorInspectorPlugin);
AnimationMarkerKeyEditEditor *amk_editor = nullptr;
public:
virtual bool can_handle(Object *p_object) override;
virtual void parse_begin(Object *p_object) override;
};
class AnimationMarkerKeyEditEditorPlugin : public EditorPlugin {
GDCLASS(AnimationMarkerKeyEditEditorPlugin, EditorPlugin);
EditorInspectorPluginAnimationMarkerKeyEdit *amk_plugin = nullptr;
public:
bool has_main_screen() const override { return false; }
virtual bool handles(Object *p_object) const override;
virtual String get_plugin_name() const override { return "AnimationMarkerKeyEdit"; }
AnimationMarkerKeyEditEditorPlugin();
};
#endif // ANIMATION_PLAYER_EDITOR_PLUGIN_H

View file

@ -30,8 +30,6 @@
#include "animation_state_machine_editor.h"
#include "core/config/project_settings.h"
#include "core/input/input.h"
#include "core/io/resource_loader.h"
#include "core/math/geometry_2d.h"
#include "core/os/keyboard.h"
@ -41,10 +39,7 @@
#include "editor/gui/editor_file_dialog.h"
#include "editor/themes/editor_scale.h"
#include "scene/animation/animation_blend_tree.h"
#include "scene/animation/animation_player.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/option_button.h"
#include "scene/gui/panel.h"
#include "scene/gui/panel_container.h"
#include "scene/gui/separator.h"
#include "scene/gui/tree.h"
@ -71,6 +66,7 @@ void AnimationNodeStateMachineEditor::edit(const Ref<AnimationNode> &p_node) {
selected_transition_index = -1;
selected_node = StringName();
selected_nodes.clear();
connected_nodes.clear();
_update_mode();
_update_graph();
}
@ -97,7 +93,7 @@ String AnimationNodeStateMachineEditor::_get_root_playback_path(String &r_node_d
while (!is_playable_anodesm_found) {
base_path = String("/").join(edited_path);
Ref<AnimationNodeStateMachine> anodesm = !edited_path.size() ? Ref<AnimationNode>(tree->get_root_animation_node().ptr()) : tree->get_root_animation_node()->find_node_by_path(base_path);
if (!anodesm.is_valid()) {
if (anodesm.is_null()) {
break;
} else {
if (anodesm->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
@ -137,7 +133,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
String node_directory;
Ref<AnimationNodeStateMachinePlayback> playback = tree->get(_get_root_playback_path(node_directory));
if (!playback.is_valid()) {
if (playback.is_null()) {
return;
}
@ -214,6 +210,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
}
selected_nodes.insert(selected_node);
_update_connected_nodes(selected_node);
Ref<AnimationNode> anode = state_machine->get_node(selected_node);
EditorNode::get_singleton()->push_item(anode.ptr(), "", true);
@ -228,9 +225,12 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
}
}
//test the lines now
// Test the transition lines.
int closest = -1;
float closest_d = 1e20;
Vector<int> close_candidates;
// First find closest lines using point-to-segment distance.
for (int i = 0; i < transition_lines.size(); i++) {
Vector2 s[2] = {
transition_lines[i].from,
@ -238,13 +238,34 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
};
Vector2 cpoint = Geometry2D::get_closest_point_to_segment(mb->get_position(), s);
float d = cpoint.distance_to(mb->get_position());
if (d > transition_lines[i].width) {
continue;
}
if (d < closest_d) {
closest = i;
// If this is very close to our current closest distance, add it to candidates.
if (Math::abs(d - closest_d) < 2.0) { // Within 2 pixels.
close_candidates.push_back(i);
} else if (d < closest_d) {
closest_d = d;
closest = i;
close_candidates.clear();
close_candidates.push_back(i);
}
}
// Use midpoint distance as bias.
if (close_candidates.size() > 1) {
float best_midpoint_dist = 1e20;
for (int idx : close_candidates) {
Vector2 midpoint = (transition_lines[idx].from + transition_lines[idx].to) / 2.0;
float midpoint_dist = midpoint.distance_to(mb->get_position());
if (midpoint_dist < best_midpoint_dist) {
best_midpoint_dist = midpoint_dist;
closest = idx;
}
}
}
@ -253,6 +274,11 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
selected_transition_to = transition_lines[closest].to_node;
selected_transition_index = closest;
// Update connected_nodes for the selected transition.
connected_nodes.clear();
connected_nodes.insert(selected_transition_from);
connected_nodes.insert(selected_transition_to);
Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(closest);
if (!state_machine->is_transition_across_group(closest)) {
EditorNode::get_singleton()->push_item(tr.ptr(), "", true);
@ -318,7 +344,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
Ref<AnimationNodeStateMachine> anodesm = node;
Ref<AnimationNodeEndState> end_node = node;
if (state_machine->has_transition(connecting_from, connecting_to_node) && state_machine->can_edit_node(connecting_to_node) && !anodesm.is_valid()) {
if (state_machine->has_transition(connecting_from, connecting_to_node) && state_machine->can_edit_node(connecting_to_node) && anodesm.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("Transition exists!"));
connecting = false;
} else {
@ -353,6 +379,48 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
_update_mode();
}
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
StringName clicked_node;
for (int i = node_rects.size() - 1; i >= 0; i--) {
if (node_rects[i].node.has_point(mb->get_position())) {
clicked_node = node_rects[i].node_name;
break;
}
}
if (clicked_node != StringName()) {
if (selected_nodes.has(clicked_node) && mb->is_shift_pressed()) {
selected_nodes.erase(clicked_node);
} else {
if (!mb->is_shift_pressed()) {
selected_nodes.clear();
}
selected_nodes.insert(clicked_node);
}
selected_node = clicked_node;
} else {
// Clicked on empty space.
selected_nodes.clear();
selected_node = StringName();
}
_update_connected_nodes(selected_node);
state_machine_draw->queue_redraw();
_update_mode();
if (clicked_node != StringName()) {
Ref<AnimationNode> anode = state_machine->get_node(clicked_node);
EditorNode::get_singleton()->push_item(anode.ptr(), "", true);
dragging_selected_attempt = true;
dragging_selected = false;
drag_from = mb->get_position();
snap_x = StringName();
snap_y = StringName();
}
return;
}
Ref<InputEventMouseMotion> mm = p_event;
// Pan window
@ -618,7 +686,7 @@ bool AnimationNodeStateMachineEditor::_create_submenu(PopupMenu *p_menu, Ref<Ani
if (ansm == state_machine) {
end_menu->add_item(E, nodes_to_connect.size());
nodes_to_connect.push_back(AnimationNodeStateMachine::END_NODE);
nodes_to_connect.push_back(SceneStringName(End));
continue;
}
@ -730,7 +798,7 @@ void AnimationNodeStateMachineEditor::_add_menu_type(int p_index) {
base_name = type.replace_first("AnimationNode", "");
}
if (!node.is_valid()) {
if (node.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("This type of node can't be used. Only root nodes are allowed."));
return;
}
@ -836,11 +904,15 @@ void AnimationNodeStateMachineEditor::_add_transition(const bool p_nested_action
connecting = false;
}
void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_is_across_group) {
void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_is_across_group, float p_opacity) {
Color line_color = p_enabled ? theme_cache.transition_color : theme_cache.transition_disabled_color;
Color icon_color = p_enabled ? theme_cache.transition_icon_color : theme_cache.transition_icon_disabled_color;
Color highlight_color = p_enabled ? theme_cache.highlight_color : theme_cache.highlight_disabled_color;
line_color.a *= p_opacity;
icon_color.a *= p_opacity;
highlight_color.a *= p_opacity;
if (p_travel) {
line_color = highlight_color;
}
@ -853,6 +925,7 @@ void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, co
if (p_fade_ratio > 0.0) {
Color fade_line_color = highlight_color;
fade_line_color.set_hsv(1.0, fade_line_color.get_s(), fade_line_color.get_v());
fade_line_color.a *= p_opacity;
state_machine_draw->draw_line(p_from, p_from.lerp(p_to, p_fade_ratio), fade_line_color, 2);
}
@ -897,6 +970,25 @@ void AnimationNodeStateMachineEditor::_clip_dst_line_to_rect(const Vector2 &p_fr
}
}
Ref<StyleBox> AnimationNodeStateMachineEditor::_adjust_stylebox_opacity(Ref<StyleBox> p_style, float p_opacity) {
Ref<StyleBox> style = p_style->duplicate();
if (style->is_class("StyleBoxFlat")) {
Ref<StyleBoxFlat> flat_style = style;
Color bg_color = flat_style->get_bg_color();
Color border_color = flat_style->get_border_color();
Color shadow_color = flat_style->get_shadow_color();
bg_color.a *= p_opacity;
border_color.a *= p_opacity;
shadow_color.a *= p_opacity;
flat_style->set_bg_color(bg_color);
flat_style->set_border_color(border_color);
flat_style->set_shadow_color(shadow_color);
}
return style;
}
void AnimationNodeStateMachineEditor::_state_machine_draw() {
AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
if (!tree) {
@ -917,7 +1009,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
}
if (state_machine_draw->has_focus()) {
state_machine_draw->draw_rect(Rect2(Point2(), state_machine_draw->get_size()), theme_cache.highlight_color, false);
state_machine_draw->draw_rect(Rect2(Point2(), state_machine_draw->get_size()), theme_cache.focus_color, false);
}
int sep = 3 * EDSCALE;
@ -1088,7 +1180,30 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
for (int i = 0; i < transition_lines.size(); i++) {
TransitionLine tl = transition_lines[i];
if (!tl.hidden) {
_connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, tl.selected, tl.travel, tl.fade_ratio, tl.auto_advance, tl.is_across_group);
float opacity = 0.2; // Default to reduced opacity.
if (selected_transition_from != StringName() && selected_transition_to != StringName()) {
// A transition is selected.
if ((tl.from_node == selected_transition_from && tl.to_node == selected_transition_to) || (tl.from_node == selected_transition_to && tl.to_node == selected_transition_from)) {
opacity = 1.0; // Full opacity for the selected transition pair.
}
} else if (!connected_nodes.is_empty()) {
// A node is selected.
if (connected_nodes.has(selected_node)) {
// Only keep full opacity for transitions directly connected to the selected node.
if (tl.from_node == selected_node || tl.to_node == selected_node) {
opacity = 1.0;
}
} else {
// If no node is selected, all transitions are at full opacity.
opacity = 1.0;
}
} else {
// If nothing is selected, all transitions are at full opacity.
opacity = 1.0;
}
_connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, tl.selected, tl.travel, tl.fade_ratio, tl.auto_advance, tl.is_across_group, opacity);
}
}
@ -1105,31 +1220,45 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
Vector2 offset = nr.node.position;
int h = nr.node.size.height;
//prepre rect
//now scroll it to draw
Ref<StyleBox> node_frame_style = is_selected ? theme_cache.node_frame_selected : theme_cache.node_frame;
state_machine_draw->draw_style_box(node_frame_style, nr.node);
if (!is_selected && AnimationNodeStateMachine::START_NODE == name) {
state_machine_draw->draw_style_box(theme_cache.node_frame_start, nr.node);
float opacity = 1.0;
if (selected_transition_from != StringName() && selected_transition_to != StringName()) {
// A transition is selected.
if (name != selected_transition_from && name != selected_transition_to) {
opacity = 0.2;
}
} else if (!connected_nodes.is_empty() && !connected_nodes.has(name)) {
// A node is selected.
opacity = 0.2;
}
if (!is_selected && AnimationNodeStateMachine::END_NODE == name) {
state_machine_draw->draw_style_box(theme_cache.node_frame_end, nr.node);
Ref<StyleBox> original_style = is_selected ? theme_cache.node_frame_selected : theme_cache.node_frame;
Ref<StyleBox> node_style = _adjust_stylebox_opacity(original_style, opacity);
state_machine_draw->draw_style_box(node_style, nr.node);
if (!is_selected && SceneStringName(Start) == name) {
Ref<StyleBox> start_style = _adjust_stylebox_opacity(theme_cache.node_frame_start, opacity);
state_machine_draw->draw_style_box(start_style, nr.node);
}
if (!is_selected && SceneStringName(End) == name) {
Ref<StyleBox> end_style = _adjust_stylebox_opacity(theme_cache.node_frame_end, opacity);
state_machine_draw->draw_style_box(end_style, nr.node);
}
if (playing && (blend_from == name || current == name || travel_path.has(name))) {
state_machine_draw->draw_style_box(theme_cache.node_frame_playing, nr.node);
Ref<StyleBox> playing_style = _adjust_stylebox_opacity(theme_cache.node_frame_playing, opacity);
state_machine_draw->draw_style_box(playing_style, nr.node);
}
offset.x += node_frame_style->get_offset().x;
offset.x += original_style->get_offset().x;
nr.play.position = offset + Vector2(0, (h - theme_cache.play_node->get_height()) / 2).floor();
nr.play.size = theme_cache.play_node->get_size();
Color color_mod = Color(1, 1, 1, opacity);
if (hovered_node_name == name && hovered_node_area == HOVER_NODE_PLAY) {
state_machine_draw->draw_texture(theme_cache.play_node, nr.play.position, theme_cache.highlight_color);
state_machine_draw->draw_texture(theme_cache.play_node, nr.play.position, theme_cache.highlight_color * color_mod);
} else {
state_machine_draw->draw_texture(theme_cache.play_node, nr.play.position);
state_machine_draw->draw_texture(theme_cache.play_node, nr.play.position, color_mod);
}
offset.x += sep + theme_cache.play_node->get_width();
@ -1137,7 +1266,9 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
nr.name.position = offset + Vector2(0, (h - theme_cache.node_title_font->get_height(theme_cache.node_title_font_size)) / 2).floor();
nr.name.size = Vector2(name_string_size, theme_cache.node_title_font->get_height(theme_cache.node_title_font_size));
state_machine_draw->draw_string(theme_cache.node_title_font, nr.name.position + Vector2(0, theme_cache.node_title_font->get_ascent(theme_cache.node_title_font_size)), name, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.node_title_font_size, theme_cache.node_title_font_color);
Color font_color = theme_cache.node_title_font_color;
font_color.a *= opacity;
state_machine_draw->draw_string(theme_cache.node_title_font, nr.name.position + Vector2(0, theme_cache.node_title_font->get_ascent(theme_cache.node_title_font_size)), name, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.node_title_font_size, font_color);
offset.x += name_string_size + sep;
nr.can_edit = needs_editor;
@ -1146,9 +1277,9 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
nr.edit.size = theme_cache.edit_node->get_size();
if (hovered_node_name == name && hovered_node_area == HOVER_NODE_EDIT) {
state_machine_draw->draw_texture(theme_cache.edit_node, nr.edit.position, theme_cache.highlight_color);
state_machine_draw->draw_texture(theme_cache.edit_node, nr.edit.position, theme_cache.highlight_color * color_mod);
} else {
state_machine_draw->draw_texture(theme_cache.edit_node, nr.edit.position);
state_machine_draw->draw_texture(theme_cache.edit_node, nr.edit.position, color_mod);
}
}
}
@ -1177,6 +1308,23 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
state_machine_play_pos->queue_redraw();
}
void AnimationNodeStateMachineEditor::_update_connected_nodes(const StringName &p_node) {
connected_nodes.clear();
if (p_node != StringName()) {
connected_nodes.insert(p_node);
Vector<StringName> nodes_to = state_machine->get_nodes_with_transitions_to(p_node);
for (const StringName &node_to : nodes_to) {
connected_nodes.insert(node_to);
}
Vector<StringName> nodes_from = state_machine->get_nodes_with_transitions_from(p_node);
for (const StringName &node_from : nodes_from) {
connected_nodes.insert(node_from);
}
}
}
void AnimationNodeStateMachineEditor::_state_machine_pos_draw_individual(const String &p_name, float p_ratio) {
AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
if (!tree) {
@ -1184,11 +1332,11 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw_individual(const S
}
Ref<AnimationNodeStateMachinePlayback> playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback");
if (!playback.is_valid() || !playback->is_playing()) {
if (playback.is_null() || !playback->is_playing()) {
return;
}
if (p_name == AnimationNodeStateMachine::START_NODE || p_name == AnimationNodeStateMachine::END_NODE || p_name.is_empty()) {
if (p_name == SceneStringName(Start) || p_name == SceneStringName(End) || p_name.is_empty()) {
return;
}
@ -1233,7 +1381,7 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw_all() {
}
Ref<AnimationNodeStateMachinePlayback> playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback");
if (!playback.is_valid() || !playback->is_playing()) {
if (playback.is_null() || !playback->is_playing()) {
return;
}
@ -1271,18 +1419,18 @@ void AnimationNodeStateMachineEditor::_notification(int p_what) {
error_panel->add_theme_style_override(SceneStringName(panel), theme_cache.error_panel_style);
error_label->add_theme_color_override(SceneStringName(font_color), theme_cache.error_color);
tool_select->set_icon(theme_cache.tool_icon_select);
tool_create->set_icon(theme_cache.tool_icon_create);
tool_connect->set_icon(theme_cache.tool_icon_connect);
tool_select->set_button_icon(theme_cache.tool_icon_select);
tool_create->set_button_icon(theme_cache.tool_icon_create);
tool_connect->set_button_icon(theme_cache.tool_icon_connect);
switch_mode->clear();
switch_mode->add_icon_item(theme_cache.transition_icon_immediate, TTR("Immediate"));
switch_mode->add_icon_item(theme_cache.transition_icon_sync, TTR("Sync"));
switch_mode->add_icon_item(theme_cache.transition_icon_end, TTR("At End"));
auto_advance->set_icon(theme_cache.play_icon_auto);
auto_advance->set_button_icon(theme_cache.play_icon_auto);
tool_erase->set_icon(theme_cache.tool_icon_erase);
tool_erase->set_button_icon(theme_cache.tool_icon_erase);
play_mode->clear();
play_mode->add_icon_item(theme_cache.play_icon_travel, TTR("Travel"));
@ -1465,7 +1613,7 @@ void AnimationNodeStateMachineEditor::_open_editor(const String &p_name) {
void AnimationNodeStateMachineEditor::_name_edited(const String &p_text) {
const String &new_name = p_text;
ERR_FAIL_COND(new_name.is_empty() || new_name.contains(".") || new_name.contains("/"));
ERR_FAIL_COND(new_name.is_empty() || new_name.contains_char('.') || new_name.contains_char('/'));
if (new_name == prev_name) {
return; // Nothing to do.
@ -1523,7 +1671,7 @@ void AnimationNodeStateMachineEditor::_erase_selected(const bool p_nested_action
undo_redo->create_action(TTR("Node Removed"));
for (int i = 0; i < node_rects.size(); i++) {
if (node_rects[i].node_name == AnimationNodeStateMachine::START_NODE || node_rects[i].node_name == AnimationNodeStateMachine::END_NODE) {
if (node_rects[i].node_name == SceneStringName(Start) || node_rects[i].node_name == SceneStringName(End)) {
continue;
}
@ -1583,7 +1731,7 @@ void AnimationNodeStateMachineEditor::_update_mode() {
if (tool_select->is_pressed()) {
selection_tools_hb->show();
bool nothing_selected = selected_nodes.is_empty() && selected_transition_from == StringName() && selected_transition_to == StringName();
bool start_end_selected = selected_nodes.size() == 1 && (*selected_nodes.begin() == AnimationNodeStateMachine::START_NODE || *selected_nodes.begin() == AnimationNodeStateMachine::END_NODE);
bool start_end_selected = selected_nodes.size() == 1 && (*selected_nodes.begin() == SceneStringName(Start) || *selected_nodes.begin() == SceneStringName(End));
tool_erase->set_disabled(nothing_selected || start_end_selected || read_only);
} else {
selection_tools_hb->hide();
@ -1642,6 +1790,7 @@ void AnimationNodeStateMachineEditor::_bind_methods() {
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, AnimationNodeStateMachineEditor, transition_icon_disabled_color, "transition_icon_disabled_color", "GraphStateMachine");
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, AnimationNodeStateMachineEditor, highlight_color, "highlight_color", "GraphStateMachine");
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, AnimationNodeStateMachineEditor, highlight_disabled_color, "highlight_disabled_color", "GraphStateMachine");
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, AnimationNodeStateMachineEditor, focus_color, "focus_color", "GraphStateMachine");
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, AnimationNodeStateMachineEditor, guideline_color, "guideline_color", "GraphStateMachine");
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_ICON, AnimationNodeStateMachineEditor, transition_icons[0], "TransitionImmediateBig", "EditorIcons");
@ -1667,7 +1816,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() {
bg.instantiate();
tool_select = memnew(Button);
tool_select->set_theme_type_variation("FlatButton");
tool_select->set_theme_type_variation(SceneStringName(FlatButton));
top_hb->add_child(tool_select);
tool_select->set_toggle_mode(true);
tool_select->set_button_group(bg);
@ -1676,7 +1825,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() {
tool_select->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeStateMachineEditor::_update_mode), CONNECT_DEFERRED);
tool_create = memnew(Button);
tool_create->set_theme_type_variation("FlatButton");
tool_create->set_theme_type_variation(SceneStringName(FlatButton));
top_hb->add_child(tool_create);
tool_create->set_toggle_mode(true);
tool_create->set_button_group(bg);
@ -1684,7 +1833,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() {
tool_create->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeStateMachineEditor::_update_mode), CONNECT_DEFERRED);
tool_connect = memnew(Button);
tool_connect->set_theme_type_variation("FlatButton");
tool_connect->set_theme_type_variation(SceneStringName(FlatButton));
top_hb->add_child(tool_connect);
tool_connect->set_toggle_mode(true);
tool_connect->set_button_group(bg);
@ -1697,7 +1846,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() {
selection_tools_hb->add_child(memnew(VSeparator));
tool_erase = memnew(Button);
tool_erase->set_theme_type_variation("FlatButton");
tool_erase->set_theme_type_variation(SceneStringName(FlatButton));
tool_erase->set_tooltip_text(TTR("Remove selected node or transition."));
tool_erase->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeStateMachineEditor::_erase_selected).bind(false));
tool_erase->set_disabled(true);
@ -1712,7 +1861,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() {
transition_tools_hb->add_child(switch_mode);
auto_advance = memnew(Button);
auto_advance->set_theme_type_variation("FlatButton");
auto_advance->set_theme_type_variation(SceneStringName(FlatButton));
auto_advance->set_tooltip_text(TTR("New Transitions Should Auto Advance"));
auto_advance->set_toggle_mode(true);
auto_advance->set_pressed(true);
@ -1794,7 +1943,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() {
name_edit = memnew(LineEdit);
name_edit_popup->add_child(name_edit);
name_edit->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
name_edit->connect("text_submitted", callable_mp(this, &AnimationNodeStateMachineEditor::_name_edited));
name_edit->connect(SceneStringName(text_submitted), callable_mp(this, &AnimationNodeStateMachineEditor::_name_edited));
name_edit->connect(SceneStringName(focus_exited), callable_mp(this, &AnimationNodeStateMachineEditor::_name_edited_focus_out));
open_file = memnew(EditorFileDialog);

View file

@ -117,6 +117,7 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
Color transition_icon_disabled_color;
Color highlight_color;
Color highlight_disabled_color;
Color focus_color;
Color guideline_color;
Ref<Texture2D> transition_icons[6]{};
@ -130,7 +131,7 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
static AnimationNodeStateMachineEditor *singleton;
void _state_machine_gui_input(const Ref<InputEvent> &p_event);
void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_is_across_group);
void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_is_across_group, float p_opacity = 1.0);
void _state_machine_draw();
@ -287,6 +288,11 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
MENU_LOAD_FILE_CONFIRM = 1002
};
HashSet<StringName> connected_nodes;
void _update_connected_nodes(const StringName &p_node);
Ref<StyleBox> _adjust_stylebox_opacity(Ref<StyleBox> p_style, float p_opacity);
protected:
void _notification(int p_what);
static void _bind_methods();
@ -322,7 +328,7 @@ protected:
public:
void add_transition(const StringName &p_from, const StringName &p_to, Ref<AnimationNodeStateMachineTransition> p_transition);
EditorAnimationMultiTransitionEdit(){};
EditorAnimationMultiTransitionEdit() {}
};
#endif // ANIMATION_STATE_MACHINE_EDITOR_H

View file

@ -199,9 +199,6 @@ void AnimationTreeEditor::_notification(int p_what) {
}
}
void AnimationTreeEditor::_bind_methods() {
}
AnimationTreeEditor *AnimationTreeEditor::singleton = nullptr;
void AnimationTreeEditor::add_plugin(AnimationTreeNodeEditorPlugin *p_editor) {
@ -237,7 +234,8 @@ bool AnimationTreeEditor::can_edit(const Ref<AnimationNode> &p_node) const {
}
Vector<String> AnimationTreeEditor::get_animation_list() {
if (!singleton->tree || !singleton->is_visible()) {
// This can be called off the main thread due to resource preview generation. Quit early in that case.
if (!singleton->tree || !Thread::is_main_thread() || !singleton->is_visible()) {
// When tree is empty, singleton not in the main thread.
return Vector<String>();
}
@ -307,7 +305,7 @@ AnimationTreeEditorPlugin::AnimationTreeEditorPlugin() {
anim_tree_editor = memnew(AnimationTreeEditor);
anim_tree_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE);
button = EditorNode::get_bottom_panel()->add_item(TTR("AnimationTree"), anim_tree_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_animation_tree_bottom_panel", TTR("Toggle AnimationTree Bottom Panel")));
button = EditorNode::get_bottom_panel()->add_item(TTR("AnimationTree"), anim_tree_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_animation_tree_bottom_panel", TTRC("Toggle AnimationTree Bottom Panel")));
button->hide();
}

View file

@ -72,7 +72,6 @@ class AnimationTreeEditor : public VBoxContainer {
protected:
void _notification(int p_what);
void _node_removed(Node *p_node);
static void _bind_methods();
static AnimationTreeEditor *singleton;
@ -101,7 +100,7 @@ class AnimationTreeEditorPlugin : public EditorPlugin {
Button *button = nullptr;
public:
virtual String get_name() const override { return "AnimationTree"; }
virtual String get_plugin_name() const override { return "AnimationTree"; }
bool has_main_screen() const override { return false; }
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;

View file

@ -30,11 +30,11 @@
#include "asset_library_editor_plugin.h"
#include "core/input/input.h"
#include "core/io/json.h"
#include "core/io/stream_peer_tls.h"
#include "core/os/keyboard.h"
#include "core/version.h"
#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
@ -43,10 +43,11 @@
#include "editor/project_settings_editor.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/separator.h"
#include "scene/resources/image_texture.h"
static inline void setup_http_request(HTTPRequest *request) {
request->set_use_threads(EDITOR_DEF("asset_library/use_threads", true));
request->set_use_threads(EDITOR_GET("asset_library/use_threads"));
const String proxy_host = EDITOR_GET("network/http_proxy/host");
const int proxy_port = EDITOR_GET("network/http_proxy/port");
@ -211,12 +212,12 @@ void EditorAssetLibraryItemDescription::set_image(int p_type, int p_index, const
// Overlay and thumbnail need the same format for `blend_rect` to work.
thumbnail->convert(Image::FORMAT_RGBA8);
thumbnail->blend_rect(overlay, overlay->get_used_rect(), overlay_pos);
preview_images[i].button->set_icon(ImageTexture::create_from_image(thumbnail));
preview_images[i].button->set_button_icon(ImageTexture::create_from_image(thumbnail));
// Make it clearer that clicking it will open an external link
preview_images[i].button->set_default_cursor_shape(Control::CURSOR_POINTING_HAND);
} else {
preview_images[i].button->set_icon(p_image);
preview_images[i].button->set_button_icon(p_image);
}
break;
}
@ -301,7 +302,7 @@ void EditorAssetLibraryItemDescription::add_preview(int p_id, bool p_video, cons
new_preview.video_link = p_url;
new_preview.is_video = p_video;
new_preview.button = memnew(Button);
new_preview.button->set_icon(previews->get_editor_theme_icon(SNAME("ThumbnailWait")));
new_preview.button->set_button_icon(previews->get_editor_theme_icon(SNAME("ThumbnailWait")));
new_preview.button->set_toggle_mode(true);
new_preview.button->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItemDescription::_preview_click).bind(p_id));
preview_hb->add_child(new_preview.button);
@ -445,7 +446,7 @@ void EditorAssetLibraryItemDownload::configure(const String &p_title, int p_asse
title->set_text(p_title);
icon->set_texture(p_preview);
asset_id = p_asset_id;
if (!p_preview.is_valid()) {
if (p_preview.is_null()) {
icon->set_texture(get_editor_theme_icon(SNAME("FileBrokenBigThumb")));
}
host = p_download_url;
@ -702,6 +703,7 @@ void EditorAssetLibrary::_notification(int p_what) {
}
void EditorAssetLibrary::_update_repository_options() {
// TODO: Move to editor_settings.cpp
Dictionary default_urls;
default_urls["godotengine.org (Official)"] = "https://godotengine.org/asset-library/api";
Dictionary available_urls = _EDITOR_DEF("asset_library/available_urls", default_urls, true);
@ -905,7 +907,7 @@ void EditorAssetLibrary::_image_request_completed(int p_status, int p_code, cons
for (int i = 0; i < headers.size(); i++) {
if (headers[i].findn("ETag:") == 0) { // Save etag
String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().path_join("assetimage_" + image_queue[p_queue_id].image_url.md5_text());
String new_etag = headers[i].substr(headers[i].find(":") + 1, headers[i].length()).strip_edges();
String new_etag = headers[i].substr(headers[i].find_char(':') + 1, headers[i].length()).strip_edges();
Ref<FileAccess> file = FileAccess::open(cache_filename_base + ".etag", FileAccess::WRITE);
if (file.is_valid()) {
file->store_line(new_etag);
@ -991,7 +993,8 @@ void EditorAssetLibrary::_request_image(ObjectID p_for, int p_asset_id, String p
String url_host;
int url_port;
String url_path;
Error err = trimmed_url.parse_url(url_scheme, url_host, url_port, url_path);
String url_fragment;
Error err = trimmed_url.parse_url(url_scheme, url_host, url_port, url_path, url_fragment);
if (err != OK) {
if (is_print_verbose_enabled()) {
ERR_PRINT(vformat("Asset Library: Invalid image URL '%s' for asset # %d.", trimmed_url, p_asset_id));
@ -1779,7 +1782,7 @@ bool AssetLibraryEditorPlugin::is_available() {
// directly from GitHub which does not set CORS.
return false;
#else
return StreamPeerTLS::is_available();
return StreamPeerTLS::is_available() && !Engine::get_singleton()->is_recovery_mode_hint();
#endif
}
@ -1794,7 +1797,7 @@ void AssetLibraryEditorPlugin::make_visible(bool p_visible) {
AssetLibraryEditorPlugin::AssetLibraryEditorPlugin() {
addon_library = memnew(EditorAssetLibrary);
addon_library->set_v_size_flags(Control::SIZE_EXPAND_FILL);
EditorNode::get_singleton()->get_main_screen_control()->add_child(addon_library);
EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(addon_library);
addon_library->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
addon_library->hide();
}

View file

@ -33,9 +33,7 @@
#include "editor/editor_asset_installer.h"
#include "editor/plugins/editor_plugin.h"
#include "editor/plugins/editor_plugin_settings.h"
#include "scene/gui/box_container.h"
#include "scene/gui/check_box.h"
#include "scene/gui/grid_container.h"
#include "scene/gui/line_edit.h"
#include "scene/gui/link_button.h"
@ -45,9 +43,8 @@
#include "scene/gui/progress_bar.h"
#include "scene/gui/rich_text_label.h"
#include "scene/gui/scroll_container.h"
#include "scene/gui/separator.h"
#include "scene/gui/tab_container.h"
#include "scene/gui/texture_button.h"
#include "scene/gui/texture_rect.h"
#include "scene/main/http_request.h"
class EditorFileDialog;
@ -342,7 +339,7 @@ class AssetLibraryEditorPlugin : public EditorPlugin {
public:
static bool is_available();
virtual String get_name() const override { return "AssetLib"; }
virtual String get_plugin_name() const override { return "AssetLib"; }
bool has_main_screen() const override { return true; }
virtual void edit(Object *p_object) override {}
virtual bool handles(Object *p_object) const override { return false; }

View file

@ -50,8 +50,8 @@ void AudioStreamEditor::_notification(int p_what) {
_current_label->add_theme_font_override(SceneStringName(font), font);
_duration_label->add_theme_font_override(SceneStringName(font), font);
_play_button->set_icon(get_editor_theme_icon(SNAME("MainPlay")));
_stop_button->set_icon(get_editor_theme_icon(SNAME("Stop")));
_play_button->set_button_icon(get_editor_theme_icon(SNAME("MainPlay")));
_stop_button->set_button_icon(get_editor_theme_icon(SNAME("Stop")));
_preview->set_color(get_theme_color(SNAME("dark_color_2"), EditorStringName(Editor)));
set_color(get_theme_color(SNAME("dark_color_1"), EditorStringName(Editor)));
@ -121,26 +121,26 @@ void AudioStreamEditor::_play() {
if (_player->is_playing()) {
_pausing = true;
_player->stop();
_play_button->set_icon(get_editor_theme_icon(SNAME("MainPlay")));
_play_button->set_button_icon(get_editor_theme_icon(SNAME("MainPlay")));
set_process(false);
} else {
_pausing = false;
_player->play(_current);
_play_button->set_icon(get_editor_theme_icon(SNAME("Pause")));
_play_button->set_button_icon(get_editor_theme_icon(SNAME("Pause")));
set_process(true);
}
}
void AudioStreamEditor::_stop() {
_player->stop();
_play_button->set_icon(get_editor_theme_icon(SNAME("MainPlay")));
_play_button->set_button_icon(get_editor_theme_icon(SNAME("MainPlay")));
_current = 0;
_indicator->queue_redraw();
set_process(false);
}
void AudioStreamEditor::_on_finished() {
_play_button->set_icon(get_editor_theme_icon(SNAME("MainPlay")));
_play_button->set_button_icon(get_editor_theme_icon(SNAME("MainPlay")));
if (!_pausing) {
_current = 0;
_indicator->queue_redraw();
@ -245,7 +245,7 @@ AudioStreamEditor::AudioStreamEditor() {
_play_button->set_flat(true);
_play_button->set_focus_mode(Control::FOCUS_NONE);
_play_button->connect(SceneStringName(pressed), callable_mp(this, &AudioStreamEditor::_play));
_play_button->set_shortcut(ED_SHORTCUT("audio_stream_editor/audio_preview_play_pause", TTR("Audio Preview Play/Pause"), Key::SPACE));
_play_button->set_shortcut(ED_SHORTCUT("audio_stream_editor/audio_preview_play_pause", TTRC("Audio Preview Play/Pause"), Key::SPACE));
_stop_button = memnew(Button);
hbox->add_child(_stop_button);

View file

@ -32,6 +32,7 @@
#include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
#include "servers/audio/audio_stream.h"
void AudioStreamRandomizerEditorPlugin::edit(Object *p_object) {
}

View file

@ -32,7 +32,6 @@
#define AUDIO_STREAM_RANDOMIZER_EDITOR_PLUGIN_H
#include "editor/plugins/editor_plugin.h"
#include "servers/audio/audio_stream.h"
class AudioStreamRandomizerEditorPlugin : public EditorPlugin {
GDCLASS(AudioStreamRandomizerEditorPlugin, EditorPlugin);
@ -41,7 +40,7 @@ private:
void _move_stream_array_element(Object *p_undo_redo, Object *p_edited, const String &p_array_prefix, int p_from_index, int p_to_pos);
public:
virtual String get_name() const override { return "AudioStreamRandomizer"; }
virtual String get_plugin_name() const override { return "AudioStreamRandomizer"; }
bool has_main_screen() const override { return false; }
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;

View file

@ -41,6 +41,8 @@
#include "scene/gui/separator.h"
#include "scene/gui/texture_rect.h"
#include "modules/regex/regex.h"
void BoneMapperButton::fetch_textures() {
if (selected) {
set_texture_normal(get_editor_theme_icon(SNAME("BoneMapperHandleSelected")));
@ -119,7 +121,7 @@ void BoneMapperItem::create_editor() {
hbox->add_child(skeleton_bone_selector);
picker_button = memnew(Button);
picker_button->set_icon(get_editor_theme_icon(SNAME("ClassList")));
picker_button->set_button_icon(get_editor_theme_icon(SNAME("ClassList")));
picker_button->connect(SceneStringName(pressed), callable_mp(this, &BoneMapperItem::_open_picker));
hbox->add_child(picker_button);
@ -147,7 +149,7 @@ void BoneMapperItem::_notification(int p_what) {
bone_map->connect("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property));
} break;
case NOTIFICATION_EXIT_TREE: {
if (!bone_map.is_null() && bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property))) {
if (bone_map.is_valid() && bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property))) {
bone_map->disconnect("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property));
}
} break;
@ -252,9 +254,6 @@ StringName BonePicker::get_selected_bone() {
return selected->get_text(0);
}
void BonePicker::_bind_methods() {
}
void BonePicker::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
@ -299,7 +298,7 @@ void BoneMapper::create_editor() {
group_hbox->add_child(profile_group_selector);
clear_mapping_button = memnew(Button);
clear_mapping_button->set_icon(get_editor_theme_icon(SNAME("Clear")));
clear_mapping_button->set_button_icon(get_editor_theme_icon(SNAME("Clear")));
clear_mapping_button->set_tooltip_text(TTR("Clear mappings in current group."));
clear_mapping_button->connect(SceneStringName(pressed), callable_mp(this, &BoneMapper::_clear_mapping_current_group));
group_hbox->add_child(clear_mapping_button);
@ -330,7 +329,7 @@ void BoneMapper::create_editor() {
}
void BoneMapper::update_group_idx() {
if (!bone_map->get_profile().is_valid()) {
if (bone_map->get_profile().is_null()) {
return;
}
@ -415,7 +414,7 @@ void BoneMapper::recreate_editor() {
profile_texture->set_texture(Ref<Texture2D>());
}
if (!profile.is_valid()) {
if (profile.is_null()) {
return;
}
@ -534,10 +533,9 @@ void BoneMapper::_clear_mapping_current_group() {
}
}
#ifdef MODULE_REGEX_ENABLED
bool BoneMapper::is_match_with_bone_name(const String &p_bone_name, const String &p_word) {
RegEx re = RegEx(p_word);
return !re.search(p_bone_name.to_lower()).is_null();
return re.search(p_bone_name.to_lower()).is_valid();
}
int BoneMapper::search_bone_by_name(Skeleton3D *p_skeleton, const Vector<String> &p_picklist, BoneSegregation p_segregation, int p_parent, int p_child, int p_children_count) {
@ -631,11 +629,11 @@ BoneMapper::BoneSegregation BoneMapper::guess_bone_segregation(const String &p_b
for (uint32_t i = 0; i < left_words.size(); i++) {
RegEx re_l = RegEx(left_words[i]);
if (!re_l.search(fixed_bn).is_null()) {
if (re_l.search(fixed_bn).is_valid()) {
return BONE_SEGREGATION_LEFT;
}
RegEx re_r = RegEx(right_words[i]);
if (!re_r.search(fixed_bn).is_null()) {
if (re_r.search(fixed_bn).is_valid()) {
return BONE_SEGREGATION_RIGHT;
}
}
@ -684,7 +682,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
bool found = false;
for (int i = 0; i < search_path.size(); i++) {
RegEx re = RegEx("root");
if (!re.search(skeleton->get_bone_name(search_path[i]).to_lower()).is_null()) {
if (re.search(skeleton->get_bone_name(search_path[i]).to_lower()).is_valid()) {
bone_idx = search_path[i]; // Name match is preferred.
found = true;
break;
@ -862,7 +860,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
// 4-1. Guess Finger
int tips_index = -1;
bool thumb_tips_size = 0;
bool thumb_tips_size = false;
bool named_finger_is_found = false;
LocalVector<String> fingers;
fingers.push_back("thumb|pollex");
@ -951,7 +949,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
Vector<String> finger_names;
for (int i = 0; i < search_path.size(); i++) {
String bn = skeleton->get_bone_name(search_path[i]);
if (!finger_re.search(bn.to_lower()).is_null()) {
if (finger_re.search(bn.to_lower()).is_valid()) {
finger_names.push_back(bn);
}
}
@ -997,7 +995,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
}
tips_index = -1;
thumb_tips_size = 0;
thumb_tips_size = false;
named_finger_is_found = false;
if (right_hand_or_palm != -1) {
LocalVector<LocalVector<String>> right_fingers_map;
@ -1080,7 +1078,7 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
Vector<String> finger_names;
for (int i = 0; i < search_path.size(); i++) {
String bn = skeleton->get_bone_name(search_path[i]);
if (!finger_re.search(bn.to_lower()).is_null()) {
if (finger_re.search(bn.to_lower()).is_valid()) {
finger_names.push_back(bn);
}
}
@ -1232,9 +1230,11 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
picklist.push_back("face");
int head = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck);
if (head == -1) {
search_path = skeleton->get_bone_children(neck);
if (search_path.size() == 1) {
head = search_path[0]; // Maybe only one child of the Neck is Head.
if (neck != -1) {
search_path = skeleton->get_bone_children(neck);
if (search_path.size() == 1) {
head = search_path[0]; // Maybe only one child of the Neck is Head.
}
}
}
if (head == -1) {
@ -1353,7 +1353,6 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
WARN_PRINT("Finish auto mapping.");
}
#endif // MODULE_REGEX_ENABLED
void BoneMapper::_value_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {
set(p_property, p_value);
@ -1368,9 +1367,7 @@ void BoneMapper::_profile_changed(const String &p_property, const Variant &p_val
if (profile.is_valid()) {
SkeletonProfileHumanoid *hmn = Object::cast_to<SkeletonProfileHumanoid>(profile.ptr());
if (hmn) {
#ifdef MODULE_REGEX_ENABLED
_run_auto_mapping();
#endif // MODULE_REGEX_ENABLED
}
}
}
@ -1393,7 +1390,7 @@ void BoneMapper::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_TREE: {
clear_items();
if (!bone_map.is_null()) {
if (bone_map.is_valid()) {
if (bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapper::_update_state))) {
bone_map->disconnect("bone_map_updated", callable_mp(this, &BoneMapper::_update_state));
}
@ -1478,12 +1475,6 @@ void EditorInspectorPluginBoneMap::parse_begin(Object *p_object) {
}
BoneMapEditorPlugin::BoneMapEditorPlugin() {
// Register properties in editor settings.
EDITOR_DEF("editors/bone_mapper/handle_colors/unset", Color(0.3, 0.3, 0.3));
EDITOR_DEF("editors/bone_mapper/handle_colors/set", Color(0.1, 0.6, 0.25));
EDITOR_DEF("editors/bone_mapper/handle_colors/missing", Color(0.8, 0.2, 0.8));
EDITOR_DEF("editors/bone_mapper/handle_colors/error", Color(0.8, 0.2, 0.2));
Ref<EditorInspectorPluginBoneMap> inspector_plugin;
inspector_plugin.instantiate();
add_inspector_plugin(inspector_plugin);

View file

@ -35,11 +35,6 @@
#include "editor/editor_properties.h"
#include "editor/plugins/editor_plugin.h"
#include "modules/modules_enabled.gen.h" // For regex.
#ifdef MODULE_REGEX_ENABLED
#include "modules/regex/regex.h"
#endif
#include "scene/3d/skeleton_3d.h"
#include "scene/gui/box_container.h"
#include "scene/gui/color_rect.h"
@ -122,8 +117,6 @@ public:
protected:
void _notification(int p_what);
static void _bind_methods();
void _confirm();
private:
@ -172,7 +165,6 @@ class BoneMapper : public VBoxContainer {
void _apply_picker_selection();
void _clear_mapping_current_group();
#ifdef MODULE_REGEX_ENABLED
/* For auto mapping */
enum BoneSegregation {
BONE_SEGREGATION_NONE,
@ -184,7 +176,6 @@ class BoneMapper : public VBoxContainer {
BoneSegregation guess_bone_segregation(const String &p_bone_name);
void auto_mapping_process(Ref<BoneMap> &p_bone_map);
void _run_auto_mapping();
#endif // MODULE_REGEX_ENABLED
protected:
void _notification(int p_what);
@ -233,7 +224,7 @@ class BoneMapEditorPlugin : public EditorPlugin {
GDCLASS(BoneMapEditorPlugin, EditorPlugin);
public:
virtual String get_name() const override { return "BoneMap"; }
virtual String get_plugin_name() const override { return "BoneMap"; }
BoneMapEditorPlugin();
};

View file

@ -30,8 +30,11 @@
#include "camera_3d_editor_plugin.h"
#include "core/config/project_settings.h"
#include "editor/editor_node.h"
#include "node_3d_editor_plugin.h"
#include "scene/gui/texture_rect.h"
#include "scene/main/viewport.h"
void Camera3DEditor::_node_removed(Node *p_node) {
if (p_node == node) {
@ -46,9 +49,6 @@ void Camera3DEditor::_pressed() {
Node3DEditor::get_singleton()->set_custom_camera(sn);
}
void Camera3DEditor::_bind_methods() {
}
void Camera3DEditor::edit(Node *p_camera) {
node = p_camera;
@ -79,9 +79,36 @@ Camera3DEditor::Camera3DEditor() {
preview->connect(SceneStringName(pressed), callable_mp(this, &Camera3DEditor::_pressed));
}
void Camera3DPreview::_update_sub_viewport_size() {
sub_viewport->set_size(Node3DEditor::get_camera_viewport_size(camera));
}
Camera3DPreview::Camera3DPreview(Camera3D *p_camera) :
TexturePreview(nullptr, false), camera(p_camera), sub_viewport(memnew(SubViewport)) {
RenderingServer::get_singleton()->viewport_attach_camera(sub_viewport->get_viewport_rid(), camera->get_camera());
add_child(sub_viewport);
TextureRect *display = get_texture_display();
display->set_texture(sub_viewport->get_texture());
sub_viewport->connect("size_changed", callable_mp((CanvasItem *)display, &CanvasItem::queue_redraw));
sub_viewport->get_texture()->connect_changed(callable_mp((TexturePreview *)this, &Camera3DPreview::_update_texture_display_ratio));
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Camera3DPreview::_update_sub_viewport_size));
_update_sub_viewport_size();
}
bool EditorInspectorPluginCamera3DPreview::can_handle(Object *p_object) {
return Object::cast_to<Camera3D>(p_object) != nullptr;
}
void EditorInspectorPluginCamera3DPreview::parse_begin(Object *p_object) {
Camera3D *camera = Object::cast_to<Camera3D>(p_object);
Camera3DPreview *preview = memnew(Camera3DPreview(camera));
add_custom_control(preview);
}
void Camera3DEditorPlugin::edit(Object *p_object) {
Node3DEditor::get_singleton()->set_can_preview(Object::cast_to<Camera3D>(p_object));
//camera_editor->edit(Object::cast_to<Node>(p_object));
}
bool Camera3DEditorPlugin::handles(Object *p_object) const {
@ -89,27 +116,15 @@ bool Camera3DEditorPlugin::handles(Object *p_object) const {
}
void Camera3DEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
//Node3DEditor::get_singleton()->set_can_preview(Object::cast_to<Camera3D>(p_object));
} else {
if (!p_visible) {
Node3DEditor::get_singleton()->set_can_preview(nullptr);
}
}
Camera3DEditorPlugin::Camera3DEditorPlugin() {
/* camera_editor = memnew( CameraEditor );
EditorNode::get_singleton()->get_main_screen_control()->add_child(camera_editor);
camera_editor->set_anchor(SIDE_LEFT,Control::ANCHOR_END);
camera_editor->set_anchor(SIDE_RIGHT,Control::ANCHOR_END);
camera_editor->set_offset(SIDE_LEFT,60);
camera_editor->set_offset(SIDE_RIGHT,0);
camera_editor->set_offset(SIDE_TOP,0);
camera_editor->set_offset(SIDE_BOTTOM,10);
camera_editor->hide();
*/
Ref<EditorInspectorPluginCamera3DPreview> plugin;
plugin.instantiate();
add_inspector_plugin(plugin);
}
Camera3DEditorPlugin::~Camera3DEditorPlugin() {

View file

@ -32,7 +32,10 @@
#define CAMERA_3D_EDITOR_PLUGIN_H
#include "editor/plugins/editor_plugin.h"
#include "scene/3d/camera_3d.h"
#include "editor/plugins/texture_editor_plugin.h"
class Camera3D;
class SubViewport;
class Camera3DEditor : public Control {
GDCLASS(Camera3DEditor, Control);
@ -45,20 +48,37 @@ class Camera3DEditor : public Control {
protected:
void _node_removed(Node *p_node);
static void _bind_methods();
public:
void edit(Node *p_camera);
Camera3DEditor();
};
class Camera3DPreview : public TexturePreview {
GDCLASS(Camera3DPreview, TexturePreview);
Camera3D *camera = nullptr;
SubViewport *sub_viewport = nullptr;
void _update_sub_viewport_size();
public:
Camera3DPreview(Camera3D *p_camera);
};
class EditorInspectorPluginCamera3DPreview : public EditorInspectorPluginTexture {
GDCLASS(EditorInspectorPluginCamera3DPreview, EditorInspectorPluginTexture);
public:
virtual bool can_handle(Object *p_object) override;
virtual void parse_begin(Object *p_object) override;
};
class Camera3DEditorPlugin : public EditorPlugin {
GDCLASS(Camera3DEditorPlugin, EditorPlugin);
//CameraEditor *camera_editor;
public:
virtual String get_name() const override { return "Camera3D"; }
virtual String get_plugin_name() const override { return "Camera3D"; }
bool has_main_screen() const override { return false; }
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;

File diff suppressed because it is too large Load diff

View file

@ -335,7 +335,6 @@ private:
Button *group_button = nullptr;
Button *ungroup_button = nullptr;
Button *override_camera_button = nullptr;
MenuButton *view_menu = nullptr;
PopupMenu *grid_menu = nullptr;
PopupMenu *theme_menu = nullptr;
@ -381,7 +380,6 @@ private:
Ref<Shortcut> divide_grid_step_shortcut;
Ref<ViewPanner> panner;
bool warped_panning = true;
void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
@ -404,6 +402,7 @@ private:
Vector2 _anchor_to_position(const Control *p_control, Vector2 anchor);
Vector2 _position_to_anchor(const Control *p_control, Vector2 position);
void _prepare_view_menu();
void _popup_callback(int p_op);
bool updating_scroll = false;
void _update_scroll(real_t);
@ -518,11 +517,8 @@ private:
void _zoom_on_position(real_t p_zoom, Point2 p_position = Point2());
void _button_toggle_smart_snap(bool p_status);
void _button_toggle_grid_snap(bool p_status);
void _button_override_camera(bool p_pressed);
void _button_tool_select(int p_index);
void _update_override_camera_button(bool p_game_running);
HSplitContainer *left_panel_split = nullptr;
HSplitContainer *right_panel_split = nullptr;
VSplitContainer *bottom_split = nullptr;
@ -607,7 +603,7 @@ protected:
void _notification(int p_what);
public:
virtual String get_name() const override { return "2D"; }
virtual String get_plugin_name() const override { return "2D"; }
bool has_main_screen() const override { return true; }
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;

View file

@ -35,6 +35,7 @@
#include "editor/editor_undo_redo_manager.h"
#include "scene/2d/physics/ray_cast_2d.h"
#include "scene/2d/physics/shape_cast_2d.h"
#include "scene/main/viewport.h"
void Cast2DEditor::_notification(int p_what) {
switch (p_what) {
@ -59,7 +60,12 @@ bool Cast2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
return false;
}
Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform();
Viewport *vp = node->get_viewport();
if (vp && !vp->is_visible_subviewport()) {
return false;
}
Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_screen_transform();
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {
@ -98,7 +104,7 @@ bool Cast2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid() && pressed) {
Vector2 point = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mm->get_position()));
point = node->get_global_transform().affine_inverse().xform(point);
point = node->get_screen_transform().affine_inverse().xform(point);
node->set("target_position", point);
canvas_item_editor->update_viewport();
@ -114,7 +120,12 @@ void Cast2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
return;
}
Transform2D gt = canvas_item_editor->get_canvas_transform() * node->get_global_transform();
Viewport *vp = node->get_viewport();
if (vp && !vp->is_visible_subviewport()) {
return;
}
Transform2D gt = canvas_item_editor->get_canvas_transform() * node->get_screen_transform();
const Ref<Texture2D> handle = get_editor_theme_icon(SNAME("EditorHandle"));
p_overlay->draw_texture(handle, gt.xform((Vector2)node->get("target_position")) - handle->get_size() / 2);

View file

@ -65,7 +65,7 @@ public:
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return cast_2d_editor->forward_canvas_gui_input(p_event); }
virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { cast_2d_editor->forward_canvas_draw_over_viewport(p_overlay); }
virtual String get_name() const override { return "Cast2D"; }
virtual String get_plugin_name() const override { return "Cast2D"; }
bool has_main_screen() const override { return false; }
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;

View file

@ -35,6 +35,7 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
#include "scene/main/viewport.h"
#include "scene/resources/2d/capsule_shape_2d.h"
#include "scene/resources/2d/circle_shape_2d.h"
#include "scene/resources/2d/concave_polygon_shape_2d.h"
@ -72,9 +73,17 @@ Variant CollisionShape2DEditor::get_handle_value(int idx) const {
} break;
case CONCAVE_POLYGON_SHAPE: {
Ref<ConcavePolygonShape2D> shape = node->get_shape();
const Vector<Vector2> &segments = shape->get_segments();
return segments[idx];
} break;
case CONVEX_POLYGON_SHAPE: {
Ref<ConvexPolygonShape2D> shape = node->get_shape();
const Vector<Vector2> &points = shape->get_points();
return points[idx];
} break;
case WORLD_BOUNDARY_SHAPE: {
@ -144,9 +153,27 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) {
} break;
case CONCAVE_POLYGON_SHAPE: {
Ref<ConcavePolygonShape2D> concave_shape = node->get_shape();
Vector<Vector2> segments = concave_shape->get_segments();
ERR_FAIL_INDEX(idx, segments.size());
segments.write[idx] = p_point;
concave_shape->set_segments(segments);
} break;
case CONVEX_POLYGON_SHAPE: {
Ref<ConvexPolygonShape2D> convex_shape = node->get_shape();
Vector<Vector2> points = convex_shape->get_points();
ERR_FAIL_INDEX(idx, points.size());
points.write[idx] = p_point;
convex_shape->set_points(points);
} break;
case WORLD_BOUNDARY_SHAPE: {
@ -236,11 +263,33 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) {
} break;
case CONCAVE_POLYGON_SHAPE: {
// Cannot be edited directly, use CollisionPolygon2D instead.
Ref<ConcavePolygonShape2D> concave_shape = node->get_shape();
Vector2 values = p_org;
Vector<Vector2> undo_segments = concave_shape->get_segments();
ERR_FAIL_INDEX(idx, undo_segments.size());
undo_segments.write[idx] = values;
undo_redo->add_do_method(concave_shape.ptr(), "set_segments", concave_shape->get_segments());
undo_redo->add_undo_method(concave_shape.ptr(), "set_segments", undo_segments);
} break;
case CONVEX_POLYGON_SHAPE: {
// Cannot be edited directly, use CollisionPolygon2D instead.
Ref<ConvexPolygonShape2D> convex_shape = node->get_shape();
Vector2 values = p_org;
Vector<Vector2> undo_points = convex_shape->get_points();
ERR_FAIL_INDEX(idx, undo_points.size());
undo_points.write[idx] = values;
undo_redo->add_do_method(convex_shape.ptr(), "set_points", convex_shape->get_points());
undo_redo->add_undo_method(convex_shape.ptr(), "set_points", undo_points);
} break;
case WORLD_BOUNDARY_SHAPE: {
@ -299,12 +348,17 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e
return false;
}
Viewport *vp = node->get_viewport();
if (vp && !vp->is_visible_subviewport()) {
return false;
}
if (shape_type == -1) {
return false;
}
Ref<InputEventMouseButton> mb = p_event;
Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform();
Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_screen_transform();
if (mb.is_valid()) {
Vector2 gpoint = mb->get_position();
@ -359,6 +413,7 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e
}
Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mm->get_position()));
cpoint = node->get_viewport()->get_popup_base_transform().affine_inverse().xform(cpoint);
cpoint = original_transform.affine_inverse().xform(cpoint);
last_point = cpoint;
@ -431,11 +486,16 @@ void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overla
return;
}
Viewport *vp = node->get_viewport();
if (vp && !vp->is_visible_subviewport()) {
return;
}
if (shape_type == -1) {
return;
}
Transform2D gt = canvas_item_editor->get_canvas_transform() * node->get_global_transform();
Transform2D gt = canvas_item_editor->get_canvas_transform() * node->get_screen_transform();
Ref<Texture2D> h = get_editor_theme_icon(SNAME("EditorHandle"));
Vector2 size = h->get_size() * 0.5;
@ -469,9 +529,29 @@ void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overla
} break;
case CONCAVE_POLYGON_SHAPE: {
Ref<ConcavePolygonShape2D> shape = current_shape;
const Vector<Vector2> &segments = shape->get_segments();
handles.resize(segments.size());
for (int i = 0; i < handles.size(); i++) {
handles.write[i] = segments[i];
p_overlay->draw_texture(h, gt.xform(handles[i]) - size);
}
} break;
case CONVEX_POLYGON_SHAPE: {
Ref<ConvexPolygonShape2D> shape = current_shape;
const Vector<Vector2> &points = shape->get_points();
handles.resize(points.size());
for (int i = 0; i < handles.size(); i++) {
handles.write[i] = points[i];
p_overlay->draw_texture(h, gt.xform(handles[i]) - size);
}
} break;
case WORLD_BOUNDARY_SHAPE: {

View file

@ -105,7 +105,7 @@ public:
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return collision_shape_2d_editor->forward_canvas_gui_input(p_event); }
virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { collision_shape_2d_editor->forward_canvas_draw_over_viewport(p_overlay); }
virtual String get_name() const override { return "CollisionShape2D"; }
virtual String get_plugin_name() const override { return "CollisionShape2D"; }
bool has_main_screen() const override { return false; }
virtual void edit(Object *p_obj) override;
virtual bool handles(Object *p_obj) const override;

View file

@ -0,0 +1,146 @@
/**************************************************************************/
/* color_channel_selector.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "color_channel_selector.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/panel_container.h"
#include "scene/resources/style_box_flat.h"
ColorChannelSelector::ColorChannelSelector() {
toggle_button = memnew(Button);
toggle_button->set_flat(true);
toggle_button->set_toggle_mode(true);
toggle_button->connect(SceneStringName(toggled), callable_mp(this, &ColorChannelSelector::on_toggled));
toggle_button->add_theme_style_override("focus", memnew(StyleBoxEmpty));
add_child(toggle_button);
panel = memnew(PanelContainer);
panel->hide();
HBoxContainer *container = memnew(HBoxContainer);
container->add_theme_constant_override("separation", 0);
create_button(0, "R", container);
create_button(1, "G", container);
create_button(2, "B", container);
create_button(3, "A", container);
// Use a bit of transparency to be less distracting.
set_modulate(Color(1, 1, 1, 0.7));
panel->add_child(container);
add_child(panel);
}
void ColorChannelSelector::_notification(int p_what) {
if (p_what == NOTIFICATION_THEME_CHANGED) {
// PanelContainer's background is invisible in the editor. We need a background.
// And we need this in turn because buttons don't look good without background (for example, hover is transparent).
Ref<StyleBox> bg_style = get_theme_stylebox(SceneStringName(panel), "TabContainer");
ERR_FAIL_COND(bg_style.is_null());
bg_style = bg_style->duplicate();
// The default content margin makes the widget become a bit too large. It should be like mini-toolbar.
const float editor_scale = EditorScale::get_scale();
bg_style->set_content_margin(SIDE_LEFT, 1.0f * editor_scale);
bg_style->set_content_margin(SIDE_RIGHT, 1.0f * editor_scale);
bg_style->set_content_margin(SIDE_TOP, 1.0f * editor_scale);
bg_style->set_content_margin(SIDE_BOTTOM, 1.0f * editor_scale);
panel->add_theme_style_override(SceneStringName(panel), bg_style);
Ref<Texture2D> icon = get_editor_theme_icon(SNAME("TexturePreviewChannels"));
toggle_button->set_button_icon(icon);
}
}
void ColorChannelSelector::set_available_channels_mask(uint32_t p_mask) {
for (unsigned int i = 0; i < CHANNEL_COUNT; ++i) {
const bool available = (p_mask & (1u << i)) != 0;
Button *button = channel_buttons[i];
button->set_visible(available);
}
}
void ColorChannelSelector::on_channel_button_toggled(bool p_unused_pressed) {
emit_signal("selected_channels_changed");
}
uint32_t ColorChannelSelector::get_selected_channels_mask() const {
uint32_t mask = 0;
for (unsigned int i = 0; i < CHANNEL_COUNT; ++i) {
Button *button = channel_buttons[i];
if (button->is_visible() && channel_buttons[i]->is_pressed()) {
mask |= (1 << i);
}
}
return mask;
}
// Helper
Vector4 ColorChannelSelector::get_selected_channel_factors() const {
Vector4 channel_factors;
const uint32_t mask = get_selected_channels_mask();
for (unsigned int i = 0; i < 4; ++i) {
if ((mask & (1 << i)) != 0) {
channel_factors[i] = 1;
}
}
return channel_factors;
}
void ColorChannelSelector::create_button(unsigned int p_channel_index, const String &p_text, Control *p_parent) {
ERR_FAIL_COND(p_channel_index >= CHANNEL_COUNT);
ERR_FAIL_COND(channel_buttons[p_channel_index] != nullptr);
Button *button = memnew(Button);
button->set_text(p_text);
button->set_toggle_mode(true);
button->set_pressed(true);
// Don't show focus, it stands out too much and remains visible which can be confusing.
button->add_theme_style_override("focus", memnew(StyleBoxEmpty));
// Make it look similar to toolbar buttons.
button->set_theme_type_variation(SceneStringName(FlatButton));
button->connect(SceneStringName(toggled), callable_mp(this, &ColorChannelSelector::on_channel_button_toggled));
p_parent->add_child(button);
channel_buttons[p_channel_index] = button;
}
void ColorChannelSelector::on_toggled(bool p_pressed) {
panel->set_visible(p_pressed);
}
void ColorChannelSelector::_bind_methods() {
ADD_SIGNAL(MethodInfo("selected_channels_changed"));
}

View file

@ -1,5 +1,5 @@
/**************************************************************************/
/* cpu_particles_2d_editor_plugin.h */
/* color_channel_selector.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -28,68 +28,38 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef CPU_PARTICLES_2D_EDITOR_PLUGIN_H
#define CPU_PARTICLES_2D_EDITOR_PLUGIN_H
#ifndef COLOR_CHANNEL_SELECTOR_H
#define COLOR_CHANNEL_SELECTOR_H
#include "editor/plugins/editor_plugin.h"
#include "scene/2d/cpu_particles_2d.h"
#include "scene/2d/physics/collision_polygon_2d.h"
#include "scene/gui/box_container.h"
class CheckBox;
class ConfirmationDialog;
class SpinBox;
class EditorFileDialog;
class MenuButton;
class OptionButton;
class PanelContainer;
class Button;
class CPUParticles2DEditorPlugin : public EditorPlugin {
GDCLASS(CPUParticles2DEditorPlugin, EditorPlugin);
class ColorChannelSelector : public HBoxContainer {
GDCLASS(ColorChannelSelector, HBoxContainer);
enum {
MENU_LOAD_EMISSION_MASK,
MENU_CLEAR_EMISSION_MASK,
MENU_RESTART,
MENU_CONVERT_TO_GPU_PARTICLES,
};
enum EmissionMode {
EMISSION_MODE_SOLID,
EMISSION_MODE_BORDER,
EMISSION_MODE_BORDER_DIRECTED
};
CPUParticles2D *particles = nullptr;
EditorFileDialog *file = nullptr;
HBoxContainer *toolbar = nullptr;
MenuButton *menu = nullptr;
ConfirmationDialog *emission_mask = nullptr;
OptionButton *emission_mask_mode = nullptr;
CheckBox *emission_mask_centered = nullptr;
CheckBox *emission_colors = nullptr;
String source_emission_file;
void _file_selected(const String &p_file);
void _menu_callback(int p_idx);
void _generate_emission_mask();
protected:
void _notification(int p_what);
static void _bind_methods();
static const unsigned int CHANNEL_COUNT = 4;
public:
virtual String get_name() const override { return "CPUParticles2D"; }
bool has_main_screen() const override { return false; }
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;
virtual void make_visible(bool p_visible) override;
ColorChannelSelector();
CPUParticles2DEditorPlugin();
~CPUParticles2DEditorPlugin();
void set_available_channels_mask(uint32_t p_mask);
uint32_t get_selected_channels_mask() const;
Vector4 get_selected_channel_factors() const;
private:
void _notification(int p_what);
void on_channel_button_toggled(bool p_unused_pressed);
void create_button(unsigned int p_channel_index, const String &p_text, Control *p_parent);
void on_toggled(bool p_pressed);
static void _bind_methods();
Button *channel_buttons[CHANNEL_COUNT] = {};
PanelContainer *panel = nullptr;
Button *toggle_button = nullptr;
};
#endif // CPU_PARTICLES_2D_EDITOR_PLUGIN_H
#endif // COLOR_CHANNEL_SELECTOR_H

View file

@ -31,7 +31,6 @@
#include "control_editor_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
@ -158,7 +157,7 @@ ControlPositioningWarning::ControlPositioningWarning() {
void EditorPropertyAnchorsPreset::_set_read_only(bool p_read_only) {
options->set_disabled(p_read_only);
};
}
void EditorPropertyAnchorsPreset::_option_selected(int p_which) {
int64_t val = options->get_item_metadata(p_which);
@ -222,7 +221,7 @@ void EditorPropertySizeFlags::_set_read_only(bool p_read_only) {
check->set_disabled(p_read_only);
}
flag_presets->set_disabled(p_read_only);
};
}
void EditorPropertySizeFlags::_preset_selected(int p_which) {
int preset = flag_presets->get_item_id(p_which);
@ -511,6 +510,9 @@ void ControlEditorPopupButton::_notification(int p_what) {
case NOTIFICATION_DRAW: {
if (arrow_icon.is_valid()) {
Vector2 arrow_pos = Point2(26, 0) * EDSCALE;
if (is_layout_rtl()) {
arrow_pos.x = get_size().x - arrow_pos.x - arrow_icon->get_width();
}
arrow_pos.y = get_size().y / 2 - arrow_icon->get_height() / 2;
draw_texture(arrow_icon, arrow_pos);
}
@ -529,12 +531,11 @@ void ControlEditorPopupButton::_notification(int p_what) {
}
ControlEditorPopupButton::ControlEditorPopupButton() {
set_theme_type_variation("FlatButton");
set_theme_type_variation(SceneStringName(FlatButton));
set_toggle_mode(true);
set_focus_mode(FOCUS_NONE);
popup_panel = memnew(PopupPanel);
popup_panel->set_theme_type_variation("ControlEditorPopupPanel");
add_child(popup_panel);
popup_panel->connect("about_to_popup", callable_mp(this, &ControlEditorPopupButton::_popup_visibility_changed).bind(true));
popup_panel->connect("popup_hide", callable_mp(this, &ControlEditorPopupButton::_popup_visibility_changed).bind(false));
@ -571,27 +572,27 @@ void AnchorPresetPicker::_notification(int p_notification) {
switch (p_notification) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
preset_buttons[PRESET_TOP_LEFT]->set_icon(get_editor_theme_icon(SNAME("ControlAlignTopLeft")));
preset_buttons[PRESET_CENTER_TOP]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterTop")));
preset_buttons[PRESET_TOP_RIGHT]->set_icon(get_editor_theme_icon(SNAME("ControlAlignTopRight")));
preset_buttons[PRESET_TOP_LEFT]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignTopLeft")));
preset_buttons[PRESET_CENTER_TOP]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignCenterTop")));
preset_buttons[PRESET_TOP_RIGHT]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignTopRight")));
preset_buttons[PRESET_CENTER_LEFT]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterLeft")));
preset_buttons[PRESET_CENTER]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenter")));
preset_buttons[PRESET_CENTER_RIGHT]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterRight")));
preset_buttons[PRESET_CENTER_LEFT]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignCenterLeft")));
preset_buttons[PRESET_CENTER]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignCenter")));
preset_buttons[PRESET_CENTER_RIGHT]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignCenterRight")));
preset_buttons[PRESET_BOTTOM_LEFT]->set_icon(get_editor_theme_icon(SNAME("ControlAlignBottomLeft")));
preset_buttons[PRESET_CENTER_BOTTOM]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterBottom")));
preset_buttons[PRESET_BOTTOM_RIGHT]->set_icon(get_editor_theme_icon(SNAME("ControlAlignBottomRight")));
preset_buttons[PRESET_BOTTOM_LEFT]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignBottomLeft")));
preset_buttons[PRESET_CENTER_BOTTOM]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignCenterBottom")));
preset_buttons[PRESET_BOTTOM_RIGHT]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignBottomRight")));
preset_buttons[PRESET_TOP_WIDE]->set_icon(get_editor_theme_icon(SNAME("ControlAlignTopWide")));
preset_buttons[PRESET_HCENTER_WIDE]->set_icon(get_editor_theme_icon(SNAME("ControlAlignHCenterWide")));
preset_buttons[PRESET_BOTTOM_WIDE]->set_icon(get_editor_theme_icon(SNAME("ControlAlignBottomWide")));
preset_buttons[PRESET_TOP_WIDE]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignTopWide")));
preset_buttons[PRESET_HCENTER_WIDE]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignHCenterWide")));
preset_buttons[PRESET_BOTTOM_WIDE]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignBottomWide")));
preset_buttons[PRESET_LEFT_WIDE]->set_icon(get_editor_theme_icon(SNAME("ControlAlignLeftWide")));
preset_buttons[PRESET_VCENTER_WIDE]->set_icon(get_editor_theme_icon(SNAME("ControlAlignVCenterWide")));
preset_buttons[PRESET_RIGHT_WIDE]->set_icon(get_editor_theme_icon(SNAME("ControlAlignRightWide")));
preset_buttons[PRESET_LEFT_WIDE]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignLeftWide")));
preset_buttons[PRESET_VCENTER_WIDE]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignVCenterWide")));
preset_buttons[PRESET_RIGHT_WIDE]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignRightWide")));
preset_buttons[PRESET_FULL_RECT]->set_icon(get_editor_theme_icon(SNAME("ControlAlignFullRect")));
preset_buttons[PRESET_FULL_RECT]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignFullRect")));
} break;
}
}
@ -689,17 +690,17 @@ void SizeFlagPresetPicker::_notification(int p_notification) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
if (vertical) {
preset_buttons[SIZE_SHRINK_BEGIN]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterTop")));
preset_buttons[SIZE_SHRINK_CENTER]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenter")));
preset_buttons[SIZE_SHRINK_END]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterBottom")));
preset_buttons[SIZE_SHRINK_BEGIN]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignCenterTop")));
preset_buttons[SIZE_SHRINK_CENTER]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignCenter")));
preset_buttons[SIZE_SHRINK_END]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignCenterBottom")));
preset_buttons[SIZE_FILL]->set_icon(get_editor_theme_icon(SNAME("ControlAlignVCenterWide")));
preset_buttons[SIZE_FILL]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignVCenterWide")));
} else {
preset_buttons[SIZE_SHRINK_BEGIN]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterLeft")));
preset_buttons[SIZE_SHRINK_CENTER]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenter")));
preset_buttons[SIZE_SHRINK_END]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterRight")));
preset_buttons[SIZE_SHRINK_BEGIN]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignCenterLeft")));
preset_buttons[SIZE_SHRINK_CENTER]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignCenter")));
preset_buttons[SIZE_SHRINK_END]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignCenterRight")));
preset_buttons[SIZE_FILL]->set_icon(get_editor_theme_icon(SNAME("ControlAlignHCenterWide")));
preset_buttons[SIZE_FILL]->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignHCenterWide")));
}
} break;
}
@ -1049,9 +1050,9 @@ void ControlEditorToolbar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
anchors_button->set_icon(get_editor_theme_icon(SNAME("ControlLayout")));
anchor_mode_button->set_icon(get_editor_theme_icon(SNAME("Anchor")));
containers_button->set_icon(get_editor_theme_icon(SNAME("ContainerLayout")));
anchors_button->set_button_icon(get_editor_theme_icon(SNAME("ControlLayout")));
anchor_mode_button->set_button_icon(get_editor_theme_icon(SNAME("Anchor")));
containers_button->set_button_icon(get_editor_theme_icon(SNAME("ContainerLayout")));
} break;
}
}
@ -1080,11 +1081,11 @@ ControlEditorToolbar::ControlEditorToolbar() {
keep_ratio_button->connect(SceneStringName(pressed), callable_mp(this, &ControlEditorToolbar::_anchors_to_current_ratio));
anchor_mode_button = memnew(Button);
anchor_mode_button->set_theme_type_variation("FlatButton");
anchor_mode_button->set_theme_type_variation(SceneStringName(FlatButton));
anchor_mode_button->set_toggle_mode(true);
anchor_mode_button->set_tooltip_text(TTR("When active, moving Control nodes changes their anchors instead of their offsets."));
add_child(anchor_mode_button);
anchor_mode_button->connect("toggled", callable_mp(this, &ControlEditorToolbar::_anchor_mode_toggled));
anchor_mode_button->connect(SceneStringName(toggled), callable_mp(this, &ControlEditorToolbar::_anchor_mode_toggled));
// Container tools.
containers_button = memnew(ControlEditorPopupButton);

View file

@ -241,7 +241,7 @@ protected:
static ControlEditorToolbar *singleton;
public:
bool is_anchors_mode_enabled() { return anchors_mode; };
bool is_anchors_mode_enabled() { return anchors_mode; }
static ControlEditorToolbar *get_singleton() { return singleton; }
@ -255,7 +255,7 @@ class ControlEditorPlugin : public EditorPlugin {
ControlEditorToolbar *toolbar = nullptr;
public:
virtual String get_name() const override { return "Control"; }
virtual String get_plugin_name() const override { return "Control"; }
ControlEditorPlugin();
};

View file

@ -1,312 +0,0 @@
/**************************************************************************/
/* cpu_particles_2d_editor_plugin.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "cpu_particles_2d_editor_plugin.h"
#include "canvas_item_editor_plugin.h"
#include "core/io/image_loader.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/scene_tree_dock.h"
#include "scene/2d/cpu_particles_2d.h"
#include "scene/2d/gpu_particles_2d.h"
#include "scene/gui/check_box.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/option_button.h"
#include "scene/gui/separator.h"
#include "scene/gui/spin_box.h"
#include "scene/resources/particle_process_material.h"
void CPUParticles2DEditorPlugin::edit(Object *p_object) {
particles = Object::cast_to<CPUParticles2D>(p_object);
}
bool CPUParticles2DEditorPlugin::handles(Object *p_object) const {
return p_object->is_class("CPUParticles2D");
}
void CPUParticles2DEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
toolbar->show();
} else {
toolbar->hide();
}
}
void CPUParticles2DEditorPlugin::_file_selected(const String &p_file) {
source_emission_file = p_file;
emission_mask->popup_centered();
}
void CPUParticles2DEditorPlugin::_menu_callback(int p_idx) {
switch (p_idx) {
case MENU_LOAD_EMISSION_MASK: {
file->popup_file_dialog();
} break;
case MENU_CLEAR_EMISSION_MASK: {
emission_mask->popup_centered();
} break;
case MENU_RESTART: {
particles->restart();
} break;
case MENU_CONVERT_TO_GPU_PARTICLES: {
GPUParticles2D *gpu_particles = memnew(GPUParticles2D);
gpu_particles->convert_from_particles(particles);
gpu_particles->set_name(particles->get_name());
gpu_particles->set_transform(particles->get_transform());
gpu_particles->set_visible(particles->is_visible());
gpu_particles->set_process_mode(particles->get_process_mode());
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Convert to GPUParticles3D"), UndoRedo::MERGE_DISABLE, particles);
SceneTreeDock::get_singleton()->replace_node(particles, gpu_particles);
ur->commit_action(false);
} break;
}
}
void CPUParticles2DEditorPlugin::_generate_emission_mask() {
Ref<Image> img;
img.instantiate();
Error err = ImageLoader::load_image(source_emission_file, img);
ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'.");
if (img->is_compressed()) {
img->decompress();
}
img->convert(Image::FORMAT_RGBA8);
ERR_FAIL_COND(img->get_format() != Image::FORMAT_RGBA8);
Size2i s = img->get_size();
ERR_FAIL_COND(s.width == 0 || s.height == 0);
Vector<Point2> valid_positions;
Vector<Point2> valid_normals;
Vector<uint8_t> valid_colors;
valid_positions.resize(s.width * s.height);
EmissionMode emode = (EmissionMode)emission_mask_mode->get_selected();
if (emode == EMISSION_MODE_BORDER_DIRECTED) {
valid_normals.resize(s.width * s.height);
}
bool capture_colors = emission_colors->is_pressed();
if (capture_colors) {
valid_colors.resize(s.width * s.height * 4);
}
int vpc = 0;
{
Vector<uint8_t> img_data = img->get_data();
const uint8_t *r = img_data.ptr();
for (int i = 0; i < s.width; i++) {
for (int j = 0; j < s.height; j++) {
uint8_t a = r[(j * s.width + i) * 4 + 3];
if (a > 128) {
if (emode == EMISSION_MODE_SOLID) {
if (capture_colors) {
valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
}
valid_positions.write[vpc++] = Point2(i, j);
} else {
bool on_border = false;
for (int x = i - 1; x <= i + 1; x++) {
for (int y = j - 1; y <= j + 1; y++) {
if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
on_border = true;
break;
}
}
if (on_border) {
break;
}
}
if (on_border) {
valid_positions.write[vpc] = Point2(i, j);
if (emode == EMISSION_MODE_BORDER_DIRECTED) {
Vector2 normal;
for (int x = i - 2; x <= i + 2; x++) {
for (int y = j - 2; y <= j + 2; y++) {
if (x == i && y == j) {
continue;
}
if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
normal += Vector2(x - i, y - j).normalized();
}
}
}
normal.normalize();
valid_normals.write[vpc] = normal;
}
if (capture_colors) {
valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
}
vpc++;
}
}
}
}
}
}
valid_positions.resize(vpc);
if (valid_normals.size()) {
valid_normals.resize(vpc);
}
ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image...");
if (capture_colors) {
PackedColorArray pca;
pca.resize(vpc);
Color *pcaw = pca.ptrw();
for (int i = 0; i < vpc; i += 1) {
Color color;
color.r = valid_colors[i * 4 + 0] / 255.0f;
color.g = valid_colors[i * 4 + 1] / 255.0f;
color.b = valid_colors[i * 4 + 2] / 255.0f;
color.a = valid_colors[i * 4 + 3] / 255.0f;
pcaw[i] = color;
}
particles->set_emission_colors(pca);
}
if (valid_normals.size()) {
particles->set_emission_shape(CPUParticles2D::EMISSION_SHAPE_DIRECTED_POINTS);
PackedVector2Array norms;
norms.resize(valid_normals.size());
Vector2 *normsw = norms.ptrw();
for (int i = 0; i < valid_normals.size(); i += 1) {
normsw[i] = valid_normals[i];
}
particles->set_emission_normals(norms);
} else {
particles->set_emission_shape(CPUParticles2D::EMISSION_SHAPE_POINTS);
}
{
Vector2 offset;
if (emission_mask_centered->is_pressed()) {
offset = Vector2(-s.width * 0.5, -s.height * 0.5);
}
PackedVector2Array points;
points.resize(valid_positions.size());
Vector2 *pointsw = points.ptrw();
for (int i = 0; i < valid_positions.size(); i += 1) {
pointsw[i] = valid_positions[i] + offset;
}
particles->set_emission_points(points);
}
}
void CPUParticles2DEditorPlugin::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CPUParticles2DEditorPlugin::_menu_callback));
menu->set_icon(file->get_editor_theme_icon(SNAME("CPUParticles2D")));
file->connect("file_selected", callable_mp(this, &CPUParticles2DEditorPlugin::_file_selected));
} break;
}
}
void CPUParticles2DEditorPlugin::_bind_methods() {
}
CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() {
particles = nullptr;
toolbar = memnew(HBoxContainer);
add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar);
toolbar->hide();
menu = memnew(MenuButton);
menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_RESTART);
menu->get_popup()->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK);
menu->get_popup()->add_item(TTR("Convert to GPUParticles2D"), MENU_CONVERT_TO_GPU_PARTICLES);
menu->set_text(TTR("CPUParticles2D"));
menu->set_switch_on_hover(true);
toolbar->add_child(menu);
file = memnew(EditorFileDialog);
List<String> ext;
ImageLoader::get_recognized_extensions(&ext);
for (const String &E : ext) {
file->add_filter("*." + E, E.to_upper());
}
file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
toolbar->add_child(file);
emission_mask = memnew(ConfirmationDialog);
emission_mask->set_title(TTR("Load Emission Mask"));
VBoxContainer *emvb = memnew(VBoxContainer);
emission_mask->add_child(emvb);
emission_mask_mode = memnew(OptionButton);
emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode);
emission_mask_mode->add_item(TTR("Solid Pixels"), EMISSION_MODE_SOLID);
emission_mask_mode->add_item(TTR("Border Pixels"), EMISSION_MODE_BORDER);
emission_mask_mode->add_item(TTR("Directed Border Pixels"), EMISSION_MODE_BORDER_DIRECTED);
VBoxContainer *optionsvb = memnew(VBoxContainer);
emvb->add_margin_child(TTR("Options"), optionsvb);
emission_mask_centered = memnew(CheckBox);
emission_mask_centered->set_text(TTR("Centered"));
optionsvb->add_child(emission_mask_centered);
emission_colors = memnew(CheckBox);
emission_colors->set_text(TTR("Capture Colors from Pixel"));
optionsvb->add_child(emission_colors);
toolbar->add_child(emission_mask);
emission_mask->connect(SceneStringName(confirmed), callable_mp(this, &CPUParticles2DEditorPlugin::_generate_emission_mask));
}
CPUParticles2DEditorPlugin::~CPUParticles2DEditorPlugin() {
}

View file

@ -1,220 +0,0 @@
/**************************************************************************/
/* cpu_particles_3d_editor_plugin.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "cpu_particles_3d_editor_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/scene_tree_editor.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "editor/scene_tree_dock.h"
#include "scene/gui/menu_button.h"
void CPUParticles3DEditor::_node_removed(Node *p_node) {
if (p_node == node) {
node = nullptr;
hide();
}
}
void CPUParticles3DEditor::_notification(int p_notification) {
switch (p_notification) {
case NOTIFICATION_ENTER_TREE: {
options->set_icon(get_editor_theme_icon(SNAME("CPUParticles3D")));
} break;
}
}
void CPUParticles3DEditor::_menu_option(int p_option) {
switch (p_option) {
case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: {
emission_tree_dialog->popup_scenetree_dialog();
} break;
case MENU_OPTION_RESTART: {
node->restart();
} break;
case MENU_OPTION_CONVERT_TO_GPU_PARTICLES: {
GPUParticles3D *gpu_particles = memnew(GPUParticles3D);
gpu_particles->convert_from_particles(node);
gpu_particles->set_name(node->get_name());
gpu_particles->set_transform(node->get_transform());
gpu_particles->set_visible(node->is_visible());
gpu_particles->set_process_mode(node->get_process_mode());
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Convert to GPUParticles3D"), UndoRedo::MERGE_DISABLE, node);
SceneTreeDock::get_singleton()->replace_node(node, gpu_particles);
ur->commit_action(false);
} break;
case MENU_OPTION_GENERATE_AABB: {
// Add one second to the default generation lifetime, since the progress is updated every second.
generate_seconds->set_value(MAX(1.0, trunc(node->get_lifetime()) + 1.0));
if (generate_seconds->get_value() >= 11.0 + CMP_EPSILON) {
// Only pop up the time dialog if the particle's lifetime is long enough to warrant shortening it.
generate_aabb->popup_centered();
} else {
// Generate the visibility AABB immediately.
_generate_aabb();
}
} break;
}
}
void CPUParticles3DEditor::_generate_aabb() {
double time = generate_seconds->get_value();
double running = 0.0;
EditorProgress ep("gen_aabb", TTR("Generating Visibility AABB (Waiting for Particle Simulation)"), int(time));
bool was_emitting = node->is_emitting();
if (!was_emitting) {
node->set_emitting(true);
OS::get_singleton()->delay_usec(1000);
}
AABB rect;
while (running < time) {
uint64_t ticks = OS::get_singleton()->get_ticks_usec();
ep.step(TTR("Generating..."), int(running), true);
OS::get_singleton()->delay_usec(1000);
AABB capture = node->capture_aabb();
if (rect == AABB()) {
rect = capture;
} else {
rect.merge_with(capture);
}
running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
}
if (!was_emitting) {
node->set_emitting(false);
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Generate Visibility AABB"));
ur->add_do_method(node, "set_visibility_aabb", rect);
ur->add_undo_method(node, "set_visibility_aabb", node->get_visibility_aabb());
ur->commit_action();
}
void CPUParticles3DEditor::edit(CPUParticles3D *p_particles) {
base_node = p_particles;
node = p_particles;
}
void CPUParticles3DEditor::_generate_emission_points() {
/// hacer codigo aca
Vector<Vector3> points;
Vector<Vector3> normals;
if (!_generate(points, normals)) {
return;
}
if (normals.size() == 0) {
node->set_emission_shape(CPUParticles3D::EMISSION_SHAPE_POINTS);
node->set_emission_points(points);
} else {
node->set_emission_shape(CPUParticles3D::EMISSION_SHAPE_DIRECTED_POINTS);
node->set_emission_points(points);
node->set_emission_normals(normals);
}
}
void CPUParticles3DEditor::_bind_methods() {
}
CPUParticles3DEditor::CPUParticles3DEditor() {
particles_editor_hb = memnew(HBoxContainer);
Node3DEditor::get_singleton()->add_control_to_menu_panel(particles_editor_hb);
options = memnew(MenuButton);
options->set_switch_on_hover(true);
particles_editor_hb->add_child(options);
particles_editor_hb->hide();
options->set_text(TTR("CPUParticles3D"));
options->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_OPTION_RESTART);
options->get_popup()->add_item(TTR("Generate AABB"), MENU_OPTION_GENERATE_AABB);
options->get_popup()->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE);
options->get_popup()->add_item(TTR("Convert to GPUParticles3D"), MENU_OPTION_CONVERT_TO_GPU_PARTICLES);
options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CPUParticles3DEditor::_menu_option));
generate_aabb = memnew(ConfirmationDialog);
generate_aabb->set_title(TTR("Generate Visibility AABB"));
VBoxContainer *genvb = memnew(VBoxContainer);
generate_aabb->add_child(genvb);
generate_seconds = memnew(SpinBox);
genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds);
generate_seconds->set_min(0.1);
generate_seconds->set_max(25);
generate_seconds->set_value(2);
add_child(generate_aabb);
generate_aabb->connect(SceneStringName(confirmed), callable_mp(this, &CPUParticles3DEditor::_generate_aabb));
}
void CPUParticles3DEditorPlugin::edit(Object *p_object) {
particles_editor->edit(Object::cast_to<CPUParticles3D>(p_object));
}
bool CPUParticles3DEditorPlugin::handles(Object *p_object) const {
return p_object->is_class("CPUParticles3D");
}
void CPUParticles3DEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
particles_editor->show();
particles_editor->particles_editor_hb->show();
} else {
particles_editor->particles_editor_hb->hide();
particles_editor->hide();
particles_editor->edit(nullptr);
}
}
CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin() {
particles_editor = memnew(CPUParticles3DEditor);
EditorNode::get_singleton()->get_main_screen_control()->add_child(particles_editor);
particles_editor->hide();
}
CPUParticles3DEditorPlugin::~CPUParticles3DEditorPlugin() {
}

View file

@ -64,6 +64,7 @@ void CurveEdit::set_curve(Ref<Curve> p_curve) {
if (curve.is_valid()) {
curve->disconnect_changed(callable_mp(this, &CurveEdit::_curve_changed));
curve->disconnect(Curve::SIGNAL_RANGE_CHANGED, callable_mp(this, &CurveEdit::_curve_changed));
curve->disconnect(Curve::SIGNAL_DOMAIN_CHANGED, callable_mp(this, &CurveEdit::_curve_changed));
}
curve = p_curve;
@ -71,6 +72,7 @@ void CurveEdit::set_curve(Ref<Curve> p_curve) {
if (curve.is_valid()) {
curve->connect_changed(callable_mp(this, &CurveEdit::_curve_changed));
curve->connect(Curve::SIGNAL_RANGE_CHANGED, callable_mp(this, &CurveEdit::_curve_changed));
curve->connect(Curve::SIGNAL_DOMAIN_CHANGED, callable_mp(this, &CurveEdit::_curve_changed));
}
// Note: if you edit a curve, then set another, and try to undo,
@ -226,10 +228,10 @@ void CurveEdit::gui_input(const Ref<InputEvent> &p_event) {
}
} else if (grabbing == GRAB_NONE) {
// Adding a new point. Insert a temporary point for the user to adjust, so it's not in the undo/redo.
Vector2 new_pos = get_world_pos(mpos).clamp(Vector2(0.0, curve->get_min_value()), Vector2(1.0, curve->get_max_value()));
Vector2 new_pos = get_world_pos(mpos).clamp(Vector2(curve->get_min_domain(), curve->get_min_value()), Vector2(curve->get_max_domain(), curve->get_max_value()));
if (snap_enabled || mb->is_command_or_control_pressed()) {
new_pos.x = Math::snapped(new_pos.x, 1.0 / snap_count);
new_pos.y = Math::snapped(new_pos.y - curve->get_min_value(), curve->get_range() / snap_count) + curve->get_min_value();
new_pos.x = Math::snapped(new_pos.x - curve->get_min_domain(), curve->get_domain_range() / snap_count) + curve->get_min_domain();
new_pos.y = Math::snapped(new_pos.y - curve->get_min_value(), curve->get_value_range() / snap_count) + curve->get_min_value();
}
new_pos.x = get_offset_without_collision(selected_index, new_pos.x, mpos.x >= get_view_pos(new_pos).x);
@ -276,11 +278,11 @@ void CurveEdit::gui_input(const Ref<InputEvent> &p_event) {
if (selected_index != -1) {
if (selected_tangent_index == TANGENT_NONE) {
// Drag point.
Vector2 new_pos = get_world_pos(mpos).clamp(Vector2(0.0, curve->get_min_value()), Vector2(1.0, curve->get_max_value()));
Vector2 new_pos = get_world_pos(mpos).clamp(Vector2(curve->get_min_domain(), curve->get_min_value()), Vector2(curve->get_max_domain(), curve->get_max_value()));
if (snap_enabled || mm->is_command_or_control_pressed()) {
new_pos.x = Math::snapped(new_pos.x, 1.0 / snap_count);
new_pos.y = Math::snapped(new_pos.y - curve->get_min_value(), curve->get_range() / snap_count) + curve->get_min_value();
new_pos.x = Math::snapped(new_pos.x - curve->get_min_domain(), curve->get_domain_range() / snap_count) + curve->get_min_domain();
new_pos.y = Math::snapped(new_pos.y - curve->get_min_value(), curve->get_value_range() / snap_count) + curve->get_min_value();
}
// Allow to snap to axes with Shift.
@ -295,8 +297,8 @@ void CurveEdit::gui_input(const Ref<InputEvent> &p_event) {
// Allow to constraint the point between the adjacent two with Alt.
if (mm->is_alt_pressed()) {
float prev_point_offset = (selected_index > 0) ? (curve->get_point_position(selected_index - 1).x + 0.00001) : 0.0;
float next_point_offset = (selected_index < curve->get_point_count() - 1) ? (curve->get_point_position(selected_index + 1).x - 0.00001) : 1.0;
float prev_point_offset = (selected_index > 0) ? (curve->get_point_position(selected_index - 1).x + 0.00001) : curve->get_min_domain();
float next_point_offset = (selected_index < curve->get_point_count() - 1) ? (curve->get_point_position(selected_index + 1).x - 0.00001) : curve->get_max_domain();
new_pos.x = CLAMP(new_pos.x, prev_point_offset, next_point_offset);
}
@ -357,37 +359,39 @@ void CurveEdit::use_preset(int p_preset_id) {
Array previous_data = curve->get_data();
curve->clear_points();
float min_value = curve->get_min_value();
float max_value = curve->get_max_value();
const float min_y = curve->get_min_value();
const float max_y = curve->get_max_value();
const float min_x = curve->get_min_domain();
const float max_x = curve->get_max_domain();
switch (p_preset_id) {
case PRESET_CONSTANT:
curve->add_point(Vector2(0, (min_value + max_value) / 2.0));
curve->add_point(Vector2(1, (min_value + max_value) / 2.0));
curve->add_point(Vector2(min_x, (min_y + max_y) / 2.0));
curve->add_point(Vector2(max_x, (min_y + max_y) / 2.0));
curve->set_point_right_mode(0, Curve::TANGENT_LINEAR);
curve->set_point_left_mode(1, Curve::TANGENT_LINEAR);
break;
case PRESET_LINEAR:
curve->add_point(Vector2(0, min_value));
curve->add_point(Vector2(1, max_value));
curve->add_point(Vector2(min_x, min_y));
curve->add_point(Vector2(max_x, max_y));
curve->set_point_right_mode(0, Curve::TANGENT_LINEAR);
curve->set_point_left_mode(1, Curve::TANGENT_LINEAR);
break;
case PRESET_EASE_IN:
curve->add_point(Vector2(0, min_value));
curve->add_point(Vector2(1, max_value), curve->get_range() * 1.4, 0);
curve->add_point(Vector2(min_x, min_y));
curve->add_point(Vector2(max_x, max_y), curve->get_value_range() / curve->get_domain_range() * 1.4, 0);
break;
case PRESET_EASE_OUT:
curve->add_point(Vector2(0, min_value), 0, curve->get_range() * 1.4);
curve->add_point(Vector2(1, max_value));
curve->add_point(Vector2(min_x, min_y), 0, curve->get_value_range() / curve->get_domain_range() * 1.4);
curve->add_point(Vector2(max_x, max_y));
break;
case PRESET_SMOOTHSTEP:
curve->add_point(Vector2(0, min_value));
curve->add_point(Vector2(1, max_value));
curve->add_point(Vector2(min_x, min_y));
curve->add_point(Vector2(max_x, max_y));
break;
default:
@ -411,7 +415,7 @@ void CurveEdit::_curve_changed() {
}
}
int CurveEdit::get_point_at(Vector2 p_pos) const {
int CurveEdit::get_point_at(const Vector2 &p_pos) const {
if (curve.is_null()) {
return -1;
}
@ -432,7 +436,7 @@ int CurveEdit::get_point_at(Vector2 p_pos) const {
return closest_idx;
}
CurveEdit::TangentIndex CurveEdit::get_tangent_at(Vector2 p_pos) const {
CurveEdit::TangentIndex CurveEdit::get_tangent_at(const Vector2 &p_pos) const {
if (curve.is_null() || selected_index < 0) {
return TANGENT_NONE;
}
@ -491,7 +495,7 @@ float CurveEdit::get_offset_without_collision(int p_current_index, float p_offse
return safe_offset;
}
void CurveEdit::add_point(Vector2 p_pos) {
void CurveEdit::add_point(const Vector2 &p_pos) {
ERR_FAIL_COND(curve.is_null());
// Add a point to get its index, then remove it immediately. Trick to feed the UndoRedo.
@ -531,7 +535,7 @@ void CurveEdit::remove_point(int p_index) {
undo_redo->commit_action();
}
void CurveEdit::set_point_position(int p_index, Vector2 p_pos) {
void CurveEdit::set_point_position(int p_index, const Vector2 &p_pos) {
ERR_FAIL_COND(curve.is_null());
ERR_FAIL_INDEX_MSG(p_index, curve->get_point_count(), "Curve point is out of bounds.");
@ -657,10 +661,12 @@ void CurveEdit::update_view_transform() {
const real_t margin = font->get_height(font_size) + 2 * EDSCALE;
float min_x = curve.is_valid() ? curve->get_min_domain() : 0.0;
float max_x = curve.is_valid() ? curve->get_max_domain() : 1.0;
float min_y = curve.is_valid() ? curve->get_min_value() : 0.0;
float max_y = curve.is_valid() ? curve->get_max_value() : 1.0;
const Rect2 world_rect = Rect2(Curve::MIN_X, min_y, Curve::MAX_X, max_y - min_y);
const Rect2 world_rect = Rect2(min_x, min_y, max_x - min_x, max_y - min_y);
const Size2 view_margin(margin, margin);
const Size2 view_size = get_size() - view_margin * 2;
const Vector2 scale = view_size / world_rect.size;
@ -707,71 +713,57 @@ Vector2 CurveEdit::get_tangent_view_pos(int p_index, TangentIndex p_tangent) con
return tangent_view_pos;
}
Vector2 CurveEdit::get_view_pos(Vector2 p_world_pos) const {
Vector2 CurveEdit::get_view_pos(const Vector2 &p_world_pos) const {
return _world_to_view.xform(p_world_pos);
}
Vector2 CurveEdit::get_world_pos(Vector2 p_view_pos) const {
Vector2 CurveEdit::get_world_pos(const Vector2 &p_view_pos) const {
return _world_to_view.affine_inverse().xform(p_view_pos);
}
// Uses non-baked points, but takes advantage of ordered iteration to be faster.
template <typename T>
static void plot_curve_accurate(const Curve &curve, float step, Vector2 scaling, T plot_func) {
if (curve.get_point_count() <= 1) {
// Not enough points to make a curve, so it's just a straight line.
// The added tiny vectors make the drawn line stay exactly within the bounds in practice.
float y = curve.sample(0);
plot_func(Vector2(0, y) * scaling + Vector2(0.5, 0), Vector2(1.f, y) * scaling - Vector2(1.5, 0), true);
void CurveEdit::plot_curve_accurate(float p_step, const Color &p_line_color, const Color &p_edge_line_color) {
const real_t min_x = curve->get_min_domain();
const real_t max_x = curve->get_max_domain();
if (curve->get_point_count() <= 1) { // Draw single line through entire plot.
real_t y = curve->sample(0);
draw_line(get_view_pos(Vector2(min_x, y)) + Vector2(0.5, 0), get_view_pos(Vector2(max_x, y)) - Vector2(1.5, 0), p_line_color, LINE_WIDTH, true);
return;
}
} else {
Vector2 first_point = curve.get_point_position(0);
Vector2 last_point = curve.get_point_position(curve.get_point_count() - 1);
Vector2 first_point = curve->get_point_position(0);
Vector2 last_point = curve->get_point_position(curve->get_point_count() - 1);
// Edge lines
plot_func(Vector2(0, first_point.y) * scaling + Vector2(0.5, 0), first_point * scaling, false);
plot_func(Vector2(Curve::MAX_X, last_point.y) * scaling - Vector2(1.5, 0), last_point * scaling, false);
// Transform pixels-per-step into curve domain. Only works for non-rotated transforms.
const float world_step_size = p_step / _world_to_view.get_scale().x;
// Draw section by section, so that we get maximum precision near points.
// It's an accurate representation, but slower than using the baked one.
for (int i = 1; i < curve.get_point_count(); ++i) {
Vector2 a = curve.get_point_position(i - 1);
Vector2 b = curve.get_point_position(i);
// Edge lines.
draw_line(get_view_pos(Vector2(min_x, first_point.y)) + Vector2(0.5, 0), get_view_pos(first_point), p_edge_line_color, LINE_WIDTH, true);
draw_line(get_view_pos(last_point), get_view_pos(Vector2(max_x, last_point.y)) - Vector2(1.5, 0), p_edge_line_color, LINE_WIDTH, true);
Vector2 pos = a;
Vector2 prev_pos = a;
// Draw section by section, so that we get maximum precision near points.
// It's an accurate representation, but slower than using the baked one.
for (int i = 1; i < curve->get_point_count(); ++i) {
Vector2 a = curve->get_point_position(i - 1);
Vector2 b = curve->get_point_position(i);
float scaled_step = step / scaling.x;
float samples = (b.x - a.x) / scaled_step;
Vector2 pos = a;
Vector2 prev_pos = a;
for (int j = 1; j < samples; j++) {
float x = j * scaled_step;
pos.x = a.x + x;
pos.y = curve.sample_local_nocheck(i - 1, x);
plot_func(prev_pos * scaling, pos * scaling, true);
prev_pos = pos;
}
float samples = (b.x - a.x) / world_step_size;
plot_func(prev_pos * scaling, b * scaling, true);
for (int j = 1; j < samples; j++) {
float x = j * world_step_size;
pos.x = a.x + x;
pos.y = curve->sample_local_nocheck(i - 1, x);
draw_line(get_view_pos(prev_pos), get_view_pos(pos), p_line_color, LINE_WIDTH, true);
prev_pos = pos;
}
draw_line(get_view_pos(prev_pos), get_view_pos(b), p_line_color, LINE_WIDTH, true);
}
}
struct CanvasItemPlotCurve {
CanvasItem &ci;
Color color1;
Color color2;
CanvasItemPlotCurve(CanvasItem &p_ci, Color p_color1, Color p_color2) :
ci(p_ci),
color1(p_color1),
color2(p_color2) {}
void operator()(Vector2 pos0, Vector2 pos1, bool in_definition) {
ci.draw_line(pos0, pos1, in_definition ? color1 : color2, 0.5, true);
}
};
void CurveEdit::_redraw() {
if (curve.is_null()) {
return;
@ -784,7 +776,7 @@ void CurveEdit::_redraw() {
Vector2 view_size = get_rect().size;
draw_style_box(get_theme_stylebox(SceneStringName(panel), SNAME("Tree")), Rect2(Point2(), view_size));
// Draw snapping grid, then primary grid.
// Draw primary grid.
draw_set_transform_matrix(_world_to_view);
Vector2 min_edge = get_world_pos(Vector2(0, view_size.y));
@ -794,15 +786,15 @@ void CurveEdit::_redraw() {
const Color grid_color = get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.1);
const Vector2i grid_steps = Vector2i(4, 2);
const Vector2 step_size = Vector2(1, curve->get_range()) / grid_steps;
const Vector2 step_size = Vector2(curve->get_domain_range(), curve->get_value_range()) / grid_steps;
draw_line(Vector2(min_edge.x, curve->get_min_value()), Vector2(max_edge.x, curve->get_min_value()), grid_color_primary);
draw_line(Vector2(max_edge.x, curve->get_max_value()), Vector2(min_edge.x, curve->get_max_value()), grid_color_primary);
draw_line(Vector2(0, min_edge.y), Vector2(0, max_edge.y), grid_color_primary);
draw_line(Vector2(1, max_edge.y), Vector2(1, min_edge.y), grid_color_primary);
draw_line(Vector2(curve->get_min_domain(), min_edge.y), Vector2(curve->get_min_domain(), max_edge.y), grid_color_primary);
draw_line(Vector2(curve->get_max_domain(), max_edge.y), Vector2(curve->get_max_domain(), min_edge.y), grid_color_primary);
for (int i = 1; i < grid_steps.x; i++) {
real_t x = i * step_size.x;
real_t x = curve->get_min_domain() + i * step_size.x;
draw_line(Vector2(x, min_edge.y), Vector2(x, max_edge.y), grid_color);
}
@ -819,30 +811,26 @@ void CurveEdit::_redraw() {
float font_height = font->get_height(font_size);
Color text_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
int pad = Math::round(2 * EDSCALE);
for (int i = 0; i <= grid_steps.x; ++i) {
real_t x = i * step_size.x;
draw_string(font, get_view_pos(Vector2(x - step_size.x / 2, curve->get_min_value())) + Vector2(0, font_height - Math::round(2 * EDSCALE)), String::num(x, 2), HORIZONTAL_ALIGNMENT_CENTER, get_view_pos(Vector2(step_size.x, 0)).x, font_size, text_color);
real_t x = curve->get_min_domain() + i * step_size.x;
draw_string(font, get_view_pos(Vector2(x, curve->get_min_value())) + Vector2(pad, font_height - pad), String::num(x, 2), HORIZONTAL_ALIGNMENT_CENTER, -1, font_size, text_color);
}
for (int i = 0; i <= grid_steps.y; ++i) {
real_t y = curve->get_min_value() + i * step_size.y;
draw_string(font, get_view_pos(Vector2(0, y)) + Vector2(2, -2), String::num(y, 2), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);
draw_string(font, get_view_pos(Vector2(curve->get_min_domain(), y)) + Vector2(pad, -pad), String::num(y, 2), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);
}
// Draw curve.
// An unusual transform so we can offset the curve before scaling it up, allowing the curve to be antialiased.
// The scaling up ensures that the curve rendering doesn't break when we use a quad line to draw it.
draw_set_transform_matrix(Transform2D(0, get_view_pos(Vector2(0, 0))));
// Draw curve in view coordinates. Curve world-to-view point conversion happens in plot_curve_accurate().
const Color line_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
const Color edge_line_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor)) * Color(1, 1, 1, 0.75);
CanvasItemPlotCurve plot_func(*this, line_color, edge_line_color);
plot_curve_accurate(**curve, 2.f, (get_view_pos(Vector2(1, curve->get_max_value())) - get_view_pos(Vector2(0, curve->get_min_value()))) / Vector2(1, curve->get_range()), plot_func);
plot_curve_accurate(STEP_SIZE, line_color, edge_line_color);
// Draw points, except for the selected one.
draw_set_transform_matrix(Transform2D());
bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT);
@ -934,8 +922,8 @@ void CurveEdit::_redraw() {
draw_set_transform_matrix(_world_to_view);
if (Input::get_singleton()->is_key_pressed(Key::ALT) && grabbing != GRAB_NONE && selected_tangent_index == TANGENT_NONE) {
float prev_point_offset = (selected_index > 0) ? curve->get_point_position(selected_index - 1).x : 0.0;
float next_point_offset = (selected_index < curve->get_point_count() - 1) ? curve->get_point_position(selected_index + 1).x : 1.0;
float prev_point_offset = (selected_index > 0) ? curve->get_point_position(selected_index - 1).x : curve->get_min_domain();
float next_point_offset = (selected_index < curve->get_point_count() - 1) ? curve->get_point_position(selected_index + 1).x : curve->get_max_domain();
draw_line(Vector2(prev_point_offset, curve->get_min_value()), Vector2(prev_point_offset, curve->get_max_value()), Color(point_color, 0.6));
draw_line(Vector2(next_point_offset, curve->get_min_value()), Vector2(next_point_offset, curve->get_max_value()), Color(point_color, 0.6));
@ -943,7 +931,7 @@ void CurveEdit::_redraw() {
if (shift_pressed && grabbing != GRAB_NONE && selected_tangent_index == TANGENT_NONE) {
draw_line(Vector2(initial_grab_pos.x, curve->get_min_value()), Vector2(initial_grab_pos.x, curve->get_max_value()), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)).darkened(0.4));
draw_line(Vector2(0, initial_grab_pos.y), Vector2(1, initial_grab_pos.y), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)).darkened(0.4));
draw_line(Vector2(curve->get_min_domain(), initial_grab_pos.y), Vector2(curve->get_max_domain(), initial_grab_pos.y), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)).darkened(0.4));
}
}
@ -972,7 +960,7 @@ void CurveEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
spacing = Math::round(BASE_SPACING * get_theme_default_base_scale());
snap_button->set_icon(get_editor_theme_icon(SNAME("SnapGrid")));
snap_button->set_button_icon(get_editor_theme_icon(SNAME("SnapGrid")));
PopupMenu *p = presets_button->get_popup();
p->clear();
p->add_icon_item(get_editor_theme_icon(SNAME("CurveConstant")), TTR("Constant"), CurveEdit::PRESET_CONSTANT);
@ -1003,7 +991,7 @@ CurveEditor::CurveEditor() {
snap_button->set_tooltip_text(TTR("Toggle Grid Snap"));
snap_button->set_toggle_mode(true);
toolbar->add_child(snap_button);
snap_button->connect("toggled", callable_mp(this, &CurveEditor::_set_snap_enabled));
snap_button->connect(SceneStringName(toggled), callable_mp(this, &CurveEditor::_set_snap_enabled));
toolbar->add_child(memnew(VSeparator));
@ -1079,15 +1067,15 @@ Ref<Texture2D> CurvePreviewGenerator::generate(const Ref<Resource> &p_from, cons
Color line_color = EditorInterface::get_singleton()->get_editor_theme()->get_color(SceneStringName(font_color), EditorStringName(Editor));
// Set the first pixel of the thumbnail.
float v = (curve->sample_baked(0) - curve->get_min_value()) / curve->get_range();
float v = (curve->sample_baked(curve->get_min_domain()) - curve->get_min_value()) / curve->get_value_range();
int y = CLAMP(im.get_height() - v * im.get_height(), 0, im.get_height() - 1);
im.set_pixel(0, y, line_color);
// Plot a line towards the next point.
int prev_y = y;
for (int x = 1; x < im.get_width(); ++x) {
float t = static_cast<float>(x) / im.get_width();
v = (curve->sample_baked(t) - curve->get_min_value()) / curve->get_range();
float t = static_cast<float>(x) / im.get_width() * curve->get_domain_range() + curve->get_min_domain();
v = (curve->sample_baked(t) - curve->get_min_value()) / curve->get_value_range();
y = CLAMP(im.get_height() - v * im.get_height(), 0, im.get_height() - 1);
Vector<Point2i> points = Geometry2D::bresenham_line(Point2i(x - 1, prev_y), Point2i(x, y));

View file

@ -78,14 +78,14 @@ private:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
void _curve_changed();
int get_point_at(Vector2 p_pos) const;
TangentIndex get_tangent_at(Vector2 p_pos) const;
int get_point_at(const Vector2 &p_pos) const;
TangentIndex get_tangent_at(const Vector2 &p_pos) const;
float get_offset_without_collision(int p_current_index, float p_offset, bool p_prioritize_right = true);
void add_point(Vector2 p_pos);
void add_point(const Vector2 &p_pos);
void remove_point(int p_index);
void set_point_position(int p_index, Vector2 p_pos);
void set_point_position(int p_index, const Vector2 &p_pos);
void set_point_tangents(int p_index, float p_left, float p_right);
void set_point_left_tangent(int p_index, float p_tangent);
@ -94,17 +94,20 @@ private:
void update_view_transform();
void plot_curve_accurate(float p_step, const Color &p_line_color, const Color &p_edge_line_color);
void set_selected_index(int p_index);
void set_selected_tangent_index(TangentIndex p_tangent);
Vector2 get_tangent_view_pos(int p_index, TangentIndex p_tangent) const;
Vector2 get_view_pos(Vector2 p_world_pos) const;
Vector2 get_world_pos(Vector2 p_view_pos) const;
Vector2 get_view_pos(const Vector2 &p_world_pos) const;
Vector2 get_world_pos(const Vector2 &p_view_pos) const;
void _redraw();
private:
const float ASPECT_RATIO = 6.f / 13.f;
const float LINE_WIDTH = 0.5f;
const int STEP_SIZE = 2; // Number of pixels between plot points.
Transform2D _world_to_view;
@ -136,9 +139,9 @@ private:
};
GrabMode grabbing = GRAB_NONE;
Vector2 initial_grab_pos;
int initial_grab_index;
float initial_grab_left_tangent;
float initial_grab_right_tangent;
int initial_grab_index = -1;
float initial_grab_left_tangent = 0;
float initial_grab_right_tangent = 0;
bool snap_enabled = false;
int snap_count = 10;
@ -185,7 +188,7 @@ class CurveEditorPlugin : public EditorPlugin {
public:
CurveEditorPlugin();
virtual String get_name() const override { return "Curve"; }
virtual String get_plugin_name() const override { return "Curve"; }
};
class CurvePreviewGenerator : public EditorResourcePreviewGenerator {

View file

@ -40,59 +40,57 @@
#include "editor/gui/editor_bottom_panel.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/run_instances_dialog.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/menu_button.h"
DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) {
EditorDebuggerServer::initialize();
ED_SHORTCUT("debugger/step_into", TTR("Step Into"), Key::F11);
ED_SHORTCUT("debugger/step_over", TTR("Step Over"), Key::F10);
ED_SHORTCUT("debugger/break", TTR("Break"));
ED_SHORTCUT("debugger/continue", TTR("Continue"), Key::F12);
ED_SHORTCUT("debugger/debug_with_external_editor", TTR("Debug with External Editor"));
ED_SHORTCUT("debugger/step_into", TTRC("Step Into"), Key::F11);
ED_SHORTCUT("debugger/step_over", TTRC("Step Over"), Key::F10);
ED_SHORTCUT("debugger/break", TTRC("Break"));
ED_SHORTCUT("debugger/continue", TTRC("Continue"), Key::F12);
ED_SHORTCUT("debugger/debug_with_external_editor", TTRC("Debug with External Editor"));
// File Server for deploy with remote filesystem.
file_server = memnew(EditorFileServer);
EditorDebuggerNode *debugger = memnew(EditorDebuggerNode);
Button *db = EditorNode::get_bottom_panel()->add_item(TTR("Debugger"), debugger, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_debugger_bottom_panel", TTR("Toggle Debugger Bottom Panel"), KeyModifierMask::ALT | Key::D));
Button *db = EditorNode::get_bottom_panel()->add_item(TTR("Debugger"), debugger, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_debugger_bottom_panel", TTRC("Toggle Debugger Bottom Panel"), KeyModifierMask::ALT | Key::D));
debugger->set_tool_button(db);
// Main editor debug menu.
debug_menu = p_debug_menu;
debug_menu->set_hide_on_checkable_item_selection(false);
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/deploy_with_remote_debug", TTR("Deploy with Remote Debug")), RUN_DEPLOY_REMOTE_DEBUG);
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/deploy_with_remote_debug", TTRC("Deploy with Remote Debug")), RUN_DEPLOY_REMOTE_DEBUG);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, using one-click deploy will make the executable attempt to connect to this computer's IP so the running project can be debugged.\nThis option is intended to be used for remote debugging (typically with a mobile device).\nYou don't need to enable it to use the GDScript debugger locally."));
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/small_deploy_with_network_fs", TTR("Small Deploy with Network Filesystem")), RUN_FILE_SERVER);
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/small_deploy_with_network_fs", TTRC("Small Deploy with Network Filesystem")), RUN_FILE_SERVER);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, using one-click deploy for Android will only export an executable without the project data.\nThe filesystem will be provided from the project by the editor over the network.\nOn Android, deploying will use the USB cable for faster performance. This option speeds up testing for projects with large assets."));
debug_menu->add_separator();
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_collision_shapes", TTR("Visible Collision Shapes")), RUN_DEBUG_COLLISIONS);
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_collision_shapes", TTRC("Visible Collision Shapes")), RUN_DEBUG_COLLISIONS);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, collision shapes and raycast nodes (for 2D and 3D) will be visible in the running project."));
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_paths", TTR("Visible Paths")), RUN_DEBUG_PATHS);
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_paths", TTRC("Visible Paths")), RUN_DEBUG_PATHS);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, curve resources used by path nodes will be visible in the running project."));
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_navigation", TTR("Visible Navigation")), RUN_DEBUG_NAVIGATION);
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_navigation", TTRC("Visible Navigation")), RUN_DEBUG_NAVIGATION);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, navigation meshes, and polygons will be visible in the running project."));
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_avoidance", TTR("Visible Avoidance")), RUN_DEBUG_AVOIDANCE);
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_avoidance", TTRC("Visible Avoidance")), RUN_DEBUG_AVOIDANCE);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, avoidance object shapes, radiuses, and velocities will be visible in the running project."));
debug_menu->add_separator();
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_canvas_redraw", TTR("Debug CanvasItem Redraws")), RUN_DEBUG_CANVAS_REDRAW);
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_canvas_redraw", TTRC("Debug CanvasItem Redraws")), RUN_DEBUG_CANVAS_REDRAW);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, redraw requests of 2D objects will become visible (as a short flash) in the running project.\nThis is useful to troubleshoot low processor mode."));
debug_menu->add_separator();
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/sync_scene_changes", TTR("Synchronize Scene Changes")), RUN_LIVE_DEBUG);
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/sync_scene_changes", TTRC("Synchronize Scene Changes")), RUN_LIVE_DEBUG);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, any changes made to the scene in the editor will be replicated in the running project.\nWhen used remotely on a device, this is more efficient when the network filesystem option is enabled."));
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/sync_script_changes", TTR("Synchronize Script Changes")), RUN_RELOAD_SCRIPTS);
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/sync_script_changes", TTRC("Synchronize Script Changes")), RUN_RELOAD_SCRIPTS);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, any script that is saved will be reloaded in the running project.\nWhen used remotely on a device, this is more efficient when the network filesystem option is enabled."));
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/keep_server_open", TTR("Keep Debug Server Open")), SERVER_KEEP_OPEN);
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/keep_server_open", TTRC("Keep Debug Server Open")), SERVER_KEEP_OPEN);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, the editor debug server will stay open and listen for new sessions started outside of the editor itself."));
@ -228,7 +226,7 @@ void DebuggerEditorPlugin::_notification(int p_what) {
}
void DebuggerEditorPlugin::_update_debug_options() {
bool check_deploy_remote = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_deploy_remote_debug", false);
bool check_deploy_remote = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_deploy_remote_debug", true);
bool check_file_server = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_file_server", false);
bool check_debug_collisions = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_collisions", false);
bool check_debug_paths = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_paths", false);

View file

@ -67,7 +67,7 @@ private:
void _menu_option(int p_option);
public:
virtual String get_name() const override { return "Debugger"; }
virtual String get_plugin_name() const override { return "Debugger"; }
bool has_main_screen() const override { return false; }
DebuggerEditorPlugin(PopupMenu *p_menu);

View file

@ -121,7 +121,7 @@ Ref<Resource> DedicatedServerExportPlugin::_customize_resource(const Ref<Resourc
if (p_resource.is_valid() && current_export_mode == EditorExportPreset::MODE_FILE_STRIP && p_resource->has_method("create_placeholder")) {
Callable::CallError err;
Ref<Resource> result = const_cast<Resource *>(p_resource.ptr())->callp("create_placeholder", nullptr, 0, err);
Ref<Resource> result = p_resource->callp("create_placeholder", nullptr, 0, err);
if (err.error == Callable::CallError::CALL_OK) {
return result;
}

View file

@ -31,7 +31,7 @@
#ifndef DEDICATED_SERVER_EXPORT_PLUGIN_H
#define DEDICATED_SERVER_EXPORT_PLUGIN_H
#include "editor/export/editor_export.h"
#include "editor/export/editor_export_plugin.h"
class DedicatedServerExportPlugin : public EditorExportPlugin {
private:

View file

@ -0,0 +1,212 @@
/**************************************************************************/
/* editor_context_menu_plugin.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_context_menu_plugin.h"
#include "core/input/shortcut.h"
#include "editor/editor_string_names.h"
#include "scene/gui/popup_menu.h"
#include "scene/resources/texture.h"
void EditorContextMenuPlugin::get_options(const Vector<String> &p_paths) {
GDVIRTUAL_CALL(_popup_menu, p_paths);
}
void EditorContextMenuPlugin::add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable) {
context_menu_shortcuts.insert(p_shortcut, p_callable);
}
void EditorContextMenuPlugin::add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture) {
ERR_FAIL_COND_MSG(context_menu_items.has(p_name), "Context menu item already registered.");
ERR_FAIL_COND_MSG(context_menu_items.size() == MAX_ITEMS, "Maximum number of context menu items reached.");
ContextMenuItem item;
item.item_name = p_name;
item.callable = p_callable;
item.icon = p_texture;
context_menu_items.insert(p_name, item);
}
void EditorContextMenuPlugin::add_context_menu_item_from_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut, const Ref<Texture2D> &p_texture) {
Callable *callback = context_menu_shortcuts.getptr(p_shortcut);
ERR_FAIL_NULL_MSG(callback, "Shortcut not registered. Use add_menu_shortcut() first.");
ContextMenuItem item;
item.item_name = p_name;
item.callable = *callback;
item.icon = p_texture;
item.shortcut = p_shortcut;
context_menu_items.insert(p_name, item);
}
void EditorContextMenuPlugin::add_context_submenu_item(const String &p_name, PopupMenu *p_menu, const Ref<Texture2D> &p_texture) {
ERR_FAIL_NULL(p_menu);
ContextMenuItem item;
item.item_name = p_name;
item.icon = p_texture;
item.submenu = p_menu;
context_menu_items.insert(p_name, item);
}
void EditorContextMenuPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_menu_shortcut", "shortcut", "callback"), &EditorContextMenuPlugin::add_menu_shortcut);
ClassDB::bind_method(D_METHOD("add_context_menu_item", "name", "callback", "icon"), &EditorContextMenuPlugin::add_context_menu_item, DEFVAL(Ref<Texture2D>()));
ClassDB::bind_method(D_METHOD("add_context_menu_item_from_shortcut", "name", "shortcut", "icon"), &EditorContextMenuPlugin::add_context_menu_item_from_shortcut, DEFVAL(Ref<Texture2D>()));
ClassDB::bind_method(D_METHOD("add_context_submenu_item", "name", "menu", "icon"), &EditorContextMenuPlugin::add_context_submenu_item, DEFVAL(Ref<Texture2D>()));
GDVIRTUAL_BIND(_popup_menu, "paths");
BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TREE);
BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM);
BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR);
BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM_CREATE);
BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR_CODE);
BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TABS);
BIND_ENUM_CONSTANT(CONTEXT_SLOT_2D_EDITOR);
}
void EditorContextMenuPluginManager::add_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
ERR_FAIL_COND(p_plugin.is_null());
ERR_FAIL_COND(plugin_list.has(p_plugin));
p_plugin->slot = p_slot;
plugin_list.push_back(p_plugin);
}
void EditorContextMenuPluginManager::remove_plugin(const Ref<EditorContextMenuPlugin> &p_plugin) {
ERR_FAIL_COND(p_plugin.is_null());
ERR_FAIL_COND(!plugin_list.has(p_plugin));
plugin_list.erase(p_plugin);
}
bool EditorContextMenuPluginManager::has_plugins_for_slot(ContextMenuSlot p_slot) {
for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
if (plugin->slot == p_slot) {
return true;
}
}
return false;
}
void EditorContextMenuPluginManager::add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths) {
bool separator_added = false;
const int icon_size = p_popup->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
int id = EditorContextMenuPlugin::BASE_ID;
for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
if (plugin->slot != p_slot) {
continue;
}
plugin->context_menu_items.clear();
plugin->get_options(p_paths);
HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = plugin->context_menu_items;
if (items.size() > 0 && !separator_added) {
separator_added = true;
p_popup->add_separator();
}
for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) {
EditorContextMenuPlugin::ContextMenuItem &item = E.value;
item.id = id;
if (item.submenu) {
p_popup->add_submenu_node_item(item.item_name, item.submenu, id);
} else {
p_popup->add_item(item.item_name, id);
}
if (item.icon.is_valid()) {
p_popup->set_item_icon(-1, item.icon);
p_popup->set_item_icon_max_width(-1, icon_size);
}
if (item.shortcut.is_valid()) {
p_popup->set_item_shortcut(-1, item.shortcut, true);
}
id++;
}
}
}
Callable EditorContextMenuPluginManager::match_custom_shortcut(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<InputEvent> &p_event) {
for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
if (plugin->slot != p_slot) {
continue;
}
for (KeyValue<Ref<Shortcut>, Callable> &E : plugin->context_menu_shortcuts) {
if (E.key->matches_event(p_event)) {
return E.value;
}
}
}
return Callable();
}
bool EditorContextMenuPluginManager::activate_custom_option(ContextMenuSlot p_slot, int p_option, const Variant &p_arg) {
for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
if (plugin->slot != p_slot) {
continue;
}
for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : plugin->context_menu_items) {
if (E.value.id == p_option) {
invoke_callback(E.value.callable, p_arg);
return true;
}
}
}
return false;
}
void EditorContextMenuPluginManager::invoke_callback(const Callable &p_callback, const Variant &p_arg) {
const Variant *argptr = &p_arg;
Callable::CallError ce;
Variant result;
p_callback.callp(&argptr, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_FAIL_MSG("Failed to execute context menu callback: " + Variant::get_callable_error_text(p_callback, &argptr, 1, ce) + ".");
}
}
void EditorContextMenuPluginManager::create() {
ERR_FAIL_COND(singleton != nullptr);
singleton = memnew(EditorContextMenuPluginManager);
}
void EditorContextMenuPluginManager::cleanup() {
ERR_FAIL_NULL(singleton);
memdelete(singleton);
singleton = nullptr;
}

View file

@ -0,0 +1,117 @@
/**************************************************************************/
/* editor_context_menu_plugin.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#ifndef EDITOR_CONTEXT_MENU_PLUGIN_H
#define EDITOR_CONTEXT_MENU_PLUGIN_H
#include "core/object/gdvirtual.gen.inc"
#include "core/object/ref_counted.h"
class InputEvent;
class PopupMenu;
class Shortcut;
class Texture2D;
class EditorContextMenuPlugin : public RefCounted {
GDCLASS(EditorContextMenuPlugin, RefCounted);
friend class EditorContextMenuPluginManager;
inline static constexpr int MAX_ITEMS = 100;
public:
enum ContextMenuSlot {
CONTEXT_SLOT_SCENE_TREE,
CONTEXT_SLOT_FILESYSTEM,
CONTEXT_SLOT_SCRIPT_EDITOR,
CONTEXT_SLOT_FILESYSTEM_CREATE,
CONTEXT_SLOT_SCRIPT_EDITOR_CODE,
CONTEXT_SLOT_SCENE_TABS,
CONTEXT_SLOT_2D_EDITOR,
};
inline static constexpr int BASE_ID = 2000;
private:
int slot = -1;
public:
struct ContextMenuItem {
int id = 0;
String item_name;
Callable callable;
Ref<Texture2D> icon;
Ref<Shortcut> shortcut;
PopupMenu *submenu = nullptr;
};
HashMap<String, ContextMenuItem> context_menu_items;
HashMap<Ref<Shortcut>, Callable> context_menu_shortcuts;
protected:
static void _bind_methods();
GDVIRTUAL1(_popup_menu, Vector<String>);
public:
virtual void get_options(const Vector<String> &p_paths);
void add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable);
void add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture);
void add_context_menu_item_from_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut, const Ref<Texture2D> &p_texture);
void add_context_submenu_item(const String &p_name, PopupMenu *p_menu, const Ref<Texture2D> &p_texture);
};
VARIANT_ENUM_CAST(EditorContextMenuPlugin::ContextMenuSlot);
class EditorContextMenuPluginManager : public Object {
GDCLASS(EditorContextMenuPluginManager, Object);
using ContextMenuSlot = EditorContextMenuPlugin::ContextMenuSlot;
static inline EditorContextMenuPluginManager *singleton = nullptr;
LocalVector<Ref<EditorContextMenuPlugin>> plugin_list;
public:
static EditorContextMenuPluginManager *get_singleton() { return singleton; }
void add_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
void remove_plugin(const Ref<EditorContextMenuPlugin> &p_plugin);
bool has_plugins_for_slot(ContextMenuSlot p_slot);
void add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths);
Callable match_custom_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event);
bool activate_custom_option(ContextMenuSlot p_slot, int p_option, const Variant &p_arg);
void invoke_callback(const Callable &p_callback, const Variant &p_arg);
static void create();
static void cleanup();
};
#endif // EDITOR_CONTEXT_MENU_PLUGIN_H

View file

@ -40,6 +40,7 @@
#include "editor/editor_translation_parser.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/export/editor_export.h"
#include "editor/export/editor_export_platform.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/gui/editor_title_bar.h"
#include "editor/import/3d/resource_importer_scene.h"
@ -54,8 +55,6 @@
#include "editor/scene_tree_dock.h"
#include "scene/3d/camera_3d.h"
#include "scene/gui/popup_menu.h"
#include "scene/resources/image_texture.h"
#include "servers/rendering_server.h"
void EditorPlugin::add_custom_type(const String &p_type, const String &p_base, const Ref<Script> &p_script, const Ref<Texture2D> &p_icon) {
EditorNode::get_editor_data().add_custom_type(p_type, p_base, p_script, p_icon);
@ -152,7 +151,6 @@ void EditorPlugin::add_control_to_container(CustomControlContainer p_location, C
} break;
case CONTAINER_PROJECT_SETTING_TAB_RIGHT: {
ProjectSettingsEditor::get_singleton()->get_tabs()->add_child(p_control);
ProjectSettingsEditor::get_singleton()->get_tabs()->move_child(p_control, 1);
} break;
}
@ -307,13 +305,13 @@ void EditorPlugin::forward_3d_force_draw_over_viewport(Control *p_overlay) {
GDVIRTUAL_CALL(_forward_3d_force_draw_over_viewport, p_overlay);
}
String EditorPlugin::get_name() const {
String EditorPlugin::get_plugin_name() const {
String name;
GDVIRTUAL_CALL(_get_plugin_name, name);
return name;
}
const Ref<Texture2D> EditorPlugin::get_icon() const {
const Ref<Texture2D> EditorPlugin::get_plugin_icon() const {
Ref<Texture2D> icon;
GDVIRTUAL_CALL(_get_plugin_icon, icon);
return icon;
@ -402,17 +400,17 @@ void EditorPlugin::remove_undo_redo_inspector_hook_callback(Callable p_callable)
}
void EditorPlugin::add_translation_parser_plugin(const Ref<EditorTranslationParserPlugin> &p_parser) {
ERR_FAIL_COND(!p_parser.is_valid());
ERR_FAIL_COND(p_parser.is_null());
EditorTranslationParser::get_singleton()->add_parser(p_parser, EditorTranslationParser::CUSTOM);
}
void EditorPlugin::remove_translation_parser_plugin(const Ref<EditorTranslationParserPlugin> &p_parser) {
ERR_FAIL_COND(!p_parser.is_valid());
ERR_FAIL_COND(p_parser.is_null());
EditorTranslationParser::get_singleton()->remove_parser(p_parser, EditorTranslationParser::CUSTOM);
}
void EditorPlugin::add_import_plugin(const Ref<EditorImportPlugin> &p_importer, bool p_first_priority) {
ERR_FAIL_COND(!p_importer.is_valid());
ERR_FAIL_COND(p_importer.is_null());
ResourceFormatImporter::get_singleton()->add_importer(p_importer, p_first_priority);
// Plugins are now loaded during the first scan. It's important not to start another scan,
// even a deferred one, as it would cause a scan during a scan at the next main thread iteration.
@ -422,7 +420,7 @@ void EditorPlugin::add_import_plugin(const Ref<EditorImportPlugin> &p_importer,
}
void EditorPlugin::remove_import_plugin(const Ref<EditorImportPlugin> &p_importer) {
ERR_FAIL_COND(!p_importer.is_valid());
ERR_FAIL_COND(p_importer.is_null());
ResourceFormatImporter::get_singleton()->remove_importer(p_importer);
// Plugins are now loaded during the first scan. It's important not to start another scan,
// even a deferred one, as it would cause a scan during a scan at the next main thread iteration.
@ -432,42 +430,52 @@ void EditorPlugin::remove_import_plugin(const Ref<EditorImportPlugin> &p_importe
}
void EditorPlugin::add_export_plugin(const Ref<EditorExportPlugin> &p_exporter) {
ERR_FAIL_COND(!p_exporter.is_valid());
ERR_FAIL_COND(p_exporter.is_null());
EditorExport::get_singleton()->add_export_plugin(p_exporter);
}
void EditorPlugin::remove_export_plugin(const Ref<EditorExportPlugin> &p_exporter) {
ERR_FAIL_COND(!p_exporter.is_valid());
ERR_FAIL_COND(p_exporter.is_null());
EditorExport::get_singleton()->remove_export_plugin(p_exporter);
}
void EditorPlugin::add_export_platform(const Ref<EditorExportPlatform> &p_platform) {
ERR_FAIL_COND(p_platform.is_null());
EditorExport::get_singleton()->add_export_platform(p_platform);
}
void EditorPlugin::remove_export_platform(const Ref<EditorExportPlatform> &p_platform) {
ERR_FAIL_COND(p_platform.is_null());
EditorExport::get_singleton()->remove_export_platform(p_platform);
}
void EditorPlugin::add_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin) {
ERR_FAIL_COND(!p_gizmo_plugin.is_valid());
ERR_FAIL_COND(p_gizmo_plugin.is_null());
Node3DEditor::get_singleton()->add_gizmo_plugin(p_gizmo_plugin);
}
void EditorPlugin::remove_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin) {
ERR_FAIL_COND(!p_gizmo_plugin.is_valid());
ERR_FAIL_COND(p_gizmo_plugin.is_null());
Node3DEditor::get_singleton()->remove_gizmo_plugin(p_gizmo_plugin);
}
void EditorPlugin::add_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin) {
ERR_FAIL_COND(!p_plugin.is_valid());
ERR_FAIL_COND(p_plugin.is_null());
EditorInspector::add_inspector_plugin(p_plugin);
}
void EditorPlugin::remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin) {
ERR_FAIL_COND(!p_plugin.is_valid());
ERR_FAIL_COND(p_plugin.is_null());
EditorInspector::remove_inspector_plugin(p_plugin);
}
void EditorPlugin::add_scene_format_importer_plugin(const Ref<EditorSceneFormatImporter> &p_importer, bool p_first_priority) {
ERR_FAIL_COND(!p_importer.is_valid());
ERR_FAIL_COND(p_importer.is_null());
ResourceImporterScene::add_scene_importer(p_importer, p_first_priority);
}
void EditorPlugin::remove_scene_format_importer_plugin(const Ref<EditorSceneFormatImporter> &p_importer) {
ERR_FAIL_COND(!p_importer.is_valid());
ERR_FAIL_COND(p_importer.is_null());
ResourceImporterScene::remove_scene_importer(p_importer);
}
@ -479,6 +487,14 @@ void EditorPlugin::remove_scene_post_import_plugin(const Ref<EditorScenePostImpo
ResourceImporterScene::remove_post_importer_plugin(p_plugin);
}
void EditorPlugin::add_context_menu_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
EditorContextMenuPluginManager::get_singleton()->add_plugin(p_slot, p_plugin);
}
void EditorPlugin::remove_context_menu_plugin(const Ref<EditorContextMenuPlugin> &p_plugin) {
EditorContextMenuPluginManager::get_singleton()->remove_plugin(p_plugin);
}
int find(const PackedStringArray &a, const String &v) {
const String *r = a.ptr();
for (int j = 0; j < a.size(); ++j) {
@ -608,6 +624,8 @@ void EditorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_scene_post_import_plugin", "scene_import_plugin"), &EditorPlugin::remove_scene_post_import_plugin);
ClassDB::bind_method(D_METHOD("add_export_plugin", "plugin"), &EditorPlugin::add_export_plugin);
ClassDB::bind_method(D_METHOD("remove_export_plugin", "plugin"), &EditorPlugin::remove_export_plugin);
ClassDB::bind_method(D_METHOD("add_export_platform", "platform"), &EditorPlugin::add_export_platform);
ClassDB::bind_method(D_METHOD("remove_export_platform", "platform"), &EditorPlugin::remove_export_platform);
ClassDB::bind_method(D_METHOD("add_node_3d_gizmo_plugin", "plugin"), &EditorPlugin::add_node_3d_gizmo_plugin);
ClassDB::bind_method(D_METHOD("remove_node_3d_gizmo_plugin", "plugin"), &EditorPlugin::remove_node_3d_gizmo_plugin);
ClassDB::bind_method(D_METHOD("add_inspector_plugin", "plugin"), &EditorPlugin::add_inspector_plugin);
@ -616,6 +634,8 @@ void EditorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_resource_conversion_plugin", "plugin"), &EditorPlugin::remove_resource_conversion_plugin);
ClassDB::bind_method(D_METHOD("set_input_event_forwarding_always_enabled"), &EditorPlugin::set_input_event_forwarding_always_enabled);
ClassDB::bind_method(D_METHOD("set_force_draw_over_forwarding_enabled"), &EditorPlugin::set_force_draw_over_forwarding_enabled);
ClassDB::bind_method(D_METHOD("add_context_menu_plugin", "slot", "plugin"), &EditorPlugin::add_context_menu_plugin);
ClassDB::bind_method(D_METHOD("remove_context_menu_plugin", "plugin"), &EditorPlugin::remove_context_menu_plugin);
ClassDB::bind_method(D_METHOD("get_editor_interface"), &EditorPlugin::get_editor_interface);
ClassDB::bind_method(D_METHOD("get_script_create_dialog"), &EditorPlugin::get_script_create_dialog);

View file

@ -32,6 +32,7 @@
#define EDITOR_PLUGIN_H
#include "core/io/config_file.h"
#include "editor/plugins/editor_context_menu_plugin.h"
#include "scene/3d/camera_3d.h"
#include "scene/gui/control.h"
@ -41,6 +42,7 @@ class PopupMenu;
class EditorDebuggerPlugin;
class EditorExport;
class EditorExportPlugin;
class EditorExportPlatform;
class EditorImportPlugin;
class EditorInspectorPlugin;
class EditorInterface;
@ -179,8 +181,8 @@ public:
virtual void forward_3d_draw_over_viewport(Control *p_overlay);
virtual void forward_3d_force_draw_over_viewport(Control *p_overlay);
virtual String get_name() const;
virtual const Ref<Texture2D> get_icon() const;
virtual String get_plugin_name() const;
virtual const Ref<Texture2D> get_plugin_icon() const;
virtual String get_plugin_version() const;
virtual void set_plugin_version(const String &p_version);
virtual bool has_main_screen() const;
@ -224,6 +226,9 @@ public:
void add_export_plugin(const Ref<EditorExportPlugin> &p_exporter);
void remove_export_plugin(const Ref<EditorExportPlugin> &p_exporter);
void add_export_platform(const Ref<EditorExportPlatform> &p_platform);
void remove_export_platform(const Ref<EditorExportPlatform> &p_platform);
void add_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin);
void remove_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin);
@ -245,6 +250,9 @@ public:
void add_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin);
void remove_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin);
void add_context_menu_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
void remove_context_menu_plugin(const Ref<EditorContextMenuPlugin> &p_plugin);
void enable_plugin();
void disable_plugin();

View file

@ -34,11 +34,12 @@
#include "core/io/config_file.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/os/main_loop.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/margin_container.h"
#include "scene/gui/separator.h"
#include "scene/gui/texture_rect.h"
#include "scene/gui/tree.h"
void EditorPluginSettings::_notification(int p_what) {
@ -51,6 +52,12 @@ void EditorPluginSettings::_notification(int p_what) {
plugin_config_dialog->connect("plugin_ready", callable_mp(EditorNode::get_singleton(), &EditorNode::_on_plugin_ready));
plugin_list->connect("button_clicked", callable_mp(this, &EditorPluginSettings::_cell_button_pressed));
} break;
case NOTIFICATION_THEME_CHANGED: {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
recovery_mode_icon->set_texture(get_editor_theme_icon(SNAME("NodeWarning")));
}
} break;
}
}
@ -199,9 +206,6 @@ Vector<String> EditorPluginSettings::_get_plugins(const String &p_dir) {
return plugins;
}
void EditorPluginSettings::_bind_methods() {
}
EditorPluginSettings::EditorPluginSettings() {
ProjectSettings::get_singleton()->add_hidden_prefix("editor_plugins/");
@ -209,6 +213,23 @@ EditorPluginSettings::EditorPluginSettings() {
plugin_config_dialog->config("");
add_child(plugin_config_dialog);
if (Engine::get_singleton()->is_recovery_mode_hint()) {
HBoxContainer *c = memnew(HBoxContainer);
add_child(c);
recovery_mode_icon = memnew(TextureRect);
recovery_mode_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
c->add_child(recovery_mode_icon);
Label *recovery_mode_label = memnew(Label(TTR("Recovery mode is enabled. Enabled plugins will not run while this mode is active.")));
recovery_mode_label->set_theme_type_variation("HeaderSmall");
recovery_mode_label->set_h_size_flags(SIZE_EXPAND_FILL);
c->add_child(recovery_mode_label);
HSeparator *sep = memnew(HSeparator);
add_child(sep);
}
HBoxContainer *title_hb = memnew(HBoxContainer);
Label *label = memnew(Label(TTR("Installed Plugins:")));
label->set_theme_type_variation("HeaderSmall");

View file

@ -31,9 +31,9 @@
#ifndef EDITOR_PLUGIN_SETTINGS_H
#define EDITOR_PLUGIN_SETTINGS_H
#include "editor/editor_data.h"
#include "editor/plugins/plugin_config_dialog.h"
class TextureRect;
class Tree;
class EditorPluginSettings : public VBoxContainer {
@ -55,6 +55,7 @@ class EditorPluginSettings : public VBoxContainer {
};
PluginConfigDialog *plugin_config_dialog = nullptr;
TextureRect *recovery_mode_icon = nullptr;
Tree *plugin_list = nullptr;
bool updating = false;
@ -67,8 +68,6 @@ class EditorPluginSettings : public VBoxContainer {
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void update_plugins();

View file

@ -31,10 +31,9 @@
#include "editor_preview_plugins.h"
#include "core/config/project_settings.h"
#include "core/io/file_access_memory.h"
#include "core/io/image.h"
#include "core/io/resource_loader.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
#include "editor/themes/editor_scale.h"
@ -76,7 +75,7 @@ void post_process_preview(Ref<Image> p_image) {
}
bool EditorTexturePreviewPlugin::handles(const String &p_type) const {
return ClassDB::is_parent_class(p_type, "Texture2D");
return ClassDB::is_parent_class(p_type, "Texture");
}
bool EditorTexturePreviewPlugin::generate_small_preview_automatically() const {
@ -85,23 +84,64 @@ bool EditorTexturePreviewPlugin::generate_small_preview_automatically() const {
Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
Ref<Image> img;
Ref<AtlasTexture> atex = p_from;
if (atex.is_valid()) {
Ref<Texture2D> tex = atex->get_atlas();
if (!tex.is_valid()) {
Ref<AtlasTexture> tex_atlas = p_from;
Ref<Texture3D> tex_3d = p_from;
Ref<TextureLayered> tex_lyr = p_from;
if (tex_atlas.is_valid()) {
Ref<Texture2D> tex = tex_atlas->get_atlas();
if (tex.is_null()) {
return Ref<Texture2D>();
}
Ref<Image> atlas = tex->get_image();
if (!atlas.is_valid()) {
if (atlas.is_null()) {
return Ref<Texture2D>();
}
if (!atex->get_region().has_area()) {
if (atlas->is_compressed()) {
atlas = atlas->duplicate();
if (atlas->decompress() != OK) {
return Ref<Texture2D>();
}
}
if (!tex_atlas->get_region().has_area()) {
return Ref<Texture2D>();
}
img = atlas->get_region(atex->get_region());
img = atlas->get_region(tex_atlas->get_region());
} else if (tex_3d.is_valid()) {
if (tex_3d->get_depth() == 0) {
return Ref<Texture2D>();
}
Vector<Ref<Image>> data = tex_3d->get_data();
if (data.size() != tex_3d->get_depth()) {
return Ref<Texture2D>();
}
// Use the middle slice for the thumbnail.
const int mid_depth = (tex_3d->get_depth() - 1) / 2;
if (!data.is_empty() && data[mid_depth].is_valid()) {
img = data[mid_depth]->duplicate();
}
} else if (tex_lyr.is_valid()) {
if (tex_lyr->get_layers() == 0) {
return Ref<Texture2D>();
}
// Use the middle slice for the thumbnail.
const int mid_layer = (tex_lyr->get_layers() - 1) / 2;
Ref<Image> data = tex_lyr->get_layer_data(mid_layer);
if (data.is_valid()) {
img = data->duplicate();
}
} else {
Ref<Texture2D> tex = p_from;
if (tex.is_valid()) {
@ -115,6 +155,7 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from,
if (img.is_null() || img->is_empty()) {
return Ref<Texture2D>();
}
p_metadata["dimensions"] = img->get_size();
img->clear_mipmaps();
@ -313,7 +354,7 @@ Ref<Texture2D> EditorMaterialPreviewPlugin::generate(const Ref<Resource> &p_from
Ref<Image> img = RS::get_singleton()->texture_2d_get(viewport_texture);
RS::get_singleton()->mesh_surface_set_material(sphere, 0, RID());
ERR_FAIL_COND_V(!img.is_valid(), Ref<ImageTexture>());
ERR_FAIL_COND_V(img.is_null(), Ref<ImageTexture>());
img->convert(Image::FORMAT_RGBA8);
int thumbnail_size = MAX(p_size.x, p_size.y);

View file

@ -31,7 +31,6 @@
#ifndef EDITOR_PREVIEW_PLUGINS_H
#define EDITOR_PREVIEW_PLUGINS_H
#include "core/templates/safe_refcount.h"
#include "editor/editor_resource_preview.h"
class ScriptLanguage;
@ -96,7 +95,7 @@ class EditorMaterialPreviewPlugin : public EditorResourcePreviewGenerator {
RID light_instance2;
RID camera;
RID camera_attributes;
DrawRequester draw_requester;
mutable DrawRequester draw_requester;
public:
virtual bool handles(const String &p_type) const override;
@ -144,7 +143,7 @@ class EditorMeshPreviewPlugin : public EditorResourcePreviewGenerator {
RID light_instance2;
RID camera;
RID camera_attributes;
DrawRequester draw_requester;
mutable DrawRequester draw_requester;
public:
virtual bool handles(const String &p_type) const override;
@ -162,7 +161,7 @@ class EditorFontPreviewPlugin : public EditorResourcePreviewGenerator {
RID viewport_texture;
RID canvas;
RID canvas_item;
DrawRequester draw_requester;
mutable DrawRequester draw_requester;
public:
virtual bool handles(const String &p_type) const override;

View file

@ -0,0 +1,415 @@
/**************************************************************************/
/* embedded_process.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "embedded_process.h"
#include "editor/editor_string_names.h"
#include "scene/main/window.h"
#include "scene/resources/style_box_flat.h"
#include "scene/theme/theme_db.h"
void EmbeddedProcess::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
window = get_window();
} break;
case NOTIFICATION_PROCESS: {
if (updated_embedded_process_queued) {
updated_embedded_process_queued = false;
_update_embedded_process();
}
} break;
case NOTIFICATION_DRAW: {
_draw();
} break;
case NOTIFICATION_RESIZED:
case NOTIFICATION_VISIBILITY_CHANGED:
case NOTIFICATION_WM_POSITION_CHANGED: {
queue_update_embedded_process();
} break;
case NOTIFICATION_THEME_CHANGED: {
focus_style_box = get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles));
Ref<StyleBoxFlat> focus_style_box_flat = focus_style_box;
if (focus_style_box_flat.is_valid()) {
margin_top_left = Point2i(focus_style_box_flat->get_border_width(SIDE_LEFT), focus_style_box_flat->get_border_width(SIDE_TOP));
margin_bottom_right = Point2i(focus_style_box_flat->get_border_width(SIDE_RIGHT), focus_style_box_flat->get_border_width(SIDE_BOTTOM));
} else if (focus_style_box.is_valid()) {
margin_top_left = Point2i(focus_style_box->get_margin(SIDE_LEFT), focus_style_box->get_margin(SIDE_TOP));
margin_bottom_right = Point2i(focus_style_box->get_margin(SIDE_RIGHT), focus_style_box->get_margin(SIDE_BOTTOM));
} else {
margin_top_left = Point2i();
margin_bottom_right = Point2i();
}
} break;
case NOTIFICATION_FOCUS_ENTER: {
queue_update_embedded_process();
} break;
case NOTIFICATION_APPLICATION_FOCUS_IN: {
application_has_focus = true;
last_application_focus_time = OS::get_singleton()->get_ticks_msec();
} break;
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
application_has_focus = false;
} break;
}
}
void EmbeddedProcess::set_window_size(const Size2i p_window_size) {
if (window_size != p_window_size) {
window_size = p_window_size;
queue_update_embedded_process();
}
}
void EmbeddedProcess::set_keep_aspect(bool p_keep_aspect) {
if (keep_aspect != p_keep_aspect) {
keep_aspect = p_keep_aspect;
queue_update_embedded_process();
}
}
Rect2i EmbeddedProcess::_get_global_embedded_window_rect() {
Rect2i control_rect = get_global_rect();
control_rect = Rect2i(control_rect.position + margin_top_left, (control_rect.size - get_margins_size()).maxi(1));
if (window_size != Size2i()) {
Rect2i desired_rect = Rect2i();
if (!keep_aspect && control_rect.size.x >= window_size.x && control_rect.size.y >= window_size.y) {
// Fixed at the desired size.
desired_rect.size = window_size;
} else {
float ratio = MIN((float)control_rect.size.x / window_size.x, (float)control_rect.size.y / window_size.y);
desired_rect.size = Size2i(window_size.x * ratio, window_size.y * ratio).maxi(1);
}
desired_rect.position = Size2i(control_rect.position.x + ((control_rect.size.x - desired_rect.size.x) / 2), control_rect.position.y + ((control_rect.size.y - desired_rect.size.y) / 2));
return desired_rect;
} else {
// Stretch, use all the control area.
return control_rect;
}
}
Rect2i EmbeddedProcess::get_screen_embedded_window_rect() {
Rect2i rect = _get_global_embedded_window_rect();
if (window) {
rect.position += window->get_position();
}
return rect;
}
int EmbeddedProcess::get_margin_size(Side p_side) const {
ERR_FAIL_INDEX_V((int)p_side, 4, 0);
switch (p_side) {
case SIDE_LEFT:
return margin_top_left.x;
case SIDE_RIGHT:
return margin_bottom_right.x;
case SIDE_TOP:
return margin_top_left.y;
case SIDE_BOTTOM:
return margin_bottom_right.y;
}
return 0;
}
Size2 EmbeddedProcess::get_margins_size() {
return margin_top_left + margin_bottom_right;
}
bool EmbeddedProcess::is_embedding_in_progress() {
return !timer_embedding->is_stopped();
}
bool EmbeddedProcess::is_embedding_completed() {
return embedding_completed;
}
int EmbeddedProcess::get_embedded_pid() const {
return current_process_id;
}
void EmbeddedProcess::embed_process(OS::ProcessID p_pid) {
if (!window) {
return;
}
ERR_FAIL_COND_MSG(!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING), "Embedded process not supported by this display server.");
if (current_process_id != 0) {
// Stop embedding the last process.
OS::get_singleton()->kill(current_process_id);
}
reset();
current_process_id = p_pid;
start_embedding_time = OS::get_singleton()->get_ticks_msec();
embedding_grab_focus = has_focus();
timer_update_embedded_process->start();
set_process(true);
set_notify_transform(true);
// Attempt to embed the process, but if it has just started and the window is not ready yet,
// we will retry in this case.
_try_embed_process();
}
void EmbeddedProcess::reset() {
if (current_process_id != 0 && embedding_completed) {
DisplayServer::get_singleton()->remove_embedded_process(current_process_id);
}
current_process_id = 0;
embedding_completed = false;
start_embedding_time = 0;
embedding_grab_focus = false;
timer_embedding->stop();
timer_update_embedded_process->stop();
set_process(false);
set_notify_transform(false);
queue_redraw();
}
void EmbeddedProcess::request_close() {
if (current_process_id != 0 && embedding_completed) {
DisplayServer::get_singleton()->request_close_embedded_process(current_process_id);
}
}
void EmbeddedProcess::_try_embed_process() {
bool is_visible = is_visible_in_tree();
Error err = DisplayServer::get_singleton()->embed_process(window->get_window_id(), current_process_id, get_screen_embedded_window_rect(), is_visible, is_visible && application_has_focus && embedding_grab_focus);
if (err == OK) {
embedding_completed = true;
queue_redraw();
emit_signal(SNAME("embedding_completed"));
} else if (err == ERR_DOES_NOT_EXIST) {
if (OS::get_singleton()->get_ticks_msec() - start_embedding_time >= (uint64_t)embedding_timeout) {
// Embedding process timed out.
reset();
emit_signal(SNAME("embedding_failed"));
} else {
// Tries another shot.
timer_embedding->start();
}
} else {
// Another unknown error.
reset();
emit_signal(SNAME("embedding_failed"));
}
}
bool EmbeddedProcess::_is_embedded_process_updatable() {
return window && current_process_id != 0 && embedding_completed;
}
void EmbeddedProcess::queue_update_embedded_process() {
updated_embedded_process_queued = true;
}
void EmbeddedProcess::_timer_update_embedded_process_timeout() {
_check_focused_process_id();
_check_mouse_over();
if (!updated_embedded_process_queued) {
// We need to detect when the control globally changes location or size on the screen.
// NOTIFICATION_RESIZED and NOTIFICATION_WM_POSITION_CHANGED are not enough to detect
// resized parent to siblings controls that can affect global position.
Rect2i new_global_rect = get_global_rect();
if (last_global_rect != new_global_rect) {
last_global_rect = new_global_rect;
queue_update_embedded_process();
}
}
}
void EmbeddedProcess::_update_embedded_process() {
if (!_is_embedded_process_updatable()) {
return;
}
bool must_grab_focus = false;
bool focus = has_focus();
if (last_updated_embedded_process_focused != focus) {
if (focus) {
must_grab_focus = true;
}
last_updated_embedded_process_focused = focus;
}
DisplayServer::get_singleton()->embed_process(window->get_window_id(), current_process_id, get_screen_embedded_window_rect(), is_visible_in_tree(), must_grab_focus);
emit_signal(SNAME("embedded_process_updated"));
}
void EmbeddedProcess::_timer_embedding_timeout() {
_try_embed_process();
}
void EmbeddedProcess::_draw() {
if (focused_process_id == current_process_id && has_focus() && focus_style_box.is_valid()) {
Size2 size = get_size();
Rect2 r = Rect2(Point2(), size);
focus_style_box->draw(get_canvas_item(), r);
}
}
void EmbeddedProcess::_check_mouse_over() {
// This method checks if the mouse is over the embedded process while the current application is focused.
// The goal is to give focus to the embedded process as soon as the mouse hovers over it,
// allowing the user to interact with it immediately without needing to click first.
if (!embedding_completed || !application_has_focus || !window || has_focus() || !is_visible_in_tree() || !window->has_focus() || Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) || Input::get_singleton()->is_mouse_button_pressed(MouseButton::RIGHT)) {
return;
}
// Before checking whether the mouse is truly inside the embedded process, ensure
// the editor has enough time to re-render. When a breakpoint is hit in the script editor,
// `_check_mouse_over` may be triggered before the editor hides the game workspace.
// This prevents the embedded process from regaining focus immediately after the editor has taken it.
if (OS::get_singleton()->get_ticks_msec() - last_application_focus_time < 500) {
return;
}
// Input::is_mouse_button_pressed is not sufficient to detect the mouse button state
// while the floating game window is being resized.
BitField<MouseButtonMask> mouse_button_mask = DisplayServer::get_singleton()->mouse_get_button_state();
if (!mouse_button_mask.is_empty()) {
return;
}
// Not stealing focus from a textfield.
if (get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) {
return;
}
Vector2 mouse_position = DisplayServer::get_singleton()->mouse_get_position();
Rect2i window_rect = get_screen_embedded_window_rect();
if (!window_rect.has_point(mouse_position)) {
return;
}
// Don't grab the focus if mouse over another window.
DisplayServer::WindowID window_id_over = DisplayServer::get_singleton()->get_window_at_screen_position(mouse_position);
if (window_id_over > 0 && window_id_over != window->get_window_id()) {
return;
}
// When there's a modal window, we don't want to grab the focus to prevent
// the game window to go in front of the modal window.
if (_get_current_modal_window()) {
return;
}
// Force "regrabbing" the game window focus.
last_updated_embedded_process_focused = false;
grab_focus();
queue_redraw();
}
void EmbeddedProcess::_check_focused_process_id() {
OS::ProcessID process_id = DisplayServer::get_singleton()->get_focused_process_id();
if (process_id != focused_process_id) {
focused_process_id = process_id;
if (focused_process_id == current_process_id) {
// The embedded process got the focus.
// Refocus the current model when focusing the embedded process.
Window *modal_window = _get_current_modal_window();
if (!modal_window) {
emit_signal(SNAME("embedded_process_focused"));
if (has_focus()) {
// Redraw to updated the focus style.
queue_redraw();
} else {
grab_focus();
}
}
} else if (has_focus()) {
release_focus();
}
}
// Ensure that the opened modal dialog is refocused when the focused process is the embedded process.
if (!application_has_focus && focused_process_id == current_process_id) {
Window *modal_window = _get_current_modal_window();
if (modal_window) {
if (modal_window->get_mode() == Window::MODE_MINIMIZED) {
modal_window->set_mode(Window::MODE_WINDOWED);
}
callable_mp(modal_window, &Window::grab_focus).call_deferred();
}
}
}
Window *EmbeddedProcess::_get_current_modal_window() {
Vector<DisplayServer::WindowID> wl = DisplayServer::get_singleton()->get_window_list();
for (const DisplayServer::WindowID &window_id : wl) {
Window *w = Window::get_from_id(window_id);
if (!w) {
continue;
}
if (w->is_exclusive()) {
return w;
}
}
return nullptr;
}
void EmbeddedProcess::_bind_methods() {
ADD_SIGNAL(MethodInfo("embedding_completed"));
ADD_SIGNAL(MethodInfo("embedding_failed"));
ADD_SIGNAL(MethodInfo("embedded_process_updated"));
ADD_SIGNAL(MethodInfo("embedded_process_focused"));
}
EmbeddedProcess::EmbeddedProcess() {
timer_embedding = memnew(Timer);
timer_embedding->set_wait_time(0.1);
timer_embedding->set_one_shot(true);
add_child(timer_embedding);
timer_embedding->connect("timeout", callable_mp(this, &EmbeddedProcess::_timer_embedding_timeout));
timer_update_embedded_process = memnew(Timer);
timer_update_embedded_process->set_wait_time(0.1);
add_child(timer_update_embedded_process);
timer_update_embedded_process->connect("timeout", callable_mp(this, &EmbeddedProcess::_timer_update_embedded_process_timeout));
set_focus_mode(FOCUS_ALL);
}
EmbeddedProcess::~EmbeddedProcess() {
if (current_process_id != 0) {
// Stop embedding the last process.
OS::get_singleton()->kill(current_process_id);
reset();
}
}

View file

@ -1,5 +1,5 @@
/**************************************************************************/
/* gpu_particles_2d_editor_plugin.h */
/* embedded_process.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -28,75 +28,70 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GPU_PARTICLES_2D_EDITOR_PLUGIN_H
#define GPU_PARTICLES_2D_EDITOR_PLUGIN_H
#ifndef EMBEDDED_PROCESS_H
#define EMBEDDED_PROCESS_H
#include "editor/plugins/editor_plugin.h"
#include "scene/2d/gpu_particles_2d.h"
#include "scene/2d/physics/collision_polygon_2d.h"
#include "scene/gui/box_container.h"
#include "scene/gui/spin_box.h"
#include "scene/gui/control.h"
class CheckBox;
class ConfirmationDialog;
class EditorFileDialog;
class MenuButton;
class OptionButton;
class EmbeddedProcess : public Control {
GDCLASS(EmbeddedProcess, Control);
class GPUParticles2DEditorPlugin : public EditorPlugin {
GDCLASS(GPUParticles2DEditorPlugin, EditorPlugin);
bool application_has_focus = true;
uint64_t last_application_focus_time = 0;
OS::ProcessID focused_process_id = 0;
OS::ProcessID current_process_id = 0;
bool embedding_grab_focus = false;
bool embedding_completed = false;
uint64_t start_embedding_time = 0;
bool updated_embedded_process_queued = false;
bool last_updated_embedded_process_focused = false;
enum {
MENU_GENERATE_VISIBILITY_RECT,
MENU_LOAD_EMISSION_MASK,
MENU_CLEAR_EMISSION_MASK,
MENU_OPTION_CONVERT_TO_CPU_PARTICLES,
MENU_RESTART
};
Window *window = nullptr;
Timer *timer_embedding = nullptr;
Timer *timer_update_embedded_process = nullptr;
enum EmissionMode {
EMISSION_MODE_SOLID,
EMISSION_MODE_BORDER,
EMISSION_MODE_BORDER_DIRECTED
};
const int embedding_timeout = 45000;
GPUParticles2D *particles = nullptr;
List<GPUParticles2D *> selected_particles;
bool keep_aspect = false;
Size2i window_size;
Ref<StyleBox> focus_style_box;
Point2i margin_top_left;
Point2i margin_bottom_right;
Rect2i last_global_rect;
EditorFileDialog *file = nullptr;
HBoxContainer *toolbar = nullptr;
MenuButton *menu = nullptr;
ConfirmationDialog *generate_visibility_rect = nullptr;
SpinBox *generate_seconds = nullptr;
ConfirmationDialog *emission_mask = nullptr;
OptionButton *emission_mask_mode = nullptr;
CheckBox *emission_mask_centered = nullptr;
CheckBox *emission_colors = nullptr;
String source_emission_file;
void _file_selected(const String &p_file);
void _menu_callback(int p_idx);
void _generate_visibility_rect();
void _generate_emission_mask();
void _selection_changed();
void _try_embed_process();
void _update_embedded_process();
void _timer_embedding_timeout();
void _timer_update_embedded_process_timeout();
void _draw();
void _check_mouse_over();
void _check_focused_process_id();
bool _is_embedded_process_updatable();
Rect2i _get_global_embedded_window_rect();
Window *_get_current_modal_window();
protected:
void _notification(int p_what);
static void _bind_methods();
void _notification(int p_what);
public:
virtual String get_name() const override { return "GPUParticles2D"; }
bool has_main_screen() const override { return false; }
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;
virtual void make_visible(bool p_visible) override;
void embed_process(OS::ProcessID p_pid);
void reset();
void request_close();
GPUParticles2DEditorPlugin();
~GPUParticles2DEditorPlugin();
void set_window_size(const Size2i p_window_size);
void set_keep_aspect(bool p_keep_aspect);
void queue_update_embedded_process();
Rect2i get_screen_embedded_window_rect();
int get_margin_size(Side p_side) const;
Size2 get_margins_size();
bool is_embedding_in_progress();
bool is_embedding_completed();
int get_embedded_pid() const;
EmbeddedProcess();
~EmbeddedProcess();
};
#endif // GPU_PARTICLES_2D_EDITOR_PLUGIN_H
#endif // EMBEDDED_PROCESS_H

View file

@ -30,6 +30,7 @@
#include "font_config_plugin.h"
#include "core/string/translation_server.h"
#include "editor/editor_settings.h"
#include "editor/import/dynamic_font_import_settings.h"
#include "editor/themes/editor_scale.h"
@ -63,9 +64,6 @@ bool EditorPropertyFontMetaObject::_get(const StringName &p_name, Variant &r_ret
return false;
}
void EditorPropertyFontMetaObject::_bind_methods() {
}
void EditorPropertyFontMetaObject::set_dict(const Dictionary &p_dict) {
dict = p_dict;
}
@ -123,13 +121,8 @@ bool EditorPropertyFontOTObject::_property_can_revert(const StringName &p_name)
if (name.begins_with("keys")) {
int key = name.get_slicec('/', 1).to_int();
if (defaults_dict.has(key) && dict.has(key)) {
int value = dict[key];
Vector3i range = defaults_dict[key];
return range.z != value;
}
return defaults_dict.has(key) && dict.has(key);
}
return false;
}
@ -144,7 +137,6 @@ bool EditorPropertyFontOTObject::_property_get_revert(const StringName &p_name,
return true;
}
}
return false;
}
@ -157,7 +149,7 @@ void EditorPropertyFontMetaOverride::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
if (button_add) {
button_add->set_icon(get_editor_theme_icon(SNAME("Add")));
button_add->set_button_icon(get_editor_theme_icon(SNAME("Add")));
}
} break;
}
@ -303,7 +295,7 @@ void EditorPropertyFontMetaOverride::update_property() {
hbox->add_child(prop);
prop->set_h_size_flags(SIZE_EXPAND_FILL);
Button *remove = memnew(Button);
remove->set_icon(get_editor_theme_icon(SNAME("Remove")));
remove->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
hbox->add_child(remove);
remove->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyFontMetaOverride::_remove).bind(remove, name));
@ -478,7 +470,7 @@ void EditorPropertyOTVariation::update_property() {
Vector3i range = supported.get_value_at_index(i);
EditorPropertyInteger *prop = memnew(EditorPropertyInteger);
prop->setup(range.x, range.y, false, 1, false, false);
prop->setup(range.x, range.y, false, true, false, false);
prop->set_object_and_property(object.ptr(), "keys/" + itos(name_tag));
String name = TS->tag_to_name(name_tag);
@ -560,7 +552,7 @@ void EditorPropertyOTFeatures::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
if (button_add) {
button_add->set_icon(get_editor_theme_icon(SNAME("Add")));
button_add->set_button_icon(get_editor_theme_icon(SNAME("Add")));
}
} break;
}
@ -797,7 +789,7 @@ void EditorPropertyOTFeatures::update_property() {
hbox->add_child(prop);
prop->set_h_size_flags(SIZE_EXPAND_FILL);
Button *remove = memnew(Button);
remove->set_icon(get_editor_theme_icon(SNAME("Remove")));
remove->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
hbox->add_child(remove);
remove->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyOTFeatures::_remove).bind(remove, name_tag));
@ -806,7 +798,7 @@ void EditorPropertyOTFeatures::update_property() {
}
button_add = EditorInspector::create_inspector_action_button(TTR("Add Feature"));
button_add->set_icon(get_editor_theme_icon(SNAME("Add")));
button_add->set_button_icon(get_editor_theme_icon(SNAME("Add")));
button_add->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyOTFeatures::_add_menu));
property_vbox->add_child(button_add);
@ -924,7 +916,8 @@ void FontPreview::_notification(int p_what) {
name = vformat("%s (%s)", prev_font->get_font_name(), prev_font->get_font_style_name());
}
if (prev_font->is_class("FontVariation")) {
name += " " + TTR(" - Variation");
// TRANSLATORS: This refers to variable font config, appended to the font name.
name += " - " + TTR("Variation");
}
font->draw_string(get_canvas_item(), Point2(0, font->get_height(font_size) + 2 * EDSCALE), name, HORIZONTAL_ALIGNMENT_CENTER, get_size().x, font_size, text_color);

View file

@ -31,7 +31,6 @@
#ifndef FONT_CONFIG_PLUGIN_H
#define FONT_CONFIG_PLUGIN_H
#include "core/io/marshalls.h"
#include "editor/editor_properties.h"
#include "editor/editor_properties_array_dict.h"
#include "editor/plugins/editor_plugin.h"
@ -46,13 +45,12 @@ class EditorPropertyFontMetaObject : public RefCounted {
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
static void _bind_methods();
public:
void set_dict(const Dictionary &p_dict);
Dictionary get_dict();
EditorPropertyFontMetaObject(){};
EditorPropertyFontMetaObject() {}
};
/*************************************************************************/
@ -76,7 +74,7 @@ public:
void set_defaults(const Dictionary &p_dict);
Dictionary get_defaults();
EditorPropertyFontOTObject(){};
EditorPropertyFontOTObject() {}
};
/*************************************************************************/
@ -104,7 +102,7 @@ class EditorPropertyFontMetaOverride : public EditorProperty {
protected:
void _notification(int p_what);
static void _bind_methods(){};
static void _bind_methods() {}
void _edit_pressed();
void _page_changed(int p_page);
@ -139,7 +137,7 @@ class EditorPropertyOTVariation : public EditorProperty {
EditorPaginator *paginator = nullptr;
protected:
static void _bind_methods(){};
static void _bind_methods() {}
void _edit_pressed();
void _page_changed(int p_page);
@ -188,7 +186,7 @@ class EditorPropertyOTFeatures : public EditorProperty {
protected:
void _notification(int p_what);
static void _bind_methods(){};
static void _bind_methods() {}
void _edit_pressed();
void _page_changed(int p_page);
@ -257,7 +255,7 @@ protected:
virtual void _add_element() override;
void _add_font(int p_option);
static void _bind_methods(){};
static void _bind_methods() {}
public:
EditorPropertyFontNamesArray();
@ -281,7 +279,7 @@ class FontEditorPlugin : public EditorPlugin {
public:
FontEditorPlugin();
virtual String get_name() const override { return "Font"; }
virtual String get_plugin_name() const override { return "Font"; }
};
#endif // FONT_CONFIG_PLUGIN_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,256 @@
/**************************************************************************/
/* game_view_plugin.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#ifndef GAME_VIEW_PLUGIN_H
#define GAME_VIEW_PLUGIN_H
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_main_screen.h"
#include "editor/plugins/editor_debugger_plugin.h"
#include "editor/plugins/editor_plugin.h"
#include "scene/debugger/scene_debugger.h"
#include "scene/gui/box_container.h"
class EmbeddedProcess;
class VSeparator;
class WindowWrapper;
class ScriptEditorDebugger;
class GameViewDebugger : public EditorDebuggerPlugin {
GDCLASS(GameViewDebugger, EditorDebuggerPlugin);
private:
Vector<Ref<EditorDebuggerSession>> sessions;
bool is_feature_enabled = true;
int node_type = RuntimeNodeSelect::NODE_TYPE_NONE;
bool selection_visible = true;
int select_mode = RuntimeNodeSelect::SELECT_MODE_SINGLE;
EditorDebuggerNode::CameraOverride camera_override_mode = EditorDebuggerNode::OVERRIDE_INGAME;
void _session_started(Ref<EditorDebuggerSession> p_session);
void _session_stopped();
protected:
static void _bind_methods();
public:
void set_is_feature_enabled(bool p_enabled);
void set_suspend(bool p_enabled);
void next_frame();
void set_node_type(int p_type);
void set_select_mode(int p_mode);
void set_selection_visible(bool p_visible);
void set_camera_override(bool p_enabled);
void set_camera_manipulate_mode(EditorDebuggerNode::CameraOverride p_mode);
void reset_camera_2d_position();
void reset_camera_3d_position();
virtual void setup_session(int p_session_id) override;
GameViewDebugger() {}
};
class GameView : public VBoxContainer {
GDCLASS(GameView, VBoxContainer);
enum {
CAMERA_RESET_2D,
CAMERA_RESET_3D,
CAMERA_MODE_INGAME,
CAMERA_MODE_EDITORS,
EMBED_RUN_GAME_EMBEDDED,
EMBED_MAKE_FLOATING_ON_PLAY,
};
enum EmbedSizeMode {
SIZE_MODE_FIXED,
SIZE_MODE_KEEP_ASPECT,
SIZE_MODE_STRETCH,
};
enum EmbedAvailability {
EMBED_AVAILABLE,
EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED,
EMBED_NOT_AVAILABLE_MINIMIZED,
EMBED_NOT_AVAILABLE_MAXIMIZED,
EMBED_NOT_AVAILABLE_FULLSCREEN,
EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE,
EMBED_NOT_AVAILABLE_PROJECT_DISPLAY_DRIVER,
};
inline static GameView *singleton = nullptr;
Ref<GameViewDebugger> debugger;
WindowWrapper *window_wrapper = nullptr;
bool is_feature_enabled = true;
int active_sessions = 0;
int screen_index_before_start = -1;
ScriptEditorDebugger *embedded_script_debugger = nullptr;
bool embed_on_play = true;
bool make_floating_on_play = true;
EmbedSizeMode embed_size_mode = SIZE_MODE_FIXED;
bool paused = false;
Size2 size_paused;
Rect2i floating_window_rect;
int floating_window_screen = -1;
Button *suspend_button = nullptr;
Button *next_frame_button = nullptr;
Button *node_type_button[RuntimeNodeSelect::NODE_TYPE_MAX];
Button *select_mode_button[RuntimeNodeSelect::SELECT_MODE_MAX];
Button *hide_selection = nullptr;
Button *camera_override_button = nullptr;
MenuButton *camera_override_menu = nullptr;
VSeparator *embedding_separator = nullptr;
Button *fixed_size_button = nullptr;
Button *keep_aspect_button = nullptr;
Button *stretch_button = nullptr;
MenuButton *embed_options_menu = nullptr;
Label *game_size_label = nullptr;
Panel *panel = nullptr;
EmbeddedProcess *embedded_process = nullptr;
Label *state_label = nullptr;
void _sessions_changed();
void _update_debugger_buttons();
void _suspend_button_toggled(bool p_pressed);
void _node_type_pressed(int p_option);
void _select_mode_pressed(int p_option);
void _embed_options_menu_menu_id_pressed(int p_id);
void _size_mode_button_pressed(int size_mode);
void _play_pressed();
static void _instance_starting_static(int p_idx, List<String> &r_arguments);
void _instance_starting(int p_idx, List<String> &r_arguments);
void _stop_pressed();
void _embedding_completed();
void _embedding_failed();
void _embedded_process_updated();
void _embedded_process_focused();
void _editor_or_project_settings_changed();
EmbedAvailability _get_embed_available();
void _update_ui();
void _update_embed_menu_options();
void _update_embed_window_size();
void _update_arguments_for_instance(int p_idx, List<String> &r_arguments);
void _show_update_window_wrapper();
void _hide_selection_toggled(bool p_pressed);
void _camera_override_button_toggled(bool p_pressed);
void _camera_override_menu_id_pressed(int p_id);
void _window_close_request();
void _update_floating_window_settings();
void _attach_script_debugger();
void _detach_script_debugger();
void _remote_window_title_changed(String title);
void _debugger_breaked(bool p_breaked, bool p_can_debug);
protected:
void _notification(int p_what);
public:
void set_is_feature_enabled(bool p_enabled);
void set_state(const Dictionary &p_state);
Dictionary get_state() const;
void set_window_layout(Ref<ConfigFile> p_layout);
void get_window_layout(Ref<ConfigFile> p_layout);
GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper);
};
class GameViewPlugin : public EditorPlugin {
GDCLASS(GameViewPlugin, EditorPlugin);
#ifndef ANDROID_ENABLED
GameView *game_view = nullptr;
WindowWrapper *window_wrapper = nullptr;
#endif
Ref<GameViewDebugger> debugger;
String last_editor;
void _feature_profile_changed();
#ifndef ANDROID_ENABLED
void _window_visibility_changed(bool p_visible);
#endif
void _save_last_editor(const String &p_editor);
void _focus_another_editor();
bool _is_window_wrapper_enabled() const;
protected:
void _notification(int p_what);
public:
virtual String get_plugin_name() const override { return "Game"; }
bool has_main_screen() const override { return true; }
virtual void edit(Object *p_object) override {}
virtual bool handles(Object *p_object) const override { return false; }
virtual void selected_notify() override;
Ref<GameViewDebugger> get_debugger() const { return debugger; }
#ifndef ANDROID_ENABLED
virtual void make_visible(bool p_visible) override;
virtual void set_window_layout(Ref<ConfigFile> p_layout) override;
virtual void get_window_layout(Ref<ConfigFile> p_layout) override;
virtual void set_state(const Dictionary &p_state) override;
virtual Dictionary get_state() const override;
#endif
GameViewPlugin();
~GameViewPlugin();
};
#endif // GAME_VIEW_PLUGIN_H

View file

@ -31,6 +31,7 @@
#ifndef GDEXTENSION_EXPORT_PLUGIN_H
#define GDEXTENSION_EXPORT_PLUGIN_H
#include "core/extension/gdextension_library_loader.h"
#include "editor/export/editor_export.h"
class GDExtensionExportPlugin : public EditorExportPlugin {
@ -72,16 +73,19 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
all_archs.insert("ppc32");
all_archs.insert("ppc64");
all_archs.insert("wasm32");
all_archs.insert("loongarch64");
all_archs.insert("universal");
HashSet<String> archs;
HashSet<String> features_wo_arch;
Vector<String> features_vector;
for (const String &tag : p_features) {
if (all_archs.has(tag)) {
archs.insert(tag);
} else {
features_wo_arch.insert(tag);
}
features_vector.append(tag);
}
if (archs.is_empty()) {
@ -89,11 +93,22 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
}
HashSet<String> libs_added;
struct FoundLibInfo {
int count = 0;
Vector<String> libs;
};
HashMap<String, FoundLibInfo> libs_found;
for (const String &arch_tag : archs) {
if (arch_tag != "universal") {
libs_found[arch_tag] = FoundLibInfo();
}
}
for (const String &arch_tag : archs) {
PackedStringArray tags;
String library_path = GDExtension::find_extension_library(
String library_path = GDExtensionLibraryLoader::find_extension_library(
p_path, config, [features_wo_arch, arch_tag](const String &p_feature) { return features_wo_arch.has(p_feature) || (p_feature == arch_tag); }, &tags);
if (libs_added.has(library_path)) {
continue; // Universal library, already added for another arch, do not duplicate.
}
@ -121,19 +136,38 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
String linker_flags = "-Wl,-U,_" + entry_symbol;
add_ios_linker_flags(linker_flags);
}
} else {
Vector<String> features_vector;
for (const String &E : p_features) {
features_vector.append(E);
// Update found library info.
if (arch_tag == "universal") {
for (const String &sub_arch_tag : archs) {
if (sub_arch_tag != "universal") {
libs_found[sub_arch_tag].count++;
libs_found[sub_arch_tag].libs.push_back(library_path);
}
}
} else {
libs_found[arch_tag].count++;
libs_found[arch_tag].libs.push_back(library_path);
}
ERR_FAIL_MSG(vformat("No suitable library found for GDExtension: %s. Possible feature flags for your platform: %s", p_path, String(", ").join(features_vector)));
}
Vector<SharedObject> dependencies_shared_objects = GDExtension::find_extension_dependencies(p_path, config, [p_features](String p_feature) { return p_features.has(p_feature); });
Vector<SharedObject> dependencies_shared_objects = GDExtensionLibraryLoader::find_extension_dependencies(p_path, config, [p_features](String p_feature) { return p_features.has(p_feature); });
for (const SharedObject &shared_object : dependencies_shared_objects) {
_add_shared_object(shared_object);
}
}
for (const KeyValue<String, FoundLibInfo> &E : libs_found) {
if (E.value.count == 0) {
if (get_export_platform().is_valid()) {
get_export_platform()->add_message(EditorExportPlatform::EXPORT_MESSAGE_WARNING, TTR("GDExtension"), vformat(TTR("No \"%s\" library found for GDExtension: \"%s\". Possible feature flags for your platform: %s"), E.key, p_path, String(", ").join(features_vector)));
}
} else if (E.value.count > 1) {
if (get_export_platform().is_valid()) {
get_export_platform()->add_message(EditorExportPlatform::EXPORT_MESSAGE_WARNING, TTR("GDExtension"), vformat(TTR("Multiple \"%s\" libraries found for GDExtension: \"%s\": \"%s\"."), E.key, p_path, String(", ").join(E.value.libs)));
}
}
}
}
#endif // GDEXTENSION_EXPORT_PLUGIN_H

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")

View file

@ -32,7 +32,6 @@
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/audio_listener_3d.h"
AudioListener3DGizmoPlugin::AudioListener3DGizmoPlugin() {

View file

@ -34,11 +34,10 @@
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/audio_stream_player_3d.h"
AudioStreamPlayer3DGizmoPlugin::AudioStreamPlayer3DGizmoPlugin() {
Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/stream_player_3d", Color(0.4, 0.8, 1));
Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/stream_player_3d");
create_icon_material("stream_player_3d_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Gizmo3DSamplePlayer"), EditorStringName(EditorIcons)));
create_material("stream_player_3d_material_primary", gizmo_color);

View file

@ -30,7 +30,6 @@
#include "camera_3d_gizmo_plugin.h"
#include "core/config/project_settings.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@ -39,31 +38,13 @@
#include "scene/3d/camera_3d.h"
Camera3DGizmoPlugin::Camera3DGizmoPlugin() {
Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/camera", Color(0.8, 0.4, 0.8));
Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/camera");
create_material("camera_material", gizmo_color);
create_icon_material("camera_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoCamera3D"), EditorStringName(EditorIcons)));
create_handle_material("handles");
}
Size2i Camera3DGizmoPlugin::_get_viewport_size(Camera3D *p_camera) {
Viewport *viewport = p_camera->get_viewport();
Window *window = Object::cast_to<Window>(viewport);
if (window) {
return window->get_size();
}
SubViewport *sub_viewport = Object::cast_to<SubViewport>(viewport);
ERR_FAIL_NULL_V(sub_viewport, Size2i());
if (sub_viewport == EditorNode::get_singleton()->get_scene_root()) {
return Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));
}
return sub_viewport->get_size();
}
bool Camera3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<Camera3D>(p_spatial) != nullptr;
}
@ -112,9 +93,12 @@ void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id,
float a = _find_closest_angle_to_half_pi_arc(s[0], s[1], 1.0, gt2);
camera->set("fov", CLAMP(a * 2.0, 1, 179));
} else {
Camera3D::KeepAspect aspect = camera->get_keep_aspect_mode();
Vector3 camera_far = aspect == Camera3D::KeepAspect::KEEP_WIDTH ? Vector3(4096, 0, -1) : Vector3(0, 4096, -1);
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(Vector3(0, 0, -1), Vector3(4096, 0, -1), s[0], s[1], ra, rb);
float d = ra.x * 2;
Geometry3D::get_closest_points_between_segments(Vector3(0, 0, -1), camera_far, s[0], s[1], ra, rb);
float d = aspect == Camera3D::KeepAspect::KEEP_WIDTH ? ra.x * 2 : ra.y * 2;
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
}
@ -163,7 +147,7 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Ref<Material> material = get_material("camera_material", p_gizmo);
Ref<Material> icon = get_material("camera_icon", p_gizmo);
const Size2i viewport_size = _get_viewport_size(camera);
const Size2i viewport_size = Node3DEditor::get_camera_viewport_size(camera);
const real_t viewport_aspect = viewport_size.x > 0 && viewport_size.y > 0 ? viewport_size.aspect() : 1.0;
const Size2 size_factor = viewport_aspect > 1.0 ? Size2(1.0, 1.0 / viewport_aspect) : Size2(viewport_aspect, 1.0);
@ -213,25 +197,33 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
} break;
case Camera3D::PROJECTION_ORTHOGONAL: {
float size = camera->get_size();
Camera3D::KeepAspect aspect = camera->get_keep_aspect_mode();
float hsize = size * 0.5;
Vector3 right(hsize * size_factor.x, 0, 0);
Vector3 up(0, hsize * size_factor.y, 0);
float size = camera->get_size();
float keep_size = size * 0.5;
Vector3 right, up;
Vector3 back(0, 0, -1.0);
Vector3 front(0, 0, 0);
if (aspect == Camera3D::KeepAspect::KEEP_WIDTH) {
right = Vector3(keep_size, 0, 0);
up = Vector3(0, keep_size / viewport_aspect, 0);
handles.push_back(right + back);
} else {
right = Vector3(keep_size * viewport_aspect, 0, 0);
up = Vector3(0, keep_size, 0);
handles.push_back(up + back);
}
ADD_QUAD(-up - right, -up + right, up + right, up - right);
ADD_QUAD(-up - right + back, -up + right + back, up + right + back, up - right + back);
ADD_QUAD(up + right, up + right + back, up - right + back, up - right);
ADD_QUAD(-up + right, -up + right + back, -up - right + back, -up - right);
handles.push_back(right + back);
right.x = MIN(right.x, hsize * 0.25);
Vector3 tup(0, up.y + hsize / 2, back.z);
right.x = MIN(right.x, keep_size * 0.25);
Vector3 tup(0, up.y + keep_size / 2, back.z);
ADD_TRIANGLE(tup, right + up + back, -right + up + back);
} break;
case Camera3D::PROJECTION_FRUSTUM: {

View file

@ -37,7 +37,6 @@ class Camera3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Camera3DGizmoPlugin, EditorNode3DGizmoPlugin);
private:
static Size2i _get_viewport_size(Camera3D *p_camera);
static float _find_closest_angle_to_half_pi_arc(const Vector3 &p_from, const Vector3 &p_to, float p_arc_radius, const Transform3D &p_arc_xform);
public:

View file

@ -30,8 +30,6 @@
#include "collision_object_3d_gizmo_plugin.h"
#include "editor/editor_settings.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/physics/collision_object_3d.h"
#include "scene/3d/physics/collision_polygon_3d.h"
#include "scene/3d/physics/collision_shape_3d.h"

View file

@ -30,16 +30,44 @@
#include "collision_polygon_3d_gizmo_plugin.h"
#include "editor/editor_settings.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "core/math/geometry_2d.h"
#include "scene/3d/physics/collision_polygon_3d.h"
CollisionPolygon3DGizmoPlugin::CollisionPolygon3DGizmoPlugin() {
const Color gizmo_color = SceneTree::get_singleton()->get_debug_collisions_color();
create_material("shape_material", gizmo_color);
const float gizmo_value = gizmo_color.get_v();
const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65);
create_material("shape_material_disabled", gizmo_color_disabled);
create_collision_material("shape_material", 2.0);
create_collision_material("shape_material_arraymesh", 0.0625);
create_collision_material("shape_material_disabled", 0.0625);
create_collision_material("shape_material_arraymesh_disabled", 0.015625);
}
void CollisionPolygon3DGizmoPlugin::create_collision_material(const String &p_name, float p_alpha) {
Vector<Ref<StandardMaterial3D>> mats;
const Color collision_color(1.0, 1.0, 1.0, p_alpha);
for (int i = 0; i < 4; i++) {
bool instantiated = i < 2;
Ref<StandardMaterial3D> material;
material.instantiate();
Color color = collision_color;
color.a *= instantiated ? 0.25 : 1.0;
material->set_albedo(color);
material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 1);
material->set_cull_mode(StandardMaterial3D::CULL_BACK);
material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
mats.push_back(material);
}
materials[p_name] = mats;
}
bool CollisionPolygon3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
@ -59,6 +87,13 @@ void CollisionPolygon3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p_gizmo->clear();
const Ref<StandardMaterial3D> material =
get_material(!polygon->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo);
const Ref<StandardMaterial3D> material_arraymesh =
get_material(!polygon->is_disabled() ? "shape_material_arraymesh" : "shape_material_arraymesh_disabled", p_gizmo);
const Color collision_color = polygon->is_disabled() ? Color(1.0, 1.0, 1.0, 0.75) : polygon->get_debug_color();
Vector<Vector2> points = polygon->get_polygon();
float depth = polygon->get_depth() * 0.5;
@ -73,9 +108,125 @@ void CollisionPolygon3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
lines.push_back(Vector3(points[i].x, points[i].y, -depth));
}
const Ref<Material> material =
get_material(!polygon->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo);
if (polygon->get_debug_fill_enabled()) {
Ref<ArrayMesh> array_mesh;
array_mesh.instantiate();
p_gizmo->add_lines(lines, material);
Vector<Vector3> verts;
Vector<Color> colors;
Vector<int> indices;
// Determine orientation of the 2D polygon's vertices to determine
// which direction to draw outer polygons.
float signed_area = 0.0f;
for (int i = 0; i < points.size(); i++) {
const int j = (i + 1) % points.size();
signed_area += points[i].x * points[j].y - points[j].x * points[i].y;
}
// Generate triangles for the sides of the extruded polygon.
for (int i = 0; i < points.size(); i++) {
verts.push_back(Vector3(points[i].x, points[i].y, depth));
verts.push_back(Vector3(points[i].x, points[i].y, -depth));
colors.push_back(collision_color);
colors.push_back(collision_color);
}
for (int i = 0; i < verts.size(); i += 2) {
const int j = (i + 1) % verts.size();
const int k = (i + 2) % verts.size();
const int l = (i + 3) % verts.size();
indices.push_back(i);
if (signed_area < 0) {
indices.push_back(j);
indices.push_back(k);
} else {
indices.push_back(k);
indices.push_back(j);
}
indices.push_back(j);
if (signed_area < 0) {
indices.push_back(l);
indices.push_back(k);
} else {
indices.push_back(k);
indices.push_back(l);
}
}
Vector<Vector<Vector2>> decomp = Geometry2D::decompose_polygon_in_convex(polygon->get_polygon());
// Generate triangles for the bottom cap of the extruded polygon.
for (int i = 0; i < decomp.size(); i++) {
Vector<Vector3> cap_verts_bottom;
Vector<Color> cap_colours_bottom;
Vector<int> cap_indices_bottom;
const int index_offset = verts.size();
const Vector<Vector2> &convex = decomp[i];
for (int j = 0; j < convex.size(); j++) {
cap_verts_bottom.push_back(Vector3(convex[j].x, convex[j].y, -depth));
cap_colours_bottom.push_back(collision_color);
}
if (convex.size() >= 3) {
for (int j = 1; j < convex.size(); j++) {
const int k = (j + 1) % convex.size();
cap_indices_bottom.push_back(index_offset + 0);
cap_indices_bottom.push_back(index_offset + j);
cap_indices_bottom.push_back(index_offset + k);
}
}
verts.append_array(cap_verts_bottom);
colors.append_array(cap_colours_bottom);
indices.append_array(cap_indices_bottom);
}
// Generate triangles for the top cap of the extruded polygon.
for (int i = 0; i < decomp.size(); i++) {
Vector<Vector3> cap_verts_top;
Vector<Color> cap_colours_top;
Vector<int> cap_indices_top;
const int index_offset = verts.size();
const Vector<Vector2> &convex = decomp[i];
for (int j = 0; j < convex.size(); j++) {
cap_verts_top.push_back(Vector3(convex[j].x, convex[j].y, depth));
cap_colours_top.push_back(collision_color);
}
if (convex.size() >= 3) {
for (int j = 1; j < convex.size(); j++) {
const int k = (j + 1) % convex.size();
cap_indices_top.push_back(index_offset + k);
cap_indices_top.push_back(index_offset + j);
cap_indices_top.push_back(index_offset + 0);
}
}
verts.append_array(cap_verts_top);
colors.append_array(cap_colours_top);
indices.append_array(cap_indices_top);
}
Array a;
a.resize(Mesh::ARRAY_MAX);
a[RS::ARRAY_VERTEX] = verts;
a[RS::ARRAY_COLOR] = colors;
a[RS::ARRAY_INDEX] = indices;
array_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a);
p_gizmo->add_mesh(array_mesh, material_arraymesh);
}
p_gizmo->add_lines(lines, material, false, collision_color);
p_gizmo->add_collision_segments(lines);
}

View file

@ -36,6 +36,8 @@
class CollisionPolygon3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(CollisionPolygon3DGizmoPlugin, EditorNode3DGizmoPlugin);
void create_collision_material(const String &p_name, float p_alpha);
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;

View file

@ -32,7 +32,6 @@
#include "core/math/convex_hull.h"
#include "core/math/geometry_3d.h"
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/plugins/gizmos/gizmo_3d_helper.h"
#include "editor/plugins/node_3d_editor_plugin.h"
@ -49,17 +48,47 @@
CollisionShape3DGizmoPlugin::CollisionShape3DGizmoPlugin() {
helper.instantiate();
const Color gizmo_color = SceneTree::get_singleton()->get_debug_collisions_color();
create_material("shape_material", gizmo_color);
const float gizmo_value = gizmo_color.get_v();
const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65);
create_material("shape_material_disabled", gizmo_color_disabled);
create_collision_material("shape_material", 2.0);
create_collision_material("shape_material_arraymesh", 0.0625);
create_collision_material("shape_material_disabled", 0.0625);
create_collision_material("shape_material_arraymesh_disabled", 0.015625);
create_handle_material("handles");
}
CollisionShape3DGizmoPlugin::~CollisionShape3DGizmoPlugin() {
}
void CollisionShape3DGizmoPlugin::create_collision_material(const String &p_name, float p_alpha) {
Vector<Ref<StandardMaterial3D>> mats;
const Color collision_color(1.0, 1.0, 1.0, p_alpha);
for (int i = 0; i < 4; i++) {
bool instantiated = i < 2;
Ref<StandardMaterial3D> material = memnew(StandardMaterial3D);
Color color = collision_color;
color.a *= instantiated ? 0.25 : 1.0;
material->set_albedo(color);
material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 1);
material->set_cull_mode(StandardMaterial3D::CULL_BACK);
material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
mats.push_back(material);
}
materials[p_name] = mats;
}
bool CollisionShape3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<CollisionShape3D>(p_spatial) != nullptr;
}
@ -93,7 +122,7 @@ String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_g
}
if (Object::cast_to<CylinderShape3D>(*s)) {
return p_id == 0 ? "Radius" : "Height";
return helper->cylinder_get_handle_name(p_id);
}
if (Object::cast_to<SeparationRayShape3D>(*s)) {
@ -219,25 +248,15 @@ void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, i
}
if (Object::cast_to<CylinderShape3D>(*s)) {
Vector3 axis;
axis[p_id == 0 ? 0 : 1] = 1.0;
Ref<CylinderShape3D> cs2 = s;
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
float d = axis.dot(ra);
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
}
if (d < 0.001) {
d = 0.001;
}
if (p_id == 0) {
cs2->set_radius(d);
} else if (p_id == 1) {
cs2->set_height(d * 2.0);
}
real_t height = cs2->get_height();
real_t radius = cs2->get_radius();
Vector3 position;
helper->cylinder_set_handle(sg, p_id, height, radius, position);
cs2->set_height(height);
cs2->set_radius(radius);
cs->set_global_position(position);
}
}
@ -293,31 +312,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo
if (Object::cast_to<CylinderShape3D>(*s)) {
Ref<CylinderShape3D> ss = s;
if (p_cancel) {
if (p_id == 0) {
ss->set_radius(p_restore);
} else {
ss->set_height(p_restore);
}
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
if (p_id == 0) {
ur->create_action(TTR("Change Cylinder Shape Radius"));
ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius());
ur->add_undo_method(ss.ptr(), "set_radius", p_restore);
} else {
ur->create_action(
///
////////
TTR("Change Cylinder Shape Height"));
ur->add_do_method(ss.ptr(), "set_height", ss->get_height());
ur->add_undo_method(ss.ptr(), "set_height", p_restore);
}
ur->commit_action();
helper->cylinder_commit_handle(p_id, TTR("Change Cylinder Shape Radius"), TTR("Change Cylinder Shape Height"), p_cancel, cs, *ss, *ss);
}
if (Object::cast_to<SeparationRayShape3D>(*s)) {
@ -345,9 +340,20 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
return;
}
const Ref<Material> material =
const Ref<StandardMaterial3D> material =
get_material(!cs->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo);
Ref<Material> handles_material = get_material("handles");
const Ref<StandardMaterial3D> material_arraymesh =
get_material(!cs->is_disabled() ? "shape_material_arraymesh" : "shape_material_arraymesh_disabled", p_gizmo);
const Ref<Material> handles_material = get_material("handles");
const Color collision_color = cs->is_disabled() ? Color(1.0, 1.0, 1.0, 0.75) : cs->get_debug_color();
if (cs->get_debug_fill_enabled()) {
Ref<ArrayMesh> array_mesh = s->get_debug_arraymesh_faces(collision_color);
if (array_mesh.is_valid() && array_mesh->get_surface_count() > 0) {
p_gizmo->add_mesh(array_mesh, material_arraymesh);
}
}
if (Object::cast_to<SphereShape3D>(*s)) {
Ref<SphereShape3D> sp = s;
@ -385,7 +391,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
collision_segments.push_back(Vector3(b.x, b.y, 0));
}
p_gizmo->add_lines(points, material);
p_gizmo->add_lines(points, material, false, collision_color);
p_gizmo->add_collision_segments(collision_segments);
Vector<Vector3> handles;
handles.push_back(Vector3(r, 0, 0));
@ -408,7 +414,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
const Vector<Vector3> handles = helper->box_get_handles(bs->get_size());
p_gizmo->add_lines(lines, material);
p_gizmo->add_lines(lines, material, false, collision_color);
p_gizmo->add_collision_segments(lines);
p_gizmo->add_handles(handles, handles_material);
}
@ -446,7 +452,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
points.push_back(Vector3(b.y, b.x, 0) + dud);
}
p_gizmo->add_lines(points, material);
p_gizmo->add_lines(points, material, false, collision_color);
Vector<Vector3> collision_segments;
@ -510,7 +516,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
}
}
p_gizmo->add_lines(points, material);
p_gizmo->add_lines(points, material, false, collision_color);
Vector<Vector3> collision_segments;
@ -534,10 +540,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p_gizmo->add_collision_segments(collision_segments);
Vector<Vector3> handles = {
Vector3(cs2->get_radius(), 0, 0),
Vector3(0, cs2->get_height() * 0.5, 0)
};
Vector<Vector3> handles = helper->cylinder_get_handles(cs2->get_height(), cs2->get_radius());
p_gizmo->add_handles(handles, handles_material);
}
@ -568,27 +571,26 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p.normal * p.d + p.normal * 3
};
p_gizmo->add_lines(points, material);
p_gizmo->add_lines(points, material, false, collision_color);
p_gizmo->add_collision_segments(points);
}
if (Object::cast_to<ConvexPolygonShape3D>(*s)) {
Vector<Vector3> points = Object::cast_to<ConvexPolygonShape3D>(*s)->get_points();
if (points.size() > 3) {
if (points.size() > 1) { // Need at least 2 points for a line.
Vector<Vector3> varr = Variant(points);
Geometry3D::MeshData md;
Error err = ConvexHullComputer::convex_hull(varr, md);
if (err == OK) {
Vector<Vector3> points2;
points2.resize(md.edges.size() * 2);
Vector<Vector3> lines;
lines.resize(md.edges.size() * 2);
for (uint32_t i = 0; i < md.edges.size(); i++) {
points2.write[i * 2 + 0] = md.vertices[md.edges[i].vertex_a];
points2.write[i * 2 + 1] = md.vertices[md.edges[i].vertex_b];
lines.write[i * 2 + 0] = md.vertices[md.edges[i].vertex_a];
lines.write[i * 2 + 1] = md.vertices[md.edges[i].vertex_b];
}
p_gizmo->add_lines(points2, material);
p_gizmo->add_collision_segments(points2);
p_gizmo->add_lines(lines, material, false, collision_color);
p_gizmo->add_collision_segments(lines);
}
}
}
@ -596,7 +598,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
if (Object::cast_to<ConcavePolygonShape3D>(*s)) {
Ref<ConcavePolygonShape3D> cs2 = s;
Ref<ArrayMesh> mesh = cs2->get_debug_mesh();
p_gizmo->add_mesh(mesh, material);
p_gizmo->add_lines(cs2->get_debug_mesh_lines(), material, false, collision_color);
p_gizmo->add_collision_segments(cs2->get_debug_mesh_lines());
}
@ -607,7 +609,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Vector3(),
Vector3(0, 0, rs->get_length())
};
p_gizmo->add_lines(points, material);
p_gizmo->add_lines(points, material, false, collision_color);
p_gizmo->add_collision_segments(points);
Vector<Vector3> handles;
handles.push_back(Vector3(0, 0, rs->get_length()));
@ -617,7 +619,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
if (Object::cast_to<HeightMapShape3D>(*s)) {
Ref<HeightMapShape3D> hms = s;
Ref<ArrayMesh> mesh = hms->get_debug_mesh();
p_gizmo->add_mesh(mesh, material);
Vector<Vector3> lines = hms->get_debug_mesh_lines();
p_gizmo->add_lines(lines, material, false, collision_color);
}
}

View file

@ -38,6 +38,8 @@ class Gizmo3DHelper;
class CollisionShape3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(CollisionShape3DGizmoPlugin, EditorNode3DGizmoPlugin);
void create_collision_material(const String &p_name, float p_alpha);
Ref<Gizmo3DHelper> helper;
public:

View file

@ -33,11 +33,10 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/cpu_particles_3d.h"
CPUParticles3DGizmoPlugin::CPUParticles3DGizmoPlugin() {
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particles", Color(0.8, 0.7, 0.4));
Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/particles");
create_material("particles_material", gizmo_color);
gizmo_color.a = MAX((gizmo_color.a - 0.2) * 0.02, 0.0);
create_material("particles_solid_material", gizmo_color);

View file

@ -33,14 +33,12 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/plugins/gizmos/gizmo_3d_helper.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/decal.h"
DecalGizmoPlugin::DecalGizmoPlugin() {
helper.instantiate();
Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/decal", Color(0.6, 0.5, 1.0));
Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/decal");
create_icon_material("decal_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoDecal"), EditorStringName(EditorIcons)));

View file

@ -33,14 +33,12 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/plugins/gizmos/gizmo_3d_helper.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/fog_volume.h"
FogVolumeGizmoPlugin::FogVolumeGizmoPlugin() {
helper.instantiate();
Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/fog_volume", Color(0.5, 0.7, 1));
Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/fog_volume");
create_material("shape_material", gizmo_color);
gizmo_color.a = 0.15;
create_material("shape_material_internal", gizmo_color);

View file

@ -30,9 +30,7 @@
#include "geometry_instance_3d_gizmo_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/visual_instance_3d.h"
GeometryInstance3DGizmoPlugin::GeometryInstance3DGizmoPlugin() {

View file

@ -139,3 +139,98 @@ void Gizmo3DHelper::box_commit_handle(const String &p_action_name, bool p_cancel
ur->add_undo_property(p_position_object, p_position_property, initial_transform.get_origin());
ur->commit_action();
}
Vector<Vector3> Gizmo3DHelper::cylinder_get_handles(real_t p_height, real_t p_radius) {
Vector<Vector3> handles;
handles.push_back(Vector3(p_radius, 0, 0));
handles.push_back(Vector3(0, p_height * 0.5, 0));
handles.push_back(Vector3(0, p_height * -0.5, 0));
return handles;
}
String Gizmo3DHelper::cylinder_get_handle_name(int p_id) const {
if (p_id == 0) {
return "Radius";
} else {
return "Height";
}
}
void Gizmo3DHelper::cylinder_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius, Vector3 &r_cylinder_position) {
int sign = p_id == 2 ? -1 : 1;
int axis = p_id == 0 ? 0 : 1;
Vector3 axis_vector;
axis_vector[axis] = sign;
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(axis_vector * -4096, axis_vector * 4096, p_segment[0], p_segment[1], ra, rb);
float d = axis_vector.dot(ra);
// Snap to grid.
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
}
if (p_id == 0) {
// Adjust radius.
if (d < 0.001) {
d = 0.001;
}
r_radius = d;
r_cylinder_position = initial_transform.get_origin();
} else if (p_id == 1 || p_id == 2) {
real_t initial_height = initial_value;
// Adjust height.
if (Input::get_singleton()->is_key_pressed(Key::ALT)) {
r_height = d * 2.0;
} else {
r_height = (initial_height * 0.5) + d;
}
if (r_height < 0.001) {
r_height = 0.001;
}
// Adjust position.
if (Input::get_singleton()->is_key_pressed(Key::ALT)) {
r_cylinder_position = initial_transform.get_origin();
} else {
Vector3 offset;
offset[axis] = (r_height - initial_height) * 0.5 * sign;
r_cylinder_position = initial_transform.xform(offset);
}
}
}
void Gizmo3DHelper::cylinder_commit_handle(int p_id, const String &p_radius_action_name, const String &p_height_action_name, bool p_cancel, Object *p_position_object, Object *p_height_object, Object *p_radius_object, const StringName &p_position_property, const StringName &p_height_property, const StringName &p_radius_property) {
if (!p_height_object) {
p_height_object = p_position_object;
}
if (!p_radius_object) {
p_radius_object = p_position_object;
}
if (p_cancel) {
if (p_id == 0) {
p_radius_object->set(p_radius_property, initial_value);
} else {
p_height_object->set(p_height_property, initial_value);
}
p_position_object->set(p_position_property, initial_transform.get_origin());
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(p_id == 0 ? p_radius_action_name : p_height_action_name);
if (p_id == 0) {
ur->add_do_property(p_radius_object, p_radius_property, p_radius_object->get(p_radius_property));
ur->add_undo_property(p_radius_object, p_radius_property, initial_value);
} else {
ur->add_do_property(p_height_object, p_height_property, p_height_object->get(p_height_property));
ur->add_do_property(p_position_object, p_position_property, p_position_object->get(p_position_property));
ur->add_undo_property(p_height_object, p_height_property, initial_value);
ur->add_undo_property(p_position_object, p_position_property, initial_transform.get_origin());
}
ur->commit_action();
}

View file

@ -50,6 +50,11 @@ public:
String box_get_handle_name(int p_id) const;
void box_set_handle(const Vector3 p_segment[2], int p_id, Vector3 &r_box_size, Vector3 &r_box_position);
void box_commit_handle(const String &p_action_name, bool p_cancel, Object *p_position_object, Object *p_size_object = nullptr, const StringName &p_position_property = "global_position", const StringName &p_size_property = "size");
Vector<Vector3> cylinder_get_handles(real_t p_height, real_t p_radius);
String cylinder_get_handle_name(int p_id) const;
void cylinder_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius, Vector3 &r_cylinder_position);
void cylinder_commit_handle(int p_id, const String &p_radius_action_name, const String &p_height_action_name, bool p_cancel, Object *p_position_object, Object *p_height_object = nullptr, Object *p_radius_object = nullptr, const StringName &p_position_property = "global_position", const StringName &p_height_property = "height", const StringName &p_radius_property = "radius");
};
#endif // GIZMO_3D_HELPER_H

View file

@ -33,12 +33,10 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/gpu_particles_3d.h"
GPUParticles3DGizmoPlugin::GPUParticles3DGizmoPlugin() {
Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/particles", Color(0.8, 0.7, 0.4));
Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/particles");
create_material("particles_material", gizmo_color);
gizmo_color.a = MAX((gizmo_color.a - 0.2) * 0.02, 0.0);
create_icon_material("particles_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoGPUParticles3D"), EditorStringName(EditorIcons)));

View file

@ -39,12 +39,12 @@
GPUParticlesCollision3DGizmoPlugin::GPUParticlesCollision3DGizmoPlugin() {
helper.instantiate();
Color gizmo_color_attractor = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/particle_attractor", Color(1, 0.7, 0.5));
Color gizmo_color_attractor = EDITOR_GET("editors/3d_gizmos/gizmo_colors/particle_attractor");
create_material("shape_material_attractor", gizmo_color_attractor);
gizmo_color_attractor.a = 0.15;
create_material("shape_material_attractor_internal", gizmo_color_attractor);
Color gizmo_color_collision = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/particle_collision", Color(0.5, 0.7, 1));
Color gizmo_color_collision = EDITOR_GET("editors/3d_gizmos/gizmo_colors/particle_collision");
create_material("shape_material_collision", gizmo_color_collision);
gizmo_color_collision.a = 0.15;
create_material("shape_material_collision_internal", gizmo_color_collision);

View file

@ -32,7 +32,6 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/physics/joints/cone_twist_joint_3d.h"
#include "scene/3d/physics/joints/generic_6dof_joint_3d.h"
#include "scene/3d/physics/joints/hinge_joint_3d.h"
@ -280,8 +279,8 @@ void JointGizmosDrawer::draw_cone(const Transform3D &p_offset, const Basis &p_ba
Joint3DGizmoPlugin::Joint3DGizmoPlugin() {
create_material("joint_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/joint"));
create_material("joint_body_a_material", EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/joint_body_a", Color(0.6, 0.8, 1)));
create_material("joint_body_b_material", EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/joint_body_b", Color(0.6, 0.9, 1)));
create_material("joint_body_a_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/joint_body_a"));
create_material("joint_body_b_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/joint_body_b"));
update_timer = memnew(Timer);
update_timer->set_name("JointGizmoUpdateTimer");
@ -293,9 +292,15 @@ Joint3DGizmoPlugin::Joint3DGizmoPlugin() {
void Joint3DGizmoPlugin::incremental_update_gizmos() {
if (!current_gizmos.is_empty()) {
update_idx++;
update_idx = update_idx % current_gizmos.size();
redraw(current_gizmos.get(update_idx));
HashSet<EditorNode3DGizmo *>::Iterator E = current_gizmos.find(last_drawn);
if (E) {
++E;
}
if (!E) {
E = current_gizmos.begin();
}
redraw(*E);
last_drawn = *E;
}
}

View file

@ -37,7 +37,7 @@ class Joint3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Joint3DGizmoPlugin, EditorNode3DGizmoPlugin);
Timer *update_timer = nullptr;
uint64_t update_idx = 0;
EditorNode3DGizmo *last_drawn = nullptr;
void incremental_update_gizmos();

View file

@ -30,7 +30,6 @@
#include "label_3d_gizmo_plugin.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/label_3d.h"
Label3DGizmoPlugin::Label3DGizmoPlugin() {

View file

@ -30,9 +30,7 @@
#include "light_3d_gizmo_plugin.h"
#include "core/config/project_settings.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/plugins/node_3d_editor_plugin.h"

View file

@ -33,18 +33,20 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/lightmap_gi.h"
LightmapGIGizmoPlugin::LightmapGIGizmoPlugin() {
Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/lightmap_lines", Color(0.5, 0.6, 1));
Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/lightmap_lines");
gizmo_color.a = 0.1;
create_material("lightmap_lines", gizmo_color);
Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
// Fade out probes when camera gets too close to them.
mat->set_distance_fade(StandardMaterial3D::DISTANCE_FADE_PIXEL_DITHER);
mat->set_distance_fade_min_distance(0.5);
mat->set_distance_fade_max_distance(1.5);
mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, false);
mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);

View file

@ -33,13 +33,12 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/lightmap_probe.h"
LightmapProbeGizmoPlugin::LightmapProbeGizmoPlugin() {
create_icon_material("lightmap_probe_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoLightmapProbe"), EditorStringName(EditorIcons)));
Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/lightprobe_lines", Color(0.5, 0.6, 1));
Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/lightprobe_lines");
gizmo_color.a = 0.3;
create_material("lightprobe_lines", gizmo_color);

View file

@ -32,11 +32,10 @@
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/marker_3d.h"
Marker3DGizmoPlugin::Marker3DGizmoPlugin() {
pos3d_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
pos3d_mesh.instantiate();
Vector<Vector3> cursor_points;
Vector<Color> cursor_colors;

View file

@ -33,6 +33,7 @@
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/soft_body_3d.h"
#include "scene/resources/3d/primitive_meshes.h"
MeshInstance3DGizmoPlugin::MeshInstance3DGizmoPlugin() {
}
@ -60,11 +61,26 @@ void MeshInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Ref<Mesh> m = mesh->get_mesh();
if (!m.is_valid()) {
if (m.is_null()) {
return; //none
}
Ref<TriangleMesh> tm = m->generate_triangle_mesh();
Ref<TriangleMesh> tm;
Ref<PlaneMesh> plane_mesh = mesh->get_mesh();
if (plane_mesh.is_valid() && (plane_mesh->get_subdivide_depth() > 0 || plane_mesh->get_subdivide_width() > 0)) {
// PlaneMesh subdiv makes gizmo redraw very slow due to TriangleMesh BVH calculation for every face.
// For gizmo collision this is very much unnecessary since a PlaneMesh is always flat, 2 faces is enough.
Ref<PlaneMesh> simple_plane_mesh;
simple_plane_mesh.instantiate();
simple_plane_mesh->set_orientation(plane_mesh->get_orientation());
simple_plane_mesh->set_size(plane_mesh->get_size());
simple_plane_mesh->set_center_offset(plane_mesh->get_center_offset());
tm = simple_plane_mesh->generate_triangle_mesh();
} else {
tm = m->generate_triangle_mesh();
}
if (tm.is_valid()) {
p_gizmo->add_collision_triangles(tm);
}

View file

@ -70,68 +70,137 @@ void NavigationLink3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p_gizmo->clear();
// Draw line between the points.
// Number of points in an octant. So there ill be 8 * points_in_octant points in total.
// Correspond to the smoothness of the circle.
const uint32_t points_in_octant = 4;
real_t inc = (Math_PI / (4 * points_in_octant));
Vector<Vector3> lines;
lines.append(start_position);
lines.append(end_position);
// points_in_octant * 8 * 2 per circle * 2 circles. 2 for the start-end. 4 for the arrow, and another 4 if bidirectionnal.
lines.resize(points_in_octant * 8 * 2 * 2 + 2 + 4 + (link->is_bidirectional() ? 4 : 0));
uint32_t index = 0;
Vector3 *lines_ptrw = lines.ptrw();
// Draw line between the points.
lines_ptrw[index++] = start_position;
lines_ptrw[index++] = end_position;
real_t search_radius_squared = search_radius * search_radius;
// Draw start position search radius
for (int i = 0; i < 30; i++) {
// Create a circle
const float ra = Math::deg_to_rad((float)(i * 12));
const float rb = Math::deg_to_rad((float)((i + 1) * 12));
const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * search_radius;
const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * search_radius;
// Draw circles at start and end positions in one go.
real_t r = 0;
Vector2 a = Vector2(search_radius, 0);
for (uint32_t i = 0; i < points_in_octant; i++) {
r += inc;
real_t x = Math::cos(r) * search_radius;
real_t y = Math::sqrt(search_radius_squared - (x * x));
// Draw axis-aligned circle
// Draw axis-aligned circle.
switch (up_axis) {
case Vector3::AXIS_X:
lines.append(start_position + Vector3(0, a.x, a.y));
lines.append(start_position + Vector3(0, b.x, b.y));
#define PUSH_OCTANT(_position, a, b) \
lines_ptrw[index++] = _position + Vector3(0, a.x, a.y); \
lines_ptrw[index++] = _position + Vector3(0, x, y); \
lines_ptrw[index++] = _position + Vector3(0, -a.x, a.y); \
lines_ptrw[index++] = _position + Vector3(0, x, y); \
lines_ptrw[index++] = _position + Vector3(0, a.x, -a.y); \
lines_ptrw[index++] = _position + Vector3(0, x, -y); \
lines_ptrw[index++] = _position + Vector3(0, -a.x, -a.y); \
lines_ptrw[index++] = _position + Vector3(0, x, y); \
lines_ptrw[index++] = _position + Vector3(0, a.y, a.x); \
lines_ptrw[index++] = _position + Vector3(0, y, x); \
lines_ptrw[index++] = _position + Vector3(0, -a.y, a.x); \
lines_ptrw[index++] = _position + Vector3(0, -y, x); \
lines_ptrw[index++] = _position + Vector3(0, a.y, -a.x); \
lines_ptrw[index++] = _position + Vector3(0, y, -x); \
lines_ptrw[index++] = _position + Vector3(0, -a.y, -a.x); \
lines_ptrw[index++] = _position + Vector3(0, -y, -x);
PUSH_OCTANT(start_position, a, b)
PUSH_OCTANT(end_position, a, b)
#undef PUSH_OCTANT
break;
case Vector3::AXIS_Y:
lines.append(start_position + Vector3(a.x, 0, a.y));
lines.append(start_position + Vector3(b.x, 0, b.y));
#define PUSH_OCTANT(_position, a, b) \
lines_ptrw[index++] = _position + Vector3(a.x, 0, a.y); \
lines_ptrw[index++] = _position + Vector3(x, 0, y); \
lines_ptrw[index++] = _position + Vector3(-a.x, 0, a.y); \
lines_ptrw[index++] = _position + Vector3(-x, 0, y); \
lines_ptrw[index++] = _position + Vector3(a.x, 0, -a.y); \
lines_ptrw[index++] = _position + Vector3(x, 0, -y); \
lines_ptrw[index++] = _position + Vector3(-a.x, 0, -a.y); \
lines_ptrw[index++] = _position + Vector3(-x, 0, -y); \
lines_ptrw[index++] = _position + Vector3(a.y, 0, a.x); \
lines_ptrw[index++] = _position + Vector3(y, 0, x); \
lines_ptrw[index++] = _position + Vector3(-a.y, 0, a.x); \
lines_ptrw[index++] = _position + Vector3(-y, 0, x); \
lines_ptrw[index++] = _position + Vector3(a.y, 0, -a.x); \
lines_ptrw[index++] = _position + Vector3(y, 0, -x); \
lines_ptrw[index++] = _position + Vector3(-a.y, 0, -a.x); \
lines_ptrw[index++] = _position + Vector3(-y, 0, -x);
PUSH_OCTANT(start_position, a, b)
PUSH_OCTANT(end_position, a, b)
#undef PUSH_OCTANT
break;
case Vector3::AXIS_Z:
lines.append(start_position + Vector3(a.x, a.y, 0));
lines.append(start_position + Vector3(b.x, b.y, 0));
#define PUSH_OCTANT(_position, a, b) \
lines_ptrw[index++] = _position + Vector3(a.x, a.y, 0); \
lines_ptrw[index++] = _position + Vector3(x, y, 0); \
lines_ptrw[index++] = _position + Vector3(-a.x, a.y, 0); \
lines_ptrw[index++] = _position + Vector3(-x, y, 0); \
lines_ptrw[index++] = _position + Vector3(a.x, -a.y, 0); \
lines_ptrw[index++] = _position + Vector3(x, -y, 0); \
lines_ptrw[index++] = _position + Vector3(-a.x, -a.y, 0); \
lines_ptrw[index++] = _position + Vector3(-x, -y, 0); \
lines_ptrw[index++] = _position + Vector3(a.y, a.x, 0); \
lines_ptrw[index++] = _position + Vector3(y, x, 0); \
lines_ptrw[index++] = _position + Vector3(-a.y, a.x, 0); \
lines_ptrw[index++] = _position + Vector3(-y, x, 0); \
lines_ptrw[index++] = _position + Vector3(a.y, -a.x, 0); \
lines_ptrw[index++] = _position + Vector3(y, -x, 0); \
lines_ptrw[index++] = _position + Vector3(-a.y, -a.x, 0); \
lines_ptrw[index++] = _position + Vector3(-y, -x, 0);
PUSH_OCTANT(start_position, a, b)
PUSH_OCTANT(end_position, a, b)
#undef PUSH_OCTANT
break;
}
a.x = x;
a.y = y;
}
// Draw end position search radius
for (int i = 0; i < 30; i++) {
// Create a circle
const float ra = Math::deg_to_rad((float)(i * 12));
const float rb = Math::deg_to_rad((float)((i + 1) * 12));
const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * search_radius;
const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * search_radius;
const Vector3 link_segment = end_position - start_position;
const Vector3 up = Vector3(0.0, 1.0, 0.0);
const float arror_len = 0.5;
// Draw axis-aligned circle
switch (up_axis) {
case Vector3::AXIS_X:
lines.append(end_position + Vector3(0, a.x, a.y));
lines.append(end_position + Vector3(0, b.x, b.y));
break;
case Vector3::AXIS_Y:
lines.append(end_position + Vector3(a.x, 0, a.y));
lines.append(end_position + Vector3(b.x, 0, b.y));
break;
case Vector3::AXIS_Z:
lines.append(end_position + Vector3(a.x, a.y, 0));
lines.append(end_position + Vector3(b.x, b.y, 0));
break;
}
{
Vector3 anchor = start_position + (link_segment * 0.75);
Vector3 direction = start_position.direction_to(end_position);
Vector3 arrow_dir = direction.cross(up);
lines_ptrw[index++] = anchor;
lines_ptrw[index++] = anchor + (arrow_dir - direction) * arror_len;
arrow_dir = -direction.cross(up);
lines_ptrw[index++] = anchor;
lines_ptrw[index++] = anchor + (arrow_dir - direction) * arror_len;
}
if (link->is_bidirectional()) {
Vector3 anchor = start_position + (link_segment * 0.25);
Vector3 direction = end_position.direction_to(start_position);
Vector3 arrow_dir = direction.cross(up);
lines_ptrw[index++] = anchor;
lines_ptrw[index++] = anchor + (arrow_dir - direction) * arror_len;
arrow_dir = -direction.cross(up);
lines_ptrw[index++] = anchor;
lines_ptrw[index++] = anchor + (arrow_dir - direction) * arror_len;
}
p_gizmo->add_lines(lines, link->is_enabled() ? link_material : link_material_disabled);
p_gizmo->add_collision_segments(lines);
Vector<Vector3> handles;
handles.append(start_position);
handles.append(end_position);
p_gizmo->add_handles(handles, handles_material);
p_gizmo->add_handles(Vector({ start_position, end_position }), handles_material);
}
String NavigationLink3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {

View file

@ -30,7 +30,6 @@
#include "navigation_region_3d_gizmo_plugin.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/navigation_region_3d.h"
#include "servers/navigation_server_3d.h"

View file

@ -36,7 +36,7 @@
#include "scene/3d/occluder_instance_3d.h"
OccluderInstance3DGizmoPlugin::OccluderInstance3DGizmoPlugin() {
create_material("line_material", EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/occluder", Color(0.8, 0.5, 1)));
create_material("line_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/occluder"));
create_handle_material("handles");
}
@ -242,7 +242,7 @@ void OccluderInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Ref<Occluder3D> o = occluder_instance->get_occluder();
if (!o.is_valid()) {
if (o.is_null()) {
return;
}

View file

@ -0,0 +1,339 @@
/**************************************************************************/
/* particles_3d_emission_shape_gizmo_plugin.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "particles_3d_emission_shape_gizmo_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/cpu_particles_3d.h"
#include "scene/3d/gpu_particles_3d.h"
#include "scene/resources/3d/primitive_meshes.h"
#include "scene/resources/particle_process_material.h"
Particles3DEmissionShapeGizmoPlugin::Particles3DEmissionShapeGizmoPlugin() {
helper.instantiate();
Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/particles_emission_shape", Color(0.5, 0.7, 1));
create_material("particles_emission_shape_material", gizmo_color);
create_handle_material("handles");
}
bool Particles3DEmissionShapeGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<GPUParticles3D>(p_spatial) || Object::cast_to<CPUParticles3D>(p_spatial) != nullptr;
}
String Particles3DEmissionShapeGizmoPlugin::get_gizmo_name() const {
return "Particles3DEmissionShape";
}
int Particles3DEmissionShapeGizmoPlugin::get_priority() const {
return -1;
}
bool Particles3DEmissionShapeGizmoPlugin::is_selectable_when_hidden() const {
return true;
}
String Particles3DEmissionShapeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
return "";
}
Variant Particles3DEmissionShapeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
return Variant();
}
void Particles3DEmissionShapeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
}
void Particles3DEmissionShapeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
}
void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p_gizmo->clear();
if (Object::cast_to<GPUParticles3D>(p_gizmo->get_node_3d())) {
const GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_node_3d());
const Ref<ParticleProcessMaterial> mat = particles->get_process_material();
if (mat.is_valid()) {
const ParticleProcessMaterial::EmissionShape shape = mat->get_emission_shape();
const Ref<Material> material = get_material("particles_emission_shape_material", p_gizmo);
const Ref<Material> handles_material = get_material("handles");
if (shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE || shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE_SURFACE) {
const Vector3 offset = mat->get_emission_shape_offset();
const Vector3 scale = mat->get_emission_shape_scale();
const float r = mat->get_emission_sphere_radius();
Vector<Vector3> points;
for (int i = 0; i <= 120; i++) {
const float ra = Math::deg_to_rad((float)(i * 3));
const float rb = Math::deg_to_rad((float)((i + 1) * 3));
const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
points.push_back(Vector3(a.x * scale.x + offset.x, offset.y, a.y * scale.z + offset.z));
points.push_back(Vector3(b.x * scale.x + offset.x, offset.y, b.y * scale.z + offset.z));
points.push_back(Vector3(offset.x, a.x * scale.y + offset.y, a.y * scale.z + offset.z));
points.push_back(Vector3(offset.x, b.x * scale.y + offset.y, b.y * scale.z + offset.z));
points.push_back(Vector3(a.x * scale.x + offset.x, a.y * scale.y + offset.y, offset.z));
points.push_back(Vector3(b.x * scale.x + offset.x, b.y * scale.y + offset.y, offset.z));
}
if (p_gizmo->is_selected()) {
p_gizmo->add_lines(points, material);
}
} else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_BOX) {
const Vector3 offset = mat->get_emission_shape_offset();
const Vector3 scale = mat->get_emission_shape_scale();
const Vector3 box_extents = mat->get_emission_box_extents();
Ref<BoxMesh> box;
box.instantiate();
const AABB box_aabb = box->get_aabb();
Vector<Vector3> lines;
for (int i = 0; i < 12; i++) {
Vector3 a;
Vector3 b;
box_aabb.get_edge(i, a, b);
// Multiplication by 2 due to the extents being only half of the box size.
lines.push_back(a * 2.0 * scale * box_extents + offset);
lines.push_back(b * 2.0 * scale * box_extents + offset);
}
if (p_gizmo->is_selected()) {
p_gizmo->add_lines(lines, material);
}
} else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_RING) {
const Vector3 offset = mat->get_emission_shape_offset();
const Vector3 scale = mat->get_emission_shape_scale();
const float ring_height = mat->get_emission_ring_height();
const float half_ring_height = ring_height / 2;
const float ring_radius = mat->get_emission_ring_radius();
const float ring_inner_radius = mat->get_emission_ring_inner_radius();
const Vector3 ring_axis = mat->get_emission_ring_axis();
const float ring_cone_angle = mat->get_emission_ring_cone_angle();
const float ring_radius_top = MAX(ring_radius - Math::tan(Math::deg_to_rad(90.0 - ring_cone_angle)) * ring_height, 0.0);
const float ring_inner_radius_top = (ring_inner_radius / ring_radius) * ring_radius_top;
Vector<Vector3> points;
Basis basis;
basis.rows[1] = ring_axis.normalized();
basis.rows[0] = Vector3(basis[1][1], -basis[1][2], -basis[1][0]).normalized();
basis.rows[0] = (basis[0] - basis[0].dot(basis[1]) * basis[1]).normalized();
basis[2] = basis[0].cross(basis[1]).normalized();
basis.invert();
for (int i = 0; i <= 120; i++) {
const float ra = Math::deg_to_rad((float)(i * 3));
const float ra_sin = Math::sin(ra);
const float ra_cos = Math::cos(ra);
const float rb = Math::deg_to_rad((float)((i + 1) * 3));
const float rb_sin = Math::sin(rb);
const float rb_cos = Math::cos(rb);
const Point2 a = Vector2(ra_sin, ra_cos) * ring_radius;
const Point2 b = Vector2(rb_sin, rb_cos) * ring_radius;
const Point2 a2 = Vector2(ra_sin, ra_cos) * ring_radius_top;
const Point2 b2 = Vector2(rb_sin, rb_cos) * ring_radius_top;
const Point2 inner_a = Vector2(ra_sin, ra_cos) * ring_inner_radius;
const Point2 inner_b = Vector2(rb_sin, rb_cos) * ring_inner_radius;
const Point2 inner_a2 = Vector2(ra_sin, ra_cos) * ring_inner_radius_top;
const Point2 inner_b2 = Vector2(rb_sin, rb_cos) * ring_inner_radius_top;
// Outer top ring cap.
points.push_back(basis.xform(Vector3(a2.x, half_ring_height, a2.y)) * scale + offset);
points.push_back(basis.xform(Vector3(b2.x, half_ring_height, b2.y)) * scale + offset);
// Outer bottom ring cap.
points.push_back(basis.xform(Vector3(a.x, -half_ring_height, a.y)) * scale + offset);
points.push_back(basis.xform(Vector3(b.x, -half_ring_height, b.y)) * scale + offset);
// Inner top ring cap.
points.push_back(basis.xform(Vector3(inner_a2.x, half_ring_height, inner_a2.y)) * scale + offset);
points.push_back(basis.xform(Vector3(inner_b2.x, half_ring_height, inner_b2.y)) * scale + offset);
// Inner bottom ring cap.
points.push_back(basis.xform(Vector3(inner_a.x, -half_ring_height, inner_a.y)) * scale + offset);
points.push_back(basis.xform(Vector3(inner_b.x, -half_ring_height, inner_b.y)) * scale + offset);
}
for (int i = 0; i <= 120; i = i + 30) {
const float ra = Math::deg_to_rad((float)(i * 3));
const float ra_sin = Math::sin(ra);
const float ra_cos = Math::cos(ra);
const Point2 a = Vector2(ra_sin, ra_cos) * ring_radius;
const Point2 a2 = Vector2(ra_sin, ra_cos) * ring_radius_top;
const Point2 inner_a = Vector2(ra_sin, ra_cos) * ring_inner_radius;
const Point2 inner_a2 = Vector2(ra_sin, ra_cos) * ring_inner_radius_top;
// Outer 90 degrees vertical lines.
points.push_back(basis.xform(Vector3(a2.x, half_ring_height, a2.y)) * scale + offset);
points.push_back(basis.xform(Vector3(a.x, -half_ring_height, a.y)) * scale + offset);
// Inner 90 degrees vertical lines.
points.push_back(basis.xform(Vector3(inner_a2.x, half_ring_height, inner_a2.y)) * scale + offset);
points.push_back(basis.xform(Vector3(inner_a.x, -half_ring_height, inner_a.y)) * scale + offset);
}
if (p_gizmo->is_selected()) {
p_gizmo->add_lines(points, material);
}
}
}
} else if (Object::cast_to<CPUParticles3D>(p_gizmo->get_node_3d())) {
const CPUParticles3D *particles = Object::cast_to<CPUParticles3D>(p_gizmo->get_node_3d());
const CPUParticles3D::EmissionShape shape = particles->get_emission_shape();
const Ref<Material> material = get_material("particles_emission_shape_material", p_gizmo);
const Ref<Material> handles_material = get_material("handles");
if (shape == CPUParticles3D::EMISSION_SHAPE_SPHERE || shape == CPUParticles3D::EMISSION_SHAPE_SPHERE_SURFACE) {
const float r = particles->get_emission_sphere_radius();
Vector<Vector3> points;
for (int i = 0; i <= 120; i++) {
const float ra = Math::deg_to_rad((float)(i * 3));
const float rb = Math::deg_to_rad((float)((i + 1) * 3));
const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
points.push_back(Vector3(a.x, 0.0, a.y));
points.push_back(Vector3(b.x, 0.0, b.y));
points.push_back(Vector3(0.0, a.x, a.y));
points.push_back(Vector3(0.0, b.x, b.y));
points.push_back(Vector3(a.x, a.y, 0.0));
points.push_back(Vector3(b.x, b.y, 0.0));
}
if (p_gizmo->is_selected()) {
p_gizmo->add_lines(points, material);
}
} else if (shape == CPUParticles3D::EMISSION_SHAPE_BOX) {
const Vector3 box_extents = particles->get_emission_box_extents();
Ref<BoxMesh> box;
box.instantiate();
const AABB box_aabb = box->get_aabb();
Vector<Vector3> lines;
for (int i = 0; i < 12; i++) {
Vector3 a;
Vector3 b;
box_aabb.get_edge(i, a, b);
// Multiplication by 2 due to the extents being only half of the box size.
lines.push_back(a * 2.0 * box_extents);
lines.push_back(b * 2.0 * box_extents);
}
if (p_gizmo->is_selected()) {
p_gizmo->add_lines(lines, material);
}
} else if (shape == CPUParticles3D::EMISSION_SHAPE_RING) {
const float ring_height = particles->get_emission_ring_height();
const float half_ring_height = ring_height / 2;
const float ring_radius = particles->get_emission_ring_radius();
const float ring_inner_radius = particles->get_emission_ring_inner_radius();
const Vector3 ring_axis = particles->get_emission_ring_axis();
const float ring_cone_angle = particles->get_emission_ring_cone_angle();
const float ring_radius_top = MAX(ring_radius - Math::tan(Math::deg_to_rad(90.0 - ring_cone_angle)) * ring_height, 0.0);
const float ring_inner_radius_top = (ring_inner_radius / ring_radius) * ring_radius_top;
Vector<Vector3> points;
Basis basis;
basis.rows[1] = ring_axis.normalized();
basis.rows[0] = Vector3(basis[1][1], -basis[1][2], -basis[1][0]).normalized();
basis.rows[0] = (basis[0] - basis[0].dot(basis[1]) * basis[1]).normalized();
basis[2] = basis[0].cross(basis[1]).normalized();
basis.invert();
for (int i = 0; i <= 120; i++) {
const float ra = Math::deg_to_rad((float)(i * 3));
const float ra_sin = Math::sin(ra);
const float ra_cos = Math::cos(ra);
const float rb = Math::deg_to_rad((float)((i + 1) * 3));
const float rb_sin = Math::sin(rb);
const float rb_cos = Math::cos(rb);
const Point2 a = Vector2(ra_sin, ra_cos) * ring_radius;
const Point2 b = Vector2(rb_sin, rb_cos) * ring_radius;
const Point2 a2 = Vector2(ra_sin, ra_cos) * ring_radius_top;
const Point2 b2 = Vector2(rb_sin, rb_cos) * ring_radius_top;
const Point2 inner_a = Vector2(ra_sin, ra_cos) * ring_inner_radius;
const Point2 inner_b = Vector2(rb_sin, rb_cos) * ring_inner_radius;
const Point2 inner_a2 = Vector2(ra_sin, ra_cos) * ring_inner_radius_top;
const Point2 inner_b2 = Vector2(rb_sin, rb_cos) * ring_inner_radius_top;
// Outer top ring cap.
points.push_back(basis.xform(Vector3(a2.x, half_ring_height, a2.y)));
points.push_back(basis.xform(Vector3(b2.x, half_ring_height, b2.y)));
// Outer bottom ring cap.
points.push_back(basis.xform(Vector3(a.x, -half_ring_height, a.y)));
points.push_back(basis.xform(Vector3(b.x, -half_ring_height, b.y)));
// Inner top ring cap.
points.push_back(basis.xform(Vector3(inner_a2.x, half_ring_height, inner_a2.y)));
points.push_back(basis.xform(Vector3(inner_b2.x, half_ring_height, inner_b2.y)));
// Inner bottom ring cap.
points.push_back(basis.xform(Vector3(inner_a.x, -half_ring_height, inner_a.y)));
points.push_back(basis.xform(Vector3(inner_b.x, -half_ring_height, inner_b.y)));
}
for (int i = 0; i <= 120; i = i + 30) {
const float ra = Math::deg_to_rad((float)(i * 3));
const float ra_sin = Math::sin(ra);
const float ra_cos = Math::cos(ra);
const Point2 a = Vector2(ra_sin, ra_cos) * ring_radius;
const Point2 a2 = Vector2(ra_sin, ra_cos) * ring_radius_top;
const Point2 inner_a = Vector2(ra_sin, ra_cos) * ring_inner_radius;
const Point2 inner_a2 = Vector2(ra_sin, ra_cos) * ring_inner_radius_top;
// Outer 90 degrees vertical lines.
points.push_back(basis.xform(Vector3(a2.x, half_ring_height, a2.y)));
points.push_back(basis.xform(Vector3(a.x, -half_ring_height, a.y)));
// Inner 90 degrees vertical lines.
points.push_back(basis.xform(Vector3(inner_a2.x, half_ring_height, inner_a2.y)));
points.push_back(basis.xform(Vector3(inner_a.x, -half_ring_height, inner_a.y)));
}
if (p_gizmo->is_selected()) {
p_gizmo->add_lines(points, material);
}
}
}
}

View file

@ -0,0 +1,57 @@
/**************************************************************************/
/* particles_3d_emission_shape_gizmo_plugin.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#ifndef PARTICLES_3D_EMISSION_SHAPE_GIZMO_PLUGIN_H
#define PARTICLES_3D_EMISSION_SHAPE_GIZMO_PLUGIN_H
#include "editor/plugins/gizmos/gizmo_3d_helper.h"
#include "editor/plugins/node_3d_editor_gizmos.h"
class Particles3DEmissionShapeGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Particles3DEmissionShapeGizmoPlugin, EditorNode3DGizmoPlugin);
Ref<Gizmo3DHelper> helper;
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const override;
bool is_selectable_when_hidden() const override;
void redraw(EditorNode3DGizmo *p_gizmo) override;
String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override;
void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override;
Particles3DEmissionShapeGizmoPlugin();
};
#endif // PARTICLES_3D_EMISSION_SHAPE_GIZMO_PLUGIN_H

View file

@ -32,9 +32,8 @@
#include "editor/editor_settings.h"
#include "editor/plugins/gizmos/joint_3d_gizmo_plugin.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/physical_bone_simulator_3d.h"
#include "scene/3d/physics/physical_bone_3d.h"
#include "scene/3d/physics/physics_body_3d.h"
PhysicalBone3DGizmoPlugin::PhysicalBone3DGizmoPlugin() {
create_material("joint_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/joint"));

View file

@ -30,8 +30,6 @@
#include "ray_cast_3d_gizmo_plugin.h"
#include "editor/editor_settings.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/physics/ray_cast_3d.h"
RayCast3DGizmoPlugin::RayCast3DGizmoPlugin() {

View file

@ -40,16 +40,13 @@
ReflectionProbeGizmoPlugin::ReflectionProbeGizmoPlugin() {
helper.instantiate();
Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/reflection_probe", Color(0.6, 1, 0.5));
Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/reflection_probe");
create_material("reflection_probe_material", gizmo_color);
gizmo_color.a = 0.5;
create_material("reflection_internal_material", gizmo_color);
gizmo_color.a = 0.1;
create_material("reflection_probe_solid_material", gizmo_color);
create_icon_material("reflection_probe_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoReflectionProbe"), EditorStringName(EditorIcons)));
create_handle_material("handles");
}
@ -165,29 +162,43 @@ void ReflectionProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
aabb.position = -size / 2;
aabb.size = size;
for (int i = 0; i < 12; i++) {
Vector3 a, b;
aabb.get_edge(i, a, b);
lines.push_back(a);
lines.push_back(b);
AABB blend_aabb;
for (int i = 0; i < 3; i++) {
blend_aabb.position[i] = aabb.position[i] + probe->get_blend_distance();
blend_aabb.size[i] = aabb.size[i] - probe->get_blend_distance() * 2.0;
if (blend_aabb.size[i] < blend_aabb.position[i]) {
blend_aabb.position[i] = aabb.position[i] + aabb.size[i] / 2.0;
blend_aabb.size[i] = 0.0;
}
}
for (int i = 0; i < 8; i++) {
Vector3 ep = aabb.get_endpoint(i);
internal_lines.push_back(probe->get_origin_offset());
internal_lines.push_back(ep);
if (probe->get_blend_distance() != 0.0) {
for (int i = 0; i < 12; i++) {
Vector3 a;
Vector3 b;
blend_aabb.get_edge(i, a, b);
lines.push_back(a);
lines.push_back(b);
}
for (int i = 0; i < 8; i++) {
Vector3 ep = aabb.get_endpoint(i);
internal_lines.push_back(blend_aabb.get_endpoint(i));
internal_lines.push_back(ep);
}
}
Vector<Vector3> handles = helper->box_get_handles(probe->get_size());
for (int i = 0; i < 3; i++) {
Vector3 orig_handle = probe->get_origin_offset();
orig_handle[i] -= 0.25;
lines.push_back(orig_handle);
handles.push_back(orig_handle);
if (probe->get_origin_offset() != Vector3(0.0, 0.0, 0.0)) {
for (int i = 0; i < 3; i++) {
Vector3 orig_handle = probe->get_origin_offset();
orig_handle[i] -= 0.25;
lines.push_back(orig_handle);
orig_handle[i] += 0.5;
lines.push_back(orig_handle);
orig_handle[i] += 0.5;
lines.push_back(orig_handle);
}
}
Ref<Material> material = get_material("reflection_probe_material", p_gizmo);
@ -196,11 +207,6 @@ void ReflectionProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p_gizmo->add_lines(lines, material);
p_gizmo->add_lines(internal_lines, material_internal);
if (p_gizmo->is_selected()) {
Ref<Material> solid_material = get_material("reflection_probe_solid_material", p_gizmo);
p_gizmo->add_solid_box(solid_material, probe->get_size());
}
p_gizmo->add_handles(handles, get_material("handles"));
}

View file

@ -30,8 +30,6 @@
#include "shape_cast_3d_gizmo_plugin.h"
#include "editor/editor_settings.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/physics/shape_cast_3d.h"
ShapeCast3DGizmoPlugin::ShapeCast3DGizmoPlugin() {

View file

@ -30,8 +30,6 @@
#include "soft_body_3d_gizmo_plugin.h"
#include "editor/editor_settings.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/soft_body_3d.h"
SoftBody3DGizmoPlugin::SoftBody3DGizmoPlugin() {

View file

@ -30,10 +30,7 @@
#include "spring_arm_3d_gizmo_plugin.h"
#include "editor/editor_settings.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/physics/spring_arm_3d.h"
#include "scene/resources/3d/shape_3d.h"
void SpringArm3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
SpringArm3D *spring_arm = Object::cast_to<SpringArm3D>(p_gizmo->get_node_3d());

View file

@ -0,0 +1,424 @@
/**************************************************************************/
/* spring_bone_3d_gizmo_plugin.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "spring_bone_3d_gizmo_plugin.h"
#include "editor/editor_settings.h"
#include "scene/3d/spring_bone_collision_capsule_3d.h"
#include "scene/3d/spring_bone_collision_plane_3d.h"
#include "scene/3d/spring_bone_collision_sphere_3d.h"
// SpringBoneSimulator3D
SpringBoneSimulator3DGizmoPlugin::SelectionMaterials SpringBoneSimulator3DGizmoPlugin::selection_materials;
SpringBoneSimulator3DGizmoPlugin::SpringBoneSimulator3DGizmoPlugin() {
selection_materials.unselected_mat.instantiate();
selection_materials.unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
selection_materials.unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
selection_materials.selected_mat.instantiate();
Ref<Shader> sh;
sh.instantiate();
sh->set_code(R"(
// Skeleton 3D gizmo bones shader.
shader_type spatial;
render_mode unshaded, shadows_disabled;
void vertex() {
if (!OUTPUT_IS_SRGB) {
COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) );
}
VERTEX = VERTEX;
POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0);
POSITION.z = mix(POSITION.z, POSITION.w, 0.998);
}
void fragment() {
ALBEDO = COLOR.rgb;
ALPHA = COLOR.a;
}
)");
selection_materials.selected_mat->set_shader(sh);
}
SpringBoneSimulator3DGizmoPlugin::~SpringBoneSimulator3DGizmoPlugin() {
selection_materials.unselected_mat.unref();
selection_materials.selected_mat.unref();
}
bool SpringBoneSimulator3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<SpringBoneSimulator3D>(p_spatial) != nullptr;
}
String SpringBoneSimulator3DGizmoPlugin::get_gizmo_name() const {
return "SpringBoneSimulator3D";
}
int SpringBoneSimulator3DGizmoPlugin::get_priority() const {
return -1;
}
void SpringBoneSimulator3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
SpringBoneSimulator3D *simulator = Object::cast_to<SpringBoneSimulator3D>(p_gizmo->get_node_3d());
p_gizmo->clear();
if (!simulator->get_setting_count()) {
return;
}
Skeleton3D *skeleton = simulator->get_skeleton();
if (!skeleton) {
return;
}
Ref<ArrayMesh> mesh = get_joints_mesh(skeleton, simulator, p_gizmo->is_selected());
Transform3D skel_tr = simulator->get_global_transform().inverse() * skeleton->get_global_transform();
p_gizmo->add_mesh(mesh, Ref<Material>(), skel_tr, skeleton->register_skin(skeleton->create_skin_from_rest_transforms()));
}
Ref<ArrayMesh> SpringBoneSimulator3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_skeleton, SpringBoneSimulator3D *p_simulator, bool p_is_selected) {
Color bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/spring_bone_joint");
Ref<SurfaceTool> surface_tool;
surface_tool.instantiate();
surface_tool->begin(Mesh::PRIMITIVE_LINES);
if (p_is_selected) {
surface_tool->set_material(selection_materials.selected_mat);
} else {
selection_materials.unselected_mat->set_albedo(bone_color);
surface_tool->set_material(selection_materials.unselected_mat);
}
LocalVector<int> bones;
LocalVector<float> weights;
bones.resize(4);
weights.resize(4);
for (int i = 0; i < 4; i++) {
bones[i] = 0;
weights[i] = 0;
}
weights[0] = 1;
for (int i = 0; i < p_simulator->get_setting_count(); i++) {
int current_bone = -1;
int prev_bone = -1;
int joint_end = p_simulator->get_joint_count(i) - 1;
for (int j = 0; j <= joint_end; j++) {
current_bone = p_simulator->get_joint_bone(i, j);
if (j > 0) {
Transform3D parent_global_pose = p_skeleton->get_bone_global_rest(prev_bone);
Transform3D global_pose = p_skeleton->get_bone_global_rest(current_bone);
draw_line(surface_tool, parent_global_pose.origin, global_pose.origin, bone_color);
draw_sphere(surface_tool, global_pose.basis, global_pose.origin, p_simulator->get_joint_radius(i, j - 1), bone_color);
}
if (j == joint_end && p_simulator->is_end_bone_extended(i) && p_simulator->get_end_bone_length(i) > 0) {
Vector3 axis = p_simulator->get_end_bone_axis(current_bone, p_simulator->get_end_bone_direction(i));
if (axis.is_zero_approx()) {
continue;
}
bones[0] = current_bone;
surface_tool->set_bones(bones);
surface_tool->set_weights(weights);
Transform3D global_pose = p_skeleton->get_bone_global_rest(current_bone);
axis = global_pose.xform(axis * p_simulator->get_end_bone_length(i));
draw_line(surface_tool, global_pose.origin, axis, bone_color);
draw_sphere(surface_tool, global_pose.basis, axis, p_simulator->get_joint_radius(i, j), bone_color);
} else {
bones[0] = current_bone;
surface_tool->set_bones(bones);
surface_tool->set_weights(weights);
}
prev_bone = current_bone;
}
}
return surface_tool->commit();
}
void SpringBoneSimulator3DGizmoPlugin::draw_sphere(Ref<SurfaceTool> &p_surface_tool, const Basis &p_basis, const Vector3 &p_center, float p_radius, const Color &p_color) {
static const Vector3 VECTOR3_RIGHT = Vector3(1, 0, 0);
static const Vector3 VECTOR3_UP = Vector3(0, 1, 0);
static const Vector3 VECTOR3_FORWARD = Vector3(0, 0, 1);
static const int STEP = 16;
static const float SPPI = Math_TAU / (float)STEP;
for (int i = 1; i <= STEP; i++) {
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_UP * p_radius)).rotated(p_basis.xform(VECTOR3_RIGHT), SPPI * ((i - 1) % STEP))));
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_UP * p_radius)).rotated(p_basis.xform(VECTOR3_RIGHT), SPPI * (i % STEP))));
}
for (int i = 1; i <= STEP; i++) {
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_RIGHT * p_radius)).rotated(p_basis.xform(VECTOR3_FORWARD), SPPI * ((i - 1) % STEP))));
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_RIGHT * p_radius)).rotated(p_basis.xform(VECTOR3_FORWARD), SPPI * (i % STEP))));
}
for (int i = 1; i <= STEP; i++) {
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_FORWARD * p_radius)).rotated(p_basis.xform(VECTOR3_UP), SPPI * ((i - 1) % STEP))));
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_FORWARD * p_radius)).rotated(p_basis.xform(VECTOR3_UP), SPPI * (i % STEP))));
}
}
void SpringBoneSimulator3DGizmoPlugin::draw_line(Ref<SurfaceTool> &p_surface_tool, const Vector3 &p_begin_pos, const Vector3 &p_end_pos, const Color &p_color) {
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex(p_begin_pos);
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex(p_end_pos);
}
// SpringBoneCollision3D
SpringBoneCollision3DGizmoPlugin::SelectionMaterials SpringBoneCollision3DGizmoPlugin::selection_materials;
SpringBoneCollision3DGizmoPlugin::SpringBoneCollision3DGizmoPlugin() {
selection_materials.unselected_mat.instantiate();
selection_materials.unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
selection_materials.unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
selection_materials.selected_mat.instantiate();
Ref<Shader> sh;
sh.instantiate();
sh->set_code(R"(
// Skeleton 3D gizmo bones shader.
shader_type spatial;
render_mode unshaded, shadows_disabled;
void vertex() {
if (!OUTPUT_IS_SRGB) {
COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) );
}
VERTEX = VERTEX;
POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0);
POSITION.z = mix(POSITION.z, POSITION.w, 0.998);
}
void fragment() {
ALBEDO = COLOR.rgb;
ALPHA = COLOR.a;
}
)");
selection_materials.selected_mat->set_shader(sh);
}
SpringBoneCollision3DGizmoPlugin::~SpringBoneCollision3DGizmoPlugin() {
selection_materials.unselected_mat.unref();
selection_materials.selected_mat.unref();
}
bool SpringBoneCollision3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<SpringBoneCollision3D>(p_spatial) != nullptr;
}
String SpringBoneCollision3DGizmoPlugin::get_gizmo_name() const {
return "SpringBoneCollision3D";
}
int SpringBoneCollision3DGizmoPlugin::get_priority() const {
return -1;
}
void SpringBoneCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
SpringBoneCollision3D *collision = Object::cast_to<SpringBoneCollision3D>(p_gizmo->get_node_3d());
p_gizmo->clear();
Ref<ArrayMesh> mesh = get_collision_mesh(collision, p_gizmo->is_selected());
p_gizmo->add_mesh(mesh);
}
Ref<ArrayMesh> SpringBoneCollision3DGizmoPlugin::get_collision_mesh(SpringBoneCollision3D *p_collision, bool p_is_selected) {
Color collision_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/spring_bone_collision");
Color inside_collision_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/spring_bone_inside_collision");
Ref<SurfaceTool> surface_tool;
surface_tool.instantiate();
surface_tool->begin(Mesh::PRIMITIVE_LINES);
if (p_is_selected) {
surface_tool->set_material(selection_materials.selected_mat);
} else {
selection_materials.unselected_mat->set_albedo(collision_color);
surface_tool->set_material(selection_materials.unselected_mat);
}
SpringBoneCollisionSphere3D *sphere = Object::cast_to<SpringBoneCollisionSphere3D>(p_collision);
if (sphere) {
draw_sphere(surface_tool, sphere->get_radius(), sphere->is_inside() ? inside_collision_color : collision_color);
return surface_tool->commit();
}
SpringBoneCollisionCapsule3D *capsule = Object::cast_to<SpringBoneCollisionCapsule3D>(p_collision);
if (capsule) {
draw_capsule(surface_tool, capsule->get_radius(), capsule->get_height(), capsule->is_inside() ? inside_collision_color : collision_color);
return surface_tool->commit();
}
SpringBoneCollisionPlane3D *plane = Object::cast_to<SpringBoneCollisionPlane3D>(p_collision);
if (plane) {
draw_plane(surface_tool, collision_color);
return surface_tool->commit();
}
return surface_tool->commit();
}
void SpringBoneCollision3DGizmoPlugin::draw_sphere(Ref<SurfaceTool> &p_surface_tool, float p_radius, const Color &p_color) {
static const Vector3 VECTOR3_RIGHT = Vector3(1, 0, 0);
static const Vector3 VECTOR3_UP = Vector3(0, 1, 0);
static const Vector3 VECTOR3_FORWARD = Vector3(0, 0, 1);
static const int STEP = 16;
static const float SPPI = Math_TAU / (float)STEP;
for (int i = 1; i <= STEP; i++) {
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex((VECTOR3_UP * p_radius).rotated(VECTOR3_RIGHT, SPPI * ((i - 1) % STEP)));
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex((VECTOR3_UP * p_radius).rotated(VECTOR3_RIGHT, SPPI * (i % STEP)));
}
for (int i = 1; i <= STEP; i++) {
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex((VECTOR3_RIGHT * p_radius).rotated(VECTOR3_FORWARD, SPPI * ((i - 1) % STEP)));
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex((VECTOR3_RIGHT * p_radius).rotated(VECTOR3_FORWARD, SPPI * (i % STEP)));
}
for (int i = 1; i <= STEP; i++) {
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex((VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * ((i - 1) % STEP)));
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex((VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * (i % STEP)));
}
}
void SpringBoneCollision3DGizmoPlugin::draw_capsule(Ref<SurfaceTool> &p_surface_tool, float p_radius, float p_height, const Color &p_color) {
static const Vector3 VECTOR3_RIGHT = Vector3(1, 0, 0);
static const Vector3 VECTOR3_UP = Vector3(0, 1, 0);
static const Vector3 VECTOR3_FORWARD = Vector3(0, 0, 1);
static const int STEP = 16;
static const int HALF_STEP = 8;
static const float SPPI = Math_TAU / (float)STEP;
static const float HALF_PI = Math_PI * 0.5;
Vector3 top = VECTOR3_UP * (p_height * 0.5 - p_radius);
Vector3 bottom = -top;
for (int i = 1; i <= STEP; i++) {
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex((i - 1 < HALF_STEP ? top : bottom) + (VECTOR3_UP * p_radius).rotated(VECTOR3_RIGHT, -HALF_PI + SPPI * ((i - 1) % STEP)));
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex((i - 1 < HALF_STEP ? top : bottom) + (VECTOR3_UP * p_radius).rotated(VECTOR3_RIGHT, -HALF_PI + SPPI * (i % STEP)));
}
for (int i = 1; i <= STEP; i++) {
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex((i - 1 < HALF_STEP ? top : bottom) + (VECTOR3_RIGHT * p_radius).rotated(VECTOR3_FORWARD, SPPI * ((i - 1) % STEP)));
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex((i - 1 < HALF_STEP ? top : bottom) + (VECTOR3_RIGHT * p_radius).rotated(VECTOR3_FORWARD, SPPI * (i % STEP)));
}
for (int i = 1; i <= STEP; i++) {
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex(top + (VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * ((i - 1) % STEP)));
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex(top + (VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * (i % STEP)));
}
for (int i = 1; i <= STEP; i++) {
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex(bottom + (VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * ((i - 1) % STEP)));
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex(bottom + (VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * (i % STEP)));
}
LocalVector<Vector3> directions;
directions.resize(4);
directions[0] = VECTOR3_RIGHT;
directions[1] = -VECTOR3_RIGHT;
directions[2] = VECTOR3_FORWARD;
directions[3] = -VECTOR3_FORWARD;
for (int i = 0; i < 4; i++) {
Vector3 dir = directions[i] * p_radius;
p_surface_tool->set_color(p_color);
p_surface_tool->add_vertex(top + dir);
p_surface_tool->add_vertex(bottom + dir);
}
}
void SpringBoneCollision3DGizmoPlugin::draw_plane(Ref<SurfaceTool> &p_surface_tool, const Color &p_color) {
static const Vector3 VECTOR3_UP = Vector3(0, 1, 0);
static const float HALF_PI = Math_PI * 0.5;
static const float ARROW_LENGTH = 0.3;
static const float ARROW_HALF_WIDTH = 0.05;
static const float ARROW_TOP_HALF_WIDTH = 0.1;
static const float ARROW_TOP = 0.5;
static const float RECT_SIZE = 1.0;
static const int RECT_STEP_COUNT = 9;
static const float RECT_HALF_SIZE = RECT_SIZE * 0.5;
static const float RECT_STEP = RECT_SIZE / (float)RECT_STEP_COUNT;
p_surface_tool->set_color(p_color);
// Draw arrow of the normal.
LocalVector<Vector3> arrow;
arrow.resize(7);
arrow[0] = Vector3(0, ARROW_TOP, 0);
arrow[1] = Vector3(-ARROW_TOP_HALF_WIDTH, ARROW_LENGTH, 0);
arrow[2] = Vector3(-ARROW_HALF_WIDTH, ARROW_LENGTH, 0);
arrow[3] = Vector3(-ARROW_HALF_WIDTH, 0, 0);
arrow[4] = Vector3(ARROW_HALF_WIDTH, 0, 0);
arrow[5] = Vector3(ARROW_HALF_WIDTH, ARROW_LENGTH, 0);
arrow[6] = Vector3(ARROW_TOP_HALF_WIDTH, ARROW_LENGTH, 0);
for (int i = 0; i < 2; i++) {
Basis ma(VECTOR3_UP, HALF_PI * i);
for (uint32_t j = 0; j < arrow.size(); j++) {
Vector3 v1 = arrow[j];
Vector3 v2 = arrow[(j + 1) % arrow.size()];
p_surface_tool->add_vertex(ma.xform(v1));
p_surface_tool->add_vertex(ma.xform(v2));
}
}
// Draw dashed line of the rect.
for (int i = 0; i < 4; i++) {
Basis ma(VECTOR3_UP, HALF_PI * i);
for (int j = 0; j < RECT_STEP_COUNT; j++) {
if (j % 2 == 1) {
continue;
}
Vector3 v1 = Vector3(RECT_HALF_SIZE, 0, RECT_HALF_SIZE - RECT_STEP * j);
Vector3 v2 = Vector3(RECT_HALF_SIZE, 0, RECT_HALF_SIZE - RECT_STEP * (j + 1));
p_surface_tool->add_vertex(ma.xform(v1));
p_surface_tool->add_vertex(ma.xform(v2));
}
}
}

View file

@ -0,0 +1,89 @@
/**************************************************************************/
/* spring_bone_3d_gizmo_plugin.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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. */
/**************************************************************************/
#ifndef SPRING_BONE_3D_GIZMO_PLUGIN_H
#define SPRING_BONE_3D_GIZMO_PLUGIN_H
#include "editor/plugins/editor_plugin.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/spring_bone_collision_3d.h"
#include "scene/3d/spring_bone_simulator_3d.h"
#include "scene/resources/surface_tool.h"
class SpringBoneSimulator3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(SpringBoneSimulator3DGizmoPlugin, EditorNode3DGizmoPlugin);
struct SelectionMaterials {
Ref<StandardMaterial3D> unselected_mat;
Ref<ShaderMaterial> selected_mat;
};
static SelectionMaterials selection_materials;
public:
static Ref<ArrayMesh> get_joints_mesh(Skeleton3D *p_skeleton, SpringBoneSimulator3D *p_simulator, bool p_is_selected);
static void draw_sphere(Ref<SurfaceTool> &p_surface_tool, const Basis &p_basis, const Vector3 &p_center, float p_radius, const Color &p_color);
static void draw_line(Ref<SurfaceTool> &p_surface_tool, const Vector3 &p_begin_pos, const Vector3 &p_end_pos, const Color &p_color);
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const override;
void redraw(EditorNode3DGizmo *p_gizmo) override;
SpringBoneSimulator3DGizmoPlugin();
~SpringBoneSimulator3DGizmoPlugin();
};
class SpringBoneCollision3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(SpringBoneCollision3DGizmoPlugin, EditorNode3DGizmoPlugin);
struct SelectionMaterials {
Ref<StandardMaterial3D> unselected_mat;
Ref<ShaderMaterial> selected_mat;
};
static SelectionMaterials selection_materials;
public:
static Ref<ArrayMesh> get_collision_mesh(SpringBoneCollision3D *p_collision, bool p_is_selected);
static void draw_sphere(Ref<SurfaceTool> &p_surface_tool, float p_radius, const Color &p_color);
static void draw_capsule(Ref<SurfaceTool> &p_surface_tool, float p_radius, float p_height, const Color &p_color);
static void draw_plane(Ref<SurfaceTool> &p_surface_tool, const Color &p_color);
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const override;
void redraw(EditorNode3DGizmo *p_gizmo) override;
SpringBoneCollision3DGizmoPlugin();
~SpringBoneCollision3DGizmoPlugin();
};
#endif // SPRING_BONE_3D_GIZMO_PLUGIN_H

View file

@ -30,7 +30,6 @@
#include "sprite_base_3d_gizmo_plugin.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/sprite_3d.h"
SpriteBase3DGizmoPlugin::SpriteBase3DGizmoPlugin() {

View file

@ -30,8 +30,6 @@
#include "vehicle_body_3d_gizmo_plugin.h"
#include "editor/editor_settings.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/physics/vehicle_body_3d.h"
VehicleWheel3DGizmoPlugin::VehicleWheel3DGizmoPlugin() {

Some files were not shown because too many files have changed in this diff Show more