如何绘制一条经过多点的贝赛尔曲线

riadevID: 

比如屏幕上有N个点,我们需要用线将他们连接起来,如果是直线很好办,直接用moveTo和lineTo就搞定了。但如果是曲线呢,比如我们在绘制图表的过程中,需要用贝赛尔曲线将N个点连接,怎么办呢?

如果你认为用graphics.curveTo能搞定就错了,因为难以计算控制点,你无法连接成一条平滑的曲线。

好在还有第三方的类库CubicBezier,看它完成的效果:

使用方法

  1. import flash.display.MovieClip;
  2. import flash.geom.Point;
  3.  
  4. this.graphics.lineStyle(3,0x000000,1);
  5. var points:Array = [];
  6. for(var i:uint = 1;i<8;i++) {
  7. var m:MovieClip = this["m"+i];
  8. points.push(new Point(m.x,m.y));
  9. }
  10. CubicBezier.curveThroughPoints(this.graphics,points);

  1. package{
  2.  
  3. import flash.display.Graphics;
  4. import flash.geom.Point;
  5. import fl.motion.BezierSegment;
  6.  
  7. public class CubicBezier{
  8.  
  9. /* pubic static function drawCurve
  10. * Draws a single cubic Bézier curve
  11. * @param:
  12. * g:Graphics -Graphics on which to draw the curve
  13. * p1:Point -First point in the curve
  14. * p2:Point -Second point (control point) in the curve
  15. * p3:Point -Third point (control point) in the curve
  16. * p4:Point -Fourth point in the curve
  17. * @return:
  18. */
  19. public static function drawCurve(g:Graphics, p1:Point, p2:Point, p3:Point, p4:Point):void{
  20. var bezier = new BezierSegment(p1,p2,p3,p4); // BezierSegment using the four points
  21. g.moveTo(p1.x,p1.y);
  22. // Construct the curve out of 100 segments (adjust number for less/more detail)
  23. for (var t=.01;t<1.01;t+=.01){
  24. var val = bezier.getValue(t); // x,y on the curve for a given t
  25. g.lineTo(val.x,val.y);
  26. }
  27. }
  28.  
  29. /* public static function curveThroughPoints
  30. * Draws a smooth curve through a series of points. For a closed curve, make the first and last points the same.
  31. * @param:
  32. * g:Graphics -Graphics on which to draw the curve
  33. * p:Array -Array of Point instances
  34. * z:Number -A factor (between 0 and 1) to reduce the size of curves by limiting the distance of control points from anchor points.
  35. * For example, z=.5 limits control points to half the distance of the closer adjacent anchor point.
  36. * I put the option here, but I recommend sticking with .5
  37. * angleFactor:Number -Adjusts the size of curves depending on how acute the angle between points is. Curves are reduced as acuteness
  38. * increases, and this factor controls by how much.
  39. * 1 = curves are reduced in direct proportion to acuteness
  40. * 0 = curves are not reduced at all based on acuteness
  41. * in between = the reduction is basically a percentage of the full reduction
  42. * moveTo:Bollean -Specifies whether to move to the first point in the curve rather than continuing drawing
  43. * from wherever drawing left off.
  44. * @return:
  45. */
  46. public static function curveThroughPoints(g:Graphics, points:Array/*of Points*/, z:Number = .5, angleFactor:Number = .75, moveTo:Boolean = true):void{
  47. try {
  48. var p:Array = points.slice(); // Local copy of points array
  49. var duplicates:Array = new Array(); // Array to hold indices of duplicate points
  50. // Check to make sure array contains only Points
  51. for (var i=0; i<p.length; i++){
  52. if (!(p[i] is Point)){
  53. throw new Error("Array must contain Point objects");
  54. }
  55. // Check for the same point twice in a row
  56. if (i > 0){
  57. if (p[i].x == p[i-1].x && p[i].y == p[i-1].y){
  58. duplicates.push(i); // add index of duplicate to duplicates array
  59. }
  60. }
  61. }
  62. // Loop through duplicates array and remove points from the points array
  63. for (i=duplicates.length-1; i>=0; i--){
  64. p.splice(duplicates[i],1);
  65. }
  66. // Make sure z is between 0 and 1 (too messy otherwise)
  67. if (z <= 0){
  68. z = .5;
  69. } else if (z > 1){
  70. z = 1;
  71. }
  72. // Make sure angleFactor is between 0 and 1
  73. if (angleFactor < 0){
  74. angleFactor = 0;
  75. } else if (angleFactor > 1){
  76. angleFactor = 1;
  77. }
  78.  
  79. //
  80. // First calculate all the curve control points
  81. //
  82.  
  83. // None of this junk will do any good if there are only two points
  84. if (p.length > 2){
  85. // Ordinarily, curve calculations will start with the second point and go through the second-to-last point
  86. var firstPt = 1;
  87. var lastPt = p.length-1;
  88. // Check if this is a closed line (the first and last points are the same)
  89. if (p[0].x == p[p.length-1].x && p[0].y == p[p.length-1].y){
  90. // Include first and last points in curve calculations
  91. firstPt = 0;
  92. lastPt = p.length;
  93. }
  94.  
  95. var controlPts:Array = new Array(); // An array to store the two control points (of a cubic Bézier curve) for each point
  96. // Loop through all the points (except the first and last if not a closed line) to get curve control points for each.
  97. for (i=firstPt; i<lastPt; i++) {
  98.  
  99. // The previous, current, and next points
  100. var p0 = (i-1 < 0) ? p[p.length-2] : p[i-1]; // If the first point (of a closed line), use the second-to-last point as the previous point
  101. var p1 = p[i];
  102. var p2 = (i+1 == p.length) ? p[1] : p[i+1]; // If the last point (of a closed line), use the second point as the next point
  103. var a = Point.distance(p0,p1); // Distance from previous point to current point
  104. if (a < 0.001) a = .001; // Correct for near-zero distances, a cheap way to prevent division by zero
  105. var b = Point.distance(p1,p2); // Distance from current point to next point
  106. if (b < 0.001) b = .001;
  107. var c = Point.distance(p0,p2); // Distance from previous point to next point
  108. if (c < 0.001) c = .001;
  109. var cos = (b*b+a*a-c*c)/(2*b*a);
  110. // Make sure above value is between -1 and 1 so that Math.acos will work
  111. if (cos < -1) cos = -1;
  112. else if (cos > 1) cos = 1;
  113. var C = Math.acos(cos); // Angle formed by the two sides of the triangle (described by the three points above) adjacent to the current point
  114. // Duplicate set of points. Start by giving previous and next points values RELATIVE to the current point.
  115. var aPt = new Point(p0.x-p1.x,p0.y-p1.y);
  116. var bPt = new Point(p1.x,p1.y);
  117. var cPt = new Point(p2.x-p1.x,p2.y-p1.y);
  118. /*
  119. We'll be adding adding the vectors from the previous and next points to the current point,
  120. but we don't want differing magnitudes (i.e. line segment lengths) to affect the direction
  121. of the new vector. Therefore we make sure the segments we use, based on the duplicate points
  122. created above, are of equal length. The angle of the new vector will thus bisect angle C
  123. (defined above) and the perpendicular to this is nice for the line tangent to the curve.
  124. The curve control points will be along that tangent line.
  125. */
  126. if (a > b){
  127. aPt.normalize(b); // Scale the segment to aPt (bPt to aPt) to the size of b (bPt to cPt) if b is shorter.
  128. } else if (b > a){
  129. cPt.normalize(a); // Scale the segment to cPt (bPt to cPt) to the size of a (aPt to bPt) if a is shorter.
  130. }
  131. // Offset aPt and cPt by the current point to get them back to their absolute position.
  132. aPt.offset(p1.x,p1.y);
  133. cPt.offset(p1.x,p1.y);
  134. // Get the sum of the two vectors, which is perpendicular to the line along which our curve control points will lie.
  135. var ax = bPt.x-aPt.x; // x component of the segment from previous to current point
  136. var ay = bPt.y-aPt.y;
  137. var bx = bPt.x-cPt.x; // x component of the segment from next to current point
  138. var by = bPt.y-cPt.y;
  139. var rx = ax + bx; // sum of x components
  140. var ry = ay + by;
  141. // Correct for three points in a line by finding the angle between just two of them
  142. if (rx == 0 && ry == 0){
  143. rx = -bx; // Really not sure why this seems to have to be negative
  144. ry = by;
  145. }
  146. // Switch rx and ry when y or x difference is 0. This seems to prevent the angle from being perpendicular to what it should be.
  147. if (ay == 0 && by == 0){
  148. rx = 0;
  149. ry = 1;
  150. } else if (ax == 0 && bx == 0){
  151. rx = 1;
  152. ry = 0;
  153. }
  154. var r = Math.sqrt(rx*rx+ry*ry); // length of the summed vector - not being used, but there it is anyway
  155. var theta = Math.atan2(ry,rx); // angle of the new vector
  156.  
  157. var controlDist = Math.min(a,b)*z; // Distance of curve control points from current point: a fraction the length of the shorter adjacent triangle side
  158. var controlScaleFactor = C/Math.PI; // Scale the distance based on the acuteness of the angle. Prevents big loops around long, sharp-angled triangles.
  159. controlDist *= ((1-angleFactor) + angleFactor*controlScaleFactor); // Mess with this for some fine-tuning
  160. var controlAngle = theta+Math.PI/2; // The angle from the current point to control points: the new vector angle plus 90 degrees (tangent to the curve).
  161. var controlPoint2 = Point.polar(controlDist,controlAngle); // Control point 2, curving to the next point.
  162. var controlPoint1 = Point.polar(controlDist,controlAngle+Math.PI); // Control point 1, curving from the previous point (180 degrees away from control point 2).
  163. // Offset control points to put them in the correct absolute position
  164. controlPoint1.offset(p1.x,p1.y);
  165. controlPoint2.offset(p1.x,p1.y);
  166. /*
  167. Haven't quite worked out how this happens, but some control points will be reversed.
  168. In this case controlPoint2 will be farther from the next point than controlPoint1 is.
  169. Check for that and switch them if it's true.
  170. */
  171. if (Point.distance(controlPoint2,p2) > Point.distance(controlPoint1,p2)){
  172. controlPts[i] = new Array(controlPoint2,controlPoint1); // Add the two control points to the array in reverse order
  173. } else {
  174. controlPts[i] = new Array(controlPoint1,controlPoint2); // Otherwise add the two control points to the array in normal order
  175. }
  176. // Uncomment to draw lines showing where the control points are.
  177. /*
  178. g.moveTo(p1.x,p1.y);
  179. g.lineTo(controlPoint2.x,controlPoint2.y);
  180. g.moveTo(p1.x,p1.y);
  181. g.lineTo(controlPoint1.x,controlPoint1.y);
  182. */
  183. }
  184.  
  185. //
  186. // Now draw the curve
  187. //
  188. // If moveTo condition is false, this curve can connect to a previous curve on the same graphics.
  189. if (moveTo) g.moveTo(p[0].x, p[0].y);
  190. else g.lineTo(p[0].x, p[0].y);
  191. // If this isn't a closed line
  192. if (firstPt == 1){
  193. // Draw a regular quadratic Bézier curve from the first to second points, using the first control point of the second point
  194. g.curveTo(controlPts[1][0].x,controlPts[1][0].y,p[1].x,p[1].y);
  195. }
  196. var straightLines:Boolean = true; // Change to true if you want to use lineTo for straight lines of 3 or more points rather than curves. You'll get straight lines but possible sharp corners!
  197. // Loop through points to draw cubic Bézier curves through the penultimate point, or through the last point if the line is closed.
  198. for (i=firstPt;i<lastPt-1;i++){
  199. // Determine if multiple points in a row are in a straight line
  200. var isStraight:Boolean = ( ( i > 0 && Math.atan2(p[i].y-p[i-1].y,p[i].x-p[i-1].x) == Math.atan2(p[i+1].y-p[i].y,p[i+1].x-p[i].x) ) || ( i < p.length - 2 && Math.atan2(p[i+2].y-p[i+1].y,p[i+2].x-p[i+1].x) == Math.atan2(p[i+1].y-p[i].y,p[i+1].x-p[i].x) ) );
  201. if (straightLines && isStraight){
  202. g.lineTo(p[i+1].x,p[i+1].y);
  203. } else {
  204. // BezierSegment instance using the current point, its second control point, the next point's first control point, and the next point
  205. var bezier:BezierSegment = new BezierSegment(p[i],controlPts[i][1],controlPts[i+1][0],p[i+1]);
  206. // Construct the curve out of 100 segments (adjust number for less/more detail)
  207. for (var t=.01;t<1.01;t+=.01){
  208. var val = bezier.getValue(t); // x,y on the curve for a given t
  209. g.lineTo(val.x,val.y);
  210. }
  211. }
  212. }
  213. // If this isn't a closed line
  214. if (lastPt == p.length-1){
  215. // Curve to the last point using the second control point of the penultimate point.
  216. g.curveTo(controlPts[i][1].x,controlPts[i][1].y,p[i+1].x,p[i+1].y);
  217. }
  218. // just draw a line if only two points
  219. } else if (p.length == 2){
  220. g.moveTo(p[0].x,p[0].y);
  221. g.lineTo(p[1].x,p[1].y);
  222. }
  223. }
  224. // Catch error
  225. catch (e) {
  226. trace(e.getStackTrace());
  227. }
  228. }
  229.  
  230. }
  231.  
  232. }

您给予的分值: None 平均分: 9.3 ( 3 票)

发表新评论

  • 网页地址和电子邮件地址将会被自动转换为链接。
  • 行和段被自动切分。
  • 您可以使用下面的标签来高亮显示您的评论内容: <code>, <blockcode>. 可以使用"[foo]".旁边显示标签样式 "<foo>" PHP代码可以用这样的区块来包含<?php ... ?> or <% ... %>

更多格式化选项信息

验证区域
系统验证:请回答下面的问题