Did some major digging and found some good material. It took me a while, but I have it working -- I can take two polygons and combine them into one polygon.
In case it may be helpful to anyone, here is the code I wrote (in JavaScript):
Code:
function polyUnion(poly1, poly2) {
var i1 = 0,
i2 = 0,
i1Active = true, //keep track of which polygon we're tracing
numLines = poly1.points.length + poly2.points.length,
res = { // function result object
points : []
};
// This is where the magic begins.
for (var i = 0; i < numLines; i++) {
// If we go out of bounds, wrap the value
i1 %= poly1.points.length;
i2 %= poly2.points.length;
// Get the line data for intersection testing
// Add a "buried" property for splicing to remove extraneous (and problematic) points
var line1 = {
startPoint : {
x : poly1.points[i1].x,
y : poly1.points[i1].y,
buried : pointInPoly(poly2, poly1.points[i1])
},
endPoint : {
x : poly1.points[(i1 + 1) % poly1.points.length].x,
y : poly1.points[(i1 + 1) % poly1.points.length].y
}
};
var line2 = {
startPoint : {
x : poly2.points[i2].x,
y : poly2.points[i2].y,
buried : pointInPoly(poly1, poly2.points[i2])
},
endPoint : {
x : poly2.points[(i2 + 1) % poly2.points.length].x,
y : poly2.points[(i2 + 1) % poly2.points.length].y
}
};
var lineTest;
// Need to know which polygon we are currently following to know which test to do
if (i1Active) {
lineTest = linePolyTest(line1, poly2);
} else {
lineTest = linePolyTest(line2, poly1);
}
if (lineTest) {
// We have an intersection
if (i1Active) {
res.points.push({
x : line1.startPoint.x,
y : line1.startPoint.y,
buried : pointInPoly(poly2, line1.startPoint)
});
res.points.push(lineTest);
i1++;
} else {
res.points.push({
x : line2.startPoint.x,
y : line2.startPoint.y,
buried : pointInPoly(poly1, line2.startPoint)
});
res.points.push(lineTest);
i2++;
}
i1Active = !i1Active; //since we have an intersection, we should switch polygons
} else {
// No intersection, just need to get the next point in the polygon
if (i1Active) {
res.points.push({
x : line1.startPoint.x,
y : line1.startPoint.y,
buried : pointInPoly(poly2, line1.startPoint)
});
i1++;
} else {
res.points.push({
x : line2.startPoint.x,
y : line2.startPoint.y,
buried : pointInPoly(poly1, line2.startPoint)
});
i2++;
}
}
}
// Now we can get rid of buried points to help prevent geometric glitches
var numSpliced = 0;
for (var i = 0, currPoint; currPoint = res.points[i]; i++) {
if (currPoint.buried == true) {
res.points.splice(i, 1);
numSpliced++;
}
}
// Woohoo, we made it!
return res;
}
function linePolyTest(line, poly) {
// This function tests to see if a line intersects a polygon
// It simply takes the line argument, and then iterates through the polygon
// argument's points, building lines for simple intersection testing
for (var i = 0; i < poly.points.length; i++) {
var polyLine = {
startPoint : {
x : poly.points[i].x,
y : poly.points[i].y
},
endPoint : {
x : poly.points[(i+1) % poly.points.length].x,
y : poly.points[(i+1) % poly.points.length].y
}
};
// Perform the actual line intersection test and return the point of an intersection
var lineTest = lineIntersect(line, polyLine);
if (lineTest) {
return lineTest;
}
}
}
function pointInPoly(poly, point) {
// Tests whether a point is inside a polygon.
var i,
n = poly.points.length,
angle = 0.0,
p1, p2;
for (i = 0; i < n; i++) {
p1 = {
x : poly.points[i].x - point.x,
y : poly.points[i].y - point.y
};
p2 = {
x : poly.points[(i+1)%n].x - point.x,
y : poly.points[(i+1)%n].y - point.y
};
angle += getAngle(p1.x, p1.y, p2.x, p2.y);
}
if (Math.abs(angle) < Math.PI) {
return false;
} else {
return true;
}
}
function getAngle(x1, y1, x2, y2) {
// finds the angle between two points
var dtheta, theta1, theta2;
theta1 = Math.atan2(y1,x1);
theta2 = Math.atan2(y2,x2);
dtheta = theta2 - theta1;
while (dtheta > Math.PI) {
dtheta -= Math.PI * 2;
}
while (dtheta < -Math.PI) {
dtheta += Math.PI * 2;
}
return dtheta;
}
function lineIntersect(line1, line2) {
// Line Intersection test
// Thank you to Paul Bourke -- http://paulbourke.net/
var denom = ((line2.endPoint.y - line2.startPoint.y) * (line1.endPoint.x - line1.startPoint.x)) -
((line2.endPoint.x - line2.startPoint.x) * (line1.endPoint.y - line1.startPoint.y));
var nume_a = ((line2.endPoint.x - line2.startPoint.x) * (line1.startPoint.y - line2.startPoint.y)) -
((line2.endPoint.y - line2.startPoint.y) * (line1.startPoint.x - line2.startPoint.x));
var nume_b = ((line1.endPoint.x - line1.startPoint.x) * (line1.startPoint.y - line2.startPoint.y)) -
((line1.endPoint.y - line1.startPoint.y) * (line1.startPoint.x - line2.startPoint.x));
if (denom == 0.0) {
return false;
}
var ua = nume_a / denom;
var ub = nume_b / denom;
if (ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0) {
return {
x : line1.startPoint.x + ua * (line1.endPoint.x - line1.startPoint.x),
y : line1.startPoint.y + ua * (line1.endPoint.y - line1.startPoint.y)
};
}
return false;
}
Probably not the most effective method... if anyone has any tips, I would appreciate it if you would pass them along!
