From b933c1058bae5b1189c83d10688305280460cea5 Mon Sep 17 00:00:00 2001 From: Yannic Beyer Date: Fri, 7 Jul 2023 15:41:12 +0200 Subject: [PATCH] Initial FlexiFlightViz version --- .github/workflows/build.yml | 106 ++++ FlexiFlightVis.pro | 36 ++ README.md | 47 ++ deploy/FlexiFlightVis.desktop.desktop | 7 + deploy/FlexiFlightVis.png | Bin 0 -> 86251 bytes src/aircraft.cpp | 817 ++++++++++++++++++++++++++ src/aircraft.h | 192 ++++++ src/main.cpp | 14 + src/mainwindow.cpp | 184 ++++++ src/mainwindow.h | 62 ++ src/myudp.cpp | 118 ++++ src/myudp.h | 39 ++ src/structWithFieldnames.cpp | 304 ++++++++++ src/structWithFieldnames.h | 58 ++ 14 files changed, 1984 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 FlexiFlightVis.pro create mode 100644 README.md create mode 100644 deploy/FlexiFlightVis.desktop.desktop create mode 100644 deploy/FlexiFlightVis.png create mode 100644 src/aircraft.cpp create mode 100644 src/aircraft.h create mode 100644 src/main.cpp create mode 100644 src/mainwindow.cpp create mode 100644 src/mainwindow.h create mode 100644 src/myudp.cpp create mode 100644 src/myudp.h create mode 100644 src/structWithFieldnames.cpp create mode 100644 src/structWithFieldnames.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..f7122ec --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,106 @@ +name: Build and Test + +on: + push: + branches: + - main + tags: + - 'v*' + pull_request: + branches: + - main + +defaults: + run: + shell: bash + +env: + SOURCE_DIR: ${{ github.workspace }} + QT_VERSION: 5.15.2 + ARTIFACT: FlexiFlightVis + +jobs: + build: + runs-on: ubuntu-20.04 + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + version: ${{ env.QT_VERSION }} + host: linux + target: desktop + dir: ${{ runner.temp }} + + - name: Install dependencies + run: sudo apt-get install -y libglfw3-dev libgl1-mesa-dev libglu1-mesa-dev + + - name: Create build directory + run: | + mkdir ${{ runner.temp }}/shadow_build_dir + mkdir ${{ runner.temp }}/shadow_app_dir + + - name: Build + working-directory: ${{ runner.temp }}/shadow_build_dir + run: | + qmake ${SOURCE_DIR}/FlexiFlightVis.pro -spec linux-g++ CONFIG+=debug CONFIG+=qml_debug + make + + - name: Download linuxdeployqt + working-directory: ${{ runner.temp }}/shadow_app_dir + run: | + wget -c --quiet "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" + chmod a+x linuxdeployqt-continuous-x86_64.AppImage + + - name: Create AppImage + working-directory: ${{ runner.temp }}/shadow_app_dir + run: | + cp ${SOURCE_DIR}/deploy/FlexiFlightVis.desktop.desktop ${{ runner.temp }}/shadow_build_dir/ + cp ${SOURCE_DIR}/deploy/FlexiFlightVis.png ${{ runner.temp }}/shadow_build_dir/ + ./linuxdeployqt-continuous-x86_64.AppImage ../shadow_build_dir/FlexiFlightVis -appimage + + - name: Save artifact + uses: actions/upload-artifact@main + with: + name: ${{ env.ARTIFACT }} + path: ${{ runner.temp }}/shadow_app_dir/${{ env.ARTIFACT }}-x86_64.AppImage + + release: + if: github.event_name == 'push' && github.ref_type == 'tag' + name: Release + runs-on: ubuntu-20.04 + needs: build + steps: + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: ${{ env.ARTIFACT }} + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + draft: false + prerelease: false + - name: Set env + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + - name: Test + run: | + echo $RELEASE_VERSION + echo ${{ env.RELEASE_VERSION }} + - name: Upload release asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./FlexiFlightVis-x86_64.AppImage + asset_name: FlexiFlightVis-${{ env.RELEASE_VERSION }}.AppImage + asset_content_type: application + diff --git a/FlexiFlightVis.pro b/FlexiFlightVis.pro new file mode 100644 index 0000000..f068598 --- /dev/null +++ b/FlexiFlightVis.pro @@ -0,0 +1,36 @@ +QT += core gui opengl network + +LIBS += -lGLU + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + src/aircraft.cpp \ + src/main.cpp \ + src/mainwindow.cpp \ + src/myudp.cpp \ + src/structWithFieldnames.cpp + +HEADERS += \ + src/aircraft.h \ + src/mainwindow.h \ + src/myudp.h \ + src/structWithFieldnames.h + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d5c795 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# FlexiFlightVis + +FlexiFlightVis is a program for a technical visualization of flexible airplanes. +It is intended to run the simulation of the flexible airplane in Matlab/Simulink. +However, since Matlab/Simulink has limited capabilities for 3D visualization, FlexiFlightVis can be used for visualization. +Therefore, the state of the airplane must be transferred from Matlab/Simulink to FlexiFlightVis via UDP for real-time visualization. +Because of the UDP interface, FlexiFlightVis can run on a different computer than the Matlab/Simulink simulation. +FlexiFlightVis assumes a certain organization of the aircraft state (struct). +Take a look at the example to see how it works. + + +## Installation + +- Linux: + Download and run the AppImage in the Releases. + +- Windows: + Currently not supported (but you can try to build from source). + + +## Example + +FlexiFlightVis is used in the following research project: +[https://github.com/iff-gsc/SE2A_Aviation_2023](https://github.com/iff-gsc/SE2A_Aviation_2023) + + +## Build from source (Linux) + +- Install Qt5: + ``` + sudo apt-get install qt5-default + ``` +- Install dependencies: + ``` + sudo apt-get install -y libglfw3-dev libgl1-mesa-dev libglu1-mesa-dev + ``` +- Clone FlexiFlightVis: + ``` + git clone https://github.com/iff-gsc/flexiflightvis.git + ``` +- Compile the program from Qt Creator or from terminal: + ``` + mkdir build + cd build + qmake ../FlexiFlightVis/FlexiFlightVis.pro -spec linux-g++ CONFIG+=debug CONFIG+=qml_debug + make + ``` diff --git a/deploy/FlexiFlightVis.desktop.desktop b/deploy/FlexiFlightVis.desktop.desktop new file mode 100644 index 0000000..2f227f3 --- /dev/null +++ b/deploy/FlexiFlightVis.desktop.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Application +Name=FlexiFlightVis +Comment=Aeroelastic Airplane Visualization +Exec=FlexiFlightVis +Icon=FlexiFlightVis +Categories=Office; diff --git a/deploy/FlexiFlightVis.png b/deploy/FlexiFlightVis.png new file mode 100644 index 0000000000000000000000000000000000000000..e44d21038472f46b726dc626535770f1a4c8e765 GIT binary patch literal 86251 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

YUY z0@bnS*m|0x@`pPRXp5+<8dlSBQB$WY-1>PCn*u-L(UNpS`Rd>n&yINAzOXhPb` zqy^~;(25^w0uD<2)tj4o52Yp$LLp0qp7q`#o}ERHP&T@CGcjt~A8pYuUwAovaQYzK zJ^ekpy?%@~u}_CQrcd!MeQ+DHP3>pAhREPBFi0BGSIt@dx<+N%5HcgvAX<=qi1PtTjlAyI zATbQ(*$8w0*pUs=Zfp&*2T?Zx?C^Hw>Vdk?ljATrGUUJz$v3 zE;6e6zyu6c|9*ZK~01iaXq6ucDHCUs%ZbPj#(wM=bi%y zkBkx<52DdLEk%(+Q)AX1)611~LxGKR0;4b|j%UI!HX?Y@sh(ou&frfsi~Cqb8=fL2moAB=cJ+KedLE z^n|G>JKMMhC{5Cm_fRv`!=EoWplcdaO?U0B6B-K2+Rcz>8|IImuUs+e2u~m5!y`s# z$1^S=A2(ng6hw^*C?t{8yDGCxTww%?R8lQMDA|Q#fI>})?3$$pWA4E@(>@OlXceS$ zaj4v-V*wr9fU$Am?cLl7l(5`zoO;Pn>MBhS zTJdx&7&T$R5p#wm40;N3gry6lUzY_0TEw;_*9s}0D_LbzP=%}QA<8wBN`~|0R$Be4 zngQMMK#!@WyU%RQb#kwsXq<~@^bjQ|5^lCfP1KJZo%*=>M?AbGXVSk8z(yht$}WYY z&3lneoo}$97%5y(F_D49S&Ip9=HZf55`C}a(?)f)Os&u=YAhU_{l2-Vg9m)GXk_cH z7n0|zpZ79$OJHY<9f=LAniS8eR9M^mxF(4LO5ojs=R`@9UsWS#3=7K>DJuLem$q26 zAq1hG(bkCQ*4?!xIU388_!oPwOYVD6!M-~NC3##ZmhpQShG80;7l4S>1-bZrX4FuR zOdy}d2|d>Y%fCgGp3fC*4=6Z`8ZR&8eg=A-v_SzMo6hCb6HDF*`!JrVd|m@ZgO+eh15rM+@3Ypx& z>HQ$r3WR!|0exYkqP_J|TLZkDfaWD6*ebdPEu&CY(@(fqaV@bPrm_nl=GItp&LFb$ zwsSUcM3)-9l_2-K8_L%i?w8>AdUhTJtJtPDm4SFXx1}WV@bts6pYmkg+L?zZ)of@3}Y;I8A~f$LOhNgxQQP z?2T!6eN3}DN8p7O3kn?nxp98JI+U*fao`MmX($ao)~S`!DZ+b{>+rsoy&9rYBXd1f zsJi?;Xe}j-VmSRf-uzAp6nzTX5&BjlAK5d&>J9^r^B~I3D_SX&k^Nh;77=@(s0&@G zpn!7SRTydoP1P3=H0C567q`Z=vpz2HfRBHh=XzEgb<36P2n)>cevZQL^}-8!)PL&W z6_#9?Scxs?&Fju%{El(tckJ1i+-sjWb?SL{-bpAce>gt!?j98IF)3fgTe5N%`m`KW z^c08pgR@BrmV(l2z=b1jWh^OPII}!lcu!v9{`vf0&i+JlQRj;Ny1A#ZUle?= z1o&|~g+ezDnUt*TQZaxyBShZYs@a9}Z29+DczB_I(}>0!#Z&$|ml$zszz;-29q^6T z!eCc%771>qY}PKqlcHP;P=KkWjO-BNM*g!0!)A&=SUX++vj6y^uje zAhSY|QO-g9u&!x@n(o+}_k?YmFlZB@PhTV&>kXU6w^ri zlp+LXoh&f7R&0pjq{uCT=mT<);QOpp5Vj6ZsUGr+#xdIMYm8^EsnnKc?Zf{zgQE*OvyPLmHWYf9y1SQ-Q zA;1P~T)LW!B>=}z3HvA^tObs`E5+b$u34JZ-MEFm;kJjzIyh%mL2BxCJn$g>htHYb?|9&^tM~Ymwm? z=jM(Tty>QX&2Su4@ZrxdrC=H``?|DNgYkR>|h^#xW zn+la`b~!b0sc;WOWvzz7N1=7+aYyvY@8g2Gv)red^ng~8@N(c*1je8+QY?6K+(+`> z8Ps;gExQ(Sh_oyUL3(@`h4KBKF&A0Pv5F$Qn`NAmc7$d#kbaI}Nvl8fpIgyA`a!fZqqclv?y zZrFt{3eu}{oq?R#L=is*b#Q-s6ApUVh-X3b3;AqFEVJ~EZoe{(=6~>jZ!Twe7)u%k zEBMMv0P!W}$Va_t;C68h!mOq<>lN*;kLO9zN&|pM@M`R%xu9fVN~2 zo1BZS!H^bHtR%eF67q7cE-NUJW9cA=ij@$NA%{@;_5NT3x+qOY=W@gX9z1}fA3SL1 zz-aNUdiA9#-={>_pMo+ZDys?g;Kx2*ubGlZc{ns6hbV!*n5ci^Qw|O%drnJx_~@+s0p*V2fVvJrkPgqqGbh@S_UP1%kb)e%>L7y3U9BoVifdo z8!fUqrZlbBx+Xi%XDKEvFfRX{dq+^7#aL7H;UtD(n2Ba)S<<(}#m#sc7E>!3N)Fbu zl38sbn}GO9$Vd$u)v#HHDCP8wli!ZeC>iQS-Mb9*+$Vz=HndfwSdNl?tU&dkf`gY! z)hrEA!ZEfTMayu?MpnUXVG;S)^BC~C%@J*`jp%S%&uf4Md~EhM<_wl+WkRu|JgtB- zQ~ywjXOyw>jHPM8Ic9~znkuz@9Xac5&7owDngIcs!wWl`tk=GT=+T{?2e$S?ALF=` zy)rRcI(#>7_ZJ&02wRnB_n5$_nbc097RiQ7ZgNTj!KcFmK1J3OAH#Q+A{fL?`D%~n zLQSF-2dxNi*`83fXvVH7Mvxys%X#2^GpxKCX6bcO#)B;Nmixki=qvCBlwzBf}!(qe;lo;DdY(*^|Z^>^US31enVx zJmG9uC!C05iO-h6x53{z2Z#1esv=(?g8BCDM&!zk51_yYIMhF1BNsT@{a=6>=_ zmk^UIv-XQL(YhwCYr1V`LQFhQj9xS&&n1na$6jMw86ekM+KUtRnS*!jTT);`C`L+aWZ_X!X52C&XPAP zO$xc^KH<#XGCCn8Z<#43>F8xp4<7K1_kxi}z>XUdd9rAR$nE}S!r`fSqH zvyfcCnq$%>(byYsLInslX$=Bu+8Q&R*%*=6(auADOXY$l{H;7MAQr?fi&J*KoHe4X z@Gch~!+8Hdg)U3WT*EbxELR?Sq>288;-wOW8mV9{`MR)a0&Es2a*koVF<5_kai~A)BvYl zkaA}}7oL^t#|26t-X_kw$ZRjI3>n5Y^pvo#~fHi0{n7cosUcA5A>uX0K5;d?CG?zl6+;hrUj zGsW3&C^8CA@L~!|i3bIIU6e2kVUIYI0s$13kg6B<2Td4jA>fs;3m_Clj})#&Ce1#E zuOa&KQs?m|k_$cub*|*ZRIKv4FmXtxxagl3i?krq4OM(r1|i!bPby*VD@+T$=0zn=y|`W0H1VViFn0T)tU zmff^mgFyvlJVgOxEfcc|c9-DN(12#Q=Q?C9LpA`%c;KQvN)c9)+p8yioOlR2%ubG@ z3?UTDflfvMQ3b@a$TzExVXY8jVMRy9kZEdJvJD#t*Gb{W^z#vr3CJDaj8wmIl-n%ee|EI#J(T1QIi9jd9?@hSgy~#?u#S%y5*{j zmspD)pGI~U_kVum*9RHX3ne%(U|!SuyN4{KYkqQl;S=lu$EmQKIAo2QfYAw)Ir?3z z23S_(vvzR`KMu8n3B?v1Sg2U#P&3CyMcY+`)9uSd3~ta|2rj$O+YDRKr0rI-=&5Jsh{$ogHuq8bi$LcIM~{VJ8U%ZMYH?@2>d{S zv&e>@2n#-u>i;PIxHx1S#5@uCoNR5mCJxG~doQh57G&r=Zx^~sK*w>TcB{y^~9ZJm+SJ8m{T+kU@YF7p}JPkJHylQXOtiCQ3`c7AiUbTQZ zE>B9%W!}6hHB40Cw_xs`@5_!CZ0x0sqATMouu@npa&ygqIWn*?DB#r%4wQojp>%Ml zW(lK(RAFq5?mMh*dW2C=;{b=gD~D0m=E@d~$|}&^eFuhj630dm`}i$5E)#Cp@fy

P#pcK;(t@;&A9M( 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 n6*UwU< fieldnames = state.getSubStructNames(); + for (int i=0; i wind_identifier = {"external"}; + for (int j=0; j V_Wb_i = windStruct.getSubData("V_Wb"); + m_V_Wb_i = Math::reshape2Matrix(V_Wb_i); + break; + } + } + } +} + +void Wind::setScaling(double velocity) { + m_scaling = 50 / velocity; +} + +double RigidBody::getVelocity() { + double velocity = m_V_Kb.length(); + // double velocity = -m_V_Kb.x(); + // double velocity = 15.0f; + return velocity; +} + +void Wing::setVelocity(double velocity) { + m_velocity = velocity; +} + +void Fuselage::setVelocity(double velocity) { + m_velocity = velocity; +} + +void Wind::plot(double downShift, QVector> pos, double velocity) { + // draw fuselage geometry + + QVector3D start_pt; + QVector3D end_pt; + + int length; + if (m_V_Wb_i.isEmpty()) { + length = 0; + } + else { + length = m_V_Wb_i[0].length(); + } + + glColor3f(143.0/255.0,169.0/255.0,186.0/255.0); + + QVector3D shift(0,0,downShift); + QGenericMatrix<3,3,double> rotationMatrix; + rotationMatrix.setToIdentity(); + + setScaling(velocity); + + for (int i=0; i < length; i++) { + + // duplicate (to do) + start_pt.setX(pos[0][i]); + start_pt.setY(pos[1][i]); + start_pt.setZ(0); + + start_pt = Math::local2Global(start_pt,shift,rotationMatrix); + + end_pt.setX(pos[0][i]+m_scaling*m_V_Wb_i[0][i]); + end_pt.setY(pos[1][i]+m_scaling*m_V_Wb_i[1][i]); + end_pt.setZ(0+m_scaling*m_V_Wb_i[2][i]); + end_pt = Math::local2Global(end_pt,shift,rotationMatrix); + + //glPointSize ( 1.0f ); + glBegin ( GL_POINTS ); + glVertex3f ( start_pt.x(), start_pt.y(), start_pt.z() ); + glEnd(); + + glBegin(GL_LINE_STRIP); + glVertex3f(start_pt.x(),start_pt.y(),start_pt.z()); + glVertex3f(end_pt.x(),end_pt.y(),end_pt.z()); + glEnd(); + } +} + +void RigidBody::setRigidBody(StructWithFieldnames rigidBody) { + QVector q_bg = rigidBody.getSubData("q_bg"); + QVector V_Kb = rigidBody.getSubData("V_Kb"); + m_q_bg.setScalar(q_bg[0]); + m_q_bg.setX(q_bg[1]); + m_q_bg.setY(q_bg[2]); + m_q_bg.setZ(q_bg[3]); + m_V_Kb.setX(V_Kb[0]); + m_V_Kb.setY(V_Kb[1]); + m_V_Kb.setZ(V_Kb[2]); +} + +void Wing::setWing(QString name, Vortex vortex, Cntrl_pt cntrl_pt) { + // m_name = Property::m_name; + Property::setName(name); + m_vortex = vortex; + m_cntrl_pt = cntrl_pt; +} + +void Vortex::setVortex(StructWithFieldnames vortex) { + QVector fieldNames = vortex.getSubDataNames(); + for (int i=0; i pos = vortex.getSubData(fieldName); + int num_points = pos.length()/3; + for (int i=0; i fieldNames = cntrl_pt.getSubDataNames(); + for (int i=0; i pos = cntrl_pt.getSubData(fieldname); + int num_points = pos.length()/3; + for (int i=0; i fieldNames = coeffLocal.getSubDataNames(); + for (int i=0; i array; + QVector> array2D(3); + int k = 0; + if (fieldname=="c_XYZ_b") { + array = coeffLocal.getSubData(fieldname); + int arrayLength = array.length(); + int subArrayLength = arrayLength/3; + for (int i=0; i<3; i++) { + QVector subArray(subArrayLength); + k = i; + for (int j=0; j subArray(subArrayLength); + k = i; + for (int j=0; j fieldNames = wing.getSubStructNames(); + StructWithFieldnames geometry = wing.getSubStruct("geometry"); + StructWithFieldnames vortex = geometry.getSubStruct("line_25"); + StructWithFieldnames cntrl_pt = geometry.getSubStruct("ctrl_pt"); + StructWithFieldnames aero = wing.getSubStruct("aero"); + StructWithFieldnames coeffLocal = aero.getSubStruct("coeff_loc"); + StructWithFieldnames segments = geometry.getSubStruct("segments"); + StructWithFieldnames actuators = wing.getSubStruct("actuators"); + StructWithFieldnames segments2 = actuators.getSubStruct("segments"); + QVector origin = geometry.getSubData("origin"); + StructWithFieldnames unsteady = aero.getSubStruct("unsteady"); + this->m_vortex.setVortex(vortex); + this->m_cntrl_pt.setCntrlPt(cntrl_pt); + this->m_coeffLocal.setCoeffLocal(coeffLocal); + this->m_flap_depth = segments.getSubData("flap_depth"); + QVector flap_deflection_2x = segments2.getSubData("pos"); + for (int i=0; i X = unsteady.getSubData("X"); + for (int i=0; isetOrigin(origin); + this->wind.setWind(wing); +} + +void Wing::setWing(StructWithFieldnames wing, QString name) { + this->setWing(wing); + this->setName(name); +} + +void Wing::setOrigin(QVector origin) { + m_origin.setX(origin[0]); + m_origin.setY(origin[1]); + m_origin.setZ(origin[2]); +} + +QVector3D Wing::getPointLeadAt(int i) { + QVector3D vortex_i( m_vortex.m_x[i], m_vortex.m_y[i], m_vortex.m_z[i] ); + // shift the point + vortex_i = vortex_i + m_origin + m_shift; + double chord_i = m_vortex.m_c[i]; + double twist_i; + if (i==0) { + twist_i = m_cntrl_pt.m_local_incidence[0]; + } + else if (i==m_vortex.m_x.length()-1) { + twist_i = m_cntrl_pt.m_local_incidence.last(); + } + else { + twist_i = ( m_cntrl_pt.m_local_incidence[i-1] + m_cntrl_pt.m_local_incidence[i] ) / 2; + } + QGenericMatrix<1,3,double> pointLeadMat; + pointLeadMat(0,0) = vortex_i.x()+chord_i/4*cos(twist_i); + pointLeadMat(1,0) = vortex_i.y(); + pointLeadMat(2,0) = vortex_i.z()-chord_i/4*sin(twist_i); + + // rotate the point + pointLeadMat = m_rotationMatrix * pointLeadMat; + + QVector3D pointLead(pointLeadMat(0,0),pointLeadMat(1,0),pointLeadMat(2,0)); + + return pointLead; +} + +QVector3D Wing::getPointTrailAt(int i, int side) { + QVector3D vortex_i( m_vortex.m_x[i], m_vortex.m_y[i], m_vortex.m_z[i] ); + // shift the point + vortex_i = vortex_i + m_origin + m_shift; + double chord_i = m_vortex.m_c[i]; + double twist_i; + if (i==0) { + twist_i = m_cntrl_pt.m_local_incidence[0]; + } + else if (i==m_vortex.m_x.length()-1) { + twist_i = m_cntrl_pt.m_local_incidence.last(); + } + else { + twist_i = ( m_cntrl_pt.m_local_incidence[i-1] + m_cntrl_pt.m_local_incidence[i] ) / 2; + } + QGenericMatrix<1,3,double> pointTrailMat; + int flap_idx = 0; + if ( (side == 0) & (i > 0)) { + flap_idx = i-1; + } + else if (side == 1) { + if (i <= m_cntrl_pt.m_x.length()) { + flap_idx = i; + } + else { + flap_idx = m_cntrl_pt.m_x.length(); + } + } + double rel_shift = 0.75 - m_flap_depth[flap_idx]; + pointTrailMat(0,0) = vortex_i.x()-chord_i*rel_shift*cos(twist_i); + pointTrailMat(1,0) = vortex_i.y(); + pointTrailMat(2,0) = vortex_i.z()+chord_i*rel_shift*sin(twist_i); + // rotate the point + pointTrailMat = m_rotationMatrix * pointTrailMat; + + QVector3D pointTrail(pointTrailMat(0,0),pointTrailMat(1,0),pointTrailMat(2,0)); + + return pointTrail; +} + +QVector3D Wing::getPointFlapAt(int i, int side) { + QVector3D vortex_i( m_vortex.m_x[i], m_vortex.m_y[i], m_vortex.m_z[i] ); + // shift the point + vortex_i = vortex_i + m_origin + m_shift; + double chord_i = m_vortex.m_c[i]; + double twist_i; + if (i==0) { + twist_i = m_cntrl_pt.m_local_incidence[0]; + } + else if (i==m_vortex.m_x.length()-1) { + twist_i = m_cntrl_pt.m_local_incidence.last(); + } + else { + twist_i = ( m_cntrl_pt.m_local_incidence[i-1] + m_cntrl_pt.m_local_incidence[i] ) / 2; + } + QGenericMatrix<1,3,double> pointFlapMat; + int flap_idx = 0; + if ( (side == 0) & (i > 0)) { + flap_idx = i-1; + } + else if (side == 1) { + if (i <= m_cntrl_pt.m_x.length()) { + flap_idx = i; + } + else { + flap_idx = m_cntrl_pt.m_x.length(); + } + } + double rel_shift = 0.75 - m_flap_depth[flap_idx]; + double rel_shift_flap = m_flap_depth[flap_idx]; + int ii; + if (i>m_cntrl_pt.m_x.length()) { + ii = m_cntrl_pt.m_x.length(); + } + else { + ii = i; + } + // rotate about flap rotation axis + QVector3D axis = this->getPointTrailAt(ii+1,0) - this->getPointTrailAt(ii,1); + QQuaternion flap_quat = QQuaternion::fromAxisAndAngle(axis,m_flap_deflection[flap_idx]+57.3*twist_i); + QVector3D flap_vector; + flap_vector.setX(-rel_shift_flap*chord_i); + flap_vector.setY(0); + flap_vector.setZ(0); + QVector3D flap_vector_rotated = flap_quat.rotatedVector(flap_vector); + pointFlapMat(0,0) = vortex_i.x() - chord_i*rel_shift*cos(twist_i) + flap_vector_rotated.x(); + pointFlapMat(1,0) = vortex_i.y() + flap_vector_rotated.y(); + pointFlapMat(2,0) = vortex_i.z() + chord_i*rel_shift*sin(twist_i) + flap_vector_rotated.z(); + // rotate the point + pointFlapMat = m_rotationMatrix * pointFlapMat; + + QVector3D pointFlap(pointFlapMat(0,0),pointFlapMat(1,0),pointFlapMat(2,0)); + + return pointFlap; +} + +QVector3D Wing::getCenterOfPressureAt(int i) { + QVector3D cntrl_pt_i( m_cntrl_pt.m_x[i], m_cntrl_pt.m_y[i], m_cntrl_pt.m_z[i] ); + QVector3D cop_i; + QGenericMatrix<1,3,double> cop_i_Mat; + // shift the point + cntrl_pt_i = cntrl_pt_i + m_origin + m_shift; + // shift the point from control point to center of pressure + float chord = ( m_vortex.m_c[i] + m_vortex.m_c[i+1] ) / 2; + float distance = m_coeffLocal.m_c_m_airfoil[i] / sqrt( pow(m_coeffLocal.m_c_XYZ_b[0][i],2) + pow(m_coeffLocal.m_c_XYZ_b[1][i],2) + pow(m_coeffLocal.m_c_XYZ_b[2][i],2) ) * chord; + if (distance>chord/4) { + distance = chord/4; + } + else if (distance < -chord*3/4) { + distance = -chord*3/4; + } + // shift from control point to c/4 + distance = distance + chord/2; + cop_i.setX( cntrl_pt_i.x() + distance * cos(m_cntrl_pt.m_local_incidence[i]) ); + cop_i.setY(cntrl_pt_i.y()); + cop_i.setZ( cntrl_pt_i.z() - distance * sin(m_cntrl_pt.m_local_incidence[i]) ); + + // rotate (wow) + cop_i_Mat(0,0) = cop_i.x(); + cop_i_Mat(1,0) = cop_i.y(); + cop_i_Mat(2,0) = cop_i.z(); + cop_i_Mat = m_rotationMatrix * cop_i_Mat; + cop_i.setX(cop_i_Mat(0,0)); + cop_i.setY(cop_i_Mat(1,0)); + cop_i.setZ(cop_i_Mat(2,0)); + + return cop_i; +} + +void Wing::plot(QVector3D shift, QQuaternion rotation) { + m_shift = shift; + m_rotationMatrix = Math::quaternion2RotationMatrix(rotation); + this->plot(); +} + +void Wing::plot() { + + // draw aerodynamic forces + QVector3D pointCop; + QVector3D pointCopPlusForce; + QVector3D pointCopPlusLadForce; + for (int i=0; i < m_cntrl_pt.m_x.length(); i++) { + pointCop = this->getCenterOfPressureAt(i); + pointCopPlusForce = pointCop; + pointCopPlusLadForce = pointCop; + QVector3D forceVector( m_coeffLocal.m_c_XYZ_b[0][i], m_coeffLocal.m_c_XYZ_b[1][i], m_coeffLocal.m_c_XYZ_b[2][i] ); + QVector3D ladForceVector( 0, 0, -m_cL_act2[i] ); + + // rotate (wow) + QGenericMatrix<1,3,double> forceVectorMat; + forceVectorMat(0,0) = forceVector.x(); + forceVectorMat(1,0) = forceVector.y(); + forceVectorMat(2,0) = forceVector.z(); + forceVectorMat = m_rotationMatrix * forceVectorMat; + forceVector.setX(forceVectorMat(0,0)); + forceVector.setY(forceVectorMat(1,0)); + forceVector.setZ(forceVectorMat(2,0)); + QGenericMatrix<1,3,double> ladForceVectorMat; + ladForceVectorMat(0,0) = ladForceVector.x(); + ladForceVectorMat(1,0) = ladForceVector.y(); + ladForceVectorMat(2,0) = ladForceVector.z(); + ladForceVectorMat = m_rotationMatrix * ladForceVectorMat; + ladForceVector.setX(ladForceVectorMat(0,0)); + ladForceVector.setY(ladForceVectorMat(1,0)); + ladForceVector.setZ(ladForceVectorMat(2,0)); + + pointCopPlusForce = pointCop + forceVector * (m_vortex.m_c[i]+m_vortex.m_c[i+1])/2 * 5; + pointCopPlusLadForce = pointCop + ladForceVector * (m_vortex.m_c[i]+m_vortex.m_c[i+1])/2 * 5; + QVector3D pointSeparationHelp = pointCop + (1-m_trailing_edge_sep_pt[i])*(pointCopPlusForce-pointCop); + // pointCopPlusForce = pointCop + forceVector * 10; + glBegin(GL_LINE_STRIP); + glColor4f(m_forceColor[0],m_forceColor[1],m_forceColor[2],m_forceAlpha); + glVertex3f(pointCop.x(),pointCop.y(),pointCop.z()); + glColor4f(m_forceColor[0],m_forceColor[1],m_forceColor[2],m_forceAlpha); + glVertex3f(pointSeparationHelp.x(),pointSeparationHelp.y(),pointSeparationHelp.z()); + glColor4f(m_stallColor[0],m_stallColor[1],m_stallColor[2],m_stallAlpha); + glVertex3f(pointCopPlusForce.x(),pointCopPlusForce.y(),pointCopPlusForce.z()); + glEnd(); + glColor4f(m_ladForceColor[0],m_ladForceColor[1],m_ladForceColor[2],m_ladForceAlpha); + glBegin(GL_LINE_STRIP); + glVertex3f(pointCop.x(),pointCop.y(),pointCop.z()); + glVertex3f(pointCopPlusLadForce.x(),pointCopPlusLadForce.y(),pointCopPlusLadForce.z()); + glEnd(); + } + // draw wing geometry + QVector3D pointLeadLeft; + QVector3D pointTrailLeft; + QVector3D pointLeadRight; + QVector3D pointTrailRight; + QVector3D pointFlapLeft; + QVector3D pointFlapRight; + QVector3D cntrl_pt; + QVector> cntrl_pos{3}; + + for (int i=0; i < m_cntrl_pt.m_x.length(); i++) { + pointLeadRight = this->getPointLeadAt(i+1); + pointTrailRight = this->getPointTrailAt(i+1,0); + pointLeadLeft = this->getPointLeadAt(i); + pointTrailLeft = this->getPointTrailAt(i,1); + glColor4f(m_faceColor[0],m_faceColor[1],m_faceColor[2],m_faceAlpha); + glBegin(GL_QUADS); + glVertex3f(pointLeadLeft.x(),pointLeadLeft.y(),pointLeadLeft.z()); + glVertex3f(pointLeadRight.x(),pointLeadRight.y(),pointLeadRight.z()); + glVertex3f(pointTrailRight.x(),pointTrailRight.y(),pointTrailRight.z()); + glVertex3f(pointTrailLeft.x(),pointTrailLeft.y(),pointTrailLeft.z()); + glEnd(); + glColor4f(m_lineColor[0],m_lineColor[1],m_lineColor[2],m_lineAlpha); + glBegin(GL_LINE_STRIP); + glVertex3f(pointLeadLeft.x(),pointLeadLeft.y(),pointLeadLeft.z()); + glVertex3f(pointLeadRight.x(),pointLeadRight.y(),pointLeadRight.z()); + glVertex3f(pointTrailRight.x(),pointTrailRight.y(),pointTrailRight.z()); + glVertex3f(pointTrailLeft.x(),pointTrailLeft.y(),pointTrailLeft.z()); + glVertex3f(pointLeadLeft.x(),pointLeadLeft.y(),pointLeadLeft.z()); + glEnd(); + cntrl_pt.setX(m_cntrl_pt.m_x[i]); + cntrl_pt.setY(m_cntrl_pt.m_y[i]); + cntrl_pt.setZ(m_cntrl_pt.m_z[i]); + cntrl_pt = Math::local2Global(cntrl_pt,m_origin+m_shift,m_rotationMatrix); + cntrl_pos[0].append(cntrl_pt[0]); + cntrl_pos[1].append(cntrl_pt[1]); + cntrl_pos[2].append(cntrl_pt[2]); + } + + for (int i=0; i < m_cntrl_pt.m_x.length()-1; i++) { + pointTrailRight = this->getPointTrailAt(i+1,0); + pointFlapRight = this->getPointFlapAt(i+1,0); + pointFlapLeft = this->getPointFlapAt(i,1); + pointTrailLeft = this->getPointTrailAt(i,1); + glColor4f(m_flapLineColor[0],m_flapLineColor[1],m_flapLineColor[2],m_flapLineAlpha); + glBegin(GL_LINE_STRIP); + glVertex3f(pointTrailLeft.x(),pointTrailLeft.y(),pointTrailLeft.z()); + glVertex3f(pointTrailRight.x(),pointTrailRight.y(),pointTrailRight.z()); + glVertex3f(pointFlapRight.x(),pointFlapRight.y(),pointFlapRight.z()); + glVertex3f(pointFlapLeft.x(),pointFlapLeft.y(),pointFlapLeft.z()); + glVertex3f(pointTrailLeft.x(),pointTrailLeft.y(),pointTrailLeft.z()); + glEnd(); + glColor4f(m_flapFaceColor[0],m_flapFaceColor[1],m_flapFaceColor[2],m_flapFaceAlpha); + glBegin(GL_QUADS); + glVertex3f(pointTrailLeft.x(),pointTrailLeft.y(),pointTrailLeft.z()); + glVertex3f(pointTrailRight.x(),pointTrailRight.y(),pointTrailRight.z()); + glVertex3f(pointFlapRight.x(),pointFlapRight.y(),pointFlapRight.z()); + glVertex3f(pointFlapLeft.x(),pointFlapLeft.y(),pointFlapLeft.z()); + glEnd(); + } + + this->wind.plot(20,cntrl_pos,m_velocity); + +} + + +void Fuselage::setFuselage(StructWithFieldnames fuselage) { + QVector fieldNames = fuselage.getSubStructNames(); + StructWithFieldnames geometry = fuselage.getSubStruct("geometry"); + QVector cntrl_pos = geometry.getSubData("cntrl_pos"); + QVector border_pos = geometry.getSubData("border_pos"); + m_width = geometry.getSubData("width"); + StructWithFieldnames aero = fuselage.getSubStruct("aero"); + QVector c_XYZ_b_i = aero.getSubData("C_XYZ_b_i"); + QVector R_Ab_i = aero.getSubData("R_Ab_i"); + + m_cntrl_pt = Math::reshape2Matrix(cntrl_pos); + m_border_pt = Math::reshape2Matrix(border_pos); + m_c_XYZ_b_i = Math::reshape2Matrix(c_XYZ_b_i); + m_R_Ab_i = Math::reshape2Matrix(R_Ab_i); + + m_wind.setWind(fuselage); +} + +void Fuselage::setFuselage(StructWithFieldnames fuselage, QString name) { + this->setFuselage(fuselage); + this->setName(name); +} + +QVector> Math::reshape2Matrix(QVector vector) { + QVector> array2D(3); + int arrayLength = vector.length(); + int subArrayLength = arrayLength/3; + for (int i=0; i<3; i++) { + QVector subArray(subArrayLength); + int k = i; + for (int j=0; j Math::quaternion2RotationMatrix(QQuaternion rotationQuaternion) { + QGenericMatrix<3,3,double> rotationMatrix; + float *dataMat = rotationQuaternion.toRotationMatrix().data(); + int k = 0; + for (int i=0; i<3; i++) { + for (int j=0; j<3; j++) { + rotationMatrix(j,i) = *(dataMat + k++); + } + } + return rotationMatrix; +} + +void Fuselage::plot(QVector3D shift, QQuaternion rotation) { + m_shift = shift; + m_rotationMatrix = Math::quaternion2RotationMatrix(rotation); + this->plot(); +} + +void Fuselage::plot() { + + QVector3D cntrl_pt; + QVector3D border_pt; + QVector3D next_border_pt; + QVector3D R_Ab_i_end; + + QVector> border_pos = m_border_pt; + + QVector> cycle1; + QVector> cycle2; + + int length; + if (m_border_pt.isEmpty()) { + length = 0; + } + else { + length = m_border_pt[0].length(); + } + for (int i=0; i < length-1; i++) { + border_pt.setX(m_border_pt[0][i]); + border_pt.setY(m_border_pt[1][i]); + border_pt.setZ(m_border_pt[2][i]); + border_pt = Math::local2Global(border_pt,m_shift,m_rotationMatrix); + + border_pos[0][i] = border_pt.x(); + border_pos[1][i] = border_pt.y(); + border_pos[2][i] = border_pt.z(); + + next_border_pt.setX(m_border_pt[0][i+1]); + next_border_pt.setY(m_border_pt[1][i+1]); + next_border_pt.setZ(m_border_pt[2][i+1]); + next_border_pt = Math::local2Global(next_border_pt,m_shift,m_rotationMatrix); + + cntrl_pt.setX(m_cntrl_pt[0][i]); + cntrl_pt.setY(m_cntrl_pt[1][i]); + cntrl_pt.setZ(m_cntrl_pt[2][i]); + cntrl_pt = Math::local2Global(cntrl_pt,m_shift,m_rotationMatrix); + + // scaling = (m_width[i]+m_width[i+1])/2; + double scaling = 5; + R_Ab_i_end.setX(m_cntrl_pt[0][i]+scaling*m_c_XYZ_b_i[0][i]*(m_width[i]+m_width[i+1])/2); + R_Ab_i_end.setY(m_cntrl_pt[1][i]+scaling*m_c_XYZ_b_i[1][i]*(m_width[i]+m_width[i+1])/2); + R_Ab_i_end.setZ(m_cntrl_pt[2][i]+scaling*m_c_XYZ_b_i[2][i]*(m_width[i]+m_width[i+1])/2); + R_Ab_i_end = Math::local2Global(R_Ab_i_end,m_shift,m_rotationMatrix); + + // draw fuselage geometry: center line + glColor4f(m_lineColor[0],m_lineColor[1],m_lineColor[2],m_lineAlpha); + glBegin(GL_LINE_STRIP); + glVertex3f(border_pt.x(),border_pt.y(),border_pt.z()); + glVertex3f(cntrl_pt.x(),cntrl_pt.y(),cntrl_pt.z()); + glVertex3f(next_border_pt.x(),next_border_pt.y(),next_border_pt.z()); + glEnd(); + + // draw aerodynamic forces + glColor4f(m_forceColor[0],m_forceColor[1],m_forceColor[2],m_forceAlpha); + glBegin(GL_LINE_STRIP); + glVertex3f(cntrl_pt.x(),cntrl_pt.y(),cntrl_pt.z()); + glVertex3f(R_Ab_i_end.x(),R_Ab_i_end.y(),R_Ab_i_end.z()); + glEnd(); + + // draw fuselage geometry: cycles + glColor4f(m_lineColor[0],m_lineColor[1],m_lineColor[2],m_lineAlpha); + cycle1 = drawHollowCircle(border_pt,m_rotationMatrix,m_width[i]/2); + cycle2 = drawHollowCircle(next_border_pt,m_rotationMatrix,m_width[i+1]/2); + + // draw fuselage geometry: conic faces + glColor4f(m_faceColor[0],m_faceColor[1],m_faceColor[2],m_faceAlpha); + drawConicFace(cycle1, cycle2); + + } + + m_wind.plot(20,border_pos,m_velocity); + +} + + +QVector> Fuselage::drawHollowCircle(QVector3D position, QGenericMatrix<3,3,double> orientation, double radius){ + int i; + + double PI = 3.14159; + + //GLfloat radius = 0.8f; //radius + double twicePi = 2.0f * PI; + + QVector> positionMat; + QVector positionSub (3); + + QGenericMatrix<1,3,double> circlePt; + + glBegin(GL_LINE_LOOP); + for(i = 0; i <= m_lineAmount;i++) { + circlePt(2,0) = (radius * cos(i * twicePi / m_lineAmount)); + circlePt(1,0) = (radius * sin(i * twicePi / m_lineAmount)); + circlePt(0,0) = 0; + circlePt = orientation * circlePt; + circlePt(0,0) += position.x(); + circlePt(1,0) += position.y(); + circlePt(2,0) += position.z(); + glVertex3f( circlePt(0,0), circlePt(1,0), circlePt(2,0) ); + positionSub[0] = circlePt(0,0); + positionSub[1] = circlePt(1,0); + positionSub[2] = circlePt(2,0); + positionMat.append(positionSub); + } + glEnd(); + return positionMat; +} + +void Fuselage::drawConicFace(QVector> positionMat1, QVector> positionMat2){ + int i; + + QGenericMatrix<1,3,double> circlePt; + + for(i = 0; i <= m_lineAmount-1;i++) { + glBegin(GL_QUADS); + glVertex3f( positionMat1[i][0], positionMat1[i][1], positionMat1[i][2] ); + glVertex3f( positionMat1[i+1][0], positionMat1[i+1][1], positionMat1[i+1][2] ); + glVertex3f( positionMat2[i+1][0], positionMat2[i+1][1], positionMat2[i+1][2] ); + glVertex3f( positionMat2[i][0], positionMat2[i][1], positionMat2[i][2] ); + glEnd(); + } +} + + +QVector3D Math::local2Global(QVector3D vector, QVector3D shift, QGenericMatrix<3,3,double> rotationMatrix ) { + + vector = vector + shift; + + QGenericMatrix<1,3,double> vectorMat; + + vectorMat(0,0) = vector.x(); + vectorMat(1,0) = vector.y(); + vectorMat(2,0) = vector.z(); + + vectorMat = rotationMatrix * vectorMat; + + vector.setX(vectorMat(0,0)); + vector.setY(vectorMat(1,0)); + vector.setZ(vectorMat(2,0)); + + return vector; +} + + +void Aircraft::setPart(Wing wing_){ + bool replaced = false; + for (int i=0; i fieldnames = aircraft.getSubStructNames(); + for (int i=0; i wing_identifier = m_wing_array[0].m_name_identifier; + for (int j=0; j fuselage_identifier = m_fuselage_array[0].m_name_identifier; + for (int j=0; j rigidBody_identifier = m_rigidBody.m_name_identifier; + for (int j=0; j pos_ref = config.getSubData("xyz_ref_c"); + m_posRef[0] = -pos_ref[0]; + m_posRef[1] = -pos_ref[1]; + m_posRef[2] = -pos_ref[2]; + double velocity = m_rigidBody.getVelocity(); + for (int i = 0; i +#include +#include +#include +#include +#include + +#include "structWithFieldnames.h" + + + +class Property { +public: + Property(QVector name_identifier) { + for (int i=0; i m_name_identifier; + QString getName(); + void setName(QString name); + bool isProperty(QString string_); +private: + QString m_name; +}; + +class Wind { + friend class Math; + friend class Aircraft; +public: + void setScaling(double velocity); + void setWind(StructWithFieldnames wind); + void plot(double downShift, QVector> pos, double velocity); +private: + QVector> m_V_Wb_i; + double m_scaling = 1; + double m_downShift = 20; +}; + +class Math { +public: + static QVector3D local2Global(QVector3D vector, QVector3D shift, QGenericMatrix<3,3,double> rotationMatrix ); + static QVector> reshape2Matrix(QVector vector); + static QGenericMatrix<3,3,double> quaternion2RotationMatrix(QQuaternion rotationQuaternion); +}; + +class Vortex : public Property { + friend class Wing; +public: + Vortex() : Property({"vortex"}) {}; + void setVortex(StructWithFieldnames vortex); +private: + QVector m_x; + QVector m_y; + QVector m_z; + QVector m_c; +}; + +class Cntrl_pt : public Property { + friend class Wing; +public: + Cntrl_pt() : Property({"cntrl_pt"}) {}; + void setCntrlPt(StructWithFieldnames cntrl_pt); +private: + QVector m_x; + QVector m_y; + QVector m_z; + QVector m_local_incidence; +}; + +class CoeffLocal : public Property { + friend class Wing; +public: + CoeffLocal() : Property({"coeff_loc","coeffLocal"}) {}; + void setCoeffLocal(StructWithFieldnames coeffLocal); +private: + QVector> m_c_XYZ_b; + QVector> m_c_lmn_b; + QVector m_c_m_airfoil; +}; + + +class Wing : public Property { +public: + Wing() : Property({"wing","htp","vtp","plane"}) { + m_rotationMatrix.setToIdentity(); + m_shift={0,0,0}; + }; + void setWing(QString name, Vortex vortex, Cntrl_pt cntrl_pt); + void setWing(StructWithFieldnames wing); + void setWing(StructWithFieldnames wing, QString name); + void setOrigin(QVector origin); + QVector3D getPointLeadAt(int i); + QVector3D getPointTrailAt(int i, int side); + QVector3D getPointFlapAt(int i, int side); + QVector3D getCenterOfPressureAt(int i); + void plot(); + void plot(QVector3D shift, QQuaternion rotation); + void setVelocity(double velocity); +private: + Vortex m_vortex; + Cntrl_pt m_cntrl_pt; + CoeffLocal m_coeffLocal; + QVector m_cL_act2; + QVector3D m_origin; + QGenericMatrix<3,3,double> m_rotationMatrix; + QVector3D m_shift; + QVector m_flap_depth; + QVector m_flap_deflection; + Wind wind; + double m_velocity; + QVector m_trailing_edge_sep_pt; + float m_lineColor[3] = {0.1,0.1,0.1}; + float m_lineAlpha = 1.0; + float m_faceColor[3] = {0.99,0.99,0.99}; + float m_faceAlpha = 0.5; + float m_forceColor[3] = {76.0/255.0,212.0/255.,38.0/255.0}; + float m_forceAlpha = 1.0; + float m_flapLineColor[3] = {0.1,0.1,0.1}; + float m_flapLineAlpha = 1.0; + float m_flapFaceColor[3] = {0.65,0.65,0.65}; + float m_flapFaceAlpha = 0.5; + float m_stallColor[3] = {247.0/255.0,132.0/255.0,17.0/255.0}; + float m_stallAlpha = 0.7; + float m_ladForceColor[3] = {0.7,0.45,0.9}; + float m_ladForceAlpha = 1; +}; + + +class Fuselage : public Property { + friend class Math; +public: + Fuselage() : Property({"fuselage"}) {}; + void setFuselage(StructWithFieldnames fuselage); + void setFuselage(StructWithFieldnames fuselage, QString name); + void plot(); + void plot(QVector3D shift, QQuaternion rotation); + QVector> drawHollowCircle(QVector3D position, QGenericMatrix<3,3,double> orientation, double radius); + void drawConicFace(QVector> positionMat1, QVector> positionMat2); + void setVelocity(double velocity); +private: + QVector> m_cntrl_pt; + QVector> m_border_pt; + QVector> m_c_XYZ_b_i; + QVector> m_R_Ab_i; + QVector m_width; + QVector3D m_shift; + QGenericMatrix<3,3,double> m_rotationMatrix; + Wind m_wind; + double m_velocity; + int m_lineAmount = 100; + float m_lineColor[3] = {0.1,0.1,0.1}; + float m_lineAlpha = 1.0; + float m_faceColor[3] = {0.99,0.99,0.99}; + float m_faceAlpha = 0.5; + float m_forceColor[3] = {76.0/255.0,212.0/255.,38.0/255.0}; + float m_forceAlpha = 1.0; +}; + + +class RigidBody : public Property { +public: + RigidBody() : Property({"body"}) {}; + void setRigidBody(StructWithFieldnames name); + QQuaternion m_q_bg; + QVector3D m_V_Kb; + double getVelocity(); +}; + + +class Aircraft{ +public: + Aircraft() { + m_wing_array.append(Wing()); + m_fuselage_array.append(Fuselage()); + }; + void setPart(Wing wing_); + void setPart(Fuselage fuselage_); + void setAircraft(StructWithFieldnames aircraft); + void plot(); + void plot(QQuaternion rotation); + QVector3D m_posRef; +private: + QVector m_wing_array; + QVector m_fuselage_array; + RigidBody m_rigidBody; +}; + + + + + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ed4064a --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,14 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.setTitle("FlexiFlightVis - Aeroelastic Flight Dynamics Visualization"); + w.resize(1280, 960); + w.show(); + + return a.exec(); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp new file mode 100644 index 0000000..f511f17 --- /dev/null +++ b/src/mainwindow.cpp @@ -0,0 +1,184 @@ +#include "mainwindow.h" + +MainWindow::MainWindow(QWidget *parent) +{ + setSurfaceType(QWindow::OpenGLSurface); + + QSurfaceFormat format; + format.setProfile(QSurfaceFormat::CompatibilityProfile); + format.setVersion(2,1); + setFormat(format); + + context = new QOpenGLContext; + context->setFormat(format); + context->create(); + context->makeCurrent(this); + + openGLFunctions = context->functions(); + + QTimer *timer = new QTimer(this); + + connect(timer, SIGNAL(timeout()), this, SLOT(UpdateAnimation())); + + timer->start(); + +} + +MainWindow::~MainWindow() +{ +} + +void MainWindow::initializeGL() +{ + glEnable(GL_DEPTH_TEST); + resizeGL(this->width(), this->height()); + frameTime.start(); +} + +void MainWindow::resizeGL(int w, int h) +{ + // Set viewport + glViewport(0,0,w,h); + qreal aspectratio = qreal(w)/qreal(h); + + // Initialize projection matrix + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + gluPerspective(75, aspectratio, 0.1, 400000000); + + // Initialize model view matrix + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + +} + +void MainWindow::paintGL() +{ + + glClearColor(182.0/255.0,201.0/255.0,214.0/255.0, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glEnable( GL_BLEND ); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendEquation(GL_FUNC_ADD); + + // Reset model view matrix + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // 3D transformation + glTranslatef(0.0, 0.0, -50.0*zoomScale); + QCursor cursor; + if (mouseHold) { + MainWindow::mouseHoldEvent(&cursor); + } + // rotation axis and angle are needed for glRotateF function + QVector3D rotationAxis; + float rotationAngle; + rotationQuatTotal.getAxisAndAngle(&rotationAxis,&rotationAngle); + glRotatef( rotationAngle, rotationAxis[0], rotationAxis[1], rotationAxis[2] ); + + + glLineWidth(2.0f); + glPointSize(4.0f); + + m_client.m_aircraft.plot(); + + + // draw axis frame + QVector3D point0 = {0,0,0}; + QVector3D pointX = {1,0,0}; + QVector3D pointY = {0,1,0}; + QVector3D pointZ = {0,0,1}; + + glBegin(GL_LINE_STRIP); + glColor3f(1,0,0); + glVertex3f(point0.x(),point0.y(),point0.z()); + glVertex3f(pointX.x(),pointX.y(),pointX.z()); + glEnd(); + glBegin(GL_LINE_STRIP); + glColor3f(0,1,0); + glVertex3f(point0.x(),point0.y(),point0.z()); + glVertex3f(pointY.x(),pointY.y(),pointY.z()); + glEnd(); + glBegin(GL_LINE_STRIP); + glColor3f(0,0,1); + glVertex3f(point0.x(),point0.y(),point0.z()); + glVertex3f(pointZ.x(),pointZ.y(),pointZ.z()); + glEnd(); + + + ++frameCount; + + if (frameTime.elapsed() >= 1000) + { + fps = frameCount / ((double)frameTime.elapsed()/1000.0); + } + + // qInfo() << "FPS: " + QString::number(fps); + + glFlush(); +} + +void MainWindow::resizeEvent(QResizeEvent *event) +{ + resizeGL(this->width(), this->height()); + this->update(); +} + +void MainWindow::paintEvent(QPaintEvent *event) +{ + paintGL(); +} + +void MainWindow::UpdateAnimation() +{ + this->update(); +} + +void MainWindow::mousePressEvent(QMouseEvent *event) +{ + mouseHold = true; + mouseHoldFirstCall = true; +} + +void MainWindow::mouseReleaseEvent(QMouseEvent *event) +{ + mouseHold = false; +} + +void MainWindow::mouseHoldEvent(QCursor *cursor) +{ + // reset mouse position when the mouse is pressed + if (mouseHoldFirstCall) { + mousePos = cursor->pos(); + lastMousePos = mousePos; + mouseHoldFirstCall = false; + } + // incrementation + else { + lastMousePos = mousePos; + mousePos = cursor->pos(); + rotationQuatLast = rotationQuatTotal; + } + + // rotation axis and angle update + QPoint diff = mousePos - lastMousePos; + qreal distance = qSqrt(pow(diff.x(),2)+pow(diff.y(),2)); + float rotationAngleUpdate = distance * 0.5; + QVector3D rotationAxisCurrent = QVector3D(diff.y(), diff.x(), 0.0).normalized(); + // rotation relative to last rotation + QQuaternion rotationQuatUpdate = QQuaternion::fromAxisAndAngle(rotationAxisCurrent,rotationAngleUpdate); + + // combined rotation (by quaternion multiplication) + rotationQuatTotal = rotationQuatUpdate * rotationQuatLast; +} + +void MainWindow::wheelEvent(QWheelEvent *event) +{ + QPoint numDegrees = event->angleDelta(); + if (numDegrees.y()<0) zoomScale = zoomScale*1.1; + if (numDegrees.y()>0) zoomScale = zoomScale/1.1; +} diff --git a/src/mainwindow.h b/src/mainwindow.h new file mode 100644 index 0000000..cdb610e --- /dev/null +++ b/src/mainwindow.h @@ -0,0 +1,62 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include "myudp.h" +#include + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QOpenGLWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +protected: + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseHoldEvent(QCursor *cursor); + void wheelEvent(QWheelEvent *event) override; + virtual void initializeGL() override; + virtual void resizeGL(int w, int h) override; + virtual void paintGL() override; + void resizeEvent(QResizeEvent *event) override; + void paintEvent(QPaintEvent *event) override; + QQuaternion quatMultiply(QQuaternion q_left, QQuaternion q_right); + + +public slots: + void UpdateAnimation(); + +private: + // Ui::MainWindow *ui; + QOpenGLContext *context; + QOpenGLFunctions *openGLFunctions; + + MyUDP m_client; + + bool mouseHold = false; + bool mouseHoldFirstCall; + QPoint mousePos; + QPoint lastMousePos; + QQuaternion rotationQuatTotal = QQuaternion::fromEulerAngles({110,0,220}); + QQuaternion rotationQuatLast = rotationQuatTotal; + float zoomScale = 1; + + int frameCount = 0; + QTime frameTime; + double fps; + +}; +#endif // MAINWINDOW_H diff --git a/src/myudp.cpp b/src/myudp.cpp new file mode 100644 index 0000000..671d3f3 --- /dev/null +++ b/src/myudp.cpp @@ -0,0 +1,118 @@ +#include "myudp.h" + +MyUDP::MyUDP(QObject *parent) : QObject(parent), + // m_senderHostAddress(QHostAddress::LocalHost), + m_senderHostAddress(QHostAddress::Any), + // m_senderHostAddress("192.168.11.13"), + m_port(4321) +{ + // create a QUDP socket + socket = new QUdpSocket(this); + + // The most common way to use QUdpSocket class is to bind it to an address and port using bind() + // bool QAbstractSocket::bind(const QHostAddress & address, + // quint16 port = 0, BindMode mode = DefaultForPlatform) + socket->bind(m_senderHostAddress, m_port); + + socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption,1250000); + // Connect the signal of the QUdpSocket with the slot of this MyUDP class + // If a package is available for the specified Host and Port, call the function readyRead of this class + connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead())); +} + +void MyUDP::getPoints(QVector &x, QVector &y, QVector &z) { + x = m_x; + y = m_y; + z = m_z; +} + +int16_t MyUDP::getNumberOfDataPoints() { + return m_numberOfDataPoints; +} + +void MyUDP::setHostAddressAndPort(QString hostAddress, quint16 port) +{ + m_senderHostAddress = QHostAddress(hostAddress); + m_port = port; + socket->close(); + socket->bind(m_senderHostAddress, m_port); + + connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead())); +} + +QString MyUDP::getHostAddress() +{ + return m_senderHostAddress.toString(); +} + +quint16 MyUDP::getHostPort() +{ + return m_port; +} + +int i = 0; + +/***************************************************************************** + * This function is called, when a package is available + *****************************************************************************/ +void MyUDP::readyRead() { + // Create a QByteArray as a buffer + QByteArray buffer; + buffer.resize(socket->pendingDatagramSize()); + + // Create variables for the Host address and the Port of the sender + QHostAddress sender; + quint16 senderPort; + + // qint64 QUdpSocket::readDatagram(char * data, qint64 maxSize, + // QHostAddress * address = 0, quint16 * port = 0) + // Receives a datagram no larger than maxSize bytes and stores it in data. + // The sender's host address and port is stored in *address and *port + // (unless the pointers are 0). + qint64 dataGramSize = socket->readDatagram(buffer.data(), buffer.size(), + &sender, &senderPort); + + // Check if receiving of data was successful + if (dataGramSize != -1) { + // Create QDataStream to read serialized of binary data stored in buffer + QDataStream stream(buffer); + // Set the QDataStream to write data in little endian, because "Simulink UDP Receive Binary" block receives only + // little endian + stream.setByteOrder(QDataStream::LittleEndian); + + stream.setFloatingPointPrecision(QDataStream::SinglePrecision); + + /* + StructWithFieldnames test; + test.setFromStream(stream); + + QVector subStructNames = test.getSubStructNames(); + StructWithFieldnames sub = test.getSubStruct(subStructNames[0]); + */ + + + + StructWithFieldnames aircraftStruct; + aircraftStruct.setFromStream(stream); + + m_aircraft = Aircraft(); + m_aircraft.setAircraft(aircraftStruct); + + + + if (i>30) { + i = 0; + } + else { + i++; + } + + qInfo() << i; + // qInfo() << subStructNames[0]; + + } + else { + std::cout << "An ERROR occures while receiving datagram!" << std::endl; + } + emit receivedValues(); +} diff --git a/src/myudp.h b/src/myudp.h new file mode 100644 index 0000000..d763d9f --- /dev/null +++ b/src/myudp.h @@ -0,0 +1,39 @@ +#ifndef MYUDP_H +#define MYUDP_H + +#include +#include +#include +#include +#include +#include "aircraft.h" + + +class MyUDP : public QObject { + Q_OBJECT +public: + explicit MyUDP(QObject *parent = 0); + void getPoints(QVector &m_x, QVector &m_y, QVector &m_z); + int16_t getNumberOfDataPoints(); + void setHostAddressAndPort(QString hostAddress, quint16 port); + QString getHostAddress(); + quint16 getHostPort(); + Aircraft m_aircraft; +signals: + void receivedValues(); + void numberOfDataPointsChanged(); + +public slots: + void readyRead(); + + +private: + QUdpSocket *socket; + QVector m_x, m_y, m_z; + int16_t m_numberOfDataPoints = 0; + QHostAddress m_senderHostAddress; + quint16 m_port; + QDataStream m_stream; +}; + +#endif // MYUDP_H diff --git a/src/structWithFieldnames.cpp b/src/structWithFieldnames.cpp new file mode 100644 index 0000000..5a86304 --- /dev/null +++ b/src/structWithFieldnames.cpp @@ -0,0 +1,304 @@ +#include "structWithFieldnames.h" + + +QVector StructWithFieldnames::getSubStructNames() { + QVector subStructNames; + for (int i=0; i StructWithFieldnames::getSubDataNames() { + QVector subDataNames; + for (int i=0; i StructWithFieldnames::getSubNames() { + QVector subNames; + for (int i=0; i StructWithFieldnames::getSubData(QString structName) { + QVector subData; + int varIndex = -1; + for (int i=0; i=0) { + QVector assignmentIndex = m_assignmentIndex[varIndex]; + for (int i=0; i dataIndex; + int16_t numberOfDataPoints = 0; + typeudp numberOfDataPointsD = 0; + QString field_name; + QVector field_val; + QVector assignmentIndex; + QVector init_asgnmt_idx = {-1}; + typeudp val = 0; + bool new_struct = false; + bool read_name = false; + int read_name_idx; + bool read_data = false; + int field_name_length; + + // the very first element of the stream is the length + stream >> numberOfDataPointsD; + numberOfDataPoints = (int) round(numberOfDataPointsD); + + // decode the stream from the first element to the last + for (int i=0; i> val; + // the first element is the identifier + if (i==0) { + identifier = val; + currentIdentifier = identifier; + } + // did we find an identifier? + if (round(val)>=identifier && round(val)<=currentIdentifier+1) { + new_struct = true; + read_data = false; + dataIndex.clear(); + // did the level increase? + if (round(val)==currentIdentifier+1) { + currentIdentifier++; + // ... then the last element was a struct (exept it was the first one) + if (m_level.size()>0) { + m_isStruct.append(true); + } + } + // did the level decrease? + else if (round(val)=0; j--) { + if (m_level[j]==m_level.last()-l) { + assignmentIndex.prepend(j); + } + if (m_level[j]0) { + m_isStruct.append(false); + } + } + } + // no identifier was detected, so we read the data (there are 3 states) + else { + if (read_data) { + // 3rd state: read data of variable + // set data to the member variable + m_data.append((double) val); + // overwrite first element (from initialization) or append vector + if (m_assignmentIndex[m_varIndex.last()].first()==init_asgnmt_idx.first()) { + m_assignmentIndex[m_varIndex.last()].first() = m_data.length()-1; + } + else { + m_assignmentIndex[m_varIndex.last()].append(m_data.length()-1); + } + // the very last variable can not be a struct + if (i==numberOfDataPoints-1) { + m_isStruct.append(false); + // depending on how many levels it has decreased, we now can assign the indices of all structs that are now entirely read + // (copy paste) + int num_finished_structs = m_level.last()-1; + for (int l=0; l=0; j--) { + if (m_level[j]==m_level.last()-l) { + assignmentIndex.prepend(j); + } + if (m_level[j]=0) { + index = getIndexFromAssignmentIndex(m_assignmentIndex[varIndex][i]); + nextLayer.m_varName.append(m_varName[index]); + nextLayer.m_level.append(m_level[index]); + nextLayer.m_isStruct.append(m_isStruct[index]); + nextLayer.m_varIndex.append(m_varIndex[index]); + nextLayer.m_assignmentIndex.append(m_assignmentIndex[index]); + } + } + } + return nextLayer; +} + +int StructWithFieldnames::getIndexFromAssignmentIndex(int assigmentIndex) { + int index = -1; + for (int i=0; i +#include +#include +#include +#include + +typedef float typeudp; + + +class StructWithFieldnames { +public: + void setFromStream(QDataStream & stream); + QVector getSubStructNames(); + QVector getSubDataNames(); + QVector getSubNames(); + StructWithFieldnames getSubStruct(QString structName); + QVector getSubData(QString dataName); + + // to do + //StructWithFieldnames getSubStruct(QVector strucNames); + // QVector getSubData(QVector dataNames); + +private: + // variables + QVector m_varName; + QVector m_varIndex; + QVector m_level; + QVector m_isStruct; + QVector> m_assignmentIndex; + QVector m_data; + + // functions + int getNumberOfFurtherLevels(); + int getNumberOfFurtherLevels(QVector structNames); + + int getIndexFromAssignmentIndex(int assigmentIndex); + StructWithFieldnames getNextLayerInfo(int varIndex); + StructWithFieldnames getNextLayerInfoRecursive(int varIndex); + void sortIndices(); + + /* + // to do + QVector getAllNestedStructIndices(); + QVector getAllNestedStructIndices(QString structName); + QVector getAllNestedStructIndices(QVector structNames); + + QVector getAllNestedStructNames(); + QVector getAllNestedStructNames(QString structNames); + QVector getAllNestedStructNames(QVector structNames); + + QVector getAllNamesAtLevel(int level); // not important + QVector getAllStructNamesAtLevel(int level); // not important + QVector getAllDataNamesAtLevel(int level); // not important + */ + +}; + +