Skip to content

Instantly share code, notes, and snippets.

@omnizach
Last active February 29, 2016 21:01
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/e786dde6a6358008d1fb to your computer and use it in GitHub Desktop.
Save omnizach/e786dde6a6358008d1fb to your computer and use it in GitHub Desktop.
Animate Along Path II

Animate Along Path II

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 uses the normalize function to spread the curves out along the length of the spline uniformly. See the same example without normalization.

(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'),
labelFreq = 10;
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)
.normalize('length', 8);
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.filter(function(d, i) { return i % labelFreq === 0; }))
.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 * labelFreq / 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