#ifndef MV2DELEMENTSTAIR_H #define MV2DELEMENTSTAIR_H #include "MV2DElement.h" #include "MapViewElementHelper.h" #include class MV2DElementStair : public MV2DElement { private: bool sel = false; Floorplan::Floor* floor; Floorplan::Stair* stair; int selPart = -1; int selNode = -1; public: /** ctor with the AP to render/edit */ MV2DElementStair(Floorplan::Floor* floor, Floorplan::Stair* stair) : floor(floor), stair(stair) {;} /** get the element's 3D bounding box */ BBox2 getBoundingBox() const override { 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 */ float getMinDistanceXY(const Point2 p) const override { auto comp = [p] (const Floorplan::StairPart& p1, const Floorplan::StairPart& p2) { const float d1 = MapElementHelper::getLineDistanceXY(p1.start.xy(), p1.end.xy(), p); const float 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 getSelPart() const {return selPart;} int getSelNode() const {return selNode;} static inline float clamp01(const float val) { if (val < 0) {return 0;} if (val > 1) {return 1;} return val; } /** repaint me */ void paint(Painter& p) override { Floorplan::StairFreeform* stair = dynamic_cast(this->stair); // if (sel) { // p.setPenBrush(Qt::black, CFG::SEL_COLOR); // p.drawCircle(stair->center); // } else if (hasFocus()) { // p.setPenBrush(Qt::black, Qt::NoBrush); // p.drawCircle(stair->center); // } else { // p.setPenBrush(Qt::gray, Qt::NoBrush); // p.drawCircle(stair->center); // } // 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); 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*255, p1*255, p1*255, 128)); gradient.setColorAt(1, QColor(p2*255, p2*255, 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 if (i == 0) { if (quad.p1.z != floor->getStartingZ()) { p.drawLine(quad.p1, quad.p2); } } else if (i == (int) parts.size() - 1) { if (quad.p3.z != floor->getEndingZ()) { p.drawLine(quad.p3, quad.p4); } } 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 == selPart && selNode == 0) ? CFG::SEL_COLOR : Qt::NoBrush); // part start p.drawCircle(part.start.xy()); p.setPenBrush(Qt::black, (cnt == selPart && selNode == 1) ? CFG::SEL_COLOR : Qt::NoBrush); // part end p.drawRect(part.end.xy()); 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()); } } } /** mouse pressed at the given point */ virtual void mousePressed(MapView2D* v, const Point2 p) override { (void) v; (void) p; } /** mouse moved to the given point */ virtual void mouseMove(MapView2D* v, const Point2 _p) override { (void) v; if (selPart == -1) {return;} Floorplan::StairFreeform* stair = dynamic_cast(this->stair); const Point2 p = v->getScaler().snap(_p); stair->parts[selPart][selNode].x = p.x; stair->parts[selPart][selNode].y = p.y; } /** mouse released */ virtual void mouseReleased(MapView2D* v, const Point2 _p) override { (void) v; (void) _p; // select a new point on mouse-release (more robust than on mouse-press) Floorplan::StairFreeform* stair = dynamic_cast(this->stair); const float t = v->getScaler().sm(CFG::SEL_THRESHOLD_SIZE_PX); // auto comp = [&] (const Point3 a, const Point3 b) {return a.xy().getDistance(_p) < b.xy().getDistance(_p);}; // auto it = std::min_element(stair->nodes.begin(), stair->nodes.end(), comp); // if (it == stair->nodes.end()) {selIdx = -1;} // none found -> skip // else if ((*it).xy().getDistance(_p) > t) {selIdx = -1;} // nearest distance is above threshold -> skip // else {selIdx = it - stair->nodes.begin();} float best = 999999; int minPart; int minNode; for (int part = 0; part < (int) stair->parts.size(); ++part) { for (int node = 0; node < 2; ++node) { const float dist = stair->parts[part][node].xy().getDistance(_p); if (dist < best) {best = dist; minPart = part; minNode = node;} } } if (best <= t) { selPart = minPart; selNode = minNode; } else { selPart = -1; selNode = -1; } emit v->onElementChange(this); } virtual bool keyPressEvent(MapView2D* v, QKeyEvent *e) override { (void) v; (void) e; Floorplan::StairFreeform* stair = dynamic_cast(this->stair); if (e->key() == Qt::Key_Delete) { // delete the currently selected vertex? if (selPart != -1) { // stair->nodes.erase(stair->nodes.begin() + selIdx); // selIdx = -1; // return true; } } else if (e->key() == Qt::Key_Plus && selPart != -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 = selPart + 1; const Point3 p0 = stair->parts[selPart][1]; const Point3 p1 = p0 + Point3(1,1,0); const Point3 p2 = p1 + Point3(2,2,0); const float w = stair->parts[selPart].width; stair->parts.insert(stair->parts.begin() + idxNew, Floorplan::StairPart(p1, p2, w)); return true; } // not consumed return false; } virtual void onFocus() override { selPart = -1; selNode = -1; } virtual void onUnfocus() override { sel = false; } }; #endif // MV2DELEMENTSTAIR_H