您的位置:首页 > 其它

MFC下如何绘制一个类似云(cloud)的图形

2013-09-16 17:42 537 查看
How to draw a cloud shape with MFC

问题描述:添加一种云状,有曲线外边的绘图工具,类似line,circle,rectangle。通过鼠标点击的点确定图形的顶点,在由此形成的多边形外围绘制曲线,形成最终图形。多边形可以凸或者凹,但不可以自交叉(self-intersect)。

解决方案:每次取出两个相邻的顶点,通过计算两顶点的距离,得出可以绘制弧线的数量,并绘制数目不等的半圆弧连接顶点,这时弧线的半径是固定的。(当然也可以绘制固定个数的弧线,此时弧线的半径是可变的,但是忽大忽小的弧线导致绘制效果不太理想。)

遇到的问题:

1. 当弧线半径一定时,顶点间的距离无法保证可以绘制完整的弧线。

有两个选择:一个是在顶点处终止弧的绘制,每条边自行负责绘制弧线。另一个是当一个边的距离不够绘制一个完整的弧线时,跨越顶点绘制到邻近的边上,这时候绘制的不是半圆弧了。第二种方法需要更多的计算。相比之下,第一种方法比较简单,但是有两种情况需要区分:

(1)两顶点的距离非常近,无法绘制出一个完整的弧。(N=0)

此时需要直接用两个顶点作为弧线的起始点,绘制一个小弧。

(2)两顶点的距离足够绘制N(N>=1)个完整的弧,但还会有剩余的距离绘制一个不完整的弧。

为了使弧线显得连续好看,当剩余距离小于弧线半径时,前一个完整的弧吞并剩余距离。当剩余距离大于弧线半径时,直接用剩余距离绘制一个小弧。

void DrawCloudArc(HDC hDC)

{

int nCount = vcPoints.size();

if(nCount < 4)

return;

float arc_radius = 60;

float arc_diameter = arc_radius * 2;

CDC *pDC = CDC::FromHandle(hDC);

double sum = 0.0;

for(int i = 0; i < nCount; i++)

{

int j = (i+1) % nCount;

sum += (vcPoints[j].x - vcPoints[i].x) * ( vcPoints[j].y + vcPoints[i].y);

}

bool b_clockwise = sum > 0.0;

for(int i = 0; i < nCount; ++i)

{

int j = (i + 1) % nCount;

CPoint ptStart = vcPoints.at(i);

CPoint ptEnd = vcPoints.at(j);

float xdiff = ptEnd.x - ptStart.x;

float ydiff = ptEnd.y - ptStart.y;

float dis_pt = std::sqrt(ydiff * ydiff + xdiff * xdiff);

float cosangle = xdiff / dis_pt;

float sinangle = ydiff / dis_pt;

int nArcPos = 0;

int nArcCount = dis_pt/arc_diameter;

CPoint pt1, pt2;

// if the two points of cloud are too near to draw a whole arc curve.

if( nArcCount == 0 )

{

DrawCloudArcWithPoints(pDC, ptStart, ptEnd, b_clockwise);

}

while(nArcPos < nArcCount)

{

pt1.x = ptStart.x + nArcPos * arc_diameter * cosangle;

pt1.y = ptStart.y + nArcPos * arc_diameter * sinangle;

pt2.x = ptStart.x + (nArcPos + 1) * arc_diameter * cosangle;

pt2.y = ptStart.y + (nArcPos + 1) * arc_diameter * sinangle;

CPoint ptNext1 = pt2;

CPoint ptNext2 = ptEnd;

//add extra effect for the last arc curve.. with different touch points

if( nArcPos == nArcCount - 1 )

{

float xnd = ptNext1.x - ptNext2.x;

float ynd = ptNext1.y - ptNext2.y;

float dDis = std::sqrt(xnd * xnd + ynd * ynd);

// if the left distance is larger than radius, draw an extra arc curve from ptNext1 to ptEnd.

// otherwise change pt2 to be ptEnd and draw the arc curve from pt1 to ptEnd.

if( dDis > 0.0 )

{

if( dDis >= arc_diameter * 0.5 )

{

DrawCloudArcWithPoints(pDC, ptNext1, ptEnd, b_clockwise);

nArcPos++;

}

else

{

pt2 = ptEnd;

}

}

}

DrawCloudArcWithPoints(pDC, pt1, pt2, b_clockwise);

nArcPos++;

}

}

}

void DrawCloudArcWithPoints(CDC *pDC, CPoint pt1, CPoint pt2, bool b_clockwise)

{

CPoint pt0;

pt0.x = (pt1.x+pt2.x)/2;

pt0.y = (pt1.y+pt2.y)/2;

float xd = pt1.x-pt2.x;

float yd = pt1.y-pt2.y;

float radius = std::sqrt(xd*xd+yd*yd)/2;

CPoint tl(pt0.x - radius, pt0.y - radius);

CPoint br(pt0.x + radius, pt0.y + radius);

CRect rect(tl, br);

if(b_clockwise)

pDC->Arc(&rect, pt1, pt2);

else

pDC->Arc(&rect, pt2, pt1);

}

2.需要知道一个多边形的顶点的顺序是顺时针还是逆时针。因为需要把弧线绘制到多边形的边的外面,要注意绘制弧线的方向。

假设有n(0...n-1)个顶点P0....Pn-1。算法如下:

double sum = 0.0;

for(int pre
= 0; pre < n; pre++)

{

int next = (pre+1)%n;

sum += (P[next].x - P[pre].x) * (P[next].y + P[pre].y);

}

bool bclockwise = sum > 0;

此算法适合凹或凸多边形,自交叉多边形(本次没有用到,有待验证)。

参考资料:http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order

3. 需要额外的扩大刷新区域,因为有弧的存在。

刷新用的rgn至少需要扩大一个弧半径的大小,然而由于合并小弧,至少要扩大1.2倍左右的弧半径。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: