二次贝塞尔曲线长度

二次贝塞尔曲线通常以如下方式构建,给定二维平面上的固定点P0,P1,P2,用B(t)表示该条曲线

用一个动画来演示,可以更加清楚的表明这条曲线的构建过程






如果t变量本身线形变化的话,这条贝塞尔曲线本身的生成过程是并不是匀速的,通常都是两头快中间慢。




如果t变量本身线形变化的话,这条贝塞尔曲线本身的生成过程是并不是匀速的,通常都是两头快中间慢。

如何想要得到匀速的贝塞尔曲线运动呢?比如我们在某款游戏中设计了一条贝塞尔曲线的路径,如何实现玩家匀速在这条路径上运动呢?
思考这个算法颇费了一番脑筋,其间还得到数学牛人Charlesgao的帮助,非常感谢他(比较糗的是,我问问题的时候就把其中的一个公式搞错了,见笑了-_-!)。

首先需要求得B(t)相对于t的速度公式s(t)

s(t) = Math.sqrt(Bx(t)^2 + By(t)^2)

为了简化公式,我们定义如下变量:

a = P0 -2*P1 + P2

b = 2*P1 - 2P0

计算出的s(t)可以表达为:

s(t) =Math.sqrt(A*t^2 + B*t + C)

其中A,B,C是根据P0,P1,P2计算出的常数:

A = 4 * (ax^2 + by^2)

B = 4 * (ax* by + ay* by)

C = bx^2 + by^2

根据这个公式,求得贝塞尔曲线的长度公式L(t):


设t`就是能够使L实现匀速运动的自变量,那么显然L(t`)=L(1.0)*t,即:


由于L(t)函数非常复杂,直接求逆函数的表达式几乎不可能,还好我们可以知道它的导数为s(t),在实际使用中,可以使用牛顿切线法求出近似解。其迭代算法可以表达为:


我写了一个测试程序用于验证该算法,运算结果如下,可以看到,这条曲线已经是以匀速方式生成的了 ^_^:

 

 

  1. #include <stdio.h>  
  2.   
  3. #include <math.h>  
  4.   
  5. #include <windows.h>  
  6.   
  7.   
  8.   
  9. //三个控制点  
  10.   
  11. POINT P0={50,50},P1={500,600},P2={800,200};  
  12.   
  13.   
  14.   
  15. int ax = P0.x-2*P1.x+P2.x;  
  16.   
  17. int ay = P0.y-2*P1.y+P2.y;  
  18.   
  19. int bx = 2*P1.x-2*P0.x;  
  20.   
  21. int by = 2*P1.y-2*P0.y;  
  22.   
  23.   
  24.   
  25. double A = 4*(ax*ax+ay*ay);  
  26.   
  27. double B = 4*(ax*bx+ay*by);  
  28.   
  29. double C = bx*bx+by*by;  
  30.   
  31.   
  32.   
  33. //曲线总长度  
  34.   
  35. double total_length = 0.0;  
  36.   
  37.   
  38.   
  39. //曲线分割的份数  
  40.   
  41. const int STEP = 70;  
  42.   
  43.   
  44.   
  45. //用于保存绘制点数据的数组  
  46.   
  47. POINT pixels[STEP];  
  48.   
  49.   
  50.   
  51. //-------------------------------------------------------------------------------------  
  52.   
  53. //速度函数  
  54.   
  55. /* 
  56.  
  57. s(t_) = Sqrt[A*t*t+B*t+C] 
  58.  
  59. */  
  60.   
  61. double s(double t)  
  62.   
  63. {  
  64.   
  65.     return sqrt(A*t*t+B*t+C);  
  66.   
  67. }  
  68.   
  69.   
  70.   
  71. //-------------------------------------------------------------------------------------  
  72.   
  73. //长度函数  
  74.   
  75. /* 
  76.  
  77.  
  78.  
  79. L(t) = Integrate[s[t], t] 
  80.  
  81.  
  82.  
  83. L(t_) = ((2*Sqrt[A]*(2*A*t*Sqrt[C + t*(B + A*t)] + B*(-Sqrt[C] + Sqrt[C + t*(B + A*t)])) +  
  84.  
  85.             (B^2 - 4*A*C) (Log[B + 2*Sqrt[A]*Sqrt[C]] - Log[B + 2*A*t + 2 Sqrt[A]*Sqrt[C + t*(B + A*t)]])) 
  86.  
  87.                 /(8* A^(3/2))); 
  88.  
  89. */  
  90.   
  91. double L(double t)  
  92.   
  93. {  
  94.   
  95.     double temp1 = sqrt(C+t*(B+A*t));  
  96.   
  97.     double temp2 = (2*A*t*temp1+B*(temp1-sqrt(C)));  
  98.   
  99.     double temp3 = log(B+2*sqrt(A)*sqrt(C));  
  100.   
  101.     double temp4 = log(B+2*A*t+2*sqrt(A)*temp1);  
  102.   
  103.     double temp5 = 2*sqrt(A)*temp2;  
  104.   
  105.     double temp6 = (B*B-4*A*C)*(temp3-temp4);  
  106.   
  107.       
  108.   
  109.     return (temp5+temp6)/(8*pow(A,1.5));  
  110.   
  111. }  
  112.   
  113.   
  114.   
  115. //-------------------------------------------------------------------------------------  
  116.   
  117. //长度函数反函数,使用牛顿切线法求解  
  118.   
  119. /* 
  120.  
  121.     X(n+1) = Xn - F(Xn)/F'(Xn) 
  122.  
  123. */  
  124.   
  125. double InvertL(double t, double l)  
  126.   
  127. {  
  128.   
  129.     double t1=t, t2;  
  130.   
  131.       
  132.   
  133.     do  
  134.   
  135.     {  
  136.   
  137.         t2 = t1 - (L(t1)-l)/s(t1);  
  138.   
  139.         if(abs(t1-t2)<0.000001) break;  
  140.   
  141.         t1=t2;  
  142.   
  143.     }while(true);  
  144.   
  145.     return t2;  
  146.   
  147. }  
  148.   
  149.   
  150.   
  151. //-------------------------------------------------------------------------------------  
  152.   
  153. LRESULT CALLBACK _WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  
  154.   
  155. {  
  156.   
  157.     switch (message)   
  158.   
  159.     {  
  160.   
  161.     case WM_TIMER:  
  162.   
  163.         {  
  164.   
  165.             static nIndex = 0;  
  166.   
  167.             if(nIndex>=0 && nIndex<=STEP)  
  168.   
  169.             {  
  170.   
  171.                 double t = (double)nIndex/STEP;  
  172.   
  173.                 //如果按照线形增长,此时对应的曲线长度  
  174.   
  175.                 double l = t*total_length;  
  176.   
  177.                 //根据L函数的反函数,求得l对应的t值  
  178.   
  179.                 t = InvertL(t, l);  
  180.   
  181.   
  182.   
  183.                 //根据贝塞尔曲线函数,求得取得此时的x,y坐标  
  184.   
  185.                 double x = (1-t)*(1-t)*P0.x +2*(1-t)*t*P1.x + t*t*P2.x;  
  186.   
  187.                 double y = (1-t)*(1-t)*P0.y +2*(1-t)*t*P1.y + t*t*P2.y;  
  188.   
  189.   
  190.   
  191.                 //取整  
  192.   
  193.                 pixels[nIndex].x = (int)(x+0.5);  
  194.   
  195.                 pixels[nIndex].y = (int)(y+0.5);  
  196.   
  197.   
  198.   
  199.                 nIndex++;  
  200.   
  201.                 InvalidateRect(hWnd, 0, 0);  
  202.   
  203.             }  
  204.   
  205.             else  
  206.   
  207.             {  
  208.   
  209.                 KillTimer(hWnd, 101);  
  210.   
  211.             }  
  212.   
  213.         }  
  214.   
  215.         break;  
  216.   
  217.     case WM_PAINT:  
  218.   
  219.         {  
  220.   
  221.             PAINTSTRUCT ps;  
  222.   
  223.             HDC hdc = BeginPaint(hWnd, &ps);  
  224.   
  225.             ::MoveToEx(hdc, P0.x, P0.y, 0);  
  226.   
  227.             LineTo(hdc, P1.x, P1.y);  
  228.   
  229.             LineTo(hdc, P2.x, P2.y);  
  230.   
  231.   
  232.   
  233.             for(int i=0; i<STEP; i++)  
  234.   
  235.             {  
  236.   
  237.                 const POINT &pt = pixels[i];  
  238.   
  239.                 if(pt.x==0 && pt.y==0) break;  
  240.   
  241.   
  242.   
  243.                 ::MoveToEx(hdc, pt.x-2, pt.y, 0);  
  244.   
  245.                 ::LineTo(hdc, pt.x+2, pt.y);  
  246.   
  247.                 ::MoveToEx(hdc, pt.x, pt.y-2, 0);  
  248.   
  249.                 ::LineTo(hdc, pt.x, pt.y+2);  
  250.   
  251.             }  
  252.   
  253.             EndPaint(hWnd, &ps);  
  254.   
  255.         }  
  256.   
  257.         break;  
  258.   
  259.     case WM_DESTROY:  
  260.   
  261.         PostQuitMessage(0);  
  262.   
  263.         break;  
  264.   
  265.     default:  
  266.   
  267.         return DefWindowProc(hWnd, message, wParam, lParam);  
  268.   
  269.     }  
  270.   
  271.     return 0;  
  272.   
  273. }  
  274.   
  275.   
  276.   
  277. //-------------------------------------------------------------------------------------  
  278.   
  279. int APIENTRY WinMain(HINSTANCE hInstance,  
  280.   
  281.                      HINSTANCE hPrevInstance,  
  282.   
  283.                      LPTSTR    lpCmdLine,  
  284.   
  285.                      int       nCmdShow)  
  286.   
  287. {  
  288.   
  289.     //注册窗口类  
  290.   
  291.     WNDCLASSEX wcex;  
  292.   
  293.     ZeroMemory(&wcex, sizeof(WNDCLASSEX));  
  294.   
  295.   
  296.   
  297.     wcex.cbSize = sizeof(WNDCLASSEX);   
  298.   
  299.     wcex.style          = CS_HREDRAW | CS_VREDRAW;  
  300.   
  301.     wcex.lpfnWndProc    = (WNDPROC)_WndProc;  
  302.   
  303.     wcex.hInstance      = hInstance;  
  304.   
  305.     wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);  
  306.   
  307.     wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);  
  308.   
  309.     wcex.lpszClassName  = "BezierClass";  
  310.   
  311.     RegisterClassEx(&wcex);  
  312.   
  313.   
  314.   
  315.     //创建窗口  
  316.   
  317.     HWND hWnd = CreateWindow("BezierClass""BezierDemo", WS_OVERLAPPEDWINDOW,  
  318.   
  319.       CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);  
  320.   
  321.     ShowWindow(hWnd, nCmdShow);  
  322.   
  323.     UpdateWindow(hWnd);  
  324.   
  325.   
  326.   
  327.     //计算总长度  
  328.   
  329.     total_length = L(1);  
  330.   
  331.   
  332.   
  333.     //清空绘制点数据  
  334.   
  335.     ZeroMemory(&pixels, sizeof(pixels));  
  336.   
  337.   
  338.   
  339.     //设定定时刷新计时器  
  340.   
  341.     SetTimer(hWnd, 101, 10, 0);  
  342.   
  343.   
  344.   
  345.     //消息循环  
  346.   
  347.     MSG msg;  
  348.   
  349.     while(GetMessage(&msg, NULL, 0, 0))   
  350.   
  351.     {  
  352.   
  353.         TranslateMessage(&msg);  
  354.   
  355.         DispatchMessage(&msg);  
  356.   
  357.     }  
  358.   
  359.   
  360.   
  361.     return (int) msg.wParam;  
  362.   
  363. }  

来自:http://blog.csdn.net/kongbu0622/article/details/10123989

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值