Created
February 3, 2012 16:39
-
-
Save desandro/1731022 to your computer and use it in GitHub Desktop.
quadratic curve interpolation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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