mirror of
https://github.com/Relintai/tile_map_backport.git
synced 2024-11-09 03:12:09 +01:00
Initial commit. Already started working on the tile map's data classes.
This commit is contained in:
commit
dd6feeb933
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.import
|
||||||
|
*.d
|
||||||
|
*.o
|
||||||
|
*.meta
|
||||||
|
*.pyc
|
||||||
|
*.obj
|
||||||
|
*.bc
|
||||||
|
|
19
LICENSE
Normal file
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2019-2021 Péter Magyar
|
||||||
|
|
||||||
|
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.
|
23
README.md
Normal file
23
README.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# R Tile Map
|
||||||
|
|
||||||
|
Godot 4.0's TileMap backported to 3.x as an engine module, with (eventually) a few smaller features added.
|
||||||
|
|
||||||
|
# Building
|
||||||
|
|
||||||
|
1. Get the source code for the engine.
|
||||||
|
|
||||||
|
```git clone -b 3.x https://github.com/godotengine/godot.git godot```
|
||||||
|
|
||||||
|
2. Go into Godot's modules directory.
|
||||||
|
|
||||||
|
```
|
||||||
|
cd ./godot/modules/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Clone this repository
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/Relintai/rtile_map rtile_map
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Build Godot. [Tutorial](https://docs.godotengine.org/en/latest/development/compiling/index.html)
|
13
SCsub
Normal file
13
SCsub
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Import('env')
|
||||||
|
|
||||||
|
env.add_source_files(env.modules_sources,"register_types.cpp")
|
||||||
|
env.add_source_files(env.modules_sources,"vector3i.cpp")
|
||||||
|
env.add_source_files(env.modules_sources,"polypartition.cpp")
|
||||||
|
env.add_source_files(env.modules_sources,"rtile_set.cpp")
|
||||||
|
|
||||||
|
#if env["tools"]:
|
||||||
|
# env.add_source_files(env.modules_sources, "skeleton_editor_plugin_remover.cpp")
|
||||||
|
# env.add_source_files(env.modules_sources, "skeleton_editor_plugin.cpp")
|
||||||
|
# env.add_source_files(env.modules_sources, "spatial_editor_gizmos.cpp")
|
||||||
|
# env.add_source_files(env.modules_sources, "skeleton_editor_module_plugin.cpp")
|
||||||
|
|
13
config.py
Normal file
13
config.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
def can_build(env, platform):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def configure(env):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_doc_classes():
|
||||||
|
return [
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_doc_path():
|
||||||
|
return "doc_classes"
|
166
delaunay_2d.h
Normal file
166
delaunay_2d.h
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* delaunay_2d.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef DELAUNAY_2D_H
|
||||||
|
#define DELAUNAY_2D_H
|
||||||
|
|
||||||
|
#include "core/math/rect2.h"
|
||||||
|
|
||||||
|
class Delaunay2D {
|
||||||
|
public:
|
||||||
|
struct Triangle {
|
||||||
|
int points[3];
|
||||||
|
bool bad = false;
|
||||||
|
Triangle() {}
|
||||||
|
Triangle(int p_a, int p_b, int p_c) {
|
||||||
|
points[0] = p_a;
|
||||||
|
points[1] = p_b;
|
||||||
|
points[2] = p_c;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Edge {
|
||||||
|
int edge[2];
|
||||||
|
bool bad = false;
|
||||||
|
Edge() {}
|
||||||
|
Edge(int p_a, int p_b) {
|
||||||
|
edge[0] = p_a;
|
||||||
|
edge[1] = p_b;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool circum_circle_contains(const Vector<Vector2> &p_vertices, const Triangle &p_triangle, int p_vertex) {
|
||||||
|
Vector2 p1 = p_vertices[p_triangle.points[0]];
|
||||||
|
Vector2 p2 = p_vertices[p_triangle.points[1]];
|
||||||
|
Vector2 p3 = p_vertices[p_triangle.points[2]];
|
||||||
|
|
||||||
|
real_t ab = p1.x * p1.x + p1.y * p1.y;
|
||||||
|
real_t cd = p2.x * p2.x + p2.y * p2.y;
|
||||||
|
real_t ef = p3.x * p3.x + p3.y * p3.y;
|
||||||
|
|
||||||
|
Vector2 circum(
|
||||||
|
(ab * (p3.y - p2.y) + cd * (p1.y - p3.y) + ef * (p2.y - p1.y)) / (p1.x * (p3.y - p2.y) + p2.x * (p1.y - p3.y) + p3.x * (p2.y - p1.y)),
|
||||||
|
(ab * (p3.x - p2.x) + cd * (p1.x - p3.x) + ef * (p2.x - p1.x)) / (p1.y * (p3.x - p2.x) + p2.y * (p1.x - p3.x) + p3.y * (p2.x - p1.x)));
|
||||||
|
|
||||||
|
circum *= 0.5;
|
||||||
|
float r = p1.distance_squared_to(circum);
|
||||||
|
float d = p_vertices[p_vertex].distance_squared_to(circum);
|
||||||
|
return d <= r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool edge_compare(const Vector<Vector2> &p_vertices, const Edge &p_a, const Edge &p_b) {
|
||||||
|
if (p_vertices[p_a.edge[0]].is_equal_approx(p_vertices[p_b.edge[0]]) && p_vertices[p_a.edge[1]].is_equal_approx(p_vertices[p_b.edge[1]])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p_vertices[p_a.edge[0]].is_equal_approx(p_vertices[p_b.edge[1]]) && p_vertices[p_a.edge[1]].is_equal_approx(p_vertices[p_b.edge[0]])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector<Triangle> triangulate(const Vector<Vector2> &p_points) {
|
||||||
|
Vector<Vector2> points = p_points;
|
||||||
|
Vector<Triangle> triangles;
|
||||||
|
|
||||||
|
Rect2 rect;
|
||||||
|
for (int i = 0; i < p_points.size(); i++) {
|
||||||
|
if (i == 0) {
|
||||||
|
rect.position = p_points[i];
|
||||||
|
} else {
|
||||||
|
rect.expand_to(p_points[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float delta_max = MAX(rect.size.width, rect.size.height);
|
||||||
|
Vector2 center = rect.get_center();
|
||||||
|
|
||||||
|
points.push_back(Vector2(center.x - 20 * delta_max, center.y - delta_max));
|
||||||
|
points.push_back(Vector2(center.x, center.y + 20 * delta_max));
|
||||||
|
points.push_back(Vector2(center.x + 20 * delta_max, center.y - delta_max));
|
||||||
|
|
||||||
|
triangles.push_back(Triangle(p_points.size() + 0, p_points.size() + 1, p_points.size() + 2));
|
||||||
|
|
||||||
|
for (int i = 0; i < p_points.size(); i++) {
|
||||||
|
Vector<Edge> polygon;
|
||||||
|
|
||||||
|
for (int j = 0; j < triangles.size(); j++) {
|
||||||
|
if (circum_circle_contains(points, triangles[j], i)) {
|
||||||
|
triangles.write[j].bad = true;
|
||||||
|
polygon.push_back(Edge(triangles[j].points[0], triangles[j].points[1]));
|
||||||
|
polygon.push_back(Edge(triangles[j].points[1], triangles[j].points[2]));
|
||||||
|
polygon.push_back(Edge(triangles[j].points[2], triangles[j].points[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < triangles.size(); j++) {
|
||||||
|
if (triangles[j].bad) {
|
||||||
|
triangles.remove_at(j);
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < polygon.size(); j++) {
|
||||||
|
for (int k = j + 1; k < polygon.size(); k++) {
|
||||||
|
if (edge_compare(points, polygon[j], polygon[k])) {
|
||||||
|
polygon.write[j].bad = true;
|
||||||
|
polygon.write[k].bad = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < polygon.size(); j++) {
|
||||||
|
if (polygon[j].bad) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
triangles.push_back(Triangle(polygon[j].edge[0], polygon[j].edge[1], i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < triangles.size(); i++) {
|
||||||
|
bool invalid = false;
|
||||||
|
for (int j = 0; j < 3; j++) {
|
||||||
|
if (triangles[i].points[j] >= p_points.size()) {
|
||||||
|
invalid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (invalid) {
|
||||||
|
triangles.remove_at(i);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return triangles;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DELAUNAY_2D_H
|
388
geometry_2d.cpp
Normal file
388
geometry_2d.cpp
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* geometry_2d.cpp */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#include "geometry_2d.h"
|
||||||
|
|
||||||
|
#include "thirdparty/misc/clipper.hpp"
|
||||||
|
#include "thirdparty/misc/polypartition.h"
|
||||||
|
#define STB_RECT_PACK_IMPLEMENTATION
|
||||||
|
#include "stb_rect_pack.h"
|
||||||
|
|
||||||
|
#define SCALE_FACTOR 100000.0 // Based on CMP_EPSILON.
|
||||||
|
|
||||||
|
Vector<Vector<Vector2>> Geometry2D::decompose_polygon_in_convex(Vector<Point2> polygon) {
|
||||||
|
Vector<Vector<Vector2>> decomp;
|
||||||
|
List<TPPLPoly> in_poly, out_poly;
|
||||||
|
|
||||||
|
TPPLPoly inp;
|
||||||
|
inp.Init(polygon.size());
|
||||||
|
for (int i = 0; i < polygon.size(); i++) {
|
||||||
|
inp.GetPoint(i) = polygon[i];
|
||||||
|
}
|
||||||
|
inp.SetOrientation(TPPL_ORIENTATION_CCW);
|
||||||
|
in_poly.push_back(inp);
|
||||||
|
TPPLPartition tpart;
|
||||||
|
if (tpart.ConvexPartition_HM(&in_poly, &out_poly) == 0) { // Failed.
|
||||||
|
ERR_PRINT("Convex decomposing failed!");
|
||||||
|
return decomp;
|
||||||
|
}
|
||||||
|
|
||||||
|
decomp.resize(out_poly.size());
|
||||||
|
int idx = 0;
|
||||||
|
for (List<TPPLPoly>::Element *I = out_poly.front(); I; I = I->next()) {
|
||||||
|
TPPLPoly &tp = I->get();
|
||||||
|
|
||||||
|
decomp.write[idx].resize(tp.GetNumPoints());
|
||||||
|
|
||||||
|
for (int64_t i = 0; i < tp.GetNumPoints(); i++) {
|
||||||
|
decomp.write[idx].write[i] = tp.GetPoint(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decomp;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct _AtlasWorkRect {
|
||||||
|
Size2i s;
|
||||||
|
Point2i p;
|
||||||
|
int idx;
|
||||||
|
_FORCE_INLINE_ bool operator<(const _AtlasWorkRect &p_r) const { return s.width > p_r.s.width; };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _AtlasWorkRectResult {
|
||||||
|
Vector<_AtlasWorkRect> result;
|
||||||
|
int max_w;
|
||||||
|
int max_h;
|
||||||
|
};
|
||||||
|
|
||||||
|
void Geometry2D::make_atlas(const Vector<Size2i> &p_rects, Vector<Point2i> &r_result, Size2i &r_size) {
|
||||||
|
// Super simple, almost brute force scanline stacking fitter.
|
||||||
|
// It's pretty basic for now, but it tries to make sure that the aspect ratio of the
|
||||||
|
// resulting atlas is somehow square. This is necessary because video cards have limits
|
||||||
|
// on texture size (usually 2048 or 4096), so the squarer a texture, the more the chances
|
||||||
|
// that it will work in every hardware.
|
||||||
|
// For example, it will prioritize a 1024x1024 atlas (works everywhere) instead of a
|
||||||
|
// 256x8192 atlas (won't work anywhere).
|
||||||
|
|
||||||
|
ERR_FAIL_COND(p_rects.size() == 0);
|
||||||
|
for (int i = 0; i < p_rects.size(); i++) {
|
||||||
|
ERR_FAIL_COND(p_rects[i].width <= 0);
|
||||||
|
ERR_FAIL_COND(p_rects[i].height <= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<_AtlasWorkRect> wrects;
|
||||||
|
wrects.resize(p_rects.size());
|
||||||
|
for (int i = 0; i < p_rects.size(); i++) {
|
||||||
|
wrects.write[i].s = p_rects[i];
|
||||||
|
wrects.write[i].idx = i;
|
||||||
|
}
|
||||||
|
wrects.sort();
|
||||||
|
int widest = wrects[0].s.width;
|
||||||
|
|
||||||
|
Vector<_AtlasWorkRectResult> results;
|
||||||
|
|
||||||
|
for (int i = 0; i <= 12; i++) {
|
||||||
|
int w = 1 << i;
|
||||||
|
int max_h = 0;
|
||||||
|
int max_w = 0;
|
||||||
|
if (w < widest) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<int> hmax;
|
||||||
|
hmax.resize(w);
|
||||||
|
for (int j = 0; j < w; j++) {
|
||||||
|
hmax.write[j] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place them.
|
||||||
|
int ofs = 0;
|
||||||
|
int limit_h = 0;
|
||||||
|
for (int j = 0; j < wrects.size(); j++) {
|
||||||
|
if (ofs + wrects[j].s.width > w) {
|
||||||
|
ofs = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int from_y = 0;
|
||||||
|
for (int k = 0; k < wrects[j].s.width; k++) {
|
||||||
|
if (hmax[ofs + k] > from_y) {
|
||||||
|
from_y = hmax[ofs + k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wrects.write[j].p.x = ofs;
|
||||||
|
wrects.write[j].p.y = from_y;
|
||||||
|
int end_h = from_y + wrects[j].s.height;
|
||||||
|
int end_w = ofs + wrects[j].s.width;
|
||||||
|
if (ofs == 0) {
|
||||||
|
limit_h = end_h;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int k = 0; k < wrects[j].s.width; k++) {
|
||||||
|
hmax.write[ofs + k] = end_h;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end_h > max_h) {
|
||||||
|
max_h = end_h;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end_w > max_w) {
|
||||||
|
max_w = end_w;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ofs == 0 || end_h > limit_h) { // While h limit not reached, keep stacking.
|
||||||
|
ofs += wrects[j].s.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_AtlasWorkRectResult result;
|
||||||
|
result.result = wrects;
|
||||||
|
result.max_h = max_h;
|
||||||
|
result.max_w = max_w;
|
||||||
|
results.push_back(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the result with the best aspect ratio.
|
||||||
|
|
||||||
|
int best = -1;
|
||||||
|
real_t best_aspect = 1e20;
|
||||||
|
|
||||||
|
for (int i = 0; i < results.size(); i++) {
|
||||||
|
real_t h = next_power_of_2(results[i].max_h);
|
||||||
|
real_t w = next_power_of_2(results[i].max_w);
|
||||||
|
real_t aspect = h > w ? h / w : w / h;
|
||||||
|
if (aspect < best_aspect) {
|
||||||
|
best = i;
|
||||||
|
best_aspect = aspect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r_result.resize(p_rects.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < p_rects.size(); i++) {
|
||||||
|
r_result.write[results[best].result[i].idx] = results[best].result[i].p;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_size = Size2(results[best].max_w, results[best].max_h);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<Vector<Point2>> Geometry2D::_polypaths_do_operation(PolyBooleanOperation p_op, const Vector<Point2> &p_polypath_a, const Vector<Point2> &p_polypath_b, bool is_a_open) {
|
||||||
|
using namespace ClipperLib;
|
||||||
|
|
||||||
|
ClipType op = ctUnion;
|
||||||
|
|
||||||
|
switch (p_op) {
|
||||||
|
case OPERATION_UNION:
|
||||||
|
op = ctUnion;
|
||||||
|
break;
|
||||||
|
case OPERATION_DIFFERENCE:
|
||||||
|
op = ctDifference;
|
||||||
|
break;
|
||||||
|
case OPERATION_INTERSECTION:
|
||||||
|
op = ctIntersection;
|
||||||
|
break;
|
||||||
|
case OPERATION_XOR:
|
||||||
|
op = ctXor;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Path path_a, path_b;
|
||||||
|
|
||||||
|
// Need to scale points (Clipper's requirement for robust computation).
|
||||||
|
for (int i = 0; i != p_polypath_a.size(); ++i) {
|
||||||
|
path_a << IntPoint(p_polypath_a[i].x * SCALE_FACTOR, p_polypath_a[i].y * SCALE_FACTOR);
|
||||||
|
}
|
||||||
|
for (int i = 0; i != p_polypath_b.size(); ++i) {
|
||||||
|
path_b << IntPoint(p_polypath_b[i].x * SCALE_FACTOR, p_polypath_b[i].y * SCALE_FACTOR);
|
||||||
|
}
|
||||||
|
Clipper clp;
|
||||||
|
clp.AddPath(path_a, ptSubject, !is_a_open); // Forward compatible with Clipper 10.0.0.
|
||||||
|
clp.AddPath(path_b, ptClip, true); // Polylines cannot be set as clip.
|
||||||
|
|
||||||
|
Paths paths;
|
||||||
|
|
||||||
|
if (is_a_open) {
|
||||||
|
PolyTree tree; // Needed to populate polylines.
|
||||||
|
clp.Execute(op, tree);
|
||||||
|
OpenPathsFromPolyTree(tree, paths);
|
||||||
|
} else {
|
||||||
|
clp.Execute(op, paths); // Works on closed polygons only.
|
||||||
|
}
|
||||||
|
// Have to scale points down now.
|
||||||
|
Vector<Vector<Point2>> polypaths;
|
||||||
|
|
||||||
|
for (Paths::size_type i = 0; i < paths.size(); ++i) {
|
||||||
|
Vector<Vector2> polypath;
|
||||||
|
|
||||||
|
const Path &scaled_path = paths[i];
|
||||||
|
|
||||||
|
for (Paths::size_type j = 0; j < scaled_path.size(); ++j) {
|
||||||
|
polypath.push_back(Point2(
|
||||||
|
static_cast<real_t>(scaled_path[j].X) / SCALE_FACTOR,
|
||||||
|
static_cast<real_t>(scaled_path[j].Y) / SCALE_FACTOR));
|
||||||
|
}
|
||||||
|
polypaths.push_back(polypath);
|
||||||
|
}
|
||||||
|
return polypaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<Vector<Point2>> Geometry2D::_polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) {
|
||||||
|
using namespace ClipperLib;
|
||||||
|
|
||||||
|
JoinType jt = jtSquare;
|
||||||
|
|
||||||
|
switch (p_join_type) {
|
||||||
|
case JOIN_SQUARE:
|
||||||
|
jt = jtSquare;
|
||||||
|
break;
|
||||||
|
case JOIN_ROUND:
|
||||||
|
jt = jtRound;
|
||||||
|
break;
|
||||||
|
case JOIN_MITER:
|
||||||
|
jt = jtMiter;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
EndType et = etClosedPolygon;
|
||||||
|
|
||||||
|
switch (p_end_type) {
|
||||||
|
case END_POLYGON:
|
||||||
|
et = etClosedPolygon;
|
||||||
|
break;
|
||||||
|
case END_JOINED:
|
||||||
|
et = etClosedLine;
|
||||||
|
break;
|
||||||
|
case END_BUTT:
|
||||||
|
et = etOpenButt;
|
||||||
|
break;
|
||||||
|
case END_SQUARE:
|
||||||
|
et = etOpenSquare;
|
||||||
|
break;
|
||||||
|
case END_ROUND:
|
||||||
|
et = etOpenRound;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ClipperOffset co(2.0, 0.25 * SCALE_FACTOR); // Defaults from ClipperOffset.
|
||||||
|
Path path;
|
||||||
|
|
||||||
|
// Need to scale points (Clipper's requirement for robust computation).
|
||||||
|
for (int i = 0; i != p_polypath.size(); ++i) {
|
||||||
|
path << IntPoint(p_polypath[i].x * SCALE_FACTOR, p_polypath[i].y * SCALE_FACTOR);
|
||||||
|
}
|
||||||
|
co.AddPath(path, jt, et);
|
||||||
|
|
||||||
|
Paths paths;
|
||||||
|
co.Execute(paths, p_delta * SCALE_FACTOR); // Inflate/deflate.
|
||||||
|
|
||||||
|
// Have to scale points down now.
|
||||||
|
Vector<Vector<Point2>> polypaths;
|
||||||
|
|
||||||
|
for (Paths::size_type i = 0; i < paths.size(); ++i) {
|
||||||
|
Vector<Vector2> polypath;
|
||||||
|
|
||||||
|
const Path &scaled_path = paths[i];
|
||||||
|
|
||||||
|
for (Paths::size_type j = 0; j < scaled_path.size(); ++j) {
|
||||||
|
polypath.push_back(Point2(
|
||||||
|
static_cast<real_t>(scaled_path[j].X) / SCALE_FACTOR,
|
||||||
|
static_cast<real_t>(scaled_path[j].Y) / SCALE_FACTOR));
|
||||||
|
}
|
||||||
|
polypaths.push_back(polypath);
|
||||||
|
}
|
||||||
|
return polypaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<Point2i> Geometry2D::pack_rects(const Vector<Size2i> &p_sizes, const Size2i &p_atlas_size) {
|
||||||
|
Vector<stbrp_node> nodes;
|
||||||
|
nodes.resize(p_atlas_size.width);
|
||||||
|
|
||||||
|
stbrp_context context;
|
||||||
|
stbrp_init_target(&context, p_atlas_size.width, p_atlas_size.height, nodes.ptrw(), p_atlas_size.width);
|
||||||
|
|
||||||
|
Vector<stbrp_rect> rects;
|
||||||
|
rects.resize(p_sizes.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < p_sizes.size(); i++) {
|
||||||
|
rects.write[i].id = 0;
|
||||||
|
rects.write[i].w = p_sizes[i].width;
|
||||||
|
rects.write[i].h = p_sizes[i].height;
|
||||||
|
rects.write[i].x = 0;
|
||||||
|
rects.write[i].y = 0;
|
||||||
|
rects.write[i].was_packed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int res = stbrp_pack_rects(&context, rects.ptrw(), rects.size());
|
||||||
|
if (res == 0) { //pack failed
|
||||||
|
return Vector<Point2i>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<Point2i> ret;
|
||||||
|
ret.resize(p_sizes.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < p_sizes.size(); i++) {
|
||||||
|
Point2i r(rects[i].x, rects[i].y);
|
||||||
|
ret.write[i] = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<Vector3i> Geometry2D::partial_pack_rects(const Vector<Vector2i> &p_sizes, const Size2i &p_atlas_size) {
|
||||||
|
Vector<stbrp_node> nodes;
|
||||||
|
nodes.resize(p_atlas_size.width);
|
||||||
|
memset(nodes.ptrw(), 0, sizeof(stbrp_node) * nodes.size());
|
||||||
|
|
||||||
|
stbrp_context context;
|
||||||
|
stbrp_init_target(&context, p_atlas_size.width, p_atlas_size.height, nodes.ptrw(), p_atlas_size.width);
|
||||||
|
|
||||||
|
Vector<stbrp_rect> rects;
|
||||||
|
rects.resize(p_sizes.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < p_sizes.size(); i++) {
|
||||||
|
rects.write[i].id = i;
|
||||||
|
rects.write[i].w = p_sizes[i].width;
|
||||||
|
rects.write[i].h = p_sizes[i].height;
|
||||||
|
rects.write[i].x = 0;
|
||||||
|
rects.write[i].y = 0;
|
||||||
|
rects.write[i].was_packed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
stbrp_pack_rects(&context, rects.ptrw(), rects.size());
|
||||||
|
|
||||||
|
Vector<Vector3i> ret;
|
||||||
|
ret.resize(p_sizes.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < p_sizes.size(); i++) {
|
||||||
|
ret.write[rects[i].id] = Vector3i(rects[i].x, rects[i].y, rects[i].was_packed != 0 ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
471
geometry_2d.h
Normal file
471
geometry_2d.h
Normal file
@ -0,0 +1,471 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* geometry_2d.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef GEOMETRY_2D_H
|
||||||
|
#define GEOMETRY_2D_H
|
||||||
|
|
||||||
|
#include "delaunay_2d.h"
|
||||||
|
#include "core/math/triangulate.h"
|
||||||
|
#include "vector3i.h"
|
||||||
|
#include "core/vector.h"
|
||||||
|
|
||||||
|
class Geometry2D {
|
||||||
|
public:
|
||||||
|
static real_t get_closest_points_between_segments(const Vector2 &p1, const Vector2 &q1, const Vector2 &p2, const Vector2 &q2, Vector2 &c1, Vector2 &c2) {
|
||||||
|
Vector2 d1 = q1 - p1; // Direction vector of segment S1.
|
||||||
|
Vector2 d2 = q2 - p2; // Direction vector of segment S2.
|
||||||
|
Vector2 r = p1 - p2;
|
||||||
|
real_t a = d1.dot(d1); // Squared length of segment S1, always nonnegative.
|
||||||
|
real_t e = d2.dot(d2); // Squared length of segment S2, always nonnegative.
|
||||||
|
real_t f = d2.dot(r);
|
||||||
|
real_t s, t;
|
||||||
|
// Check if either or both segments degenerate into points.
|
||||||
|
if (a <= CMP_EPSILON && e <= CMP_EPSILON) {
|
||||||
|
// Both segments degenerate into points.
|
||||||
|
c1 = p1;
|
||||||
|
c2 = p2;
|
||||||
|
return Math::sqrt((c1 - c2).dot(c1 - c2));
|
||||||
|
}
|
||||||
|
if (a <= CMP_EPSILON) {
|
||||||
|
// First segment degenerates into a point.
|
||||||
|
s = 0.0;
|
||||||
|
t = f / e; // s = 0 => t = (b*s + f) / e = f / e
|
||||||
|
t = CLAMP(t, 0.0, 1.0);
|
||||||
|
} else {
|
||||||
|
real_t c = d1.dot(r);
|
||||||
|
if (e <= CMP_EPSILON) {
|
||||||
|
// Second segment degenerates into a point.
|
||||||
|
t = 0.0;
|
||||||
|
s = CLAMP(-c / a, 0.0, 1.0); // t = 0 => s = (b*t - c) / a = -c / a
|
||||||
|
} else {
|
||||||
|
// The general nondegenerate case starts here.
|
||||||
|
real_t b = d1.dot(d2);
|
||||||
|
real_t denom = a * e - b * b; // Always nonnegative.
|
||||||
|
// If segments not parallel, compute closest point on L1 to L2 and
|
||||||
|
// clamp to segment S1. Else pick arbitrary s (here 0).
|
||||||
|
if (denom != 0.0) {
|
||||||
|
s = CLAMP((b * f - c * e) / denom, 0.0, 1.0);
|
||||||
|
} else {
|
||||||
|
s = 0.0;
|
||||||
|
}
|
||||||
|
// Compute point on L2 closest to S1(s) using
|
||||||
|
// t = Dot((P1 + D1*s) - P2,D2) / Dot(D2,D2) = (b*s + f) / e
|
||||||
|
t = (b * s + f) / e;
|
||||||
|
|
||||||
|
//If t in [0,1] done. Else clamp t, recompute s for the new value
|
||||||
|
// of t using s = Dot((P2 + D2*t) - P1,D1) / Dot(D1,D1)= (t*b - c) / a
|
||||||
|
// and clamp s to [0, 1].
|
||||||
|
if (t < 0.0) {
|
||||||
|
t = 0.0;
|
||||||
|
s = CLAMP(-c / a, 0.0, 1.0);
|
||||||
|
} else if (t > 1.0) {
|
||||||
|
t = 1.0;
|
||||||
|
s = CLAMP((b - c) / a, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c1 = p1 + d1 * s;
|
||||||
|
c2 = p2 + d2 * t;
|
||||||
|
return Math::sqrt((c1 - c2).dot(c1 - c2));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector2 get_closest_point_to_segment(const Vector2 &p_point, const Vector2 *p_segment) {
|
||||||
|
Vector2 p = p_point - p_segment[0];
|
||||||
|
Vector2 n = p_segment[1] - p_segment[0];
|
||||||
|
real_t l2 = n.length_squared();
|
||||||
|
if (l2 < 1e-20) {
|
||||||
|
return p_segment[0]; // Both points are the same, just give any.
|
||||||
|
}
|
||||||
|
|
||||||
|
real_t d = n.dot(p) / l2;
|
||||||
|
|
||||||
|
if (d <= 0.0) {
|
||||||
|
return p_segment[0]; // Before first point.
|
||||||
|
} else if (d >= 1.0) {
|
||||||
|
return p_segment[1]; // After first point.
|
||||||
|
} else {
|
||||||
|
return p_segment[0] + n * d; // Inside.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_point_in_triangle(const Vector2 &s, const Vector2 &a, const Vector2 &b, const Vector2 &c) {
|
||||||
|
Vector2 an = a - s;
|
||||||
|
Vector2 bn = b - s;
|
||||||
|
Vector2 cn = c - s;
|
||||||
|
|
||||||
|
bool orientation = an.cross(bn) > 0;
|
||||||
|
|
||||||
|
if ((bn.cross(cn) > 0) != orientation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (cn.cross(an) > 0) == orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector2 get_closest_point_to_segment_uncapped(const Vector2 &p_point, const Vector2 *p_segment) {
|
||||||
|
Vector2 p = p_point - p_segment[0];
|
||||||
|
Vector2 n = p_segment[1] - p_segment[0];
|
||||||
|
real_t l2 = n.length_squared();
|
||||||
|
if (l2 < 1e-20) {
|
||||||
|
return p_segment[0]; // Both points are the same, just give any.
|
||||||
|
}
|
||||||
|
|
||||||
|
real_t d = n.dot(p) / l2;
|
||||||
|
|
||||||
|
return p_segment[0] + n * d; // Inside.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable False Positives in MSVC compiler; we correctly check for 0 here to prevent a division by 0.
|
||||||
|
// See: https://github.com/godotengine/godot/pull/44274
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(disable : 4723)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool line_intersects_line(const Vector2 &p_from_a, const Vector2 &p_dir_a, const Vector2 &p_from_b, const Vector2 &p_dir_b, Vector2 &r_result) {
|
||||||
|
// See http://paulbourke.net/geometry/pointlineplane/
|
||||||
|
|
||||||
|
const real_t denom = p_dir_b.y * p_dir_a.x - p_dir_b.x * p_dir_a.y;
|
||||||
|
if (Math::is_zero_approx(denom)) { // Parallel?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Vector2 v = p_from_a - p_from_b;
|
||||||
|
const real_t t = (p_dir_b.x * v.y - p_dir_b.y * v.x) / denom;
|
||||||
|
r_result = p_from_a + t * p_dir_a;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-enable division by 0 warning
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(default : 4723)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool segment_intersects_segment(const Vector2 &p_from_a, const Vector2 &p_to_a, const Vector2 &p_from_b, const Vector2 &p_to_b, Vector2 *r_result) {
|
||||||
|
Vector2 B = p_to_a - p_from_a;
|
||||||
|
Vector2 C = p_from_b - p_from_a;
|
||||||
|
Vector2 D = p_to_b - p_from_a;
|
||||||
|
|
||||||
|
real_t ABlen = B.dot(B);
|
||||||
|
if (ABlen <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Vector2 Bn = B / ABlen;
|
||||||
|
C = Vector2(C.x * Bn.x + C.y * Bn.y, C.y * Bn.x - C.x * Bn.y);
|
||||||
|
D = Vector2(D.x * Bn.x + D.y * Bn.y, D.y * Bn.x - D.x * Bn.y);
|
||||||
|
|
||||||
|
// Fail if C x B and D x B have the same sign (segments don't intersect).
|
||||||
|
if ((C.y < -CMP_EPSILON && D.y < -CMP_EPSILON) || (C.y > CMP_EPSILON && D.y > CMP_EPSILON)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail if segments are parallel or colinear.
|
||||||
|
// (when A x B == zero, i.e (C - D) x B == zero, i.e C x B == D x B)
|
||||||
|
if (Math::is_equal_approx(C.y, D.y)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
real_t ABpos = D.x + (C.x - D.x) * D.y / (D.y - C.y);
|
||||||
|
|
||||||
|
// Fail if segment C-D crosses line A-B outside of segment A-B.
|
||||||
|
if (ABpos < 0 || ABpos > 1.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the discovered position to line A-B in the original coordinate system.
|
||||||
|
if (r_result) {
|
||||||
|
*r_result = p_from_a + B * ABpos;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool is_point_in_circle(const Vector2 &p_point, const Vector2 &p_circle_pos, real_t p_circle_radius) {
|
||||||
|
return p_point.distance_squared_to(p_circle_pos) <= p_circle_radius * p_circle_radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
static real_t segment_intersects_circle(const Vector2 &p_from, const Vector2 &p_to, const Vector2 &p_circle_pos, real_t p_circle_radius) {
|
||||||
|
Vector2 line_vec = p_to - p_from;
|
||||||
|
Vector2 vec_to_line = p_from - p_circle_pos;
|
||||||
|
|
||||||
|
// Create a quadratic formula of the form ax^2 + bx + c = 0
|
||||||
|
real_t a, b, c;
|
||||||
|
|
||||||
|
a = line_vec.dot(line_vec);
|
||||||
|
b = 2 * vec_to_line.dot(line_vec);
|
||||||
|
c = vec_to_line.dot(vec_to_line) - p_circle_radius * p_circle_radius;
|
||||||
|
|
||||||
|
// Solve for t.
|
||||||
|
real_t sqrtterm = b * b - 4 * a * c;
|
||||||
|
|
||||||
|
// If the term we intend to square root is less than 0 then the answer won't be real,
|
||||||
|
// so it definitely won't be t in the range 0 to 1.
|
||||||
|
if (sqrtterm < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we can assume that the line segment starts outside the circle (e.g. for continuous time collision detection)
|
||||||
|
// then the following can be skipped and we can just return the equivalent of res1.
|
||||||
|
sqrtterm = Math::sqrt(sqrtterm);
|
||||||
|
real_t res1 = (-b - sqrtterm) / (2 * a);
|
||||||
|
real_t res2 = (-b + sqrtterm) / (2 * a);
|
||||||
|
|
||||||
|
if (res1 >= 0 && res1 <= 1) {
|
||||||
|
return res1;
|
||||||
|
}
|
||||||
|
if (res2 >= 0 && res2 <= 1) {
|
||||||
|
return res2;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PolyBooleanOperation {
|
||||||
|
OPERATION_UNION,
|
||||||
|
OPERATION_DIFFERENCE,
|
||||||
|
OPERATION_INTERSECTION,
|
||||||
|
OPERATION_XOR
|
||||||
|
};
|
||||||
|
enum PolyJoinType {
|
||||||
|
JOIN_SQUARE,
|
||||||
|
JOIN_ROUND,
|
||||||
|
JOIN_MITER
|
||||||
|
};
|
||||||
|
enum PolyEndType {
|
||||||
|
END_POLYGON,
|
||||||
|
END_JOINED,
|
||||||
|
END_BUTT,
|
||||||
|
END_SQUARE,
|
||||||
|
END_ROUND
|
||||||
|
};
|
||||||
|
|
||||||
|
static Vector<Vector<Point2>> merge_polygons(const Vector<Point2> &p_polygon_a, const Vector<Point2> &p_polygon_b) {
|
||||||
|
return _polypaths_do_operation(OPERATION_UNION, p_polygon_a, p_polygon_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector<Vector<Point2>> clip_polygons(const Vector<Point2> &p_polygon_a, const Vector<Point2> &p_polygon_b) {
|
||||||
|
return _polypaths_do_operation(OPERATION_DIFFERENCE, p_polygon_a, p_polygon_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector<Vector<Point2>> intersect_polygons(const Vector<Point2> &p_polygon_a, const Vector<Point2> &p_polygon_b) {
|
||||||
|
return _polypaths_do_operation(OPERATION_INTERSECTION, p_polygon_a, p_polygon_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector<Vector<Point2>> exclude_polygons(const Vector<Point2> &p_polygon_a, const Vector<Point2> &p_polygon_b) {
|
||||||
|
return _polypaths_do_operation(OPERATION_XOR, p_polygon_a, p_polygon_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector<Vector<Point2>> clip_polyline_with_polygon(const Vector<Vector2> &p_polyline, const Vector<Vector2> &p_polygon) {
|
||||||
|
return _polypaths_do_operation(OPERATION_DIFFERENCE, p_polyline, p_polygon, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector<Vector<Point2>> intersect_polyline_with_polygon(const Vector<Vector2> &p_polyline, const Vector<Vector2> &p_polygon) {
|
||||||
|
return _polypaths_do_operation(OPERATION_INTERSECTION, p_polyline, p_polygon, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector<Vector<Point2>> offset_polygon(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type) {
|
||||||
|
return _polypath_offset(p_polygon, p_delta, p_join_type, END_POLYGON);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector<Vector<Point2>> offset_polyline(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) {
|
||||||
|
ERR_FAIL_COND_V_MSG(p_end_type == END_POLYGON, Vector<Vector<Point2>>(), "Attempt to offset a polyline like a polygon (use offset_polygon instead).");
|
||||||
|
|
||||||
|
return _polypath_offset(p_polygon, p_delta, p_join_type, p_end_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector<int> triangulate_delaunay(const Vector<Vector2> &p_points) {
|
||||||
|
Vector<Delaunay2D::Triangle> tr = Delaunay2D::triangulate(p_points);
|
||||||
|
Vector<int> triangles;
|
||||||
|
|
||||||
|
for (int i = 0; i < tr.size(); i++) {
|
||||||
|
triangles.push_back(tr[i].points[0]);
|
||||||
|
triangles.push_back(tr[i].points[1]);
|
||||||
|
triangles.push_back(tr[i].points[2]);
|
||||||
|
}
|
||||||
|
return triangles;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector<int> triangulate_polygon(const Vector<Vector2> &p_polygon) {
|
||||||
|
Vector<int> triangles;
|
||||||
|
if (!Triangulate::triangulate(p_polygon, triangles)) {
|
||||||
|
return Vector<int>(); //fail
|
||||||
|
}
|
||||||
|
return triangles;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_polygon_clockwise(const Vector<Vector2> &p_polygon) {
|
||||||
|
int c = p_polygon.size();
|
||||||
|
if (c < 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const Vector2 *p = p_polygon.ptr();
|
||||||
|
real_t sum = 0;
|
||||||
|
for (int i = 0; i < c; i++) {
|
||||||
|
const Vector2 &v1 = p[i];
|
||||||
|
const Vector2 &v2 = p[(i + 1) % c];
|
||||||
|
sum += (v2.x - v1.x) * (v2.y + v1.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum > 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternate implementation that should be faster.
|
||||||
|
static bool is_point_in_polygon(const Vector2 &p_point, const Vector<Vector2> &p_polygon) {
|
||||||
|
int c = p_polygon.size();
|
||||||
|
if (c < 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const Vector2 *p = p_polygon.ptr();
|
||||||
|
Vector2 further_away(-1e20, -1e20);
|
||||||
|
Vector2 further_away_opposite(1e20, 1e20);
|
||||||
|
|
||||||
|
for (int i = 0; i < c; i++) {
|
||||||
|
further_away.x = MAX(p[i].x, further_away.x);
|
||||||
|
further_away.y = MAX(p[i].y, further_away.y);
|
||||||
|
further_away_opposite.x = MIN(p[i].x, further_away_opposite.x);
|
||||||
|
further_away_opposite.y = MIN(p[i].y, further_away_opposite.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make point outside that won't intersect with points in segment from p_point.
|
||||||
|
further_away += (further_away - further_away_opposite) * Vector2(1.221313, 1.512312);
|
||||||
|
|
||||||
|
int intersections = 0;
|
||||||
|
for (int i = 0; i < c; i++) {
|
||||||
|
const Vector2 &v1 = p[i];
|
||||||
|
const Vector2 &v2 = p[(i + 1) % c];
|
||||||
|
|
||||||
|
Vector2 res;
|
||||||
|
if (segment_intersects_segment(v1, v2, p_point, further_away, &res)) {
|
||||||
|
intersections++;
|
||||||
|
if (res.is_equal_approx(p_point)) {
|
||||||
|
// Point is in one of the polygon edges.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (intersections & 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_segment_intersecting_polygon(const Vector2 &p_from, const Vector2 &p_to, const Vector<Vector2> &p_polygon) {
|
||||||
|
int c = p_polygon.size();
|
||||||
|
const Vector2 *p = p_polygon.ptr();
|
||||||
|
for (int i = 0; i < c; i++) {
|
||||||
|
const Vector2 &v1 = p[i];
|
||||||
|
const Vector2 &v2 = p[(i + 1) % c];
|
||||||
|
if (segment_intersects_segment(p_from, p_to, v1, v2, nullptr)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static real_t vec2_cross(const Point2 &O, const Point2 &A, const Point2 &B) {
|
||||||
|
return (real_t)(A.x - O.x) * (B.y - O.y) - (real_t)(A.y - O.y) * (B.x - O.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a list of points on the convex hull in counter-clockwise order.
|
||||||
|
// Note: the last point in the returned list is the same as the first one.
|
||||||
|
static Vector<Point2> convex_hull(Vector<Point2> P) {
|
||||||
|
int n = P.size(), k = 0;
|
||||||
|
Vector<Point2> H;
|
||||||
|
H.resize(2 * n);
|
||||||
|
|
||||||
|
// Sort points lexicographically.
|
||||||
|
P.sort();
|
||||||
|
|
||||||
|
// Build lower hull.
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
while (k >= 2 && vec2_cross(H[k - 2], H[k - 1], P[i]) <= 0) {
|
||||||
|
k--;
|
||||||
|
}
|
||||||
|
H.write[k++] = P[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build upper hull.
|
||||||
|
for (int i = n - 2, t = k + 1; i >= 0; i--) {
|
||||||
|
while (k >= t && vec2_cross(H[k - 2], H[k - 1], P[i]) <= 0) {
|
||||||
|
k--;
|
||||||
|
}
|
||||||
|
H.write[k++] = P[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
H.resize(k);
|
||||||
|
return H;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector<Point2i> bresenham_line(const Point2i &p_start, const Point2i &p_end) {
|
||||||
|
Vector<Point2i> points;
|
||||||
|
|
||||||
|
Vector2i delta = (p_end - p_start).abs() * 2;
|
||||||
|
Vector2i step = (p_end - p_start).sign();
|
||||||
|
Vector2i current = p_start;
|
||||||
|
|
||||||
|
if (delta.x > delta.y) {
|
||||||
|
int err = delta.x / 2;
|
||||||
|
|
||||||
|
for (; current.x != p_end.x; current.x += step.x) {
|
||||||
|
points.push_back(current);
|
||||||
|
|
||||||
|
err -= delta.y;
|
||||||
|
if (err < 0) {
|
||||||
|
current.y += step.y;
|
||||||
|
err += delta.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int err = delta.y / 2;
|
||||||
|
|
||||||
|
for (; current.y != p_end.y; current.y += step.y) {
|
||||||
|
points.push_back(current);
|
||||||
|
|
||||||
|
err -= delta.x;
|
||||||
|
if (err < 0) {
|
||||||
|
current.x += step.x;
|
||||||
|
err += delta.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
points.push_back(current);
|
||||||
|
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector<Vector<Vector2>> decompose_polygon_in_convex(Vector<Point2> polygon);
|
||||||
|
|
||||||
|
static void make_atlas(const Vector<Size2i> &p_rects, Vector<Point2i> &r_result, Size2i &r_size);
|
||||||
|
static Vector<Point2i> pack_rects(const Vector<Size2i> &p_sizes, const Size2i &p_atlas_size);
|
||||||
|
static Vector<Vector3i> partial_pack_rects(const Vector<Vector2i> &p_sizes, const Size2i &p_atlas_size);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static Vector<Vector<Point2>> _polypaths_do_operation(PolyBooleanOperation p_op, const Vector<Point2> &p_polypath_a, const Vector<Point2> &p_polypath_b, bool is_a_open = false);
|
||||||
|
static Vector<Vector<Point2>> _polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // GEOMETRY_2D_H
|
1849
polypartition.cpp
Normal file
1849
polypartition.cpp
Normal file
File diff suppressed because it is too large
Load Diff
378
polypartition.h
Normal file
378
polypartition.h
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2011-2021 Ivan Fratric and contributors. */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef POLYPARTITION_H
|
||||||
|
#define POLYPARTITION_H
|
||||||
|
|
||||||
|
#include "core/math/vector2.h"
|
||||||
|
#include "core/list.h"
|
||||||
|
#include "core/set.h"
|
||||||
|
|
||||||
|
typedef double tppl_float;
|
||||||
|
|
||||||
|
enum TPPLOrientation {
|
||||||
|
TPPL_ORIENTATION_CW = -1,
|
||||||
|
TPPL_ORIENTATION_NONE = 0,
|
||||||
|
TPPL_ORIENTATION_CCW = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum TPPLVertexType {
|
||||||
|
TPPL_VERTEXTYPE_REGULAR = 0,
|
||||||
|
TPPL_VERTEXTYPE_START = 1,
|
||||||
|
TPPL_VERTEXTYPE_END = 2,
|
||||||
|
TPPL_VERTEXTYPE_SPLIT = 3,
|
||||||
|
TPPL_VERTEXTYPE_MERGE = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2D point structure.
|
||||||
|
typedef Vector2 TPPLPoint;
|
||||||
|
|
||||||
|
// Polygon implemented as an array of points with a "hole" flag.
|
||||||
|
class TPPLPoly {
|
||||||
|
protected:
|
||||||
|
TPPLPoint *points;
|
||||||
|
long numpoints;
|
||||||
|
bool hole;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Constructors and destructors.
|
||||||
|
TPPLPoly();
|
||||||
|
~TPPLPoly();
|
||||||
|
|
||||||
|
TPPLPoly(const TPPLPoly &src);
|
||||||
|
TPPLPoly &operator=(const TPPLPoly &src);
|
||||||
|
|
||||||
|
// Getters and setters.
|
||||||
|
long GetNumPoints() const {
|
||||||
|
return numpoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsHole() const {
|
||||||
|
return hole;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetHole(bool hole) {
|
||||||
|
this->hole = hole;
|
||||||
|
}
|
||||||
|
|
||||||
|
TPPLPoint &GetPoint(long i) {
|
||||||
|
return points[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const TPPLPoint &GetPoint(long i) const {
|
||||||
|
return points[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
TPPLPoint *GetPoints() {
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
TPPLPoint &operator[](int i) {
|
||||||
|
return points[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const TPPLPoint &operator[](int i) const {
|
||||||
|
return points[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clears the polygon points.
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
// Inits the polygon with numpoints vertices.
|
||||||
|
void Init(long numpoints);
|
||||||
|
|
||||||
|
// Creates a triangle with points p1, p2, and p3.
|
||||||
|
void Triangle(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3);
|
||||||
|
|
||||||
|
// Inverts the orfer of vertices.
|
||||||
|
void Invert();
|
||||||
|
|
||||||
|
// Returns the orientation of the polygon.
|
||||||
|
// Possible values:
|
||||||
|
// TPPL_ORIENTATION_CCW: Polygon vertices are in counter-clockwise order.
|
||||||
|
// TPPL_ORIENTATION_CW: Polygon vertices are in clockwise order.
|
||||||
|
// TPPL_ORIENTATION_NONE: The polygon has no (measurable) area.
|
||||||
|
TPPLOrientation GetOrientation() const;
|
||||||
|
|
||||||
|
// Sets the polygon orientation.
|
||||||
|
// Possible values:
|
||||||
|
// TPPL_ORIENTATION_CCW: Sets vertices in counter-clockwise order.
|
||||||
|
// TPPL_ORIENTATION_CW: Sets vertices in clockwise order.
|
||||||
|
// TPPL_ORIENTATION_NONE: Reverses the orientation of the vertices if there
|
||||||
|
// is one, otherwise does nothing (if orientation is already NONE).
|
||||||
|
void SetOrientation(TPPLOrientation orientation);
|
||||||
|
|
||||||
|
// Checks whether a polygon is valid or not.
|
||||||
|
inline bool Valid() const { return this->numpoints >= 3; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef TPPL_ALLOCATOR
|
||||||
|
typedef List<TPPLPoly, TPPL_ALLOCATOR(TPPLPoly)> TPPLPolyList;
|
||||||
|
#else
|
||||||
|
typedef List<TPPLPoly> TPPLPolyList;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class TPPLPartition {
|
||||||
|
protected:
|
||||||
|
struct PartitionVertex {
|
||||||
|
bool isActive;
|
||||||
|
bool isConvex;
|
||||||
|
bool isEar;
|
||||||
|
|
||||||
|
TPPLPoint p;
|
||||||
|
tppl_float angle;
|
||||||
|
PartitionVertex *previous;
|
||||||
|
PartitionVertex *next;
|
||||||
|
|
||||||
|
PartitionVertex();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MonotoneVertex {
|
||||||
|
TPPLPoint p;
|
||||||
|
long previous;
|
||||||
|
long next;
|
||||||
|
};
|
||||||
|
|
||||||
|
class VertexSorter {
|
||||||
|
MonotoneVertex *vertices;
|
||||||
|
|
||||||
|
public:
|
||||||
|
VertexSorter(MonotoneVertex *v) :
|
||||||
|
vertices(v) {}
|
||||||
|
bool operator()(long index1, long index2);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Diagonal {
|
||||||
|
long index1;
|
||||||
|
long index2;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef TPPL_ALLOCATOR
|
||||||
|
typedef List<Diagonal, TPPL_ALLOCATOR(Diagonal)> DiagonalList;
|
||||||
|
#else
|
||||||
|
typedef List<Diagonal> DiagonalList;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Dynamic programming state for minimum-weight triangulation.
|
||||||
|
struct DPState {
|
||||||
|
bool visible;
|
||||||
|
tppl_float weight;
|
||||||
|
long bestvertex;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dynamic programming state for convex partitioning.
|
||||||
|
struct DPState2 {
|
||||||
|
bool visible;
|
||||||
|
long weight;
|
||||||
|
DiagonalList pairs;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Edge that intersects the scanline.
|
||||||
|
struct ScanLineEdge {
|
||||||
|
mutable long index;
|
||||||
|
TPPLPoint p1;
|
||||||
|
TPPLPoint p2;
|
||||||
|
|
||||||
|
// Determines if the edge is to the left of another edge.
|
||||||
|
bool operator<(const ScanLineEdge &other) const;
|
||||||
|
|
||||||
|
bool IsConvex(const TPPLPoint &p1, const TPPLPoint &p2, const TPPLPoint &p3) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Standard helper functions.
|
||||||
|
bool IsConvex(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3);
|
||||||
|
bool IsReflex(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3);
|
||||||
|
bool IsInside(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3, TPPLPoint &p);
|
||||||
|
|
||||||
|
bool InCone(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3, TPPLPoint &p);
|
||||||
|
bool InCone(PartitionVertex *v, TPPLPoint &p);
|
||||||
|
|
||||||
|
int Intersects(TPPLPoint &p11, TPPLPoint &p12, TPPLPoint &p21, TPPLPoint &p22);
|
||||||
|
|
||||||
|
TPPLPoint Normalize(const TPPLPoint &p);
|
||||||
|
tppl_float Distance(const TPPLPoint &p1, const TPPLPoint &p2);
|
||||||
|
|
||||||
|
// Helper functions for Triangulate_EC.
|
||||||
|
void UpdateVertexReflexity(PartitionVertex *v);
|
||||||
|
void UpdateVertex(PartitionVertex *v, PartitionVertex *vertices, long numvertices);
|
||||||
|
|
||||||
|
// Helper functions for ConvexPartition_OPT.
|
||||||
|
void UpdateState(long a, long b, long w, long i, long j, DPState2 **dpstates);
|
||||||
|
void TypeA(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates);
|
||||||
|
void TypeB(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates);
|
||||||
|
|
||||||
|
// Helper functions for MonotonePartition.
|
||||||
|
bool Below(TPPLPoint &p1, TPPLPoint &p2);
|
||||||
|
void AddDiagonal(MonotoneVertex *vertices, long *numvertices, long index1, long index2,
|
||||||
|
TPPLVertexType *vertextypes, Set<ScanLineEdge>::Element **edgeTreeIterators,
|
||||||
|
Set<ScanLineEdge> *edgeTree, long *helpers);
|
||||||
|
|
||||||
|
// Triangulates a monotone polygon, used in Triangulate_MONO.
|
||||||
|
int TriangulateMonotone(TPPLPoly *inPoly, TPPLPolyList *triangles);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Simple heuristic procedure for removing holes from a list of polygons.
|
||||||
|
// It works by creating a diagonal from the right-most hole vertex
|
||||||
|
// to some other visible vertex.
|
||||||
|
// Time complexity: O(h*(n^2)), h is the # of holes, n is the # of vertices.
|
||||||
|
// Space complexity: O(n)
|
||||||
|
// params:
|
||||||
|
// inpolys:
|
||||||
|
// A list of polygons that can contain holes.
|
||||||
|
// Vertices of all non-hole polys have to be in counter-clockwise order.
|
||||||
|
// Vertices of all hole polys have to be in clockwise order.
|
||||||
|
// outpolys:
|
||||||
|
// A list of polygons without holes.
|
||||||
|
// Returns 1 on success, 0 on failure.
|
||||||
|
int RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys);
|
||||||
|
|
||||||
|
// Triangulates a polygon by ear clipping.
|
||||||
|
// Time complexity: O(n^2), n is the number of vertices.
|
||||||
|
// Space complexity: O(n)
|
||||||
|
// params:
|
||||||
|
// poly:
|
||||||
|
// An input polygon to be triangulated.
|
||||||
|
// Vertices have to be in counter-clockwise order.
|
||||||
|
// triangles:
|
||||||
|
// A list of triangles (result).
|
||||||
|
// Returns 1 on success, 0 on failure.
|
||||||
|
int Triangulate_EC(TPPLPoly *poly, TPPLPolyList *triangles);
|
||||||
|
|
||||||
|
// Triangulates a list of polygons that may contain holes by ear clipping
|
||||||
|
// algorithm. It first calls RemoveHoles to get rid of the holes, and then
|
||||||
|
// calls Triangulate_EC for each resulting polygon.
|
||||||
|
// Time complexity: O(h*(n^2)), h is the # of holes, n is the # of vertices.
|
||||||
|
// Space complexity: O(n)
|
||||||
|
// params:
|
||||||
|
// inpolys:
|
||||||
|
// A list of polygons to be triangulated (can contain holes).
|
||||||
|
// Vertices of all non-hole polys have to be in counter-clockwise order.
|
||||||
|
// Vertices of all hole polys have to be in clockwise order.
|
||||||
|
// triangles:
|
||||||
|
// A list of triangles (result).
|
||||||
|
// Returns 1 on success, 0 on failure.
|
||||||
|
int Triangulate_EC(TPPLPolyList *inpolys, TPPLPolyList *triangles);
|
||||||
|
|
||||||
|
// Creates an optimal polygon triangulation in terms of minimal edge length.
|
||||||
|
// Time complexity: O(n^3), n is the number of vertices
|
||||||
|
// Space complexity: O(n^2)
|
||||||
|
// params:
|
||||||
|
// poly:
|
||||||
|
// An input polygon to be triangulated.
|
||||||
|
// Vertices have to be in counter-clockwise order.
|
||||||
|
// triangles:
|
||||||
|
// A list of triangles (result).
|
||||||
|
// Returns 1 on success, 0 on failure.
|
||||||
|
int Triangulate_OPT(TPPLPoly *poly, TPPLPolyList *triangles);
|
||||||
|
|
||||||
|
// Triangulates a polygon by first partitioning it into monotone polygons.
|
||||||
|
// Time complexity: O(n*log(n)), n is the number of vertices.
|
||||||
|
// Space complexity: O(n)
|
||||||
|
// params:
|
||||||
|
// poly:
|
||||||
|
// An input polygon to be triangulated.
|
||||||
|
// Vertices have to be in counter-clockwise order.
|
||||||
|
// triangles:
|
||||||
|
// A list of triangles (result).
|
||||||
|
// Returns 1 on success, 0 on failure.
|
||||||
|
int Triangulate_MONO(TPPLPoly *poly, TPPLPolyList *triangles);
|
||||||
|
|
||||||
|
// Triangulates a list of polygons by first
|
||||||
|
// partitioning them into monotone polygons.
|
||||||
|
// Time complexity: O(n*log(n)), n is the number of vertices.
|
||||||
|
// Space complexity: O(n)
|
||||||
|
// params:
|
||||||
|
// inpolys:
|
||||||
|
// A list of polygons to be triangulated (can contain holes).
|
||||||
|
// Vertices of all non-hole polys have to be in counter-clockwise order.
|
||||||
|
// Vertices of all hole polys have to be in clockwise order.
|
||||||
|
// triangles:
|
||||||
|
// A list of triangles (result).
|
||||||
|
// Returns 1 on success, 0 on failure.
|
||||||
|
int Triangulate_MONO(TPPLPolyList *inpolys, TPPLPolyList *triangles);
|
||||||
|
|
||||||
|
// Creates a monotone partition of a list of polygons that
|
||||||
|
// can contain holes. Triangulates a set of polygons by
|
||||||
|
// first partitioning them into monotone polygons.
|
||||||
|
// Time complexity: O(n*log(n)), n is the number of vertices.
|
||||||
|
// Space complexity: O(n)
|
||||||
|
// params:
|
||||||
|
// inpolys:
|
||||||
|
// A list of polygons to be triangulated (can contain holes).
|
||||||
|
// Vertices of all non-hole polys have to be in counter-clockwise order.
|
||||||
|
// Vertices of all hole polys have to be in clockwise order.
|
||||||
|
// monotonePolys:
|
||||||
|
// A list of monotone polygons (result).
|
||||||
|
// Returns 1 on success, 0 on failure.
|
||||||
|
int MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monotonePolys);
|
||||||
|
|
||||||
|
// Partitions a polygon into convex polygons by using the
|
||||||
|
// Hertel-Mehlhorn algorithm. The algorithm gives at most four times
|
||||||
|
// the number of parts as the optimal algorithm, however, in practice
|
||||||
|
// it works much better than that and often gives optimal partition.
|
||||||
|
// It uses triangulation obtained by ear clipping as intermediate result.
|
||||||
|
// Time complexity O(n^2), n is the number of vertices.
|
||||||
|
// Space complexity: O(n)
|
||||||
|
// params:
|
||||||
|
// poly:
|
||||||
|
// An input polygon to be partitioned.
|
||||||
|
// Vertices have to be in counter-clockwise order.
|
||||||
|
// parts:
|
||||||
|
// Resulting list of convex polygons.
|
||||||
|
// Returns 1 on success, 0 on failure.
|
||||||
|
int ConvexPartition_HM(TPPLPoly *poly, TPPLPolyList *parts);
|
||||||
|
|
||||||
|
// Partitions a list of polygons into convex parts by using the
|
||||||
|
// Hertel-Mehlhorn algorithm. The algorithm gives at most four times
|
||||||
|
// the number of parts as the optimal algorithm, however, in practice
|
||||||
|
// it works much better than that and often gives optimal partition.
|
||||||
|
// It uses triangulation obtained by ear clipping as intermediate result.
|
||||||
|
// Time complexity O(n^2), n is the number of vertices.
|
||||||
|
// Space complexity: O(n)
|
||||||
|
// params:
|
||||||
|
// inpolys:
|
||||||
|
// An input list of polygons to be partitioned. Vertices of
|
||||||
|
// all non-hole polys have to be in counter-clockwise order.
|
||||||
|
// Vertices of all hole polys have to be in clockwise order.
|
||||||
|
// parts:
|
||||||
|
// Resulting list of convex polygons.
|
||||||
|
// Returns 1 on success, 0 on failure.
|
||||||
|
int ConvexPartition_HM(TPPLPolyList *inpolys, TPPLPolyList *parts);
|
||||||
|
|
||||||
|
// Optimal convex partitioning (in terms of number of resulting
|
||||||
|
// convex polygons) using the Keil-Snoeyink algorithm.
|
||||||
|
// For reference, see M. Keil, J. Snoeyink, "On the time bound for
|
||||||
|
// convex decomposition of simple polygons", 1998.
|
||||||
|
// Time complexity O(n^3), n is the number of vertices.
|
||||||
|
// Space complexity: O(n^3)
|
||||||
|
// params:
|
||||||
|
// poly:
|
||||||
|
// An input polygon to be partitioned.
|
||||||
|
// Vertices have to be in counter-clockwise order.
|
||||||
|
// parts:
|
||||||
|
// Resulting list of convex polygons.
|
||||||
|
// Returns 1 on success, 0 on failure.
|
||||||
|
int ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
35
register_types.cpp
Normal file
35
register_types.cpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2021 Péter Magyar
|
||||||
|
|
||||||
|
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 "register_types.h"
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void register_rtile_map_types() {
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
// EditorPlugins::add_by_type<ModuleSkeletonEditorPlugin>();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void unregister_rtile_map_types() {
|
||||||
|
}
|
24
register_types.h
Normal file
24
register_types.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2021 Péter Magyar
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void register_rtile_map_types();
|
||||||
|
void unregister_rtile_map_types();
|
5599
rtile_set.cpp
Normal file
5599
rtile_set.cpp
Normal file
File diff suppressed because it is too large
Load Diff
912
rtile_set.h
Normal file
912
rtile_set.h
Normal file
@ -0,0 +1,912 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tile_set.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef RTILE_SET_H
|
||||||
|
#define RTILE_SET_H
|
||||||
|
|
||||||
|
#include "core/resource.h"
|
||||||
|
#include "core/object.h"
|
||||||
|
#include "core/vector.h"
|
||||||
|
#include "scene/2d/light_occluder_2d.h"
|
||||||
|
//#include "scene/2d/navigation_region_2d.h"
|
||||||
|
//#include "servers/navigation_server_2d.h"
|
||||||
|
#include "scene/2d/navigation_polygon.h"
|
||||||
|
#include "scene/2d/canvas_item.h"
|
||||||
|
#include "scene/resources/texture.h"
|
||||||
|
|
||||||
|
#include "scene/resources/concave_polygon_shape_2d.h"
|
||||||
|
#include "scene/resources/convex_polygon_shape_2d.h"
|
||||||
|
#include "scene/resources/packed_scene.h"
|
||||||
|
#include "scene/resources/physics_material.h"
|
||||||
|
#include "scene/resources/shape_2d.h"
|
||||||
|
|
||||||
|
#ifndef DISABLE_DEPRECATED
|
||||||
|
#include "scene/resources/shader.h"
|
||||||
|
#include "scene/resources/texture.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class RTileMap;
|
||||||
|
struct RTileMapQuadrant;
|
||||||
|
class RTileSetSource;
|
||||||
|
class RTileSetAtlasSource;
|
||||||
|
class RTileData;
|
||||||
|
|
||||||
|
// Forward-declare the plugins.
|
||||||
|
class RTileSetPlugin;
|
||||||
|
class RTileSetPluginAtlasRendering;
|
||||||
|
class RTileSetPluginAtlasPhysics;
|
||||||
|
class RTileSetPluginAtlasNavigation;
|
||||||
|
|
||||||
|
union RTileMapCell {
|
||||||
|
struct {
|
||||||
|
int32_t source_id : 16;
|
||||||
|
int16_t coord_x : 16;
|
||||||
|
int16_t coord_y : 16;
|
||||||
|
int32_t alternative_tile : 16;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t _u64t;
|
||||||
|
RTileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = Vector2i(-1, -1), int p_alternative_tile = -1) { // default are INVALID_SOURCE, INVALID_ATLAS_COORDS, INVALID_TILE_ALTERNATIVE
|
||||||
|
source_id = p_source_id;
|
||||||
|
set_atlas_coords(p_atlas_coords);
|
||||||
|
alternative_tile = p_alternative_tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2i get_atlas_coords() const {
|
||||||
|
return Vector2i(coord_x, coord_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_atlas_coords(const Vector2i &r_coords) {
|
||||||
|
coord_x = r_coords.x;
|
||||||
|
coord_y = r_coords.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const RTileMapCell &p_other) const {
|
||||||
|
if (source_id == p_other.source_id) {
|
||||||
|
if (coord_x == p_other.coord_x) {
|
||||||
|
if (coord_y == p_other.coord_y) {
|
||||||
|
return alternative_tile < p_other.alternative_tile;
|
||||||
|
} else {
|
||||||
|
return coord_y < p_other.coord_y;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return coord_x < p_other.coord_x;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return source_id < p_other.source_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const RTileMapCell &p_other) const {
|
||||||
|
return !(source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class RTileMapPattern : public Resource {
|
||||||
|
GDCLASS(RTileMapPattern, Resource);
|
||||||
|
|
||||||
|
Vector2i size;
|
||||||
|
Map<Vector2i, RTileMapCell> pattern;
|
||||||
|
|
||||||
|
void _set_tile_data(const Vector<int> &p_data);
|
||||||
|
Vector<int> _get_tile_data() const;
|
||||||
|
|
||||||
|
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_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile = 0);
|
||||||
|
bool has_cell(const Vector2i &p_coords) const;
|
||||||
|
void remove_cell(const Vector2i &p_coords, bool p_update_size = true);
|
||||||
|
int get_cell_source_id(const Vector2i &p_coords) const;
|
||||||
|
Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const;
|
||||||
|
int get_cell_alternative_tile(const Vector2i &p_coords) const;
|
||||||
|
|
||||||
|
Vector<Vector2i> get_used_cells() const;
|
||||||
|
|
||||||
|
Vector2i get_size() const;
|
||||||
|
void set_size(const Vector2i &p_size);
|
||||||
|
bool is_empty() const;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
class RTileSet : public Resource {
|
||||||
|
GDCLASS(RTileSet, Resource);
|
||||||
|
|
||||||
|
#ifndef DISABLE_DEPRECATED
|
||||||
|
private:
|
||||||
|
struct CompatibilityShapeData {
|
||||||
|
Vector2i autotile_coords;
|
||||||
|
bool one_way;
|
||||||
|
float one_way_margin;
|
||||||
|
Ref<Shape2D> shape;
|
||||||
|
Transform2D transform;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CompatibilityTileData {
|
||||||
|
String name;
|
||||||
|
Ref<Texture> texture;
|
||||||
|
Vector2 tex_offset;
|
||||||
|
Ref<ShaderMaterial> material;
|
||||||
|
Rect2 region;
|
||||||
|
int tile_mode = 0;
|
||||||
|
Color modulate = Color(1, 1, 1);
|
||||||
|
|
||||||
|
// Atlas or autotiles data
|
||||||
|
int autotile_bitmask_mode = 0;
|
||||||
|
Vector2 autotile_icon_coordinate;
|
||||||
|
Size2i autotile_tile_size = Size2i(16, 16);
|
||||||
|
|
||||||
|
int autotile_spacing = 0;
|
||||||
|
Map<Vector2i, int> autotile_bitmask_flags;
|
||||||
|
Map<Vector2i, Ref<OccluderPolygon2D>> autotile_occluder_map;
|
||||||
|
Map<Vector2i, Ref<NavigationPolygon>> autotile_navpoly_map;
|
||||||
|
Map<Vector2i, int> autotile_priority_map;
|
||||||
|
Map<Vector2i, int> autotile_z_index_map;
|
||||||
|
|
||||||
|
Vector<CompatibilityShapeData> shapes;
|
||||||
|
Ref<OccluderPolygon2D> occluder;
|
||||||
|
Vector2 occluder_offset;
|
||||||
|
Ref<NavigationPolygon> navigation;
|
||||||
|
Vector2 navigation_offset;
|
||||||
|
int z_index = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum CompatibilityTileMode {
|
||||||
|
COMPATIBILITY_TILE_MODE_SINGLE_TILE = 0,
|
||||||
|
COMPATIBILITY_TILE_MODE_AUTO_TILE,
|
||||||
|
COMPATIBILITY_TILE_MODE_ATLAS_TILE,
|
||||||
|
};
|
||||||
|
|
||||||
|
Map<int, CompatibilityTileData *> compatibility_data;
|
||||||
|
Map<int, int> compatibility_tilemap_mapping_tile_modes;
|
||||||
|
Map<int, Map<Array, Array>> compatibility_tilemap_mapping;
|
||||||
|
|
||||||
|
void _compatibility_conversion();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Format of output array [source_id, atlas_coords, alternative]
|
||||||
|
Array compatibility_tilemap_map(int p_tile_id, Vector2i p_coords, bool p_flip_h, bool p_flip_v, bool p_transpose);
|
||||||
|
#endif // DISABLE_DEPRECATED
|
||||||
|
|
||||||
|
public:
|
||||||
|
static const int INVALID_SOURCE; // -1;
|
||||||
|
|
||||||
|
enum CellNeighbor {
|
||||||
|
CELL_NEIGHBOR_RIGHT_SIDE = 0,
|
||||||
|
CELL_NEIGHBOR_RIGHT_CORNER,
|
||||||
|
CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE,
|
||||||
|
CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER,
|
||||||
|
CELL_NEIGHBOR_BOTTOM_SIDE,
|
||||||
|
CELL_NEIGHBOR_BOTTOM_CORNER,
|
||||||
|
CELL_NEIGHBOR_BOTTOM_LEFT_SIDE,
|
||||||
|
CELL_NEIGHBOR_BOTTOM_LEFT_CORNER,
|
||||||
|
CELL_NEIGHBOR_LEFT_SIDE,
|
||||||
|
CELL_NEIGHBOR_LEFT_CORNER,
|
||||||
|
CELL_NEIGHBOR_TOP_LEFT_SIDE,
|
||||||
|
CELL_NEIGHBOR_TOP_LEFT_CORNER,
|
||||||
|
CELL_NEIGHBOR_TOP_SIDE,
|
||||||
|
CELL_NEIGHBOR_TOP_CORNER,
|
||||||
|
CELL_NEIGHBOR_TOP_RIGHT_SIDE,
|
||||||
|
CELL_NEIGHBOR_TOP_RIGHT_CORNER,
|
||||||
|
CELL_NEIGHBOR_MAX,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *CELL_NEIGHBOR_ENUM_TO_TEXT[];
|
||||||
|
|
||||||
|
enum TerrainMode {
|
||||||
|
TERRAIN_MODE_MATCH_CORNERS_AND_SIDES = 0,
|
||||||
|
TERRAIN_MODE_MATCH_CORNERS,
|
||||||
|
TERRAIN_MODE_MATCH_SIDES,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum TileShape {
|
||||||
|
TILE_SHAPE_SQUARE,
|
||||||
|
TILE_SHAPE_ISOMETRIC,
|
||||||
|
TILE_SHAPE_HALF_OFFSET_SQUARE,
|
||||||
|
TILE_SHAPE_HEXAGON,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum TileLayout {
|
||||||
|
TILE_LAYOUT_STACKED,
|
||||||
|
TILE_LAYOUT_STACKED_OFFSET,
|
||||||
|
TILE_LAYOUT_STAIRS_RIGHT,
|
||||||
|
TILE_LAYOUT_STAIRS_DOWN,
|
||||||
|
TILE_LAYOUT_DIAMOND_RIGHT,
|
||||||
|
TILE_LAYOUT_DIAMOND_DOWN,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum TileOffsetAxis {
|
||||||
|
TILE_OFFSET_AXIS_HORIZONTAL,
|
||||||
|
TILE_OFFSET_AXIS_VERTICAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PackedSceneSource {
|
||||||
|
Ref<PackedScene> scene;
|
||||||
|
Vector2 offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TerrainsPattern {
|
||||||
|
bool valid = false;
|
||||||
|
int bits[RTileSet::CELL_NEIGHBOR_MAX];
|
||||||
|
bool is_valid_bit[RTileSet::CELL_NEIGHBOR_MAX];
|
||||||
|
|
||||||
|
int not_empty_terrains_count = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool is_valid() const;
|
||||||
|
bool is_erase_pattern() const;
|
||||||
|
|
||||||
|
bool operator<(const TerrainsPattern &p_terrains_pattern) const;
|
||||||
|
bool operator==(const TerrainsPattern &p_terrains_pattern) const;
|
||||||
|
|
||||||
|
void set_terrain(RTileSet::CellNeighbor p_peering_bit, int p_terrain);
|
||||||
|
int get_terrain(RTileSet::CellNeighbor p_peering_bit) const;
|
||||||
|
|
||||||
|
void set_terrains_from_array(Array p_terrains);
|
||||||
|
Array get_terrains_as_array() const;
|
||||||
|
|
||||||
|
TerrainsPattern(const RTileSet *p_tile_set, int p_terrain_set);
|
||||||
|
TerrainsPattern() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
virtual void _validate_property(PropertyInfo &property) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// --- TileSet data ---
|
||||||
|
// Basic shape and layout.
|
||||||
|
TileShape tile_shape = TILE_SHAPE_SQUARE;
|
||||||
|
TileLayout tile_layout = TILE_LAYOUT_STACKED;
|
||||||
|
TileOffsetAxis tile_offset_axis = TILE_OFFSET_AXIS_HORIZONTAL;
|
||||||
|
Size2i tile_size = Size2i(16, 16); //Size2(64, 64);
|
||||||
|
Vector2 tile_skew = Vector2(0, 0);
|
||||||
|
|
||||||
|
// Rendering.
|
||||||
|
bool uv_clipping = false;
|
||||||
|
struct OcclusionLayer {
|
||||||
|
uint32_t light_mask = 1;
|
||||||
|
bool sdf_collision = false;
|
||||||
|
};
|
||||||
|
Vector<OcclusionLayer> occlusion_layers;
|
||||||
|
|
||||||
|
Ref<ArrayMesh> tile_lines_mesh;
|
||||||
|
Ref<ArrayMesh> tile_filled_mesh;
|
||||||
|
bool tile_meshes_dirty = true;
|
||||||
|
|
||||||
|
// Physics
|
||||||
|
struct PhysicsLayer {
|
||||||
|
uint32_t collision_layer = 1;
|
||||||
|
uint32_t collision_mask = 1;
|
||||||
|
Ref<PhysicsMaterial> physics_material;
|
||||||
|
};
|
||||||
|
Vector<PhysicsLayer> physics_layers;
|
||||||
|
|
||||||
|
// Terrains
|
||||||
|
struct Terrain {
|
||||||
|
String name;
|
||||||
|
Color color;
|
||||||
|
};
|
||||||
|
struct TerrainSet {
|
||||||
|
TerrainMode mode = TERRAIN_MODE_MATCH_CORNERS_AND_SIDES;
|
||||||
|
Vector<Terrain> terrains;
|
||||||
|
};
|
||||||
|
Vector<TerrainSet> terrain_sets;
|
||||||
|
|
||||||
|
Map<TerrainMode, Map<CellNeighbor, Ref<ArrayMesh>>> terrain_bits_meshes;
|
||||||
|
bool terrain_bits_meshes_dirty = true;
|
||||||
|
|
||||||
|
LocalVector<Map<RTileSet::TerrainsPattern, Set<RTileMapCell>>> per_terrain_pattern_tiles; // Cached data.
|
||||||
|
bool terrains_cache_dirty = true;
|
||||||
|
void _update_terrains_cache();
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
struct NavigationLayer {
|
||||||
|
uint32_t layers = 1;
|
||||||
|
};
|
||||||
|
Vector<NavigationLayer> navigation_layers;
|
||||||
|
|
||||||
|
// CustomData
|
||||||
|
struct CustomDataLayer {
|
||||||
|
String name;
|
||||||
|
Variant::Type type = Variant::NIL;
|
||||||
|
};
|
||||||
|
Vector<CustomDataLayer> custom_data_layers;
|
||||||
|
Map<String, int> custom_data_layers_by_name;
|
||||||
|
|
||||||
|
// Per Atlas source data.
|
||||||
|
Map<int, Ref<RTileSetSource>> sources;
|
||||||
|
Vector<int> source_ids;
|
||||||
|
int next_source_id = 0;
|
||||||
|
// ---------------------
|
||||||
|
|
||||||
|
LocalVector<Ref<RTileMapPattern>> patterns;
|
||||||
|
|
||||||
|
void _compute_next_source_id();
|
||||||
|
void _source_changed();
|
||||||
|
|
||||||
|
// Tile proxies
|
||||||
|
Map<int, int> source_level_proxies;
|
||||||
|
Map<Array, Array> coords_level_proxies;
|
||||||
|
Map<Array, Array> alternative_level_proxies;
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
Vector<Point2> _get_square_corner_or_side_terrain_bit_polygon(Vector2i p_size, RTileSet::CellNeighbor p_bit);
|
||||||
|
Vector<Point2> _get_square_corner_terrain_bit_polygon(Vector2i p_size, RTileSet::CellNeighbor p_bit);
|
||||||
|
Vector<Point2> _get_square_side_terrain_bit_polygon(Vector2i p_size, RTileSet::CellNeighbor p_bit);
|
||||||
|
|
||||||
|
Vector<Point2> _get_isometric_corner_or_side_terrain_bit_polygon(Vector2i p_size, RTileSet::CellNeighbor p_bit);
|
||||||
|
Vector<Point2> _get_isometric_corner_terrain_bit_polygon(Vector2i p_size, RTileSet::CellNeighbor p_bit);
|
||||||
|
Vector<Point2> _get_isometric_side_terrain_bit_polygon(Vector2i p_size, RTileSet::CellNeighbor p_bit);
|
||||||
|
|
||||||
|
Vector<Point2> _get_half_offset_corner_or_side_terrain_bit_polygon(Vector2i p_size, RTileSet::CellNeighbor p_bit, float p_overlap, RTileSet::TileOffsetAxis p_offset_axis);
|
||||||
|
Vector<Point2> _get_half_offset_corner_terrain_bit_polygon(Vector2i p_size, RTileSet::CellNeighbor p_bit, float p_overlap, RTileSet::TileOffsetAxis p_offset_axis);
|
||||||
|
Vector<Point2> _get_half_offset_side_terrain_bit_polygon(Vector2i p_size, RTileSet::CellNeighbor p_bit, float p_overlap, RTileSet::TileOffsetAxis p_offset_axis);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// --- Plugins ---
|
||||||
|
Vector<RTileSetPlugin *> get_tile_set_atlas_plugins() const;
|
||||||
|
|
||||||
|
// --- Accessors for TileSet data ---
|
||||||
|
|
||||||
|
// -- Shape and layout --
|
||||||
|
void set_tile_shape(TileShape p_shape);
|
||||||
|
TileShape get_tile_shape() const;
|
||||||
|
void set_tile_layout(TileLayout p_layout);
|
||||||
|
TileLayout get_tile_layout() const;
|
||||||
|
void set_tile_offset_axis(TileOffsetAxis p_alignment);
|
||||||
|
TileOffsetAxis get_tile_offset_axis() const;
|
||||||
|
void set_tile_size(Size2i p_size);
|
||||||
|
Size2i get_tile_size() const;
|
||||||
|
|
||||||
|
// -- Sources management --
|
||||||
|
int get_next_source_id() const;
|
||||||
|
int get_source_count() const;
|
||||||
|
int get_source_id(int p_index) const;
|
||||||
|
int add_source(Ref<RTileSetSource> p_tile_set_source, int p_source_id_override = -1);
|
||||||
|
void set_source_id(int p_source_id, int p_new_id);
|
||||||
|
void remove_source(int p_source_id);
|
||||||
|
bool has_source(int p_source_id) const;
|
||||||
|
Ref<RTileSetSource> get_source(int p_source_id) const;
|
||||||
|
|
||||||
|
// Rendering
|
||||||
|
void set_uv_clipping(bool p_uv_clipping);
|
||||||
|
bool is_uv_clipping() const;
|
||||||
|
|
||||||
|
int get_occlusion_layers_count() const;
|
||||||
|
void add_occlusion_layer(int p_index = -1);
|
||||||
|
void move_occlusion_layer(int p_from_index, int p_to_pos);
|
||||||
|
void remove_occlusion_layer(int p_index);
|
||||||
|
void set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask);
|
||||||
|
int get_occlusion_layer_light_mask(int p_layer_index) const;
|
||||||
|
void set_occlusion_layer_sdf_collision(int p_layer_index, bool p_sdf_collision);
|
||||||
|
bool get_occlusion_layer_sdf_collision(int p_layer_index) const;
|
||||||
|
|
||||||
|
// Physics
|
||||||
|
int get_physics_layers_count() const;
|
||||||
|
void add_physics_layer(int p_index = -1);
|
||||||
|
void move_physics_layer(int p_from_index, int p_to_pos);
|
||||||
|
void remove_physics_layer(int p_index);
|
||||||
|
void set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer);
|
||||||
|
uint32_t get_physics_layer_collision_layer(int p_layer_index) const;
|
||||||
|
void set_physics_layer_collision_mask(int p_layer_index, uint32_t p_mask);
|
||||||
|
uint32_t get_physics_layer_collision_mask(int p_layer_index) const;
|
||||||
|
void set_physics_layer_physics_material(int p_layer_index, Ref<PhysicsMaterial> p_physics_material);
|
||||||
|
Ref<PhysicsMaterial> get_physics_layer_physics_material(int p_layer_index) const;
|
||||||
|
|
||||||
|
// Terrain sets
|
||||||
|
int get_terrain_sets_count() const;
|
||||||
|
void add_terrain_set(int p_index = -1);
|
||||||
|
void move_terrain_set(int p_from_index, int p_to_pos);
|
||||||
|
void remove_terrain_set(int p_index);
|
||||||
|
void set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode);
|
||||||
|
TerrainMode get_terrain_set_mode(int p_terrain_set) const;
|
||||||
|
|
||||||
|
// Terrains
|
||||||
|
int get_terrains_count(int p_terrain_set) const;
|
||||||
|
void add_terrain(int p_terrain_set, int p_index = -1);
|
||||||
|
void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos);
|
||||||
|
void remove_terrain(int p_terrain_set, int p_index);
|
||||||
|
void set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name);
|
||||||
|
String get_terrain_name(int p_terrain_set, int p_terrain_index) const;
|
||||||
|
void set_terrain_color(int p_terrain_set, int p_terrain_index, Color p_color);
|
||||||
|
Color get_terrain_color(int p_terrain_set, int p_terrain_index) const;
|
||||||
|
bool is_valid_peering_bit_for_mode(RTileSet::TerrainMode p_terrain_mode, RTileSet::CellNeighbor p_peering_bit) const;
|
||||||
|
bool is_valid_peering_bit_terrain(int p_terrain_set, RTileSet::CellNeighbor p_peering_bit) const;
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
int get_navigation_layers_count() const;
|
||||||
|
void add_navigation_layer(int p_index = -1);
|
||||||
|
void move_navigation_layer(int p_from_index, int p_to_pos);
|
||||||
|
void remove_navigation_layer(int p_index);
|
||||||
|
void set_navigation_layer_layers(int p_layer_index, uint32_t p_layers);
|
||||||
|
uint32_t get_navigation_layer_layers(int p_layer_index) const;
|
||||||
|
|
||||||
|
// Custom data
|
||||||
|
int get_custom_data_layers_count() const;
|
||||||
|
void add_custom_data_layer(int p_index = -1);
|
||||||
|
void move_custom_data_layer(int p_from_index, int p_to_pos);
|
||||||
|
void remove_custom_data_layer(int p_index);
|
||||||
|
int get_custom_data_layer_by_name(String p_value) const;
|
||||||
|
void set_custom_data_name(int p_layer_id, String p_value);
|
||||||
|
String get_custom_data_name(int p_layer_id) const;
|
||||||
|
void set_custom_data_type(int p_layer_id, Variant::Type p_value);
|
||||||
|
Variant::Type get_custom_data_type(int p_layer_id) const;
|
||||||
|
|
||||||
|
// Tiles proxies.
|
||||||
|
void set_source_level_tile_proxy(int p_source_from, int p_source_to);
|
||||||
|
int get_source_level_tile_proxy(int p_source_from);
|
||||||
|
bool has_source_level_tile_proxy(int p_source_from);
|
||||||
|
void remove_source_level_tile_proxy(int p_source_from);
|
||||||
|
|
||||||
|
void set_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_source_to, Vector2i p_coords_to);
|
||||||
|
Array get_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from);
|
||||||
|
bool has_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from);
|
||||||
|
void remove_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from);
|
||||||
|
|
||||||
|
void set_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from, int p_source_to, Vector2i p_coords_to, int p_alternative_to);
|
||||||
|
Array get_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from);
|
||||||
|
bool has_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from);
|
||||||
|
void remove_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from);
|
||||||
|
|
||||||
|
Array get_source_level_tile_proxies() const;
|
||||||
|
Array get_coords_level_tile_proxies() const;
|
||||||
|
Array get_alternative_level_tile_proxies() const;
|
||||||
|
|
||||||
|
Array map_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) const;
|
||||||
|
|
||||||
|
void cleanup_invalid_tile_proxies();
|
||||||
|
void clear_tile_proxies();
|
||||||
|
|
||||||
|
// Patterns.
|
||||||
|
int add_pattern(Ref<RTileMapPattern> p_pattern, int p_index = -1);
|
||||||
|
Ref<RTileMapPattern> get_pattern(int p_index);
|
||||||
|
void remove_pattern(int p_index);
|
||||||
|
int get_patterns_count();
|
||||||
|
|
||||||
|
// Terrains.
|
||||||
|
Set<TerrainsPattern> get_terrains_pattern_set(int p_terrain_set);
|
||||||
|
Set<RTileMapCell> get_tiles_for_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern);
|
||||||
|
RTileMapCell get_random_tile_from_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern);
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
Vector<Vector2> get_tile_shape_polygon();
|
||||||
|
void draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled = false, Ref<Texture> p_texture = Ref<Texture>());
|
||||||
|
|
||||||
|
Vector<Point2> get_terrain_bit_polygon(int p_terrain_set, RTileSet::CellNeighbor p_bit);
|
||||||
|
void draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, const RTileData *p_tile_data);
|
||||||
|
Vector<Vector<Ref<Texture>>> generate_terrains_icons(Size2i p_size);
|
||||||
|
|
||||||
|
// Resource management
|
||||||
|
virtual void reset_state();
|
||||||
|
|
||||||
|
RTileSet();
|
||||||
|
~RTileSet();
|
||||||
|
};
|
||||||
|
|
||||||
|
class RTileSetSource : public Resource {
|
||||||
|
GDCLASS(RTileSetSource, Resource);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const RTileSet *tile_set = nullptr;
|
||||||
|
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
public:
|
||||||
|
static const Vector2i INVALID_ATLAS_COORDS; // Vector2i(-1, -1);
|
||||||
|
static const int INVALID_TILE_ALTERNATIVE; // -1;
|
||||||
|
|
||||||
|
// Not exposed.
|
||||||
|
virtual void set_tile_set(const RTileSet *p_tile_set);
|
||||||
|
virtual void notify_tile_data_properties_should_change(){};
|
||||||
|
virtual void add_occlusion_layer(int p_index){};
|
||||||
|
virtual void move_occlusion_layer(int p_from_index, int p_to_pos){};
|
||||||
|
virtual void remove_occlusion_layer(int p_index){};
|
||||||
|
virtual void add_physics_layer(int p_index){};
|
||||||
|
virtual void move_physics_layer(int p_from_index, int p_to_pos){};
|
||||||
|
virtual void remove_physics_layer(int p_index){};
|
||||||
|
virtual void add_terrain_set(int p_index){};
|
||||||
|
virtual void move_terrain_set(int p_from_index, int p_to_pos){};
|
||||||
|
virtual void remove_terrain_set(int p_index){};
|
||||||
|
virtual void add_terrain(int p_terrain_set, int p_index){};
|
||||||
|
virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos){};
|
||||||
|
virtual void remove_terrain(int p_terrain_set, int p_index){};
|
||||||
|
virtual void add_navigation_layer(int p_index){};
|
||||||
|
virtual void move_navigation_layer(int p_from_index, int p_to_pos){};
|
||||||
|
virtual void remove_navigation_layer(int p_index){};
|
||||||
|
virtual void add_custom_data_layer(int p_index){};
|
||||||
|
virtual void move_custom_data_layer(int p_from_index, int p_to_pos){};
|
||||||
|
virtual void remove_custom_data_layer(int p_index){};
|
||||||
|
virtual void reset_state() {};
|
||||||
|
|
||||||
|
// Tiles.
|
||||||
|
virtual int get_tiles_count() const = 0;
|
||||||
|
virtual Vector2i get_tile_id(int tile_index) const = 0;
|
||||||
|
virtual bool has_tile(Vector2i p_atlas_coords) const = 0;
|
||||||
|
|
||||||
|
// Alternative tiles.
|
||||||
|
virtual int get_alternative_tiles_count(const Vector2i p_atlas_coords) const = 0;
|
||||||
|
virtual int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const = 0;
|
||||||
|
virtual bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RTileSetAtlasSource : public RTileSetSource {
|
||||||
|
GDCLASS(RTileSetAtlasSource, RTileSetSource);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct TileAlternativesData {
|
||||||
|
Vector2i size_in_atlas = Vector2i(1, 1);
|
||||||
|
Vector2i texture_offset;
|
||||||
|
|
||||||
|
// Animation
|
||||||
|
int animation_columns = 0;
|
||||||
|
Vector2i animation_separation;
|
||||||
|
real_t animation_speed = 1.0;
|
||||||
|
LocalVector<real_t> animation_frames_durations;
|
||||||
|
|
||||||
|
// Alternatives
|
||||||
|
Map<int, RTileData *> alternatives;
|
||||||
|
Vector<int> alternatives_ids;
|
||||||
|
int next_alternative_id = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
Ref<Texture> texture;
|
||||||
|
Vector2i margins;
|
||||||
|
Vector2i separation;
|
||||||
|
Size2i texture_region_size = Size2i(16, 16);
|
||||||
|
|
||||||
|
Map<Vector2i, TileAlternativesData> tiles;
|
||||||
|
Vector<Vector2i> tiles_ids;
|
||||||
|
Map<Vector2i, Vector2i> _coords_mapping_cache; // Maps any coordinate to the including tile
|
||||||
|
|
||||||
|
RTileData *_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile);
|
||||||
|
const RTileData *_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) const;
|
||||||
|
|
||||||
|
void _compute_next_alternative_id(const Vector2i p_atlas_coords);
|
||||||
|
|
||||||
|
void _clear_coords_mapping_cache(Vector2i p_atlas_coords);
|
||||||
|
void _create_coords_mapping_cache(Vector2i p_atlas_coords);
|
||||||
|
|
||||||
|
void _clear_tiles_outside_texture();
|
||||||
|
|
||||||
|
bool use_texture_padding = true;
|
||||||
|
Ref<ImageTexture> padded_texture;
|
||||||
|
bool padded_texture_needs_update = false;
|
||||||
|
void _queue_update_padded_texture();
|
||||||
|
void _update_padded_texture();
|
||||||
|
|
||||||
|
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:
|
||||||
|
// Not exposed.
|
||||||
|
virtual void set_tile_set(const RTileSet *p_tile_set) override;
|
||||||
|
const RTileSet *get_tile_set() const;
|
||||||
|
virtual void notify_tile_data_properties_should_change() override;
|
||||||
|
virtual void add_occlusion_layer(int p_index) override;
|
||||||
|
virtual void move_occlusion_layer(int p_from_index, int p_to_pos) override;
|
||||||
|
virtual void remove_occlusion_layer(int p_index) override;
|
||||||
|
virtual void add_physics_layer(int p_index) override;
|
||||||
|
virtual void move_physics_layer(int p_from_index, int p_to_pos) override;
|
||||||
|
virtual void remove_physics_layer(int p_index) override;
|
||||||
|
virtual void add_terrain_set(int p_index) override;
|
||||||
|
virtual void move_terrain_set(int p_from_index, int p_to_pos) override;
|
||||||
|
virtual void remove_terrain_set(int p_index) override;
|
||||||
|
virtual void add_terrain(int p_terrain_set, int p_index) override;
|
||||||
|
virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) override;
|
||||||
|
virtual void remove_terrain(int p_terrain_set, int p_index) override;
|
||||||
|
virtual void add_navigation_layer(int p_index) override;
|
||||||
|
virtual void move_navigation_layer(int p_from_index, int p_to_pos) override;
|
||||||
|
virtual void remove_navigation_layer(int p_index) override;
|
||||||
|
virtual void add_custom_data_layer(int p_index) override;
|
||||||
|
virtual void move_custom_data_layer(int p_from_index, int p_to_pos) override;
|
||||||
|
virtual void remove_custom_data_layer(int p_index) override;
|
||||||
|
virtual void reset_state() override;
|
||||||
|
|
||||||
|
// Base properties.
|
||||||
|
void set_texture(Ref<Texture> p_texture);
|
||||||
|
Ref<Texture> get_texture() const;
|
||||||
|
void set_margins(Vector2i p_margins);
|
||||||
|
Vector2i get_margins() const;
|
||||||
|
void set_separation(Vector2i p_separation);
|
||||||
|
Vector2i get_separation() const;
|
||||||
|
void set_texture_region_size(Vector2i p_tile_size);
|
||||||
|
Vector2i get_texture_region_size() const;
|
||||||
|
|
||||||
|
// Padding.
|
||||||
|
void set_use_texture_padding(bool p_use_padding);
|
||||||
|
bool get_use_texture_padding() const;
|
||||||
|
|
||||||
|
// Base tiles.
|
||||||
|
void create_tile(const Vector2i p_atlas_coords, const Vector2i p_size = Vector2i(1, 1));
|
||||||
|
void remove_tile(Vector2i p_atlas_coords);
|
||||||
|
virtual bool has_tile(Vector2i p_atlas_coords) const override;
|
||||||
|
void move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords = INVALID_ATLAS_COORDS, Vector2i p_new_size = Vector2i(-1, -1));
|
||||||
|
Vector2i get_tile_size_in_atlas(Vector2i p_atlas_coords) const;
|
||||||
|
|
||||||
|
virtual int get_tiles_count() const override;
|
||||||
|
virtual Vector2i get_tile_id(int p_index) const override;
|
||||||
|
|
||||||
|
bool has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_size, int p_animation_columns, Vector2i p_animation_separation, int p_frames_count, Vector2i p_ignored_tile = INVALID_ATLAS_COORDS) const;
|
||||||
|
PoolVector2Array get_tiles_to_be_removed_on_change(Ref<Texture> p_texture, Vector2i p_margins, Vector2i p_separation, Vector2i p_texture_region_size);
|
||||||
|
Vector2i get_tile_at_coords(Vector2i p_atlas_coords) const;
|
||||||
|
|
||||||
|
// Animation.
|
||||||
|
void set_tile_animation_columns(const Vector2i p_atlas_coords, int p_frame_columns);
|
||||||
|
int get_tile_animation_columns(const Vector2i p_atlas_coords) const;
|
||||||
|
void set_tile_animation_separation(const Vector2i p_atlas_coords, const Vector2i p_separation);
|
||||||
|
Vector2i get_tile_animation_separation(const Vector2i p_atlas_coords) const;
|
||||||
|
void set_tile_animation_speed(const Vector2i p_atlas_coords, real_t p_speed);
|
||||||
|
real_t get_tile_animation_speed(const Vector2i p_atlas_coords) const;
|
||||||
|
void set_tile_animation_frames_count(const Vector2i p_atlas_coords, int p_frames_count);
|
||||||
|
int get_tile_animation_frames_count(const Vector2i p_atlas_coords) const;
|
||||||
|
void set_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index, real_t p_duration);
|
||||||
|
real_t get_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index) const;
|
||||||
|
real_t get_tile_animation_total_duration(const Vector2i p_atlas_coords) const;
|
||||||
|
|
||||||
|
// Alternative tiles.
|
||||||
|
int create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override = -1);
|
||||||
|
void remove_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile);
|
||||||
|
void set_alternative_tile_id(const Vector2i p_atlas_coords, int p_alternative_tile, int p_new_id);
|
||||||
|
virtual bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override;
|
||||||
|
int get_next_alternative_tile_id(const Vector2i p_atlas_coords) const;
|
||||||
|
|
||||||
|
virtual int get_alternative_tiles_count(const Vector2i p_atlas_coords) const override;
|
||||||
|
virtual int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override;
|
||||||
|
|
||||||
|
// Get data associated to a tile.
|
||||||
|
Object *get_tile_data(const Vector2i p_atlas_coords, int p_alternative_tile) const;
|
||||||
|
|
||||||
|
// Helpers.
|
||||||
|
Vector2i get_atlas_grid_size() const;
|
||||||
|
Rect2i get_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const;
|
||||||
|
Vector2i get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const;
|
||||||
|
|
||||||
|
// Getters for texture and tile region (padded or not)
|
||||||
|
Ref<Texture> get_runtime_texture() const;
|
||||||
|
Rect2i get_runtime_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const;
|
||||||
|
|
||||||
|
~RTileSetAtlasSource();
|
||||||
|
};
|
||||||
|
|
||||||
|
class TileSetScenesCollectionSource : public RTileSetSource {
|
||||||
|
GDCLASS(TileSetScenesCollectionSource, RTileSetSource);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct SceneData {
|
||||||
|
Ref<PackedScene> scene;
|
||||||
|
bool display_placeholder = false;
|
||||||
|
};
|
||||||
|
Vector<int> scenes_ids;
|
||||||
|
Map<int, SceneData> scenes;
|
||||||
|
int next_scene_id = 1;
|
||||||
|
|
||||||
|
void _compute_next_alternative_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:
|
||||||
|
// Tiles.
|
||||||
|
int get_tiles_count() const override;
|
||||||
|
Vector2i get_tile_id(int p_tile_index) const override;
|
||||||
|
bool has_tile(Vector2i p_atlas_coords) const override;
|
||||||
|
|
||||||
|
// Alternative tiles.
|
||||||
|
int get_alternative_tiles_count(const Vector2i p_atlas_coords) const override;
|
||||||
|
int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override;
|
||||||
|
bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override;
|
||||||
|
|
||||||
|
// Scenes accessors. Lot are similar to "Alternative tiles".
|
||||||
|
int get_scene_tiles_count() { return get_alternative_tiles_count(Vector2i()); }
|
||||||
|
int get_scene_tile_id(int p_index) { return get_alternative_tile_id(Vector2i(), p_index); };
|
||||||
|
bool has_scene_tile_id(int p_id) { return has_alternative_tile(Vector2i(), p_id); };
|
||||||
|
int create_scene_tile(Ref<PackedScene> p_packed_scene = Ref<PackedScene>(), int p_id_override = -1);
|
||||||
|
void set_scene_tile_id(int p_id, int p_new_id);
|
||||||
|
void set_scene_tile_scene(int p_id, Ref<PackedScene> p_packed_scene);
|
||||||
|
Ref<PackedScene> get_scene_tile_scene(int p_id) const;
|
||||||
|
void set_scene_tile_display_placeholder(int p_id, bool p_packed_scene);
|
||||||
|
bool get_scene_tile_display_placeholder(int p_id) const;
|
||||||
|
void remove_scene_tile(int p_id);
|
||||||
|
int get_next_scene_tile_id() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RTileData : public Object {
|
||||||
|
GDCLASS(RTileData, Object);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const RTileSet *tile_set = nullptr;
|
||||||
|
bool allow_transform = true;
|
||||||
|
|
||||||
|
// Rendering
|
||||||
|
bool flip_h = false;
|
||||||
|
bool flip_v = false;
|
||||||
|
bool transpose = false;
|
||||||
|
Vector2i tex_offset = Vector2i();
|
||||||
|
Ref<ShaderMaterial> material = Ref<ShaderMaterial>();
|
||||||
|
Color modulate = Color(1.0, 1.0, 1.0, 1.0);
|
||||||
|
int z_index = 0;
|
||||||
|
int y_sort_origin = 0;
|
||||||
|
Vector<Ref<OccluderPolygon2D>> occluders;
|
||||||
|
|
||||||
|
// Physics
|
||||||
|
struct PhysicsLayerTileData {
|
||||||
|
struct PolygonShapeTileData {
|
||||||
|
LocalVector<Vector2> polygon;
|
||||||
|
LocalVector<Ref<ConvexPolygonShape2D>> shapes;
|
||||||
|
bool one_way = false;
|
||||||
|
float one_way_margin = 1.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector2 linear_velocity;
|
||||||
|
double angular_velocity = 0.0;
|
||||||
|
Vector<PolygonShapeTileData> polygons;
|
||||||
|
};
|
||||||
|
Vector<PhysicsLayerTileData> physics;
|
||||||
|
// TODO add support for areas.
|
||||||
|
|
||||||
|
// Terrain
|
||||||
|
int terrain_set = -1;
|
||||||
|
int terrain_peering_bits[16] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
Vector<Ref<NavigationPolygon>> navigation;
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
double probability = 1.0;
|
||||||
|
|
||||||
|
// Custom data
|
||||||
|
Vector<Variant> custom_data;
|
||||||
|
|
||||||
|
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:
|
||||||
|
// Not exposed.
|
||||||
|
void set_tile_set(const RTileSet *p_tile_set);
|
||||||
|
void notify_tile_data_properties_should_change();
|
||||||
|
void add_occlusion_layer(int p_index);
|
||||||
|
void move_occlusion_layer(int p_from_index, int p_to_pos);
|
||||||
|
void remove_occlusion_layer(int p_index);
|
||||||
|
void add_physics_layer(int p_index);
|
||||||
|
void move_physics_layer(int p_from_index, int p_to_pos);
|
||||||
|
void remove_physics_layer(int p_index);
|
||||||
|
void add_terrain_set(int p_index);
|
||||||
|
void move_terrain_set(int p_from_index, int p_to_pos);
|
||||||
|
void remove_terrain_set(int p_index);
|
||||||
|
void add_terrain(int p_terrain_set, int p_index);
|
||||||
|
void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos);
|
||||||
|
void remove_terrain(int p_terrain_set, int p_index);
|
||||||
|
void add_navigation_layer(int p_index);
|
||||||
|
void move_navigation_layer(int p_from_index, int p_to_pos);
|
||||||
|
void remove_navigation_layer(int p_index);
|
||||||
|
void add_custom_data_layer(int p_index);
|
||||||
|
void move_custom_data_layer(int p_from_index, int p_to_pos);
|
||||||
|
void remove_custom_data_layer(int p_index);
|
||||||
|
void reset_state();
|
||||||
|
void set_allow_transform(bool p_allow_transform);
|
||||||
|
bool is_allowing_transform() const;
|
||||||
|
|
||||||
|
// To duplicate a TileData object, needed for runtiume update.
|
||||||
|
RTileData *duplicate();
|
||||||
|
|
||||||
|
// Rendering
|
||||||
|
void set_flip_h(bool p_flip_h);
|
||||||
|
bool get_flip_h() const;
|
||||||
|
void set_flip_v(bool p_flip_v);
|
||||||
|
bool get_flip_v() const;
|
||||||
|
void set_transpose(bool p_transpose);
|
||||||
|
bool get_transpose() const;
|
||||||
|
|
||||||
|
void set_texture_offset(Vector2i p_texture_offset);
|
||||||
|
Vector2i get_texture_offset() const;
|
||||||
|
void set_material(Ref<ShaderMaterial> p_material);
|
||||||
|
Ref<ShaderMaterial> get_material() const;
|
||||||
|
void set_modulate(Color p_modulate);
|
||||||
|
Color get_modulate() const;
|
||||||
|
void set_z_index(int p_z_index);
|
||||||
|
int get_z_index() const;
|
||||||
|
void set_y_sort_origin(int p_y_sort_origin);
|
||||||
|
int get_y_sort_origin() const;
|
||||||
|
|
||||||
|
void set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon);
|
||||||
|
Ref<OccluderPolygon2D> get_occluder(int p_layer_id) const;
|
||||||
|
|
||||||
|
// Physics
|
||||||
|
void set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity);
|
||||||
|
Vector2 get_constant_linear_velocity(int p_layer_id) const;
|
||||||
|
void set_constant_angular_velocity(int p_layer_id, real_t p_velocity);
|
||||||
|
real_t get_constant_angular_velocity(int p_layer_id) const;
|
||||||
|
void set_collision_polygons_count(int p_layer_id, int p_shapes_count);
|
||||||
|
int get_collision_polygons_count(int p_layer_id) const;
|
||||||
|
void add_collision_polygon(int p_layer_id);
|
||||||
|
void remove_collision_polygon(int p_layer_id, int p_polygon_index);
|
||||||
|
void set_collision_polygon_points(int p_layer_id, int p_polygon_index, Vector<Vector2> p_polygon);
|
||||||
|
Vector<Vector2> get_collision_polygon_points(int p_layer_id, int p_polygon_index) const;
|
||||||
|
void set_collision_polygon_one_way(int p_layer_id, int p_polygon_index, bool p_one_way);
|
||||||
|
bool is_collision_polygon_one_way(int p_layer_id, int p_polygon_index) const;
|
||||||
|
void set_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index, float p_one_way_margin);
|
||||||
|
float get_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index) const;
|
||||||
|
int get_collision_polygon_shapes_count(int p_layer_id, int p_polygon_index) const;
|
||||||
|
Ref<ConvexPolygonShape2D> get_collision_polygon_shape(int p_layer_id, int p_polygon_index, int shape_index) const;
|
||||||
|
|
||||||
|
// Terrain
|
||||||
|
void set_terrain_set(int p_terrain_id);
|
||||||
|
int get_terrain_set() const;
|
||||||
|
void set_peering_bit_terrain(RTileSet::CellNeighbor p_peering_bit, int p_terrain_id);
|
||||||
|
int get_peering_bit_terrain(RTileSet::CellNeighbor p_peering_bit) const;
|
||||||
|
bool is_valid_peering_bit_terrain(RTileSet::CellNeighbor p_peering_bit) const;
|
||||||
|
|
||||||
|
RTileSet::TerrainsPattern get_terrains_pattern() const; // Not exposed.
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
void set_navigation_polygon(int p_layer_id, Ref<NavigationPolygon> p_navigation_polygon);
|
||||||
|
Ref<NavigationPolygon> get_navigation_polygon(int p_layer_id) const;
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
void set_probability(float p_probability);
|
||||||
|
float get_probability() const;
|
||||||
|
|
||||||
|
// Custom data.
|
||||||
|
void set_custom_data(String p_layer_name, Variant p_value);
|
||||||
|
Variant get_custom_data(String p_layer_name) const;
|
||||||
|
void set_custom_data_by_layer_id(int p_layer_id, Variant p_value);
|
||||||
|
Variant get_custom_data_by_layer_id(int p_layer_id) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
VARIANT_ENUM_CAST(RTileSet::CellNeighbor);
|
||||||
|
VARIANT_ENUM_CAST(RTileSet::TerrainMode);
|
||||||
|
VARIANT_ENUM_CAST(RTileSet::TileShape);
|
||||||
|
VARIANT_ENUM_CAST(RTileSet::TileLayout);
|
||||||
|
VARIANT_ENUM_CAST(RTileSet::TileOffsetAxis);
|
||||||
|
|
||||||
|
#endif // TILE_SET_H
|
628
stb_rect_pack.h
Normal file
628
stb_rect_pack.h
Normal file
@ -0,0 +1,628 @@
|
|||||||
|
// stb_rect_pack.h - v1.00 - public domain - rectangle packing
|
||||||
|
// Sean Barrett 2014
|
||||||
|
//
|
||||||
|
// Useful for e.g. packing rectangular textures into an atlas.
|
||||||
|
// Does not do rotation.
|
||||||
|
//
|
||||||
|
// Not necessarily the awesomest packing method, but better than
|
||||||
|
// the totally naive one in stb_truetype (which is primarily what
|
||||||
|
// this is meant to replace).
|
||||||
|
//
|
||||||
|
// Has only had a few tests run, may have issues.
|
||||||
|
//
|
||||||
|
// More docs to come.
|
||||||
|
//
|
||||||
|
// No memory allocations; uses qsort() and assert() from stdlib.
|
||||||
|
// Can override those by defining STBRP_SORT and STBRP_ASSERT.
|
||||||
|
//
|
||||||
|
// This library currently uses the Skyline Bottom-Left algorithm.
|
||||||
|
//
|
||||||
|
// Please note: better rectangle packers are welcome! Please
|
||||||
|
// implement them to the same API, but with a different init
|
||||||
|
// function.
|
||||||
|
//
|
||||||
|
// Credits
|
||||||
|
//
|
||||||
|
// Library
|
||||||
|
// Sean Barrett
|
||||||
|
// Minor features
|
||||||
|
// Martins Mozeiko
|
||||||
|
// github:IntellectualKitty
|
||||||
|
//
|
||||||
|
// Bugfixes / warning fixes
|
||||||
|
// Jeremy Jaussaud
|
||||||
|
// Fabian Giesen
|
||||||
|
//
|
||||||
|
// Version history:
|
||||||
|
//
|
||||||
|
// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
|
||||||
|
// 0.99 (2019-02-07) warning fixes
|
||||||
|
// 0.11 (2017-03-03) return packing success/fail result
|
||||||
|
// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
|
||||||
|
// 0.09 (2016-08-27) fix compiler warnings
|
||||||
|
// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
|
||||||
|
// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
|
||||||
|
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
|
||||||
|
// 0.05: added STBRP_ASSERT to allow replacing assert
|
||||||
|
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
|
||||||
|
// 0.01: initial release
|
||||||
|
//
|
||||||
|
// LICENSE
|
||||||
|
//
|
||||||
|
// See end of file for license information.
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// INCLUDE SECTION
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef STB_INCLUDE_STB_RECT_PACK_H
|
||||||
|
#define STB_INCLUDE_STB_RECT_PACK_H
|
||||||
|
|
||||||
|
#define STB_RECT_PACK_VERSION 1
|
||||||
|
|
||||||
|
#ifdef STBRP_STATIC
|
||||||
|
#define STBRP_DEF static
|
||||||
|
#else
|
||||||
|
#define STBRP_DEF extern
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct stbrp_context stbrp_context;
|
||||||
|
typedef struct stbrp_node stbrp_node;
|
||||||
|
typedef struct stbrp_rect stbrp_rect;
|
||||||
|
|
||||||
|
#ifdef STBRP_LARGE_RECTS
|
||||||
|
typedef int stbrp_coord;
|
||||||
|
#else
|
||||||
|
typedef unsigned short stbrp_coord;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
|
||||||
|
// Assign packed locations to rectangles. The rectangles are of type
|
||||||
|
// 'stbrp_rect' defined below, stored in the array 'rects', and there
|
||||||
|
// are 'num_rects' many of them.
|
||||||
|
//
|
||||||
|
// Rectangles which are successfully packed have the 'was_packed' flag
|
||||||
|
// set to a non-zero value and 'x' and 'y' store the minimum location
|
||||||
|
// on each axis (i.e. bottom-left in cartesian coordinates, top-left
|
||||||
|
// if you imagine y increasing downwards). Rectangles which do not fit
|
||||||
|
// have the 'was_packed' flag set to 0.
|
||||||
|
//
|
||||||
|
// You should not try to access the 'rects' array from another thread
|
||||||
|
// while this function is running, as the function temporarily reorders
|
||||||
|
// the array while it executes.
|
||||||
|
//
|
||||||
|
// To pack into another rectangle, you need to call stbrp_init_target
|
||||||
|
// again. To continue packing into the same rectangle, you can call
|
||||||
|
// this function again. Calling this multiple times with multiple rect
|
||||||
|
// arrays will probably produce worse packing results than calling it
|
||||||
|
// a single time with the full rectangle array, but the option is
|
||||||
|
// available.
|
||||||
|
//
|
||||||
|
// The function returns 1 if all of the rectangles were successfully
|
||||||
|
// packed and 0 otherwise.
|
||||||
|
|
||||||
|
struct stbrp_rect
|
||||||
|
{
|
||||||
|
// reserved for your use:
|
||||||
|
int id;
|
||||||
|
|
||||||
|
// input:
|
||||||
|
stbrp_coord w, h;
|
||||||
|
|
||||||
|
// output:
|
||||||
|
stbrp_coord x, y;
|
||||||
|
int was_packed; // non-zero if valid packing
|
||||||
|
|
||||||
|
}; // 16 bytes, nominally
|
||||||
|
|
||||||
|
|
||||||
|
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
|
||||||
|
// Initialize a rectangle packer to:
|
||||||
|
// pack a rectangle that is 'width' by 'height' in dimensions
|
||||||
|
// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
|
||||||
|
//
|
||||||
|
// You must call this function every time you start packing into a new target.
|
||||||
|
//
|
||||||
|
// There is no "shutdown" function. The 'nodes' memory must stay valid for
|
||||||
|
// the following stbrp_pack_rects() call (or calls), but can be freed after
|
||||||
|
// the call (or calls) finish.
|
||||||
|
//
|
||||||
|
// Note: to guarantee best results, either:
|
||||||
|
// 1. make sure 'num_nodes' >= 'width'
|
||||||
|
// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
|
||||||
|
//
|
||||||
|
// If you don't do either of the above things, widths will be quantized to multiples
|
||||||
|
// of small integers to guarantee the algorithm doesn't run out of temporary storage.
|
||||||
|
//
|
||||||
|
// If you do #2, then the non-quantized algorithm will be used, but the algorithm
|
||||||
|
// may run out of temporary storage and be unable to pack some rectangles.
|
||||||
|
|
||||||
|
STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
|
||||||
|
// Optionally call this function after init but before doing any packing to
|
||||||
|
// change the handling of the out-of-temp-memory scenario, described above.
|
||||||
|
// If you call init again, this will be reset to the default (false).
|
||||||
|
|
||||||
|
|
||||||
|
STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
|
||||||
|
// Optionally select which packing heuristic the library should use. Different
|
||||||
|
// heuristics will produce better/worse results for different data sets.
|
||||||
|
// If you call init again, this will be reset to the default.
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
STBRP_HEURISTIC_Skyline_default=0,
|
||||||
|
STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
|
||||||
|
STBRP_HEURISTIC_Skyline_BF_sortHeight
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// the details of the following structures don't matter to you, but they must
|
||||||
|
// be visible so you can handle the memory allocations for them
|
||||||
|
|
||||||
|
struct stbrp_node
|
||||||
|
{
|
||||||
|
stbrp_coord x,y;
|
||||||
|
stbrp_node *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct stbrp_context
|
||||||
|
{
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int align;
|
||||||
|
int init_mode;
|
||||||
|
int heuristic;
|
||||||
|
int num_nodes;
|
||||||
|
stbrp_node *active_head;
|
||||||
|
stbrp_node *free_head;
|
||||||
|
stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// IMPLEMENTATION SECTION
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifdef STB_RECT_PACK_IMPLEMENTATION
|
||||||
|
#ifndef STBRP_SORT
|
||||||
|
#include <stdlib.h>
|
||||||
|
#define STBRP_SORT qsort
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef STBRP_ASSERT
|
||||||
|
#include <assert.h>
|
||||||
|
#define STBRP_ASSERT assert
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define STBRP__NOTUSED(v) (void)(v)
|
||||||
|
#else
|
||||||
|
#define STBRP__NOTUSED(v) (void)sizeof(v)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
STBRP__INIT_skyline = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
|
||||||
|
{
|
||||||
|
switch (context->init_mode) {
|
||||||
|
case STBRP__INIT_skyline:
|
||||||
|
STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
|
||||||
|
context->heuristic = heuristic;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
STBRP_ASSERT(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
|
||||||
|
{
|
||||||
|
if (allow_out_of_mem)
|
||||||
|
// if it's ok to run out of memory, then don't bother aligning them;
|
||||||
|
// this gives better packing, but may fail due to OOM (even though
|
||||||
|
// the rectangles easily fit). @TODO a smarter approach would be to only
|
||||||
|
// quantize once we've hit OOM, then we could get rid of this parameter.
|
||||||
|
context->align = 1;
|
||||||
|
else {
|
||||||
|
// if it's not ok to run out of memory, then quantize the widths
|
||||||
|
// so that num_nodes is always enough nodes.
|
||||||
|
//
|
||||||
|
// I.e. num_nodes * align >= width
|
||||||
|
// align >= width / num_nodes
|
||||||
|
// align = ceil(width/num_nodes)
|
||||||
|
|
||||||
|
context->align = (context->width + context->num_nodes-1) / context->num_nodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
#ifndef STBRP_LARGE_RECTS
|
||||||
|
STBRP_ASSERT(width <= 0xffff && height <= 0xffff);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (i=0; i < num_nodes-1; ++i)
|
||||||
|
nodes[i].next = &nodes[i+1];
|
||||||
|
nodes[i].next = NULL;
|
||||||
|
context->init_mode = STBRP__INIT_skyline;
|
||||||
|
context->heuristic = STBRP_HEURISTIC_Skyline_default;
|
||||||
|
context->free_head = &nodes[0];
|
||||||
|
context->active_head = &context->extra[0];
|
||||||
|
context->width = width;
|
||||||
|
context->height = height;
|
||||||
|
context->num_nodes = num_nodes;
|
||||||
|
stbrp_setup_allow_out_of_mem(context, 0);
|
||||||
|
|
||||||
|
// node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
|
||||||
|
context->extra[0].x = 0;
|
||||||
|
context->extra[0].y = 0;
|
||||||
|
context->extra[0].next = &context->extra[1];
|
||||||
|
context->extra[1].x = (stbrp_coord) width;
|
||||||
|
#ifdef STBRP_LARGE_RECTS
|
||||||
|
context->extra[1].y = (1<<30);
|
||||||
|
#else
|
||||||
|
context->extra[1].y = 65535;
|
||||||
|
#endif
|
||||||
|
context->extra[1].next = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find minimum y position if it starts at x1
|
||||||
|
static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
|
||||||
|
{
|
||||||
|
stbrp_node *node = first;
|
||||||
|
int x1 = x0 + width;
|
||||||
|
int min_y, visited_width, waste_area;
|
||||||
|
|
||||||
|
STBRP__NOTUSED(c);
|
||||||
|
|
||||||
|
STBRP_ASSERT(first->x <= x0);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// skip in case we're past the node
|
||||||
|
while (node->next->x <= x0)
|
||||||
|
++node;
|
||||||
|
#else
|
||||||
|
STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
|
||||||
|
#endif
|
||||||
|
|
||||||
|
STBRP_ASSERT(node->x <= x0);
|
||||||
|
|
||||||
|
min_y = 0;
|
||||||
|
waste_area = 0;
|
||||||
|
visited_width = 0;
|
||||||
|
while (node->x < x1) {
|
||||||
|
if (node->y > min_y) {
|
||||||
|
// raise min_y higher.
|
||||||
|
// we've accounted for all waste up to min_y,
|
||||||
|
// but we'll now add more waste for everything we've visted
|
||||||
|
waste_area += visited_width * (node->y - min_y);
|
||||||
|
min_y = node->y;
|
||||||
|
// the first time through, visited_width might be reduced
|
||||||
|
if (node->x < x0)
|
||||||
|
visited_width += node->next->x - x0;
|
||||||
|
else
|
||||||
|
visited_width += node->next->x - node->x;
|
||||||
|
} else {
|
||||||
|
// add waste area
|
||||||
|
int under_width = node->next->x - node->x;
|
||||||
|
if (under_width + visited_width > width)
|
||||||
|
under_width = width - visited_width;
|
||||||
|
waste_area += under_width * (min_y - node->y);
|
||||||
|
visited_width += under_width;
|
||||||
|
}
|
||||||
|
node = node->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
*pwaste = waste_area;
|
||||||
|
return min_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int x,y;
|
||||||
|
stbrp_node **prev_link;
|
||||||
|
} stbrp__findresult;
|
||||||
|
|
||||||
|
static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
|
||||||
|
{
|
||||||
|
int best_waste = (1<<30), best_x, best_y = (1 << 30);
|
||||||
|
stbrp__findresult fr;
|
||||||
|
stbrp_node **prev, *node, *tail, **best = NULL;
|
||||||
|
|
||||||
|
// align to multiple of c->align
|
||||||
|
width = (width + c->align - 1);
|
||||||
|
width -= width % c->align;
|
||||||
|
STBRP_ASSERT(width % c->align == 0);
|
||||||
|
|
||||||
|
// if it can't possibly fit, bail immediately
|
||||||
|
if (width > c->width || height > c->height) {
|
||||||
|
fr.prev_link = NULL;
|
||||||
|
fr.x = fr.y = 0;
|
||||||
|
return fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = c->active_head;
|
||||||
|
prev = &c->active_head;
|
||||||
|
while (node->x + width <= c->width) {
|
||||||
|
int y,waste;
|
||||||
|
y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
|
||||||
|
if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
|
||||||
|
// bottom left
|
||||||
|
if (y < best_y) {
|
||||||
|
best_y = y;
|
||||||
|
best = prev;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// best-fit
|
||||||
|
if (y + height <= c->height) {
|
||||||
|
// can only use it if it first vertically
|
||||||
|
if (y < best_y || (y == best_y && waste < best_waste)) {
|
||||||
|
best_y = y;
|
||||||
|
best_waste = waste;
|
||||||
|
best = prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev = &node->next;
|
||||||
|
node = node->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
best_x = (best == NULL) ? 0 : (*best)->x;
|
||||||
|
|
||||||
|
// if doing best-fit (BF), we also have to try aligning right edge to each node position
|
||||||
|
//
|
||||||
|
// e.g, if fitting
|
||||||
|
//
|
||||||
|
// ____________________
|
||||||
|
// |____________________|
|
||||||
|
//
|
||||||
|
// into
|
||||||
|
//
|
||||||
|
// | |
|
||||||
|
// | ____________|
|
||||||
|
// |____________|
|
||||||
|
//
|
||||||
|
// then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
|
||||||
|
//
|
||||||
|
// This makes BF take about 2x the time
|
||||||
|
|
||||||
|
if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
|
||||||
|
tail = c->active_head;
|
||||||
|
node = c->active_head;
|
||||||
|
prev = &c->active_head;
|
||||||
|
// find first node that's admissible
|
||||||
|
while (tail->x < width)
|
||||||
|
tail = tail->next;
|
||||||
|
while (tail) {
|
||||||
|
int xpos = tail->x - width;
|
||||||
|
int y,waste;
|
||||||
|
STBRP_ASSERT(xpos >= 0);
|
||||||
|
// find the left position that matches this
|
||||||
|
while (node->next->x <= xpos) {
|
||||||
|
prev = &node->next;
|
||||||
|
node = node->next;
|
||||||
|
}
|
||||||
|
STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
|
||||||
|
y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
|
||||||
|
if (y + height <= c->height) {
|
||||||
|
if (y <= best_y) {
|
||||||
|
if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
|
||||||
|
best_x = xpos;
|
||||||
|
STBRP_ASSERT(y <= best_y);
|
||||||
|
best_y = y;
|
||||||
|
best_waste = waste;
|
||||||
|
best = prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tail = tail->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fr.prev_link = best;
|
||||||
|
fr.x = best_x;
|
||||||
|
fr.y = best_y;
|
||||||
|
return fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
|
||||||
|
{
|
||||||
|
// find best position according to heuristic
|
||||||
|
stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
|
||||||
|
stbrp_node *node, *cur;
|
||||||
|
|
||||||
|
// bail if:
|
||||||
|
// 1. it failed
|
||||||
|
// 2. the best node doesn't fit (we don't always check this)
|
||||||
|
// 3. we're out of memory
|
||||||
|
if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
|
||||||
|
res.prev_link = NULL;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// on success, create new node
|
||||||
|
node = context->free_head;
|
||||||
|
node->x = (stbrp_coord) res.x;
|
||||||
|
node->y = (stbrp_coord) (res.y + height);
|
||||||
|
|
||||||
|
context->free_head = node->next;
|
||||||
|
|
||||||
|
// insert the new node into the right starting point, and
|
||||||
|
// let 'cur' point to the remaining nodes needing to be
|
||||||
|
// stiched back in
|
||||||
|
|
||||||
|
cur = *res.prev_link;
|
||||||
|
if (cur->x < res.x) {
|
||||||
|
// preserve the existing one, so start testing with the next one
|
||||||
|
stbrp_node *next = cur->next;
|
||||||
|
cur->next = node;
|
||||||
|
cur = next;
|
||||||
|
} else {
|
||||||
|
*res.prev_link = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// from here, traverse cur and free the nodes, until we get to one
|
||||||
|
// that shouldn't be freed
|
||||||
|
while (cur->next && cur->next->x <= res.x + width) {
|
||||||
|
stbrp_node *next = cur->next;
|
||||||
|
// move the current node to the free list
|
||||||
|
cur->next = context->free_head;
|
||||||
|
context->free_head = cur;
|
||||||
|
cur = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stitch the list back in
|
||||||
|
node->next = cur;
|
||||||
|
|
||||||
|
if (cur->x < res.x + width)
|
||||||
|
cur->x = (stbrp_coord) (res.x + width);
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
cur = context->active_head;
|
||||||
|
while (cur->x < context->width) {
|
||||||
|
STBRP_ASSERT(cur->x < cur->next->x);
|
||||||
|
cur = cur->next;
|
||||||
|
}
|
||||||
|
STBRP_ASSERT(cur->next == NULL);
|
||||||
|
|
||||||
|
{
|
||||||
|
int count=0;
|
||||||
|
cur = context->active_head;
|
||||||
|
while (cur) {
|
||||||
|
cur = cur->next;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
cur = context->free_head;
|
||||||
|
while (cur) {
|
||||||
|
cur = cur->next;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
STBRP_ASSERT(count == context->num_nodes+2);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rect_height_compare(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
const stbrp_rect *p = (const stbrp_rect *) a;
|
||||||
|
const stbrp_rect *q = (const stbrp_rect *) b;
|
||||||
|
if (p->h > q->h)
|
||||||
|
return -1;
|
||||||
|
if (p->h < q->h)
|
||||||
|
return 1;
|
||||||
|
return (p->w > q->w) ? -1 : (p->w < q->w);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rect_original_order(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
const stbrp_rect *p = (const stbrp_rect *) a;
|
||||||
|
const stbrp_rect *q = (const stbrp_rect *) b;
|
||||||
|
return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef STBRP_LARGE_RECTS
|
||||||
|
#define STBRP__MAXVAL 0xffffffff
|
||||||
|
#else
|
||||||
|
#define STBRP__MAXVAL 0xffff
|
||||||
|
#endif
|
||||||
|
|
||||||
|
STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
|
||||||
|
{
|
||||||
|
int i, all_rects_packed = 1;
|
||||||
|
|
||||||
|
// we use the 'was_packed' field internally to allow sorting/unsorting
|
||||||
|
for (i=0; i < num_rects; ++i) {
|
||||||
|
rects[i].was_packed = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort according to heuristic
|
||||||
|
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
|
||||||
|
|
||||||
|
for (i=0; i < num_rects; ++i) {
|
||||||
|
if (rects[i].w == 0 || rects[i].h == 0) {
|
||||||
|
rects[i].x = rects[i].y = 0; // empty rect needs no space
|
||||||
|
} else {
|
||||||
|
stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
|
||||||
|
if (fr.prev_link) {
|
||||||
|
rects[i].x = (stbrp_coord) fr.x;
|
||||||
|
rects[i].y = (stbrp_coord) fr.y;
|
||||||
|
} else {
|
||||||
|
rects[i].x = rects[i].y = STBRP__MAXVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsort
|
||||||
|
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
|
||||||
|
|
||||||
|
// set was_packed flags and all_rects_packed status
|
||||||
|
for (i=0; i < num_rects; ++i) {
|
||||||
|
rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
|
||||||
|
if (!rects[i].was_packed)
|
||||||
|
all_rects_packed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the all_rects_packed status
|
||||||
|
return all_rects_packed;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
This software is available under 2 licenses -- choose whichever you prefer.
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
ALTERNATIVE A - MIT License
|
||||||
|
Copyright (c) 2017 Sean Barrett
|
||||||
|
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.
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||||||
|
software, either in source code form or as a compiled binary, for any purpose,
|
||||||
|
commercial or non-commercial, and by any means.
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||||
|
software dedicate any and all copyright interest in the software to the public
|
||||||
|
domain. We make this dedication for the benefit of the public at large and to
|
||||||
|
the detriment of our heirs and successors. We intend this dedication to be an
|
||||||
|
overt act of relinquishment in perpetuity of all present and future rights to
|
||||||
|
this software under copyright law.
|
||||||
|
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 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.
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
*/
|
5
tile_editor/SCsub
Normal file
5
tile_editor/SCsub
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
Import("env")
|
||||||
|
|
||||||
|
env.add_source_files(env.editor_sources, "*.cpp")
|
323
tile_editor/atlas_merging_dialog.cpp
Normal file
323
tile_editor/atlas_merging_dialog.cpp
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* atlas_merging_dialog.cpp */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#include "atlas_merging_dialog.h"
|
||||||
|
|
||||||
|
#include "editor/editor_scale.h"
|
||||||
|
|
||||||
|
#include "scene/gui/control.h"
|
||||||
|
#include "scene/gui/split_container.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(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;
|
||||||
|
output_image.instantiate();
|
||||||
|
output_image->create(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++) {
|
||||||
|
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 merged TileSetAtlasSource.
|
||||||
|
Vector2i atlas_offset;
|
||||||
|
int line_height = 0;
|
||||||
|
for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {
|
||||||
|
Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index];
|
||||||
|
merged_mapping.push_back(Map<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_altas = Rect2i(atlas_offset + tile_id, atlas_source->get_tile_size_in_atlas(tile_id));
|
||||||
|
|
||||||
|
// Create tiles and alternatives, then copy their properties.
|
||||||
|
for (int alternative_index = 0; alternative_index < atlas_source->get_alternative_tiles_count(tile_id); alternative_index++) {
|
||||||
|
int alternative_id = atlas_source->get_alternative_tile_id(tile_id, alternative_index);
|
||||||
|
if (alternative_id == 0) {
|
||||||
|
merged->create_tile(new_tile_rect_in_altas.position, new_tile_rect_in_altas.size);
|
||||||
|
} else {
|
||||||
|
merged->create_alternative_tile(new_tile_rect_in_altas.position, alternative_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the properties.
|
||||||
|
TileData *original_tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id));
|
||||||
|
List<PropertyInfo> properties;
|
||||||
|
original_tile_data->get_property_list(&properties);
|
||||||
|
for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
|
||||||
|
const StringName &property_name = E->get().name;
|
||||||
|
merged->set(property_name, original_tile_data->get(property_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to the mapping.
|
||||||
|
merged_mapping[source_index][tile_id] = new_tile_rect_in_altas.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
Rect2 dst_rect_wide = Rect2i(new_tile_rect_in_altas.position * new_texture_region_size, new_tile_rect_in_altas.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(atlas_source->get_texture()->get_image(), src_rect, dst_rect_wide.get_center() - src_rect.size / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref<ImageTexture> output_image_texture;
|
||||||
|
output_image_texture.instantiate();
|
||||||
|
output_image_texture->create_from_image(output_image);
|
||||||
|
|
||||||
|
merged->set_name(p_atlas_sources[0]->get_name());
|
||||||
|
merged->set_texture(output_image_texture);
|
||||||
|
merged->set_texture_region_size(new_texture_region_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(String p_path) {
|
||||||
|
ERR_FAIL_COND(!merged.is_valid());
|
||||||
|
|
||||||
|
Ref<ImageTexture> output_image_texture = merged->get_texture();
|
||||||
|
output_image_texture->get_image()->save_png(p_path);
|
||||||
|
|
||||||
|
Ref<Texture2D> new_texture_resource = ResourceLoader::load(p_path, "Texture2D");
|
||||||
|
merged->set_texture(new_texture_resource);
|
||||||
|
|
||||||
|
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();
|
||||||
|
commited_actions_count++;
|
||||||
|
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AtlasMergingDialog::ok_pressed() {
|
||||||
|
delete_original_atlases = false;
|
||||||
|
editor_file_dialog->popup_file_dialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AtlasMergingDialog::cancel_pressed() {
|
||||||
|
for (int i = 0; i < commited_actions_count; i++) {
|
||||||
|
undo_redo->undo();
|
||||||
|
}
|
||||||
|
commited_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::update_tile_set(Ref<TileSet> p_tile_set) {
|
||||||
|
ERR_FAIL_COND(!p_tile_set.is_valid());
|
||||||
|
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("%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(atlas_merging_atlases_list->get_item_count() - 1, source_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_ok_button()->set_disabled(true);
|
||||||
|
merge_button->set_disabled(true);
|
||||||
|
|
||||||
|
commited_actions_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
AtlasMergingDialog::AtlasMergingDialog() {
|
||||||
|
// Atlas merging window.
|
||||||
|
set_title(TTR("Atlas Merging"));
|
||||||
|
set_hide_on_ok(false);
|
||||||
|
|
||||||
|
// Ok buttons
|
||||||
|
get_ok_button()->set_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_fixed_icon_size(Size2i(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);
|
||||||
|
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->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_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(true);
|
||||||
|
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_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);
|
||||||
|
}
|
86
tile_editor/atlas_merging_dialog.h
Normal file
86
tile_editor/atlas_merging_dialog.h
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* atlas_merging_dialog.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef ATLAS_MERGING_DIALOG_H
|
||||||
|
#define ATLAS_MERGING_DIALOG_H
|
||||||
|
|
||||||
|
#include "editor/editor_node.h"
|
||||||
|
#include "editor/editor_properties.h"
|
||||||
|
|
||||||
|
#include "scene/gui/dialogs.h"
|
||||||
|
#include "scene/gui/item_list.h"
|
||||||
|
#include "scene/gui/texture_rect.h"
|
||||||
|
#include "scene/resources/tile_set.h"
|
||||||
|
|
||||||
|
class AtlasMergingDialog : public ConfirmationDialog {
|
||||||
|
GDCLASS(AtlasMergingDialog, ConfirmationDialog);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int commited_actions_count = 0;
|
||||||
|
bool delete_original_atlases = true;
|
||||||
|
Ref<TileSetAtlasSource> merged;
|
||||||
|
LocalVector<Map<Vector2i, Vector2i>> merged_mapping;
|
||||||
|
Ref<TileSet> tile_set;
|
||||||
|
|
||||||
|
UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
|
||||||
|
|
||||||
|
// Settings.
|
||||||
|
int next_line_after_column = 30;
|
||||||
|
|
||||||
|
// GUI.
|
||||||
|
ItemList *atlas_merging_atlases_list;
|
||||||
|
EditorPropertyVector2i *texture_region_size_editor_property;
|
||||||
|
EditorPropertyInteger *columns_editor_property;
|
||||||
|
TextureRect *preview;
|
||||||
|
Label *select_2_atlases_label;
|
||||||
|
EditorFileDialog *editor_file_dialog;
|
||||||
|
Button *merge_button;
|
||||||
|
|
||||||
|
void _property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing);
|
||||||
|
|
||||||
|
void _generate_merged(Vector<Ref<TileSetAtlasSource>> p_atlas_sources, int p_max_columns);
|
||||||
|
void _update_texture();
|
||||||
|
void _merge_confirmed(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;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void update_tile_set(Ref<TileSet> p_tile_set);
|
||||||
|
|
||||||
|
AtlasMergingDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ATLAS_MERGING_DIALOG_H
|
687
tile_editor/tile_atlas_view.cpp
Normal file
687
tile_editor/tile_atlas_view.cpp
Normal file
@ -0,0 +1,687 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tile_atlas_view.cpp */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#include "tile_atlas_view.h"
|
||||||
|
|
||||||
|
#include "core/input/input.h"
|
||||||
|
#include "core/os/keyboard.h"
|
||||||
|
#include "scene/2d/tile_map.h"
|
||||||
|
#include "scene/gui/box_container.h"
|
||||||
|
#include "scene/gui/label.h"
|
||||||
|
#include "scene/gui/panel.h"
|
||||||
|
#include "scene/gui/texture_rect.h"
|
||||||
|
|
||||||
|
#include "editor/editor_scale.h"
|
||||||
|
#include "editor/editor_settings.h"
|
||||||
|
|
||||||
|
void TileAtlasView::gui_input(const Ref<InputEvent> &p_event) {
|
||||||
|
Ref<InputEventMouseButton> mb = p_event;
|
||||||
|
if (mb.is_valid()) {
|
||||||
|
drag_type = DRAG_TYPE_NONE;
|
||||||
|
|
||||||
|
Vector2i scroll_vec = Vector2((mb->get_button_index() == MouseButton::WHEEL_LEFT) - (mb->get_button_index() == MouseButton::WHEEL_RIGHT), (mb->get_button_index() == MouseButton::WHEEL_UP) - (mb->get_button_index() == MouseButton::WHEEL_DOWN));
|
||||||
|
if (scroll_vec != Vector2()) {
|
||||||
|
if (mb->is_ctrl_pressed()) {
|
||||||
|
if (mb->is_shift_pressed()) {
|
||||||
|
panning.x += 32 * mb->get_factor() * scroll_vec.y;
|
||||||
|
panning.y += 32 * mb->get_factor() * scroll_vec.x;
|
||||||
|
} else {
|
||||||
|
panning.y += 32 * mb->get_factor() * scroll_vec.y;
|
||||||
|
panning.x += 32 * mb->get_factor() * scroll_vec.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
|
||||||
|
_update_zoom_and_panning(true);
|
||||||
|
accept_event();
|
||||||
|
|
||||||
|
} else if (!mb->is_shift_pressed()) {
|
||||||
|
zoom_widget->set_zoom_by_increments(scroll_vec.y * 2);
|
||||||
|
emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
|
||||||
|
_update_zoom_and_panning(true);
|
||||||
|
accept_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mb->get_button_index() == MouseButton::MIDDLE || mb->get_button_index() == MouseButton::RIGHT) {
|
||||||
|
if (mb->is_pressed()) {
|
||||||
|
drag_type = DRAG_TYPE_PAN;
|
||||||
|
} else {
|
||||||
|
drag_type = DRAG_TYPE_NONE;
|
||||||
|
}
|
||||||
|
accept_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref<InputEventMouseMotion> mm = p_event;
|
||||||
|
if (mm.is_valid()) {
|
||||||
|
if (drag_type == DRAG_TYPE_PAN) {
|
||||||
|
panning += mm->get_relative();
|
||||||
|
_update_zoom_and_panning();
|
||||||
|
emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
|
||||||
|
accept_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Size2i TileAtlasView::_compute_base_tiles_control_size() {
|
||||||
|
// 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() {
|
||||||
|
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 = Object::cast_to<TileData>(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) {
|
||||||
|
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->update();
|
||||||
|
background_right->update();
|
||||||
|
|
||||||
|
// Zoom on the position.
|
||||||
|
if (p_zoom_on_mouse_pos) {
|
||||||
|
// Offset the panning relative to the center of panel.
|
||||||
|
Vector2 relative_mpos = get_local_mouse_position() - 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_equal_approx(Vector2()));
|
||||||
|
|
||||||
|
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) {
|
||||||
|
base_tiles_root_control->set_tooltip("");
|
||||||
|
|
||||||
|
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(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: 0"), source_id, coords));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileAtlasView::_draw_base_tiles() {
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
Vector2i offset_pos = base_frame_rect.get_center() + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0);
|
||||||
|
|
||||||
|
// Draw the tile.
|
||||||
|
TileMap::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0, frame);
|
||||||
|
|
||||||
|
// Draw, the texture in the separation areas
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileAtlasView::_draw_base_tiles_texture_grid() {
|
||||||
|
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() {
|
||||||
|
// Draw the shapes.
|
||||||
|
Color grid_color = EditorSettings::get_singleton()->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_effective_texture_offset(tile_id, 0);
|
||||||
|
|
||||||
|
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);
|
||||||
|
Transform2D tile_xform;
|
||||||
|
tile_xform.set_origin(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("");
|
||||||
|
|
||||||
|
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(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: %d"), source_id, coords, alternative_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileAtlasView::_draw_alternatives() {
|
||||||
|
// 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 = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(atlas_coords, alternative_id));
|
||||||
|
bool transposed = tile_data->get_transpose();
|
||||||
|
|
||||||
|
// Update the y to max value.
|
||||||
|
Vector2i offset_pos = current_pos;
|
||||||
|
if (transposed) {
|
||||||
|
offset_pos = (current_pos + Vector2(texture_region_size.y, texture_region_size.x) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id));
|
||||||
|
y_increment = MAX(y_increment, texture_region_size.x);
|
||||||
|
} else {
|
||||||
|
offset_pos = (current_pos + texture_region_size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id));
|
||||||
|
y_increment = MAX(y_increment, texture_region_size.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the tile.
|
||||||
|
TileMap::draw_tile(alternatives_draw->get_canvas_item(), 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() {
|
||||||
|
Ref<Texture2D> texture = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons"));
|
||||||
|
background_left->set_size(base_tiles_root_control->get_custom_minimum_size());
|
||||||
|
background_left->draw_texture_rect(texture, Rect2(Vector2(), background_left->get_size()), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileAtlasView::_draw_background_right() {
|
||||||
|
Ref<Texture2D> texture = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons"));
|
||||||
|
background_right->set_size(alternative_tiles_root_control->get_custom_minimum_size());
|
||||||
|
background_right->draw_texture_rect(texture, 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) {
|
||||||
|
ERR_FAIL_COND(!p_tile_set);
|
||||||
|
ERR_FAIL_COND(!p_tile_set_atlas_source);
|
||||||
|
ERR_FAIL_COND(p_source_id < 0);
|
||||||
|
ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source);
|
||||||
|
|
||||||
|
tile_set = p_tile_set;
|
||||||
|
tile_set_atlas_source = 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();
|
||||||
|
|
||||||
|
// Change children control size.
|
||||||
|
Size2i base_tiles_control_size = _compute_base_tiles_control_size();
|
||||||
|
for (int i = 0; i < base_tiles_drawing_root->get_child_count(); i++) {
|
||||||
|
Control *control = Object::cast_to<Control>(base_tiles_drawing_root->get_child(i));
|
||||||
|
if (control) {
|
||||||
|
control->set_size(base_tiles_control_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Size2i alternative_control_size = _compute_alternative_tiles_control_size();
|
||||||
|
for (int i = 0; i < alternative_tiles_drawing_root->get_child_count(); i++) {
|
||||||
|
Control *control = Object::cast_to<Control>(alternative_tiles_drawing_root->get_child(i));
|
||||||
|
if (control) {
|
||||||
|
control->set_size(alternative_control_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update.
|
||||||
|
base_tiles_draw->update();
|
||||||
|
base_tiles_texture_grid->update();
|
||||||
|
base_tiles_shape_grid->update();
|
||||||
|
alternatives_draw->update();
|
||||||
|
background_left->update();
|
||||||
|
background_right->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
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) const {
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Compute index in atlas
|
||||||
|
Vector2 pos = p_pos - margins;
|
||||||
|
Vector2i ret = (pos / (texture_region_size + separation)).floor();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TileSetSource::INVALID_ATLAS_COORDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileAtlasView::_update_alternative_tiles_rect_cache() {
|
||||||
|
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 = Object::cast_to<TileData>(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] = Map<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, Map<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::update() {
|
||||||
|
base_tiles_draw->update();
|
||||||
|
base_tiles_texture_grid->update();
|
||||||
|
base_tiles_shape_grid->update();
|
||||||
|
alternatives_draw->update();
|
||||||
|
background_left->update();
|
||||||
|
background_right->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileAtlasView::_notification(int p_what) {
|
||||||
|
switch (p_what) {
|
||||||
|
case NOTIFICATION_READY:
|
||||||
|
button_center_view->set_icon(get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons")));
|
||||||
|
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_WIDE);
|
||||||
|
panel->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
panel->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
add_child(panel);
|
||||||
|
|
||||||
|
// Scrollingsc
|
||||||
|
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));
|
||||||
|
|
||||||
|
button_center_view = memnew(Button);
|
||||||
|
button_center_view->set_icon(get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons")));
|
||||||
|
button_center_view->set_anchors_and_offsets_preset(Control::PRESET_TOP_RIGHT, Control::PRESET_MODE_MINSIZE, 5);
|
||||||
|
button_center_view->connect("pressed", callable_mp(this, &TileAtlasView::_center_view));
|
||||||
|
button_center_view->set_flat(true);
|
||||||
|
button_center_view->set_disabled(true);
|
||||||
|
button_center_view->set_tooltip(TTR("Center View"));
|
||||||
|
add_child(button_center_view);
|
||||||
|
|
||||||
|
center_container = memnew(CenterContainer);
|
||||||
|
center_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
|
||||||
|
center_container->set_anchors_preset(Control::PRESET_CENTER);
|
||||||
|
center_container->connect("gui_input", callable_mp(this, &TileAtlasView::gui_input));
|
||||||
|
panel->add_child(center_container);
|
||||||
|
|
||||||
|
missing_source_label = memnew(Label);
|
||||||
|
missing_source_label->set_text(TTR("No atlas source with a valid texture selected."));
|
||||||
|
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("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_WIDE);
|
||||||
|
background_left->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED);
|
||||||
|
background_left->connect("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_anchors_and_offsets_preset(Control::PRESET_WIDE);
|
||||||
|
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_WIDE);
|
||||||
|
base_tiles_draw->connect("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_WIDE);
|
||||||
|
base_tiles_texture_grid->connect("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_WIDE);
|
||||||
|
base_tiles_shape_grid->connect("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->connect("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_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED);
|
||||||
|
background_right->connect("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->connect("draw", callable_mp(this, &TileAtlasView::_draw_alternatives));
|
||||||
|
alternative_tiles_drawing_root->add_child(alternatives_draw);
|
||||||
|
}
|
156
tile_editor/tile_atlas_view.h
Normal file
156
tile_editor/tile_atlas_view.h
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tile_atlas_view.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TILE_ATLAS_VIEW_H
|
||||||
|
#define TILE_ATLAS_VIEW_H
|
||||||
|
|
||||||
|
#include "editor/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/gui/scroll_container.h"
|
||||||
|
#include "scene/gui/texture_rect.h"
|
||||||
|
#include "scene/resources/tile_set.h"
|
||||||
|
|
||||||
|
class TileAtlasView : public Control {
|
||||||
|
GDCLASS(TileAtlasView, Control);
|
||||||
|
|
||||||
|
private:
|
||||||
|
TileSet *tile_set;
|
||||||
|
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;
|
||||||
|
Button *button_center_view;
|
||||||
|
CenterContainer *center_container;
|
||||||
|
Vector2 panning;
|
||||||
|
void _update_zoom_and_panning(bool p_zoom_on_mouse_pos = false);
|
||||||
|
void _zoom_widget_changed();
|
||||||
|
void _center_view();
|
||||||
|
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||||
|
|
||||||
|
Map<Vector2, Map<int, Rect2i>> alternative_tiles_rect_cache;
|
||||||
|
void _update_alternative_tiles_rect_cache();
|
||||||
|
|
||||||
|
MarginContainer *margin_container;
|
||||||
|
int margin_container_paddings[4] = { 0, 0, 0, 0 };
|
||||||
|
HBoxContainer *hbox;
|
||||||
|
Label *missing_source_label;
|
||||||
|
|
||||||
|
// Background
|
||||||
|
Control *background_left;
|
||||||
|
void _draw_background_left();
|
||||||
|
Control *background_right;
|
||||||
|
void _draw_background_right();
|
||||||
|
|
||||||
|
// Left side.
|
||||||
|
Control *base_tiles_root_control;
|
||||||
|
void _base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event);
|
||||||
|
|
||||||
|
Control *base_tiles_drawing_root;
|
||||||
|
|
||||||
|
Control *base_tiles_draw;
|
||||||
|
void _draw_base_tiles();
|
||||||
|
|
||||||
|
Control *base_tiles_texture_grid;
|
||||||
|
void _draw_base_tiles_texture_grid();
|
||||||
|
|
||||||
|
Control *base_tiles_shape_grid;
|
||||||
|
void _draw_base_tiles_shape_grid();
|
||||||
|
|
||||||
|
Size2i _compute_base_tiles_control_size();
|
||||||
|
|
||||||
|
// Right side.
|
||||||
|
Control *alternative_tiles_root_control;
|
||||||
|
void _alternative_tiles_root_control_gui_input(const Ref<InputEvent> &p_event);
|
||||||
|
|
||||||
|
Control *alternative_tiles_drawing_root;
|
||||||
|
|
||||||
|
Control *alternatives_draw;
|
||||||
|
void _draw_alternatives();
|
||||||
|
|
||||||
|
Size2i _compute_alternative_tiles_control_size();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
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) 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_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_mouse_filter(Control::MOUSE_FILTER_PASS);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update everything.
|
||||||
|
void update();
|
||||||
|
|
||||||
|
TileAtlasView();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TILE_ATLAS_VIEW
|
2624
tile_editor/tile_data_editors.cpp
Normal file
2624
tile_editor/tile_data_editors.cpp
Normal file
File diff suppressed because it is too large
Load Diff
417
tile_editor/tile_data_editors.h
Normal file
417
tile_editor/tile_data_editors.h
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tile_data_editors.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TILE_DATA_EDITORS_H
|
||||||
|
#define TILE_DATA_EDITORS_H
|
||||||
|
|
||||||
|
#include "tile_atlas_view.h"
|
||||||
|
|
||||||
|
#include "editor/editor_node.h"
|
||||||
|
#include "editor/editor_properties.h"
|
||||||
|
|
||||||
|
#include "scene/2d/tile_map.h"
|
||||||
|
#include "scene/gui/box_container.h"
|
||||||
|
#include "scene/gui/control.h"
|
||||||
|
#include "scene/gui/label.h"
|
||||||
|
|
||||||
|
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:
|
||||||
|
Map<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(StringName p_name);
|
||||||
|
void add_dummy_property(StringName p_name);
|
||||||
|
void remove_dummy_property(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;
|
||||||
|
UndoRedo *editor_undo_redo = EditorNode::get_undo_redo();
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
int drag_point_index;
|
||||||
|
Vector2 drag_last_pos;
|
||||||
|
PackedVector2Array drag_old_polygon;
|
||||||
|
|
||||||
|
HBoxContainer *toolbar;
|
||||||
|
Ref<ButtonGroup> tools_button_group;
|
||||||
|
Button *button_create;
|
||||||
|
Button *button_edit;
|
||||||
|
Button *button_delete;
|
||||||
|
Button *button_pixel_snap;
|
||||||
|
MenuButton *button_advanced_menu;
|
||||||
|
|
||||||
|
Vector<Point2> in_creation_polygon;
|
||||||
|
|
||||||
|
Panel *panel;
|
||||||
|
Control *base_control;
|
||||||
|
EditorZoomWidget *editor_zoom_widget;
|
||||||
|
Button *button_center_view;
|
||||||
|
Vector2 panning;
|
||||||
|
|
||||||
|
Ref<Texture2D> background_texture;
|
||||||
|
Rect2 background_region;
|
||||||
|
Vector2 background_offset;
|
||||||
|
bool background_h_flip;
|
||||||
|
bool background_v_flip;
|
||||||
|
bool background_transpose;
|
||||||
|
Color background_modulate;
|
||||||
|
|
||||||
|
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 _snap_to_tile_shape(Point2 &r_point, float &r_current_snapped_dist, float p_snap_dist);
|
||||||
|
void _snap_to_half_pixel(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(Ref<Texture2D> p_texture, Rect2 p_region = Rect2(), Vector2 p_offset = Vector2(), bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false, Color p_modulate = Color(1.0, 1.0, 1.0, 0.0));
|
||||||
|
|
||||||
|
int get_polygon_count();
|
||||||
|
int add_polygon(Vector<Point2> p_polygon, int p_index = -1);
|
||||||
|
void remove_polygon(int p_index);
|
||||||
|
void clear_polygons();
|
||||||
|
void set_polygon(int p_polygon_index, 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;
|
||||||
|
|
||||||
|
// UI
|
||||||
|
Ref<Texture2D> tile_bool_checked;
|
||||||
|
Ref<Texture2D> tile_bool_unchecked;
|
||||||
|
Label *label;
|
||||||
|
|
||||||
|
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;
|
||||||
|
Map<TileMapCell, Variant> drag_modified;
|
||||||
|
Variant drag_painted_value;
|
||||||
|
|
||||||
|
void _property_value_changed(StringName p_property, Variant p_value, StringName p_field);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
DummyObject *dummy_object = memnew(DummyObject);
|
||||||
|
|
||||||
|
UndoRedo *undo_redo = EditorNode::get_undo_redo();
|
||||||
|
|
||||||
|
StringName type;
|
||||||
|
String property;
|
||||||
|
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, 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, Map<TileMapCell, Variant> p_previous_values, 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, String p_property, String p_label = "", Variant p_default_value = Variant());
|
||||||
|
|
||||||
|
TileDataDefaultEditor();
|
||||||
|
~TileDataDefaultEditor();
|
||||||
|
};
|
||||||
|
|
||||||
|
class TileDataTextureOffsetEditor : public TileDataDefaultEditor {
|
||||||
|
GDCLASS(TileDataTextureOffsetEditor, 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;
|
||||||
|
|
||||||
|
void _polygon_changed(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, 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, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
UndoRedo *undo_redo = EditorNode::get_undo_redo();
|
||||||
|
|
||||||
|
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;
|
||||||
|
DummyObject *dummy_object = memnew(DummyObject);
|
||||||
|
Map<StringName, EditorProperty *> property_editors;
|
||||||
|
|
||||||
|
void _property_value_changed(StringName p_property, Variant p_value, StringName p_field);
|
||||||
|
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, 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, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
UndoRedo *undo_redo = EditorNode::get_undo_redo();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
Map<TileMapCell, Variant> drag_modified;
|
||||||
|
Variant drag_painted_value;
|
||||||
|
|
||||||
|
// UI
|
||||||
|
Label *label;
|
||||||
|
DummyObject *dummy_object = memnew(DummyObject);
|
||||||
|
EditorPropertyEnum *terrain_set_property_editor = nullptr;
|
||||||
|
EditorPropertyEnum *terrain_property_editor = nullptr;
|
||||||
|
|
||||||
|
void _property_value_changed(StringName p_property, Variant p_value, StringName p_field);
|
||||||
|
|
||||||
|
void _update_terrain_selector();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void _tile_set_changed() override;
|
||||||
|
|
||||||
|
void _notification(int p_what);
|
||||||
|
|
||||||
|
UndoRedo *undo_redo = EditorNode::get_undo_redo();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
void _polygon_changed(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, 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, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
UndoRedo *undo_redo = EditorNode::get_undo_redo();
|
||||||
|
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TILE_DATA_EDITORS_H
|
4009
tile_editor/tile_map_editor.cpp
Normal file
4009
tile_editor/tile_map_editor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
362
tile_editor/tile_map_editor.h
Normal file
362
tile_editor/tile_map_editor.h
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tile_map_editor.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TILE_MAP_EDITOR_H
|
||||||
|
#define TILE_MAP_EDITOR_H
|
||||||
|
|
||||||
|
#include "tile_atlas_view.h"
|
||||||
|
|
||||||
|
#include "core/os/thread.h"
|
||||||
|
#include "core/typedefs.h"
|
||||||
|
#include "editor/editor_node.h"
|
||||||
|
#include "scene/2d/tile_map.h"
|
||||||
|
#include "scene/gui/box_container.h"
|
||||||
|
#include "scene/gui/tab_bar.h"
|
||||||
|
|
||||||
|
class TileMapEditorPlugin : public Object {
|
||||||
|
public:
|
||||||
|
struct TabData {
|
||||||
|
Control *toolbar;
|
||||||
|
Control *panel;
|
||||||
|
};
|
||||||
|
|
||||||
|
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_id, int p_tile_map_layer){};
|
||||||
|
};
|
||||||
|
|
||||||
|
class TileMapEditorTilesPlugin : public TileMapEditorPlugin {
|
||||||
|
GDCLASS(TileMapEditorTilesPlugin, TileMapEditorPlugin);
|
||||||
|
|
||||||
|
private:
|
||||||
|
UndoRedo *undo_redo = EditorNode::get_undo_redo();
|
||||||
|
ObjectID tile_map_id;
|
||||||
|
int tile_map_layer = -1;
|
||||||
|
virtual void edit(ObjectID p_tile_map_id, int p_tile_map_layer) override;
|
||||||
|
|
||||||
|
///// Toolbar /////
|
||||||
|
HBoxContainer *toolbar;
|
||||||
|
|
||||||
|
Ref<ButtonGroup> tool_buttons_group;
|
||||||
|
Button *select_tool_button;
|
||||||
|
Button *paint_tool_button;
|
||||||
|
Button *line_tool_button;
|
||||||
|
Button *rect_tool_button;
|
||||||
|
Button *bucket_tool_button;
|
||||||
|
|
||||||
|
HBoxContainer *tools_settings;
|
||||||
|
|
||||||
|
VSeparator *tools_settings_vsep;
|
||||||
|
Button *picker_button;
|
||||||
|
Button *erase_button;
|
||||||
|
|
||||||
|
VSeparator *tools_settings_vsep_2;
|
||||||
|
CheckBox *bucket_contiguous_checkbox;
|
||||||
|
CheckBox *random_tile_checkbox;
|
||||||
|
float scattering = 0.0;
|
||||||
|
Label *scatter_label;
|
||||||
|
SpinBox *scatter_spinbox;
|
||||||
|
void _on_random_tile_checkbox_toggled(bool p_pressed);
|
||||||
|
void _on_scattering_spinbox_changed(double p_value);
|
||||||
|
|
||||||
|
void _update_toolbar();
|
||||||
|
|
||||||
|
///// 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;
|
||||||
|
Map<Vector2i, TileMapCell> drag_modified;
|
||||||
|
|
||||||
|
TileMapCell _pick_random_tile(Ref<TileMapPattern> p_pattern);
|
||||||
|
Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos, bool p_erase);
|
||||||
|
Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase);
|
||||||
|
Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase);
|
||||||
|
void _stop_dragging();
|
||||||
|
|
||||||
|
///// Selection system. /////
|
||||||
|
Set<Vector2i> tile_map_selection;
|
||||||
|
Ref<TileMapPattern> tile_map_clipboard;
|
||||||
|
Ref<TileMapPattern> selection_pattern;
|
||||||
|
void _set_tile_map_selection(const TypedArray<Vector2i> &p_selection);
|
||||||
|
TypedArray<Vector2i> _get_tile_map_selection() const;
|
||||||
|
|
||||||
|
Set<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();
|
||||||
|
|
||||||
|
///// Bottom panel common ////
|
||||||
|
void _tab_changed();
|
||||||
|
|
||||||
|
///// Bottom panel tiles ////
|
||||||
|
VBoxContainer *tiles_bottom_panel;
|
||||||
|
Label *missing_source_label;
|
||||||
|
Label *invalid_source_label;
|
||||||
|
|
||||||
|
ItemList *sources_list;
|
||||||
|
|
||||||
|
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;
|
||||||
|
HSplitContainer *atlas_sources_split_container;
|
||||||
|
|
||||||
|
bool tile_set_dragging_selection = false;
|
||||||
|
Vector2i tile_set_drag_start_mouse_pos;
|
||||||
|
|
||||||
|
Control *tile_atlas_control;
|
||||||
|
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;
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Scenes collection sources.
|
||||||
|
ItemList *scene_tiles_list;
|
||||||
|
|
||||||
|
void _update_scenes_collection_view();
|
||||||
|
void _scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud);
|
||||||
|
void _scenes_list_multi_selected(int p_index, bool p_selected);
|
||||||
|
void _scenes_list_nothing_selected();
|
||||||
|
|
||||||
|
///// Bottom panel patterns ////
|
||||||
|
VBoxContainer *patterns_bottom_panel;
|
||||||
|
ItemList *patterns_item_list;
|
||||||
|
Label *patterns_help_label;
|
||||||
|
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();
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
TileMapEditorTilesPlugin();
|
||||||
|
~TileMapEditorTilesPlugin();
|
||||||
|
};
|
||||||
|
|
||||||
|
class TileMapEditorTerrainsPlugin : public TileMapEditorPlugin {
|
||||||
|
GDCLASS(TileMapEditorTerrainsPlugin, TileMapEditorPlugin);
|
||||||
|
|
||||||
|
private:
|
||||||
|
UndoRedo *undo_redo = EditorNode::get_undo_redo();
|
||||||
|
ObjectID tile_map_id;
|
||||||
|
int tile_map_layer = -1;
|
||||||
|
virtual void edit(ObjectID p_tile_map_id, int p_tile_map_layer) override;
|
||||||
|
|
||||||
|
// Toolbar.
|
||||||
|
HBoxContainer *toolbar;
|
||||||
|
|
||||||
|
Ref<ButtonGroup> tool_buttons_group;
|
||||||
|
Button *paint_tool_button;
|
||||||
|
Button *line_tool_button;
|
||||||
|
Button *rect_tool_button;
|
||||||
|
Button *bucket_tool_button;
|
||||||
|
|
||||||
|
HBoxContainer *tools_settings;
|
||||||
|
|
||||||
|
VSeparator *tools_settings_vsep;
|
||||||
|
Button *picker_button;
|
||||||
|
Button *erase_button;
|
||||||
|
|
||||||
|
VSeparator *tools_settings_vsep_2;
|
||||||
|
CheckBox *bucket_contiguous_checkbox;
|
||||||
|
void _update_toolbar();
|
||||||
|
|
||||||
|
// Main vbox.
|
||||||
|
VBoxContainer *main_vbox_container;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
Map<Vector2i, TileMapCell> drag_modified;
|
||||||
|
|
||||||
|
// Painting
|
||||||
|
Map<Vector2i, TileMapCell> _draw_terrains(const Map<Vector2i, TileSet::TerrainsPattern> &p_to_paint, int p_terrain_set) const;
|
||||||
|
Map<Vector2i, TileMapCell> _draw_line(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase);
|
||||||
|
Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase);
|
||||||
|
Set<Vector2i> _get_cells_for_bucket_fill(Vector2i p_coords, bool p_contiguous);
|
||||||
|
Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase);
|
||||||
|
void _stop_dragging();
|
||||||
|
|
||||||
|
int selected_terrain_set = -1;
|
||||||
|
TileSet::TerrainsPattern selected_terrains_pattern;
|
||||||
|
void _update_selection();
|
||||||
|
|
||||||
|
// Bottom panel.
|
||||||
|
Tree *terrains_tree;
|
||||||
|
ItemList *terrains_tile_list;
|
||||||
|
|
||||||
|
// Cache.
|
||||||
|
LocalVector<LocalVector<Set<TileSet::TerrainsPattern>>> per_terrain_terrains_patterns;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
TileMapEditorTerrainsPlugin();
|
||||||
|
~TileMapEditorTerrainsPlugin();
|
||||||
|
};
|
||||||
|
|
||||||
|
class TileMapEditor : public VBoxContainer {
|
||||||
|
GDCLASS(TileMapEditor, VBoxContainer);
|
||||||
|
|
||||||
|
private:
|
||||||
|
UndoRedo *undo_redo = EditorNode::get_undo_redo();
|
||||||
|
bool tileset_changed_needs_update = false;
|
||||||
|
ObjectID tile_map_id;
|
||||||
|
int tile_map_layer = -1;
|
||||||
|
|
||||||
|
// Vector to keep plugins.
|
||||||
|
Vector<TileMapEditorPlugin *> tile_map_editor_plugins;
|
||||||
|
|
||||||
|
// Toolbar.
|
||||||
|
HBoxContainer *tile_map_toolbar;
|
||||||
|
|
||||||
|
PopupMenu *layers_selection_popup;
|
||||||
|
Button *layers_selection_button;
|
||||||
|
Button *toogle_highlight_selected_layer_button;
|
||||||
|
void _layers_selection_button_draw();
|
||||||
|
void _layers_selection_button_pressed();
|
||||||
|
void _layers_selection_id_pressed(int p_id);
|
||||||
|
|
||||||
|
Button *toggle_grid_button;
|
||||||
|
void _on_grid_toggled(bool p_pressed);
|
||||||
|
|
||||||
|
MenuButton *advanced_menu_button;
|
||||||
|
void _advanced_menu_button_id_pressed(int p_id);
|
||||||
|
|
||||||
|
// Bottom panel.
|
||||||
|
Label *missing_tileset_label;
|
||||||
|
TabBar *tabs_bar;
|
||||||
|
LocalVector<TileMapEditorPlugin::TabData> tabs_data;
|
||||||
|
LocalVector<TileMapEditorPlugin *> tabs_plugins;
|
||||||
|
void _update_bottom_panel();
|
||||||
|
|
||||||
|
// TileMap.
|
||||||
|
Ref<Texture2D> missing_tile_texture;
|
||||||
|
Ref<Texture2D> warning_pattern_texture;
|
||||||
|
|
||||||
|
// CallBack.
|
||||||
|
void _tile_map_changed();
|
||||||
|
void _tab_changed(int p_tab_changed);
|
||||||
|
|
||||||
|
// Updates.
|
||||||
|
void _layers_select_next_or_previous(bool p_next);
|
||||||
|
void _update_layers_selection();
|
||||||
|
|
||||||
|
// Inspector undo/redo callback.
|
||||||
|
void _move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, 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(TileMap *p_tile_map);
|
||||||
|
|
||||||
|
TileMapEditor();
|
||||||
|
~TileMapEditor();
|
||||||
|
|
||||||
|
// Static functions.
|
||||||
|
static Vector<Vector2i> get_line(TileMap *p_tile_map, Vector2i p_from_cell, Vector2i p_to_cell);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TILE_MAP_EDITOR_PLUGIN_H
|
476
tile_editor/tile_proxies_manager_dialog.cpp
Normal file
476
tile_editor/tile_proxies_manager_dialog.cpp
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tile_proxies_manager_dialog.cpp */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#include "tile_proxies_manager_dialog.h"
|
||||||
|
|
||||||
|
#include "editor/editor_scale.h"
|
||||||
|
|
||||||
|
void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list) {
|
||||||
|
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() {
|
||||||
|
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_coords_level_tile_proxy(key[0], key[1]);
|
||||||
|
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();
|
||||||
|
|
||||||
|
commited_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() {
|
||||||
|
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();
|
||||||
|
commited_actions_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileProxiesManagerDialog::_clear_invalid_button_pressed() {
|
||||||
|
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() {
|
||||||
|
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).max(Vector2i(-1, -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).max(Vector2i(0, 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() {
|
||||||
|
for (int i = 0; i < commited_actions_count; i++) {
|
||||||
|
undo_redo->undo();
|
||||||
|
}
|
||||||
|
commited_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_valid());
|
||||||
|
tile_set = p_tile_set;
|
||||||
|
commited_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_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_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(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_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_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(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_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_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(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("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, 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, 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, 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, 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("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("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("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);
|
||||||
|
}
|
90
tile_editor/tile_proxies_manager_dialog.h
Normal file
90
tile_editor/tile_proxies_manager_dialog.h
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tile_proxies_manager_dialog.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TILE_PROXIES_MANAGER_DIALOG_H
|
||||||
|
#define TILE_PROXIES_MANAGER_DIALOG_H
|
||||||
|
|
||||||
|
#include "editor/editor_node.h"
|
||||||
|
#include "editor/editor_properties.h"
|
||||||
|
|
||||||
|
#include "scene/2d/tile_map.h"
|
||||||
|
#include "scene/gui/dialogs.h"
|
||||||
|
#include "scene/gui/item_list.h"
|
||||||
|
|
||||||
|
class TileProxiesManagerDialog : public ConfirmationDialog {
|
||||||
|
GDCLASS(TileProxiesManagerDialog, ConfirmationDialog);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int commited_actions_count = 0;
|
||||||
|
Ref<TileSet> tile_set;
|
||||||
|
|
||||||
|
UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
|
||||||
|
|
||||||
|
TileMapCell from;
|
||||||
|
TileMapCell to;
|
||||||
|
|
||||||
|
// GUI
|
||||||
|
ItemList *source_level_list;
|
||||||
|
ItemList *coords_level_list;
|
||||||
|
ItemList *alternative_level_list;
|
||||||
|
|
||||||
|
EditorPropertyInteger *source_from_property_editor;
|
||||||
|
EditorPropertyVector2i *coords_from_property_editor;
|
||||||
|
EditorPropertyInteger *alternative_from_property_editor;
|
||||||
|
EditorPropertyInteger *source_to_property_editor;
|
||||||
|
EditorPropertyVector2i *coords_to_property_editor;
|
||||||
|
EditorPropertyInteger *alternative_to_property_editor;
|
||||||
|
|
||||||
|
PopupMenu *popup_menu;
|
||||||
|
void _right_clicked(int p_item, Vector2 p_local_mouse_pos, 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();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TILE_PROXIES_MANAGER_DIALOG_H
|
2728
tile_editor/tile_set_atlas_source_editor.cpp
Normal file
2728
tile_editor/tile_set_atlas_source_editor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
318
tile_editor/tile_set_atlas_source_editor.h
Normal file
318
tile_editor/tile_set_atlas_source_editor.h
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tile_set_atlas_source_editor.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TILE_SET_ATLAS_SOURCE_EDITOR_H
|
||||||
|
#define TILE_SET_ATLAS_SOURCE_EDITOR_H
|
||||||
|
|
||||||
|
#include "tile_atlas_view.h"
|
||||||
|
#include "tile_data_editors.h"
|
||||||
|
|
||||||
|
#include "editor/editor_node.h"
|
||||||
|
#include "scene/gui/split_container.h"
|
||||||
|
#include "scene/resources/tile_set.h"
|
||||||
|
|
||||||
|
class TileSet;
|
||||||
|
|
||||||
|
class TileSetAtlasSourceEditor : public HBoxContainer {
|
||||||
|
GDCLASS(TileSetAtlasSourceEditor, HBoxContainer);
|
||||||
|
|
||||||
|
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;
|
||||||
|
TileSetAtlasSource *tile_set_atlas_source = nullptr;
|
||||||
|
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();
|
||||||
|
|
||||||
|
void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id);
|
||||||
|
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;
|
||||||
|
|
||||||
|
TileSetAtlasSource *tile_set_atlas_source = nullptr;
|
||||||
|
Set<TileSelection> tiles = Set<TileSelection>();
|
||||||
|
|
||||||
|
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:
|
||||||
|
TileSetAtlasSource *get_edited_tile_set_atlas_source() const { return tile_set_atlas_source; };
|
||||||
|
Set<TileSelection> get_edited_tiles() const { return tiles; };
|
||||||
|
|
||||||
|
// Update the proxyed object.
|
||||||
|
void edit(TileSetAtlasSource *p_tile_set_atlas_source, Set<TileSelection> p_tiles = Set<TileSelection>());
|
||||||
|
|
||||||
|
AtlasTileProxyObject(TileSetAtlasSourceEditor *p_tiles_set_atlas_source_editor) {
|
||||||
|
tiles_set_atlas_source_editor = p_tiles_set_atlas_source_editor;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ref<TileSet> tile_set;
|
||||||
|
TileSetAtlasSource *tile_set_atlas_source = nullptr;
|
||||||
|
int tile_set_atlas_source_id = TileSet::INVALID_SOURCE;
|
||||||
|
|
||||||
|
UndoRedo *undo_redo = EditorNode::get_undo_redo();
|
||||||
|
|
||||||
|
bool tile_set_changed_needs_update = false;
|
||||||
|
|
||||||
|
// -- Properties painting --
|
||||||
|
VBoxContainer *tile_data_painting_editor_container;
|
||||||
|
Label *tile_data_editors_label;
|
||||||
|
Button *tile_data_editor_dropdown_button;
|
||||||
|
Popup *tile_data_editors_popup;
|
||||||
|
Tree *tile_data_editors_tree;
|
||||||
|
void _tile_data_editor_dropdown_button_draw();
|
||||||
|
void _tile_data_editor_dropdown_button_pressed();
|
||||||
|
|
||||||
|
// -- Tile data editors --
|
||||||
|
String current_property;
|
||||||
|
Control *current_tile_data_editor_toolbar = nullptr;
|
||||||
|
Map<String, TileDataEditor *> tile_data_editors;
|
||||||
|
TileDataEditor *current_tile_data_editor = nullptr;
|
||||||
|
void _tile_data_editors_tree_selected();
|
||||||
|
|
||||||
|
// -- Inspector --
|
||||||
|
AtlasTileProxyObject *tile_proxy_object;
|
||||||
|
Label *tile_inspector_label;
|
||||||
|
EditorInspector *tile_inspector;
|
||||||
|
Label *tile_inspector_no_tile_selected_label;
|
||||||
|
String selected_property;
|
||||||
|
void _inspector_property_selected(String p_property);
|
||||||
|
|
||||||
|
TileSetAtlasSourceProxyObject *atlas_source_proxy_object;
|
||||||
|
Label *atlas_source_inspector_label;
|
||||||
|
EditorInspector *atlas_source_inspector;
|
||||||
|
|
||||||
|
// -- Atlas view --
|
||||||
|
HBoxContainer *toolbox;
|
||||||
|
Label *tile_atlas_view_missing_source_label;
|
||||||
|
TileAtlasView *tile_atlas_view;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
Set<Vector2i> drag_modified_tiles;
|
||||||
|
void _end_dragging();
|
||||||
|
|
||||||
|
Map<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,
|
||||||
|
};
|
||||||
|
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;
|
||||||
|
Button *tool_select_button;
|
||||||
|
Button *tool_paint_button;
|
||||||
|
Label *tool_tile_id_label;
|
||||||
|
|
||||||
|
// Tool settings.
|
||||||
|
HBoxContainer *tool_settings;
|
||||||
|
VSeparator *tool_settings_vsep;
|
||||||
|
HBoxContainer *tool_settings_tile_data_toolbar_container;
|
||||||
|
Button *tools_settings_erase_button;
|
||||||
|
MenuButton *tool_advanced_menu_buttom;
|
||||||
|
|
||||||
|
// Selection.
|
||||||
|
Set<TileSelection> selection;
|
||||||
|
|
||||||
|
void _set_selection_from_array(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;
|
||||||
|
PopupMenu *empty_base_tile_popup_menu;
|
||||||
|
Ref<Texture2D> resize_handle;
|
||||||
|
Ref<Texture2D> resize_handle_disabled;
|
||||||
|
Control *tile_atlas_control;
|
||||||
|
Control *tile_atlas_control_unscaled;
|
||||||
|
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;
|
||||||
|
Control *alternative_tiles_control;
|
||||||
|
Control *alternative_tiles_control_unscaled;
|
||||||
|
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();
|
||||||
|
|
||||||
|
// -- input events --
|
||||||
|
void _unhandled_key_input(const Ref<InputEvent> &p_event);
|
||||||
|
|
||||||
|
// -- Misc --
|
||||||
|
void _auto_create_tiles();
|
||||||
|
void _auto_remove_tiles();
|
||||||
|
AcceptDialog *confirm_auto_create_tiles;
|
||||||
|
|
||||||
|
void _tile_set_changed();
|
||||||
|
void _tile_proxy_object_changed(String p_what);
|
||||||
|
void _atlas_source_proxy_object_changed(String p_what);
|
||||||
|
|
||||||
|
void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void _notification(int p_what);
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_source, int p_source_id);
|
||||||
|
void init_source();
|
||||||
|
|
||||||
|
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;
|
||||||
|
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 uint32_t p_usage, const bool p_wide = false) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TILE_SET_ATLAS_SOURCE_EDITOR_H
|
772
tile_editor/tile_set_editor.cpp
Normal file
772
tile_editor/tile_set_editor.cpp
Normal file
@ -0,0 +1,772 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tile_set_editor.cpp */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#include "tile_set_editor.h"
|
||||||
|
|
||||||
|
#include "tile_data_editors.h"
|
||||||
|
#include "tiles_editor_plugin.h"
|
||||||
|
|
||||||
|
#include "editor/editor_scale.h"
|
||||||
|
|
||||||
|
#include "scene/gui/box_container.h"
|
||||||
|
#include "scene/gui/control.h"
|
||||||
|
#include "scene/gui/tab_container.h"
|
||||||
|
|
||||||
|
TileSetEditor *TileSetEditor::singleton = nullptr;
|
||||||
|
|
||||||
|
void TileSetEditor::_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
|
||||||
|
ERR_FAIL_COND(!tile_set.is_valid());
|
||||||
|
|
||||||
|
if (!_can_drop_data_fw(p_point, p_data, p_from)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p_from == sources_list) {
|
||||||
|
// Handle dropping a texture in the list of atlas resources.
|
||||||
|
int source_id = TileSet::INVALID_SOURCE;
|
||||||
|
int added = 0;
|
||||||
|
Dictionary d = p_data;
|
||||||
|
Vector<String> files = d["files"];
|
||||||
|
for (int i = 0; i < files.size(); i++) {
|
||||||
|
Ref<Texture2D> resource = ResourceLoader::load(files[i]);
|
||||||
|
if (resource.is_valid()) {
|
||||||
|
// Retrieve the id for the next created source.
|
||||||
|
source_id = tile_set->get_next_source_id();
|
||||||
|
|
||||||
|
// Actually create the new source.
|
||||||
|
Ref<TileSetAtlasSource> atlas_source = memnew(TileSetAtlasSource);
|
||||||
|
atlas_source->set_texture(resource);
|
||||||
|
undo_redo->create_action(TTR("Add a new atlas source"));
|
||||||
|
undo_redo->add_do_method(*tile_set, "add_source", atlas_source, source_id);
|
||||||
|
undo_redo->add_do_method(*atlas_source, "set_texture_region_size", tile_set->get_tile_size());
|
||||||
|
undo_redo->add_undo_method(*tile_set, "remove_source", source_id);
|
||||||
|
undo_redo->commit_action();
|
||||||
|
added += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (added == 1) {
|
||||||
|
tile_set_atlas_source_editor->init_source();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the selected source (thus triggering an update).
|
||||||
|
_update_sources_list(source_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TileSetEditor::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
|
||||||
|
ERR_FAIL_COND_V(!tile_set.is_valid(), false);
|
||||||
|
|
||||||
|
if (p_from == sources_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.size() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < files.size(); i++) {
|
||||||
|
String file = files[i];
|
||||||
|
String ftype = EditorFileSystem::get_singleton()->get_file_type(file);
|
||||||
|
|
||||||
|
if (!ClassDB::is_parent_class(ftype, "Texture2D")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::_update_sources_list(int force_selected_id) {
|
||||||
|
ERR_FAIL_COND(!tile_set.is_valid());
|
||||||
|
|
||||||
|
// Get the previously selected id.
|
||||||
|
int old_selected = TileSet::INVALID_SOURCE;
|
||||||
|
if (sources_list->get_current() >= 0) {
|
||||||
|
int source_id = sources_list->get_item_metadata(sources_list->get_current());
|
||||||
|
if (tile_set->has_source(source_id)) {
|
||||||
|
old_selected = source_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int to_select = TileSet::INVALID_SOURCE;
|
||||||
|
if (force_selected_id >= 0) {
|
||||||
|
to_select = force_selected_id;
|
||||||
|
} else if (old_selected >= 0) {
|
||||||
|
to_select = old_selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the list.
|
||||||
|
sources_list->clear();
|
||||||
|
|
||||||
|
// Update the atlas sources.
|
||||||
|
for (int i = 0; i < tile_set->get_source_count(); i++) {
|
||||||
|
int source_id = tile_set->get_source_id(i);
|
||||||
|
|
||||||
|
TileSetSource *source = *tile_set->get_source(source_id);
|
||||||
|
|
||||||
|
Ref<Texture2D> texture;
|
||||||
|
String item_text;
|
||||||
|
|
||||||
|
// Common to all type of sources.
|
||||||
|
if (!source->get_name().is_empty()) {
|
||||||
|
item_text = vformat(TTR("%s (id:%d)"), source->get_name(), source_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atlas source.
|
||||||
|
TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
|
||||||
|
if (atlas_source) {
|
||||||
|
texture = atlas_source->get_texture();
|
||||||
|
if (item_text.is_empty()) {
|
||||||
|
if (texture.is_valid()) {
|
||||||
|
item_text = vformat("%s (ID:%d)", texture->get_path().get_file(), source_id);
|
||||||
|
} else {
|
||||||
|
item_text = vformat(TTR("No Texture Atlas Source (ID:%d)"), source_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scene collection source.
|
||||||
|
TileSetScenesCollectionSource *scene_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
|
||||||
|
if (scene_collection_source) {
|
||||||
|
texture = get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons"));
|
||||||
|
if (item_text.is_empty()) {
|
||||||
|
item_text = vformat(TTR("Scene Collection Source (ID:%d)"), source_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use default if not valid.
|
||||||
|
if (item_text.is_empty()) {
|
||||||
|
item_text = vformat(TTR("Unknown Type Source (ID:%d)"), source_id);
|
||||||
|
}
|
||||||
|
if (!texture.is_valid()) {
|
||||||
|
texture = missing_texture_texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
sources_list->add_item(item_text, texture);
|
||||||
|
sources_list->set_item_metadata(i, source_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set again the current selected item if needed.
|
||||||
|
if (to_select >= 0) {
|
||||||
|
for (int i = 0; i < sources_list->get_item_count(); i++) {
|
||||||
|
if ((int)sources_list->get_item_metadata(i) == to_select) {
|
||||||
|
sources_list->set_current(i);
|
||||||
|
if (old_selected != to_select) {
|
||||||
|
sources_list->emit_signal(SNAME("item_selected"), sources_list->get_current());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If nothing is selected, select the first entry.
|
||||||
|
if (sources_list->get_current() < 0 && sources_list->get_item_count() > 0) {
|
||||||
|
sources_list->set_current(0);
|
||||||
|
if (old_selected != int(sources_list->get_item_metadata(0))) {
|
||||||
|
sources_list->emit_signal(SNAME("item_selected"), sources_list->get_current());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no source left, hide all editors and show the label.
|
||||||
|
_source_selected(sources_list->get_current());
|
||||||
|
|
||||||
|
// Synchronize the lists.
|
||||||
|
TilesEditorPlugin::get_singleton()->set_sources_lists_current(sources_list->get_current());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::_source_selected(int p_source_index) {
|
||||||
|
ERR_FAIL_COND(!tile_set.is_valid());
|
||||||
|
|
||||||
|
// Update the selected source.
|
||||||
|
sources_delete_button->set_disabled(p_source_index < 0);
|
||||||
|
|
||||||
|
if (p_source_index >= 0) {
|
||||||
|
int source_id = sources_list->get_item_metadata(p_source_index);
|
||||||
|
TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id));
|
||||||
|
TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(*tile_set->get_source(source_id));
|
||||||
|
if (atlas_source) {
|
||||||
|
no_source_selected_label->hide();
|
||||||
|
tile_set_atlas_source_editor->edit(*tile_set, atlas_source, source_id);
|
||||||
|
tile_set_atlas_source_editor->show();
|
||||||
|
tile_set_scenes_collection_source_editor->hide();
|
||||||
|
} else if (scenes_collection_source) {
|
||||||
|
no_source_selected_label->hide();
|
||||||
|
tile_set_atlas_source_editor->hide();
|
||||||
|
tile_set_scenes_collection_source_editor->edit(*tile_set, scenes_collection_source, source_id);
|
||||||
|
tile_set_scenes_collection_source_editor->show();
|
||||||
|
} else {
|
||||||
|
no_source_selected_label->show();
|
||||||
|
tile_set_atlas_source_editor->hide();
|
||||||
|
tile_set_scenes_collection_source_editor->hide();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
no_source_selected_label->show();
|
||||||
|
tile_set_atlas_source_editor->hide();
|
||||||
|
tile_set_scenes_collection_source_editor->hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::_source_delete_pressed() {
|
||||||
|
ERR_FAIL_COND(!tile_set.is_valid());
|
||||||
|
|
||||||
|
// Update the selected source.
|
||||||
|
int to_delete = sources_list->get_item_metadata(sources_list->get_current());
|
||||||
|
|
||||||
|
Ref<TileSetSource> source = tile_set->get_source(to_delete);
|
||||||
|
|
||||||
|
// Remove the source.
|
||||||
|
undo_redo->create_action(TTR("Remove source"));
|
||||||
|
undo_redo->add_do_method(*tile_set, "remove_source", to_delete);
|
||||||
|
undo_redo->add_undo_method(*tile_set, "add_source", source, to_delete);
|
||||||
|
undo_redo->commit_action();
|
||||||
|
|
||||||
|
_update_sources_list();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::_source_add_id_pressed(int p_id_pressed) {
|
||||||
|
ERR_FAIL_COND(!tile_set.is_valid());
|
||||||
|
|
||||||
|
switch (p_id_pressed) {
|
||||||
|
case 0: {
|
||||||
|
int source_id = tile_set->get_next_source_id();
|
||||||
|
|
||||||
|
Ref<TileSetAtlasSource> atlas_source = memnew(TileSetAtlasSource);
|
||||||
|
|
||||||
|
// Add a new source.
|
||||||
|
undo_redo->create_action(TTR("Add atlas source"));
|
||||||
|
undo_redo->add_do_method(*tile_set, "add_source", atlas_source, source_id);
|
||||||
|
undo_redo->add_do_method(*atlas_source, "set_texture_region_size", tile_set->get_tile_size());
|
||||||
|
undo_redo->add_undo_method(*tile_set, "remove_source", source_id);
|
||||||
|
undo_redo->commit_action();
|
||||||
|
|
||||||
|
_update_sources_list(source_id);
|
||||||
|
} break;
|
||||||
|
case 1: {
|
||||||
|
int source_id = tile_set->get_next_source_id();
|
||||||
|
|
||||||
|
Ref<TileSetScenesCollectionSource> scene_collection_source = memnew(TileSetScenesCollectionSource);
|
||||||
|
|
||||||
|
// Add a new source.
|
||||||
|
undo_redo->create_action(TTR("Add atlas source"));
|
||||||
|
undo_redo->add_do_method(*tile_set, "add_source", scene_collection_source, source_id);
|
||||||
|
undo_redo->add_undo_method(*tile_set, "remove_source", source_id);
|
||||||
|
undo_redo->commit_action();
|
||||||
|
|
||||||
|
_update_sources_list(source_id);
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
ERR_FAIL();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::_sources_advanced_menu_id_pressed(int p_id_pressed) {
|
||||||
|
ERR_FAIL_COND(!tile_set.is_valid());
|
||||||
|
|
||||||
|
switch (p_id_pressed) {
|
||||||
|
case 0: {
|
||||||
|
atlas_merging_dialog->update_tile_set(tile_set);
|
||||||
|
atlas_merging_dialog->popup_centered_ratio(0.5);
|
||||||
|
} break;
|
||||||
|
case 1: {
|
||||||
|
tile_proxies_manager_dialog->update_tile_set(tile_set);
|
||||||
|
tile_proxies_manager_dialog->popup_centered_ratio(0.5);
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::_notification(int p_what) {
|
||||||
|
switch (p_what) {
|
||||||
|
case NOTIFICATION_ENTER_TREE:
|
||||||
|
case NOTIFICATION_THEME_CHANGED:
|
||||||
|
sources_delete_button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
|
||||||
|
sources_add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
|
||||||
|
sources_advanced_menu_button->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons")));
|
||||||
|
missing_texture_texture = get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons"));
|
||||||
|
break;
|
||||||
|
case NOTIFICATION_INTERNAL_PROCESS:
|
||||||
|
if (tile_set_changed_needs_update) {
|
||||||
|
if (tile_set.is_valid()) {
|
||||||
|
tile_set->set_edited(true);
|
||||||
|
}
|
||||||
|
_update_sources_list();
|
||||||
|
_update_patterns_list();
|
||||||
|
tile_set_changed_needs_update = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::_patterns_item_list_gui_input(const Ref<InputEvent> &p_event) {
|
||||||
|
ERR_FAIL_COND(!tile_set.is_valid());
|
||||||
|
|
||||||
|
if (ED_IS_SHORTCUT("tiles_editor/delete", p_event) && p_event->is_pressed() && !p_event->is_echo()) {
|
||||||
|
Vector<int> selected = patterns_item_list->get_selected_items();
|
||||||
|
undo_redo->create_action(TTR("Remove TileSet patterns"));
|
||||||
|
for (int i = 0; i < selected.size(); i++) {
|
||||||
|
int pattern_index = selected[i];
|
||||||
|
undo_redo->add_do_method(*tile_set, "remove_pattern", pattern_index);
|
||||||
|
undo_redo->add_undo_method(*tile_set, "add_pattern", tile_set->get_pattern(pattern_index), pattern_index);
|
||||||
|
}
|
||||||
|
undo_redo->commit_action();
|
||||||
|
patterns_item_list->accept_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::_pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture) {
|
||||||
|
// TODO optimize ?
|
||||||
|
for (int i = 0; i < patterns_item_list->get_item_count(); i++) {
|
||||||
|
if (patterns_item_list->get_item_metadata(i) == p_pattern) {
|
||||||
|
patterns_item_list->set_item_icon(i, p_texture);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::_update_patterns_list() {
|
||||||
|
ERR_FAIL_COND(!tile_set.is_valid());
|
||||||
|
|
||||||
|
// Recreate the items.
|
||||||
|
patterns_item_list->clear();
|
||||||
|
for (int i = 0; i < tile_set->get_patterns_count(); i++) {
|
||||||
|
int id = patterns_item_list->add_item("");
|
||||||
|
patterns_item_list->set_item_metadata(id, tile_set->get_pattern(i));
|
||||||
|
TilesEditorPlugin::get_singleton()->queue_pattern_preview(tile_set, tile_set->get_pattern(i), callable_mp(this, &TileSetEditor::_pattern_preview_done));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the label visibility.
|
||||||
|
patterns_help_label->set_visible(patterns_item_list->get_item_count() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::_tile_set_changed() {
|
||||||
|
tile_set_changed_needs_update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::_tab_changed(int p_tab_changed) {
|
||||||
|
split_container->set_visible(p_tab_changed == 0);
|
||||||
|
patterns_item_list->set_visible(p_tab_changed == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) {
|
||||||
|
UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo);
|
||||||
|
ERR_FAIL_COND(!undo_redo);
|
||||||
|
|
||||||
|
TileSet *tile_set = Object::cast_to<TileSet>(p_edited);
|
||||||
|
if (!tile_set) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<String> components = String(p_array_prefix).split("/", true, 2);
|
||||||
|
|
||||||
|
// Compute the array indices to save.
|
||||||
|
int begin = 0;
|
||||||
|
int end;
|
||||||
|
if (p_array_prefix == "occlusion_layer_") {
|
||||||
|
end = tile_set->get_occlusion_layers_count();
|
||||||
|
} else if (p_array_prefix == "physics_layer_") {
|
||||||
|
end = tile_set->get_physics_layers_count();
|
||||||
|
} else if (p_array_prefix == "terrain_set_") {
|
||||||
|
end = tile_set->get_terrain_sets_count();
|
||||||
|
} else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") {
|
||||||
|
int terrain_set = components[0].trim_prefix("terrain_set_").to_int();
|
||||||
|
end = tile_set->get_terrains_count(terrain_set);
|
||||||
|
} else if (p_array_prefix == "navigation_layer_") {
|
||||||
|
end = tile_set->get_navigation_layers_count();
|
||||||
|
} else if (p_array_prefix == "custom_data_layer_") {
|
||||||
|
end = tile_set->get_custom_data_layers_count();
|
||||||
|
} else {
|
||||||
|
ERR_FAIL_MSG("Invalid array prefix for TileSet.");
|
||||||
|
}
|
||||||
|
if (p_from_index < 0) {
|
||||||
|
// Adding new.
|
||||||
|
if (p_to_pos >= 0) {
|
||||||
|
begin = p_to_pos;
|
||||||
|
} else {
|
||||||
|
end = 0; // Nothing to save when adding at the end.
|
||||||
|
}
|
||||||
|
} else if (p_to_pos < 0) {
|
||||||
|
// Removing.
|
||||||
|
begin = p_from_index;
|
||||||
|
} else {
|
||||||
|
// Moving.
|
||||||
|
begin = MIN(p_from_index, p_to_pos);
|
||||||
|
end = MIN(MAX(p_from_index, p_to_pos) + 1, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property));
|
||||||
|
// Save layers' properties.
|
||||||
|
List<PropertyInfo> properties;
|
||||||
|
tile_set->get_property_list(&properties);
|
||||||
|
for (PropertyInfo pi : properties) {
|
||||||
|
if (pi.name.begins_with(p_array_prefix)) {
|
||||||
|
String str = pi.name.trim_prefix(p_array_prefix);
|
||||||
|
int to_char_index = 0;
|
||||||
|
while (to_char_index < str.length()) {
|
||||||
|
if (str[to_char_index] < '0' || str[to_char_index] > '9') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
to_char_index++;
|
||||||
|
}
|
||||||
|
if (to_char_index > 0) {
|
||||||
|
int array_index = str.left(to_char_index).to_int();
|
||||||
|
if (array_index >= begin && array_index < end) {
|
||||||
|
ADD_UNDO(tile_set, pi.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save properties for TileSetAtlasSources tile data
|
||||||
|
for (int i = 0; i < tile_set->get_source_count(); i++) {
|
||||||
|
int source_id = tile_set->get_source_id(i);
|
||||||
|
|
||||||
|
Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id);
|
||||||
|
if (tas.is_valid()) {
|
||||||
|
for (int j = 0; j < tas->get_tiles_count(); j++) {
|
||||||
|
Vector2i tile_id = tas->get_tile_id(j);
|
||||||
|
for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) {
|
||||||
|
int alternative_id = tas->get_alternative_tile_id(tile_id, k);
|
||||||
|
TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id));
|
||||||
|
ERR_FAIL_COND(!tile_data);
|
||||||
|
|
||||||
|
// Actually saving stuff.
|
||||||
|
if (p_array_prefix == "occlusion_layer_") {
|
||||||
|
for (int layer_index = begin; layer_index < end; layer_index++) {
|
||||||
|
ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", layer_index));
|
||||||
|
}
|
||||||
|
} else if (p_array_prefix == "physics_layer_") {
|
||||||
|
for (int layer_index = begin; layer_index < end; layer_index++) {
|
||||||
|
ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", layer_index));
|
||||||
|
for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(layer_index); polygon_index++) {
|
||||||
|
ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, polygon_index));
|
||||||
|
ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, polygon_index));
|
||||||
|
ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, polygon_index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (p_array_prefix == "terrain_set_") {
|
||||||
|
ADD_UNDO(tile_data, "terrain_set");
|
||||||
|
for (int terrain_set_index = begin; terrain_set_index < end; terrain_set_index++) {
|
||||||
|
for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) {
|
||||||
|
TileSet::CellNeighbor bit = TileSet::CellNeighbor(l);
|
||||||
|
if (tile_data->is_valid_peering_bit_terrain(bit)) {
|
||||||
|
ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") {
|
||||||
|
for (int terrain_index = 0; terrain_index < TileSet::CELL_NEIGHBOR_MAX; terrain_index++) {
|
||||||
|
TileSet::CellNeighbor bit = TileSet::CellNeighbor(terrain_index);
|
||||||
|
if (tile_data->is_valid_peering_bit_terrain(bit)) {
|
||||||
|
ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[terrain_index]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (p_array_prefix == "navigation_layer_") {
|
||||||
|
for (int layer_index = begin; layer_index < end; layer_index++) {
|
||||||
|
ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", layer_index));
|
||||||
|
}
|
||||||
|
} else if (p_array_prefix == "custom_data_layer_") {
|
||||||
|
for (int layer_index = begin; layer_index < end; layer_index++) {
|
||||||
|
ADD_UNDO(tile_data, vformat("custom_data_%d", layer_index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef ADD_UNDO
|
||||||
|
|
||||||
|
// Add do method.
|
||||||
|
if (p_array_prefix == "occlusion_layer_") {
|
||||||
|
if (p_from_index < 0) {
|
||||||
|
undo_redo->add_do_method(tile_set, "add_occlusion_layer", p_to_pos);
|
||||||
|
} else if (p_to_pos < 0) {
|
||||||
|
undo_redo->add_do_method(tile_set, "remove_occlusion_layer", p_from_index);
|
||||||
|
} else {
|
||||||
|
undo_redo->add_do_method(tile_set, "move_occlusion_layer", p_from_index, p_to_pos);
|
||||||
|
}
|
||||||
|
} else if (p_array_prefix == "physics_layer_") {
|
||||||
|
if (p_from_index < 0) {
|
||||||
|
undo_redo->add_do_method(tile_set, "add_physics_layer", p_to_pos);
|
||||||
|
} else if (p_to_pos < 0) {
|
||||||
|
undo_redo->add_do_method(tile_set, "remove_physics_layer", p_from_index);
|
||||||
|
} else {
|
||||||
|
undo_redo->add_do_method(tile_set, "move_physics_layer", p_from_index, p_to_pos);
|
||||||
|
}
|
||||||
|
} else if (p_array_prefix == "terrain_set_") {
|
||||||
|
if (p_from_index < 0) {
|
||||||
|
undo_redo->add_do_method(tile_set, "add_terrain_set", p_to_pos);
|
||||||
|
} else if (p_to_pos < 0) {
|
||||||
|
undo_redo->add_do_method(tile_set, "remove_terrain_set", p_from_index);
|
||||||
|
} else {
|
||||||
|
undo_redo->add_do_method(tile_set, "move_terrain_set", p_from_index, p_to_pos);
|
||||||
|
}
|
||||||
|
} else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") {
|
||||||
|
int terrain_set = components[0].trim_prefix("terrain_set_").to_int();
|
||||||
|
if (p_from_index < 0) {
|
||||||
|
undo_redo->add_do_method(tile_set, "add_terrain", terrain_set, p_to_pos);
|
||||||
|
} else if (p_to_pos < 0) {
|
||||||
|
undo_redo->add_do_method(tile_set, "remove_terrain", terrain_set, p_from_index);
|
||||||
|
} else {
|
||||||
|
undo_redo->add_do_method(tile_set, "move_terrain", terrain_set, p_from_index, p_to_pos);
|
||||||
|
}
|
||||||
|
} else if (p_array_prefix == "navigation_layer_") {
|
||||||
|
if (p_from_index < 0) {
|
||||||
|
undo_redo->add_do_method(tile_set, "add_navigation_layer", p_to_pos);
|
||||||
|
} else if (p_to_pos < 0) {
|
||||||
|
undo_redo->add_do_method(tile_set, "remove_navigation_layer", p_from_index);
|
||||||
|
} else {
|
||||||
|
undo_redo->add_do_method(tile_set, "move_navigation_layer", p_from_index, p_to_pos);
|
||||||
|
}
|
||||||
|
} else if (p_array_prefix == "custom_data_layer_") {
|
||||||
|
if (p_from_index < 0) {
|
||||||
|
undo_redo->add_do_method(tile_set, "add_custom_data_layer", p_to_pos);
|
||||||
|
} else if (p_to_pos < 0) {
|
||||||
|
undo_redo->add_do_method(tile_set, "remove_custom_data_layer", p_from_index);
|
||||||
|
} else {
|
||||||
|
undo_redo->add_do_method(tile_set, "move_custom_data_layer", p_from_index, p_to_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) {
|
||||||
|
UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo);
|
||||||
|
ERR_FAIL_COND(!undo_redo);
|
||||||
|
|
||||||
|
#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property));
|
||||||
|
TileSet *tile_set = Object::cast_to<TileSet>(p_edited);
|
||||||
|
if (tile_set) {
|
||||||
|
Vector<String> components = p_property.split("/", true, 3);
|
||||||
|
for (int i = 0; i < tile_set->get_source_count(); i++) {
|
||||||
|
int source_id = tile_set->get_source_id(i);
|
||||||
|
|
||||||
|
Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id);
|
||||||
|
if (tas.is_valid()) {
|
||||||
|
for (int j = 0; j < tas->get_tiles_count(); j++) {
|
||||||
|
Vector2i tile_id = tas->get_tile_id(j);
|
||||||
|
for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) {
|
||||||
|
int alternative_id = tas->get_alternative_tile_id(tile_id, k);
|
||||||
|
TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id));
|
||||||
|
ERR_FAIL_COND(!tile_data);
|
||||||
|
|
||||||
|
if (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") {
|
||||||
|
ADD_UNDO(tile_data, "terrain_set");
|
||||||
|
for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) {
|
||||||
|
TileSet::CellNeighbor bit = TileSet::CellNeighbor(l);
|
||||||
|
if (tile_data->is_valid_peering_bit_terrain(bit)) {
|
||||||
|
ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int() && components[1] == "type") {
|
||||||
|
int custom_data_layer = components[0].trim_prefix("custom_data_layer_").is_valid_int();
|
||||||
|
ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef ADD_UNDO
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::_bind_methods() {
|
||||||
|
ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &TileSetEditor::_can_drop_data_fw);
|
||||||
|
ClassDB::bind_method(D_METHOD("_drop_data_fw"), &TileSetEditor::_drop_data_fw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetEditor::edit(Ref<TileSet> p_tile_set) {
|
||||||
|
if (p_tile_set == tile_set) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove listener.
|
||||||
|
if (tile_set.is_valid()) {
|
||||||
|
tile_set->disconnect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the edited object.
|
||||||
|
tile_set = p_tile_set;
|
||||||
|
|
||||||
|
// Add the listener again.
|
||||||
|
if (tile_set.is_valid()) {
|
||||||
|
tile_set->connect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed));
|
||||||
|
_update_sources_list();
|
||||||
|
_update_patterns_list();
|
||||||
|
}
|
||||||
|
|
||||||
|
tile_set_atlas_source_editor->hide();
|
||||||
|
tile_set_scenes_collection_source_editor->hide();
|
||||||
|
no_source_selected_label->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
TileSetEditor::TileSetEditor() {
|
||||||
|
singleton = this;
|
||||||
|
|
||||||
|
set_process_internal(true);
|
||||||
|
|
||||||
|
// TabBar.
|
||||||
|
tabs_bar = memnew(TabBar);
|
||||||
|
tabs_bar->set_clip_tabs(false);
|
||||||
|
tabs_bar->add_tab(TTR("Tiles"));
|
||||||
|
tabs_bar->add_tab(TTR("Patterns"));
|
||||||
|
tabs_bar->connect("tab_changed", callable_mp(this, &TileSetEditor::_tab_changed));
|
||||||
|
|
||||||
|
tile_set_toolbar = memnew(HBoxContainer);
|
||||||
|
tile_set_toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
tile_set_toolbar->add_child(tabs_bar);
|
||||||
|
add_child(tile_set_toolbar);
|
||||||
|
|
||||||
|
//// Tiles ////
|
||||||
|
// Split container.
|
||||||
|
split_container = memnew(HSplitContainer);
|
||||||
|
split_container->set_name(TTR("Tiles"));
|
||||||
|
split_container->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
split_container->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
add_child(split_container);
|
||||||
|
|
||||||
|
// Sources list.
|
||||||
|
VBoxContainer *split_container_left_side = memnew(VBoxContainer);
|
||||||
|
split_container_left_side->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
split_container_left_side->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
split_container_left_side->set_stretch_ratio(0.25);
|
||||||
|
split_container_left_side->set_custom_minimum_size(Size2i(70, 0) * EDSCALE);
|
||||||
|
split_container->add_child(split_container_left_side);
|
||||||
|
|
||||||
|
sources_list = memnew(ItemList);
|
||||||
|
sources_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE);
|
||||||
|
sources_list->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
sources_list->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
sources_list->connect("item_selected", callable_mp(this, &TileSetEditor::_source_selected));
|
||||||
|
sources_list->connect("item_selected", callable_mp(TilesEditorPlugin::get_singleton(), &TilesEditorPlugin::set_sources_lists_current));
|
||||||
|
sources_list->connect("visibility_changed", callable_mp(TilesEditorPlugin::get_singleton(), &TilesEditorPlugin::synchronize_sources_list), varray(sources_list));
|
||||||
|
sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
|
||||||
|
sources_list->set_drag_forwarding(this);
|
||||||
|
split_container_left_side->add_child(sources_list);
|
||||||
|
|
||||||
|
HBoxContainer *sources_bottom_actions = memnew(HBoxContainer);
|
||||||
|
sources_bottom_actions->set_alignment(BoxContainer::ALIGNMENT_END);
|
||||||
|
split_container_left_side->add_child(sources_bottom_actions);
|
||||||
|
|
||||||
|
sources_delete_button = memnew(Button);
|
||||||
|
sources_delete_button->set_flat(true);
|
||||||
|
sources_delete_button->set_disabled(true);
|
||||||
|
sources_delete_button->connect("pressed", callable_mp(this, &TileSetEditor::_source_delete_pressed));
|
||||||
|
sources_bottom_actions->add_child(sources_delete_button);
|
||||||
|
|
||||||
|
sources_add_button = memnew(MenuButton);
|
||||||
|
sources_add_button->set_flat(true);
|
||||||
|
sources_add_button->get_popup()->add_item(TTR("Atlas"));
|
||||||
|
sources_add_button->get_popup()->add_item(TTR("Scenes Collection"));
|
||||||
|
sources_add_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_source_add_id_pressed));
|
||||||
|
sources_bottom_actions->add_child(sources_add_button);
|
||||||
|
|
||||||
|
sources_advanced_menu_button = memnew(MenuButton);
|
||||||
|
sources_advanced_menu_button->set_flat(true);
|
||||||
|
sources_advanced_menu_button->get_popup()->add_item(TTR("Open Atlas Merging Tool"));
|
||||||
|
sources_advanced_menu_button->get_popup()->add_item(TTR("Manage Tile Proxies"));
|
||||||
|
sources_advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_sources_advanced_menu_id_pressed));
|
||||||
|
sources_bottom_actions->add_child(sources_advanced_menu_button);
|
||||||
|
|
||||||
|
atlas_merging_dialog = memnew(AtlasMergingDialog);
|
||||||
|
add_child(atlas_merging_dialog);
|
||||||
|
|
||||||
|
tile_proxies_manager_dialog = memnew(TileProxiesManagerDialog);
|
||||||
|
add_child(tile_proxies_manager_dialog);
|
||||||
|
|
||||||
|
// Right side container.
|
||||||
|
VBoxContainer *split_container_right_side = memnew(VBoxContainer);
|
||||||
|
split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
split_container_right_side->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
split_container->add_child(split_container_right_side);
|
||||||
|
|
||||||
|
// No source selected.
|
||||||
|
no_source_selected_label = memnew(Label);
|
||||||
|
no_source_selected_label->set_text(TTR("No TileSet source selected. Select or create a TileSet source."));
|
||||||
|
no_source_selected_label->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
no_source_selected_label->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
no_source_selected_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
||||||
|
no_source_selected_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
|
||||||
|
split_container_right_side->add_child(no_source_selected_label);
|
||||||
|
|
||||||
|
// Atlases editor.
|
||||||
|
tile_set_atlas_source_editor = memnew(TileSetAtlasSourceEditor);
|
||||||
|
tile_set_atlas_source_editor->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
tile_set_atlas_source_editor->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
tile_set_atlas_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_sources_list));
|
||||||
|
split_container_right_side->add_child(tile_set_atlas_source_editor);
|
||||||
|
tile_set_atlas_source_editor->hide();
|
||||||
|
|
||||||
|
// Scenes collection editor.
|
||||||
|
tile_set_scenes_collection_source_editor = memnew(TileSetScenesCollectionSourceEditor);
|
||||||
|
tile_set_scenes_collection_source_editor->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
tile_set_scenes_collection_source_editor->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
tile_set_scenes_collection_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_sources_list));
|
||||||
|
split_container_right_side->add_child(tile_set_scenes_collection_source_editor);
|
||||||
|
tile_set_scenes_collection_source_editor->hide();
|
||||||
|
|
||||||
|
//// Patterns ////
|
||||||
|
int thumbnail_size = 64;
|
||||||
|
patterns_item_list = memnew(ItemList);
|
||||||
|
patterns_item_list->set_max_columns(0);
|
||||||
|
patterns_item_list->set_icon_mode(ItemList::ICON_MODE_TOP);
|
||||||
|
patterns_item_list->set_fixed_column_width(thumbnail_size * 3 / 2);
|
||||||
|
patterns_item_list->set_max_text_lines(2);
|
||||||
|
patterns_item_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));
|
||||||
|
patterns_item_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||||
|
patterns_item_list->connect("gui_input", callable_mp(this, &TileSetEditor::_patterns_item_list_gui_input));
|
||||||
|
add_child(patterns_item_list);
|
||||||
|
patterns_item_list->hide();
|
||||||
|
|
||||||
|
patterns_help_label = memnew(Label);
|
||||||
|
patterns_help_label->set_text(TTR("Add new patterns in the TileMap editing mode."));
|
||||||
|
patterns_help_label->set_anchors_and_offsets_preset(Control::PRESET_CENTER);
|
||||||
|
patterns_item_list->add_child(patterns_help_label);
|
||||||
|
|
||||||
|
// Registers UndoRedo inspector callback.
|
||||||
|
EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileSet"), callable_mp(this, &TileSetEditor::_move_tile_set_array_element));
|
||||||
|
EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
TileSetEditor::~TileSetEditor() {
|
||||||
|
if (tile_set.is_valid()) {
|
||||||
|
tile_set->disconnect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed));
|
||||||
|
}
|
||||||
|
}
|
108
tile_editor/tile_set_editor.h
Normal file
108
tile_editor/tile_set_editor.h
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tile_set_editor.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TILE_SET_EDITOR_H
|
||||||
|
#define TILE_SET_EDITOR_H
|
||||||
|
|
||||||
|
#include "atlas_merging_dialog.h"
|
||||||
|
#include "scene/gui/box_container.h"
|
||||||
|
#include "scene/resources/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 TileSetEditor : public VBoxContainer {
|
||||||
|
GDCLASS(TileSetEditor, VBoxContainer);
|
||||||
|
|
||||||
|
static TileSetEditor *singleton;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ref<TileSet> tile_set;
|
||||||
|
bool tile_set_changed_needs_update = false;
|
||||||
|
HSplitContainer *split_container;
|
||||||
|
|
||||||
|
// TabBar.
|
||||||
|
HBoxContainer *tile_set_toolbar;
|
||||||
|
TabBar *tabs_bar;
|
||||||
|
|
||||||
|
// Tiles.
|
||||||
|
Label *no_source_selected_label;
|
||||||
|
TileSetAtlasSourceEditor *tile_set_atlas_source_editor;
|
||||||
|
TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor;
|
||||||
|
|
||||||
|
UndoRedo *undo_redo = EditorNode::get_undo_redo();
|
||||||
|
|
||||||
|
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 _update_sources_list(int force_selected_id = -1);
|
||||||
|
|
||||||
|
// Sources management.
|
||||||
|
Button *sources_delete_button;
|
||||||
|
MenuButton *sources_add_button;
|
||||||
|
MenuButton *sources_advanced_menu_button;
|
||||||
|
ItemList *sources_list;
|
||||||
|
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);
|
||||||
|
|
||||||
|
AtlasMergingDialog *atlas_merging_dialog;
|
||||||
|
TileProxiesManagerDialog *tile_proxies_manager_dialog;
|
||||||
|
|
||||||
|
// Patterns.
|
||||||
|
ItemList *patterns_item_list;
|
||||||
|
Label *patterns_help_label;
|
||||||
|
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();
|
||||||
|
|
||||||
|
void _tile_set_changed();
|
||||||
|
void _tab_changed(int p_tab_changed);
|
||||||
|
|
||||||
|
void _move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos);
|
||||||
|
void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void _notification(int p_what);
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
public:
|
||||||
|
_FORCE_INLINE_ static TileSetEditor *get_singleton() { return singleton; }
|
||||||
|
|
||||||
|
void edit(Ref<TileSet> p_tile_set);
|
||||||
|
|
||||||
|
TileSetEditor();
|
||||||
|
~TileSetEditor();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TILE_SET_EDITOR_PLUGIN_H
|
533
tile_editor/tile_set_scenes_collection_source_editor.cpp
Normal file
533
tile_editor/tile_set_scenes_collection_source_editor.cpp
Normal file
@ -0,0 +1,533 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tile_set_scenes_collection_source_editor.cpp */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#include "tile_set_scenes_collection_source_editor.h"
|
||||||
|
|
||||||
|
#include "editor/editor_resource_preview.h"
|
||||||
|
#include "editor/editor_scale.h"
|
||||||
|
#include "editor/editor_settings.h"
|
||||||
|
|
||||||
|
#include "scene/gui/item_list.h"
|
||||||
|
|
||||||
|
#include "core/core_string_names.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(SNAME("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(SNAME("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_valid());
|
||||||
|
ERR_FAIL_COND(!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(CoreStringNames::get_singleton()->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(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) {
|
||||||
|
tile_set_scenes_collection_source->connect(CoreStringNames::get_singleton()->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(SNAME("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(SNAME("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(SNAME("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_COND(!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(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, 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()->open_request(packed_scene->get_path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSetScenesCollectionSourceEditor::_source_add_pressed() {
|
||||||
|
int scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id();
|
||||||
|
undo_redo->create_action(TTR("Add a Scene Tile"));
|
||||||
|
undo_redo->add_do_method(tile_set_scenes_collection_source, "create_scene_tile", Ref<PackedScene>(), 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.size() <= 0);
|
||||||
|
int scene_id = scene_tiles_list->get_item_metadata(selected_indices[0]);
|
||||||
|
|
||||||
|
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.size() <= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
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_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons")));
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reselect if needed.
|
||||||
|
if (to_reselect >= 0) {
|
||||||
|
scene_tiles_list->select(to_reselect);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon size update.
|
||||||
|
int int_size = int(EditorSettings::get_singleton()->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_ENTER_TREE:
|
||||||
|
case NOTIFICATION_THEME_CHANGED:
|
||||||
|
scene_tile_add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
|
||||||
|
scene_tile_delete_button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
|
||||||
|
_update_scenes_list();
|
||||||
|
break;
|
||||||
|
case NOTIFICATION_INTERNAL_PROCESS:
|
||||||
|
if (tile_set_scenes_collection_source_changed_needs_update) {
|
||||||
|
// 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;
|
||||||
|
default:
|
||||||
|
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_valid());
|
||||||
|
ERR_FAIL_COND(!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 (p_tile_set == tile_set && p_tile_set_scenes_collection_source == tile_set_scenes_collection_source && p_source_id == tile_set_source_id) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
int scene_id = -1;
|
||||||
|
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()) {
|
||||||
|
scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id();
|
||||||
|
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.size() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < files.size(); i++) {
|
||||||
|
String file = files[i];
|
||||||
|
String ftype = EditorFileSystem::get_singleton()->get_file_type(file);
|
||||||
|
|
||||||
|
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);
|
||||||
|
ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &TileSetScenesCollectionSourceEditor::_can_drop_data_fw);
|
||||||
|
ClassDB::bind_method(D_METHOD("_drop_data_fw"), &TileSetScenesCollectionSourceEditor::_drop_data_fw);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(Size2i(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_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("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_scenes_collection_source_proxy_object_changed));
|
||||||
|
|
||||||
|
scenes_collection_source_inspector = memnew(EditorInspector);
|
||||||
|
scenes_collection_source_inspector->set_undo_redo(undo_redo);
|
||||||
|
scenes_collection_source_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
|
||||||
|
scenes_collection_source_inspector->edit(scenes_collection_source_proxy_object);
|
||||||
|
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("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_scenes_list).unbind(1));
|
||||||
|
tile_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_action_buttons).unbind(1));
|
||||||
|
|
||||||
|
tile_inspector = memnew(EditorInspector);
|
||||||
|
tile_inspector->set_undo_redo(undo_redo);
|
||||||
|
tile_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
|
||||||
|
tile_inspector->edit(tile_proxy_object);
|
||||||
|
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_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
scene_tiles_list->set_drag_forwarding(this);
|
||||||
|
scene_tiles_list->connect("item_selected", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_tile_inspector).unbind(1));
|
||||||
|
scene_tiles_list->connect("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_flat(true);
|
||||||
|
scene_tile_add_button->connect("pressed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_source_add_pressed));
|
||||||
|
scenes_bottom_actions->add_child(scene_tile_add_button);
|
||||||
|
|
||||||
|
scene_tile_delete_button = memnew(Button);
|
||||||
|
scene_tile_delete_button->set_flat(true);
|
||||||
|
scene_tile_delete_button->set_disabled(true);
|
||||||
|
scene_tile_delete_button->connect("pressed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_source_delete_pressed));
|
||||||
|
scenes_bottom_actions->add_child(scene_tile_delete_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
TileSetScenesCollectionSourceEditor::~TileSetScenesCollectionSourceEditor() {
|
||||||
|
memdelete(scenes_collection_source_proxy_object);
|
||||||
|
memdelete(tile_proxy_object);
|
||||||
|
}
|
141
tile_editor/tile_set_scenes_collection_source_editor.h
Normal file
141
tile_editor/tile_set_scenes_collection_source_editor.h
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tile_set_scenes_collection_source_editor.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H
|
||||||
|
#define TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H
|
||||||
|
|
||||||
|
#include "editor/editor_node.h"
|
||||||
|
#include "scene/gui/box_container.h"
|
||||||
|
#include "scene/resources/tile_set.h"
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 proxyed 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:
|
||||||
|
Ref<TileSet> tile_set;
|
||||||
|
TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr;
|
||||||
|
int tile_set_source_id = -1;
|
||||||
|
|
||||||
|
UndoRedo *undo_redo = EditorNode::get_undo_redo();
|
||||||
|
|
||||||
|
bool tile_set_scenes_collection_source_changed_needs_update = false;
|
||||||
|
|
||||||
|
// Source inspector.
|
||||||
|
TileSetScenesCollectionProxyObject *scenes_collection_source_proxy_object;
|
||||||
|
Label *scenes_collection_source_inspector_label;
|
||||||
|
EditorInspector *scenes_collection_source_inspector;
|
||||||
|
|
||||||
|
// Tile inspector.
|
||||||
|
SceneTileProxyObject *tile_proxy_object;
|
||||||
|
Label *tile_inspector_label;
|
||||||
|
EditorInspector *tile_inspector;
|
||||||
|
|
||||||
|
ItemList *scene_tiles_list;
|
||||||
|
Button *scene_tile_add_button;
|
||||||
|
Button *scene_tile_delete_button;
|
||||||
|
|
||||||
|
void _tile_set_scenes_collection_source_changed();
|
||||||
|
void _scenes_collection_source_proxy_object_changed(String p_what);
|
||||||
|
void _scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud);
|
||||||
|
void _scenes_list_item_activated(int p_index);
|
||||||
|
|
||||||
|
void _source_add_pressed();
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
323
tile_editor/tiles_editor_plugin.cpp
Normal file
323
tile_editor/tiles_editor_plugin.cpp
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tiles_editor_plugin.cpp */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#include "tiles_editor_plugin.h"
|
||||||
|
|
||||||
|
#include "core/os/mutex.h"
|
||||||
|
|
||||||
|
#include "editor/editor_node.h"
|
||||||
|
#include "editor/editor_scale.h"
|
||||||
|
#include "editor/plugins/canvas_item_editor_plugin.h"
|
||||||
|
|
||||||
|
#include "scene/2d/tile_map.h"
|
||||||
|
#include "scene/gui/box_container.h"
|
||||||
|
#include "scene/gui/button.h"
|
||||||
|
#include "scene/gui/control.h"
|
||||||
|
#include "scene/gui/separator.h"
|
||||||
|
#include "scene/resources/tile_set.h"
|
||||||
|
|
||||||
|
#include "tile_set_editor.h"
|
||||||
|
|
||||||
|
TilesEditorPlugin *TilesEditorPlugin::singleton = nullptr;
|
||||||
|
|
||||||
|
void TilesEditorPlugin::_preview_frame_started() {
|
||||||
|
RS::get_singleton()->request_frame_drawn_callback(callable_mp(const_cast<TilesEditorPlugin *>(this), &TilesEditorPlugin::_pattern_preview_done));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TilesEditorPlugin::_pattern_preview_done() {
|
||||||
|
pattern_preview_done.post();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TilesEditorPlugin::_thread_func(void *ud) {
|
||||||
|
TilesEditorPlugin *te = (TilesEditorPlugin *)ud;
|
||||||
|
te->_thread();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TilesEditorPlugin::_thread() {
|
||||||
|
pattern_thread_exited.clear();
|
||||||
|
while (!pattern_thread_exit.is_set()) {
|
||||||
|
pattern_preview_sem.wait();
|
||||||
|
|
||||||
|
pattern_preview_mutex.lock();
|
||||||
|
if (pattern_preview_queue.size()) {
|
||||||
|
QueueItem item = pattern_preview_queue.front()->get();
|
||||||
|
pattern_preview_queue.pop_front();
|
||||||
|
pattern_preview_mutex.unlock();
|
||||||
|
|
||||||
|
int thumbnail_size = EditorSettings::get_singleton()->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);
|
||||||
|
|
||||||
|
TileMap *tile_map = memnew(TileMap);
|
||||||
|
tile_map->set_tileset(item.tile_set);
|
||||||
|
tile_map->set_pattern(0, Vector2(), item.pattern);
|
||||||
|
viewport->add_child(tile_map);
|
||||||
|
|
||||||
|
TypedArray<Vector2i> used_cells = tile_map->get_used_cells(0);
|
||||||
|
|
||||||
|
Rect2 encompassing_rect = Rect2();
|
||||||
|
encompassing_rect.set_position(tile_map->map_to_world(used_cells[0]));
|
||||||
|
for (int i = 0; i < used_cells.size(); i++) {
|
||||||
|
Vector2i cell = used_cells[i];
|
||||||
|
Vector2 world_pos = tile_map->map_to_world(cell);
|
||||||
|
encompassing_rect.expand_to(world_pos);
|
||||||
|
|
||||||
|
// Texture.
|
||||||
|
Ref<TileSetAtlasSource> atlas_source = tile_set->get_source(tile_map->get_cell_source_id(0, cell));
|
||||||
|
if (atlas_source.is_valid()) {
|
||||||
|
Vector2i coords = tile_map->get_cell_atlas_coords(0, cell);
|
||||||
|
int alternative = tile_map->get_cell_alternative_tile(0, cell);
|
||||||
|
|
||||||
|
Vector2 center = world_pos - atlas_source->get_tile_effective_texture_offset(coords, alternative);
|
||||||
|
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->set_scale(scale);
|
||||||
|
tile_map->set_position(-(scale * encompassing_rect.get_center()) + thumbnail_size2 / 2);
|
||||||
|
|
||||||
|
// Add the viewport at the lasst moment to avoid rendering too early.
|
||||||
|
EditorNode::get_singleton()->add_child(viewport);
|
||||||
|
|
||||||
|
RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<TilesEditorPlugin *>(this), &TilesEditorPlugin::_preview_frame_started), Vector<Variant>(), Object::CONNECT_ONESHOT);
|
||||||
|
|
||||||
|
pattern_preview_done.wait();
|
||||||
|
|
||||||
|
Ref<Image> image = viewport->get_texture()->get_image();
|
||||||
|
Ref<ImageTexture> image_texture;
|
||||||
|
image_texture.instantiate();
|
||||||
|
image_texture->create_from_image(image);
|
||||||
|
|
||||||
|
// Find the index for the given pattern. TODO: optimize.
|
||||||
|
Variant args[] = { item.pattern, image_texture };
|
||||||
|
const Variant *args_ptr[] = { &args[0], &args[1] };
|
||||||
|
Variant r;
|
||||||
|
Callable::CallError error;
|
||||||
|
item.callback.call(args_ptr, 2, r, error);
|
||||||
|
|
||||||
|
viewport->queue_delete();
|
||||||
|
} else {
|
||||||
|
pattern_preview_mutex.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pattern_thread_exited.set();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TilesEditorPlugin::_tile_map_changed() {
|
||||||
|
tile_map_changed_needs_update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TilesEditorPlugin::_update_editors() {
|
||||||
|
// If tile_map is not edited, we change the edited only if we are not editing a tile_set.
|
||||||
|
tileset_editor->edit(tile_set);
|
||||||
|
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
|
||||||
|
if (tile_map) {
|
||||||
|
tilemap_editor->edit(tile_map);
|
||||||
|
} else {
|
||||||
|
tilemap_editor->edit(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the viewport.
|
||||||
|
CanvasItemEditor::get_singleton()->update_viewport();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TilesEditorPlugin::_notification(int p_what) {
|
||||||
|
switch (p_what) {
|
||||||
|
case NOTIFICATION_INTERNAL_PROCESS: {
|
||||||
|
if (tile_map_changed_needs_update) {
|
||||||
|
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
|
||||||
|
if (tile_map) {
|
||||||
|
tile_set = tile_map->get_tileset();
|
||||||
|
}
|
||||||
|
_update_editors();
|
||||||
|
tile_map_changed_needs_update = false;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TilesEditorPlugin::make_visible(bool p_visible) {
|
||||||
|
if (p_visible) {
|
||||||
|
// Disable and hide invalid editors.
|
||||||
|
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
|
||||||
|
tileset_editor_button->set_visible(tile_set.is_valid());
|
||||||
|
tilemap_editor_button->set_visible(tile_map);
|
||||||
|
if (tile_map) {
|
||||||
|
editor_node->make_bottom_panel_item_visible(tilemap_editor);
|
||||||
|
} else {
|
||||||
|
editor_node->make_bottom_panel_item_visible(tileset_editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
tileset_editor_button->hide();
|
||||||
|
tilemap_editor_button->hide();
|
||||||
|
editor_node->hide_bottom_panel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TilesEditorPlugin::queue_pattern_preview(Ref<TileSet> p_tile_set, Ref<TileMapPattern> p_pattern, Callable p_callback) {
|
||||||
|
ERR_FAIL_COND(!p_tile_set.is_valid());
|
||||||
|
ERR_FAIL_COND(!p_pattern.is_valid());
|
||||||
|
{
|
||||||
|
MutexLock lock(pattern_preview_mutex);
|
||||||
|
pattern_preview_queue.push_back({ p_tile_set, p_pattern, p_callback });
|
||||||
|
}
|
||||||
|
pattern_preview_sem.post();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TilesEditorPlugin::set_sources_lists_current(int p_current) {
|
||||||
|
atlas_sources_lists_current = p_current;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TilesEditorPlugin::synchronize_sources_list(Object *p_current) {
|
||||||
|
ItemList *item_list = Object::cast_to<ItemList>(p_current);
|
||||||
|
ERR_FAIL_COND(!item_list);
|
||||||
|
|
||||||
|
if (item_list->is_visible_in_tree()) {
|
||||||
|
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->emit_signal(SNAME("item_selected"), atlas_sources_lists_current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TilesEditorPlugin::set_atlas_view_transform(float p_zoom, Vector2 p_scroll) {
|
||||||
|
atlas_view_zoom = p_zoom;
|
||||||
|
atlas_view_scroll = p_scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TilesEditorPlugin::synchronize_atlas_view(Object *p_current) {
|
||||||
|
TileAtlasView *tile_atlas_view = Object::cast_to<TileAtlasView>(p_current);
|
||||||
|
ERR_FAIL_COND(!tile_atlas_view);
|
||||||
|
|
||||||
|
if (tile_atlas_view->is_visible_in_tree()) {
|
||||||
|
tile_atlas_view->set_transform(atlas_view_zoom, atlas_view_scroll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TilesEditorPlugin::edit(Object *p_object) {
|
||||||
|
// Disconnect to changes.
|
||||||
|
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
|
||||||
|
if (tile_map) {
|
||||||
|
tile_map->disconnect("changed", callable_mp(this, &TilesEditorPlugin::_tile_map_changed));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update edited objects.
|
||||||
|
tile_set = Ref<TileSet>();
|
||||||
|
if (p_object) {
|
||||||
|
if (p_object->is_class("TileMap")) {
|
||||||
|
tile_map_id = p_object->get_instance_id();
|
||||||
|
tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
|
||||||
|
tile_set = tile_map->get_tileset();
|
||||||
|
editor_node->make_bottom_panel_item_visible(tilemap_editor);
|
||||||
|
} else if (p_object->is_class("TileSet")) {
|
||||||
|
tile_set = Ref<TileSet>(p_object);
|
||||||
|
if (tile_map) {
|
||||||
|
if (tile_map->get_tileset() != tile_set || !tile_map->is_inside_tree()) {
|
||||||
|
tile_map = nullptr;
|
||||||
|
tile_map_id = ObjectID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editor_node->make_bottom_panel_item_visible(tileset_editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the editors.
|
||||||
|
_update_editors();
|
||||||
|
|
||||||
|
// Add change listener.
|
||||||
|
if (tile_map) {
|
||||||
|
tile_map->connect("changed", callable_mp(this, &TilesEditorPlugin::_tile_map_changed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TilesEditorPlugin::handles(Object *p_object) const {
|
||||||
|
return p_object->is_class("TileMap") || p_object->is_class("TileSet");
|
||||||
|
}
|
||||||
|
|
||||||
|
TilesEditorPlugin::TilesEditorPlugin(EditorNode *p_node) {
|
||||||
|
set_process_internal(true);
|
||||||
|
|
||||||
|
// Update the singleton.
|
||||||
|
singleton = this;
|
||||||
|
|
||||||
|
editor_node = p_node;
|
||||||
|
|
||||||
|
// Tileset editor.
|
||||||
|
tileset_editor = memnew(TileSetEditor);
|
||||||
|
tileset_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||||
|
tileset_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||||
|
tileset_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
|
||||||
|
tileset_editor->hide();
|
||||||
|
|
||||||
|
// Tilemap editor.
|
||||||
|
tilemap_editor = memnew(TileMapEditor);
|
||||||
|
tilemap_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||||
|
tilemap_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||||
|
tilemap_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
|
||||||
|
tilemap_editor->hide();
|
||||||
|
|
||||||
|
// Pattern preview generation thread.
|
||||||
|
pattern_preview_thread.start(_thread_func, this);
|
||||||
|
|
||||||
|
// Bottom buttons.
|
||||||
|
tileset_editor_button = p_node->add_bottom_panel_item(TTR("TileSet"), tileset_editor);
|
||||||
|
tileset_editor_button->hide();
|
||||||
|
tilemap_editor_button = p_node->add_bottom_panel_item(TTR("TileMap"), tilemap_editor);
|
||||||
|
tilemap_editor_button->hide();
|
||||||
|
|
||||||
|
// Initialization.
|
||||||
|
_update_editors();
|
||||||
|
}
|
||||||
|
|
||||||
|
TilesEditorPlugin::~TilesEditorPlugin() {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
113
tile_editor/tiles_editor_plugin.h
Normal file
113
tile_editor/tiles_editor_plugin.h
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tiles_editor_plugin.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TILES_EDITOR_PLUGIN_H
|
||||||
|
#define TILES_EDITOR_PLUGIN_H
|
||||||
|
|
||||||
|
#include "editor/editor_plugin.h"
|
||||||
|
#include "scene/gui/box_container.h"
|
||||||
|
|
||||||
|
#include "tile_atlas_view.h"
|
||||||
|
#include "tile_map_editor.h"
|
||||||
|
#include "tile_set_editor.h"
|
||||||
|
|
||||||
|
class TilesEditorPlugin : public EditorPlugin {
|
||||||
|
GDCLASS(TilesEditorPlugin, EditorPlugin);
|
||||||
|
|
||||||
|
static TilesEditorPlugin *singleton;
|
||||||
|
|
||||||
|
private:
|
||||||
|
EditorNode *editor_node;
|
||||||
|
|
||||||
|
bool tile_map_changed_needs_update = false;
|
||||||
|
ObjectID tile_map_id;
|
||||||
|
Ref<TileSet> tile_set;
|
||||||
|
|
||||||
|
Button *tilemap_editor_button;
|
||||||
|
TileMapEditor *tilemap_editor;
|
||||||
|
|
||||||
|
Button *tileset_editor_button;
|
||||||
|
TileSetEditor *tileset_editor;
|
||||||
|
|
||||||
|
void _update_editors();
|
||||||
|
|
||||||
|
// For synchronization.
|
||||||
|
int atlas_sources_lists_current = 0;
|
||||||
|
float atlas_view_zoom = 1.0;
|
||||||
|
Vector2 atlas_view_scroll = Vector2();
|
||||||
|
|
||||||
|
void _tile_map_changed();
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void _notification(int p_what);
|
||||||
|
|
||||||
|
public:
|
||||||
|
_FORCE_INLINE_ static TilesEditorPlugin *get_singleton() { return singleton; }
|
||||||
|
|
||||||
|
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return tilemap_editor->forward_canvas_gui_input(p_event); }
|
||||||
|
virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { tilemap_editor->forward_canvas_draw_over_viewport(p_overlay); }
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
void set_atlas_view_transform(float p_zoom, Vector2 p_scroll);
|
||||||
|
void synchronize_atlas_view(Object *p_current);
|
||||||
|
|
||||||
|
virtual void edit(Object *p_object) override;
|
||||||
|
virtual bool handles(Object *p_object) const override;
|
||||||
|
virtual void make_visible(bool p_visible) override;
|
||||||
|
|
||||||
|
TilesEditorPlugin(EditorNode *p_node);
|
||||||
|
~TilesEditorPlugin();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TILES_EDITOR_PLUGIN_H
|
3702
tile_map.cpp
Normal file
3702
tile_map.cpp
Normal file
File diff suppressed because it is too large
Load Diff
394
tile_map.h
Normal file
394
tile_map.h
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* tile_map.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TILE_MAP_H
|
||||||
|
#define TILE_MAP_H
|
||||||
|
|
||||||
|
#include "scene/2d/node_2d.h"
|
||||||
|
#include "scene/gui/control.h"
|
||||||
|
#include "scene/resources/tile_set.h"
|
||||||
|
|
||||||
|
class TileSetAtlasSource;
|
||||||
|
|
||||||
|
struct TileMapQuadrant {
|
||||||
|
struct CoordsWorldComparator {
|
||||||
|
_ALWAYS_INLINE_ bool operator()(const Vector2i &p_a, const Vector2i &p_b) const {
|
||||||
|
// We sort the cells by their world coords, as it is needed by rendering.
|
||||||
|
if (p_a.y == p_b.y) {
|
||||||
|
return p_a.x > p_b.x;
|
||||||
|
} else {
|
||||||
|
return p_a.y < p_b.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dirty list element
|
||||||
|
SelfList<TileMapQuadrant> dirty_list_element;
|
||||||
|
|
||||||
|
// Quadrant layer and coords.
|
||||||
|
int layer = -1;
|
||||||
|
Vector2i coords;
|
||||||
|
|
||||||
|
// TileMapCells
|
||||||
|
Set<Vector2i> cells;
|
||||||
|
// We need those two maps to sort by world position for rendering
|
||||||
|
// This is kind of workaround, it would be better to sort the cells directly in the "cells" set instead.
|
||||||
|
Map<Vector2i, Vector2i> map_to_world;
|
||||||
|
Map<Vector2i, Vector2i, CoordsWorldComparator> world_to_map;
|
||||||
|
|
||||||
|
// Debug.
|
||||||
|
RID debug_canvas_item;
|
||||||
|
|
||||||
|
// Rendering.
|
||||||
|
List<RID> canvas_items;
|
||||||
|
List<RID> occluders;
|
||||||
|
|
||||||
|
// Physics.
|
||||||
|
List<RID> bodies;
|
||||||
|
|
||||||
|
// Navigation.
|
||||||
|
Map<Vector2i, Vector<RID>> navigation_regions;
|
||||||
|
|
||||||
|
// Scenes.
|
||||||
|
Map<Vector2i, String> scenes;
|
||||||
|
|
||||||
|
// Runtime TileData cache.
|
||||||
|
Map<Vector2i, TileData *> runtime_tile_data_cache;
|
||||||
|
|
||||||
|
void operator=(const TileMapQuadrant &q) {
|
||||||
|
layer = q.layer;
|
||||||
|
coords = q.coords;
|
||||||
|
debug_canvas_item = q.debug_canvas_item;
|
||||||
|
canvas_items = q.canvas_items;
|
||||||
|
occluders = q.occluders;
|
||||||
|
bodies = q.bodies;
|
||||||
|
navigation_regions = q.navigation_regions;
|
||||||
|
}
|
||||||
|
|
||||||
|
TileMapQuadrant(const TileMapQuadrant &q) :
|
||||||
|
dirty_list_element(this) {
|
||||||
|
layer = q.layer;
|
||||||
|
coords = q.coords;
|
||||||
|
debug_canvas_item = q.debug_canvas_item;
|
||||||
|
canvas_items = q.canvas_items;
|
||||||
|
occluders = q.occluders;
|
||||||
|
bodies = q.bodies;
|
||||||
|
navigation_regions = q.navigation_regions;
|
||||||
|
}
|
||||||
|
|
||||||
|
TileMapQuadrant() :
|
||||||
|
dirty_list_element(this) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TileMap : public Node2D {
|
||||||
|
GDCLASS(TileMap, Node2D);
|
||||||
|
|
||||||
|
public:
|
||||||
|
class TerrainConstraint {
|
||||||
|
private:
|
||||||
|
const TileMap *tile_map;
|
||||||
|
Vector2i base_cell_coords = Vector2i();
|
||||||
|
int bit = -1;
|
||||||
|
int terrain = -1;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool operator<(const TerrainConstraint &p_other) const {
|
||||||
|
if (base_cell_coords == p_other.base_cell_coords) {
|
||||||
|
return bit < p_other.bit;
|
||||||
|
}
|
||||||
|
return base_cell_coords < p_other.base_cell_coords;
|
||||||
|
}
|
||||||
|
|
||||||
|
String to_string() const {
|
||||||
|
return vformat("Constraint {pos:%s, bit:%d, terrain:%d}", base_cell_coords, bit, terrain);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2i get_base_cell_coords() const {
|
||||||
|
return base_cell_coords;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const;
|
||||||
|
|
||||||
|
void set_terrain(int p_terrain) {
|
||||||
|
terrain = p_terrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_terrain() const {
|
||||||
|
return terrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain);
|
||||||
|
TerrainConstraint() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum VisibilityMode {
|
||||||
|
VISIBILITY_MODE_DEFAULT,
|
||||||
|
VISIBILITY_MODE_FORCE_SHOW,
|
||||||
|
VISIBILITY_MODE_FORCE_HIDE,
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class TileSetPlugin;
|
||||||
|
|
||||||
|
// A compatibility enum to specify how is the data if formatted.
|
||||||
|
enum DataFormat {
|
||||||
|
FORMAT_1 = 0,
|
||||||
|
FORMAT_2,
|
||||||
|
FORMAT_3
|
||||||
|
};
|
||||||
|
mutable DataFormat format = FORMAT_1; // Assume lowest possible format if none is present;
|
||||||
|
|
||||||
|
static constexpr float FP_ADJUST = 0.00001;
|
||||||
|
|
||||||
|
// Properties.
|
||||||
|
Ref<TileSet> tile_set;
|
||||||
|
int quadrant_size = 16;
|
||||||
|
bool collision_animatable = false;
|
||||||
|
VisibilityMode collision_visibility_mode = VISIBILITY_MODE_DEFAULT;
|
||||||
|
VisibilityMode navigation_visibility_mode = VISIBILITY_MODE_DEFAULT;
|
||||||
|
|
||||||
|
// Updates.
|
||||||
|
bool pending_update = false;
|
||||||
|
|
||||||
|
// Rect.
|
||||||
|
Rect2 rect_cache;
|
||||||
|
bool rect_cache_dirty = true;
|
||||||
|
Rect2i used_rect_cache;
|
||||||
|
bool used_rect_cache_dirty = true;
|
||||||
|
|
||||||
|
// TileMap layers.
|
||||||
|
struct TileMapLayer {
|
||||||
|
String name;
|
||||||
|
bool enabled = true;
|
||||||
|
Color modulate = Color(1, 1, 1, 1);
|
||||||
|
bool y_sort_enabled = false;
|
||||||
|
int y_sort_origin = 0;
|
||||||
|
int z_index = 0;
|
||||||
|
RID canvas_item;
|
||||||
|
Map<Vector2i, TileMapCell> tile_map;
|
||||||
|
Map<Vector2i, TileMapQuadrant> quadrant_map;
|
||||||
|
SelfList<TileMapQuadrant>::List dirty_quadrant_list;
|
||||||
|
};
|
||||||
|
LocalVector<TileMapLayer> layers;
|
||||||
|
int selected_layer = -1;
|
||||||
|
|
||||||
|
// Mapping for RID to coords.
|
||||||
|
Map<RID, Vector2i> bodies_coords;
|
||||||
|
|
||||||
|
// Quadrants and internals management.
|
||||||
|
Vector2i _coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const;
|
||||||
|
|
||||||
|
Map<Vector2i, TileMapQuadrant>::Element *_create_quadrant(int p_layer, const Vector2i &p_qk);
|
||||||
|
|
||||||
|
void _make_quadrant_dirty(Map<Vector2i, TileMapQuadrant>::Element *Q);
|
||||||
|
void _make_all_quadrants_dirty();
|
||||||
|
void _queue_update_dirty_quadrants();
|
||||||
|
|
||||||
|
void _update_dirty_quadrants();
|
||||||
|
|
||||||
|
void _recreate_layer_internals(int p_layer);
|
||||||
|
void _recreate_internals();
|
||||||
|
|
||||||
|
void _erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q);
|
||||||
|
void _clear_layer_internals(int p_layer);
|
||||||
|
void _clear_internals();
|
||||||
|
|
||||||
|
// Rect caching.
|
||||||
|
void _recompute_rect_cache();
|
||||||
|
|
||||||
|
// Per-system methods.
|
||||||
|
bool _rendering_quadrant_order_dirty = false;
|
||||||
|
void _rendering_notification(int p_what);
|
||||||
|
void _rendering_update_layer(int p_layer);
|
||||||
|
void _rendering_cleanup_layer(int p_layer);
|
||||||
|
void _rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list);
|
||||||
|
void _rendering_create_quadrant(TileMapQuadrant *p_quadrant);
|
||||||
|
void _rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant);
|
||||||
|
void _rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant);
|
||||||
|
|
||||||
|
Transform2D last_valid_transform;
|
||||||
|
Transform2D new_transform;
|
||||||
|
void _physics_notification(int p_what);
|
||||||
|
void _physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list);
|
||||||
|
void _physics_cleanup_quadrant(TileMapQuadrant *p_quadrant);
|
||||||
|
void _physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant);
|
||||||
|
|
||||||
|
void _navigation_notification(int p_what);
|
||||||
|
void _navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list);
|
||||||
|
void _navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant);
|
||||||
|
void _navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant);
|
||||||
|
|
||||||
|
void _scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list);
|
||||||
|
void _scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant);
|
||||||
|
void _scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant);
|
||||||
|
|
||||||
|
// Terrains.
|
||||||
|
Set<TileSet::TerrainsPattern> _get_valid_terrains_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<TerrainConstraint> p_constraints);
|
||||||
|
|
||||||
|
// Set and get tiles from data arrays.
|
||||||
|
void _set_tile_data(int p_layer, const Vector<int> &p_data);
|
||||||
|
Vector<int> _get_tile_data(int p_layer) const;
|
||||||
|
|
||||||
|
void _build_runtime_update_tile_data(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list);
|
||||||
|
|
||||||
|
void _tile_set_changed();
|
||||||
|
bool _tile_set_changed_deferred_update_needed = false;
|
||||||
|
void _tile_set_changed_deferred_update();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
void _notification(int p_what);
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
public:
|
||||||
|
static Vector2i transform_coords_layout(Vector2i p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout);
|
||||||
|
|
||||||
|
enum {
|
||||||
|
INVALID_CELL = -1
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
virtual Rect2 _edit_get_rect() const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void set_tileset(const Ref<TileSet> &p_tileset);
|
||||||
|
Ref<TileSet> get_tileset() const;
|
||||||
|
|
||||||
|
void set_quadrant_size(int p_size);
|
||||||
|
int get_quadrant_size() const;
|
||||||
|
|
||||||
|
static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame = -1, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0), const TileData *p_tile_data_override = nullptr);
|
||||||
|
|
||||||
|
// Layers management.
|
||||||
|
int get_layers_count() const;
|
||||||
|
void add_layer(int p_to_pos);
|
||||||
|
void move_layer(int p_layer, int p_to_pos);
|
||||||
|
void remove_layer(int p_layer);
|
||||||
|
void set_layer_name(int p_layer, String p_name);
|
||||||
|
String get_layer_name(int p_layer) const;
|
||||||
|
void set_layer_enabled(int p_layer, bool p_visible);
|
||||||
|
bool is_layer_enabled(int p_layer) const;
|
||||||
|
void set_layer_modulate(int p_layer, Color p_modulate);
|
||||||
|
Color get_layer_modulate(int p_layer) const;
|
||||||
|
void set_layer_y_sort_enabled(int p_layer, bool p_enabled);
|
||||||
|
bool is_layer_y_sort_enabled(int p_layer) const;
|
||||||
|
void set_layer_y_sort_origin(int p_layer, int p_y_sort_origin);
|
||||||
|
int get_layer_y_sort_origin(int p_layer) const;
|
||||||
|
void set_layer_z_index(int p_layer, int p_z_index);
|
||||||
|
int get_layer_z_index(int p_layer) const;
|
||||||
|
void set_selected_layer(int p_layer_id); // For editor use.
|
||||||
|
int get_selected_layer() const;
|
||||||
|
|
||||||
|
void set_collision_animatable(bool p_enabled);
|
||||||
|
bool is_collision_animatable() const;
|
||||||
|
|
||||||
|
// Debug visibility modes.
|
||||||
|
void set_collision_visibility_mode(VisibilityMode p_show_collision);
|
||||||
|
VisibilityMode get_collision_visibility_mode();
|
||||||
|
|
||||||
|
void set_navigation_visibility_mode(VisibilityMode p_show_navigation);
|
||||||
|
VisibilityMode get_navigation_visibility_mode();
|
||||||
|
|
||||||
|
// Cells accessors.
|
||||||
|
void set_cell(int p_layer, const Vector2i &p_coords, int p_source_id = -1, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE);
|
||||||
|
int get_cell_source_id(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
|
||||||
|
Vector2i get_cell_atlas_coords(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
|
||||||
|
int get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
|
||||||
|
|
||||||
|
// Patterns.
|
||||||
|
Ref<TileMapPattern> get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array);
|
||||||
|
Vector2i map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, Ref<TileMapPattern> p_pattern);
|
||||||
|
void set_pattern(int p_layer, Vector2i p_position, const Ref<TileMapPattern> p_pattern);
|
||||||
|
|
||||||
|
// Terrains.
|
||||||
|
Set<TerrainConstraint> get_terrain_constraints_from_removed_cells_list(int p_layer, const Set<Vector2i> &p_to_replace, int p_terrain_set, bool p_ignore_empty_terrains = true) const; // Not exposed.
|
||||||
|
Set<TerrainConstraint> get_terrain_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; // Not exposed.
|
||||||
|
Map<Vector2i, TileSet::TerrainsPattern> terrain_wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TerrainConstraint> p_constraints); // Not exposed.
|
||||||
|
void set_cells_from_surrounding_terrains(int p_layer, TypedArray<Vector2i> p_coords_array, int p_terrain_set, bool p_ignore_empty_terrains = true);
|
||||||
|
|
||||||
|
// Not exposed to users
|
||||||
|
TileMapCell get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
|
||||||
|
Map<Vector2i, TileMapQuadrant> *get_quadrant_map(int p_layer);
|
||||||
|
int get_effective_quadrant_size(int p_layer) const;
|
||||||
|
//---
|
||||||
|
|
||||||
|
virtual void set_y_sort_enabled(bool p_enable) override;
|
||||||
|
|
||||||
|
Vector2 map_to_world(const Vector2i &p_pos) const;
|
||||||
|
Vector2i world_to_map(const Vector2 &p_pos) const;
|
||||||
|
|
||||||
|
bool is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const;
|
||||||
|
Vector2i get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const;
|
||||||
|
|
||||||
|
TypedArray<Vector2i> get_used_cells(int p_layer) const;
|
||||||
|
Rect2 get_used_rect(); // Not const because of cache
|
||||||
|
|
||||||
|
// Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems
|
||||||
|
virtual void set_light_mask(int p_light_mask) override;
|
||||||
|
virtual void set_material(const Ref<Material> &p_material) override;
|
||||||
|
virtual void set_use_parent_material(bool p_use_parent_material) override;
|
||||||
|
virtual void set_texture_filter(CanvasItem::TextureFilter p_texture_filter) override;
|
||||||
|
virtual void set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) override;
|
||||||
|
|
||||||
|
// For finding tiles from collision.
|
||||||
|
Vector2i get_coords_for_body_rid(RID p_physics_body);
|
||||||
|
|
||||||
|
// Fixing a nclearing methods.
|
||||||
|
void fix_invalid_tiles();
|
||||||
|
|
||||||
|
// Clears tiles from a given layer
|
||||||
|
void clear_layer(int p_layer);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
// Force a TileMap update
|
||||||
|
void force_update(int p_layer = -1);
|
||||||
|
|
||||||
|
// Helpers?
|
||||||
|
TypedArray<Vector2i> get_surrounding_tiles(Vector2i coords);
|
||||||
|
void draw_cells_outline(Control *p_control, Set<Vector2i> p_cells, Color p_color, Transform2D p_transform = Transform2D());
|
||||||
|
|
||||||
|
// Virtual function to modify the TileData at runtime
|
||||||
|
GDVIRTUAL2R(bool, _use_tile_data_runtime_update, int, Vector2i);
|
||||||
|
GDVIRTUAL3(_tile_data_runtime_update, int, Vector2i, TileData *);
|
||||||
|
|
||||||
|
// Configuration warnings.
|
||||||
|
TypedArray<String> get_configuration_warnings() const override;
|
||||||
|
|
||||||
|
TileMap();
|
||||||
|
~TileMap();
|
||||||
|
};
|
||||||
|
|
||||||
|
VARIANT_ENUM_CAST(TileMap::VisibilityMode);
|
||||||
|
|
||||||
|
#endif // TILE_MAP_H
|
60
vector3i.cpp
Normal file
60
vector3i.cpp
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* vector3i.cpp */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#include "vector3i.h"
|
||||||
|
|
||||||
|
void Vector3i::set_axis(const int p_axis, const int32_t p_value) {
|
||||||
|
ERR_FAIL_INDEX(p_axis, 3);
|
||||||
|
coord[p_axis] = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t Vector3i::get_axis(const int p_axis) const {
|
||||||
|
ERR_FAIL_INDEX_V(p_axis, 3, 0);
|
||||||
|
return operator[](p_axis);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i::Axis Vector3i::min_axis_index() const {
|
||||||
|
return x < y ? (x < z ? Vector3i::AXIS_X : Vector3i::AXIS_Z) : (y < z ? Vector3i::AXIS_Y : Vector3i::AXIS_Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i::Axis Vector3i::max_axis_index() const {
|
||||||
|
return x < y ? (y < z ? Vector3i::AXIS_Z : Vector3i::AXIS_Y) : (x < z ? Vector3i::AXIS_Z : Vector3i::AXIS_X);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i Vector3i::clamp(const Vector3i &p_min, const Vector3i &p_max) const {
|
||||||
|
return Vector3i(
|
||||||
|
CLAMP(x, p_min.x, p_max.x),
|
||||||
|
CLAMP(y, p_min.y, p_max.y),
|
||||||
|
CLAMP(z, p_min.z, p_max.z));
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i::operator String() const {
|
||||||
|
return "(" + itos(x) + ", " + itos(y) + ", " + itos(z) + ")";
|
||||||
|
}
|
295
vector3i.h
Normal file
295
vector3i.h
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
/*************************************************************************/
|
||||||
|
/* vector3i.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#ifndef VECTOR3I_H
|
||||||
|
#define VECTOR3I_H
|
||||||
|
|
||||||
|
#include "core/ustring.h"
|
||||||
|
#include "core/typedefs.h"
|
||||||
|
|
||||||
|
#ifndef SIGN
|
||||||
|
#define SIGN(m_v) (((m_v) == 0) ? (0.0) : (((m_v) < 0) ? (-1.0) : (+1.0)))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct Vector3i {
|
||||||
|
enum Axis {
|
||||||
|
AXIS_X,
|
||||||
|
AXIS_Y,
|
||||||
|
AXIS_Z,
|
||||||
|
};
|
||||||
|
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
int32_t x;
|
||||||
|
int32_t y;
|
||||||
|
int32_t z;
|
||||||
|
};
|
||||||
|
|
||||||
|
int32_t coord[3] = { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
_FORCE_INLINE_ const int32_t &operator[](const int p_axis) const {
|
||||||
|
return coord[p_axis];
|
||||||
|
}
|
||||||
|
|
||||||
|
_FORCE_INLINE_ int32_t &operator[](const int p_axis) {
|
||||||
|
return coord[p_axis];
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_axis(const int p_axis, const int32_t p_value);
|
||||||
|
int32_t get_axis(const int p_axis) const;
|
||||||
|
|
||||||
|
Vector3i::Axis min_axis_index() const;
|
||||||
|
Vector3i::Axis max_axis_index() const;
|
||||||
|
|
||||||
|
_FORCE_INLINE_ void zero();
|
||||||
|
|
||||||
|
_FORCE_INLINE_ Vector3i abs() const;
|
||||||
|
_FORCE_INLINE_ Vector3i sign() const;
|
||||||
|
Vector3i clamp(const Vector3i &p_min, const Vector3i &p_max) const;
|
||||||
|
|
||||||
|
/* Operators */
|
||||||
|
|
||||||
|
_FORCE_INLINE_ Vector3i &operator+=(const Vector3i &p_v);
|
||||||
|
_FORCE_INLINE_ Vector3i operator+(const Vector3i &p_v) const;
|
||||||
|
_FORCE_INLINE_ Vector3i &operator-=(const Vector3i &p_v);
|
||||||
|
_FORCE_INLINE_ Vector3i operator-(const Vector3i &p_v) const;
|
||||||
|
_FORCE_INLINE_ Vector3i &operator*=(const Vector3i &p_v);
|
||||||
|
_FORCE_INLINE_ Vector3i operator*(const Vector3i &p_v) const;
|
||||||
|
_FORCE_INLINE_ Vector3i &operator/=(const Vector3i &p_v);
|
||||||
|
_FORCE_INLINE_ Vector3i operator/(const Vector3i &p_v) const;
|
||||||
|
_FORCE_INLINE_ Vector3i &operator%=(const Vector3i &p_v);
|
||||||
|
_FORCE_INLINE_ Vector3i operator%(const Vector3i &p_v) const;
|
||||||
|
|
||||||
|
_FORCE_INLINE_ Vector3i &operator*=(const int32_t p_scalar);
|
||||||
|
_FORCE_INLINE_ Vector3i operator*(const int32_t p_scalar) const;
|
||||||
|
_FORCE_INLINE_ Vector3i &operator/=(const int32_t p_scalar);
|
||||||
|
_FORCE_INLINE_ Vector3i operator/(const int32_t p_scalar) const;
|
||||||
|
_FORCE_INLINE_ Vector3i &operator%=(const int32_t p_scalar);
|
||||||
|
_FORCE_INLINE_ Vector3i operator%(const int32_t p_scalar) const;
|
||||||
|
|
||||||
|
_FORCE_INLINE_ Vector3i operator-() const;
|
||||||
|
|
||||||
|
_FORCE_INLINE_ bool operator==(const Vector3i &p_v) const;
|
||||||
|
_FORCE_INLINE_ bool operator!=(const Vector3i &p_v) const;
|
||||||
|
_FORCE_INLINE_ bool operator<(const Vector3i &p_v) const;
|
||||||
|
_FORCE_INLINE_ bool operator<=(const Vector3i &p_v) const;
|
||||||
|
_FORCE_INLINE_ bool operator>(const Vector3i &p_v) const;
|
||||||
|
_FORCE_INLINE_ bool operator>=(const Vector3i &p_v) const;
|
||||||
|
|
||||||
|
operator String() const;
|
||||||
|
|
||||||
|
_FORCE_INLINE_ Vector3i() {}
|
||||||
|
_FORCE_INLINE_ Vector3i(const int32_t p_x, const int32_t p_y, const int32_t p_z) {
|
||||||
|
x = p_x;
|
||||||
|
y = p_y;
|
||||||
|
z = p_z;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector3i Vector3i::abs() const {
|
||||||
|
return Vector3i(ABS(x), ABS(y), ABS(z));
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i Vector3i::sign() const {
|
||||||
|
return Vector3i(SIGN(x), SIGN(y), SIGN(z));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Operators */
|
||||||
|
|
||||||
|
Vector3i &Vector3i::operator+=(const Vector3i &p_v) {
|
||||||
|
x += p_v.x;
|
||||||
|
y += p_v.y;
|
||||||
|
z += p_v.z;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i Vector3i::operator+(const Vector3i &p_v) const {
|
||||||
|
return Vector3i(x + p_v.x, y + p_v.y, z + p_v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i &Vector3i::operator-=(const Vector3i &p_v) {
|
||||||
|
x -= p_v.x;
|
||||||
|
y -= p_v.y;
|
||||||
|
z -= p_v.z;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i Vector3i::operator-(const Vector3i &p_v) const {
|
||||||
|
return Vector3i(x - p_v.x, y - p_v.y, z - p_v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i &Vector3i::operator*=(const Vector3i &p_v) {
|
||||||
|
x *= p_v.x;
|
||||||
|
y *= p_v.y;
|
||||||
|
z *= p_v.z;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i Vector3i::operator*(const Vector3i &p_v) const {
|
||||||
|
return Vector3i(x * p_v.x, y * p_v.y, z * p_v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i &Vector3i::operator/=(const Vector3i &p_v) {
|
||||||
|
x /= p_v.x;
|
||||||
|
y /= p_v.y;
|
||||||
|
z /= p_v.z;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i Vector3i::operator/(const Vector3i &p_v) const {
|
||||||
|
return Vector3i(x / p_v.x, y / p_v.y, z / p_v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i &Vector3i::operator%=(const Vector3i &p_v) {
|
||||||
|
x %= p_v.x;
|
||||||
|
y %= p_v.y;
|
||||||
|
z %= p_v.z;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i Vector3i::operator%(const Vector3i &p_v) const {
|
||||||
|
return Vector3i(x % p_v.x, y % p_v.y, z % p_v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i &Vector3i::operator*=(const int32_t p_scalar) {
|
||||||
|
x *= p_scalar;
|
||||||
|
y *= p_scalar;
|
||||||
|
z *= p_scalar;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
_FORCE_INLINE_ Vector3i operator*(const int32_t p_scalar, const Vector3i &p_vector) {
|
||||||
|
return p_vector * p_scalar;
|
||||||
|
}
|
||||||
|
|
||||||
|
_FORCE_INLINE_ Vector3i operator*(const int64_t p_scalar, const Vector3i &p_vector) {
|
||||||
|
return p_vector * p_scalar;
|
||||||
|
}
|
||||||
|
|
||||||
|
_FORCE_INLINE_ Vector3i operator*(const float p_scalar, const Vector3i &p_vector) {
|
||||||
|
return p_vector * p_scalar;
|
||||||
|
}
|
||||||
|
|
||||||
|
_FORCE_INLINE_ Vector3i operator*(const double p_scalar, const Vector3i &p_vector) {
|
||||||
|
return p_vector * p_scalar;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i Vector3i::operator*(const int32_t p_scalar) const {
|
||||||
|
return Vector3i(x * p_scalar, y * p_scalar, z * p_scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i &Vector3i::operator/=(const int32_t p_scalar) {
|
||||||
|
x /= p_scalar;
|
||||||
|
y /= p_scalar;
|
||||||
|
z /= p_scalar;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i Vector3i::operator/(const int32_t p_scalar) const {
|
||||||
|
return Vector3i(x / p_scalar, y / p_scalar, z / p_scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i &Vector3i::operator%=(const int32_t p_scalar) {
|
||||||
|
x %= p_scalar;
|
||||||
|
y %= p_scalar;
|
||||||
|
z %= p_scalar;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i Vector3i::operator%(const int32_t p_scalar) const {
|
||||||
|
return Vector3i(x % p_scalar, y % p_scalar, z % p_scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i Vector3i::operator-() const {
|
||||||
|
return Vector3i(-x, -y, -z);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Vector3i::operator==(const Vector3i &p_v) const {
|
||||||
|
return (x == p_v.x && y == p_v.y && z == p_v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Vector3i::operator!=(const Vector3i &p_v) const {
|
||||||
|
return (x != p_v.x || y != p_v.y || z != p_v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Vector3i::operator<(const Vector3i &p_v) const {
|
||||||
|
if (x == p_v.x) {
|
||||||
|
if (y == p_v.y) {
|
||||||
|
return z < p_v.z;
|
||||||
|
} else {
|
||||||
|
return y < p_v.y;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return x < p_v.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Vector3i::operator>(const Vector3i &p_v) const {
|
||||||
|
if (x == p_v.x) {
|
||||||
|
if (y == p_v.y) {
|
||||||
|
return z > p_v.z;
|
||||||
|
} else {
|
||||||
|
return y > p_v.y;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return x > p_v.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Vector3i::operator<=(const Vector3i &p_v) const {
|
||||||
|
if (x == p_v.x) {
|
||||||
|
if (y == p_v.y) {
|
||||||
|
return z <= p_v.z;
|
||||||
|
} else {
|
||||||
|
return y < p_v.y;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return x < p_v.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Vector3i::operator>=(const Vector3i &p_v) const {
|
||||||
|
if (x == p_v.x) {
|
||||||
|
if (y == p_v.y) {
|
||||||
|
return z >= p_v.z;
|
||||||
|
} else {
|
||||||
|
return y > p_v.y;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return x > p_v.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Vector3i::zero() {
|
||||||
|
x = y = z = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // VECTOR3I_H
|
Loading…
Reference in New Issue
Block a user