Skip to content

Instantly share code, notes, and snippets.

@yurydelendik
Created October 25, 2012 01:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yurydelendik/3950036 to your computer and use it in GitHub Desktop.
Save yurydelendik/3950036 to your computer and use it in GitHub Desktop.
Polygon stuff
<!DOCTYPE html>
<html>
<head>
<script>
function PolygonTracker() {
this.polygons = [];
this.currentPoints = [];
this.oddEvenRule = false;
this.APPROX_SECTIONS = 50;
}
PolygonTracker.prototype = {
closePolygon: function () {
if (this.currentPoints.length == 0) {
return;
}
var polygon = {
oddEvenRule: this.oddEvenRule,
points: this.currentPoints
};
this.polygons.push(polygon);
this.currentPoints = [];
this.oddEvenRule = false;
},
addPoint: function(x, y) {
this.currentPoints.push(x);
this.currentPoints.push(y);
},
approxQuadraticCurve: function(cpx, cpy, x, y) {
var points = this.currentPoints;
var x0 = points[points.length - 2], y0 = points[points.length - 1];
var sections = this.APPROX_SECTIONS;
for (var i = 1; i <= sections; i++) {
var r2 = i / sections, r1 = 1 - r2;
var cx1 = x0 * r1 + cpx * r2, cy1 = y0 * r1 + cpy * r2;
var cx2 = cpx * r1 + x * r2, cy2 = cpy * r1 + y * r2;
var cx = cx1 * r1 + cx2 * r2, cy = cy1 * r1 + cy2 * r2;
this.addPoint(cx, cy);
}
},
approxBezierCurve: function(cpx1, cpy1, cpx2, cpy2, x, y) {
var points = this.currentPoints;
var x0 = points[points.length - 2], y0 = points[points.length - 1];
var sections = this.APPROX_SECTIONS;
for (var i = 1; i <= sections; i++) {
var r2 = i / sections, r1 = 1 - r2;
var cx1 = x0 * r1 + cpx1 * r2, cy1 = y0 * r1 + cpy1 * r2;
var cx2 = cpx1 * r1 + cpx2 * r2, cy2 = cpy1 * r1 + cpy2 * r2;
var cx3 = cpx2 * r1 + x * r2, cy3 = cpy2 * r1 + y * r2;
var cx12 = cx1 * r1 + cx2 * r2, cy12 = cy1 * r1 + cy2 * r2;
var cx23 = cx2 * r1 + cx3 * r2, cy23 = cy2 * r1 + cy3 * r2;
var cx = cx12 * r1 + cx23 * r2, cy = cy12 * r1 + cy23 * r2;
this.addPoint(cx, cy);
}
},
getBounds: function () {
var i = 0;
while (i < this.polygons.length && this.polygons[i].points.length === 0) {
i++;
}
if (i >= this.polygons.length) {
return { minX: 0, minY: 0, maxX: 0, maxY: 0 };
}
var points = this.polygons[i].points;
var minX = points[0], minY = points[1], maxX = points[0], maxY = points[1];
for (; i < this.polygons.length; i++) {
var points = this.polygons[i].points;
for (var j = 0; j < points.length; j += 2) {
var px = points[j], py = points[j + 1];
if (minX > px) minX = px;
if (minY > py) minY = py;
if (maxX < px) maxX = px;
if (maxY < py) maxY = py;
}
}
return { minX: minX, minY: minY, maxX: maxX, maxY: maxY };
},
isIn: function(x, y) {
// running directional vector towards (+infinity, y - tiny)
var dx = -1e+4, dy = 1e-4;
for (var i = 0; i < this.polygons.length; i++) {
var polygon = this.polygons[i];
var points = polygon.points;
if (points.length < 6) continue;
// .. and trying to intersect with every polygon edge
var px1 = points[points.length - 2], py1 = points[points.length - 1];
var intersections = 0;
for (var j = 0; j < points.length; j += 2) {
var px2 = points[j], py2 = points[j + 1];
// (x1 - x) * (y1 - y2) - (y1 - y) * (x1 - x2) = 0, a*x + b*y = c
var a1 = -(py1 - py2), b1 = (px1 - px2), c1 = px1 * py2 - py1 * px2;
// dx = x1 - x2, dy = y1 - y2
var a2 = -dy, b2 = dx, c2 = y * dx - x * dy;
var d = a1 * b2 - a2 * b1;
if (d == 0) continue; // (px1,py1) == (px2,py2)
var int_x = (c1 * b2 - c2 * b1) / d;
// var int_y = (a1 * c2 - a2 * c1) / d;
var intersect = false;
if (int_x >= x) {
var s1 = (x - px1) * dy - (y - py1) * dx;
var s2 = (x - px2) * dy - (y - py2) * dx;
if ((s1 < 0) !== (s2 < 0)) {
intersections += s1 < 0 ? 1 : -1;
}
}
px1 = px2; py1 = py2;
}
if (polygon.oddEvenRule) {
if (Math.abs(intersections) % 2)
return true;
} else {
if (intersections != 0)
return true;
}
}
return false;
}
};
var pt = new PolygonTracker();
var last = null, first = null, cp = [], last0;
</script>
<style>
body { color: white; background: navy; }
canvas { background: white; }
#msg { position: fixed; top: 0; width: 200px; height: 100%; left: 500px; }
</style>
</head>
<body>
<canvas id="c" width="400" height="400"></canvas><br>
<input type="radio" name="mode" id="add" checked> add
<input type="radio" name="mode" id="in"> in check
<input type="radio" name="fill" id="nonzero" checked> n-zero
<input type="radio" name="fill" id="oddeven"> oddeven
<button id="new">close</button>
<button id="bounds">bounds</button><br>
<textarea id="msg"></textarea>
<script>
var c = document.getElementById("c");
var ctx = c.getContext("2d");
function msg(s) {
document.getElementById('msg').value += s + '\n';
}
function closePolygon() {
if (!last) return;
pt.closePolygon();
pt.oddEvenRule = document.getElementById('oddeven').checked;
ctx.strokeStyle = "green";
ctx.beginPath();
ctx.moveTo(last.x, last.y);
ctx.lineTo(first.x, first.y);
ctx.stroke();
last = first = null;
msg('polygon closed');
}
c.addEventListener("mousedown", function (evt) {
var x = evt.clientX - c.offsetLeft;
var y = evt.clientY - c.offsetTop;
if (document.getElementById('add').checked) {
if (last) {
ctx.strokeStyle = "green";
ctx.beginPath();
ctx.moveTo(last.x, last.y);
ctx.lineTo(x, y);
ctx.stroke();
} else
first = {x: x, y: y};
ctx.fillStyle = 'red';
ctx.fillRect(x - 1, y - 1, 3, 3);
last = {x: x, y: y};
if (evt.shiftKey) {
cp.push({x: x, y: y});
msg("cp" + cp.length);
} else {
switch (cp.length) {
default:
pt.addPoint(x, y);
msg("addpoint");
break;
case 1:
pt.approxQuadraticCurve(cp[0].x, cp[0].y, x, y);
ctx.strokeStyle = "orange";
ctx.beginPath();
ctx.moveTo(last0.x, last0.y);
ctx.quadraticCurveTo(cp[0].x, cp[0].y, x, y);
ctx.stroke();
msg("quad");
break;
case 2:
pt.approxBezierCurve(cp[0].x, cp[0].y, cp[1].x, cp[1].y, x, y);
ctx.strokeStyle = "pink";
ctx.beginPath();
ctx.moveTo(last0.x, last0.y);
ctx.bezierCurveTo(cp[0].x, cp[0].y, cp[1].x, cp[1].y, x, y);
ctx.stroke();
msg("bez");
break;
}
cp = [];
last0 = last;
}
} else {
closePolygon();
msg('x=' + x + ',y=' + y + ': ' + pt.isIn(x, y));
}
});
document.getElementById('msg').value = '';
document.getElementById('add').checked = true;
document.getElementById('in').checked = false;
document.getElementById('nonzero').checked = true;
document.getElementById('oddeven').checked = false;
document.getElementById('new').addEventListener("click", function (evt) {
closePolygon();
});
document.getElementById('bounds').addEventListener("click", function (evt) {
closePolygon();
var b = pt.getBounds();
ctx.strokeStyle = "blue";
ctx.beginPath();
ctx.rect(b.minX, b.minY, b.maxX - b.minX, b.maxY - b.minY);
ctx.stroke();
});
document.getElementById('nonzero').addEventListener("click", function (evt) {
pt.oddEvenRule = false;
});
document.getElementById('oddeven').addEventListener("click", function (evt) {
pt.oddEvenRule = true;
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment