Skip to content

Instantly share code, notes, and snippets.

@Kcnarf
Last active December 3, 2018 08:26
Show Gist options
  • Save Kcnarf/2410bccd4af0ccdd1e1976e0204e4a72 to your computer and use it in GitHub Desktop.
Save Kcnarf/2410bccd4af0ccdd1e1976e0204e4a72 to your computer and use it in GitHub Desktop.
Voronoï playground : parquet deformation
license: gpl-3.0
border: no

This block is a recreation (and a continuation of 1) inspired by parquet deformation, which consists on progressively transform a shape into another one (cf. www.theguardian.com/[...]/crazy-paving-the-twisted-world-of-parquet-deformations or http://www.tess-elation.co.uk/parquet-deformations).

In this block, the inital shape is the bottom most quazi-rectangle, and it is progressively transformed into the top most quazi-hexagon shape. The final result is obtained by using a Voronoï layout, with inner and outer cells hidden, and by positioning seeds at adequate positions (click to show/hide seeds): seeds are positioned on cocentric rings, and each ring has one less (or one more) seed than its contiguous rings. This difference in the number of seeds per ring produces the progressive shape transformation. At the bottom of the figure, first seeds of all rings are perfectly aligned, which leads to quazi-rectangle Voronoï cells. Conversely, at the top of the figure, seeds are alternatively aligned/disaligned (due to odd/even number of seeds), which leads to quazy-hexagon Voronoï cells.

I came to parquet deformation because the Voronoï layout makes a tessellation of the 2D plane, as the parquet does. Parquet deformation is also closely linked to Escher's researches and amazing drawings (cf. http://en.tessellations-nicolas.com/method.php).

Acknowledgments to:

<meta charset="utf-8">
<title>Voronoï playground : parquet deformation</title>
<meta content="Using D3's Voronoï layout to simulate parquet deformation" name="description">
<style>
#under-construction {
display: none;
position: absolute;
top: 200px;
left: 300px;
font-size: 40px;
}
svg {
margin: 1px;
border-radius: 1000px;
box-shadow: 2px 2px 6px grey;
}
#voronoi-container {
filter: url("#blur");
}
#hover-area {
fill: transparent;
stroke: none;
cursor: crosshair;
}
.seed {
fill: black;
}
.cell {
fill: none;
stroke: grey;
}
.hide {
display: none;
}
</style>
<body>
<div id="under-construction">
UNDER CONSTRUCTION
</div>
<svg>
<defs>
<filter id="blur">
<feGaussianBlur result="blur1" in="SourceGraphic" stdDeviation="0.5" />
</filter>
</defs>
<g id="drawing-area">
<path id="wave-path"/>
<g id="voronoi-container"/>
<g id="seed-container"/>
<circle id="hover-area"></circle>
</g>
</svg>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="simplex-noise.min.js"></script>
<script>
var _2PI = 2*Math.PI;
//begin: layout conf.
var svgbw = 1, //svg border width
svgm = 10, //svg margin
totalWidth = 500,
totalHeight = 500,
width = totalWidth-(svgm+svgbw)*2,
height = totalHeight-(svgm+svgbw)*2,
midWidth = width/2,
midHeight = height/2;
//end: layout conf.
//begin: voronoi conf.
//we oragnize seeds on cocentric rings;
//cells from the inner and outter rings will be hidden
var visibleRingCount = 15,
minVisibleSeedCount = 12,
maxVisibleSeedCount = 48,
visibleSeedCountScale = d3.scaleQuantize()
.domain([0,midWidth])
.range(d3.range(minVisibleSeedCount,maxVisibleSeedCount+1)),
visibleSeedCount = visibleSeedCountScale(midWidth),
outerRingDistance = midWidth,
hideSeeds = true,
seeds = [];
//end: voronoi conf.
var voronoiLayout = d3.voronoi()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.extent([[-(midWidth+10), -(midHeight+10)], [midWidth+10, midHeight+10]]);
var svg, drawingArea, voronoiContainer, seedContainer;
initLayout();
computeSeeds();
redrawVoronoi();
redrawSeeds();
function initLayout() {
svg = d3.select("svg")
.attr("width", width)
.attr("height", height);
drawingArea = d3.select("#drawing-area")
.attr("transform", "translate("+[midWidth, midHeight]+")rotate(90)");
d3.select("#hover-area")
.attr("r", midWidth)
.on("touchmove mousemove", moved)
.on("mouseout", exited)
.on("click", clicked);
voronoiContainer = d3.select("#voronoi-container");
seedContainer = d3.select("#seed-container").classed("hide", hideSeeds);
}
function moved() {
var coords = d3.mouse(this);
var mouseRadius = Math.sqrt(Math.pow(coords[0],2)+Math.pow(coords[1],2));
visibleSeedCount = visibleSeedCountScale(mouseRadius);
computeSeeds();
redrawVoronoi();
redrawSeeds();
}
function exited() {
visibleSeedCount = visibleSeedCountScale(midWidth);
redrawSeeds;
}
function clicked() {
hideSeeds = !hideSeeds;
seedContainer.classed("hide", hideSeeds);
}
function computeSeeds() {
seeds = [];
var hidden = true,
currentRingDistance = outerRingDistance,
ri=visibleRingCount,
currentInterRing;
//add outer ring, which produces hidden cells
seeds = seeds.concat(ringSeeds(visibleSeedCount-ri%2, currentRingDistance, hidden));
//add interim rings, going to the center, which produces visible cells; recomputing adequate distance between rings in order to have square-like cells at each ring's bottom
while(ri>0) {
ri--;
currentInterRing = currentRingDistance*Math.sin(_2PI/visibleSeedCount);
currentRingDistance -= currentInterRing;
seeds = seeds.concat(ringSeeds(visibleSeedCount-ri%2, currentRingDistance, !hidden));
}
//add inner ring, which produces hidden cells
ri--
currentInterRing = currentRingDistance*Math.sin(_2PI/visibleSeedCount);
currentRingDistance -= currentInterRing;
seeds = seeds.concat(ringSeeds(visibleSeedCount-ri%2, currentRingDistance, hidden));
}
function ringSeeds(seedCount, distance, hidden) {
var ringSeeds=[],
interRadAngle = _2PI/seedCount,
interDegAngle = 360/seedCount,
radAngle, cos, sin, distance;
for(var ai=0; ai<seedCount; ai++) {
radAngle = interRadAngle*ai;
degAngle = interDegAngle*ai;
cos = Math.cos(radAngle);
sin = Math.sin(radAngle);
ringSeeds.push({
x: (distance*cos).toFixed(4),
y: (distance*sin).toFixed(4),
hidden: hidden,
strokeColor: d3.hsl(Math.abs(degAngle-180), 1, 0.45)
});
}
return ringSeeds;
}
function redrawVoronoi() {
//begin: draw Voronoi cells
voronoiContainer.selectAll(".cell").remove();
var drawnCells = voronoiContainer.selectAll(".cell")
.data(voronoiLayout.polygons(seeds));
drawnCells.enter()
.append("path")
.classed("cell", true)
.classed("hide", function(d){ return d.data.hidden; })
.attr("d", function(d){ return d3.line()(d)+"z"; })
.style("stroke", function(d){ return d.data.strokeColor; });
//end: draw Voronoi cells
}
function redrawSeeds() {
//begin: draw Voronoi cells
seedContainer.selectAll(".seed").remove();
var drawnSeeds = seedContainer.selectAll(".seed")
.data(seeds);
drawnSeeds.enter()
.append("circle")
.classed("seed", true)
.attr("cx", function(d){ return d.x; })
.attr("cy", function(d){ return d.y; })
.attr("r", 1);
//end: draw Voronoi cells
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment