Skip to content

Instantly share code, notes, and snippets.

@desandro
Created February 3, 2012 16:39
Show Gist options
  • Save desandro/1731022 to your computer and use it in GitHub Desktop.
Save desandro/1731022 to your computer and use it in GitHub Desktop.
quadratic curve interpolation
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>quad curve</title>
<style>
body {
background: #CCC;
}
canvas {
background: white;
}
#box {
width: 50px;
height: 50px;
background: #D22;
position: relative;
}
</style>
</head>
<body>
<canvas></canvas>
<div id="box"></div>
<script>
const PI = Math.PI;
const TWO_PI = PI * 2;
var canvas, ctx;
var w = 400;
var h = 400;
// where the progression ends
var max = 0.8;
// how far along the linear progression, should the curve begin
var startCurve = 0.5;
var p1 = {
x: w * max * startCurve,
y: h * max * startCurve
};
var p2 = {
x: w,
y: h * max
};
var cp = {
x: w * max,
y: h * max
};
function line( pA, pB, color ) {
ctx.strokeStyle = color || 'hsla( 0, 0%, 0%, 0.5 )';
ctx.beginPath();
ctx.moveTo( pA.x, pA.y );
ctx.lineTo( pB.x, pB.y );
ctx.stroke();
}
function point( p, color ) {
ctx.fillStyle = color || 'hsla( 0, 0%, 0%, 0.5 )';
ctx.beginPath();
ctx.arc( p.x, p.y, 3, 0, TWO_PI );
ctx.fill();
}
function getLerpPoint( pA, pB, i ) {
return {
x: ( pB.x - pA.x ) * i + pA.x,
y: ( pB.y - pA.y ) * i + pA.y
};
}
function render( x ) {
ctx.clearRect( 0, 0, w * 2, h );
ctx.save();
ctx.scale( 1, -1 );
ctx.translate( 0, -h)
// draw the linear progression portion
line( { x: 0, y: 0 }, p1, 'hsla( 120, 100%, 40%, 0.3 )' );
// draw points and lines of quadratic curve
point( p1 );
point( cp );
point( p2 );
line( p1, cp, 'hsla( 300, 100%, 50%, 0.3 )' );
line( cp, p2, 'hsla( 300, 100%, 50%, 0.3 )' );
// draw x value
line( { x: x, y: 0 }, { x: x, y: h }, 'hsla( 0, 0%, 0%, 0.3 )' );
// draw quadratic curve
ctx.strokeStyle = '#22A';
ctx.beginPath();
ctx.moveTo( p1.x, p1.y );
ctx.quadraticCurveTo( cp.x, cp.y, p2.x, p2.y );
ctx.stroke();
// where curve starts
var progressionPoint
if ( x > p2.x ) {
progressionPoint = { x: x, y: p2.y };
} else if ( x < p1.x ) {
// if in linear progression, before the curve begins
progressionPoint = { x: x, y: x };
} else {
// thx to Wes Dimiceli for figuring the math all out
var denom = p2.x - 2 * cp.x + p1.x;
var a = ( cp.x - p1.x ) / denom;
var b = Math.sqrt( ( x - p1.x ) / denom + a * a );
var i1 = b - a;
var i2 = -b - a;
// since sqrt can be +/-, use value that is less than 1
var i = i1 <= 1 ? i1 : i2;
var sp1 = getLerpPoint( p1, cp, i );
point( sp1, 'orange' );
var sp2 = getLerpPoint( cp, p2, i );
point( sp2, 'orange' );
line( sp1, sp2, 'orange' );
progressionPoint = getLerpPoint( sp1, sp2, i );
}
point( progressionPoint, 'black' );
ctx.restore();
}
function onMousemove( event ) {
var x = ( event.pageX - canvas.offsetLeft );
render( x );
}
function lerp( a, b, i ) {
return ( b - a ) * i + a;
}
function quadLimit( value, max, limit, startCurve ) {
startCurve = startCurve || 0;
var p1 = limit * startCurve;
if ( value > max ) {
// if value is more than max, cap it at the limit
value = limit;
} else if ( value > p1 ) {
// within curve, let's get y value
// thx to Wes Dimiceli for figuring the math all out
var denom = max - 2 * limit + p1;
// prevent divide by 0
denom = denom === 0 ? 1 : denom;
var a = ( limit - p1 ) / denom;
var b = Math.sqrt( ( value - p1 ) / denom + a * a );
var i1 = b - a;
var i2 = -b - a;
// since sqrt can be +/-, use value that is less than 1
var i = i1 <= 1 ? i1 : i2;
var sp1y = lerp( p1, limit, i );
value = lerp( sp1y, limit, i );
}
return value;
}
var box;
var boxOriginX;
function onBoxMousedown( event ) {
boxOriginX = event.pageX;
window.addEventListener( 'mousemove', onBoxMousemove, false );
window.addEventListener( 'mouseup', onBoxmouseup, false );
}
function onBoxMousemove( event ) {
var x = event.pageX - boxOriginX;
x = quadLimit( x, 1000, 750, 0.4 );
// console.log( x )
box.style.left = ~~x + 'px';
}
function onBoxmouseup( event ) {
box.style.left = 0;
window.removeEventListener( 'mousemove', onBoxMousemove, false );
window.removeEventListener( 'mouseup', onBoxmouseup, false );
}
window.onload = function() {
canvas = document.getElementsByTagName('canvas')[0];
canvas.width = w * 2;
canvas.height = h;
ctx = canvas.getContext('2d');
render();
canvas.addEventListener( 'mousemove', onMousemove, false);
box = document.getElementById('box');
box.addEventListener( 'mousedown', onBoxMousedown, false );
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment