Folding a 1-dimensional line with a random crease pattern (mountains and valleys).
It is not guaranteed that every crease pattern is flat-foldable, but it is possible to turn a pattern that is not flat-foldable into one that is by adding creases.
xxxxxxxxxx
<meta charset="utf-8">
<style>
line {
stroke: rgba(0, 0, 0, 0.2);
fill: none;
stroke-width: 3px;
stroke-linecap: round;
}
circle {
stroke-width: 0.5px;
stroke: rgb(0, 0, 0);
}
</style>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="flat-fold.js" charset="utf-8"></script>
<body>
<svg width="960px" height="960px"></svg>
</body>
<script>
var svg = d3.select("svg"),
margin = { top: 100, left: 100, right: 100, bottom: 100},
width = 960,
height = 960,
radius = 3,
seedLo = 7,
seedHi = 12;
svg = svg.append("g")
.attr("transform", translate(margin.left, height / 2));
var X = d3.scaleLinear()
.domain([0, 1])
.range([0, width - margin.left - margin.right]);
// Create random creases in a unit interval [0, 1].
var data = d3.range(d3.randomUniform(seedLo, seedHi)() | 0).map((d, i, arr) => {
return d3.randomUniform()();
}).sort((a, b) => a - b).filter((d, i, arr) => {
// Ensure minimum distance between creases to avoid vertex overlaps.
return i === 0 || (X(d) - X(arr[i - 1]) > 4 * radius);
});
// True for mountain fold; false for valley fold.
var creases = data.map((d, i) => d3.randomUniform()() >= 0.2);
data = [0].concat(data).concat([1]);
var chain = svg;
var folding = FlatFold(data, creases);
var foldedOrder = folding.order();
var foldedLine = folding.line();
var foldedCreases = folding.creases();
var max = d3.max(foldedOrder);
var lastIndex = foldedOrder.indexOf(max);
// Create nested chain of groups.
for (let i = 0; i < foldedLine.length - 1; i++) {
chain = chain.selectAll("g")
.data([foldedLine[i]])
.enter().append("g")
.attr("transform", translate((i > 0
? X(foldedLine[i] - foldedLine[i - 1])
: X(foldedLine[i])), 0));
chain.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("y2", 0)
.attr("x2", X(foldedLine[i + 1]) - X(foldedLine[i]));
// The endpoints are not creases.
if (i > 0 && i < foldedLine.length - 1) {
chain.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", radius);
}
}
var stopped = false;
// Keep the segments centered in the viewport.
var timer = d3.timer(function(elapsed) {
let bbox = svg.node().getBBox(),
center = [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2],
dx = width / 2 - center[0],
dy = height / 2 - center[1];
svg.transition()
.ease(d3.easeLinear)
// Provide a little hysteresis.
.duration(500)
.attr("transform", (d, i) => {
return translate(dx, dy);
});
if (stopped) timer.stop();
});
svg.select("g").selectAll("g")
.style("fill", (d, i) => {
if (data.indexOf(foldedLine[i]) === -1) return "#00FF00";
return (foldedCreases[i]) ? "#FF0000" : "#0000FF";
})
.transition()
.duration(2500)
.delay((d, i) => 2500 * foldedOrder[i])
.attr("transform", (d, i) => {
let dx = i > 0 ? X(foldedLine[i + 1] - foldedLine[i]) : X(foldedLine[i + 1]);
return translate(dx, 0) + rotate(180 * ((foldedCreases[i]) ? 1 : -1));
})
.style("fill", "#000000")
.on("end", function(d, i) {
// The last crease has finished folding.
if (i === lastIndex) {
window.setTimeout(() => { stopped = true; }, 1000);
}
});
svg.select("g").selectAll("circle")
.transition()
.duration(2500)
.delay((d, i) => 2500 * foldedOrder[i])
.attr("r", radius / 2);
function translate(x, y) {
return "translate(" + x + "," + y + ")";
}
function rotate(degrees) {
return "rotate(" + degrees + ")";
}
</script>
https://d3js.org/d3.v4.min.js