Experimenting...

This commit is contained in:
KP 2024-07-24 00:08:44 -05:00
parent a63c6e683e
commit 5a13579849
6 changed files with 346 additions and 6833 deletions

File diff suppressed because it is too large Load diff

View file

@ -251,7 +251,7 @@ bool clip2tri::triangulateComplex(vector<Point> &outputTriangles, const Path &ou
F64(S64(steiner_points->at(j).x * CLIPPER_SCALE_FACT)),
F64(S64(steiner_points->at(j).y * CLIPPER_SCALE_FACT)),
});
cdt->AddPoint(steiners[j]);
//cdt->AddPoint(steiners[j]);
}
}
// end: steiner points

View file

@ -5,7 +5,7 @@
#include "KP3D_Log.h"
namespace {
const float EPSILON = 0.0001f;
const float EPSILON = 1.0f / 128.0f;
}
namespace kp3d {
@ -50,14 +50,102 @@ std::vector<std::vector<Vec2>> SplitComplexPolygon(std::vector<Vec2> polygon)
output.push_back(polygon_excerpt);
}
if (output.size() > 1)
{
KP3D_LOG_INFO("Split: ");
for (int i = 0; i < output.size(); i++)
KP3D_LOG_INFO("{} size: {}", i, output[i].size());
}
return output;
}
std::vector<std::vector<Vec2>> ClipPolygonHoles(std::vector<Vec2> polygon, std::vector<std::vector<Vec2>> holes)
{
if (holes.empty())
{
return { polygon };
}
for (std::vector<Vec2>& hole : holes)
{
polygon.push_back(polygon[0]);
for (Vec2 point: hole)
polygon.push_back(point);
polygon.push_back(hole[0]);
}
//polygon.push_back(polygon[0]);
//polygon.erase(std::unique(polygon.begin(), polygon.end()), polygon.end());
return SplitComplexPolygon(polygon);
#if 0
using namespace Clipper2Lib;
if (holes.empty())
{
return {polygon};
}
if (holes.size() > 1)
holes.pop_back();
//ClipperD c;
PathsD solution;
// PolyPathD tree;
PathD complex_polygon;
for (const Vec2& v: polygon)
{
complex_polygon.push_back({v.x, v.y});
}
// PolyTreeD cp(&tree, complex_polygon);
PathsD subjects;
subjects.push_back(complex_polygon);
PathsD clips;
KP3D_LOG_INFO("# holes: {}", holes.size());
for (const auto& vl: holes)
{
PathD clip;
for (const Vec2& v : vl) {
clip.push_back({ v.x, v.y });
//complex_polygon.push_back({v.x, v.y});
}
clips.push_back(clip);
// cp.AddChild(clip);
//tree.AddChild(clip);
}
// tree.AddChild(complex_polygon); ?
//c.AddSubject({complex_polygon});
//c.AddSubject(clips);
//c.AddClip(clips);
//c.AddClip(clips);
// PolyTreeD clipper_output;
//PathsD clipper_output;
//c.Execute(ClipType::Difference, FillRule::NonZero, clipper_output);
PathsD clipper_output = Difference(subjects, clips, FillRule::EvenOdd);
std::vector<std::vector<Vec2>> output;
for (auto& o: clipper_output)
{
std::vector<Vec2> polygon_excerpt;
for (const auto& point: o)
polygon_excerpt.push_back({(float) point.x, (float) point.y});
output.push_back(polygon_excerpt);
}
if (output.size() > 0)
{
KP3D_LOG_INFO("Split ({}):", output.size());
for (int i = 0; i < output.size(); i++)
KP3D_LOG_INFO("{} size: {}", i, output[i].size());
}
if (output.empty())
{
return { polygon };
}
return output;
#endif
}
} // namespace kp3d

View file

@ -54,5 +54,7 @@ bool PointInLine(XYf point, XYf line_start, XYf line_end);
float Distance(XYf a, XYf b);
std::vector<std::vector<Vec2>> SplitComplexPolygon(std::vector<Vec2> polygon);
std::vector<std::vector<Vec2>> ClipPolygonHoles(std::vector<Vec2> polygon, std::vector<std::vector<Vec2>> holes);
} // namespace kp3d

View file

@ -4,6 +4,7 @@
#include <clip2tri/clip2tri.h>
#include <clipper/clipper.hpp>
#include <clipper2/clipper.h>
#include <poly2tri/poly2tri.h>
#include "KP3D_Renderer3D.h"
@ -13,9 +14,49 @@
namespace {
using namespace kp3d;
bool SectorContains(const kp3d::Sector& outer, const kp3d::Sector& inner) {
} // namespace
using namespace Clipper2Lib;
using namespace kp3d;
PathD outer_path;
for (const Wall& l: outer.walls)
outer_path.push_back({l.start.x, l.start.y});
PathD inner_path;
for (const Wall& l: inner.walls)
inner_path.push_back({l.start.x, l.start.y});
bool completely_inside = false;
bool partially_inside = false;
int count = 0;
int partial_count = 0;
for (auto& p : inner_path)
{
PointInPolygonResult status = Clipper2Lib::PointInPolygon(p, outer_path);
if (status == PointInPolygonResult::IsInside)
count++;
if (status == PointInPolygonResult::IsOn)
partial_count++;
}
if (count == inner_path.size())
completely_inside = true;
if (partial_count > 0 && count > 0)
partially_inside = true;
// KP3D_LOG_INFO("COMPLETE: {}, PARTIAL: {} / {}", count, partial_count, child_path.size());
if (completely_inside)
{
//s.children.push_back(&s2);
//s2.parents.push_back(s.id);
}
return completely_inside || (count > 0 && partial_count < 4);
}
}
namespace kp3d {
@ -29,13 +70,11 @@ Map::~Map()
ErrCode Map::LoadFromFile(const std::string& path)
{
// TODO: ...
return FAILURE;
}
ErrCode Map::SaveToFile(const std::string& path)
{
// TODO: ...
return FAILURE;
}
@ -44,7 +83,7 @@ ErrCode Map::SaveToFile(const std::string& path)
*/
void Map::BuildFlat(Sector& sector, Flat& flat, bool invert)
{
const float e = 0.001f;
const float e = 1.0f / 128.0f;
if (sector.inverted)
invert ^= 1;
@ -53,75 +92,78 @@ void Map::BuildFlat(Sector& sector, Flat& flat, bool invert)
for (const auto& st: flat.steiner_points)
steiner_points.emplace_back(st.x, st.z);
std::vector<std::vector<c2t::Point>> subsector_polygons; // unused for now
std::vector<Sector*> secs_inside;
for (Sector* sp : sector.children)
std::vector<std::vector<c2t::Point>> subsector_polygons;
for (Sector* s: sector.children)
{
std::vector<c2t::Point> ss;
Sector& s = *sp;
if (!s.children.empty())
continue;
for (Wall& l : s.walls)
float area = 0.0f;
for (Wall& l : s->walls)
{
if (PointInPolygon(sector.walls, l.start))
if (s->inverted)
{
ss.push_back({ l.start.x, l.start.y });
}
}
for (Wall& l : s.walls)
{
if (s.inverted)
{
if (!FloatCmp(s.floor.base_height, sector.floor.base_height))
s.floor.texture = nullptr;
if (!FloatCmp(s.ceiling.base_height, sector.ceiling.base_height))
s.ceiling.texture = nullptr;
//if (s.ceiling.base_height <= sector.ceiling.base_height + 0.000001f)
// s.ceiling.texture = nullptr;
continue;
if (FloatCmp(s->floor.base_height, sector.floor.base_height, e))
s->floor.texture = nullptr;
if (FloatCmp(s->ceiling.base_height, sector.ceiling.base_height, e))
s->ceiling.texture = nullptr;
//continue;
}
else
{
l.textures[TEX_FRONT] = nullptr;
l.flags = Wall::FLAG_OPENING;
l.portal = &sector;
}
ss.push_back({l.start.x, l.start.y});
area += l.start.x * l.end.y;
area -= l.end.x * l.start.y;
}
//if (area > 0)//<= 0.0f)
// std::reverse(ss.begin(), ss.end());
subsector_polygons.push_back(ss);
}
std::vector<c2t::Point> sector_polygons;
std::vector<c2t::Point> flat_polygons;
for (Wall& l: sector.walls)
sector_polygons.emplace_back(l.start.x, l.start.y);
// Triangulate the sector floors and ceilings and send the result to the mesh. To do this we're using version of the Clipper
// library merged with poly2tri, called clip2tri, which I've since modified to support Steiner points.
c2t::clip2tri clipper;
std::vector<c2t::Point> clipper_out;
clipper.triangulate(subsector_polygons, clipper_out, sector_polygons, &steiner_points);
const float MAX = 1000000.0f;
float min_height = MAX;
float max_height = -MAX;
for (int i = 0; i < clipper_out.size(); i += 3)
flat_polygons.emplace_back(l.start.x, l.start.y);
KP3D_LOG_INFO("TRIANGULATING SECTOR: {}", sector.id);
//std::vector<std::vector<Vec2>> polygons = ClipPolygonHoles(flat_polygons, subsector_polygons);
//for (const std::vector<Vec2>& polygon: polygons)
//const auto& polygon = polygons[1 % polygons.size()];
{
c2t::Point a = clipper_out.at(i + 0);
c2t::Point b = clipper_out.at(i + 1);
c2t::Point c = clipper_out.at(i + 2);
float au = a.x * 0.5f, av = a.y * 0.5f;
float bu = b.x * 0.5f, bv = b.y * 0.5f;
float cu = c.x * 0.5f, cv = c.y * 0.5f;
//std::vector<c2t::Point> sector_polygons;
//for (const Vec2& l: sector.walls)
// sector_polygons.push_back({l.x, l.y});
// Build mesh data for floor
// Triangulate the sector floors and ceilings and send the result to the mesh. To do this we're using version of the Clipper
// library merged with poly2tri, called clip2tri, which I've since modified to support Steiner points.
c2t::clip2tri clipper;
std::vector<c2t::Point> clipper_out;
clipper.triangulate(subsector_polygons, clipper_out, flat_polygons, &steiner_points);
const float MAX = 1000000.0f;
float min_height = MAX;
float max_height = -MAX;
for (int i = 0; i < clipper_out.size(); i += 3)
{
c2t::Point a = clipper_out.at(i + 0);
c2t::Point b = clipper_out.at(i + 1);
c2t::Point c = clipper_out.at(i + 2);
float au = a.x * 0.5f, av = a.y * 0.5f;
float bu = b.x * 0.5f, bv = b.y * 0.5f;
float cu = c.x * 0.5f, cv = c.y * 0.5f;
// Build mesh data for floor
Vec3 fva = Vec3(a.x, flat.base_height, a.y);
Vec3 fvb = Vec3(b.x, flat.base_height, b.y);
Vec3 fvc = Vec3(c.x, flat.base_height, c.y);
for (const Vec3& steiner: flat.steiner_points)
for (const Vec3& steiner : flat.steiner_points)
{
if (FloatCmp(fva.x, steiner.x, e) && FloatCmp(fva.z, steiner.z, e)) fva.y += steiner.y;
if (FloatCmp(fvb.x, steiner.x, e) && FloatCmp(fvb.z, steiner.z, e)) fvb.y += steiner.y;
@ -147,7 +189,7 @@ void Map::BuildFlat(Sector& sector, Flat& flat, bool invert)
flat.triangulated_data.push_back(vtxb);
flat.triangulated_data.push_back(vtxc);
if (flat.texture)
m_mesh.AddBatch(flat.texture, {vtxa, vtxb, vtxc}, !invert);
m_mesh.AddBatch(flat.texture, { vtxa, vtxb, vtxc }, !invert);
}
}
}
@ -166,47 +208,27 @@ void Map::BuildQuad(Sector& sector, Flat& flat_top, Flat& flat_bottom, const Tex
if (sector.inverted)
flip ^= 1;
Vec3 p1 = Vec3(pos_a.x, pos_a.y, pos_a.z);
Vec3 p2 = Vec3(pos_a.x, pos_b.y, pos_a.z);
Vec3 p3 = Vec3(pos_b.x, pos_a.y, pos_b.z);
Vec3 p4 = Vec3(pos_b.x, pos_b.y, pos_b.z);
std::vector<Vec2> points;
points.emplace_back(0.0f);
for (const Vertex3D& v: flat_bottom.triangulated_data)
{
if (!PointInLine({v.position.x, v.position.z}, {pos_a.x, pos_a.z}, {pos_b.x, pos_b.z}))
continue;
// Project 3D point into 2D
Vec2 mpos = {Distance({v.position.x, v.position.z}, {p1.x, p1.z}), v.position.y};
// Overwrite the start point if we've landed on it
if (FloatCmp(v.position.x, p1.x, 0.0001f) && FloatCmp(v.position.z, p1.z, 0.0001f))
points[0] = mpos;
Vec2 mpos = {Distance({v.position.x, v.position.z}, {pos_a.x, pos_a.z}), v.position.y};
points.push_back(mpos);
}
std::vector<Vec2> top_points;
top_points.emplace_back(Distance({ p4.x, p4.z }, { p1.x, p1.z }), p4.y); // This may be re-enabled, BUT
for (const Vertex3D& v: flat_top.triangulated_data)
{
if (!PointInLine({v.position.x, v.position.z}, {pos_a.x, pos_a.z}, {pos_b.x, pos_b.z}))
continue;
// Project 3D point into 2D
Vec2 mpos = {Distance({v.position.x, v.position.z}, {p1.x, p1.z}), v.position.y};
if (FloatCmp(v.position.x, p4.x, 0.0001f) && FloatCmp(v.position.z, p4.z, 0.0001f))
top_points[0] = mpos;
Vec2 mpos = {Distance({v.position.x, v.position.z}, {pos_a.x, pos_a.z}), v.position.y};
top_points.push_back(mpos);
}
std::sort(points.begin() + 1, points.end(), [&](Vec2 a, Vec2 b) { return a.x < b.x; });
std::sort(top_points.begin() + 1 , top_points.end(), [&](Vec2 a, Vec2 b) { return a.x > b.x; }); // ^^^^ If you do, add +1 to begin()
std::sort(points.begin(), points.end(), [&](Vec2 a, Vec2 b) { return a.x < b.x; });
std::sort(top_points.begin(), top_points.end(), [&](Vec2 a, Vec2 b) { return a.x > b.x; });
points.insert(points.end(), top_points.begin(), top_points.end());
float angle = atan2({p4.z - p1.z}, {p4.x - p1.x});
float angle = atan2({pos_b.z - pos_a.z}, {pos_b.x - pos_a.x});
points.erase(unique(points.begin(), points.end()), points.end());
@ -215,53 +237,29 @@ void Map::BuildQuad(Sector& sector, Flat& flat_top, Flat& flat_bottom, const Tex
// There's a chance we may have ended up with a complex polygon, which poly2tri isn't able to handle. So our solution is to
// break them up.
std::vector<std::vector<Vec2>> polygons = SplitComplexPolygon(points);
//auto& polygon = polygons.front();
for (const std::vector<Vec2>& polygon: polygons)
{
std::vector<std::vector<c2t::Point>> holes; // unused for now
std::vector<c2t::Point> wall_polygon;
for (Vec2 p : polygon)
for (Vec2 p: polygon)
wall_polygon.emplace_back(p.x, p.y);
c2t::clip2tri clipper;
std::vector<c2t::Point> clipper_out;
clipper.triangulate(holes, clipper_out, wall_polygon);
p2t::CDT cdt();
clipper.triangulate({}, clipper_out, wall_polygon);
for (int i = 0; i < clipper_out.size(); i += 3)
{
c2t::Point a = clipper_out.at(i + 0);
c2t::Point b = clipper_out.at(i + 1);
c2t::Point c = clipper_out.at(i + 2);
// Safety clamp
if (a.x < 0) a.x = 0;
if (b.x < 0) b.x = 0;
if (c.x < 0) c.x = 0;
if (a.x > top_points[0].x)
a.x = top_points[0].x;
if (b.x > top_points[0].x)
b.x = top_points[0].x;
if (c.x > top_points[0].x)
c.x = top_points[0].x;
//if (flip)
// std::swap(b, c);
float au = a.x * 0.5f, av = a.y * 0.5f;
float bu = b.x * 0.5f, bv = b.y * 0.5f;
float cu = c.x * 0.5f, cv = c.y * 0.5f;
// Build mesh data for wall
Vec3 fva = Vec3(a.x, a.y, 0.0f).Rotated({ 0, 1, 0 }, -ToDegrees(angle)); fva.x += p1.x; fva.z += p1.z;
Vec3 fvb = Vec3(b.x, b.y, 0.0f).Rotated({ 0, 1, 0 }, -ToDegrees(angle)); fvb.x += p1.x; fvb.z += p1.z;
Vec3 fvc = Vec3(c.x, c.y, 0.0f).Rotated({ 0, 1, 0 }, -ToDegrees(angle)); fvc.x += p1.x; fvc.z += p1.z;
bool iba = PointInLine({ fva.x, fva.z }, { pos_a.x, pos_a.z }, { pos_b.x, pos_b.z });
bool ibb = PointInLine({ fvb.x, fvb.z }, { pos_a.x, pos_a.z }, { pos_b.x, pos_b.z });
bool ibc = PointInLine({ fvc.x, fvc.z }, { pos_a.x, pos_a.z }, { pos_b.x, pos_b.z });
Vec3 fva = Vec3(a.x, a.y, 0.0f).Rotated({0, 1, 0}, -ToDegrees(angle)); fva.x += pos_a.x; fva.z += pos_a.z;
Vec3 fvb = Vec3(b.x, b.y, 0.0f).Rotated({0, 1, 0}, -ToDegrees(angle)); fvb.x += pos_a.x; fvb.z += pos_a.z;
Vec3 fvc = Vec3(c.x, c.y, 0.0f).Rotated({0, 1, 0}, -ToDegrees(angle)); fvc.x += pos_a.x; fvc.z += pos_a.z;
// Fix up the UVs so they keep the right scale
float tw = texture ? texture_scale / texture->GetWidth() : 0.0f;
@ -281,20 +279,11 @@ void Map::BuildWall(Sector& sector, Wall& wall)
Vec3 b = {wall.end.x, 0.0f, wall.end.y};
BuildQuad(sector, sector.floor, sector.ceiling, wall.textures[TEX_FRONT], a, b, false, false, false, wall.uv_offset[TEX_FRONT]);
if (wall.flags & Wall::OPENING)
if (wall.flags & Wall::FLAG_OPENING)
{
Flat& ft = sector.floor;
Flat& fb = wall.portal->floor;
a.y = ft.base_height;
b.y = fb.base_height;
BuildQuad(sector, ft, fb, wall.textures[TEX_LOWER], a, b, false, false, false, wall.uv_offset[TEX_LOWER]);
Flat& ct = sector.ceiling;
Flat& cb = wall.portal->ceiling;
a.y = ct.base_height;
b.y = cb.base_height;
BuildQuad(sector, ct, cb, wall.textures[TEX_UPPER], a, b, true, false, false, wall.uv_offset[TEX_UPPER]);
// Build upper and lower walls for sectors connected to other sectors
BuildQuad(sector, sector.floor, wall.portal->floor, wall.textures[TEX_LOWER], a, b, false, false, false, wall.uv_offset[TEX_LOWER]);
BuildQuad(sector, sector.ceiling, wall.portal->ceiling, wall.textures[TEX_UPPER], a, b, true, false, false, wall.uv_offset[TEX_UPPER]);
}
}
@ -334,8 +323,8 @@ void Map::JoinSectors(Sector& sector)
w1.end.x = l.start.x;
w1.end.y = l.start.y;
w1.textures[TEX_FRONT] = nullptr;
w1.flags = Wall::FLAG_OPENING;
w1.portal = &s;
w1.flags = Wall::OPENING;
Wall w2 = ld;
w2.start.x = w1.end.x;
w2.start.y = w1.end.y;
@ -345,10 +334,10 @@ void Map::JoinSectors(Sector& sector)
InsertLine(sector.walls, i + 1, w1);
InsertLine(sector.walls, i + 2, w2);
l.textures[TEX_FRONT] = nullptr;
// Mark the other one as an opening
// This really shouldn't be necessary I think, but whatever
l.flags = Wall::OPENING;
l.textures[TEX_FRONT] = nullptr;
l.flags = Wall::FLAG_OPENING;
l.portal = &sector;
}
}
@ -427,36 +416,90 @@ void Map::Init()
//build_sector(&tex3, &tex, &tex2, -1.2f, 3.0f, 5, points5, std::size(points5));
build_sector(&tex3, &tex, &tex2, test_u, test_l, 5, points5, std::size(points5), false);
// Build tree
for (Sector& sp: sectors)
// Correct sectors with counter-clockwise linedef order using the shoelace formula,
// also while we're at it grab the sector areas (will be useful later) and reset old data.
std::unordered_map<int, float> sector_areas;
int i = 0;
for (Sector& s: sectors)
{
for (Sector& s: sectors)
{
if (s.id == sp.id)
continue;
// Since we're regenerating the map, we don't want to re-use this data, it may be old.
s.parents.clear();
s.flags = Sector::NO_FLAGS;
s.parent_id = 0;
s.id = ++i;
int cnt = 0;
// Now perform the swap + save the area
float area = 0.0f;
for (Wall& l: s.walls)
{
bool touched = l.flags & Wall::FLAG_TOUCHED;
bool no_collisions = l.flags & Wall::FLAG_NO_COLLISION;
l.flags = Wall::NO_FLAGS;
if (touched)
l.flags |= Wall::FLAG_TOUCHED;
if (no_collisions)
l.flags |= Wall::FLAG_NO_COLLISION;
area += l.start.x * l.end.y;
area -= l.end.x * l.start.y;
}
area *= 0.5f;
sector_areas.emplace(s.id, fabsf(area));
if (area <= 0.0f)
{
std::reverse(s.walls.begin(), s.walls.end());
for (Wall& l: s.walls)
{
if (PointInPolygon(sp.walls, l.start))
{
cnt++;
}
std::swap(l.start, l.end);
}
s.area = fabsf(area);
}
std::sort(sectors.begin(), sectors.end(), [](const Sector& a, const Sector& b) {
return a.area < b.area;
});
for (auto& sector: sectors) {
for (auto& potentialParent: sectors) {
if (sector.id == potentialParent.id)
continue;
if (SectorContains(potentialParent, sector)) {
potentialParent.children.push_back(&sector);
sector.parent_id = potentialParent.id;
break;
}
if (cnt == s.walls.size())
sp.children.push_back(&s);
}
}
KP3D_LOG_INFO("SECTOR HIERARCHY");
for (Sector& s: sectors)
{
KP3D_LOG_INFO("Sector {} (area: {})", s.id, s.area);
KP3D_LOG_INFO(" Parent: ");
KP3D_LOG_INFO(" - {}", s.parent_id ? std::to_string(s.parent_id) : "[NO PARENT]");
KP3D_LOG_INFO(" Children: ");
for (Sector* c: s.children)
{
KP3D_LOG_INFO(" - {}", c->id);
}
if (s.children.empty())
KP3D_LOG_INFO(" - [NO CHILDREN]");
KP3D_LOG_INFO("-----");
}
// Preproc
for (Sector& s : sectors)
for (Sector& s: sectors)
{
// KP3D_LOG_INFO("BUILDING SECTOR: {}", s.id);
// Build up "steiner points" for terrain only
// We'll do this along a grid for now; it should be noted that this will be expected to be part of the sector data later
int num_steiners_along_xy = 50;
float steiner_size = 1.0f;
float steiner_size = 1.1f;
for (int i = 0; i < num_steiners_along_xy; i++)
{
for (int j = 0; j < num_steiners_along_xy; j++)
@ -489,12 +532,14 @@ void Map::Init()
}
JoinSectors(s);
}
for (Sector& s : sectors)
{
// Build level geometry
BuildFlat(s, s.floor, false);
BuildFlat(s, s.ceiling, true);
}
for (Sector& s : sectors)
for (Wall& ld : s.walls)
BuildWall(s, ld);

View file

@ -23,8 +23,10 @@ struct Wall
enum Flags: byte
{
NO_FLAGS,
OPENING
// ...
FLAG_OPENING = 1 << 0,
FLAG_DELETE = 1 << 1,
FLAG_NO_COLLISION = 1 << 2,
FLAG_TOUCHED = 1 << 3
};
const Texture* textures[3];
@ -54,8 +56,13 @@ struct Sector
{
enum Flags: byte
{
NO_FLAGS
// ...
NO_FLAGS,
FLAG_SUBSECTOR = 1 << 0,
FLAG_HAS_SUBSECTOR = 1 << 1,
FLAG_FLIP_SSECTOR_UL_WALLS = 1 << 2,
FLAG_SUBSECTOR_CONNECTED_TO_SOMETHING = 1 << 3,
FLAG_SUBSECTOR_EXEMPT = 1 << 4,
FLAG_DELETE = 1 << 5
};
uint id;
@ -74,6 +81,9 @@ struct Sector
bool inverted = false;
std::vector<Sector*> children;
std::vector<int> parents;
float area = 0.0f;
};