模擬PLC 的圓弧插補方式在VC中繪制圓弧


       最近同事想讓要做一個繪圖的控件。VC里面的畫弧函數Arc需要提供外接矩形的坐標。同事覺得不好用,他更習慣圓弧插補的那種方式。於是看了看圓弧插補的東西。其實這種畫弧方式就是提供圓弧的起點、終點和半徑來畫弧。

           首先來簡單介紹下圓弧插補:

           有兩種圓弧插補:

            G02     順時針圓弧插補

            G03    逆時針圓弧插補

 

          圓弧插補編程(半徑編程):

          圓弧用編程功能G02 或G03 和其后圓弧終點坐標和半徑值定義。

          

圓弧半徑用字母“R”表示。如果圓弧小於180 度,半徑用正數符號,如果大於180 度用負數符號。這樣基於所選圓弧插補(G02 或G03),可定義所選圓弧。

 

結合圓弧插補,設計繪制圓弧的函數:函數可分為兩種,順時針繪制和逆時針繪制(分布對應G02 和G03)。函數的參數為圓弧起點,終點,半徑。其中的半徑若為正數,則繪制的圓弧為弧度小於180 的弧,這里稱為小圓弧。若半徑為負數,則繪制的弧為大雨180度的弧,這里成之為大圓弧。

 

圓弧的繪制最終還是要使用C++ 提供的畫弧函數Arc 。 因此我們需要找出來圓所在的外接矩形(這里是正方形)。因為我們已知半徑,所以找到圓心就可以推導出圓所在的矩形。

圓心的推導過程參考文章 已知圓上兩點坐標和半徑,求圓心   已知兩點坐標和半徑,求圓心 。圓心解出來有兩個(x01,y01)(x02,y02)。如圖所示,過相同的點並且半徑相同的圓也確實有兩個。那么到底哪一個是符合條件的圓呢。

 

 首先來討論逆時針畫弧的函數。如上圖,從起點A到終點B,小圓弧就指的紅色部分的弧,大圓弧是指的藍色部分的弧。小圓弧的圓心是O2,大圓弧的圓心是O1;

那么由什么條件能判斷出所得的兩個圓心(x01,y01)(x02,y02)哪一個是逆時針里的大圓弧的圓心O1,哪一個是逆時針里的小圓弧圓心O2呢? 這里我采用的是向量叉乘的方式判斷的。

 

也就是起點到終點組成的向量,與起點與大弧圓心組成的向量叉乘結果是小於0 的。(這個從圖上使用右手法則可以判斷出來,由AB 向AO1 彎曲,拇指垂直屏幕向里)。

(有關向量知識參考 C語言-向量基本概念  向量叉乘判斷點的位置)。

所以在上一步所得的兩個圓心坐標,與起點坐標組成向量。

設A(x1,y1) B(x2,y2)

向量AB={x2-x1,y2-y1}

向量a={x01-x1,y01-y1}

向量b={x02-x1,y02-y1}

則(x01,y01)為大圓弧圓心 (x02,y02)為小圓弧圓心

否則 反之。

 代碼如下:

//已知圓弧上兩點 和半徑,求圓心
void CircleCenter(double x1,double y1,double x2,double y2,double R,double &x01,double &y01,double &x02,double &y02)  
{  
	//x1 == x2
	if (abs(x1-x2)<0.0000001)
	{
		//(x1,y1)(x2,y2)之間的距離 /2
		double dis = abs(y1-y2)/2;
		
		double dx = sqrt(R*R-dis*dis);

		double dy = (y1+y2)/2;

		x01 = x1-dx;
		y01 = dy;

		x02 = x1+dx;
		y02 = dy;
		return ;
	}

	double c1 = (x2*x2 - x1*x1 + y2*y2 - y1*y1) / (2 *(x2 - x1));  
	double c2 = (y2 - y1) / (x2 - x1);  //斜率
	double A = (c2*c2 + 1);  
	double B = (2 * x1*c2 - 2 * c1*c2 - 2 * y1);  
	double C = x1*x1 - 2 * x1*c1 + c1*c1 + y1*y1 - R*R;  
	y01 = (-B + sqrt(B*B - 4 * A*C)) / (2 * A);
	x01 = c1 - c2 * y01;   

	y02 =  (-B - sqrt(B*B - 4 * A*C)) / (2 * A);
	x02 = c1 - c2*y02;
}  

  

//逆時針畫弧
void CDrawShapeCtrl::Arc_AntiClock(DOUBLE StartX, DOUBLE StartY, DOUBLE EndX, DOUBLE EndY, DOUBLE R)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// TODO: Add your dispatch handler code here
	//圓心坐標
	double x01,y01,x02,y02;
	double x_big,y_big;//大弧圓心
	double x_small,y_small;//小弧圓心

	LONG nLeftRect, nTopRect,nRightRect,nBottomRect;

  CircleCenter(StartX,StartY,EndX,EndY,R,x01,y01,x02,y02);

  //向量 
  double ax = EndX- StartX;
  double ay = EndY - StartY;

  double bx = x01 - StartX;
  double by = y01 - StartY;

  //利用向量的叉乘判斷圓心位置
  //叉乘<0  則為大弧圓心;否則為小弧圓心
   double mulRt = ax*by-bx*ay;

   if (mulRt<0)
   {
	   x_big = x01;
	   y_big = y01;
	   x_small = x02;
	   y_small = y02;
   }
   else
   {
	   x_big = x02;
	   y_big = y02;
	   x_small = x01;
	   y_small = y01;
   }

   CClientDC dc(this);

   CRect rc;
   GetClientRect(rc);
   dc.SetMapMode(MM_ISOTROPIC);//MM_ISOTROPIC

   //邏輯坐標原點
   dc.SetViewportOrg(rc.right/2,rc.bottom/2);
   //設置映射比例為1,邏輯坐標Y軸方向與設備坐標相反
   dc.SetWindowExt(100,100);
   dc.SetViewportExt(100,-100);

  //R>0   弧<180度; R<0  弧>180度
  if (R<0) //大弧
	  {
		  nLeftRect = x_big-R;
		  nTopRect = y_big + R;
		  nRightRect = x_big+R;
		  nBottomRect = y_big -R;

		 dc.Arc(nLeftRect,nTopRect,nRightRect,nBottomRect,StartX,StartY,EndX,EndY);	
	  }
  else //小弧
	  {
		  nLeftRect = x_small-R;
		  nTopRect = y_small+R;
		  nRightRect = x_small+R;
		  nBottomRect = y_small - R;
		  dc.Arc(nLeftRect,nTopRect,nRightRect,nBottomRect,StartX,StartY,EndX,EndY);

	  }

}

 順時針函數,只要將起點終點坐標對換,直接調用逆時針函數即可。

 

 arc 函數參考:

https://blog.csdn.net/u012513234/article/details/45460783

 

 

 

      

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM