Debugging Triangle to rectangle decomposition for equidecomposition of two polygons of equal area.
xxxxxxxxxx
<style>
circle {
cursor: move;
fill: #fff;
stroke: #000;
stroke-width: 1px;
}
circle.active {
stroke: #000;
stroke-width: 2px;
}
path.subject {
fill: none;
stroke: rgba(0, 0, 0, 0.3);
stroke-width: 2px;
}
path.canonicalRectangle {
fill: none;
stroke: rgba(0, 0, 240, 1);
stroke-width: 2px;
}
path.canonicalSquare {
fill: none;
fill: rgba(240, 0, 0, 0.1);
stroke-dasharray: 5, 5;
stroke: rgba(240, 0, 0, 1);
stroke-width: 2px;
}
</style>
<svg width="960" height="960"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-array.v1.min.js"></script>
<script src="https://d3js.org/d3-polygon.v1.min.js"></script>
<script src="https://npmcdn.com/earcut@2.1.1/dist/earcut.min.js"></script>
<script src="scissors.debug.js"></script>
<script>
var svg = d3.select("svg"),
width = svg.attr("width"),
height = svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory20);
// Counter-clockwise orientation.
var subject = [[width / 2, height / 5],
[width / 5, 3 * height / 5],
[3 * width / 5, 3 * height / 5]];
var subjectPolygon = svg.append("path")
.datum(subject)
.attr("class", "subject");
// Control points for the subject polygon.
var circle = svg.selectAll("circle")
.data(subject);
var origin = [0, 0];
circle.enter()
.append("circle")
.attr("r", 9)
.attr("cx", function(d) { return d[0]; })
.attr("cy", function(d) { return d[1]; })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
update();
// Redraw polygons in response to control point movement.
function update() {
let sideLength = Math.sqrt(Math.abs(d3.polygonArea(subject))),
canonicalRectangle = scissors.triangle2Rectangle(subject),
canonicalSquare = scissors.rectangle2Rectangle(canonicalRectangle, sideLength);
// Ensure subject polygon has counter-clockwise orientation.
if (!ccw(subject)) subject.reverse();
subjectPolygon.attr("d", function(d) { return "M" + d.join("L") + "Z"; });
svg.selectAll(".canonicalRectangle").remove();
svg.selectAll(".canonicalSquare").remove();
rectangle = svg.selectAll(".canonicalRectangle")
.data(canonicalRectangle); // Key on Object reference.
rectangle.enter().insert("path")
.attr("class", "canonicalRectangle")
.attr("d", function(d) { return "M" + d.join("L") + "Z"; });
square = svg.selectAll(".canonicalSquare")
.data(canonicalSquare); // Key on Object reference.
square.enter().insert("path")
.attr("fill", function(d, i) { return color(i); })
.attr("class", "canonicalSquare")
.attr("d", function(d) { return "M" + d.join("L") + "Z"; });
// Circles drawn on top of all other SVG elements.
d3.selectAll("circle").raise();
}
function dragstarted(d) {
d3.select(this).classed("active", true);
}
function dragged(d) {
d3.select(this)
.attr("cx", d[0] = d3.event.x)
.attr("cy", d[1] = d3.event.y)
update();
}
function dragended(d, i) {
d3.select(this).classed("active", false);
}
function polygonClone(polygon) {
var cloned = [],
i,
n;
for (i = 0, n = polygon.length; i < n; i++) {
cloned.push([polygon[i][0], polygon[i][1]]);
}
return cloned;
}
// Returns true of the given polygon has counter-clockwise orientation.
function ccw(polygon) {
function polygonInside(p, a, b) {
return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
}
return polygonInside(d3.polygonCentroid(polygon), polygon[0], polygon[1]);
}
</script>
https://d3js.org/d3.v4.min.js
https://d3js.org/d3-array.v1.min.js
https://d3js.org/d3-polygon.v1.min.js
https://npmcdn.com/earcut@2.1.1/dist/earcut.min.js