|
<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; |
|
} |
|
|
|
#hover-area { |
|
fill: transparent; |
|
stroke: none; |
|
cursor: crosshair; |
|
} |
|
|
|
#heart { |
|
fill: pink; |
|
text-anchor: middle; |
|
} |
|
|
|
.seed { |
|
fill: grey; |
|
} |
|
|
|
.cell { |
|
fill: none; |
|
stroke: grey; |
|
} |
|
|
|
.hide { |
|
display: none; |
|
} |
|
</style> |
|
<body> |
|
<div id="under-construction"> |
|
UNDER CONSTRUCTION |
|
</div> |
|
|
|
<svg> |
|
<g id="drawing-area"> |
|
<path id="wave-path"/> |
|
<g id="voronoi-container"/> |
|
<g id="seed-container"/> |
|
<text id="heart">♥</text> |
|
<circle id="hover-area"></circle> |
|
</g> |
|
</svg> |
|
|
|
<script src="https://d3js.org/d3.v4.min.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 3 cocentric rings; |
|
//only cells from the middle ring will be shown |
|
var visibleSeedCount = 48, |
|
hiddenSeedCount = visibleSeedCount-1, |
|
seedDistance = 3*midWidth/5, |
|
interRingMaxDistance = midWidth/6, |
|
interRingDistance = 0, |
|
interRingDistanceObjective = interRingMaxDistance, // store next interRingDistance until the current frame is displayed |
|
interRingHeartTreshold = 0.25, |
|
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(); |
|
|
|
d3.interval(function(elapsed) { |
|
if (interRingDistance !== interRingDistanceObjective) { |
|
interRingDistance = interRingDistanceObjective; |
|
|
|
computeSeeds(); |
|
redrawVoronoi(); |
|
redrawSeeds(); |
|
seedContainer.classed("hide", hideSeeds); |
|
} |
|
}); |
|
|
|
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"); |
|
heart = d3.select("#heart").attr("transform", "rotate(-90)translate(0,6)") |
|
} |
|
|
|
function moved() { |
|
var coords = d3.mouse(this); |
|
var mouseRadius = Math.sqrt(Math.pow(coords[0],2)+Math.pow(coords[1],2)); |
|
var scaledMouseRadius = mouseRadius*interRingMaxDistance/midWidth; |
|
if (scaledMouseRadius<interRingHeartTreshold) { |
|
interRingDistanceObjective = interRingHeartTreshold; |
|
} else { |
|
interRingDistanceObjective = scaledMouseRadius; |
|
} |
|
} |
|
|
|
function exited() { |
|
interRingDistanceObjective = interRingMaxDistance; |
|
} |
|
|
|
function clicked() { |
|
hideSeeds = !hideSeeds; |
|
} |
|
|
|
function computeSeeds() { |
|
seeds = []; |
|
var hidden = true; |
|
|
|
//compute seeds for visible cells |
|
seeds = seeds.concat(ringSeeds(visibleSeedCount, 0, !hidden)); |
|
//compute seeds for inner hidden cells |
|
seeds = seeds.concat(ringSeeds(hiddenSeedCount, -interRingDistance, hidden)); |
|
//compute seeds for outer hidden cells |
|
seeds = seeds.concat(ringSeeds(hiddenSeedCount, interRingDistance, hidden)); |
|
} |
|
|
|
function ringSeeds(count, distanceDelta, hidden) { |
|
var ringSeeds=[], |
|
interRadAngle = _2PI/count, |
|
interDegAngle = 360/count, |
|
radAngle, cos, sin, distance; |
|
|
|
for(var ai=0; ai<count; ai++) { |
|
radAngle = interRadAngle*ai; |
|
cos = Math.cos(radAngle); |
|
sin = Math.sin(radAngle); |
|
distance = seedDistance+distanceDelta*Math.cos(radAngle/2); |
|
ringSeeds.push({ |
|
x: distance*cos, |
|
y: distance*sin, |
|
hidden: hidden, |
|
strokeColor: d3.hsl(Math.abs(interDegAngle*ai-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("fill", 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> |