initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

9
editor/scene/2d/SCsub Normal file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.editor_sources, "*.cpp")
SConscript("physics/SCsub")
SConscript("tiles/SCsub")

View File

@@ -0,0 +1,953 @@
/**************************************************************************/
/* abstract_polygon_2d_editor.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 "abstract_polygon_2d_editor.h"
#include "core/math/geometry_2d.h"
#include "core/os/keyboard.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/scene/canvas_item_editor_plugin.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/button.h"
#include "scene/gui/dialogs.h"
bool AbstractPolygon2DEditor::Vertex::operator==(const AbstractPolygon2DEditor::Vertex &p_vertex) const {
return polygon == p_vertex.polygon && vertex == p_vertex.vertex;
}
bool AbstractPolygon2DEditor::Vertex::operator!=(const AbstractPolygon2DEditor::Vertex &p_vertex) const {
return !(*this == p_vertex);
}
bool AbstractPolygon2DEditor::Vertex::valid() const {
return vertex >= 0;
}
bool AbstractPolygon2DEditor::_is_empty() const {
if (!_get_node()) {
return true;
}
const int n = _get_polygon_count();
for (int i = 0; i < n; i++) {
Vector<Vector2> vertices = _get_polygon(i);
if (vertices.size() != 0) {
return false;
}
}
return true;
}
bool AbstractPolygon2DEditor::_is_line() const {
return false;
}
bool AbstractPolygon2DEditor::_has_uv() const {
return false;
}
int AbstractPolygon2DEditor::_get_polygon_count() const {
return 1;
}
Variant AbstractPolygon2DEditor::_get_polygon(int p_idx) const {
return _get_node()->get("polygon");
}
void AbstractPolygon2DEditor::_set_polygon(int p_idx, const Variant &p_polygon) const {
_get_node()->set("polygon", p_polygon);
}
void AbstractPolygon2DEditor::_action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) {
Node2D *node = _get_node();
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->add_do_method(node, "set_polygon", p_polygon);
undo_redo->add_undo_method(node, "set_polygon", p_previous);
}
Vector2 AbstractPolygon2DEditor::_get_offset(int p_idx) const {
return Vector2(0, 0);
}
void AbstractPolygon2DEditor::_commit_action() {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
undo_redo->commit_action();
}
void AbstractPolygon2DEditor::_action_add_polygon(const Variant &p_polygon) {
_action_set_polygon(0, p_polygon);
}
void AbstractPolygon2DEditor::_action_remove_polygon(int p_idx) {
_action_set_polygon(p_idx, _get_polygon(p_idx), Vector<Vector2>());
}
void AbstractPolygon2DEditor::_action_set_polygon(int p_idx, const Variant &p_polygon) {
_action_set_polygon(p_idx, _get_polygon(p_idx), p_polygon);
}
bool AbstractPolygon2DEditor::_has_resource() const {
return true;
}
void AbstractPolygon2DEditor::_create_resource() {
}
Vector2 AbstractPolygon2DEditor::_get_geometric_center() const {
int n_polygons = _get_polygon_count();
double cx = 0.0;
double cy = 0.0;
int n_subs = 0;
for (int i = 0; i < n_polygons; i++) {
const Vector<Vector2> &vertices = _get_polygon(i);
Vector<Vector<Point2>> decomp = ::Geometry2D::decompose_polygon_in_convex(vertices);
if (decomp.is_empty()) {
continue;
}
for (const Vector<Vector2> &sub : decomp) {
int sub_n_points = sub.size();
double sub_area2x = 0.0;
double sub_cx = 0.0;
double sub_cy = 0.0;
for (int n = 0; n < sub_n_points; n++) {
int next = (n + 1 < sub_n_points) ? n + 1 : 0;
sub_area2x += (sub[n].x * sub[next].y) - (sub[next].x * sub[n].y);
sub_cx += (sub[n].x + sub[next].x) * (sub[n].x * sub[next].y - sub[next].x * sub[n].y);
sub_cy += (sub[n].y + sub[next].y) * (sub[n].x * sub[next].y - sub[next].x * sub[n].y);
}
sub_cx /= (sub_area2x * 3);
sub_cy /= (sub_area2x * 3);
cx += sub_cx;
cy += sub_cy;
}
n_subs += decomp.size();
}
cx /= n_subs;
cy /= n_subs;
return Vector2(cx, cy);
}
void AbstractPolygon2DEditor::_menu_option(int p_option) {
switch (p_option) {
case MODE_CREATE: {
mode = MODE_CREATE;
button_create->set_pressed(true);
button_edit->set_pressed(false);
button_delete->set_pressed(false);
} break;
case MODE_EDIT: {
_wip_close();
mode = MODE_EDIT;
button_create->set_pressed(false);
button_edit->set_pressed(true);
button_delete->set_pressed(false);
} break;
case MODE_DELETE: {
_wip_close();
mode = MODE_DELETE;
button_create->set_pressed(false);
button_edit->set_pressed(false);
button_delete->set_pressed(true);
} break;
case CENTER_POLY: {
_wip_close();
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Move Origin to Geometric Center"));
Vector2 center = _get_geometric_center();
int n_polygons = _get_polygon_count();
for (int i = 0; i < n_polygons; i++) {
const Vector<Vector2> &vertices = _get_polygon(i);
int n_points = vertices.size();
Vector<Vector2> new_vertices;
new_vertices.resize(n_points);
for (int n = 0; n < n_points; n++) {
new_vertices.write[n] = vertices[n] - center;
}
_action_set_polygon(i, vertices, new_vertices);
}
Node2D *node = _get_node();
Vector2 node_pos = node->get_position();
undo_redo->add_do_method(node, "set_position", node_pos + node->get_transform().basis_xform(center));
undo_redo->add_undo_method(node, "set_position", node_pos);
_commit_action();
} break;
}
}
void AbstractPolygon2DEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
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")));
button_center->set_button_icon(get_editor_theme_icon(SNAME("CurveCenter")));
} break;
case NOTIFICATION_READY: {
disable_polygon_editing(false, String());
button_edit->set_pressed(true);
get_tree()->connect("node_removed", callable_mp(this, &AbstractPolygon2DEditor::_node_removed));
create_resource->connect(SceneStringName(confirmed), callable_mp(this, &AbstractPolygon2DEditor::_create_resource));
} break;
}
}
void AbstractPolygon2DEditor::_node_removed(Node *p_node) {
if (p_node == _get_node()) {
edit(nullptr);
hide();
canvas_item_editor->update_viewport();
}
}
void AbstractPolygon2DEditor::_wip_changed() {
if (wip_active && _is_line()) {
_set_polygon(0, wip);
}
}
void AbstractPolygon2DEditor::_wip_cancel() {
wip.clear();
wip_active = false;
edited_point = PosVertex();
hover_point = Vertex();
selected_point = Vertex();
center_drag = false;
canvas_item_editor->update_viewport();
}
void AbstractPolygon2DEditor::_wip_close() {
if (!wip_active) {
return;
}
if (_is_line()) {
_set_polygon(0, wip);
} else if (wip.size() >= (_is_line() ? 2 : 3)) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Create Polygon"));
_action_add_polygon(wip);
if (_has_uv()) {
undo_redo->add_do_method(_get_node(), "set_uv", Vector<Vector2>());
undo_redo->add_undo_method(_get_node(), "set_uv", _get_node()->get("uv"));
}
_commit_action();
} else {
return;
}
mode = MODE_EDIT;
button_edit->set_pressed(true);
button_create->set_pressed(false);
button_delete->set_pressed(false);
wip.clear();
wip_active = false;
edited_point = PosVertex();
hover_point = Vertex();
selected_point = Vertex();
center_drag = false;
}
void AbstractPolygon2DEditor::disable_polygon_editing(bool p_disable, const String &p_reason) {
_polygon_editing_enabled = !p_disable;
button_create->set_disabled(p_disable);
button_edit->set_disabled(p_disable);
button_delete->set_disabled(p_disable);
button_center->set_disabled(p_disable);
if (p_disable) {
button_create->set_tooltip_text(p_reason);
button_edit->set_tooltip_text(p_reason);
button_delete->set_tooltip_text(p_reason);
button_center->set_tooltip_text(p_reason);
} else {
button_create->set_tooltip_text(TTRC("Create points."));
button_edit->set_tooltip_text(TTRC("Edit points.\nLMB: Move Point\nRMB: Erase Point"));
button_delete->set_tooltip_text(TTRC("Erase points."));
button_center->set_tooltip_text(TTRC("Move center of gravity to geometric center."));
}
}
bool AbstractPolygon2DEditor::_commit_drag() {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
center_drag = false;
int n_polygons = _get_polygon_count();
ERR_FAIL_COND_V(pre_center_move_edit.size() != n_polygons, false);
undo_redo->create_action(TTR("Move Geometric Center"));
for (int i = 0; i < n_polygons; i++) {
_action_set_polygon(i, pre_center_move_edit[i], _get_polygon(i));
}
pre_center_move_edit.clear();
_commit_action();
return true;
}
bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
if (!_get_node() || !_polygon_editing_enabled) {
return false;
}
if (!_get_node()->is_visible_in_tree()) {
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;
if (!_has_resource()) {
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
create_resource->set_text(String("No polygon resource on this node.\nCreate and assign one?"));
create_resource->popup_centered();
}
return (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT);
}
CanvasItemEditor::Tool tool = CanvasItemEditor::get_singleton()->get_current_tool();
if (tool != CanvasItemEditor::TOOL_SELECT) {
return false;
}
if (mb.is_valid()) {
Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_screen_transform();
Vector2 gpoint = 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) {
if (mb->is_pressed()) {
if (mb->is_meta_pressed() || mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) {
return false;
}
const PosVertex closest = closest_point(gpoint);
if (closest.valid()) {
original_mouse_pos = gpoint;
pre_move_edit = _get_polygon(closest.polygon);
edited_point = PosVertex(closest, xform.affine_inverse().xform(closest.pos));
selected_point = closest;
edge_point = PosVertex();
canvas_item_editor->update_viewport();
return true;
} else {
selected_point = Vertex();
const PosVertex insert = closest_edge_point(gpoint);
if (insert.valid()) {
Vector<Vector2> vertices = _get_polygon(insert.polygon);
if (vertices.size() < (_is_line() ? 2 : 3)) {
vertices.push_back(cpoint);
undo_redo->create_action(TTR("Edit Polygon"));
selected_point = Vertex(insert.polygon, vertices.size());
_action_set_polygon(insert.polygon, vertices);
_commit_action();
return true;
} else {
edited_point = PosVertex(insert.polygon, insert.vertex + 1, xform.affine_inverse().xform(insert.pos));
vertices.insert(edited_point.vertex, edited_point.pos);
pre_move_edit = vertices;
selected_point = Vertex(edited_point.polygon, edited_point.vertex);
edge_point = PosVertex();
undo_redo->create_action(TTR("Insert Point"));
_action_set_polygon(insert.polygon, vertices);
_commit_action();
return true;
}
}
}
} else {
if (edited_point.valid()) {
if (original_mouse_pos != gpoint) {
Vector<Vector2> vertices = _get_polygon(edited_point.polygon);
ERR_FAIL_INDEX_V(edited_point.vertex, vertices.size(), false);
vertices.write[edited_point.vertex] = edited_point.pos - _get_offset(edited_point.polygon);
undo_redo->create_action(TTR("Edit Polygon"));
_action_set_polygon(edited_point.polygon, pre_move_edit, vertices);
_commit_action();
}
edited_point = PosVertex();
return true;
}
}
} else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed() && !edited_point.valid()) {
const PosVertex closest = closest_point(gpoint);
if (closest.valid()) {
remove_point(closest);
return true;
}
}
} else if (mode == MODE_DELETE) {
if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
const PosVertex closest = closest_point(gpoint);
if (closest.valid()) {
remove_point(closest);
return true;
}
}
}
if (mode == MODE_CREATE) {
if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
if (_is_line()) {
// for lines, we don't have a wip mode, and we can undo each single add point.
Vector<Vector2> vertices = _get_polygon(0);
vertices.push_back(cpoint);
undo_redo->create_action(TTR("Insert Point"));
_action_set_polygon(0, vertices);
_commit_action();
return true;
} else if (!wip_active) {
wip.clear();
wip.push_back(cpoint);
wip_active = true;
_wip_changed();
edited_point = PosVertex(-1, 1, cpoint);
canvas_item_editor->update_viewport();
hover_point = Vertex();
selected_point = Vertex(0);
edge_point = PosVertex();
return true;
} else {
const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
if (!_is_line() && wip.size() > 1 && xform.xform(wip[0]).distance_to(xform.xform(cpoint)) < grab_threshold) {
//wip closed
_wip_close();
return true;
} else {
//add wip point
wip.push_back(cpoint);
_wip_changed();
edited_point = PosVertex(-1, wip.size(), cpoint);
selected_point = Vertex(wip.size() - 1);
canvas_item_editor->update_viewport();
return true;
}
}
} else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed() && wip_active) {
_wip_cancel();
}
}
// Center drag.
if (edit_origin_and_center) {
real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_meta_pressed() || mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) {
return false;
}
if (mb->is_pressed() && !center_drag) {
Vector2 center_point = xform.xform(_get_geometric_center());
if ((gpoint - center_point).length() < grab_threshold) {
pre_center_move_edit.clear();
int n_polygons = _get_polygon_count();
for (int i = 0; i < n_polygons; i++) {
pre_center_move_edit.push_back(_get_polygon(i));
}
center_drag_origin = cpoint;
center_drag = true;
return true;
}
} else if (center_drag) {
return _commit_drag();
}
} else if (mb->get_button_index() == MouseButton::RIGHT && center_drag) {
_commit_drag();
}
}
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
Vector2 gpoint = mm->get_position();
if (center_drag) {
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);
Vector2 delta = center_drag_origin - cpoint;
int n_polygons = _get_polygon_count();
for (int i = 0; i < n_polygons; i++) {
const Vector<Vector2> &vertices = _get_polygon(i);
int n_points = vertices.size();
Vector<Vector2> new_vertices;
new_vertices.resize(n_points);
for (int n = 0; n < n_points; n++) {
new_vertices.write[n] = vertices[n] - delta;
}
_set_polygon(i, new_vertices);
}
center_drag_origin = cpoint;
} else if (edited_point.valid() && (wip_active || mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
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()) {
Vector2 old_point = pre_move_edit.get(selected_point.vertex);
if (Math::abs(cpoint.x - old_point.x) > Math::abs(cpoint.y - old_point.y)) {
cpoint.y = old_point.y;
} else {
cpoint.x = old_point.x;
}
}
edited_point = PosVertex(edited_point, cpoint);
if (!wip_active) {
Vector<Vector2> vertices = _get_polygon(edited_point.polygon);
ERR_FAIL_INDEX_V(edited_point.vertex, vertices.size(), false);
vertices.write[edited_point.vertex] = cpoint - _get_offset(edited_point.polygon);
_set_polygon(edited_point.polygon, vertices);
}
canvas_item_editor->update_viewport();
} else if (mode == MODE_EDIT || (_is_line() && mode == MODE_CREATE)) {
const PosVertex new_hover_point = closest_point(gpoint);
if (hover_point != new_hover_point) {
hover_point = new_hover_point;
canvas_item_editor->update_viewport();
}
bool edge_hover = false;
if (!hover_point.valid()) {
const PosVertex on_edge_vertex = closest_edge_point(gpoint);
if (on_edge_vertex.valid()) {
hover_point = Vertex();
edge_point = on_edge_vertex;
canvas_item_editor->update_viewport();
edge_hover = true;
}
}
if (!edge_hover && edge_point.valid()) {
edge_point = PosVertex();
canvas_item_editor->update_viewport();
}
}
}
Ref<InputEventKey> k = p_event;
if (k.is_valid() && k->is_pressed()) {
if (k->get_keycode() == Key::KEY_DELETE || k->get_keycode() == Key::BACKSPACE) {
if (wip_active && selected_point.polygon == -1) {
if (wip.size() > selected_point.vertex) {
wip.remove_at(selected_point.vertex);
_wip_changed();
selected_point = wip.size() - 1;
canvas_item_editor->update_viewport();
return true;
}
} else {
const Vertex active_point = get_active_point();
if (active_point.valid()) {
remove_point(active_point);
return true;
}
}
} else if (wip_active && k->get_keycode() == Key::ENTER) {
_wip_close();
} else if (wip_active && k->get_keycode() == Key::ESCAPE) {
_wip_cancel();
}
}
return false;
}
void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
if (!_get_node()) {
return;
}
if (!_get_node()->is_visible_in_tree()) {
return;
}
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"));
const Ref<Texture2D> nhandle = get_editor_theme_icon(SNAME("EditorPathNullHandle"));
Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
const float outline_size = 4 * EDSCALE;
Color font_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
Color outline_color = font_color.inverted();
const Vertex active_point = get_active_point();
const int n_polygons = _get_polygon_count();
const bool is_closed = !_is_line();
if (edit_origin_and_center) {
const Vector2 &center = _get_geometric_center();
if (!center.is_zero_approx()) {
const Vector2 point = xform.xform(center);
p_overlay->draw_texture(nhandle, point - nhandle->get_size() * 0.5, Color(1, 1, 0.4));
Size2 lbl_size = font->get_string_size("c", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
p_overlay->draw_string_outline(font, point - lbl_size * 0.5, "c", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
p_overlay->draw_string(font, point - lbl_size * 0.5, "c", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
}
{
const Vector2 point = xform.xform(Vector2());
p_overlay->draw_texture(nhandle, point - nhandle->get_size() * 0.5, center.is_equal_approx(Vector2()) ? Color(1, 1, 0.4) : Color(1, 0.4, 1));
Size2 lbl_size = font->get_string_size("o", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
p_overlay->draw_string_outline(font, point - lbl_size * 0.5, "o", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
p_overlay->draw_string(font, point - lbl_size * 0.5, "o", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
}
}
for (int j = -1; j < n_polygons; j++) {
if (wip_active && wip_destructive && j != -1) {
continue;
}
Vector<Vector2> points;
Vector2 offset;
if (wip_active && j == edited_point.polygon) {
points = Variant(wip);
offset = Vector2(0, 0);
} else {
if (j == -1) {
continue;
}
points = _get_polygon(j);
offset = _get_offset(j);
}
if (!wip_active && j == edited_point.polygon && EDITOR_GET("editors/polygon_editor/show_previous_outline")) {
const Color col = Color(0.5, 0.5, 0.5); // FIXME polygon->get_outline_color();
const int n = pre_move_edit.size();
for (int i = 0; i < n - (is_closed ? 0 : 1); i++) {
Vector2 p, p2;
p = pre_move_edit[i] + offset;
p2 = pre_move_edit[(i + 1) % n] + offset;
Vector2 point = xform.xform(p);
Vector2 next_point = xform.xform(p2);
p_overlay->draw_line(point, next_point, col, Math::round(2 * EDSCALE));
}
}
const int n_points = points.size();
const Color col = Color(1, 0.3, 0.1, 0.8);
for (int i = 0; i < n_points; i++) {
const Vertex vertex(j, i);
const Vector2 p = (vertex == edited_point) ? edited_point.pos : (points[i] + offset);
const Vector2 point = xform.xform(p);
if (is_closed || i < n_points - 1) {
Vector2 p2;
if (j == edited_point.polygon &&
((wip_active && i == n_points - 1) || (((i + 1) % n_points) == edited_point.vertex))) {
p2 = edited_point.pos;
} else {
p2 = points[(i + 1) % n_points] + offset;
}
const Vector2 next_point = xform.xform(p2);
p_overlay->draw_line(point, next_point, col, Math::round(2 * EDSCALE));
}
}
for (int i = 0; i < n_points; i++) {
const Vertex vertex(j, i);
const Vector2 p = (vertex == edited_point) ? edited_point.pos : (points[i] + offset);
const Vector2 point = xform.xform(p);
const Color overlay_modulate = vertex == active_point ? Color(0.4, 1, 1) : Color(1, 1, 1);
p_overlay->draw_texture(handle, point - handle->get_size() * 0.5, overlay_modulate);
if (vertex == hover_point) {
String num = String::num_int64(vertex.vertex);
Size2 num_size = font->get_string_size(num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
p_overlay->draw_string_outline(font, point - num_size * 0.5, num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
p_overlay->draw_string(font, point - num_size * 0.5, num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
}
}
}
if (edge_point.valid()) {
Ref<Texture2D> add_handle = get_editor_theme_icon(SNAME("EditorHandleAdd"));
p_overlay->draw_texture(add_handle, edge_point.pos - add_handle->get_size() * 0.5);
}
}
void AbstractPolygon2DEditor::set_edit_origin_and_center(bool p_enabled) {
edit_origin_and_center = p_enabled;
if (button_center) {
button_center->set_visible(edit_origin_and_center);
}
}
void AbstractPolygon2DEditor::edit(Node *p_polygon) {
if (!canvas_item_editor) {
canvas_item_editor = CanvasItemEditor::get_singleton();
}
if (p_polygon) {
_set_node(p_polygon);
// Enable the pencil tool if the polygon is empty.
if (_is_empty()) {
_menu_option(MODE_CREATE);
} else {
_menu_option(MODE_EDIT);
}
wip.clear();
wip_active = false;
edited_point = PosVertex();
hover_point = Vertex();
selected_point = Vertex();
center_drag = false;
} else {
_set_node(nullptr);
}
canvas_item_editor->update_viewport();
}
void AbstractPolygon2DEditor::remove_point(const Vertex &p_vertex) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
Vector<Vector2> vertices = _get_polygon(p_vertex.polygon);
if (vertices.size() > (_is_line() ? 2 : 3)) {
vertices.remove_at(p_vertex.vertex);
undo_redo->create_action(TTR("Edit Polygon (Remove Point)"));
_action_set_polygon(p_vertex.polygon, vertices);
_commit_action();
} else {
undo_redo->create_action(TTR("Remove Polygon And Point"));
_action_remove_polygon(p_vertex.polygon);
_commit_action();
}
if (_is_empty()) {
_menu_option(MODE_CREATE);
}
hover_point = Vertex();
if (selected_point == p_vertex) {
selected_point = Vertex();
}
}
AbstractPolygon2DEditor::Vertex AbstractPolygon2DEditor::get_active_point() const {
return hover_point.valid() ? hover_point : selected_point;
}
AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_point(const Vector2 &p_pos) 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_screen_transform();
PosVertex closest;
real_t closest_dist = 1e10;
for (int j = 0; j < n_polygons; j++) {
Vector<Vector2> points = _get_polygon(j);
const Vector2 offset = _get_offset(j);
const int n_points = points.size();
for (int i = 0; i < n_points; i++) {
Vector2 cp = xform.xform(points[i] + offset);
real_t d = cp.distance_to(p_pos);
if (d < closest_dist && d < grab_threshold) {
closest_dist = d;
closest = PosVertex(j, i, cp);
}
}
}
return closest;
}
AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_edge_point(const Vector2 &p_pos) const {
const real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
const real_t eps = grab_threshold * 2;
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_screen_transform();
PosVertex closest;
real_t closest_dist = 1e10;
for (int j = 0; j < n_polygons; j++) {
Vector<Vector2> points = _get_polygon(j);
const Vector2 offset = _get_offset(j);
const int n_points = points.size();
const int n_segments = n_points - (_is_line() ? 1 : 0);
for (int i = 0; i < n_segments; i++) {
const Vector2 segment_a = xform.xform(points[i] + offset);
const Vector2 segment_b = xform.xform(points[(i + 1) % n_points] + offset);
Vector2 cp = Geometry2D::get_closest_point_to_segment(p_pos, segment_a, segment_b);
if (cp.distance_squared_to(segment_a) < eps2 || cp.distance_squared_to(segment_b) < eps2) {
continue; //not valid to reuse point
}
real_t d = cp.distance_to(p_pos);
if (d < closest_dist && d < grab_threshold) {
closest_dist = d;
closest = PosVertex(j, i, cp);
}
}
}
return closest;
}
AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) {
edited_point = PosVertex();
center_drag = false;
wip_destructive = p_wip_destructive;
hover_point = Vertex();
selected_point = Vertex();
edge_point = PosVertex();
button_create = memnew(Button);
button_create->set_theme_type_variation(SceneStringName(FlatButton));
button_create->set_accessibility_name(TTRC("Create Polygon Points"));
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(SceneStringName(FlatButton));
button_edit->set_accessibility_name(TTRC("Edit Polygon Points"));
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(SceneStringName(FlatButton));
button_delete->set_accessibility_name(TTRC("Delete Polygon Points"));
add_child(button_delete);
button_delete->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_DELETE));
button_delete->set_toggle_mode(true);
button_center = memnew(Button);
button_center->set_theme_type_variation(SceneStringName(FlatButton));
add_child(button_center);
button_center->connect(SceneStringName(pressed), callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(CENTER_POLY));
button_center->set_visible(edit_origin_and_center);
create_resource = memnew(ConfirmationDialog);
add_child(create_resource);
create_resource->set_ok_button_text(TTR("Create"));
}
void AbstractPolygon2DEditorPlugin::edit(Object *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 {
return p_object->is_class(klass);
}
void AbstractPolygon2DEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
polygon_editor->show();
} else {
polygon_editor->hide();
polygon_editor->edit(nullptr);
}
}
AbstractPolygon2DEditorPlugin::AbstractPolygon2DEditorPlugin(AbstractPolygon2DEditor *p_polygon_editor, const String &p_class) :
polygon_editor(p_polygon_editor),
klass(p_class) {
CanvasItemEditor::get_singleton()->add_control_to_menu_panel(polygon_editor);
polygon_editor->hide();
}

View File

@@ -0,0 +1,177 @@
/**************************************************************************/
/* abstract_polygon_2d_editor.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. */
/**************************************************************************/
#pragma once
#include "editor/plugins/editor_plugin.h"
#include "scene/2d/node_2d.h"
#include "scene/gui/box_container.h"
class Button;
class CanvasItemEditor;
class ConfirmationDialog;
class AbstractPolygon2DEditor : public HBoxContainer {
GDCLASS(AbstractPolygon2DEditor, HBoxContainer);
Button *button_create = nullptr;
Button *button_edit = nullptr;
Button *button_delete = nullptr;
Button *button_center = nullptr;
struct Vertex {
Vertex() {}
Vertex(int p_vertex) :
vertex(p_vertex) {}
Vertex(int p_polygon, int p_vertex) :
polygon(p_polygon),
vertex(p_vertex) {}
bool operator==(const Vertex &p_vertex) const;
bool operator!=(const Vertex &p_vertex) const;
bool valid() const;
int polygon = -1;
int vertex = -1;
};
struct PosVertex : public Vertex {
PosVertex() {}
PosVertex(const Vertex &p_vertex, const Vector2 &p_pos) :
Vertex(p_vertex.polygon, p_vertex.vertex),
pos(p_pos) {}
PosVertex(int p_polygon, int p_vertex, const Vector2 &p_pos) :
Vertex(p_polygon, p_vertex),
pos(p_pos) {}
Vector2 pos;
};
PosVertex edited_point;
Vertex hover_point; // point under mouse cursor
Vertex selected_point; // currently selected
PosVertex edge_point; // adding an edge point?
Vector2 original_mouse_pos;
Vector<Vector2> pre_move_edit;
Vector<Vector2> wip;
bool wip_active = false;
bool wip_destructive = false;
Vector<Vector<Vector2>> pre_center_move_edit;
bool center_drag = false;
Vector2 center_drag_origin;
bool edit_origin_and_center = false;
bool _polygon_editing_enabled = false;
CanvasItemEditor *canvas_item_editor = nullptr;
Panel *panel = nullptr;
ConfirmationDialog *create_resource = nullptr;
protected:
enum {
MODE_CREATE,
MODE_EDIT,
MODE_DELETE,
MODE_CONT,
CENTER_POLY,
};
int mode = MODE_EDIT;
virtual void _menu_option(int p_option);
void _wip_changed();
void _wip_close();
void _wip_cancel();
void _notification(int p_what);
void _node_removed(Node *p_node);
bool _commit_drag();
void remove_point(const Vertex &p_vertex);
Vertex get_active_point() const;
PosVertex closest_point(const Vector2 &p_pos) const;
PosVertex closest_edge_point(const Vector2 &p_pos) const;
bool _is_empty() const;
virtual Node2D *_get_node() const = 0;
virtual void _set_node(Node *p_polygon) = 0;
virtual bool _is_line() const;
virtual bool _has_uv() const;
virtual int _get_polygon_count() const;
virtual Vector2 _get_offset(int p_idx) const;
virtual Variant _get_polygon(int p_idx) const;
virtual void _set_polygon(int p_idx, const Variant &p_polygon) const;
virtual void _action_add_polygon(const Variant &p_polygon);
virtual void _action_remove_polygon(int p_idx);
virtual void _action_set_polygon(int p_idx, const Variant &p_polygon);
virtual void _action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon);
virtual void _commit_action();
virtual Vector2 _get_geometric_center() const;
virtual bool _has_resource() const;
virtual void _create_resource();
public:
void disable_polygon_editing(bool p_disable, const String &p_reason);
bool forward_gui_input(const Ref<InputEvent> &p_event);
void forward_canvas_draw_over_viewport(Control *p_overlay);
void set_edit_origin_and_center(bool p_enabled);
void edit(Node *p_polygon);
AbstractPolygon2DEditor(bool p_wip_destructive = true);
};
class AbstractPolygon2DEditorPlugin : public EditorPlugin {
GDCLASS(AbstractPolygon2DEditorPlugin, EditorPlugin);
AbstractPolygon2DEditor *polygon_editor = nullptr;
String klass;
public:
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return polygon_editor->forward_gui_input(p_event); }
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_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;
AbstractPolygon2DEditorPlugin(AbstractPolygon2DEditor *p_polygon_editor, const String &p_class);
};

View File

@@ -0,0 +1,333 @@
/**************************************************************************/
/* camera_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 "camera_2d_editor_plugin.h"
#include "core/config/project_settings.h"
#include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/scene/canvas_item_editor_plugin.h"
#include "editor/themes/editor_scale.h"
#include "scene/2d/camera_2d.h"
#include "scene/gui/label.h"
#include "scene/gui/menu_button.h"
void Camera2DEditor::edit(Camera2D *p_camera) {
if (p_camera == selected_camera) {
return;
}
const Callable update_overlays = callable_mp(plugin, &EditorPlugin::update_overlays);
if (selected_camera) {
selected_camera->disconnect(SceneStringName(draw), update_overlays);
if (drag_type != Drag::NONE) {
selected_camera->set_limit_rect(drag_revert);
}
drag_type = Drag::NONE;
hover_type = Drag::NONE;
CanvasItemEditor::get_singleton()->set_cursor_shape_override(CURSOR_ARROW);
}
selected_camera = p_camera;
if (selected_camera) {
selected_camera->connect(SceneStringName(draw), update_overlays);
}
plugin->update_overlays();
}
bool Camera2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
if (!selected_camera || !selected_camera->is_limit_enabled()) {
return false;
}
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
if (hover_type != Drag::NONE) {
Vector2 pos = CanvasItemEditor::get_singleton()->get_canvas_transform().affine_inverse().xform(mb->get_position());
const Rect2 limit_rect = selected_camera->get_limit_rect();
drag_type = hover_type;
drag_revert = selected_camera->get_limit_rect();
center_drag_point = pos - limit_rect.position;
return true;
}
} else if (drag_type != Drag::NONE) {
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Edit Camera2D Limits"));
ur->add_do_method(selected_camera, "_set_limit_rect", selected_camera->get_limit_rect());
ur->add_do_method(this, "_update_overlays_if_needed", selected_camera);
ur->add_undo_method(selected_camera, "_set_limit_rect", drag_revert);
ur->add_undo_method(this, "_update_overlays_if_needed", selected_camera);
ur->commit_action(false);
drag_type = Drag::NONE;
return true;
}
} else if (drag_type != Drag::NONE && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
selected_camera->set_limit_rect(drag_revert);
drag_type = Drag::NONE;
plugin->update_overlays();
_update_hover(mb->get_position());
return true;
}
return false;
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
Vector2 pos = mm->get_position();
if (drag_type == Drag::NONE) {
_update_hover(pos);
return false;
}
pos = CanvasItemEditor::get_singleton()->get_canvas_transform().affine_inverse().xform(pos);
pos = CanvasItemEditor::get_singleton()->snap_point(pos);
switch (drag_type) {
case Drag::LEFT: {
selected_camera->set_limit(SIDE_LEFT, MIN(selected_camera->get_limit(SIDE_RIGHT), pos.x));
plugin->update_overlays();
} break;
case Drag::RIGHT: {
selected_camera->set_limit(SIDE_RIGHT, MAX(selected_camera->get_limit(SIDE_LEFT), pos.x));
plugin->update_overlays();
} break;
case Drag::TOP: {
selected_camera->set_limit(SIDE_TOP, MIN(selected_camera->get_limit(SIDE_BOTTOM), pos.y));
plugin->update_overlays();
} break;
case Drag::BOTTOM: {
selected_camera->set_limit(SIDE_BOTTOM, MAX(selected_camera->get_limit(SIDE_TOP), pos.y));
plugin->update_overlays();
} break;
case Drag::TOP_LEFT: {
selected_camera->set_limit(SIDE_LEFT, MIN(selected_camera->get_limit(SIDE_RIGHT), pos.x));
selected_camera->set_limit(SIDE_TOP, MIN(selected_camera->get_limit(SIDE_BOTTOM), pos.y));
plugin->update_overlays();
} break;
case Drag::TOP_RIGHT: {
selected_camera->set_limit(SIDE_RIGHT, MAX(selected_camera->get_limit(SIDE_LEFT), pos.x));
selected_camera->set_limit(SIDE_TOP, MIN(selected_camera->get_limit(SIDE_BOTTOM), pos.y));
plugin->update_overlays();
} break;
case Drag::BOTTOM_LEFT: {
selected_camera->set_limit(SIDE_LEFT, MIN(selected_camera->get_limit(SIDE_RIGHT), pos.x));
selected_camera->set_limit(SIDE_BOTTOM, MAX(selected_camera->get_limit(SIDE_TOP), pos.y));
plugin->update_overlays();
} break;
case Drag::BOTTOM_RIGHT: {
selected_camera->set_limit(SIDE_RIGHT, MAX(selected_camera->get_limit(SIDE_LEFT), pos.x));
selected_camera->set_limit(SIDE_BOTTOM, MAX(selected_camera->get_limit(SIDE_TOP), pos.y));
plugin->update_overlays();
} break;
case Drag::CENTER: {
Rect2 target_rect = selected_camera->get_limit_rect();
target_rect.position = pos - center_drag_point;
selected_camera->set_limit_rect(target_rect);
plugin->update_overlays();
} break;
case Drag::NONE: {
} break;
}
return true;
}
return false;
}
void Camera2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
if (!selected_camera || !selected_camera->is_limit_enabled()) {
return;
}
Rect2 limit_rect = selected_camera->get_limit_rect();
limit_rect = CanvasItemEditor::get_singleton()->get_canvas_transform().xform(limit_rect);
p_overlay->draw_rect(limit_rect, Color(1, 1, 0.25, 0.63), false, 3);
}
void Camera2DEditor::_menu_option(int p_option) {
switch (p_option) {
case MENU_SNAP_LIMITS_TO_VIEWPORT: {
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Snap Camera2D Limits to the Viewport"), UndoRedo::MERGE_DISABLE, selected_camera);
ur->add_do_method(this, "_snap_limits_to_viewport", selected_camera);
ur->add_undo_method(selected_camera, "_set_limit_rect", selected_camera->get_limit_rect());
ur->add_undo_method(this, "_update_overlays_if_needed", selected_camera);
ur->commit_action();
} break;
}
}
void Camera2DEditor::_snap_limits_to_viewport(Camera2D *p_camera) {
p_camera->set_limit(SIDE_LEFT, 0);
p_camera->set_limit(SIDE_TOP, 0);
p_camera->set_limit(SIDE_RIGHT, GLOBAL_GET("display/window/size/viewport_width"));
p_camera->set_limit(SIDE_BOTTOM, GLOBAL_GET("display/window/size/viewport_height"));
_update_overlays_if_needed(p_camera);
}
void Camera2DEditor::_update_overlays_if_needed(Camera2D *p_camera) {
if (p_camera == selected_camera) {
plugin->update_overlays();
}
}
void Camera2DEditor::_update_hover(const Vector2 &p_mouse_pos) {
if (CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) {
hover_type = Drag::NONE;
CanvasItemEditor::get_singleton()->set_cursor_shape_override();
return;
}
const Rect2 limit_rect = CanvasItemEditor::get_singleton()->get_canvas_transform().xform(selected_camera->get_limit_rect());
const float drag_tolerance = 8.0;
const Vector2 tolerance_vector = Vector2(1, 1) * drag_tolerance;
hover_type = Drag::NONE;
if (Rect2(limit_rect.position - tolerance_vector, tolerance_vector * 2).has_point(p_mouse_pos)) {
hover_type = Drag::TOP_LEFT;
} else if (Rect2(Vector2(limit_rect.get_end().x, limit_rect.position.y) - tolerance_vector, tolerance_vector * 2).has_point(p_mouse_pos)) {
hover_type = Drag::TOP_RIGHT;
} else if (Rect2(Vector2(limit_rect.position.x, limit_rect.get_end().y) - tolerance_vector, tolerance_vector * 2).has_point(p_mouse_pos)) {
hover_type = Drag::BOTTOM_LEFT;
} else if (Rect2(limit_rect.get_end() - tolerance_vector, tolerance_vector * 2).has_point(p_mouse_pos)) {
hover_type = Drag::BOTTOM_RIGHT;
} else if (p_mouse_pos.y > limit_rect.position.y && p_mouse_pos.y < limit_rect.get_end().y) {
if (Math::abs(p_mouse_pos.x - limit_rect.position.x) < drag_tolerance) {
hover_type = Drag::LEFT;
} else if (Math::abs(p_mouse_pos.x - limit_rect.get_end().x) < drag_tolerance) {
hover_type = Drag::RIGHT;
}
} else if (p_mouse_pos.x > limit_rect.position.x && p_mouse_pos.x < limit_rect.get_end().x) {
if (Math::abs(p_mouse_pos.y - limit_rect.position.y) < drag_tolerance) {
hover_type = Drag::TOP;
} else if (Math::abs(p_mouse_pos.y - limit_rect.get_end().y) < drag_tolerance) {
hover_type = Drag::BOTTOM;
}
}
if (hover_type == Drag::NONE && limit_rect.has_point(p_mouse_pos)) {
const Rect2 editor_rect = Rect2(Vector2(), CanvasItemEditor::get_singleton()->get_viewport_control()->get_size());
const Rect2 transformed_rect = selected_camera->get_viewport()->get_canvas_transform().xform_inv(limit_rect);
// Only allow center drag if any limit edge is visible on screen.
bool edge_visible = false;
edge_visible = edge_visible || (transformed_rect.get_end().y > editor_rect.position.y && transformed_rect.position.y < editor_rect.get_end().y && transformed_rect.position.x > editor_rect.position.x && transformed_rect.position.x < editor_rect.get_end().x);
edge_visible = edge_visible || (transformed_rect.get_end().y > editor_rect.position.y && transformed_rect.position.y < editor_rect.get_end().y && transformed_rect.get_end().x > editor_rect.position.x && transformed_rect.get_end().x < editor_rect.get_end().x);
edge_visible = edge_visible || (transformed_rect.get_end().x > editor_rect.position.x && transformed_rect.position.x < editor_rect.get_end().x && transformed_rect.position.y > editor_rect.position.y && transformed_rect.position.y < editor_rect.get_end().y);
edge_visible = edge_visible || (transformed_rect.get_end().x > editor_rect.position.x && transformed_rect.position.x < editor_rect.get_end().x && transformed_rect.get_end().y > editor_rect.position.y && transformed_rect.get_end().y < editor_rect.get_end().y);
if (edge_visible) {
hover_type = Drag::CENTER;
}
}
switch (hover_type) {
case Drag::NONE: {
CanvasItemEditor::get_singleton()->set_cursor_shape_override();
} break;
case Drag::LEFT:
case Drag::RIGHT: {
CanvasItemEditor::get_singleton()->set_cursor_shape_override(CURSOR_HSIZE);
} break;
case Drag::TOP:
case Drag::BOTTOM: {
CanvasItemEditor::get_singleton()->set_cursor_shape_override(CURSOR_VSIZE);
} break;
case Drag::TOP_LEFT:
case Drag::BOTTOM_RIGHT: {
CanvasItemEditor::get_singleton()->set_cursor_shape_override(CURSOR_FDIAGSIZE);
} break;
case Drag::TOP_RIGHT:
case Drag::BOTTOM_LEFT: {
CanvasItemEditor::get_singleton()->set_cursor_shape_override(CURSOR_BDIAGSIZE);
} break;
case Drag::CENTER: {
CanvasItemEditor::get_singleton()->set_cursor_shape_override(CURSOR_MOVE);
} break;
}
}
void Camera2DEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
options->set_button_icon(get_editor_theme_icon(SNAME("Camera2D")));
} break;
}
}
void Camera2DEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_snap_limits_to_viewport", "camera"), &Camera2DEditor::_snap_limits_to_viewport);
ClassDB::bind_method(D_METHOD("_update_overlays_if_needed", "camera"), &Camera2DEditor::_update_overlays_if_needed);
}
Camera2DEditor::Camera2DEditor(EditorPlugin *p_plugin) {
plugin = p_plugin;
options = memnew(MenuButton);
options->set_text(TTRC("Camera2D"));
options->get_popup()->add_item(TTRC("Snap the Limits to the Viewport"), MENU_SNAP_LIMITS_TO_VIEWPORT);
options->set_switch_on_hover(true);
options->hide();
CanvasItemEditor::get_singleton()->add_control_to_menu_panel(options);
options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &Camera2DEditor::_menu_option));
}
void Camera2DEditorPlugin::edit(Object *p_object) {
camera_2d_editor->edit(Object::cast_to<Camera2D>(p_object));
}
bool Camera2DEditorPlugin::handles(Object *p_object) const {
return p_object->is_class("Camera2D");
}
void Camera2DEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
camera_2d_editor->options->show();
} else {
camera_2d_editor->options->hide();
}
}
Camera2DEditorPlugin::Camera2DEditorPlugin() {
camera_2d_editor = memnew(Camera2DEditor(this));
EditorNode::get_singleton()->get_gui_base()->add_child(camera_2d_editor);
}

View File

@@ -0,0 +1,103 @@
/**************************************************************************/
/* camera_2d_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/plugins/editor_plugin.h"
class Camera2D;
class Label;
class MenuButton;
class Camera2DEditor : public Control {
GDCLASS(Camera2DEditor, Control);
EditorPlugin *plugin = nullptr;
enum Menu {
MENU_SNAP_LIMITS_TO_VIEWPORT,
};
enum class Drag {
NONE,
LEFT,
TOP,
RIGHT,
BOTTOM,
TOP_LEFT,
TOP_RIGHT,
BOTTOM_LEFT,
BOTTOM_RIGHT,
CENTER,
};
Drag drag_type = Drag::NONE;
Drag hover_type = Drag::NONE;
Rect2 drag_revert;
Vector2 center_drag_point;
Camera2D *selected_camera = nullptr;
friend class Camera2DEditorPlugin;
MenuButton *options = nullptr;
void _menu_option(int p_option);
void _snap_limits_to_viewport(Camera2D *p_camera);
void _update_overlays_if_needed(Camera2D *p_camera);
void _update_hover(const Vector2 &p_mouse_pos);
protected:
static void _bind_methods();
void _notification(int p_what);
public:
void edit(Camera2D *p_camera);
bool forward_canvas_gui_input(const Ref<InputEvent> &p_event);
void forward_canvas_draw_over_viewport(Control *p_overlay);
Camera2DEditor(EditorPlugin *p_plugin);
};
class Camera2DEditorPlugin : public EditorPlugin {
GDCLASS(Camera2DEditorPlugin, EditorPlugin);
Camera2DEditor *camera_2d_editor = nullptr;
public:
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;
virtual void make_visible(bool p_visible) override;
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return camera_2d_editor->forward_canvas_gui_input(p_event); }
virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { camera_2d_editor->forward_canvas_draw_over_viewport(p_overlay); }
Camera2DEditorPlugin();
};

View File

@@ -0,0 +1,111 @@
/**************************************************************************/
/* light_occluder_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 "light_occluder_2d_editor_plugin.h"
#include "editor/editor_undo_redo_manager.h"
Ref<OccluderPolygon2D> LightOccluder2DEditor::_ensure_occluder() const {
Ref<OccluderPolygon2D> occluder = node->get_occluder_polygon();
if (occluder.is_null()) {
occluder.instantiate();
node->set_occluder_polygon(occluder);
}
return occluder;
}
Node2D *LightOccluder2DEditor::_get_node() const {
return node;
}
void LightOccluder2DEditor::_set_node(Node *p_polygon) {
node = Object::cast_to<LightOccluder2D>(p_polygon);
}
bool LightOccluder2DEditor::_is_line() const {
Ref<OccluderPolygon2D> occluder = node->get_occluder_polygon();
if (occluder.is_valid()) {
return !occluder->is_closed();
} else {
return false;
}
}
int LightOccluder2DEditor::_get_polygon_count() const {
Ref<OccluderPolygon2D> occluder = node->get_occluder_polygon();
if (occluder.is_valid()) {
return occluder->get_polygon().size();
} else {
return 0;
}
}
Variant LightOccluder2DEditor::_get_polygon(int p_idx) const {
Ref<OccluderPolygon2D> occluder = node->get_occluder_polygon();
if (occluder.is_valid()) {
return occluder->get_polygon();
} else {
return Variant(Vector<Vector2>());
}
}
void LightOccluder2DEditor::_set_polygon(int p_idx, const Variant &p_polygon) const {
Ref<OccluderPolygon2D> occluder = _ensure_occluder();
occluder->set_polygon(p_polygon);
}
void LightOccluder2DEditor::_action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) {
Ref<OccluderPolygon2D> occluder = _ensure_occluder();
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->add_do_method(occluder.ptr(), "set_polygon", p_polygon);
undo_redo->add_undo_method(occluder.ptr(), "set_polygon", p_previous);
}
bool LightOccluder2DEditor::_has_resource() const {
return node && node->get_occluder_polygon().is_valid();
}
void LightOccluder2DEditor::_create_resource() {
if (!node) {
return;
}
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Create Occluder Polygon"));
undo_redo->add_do_method(node, "set_occluder_polygon", Ref<OccluderPolygon2D>(memnew(OccluderPolygon2D)));
undo_redo->add_undo_method(node, "set_occluder_polygon", Variant(Ref<RefCounted>()));
undo_redo->commit_action();
_menu_option(MODE_CREATE);
}
LightOccluder2DEditorPlugin::LightOccluder2DEditorPlugin() :
AbstractPolygon2DEditorPlugin(memnew(LightOccluder2DEditor), "LightOccluder2D") {
}

View File

@@ -0,0 +1,63 @@
/**************************************************************************/
/* light_occluder_2d_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/2d/abstract_polygon_2d_editor.h"
#include "scene/2d/light_occluder_2d.h"
class LightOccluder2DEditor : public AbstractPolygon2DEditor {
GDCLASS(LightOccluder2DEditor, AbstractPolygon2DEditor);
LightOccluder2D *node = nullptr;
Ref<OccluderPolygon2D> _ensure_occluder() const;
protected:
virtual Node2D *_get_node() const override;
virtual void _set_node(Node *p_polygon) override;
virtual bool _is_line() const override;
virtual int _get_polygon_count() const override;
virtual Variant _get_polygon(int p_idx) const override;
virtual void _set_polygon(int p_idx, const Variant &p_polygon) const override;
virtual void _action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) override;
virtual bool _has_resource() const override;
virtual void _create_resource() override;
};
class LightOccluder2DEditorPlugin : public AbstractPolygon2DEditorPlugin {
GDCLASS(LightOccluder2DEditorPlugin, AbstractPolygon2DEditorPlugin);
public:
LightOccluder2DEditorPlugin();
};

View File

@@ -0,0 +1,64 @@
/**************************************************************************/
/* line_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 "line_2d_editor_plugin.h"
#include "editor/editor_undo_redo_manager.h"
Node2D *Line2DEditor::_get_node() const {
return node;
}
void Line2DEditor::_set_node(Node *p_line) {
node = Object::cast_to<Line2D>(p_line);
}
bool Line2DEditor::_is_line() const {
return true;
}
Variant Line2DEditor::_get_polygon(int p_idx) const {
return _get_node()->get("points");
}
void Line2DEditor::_set_polygon(int p_idx, const Variant &p_polygon) const {
_get_node()->set("points", p_polygon);
}
void Line2DEditor::_action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) {
Node2D *_node = _get_node();
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->add_do_method(_node, "set_points", p_polygon);
undo_redo->add_undo_method(_node, "set_points", p_previous);
}
Line2DEditorPlugin::Line2DEditorPlugin() :
AbstractPolygon2DEditorPlugin(memnew(Line2DEditor), "Line2D") {
}

View File

@@ -0,0 +1,56 @@
/**************************************************************************/
/* line_2d_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/2d/abstract_polygon_2d_editor.h"
#include "scene/2d/line_2d.h"
class Line2DEditor : public AbstractPolygon2DEditor {
GDCLASS(Line2DEditor, AbstractPolygon2DEditor);
Line2D *node = nullptr;
protected:
virtual Node2D *_get_node() const override;
virtual void _set_node(Node *p_line) override;
virtual bool _is_line() const override;
virtual Variant _get_polygon(int p_idx) const override;
virtual void _set_polygon(int p_idx, const Variant &p_polygon) const override;
virtual void _action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) override;
};
class Line2DEditorPlugin : public AbstractPolygon2DEditorPlugin {
GDCLASS(Line2DEditorPlugin, AbstractPolygon2DEditorPlugin);
public:
Line2DEditorPlugin();
};

View File

@@ -0,0 +1,139 @@
/**************************************************************************/
/* parallax_background_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 "parallax_background_editor_plugin.h"
#include "editor/docks/scene_tree_dock.h"
#include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/scene/canvas_item_editor_plugin.h"
#include "scene/2d/parallax_2d.h"
#include "scene/2d/parallax_background.h"
#include "scene/2d/parallax_layer.h"
#include "scene/gui/box_container.h"
#include "scene/gui/menu_button.h"
void ParallaxBackgroundEditorPlugin::edit(Object *p_object) {
parallax_background = Object::cast_to<ParallaxBackground>(p_object);
}
bool ParallaxBackgroundEditorPlugin::handles(Object *p_object) const {
return Object::cast_to<ParallaxBackground>(p_object) != nullptr;
}
void ParallaxBackgroundEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
toolbar->show();
} else {
toolbar->hide();
}
}
void ParallaxBackgroundEditorPlugin::_menu_callback(int p_idx) {
if (p_idx == MENU_CONVERT_TO_PARALLAX_2D) {
convert_to_parallax2d();
}
}
void ParallaxBackgroundEditorPlugin::convert_to_parallax2d() {
ParallaxBackground *parallax_bg = parallax_background;
TypedArray<Node> children = parallax_bg->get_children();
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Convert to Parallax2D"), UndoRedo::MERGE_DISABLE, parallax_bg);
for (int i = 0; i < children.size(); i++) {
ParallaxLayer *parallax_layer = Object::cast_to<ParallaxLayer>(children[i]);
if (!parallax_layer) {
continue;
}
Parallax2D *parallax2d = memnew(Parallax2D);
Point2 offset = parallax_bg->get_scroll_base_offset() * parallax_layer->get_motion_scale();
offset += parallax_layer->get_motion_offset() + parallax_layer->get_position();
parallax2d->set_scroll_offset(offset);
Point2 limit_begin = parallax2d->get_limit_begin();
Point2 limit_end = parallax2d->get_limit_end();
if (parallax_bg->get_limit_begin().x != 0 || parallax_bg->get_limit_end().x != 0) {
limit_begin.x = parallax_bg->get_limit_begin().x;
limit_end.x = parallax_bg->get_limit_end().x;
}
if (parallax_bg->get_limit_begin().y != 0 || parallax_bg->get_limit_end().y != 0) {
limit_begin.y = parallax_bg->get_limit_begin().y;
limit_end.y = parallax_bg->get_limit_end().y;
}
parallax2d->set_limit_begin(limit_begin);
parallax2d->set_limit_end(limit_end);
parallax2d->set_follow_viewport(!parallax_bg->is_ignore_camera_zoom());
parallax2d->set_repeat_size(parallax_layer->get_mirroring());
parallax2d->set_scroll_scale(parallax_bg->get_scroll_base_scale() * parallax_layer->get_motion_scale());
SceneTreeDock::get_singleton()->replace_node(parallax_layer, parallax2d);
}
if (parallax_bg->is_ignore_camera_zoom()) {
CanvasLayer *canvas_layer = memnew(CanvasLayer);
SceneTreeDock::get_singleton()->replace_node(parallax_bg, canvas_layer);
} else {
Node2D *node2d = memnew(Node2D);
SceneTreeDock::get_singleton()->replace_node(parallax_bg, node2d);
}
ur->commit_action(false);
}
void ParallaxBackgroundEditorPlugin::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ParallaxBackgroundEditorPlugin::_menu_callback));
menu->set_button_icon(menu->get_editor_theme_icon(SNAME("ParallaxBackground")));
} break;
}
}
ParallaxBackgroundEditorPlugin::ParallaxBackgroundEditorPlugin() {
toolbar = memnew(HBoxContainer);
toolbar->hide();
add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar);
menu = memnew(MenuButton);
menu->get_popup()->add_item(TTR("Convert to Parallax2D"), MENU_CONVERT_TO_PARALLAX_2D);
menu->set_text(TTR("ParallaxBackground"));
menu->set_switch_on_hover(true);
menu->set_flat(false);
menu->set_theme_type_variation("FlatMenuButton");
toolbar->add_child(menu);
}

View File

@@ -0,0 +1,64 @@
/**************************************************************************/
/* parallax_background_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/plugins/editor_plugin.h"
class HBoxContainer;
class MenuButton;
class ParallaxBackground;
class ParallaxBackgroundEditorPlugin : public EditorPlugin {
GDCLASS(ParallaxBackgroundEditorPlugin, EditorPlugin);
enum {
MENU_CONVERT_TO_PARALLAX_2D,
};
ParallaxBackground *parallax_background = nullptr;
HBoxContainer *toolbar = nullptr;
MenuButton *menu = nullptr;
void _menu_callback(int p_idx);
void convert_to_parallax2d();
protected:
void _notification(int p_what);
public:
virtual String get_plugin_name() const override { return "ParallaxBackground"; }
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;
ParallaxBackgroundEditorPlugin();
};

View File

@@ -0,0 +1,540 @@
/**************************************************************************/
/* 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 "particles_2d_editor_plugin.h"
#include "core/io/image_loader.h"
#include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_file_dialog.h"
#include "scene/2d/cpu_particles_2d.h"
#include "scene/2d/gpu_particles_2d.h"
#include "scene/gui/box_container.h"
#include "scene/gui/check_box.h"
#include "scene/gui/option_button.h"
#include "scene/gui/popup_menu.h"
#include "scene/gui/spin_box.h"
#include "scene/resources/image_texture.h"
#include "scene/resources/particle_process_material.h"
void GPUParticles2DEditorPlugin::_menu_callback(int p_idx) {
if (p_idx == MENU_GENERATE_VISIBILITY_RECT) {
if (need_show_lifetime_dialog(generate_seconds)) {
generate_visibility_rect->popup_centered();
} else {
_generate_visibility_rect();
}
} else {
Particles2DEditorPlugin::_menu_callback(p_idx);
}
}
void GPUParticles2DEditorPlugin::_add_menu_options(PopupMenu *p_menu) {
Particles2DEditorPlugin::_add_menu_options(p_menu);
p_menu->add_item(TTR("Generate Visibility Rect"), MENU_GENERATE_VISIBILITY_RECT);
}
void Particles2DEditorPlugin::_file_selected(const String &p_file) {
source_emission_file = p_file;
emission_mask->popup_centered();
}
void Particles2DEditorPlugin::_get_base_emission_mask(PackedVector2Array &r_valid_positions, PackedVector2Array &r_valid_normals, PackedByteArray &r_valid_colors, Vector2i &r_image_size) {
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);
r_image_size = s;
r_valid_positions.resize(s.width * s.height);
EmissionMode emode = (EmissionMode)emission_mask_mode->get_selected();
if (emode == EMISSION_MODE_BORDER_DIRECTED) {
r_valid_normals.resize(s.width * s.height);
}
bool capture_colors = emission_colors->is_pressed();
if (capture_colors) {
r_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) {
r_valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
r_valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
r_valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
r_valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
}
r_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) {
r_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();
r_valid_normals.write[vpc] = normal;
}
if (capture_colors) {
r_valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
r_valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
r_valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
r_valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
}
vpc++;
}
}
}
}
}
}
r_valid_positions.resize(vpc);
if (!r_valid_normals.is_empty()) {
r_valid_normals.resize(vpc);
}
}
Particles2DEditorPlugin::Particles2DEditorPlugin() {
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);
EditorNode::get_singleton()->get_gui_base()->add_child(file);
file->connect("file_selected", callable_mp(this, &Particles2DEditorPlugin::_file_selected));
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);
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);
emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode);
VBoxContainer *optionsvb = memnew(VBoxContainer);
emvb->add_margin_child(TTR("Options"), optionsvb);
emission_mask_centered = memnew(CheckBox(TTR("Centered")));
optionsvb->add_child(emission_mask_centered);
emission_colors = memnew(CheckBox(TTR("Capture Colors from Pixel")));
optionsvb->add_child(emission_colors);
EditorNode::get_singleton()->get_gui_base()->add_child(emission_mask);
emission_mask->connect(SceneStringName(confirmed), callable_mp(this, &Particles2DEditorPlugin::_generate_emission_mask));
}
void Particles2DEditorPlugin::_set_show_gizmos(Node *p_node, bool p_show) {
GPUParticles2D *gpu_particles = Object::cast_to<GPUParticles2D>(p_node);
if (gpu_particles) {
gpu_particles->set_show_gizmos(p_show);
}
CPUParticles2D *cpu_particles = Object::cast_to<CPUParticles2D>(p_node);
if (cpu_particles) {
cpu_particles->set_show_gizmos(p_show);
}
// The `selection_changed` signal is deferred. A node could be deleted before the signal is emitted.
if (p_show) {
p_node->connect(SceneStringName(tree_exiting), callable_mp(this, &Particles2DEditorPlugin::_node_removed).bind(p_node));
} else {
p_node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Particles2DEditorPlugin::_node_removed));
}
}
void Particles2DEditorPlugin::_selection_changed() {
List<Node *> current_selection = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();
if (selected_particles.is_empty() && current_selection.is_empty()) {
return;
}
// Turn gizmos off for nodes that are no longer selected.
for (List<Node *>::Element *E = selected_particles.front(); E;) {
Node *node = E->get();
List<Node *>::Element *N = E->next();
if (current_selection.find(node) == nullptr) {
_set_show_gizmos(node, false);
selected_particles.erase(E);
}
E = N;
}
// Turn gizmos on for nodes that are newly selected.
for (Node *node : current_selection) {
if (selected_particles.find(node) == nullptr) {
_set_show_gizmos(node, true);
selected_particles.push_back(node);
}
}
}
void Particles2DEditorPlugin::_node_removed(Node *p_node) {
List<Node *>::Element *E = selected_particles.find(p_node);
if (E) {
_set_show_gizmos(E->get(), false);
selected_particles.erase(E);
}
}
void GPUParticles2DEditorPlugin::_generate_visibility_rect() {
GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node);
double time = generate_seconds->get_value();
float running = 0.0;
EditorProgress ep("gen_vrect", TTR("Generating Visibility Rect (Waiting for Particle Simulation)"), int(time));
bool was_emitting = particles->is_emitting();
if (!was_emitting) {
particles->set_emitting(true);
OS::get_singleton()->delay_usec(1000);
}
Rect2 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);
Rect2 capture = particles->capture_rect();
if (rect == Rect2()) {
rect = capture;
} else {
rect = rect.merge(capture);
}
running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
}
if (!was_emitting) {
particles->set_emitting(false);
}
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Generate Visibility Rect"));
undo_redo->add_do_method(particles, "set_visibility_rect", rect);
undo_redo->add_undo_method(particles, "set_visibility_rect", particles->get_visibility_rect());
undo_redo->commit_action();
}
void Particles2DEditorPlugin::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &Particles2DEditorPlugin::_selection_changed));
} break;
}
}
void Particles2DEditorPlugin::_menu_callback(int p_idx) {
if (p_idx == MENU_LOAD_EMISSION_MASK) {
GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node);
if (particles && particles->get_process_material().is_null()) {
EditorNode::get_singleton()->show_warning(TTR("Loading emission mask requires ParticleProcessMaterial."));
return;
}
file->popup_file_dialog();
} else {
ParticlesEditorPlugin::_menu_callback(p_idx);
}
}
void Particles2DEditorPlugin::_add_menu_options(PopupMenu *p_menu) {
p_menu->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK);
}
Node *GPUParticles2DEditorPlugin::_convert_particles() {
GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node);
CPUParticles2D *cpu_particles = memnew(CPUParticles2D);
cpu_particles->convert_from_particles(particles);
cpu_particles->set_name(particles->get_name());
cpu_particles->set_transform(particles->get_transform());
cpu_particles->set_visible(particles->is_visible());
cpu_particles->set_process_mode(particles->get_process_mode());
cpu_particles->set_z_index(particles->get_z_index());
return cpu_particles;
}
void GPUParticles2DEditorPlugin::_generate_emission_mask() {
GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node);
Ref<ParticleProcessMaterial> pm = particles->get_process_material();
ERR_FAIL_COND(pm.is_null());
PackedVector2Array valid_positions;
PackedVector2Array valid_normals;
PackedByteArray valid_colors;
Vector2i image_size;
_get_base_emission_mask(valid_positions, valid_normals, valid_colors, image_size);
ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image...");
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Load Emission Mask"));
ParticleProcessMaterial *pmptr = pm.ptr();
Vector<uint8_t> texdata;
int vpc = valid_positions.size();
int w = 2048;
int h = (vpc / 2048) + 1;
texdata.resize(w * h * 2 * sizeof(float));
{
Vector2 offset;
if (emission_mask_centered->is_pressed()) {
offset = Vector2(-image_size.width * 0.5, -image_size.height * 0.5);
}
uint8_t *tw = texdata.ptrw();
float *twf = reinterpret_cast<float *>(tw);
for (int i = 0; i < vpc; i++) {
twf[i * 2 + 0] = valid_positions[i].x + offset.x;
twf[i * 2 + 1] = valid_positions[i].y + offset.y;
}
}
Ref<Image> img;
img.instantiate();
img->set_data(w, h, false, Image::FORMAT_RGF, texdata);
undo_redo->add_do_property(pmptr, "emission_point_texture", ImageTexture::create_from_image(img));
undo_redo->add_undo_property(pmptr, "emission_point_texture", pm->get_emission_point_texture());
undo_redo->add_do_property(pmptr, "emission_point_count", vpc);
undo_redo->add_undo_property(pmptr, "emission_point_count", pm->get_emission_point_count());
if (emission_colors->is_pressed()) {
Vector<uint8_t> colordata;
colordata.resize(w * h * 4); //use RG texture
{
uint8_t *tw = colordata.ptrw();
for (int i = 0; i < vpc * 4; i++) {
tw[i] = valid_colors[i];
}
}
img.instantiate();
img->set_data(w, h, false, Image::FORMAT_RGBA8, colordata);
undo_redo->add_do_property(pmptr, "emission_color_texture", ImageTexture::create_from_image(img));
undo_redo->add_undo_property(pmptr, "emission_color_texture", pm->get_emission_color_texture());
}
if (!valid_normals.is_empty()) {
undo_redo->add_do_property(pmptr, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS);
undo_redo->add_undo_property(pmptr, "emission_shape", pm->get_emission_shape());
pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS);
Vector<uint8_t> normdata;
normdata.resize(w * h * 2 * sizeof(float)); //use RG texture
{
uint8_t *tw = normdata.ptrw();
float *twf = reinterpret_cast<float *>(tw);
for (int i = 0; i < vpc; i++) {
twf[i * 2 + 0] = valid_normals[i].x;
twf[i * 2 + 1] = valid_normals[i].y;
}
}
img.instantiate();
img->set_data(w, h, false, Image::FORMAT_RGF, normdata);
undo_redo->add_do_property(pmptr, "emission_normal_texture", ImageTexture::create_from_image(img));
undo_redo->add_undo_property(pmptr, "emission_normal_texture", pm->get_emission_normal_texture());
} else {
undo_redo->add_do_property(pmptr, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_POINTS);
undo_redo->add_undo_property(pmptr, "emission_shape", pm->get_emission_shape());
}
undo_redo->commit_action();
}
GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() {
handled_type = TTRC("GPUParticles2D");
conversion_option_name = TTR("Convert to CPUParticles2D");
generate_visibility_rect = memnew(ConfirmationDialog);
generate_visibility_rect->set_title(TTR("Generate Visibility Rect"));
VBoxContainer *genvb = memnew(VBoxContainer);
generate_visibility_rect->add_child(genvb);
generate_seconds = memnew(SpinBox);
generate_seconds->set_min(0.1);
generate_seconds->set_max(25);
generate_seconds->set_value(2);
genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds);
EditorNode::get_singleton()->get_gui_base()->add_child(generate_visibility_rect);
generate_visibility_rect->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles2DEditorPlugin::_generate_visibility_rect));
}
Node *CPUParticles2DEditorPlugin::_convert_particles() {
CPUParticles2D *particles = Object::cast_to<CPUParticles2D>(edited_node);
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());
return gpu_particles;
}
void CPUParticles2DEditorPlugin::_generate_emission_mask() {
CPUParticles2D *particles = Object::cast_to<CPUParticles2D>(edited_node);
PackedVector2Array valid_positions;
PackedVector2Array valid_normals;
PackedByteArray valid_colors;
Vector2i image_size;
_get_base_emission_mask(valid_positions, valid_normals, valid_colors, image_size);
ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image...");
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Load Emission Mask"));
int vpc = valid_positions.size();
if (emission_colors->is_pressed()) {
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;
}
undo_redo->add_do_property(particles, "emission_colors", pca);
undo_redo->add_undo_property(particles, "emission_colors", particles->get_emission_colors());
}
if (!valid_normals.is_empty()) {
undo_redo->add_do_property(particles, "emission_shape", CPUParticles2D::EMISSION_SHAPE_DIRECTED_POINTS);
undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_shape());
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];
}
undo_redo->add_do_property(particles, "emission_normals", norms);
undo_redo->add_undo_property(particles, "emission_normals", particles->get_emission_normals());
} else {
undo_redo->add_do_property(particles, "emission_shape", CPUParticles2D::EMISSION_SHAPE_POINTS);
undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_shape());
}
{
Vector2 offset;
if (emission_mask_centered->is_pressed()) {
offset = Vector2(-image_size.width * 0.5, -image_size.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;
}
undo_redo->add_do_property(particles, "emission_points", points);
undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_points());
}
undo_redo->commit_action();
}
CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() {
handled_type = TTRC("CPUParticles2D");
conversion_option_name = TTR("Convert to GPUParticles2D");
}

View File

@@ -0,0 +1,109 @@
/**************************************************************************/
/* particles_2d_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/particles_editor_plugin.h"
class EditorFileDialog;
class Particles2DEditorPlugin : public ParticlesEditorPlugin {
GDCLASS(Particles2DEditorPlugin, ParticlesEditorPlugin);
protected:
enum {
MENU_LOAD_EMISSION_MASK = 100,
};
List<Node *> selected_particles;
enum EmissionMode {
EMISSION_MODE_SOLID,
EMISSION_MODE_BORDER,
EMISSION_MODE_BORDER_DIRECTED
};
EditorFileDialog *file = nullptr;
ConfirmationDialog *emission_mask = nullptr;
OptionButton *emission_mask_mode = nullptr;
CheckBox *emission_mask_centered = nullptr;
CheckBox *emission_colors = nullptr;
String source_emission_file;
virtual void _menu_callback(int p_idx) override;
virtual void _add_menu_options(PopupMenu *p_menu) override;
void _file_selected(const String &p_file);
void _get_base_emission_mask(PackedVector2Array &r_valid_positions, PackedVector2Array &r_valid_normals, PackedByteArray &r_valid_colors, Vector2i &r_image_size);
virtual void _generate_emission_mask() = 0;
void _notification(int p_what);
void _set_show_gizmos(Node *p_node, bool p_show);
void _selection_changed();
void _node_removed(Node *p_node);
public:
Particles2DEditorPlugin();
};
class GPUParticles2DEditorPlugin : public Particles2DEditorPlugin {
GDCLASS(GPUParticles2DEditorPlugin, Particles2DEditorPlugin);
enum {
MENU_GENERATE_VISIBILITY_RECT = 200,
};
ConfirmationDialog *generate_visibility_rect = nullptr;
SpinBox *generate_seconds = nullptr;
void _generate_visibility_rect();
protected:
void _menu_callback(int p_idx) override;
void _add_menu_options(PopupMenu *p_menu) override;
Node *_convert_particles() override;
void _generate_emission_mask() override;
public:
GPUParticles2DEditorPlugin();
};
class CPUParticles2DEditorPlugin : public Particles2DEditorPlugin {
GDCLASS(CPUParticles2DEditorPlugin, Particles2DEditorPlugin);
protected:
Node *_convert_particles() override;
void _generate_emission_mask() override;
public:
CPUParticles2DEditorPlugin();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,157 @@
/**************************************************************************/
/* path_2d_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/plugins/editor_plugin.h"
#include "scene/2d/path_2d.h"
#include "scene/gui/box_container.h"
class CanvasItemEditor;
class ConfirmationDialog;
class MenuButton;
class Path2DEditor : public HBoxContainer {
GDCLASS(Path2DEditor, HBoxContainer);
friend class Path2DEditorPlugin;
CanvasItemEditor *canvas_item_editor = nullptr;
Panel *panel = nullptr;
Path2D *node = nullptr;
enum Mode {
MODE_CREATE,
MODE_EDIT,
MODE_EDIT_CURVE,
MODE_DELETE,
MODE_CLOSE,
MODE_CLEAR_POINTS,
};
Mode mode = MODE_EDIT;
HBoxContainer *toolbar = nullptr;
Button *curve_clear_points = nullptr;
Button *curve_close = nullptr;
Button *curve_create = nullptr;
Button *curve_del = nullptr;
Button *curve_edit = nullptr;
Button *curve_edit_curve = nullptr;
MenuButton *handle_menu = nullptr;
Button *create_curve_button = nullptr;
ConfirmationDialog *clear_points_dialog = nullptr;
bool mirror_handle_angle = true;
bool mirror_handle_length = true;
bool on_edge = false;
enum HandleOption {
HANDLE_OPTION_ANGLE,
HANDLE_OPTION_LENGTH,
};
enum Action {
ACTION_NONE,
ACTION_MOVING_POINT,
ACTION_MOVING_NEW_POINT,
ACTION_MOVING_NEW_POINT_FROM_SPLIT,
ACTION_MOVING_IN,
ACTION_MOVING_OUT,
};
Action action = ACTION_NONE;
int action_point = 0;
Point2 moving_from;
Point2 moving_screen_from;
float orig_in_length = 0.0f;
float orig_out_length = 0.0f;
Vector2 edge_point;
Vector2 original_mouse_pos;
// Number of control points in range of the last click.
// 0, 1, or 2.
int control_points_in_range = 0;
void _mode_selected(int p_mode);
void _handle_option_pressed(int p_option);
void _cancel_current_action();
void _node_visibility_changed();
void _update_toolbar();
void _create_curve();
void _confirm_clear_points();
void _clear_curve_points(Path2D *p_path2d);
void _restore_curve_points(Path2D *p_path2d, const PackedVector2Array &p_points);
RID debug_mesh_rid;
RID debug_handle_mesh_rid;
RID debug_handle_multimesh_rid;
RID debug_handle_curve_multimesh_rid;
RID debug_handle_sharp_multimesh_rid;
RID debug_handle_smooth_multimesh_rid;
LocalVector<Vector2> debug_handle_lines;
LocalVector<Transform2D> debug_handle_curve_transforms;
LocalVector<Transform2D> debug_handle_sharp_transforms;
LocalVector<Transform2D> debug_handle_smooth_transforms;
protected:
void _notification(int p_what);
void _node_removed(Node *p_node);
static void _bind_methods();
public:
bool forward_gui_input(const Ref<InputEvent> &p_event);
void forward_canvas_draw_over_viewport(Control *p_overlay);
void edit(Node *p_path2d);
Path2DEditor();
~Path2DEditor();
};
class Path2DEditorPlugin : public EditorPlugin {
GDCLASS(Path2DEditorPlugin, EditorPlugin);
Path2DEditor *path2d_editor = nullptr;
public:
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return path2d_editor->forward_gui_input(p_event); }
virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { path2d_editor->forward_canvas_draw_over_viewport(p_overlay); }
virtual String get_plugin_name() const override { return "Path2D"; }
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;
Path2DEditorPlugin();
};

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.editor_sources, "*.cpp")

View File

@@ -0,0 +1,167 @@
/**************************************************************************/
/* cast_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 "cast_2d_editor_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/scene/canvas_item_editor_plugin.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) {
case NOTIFICATION_ENTER_TREE: {
get_tree()->connect("node_removed", callable_mp(this, &Cast2DEditor::_node_removed));
} break;
case NOTIFICATION_EXIT_TREE: {
get_tree()->disconnect("node_removed", callable_mp(this, &Cast2DEditor::_node_removed));
} break;
}
}
void Cast2DEditor::_node_removed(Node *p_node) {
if (p_node == node) {
node = nullptr;
}
}
bool Cast2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
if (!node || !node->is_visible_in_tree()) {
return false;
}
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) {
Vector2 target_position = node->get("target_position");
Vector2 gpoint = mb->get_position();
if (mb->is_pressed()) {
if (xform.xform(target_position).distance_to(gpoint) < 8) {
pressed = true;
original_target_position = target_position;
original_mouse_pos = gpoint;
return true;
} else {
pressed = false;
return false;
}
} else if (pressed) {
if (original_mouse_pos != gpoint) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Set Target Position"));
undo_redo->add_do_property(node, "target_position", target_position);
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_property(node, "target_position", original_target_position);
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
undo_redo->commit_action();
}
pressed = false;
return true;
}
}
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_screen_transform().affine_inverse().xform(point);
node->set("target_position", point);
canvas_item_editor->update_viewport();
return true;
}
return false;
}
void Cast2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
if (!node || !node->is_visible_in_tree()) {
return;
}
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);
}
void Cast2DEditor::edit(Node2D *p_node) {
if (!canvas_item_editor) {
canvas_item_editor = CanvasItemEditor::get_singleton();
}
if (Object::cast_to<RayCast2D>(p_node) || Object::cast_to<ShapeCast2D>(p_node)) {
node = p_node;
} else {
node = nullptr;
}
canvas_item_editor->update_viewport();
}
///////////////////////
void Cast2DEditorPlugin::edit(Object *p_object) {
cast_2d_editor->edit(Object::cast_to<Node2D>(p_object));
}
bool Cast2DEditorPlugin::handles(Object *p_object) const {
return Object::cast_to<RayCast2D>(p_object) != nullptr || Object::cast_to<ShapeCast2D>(p_object) != nullptr;
}
void Cast2DEditorPlugin::make_visible(bool p_visible) {
if (!p_visible) {
edit(nullptr);
}
}
Cast2DEditorPlugin::Cast2DEditorPlugin() {
cast_2d_editor = memnew(Cast2DEditor);
EditorNode::get_singleton()->get_gui_base()->add_child(cast_2d_editor);
}

View File

@@ -0,0 +1,74 @@
/**************************************************************************/
/* cast_2d_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/plugins/editor_plugin.h"
#include "scene/2d/node_2d.h"
class CanvasItemEditor;
class Cast2DEditor : public Control {
GDCLASS(Cast2DEditor, Control);
CanvasItemEditor *canvas_item_editor = nullptr;
Node2D *node = nullptr;
bool pressed = false;
Point2 original_target_position;
Vector2 original_mouse_pos;
protected:
void _notification(int p_what);
void _node_removed(Node *p_node);
public:
bool forward_canvas_gui_input(const Ref<InputEvent> &p_event);
void forward_canvas_draw_over_viewport(Control *p_overlay);
void edit(Node2D *p_node);
};
class Cast2DEditorPlugin : public EditorPlugin {
GDCLASS(Cast2DEditorPlugin, EditorPlugin);
Cast2DEditor *cast_2d_editor = nullptr;
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_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;
virtual void make_visible(bool visible) override;
Cast2DEditorPlugin();
};

View File

@@ -0,0 +1,47 @@
/**************************************************************************/
/* collision_polygon_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 "collision_polygon_2d_editor_plugin.h"
Node2D *CollisionPolygon2DEditor::_get_node() const {
return node;
}
void CollisionPolygon2DEditor::_set_node(Node *p_polygon) {
node = Object::cast_to<CollisionPolygon2D>(p_polygon);
}
CollisionPolygon2DEditor::CollisionPolygon2DEditor() {
set_edit_origin_and_center(true);
}
CollisionPolygon2DEditorPlugin::CollisionPolygon2DEditorPlugin() :
AbstractPolygon2DEditorPlugin(memnew(CollisionPolygon2DEditor), "CollisionPolygon2D") {
}

View File

@@ -0,0 +1,54 @@
/**************************************************************************/
/* collision_polygon_2d_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/2d/abstract_polygon_2d_editor.h"
#include "scene/2d/physics/collision_polygon_2d.h"
class CollisionPolygon2DEditor : public AbstractPolygon2DEditor {
GDCLASS(CollisionPolygon2DEditor, AbstractPolygon2DEditor);
CollisionPolygon2D *node = nullptr;
protected:
virtual Node2D *_get_node() const override;
virtual void _set_node(Node *p_polygon) override;
public:
CollisionPolygon2DEditor();
};
class CollisionPolygon2DEditorPlugin : public AbstractPolygon2DEditorPlugin {
GDCLASS(CollisionPolygon2DEditorPlugin, AbstractPolygon2DEditorPlugin);
public:
CollisionPolygon2DEditorPlugin();
};

View File

@@ -0,0 +1,667 @@
/**************************************************************************/
/* collision_shape_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 "collision_shape_2d_editor_plugin.h"
#include "core/os/keyboard.h"
#include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/scene/canvas_item_editor_plugin.h"
#include "editor/settings/editor_settings.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"
#include "scene/resources/2d/convex_polygon_shape_2d.h"
#include "scene/resources/2d/rectangle_shape_2d.h"
#include "scene/resources/2d/segment_shape_2d.h"
#include "scene/resources/2d/separation_ray_shape_2d.h"
#include "scene/resources/2d/world_boundary_shape_2d.h"
CollisionShape2DEditor::CollisionShape2DEditor() {
grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
}
void CollisionShape2DEditor::_node_removed(Node *p_node) {
if (p_node == node) {
node = nullptr;
}
}
Variant CollisionShape2DEditor::get_handle_value(int idx) const {
switch (shape_type) {
case CAPSULE_SHAPE: {
Ref<CapsuleShape2D> capsule = node->get_shape();
return Vector2(capsule->get_radius(), capsule->get_height());
} break;
case CIRCLE_SHAPE: {
Ref<CircleShape2D> circle = node->get_shape();
if (idx == 0) {
return circle->get_radius();
}
} 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: {
Ref<WorldBoundaryShape2D> world_boundary = node->get_shape();
if (idx == 0) {
return world_boundary->get_distance();
} else {
return world_boundary->get_normal();
}
} break;
case SEPARATION_RAY_SHAPE: {
Ref<SeparationRayShape2D> ray = node->get_shape();
if (idx == 0) {
return ray->get_length();
}
} break;
case RECTANGLE_SHAPE: {
Ref<RectangleShape2D> rect = node->get_shape();
if (idx < 8) {
return rect->get_size().abs();
}
} break;
case SEGMENT_SHAPE: {
Ref<SegmentShape2D> seg = node->get_shape();
if (idx == 0) {
return seg->get_a();
} else if (idx == 1) {
return seg->get_b();
}
} break;
}
return Variant();
}
void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) {
switch (shape_type) {
case CAPSULE_SHAPE: {
if (idx < 2) {
Ref<CapsuleShape2D> capsule = node->get_shape();
real_t parameter = Math::abs(p_point[idx]);
if (idx == 0) {
capsule->set_radius(parameter);
} else if (idx == 1) {
capsule->set_height(parameter * 2);
}
}
} break;
case CIRCLE_SHAPE: {
Ref<CircleShape2D> circle = node->get_shape();
circle->set_radius(p_point.length());
} 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: {
if (idx < 2) {
Ref<WorldBoundaryShape2D> world_boundary = node->get_shape();
if (idx == 0) {
Vector2 normal = world_boundary->get_normal();
world_boundary->set_distance(p_point.dot(normal) / normal.length_squared());
} else {
real_t dir = world_boundary->get_distance() < 0 ? -1 : 1;
world_boundary->set_normal(p_point.normalized() * dir);
}
}
} break;
case SEPARATION_RAY_SHAPE: {
Ref<SeparationRayShape2D> ray = node->get_shape();
ray->set_length(Math::abs(p_point.y));
} break;
case RECTANGLE_SHAPE: {
if (idx < 8) {
Ref<RectangleShape2D> rect = node->get_shape();
Vector2 size = (Point2)original;
if (RECT_HANDLES[idx].x != 0) {
size.x = p_point.x * RECT_HANDLES[idx].x * 2;
}
if (RECT_HANDLES[idx].y != 0) {
size.y = p_point.y * RECT_HANDLES[idx].y * 2;
}
if (Input::get_singleton()->is_key_pressed(Key::ALT)) {
rect->set_size(size.abs());
node->set_global_position(original_transform.get_origin());
} else {
rect->set_size(((Point2)original + (size - (Point2)original) * 0.5).abs());
Point2 pos = original_transform.affine_inverse().xform(original_transform.get_origin());
pos += (size - (Point2)original) * 0.5 * RECT_HANDLES[idx] * 0.5;
node->set_global_position(original_transform.xform(pos));
}
}
} break;
case SEGMENT_SHAPE: {
if (edit_handle < 2) {
Ref<SegmentShape2D> seg = node->get_shape();
if (idx == 0) {
seg->set_a(p_point);
} else if (idx == 1) {
seg->set_b(p_point);
}
}
} break;
}
}
void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Set Handle"));
switch (shape_type) {
case CAPSULE_SHAPE: {
Ref<CapsuleShape2D> capsule = node->get_shape();
Vector2 values = p_org;
if (idx == 0) {
undo_redo->add_do_method(capsule.ptr(), "set_radius", capsule->get_radius());
} else if (idx == 1) {
undo_redo->add_do_method(capsule.ptr(), "set_height", capsule->get_height());
}
undo_redo->add_undo_method(capsule.ptr(), "set_radius", values[0]);
undo_redo->add_undo_method(capsule.ptr(), "set_height", values[1]);
} break;
case CIRCLE_SHAPE: {
Ref<CircleShape2D> circle = node->get_shape();
undo_redo->add_do_method(circle.ptr(), "set_radius", circle->get_radius());
undo_redo->add_undo_method(circle.ptr(), "set_radius", p_org);
} break;
case CONCAVE_POLYGON_SHAPE: {
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: {
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: {
Ref<WorldBoundaryShape2D> world_boundary = node->get_shape();
if (idx == 0) {
undo_redo->add_do_method(world_boundary.ptr(), "set_distance", world_boundary->get_distance());
undo_redo->add_undo_method(world_boundary.ptr(), "set_distance", p_org);
} else {
undo_redo->add_do_method(world_boundary.ptr(), "set_normal", world_boundary->get_normal());
undo_redo->add_undo_method(world_boundary.ptr(), "set_normal", p_org);
}
} break;
case SEPARATION_RAY_SHAPE: {
Ref<SeparationRayShape2D> ray = node->get_shape();
undo_redo->add_do_method(ray.ptr(), "set_length", ray->get_length());
undo_redo->add_undo_method(ray.ptr(), "set_length", p_org);
} break;
case RECTANGLE_SHAPE: {
Ref<RectangleShape2D> rect = node->get_shape();
undo_redo->add_do_method(rect.ptr(), "set_size", rect->get_size());
undo_redo->add_do_method(node, "set_global_transform", node->get_global_transform());
undo_redo->add_undo_method(rect.ptr(), "set_size", p_org);
undo_redo->add_undo_method(node, "set_global_transform", original_transform);
} break;
case SEGMENT_SHAPE: {
Ref<SegmentShape2D> seg = node->get_shape();
if (idx == 0) {
undo_redo->add_do_method(seg.ptr(), "set_a", seg->get_a());
undo_redo->add_undo_method(seg.ptr(), "set_a", p_org);
} else if (idx == 1) {
undo_redo->add_do_method(seg.ptr(), "set_b", seg->get_b());
undo_redo->add_undo_method(seg.ptr(), "set_b", p_org);
}
} break;
}
undo_redo->commit_action();
}
bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
if (!node) {
return false;
}
if (!node->is_visible_in_tree()) {
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_screen_transform();
if (mb.is_valid()) {
Vector2 gpoint = mb->get_position();
if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
for (int i = 0; i < handles.size(); i++) {
if (xform.xform(handles[i]).distance_to(gpoint) < grab_threshold) {
edit_handle = i;
break;
}
}
if (edit_handle == -1) {
pressed = false;
return false;
}
original_mouse_pos = gpoint;
original_point = handles[edit_handle];
original = get_handle_value(edit_handle);
original_transform = node->get_global_transform();
last_point = original;
pressed = true;
return true;
} else {
if (pressed) {
if (original_mouse_pos != gpoint) {
commit_handle(edit_handle, original);
}
edit_handle = -1;
pressed = false;
return true;
}
}
}
return false;
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
if (edit_handle == -1 || !pressed) {
return false;
}
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;
set_handle(edit_handle, cpoint);
return true;
}
Ref<InputEventKey> k = p_event;
if (k.is_valid()) {
if (edit_handle == -1 || !pressed || k->is_echo()) {
return false;
}
if (shape_type == RECTANGLE_SHAPE && k->get_keycode() == Key::ALT) {
set_handle(edit_handle, last_point); // Update handle when Alt key is toggled.
}
}
return false;
}
void CollisionShape2DEditor::_shape_changed() {
canvas_item_editor->update_viewport();
if (current_shape.is_valid()) {
current_shape->disconnect_changed(callable_mp(canvas_item_editor, &CanvasItemEditor::update_viewport));
current_shape = Ref<Shape2D>();
shape_type = -1;
}
if (!node) {
return;
}
current_shape = node->get_shape();
if (current_shape.is_valid()) {
current_shape->connect_changed(callable_mp(canvas_item_editor, &CanvasItemEditor::update_viewport));
} else {
return;
}
if (Object::cast_to<CapsuleShape2D>(*current_shape)) {
shape_type = CAPSULE_SHAPE;
} else if (Object::cast_to<CircleShape2D>(*current_shape)) {
shape_type = CIRCLE_SHAPE;
} else if (Object::cast_to<ConcavePolygonShape2D>(*current_shape)) {
shape_type = CONCAVE_POLYGON_SHAPE;
} else if (Object::cast_to<ConvexPolygonShape2D>(*current_shape)) {
shape_type = CONVEX_POLYGON_SHAPE;
} else if (Object::cast_to<WorldBoundaryShape2D>(*current_shape)) {
shape_type = WORLD_BOUNDARY_SHAPE;
} else if (Object::cast_to<SeparationRayShape2D>(*current_shape)) {
shape_type = SEPARATION_RAY_SHAPE;
} else if (Object::cast_to<RectangleShape2D>(*current_shape)) {
shape_type = RECTANGLE_SHAPE;
} else if (Object::cast_to<SegmentShape2D>(*current_shape)) {
shape_type = SEGMENT_SHAPE;
}
}
void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
if (!node) {
return;
}
if (!node->is_visible_in_tree()) {
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_screen_transform();
Ref<Texture2D> h = get_editor_theme_icon(SNAME("EditorHandle"));
Vector2 size = h->get_size() * 0.5;
handles.clear();
switch (shape_type) {
case CAPSULE_SHAPE: {
Ref<CapsuleShape2D> shape = current_shape;
handles.resize(2);
float radius = shape->get_radius();
float height = shape->get_height() / 2;
handles.write[0] = Point2(radius, 0);
handles.write[1] = Point2(0, height);
p_overlay->draw_texture(h, gt.xform(handles[0]) - size);
p_overlay->draw_texture(h, gt.xform(handles[1]) - size);
} break;
case CIRCLE_SHAPE: {
Ref<CircleShape2D> shape = current_shape;
handles.resize(1);
handles.write[0] = Point2(shape->get_radius(), 0);
p_overlay->draw_texture(h, gt.xform(handles[0]) - size);
} 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: {
Ref<WorldBoundaryShape2D> shape = current_shape;
handles.resize(2);
handles.write[0] = shape->get_normal() * shape->get_distance();
handles.write[1] = shape->get_normal() * (shape->get_distance() + 30.0);
p_overlay->draw_texture(h, gt.xform(handles[0]) - size);
p_overlay->draw_texture(h, gt.xform(handles[1]) - size);
} break;
case SEPARATION_RAY_SHAPE: {
Ref<SeparationRayShape2D> shape = current_shape;
handles.resize(1);
handles.write[0] = Point2(0, shape->get_length());
p_overlay->draw_texture(h, gt.xform(handles[0]) - size);
} break;
case RECTANGLE_SHAPE: {
Ref<RectangleShape2D> shape = current_shape;
handles.resize(8);
Vector2 ext = shape->get_size() / 2;
for (int i = 0; i < handles.size(); i++) {
handles.write[i] = RECT_HANDLES[i] * ext;
p_overlay->draw_texture(h, gt.xform(handles[i]) - size);
}
} break;
case SEGMENT_SHAPE: {
Ref<SegmentShape2D> shape = current_shape;
handles.resize(2);
handles.write[0] = shape->get_a();
handles.write[1] = shape->get_b();
p_overlay->draw_texture(h, gt.xform(handles[0]) - size);
p_overlay->draw_texture(h, gt.xform(handles[1]) - size);
} break;
}
}
void CollisionShape2DEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
get_tree()->connect("node_removed", callable_mp(this, &CollisionShape2DEditor::_node_removed));
} break;
case NOTIFICATION_EXIT_TREE: {
get_tree()->disconnect("node_removed", callable_mp(this, &CollisionShape2DEditor::_node_removed));
} break;
case NOTIFICATION_PROCESS: {
if (node && node->get_shape() != current_shape) {
_shape_changed();
}
} break;
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
if (EditorSettings::get_singleton()->check_changed_settings_in_group("editors/polygon_editor")) {
grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
}
} break;
}
}
void CollisionShape2DEditor::edit(Node *p_node) {
if (!canvas_item_editor) {
canvas_item_editor = CanvasItemEditor::get_singleton();
}
if (p_node) {
node = Object::cast_to<CollisionShape2D>(p_node);
set_process(true);
} else {
if (pressed) {
set_handle(edit_handle, original_point);
pressed = false;
}
edit_handle = -1;
node = nullptr;
set_process(false);
}
_shape_changed();
}
void CollisionShape2DEditorPlugin::edit(Object *p_obj) {
collision_shape_2d_editor->edit(Object::cast_to<Node>(p_obj));
}
bool CollisionShape2DEditorPlugin::handles(Object *p_obj) const {
return p_obj->is_class("CollisionShape2D");
}
void CollisionShape2DEditorPlugin::make_visible(bool visible) {
if (!visible) {
edit(nullptr);
}
}
CollisionShape2DEditorPlugin::CollisionShape2DEditorPlugin() {
collision_shape_2d_editor = memnew(CollisionShape2DEditor);
EditorNode::get_singleton()->get_gui_base()->add_child(collision_shape_2d_editor);
}

View File

@@ -0,0 +1,114 @@
/**************************************************************************/
/* collision_shape_2d_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/plugins/editor_plugin.h"
#include "scene/2d/physics/collision_shape_2d.h"
class CanvasItemEditor;
class CollisionShape2DEditor : public Control {
GDCLASS(CollisionShape2DEditor, Control);
enum ShapeType {
CAPSULE_SHAPE,
CIRCLE_SHAPE,
CONCAVE_POLYGON_SHAPE,
CONVEX_POLYGON_SHAPE,
WORLD_BOUNDARY_SHAPE,
SEPARATION_RAY_SHAPE,
RECTANGLE_SHAPE,
SEGMENT_SHAPE
};
const Point2 RECT_HANDLES[8] = {
Point2(1, 0),
Point2(1, 1),
Point2(0, 1),
Point2(-1, 1),
Point2(-1, 0),
Point2(-1, -1),
Point2(0, -1),
Point2(1, -1),
};
CanvasItemEditor *canvas_item_editor = nullptr;
CollisionShape2D *node = nullptr;
Vector<Point2> handles;
int shape_type = -1;
int edit_handle = -1;
bool pressed = false;
real_t grab_threshold = 8;
Variant original;
Transform2D original_transform;
Vector2 original_point;
Point2 last_point;
Vector2 original_mouse_pos;
Ref<Shape2D> current_shape;
Variant get_handle_value(int idx) const;
void set_handle(int idx, Point2 &p_point);
void commit_handle(int idx, Variant &p_org);
void _shape_changed();
protected:
void _notification(int p_what);
void _node_removed(Node *p_node);
public:
bool forward_canvas_gui_input(const Ref<InputEvent> &p_event);
void forward_canvas_draw_over_viewport(Control *p_overlay);
void edit(Node *p_node);
CollisionShape2DEditor();
};
class CollisionShape2DEditorPlugin : public EditorPlugin {
GDCLASS(CollisionShape2DEditorPlugin, EditorPlugin);
CollisionShape2DEditor *collision_shape_2d_editor = nullptr;
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_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;
virtual void make_visible(bool visible) override;
CollisionShape2DEditorPlugin();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,197 @@
/**************************************************************************/
/* polygon_2d_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/2d/abstract_polygon_2d_editor.h"
#include "scene/2d/polygon_2d.h"
class AcceptDialog;
class ButtonGroup;
class EditorZoomWidget;
class HScrollBar;
class HSlider;
class Label;
class MenuButton;
class Panel;
class ScrollContainer;
class SpinBox;
class TextureRect;
class ViewPanner;
class VScrollBar;
class Polygon2DEditor : public AbstractPolygon2DEditor {
GDCLASS(Polygon2DEditor, AbstractPolygon2DEditor);
enum {
MENU_POLYGON_TO_UV,
MENU_UV_TO_POLYGON,
MENU_UV_CLEAR,
MENU_GRID_SETTINGS,
};
enum Mode {
MODE_POINTS,
MODE_POLYGONS,
MODE_UV,
MODE_BONES,
MODE_MAX
};
enum Action {
ACTION_CREATE,
ACTION_CREATE_INTERNAL,
ACTION_REMOVE_INTERNAL,
ACTION_EDIT_POINT,
ACTION_MOVE,
ACTION_ROTATE,
ACTION_SCALE,
ACTION_ADD_POLYGON,
ACTION_REMOVE_POLYGON,
ACTION_PAINT_WEIGHT,
ACTION_CLEAR_WEIGHT,
ACTION_MAX
};
Polygon2D *node = nullptr;
Polygon2D *previous_node = nullptr;
Button *dock_button = nullptr;
VBoxContainer *polygon_edit = nullptr;
Mode current_mode = MODE_MAX; // Uninitialized.
Button *mode_buttons[MODE_MAX];
Action selected_action = ACTION_CREATE;
Button *action_buttons[ACTION_MAX];
Button *b_snap_enable = nullptr;
Button *b_snap_grid = nullptr;
MenuButton *edit_menu = nullptr;
Control *canvas = nullptr;
Panel *canvas_background = nullptr;
Polygon2D *preview_polygon = nullptr;
EditorZoomWidget *zoom_widget = nullptr;
HScrollBar *hscroll = nullptr;
VScrollBar *vscroll = nullptr;
bool center_view_on_draw = false;
Ref<ViewPanner> panner;
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);
Vector2 draw_offset;
real_t draw_zoom = 1.0;
VBoxContainer *bone_scroll_main_vb = nullptr;
ScrollContainer *bone_scroll = nullptr;
VBoxContainer *bone_scroll_vb = nullptr;
Button *sync_bones = nullptr;
HSlider *bone_paint_strength = nullptr;
SpinBox *bone_paint_radius = nullptr;
Label *bone_paint_radius_label = nullptr;
bool bone_painting = false;
int bone_painting_bone = 0;
Vector<float> prev_weights;
Vector2 bone_paint_pos;
AcceptDialog *grid_settings = nullptr;
void _sync_bones();
void _update_bone_list();
Vector<Vector2> editing_points;
Vector<Vector2> previous_uv;
Vector<Vector2> previous_polygon;
Vector<Color> previous_colors;
int previous_internal_vertices = 0;
Array previous_bones;
Array previous_polygons;
Vector2 create_to;
int point_drag_index = -1;
bool is_dragging = false;
bool is_creating = false;
Vector<int> polygon_create;
Action current_action = ACTION_CREATE;
Vector2 drag_from;
AcceptDialog *error = nullptr;
bool use_snap = false;
bool snap_show_grid = false;
Vector2 snap_offset;
Vector2 snap_step;
void _edit_menu_option(int p_option);
void _cancel_editing();
void _update_polygon_editing_state();
void _update_available_modes();
void _center_view();
void _update_zoom_and_pan(bool p_zoom_at_center);
void _canvas_input(const Ref<InputEvent> &p_input);
void _center_view_on_draw(bool p_enabled = true);
void _canvas_draw();
void _set_action(int p_mode);
void _set_use_snap(bool p_use);
void _set_show_grid(bool p_show);
void _set_snap_off_x(real_t p_val);
void _set_snap_off_y(real_t p_val);
void _set_snap_step_x(real_t p_val);
void _set_snap_step_y(real_t p_val);
void _select_mode(int p_mode);
void _bone_paint_selected(int p_index);
int _get_polygon_count() const override;
protected:
virtual Node2D *_get_node() const override;
virtual void _set_node(Node *p_polygon) override;
virtual Vector2 _get_offset(int p_idx) const override;
virtual bool _has_uv() const override { return true; }
virtual void _commit_action() override;
void _notification(int p_what);
static void _bind_methods();
Vector2 snap_point(Vector2 p_target) const;
public:
Polygon2DEditor();
};
class Polygon2DEditorPlugin : public AbstractPolygon2DEditorPlugin {
GDCLASS(Polygon2DEditorPlugin, AbstractPolygon2DEditorPlugin);
public:
Polygon2DEditorPlugin();
};

View File

@@ -0,0 +1,137 @@
/**************************************************************************/
/* skeleton_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 "skeleton_2d_editor_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/scene/canvas_item_editor_plugin.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/menu_button.h"
void Skeleton2DEditor::_node_removed(Node *p_node) {
if (p_node == node) {
node = nullptr;
options->hide();
}
}
void Skeleton2DEditor::edit(Skeleton2D *p_sprite) {
node = p_sprite;
}
void Skeleton2DEditor::_menu_option(int p_option) {
if (!node) {
return;
}
switch (p_option) {
case MENU_OPTION_SET_REST: {
if (node->get_bone_count() == 0) {
err_dialog->set_text(TTR("This skeleton has no bones, create some children Bone2D nodes."));
err_dialog->popup_centered();
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Set Rest Pose to Bones"));
for (int i = 0; i < node->get_bone_count(); i++) {
Bone2D *bone = node->get_bone(i);
ur->add_do_method(bone, "set_transform", bone->get_rest());
ur->add_undo_method(bone, "set_transform", bone->get_transform());
}
ur->commit_action();
} break;
case MENU_OPTION_MAKE_REST: {
if (node->get_bone_count() == 0) {
err_dialog->set_text(TTR("This skeleton has no bones, create some children Bone2D nodes."));
err_dialog->popup_centered();
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Create Rest Pose from Bones"));
for (int i = 0; i < node->get_bone_count(); i++) {
Bone2D *bone = node->get_bone(i);
ur->add_do_method(bone, "set_rest", bone->get_transform());
ur->add_undo_method(bone, "set_rest", bone->get_rest());
}
ur->commit_action();
} break;
}
}
Skeleton2DEditor::Skeleton2DEditor() {
options = memnew(MenuButton);
CanvasItemEditor::get_singleton()->add_control_to_menu_panel(options);
options->set_text(TTR("Skeleton2D"));
options->set_button_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Skeleton2D"), EditorStringName(EditorIcons)));
options->set_flat(false);
options->set_theme_type_variation("FlatMenuButton");
options->get_popup()->add_item(TTR("Reset to Rest Pose"), MENU_OPTION_SET_REST);
options->get_popup()->add_separator();
// Use the "Overwrite" word to highlight that this is a destructive operation.
options->get_popup()->add_item(TTR("Overwrite Rest Pose"), MENU_OPTION_MAKE_REST);
options->set_switch_on_hover(true);
options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &Skeleton2DEditor::_menu_option));
err_dialog = memnew(AcceptDialog);
add_child(err_dialog);
}
void Skeleton2DEditorPlugin::edit(Object *p_object) {
sprite_editor->edit(Object::cast_to<Skeleton2D>(p_object));
}
bool Skeleton2DEditorPlugin::handles(Object *p_object) const {
return p_object->is_class("Skeleton2D");
}
void Skeleton2DEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
sprite_editor->options->show();
} else {
sprite_editor->options->hide();
sprite_editor->edit(nullptr);
}
}
Skeleton2DEditorPlugin::Skeleton2DEditorPlugin() {
sprite_editor = memnew(Skeleton2DEditor);
EditorNode::get_singleton()->get_gui_base()->add_child(sprite_editor);
make_visible(false);
//sprite_editor->options->hide();
}

View File

@@ -0,0 +1,78 @@
/**************************************************************************/
/* skeleton_2d_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/plugins/editor_plugin.h"
#include "scene/2d/skeleton_2d.h"
class AcceptDialog;
class MenuButton;
class Skeleton2DEditor : public Control {
GDCLASS(Skeleton2DEditor, Control);
enum Menu {
MENU_OPTION_SET_REST,
MENU_OPTION_MAKE_REST,
};
Skeleton2D *node = nullptr;
MenuButton *options = nullptr;
AcceptDialog *err_dialog = nullptr;
void _menu_option(int p_option);
//void _create_uv_lines();
friend class Skeleton2DEditorPlugin;
protected:
void _node_removed(Node *p_node);
public:
void edit(Skeleton2D *p_sprite);
Skeleton2DEditor();
};
class Skeleton2DEditorPlugin : public EditorPlugin {
GDCLASS(Skeleton2DEditorPlugin, EditorPlugin);
Skeleton2DEditor *sprite_editor = nullptr;
public:
virtual String get_plugin_name() const override { return "Skeleton2D"; }
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;
Skeleton2DEditorPlugin();
};

View File

@@ -0,0 +1,739 @@
/**************************************************************************/
/* sprite_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 "sprite_2d_editor_plugin.h"
#include "core/math/geometry_2d.h"
#include "editor/docks/scene_tree_dock.h"
#include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_zoom_widget.h"
#include "editor/scene/canvas_item_editor_plugin.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/2d/light_occluder_2d.h"
#include "scene/2d/mesh_instance_2d.h"
#include "scene/2d/physics/collision_polygon_2d.h"
#include "scene/2d/polygon_2d.h"
#include "scene/gui/box_container.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/panel.h"
#include "scene/gui/view_panner.h"
#include "thirdparty/clipper2/include/clipper2/clipper.h"
#define PRECISION 1
void Sprite2DEditor::_node_removed(Node *p_node) {
if (p_node == node) {
node = nullptr;
options->hide();
}
}
Vector<Vector2> expand(const Vector<Vector2> &points, const Rect2i &rect, float epsilon = 2.0) {
int size = points.size();
ERR_FAIL_COND_V(size < 2, Vector<Vector2>());
Clipper2Lib::PathD subj(points.size());
for (int i = 0; i < points.size(); i++) {
subj[i] = Clipper2Lib::PointD(points[i].x, points[i].y);
}
Clipper2Lib::PathsD solution = Clipper2Lib::InflatePaths({ subj }, epsilon, Clipper2Lib::JoinType::Miter, Clipper2Lib::EndType::Polygon, 2.0, PRECISION, 0.0);
// Here the miter_limit = 2.0 and arc_tolerance = 0.0 are Clipper2 defaults,
// and PRECISION is used to scale points up internally, to attain the desired precision.
ERR_FAIL_COND_V(solution.size() == 0, points);
// Clamp into the specified rect.
Clipper2Lib::RectD clamp(rect.position.x,
rect.position.y,
rect.position.x + rect.size.width,
rect.position.y + rect.size.height);
Clipper2Lib::PathsD out = Clipper2Lib::RectClip(clamp, solution[0], PRECISION);
// Here PRECISION is used to scale points up internally, to attain the desired precision.
ERR_FAIL_COND_V(out.size() == 0, points);
const Clipper2Lib::PathD &p2 = out[0];
Vector<Vector2> outPoints;
int lasti = p2.size() - 1;
Vector2 prev = Vector2(p2[lasti].x, p2[lasti].y);
for (uint64_t i = 0; i < p2.size(); i++) {
Vector2 cur = Vector2(p2[i].x, p2[i].y);
if (cur.distance_to(prev) > 0.5) {
outPoints.push_back(cur);
prev = cur;
}
}
return outPoints;
}
void Sprite2DEditor::_menu_option(int p_option) {
if (!node) {
return;
}
selected_menu_item = (Menu)p_option;
switch (p_option) {
case MENU_OPTION_CONVERT_TO_MESH_2D: {
debug_uv_dialog->set_ok_button_text(TTR("Create MeshInstance2D"));
debug_uv_dialog->set_title(TTR("MeshInstance2D Preview"));
_popup_debug_uv_dialog();
} break;
case MENU_OPTION_CONVERT_TO_POLYGON_2D: {
debug_uv_dialog->set_ok_button_text(TTR("Create Polygon2D"));
debug_uv_dialog->set_title(TTR("Polygon2D Preview"));
_popup_debug_uv_dialog();
} break;
case MENU_OPTION_CREATE_COLLISION_POLY_2D: {
debug_uv_dialog->set_ok_button_text(TTR("Create CollisionPolygon2D"));
debug_uv_dialog->set_title(TTR("CollisionPolygon2D Preview"));
_popup_debug_uv_dialog();
} break;
case MENU_OPTION_CREATE_LIGHT_OCCLUDER_2D: {
debug_uv_dialog->set_ok_button_text(TTR("Create LightOccluder2D"));
debug_uv_dialog->set_title(TTR("LightOccluder2D Preview"));
_popup_debug_uv_dialog();
} break;
}
}
void Sprite2DEditor::_popup_debug_uv_dialog() {
String error_message;
if (node->get_owner() != get_tree()->get_edited_scene_root() && node != get_tree()->get_edited_scene_root()) {
error_message = TTR("Can't convert a sprite from a foreign scene.");
}
Ref<Texture2D> texture = node->get_texture();
if (texture.is_null()) {
error_message = TTR("Can't convert an empty sprite to mesh.");
}
if (!error_message.is_empty()) {
err_dialog->set_text(error_message);
err_dialog->popup_centered();
return;
}
_update_mesh_data();
debug_uv_dialog->popup_centered();
get_tree()->connect("process_frame", callable_mp(this, &Sprite2DEditor::_center_view), CONNECT_ONE_SHOT);
debug_uv->set_texture_filter(node->get_texture_filter_in_tree());
debug_uv->queue_redraw();
}
void Sprite2DEditor::_update_mesh_data() {
ERR_FAIL_NULL(node);
Ref<Texture2D> texture = node->get_texture();
ERR_FAIL_COND(texture.is_null());
Ref<Image> image = texture->get_image();
ERR_FAIL_COND(image.is_null());
if (image->is_compressed()) {
image->decompress();
}
Rect2 rect = node->is_region_enabled() ? node->get_region_rect() : Rect2(Point2(), image->get_size());
rect.size /= Vector2(node->get_hframes(), node->get_vframes());
rect.position += node->get_frame_coords() * rect.size;
Ref<BitMap> bm;
bm.instantiate();
bm->create_from_image_alpha(image);
int shrink = shrink_pixels->get_value();
if (shrink > 0) {
bm->shrink_mask(shrink, rect);
}
int grow = grow_pixels->get_value();
if (grow > 0) {
bm->grow_mask(grow, rect);
}
float epsilon = simplification->get_value();
Vector<Vector<Vector2>> lines = bm->clip_opaque_to_polygons(rect, epsilon);
uv_lines.clear();
computed_vertices.clear();
computed_uv.clear();
computed_indices.clear();
Size2 img_size = image->get_size();
for (int i = 0; i < lines.size(); i++) {
lines.write[i] = expand(lines[i], rect, epsilon);
}
if (selected_menu_item == MENU_OPTION_CONVERT_TO_MESH_2D) {
for (int j = 0; j < lines.size(); j++) {
int index_ofs = computed_vertices.size();
for (int i = 0; i < lines[j].size(); i++) {
Vector2 vtx = lines[j][i];
computed_uv.push_back((vtx + rect.position) / img_size);
if (node->is_flipped_h()) {
vtx.x = rect.size.x - vtx.x;
}
if (node->is_flipped_v()) {
vtx.y = rect.size.y - vtx.y;
}
vtx += node->get_offset();
if (node->is_centered()) {
vtx -= rect.size / 2.0;
}
computed_vertices.push_back(vtx);
}
Vector<int> poly = Geometry2D::triangulate_polygon(lines[j]);
for (int i = 0; i < poly.size(); i += 3) {
for (int k = 0; k < 3; k++) {
int idx = i + k;
int idxn = i + (k + 1) % 3;
uv_lines.push_back(lines[j][poly[idx]] + rect.position);
uv_lines.push_back(lines[j][poly[idxn]] + rect.position);
computed_indices.push_back(poly[idx] + index_ofs);
}
}
}
}
outline_lines.clear();
computed_outline_lines.clear();
if (selected_menu_item == MENU_OPTION_CONVERT_TO_POLYGON_2D || selected_menu_item == MENU_OPTION_CREATE_COLLISION_POLY_2D || selected_menu_item == MENU_OPTION_CREATE_LIGHT_OCCLUDER_2D) {
outline_lines.resize(lines.size());
computed_outline_lines.resize(lines.size());
for (int pi = 0; pi < lines.size(); pi++) {
Vector<Vector2> ol;
Vector<Vector2> col;
ol.resize(lines[pi].size());
col.resize(lines[pi].size());
for (int i = 0; i < lines[pi].size(); i++) {
Vector2 vtx = lines[pi][i];
ol.write[i] = vtx + rect.position;
if (node->is_flipped_h()) {
vtx.x = rect.size.x - vtx.x;
}
if (node->is_flipped_v()) {
vtx.y = rect.size.y - vtx.y;
}
// Don't bake offset to Polygon2D which has offset property.
if (selected_menu_item != MENU_OPTION_CONVERT_TO_POLYGON_2D) {
vtx += node->get_offset();
}
if (node->is_centered()) {
vtx -= rect.size / 2.0;
}
col.write[i] = vtx;
}
outline_lines.write[pi] = ol;
computed_outline_lines.write[pi] = col;
}
}
debug_uv->queue_redraw();
}
void Sprite2DEditor::_create_node() {
switch (selected_menu_item) {
case MENU_OPTION_CONVERT_TO_MESH_2D: {
_convert_to_mesh_2d_node();
} break;
case MENU_OPTION_CONVERT_TO_POLYGON_2D: {
_convert_to_polygon_2d_node();
} break;
case MENU_OPTION_CREATE_COLLISION_POLY_2D: {
_create_collision_polygon_2d_node();
} break;
case MENU_OPTION_CREATE_LIGHT_OCCLUDER_2D: {
_create_light_occluder_2d_node();
} break;
}
}
void Sprite2DEditor::_convert_to_mesh_2d_node() {
if (computed_vertices.size() < 3) {
err_dialog->set_text(TTR("Invalid geometry, can't replace by mesh."));
err_dialog->popup_centered();
return;
}
Ref<ArrayMesh> mesh;
mesh.instantiate();
Array a;
a.resize(Mesh::ARRAY_MAX);
a[Mesh::ARRAY_VERTEX] = computed_vertices;
a[Mesh::ARRAY_TEX_UV] = computed_uv;
a[Mesh::ARRAY_INDEX] = computed_indices;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES);
MeshInstance2D *mesh_instance = memnew(MeshInstance2D);
mesh_instance->set_mesh(mesh);
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Convert to MeshInstance2D"), UndoRedo::MERGE_DISABLE, node);
SceneTreeDock::get_singleton()->replace_node(node, mesh_instance);
ur->commit_action(false);
}
void Sprite2DEditor::_convert_to_polygon_2d_node() {
if (computed_outline_lines.is_empty()) {
err_dialog->set_text(TTR("Invalid geometry, can't create polygon."));
err_dialog->popup_centered();
return;
}
Polygon2D *polygon_2d_instance = memnew(Polygon2D);
int total_point_count = 0;
for (int i = 0; i < computed_outline_lines.size(); i++) {
total_point_count += computed_outline_lines[i].size();
}
PackedVector2Array polygon;
polygon.resize(total_point_count);
Vector2 *polygon_write = polygon.ptrw();
PackedVector2Array uvs;
uvs.resize(total_point_count);
Vector2 *uvs_write = uvs.ptrw();
int current_point_index = 0;
Array polys;
polys.resize(computed_outline_lines.size());
for (int i = 0; i < computed_outline_lines.size(); i++) {
Vector<Vector2> outline = computed_outline_lines[i];
Vector<Vector2> uv_outline = outline_lines[i];
PackedInt32Array pia;
pia.resize(outline.size());
int *pia_write = pia.ptrw();
for (int pi = 0; pi < outline.size(); pi++) {
polygon_write[current_point_index] = outline[pi];
uvs_write[current_point_index] = uv_outline[pi];
pia_write[pi] = current_point_index;
current_point_index++;
}
polys[i] = pia;
}
polygon_2d_instance->set_uv(uvs);
polygon_2d_instance->set_polygon(polygon);
polygon_2d_instance->set_polygons(polys);
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Convert to Polygon2D"), UndoRedo::MERGE_DISABLE, node);
SceneTreeDock::get_singleton()->replace_node(node, polygon_2d_instance);
ur->commit_action(false);
}
void Sprite2DEditor::_create_collision_polygon_2d_node() {
if (computed_outline_lines.is_empty()) {
err_dialog->set_text(TTR("Invalid geometry, can't create collision polygon."));
err_dialog->popup_centered();
return;
}
for (int i = 0; i < computed_outline_lines.size(); i++) {
Vector<Vector2> outline = computed_outline_lines[i];
CollisionPolygon2D *collision_polygon_2d_instance = memnew(CollisionPolygon2D);
collision_polygon_2d_instance->set_polygon(outline);
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Create CollisionPolygon2D Sibling"), UndoRedo::MERGE_DISABLE, node);
ur->add_do_method(this, "_add_as_sibling_or_child", node, collision_polygon_2d_instance);
ur->add_do_reference(collision_polygon_2d_instance);
ur->add_undo_method(node != get_tree()->get_edited_scene_root() ? node->get_parent() : get_tree()->get_edited_scene_root(), "remove_child", collision_polygon_2d_instance);
ur->commit_action();
}
}
void Sprite2DEditor::_create_light_occluder_2d_node() {
if (computed_outline_lines.is_empty()) {
err_dialog->set_text(TTR("Invalid geometry, can't create light occluder."));
err_dialog->popup_centered();
return;
}
for (int i = 0; i < computed_outline_lines.size(); i++) {
Vector<Vector2> outline = computed_outline_lines[i];
Ref<OccluderPolygon2D> polygon;
polygon.instantiate();
PackedVector2Array a;
a.resize(outline.size());
Vector2 *aw = a.ptrw();
for (int io = 0; io < outline.size(); io++) {
aw[io] = outline[io];
}
polygon->set_polygon(a);
LightOccluder2D *light_occluder_2d_instance = memnew(LightOccluder2D);
light_occluder_2d_instance->set_occluder_polygon(polygon);
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Create LightOccluder2D Sibling"), UndoRedo::MERGE_DISABLE, node);
ur->add_do_method(this, "_add_as_sibling_or_child", node, light_occluder_2d_instance);
ur->add_do_reference(light_occluder_2d_instance);
ur->add_undo_method(node != get_tree()->get_edited_scene_root() ? node->get_parent() : get_tree()->get_edited_scene_root(), "remove_child", light_occluder_2d_instance);
ur->commit_action();
}
}
void Sprite2DEditor::_add_as_sibling_or_child(Node *p_own_node, Node *p_new_node) {
// Can't make sibling if own node is scene root
if (p_own_node != get_tree()->get_edited_scene_root()) {
p_own_node->get_parent()->add_child(p_new_node, true);
Object::cast_to<Node2D>(p_new_node)->set_transform(Object::cast_to<Node2D>(p_own_node)->get_transform());
} else {
p_own_node->add_child(p_new_node, true);
}
p_new_node->set_owner(get_tree()->get_edited_scene_root());
}
void Sprite2DEditor::_sync_sprite_resize_mode() {
if (node != nullptr) {
node->_editor_set_dragging_to_resize_rect(resize_region_rect->is_pressed());
}
}
void Sprite2DEditor::_update_sprite_resize_mode_button() {
if (node == nullptr) {
return;
}
resize_region_rect->set_disabled(!node->is_region_enabled());
resize_region_rect->set_pressed(node->_editor_is_dragging_to_resiz_rect());
resize_region_rect->set_tooltip_text(node->is_region_enabled() ? "" : TTRC("Sprite's region needs to be enabled in the inspector."));
}
void Sprite2DEditor::_debug_uv_input(const Ref<InputEvent> &p_input) {
if (panner->gui_input(p_input, debug_uv->get_global_rect())) {
accept_event();
}
}
void Sprite2DEditor::_debug_uv_draw() {
debug_uv->draw_set_transform(-draw_offset * draw_zoom, 0, Vector2(draw_zoom, draw_zoom));
Ref<Texture2D> tex = node->get_texture();
ERR_FAIL_COND(tex.is_null());
debug_uv->draw_texture(tex, Point2());
Color color = Color(1.0, 0.8, 0.7);
if (selected_menu_item == MENU_OPTION_CONVERT_TO_MESH_2D && uv_lines.size() > 0) {
debug_uv->draw_multiline(uv_lines, color);
} else if ((selected_menu_item == MENU_OPTION_CONVERT_TO_POLYGON_2D || selected_menu_item == MENU_OPTION_CREATE_COLLISION_POLY_2D || selected_menu_item == MENU_OPTION_CREATE_LIGHT_OCCLUDER_2D) && outline_lines.size() > 0) {
for (int i = 0; i < outline_lines.size(); i++) {
Vector<Vector2> outline = outline_lines[i];
debug_uv->draw_polyline(outline, color);
debug_uv->draw_line(outline[0], outline[outline.size() - 1], color);
}
}
}
void Sprite2DEditor::_center_view() {
Ref<Texture2D> tex = node->get_texture();
ERR_FAIL_COND(tex.is_null());
Vector2 zoom_factor = (debug_uv->get_size() - Vector2(1, 1) * 50 * EDSCALE) / tex->get_size();
zoom_widget->set_zoom(MIN(zoom_factor.x, zoom_factor.y));
// Recalculate scroll limits.
_update_zoom_and_pan(false);
Vector2 offset = (tex->get_size() - debug_uv->get_size() / zoom_widget->get_zoom()) / 2;
h_scroll->set_value_no_signal(offset.x);
v_scroll->set_value_no_signal(offset.y);
_update_zoom_and_pan(false);
}
void Sprite2DEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
h_scroll->set_value_no_signal(h_scroll->get_value() - p_scroll_vec.x / draw_zoom);
v_scroll->set_value_no_signal(v_scroll->get_value() - p_scroll_vec.y / draw_zoom);
_update_zoom_and_pan(false);
}
void Sprite2DEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
const real_t prev_zoom = draw_zoom;
zoom_widget->set_zoom(draw_zoom * p_zoom_factor);
draw_offset += p_origin / prev_zoom - p_origin / zoom_widget->get_zoom();
h_scroll->set_value_no_signal(draw_offset.x);
v_scroll->set_value_no_signal(draw_offset.y);
_update_zoom_and_pan(false);
}
void Sprite2DEditor::_update_zoom_and_pan(bool p_zoom_at_center) {
real_t previous_zoom = draw_zoom;
draw_zoom = zoom_widget->get_zoom();
draw_offset = Vector2(h_scroll->get_value(), v_scroll->get_value());
if (p_zoom_at_center) {
Vector2 center = debug_uv->get_size() / 2;
draw_offset += center / previous_zoom - center / draw_zoom;
}
Ref<Texture2D> tex = node->get_texture();
ERR_FAIL_COND(tex.is_null());
Point2 min_corner;
Point2 max_corner = tex->get_size();
Size2 page_size = debug_uv->get_size() / draw_zoom;
Vector2 margin = Vector2(50, 50) * EDSCALE / draw_zoom;
min_corner -= page_size - margin;
max_corner += page_size - margin;
h_scroll->set_block_signals(true);
h_scroll->set_min(min_corner.x);
h_scroll->set_max(max_corner.x);
h_scroll->set_page(page_size.x);
h_scroll->set_value(draw_offset.x);
h_scroll->set_block_signals(false);
v_scroll->set_block_signals(true);
v_scroll->set_min(min_corner.y);
v_scroll->set_max(max_corner.y);
v_scroll->set_page(page_size.y);
v_scroll->set_value(draw_offset.y);
v_scroll->set_block_signals(false);
debug_uv->queue_redraw();
}
void Sprite2DEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
v_scroll->set_anchors_and_offsets_preset(Control::PRESET_RIGHT_WIDE);
h_scroll->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_WIDE);
// Avoid scrollbar overlapping.
Size2 hmin = h_scroll->get_combined_minimum_size();
Size2 vmin = v_scroll->get_combined_minimum_size();
h_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, -vmin.width);
v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -hmin.height);
[[fallthrough]];
}
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
if (!EditorSettings::get_singleton()->check_changed_settings_in_group("editors/panning")) {
break;
}
[[fallthrough]];
}
case NOTIFICATION_ENTER_TREE: {
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")));
panner->setup_warped_panning(debug_uv_dialog, EDITOR_GET("editors/panning/warped_mouse_panning"));
} break;
case NOTIFICATION_THEME_CHANGED: {
options->set_button_icon(get_editor_theme_icon(SNAME("Sprite2D")));
options->get_popup()->set_item_icon(MENU_OPTION_CONVERT_TO_MESH_2D, get_editor_theme_icon(SNAME("MeshInstance2D")));
options->get_popup()->set_item_icon(MENU_OPTION_CONVERT_TO_POLYGON_2D, get_editor_theme_icon(SNAME("Polygon2D")));
options->get_popup()->set_item_icon(MENU_OPTION_CREATE_COLLISION_POLY_2D, get_editor_theme_icon(SNAME("CollisionPolygon2D")));
options->get_popup()->set_item_icon(MENU_OPTION_CREATE_LIGHT_OCCLUDER_2D, get_editor_theme_icon(SNAME("LightOccluder2D")));
resize_region_rect->set_button_icon(get_editor_theme_icon(SNAME("KeepAspect")));
} break;
}
}
void Sprite2DEditor::_bind_methods() {
ClassDB::bind_method("_add_as_sibling_or_child", &Sprite2DEditor::_add_as_sibling_or_child);
}
void Sprite2DEditor::edit(Sprite2D *p_sprite) {
Callable callback_update_button = callable_mp(this, &Sprite2DEditor::_update_sprite_resize_mode_button);
StringName signal_name = SNAME("_editor_region_rect_enabled");
if (node != nullptr && node->is_connected(signal_name, callback_update_button)) {
node->disconnect(signal_name, callback_update_button);
}
node = p_sprite;
if (node != nullptr && !node->is_connected(signal_name, callback_update_button)) {
node->connect(signal_name, callback_update_button);
}
_update_sprite_resize_mode_button();
}
Sprite2DEditor::Sprite2DEditor() {
// Top HBoxContainer definition
top_hb = memnew(HBoxContainer);
CanvasItemEditor::get_singleton()->add_control_to_menu_panel(top_hb);
// Options definition
options = memnew(MenuButton);
top_hb->add_child(options);
options->set_text(TTR("Sprite2D"));
options->set_flat(false);
options->set_theme_type_variation("FlatMenuButton");
options->get_popup()->add_item(TTR("Convert to MeshInstance2D"), MENU_OPTION_CONVERT_TO_MESH_2D);
options->get_popup()->add_item(TTR("Convert to Polygon2D"), MENU_OPTION_CONVERT_TO_POLYGON_2D);
options->get_popup()->add_item(TTR("Create CollisionPolygon2D Sibling"), MENU_OPTION_CREATE_COLLISION_POLY_2D);
options->get_popup()->add_item(TTR("Create LightOccluder2D Sibling"), MENU_OPTION_CREATE_LIGHT_OCCLUDER_2D);
options->set_switch_on_hover(true);
options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &Sprite2DEditor::_menu_option));
// Resize region rect definition
resize_region_rect = memnew(Button);
resize_region_rect->set_theme_type_variation("FlatMenuButton");
resize_region_rect->set_toggle_mode(true);
resize_region_rect->set_shortcut(ED_SHORTCUT("canvas_item_editor/resize_region_rect", TTRC("Drag to Resize Region Rect"), KeyModifierMask::CMD_OR_CTRL | Key::R));
resize_region_rect->connect(SceneStringName(pressed), callable_mp(this, &Sprite2DEditor::_sync_sprite_resize_mode));
top_hb->add_child(resize_region_rect);
// Other elements definition
err_dialog = memnew(AcceptDialog);
add_child(err_dialog);
debug_uv_dialog = memnew(ConfirmationDialog);
debug_uv_dialog->set_size(Size2(960, 540) * EDSCALE);
VBoxContainer *vb = memnew(VBoxContainer);
debug_uv_dialog->add_child(vb);
debug_uv = memnew(Panel);
debug_uv->connect(SceneStringName(gui_input), callable_mp(this, &Sprite2DEditor::_debug_uv_input));
debug_uv->connect(SceneStringName(draw), callable_mp(this, &Sprite2DEditor::_debug_uv_draw));
debug_uv->set_clip_contents(true);
vb->add_margin_child(TTR("Preview:"), debug_uv, true);
panner.instantiate();
panner->set_callbacks(callable_mp(this, &Sprite2DEditor::_pan_callback), callable_mp(this, &Sprite2DEditor::_zoom_callback));
zoom_widget = memnew(EditorZoomWidget);
debug_uv->add_child(zoom_widget);
zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);
zoom_widget->connect("zoom_changed", callable_mp(this, &Sprite2DEditor::_update_zoom_and_pan).unbind(1).bind(true));
zoom_widget->set_shortcut_context(nullptr);
v_scroll = memnew(VScrollBar);
debug_uv->add_child(v_scroll);
v_scroll->connect(SceneStringName(value_changed), callable_mp(this, &Sprite2DEditor::_update_zoom_and_pan).unbind(1).bind(false));
h_scroll = memnew(HScrollBar);
debug_uv->add_child(h_scroll);
h_scroll->connect(SceneStringName(value_changed), callable_mp(this, &Sprite2DEditor::_update_zoom_and_pan).unbind(1).bind(false));
debug_uv_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Sprite2DEditor::_create_node));
HBoxContainer *hb = memnew(HBoxContainer);
hb->add_child(memnew(Label(TTR("Simplification:"))));
simplification = memnew(SpinBox);
simplification->set_min(0.01);
simplification->set_max(10.00);
simplification->set_step(0.01);
simplification->set_value(2);
simplification->set_accessibility_name(TTRC("Simplification:"));
hb->add_child(simplification);
hb->add_spacer();
hb->add_child(memnew(Label(TTR("Shrink (Pixels):"))));
shrink_pixels = memnew(SpinBox);
shrink_pixels->set_min(0);
shrink_pixels->set_max(10);
shrink_pixels->set_step(1);
shrink_pixels->set_value(0);
shrink_pixels->set_accessibility_name(TTRC("Shrink (Pixels):"));
hb->add_child(shrink_pixels);
hb->add_spacer();
hb->add_child(memnew(Label(TTR("Grow (Pixels):"))));
grow_pixels = memnew(SpinBox);
grow_pixels->set_min(0);
grow_pixels->set_max(10);
grow_pixels->set_step(1);
grow_pixels->set_value(2);
grow_pixels->set_accessibility_name(TTRC("Grow (Pixels):"));
hb->add_child(grow_pixels);
hb->add_spacer();
update_preview = memnew(Button);
update_preview->set_text(TTR("Update Preview"));
update_preview->connect(SceneStringName(pressed), callable_mp(this, &Sprite2DEditor::_update_mesh_data));
hb->add_child(update_preview);
vb->add_margin_child(TTR("Settings:"), hb);
add_child(debug_uv_dialog);
}
void Sprite2DEditorPlugin::edit(Object *p_object) {
sprite_editor->edit(Object::cast_to<Sprite2D>(p_object));
}
bool Sprite2DEditorPlugin::handles(Object *p_object) const {
return p_object->is_class("Sprite2D");
}
void Sprite2DEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
sprite_editor->top_hb->show();
} else {
sprite_editor->top_hb->hide();
sprite_editor->edit(nullptr);
}
}
Sprite2DEditorPlugin::Sprite2DEditorPlugin() {
sprite_editor = memnew(Sprite2DEditor);
EditorNode::get_singleton()->get_gui_base()->add_child(sprite_editor);
make_visible(false);
//sprite_editor->options->hide();
}

View File

@@ -0,0 +1,140 @@
/**************************************************************************/
/* sprite_2d_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/plugins/editor_plugin.h"
#include "scene/2d/sprite_2d.h"
#include "scene/gui/spin_box.h"
class AcceptDialog;
class Button;
class ConfirmationDialog;
class EditorZoomWidget;
class HBoxContainer;
class MenuButton;
class Panel;
class ViewPanner;
class Sprite2DEditor : public Control {
GDCLASS(Sprite2DEditor, Control);
enum Menu {
MENU_OPTION_CONVERT_TO_MESH_2D,
MENU_OPTION_CONVERT_TO_POLYGON_2D,
MENU_OPTION_CREATE_COLLISION_POLY_2D,
MENU_OPTION_CREATE_LIGHT_OCCLUDER_2D
};
HBoxContainer *top_hb = nullptr;
Menu selected_menu_item;
Sprite2D *node = nullptr;
MenuButton *options = nullptr;
Button *resize_region_rect = nullptr;
ConfirmationDialog *outline_dialog = nullptr;
AcceptDialog *err_dialog = nullptr;
ConfirmationDialog *debug_uv_dialog = nullptr;
Panel *debug_uv = nullptr;
Vector<Vector2> uv_lines;
Vector<Vector<Vector2>> outline_lines;
Vector<Vector<Vector2>> computed_outline_lines;
Vector<Vector2> computed_vertices;
Vector<Vector2> computed_uv;
Vector<int> computed_indices;
HScrollBar *h_scroll = nullptr;
VScrollBar *v_scroll = nullptr;
EditorZoomWidget *zoom_widget = nullptr;
Ref<ViewPanner> panner;
Vector2 draw_offset;
real_t draw_zoom = 1.0;
SpinBox *simplification = nullptr;
SpinBox *grow_pixels = nullptr;
SpinBox *shrink_pixels = nullptr;
Button *update_preview = nullptr;
void _menu_option(int p_option);
//void _create_uv_lines();
friend class Sprite2DEditorPlugin;
void _debug_uv_input(const Ref<InputEvent> &p_input);
void _debug_uv_draw();
void _popup_debug_uv_dialog();
void _center_view();
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);
void _update_zoom_and_pan(bool p_zoom_at_center);
void _update_mesh_data();
void _create_node();
void _convert_to_mesh_2d_node();
void _convert_to_polygon_2d_node();
void _create_collision_polygon_2d_node();
void _create_light_occluder_2d_node();
void _add_as_sibling_or_child(Node *p_own_node, Node *p_new_node);
void _sync_sprite_resize_mode();
void _update_sprite_resize_mode_button();
protected:
void _node_removed(Node *p_node);
void _notification(int p_what);
static void _bind_methods();
public:
void edit(Sprite2D *p_sprite);
Sprite2DEditor();
};
class Sprite2DEditorPlugin : public EditorPlugin {
GDCLASS(Sprite2DEditorPlugin, EditorPlugin);
Sprite2DEditor *sprite_editor = nullptr;
Label *dragging_mode_hint = nullptr;
public:
virtual String get_plugin_name() const override { return "Sprite2D"; }
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;
Sprite2DEditorPlugin();
};

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.editor_sources, "*.cpp")

View File

@@ -0,0 +1,369 @@
/**************************************************************************/
/* atlas_merging_dialog.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 "atlas_merging_dialog.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/control.h"
#include "scene/gui/split_container.h"
#include "scene/resources/image_texture.h"
void AtlasMergingDialog::_property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) {
_set(p_property, p_value);
}
void AtlasMergingDialog::_generate_merged(const Vector<Ref<TileSetAtlasSource>> &p_atlas_sources, int p_max_columns) {
merged.instantiate();
merged_mapping.clear();
if (p_atlas_sources.size() >= 2) {
Ref<Image> output_image = Image::create_empty(1, 1, false, Image::FORMAT_RGBA8);
// Compute the new texture region size.
Vector2i new_texture_region_size;
for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {
const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index];
new_texture_region_size = new_texture_region_size.max(atlas_source->get_texture_region_size());
}
// Generate the new texture.
Vector2i atlas_offset;
int line_height = 0;
for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {
const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index];
Ref<Image> input_image = atlas_source->get_texture()->get_image();
if (input_image->get_format() != Image::FORMAT_RGBA8) {
input_image->convert(Image::FORMAT_RGBA8);
}
merged_mapping.push_back(HashMap<Vector2i, Vector2i>());
// Layout the tiles.
Vector2i atlas_size;
for (int tile_index = 0; tile_index < atlas_source->get_tiles_count(); tile_index++) {
Vector2i tile_id = atlas_source->get_tile_id(tile_index);
atlas_size = atlas_size.max(tile_id + atlas_source->get_tile_size_in_atlas(tile_id));
Rect2i new_tile_rect_in_atlas = Rect2i(atlas_offset + tile_id, atlas_source->get_tile_size_in_atlas(tile_id));
// Copy the texture.
for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(tile_id); frame++) {
Rect2i src_rect = atlas_source->get_tile_texture_region(tile_id, frame);
Vector2i new_position = new_tile_rect_in_atlas.position * new_texture_region_size;
if (frame > 0) {
new_position += src_rect.size * Vector2i(frame, 0);
atlas_size.x = MAX(frame + 1, atlas_size.x);
}
Rect2 dst_rect_wide = Rect2i(new_position, new_tile_rect_in_atlas.size * new_texture_region_size);
if (dst_rect_wide.get_end().x > output_image->get_width() || dst_rect_wide.get_end().y > output_image->get_height()) {
output_image->crop(MAX(dst_rect_wide.get_end().x, output_image->get_width()), MAX(dst_rect_wide.get_end().y, output_image->get_height()));
}
output_image->blit_rect(input_image, src_rect, dst_rect_wide.get_center() - src_rect.size / 2);
}
// Add to the mapping.
merged_mapping[source_index][tile_id] = new_tile_rect_in_atlas.position;
}
// Compute the atlas offset.
line_height = MAX(atlas_size.y, line_height);
atlas_offset.x += atlas_size.x;
if (atlas_offset.x >= p_max_columns) {
atlas_offset.x = 0;
atlas_offset.y += line_height;
line_height = 0;
}
}
merged->set_name(p_atlas_sources[0]->get_name());
merged->set_texture(ImageTexture::create_from_image(output_image));
merged->set_texture_region_size(new_texture_region_size);
// Copy the tiles to the merged TileSetAtlasSource.
for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {
const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index];
for (KeyValue<Vector2i, Vector2i> tile_mapping : merged_mapping[source_index]) {
// Create tiles and alternatives, then copy their properties.
for (int alternative_index = 0; alternative_index < atlas_source->get_alternative_tiles_count(tile_mapping.key); alternative_index++) {
int alternative_id = atlas_source->get_alternative_tile_id(tile_mapping.key, alternative_index);
int changed_id = -1;
if (alternative_id == 0) {
merged->create_tile(tile_mapping.value, atlas_source->get_tile_size_in_atlas(tile_mapping.key));
int count = atlas_source->get_tile_animation_frames_count(tile_mapping.key);
merged->set_tile_animation_frames_count(tile_mapping.value, count);
for (int i = 0; i < count; i++) {
merged->set_tile_animation_frame_duration(tile_mapping.value, i, atlas_source->get_tile_animation_frame_duration(tile_mapping.key, i));
}
merged->set_tile_animation_speed(tile_mapping.value, atlas_source->get_tile_animation_speed(tile_mapping.key));
merged->set_tile_animation_mode(tile_mapping.value, atlas_source->get_tile_animation_mode(tile_mapping.key));
} else {
changed_id = merged->create_alternative_tile(tile_mapping.value, alternative_index);
}
// Copy the properties.
TileData *src_tile_data = atlas_source->get_tile_data(tile_mapping.key, alternative_id);
List<PropertyInfo> properties;
src_tile_data->get_property_list(&properties);
TileData *dst_tile_data = merged->get_tile_data(tile_mapping.value, changed_id == -1 ? alternative_id : changed_id);
for (PropertyInfo property : properties) {
if (!(property.usage & PROPERTY_USAGE_STORAGE)) {
continue;
}
Variant value = src_tile_data->get(property.name);
Variant default_value = ClassDB::class_get_default_property_value("TileData", property.name);
if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) {
continue;
}
dst_tile_data->set(property.name, value);
}
}
}
}
}
}
void AtlasMergingDialog::_update_texture() {
Vector<int> selected = atlas_merging_atlases_list->get_selected_items();
if (selected.size() >= 2) {
Vector<Ref<TileSetAtlasSource>> to_merge;
for (int i = 0; i < selected.size(); i++) {
int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]);
to_merge.push_back(tile_set->get_source(source_id));
}
_generate_merged(to_merge, next_line_after_column);
preview->set_texture(merged->get_texture());
preview->show();
select_2_atlases_label->hide();
get_ok_button()->set_disabled(false);
merge_button->set_disabled(false);
} else {
_generate_merged(Vector<Ref<TileSetAtlasSource>>(), next_line_after_column);
preview->set_texture(Ref<Texture2D>());
preview->hide();
select_2_atlases_label->show();
get_ok_button()->set_disabled(true);
merge_button->set_disabled(true);
}
}
void AtlasMergingDialog::_merge_confirmed(const String &p_path) {
ERR_FAIL_COND(merged.is_null());
Ref<ImageTexture> output_image_texture = merged->get_texture();
output_image_texture->get_image()->save_png(p_path);
ResourceLoader::import(p_path);
Ref<Texture2D> new_texture_resource = ResourceLoader::load(p_path, "Texture2D");
merged->set_texture(new_texture_resource);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Merge TileSetAtlasSource"));
int next_id = tile_set->get_next_source_id();
undo_redo->add_do_method(*tile_set, "add_source", merged, next_id);
undo_redo->add_undo_method(*tile_set, "remove_source", next_id);
if (delete_original_atlases) {
// Delete originals if needed.
Vector<int> selected = atlas_merging_atlases_list->get_selected_items();
for (int i = 0; i < selected.size(); i++) {
int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]);
Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id);
undo_redo->add_do_method(*tile_set, "remove_source", source_id);
undo_redo->add_undo_method(*tile_set, "add_source", tas, source_id);
// Add the tile proxies.
for (int tile_index = 0; tile_index < tas->get_tiles_count(); tile_index++) {
Vector2i tile_id = tas->get_tile_id(tile_index);
undo_redo->add_do_method(*tile_set, "set_coords_level_tile_proxy", source_id, tile_id, next_id, merged_mapping[i][tile_id]);
if (tile_set->has_coords_level_tile_proxy(source_id, tile_id)) {
Array a = tile_set->get_coords_level_tile_proxy(source_id, tile_id);
undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", a[0], a[1]);
} else {
undo_redo->add_undo_method(*tile_set, "remove_coords_level_tile_proxy", source_id, tile_id);
}
}
}
}
undo_redo->commit_action();
committed_actions_count++;
hide();
}
void AtlasMergingDialog::ok_pressed() {
delete_original_atlases = false;
editor_file_dialog->popup_file_dialog();
}
void AtlasMergingDialog::cancel_pressed() {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
for (int i = 0; i < committed_actions_count; i++) {
undo_redo->undo();
}
committed_actions_count = 0;
}
void AtlasMergingDialog::custom_action(const String &p_action) {
if (p_action == "merge") {
delete_original_atlases = true;
editor_file_dialog->popup_file_dialog();
}
}
bool AtlasMergingDialog::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "next_line_after_column" && p_value.get_type() == Variant::INT) {
next_line_after_column = p_value;
_update_texture();
return true;
}
return false;
}
bool AtlasMergingDialog::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "next_line_after_column") {
r_ret = next_line_after_column;
return true;
}
return false;
}
void AtlasMergingDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
if (is_visible()) {
_update_texture();
}
} break;
}
}
void AtlasMergingDialog::update_tile_set(Ref<TileSet> p_tile_set) {
ERR_FAIL_COND(p_tile_set.is_null());
tile_set = p_tile_set;
atlas_merging_atlases_list->clear();
for (int i = 0; i < p_tile_set->get_source_count(); i++) {
int source_id = p_tile_set->get_source_id(i);
Ref<TileSetAtlasSource> atlas_source = p_tile_set->get_source(source_id);
if (atlas_source.is_valid()) {
Ref<Texture2D> texture = atlas_source->get_texture();
if (texture.is_valid()) {
String item_text = vformat(TTR("%s (ID: %d)"), texture->get_path().get_file(), source_id);
atlas_merging_atlases_list->add_item(item_text, texture);
atlas_merging_atlases_list->set_item_metadata(-1, source_id);
}
}
}
get_ok_button()->set_disabled(true);
merge_button->set_disabled(true);
committed_actions_count = 0;
}
AtlasMergingDialog::AtlasMergingDialog() {
// Atlas merging window.
set_title(TTR("Atlas Merging"));
set_hide_on_ok(false);
// Ok buttons
set_ok_button_text(TTR("Merge (Keep original Atlases)"));
get_ok_button()->set_disabled(true);
merge_button = add_button(TTR("Merge"), true, "merge");
merge_button->set_disabled(true);
HSplitContainer *atlas_merging_h_split_container = memnew(HSplitContainer);
atlas_merging_h_split_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
atlas_merging_h_split_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
add_child(atlas_merging_h_split_container);
// Atlas sources item list.
atlas_merging_atlases_list = memnew(ItemList);
atlas_merging_atlases_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
atlas_merging_atlases_list->set_fixed_icon_size(Size2(60, 60) * EDSCALE);
atlas_merging_atlases_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
atlas_merging_atlases_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
atlas_merging_atlases_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
atlas_merging_atlases_list->set_custom_minimum_size(Size2(100, 200));
atlas_merging_atlases_list->set_select_mode(ItemList::SELECT_MULTI);
atlas_merging_atlases_list->set_theme_type_variation("ItemListSecondary");
atlas_merging_atlases_list->connect("multi_selected", callable_mp(this, &AtlasMergingDialog::_update_texture).unbind(2));
atlas_merging_h_split_container->add_child(atlas_merging_atlases_list);
VBoxContainer *atlas_merging_right_panel = memnew(VBoxContainer);
atlas_merging_right_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL);
atlas_merging_right_panel->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
atlas_merging_h_split_container->add_child(atlas_merging_right_panel);
// Settings.
Label *settings_label = memnew(Label);
settings_label->set_text(TTR("Settings:"));
atlas_merging_right_panel->add_child(settings_label);
columns_editor_property = memnew(EditorPropertyInteger);
columns_editor_property->set_label(TTR("Next Line After Column"));
columns_editor_property->set_object_and_property(this, "next_line_after_column");
columns_editor_property->update_property();
columns_editor_property->connect("property_changed", callable_mp(this, &AtlasMergingDialog::_property_changed));
atlas_merging_right_panel->add_child(columns_editor_property);
// Preview.
Label *preview_label = memnew(Label);
preview_label->set_text(TTR("Preview:"));
atlas_merging_right_panel->add_child(preview_label);
preview = memnew(TextureRect);
preview->set_h_size_flags(Control::SIZE_EXPAND_FILL);
preview->set_v_size_flags(Control::SIZE_EXPAND_FILL);
preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
preview->hide();
preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
atlas_merging_right_panel->add_child(preview);
select_2_atlases_label = memnew(Label);
select_2_atlases_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
select_2_atlases_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
select_2_atlases_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
select_2_atlases_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
select_2_atlases_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
select_2_atlases_label->set_text(TTR("Please select two atlases or more."));
atlas_merging_right_panel->add_child(select_2_atlases_label);
// The file dialog to choose the texture path.
editor_file_dialog = memnew(EditorFileDialog);
editor_file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
editor_file_dialog->add_filter("*.png");
editor_file_dialog->connect("file_selected", callable_mp(this, &AtlasMergingDialog::_merge_confirmed));
add_child(editor_file_dialog);
}

View File

@@ -0,0 +1,84 @@
/**************************************************************************/
/* atlas_merging_dialog.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. */
/**************************************************************************/
#pragma once
#include "editor/inspector/editor_properties.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/item_list.h"
#include "scene/gui/texture_rect.h"
#include "scene/resources/2d/tile_set.h"
class EditorFileDialog;
class EditorPropertyVector2i;
class AtlasMergingDialog : public ConfirmationDialog {
GDCLASS(AtlasMergingDialog, ConfirmationDialog);
private:
int committed_actions_count = 0;
bool delete_original_atlases = true;
Ref<TileSetAtlasSource> merged;
LocalVector<HashMap<Vector2i, Vector2i>> merged_mapping;
Ref<TileSet> tile_set;
// Settings.
int next_line_after_column = 30;
// GUI.
ItemList *atlas_merging_atlases_list = nullptr;
EditorPropertyVector2i *texture_region_size_editor_property = nullptr;
EditorPropertyInteger *columns_editor_property = nullptr;
TextureRect *preview = nullptr;
Label *select_2_atlases_label = nullptr;
EditorFileDialog *editor_file_dialog = nullptr;
Button *merge_button = nullptr;
void _property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing);
void _generate_merged(const Vector<Ref<TileSetAtlasSource>> &p_atlas_sources, int p_max_columns);
void _update_texture();
void _merge_confirmed(const String &p_path);
protected:
virtual void ok_pressed() override;
virtual void cancel_pressed() override;
virtual void custom_action(const String &) override;
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _notification(int p_what);
public:
void update_tile_set(Ref<TileSet> p_tile_set);
AtlasMergingDialog();
};

View File

@@ -0,0 +1,766 @@
/**************************************************************************/
/* tile_atlas_view.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 "tile_atlas_view.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/2d/tile_map_layer.h"
#include "scene/gui/box_container.h"
#include "scene/gui/label.h"
#include "scene/gui/panel.h"
#include "scene/gui/view_panner.h"
void TileAtlasView::gui_input(const Ref<InputEvent> &p_event) {
if (panner->gui_input(p_event, get_global_rect())) {
accept_event();
}
}
void TileAtlasView::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
panning += p_scroll_vec;
_update_zoom_and_panning(true);
emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
}
void TileAtlasView::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
zoom_widget->set_zoom(zoom_widget->get_zoom() * p_zoom_factor);
_update_zoom_and_panning(true, p_origin);
emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
}
Size2i TileAtlasView::_compute_base_tiles_control_size() {
if (tile_set_atlas_source.is_null()) {
return Size2i();
}
// Update the texture.
Vector2i size;
Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
if (texture.is_valid()) {
size = texture->get_size();
}
return size;
}
Size2i TileAtlasView::_compute_alternative_tiles_control_size() {
if (tile_set_atlas_source.is_null()) {
return Size2i();
}
Vector2i size;
for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id);
Vector2i line_size;
Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(tile_id).size;
for (int j = 1; j < alternatives_count; j++) {
int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j);
bool transposed = tile_set_atlas_source->get_tile_data(tile_id, alternative_id)->get_transpose();
line_size.x += transposed ? texture_region_size.y : texture_region_size.x;
line_size.y = MAX(line_size.y, transposed ? texture_region_size.x : texture_region_size.y);
}
size.x = MAX(size.x, line_size.x);
size.y += line_size.y;
}
return size;
}
void TileAtlasView::_update_zoom_and_panning(bool p_zoom_on_mouse_pos, const Vector2 &p_mouse_pos) {
if (tile_set_atlas_source.is_null()) {
return;
}
float zoom = zoom_widget->get_zoom();
// Compute the minimum sizes.
Size2i base_tiles_control_size = _compute_base_tiles_control_size();
base_tiles_root_control->set_custom_minimum_size(Vector2(base_tiles_control_size) * zoom);
Size2i alternative_tiles_control_size = _compute_alternative_tiles_control_size();
alternative_tiles_root_control->set_custom_minimum_size(Vector2(alternative_tiles_control_size) * zoom);
// Set the texture for the base tiles.
Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
// Set the scales.
if (base_tiles_control_size.x > 0 && base_tiles_control_size.y > 0) {
base_tiles_drawing_root->set_scale(Vector2(zoom, zoom));
} else {
base_tiles_drawing_root->set_scale(Vector2(1, 1));
}
if (alternative_tiles_control_size.x > 0 && alternative_tiles_control_size.y > 0) {
alternative_tiles_drawing_root->set_scale(Vector2(zoom, zoom));
} else {
alternative_tiles_drawing_root->set_scale(Vector2(1, 1));
}
// Update the margin container's margins.
const char *constants[] = { "margin_left", "margin_top", "margin_right", "margin_bottom" };
for (int i = 0; i < 4; i++) {
margin_container->add_theme_constant_override(constants[i], margin_container_paddings[i] * zoom);
}
// Update the backgrounds.
background_left->set_size(base_tiles_root_control->get_custom_minimum_size());
background_right->set_size(alternative_tiles_root_control->get_custom_minimum_size());
// Zoom on the position.
if (p_zoom_on_mouse_pos) {
Vector2 relative_mpos = p_mouse_pos - get_size() / 2;
panning = (panning - relative_mpos) * zoom / previous_zoom + relative_mpos;
} else {
// Center of panel.
panning = panning * zoom / previous_zoom;
}
button_center_view->set_disabled(panning.is_zero_approx());
previous_zoom = zoom;
center_container->set_begin(panning - center_container->get_minimum_size() / 2);
center_container->set_size(center_container->get_minimum_size());
}
void TileAtlasView::_zoom_widget_changed() {
_update_zoom_and_panning();
emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
}
void TileAtlasView::_center_view() {
panning = Vector2();
button_center_view->set_disabled(true);
_update_zoom_and_panning();
emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
}
void TileAtlasView::_base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event) {
if (tile_set_atlas_source.is_null()) {
return;
}
base_tiles_root_control->set_tooltip_text("");
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
Transform2D xform = base_tiles_drawing_root->get_transform().affine_inverse();
Vector2i coords = get_atlas_tile_coords_at_pos(xform.xform(mm->get_position()));
if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
coords = tile_set_atlas_source->get_tile_at_coords(coords);
if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
base_tiles_root_control->set_tooltip_text(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: 0"), source_id, coords));
}
}
}
}
void TileAtlasView::_draw_base_tiles() {
if (tile_set.is_null() || tile_set_atlas_source.is_null()) {
return;
}
Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
if (texture.is_valid()) {
Vector2i margins = tile_set_atlas_source->get_margins();
Vector2i separation = tile_set_atlas_source->get_separation();
Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
// Draw the texture where there is no tile.
for (int x = 0; x < grid_size.x; x++) {
for (int y = 0; y < grid_size.y; y++) {
Vector2i coords = Vector2i(x, y);
if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
Rect2i rect = Rect2i((texture_region_size + separation) * coords + margins, texture_region_size + separation);
rect = rect.intersection(Rect2i(Vector2(), texture->get_size()));
if (rect.size.x > 0 && rect.size.y > 0) {
base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
}
}
}
}
// Draw dark overlay after for performance reasons.
for (int x = 0; x < grid_size.x; x++) {
for (int y = 0; y < grid_size.y; y++) {
Vector2i coords = Vector2i(x, y);
if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
Rect2i rect = Rect2i((texture_region_size + separation) * coords + margins, texture_region_size + separation);
rect = rect.intersection(Rect2i(Vector2(), texture->get_size()));
if (rect.size.x > 0 && rect.size.y > 0) {
base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
}
}
}
}
// Draw the texture around the grid.
Rect2i rect;
// Top.
rect.position = Vector2i();
rect.set_end(Vector2i(texture->get_size().x, margins.y));
base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
// Bottom
int bottom_border = margins.y + (grid_size.y * (texture_region_size.y + separation.y));
if (bottom_border < texture->get_size().y) {
rect.position = Vector2i(0, bottom_border);
rect.set_end(texture->get_size());
base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
}
// Left
rect.position = Vector2i(0, margins.y);
rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * (texture_region_size.y + separation.y))));
base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
// Right.
int right_border = margins.x + (grid_size.x * (texture_region_size.x + separation.x));
if (right_border < texture->get_size().x) {
rect.position = Vector2i(right_border, margins.y);
rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * (texture_region_size.y + separation.y))));
base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
}
// Draw actual tiles, using their properties (modulation, etc...)
for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
// Different materials need to be drawn with different CanvasItems.
RID ci_rid = _get_canvas_item_to_draw(tile_set_atlas_source->get_tile_data(atlas_coords, 0), base_tiles_draw, material_tiles_draw);
for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(atlas_coords); frame++) {
// Update the y to max value.
Rect2i base_frame_rect = tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame);
Vector2 offset_pos = Rect2(base_frame_rect).get_center() + Vector2(tile_set_atlas_source->get_tile_data(atlas_coords, 0)->get_texture_origin());
// Draw the tile.
TileMapLayer::draw_tile(ci_rid, offset_pos, tile_set, source_id, atlas_coords, 0, frame);
}
}
// Draw Dark overlay on separation in its own pass.
if (separation.x > 0 || separation.y > 0) {
for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(atlas_coords); frame++) {
// Update the y to max value.
Rect2i base_frame_rect = tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame);
if (separation.x > 0) {
Rect2i right_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(base_frame_rect.size.x, 0), Vector2i(separation.x, base_frame_rect.size.y));
right_sep_rect = right_sep_rect.intersection(Rect2i(Vector2(), texture->get_size()));
if (right_sep_rect.size.x > 0 && right_sep_rect.size.y > 0) {
//base_tiles_draw->draw_texture_rect_region(texture, right_sep_rect, right_sep_rect);
base_tiles_draw->draw_rect(right_sep_rect, Color(0.0, 0.0, 0.0, 0.5));
}
}
if (separation.y > 0) {
Rect2i bottom_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(0, base_frame_rect.size.y), Vector2i(base_frame_rect.size.x + separation.x, separation.y));
bottom_sep_rect = bottom_sep_rect.intersection(Rect2i(Vector2(), texture->get_size()));
if (bottom_sep_rect.size.x > 0 && bottom_sep_rect.size.y > 0) {
//base_tiles_draw->draw_texture_rect_region(texture, bottom_sep_rect, bottom_sep_rect);
base_tiles_draw->draw_rect(bottom_sep_rect, Color(0.0, 0.0, 0.0, 0.5));
}
}
}
}
}
}
}
RID TileAtlasView::_get_canvas_item_to_draw(const TileData *p_for_data, const CanvasItem *p_base_item, HashMap<Ref<Material>, RID> &p_material_map) {
Ref<Material> mat = p_for_data->get_material();
if (mat.is_null()) {
return p_base_item->get_canvas_item();
} else if (p_material_map.has(mat)) {
return p_material_map[mat];
} else {
RID ci_rid = RS::get_singleton()->canvas_item_create();
RS::get_singleton()->canvas_item_set_parent(ci_rid, p_base_item->get_canvas_item());
RS::get_singleton()->canvas_item_set_material(ci_rid, mat->get_rid());
RS::get_singleton()->canvas_item_set_default_texture_filter(ci_rid, RS::CanvasItemTextureFilter(p_base_item->get_texture_filter_in_tree()));
p_material_map[mat] = ci_rid;
return ci_rid;
}
}
void TileAtlasView::_clear_material_canvas_items() {
for (KeyValue<Ref<Material>, RID> kv : material_tiles_draw) {
RS::get_singleton()->free(kv.value);
}
material_tiles_draw.clear();
for (KeyValue<Ref<Material>, RID> kv : material_alternatives_draw) {
RS::get_singleton()->free(kv.value);
}
material_alternatives_draw.clear();
}
void TileAtlasView::_draw_base_tiles_texture_grid() {
if (tile_set_atlas_source.is_null()) {
return;
}
Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
if (texture.is_valid()) {
Vector2i margins = tile_set_atlas_source->get_margins();
Vector2i separation = tile_set_atlas_source->get_separation();
Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
// Draw each tile texture region.
for (int x = 0; x < grid_size.x; x++) {
for (int y = 0; y < grid_size.y; y++) {
Vector2i origin = margins + (Vector2i(x, y) * (texture_region_size + separation));
Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y));
if (base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) {
if (base_tile_coords == Vector2i(x, y)) {
// Draw existing tile.
Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(base_tile_coords);
Vector2 region_size = texture_region_size * size_in_atlas + separation * (size_in_atlas - Vector2i(1, 1));
base_tiles_texture_grid->draw_rect(Rect2i(origin, region_size), Color(1.0, 1.0, 1.0, 0.8), false);
}
} else {
// Draw the grid.
base_tiles_texture_grid->draw_rect(Rect2i(origin, texture_region_size), Color(0.7, 0.7, 0.7, 0.1), false);
}
}
}
}
}
void TileAtlasView::_draw_base_tiles_shape_grid() {
if (tile_set.is_null() || tile_set_atlas_source.is_null()) {
return;
}
// Draw the shapes.
Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color");
Vector2i tile_shape_size = tile_set->get_tile_size();
for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
Vector2 in_tile_base_offset = tile_set_atlas_source->get_tile_data(tile_id, 0)->get_texture_origin();
if (tile_set_atlas_source->is_rect_in_tile_texture_region(tile_id, 0, Rect2(Vector2(-tile_shape_size) / 2, tile_shape_size))) {
for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(tile_id); frame++) {
Color color = grid_color;
if (frame > 0) {
color.a *= 0.3;
}
Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(tile_id, frame);
Transform2D tile_xform;
tile_xform.set_origin(Rect2(texture_region).get_center() + in_tile_base_offset);
tile_xform.set_scale(tile_shape_size);
tile_set->draw_tile_shape(base_tiles_shape_grid, tile_xform, color);
}
}
}
}
void TileAtlasView::_alternative_tiles_root_control_gui_input(const Ref<InputEvent> &p_event) {
alternative_tiles_root_control->set_tooltip_text("");
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
Transform2D xform = alternative_tiles_drawing_root->get_transform().affine_inverse();
Vector3i coords3 = get_alternative_tile_at_pos(xform.xform(mm->get_position()));
Vector2i coords = Vector2i(coords3.x, coords3.y);
int alternative_id = coords3.z;
if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) {
alternative_tiles_root_control->set_tooltip_text(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: %d"), source_id, coords, alternative_id));
}
}
}
void TileAtlasView::_draw_alternatives() {
if (tile_set.is_null() || tile_set_atlas_source.is_null()) {
return;
}
// Draw the alternative tiles.
Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
if (texture.is_valid()) {
Vector2 current_pos;
for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
current_pos.x = 0;
int y_increment = 0;
Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(atlas_coords).size;
int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(atlas_coords);
for (int j = 1; j < alternatives_count; j++) {
int alternative_id = tile_set_atlas_source->get_alternative_tile_id(atlas_coords, j);
TileData *tile_data = tile_set_atlas_source->get_tile_data(atlas_coords, alternative_id);
bool transposed = tile_data->get_transpose();
// Different materials need to be drawn with different CanvasItems.
RID ci_rid = _get_canvas_item_to_draw(tile_data, alternatives_draw, material_alternatives_draw);
// Update the y to max value.
Vector2i offset_pos;
if (transposed) {
offset_pos = (current_pos + Vector2(texture_region_size.y, texture_region_size.x) / 2 + tile_data->get_texture_origin());
y_increment = MAX(y_increment, texture_region_size.x);
} else {
offset_pos = (current_pos + texture_region_size / 2 + tile_data->get_texture_origin());
y_increment = MAX(y_increment, texture_region_size.y);
}
// Draw the tile.
TileMapLayer::draw_tile(ci_rid, offset_pos, tile_set, source_id, atlas_coords, alternative_id);
// Increment the x position.
current_pos.x += transposed ? texture_region_size.y : texture_region_size.x;
}
if (alternatives_count > 1) {
current_pos.y += y_increment;
}
}
}
}
void TileAtlasView::_draw_background_left() {
background_left->draw_texture_rect(theme_cache.checkerboard, Rect2(Vector2(), background_left->get_size()), true);
}
void TileAtlasView::_draw_background_right() {
background_right->draw_texture_rect(theme_cache.checkerboard, Rect2(Vector2(), background_right->get_size()), true);
}
void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) {
tile_set = Ref<TileSet>(p_tile_set);
tile_set_atlas_source = Ref<TileSetAtlasSource>(p_tile_set_atlas_source);
_clear_material_canvas_items();
if (tile_set.is_null()) {
return;
}
ERR_FAIL_COND(p_source_id < 0);
ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source);
source_id = p_source_id;
// Show or hide the view.
bool valid = tile_set_atlas_source->get_texture().is_valid();
hbox->set_visible(valid);
missing_source_label->set_visible(!valid);
// Update the rect cache.
_update_alternative_tiles_rect_cache();
// Update everything.
_update_zoom_and_panning();
base_tiles_drawing_root->set_size(_compute_base_tiles_control_size());
alternative_tiles_drawing_root->set_size(_compute_alternative_tiles_control_size());
// Update.
base_tiles_draw->queue_redraw();
base_tiles_texture_grid->queue_redraw();
base_tiles_shape_grid->queue_redraw();
alternatives_draw->queue_redraw();
background_left->queue_redraw();
background_right->queue_redraw();
}
float TileAtlasView::get_zoom() const {
return zoom_widget->get_zoom();
}
void TileAtlasView::set_transform(float p_zoom, Vector2i p_panning) {
zoom_widget->set_zoom(p_zoom);
panning = p_panning;
_update_zoom_and_panning();
}
void TileAtlasView::set_padding(Side p_side, int p_padding) {
ERR_FAIL_COND(p_padding < 0);
margin_container_paddings[p_side] = p_padding;
}
Vector2i TileAtlasView::get_atlas_tile_coords_at_pos(const Vector2 p_pos, bool p_clamp) const {
if (tile_set_atlas_source.is_null()) {
return Vector2i();
}
Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
if (texture.is_null()) {
return TileSetSource::INVALID_ATLAS_COORDS;
}
Vector2i margins = tile_set_atlas_source->get_margins();
Vector2i separation = tile_set_atlas_source->get_separation();
Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
// Compute index in atlas.
Vector2 pos = p_pos - margins;
Vector2i ret = (pos / (texture_region_size + separation)).floor();
// Clamp.
if (p_clamp) {
Vector2i size = tile_set_atlas_source->get_atlas_grid_size();
ret = ret.clamp(Vector2i(), size - Vector2i(1, 1));
}
return ret;
}
void TileAtlasView::_update_alternative_tiles_rect_cache() {
if (tile_set_atlas_source.is_null()) {
return;
}
alternative_tiles_rect_cache.clear();
Rect2i current;
for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id);
Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(tile_id).size;
int line_height = 0;
for (int j = 1; j < alternatives_count; j++) {
int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j);
TileData *tile_data = tile_set_atlas_source->get_tile_data(tile_id, alternative_id);
bool transposed = tile_data->get_transpose();
current.size = transposed ? Vector2i(texture_region_size.y, texture_region_size.x) : texture_region_size;
// Update the rect.
if (!alternative_tiles_rect_cache.has(tile_id)) {
alternative_tiles_rect_cache[tile_id] = HashMap<int, Rect2i>();
}
alternative_tiles_rect_cache[tile_id][alternative_id] = current;
current.position.x += transposed ? texture_region_size.y : texture_region_size.x;
line_height = MAX(line_height, transposed ? texture_region_size.x : texture_region_size.y);
}
current.position.x = 0;
current.position.y += line_height;
}
}
Vector3i TileAtlasView::get_alternative_tile_at_pos(const Vector2 p_pos) const {
for (const KeyValue<Vector2, HashMap<int, Rect2i>> &E_coords : alternative_tiles_rect_cache) {
for (const KeyValue<int, Rect2i> &E_alternative : E_coords.value) {
if (E_alternative.value.has_point(p_pos)) {
return Vector3i(E_coords.key.x, E_coords.key.y, E_alternative.key);
}
}
}
return Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE);
}
Rect2i TileAtlasView::get_alternative_tile_rect(const Vector2i p_coords, int p_alternative_tile) {
ERR_FAIL_COND_V_MSG(!alternative_tiles_rect_cache.has(p_coords), Rect2i(), vformat("No cached rect for tile coords:%s", p_coords));
ERR_FAIL_COND_V_MSG(!alternative_tiles_rect_cache[p_coords].has(p_alternative_tile), Rect2i(), vformat("No cached rect for tile coords:%s alternative_id:%d", p_coords, p_alternative_tile));
return alternative_tiles_rect_cache[p_coords][p_alternative_tile];
}
void TileAtlasView::queue_redraw() {
base_tiles_draw->queue_redraw();
base_tiles_texture_grid->queue_redraw();
base_tiles_shape_grid->queue_redraw();
alternatives_draw->queue_redraw();
background_left->queue_redraw();
background_right->queue_redraw();
}
void TileAtlasView::_update_theme_item_cache() {
Control::_update_theme_item_cache();
theme_cache.center_view_icon = get_editor_theme_icon(SNAME("CenterView"));
theme_cache.checkerboard = get_editor_theme_icon(SNAME("Checkerboard"));
}
void TileAtlasView::_notification(int p_what) {
switch (p_what) {
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
if (!EditorSettings::get_singleton()->check_changed_settings_in_group("editors/panning")) {
break;
}
[[fallthrough]];
}
case NOTIFICATION_ENTER_TREE: {
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")));
panner->setup_warped_panning(get_viewport(), EDITOR_GET("editors/panning/warped_mouse_panning"));
} break;
case NOTIFICATION_THEME_CHANGED: {
button_center_view->set_button_icon(theme_cache.center_view_icon);
} break;
}
}
void TileAtlasView::_bind_methods() {
ADD_SIGNAL(MethodInfo("transform_changed", PropertyInfo(Variant::FLOAT, "zoom"), PropertyInfo(Variant::VECTOR2, "scroll")));
}
TileAtlasView::TileAtlasView() {
set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
Panel *panel = memnew(Panel);
panel->set_clip_contents(true);
panel->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
panel->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
panel->set_h_size_flags(SIZE_EXPAND_FILL);
panel->set_v_size_flags(SIZE_EXPAND_FILL);
add_child(panel);
zoom_widget = memnew(EditorZoomWidget);
add_child(zoom_widget);
zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);
zoom_widget->connect("zoom_changed", callable_mp(this, &TileAtlasView::_zoom_widget_changed).unbind(1));
zoom_widget->set_shortcut_context(this);
button_center_view = memnew(Button);
button_center_view->set_anchors_and_offsets_preset(Control::PRESET_TOP_RIGHT, Control::PRESET_MODE_MINSIZE, 5);
button_center_view->set_grow_direction_preset(Control::PRESET_TOP_RIGHT);
button_center_view->connect(SceneStringName(pressed), callable_mp(this, &TileAtlasView::_center_view));
button_center_view->set_flat(true);
button_center_view->set_disabled(true);
button_center_view->set_tooltip_text(TTR("Center View"));
add_child(button_center_view);
panner.instantiate();
panner->set_callbacks(callable_mp(this, &TileAtlasView::_pan_callback), callable_mp(this, &TileAtlasView::_zoom_callback));
panner->set_enable_rmb(true);
center_container = memnew(CenterContainer);
center_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
center_container->set_anchors_preset(Control::PRESET_CENTER);
center_container->connect(SceneStringName(gui_input), callable_mp(this, &TileAtlasView::gui_input));
center_container->connect(SceneStringName(focus_exited), callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
center_container->set_focus_mode(FOCUS_CLICK);
panel->add_child(center_container);
missing_source_label = memnew(Label);
missing_source_label->set_focus_mode(FOCUS_ACCESSIBILITY);
missing_source_label->set_text(TTR("The selected atlas source has no valid texture. Assign a texture in the TileSet bottom tab."));
center_container->add_child(missing_source_label);
margin_container = memnew(MarginContainer);
margin_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
center_container->add_child(margin_container);
hbox = memnew(HBoxContainer);
hbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
hbox->add_theme_constant_override("separation", 10);
hbox->hide();
margin_container->add_child(hbox);
VBoxContainer *left_vbox = memnew(VBoxContainer);
left_vbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
hbox->add_child(left_vbox);
VBoxContainer *right_vbox = memnew(VBoxContainer);
right_vbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
hbox->add_child(right_vbox);
// Base tiles.
Label *base_tile_label = memnew(Label);
base_tile_label->set_mouse_filter(Control::MOUSE_FILTER_PASS);
base_tile_label->set_text(TTR("Base Tiles"));
base_tile_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
left_vbox->add_child(base_tile_label);
base_tiles_root_control = memnew(Control);
base_tiles_root_control->set_mouse_filter(Control::MOUSE_FILTER_PASS);
base_tiles_root_control->set_v_size_flags(Control::SIZE_EXPAND_FILL);
base_tiles_root_control->connect(SceneStringName(gui_input), callable_mp(this, &TileAtlasView::_base_tiles_root_control_gui_input));
left_vbox->add_child(base_tiles_root_control);
background_left = memnew(Control);
background_left->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
background_left->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT);
background_left->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED);
background_left->connect(SceneStringName(draw), callable_mp(this, &TileAtlasView::_draw_background_left));
base_tiles_root_control->add_child(background_left);
base_tiles_drawing_root = memnew(Control);
base_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
base_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST);
base_tiles_root_control->add_child(base_tiles_drawing_root);
base_tiles_draw = memnew(Control);
base_tiles_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
base_tiles_draw->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
base_tiles_draw->connect(SceneStringName(draw), callable_mp(this, &TileAtlasView::_draw_base_tiles));
base_tiles_drawing_root->add_child(base_tiles_draw);
base_tiles_texture_grid = memnew(Control);
base_tiles_texture_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
base_tiles_texture_grid->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
base_tiles_texture_grid->connect(SceneStringName(draw), callable_mp(this, &TileAtlasView::_draw_base_tiles_texture_grid));
base_tiles_drawing_root->add_child(base_tiles_texture_grid);
base_tiles_shape_grid = memnew(Control);
base_tiles_shape_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
base_tiles_shape_grid->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
base_tiles_shape_grid->connect(SceneStringName(draw), callable_mp(this, &TileAtlasView::_draw_base_tiles_shape_grid));
base_tiles_drawing_root->add_child(base_tiles_shape_grid);
// Alternative tiles.
Label *alternative_tiles_label = memnew(Label);
alternative_tiles_label->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
alternative_tiles_label->set_text(TTR("Alternative Tiles"));
alternative_tiles_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
right_vbox->add_child(alternative_tiles_label);
alternative_tiles_root_control = memnew(Control);
alternative_tiles_root_control->set_mouse_filter(Control::MOUSE_FILTER_PASS);
alternative_tiles_root_control->set_v_size_flags(Control::SIZE_EXPAND_FILL);
alternative_tiles_root_control->connect(SceneStringName(gui_input), callable_mp(this, &TileAtlasView::_alternative_tiles_root_control_gui_input));
right_vbox->add_child(alternative_tiles_root_control);
background_right = memnew(Control);
background_right->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
background_right->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT);
background_right->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED);
background_right->connect(SceneStringName(draw), callable_mp(this, &TileAtlasView::_draw_background_right));
alternative_tiles_root_control->add_child(background_right);
alternative_tiles_drawing_root = memnew(Control);
alternative_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
alternative_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST);
alternative_tiles_root_control->add_child(alternative_tiles_drawing_root);
alternatives_draw = memnew(Control);
alternatives_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
alternatives_draw->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
alternatives_draw->connect(SceneStringName(draw), callable_mp(this, &TileAtlasView::_draw_alternatives));
alternative_tiles_drawing_root->add_child(alternatives_draw);
}
TileAtlasView::~TileAtlasView() {
_clear_material_canvas_items();
}

View File

@@ -0,0 +1,171 @@
/**************************************************************************/
/* tile_atlas_view.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. */
/**************************************************************************/
#pragma once
#include "editor/gui/editor_zoom_widget.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/center_container.h"
#include "scene/gui/label.h"
#include "scene/gui/margin_container.h"
#include "scene/resources/2d/tile_set.h"
class ViewPanner;
class TileAtlasView : public Control {
GDCLASS(TileAtlasView, Control);
private:
Ref<TileSet> tile_set;
Ref<TileSetAtlasSource> tile_set_atlas_source;
int source_id = TileSet::INVALID_SOURCE;
enum DragType {
DRAG_TYPE_NONE,
DRAG_TYPE_PAN,
};
DragType drag_type = DRAG_TYPE_NONE;
float previous_zoom = 1.0;
EditorZoomWidget *zoom_widget = nullptr;
Button *button_center_view = nullptr;
CenterContainer *center_container = nullptr;
Vector2 panning;
void _update_zoom_and_panning(bool p_zoom_on_mouse_pos = false, const Vector2 &p_mouse_pos = Vector2());
void _zoom_widget_changed();
void _center_view();
virtual void gui_input(const Ref<InputEvent> &p_event) override;
Ref<ViewPanner> panner;
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);
HashMap<Vector2, HashMap<int, Rect2i>> alternative_tiles_rect_cache;
void _update_alternative_tiles_rect_cache();
MarginContainer *margin_container = nullptr;
int margin_container_paddings[4] = { 0, 0, 0, 0 };
HBoxContainer *hbox = nullptr;
Label *missing_source_label = nullptr;
// Background
Control *background_left = nullptr;
void _draw_background_left();
Control *background_right = nullptr;
void _draw_background_right();
// Left side.
Control *base_tiles_root_control = nullptr;
void _base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event);
Control *base_tiles_drawing_root = nullptr;
Control *base_tiles_draw = nullptr;
HashMap<Ref<Material>, RID> material_tiles_draw;
HashMap<Ref<Material>, RID> material_alternatives_draw;
void _draw_base_tiles();
RID _get_canvas_item_to_draw(const TileData *p_for_data, const CanvasItem *p_base_item, HashMap<Ref<Material>, RID> &p_material_map);
void _clear_material_canvas_items();
Control *base_tiles_texture_grid = nullptr;
void _draw_base_tiles_texture_grid();
Control *base_tiles_shape_grid = nullptr;
void _draw_base_tiles_shape_grid();
Size2i _compute_base_tiles_control_size();
// Right side.
Control *alternative_tiles_root_control = nullptr;
void _alternative_tiles_root_control_gui_input(const Ref<InputEvent> &p_event);
Control *alternative_tiles_drawing_root = nullptr;
Control *alternatives_draw = nullptr;
void _draw_alternatives();
Size2i _compute_alternative_tiles_control_size();
struct ThemeCache {
Ref<Texture2D> center_view_icon;
Ref<Texture2D> checkerboard;
} theme_cache;
protected:
virtual void _update_theme_item_cache() override;
void _notification(int p_what);
static void _bind_methods();
public:
// Global.
void set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id);
float get_zoom() const;
void set_transform(float p_zoom, Vector2i p_panning);
void set_padding(Side p_side, int p_padding);
// Left side.
void set_texture_grid_visible(bool p_visible) { base_tiles_texture_grid->set_visible(p_visible); }
void set_tile_shape_grid_visible(bool p_visible) { base_tiles_shape_grid->set_visible(p_visible); }
Vector2i get_atlas_tile_coords_at_pos(const Vector2 p_pos, bool p_clamp = false) const;
void add_control_over_atlas_tiles(Control *p_control, bool scaled = true) {
if (scaled) {
base_tiles_drawing_root->add_child(p_control);
} else {
base_tiles_root_control->add_child(p_control);
}
p_control->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
p_control->set_mouse_filter(Control::MOUSE_FILTER_PASS);
}
// Right side.
Vector3i get_alternative_tile_at_pos(const Vector2 p_pos) const;
Rect2i get_alternative_tile_rect(const Vector2i p_coords, int p_alternative_tile);
void add_control_over_alternative_tiles(Control *p_control, bool scaled = true) {
if (scaled) {
alternative_tiles_drawing_root->add_child(p_control);
} else {
alternative_tiles_root_control->add_child(p_control);
}
p_control->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
p_control->set_mouse_filter(Control::MOUSE_FILTER_PASS);
}
// Redraw everything.
void queue_redraw();
TileAtlasView();
~TileAtlasView();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,415 @@
/**************************************************************************/
/* tile_data_editors.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. */
/**************************************************************************/
#pragma once
#include "tile_atlas_view.h"
#include "editor/inspector/editor_properties.h"
#include "scene/gui/box_container.h"
class Label;
class MenuButton;
class SpinBox;
class EditorUndoRedoManager;
class TileDataEditor : public VBoxContainer {
GDCLASS(TileDataEditor, VBoxContainer);
private:
bool _tile_set_changed_update_needed = false;
void _tile_set_changed_plan_update();
void _tile_set_changed_deferred_update();
protected:
Ref<TileSet> tile_set;
TileData *_get_tile_data(TileMapCell p_cell);
virtual void _tile_set_changed() {}
static void _bind_methods();
public:
void set_tile_set(Ref<TileSet> p_tile_set);
// Input to handle painting.
virtual Control *get_toolbar() { return nullptr; }
virtual void forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) {}
virtual void forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) {}
virtual void forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) {}
virtual void forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) {}
// Used to draw the tile data property value over a tile.
virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) {}
};
class DummyObject : public Object {
GDCLASS(DummyObject, Object)
private:
HashMap<String, Variant> properties;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
public:
bool has_dummy_property(const StringName &p_name);
void add_dummy_property(const StringName &p_name);
void remove_dummy_property(const StringName &p_name);
void clear_dummy_properties();
};
class GenericTilePolygonEditor : public VBoxContainer {
GDCLASS(GenericTilePolygonEditor, VBoxContainer);
private:
Ref<TileSet> tile_set;
LocalVector<Vector<Point2>> polygons;
bool multiple_polygon_mode = false;
bool use_undo_redo = true;
// UI
int hovered_polygon_index = -1;
int hovered_point_index = -1;
int hovered_segment_index = -1;
Vector2 hovered_segment_point;
enum DragType {
DRAG_TYPE_NONE,
DRAG_TYPE_DRAG_POINT,
DRAG_TYPE_CREATE_POINT,
DRAG_TYPE_PAN,
};
DragType drag_type = DRAG_TYPE_NONE;
int drag_polygon_index = 0;
int drag_point_index = 0;
Vector2 drag_last_pos;
PackedVector2Array drag_old_polygon;
HBoxContainer *toolbar = nullptr;
Ref<ButtonGroup> tools_button_group;
Button *button_expand = nullptr;
Button *button_create = nullptr;
Button *button_edit = nullptr;
Button *button_delete = nullptr;
MenuButton *button_advanced_menu = nullptr;
enum Snap {
SNAP_NONE,
SNAP_HALF_PIXEL,
SNAP_GRID,
};
int current_snap_option = SNAP_HALF_PIXEL;
MenuButton *button_pixel_snap = nullptr;
SpinBox *snap_subdivision = nullptr;
Vector<Point2> in_creation_polygon;
Panel *panel = nullptr;
Control *base_control = nullptr;
EditorZoomWidget *editor_zoom_widget = nullptr;
Button *button_center_view = nullptr;
Vector2 panning;
bool initializing = true;
Ref<TileSetAtlasSource> background_atlas_source;
Vector2i background_atlas_coords;
int background_alternative_id;
Color polygon_color = Color(1.0, 0.0, 0.0);
enum AdvancedMenuOption {
RESET_TO_DEFAULT_TILE,
CLEAR_TILE,
ROTATE_RIGHT,
ROTATE_LEFT,
FLIP_HORIZONTALLY,
FLIP_VERTICALLY,
};
void _base_control_draw();
void _zoom_changed();
void _advanced_menu_item_pressed(int p_item_pressed);
void _center_view();
void _base_control_gui_input(Ref<InputEvent> p_event);
void _set_snap_option(int p_index);
void _store_snap_options();
void _toggle_expand(bool p_expand);
void _snap_to_tile_shape(Point2 &r_point, float &r_current_snapped_dist, float p_snap_dist);
void _snap_point(Point2 &r_point);
void _grab_polygon_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_point_index);
void _grab_polygon_segment_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_segment_index, Vector2 &r_point);
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void set_use_undo_redo(bool p_use_undo_redo);
void set_tile_set(Ref<TileSet> p_tile_set);
void set_background_tile(const TileSetAtlasSource *p_atlas_source, const Vector2 &p_atlas_coords, int p_alternative_id);
int get_polygon_count();
int add_polygon(const Vector<Point2> &p_polygon, int p_index = -1);
void remove_polygon(int p_index);
void clear_polygons();
void set_polygon(int p_polygon_index, const Vector<Point2> &p_polygon);
Vector<Point2> get_polygon(int p_polygon_index);
void set_polygons_color(Color p_color);
void set_multiple_polygon_mode(bool p_multiple_polygon_mode);
GenericTilePolygonEditor();
};
class TileDataDefaultEditor : public TileDataEditor {
GDCLASS(TileDataDefaultEditor, TileDataEditor);
private:
// Toolbar
HBoxContainer *toolbar = memnew(HBoxContainer);
Button *picker_button = nullptr;
// UI
Ref<Texture2D> tile_bool_checked;
Ref<Texture2D> tile_bool_unchecked;
Label *label = nullptr;
EditorProperty *property_editor = nullptr;
// Painting state.
enum DragType {
DRAG_TYPE_NONE = 0,
DRAG_TYPE_PAINT,
DRAG_TYPE_PAINT_RECT,
};
DragType drag_type = DRAG_TYPE_NONE;
Vector2 drag_start_pos;
Vector2 drag_last_pos;
HashMap<TileMapCell, Variant, TileMapCell> drag_modified;
Variant drag_painted_value;
void _property_value_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field);
protected:
DummyObject *dummy_object = memnew(DummyObject);
StringName type;
String property;
Variant::Type property_type;
void _notification(int p_what);
virtual Variant _get_painted_value();
virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile);
virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, const Variant &p_value);
virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile);
virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, const Variant &p_new_value);
public:
virtual Control *get_toolbar() override { return toolbar; }
virtual void forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override;
virtual void forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override;
virtual void forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override;
virtual void forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override;
virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
void setup_property_editor(Variant::Type p_type, const String &p_property, const String &p_label = "", const Variant &p_default_value = Variant());
Variant::Type get_property_type();
TileDataDefaultEditor();
~TileDataDefaultEditor();
};
class TileDataTextureOriginEditor : public TileDataDefaultEditor {
GDCLASS(TileDataTextureOriginEditor, TileDataDefaultEditor);
public:
virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
};
class TileDataPositionEditor : public TileDataDefaultEditor {
GDCLASS(TileDataPositionEditor, TileDataDefaultEditor);
public:
virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
};
class TileDataYSortEditor : public TileDataDefaultEditor {
GDCLASS(TileDataYSortEditor, TileDataDefaultEditor);
public:
virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
};
class TileDataOcclusionShapeEditor : public TileDataDefaultEditor {
GDCLASS(TileDataOcclusionShapeEditor, TileDataDefaultEditor);
private:
int occlusion_layer = -1;
// UI
GenericTilePolygonEditor *polygon_editor = nullptr;
void _polygon_changed(const PackedVector2Array &p_polygon);
virtual Variant _get_painted_value() override;
virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override;
virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, const Variant &p_value) override;
virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override;
virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, const Variant &p_new_value) override;
protected:
virtual void _tile_set_changed() override;
void _notification(int p_what);
public:
virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
void set_occlusion_layer(int p_occlusion_layer) { occlusion_layer = p_occlusion_layer; }
TileDataOcclusionShapeEditor();
};
class TileDataCollisionEditor : public TileDataDefaultEditor {
GDCLASS(TileDataCollisionEditor, TileDataDefaultEditor);
int physics_layer = -1;
// UI
GenericTilePolygonEditor *polygon_editor = nullptr;
DummyObject *dummy_object = memnew(DummyObject);
HashMap<StringName, EditorProperty *> property_editors;
void _property_value_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field);
void _property_selected(const StringName &p_path, int p_focusable);
void _polygons_changed();
virtual Variant _get_painted_value() override;
virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override;
virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, const Variant &p_value) override;
virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override;
virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, const Variant &p_new_value) override;
protected:
virtual void _tile_set_changed() override;
void _notification(int p_what);
public:
virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
void set_physics_layer(int p_physics_layer) { physics_layer = p_physics_layer; }
TileDataCollisionEditor();
~TileDataCollisionEditor();
};
class TileDataTerrainsEditor : public TileDataEditor {
GDCLASS(TileDataTerrainsEditor, TileDataEditor);
private:
// Toolbar
HBoxContainer *toolbar = memnew(HBoxContainer);
Button *picker_button = nullptr;
// Painting state.
enum DragType {
DRAG_TYPE_NONE = 0,
DRAG_TYPE_PAINT_TERRAIN_SET,
DRAG_TYPE_PAINT_TERRAIN_SET_RECT,
DRAG_TYPE_PAINT_TERRAIN_BITS,
DRAG_TYPE_PAINT_TERRAIN_BITS_RECT,
};
DragType drag_type = DRAG_TYPE_NONE;
Vector2 drag_start_pos;
Vector2 drag_last_pos;
HashMap<TileMapCell, Variant, TileMapCell> drag_modified;
Variant drag_painted_value;
// UI
Label *label = nullptr;
DummyObject *dummy_object = memnew(DummyObject);
EditorPropertyEnum *terrain_set_property_editor = nullptr;
EditorPropertyEnum *terrain_property_editor = nullptr;
void _property_value_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field);
void _update_terrain_selector();
protected:
virtual void _tile_set_changed() override;
void _notification(int p_what);
public:
virtual Control *get_toolbar() override { return toolbar; }
virtual void forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override;
virtual void forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override;
virtual void forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override;
virtual void forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override;
virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
TileDataTerrainsEditor();
~TileDataTerrainsEditor();
};
class TileDataNavigationEditor : public TileDataDefaultEditor {
GDCLASS(TileDataNavigationEditor, TileDataDefaultEditor);
private:
int navigation_layer = -1;
PackedVector2Array navigation_polygon;
// UI
GenericTilePolygonEditor *polygon_editor = nullptr;
void _polygon_changed(const PackedVector2Array &p_polygon);
virtual Variant _get_painted_value() override;
virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override;
virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, const Variant &p_value) override;
virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override;
virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, const Variant &p_new_value) override;
protected:
virtual void _tile_set_changed() override;
void _notification(int p_what);
public:
virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
void set_navigation_layer(int p_navigation_layer) { navigation_layer = p_navigation_layer; }
TileDataNavigationEditor();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,427 @@
/**************************************************************************/
/* tile_map_layer_editor.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. */
/**************************************************************************/
#pragma once
#include "tile_atlas_view.h"
#include "core/os/thread.h"
#include "scene/gui/box_container.h"
#include "scene/gui/check_box.h"
#include "scene/gui/flow_container.h"
#include "scene/gui/item_list.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/gui/split_container.h"
#include "scene/gui/tab_bar.h"
#include "scene/gui/tree.h"
class TileMapLayer;
class TileMapLayerEditor;
class TileMapLayerSubEditorPlugin : public Object {
protected:
ObjectID edited_tile_map_layer_id;
TileMapLayer *_get_edited_layer() const;
public:
struct TabData {
Control *toolbar = nullptr;
Control *panel = nullptr;
};
virtual Vector<TabData> get_tabs() const {
return Vector<TabData>();
}
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return false; }
virtual void forward_canvas_draw_over_viewport(Control *p_overlay) {}
virtual void tile_set_changed() {}
virtual void edit(ObjectID p_tile_map_layer_id) {}
virtual void draw_tile_coords_over_viewport(Control *p_overlay, const TileMapLayer *p_edited_layer, Ref<TileSet> p_tile_set, bool p_show_rectangle_size, const Vector2i &p_rectangle_origin);
};
class TileMapLayerEditorTilesPlugin : public TileMapLayerSubEditorPlugin {
GDCLASS(TileMapLayerEditorTilesPlugin, TileMapLayerSubEditorPlugin);
public:
enum TileTransformType {
TRANSFORM_ROTATE_LEFT,
TRANSFORM_ROTATE_RIGHT,
TRANSFORM_FLIP_H,
TRANSFORM_FLIP_V,
};
private:
///// Toolbar /////
HBoxContainer *toolbar = nullptr;
Ref<ButtonGroup> tool_buttons_group;
Button *select_tool_button = nullptr;
Button *paint_tool_button = nullptr;
Button *line_tool_button = nullptr;
Button *rect_tool_button = nullptr;
Button *bucket_tool_button = nullptr;
HBoxContainer *tools_settings = nullptr;
VSeparator *tools_settings_vsep = nullptr;
Button *picker_button = nullptr;
Button *erase_button = nullptr;
HBoxContainer *transform_toolbar = nullptr;
Button *transform_button_rotate_left = nullptr;
Button *transform_button_rotate_right = nullptr;
Button *transform_button_flip_h = nullptr;
Button *transform_button_flip_v = nullptr;
VSeparator *tools_settings_vsep_2 = nullptr;
CheckBox *bucket_contiguous_checkbox = nullptr;
Button *random_tile_toggle = nullptr;
HBoxContainer *scatter_controls_container = nullptr;
float scattering = 0.0;
Label *scatter_label = nullptr;
SpinBox *scatter_spinbox = nullptr;
void _on_random_tile_checkbox_toggled(bool p_pressed);
void _on_scattering_spinbox_changed(double p_value);
void _update_toolbar();
void _update_transform_buttons();
void _set_transform_buttons_state(const Vector<Button *> &p_enabled_buttons, const Vector<Button *> &p_disabled_buttons, const String &p_why_disabled);
///// Tilemap editing. /////
bool has_mouse = false;
void _mouse_exited_viewport();
enum DragType {
DRAG_TYPE_NONE = 0,
DRAG_TYPE_SELECT,
DRAG_TYPE_MOVE,
DRAG_TYPE_PAINT,
DRAG_TYPE_LINE,
DRAG_TYPE_RECT,
DRAG_TYPE_BUCKET,
DRAG_TYPE_PICK,
DRAG_TYPE_CLIPBOARD_PASTE,
};
DragType drag_type = DRAG_TYPE_NONE;
bool drag_erasing = false;
Vector2 drag_start_mouse_pos;
Vector2 drag_last_mouse_pos;
HashMap<Vector2i, TileMapCell> drag_modified;
TileMapCell _pick_random_tile(Ref<TileMapPattern> p_pattern);
HashMap<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos, bool p_erase);
HashMap<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase);
HashMap<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase);
void _stop_dragging();
void _apply_transform(TileTransformType p_type);
int _get_transformed_alternative(int p_alternative_id, TileTransformType p_transform);
///// Selection system. /////
RBSet<Vector2i> tile_map_selection;
Ref<TileMapPattern> tile_map_clipboard;
Ref<TileMapPattern> selection_pattern;
Ref<TileMapPattern> erase_pattern;
void _set_tile_map_selection(const TypedArray<Vector2i> &p_selection);
TypedArray<Vector2i> _get_tile_map_selection() const;
RBSet<TileMapCell> tile_set_selection;
void _update_selection_pattern_from_tilemap_selection();
void _update_selection_pattern_from_tileset_tiles_selection();
void _update_selection_pattern_from_tileset_pattern_selection();
void _update_tileset_selection_from_selection_pattern();
void _update_fix_selected_and_hovered();
void _fix_invalid_tiles_in_tile_map_selection();
void patterns_item_list_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index);
///// Bottom panel common ////
void _tab_changed();
///// Bottom panel tiles ////
VBoxContainer *tiles_bottom_panel = nullptr;
Label *missing_source_label = nullptr;
Label *invalid_source_label = nullptr;
ItemList *sources_list = nullptr;
MenuButton *source_sort_button = nullptr;
Ref<Texture2D> missing_atlas_texture_icon;
void _update_tile_set_sources_list();
void _update_source_display();
// Atlas sources.
TileMapCell hovered_tile;
TileAtlasView *tile_atlas_view = nullptr;
HSplitContainer *atlas_sources_split_container = nullptr;
bool tile_set_dragging_selection = false;
Vector2i tile_set_drag_start_mouse_pos;
Control *tile_atlas_control = nullptr;
void _tile_atlas_control_mouse_exited();
void _tile_atlas_control_gui_input(const Ref<InputEvent> &p_event);
void _tile_atlas_control_draw();
Control *alternative_tiles_control = nullptr;
void _tile_alternatives_control_draw();
void _tile_alternatives_control_mouse_exited();
void _tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event);
void _update_atlas_view();
void _set_source_sort(int p_sort);
// Scenes collection sources.
ItemList *scene_tiles_list = nullptr;
void _update_scenes_collection_view();
void _scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_ud);
void _scenes_list_multi_selected(int p_index, bool p_selected);
void _scenes_list_lmb_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index);
///// Bottom panel patterns ////
VBoxContainer *patterns_bottom_panel = nullptr;
ItemList *patterns_item_list = nullptr;
Label *patterns_help_label = nullptr;
void _patterns_item_list_gui_input(const Ref<InputEvent> &p_event);
void _pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture);
bool select_last_pattern = false;
void _update_patterns_list();
// General
void _update_theme();
List<BaseButton *> viewport_shortcut_buttons;
// Update callback
virtual void tile_set_changed() override;
protected:
static void _bind_methods();
public:
virtual Vector<TabData> get_tabs() const override;
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override;
virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override;
virtual void edit(ObjectID p_tile_map_layer_id) override;
TileMapLayerEditorTilesPlugin();
};
class TileMapLayerEditorTerrainsPlugin : public TileMapLayerSubEditorPlugin {
GDCLASS(TileMapLayerEditorTerrainsPlugin, TileMapLayerSubEditorPlugin);
private:
// Toolbar.
HBoxContainer *toolbar = nullptr;
Ref<ButtonGroup> tool_buttons_group;
Button *paint_tool_button = nullptr;
Button *line_tool_button = nullptr;
Button *rect_tool_button = nullptr;
Button *bucket_tool_button = nullptr;
HBoxContainer *tools_settings = nullptr;
VSeparator *tools_settings_vsep = nullptr;
Button *picker_button = nullptr;
Button *erase_button = nullptr;
VSeparator *tools_settings_vsep_2 = nullptr;
CheckBox *bucket_contiguous_checkbox = nullptr;
void _update_toolbar();
// Main vbox.
VBoxContainer *main_vbox_container = nullptr;
// TileMap editing.
bool has_mouse = false;
void _mouse_exited_viewport();
enum DragType {
DRAG_TYPE_NONE = 0,
DRAG_TYPE_PAINT,
DRAG_TYPE_LINE,
DRAG_TYPE_RECT,
DRAG_TYPE_BUCKET,
DRAG_TYPE_PICK,
};
DragType drag_type = DRAG_TYPE_NONE;
bool drag_erasing = false;
Vector2 drag_start_mouse_pos;
Vector2 drag_last_mouse_pos;
HashMap<Vector2i, TileMapCell> drag_modified;
// Painting
HashMap<Vector2i, TileMapCell> _draw_terrain_path_or_connect(const Vector<Vector2i> &p_to_paint, int p_terrain_set, int p_terrain, bool p_connect) const;
HashMap<Vector2i, TileMapCell> _draw_terrain_pattern(const Vector<Vector2i> &p_to_paint, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const;
HashMap<Vector2i, TileMapCell> _draw_line(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase);
HashMap<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase);
RBSet<Vector2i> _get_cells_for_bucket_fill(Vector2i p_coords, bool p_contiguous);
HashMap<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase);
void _stop_dragging();
enum SelectedType {
SELECTED_TYPE_CONNECT = 0,
SELECTED_TYPE_PATH,
SELECTED_TYPE_PATTERN,
};
SelectedType selected_type;
int selected_terrain_set = -1;
int selected_terrain = -1;
TileSet::TerrainsPattern selected_terrains_pattern;
void _update_selection();
// Bottom panel.
Tree *terrains_tree = nullptr;
ItemList *terrains_tile_list = nullptr;
// Cache.
LocalVector<LocalVector<RBSet<TileSet::TerrainsPattern>>> per_terrain_terrains_patterns;
List<BaseButton *> viewport_shortcut_buttons;
// Update functions.
void _update_terrains_cache();
void _update_terrains_tree();
void _update_tiles_list();
void _update_theme();
// Update callback
virtual void tile_set_changed() override;
public:
virtual Vector<TabData> get_tabs() const override;
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override;
virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override;
virtual void edit(ObjectID p_tile_map_layer_id) override;
TileMapLayerEditorTerrainsPlugin();
};
class TileMapLayerEditor : public VBoxContainer {
GDCLASS(TileMapLayerEditor, VBoxContainer);
private:
bool tile_map_layer_changed_needs_update = false;
ObjectID edited_tile_map_layer_id;
bool is_multi_node_edit = false;
Vector<TileMapLayer *> tile_map_layers_in_scene_cache;
bool layers_in_scene_list_cache_needs_update = false;
TileMapLayer *_get_edited_layer() const;
void _find_tile_map_layers_in_scene(Node *p_current, const Node *p_owner, Vector<TileMapLayer *> &r_list) const;
void _update_tile_map_layers_in_scene_list_cache();
void _node_change(Node *p_node);
Control *custom_overlay = nullptr;
void _draw_overlay();
// Vector to keep plugins.
Vector<TileMapLayerSubEditorPlugin *> tile_map_editor_plugins;
// Toolbar.
HFlowContainer *tile_map_toolbar = nullptr;
bool show_layers_selector = false;
HBoxContainer *layer_selection_hbox = nullptr;
Button *select_previous_layer = nullptr;
void _select_previous_layer_pressed();
Button *select_next_layer = nullptr;
void _select_next_layer_pressed();
Button *select_all_layers = nullptr;
void _select_all_layers_pressed();
OptionButton *layers_selection_button = nullptr;
void _layers_selection_item_selected(int p_index);
void _update_layers_selector();
Button *toggle_highlight_selected_layer_button = nullptr;
void _clear_all_layers_highlighting();
void _update_all_layers_highlighting();
void _highlight_selected_layer_button_toggled(bool p_pressed);
Button *toggle_grid_button = nullptr;
void _on_grid_toggled(bool p_pressed);
enum {
ADVANCED_MENU_REPLACE_WITH_PROXIES,
ADVANCED_MENU_EXTRACT_TILE_MAP_LAYERS,
};
MenuButton *advanced_menu_button = nullptr;
void _advanced_menu_button_id_pressed(int p_id);
// Bottom panel.
Label *cant_edit_label = nullptr;
TabBar *tabs_bar = nullptr;
LocalVector<TileMapLayerSubEditorPlugin::TabData> tabs_data;
LocalVector<TileMapLayerSubEditorPlugin *> tabs_plugins;
void _update_bottom_panel();
// TileMap.
Ref<Texture2D> missing_tile_texture;
Ref<Texture2D> warning_pattern_texture;
// CallBack.
void _tile_map_layer_changed();
void _tab_changed(int p_tab_changed);
// Updates.
void _layers_select_next_or_previous(bool p_next);
// Inspector undo/redo callback.
void _move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, const String &p_array_prefix, int p_from_index, int p_to_pos);
protected:
void _notification(int p_what);
void _draw_shape(Control *p_control, Rect2 p_region, TileSet::TileShape p_shape, TileSet::TileOffsetAxis p_offset_axis, Color p_color);
public:
bool forward_canvas_gui_input(const Ref<InputEvent> &p_event);
void forward_canvas_draw_over_viewport(Control *p_overlay);
void edit(Object *p_tile_map_layer);
void set_show_layer_selector(bool p_show_layer_selector);
TileMapLayerEditor();
~TileMapLayerEditor();
// Static functions.
static Vector<Vector2i> get_line(const TileMapLayer *p_tile_map_layer, Vector2i p_from_cell, Vector2i p_to_cell);
};
VARIANT_ENUM_CAST(TileMapLayerEditorTilesPlugin::TileTransformType);

View File

@@ -0,0 +1,492 @@
/**************************************************************************/
/* tile_proxies_manager_dialog.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 "tile_proxies_manager_dialog.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/inspector/editor_properties_vector.h"
#include "editor/settings/editor_settings.h"
#include "scene/gui/popup_menu.h"
#include "scene/gui/separator.h"
void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index, Object *p_item_list) {
if (p_mouse_button_index != MouseButton::RIGHT) {
return;
}
ItemList *item_list = Object::cast_to<ItemList>(p_item_list);
popup_menu->reset_size();
popup_menu->set_position(get_position() + item_list->get_global_mouse_position());
popup_menu->popup();
}
void TileProxiesManagerDialog::_menu_id_pressed(int p_id) {
if (p_id == 0) {
// Delete.
_delete_selected_bindings();
}
}
void TileProxiesManagerDialog::_delete_selected_bindings() {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Remove Tile Proxies"));
Vector<int> source_level_selected = source_level_list->get_selected_items();
for (int i = 0; i < source_level_selected.size(); i++) {
int key = source_level_list->get_item_metadata(source_level_selected[i]);
int val = tile_set->get_source_level_tile_proxy(key);
undo_redo->add_do_method(*tile_set, "remove_source_level_tile_proxy", key);
undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", key, val);
}
Vector<int> coords_level_selected = coords_level_list->get_selected_items();
for (int i = 0; i < coords_level_selected.size(); i++) {
Array key = coords_level_list->get_item_metadata(coords_level_selected[i]);
Array val = tile_set->get_coords_level_tile_proxy(key[0], key[1]);
undo_redo->add_do_method(*tile_set, "remove_coords_level_tile_proxy", key[0], key[1]);
undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", key[0], key[1], val[0], val[1]);
}
Vector<int> alternative_level_selected = alternative_level_list->get_selected_items();
for (int i = 0; i < alternative_level_selected.size(); i++) {
Array key = alternative_level_list->get_item_metadata(alternative_level_selected[i]);
Array val = tile_set->get_alternative_level_tile_proxy(key[0], key[1], key[2]);
undo_redo->add_do_method(*tile_set, "remove_alternative_level_tile_proxy", key[0], key[1], key[2]);
undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", key[0], key[1], key[2], val[0], val[1], val[2]);
}
undo_redo->add_do_method(this, "_update_lists");
undo_redo->add_undo_method(this, "_update_lists");
undo_redo->commit_action();
committed_actions_count += 1;
}
void TileProxiesManagerDialog::_update_lists() {
source_level_list->clear();
coords_level_list->clear();
alternative_level_list->clear();
Array proxies = tile_set->get_source_level_tile_proxies();
for (int i = 0; i < proxies.size(); i++) {
Array proxy = proxies[i];
String text = vformat("%s", proxy[0]).rpad(5) + "-> " + vformat("%s", proxy[1]);
int id = source_level_list->add_item(text);
source_level_list->set_item_metadata(id, proxy[0]);
}
proxies = tile_set->get_coords_level_tile_proxies();
for (int i = 0; i < proxies.size(); i++) {
Array proxy = proxies[i];
String text = vformat("%s, %s", proxy[0], proxy[1]).rpad(17) + "-> " + vformat("%s, %s", proxy[2], proxy[3]);
int id = coords_level_list->add_item(text);
coords_level_list->set_item_metadata(id, proxy.slice(0, 2));
}
proxies = tile_set->get_alternative_level_tile_proxies();
for (int i = 0; i < proxies.size(); i++) {
Array proxy = proxies[i];
String text = vformat("%s, %s, %s", proxy[0], proxy[1], proxy[2]).rpad(24) + "-> " + vformat("%s, %s, %s", proxy[3], proxy[4], proxy[5]);
int id = alternative_level_list->add_item(text);
alternative_level_list->set_item_metadata(id, proxy.slice(0, 3));
}
}
void TileProxiesManagerDialog::_update_enabled_property_editors() {
if (from.source_id == TileSet::INVALID_SOURCE) {
from.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
to.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
from.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
to.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
coords_from_property_editor->hide();
coords_to_property_editor->hide();
alternative_from_property_editor->hide();
alternative_to_property_editor->hide();
} else if (from.get_atlas_coords().x == -1 || from.get_atlas_coords().y == -1) {
from.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
to.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
coords_from_property_editor->show();
coords_to_property_editor->show();
alternative_from_property_editor->hide();
alternative_to_property_editor->hide();
} else {
coords_from_property_editor->show();
coords_to_property_editor->show();
alternative_from_property_editor->show();
alternative_to_property_editor->show();
}
source_from_property_editor->update_property();
source_to_property_editor->update_property();
coords_from_property_editor->update_property();
coords_to_property_editor->update_property();
alternative_from_property_editor->update_property();
alternative_to_property_editor->update_property();
}
void TileProxiesManagerDialog::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing) {
_set(p_path, p_value);
}
void TileProxiesManagerDialog::_add_button_pressed() {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
if (from.source_id != TileSet::INVALID_SOURCE && to.source_id != TileSet::INVALID_SOURCE) {
Vector2i from_coords = from.get_atlas_coords();
Vector2i to_coords = to.get_atlas_coords();
if (from_coords.x >= 0 && from_coords.y >= 0 && to_coords.x >= 0 && to_coords.y >= 0) {
if (from.alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE && to.alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE) {
undo_redo->create_action(TTR("Create Alternative-level Tile Proxy"));
undo_redo->add_do_method(*tile_set, "set_alternative_level_tile_proxy", from.source_id, from.get_atlas_coords(), from.alternative_tile, to.source_id, to.get_atlas_coords(), to.alternative_tile);
if (tile_set->has_alternative_level_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile)) {
Array a = tile_set->get_alternative_level_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile);
undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", to.source_id, to.get_atlas_coords(), to.alternative_tile, a[0], a[1], a[2]);
} else {
undo_redo->add_undo_method(*tile_set, "remove_alternative_level_tile_proxy", from.source_id, from.get_atlas_coords(), from.alternative_tile);
}
} else {
undo_redo->create_action(TTR("Create Coords-level Tile Proxy"));
undo_redo->add_do_method(*tile_set, "set_coords_level_tile_proxy", from.source_id, from.get_atlas_coords(), to.source_id, to.get_atlas_coords());
if (tile_set->has_coords_level_tile_proxy(from.source_id, from.get_atlas_coords())) {
Array a = tile_set->get_coords_level_tile_proxy(from.source_id, from.get_atlas_coords());
undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", to.source_id, to.get_atlas_coords(), a[0], a[1]);
} else {
undo_redo->add_undo_method(*tile_set, "remove_coords_level_tile_proxy", from.source_id, from.get_atlas_coords());
}
}
} else {
undo_redo->create_action(TTR("Create source-level Tile Proxy"));
undo_redo->add_do_method(*tile_set, "set_source_level_tile_proxy", from.source_id, to.source_id);
if (tile_set->has_source_level_tile_proxy(from.source_id)) {
undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", to.source_id, tile_set->get_source_level_tile_proxy(from.source_id));
} else {
undo_redo->add_undo_method(*tile_set, "remove_source_level_tile_proxy", from.source_id);
}
}
undo_redo->add_do_method(this, "_update_lists");
undo_redo->add_undo_method(this, "_update_lists");
undo_redo->commit_action();
committed_actions_count++;
}
}
void TileProxiesManagerDialog::_clear_invalid_button_pressed() {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Delete All Invalid Tile Proxies"));
undo_redo->add_do_method(*tile_set, "cleanup_invalid_tile_proxies");
Array proxies = tile_set->get_source_level_tile_proxies();
for (int i = 0; i < proxies.size(); i++) {
Array proxy = proxies[i];
undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", proxy[0], proxy[1]);
}
proxies = tile_set->get_coords_level_tile_proxies();
for (int i = 0; i < proxies.size(); i++) {
Array proxy = proxies[i];
undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3]);
}
proxies = tile_set->get_alternative_level_tile_proxies();
for (int i = 0; i < proxies.size(); i++) {
Array proxy = proxies[i];
undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3], proxy[4], proxy[5]);
}
undo_redo->add_do_method(this, "_update_lists");
undo_redo->add_undo_method(this, "_update_lists");
undo_redo->commit_action();
}
void TileProxiesManagerDialog::_clear_all_button_pressed() {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Delete All Tile Proxies"));
undo_redo->add_do_method(*tile_set, "clear_tile_proxies");
Array proxies = tile_set->get_source_level_tile_proxies();
for (int i = 0; i < proxies.size(); i++) {
Array proxy = proxies[i];
undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", proxy[0], proxy[1]);
}
proxies = tile_set->get_coords_level_tile_proxies();
for (int i = 0; i < proxies.size(); i++) {
Array proxy = proxies[i];
undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3]);
}
proxies = tile_set->get_alternative_level_tile_proxies();
for (int i = 0; i < proxies.size(); i++) {
Array proxy = proxies[i];
undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3], proxy[4], proxy[5]);
}
undo_redo->add_do_method(this, "_update_lists");
undo_redo->add_undo_method(this, "_update_lists");
undo_redo->commit_action();
}
bool TileProxiesManagerDialog::_set(const StringName &p_name, const Variant &p_value) {
if (p_name == "from_source") {
from.source_id = MAX(int(p_value), -1);
} else if (p_name == "from_coords") {
from.set_atlas_coords(Vector2i(p_value).maxi(-1));
} else if (p_name == "from_alternative") {
from.alternative_tile = MAX(int(p_value), -1);
} else if (p_name == "to_source") {
to.source_id = MAX(int(p_value), 0);
} else if (p_name == "to_coords") {
to.set_atlas_coords(Vector2i(p_value).maxi(0));
} else if (p_name == "to_alternative") {
to.alternative_tile = MAX(int(p_value), 0);
} else {
return false;
}
_update_enabled_property_editors();
return true;
}
bool TileProxiesManagerDialog::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "from_source") {
r_ret = from.source_id;
} else if (p_name == "from_coords") {
r_ret = from.get_atlas_coords();
} else if (p_name == "from_alternative") {
r_ret = from.alternative_tile;
} else if (p_name == "to_source") {
r_ret = to.source_id;
} else if (p_name == "to_coords") {
r_ret = to.get_atlas_coords();
} else if (p_name == "to_alternative") {
r_ret = to.alternative_tile;
} else {
return false;
}
return true;
}
void TileProxiesManagerDialog::_unhandled_key_input(Ref<InputEvent> p_event) {
ERR_FAIL_COND(p_event.is_null());
if (p_event->is_pressed() && !p_event->is_echo() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event))) {
if (!is_inside_tree() || !is_visible()) {
return;
}
if (popup_menu->activate_item_by_event(p_event, false)) {
set_input_as_handled();
}
}
}
void TileProxiesManagerDialog::cancel_pressed() {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
for (int i = 0; i < committed_actions_count; i++) {
undo_redo->undo();
}
committed_actions_count = 0;
}
void TileProxiesManagerDialog::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_lists"), &TileProxiesManagerDialog::_update_lists);
ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &TileProxiesManagerDialog::_unhandled_key_input);
}
void TileProxiesManagerDialog::update_tile_set(Ref<TileSet> p_tile_set) {
ERR_FAIL_COND(p_tile_set.is_null());
tile_set = p_tile_set;
committed_actions_count = 0;
_update_lists();
}
TileProxiesManagerDialog::TileProxiesManagerDialog() {
// Tile proxy management window.
set_title(TTR("Tile Proxies Management"));
set_process_unhandled_key_input(true);
to.source_id = 0;
to.set_atlas_coords(Vector2i());
to.alternative_tile = 0;
VBoxContainer *vbox_container = memnew(VBoxContainer);
vbox_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
vbox_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
add_child(vbox_container);
Label *source_level_label = memnew(Label);
source_level_label->set_text(TTR("Source-level proxies"));
vbox_container->add_child(source_level_label);
source_level_list = memnew(ItemList);
source_level_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
source_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
source_level_list->set_select_mode(ItemList::SELECT_MULTI);
source_level_list->set_allow_rmb_select(true);
source_level_list->connect("item_clicked", callable_mp(this, &TileProxiesManagerDialog::_right_clicked).bind(source_level_list));
vbox_container->add_child(source_level_list);
Label *coords_level_label = memnew(Label);
coords_level_label->set_text(TTR("Coords-level proxies"));
vbox_container->add_child(coords_level_label);
coords_level_list = memnew(ItemList);
coords_level_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
coords_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
coords_level_list->set_select_mode(ItemList::SELECT_MULTI);
coords_level_list->set_allow_rmb_select(true);
coords_level_list->connect("item_clicked", callable_mp(this, &TileProxiesManagerDialog::_right_clicked).bind(coords_level_list));
vbox_container->add_child(coords_level_list);
Label *alternative_level_label = memnew(Label);
alternative_level_label->set_text(TTR("Alternative-level proxies"));
vbox_container->add_child(alternative_level_label);
alternative_level_list = memnew(ItemList);
alternative_level_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
alternative_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
alternative_level_list->set_select_mode(ItemList::SELECT_MULTI);
alternative_level_list->set_allow_rmb_select(true);
alternative_level_list->connect("item_clicked", callable_mp(this, &TileProxiesManagerDialog::_right_clicked).bind(alternative_level_list));
vbox_container->add_child(alternative_level_list);
popup_menu = memnew(PopupMenu);
popup_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_delete"));
popup_menu->connect(SceneStringName(id_pressed), callable_mp(this, &TileProxiesManagerDialog::_menu_id_pressed));
add_child(popup_menu);
// Add proxy panel.
HSeparator *h_separator = memnew(HSeparator);
vbox_container->add_child(h_separator);
Label *add_label = memnew(Label);
add_label->set_text(TTR("Add a new tile proxy:"));
vbox_container->add_child(add_label);
HBoxContainer *hboxcontainer = memnew(HBoxContainer);
vbox_container->add_child(hboxcontainer);
// From
VBoxContainer *vboxcontainer_from = memnew(VBoxContainer);
vboxcontainer_from->set_h_size_flags(Control::SIZE_EXPAND_FILL);
hboxcontainer->add_child(vboxcontainer_from);
source_from_property_editor = memnew(EditorPropertyInteger);
source_from_property_editor->set_label(TTR("From Source"));
source_from_property_editor->set_object_and_property(this, "from_source");
source_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed));
source_from_property_editor->set_selectable(false);
source_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
source_from_property_editor->setup(-1, 99999, 1, false, true, false);
vboxcontainer_from->add_child(source_from_property_editor);
coords_from_property_editor = memnew(EditorPropertyVector2i);
coords_from_property_editor->set_label(TTR("From Coords"));
coords_from_property_editor->set_object_and_property(this, "from_coords");
coords_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed));
coords_from_property_editor->set_selectable(false);
coords_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
coords_from_property_editor->setup(-1, 99999, true);
coords_from_property_editor->hide();
vboxcontainer_from->add_child(coords_from_property_editor);
alternative_from_property_editor = memnew(EditorPropertyInteger);
alternative_from_property_editor->set_label(TTR("From Alternative"));
alternative_from_property_editor->set_object_and_property(this, "from_alternative");
alternative_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed));
alternative_from_property_editor->set_selectable(false);
alternative_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
alternative_from_property_editor->setup(-1, 99999, 1, false, true, false);
alternative_from_property_editor->hide();
vboxcontainer_from->add_child(alternative_from_property_editor);
// To
VBoxContainer *vboxcontainer_to = memnew(VBoxContainer);
vboxcontainer_to->set_h_size_flags(Control::SIZE_EXPAND_FILL);
hboxcontainer->add_child(vboxcontainer_to);
source_to_property_editor = memnew(EditorPropertyInteger);
source_to_property_editor->set_label(TTR("To Source"));
source_to_property_editor->set_object_and_property(this, "to_source");
source_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed));
source_to_property_editor->set_selectable(false);
source_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
source_to_property_editor->setup(-1, 99999, 1, false, true, false);
vboxcontainer_to->add_child(source_to_property_editor);
coords_to_property_editor = memnew(EditorPropertyVector2i);
coords_to_property_editor->set_label(TTR("To Coords"));
coords_to_property_editor->set_object_and_property(this, "to_coords");
coords_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed));
coords_to_property_editor->set_selectable(false);
coords_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
coords_to_property_editor->setup(-1, 99999, true);
coords_to_property_editor->hide();
vboxcontainer_to->add_child(coords_to_property_editor);
alternative_to_property_editor = memnew(EditorPropertyInteger);
alternative_to_property_editor->set_label(TTR("To Alternative"));
alternative_to_property_editor->set_object_and_property(this, "to_alternative");
alternative_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed));
alternative_to_property_editor->set_selectable(false);
alternative_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
alternative_to_property_editor->setup(-1, 99999, 1, false, true, false);
alternative_to_property_editor->hide();
vboxcontainer_to->add_child(alternative_to_property_editor);
Button *add_button = memnew(Button);
add_button->set_text(TTR("Add"));
add_button->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
add_button->connect(SceneStringName(pressed), callable_mp(this, &TileProxiesManagerDialog::_add_button_pressed));
vbox_container->add_child(add_button);
h_separator = memnew(HSeparator);
vbox_container->add_child(h_separator);
// Generic actions.
Label *generic_actions_label = memnew(Label);
generic_actions_label->set_text(TTR("Global actions:"));
vbox_container->add_child(generic_actions_label);
hboxcontainer = memnew(HBoxContainer);
vbox_container->add_child(hboxcontainer);
Button *clear_invalid_button = memnew(Button);
clear_invalid_button->set_text(TTR("Clear Invalid"));
clear_invalid_button->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
clear_invalid_button->connect(SceneStringName(pressed), callable_mp(this, &TileProxiesManagerDialog::_clear_invalid_button_pressed));
hboxcontainer->add_child(clear_invalid_button);
Button *clear_all_button = memnew(Button);
clear_all_button->set_text(TTR("Clear All"));
clear_all_button->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
clear_all_button->connect(SceneStringName(pressed), callable_mp(this, &TileProxiesManagerDialog::_clear_all_button_pressed));
hboxcontainer->add_child(clear_all_button);
h_separator = memnew(HSeparator);
vbox_container->add_child(h_separator);
}

View File

@@ -0,0 +1,86 @@
/**************************************************************************/
/* tile_proxies_manager_dialog.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. */
/**************************************************************************/
#pragma once
#include "editor/inspector/editor_properties.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/item_list.h"
#include "scene/resources/2d/tile_set.h"
class EditorPropertyVector2i;
class EditorUndoRedoManager;
class TileProxiesManagerDialog : public ConfirmationDialog {
GDCLASS(TileProxiesManagerDialog, ConfirmationDialog);
private:
int committed_actions_count = 0;
Ref<TileSet> tile_set;
TileMapCell from;
TileMapCell to;
// GUI
ItemList *source_level_list = nullptr;
ItemList *coords_level_list = nullptr;
ItemList *alternative_level_list = nullptr;
EditorPropertyInteger *source_from_property_editor = nullptr;
EditorPropertyVector2i *coords_from_property_editor = nullptr;
EditorPropertyInteger *alternative_from_property_editor = nullptr;
EditorPropertyInteger *source_to_property_editor = nullptr;
EditorPropertyVector2i *coords_to_property_editor = nullptr;
EditorPropertyInteger *alternative_to_property_editor = nullptr;
PopupMenu *popup_menu = nullptr;
void _right_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index, Object *p_item_list);
void _menu_id_pressed(int p_id);
void _delete_selected_bindings();
void _update_lists();
void _update_enabled_property_editors();
void _property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing);
void _add_button_pressed();
void _clear_invalid_button_pressed();
void _clear_all_button_pressed();
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _unhandled_key_input(Ref<InputEvent> p_event);
virtual void cancel_pressed() override;
static void _bind_methods();
public:
void update_tile_set(Ref<TileSet> p_tile_set);
TileProxiesManagerDialog();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,334 @@
/**************************************************************************/
/* tile_set_atlas_source_editor.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. */
/**************************************************************************/
#pragma once
#include "tile_atlas_view.h"
#include "tile_data_editors.h"
#include "scene/gui/split_container.h"
#include "scene/resources/2d/tile_set.h"
class Popup;
class TileSet;
class Tree;
class VSeparator;
class TileSetAtlasSourceEditor : public HSplitContainer {
GDCLASS(TileSetAtlasSourceEditor, HSplitContainer);
public:
// A class to store which tiles are selected.
struct TileSelection {
Vector2i tile = TileSetSource::INVALID_ATLAS_COORDS;
int alternative = TileSetSource::INVALID_TILE_ALTERNATIVE;
bool operator<(const TileSelection &p_other) const {
if (tile == p_other.tile) {
return alternative < p_other.alternative;
} else {
return tile < p_other.tile;
}
}
};
// -- Proxy object for an atlas source, needed by the inspector --
class TileSetAtlasSourceProxyObject : public Object {
GDCLASS(TileSetAtlasSourceProxyObject, Object);
private:
Ref<TileSet> tile_set;
Ref<TileSetAtlasSource> tile_set_atlas_source;
int source_id = TileSet::INVALID_SOURCE;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
public:
void set_id(int p_id);
int get_id() const;
void edit(Ref<TileSet> p_tile_set, Ref<TileSetAtlasSource> p_tile_set_atlas_source, int p_source_id);
Ref<TileSetAtlasSource> get_edited() { return tile_set_atlas_source; }
};
// -- Proxy object for a tile, needed by the inspector --
class AtlasTileProxyObject : public Object {
GDCLASS(AtlasTileProxyObject, Object);
private:
TileSetAtlasSourceEditor *tiles_set_atlas_source_editor = nullptr;
Ref<TileSetAtlasSource> tile_set_atlas_source;
RBSet<TileSelection> tiles;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
public:
Ref<TileSetAtlasSource> get_edited_tile_set_atlas_source() const { return tile_set_atlas_source; }
RBSet<TileSelection> get_edited_tiles() const { return tiles; }
// Update the proxied object.
void edit(Ref<TileSetAtlasSource> p_tile_set_atlas_source, const RBSet<TileSelection> &p_tiles = RBSet<TileSelection>());
AtlasTileProxyObject(TileSetAtlasSourceEditor *p_tiles_set_atlas_source_editor) {
tiles_set_atlas_source_editor = p_tiles_set_atlas_source_editor;
}
};
class TileAtlasControl : public Control {
TileSetAtlasSourceEditor *editor = nullptr;
public:
virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override;
TileAtlasControl(TileSetAtlasSourceEditor *p_editor) { editor = p_editor; }
};
friend class TileAtlasControl;
private:
bool read_only = false;
Ref<TileSet> tile_set;
TileSetAtlasSource *tile_set_atlas_source = nullptr;
int tile_set_atlas_source_id = TileSet::INVALID_SOURCE;
Ref<Texture2D> atlas_source_texture;
bool tile_set_changed_needs_update = false;
// -- Properties painting --
ScrollContainer *tile_data_editors_scroll = nullptr;
VBoxContainer *tile_data_painting_editor_container = nullptr;
Label *tile_data_editors_label = nullptr;
Button *tile_data_editor_dropdown_button = nullptr;
Popup *tile_data_editors_popup = nullptr;
Tree *tile_data_editors_tree = nullptr;
void _tile_data_editor_dropdown_button_draw();
void _tile_data_editor_dropdown_button_pressed();
// -- Tile data editors --
String current_property;
HashMap<String, TileDataEditor *> tile_data_editors;
TileDataEditor *current_tile_data_editor = nullptr;
void _tile_data_editors_tree_selected();
// -- Inspector --
AtlasTileProxyObject *tile_proxy_object = nullptr;
EditorInspector *tile_inspector = nullptr;
Label *tile_inspector_no_tile_selected_label = nullptr;
String selected_property;
void _inspector_property_selected(const String &p_property);
TileSetAtlasSourceProxyObject *atlas_source_proxy_object = nullptr;
EditorInspector *atlas_source_inspector = nullptr;
// -- Atlas view --
TileAtlasView *tile_atlas_view = nullptr;
Label *help_label = nullptr;
// Dragging
enum DragType {
DRAG_TYPE_NONE = 0,
DRAG_TYPE_CREATE_TILES,
DRAG_TYPE_CREATE_TILES_USING_RECT,
DRAG_TYPE_CREATE_BIG_TILE,
DRAG_TYPE_REMOVE_TILES,
DRAG_TYPE_REMOVE_TILES_USING_RECT,
DRAG_TYPE_MOVE_TILE,
DRAG_TYPE_RECT_SELECT,
DRAG_TYPE_MAY_POPUP_MENU,
// WARNING: Keep in this order.
DRAG_TYPE_RESIZE_TOP_LEFT,
DRAG_TYPE_RESIZE_TOP,
DRAG_TYPE_RESIZE_TOP_RIGHT,
DRAG_TYPE_RESIZE_RIGHT,
DRAG_TYPE_RESIZE_BOTTOM_RIGHT,
DRAG_TYPE_RESIZE_BOTTOM,
DRAG_TYPE_RESIZE_BOTTOM_LEFT,
DRAG_TYPE_RESIZE_LEFT,
};
DragType drag_type = DRAG_TYPE_NONE;
Vector2i drag_start_mouse_pos;
Vector2i drag_last_mouse_pos;
Vector2i drag_current_tile;
Rect2i drag_start_tile_shape;
RBSet<Vector2i> drag_modified_tiles;
void _end_dragging();
HashMap<Vector2i, List<const PropertyInfo *>> _group_properties_per_tiles(const List<PropertyInfo> &r_list, const TileSetAtlasSource *p_atlas);
// Popup functions.
enum MenuOptions {
TILE_CREATE,
TILE_CREATE_ALTERNATIVE,
TILE_DELETE,
ADVANCED_AUTO_CREATE_TILES,
ADVANCED_AUTO_REMOVE_TILES,
ADVANCED_CLEANUP_TILES,
};
Vector2i menu_option_coords;
int menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE;
void _menu_option(int p_option);
// Tool buttons.
Ref<ButtonGroup> tools_button_group;
Button *tool_setup_atlas_source_button = nullptr;
Button *tool_select_button = nullptr;
Button *tool_paint_button = nullptr;
Label *tool_tile_id_label = nullptr;
// Tool settings.
HBoxContainer *tool_settings = nullptr;
HBoxContainer *tool_settings_tile_data_toolbar_container = nullptr;
Button *tools_settings_erase_button = nullptr;
MenuButton *tool_advanced_menu_button = nullptr;
TextureRect *outside_tiles_warning = nullptr;
// Selection.
RBSet<TileSelection> selection;
void _set_selection_from_array(const Array &p_selection);
Array _get_selection_as_array();
// A control on the tile atlas to draw and handle input events.
Vector2i hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS;
PopupMenu *base_tile_popup_menu = nullptr;
PopupMenu *empty_base_tile_popup_menu = nullptr;
Ref<Texture2D> resize_handle;
Ref<Texture2D> resize_handle_disabled;
Control *tile_atlas_control = nullptr;
Control *tile_atlas_control_unscaled = nullptr;
void _tile_atlas_control_draw();
void _tile_atlas_control_unscaled_draw();
void _tile_atlas_control_mouse_exited();
void _tile_atlas_control_gui_input(const Ref<InputEvent> &p_event);
void _tile_atlas_view_transform_changed();
// A control over the alternative tiles.
Vector3i hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE);
PopupMenu *alternative_tile_popup_menu = nullptr;
Control *alternative_tiles_control = nullptr;
Control *alternative_tiles_control_unscaled = nullptr;
void _tile_alternatives_create_button_pressed(const Vector2i &p_atlas_coords);
void _tile_alternatives_control_draw();
void _tile_alternatives_control_unscaled_draw();
void _tile_alternatives_control_mouse_exited();
void _tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event);
// -- Update functions --
void _update_tile_id_label();
void _update_source_inspector();
void _update_fix_selected_and_hovered_tiles();
void _update_atlas_source_inspector();
void _update_tile_inspector();
void _update_tile_data_editors();
void _update_current_tile_data_editor();
void _update_manage_tile_properties_button();
void _update_atlas_view();
void _update_toolbar();
void _update_buttons();
// -- Misc --
void _auto_create_tiles();
void _auto_remove_tiles();
void _cancel_auto_create_tiles();
AcceptDialog *confirm_auto_create_tiles = nullptr;
Vector<Ref<TileSetAtlasSource>> atlases_to_auto_create_tiles;
Vector2i _get_drag_offset_tile_coords(const Vector2i &p_offset) const;
void _update_source_texture();
void _check_outside_tiles();
void _cleanup_outside_tiles();
void _tile_set_changed();
void _tile_proxy_object_changed(const String &p_what);
void _atlas_source_proxy_object_changed(const String &p_what);
void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, const String &p_property, const Variant &p_new_value);
protected:
void _notification(int p_what);
static void _bind_methods();
// -- input events --
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
public:
void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_source, int p_source_id);
void init_new_atlases(const Vector<Ref<TileSetAtlasSource>> &p_atlases);
TileSetAtlasSourceEditor();
~TileSetAtlasSourceEditor();
};
class EditorPropertyTilePolygon : public EditorProperty {
GDCLASS(EditorPropertyTilePolygon, EditorProperty);
StringName count_property;
String element_pattern;
String base_type;
void _add_focusable_children(Node *p_node);
GenericTilePolygonEditor *generic_tile_polygon_editor = nullptr;
void _polygons_changed();
public:
virtual void update_property() override;
void setup_single_mode(const StringName &p_property, const String &p_base_type);
void setup_multiple_mode(const StringName &p_property, const StringName &p_count_property, const String &p_element_pattern, const String &p_base_type);
EditorPropertyTilePolygon();
};
class EditorInspectorPluginTileData : public EditorInspectorPlugin {
GDCLASS(EditorInspectorPluginTileData, EditorInspectorPlugin);
void _occlusion_polygon_set_callback();
void _polygons_changed(Object *p_generic_tile_polygon_editor, Object *p_object, const String &p_path);
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 = false) override;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,142 @@
/**************************************************************************/
/* tile_set_editor.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. */
/**************************************************************************/
#pragma once
#include "atlas_merging_dialog.h"
#include "scene/gui/tab_bar.h"
#include "scene/resources/2d/tile_set.h"
#include "tile_proxies_manager_dialog.h"
#include "tile_set_atlas_source_editor.h"
#include "tile_set_scenes_collection_source_editor.h"
class AcceptDialog;
class SpinBox;
class HBoxContainer;
class SplitContainer;
class EditorFileDialog;
class EditorInspectorPlugin;
class TileSetEditor : public MarginContainer {
GDCLASS(TileSetEditor, MarginContainer);
static TileSetEditor *singleton;
private:
bool read_only = false;
Ref<TileSet> tile_set;
bool tile_set_changed_needs_update = false;
HSplitContainer *split_container = nullptr;
// TabBar.
HBoxContainer *tile_set_toolbar = nullptr;
TabBar *tabs_bar = nullptr;
// Tiles.
Label *no_source_selected_label = nullptr;
TileSetAtlasSourceEditor *tile_set_atlas_source_editor = nullptr;
TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor = nullptr;
void _drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
void _load_texture_files(const Vector<String> &p_paths);
void _update_sources_list(int force_selected_id = -1);
// Sources management.
Button *sources_delete_button = nullptr;
MenuButton *sources_add_button = nullptr;
MenuButton *source_sort_button = nullptr;
MenuButton *sources_advanced_menu_button = nullptr;
ItemList *sources_list = nullptr;
Ref<Texture2D> missing_texture_texture;
void _source_selected(int p_source_index);
void _source_delete_pressed();
void _source_add_id_pressed(int p_id_pressed);
void _sources_advanced_menu_id_pressed(int p_id_pressed);
void _set_source_sort(int p_sort);
EditorFileDialog *texture_file_dialog = nullptr;
AtlasMergingDialog *atlas_merging_dialog = nullptr;
TileProxiesManagerDialog *tile_proxies_manager_dialog = nullptr;
bool first_edit = true;
// Patterns.
ItemList *patterns_item_list = nullptr;
Label *patterns_help_label = nullptr;
void _patterns_item_list_gui_input(const Ref<InputEvent> &p_event);
void _pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture);
bool select_last_pattern = false;
void _update_patterns_list();
// Expanded editor.
PanelContainer *expanded_area = nullptr;
Control *expanded_editor = nullptr;
ObjectID expanded_editor_parent;
LocalVector<SplitContainer *> disable_on_expand;
void _tile_set_changed();
void _tab_changed(int p_tab_changed);
void _move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, const String &p_array_prefix, int p_from_index, int p_to_pos);
void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, const String &p_property, const Variant &p_new_value);
protected:
void _notification(int p_what);
public:
_FORCE_INLINE_ static TileSetEditor *get_singleton() { return singleton; }
void edit(Ref<TileSet> p_tile_set);
void add_expanded_editor(Control *p_editor);
void remove_expanded_editor();
void register_split(SplitContainer *p_split);
TileSetEditor();
};
class TileSourceInspectorPlugin : public EditorInspectorPlugin {
GDCLASS(TileSourceInspectorPlugin, EditorInspectorPlugin);
AcceptDialog *id_edit_dialog = nullptr;
Label *id_label = nullptr;
SpinBox *id_input = nullptr;
Object *edited_source = nullptr;
void _show_id_edit_dialog(Object *p_for_source);
void _confirm_change_id();
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 = false) override;
};

View File

@@ -0,0 +1,593 @@
/**************************************************************************/
/* tile_set_scenes_collection_source_editor.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 "tile_set_scenes_collection_source_editor.h"
#include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/file_system/editor_file_system.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/inspector/editor_resource_preview.h"
#include "editor/scene/2d/tiles/tile_set_editor.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/button.h"
#include "scene/gui/item_list.h"
#include "scene/gui/label.h"
#include "scene/gui/split_container.h"
void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::set_id(int p_id) {
ERR_FAIL_COND(p_id < 0);
if (source_id == p_id) {
return;
}
ERR_FAIL_COND_MSG(tile_set->has_source(p_id), vformat("Cannot change TileSet Scenes Collection source ID. Another TileSet source exists with id %d.", p_id));
int previous_source = source_id;
source_id = p_id; // source_id must be updated before, because it's used by the source list update.
tile_set->set_source_id(previous_source, p_id);
emit_signal(CoreStringName(changed), "id");
}
int TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::get_id() {
return source_id;
}
bool TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_set(const StringName &p_name, const Variant &p_value) {
String name = p_name;
if (name == "name") {
// Use the resource_name property to store the source's name.
name = "resource_name";
}
bool valid = false;
tile_set_scenes_collection_source->set(name, p_value, &valid);
if (valid) {
emit_signal(CoreStringName(changed), String(name).utf8().get_data());
}
return valid;
}
bool TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_get(const StringName &p_name, Variant &r_ret) const {
if (!tile_set_scenes_collection_source) {
return false;
}
String name = p_name;
if (name == "name") {
// Use the resource_name property to store the source's name.
name = "resource_name";
}
bool valid = false;
r_ret = tile_set_scenes_collection_source->get(name, &valid);
return valid;
}
void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::STRING, "name", PROPERTY_HINT_NONE, ""));
}
void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_bind_methods() {
// -- Shape and layout --
ClassDB::bind_method(D_METHOD("set_id", "id"), &TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::set_id);
ClassDB::bind_method(D_METHOD("get_id"), &TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::get_id);
ADD_PROPERTY(PropertyInfo(Variant::INT, "id"), "set_id", "get_id");
ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what")));
}
void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id) {
ERR_FAIL_COND(p_tile_set.is_null());
ERR_FAIL_NULL(p_tile_set_scenes_collection_source);
ERR_FAIL_COND(p_source_id < 0);
ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_scenes_collection_source);
if (tile_set == p_tile_set && tile_set_scenes_collection_source == p_tile_set_scenes_collection_source && source_id == p_source_id) {
return;
}
// Disconnect to changes.
if (tile_set_scenes_collection_source) {
tile_set_scenes_collection_source->disconnect(CoreStringName(property_list_changed), callable_mp((Object *)this, &Object::notify_property_list_changed));
}
tile_set = p_tile_set;
tile_set_scenes_collection_source = p_tile_set_scenes_collection_source;
source_id = p_source_id;
// Connect to changes.
if (tile_set_scenes_collection_source) {
if (!tile_set_scenes_collection_source->is_connected(CoreStringName(property_list_changed), callable_mp((Object *)this, &Object::notify_property_list_changed))) {
tile_set_scenes_collection_source->connect(CoreStringName(property_list_changed), callable_mp((Object *)this, &Object::notify_property_list_changed));
}
}
notify_property_list_changed();
}
// -- Proxy object used by the tile inspector --
bool TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_set(const StringName &p_name, const Variant &p_value) {
if (!tile_set_scenes_collection_source) {
return false;
}
if (p_name == "id") {
int as_int = int(p_value);
ERR_FAIL_COND_V(as_int < 0, false);
ERR_FAIL_COND_V(tile_set_scenes_collection_source->has_scene_tile_id(as_int), false);
tile_set_scenes_collection_source->set_scene_tile_id(scene_id, as_int);
scene_id = as_int;
emit_signal(CoreStringName(changed), "id");
for (int i = 0; i < tile_set_scenes_collection_source_editor->scene_tiles_list->get_item_count(); i++) {
if (int(tile_set_scenes_collection_source_editor->scene_tiles_list->get_item_metadata(i)) == scene_id) {
tile_set_scenes_collection_source_editor->scene_tiles_list->select(i);
break;
}
}
return true;
} else if (p_name == "scene") {
tile_set_scenes_collection_source->set_scene_tile_scene(scene_id, p_value);
emit_signal(CoreStringName(changed), "scene");
return true;
} else if (p_name == "display_placeholder") {
tile_set_scenes_collection_source->set_scene_tile_display_placeholder(scene_id, p_value);
emit_signal(CoreStringName(changed), "display_placeholder");
return true;
}
return false;
}
bool TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_get(const StringName &p_name, Variant &r_ret) const {
if (!tile_set_scenes_collection_source) {
return false;
}
if (p_name == "id") {
r_ret = scene_id;
return true;
} else if (p_name == "scene") {
r_ret = tile_set_scenes_collection_source->get_scene_tile_scene(scene_id);
return true;
} else if (p_name == "display_placeholder") {
r_ret = tile_set_scenes_collection_source->get_scene_tile_display_placeholder(scene_id);
return true;
}
return false;
}
void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_get_property_list(List<PropertyInfo> *p_list) const {
if (!tile_set_scenes_collection_source) {
return;
}
p_list->push_back(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_NONE, ""));
p_list->push_back(PropertyInfo(Variant::OBJECT, "scene", PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"));
p_list->push_back(PropertyInfo(Variant::BOOL, "display_placeholder", PROPERTY_HINT_NONE, ""));
}
void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::edit(TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_scene_id) {
ERR_FAIL_NULL(p_tile_set_scenes_collection_source);
ERR_FAIL_COND(!p_tile_set_scenes_collection_source->has_scene_tile_id(p_scene_id));
if (tile_set_scenes_collection_source == p_tile_set_scenes_collection_source && scene_id == p_scene_id) {
return;
}
tile_set_scenes_collection_source = p_tile_set_scenes_collection_source;
scene_id = p_scene_id;
notify_property_list_changed();
}
void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_bind_methods() {
ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what")));
}
void TileSetScenesCollectionSourceEditor::_scenes_collection_source_proxy_object_changed(const String &p_what) {
if (p_what == "id") {
emit_signal(SNAME("source_id_changed"), scenes_collection_source_proxy_object->get_id());
}
}
void TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed() {
tile_set_scenes_collection_source_changed_needs_update = true;
}
void TileSetScenesCollectionSourceEditor::_scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_ud) {
int index = p_ud;
if (index >= 0 && index < scene_tiles_list->get_item_count()) {
scene_tiles_list->set_item_icon(index, p_preview);
}
}
void TileSetScenesCollectionSourceEditor::_scenes_list_item_activated(int p_index) {
Ref<PackedScene> packed_scene = tile_set_scenes_collection_source->get_scene_tile_scene(scene_tiles_list->get_item_metadata(p_index));
if (packed_scene.is_valid()) {
EditorNode::get_singleton()->load_scene(packed_scene->get_path());
}
}
void TileSetScenesCollectionSourceEditor::_source_add_pressed() {
if (!scene_select_dialog) {
scene_select_dialog = memnew(EditorFileDialog);
add_child(scene_select_dialog);
scene_select_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
scene_select_dialog->connect("file_selected", callable_mp(this, &TileSetScenesCollectionSourceEditor::_scene_file_selected));
for (const String &E : Vector<String>{ "tscn", "scn" }) {
scene_select_dialog->add_filter("*." + E, E.to_upper());
}
}
scene_select_dialog->popup_file_dialog();
}
void TileSetScenesCollectionSourceEditor::_scene_file_selected(const String &p_path) {
Ref<PackedScene> scene = ResourceLoader::load(p_path);
int scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id();
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Add a Scene Tile"));
undo_redo->add_do_method(tile_set_scenes_collection_source, "create_scene_tile", scene, scene_id);
undo_redo->add_undo_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id);
undo_redo->commit_action();
_update_scenes_list();
_update_action_buttons();
_update_tile_inspector();
}
void TileSetScenesCollectionSourceEditor::_source_delete_pressed() {
Vector<int> selected_indices = scene_tiles_list->get_selected_items();
ERR_FAIL_COND(selected_indices.is_empty());
int scene_id = scene_tiles_list->get_item_metadata(selected_indices[0]);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Remove a Scene Tile"));
undo_redo->add_do_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id);
undo_redo->add_undo_method(tile_set_scenes_collection_source, "create_scene_tile", tile_set_scenes_collection_source->get_scene_tile_scene(scene_id), scene_id);
undo_redo->commit_action();
_update_scenes_list();
_update_action_buttons();
_update_tile_inspector();
}
void TileSetScenesCollectionSourceEditor::_update_source_inspector() {
// Update the proxy object.
scenes_collection_source_proxy_object->edit(tile_set, tile_set_scenes_collection_source, tile_set_source_id);
}
void TileSetScenesCollectionSourceEditor::_update_tile_inspector() {
Vector<int> selected_indices = scene_tiles_list->get_selected_items();
bool has_atlas_tile_selected = (selected_indices.size() > 0);
// Update the proxy object.
if (has_atlas_tile_selected) {
int scene_id = scene_tiles_list->get_item_metadata(selected_indices[0]);
tile_proxy_object->edit(tile_set_scenes_collection_source, scene_id);
}
// Update visibility.
tile_inspector_label->set_visible(has_atlas_tile_selected);
tile_inspector->set_visible(has_atlas_tile_selected);
}
void TileSetScenesCollectionSourceEditor::_update_action_buttons() {
Vector<int> selected_indices = scene_tiles_list->get_selected_items();
scene_tile_delete_button->set_disabled(selected_indices.is_empty() || read_only);
}
void TileSetScenesCollectionSourceEditor::_update_scenes_list() {
if (!tile_set_scenes_collection_source) {
return;
}
// Get the previously selected id.
Vector<int> selected_indices = scene_tiles_list->get_selected_items();
int old_selected_scene_id = (selected_indices.size() > 0) ? int(scene_tiles_list->get_item_metadata(selected_indices[0])) : -1;
// Clear the list.
scene_tiles_list->clear();
// Rebuild the list.
int to_reselect = -1;
for (int i = 0; i < tile_set_scenes_collection_source->get_scene_tiles_count(); i++) {
int scene_id = tile_set_scenes_collection_source->get_scene_tile_id(i);
Ref<PackedScene> scene = tile_set_scenes_collection_source->get_scene_tile_scene(scene_id);
int item_index = 0;
if (scene.is_valid()) {
item_index = scene_tiles_list->add_item(vformat("%s (path:%s id:%d)", scene->get_path().get_file().get_basename(), scene->get_path(), scene_id));
Variant udata = i;
EditorResourcePreview::get_singleton()->queue_edited_resource_preview(scene, this, "_scene_thumbnail_done", udata);
} else {
item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), get_editor_theme_icon(SNAME("PackedScene")));
}
scene_tiles_list->set_item_metadata(item_index, scene_id);
if (old_selected_scene_id >= 0 && scene_id == old_selected_scene_id) {
to_reselect = i;
}
}
if (scene_tiles_list->get_item_count() == 0) {
scene_tiles_list->add_item(TTR("Drag and drop scenes here or use the Add button."));
scene_tiles_list->set_item_disabled(-1, true);
}
// Reselect if needed.
if (to_reselect >= 0) {
scene_tiles_list->select(to_reselect);
}
// Icon size update.
int int_size = int(EDITOR_GET("filesystem/file_dialog/thumbnail_size")) * EDSCALE;
scene_tiles_list->set_fixed_icon_size(Vector2(int_size, int_size));
}
void TileSetScenesCollectionSourceEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
scenes_collection_source_inspector->edit(scenes_collection_source_proxy_object);
scenes_collection_source_inspector->add_custom_property_description("TileSetScenesCollectionProxyObject", "id", TTR("The tile's unique identifier within this TileSet. Each tile stores its source ID, so changing one may make tiles invalid."));
scenes_collection_source_inspector->add_custom_property_description("TileSetScenesCollectionProxyObject", "name", TTR("The human-readable name for the scene collection. Use a descriptive name here for organizational purposes (such as \"obstacles\", \"decoration\", etc.)."));
tile_inspector->edit(tile_proxy_object);
tile_inspector->add_custom_property_description("SceneTileProxyObject", "id", TTR("ID of the scene tile in the collection. Each painted tile has associated ID, so changing this property may cause your TileMaps to not display properly."));
tile_inspector->add_custom_property_description("SceneTileProxyObject", "scene", TTR("Absolute path to the scene associated with this tile."));
tile_inspector->add_custom_property_description("SceneTileProxyObject", "display_placeholder", TTR("If [code]true[/code], a placeholder marker will be displayed on top of the scene's preview. The marker is displayed anyway if the scene has no valid preview."));
} break;
case NOTIFICATION_THEME_CHANGED: {
scene_tile_add_button->set_button_icon(get_editor_theme_icon(SNAME("Add")));
scene_tile_delete_button->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
_update_scenes_list();
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
if (tile_set_scenes_collection_source_changed_needs_update) {
read_only = false;
// Add the listener again and check for read-only status.
if (tile_set.is_valid()) {
read_only = EditorNode::get_singleton()->is_resource_read_only(tile_set);
}
// Update everything.
_update_source_inspector();
_update_scenes_list();
_update_action_buttons();
_update_tile_inspector();
tile_set_scenes_collection_source_changed_needs_update = false;
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
// Update things just in case.
_update_scenes_list();
_update_action_buttons();
} break;
}
}
void TileSetScenesCollectionSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id) {
ERR_FAIL_COND(p_tile_set.is_null());
ERR_FAIL_NULL(p_tile_set_scenes_collection_source);
ERR_FAIL_COND(p_source_id < 0);
ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_scenes_collection_source);
bool new_read_only_state = false;
if (p_tile_set.is_valid()) {
new_read_only_state = EditorNode::get_singleton()->is_resource_read_only(p_tile_set);
}
if (p_tile_set == tile_set && p_tile_set_scenes_collection_source == tile_set_scenes_collection_source && p_source_id == tile_set_source_id && new_read_only_state == read_only) {
return;
}
// Remove listener for old objects.
if (tile_set_scenes_collection_source) {
tile_set_scenes_collection_source->disconnect_changed(callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed));
}
// Change the edited object.
tile_set = p_tile_set;
tile_set_scenes_collection_source = p_tile_set_scenes_collection_source;
tile_set_source_id = p_source_id;
// Read-only status is false by default
read_only = new_read_only_state;
if (tile_set.is_valid()) {
scenes_collection_source_inspector->set_read_only(read_only);
tile_inspector->set_read_only(read_only);
scene_tile_add_button->set_disabled(read_only);
}
// Add the listener again.
if (tile_set_scenes_collection_source) {
tile_set_scenes_collection_source->connect_changed(callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed));
}
// Update everything.
_update_source_inspector();
_update_scenes_list();
_update_action_buttons();
_update_tile_inspector();
}
void TileSetScenesCollectionSourceEditor::_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
if (!_can_drop_data_fw(p_point, p_data, p_from)) {
return;
}
if (p_from == scene_tiles_list) {
// Handle dropping a texture in the list of atlas resources.
Dictionary d = p_data;
Vector<String> files = d["files"];
for (int i = 0; i < files.size(); i++) {
Ref<PackedScene> resource = ResourceLoader::load(files[i]);
if (resource.is_valid()) {
int scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id();
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Add a Scene Tile"));
undo_redo->add_do_method(tile_set_scenes_collection_source, "create_scene_tile", resource, scene_id);
undo_redo->add_undo_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id);
undo_redo->commit_action();
}
}
_update_scenes_list();
_update_action_buttons();
_update_tile_inspector();
}
}
bool TileSetScenesCollectionSourceEditor::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
if (p_from == scene_tiles_list) {
Dictionary d = p_data;
if (!d.has("type")) {
return false;
}
// Check if we have a Texture2D.
if (String(d["type"]) == "files") {
Vector<String> files = d["files"];
if (files.is_empty()) {
return false;
}
for (int i = 0; i < files.size(); i++) {
String ftype = EditorFileSystem::get_singleton()->get_file_type(files[i]);
if (!ClassDB::is_parent_class(ftype, "PackedScene")) {
return false;
}
}
return true;
}
}
return false;
}
void TileSetScenesCollectionSourceEditor::_bind_methods() {
ADD_SIGNAL(MethodInfo("source_id_changed", PropertyInfo(Variant::INT, "source_id")));
ClassDB::bind_method(D_METHOD("_scene_thumbnail_done"), &TileSetScenesCollectionSourceEditor::_scene_thumbnail_done);
}
TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() {
// -- Right side --
HSplitContainer *split_container_right_side = memnew(HSplitContainer);
split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL);
add_child(split_container_right_side);
// Middle panel.
ScrollContainer *middle_panel = memnew(ScrollContainer);
middle_panel->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
middle_panel->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
split_container_right_side->add_child(middle_panel);
VBoxContainer *middle_vbox_container = memnew(VBoxContainer);
middle_vbox_container->set_h_size_flags(SIZE_EXPAND_FILL);
middle_panel->add_child(middle_vbox_container);
// Scenes collection source inspector.
scenes_collection_source_inspector_label = memnew(Label);
scenes_collection_source_inspector_label->set_focus_mode(FOCUS_ACCESSIBILITY);
scenes_collection_source_inspector_label->set_text(TTR("Scenes collection properties:"));
middle_vbox_container->add_child(scenes_collection_source_inspector_label);
scenes_collection_source_proxy_object = memnew(TileSetScenesCollectionProxyObject());
scenes_collection_source_proxy_object->connect(CoreStringName(changed), callable_mp(this, &TileSetScenesCollectionSourceEditor::_scenes_collection_source_proxy_object_changed));
scenes_collection_source_inspector = memnew(EditorInspector);
scenes_collection_source_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
scenes_collection_source_inspector->set_use_doc_hints(true);
middle_vbox_container->add_child(scenes_collection_source_inspector);
// Tile inspector.
tile_inspector_label = memnew(Label);
tile_inspector_label->set_text(TTR("Tile properties:"));
tile_inspector_label->hide();
middle_vbox_container->add_child(tile_inspector_label);
tile_proxy_object = memnew(SceneTileProxyObject(this));
tile_proxy_object->connect(CoreStringName(changed), callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_scenes_list).unbind(1));
tile_proxy_object->connect(CoreStringName(changed), callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_action_buttons).unbind(1));
tile_inspector = memnew(EditorInspector);
tile_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
tile_inspector->set_use_doc_hints(true);
tile_inspector->set_use_folding(true);
middle_vbox_container->add_child(tile_inspector);
// Scenes list.
VBoxContainer *right_vbox_container = memnew(VBoxContainer);
split_container_right_side->add_child(right_vbox_container);
scene_tiles_list = memnew(ItemList);
scene_tiles_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
scene_tiles_list->set_h_size_flags(SIZE_EXPAND_FILL);
scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL);
SET_DRAG_FORWARDING_CDU(scene_tiles_list, TileSetScenesCollectionSourceEditor);
scene_tiles_list->connect(SceneStringName(item_selected), callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_tile_inspector).unbind(1));
scene_tiles_list->connect(SceneStringName(item_selected), callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_action_buttons).unbind(1));
scene_tiles_list->connect("item_activated", callable_mp(this, &TileSetScenesCollectionSourceEditor::_scenes_list_item_activated));
scene_tiles_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
right_vbox_container->add_child(scene_tiles_list);
HBoxContainer *scenes_bottom_actions = memnew(HBoxContainer);
right_vbox_container->add_child(scenes_bottom_actions);
scene_tile_add_button = memnew(Button);
scene_tile_add_button->set_theme_type_variation(SceneStringName(FlatButton));
scene_tile_add_button->connect(SceneStringName(pressed), callable_mp(this, &TileSetScenesCollectionSourceEditor::_source_add_pressed));
scene_tile_add_button->set_accessibility_name(TTRC("Add"));
scenes_bottom_actions->add_child(scene_tile_add_button);
scene_tile_delete_button = memnew(Button);
scene_tile_delete_button->set_theme_type_variation(SceneStringName(FlatButton));
scene_tile_delete_button->set_disabled(true);
scene_tile_delete_button->connect(SceneStringName(pressed), callable_mp(this, &TileSetScenesCollectionSourceEditor::_source_delete_pressed));
scene_tile_delete_button->set_accessibility_name(TTRC("Delete"));
scenes_bottom_actions->add_child(scene_tile_delete_button);
EditorInspector::add_inspector_plugin(memnew(TileSourceInspectorPlugin));
}
TileSetScenesCollectionSourceEditor::~TileSetScenesCollectionSourceEditor() {
memdelete(scenes_collection_source_proxy_object);
memdelete(tile_proxy_object);
}

View File

@@ -0,0 +1,145 @@
/**************************************************************************/
/* tile_set_scenes_collection_source_editor.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. */
/**************************************************************************/
#pragma once
#include "editor/inspector/editor_inspector.h"
#include "scene/gui/box_container.h"
#include "scene/resources/2d/tile_set.h"
class Button;
class ItemList;
class Label;
class EditorFileDialog;
class TileSetScenesCollectionSourceEditor : public HBoxContainer {
GDCLASS(TileSetScenesCollectionSourceEditor, HBoxContainer);
private:
// -- Proxy object for an atlas source, needed by the inspector --
class TileSetScenesCollectionProxyObject : public Object {
GDCLASS(TileSetScenesCollectionProxyObject, Object);
private:
Ref<TileSet> tile_set;
TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr;
int source_id = -1;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
public:
void set_id(int p_id);
int get_id();
void edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id);
};
// -- Proxy object for a tile, needed by the inspector --
class SceneTileProxyObject : public Object {
GDCLASS(SceneTileProxyObject, Object);
private:
TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor = nullptr;
TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr;
int source_id;
int scene_id;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
public:
// Update the proxied object.
void edit(TileSetScenesCollectionSource *p_tile_set_atlas_source, int p_scene_id);
SceneTileProxyObject(TileSetScenesCollectionSourceEditor *p_tiles_set_scenes_collection_source_editor) {
tile_set_scenes_collection_source_editor = p_tiles_set_scenes_collection_source_editor;
}
};
private:
bool read_only = false;
Ref<TileSet> tile_set;
TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr;
int tile_set_source_id = -1;
bool tile_set_scenes_collection_source_changed_needs_update = false;
// Source inspector.
TileSetScenesCollectionProxyObject *scenes_collection_source_proxy_object = nullptr;
Label *scenes_collection_source_inspector_label = nullptr;
EditorInspector *scenes_collection_source_inspector = nullptr;
// Tile inspector.
SceneTileProxyObject *tile_proxy_object = nullptr;
Label *tile_inspector_label = nullptr;
EditorInspector *tile_inspector = nullptr;
ItemList *scene_tiles_list = nullptr;
Button *scene_tile_add_button = nullptr;
Button *scene_tile_delete_button = nullptr;
EditorFileDialog *scene_select_dialog = nullptr;
void _tile_set_scenes_collection_source_changed();
void _scenes_collection_source_proxy_object_changed(const String &p_what);
void _scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_ud);
void _scenes_list_item_activated(int p_index);
void _source_add_pressed();
void _scene_file_selected(const String &p_path);
void _source_delete_pressed();
// Update methods.
void _update_source_inspector();
void _update_tile_inspector();
void _update_scenes_list();
void _update_action_buttons();
void _drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id);
TileSetScenesCollectionSourceEditor();
~TileSetScenesCollectionSourceEditor();
};

View File

@@ -0,0 +1,555 @@
/**************************************************************************/
/* tiles_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 "tiles_editor_plugin.h"
#include "tile_set_editor.h"
#include "core/os/mutex.h"
#include "editor/editor_interface.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/inspector/multi_node_edit.h"
#include "editor/scene/canvas_item_editor_plugin.h"
#include "editor/settings/editor_command_palette.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/2d/tile_map.h"
#include "scene/2d/tile_map_layer.h"
#include "scene/gui/button.h"
#include "scene/gui/control.h"
#include "scene/resources/2d/tile_set.h"
#include "scene/resources/image_texture.h"
TilesEditorUtils *TilesEditorUtils::singleton = nullptr;
TileMapEditorPlugin *tile_map_plugin_singleton = nullptr;
TileSetEditorPlugin *tile_set_plugin_singleton = nullptr;
void TilesEditorUtils::_preview_frame_started() {
RS::get_singleton()->request_frame_drawn_callback(callable_mp(this, &TilesEditorUtils::_pattern_preview_done));
}
void TilesEditorUtils::_pattern_preview_done() {
pattern_preview_done.post();
}
void TilesEditorUtils::_thread_func(void *ud) {
TilesEditorUtils *te = static_cast<TilesEditorUtils *>(ud);
set_current_thread_safe_for_nodes(true);
te->_thread();
}
void TilesEditorUtils::_thread() {
pattern_thread_exited.clear();
while (!pattern_thread_exit.is_set()) {
pattern_preview_sem.wait();
pattern_preview_mutex.lock();
if (pattern_preview_queue.is_empty()) {
pattern_preview_mutex.unlock();
} else {
QueueItem item = pattern_preview_queue.front()->get();
pattern_preview_queue.pop_front();
pattern_preview_mutex.unlock();
int thumbnail_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
thumbnail_size *= EDSCALE;
Vector2 thumbnail_size2 = Vector2(thumbnail_size, thumbnail_size);
if (item.pattern.is_valid() && !item.pattern->is_empty()) {
// Generate the pattern preview
SubViewport *viewport = memnew(SubViewport);
viewport->set_size(thumbnail_size2);
viewport->set_disable_input(true);
viewport->set_transparent_background(true);
viewport->set_update_mode(SubViewport::UPDATE_ONCE);
TileMapLayer *tile_map_layer = memnew(TileMapLayer);
tile_map_layer->set_tile_set(item.tile_set);
tile_map_layer->set_pattern(Vector2(), item.pattern);
viewport->add_child(tile_map_layer);
Rect2 encompassing_rect;
encompassing_rect.set_position(tile_map_layer->map_to_local(tile_map_layer->get_tile_map_layer_data().begin()->key));
for (KeyValue<Vector2i, CellData> kv : tile_map_layer->get_tile_map_layer_data()) {
Vector2i cell = kv.key;
Vector2 world_pos = tile_map_layer->map_to_local(cell);
encompassing_rect.expand_to(world_pos);
// Texture.
Ref<TileSetAtlasSource> atlas_source = item.tile_set->get_source(tile_map_layer->get_cell_source_id(cell));
if (atlas_source.is_valid()) {
Vector2i coords = tile_map_layer->get_cell_atlas_coords(cell);
int alternative = tile_map_layer->get_cell_alternative_tile(cell);
if (atlas_source->has_tile(coords) && atlas_source->has_alternative_tile(coords, alternative)) {
Vector2 center = world_pos - atlas_source->get_tile_data(coords, alternative)->get_texture_origin();
encompassing_rect.expand_to(center - atlas_source->get_tile_texture_region(coords).size / 2);
encompassing_rect.expand_to(center + atlas_source->get_tile_texture_region(coords).size / 2);
}
}
}
Vector2 scale = thumbnail_size2 / MAX(encompassing_rect.size.x, encompassing_rect.size.y);
tile_map_layer->set_scale(scale);
tile_map_layer->set_position(-(scale * encompassing_rect.get_center()) + thumbnail_size2 / 2);
// Add the viewport at the last moment to avoid rendering too early.
callable_mp((Node *)EditorNode::get_singleton(), &Node::add_child).call_deferred(viewport, false, Node::INTERNAL_MODE_DISABLED);
RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(this, &TilesEditorUtils::_preview_frame_started), Object::CONNECT_ONE_SHOT);
pattern_preview_done.wait();
Ref<Image> image = viewport->get_texture()->get_image();
// Find the index for the given pattern. TODO: optimize.
item.callback.call(item.pattern, ImageTexture::create_from_image(image));
viewport->queue_free();
}
}
}
pattern_thread_exited.set();
}
void TilesEditorUtils::queue_pattern_preview(Ref<TileSet> p_tile_set, Ref<TileMapPattern> p_pattern, Callable p_callback) {
ERR_FAIL_COND(p_tile_set.is_null());
ERR_FAIL_COND(p_pattern.is_null());
{
MutexLock lock(pattern_preview_mutex);
pattern_preview_queue.push_back({ p_tile_set, p_pattern, p_callback });
}
pattern_preview_sem.post();
}
void TilesEditorUtils::set_sources_lists_current(int p_current) {
atlas_sources_lists_current = p_current;
}
void TilesEditorUtils::synchronize_sources_list(Object *p_current_list, Object *p_current_sort_button) {
ItemList *item_list = Object::cast_to<ItemList>(p_current_list);
MenuButton *sorting_button = Object::cast_to<MenuButton>(p_current_sort_button);
ERR_FAIL_NULL(item_list);
ERR_FAIL_NULL(sorting_button);
if (sorting_button->is_visible_in_tree()) {
for (int i = 0; i != SOURCE_SORT_MAX; i++) {
sorting_button->get_popup()->set_item_checked(i, (i == (int)source_sort));
}
}
if (item_list->is_visible_in_tree()) {
// Make sure the selection is not overwritten after sorting.
int atlas_sources_lists_current_mem = atlas_sources_lists_current;
item_list->emit_signal(SNAME("sort_request"));
atlas_sources_lists_current = atlas_sources_lists_current_mem;
if (atlas_sources_lists_current < 0 || atlas_sources_lists_current >= item_list->get_item_count()) {
item_list->deselect_all();
} else {
item_list->set_current(atlas_sources_lists_current);
item_list->ensure_current_is_visible();
item_list->emit_signal(SceneStringName(item_selected), atlas_sources_lists_current);
}
}
}
void TilesEditorUtils::set_atlas_view_transform(float p_zoom, Vector2 p_scroll) {
atlas_view_zoom = p_zoom;
atlas_view_scroll = p_scroll;
}
void TilesEditorUtils::synchronize_atlas_view(Object *p_current) {
TileAtlasView *tile_atlas_view = Object::cast_to<TileAtlasView>(p_current);
ERR_FAIL_NULL(tile_atlas_view);
if (tile_atlas_view->is_visible_in_tree()) {
tile_atlas_view->set_transform(atlas_view_zoom, atlas_view_scroll);
}
}
void TilesEditorUtils::set_sorting_option(int p_option) {
source_sort = p_option;
}
List<int> TilesEditorUtils::get_sorted_sources(const Ref<TileSet> p_tile_set) const {
SourceNameComparator::tile_set = p_tile_set;
List<int> source_ids;
for (int i = 0; i < p_tile_set->get_source_count(); i++) {
source_ids.push_back(p_tile_set->get_source_id(i));
}
switch (source_sort) {
case SOURCE_SORT_ID_REVERSE:
// Already sorted.
source_ids.reverse();
break;
case SOURCE_SORT_NAME:
source_ids.sort_custom<SourceNameComparator>();
break;
case SOURCE_SORT_NAME_REVERSE:
source_ids.sort_custom<SourceNameComparator>();
source_ids.reverse();
break;
default: // SOURCE_SORT_ID
break;
}
SourceNameComparator::tile_set.unref();
return source_ids;
}
Ref<TileSet> TilesEditorUtils::SourceNameComparator::tile_set;
bool TilesEditorUtils::SourceNameComparator::operator()(const int &p_a, const int &p_b) const {
String name_a;
String name_b;
{
TileSetSource *source = *tile_set->get_source(p_a);
if (!source->get_name().is_empty()) {
name_a = source->get_name();
}
TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
if (atlas_source) {
Ref<Texture2D> texture = atlas_source->get_texture();
if (name_a.is_empty() && texture.is_valid()) {
name_a = texture->get_path().get_file();
}
}
if (name_a.is_empty()) {
name_a = itos(p_a);
}
}
{
TileSetSource *source = *tile_set->get_source(p_b);
if (!source->get_name().is_empty()) {
name_b = source->get_name();
}
TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
if (atlas_source) {
Ref<Texture2D> texture = atlas_source->get_texture();
if (name_b.is_empty() && texture.is_valid()) {
name_b = texture->get_path().get_file();
}
}
if (name_b.is_empty()) {
name_b = itos(p_b);
}
}
return NaturalNoCaseComparator()(name_a, name_b);
}
void TilesEditorUtils::display_tile_set_editor_panel() {
tile_map_plugin_singleton->hide_editor();
tile_set_plugin_singleton->make_visible(true);
}
void TilesEditorUtils::draw_selection_rect(CanvasItem *p_ci, const Rect2 &p_rect, const Color &p_color) {
Ref<Texture2D> selection_texture = EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("TileSelection"), EditorStringName(EditorIcons));
real_t scale = p_ci->get_global_transform().get_scale().x * 0.5;
p_ci->draw_set_transform(p_rect.position, 0, Vector2(1, 1) / scale);
RS::get_singleton()->canvas_item_add_nine_patch(
p_ci->get_canvas_item(), Rect2(Vector2(), p_rect.size * scale), Rect2(), selection_texture->get_rid(),
Vector2(2, 2), Vector2(2, 2), RS::NINE_PATCH_STRETCH, RS::NINE_PATCH_STRETCH, false, p_color);
p_ci->draw_set_transform_matrix(Transform2D());
}
TilesEditorUtils::TilesEditorUtils() {
singleton = this;
// Pattern preview generation thread.
pattern_preview_thread.start(_thread_func, this);
ED_SHORTCUT("tiles_editor/cut", TTRC("Cut"), KeyModifierMask::CMD_OR_CTRL | Key::X);
ED_SHORTCUT("tiles_editor/copy", TTRC("Copy"), KeyModifierMask::CMD_OR_CTRL | Key::C);
ED_SHORTCUT("tiles_editor/paste", TTRC("Paste"), KeyModifierMask::CMD_OR_CTRL | Key::V);
ED_SHORTCUT("tiles_editor/cancel", TTRC("Cancel"), Key::ESCAPE);
ED_SHORTCUT("tiles_editor/delete", TTRC("Delete"), Key::KEY_DELETE);
ED_SHORTCUT("tiles_editor/paint_tool", TTRC("Paint Tool"), Key::D);
ED_SHORTCUT("tiles_editor/line_tool", TTRC("Line Tool"), Key::L);
ED_SHORTCUT("tiles_editor/rect_tool", TTRC("Rect Tool"), Key::R);
ED_SHORTCUT("tiles_editor/bucket_tool", TTRC("Bucket Tool"), Key::B);
ED_SHORTCUT("tiles_editor/eraser", TTRC("Eraser Tool"), Key::E);
ED_SHORTCUT("tiles_editor/picker", TTRC("Picker Tool"), Key::P);
}
TilesEditorUtils::~TilesEditorUtils() {
if (pattern_preview_thread.is_started()) {
pattern_thread_exit.set();
pattern_preview_sem.post();
while (!pattern_thread_exited.is_set()) {
OS::get_singleton()->delay_usec(10000);
RenderingServer::get_singleton()->sync(); //sync pending stuff, as thread may be blocked on visual server
}
pattern_preview_thread.wait_to_finish();
}
singleton = nullptr;
}
void TileMapEditorPlugin::_tile_map_layer_changed() {
if (tile_map_changed_needs_update) {
return;
}
tile_map_changed_needs_update = true;
callable_mp(this, &TileMapEditorPlugin::_update_tile_map).call_deferred();
}
void TileMapEditorPlugin::_tile_map_layer_removed() {
// Workaround for TileMap, making sure the editor stays open when you delete the currently edited layer.
TileMap *tile_map = ObjectDB::get_instance<TileMap>(tile_map_group_id);
if (tile_map) {
edit(tile_map);
}
}
void TileMapEditorPlugin::_update_tile_map() {
TileMapLayer *edited_layer = ObjectDB::get_instance<TileMapLayer>(tile_map_layer_id);
if (edited_layer) {
Ref<TileSet> tile_set = edited_layer->get_tile_set();
if (tile_set.is_valid() && tile_set_id != tile_set->get_instance_id()) {
tile_set_plugin_singleton->edit(tile_set.ptr());
tile_set_plugin_singleton->make_visible(true);
tile_set_id = tile_set->get_instance_id();
} else if (tile_set.is_null()) {
tile_set_plugin_singleton->edit(nullptr);
tile_set_plugin_singleton->make_visible(false);
tile_set_id = ObjectID();
}
}
tile_map_changed_needs_update = false;
}
void TileMapEditorPlugin::_select_layer(const StringName &p_name) {
TileMapLayer *edited_layer = ObjectDB::get_instance<TileMapLayer>(tile_map_layer_id);
ERR_FAIL_NULL(edited_layer);
Node *parent = edited_layer->get_parent();
if (parent) {
TileMapLayer *new_layer = Object::cast_to<TileMapLayer>(parent->get_node_or_null(String(p_name)));
edit(new_layer);
}
}
void TileMapEditorPlugin::_edit_tile_map_layer(TileMapLayer *p_tile_map_layer, bool p_show_layer_selector) {
ERR_FAIL_NULL(p_tile_map_layer);
editor->edit(p_tile_map_layer);
editor->set_show_layer_selector(p_show_layer_selector);
// Update the object IDs.
tile_map_layer_id = p_tile_map_layer->get_instance_id();
p_tile_map_layer->connect(CoreStringName(changed), callable_mp(this, &TileMapEditorPlugin::_tile_map_layer_changed));
p_tile_map_layer->connect(SceneStringName(tree_exited), callable_mp(this, &TileMapEditorPlugin::_tile_map_layer_removed));
// Update the edited tileset.
Ref<TileSet> tile_set = p_tile_map_layer->get_tile_set();
if (tile_set.is_valid()) {
tile_set_plugin_singleton->edit(tile_set.ptr());
tile_set_plugin_singleton->make_visible(true);
tile_set_id = tile_set->get_instance_id();
} else {
tile_set_plugin_singleton->edit(nullptr);
tile_set_plugin_singleton->make_visible(false);
}
}
void TileMapEditorPlugin::_edit_tile_map(TileMap *p_tile_map) {
ERR_FAIL_NULL(p_tile_map);
if (p_tile_map->get_layers_count() > 0) {
TileMapLayer *selected_layer = Object::cast_to<TileMapLayer>(p_tile_map->get_child(0));
_edit_tile_map_layer(selected_layer, true);
} else {
editor->edit(nullptr);
editor->set_show_layer_selector(false);
}
}
void TileMapEditorPlugin::_notification(int p_notification) {
if (p_notification == NOTIFICATION_EXIT_TREE) {
get_tree()->queue_delete(TilesEditorUtils::get_singleton());
}
}
void TileMapEditorPlugin::edit(Object *p_object) {
TileMapLayer *edited_layer = ObjectDB::get_instance<TileMapLayer>(tile_map_layer_id);
if (edited_layer) {
edited_layer->disconnect(CoreStringName(changed), callable_mp(this, &TileMapEditorPlugin::_tile_map_layer_changed));
edited_layer->disconnect(SceneStringName(tree_exited), callable_mp(this, &TileMapEditorPlugin::_tile_map_layer_removed));
}
tile_map_group_id = ObjectID();
tile_map_layer_id = ObjectID();
tile_set_id = ObjectID();
TileMap *tile_map = Object::cast_to<TileMap>(p_object);
TileMapLayer *tile_map_layer = Object::cast_to<TileMapLayer>(p_object);
MultiNodeEdit *multi_node_edit = Object::cast_to<MultiNodeEdit>(p_object);
if (tile_map) {
_edit_tile_map(tile_map);
} else if (tile_map_layer) {
_edit_tile_map_layer(tile_map_layer, false);
} else if (multi_node_edit) {
editor->edit(multi_node_edit);
} else {
editor->edit(nullptr);
}
}
bool TileMapEditorPlugin::handles(Object *p_object) const {
MultiNodeEdit *multi_node_edit = Object::cast_to<MultiNodeEdit>(p_object);
Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
if (multi_node_edit && edited_scene) {
bool only_tile_map_layers = true;
for (int i = 0; i < multi_node_edit->get_node_count(); i++) {
if (!Object::cast_to<TileMapLayer>(edited_scene->get_node(multi_node_edit->get_node(i)))) {
only_tile_map_layers = false;
break;
}
}
return only_tile_map_layers;
}
return Object::cast_to<TileMapLayer>(p_object) != nullptr || Object::cast_to<TileMap>(p_object) != nullptr;
}
void TileMapEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
button->show();
EditorNode::get_bottom_panel()->make_item_visible(editor);
} else {
button->hide();
if (editor->is_visible_in_tree()) {
EditorNode::get_bottom_panel()->hide_bottom_panel();
}
}
}
bool TileMapEditorPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
return editor->forward_canvas_gui_input(p_event);
}
void TileMapEditorPlugin::forward_canvas_draw_over_viewport(Control *p_overlay) {
editor->forward_canvas_draw_over_viewport(p_overlay);
}
void TileMapEditorPlugin::hide_editor() {
if (editor->is_visible_in_tree()) {
EditorNode::get_bottom_panel()->hide_bottom_panel();
}
}
bool TileMapEditorPlugin::is_editor_visible() const {
return editor->is_visible_in_tree();
}
TileMapEditorPlugin::TileMapEditorPlugin() {
if (!TilesEditorUtils::get_singleton()) {
memnew(TilesEditorUtils);
}
tile_map_plugin_singleton = this;
editor = memnew(TileMapLayerEditor);
editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
editor->hide();
button = EditorNode::get_bottom_panel()->add_item(TTRC("TileMap"), editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_tile_map_bottom_panel", TTRC("Toggle TileMap Bottom Panel")));
button->hide();
}
TileMapEditorPlugin::~TileMapEditorPlugin() {
tile_map_plugin_singleton = nullptr;
}
void TileSetEditorPlugin::edit(Object *p_object) {
editor->edit(Ref<TileSet>(p_object));
if (p_object) {
edited_tileset = p_object->get_instance_id();
} else {
edited_tileset = ObjectID();
}
}
bool TileSetEditorPlugin::handles(Object *p_object) const {
return Object::cast_to<TileSet>(p_object) != nullptr;
}
void TileSetEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
button->show();
if (!tile_map_plugin_singleton->is_editor_visible()) {
EditorNode::get_bottom_panel()->make_item_visible(editor);
}
} else {
button->hide();
if (editor->is_visible_in_tree()) {
EditorNode::get_bottom_panel()->hide_bottom_panel();
}
}
}
ObjectID TileSetEditorPlugin::get_edited_tileset() const {
return edited_tileset;
}
TileSetEditorPlugin::TileSetEditorPlugin() {
if (!TilesEditorUtils::get_singleton()) {
memnew(TilesEditorUtils);
}
tile_set_plugin_singleton = this;
editor = memnew(TileSetEditor);
editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
editor->hide();
button = EditorNode::get_bottom_panel()->add_item(TTRC("TileSet"), editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_tile_set_bottom_panel", TTRC("Toggle TileSet Bottom Panel")));
button->hide();
}
TileSetEditorPlugin::~TileSetEditorPlugin() {
tile_set_plugin_singleton = nullptr;
}

View File

@@ -0,0 +1,164 @@
/**************************************************************************/
/* tiles_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/plugins/editor_plugin.h"
#include "tile_map_layer_editor.h"
#include "tile_set_editor.h"
class TilesEditorUtils : public Object {
GDCLASS(TilesEditorUtils, Object);
static TilesEditorUtils *singleton;
public:
enum SourceSortOption {
SOURCE_SORT_ID = 0,
SOURCE_SORT_ID_REVERSE,
SOURCE_SORT_NAME,
SOURCE_SORT_NAME_REVERSE,
SOURCE_SORT_MAX
};
private:
// For synchronization.
int atlas_sources_lists_current = 0;
float atlas_view_zoom = 1.0;
Vector2 atlas_view_scroll;
// Source sorting.
int source_sort = SOURCE_SORT_ID;
struct SourceNameComparator {
static Ref<TileSet> tile_set;
bool operator()(const int &p_a, const int &p_b) const;
};
// Patterns preview generation.
struct QueueItem {
Ref<TileSet> tile_set;
Ref<TileMapPattern> pattern;
Callable callback;
};
List<QueueItem> pattern_preview_queue;
Mutex pattern_preview_mutex;
Semaphore pattern_preview_sem;
Thread pattern_preview_thread;
SafeFlag pattern_thread_exit;
SafeFlag pattern_thread_exited;
Semaphore pattern_preview_done;
void _preview_frame_started();
void _pattern_preview_done();
static void _thread_func(void *ud);
void _thread();
public:
_FORCE_INLINE_ static TilesEditorUtils *get_singleton() { return singleton; }
// Pattern preview API.
void queue_pattern_preview(Ref<TileSet> p_tile_set, Ref<TileMapPattern> p_pattern, Callable p_callback);
// To synchronize the atlas sources lists.
void set_sources_lists_current(int p_current);
void synchronize_sources_list(Object *p_current_list, Object *p_current_sort_button);
void set_atlas_view_transform(float p_zoom, Vector2 p_scroll);
void synchronize_atlas_view(Object *p_current);
// Sorting.
void set_sorting_option(int p_option);
List<int> get_sorted_sources(const Ref<TileSet> p_tile_set) const;
// Misc.
void display_tile_set_editor_panel();
static void draw_selection_rect(CanvasItem *p_ci, const Rect2 &p_rect, const Color &p_color = Color(1.0, 1.0, 1.0));
TilesEditorUtils();
~TilesEditorUtils();
};
class TileMapEditorPlugin : public EditorPlugin {
GDCLASS(TileMapEditorPlugin, EditorPlugin);
TileMapLayerEditor *editor = nullptr;
Button *button = nullptr;
ObjectID tile_map_layer_id;
ObjectID tile_map_group_id; // Allow keeping the layer selector up to date.
bool tile_map_changed_needs_update = false;
ObjectID tile_set_id; // The TileSet associated with the TileMap.
void _tile_map_layer_changed();
void _tile_map_layer_removed();
void _update_tile_map();
void _select_layer(const StringName &p_name);
void _edit_tile_map_layer(TileMapLayer *p_tile_map_layer, bool p_show_layer_selector);
void _edit_tile_map(TileMap *p_tile_map);
protected:
void _notification(int p_notification);
public:
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;
virtual void make_visible(bool p_visible) override;
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override;
virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override;
void hide_editor();
bool is_editor_visible() const;
TileMapEditorPlugin();
~TileMapEditorPlugin();
};
class TileSetEditorPlugin : public EditorPlugin {
GDCLASS(TileSetEditorPlugin, EditorPlugin);
TileSetEditor *editor = nullptr;
Button *button = nullptr;
ObjectID edited_tileset;
public:
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;
virtual void make_visible(bool p_visible) override;
ObjectID get_edited_tileset() const;
TileSetEditorPlugin();
~TileSetEditorPlugin();
};

9
editor/scene/3d/SCsub Normal file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.editor_sources, "*.cpp")
SConscript("gizmos/SCsub")
SConscript("physics/SCsub")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,223 @@
/**************************************************************************/
/* bone_map_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/editor_node.h"
#include "editor/inspector/editor_properties.h"
#include "editor/plugins/editor_plugin.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/gui/box_container.h"
#include "scene/gui/color_rect.h"
#include "scene/gui/dialogs.h"
#include "scene/resources/bone_map.h"
#include "scene/resources/texture.h"
class AspectRatioContainer;
class BoneMapperButton : public TextureButton {
GDCLASS(BoneMapperButton, TextureButton);
public:
enum BoneMapState {
BONE_MAP_STATE_UNSET,
BONE_MAP_STATE_SET,
BONE_MAP_STATE_MISSING,
BONE_MAP_STATE_ERROR
};
private:
StringName profile_bone_name;
bool selected = false;
bool require = false;
TextureRect *circle = nullptr;
void fetch_textures();
protected:
void _notification(int p_what);
public:
StringName get_profile_bone_name() const;
void set_state(BoneMapState p_state);
bool is_require() const;
BoneMapperButton(const StringName &p_profile_bone_name, bool p_require, bool p_selected);
};
class BoneMapperItem : public VBoxContainer {
GDCLASS(BoneMapperItem, VBoxContainer);
int button_id = -1;
StringName profile_bone_name;
Ref<BoneMap> bone_map;
EditorPropertyText *skeleton_bone_selector = nullptr;
Button *picker_button = nullptr;
void _update_property();
void _open_picker();
protected:
void _notification(int p_what);
static void _bind_methods();
virtual void _value_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing);
virtual void create_editor();
public:
void assign_button_id(int p_button_id);
BoneMapperItem(Ref<BoneMap> &p_bone_map, const StringName &p_profile_bone_name = StringName());
};
class BonePicker : public AcceptDialog {
GDCLASS(BonePicker, AcceptDialog);
Skeleton3D *skeleton = nullptr;
Tree *bones = nullptr;
public:
void popup_bones_tree(const Size2i &p_minsize = Size2i());
bool has_selected_bone();
StringName get_selected_bone();
protected:
void _notification(int p_what);
void _confirm();
private:
void create_editors();
void create_bones_tree(Skeleton3D *p_skeleton);
public:
BonePicker(Skeleton3D *p_skeleton);
};
class BoneMapper : public VBoxContainer {
GDCLASS(BoneMapper, VBoxContainer);
Skeleton3D *skeleton = nullptr;
Ref<BoneMap> bone_map;
EditorPropertyResource *profile_selector = nullptr;
Vector<BoneMapperItem *> bone_mapper_items;
Button *clear_mapping_button = nullptr;
VBoxContainer *mapper_item_vbox = nullptr;
int current_group_idx = 0;
int current_bone_idx = -1;
AspectRatioContainer *bone_mapper_field = nullptr;
EditorPropertyEnum *profile_group_selector = nullptr;
ColorRect *profile_bg = nullptr;
TextureRect *profile_texture = nullptr;
Vector<BoneMapperButton *> bone_mapper_buttons;
void create_editor();
void recreate_editor();
void clear_items();
void recreate_items();
void update_group_idx();
void _update_state();
/* Bone picker */
BonePicker *picker = nullptr;
StringName picker_key_name;
void _pick_bone(const StringName &p_bone_name);
void _apply_picker_selection();
void _clear_mapping_current_group();
/* For auto mapping */
enum BoneSegregation {
BONE_SEGREGATION_NONE,
BONE_SEGREGATION_LEFT,
BONE_SEGREGATION_RIGHT
};
bool is_match_with_bone_name(const String &p_bone_name, const String &p_word);
int search_bone_by_name(Skeleton3D *p_skeleton, const Vector<String> &p_picklist, BoneSegregation p_segregation = BONE_SEGREGATION_NONE, int p_parent = -1, int p_child = -1, int p_children_count = -1);
BoneSegregation guess_bone_segregation(const String &p_bone_name);
void auto_mapping_process(Ref<BoneMap> &p_bone_map);
void _run_auto_mapping();
protected:
void _notification(int p_what);
static void _bind_methods();
virtual void _value_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing);
virtual void _profile_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing);
public:
void set_current_group_idx(int p_group_idx);
int get_current_group_idx() const;
void set_current_bone_idx(int p_bone_idx);
int get_current_bone_idx() const;
BoneMapper(Skeleton3D *p_skeleton, Ref<BoneMap> &p_bone_map);
};
class BoneMapEditor : public VBoxContainer {
GDCLASS(BoneMapEditor, VBoxContainer);
Skeleton3D *skeleton = nullptr;
Ref<BoneMap> bone_map;
BoneMapper *bone_mapper = nullptr;
void fetch_objects();
void create_editors();
protected:
void _notification(int p_what);
public:
BoneMapEditor(Ref<BoneMap> &p_bone_map);
};
class EditorInspectorPluginBoneMap : public EditorInspectorPlugin {
GDCLASS(EditorInspectorPluginBoneMap, EditorInspectorPlugin);
BoneMapEditor *editor = nullptr;
public:
virtual bool can_handle(Object *p_object) override;
virtual void parse_begin(Object *p_object) override;
};
class BoneMapEditorPlugin : public EditorPlugin {
GDCLASS(BoneMapEditorPlugin, EditorPlugin);
public:
virtual String get_plugin_name() const override { return "BoneMap"; }
BoneMapEditorPlugin();
};

View File

@@ -0,0 +1,128 @@
/**************************************************************************/
/* camera_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 "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) {
node = nullptr;
Node3DEditor::get_singleton()->set_custom_camera(nullptr);
hide();
}
}
void Camera3DEditor::_pressed() {
Node *sn = (node && preview->is_pressed()) ? node : nullptr;
Node3DEditor::get_singleton()->set_custom_camera(sn);
}
void Camera3DEditor::edit(Node *p_camera) {
node = p_camera;
if (!node) {
preview->set_pressed(false);
Node3DEditor::get_singleton()->set_custom_camera(nullptr);
} else {
if (preview->is_pressed()) {
Node3DEditor::get_singleton()->set_custom_camera(p_camera);
} else {
Node3DEditor::get_singleton()->set_custom_camera(nullptr);
}
}
}
Camera3DEditor::Camera3DEditor() {
preview = memnew(Button);
add_child(preview);
preview->set_text(TTR("Preview"));
preview->set_toggle_mode(true);
preview->set_anchor(SIDE_LEFT, Control::ANCHOR_END);
preview->set_anchor(SIDE_RIGHT, Control::ANCHOR_END);
preview->set_offset(SIDE_LEFT, -60);
preview->set_offset(SIDE_RIGHT, 0);
preview->set_offset(SIDE_TOP, 0);
preview->set_offset(SIDE_BOTTOM, 10);
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));
}
bool Camera3DEditorPlugin::handles(Object *p_object) const {
return p_object->is_class("Camera3D");
}
void Camera3DEditorPlugin::make_visible(bool p_visible) {
if (!p_visible) {
Node3DEditor::get_singleton()->set_can_preview(nullptr);
}
}
Camera3DEditorPlugin::Camera3DEditorPlugin() {
Ref<EditorInspectorPluginCamera3DPreview> plugin;
plugin.instantiate();
add_inspector_plugin(plugin);
}

View File

@@ -0,0 +1,87 @@
/**************************************************************************/
/* camera_3d_editor_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. */
/**************************************************************************/
#pragma once
#include "editor/plugins/editor_plugin.h"
#include "editor/scene/texture/texture_editor_plugin.h"
class Camera3D;
class SubViewport;
class Camera3DEditor : public Control {
GDCLASS(Camera3DEditor, Control);
Panel *panel = nullptr;
Button *preview = nullptr;
Node *node = nullptr;
void _pressed();
protected:
void _node_removed(Node *p_node);
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);
public:
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;
virtual void make_visible(bool p_visible) override;
Camera3DEditorPlugin();
};

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.editor_sources, "*.cpp")
SConscript("physics/SCsub")

View File

@@ -0,0 +1,56 @@
/**************************************************************************/
/* audio_listener_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 "audio_listener_3d_gizmo_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "scene/3d/audio_listener_3d.h"
AudioListener3DGizmoPlugin::AudioListener3DGizmoPlugin() {
create_icon_material("audio_listener_3d_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoAudioListener3D"), EditorStringName(EditorIcons)));
}
bool AudioListener3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<AudioListener3D>(p_spatial) != nullptr;
}
String AudioListener3DGizmoPlugin::get_gizmo_name() const {
return "AudioListener3D";
}
int AudioListener3DGizmoPlugin::get_priority() const {
return -1;
}
void AudioListener3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
const Ref<Material> icon = get_material("audio_listener_3d_icon", p_gizmo);
p_gizmo->add_unscaled_billboard(icon, 0.05);
}

View File

@@ -0,0 +1,46 @@
/**************************************************************************/
/* audio_listener_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class AudioListener3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(AudioListener3DGizmoPlugin, EditorNode3DGizmoPlugin);
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const override;
void redraw(EditorNode3DGizmo *p_gizmo) override;
AudioListener3DGizmoPlugin();
};

View File

@@ -0,0 +1,314 @@
/**************************************************************************/
/* audio_stream_player_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 "audio_stream_player_3d_gizmo_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/settings/editor_settings.h"
#include "scene/3d/audio_stream_player_3d.h"
AudioStreamPlayer3DGizmoPlugin::AudioStreamPlayer3DGizmoPlugin() {
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);
create_material("stream_player_3d_material_secondary", gizmo_color * Color(1, 1, 1, 0.35));
// Enable vertex colors for the billboard material as the gizmo color depends on the
// AudioStreamPlayer3D attenuation type and source (Unit Size or Max Distance).
create_material("stream_player_3d_material_billboard", Color(1, 1, 1), true, false, true);
create_handle_material("handles");
}
bool AudioStreamPlayer3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<AudioStreamPlayer3D>(p_spatial) != nullptr;
}
String AudioStreamPlayer3DGizmoPlugin::get_gizmo_name() const {
return "AudioStreamPlayer3D";
}
int AudioStreamPlayer3DGizmoPlugin::get_priority() const {
return -1;
}
String AudioStreamPlayer3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
return "Emission Radius";
}
Variant AudioStreamPlayer3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_node_3d());
return player->get_emission_angle();
}
void AudioStreamPlayer3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_node_3d());
Transform3D gt = player->get_global_transform();
Transform3D gi = gt.affine_inverse();
Vector3 ray_from = p_camera->project_ray_origin(p_point);
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
Vector3 ray_to = ray_from + ray_dir * 4096;
ray_from = gi.xform(ray_from);
ray_to = gi.xform(ray_to);
float closest_dist = 1e20;
float closest_angle = 1e20;
for (int i = 0; i < 180; i++) {
float a = Math::deg_to_rad((float)i);
float an = Math::deg_to_rad((float)(i + 1));
Vector3 from(Math::sin(a), 0, -Math::cos(a));
Vector3 to(Math::sin(an), 0, -Math::cos(an));
Vector3 r1, r2;
Geometry3D::get_closest_points_between_segments(from, to, ray_from, ray_to, r1, r2);
float d = r1.distance_to(r2);
if (d < closest_dist) {
closest_dist = d;
closest_angle = i;
}
}
if (closest_angle < 91) {
player->set_emission_angle(closest_angle);
}
}
void AudioStreamPlayer3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_node_3d());
if (p_cancel) {
player->set_emission_angle(p_restore);
} else {
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Change AudioStreamPlayer3D Emission Angle"));
ur->add_do_method(player, "set_emission_angle", player->get_emission_angle());
ur->add_undo_method(player, "set_emission_angle", p_restore);
ur->commit_action();
}
}
void AudioStreamPlayer3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p_gizmo->clear();
if (p_gizmo->is_selected()) {
const AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_node_3d());
if (player->get_attenuation_model() != AudioStreamPlayer3D::ATTENUATION_DISABLED || player->get_max_distance() > CMP_EPSILON) {
// Draw a circle to represent sound volume attenuation.
// Use only a billboard circle to represent radius.
// This helps distinguish AudioStreamPlayer3D gizmos from OmniLight3D gizmos.
const Ref<Material> lines_billboard_material = get_material("stream_player_3d_material_billboard", p_gizmo);
// Soft distance cap varies depending on attenuation model, as some will fade out more aggressively than others.
// Multipliers were empirically determined through testing.
float soft_multiplier;
switch (player->get_attenuation_model()) {
case AudioStreamPlayer3D::ATTENUATION_INVERSE_DISTANCE:
soft_multiplier = 12.0;
break;
case AudioStreamPlayer3D::ATTENUATION_INVERSE_SQUARE_DISTANCE:
soft_multiplier = 4.0;
break;
case AudioStreamPlayer3D::ATTENUATION_LOGARITHMIC:
soft_multiplier = 3.25;
break;
default:
// Ensures Max Distance's radius visualization is not capped by Unit Size
// (when the attenuation mode is Disabled).
soft_multiplier = 10000.0;
break;
}
// Draw the distance at which the sound can be reasonably heard.
// This can be either a hard distance cap with the Max Distance property (if set above 0.0),
// or a soft distance cap with the Unit Size property (sound never reaches true zero).
// When Max Distance is 0.0, `r` represents the distance above which the
// sound can't be heard in *most* (but not all) scenarios.
float radius = player->get_unit_size() * soft_multiplier;
if (player->get_max_distance() > CMP_EPSILON) {
radius = MIN(radius, player->get_max_distance());
}
#define PUSH_QUARTER_XY(m_from_x, m_from_y, m_to_x, m_to_y, m_y) \
points_ptrw[index++] = Vector3(m_from_x, -m_from_y - m_y, 0); \
points_ptrw[index++] = Vector3(m_to_x, -m_to_y - m_y, 0); \
points_ptrw[index++] = Vector3(m_from_x, m_from_y + m_y, 0); \
points_ptrw[index++] = Vector3(m_to_x, m_to_y + m_y, 0); \
points_ptrw[index++] = Vector3(-m_from_x, -m_from_y - m_y, 0); \
points_ptrw[index++] = Vector3(-m_to_x, -m_to_y - m_y, 0); \
points_ptrw[index++] = Vector3(-m_from_x, m_from_y + m_y, 0); \
points_ptrw[index++] = Vector3(-m_to_x, m_to_y + m_y, 0);
// Number of points in an octant. So there will be 8 * points_in_octant points in total.
// This corresponds to the smoothness of the circle.
const uint32_t points_in_octant = 15;
const real_t octant_angle = Math::PI / 4;
const real_t inc = (Math::PI / (4 * points_in_octant));
const real_t radius_squared = radius * radius;
real_t r = 0;
Vector<Vector3> points_billboard;
points_billboard.resize(8 * points_in_octant * 2);
Vector3 *points_ptrw = points_billboard.ptrw();
uint32_t index = 0;
float previous_x = radius;
float previous_y = 0.f;
for (uint32_t i = 0; i < points_in_octant; i++) {
r += inc;
real_t x = Math::cos((i == points_in_octant - 1) ? octant_angle : r) * radius;
real_t y = Math::sqrt(radius_squared - (x * x));
PUSH_QUARTER_XY(previous_x, previous_y, x, y, 0);
PUSH_QUARTER_XY(previous_y, previous_x, y, x, 0);
previous_x = x;
previous_y = y;
}
#undef PUSH_QUARTER_XY
Color color;
switch (player->get_attenuation_model()) {
// Pick cold colors for all attenuation models (except Disabled),
// so that soft caps can be easily distinguished from hard caps
// (which use warm colors).
case AudioStreamPlayer3D::ATTENUATION_INVERSE_DISTANCE:
color = Color(0.4, 0.8, 1);
break;
case AudioStreamPlayer3D::ATTENUATION_INVERSE_SQUARE_DISTANCE:
color = Color(0.4, 0.5, 1);
break;
case AudioStreamPlayer3D::ATTENUATION_LOGARITHMIC:
color = Color(0.4, 0.2, 1);
break;
default:
// Disabled attenuation mode.
// This is never reached when Max Distance is 0, but the
// hue-inverted form of this color will be used if Max Distance is greater than 0.
color = Color(1, 1, 1);
break;
}
if (player->get_max_distance() > CMP_EPSILON) {
// Sound is hard-capped by max distance. The attenuation model still matters,
// so invert the hue of the color that was chosen above.
color.set_h(color.get_h() + 0.5);
}
p_gizmo->add_lines(points_billboard, lines_billboard_material, true, color);
}
if (player->is_emission_angle_enabled()) {
const float ha = Math::deg_to_rad(player->get_emission_angle());
const float ofs = -Math::cos(ha);
const float radius = Math::sin(ha);
const uint32_t points_in_octant = 7;
const real_t octant_angle = Math::PI / 4;
const real_t inc = (Math::PI / (4 * points_in_octant));
const real_t radius_squared = radius * radius;
real_t r = 0;
Vector<Vector3> points_primary;
points_primary.resize(8 * points_in_octant * 2);
Vector3 *points_ptrw = points_primary.ptrw();
uint32_t index = 0;
float previous_x = radius;
float previous_y = 0.f;
#define PUSH_QUARTER(m_from_x, m_from_y, m_to_x, m_to_y, m_y) \
points_ptrw[index++] = Vector3(m_from_x, -m_from_y, m_y); \
points_ptrw[index++] = Vector3(m_to_x, -m_to_y, m_y); \
points_ptrw[index++] = Vector3(m_from_x, m_from_y, m_y); \
points_ptrw[index++] = Vector3(m_to_x, m_to_y, m_y); \
points_ptrw[index++] = Vector3(-m_from_x, -m_from_y, m_y); \
points_ptrw[index++] = Vector3(-m_to_x, -m_to_y, m_y); \
points_ptrw[index++] = Vector3(-m_from_x, m_from_y, m_y); \
points_ptrw[index++] = Vector3(-m_to_x, m_to_y, m_y);
for (uint32_t i = 0; i < points_in_octant; i++) {
r += inc;
real_t x = Math::cos((i == points_in_octant - 1) ? octant_angle : r) * radius;
real_t y = Math::sqrt(radius_squared - (x * x));
PUSH_QUARTER(previous_x, previous_y, x, y, ofs);
PUSH_QUARTER(previous_y, previous_x, y, x, ofs);
previous_x = x;
previous_y = y;
}
#undef PUSH_QUARTER
const Ref<Material> material_primary = get_material("stream_player_3d_material_primary", p_gizmo);
p_gizmo->add_lines(points_primary, material_primary);
Vector<Vector3> points_secondary;
points_secondary.resize(16);
Vector3 *points_second_ptrw = points_secondary.ptrw();
uint32_t index2 = 0;
// Lines to the circle.
points_second_ptrw[index2++] = Vector3();
points_second_ptrw[index2++] = Vector3(radius, 0, ofs);
points_second_ptrw[index2++] = Vector3();
points_second_ptrw[index2++] = Vector3(-radius, 0, ofs);
points_second_ptrw[index2++] = Vector3();
points_second_ptrw[index2++] = Vector3(0, radius, ofs);
points_second_ptrw[index2++] = Vector3();
points_second_ptrw[index2++] = Vector3(0, -radius, ofs);
real_t octant_value = Math::cos(octant_angle) * radius;
points_second_ptrw[index2++] = Vector3();
points_second_ptrw[index2++] = Vector3(octant_value, octant_value, ofs);
points_second_ptrw[index2++] = Vector3();
points_second_ptrw[index2++] = Vector3(-octant_value, octant_value, ofs);
points_second_ptrw[index2++] = Vector3();
points_second_ptrw[index2++] = Vector3(-octant_value, -octant_value, ofs);
points_second_ptrw[index2++] = Vector3();
points_second_ptrw[index2++] = Vector3(octant_value, -octant_value, ofs);
const Ref<Material> material_secondary = get_material("stream_player_3d_material_secondary", p_gizmo);
p_gizmo->add_lines(points_secondary, material_secondary);
Vector<Vector3> handles;
handles.push_back(Vector3(Math::sin(ha), 0, -Math::cos(ha)));
p_gizmo->add_handles(handles, get_material("handles"));
}
}
const Ref<Material> icon = get_material("stream_player_3d_icon", p_gizmo);
p_gizmo->add_unscaled_billboard(icon, 0.05);
}

View File

@@ -0,0 +1,50 @@
/**************************************************************************/
/* audio_stream_player_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class AudioStreamPlayer3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(AudioStreamPlayer3DGizmoPlugin, EditorNode3DGizmoPlugin);
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const 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;
void redraw(EditorNode3DGizmo *p_gizmo) override;
AudioStreamPlayer3DGizmoPlugin();
};

View File

@@ -0,0 +1,292 @@
/**************************************************************************/
/* camera_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 "camera_3d_gizmo_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/scene/3d/node_3d_editor_plugin.h"
#include "editor/settings/editor_settings.h"
#include "scene/3d/camera_3d.h"
Camera3DGizmoPlugin::Camera3DGizmoPlugin() {
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");
}
bool Camera3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<Camera3D>(p_spatial) != nullptr;
}
String Camera3DGizmoPlugin::get_gizmo_name() const {
return "Camera3D";
}
int Camera3DGizmoPlugin::get_priority() const {
return -1;
}
String Camera3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d());
if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
return "FOV";
} else {
return "Size";
}
}
Variant Camera3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d());
if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
return camera->get_fov();
} else {
return camera->get_size();
}
}
void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d());
Transform3D gt = camera->get_global_transform();
Transform3D gi = gt.affine_inverse();
Vector3 ray_from = p_camera->project_ray_origin(p_point);
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
Vector3 s[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
Transform3D gt2 = camera->get_global_transform();
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), 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());
}
d = CLAMP(d, 0.1, 16384);
camera->set("size", d);
}
}
void Camera3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d());
if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
if (p_cancel) {
camera->set("fov", p_restore);
} else {
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Change Camera FOV"));
ur->add_do_property(camera, "fov", camera->get_fov());
ur->add_undo_property(camera, "fov", p_restore);
ur->commit_action();
}
} else {
if (p_cancel) {
camera->set("size", p_restore);
} else {
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Change Camera Size"));
ur->add_do_property(camera, "size", camera->get_size());
ur->add_undo_property(camera, "size", p_restore);
ur->commit_action();
}
}
}
void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d());
p_gizmo->clear();
Vector<Vector3> lines;
Vector<Vector3> handles;
Ref<Material> material = get_material("camera_material", p_gizmo);
Ref<Material> icon = get_material("camera_icon", p_gizmo);
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);
#define ADD_TRIANGLE(m_a, m_b, m_c) \
{ \
lines.push_back(m_a); \
lines.push_back(m_b); \
lines.push_back(m_b); \
lines.push_back(m_c); \
lines.push_back(m_c); \
lines.push_back(m_a); \
}
#define ADD_QUAD(m_a, m_b, m_c, m_d) \
{ \
lines.push_back(m_a); \
lines.push_back(m_b); \
lines.push_back(m_b); \
lines.push_back(m_c); \
lines.push_back(m_c); \
lines.push_back(m_d); \
lines.push_back(m_d); \
lines.push_back(m_a); \
}
switch (camera->get_projection()) {
case Camera3D::PROJECTION_PERSPECTIVE: {
// The real FOV is halved for accurate representation
float fov = camera->get_fov() / 2.0;
const float hsize = Math::sin(Math::deg_to_rad(fov));
const float depth = -Math::cos(Math::deg_to_rad(fov));
Vector3 side;
if (camera->get_keep_aspect_mode() == Camera3D::KEEP_WIDTH) {
side = Vector3(hsize * size_factor.x, 0, depth * size_factor.x);
} else {
side = Vector3(hsize * size_factor.x, 0, depth * size_factor.y);
}
Vector3 nside = Vector3(-side.x, side.y, side.z);
Vector3 up = Vector3(0, hsize * size_factor.y, 0);
ADD_TRIANGLE(Vector3(), side + up, side - up);
ADD_TRIANGLE(Vector3(), nside + up, nside - up);
ADD_TRIANGLE(Vector3(), side + up, nside + up);
ADD_TRIANGLE(Vector3(), side - up, nside - up);
handles.push_back(side);
side.x = MIN(side.x, hsize * 0.25);
nside.x = -side.x;
Vector3 tup(0, up.y + hsize / 2, side.z);
ADD_TRIANGLE(tup, side + up, nside + up);
} break;
case Camera3D::PROJECTION_ORTHOGONAL: {
Camera3D::KeepAspect aspect = camera->get_keep_aspect_mode();
float size = camera->get_size();
float keep_size = size * 0.5;
Vector3 right, up;
Vector3 back(0, 0, -1.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);
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: {
float hsize = camera->get_size() / 2.0;
Vector3 side = Vector3(hsize, 0, -camera->get_near()).normalized();
side.x *= size_factor.x;
Vector3 nside = Vector3(-side.x, side.y, side.z);
Vector3 up = Vector3(0, hsize * size_factor.y, 0);
Vector3 offset = Vector3(camera->get_frustum_offset().x, camera->get_frustum_offset().y, 0.0);
ADD_TRIANGLE(Vector3(), side + up + offset, side - up + offset);
ADD_TRIANGLE(Vector3(), nside + up + offset, nside - up + offset);
ADD_TRIANGLE(Vector3(), side + up + offset, nside + up + offset);
ADD_TRIANGLE(Vector3(), side - up + offset, nside - up + offset);
side.x = MIN(side.x, hsize * 0.25);
nside.x = -side.x;
Vector3 tup(0, up.y + hsize / 2, side.z);
ADD_TRIANGLE(tup + offset, side + up + offset, nside + up + offset);
} break;
}
#undef ADD_TRIANGLE
#undef ADD_QUAD
p_gizmo->add_lines(lines, material);
p_gizmo->add_unscaled_billboard(icon, 0.05);
p_gizmo->add_collision_segments(lines);
if (!handles.is_empty()) {
p_gizmo->add_handles(handles, get_material("handles"));
}
}
float Camera3DGizmoPlugin::_find_closest_angle_to_half_pi_arc(const Vector3 &p_from, const Vector3 &p_to, float p_arc_radius, const Transform3D &p_arc_xform) {
//bleh, discrete is simpler
static const int arc_test_points = 64;
float min_d = 1e20;
Vector3 min_p;
for (int i = 0; i < arc_test_points; i++) {
float a = i * Math::PI * 0.5 / arc_test_points;
float an = (i + 1) * Math::PI * 0.5 / arc_test_points;
Vector3 p = Vector3(Math::cos(a), 0, -Math::sin(a)) * p_arc_radius;
Vector3 n = Vector3(Math::cos(an), 0, -Math::sin(an)) * p_arc_radius;
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(p, n, p_from, p_to, ra, rb);
float d = ra.distance_to(rb);
if (d < min_d) {
min_d = d;
min_p = ra;
}
}
//min_p = p_arc_xform.affine_inverse().xform(min_p);
float a = (Math::PI * 0.5) - Vector2(min_p.x, -min_p.z).angle();
return Math::rad_to_deg(a);
}

View File

@@ -0,0 +1,53 @@
/**************************************************************************/
/* camera_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class Camera3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Camera3DGizmoPlugin, EditorNode3DGizmoPlugin);
private:
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:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const 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;
void redraw(EditorNode3DGizmo *p_gizmo) override;
Camera3DGizmoPlugin();
};

View File

@@ -0,0 +1,107 @@
/**************************************************************************/
/* cpu_particles_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 "cpu_particles_3d_gizmo_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/settings/editor_settings.h"
#include "scene/3d/cpu_particles_3d.h"
CPUParticles3DGizmoPlugin::CPUParticles3DGizmoPlugin() {
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);
create_icon_material("particles_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoCPUParticles3D"), EditorStringName(EditorIcons)));
}
bool CPUParticles3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<CPUParticles3D>(p_spatial) != nullptr;
}
String CPUParticles3DGizmoPlugin::get_gizmo_name() const {
return "CPUParticles3D";
}
int CPUParticles3DGizmoPlugin::get_priority() const {
return -1;
}
bool CPUParticles3DGizmoPlugin::is_selectable_when_hidden() const {
return true;
}
void CPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
CPUParticles3D *particles = Object::cast_to<CPUParticles3D>(p_gizmo->get_node_3d());
p_gizmo->clear();
Vector<Vector3> lines;
AABB aabb = particles->get_visibility_aabb();
for (int i = 0; i < 12; i++) {
Vector3 a, b;
aabb.get_edge(i, a, b);
lines.push_back(a);
lines.push_back(b);
}
Vector<Vector3> handles;
for (int i = 0; i < 3; i++) {
Vector3 ax;
ax[i] = aabb.position[i] + aabb.size[i];
ax[(i + 1) % 3] = aabb.position[(i + 1) % 3] + aabb.size[(i + 1) % 3] * 0.5;
ax[(i + 2) % 3] = aabb.position[(i + 2) % 3] + aabb.size[(i + 2) % 3] * 0.5;
handles.push_back(ax);
}
Vector3 center = aabb.get_center();
for (int i = 0; i < 3; i++) {
Vector3 ax;
ax[i] = 1.0;
handles.push_back(center + ax);
lines.push_back(center);
lines.push_back(center + ax);
}
Ref<Material> material = get_material("particles_material", p_gizmo);
p_gizmo->add_lines(lines, material);
if (p_gizmo->is_selected()) {
Ref<Material> solid_material = get_material("particles_solid_material", p_gizmo);
p_gizmo->add_solid_box(solid_material, aabb.get_size(), aabb.get_center());
}
Ref<Material> icon = get_material("particles_icon", p_gizmo);
p_gizmo->add_unscaled_billboard(icon, 0.05);
}

View File

@@ -0,0 +1,45 @@
/**************************************************************************/
/* cpu_particles_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class CPUParticles3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(CPUParticles3DGizmoPlugin, EditorNode3DGizmoPlugin);
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;
CPUParticles3DGizmoPlugin();
};

View File

@@ -0,0 +1,131 @@
/**************************************************************************/
/* decal_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 "decal_gizmo_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/scene/3d/gizmos/gizmo_3d_helper.h"
#include "editor/settings/editor_settings.h"
#include "scene/3d/decal.h"
DecalGizmoPlugin::DecalGizmoPlugin() {
helper.instantiate();
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)));
create_material("decal_material", gizmo_color);
create_handle_material("handles");
}
bool DecalGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<Decal>(p_spatial) != nullptr;
}
String DecalGizmoPlugin::get_gizmo_name() const {
return "Decal";
}
int DecalGizmoPlugin::get_priority() const {
return -1;
}
String DecalGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
return helper->box_get_handle_name(p_id);
}
Variant DecalGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
Decal *decal = Object::cast_to<Decal>(p_gizmo->get_node_3d());
return decal->get_size();
}
void DecalGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) {
helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform());
}
void DecalGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
Decal *decal = Object::cast_to<Decal>(p_gizmo->get_node_3d());
Vector3 size = decal->get_size();
Vector3 sg[2];
helper->get_segment(p_camera, p_point, sg);
Vector3 position;
helper->box_set_handle(sg, p_id, size, position);
decal->set_size(size);
decal->set_global_position(position);
}
void DecalGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
helper->box_commit_handle(TTR("Change Decal Size"), p_cancel, p_gizmo->get_node_3d());
}
void DecalGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Decal *decal = Object::cast_to<Decal>(p_gizmo->get_node_3d());
p_gizmo->clear();
Vector<Vector3> lines;
Vector3 size = decal->get_size();
AABB aabb;
aabb.position = -size / 2;
aabb.size = size;
for (int i = 0; i < 12; i++) {
Vector3 a, b;
aabb.get_edge(i, a, b);
if (a.y == b.y) {
lines.push_back(a);
lines.push_back(b);
} else {
Vector3 ah = a.lerp(b, 0.2);
lines.push_back(a);
lines.push_back(ah);
Vector3 bh = b.lerp(a, 0.2);
lines.push_back(b);
lines.push_back(bh);
}
}
float half_size_y = size.y / 2;
lines.push_back(Vector3(0, half_size_y, 0));
lines.push_back(Vector3(0, half_size_y * 1.2, 0));
Vector<Vector3> handles = helper->box_get_handles(decal->get_size());
Ref<Material> material = get_material("decal_material", p_gizmo);
const Ref<Material> icon = get_material("decal_icon", p_gizmo);
p_gizmo->add_lines(lines, material);
p_gizmo->add_unscaled_billboard(icon, 0.05);
p_gizmo->add_handles(handles, get_material("handles"));
}

View File

@@ -0,0 +1,55 @@
/**************************************************************************/
/* decal_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class Gizmo3DHelper;
class DecalGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(DecalGizmoPlugin, EditorNode3DGizmoPlugin);
Ref<Gizmo3DHelper> helper;
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() 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 begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) 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;
DecalGizmoPlugin();
};

View File

@@ -0,0 +1,125 @@
/**************************************************************************/
/* fog_volume_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 "fog_volume_gizmo_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/scene/3d/gizmos/gizmo_3d_helper.h"
#include "editor/settings/editor_settings.h"
#include "scene/3d/fog_volume.h"
FogVolumeGizmoPlugin::FogVolumeGizmoPlugin() {
helper.instantiate();
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);
create_icon_material("fog_volume_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoFogVolume"), EditorStringName(EditorIcons)));
create_handle_material("handles");
}
bool FogVolumeGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return (Object::cast_to<FogVolume>(p_spatial) != nullptr);
}
String FogVolumeGizmoPlugin::get_gizmo_name() const {
return "FogVolume";
}
int FogVolumeGizmoPlugin::get_priority() const {
return -1;
}
String FogVolumeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
return helper->box_get_handle_name(p_id);
}
Variant FogVolumeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
return Vector3(p_gizmo->get_node_3d()->call("get_size"));
}
void FogVolumeGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) {
helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform());
}
void FogVolumeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
FogVolume *fog_volume = Object::cast_to<FogVolume>(p_gizmo->get_node_3d());
Vector3 size = fog_volume->get_size();
Vector3 sg[2];
helper->get_segment(p_camera, p_point, sg);
Vector3 position;
helper->box_set_handle(sg, p_id, size, position);
fog_volume->set_size(size);
fog_volume->set_global_position(position);
}
void FogVolumeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
helper->box_commit_handle(TTR("Change FogVolume Size"), p_cancel, p_gizmo->get_node_3d());
}
void FogVolumeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
FogVolume *fog_volume = Object::cast_to<FogVolume>(p_gizmo->get_node_3d());
p_gizmo->clear();
if (fog_volume->get_shape() != RS::FOG_VOLUME_SHAPE_WORLD) {
const Ref<Material> material =
get_material("shape_material", p_gizmo);
const Ref<Material> material_internal =
get_material("shape_material_internal", p_gizmo);
Ref<Material> handles_material = get_material("handles");
Vector<Vector3> lines;
AABB aabb;
aabb.size = fog_volume->get_size();
aabb.position = aabb.size / -2;
for (int i = 0; i < 12; i++) {
Vector3 a, b;
aabb.get_edge(i, a, b);
lines.push_back(a);
lines.push_back(b);
}
Vector<Vector3> handles = helper->box_get_handles(fog_volume->get_size());
p_gizmo->add_lines(lines, material);
p_gizmo->add_collision_segments(lines);
const Ref<Material> icon = get_material("fog_volume_icon", p_gizmo);
p_gizmo->add_unscaled_billboard(icon, 0.05);
p_gizmo->add_handles(handles, handles_material);
}
}

View File

@@ -0,0 +1,55 @@
/**************************************************************************/
/* fog_volume_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class Gizmo3DHelper;
class FogVolumeGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(FogVolumeGizmoPlugin, EditorNode3DGizmoPlugin);
Ref<Gizmo3DHelper> helper;
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() 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 begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) 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;
FogVolumeGizmoPlugin();
};

View File

@@ -0,0 +1,74 @@
/**************************************************************************/
/* geometry_instance_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 "geometry_instance_3d_gizmo_plugin.h"
#include "editor/settings/editor_settings.h"
#include "scene/3d/visual_instance_3d.h"
bool GeometryInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<GeometryInstance3D>(p_spatial) != nullptr;
}
String GeometryInstance3DGizmoPlugin::get_gizmo_name() const {
return "MeshInstance3DCustomAABB";
}
int GeometryInstance3DGizmoPlugin::get_priority() const {
return -1;
}
void GeometryInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
GeometryInstance3D *geometry = Object::cast_to<GeometryInstance3D>(p_gizmo->get_node_3d());
p_gizmo->clear();
if (p_gizmo->is_selected()) {
AABB aabb = geometry->get_custom_aabb();
Vector<Vector3> lines;
for (int i = 0; i < 12; i++) {
Vector3 a;
Vector3 b;
aabb.get_edge(i, a, b);
lines.push_back(a);
lines.push_back(b);
}
Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
const Color selection_box_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/aabb");
mat->set_albedo(selection_box_color);
mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
p_gizmo->add_lines(lines, mat);
}
}

View File

@@ -0,0 +1,44 @@
/**************************************************************************/
/* geometry_instance_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class GeometryInstance3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(GeometryInstance3DGizmoPlugin, EditorNode3DGizmoPlugin);
public:
virtual bool has_gizmo(Node3D *p_spatial) override;
virtual String get_gizmo_name() const override;
virtual int get_priority() const override;
virtual void redraw(EditorNode3DGizmo *p_gizmo) override;
};

View File

@@ -0,0 +1,243 @@
/**************************************************************************/
/* gizmo_3d_helper.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 "gizmo_3d_helper.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/scene/3d/node_3d_editor_plugin.h"
#include "scene/3d/camera_3d.h"
void Gizmo3DHelper::initialize_handle_action(const Variant &p_initial_value, const Transform3D &p_initial_transform) {
initial_value = p_initial_value;
initial_transform = p_initial_transform;
}
void Gizmo3DHelper::get_segment(Camera3D *p_camera, const Point2 &p_point, Vector3 *r_segment) {
Transform3D gt = initial_transform;
Transform3D gi = gt.affine_inverse();
Vector3 ray_from = p_camera->project_ray_origin(p_point);
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
r_segment[0] = gi.xform(ray_from);
r_segment[1] = gi.xform(ray_from + ray_dir * 4096);
}
Vector<Vector3> Gizmo3DHelper::box_get_handles(const Vector3 &p_box_size) {
Vector<Vector3> handles;
for (int i = 0; i < 3; i++) {
Vector3 ax;
ax[i] = p_box_size[i] / 2;
handles.push_back(ax);
handles.push_back(-ax);
}
return handles;
}
String Gizmo3DHelper::box_get_handle_name(int p_id) const {
switch (p_id) {
case 0:
case 1:
return "Size X";
case 2:
case 3:
return "Size Y";
case 4:
case 5:
return "Size Z";
}
return "";
}
void Gizmo3DHelper::box_set_handle(const Vector3 p_segment[2], int p_id, Vector3 &r_box_size, Vector3 &r_box_position) {
int axis = p_id / 2;
int sign = p_id % 2 * -2 + 1;
Vector3 initial_size = initial_value;
float neg_end = initial_size[axis] * -0.5;
float pos_end = initial_size[axis] * 0.5;
Vector3 axis_segment[2] = { Vector3(), Vector3() };
axis_segment[0][axis] = 4096.0;
axis_segment[1][axis] = -4096.0;
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(axis_segment[0], axis_segment[1], p_segment[0], p_segment[1], ra, rb);
// Calculate new size.
r_box_size = initial_size;
if (Input::get_singleton()->is_key_pressed(Key::ALT)) {
r_box_size[axis] = ra[axis] * sign * 2;
} else {
r_box_size[axis] = sign > 0 ? ra[axis] - neg_end : pos_end - ra[axis];
}
// Snap to grid.
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
r_box_size[axis] = Math::snapped(r_box_size[axis], Node3DEditor::get_singleton()->get_translate_snap());
}
r_box_size[axis] = MAX(r_box_size[axis], 0.001);
// Adjust position.
if (Input::get_singleton()->is_key_pressed(Key::ALT)) {
r_box_position = initial_transform.get_origin();
} else {
if (sign > 0) {
pos_end = neg_end + r_box_size[axis];
} else {
neg_end = pos_end - r_box_size[axis];
}
Vector3 offset;
offset[axis] = (pos_end + neg_end) * 0.5;
r_box_position = initial_transform.xform(offset);
}
}
void Gizmo3DHelper::box_commit_handle(const String &p_action_name, bool p_cancel, Object *p_position_object, Object *p_size_object, const StringName &p_position_property, const StringName &p_size_property) {
if (!p_size_object) {
p_size_object = p_position_object;
}
if (p_cancel) {
p_size_object->set(p_size_property, initial_value);
p_position_object->set(p_position_property, initial_transform.get_origin());
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(p_action_name);
ur->add_do_property(p_size_object, p_size_property, p_size_object->get(p_size_property));
ur->add_do_property(p_position_object, p_position_property, p_position_object->get(p_position_property));
ur->add_undo_property(p_size_object, p_size_property, initial_value);
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_or_capsule_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius, Vector3 &r_cylinder_position, bool p_is_capsule) {
real_t initial_radius = initial_value.operator Vector2().x;
real_t initial_height = initial_value.operator Vector2().y;
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();
if (p_is_capsule) {
r_height = MAX(initial_height, r_radius * 2.0);
} else {
r_height = initial_height;
}
} else if (p_id == 1 || p_id == 2) {
// 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);
}
if (p_is_capsule) {
r_radius = MIN(initial_radius, r_height / 2.0);
} else {
r_radius = initial_radius;
}
}
}
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) {
p_radius_object->set(p_radius_property, initial_value.operator Vector2().x);
p_height_object->set(p_height_property, initial_value.operator Vector2().y);
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);
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.operator Vector2().x);
ur->add_do_property(p_height_object, p_height_property, p_height_object->get(p_height_property));
ur->add_undo_property(p_height_object, p_height_property, initial_value.operator Vector2().y);
ur->add_do_property(p_position_object, p_position_property, p_position_object->get(p_position_property));
ur->add_undo_property(p_position_object, p_position_property, initial_transform.get_origin());
ur->commit_action();
}

View File

@@ -0,0 +1,84 @@
/**************************************************************************/
/* gizmo_3d_helper.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. */
/**************************************************************************/
#pragma once
#include "core/object/ref_counted.h"
class Camera3D;
class Gizmo3DHelper : public RefCounted {
GDCLASS(Gizmo3DHelper, RefCounted);
int current_handle_id;
Variant initial_value;
Transform3D initial_transform;
private:
void _cylinder_or_capsule_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius, Vector3 &r_cylinder_position, bool p_is_capsule);
public:
/**
* Initializes a new action involving a handle.
*
* Depending on the type of gizmo that will be used, different formats for the `p_initial_value` are required:
* Box: The size of the box as `Vector3`
* Cylinder or Capsule: A `Vector2` of the form `Vector2(radius, height)`
*/
void initialize_handle_action(const Variant &p_initial_value, const Transform3D &p_initial_transform);
void get_segment(Camera3D *p_camera, const Point2 &p_point, Vector3 *r_segment);
// Box
Vector<Vector3> box_get_handles(const Vector3 &p_box_size);
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");
// Cylinder
Vector<Vector3> cylinder_get_handles(real_t p_height, real_t p_radius);
String cylinder_get_handle_name(int p_id) const;
_FORCE_INLINE_ void cylinder_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius, Vector3 &r_cylinder_position) {
_cylinder_or_capsule_set_handle(p_segment, p_id, r_height, r_radius, r_cylinder_position, false);
}
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");
// Capsule
_FORCE_INLINE_ Vector<Vector3> capsule_get_handles(real_t p_height, real_t p_radius) { return cylinder_get_handles(p_height, p_radius); }
_FORCE_INLINE_ String capsule_get_handle_name(int p_id) { return cylinder_get_handle_name(p_id); }
_FORCE_INLINE_ void capsule_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius, Vector3 &r_capsule_position) {
_cylinder_or_capsule_set_handle(p_segment, p_id, r_height, r_radius, r_capsule_position, true);
}
_FORCE_INLINE_ void capsule_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") {
cylinder_commit_handle(p_id, p_radius_action_name, p_height_action_name, p_cancel, p_position_object, p_height_object, p_radius_object, p_position_property, p_height_property, p_radius_property);
}
};

View File

@@ -0,0 +1,84 @@
/**************************************************************************/
/* gpu_particles_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 "gpu_particles_3d_gizmo_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/settings/editor_settings.h"
#include "scene/3d/gpu_particles_3d.h"
GPUParticles3DGizmoPlugin::GPUParticles3DGizmoPlugin() {
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)));
}
bool GPUParticles3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<GPUParticles3D>(p_spatial) != nullptr;
}
String GPUParticles3DGizmoPlugin::get_gizmo_name() const {
return "GPUParticles3D";
}
int GPUParticles3DGizmoPlugin::get_priority() const {
return -1;
}
bool GPUParticles3DGizmoPlugin::is_selectable_when_hidden() const {
return true;
}
void GPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p_gizmo->clear();
if (p_gizmo->is_selected()) {
GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_node_3d());
Vector<Vector3> lines;
AABB aabb = particles->get_visibility_aabb();
for (int i = 0; i < 12; i++) {
Vector3 a, b;
aabb.get_edge(i, a, b);
lines.push_back(a);
lines.push_back(b);
}
Ref<Material> material = get_material("particles_material", p_gizmo);
p_gizmo->add_lines(lines, material);
}
Ref<Material> icon = get_material("particles_icon", p_gizmo);
p_gizmo->add_unscaled_billboard(icon, 0.05);
}

View File

@@ -0,0 +1,46 @@
/**************************************************************************/
/* gpu_particles_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class GPUParticles3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(GPUParticles3DGizmoPlugin, EditorNode3DGizmoPlugin);
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;
GPUParticles3DGizmoPlugin();
};

View File

@@ -0,0 +1,306 @@
/**************************************************************************/
/* gpu_particles_collision_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 "gpu_particles_collision_3d_gizmo_plugin.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/scene/3d/gizmos/gizmo_3d_helper.h"
#include "editor/scene/3d/node_3d_editor_plugin.h"
#include "editor/settings/editor_settings.h"
#include "scene/3d/gpu_particles_collision_3d.h"
GPUParticlesCollision3DGizmoPlugin::GPUParticlesCollision3DGizmoPlugin() {
helper.instantiate();
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_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);
create_handle_material("handles");
}
bool GPUParticlesCollision3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return (Object::cast_to<GPUParticlesCollision3D>(p_spatial) != nullptr) || (Object::cast_to<GPUParticlesAttractor3D>(p_spatial) != nullptr);
}
String GPUParticlesCollision3DGizmoPlugin::get_gizmo_name() const {
return "GPUParticlesCollision3D";
}
int GPUParticlesCollision3DGizmoPlugin::get_priority() const {
return -1;
}
String GPUParticlesCollision3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
const Node3D *cs = p_gizmo->get_node_3d();
if (Object::cast_to<GPUParticlesCollisionSphere3D>(cs) || Object::cast_to<GPUParticlesAttractorSphere3D>(cs)) {
return "Radius";
}
if (Object::cast_to<GPUParticlesCollisionBox3D>(cs) || Object::cast_to<GPUParticlesAttractorBox3D>(cs) || Object::cast_to<GPUParticlesAttractorVectorField3D>(cs) || Object::cast_to<GPUParticlesCollisionSDF3D>(cs) || Object::cast_to<GPUParticlesCollisionHeightField3D>(cs)) {
return helper->box_get_handle_name(p_id);
}
return "";
}
Variant GPUParticlesCollision3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
const Node3D *cs = p_gizmo->get_node_3d();
if (Object::cast_to<GPUParticlesCollisionSphere3D>(cs) || Object::cast_to<GPUParticlesAttractorSphere3D>(cs)) {
return p_gizmo->get_node_3d()->call("get_radius");
}
if (Object::cast_to<GPUParticlesCollisionBox3D>(cs) || Object::cast_to<GPUParticlesAttractorBox3D>(cs) || Object::cast_to<GPUParticlesAttractorVectorField3D>(cs) || Object::cast_to<GPUParticlesCollisionSDF3D>(cs) || Object::cast_to<GPUParticlesCollisionHeightField3D>(cs)) {
return Vector3(p_gizmo->get_node_3d()->call("get_size"));
}
return Variant();
}
void GPUParticlesCollision3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) {
helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform());
}
void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
Node3D *sn = p_gizmo->get_node_3d();
Vector3 sg[2];
helper->get_segment(p_camera, p_point, sg);
if (Object::cast_to<GPUParticlesCollisionSphere3D>(sn) || Object::cast_to<GPUParticlesAttractorSphere3D>(sn)) {
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb);
float d = ra.x;
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
}
if (d < 0.001) {
d = 0.001;
}
sn->call("set_radius", d);
}
if (Object::cast_to<GPUParticlesCollisionBox3D>(sn) || Object::cast_to<GPUParticlesAttractorBox3D>(sn) || Object::cast_to<GPUParticlesAttractorVectorField3D>(sn) || Object::cast_to<GPUParticlesCollisionSDF3D>(sn) || Object::cast_to<GPUParticlesCollisionHeightField3D>(sn)) {
Vector3 size = sn->call("get_size");
Vector3 position;
helper->box_set_handle(sg, p_id, size, position);
sn->call("set_size", size);
sn->set_global_position(position);
}
}
void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
Node3D *sn = p_gizmo->get_node_3d();
if (Object::cast_to<GPUParticlesCollisionSphere3D>(sn) || Object::cast_to<GPUParticlesAttractorSphere3D>(sn)) {
if (p_cancel) {
sn->call("set_radius", p_restore);
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Change Radius"));
ur->add_do_method(sn, "set_radius", sn->call("get_radius"));
ur->add_undo_method(sn, "set_radius", p_restore);
ur->commit_action();
}
if (Object::cast_to<GPUParticlesCollisionBox3D>(sn) || Object::cast_to<GPUParticlesAttractorBox3D>(sn) || Object::cast_to<GPUParticlesAttractorVectorField3D>(sn) || Object::cast_to<GPUParticlesCollisionSDF3D>(sn) || Object::cast_to<GPUParticlesCollisionHeightField3D>(sn)) {
helper->box_commit_handle("Change Box Shape Size", p_cancel, sn);
}
}
void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Node3D *cs = p_gizmo->get_node_3d();
p_gizmo->clear();
Ref<Material> material;
Ref<Material> material_internal;
if (Object::cast_to<GPUParticlesAttractor3D>(cs)) {
material = get_material("shape_material_attractor", p_gizmo);
material_internal = get_material("shape_material_attractor_internal", p_gizmo);
} else {
material = get_material("shape_material_collision", p_gizmo);
material_internal = get_material("shape_material_collision_internal", p_gizmo);
}
const Ref<Material> handles_material = get_material("handles");
if (Object::cast_to<GPUParticlesCollisionSphere3D>(cs) || Object::cast_to<GPUParticlesAttractorSphere3D>(cs)) {
float radius = cs->call("get_radius");
#define PUSH_QUARTER(m_from_x, m_from_y, m_to_x, m_to_y, m_y) \
points_ptrw[index++] = Vector3(m_from_x, m_y, m_from_y); \
points_ptrw[index++] = Vector3(m_to_x, m_y, m_to_y); \
points_ptrw[index++] = Vector3(m_from_x, m_y, -m_from_y); \
points_ptrw[index++] = Vector3(m_to_x, m_y, -m_to_y); \
points_ptrw[index++] = Vector3(-m_from_x, m_y, m_from_y); \
points_ptrw[index++] = Vector3(-m_to_x, m_y, m_to_y); \
points_ptrw[index++] = Vector3(-m_from_x, m_y, -m_from_y); \
points_ptrw[index++] = Vector3(-m_to_x, m_y, -m_to_y);
#define PUSH_QUARTER_XY(m_from_x, m_from_y, m_to_x, m_to_y) \
points_ptrw[index++] = Vector3(m_from_x, -m_from_y, 0); \
points_ptrw[index++] = Vector3(m_to_x, -m_to_y, 0); \
points_ptrw[index++] = Vector3(m_from_x, m_from_y, 0); \
points_ptrw[index++] = Vector3(m_to_x, m_to_y, 0); \
points_ptrw[index++] = Vector3(-m_from_x, -m_from_y, 0); \
points_ptrw[index++] = Vector3(-m_to_x, -m_to_y, 0); \
points_ptrw[index++] = Vector3(-m_from_x, m_from_y, 0); \
points_ptrw[index++] = Vector3(-m_to_x, m_to_y, 0);
#define PUSH_QUARTER_YZ(m_from_x, m_from_y, m_to_x, m_to_y) \
points_ptrw[index++] = Vector3(0, -m_from_y, m_from_x); \
points_ptrw[index++] = Vector3(0, -m_to_y, m_to_x); \
points_ptrw[index++] = Vector3(0, m_from_y, m_from_x); \
points_ptrw[index++] = Vector3(0, m_to_y, m_to_x); \
points_ptrw[index++] = Vector3(0, -m_from_y, -m_from_x); \
points_ptrw[index++] = Vector3(0, -m_to_y, -m_to_x); \
points_ptrw[index++] = Vector3(0, m_from_y, -m_from_x); \
points_ptrw[index++] = Vector3(0, m_to_y, -m_to_x);
// Number of points in an octant. So there will be 8 * points_in_octant points in total.
// This corresponds to the smoothness of the circle.
const uint32_t points_in_octant = 16;
const real_t octant_angle = Math::PI / 4;
const real_t inc = (Math::PI / (4 * points_in_octant));
const real_t radius_squared = radius * radius;
real_t r = 0;
Vector<Vector3> points;
points.resize(3 * 8 * points_in_octant * 2);
Vector3 *points_ptrw = points.ptrw();
uint32_t index = 0;
float previous_x = radius;
float previous_y = 0.f;
for (uint32_t i = 0; i < points_in_octant; ++i) {
r += inc;
real_t x = Math::cos((i == points_in_octant - 1) ? octant_angle : r) * radius;
real_t y = Math::sqrt(radius_squared - (x * x));
PUSH_QUARTER(previous_x, previous_y, x, y, 0);
PUSH_QUARTER(previous_y, previous_x, y, x, 0);
PUSH_QUARTER_XY(previous_x, previous_y, x, y);
PUSH_QUARTER_XY(previous_y, previous_x, y, x);
PUSH_QUARTER_YZ(previous_x, previous_y, x, y);
PUSH_QUARTER_YZ(previous_y, previous_x, y, x);
previous_x = x;
previous_y = y;
}
p_gizmo->add_lines(points, material);
p_gizmo->add_collision_segments(points);
Vector<Vector3> handles;
handles.push_back(Vector3(r, 0, 0));
p_gizmo->add_handles(handles, handles_material);
#undef PUSH_QUARTER
#undef PUSH_QUARTER_XY
#undef PUSH_QUARTER_YZ
}
if (Object::cast_to<GPUParticlesCollisionBox3D>(cs) || Object::cast_to<GPUParticlesAttractorBox3D>(cs) || Object::cast_to<GPUParticlesAttractorVectorField3D>(cs) || Object::cast_to<GPUParticlesCollisionSDF3D>(cs) || Object::cast_to<GPUParticlesCollisionHeightField3D>(cs)) {
Vector<Vector3> lines;
AABB aabb;
aabb.size = cs->call("get_size").operator Vector3();
aabb.position = aabb.size / -2;
for (int i = 0; i < 12; i++) {
Vector3 a, b;
aabb.get_edge(i, a, b);
lines.push_back(a);
lines.push_back(b);
}
Vector<Vector3> handles = helper->box_get_handles(aabb.size);
p_gizmo->add_lines(lines, material);
p_gizmo->add_collision_segments(lines);
p_gizmo->add_handles(handles, handles_material);
GPUParticlesCollisionSDF3D *col_sdf = Object::cast_to<GPUParticlesCollisionSDF3D>(cs);
if (col_sdf) {
static const int subdivs[GPUParticlesCollisionSDF3D::RESOLUTION_MAX] = { 16, 32, 64, 128, 256, 512 };
int subdiv = subdivs[col_sdf->get_resolution()];
float cell_size = aabb.get_longest_axis_size() / subdiv;
lines.clear();
for (int i = 1; i < subdiv; i++) {
for (int j = 0; j < 3; j++) {
if (cell_size * i > aabb.size[j]) {
continue;
}
int j_n1 = (j + 1) % 3;
int j_n2 = (j + 2) % 3;
for (int k = 0; k < 4; k++) {
Vector3 from = aabb.position, to = aabb.position;
from[j] += cell_size * i;
to[j] += cell_size * i;
if (k & 1) {
to[j_n1] += aabb.size[j_n1];
} else {
to[j_n2] += aabb.size[j_n2];
}
if (k & 2) {
from[j_n1] += aabb.size[j_n1];
from[j_n2] += aabb.size[j_n2];
}
lines.push_back(from);
lines.push_back(to);
}
}
}
p_gizmo->add_lines(lines, material_internal);
}
}
}

View File

@@ -0,0 +1,55 @@
/**************************************************************************/
/* gpu_particles_collision_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class Gizmo3DHelper;
class GPUParticlesCollision3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(GPUParticlesCollision3DGizmoPlugin, EditorNode3DGizmoPlugin);
Ref<Gizmo3DHelper> helper;
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() 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 begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) 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;
GPUParticlesCollision3DGizmoPlugin();
};

View File

@@ -0,0 +1,60 @@
/**************************************************************************/
/* label_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 "label_3d_gizmo_plugin.h"
#include "scene/3d/label_3d.h"
bool Label3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<Label3D>(p_spatial) != nullptr;
}
String Label3DGizmoPlugin::get_gizmo_name() const {
return "Label3D";
}
int Label3DGizmoPlugin::get_priority() const {
return -1;
}
bool Label3DGizmoPlugin::can_be_hidden() const {
return false;
}
void Label3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Label3D *label = Object::cast_to<Label3D>(p_gizmo->get_node_3d());
p_gizmo->clear();
Ref<TriangleMesh> tm = label->generate_triangle_mesh();
if (tm.is_valid()) {
p_gizmo->add_collision_triangles(tm);
}
}

View File

@@ -0,0 +1,44 @@
/**************************************************************************/
/* label_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class Label3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Label3DGizmoPlugin, EditorNode3DGizmoPlugin);
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const override;
bool can_be_hidden() const override;
void redraw(EditorNode3DGizmo *p_gizmo) override;
};

View File

@@ -0,0 +1,316 @@
/**************************************************************************/
/* light_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 "light_3d_gizmo_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/scene/3d/node_3d_editor_plugin.h"
#include "scene/3d/light_3d.h"
Light3DGizmoPlugin::Light3DGizmoPlugin() {
// Enable vertex colors for the materials below as the gizmo color depends on the light color.
create_material("lines_primary", Color(1, 1, 1), false, false, true);
create_material("lines_secondary", Color(1, 1, 1, 0.35), false, false, true);
create_material("lines_billboard", Color(1, 1, 1), true, false, true);
create_icon_material("light_directional_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoDirectionalLight"), EditorStringName(EditorIcons)));
create_icon_material("light_omni_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoLight"), EditorStringName(EditorIcons)));
create_icon_material("light_spot_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoSpotLight"), EditorStringName(EditorIcons)));
create_handle_material("handles");
create_handle_material("handles_billboard", true);
}
bool Light3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<Light3D>(p_spatial) != nullptr;
}
String Light3DGizmoPlugin::get_gizmo_name() const {
return "Light3D";
}
int Light3DGizmoPlugin::get_priority() const {
return -1;
}
String Light3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
if (p_id == 0) {
return "Radius";
} else {
return "Aperture";
}
}
Variant Light3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_node_3d());
if (p_id == 0) {
return light->get_param(Light3D::PARAM_RANGE);
}
if (p_id == 1) {
return light->get_param(Light3D::PARAM_SPOT_ANGLE);
}
return Variant();
}
void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_node_3d());
Transform3D gt = light->get_global_transform();
Transform3D gi = gt.affine_inverse();
Vector3 ray_from = p_camera->project_ray_origin(p_point);
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
Vector3 s[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
if (p_id == 0) {
if (Object::cast_to<SpotLight3D>(light)) {
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(0, 0, -4096), s[0], s[1], ra, rb);
float d = -ra.z;
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
}
if (d <= 0) { // Equal is here for negative zero.
d = 0;
}
light->set_param(Light3D::PARAM_RANGE, d);
} else if (Object::cast_to<OmniLight3D>(light)) {
Plane cp = Plane(p_camera->get_transform().basis.get_column(2), gt.origin);
Vector3 inters;
if (cp.intersects_ray(ray_from, ray_dir, &inters)) {
float r = inters.distance_to(gt.origin);
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
r = Math::snapped(r, Node3DEditor::get_singleton()->get_translate_snap());
}
light->set_param(Light3D::PARAM_RANGE, r);
}
}
} else if (p_id == 1) {
float a = _find_closest_angle_to_half_pi_arc(s[0], s[1], light->get_param(Light3D::PARAM_RANGE), gt);
light->set_param(Light3D::PARAM_SPOT_ANGLE, CLAMP(a, 0.01, 89.99));
}
}
void Light3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_node_3d());
if (p_cancel) {
light->set_param(p_id == 0 ? Light3D::PARAM_RANGE : Light3D::PARAM_SPOT_ANGLE, p_restore);
} else if (p_id == 0) {
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Change Light Radius"));
ur->add_do_method(light, "set_param", Light3D::PARAM_RANGE, light->get_param(Light3D::PARAM_RANGE));
ur->add_undo_method(light, "set_param", Light3D::PARAM_RANGE, p_restore);
ur->commit_action();
} else if (p_id == 1) {
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Change Light Radius"));
ur->add_do_method(light, "set_param", Light3D::PARAM_SPOT_ANGLE, light->get_param(Light3D::PARAM_SPOT_ANGLE));
ur->add_undo_method(light, "set_param", Light3D::PARAM_SPOT_ANGLE, p_restore);
ur->commit_action();
}
}
void Light3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_node_3d());
Color color = light->get_color().srgb_to_linear() * light->get_correlated_color().srgb_to_linear();
color = color.linear_to_srgb();
// Make the gizmo color as bright as possible for better visibility
color.set_hsv(color.get_h(), color.get_s(), 1);
p_gizmo->clear();
if (Object::cast_to<DirectionalLight3D>(light)) {
if (p_gizmo->is_selected()) {
Ref<Material> material = get_material("lines_primary", p_gizmo);
const int arrow_points = 7;
const float arrow_length = 1.5;
Vector3 arrow[arrow_points] = {
Vector3(0, 0, -1),
Vector3(0, 0.8, 0),
Vector3(0, 0.3, 0),
Vector3(0, 0.3, arrow_length),
Vector3(0, -0.3, arrow_length),
Vector3(0, -0.3, 0),
Vector3(0, -0.8, 0)
};
int arrow_sides = 2;
Vector<Vector3> lines;
for (int i = 0; i < arrow_sides; i++) {
for (int j = 0; j < arrow_points; j++) {
Basis ma(Vector3(0, 0, 1), Math::PI * i / arrow_sides);
Vector3 v1 = arrow[j] - Vector3(0, 0, arrow_length);
Vector3 v2 = arrow[(j + 1) % arrow_points] - Vector3(0, 0, arrow_length);
lines.push_back(ma.xform(v1));
lines.push_back(ma.xform(v2));
}
}
p_gizmo->add_lines(lines, material, false, color);
}
Ref<Material> icon = get_material("light_directional_icon", p_gizmo);
p_gizmo->add_unscaled_billboard(icon, 0.05, color);
}
if (Object::cast_to<OmniLight3D>(light)) {
if (p_gizmo->is_selected()) {
// Use both a billboard circle and 3 non-billboard circles for a better sphere-like representation
const Ref<Material> lines_material = get_material("lines_secondary", p_gizmo);
const Ref<Material> lines_billboard_material = get_material("lines_billboard", p_gizmo);
OmniLight3D *on = Object::cast_to<OmniLight3D>(light);
const float r = on->get_param(Light3D::PARAM_RANGE);
Vector<Vector3> points;
Vector<Vector3> points_billboard;
for (int i = 0; i < 120; i++) {
// Create a circle
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;
// Draw axis-aligned circles
points.push_back(Vector3(a.x, 0, a.y));
points.push_back(Vector3(b.x, 0, b.y));
points.push_back(Vector3(0, a.x, a.y));
points.push_back(Vector3(0, b.x, b.y));
points.push_back(Vector3(a.x, a.y, 0));
points.push_back(Vector3(b.x, b.y, 0));
// Draw a billboarded circle
points_billboard.push_back(Vector3(a.x, a.y, 0));
points_billboard.push_back(Vector3(b.x, b.y, 0));
}
p_gizmo->add_lines(points, lines_material, true, color);
p_gizmo->add_lines(points_billboard, lines_billboard_material, true, color);
Vector<Vector3> handles;
handles.push_back(Vector3(r, 0, 0));
p_gizmo->add_handles(handles, get_material("handles_billboard"), Vector<int>(), true);
}
const Ref<Material> icon = get_material("light_omni_icon", p_gizmo);
p_gizmo->add_unscaled_billboard(icon, 0.05, color);
}
if (Object::cast_to<SpotLight3D>(light)) {
if (p_gizmo->is_selected()) {
const Ref<Material> material_primary = get_material("lines_primary", p_gizmo);
const Ref<Material> material_secondary = get_material("lines_secondary", p_gizmo);
Vector<Vector3> points_primary;
Vector<Vector3> points_secondary;
SpotLight3D *sl = Object::cast_to<SpotLight3D>(light);
float r = sl->get_param(Light3D::PARAM_RANGE);
float w = r * Math::sin(Math::deg_to_rad(sl->get_param(Light3D::PARAM_SPOT_ANGLE)));
float d = r * Math::cos(Math::deg_to_rad(sl->get_param(Light3D::PARAM_SPOT_ANGLE)));
for (int i = 0; i < 120; i++) {
// Draw a circle
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)) * w;
const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w;
points_primary.push_back(Vector3(a.x, a.y, -d));
points_primary.push_back(Vector3(b.x, b.y, -d));
if (i % 15 == 0) {
// Draw 8 lines from the cone origin to the sides of the circle
points_secondary.push_back(Vector3(a.x, a.y, -d));
points_secondary.push_back(Vector3());
}
}
points_primary.push_back(Vector3(0, 0, -r));
points_primary.push_back(Vector3());
p_gizmo->add_lines(points_primary, material_primary, false, color);
p_gizmo->add_lines(points_secondary, material_secondary, false, color);
Vector<Vector3> handles = {
Vector3(0, 0, -r),
Vector3(w, 0, -d)
};
p_gizmo->add_handles(handles, get_material("handles"));
}
const Ref<Material> icon = get_material("light_spot_icon", p_gizmo);
p_gizmo->add_unscaled_billboard(icon, 0.05, color);
}
}
float Light3DGizmoPlugin::_find_closest_angle_to_half_pi_arc(const Vector3 &p_from, const Vector3 &p_to, float p_arc_radius, const Transform3D &p_arc_xform) {
//bleh, discrete is simpler
static const int arc_test_points = 64;
float min_d = 1e20;
Vector3 min_p;
for (int i = 0; i < arc_test_points; i++) {
float a = i * Math::PI * 0.5 / arc_test_points;
float an = (i + 1) * Math::PI * 0.5 / arc_test_points;
Vector3 p = Vector3(Math::cos(a), 0, -Math::sin(a)) * p_arc_radius;
Vector3 n = Vector3(Math::cos(an), 0, -Math::sin(an)) * p_arc_radius;
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(p, n, p_from, p_to, ra, rb);
float d = ra.distance_to(rb);
if (d < min_d) {
min_d = d;
min_p = ra;
}
}
//min_p = p_arc_xform.affine_inverse().xform(min_p);
float a = (Math::PI * 0.5) - Vector2(min_p.x, -min_p.z).angle();
return Math::rad_to_deg(a);
}

View File

@@ -0,0 +1,53 @@
/**************************************************************************/
/* light_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class Light3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Light3DGizmoPlugin, EditorNode3DGizmoPlugin);
private:
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:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const 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;
void redraw(EditorNode3DGizmo *p_gizmo) override;
Light3DGizmoPlugin();
};

View File

@@ -0,0 +1,224 @@
/**************************************************************************/
/* lightmap_gi_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 "lightmap_gi_gizmo_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/settings/editor_settings.h"
#include "scene/3d/lightmap_gi.h"
LightmapGIGizmoPlugin::LightmapGIGizmoPlugin() {
// NOTE: This gizmo only renders solid spheres for previewing indirect lighting on dynamic objects.
// The wireframe representation for LightmapProbe nodes is handled in LightmapProbeGizmoPlugin.
Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/lightmap_lines");
probe_size = EDITOR_GET("editors/3d_gizmos/gizmo_settings/lightmap_gi_probe_size");
gizmo_color.a = 0.1;
create_material("lightmap_lines", gizmo_color);
Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
// 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(probe_size * 0.5);
mat->set_distance_fade_max_distance(probe_size * 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);
add_material("lightmap_probe_material", mat);
create_icon_material("baked_indirect_light_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoLightmapGI"), EditorStringName(EditorIcons)));
}
bool LightmapGIGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<LightmapGI>(p_spatial) != nullptr;
}
String LightmapGIGizmoPlugin::get_gizmo_name() const {
return "LightmapGI";
}
int LightmapGIGizmoPlugin::get_priority() const {
return -1;
}
void LightmapGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Ref<Material> icon = get_material("baked_indirect_light_icon", p_gizmo);
LightmapGI *baker = Object::cast_to<LightmapGI>(p_gizmo->get_node_3d());
Ref<LightmapGIData> data = baker->get_light_data();
p_gizmo->clear();
p_gizmo->add_unscaled_billboard(icon, 0.05);
if (data.is_null() || !p_gizmo->is_selected()) {
return;
}
Ref<Material> material_lines = get_material("lightmap_lines", p_gizmo);
Ref<Material> material_probes = get_material("lightmap_probe_material", p_gizmo);
Vector<Vector3> lines;
HashSet<Vector2i> lines_found;
Vector<Vector3> points = data->get_capture_points();
if (points.is_empty()) {
return;
}
Vector<Color> sh = data->get_capture_sh();
if (sh.size() != points.size() * 9) {
return;
}
Vector<int> tetrahedrons = data->get_capture_tetrahedra();
for (int i = 0; i < tetrahedrons.size(); i += 4) {
for (int j = 0; j < 4; j++) {
for (int k = j + 1; k < 4; k++) {
Vector2i pair;
pair.x = tetrahedrons[i + j];
pair.y = tetrahedrons[i + k];
if (pair.y < pair.x) {
SWAP(pair.x, pair.y);
}
if (lines_found.has(pair)) {
continue;
}
lines_found.insert(pair);
lines.push_back(points[pair.x]);
lines.push_back(points[pair.y]);
}
}
}
p_gizmo->add_lines(lines, material_lines);
int stack_count = 8;
int sector_count = 16;
float sector_step = (Math::PI * 2.0) / sector_count;
float stack_step = Math::PI / stack_count;
LocalVector<Vector3> vertices;
LocalVector<Color> colors;
LocalVector<int> indices;
float radius = probe_size * 0.5f;
if (!Math::is_zero_approx(radius)) {
// L2 Spherical Harmonics evaluation and diffuse convolution coefficients.
const float sh_coeffs[5] = {
static_cast<float>(sqrt(1.0 / (4.0 * Math::PI)) * Math::PI),
static_cast<float>(sqrt(3.0 / (4.0 * Math::PI)) * Math::PI * 2.0 / 3.0),
static_cast<float>(sqrt(15.0 / (4.0 * Math::PI)) * Math::PI * 1.0 / 4.0),
static_cast<float>(sqrt(5.0 / (16.0 * Math::PI)) * Math::PI * 1.0 / 4.0),
static_cast<float>(sqrt(15.0 / (16.0 * Math::PI)) * Math::PI * 1.0 / 4.0)
};
for (int p = 0; p < points.size(); p++) {
int vertex_base = vertices.size();
Vector3 sh_col[9];
for (int i = 0; i < 9; i++) {
sh_col[i].x = sh[p * 9 + i].r;
sh_col[i].y = sh[p * 9 + i].g;
sh_col[i].z = sh[p * 9 + i].b;
}
for (int i = 0; i <= stack_count; ++i) {
float stack_angle = Math::PI / 2 - i * stack_step; // starting from pi/2 to -pi/2
float xy = radius * Math::cos(stack_angle); // r * cos(u)
float z = radius * Math::sin(stack_angle); // r * sin(u)
// add (sector_count+1) vertices per stack
// the first and last vertices have same position and normal, but different tex coords
for (int j = 0; j <= sector_count; ++j) {
float sector_angle = j * sector_step; // starting from 0 to 2pi
// vertex position (x, y, z)
float x = xy * Math::cos(sector_angle); // r * cos(u) * cos(v)
float y = xy * Math::sin(sector_angle); // r * cos(u) * sin(v)
Vector3 n = Vector3(x, z, y);
vertices.push_back(points[p] + n);
n.normalize();
const Vector3 light = (sh_coeffs[0] * sh_col[0] +
sh_coeffs[1] * sh_col[1] * n.y +
sh_coeffs[1] * sh_col[2] * n.z +
sh_coeffs[1] * sh_col[3] * n.x +
sh_coeffs[2] * sh_col[4] * n.x * n.y +
sh_coeffs[2] * sh_col[5] * n.y * n.z +
sh_coeffs[3] * sh_col[6] * (3.0 * n.z * n.z - 1.0) +
sh_coeffs[2] * sh_col[7] * n.x * n.z +
sh_coeffs[4] * sh_col[8] * (n.x * n.x - n.y * n.y));
colors.push_back(Color(light.x, light.y, light.z, 1));
}
}
for (int i = 0; i < stack_count; ++i) {
int k1 = i * (sector_count + 1); // beginning of current stack
int k2 = k1 + sector_count + 1; // beginning of next stack
for (int j = 0; j < sector_count; ++j, ++k1, ++k2) {
// 2 triangles per sector excluding first and last stacks
// k1 => k2 => k1+1
if (i != 0) {
indices.push_back(vertex_base + k1);
indices.push_back(vertex_base + k2);
indices.push_back(vertex_base + k1 + 1);
}
// k1+1 => k2 => k2+1
if (i != (stack_count - 1)) {
indices.push_back(vertex_base + k1 + 1);
indices.push_back(vertex_base + k2);
indices.push_back(vertex_base + k2 + 1);
}
}
}
}
Array array;
array.resize(RS::ARRAY_MAX);
array[RS::ARRAY_VERTEX] = Vector<Vector3>(vertices);
array[RS::ARRAY_INDEX] = Vector<int>(indices);
array[RS::ARRAY_COLOR] = Vector<Color>(colors);
Ref<ArrayMesh> mesh;
mesh.instantiate();
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array, Array(), Dictionary(), 0); //no compression
mesh->surface_set_material(0, material_probes);
p_gizmo->add_mesh(mesh);
}
}

View File

@@ -0,0 +1,47 @@
/**************************************************************************/
/* lightmap_gi_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class LightmapGIGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(LightmapGIGizmoPlugin, EditorNode3DGizmoPlugin);
float probe_size = 0.4f;
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const override;
void redraw(EditorNode3DGizmo *p_gizmo) override;
LightmapGIGizmoPlugin();
};

View File

@@ -0,0 +1,126 @@
/**************************************************************************/
/* lightmap_probe_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 "lightmap_probe_gizmo_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/settings/editor_settings.h"
#include "scene/3d/lightmap_probe.h"
LightmapProbeGizmoPlugin::LightmapProbeGizmoPlugin() {
// NOTE: This gizmo only renders LightmapProbe nodes as wireframes.
// The solid sphere representation is handled in LightmapGIGizmoPlugin.
create_icon_material("lightmap_probe_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoLightmapProbe"), EditorStringName(EditorIcons)));
Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/lightprobe_lines");
probe_size = EDITOR_GET("editors/3d_gizmos/gizmo_settings/lightmap_gi_probe_size");
gizmo_color.a = 0.3;
create_material("lightprobe_lines", gizmo_color);
}
bool LightmapProbeGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<LightmapProbe>(p_spatial) != nullptr;
}
String LightmapProbeGizmoPlugin::get_gizmo_name() const {
return "LightmapProbe";
}
int LightmapProbeGizmoPlugin::get_priority() const {
return -1;
}
void LightmapProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Ref<Material> material_lines = get_material("lightprobe_lines", p_gizmo);
p_gizmo->clear();
Vector<Vector3> lines;
int stack_count = 8;
int sector_count = 16;
float sector_step = (Math::PI * 2.0) / sector_count;
float stack_step = Math::PI / stack_count;
Vector<Vector3> vertices;
// Make the lines' radius slightly smaller than its mesh representation to avoid Z-fighting.
float radius = probe_size * 0.495f;
if (!Math::is_zero_approx(radius)) {
for (int i = 0; i <= stack_count; ++i) {
float stack_angle = Math::PI / 2 - i * stack_step; // starting from pi/2 to -pi/2
float xy = radius * Math::cos(stack_angle); // r * cos(u)
float z = radius * Math::sin(stack_angle); // r * sin(u)
// add (sector_count+1) vertices per stack
// the first and last vertices have same position and normal, but different tex coords
for (int j = 0; j <= sector_count; ++j) {
float sector_angle = j * sector_step; // starting from 0 to 2pi
// vertex position (x, y, z)
float x = xy * Math::cos(sector_angle); // r * cos(u) * cos(v)
float y = xy * Math::sin(sector_angle); // r * cos(u) * sin(v)
Vector3 n = Vector3(x, z, y);
vertices.push_back(n);
}
}
for (int i = 0; i < stack_count; ++i) {
int k1 = i * (sector_count + 1); // beginning of current stack
int k2 = k1 + sector_count + 1; // beginning of next stack
for (int j = 0; j < sector_count; ++j, ++k1, ++k2) {
// 2 triangles per sector excluding first and last stacks
// k1 => k2 => k1+1
if (i != 0) {
lines.push_back(vertices[k1]);
lines.push_back(vertices[k2]);
lines.push_back(vertices[k1]);
lines.push_back(vertices[k1 + 1]);
}
if (i != (stack_count - 1)) {
lines.push_back(vertices[k1 + 1]);
lines.push_back(vertices[k2]);
lines.push_back(vertices[k2]);
lines.push_back(vertices[k2 + 1]);
}
}
}
p_gizmo->add_lines(lines, material_lines);
}
const Ref<Material> icon = get_material("lightmap_probe_icon", p_gizmo);
p_gizmo->add_unscaled_billboard(icon, 0.05);
}

View File

@@ -0,0 +1,47 @@
/**************************************************************************/
/* lightmap_probe_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class LightmapProbeGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(LightmapProbeGizmoPlugin, EditorNode3DGizmoPlugin);
float probe_size = 0.4f;
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const override;
void redraw(EditorNode3DGizmo *p_gizmo) override;
LightmapProbeGizmoPlugin();
};

View File

@@ -0,0 +1,127 @@
/**************************************************************************/
/* marker_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 "marker_3d_gizmo_plugin.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "scene/3d/marker_3d.h"
Marker3DGizmoPlugin::Marker3DGizmoPlugin() {
pos3d_mesh.instantiate();
Vector<Vector3> cursor_points;
Vector<Color> cursor_colors;
const float cs = 1.0;
// Add more points to create a "hard stop" in the color gradient.
cursor_points.push_back(Vector3(+cs, 0, 0));
cursor_points.push_back(Vector3());
cursor_points.push_back(Vector3());
cursor_points.push_back(Vector3(-cs, 0, 0));
cursor_points.push_back(Vector3(0, +cs, 0));
cursor_points.push_back(Vector3());
cursor_points.push_back(Vector3());
cursor_points.push_back(Vector3(0, -cs, 0));
cursor_points.push_back(Vector3(0, 0, +cs));
cursor_points.push_back(Vector3());
cursor_points.push_back(Vector3());
cursor_points.push_back(Vector3(0, 0, -cs));
// Use the axis color which is brighter for the positive axis.
// Use a darkened axis color for the negative axis.
// This makes it possible to see in which direction the Marker3D node is rotated
// (which can be important depending on how it's used).
const Color color_x = EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("axis_x_color"), EditorStringName(Editor));
cursor_colors.push_back(color_x);
cursor_colors.push_back(color_x);
// FIXME: Use less strong darkening factor once GH-48573 is fixed.
// The current darkening factor compensates for lines being too bright in the 3D editor.
cursor_colors.push_back(color_x.lerp(Color(0, 0, 0), 0.75));
cursor_colors.push_back(color_x.lerp(Color(0, 0, 0), 0.75));
const Color color_y = EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("axis_y_color"), EditorStringName(Editor));
cursor_colors.push_back(color_y);
cursor_colors.push_back(color_y);
cursor_colors.push_back(color_y.lerp(Color(0, 0, 0), 0.75));
cursor_colors.push_back(color_y.lerp(Color(0, 0, 0), 0.75));
const Color color_z = EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("axis_z_color"), EditorStringName(Editor));
cursor_colors.push_back(color_z);
cursor_colors.push_back(color_z);
cursor_colors.push_back(color_z.lerp(Color(0, 0, 0), 0.75));
cursor_colors.push_back(color_z.lerp(Color(0, 0, 0), 0.75));
Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
Array d;
d.resize(RS::ARRAY_MAX);
d[Mesh::ARRAY_VERTEX] = cursor_points;
d[Mesh::ARRAY_COLOR] = cursor_colors;
pos3d_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, d);
pos3d_mesh->surface_set_material(0, mat);
}
bool Marker3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<Marker3D>(p_spatial) != nullptr;
}
String Marker3DGizmoPlugin::get_gizmo_name() const {
return "Marker3D";
}
int Marker3DGizmoPlugin::get_priority() const {
return -1;
}
void Marker3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
const Marker3D *marker = Object::cast_to<Marker3D>(p_gizmo->get_node_3d());
const real_t extents = marker->get_gizmo_extents();
const Transform3D xform(Basis::from_scale(Vector3(extents, extents, extents)));
p_gizmo->clear();
p_gizmo->add_mesh(pos3d_mesh, Ref<Material>(), xform);
const Vector<Vector3> points = {
Vector3(-extents, 0, 0),
Vector3(+extents, 0, 0),
Vector3(0, -extents, 0),
Vector3(0, +extents, 0),
Vector3(0, 0, -extents),
Vector3(0, 0, +extents),
};
p_gizmo->add_collision_segments(points);
}

View File

@@ -0,0 +1,47 @@
/**************************************************************************/
/* marker_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class Marker3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Marker3DGizmoPlugin, EditorNode3DGizmoPlugin);
Ref<ArrayMesh> pos3d_mesh;
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const override;
void redraw(EditorNode3DGizmo *p_gizmo) override;
Marker3DGizmoPlugin();
};

View File

@@ -0,0 +1,84 @@
/**************************************************************************/
/* mesh_instance_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 "mesh_instance_3d_gizmo_plugin.h"
#include "editor/scene/3d/node_3d_editor_plugin.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/physics/soft_body_3d.h"
#include "scene/resources/3d/primitive_meshes.h"
bool MeshInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<MeshInstance3D>(p_spatial) != nullptr && Object::cast_to<SoftBody3D>(p_spatial) == nullptr;
}
String MeshInstance3DGizmoPlugin::get_gizmo_name() const {
return "MeshInstance3D";
}
int MeshInstance3DGizmoPlugin::get_priority() const {
return -1;
}
bool MeshInstance3DGizmoPlugin::can_be_hidden() const {
return false;
}
void MeshInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
MeshInstance3D *mesh = Object::cast_to<MeshInstance3D>(p_gizmo->get_node_3d());
p_gizmo->clear();
Ref<Mesh> m = mesh->get_mesh();
if (m.is_null()) {
return; //none
}
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

@@ -0,0 +1,44 @@
/**************************************************************************/
/* mesh_instance_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class MeshInstance3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(MeshInstance3DGizmoPlugin, EditorNode3DGizmoPlugin);
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const override;
bool can_be_hidden() const override;
void redraw(EditorNode3DGizmo *p_gizmo) override;
};

View File

@@ -0,0 +1,284 @@
/**************************************************************************/
/* occluder_instance_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 "occluder_instance_3d_gizmo_plugin.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/scene/3d/node_3d_editor_plugin.h"
#include "editor/settings/editor_settings.h"
#include "scene/3d/occluder_instance_3d.h"
OccluderInstance3DGizmoPlugin::OccluderInstance3DGizmoPlugin() {
create_material("line_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/occluder"));
create_handle_material("handles");
}
bool OccluderInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<OccluderInstance3D>(p_spatial) != nullptr;
}
String OccluderInstance3DGizmoPlugin::get_gizmo_name() const {
return "OccluderInstance3D";
}
int OccluderInstance3DGizmoPlugin::get_priority() const {
return -1;
}
String OccluderInstance3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
const OccluderInstance3D *cs = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d());
Ref<Occluder3D> o = cs->get_occluder();
if (o.is_null()) {
return "";
}
if (Object::cast_to<SphereOccluder3D>(*o)) {
return "Radius";
}
if (Object::cast_to<BoxOccluder3D>(*o) || Object::cast_to<QuadOccluder3D>(*o)) {
return "Size";
}
return "";
}
Variant OccluderInstance3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d());
Ref<Occluder3D> o = oi->get_occluder();
if (o.is_null()) {
return Variant();
}
if (Object::cast_to<SphereOccluder3D>(*o)) {
Ref<SphereOccluder3D> so = o;
return so->get_radius();
}
if (Object::cast_to<BoxOccluder3D>(*o)) {
Ref<BoxOccluder3D> bo = o;
return bo->get_size();
}
if (Object::cast_to<QuadOccluder3D>(*o)) {
Ref<QuadOccluder3D> qo = o;
return qo->get_size();
}
return Variant();
}
void OccluderInstance3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d());
Ref<Occluder3D> o = oi->get_occluder();
if (o.is_null()) {
return;
}
Transform3D gt = oi->get_global_transform();
Transform3D gi = gt.affine_inverse();
Vector3 ray_from = p_camera->project_ray_origin(p_point);
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
bool snap_enabled = Node3DEditor::get_singleton()->is_snap_enabled();
float snap = Node3DEditor::get_singleton()->get_translate_snap();
if (Object::cast_to<SphereOccluder3D>(*o)) {
Ref<SphereOccluder3D> so = o;
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb);
float d = ra.x;
if (snap_enabled) {
d = Math::snapped(d, snap);
}
if (d < 0.001) {
d = 0.001;
}
so->set_radius(d);
}
if (Object::cast_to<BoxOccluder3D>(*o)) {
Vector3 axis;
axis[p_id] = 1.0;
Ref<BoxOccluder3D> bo = o;
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
float d = ra[p_id] * 2;
if (snap_enabled) {
d = Math::snapped(d, snap);
}
if (d < 0.001) {
d = 0.001;
}
Vector3 he = bo->get_size();
he[p_id] = d;
bo->set_size(he);
}
if (Object::cast_to<QuadOccluder3D>(*o)) {
Ref<QuadOccluder3D> qo = o;
Plane p = Plane(Vector3(0.0f, 0.0f, 1.0f), 0.0f);
Vector3 intersection;
if (!p.intersects_segment(sg[0], sg[1], &intersection)) {
return;
}
if (p_id == 2) {
Vector2 s = Vector2(intersection.x, intersection.y) * 2.0f;
if (snap_enabled) {
s = s.snappedf(snap);
}
s = s.maxf(0.001);
qo->set_size(s);
} else {
float d = intersection[p_id];
if (snap_enabled) {
d = Math::snapped(d, snap);
}
if (d < 0.001) {
d = 0.001;
}
Vector2 he = qo->get_size();
he[p_id] = d * 2.0f;
qo->set_size(he);
}
}
}
void OccluderInstance3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d());
Ref<Occluder3D> o = oi->get_occluder();
if (o.is_null()) {
return;
}
if (Object::cast_to<SphereOccluder3D>(*o)) {
Ref<SphereOccluder3D> so = o;
if (p_cancel) {
so->set_radius(p_restore);
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Change Sphere Shape Radius"));
ur->add_do_method(so.ptr(), "set_radius", so->get_radius());
ur->add_undo_method(so.ptr(), "set_radius", p_restore);
ur->commit_action();
}
if (Object::cast_to<BoxOccluder3D>(*o)) {
Ref<BoxOccluder3D> bo = o;
if (p_cancel) {
bo->set_size(p_restore);
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Change Box Shape Size"));
ur->add_do_method(bo.ptr(), "set_size", bo->get_size());
ur->add_undo_method(bo.ptr(), "set_size", p_restore);
ur->commit_action();
}
if (Object::cast_to<QuadOccluder3D>(*o)) {
Ref<QuadOccluder3D> qo = o;
if (p_cancel) {
qo->set_size(p_restore);
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Change Box Shape Size"));
ur->add_do_method(qo.ptr(), "set_size", qo->get_size());
ur->add_undo_method(qo.ptr(), "set_size", p_restore);
ur->commit_action();
}
}
void OccluderInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
OccluderInstance3D *occluder_instance = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d());
p_gizmo->clear();
Ref<Occluder3D> o = occluder_instance->get_occluder();
if (o.is_null()) {
return;
}
Vector<Vector3> lines = o->get_debug_lines();
if (!lines.is_empty()) {
Ref<Material> material = get_material("line_material", p_gizmo);
p_gizmo->add_lines(lines, material);
p_gizmo->add_collision_segments(lines);
}
Ref<Material> handles_material = get_material("handles");
if (Object::cast_to<SphereOccluder3D>(*o)) {
Ref<SphereOccluder3D> so = o;
float r = so->get_radius();
Vector<Vector3> handles = { Vector3(r, 0, 0) };
p_gizmo->add_handles(handles, handles_material);
}
if (Object::cast_to<BoxOccluder3D>(*o)) {
Ref<BoxOccluder3D> bo = o;
Vector<Vector3> handles;
for (int i = 0; i < 3; i++) {
Vector3 ax;
ax[i] = bo->get_size()[i] / 2;
handles.push_back(ax);
}
p_gizmo->add_handles(handles, handles_material);
}
if (Object::cast_to<QuadOccluder3D>(*o)) {
Ref<QuadOccluder3D> qo = o;
Vector2 size = qo->get_size();
Vector3 s = Vector3(size.x, size.y, 0.0f) / 2.0f;
Vector<Vector3> handles = { Vector3(s.x, 0.0f, 0.0f), Vector3(0.0f, s.y, 0.0f), Vector3(s.x, s.y, 0.0f) };
p_gizmo->add_handles(handles, handles_material);
}
}

View File

@@ -0,0 +1,50 @@
/**************************************************************************/
/* occluder_instance_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class OccluderInstance3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(OccluderInstance3DGizmoPlugin, EditorNode3DGizmoPlugin);
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() 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;
OccluderInstance3DGizmoPlugin();
};

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_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/scene/3d/node_3d_editor_plugin.h"
#include "editor/settings/editor_settings.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,54 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/gizmos/gizmo_3d_helper.h"
#include "editor/scene/3d/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();
};

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.editor_sources, "*.cpp")

View File

@@ -0,0 +1,84 @@
/**************************************************************************/
/* collision_object_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 "collision_object_3d_gizmo_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"
#include "scene/resources/surface_tool.h"
CollisionObject3DGizmoPlugin::CollisionObject3DGizmoPlugin() {
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);
}
bool CollisionObject3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<CollisionObject3D>(p_spatial) != nullptr;
}
String CollisionObject3DGizmoPlugin::get_gizmo_name() const {
return "CollisionObject3D";
}
int CollisionObject3DGizmoPlugin::get_priority() const {
return -2;
}
void CollisionObject3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_gizmo->get_node_3d());
p_gizmo->clear();
List<uint32_t> owner_ids;
co->get_shape_owners(&owner_ids);
for (uint32_t &owner_id : owner_ids) {
Transform3D xform = co->shape_owner_get_transform(owner_id);
Object *owner = co->shape_owner_get_owner(owner_id);
// Exclude CollisionShape3D and CollisionPolygon3D as they have their gizmo.
if (!Object::cast_to<CollisionShape3D>(owner) && !Object::cast_to<CollisionPolygon3D>(owner)) {
Ref<Material> material = get_material(!co->is_shape_owner_disabled(owner_id) ? "shape_material" : "shape_material_disabled", p_gizmo);
for (int shape_id = 0; shape_id < co->shape_owner_get_shape_count(owner_id); shape_id++) {
Ref<Shape3D> s = co->shape_owner_get_shape(owner_id, shape_id);
if (s.is_null()) {
continue;
}
SurfaceTool st;
st.append_from(s->get_debug_mesh(), 0, xform);
p_gizmo->add_mesh(st.commit(), material);
p_gizmo->add_collision_segments(s->get_debug_mesh_lines());
}
}
}
}

View File

@@ -0,0 +1,45 @@
/**************************************************************************/
/* collision_object_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class CollisionObject3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(CollisionObject3DGizmoPlugin, EditorNode3DGizmoPlugin);
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const override;
void redraw(EditorNode3DGizmo *p_gizmo) override;
CollisionObject3DGizmoPlugin();
};

View File

@@ -0,0 +1,237 @@
/**************************************************************************/
/* collision_polygon_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 "collision_polygon_3d_gizmo_plugin.h"
#include "core/math/geometry_2d.h"
#include "scene/3d/physics/collision_polygon_3d.h"
CollisionPolygon3DGizmoPlugin::CollisionPolygon3DGizmoPlugin() {
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) {
return Object::cast_to<CollisionPolygon3D>(p_spatial) != nullptr;
}
String CollisionPolygon3DGizmoPlugin::get_gizmo_name() const {
return "CollisionPolygon3D";
}
int CollisionPolygon3DGizmoPlugin::get_priority() const {
return -1;
}
void CollisionPolygon3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
CollisionPolygon3D *polygon = Object::cast_to<CollisionPolygon3D>(p_gizmo->get_node_3d());
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;
Vector<Vector3> lines;
const int points_size = points.size();
for (int i = 0; i < points_size; i++) {
int n = (i + 1) % points_size;
lines.push_back(Vector3(points[i].x, points[i].y, depth));
lines.push_back(Vector3(points[n].x, points[n].y, depth));
lines.push_back(Vector3(points[i].x, points[i].y, -depth));
lines.push_back(Vector3(points[n].x, points[n].y, -depth));
lines.push_back(Vector3(points[i].x, points[i].y, depth));
lines.push_back(Vector3(points[i].x, points[i].y, -depth));
}
if (polygon->get_debug_fill_enabled()) {
Ref<ArrayMesh> array_mesh;
array_mesh.instantiate();
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);
}
const int verts_size = verts.size();
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];
const int convex_size = convex.size();
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];
const int convex_size = convex.size();
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

@@ -0,0 +1,46 @@
/**************************************************************************/
/* collision_polygon_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
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;
int get_priority() const override;
void redraw(EditorNode3DGizmo *p_gizmo) override;
CollisionPolygon3DGizmoPlugin();
};

View File

@@ -0,0 +1,669 @@
/**************************************************************************/
/* collision_shape_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 "collision_shape_3d_gizmo_plugin.h"
#include "core/math/convex_hull.h"
#include "core/math/geometry_3d.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/scene/3d/gizmos/gizmo_3d_helper.h"
#include "editor/scene/3d/node_3d_editor_plugin.h"
#include "scene/3d/physics/collision_shape_3d.h"
#include "scene/resources/3d/box_shape_3d.h"
#include "scene/resources/3d/capsule_shape_3d.h"
#include "scene/resources/3d/concave_polygon_shape_3d.h"
#include "scene/resources/3d/convex_polygon_shape_3d.h"
#include "scene/resources/3d/cylinder_shape_3d.h"
#include "scene/resources/3d/height_map_shape_3d.h"
#include "scene/resources/3d/separation_ray_shape_3d.h"
#include "scene/resources/3d/sphere_shape_3d.h"
#include "scene/resources/3d/world_boundary_shape_3d.h"
CollisionShape3DGizmoPlugin::CollisionShape3DGizmoPlugin() {
helper.instantiate();
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");
}
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;
}
String CollisionShape3DGizmoPlugin::get_gizmo_name() const {
return "CollisionShape3D";
}
int CollisionShape3DGizmoPlugin::get_priority() const {
return -1;
}
String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
const CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d());
Ref<Shape3D> s = cs->get_shape();
if (s.is_null()) {
return "";
}
if (Object::cast_to<SphereShape3D>(*s)) {
return "Radius";
}
if (Object::cast_to<BoxShape3D>(*s)) {
return helper->box_get_handle_name(p_id);
}
if (Object::cast_to<CapsuleShape3D>(*s)) {
return helper->capsule_get_handle_name(p_id);
}
if (Object::cast_to<CylinderShape3D>(*s)) {
return helper->cylinder_get_handle_name(p_id);
}
if (Object::cast_to<SeparationRayShape3D>(*s)) {
return "Length";
}
return "";
}
Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d());
Ref<Shape3D> s = cs->get_shape();
if (s.is_null()) {
return Variant();
}
if (Object::cast_to<SphereShape3D>(*s)) {
Ref<SphereShape3D> ss = s;
return ss->get_radius();
}
if (Object::cast_to<BoxShape3D>(*s)) {
Ref<BoxShape3D> bs = s;
return bs->get_size();
}
if (Object::cast_to<CapsuleShape3D>(*s)) {
Ref<CapsuleShape3D> cs2 = s;
return Vector2(cs2->get_radius(), cs2->get_height());
}
if (Object::cast_to<CylinderShape3D>(*s)) {
Ref<CylinderShape3D> cs2 = s;
return Vector2(cs2->get_radius(), cs2->get_height());
}
if (Object::cast_to<SeparationRayShape3D>(*s)) {
Ref<SeparationRayShape3D> cs2 = s;
return cs2->get_length();
}
return Variant();
}
void CollisionShape3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) {
helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform());
}
void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d());
Ref<Shape3D> s = cs->get_shape();
if (s.is_null()) {
return;
}
Vector3 sg[2];
helper->get_segment(p_camera, p_point, sg);
if (Object::cast_to<SphereShape3D>(*s)) {
Ref<SphereShape3D> ss = s;
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb);
float d = ra.x;
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
}
if (d < 0.001) {
d = 0.001;
}
ss->set_radius(d);
}
if (Object::cast_to<SeparationRayShape3D>(*s)) {
Ref<SeparationRayShape3D> rs = s;
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(0, 0, 4096), sg[0], sg[1], ra, rb);
float d = ra.z;
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
}
if (d < 0.001) {
d = 0.001;
}
rs->set_length(d);
}
if (Object::cast_to<BoxShape3D>(*s)) {
Ref<BoxShape3D> bs = s;
Vector3 size = bs->get_size();
Vector3 position;
helper->box_set_handle(sg, p_id, size, position);
bs->set_size(size);
cs->set_global_position(position);
}
if (Object::cast_to<CapsuleShape3D>(*s)) {
Ref<CapsuleShape3D> cs2 = s;
real_t height = cs2->get_height();
real_t radius = cs2->get_radius();
Vector3 position;
helper->capsule_set_handle(sg, p_id, height, radius, position);
cs2->set_height(height);
cs2->set_radius(radius);
cs->set_global_position(position);
}
if (Object::cast_to<CylinderShape3D>(*s)) {
Ref<CylinderShape3D> cs2 = s;
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);
}
}
void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d());
Ref<Shape3D> s = cs->get_shape();
if (s.is_null()) {
return;
}
if (Object::cast_to<SphereShape3D>(*s)) {
Ref<SphereShape3D> ss = s;
if (p_cancel) {
ss->set_radius(p_restore);
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Change Sphere Shape Radius"));
ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius());
ur->add_undo_method(ss.ptr(), "set_radius", p_restore);
ur->commit_action();
}
if (Object::cast_to<BoxShape3D>(*s)) {
helper->box_commit_handle(TTR("Change Box Shape Size"), p_cancel, cs, s.ptr());
}
if (Object::cast_to<CapsuleShape3D>(*s)) {
Ref<CapsuleShape3D> ss = s;
helper->cylinder_commit_handle(p_id, TTR("Change Capsule Shape Radius"), TTR("Change Capsule Shape Height"), p_cancel, cs, *ss, *ss);
}
if (Object::cast_to<CylinderShape3D>(*s)) {
Ref<CylinderShape3D> ss = s;
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)) {
Ref<SeparationRayShape3D> ss = s;
if (p_cancel) {
ss->set_length(p_restore);
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Change Separation Ray Shape Length"));
ur->add_do_method(ss.ptr(), "set_length", ss->get_length());
ur->add_undo_method(ss.ptr(), "set_length", p_restore);
ur->commit_action();
}
}
void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d());
p_gizmo->clear();
Ref<Shape3D> s = cs->get_shape();
if (s.is_null()) {
return;
}
const Ref<StandardMaterial3D> material =
get_material(!cs->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo);
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;
float radius = sp->get_radius();
#define PUSH_QUARTER(from_x, from_y, to_x, to_y, y) \
points_ptrw[index++] = Vector3(from_x, y, from_y); \
points_ptrw[index++] = Vector3(to_x, y, to_y); \
points_ptrw[index++] = Vector3(from_x, y, -from_y); \
points_ptrw[index++] = Vector3(to_x, y, -to_y); \
points_ptrw[index++] = Vector3(-from_x, y, from_y); \
points_ptrw[index++] = Vector3(-to_x, y, to_y); \
points_ptrw[index++] = Vector3(-from_x, y, -from_y); \
points_ptrw[index++] = Vector3(-to_x, y, -to_y);
#define PUSH_QUARTER_XY(from_x, from_y, to_x, to_y, y) \
points_ptrw[index++] = Vector3(from_x, -from_y - y, 0); \
points_ptrw[index++] = Vector3(to_x, -to_y - y, 0); \
points_ptrw[index++] = Vector3(from_x, from_y + y, 0); \
points_ptrw[index++] = Vector3(to_x, to_y + y, 0); \
points_ptrw[index++] = Vector3(-from_x, -from_y - y, 0); \
points_ptrw[index++] = Vector3(-to_x, -to_y - y, 0); \
points_ptrw[index++] = Vector3(-from_x, from_y + y, 0); \
points_ptrw[index++] = Vector3(-to_x, to_y + y, 0);
#define PUSH_QUARTER_YZ(from_x, from_y, to_x, to_y, y) \
points_ptrw[index++] = Vector3(0, -from_y - y, from_x); \
points_ptrw[index++] = Vector3(0, -to_y - y, to_x); \
points_ptrw[index++] = Vector3(0, from_y + y, from_x); \
points_ptrw[index++] = Vector3(0, to_y + y, to_x); \
points_ptrw[index++] = Vector3(0, -from_y - y, -from_x); \
points_ptrw[index++] = Vector3(0, -to_y - y, -to_x); \
points_ptrw[index++] = Vector3(0, from_y + y, -from_x); \
points_ptrw[index++] = Vector3(0, to_y + y, -to_x);
// Number of points in an octant. So there will be 8 * points_in_octant * 2 points in total for one circle.
// This Corresponds to the smoothness of the circle.
const uint32_t points_in_octant = 16;
const real_t inc = (Math::PI / (4 * points_in_octant));
const real_t radius_squared = radius * radius;
real_t r = 0;
Vector<Vector3> points;
uint32_t index = 0;
// 3 full circles.
points.resize(3 * 8 * points_in_octant * 2);
Vector3 *points_ptrw = points.ptrw();
float previous_x = radius;
float previous_y = 0.f;
for (uint32_t i = 0; i < points_in_octant; ++i) {
r += inc;
real_t x = Math::cos(r) * radius;
real_t y = Math::sqrt(radius_squared - (x * x));
PUSH_QUARTER(previous_x, previous_y, x, y, 0);
PUSH_QUARTER(previous_y, previous_x, y, x, 0);
PUSH_QUARTER_XY(previous_x, previous_y, x, y, 0);
PUSH_QUARTER_XY(previous_y, previous_x, y, x, 0);
PUSH_QUARTER_YZ(previous_x, previous_y, x, y, 0);
PUSH_QUARTER_YZ(previous_y, previous_x, y, x, 0)
previous_x = x;
previous_y = y;
}
#undef PUSH_QUARTER
#undef PUSH_QUARTER_XY
#undef PUSH_QUARTER_YZ
p_gizmo->add_lines(points, material, false, collision_color);
p_gizmo->add_collision_segments(points);
Vector<Vector3> handles;
handles.push_back(Vector3(radius, 0, 0));
p_gizmo->add_handles(handles, handles_material);
}
if (Object::cast_to<BoxShape3D>(*s)) {
Ref<BoxShape3D> bs = s;
Vector<Vector3> lines;
AABB aabb;
aabb.position = -bs->get_size() / 2;
aabb.size = bs->get_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);
}
const Vector<Vector3> handles = helper->box_get_handles(bs->get_size());
p_gizmo->add_lines(lines, material, false, collision_color);
p_gizmo->add_collision_segments(lines);
p_gizmo->add_handles(handles, handles_material);
}
if (Object::cast_to<CapsuleShape3D>(*s)) {
Ref<CapsuleShape3D> cs2 = s;
float radius = cs2->get_radius();
float height = cs2->get_height();
// Number of points in an octant. So there will be 8 * points_in_octant points in total.
// This corresponds to the smoothness of the circle.
const uint32_t points_in_octant = 16;
const real_t octant_angle = Math::PI / 4;
const real_t inc = (Math::PI / (4 * points_in_octant));
const real_t radius_squared = radius * radius;
real_t r = 0;
Vector<Vector3> points;
// 4 vertical lines and 4 full circles.
points.resize(4 * 2 + 4 * 8 * points_in_octant * 2);
Vector3 *points_ptrw = points.ptrw();
uint32_t index = 0;
float y_value = height * 0.5 - radius;
// Vertical Lines.
points_ptrw[index++] = Vector3(0.f, y_value, radius);
points_ptrw[index++] = Vector3(0.f, -y_value, radius);
points_ptrw[index++] = Vector3(0.f, y_value, -radius);
points_ptrw[index++] = Vector3(0.f, -y_value, -radius);
points_ptrw[index++] = Vector3(radius, y_value, 0.f);
points_ptrw[index++] = Vector3(radius, -y_value, 0.f);
points_ptrw[index++] = Vector3(-radius, y_value, 0.f);
points_ptrw[index++] = Vector3(-radius, -y_value, 0.f);
#define PUSH_QUARTER(from_x, from_y, to_x, to_y, y) \
points_ptrw[index++] = Vector3(from_x, y, from_y); \
points_ptrw[index++] = Vector3(to_x, y, to_y); \
points_ptrw[index++] = Vector3(from_x, y, -from_y); \
points_ptrw[index++] = Vector3(to_x, y, -to_y); \
points_ptrw[index++] = Vector3(-from_x, y, from_y); \
points_ptrw[index++] = Vector3(-to_x, y, to_y); \
points_ptrw[index++] = Vector3(-from_x, y, -from_y); \
points_ptrw[index++] = Vector3(-to_x, y, -to_y);
#define PUSH_QUARTER_XY(from_x, from_y, to_x, to_y, y) \
points_ptrw[index++] = Vector3(from_x, -from_y - y, 0); \
points_ptrw[index++] = Vector3(to_x, -to_y - y, 0); \
points_ptrw[index++] = Vector3(from_x, from_y + y, 0); \
points_ptrw[index++] = Vector3(to_x, to_y + y, 0); \
points_ptrw[index++] = Vector3(-from_x, -from_y - y, 0); \
points_ptrw[index++] = Vector3(-to_x, -to_y - y, 0); \
points_ptrw[index++] = Vector3(-from_x, from_y + y, 0); \
points_ptrw[index++] = Vector3(-to_x, to_y + y, 0);
#define PUSH_QUARTER_YZ(from_x, from_y, to_x, to_y, y) \
points_ptrw[index++] = Vector3(0, -from_y - y, from_x); \
points_ptrw[index++] = Vector3(0, -to_y - y, to_x); \
points_ptrw[index++] = Vector3(0, from_y + y, from_x); \
points_ptrw[index++] = Vector3(0, to_y + y, to_x); \
points_ptrw[index++] = Vector3(0, -from_y - y, -from_x); \
points_ptrw[index++] = Vector3(0, -to_y - y, -to_x); \
points_ptrw[index++] = Vector3(0, from_y + y, -from_x); \
points_ptrw[index++] = Vector3(0, to_y + y, -to_x);
float previous_x = radius;
float previous_y = 0.f;
for (uint32_t i = 0; i < points_in_octant; ++i) {
r += inc;
real_t x = Math::cos((i == points_in_octant - 1) ? octant_angle : r) * radius;
real_t y = Math::sqrt(radius_squared - (x * x));
// High circle ring.
PUSH_QUARTER(previous_x, previous_y, x, y, y_value);
PUSH_QUARTER(previous_y, previous_x, y, x, y_value);
// Low circle ring.
PUSH_QUARTER(previous_x, previous_y, x, y, -y_value);
PUSH_QUARTER(previous_y, previous_x, y, x, -y_value);
// Up and Low circle in X-Y plane.
PUSH_QUARTER_XY(previous_x, previous_y, x, y, y_value);
PUSH_QUARTER_XY(previous_y, previous_x, y, x, y_value);
// Up and Low circle in Y-Z plane.
PUSH_QUARTER_YZ(previous_x, previous_y, x, y, y_value);
PUSH_QUARTER_YZ(previous_y, previous_x, y, x, y_value)
previous_x = x;
previous_y = y;
}
#undef PUSH_QUARTER
#undef PUSH_QUARTER_XY
#undef PUSH_QUARTER_YZ
p_gizmo->add_lines(points, material, false, collision_color);
p_gizmo->add_collision_segments(points);
Vector<Vector3> handles = helper->capsule_get_handles(cs2->get_height(), cs2->get_radius());
p_gizmo->add_handles(handles, handles_material);
}
if (Object::cast_to<CylinderShape3D>(*s)) {
Ref<CylinderShape3D> cs2 = s;
float radius = cs2->get_radius();
float height = cs2->get_height();
#define PUSH_QUARTER(from_x, from_y, to_x, to_y, y) \
points_ptrw[index++] = Vector3(from_x, y, from_y); \
points_ptrw[index++] = Vector3(to_x, y, to_y); \
points_ptrw[index++] = Vector3(from_x, y, -from_y); \
points_ptrw[index++] = Vector3(to_x, y, -to_y); \
points_ptrw[index++] = Vector3(-from_x, y, from_y); \
points_ptrw[index++] = Vector3(-to_x, y, to_y); \
points_ptrw[index++] = Vector3(-from_x, y, -from_y); \
points_ptrw[index++] = Vector3(-to_x, y, -to_y);
// Number of points in an octant. So there will be 8 * points_in_octant * 2 points in total for one circle.
// This corresponds to the smoothness of the circle.
const uint32_t points_in_octant = 16;
const real_t inc = (Math::PI / (4 * points_in_octant));
const real_t radius_squared = radius * radius;
real_t r = 0;
Vector<Vector3> points;
uint32_t index = 0;
// 4 vertical lines and 2 full circles.
points.resize(4 * 2 + 2 * 8 * points_in_octant * 2);
Vector3 *points_ptrw = points.ptrw();
float y_value = height * 0.5;
// Vertical lines.
points_ptrw[index++] = Vector3(0.f, y_value, radius);
points_ptrw[index++] = Vector3(0.f, -y_value, radius);
points_ptrw[index++] = Vector3(0.f, y_value, -radius);
points_ptrw[index++] = Vector3(0.f, -y_value, -radius);
points_ptrw[index++] = Vector3(radius, y_value, 0.f);
points_ptrw[index++] = Vector3(radius, -y_value, 0.f);
points_ptrw[index++] = Vector3(-radius, y_value, 0.f);
points_ptrw[index++] = Vector3(-radius, -y_value, 0.f);
float previous_x = radius;
float previous_y = 0.f;
for (uint32_t i = 0; i < points_in_octant; ++i) {
r += inc;
real_t x = Math::cos(r) * radius;
real_t y = Math::sqrt(radius_squared - (x * x));
// High circle ring.
PUSH_QUARTER(previous_x, previous_y, x, y, y_value);
PUSH_QUARTER(previous_y, previous_x, y, x, y_value);
// Low circle ring.
PUSH_QUARTER(previous_x, previous_y, x, y, -y_value);
PUSH_QUARTER(previous_y, previous_x, y, x, -y_value);
previous_x = x;
previous_y = y;
}
#undef PUSH_QUARTER
p_gizmo->add_lines(points, material, false, collision_color);
p_gizmo->add_collision_segments(points);
Vector<Vector3> handles = helper->cylinder_get_handles(cs2->get_height(), cs2->get_radius());
p_gizmo->add_handles(handles, handles_material);
}
if (Object::cast_to<WorldBoundaryShape3D>(*s)) {
Ref<WorldBoundaryShape3D> wbs = s;
const Plane &p = wbs->get_plane();
Vector3 n1 = p.get_any_perpendicular_normal();
Vector3 n2 = p.normal.cross(n1).normalized();
Vector3 pface[4] = {
p.normal * p.d + n1 * 10.0 + n2 * 10.0,
p.normal * p.d + n1 * 10.0 + n2 * -10.0,
p.normal * p.d + n1 * -10.0 + n2 * -10.0,
p.normal * p.d + n1 * -10.0 + n2 * 10.0,
};
Vector<Vector3> points = {
pface[0],
pface[1],
pface[1],
pface[2],
pface[2],
pface[3],
pface[3],
pface[0],
p.normal * p.d,
p.normal * p.d + p.normal * 3
};
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() > 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> lines;
lines.resize(md.edges.size() * 2);
for (uint32_t i = 0; i < md.edges.size(); i++) {
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(lines, material, false, collision_color);
p_gizmo->add_collision_segments(lines);
}
}
}
if (Object::cast_to<ConcavePolygonShape3D>(*s)) {
Ref<ConcavePolygonShape3D> cs2 = s;
Ref<ArrayMesh> mesh = cs2->get_debug_mesh();
p_gizmo->add_lines(cs2->get_debug_mesh_lines(), material, false, collision_color);
p_gizmo->add_collision_segments(cs2->get_debug_mesh_lines());
}
if (Object::cast_to<SeparationRayShape3D>(*s)) {
Ref<SeparationRayShape3D> rs = s;
Vector<Vector3> points = {
Vector3(),
Vector3(0, 0, rs->get_length())
};
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()));
p_gizmo->add_handles(handles, handles_material);
}
if (Object::cast_to<HeightMapShape3D>(*s)) {
Ref<HeightMapShape3D> hms = s;
Vector<Vector3> lines = hms->get_debug_mesh_lines();
p_gizmo->add_lines(lines, material, false, collision_color);
}
}

View File

@@ -0,0 +1,57 @@
/**************************************************************************/
/* collision_shape_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class Gizmo3DHelper;
class CollisionShape3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(CollisionShape3DGizmoPlugin, EditorNode3DGizmoPlugin);
void create_collision_material(const String &p_name, float p_alpha);
Ref<Gizmo3DHelper> helper;
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() 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 begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) 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;
CollisionShape3DGizmoPlugin();
};

View File

@@ -0,0 +1,723 @@
/**************************************************************************/
/* joint_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 "joint_3d_gizmo_plugin.h"
#include "editor/editor_node.h"
#include "editor/settings/editor_settings.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"
#include "scene/3d/physics/joints/pin_joint_3d.h"
#include "scene/3d/physics/joints/slider_joint_3d.h"
#define BODY_A_RADIUS 0.25
#define BODY_B_RADIUS 0.27
Basis JointGizmosDrawer::look_body(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
const Vector3 &p_eye(p_joint_transform.origin);
const Vector3 &p_target(p_body_transform.origin);
Vector3 v_x, v_y, v_z;
// Look the body with X
v_x = p_target - p_eye;
v_x.normalize();
v_z = v_x.cross(Vector3(0, 1, 0));
v_z.normalize();
v_y = v_z.cross(v_x);
v_y.normalize();
Basis base;
base.set_columns(v_x, v_y, v_z);
// Absorb current joint transform
base = p_joint_transform.basis.inverse() * base;
return base;
}
Basis JointGizmosDrawer::look_body_toward(Vector3::Axis p_axis, const Transform3D &joint_transform, const Transform3D &body_transform) {
switch (p_axis) {
case Vector3::AXIS_X:
return look_body_toward_x(joint_transform, body_transform);
case Vector3::AXIS_Y:
return look_body_toward_y(joint_transform, body_transform);
case Vector3::AXIS_Z:
return look_body_toward_z(joint_transform, body_transform);
default:
return Basis();
}
}
Basis JointGizmosDrawer::look_body_toward_x(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
const Vector3 &p_eye(p_joint_transform.origin);
const Vector3 &p_target(p_body_transform.origin);
const Vector3 p_front(p_joint_transform.basis.get_column(0));
Vector3 v_x, v_y, v_z;
// Look the body with X
v_x = p_target - p_eye;
v_x.normalize();
v_y = p_front.cross(v_x);
v_y.normalize();
v_z = v_y.cross(p_front);
v_z.normalize();
// Clamp X to FRONT axis
v_x = p_front;
v_x.normalize();
Basis base;
base.set_columns(v_x, v_y, v_z);
// Absorb current joint transform
base = p_joint_transform.basis.inverse() * base;
return base;
}
Basis JointGizmosDrawer::look_body_toward_y(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
const Vector3 &p_eye(p_joint_transform.origin);
const Vector3 &p_target(p_body_transform.origin);
const Vector3 p_up(p_joint_transform.basis.get_column(1));
Vector3 v_x, v_y, v_z;
// Look the body with X
v_x = p_target - p_eye;
v_x.normalize();
v_z = v_x.cross(p_up);
v_z.normalize();
v_x = p_up.cross(v_z);
v_x.normalize();
// Clamp Y to UP axis
v_y = p_up;
v_y.normalize();
Basis base;
base.set_columns(v_x, v_y, v_z);
// Absorb current joint transform
base = p_joint_transform.basis.inverse() * base;
return base;
}
Basis JointGizmosDrawer::look_body_toward_z(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
const Vector3 &p_eye(p_joint_transform.origin);
const Vector3 &p_target(p_body_transform.origin);
const Vector3 p_lateral(p_joint_transform.basis.get_column(2));
Vector3 v_x, v_y, v_z;
// Look the body with X
v_x = p_target - p_eye;
v_x.normalize();
v_z = p_lateral;
v_z.normalize();
v_y = v_z.cross(v_x);
v_y.normalize();
// Clamp X to Z axis
v_x = v_y.cross(v_z);
v_x.normalize();
Basis base;
base.set_columns(v_x, v_y, v_z);
// Absorb current joint transform
base = p_joint_transform.basis.inverse() * base;
return base;
}
void JointGizmosDrawer::draw_circle(Vector3::Axis p_axis, real_t p_radius, const Transform3D &p_offset, const Basis &p_base, real_t p_limit_lower, real_t p_limit_upper, Vector<Vector3> &r_points, bool p_inverse) {
if (p_limit_lower == p_limit_upper) {
r_points.push_back(p_offset.translated_local(Vector3()).origin);
r_points.push_back(p_offset.translated_local(p_base.xform(Vector3(0.5, 0, 0))).origin);
} else {
if (p_limit_lower > p_limit_upper) {
p_limit_lower = -Math::PI;
p_limit_upper = Math::PI;
}
const int points = 32;
for (int i = 0; i < points; i++) {
real_t s = p_limit_lower + i * (p_limit_upper - p_limit_lower) / points;
real_t n = p_limit_lower + (i + 1) * (p_limit_upper - p_limit_lower) / points;
Vector3 from;
Vector3 to;
switch (p_axis) {
case Vector3::AXIS_X:
if (p_inverse) {
from = p_base.xform(Vector3(0, Math::sin(s), Math::cos(s))) * p_radius;
to = p_base.xform(Vector3(0, Math::sin(n), Math::cos(n))) * p_radius;
} else {
from = p_base.xform(Vector3(0, -Math::sin(s), Math::cos(s))) * p_radius;
to = p_base.xform(Vector3(0, -Math::sin(n), Math::cos(n))) * p_radius;
}
break;
case Vector3::AXIS_Y:
if (p_inverse) {
from = p_base.xform(Vector3(Math::cos(s), 0, -Math::sin(s))) * p_radius;
to = p_base.xform(Vector3(Math::cos(n), 0, -Math::sin(n))) * p_radius;
} else {
from = p_base.xform(Vector3(Math::cos(s), 0, Math::sin(s))) * p_radius;
to = p_base.xform(Vector3(Math::cos(n), 0, Math::sin(n))) * p_radius;
}
break;
case Vector3::AXIS_Z:
from = p_base.xform(Vector3(Math::cos(s), Math::sin(s), 0)) * p_radius;
to = p_base.xform(Vector3(Math::cos(n), Math::sin(n), 0)) * p_radius;
break;
}
if (i == points - 1) {
r_points.push_back(p_offset.translated_local(to).origin);
r_points.push_back(p_offset.translated_local(Vector3()).origin);
}
if (i == 0) {
r_points.push_back(p_offset.translated_local(from).origin);
r_points.push_back(p_offset.translated_local(Vector3()).origin);
}
r_points.push_back(p_offset.translated_local(from).origin);
r_points.push_back(p_offset.translated_local(to).origin);
}
r_points.push_back(p_offset.translated_local(Vector3(0, p_radius * 1.5, 0)).origin);
r_points.push_back(p_offset.translated_local(Vector3()).origin);
}
}
void JointGizmosDrawer::draw_cone(const Transform3D &p_offset, const Basis &p_base, real_t p_swing, real_t p_twist, Vector<Vector3> &r_points) {
float r = 1.0;
float w = r * Math::sin(p_swing);
float d = r * Math::cos(p_swing);
//swing
for (int i = 0; i < 360; i += 10) {
float ra = Math::deg_to_rad((float)i);
float rb = Math::deg_to_rad((float)i + 10);
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w;
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w;
r_points.push_back(p_offset.translated_local(p_base.xform(Vector3(d, a.x, a.y))).origin);
r_points.push_back(p_offset.translated_local(p_base.xform(Vector3(d, b.x, b.y))).origin);
if (i % 90 == 0) {
r_points.push_back(p_offset.translated_local(p_base.xform(Vector3(d, a.x, a.y))).origin);
r_points.push_back(p_offset.translated_local(p_base.xform(Vector3())).origin);
}
}
r_points.push_back(p_offset.translated_local(p_base.xform(Vector3())).origin);
r_points.push_back(p_offset.translated_local(p_base.xform(Vector3(1, 0, 0))).origin);
/// Twist
float ts = Math::rad_to_deg(p_twist);
ts = MIN(ts, 720);
for (int i = 0; i < int(ts); i += 5) {
float ra = Math::deg_to_rad((float)i);
float rb = Math::deg_to_rad((float)i + 5);
float c = i / 720.0;
float cn = (i + 5) / 720.0;
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w * c;
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w * cn;
r_points.push_back(p_offset.translated_local(p_base.xform(Vector3(c, a.x, a.y))).origin);
r_points.push_back(p_offset.translated_local(p_base.xform(Vector3(cn, b.x, b.y))).origin);
}
}
////
Joint3DGizmoPlugin::Joint3DGizmoPlugin() {
create_material("joint_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/joint"));
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");
update_timer->set_wait_time(1.0 / 120.0);
update_timer->connect("timeout", callable_mp(this, &Joint3DGizmoPlugin::incremental_update_gizmos));
update_timer->set_autostart(true);
callable_mp((Node *)EditorNode::get_singleton(), &Node::add_child).call_deferred(update_timer, false, Node::INTERNAL_MODE_DISABLED);
}
void Joint3DGizmoPlugin::incremental_update_gizmos() {
if (!current_gizmos.is_empty()) {
HashSet<EditorNode3DGizmo *>::Iterator E = current_gizmos.find(last_drawn);
if (E) {
++E;
}
if (!E) {
E = current_gizmos.begin();
}
redraw(*E);
last_drawn = *E;
}
}
bool Joint3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<Joint3D>(p_spatial) != nullptr;
}
String Joint3DGizmoPlugin::get_gizmo_name() const {
return "Joint3D";
}
int Joint3DGizmoPlugin::get_priority() const {
return -1;
}
void Joint3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Joint3D *joint = Object::cast_to<Joint3D>(p_gizmo->get_node_3d());
p_gizmo->clear();
Node3D *node_body_a = nullptr;
if (!joint->get_node_a().is_empty()) {
node_body_a = Object::cast_to<Node3D>(joint->get_node(joint->get_node_a()));
}
Node3D *node_body_b = nullptr;
if (!joint->get_node_b().is_empty()) {
node_body_b = Object::cast_to<Node3D>(joint->get_node(joint->get_node_b()));
}
if (!node_body_a && !node_body_b) {
return;
}
Ref<Material> common_material = get_material("joint_material", p_gizmo);
Ref<Material> body_a_material = get_material("joint_body_a_material", p_gizmo);
Ref<Material> body_b_material = get_material("joint_body_b_material", p_gizmo);
Vector<Vector3> points;
Vector<Vector3> body_a_points;
Vector<Vector3> body_b_points;
if (Object::cast_to<PinJoint3D>(joint)) {
CreatePinJointGizmo(Transform3D(), points);
p_gizmo->add_collision_segments(points);
p_gizmo->add_lines(points, common_material);
}
HingeJoint3D *hinge = Object::cast_to<HingeJoint3D>(joint);
if (hinge) {
CreateHingeJointGizmo(
Transform3D(),
hinge->get_global_transform(),
node_body_a ? node_body_a->get_global_transform() : Transform3D(),
node_body_b ? node_body_b->get_global_transform() : Transform3D(),
hinge->get_param(HingeJoint3D::PARAM_LIMIT_LOWER),
hinge->get_param(HingeJoint3D::PARAM_LIMIT_UPPER),
hinge->get_flag(HingeJoint3D::FLAG_USE_LIMIT),
points,
node_body_a ? &body_a_points : nullptr,
node_body_b ? &body_b_points : nullptr);
p_gizmo->add_collision_segments(points);
p_gizmo->add_collision_segments(body_a_points);
p_gizmo->add_collision_segments(body_b_points);
p_gizmo->add_lines(points, common_material);
p_gizmo->add_lines(body_a_points, body_a_material);
p_gizmo->add_lines(body_b_points, body_b_material);
}
SliderJoint3D *slider = Object::cast_to<SliderJoint3D>(joint);
if (slider) {
CreateSliderJointGizmo(
Transform3D(),
slider->get_global_transform(),
node_body_a ? node_body_a->get_global_transform() : Transform3D(),
node_body_b ? node_body_b->get_global_transform() : Transform3D(),
slider->get_param(SliderJoint3D::PARAM_ANGULAR_LIMIT_LOWER),
slider->get_param(SliderJoint3D::PARAM_ANGULAR_LIMIT_UPPER),
slider->get_param(SliderJoint3D::PARAM_LINEAR_LIMIT_LOWER),
slider->get_param(SliderJoint3D::PARAM_LINEAR_LIMIT_UPPER),
points,
node_body_a ? &body_a_points : nullptr,
node_body_b ? &body_b_points : nullptr);
p_gizmo->add_collision_segments(points);
p_gizmo->add_collision_segments(body_a_points);
p_gizmo->add_collision_segments(body_b_points);
p_gizmo->add_lines(points, common_material);
p_gizmo->add_lines(body_a_points, body_a_material);
p_gizmo->add_lines(body_b_points, body_b_material);
}
ConeTwistJoint3D *cone = Object::cast_to<ConeTwistJoint3D>(joint);
if (cone) {
CreateConeTwistJointGizmo(
Transform3D(),
cone->get_global_transform(),
node_body_a ? node_body_a->get_global_transform() : Transform3D(),
node_body_b ? node_body_b->get_global_transform() : Transform3D(),
cone->get_param(ConeTwistJoint3D::PARAM_SWING_SPAN),
cone->get_param(ConeTwistJoint3D::PARAM_TWIST_SPAN),
node_body_a ? &body_a_points : nullptr,
node_body_b ? &body_b_points : nullptr);
p_gizmo->add_collision_segments(body_a_points);
p_gizmo->add_collision_segments(body_b_points);
p_gizmo->add_lines(body_a_points, body_a_material);
p_gizmo->add_lines(body_b_points, body_b_material);
}
Generic6DOFJoint3D *gen = Object::cast_to<Generic6DOFJoint3D>(joint);
if (gen) {
CreateGeneric6DOFJointGizmo(
Transform3D(),
gen->get_global_transform(),
node_body_a ? node_body_a->get_global_transform() : Transform3D(),
node_body_b ? node_body_b->get_global_transform() : Transform3D(),
gen->get_param_x(Generic6DOFJoint3D::PARAM_ANGULAR_LOWER_LIMIT),
gen->get_param_x(Generic6DOFJoint3D::PARAM_ANGULAR_UPPER_LIMIT),
gen->get_param_x(Generic6DOFJoint3D::PARAM_LINEAR_LOWER_LIMIT),
gen->get_param_x(Generic6DOFJoint3D::PARAM_LINEAR_UPPER_LIMIT),
gen->get_flag_x(Generic6DOFJoint3D::FLAG_ENABLE_ANGULAR_LIMIT),
gen->get_flag_x(Generic6DOFJoint3D::FLAG_ENABLE_LINEAR_LIMIT),
gen->get_param_y(Generic6DOFJoint3D::PARAM_ANGULAR_LOWER_LIMIT),
gen->get_param_y(Generic6DOFJoint3D::PARAM_ANGULAR_UPPER_LIMIT),
gen->get_param_y(Generic6DOFJoint3D::PARAM_LINEAR_LOWER_LIMIT),
gen->get_param_y(Generic6DOFJoint3D::PARAM_LINEAR_UPPER_LIMIT),
gen->get_flag_y(Generic6DOFJoint3D::FLAG_ENABLE_ANGULAR_LIMIT),
gen->get_flag_y(Generic6DOFJoint3D::FLAG_ENABLE_LINEAR_LIMIT),
gen->get_param_z(Generic6DOFJoint3D::PARAM_ANGULAR_LOWER_LIMIT),
gen->get_param_z(Generic6DOFJoint3D::PARAM_ANGULAR_UPPER_LIMIT),
gen->get_param_z(Generic6DOFJoint3D::PARAM_LINEAR_LOWER_LIMIT),
gen->get_param_z(Generic6DOFJoint3D::PARAM_LINEAR_UPPER_LIMIT),
gen->get_flag_z(Generic6DOFJoint3D::FLAG_ENABLE_ANGULAR_LIMIT),
gen->get_flag_z(Generic6DOFJoint3D::FLAG_ENABLE_LINEAR_LIMIT),
points,
node_body_a ? &body_a_points : nullptr,
node_body_a ? &body_b_points : nullptr);
p_gizmo->add_collision_segments(points);
p_gizmo->add_collision_segments(body_a_points);
p_gizmo->add_collision_segments(body_b_points);
p_gizmo->add_lines(points, common_material);
p_gizmo->add_lines(body_a_points, body_a_material);
p_gizmo->add_lines(body_b_points, body_b_material);
}
}
void Joint3DGizmoPlugin::CreatePinJointGizmo(const Transform3D &p_offset, Vector<Vector3> &r_cursor_points) {
float cs = 0.25;
r_cursor_points.push_back(p_offset.translated_local(Vector3(+cs, 0, 0)).origin);
r_cursor_points.push_back(p_offset.translated_local(Vector3(-cs, 0, 0)).origin);
r_cursor_points.push_back(p_offset.translated_local(Vector3(0, +cs, 0)).origin);
r_cursor_points.push_back(p_offset.translated_local(Vector3(0, -cs, 0)).origin);
r_cursor_points.push_back(p_offset.translated_local(Vector3(0, 0, +cs)).origin);
r_cursor_points.push_back(p_offset.translated_local(Vector3(0, 0, -cs)).origin);
}
void Joint3DGizmoPlugin::CreateHingeJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_limit_lower, real_t p_limit_upper, bool p_use_limit, Vector<Vector3> &r_common_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points) {
r_common_points.push_back(p_offset.translated_local(Vector3(0, 0, 0.5)).origin);
r_common_points.push_back(p_offset.translated_local(Vector3(0, 0, -0.5)).origin);
if (!p_use_limit) {
p_limit_upper = -1;
p_limit_lower = 0;
}
if (r_body_a_points) {
JointGizmosDrawer::draw_circle(Vector3::AXIS_Z,
BODY_A_RADIUS,
p_offset,
JointGizmosDrawer::look_body_toward_z(p_trs_joint, p_trs_body_a),
p_limit_lower,
p_limit_upper,
*r_body_a_points);
}
if (r_body_b_points) {
JointGizmosDrawer::draw_circle(Vector3::AXIS_Z,
BODY_B_RADIUS,
p_offset,
JointGizmosDrawer::look_body_toward_z(p_trs_joint, p_trs_body_b),
p_limit_lower,
p_limit_upper,
*r_body_b_points);
}
}
void Joint3DGizmoPlugin::CreateSliderJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_angular_limit_lower, real_t p_angular_limit_upper, real_t p_linear_limit_lower, real_t p_linear_limit_upper, Vector<Vector3> &r_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points) {
p_linear_limit_lower = -p_linear_limit_lower;
p_linear_limit_upper = -p_linear_limit_upper;
float cs = 0.25;
r_points.push_back(p_offset.translated_local(Vector3(0, 0, 0.5)).origin);
r_points.push_back(p_offset.translated_local(Vector3(0, 0, -0.5)).origin);
if (p_linear_limit_lower >= p_linear_limit_upper) {
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, 0, 0)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, 0, 0)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, -cs, -cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, -cs, cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, -cs, cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, cs, cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, cs, cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, cs, -cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, cs, -cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, -cs, -cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, -cs, -cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, -cs, cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, -cs, cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, cs, cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, cs, cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, cs, -cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, cs, -cs)).origin);
r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, -cs, -cs)).origin);
} else {
r_points.push_back(p_offset.translated_local(Vector3(+cs * 2, 0, 0)).origin);
r_points.push_back(p_offset.translated_local(Vector3(-cs * 2, 0, 0)).origin);
}
if (r_body_a_points) {
JointGizmosDrawer::draw_circle(
Vector3::AXIS_X,
BODY_A_RADIUS,
p_offset,
JointGizmosDrawer::look_body_toward(Vector3::AXIS_X, p_trs_joint, p_trs_body_a),
p_angular_limit_lower,
p_angular_limit_upper,
*r_body_a_points);
}
if (r_body_b_points) {
JointGizmosDrawer::draw_circle(
Vector3::AXIS_X,
BODY_B_RADIUS,
p_offset,
JointGizmosDrawer::look_body_toward(Vector3::AXIS_X, p_trs_joint, p_trs_body_b),
p_angular_limit_lower,
p_angular_limit_upper,
*r_body_b_points,
true);
}
}
void Joint3DGizmoPlugin::CreateConeTwistJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_swing, real_t p_twist, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points) {
if (r_body_a_points) {
JointGizmosDrawer::draw_cone(
p_offset,
JointGizmosDrawer::look_body(p_trs_joint, p_trs_body_a),
p_swing,
p_twist,
*r_body_a_points);
}
if (r_body_b_points) {
JointGizmosDrawer::draw_cone(
p_offset,
JointGizmosDrawer::look_body(p_trs_joint, p_trs_body_b),
p_swing,
p_twist,
*r_body_b_points);
}
}
void Joint3DGizmoPlugin::CreateGeneric6DOFJointGizmo(
const Transform3D &p_offset,
const Transform3D &p_trs_joint,
const Transform3D &p_trs_body_a,
const Transform3D &p_trs_body_b,
real_t p_angular_limit_lower_x,
real_t p_angular_limit_upper_x,
real_t p_linear_limit_lower_x,
real_t p_linear_limit_upper_x,
bool p_enable_angular_limit_x,
bool p_enable_linear_limit_x,
real_t p_angular_limit_lower_y,
real_t p_angular_limit_upper_y,
real_t p_linear_limit_lower_y,
real_t p_linear_limit_upper_y,
bool p_enable_angular_limit_y,
bool p_enable_linear_limit_y,
real_t p_angular_limit_lower_z,
real_t p_angular_limit_upper_z,
real_t p_linear_limit_lower_z,
real_t p_linear_limit_upper_z,
bool p_enable_angular_limit_z,
bool p_enable_linear_limit_z,
Vector<Vector3> &r_points,
Vector<Vector3> *r_body_a_points,
Vector<Vector3> *r_body_b_points) {
float cs = 0.25;
for (int ax = 0; ax < 3; ax++) {
float ll = 0;
float ul = 0;
float lll = 0;
float lul = 0;
int a1 = 0;
int a2 = 0;
int a3 = 0;
bool enable_ang = false;
bool enable_lin = false;
switch (ax) {
case 0:
ll = p_angular_limit_lower_x;
ul = p_angular_limit_upper_x;
lll = -p_linear_limit_lower_x;
lul = -p_linear_limit_upper_x;
enable_ang = p_enable_angular_limit_x;
enable_lin = p_enable_linear_limit_x;
a1 = 0;
a2 = 1;
a3 = 2;
break;
case 1:
ll = p_angular_limit_lower_y;
ul = p_angular_limit_upper_y;
lll = -p_linear_limit_lower_y;
lul = -p_linear_limit_upper_y;
enable_ang = p_enable_angular_limit_y;
enable_lin = p_enable_linear_limit_y;
a1 = 1;
a2 = 2;
a3 = 0;
break;
case 2:
ll = p_angular_limit_lower_z;
ul = p_angular_limit_upper_z;
lll = -p_linear_limit_lower_z;
lul = -p_linear_limit_upper_z;
enable_ang = p_enable_angular_limit_z;
enable_lin = p_enable_linear_limit_z;
a1 = 2;
a2 = 0;
a3 = 1;
break;
}
#define ADD_VTX(x, y, z) \
{ \
Vector3 v; \
v[a1] = (x); \
v[a2] = (y); \
v[a3] = (z); \
r_points.push_back(p_offset.translated_local(v).origin); \
}
if (enable_lin && lll >= lul) {
ADD_VTX(lul, 0, 0);
ADD_VTX(lll, 0, 0);
ADD_VTX(lul, -cs, -cs);
ADD_VTX(lul, -cs, cs);
ADD_VTX(lul, -cs, cs);
ADD_VTX(lul, cs, cs);
ADD_VTX(lul, cs, cs);
ADD_VTX(lul, cs, -cs);
ADD_VTX(lul, cs, -cs);
ADD_VTX(lul, -cs, -cs);
ADD_VTX(lll, -cs, -cs);
ADD_VTX(lll, -cs, cs);
ADD_VTX(lll, -cs, cs);
ADD_VTX(lll, cs, cs);
ADD_VTX(lll, cs, cs);
ADD_VTX(lll, cs, -cs);
ADD_VTX(lll, cs, -cs);
ADD_VTX(lll, -cs, -cs);
} else {
ADD_VTX(+cs * 2, 0, 0);
ADD_VTX(-cs * 2, 0, 0);
}
if (!enable_ang) {
ll = 0;
ul = -1;
}
if (r_body_a_points) {
JointGizmosDrawer::draw_circle(
static_cast<Vector3::Axis>(ax),
BODY_A_RADIUS,
p_offset,
JointGizmosDrawer::look_body_toward(static_cast<Vector3::Axis>(ax), p_trs_joint, p_trs_body_a),
ll,
ul,
*r_body_a_points,
true);
}
if (r_body_b_points) {
JointGizmosDrawer::draw_circle(
static_cast<Vector3::Axis>(ax),
BODY_B_RADIUS,
p_offset,
JointGizmosDrawer::look_body_toward(static_cast<Vector3::Axis>(ax), p_trs_joint, p_trs_body_b),
ll,
ul,
*r_body_b_points);
}
}
#undef ADD_VTX
}

View File

@@ -0,0 +1,96 @@
/**************************************************************************/
/* joint_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class Joint3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Joint3DGizmoPlugin, EditorNode3DGizmoPlugin);
Timer *update_timer = nullptr;
EditorNode3DGizmo *last_drawn = nullptr;
void incremental_update_gizmos();
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const override;
void redraw(EditorNode3DGizmo *p_gizmo) override;
static void CreatePinJointGizmo(const Transform3D &p_offset, Vector<Vector3> &r_cursor_points);
static void CreateHingeJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_limit_lower, real_t p_limit_upper, bool p_use_limit, Vector<Vector3> &r_common_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points);
static void CreateSliderJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_angular_limit_lower, real_t p_angular_limit_upper, real_t p_linear_limit_lower, real_t p_linear_limit_upper, Vector<Vector3> &r_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points);
static void CreateConeTwistJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_swing, real_t p_twist, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points);
static void CreateGeneric6DOFJointGizmo(
const Transform3D &p_offset,
const Transform3D &p_trs_joint,
const Transform3D &p_trs_body_a,
const Transform3D &p_trs_body_b,
real_t p_angular_limit_lower_x,
real_t p_angular_limit_upper_x,
real_t p_linear_limit_lower_x,
real_t p_linear_limit_upper_x,
bool p_enable_angular_limit_x,
bool p_enable_linear_limit_x,
real_t p_angular_limit_lower_y,
real_t p_angular_limit_upper_y,
real_t p_linear_limit_lower_y,
real_t p_linear_limit_upper_y,
bool p_enable_angular_limit_y,
bool p_enable_linear_limit_y,
real_t p_angular_limit_lower_z,
real_t p_angular_limit_upper_z,
real_t p_linear_limit_lower_z,
real_t p_linear_limit_upper_z,
bool p_enable_angular_limit_z,
bool p_enable_linear_limit_z,
Vector<Vector3> &r_points,
Vector<Vector3> *r_body_a_points,
Vector<Vector3> *r_body_b_points);
Joint3DGizmoPlugin();
};
class JointGizmosDrawer {
public:
static Basis look_body(const Transform3D &p_joint_transform, const Transform3D &p_body_transform);
static Basis look_body_toward(Vector3::Axis p_axis, const Transform3D &joint_transform, const Transform3D &body_transform);
static Basis look_body_toward_x(const Transform3D &p_joint_transform, const Transform3D &p_body_transform);
static Basis look_body_toward_y(const Transform3D &p_joint_transform, const Transform3D &p_body_transform);
/// Special function just used for physics joints, it returns a basis constrained toward Joint Z axis
/// with axis X and Y that are looking toward the body and oriented toward up
static Basis look_body_toward_z(const Transform3D &p_joint_transform, const Transform3D &p_body_transform);
// Draw circle around p_axis
static void draw_circle(Vector3::Axis p_axis, real_t p_radius, const Transform3D &p_offset, const Basis &p_base, real_t p_limit_lower, real_t p_limit_upper, Vector<Vector3> &r_points, bool p_inverse = false);
static void draw_cone(const Transform3D &p_offset, const Basis &p_base, real_t p_swing, real_t p_twist, Vector<Vector3> &r_points);
};

View File

@@ -0,0 +1,167 @@
/**************************************************************************/
/* physics_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 "physics_bone_3d_gizmo_plugin.h"
#include "editor/scene/3d/gizmos/physics/joint_3d_gizmo_plugin.h"
#include "editor/settings/editor_settings.h"
#include "scene/3d/physics/physical_bone_3d.h"
#include "scene/3d/physics/physical_bone_simulator_3d.h"
PhysicalBone3DGizmoPlugin::PhysicalBone3DGizmoPlugin() {
create_material("joint_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/joint"));
}
bool PhysicalBone3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<PhysicalBone3D>(p_spatial) != nullptr;
}
String PhysicalBone3DGizmoPlugin::get_gizmo_name() const {
return "PhysicalBone3D";
}
int PhysicalBone3DGizmoPlugin::get_priority() const {
return -1;
}
void PhysicalBone3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p_gizmo->clear();
PhysicalBone3D *physical_bone = Object::cast_to<PhysicalBone3D>(p_gizmo->get_node_3d());
if (!physical_bone) {
return;
}
PhysicalBoneSimulator3D *sm(physical_bone->get_simulator());
if (!sm) {
return;
}
PhysicalBone3D *pb(sm->get_physical_bone(physical_bone->get_bone_id()));
if (!pb) {
return;
}
PhysicalBone3D *pbp(sm->get_physical_bone_parent(physical_bone->get_bone_id()));
if (!pbp) {
return;
}
Vector<Vector3> points;
switch (physical_bone->get_joint_type()) {
case PhysicalBone3D::JOINT_TYPE_PIN: {
Joint3DGizmoPlugin::CreatePinJointGizmo(physical_bone->get_joint_offset(), points);
} break;
case PhysicalBone3D::JOINT_TYPE_CONE: {
const PhysicalBone3D::ConeJointData *cjd(static_cast<const PhysicalBone3D::ConeJointData *>(physical_bone->get_joint_data()));
Joint3DGizmoPlugin::CreateConeTwistJointGizmo(
physical_bone->get_joint_offset(),
physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
pb->get_global_transform(),
pbp->get_global_transform(),
cjd->swing_span,
cjd->twist_span,
&points,
&points);
} break;
case PhysicalBone3D::JOINT_TYPE_HINGE: {
const PhysicalBone3D::HingeJointData *hjd(static_cast<const PhysicalBone3D::HingeJointData *>(physical_bone->get_joint_data()));
Joint3DGizmoPlugin::CreateHingeJointGizmo(
physical_bone->get_joint_offset(),
physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
pb->get_global_transform(),
pbp->get_global_transform(),
hjd->angular_limit_lower,
hjd->angular_limit_upper,
hjd->angular_limit_enabled,
points,
&points,
&points);
} break;
case PhysicalBone3D::JOINT_TYPE_SLIDER: {
const PhysicalBone3D::SliderJointData *sjd(static_cast<const PhysicalBone3D::SliderJointData *>(physical_bone->get_joint_data()));
Joint3DGizmoPlugin::CreateSliderJointGizmo(
physical_bone->get_joint_offset(),
physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
pb->get_global_transform(),
pbp->get_global_transform(),
sjd->angular_limit_lower,
sjd->angular_limit_upper,
sjd->linear_limit_lower,
sjd->linear_limit_upper,
points,
&points,
&points);
} break;
case PhysicalBone3D::JOINT_TYPE_6DOF: {
const PhysicalBone3D::SixDOFJointData *sdofjd(static_cast<const PhysicalBone3D::SixDOFJointData *>(physical_bone->get_joint_data()));
Joint3DGizmoPlugin::CreateGeneric6DOFJointGizmo(
physical_bone->get_joint_offset(),
physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
pb->get_global_transform(),
pbp->get_global_transform(),
sdofjd->axis_data[0].angular_limit_lower,
sdofjd->axis_data[0].angular_limit_upper,
sdofjd->axis_data[0].linear_limit_lower,
sdofjd->axis_data[0].linear_limit_upper,
sdofjd->axis_data[0].angular_limit_enabled,
sdofjd->axis_data[0].linear_limit_enabled,
sdofjd->axis_data[1].angular_limit_lower,
sdofjd->axis_data[1].angular_limit_upper,
sdofjd->axis_data[1].linear_limit_lower,
sdofjd->axis_data[1].linear_limit_upper,
sdofjd->axis_data[1].angular_limit_enabled,
sdofjd->axis_data[1].linear_limit_enabled,
sdofjd->axis_data[2].angular_limit_lower,
sdofjd->axis_data[2].angular_limit_upper,
sdofjd->axis_data[2].linear_limit_lower,
sdofjd->axis_data[2].linear_limit_upper,
sdofjd->axis_data[2].angular_limit_enabled,
sdofjd->axis_data[2].linear_limit_enabled,
points,
&points,
&points);
} break;
default:
return;
}
Ref<Material> material = get_material("joint_material", p_gizmo);
p_gizmo->add_collision_segments(points);
p_gizmo->add_lines(points, material);
}

View File

@@ -0,0 +1,45 @@
/**************************************************************************/
/* physics_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. */
/**************************************************************************/
#pragma once
#include "editor/scene/3d/node_3d_editor_gizmos.h"
class PhysicalBone3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(PhysicalBone3DGizmoPlugin, EditorNode3DGizmoPlugin);
public:
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const override;
void redraw(EditorNode3DGizmo *p_gizmo) override;
PhysicalBone3DGizmoPlugin();
};

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