StackGenVis: Alignment of Data, Algorithms, and Models for Stacking Ensemble Learning Using Performance Metrics
https://doi.org/10.1109/TVCG.2020.3030352
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1362 lines
33 KiB
1362 lines
33 KiB
4 years ago
|
<!doctype html>
|
||
|
<html lang="en">
|
||
|
<head>
|
||
|
<title>Polygon Clipping - Based somewhat on F. Martinez et al. (2008)</title>
|
||
|
<!--
|
||
|
|
||
|
/*
|
||
|
* @copyright 2016 Sean Connelly (@voidqk), http://syntheti.cc
|
||
|
* @license MIT
|
||
|
* @preserve Project Home: https://github.com/voidqk/polybooljs
|
||
|
*/
|
||
|
|
||
|
-->
|
||
|
<style>
|
||
|
body, html {
|
||
|
background-color: #ddf;
|
||
|
font-family: sans-serif;
|
||
|
color: #333;
|
||
|
}
|
||
|
a {
|
||
|
color: #33d;
|
||
|
text-decoration: none;
|
||
|
}
|
||
|
a:hover, a:active {
|
||
|
text-decoration: underline;
|
||
|
}
|
||
|
p {
|
||
|
text-align: center;
|
||
|
}
|
||
|
</style>
|
||
|
<script src="./polybool.js"></script>
|
||
|
</head>
|
||
|
<body onload="javascript: init();">
|
||
|
<script>
|
||
|
|
||
|
var cnv, ctx;
|
||
|
var rscale = 2;
|
||
|
var wscale = 2;
|
||
|
var cnvWidth, cnvHeight;
|
||
|
var hover = false;
|
||
|
var mode = 'Intersect';
|
||
|
|
||
|
var polyCases = [{
|
||
|
name: 'Assorted Polygons',
|
||
|
poly1: {
|
||
|
regions: [
|
||
|
[ [500,60],[500,150],[320,150],[260,210],[200,150],[200,60] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
},
|
||
|
poly2: {
|
||
|
regions: [
|
||
|
[ [500,60],[500,150],[460,190],[460,110],[400,180],[160,90] ],
|
||
|
[ [220,170],[260,30],[310,160],[310,210],[260,170],[240,190] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
}
|
||
|
}, {
|
||
|
name: 'Simple Rectangles',
|
||
|
poly1: {
|
||
|
regions: [
|
||
|
[ [200, 50], [600, 50], [600, 150], [200, 150] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
},
|
||
|
poly2: {
|
||
|
regions: [
|
||
|
[ [300, 150], [500, 150], [500, 200], [300, 200] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
}
|
||
|
}, {
|
||
|
name: 'Shared Right Edge',
|
||
|
poly1: {
|
||
|
regions: [
|
||
|
[ [500,60],[500,150],[200,150],[200,60] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
},
|
||
|
poly2: {
|
||
|
regions: [
|
||
|
[ [500,60],[500,150],[450,230],[400,180],[590,60] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
}
|
||
|
}, {
|
||
|
name: 'Simple Boxes',
|
||
|
poly1: {
|
||
|
regions: [
|
||
|
[ [500,60],[500,150],[200,150],[200,60] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
},
|
||
|
poly2: {
|
||
|
regions: [
|
||
|
[ [500,60],[500,150],[380,150],[380,60] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
}
|
||
|
}, {
|
||
|
name: 'Simple Self-Overlap',
|
||
|
poly1: {
|
||
|
regions: [
|
||
|
[ [200,50],[400,50],[400,150],[200,150] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
},
|
||
|
poly2: {
|
||
|
regions: [
|
||
|
[ [400,150],[500,150],[300,50],[400,50] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
}
|
||
|
}, {
|
||
|
name: 'M Shape',
|
||
|
poly1: {
|
||
|
regions: [
|
||
|
[ [570,60],[570,150],[60,150],[60,60] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
},
|
||
|
poly2: {
|
||
|
regions: [
|
||
|
[ [500,60],[500,130],[330,20],[180,130],[120,60] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
}
|
||
|
}, {
|
||
|
name: 'Two Triangles With Common Edge',
|
||
|
poly1: {
|
||
|
regions: [
|
||
|
[ [620,60],[620,150],[90,150],[90,60] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
},
|
||
|
poly2: {
|
||
|
regions: [
|
||
|
[ [350,60],[480,200],[180,60] ],
|
||
|
[ [180,60],[500,60],[180,220] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
}
|
||
|
}, {
|
||
|
name: 'Two Trianges With Common Edge, pt. 2',
|
||
|
poly1: {
|
||
|
regions: [
|
||
|
[ [620,60],[620,150],[90,150],[90,60] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
},
|
||
|
poly2: {
|
||
|
regions: [
|
||
|
[ [400,60],[270,120],[210,60] ],
|
||
|
[ [210,60],[530,60],[210,220] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
}
|
||
|
}, {
|
||
|
name: 'Two Triangles With Common Edge, pt. 3',
|
||
|
poly1: {
|
||
|
regions: [
|
||
|
[ [620,60],[620,150],[90,150],[90,60] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
},
|
||
|
poly2: {
|
||
|
regions: [
|
||
|
[ [370,60],[300,220],[560,60] ],
|
||
|
[ [180,60],[500,60],[180,220] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
}
|
||
|
}, {
|
||
|
name: 'Three Triangles',
|
||
|
poly1: {
|
||
|
regions: [
|
||
|
[ [500,60],[500,150],[320,150] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
},
|
||
|
poly2: {
|
||
|
regions: [
|
||
|
[ [500,60],[500,150],[460,190] ],
|
||
|
[ [220,170],[260,30],[310,160] ],
|
||
|
[ [260,210],[200,150],[200,60] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
}
|
||
|
}, {
|
||
|
name: 'Adjacent Edges in Status',
|
||
|
poly1: {
|
||
|
regions: [
|
||
|
[ [620,60],[620,150],[90,150],[90,60] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
},
|
||
|
poly2: {
|
||
|
regions: [
|
||
|
[ [110,60],[420,230],[540,60] ],
|
||
|
[ [180,60],[430,160],[190,200] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
}
|
||
|
}, {
|
||
|
name: 'Coincident Self-Intersection',
|
||
|
poly1: {
|
||
|
regions: [
|
||
|
[ [500,60],[500,150],[320,150],[260,210],[200,150],[200,60] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
},
|
||
|
poly2: {
|
||
|
regions: [
|
||
|
[ [500,60],[500,150],[460,190],[460,110],[400,180],[70,90] ],
|
||
|
[ [220,170],[580,130],[310,160],[310,210],[260,170],[240,190] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
}
|
||
|
}, {
|
||
|
name: 'Coincident Self-Intersection, pt. 2',
|
||
|
poly1: {
|
||
|
regions: [
|
||
|
[ [100, 100], [200, 200], [300, 100] ],
|
||
|
[ [200, 100], [300, 200], [400, 100] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
},
|
||
|
poly2: {
|
||
|
regions: [
|
||
|
[ [50, 50], [200, 50], [300, 150] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
}
|
||
|
}, {
|
||
|
name: 'Triple Overlap',
|
||
|
poly1: {
|
||
|
regions: [
|
||
|
[ [500, 60], [500, 150], [320, 150], [260, 210], [200, 150], [200, 60] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
},
|
||
|
poly2: {
|
||
|
regions:[
|
||
|
[ [500, 60], [500, 150], [370, 60], [310, 60], [400, 180], [230, 60] ],
|
||
|
[ [260, 60], [410, 60], [310, 160], [310, 210], [260, 170], [240, 190] ]
|
||
|
],
|
||
|
inverted: false
|
||
|
}
|
||
|
}];
|
||
|
|
||
|
var nextDemoIndex = -1;
|
||
|
var caseName, poly1, poly2, polyBox;
|
||
|
var scaleToFit = false;
|
||
|
function nextDemo(d){
|
||
|
nextDemoIndex = (nextDemoIndex + d) % polyCases.length;
|
||
|
if (nextDemoIndex < 0)
|
||
|
nextDemoIndex += polyCases.length;
|
||
|
var demo = polyCases[nextDemoIndex];
|
||
|
caseName = (nextDemoIndex + 1) + '. ' + demo.name;
|
||
|
poly1 = demo.poly1;
|
||
|
poly2 = demo.poly2;
|
||
|
polyBox = { min: [false, false], max: [false, false] };
|
||
|
function calcBox(regions){
|
||
|
for (var r = 0; r < regions.length; r++){
|
||
|
var region = regions[r];
|
||
|
for (var p = 0; p < region.length; p++){
|
||
|
var pt = region[p];
|
||
|
if (polyBox.min[0] === false || pt[0] < polyBox.min[0])
|
||
|
polyBox.min[0] = pt[0];
|
||
|
if (polyBox.min[1] === false || pt[1] < polyBox.min[1])
|
||
|
polyBox.min[1] = pt[1];
|
||
|
if (polyBox.max[0] === false || pt[0] > polyBox.max[0])
|
||
|
polyBox.max[0] = pt[0];
|
||
|
if (polyBox.max[1] === false || pt[1] > polyBox.max[1])
|
||
|
polyBox.max[1] = pt[1];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
calcBox(poly1.regions);
|
||
|
calcBox(poly2.regions);
|
||
|
recalc();
|
||
|
}
|
||
|
|
||
|
function setMode(txt){
|
||
|
mode = txt;
|
||
|
recalc();
|
||
|
}
|
||
|
|
||
|
function colComp(n){
|
||
|
n = Math.floor(n * 256);
|
||
|
if (n > 255)
|
||
|
return 255;
|
||
|
if (n < 0)
|
||
|
return 0;
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
function colToHex(color){
|
||
|
function hex(n){
|
||
|
n = colComp(n).toString(16);
|
||
|
return (n.length <= 1 ? '0' : '') + n;
|
||
|
}
|
||
|
return '#' + hex(color[0]) + hex(color[1]) + hex(color[2]);
|
||
|
}
|
||
|
|
||
|
function vertRadius(region, vert){
|
||
|
if (vert === false)
|
||
|
return 0;
|
||
|
if (hover !== false && hover.region === region && hover.vert === vert)
|
||
|
return 6;
|
||
|
return 3;
|
||
|
}
|
||
|
|
||
|
function drawVerts(poly, offset){
|
||
|
poly.regions.forEach(function(region, region_i){
|
||
|
for (var i = 0; i < region.length; i++){
|
||
|
ctx.beginPath();
|
||
|
ctx.arc(scaleX(region[i][0]), scaleY(region[i][1]) + offset, vertRadius(region, i),
|
||
|
0, Math.PI * 2);
|
||
|
ctx.fill();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
var buildLogMax = 0;
|
||
|
var buildLogTimer = false;
|
||
|
function buildLogNext(delta){
|
||
|
buildLogMax += delta;
|
||
|
if (buildLogMax < 0)
|
||
|
buildLogMax = 0;
|
||
|
if (buildLogMax > clipResult.build_log.length)
|
||
|
buildLogMax = clipResult.build_log.length;
|
||
|
redraw();
|
||
|
return buildLogMax >= clipResult.build_log.length;
|
||
|
}
|
||
|
|
||
|
function buildLogNextWrap(delta){
|
||
|
buildLogStop();
|
||
|
if (buildLogMax <= 0 && delta < 0){
|
||
|
buildLogMax = clipResult.build_log.length;
|
||
|
redraw();
|
||
|
}
|
||
|
else if (buildLogMax >= clipResult.build_log.length && delta > 0){
|
||
|
buildLogMax = 0;
|
||
|
redraw();
|
||
|
}
|
||
|
else
|
||
|
buildLogNext(delta);
|
||
|
}
|
||
|
|
||
|
function buildLogStop(){
|
||
|
if (buildLogTimer === false)
|
||
|
return;
|
||
|
clearInterval(buildLogTimer);
|
||
|
buildLogTimer = false;
|
||
|
document.getElementById('bl_play').innerHTML = 'Play';
|
||
|
}
|
||
|
|
||
|
function buildLogPlay(){
|
||
|
if (buildLogTimer === false){
|
||
|
if (buildLogNext(1))
|
||
|
buildLogMax = -1;
|
||
|
buildLogTimer = setInterval(function(){
|
||
|
if (buildLogNext(1)){
|
||
|
buildLogStop();
|
||
|
buildLogTimer = setInterval(function(){
|
||
|
buildLogNextWrap(1);
|
||
|
clearInterval(buildLogTimer);
|
||
|
}, 100);
|
||
|
}
|
||
|
}, 100);
|
||
|
}
|
||
|
else{
|
||
|
clearInterval(buildLogTimer);
|
||
|
buildLogTimer = false;
|
||
|
}
|
||
|
document.getElementById('bl_play').innerHTML = buildLogTimer === false ? 'Play' : 'Stop';
|
||
|
}
|
||
|
|
||
|
var clipResult;
|
||
|
function recalc(){
|
||
|
buildLogStop();
|
||
|
buildLogMax = 0;
|
||
|
|
||
|
var func = ({
|
||
|
'Intersect' : PolyBool.intersect,
|
||
|
'Union' : PolyBool.union,
|
||
|
'Red - Blue': PolyBool.difference,
|
||
|
'Blue - Red': PolyBool.differenceRev,
|
||
|
'Xor' : PolyBool.xor
|
||
|
})[mode];
|
||
|
var BL = PolyBool.buildLog(true);
|
||
|
clipResult = {
|
||
|
result: func(poly1, poly2),
|
||
|
build_log: BL
|
||
|
};
|
||
|
redraw();
|
||
|
|
||
|
// output GeoJSON
|
||
|
|
||
|
var geojson = PolyBool.polygonToGeoJSON(clipResult.result);
|
||
|
function scalePoly(p){
|
||
|
// we need to scale the result because pixel coordinates are around 500, and that's not
|
||
|
// valid long/lat coordinates... so we just divide everything by 10
|
||
|
// (and out of pure luck this tends to place our polygons over Ethiopia...!)
|
||
|
for (var i = 0; i < p.length; i++){
|
||
|
for (var j = 0; j < p[i].length; j++)
|
||
|
p[i][j] = [p[i][j][0] * 0.1, p[i][j][1] * 0.1];
|
||
|
}
|
||
|
}
|
||
|
// I suppose we could just JSON.stringify(geojson, null, ' '), but that doesn't look so
|
||
|
// pretty (imho), so this is a bit stupid but I format it myself so it looks better :-P
|
||
|
var out = ['{', ' "type": ' + JSON.stringify(geojson.type) + ','];
|
||
|
function outLine(line, space, tail){
|
||
|
var o = space + '[';
|
||
|
for (var i = 0; i < line.length; i++){
|
||
|
o += '[' + line[i] + ']';
|
||
|
if (i < line.length - 1)
|
||
|
o += ', ';
|
||
|
}
|
||
|
out.push(o + ']' + (tail ? '' : ','));
|
||
|
}
|
||
|
if (geojson.type == 'Polygon'){
|
||
|
scalePoly(geojson.coordinates);
|
||
|
out.push(' "coordinates": [');
|
||
|
for (var i = 0; i < geojson.coordinates.length; i++)
|
||
|
outLine(geojson.coordinates[i], ' ', i === geojson.coordinates.length - 1);
|
||
|
out.push(' ]');
|
||
|
}
|
||
|
else{
|
||
|
for (var i = 0; i < geojson.coordinates.length; i++)
|
||
|
scalePoly(geojson.coordinates[i]);
|
||
|
out.push(' "coordinates": [[');
|
||
|
for (var i = 0; i < geojson.coordinates.length; i++){
|
||
|
for (var j = 0; j < geojson.coordinates[i].length; j++)
|
||
|
outLine(geojson.coordinates[i][j], ' ', j === geojson.coordinates[i].length - 1);
|
||
|
if (i < geojson.coordinates.length - 1)
|
||
|
out.push(' ], [');
|
||
|
}
|
||
|
out.push(' ]]');
|
||
|
}
|
||
|
out.push('}', '');
|
||
|
document.getElementById('geojson').value = out.join('\n');
|
||
|
}
|
||
|
|
||
|
function scaleX(x){
|
||
|
if (!scaleToFit)
|
||
|
return x;
|
||
|
return (x - polyBox.min[0]) * 650 / (polyBox.max[0] - polyBox.min[0]) + 25;
|
||
|
}
|
||
|
|
||
|
function scaleY(y){
|
||
|
if (!scaleToFit)
|
||
|
return y;
|
||
|
return (y - polyBox.min[1]) * 200 / (polyBox.max[1] - polyBox.min[1]) + 25;
|
||
|
}
|
||
|
|
||
|
function unscaleX(x){
|
||
|
if (!scaleToFit)
|
||
|
return x;
|
||
|
return (x - 25) * (polyBox.max[0] - polyBox.min[0]) / 650 + polyBox.min[0];
|
||
|
}
|
||
|
|
||
|
function unscaleY(y){
|
||
|
if (!scaleToFit)
|
||
|
return y;
|
||
|
return (y - 25) * (polyBox.max[1] - polyBox.min[1]) / 200 + polyBox.min[1];
|
||
|
}
|
||
|
|
||
|
function drawRegions(regions, offset){
|
||
|
regions.forEach(function(region, i){
|
||
|
if (region.length <= 0)
|
||
|
return;
|
||
|
ctx.moveTo(scaleX(region[0][0]), scaleY(region[0][1]) + offset);
|
||
|
for (var i = 1; i < region.length; i++)
|
||
|
ctx.lineTo(scaleX(region[i][0]), scaleY(region[i][1]) + offset);
|
||
|
ctx.closePath();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function polyStroke(result, offset){
|
||
|
ctx.beginPath();
|
||
|
drawRegions(result.regions, offset);
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
|
||
|
function polyFill(result, rect1, rect2, offset){
|
||
|
ctx.beginPath();
|
||
|
if (result.inverted){
|
||
|
ctx.moveTo(rect1[0], rect1[1] + offset);
|
||
|
ctx.lineTo(rect1[0], rect2[1] + offset);
|
||
|
ctx.lineTo(rect2[0], rect2[1] + offset);
|
||
|
ctx.lineTo(rect2[0], rect1[1] + offset);
|
||
|
ctx.closePath();
|
||
|
}
|
||
|
drawRegions(result.regions, offset);
|
||
|
ctx.fill('evenodd');
|
||
|
}
|
||
|
|
||
|
function redraw(){
|
||
|
ctx.fillStyle = '#fff';
|
||
|
ctx.fillRect(0, 0, cnvWidth, cnvHeight);
|
||
|
ctx.lineWidth = 2;
|
||
|
|
||
|
var labels = [];
|
||
|
|
||
|
// this is quite the mess... sorry...
|
||
|
var bl_oldsegs = [];
|
||
|
var bl_segs = [];
|
||
|
var bl_segid = {};
|
||
|
var bl_vert = 0;
|
||
|
var bl_last_check = false;
|
||
|
var bl_last_check_i1 = false;
|
||
|
var bl_last_check_i2 = false;
|
||
|
var bl_last_div_seg = false;
|
||
|
var bl_last_pop_seg = false;
|
||
|
var bl_last_pop_seg_i = false;
|
||
|
var bl_last_status = false;
|
||
|
var bl_last_temp_status = false;
|
||
|
var bl_last_temp_status_i = false;
|
||
|
var bl_last_chop = false;
|
||
|
var bl_last_seg_keep = false;
|
||
|
var bl_last_seg_keep_match = false;
|
||
|
var bl_last_done = false;
|
||
|
var bl_finish = false;
|
||
|
var bl_status = [];
|
||
|
var bl_chains = [];
|
||
|
var bl_chainids = [];
|
||
|
var bl_nextchainid = 0;
|
||
|
var bl_oldchains = [];
|
||
|
var bl_oldchainids = [];
|
||
|
var bl_phase = 0;
|
||
|
var bl_selected = false;
|
||
|
clipResult.build_log.forEach(function(blw, i){
|
||
|
if (i >= buildLogMax)
|
||
|
return;
|
||
|
var bl = blw.data;
|
||
|
bl_last_check = false;
|
||
|
bl_last_div_seg = false;
|
||
|
bl_last_pop_seg = false;
|
||
|
bl_last_status = false;
|
||
|
bl_last_chop = false;
|
||
|
bl_last_done = false;
|
||
|
switch (blw.type){
|
||
|
case 'vert':
|
||
|
bl_vert = bl.x;
|
||
|
break;
|
||
|
case 'new_seg':
|
||
|
for (var i = 0; i < bl_segs.length; i++){
|
||
|
if (bl_segs[i].id === bl.seg.id){
|
||
|
bl_segs.splice(i, 1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
for (var i = 0; i < bl_oldsegs.length; i++){
|
||
|
if (bl_oldsegs[i].id === bl.seg.id){
|
||
|
bl_oldsegs.splice(i, 1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
bl_segs.push(bl.seg);
|
||
|
bl_segid[bl.seg.id] = {
|
||
|
phase: bl_phase,
|
||
|
primary: bl.primary
|
||
|
};
|
||
|
break;
|
||
|
case 'rem_seg':
|
||
|
for (var i = 0; i < bl_segs.length; i++){
|
||
|
if (bl_segs[i].id === bl.seg.id){
|
||
|
bl_segs.splice(i, 1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
for (var i = 0; i < bl_oldsegs.length; i++){
|
||
|
if (bl_oldsegs[i].id === bl.seg.id){
|
||
|
bl_oldsegs.splice(i, 1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 'check':
|
||
|
bl_last_check = bl;
|
||
|
bl_last_check_i1 = false;
|
||
|
bl_last_check_i2 = false;
|
||
|
for (var i = 0; i < bl_status.length; i++){
|
||
|
if (bl_status[i].id === bl.seg1.id)
|
||
|
bl_last_check_i1 = i;
|
||
|
if (bl_status[i].id === bl.seg2.id)
|
||
|
bl_last_check_i2 = i;
|
||
|
}
|
||
|
break;
|
||
|
case 'pop_seg':
|
||
|
bl_last_pop_seg = bl;
|
||
|
for (var i = 0; i < bl_segs.length; i++){
|
||
|
if (bl_segs[i].id === bl.seg.id){
|
||
|
var r = bl_segs.splice(i, 1)[0];
|
||
|
bl_oldsegs.push(r);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
for (var i = 0; i < bl_status.length; i++){
|
||
|
if (bl_status[i].id === bl.seg.id){
|
||
|
bl_last_pop_seg_i = i;
|
||
|
bl_status.splice(i, 1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 'div_seg':
|
||
|
bl_last_div_seg = bl;
|
||
|
break;
|
||
|
case 'temp_status':
|
||
|
bl_last_temp_status = bl;
|
||
|
if (bl.above === false)
|
||
|
bl_last_temp_status_i = -0.5;
|
||
|
else{
|
||
|
for (var i = 0; i < bl_status.length; i++){
|
||
|
if (bl_status[i].id === bl.above.id){
|
||
|
bl_last_temp_status_i = i + 0.5;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 'status':
|
||
|
bl_last_temp_status = false;
|
||
|
bl_last_status = bl.seg.id;
|
||
|
for (var i = 0; i < bl_segs.length; i++){
|
||
|
if (bl_segs[i].id === bl.seg.id){
|
||
|
bl_segs[i] = bl.seg;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (bl.above === false)
|
||
|
bl_status.unshift(bl.seg);
|
||
|
else{
|
||
|
for (var i = 0; i < bl_status.length; i++){
|
||
|
if (bl_status[i].id === bl.above.id){
|
||
|
bl_status.splice(i + 1, 0, bl.seg);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 'rewind':
|
||
|
bl_last_temp_status = false;
|
||
|
for (var i = 0; i < bl_segs.length; i++){
|
||
|
if (bl_segs[i].id === bl.seg.id){
|
||
|
bl_segs.splice(i, 1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 'chop':
|
||
|
bl_last_chop = bl;
|
||
|
for (var i = 0; i < bl_segs.length; i++){
|
||
|
if (bl_segs[i].id === bl.seg.id){
|
||
|
bl_segs[i] = JSON.parse(JSON.stringify(bl_segs[i]));
|
||
|
bl_segs[i].end = bl.pt;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 'seg_update':
|
||
|
function chk_seg(seg){
|
||
|
if (seg.id !== bl.seg.id)
|
||
|
return seg;
|
||
|
return bl.seg;
|
||
|
}
|
||
|
for (var i = 0; i < bl_segs.length; i++)
|
||
|
bl_segs[i] = chk_seg(bl_segs[i]);
|
||
|
for (var i = 0; i < bl_oldsegs.length; i++)
|
||
|
bl_oldsegs[i] = chk_seg(bl_oldsegs[i]);
|
||
|
break;
|
||
|
case 'log':
|
||
|
if (i === buildLogMax - 1)
|
||
|
console.log(bl.txt);
|
||
|
break;
|
||
|
case 'reset':
|
||
|
bl_segs = [];
|
||
|
bl_oldsegs = [];
|
||
|
bl_segid = {};
|
||
|
bl_status = [];
|
||
|
bl_last_temp_status = false;
|
||
|
break;
|
||
|
case 'selected':
|
||
|
bl_selected = bl.segs;
|
||
|
bl_phase++;
|
||
|
break;
|
||
|
case 'chain_start':
|
||
|
bl_last_seg_keep = bl;
|
||
|
bl_last_seg_keep_match = false;
|
||
|
break;
|
||
|
case 'chain_new':
|
||
|
bl_chains.push([ bl.pt1, bl.pt2 ]);
|
||
|
bl_chainids.push(bl_nextchainid++);
|
||
|
bl_last_seg_keep = false;
|
||
|
break;
|
||
|
case 'chain_rev':
|
||
|
bl_chains[bl.index].reverse();
|
||
|
break;
|
||
|
case 'chain_add_head':
|
||
|
bl_chains[bl.index].unshift(bl.pt);
|
||
|
bl_last_seg_keep = false;
|
||
|
break;
|
||
|
case 'chain_add_tail':
|
||
|
bl_chains[bl.index].push(bl.pt);
|
||
|
bl_last_seg_keep = false;
|
||
|
break;
|
||
|
case 'chain_rem_head':
|
||
|
bl_chains[bl.index].shift();
|
||
|
break;
|
||
|
case 'chain_rem_tail':
|
||
|
bl_chains[bl.index].pop();
|
||
|
break;
|
||
|
case 'chain_match':
|
||
|
bl_last_seg_keep_match = bl.index;
|
||
|
break;
|
||
|
case 'chain_con':
|
||
|
bl_last_seg_keep_match = bl.index1;
|
||
|
break;
|
||
|
case 'chain_join':
|
||
|
bl_chains[bl.index1] = bl_chains[bl.index1].concat(bl_chains[bl.index2]);
|
||
|
bl_chains.splice(bl.index2, 1);
|
||
|
bl_chainids.splice(bl.index2, 1);
|
||
|
bl_last_seg_keep = false;
|
||
|
break;
|
||
|
case 'chain_close':
|
||
|
bl_oldchains.push(bl_chains.splice(bl.index, 1)[0]);
|
||
|
bl_oldchainids.push(bl_chainids.splice(bl.index, 1)[0]);
|
||
|
bl_last_seg_keep = false;
|
||
|
break;
|
||
|
case 'done':
|
||
|
bl_last_done = true;
|
||
|
bl_vert = false;
|
||
|
bl_phase++;
|
||
|
if (bl_phase === 5)
|
||
|
bl_finish = true;
|
||
|
break;
|
||
|
default: console.log(blw.type, bl);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
function drawseg(seg, fade, size){
|
||
|
var poly = bl_segid[seg.id];
|
||
|
var poly1 = poly.phase === 0 || (((poly.phase === 2 && !bl_last_done) || bl_phase === 3) && poly.primary);
|
||
|
if (!poly1 && bl_phase === 0)
|
||
|
return;
|
||
|
if (poly1 && bl_phase === 1 && !bl_last_done)
|
||
|
return;
|
||
|
if (bl_phase === 2 && bl_last_done && poly1)
|
||
|
return;
|
||
|
var ang = Math.atan2(seg.end[1] - seg.start[1], seg.end[0] - seg.start[0]);
|
||
|
ctx.beginPath();
|
||
|
ctx.moveTo(
|
||
|
seg.start[0],
|
||
|
seg.start[1]
|
||
|
);
|
||
|
ctx.lineTo(
|
||
|
seg.end[0],
|
||
|
seg.end[1]
|
||
|
);
|
||
|
ctx.strokeStyle = poly1 ? (fade ? '#faa' : '#f00') : (fade ? '#aaf' : '#00f');
|
||
|
ctx.lineWidth = size;
|
||
|
ctx.stroke();
|
||
|
function drawfill(ang, fill, mine){
|
||
|
var poly1 =
|
||
|
poly.phase === 0 ? mine :
|
||
|
poly.phase === 1 ? !mine :
|
||
|
mine === poly.primary;
|
||
|
var dist = 6;
|
||
|
ctx.beginPath();
|
||
|
ctx.arc(
|
||
|
dist * Math.cos(ang) + (seg.start[0] + seg.end[0]) / 2,
|
||
|
dist * Math.sin(ang) + (seg.start[1] + seg.end[1]) / 2,
|
||
|
fill === null ? 1 : 3,
|
||
|
0, Math.PI * 2
|
||
|
);
|
||
|
ctx.fillStyle = (fill === true || fill === null) ? (poly1 ? '#f00' : '#00f') : '#fff';
|
||
|
ctx.fill();
|
||
|
ctx.strokeStyle = (fill === true) ? '#000' : (poly1 ? '#f00' : '#00f');
|
||
|
ctx.lineWidth = 1;
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
var d = (bl_phase <= 1 || (bl_phase === 2 && bl_last_done)) ? 0 : 0.75;
|
||
|
drawfill(ang + Math.PI * 0.5 - d, seg.myFill.above, true);
|
||
|
drawfill(ang - Math.PI * 0.5 + d, seg.myFill.below, true);
|
||
|
if (bl_phase > 1 && !(bl_phase === 2 && bl_last_done)){
|
||
|
drawfill(ang + Math.PI * 0.5 + d, seg.otherFill ? seg.otherFill.above : null, false);
|
||
|
drawfill(ang - Math.PI * 0.5 - d, seg.otherFill ? seg.otherFill.below : null, false);
|
||
|
}
|
||
|
}
|
||
|
if (bl_phase < 4){
|
||
|
bl_oldsegs.forEach(function(seg){
|
||
|
drawseg(seg, true, 1);
|
||
|
});
|
||
|
bl_segs.forEach(function(seg){
|
||
|
drawseg(seg, false, 2);
|
||
|
labels.push({
|
||
|
txt: seg.id,
|
||
|
x: (seg.start[0] + seg.end[0]) / 2,
|
||
|
y: (seg.start[1] + seg.end[1]) / 2
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
var stw = 40;
|
||
|
var sth = 20;
|
||
|
function stpos(i){
|
||
|
return [ stw * 1.5 + 20, cnvHeight / 2 - i * sth - 30 - sth / 2 ];
|
||
|
}
|
||
|
if (bl_phase < 4 && bl_last_check){
|
||
|
ctx.beginPath();
|
||
|
ctx.moveTo(bl_last_check.seg1.start[0], bl_last_check.seg1.start[1]);
|
||
|
ctx.lineTo(bl_last_check.seg1.end[0], bl_last_check.seg1.end[1]);
|
||
|
ctx.strokeStyle = '#0f0';
|
||
|
ctx.lineWidth = 2;
|
||
|
ctx.stroke();
|
||
|
ctx.beginPath();
|
||
|
ctx.moveTo(bl_last_check.seg2.start[0], bl_last_check.seg2.start[1]);
|
||
|
ctx.lineTo(bl_last_check.seg2.end[0], bl_last_check.seg2.end[1]);
|
||
|
ctx.strokeStyle = '#0f0';
|
||
|
ctx.lineWidth = 2;
|
||
|
ctx.stroke();
|
||
|
if (bl_last_check_i1 === false || bl_last_check_i2 === false){
|
||
|
ctx.beginPath();
|
||
|
var c1 = stpos(bl_last_temp_status_i);
|
||
|
c1[0] += stw + 10;
|
||
|
var c2 = stpos(bl_last_check_i1 === false ? bl_last_check_i2 : bl_last_check_i1);
|
||
|
ctx.moveTo(c1[0], c1[1]);
|
||
|
ctx.lineTo(c2[0], c2[1]);
|
||
|
ctx.lineWidth = 2;
|
||
|
ctx.strokeStyle = '#0f0';
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
else{
|
||
|
ctx.beginPath();
|
||
|
var c1 = stpos(bl_last_check_i1);
|
||
|
var c2 = stpos(bl_last_check_i2);
|
||
|
ctx.arc(c1[0] + stw / 2, (c2[1] + c1[1]) / 2,
|
||
|
Math.abs(c2[1] - c1[1]) / 2, 0, Math.PI * 2);
|
||
|
ctx.lineWidth = 2;
|
||
|
ctx.strokeStyle = '#0f0';
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
}
|
||
|
if (bl_phase < 4 && bl_last_div_seg){
|
||
|
ctx.beginPath();
|
||
|
ctx.arc(bl_last_div_seg.pt[0], bl_last_div_seg.pt[1], 6, 0, Math.PI * 2);
|
||
|
ctx.fillStyle = '#aa0';
|
||
|
ctx.fill();
|
||
|
ctx.strokeStyle = '#000';
|
||
|
ctx.lineWidth = 1;
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
if (bl_phase < 4){
|
||
|
for (var i = 0; i < bl_status.length; i++){
|
||
|
var c = stpos(i);
|
||
|
labels.push({
|
||
|
txt: bl_status[i].id,
|
||
|
x: c[0],
|
||
|
y: c[1]
|
||
|
});
|
||
|
ctx.beginPath();
|
||
|
ctx.rect(c[0] - stw / 2, c[1] - sth / 2, stw, sth);
|
||
|
ctx.fillStyle = bl_last_status === bl_status[i].id ? '#ff6' : '#fff';
|
||
|
ctx.fill();
|
||
|
ctx.strokeStyle = bl_last_status === bl_status[i].id ? '#444' : '#aaa';
|
||
|
ctx.lineWidth = 1;
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
}
|
||
|
if (bl_phase < 4 && bl_last_pop_seg){
|
||
|
var c = stpos(bl_last_pop_seg_i);
|
||
|
c[0] -= stw + 10;
|
||
|
labels.push({
|
||
|
txt: bl_last_pop_seg.seg.id,
|
||
|
x: c[0],
|
||
|
y: c[1]
|
||
|
});
|
||
|
ctx.beginPath();
|
||
|
ctx.rect(c[0] - stw / 2, c[1] - sth / 2, stw, sth);
|
||
|
ctx.fillStyle = '#eee';
|
||
|
ctx.fill();
|
||
|
ctx.strokeStyle = '#ddd'
|
||
|
ctx.lineWidth = 1;
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
if (bl_phase < 4 && bl_last_temp_status){
|
||
|
var c = stpos(bl_last_temp_status_i);
|
||
|
c[0] += stw + 10;
|
||
|
labels.push({
|
||
|
txt: bl_last_temp_status.seg.id,
|
||
|
x: c[0],
|
||
|
y: c[1]
|
||
|
});
|
||
|
ctx.beginPath();
|
||
|
ctx.rect(c[0] - stw / 2, c[1] - sth / 2, stw, sth);
|
||
|
ctx.fillStyle = '#dfd';
|
||
|
ctx.fill();
|
||
|
ctx.strokeStyle = '#cfc'
|
||
|
ctx.lineWidth = 1;
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
if (bl_phase < 4 && bl_last_chop){
|
||
|
ctx.beginPath();
|
||
|
ctx.moveTo(bl_last_chop.seg.start[0], bl_last_chop.seg.start[1]);
|
||
|
ctx.lineTo(bl_last_chop.pt[0], bl_last_chop.pt[1]);
|
||
|
ctx.lineWidth = 3;
|
||
|
ctx.strokeStyle = '#f00';
|
||
|
ctx.stroke();
|
||
|
ctx.beginPath();
|
||
|
ctx.moveTo(bl_last_chop.pt[0], bl_last_chop.pt[1]);
|
||
|
ctx.lineTo(bl_last_chop.seg.end[0], bl_last_chop.seg.end[1]);
|
||
|
ctx.lineWidth = 2;
|
||
|
ctx.strokeStyle = '#ddd';
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
if (bl_phase < 4 && bl_vert !== false){
|
||
|
ctx.beginPath();
|
||
|
ctx.moveTo(bl_vert, cnvHeight / 2);
|
||
|
ctx.lineTo(bl_vert, 0);
|
||
|
ctx.strokeStyle = 'rgba(0, 0, 0, 0.2)';
|
||
|
ctx.lineWidth = 1;
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
if (bl_phase === 4){
|
||
|
bl_selected.forEach(function(seg){
|
||
|
ctx.beginPath();
|
||
|
ctx.moveTo(seg.start[0], seg.start[1]);
|
||
|
ctx.lineTo(seg.end[0], seg.end[1]);
|
||
|
ctx.strokeStyle = bl_last_done ? '#070' : '#e2e2e2';
|
||
|
ctx.lineWidth = 1;
|
||
|
ctx.stroke();
|
||
|
ctx.beginPath();
|
||
|
ctx.arc(seg.start[0], seg.start[1], 3, 0, Math.PI * 2);
|
||
|
ctx.fillStyle = bl_last_done ? '#070' : '#e2e2e2';
|
||
|
ctx.fill();
|
||
|
ctx.beginPath();
|
||
|
ctx.arc(seg.end[0], seg.end[1], 3, 0, Math.PI * 2);
|
||
|
ctx.fillStyle = bl_last_done ? '#070' : '#e2e2e2';
|
||
|
ctx.fill();
|
||
|
});
|
||
|
}
|
||
|
if (bl_phase === 4 || (bl_phase === 5 && bl_last_done)){
|
||
|
ctx.beginPath();
|
||
|
bl_oldchains.forEach(function(ch){
|
||
|
for (var i = 0; i < ch.length; i++){
|
||
|
var pt = ch[i];
|
||
|
if (i === 0)
|
||
|
ctx.moveTo(pt[0], pt[1]);
|
||
|
else
|
||
|
ctx.lineTo(pt[0], pt[1]);
|
||
|
}
|
||
|
ctx.lineTo(ch[0][0], ch[0][1]);
|
||
|
});
|
||
|
ctx.fillStyle = 'rgba(0, 255, 0, 0.2)';
|
||
|
ctx.fill('evenodd');
|
||
|
ctx.strokeStyle = '#7f7';
|
||
|
ctx.lineWidth = 1;
|
||
|
ctx.stroke();
|
||
|
bl_oldchains.forEach(function(ch, chi){
|
||
|
var totx = 0;
|
||
|
var toty = 0;
|
||
|
for (var i = 0; i < ch.length; i++){
|
||
|
var pt = ch[i];
|
||
|
totx += pt[0];
|
||
|
toty += pt[1];
|
||
|
ctx.beginPath();
|
||
|
ctx.arc(pt[0], pt[1], 3, 0, Math.PI * 2);
|
||
|
ctx.fillStyle = '#7f7';
|
||
|
ctx.fill();
|
||
|
}
|
||
|
labels.push({
|
||
|
txt: bl_oldchainids[chi],
|
||
|
x: totx / ch.length,
|
||
|
y: toty / ch.length
|
||
|
});
|
||
|
});
|
||
|
|
||
|
function drawarrow(pt1, pt2, rad){
|
||
|
var arrowLen = 8;
|
||
|
var arrowAng = Math.PI - 0.45;
|
||
|
ctx.beginPath();
|
||
|
ctx.moveTo(pt1[0], pt1[1]);
|
||
|
ctx.lineTo(pt2[0], pt2[1]);
|
||
|
|
||
|
var ang = Math.atan2(pt2[1] - pt1[1], pt2[0] - pt1[0]);
|
||
|
var ax = pt2[0] - Math.cos(ang) * rad;
|
||
|
var ay = pt2[1] - Math.sin(ang) * rad;
|
||
|
ctx.moveTo(ax, ay);
|
||
|
ctx.lineTo(
|
||
|
ax + Math.cos(ang + arrowAng) * arrowLen,
|
||
|
ay + Math.sin(ang + arrowAng) * arrowLen
|
||
|
);
|
||
|
ctx.moveTo(ax, ay);
|
||
|
ctx.lineTo(
|
||
|
ax + Math.cos(ang - arrowAng) * arrowLen,
|
||
|
ay + Math.sin(ang - arrowAng) * arrowLen
|
||
|
);
|
||
|
}
|
||
|
|
||
|
bl_chains.forEach(function(ch, chi){
|
||
|
var rad = 3;
|
||
|
for (var i = 0; i < ch.length - 1; i++){
|
||
|
var pt1 = ch[i];
|
||
|
var pt2 = ch[i + 1];
|
||
|
drawarrow(pt1, pt2, rad);
|
||
|
ctx.lineWidth = 1;
|
||
|
ctx.strokeStyle = '#070';
|
||
|
ctx.stroke();
|
||
|
|
||
|
ctx.beginPath();
|
||
|
ctx.arc(pt1[0], pt1[1], rad, 0, Math.PI * 2);
|
||
|
ctx.fillStyle = '#070';
|
||
|
ctx.fill();
|
||
|
ctx.beginPath();
|
||
|
ctx.arc(pt2[0], pt2[1], rad, 0, Math.PI * 2);
|
||
|
ctx.fillStyle = '#070';
|
||
|
ctx.fill();
|
||
|
|
||
|
labels.push({
|
||
|
txt: bl_chainids[chi],
|
||
|
x: (pt1[0] + pt2[0]) / 2,
|
||
|
y: (pt1[1] + pt2[1]) / 2
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
if (bl_last_seg_keep){
|
||
|
var pt1 = bl_last_seg_keep.seg.start;
|
||
|
var pt2 = bl_last_seg_keep.seg.end;
|
||
|
ctx.beginPath();
|
||
|
drawarrow(pt1, pt2, 3.5);
|
||
|
ctx.lineWidth = 2;
|
||
|
ctx.strokeStyle = '#0f0';
|
||
|
ctx.stroke();
|
||
|
|
||
|
ctx.beginPath();
|
||
|
ctx.arc(pt1[0], pt1[1], 3.5, 0, Math.PI * 2);
|
||
|
ctx.fillStyle = '#0f0';
|
||
|
ctx.fill();
|
||
|
ctx.beginPath();
|
||
|
ctx.arc(pt2[0], pt2[1], 3.5, 0, Math.PI * 2);
|
||
|
ctx.fillStyle = '#0f0';
|
||
|
ctx.fill();
|
||
|
|
||
|
if (bl_last_seg_keep_match !== false){
|
||
|
labels.push({
|
||
|
txt: bl_chainids[bl_last_seg_keep_match],
|
||
|
x: (pt1[0] + pt2[0]) / 2,
|
||
|
y: (pt1[1] + pt2[1]) / 2
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// move labels around so that they don't overlap
|
||
|
for (var i = 1; i < labels.length; i++){
|
||
|
if (labels[i].txt === '')
|
||
|
continue;
|
||
|
for (var j = 0; j < i; j++){
|
||
|
if (labels[j].txt === '')
|
||
|
continue;
|
||
|
var dx = labels[i].x - labels[j].x;
|
||
|
var dy = labels[i].y - labels[j].y;
|
||
|
var dist = Math.sqrt(dx * dx + dy * dy);
|
||
|
if (dist < 5){
|
||
|
labels[j].txt += ', ' + labels[i].txt;
|
||
|
labels[i].txt = '';
|
||
|
}
|
||
|
else if (dist < 16){
|
||
|
labels[j].y -= 4;
|
||
|
labels[i].y += 12;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ctx.fillStyle = 'rgba(255, 0, 0, 0.2)';
|
||
|
polyFill(poly1, [0, 0], [cnvWidth, cnvHeight / 2], cnvHeight / 2);
|
||
|
ctx.fillStyle = 'rgba(0, 0, 255, 0.2)';
|
||
|
polyFill(poly2, [0, 0], [cnvWidth, cnvHeight / 2], cnvHeight / 2);
|
||
|
|
||
|
ctx.lineWidth = 1;
|
||
|
ctx.strokeStyle = '#f00';
|
||
|
polyStroke(poly1, cnvHeight / 2);
|
||
|
ctx.lineWidth = 1;
|
||
|
ctx.strokeStyle = '#00f';
|
||
|
polyStroke(poly2, cnvHeight / 2);
|
||
|
|
||
|
ctx.fillStyle = '#f00';
|
||
|
drawVerts(poly1, cnvHeight / 2);
|
||
|
ctx.fillStyle = '#00f';
|
||
|
drawVerts(poly2, cnvHeight / 2);
|
||
|
|
||
|
// draw labels
|
||
|
//*
|
||
|
ctx.save();
|
||
|
ctx.setTransform(rscale, 0, 0, rscale, 0, 0);
|
||
|
ctx.font = '10px sans-serif';
|
||
|
ctx.textAlign = 'center';
|
||
|
ctx.textBaseline = 'middle';
|
||
|
labels.forEach(function(lbl){
|
||
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
|
||
|
ctx.fillText(lbl.txt, lbl.x + 1, cnvHeight - lbl.y + 1);
|
||
|
ctx.fillText(lbl.txt, lbl.x + 1, cnvHeight - lbl.y + 0);
|
||
|
ctx.fillText(lbl.txt, lbl.x + 1, cnvHeight - lbl.y + -1);
|
||
|
ctx.fillText(lbl.txt, lbl.x + 0, cnvHeight - lbl.y + 1);
|
||
|
ctx.fillText(lbl.txt, lbl.x + 0, cnvHeight - lbl.y + -1);
|
||
|
ctx.fillText(lbl.txt, lbl.x + -1, cnvHeight - lbl.y + 1);
|
||
|
ctx.fillText(lbl.txt, lbl.x + -1, cnvHeight - lbl.y + 0);
|
||
|
ctx.fillText(lbl.txt, lbl.x + -1, cnvHeight - lbl.y + -1);
|
||
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
||
|
ctx.fillText(lbl.txt, lbl.x, cnvHeight - lbl.y);
|
||
|
});
|
||
|
ctx.restore(); // */
|
||
|
|
||
|
if (buildLogMax === 0){
|
||
|
ctx.fillStyle = 'rgba(0, 255, 0, 0.7)';
|
||
|
ctx.strokeStyle = '#070';
|
||
|
ctx.lineWidth = 1;
|
||
|
polyFill(clipResult.result, [0, 0], [cnvWidth, cnvHeight / 2], 0);
|
||
|
polyStroke(clipResult.result, 0);
|
||
|
ctx.fillStyle = '#070';
|
||
|
drawVerts(clipResult.result, 0);
|
||
|
}
|
||
|
|
||
|
ctx.save();
|
||
|
ctx.setTransform(rscale, 0, 0, rscale, 0, 0);
|
||
|
ctx.font = '13px sans-serif';
|
||
|
ctx.fillStyle = '#000';
|
||
|
ctx.fillText(mode, 4, cnvHeight / 2 + 16);
|
||
|
ctx.fillText(caseName, 4, 16);
|
||
|
ctx.restore();
|
||
|
|
||
|
var phase = buildLogMax === 0 ? 'Result' :
|
||
|
bl_phase === 0 ? 'Phase 1. Self-Intersect Red' :
|
||
|
bl_phase === 1 ? (bl_last_done ? 'Phase 1 Result' : 'Phase 2. Self-Intersect Blue') :
|
||
|
bl_phase === 2 ? (bl_last_done ? 'Phase 2 Result' : 'Phase 3. Red vs. Blue') :
|
||
|
bl_phase === 3 ? 'Phase 3 Result' :
|
||
|
bl_phase === 4 ? (bl_last_done ? 'Segment Selection' : 'Phase 4. Segment Chaining') :
|
||
|
'Phase 4 Result';
|
||
|
ctx.save();
|
||
|
ctx.setTransform(rscale, 0, 0, rscale, 0, 0);
|
||
|
ctx.font = '13px sans-serif';
|
||
|
ctx.textAlign = 'right';
|
||
|
ctx.fillStyle = '#000';
|
||
|
ctx.fillText(phase, cnvWidth - 4, cnvHeight / 2 + 16);
|
||
|
ctx.restore();
|
||
|
|
||
|
ctx.fillStyle = '#999';
|
||
|
ctx.fillRect(0, 0, cnvWidth * buildLogMax / clipResult.build_log.length, 3);
|
||
|
|
||
|
ctx.beginPath();
|
||
|
for (var x = 0; x < cnvWidth; x += 10){
|
||
|
ctx.moveTo(x, cnvHeight / 2);
|
||
|
ctx.lineTo(x + 5, cnvHeight / 2);
|
||
|
}
|
||
|
ctx.strokeStyle = 'rgba(0, 0, 0, 0.3)';
|
||
|
ctx.lineWidth = 1;
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
|
||
|
function init(){
|
||
|
cnv = document.getElementById('cnv');
|
||
|
ctx = cnv.getContext('2d');
|
||
|
cnv.style.width = cnv.width / wscale + 'px';
|
||
|
cnv.style.height = cnv.height / wscale + 'px';
|
||
|
// make y go up and scale by 2 (for high DPI screens)
|
||
|
ctx.transform(rscale, 0, 0, -rscale, 0, cnv.height);
|
||
|
cnvWidth = cnv.width / rscale;
|
||
|
cnvHeight = cnv.height / rscale;
|
||
|
|
||
|
nextDemo(1);
|
||
|
|
||
|
function mousePos(e){
|
||
|
var rect = cnv.getBoundingClientRect();
|
||
|
return [
|
||
|
e.clientX - rect.left,
|
||
|
cnvHeight - e.clientY + rect.top
|
||
|
];
|
||
|
}
|
||
|
|
||
|
function closestPoint(poly, x, y){
|
||
|
x = unscaleX(x);
|
||
|
y = unscaleY(y);
|
||
|
var reg = false;
|
||
|
var vert = false;
|
||
|
var len2 = false;
|
||
|
poly.regions.forEach(function(region){
|
||
|
for (var i = 0; i < region.length; i++){
|
||
|
var dx = scaleX(region[i][0] - x);
|
||
|
var dy = scaleY(region[i][1] - y);
|
||
|
var thisLen2 = dx * dx + dy * dy;
|
||
|
if (len2 === false || thisLen2 < len2){
|
||
|
reg = region;
|
||
|
vert = i;
|
||
|
len2 = thisLen2;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
return {
|
||
|
region: reg,
|
||
|
vert: vert,
|
||
|
len: Math.sqrt(len2)
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function mouseTrackHover(mp){
|
||
|
// look for the closest node
|
||
|
var p1 = closestPoint(poly1, mp[0], mp[1] - cnvHeight / 2);
|
||
|
var p2 = closestPoint(poly2, mp[0], mp[1] - cnvHeight / 2);
|
||
|
if (p2.len < p1.len)
|
||
|
p1 = p2;
|
||
|
if (p1.len > 10){
|
||
|
if (hover !== false){
|
||
|
hover = false;
|
||
|
redraw();
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
if (hover === false || hover.region !== p1.region || hover.vert !== p1.vert){
|
||
|
hover = p1;
|
||
|
redraw();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var dragging = false;
|
||
|
|
||
|
cnv.addEventListener('mousemove', function(e){
|
||
|
var mp = mousePos(e);
|
||
|
if (dragging){
|
||
|
var dx = mp[0] - dragging[0];
|
||
|
var dy = mp[1] - dragging[1];
|
||
|
var pt = hover.region[hover.vert];
|
||
|
if (pt[1] + dy < 0)
|
||
|
dy = -pt[1];
|
||
|
if (document.getElementById('snap').checked){
|
||
|
var tx = pt[0] + dx;
|
||
|
var ty = pt[1] + dy;
|
||
|
tx = Math.round(tx / 10) * 10;
|
||
|
ty = Math.round(ty / 10) * 10;
|
||
|
dx = tx - pt[0];
|
||
|
dy = ty - pt[1];
|
||
|
}
|
||
|
if (dx !== 0 || dy !== 0){
|
||
|
dragging = [dragging[0] + dx, dragging[1] + dy];
|
||
|
pt[0] = unscaleX(scaleX(pt[0]) + dx);
|
||
|
pt[1] = unscaleY(scaleY(pt[1]) + dy);
|
||
|
recalc();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
mouseTrackHover(mp);
|
||
|
|
||
|
if (window.debugMousePos === true){
|
||
|
ctx.save();
|
||
|
var mx = mp[0], my = mp[1] - cnvHeight / 2;
|
||
|
if (hover !== false){
|
||
|
mx = hover.region[hover.vert][0];
|
||
|
my = hover.region[hover.vert][1];
|
||
|
}
|
||
|
ctx.setTransform(rscale, 0, 0, rscale, 0, 0);
|
||
|
ctx.fillStyle = '#fff';
|
||
|
ctx.fillRect(0, 0, 100, 20);
|
||
|
ctx.fillStyle = '#000';
|
||
|
ctx.textAlign = 'left'
|
||
|
ctx.textBaseline = 'top';
|
||
|
ctx.fillText('(' + mx + ', ' + my + ')', 0, 0);
|
||
|
ctx.restore();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
cnv.addEventListener('mouseup', function(e){
|
||
|
var mp = mousePos(e);
|
||
|
if (dragging){
|
||
|
dragging = false;
|
||
|
mouseTrackHover(mp);
|
||
|
redraw();
|
||
|
}
|
||
|
else
|
||
|
mouseTrackHover(mp);
|
||
|
});
|
||
|
|
||
|
cnv.addEventListener('mouseleave', function(e){
|
||
|
if (dragging){
|
||
|
dragging = false;
|
||
|
hover = false;
|
||
|
redraw();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
cnv.addEventListener('mousedown', function(e){
|
||
|
var mp = mousePos(e);
|
||
|
mouseTrackHover(mp);
|
||
|
if (hover !== false){
|
||
|
dragging = mp; // begin dragging
|
||
|
e.preventDefault();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
document.addEventListener('keydown', function(e){
|
||
|
if (e.keyCode === 37){ // left
|
||
|
buildLogNextWrap(e.shiftKey ? -10 : -1);
|
||
|
e.preventDefault();
|
||
|
}
|
||
|
else if (e.keyCode === 39){ // right
|
||
|
buildLogNextWrap(e.shiftKey ? 10 : 1);
|
||
|
e.preventDefault();
|
||
|
}
|
||
|
else if (e.keyCode === 32){ // space
|
||
|
buildLogPlay();
|
||
|
e.preventDefault();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
</script>
|
||
|
<p>
|
||
|
<canvas id="cnv" width="1400" height="1000"></canvas>
|
||
|
</p>
|
||
|
<p>
|
||
|
Drag the polygon nodes to change the shape. Click the buttons below and have fun!
|
||
|
</p>
|
||
|
<p>
|
||
|
Operation:
|
||
|
<button onclick="javascript: setMode('Intersect');">Intersect</button>
|
||
|
<button onclick="javascript: setMode('Union');">Union</button>
|
||
|
<button onclick="javascript: setMode('Red - Blue');">Red - Blue</button>
|
||
|
<button onclick="javascript: setMode('Blue - Red');">Blue - Red</button>
|
||
|
<button onclick="javascript: setMode('Xor');">Xor</button>
|
||
|
</p>
|
||
|
<p>
|
||
|
<input type="checkbox" id="snap" checked="checked" /><label for="snap"> Snap</label>
|
||
|
<button style="margin-left: 1em;" onclick="javascript: poly1.inverted = !poly1.inverted; recalc();">Invert Red</button>
|
||
|
<button onclick="javascript: poly2.inverted = !poly2.inverted; recalc();">Invert Blue</button>
|
||
|
<span style="margin-left: 1em;">Animation:</span>
|
||
|
<button onclick="javascript: buildLogNextWrap(-1);">Prev</button>
|
||
|
<button onclick="javascript: buildLogPlay();" id="bl_play">Play</button>
|
||
|
<button onclick="javascript: buildLogNextWrap(1);">Next</button>
|
||
|
<span style="margin-left: 1em;">Demo:</span>
|
||
|
<button onclick="javascript: nextDemo(-1);">Prev</button>
|
||
|
<button onclick="javascript: nextDemo(1);">Next</button>
|
||
|
</p>
|
||
|
<p>
|
||
|
<a href="https://tools.ietf.org/html/rfc7946">GeoJSON</a> of result (experimental):
|
||
|
</p>
|
||
|
<center><textarea id="geojson" rows="8" style="width: 650px;"></textarea></center>
|
||
|
<p>
|
||
|
Polygon Clipping Demo based somewhat on the F. Martinez et al. algorithm (2008)
|
||
|
</p>
|
||
|
<p>
|
||
|
Coded (painfully) by
|
||
|
<a href="https://twitter.com/voidqk">@voidqk</a> from
|
||
|
<a href="http://syntheti.cc">syntheti.cc</a> – MIT License
|
||
|
</p>
|
||
|
<p>
|
||
|
<a href="http://syntheti.cc/article/polygon-clipping-pt2/">Read the companion tutorial</a>
|
||
|
<span style="padding: 0 1em; opacity: 0.3;">|</span>
|
||
|
<a href="https://github.com/voidqk/polybooljs">Project home on GitHub</a>
|
||
|
</p>
|
||
|
</body>
|
||
|
</html>
|