UE4嵌入Qt5 三維可視化案例


 

該項目為中鐵客戶端(QT部分),采用QT5.7X64編譯,適用於windows server 2016及以上系統。該項目主要包括:數據可視化、Socket通訊、內嵌ue4可執行程序三大方面
一:數據可視化

數據可視化即采用圖形圖表等第三方庫對采集的數據進行動態展示,可以非常直觀的查看傳感器采集到的數據。這部分主要采用了兩大圖形圖標庫:QT自帶的庫:QtCharts和第三方庫:QCustomPlot。

1.1:QtCharts

QtCharts由於是qt自帶的使用也是比較廣泛的圖形展示庫,本項目中將它封裝在statisticalwindow類中。我們在使用它時應該注意三點

①在.pro文件中添加:

QT += charts;

②添加自己需要的頭文件:

#include <QtCharts/QChartView>
#include <QtCharts/QPieSeries>
#include <QtCharts/QPieSlice>
//根據需要自行添加

③在代碼前聲明命名空間:

using namespace QtCharts;

示例代碼:

void StatisticalWindow::Creatpie_chart()
{
//構建QPieSeries作為餅圖為其添加3個切片QPieSlice數據源
QPieSeries *pieSeries = new QPieSeries();
//設置中心圓大小
pieSeries->setHoleSize(0.35);
//添加數據
QPieSlice *slice1=pieSeries->append(QString::fromLocal8Bit("正常個數92個"), 40);
slice1->setColor(QColor(0,226,255,150));//設置背景顏色
slice1->setLabelColor(QColor(0,226,255,255));//標簽顏色
slice1->setBorderColor(QColor(255,58,93,90));//邊框顏色
slice1->setLabelVisible(true);//設置標簽可見
QPieSlice *slice2=pieSeries->append(QString::fromLocal8Bit("異常個數6個"), 6);
slice2->setLabelColor(QColor(0,226,255,255));
slice2->setColor(QColor(0,226,255,220));
slice2->setBorderColor(QColor(255,58,93,90));//邊框顏色
slice2->setLabelVisible(true);
QPieSlice *slice3 = pieSeries->append(QString::fromLocal8Bit("危險個數4個"), 4);
//slice3->setExploded();//炸開
slice3->setLabelVisible(true);
slice3->setLabelColor(QColor(0,226,255,255));
slice3->setBorderColor(QColor(255,58,93,90));//邊框顏色
slice3->setColor(QColor(255,58,93,230));
QChart *pieChart = new QChart();
pieChart->addSeries(pieSeries); // 將 series 添加至圖表
pieChart->setTitle(QString::fromLocal8Bit("所有監測點數據統計"));
pieChart->setTitleFont(QFont(QString::fromLocal8Bit("所有監測點數據統計"),11));//字體大小
pieChart->setTitleBrush(QBrush(QColor(255,255,255,200)));
//pieChart->setGeometry(0,400,400,400);
pieChart->legend()->setAlignment(Qt::AlignBottom);
//pieChart->setTheme(QChart::ChartThemeBrownSand);
QString path1=QDir::currentPath()+"/imgs/1.png";//大表盤背景圖
QPixmap pixmap(path1);//設定圖片
ui->QchartviewPie_label->setPixmap(pixmap);
pieChart->setBackgroundBrush(QBrush(QColor(255,255,255,0)));//設置背景透明
//設置給控件顯示(QchartviewPie是ui界面中拖入widget控件並將它提升為Qchartview類)
ui->QchartviewPie->setRenderHint(QPainter::Antialiasing);
pieChart->setSelected(true);
ui->QchartviewPie->setChart(pieChart);
}

示例效果:

 

1.2:QCustomPlot

QCustomPlot是目前QT公認為比較好用的第三方圖形展示庫,庫里就只有qcustomplot.h和qcustomplot.cpp兩個主要文件。本項目主要封裝於showonepoint類中,以及mainwindow類也有使用。使用各類需要注意:

①:將下載好的QCustomPlot壓縮包解壓后的qcustomplot.h和qcustomplot.cpp兩個文件放到自己項目的.pro同級目錄,然后打開項目-添加現有文件,分別將這兩個文件添加進自己的項目中。

②:在.pro文件中添加:

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport

③:執行qmark

示例代碼:

//該函數代碼不完整,主要展示圖表細節設置代碼
void MainWindow::ShowAllType()
{
//三大類的折線展示圖
ui->AllTypeshow->addGraph();//添加線條
......
//設置線條
ui->AllTypeshow->graph(0)->setName(QString::fromLocal8Bit("風速平均值"));
ui->AllTypeshow->graph(0)->setPen(QPen(QColor(0,226,255,180)));//風速線顏色
ui->AllTypeshow->setBackground(QBrush(QColor(27, 27, 27,255)));//背景色
//設置坐標軸名稱
//ui->AllTypeshow->yAxis->setSelectedTickLabelColor(QColor(0,226, 255,255));//設置單擊選中后的坐標顏色
ui->AllTypeshow->xAxis->setLabel(QString::fromLocal8Bit("各類傳感器實時數據"));//x標簽
ui->AllTypeshow->xAxis->setLabelColor(QColor(255,255, 255,200));//x標簽顏色
ui->AllTypeshow->xAxis->setTickLabelColor(QColor(0,226, 255,255));//x的數據顏色
ui->AllTypeshow->xAxis->setBasePen(QPen(QColor(0,226, 255,255)));//x軸基礎線顏色
ui->AllTypeshow->xAxis->setSubTickPen(QPen(QColor(0,226, 255,255)));//x軸小刻度顏色
ui->AllTypeshow->yAxis->setLabelColor(QColor(255,255, 255,200));//Y標簽
ui->AllTypeshow->yAxis->setTickLabelColor(QColor(0,226, 255,255));
ui->AllTypeshow->yAxis->setLabel(QString::fromLocal8Bit("實時平均值"));//y標簽
ui->AllTypeshow->yAxis->setBasePen(QPen(QColor(0,226, 255,255)));
ui->AllTypeshow->yAxis->setSubTickPen(QPen(QColor(0,226, 255,255)));
ui->AllTypeshow->xAxis->grid()->setPen(QPen(QColor(0,226, 255,100)));//x柵格顏色
QPen qcustomx_grid_pen;//x軸網格線風格
qcustomx_grid_pen.setColor(QColor(0,226, 255,100));
qcustomx_grid_pen.setStyle(Qt::DotLine);
ui->AllTypeshow->xAxis->grid()->setPen(qcustomx_grid_pen);
QPen qcustomy_grid_pen;//y軸網格線風格
qcustomy_grid_pen.setColor(QColor(0,226, 255,100));
qcustomy_grid_pen.setStyle(Qt::DotLine);
ui->AllTypeshow->yAxis->grid()->setPen(qcustomy_grid_pen);
ui->AllTypeshow->xAxis-> setTicks(true); //不顯示坐標軸
ui->AllTypeshow->xAxis->setRange(0, 30);
ui->AllTypeshow->yAxis->setRange(1, 80);
//啟用拖拽事件等
ui->AllTypeshow->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes |
QCP::iSelectLegend | QCP::iSelectPlottables);
ui->AllTypeshow->legend->setBrush(QBrush(QColor(0,226,255,100)));
ui->AllTypeshow->legend->setVisible(true);//設置圖表的標注可見
//添加數據,這里演示y=30-35浮動數據
for (int i = 0; i<30; i++)
{
X_WD.append(i);
// Y_WD.append(qrand()%3+30);//溫度數據取值在30-33浮動
Y_FS.append(qrand()%8+3);//風速在3-11浮動(單位:m/s)
Y_ZD.append(qrand()%10+50);//震動在50-60浮動(單位%)
Y_YL.append(qrand()%10+15);//應力在15-25浮動單位用kgf/mm²
Y_JS.append(qrand()%10+30);
}
//給三大類實時數據設置數據
ui->AllTypeshow->graph(0)->setData(X_WD, Y_FS);
ui->AllTypeshow->graph(1)->setData(X_WD, Y_ZD);//震動
ui->AllTypeshow->graph(2)->setData(X_WD, Y_YL);
ui->AllTypeshow->graph(3)->setData(X_WD, Y_JS);
}

示例效果:


二:Socket通訊

QT部分的通訊主要涉及到和服務端 、后台通訊以及ue4程序間的通訊,均采用socket通訊來完成:
2.1:服務端通訊

服務端通訊:主要 是獲取傳感器數據,接收多個客戶端並滿足高並發請求傳感器數據。

接收客戶端指令后建立任務隊列,服務端根據隊列發送命令到傳感器以獲取各個傳感器的狀態以及數據等,解析或計算后返回給客戶端用於實施呈現在圖形圖表之中。

主要使用了 -------- 完成端口模型

示例代碼:

void IO_CompletionPort::Init_CompletionPort()
{

this->getcomport();
//初始化完成端口
serialport = new CSerialPort();
//serialport->InitPort("COM2", 9600, true, NOPARITY, 8, ONESTOPBIT);


USES_CONVERSION;
char*comportss = T2A(this->comport);
serialport->InitPort(comportss, 9600, true, NOPARITY, 8, ONESTOPBIT);

//初始化串口連接對象
AllTCPClient.empty();
BShockCollect = false;
//shock采集是否打開
WSADATA wsaData;
//加載socket版本
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
Function->WriteLog(_T("初始化模塊失敗"));
return;
}
else
{
CString message1 = _T("監聽串口:") + this->comport + _T("成功");
Function->WriteLog(message1);
Function->WriteLog(_T("初始化模塊成功"));
}
//創建完成端口
CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (CompletionPort == NULL)
{
Function->WriteLog(_T("創建完成端口失敗"));
return;
}
else
{
Function->WriteLog(_T("創建完成端口成功"));
}
SYSTEM_INFO si;
GetSystemInfo(&si);
int m_nProcessors = si.dwNumberOfProcessors;
NumOfThread = m_nProcessors;
AllWorkThread = new HANDLE[NumOfThread];
for (int i = 0; i < NumOfThread; i++)
{
CWinThread* Thread = AfxBeginThread(WorkThread, this);
AllWorkThread[i] = Thread;
if (Thread)
{
CString str;
str.Format(_T("%d"), Thread->m_nThreadID);
Function->WriteLog(_T("創建工作線程ID=") + str + _T("成功"));
}
else
{
Function->WriteLog(_T("創建工作線程失敗"));
return;
}
}
//根據CPU數量創建*2的工作組線程
ServerListen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
//創建監聽socket
struct sockaddr_in ServerAddress;
ZeroMemory((char *)&ServerAddress, sizeof(ServerAddress));
ServerAddress.sin_family = AF_INET;
ServerAddress.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
ServerAddress.sin_port = htons(Port);
//綁定監聽socket到端口
if (SOCKET_ERROR == bind(ServerListen, (struct sockaddr *) &ServerAddress, sizeof(ServerAddress)))
{
Function->WriteLog(_T("綁定端口失敗"));
}
if (listen(ServerListen, SOMAXCONN) == SOCKET_ERROR)
{
Function->WriteLog(_T("監聽失敗"));
}
else
{
//創建監聽socket的數據
PPER_SOCKET_CONTEXT mainPerHandleData = (PPER_SOCKET_CONTEXT)GlobalAlloc(GPTR, sizeof(PER_SOCKET_CONTEXT));
if (mainPerHandleData == NULL)
{
Function->WriteLog(_T("申請監聽IO數據失敗"));
}
mainPerHandleData->m_Socket = ServerListen;
//將監聽socket的數據綁定到完成端口
if (CreateIoCompletionPort((HANDLE)ServerListen, CompletionPort, (DWORD)mainPerHandleData, 0) == NULL)
{
Function->WriteLog(_T("創建監聽套接字數據失敗"));
}
//創建預接受的socket,並且把這個socket綁定到接受函數指針之上
GuidAcceptEx = WSAID_ACCEPTEX;
DWORD dwBytes = 0;
SOCKET Accept = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (SOCKET_ERROR == WSAIoctl(Accept, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidAcceptEx, sizeof(GuidAcceptEx), &m_lpfnAcceptEx, sizeof(m_lpfnAcceptEx), &dwBytes, NULL, NULL))
{
CString E;
E.Format(_T("%d"), WSAGetLastError());
Function->WriteLog(_T("查找接受函數指針錯誤:") + E);
}
//將接受socket的數據也生成出來然后綁定到函數指針上面
PPER_IO_CONTEXT mainPerIoData = (PPER_IO_CONTEXT)GlobalAlloc(GPTR, sizeof(PER_IO_CONTEXT));
mainPerIoData->m_sockAccept = Accept;
mainPerIoData->dataLength = DATA_BUFSIZE;
RtlZeroMemory(&(mainPerIoData->m_Overlapped), sizeof(OVERLAPPED));
mainPerIoData->m_OpType = ACCEPT;
mainPerIoData->dataLength = DATA_BUFSIZE;
mainPerIoData->m_wsaBuf.buf = mainPerIoData->m_szBuffer;
mainPerIoData->m_wsaBuf.len = mainPerIoData->dataLength;
if (FALSE == m_lpfnAcceptEx(mainPerHandleData->m_Socket, mainPerIoData->m_sockAccept, &mainPerIoData->m_szBuffer, mainPerIoData->dataLength - ((sizeof(SOCKADDR_IN) + 16) * 2), sizeof(SOCKADDR_IN) + 16, sizeof(SOCKADDR_IN) + 16, &dwBytes, &(mainPerIoData->m_Overlapped)))
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
CString E;
E.Format(_T("%d"), WSAGetLastError());
Function->WriteLog(_T("投遞接收請求錯誤:") + E);
}
}
}
}

2.2:ue4程序通訊

內嵌ue4程序通訊:主要是將qt作為服務端,在啟動qt時初始化QTcpServer進行監聽,,然后啟動外部ue4程序等待其連接。

主要功能:

ue4發消息qt接消息,qt接收消息並解析后執行響應操作。(場景中傳感器點擊,切換當前展示的傳感器等)
qt發消息,ue4程序接消息並解析:(響應解除警報,取消閃爍報警、實時更新傳感器狀態、警報報警等)

①在.pro中添加: QT += network

②在頭文件添加

#include <QTcpServer>
#include <QTcpSocket>

②初始化監聽、新連接、接收消息

//初始化socket進行監聽,等待連接
void MainWindow::SocketListen()
{
server = new QTcpServer();
server->listen(QHostAddress::Any, Port);//監聽端口Port(#define Port 9537)
connect(server, SIGNAL(newConnection()), this, SLOT(SocketConnect()));//當有一個新連接過來, 執行socketcontent
}
//新的Socket連接
void MainWindow::SocketConnect()
{
//初始化thissocket,得到這個連接
thissocket = server->nextPendingConnection();
if(thissocket)
{
connect(thissocket, SIGNAL(readyRead()), this, SLOT(SocketRecv())); //當接受到消息
}
}
//接受解析socket消息
void MainWindow::SocketRecv()
{
if(thissocket->isValid())
{
QByteArray arr=thissocket->readAll();//讀取消息
QString data = arr;//QString::fromLocal8Bit(arr);將接收得到的unicode字符轉為自己的utf8字符集,如果發送的已經轉了那就直接接收
if(!data.isEmpty())
{
Execute_sockecommand(data);//根據命令執行操作響應
}
}
}


三:內嵌ue4可執行程序

該部分主要是將外部程序嵌入qt程序中,作為其一個子窗口進行展示。只要是通過qt啟動外部程序,然后利用windows函數FindWindow()獲得ue4窗口句柄handle,然后就是對這個handle的操作。

目前這個嵌入ue4exe的地方有點問題:一般做法是利用一個QWindow 來作為這個handle的代理,再利用QWidget在顯示這個QWindow 已達到QT嵌入外部exe。

主要功能:

傳感器位置點坐標轉換,即需要解決現實場景->三維場景的傳感器映射。(解決經緯度坐標->ue4的世界空間坐標)。
傳感器動態配置,基於上方的坐標轉換,在后台進行傳感器添加后,通過讀取sql數據庫進行不同種類傳感器的實時生成。(實際最后采用的http協議,由java后端提供接口,沒有直接查詢數據庫)。
三維場景的效果:樓層分層透明、各個方位的視圖展示、傳感器報警快速定位,場景漫游模式等(ue4是很強大的三維引擎,只要產品經理給力可以設計很多的酷炫效果,已達到說服客戶的目的哦)。

**********實際上這種方法對絕大多數應用程序來說都比較實用,但是對於ue4來說卻存在焦點問題!嘗試許久無果后,無奈之舉只能直接對handle進行移動縮放讓其跟隨qt窗口進行運動!!!!!

**********最后設計成了四個window窗口,即上、右、下三個qt窗口和左邊的ue4窗口。

①移動ue4exe窗口

MoveWindow(hwnWindow,startpoint.x(),startpoint.y(),1041,540,1);

②關閉時自身時結束ue4exe。(結束本應該寫在~mainwindow()里面,這里直接截取QCloseEvent事件進行處理)

//關閉操作響應
void MainWindow::closeEvent(QCloseEvent *event)
{
switch( QMessageBox::information(this,QString::fromLocal8Bit("提示"),QString::fromLocal8Bit("你確定退出該軟件?"),QString::fromLocal8Bit("確定"),QString::fromLocal8Bit("取消"),0,1))
{
case 0:
{
QString exename="UE4Game.exe";
exename="sensor.exe";
terminateMyexe(exename);//查找並結束exe
delete mypie;
break;
event->accept();//接受關閉消息
}
case 1:
default:
event->ignore();//忽略
break;
}
}


四:管理后台

該系統為滿足客戶要求的傳感器動態化,需要解決進行傳感器的動態配置和解析,即現場加裝了一個風速傳感器后,系統能夠自動獲取並展示到三維模型中去,實現實時的全動態加載和監控。

后台采用java開發。

主要功能:

基本的角色、賬號等菜單、數據權限
動態添加、修改傳感器信息,計算參數配置
數據統計等。

 

客戶端整體效果:

該版本為了解決ue4焦點問題而新寫的第二版本,無奈的采用了3個qt的window進行拼接,后面的同學如果采用這方式進行大屏展示,並解決了相關ue4的焦點問題。歡迎留言討論,大家一起學習。
————————————————
版權聲明:本文為CSDN博主「螢火1129」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_22824481/article/details/79616433


免責聲明!

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



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