#include "MV2DElementStair.h" #include "MV2DElement.h" #include "HasMoveableNodes.h" #include "MapViewElementHelper.h" #include /** is the given stair's end connected to ANY of the floorplan's floors? */ static inline bool stairEndConnected(const Floorplan::IndoorMap* map, const Floorplan::Floor* floor, const Floorplan::Stair* stair) { const int stairEnd_cm = std::round( (floor->atHeight + stair->getParts().back().end.z) * 100 ); std::vector floorsAtHeight_cm; for (const Floorplan::Floor* f : map->floors) { const int height_cm = std::round(f->atHeight*100); floorsAtHeight_cm.push_back(height_cm); } const bool connected = std::find(floorsAtHeight_cm.begin(), floorsAtHeight_cm.end(), stairEnd_cm) != floorsAtHeight_cm.end(); return connected; } static inline float clamp01(const float val) { if (val < 0) {return 0;} if (val > 1) {return 1;} return val; } MV2DElementStair::MV2DElementStair(Floorplan::IndoorMap* map, Floorplan::Floor* floor, Floorplan::Stair* stair) : map(map), floor(floor), stair(stair) { ; } BBox2 MV2DElementStair::getBoundingBox() const { BBox2 bbox; if (dynamic_cast(stair)) { Floorplan::StairFreeform* stair = dynamic_cast(this->stair); for (const Floorplan::StairPart p : stair->parts) { bbox.add(p.start.xy()); bbox.add(p.end.xy()); } } return bbox; } /** get the element's minimal distance (nearest whatsoever) to the given point */ ClickDist MV2DElementStair::getMinDistanceXY(const Point2 p) const { auto comp = [p] (const Floorplan::StairPart& p1, const Floorplan::StairPart& p2) { const ClickDist d1 = MapElementHelper::getLineDistanceXY(p1.start.xy(), p1.end.xy(), p); const ClickDist d2 = MapElementHelper::getLineDistanceXY(p2.start.xy(), p2.end.xy(), p); return d1 < d2; }; auto parts = stair->getParts(); auto min = std::min_element(parts.begin(), parts.end(), comp); return MapElementHelper::getLineDistanceXY(min->start.xy(), min->end.xy(), p); } int MV2DElementStair::getSelPart() const {return getSelectedNode() < 0 ? getSelectedNode() : getSelectedNode() / 2;} int MV2DElementStair::getSelNode() const {return getSelectedNode() < 0 ? getSelectedNode() : getSelectedNode() % 2;} void MV2DElementStair::paint(Painter& p) { Floorplan::StairFreeform* stair = dynamic_cast(this->stair); // draw all parts of the stair (all polygons) p.setPenBrush(Qt::black, Qt::NoBrush); std::vector parts = stair->getParts(); std::vector quads = Floorplan::getQuads(parts, floor); // skip drawing? BBox2 bbox; for (const Floorplan::Quad3& q : quads) { bbox.add(q.p1.xy()); bbox.add(q.p2.xy()); bbox.add(q.p3.xy()); bbox.add(q.p4.xy()); } if (!p.isVisible(bbox)) { return; } for (int i = 0; i < (int) parts.size(); ++i) { const Floorplan::StairPart& part = parts[i]; const Floorplan::Quad3& quad = quads[i]; // fill the polygon with a gradient corresponding with the stair's height relative to the floor's height QLinearGradient gradient(p.s.xms(part.start.x), p.s.yms(part.start.y), p.s.xms(part.end.x), p.s.yms(part.end.y)); const float p1 = 0.1 + clamp01( part.start.z / floor->height) * 0.8; const float p2 = 0.1 + clamp01( part.end.z / floor->height) * 0.8; gradient.setColorAt(0, QColor(p1*128, p1*128, p1*255, 128)); gradient.setColorAt(1, QColor(p2*128, p2*128, p2*255, 128)); p.setBrush(gradient); p.setPen(QColor(0,0,0,128)); // polygon-construction //const Floorplan::Quad3 quad = part.getQuad(floor); const std::vector points = {quad.p1, quad.p2, quad.p3, quad.p4}; p.drawPolygon(points); QPen pen; pen.setWidth(5); pen.setColor(QColor(255,0,0)); p.setPen(pen); // LINT disconnected start if (i == 0) { if (quad.p1.z != floor->getStartingZ()) { p.drawLine(quad.p1, quad.p2); } } // LINT disconnected end if (i == (int) parts.size() - 1) { //if (quad.p3.z != floor->getEndingZ()) { if (!stairEndConnected(map, floor, stair)) { p.drawLine(quad.p3, quad.p4); } } // LINT disconnected within if (i > 0) { if (quads[i-1].p4.z != quads[i-0].p1.z) { p.drawLine(quad.p1, quad.p2); } } } if (hasFocus()) { int cnt = 0; std::vector parts = stair->getParts(); for (const Floorplan::StairPart& part : parts) { //p.setPenBrush(Qt::black, (cnt == getSelPart() && getSelNode() == 0) ? CFG::SEL_COLOR : Qt::NoBrush); // part start //p.drawCircle(part.start.xy()); // DEPRECATED //p.setPenBrush(Qt::black, (cnt == getSelPart() && getSelNode() == 1) ? CFG::SEL_COLOR : Qt::NoBrush); // part end //p.drawRect(part.end.xy()); // DEPRECATED p.setPenBrush(Qt::blue, Qt::NoBrush); Point2 ctr = (part.start+part.end).xy() / 2; p.drawText(ctr, "p" + std::to_string(cnt+1)); // part name ++cnt; } for (int i = 0; i < (int)parts.size() - 1; ++i) { const Point3 p1 = parts[i+0][1]; const Point3 p2 = parts[i+1][0]; p.drawLine(p1.xy(), p2.xy()); } } } bool MV2DElementStair::keyPressEvent(MapView2D* v, QKeyEvent *e) { (void) v; (void) e; Floorplan::StairFreeform* stair = dynamic_cast(this->stair); if (e->key() == Qt::Key_Delete) { // delete the currently selected vertex? if (getSelPart() != -1) { // stair->nodes.erase(stair->nodes.begin() + selIdx); // selIdx = -1; // return true; } } else if (e->key() == Qt::Key_Plus && getSelPart() != -1) { // int idx1 = selIdx; // int idx2 = (selIdx + 1) % stair->nodes.size(); // int idxNew = idx2; // Point3 pNew = (stair->nodes[idx1] + stair->nodes[idx2]) / 2.0f; // stair->nodes.insert(stair->nodes.begin() + idxNew, pNew); // selIdx = idxNew; // return true; const int idxNew = getSelPart() + 1; const Point3 p0 = stair->parts[getSelPart()][1]; const Point3 p1 = p0 + Point3(1,1,0); const Point3 p2 = p1 + Point3(2,2,0); const float w = stair->parts[getSelPart()].width; stair->parts.insert(stair->parts.begin() + idxNew, Floorplan::StairPart(p1, p2, w)); return true; } // not consumed return false; } void MV2DElementStair::onFocus() { // TODO? } void MV2DElementStair::onUnfocus() { sel = false; } std::vector MV2DElementStair::getMoveableNodes() const { std::vector nodes; Floorplan::StairFreeform* stair = dynamic_cast(this->stair); // get a list of all moveable nodes (2 per stair-part) int idx = 0; for (size_t part = 0; part < stair->parts.size(); ++part) { for (int node = 0; node < 2; ++node) { MoveableNode mn(idx++, stair->parts[part][node].xy()); nodes.push_back(mn); } } return nodes; } void MV2DElementStair::onNodeMove(MapView2D* v, const int userIdx, const Point2 newPos) { (void) v; // convert node-index back to stair-part and node-nr within stair-part const int selPart = userIdx / 2; const int selNode = userIdx % 2; // move the node Floorplan::StairFreeform* stair = dynamic_cast(this->stair); stair->parts[selPart][selNode].x = newPos.x; stair->parts[selPart][selNode].y = newPos.y; } void MV2DElementStair::onNodeMoved(MapView2D* v, const int userIdx, const Point2 newPos) { (void) userIdx; (void) newPos; emit v->onElementChange(this); } void MV2DElementStair::onNodeSelect(MapView2D* v, const int userIdx) { HasMoveableNodes::onNodeSelect(v, userIdx); emit v->onElementChange(this); }