Skip to content

Commit 436eafe

Browse files
committed
[Domain] consolidate geometry floor helpers
1 parent 053a0c4 commit 436eafe

25 files changed

Lines changed: 386 additions & 479 deletions

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,17 @@ if (BUILD_TESTING)
184184
if (SAFECROWD_BUILD_APP)
185185
target_sources(safecrowd_tests
186186
PRIVATE
187+
tests/LayoutCanvasSnappingTests.cpp
187188
tests/ProjectPersistenceTests.cpp
189+
src/application/LayoutCanvasRendering.cpp
190+
src/application/LayoutCanvasSnapping.cpp
188191
src/application/ProjectPersistence.cpp
189192
)
190193

191194
target_link_libraries(safecrowd_tests
192195
PRIVATE
193196
Qt6::Core
197+
Qt6::Gui
194198
)
195199
endif()
196200

src/application/LayoutCanvasRendering.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include "application/LayoutCanvasRendering.h"
22

3+
#include "domain/GeometryQueries.h"
4+
35
#include <algorithm>
46
#include <cmath>
57

@@ -19,9 +21,7 @@ namespace {
1921
const QColor kDoorStrokeColor("#ff8c00");
2022
const QColor kOpeningStrokeColor("#2f6fb2");
2123

22-
bool matchesFloor(const std::string& elementFloorId, const std::string& floorId) {
23-
return floorId.empty() || elementFloorId.empty() || elementFloorId == floorId;
24-
}
24+
using safecrowd::domain::matchesFloor;
2525

2626
bool isVerticalConnection(const safecrowd::domain::Connection2D& connection) {
2727
return connection.kind == safecrowd::domain::ConnectionKind::Stair

src/application/LayoutCanvasSnapping.cpp

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include "application/LayoutCanvasSnapping.h"
22

3+
#include "domain/GeometryQueries.h"
4+
35
#include <algorithm>
46
#include <cmath>
57
#include <limits>
@@ -14,24 +16,15 @@ struct SnapGeometry {
1416
std::vector<safecrowd::domain::LineSegment2D> edges{};
1517
};
1618

17-
bool matchesFloor(const std::string& elementFloorId, const std::string& floorId) {
18-
return floorId.empty() || elementFloorId.empty() || elementFloorId == floorId;
19-
}
20-
21-
safecrowd::domain::Point2D operator-(const safecrowd::domain::Point2D& lhs, const safecrowd::domain::Point2D& rhs) {
22-
return {.x = lhs.x - rhs.x, .y = lhs.y - rhs.y};
23-
}
19+
using safecrowd::domain::matchesFloor;
20+
using safecrowd::domain::closestPointOnSegment;
2421

2522
safecrowd::domain::Point2D operator+(const safecrowd::domain::Point2D& lhs, const safecrowd::domain::Point2D& rhs) {
2623
return {.x = lhs.x + rhs.x, .y = lhs.y + rhs.y};
2724
}
2825

29-
safecrowd::domain::Point2D operator*(const safecrowd::domain::Point2D& point, double scalar) {
30-
return {.x = point.x * scalar, .y = point.y * scalar};
31-
}
32-
33-
double dot(const safecrowd::domain::Point2D& lhs, const safecrowd::domain::Point2D& rhs) {
34-
return (lhs.x * rhs.x) + (lhs.y * rhs.y);
26+
safecrowd::domain::Point2D operator-(const safecrowd::domain::Point2D& lhs, const safecrowd::domain::Point2D& rhs) {
27+
return {.x = lhs.x - rhs.x, .y = lhs.y - rhs.y};
3528
}
3629

3730
double screenDistance(
@@ -43,6 +36,15 @@ double screenDistance(
4336
return std::hypot(a.x() - b.x(), a.y() - b.y());
4437
}
4538

39+
double maxAxisScreenDistance(
40+
const LayoutCanvasTransform& transform,
41+
const safecrowd::domain::Point2D& lhs,
42+
const safecrowd::domain::Point2D& rhs) {
43+
const auto a = transform.map(lhs);
44+
const auto b = transform.map(rhs);
45+
return std::max(std::abs(a.x() - b.x()), std::abs(a.y() - b.y()));
46+
}
47+
4648
safecrowd::domain::Point2D snapPointToGrid(const safecrowd::domain::Point2D& point, double spacingMeters) {
4749
if (spacingMeters <= 0.0) {
4850
return point;
@@ -59,20 +61,6 @@ LayoutSnapOptions withoutGridSnap(LayoutSnapOptions options) {
5961
return options;
6062
}
6163

62-
safecrowd::domain::Point2D closestPointOnSegment(
63-
const safecrowd::domain::Point2D& point,
64-
const safecrowd::domain::Point2D& start,
65-
const safecrowd::domain::Point2D& end) {
66-
const auto segment = end - start;
67-
const auto lengthSquared = dot(segment, segment);
68-
if (lengthSquared <= 1e-9) {
69-
return start;
70-
}
71-
72-
const auto t = std::clamp(dot(point - start, segment) / lengthSquared, 0.0, 1.0);
73-
return start + (segment * t);
74-
}
75-
7664
void appendPolygonSnapGeometry(
7765
const safecrowd::domain::Polygon2D& polygon,
7866
std::vector<safecrowd::domain::Point2D>& vertices,
@@ -414,7 +402,7 @@ LayoutDragSnapResult snapLayoutSelectionDrag(
414402
continue;
415403
}
416404

417-
const auto distance = screenDistance(transform, moved, snapped.point);
405+
const auto distance = maxAxisScreenDistance(transform, moved, snapped.point);
418406
if (distance <= bestDistance) {
419407
bestDistance = distance;
420408
result = {

src/application/LayoutNavigationPanelWidget.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,13 @@ const QColor kExitAccentColor("#2d8f5b");
2222
const QColor kDoorAccentColor("#ff8c00");
2323

2424
using safecrowd::domain::distanceToPolygonBoundary;
25+
using safecrowd::domain::matchesFloor;
2526
using safecrowd::domain::pointInPolygon;
2627

2728
QString floorActionId(const std::string& floorId) {
2829
return QString("floor:%1").arg(QString::fromStdString(floorId));
2930
}
3031

31-
bool matchesFloor(const std::string& elementFloorId, const std::string& floorId) {
32-
return floorId.empty() || elementFloorId.empty() || elementFloorId == floorId;
33-
}
34-
3532
QIcon treeIcon(const QString& resourcePath, const QColor& color) {
3633
return makeSvgToolIcon(resourcePath, color, QSize(18, 18));
3734
}

src/application/LayoutPreviewGeometry.cpp

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include "application/LayoutPreviewGeometry.h"
22

3+
#include "domain/GeometryQueries.h"
4+
35
#include <algorithm>
46
#include <cmath>
57
#include <functional>
@@ -17,17 +19,11 @@ constexpr double kMinimumDoorWidth = 0.9;
1719

1820
using Bounds2D = LayoutCanvasBounds;
1921
bool matchesFloor(const std::string& elementFloorId, const QString& floorId) {
20-
return floorId.isEmpty() || elementFloorId.empty() || QString::fromStdString(elementFloorId) == floorId;
22+
return safecrowd::domain::matchesFloor(elementFloorId, floorId.toStdString());
2123
}
2224

2325
QString defaultFloorId(const safecrowd::domain::FacilityLayout2D& layout) {
24-
if (!layout.floors.empty() && !layout.floors.front().id.empty()) {
25-
return QString::fromStdString(layout.floors.front().id);
26-
}
27-
if (!layout.levelId.empty()) {
28-
return QString::fromStdString(layout.levelId);
29-
}
30-
return "L1";
26+
return QString::fromStdString(safecrowd::domain::defaultFloorId(layout, "L1"));
3127
}
3228

3329
QString nextFloorId(const safecrowd::domain::FacilityLayout2D& layout) {
@@ -47,7 +43,7 @@ QString nextFloorId(const safecrowd::domain::FacilityLayout2D& layout) {
4743
}
4844

4945
void ensureLayoutFloors(safecrowd::domain::FacilityLayout2D& layout) {
50-
const auto floorId = defaultFloorId(layout);
46+
const auto floorId = safecrowd::application::defaultFloorId(layout);
5147
if (layout.floors.empty()) {
5248
layout.floors.push_back({
5349
.id = floorId.toStdString(),

src/application/LayoutPreviewWidget.cpp

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,29 @@ void LayoutPreviewWidget::mousePressEvent(QMouseEvent* event) {
12481248
return;
12491249
}
12501250

1251+
if (event->button() == Qt::LeftButton
1252+
&& toolMode_ == ToolMode::Select
1253+
&& importResult_.layout.has_value()
1254+
&& hasSelection()) {
1255+
const auto bounds = collectBounds(importResult_, currentFloorId());
1256+
if (bounds.has_value()) {
1257+
const LayoutTransform transform(*bounds, previewViewport(rect()), camera_.zoom(), camera_.panOffset());
1258+
const bool clickedCurrentSelection = selectedElementContainsContextPoint(
1259+
*importResult_.layout,
1260+
event->position(),
1261+
transform,
1262+
currentFloorId(),
1263+
selectedZoneIds_,
1264+
selectedConnectionIds_,
1265+
selectedBarrierIds_);
1266+
if (clickedCurrentSelection) {
1267+
beginSelectionMove(event->position(), transform, *bounds);
1268+
event->accept();
1269+
return;
1270+
}
1271+
}
1272+
}
1273+
12511274
if (camera_.beginPan(event)) {
12521275
return;
12531276
}
@@ -1300,23 +1323,6 @@ void LayoutPreviewWidget::mousePressEvent(QMouseEvent* event) {
13001323
return;
13011324
}
13021325

1303-
if (importResult_.layout.has_value() && hasSelection()) {
1304-
const LayoutTransform transform(*bounds, previewViewport(rect()), camera_.zoom(), camera_.panOffset());
1305-
const bool clickedCurrentSelection = selectedElementContainsContextPoint(
1306-
*importResult_.layout,
1307-
event->position(),
1308-
transform,
1309-
currentFloorId(),
1310-
selectedZoneIds_,
1311-
selectedConnectionIds_,
1312-
selectedBarrierIds_);
1313-
if (clickedCurrentSelection) {
1314-
beginSelectionMove(event->position(), transform, *bounds);
1315-
event->accept();
1316-
return;
1317-
}
1318-
}
1319-
13201326
selectionDragging_ = true;
13211327
selectionDragStart_ = event->position();
13221328
selectionDragCurrent_ = selectionDragStart_;
@@ -1329,16 +1335,16 @@ void LayoutPreviewWidget::mousePressEvent(QMouseEvent* event) {
13291335
}
13301336

13311337
void LayoutPreviewWidget::mouseReleaseEvent(QMouseEvent* event) {
1332-
if (camera_.finishPan(event)) {
1333-
return;
1334-
}
1335-
13361338
if (selectionMoveDragging_ && event->button() == Qt::LeftButton) {
13371339
finishSelectionMove(event->position());
13381340
event->accept();
13391341
return;
13401342
}
13411343

1344+
if (camera_.finishPan(event)) {
1345+
return;
1346+
}
1347+
13421348
if (selectionDragging_ && event->button() == Qt::LeftButton) {
13431349
selectionDragging_ = false;
13441350
selectionDragCurrent_ = event->position();
@@ -1413,16 +1419,20 @@ void LayoutPreviewWidget::paintEvent(QPaintEvent* event) {
14131419
painter.setRenderHint(QPainter::Antialiasing);
14141420
painter.fillRect(rect(), QColor(250, 252, 255));
14151421

1416-
const auto bounds = collectBounds(importResult_, currentFloorId());
1417-
if (!bounds.has_value()) {
1422+
const auto liveBounds = collectBounds(importResult_, currentFloorId());
1423+
if (!liveBounds.has_value()) {
14181424
painter.setPen(QPen(QColor(80, 80, 80), 1));
14191425
painter.setFont(QFont("Segoe UI", 14, QFont::DemiBold));
14201426
painter.drawText(rect(), Qt::AlignCenter, "No layout geometry imported");
14211427
return;
14221428
}
14231429

1424-
const QRectF viewport = previewViewport(rect());
1425-
const LayoutTransform transform(*bounds, viewport, camera_.zoom(), camera_.panOffset());
1430+
const bool freezeCamera = selectionMoveDragging_ && selectionMoveBounds_.valid();
1431+
const auto bounds = freezeCamera ? selectionMoveBounds_ : *liveBounds;
1432+
const QRectF viewport = freezeCamera ? selectionMoveViewport_ : previewViewport(rect());
1433+
const auto zoom = freezeCamera ? selectionMoveZoom_ : camera_.zoom();
1434+
const auto panOffset = freezeCamera ? selectionMovePanOffset_ : camera_.panOffset();
1435+
const LayoutTransform transform(bounds, viewport, zoom, panOffset);
14261436

14271437
if (gridSnapEnabled_) {
14281438
drawLayoutCanvasGrid(painter, viewport, transform, gridSpacingMeters_);
@@ -1591,7 +1601,7 @@ void LayoutPreviewWidget::paintEvent(QPaintEvent* event) {
15911601

15921602
painter.setPen(QPen(QColor(115, 128, 140), 1));
15931603
painter.setFont(QFont("Segoe UI", 9, QFont::Medium));
1594-
painter.drawText(viewport.adjusted(0, -10, 0, 0), Qt::AlignTop | Qt::AlignRight, QString("Zoom %1%").arg(static_cast<int>(camera_.zoom() * 100.0)));
1604+
painter.drawText(viewport.adjusted(0, -10, 0, 0), Qt::AlignTop | Qt::AlignRight, QString("Zoom %1%").arg(static_cast<int>(zoom * 100.0)));
15951605
painter.drawText(viewport.adjusted(0, -10, 0, 0), Qt::AlignTop | Qt::AlignLeft, "Layout Preview");
15961606

15971607
}
@@ -1602,6 +1612,11 @@ void LayoutPreviewWidget::resizeEvent(QResizeEvent* event) {
16021612
}
16031613

16041614
void LayoutPreviewWidget::wheelEvent(QWheelEvent* event) {
1615+
if (selectionMoveDragging_) {
1616+
event->accept();
1617+
return;
1618+
}
1619+
16051620
if (switchFloorByWheel(event)) {
16061621
return;
16071622
}

src/application/ProjectPersistence.cpp

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <QStandardPaths>
1818
#include <QStorageInfo>
1919

20+
#include "domain/GeometryQueries.h"
2021
#include "domain/ImportValidationService.h"
2122

2223
namespace safecrowd::application {
@@ -625,16 +626,6 @@ safecrowd::domain::Floor2D floorFromJson(const QJsonObject& object) {
625626
};
626627
}
627628

628-
std::string defaultFloorId(const safecrowd::domain::FacilityLayout2D& layout) {
629-
if (!layout.floors.empty() && !layout.floors.front().id.empty()) {
630-
return layout.floors.front().id;
631-
}
632-
if (!layout.levelId.empty()) {
633-
return layout.levelId;
634-
}
635-
return "L1";
636-
}
637-
638629
void normalizeFloors(safecrowd::domain::FacilityLayout2D& layout) {
639630
if (layout.floors.empty()) {
640631
const auto floorId = layout.levelId.empty() ? std::string("L1") : layout.levelId;
@@ -644,7 +635,7 @@ void normalizeFloors(safecrowd::domain::FacilityLayout2D& layout) {
644635
});
645636
}
646637

647-
const auto floorId = defaultFloorId(layout);
638+
const auto floorId = safecrowd::domain::defaultFloorId(layout, "L1");
648639
if (layout.levelId.empty()) {
649640
layout.levelId = floorId;
650641
}

0 commit comments

Comments
 (0)