Warping points based on two corresponding sets of four corners, based on Projective Mappings for Image Warping by Paul Heckbert.
I had first tried a version that pre-solves the system of equations. It seems to work but the unreduced arithmetic from inverting an 8x8 matrix is... long.
xxxxxxxxxx
<html lang="en">
<head>
<meta charset="utf-8" />
<style>
path {
fill: none;
stroke-width: 3px;
stroke: #000;
stroke-linejoin: round;
}
circle {
fill: #0eb8ba;
stroke: none;
}
</style>
</head>
<body>
<div></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="warper.js"></script>
<script>
var width = 500,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
.append("g")
.attr("transform", "translate(180)");
var points = d3.range(225).map(function(i){
return [
width / 4 + (i % 15 + 1) * width / 32,
height / 4 + (Math.floor(i / 15) + 1) * height / 32
]
});
var square = getCorners(true);
var quad = svg.append("path")
.datum(square)
.call(updateQuad);
var circles = svg.selectAll("circle")
.data(points)
.enter()
.append("circle")
.attr("r", 2)
.call(updatePoint);
warp();
function warp() {
var current = quad.datum(),
next = current === square ? getCorners() : square,
projection = warper(current,next);
circles.data(circles.data().map(projection))
.transition()
.delay(100)
.duration(750)
.call(updatePoint);
quad.datum(next)
.transition()
.delay(100)
.duration(750)
.call(updateQuad)
.each("end",warp);
}
function updateQuad(sel) {
sel.attr("d",function(d){
return "M" + d.join("L") + "Z";
});
}
function updatePoint(sel) {
sel.attr("cx",function(d){
return d[0];
})
.attr("cy",function(d){
return d[1];
});
}
function getCorners(sq) {
return d3.range(4).map(function(i){
return [
((i % 3 ? 1 : 0) + (sq ? 0.5 : 0.1 + Math.random() * 0.8)) * width / 2,
(Math.floor(i / 2) + (sq ? 0.5 : 0.1 + Math.random() * 0.8)) * height / 2
];
});
}
</script>
</body>
</html>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js