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
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:
9
editor/scene/2d/SCsub
Normal file
9
editor/scene/2d/SCsub
Normal 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")
|
||||
953
editor/scene/2d/abstract_polygon_2d_editor.cpp
Normal file
953
editor/scene/2d/abstract_polygon_2d_editor.cpp
Normal 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 ¢er = _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();
|
||||
}
|
||||
177
editor/scene/2d/abstract_polygon_2d_editor.h
Normal file
177
editor/scene/2d/abstract_polygon_2d_editor.h
Normal 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);
|
||||
};
|
||||
333
editor/scene/2d/camera_2d_editor_plugin.cpp
Normal file
333
editor/scene/2d/camera_2d_editor_plugin.cpp
Normal 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);
|
||||
}
|
||||
103
editor/scene/2d/camera_2d_editor_plugin.h
Normal file
103
editor/scene/2d/camera_2d_editor_plugin.h
Normal 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();
|
||||
};
|
||||
111
editor/scene/2d/light_occluder_2d_editor_plugin.cpp
Normal file
111
editor/scene/2d/light_occluder_2d_editor_plugin.cpp
Normal 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") {
|
||||
}
|
||||
63
editor/scene/2d/light_occluder_2d_editor_plugin.h
Normal file
63
editor/scene/2d/light_occluder_2d_editor_plugin.h
Normal 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();
|
||||
};
|
||||
64
editor/scene/2d/line_2d_editor_plugin.cpp
Normal file
64
editor/scene/2d/line_2d_editor_plugin.cpp
Normal 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") {
|
||||
}
|
||||
56
editor/scene/2d/line_2d_editor_plugin.h
Normal file
56
editor/scene/2d/line_2d_editor_plugin.h
Normal 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();
|
||||
};
|
||||
139
editor/scene/2d/parallax_background_editor_plugin.cpp
Normal file
139
editor/scene/2d/parallax_background_editor_plugin.cpp
Normal 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);
|
||||
}
|
||||
64
editor/scene/2d/parallax_background_editor_plugin.h
Normal file
64
editor/scene/2d/parallax_background_editor_plugin.h
Normal 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();
|
||||
};
|
||||
540
editor/scene/2d/particles_2d_editor_plugin.cpp
Normal file
540
editor/scene/2d/particles_2d_editor_plugin.cpp
Normal 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");
|
||||
}
|
||||
109
editor/scene/2d/particles_2d_editor_plugin.h
Normal file
109
editor/scene/2d/particles_2d_editor_plugin.h
Normal 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();
|
||||
};
|
||||
1006
editor/scene/2d/path_2d_editor_plugin.cpp
Normal file
1006
editor/scene/2d/path_2d_editor_plugin.cpp
Normal file
File diff suppressed because it is too large
Load Diff
157
editor/scene/2d/path_2d_editor_plugin.h
Normal file
157
editor/scene/2d/path_2d_editor_plugin.h
Normal 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();
|
||||
};
|
||||
6
editor/scene/2d/physics/SCsub
Normal file
6
editor/scene/2d/physics/SCsub
Normal 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")
|
||||
167
editor/scene/2d/physics/cast_2d_editor_plugin.cpp
Normal file
167
editor/scene/2d/physics/cast_2d_editor_plugin.cpp
Normal 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);
|
||||
}
|
||||
74
editor/scene/2d/physics/cast_2d_editor_plugin.h
Normal file
74
editor/scene/2d/physics/cast_2d_editor_plugin.h
Normal 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();
|
||||
};
|
||||
@@ -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") {
|
||||
}
|
||||
54
editor/scene/2d/physics/collision_polygon_2d_editor_plugin.h
Normal file
54
editor/scene/2d/physics/collision_polygon_2d_editor_plugin.h
Normal 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();
|
||||
};
|
||||
667
editor/scene/2d/physics/collision_shape_2d_editor_plugin.cpp
Normal file
667
editor/scene/2d/physics/collision_shape_2d_editor_plugin.cpp
Normal 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);
|
||||
}
|
||||
114
editor/scene/2d/physics/collision_shape_2d_editor_plugin.h
Normal file
114
editor/scene/2d/physics/collision_shape_2d_editor_plugin.h
Normal 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();
|
||||
};
|
||||
1509
editor/scene/2d/polygon_2d_editor_plugin.cpp
Normal file
1509
editor/scene/2d/polygon_2d_editor_plugin.cpp
Normal file
File diff suppressed because it is too large
Load Diff
197
editor/scene/2d/polygon_2d_editor_plugin.h
Normal file
197
editor/scene/2d/polygon_2d_editor_plugin.h
Normal 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();
|
||||
};
|
||||
137
editor/scene/2d/skeleton_2d_editor_plugin.cpp
Normal file
137
editor/scene/2d/skeleton_2d_editor_plugin.cpp
Normal 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();
|
||||
}
|
||||
78
editor/scene/2d/skeleton_2d_editor_plugin.h
Normal file
78
editor/scene/2d/skeleton_2d_editor_plugin.h
Normal 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();
|
||||
};
|
||||
739
editor/scene/2d/sprite_2d_editor_plugin.cpp
Normal file
739
editor/scene/2d/sprite_2d_editor_plugin.cpp
Normal 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();
|
||||
}
|
||||
140
editor/scene/2d/sprite_2d_editor_plugin.h
Normal file
140
editor/scene/2d/sprite_2d_editor_plugin.h
Normal 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();
|
||||
};
|
||||
6
editor/scene/2d/tiles/SCsub
Normal file
6
editor/scene/2d/tiles/SCsub
Normal 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")
|
||||
369
editor/scene/2d/tiles/atlas_merging_dialog.cpp
Normal file
369
editor/scene/2d/tiles/atlas_merging_dialog.cpp
Normal 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);
|
||||
}
|
||||
84
editor/scene/2d/tiles/atlas_merging_dialog.h
Normal file
84
editor/scene/2d/tiles/atlas_merging_dialog.h
Normal 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();
|
||||
};
|
||||
766
editor/scene/2d/tiles/tile_atlas_view.cpp
Normal file
766
editor/scene/2d/tiles/tile_atlas_view.cpp
Normal 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();
|
||||
}
|
||||
171
editor/scene/2d/tiles/tile_atlas_view.h
Normal file
171
editor/scene/2d/tiles/tile_atlas_view.h
Normal 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();
|
||||
};
|
||||
3039
editor/scene/2d/tiles/tile_data_editors.cpp
Normal file
3039
editor/scene/2d/tiles/tile_data_editors.cpp
Normal file
File diff suppressed because it is too large
Load Diff
415
editor/scene/2d/tiles/tile_data_editors.h
Normal file
415
editor/scene/2d/tiles/tile_data_editors.h
Normal 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();
|
||||
};
|
||||
4539
editor/scene/2d/tiles/tile_map_layer_editor.cpp
Normal file
4539
editor/scene/2d/tiles/tile_map_layer_editor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
427
editor/scene/2d/tiles/tile_map_layer_editor.h
Normal file
427
editor/scene/2d/tiles/tile_map_layer_editor.h
Normal 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);
|
||||
492
editor/scene/2d/tiles/tile_proxies_manager_dialog.cpp
Normal file
492
editor/scene/2d/tiles/tile_proxies_manager_dialog.cpp
Normal 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);
|
||||
}
|
||||
86
editor/scene/2d/tiles/tile_proxies_manager_dialog.h
Normal file
86
editor/scene/2d/tiles/tile_proxies_manager_dialog.h
Normal 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();
|
||||
};
|
||||
3040
editor/scene/2d/tiles/tile_set_atlas_source_editor.cpp
Normal file
3040
editor/scene/2d/tiles/tile_set_atlas_source_editor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
334
editor/scene/2d/tiles/tile_set_atlas_source_editor.h
Normal file
334
editor/scene/2d/tiles/tile_set_atlas_source_editor.h
Normal 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;
|
||||
};
|
||||
1038
editor/scene/2d/tiles/tile_set_editor.cpp
Normal file
1038
editor/scene/2d/tiles/tile_set_editor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
142
editor/scene/2d/tiles/tile_set_editor.h
Normal file
142
editor/scene/2d/tiles/tile_set_editor.h
Normal 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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
145
editor/scene/2d/tiles/tile_set_scenes_collection_source_editor.h
Normal file
145
editor/scene/2d/tiles/tile_set_scenes_collection_source_editor.h
Normal 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();
|
||||
};
|
||||
555
editor/scene/2d/tiles/tiles_editor_plugin.cpp
Normal file
555
editor/scene/2d/tiles/tiles_editor_plugin.cpp
Normal 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;
|
||||
}
|
||||
164
editor/scene/2d/tiles/tiles_editor_plugin.h
Normal file
164
editor/scene/2d/tiles/tiles_editor_plugin.h
Normal 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
9
editor/scene/3d/SCsub
Normal 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")
|
||||
1478
editor/scene/3d/bone_map_editor_plugin.cpp
Normal file
1478
editor/scene/3d/bone_map_editor_plugin.cpp
Normal file
File diff suppressed because it is too large
Load Diff
223
editor/scene/3d/bone_map_editor_plugin.h
Normal file
223
editor/scene/3d/bone_map_editor_plugin.h
Normal 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();
|
||||
};
|
||||
128
editor/scene/3d/camera_3d_editor_plugin.cpp
Normal file
128
editor/scene/3d/camera_3d_editor_plugin.cpp
Normal 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);
|
||||
}
|
||||
87
editor/scene/3d/camera_3d_editor_plugin.h
Normal file
87
editor/scene/3d/camera_3d_editor_plugin.h
Normal 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();
|
||||
};
|
||||
8
editor/scene/3d/gizmos/SCsub
Normal file
8
editor/scene/3d/gizmos/SCsub
Normal 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")
|
||||
56
editor/scene/3d/gizmos/audio_listener_3d_gizmo_plugin.cpp
Normal file
56
editor/scene/3d/gizmos/audio_listener_3d_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
46
editor/scene/3d/gizmos/audio_listener_3d_gizmo_plugin.h
Normal file
46
editor/scene/3d/gizmos/audio_listener_3d_gizmo_plugin.h
Normal 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();
|
||||
};
|
||||
314
editor/scene/3d/gizmos/audio_stream_player_3d_gizmo_plugin.cpp
Normal file
314
editor/scene/3d/gizmos/audio_stream_player_3d_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
50
editor/scene/3d/gizmos/audio_stream_player_3d_gizmo_plugin.h
Normal file
50
editor/scene/3d/gizmos/audio_stream_player_3d_gizmo_plugin.h
Normal 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();
|
||||
};
|
||||
292
editor/scene/3d/gizmos/camera_3d_gizmo_plugin.cpp
Normal file
292
editor/scene/3d/gizmos/camera_3d_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
53
editor/scene/3d/gizmos/camera_3d_gizmo_plugin.h
Normal file
53
editor/scene/3d/gizmos/camera_3d_gizmo_plugin.h
Normal 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();
|
||||
};
|
||||
107
editor/scene/3d/gizmos/cpu_particles_3d_gizmo_plugin.cpp
Normal file
107
editor/scene/3d/gizmos/cpu_particles_3d_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
45
editor/scene/3d/gizmos/cpu_particles_3d_gizmo_plugin.h
Normal file
45
editor/scene/3d/gizmos/cpu_particles_3d_gizmo_plugin.h
Normal 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();
|
||||
};
|
||||
131
editor/scene/3d/gizmos/decal_gizmo_plugin.cpp
Normal file
131
editor/scene/3d/gizmos/decal_gizmo_plugin.cpp
Normal 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"));
|
||||
}
|
||||
55
editor/scene/3d/gizmos/decal_gizmo_plugin.h
Normal file
55
editor/scene/3d/gizmos/decal_gizmo_plugin.h
Normal 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();
|
||||
};
|
||||
125
editor/scene/3d/gizmos/fog_volume_gizmo_plugin.cpp
Normal file
125
editor/scene/3d/gizmos/fog_volume_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
55
editor/scene/3d/gizmos/fog_volume_gizmo_plugin.h
Normal file
55
editor/scene/3d/gizmos/fog_volume_gizmo_plugin.h
Normal 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();
|
||||
};
|
||||
74
editor/scene/3d/gizmos/geometry_instance_3d_gizmo_plugin.cpp
Normal file
74
editor/scene/3d/gizmos/geometry_instance_3d_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
44
editor/scene/3d/gizmos/geometry_instance_3d_gizmo_plugin.h
Normal file
44
editor/scene/3d/gizmos/geometry_instance_3d_gizmo_plugin.h
Normal 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;
|
||||
};
|
||||
243
editor/scene/3d/gizmos/gizmo_3d_helper.cpp
Normal file
243
editor/scene/3d/gizmos/gizmo_3d_helper.cpp
Normal 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();
|
||||
}
|
||||
84
editor/scene/3d/gizmos/gizmo_3d_helper.h
Normal file
84
editor/scene/3d/gizmos/gizmo_3d_helper.h
Normal 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);
|
||||
}
|
||||
};
|
||||
84
editor/scene/3d/gizmos/gpu_particles_3d_gizmo_plugin.cpp
Normal file
84
editor/scene/3d/gizmos/gpu_particles_3d_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
46
editor/scene/3d/gizmos/gpu_particles_3d_gizmo_plugin.h
Normal file
46
editor/scene/3d/gizmos/gpu_particles_3d_gizmo_plugin.h
Normal 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();
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
60
editor/scene/3d/gizmos/label_3d_gizmo_plugin.cpp
Normal file
60
editor/scene/3d/gizmos/label_3d_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
44
editor/scene/3d/gizmos/label_3d_gizmo_plugin.h
Normal file
44
editor/scene/3d/gizmos/label_3d_gizmo_plugin.h
Normal 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;
|
||||
};
|
||||
316
editor/scene/3d/gizmos/light_3d_gizmo_plugin.cpp
Normal file
316
editor/scene/3d/gizmos/light_3d_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
53
editor/scene/3d/gizmos/light_3d_gizmo_plugin.h
Normal file
53
editor/scene/3d/gizmos/light_3d_gizmo_plugin.h
Normal 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();
|
||||
};
|
||||
224
editor/scene/3d/gizmos/lightmap_gi_gizmo_plugin.cpp
Normal file
224
editor/scene/3d/gizmos/lightmap_gi_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
47
editor/scene/3d/gizmos/lightmap_gi_gizmo_plugin.h
Normal file
47
editor/scene/3d/gizmos/lightmap_gi_gizmo_plugin.h
Normal 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();
|
||||
};
|
||||
126
editor/scene/3d/gizmos/lightmap_probe_gizmo_plugin.cpp
Normal file
126
editor/scene/3d/gizmos/lightmap_probe_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
47
editor/scene/3d/gizmos/lightmap_probe_gizmo_plugin.h
Normal file
47
editor/scene/3d/gizmos/lightmap_probe_gizmo_plugin.h
Normal 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();
|
||||
};
|
||||
127
editor/scene/3d/gizmos/marker_3d_gizmo_plugin.cpp
Normal file
127
editor/scene/3d/gizmos/marker_3d_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
47
editor/scene/3d/gizmos/marker_3d_gizmo_plugin.h
Normal file
47
editor/scene/3d/gizmos/marker_3d_gizmo_plugin.h
Normal 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();
|
||||
};
|
||||
84
editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.cpp
Normal file
84
editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
44
editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.h
Normal file
44
editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.h
Normal 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;
|
||||
};
|
||||
284
editor/scene/3d/gizmos/occluder_instance_3d_gizmo_plugin.cpp
Normal file
284
editor/scene/3d/gizmos/occluder_instance_3d_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
50
editor/scene/3d/gizmos/occluder_instance_3d_gizmo_plugin.h
Normal file
50
editor/scene/3d/gizmos/occluder_instance_3d_gizmo_plugin.h
Normal 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();
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
6
editor/scene/3d/gizmos/physics/SCsub
Normal file
6
editor/scene/3d/gizmos/physics/SCsub
Normal 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")
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
723
editor/scene/3d/gizmos/physics/joint_3d_gizmo_plugin.cpp
Normal file
723
editor/scene/3d/gizmos/physics/joint_3d_gizmo_plugin.cpp
Normal 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
|
||||
}
|
||||
96
editor/scene/3d/gizmos/physics/joint_3d_gizmo_plugin.h
Normal file
96
editor/scene/3d/gizmos/physics/joint_3d_gizmo_plugin.h
Normal 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);
|
||||
};
|
||||
167
editor/scene/3d/gizmos/physics/physics_bone_3d_gizmo_plugin.cpp
Normal file
167
editor/scene/3d/gizmos/physics/physics_bone_3d_gizmo_plugin.cpp
Normal 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);
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user