diff --git a/article.js b/article.js
index acf2a30b..997e8031 100644
--- a/article.js
+++ b/article.js
@@ -29,7 +29,7 @@ preset:"simple",title:"Quadratic Bézier curve components",setup:this.setupQuadr
preset:"simple",title:"Quadratic Bézier curve extremities",setup:this.setupQuadratic,draw:this.draw}),r.createElement(i,{preset:"simple",title:"Cubic Bézier curve extremities",setup:this.setupCubic,draw:this.draw}))}});e.exports=o},function(e,t,n){"use strict";var r=n(0),i=n(10),a=new i("en-GB"),o="flattening",s=n(15),l=r.createClass({displayName:"Flattening",statics:{keyHandlingOptions:{propName:"steps",values:{38:1,40:-1},controller:function(e){e.steps<1&&(e.steps=1)}}},getDefaultProps:function(){return{title:a.getTitle(o)}},setupQuadratic:function(e){var t=e.getDefaultQuadratic();e.setCurve(t),e.steps=3},setupCubic:function(e){var t=e.getDefaultCubic();e.setCurve(t),e.steps=5},drawFlattened:function(e,t){e.reset(),e.setColor("#DDD"),e.drawSkeleton(t),e.setColor("#DDD"),e.drawCurve(t);for(var n,r=1/e.steps,i=t.points[0],a=r;a<1+r;a+=r)n=t.get(Math.min(a,1)),e.setColor("red"),e.drawLine(i,n),i=n;e.setFill("black"),e.text("Curve approximation using "+e.steps+" segments",{x:10,y:15})},values:{38:1,40:-1},onKeyDown:function(e,t){var n=this.values[e.keyCode];n&&(e.preventDefault(),t.steps+=n,t.steps<1&&(t.steps=1))},render:function(){return r.createElement("section",null,a.getContent(o,this))}});e.exports=s(l)},function(e,t,n){"use strict";var r=n(0),i=n(5),a=n(3),o=n(15),s=r.createClass({displayName:"GraduatedOffsetting",statics:{keyHandlingOptions:{propName:"distance",values:{38:1,40:-1}}},getDefaultProps:function(){return{title:"Graduated curve offsetting"}},setup:function(e,t){e.setCurve(t),e.distance=20},setupQuadratic:function(e){var t=e.getDefaultQuadratic();this.setup(e,t)},setupCubic:function(e){var t=e.getDefaultCubic();this.setup(e,t)},draw:function(e,t){e.reset(),e.drawSkeleton(t),e.drawCurve(t),e.setColor("blue");var n=t.outline(0,0,e.distance,e.distance);n.curves.forEach(function(t){return e.drawCurve(t)})},render:function(){return r.createElement("section",null,r.createElement(a,this.props),r.createElement("p",null,"What if we want to do graduated offsetting, starting at some distance ",r.createElement("i",null,"s")," but ending at some other distance ",r.createElement("i",null,"e"),'? well, if we can compute the length of a curve (which we can if we use the Legendre-Gauss quadrature approach) then we can also determine how far "along the line" any point on the curve is. With that knowledge, we can offset a curve so that its offset curve is not uniformly wide, but graduated between with two different offset widths at the start and end.'),r.createElement("p",null,'Like normal offsetting we cut up our curve in sub-curves, and then check at which distance along the original curve each sub-curve starts and ends, as well as to which point on the curve each of the control points map. This gives us the distance-along-the-curve for each interesting point in the sub-curve. If we call the total length of all sub-curves seen prior to seeing "the\\ current" sub-curve ',r.createElement("i",null,"S")," (and if the current sub-curve is the first one, ",r.createElement("i",null,"S")," is zero), and we call the full length of our original curve ",r.createElement("i",null,"L"),", then we get the following graduation values:"),r.createElement("ul",null,r.createElement("li",null,"start: map ",r.createElement("i",null,"S")," from interval (",r.createElement("i",null,"0,L"),") to interval ",r.createElement("i",null,"(s,e)")),r.createElement("li",null,"c1: ",r.createElement("i",null,"map(",r.createElement("strong",null,"S+d1"),", 0,L, s,e)"),", d1 = distance along curve to projection of c1"),r.createElement("li",null,"c2: ",r.createElement("i",null,"map(",r.createElement("strong",null,"S+d2"),", 0,L, s,e)"),", d2 = distance along curve to projection of c2"),r.createElement("li",null,"..."),r.createElement("li",null,"end: ",r.createElement("i",null,"map(",r.createElement("strong",null,"S+length(subcurve)"),", 0,L, s,e)"))),r.createElement("p",null,"At each of the relevant points (start, end, and the projections of the control points onto the curve) we know the curve's normal, so offsetting is simply a matter of taking our original point, and moving it along the normal vector by the offset distance for each point. Doing so will give us the following result (these have with a starting width of 0, and an end width of 40 pixels, but can be controlled with your up and down arrow keys):"),r.createElement(i,{preset:"simple",title:"Offsetting a quadratic Bézier curve",setup:this.setupQuadratic,draw:this.draw,onKeyDown:this.props.onKeyDown}),r.createElement(i,{preset:"simple",title:"Offsetting a cubic Bézier curve",setup:this.setupCubic,draw:this.draw,onKeyDown:this.props.onKeyDown}))}});e.exports=o(s)},function(e,t,n){"use strict";var r=n(0),i=n(5),a=n(3),o=r.createClass({displayName:"ABC",getDefaultProps:function(){return{title:"Curve inflections"}},setupCubic:function(e){var t=e.getDefaultCubic();e.setCurve(t)},draw:function(e,t){e.reset(),e.drawSkeleton(t),e.drawCurve(t),t.inflections().forEach(function(n){e.drawCircle(t.get(n),5)})},render:function(){return r.createElement("section",null,r.createElement(a,this.props),r.createElement("p",null,"Now that we know how to align a curve, there's one more thing we can calculate: inflection points. Imagine we have a variable size circle that we can slide up against our curve. We place it against the curve and adjust its radius so that where it touches the curve, the curvatures of the curve and the circle are the same, and then we start to slide the circle along the curve - for quadratic curves, we can always do this without the circle behaviour oddly: we might have to change the radius of the circle as we slide it alone, but it'll always sit against the same side of the curve."),r.createElement("p",null,'But what happens with cubic curves? Imagine we have an S curve and we place our circle at the start of the curve, and start sliding it along. For a while we can simply adjust the radius and things will be fine, but once we get to the midpoint of that S, something odd happens: the circle "flips" from one side of the curve to the other side, in order for the curvatures to keep matching. This is called an inflection, and we can find out where those happen relatively easily.'),r.createElement("p",null,"What we need to do is solve a simple equation:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/b039194e01b6081628efaf4aa169a4c50fa4aae4.svg",style:{width:"3.90015rem",height:"1.125rem"}})),r.createElement("p",null,"What we're saying here is that given the curvature function ",r.createElement("i",null,"C(t)"),", we want to know for which values of ",r.createElement("i",null,"t"),' this function is zero, meaning there is no "curvature", which will be exactly at the point between our circle being on one side of the curve, and our circle being on the other side of the curve. So what does ',r.createElement("i",null,"C(t)")," look like? Actually something that seems not too hard:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/2488885d57cfc55b50b1c8ad808180e50337b411.svg",style:{width:"27.37485rem",height:"1.125rem"}})),r.createElement("p",null,"So the function ",r.createElement("i",null,"C(t)")," is wholly defined by the first and second derivative functions for the parametric dimensions of our curve. And as already shown, derivatives of Bézier curves are just simpler Bézier curves, with very easy to compute new coefficients, so this should be pretty easy."),r.createElement("p",null,"However as we've seen in the section on aligning, aligning lets us simplify things ",r.createElement("em",null,"a lot"),", by completely removing the contributions of the first coordinate from most mathematical evaluations, and removing the last ",r.createElement("i",null,"y")," coordinate as well by virtue of the last point lying on the x-axis. So, while we can evaluate ",r.createElement("i",null,"C(t) = 0")," for our curve, it'll be much easier to first axis-align the curve and ",r.createElement("em",null,"then")," evalutating the curvature function."),r.createElement("div",{className:"note"},r.createElement("h3",null,"Let's derive the full formula anyway"),r.createElement("p",null,"Of course, before we do our aligned check, let's see what happens if we compute the curvature function without axis-aligning. We start with the first and second derivatives, given our basis functions:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/462664d174062cec5f7252d5912057210783776c.svg",style:{width:"41.250150000000005rem",height:"4.35015rem"}})),r.createElement("p",null,"And of course the same functions for ",r.createElement("i",null,"y"),":"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/9390cb8ff4f3a9471db41c033b415a0a90b69404.svg",style:{width:"27rem",height:"4.35015rem"}})),r.createElement("p",null,"Asking a computer to now compose the ",r.createElement("i",null,"C(t)")," function for us (and to expand it to a readable form of simple terms) gives us this rather overly complicated set of arithmetic expressions:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/92350c75ebfba40fcab87d091f1f46e4c01be720.svg",style:{width:"29.62485rem",height:"7.12485rem"}})),r.createElement("p",null,"That is... unwieldy. So, we note that there are a lot of terms that involve multiplications involving x1, y1, and y4, which would all disappear if we axis-align our curve, which is why aligning is a great idea.")),r.createElement("p",null,"Aligning our curve so that three of the eight coefficients become zero, we end up with the following simple term function for ",r.createElement("i",null,"C(t)"),":"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/f61c01094f0de8ca4c7f26a229f0206d54b13930.svg",style:{width:"37.49985rem",height:"1.35rem"}})),r.createElement("p",null,"That's a lot easier to work with: we see a fair number of terms that we can compute and then cache, giving us the following simplification:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/43ea1c1ea2c15f37d7390c16dfc77834b461777e.svg",style:{width:"33.75rem",height:"5.54985rem"}})),r.createElement("p",null,"This is a plain quadratic curve, and we know how to solve ",r.createElement("i",null,"C(t) = 0"),"; we use the quadratic formula:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/368099657b735b0d17becbbe7be915e88e8c04c5.svg",style:{width:"30.675150000000002rem",height:"4.1998500000000005rem"}})),r.createElement("p",null,"We can easily compute this value ",r.createElement("em",null,"if")," the descriminator isn't a negative number (because we only want real roots, not complex roots), and ",r.createElement("em",null,"if")," v",r.createElement("sub",null,"1")," is not zero, because divisions by zero are rather useless."),r.createElement("p",null,"Taking that into account, we compute ",r.createElement("i",null,"t"),", we disregard any ",r.createElement("i",null,"t")," value that isn't in the Bézier interval [0,1], and we now know at which ",r.createElement("i",null,"t")," value(s) our curve will inflect."),r.createElement(i,{title:"Finding cubic Bézier curve inflections",setup:this.setupCubic,draw:this.draw}))}});e.exports=o},function(e,t,n){"use strict";var r=n(0),i=n(5),a=n(3),o=Math.min,s=Math.max,l=r.createClass({displayName:"Intersections",getDefaultProps:function(){return{title:"Intersections"}},setupLines:function(e){var t=new e.Bezier([50,50,150,110]),n=new e.Bezier([50,250,170,170]);e.setCurve(t,n)},drawLineIntersection:function(e,t){e.reset();var n=e.utils.lli4,r=n(t[0].points[0],t[0].points[1],t[1].points[0],t[1].points[1]),i=0;t.forEach(function(t){if(e.drawSkeleton(t),e.setColor("black"),r){var n=t.points,a=o(n[0].x,n[1].x),l=o(n[0].y,n[1].y),u=s(n[0].x,n[1].x),c=s(n[0].y,n[1].y);a<=r.x&&l<=r.y&&u>=r.x&&c>=r.y&&(e.setColor("#00FF00"),i++)}e.drawCurve(t)}),r&&(e.setColor(i<2?"red":"#00FF00"),e.drawCircle(r,3))},setupQuadratic:function(e){var t=e.getDefaultQuadratic(),n=new e.Bezier([15,250,220,20]);e.setCurve(t,n)},setupCubic:function(e){var t=new e.Bezier([100,240,30,60,210,230,160,30]),n=new e.Bezier([25,260,230,20]);e.setCurve(t,n)},draw:function(e,t){e.reset(),t.forEach(function(t){e.drawSkeleton(t),e.drawCurve(t)});var n=e.utils,r={p1:t[1].points[0],p2:t[1].points[1]},i=n.align(t[0].points,r),a=new e.Bezier(i),o=n.roots(a.points);o.forEach(function(n){var r=t[0].get(n);e.drawCircle(r,3),e.text("t = "+n,{x:r.x+5,y:r.y+10})})},render:function(){return r.createElement("section",null,r.createElement(a,this.props),r.createElement("p",null,"Let's look at some more things we will want to do with Bézier curves. Almost immediately after figuring out how to get bounding boxes to work, people tend to run into the problem that even though the minimal bounding box (based on rotation) is tight, it's not sufficient to perform true collision detection. It's a good first step to make sure there ",r.createElement("em",null,"might")," be a collision (if there is no bounding box overlap, there can't be one), but in order to do real collision detection we need to know whether or not there's an intersection on the actual curve."),r.createElement("p",null,"We'll do this in steps, because it's a bit of a journey to get to curve/curve intersection checking. First, let's start simple, by implementing a line-line intersection checker. While we can solve this the traditional calculus way (determine the functions for both lines, then compute the intersection by equating them and solving for two unknowns), linear algebra actually offers a nicer solution."),r.createElement("h3",null,"Line-line intersections"),r.createElement("p",{id:"intersection_ll"},"if we have two line segments with two coordinates each, segments A-B and C-D, we can find the intersection of the lines these segments are an intervals on by linear algebra, using the procedure outlined in this ",r.createElement("a",{href:"http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry2#line_line_intersection"},"top coder")," article. Of course, we need to make sure that the intersection isn't just on the lines our line segments lie on, but also on our line segments themselves, so after we find the intersection we need to verify it lies without the bounds of our original line segments."),r.createElement("p",null),r.createElement("p",null,"The following graphic implements this intersection detection, showing a red point for an intersection on the lines our segments lie on (thus being a virtual intersection point), and a green point for an intersection that lies on both segments (being a real intersection point)."),r.createElement(i,{preset:"simple",title:"Line/line intersections",setup:this.setupLines,draw:this.drawLineIntersection}),r.createElement("div",{className:"howtocode"},r.createElement("h3",null,"Implementing line-line intersections"),r.createElement("p",null,"Let's have a look at how to implement a line-line intersection checking function. The basics are covered in the article mentioned above, but sometimes you need more function signatures, because you might not want to call your function with eight distinct parameters. Maybe you're using point structs or the line. Let's get coding:"),r.createElement("pre",null,"lli8 = function(x1,y1,x2,y2,x3,y3,x4,y4):","\n"," var nx=(x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4),","\n"," ny=(x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4),","\n"," d=(x1-x2)*(y3-y4)-(y1-y2)*(x3-x4);","\n"," if d=0:","\n"," return false","\n"," return point(nx/d, ny/d)","\n","\n","lli4 = function(p1, p2, p3, p4):","\n"," var x1 = p1.x, y1 = p1.y,","\n"," x2 = p2.x, y2 = p2.y,","\n"," x3 = p3.x, y3 = p3.y,","\n"," x4 = p4.x, y4 = p4.y;","\n"," return lli8(x1,y1,x2,y2,x3,y3,x4,y4)","\n","\n","lli = function(line1, line2):","\n"," return lli4(line1.p1, line1.p2, line2.p1, line2.p2)")),r.createElement("h3",null,"What about curve-line intersections?"),r.createElement("p",null,"Curve/line intersection is more work, but we've already seen the techniques we need to use in order to perform it: first we translate/rotate both the line and curve together, in such a way that the line coincides with the x-axis. This will position the curve in a way that makes it cross the line at points where its y-function is zero. By doing this, the problem of finding intersections between a curve and a line has now become the problem of performing root finding on our translated/rotated curve, as we already covered in the section on finding extremities."),r.createElement(i,{preset:"simple",title:"Quadratic curve/line intersections",setup:this.setupQuadratic,draw:this.draw}),r.createElement(i,{preset:"simple",title:"Cubic curve/line intersections",setup:this.setupCubic,draw:this.draw}),r.createElement("p",null,"Curve/curve intersection, however, is more complicated. Since we have no straight line to align to, we can't simply align one of the curves and be left with a simple procedure. Instead, we'll need to apply two techniques we've not covered yet: de Casteljau's algorithm, and curve splitting."))}});e.exports=l},function(e,t,n){"use strict";var r=n(0),i=n(10),a=new i("en-GB"),o="introduction",s=r.createClass({displayName:"Introduction",getDefaultProps:function(){return{title:a.getTitle(o)}},drawQuadratic:function(e){var t=e.getDefaultQuadratic();e.setCurve(t)},drawCubic:function(e){var t=e.getDefaultCubic();e.setCurve(t)},drawCurve:function(e,t){e.reset(),e.drawSkeleton(t),e.drawCurve(t)},render:function(){return r.createElement("section",null,a.getContent(o,this))}});e.exports=s},function(e,t,n){"use strict";var r=n(0),i=n(10),a=new i("en-GB"),o="matrix",s=r.createClass({displayName:"Matrix",getDefaultProps:function(){return{title:a.getTitle(o)}},render:function(){return r.createElement("section",null,a.getContent(o,this))}});e.exports=s},function(e,t,n){"use strict";var r=n(0),i=n(10),a=new i("en-GB"),o="matrixsplit",s=r.createClass({displayName:"MatrixSplit",getDefaultProps:function(){return{title:a.getTitle(o)}},render:function(){return r.createElement("section",null,a.getContent(o,this))}});e.exports=s},function(e,t,n){"use strict";var r=n(0),i=n(5),a=n(3),o=Math.abs,s=r.createClass({displayName:"Moulding",getDefaultProps:function(){return{title:"Manipulating a curve"}},setupQuadratic:function(e){e.setPanelCount(3);var t=e.getDefaultQuadratic();t.points[2].x-=30,e.setCurve(t)},setupCubic:function(e){e.setPanelCount(3);var t=new e.Bezier([100,230,30,160,200,50,210,160]);t.points[2].y-=20,e.setCurve(t),e.lut=t.getLUT(100)},saveCurve:function(e,t){t.t&&(t.setCurve(t.newcurve),t.t=!1,t.redraw())},findTValue:function(e,t){var n=t.curve.on({x:e.offsetX,y:e.offsetY},7);return!(n<.05||n>.95)&&n},markQB:function(e,t){if(t.t=this.findTValue(e,t),t.t){var n=t.t,r=2*n,i=r*n-r,a=i+1,s=o(i/a),l=t.curve,u=t.A=l.points[1],c=t.B=l.get(n);t.C=t.utils.lli4(u,c,l.points[0],l.points[2]),t.ratio=s}},markCB:function(e,t){if(t.t=this.findTValue(e,t),t.t){var n=t.t,r=1-n,i=n*n*n,a=r*r*r,s=i+a,l=s-1,u=o(l/s),c=t.curve,h=c.hull(n),d=t.A=h[5],f=t.B=c.get(n);t.db=c.derivative(n),t.C=t.utils.lli4(d,f,c.points[0],c.points[3]),t.ratio=u}},drag:function(e,t){if(t.t){var n=t.newB={x:e.offsetX,y:e.offsetY};t.newA={x:n.x-(t.C.x-n.x)/t.ratio,y:n.y-(t.C.y-n.y)/t.ratio}}},dragQB:function(e,t){t.t&&(this.drag(e,t),t.update=[t.newA])},dragCB:function(e,t){if(t.t){this.drag(e,t);var n=t.curve,r=n.hull(t.t),i=t.B,a=r[7],o=r[8],s={x:a.x-i.x,y:a.y-i.y},l={x:o.x-i.x,y:o.y-i.y},u=n.points,c={x:t.newB.x+s.x,y:t.newB.y+s.y},h={x:t.newA.x-(t.newA.x-c.x)/(1-t.t),y:t.newA.y-(t.newA.y-c.y)/(1-t.t)},d={x:t.newB.x+l.x,y:t.newB.y+l.y},f={x:t.newA.x+(d.x-t.newA.x)/t.t,y:t.newA.y+(d.y-t.newA.y)/t.t},p={x:u[0].x+(h.x-u[0].x)/t.t,y:u[0].y+(h.y-u[0].y)/t.t},m={x:u[3].x-(u[3].x-f.x)/(1-t.t),y:u[3].y-(u[3].y-f.y)/(1-t.t)};t.p1=c,t.p2=d,t.sc1=h,t.sc2=f,t.nc1=p,t.nc2=m,t.update=[p,m]}},drawMould:function(e,t){e.reset(),e.drawSkeleton(t),e.drawCurve(t);var n=e.getPanelWidth(),r=e.getPanelHeight(),i={x:n,y:0},a=e.utils.round;if(e.setColor("black"),e.drawLine({x:0,y:0},{x:0,y:r},i),e.drawLine({x:n,y:0},{x:n,y:r},i),e.t){e.drawCircle(t.get(e.t),3),e.npts=[t.points[0]].concat(e.update).concat([t.points.slice(-1)[0]]),e.newcurve=new e.Bezier(e.npts),e.setColor("lightgrey"),e.drawCurve(e.newcurve);var o=e.drawHull(e.newcurve,e.t,i);if(e.drawLine(e.npts[0],e.npts.slice(-1)[0],i),e.drawLine(e.newA,e.newB,i),e.setColor("grey"),e.drawCircle(e.newA,3,i),e.setColor("blue"),e.drawCircle(e.B,3,i),e.drawCircle(e.C,3,i),e.drawCircle(e.newB,3,i),e.drawLine(e.B,e.C,i),e.drawLine(e.newB,e.C,i),e.setFill("black"),e.text("A'",e.newA,{x:i.x+7,y:i.y+1}),e.text("start",t.get(0),{x:i.x+7,y:i.y+1}),e.text("end",t.get(1),{x:i.x+7,y:i.y+1}),e.setFill("blue"),e.text("B'",e.newB,{x:i.x+7,y:i.y+1}),e.text("B, at t = "+a(e.t,2),e.B,{x:i.x+7,y:i.y+1}),e.text("C",e.C,{x:i.x+7,y:i.y+1}),3===t.order){var s=t.hull(e.t);e.drawLine(s[7],s[8],i),e.drawLine(o[7],o[8],i),e.drawCircle(o[7],3,i),e.drawCircle(o[8],3,i),e.text("e1",o[7],{x:i.x+7,y:i.y+1}),e.text("e2",o[8],{x:i.x+7,y:i.y+1})}i.x+=n,e.setColor("lightgrey"),e.drawSkeleton(e.newcurve,i),e.setColor("black"),e.drawCurve(e.newcurve,i)}else i.x+=n,e.drawCurve(t,i)},render:function(){return r.createElement("section",null,r.createElement(a,this.props),r.createElement("p",null,'Armed with knowledge of the "ABC" relation, we can now update a curve interactively, by letting people click anywhere on the curve, find the ',r.createElement("em",null,"t"),'-value matching that coordinate, and then letting them drag that point around. With every drag update we\'ll have a new point "B", which we can combine with the fixed point "C" to find our new point A. Once we have those, we can reconstruct the de Casteljau skeleton and thus construct a new curve with the same start/end points as the original curve, passing through the user-selected point B, with correct new control points.'),r.createElement(i,{preset:"moulding",title:"Moulding a quadratic Bézier curve",setup:this.setupQuadratic,draw:this.drawMould,onClick:this.placeMouldPoint,onMouseDown:this.markQB,onMouseDrag:this.dragQB,onMouseUp:this.saveCurve}),r.createElement("p",null,r.createElement("strong",null,"Click-dragging the curve itself")," shows what we're using to compute the new coordinates: while dragging you will see the original points B and its corresponding ",r.createElement("i",null,"t"),"-value, the original point C for that ",r.createElement("i",null,"t"),"-value, as well as the new point B' based on the mouse cursor. Since we know the ",r.createElement("i",null,"t"),"-value for this configuration, we can compute the ABC ratio for this configuration, and we know that our new point A' should like at a distance:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/e361e1235c94bbe87e95834c7fcfb6ab96e028b9.svg",style:{width:"15.600150000000001rem",height:"2.3998500000000003rem"}})),r.createElement("p",null,"For quadratic curves, this means we're done, since the new point A' is equivalent to the new quadratic control point. For cubic curves, we need to do a little more work:"),r.createElement(i,{preset:"moulding",title:"Moulding a cubic Bézier curve",setup:this.setupCubic,draw:this.drawMould,onClick:this.placeMouldPoint,onMouseDown:this.markCB,onMouseDrag:this.dragCB,onMouseUp:this.saveCurve}),r.createElement("p",null,"To help understand what's going on, the cubic graphic shows the full de Casteljau construction \"hull\" when repositioning point B. We compute A` in exactly the same way as before, but we also record the final strut line that forms B in the original curve. Given A', B', and the endpoints e1 and e2 of the strut line relative to B', we can now compute where the new control points should be. Remember that B' lies on line e1--e2 at a distance ",r.createElement("i",null,"t"),", because that's how Bézier curves work. In the same manner, we know the distance A--e1 is only line-interval [0,t] of the full segment, and A--e2 is only line-interval [t,1], so constructing the new control points is fairly easy."),r.createElement("p",null,"First, we construct the one-level-of-de-Casteljau-up points:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/f813a7d607787329d242bfbfa28570c88c3e30f5.svg",style:{width:"9.975150000000001rem",height:"5.09985rem"}})),r.createElement("p",null,"And then we can compute the new control points:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/4ec5f4148752a3d332a922048700d2c71918342f.svg",style:{width:"11.700000000000001rem",height:"4.64985rem"}})),r.createElement("p",null,"And that's cubic curve manipulation."))}});e.exports=s},function(e,t,n){"use strict";var r=n(0),i=n(5),a=n(3),o=n(15),s=r.createClass({displayName:"Offsetting",statics:{keyHandlingOptions:{propName:"distance",values:{38:1,40:-1}}},getDefaultProps:function(){return{title:"Curve offsetting"}},setup:function(e,t){e.setCurve(t),e.distance=20},setupQuadratic:function(e){var t=e.getDefaultQuadratic();this.setup(e,t)},setupCubic:function(e){var t=e.getDefaultCubic();this.setup(e,t)},draw:function(e,t){e.reset(),e.drawSkeleton(t);var n=t.reduce();n.forEach(function(t){e.setRandomColor(),e.drawCurve(t),e.drawCircle(t.points[0],1)});var r=n.slice(-1)[0];e.drawPoint(r.points[3]||r.points[2]),e.setColor("red");var i=t.offset(e.distance);i.forEach(function(t){e.drawPoint(t.points[0]),e.drawCurve(t)}),r=i.slice(-1)[0],e.drawPoint(r.points[3]||r.points[2]),e.setColor("blue"),i=t.offset(-e.distance),i.forEach(function(t){e.drawPoint(t.points[0]),e.drawCurve(t)}),r=i.slice(-1)[0],e.drawPoint(r.points[3]||r.points[2])},render:function(){return r.createElement("section",null,r.createElement(a,this.props),r.createElement("p",null,"Perhaps you are like me, and you've been writing various small programs that use Bézier curves in some way or another, and at some point you make the step to implementing path extrusion. But you don't want to do it pixel based, you want to stay in the vector world. You find that extruding lines is relatively easy, and tracing outlines is coming along nicely (although junction caps and fillets are a bit of a hassle), and then decide to do things properly and add Bézier curves to the mix. Now you have a problem."),r.createElement("p",null,"Unlike lines, you can't simply extrude a Bézier curve by taking a copy and moving it around, because of the curvatures; rather than a uniform thickness you get an extrusion that looks too thin in places, if you're lucky, but more likely will self-intersect. The trick, then, is to scale the curve, rather than simply copying it. But how do you scale a Bézier curve?"),r.createElement("p",null,"Bottom line: ",r.createElement("strong",null,"you can't"),". So you cheat. We're not going to do true curve scaling, or rather curve offsetting, because that's impossible. Instead we're going to try to generate 'looks good enough' offset curves."),r.createElement("div",{className:"note"},r.createElement("h2",null,'"What do you mean, you can\'t. Prove it."'),r.createElement("p",null,'First off, when I say "you can\'t" what I really mean is "you can\'t offset a Bézier curve with another Bézier curve". not even by using a really high order curve. You can find the function that describes the offset curve, but it won\'t be a polynomial, and as such it cannot be represented as a Bézier curve, which',r.createElement("strong",null,"has")," to be a polynomial. Let's look at why this is:"),r.createElement("p",null,"From a mathematical point of view, an offset curve ",r.createElement("i",null,"O(t)")," is a curve such that, given our original curve",r.createElement("i",null,"B(t)"),", any point on ",r.createElement("i",null,"O(t)")," is a fixed distance ",r.createElement("i",null,"d")," away from coordinate ",r.createElement("i",null,"B(t)"),". So let's math that:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/3aff5cef0028337bbb48ae64ad30000c4d5e238f.svg",style:{width:"7.275150000000001rem",height:"1.125rem"}})),r.createElement("p",null,"However, we're working in 2D, and ",r.createElement("i",null,"d")," is a single value, so we want to turn it into a vector. If we want a point distance ",r.createElement("i",null,"d"),' "away" from the curve ',r.createElement("i",null,"B(t)")," then what we really mean is that we want a point at ",r.createElement("i",null,"d"),' times the "normal vector" from point ',r.createElement("i",null,"B(t)"),', where the "normal" is a vector that runs perpendicular ("at a right angle") to the tangent at ',r.createElement("i",null,"B(t)"),". Easy enough:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/2cf48e2f8525258a3fa0fe4f10ec2acef67104b3.svg",style:{width:"10.125rem",height:"1.125rem"}})),r.createElement("p",null,"Now this still isn't very useful unless we know what the formula for ",r.createElement("i",null,"N(t)")," is, so let's find out.",r.createElement("i",null,"N(t)")," runs perpendicular to the original curve tangent, and we know that the tangent is simply",r.createElement("i",null,"B'(t)"),", so we could just rotate that 90 degrees and be done with it. However, we need to ensure that ",r.createElement("i",null,"N(t)")," has the same magnitude for every ",r.createElement("i",null,"t"),", or the offset curve won't be at a uniform distance, thus not being an offset curve at all. The easiest way to guarantee this is to make sure",r.createElement("i",null,"N(t)")," always has length 1, which we can achieve by dividing ",r.createElement("i",null,"B'(t)")," by its magnitude:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/664fba98ea17b358941b579115bf063edf87ae17.svg",style:{width:"9.450000000000001rem",height:"3.15rem"}})),r.createElement("p",null,"Determining the length requires computing an arc length, and this is where things get Tricky with a capital T. First off, to compute arc length from some start ",r.createElement("i",null,"a")," to end ",r.createElement("i",null,"b"),', we must use the formula we saw earlier. Noting that "length" is usually denoted with double vertical bars:'),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/f6d8c2965b02363e092acb00bbc1398cfbb170a4.svg",style:{width:"12.45015rem",height:"2.6248500000000003rem"}})),r.createElement("p",null,"So if we want the length of the tangent, we plug in ",r.createElement("i",null,"B'(t)"),", with ",r.createElement("i",null,"t = 0")," as start and",r.createElement("i",null,"t = 1")," as end:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/1f024282044316a9e4b3de2c855d2ceb96aff056.svg",style:{width:"15.150150000000002rem",height:"2.6248500000000003rem"}})),r.createElement("p",null,"And that's where things go wrong. It doesn't even really matter what the second derivative for ",r.createElement("i",null,"B(t)"),"is, that square root is screwing everything up, because it turns our nice polynomials into things that are no longer polynomials."),r.createElement("p",null,"There is a small class of polynomials where the square root is also a polynomial, but they're utterly useless to us: any polynomial with unweighted binomial coefficients has a square root that is also a polynomial. Now, you might think that Bézier curves are just fine because they do, but they don't; remember that only the ",r.createElement("strong",null,"base")," function has binomial coefficients. That's before we factor in our coordinates, which turn it into a non-binomial polygon. The only way to make sure the functions stay binomial is to make all our coordinates have the same value. And that's not a curve, that's a point. We can already create offset curves for points, we call them circles, and they have much simpler functions than Bézier curves."),r.createElement("p",null,"So, since the tangent length isn't a polynomial, the normalised tangent won't be a polynomial either, which means ",r.createElement("i",null,"N(t)")," won't be a polynomial, which means that ",r.createElement("i",null,"d")," times ",r.createElement("i",null,"N(t)")," won't be a polynomial, which means that, ultimately, ",r.createElement("i",null,"O(t)")," won't be a polynomial, which means that even if we can determine the function for ",r.createElement("i",null,"O(t)")," just fine (and that's far from trivial!), it simply cannot be represented as a Bézier curve."),r.createElement("p",null,"And that's one reason why Bézier curves are tricky: there are actually a ",r.createElement("i",null,"lot")," of curves that cannot be represent as a Bézier curve at all. They can't even model their own offset curves. They're weird that way. So how do all those other programs do it? Well, much like we're about to do, they cheat. We're going to approximate an offset curve in a way that will look relatively close to what the real offset curve would look like, if we could compute it.")),r.createElement("p",null,'So, you cannot offset a Bézier curve perfectly with another Bézier curve, no matter how high-order you make that other Bézier curve. However, we can chop up a curve into "safe" sub-curves (where safe means that all the control points are always on a single side of the baseline, and the midpoint of the curve at ',r.createElement("i",null,"t=0.5")," is roughly in the centre of the polygon defined by the curve coordinates) and then point-scale those sub-curves with respect to the curve's scaling origin (which is the intersection of the point normals at the start and end points)."),r.createElement("p",null,"A good way to do this reduction is to first find the curve's extreme points, as explained in the earlier section on curve extremities, and use these as initial splitting points. After this initial split, we can check each individual segment to see if it's \"safe enough\" based on where the center of the curve is. If the on-curve point for ",r.createElement("i",null,"t=0.5")," is too far off from the center, we simply split the segment down the middle. Generally this is more than enough to end up with safe segments."),r.createElement("p",null,"The following graphics show off curve offsetting, and you can use your up and down arrow keys to control the distance at which the curve gets offset. The curve first gets reduced to safe segments, each of which is then offset at the desired distance. Especially for simple curves, particularly easily set up for quadratic curves, no reduction is necessary, but the more twisty the curve gets, the more the curve needs to be reduced in order to get segments that can safely be scaled."),r.createElement(i,{
preset:"simple",title:"Offsetting a quadratic Bézier curve",setup:this.setupQuadratic,draw:this.draw,onKeyDown:this.props.onKeyDown}),r.createElement(i,{preset:"simple",title:"Offsetting a cubic Bézier curve",setup:this.setupCubic,draw:this.draw,onKeyDown:this.props.onKeyDown}),r.createElement("p",null,"You may notice that this may still lead to small 'jumps' in the sub-curves when moving the curve around. This is caused by the fact that we're still performing a naive form of offsetting, moving the control points the same distance as the start and end points. If the curve is large enough, this may still lead to incorrect offsets."))}});e.exports=o(s)},function(e,t,n){"use strict";var r=n(0),i=n(5),a=n(3),o=Math.abs,s=r.createClass({displayName:"PointCurves",getDefaultProps:function(){return{title:"Creating a curve from three points"}},setup:function(e){e.lpts=[{x:56,y:153},{x:144,y:83},{x:188,y:185}]},onClick:function(e,t){3==t.lpts.length&&(t.lpts=[]),t.lpts.push({x:e.offsetX,y:e.offsetY}),t.redraw()},getQRatio:function(e){var t=2*e,n=t*e-t,r=n+1;return o(n/r)},getCRatio:function(e){var t=1-e,n=e*e*e,r=t*t*t,i=n+r,a=i-1;return o(a/i)},drawQuadratic:function(e,t){var n=["start","t=0.5","end"];if(e.reset(),e.setColor("lightblue"),e.drawGrid(10,10),e.setFill("black"),e.setColor("black"),e.lpts.forEach(function(t,r){e.drawCircle(t,3),e.text(n[r],t,{x:5,y:2})}),3===e.lpts.length){var r=e.lpts[0],i=e.lpts[2],a=e.lpts[1],o={x:(r.x+i.x)/2,y:(r.y+i.y)/2};e.setColor("blue"),e.drawLine(r,i),e.drawLine(a,o),e.drawCircle(o,3);var s=this.getQRatio(.5),l={x:a.x+(a.x-o.x)/s,y:a.y+(a.y-o.y)/s};t=new e.Bezier([r,l,i]),e.setColor("lightgrey"),e.drawLine(l,a),e.drawLine(l,r),e.drawLine(l,i),e.setColor("black"),e.drawCircle(l,1),e.drawCurve(t)}},drawCubic:function(e,t){var n=["start","t=0.5","end"];if(e.reset(),e.setFill("black"),e.setColor("black"),e.lpts.forEach(function(t,r){e.drawCircle(t,3),e.text(n[r],t,{x:5,y:2})}),e.setColor("lightblue"),e.drawGrid(10,10),3===e.lpts.length){var r=e.lpts[0],i=e.lpts[2],a=e.lpts[1],o={x:(r.x+i.x)/2,y:(r.y+i.y)/2};e.setColor("blue"),e.drawLine(r,i),e.drawLine(a,o),e.drawCircle(o,1);var s=this.getCRatio(.5),l={x:a.x+(a.x-o.x)/s,y:a.y+(a.y-o.y)/s},u=e.utils.dist(r,i),c=u/8,h=e.utils.dist(a,o),d=4,f=c+h/d,p=f*(i.x-r.x)/u,m=f*(i.y-r.y)/u,g={x:a.x-p,y:a.y-m},v={x:a.x+p,y:a.y+m},y={x:l.x+2*(g.x-l.x),y:l.y+2*(g.y-l.y)},w={x:l.x+2*(v.x-l.x),y:l.y+2*(v.y-l.y)},b={x:r.x+2*(y.x-r.x),y:r.y+2*(y.y-r.y)},_={x:i.x+2*(w.x-i.x),y:i.y+2*(w.y-i.y)};t=new e.Bezier([r,b,_,i]),e.drawLine(g,v),e.setColor("lightgrey"),e.drawLine(l,o),e.drawLine(l,y),e.drawLine(l,w),e.drawLine(r,b),e.drawLine(i,_),e.drawLine(b,_),e.setColor("black"),e.drawCircle(l,1),e.drawCircle(b,1),e.drawCircle(_,1),e.drawCurve(t)}},render:function(){return r.createElement("section",null,r.createElement(a,this.props),r.createElement("p",null,"Given the preceding section on curve manipulation, we can also generate quadratic and cubic curves from any three points. However, unlike circle-fitting, which requires just three points, Bézier curve fitting requires three points, as well as a ",r.createElement("i",null,"t")," value, so we can figure out where point 'C' needs to be."),r.createElement("p",null,"The following graphic lets you place three points, and will use the preceding sections on the ABC ratio and curve construction to form a quadratic curve through them. You can move the points you've placed around by click-dragging, or try a new curve by drawing new points with pure clicks. (There's some freedom here, so for illustrative purposes we clamped ",r.createElement("i",null,"t")," to simply be 0.5, lets us bypass some maths, since a ",r.createElement("i",null,"t")," value of 0.5 always puts C in the middle of the start--end line segment)"),r.createElement(i,{preset:"generate",title:"Fitting a quadratic Bézier curve",setup:this.setup,draw:this.drawQuadratic,onClick:this.onClick}),r.createElement("p",null,'For cubic curves we also need some values to construct the "de Casteljau line through B" with, and that gives us quite a bit of choice. Since we\'ve clamped ',r.createElement("i",null,"t"),' to 0.5, we\'ll set up a line through B parallel to the line start--end, with a length that is proportional to the length of the line B--C: the further away from the baseline B is, the wider its construction line will be, and so the more "bulby" the curve will look. This still gives us some freedom in terms of exactly how to scale the length of the construction line as we move B closer or further away from the baseline, so I simply picked some values that sort-of-kind-of look right in that if a circle through (start,B,end) forms a perfect hemisphere, the cubic curve constructed forms something close to a hemisphere, too, and if the points lie on a line, then the curve constructed has the control points very close to B, while still lying between B and the correct curve end point:'),r.createElement(i,{preset:"generate",title:"Fitting a cubic Bézier curve",setup:this.setup,draw:this.drawCubic,onClick:this.onClick}),r.createElement("p",null,'In each graphic, the blue parts are the values that we "just have" simply by setting up our three points, combined with our decision on which ',r.createElement("i",null,"t")," value to use (and construction line orientation and length for cubic curves). There are of course many ways to determine a combination of ",r.createElement("i",null,"t"),' and tangent values that lead to a more "æsthetic" curve, but this will be left as an exercise to the reader, since there are many, and æsthetics are often quite personal.'))}});e.exports=s},function(e,t,n){"use strict";var r=n(0),i=n(5),a=n(3),o=r.createClass({displayName:"PointVectors",getDefaultProps:function(){return{title:"Tangents and normals"}},setupQuadratic:function(e){var t=e.getDefaultQuadratic();e.setCurve(t)},setupCubic:function(e){var t=e.getDefaultCubic();e.setCurve(t)},draw:function(e,t){e.reset(),e.drawSkeleton(t);var n,r,i,a,o,s,l=20;for(n=0;n<=10;n++)r=n/10,i=t.get(r),a=t.derivative(r),s=Math.sqrt(a.x*a.x+a.y*a.y),a={x:a.x/s,y:a.y/s},o=t.normal(r),e.setColor("blue"),e.drawLine(i,{x:i.x+a.x*l,y:i.y+a.y*l}),e.setColor("red"),e.drawLine(i,{x:i.x+o.x*l,y:i.y+o.y*l}),e.setColor("black"),e.drawCircle(i,3)},render:function(){return r.createElement("section",null,r.createElement(a,this.props),r.createElement("p",null,'If you want to move objects along a curve, or "away from" a curve, the two vectors you\'re most interested in are the tangent vector and normal vector for curve points. These are actually really easy to find. For moving, and orienting, along a curve we use the tangent, which indicates the direction travel at specific points, and is literally just the first derivative of our curve:'),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/2271ae26977a681a1695d14ea8255564e716916e.svg",style:{width:"10.35rem",height:"2.77515rem"}})),r.createElement("p",null,"This gives us the directional vector we want. We can normalize it to give us uniform directional vectors (having a length of 1.0) at each point, and then do whatever it is we want to do based on those directions:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/3cb2c4f5806142e83c66e1312520d0783d15201c.svg",style:{width:"17.62515rem",height:"2.025rem"}})),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/72826b8f5053c299dbb2082678191e3564bb50a6.svg",style:{width:"20.62485rem",height:"4.7250000000000005rem"}})),r.createElement("p",null,"The tangent is very useful for moving along a line, but what if we want to move away from the curve instead, perpendicular to the curve at some point ",r.createElement("i",null,"t"),'? In that case we want the "normal" vector. This vector runs at a right angle to the direction of the curve, and is typically of length 1.0, so all we have to do is rotate the normalized directional vector and we\'re done:'),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/6cb29c325e059e236343bdd448c149ecc6d8795f.svg",style:{width:"22.800150000000002rem",height:"4.57515rem"}})),r.createElement("div",{className:"note"},r.createElement("p",null,'Rotating coordinates is actually very easy, if you know the rule for it. You might find it explained as "applying a ',r.createElement("a",{href:"https://en.wikipedia.org/wiki/Rotation_matrix"},"rotation matrix"),'", which is what we\'ll look at here, too. Essentially, the idea is to take the circles over which we can rotate, and simply "sliding the coordinates" over those circles by the desired angle. If we want a quarter circle turn, we take the coordinate, slide it along the cirle by a quarter turn, and done.'),r.createElement("p",null,"To turn any point ",r.createElement("i",null,"(x,y)")," into a rotated point ",r.createElement("i",null,"(x',y')")," (over 0,0) by some angle φ, we apply this nicely easy computation:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d3932ac925ad9f238029d888dc5432f6678f6491.svg",style:{width:"12.225150000000001rem",height:"2.84985rem"}})),r.createElement("p",null,'Which is the "long" version of the following matrix transformation:'),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/7297632eb150a8f5f37178612f71e5d0f2c367b1.svg",style:{width:"15.150150000000002rem",height:"2.77515rem"}})),r.createElement("p",null,"And that's all we need to rotate any coordinate. Note that for quarter, half and three quarter turns these functions become even easier, since ",r.createElement("i",null,"sin")," and",r.createElement("i",null,"cos")," for these angles are, respectively: 0 and 1, -1 and 0, and 0 and -1."),r.createElement("p",null,"But ",r.createElement("strong",null,r.createElement("em",null,"why"))," does this work? Why this matrix multiplication?",r.createElement("a",{href:"http://en.wikipedia.org/wiki/Rotation_matrix#Decomposition_into_shears"},"wikipedia"),"(Technically, Thomas Herter and Klaus Lott) tells us that a rotation matrix can be treated as a sequence of three (elementary) shear operations. When we combine this into a single matrix operation (because all matrix multiplications can be collapsed), we get the matrix that you see above.",r.createElement("a",{href:"http://datagenetics.com/blog/august32013/index.html"},"DataGenetics")," have an excellent article about this very thing: it's really quite cool, and I strongly recommend taking a quick break from this primer to read that article.")),r.createElement("p",null,"The following two graphics show the tangent and normal along a quadratic and cubic curve, with the direction vector coloured blue, and the normal vector coloured red (the markers are spaced out evenly as ",r.createElement("i",null,"t"),"-intervals, not spaced equidistant)."),r.createElement("div",{className:"figure"},r.createElement(i,{preset:"simple",title:"Quadratic Bézier tangents and normals",inline:!0,setup:this.setupQuadratic,draw:this.draw}),r.createElement(i,{preset:"simple",title:"Cubic Bézier tangents and normals",inline:!0,setup:this.setupCubic,draw:this.draw})))}});e.exports=o},function(e,t,n){"use strict";var r=n(0),i=n(5),a=n(3),o=Math.atan2,s=Math.sqrt,l=Math.sin,u=Math.cos,c=r.createClass({displayName:"PolyBezier",getDefaultProps:function(){return{title:"Forming poly-Bézier curves"}},setupQuadratic:function(e){var t=e.getPanelWidth(),n=e.getPanelHeight(),r=t/2,i=n/2,a=40,o=[{x:r,y:a},{x:t-a,y:a},{x:t-a,y:i},{x:t-a,y:n-a},{x:r,y:n-a},{x:a,y:n-a},{x:a,y:i},{x:a,y:a}];e.lpts=o},setupCubic:function(e){var t=e.getPanelWidth(),n=e.getPanelHeight(),r=t/2,i=n/2,a=40,o=(t-2*a)/2,s=.55228,l=s*o,u=[{x:r,y:a},{x:r+l,y:a},{x:t-a,y:i-l},{x:t-a,y:i},{x:t-a,y:i+l},{x:r+l,y:n-a},{x:r,y:n-a},{x:r-l,y:n-a},{x:a,y:i+l},{x:a,y:i},{x:a,y:i-l},{x:r-l,y:a}];e.lpts=u},movePointsQuadraticLD:function(e,t){for(var n,r,i,a=1;a<4;a++)n=t+(2*a-2)+e.lpts.length,n=e.lpts[n%e.lpts.length],r=t+(2*a-1),r=e.lpts[r%e.lpts.length],i=t+2*a,i=e.lpts[i%e.lpts.length],i.x=r.x+(r.x-n.x),i.y=r.y+(r.y-n.y);i=t+6,i=e.lpts[i%e.lpts.length],e.problem=i},movePointsCubicLD:function(e,t){var n,r;t%3===1?(r=t-1,r+=r<0?e.lpts.length:0,n=t-2,n+=n<0?e.lpts.length:0):(r=(t+1)%e.lpts.length,n=(t+2)%e.lpts.length),r=e.lpts[r],n=e.lpts[n],n.x=r.x+(r.x-e.mp.x),n.y=r.y+(r.y-e.mp.y)},linkDerivatives:function(e,t){if(t.mp){var n=8===t.lpts.length,r=t.mp_idx;n?r%2!==0&&this.movePointsQuadraticLD(t,r):r%3!==0&&this.movePointsCubicLD(t,r)}},movePointsQuadraticDirOnly:function(e,t){var n,r,i;[-1,1].forEach(function(a){n=e.mp,r=t+a+e.lpts.length,r=e.lpts[r%e.lpts.length],i=t+2*a+e.lpts.length,i=e.lpts[i%e.lpts.length];var c=o(r.y-n.y,r.x-n.x),h=i.x-r.x,d=i.y-r.y,f=s(h*h+d*d);i.x=r.x+f*u(c),i.y=r.y+f*l(c)}),i=t+4,i=e.lpts[i%e.lpts.length],e.problem=i},movePointsCubicDirOnly:function(e,t){var n,r;t%3===1?(r=t-1,r+=r<0?e.lpts.length:0,n=t-2,n+=n<0?e.lpts.length:0):(r=(t+1)%e.lpts.length,n=(t+2)%e.lpts.length),r=e.lpts[r],n=e.lpts[n];var i=o(r.y-e.mp.y,r.x-e.mp.x),a=n.x-r.x,c=n.y-r.y,h=s(a*a+c*c);n.x=r.x+h*u(i),n.y=r.y+h*l(i)},linkDirection:function(e,t){if(t.mp){var n=8===t.lpts.length,r=t.mp_idx;n?r%2!==0&&this.movePointsQuadraticDirOnly(t,r):r%3!==0&&this.movePointsCubicDirOnly(t,r)}},bufferPoints:function(e,t){t.bpts=JSON.parse(JSON.stringify(t.lpts))},moveQuadraticPoint:function(e,t){this.moveCubicPoint(e,t),[-1,1].forEach(function(n){var r=t-n+e.lpts.length;r=e.lpts[r%e.lpts.length];var i=t-2*n+e.lpts.length;i=e.lpts[i%e.lpts.length];var a=t-3*n+e.lpts.length;a=e.lpts[a%e.lpts.length];var c=o(i.y-r.y,i.x-r.x),h=a.x-i.x,d=a.y-i.y,f=s(h*h+d*d);a.x=i.x+f*u(c),a.y=i.y+f*l(c)});var n=t+4;n=e.lpts[n%e.lpts.length],e.problem=n},moveCubicPoint:function(e,t){var n=e.bpts[t],r=e.lpts[t],i=r.x-n.x,a=r.y-n.y,o=e.lpts.length,s=t-1+o,l=t+1,u=e.bpts[s%o],c=e.bpts[l%o],h=e.lpts[s%o],d=e.lpts[l%o];return h.x=u.x+i,h.y=u.y+a,d.x=c.x+i,d.y=c.y+a,{x:i,y:a}},modelCurve:function(e,t){if(t.mp){var n=8===t.lpts.length,r=t.mp_idx;n?r%2!==0?this.movePointsQuadraticDirOnly(t,r):this.moveQuadraticPoint(t,r):r%3!==0?this.movePointsCubicDirOnly(t,r):this.moveCubicPoint(t,r)}},draw:function(e,t){e.reset();var n=e.lpts,r=8===n.length,i=r?new e.Bezier(n[0],n[1],n[2]):new e.Bezier(n[0],n[1],n[2],n[3]);e.drawSkeleton(i,!1,!0),e.drawCurve(i);var a=r?new e.Bezier(n[2],n[3],n[4]):new e.Bezier(n[3],n[4],n[5],n[6]);e.drawSkeleton(a,!1,!0),e.drawCurve(a);var o=r?new e.Bezier(n[4],n[5],n[6]):new e.Bezier(n[6],n[7],n[8],n[9]);e.drawSkeleton(o,!1,!0),e.drawCurve(o);var s=r?new e.Bezier(n[6],n[7],n[0]):new e.Bezier(n[9],n[10],n[11],n[0]);e.drawSkeleton(s,!1,!0),e.drawCurve(s),e.problem&&(e.setColor("red"),e.drawCircle(e.problem,5))},render:function(){return r.createElement("section",null,r.createElement(a,this.props),r.createElement("p",null,"Much like lines can be chained together to form polygons, Bézier curves can be chained together to form poly-Béziers, and the only trick required is to make sure that:"),r.createElement("ol",null,r.createElement("li",null,"the end point of each section is the starting point of the following section, and"),r.createElement("li",null,"the derivatives across that dual point line up.")),r.createElement("p",null,"Unless, of course, you want discontinuities; then you don't even need 2."),r.createElement("p",null,"We'll cover three forms of poly-Bézier curves in this section. First, we'll look at the kind that just follows point 1. where the end point of a segment is the same point as the start point of the next segment. This leads to poly-Béziers that are pretty hard to work with, but they're the easiest to implement:"),r.createElement(i,{preset:"poly",title:"Unlinked quadratic poly-Bézier",setup:this.setupQuadratic,draw:this.draw}),r.createElement(i,{preset:"poly",title:"Unlinked cubic poly-Bézier",setup:this.setupCubic,draw:this.draw}),r.createElement("p",null,'Dragging the control points around only affects the curve segments that the control point belongs to, and moving an on-curve point leaves the control points where they are, which is not the most useful for practical modelling purposes. So, let\'s add in the logic we need to make things a little better. We\'ll start by linking up control points by ensuring that the "incoming" derivative at an on-curve point is the same as it\'s "outgoing" derivative:'),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/37740bb1a0b7b1ff48bf3454e52295fc717cacbb.svg",style:{width:"8.400150000000002rem",height:"1.27485rem"}})),r.createElement("p",null,"We can effect this quite easily, because we know that the vector from a curve's last control point to its last on-curve point is equal to the derivative vector. If we want to ensure that the first control point of the next curve matches that, all we have to do is mirror that last control point through the last on-curve point. And mirroring any point A through any point B is really simple:"),r.createElement("p",null,r.createElement("img",{className:"LaTeX SVG",src:"images/latex/ce6e3939608c4ed0598107b06543c2301b91bb7f.svg",style:{width:"21.97485rem",height:"2.7rem"}})),r.createElement("p",null,"So let's implement that and see what it gets us. The following two graphics show a quadratic and a cubic poly-Bézier curve again, but this time moving the control points around moves others, too. However, you might see something unexpected going on for quadratic curves..."),r.createElement(i,{preset:"poly",title:"Loosely connected quadratic poly-Bézier",setup:this.setupQuadratic,draw:this.draw,onMouseMove:this.linkDerivatives}),r.createElement(i,{preset:"poly",title:"Loosely connected cubic poly-Bézier",setup:this.setupCubic,draw:this.draw,onMouseMove:this.linkDerivatives}),r.createElement("p",null,"As you can see, quadratic curves are particularly ill-suited for poly-Bézier curves, as all the control points are effectively linked. Move one of them, and you move all of them. Not only that, but if we move the on-curve points, it's possible to get a situation where a control point's positions is different depending on whether it's the reflection of its left or right neighbouring control point: we can't even form a proper rule-conforming curve! This means that we cannot use quadratic poly-Béziers for anything other than really, really simple shapes. And even then, they're probably the wrong choice. Cubic curves are pretty decent, but the fact that the derivatives are linked means we can't manipulate curves as well as we might if we relaxed the constraints a little."),r.createElement("p",null,"So: let's relax the requirement a little."),r.createElement("p",null,"We can change the constraint so that we still preserve the ",r.createElement("em",null,"angle")," of the derivatives across sections (so transitions from one section to the next will still look natural), but give up the requirement that they should also have the same ",r.createElement("em",null,"vector length"),". Doing so will give us a much more useful kind of poly-Bézier curve:"),r.createElement(i,{preset:"poly",title:"Loosely connected quadratic poly-Bézier",setup:this.setupQuadratic,draw:this.draw,onMouseMove:this.linkDirection}),r.createElement(i,{preset:"poly",title:"Loosely connected cubic poly-Bézier",setup:this.setupCubic,draw:this.draw,onMouseMove:this.linkDirection}),r.createElement("p",null,"Cubic curves are now better behaved when it comes to dragging control points around, but the quadratic poly-Bézier still has the problem that moving one control points will move the control points and may ending up defining \"the next\" control point in a way that doesn't work. Quadratic curves really aren't very useful to work with..."),r.createElement("p",null,"Finally, we also want to make sure that moving the on-curve coordinates preserves the relative positions of the associated control points. With that, we get to the kind of curve control that you might be familiar with from applications like Photoshop, Inkscape, Blender, etc."),r.createElement(i,{preset:"poly",title:"Loosely connected quadratic poly-Bézier",setup:this.setupQuadratic,draw:this.draw,onMouseDown:this.bufferPoints,onMouseMove:this.modelCurve}),r.createElement(i,{preset:"poly",title:"Loosely connected cubic poly-Bézier",setup:this.setupCubic,draw:this.draw,onMouseDown:this.bufferPoints,onMouseMove:this.modelCurve}),r.createElement("p",null,'Again, we see that cubic curves are now rather nice to work with, but quadratic curves have a new, very serious problem: we can move an on-curve point in such a way that we can\'t compute what needs to "happen next". Move the top point down, below the left and right points, for instance. There is no way to preserve correct control points without a kink at the bottom point. Quadratic curves: just not that good...'),r.createElement("p",null,'A final improvement is to offer fine-level control over which points behave which, so that you can have "kinks" or individually controlled segments when you need them, with nicely well-behaved curves for the rest of the path. Implementing that, is left as an excercise for the reader.'))}});e.exports=c},function(e,t,n){"use strict";var r=n(0),i=n(10),a=new i("en-GB"),o="preface",s=r.createClass({displayName:"Preface",getDefaultProps:function(){return{title:a.getTitle(o)}},render:function(){return r.createElement("section",null,a.getContent(o,this))}});e.exports=s},function(e,t,n){"use strict";var r=n(0),i=n(5),a=n(3),o=r.createClass({displayName:"Projections",getDefaultProps:function(){return{title:"Projecting a point onto a Bézier curve"}},setup:function(e){e.setSize(320,320);var t=new e.Bezier([{x:248,y:188},{x:218,y:294},{x:45,y:290},{x:12,y:236},{x:14,y:82},{x:186,y:177},{x:221,y:90},{x:18,y:156},{x:34,y:57},{x:198,y:18}]);e.setCurve(t),e._lut=t.getLUT()},findClosest:function(e,t,n){var r,i,a=e.length,o=n(e[0],t),s=0;for(r=1;r1;){for(var o=0;of){r--;break}r<0&&(r=0),r===c.length&&(r=c.length-1),d.push(c[r])}for(n=0;n0;o-=e.step)a=o/100,a>1||(e.setRandomColor(),n={x:d.x+a*(f.x-d.x),y:d.y+a*(f.y-d.y)},r={x:f.x+a*(p.x-f.x),y:f.y+a*(p.y-f.y)},i={x:n.x+a*(r.x-n.x),y:n.y+a*(r.y-n.y)},m={x:0,y:0},e.drawCircle(n,3,m),e.drawCircle(r,3,m),e.setWeight(.5),e.drawLine(n,r,m),e.setWeight(1.5),e.drawLine(d,n,m),e.drawLine(f,r,m),e.setWeight(1),m.x+=c,e.drawCircle(n,3,m),e.drawCircle(r,3,m),e.setWeight(.5),e.drawLine(n,r,m),e.setWeight(1.5),e.drawLine(n,i,m),e.setWeight(1),e.drawCircle(i,3,m),m.x+=c,e.drawCircle(i,3,m),e.text(o+"%, or t = "+e.utils.round(a,2),{x:i.x+10+m.x,y:i.y+10+m.y}))},values:{38:1,40:-1},onKeyDown:function(e,t){var n=this.values[e.keyCode];n&&(e.preventDefault(),t.step+=n,t.step<1&&(t.step=1))},render:function(){return r.createElement("section",null,a.getContent(o,this))}});e.exports=s},function(e,t,n){"use strict";var r=n(0),i=n(5),a=n(3);e.exports={preface:{title:"Preface",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"preface",title:"Preface"}),r.createElement("p",null,"In order to draw things in 2D, we usually rely on lines, which typically get classified into two categories: straight lines, and curves. The first of these are as easy to draw as they are easy to make a computer draw. Give a computer the first and last point in the line, and BAM! straight line. No questions asked."),r.createElement("p",null,"Curves, however, are a much bigger problem. While we can draw curves with ridiculous ease freehand, computers are a bit handicapped in that they can't draw curves unless there is a mathematical function that describes how it should be drawn. In fact, they even need this for straight lines, but the function is ridiculously easy, so we tend to ignore that as far as computers are concerned, all lines are \"functions\", regardless of whether they're straight or curves. However, that does mean that we need to come up with fast-to-compute functions that lead to nice looking curves on a computer. There's a number of these, and in this article we'll focus on a particular function that has received quite a bit of attention, and is used in pretty much anything that can draw curves: \"Bézier\" curves"),r.createElement("p",null,"They're named after ",r.createElement("a",{href:"https://en.wikipedia.org/wiki/Pierre_B%C3%A9zier"},"Pierre Bézier"),', who is principally responsible for getting them known to the world as a curve well-suited for design work (working for Renault and publishing his investigations in 1962), although he was not the first, or only one, to "invent" these type of curves. One might be tempted to say that the mathematician ',r.createElement("a",{href:"https://en.wikipedia.org/wiki/Paul_de_Casteljau"},"Paul de Casteljau"),' was first, investigating the nature of these curves in 1959 while working at Citroën, coming up with a really elegant way of figuring out how to draw them. However, de Casteljau did not publish his work, making the question "who was first" hard to answer in any absolute sense. Or is it? Bézier curves are, at their core, "Bernstein polynomials", a family of mathematical functions investigated by ',r.createElement("a",{href:"https://en.wikipedia.org/wiki/Sergei_Natanovich_Bernstein"},"Sergei Natanovich Bernstein"),", with publications on them at least as far back as 1912. Anyway, that's mostly trivia, what you are more likely to care about is that these curves are handy: you can link up multiple Bézier curves so that the combination looks like a single curve. If you've ever drawn Photoshop \"paths\" or worked with vector drawing programs like Flash, Illustrator or nkscape, those curves you've been drawing are Bézier curves."),r.createElement("p",null,"So, what if you need to program them yourself? What are the pitfalls? How do you draw them? What are the bounding boxes, how do you determine intersections, how can you extrude a curve, in short: how do you do everything that you might want when you do with these curves? That's what this page is for. Prepare to be mathed!"),r.createElement("p",null,"—Pomax (or in the tweetworld, ",r.createElement("a",{href:"https://twitter.com/TheRealPomax"},"@TheRealPomax"),")"),r.createElement("div",{className:"note"},r.createElement("h2",{id:"note-virtually-all-b-zier-graphics-are-interactive-"},"Note: virtually all Bézier graphics are interactive."),r.createElement("p",null,"This page uses interactive examples, relying heavily on ",r.createElement("a",{href:"http://pomax.github.io/bezierjs"},"Bezier.js"),', as well as "real" maths (in LaTeX form) which is typeset using the most excellent ',r.createElement("a",{href:"http://MathJax.org"},"MathJax")," library. The page is generated offline as a React application, using Webpack, which has made adding \"view source\" options considerably more challenging. I'm still trying to figure out how to add them back in, but it didn't feel like it should hold up deploying this update compared to the previous years' version."),r.createElement("h2",{id:"this-book-is-open-source-"},"This book is open source."),r.createElement("p",null,"This book is an open source software project, and lives on two github repositorites. The first is ",r.createElement("a",{href:"https://github.com/pomax/bezierinfo"},"https://github.com/pomax/bezierinfo")," and is the purely-for-presentation version you are viewing right now. The other repository is ",r.createElement("a",{href:"https://github.com/pomax/BezierInfo-2"},"https://github.com/pomax/BezierInfo-2"),", which is the development version, housing all the html, javascript, and css. You can fork either of these, and pretty much do with them as you please, except for passing it off as your own work wholesale, of course =)"),r.createElement("h2",{id:"how-complicated-is-the-maths-going-to-be-"},"How complicated is the maths going to be?"),r.createElement("p",null,"Most of the mathematics in this Primer are early high school maths. If you understand basic arithmetic, and you know how to read English, you should be able to get by just fine. There will at times be ",r.createElement("em",null,"far"),' more complicated maths, but if you don\'t feel like digesting them, you can safely skip over them by either skipping over the "detail boxes" in section or by just jumping to the end of a section with maths that looks too involving. The end of sections typically simply list the conclusions so you can just work with those values directly.'),r.createElement("h2",{id:"questions-comments-"},"Questions, comments:"),r.createElement("p",null,"If you have suggestions for new sections, hit up the ",r.createElement("a",{href:"https://github.com/pomax/BezierInfo-2/issues"},"Github issue tracker")," (also reachable from the repo linked to in the upper right). If you have questions about the material, there's currently no comment section while I'm doing the rewrite, but you can use the issue tracker for that as well. Once the rewrite is done, I'll add a general comment section back in, and maybe a more topical \"select this section of text and hit the 'question' button to ask a question about it\" system. We'll see."),r.createElement("h2",{id:"buy-me-a-coffee-"},"Buy me a coffee?"),r.createElement("p",null,"If you enjoyed this book, or you simply found it useful for something you were trying to get done, and you were wondering how to let me know you appreciated this book, you can always ",r.createElement("a",{href:"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QPRDLNGDANJSW"},"buy me a coffee"),", however-much a coffee is where you live. This work has grown over the years, from a small primer to a 70ish print-page-equivalent reader on the subject of Bézier curves, and a lot of coffee went into the making of it. I don't regret a minute I spent on writing it, but I can always do with some more coffee to keep on writing!")))}},introduction:{title:"A lightning introduction",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"introduction",title:"A lightning introduction",number:"1"}),r.createElement("p",null,"Let's start with the good stuff: when we're talking about Bézier curves, we're talking about the things that you can see in the following graphics. They run from some start point to some end point, with their curvature influenced by one or more \"intermediate\" control points. Now, because all the graphics on this page are interactive, go manipulate those curves a bit: click-drag the points, and see how their shape changes based on what you do."),r.createElement("div",{className:"figure"},r.createElement(i,{inline:!0,title:"Quadratic Bézier curves",setup:e.drawQuadratic,draw:e.drawCurve}),r.createElement(i,{inline:!0,title:"Cubic Bézier curves",setup:e.drawCubic,draw:e.drawCurve})),r.createElement("p",null,"These curves are used a lot in computer aided design and computer aided manufacturing (CAD/CAM) applications, as well as in graphic design programs like Adobe Illustrator and Photoshop, Inkscape, the Gimp, etc. and in graphic technologies like scalable vector graphics (SVG) and OpenType fonts (ttf/otf). A lot of things use Bézier curves, so if you want to learn more about them... prepare to get your learn on!"))}},whatis:{title:"So what makes a Bézier Curve?",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"whatis",title:"So what makes a Bézier Curve?",number:"2"}),r.createElement("p",null,"Playing with the points for curves may have given you a feel for how Bézier curves behave, but what ",r.createElement("em",null,"are")," Bézier curves, really? There are two ways to explain what a Bézier curve is, and they turn out to be the entirely equivalent, but one of them uses complicated maths, and the other uses really simple maths. So... let's start with the simple explanation:"),r.createElement("p",null,"Bezier curves are the result of ",r.createElement("a",{href:"https://en.wikipedia.org/wiki/Linear_interpolation"},"linear interpolations"),". That sounds complicated but you've been doing linear interpolation since you were very young: any time you had to point at something between two other things, you've been applying linear interpolation. It's simply \"picking a point between two points\"."),r.createElement("p",null,"If we know the distance between those two points, and we want a new point that is, say, 20% the distance away from the first point (and thus 80% the distance away from the second point) then we can compute that really easily:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/75bb049d813d8ee084b076531823f2109cc1660f.svg",style:{width:"36.750150000000005rem",height:"6.37515rem"}}),r.createElement("p",null,"So let's look at that in action: the following graphic is interactive in that you can use your up and down arrow keys to increase or decrease the interpolation ratio, to see what happens. We start with three points, which gives us two lines. Linear interpolation over those lines gives use two points, between which we can again perform linear interpolation, yielding a single point. And that point —and all points we can form in this way for all ratios taken together— form our Bézier curve:"),r.createElement(i,{title:"Linear Interpolation leading to Bézier curves",setup:e.setup,draw:e.draw,onKeyDown:e.onKeyDown}),r.createElement("p",null,"And that brings us to the complicated maths: calculus."),r.createElement("p",null,'While it doesn\'t look like that\'s what we\'ve just done, we actually just drew a quadratic curve, in steps, rather than in a single go. One of the fascinating parts about Bézier curves is that they can both be described in terms of polynomial functions, as well as in terms of very simple interpolations of interpolations of [...]. That, in turn, means we can look at what these curves can do based on both "real maths" (by examining the functions, their derivatives, and all that stuff), as well as by looking at the "mechanical" composition (which tells us that a curve will never extend beyond the points we used to construct it, for instance)'),r.createElement("p",null,"So let's start looking at Bézier curves a bit more in depth. Their mathematical expressions, the properties we can derive from those, and the various things we can do to, and with, Bézier curves."))}},explanation:{title:"The mathematics of Bézier curves",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"explanation",title:"The mathematics of Bézier curves",number:"3"}),r.createElement("p",null,'Bézier curves are a form of "parametric" function. Mathematically speaking, parametric functions are cheats: a "function" is actually a well defined term representing a mapping from any number of inputs to a ',r.createElement("strong",null,"single")," output. Numbers go in, a single number comes out. Change the numbers that go in, and the number that comes out is still a single number. Parametric functions cheat. They basically say \"alright, well, we want multiple values coming out, so we'll just use more than one function\". An illustration: Let's say we have a function that maps some value, let's call it ",r.createElement("i",null,"x"),", to some other value, using some kind of number manipulation:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/785e792c343b71d4e674ac94d8800940b30917ac.svg",style:{width:"6.22485rem",height:"1.125rem"}}),r.createElement("p",null,"The notation ",r.createElement("i",null,"f(x)")," is the standard way to show that it's a function (by convention called ",r.createElement("i",null,"f")," if we're only listing one) and its output changes based on one variable (in this case, ",r.createElement("i",null,"x"),"). Change ",r.createElement("i",null,"x"),", and the output for ",r.createElement("i",null,"f(x)")," changes."),r.createElement("p",null,"So far so good. Now, let's look at parametric functions, and how they cheat. Let's take the following two functions:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/0dfe7562b43441e72201ff4cdd2e8b6e2e3ecb2d.svg",style:{width:"6.525rem",height:"2.6248500000000003rem"}}),r.createElement("p",null,"There's nothing really remarkable about them, they're just a sine and cosine function, but you'll notice the inputs have different names. If we change the value for ",r.createElement("i",null,"a"),", we're not going to change the output value for ",r.createElement("i",null,"f(b)"),", since ",r.createElement("i",null,"a")," isn't used in that function. Parametric functions cheat by changing that. In a parametric function all the different functions share a variable, like this:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/ed6f533530199d1e99b3319ba137c1327b0459c0.svg",style:{width:"7.349849999999999rem",height:"2.6248500000000003rem"}}),r.createElement("p",null,"Multiple functions, but only one variable. If we change the value for ",r.createElement("i",null,"t"),", we change the outcome of both ",r.createElement("i",null,"f",r.createElement("sub",null,"a"),"(t)")," and ",r.createElement("i",null,"f",r.createElement("sub",null,"b"),"(t)"),". You might wonder how that's useful, and the answer is actually pretty simple: if we change the labels ",r.createElement("i",null,"f",r.createElement("sub",null,"a"),"(t)")," and ",r.createElement("i",null,"f",r.createElement("sub",null,"b"),"(t)")," with what we usually mean with them for parametric curves, things might be a lot more obvious:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/ea632ea75d6a2aeb6fe69c07feb6e76f81884746.svg",style:{width:"5.77485rem",height:"2.6248500000000003rem"}}),r.createElement("p",null,"There we go. ",r.createElement("i",null,"x"),"/",r.createElement("i",null,"y")," coordinates, linked through some mystery value ",r.createElement("i",null,"t"),"."),r.createElement("p",null,"So, parametric curves don't define a ",r.createElement("i",null,"y")," coordinate in terms of an ",r.createElement("i",null,"x"),' coordinate, like normal functions do, but they instead link the values to a "control" variable. If we vary the value of ',r.createElement("i",null,"t"),", then with every change we get ",r.createElement("strong",null,"two")," values, which we can use as (",r.createElement("i",null,"x"),",",r.createElement("i",null,"y"),") coordinates in a graph. The above set of functions, for instance, generates points on a circle: We can range ",r.createElement("i",null,"t")," from negative to positive infinity, and the resulting (",r.createElement("i",null,"x"),",",r.createElement("i",null,"y"),") coordinates will always lie on a circle with radius 1 around the origin (0,0). If we plot it for ",r.createElement("i",null,"t")," from 0 to 5, we get this (use your up and down arrow keys to change the plot end value):"),r.createElement(i,{preset:"empty",title:"A (partial) circle: x=sin(t), y=cos(t)",static:!0,setup:e.setup,draw:e.draw,onKeyDown:e.props.onKeyDown}),r.createElement("p",null,"Bézier curves are (one in many classes of) parametric functions, and are characterised by using the same base function for all its dimensions. Unlike the above example, where the ",r.createElement("i",null,"x")," and ",r.createElement("i",null,"y"),' values use different functions (one uses a sine, the other a cosine), Bézier curves use the "binomial polynomial" for both ',r.createElement("i",null,"x")," and ",r.createElement("i",null,"y"),". So what are binomial polynomials?"),r.createElement("p",null,"You may remember polynomials from high school, where they're those sums that look like:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/3e8b26cf8833db7089d65e9c6b3953a3140bb19f.svg",style:{width:"14.32485rem",height:"1.20015rem"}}),r.createElement("p",null,"If they have a highest order term ",r.createElement("i",null,"x³")," they're called \"cubic\" polynomials, if it's ",r.createElement("i",null,"x²")," it's a \"square\" polynomial, if it's just ",r.createElement("i",null,"x")," it's a line (and if there aren't even any terms with ",r.createElement("i",null,"x")," it's not a polynomial!)"),r.createElement("p",null,"Bézier curves are polynomials of ",r.createElement("i",null,"t"),", rather than ",r.createElement("i",null,"x"),", with the value for ",r.createElement("i",null,"t")," fixed being between 0 and 1, with coefficients ",r.createElement("i",null,"a"),", ",r.createElement("i",null,"b"),' etc. taking the "binomial" form, which sounds fancy but is actually a pretty simple description for mixing values:'),r.createElement("img",{
-className:"LaTeX SVG",src:"images/latex/24e915ab4c69b85951f1ea9018b0ece9e52a10dd.svg",style:{width:"24.89985rem",height:"4.1998500000000005rem"}}),r.createElement("p",null,"I know what you're thinking: that doesn't look too simple, but if we remove ",r.createElement("i",null,"t"),' and add in "times one", things suddenly look pretty easy. Check out these binomial terms:'),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/448d10d21afd49135055cf685fedf6c494984b53.svg",style:{width:"14.475150000000001rem",height:"5.175rem"}}),r.createElement("p",null,'Notice that 2 is the same as 1+1, and 3 is 2+1 and 1+2, and 6 is 3+3... As you can see, each time we go up a dimension, we simply start and end with 1, and everything in between is just "the two numbers above it, added together". Now ',r.createElement("i",null,"that's")," easy to remember."),r.createElement("p",null,"There's an equally simple way to figure out how the polynomial terms work: if we rename ",r.createElement("i",null,"(1-t)")," to ",r.createElement("i",null,"a")," and ",r.createElement("i",null,"t")," to ",r.createElement("i",null,"b"),", and remove the weights for a moment, we get this:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/87c7f5294b902def4ea56e8f6cf24265a37143b6.svg",style:{width:"20.84985rem",height:"3.825rem"}}),r.createElement("p",null,"It's basically just a sum of \"every combination of ",r.createElement("i",null,"a")," and ",r.createElement("i",null,"b"),'", progressively replacing ',r.createElement("i",null,"a"),"'s with ",r.createElement("i",null,"b"),"'s after every + sign. So that's actually pretty simple too. So now you know binomial polynomials, and just for completeness I'm going to show you the generic function for this:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d79bf595a0911c17e2ac86d8806a0a8ab6ba7dfe.svg",style:{width:"20.39985rem",height:"3.90015rem"}}),r.createElement("p",null,"And that's the full description for Bézier curves. Σ in this function indicates that this is a series of additions (using the variable listed below the Σ, starting at ...= and ending at the value listed on top of the Σ)."),r.createElement("div",{className:"howtocode"},r.createElement("h3",{id:"how-to-implement-the-basis-function"},"How to implement the basis function"),r.createElement("p",null,"We could naively implement the basis function as a mathematical construct, using the function as our guide, like this:"),r.createElement("pre",null,"function Bezier(n,t):","\n"," sum = 0","\n"," for(k=0; k<n; k++):","\n"," sum += n!/(k!*(n-k)!) * (1-t)^(n-k) * t^(k)","\n"," return sum","\n"),r.createElement("p",null,"I say we could, because we're not going to: the factorial function is ",r.createElement("em",null,"incredibly"),' expensive. And, as we can see from the above explanation, we can actually create Pascal\'s triangle quite easily without it: just start at [1], then [1,1], then [1,2,1], then [1,3,3,1], and so on, with each next row fitting 1 more number than the previous row, starting and ending with "1", with all the numbers in between being the sum of the previous row\'s elements on either side "above" the one we\'re computing.'),r.createElement("p",null,"We can generate this as a list of lists lightning fast, and then never have to compute the binomial terms because we have a lookup table:"),r.createElement("pre",null,"lut = [ [1], // n=0","\n"," [1,1], // n=1","\n"," [1,2,1], // n=2","\n"," [1,3,3,1], // n=3","\n"," [1,4,6,4,1], // n=4","\n"," [1,5,10,10,5,1], // n=5","\n"," [1,6,15,20,15,6,1]] // n=6","\n","\n","binomial(n,k):","\n"," while(n >= lut.length):","\n"," s = lut.length","\n"," nextRow = new array(size=s+1)","\n"," nextRow[0] = 1","\n"," for(i=1, prev=s-1; i<prev; i++):","\n"," nextRow[i] = lut[prev][i-1] + lut[prev][i]","\n"," nextRow[s] = 1","\n"," lut.add(nextRow)","\n"," return lut[n][k]","\n"),r.createElement("p",null,"So what's going on here? First, we declare a lookup table with a size that's reasonably large enough to accommodate most lookups. Then, we declare a function to get us the values we need, and we make sure that if an n/k pair is requested that isn't in the LUT yet, we expand it first. Our basis function now looks like this:"),r.createElement("pre",null,"function Bezier(n,t):","\n"," sum = 0","\n"," for(k=0; k<=n; k++):","\n"," sum += binomial(n,k) * (1-t)^(n-k) * t^(k)","\n"," return sum","\n"),r.createElement("p",null,"Perfect. Of course, we can optimize further. For most computer graphics purposes, we don't need arbitrary curves. We need quadratic and cubic curves (this primer actually does do arbitrary curves, so you'll find code similar to shown here), which means we can drastically simplify the code:"),r.createElement("pre",null,"function Bezier(2,t):","\n"," t2 = t * t","\n"," mt = 1-t","\n"," mt2 = mt * mt","\n"," return mt2 + 2*mt*t + t2","\n","\n","function Bezier(3,t):","\n"," t2 = t * t","\n"," t3 = t2 * t","\n"," mt = 1-t","\n"," mt2 = mt * mt","\n"," mt3 = mt2 * mt","\n"," return mt3 + 3*mt2*t + 3*mt*t2 + t3","\n"),r.createElement("p",null,"And now we know how to program the basis function. Exellent.")),r.createElement("p",null,"So, now we know what the base function(s) look(s) like, time to add in the magic that makes Bézier curves so special: control points."))}},control:{title:"Controlling Bézier curvatures",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"control",title:"Controlling Bézier curvatures",number:"4"}),r.createElement("p",null,'Bézier curves are (like all "splines") interpolation functions, meaning they take a set of points, and generate values somewhere "between" those points. (One of the consequences of this is that you\'ll never be able to generate a point that lies outside the outline for the control points, commonly called the "hull" for the curve. Useful information!). In fact, we can visualize how each point contributes to the value generated by the function, so we can see which points are important, where, in the curve.'),r.createElement("p",null,'The following graphs show the interpolation functions for quadratic and cubic curves, with "S" being the strength of a point\'s contribution to the total sum of the Bézier function. Click or click-drag to see the interpolation percentages for each curve-defining point at a specific ',r.createElement("i",null,"t")," value."),r.createElement("div",{className:"figure"},r.createElement(i,{inline:!0,preset:"simple",title:"Quadratic interpolations",draw:e.drawQuadraticLerp}),r.createElement(i,{inline:!0,preset:"simple",title:"Cubic interpolations",draw:e.drawCubicLerp}),r.createElement(i,{inline:!0,preset:"simple",title:"15th order interpolations",draw:e.draw15thLerp})),r.createElement("p",null,"Also shown is the interpolation function for a 15",r.createElement("sup",null,"th")," order Bézier function. As you can see, the start and end point contribute considerably more to the curve's shape than any other point in the control point set."),r.createElement("p",null,'If we want to change the curve, we need to change the weights of each point, effectively changing the interpolations. The way to do this is about as straight forward as possible: just multiply each point with a value that changes its strength. These values are conventionally called "Weights", and we can add them to our original Bézier function:'),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/b98618f8061e9e58289abccc06a624a14561d40f.svg",style:{width:"23.70015rem",height:"3.90015rem"}}),r.createElement("p",null,'That looks complicated, but as it so happens, the "weights" are actually just the coordinate values we want our curve to have: for an ',r.createElement("i",null,"n",r.createElement("sup",null,"th"))," order curve, w",r.createElement("sub",null,"0")," is our start coordinate, w",r.createElement("sub",null,"n")," is our last coordinate, and everything in between is a controlling coordinate. Say we want a cubic curve that starts at (120,160), is controlled by (35,200) and (220,260) and ends at (220,40), we use this Bézier curve:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/853858526831a7ef3eb170efe49de397bb4913a1.svg",style:{width:"32.025150000000004rem",height:"2.77515rem"}}),r.createElement("p",null,"Which gives us the curve we saw at the top of the article:"),r.createElement(i,{preset:"simple",title:"Our cubic Bézier curve",setup:e.drawCubic,draw:e.drawCurve}),r.createElement("p",null,"What else can we do with Bézier curves? Quite a lot, actually. The rest of this article covers a multitude of possible operations and algorithms that we can apply, and the tasks they achieve."),r.createElement("div",{className:"howtocode"},r.createElement("h3",{id:"how-to-implement-the-weighted-basis-function"},"How to implement the weighted basis function"),r.createElement("p",null,"Given that we already know how to implement basis function, adding in the control points is remarkably easy:"),r.createElement("pre",null,"function Bezier(n,t,w[]):","\n"," sum = 0","\n"," for(k=0; k<n; k++):","\n"," sum += w[k] * binomial(n,k) * (1-t)^(n-k) * t^(k)","\n"," return sum","\n"),r.createElement("p",null,"And for the extremely optimized versions:"),r.createElement("pre",null,"function Bezier(2,t,w[]):","\n"," t2 = t * t","\n"," mt = 1-t","\n"," mt2 = mt * mt","\n"," return w[0]*mt2 + w[1]*2*mt*t + w[2]*t2","\n","\n","function Bezier(3,t,w[]):","\n"," t2 = t * t","\n"," t3 = t2 * t","\n"," mt = 1-t","\n"," mt2 = mt * mt","\n"," mt3 = mt2 * mt","\n"," return w[0]*mt3 + 3*w[1]*mt2*t + 3*w[2]*mt*t2 + w[3]*t3","\n"),r.createElement("p",null,"And now we know how to program the weighted basis function.")))}},extended:{title:"The Bézier interval [0,1]",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"extended",title:"The Bézier interval [0,1]",number:"5"}),r.createElement("p",null,"Now that we know the mathematics behind Bézier curves, there's one curious thing that you may have noticed: they always run from ",r.createElement("code",null,"t=0")," to ",r.createElement("code",null,"t=1"),". Why that particular interval?"),r.createElement("p",null,'It all has to do with how we run from "the start" of our curve to "the end" of our curve. If we have a value that is a mixture of two other values, then the general formula for this is:'),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/7f5ebb8489a8d04beb28f47c8aac2632b78ae764.svg",style:{width:"14.99985rem",height:"0.9rem"}}),r.createElement("p",null,"The obvious start and end values here need to be ",r.createElement("code",null,"a=1, b=0"),", so that the mixed value is 100% value 1, and 0% value 2, and ",r.createElement("code",null,"a=0, b=1"),', so that the mixed value is 0% value 1 and 100% value 2. Additionally, we don\'t want "a" and "b" to be independent: if they are, then we could just pick whatever values we like, and end up with a mixed value that is, for example, 100% value 1 ',r.createElement("strong",null,"and")," 100% value 2. In principle that's fine, but for Bézier curves we always want mixed values ",r.createElement("em",null,"between"),' the start and end point, so we need to make sure we can never set "a" and "b" to some values that lead to a mix value that sums to more than 100%. And that\'s easy:'),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d326c8f323ccd2da00d998b533ac26a1c04fcfba.svg",style:{width:"14.774849999999999rem",height:"1.125rem"}}),r.createElement("p",null,"With this we can guarantee that we never sum above 100%. By restricting ",r.createElement("code",null,"a")," to values in the interval [0,1], we will always be somewhere between our two values (inclusively), and we will always sum to a 100% mix."),r.createElement("p",null,'But... what if we use this form, used in the assumption that we will only ever use values between 0 and 1, and instead use values outside of that interval? Do things go horribly wrong? Well... not really, but we get to "see more".'),r.createElement("p",null,'In the case of Bézier curves, extending the interval simply makes our curve "keep going". Bézier curves are simply segments on some polynomial curve, so if we pick a wider interval we simply get to see more of the curve. So what do they look like?'),r.createElement("p",null,'The following two graphics show you Bézier curves rendered "the usual way", as well as the curves they "lie on" if we were to extend the ',r.createElement("code",null,"t"),' values much further. As you can see, there\'s a lot more "shape" hidden in the rest of the curve, and we can model those parts by moving the curve points around.'),r.createElement(i,{preset:"simple",title:"Quadratic infinite interval Bézier curve",setup:e.setupQuadratic,draw:e.draw}),r.createElement(i,{preset:"simple",title:"Cubic infinite interval Bézier curve",setup:e.setupCubic,draw:e.draw}),r.createElement("p",null,"In fact, there are curves used in graphics design and computer modelling that do the opposite of Bézier curves, where rather than fixing the interval, and giving you free coordinates, they fix the coordinates, but give you freedom over the interval. A great example of this is the ",r.createElement("a",{href:"http://levien.com/phd/phd.html"},'"Spiro" curve'),", which is a curve based on part of a ",r.createElement("a",{href:"https://en.wikipedia.org/wiki/Euler_spiral"},"Cornu Spiral, also known as Euler's Spiral"),". It's a very aesthetically pleasing curve and you'll find it in quite a few graphics packages like ",r.createElement("a",{href:"https://fontforge.github.io"},"FontForge")," and ",r.createElement("a",{href:"https://inkscape.org"},"Inkscape"),", having even been used in font design (such as for the Inconsolata font)."))}},matrix:{title:"Bézier curvatures as matrix operations",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"matrix",title:"Bézier curvatures as matrix operations",number:"6"}),r.createElement("p",null,"We can also represent Bézier as matrix operations, by expressing the Bézier formula as a polynomial basis function, the weight matrix, and the actual coordinates as matrix. Let's look at what this means for the cubic curve:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d961171d6d1dfc22bb1756901e79102147914360.svg",style:{width:"31.12515rem",height:"1.20015rem"}}),r.createElement("p",null,"Disregarding our actual coordinates for a moment, we have:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/f925c339011e6c38e47b9c3a571e02fca80eb5c3.svg",style:{width:"23.475150000000003rem",height:"1.20015rem"}}),r.createElement("p",null,"We can write this as a sum of four expressions:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/30d76165668bf15f62986503bea100f39c5b9fec.svg",style:{width:"10.42515rem",height:"5.8500000000000005rem"}}),r.createElement("p",null,"And we can expand these expressions:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/7ca5abe1124ba1e51b7f12e0469cb4b1407593b8.svg",style:{width:"27.82485rem",height:"5.8500000000000005rem"}}),r.createElement("p",null,"Furthermore, we can make all the 1 and 0 factors explicit:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/bccbb94942e3ff79579e4719106f4701c157727e.svg",style:{width:"15.67485rem",height:"5.625rem"}}),r.createElement("p",null,"And ",r.createElement("em",null,"that"),", we can view as a series of four matrix operations:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d5f85d80fbbc62e1e8d58621b76f3d0224876b62.svg",style:{width:"45.225rem",height:"5.47515rem"}}),r.createElement("p",null,"If we compact this into a single matrix operation, we get:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/7140be48f45b2e7190fa8dffef5c05c47c038ab0.svg",style:{width:"16.875rem",height:"5.47515rem"}}),r.createElement("p",null,"This kind of polynomial basis representation is generally written with the bases in increasing order, which means we need to flip our ",r.createElement("code",null,"t"),' matrix horizontally, and our big "mixing" matrix upside down:'),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/4e1849950a5c13f5135aa3412e0ee634cdc67301.svg",style:{width:"16.875rem",height:"5.47515rem"}}),r.createElement("p",null,"And then finally, we can add in our original coordinates as a single third matrix:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/5910e25a46d9e86ab34513017f1274628a40e5a7.svg",style:{width:"23.925150000000002rem",height:"5.47515rem"}}),r.createElement("p",null,"We can perform the same trick for the quadratic curve, in which case we end up with:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/e56e78e406d625c2a5ec584216f79a5fee00d8ea.svg",style:{width:"19.65015rem",height:"3.97485rem"}}),r.createElement("p",null,"If we plug in a ",r.createElement("code",null,"t")," value, and then multiply the matrices, we will get exactly the same values as when we evaluate the original polynomial function, or as when we evaluate the curve using progessive linear interpolation."),r.createElement("p",null,r.createElement("strong",null,"So: why would we bother with matrices?")," Matrix representations allow us to discover things about functions that would otherwise be hard to tell. It turns out that the curves form ",r.createElement("a",{href:"https://en.wikipedia.org/wiki/Triangular_matrix"},"triangular matrices"),", and they have a determinant equal to the product of the actual coordinates we use for our curve. It's also invertible, which means there's ",r.createElement("a",{href:"https://en.wikipedia.org/wiki/Invertible_matrix#The_invertible_matrix_theorem"},"a ton of properties")," that are all satisfied. Of course, the main question is: \"Why is this useful to us, now?\", and the answer to that is that it's not immediately useful, but you'll be seeing some instances where certain curve properties can be either computed via function manipulation, or via clever use of matrices, and sometimes the matrix approach can be (drastically) faster."),r.createElement("p",null,"So for now, just remember that we can represent curves this way, and let's move on."))}},decasteljau:{title:"de Casteljau's algorithm",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"decasteljau",title:"de Casteljau's algorithm",number:"7"}),r.createElement("p",null,"If we want to draw Bézier curves we can run through all values of ",r.createElement("code",null,"t")," from 0 to 1 and then compute the weighted basis function, getting the ",r.createElement("code",null,"x/y"),' values we need to plot, but the more complex the curve gets, the more expensive this becomes. Instead, we can use "de Casteljau\'s algorithm" to draw curves, which is a geometric approach to drawing curves, and really easy to implement. So easy, in fact, you can do it by hand with a pencil and ruler.'),r.createElement("p",null,"Rather than using our calculus function to find ",r.createElement("code",null,"x/y")," values for ",r.createElement("code",null,"t"),", let's do this instead:"),r.createElement("ul",null,r.createElement("li",null,"treat ",r.createElement("code",null,"t")," as a ratio (which it is). t=0 is 0% along a line, t=1 is 100% along a line."),r.createElement("li",null,"Take all lines between the curve's defining points. For an order ",r.createElement("code",null,"n")," curve, that's ",r.createElement("code",null,"n")," lines."),r.createElement("li",null,"Place markers along each of these line, at distance ",r.createElement("code",null,"t"),". So if ",r.createElement("code",null,"t")," is 0.2, place the mark at 20% from the start, 80% from the end."),r.createElement("li",null,"Now form lines between ",r.createElement("code",null,"those")," points. This gives ",r.createElement("code",null,"n-1")," lines."),r.createElement("li",null,"Place markers along each of these line at distance ",r.createElement("code",null,"t"),"."),r.createElement("li",null,"Form lines between ",r.createElement("code",null,"those")," points. This'll be ",r.createElement("code",null,"n-2")," lines."),r.createElement("li",null,"place markers, form lines, place markers, etc."),r.createElement("li",null,"repeat this until you have only one line left. The point ",r.createElement("code",null,"t")," on that line coincides with the original curve point at ",r.createElement("code",null,"t"),".")),r.createElement("div",{className:"howtocode"},r.createElement("h3",{id:"how-to-implement-de-casteljau-s-algorithm"},"How to implement de Casteljau's algorithm"),r.createElement("p",null,"Let's just use the algorithm we just specified, and implement that:"),r.createElement("pre",null,"function drawCurve(points[], t):","\n"," if(points.length==1):","\n"," draw(points[0])","\n"," else:","\n"," newpoints=array(points.size-1)","\n"," for(i=0; i<newpoints.length; i++):","\n"," newpoints[i] = (1-t) * points[i] + t * points[i+1]","\n"," drawCurve(newpoints, t)","\n"),r.createElement("p",null,"And done, that's the algorithm implemented. Except usually you don't get the luxury of overloading the \"+\" operator, so let's also give the code for when you need to work with ",r.createElement("code",null,"x")," and ",r.createElement("code",null,"y")," values:"),r.createElement("pre",null,"function drawCurve(points[], t):","\n"," if(points.length==1):","\n"," draw(points[0])","\n"," else:","\n"," newpoints=array(points.size-1)","\n"," for(i=0; i<newpoints.length; i++):","\n"," x = (1-t) * points[i].x + t * points[i+1].x","\n"," y = (1-t) * points[i].y + t * points[i+1].y","\n"," newpoints[i] = new point(x,y)","\n"," drawCurve(newpoints, t)","\n"),r.createElement("p",null,"So what does this do? This draws a point, if the passed list of points is only 1 point long. Otherwise it will create a new list of points that sit at the ",r.createElement("i",null,"t"),' ratios (i.e. the "markers" outlined in the above algorithm), and then call the draw function for this new list.')),r.createElement("p",null,"To see this in action, mouse-over the following sketch. Moving the mouse changes which curve point is explicitly evaluated using de Casteljau's algorithm, moving the cursor left-to-right (or, of course, right-to-left), shows you how a curve is generated using this approach."),r.createElement(i,{preset:"simple",title:"Traversing a curve using de Casteljau's algorithm",setup:e.setup,draw:e.draw}))}},flattening:{title:"Simplified drawing",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"flattening",title:"Simplified drawing",number:"8"}),r.createElement("p",null,'We can also simplify the drawing process by "sampling" the curve at certain points, and then joining those points up with straight lines, a process known as "flattening", as we are reducing a curve to a simple sequence of straight, "flat" lines.'),r.createElement("p",null,'We can do this is by saying "we want X segments", and then sampling the curve at intervals that are spaced such that we end up with the number of segments we wanted. The advantage of this method is that it\'s fast: instead of evaluating 100 or even 1000 curve coordinates, we can sample a much lower number and still end up with a curve that sort-of-kind-of looks good enough. The disadvantage of course is that we lose the precision of working with "the real curve", so we usually can\'t use the flattened for for doing true intersection detection, or curvature alignment.'),r.createElement(i,{preset:"twopanel",title:"Flattening a quadratic curve",setup:e.setupQuadratic,draw:e.drawFlattened,onKeyDown:e.onKeyDown}),r.createElement(i,{preset:"twopanel",title:"Flattening a cubic curve",setup:e.setupCubic,draw:e.drawFlattened,onKeyDown:e.onKeyDown}),r.createElement("p",null,"Try clicking on the sketch and using your up and down arrow keys to lower the number of segments for both the quadratic and cubic curve. You'll notice that for certain curvatures, a low number of segments works quite well, but for more complex curvatures (try this for the cubic curve), a higher number is required to capture the curvature changes properly."),r.createElement("div",{className:"howtocode"},r.createElement("h3",{id:"how-to-implement-curve-flattening"},"How to implement curve flattening"),r.createElement("p",null,"Let's just use the algorithm we just specified, and implement that:"),r.createElement("pre",null,"function flattenCurve(curve, segmentCount):","\n"," step = 1/segmentCount;","\n"," coordinates = [curve.getXValue(0), curve.getYValue(0)]","\n"," for(i=1; i <= segmentCount; i++):","\n"," t = i*step;","\n"," coordinates.push[curve.getXValue(t), curve.getYValue(t)]","\n"," return coordinates;","\n"),r.createElement("p",null,'And done, that\'s the algorithm implemented. That just leaves drawing the resulting "curve" as a sequence of lines:'),r.createElement("pre",null,"function drawFlattenedCurve(curve, segmentCount):","\n"," coordinates = flattenCurve(curve, segmentCount)","\n"," coord = coordinates[0], _coords;","\n"," for(i=1; i < coordinates.length; i++):","\n"," _coords = coordinates[i]","\n"," line(coords, _coords)","\n"," coords = _coords","\n"),r.createElement("p",null,"We start with the first coordinate as reference point, and then just draw lines between each point and its next point.")))}},splitting:{title:"Splitting curves",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"splitting",title:"Splitting curves",number:"9"}),r.createElement("p",null,"With de Casteljau's algorithm we also find all the points we need to split up a Bézier curve into two, smaller curves, which taken together form the original curve. When we construct de Casteljau's skeleton for some value ",r.createElement("code",null,"t"),", the procedure gives us all the points we need to split a curve at that ",r.createElement("code",null,"t")," value: one curve is defined by all the inside skeleton points found prior to our on-curve point, with the other curve being defined by all the inside skeleton points after our on-curve point."),r.createElement(i,{title:"Splitting a curve",setup:e.setupCubic,draw:e.drawSplit}),r.createElement("div",{className:"howtocode"},r.createElement("h3",{id:"implementing-curve-splitting"},"implementing curve splitting"),r.createElement("p",null,"We can implement curve splitting by bolting some extra logging onto the de Casteljau function:"),r.createElement("pre",null,"left=[]","\n","right=[]","\n","function drawCurve(points[], t):","\n"," if(points.length==1):","\n"," left.add(points[0])","\n"," right.add(points[0])","\n"," draw(points[0])","\n"," else:","\n"," newpoints=array(points.size-1)","\n"," for(i=0; i<newpoints.length; i++):","\n"," if(i==0):","\n"," left.add(points[i])","\n"," if(i==newpoints.length-1):","\n"," right.add(points[i+1])","\n"," newpoints[i] = (1-t) * points[i] + t * points[i+1]","\n"," drawCurve(newpoints, t)","\n"),r.createElement("p",null,"After running this function for some value ",r.createElement("code",null,"t"),", the ",r.createElement("code",null,"left")," and ",r.createElement("code",null,"right"),' arrays will contain all the coordinates for two new curves - one to the "left" of our ',r.createElement("code",null,"t"),' value, the other on the "right", of the same order as the original curve, and overlayed exactly on the original curve.')),r.createElement("p",null,"This is best illustrated with an animated graphic (click to play/pause):"),r.createElement(i,{preset:"threepanel",title:"Bézier curve splitting",setup:e.setupCubic,draw:e.drawAnimated,onClick:e.togglePlay}))}},matrixsplit:{title:"Splitting curves using matrices",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"matrixsplit",title:"Splitting curves using matrices",number:"10"}),r.createElement("p",null,"Another way to split curves is to exploit the matrix representation of a Bézier curve. In ",r.createElement("a",{href:"#matrix"},"the section on matrices")," we saw that we can represent curves as matrix multiplications. Specifically, we saw these two forms for the quadratic, and cubic curves, respectively (using the reversed Bézier coefficients vector for legibility):"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/e56e78e406d625c2a5ec584216f79a5fee00d8ea.svg",style:{width:"19.65015rem",height:"3.97485rem"}}),r.createElement("p",null,"and"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/01ea4f74c4785a19bedf18034b51510c5ce2ad8f.svg",style:{width:"23.925150000000002rem",height:"5.47515rem"}}),r.createElement("p",null,"Let's say we want to split the curve at some point ",r.createElement("code",null,"t = z"),', forming two new (obviously smaller) Bézier curves. To find the coordinates for these two Bézier curves, we can use the matrix representation and some linear algebra. First, we split out the the actual "point on the curve" information as a new matrix multiplication:'),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d6fa091a86782480968c232ef86513c578030004.svg",style:{width:"48.07485rem",height:"4.05rem"}}),r.createElement("p",null,"and"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d487e1e0181420995be49b25bc6595c9d0360435.svg",style:{width:"60.82515rem",height:"5.54985rem"}}),r.createElement("p",null,"If we could compact these matrices back to a form ",r.createElement("strong",null,"[t values] · [bezier matrix] · [column matrix]"),", with the first two staying the same, then that column matrix on the right would be the coordinates of a new Bézier curve that describes the first segment, from ",r.createElement("code",null,"t = 0")," to ",r.createElement("code",null,"t = z"),". As it turns out, we can do this quite easily, by exploiting some simple rules of linear algebra (and if you don't care about the derivations, just skip to the end of the box for the results!)."),r.createElement("div",{className:"note"},r.createElement("h2",{id:"deriving-new-hull-coordinates"},"Deriving new hull coordinates"),r.createElement("p",null,"Deriving the two segments upon splitting a curve takes a few steps, and the higher the curve order, the more work it is, so let's look at the quadratic curve first:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d4b8355c3f1f80aacfc2766423a30151c5180a02.svg",style:{width:"26.24985rem",height:"4.05rem"}}),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/08a42b0460e1a0189813189dfa0003e40a8a8992.svg",style:{width:"17.62515rem",height:"3.97485rem"}}),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/87e2405cf52dd9e68a46d93db65ae9de705f2a9d.svg",style:{width:"17.69985rem",height:"3.97485rem"}}),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/bc85dbb857222546bd30ea559a452fe9f36c8090.svg",style:{width:"17.69985rem",height:"4.05rem"}}),r.createElement("p",null,"We do this, because [",r.createElement("em",null,"M · M",r.createElement("sup",null,"-1")),"] is the identity matrix (a bit like multiplying something by x/x in calculus. It doesn't do anything to the function, but it does allow you to rewrite it to something that may be easier to work with, or can be broken up differently). Adding that as matrix multiplication has no effect on the total formula, but it does allow us to change the matrix sequence [",r.createElement("em",null,"something · M"),"] to a sequence [",r.createElement("em",null,"M · something"),"], and that makes a world of difference: if we know what [",r.createElement("em",null,"M",r.createElement("sup",null,"-1")," · Z · M"),"] is, we can apply that to our coordinates, and be left with a proper matrix representation of a quadratic Bézier curve (which is [",r.createElement("em",null,"T · M · P"),"]), with a new set of coordinates that represent the curve from ",r.createElement("em",null,"t = 0")," to ",r.createElement("em",null,"t = z"),". So let's get computing:"),r.createElement("img",{
+className:"LaTeX SVG",src:"images/latex/24e915ab4c69b85951f1ea9018b0ece9e52a10dd.svg",style:{width:"24.89985rem",height:"4.1998500000000005rem"}}),r.createElement("p",null,"I know what you're thinking: that doesn't look too simple, but if we remove ",r.createElement("i",null,"t"),' and add in "times one", things suddenly look pretty easy. Check out these binomial terms:'),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/448d10d21afd49135055cf685fedf6c494984b53.svg",style:{width:"14.475150000000001rem",height:"5.175rem"}}),r.createElement("p",null,'Notice that 2 is the same as 1+1, and 3 is 2+1 and 1+2, and 6 is 3+3... As you can see, each time we go up a dimension, we simply start and end with 1, and everything in between is just "the two numbers above it, added together". Now ',r.createElement("i",null,"that's")," easy to remember."),r.createElement("p",null,"There's an equally simple way to figure out how the polynomial terms work: if we rename ",r.createElement("i",null,"(1-t)")," to ",r.createElement("i",null,"a")," and ",r.createElement("i",null,"t")," to ",r.createElement("i",null,"b"),", and remove the weights for a moment, we get this:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/87c7f5294b902def4ea56e8f6cf24265a37143b6.svg",style:{width:"20.84985rem",height:"3.825rem"}}),r.createElement("p",null,"It's basically just a sum of \"every combination of ",r.createElement("i",null,"a")," and ",r.createElement("i",null,"b"),'", progressively replacing ',r.createElement("i",null,"a"),"'s with ",r.createElement("i",null,"b"),"'s after every + sign. So that's actually pretty simple too. So now you know binomial polynomials, and just for completeness I'm going to show you the generic function for this:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d79bf595a0911c17e2ac86d8806a0a8ab6ba7dfe.svg",style:{width:"20.39985rem",height:"3.90015rem"}}),r.createElement("p",null,"And that's the full description for Bézier curves. Σ in this function indicates that this is a series of additions (using the variable listed below the Σ, starting at ...= and ending at the value listed on top of the Σ)."),r.createElement("div",{className:"howtocode"},r.createElement("h3",{id:"how-to-implement-the-basis-function"},"How to implement the basis function"),r.createElement("p",null,"We could naively implement the basis function as a mathematical construct, using the function as our guide, like this:"),r.createElement("pre",null,"function Bezier(n,t):","\n"," sum = 0","\n"," for(k=0; k<n; k++):","\n"," sum += n!/(k!*(n-k)!) * (1-t)^(n-k) * t^(k)","\n"," return sum","\n"),r.createElement("p",null,"I say we could, because we're not going to: the factorial function is ",r.createElement("em",null,"incredibly"),' expensive. And, as we can see from the above explanation, we can actually create Pascal\'s triangle quite easily without it: just start at [1], then [1,1], then [1,2,1], then [1,3,3,1], and so on, with each next row fitting 1 more number than the previous row, starting and ending with "1", with all the numbers in between being the sum of the previous row\'s elements on either side "above" the one we\'re computing.'),r.createElement("p",null,"We can generate this as a list of lists lightning fast, and then never have to compute the binomial terms because we have a lookup table:"),r.createElement("pre",null,"lut = [ [1], // n=0","\n"," [1,1], // n=1","\n"," [1,2,1], // n=2","\n"," [1,3,3,1], // n=3","\n"," [1,4,6,4,1], // n=4","\n"," [1,5,10,10,5,1], // n=5","\n"," [1,6,15,20,15,6,1]] // n=6","\n","\n","binomial(n,k):","\n"," while(n >= lut.length):","\n"," s = lut.length","\n"," nextRow = new array(size=s+1)","\n"," nextRow[0] = 1","\n"," for(i=1, prev=s-1; i<prev; i++):","\n"," nextRow[i] = lut[prev][i-1] + lut[prev][i]","\n"," nextRow[s] = 1","\n"," lut.add(nextRow)","\n"," return lut[n][k]","\n"),r.createElement("p",null,"So what's going on here? First, we declare a lookup table with a size that's reasonably large enough to accommodate most lookups. Then, we declare a function to get us the values we need, and we make sure that if an n/k pair is requested that isn't in the LUT yet, we expand it first. Our basis function now looks like this:"),r.createElement("pre",null,"function Bezier(n,t):","\n"," sum = 0","\n"," for(k=0; k<=n; k++):","\n"," sum += binomial(n,k) * (1-t)^(n-k) * t^(k)","\n"," return sum","\n"),r.createElement("p",null,"Perfect. Of course, we can optimize further. For most computer graphics purposes, we don't need arbitrary curves. We need quadratic and cubic curves (this primer actually does do arbitrary curves, so you'll find code similar to shown here), which means we can drastically simplify the code:"),r.createElement("pre",null,"function Bezier(2,t):","\n"," t2 = t * t","\n"," mt = 1-t","\n"," mt2 = mt * mt","\n"," return mt2 + 2*mt*t + t2","\n","\n","function Bezier(3,t):","\n"," t2 = t * t","\n"," t3 = t2 * t","\n"," mt = 1-t","\n"," mt2 = mt * mt","\n"," mt3 = mt2 * mt","\n"," return mt3 + 3*mt2*t + 3*mt*t2 + t3","\n"),r.createElement("p",null,"And now we know how to program the basis function. Exellent.")),r.createElement("p",null,"So, now we know what the base function(s) look(s) like, time to add in the magic that makes Bézier curves so special: control points."))}},control:{title:"Controlling Bézier curvatures",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"control",title:"Controlling Bézier curvatures",number:"4"}),r.createElement("p",null,'Bézier curves are (like all "splines") interpolation functions, meaning they take a set of points, and generate values somewhere "between" those points. (One of the consequences of this is that you\'ll never be able to generate a point that lies outside the outline for the control points, commonly called the "hull" for the curve. Useful information!). In fact, we can visualize how each point contributes to the value generated by the function, so we can see which points are important, where, in the curve.'),r.createElement("p",null,'The following graphs show the interpolation functions for quadratic and cubic curves, with "S" being the strength of a point\'s contribution to the total sum of the Bézier function. Click or click-drag to see the interpolation percentages for each curve-defining point at a specific ',r.createElement("i",null,"t")," value."),r.createElement("div",{className:"figure"},r.createElement(i,{inline:!0,preset:"simple",title:"Quadratic interpolations",draw:e.drawQuadraticLerp}),r.createElement(i,{inline:!0,preset:"simple",title:"Cubic interpolations",draw:e.drawCubicLerp}),r.createElement(i,{inline:!0,preset:"simple",title:"15th order interpolations",draw:e.draw15thLerp})),r.createElement("p",null,"Also shown is the interpolation function for a 15",r.createElement("sup",null,"th")," order Bézier function. As you can see, the start and end point contribute considerably more to the curve's shape than any other point in the control point set."),r.createElement("p",null,'If we want to change the curve, we need to change the weights of each point, effectively changing the interpolations. The way to do this is about as straight forward as possible: just multiply each point with a value that changes its strength. These values are conventionally called "Weights", and we can add them to our original Bézier function:'),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/b98618f8061e9e58289abccc06a624a14561d40f.svg",style:{width:"23.70015rem",height:"3.90015rem"}}),r.createElement("p",null,'That looks complicated, but as it so happens, the "weights" are actually just the coordinate values we want our curve to have: for an ',r.createElement("i",null,"n",r.createElement("sup",null,"th"))," order curve, w",r.createElement("sub",null,"0")," is our start coordinate, w",r.createElement("sub",null,"n")," is our last coordinate, and everything in between is a controlling coordinate. Say we want a cubic curve that starts at (120,160), is controlled by (35,200) and (220,260) and ends at (220,40), we use this Bézier curve:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/853858526831a7ef3eb170efe49de397bb4913a1.svg",style:{width:"32.025150000000004rem",height:"2.77515rem"}}),r.createElement("p",null,"Which gives us the curve we saw at the top of the article:"),r.createElement(i,{preset:"simple",title:"Our cubic Bézier curve",setup:e.drawCubic,draw:e.drawCurve}),r.createElement("p",null,"What else can we do with Bézier curves? Quite a lot, actually. The rest of this article covers a multitude of possible operations and algorithms that we can apply, and the tasks they achieve."),r.createElement("div",{className:"howtocode"},r.createElement("h3",{id:"how-to-implement-the-weighted-basis-function"},"How to implement the weighted basis function"),r.createElement("p",null,"Given that we already know how to implement basis function, adding in the control points is remarkably easy:"),r.createElement("pre",null,"function Bezier(n,t,w[]):","\n"," sum = 0","\n"," for(k=0; k<n; k++):","\n"," sum += w[k] * binomial(n,k) * (1-t)^(n-k) * t^(k)","\n"," return sum","\n"),r.createElement("p",null,"And for the extremely optimized versions:"),r.createElement("pre",null,"function Bezier(2,t,w[]):","\n"," t2 = t * t","\n"," mt = 1-t","\n"," mt2 = mt * mt","\n"," return w[0]*mt2 + w[1]*2*mt*t + w[2]*t2","\n","\n","function Bezier(3,t,w[]):","\n"," t2 = t * t","\n"," t3 = t2 * t","\n"," mt = 1-t","\n"," mt2 = mt * mt","\n"," mt3 = mt2 * mt","\n"," return w[0]*mt3 + 3*w[1]*mt2*t + 3*w[2]*mt*t2 + w[3]*t3","\n"),r.createElement("p",null,"And now we know how to program the weighted basis function.")))}},extended:{title:"The Bézier interval [0,1]",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"extended",title:"The Bézier interval [0,1]",number:"5"}),r.createElement("p",null,"Now that we know the mathematics behind Bézier curves, there's one curious thing that you may have noticed: they always run from ",r.createElement("code",null,"t=0")," to ",r.createElement("code",null,"t=1"),". Why that particular interval?"),r.createElement("p",null,'It all has to do with how we run from "the start" of our curve to "the end" of our curve. If we have a value that is a mixture of two other values, then the general formula for this is:'),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/7f5ebb8489a8d04beb28f47c8aac2632b78ae764.svg",style:{width:"14.99985rem",height:"0.9rem"}}),r.createElement("p",null,"The obvious start and end values here need to be ",r.createElement("code",null,"a=1, b=0"),", so that the mixed value is 100% value 1, and 0% value 2, and ",r.createElement("code",null,"a=0, b=1"),', so that the mixed value is 0% value 1 and 100% value 2. Additionally, we don\'t want "a" and "b" to be independent: if they are, then we could just pick whatever values we like, and end up with a mixed value that is, for example, 100% value 1 ',r.createElement("strong",null,"and")," 100% value 2. In principle that's fine, but for Bézier curves we always want mixed values ",r.createElement("em",null,"between"),' the start and end point, so we need to make sure we can never set "a" and "b" to some values that lead to a mix value that sums to more than 100%. And that\'s easy:'),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d326c8f323ccd2da00d998b533ac26a1c04fcfba.svg",style:{width:"14.774849999999999rem",height:"1.125rem"}}),r.createElement("p",null,"With this we can guarantee that we never sum above 100%. By restricting ",r.createElement("code",null,"a")," to values in the interval [0,1], we will always be somewhere between our two values (inclusively), and we will always sum to a 100% mix."),r.createElement("p",null,'But... what if we use this form, used in the assumption that we will only ever use values between 0 and 1, and instead use values outside of that interval? Do things go horribly wrong? Well... not really, but we get to "see more".'),r.createElement("p",null,'In the case of Bézier curves, extending the interval simply makes our curve "keep going". Bézier curves are simply segments on some polynomial curve, so if we pick a wider interval we simply get to see more of the curve. So what do they look like?'),r.createElement("p",null,'The following two graphics show you Bézier curves rendered "the usual way", as well as the curves they "lie on" if we were to extend the ',r.createElement("code",null,"t"),' values much further. As you can see, there\'s a lot more "shape" hidden in the rest of the curve, and we can model those parts by moving the curve points around.'),r.createElement(i,{preset:"simple",title:"Quadratic infinite interval Bézier curve",setup:e.setupQuadratic,draw:e.draw}),r.createElement(i,{preset:"simple",title:"Cubic infinite interval Bézier curve",setup:e.setupCubic,draw:e.draw}),r.createElement("p",null,"In fact, there are curves used in graphics design and computer modelling that do the opposite of Bézier curves, where rather than fixing the interval, and giving you free coordinates, they fix the coordinates, but give you freedom over the interval. A great example of this is the ",r.createElement("a",{href:"http://levien.com/phd/phd.html"},'"Spiro" curve'),", which is a curve based on part of a ",r.createElement("a",{href:"https://en.wikipedia.org/wiki/Euler_spiral"},"Cornu Spiral, also known as Euler's Spiral"),". It's a very aesthetically pleasing curve and you'll find it in quite a few graphics packages like ",r.createElement("a",{href:"https://fontforge.github.io"},"FontForge")," and ",r.createElement("a",{href:"https://inkscape.org"},"Inkscape"),", having even been used in font design (such as for the Inconsolata font)."))}},matrix:{title:"Bézier curvatures as matrix operations",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"matrix",title:"Bézier curvatures as matrix operations",number:"6"}),r.createElement("p",null,"We can also represent Bézier as matrix operations, by expressing the Bézier formula as a polynomial basis function, the weight matrix, and the actual coordinates as matrix. Let's look at what this means for the cubic curve:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d961171d6d1dfc22bb1756901e79102147914360.svg",style:{width:"31.12515rem",height:"1.20015rem"}}),r.createElement("p",null,"Disregarding our actual coordinates for a moment, we have:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/f925c339011e6c38e47b9c3a571e02fca80eb5c3.svg",style:{width:"23.475150000000003rem",height:"1.20015rem"}}),r.createElement("p",null,"We can write this as a sum of four expressions:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/30d76165668bf15f62986503bea100f39c5b9fec.svg",style:{width:"10.42515rem",height:"5.8500000000000005rem"}}),r.createElement("p",null,"And we can expand these expressions:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/7ca5abe1124ba1e51b7f12e0469cb4b1407593b8.svg",style:{width:"27.82485rem",height:"5.8500000000000005rem"}}),r.createElement("p",null,"Furthermore, we can make all the 1 and 0 factors explicit:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/bccbb94942e3ff79579e4719106f4701c157727e.svg",style:{width:"15.67485rem",height:"5.625rem"}}),r.createElement("p",null,"And ",r.createElement("em",null,"that"),", we can view as a series of four matrix operations:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d5f85d80fbbc62e1e8d58621b76f3d0224876b62.svg",style:{width:"45.225rem",height:"5.47515rem"}}),r.createElement("p",null,"If we compact this into a single matrix operation, we get:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/7140be48f45b2e7190fa8dffef5c05c47c038ab0.svg",style:{width:"16.875rem",height:"5.47515rem"}}),r.createElement("p",null,"This kind of polynomial basis representation is generally written with the bases in increasing order, which means we need to flip our ",r.createElement("code",null,"t"),' matrix horizontally, and our big "mixing" matrix upside down:'),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/4e1849950a5c13f5135aa3412e0ee634cdc67301.svg",style:{width:"16.875rem",height:"5.47515rem"}}),r.createElement("p",null,"And then finally, we can add in our original coordinates as a single third matrix:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/5910e25a46d9e86ab34513017f1274628a40e5a7.svg",style:{width:"23.925150000000002rem",height:"5.47515rem"}}),r.createElement("p",null,"We can perform the same trick for the quadratic curve, in which case we end up with:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/e56e78e406d625c2a5ec584216f79a5fee00d8ea.svg",style:{width:"19.65015rem",height:"3.97485rem"}}),r.createElement("p",null,"If we plug in a ",r.createElement("code",null,"t")," value, and then multiply the matrices, we will get exactly the same values as when we evaluate the original polynomial function, or as when we evaluate the curve using progessive linear interpolation."),r.createElement("p",null,r.createElement("strong",null,"So: why would we bother with matrices?")," Matrix representations allow us to discover things about functions that would otherwise be hard to tell. It turns out that the curves form ",r.createElement("a",{href:"https://en.wikipedia.org/wiki/Triangular_matrix"},"triangular matrices"),", and they have a determinant equal to the product of the actual coordinates we use for our curve. It's also invertible, which means there's ",r.createElement("a",{href:"https://en.wikipedia.org/wiki/Invertible_matrix#The_invertible_matrix_theorem"},"a ton of properties")," that are all satisfied. Of course, the main question is: \"Why is this useful to us, now?\", and the answer to that is that it's not immediately useful, but you'll be seeing some instances where certain curve properties can be either computed via function manipulation, or via clever use of matrices, and sometimes the matrix approach can be (drastically) faster."),r.createElement("p",null,"So for now, just remember that we can represent curves this way, and let's move on."))}},decasteljau:{title:"de Casteljau's algorithm",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"decasteljau",title:"de Casteljau's algorithm",number:"7"}),r.createElement("p",null,"If we want to draw Bézier curves we can run through all values of ",r.createElement("code",null,"t")," from 0 to 1 and then compute the weighted basis function, getting the ",r.createElement("code",null,"x/y"),' values we need to plot, but the more complex the curve gets, the more expensive this becomes. Instead, we can use "de Casteljau\'s algorithm" to draw curves, which is a geometric approach to drawing curves, and really easy to implement. So easy, in fact, you can do it by hand with a pencil and ruler.'),r.createElement("p",null,"Rather than using our calculus function to find ",r.createElement("code",null,"x/y")," values for ",r.createElement("code",null,"t"),", let's do this instead:"),r.createElement("ul",null,r.createElement("li",null,"treat ",r.createElement("code",null,"t")," as a ratio (which it is). t=0 is 0% along a line, t=1 is 100% along a line."),r.createElement("li",null,"Take all lines between the curve's defining points. For an order ",r.createElement("code",null,"n")," curve, that's ",r.createElement("code",null,"n")," lines."),r.createElement("li",null,"Place markers along each of these line, at distance ",r.createElement("code",null,"t"),". So if ",r.createElement("code",null,"t")," is 0.2, place the mark at 20% from the start, 80% from the end."),r.createElement("li",null,"Now form lines between ",r.createElement("code",null,"those")," points. This gives ",r.createElement("code",null,"n-1")," lines."),r.createElement("li",null,"Place markers along each of these line at distance ",r.createElement("code",null,"t"),"."),r.createElement("li",null,"Form lines between ",r.createElement("code",null,"those")," points. This'll be ",r.createElement("code",null,"n-2")," lines."),r.createElement("li",null,"place markers, form lines, place markers, etc."),r.createElement("li",null,"repeat this until you have only one line left. The point ",r.createElement("code",null,"t")," on that line coincides with the original curve point at ",r.createElement("code",null,"t"),".")),r.createElement("div",{className:"howtocode"},r.createElement("h3",{id:"how-to-implement-de-casteljau-s-algorithm"},"How to implement de Casteljau's algorithm"),r.createElement("p",null,"Let's just use the algorithm we just specified, and implement that:"),r.createElement("pre",null,"function drawCurve(points[], t):","\n"," if(points.length==1):","\n"," draw(points[0])","\n"," else:","\n"," newpoints=array(points.size-1)","\n"," for(i=0; i<newpoints.length; i++):","\n"," newpoints[i] = (1-t) * points[i] + t * points[i+1]","\n"," drawCurve(newpoints, t)","\n"),r.createElement("p",null,"And done, that's the algorithm implemented. Except usually you don't get the luxury of overloading the \"+\" operator, so let's also give the code for when you need to work with ",r.createElement("code",null,"x")," and ",r.createElement("code",null,"y")," values:"),r.createElement("pre",null,"function drawCurve(points[], t):","\n"," if(points.length==1):","\n"," draw(points[0])","\n"," else:","\n"," newpoints=array(points.size-1)","\n"," for(i=0; i<newpoints.length; i++):","\n"," x = (1-t) * points[i].x + t * points[i+1].x","\n"," y = (1-t) * points[i].y + t * points[i+1].y","\n"," newpoints[i] = new point(x,y)","\n"," drawCurve(newpoints, t)","\n"),r.createElement("p",null,"So what does this do? This draws a point, if the passed list of points is only 1 point long. Otherwise it will create a new list of points that sit at the ",r.createElement("i",null,"t"),' ratios (i.e. the "markers" outlined in the above algorithm), and then call the draw function for this new list.')),r.createElement("p",null,"To see this in action, mouse-over the following sketch. Moving the mouse changes which curve point is explicitly evaluated using de Casteljau's algorithm, moving the cursor left-to-right (or, of course, right-to-left), shows you how a curve is generated using this approach."),r.createElement(i,{preset:"simple",title:"Traversing a curve using de Casteljau's algorithm",setup:e.setup,draw:e.draw}))}},flattening:{title:"Simplified drawing",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"flattening",title:"Simplified drawing",number:"8"}),r.createElement("p",null,'We can also simplify the drawing process by "sampling" the curve at certain points, and then joining those points up with straight lines, a process known as "flattening", as we are reducing a curve to a simple sequence of straight, "flat" lines.'),r.createElement("p",null,'We can do this is by saying "we want X segments", and then sampling the curve at intervals that are spaced such that we end up with the number of segments we wanted. The advantage of this method is that it\'s fast: instead of evaluating 100 or even 1000 curve coordinates, we can sample a much lower number and still end up with a curve that sort-of-kind-of looks good enough. The disadvantage of course is that we lose the precision of working with "the real curve", so we usually can\'t use the flattened for for doing true intersection detection, or curvature alignment.'),r.createElement(i,{preset:"twopanel",title:"Flattening a quadratic curve",setup:e.setupQuadratic,draw:e.drawFlattened,onKeyDown:e.onKeyDown}),r.createElement(i,{preset:"twopanel",title:"Flattening a cubic curve",setup:e.setupCubic,draw:e.drawFlattened,onKeyDown:e.onKeyDown}),r.createElement("p",null,"Try clicking on the sketch and using your up and down arrow keys to lower the number of segments for both the quadratic and cubic curve. You'll notice that for certain curvatures, a low number of segments works quite well, but for more complex curvatures (try this for the cubic curve), a higher number is required to capture the curvature changes properly."),r.createElement("div",{className:"howtocode"},r.createElement("h3",{id:"how-to-implement-curve-flattening"},"How to implement curve flattening"),r.createElement("p",null,"Let's just use the algorithm we just specified, and implement that:"),r.createElement("pre",null,"function flattenCurve(curve, segmentCount):","\n"," step = 1/segmentCount;","\n"," coordinates = [curve.getXValue(0), curve.getYValue(0)]","\n"," for(i=1; i <= segmentCount; i++):","\n"," t = i*step;","\n"," coordinates.push[curve.getXValue(t), curve.getYValue(t)]","\n"," return coordinates;","\n"),r.createElement("p",null,'And done, that\'s the algorithm implemented. That just leaves drawing the resulting "curve" as a sequence of lines:'),r.createElement("pre",null,"function drawFlattenedCurve(curve, segmentCount):","\n"," coordinates = flattenCurve(curve, segmentCount)","\n"," coord = coordinates[0], _coords;","\n"," for(i=1; i < coordinates.length; i++):","\n"," _coords = coordinates[i]","\n"," line(coords, _coords)","\n"," coords = _coords","\n"),r.createElement("p",null,"We start with the first coordinate as reference point, and then just draw lines between each point and its next point.")))}},splitting:{title:"Splitting curves",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"splitting",title:"Splitting curves",number:"9"}),r.createElement("p",null,"With de Casteljau's algorithm we also find all the points we need to split up a Bézier curve into two, smaller curves, which taken together form the original curve. When we construct de Casteljau's skeleton for some value ",r.createElement("code",null,"t"),", the procedure gives us all the points we need to split a curve at that ",r.createElement("code",null,"t")," value: one curve is defined by all the inside skeleton points found prior to our on-curve point, with the other curve being defined by all the inside skeleton points after our on-curve point."),r.createElement(i,{title:"Splitting a curve",setup:e.setupCubic,draw:e.drawSplit}),r.createElement("div",{className:"howtocode"},r.createElement("h3",{id:"implementing-curve-splitting"},"implementing curve splitting"),r.createElement("p",null,"We can implement curve splitting by bolting some extra logging onto the de Casteljau function:"),r.createElement("pre",null,"left=[]","\n","right=[]","\n","function drawCurve(points[], t):","\n"," if(points.length==1):","\n"," left.add(points[0])","\n"," right.add(points[0])","\n"," draw(points[0])","\n"," else:","\n"," newpoints=array(points.size-1)","\n"," for(i=0; i<newpoints.length; i++):","\n"," if(i==0):","\n"," left.add(points[i])","\n"," if(i==newpoints.length-1):","\n"," right.add(points[i+1])","\n"," newpoints[i] = (1-t) * points[i] + t * points[i+1]","\n"," drawCurve(newpoints, t)","\n"),r.createElement("p",null,"After running this function for some value ",r.createElement("code",null,"t"),", the ",r.createElement("code",null,"left")," and ",r.createElement("code",null,"right"),' arrays will contain all the coordinates for two new curves - one to the "left" of our ',r.createElement("code",null,"t"),' value, the other on the "right", of the same order as the original curve, and overlayed exactly on the original curve.')),r.createElement("p",null,"This is best illustrated with an animated graphic (click to play/pause):"),r.createElement(i,{preset:"threepanel",title:"Bézier curve splitting",setup:e.setupCubic,draw:e.drawAnimated,onClick:e.togglePlay}))}},matrixsplit:{title:"Splitting curves using matrices",getContent:function(e){return r.createElement("section",null,r.createElement(a,{name:"matrixsplit",title:"Splitting curves using matrices",number:"10"}),r.createElement("p",null,"Another way to split curves is to exploit the matrix representation of a Bézier curve. In ",r.createElement("a",{href:"#matrix"},"the section on matrices")," we saw that we can represent curves as matrix multiplications. Specifically, we saw these two forms for the quadratic, and cubic curves, respectively (using the reversed Bézier coefficients vector for legibility):"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/e56e78e406d625c2a5ec584216f79a5fee00d8ea.svg",style:{width:"19.65015rem",height:"3.97485rem"}}),r.createElement("p",null,"and"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/01ea4f74c4785a19bedf18034b51510c5ce2ad8f.svg",style:{width:"23.925150000000002rem",height:"5.47515rem"}}),r.createElement("p",null,"Let's say we want to split the curve at some point ",r.createElement("code",null,"t = z"),', forming two new (obviously smaller) Bézier curves. To find the coordinates for these two Bézier curves, we can use the matrix representation and some linear algebra. First, we split out the the actual "point on the curve" information as a new matrix multiplication:'),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d6fa091a86782480968c232ef86513c578030004.svg",style:{width:"48.07485rem",height:"4.05rem"}}),r.createElement("p",null,"and"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d487e1e0181420995be49b25bc6595c9d0360435.svg",style:{width:"60.82515rem",height:"5.54985rem"}}),r.createElement("p",null,"If we could compact these matrices back to a form ",r.createElement("strong",null,"[t values] · [bezier matrix] · [column matrix]"),", with the first two staying the same, then that column matrix on the right would be the coordinates of a new Bézier curve that describes the first segment, from ",r.createElement("code",null,"t = 0")," to ",r.createElement("code",null,"t = z"),". As it turns out, we can do this quite easily, by exploiting some simple rules of linear algebra (and if you don't care about the derivations, just skip to the end of the box for the results!)."),r.createElement("div",{className:"note"},r.createElement("h2",{id:"deriving-new-hull-coordinates"},"Deriving new hull coordinates"),r.createElement("p",null,"Deriving the two segments upon splitting a curve takes a few steps, and the higher the curve order, the more work it is, so let's look at the quadratic curve first:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d4b8355c3f1f80aacfc2766423a30151c5180a02.svg",style:{width:"26.24985rem",height:"4.05rem"}}),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/fe5f623585a9bbb836f54164aecaadd3fc4ec953.svg",style:{width:"17.62515rem",height:"3.97485rem"}}),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/1eb9833685c9189c64d9cbdfdbb24a94e70e493f.svg",style:{width:"17.69985rem",height:"3.97485rem"}}),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/bc85dbb857222546bd30ea559a452fe9f36c8090.svg",style:{width:"17.69985rem",height:"4.05rem"}}),r.createElement("p",null,"We do this, because [",r.createElement("em",null,"M · M",r.createElement("sup",null,"-1")),"] is the identity matrix (a bit like multiplying something by x/x in calculus. It doesn't do anything to the function, but it does allow you to rewrite it to something that may be easier to work with, or can be broken up differently). Adding that as matrix multiplication has no effect on the total formula, but it does allow us to change the matrix sequence [",r.createElement("em",null,"something · M"),"] to a sequence [",r.createElement("em",null,"M · something"),"], and that makes a world of difference: if we know what [",r.createElement("em",null,"M",r.createElement("sup",null,"-1")," · Z · M"),"] is, we can apply that to our coordinates, and be left with a proper matrix representation of a quadratic Bézier curve (which is [",r.createElement("em",null,"T · M · P"),"]), with a new set of coordinates that represent the curve from ",r.createElement("em",null,"t = 0")," to ",r.createElement("em",null,"t = z"),". So let's get computing:"),r.createElement("img",{
className:"LaTeX SVG",src:"images/latex/1dbabc115128a85389cbbcc75fbced48e5a2ca25.svg",style:{width:"45.9rem",height:"4.35015rem"}}),r.createElement("p",null,"Excellent! Now we can form our new quadratic curve:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/2972cd74dab6560ea68189c2e53f247287cbefae.svg",style:{width:"30.45015rem",height:"3.97485rem"}}),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/39b64e07c41ef6d734064f017036f6391321e924.svg",style:{width:"35.17515rem",height:"4.1998500000000005rem"}}),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d615960f862664749c54858520c364efeb4a4c5a.svg",style:{width:"34.79985rem",height:"4.1998500000000005rem"}}),r.createElement("p",null,r.createElement("strong",null,r.createElement("em",null,"Brilliant")),": if we want a subcurve from ",r.createElement("code",null,"t = 0")," to ",r.createElement("code",null,"t = z"),", we can keep the first coordinate the same (which makes sense), our control point becomes a z-ratio mixture of the original control point and the start point, and the new end point is a mixture that looks oddly similar to a bernstein polynomial of degree two, except it uses (z-1) rather than (1-z)... These new coordinates are actually really easy to compute directly!"),r.createElement("p",null,"Of course, that's only one of the two curves. Getting the section from ",r.createElement("code",null,"t = z")," to ",r.createElement("code",null,"t = 1")," requires doing this again. We first observe what what we just did is actually evaluate the general interval [0,",r.createElement("code",null,"z"),"], which we wrote down simplified becuase of that zero, but we actually evaluated this:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/a51e64df3cb31acf32d0ad5814c8c6cff41ae611.svg",style:{width:"27.45rem",height:"3.97485rem"}}),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/0b50cdfed6656e681d5885a14a3af3e67efa4ccb.svg",style:{width:"23.99985rem",height:"4.05rem"}}),r.createElement("p",null,"If we want the interval [",r.createElement("em",null,"z"),",1], we will be evaluating this instead:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/eca8cfda9b7a3f0819ec38acc53f95af67bb26bb.svg",style:{width:"32.625rem",height:"3.97485rem"}}),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/e8c983a3efd47356c971fe46add4d0cdf103cced.svg",style:{width:"30.45015rem",height:"4.1998500000000005rem"}}),r.createElement("p",null,"We're going to do the same trick, to turn ",r.createElement("code",null,"[something · M]")," into ",r.createElement("code",null,"[M · something]"),":"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/a28b6dcc1335de19a065b6a04d8bb45d86122bb7.svg",style:{width:"52.72515rem",height:"4.35015rem"}}),r.createElement("p",null,"So, our final second curve looks like:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/5851c9191acb59456e3706a8f6f1a0f85e691eda.svg",style:{width:"30.74985rem",height:"3.97485rem"}}),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/0333e63f50b3d43067dc299280f70e9eb98711bb.svg",style:{width:"34.875rem",height:"4.1998500000000005rem"}}),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/00a133860115d7a4db4ddf62781b5ae2bffef088.svg",style:{width:"34.79985rem",height:"4.1998500000000005rem"}}),r.createElement("p",null,r.createElement("strong",null,r.createElement("em",null,"Nice")),": we see the same as before; can keep the last coordinate the same (which makes sense), our control point becomes a z-ratio mixture of the original control point and the end point, and the new start point is a mixture that looks oddly similar to a bernstein polynomial of degree two, except it uses (z-1) rather than (1-z). These new coordinates are ",r.createElement("em",null,"also")," really easy to compute directly!")),r.createElement("p",null,"So, using linear algebra rather than de Casteljau's algorithm, we have determined that for any quadratic curve split at some value ",r.createElement("code",null,"t = z"),", we get two subcurves that are described as Bézier curves with simple-to-derive coordinates."),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/5769f44aea3344c32c497a3a77d236f524222b95.svg",style:{width:"40.57515rem",height:"4.1998500000000005rem"}}),r.createElement("p",null,"and"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/1fdde935dc357642358bdf5e632d6539c9d4debd.svg",style:{width:"40.275rem",height:"4.1998500000000005rem"}}),r.createElement("p",null,"We can do the same for cubic curves. However, I'll spare you the actual derivation (don't let that stop you from writing that out yourself, though) and simply show you the resulting new coordinate sets:"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/44db09290062827525a9b23cbaf91e65063d86d7.svg",style:{width:"58.87485rem",height:"5.70015rem"}}),r.createElement("p",null,"and"),r.createElement("img",{className:"LaTeX SVG",src:"images/latex/d6b1abe72bac1b55d184f2c4254769404371d06f.svg",style:{width:"59.025150000000004rem",height:"5.70015rem"}}),r.createElement("p",null,"So, looking at our matrices, did we really need to compute the second segment matrix? No, we didn't. Actually having one segment's matrix means we implicitly have the other: push the values of each row in the matrix ",r.createElement("strong",null,r.createElement("em",null,"Q")),' to the right, with zeroes getting pushed off the right edge and appearing back on the left, and then flip the matrix vertically. Presto, you just "calculated" ',r.createElement("strong",null,r.createElement("em",null,"Q'")),"."),r.createElement("p",null,"Implementing curve splitting this way requires less recursion, and is just straight arithmetic with cached values, so can be cheaper on systems were recursion is expensive. If you're doing computation with devices that are good at matrix multiplication, chopping up a Bézier curve with this method will be a lot faster than applying de Casteljau."))}},reordering:{title:"Unknown title (reordering)",getContent:function(e){return r.createElement("section",null)}},derivatives:{title:"Unknown title (derivatives)",getContent:function(e){return r.createElement("section",null)}},pointvectors:{title:"Unknown title (pointvectors)",getContent:function(e){return r.createElement("section",null)}},components:{title:"Unknown title (components)",getContent:function(e){return r.createElement("section",null)}},extremities:{title:"Unknown title (extremities)",getContent:function(e){return r.createElement("section",null)}},boundingbox:{title:"Unknown title (boundingbox)",getContent:function(e){return r.createElement("section",null)}},aligning:{title:"Unknown title (aligning)",getContent:function(e){return r.createElement("section",null)}},tightbounds:{title:"Unknown title (tightbounds)",getContent:function(e){return r.createElement("section",null)}},inflections:{title:"Unknown title (inflections)",getContent:function(e){return r.createElement("section",null)}},canonical:{title:"Unknown title (canonical)",getContent:function(e){return r.createElement("section",null)}},arclength:{title:"Unknown title (arclength)",getContent:function(e){return r.createElement("section",null)}},arclengthapprox:{title:"Unknown title (arclengthapprox)",getContent:function(e){return r.createElement("section",null)}},tracing:{title:"Unknown title (tracing)",getContent:function(e){return r.createElement("section",null)}},intersections:{title:"Unknown title (intersections)",getContent:function(e){return r.createElement("section",null)}},curveintersection:{title:"Unknown title (curveintersection)",getContent:function(e){return r.createElement("section",null)}},abc:{title:"Unknown title (abc)",getContent:function(e){return r.createElement("section",null)}},moulding:{title:"Unknown title (moulding)",getContent:function(e){return r.createElement("section",null)}},pointcurves:{title:"Unknown title (pointcurves)",getContent:function(e){return r.createElement("section",null)}},catmullconv:{title:"Unknown title (catmullconv)",getContent:function(e){return r.createElement("section",null)}},catmullmoulding:{title:"Unknown title (catmullmoulding)",getContent:function(e){return r.createElement("section",null)}},polybezier:{title:"Unknown title (polybezier)",getContent:function(e){return r.createElement("section",null)}},shapes:{title:"Unknown title (shapes)",getContent:function(e){return r.createElement("section",null)}},projections:{title:"Unknown title (projections)",getContent:function(e){return r.createElement("section",null)}},offsetting:{title:"Unknown title (offsetting)",getContent:function(e){return r.createElement("section",null)}},graduatedoffset:{title:"Unknown title (graduatedoffset)",getContent:function(e){return r.createElement("section",null)}},circles:{title:"Unknown title (circles)",getContent:function(e){return r.createElement("section",null)}},circles_cubic:{title:"Unknown title (circles_cubic)",getContent:function(e){return r.createElement("section",null)}},arcapproximation:{title:"Unknown title (arcapproximation)",getContent:function(e){return r.createElement("section",null)}},bsplines:{title:"Unknown title (bsplines)",getContent:function(e){return r.createElement("section",null)}},comments:{title:"Unknown title (comments)",getContent:function(e){return r.createElement("section",null)}}}},function(e,t,n){e.exports=n(65)},function(e,t,n){!function(){"use strict";var t=n(66),r=function(e){this.curves=[],this._3d=!1,e&&(this.curves=e,this._3d=this.curves[0]._3d)};r.prototype={valueOf:function(){return this.toString()},toString:function(){return t.pointsToString(this.points)},addCurve:function(e){this.curves.push(e),this._3d=this._3d||e._3d},length:function(){return this.curves.map(function(e){return e.length()}).reduce(function(e,t){return e+t})},curve:function(e){return this.curves[e]},bbox:function(){for(var e=this.curves,n=e[0].bbox(),r=1;r
\ No newline at end of file
diff --git a/images/latex/fe5f623585a9bbb836f54164aecaadd3fc4ec953.svg b/images/latex/fe5f623585a9bbb836f54164aecaadd3fc4ec953.svg
new file mode 100644
index 00000000..ec593648
--- /dev/null
+++ b/images/latex/fe5f623585a9bbb836f54164aecaadd3fc4ec953.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/locales/en-GB/content.js b/locales/en-GB/content.js
index 0d0a14a6..dd9e4735 100644
--- a/locales/en-GB/content.js
+++ b/locales/en-GB/content.js
@@ -682,7 +682,7 @@ function drawCurve(points[], t):
1 & t & t^2
\end{bmatrix}
\cdot
- \underset{we\ turn\ handler...}{\underbrace{\kern 2.25em Z \cdot M \kern 2.25em}}
+ \underset{we\ turn\ this...}{\underbrace{\kern 2.25em Z \cdot M \kern 2.25em}}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
@@ -695,7 +695,7 @@ function drawCurve(points[], t):
1 & t & t^2
\end{bmatrix}
\cdot
- \underset{...into\ handler...}{\underbrace{ M \cdot M^{-1} \cdot Z \cdot M }}
+ \underset{...into\ this...}{\underbrace{ M \cdot M^{-1} \cdot Z \cdot M }}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
diff --git a/make-locales.js b/make-locales.js
index bbeed95a..e7b68642 100644
--- a/make-locales.js
+++ b/make-locales.js
@@ -197,7 +197,7 @@ sections.forEach((cname, number) => {
// Now, form a JSX resource for this locale.
var bcode = JSON.stringify(content, false, 2);
bcode = bcode.replace(/"/g, "function(handler) { return ")
- .replace(/this\./g, "handler.")
+ .replace(/this\.(\w)/g, "handler.$1")
.replace(/<\/section>"(,?)/g, "; }$1\n")
.replace(/\\"/g,'"')
.replace(/\\n/g,'\n')