Improve TileMap editor grid

This commit is contained in:
Haoyu Qiu 2022-03-11 10:05:02 +08:00
parent 8975470bc2
commit 86ec09faf5
2 changed files with 175 additions and 95 deletions

View file

@ -38,6 +38,15 @@
#include "editor/editor_settings.h" #include "editor/editor_settings.h"
#include "scene/gui/split_container.h" #include "scene/gui/split_container.h"
static float _lerp_fade(int total, int fade, float position) {
if (position < fade) {
return Math::inverse_lerp(0, fade, position);
} else if (position > (total - fade)) {
return Math::inverse_lerp(total, total - fade, position);
}
return 1.0;
}
void TileMapEditor::_node_removed(Node *p_node) { void TileMapEditor::_node_removed(Node *p_node) {
if (p_node == node) { if (p_node == node) {
node = nullptr; node = nullptr;
@ -441,12 +450,12 @@ void TileMapEditor::_update_palette() {
return; return;
} }
float min_size = EDITOR_DEF("editors/tile_map/preview_size", 64); float min_size = EDITOR_GET("editors/tile_map/preview_size");
min_size *= EDSCALE; min_size *= EDSCALE;
int hseparation = EDITOR_DEF("editors/tile_map/palette_item_hseparation", 8); int hseparation = EDITOR_GET("editors/tile_map/palette_item_hseparation");
bool show_tile_names = bool(EDITOR_DEF("editors/tile_map/show_tile_names", true)); bool show_tile_names = bool(EDITOR_GET("editors/tile_map/show_tile_names"));
bool show_tile_ids = bool(EDITOR_DEF("editors/tile_map/show_tile_ids", false)); bool show_tile_ids = bool(EDITOR_GET("editors/tile_map/show_tile_ids"));
bool sort_by_name = bool(EDITOR_DEF("editors/tile_map/sort_tiles_by_name", true)); bool sort_by_name = bool(EDITOR_GET("editors/tile_map/sort_tiles_by_name"));
palette->add_constant_override("hseparation", hseparation * EDSCALE); palette->add_constant_override("hseparation", hseparation * EDSCALE);
@ -796,6 +805,139 @@ void TileMapEditor::_erase_selection() {
} }
} }
void TileMapEditor::_draw_grid(Control *p_viewport, const Rect2 &p_rect) const {
if (!EDITOR_GET("editors/tile_map/display_grid")) {
return;
}
const Transform2D cell_xf = node->get_cell_transform();
const Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * node->get_global_transform();
// Fade the grid when the rendered cell size is relatively small.
const float cell_area = xform.xform(Rect2(Point2(), node->get_cell_size())).get_area();
const float distance_fade = MIN(Math::inverse_lerp(4.0f, 64.0f, cell_area), 1.0f);
if (distance_fade <= 0) {
return;
}
const Color grid_color = Color(EDITOR_GET("editors/tile_map/grid_color")) * Color(1, 1, 1, distance_fade);
const Color axis_color = Color(EDITOR_GET("editors/tile_map/axis_color")) * Color(1, 1, 1, distance_fade);
const int fade = 5;
const Rect2i si = p_rect.grow(fade);
// When zoomed in, it's useful to clip the rendering.
Rect2i clipped;
{
const Transform2D xform_inv = xform.affine_inverse();
const Size2 screen_size = p_viewport->get_size();
Rect2 rect;
rect.position = node->world_to_map(xform_inv.xform(Vector2()));
rect.expand_to(node->world_to_map(xform_inv.xform(Vector2(0, screen_size.height))));
rect.expand_to(node->world_to_map(xform_inv.xform(Vector2(screen_size.width, 0))));
rect.expand_to(node->world_to_map(xform_inv.xform(screen_size)));
clipped = rect.clip(si);
}
clipped.position -= si.position; // Relative to the fade rect, in grid unit.
const Point2i clipped_end = clipped.position + clipped.size;
if (clipped.has_no_area()) {
return;
}
Vector<Point2> points;
Vector<Color> colors;
// Vertical lines.
if (node->get_half_offset() != TileMap::HALF_OFFSET_X && node->get_half_offset() != TileMap::HALF_OFFSET_NEGATIVE_X) {
points.resize(4);
colors.resize(4);
for (int x = clipped.position.x; x <= clipped_end.x; x++) {
points.write[0] = xform.xform(node->map_to_world(si.position + Vector2(x, 0)));
points.write[1] = xform.xform(node->map_to_world(si.position + Vector2(x, fade)));
points.write[2] = xform.xform(node->map_to_world(si.position + Vector2(x, si.size.y - fade)));
points.write[3] = xform.xform(node->map_to_world(si.position + Vector2(x, si.size.y)));
const Color color = (x + si.position.x == 0) ? axis_color : grid_color;
const float line_opacity = _lerp_fade(si.size.x, fade, x);
colors.write[0] = Color(color.r, color.g, color.b, 0.0);
colors.write[1] = Color(color.r, color.g, color.b, color.a * line_opacity);
colors.write[2] = Color(color.r, color.g, color.b, color.a * line_opacity);
colors.write[3] = Color(color.r, color.g, color.b, 0.0);
p_viewport->draw_polyline_colors(points, colors, 1);
}
} else {
const float half_offset = node->get_half_offset() == TileMap::HALF_OFFSET_X ? 0.5 : -0.5;
const int cell_count = clipped.size.y;
points.resize(cell_count * 2);
colors.resize(cell_count * 2);
for (int x = clipped.position.x; x <= clipped_end.x; x++) {
const Color color = (x + si.position.x == 0) ? axis_color : grid_color;
const float line_opacity = _lerp_fade(si.size.x, fade, x);
for (int y = clipped.position.y; y < cell_count; y++) {
Vector2 ofs;
if (ABS(si.position.y + y) & 1) {
ofs = cell_xf[0] * half_offset;
}
points.write[y * 2 + 0] = xform.xform(ofs + node->map_to_world(si.position + Vector2(x, y), true));
points.write[y * 2 + 1] = xform.xform(ofs + node->map_to_world(si.position + Vector2(x, y + 1), true));
colors.write[y * 2 + 0] = Color(color.r, color.g, color.b, color.a * line_opacity * _lerp_fade(si.size.y, fade, y));
colors.write[y * 2 + 1] = Color(color.r, color.g, color.b, color.a * line_opacity * _lerp_fade(si.size.y, fade, y + 1));
}
p_viewport->draw_multiline_colors(points, colors, 1);
}
}
// Horizontal lines.
if (node->get_half_offset() != TileMap::HALF_OFFSET_Y && node->get_half_offset() != TileMap::HALF_OFFSET_NEGATIVE_Y) {
points.resize(4);
colors.resize(4);
for (int y = clipped.position.y; y <= clipped_end.y; y++) {
points.write[0] = xform.xform(node->map_to_world(si.position + Vector2(0, y)));
points.write[1] = xform.xform(node->map_to_world(si.position + Vector2(fade, y)));
points.write[2] = xform.xform(node->map_to_world(si.position + Vector2(si.size.x - fade, y)));
points.write[3] = xform.xform(node->map_to_world(si.position + Vector2(si.size.x, y)));
const Color color = (y + si.position.y == 0) ? axis_color : grid_color;
const float line_opacity = _lerp_fade(si.size.y, fade, y);
colors.write[0] = Color(color.r, color.g, color.b, 0.0);
colors.write[1] = Color(color.r, color.g, color.b, color.a * line_opacity);
colors.write[2] = Color(color.r, color.g, color.b, color.a * line_opacity);
colors.write[3] = Color(color.r, color.g, color.b, 0.0);
p_viewport->draw_polyline_colors(points, colors, 1);
}
} else {
const float half_offset = node->get_half_offset() == TileMap::HALF_OFFSET_Y ? 0.5 : -0.5;
const int cell_count = clipped.size.x;
points.resize(cell_count * 2);
colors.resize(cell_count * 2);
for (int y = clipped.position.y; y <= clipped_end.y; y++) {
const Color color = (y + si.position.y == 0) ? axis_color : grid_color;
const float line_opacity = _lerp_fade(si.size.y, fade, y);
for (int x = clipped.position.x; x < cell_count; x++) {
Vector2 ofs;
if (ABS(si.position.x + x) & 1) {
ofs = cell_xf[1] * half_offset;
}
points.write[x * 2 + 0] = xform.xform(ofs + node->map_to_world(si.position + Vector2(x, y), true));
points.write[x * 2 + 1] = xform.xform(ofs + node->map_to_world(si.position + Vector2(x + 1, y), true));
colors.write[x * 2 + 0] = Color(color.r, color.g, color.b, color.a * line_opacity * _lerp_fade(si.size.x, fade, x));
colors.write[x * 2 + 1] = Color(color.r, color.g, color.b, color.a * line_opacity * _lerp_fade(si.size.x, fade, x + 1));
}
p_viewport->draw_multiline_colors(points, colors, 1);
}
}
}
void TileMapEditor::_draw_cell(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform) { void TileMapEditor::_draw_cell(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform) {
if (!node->get_tileset()->has_tile(p_cell)) { if (!node->get_tileset()->has_tile(p_cell)) {
return; return;
@ -1534,96 +1676,10 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
return; return;
} }
Transform2D cell_xf = node->get_cell_transform(); _draw_grid(p_overlay, node->get_used_rect());
Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * node->get_global_transform();
Transform2D xform_inv = xform.affine_inverse();
Size2 screen_size = p_overlay->get_size(); const Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * node->get_global_transform();
{ const Transform2D cell_xf = node->get_cell_transform();
Rect2 aabb;
aabb.position = node->world_to_map(xform_inv.xform(Vector2()));
aabb.expand_to(node->world_to_map(xform_inv.xform(Vector2(0, screen_size.height))));
aabb.expand_to(node->world_to_map(xform_inv.xform(Vector2(screen_size.width, 0))));
aabb.expand_to(node->world_to_map(xform_inv.xform(screen_size)));
Rect2i si = aabb.grow(1.0);
if (node->get_half_offset() != TileMap::HALF_OFFSET_X && node->get_half_offset() != TileMap::HALF_OFFSET_NEGATIVE_X) {
int max_lines = 2000; //avoid crash if size too small
for (int i = (si.position.x) - 1; i <= (si.position.x + si.size.x); i++) {
Vector2 from = xform.xform(node->map_to_world(Vector2(i, si.position.y)));
Vector2 to = xform.xform(node->map_to_world(Vector2(i, si.position.y + si.size.y + 1)));
Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
p_overlay->draw_line(from, to, col, 1);
if (max_lines-- == 0) {
break;
}
}
} else {
int max_lines = 10000; //avoid crash if size too small
for (int i = (si.position.x) - 1; i <= (si.position.x + si.size.x); i++) {
for (int j = (si.position.y) - 1; j <= (si.position.y + si.size.y); j++) {
Vector2 ofs;
if (ABS(j) & 1) {
ofs = cell_xf[0] * (node->get_half_offset() == TileMap::HALF_OFFSET_X ? 0.5 : -0.5);
}
Vector2 from = xform.xform(node->map_to_world(Vector2(i, j), true) + ofs);
Vector2 to = xform.xform(node->map_to_world(Vector2(i, j + 1), true) + ofs);
Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
p_overlay->draw_line(from, to, col, 1);
if (--max_lines == 0) {
break;
}
}
if (max_lines == 0) {
break;
}
}
}
int max_lines = 10000; //avoid crash if size too small
if (node->get_half_offset() != TileMap::HALF_OFFSET_Y && node->get_half_offset() != TileMap::HALF_OFFSET_NEGATIVE_Y) {
for (int i = (si.position.y) - 1; i <= (si.position.y + si.size.y); i++) {
Vector2 from = xform.xform(node->map_to_world(Vector2(si.position.x, i)));
Vector2 to = xform.xform(node->map_to_world(Vector2(si.position.x + si.size.x + 1, i)));
Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
p_overlay->draw_line(from, to, col, 1);
if (max_lines-- == 0) {
break;
}
}
} else {
for (int i = (si.position.y) - 1; i <= (si.position.y + si.size.y); i++) {
for (int j = (si.position.x) - 1; j <= (si.position.x + si.size.x); j++) {
Vector2 ofs;
if (ABS(j) & 1) {
ofs = cell_xf[1] * (node->get_half_offset() == TileMap::HALF_OFFSET_Y ? 0.5 : -0.5);
}
Vector2 from = xform.xform(node->map_to_world(Vector2(j, i), true) + ofs);
Vector2 to = xform.xform(node->map_to_world(Vector2(j + 1, i), true) + ofs);
Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
p_overlay->draw_line(from, to, col, 1);
if (--max_lines == 0) {
break;
}
}
if (max_lines == 0) {
break;
}
}
}
}
if (selection_active) { if (selection_active) {
Vector<Vector2> points; Vector<Vector2> points;
@ -1685,6 +1741,22 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
return; return;
} }
// Making a Rect2i that encloses all the cells in paint_undo.
// I wonder if there is a cleaner way to do it.
const Point2i first_pos = paint_undo.front()->key();
int left = first_pos.x;
int right = first_pos.x;
int top = first_pos.y;
int bottom = first_pos.y;
for (Map<Point2i, CellOp>::Element *E = paint_undo.front()->next(); E; E = E->next()) {
const Point2i &pos = E->key();
left = MIN(pos.x, left);
right = MAX(pos.x, right);
top = MIN(pos.y, top);
bottom = MAX(pos.y, bottom);
}
_draw_grid(p_overlay, Rect2i(left, top, right - left + 1, bottom - top + 1));
for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) { for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
_draw_cell(p_overlay, ids[0], E->key(), flip_h, flip_v, transpose, autotile_coord, xform); _draw_cell(p_overlay, ids[0], E->key(), flip_h, flip_v, transpose, autotile_coord, xform);
} }
@ -1696,6 +1768,8 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
return; return;
} }
_draw_grid(p_overlay, rectangle);
for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
_draw_cell(p_overlay, ids[0], Point2i(j, i), flip_h, flip_v, transpose, autotile_coord, xform); _draw_cell(p_overlay, ids[0], Point2i(j, i), flip_h, flip_v, transpose, autotile_coord, xform);
@ -1746,6 +1820,7 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
return; return;
} }
_draw_grid(p_overlay, Rect2(over_tile, Size2(1, 1)));
_draw_cell(p_overlay, st[0], over_tile, flip_h, flip_v, transpose, autotile_coord, xform); _draw_cell(p_overlay, st[0], over_tile, flip_h, flip_v, transpose, autotile_coord, xform);
} }
} }
@ -1970,7 +2045,7 @@ TileMapEditor::TileMapEditor(EditorNode *p_editor) {
size_slider->connect("value_changed", this, "_icon_size_changed"); size_slider->connect("value_changed", this, "_icon_size_changed");
add_child(size_slider); add_child(size_slider);
int mw = EDITOR_DEF("editors/tile_map/palette_min_width", 80); int mw = EDITOR_GET("editors/tile_map/palette_min_width");
VSplitContainer *palette_container = memnew(VSplitContainer); VSplitContainer *palette_container = memnew(VSplitContainer);
palette_container->set_v_size_flags(SIZE_EXPAND_FILL); palette_container->set_v_size_flags(SIZE_EXPAND_FILL);
@ -2172,12 +2247,16 @@ void TileMapEditorPlugin::make_visible(bool p_visible) {
TileMapEditorPlugin::TileMapEditorPlugin(EditorNode *p_node) { TileMapEditorPlugin::TileMapEditorPlugin(EditorNode *p_node) {
EDITOR_DEF("editors/tile_map/preview_size", 64); EDITOR_DEF("editors/tile_map/preview_size", 64);
EDITOR_DEF("editors/tile_map/palette_min_width", 80);
EDITOR_DEF("editors/tile_map/palette_item_hseparation", 8); EDITOR_DEF("editors/tile_map/palette_item_hseparation", 8);
EDITOR_DEF("editors/tile_map/show_tile_names", true); EDITOR_DEF("editors/tile_map/show_tile_names", true);
EDITOR_DEF("editors/tile_map/show_tile_ids", false); EDITOR_DEF("editors/tile_map/show_tile_ids", false);
EDITOR_DEF("editors/tile_map/sort_tiles_by_name", true); EDITOR_DEF("editors/tile_map/sort_tiles_by_name", true);
EDITOR_DEF("editors/tile_map/bucket_fill_preview", true); EDITOR_DEF("editors/tile_map/bucket_fill_preview", true);
EDITOR_DEF("editors/tile_map/editor_side", 1); EDITOR_DEF("editors/tile_map/editor_side", 1);
EDITOR_DEF("editors/tile_map/display_grid", true);
EDITOR_DEF("editors/tile_map/grid_color", Color(1, 0.3, 0.1, 0.2));
EDITOR_DEF("editors/tile_map/axis_color", Color(1, 0.8, 0.2, 0.5));
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/tile_map/editor_side", PROPERTY_HINT_ENUM, "Left,Right")); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/tile_map/editor_side", PROPERTY_HINT_ENUM, "Left,Right"));
tile_map_editor = memnew(TileMapEditor(p_node)); tile_map_editor = memnew(TileMapEditor(p_node));

View file

@ -172,6 +172,7 @@ class TileMapEditor : public VBoxContainer {
void _select(const Point2i &p_from, const Point2i &p_to); void _select(const Point2i &p_from, const Point2i &p_to);
void _erase_selection(); void _erase_selection();
void _draw_grid(Control *p_viewport, const Rect2 &p_rect) const;
void _draw_cell(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform); void _draw_cell(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform);
void _draw_fill_preview(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform); void _draw_fill_preview(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform);
void _clear_bucket_cache(); void _clear_bucket_cache();