Skip to content

Commit d98fecd

Browse files
committed
multi layer implementation
1 parent 8750e47 commit d98fecd

5 files changed

Lines changed: 449 additions & 63 deletions

File tree

src/application/LayoutNavigationPanelWidget.cpp

Lines changed: 150 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#include "application/LayoutNavigationPanelWidget.h"
22

3+
#include <algorithm>
4+
#include <cmath>
5+
#include <limits>
36
#include <utility>
47
#include <vector>
58

@@ -10,6 +13,16 @@
1013
namespace safecrowd::application {
1114
namespace {
1215

16+
constexpr double kGeometryEpsilon = 1e-4;
17+
18+
QString floorActionId(const std::string& floorId) {
19+
return QString("floor:%1").arg(QString::fromStdString(floorId));
20+
}
21+
22+
bool matchesFloor(const std::string& elementFloorId, const std::string& floorId) {
23+
return floorId.empty() || elementFloorId.empty() || elementFloorId == floorId;
24+
}
25+
1326
QString zoneLabel(const safecrowd::domain::Zone2D& zone) {
1427
const auto id = QString::fromStdString(zone.id);
1528
const auto label = QString::fromStdString(zone.label);
@@ -24,75 +37,166 @@ NavigationTreeNode makeZoneNode(const safecrowd::domain::Zone2D& zone) {
2437
};
2538
}
2639

27-
NavigationTreeNode makeSection(const QString& label, std::vector<NavigationTreeNode> children) {
40+
QString floorLabel(const safecrowd::domain::Floor2D& floor) {
41+
const auto id = QString::fromStdString(floor.id);
42+
const auto label = QString::fromStdString(floor.label);
43+
return label.isEmpty() || label == id ? id : QString("%1 - %2").arg(label, id);
44+
}
45+
46+
QString connectionLabel(const safecrowd::domain::Connection2D& connection) {
47+
const auto id = QString::fromStdString(connection.id);
48+
return id.isEmpty()
49+
? QString("%1 -> %2").arg(QString::fromStdString(connection.fromZoneId), QString::fromStdString(connection.toZoneId))
50+
: id;
51+
}
52+
53+
QString barrierLabel(const safecrowd::domain::Barrier2D& barrier) {
54+
return QString::fromStdString(barrier.id);
55+
}
56+
57+
NavigationTreeNode makeSection(const QString& label, std::vector<NavigationTreeNode> children, const QString& id = {}) {
2858
return {
2959
.label = label,
60+
.id = id,
3061
.children = std::move(children),
3162
.expanded = true,
32-
.selectable = false,
63+
.selectable = !id.isEmpty(),
3364
};
3465
}
3566

36-
template <typename Predicate>
37-
std::vector<NavigationTreeNode> collectZones(
38-
const safecrowd::domain::FacilityLayout2D& layout,
39-
Predicate predicate) {
40-
std::vector<NavigationTreeNode> nodes;
41-
for (const auto& zone : layout.zones) {
42-
if (predicate(zone)) {
43-
nodes.push_back(makeZoneNode(zone));
67+
double distancePointToSegment(
68+
const safecrowd::domain::Point2D& point,
69+
const safecrowd::domain::Point2D& start,
70+
const safecrowd::domain::Point2D& end) {
71+
const auto dx = end.x - start.x;
72+
const auto dy = end.y - start.y;
73+
const auto lengthSquared = (dx * dx) + (dy * dy);
74+
if (lengthSquared <= kGeometryEpsilon) {
75+
return std::hypot(point.x - start.x, point.y - start.y);
76+
}
77+
78+
const auto t = std::clamp(
79+
(((point.x - start.x) * dx) + ((point.y - start.y) * dy)) / lengthSquared,
80+
0.0,
81+
1.0);
82+
return std::hypot(point.x - (start.x + (dx * t)), point.y - (start.y + (dy * t)));
83+
}
84+
85+
double distanceToPolygonBoundary(
86+
const safecrowd::domain::Polygon2D& polygon,
87+
const safecrowd::domain::Point2D& point) {
88+
double best = std::numeric_limits<double>::max();
89+
const auto checkRing = [&](const std::vector<safecrowd::domain::Point2D>& ring) {
90+
if (ring.size() < 2) {
91+
return;
92+
}
93+
for (std::size_t index = 0; index < ring.size(); ++index) {
94+
best = std::min(best, distancePointToSegment(point, ring[index], ring[(index + 1) % ring.size()]));
4495
}
96+
};
97+
98+
checkRing(polygon.outline);
99+
for (const auto& hole : polygon.holes) {
100+
checkRing(hole);
45101
}
46-
return nodes;
102+
return best;
47103
}
48104

49-
std::vector<NavigationTreeNode> buildLayoutTree(const safecrowd::domain::FacilityLayout2D* facilityLayout) {
50-
if (facilityLayout == nullptr) {
51-
return {};
105+
bool barrierTouchesZone(const safecrowd::domain::Barrier2D& barrier, const safecrowd::domain::Zone2D& zone) {
106+
for (const auto& point : barrier.geometry.vertices) {
107+
if (distanceToPolygonBoundary(zone.area, point) <= 0.08) {
108+
return true;
109+
}
52110
}
111+
return false;
112+
}
53113

54-
std::vector<NavigationTreeNode> nodes;
114+
bool isRoomLikeZone(const safecrowd::domain::Zone2D& zone) {
115+
return zone.kind == safecrowd::domain::ZoneKind::Room
116+
|| zone.kind == safecrowd::domain::ZoneKind::Intersection
117+
|| zone.kind == safecrowd::domain::ZoneKind::Unknown
118+
|| zone.kind == safecrowd::domain::ZoneKind::Exit
119+
|| zone.kind == safecrowd::domain::ZoneKind::Stair;
120+
}
121+
122+
std::vector<NavigationTreeNode> roomChildren(
123+
const safecrowd::domain::FacilityLayout2D& layout,
124+
const safecrowd::domain::Zone2D& room) {
125+
std::vector<NavigationTreeNode> children;
55126

56-
auto rooms = collectZones(*facilityLayout, [](const auto& zone) {
57-
return zone.kind == safecrowd::domain::ZoneKind::Room
58-
|| zone.kind == safecrowd::domain::ZoneKind::Intersection
59-
|| zone.kind == safecrowd::domain::ZoneKind::Unknown;
60-
});
61-
if (!rooms.empty()) {
62-
nodes.push_back(makeSection("Rooms", std::move(rooms)));
127+
for (const auto& barrier : layout.barriers) {
128+
if (!matchesFloor(barrier.floorId, room.floorId) || !barrierTouchesZone(barrier, room)) {
129+
continue;
130+
}
131+
children.push_back({
132+
.label = QString("Wall - %1").arg(barrierLabel(barrier)),
133+
.id = QString::fromStdString(barrier.id),
134+
.detail = QString("Wall: %1").arg(QString::fromStdString(barrier.id)),
135+
});
63136
}
64137

65-
auto exits = collectZones(*facilityLayout, [](const auto& zone) {
66-
return zone.kind == safecrowd::domain::ZoneKind::Exit
67-
|| zone.kind == safecrowd::domain::ZoneKind::Stair;
68-
});
69-
if (!exits.empty()) {
70-
nodes.push_back(makeSection("Exits", std::move(exits)));
138+
for (const auto& connection : layout.connections) {
139+
if (!matchesFloor(connection.floorId, room.floorId)
140+
|| (connection.fromZoneId != room.id && connection.toZoneId != room.id)) {
141+
continue;
142+
}
143+
children.push_back({
144+
.label = QString("Door - %1").arg(connectionLabel(connection)),
145+
.id = QString::fromStdString(connection.id),
146+
.detail = QString("Door: %1").arg(QString::fromStdString(connection.id)),
147+
});
71148
}
72149

73-
if (!facilityLayout->connections.empty()) {
74-
std::vector<NavigationTreeNode> connections;
75-
for (const auto& connection : facilityLayout->connections) {
76-
connections.push_back({
77-
.label = QString("%1 -> %2")
78-
.arg(QString::fromStdString(connection.fromZoneId), QString::fromStdString(connection.toZoneId)),
79-
.id = QString::fromStdString(connection.id),
80-
.detail = QString("Connection: %1").arg(QString::fromStdString(connection.id)),
81-
});
150+
return children;
151+
}
152+
153+
std::vector<safecrowd::domain::Floor2D> layoutFloors(const safecrowd::domain::FacilityLayout2D& layout) {
154+
if (!layout.floors.empty()) {
155+
return layout.floors;
156+
}
157+
158+
std::vector<safecrowd::domain::Floor2D> floors;
159+
const auto fallback = layout.levelId.empty() ? std::string{"L1"} : layout.levelId;
160+
floors.push_back({.id = fallback, .label = fallback});
161+
return floors;
162+
}
163+
164+
std::vector<NavigationTreeNode> buildLayoutTree(const safecrowd::domain::FacilityLayout2D* facilityLayout) {
165+
if (facilityLayout == nullptr) {
166+
return {};
167+
}
168+
169+
std::vector<NavigationTreeNode> nodes;
170+
for (const auto& floor : layoutFloors(*facilityLayout)) {
171+
std::vector<NavigationTreeNode> rooms;
172+
for (const auto& zone : facilityLayout->zones) {
173+
if (!matchesFloor(zone.floorId, floor.id) || !isRoomLikeZone(zone)) {
174+
continue;
175+
}
176+
auto roomNode = makeZoneNode(zone);
177+
roomNode.children = roomChildren(*facilityLayout, zone);
178+
rooms.push_back(std::move(roomNode));
82179
}
83-
nodes.push_back(makeSection("Connections", std::move(connections)));
180+
nodes.push_back(makeSection(floorLabel(floor), std::move(rooms), floorActionId(floor.id)));
84181
}
85182

86-
if (!facilityLayout->barriers.empty()) {
87-
std::vector<NavigationTreeNode> barriers;
88-
for (const auto& barrier : facilityLayout->barriers) {
89-
barriers.push_back({
90-
.label = QString::fromStdString(barrier.id),
91-
.id = QString::fromStdString(barrier.id),
92-
.detail = QString("Wall: %1").arg(QString::fromStdString(barrier.id)),
183+
if (nodes.empty()) {
184+
std::vector<NavigationTreeNode> rooms;
185+
for (const auto& zone : facilityLayout->zones) {
186+
if (isRoomLikeZone(zone)) {
187+
auto roomNode = makeZoneNode(zone);
188+
roomNode.children = roomChildren(*facilityLayout, zone);
189+
rooms.push_back(std::move(roomNode));
190+
}
191+
}
192+
if (!rooms.empty()) {
193+
nodes.push_back({
194+
.label = "Layout",
195+
.children = std::move(rooms),
196+
.expanded = true,
197+
.selectable = false,
93198
});
94199
}
95-
nodes.push_back(makeSection("Walls", std::move(barriers)));
96200
}
97201

98202
return nodes;

0 commit comments

Comments
 (0)