# Category Archives: bezier

Being able to draw smooth lines that connect arbitrary points is something that I find myself needing very frequently. This is a port of an old snippet that does just that. By averaging control points of a quadratic bezier curve we ensure that our resulting Bezier curves are always smooth.

The key can be seen here with the `bezierSkin` function. It draws either a closed or open curve.

```1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 // array of xy coords, closed boolean function bezierSkin(bez, closed = true) { var avg = calcAvgs(bez), leng = bez.length, i, n;   if (closed) { c.moveTo(avg[0], avg[1]); for (i = 2; i < leng; i += 2) { n = i + 1; c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]); } c.quadraticCurveTo(bez[0], bez[1], avg[0], avg[1]); } else { c.moveTo(bez[0], bez[1]); c.lineTo(avg[0], avg[1]); for (i = 2; i < leng - 2; i += 2) { n = i + 1; c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]); } c.lineTo(bez[leng - 2], bez[leng - 1]); } }     // create anchor points by averaging the control points function calcAvgs(p) { var avg = [], leng = p.length, prev; for (var i = 2; i < leng; i++) { prev = i - 2; avg.push((p[prev] + p[i]) / 2); } // close avg.push((p[0] + p[leng - 2]) / 2); avg.push((p[1] + p[leng - 1]) / 2); return avg; }```

The control points are then averaged to ensure that the curve contains no sharp angles.

Also posted in Graphics, Math, arrays, graphics algorithms, html5, javascript | Tagged , , , , | Leave a comment

Actionscript:
1. [SWF(backgroundColor=0x333333, width=800, height=600)];
2. x = stage.stageWidth / 2;
3. y = stage.stageHeight / 2;
4. var verts:Vector.<Number>  = new Vector.<Number>();
5. var tVerts:Vector.<Number> = new Vector.<Number>();
6. var pVerts:Vector.<Number> = new Vector.<Number>();
7. var uvts:Vector.<Number> = new Vector.<Number>();
8.
9. var igraph:Vector.<IGraphicsData> = new Vector.<IGraphicsData>();
10.
11. var tVect:Vector3D = new Vector3D();
12. var m:Matrix3D = new Matrix3D();
13. for (var i:int = 0; i<200; i++){
14.     for (var j:int = 0; j<3; j++){
15.         tVect.x = Math.random() * 0.15;
16.         m.identity();
17.         m.appendRotation(Math.random()*360, Vector3D.X_AXIS);
18.         m.appendRotation(Math.random()*360, Vector3D.Y_AXIS);
19.         m.appendRotation(Math.random()*360, Vector3D.Z_AXIS);
20.         tVect = m.transformVector(tVect);
21.         verts.push(tVect.x, tVect.y, tVect.z);
22.     }
23.
24.     var stroke:GraphicsStroke = new GraphicsStroke();
25.
26.     var col:int = [0xFFFFFF, 0x000000, 0xFFCC00, 0xFF0000][int(Math.random() * 4)];
27.
28.     stroke.thickness = 2+(Math.random()*Math.random())*8;
29.     var mg:Matrix = new Matrix();
32.
33.     var bez:GraphicsPath = new GraphicsPath();
34.     bez.commands = Vector.<int>([1, 3]);
35.     igraph.push(stroke);
36.     igraph.push(bez);
37. }
38.
39. var perspective:PerspectiveProjection = new PerspectiveProjection();
40. perspective.fieldOfView = 45;
41. var trans:Matrix3D = new Matrix3D();
42. var proj:Matrix3D = perspective.toMatrix3D();
43. var dx:Number = 0, dy:Number = 0;
44.
46. function onLoop(evt:Event):void {
47.     dx += (mouseX - dx) / 4;
48.     dy += (mouseY - dy) / 4;
49.
50.     trans.identity();
51.     trans.appendRotation(dy, Vector3D.X_AXIS);
52.     trans.appendRotation(dx, Vector3D.Y_AXIS);
53.     trans.appendTranslation(0,0,0.5);
54.     trans.transformVectors(verts, tVerts);
55.     Utils3D.projectVectors(proj, tVerts, pVerts, uvts);
56.
57.     var inc:int = 0;
58.     for (var i:int = 1; i<igraph.length; i+=2){
59.         GraphicsPath(igraph[i]).data = pVerts.slice(inc, inc + 6);
60.         inc += 6;
61.     }
62.
63.     graphics.clear();
64.     graphics.drawGraphicsData(igraph);
65. }

A variation on yesterdays post - this makes use of GraphicsGradientFill to add some depth...

Check out the swf...

## 3D Bezier

Actionscript:
1. [SWF(backgroundColor=0x000000, width=800, height=600)];
2. x = stage.stageWidth / 2;
3. y = stage.stageHeight / 2;
4. var verts:Vector.<Number>  = new Vector.<Number>();
5. var tVerts:Vector.<Number> = new Vector.<Number>();
6. var pVerts:Vector.<Number> = new Vector.<Number>();
7. var uvts:Vector.<Number> = new Vector.<Number>();
8.
9. var igraph:Vector.<IGraphicsData> = new Vector.<IGraphicsData>();
10.
11. var tVect:Vector3D = new Vector3D();
12. var m:Matrix3D = new Matrix3D();
13. for (var i:int = 0; i<300; i++){
14.     for (var j:int = 0; j<3; j++){
15.         tVect.x = Math.random() * 0.15;
16.         m.identity();
17.         m.appendRotation(Math.random()*360, Vector3D.X_AXIS);
18.         m.appendRotation(Math.random()*360, Vector3D.Y_AXIS);
19.         m.appendRotation(Math.random()*360, Vector3D.Z_AXIS);
20.         tVect = m.transformVector(tVect);
21.         verts.push(tVect.x, tVect.y, tVect.z);
22.     }
23.
24.
25.     var stroke:IGraphicsData = new GraphicsStroke();
26.
27.     var col:int = [0xFFFFFF, 0x000000, 0xFFCC00, 0xFF0000][int(Math.random() * 4)];
28.     with (stroke) thickness = (Math.random()*Math.random())*10, fill = new GraphicsSolidFill(col);
29.
30.     var bez:GraphicsPath = new GraphicsPath();
31.     bez.commands = Vector.<int>([1, 3]);
32.     igraph.push(stroke);
33.     igraph.push(bez);
34. }
35.
36. var perspective:PerspectiveProjection = new PerspectiveProjection();
37. perspective.fieldOfView = 45;
38. var trans:Matrix3D = new Matrix3D();
39. var proj:Matrix3D = perspective.toMatrix3D();
40. var dx:Number = 0, dy:Number = 0;
41.
43. function onLoop(evt:Event):void {
44.     dx += (mouseX - dx) / 4;
45.     dy += (mouseY - dy) / 4;
46.
47.     trans.identity();
48.     trans.appendRotation(dy, Vector3D.X_AXIS);
49.     trans.appendRotation(dx, Vector3D.Y_AXIS);
50.     trans.appendTranslation(0,0,0.5);
51.     trans.transformVectors(verts, tVerts);
52.     Utils3D.projectVectors(proj, tVerts, pVerts, uvts);
53.
54.     var inc:int = 0;
55.     for (var i:int = 1; i<igraph.length; i+=2){
56.         GraphicsPath(igraph[i]).data = pVerts.slice(inc, inc + 6);
57.         inc += 6;
58.     }
59.
60.     graphics.clear();
61.     graphics.drawGraphicsData(igraph);
62. }

After a few hours of trying to get a super optimized fp10 z-sorting demo happening, I gave up (for now) and decided to just let loose with something easy. The result is this 3D Bezier example...

Check out the swf...

Also posted in 3D, Graphics | Tagged , , , | 3 Comments

## Recursive 2D Structure

Actionscript:
1. [SWF(width = 600, height = 700, frameRate=24)]
2. var canvas:BitmapData = new BitmapData(stage.stageWidth,stage.stageHeight,false, 0xFFFFFF);
4.
5. var maxBranches:int = 600;
6. var branches:int = 0;
7. var startX:Number = 300
8. makeBranch(startX,690,30,-60, 60);
9.
10. function makeBranch(xp:Number, yp:Number, step:Number, min:Number, max:Number):void {
11.     var vectors:Shape = Shape(addChild(new Shape()));
12.     var cX:Number, cY:Number, eX:Number, eY:Number
13.     var dcX:Number=xp, dcY:Number=yp, deX:Number=xp, deY:Number=yp;
14.     var theta:Number = (min + Math.random()*(max-min) - 90) * Math.PI / 180;
15.     cX = xp + step * Math.cos(theta);
16.     cY = yp + step * Math.sin(theta);
17.     theta = (min + Math.random()*(max-min)-90) * Math.PI / 180;
18.     eX = cX + step * Math.cos(theta);
19.     eY = cY + step * Math.sin(theta);
20.     var run:Function = function():void{
21.          dcX +=  (cX - dcX) / 2;
22.          dcY +=  (cY - dcY) / 2;
23.          deX +=  (eX - deX) / 8;
24.          deY +=  (eY - deY) / 8;
25.          with(vectors.graphics){
26.               clear();
27.               beginFill(0xFFFFFF,0.8);
28.               lineStyle(0,0x000000,0.8);
29.               moveTo(startX, yp);
30.               lineTo(xp, yp);
31.               curveTo(dcX, dcY, deX, deY);
32.               lineTo(startX, deY);
33.          }
34.          if (Math.abs(dcX - cX) <1 && Math.abs(deX - eX) <1 && Math.abs(dcY - cY) <1 && Math.abs(deY - eY) <1){
35.              canvas.draw(vectors);
36.              removeChild(vectors);
37.              if (branches <maxBranches){
38.                  setTimeout(makeBranch, 10, deX, deY, step - Math.random(), -90, 90);
39.                  branches++;
40.                  if (int(Math.random()*2) == 1){
41.                     setTimeout(makeBranch, 10, deX, deY, step - Math.random()*3, -90, 90);
42.                     branches++;
43.                  }
44.              }
45.          }else{
46.              setTimeout(arguments.callee,  1000 / 24);
47.          }
48.     }();
49. }

This snippet uses a technique similar to what you might use to create a recursive tree. A bit of additional logic is added for bezier branches, filled shapes and animation.

WARNING: may run slow on older machines

Have a look at the swf...

Also posted in BitmapData, functions, misc, motion | Tagged , , | 7 Comments

## Bezier Intersections (not a snippet)

Actionscript:
1. var resolution:Number = .03;
2. var pointNum:int = Math.ceil(1 / resolution);
3.
4. var bezA:Array = new Array();
5. populateArray(bezA);
6. var a:Sprite = dot(100, 200);
7. var b:Sprite = dot(200, 100);
8. var c:Sprite = dot(300, 200);
9.
10. var bezB:Array = new Array();
11. populateArray(bezB);
12. var d:Sprite = dot(300, 100, 0xCCCC00);
13. var e:Sprite = dot(120, 130, 0xCCCC00);
14. var f:Sprite = dot(200, 300, 0xCCCC00);
15.
17. function onLoop(evt:Event):void {
18.       with(graphics){
19.           clear();
20.           lineStyle(0, 0x000000);
21.
22.           // calc and draw bezier points
23.           drawBezier(bezA, a, b, c);
24.           drawBezier(bezB, d, e, f);
25.
26.           // calc collisions
27.           var intersections:Array = calculateIntersection(bezA, bezB);
28.
29.            // draw collisions
30.            beginFill(0xFF0000);
31.            if (intersections.length> 0){
32.                 for (var i:int = 0; i<intersections.length; i++){
33.                     drawCircle(intersections[i].x, intersections[i].y, 3);
34.                 }
35.            }
36.            endFill();
37.      }
38. }
39.
40. function populateArray(a:Array):void {
41.     for (var i:int = 0; i<pointNum; i++){
42.         a.push(new Point());
43.     }
44. }
45.
46. function drawBezier(bez:Array, a:Sprite, b:Sprite, c:Sprite):void{
47.      with(graphics){
48.          bezier(bez, a.x, a.y, b.x, b.y, c.x, c.y);
49.          var leng:Number = bez.length;
50.          moveTo(bez[0].x, bez[0].y);
51.          for (var i:int = 1; i<leng; i++){
52.              lineTo(bez[i].x, bez[i].y);
53.          }
54.      }
55. }
56.
57. function bezier(bez:Array, x1:Number, y1:Number, x2:Number, y2:Number, x3:Number, y3:Number):void {
58.             var b:Number, a2:Number, ab2:Number, b2:Number;
59.             var pnt:Point;
60.             var inc:int = 0;
61.             for (var a:Number = 0; a <=1; a+=resolution) {
62.                 b= 1 - a;
63.                 a2 = a * a;
64.                 ab2 = a * b * 2;
65.                 b2 = b * b;
66.                 pnt = bez[inc];
67.                 pnt.x = a2 * x1 + ab2 * x2  + b2 * x3;
68.                 pnt.y = a2 * y1 + ab2 * y2 + b2 * y3;
69.                 inc++;
70.             }
71. }
72.
73. function calculateIntersection(bezA:Array, bezB:Array):Array {
74.     var intersections:Array = new Array();
75.     var ip:Point;
76.     var aLength:int = bezA.length;
77.     var bLength:int = bezB.length;
78.     var p1:Point, p2:Point, p3:Point, p4:Point;
79.
80.     // compare all line segments and check for
81.     // intersections
82.     for (var i:int = 1; i<aLength; i++){
83.         p1 = bezA[i - 1];
84.         p2 = bezA[i];
85.         for (var j:int = 1; j<bLength; j++){
86.             p3 = bezB[j - 1];
87.             p4 = bezB[j];
88.             ip = intersection(p1, p2, p3, p4);
89.             if (ip){
90.                 intersections.push(ip.clone());
91.             }
92.         }
93.     }
94.     return intersections;
95. }
96.
97. var ip:Point = new Point();
98.
99. function intersection(p1:Point, p2:Point, p3:Point, p4:Point):Point {
100.
101.     var nx:Number, ny:Number, dn:Number;
102.     var x4_x3:Number = p4.x - p3.x;
103.     var pre2:Number = p4.y - p3.y;
104.     var pre3:Number = p2.x - p1.x;
105.     var pre4:Number = p2.y - p1.y;
106.     var pre5:Number = p1.y - p3.y;
107.     var pre6:Number = p1.x - p3.x;
108.
109.     nx = x4_x3 * pre5 - pre2 * pre6;
110.     ny = pre3 * pre5 - pre4 * pre6;
111.     dn = pre2 * pre3 - x4_x3 * pre4;
112.
113.     if (dn == 0){
114.         return null
115.     }
116.     nx /= dn;
117.     ny /= dn;
118.     // has intersection
119.     if(nx>= 0 && nx <= 1 && ny>= 0 && ny <= 1){
120.         ny = p1.y + nx * pre4;
121.         nx = p1.x + nx * pre3;
122.         ip.x = nx;
123.         ip.y = ny;
124.     }else{
125.          return null;
126.     }
127.     return ip
128. }
129.
130. // draggable dot
131. function dot(xp:Number, yp:Number, col:uint = 0x507399, noDrag:Boolean = false):Sprite {
132.     var s:Sprite = Sprite(addChild(new Sprite));
133.     s.x = xp;
134.     s.y = yp;
135.     with(s.graphics) beginFill(col), drawCircle(0,0,5);
136.     if (!noDrag){
137.         s.buttonMode = true;
139.     }
140.     return s;
141. }
142. function onDrag(evt:MouseEvent):void {
143.     evt.currentTarget.startDrag()
144. }
145.
147. function onUp(evt:MouseEvent):void{
148.     stopDrag();
149. }

This is not a snippet, but it uses some functions that could be considered snippets on their own. Take a look at the swf here:

Bezier Intersection Demo

This demo show a brute force method for getting the coordinates of all intersections between two given quadtratic bezier curves. The approach is pretty simple, each bezier is actually made up of a series of line segments. The resolution variable determines how many line segments make up one bezier curve. By default I use a resolution of 0.03 (~34 lines)... the higher the resolution decimal value, the fewer the number of line segments. I then use a slight variation on yesterdays line segment intersection function to test all existing line segments for intersections.

This is a brute for approach with lots of room for improvement. But in the scenario that you only need to check a few bezier curves for intersection this approach works quite nicely. I don't recommend this approach if your trying to create a complex vector drawing tool with boolean vector operations etc...

It's easy to get an idea of the different approaches to this problem with a little googling. One example I found is from Graphics Gems:

http://tog.acm.org/GraphicsGems/gemsiv/curve_isect/

it's been awhile, but I believe this website also contains information related to this topic:

http://algorithmist.wordpress.com/

## Animate Along Bezier Curve

Actionscript:
1. var circle:Shape = Shape(addChild(new Shape));
2. with(circle.graphics) beginFill(0x000000), drawCircle(0,0,5);
3.
4. var bezierPoint:Point = new Point();
5. function bezier(a:Number, x1:Number, y1:Number, x2:Number, y2:Number, x3:Number, y3:Number):void {
6.         var b:Number =1-a;
7.         var pre1:Number=a*a;
8.         var pre2:Number=2*a*b;
9.         var pre3:Number=b*b;
10.         bezierPoint.x = pre1*x1 + pre2*x2  + pre3*x3;
11.         bezierPoint.y = pre1*y1 + pre2*y2 + pre3*y3;
12. }
13.
14. var inc:Number = 0;
15. var theta:Number = 0;
16.
18. function onLoop(evt:Event):void {
19.
20.      graphics.clear();
21.      graphics.lineStyle(0,0xFF0000);
22.      graphics.moveTo(200,200);
23.      graphics.curveTo(mouseX, mouseY, 400, 200);
24.
25.     theta+= .05;
26.     inc = .5 + .5*Math.sin(theta);
27.
28.     bezier(inc, 200, 200, mouseX, mouseY, 400, 200);
29.     circle.x = bezierPoint.x;
30.     circle.y = bezierPoint.y;
31. }

The above animates a circle along a quadratic bezier curve. This snippet was written in response to a question spurned by some of the recent bezier posts. I used Math.sin() to animate the circle but you could just as easily use modulus... simply replace lines 25-26 with the following:

Actionscript:
1. inc += .03;
2. inc %= 1;

Also posted in Graphics, graphics algorithms, motion | Tagged , | 3 Comments

## Bezier Skin (smooth curveTo())

Actionscript:
1. // graphics, vector of xy coords, closed boolean
2. function bezierSkin(g:Graphics, bez:Vector.<Number>, closed:Boolean = true):void {
3.     var avg:Vector.<Number> = calcAvgs(bez);
4.     var i:int, n:int;
5.     var leng:int = bez.length;
6.
7.     if (closed){
8.         g.moveTo(avg[0], avg[1]);
9.         for (i = 2; i <leng; i+=2) {
10.             n=i+1;
11.             g.curveTo(bez[i], bez[n], avg[i], avg[n]);
12.         }
13.         g.curveTo(bez[0], bez[1], avg[0], avg[1]);
14.     }else{
15.         g.moveTo(bez[0], bez[1]);
16.         g.lineTo(avg[0], avg[1]);
17.         for (i = 2; i <leng-2; i+=2) {
18.             n=i+1;
19.             g.curveTo(bez[i], bez[n], avg[i], avg[n]);
20.         }
21.         g.lineTo(bez[ leng - 2], bez[ leng - 1]);
22.     }
23. }
24.
25. // create anchor points by averaging the control points
26. function calcAvgs(v:Vector.<Number>):Vector.<Number> {
27.     var avg:Vector.<Number> = new Vector.<Number>();
28.     var leng:int=v.length;
29.     for (var i:int = 2; i<leng; i+=1) {
30.         var prev:Number=i-2;
31.         avg.push( (v[prev] + v[i]) / 2 );
32.     }
33.     // close
34.     avg.push( (v[0] + v[ leng - 2]) / 2 );
35.     avg.push( (v[1] + v[ leng - 1]) / 2 );
36.     return avg;
37. }
38.
39. // test out the functions:
40.
41. graphics.lineStyle(0,0x000000);
42.
43. // draw a very rounded rect
44. bezierSkin(graphics, Vector.<Number>([100,100,200,100,200,200,100,200]) );
45.
46. // draw a random curve with 10 xy coords
47. var rnd:Vector.<Number> = new Vector.<Number>();
48. for (var i:int = 0; i<20; i++) rnd.push(Math.random()*100);
49. bezierSkin(graphics, rnd);
50.
51. // erase everything and allow the used to draw a smooth curve with the mouse
53. var index:int = 0;
54. var pnts:Vector.<Number> = new Vector.<Number>();
55.
56. function onLoop(evt:MouseEvent):void {
57.      pnts.push(mouseX);
58.      pnts.push(mouseY);
59.
60.      graphics.clear();
61.      graphics.lineStyle(0,0x000000);
62.
63.       // draw a smooth curved line
64.      bezierSkin(graphics,pnts,false);
65. }

The above code uses averaging to generate anchor points for curveTo() calls. This is an easy way to always draw smooth curves.

This is an old one that I've seen floating around since the drawing api first came out... but I it's an important snippet, so I figured I'd post it.

Actionscript:
1. var canvas:BitmapData=new BitmapData(280,280,false,0x000000);
3. var color:uint;
4. // anchor x1, anchor y1,
5. // control-handle x2, control-handle y2,
6. // anchor x3, anchor y3, [resolution incremental value between 0-1]
7. function quadBezier(x1:Number, y1:Number, x2:Number, y2:Number, x3:Number, y3:Number, resolution:Number=.03):void {
8.     var b:Number,pre1:Number,pre2:Number,pre3:Number,pre4:Number;
9.     for (var a:Number = 0; a <1;a+=resolution) {
10.
11.         b=1-a;
12.         pre1=(a*a);
13.         pre2=2*a*b;
14.         pre3=(b*b);
15.
16.         canvas.setPixel(pre1*x1 + pre2*x2  + pre3*x3 ,
17.                                      pre1*y1 + pre2*y2 + pre3*y3, color);
18.     }
19. }
20.
21. // draw a few
22. color = 0xFFFFFF;
23.
24. for (var i:int = 0; i<20; i++){
25. quadBezier(40,100, 150 , 20 + i * 10 , 200, 100,.01);
26. }
27.
28. color = 0xFF0000;
29.
30. for (i= 0; i<20; i++){
31. quadBezier(150,200, 100 + i * 10, 100  , 120, 30,.01);
32. }

The above demos a function that draws quadratic bezier curves using setPixel().

One of the first posts on this site was a snippet that used setPixel() to draw a cubic bezier curve. I recently needed to do the exact same thing but I wanted to use a quadratic bezier... I knew I had the code laying around somewhere, but I couldn't seem to find it so I just looked on wikipedia and changed the previous cubicBezier() function accordingly.

Also posted in BitmapData, graphics algorithms, pixel manipulation, setPixel | Tagged , , | 2 Comments

## setPixel() Cubic Bezier

Actionscript:
1. var canvas:BitmapData=new BitmapData(280,280,false,0x000000);
3.
4. var color:uint;
5.
6. // anchor x1, anchor y1,
7. // control-handle x2, control-handle y2,
8. // anchor x3, anchor y3, control-handle x4,
9. // control-handle y4, [resolution incremental value between 0-1]
10. function cubicBezier(x1:Number, y1:Number, x2:Number, y2:Number,
11.                           x3:Number, y3:Number, x4:Number, y4:Number, resolution:Number=.03):void {
12.
13.     var b:Number,pre1:Number,pre2:Number,pre3:Number,pre4:Number;
14.     for (var a = 0; a <1; a+=resolution) {
15.         b=1-a;
16.         pre1=(a*a*a);
17.         pre2=3*(a*a)*b;
18.         pre3=3*a*(b*b);
19.         pre4=(b*b*b);
20.         canvas.setPixel(pre1*x1 + pre2*x2 + pre3*x4 + pre4*x3 ,
21.                          pre1*y1 + pre2*y2 + pre3*y4 + pre4*y3, color);
22.     }
23. }
24.
25. // draw a few
26.
27. color = 0xFFFFFF;
28.
29. cubicBezier(100,100,
30.                     150, 50,
31.                     200, 100,
32.                     150, 150);
33.
34. color = 0xFF0000;
35.
36. cubicBezier(10,10,
37.                     150, 10,
38.                     200, 200,
39.                     250, 150,
40.                 .01);
41. /*
42. WARNING: This code was written for fun. Use at your own risk.
43. */

This draws cubic bezier curves with setPixel. I'm pretty sure I picked up the original math from somewhere on the processing forums in '03 or '04...