Research for an equidecomposability (scissors congruence) implementation.
Last active
February 3, 2017 02:30
-
-
Save bmershon/fee0228bca8280f2e5cebdd9b11f2398 to your computer and use it in GitHub Desktop.
Mesh clipping
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
border: no | |
license: MIT | |
height: 960 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Version 0.0.0. Copyright 2017 Mike Bostock. | |
(function (global, factory) { | |
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | |
typeof define === 'function' && define.amd ? define(['exports'], factory) : | |
(factory((global.d3 = global.d3 || {}))); | |
}(this, function (exports) { 'use strict'; | |
// Clips the specified subject polygon to the specified clip polygon; | |
// requires the clip polygon to be counterclockwise and convex. | |
// https://en.wikipedia.org/wiki/Sutherland–Hodgman_algorithm | |
exports.polygonClip = function(clip, subject) { | |
var input, | |
closed = polygonClosed(subject), | |
i = -1, | |
n = clip.length - polygonClosed(clip), | |
j, | |
m, | |
a = clip[n - 1], | |
b, | |
c, | |
d; | |
while (++i < n) { | |
input = subject.slice(); | |
subject.length = 0; | |
b = clip[i]; | |
c = input[(m = input.length - closed) - 1]; | |
j = -1; | |
while (++j < m) { | |
d = input[j]; | |
if (polygonInside(d, a, b)) { | |
if (!polygonInside(c, a, b)) { | |
subject.push(polygonIntersect(c, d, a, b)); | |
} | |
subject.push(d); | |
} else if (polygonInside(c, a, b)) { | |
subject.push(polygonIntersect(c, d, a, b)); | |
} | |
c = d; | |
} | |
if (closed) subject.push(subject[0]); | |
a = b; | |
} | |
return subject; | |
}; | |
function polygonInside(p, a, b) { | |
return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); | |
} | |
// Intersect two infinite lines cd and ab. | |
function polygonIntersect(c, d, a, b) { | |
var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, | |
y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, | |
ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21); | |
return [x1 + ua * x21, y1 + ua * y21]; | |
} | |
// Returns true if the polygon is closed. | |
function polygonClosed(coordinates) { | |
var a = coordinates[0], | |
b = coordinates[coordinates.length - 1]; | |
return !(a[0] - b[0] || a[1] - b[1]); | |
} | |
Object.defineProperty(exports, '__esModule', {value: true}); | |
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<style> | |
circle { | |
cursor: move; | |
fill: #fff; | |
stroke: #000; | |
stroke-width: 1px; | |
} | |
circle.active { | |
stroke: #000; | |
stroke-width: 2px; | |
} | |
path.clip { | |
fill: rgba(0, 0, 0, 0.3); | |
stroke: #000; | |
stroke-width: 1px; | |
} | |
path.subject { | |
fill: rgba(0, 0, 0, 0); | |
stroke: #000; | |
stroke-width: 1px; | |
} | |
path.clipped { | |
fill: rgba(240, 0, 0, 1); | |
stroke: #000; | |
stroke-width: 3px; | |
} | |
</style> | |
<svg width="960" height="960"></svg> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="d3-polygon-clip.js"></script> | |
<script> | |
var svg = d3.select("svg"), | |
width = svg.attr("width"), | |
height = svg.attr("height"); | |
var randomX = d3.randomNormal(width / 2, width * 0.1), | |
randomY = d3.randomNormal(height / 2, height * 0.1); | |
var sites = d3.range(20).map(function(d) { | |
return [randomX(), randomY()]; | |
}); | |
// Counter-clockwise orientation. | |
var clip = [[width / 2, height / 5], | |
[width / 5, 3 * height / 5], | |
[3 * width / 5, 3 * height / 5]]; | |
var voronoi = d3.voronoi() | |
.extent([[width / 4, height / 4], [3 * width / 4, 3* height / 4]]); | |
var subject = svg.append("g") | |
.selectAll("path") | |
.data(voronoi.polygons(sites)) | |
.enter().append("path") | |
.attr("class", "subject") | |
.attr("d", function(d) { return "M" + d.join("L") + "Z"; }); | |
var clipPolygon = svg.append("path") | |
.datum(clip) | |
.attr("class", "clip"); | |
// Control points for the clip polygon. | |
var circle = svg.selectAll("circle") | |
.data(clip); | |
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() { | |
// Ensure clip polygon has counter-clockwise orientation. | |
if (!ccw(clip)) clip.reverse(); | |
clipPolygon.attr("d", function(d) { return "M" + d.join("L") + "Z"; }); | |
clipped = svg.selectAll(".clipped") | |
.data( | |
voronoi.polygons(sites) | |
.map(function(d) { | |
d3.polygonClip(clip, d); | |
return d; | |
}) | |
.filter(function(d) { | |
return d.length > 2; | |
}) | |
, function(d) { return d; }); // Key on Object reference. | |
clipped.enter().insert("path") | |
.attr("class", "clipped") | |
.attr("d", function(d) { return "M" + d.join("L") + "Z"; }); | |
clipped.exit().remove(); | |
// 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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment