Correctly triangulate bad polygons.

This commit is contained in:
GaborFarkas
2016-10-02 12:25:02 +02:00
parent 657f2c7b6c
commit a0089b5126
2 changed files with 110 additions and 56 deletions

View File

@@ -1701,11 +1701,6 @@ ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) {
changed: false
};
/**
* @private
*/
this.rtree_ = new ol.structs.RBush();
};
ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay);
@@ -1721,8 +1716,9 @@ ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function(
flatCoordinates, holeFlatCoordinates, stride) {
// Triangulate the polygon
var outerRing = new ol.structs.LinkedList();
var rtree = new ol.structs.RBush();
// Initialize the outer ring
var maxX = this.processFlatCoordinates_(flatCoordinates, stride, outerRing, true);
var maxX = this.processFlatCoordinates_(flatCoordinates, stride, outerRing, rtree, true);
// Eliminate holes, if there are any
if (holeFlatCoordinates.length) {
@@ -1735,20 +1731,17 @@ ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function(
};
holeLists.push(holeList);
holeList.maxX = this.processFlatCoordinates_(holeFlatCoordinates[i],
stride, holeList.list, false);
stride, holeList.list, rtree, false);
}
holeLists.sort(function(a, b) {
return b.maxX - a.maxX;
});
for (i = 0; i < holeLists.length; ++i) {
this.bridgeHole_(holeLists[i].list, holeLists[i].maxX, outerRing, maxX);
this.bridgeHole_(holeLists[i].list, holeLists[i].maxX, outerRing, maxX, rtree);
}
}
this.classifyPoints_(outerRing, false);
this.triangulate_(outerRing);
// We clear the R-Tree here, because hit detection does not call finish()
this.rtree_.clear();
this.classifyPoints_(outerRing, rtree, false);
this.triangulate_(outerRing, rtree);
};
@@ -1758,11 +1751,12 @@ ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function(
* @param {Array.<number>} flatCoordinates Flat coordinates.
* @param {number} stride Stride.
* @param {ol.structs.LinkedList} list Linked list.
* @param {ol.structs.RBush} rtree R-Tree of the polygon.
* @param {boolean} clockwise Coordinate order should be clockwise.
* @return {number} Maximum X value.
*/
ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function(
flatCoordinates, stride, list, clockwise) {
flatCoordinates, stride, list, rtree, clockwise) {
var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates,
0, flatCoordinates.length, stride);
var i, ii, maxX;
@@ -1807,7 +1801,7 @@ ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function(
extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x),
Math.max(p0.y, p1.y)]);
}
this.rtree_.load(extents, segments);
rtree.load(extents, segments);
return maxX;
};
@@ -1817,10 +1811,11 @@ ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function(
* Classifies the points of a polygon list as convex, reflex. Removes collinear vertices.
* @private
* @param {ol.structs.LinkedList} list Polygon ring.
* @param {ol.structs.RBush} rtree R-Tree of the polygon.
* @param {boolean} ccw The orientation of the polygon is counter-clockwise.
* @return {boolean} There were reclassified points.
*/
ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, ccw) {
ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, rtree, ccw) {
var start = list.firstItem();
var s0 = start;
var s1 = list.nextItem();
@@ -1831,7 +1826,7 @@ ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, ccw) {
ol.render.webgl.triangleIsCounterClockwise(s0.p0.x, s0.p0.y, s0.p1.x,
s0.p1.y, s1.p1.x, s1.p1.y);
if (reflex === undefined) {
this.removeItem_(s0, s1, list);
this.removeItem_(s0, s1, list, rtree);
pointsReclassified = true;
if (s1 === start) {
start = list.getNextItem();
@@ -1855,9 +1850,10 @@ ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, ccw) {
* @param {number} holeMaxX Maximum X value of the hole.
* @param {ol.structs.LinkedList} list Linked list of the polygon.
* @param {number} listMaxX Maximum X value of the polygon.
* @param {ol.structs.RBush} rtree R-Tree of the polygon.
*/
ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX,
list, listMaxX) {
list, listMaxX, rtree) {
var seg = hole.firstItem();
while (seg.p1.x !== holeMaxX) {
seg = hole.nextItem();
@@ -1871,7 +1867,7 @@ ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX,
/** @type {ol.WebglPolygonVertex} */
var p5;
var intersectingSegments = this.getIntersections_({p0: p1, p1: p2}, true);
var intersectingSegments = this.getIntersections_({p0: p1, p1: p2}, rtree, true);
for (i = 0, ii = intersectingSegments.length; i < ii; ++i) {
var currSeg = intersectingSegments[i];
if (currSeg.p0 !== p1 && currSeg.p1 !== p1) {
@@ -1887,7 +1883,7 @@ ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX,
}
bestPoint = seg.p1;
var pointsInTriangle = this.getPointsInTriangle_(p1, p5, seg.p1);
var pointsInTriangle = this.getPointsInTriangle_(p1, p5, seg.p1, rtree);
if (pointsInTriangle.length) {
var theta = Infinity;
for (i = 0, ii = pointsInTriangle.length; i < ii; ++i) {
@@ -1910,8 +1906,8 @@ ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX,
var p1Bridge = {x: seg.p1.x, y: seg.p1.y, i: seg.p1.i, reflex: undefined};
hole.getNextItem().p0 = p0Bridge;
this.insertItem_(p1, seg.p1, hole, true);
this.insertItem_(p1Bridge, p0Bridge, hole, true);
this.insertItem_(p1, seg.p1, hole, rtree);
this.insertItem_(p1Bridge, p0Bridge, hole, rtree);
seg.p1 = p1Bridge;
hole.setFirstItem();
list.concat(hole);
@@ -1921,17 +1917,18 @@ ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX,
/**
* @private
* @param {ol.structs.LinkedList} list Linked list of the polygon.
* @param {ol.structs.RBush} rtree R-Tree of the polygon.
*/
ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list) {
ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list, rtree) {
var ccw = false;
var simple = this.isSimple_(list);
var simple = this.isSimple_(list, rtree);
var pass = 0;
// Start clipping ears
while (list.getLength() > 3) {
if (simple) {
if (!this.clipEars_(list, simple, ccw)) {
if (!this.classifyPoints_(list, ccw)) {
if (!this.clipEars_(list, rtree, simple, ccw)) {
if (!this.classifyPoints_(list, rtree, ccw)) {
// We have the wrongly oriented remains of a self-intersecting polygon.
pass++;
if (pass > 1) {
@@ -1939,18 +1936,19 @@ ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list) {
break;
}
ccw = !ccw;
this.classifyPoints_(list, ccw);
this.classifyPoints_(list, rtree, ccw);
}
}
} else {
if (!this.clipEars_(list, simple, ccw)) {
if (!this.clipEars_(list, rtree, simple, ccw)) {
// We ran out of ears, try to reclassify.
if (!this.classifyPoints_(list, ccw)) {
if (!this.classifyPoints_(list, rtree, ccw)) {
// We have a bad polygon, try to resolve local self-intersections.
if (!this.resolveLocalSelfIntersections_(list)) {
simple = this.isSimple_(list);
if (!this.resolveLocalSelfIntersections_(list, rtree)) {
simple = this.isSimple_(list, rtree);
if (!simple) {
// We have a really bad polygon, try more time consuming methods.
this.splitPolygon_(list, rtree);
break;
}
}
@@ -1970,11 +1968,12 @@ ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list) {
/**
* @private
* @param {ol.structs.LinkedList} list Linked list of the polygon.
* @param {ol.structs.RBush} rtree R-Tree of the polygon.
* @param {boolean} simple The polygon is simple.
* @param {boolean} ccw Orientation of the polygon is counter-clockwise.
* @return {boolean} There were processed ears.
*/
ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, simple, ccw) {
ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, rtree, simple, ccw) {
var numIndices = this.indices_.length;
var start = list.firstItem();
var s0 = list.getPrevItem();
@@ -1991,8 +1990,8 @@ ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, simple, ccw)
// We might have a valid ear
var diagonalIsInside = ccw ? this.diagonalIsInside_(s3.p1, p2, p1, p0,
s0.p0) : this.diagonalIsInside_(s0.p0, p0, p1, p2, s3.p1);
if ((simple || this.getIntersections_({p0: p0, p1: p2}).length === 0) &&
diagonalIsInside && this.getPointsInTriangle_(p0, p1, p2, true).length === 0) {
if ((simple || this.getIntersections_({p0: p0, p1: p2}, rtree).length === 0) &&
diagonalIsInside && this.getPointsInTriangle_(p0, p1, p2, rtree, true).length === 0) {
//The diagonal is completely inside the polygon
if (p0.reflex === false || p2.reflex === false ||
ol.geom.flat.orient.linearRingIsClockwise([s0.p0.x, s0.p0.y, p0.x,
@@ -2001,7 +2000,7 @@ ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, simple, ccw)
this.indices_[numIndices++] = p0.i;
this.indices_[numIndices++] = p1.i;
this.indices_[numIndices++] = p2.i;
this.removeItem_(s1, s2, list);
this.removeItem_(s1, s2, list, rtree);
if (s2 === start) {
start = s3;
}
@@ -2023,10 +2022,11 @@ ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, simple, ccw)
/**
* @private
* @param {ol.structs.LinkedList} list Linked list of the polygon.
* @param {ol.structs.RBush} rtree R-Tree of the polygon.
* @return {boolean} There were resolved intersections.
*/
ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = function(
list) {
list, rtree) {
var start = list.firstItem();
list.nextItem();
var s0 = start;
@@ -2043,12 +2043,12 @@ ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = functio
var seg = list.prevItem();
list.removeItem();
this.rtree_.remove(seg);
rtree.remove(seg);
s0.p1 = p;
s1.p0 = p;
this.rtree_.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y),
rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y),
Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0);
this.rtree_.update([Math.min(s1.p0.x, s1.p1.x), Math.min(s1.p0.y, s1.p1.y),
rtree.update([Math.min(s1.p0.x, s1.p1.x), Math.min(s1.p0.y, s1.p1.y),
Math.max(s1.p0.x, s1.p1.x), Math.max(s1.p0.y, s1.p1.y)], s1);
this.indices_[numIndices++] = seg.p0.i;
this.indices_[numIndices++] = seg.p1.i;
@@ -2070,13 +2070,14 @@ ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = functio
/**
* @private
* @param {ol.structs.LinkedList} list Linked list of the polygon.
* @param {ol.structs.RBush} rtree R-Tree of the polygon.
* @return {boolean} The polygon is simple.
*/
ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list) {
ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list, rtree) {
var start = list.firstItem();
var seg = start;
do {
if (this.getIntersections_(seg).length) {
if (this.getIntersections_(seg, rtree).length) {
return false;
}
seg = list.nextItem();
@@ -2085,6 +2086,50 @@ ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list) {
};
/**
* @private
* @param {ol.structs.LinkedList} list Linked list of the polygon.
* @param {ol.structs.RBush} rtree R-Tree of the polygon.
*/
ol.render.webgl.PolygonReplay.prototype.splitPolygon_ = function(list, rtree) {
var start = list.firstItem();
var s0 = start;
do {
var intersections = this.getIntersections_(s0, rtree);
if (intersections.length) {
var s1 = intersections[0];
var n = this.vertices_.length / 2;
var intersection = this.calculateIntersection_(s0.p0,
s0.p1, s1.p0, s1.p1);
var p = this.createPoint_(intersection[0], intersection[1], n);
var newPolygon = new ol.structs.LinkedList();
var newRtree = new ol.structs.RBush();
this.insertItem_(p, s0.p1, newPolygon, newRtree);
s0.p1 = p;
rtree.update([Math.min(s0.p0.x, p.x), Math.min(s0.p0.y, p.y),
Math.max(s0.p0.x, p.x), Math.max(s0.p0.y, p.y)], s0);
var currItem = list.nextItem();
while (currItem !== s1) {
this.insertItem_(currItem.p0, currItem.p1, newPolygon, newRtree);
rtree.remove(currItem);
list.removeItem();
currItem = list.getCurrItem();
}
this.insertItem_(s1.p0, p, newPolygon, newRtree);
s1.p0 = p;
rtree.update([Math.min(s1.p1.x, p.x), Math.min(s1.p1.y, p.y),
Math.max(s1.p1.x, p.x), Math.max(s1.p1.y, p.y)], s1);
this.classifyPoints_(list, rtree, false);
this.triangulate_(list, rtree);
this.classifyPoints_(newPolygon, newRtree, false);
this.triangulate_(newPolygon, newRtree);
break;
}
s0 = list.nextItem();
} while (s0 !== start);
};
/**
* @private
* @param {number} x X coordinate.
@@ -2112,8 +2157,8 @@ ol.render.webgl.PolygonReplay.prototype.createPoint_ = function(x, y, i) {
* @param {ol.WebglPolygonVertex} p0 First point of segment.
* @param {ol.WebglPolygonVertex} p1 Second point of segment.
* @param {ol.structs.LinkedList} list Polygon ring.
* @param {boolean=} opt_rtree Insert the segment into the R-Tree.
* @return {Object.<string, ol.WebglPolygonVertex>} segment.
* @param {ol.structs.RBush=} opt_rtree Insert the segment into the R-Tree.
* @return {ol.WebglPolygonSegment} segment.
*/
ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt_rtree) {
var seg = {
@@ -2122,7 +2167,7 @@ ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt
};
list.insertItem(seg);
if (opt_rtree) {
this.rtree_.insert([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y),
opt_rtree.insert([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y),
Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)], seg);
}
return seg;
@@ -2131,15 +2176,16 @@ ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt
/**
* @private
* @param {Object.<string, ol.WebglPolygonVertex>} s0 Segment before the remove candidate.
* @param {Object.<string, ol.WebglPolygonVertex>} s1 Remove candidate segment.
* @param {ol.WebglPolygonSegment} s0 Segment before the remove candidate.
* @param {ol.WebglPolygonSegment} s1 Remove candidate segment.
* @param {ol.structs.LinkedList} list Polygon ring.
* @param {ol.structs.RBush} rtree R-Tree of the polygon.
*/
ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list) {
ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list, rtree) {
list.removeItem();
s0.p1 = s1.p1;
this.rtree_.remove(s1);
this.rtree_.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y),
rtree.remove(s1);
rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y),
Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0);
};
@@ -2149,14 +2195,15 @@ ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list) {
* @param {ol.WebglPolygonVertex} p0 First point.
* @param {ol.WebglPolygonVertex} p1 Second point.
* @param {ol.WebglPolygonVertex} p2 Third point.
* @param {ol.structs.RBush} rtree R-Tree of the polygon.
* @param {boolean=} opt_reflex Only include reflex points.
* @return {Array.<Object.<string, number>>} Points in the triangle.
* @return {Array.<ol.WebglPolygonVertex>} Points in the triangle.
*/
ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1,
p2, opt_reflex) {
p2, rtree, opt_reflex) {
var i, ii, j, p;
var result = [];
var segmentsInExtent = this.rtree_.getInExtent([Math.min(p0.x, p1.x, p2.x),
var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x, p2.x),
Math.min(p0.y, p1.y, p2.y), Math.max(p0.x, p1.x, p2.x), Math.max(p0.y,
p1.y, p2.y)]);
for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) {
@@ -2178,14 +2225,15 @@ ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1,
/**
* @private
* @param {Object.<string, ol.WebglPolygonVertex>} segment Segment.
* @param {ol.WebglPolygonSegment} segment Segment.
* @param {ol.structs.RBush} rtree R-Tree of the polygon.
* @param {boolean=} opt_touch Touching segments should be considered an intersection.
* @return {Array.<Object.<string, ol.WebglPolygonVertex>>} Intersecting segments.
* @return {Array.<ol.WebglPolygonSegment>} Intersecting segments.
*/
ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, opt_touch) {
ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, rtree, opt_touch) {
var p0 = segment.p0;
var p1 = segment.p1;
var segmentsInExtent = this.rtree_.getInExtent([Math.min(p0.x, p1.x),
var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x),
Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)]);
var result = [];
var i, ii;

View File

@@ -694,6 +694,12 @@ ol.ViewAnimation;
ol.WebglBufferCacheEntry;
/**
* @typedef {{p0: ol.WebglPolygonVertex,
* p1: ol.WebglPolygonVertex}}
*/
ol.WebglPolygonSegment;
/**
* @typedef {{x: number,
* y: number,