29 #include <QGraphicsLineItem>
30 #include <QGraphicsScene>
31 #include <QGraphicsSceneMouseEvent>
32 #include <unordered_set>
37 using boost::polygon::point_data;
38 using boost::polygon::voronoi_diagram;
42 const QRectF VoronoiTesselation::g_point_rect(-0.5, -0.5, 1.0, 1.0);
43 const QBrush VoronoiTesselation::g_point_brush(Qt::black);
44 const double VoronoiTesselation::g_points_precision = 1.0e3;
46 VoronoiTesselation::VoronoiTesselation(
const QPolygonF &boundaryPolygon,
QGraphicsItem *parent)
49 setFlag(QGraphicsItem::ItemClipsChildrenToShape,
true);
58 std::list<QPolygonF> clippedPolygons;
59 for (
const auto &cellPolygon : m_cell_polygons) {
63 return clippedPolygons;
68 QGraphicsPolygonItem::mousePressEvent(event);
70 if (event->button() == Qt::RightButton) {
73 auto it = std::find(m_points.begin(), m_points.end(), point->pos());
74 assert(it != m_points.end() &&
"m_points should contain the point that has to be removed");
86 QGraphicsPolygonItem::mouseDoubleClickEvent(event);
88 if (event->button() == Qt::LeftButton) {
89 m_points.push_back(event->pos());
92 point->setPos(event->pos());
93 point->setBrush(g_point_brush);
94 point->setPen(Qt::NoPen);
100 void VoronoiTesselation::UpdateTesselation()
102 std::vector<point_data<long>> points;
103 std::transform(m_points.begin(), m_points.end(), std::back_inserter(points), &ToVoronoiPoint);
105 voronoi_diagram<double> voronoi;
106 construct_voronoi(points.begin(), points.end(), &voronoi);
108 m_cell_polygons.clear();
110 std::map<const voronoi_diagram<double>::edge_type*, ClippedEdge> clippingCache;
112 for (
auto &cell : voronoi.cells()) {
113 auto edge = cell.incident_edge();
119 AddEdge(edge, polygon, clippingCache);
121 }
while (edge != cell.incident_edge());
122 m_cell_polygons.push_back(polygon);
128 void VoronoiTesselation::RedrawLines()
130 std::for_each(m_lines.begin(), m_lines.end(), [] (
QGraphicsLineItem *item) {
delete item; });
134 std::hash<double> hash_qreal;
135 auto hash_qlinef = [hash_qreal] (
const QLineF &line) {
return 29 * hash_qreal(line.p1().x()) + 53 * hash_qreal(line.p1().y()) + 29 * hash_qreal(line.p2().x()) + 53 * hash_qreal(line.p2().y()); };
136 auto equal_qlinef = [] (
const QLineF &line1,
const QLineF &line2) {
return (line1.p1() == line2.p1() && line1.p2() == line2.p2()) || (line1.p1() == line2.p2() && line1.p2() == line2.p1()); };
137 std::unordered_set<QLineF, decltype(hash_qlinef), decltype(equal_qlinef)> drawnLines(10, hash_qlinef, equal_qlinef);
139 for (
const auto &polygon : m_cell_polygons) {
140 std::vector<QPointF> polygonVector = polygon.toStdVector();
142 auto cit =
make_const_circular(polygonVector.begin(), polygonVector.end(), polygonVector.begin());
144 QLineF line(*cit, *
next(cit));
145 if (drawnLines.count(line) == 0) {
146 drawnLines.insert(line);
148 lineItem->setZValue(-1);
149 m_lines.push_back(lineItem);
151 }
while (++cit != polygonVector.begin());
155 void VoronoiTesselation::AddEdge(
const voronoi_diagram<double>::edge_type *edge, QPolygonF &cellPolygon, std::map<
const voronoi_diagram<double>::edge_type*, ClippedEdge> &clippingCache)
157 ClippedEdge clippedEdge = GetClippedEdge(edge, clippingCache);
160 if (clippedEdge.clippedP1 || clippedEdge.clippedP2 || boundingRect().contains(clippedEdge.edge.p1())) {
161 cellPolygon.append(clippedEdge.edge.p1());
163 if (clippedEdge.clippedP2) {
164 cellPolygon.append(clippedEdge.edge.p2());
166 auto reenterEdge = edge->next();
167 ClippedEdge reenterClipped;
169 reenterClipped = GetClippedEdge(reenterEdge, clippingCache);
170 assert((reenterClipped.clippedP1 || reenterEdge != edge) &&
"Can't find edge reentering the bounding rect");
171 reenterEdge = reenterEdge->next();
172 }
while (!reenterClipped.clippedP1);
178 auto nextBoundaryPoint = std::find(boundingPolygon.begin(), boundingPolygon.end(), clippedEdge.nextBoundaryP2);
179 auto reenterBoundaryPoint = std::find(boundingPolygon.begin(), boundingPolygon.end(), reenterClipped.nextBoundaryP1);
180 assert(nextBoundaryPoint != boundingPolygon.end() &&
"nextBoundaryPoint not found in boundingRect polygon");
181 assert(reenterBoundaryPoint != boundingPolygon.end() &&
"reenterBoundaryPoint not found in boundingRect polygon");
183 auto cit =
make_const_circular(boundingPolygon.begin(), boundingPolygon.end(), nextBoundaryPoint);
184 while (cit != reenterBoundaryPoint) {
185 cellPolygon.append(*cit);
192 VoronoiTesselation::ClippedEdge VoronoiTesselation::GetClippedEdge(
const boost::polygon::voronoi_diagram<double>::edge_type *edge, std::map<
const boost::polygon::voronoi_diagram<double>::edge_type*, ClippedEdge> &clippingCache)
194 if (clippingCache.find(edge) != clippingCache.end()) {
195 return clippingCache[edge];
196 }
else if (clippingCache.find(edge->twin()) != clippingCache.end()) {
197 auto reverse = clippingCache[edge->twin()];
198 return ClippedEdge{QLineF(reverse.edge.p2(), reverse.edge.p1()), reverse.clippedP2, reverse.nextBoundaryP2, reverse.clippedP1, reverse.nextBoundaryP1};
200 auto clipped = ClipEdge(edge);
201 clippingCache[edge] = clipped;
206 VoronoiTesselation::ClippedEdge VoronoiTesselation::ClipEdge(
const boost::polygon::voronoi_diagram<double>::edge_type *edge)
208 QPointF c1 = m_points[edge->cell()->source_index()];
209 QPointF c2 = m_points[edge->twin()->cell()->source_index()];
210 QPointF direction = QLineF(QPointF(), QPointF(c1.y() - c2.y(), c2.x() - c1.x())).unitVector().p2();
211 direction *= 1 + 1e-12;
213 double boundingDiagonal = QLineF(boundingRect().topLeft(), boundingRect().bottomRight()).length();
215 ClippedEdge clipped{};
216 if (edge->vertex0() && edge->vertex1()) {
217 clipped.edge.setP1(FromVoronoiPoint(*edge->vertex0()));
218 clipped.edge.setP2(FromVoronoiPoint(*edge->vertex1()));
219 }
else if (edge->vertex0()) {
220 clipped.edge.setP1(FromVoronoiPoint(*edge->vertex0()));
221 clipped.edge.setP2(clipped.edge.p1() + (boundingDiagonal + QLineF(clipped.edge.p1(), boundingRect().topLeft()).length()) * direction);
222 }
else if (edge->vertex1()) {
223 clipped.edge.setP2(FromVoronoiPoint(*edge->vertex1()));
224 clipped.edge.setP1(clipped.edge.p2() - (boundingDiagonal + QLineF(clipped.edge.p2(), boundingRect().topLeft()).length()) * direction);
226 QPointF center = (c1 + c2) / 2;
227 clipped.edge.setP1(center - boundingDiagonal * direction);
228 clipped.edge.setP2(center + boundingDiagonal * direction);
231 std::vector<std::pair<QPointF, QPointF>> intersections;
235 auto cit =
make_const_circular(boundingPolygon.begin(), boundingPolygon.end(), boundingPolygon.begin());
238 if (QLineF(*cit, *
next(cit)).intersect(clipped.edge, &p) == QLineF::BoundedIntersection) {
239 intersections.push_back(std::make_pair(p, *
next(cit)));
241 }
while (++cit != boundingPolygon.begin());
243 if (intersections.size() > 0) {
244 std::sort(intersections.begin(), intersections.end(), [clipped] (
const std::pair<QPointF, QPointF> &p,
const std::pair<QPointF, QPointF> &q) {
return QLineF(clipped.edge.p1(), p.first).length() < QLineF(clipped.edge.p1(), q.first).length(); });
245 auto itUnique = std::unique(intersections.begin(), intersections.end());
246 size_t uniqueIntersections = std::distance(intersections.begin(), itUnique);
248 assert(uniqueIntersections <= 2 &&
"More than two intersections of a line with a rectangle found");
249 assert(uniqueIntersections >= static_cast<size_t>(!edge->vertex0() + !edge->vertex1()) &&
"An edge should at least be clipped at every point at infinity");
251 if (uniqueIntersections == 1) {
252 if (!boundingRect().contains(clipped.edge.p1())) {
253 clipped.clippedP1 =
true;
254 clipped.edge.setP1(intersections[0].first);
255 clipped.nextBoundaryP1 = intersections[0].second;
256 }
else if (!boundingRect().contains(clipped.edge.p2())) {
257 clipped.clippedP2 =
true;
258 clipped.edge.setP2(intersections[0].first);
259 clipped.nextBoundaryP2 = intersections[0].second;
261 }
else if (uniqueIntersections == 2) {
262 clipped.clippedP1 = clipped.edge.p1() != intersections[0].first;
263 clipped.clippedP2 = clipped.edge.p2() != intersections[1].first;
264 clipped.edge.setP1(intersections[0].first);
265 clipped.edge.setP2(intersections[1].first);
266 clipped.nextBoundaryP1 = intersections[0].second;
267 clipped.nextBoundaryP2 = intersections[1].second;
275 point_data<long> VoronoiTesselation::ToVoronoiPoint(
const QPointF &point)
277 return point_data<long>(std::roundl(point.x() * g_points_precision), std::roundl(point.y() * g_points_precision));
280 QPointF VoronoiTesselation::FromVoronoiPoint(
const voronoi_diagram<double>::vertex_type &point)
282 return QPointF(point.x() / g_points_precision, point.y() / g_points_precision);
static QPolygonF OpenPolygon(const QPolygonF &polygon)
Opens the polygon, ie.
Namespace for SimPT tissue editor package.
Combo header for circular iterator.
virtual ~VoronoiTesselation()
Destructor.
static std::list< QPolygonF > ClipPolygon(const QPolygonF &polygon1, const QPolygonF &polygon2)
Clip a polygon with another polygon and return the intersecting subpolygons.
see the online Qt documentation
see the online Qt documentation
static bool IsClockwise(const QPolygonF &polygon)
Checks whether the vertices of a polygon are ordered clockwise or counterclockwise.
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
Reimplementation of QGraphicsItem::mouseDoubleClickEvent to add a point to the tesselation.
see the online Qt documentation
Namespace for container related classes.
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event)
Reimplementation of QGraphicsItem::mousePressEvent to remove a point from the tesselation.
Interface for VoronoiTesselation.
Functions to manipulate polygons.
CircularIterator< T > next(CircularIterator< T > i)
Helper yields the position the iterator would have if moved forward (in circular fashion) by 1 positi...
std::list< QPolygonF > GetCellPolygons()
Returns the polygons of cell of the Voronoi tesselation.
ConstCircularIterator< typename T::const_iterator > make_const_circular(const T &c)
Helper produces const circular iterator whose range corresponds to the begin and end iterators of a c...
Interface for PolygonUtils.
see the online Qt documentation