最近研究使用GDI+绘制雷达P显,参考了开源代码,实现了一个初步版本,实现的主要功能如下:
1. 绘制P显底图,含距离环、距离标注、方位射线(即方位角)等;
2. 实时显示方位扫描波位,并添加余辉效果(即渐变颜色);
3. 依据给定的检测概率,随机生成一系列的探测点迹并显示在P显底图上;
4. 实现了特定区域刷新效果——即方位扫描波位覆盖的区域才进行重绘,其它区域保持不变;
5. 在界面上添加了其它的显示信息(很多尚未添加内核模型,仅为示意)。
通过前期编程努力,在Visual Studio 2013上完成了代码编写与测试,运行效果参加本帖最后的GIF图像。通过运行发现,系统距离预期的显示效果有一定的区别,主要表现为:
1.虽然使用了InvalidRgn()函数,仅对方位扫描波位覆盖的屏幕区域才进行重绘,但是通过实际运行发现,屏幕上遗留了较多的余辉残留(图中的绿色部分),具体原因尚未发现;
2.通过长时间运行发现,程序的CPU占用较为平稳(在笔者的电脑上维持在3%左右),但是它的内存占用会缓慢地增加,表明程序中存在者内存泄露,但通过仔细检查代码,未发现问题代码。
同时,我还想补充添加两个功能,具体为:
1.目前采用的是随机生成一系列的探测点迹并显示在P显底图上的方式,仅为示意。如果我想在P显上显示一个稳定的目标轨迹(即连续多次扫描都能发现这个目标,将每次的扫描探测结果用折线连接,显示在底图上),这会牵扯到较大范围的屏幕区域重绘,目前的程序结构无法完成,不知该如何修改;
2.想实现在屏幕上通过鼠标点击选择特定点迹的功能,相应的右键菜单已经做好,但不知道如何才能实现鼠标点击选择。粗略地考虑,应该把当前屏幕上所有点位置均记录下来,然后通过获取当前鼠标点击位置,借助于“最近邻”准则来判决,这样感觉有些繁琐,不知道有没有更好的解决方式?
恳请论坛里的各位达人指点。关键部分的源码附后,程序运行结果同样附后,请大家批评指正。希望能和大家一块儿,建立一个较为完整的雷达P显示例。
注:在OnPaint()函数中引用了网上的开源代码,对作者表示感谢。
//相关成员变量定义(在.h文件中,摘抄如下)
double m_dCurSimTime; //当前扫描时间,s
double m_dCurAzimuth; //当前波束扫描方位角,rad
int m_nCurScanFrame; //当前扫描处理帧周期
int m_nCenterX; //P显中心X坐标,屏幕上
int m_nCenterY; //P显中心Y坐标,屏幕上
int m_nRadius; //P显半径,屏幕上
//其它公共变量定义
const double PI = 3.14159265358979; //圆周率
const double d2r = PI/180.0; //从°至rad的转换
const UINT TimeIntervalInMS = 100; //以ms形式表示的方位扫描时间间隔
const double BeamWidth = 6.0*d2r; //方位扫描波束宽度,rad
const double ScanPeriod = 6.0; //一个完整的方位扫描周期,s
const double ScanRate = 360.0/ScanPeriod*d2r; //方位波束扫描速率,rad/s
const double MaxDetectRange = 500.0e3; //距离环代表的最大探测距离,m
const int RANGERINGNUM = 10; //P显底图上的距离环数目
//用于实时显示状态的矩形区域(特定刷新)
RECT rectTime,rectAzScan,rectFrame;
RECT rectRate,rectDotNum,rectEchoNum,rectTgtNum,rectSysTime;
RECT rectR,rectAz,rectID;
//生成给定区间内的均匀分布随机数
double Rand(double dblStart, double dblFinish)
{
double minVal = min(dblStart, dblFinish);
double maxVal = max(dblStart, dblFinish);
return (maxVal - minVal) * (double)rand() / (RAND_MAX + 1) + minVal;
}
BOOL CPPIDispDemoDlg::OnInitDialog()
{
……
SetTimer(0, TimeIntervalInMS, NULL);
}
void CPPIDispDemoDlg::OnPaint()
{
if (IsIconic())
{
……
}
else
{
CPaintDC dc(this);
CRect rect;
This->GetClientRect(&rect);
int nMargin = 1; //边距
int nHeight = rect.Height() - 2*nMargin;
int nWidth = rect.Width() - 2*nMargin;
m_nCenterX = nWidth/2;
m_nCenterY = nHeight/2;
m_nRadius = min(nWidth,nHeight)/2;
CDC xDC;
CBitmap xBMP;
xDC.CreateCompatibleDC(&dc);
xBMP.CreateCompatibleBitmap(&dc,nWidth,nHeight);
xDC.SelectObject(xBMP);
//这是显示的核心函数!
OnDraw(&xDC);
CDC yDC;
CBitmap yBMP;
yDC.CreateCompatibleDC(&dc);
yBMP.CreateCompatibleBitmap(&dc,nWidth,nHeight);
yDC.SelectObject(&yBMP);
yDC.FillSolidRect(rect,GetSysColor(COLOR_3DFACE));
//采用双缓存机制,防止背景闪烁
yDC.BitBlt(nMargin,nMargin,nWidth,nHeight,&xDC,0,0,SRCCOPY);
dc.BitBlt(0,0,nWidth,nHeight,&yDC,0,0,SRCCOPY);
xBMP.DeleteObject();
xDC.DeleteDC();
yBMP.DeleteObject();
yDC.DeleteDC();
}
}
void CPPIDispDemoDlg::OnDraw(CDC *pDC)
{
CPen xPen(PS_DASH,1,RGB(0,0,0));
CPen *oPen = pDC->SelectObject(&xPen);
int i = 0, j = 0;
//绘制方位波束扫描余辉效果(可以进行更为精细的控制)
double deltaT = BeamWidth/255;
for(double dt = 0.0; dt < 1.0*BeamWidth; dt += deltaT)
{
long x = m_nCenterX + m_nRadius*cos(dt + m_dCurAzimuth-BeamWidth);
long y = m_nCenterY + m_nRadius*sin(dt + m_dCurAzimuth-BeamWidth);
pDC->MoveTo(m_nCenterX,m_nCenterY);
xPen.DeleteObject();
xPen.CreatePen(0,4,RGB(0,j++,0));
pDC->SelectObject(&xPen);
pDC->LineTo(x,y);
}
xPen.DeleteObject();
//绘制当前方位扫描波束中心线
xPen.CreatePen(0,4,RGB(0,255,0));
pDC->SelectObject(&xPen);
pDC->MoveTo(m_nCenterX, m_nCenterY);
pDC->LineTo(m_nCenterX + m_nRadius*cos(m_dCurAzimuth),
m_nCenterY + m_nRadius*sin(m_dCurAzimuth));
xPen.DeleteObject();
//在当前方位扫描波束覆盖的区域里生成距离、方位均随机分布的目标
int Rmt = int(Rand(10, m_nRadius));
//注意:这里显示的实际上是上一个方位扫描波束的目标,模拟实际系统的处理延迟
double angleAZ = Rand(m_dCurAzimuth - 2*BeamWidth, m_dCurAzimuth - BeamWidth);
//以给定的概率发现目标,并在P显上绘制出
double Threshold = 0.65;
if (Rand(0.0,1.0) >= 1 - Threshold)
{
CPoint dot;
dot.x = m_nCenterX + Rmt*cos(angleAZ);
dot.y = m_nCenterY + Rmt*sin(angleAZ);
pDC->SetPixel(dot,RGB(255,0,0));
pDC->FillSolidRect(dot.x, dot.y, 4, 4, RGB(255,255,0));
}
CFont font;
font.CreateFont(12,6,0,0,600,0,0,0,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,FF_ROMAN,"宋体");
CString str("");
//绘制距离环
for (i = 0; i < RANGERINGNUM-1; i++)
{
xPen.CreatePen(PS_SOLID,1,RGB(128,118,105));
pDC->SelectObject(&xPen);
pDC->Arc(m_nCenterX - m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,
m_nCenterY - m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,
m_nCenterX + m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,
m_nCenterY + m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,0,0,0,0);
xPen.DeleteObject();
//显示距离标注
if (i % 2 == 1)
{
pDC->SelectObject(&font);
pDC->SetTextColor(RGB(160,160,164));
pDC->SetBkMode(TRANSPARENT);
str.Format("%d",int((RANGERINGNUM-i)*MaxDetectRange/1.0e3/RANGERINGNUM));
pDC->TextOut(m_nCenterX + m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,
m_nCenterY, str);
}
}
xPen.CreatePen(PS_SOLID,1,RGB(128,118,105));
pDC->SelectObject(&xPen);
//绘制水平线
pDC->MoveTo(m_nCenterX - m_nRadius, m_nCenterY);
pDC->LineTo(m_nCenterX + m_nRadius, m_nCenterY);
//绘制垂直线
pDC->MoveTo(m_nCenterX, m_nCenterY - m_nRadius);
pDC->LineTo(m_nCenterX, m_nCenterY + m_nRadius);
//绘制30°方位射线
double dTheta = 30.0*d2r;
pDC->MoveTo(m_nCenterX - cos(dTheta)*m_nRadius, m_nCenterY + sin(dTheta)*m_nRadius);
pDC->LineTo(m_nCenterX + cos(dTheta)*m_nRadius, m_nCenterY - sin(dTheta)*m_nRadius);
pDC->MoveTo(m_nCenterX - cos(dTheta)*m_nRadius, m_nCenterY - sin(dTheta)*m_nRadius);
pDC->LineTo(m_nCenterX + cos(dTheta)*m_nRadius, m_nCenterY + sin(dTheta)*m_nRadius);
//绘制60°方位射线
dTheta = 60.0*d2r;
pDC->MoveTo(m_nCenterX - cos(dTheta)*m_nRadius, m_nCenterY + sin(dTheta)*m_nRadius);
pDC->LineTo(m_nCenterX + cos(dTheta)*m_nRadius, m_nCenterY - sin(dTheta)*m_nRadius);
pDC->MoveTo(m_nCenterX - cos(dTheta)*m_nRadius, m_nCenterY - sin(dTheta)*m_nRadius);
pDC->LineTo(m_nCenterX + cos(dTheta)*m_nRadius, m_nCenterY + sin(dTheta)*m_nRadius);
xPen.DeleteObject();
//对中心距离环线换用红色进行突出显示
xPen.CreatePen(PS_SOLID,2,RGB(255,0,0));
pDC->SelectObject(&xPen);
i = RANGERINGNUM-1;
pDC->Arc(m_nCenterX - m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,
m_nCenterY - m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,
m_nCenterX + m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,
m_nCenterY + m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM,0,0,0,0);
xPen.DeleteObject();
//显示距离标注
pDC->SelectObject(&font);
pDC->SetTextColor(RGB(160,160,164));
pDC->SetBkMode(TRANSPARENT);
str.Format("%d",int((RANGERINGNUM-i)*MaxDetectRange/1.0e3/RANGERINGNUM)); /单位km
pDC->TextOut(m_nCenterX + m_nRadius*(RANGERINGNUM-i)/RANGERINGNUM, m_nCenterY, str);
//显示当前仿真时间(注意:不是系统时间)
str.Format("当前时间(s):%6.3f", m_dCurSimTime);
rectTime.left = 5;
rectTime.top = 5;
rectTime.right = rectTime.left + strlen(str) * 7;
rectTime.bottom = rectTime.top + 12;
//pDC->FillSolidRect(&rectTime,RGB(255,255,0));
pDC->DrawText(str,&rectTime,DT_LEFT);
//还有其它信息显示,受限于论坛发帖长度,不再列出
……
font.DeleteObject();
//换回原来的画笔
pDC->SelectObject(oPen);
}
void CPPIDispDemoDlg::OnTimer(UINT_PTR nIDEvent)
{
//更新当前方位扫描角,以及当前仿真时刻
m_dCurAzimuth += BeamWidth;
m_dCurSimTime += 1.0e-3*TimeIntervalInMS;
//将方位扫描角限定在[0,360°]之间,并且进行方位扫描帧周期的判断
if(m_dCurAzimuth > 2*PI)
{
m_dCurAzimuth -= 2*PI;
m_nCurScanFrame++;
}
//刷新特定区域(显示各种实时状态信息)
InvalidateRect(&rectTime);
InvalidateRect(&rectAzScan);
InvalidateRect(&rectFrame);
InvalidateRect(&rectDotNum);
InvalidateRect(&rectEchoNum);
InvalidateRect(&rectTgtNum);
InvalidateRect(&rectSysTime);
InvalidateRect(&rectRate);
InvalidateRect(&rectID);
InvalidateRect(&rectR);
InvalidateRect(&rectAz);
CDC dc;
dc.CreateCompatibleDC(&dc);
dc.SetViewportOrg(m_nCenterX,m_nCenterY);
CPoint topleft,bottomright;
topleft.x = -m_nRadius ;
topleft.y = -m_nRadius;
bottomright.x = m_nRadius ;
bottomright.y = m_nRadius ;
CRgn rgn;
CRect rect(topleft,bottomright);
//当前方位扫描波束的起点、终点
CPoint pt1, pt2;
pt1.x = m_nRadius*cos(m_dCurAzimuth);
pt1.y = m_nRadius*sin(m_dCurAzimuth);
pt2.x = m_nRadius*cos(m_dCurAzimuth - 2*BeamWidth);
pt2.y = m_nRadius*sin(m_dCurAzimuth - 2*BeamWidth);
//指定当前方位扫描波束覆盖的P显区域,并进行重绘
dc.BeginPath();
dc.MoveTo(CPoint(0,0));
dc.LineTo(pt1);
dc.ArcTo(rect,pt1,pt2);
dc.LineTo(CPoint(0,0));
dc.EndPath();
rgn.CreateFromPath(&dc);
InvalidateRgn(&rgn,TRUE);
rgn.DeleteObject();
dc.DeleteDC();
CDialog::OnTimer(nIDEvent);
}
void CPPIDispDemoDlg::OnClose()
{
KillTimer(0);
……
}