Skip to content

Instantly share code, notes, and snippets.

@omnizach
Last active February 29, 2016 21:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save omnizach/d8220c3ca6d82fef083b to your computer and use it in GitHub Desktop.
Save omnizach/d8220c3ca6d82fef083b to your computer and use it in GitHub Desktop.
Animate Along Path I

Animate Along Path I

This example shows how you can create a Bezier spline and animate an object along its path. It uses my small Bezier library.

There are other solutions to this problem, and if this is all you want to accomplish, then you can use getPointAtLength on the path.

This example shows the difference between animating with respect to length and t. The t value is used internally to map out the curve, but is not uniformly distributed along the length. So, the ball speeds up and slows down because each segement takes the same amount of time to travel regardless of how long the segment is.

If you want a uniform speed, use the normalize funciton.

(function(){var t,n,e,r,i,h,s,o=function(t,n){return(+t%(n=+n)+n)%n},p=[].slice;i=function(){var t,n,e,r,i,h,s;for(i=function(){var n,e,r;for(r=[],n=0,e=arguments.length;e>n;n++)t=arguments[n],r.push(t.length);return r}.apply(this,arguments),r=Math.min.apply(Math,i),s=[],n=e=0,h=r;h>=0?h>e:e>h;n=h>=0?++e:--e)s.push(function(){var e,r,i;for(i=[],e=0,r=arguments.length;r>e;e++)t=arguments[e],i.push(t[n]);return i}.apply(this,arguments));return s},Function.prototype._getter=function(t,n){return Object.defineProperty(this.prototype,t,{get:n,configurable:!0})},t=1-1e-6,e=function(){function t(n,e){return this instanceof t?(this.x=n,void(this.y=e)):new t(n,e)}return t}(),n=function(){function t(n,e,r,i){return this instanceof t?(this.p0=n,this.p1=e,this.p2=r,void(this.p3=i)):new t(n,e,r,i)}return t._base3=function(t,n,e,r,i){var h,s;return h=-3*n+9*e-9*r+3*i,s=t*h+6*n-12*e+6*r,t*s-3*n+3*e},t.penPath=function(t){return"M "+t.p0.x+", "+t.p0.y+" C "+t.p1.x+", "+t.p1.y+" "+t.p2.x+", "+t.p2.y+" "+t.p3.x+", "+t.p3.y},t.paintPath=function(t){var n;return n=t/2,function(t){var e,r;return e=t.normal(0),r=t.normal(1),"M "+(t.p0.x-e.x*n)+", "+(t.p0.y-e.y*n)+" L "+(t.p0.x+e.x*n)+", "+(t.p0.y+e.y*n)+" L "+(t.p3.x+r.x*n)+", "+(t.p3.y+r.y*n)+" L "+(t.p3.x-r.x*n)+", "+(t.p3.y-r.y*n)+" Z"}},t.prototype._findT=function(t,n){var e;return t=Math.min(t,this.length),n=n||t/this.length,e=(this.lengthAt(n)-t)/this.length,Math.abs(e)<1e-4?n:this._findT(t,n-e/2)},t.prototype.lengthAt=function(n){var e,r;return null==n&&(n=1),n=n>1?1:0>n?0:n,r=n/2,e=function(n){var e;return e=r*n[0]+r,n[1]*Math.sqrt(Math.pow(t._base3(e,this.p0.x,this.p1.x,this.p2.x,this.p3.x),2)+Math.pow(t._base3(e,this.p0.y,this.p1.y,this.p2.y,this.p3.y),2))},r*[[-.1252,.2491],[.1252,.2491],[-.3678,.2335],[.3678,.2335],[-.5873,.2032],[.5873,.2032],[-.7699,.1601],[.7699,.1601],[-.9041,.1069],[.9041,.1069],[-.9816,.0472],[.9816,.0472]].map(e,this).reduce(function(t,n){return t+n})},t._getter("length",function(){return null!=this._length?this._length:this._length=this.lengthAt(1)}),t.prototype.x=function(t){return Math.pow(1-t,3)*this.p0.x+3*Math.pow(1-t,2)*t*this.p1.x+3*(1-t)*Math.pow(t,2)*this.p2.x+Math.pow(t,3)*this.p3.x},t.prototype.y=function(t){return Math.pow(1-t,3)*this.p0.y+3*Math.pow(1-t,2)*t*this.p1.y+3*(1-t)*Math.pow(t,2)*this.p2.y+Math.pow(t,3)*this.p3.y},t.prototype.point=function(t){return e(this.x(t),this.y(t))},t.prototype.pointAtLength=function(t){return this.point(this._findT(t))},t.prototype.firstDerivative=function(t){return new e(3*Math.pow(1-t,2)*(this.p1.x-this.p0.x)+6*(1-t)*t*(this.p2.x-this.p1.x)+3*Math.pow(t,2)*(this.p3.x-this.p2.x),3*Math.pow(1-t,2)*(this.p1.y-this.p0.y)+6*(1-t)*t*(this.p2.y-this.p1.y)+3*Math.pow(t,2)*(this.p3.y-this.p2.y))},t.prototype.secondDerivative=function(t){return new e(6*(1-t)*(this.p2.x-2*this.p1.x+this.p0.x)+6*t*(this.p3.x-2*this.p2.x+this.p2.x),6*(1-t)*(this.p2.y-2*this.p1.y+this.p0.y)+6*t*(this.p3.y-2*this.p2.y+this.p2.y))},t.prototype.curvature=function(t){var n,e;return n=this.firstDerivative(t)||0,e=this.secondDerivative(t)||0,(n.x*e.y-n.y*e.x)/Math.pow(n.x*n.x+n.y*n.y,1.5)},t.prototype.tangent=function(t){var n,r;return r=this.firstDerivative(t),n=Math.sqrt(r.x*r.x+r.y*r.y)||1,new e(r.x/n,r.y/n)},t.prototype.normal=function(t){var n;return n=this.tangent(t),new e(-n.y,n.x)},t}(),r=function(){function r(t,n){var e;return null==n&&(n=!1),this instanceof r?(this.closed=n,this.curves=r.computeSpline(t.map(function(t){return t.x}),t.map(function(t){return t.y}),n),this.startLengths=function(){var t,n,r,i;for(r=this.curves,i=[],t=0,n=r.length;n>t;t++)e=r[t],i.push(e.startLength);return i}.call(this),this.endLengths=function(){var t,n,r,i;for(r=this.curves,i=[],t=0,n=r.length;n>t;t++)e=r[t],i.push(e.endLength);return i}.call(this),void(this.length=this.endLengths.slice(-1)[0])):new r(t,n)}return r.computeControlPoints=function(t){var n,e,r,i,h,s,o,p,u,c,a,l,f,v,y;for(n=function(t){return 0>=t?0:t>=p-1?2:1},r=function(t){return t>=p-1?0:1},p=t.length-1,c=new Array(p-1),a=new Array(p-1),e=function(){var t,n,e;for(e=[],i=t=0,n=p;n>=0?n>t:t>n;i=n>=0?++t:--t)e.push(4);return e}(),e[0]=2,e[p-1]=7,l=function(){var n,e,r;for(r=[],i=n=0,e=p-1;e>=0?e>n:n>e;i=e>=0?++n:--n)r.push(4*t[i]+2*t[i+1]);return r}(),l[0]=t[0]+2*t[1],l.push(8*t[p-1]+t[p]),i=h=0,f=e.length-1;f>=0?f>h:h>f;i=f>=0?++h:--h)o=n(i+1)/e[i],e[i+1]-=o*r(i),l[i+1]-=o*l[i];for(c[p-1]=l[p-1]/e[p-1],i=s=v=e.length-2;0>=v?0>=s:s>=0;i=0>=v?++s:--s)c[i]=(l[i]-r(i)*c[i+1])/e[i];for(i=u=0,y=e.length-1;y>=0?y>u:u>y;i=y>=0?++u:--u)a[i]=2*t[i+1]-c[i+1];return a[p-1]=.5*(t[p]+c[p-1]),{p1:c,p2:a}},r.computeSpline=function(t,h,s){var u,c,a,l,f,v,y,g,x,d,w,M,m,_,L,b,A,D,I,P,C;for(l=12,s&&(v=function(t){var n,e,r,i;for(i=[],n=e=0,r=l;r>=0?r>e:e>r;n=r>=0?++e:--e)i.push(t[o(n,t.length)]);return i},f=function(t){var n,e,r,i;for(i=[],n=e=r=l;0>=r?0>e:e>0;n=0>=r?++e:--e)i.push(t[o(t.length-n,t.length)]);return i},t=p.call(f(t)).concat(p.call(t),p.call(v(t))),h=p.call(f(h)).concat(p.call(h),p.call(v(h)))),c=r.computeControlPoints(t),a=r.computeControlPoints(h),C=0,s&&(t=t.slice(l,+-l+1||9e9),h=h.slice(l,+-l+1||9e9),c.p1=c.p1.slice(l,+-l+1||9e9),a.p1=a.p1.slice(l,+-l+1||9e9),c.p2=c.p2.slice(l,+-l+1||9e9),a.p2=a.p2.slice(l,+-l+1||9e9)),D=i(t.slice(0,-1),h.slice(0,-1),c.p1,a.p1,c.p2,a.p2,t.slice(1),h.slice(1)),P=[],y=g=0,x=D.length;x>g;y=++g)I=D[y],d=I[0],w=I[1],M=I[2],m=I[3],_=I[4],L=I[5],b=I[6],A=I[7],u=new n(new e(d,w),new e(M,m),new e(_,L),new e(b,A)),u.startLength=C,u.endLength=C+u.length,u.segmentOffset=y/(t.length-1),u.index=y,C+=u.length,P.push(u);return P},r.prototype._curveIndex=function(n){var e;return n=(0>n?0:n>t?t:n)*this.curves.length,e=Math.trunc(n),{i:e,t:n-e}},r.prototype.x=function(t){var n;return n=this._curveIndex(t),this.curves[n.i].x(n.t)},r.prototype.y=function(t){var n;return n=this._curveIndex(t),this.curves[n.i].y(n.t)},r.prototype.point=function(t){var n;return n=this._curveIndex(t),this.curves[n.i].point(n.t)},r.prototype.pointAtLength=function(t){var n,e;return n=function(t,e,r,i){var h;switch(h=r+i>>>1,!1){case!((t[h-1]||0)<=e&&e<=t[h]):return h;case!(e<(t[h-1]||0)):return n(t,e,r,h);default:return n(t,e,h+1,i)}},e=n(this.endLengths,Math.min(t,this.endLengths[this.endLengths.length-1]),0,this.endLengths.length),this.curves[e].pointAtLength(t-this.startLengths[e])},r.prototype.firstDerivative=function(t){var n;return n=this._curveIndex(t),this.curves[n.i].firstDerivative(n.t)},r.prototype.secondDerivative=function(t){var n;return n=this._curveIndex(t),this.curves[n.i].secondDerivative(n.t)},r.prototype.curvature=function(t){var n;return n=this._curveIndex(t),this.curves[n.i].curvature(n.t)},r.prototype.tangent=function(t){var n;return n=this._curveIndex(t),this.curves[n.i].tangent(n.t)},r.prototype.normal=function(t){var n;return n=this._curveIndex(t),this.curves[n.i].normal(n.t)},r.prototype.normalize=function(t,n,e){var i,h;switch(e=e||Math.ceil(this.length/(n||1)),t){case"length":return n=this.length/e,h=function(){var t,r,h;for(h=[],i=t=0,r=e;r>=0?r>t:t>r;i=r>=0?++t:--t)h.push(this.pointAtLength(i*n));return h}.call(this),new r(h,this.closed);case"x":return this;default:return this}},r}(),h=function(t){return t.x.bind(t)},s=function(t){return t.y.bind(t)},this.bezier={Point:e,Curve:n,Spline:r,interpolateX:h,interpolateY:s}}).call(this);
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.bezier-curve {
stroke-width: 3px;
fill: none;
}
.bezier-curve:nth-child(even) {
stroke: #ccc;
}
.bezier-curve:nth-child(odd) {
stroke: #333;
}
.ball {
fill: #c00;
stroke: #600;
stroke-width: 2px;
}
.label rect {
fill: #fff;
stroke: #000;
stroke-width: 2px;
}
.label text {
font-family: Verdana;
font-size: 10pt;
text-anchor: middle;
alignment-baseline: middle;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="bezier.min.js"></script>
<script>
var formatLabel = d3.format('.2f');
var points = [
[250, 300],
[150, 280],
[155, 250],
[150, 220],
[250, 200],
[650, 200],
[750, 220],
[745, 250],
[750, 280],
[650, 300]
];
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var spline = bezier.Spline(points.map(function(p) { return bezier.Point(p[0], p[1]); }), true);
svg.append('g')
.selectAll('.bezier-curve')
.data(spline.curves)
.enter()
.append('path')
.classed('bezier-curve', true)
.attr('d', bezier.Curve.penPath);
var labels = svg.append('g')
.selectAll('.label')
.data(spline.curves)
.enter()
.append('g')
.classed('label', true);
labels.append('rect')
.attr('x', function(d) { return d.p0.x-30 - d.normal(0).x*40; })
.attr('y', function(d) { return d.p0.y-10 - d.normal(0).y*20; })
.attr('width', 60)
.attr('height', 20);
labels.append('text')
.attr('x', function(d) { return d.p0.x - d.normal(0).x*40; })
.attr('y', function(d) { return d.p0.y - d.normal(0).y*20; })
.text(function(d, i) { return 't = ' + formatLabel(i / spline.curves.length); });
svg.append('g')
.append('circle')
.datum(spline)
.classed('ball', true)
.attr('r', 10)
.attr('cx', spline.x(0))
.attr('cy', spline.y(0))
.transition()
.delay(1000)
.ease('linear')
.duration(5000)
.attrTween("cx", bezier.interpolateX)
.each(lap);
function lap() {
var c = d3.select(this);
(function repeat() {
c = c.transition()
.attrTween('cx', bezier.interpolateX)
.attrTween('cy', bezier.interpolateY)
.each('end', repeat);
})();
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment