Compare commits

..

No commits in common. "3c9c8b8c6a2b2e7430ff09efdc2cc0c1996b16ca" and "16ffbef2ce46087254de09c19b9f63d6246d3806" have entirely different histories.

98 changed files with 1098 additions and 13099 deletions

View File

@ -1,86 +0,0 @@
# This starter workflow is for a CMake project running on multiple platforms. There is a different
# starter workflow if you just want a single platform.
# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml
name: CMake on multiple platforms
on:
push:
branches: [ "development" ]
pull_request:
branches: [ "development" ]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
# Set fail-fast to false to ensure that feedback is delivered for all matrix combinations.
# Consider changing this to true when your workflow is stable.
fail-fast: false
# Set up a matrix to run the following 3 configurations:
# 1. <Windows, Release, latest MSVC compiler toolchain on the default runner image, default generator>
# 2. <Linux, Release, latest GCC compiler toolchain on the default runner image, default generator>
# 3. <Linux, Release, latest Clang compiler toolchain on the default runner image, default generator>
#
# To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list.
matrix:
os: [ubuntu-latest, windows-latest]
build_type: [Release]
c_compiler: [gcc-13, clang, cl]
include:
- os: windows-latest
c_compiler: cl
cpp_compiler: cl
- os: ubuntu-latest
c_compiler: gcc-13
cpp_compiler: g++-13
#- os: ubuntu-latest
#c_compiler: clang
#cpp_compiler: clang++
exclude:
- os: windows-latest
c_compiler: gcc-13
- os: windows-latest
c_compiler: clang
- os: ubuntu-latest
c_compiler: cl
# Clang is broken on ubuntu-latest. C++20 brings in inconsistent libraries
- os: ubuntu-latest
c_compiler: clang
steps:
- uses: actions/checkout@v3
- name: Install Qt
uses: jurplel/install-qt-action@v3
with:
version: 6.6.2
target: desktop
- name: Set reusable strings
# Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file.
id: strings
shell: bash
run: |
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: >
cmake -B ${{ steps.strings.outputs.build-output-dir }}
-DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }}
-DCMAKE_C_COMPILER=${{ matrix.c_compiler }}
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
-S ${{ github.workspace }}
- name: Build
# Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }}
- name: Test
working-directory: ${{ steps.strings.outputs.build-output-dir }}
# Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest --build-config ${{ matrix.build_type }} -R 'qtrocket_*'

3
.gitignore vendored
View File

@ -38,8 +38,7 @@ build/
docs/doxygen/*
# IDE
*.swp
.vscode/qtrocket.pro.user
qtrocket.pro.user
.qmake.stash
CMakeLists.txt.user

View File

@ -1,17 +0,0 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"compilerPath": "/usr/bin/g++",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "linux-gcc-x64",
"configurationProvider": "ms-vscode.cmake-tools"
}
],
"version": 4
}

View File

@ -1,3 +0,0 @@
{
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools"
}

View File

@ -1,175 +0,0 @@
cmake_minimum_required(VERSION 3.16)
project(qtrocket VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
enable_testing()
include(FetchContent)
# Google Test framework
FetchContent_Declare(googletest
GIT_REPOSITORY https://github.com/google/googletest
GIT_TAG v1.13.0)
if(WIN32)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
endif()
FetchContent_MakeAvailable(googletest)
# fmtlib dependency
#FetchContent_Declare(fmt
# GIT_REPOSITORY https://github.com/fmtlib/fmt
# GIT_TAG 9.1.0)
#FetchContent_MakeAvailable(fmt)
# jsoncpp dependency
FetchContent_Declare(jsoncpp
GIT_REPOSITORY https://github.com/open-source-parsers/jsoncpp
GIT_TAG 1.9.5)
set(JSONCPP_WITH_TESTS OFF)
set(JSONCPP_WITH_EXAMPLE OFF)
set(JSONCPP_WITH_PKGCONFIG_SUPPORT OFF)
set(JSONCPP_BUILD_SHARED_LIBS OFF)
set(JSONCPP_BUILD_OBJECT_LIBS OFF)
set(JSONCPP_BUILD_STATIC_LIBS ON)
FetchContent_MakeAvailable(jsoncpp)
# curl dependency
FetchContent_Declare(CURL
GIT_REPOSITORY https://github.com/curl/curl
GIT_TAG curl-8_0_1)
set(BUILD_CURL_EXE OFF)
set(BUILD_SHARED_LIBS OFF)
set(HTTP_ONLY ON)
set(SSL_ENABLED ON)
if(WIN32)
set(CURL_USE_SCHANNEL ON)
endif()
FetchContent_MakeAvailable(CURL)
# eigen dependency
FetchContent_Declare(Eigen
GIT_REPOSITORY https://gitlab.com/libeigen/eigen
GIT_TAG 3.4.0)
FetchContent_MakeAvailable(Eigen)
# boost dependency
FetchContent_Declare(Boost
GIT_REPOSITORY https://github.com/boostorg/boost
GIT_TAG boost-1.84.0)
set(BOOST_INCLUDE_LIBRARIES property_tree)
FetchContent_MakeAvailable(Boost)
# Add qtrocket subdirectories. These are components that will be linked in
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
if(WIN32)
set(CMAKE_PREFIX_PATH $ENV{QTDIR})
# include_directories("C:\\boost\\boost_1_82_0\\")
# find_package(Qt6Core REQUIRED)
# find_package(Qt6Widgets REQUIRED)
endif()
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools PrintSupport)
find_package(Qt6 REQUIRED COMPONENTS Widgets LinguistTools PrintSupport)
#find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools PrintSupport)
#find_package(CURL)
#find_package(fmt)
set(TS_FILES qtrocket_en_US.ts)
include_directories(${PROJECT_SOURCE_DIR})
set(PROJECT_SOURCES
main.cpp
QtRocket.cpp
QtRocket.h
gui/AboutWindow.cpp
gui/AboutWindow.h
gui/AboutWindow.ui
gui/AnalysisWindow.cpp
gui/AnalysisWindow.h
gui/AnalysisWindow.ui
gui/MainWindow.cpp
gui/MainWindow.h
gui/MainWindow.ui
gui/RocketTreeView.cpp
gui/RocketTreeView.h
gui/SimOptionsWindow.cpp
gui/SimOptionsWindow.h
gui/SimOptionsWindow.ui
gui/ThrustCurveMotorSelector.cpp
gui/ThrustCurveMotorSelector.h
gui/ThrustCurveMotorSelector.ui
gui/qcustomplot.cpp
gui/qcustomplot.h
${TS_FILES}
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(qtrocket
qtrocket.qrc
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET qtrocket APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
else()
if(ANDROID)
add_library(qtrocket SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(qtrocket
${PROJECT_SOURCES}
)
endif()
endif()
add_subdirectory(utils)
add_subdirectory(sim)
add_subdirectory(model)
#target_link_libraries(qtrocket PRIVATE
# Qt6::Widgets
# Qt6::PrintSupport
# libcurl
# jsoncpp_static
# fmt::fmt-header-only
# eigen)
target_link_libraries(qtrocket PRIVATE
Qt6::Widgets
Qt6::PrintSupport
utils
sim
model)
set_target_properties(qtrocket PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
install(TARGETS qtrocket
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(qtrocket)
endif()

View File

@ -1,13 +0,0 @@
If you want to help out and are looking for something to do, thank you! QtRocket is still in the early stages of development (as of 2023-04-24), but there are some
targets of development already that would be nice to have.
Here are some items that QtRocket needs help with and would probably take me a lot more time than someone already familiar:
* Windows build. I am not nearly as familiar with Windows development as I am with Linux/BSD, so if you can help in getting QtRocket to build and run in Windows that would be great!
I think it just needs a build of libcurl, libjson-cpp, and fmtlib to link against, and the Windows build of QtCreator can take care of the rest.
Having the source code to these libraries in a third-party subdirectory of the project that can be built along with the project may be the cleanest approach.
That way correct builds can be made on each platform without needing to track pre-built library versions, since those would be built and (statically) linked automatically.
* MacOS build. Same as with Windows. I don't own a Mac to actually try this out on, but would love to support that platform.
* Linux and xBSD builds. Testing on multiple distributions and BSDs is welcome. Fixing or reporting issues with a particular distribution/BSD would be welcome.

View File

@ -1,3 +1,4 @@
/// \cond
// C headers
// C++ headers
@ -11,8 +12,10 @@
// qtrocket headers
#include "QtRocket.h"
#include "utils/Logger.h"
#include "gui/MainWindow.h"
#include "sim/ConstantAtmosphere.h"
#include "sim/ConstantGravityModel.h"
// Initialize static member data
QtRocket* QtRocket::instance = nullptr;
@ -23,8 +26,6 @@ bool QtRocket::initialized = false;
// The gui worker thread
void guiWorker(int argc, char* argv[], int& ret)
{
utils::Logger* logger = utils::Logger::getInstance();
logger->info("Starting QApplication");
QApplication a(argc, argv);
a.setWindowIcon(QIcon(":/qtrocket.png"));
@ -44,7 +45,6 @@ void guiWorker(int argc, char* argv[], int& ret)
// Go!
MainWindow w(QtRocket::getInstance());
logger->debug("Showing MainWindow");
w.show();
ret = a.exec();
@ -64,7 +64,6 @@ void QtRocket::init()
std::lock_guard<std::mutex> lck(mtx);
if(!initialized)
{
utils::Logger::getInstance()->debug("Instantiating new QtRocket");
instance = new QtRocket();
initialized = true;
}
@ -75,25 +74,16 @@ QtRocket::QtRocket()
logger = utils::Logger::getInstance();
running = false;
// Need to set some sane defaults for the Environment
// The default constructor for Environment will do that for us, so just use that
setEnvironment(std::make_shared<sim::Environment>());
atmosphere =
std::make_shared<sim::ConstantAtmosphere>();
rocket.first =
std::make_shared<model::RocketModel>();
rocket.second =
std::make_shared<sim::Propagator>(rocket.first);
gravity =
std::make_shared<sim::ConstantGravityModel>();
motorDatabase = std::make_shared<utils::MotorModelDatabase>();
logger->debug("Initial states vector size: " + std::to_string(states.capacity()) );
// Reserve at least 1024 spaces for StateData
if(states.capacity() < 1024)
{
states.reserve(1024);
}
logger->debug("New states vector size: " + std::to_string(states.capacity()) );
rocket =
std::make_shared<Rocket>();
}
int QtRocket::run(int argc, char* argv[])
@ -110,21 +100,11 @@ int QtRocket::run(int argc, char* argv[])
return 0;
}
void QtRocket::launchRocket()
{
// initialize the propagator
rocket.first->clearStates();
rocket.second->setCurrentTime(0.0);
// start the rocket motor
rocket.first->launch();
// run the propagator until it terminates
rocket.second->runUntilTerminate();
}
void QtRocket::addMotorModels(std::vector<model::MotorModel>& m)
{
motorDatabase->addMotorModels(m);
for(const auto& i : m)
{
motorModels.push_back(i);
}
// TODO: Now clear any duplicates?
}

View File

@ -7,19 +7,16 @@
#include <atomic>
#include <memory>
#include <mutex>
#include <utility>
// 3rd party headers
/// \endcond
// qtrocket headers
#include "model/MotorModel.h"
#include "model/RocketModel.h"
#include "sim/Environment.h"
#include "sim/Propagator.h"
#include "model/Rocket.h"
#include "sim/AtmosphericModel.h"
#include "sim/GravityModel.h"
#include "utils/Logger.h"
#include "utils/MotorModelDatabase.h"
#include "utils/math/MathTypes.h"
/**
* @brief The QtRocket class is the master controller for the QtRocket application.
@ -40,31 +37,15 @@ public:
void runSim();
std::shared_ptr<sim::GravityModel> getGravityModel() { return gravity; }
std::shared_ptr<sim::AtmosphericModel> getAtmosphereModel() { return atmosphere; }
std::shared_ptr<sim::Environment> getEnvironment() { return environment; }
void setTimeStep(double t) { rocket.second->setTimeStep(t); }
std::shared_ptr<model::RocketModel> getRocket() { return rocket.first; }
std::shared_ptr<utils::MotorModelDatabase> getMotorDatabase() { return motorDatabase; }
void addMotorModels(std::vector<model::MotorModel>& m);
void addRocket(std::shared_ptr<model::RocketModel> r) { rocket.first = r; rocket.second = std::make_shared<sim::Propagator>(r); }
void addRocket(std::shared_ptr<Rocket> r) { rocket = r; }
void setEnvironment(std::shared_ptr<sim::Environment> e) { environment = e; }
void launchRocket();
/**
* @brief getStates returns a vector of time/state pairs generated during launch()
* @return vector of pairs of doubles, where the first value is a time and the second a state vector
*/
const std::vector<std::pair<double, StateData>>& getStates() const { return rocket.first->getStates(); }
/**
* @brief setInitialState sets the initial state of the Rocket.
* @param initState initial state vector (x, y, z, xDot, yDot, zDot, pitch, yaw, roll, pitchDot, yawDot, rollDot)
*/
void setInitialState(const StateData& initState) { rocket.first->setInitialState(initState); }
std::shared_ptr<Rocket> getRocket() { return rocket; }
private:
QtRocket();
@ -76,20 +57,15 @@ private:
static std::mutex mtx;
static QtRocket* instance;
// Motor "database(s)"
std::vector<model::MotorModel> motorModels;
utils::Logger* logger;
using Rocket = std::pair<std::shared_ptr<model::RocketModel>, std::shared_ptr<sim::Propagator>>;
Rocket rocket;
std::shared_ptr<Rocket> rocket;
std::shared_ptr<sim::AtmosphericModel> atmosphere;
std::shared_ptr<sim::GravityModel> gravity;
std::shared_ptr<sim::Environment> environment;
std::shared_ptr<utils::MotorModelDatabase> motorDatabase;
// Launch site
// ECEF coordinates
Vector3 launchSitePosition{0.0, 0.0, 0.0};
// Table of state data
std::vector<StateData> states;
};

View File

@ -1,2 +1,3 @@
# qtrocket
An open source model Rocket Simulator written in C++ and Qt Toolkit, coming soon
Soon to be model Rocket Simulator written in C++ and Qt Toolkit

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -9,11 +9,6 @@ AboutWindow::AboutWindow(QWidget *parent) :
ui->setupUi(this);
setWindowTitle(QString("About QtRocket"));
connect(ui->okButton,
SIGNAL(clicked()),
this,
SLOT(onButton_okButton_clicked()));
}
AboutWindow::~AboutWindow()
@ -21,7 +16,7 @@ AboutWindow::~AboutWindow()
delete ui;
}
void AboutWindow::onButton_okButton_clicked()
void AboutWindow::on_pushButton_clicked()
{
this->close();
}

View File

@ -29,7 +29,7 @@ public:
~AboutWindow();
private slots:
void onButton_okButton_clicked();
void on_pushButton_clicked();
private:
Ui::AboutWindow *ui;

View File

@ -29,7 +29,7 @@
<string>Copyright (c) 2023 by Travis Hunter</string>
</property>
</widget>
<widget class="QPushButton" name="okButton">
<widget class="QPushButton" name="pushButton">
<property name="geometry">
<rect>
<x>250</x>

View File

@ -2,8 +2,6 @@
#include "ui_AnalysisWindow.h"
#include "QtRocket.h"
#include "model/MotorModel.h"
#include "model/ThrustCurve.h"
AnalysisWindow::AnalysisWindow(QWidget *parent) :
QDialog(parent),
@ -14,31 +12,10 @@ AnalysisWindow::AnalysisWindow(QWidget *parent) :
this->hide();
this->show();
connect(ui->plotAltitudeBtn,
SIGNAL(clicked()),
this,
SLOT(onButton_plotAltitude_clicked()));
connect(ui->plotVelocityBtn,
SIGNAL(clicked()),
this,
SLOT(onButton_plotVelocity_clicked()));
connect(ui->plotMotorCurveBtn,
SIGNAL(clicked()),this,
SLOT(onButton_plotMotorCurve_clicked()));
}
AnalysisWindow::~AnalysisWindow()
{
delete ui;
}
void AnalysisWindow::onButton_plotAltitude_clicked()
{
QtRocket* qtRocket = QtRocket::getInstance();
const std::vector<std::pair<double, StateData>>& res = qtRocket->getStates();
std::shared_ptr<Rocket> rocket = QtRocket::getInstance()->getRocket();
const std::vector<std::pair<double, std::vector<double>>>& res = rocket->getStates();
auto& plot = ui->plotWidget;
plot->clearGraphs();
plot->setInteraction(QCP::iRangeDrag, true);
plot->setInteraction(QCP::iRangeZoom, true);
// generate some data:
@ -46,7 +23,7 @@ void AnalysisWindow::onButton_plotAltitude_clicked()
for (int i = 0; i < tData.size(); ++i)
{
tData[i] = res[i].first;
zData[i] = res[i].second.position[2];
zData[i] = res[i].second[2];
}
// create graph and assign data to it:
plot->addGraph();
@ -60,66 +37,7 @@ void AnalysisWindow::onButton_plotAltitude_clicked()
plot->replot();
}
void AnalysisWindow::onButton_plotVelocity_clicked()
AnalysisWindow::~AnalysisWindow()
{
QtRocket* qtRocket = QtRocket::getInstance();
const std::vector<std::pair<double, StateData>>& res = qtRocket->getStates();
auto& plot = ui->plotWidget;
plot->clearGraphs();
plot->setInteraction(QCP::iRangeDrag, true);
plot->setInteraction(QCP::iRangeZoom, true);
// generate some data:
QVector<double> tData(res.size()), zData(res.size());
for (int i = 0; i < tData.size(); ++i)
{
tData[i] = res[i].first;
zData[i] = res[i].second.velocity[2];
}
// create graph and assign data to it:
plot->addGraph();
plot->graph(0)->setData(tData, zData);
// give the axes some labels:
plot->xAxis->setLabel("time");
plot->yAxis->setLabel("Z Velocity");
// set axes ranges, so we see all data:
plot->xAxis->setRange(*std::min_element(std::begin(tData), std::end(tData)), *std::max_element(std::begin(tData), std::end(tData)));
plot->yAxis->setRange(*std::min_element(std::begin(zData), std::end(zData)), *std::max_element(std::begin(zData), std::end(zData)));
plot->replot();
}
void AnalysisWindow::onButton_plotMotorCurve_clicked()
{
std::shared_ptr<model::RocketModel> rocket = QtRocket::getInstance()->getRocket();
model::MotorModel motor = rocket->getMotorModel();
ThrustCurve tc = motor.getThrustCurve();
const std::vector<std::pair<double, double>>& res = tc.getThrustCurveData();
auto& plot = ui->plotWidget;
plot->clearGraphs();
plot->setInteraction(QCP::iRangeDrag, true);
plot->setInteraction(QCP::iRangeZoom, true);
// generate some data:
QVector<double> tData(res.size());
QVector<double> fData(res.size());
for (int i = 0; i < tData.size(); ++i)
{
tData[i] = res[i].first;
fData[i] = res[i].second;
}
// create graph and assign data to it:
plot->addGraph();
plot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 5));
plot->graph(0)->setData(tData, fData);
// give the axes some labels:
plot->xAxis->setLabel("time");
plot->yAxis->setLabel("Thrust (N)");
// set axes ranges, so we see all data:
plot->xAxis->setRange(*std::min_element(std::begin(tData), std::end(tData)), *std::max_element(std::begin(tData), std::end(tData)));
plot->yAxis->setRange(*std::min_element(std::begin(fData), std::end(fData)), *std::max_element(std::begin(fData), std::end(fData)));
plot->replot();
delete ui;
}

View File

@ -36,12 +36,6 @@ public:
explicit AnalysisWindow(QWidget *parent = nullptr);
~AnalysisWindow();
private slots:
void onButton_plotAltitude_clicked();
void onButton_plotVelocity_clicked();
void onButton_plotMotorCurve_clicked();
private:
Ui::AnalysisWindow *ui;
};

View File

@ -16,68 +16,13 @@
<widget class="QCustomPlot" name="plotWidget" native="true">
<property name="geometry">
<rect>
<x>149</x>
<x>19</x>
<y>29</y>
<width>831</width>
<height>541</height>
<width>961</width>
<height>671</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="plotAltitudeBtn">
<property name="geometry">
<rect>
<x>20</x>
<y>50</y>
<width>121</width>
<height>36</height>
</rect>
</property>
<property name="text">
<string>Plot Altitude</string>
</property>
</widget>
<widget class="QPushButton" name="plotVelocityBtn">
<property name="geometry">
<rect>
<x>20</x>
<y>110</y>
<width>121</width>
<height>36</height>
</rect>
</property>
<property name="text">
<string>Plot Velocity</string>
</property>
</widget>
<widget class="QPushButton" name="plotAtmosphereBtn">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>20</x>
<y>170</y>
<width>121</width>
<height>36</height>
</rect>
</property>
<property name="text">
<string>Plot Atmosphere</string>
</property>
</widget>
<widget class="QPushButton" name="plotMotorCurveBtn">
<property name="geometry">
<rect>
<x>20</x>
<y>230</y>
<width>121</width>
<height>36</height>
</rect>
</property>
<property name="text">
<string>Motor Curve</string>
</property>
</widget>
</widget>
<customwidgets>
<customwidget>
@ -89,7 +34,4 @@
</customwidgets>
<resources/>
<connections/>
<slots>
<slot>plotAltitude()</slot>
</slots>
</ui>

View File

@ -19,8 +19,8 @@
#include "gui/AnalysisWindow.h"
#include "gui/MainWindow.h"
#include "gui/ThrustCurveMotorSelector.h"
#include "gui/SimOptionsWindow.h"
#include "model/RocketModel.h"
#include "gui/SimulationOptions.h"
#include "model/Rocket.h"
#include "utils/RSEDatabaseLoader.h"
@ -31,59 +31,6 @@ MainWindow::MainWindow(QtRocket* _qtRocket, QWidget *parent)
qtRocket(_qtRocket)
{
ui->setupUi(this);
////////////////////////////////
// Menu signal/slot connections
////////////////////////////////
// File Menu Actions
connect(ui->actionQuit,
SIGNAL(triggered()),
this,
SLOT(onMenu_File_Quit_triggered()));
// Edit Menu Actions
connect(ui->actionSimulation_Options,
SIGNAL(triggered()),
this,
SLOT(onMenu_Edit_SimulationOptions_triggered()));
// Tools Menu Actions
connect(ui->actionSaveMotorDatabase,
SIGNAL(triggered()),
this,
SLOT(onMenu_Tools_SaveMotorDatabase()));
// Help Menu Actions
connect(ui->actionAbout,
SIGNAL(triggered()),
this,
SLOT(onMenu_Help_About_triggered()));
////////////////////////////////
// Button signal/slot connections
////////////////////////////////
connect(ui->calculateTrajectory_btn,
SIGNAL(clicked()),
this,
SLOT(onButton_calculateTrajectory_clicked()));
connect(ui->loadRSE_btn,
SIGNAL(clicked()),
this,
SLOT(onButton_loadRSE_button_clicked()));
connect(ui->setMotor_btn,
SIGNAL(clicked()),
this,
SLOT(onButton_setMotor_clicked()));
connect(ui->getTCMotorData_btn,
SIGNAL(clicked()),
this,
SLOT(onButton_getTCMotorData_clicked()));
ui->calculateTrajectory_btn->setDisabled(true);
}
MainWindow::~MainWindow()
@ -92,7 +39,7 @@ MainWindow::~MainWindow()
}
void MainWindow::onMenu_Help_About_triggered()
void MainWindow::on_actionAbout_triggered()
{
AboutWindow about;
about.setModal(true);
@ -100,13 +47,33 @@ void MainWindow::onMenu_Help_About_triggered()
}
void MainWindow::onMenu_Tools_SaveMotorDatabase()
void MainWindow::on_testButton1_clicked()
{
qtRocket->getMotorDatabase()->saveMotorDatabase("qtrocket_motors.qmd");
auto& plot = ui->plotWindow;
// generate some data:
QVector<double> x(101), y(101); // initialize with entries 0..100
for (int i=0; i<101; ++i)
{
x[i] = i/50.0 - 1; // x goes from -1 to 1
y[i] = x[i]*x[i]; // let's plot a quadratic function
}
// create graph and assign data to it:
plot->addGraph();
plot->graph(0)->setData(x, y);
// give the axes some labels:
plot->xAxis->setLabel("x");
plot->yAxis->setLabel("y");
// set axes ranges, so we see all data:
plot->xAxis->setRange(-1, 1);
plot->yAxis->setRange(0, 1);
plot->replot();
utils::RSEDatabaseLoader("Aerotech.rse");
}
void MainWindow::onButton_calculateTrajectory_clicked()
void MainWindow::on_testButton2_clicked()
{
// Get the initial conditions
double initialVelocity =
@ -123,50 +90,70 @@ void MainWindow::onButton_calculateTrajectory_clicked()
double initialVelocityX = initialVelocity * std::cos(initialAngle / 57.2958);
double initialVelocityZ = initialVelocity * std::sin(initialAngle / 57.2958);
//std::vector<double> initialState = {0.0, 0.0, 0.0, initialVelocityX, 0.0, initialVelocityZ, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
StateData initialState;
initialState.position = {0.0, 0.0, 0.0};
initialState.velocity = {initialVelocityX, 0.0, initialVelocityZ};
auto rocket = QtRocket::getInstance()->getRocket();
std::shared_ptr<Rocket> rocket = std::make_shared<Rocket>();
QtRocket::getInstance()->addRocket(rocket);
std::vector<double> initialState = {0.0, 0.0, 0.0, initialVelocityX, 0.0, initialVelocityZ, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
rocket->setInitialState(initialState);
rocket->setMass(mass);
rocket->setDragCoefficient(dragCoeff);
qtRocket->setInitialState(initialState);
qtRocket->launchRocket();
rocket->launch();
AnalysisWindow aWindow;
aWindow.setModal(false);
aWindow.exec();
/*
const std::vector<std::pair<double, std::vector<double>>>& res = rocket.getStates();
for(const auto& i : res)
{
std::cout << i.first << ": " << "(" << i.second[0] << ", " << i.second[1] << ", " << i.second[2] << ")\n";
}
auto& plot = ui->plotWindow;
// generate some data:
QVector<double> tData(res.size()), zData(res.size());
for (int i = 0; i < tData.size(); ++i)
{
tData[i] = res[i].first;
zData[i] = res[i].second[2];
}
// create graph and assign data to it:
plot->addGraph();
plot->graph(0)->setData(tData, zData);
// give the axes some labels:
plot->xAxis->setLabel("time");
plot->yAxis->setLabel("Z");
// set axes ranges, so we see all data:
plot->xAxis->setRange(*std::min_element(std::begin(tData), std::end(tData)), *std::max_element(std::begin(tData), std::end(tData)));
plot->yAxis->setRange(*std::min_element(std::begin(zData), std::end(zData)), *std::max_element(std::begin(zData), std::end(zData)));
plot->replot();
*/
}
void MainWindow::onButton_loadRSE_button_clicked()
void MainWindow::on_loadRSE_button_clicked()
{
QString rseFile = QFileDialog::getOpenFileName(this,
tr("Import RSE Database File"),
"/home",
tr("Rocksim Engine Files (*.rse)"));
tr("RSE Files (*.rse)"));
if(!rseFile.isEmpty())
utils::RSEDatabaseLoader loader(rseFile.toStdString());
ui->rocketPartButtons->findChild<QLineEdit*>(QString("databaseFileLine"))->setText(rseFile);
QComboBox* engineSelector =
ui->rocketPartButtons->findChild<QComboBox*>(QString("engineSelectorComboBox"));
const std::vector<model::MotorModel>& motors = loader.getMotors();
for(const auto& motor : motors)
{
rseDatabase.reset(new utils::RSEDatabaseLoader(rseFile.toStdString()));
ui->rocketPartButtons->findChild<QLineEdit*>(QString("databaseFileLine"))->setText(rseFile);
QComboBox* engineSelector =
ui->rocketPartButtons->findChild<QComboBox*>(QString("engineSelectorComboBox"));
const std::vector<model::MotorModel>& motors = rseDatabase->getMotors();
for(const auto& motor : motors)
{
std::cout << "Adding: " << motor.data.commonName << std::endl;
engineSelector->addItem(QString(motor.data.commonName.c_str()));
}
std::cout << "Adding: " << motor.data.commonName << std::endl;
engineSelector->addItem(QString(motor.data.commonName.c_str()));
}
}
void MainWindow::onButton_getTCMotorData_clicked()
void MainWindow::on_getTCMotorData_clicked()
{
ThrustCurveMotorSelector window;
window.setModal(false);
@ -175,33 +162,13 @@ void MainWindow::onButton_getTCMotorData_clicked()
}
void MainWindow::onMenu_Edit_SimulationOptions_triggered()
void MainWindow::on_actionSimulation_Options_triggered()
{
if(!simOptionsWindow)
{
simOptionsWindow = new SimOptionsWindow(this);
simOptionsWindow = new SimulationOptions(this);
}
simOptionsWindow->show();
}
void MainWindow::onButton_setMotor_clicked()
{
QString motorName = ui->engineSelectorComboBox->currentText();
model::MotorModel mm = rseDatabase->getMotorModelByName(motorName.toStdString());
QtRocket::getInstance()->getRocket()->setMotorModel(mm);
// Now that we have a motor selected, we can enable the calculateTrajectory button
ui->calculateTrajectory_btn->setDisabled(false);
/// TODO: Figure out if this is the right place to populate the motor database
/// or from RSEDatabaseLoader where it currently is populated.
//QtRocket::getInstance()->addMotorModels(rseDatabase->getMotors());
}
void MainWindow::onMenu_File_Quit_triggered()
{
this->close();
}

View File

@ -4,7 +4,6 @@
/// \cond
// C headers
// C++ headers
#include <memory>
// 3rd Party headers
#include <QMainWindow>
/// \endcond
@ -12,8 +11,7 @@
// qtrocket headers
#include "QtRocket.h"
#include "gui/SimOptionsWindow.h"
#include "utils/RSEDatabaseLoader.h"
#include "gui/SimulationOptions.h"
QT_BEGIN_NAMESPACE
@ -37,27 +35,22 @@ public:
private slots:
void onMenu_Help_About_triggered();
void on_actionAbout_triggered();
void onButton_calculateTrajectory_clicked();
void on_testButton1_clicked();
void onButton_loadRSE_button_clicked();
void on_testButton2_clicked();
void onButton_getTCMotorData_clicked();
void on_loadRSE_button_clicked();
void onMenu_Edit_SimulationOptions_triggered();
void on_getTCMotorData_clicked();
void onButton_setMotor_clicked();
void onMenu_File_Quit_triggered();
void onMenu_Tools_SaveMotorDatabase();
void on_actionSimulation_Options_triggered();
private:
Ui::MainWindow* ui;
QtRocket* qtRocket;
SimOptionsWindow* simOptionsWindow{nullptr};
std::unique_ptr<utils::RSEDatabaseLoader> rseDatabase;
SimulationOptions* simOptionsWindow{nullptr};
};
#endif // MAINWINDOW_H

View File

@ -61,11 +61,30 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<widget class="QPushButton" name="calculateTrajectory_btn">
<widget class="QPushButton" name="testButton1">
<property name="geometry">
<rect>
<x>240</x>
<y>440</y>
<x>30</x>
<y>20</y>
<width>80</width>
<height>25</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TestButton1</string>
</property>
</widget>
<widget class="QPushButton" name="testButton2">
<property name="geometry">
<rect>
<x>30</x>
<y>70</y>
<width>191</width>
<height>25</height>
</rect>
@ -77,7 +96,7 @@
</sizepolicy>
</property>
<property name="text">
<string>Calculate Trajectory</string>
<string>Calculate Ballistic Trajectory</string>
</property>
</widget>
<widget class="QWidget" name="layoutWidget">
@ -86,15 +105,12 @@
<x>260</x>
<y>80</y>
<width>161</width>
<height>196</height>
<height>151</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<widget class="QLineEdit" name="initialAngle">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>90.0</string>
</property>
@ -127,7 +143,7 @@
<item row="0" column="1">
<widget class="QLineEdit" name="initialVelocity">
<property name="text">
<string>5.0</string>
<string>50.0</string>
</property>
</widget>
</item>
@ -148,7 +164,7 @@
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Initial Velocity</string>
<string>Velocity</string>
</property>
</widget>
</item>
@ -172,14 +188,14 @@
<property name="geometry">
<rect>
<x>90</x>
<y>290</y>
<y>250</y>
<width>421</width>
<height>80</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QPushButton" name="loadRSE_btn">
<widget class="QPushButton" name="loadRSE_button">
<property name="text">
<string>Load RSE Database File</string>
</property>
@ -195,20 +211,13 @@
<item row="1" column="0">
<widget class="QComboBox" name="engineSelectorComboBox"/>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="setMotor_btn">
<property name="text">
<string>Set Motor</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QPushButton" name="getTCMotorData_btn">
<widget class="QPushButton" name="getTCMotorData">
<property name="geometry">
<rect>
<x>240</x>
<y>390</y>
<y>360</y>
<width>201</width>
<height>25</height>
</rect>
@ -228,7 +237,7 @@
<x>0</x>
<y>0</y>
<width>1031</width>
<height>32</height>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@ -253,7 +262,6 @@
<property name="title">
<string>Tools</string>
</property>
<addaction name="actionSaveMotorDatabase"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
@ -277,9 +285,6 @@
</property>
</action>
<action name="actionNew">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="document-new">
<normaloff>.</normaloff>.</iconset>
@ -289,9 +294,6 @@
</property>
</action>
<action name="actionOpen">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="document-open">
<normaloff>.</normaloff>.</iconset>
@ -301,9 +303,6 @@
</property>
</action>
<action name="actionSave">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="document-save">
<normaloff>.</normaloff>.</iconset>
@ -313,9 +312,6 @@
</property>
</action>
<action name="actionClose">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Close</string>
</property>
@ -330,9 +326,6 @@
</property>
</action>
<action name="actionSave_As">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="document-save-as">
<normaloff>.</normaloff>.</iconset>
@ -346,11 +339,6 @@
<string>Simulation Options</string>
</property>
</action>
<action name="actionSaveMotorDatabase">
<property name="text">
<string>Save Motor Database</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -1,74 +0,0 @@
/// \cond
// C headers
// C++ headers
#include <memory>
// 3rd party headers
/// \endcond
// qtrocket headers
#include "QtRocket.h"
#include "SimOptionsWindow.h"
#include "ui_SimOptionsWindow.h"
#include "sim/Environment.h"
SimOptionsWindow::SimOptionsWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::SimOptionsWindow)
{
ui->setupUi(this);
connect(ui->buttonBox,
SIGNAL(rejected()),
this,
SLOT(on_buttonBox_rejected()));
connect(ui->buttonBox,
SIGNAL(accepted()),
this,
SLOT(on_buttonBox_accepted()));
// populate the combo boxes
std::shared_ptr<sim::Environment> options(new sim::Environment);
std::vector<std::string> atmosphereModels = options->getAvailableAtmosphereModels();
std::vector<std::string> gravityModels = options->getAvailableGravityModels();
for(const auto& i : atmosphereModels)
{
ui->atmosphereModelCombo->addItem(QString::fromStdString(i));
}
for(const auto& i : gravityModels)
{
ui->gravityModelCombo->addItem(QString::fromStdString(i));
}
}
SimOptionsWindow::~SimOptionsWindow()
{
delete ui;
}
void SimOptionsWindow::on_buttonBox_rejected()
{
this->close();
}
void SimOptionsWindow::on_buttonBox_accepted()
{
QtRocket* qtrocket = QtRocket::getInstance();
std::shared_ptr<sim::Environment> environment(new sim::Environment);
qtrocket->setTimeStep(ui->timeStep->text().toDouble());
environment->setGravityModel(ui->gravityModelCombo->currentText().toStdString());
environment->setAtmosphereModel(ui->atmosphereModelCombo->currentText().toStdString());
qtrocket->setEnvironment(environment);
this->close();
}

View File

@ -1,27 +0,0 @@
#ifndef SIMOPTIONSWINDOW_H
#define SIMOPTIONSWINDOW_H
#include <QMainWindow>
namespace Ui {
class SimOptionsWindow;
}
class SimOptionsWindow : public QMainWindow
{
Q_OBJECT
public:
explicit SimOptionsWindow(QWidget *parent = nullptr);
~SimOptionsWindow();
private slots:
void on_buttonBox_rejected();
void on_buttonBox_accepted();
private:
Ui::SimOptionsWindow *ui;
};
#endif // SIMOPTIONSWINDOW_H

14
gui/SimulationOptions.cpp Normal file
View File

@ -0,0 +1,14 @@
#include "SimulationOptions.h"
#include "ui_SimulationOptions.h"
SimulationOptions::SimulationOptions(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::SimulationOptions)
{
ui->setupUi(this);
}
SimulationOptions::~SimulationOptions()
{
delete ui;
}

22
gui/SimulationOptions.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef SIMULATIONOPTIONS_H
#define SIMULATIONOPTIONS_H
#include <QMainWindow>
namespace Ui {
class SimulationOptions;
}
class SimulationOptions : public QMainWindow
{
Q_OBJECT
public:
explicit SimulationOptions(QWidget *parent = nullptr);
~SimulationOptions();
private:
Ui::SimulationOptions *ui;
};
#endif // SIMULATIONOPTIONS_H

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SimOptionsWindow</class>
<widget class="QMainWindow" name="SimOptionsWindow">
<class>SimulationOptions</class>
<widget class="QMainWindow" name="SimulationOptions">
<property name="geometry">
<rect>
<x>0</x>
@ -26,7 +26,7 @@
<x>60</x>
<y>20</y>
<width>261</width>
<height>156</height>
<height>131</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">

View File

@ -17,23 +17,6 @@ ThrustCurveMotorSelector::ThrustCurveMotorSelector(QWidget *parent) :
tcApi(new utils::ThrustCurveAPI)
{
ui->setupUi(this);
connect(ui->getMetadata,
SIGNAL(clicked()),
this,
SLOT(onButton_getMetadata_clicked()));
connect(ui->searchButton,
SIGNAL(clicked()),
this,
SLOT(onButton_searchButton_clicked()));
connect(ui->setMotor,
SIGNAL(clicked()),
this,
SLOT(onButton_setMotor_clicked()));
this->setWindowModality(Qt::NonModal);
this->hide();
this->show();
@ -44,7 +27,7 @@ ThrustCurveMotorSelector::~ThrustCurveMotorSelector()
delete ui;
}
void ThrustCurveMotorSelector::onButton_getMetadata_clicked()
void ThrustCurveMotorSelector::on_getMetadata_clicked()
{
// When the user clicks "Get Metadata", we want to pull in Metadata from thrustcurve.org
// and populate the Manufacturer, Diameter, and Impulse Class combo boxes
@ -67,7 +50,7 @@ void ThrustCurveMotorSelector::onButton_getMetadata_clicked()
}
void ThrustCurveMotorSelector::onButton_searchButton_clicked()
void ThrustCurveMotorSelector::on_searchButton_clicked()
{
//double diameter = ui->diameter->
@ -92,7 +75,7 @@ void ThrustCurveMotorSelector::onButton_searchButton_clicked()
}
void ThrustCurveMotorSelector::onButton_setMotor_clicked()
void ThrustCurveMotorSelector::on_setMotor_clicked()
{
//asdf
std::string commonName = ui->motorSelection->currentText().toStdString();
@ -107,34 +90,6 @@ void ThrustCurveMotorSelector::onButton_setMotor_clicked()
return item.data.commonName == commonName;
});
ThrustCurve tc = tcApi->getMotorData(mm.data.motorIdTC).getThrustCurve();
mm.addThrustCurve(tc);
QtRocket::getInstance()->getRocket()->setMotorModel(mm);
const std::vector<std::pair<double, double>>& res = tc.getThrustCurveData();
auto& plot = ui->plot;
plot->clearGraphs();
plot->setInteraction(QCP::iRangeDrag, true);
plot->setInteraction(QCP::iRangeZoom, true);
// generate some data:
QVector<double> tData(res.size());
QVector<double> fData(res.size());
for (int i = 0; i < tData.size(); ++i)
{
tData[i] = res[i].first;
fData[i] = res[i].second;
}
// create graph and assign data to it:
plot->addGraph();
plot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 5));
plot->graph(0)->setData(tData, fData);
// give the axes some labels:
plot->xAxis->setLabel("time");
plot->yAxis->setLabel("Thrust (N)");
// set axes ranges, so we see all data:
plot->xAxis->setRange(*std::min_element(std::begin(tData), std::end(tData)), *std::max_element(std::begin(tData), std::end(tData)));
plot->yAxis->setRange(*std::min_element(std::begin(fData), std::end(fData)), *std::max_element(std::begin(fData), std::end(fData)));
plot->replot();
}

View File

@ -30,11 +30,11 @@ public:
~ThrustCurveMotorSelector();
private slots:
void onButton_getMetadata_clicked();
void on_getMetadata_clicked();
void onButton_searchButton_clicked();
void on_searchButton_clicked();
void onButton_setMotor_clicked();
void on_setMotor_clicked();
private:
Ui::ThrustCurveMotorSelector *ui;

View File

@ -1,9 +1,11 @@
/// \cond
// C headers
// C++ headers
// 3rd party headers
/// \endcond
#include "QtRocket.h"
#include "utils/Logger.h"
@ -12,16 +14,54 @@ int main(int argc, char *argv[])
// Instantiate logger
utils::Logger* logger = utils::Logger::getInstance();
logger->setLogLevel(utils::Logger::PERF_);
logger->info("Logger instantiated at PERF level");
logger->setLogLevel(utils::Logger::DEBUG);
// instantiate QtRocket
logger->debug("Starting QtRocket");
QtRocket* qtrocket = QtRocket::getInstance();
// Run QtRocket. This'll start the GUI thread and block until the user
// exits the program
logger->debug("QtRocket->run()");
int retVal = qtrocket->run(argc, argv);
logger->debug("Returning");
return retVal;
}
/*
* This was the old main when it directly started the QtGui. It worked. The new way of
* starting the gui through QtRocket->run() also seems to work, but I'm leaving this here for
* now in case there are unforeseen issues with starting the gui in a separate thread via
* QtRocket::run()
*
#include "gui/MainWindow.h"
#include <QApplication>
#include <QLocale>
#include <QTranslator>
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
a.setWindowIcon(QIcon(":/qtrocket.png"));
// Start translation component.
// TODO: Only support US English at the moment. Anyone want to help translate?
QTranslator translator;
const QStringList uiLanguages = QLocale::system().uiLanguages();
for (const QString &locale : uiLanguages)
{
const QString baseName = "qtrocket_" + QLocale(locale).name();
if (translator.load(":/i18n/" + baseName))
{
a.installTranslator(&translator);
break;
}
}
// Go!
MainWindow w;
w.show();
return a.exec();
}
*/

View File

@ -1,20 +0,0 @@
add_library(model
MotorModel.cpp
MotorModel.h
MotorModelDatabase.cpp
MotorModelDatabase.h
Part.cpp
Part.h
Propagatable.cpp
Propagatable.h
RocketModel.cpp
RocketModel.h
ThrustCurve.cpp
ThrustCurve.h
InertiaTensors.h)
target_link_libraries(model PRIVATE
utils)
# Unit tests
add_subdirectory(tests)

View File

@ -1,70 +0,0 @@
#ifndef INERTIATENSORS_H
#define INERTIATENSORS_H
#include "utils/math/MathTypes.h"
namespace model
{
/**
* @brief The InertiaTensors class provides a collection of methods to
* deliver some common inertia tensors centered about the center of mass
*/
class InertiaTensors
{
public:
/**
* @brief SolidSphere
* @param radius (meters)
* @return
*/
static Matrix3 SolidSphere(double radius)
{
double xx = 0.4*radius*radius;
double yy = xx;
double zz = xx;
return Matrix3{{xx, 0, 0},
{0, yy, 0},
{0, 0, zz}};
}
/**
* @brief HollowSphere
* @param radius (meters)
* @return
*/
static Matrix3 HollowSphere(double radius)
{
double xx = (2.0/3.0)*radius*radius;
double yy = xx;
double zz = xx;
return Matrix3{{xx, 0, 0},
{0, yy, 0},
{0, 0, zz}};
}
/**
* @brief Tube - The longitudinal axis is the z-axis. Can also be used for a solid cylinder
* when innerRadius = 0.0
* @param innerRadius (meters)
* @param outerRadius (meters)
* @param length (meters)
* @return
*/
static Matrix3 Tube(double innerRadius, double outerRadius, double length)
{
double xx = (1.0/12.0)*(3.0*(innerRadius*innerRadius + outerRadius*outerRadius) + length*length);
double yy = xx;
double zz = (1.0/2.0)*(innerRadius*innerRadius + outerRadius*outerRadius);
return Matrix3{{xx, 0.0, 0.0},
{0.0, yy, 0.0},
{0.0, 0.0, zz}};
}
};
}
#endif // INERTIATENSORS_H

1
model/MotorCase.cpp Normal file
View File

@ -0,0 +1 @@
#include "MotorCase.h"

17
model/MotorCase.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef MODEL_MOTORCASE_H
#define MODEL_MOTORCASE_H
namespace model
{
class MotorCase
{
public:
MotorCase();
~MotorCase();
private:
};
} // namespace model
#endif // MODEL_MOTORCASE_H

View File

@ -1,15 +1,5 @@
/// \cond
// C headers
// C++ headers
// 3rd party headers
/// \endcond
// qtrocket headers
#include "model/MotorModel.h"
#include "utils/math/Constants.h"
#include "utils/math/UtilityMathFunctions.h"
#include "utils/Logger.h"
namespace model
{
@ -43,7 +33,7 @@ double MotorModel::getMass(double simTime) const
// If thrustTime is equal to a data point that we have, then just return
// the mass at that time. Otherwise it fell between two points and we
// will interpolate
if(utils::math::floatingPointEqual(i->first, thrustTime))
if(i->first == thrustTime)
{
// return empty mass + remaining propellant mass
return emptyMass + i->second;
@ -60,47 +50,18 @@ double MotorModel::getMass(double simTime) const
double propMassEnd = i->second;
double slope = (propMassEnd - propMassStart) / (tEnd - tStart);
double currentMass = emptyMass + propMassStart + (thrustTime - tStart) * slope;
utils::Logger::getInstance()->info("simTime: " + std::to_string(simTime) + ": motor mass: " + std::to_string(currentMass));
return currentMass;
}
// motor has burned out
else
{
return emptyMass;
}
}
double MotorModel::getThrust(double simTime)
{
if(simTime > thrust.getMaxTime() + ignitionTime)
{
if(!burnOutOccurred)
{
utils::Logger::getInstance()->info("motor burnout occurred: " + std::to_string(simTime));
burnOutOccurred = true;
}
return 0.0;
}
utils::Logger::getInstance()->info("simTime: " + std::to_string(simTime) + ": thrust: " + std::to_string(thrust.getThrust(simTime)));
return thrust.getThrust(simTime);
}
void MotorModel::setMetaData(const MetaData& md)
{
data = md;
computeMassCurve();
}
void MotorModel::moveMetaData(MetaData&& md)
{
data = std::move(md);
computeMassCurve();
}
void MotorModel::computeMassCurve()
{
emptyMass = data.totalWeight - data.propWeight;
// Calculate the Isp for the motor, as we'll need this for the computing the mass flow rate.
@ -127,5 +88,13 @@ void MotorModel::computeMassCurve()
propMass -= thrust.getThrust(t + i*timeStep) * timeStep * data.propWeight / data.totalImpulse;
}
massCurve.push_back(std::make_pair(data.burnTime, 0.0));
}
void MotorModel::moveMetaData(MetaData&& md)
{
data = std::move(md);
}
} // namespace model

View File

@ -134,7 +134,7 @@ public:
* @brief str Returns a string representation of AVAILABILITY enum
* @return string representation
*/
std::string str() const
std::string str()
{
if(availability == AVAILABILITY::REGULAR)
return std::string("regular");
@ -176,7 +176,7 @@ public:
* @brief str Returns a string representation of CERTORG enum
* @return string representation
*/
std::string str() const
std::string str()
{
if(org == CERTORG::AMRS)
return std::string("Austrialian Model Rocket Society Inc.");
@ -235,7 +235,7 @@ public:
* @brief str Returns a string representation of MOTORTYPE enum
* @return string representation
*/
std::string str() const
std::string str()
{
if(type == MOTORTYPE::SU)
return std::string("Single Use");
@ -253,12 +253,10 @@ public:
static MOTORTYPE toEnum(const std::string& name)
{
if(name == "SU" ||
name == "Single Use" ||
name == "single-use")
name == "Single Use")
return MOTORTYPE::SU;
else if(name == "reload" ||
name == "Reload" ||
name == "reloadable")
name == "Reload")
return MOTORTYPE::RELOAD;
else // It's a hybrid
return MOTORTYPE::HYBRID;
@ -286,7 +284,7 @@ public:
* @brief str Returns a string representation of MOTORMANUFACTURER enum
* @return string representation
*/
std::string str() const
std::string str()
{
switch(manufacturer)
{
@ -398,15 +396,11 @@ public:
void startMotor(double startTime) { ignitionOccurred = true; ignitionTime = startTime; }
void addThrustCurve(const ThrustCurve& tc) { thrust = tc; }
const ThrustCurve& getThrustCurve() const { return thrust; }
// Thrust parameters
MetaData data;
private:
bool ignitionOccurred{false};
bool burnOutOccurred{false};
double emptyMass;
double isp;
double maxTime;
@ -414,8 +408,6 @@ private:
ThrustCurve thrust; /// The measured motor thrust curve
std::vector<std::pair<double, double>> massCurve;
void computeMassCurve();
};

View File

@ -1,109 +0,0 @@
#include "Part.h"
#include "utils/Logger.h"
namespace model
{
Part::Part(const std::string& n,
const Matrix3& I,
double m,
const Vector3& centerMass)
: parent(nullptr),
name(n),
inertiaTensor(I),
compositeInertiaTensor(I),
mass(m),
compositeMass(m),
cm(centerMass),
needsRecomputing(false),
childParts()
{ }
Part::~Part()
{}
Part::Part(const Part& orig)
: parent(orig.parent),
name(orig.name),
inertiaTensor(orig.inertiaTensor),
compositeInertiaTensor(orig.compositeInertiaTensor),
mass(orig.mass),
compositeMass(orig.compositeMass),
cm(orig.cm),
needsRecomputing(orig.needsRecomputing),
childParts()
{
// We are copying the whole tree. If the part we're copying itself has child
// parts, we are also copying all of them! This may be inefficient and not what
// is desired, but it is less likely to lead to weird bugs with the same part
// appearing in multiple locations of the rocket
utils::Logger::getInstance()->debug("Calling model::Part copy constructor. Recursively copying all child parts. Check Part names for uniqueness");
for(const auto& i : orig.childParts)
{
Part& x = *std::get<0>(i);
std::shared_ptr<Part> tempPart = std::make_shared<Part>(x);
childParts.emplace_back(tempPart, std::get<1>(i));;
}
}
double Part::getChildMasses(double t)
{
double childMasses{0.0};
for(const auto& i : childParts)
{
childMasses += std::get<0>(i)->getMass(t);
}
return childMasses;
}
void Part::addChildPart(const Part& childPart, Vector3 position)
{
double childMass = childPart.compositeMass;
Matrix3 childInertiaTensor = childPart.compositeInertiaTensor;
std::shared_ptr<Part> newChild = std::make_shared<Part>(childPart);
// Set the parent pointer
newChild->parent = this;
// Recompute inertia tensor
childInertiaTensor += childMass * ( position.dot(position) * Matrix3::Identity() - position*position.transpose());
compositeInertiaTensor += childInertiaTensor;
compositeMass += childMass;
childParts.emplace_back(std::move(newChild), std::move(position));
if(parent)
{
parent->markAsNeedsRecomputing();
parent->recomputeInertiaTensor();
}
}
void Part::recomputeInertiaTensor()
{
if(!needsRecomputing)
{
return;
}
// recompute the whole composite inertia tensor
// Reset the composite inertia tensor
compositeInertiaTensor = inertiaTensor;
compositeMass = mass;
for(auto& [child, pos] : childParts)
{
child->recomputeInertiaTensor();
compositeInertiaTensor += child->compositeInertiaTensor + child->compositeMass * ( pos.dot(pos) * Matrix3::Identity() - pos*pos.transpose());
compositeMass += child->compositeMass;
}
needsRecomputing = false;
}
} // namespace model

View File

@ -1,136 +0,0 @@
#ifndef MODEL_PART_H
#define MODEL_PART_H
/// \cond
// C headers
// C++ headers
#include <vector>
#include <memory>
// 3rd party headers
/// \endcond
// qtrocket headers
#include "utils/math/MathTypes.h"
namespace model
{
class Part
{
public:
Part(const std::string& name,
const Matrix3& I,
double m,
const Vector3& centerMass);
virtual ~Part();
Part(const Part&);
Part& operator=(Part other)
{
if(this != &other)
{
std::swap(parent, other.parent);
std::swap(name, other.name);
std::swap(inertiaTensor, other.inertiaTensor);
std::swap(compositeInertiaTensor, other.compositeInertiaTensor);
std::swap(mass, other.mass);
std::swap(compositeMass, other.compositeMass);
std::swap(cm, other.cm);
std::swap(needsRecomputing, other.needsRecomputing);
std::swap(childParts, other.childParts);
}
return *this;
}
Part& operator=(Part&& other)
{
parent = std::move(other.parent);
name = std::move(other.name);
inertiaTensor = std::move(other.inertiaTensor);
compositeInertiaTensor = std::move(other.compositeInertiaTensor);
mass = std::move(other.mass);
compositeMass = std::move(other.compositeMass);
cm = std::move(other.cm);
needsRecomputing = std::move(other.needsRecomputing);
childParts = std::move(other.childParts);
return *this;
}
void setMass(double m) { mass = m; }
// Set the inertia tensor
void setI(const Matrix3& I) { inertiaTensor = I; }
Matrix3 getI() { return inertiaTensor; }
Matrix3 getCompositeI() { return compositeInertiaTensor; }
void setCm(const Vector3& x) { cm = x; }
// Special version of setCM that assumes the cm lies along the body x-axis
void setCm(double x) { cm = {x, 0.0, 0.0}; }
double getMass(double t)
{
return mass;
}
double getCompositeMass(double t)
{
return compositeMass;
}
/**
* @brief Add a child part to this part.
*
* @param childPart Child part to add
* @param position Relative position of the child part's center-of-mass w.r.t the
* parent's center of mass
*/
void addChildPart(const Part& childPart, Vector3 position);
/**
* @brief Recomputes the inertia tensor. If the change is due to the change in inertia
* of a child part, an optional name of the child part can be given to
* only recompute that change rather than recompute all child inertia
* tensors
*
* @param name Optional name of the child part to recompute. If empty, it will
* recompute all child inertia tensors
*/
//void recomputeInertiaTensor(std::string name = "");
void recomputeInertiaTensor();
private:
// This is a pointer to the parent Part, if it has one. Purpose is to be able to
// tell the parent if it needs to recompute anything if this part changes. e.g.
// if a change to this part's inertia tensor occurs, the parent needs to recompute
// it's total inertia tensor.
Part* parent{nullptr};
std::string name;
double getChildMasses(double t);
void markAsNeedsRecomputing()
{ needsRecomputing = true; if(parent) { parent->markAsNeedsRecomputing(); }}
// Because a part is both a simple part and the composite of itself with all of it's children,
// we will keep track of this object's inertia tensor (without children), and the composite
// one with all of it's children attached
Matrix3 inertiaTensor; // moment of inertia tensor with respect to the part's center of mass and
Matrix3 compositeInertiaTensor;
double mass; // The moment of inertia tensor also has this, so don't double compute
double compositeMass; // The mass of this part along with all attached parts
Vector3 cm; // center of mass wrt middle of component
bool needsRecomputing{false};
/// @brief child parts and the relative positions of their center of mass w.r.t.
/// the center of mass of this part
std::vector<std::tuple<std::shared_ptr<Part>, Vector3>> childParts;
};
}
#endif // MODEL_PART_H

View File

View File

@ -1,58 +0,0 @@
#ifndef MODEL_PROPAGATABLE_H
#define MODEL_PROPAGATABLE_H
/// \cond
// C headers
// C++ headers
#include <utility>
// 3rd party headers
/// \endcond
// qtrocket headers
#include "sim/Aero.h"
#include "sim/StateData.h"
#include "utils/math/MathTypes.h"
namespace model
{
class Propagatable
{
public:
Propagatable() {}
virtual ~Propagatable() {}
virtual Vector3 getForces(double t) = 0;
virtual Vector3 getTorques(double t) = 0;
virtual double getMass(double t) = 0;
virtual Matrix3 getInertiaTensor(double t) = 0;
virtual bool terminateCondition(double t) = 0;
void setCurrentState(const StateData& st) { currentState = st; }
const StateData& getCurrentState() { return currentState; }
const StateData& getInitialState() { return initialState; }
void setInitialState(const StateData& init) { initialState = init; }
void appendState(double t, const StateData& st) { states.emplace_back(t, st); }
const std::vector<std::pair<double, StateData>>& getStates() { return states; }
void clearStates() { states.clear(); }
protected:
sim::Aero aeroData;
StateData initialState;
StateData currentState;
StateData nextState;
std::vector<std::pair<double, StateData>> states;
};
}
#endif // MODEL_PROPAGATABLE_H

36
model/Rocket.cpp Normal file
View File

@ -0,0 +1,36 @@
#include "Rocket.h"
Rocket::Rocket() : propagator(this)
{
propagator.setTimeStep(0.01);
//propagator.set
}
void Rocket::launch()
{
propagator.runUntilTerminate();
}
void Rocket::setMotorModel(const model::MotorModel& motor)
{
mm = motor;
}
bool Rocket::terminateCondition(const std::pair<double, std::vector<double>>& cond)
{
if(cond.second[2] < 0)
return true;
else
return false;
}
double Rocket::getThrust(double t)
{
return tc.getThrust(t);
}
void Rocket::setThrustCurve(const ThrustCurve& curve)
{
tc = curve;
}

121
model/Rocket.h Normal file
View File

@ -0,0 +1,121 @@
#ifndef ROCKET_H
#define ROCKET_H
/// \cond
// C headers
// C++ headers
#include <memory>
#include <string>
#include <utility> // std::move
// 3rd party headers
/// \endcond
// qtrocket headers
#include "model/ThrustCurve.h"
#include "model/MotorModel.h"
#include "sim/Propagator.h"
/**
* @brief The Rocket class holds all rocket components
*
*/
class Rocket
{
public:
/**
* @brief Rocket class constructor
*/
Rocket();
/**
* @brief launch Propagates the Rocket object until termination,
* normally when altitude crosses from positive to negative
*/
void launch();
/**
* @brief getStates returns a vector of time/state pairs generated during launch()
* @return vector of pairs of doubles, where the first value is a time and the second a state vector
*/
const std::vector<std::pair<double, std::vector<double>>>& getStates() const { return propagator.getStates(); }
/**
* @brief setInitialState sets the initial state of the Rocket.
* @param initState initial state vector (x, y, z, xDot, yDot, zDot, pitch, yaw, roll, pitchDot, yawDot, rollDot)
*/
void setInitialState(const std::vector<double>& initState) { propagator.setInitialState(initState); }
/**
* @brief getMass returns the current mass of the rocket. This is the sum of all components' masses
* @return total current mass of the Rocket
*/
double getMass() const { return mass; }
/**
* @brief setMass sets the current total mass of the Rocket
* @param m total Rocket mass
* @todo This should be dynamically computed, not set. Fix this
*/
void setMass(double m) { mass = m;}
/**
* @brief setDragCoefficient sets the current total drag coefficient of the Rocket
* @param d drag coefficient
* @todo This should be dynamically computed, not set. Fix this
*/
void setDragCoefficient(double d) { dragCoeff = d; }
/**
* @brief getDragCoefficient returns the current drag coefficient
*
* This is intended to be called by the propagator during propagation.
* @return the coefficient of drag
*/
double getDragCoefficient() const { return dragCoeff; }
/**
* @brief getThrust returns current motor thrust
* @param t current simulation time
* @return thrust in Newtons
*/
double getThrust(double t);
/**
* @brief setThrustCurve sets the current thrust curve
* @param curve
* @todo Remove this
*/
void setThrustCurve(const ThrustCurve& curve);
/**
* @brief setMotorModel
* @param motor
*/
void setMotorModel(const model::MotorModel& motor);
/**
* @brief terminateCondition returns true or false, whether the passed-in time/state matches the terminate condition
* @param cond time/state pair
* @return true if the passed-in time/state satisfies the terminate condition
*/
bool terminateCondition(const std::pair<double, std::vector<double>>& cond);
/**
* @brief setName sets the rocket name
* @param n name to set the Rocket
*/
void setName(const std::string& n) { name = n; }
private:
std::string name; /// Rocket name
sim::Propagator propagator; /// propagator
double dragCoeff; /// @todo get rid of this, should be dynamically calculated
double mass; /// @todo get rid of this, should be dynamically computed, but is the current rocket mass
model::MotorModel mm; /// Current Motor Model
ThrustCurve tc; /// @todo get rid of this, should be returned from the MotorModel
};
#endif // ROCKET_H

View File

@ -1,79 +0,0 @@
// qtrocket headers
#include "RocketModel.h"
#include "QtRocket.h"
#include "InertiaTensors.h"
namespace model
{
RocketModel::RocketModel()
: topPart("NoseCone", InertiaTensors::SolidSphere(1.0), 1.0, {0.0, 0.0, 1.0})
{
}
double RocketModel::getMass(double t)
{
double mass = mm.getMass(t);
mass += topPart.getCompositeMass(t);
return mass;
}
Matrix3 RocketModel::getInertiaTensor(double)
{
return topPart.getCompositeI();
}
bool RocketModel::terminateCondition(double)
{
// Terminate propagation when the z coordinate drops below zero
if(currentState.position[2] < 0)
return true;
else
return false;
}
Vector3 RocketModel::getForces(double t)
{
// Get thrust
// Assume that thrust is always through the center of mass and in the rocket's Z-axis
Vector3 forces{0.0, 0.0, mm.getThrust(t)};
// Get gravity
auto gravityModel = QtRocket::getInstance()->getEnvironment()->getGravityModel();
Vector3 gravity = gravityModel->getAccel(currentState.position)*getMass(t);
forces += gravity;
// Calculate aero forces
return forces;
}
Vector3 RocketModel::getTorques(double t)
{
return Vector3{0.0, 0.0, 0.0};
}
double RocketModel::getThrust(double t)
{
return mm.getThrust(t);
}
void RocketModel::launch()
{
mm.startMotor(0.0);
}
void RocketModel::setMotorModel(const model::MotorModel& motor)
{
mm = motor;
}
} // namespace model

View File

@ -1,116 +0,0 @@
#ifndef ROCKETMODEL_H
#define ROCKETMODEL_H
/// \cond
// C headers
// C++ headers
#include <vector>
#include <memory>
#include <string>
#include <utility> // std::move
// 3rd party headers
/// \endcond
// qtrocket headers
#include "model/Part.h"
#include "sim/Propagator.h"
#include "model/MotorModel.h"
#include "model/Propagatable.h"
// Not yet
//#include "model/Stage.h"
namespace model
{
/**
* @brief The Rocket class holds all rocket components
*
*/
class RocketModel : public Propagatable
{
public:
/**
* @brief Rocket class constructor
*/
RocketModel();
/**
* @brief Rocket class destructor
*
*/
virtual ~RocketModel() {}
/**
* @brief launch Propagates the Rocket object until termination,
* normally when altitude crosses from positive to negative
*/
void launch();
Vector3 getForces(double t) override;
Vector3 getTorques(double t) override;
/**
* @brief getMass returns current rocket mass
* @param t current simulation time
* @return mass in kg
*/
double getMass(double t) override;
/**
* @brief terminateCondition returns true or false, whether the passed-in time/state matches the terminate condition
* @param cond time/state pair
* @return true if the passed-in time/state satisfies the terminate condition
*/
bool terminateCondition(double t) override;
Matrix3 getInertiaTensor(double t) override;
/**
* @brief getThrust returns current motor thrust
* @param t current simulation time
* @return thrust in Newtons
*/
double getThrust(double t);
/**
* @brief setMotorModel
* @param motor
*/
void setMotorModel(const model::MotorModel& motor);
/**
* @brief getMotorModel
*/
MotorModel getMotorModel() { return mm; }
/**
* @brief Returns the current motor model.
* @return The current motor model
*/
//const model::MotorModel& getCurrentMotorModel() const { return mm; }
/**
* @brief setName sets the rocket name
* @param n name to set the Rocket
*/
void setName(const std::string& n) { name = n; }
double getDragCoefficient() { return 1.0; }
void setDragCoefficient(double d) { }
void setMass(double m) { }
private:
std::string name; /// Rocket name
model::MotorModel mm; /// Current Motor Model
model::Part topPart;
};
} // namespace model
#endif // ROCKETMODEL_H

View File

@ -8,6 +8,8 @@
/// \endcond
#include "model/ThrustCurve.h"
#include "utils/Logger.h"
ThrustCurve::ThrustCurve(std::vector<std::pair<double, double>>& tc)
: thrustCurve(tc),
@ -53,6 +55,12 @@ void ThrustCurve::setIgnitionTime(double t)
double ThrustCurve::getThrust(double t)
{
// calculate t relative to the start time of the motor
static bool burnout{false};
if(!burnout && t >= (maxTime + ignitionTime))
{
burnout = true;
utils::Logger::getInstance()->info("Motor burnout at time: " + std::to_string(t));
}
t -= ignitionTime;
if(t < 0.0 || t > maxTime)
{

View File

@ -48,8 +48,6 @@ public:
*/
void setThrustCurveVector(const std::vector<std::pair<double, double>>& v);
const std::vector<std::pair<double, double>> getThrustCurveData() const { return thrustCurve; }
private:
std::vector<std::pair<double, double>> thrustCurve;
double maxTime{0.0};

View File

@ -1,16 +0,0 @@
add_executable(model_tests
PartTests.cpp
)
target_link_libraries(model_tests PRIVATE
model
utils
GTest::gtest_main
)
include(GoogleTest)
gtest_discover_tests(model_tests)
add_test(NAME qtrocket_model_tests COMMAND model_tests)

View File

@ -1,70 +0,0 @@
#include <gtest/gtest.h>
#include "model/Part.h"
class PartTest : public testing::Test
{
protected:
// Per-test-suite set-up.
// Called before the first test in this test suite.
// Can be omitted if not needed.
static void SetUpTestSuite()
{
//shared_resource_ = new ...;
// If `shared_resource_` is **not deleted** in `TearDownTestSuite()`,
// reallocation should be prevented because `SetUpTestSuite()` may be called
// in subclasses of FooTest and lead to memory leak.
//
// if (shared_resource_ == nullptr) {
// shared_resource_ = new ...;
// }
}
// Per-test-suite tear-down.
// Called after the last test in this test suite.
// Can be omitted if not needed.
static void TearDownTestSuite()
{
//delete shared_resource_;
//shared_resource_ = nullptr;
}
// You can define per-test set-up logic as usual.
void SetUp() override { }
// You can define per-test tear-down logic as usual.
void TearDown() override { }
// Some expensive resource shared by all tests.
//static T* shared_resource_;
};
//T* FooTest::shared_resource_ = nullptr;
TEST(PartTest, CreationTests)
{
Matrix3 inertia;
inertia << 1, 0, 0,
0, 1, 0,
0, 0, 1;
Vector3 cm{1, 0, 0};
model::Part testPart("testPart",
inertia,
1.0,
cm);
Matrix3 inertia2;
inertia2 << 1, 0, 0,
0, 1, 0,
0, 0, 1;
Vector3 cm2{1, 0, 0};
model::Part testPart2("testPart2",
inertia2,
1.0,
cm2);
Vector3 R{2.0, 2.0, 2.0};
testPart.addChildPart(testPart2, R);
}

107
qtrocket.pro Normal file
View File

@ -0,0 +1,107 @@
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport
CONFIG += c++17
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
QtRocket.cpp \
gui/AboutWindow.cpp \
gui/AnalysisWindow.cpp \
gui/SimulationOptions.cpp \
gui/ThrustCurveMotorSelector.cpp \
gui/qcustomplot.cpp \
main.cpp \
gui/RocketTreeView.cpp \
gui/MainWindow.cpp \
model/MotorCase.cpp \
model/MotorModel.cpp \
model/MotorModelDatabase.cpp \
model/Rocket.cpp \
model/ThrustCurve.cpp \
sim/ConstantGravityModel.cpp \
sim/GravityModel.cpp \
sim/Propagator.cpp \
sim/SphericalGeoidModel.cpp \
sim/SphericalGravityModel.cpp \
sim/StateData.cpp \
sim/USStandardAtmosphere.cpp \
sim/WindModel.cpp \
utils/BinMap.cpp \
utils/CurlConnection.cpp \
utils/Logger.cpp \
utils/RSEDatabaseLoader.cpp \
utils/ThreadPool.cpp \
utils/ThrustCurveAPI.cpp \
utils/math/Quaternion.cpp \
utils/math/UtilityMathFunctions.cpp \
utils/math/Vector3.cpp
HEADERS += \
QtRocket.h \
gui/AboutWindow.h \
gui/AnalysisWindow.h \
gui/RocketTreeView.h \
gui/MainWindow.h \
gui/SimulationOptions.h \
gui/ThrustCurveMotorSelector.h \
gui/qcustomplot.h \
model/MotorCase.h \
model/MotorModel.h \
model/MotorModelDatabase.h \
model/Rocket.h \
model/ThrustCurve.h \
sim/AtmosphericModel.h \
sim/ConstantAtmosphere.h \
sim/ConstantGravityModel.h \
sim/DESolver.h \
sim/GeoidModel.h \
sim/GravityModel.h \
sim/Propagator.h \
sim/RK4Solver.h \
sim/SphericalGeoidModel.h \
sim/SphericalGravityModel.h \
sim/StateData.h \
sim/USStandardAtmosphere.h \
sim/WindModel.h \
utils/BinMap.h \
utils/CurlConnection.h \
utils/Logger.h \
utils/RSEDatabaseLoader.h \
utils/TSQueue.h \
utils/ThreadPool.h \
utils/ThrustCurveAPI.h \
utils/Triplet.h \
utils/math/Constants.h \
utils/math/Quaternion.h \
utils/math/UtilityMathFunctions.h \
utils/math/Vector3.h
FORMS += \
gui/AboutWindow.ui \
gui/AnalysisWindow.ui \
gui/MainWindow.ui \
gui/SimulationOptions.ui \
gui/ThrustCurveMotorSelector.ui
TRANSLATIONS += \
qtrocket_en_US.ts
CONFIG += lrelease
CONFIG += embed_translations
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
unix: CONFIG += link_pkgconfig
unix: PKGCONFIG += libcurl
unix: PKGCONFIG += fmt
unix: PKGCONFIG += jsoncpp
RESOURCES += \
qtrocket.qrc

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

View File

@ -1,40 +0,0 @@
#ifndef SIM_AERO_H
#define SIM_AERO_H
/// \cond
// C headers
// C++ headers
// 3rd party headers
/// \endcond
// qtrocket headers
#include "utils/math/MathTypes.h"
namespace sim
{
class Aero
{
public:
private:
Vector3 cp; /// center of pressure
double Cx; /// longitudinal coefficient
double Cy; /// These are probably the same for axial symmetric
double Cz; /// rockets. The coeffients in the y and z body directions
double Cl; // roll moment coefficient
double Cm; // pitch moment coefficient
double Cn; // yaw moment coefficient
double baseCd; // coefficient of drag due to base drag
double Cd; // total coeffient of drag
};
}
#endif // SIM_AERO_H

View File

@ -13,10 +13,6 @@ public:
virtual double getDensity(double altitude) = 0;
virtual double getPressure(double altitude) = 0;
virtual double getTemperature(double altitude) = 0;
virtual double getSpeedOfSound(double altitude) = 0;
virtual double getDynamicViscosity(double altitude) = 0;
};
} // namespace sim

View File

@ -1,28 +0,0 @@
add_library(sim
Aero.cpp
Aero.h
AtmosphericModel.h
ConstantAtmosphere.h
ConstantGravityModel.h
DESolver.h
Environment.h
GeoidModel.h
GravityModel.h
Propagator.cpp
Propagator.h
RK4Solver.h
SphericalGeoidModel.cpp
SphericalGeoidModel.h
SphericalGravityModel.cpp
SphericalGravityModel.h
StateData.h
USStandardAtmosphere.cpp
USStandardAtmosphere.h
WindModel.cpp
WindModel.h)
target_link_libraries(sim PRIVATE
utils)
# Unit tests
add_subdirectory(tests)

View File

@ -15,10 +15,6 @@ public:
double getDensity(double) override { return 1.225; }
double getPressure(double) override { return 101325.0; }
double getTemperature(double) override { return 288.15; }
double getSpeedOfSound(double) override { return 340.294; }
double getDynamicViscosity(double) override { return 1.78938e-5; }
};
} // namespace sim

View File

@ -0,0 +1,10 @@
#include "ConstantGravityModel.h"
namespace sim {
ConstantGravityModel::ConstantGravityModel()
{
}
} // namespace sim

View File

@ -3,21 +3,18 @@
// qtrocket headers
#include "sim/GravityModel.h"
#include "utils/math/MathTypes.h"
#include "utils/Triplet.h"
namespace sim {
class ConstantGravityModel : public GravityModel
{
public:
ConstantGravityModel() {}
ConstantGravityModel();
virtual ~ConstantGravityModel() {}
virtual ~ConstantGravityModel() {}
Vector3 getAccel(double, double, double) override
{
return Vector3(0.0, 0.0, -9.8);
}
TripletD getAccel(double, double, double) override { return TripletD(0.0, 0.0, -9.8); }
};
} // namespace sim

View File

@ -4,17 +4,14 @@
/// \cond
// C headers
// C++ headers
#include <utility>
#include <vector>
// 3rd party headers
/// \endcond
// qtrocket headers
namespace sim
{
template<typename T>
class DESolver
{
public:
@ -22,17 +19,7 @@ public:
virtual ~DESolver() {}
virtual void setTimeStep(double ts) = 0;
/**
* @brief
*
* @param curVal
* @param res
* @param t Optional parameter, but not used in QtRocket. Some generic solvers take time as
* a parameter to ODEs, but QtRocket's kinematic equations don't. Since I wrote
* the RK4 solver independently as a general tool, this interface is needed
* here unfortunately.
*/
virtual std::pair<T, T> step(T& state, T& rate) = 0;
virtual void step(double t, const std::vector<double>& curVal, std::vector<double>& res ) = 0;
};
} // namespace sim

View File

@ -1,111 +0,0 @@
#ifndef SIM_ENVIRONMENT_H
#define SIM_ENVIRONMENT_H
/// \cond
// C headers
// C++ headers
#include <algorithm>
#include <map>
#include <memory>
#include <vector>
#include <iterator>
// 3rd party headers
/// \endcond
// qtrocket headers
#include "sim/ConstantGravityModel.h"
#include "sim/SphericalGravityModel.h"
#include "sim/ConstantAtmosphere.h"
#include "sim/USStandardAtmosphere.h"
namespace sim
{
/**
* @brief Holds simulation environment information, such as the gravity model, atmosphere model,
* Geoid model
*
*/
class Environment
{
public:
Environment()
{
setGravityModel("Constant Gravity");
setAtmosphereModel("Constant Atmosphere");
}
~Environment() = default;
Environment(const Environment&) = delete;
Environment(Environment&&) = delete;
Environment& operator=(const Environment&) = delete;
Environment& operator=(Environment&&) = delete;
std::vector<std::string> getAvailableGravityModels()
{
std::vector<std::string> retVal(gravityModels.size());
std::transform(gravityModels.begin(), gravityModels.end(), std::back_inserter(retVal),
[](auto& i) { return i.first; });
return retVal;
}
std::vector<std::string> getAvailableAtmosphereModels()
{
std::vector<std::string> retVal(atmosphereModels.size());
std::transform(atmosphereModels.begin(), atmosphereModels.end(), std::back_inserter(retVal),
[](auto& i) { return i.first; });
return retVal;
}
void setGravityModel(const std::string& model)
{
if(model == "Constant Gravity")
{
gravityModel = model;
gravityModels[gravityModel].reset(new sim::ConstantGravityModel);
}
else if(model == "Spherical Gravity")
{
gravityModel = model;
gravityModels[gravityModel].reset(new sim::SphericalGravityModel);
}
}
void setAtmosphereModel(const std::string& model)
{
if(model == "Constant Atmosphere")
{
atmosphereModel = model;
atmosphereModels[atmosphereModel].reset(new sim::ConstantAtmosphere);
}
else if(model == "US Standard 1976")
{
atmosphereModel = model;
atmosphereModels[atmosphereModel].reset(new sim::USStandardAtmosphere);
}
}
std::shared_ptr<sim::AtmosphericModel> getAtmosphericModel()
{
auto retVal = atmosphereModels[atmosphereModel];
return retVal;
}
std::shared_ptr<sim::GravityModel> getGravityModel() { return gravityModels[gravityModel]; }
private:
std::map<std::string, std::shared_ptr<sim::AtmosphericModel>> atmosphereModels{
{"Constant Atmosphere", std::shared_ptr<sim::AtmosphericModel>()},
{"US Standard 1976", std::shared_ptr<sim::AtmosphericModel>()}};
std::map<std::string, std::shared_ptr<GravityModel>> gravityModels{
{"Constant Gravity", std::shared_ptr<sim::GravityModel>()},
{"Spherical Gravity", std::shared_ptr<sim::GravityModel>()}};
std::string gravityModel{"Constant Gravity"}; /// Constant Gravity Model is the default
std::string atmosphereModel{"Constant Atmosphere"}; /// Constant Atmosphere Model is the default
};
} // namespace sim
#endif // SIM_ENVIRONMENT_H

14
sim/GravityModel.cpp Normal file
View File

@ -0,0 +1,14 @@
#include "GravityModel.h"
namespace sim
{
GravityModel::GravityModel()
{
}
GravityModel::~GravityModel()
{
}
} // namespace sim

View File

@ -2,7 +2,7 @@
#define SIM_GRAVITYMODEL_H
// qtrocket headers
#include "utils/math/MathTypes.h"
#include "utils/Triplet.h"
namespace sim
{
@ -10,11 +10,11 @@ namespace sim
class GravityModel
{
public:
GravityModel() {}
virtual ~GravityModel() {}
GravityModel();
virtual ~GravityModel();
virtual Vector3 getAccel(double x, double y, double z) = 0;
Vector3 getAccel(const Vector3& t) { return this->getAccel(t.x(), t.y(), t.z()); }
virtual TripletD getAccel(double x, double y, double z) = 0;
TripletD getAccel(const TripletD& t) { return this->getAccel(t.x1, t.x2, t.x3); }
};
} // namespace sim

View File

@ -13,36 +13,47 @@
// qtrocket headers
#include "Propagator.h"
#include "QtRocket.h"
#include "model/Rocket.h"
#include "sim/RK4Solver.h"
#include "utils/Logger.h"
namespace sim
namespace sim {
Propagator::Propagator(Rocket* r)
: integrator(),
rocket(r)
{
Propagator::Propagator(std::shared_ptr<model::Propagatable> r)
: linearIntegrator(),
object(r),
saveStates(true),
timeStep(0.01)
{
// Linear velocity and acceleration
std::function<std::pair<Vector3, Vector3>(Vector3&, Vector3&)> linearODEs = [this](Vector3& state, Vector3& rate) -> std::pair<Vector3, Vector3>
{
Vector3 dPosition;
Vector3 dVelocity;
// dx/dt
dPosition = rate;
// dvx/dt
dVelocity = object->getForces(currentTime) / object->getMass(currentTime);
// This is a little strange, but I have to populate the integrator unique_ptr
// with reset. make_unique() doesn't work because the compiler can't seem to
// deduce the template parameters correctly, and I don't want to specify them
// manually either. RK4Solver constructor is perfectly capable of deducing it's
// template types, and it derives from DESolver, so we can just reset the unique_ptr
// and pass it a freshly allocated RK4Solver pointer
return std::make_pair(dPosition, dVelocity);
};
// The state vector has components of the form:
// (x, y, z, xdot, ydot, zdot, pitch, yaw, roll, pitchRate, yawRate, rollRate)
integrator.reset(new RK4Solver(
/* dx/dt */ [](double, const std::vector<double>& s) -> double {return s[3]; },
/* dy/dt */ [](double, const std::vector<double>& s) -> double {return s[4]; },
/* dz/dt */ [](double, const std::vector<double>& s) -> double {return s[5]; },
/* dvx/dt */ [this](double, const std::vector<double>& ) -> double { return getForceX() / getMass(); },
/* dvy/dt */ [this](double, const std::vector<double>& ) -> double { return getForceY() / getMass(); },
/* dvz/dt */ [this](double, const std::vector<double>& ) -> double { return getForceZ() / getMass(); },
/* dpitch/dt */ [](double, const std::vector<double>& s) -> double { return s[9]; },
/* dyaw/dt */ [](double, const std::vector<double>& s) -> double { return s[10]; },
/* droll/dt */ [](double, const std::vector<double>& s) -> double { return s[11]; },
/* dpitchRate/dt */ [this](double, const std::vector<double>& s) -> double { return (getTorqueP() - s[7] * s[8] * (getIroll() - getIyaw())) / getIpitch(); },
/* dyawRate/dt */ [this](double, const std::vector<double>& s) -> double { return (getTorqueQ() - s[6] * s[9] * (getIpitch() - getIroll())) / getIyaw(); },
/* drollRate/dt */ [this](double, const std::vector<double>& s) -> double { return (getTorqueR() - s[6] * s[7] * (getIyaw() - getIpitch())) / getIroll(); }));
linearIntegrator.reset(new RK4Solver<Vector3>(linearODEs));
linearIntegrator->setTimeStep(timeStep);
saveStates = true;
integrator->setTimeStep(timeStep);
saveStates = true;
}
Propagator::~Propagator()
@ -51,41 +62,61 @@ Propagator::~Propagator()
void Propagator::runUntilTerminate()
{
std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now();
std::chrono::steady_clock::time_point endTime;
std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now();
std::chrono::steady_clock::time_point endTime;
Vector3 currentPosition;
Vector3 currentVelocity;
Vector3 nextPosition;
Vector3 nextVelocity;
while(true)
{
currentPosition = object->getCurrentState().position;
currentVelocity = object->getCurrentState().velocity;
while(true)
{
// tempRes gets overwritten
integrator->step(currentTime, currentState, tempRes);
std::tie(nextPosition, nextVelocity) = linearIntegrator->step(currentPosition, currentVelocity);
std::swap(currentState, tempRes);
if(saveStates)
{
states.push_back(std::make_pair(currentTime, currentState));
}
if(rocket->terminateCondition(std::make_pair(currentTime, currentState)))
break;
StateData nextState;
nextState.position = nextPosition;
nextState.velocity = nextVelocity;
object->setCurrentState(nextState);
currentTime += timeStep;
}
endTime = std::chrono::steady_clock::now();
if(saveStates)
{
object->appendState(currentTime, nextState);
}
if(object->terminateCondition(currentTime))
break;
currentTime += timeStep;
}
endTime = std::chrono::steady_clock::now();
std::stringstream duration;
duration << "runUntilTerminate time (microseconds): ";
duration << std::chrono::duration_cast<std::chrono::microseconds>(endTime - startTime).count();
utils::Logger::getInstance()->debug(duration.str());
std::stringstream duration;
duration << "runUntilTerminate time (microseconds): ";
duration << std::chrono::duration_cast<std::chrono::microseconds>(endTime - startTime).count();
utils::Logger::getInstance()->debug(duration.str());
}
double Propagator::getMass()
{
return rocket->getMass();
}
double Propagator::getForceX()
{
QtRocket* qtrocket = QtRocket::getInstance();
return - qtrocket->getAtmosphereModel()->getDensity(currentState[3])/ 2.0 * 0.008107 * rocket->getDragCoefficient() * currentState[3]* currentState[3];
}
double Propagator::getForceY()
{
QtRocket* qtrocket = QtRocket::getInstance();
return -qtrocket->getAtmosphereModel()->getDensity(currentState[3]) / 2.0 * 0.008107 * rocket->getDragCoefficient() * currentState[4]* currentState[4];
}
double Propagator::getForceZ()
{
QtRocket* qtrocket = QtRocket::getInstance();
double gravity = (qtrocket->getGravityModel()->getAccel(currentState[0], currentState[1], currentState[2])).x3;
double airDrag = -qtrocket->getAtmosphereModel()->getDensity(currentState[3]) / 2.0 * 0.008107 * rocket->getDragCoefficient() * currentState[5]* currentState[5];
double thrust = rocket->getThrust(currentTime);
return gravity + airDrag + thrust;
}
double Propagator::getTorqueP() { return 0.0; }
double Propagator::getTorqueQ() { return 0.0; }
double Propagator::getTorqueR() { return 0.0; }
} // namespace sim

View File

@ -5,22 +5,17 @@
// C headers
// C++ headers
#include <memory>
#include <vector>
// 3rd party headers
/// \endcond
// qtrocket headers
#include "sim/RK4Solver.h"
#include "utils/math/MathTypes.h"
#include "sim/StateData.h"
#include "model/Propagatable.h"
#include "sim/DESolver.h"
// Forward declare
namespace model
{
class Rocket;
}
class QtRocket;
namespace sim
@ -29,17 +24,22 @@ namespace sim
class Propagator
{
public:
Propagator(std::shared_ptr<model::Propagatable> o);
Propagator(Rocket* r);
~Propagator();
void setInitialState(const StateData& initialState)
void setInitialState(const std::vector<double>& initialState)
{
object->setInitialState(initialState);
currentState.resize(initialState.size());
for(std::size_t i = 0; i < initialState.size(); ++i)
{
currentState[i] = initialState[i];
}
}
const StateData& getCurrentState() const
const std::vector<double>& getCurrentState() const
{
return object->getCurrentState();
return currentState;
}
void runUntilTerminate();
@ -49,21 +49,38 @@ public:
saveStates = s;
}
void setCurrentTime(double t) { currentTime = t; }
const std::vector<std::pair<double, std::vector<double>>>& getStates() const { return states; }
void setTimeStep(double ts) { timeStep = ts; }
void setSaveStats(bool s) { saveStates = s; }
private:
double getMass();
double getForceX();
double getForceY();
double getForceZ();
std::unique_ptr<sim::RK4Solver<Vector3>> linearIntegrator;
// std::unique_ptr<sim::RK4Solver<Quaternion>> orientationIntegrator;
double getTorqueP();
double getTorqueQ();
double getTorqueR();
std::shared_ptr<model::Propagatable> object;
double getIpitch() { return 1.0; }
double getIyaw() { return 1.0; }
double getIroll() { return 1.0; }
//private:
std::unique_ptr<sim::DESolver> integrator;
Rocket* rocket;
std::vector<double> currentState{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
std::vector<double> tempRes{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
bool saveStates{true};
double currentTime{0.0};
double timeStep{0.01};
std::vector<std::pair<double, std::vector<double>>> states;
};
} // namespace sim

View File

@ -4,8 +4,10 @@
/// \cond
// C headers
// C++ headers
#include <cmath>
#include <functional>
#include <limits>
#include <vector>
// 3rd party headers
@ -14,80 +16,84 @@
// qtrocket headers
#include "sim/DESolver.h"
#include "utils/Logger.h"
#include "utils/math/MathTypes.h"
namespace sim {
/**
* @brief Runge-Kutta 4th order coupled ODE solver.
* @note This was written outside of the context of QtRocket, and it is very generic. There are
* some features of this solver that are note used by QtRocket, for example, it can solve
* and arbitrarily large system of coupled ODEs, but QtRocket only makes use of a system
* of size 6 (x, y, z, xDot, yDot, zDot) at a time.
*
* @tparam Ts
*/
template<typename T>
class RK4Solver : public DESolver<T>
template<typename... Ts>
class RK4Solver : public DESolver
{
public:
RK4Solver(std::function<std::pair<T, T>(T&, T&)> func)
RK4Solver(Ts... funcs)
{
// This only works for Eigen Vector types.
// TODO: Figure out how to make this slightly more generic, but for now
// we're only using this for Vector3 and Quaternion types
static_assert(std::is_same<T, Vector3>::value
|| std::is_same<T, Quaternion>::value,
"You can only use Vector3 or Quaternion valued functions in RK4Solver");
odes = func;
(odes.push_back(funcs), ...);
temp.resize(sizeof...(Ts));
}
virtual ~RK4Solver() {}
void setTimeStep(double inTs) override { dt = inTs; halfDT = dt / 2.0; }
std::pair<T, T> step(T& state, T& rate) override
void step(double t, const std::vector<double>& curVal, std::vector<double>& res) override
{
std::pair<T, T> res;
if(dt == std::numeric_limits<double>::quiet_NaN())
{
utils::Logger::getInstance()->error("Calling RK4Solver without setting dt first is an error");
return res;
res[0] = std::numeric_limits<double>::quiet_NaN();
}
std::tie(k1State, k1Rate) = odes(state, rate);
for(size_t i = 0; i < len; ++i)
{
k1[i] = odes[i](t, curVal);
}
// compute k2 values. This involves stepping the current values forward a half-step
// based on k1, so we do the stepping first
std::tie(tempState, tempRate) = std::make_pair(state + k1State*halfDT, rate + k1Rate*halfDT);
std::tie(k2State, k2Rate) = odes(tempState, tempRate);
for(size_t i = 0; i < len; ++i)
{
temp[i] = curVal[i] + k1[i]*dt / 2.0;
}
for(size_t i = 0; i < len; ++i)
{
k2[i] = odes[i](t + halfDT, temp);
}
// repeat for k3
for(size_t i = 0; i < len; ++i)
{
temp[i] = curVal[i] + k2[i]*dt / 2.0;
}
for(size_t i = 0; i < len; ++i)
{
k3[i] = odes[i](t + halfDT, temp);
}
std::tie(tempState, tempRate) = std::make_pair(state + k2State*halfDT, rate + k2Rate*halfDT);
std::tie(k3State, k3Rate) = odes(tempState, tempRate);
// now k4
for(size_t i = 0; i < len; ++i)
{
temp[i] = curVal[i] + k3[i]*dt;
}
for(size_t i = 0; i < len; ++i)
{
k4[i] = odes[i](t + dt, temp);
}
std::tie(tempState, tempRate) = std::make_pair(state + k3State*dt, rate + k3Rate*dt);
std::tie(k4State, k4Rate) = odes(tempState, tempRate);
// now compute the result
for(size_t i = 0; i < len; ++i)
{
res[i] = curVal[i] + (dt / 6.0)*(k1[i] + 2.0*k2[i] + 2.0*k3[i] + k4[i]);
}
res = std::make_pair(state + (dt / 6.0)*(k1State + 2.0*k2State + 2.0*k3State + k4State),
rate + (dt / 6.0)*(k1Rate + 2.0*k2Rate + 2.0*k3Rate + k4Rate));
return res;
}
private:
std::function<std::pair<T, T>(T&, T&)> odes;
std::vector<std::function<double(double, const std::vector<double>&)>> odes;
T k1State;
T k2State;
T k3State;
T k4State;
T k1Rate;
T k2Rate;
T k3Rate;
T k4Rate;
static constexpr size_t len = sizeof...(Ts);
double k1[len];
double k2[len];
double k3[len];
double k4[len];
T tempState;
T tempRate;
std::vector<double> temp;
double dt = std::numeric_limits<double>::quiet_NaN();
double halfDT = 0.0;

View File

@ -25,7 +25,7 @@ SphericalGravityModel::~SphericalGravityModel()
}
Vector3 SphericalGravityModel::getAccel(double x, double y, double z)
TripletD SphericalGravityModel::getAccel(double x, double y, double z)
{
// Convert x, y, z from meters to km. This is to avoid potential precision losses
// with using the earth's gravitation parameter in meters (14 digit number).
@ -43,7 +43,7 @@ Vector3 SphericalGravityModel::getAccel(double x, double y, double z)
double ay = accelFactor * y_km * 1000.0;
double az = accelFactor * z_km * 1000.0;
return Vector3(ax, ay, az);
return TripletD(ax, ay, az);
}

View File

@ -2,7 +2,7 @@
#define SIM_SPHERICALGRAVITYMODEL_H
// qtrocket headers
#include "sim/GravityModel.h"
#include "GravityModel.h"
namespace sim
{
@ -13,7 +13,7 @@ public:
SphericalGravityModel();
virtual ~SphericalGravityModel();
Vector3 getAccel(double x, double y, double z) override;
TripletD getAccel(double x, double y, double z) override;
};
} // namespace sim

6
sim/StateData.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "StateData.h"
StateData::StateData()
{
}

View File

@ -1,15 +1,9 @@
#ifndef STATEDATA_H
#define STATEDATA_H
/// \cond
// C headers
// C++ headers
#include <vector>
// 3rd party headers
/// \endcond
// qtrocket headers
#include "utils/math/MathTypes.h"
#include "utils/math/Vector3.h"
#include "utils/math/Quaternion.h"
/**
* @brief The StateData class holds physical state data. Things such as position, velocity,
@ -19,76 +13,20 @@
class StateData
{
public:
StateData() {}
~StateData() {}
StateData(const StateData&) = default;
StateData(StateData&&) = default;
StateData& operator=(const StateData& rhs)
{
if(this != &rhs)
{
position = rhs.position;
velocity = rhs.velocity;
orientation = rhs.orientation;
orientationRate = rhs.orientationRate;
dcm = rhs.dcm;
eulerAngles = rhs.eulerAngles;
}
return *this;
}
StateData& operator=(StateData&& rhs)
{
if(this != &rhs)
{
position = std::move(rhs.position);
velocity = std::move(rhs.velocity);
orientation = std::move(rhs.orientation);
orientationRate = std::move(rhs.orientationRate);
dcm = std::move(rhs.dcm);
eulerAngles = std::move(rhs.eulerAngles);
}
return *this;
}
std::vector<double> getPosStdVector() const
{
return std::vector<double>{position[0], position[1], position[2]};
}
std::vector<double> getVelStdVector() const
{
return std::vector<double>{velocity[0], velocity[1], velocity[2]};
}
StateData();
/// TODO: Put these behind an interface
//Vector3 getPosition() const
//{
// return position;
//}
private:
math::Vector3 position{0.0, 0.0, 0.0};
math::Vector3 velocity{0.0, 0.0, 0.0};
//Vector3 getVelocity() const
//{
// return velocity;
//}
// private:
math::Quaternion orientation{0.0, 0.0, 0.0, 0.0}; // roll, pitch, yaw
math::Quaternion orientationRate{0.0, 0.0, 0.0, 0.0}; // roll-rate, pitch-rate, yaw-rate
// Necessary?
//math::Vector3 orientationAccel;
// Intended to be used as world state data
Vector3 position{0.0, 0.0, 0.0};
Vector3 velocity{0.0, 0.0, 0.0};
// Orientation of body coordinates w.r.t. world coordinates
Quaternion orientation{0.0, 0.0, 0.0, 0.0}; /// (vector, scalar)
Quaternion orientationRate{0.0, 0.0, 0.0, 0.0}; /// (vector, scalar)
Matrix3 dcm{{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
/// Euler angles are yaw-pitch-roll, and (3-2-1) order
/// yaw - psi
/// pitch - theta
/// roll - phi
Vector3 eulerAngles{0.0, 0.0, 0.0};
// This is an array because the integrator expects it
double data[6];
};

View File

@ -10,7 +10,6 @@
// qtrocket headers
#include "sim/USStandardAtmosphere.h"
#include "utils/math/Constants.h"
#include "utils/math/UtilityMathFunctions.h"
using namespace utils::math;
@ -18,9 +17,9 @@ namespace sim
{
// Populate static data
utils::Bin initTemps()
utils::BinMap initTemps()
{
utils::Bin map;
utils::BinMap map;
map.insert(std::make_pair(0.0, 288.15));
map.insert(std::make_pair(11000.0, 216.65));
map.insert(std::make_pair(20000.0, 216.65));
@ -33,9 +32,9 @@ utils::Bin initTemps()
}
utils::Bin initLapseRates()
utils::BinMap initLapseRates()
{
utils::Bin map;
utils::BinMap map;
map.insert(std::make_pair(0.0, 0.0065));
map.insert(std::make_pair(11000.0, 0.0));
map.insert(std::make_pair(20000.0, -0.001));
@ -47,9 +46,9 @@ utils::Bin initLapseRates()
return map;
}
utils::Bin initDensities()
utils::BinMap initDensities()
{
utils::Bin map;
utils::BinMap map;
map.insert(std::make_pair(0.0, 1.225));
map.insert(std::make_pair(11000.0, 0.36391));
map.insert(std::make_pair(20000.0, 0.08803));
@ -61,24 +60,11 @@ utils::Bin initDensities()
return map;
}
utils::Bin initPressures()
{
utils::Bin map;
map.insert(std::make_pair(0.0, 101325));
map.insert(std::make_pair(11000.0, 22632.1));
map.insert(std::make_pair(20000.0, 5474.89));
map.insert(std::make_pair(32000.0, 868.02));
map.insert(std::make_pair(47000.0, 110.91));
map.insert(std::make_pair(51000.0, 66.94));
map.insert(std::make_pair(71000.0, 3.96));
utils::BinMap USStandardAtmosphere::temperatureLapseRate(initLapseRates());
utils::BinMap USStandardAtmosphere::standardTemperature(initTemps());
utils::BinMap USStandardAtmosphere::standardDensity(initDensities());
return map;
}
utils::Bin USStandardAtmosphere::temperatureLapseRate(initLapseRates());
utils::Bin USStandardAtmosphere::standardTemperature(initTemps());
utils::Bin USStandardAtmosphere::standardDensity(initDensities());
utils::Bin USStandardAtmosphere::standardPressure(initPressures());
USStandardAtmosphere::USStandardAtmosphere()
{
@ -92,64 +78,31 @@ USStandardAtmosphere::~USStandardAtmosphere()
double USStandardAtmosphere::getDensity(double altitude)
{
if(utils::math::floatingPointEqual(temperatureLapseRate[altitude], 0.0))
if(temperatureLapseRate[altitude] == 0.0)
{
return standardDensity[altitude] * std::exp(
(-Constants::g0 * Constants::airMolarMass * (altitude - standardDensity.getBinBase(altitude)))
return standardDensity[altitude] * std::exp((-Constants::g0 * Constants::airMolarMass * (altitude - standardDensity.getBinBase(altitude)))
/ (Constants::Rstar * standardTemperature[altitude]));
}
else
{
double base = (standardTemperature[altitude] - temperatureLapseRate[altitude] *
(altitude - standardDensity.getBinBase(altitude))) / standardTemperature[altitude];
double base = standardTemperature[altitude] /
(standardTemperature[altitude] + temperatureLapseRate[altitude] * (altitude - standardDensity.getBinBase(altitude)));
double exponent = (Constants::g0 * Constants::airMolarMass) /
(Constants::Rstar * temperatureLapseRate[altitude]) - 1.0;
return standardDensity[altitude] * std::pow(base, exponent);
}
}
double USStandardAtmosphere::getTemperature(double altitude)
{
double baseTemp = standardTemperature[altitude];
double baseAltitude = standardTemperature.getBinBase(altitude);
return baseTemp - (altitude - baseAltitude) * temperatureLapseRate[altitude];
}
double USStandardAtmosphere::getPressure(double altitude)
{
if(utils::math::floatingPointEqual(temperatureLapseRate[altitude], 0.0))
{
return standardPressure[altitude] * std::exp(
(-Constants::g0 * Constants::airMolarMass * (altitude - standardPressure.getBinBase(altitude)))
/ (Constants::Rstar * standardTemperature[altitude]));
}
else
{
double base = (standardTemperature[altitude] - temperatureLapseRate[altitude] *
(altitude - standardPressure.getBinBase(altitude))) / standardTemperature[altitude];
double exponent = (Constants::g0 * Constants::airMolarMass) /
double exponent = 1 + (Constants::g0 * Constants::airMolarMass) /
(Constants::Rstar * temperatureLapseRate[altitude]);
return standardPressure[altitude] * std::pow(base, exponent);
return standardDensity[altitude] * std::pow(base, exponent);
}
}
double USStandardAtmosphere::getSpeedOfSound(double altitude)
double USStandardAtmosphere::getTemperature(double /*altitude*/)
{
return std::sqrt( (Constants::gamma * Constants::Rstar * getTemperature(altitude))
/
Constants::airMolarMass);
return 0.0;
}
double USStandardAtmosphere::getDynamicViscosity(double altitude)
double USStandardAtmosphere::getPressure(double /* altitude */)
{
double temperature = getTemperature(altitude);
return (Constants::beta * std::pow(temperature, 1.5)) / ( temperature + Constants::S);
return 0.0;
}
} // namespace sim

View File

@ -3,7 +3,7 @@
// qtrocket headers
#include "sim/AtmosphericModel.h"
#include "utils/Bin.h"
#include "utils/BinMap.h"
namespace sim
{
@ -28,15 +28,10 @@ public:
double getPressure(double altitude) override;
double getTemperature(double altitude) override;
double getSpeedOfSound(double altitude) override;
double getDynamicViscosity(double altitude) override;
private:
static utils::Bin temperatureLapseRate;
static utils::Bin standardTemperature;
static utils::Bin standardDensity;
static utils::Bin standardPressure;
static utils::BinMap temperatureLapseRate;
static utils::BinMap standardTemperature;
static utils::BinMap standardDensity;
};

View File

@ -13,9 +13,9 @@ WindModel::~WindModel()
{
}
Vector3 WindModel::getWindSpeed(double /* x */, double /* y */ , double /* z */)
TripletD WindModel::getWindSpeed(double /* x */, double /* y */ , double /* z */)
{
return Vector3(0.0, 0.0, 0.0);
return TripletD(0.0, 0.0, 0.0);
}
} // namespace sim

View File

@ -2,7 +2,7 @@
#define SIM_WINDMODEL_H
// qtrocket headers
#include "utils/math/MathTypes.h"
#include "utils/Triplet.h"
namespace sim
{
@ -13,7 +13,7 @@ public:
WindModel();
virtual ~WindModel();
virtual Vector3 getWindSpeed(double x, double y, double z);
virtual TripletD getWindSpeed(double x, double y, double z);
};

View File

@ -1,15 +0,0 @@
add_executable(sim_tests
USStandardAtmosphereTests.cpp
)
target_link_libraries(sim_tests
sim
GTest::gtest_main
)
include(GoogleTest)
gtest_discover_tests(sim_tests)
add_test(NAME qtrocket_sim_tests COMMAND sim_tests)

View File

@ -1,90 +0,0 @@
#include <gtest/gtest.h>
#include "sim/USStandardAtmosphere.h"
TEST(USStandardAtmosphereTests, DensityTests)
{
sim::USStandardAtmosphere atmosphere;
// Test that the calucated values are with 0.1% of the published values in the NOAA report
EXPECT_NEAR(atmosphere.getDensity(0.0) / 1.225, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getDensity(1000.0) / 1.112, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getDensity(2000.0) / 1.007, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getDensity(3000.0) / 0.9093, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getDensity(4000.0) / 0.8194, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getDensity(5000.0) / 0.7364, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getDensity(6000.0) / 0.6601, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getDensity(7000.0) / 0.5900, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getDensity(15000.0) / 0.19367, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getDensity(20000.0) / 0.088035, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getDensity(25000.0) / 0.039466, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getDensity(30000.0) / 0.018012, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getDensity(40000.0) / 0.0038510, 1.0, 0.001);
// These are generally accurate to ~0.5%. Slight deviation of calculated
// density from the given density in the report table
EXPECT_NEAR(atmosphere.getDensity(8000.0) / 0.52579, 1.0, 0.005);
EXPECT_NEAR(atmosphere.getDensity(9000.0) / 0.46706, 1.0, 0.005);
EXPECT_NEAR(atmosphere.getDensity(10000.0) / 0.41351, 1.0, 0.005);
EXPECT_NEAR(atmosphere.getDensity(50000.0) / 0.00097752, 1.0, 0.005);
EXPECT_NEAR(atmosphere.getDensity(60000.0) / 0.00028832, 1.0, 0.005);
EXPECT_NEAR(atmosphere.getDensity(70000.0) / 0.000074243, 1.0, 0.005);
EXPECT_NEAR(atmosphere.getDensity(80000.0) / 0.000015701, 1.0, 0.005);
}
TEST(USStandardAtmosphereTests, PressureTests)
{
sim::USStandardAtmosphere atmosphere;
// Test that the calucated values are with 0.1% of the published values in the NOAA report
EXPECT_NEAR(atmosphere.getPressure(0.0) / 101325.0, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(1000.0) / 89876.0, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(2000.0) / 79501.0, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(3000.0) / 70108.0, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(4000.0) / 61640.0, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(5000.0) / 54019.0, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(6000.0) / 47181.0, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(7000.0) / 41060.0, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(8000.0) / 35599.0, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(9000.0) / 30742.0, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(10000.0) / 26436.0, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(15000.0) / 12044.0, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(20000.0) / 5474.8, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(25000.0) / 2511.0, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(30000.0) / 1171.8, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(40000.0) / 277.52, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(50000.0) / 75.944, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(60000.0) / 20.314, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(70000.0) / 4.6342, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getPressure(80000.0) / 0.88627, 1.0, 0.001);
}
TEST(USStandardAtmosphereTests, TemperatureTests)
{
sim::USStandardAtmosphere atmosphere;
// Test that the calucated values are with 0.1% of the published values in the NOAA report
EXPECT_NEAR(atmosphere.getTemperature(0.0) / 288.15, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(1000.0) / 281.651, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(2000.0) / 275.154, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(3000.0) / 268.659, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(4000.0) / 262.166, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(5000.0) / 255.676, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(6000.0) / 249.187, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(7000.0) / 242.7, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(8000.0) / 236.215, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(9000.0) / 229.733, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(10000.0) / 223.252, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(15000.0) / 216.65, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(20000.0) / 216.65, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(25000.0) / 221.552, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(30000.0) / 226.509, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(40000.0) / 251.05, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(50000.0) / 270.65, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(60000.0) / 245.45, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(70000.0) / 217.450, 1.0, 0.001);
EXPECT_NEAR(atmosphere.getTemperature(80000.0) / 196.650, 1.0, 0.001);
}

View File

@ -3,14 +3,15 @@
// C headers
// C++ headers
#include <algorithm>
#include <format>
#include <sstream>
#include <stdexcept>
// 3rd party headers
#include <fmt/core.h>
/// \endcond
// qtrocket headers
#include "Bin.h"
#include "BinMap.h"
// TODO: Check on the availability of this in Clang.
// Replace libfmt with format when LLVM libc++ supports it
@ -19,33 +20,33 @@
namespace utils
{
Bin::Bin()
BinMap::BinMap()
: bins()
{
}
Bin::Bin(Bin&& o)
BinMap::BinMap(BinMap&& o)
: bins(std::move(o.bins))
{
}
Bin::~Bin()
BinMap::~BinMap()
{
}
// TODO: Very low priority, but if anyone wants to make this more efficient it could be
// interesting
void Bin::insert(const std::pair<double, double>& toInsert)
void BinMap::insert(const std::pair<double, double>& toInsert)
{
bins.push_back(toInsert);
std::sort(bins.begin(), bins.end(),
[](const auto& a, const auto& b){ return a.first < b.first; });
}
double Bin::operator[](double key)
double BinMap::operator[](double key)
{
auto iter = bins.begin();
// If the key is less than the lowest bin value, then it is out of range
@ -55,12 +56,12 @@ double Bin::operator[](double key)
if(key < iter->first)
{
throw std::out_of_range(
std::format("{} less than lower bound {} of BinMap", key, iter->first));
fmt::format("{} less than lower bound {} of BinMap", key, iter->first));
}
// Increment it and start searching If we reach the end without finding an existing key
// greater than our search term, then we've just hit the last bin and return that
iter++;
double retVal = bins.back().second;
double retVal = bins.end()->second;
while(iter != bins.end())
{
if(key < iter->first)
@ -73,7 +74,7 @@ double Bin::operator[](double key)
return retVal;
}
double Bin::getBinBase(double key)
double BinMap::getBinBase(double key)
{
auto iter = bins.begin();
// If the key is less than the lowest bin value, then it is out of range
@ -83,7 +84,7 @@ double Bin::getBinBase(double key)
if(key < iter->first)
{
throw std::out_of_range(
std::format("{} less than lower bound {} of BinMap", key, iter->first));
fmt::format("{} less than lower bound {} of BinMap", key, iter->first));
}
// Increment it and start searching If we reach the end without finding an existing key
// greater than our search term, then we've just hit the last bin and return that

View File

@ -1,5 +1,5 @@
#ifndef UTILS_BIN_H
#define UTILS_BIN_H
#ifndef UTILS_BINMAP_H
#define UTILS_BINMAP_H
/// \cond
// C headers
@ -22,12 +22,12 @@ namespace utils {
* @todo Make this class behave more like a proper STL container. Templetize it for one
*
*/
class Bin
class BinMap
{
public:
Bin();
Bin(Bin&& o);
~Bin();
BinMap();
BinMap(BinMap&& o);
~BinMap();
void insert(const std::pair<double, double>& toInsert);
double operator[](double key);
@ -40,4 +40,4 @@ private:
} // namespace utils
#endif // UTILS_BIN_H
#endif // UTILS_BINMAP_H

View File

@ -1,30 +0,0 @@
add_library(utils
Bin.cpp
Bin.h
CurlConnection.cpp
CurlConnection.h
Logger.cpp
Logger.h
MotorModelDatabase.cpp
MotorModelDatabase.h
RSEDatabaseLoader.cpp
RSEDatabaseLoader.h
ThreadPool.cpp
ThreadPool.h
ThrustCurveAPI.cpp
ThrustCurveAPI.h
TSQueue.h
math/Constants.h
math/MathTypes.h
math/UtilityMathFunctions.h)
#target_include_directories(utils PRIVATE
# ${Boost_INCLUDE_DIRS})
target_link_libraries(utils PUBLIC
libcurl
Boost::property_tree
jsoncpp_static
eigen)

View File

@ -40,29 +40,22 @@ void Logger::log(std::string_view msg, const LogLevel& lvl)
// all levels at or lower than the current level.
switch(currentLevel)
{
case PERF_:
if(lvl == PERF_)
{
outFile << "[PERF] " << msg << std::endl;
std::cout << "[PERF] " << msg << "\n";
}
[[fallthrough]];
case DEBUG_:
if(lvl == DEBUG_)
case DEBUG:
if(lvl == DEBUG)
{
outFile << "[DEBUG] " << msg << std::endl;
std::cout << "[DEBUG] " << msg << "\n";
}
[[fallthrough]];
case INFO_:
if(lvl == INFO_)
case INFO:
if(lvl == INFO)
{
outFile << "[INFO] " << msg << std::endl;
std::cout << "[INFO] " << msg << "\n";
}
[[fallthrough]];
case WARN_:
if(lvl == WARN_)
case WARN:
if(lvl == WARN)
{
outFile << "[WARN] " << msg << std::endl;
std::cout << "[WARN] " << msg << "\n";
@ -71,7 +64,7 @@ void Logger::log(std::string_view msg, const LogLevel& lvl)
// Regardless of what level is set, ERROR is always logged, so
// rather than explicitly check for the ERROR case, we just use default case
default:
if(lvl == ERROR_)
if(lvl == ERROR)
{
outFile << "[ERROR] " << msg << std::endl;
std::cout << "[ERROR] " << msg << "\n";

View File

@ -22,11 +22,10 @@ class Logger
public:
enum LogLevel
{
ERROR_,
WARN_,
INFO_,
DEBUG_,
PERF_
ERROR,
WARN,
INFO,
DEBUG
};
static Logger* getInstance();
@ -39,11 +38,16 @@ public:
void setLogLevel(const LogLevel& lvl);
inline void error(std::string_view msg) { log(msg, ERROR_); }
inline void warn(std::string_view msg) { log(msg, WARN_); }
inline void info(std::string_view msg) { log(msg, INFO_); }
inline void debug(std::string_view msg) { log(msg, DEBUG_); }
inline void perf(std::string_view msg) { log(msg, PERF_); }
/*
std::function<void(std::string_view)> error;
std::function<void(std::string_view)> warn;
std::function<void(std::string_view)> info;
std::function<void(std::string_view)> debug;
*/
inline void error(std::string_view msg) { log(msg, ERROR); }
inline void warn(std::string_view msg) { log(msg, WARN); }
inline void info(std::string_view msg) { log(msg, INFO); }
inline void debug(std::string_view msg) { log(msg, DEBUG); }
void log(std::ostream& o, const std::string& msg);

View File

@ -1,134 +0,0 @@
// class header
#include "utils/MotorModelDatabase.h"
/// \cond
// C headers
// C++ headers
// 3rd party headers
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
/// \endcond
// qtrocket project headers
#include "QtRocket.h"
namespace utils
{
MotorModelDatabase::MotorModelDatabase()
: motorModelMap()
{
}
MotorModelDatabase::~MotorModelDatabase()
{
}
void MotorModelDatabase::addMotorModel(const model::MotorModel& m)
{
utils::Logger* logger = QtRocket::getInstance()->getLogger();
std::string name = m.data.commonName;
if(motorModelMap.find(name) != motorModelMap.end())
{
logger->debug("Replacing MotorModel " + name + " in MotorModelDatabase");
}
else
{
logger->info("Adding MotorModel " + name + " to MotorModelDatabase");
}
motorModelMap[name] = m;
}
void MotorModelDatabase::addMotorModels(const std::vector<model::MotorModel>& models)
{
utils::Logger* logger = QtRocket::getInstance()->getLogger();
for(const auto& i : models)
{
addMotorModel(i);
}
}
std::optional<model::MotorModel> MotorModelDatabase::getMotorModel(const std::string& name)
{
auto mm = motorModelMap.find(name);
if(mm == motorModelMap.end())
{
Logger::getInstance()->debug("Unable to locate " + name + " in MotorModel database");
return std::nullopt;
}
else
{
Logger::getInstance()->debug("Retrieved " + name + " from MotorModel database");
return motorModelMap[name];
}
}
void MotorModelDatabase::saveMotorDatabase(const std::string& filename)
{
namespace pt = boost::property_tree;
// top-level tree
pt::ptree tree;
tree.put("QtRocketMotorDatabase.<xmlattr>.version", "0.1");
for(const auto& i : motorModelMap)
{
pt::ptree motor;
const auto& m = i.second;
motor.put("<xmlattr>.name", m.data.commonName);
motor.put("availability", m.data.availability.str());
motor.put("avgThrust", m.data.avgThrust);
motor.put("burnTime", m.data.burnTime);
motor.put("certOrg", m.data.certOrg.str());
motor.put("commonName", m.data.commonName);
motor.put("designation", m.data.designation);
motor.put("diameter", m.data.diameter);
motor.put("impulseClass", m.data.impulseClass);
motor.put("infoUrl", m.data.infoUrl);
motor.put("length", m.data.length);
motor.put("manufacturer", m.data.manufacturer.str());
motor.put("maxThrust", m.data.maxThrust);
motor.put("motorIdTC", m.data.motorIdTC);
motor.put("propType", m.data.propType);
motor.put("sparky", m.data.sparky ? "true" : "false");
motor.put("totalImpulse", m.data.totalImpulse);
motor.put("totalWeight", m.data.totalWeight);
motor.put("type", m.data.type.str());
motor.put("lastUpdated", m.data.lastUpdated);
// delays tag is in the form of a csv string
std::stringstream delays;
for (std::size_t i = 0; i < m.data.delays.size() - 1; ++i)
{
delays << std::to_string(m.data.delays[i]) << ",";
}
delays << std::to_string(m.data.delays[m.data.delays.size() - 1]);
motor.put("delays", delays.str());
// thrust data
{
pt::ptree tc;
std::vector<std::pair<double, double>> thrust = m.getThrustCurve().getThrustCurveData();
for(const auto& j : thrust)
{
pt::ptree thrustNode;
thrustNode.put("<xmlattr>.time", j.first);
thrustNode.put("<xmlattr>.force", j.second);
tc.add_child("thrust", thrustNode);
}
motor.add_child("thrustCurve", tc);
}
tree.add_child("QtRocketMotorDatabase.MotorModels.motor", motor);
}
pt::xml_writer_settings<std::string> settings(' ', 2);
pt::write_xml(filename, tree, std::locale(), settings);
}
void MotorModelDatabase::loadMotorDatabase(const std::string& filename)
{
}
} // namespace utils

View File

@ -1,77 +0,0 @@
#ifndef UTILS_MOTORMODELDATABASE_H
#define UTILS_MOTORMODELDATABASE_H
/// \cond
// C headers
// C++ headers
#include <vector>
#include <string>
#include <map>
#include <optional>
// 3rd party headers
/// \endcond
// qtrocket headers
#include "model/MotorModel.h"
namespace utils
{
/**
* @brief MotorModelDatabase is a simple storage, search, and retrieval mechanism for Model Rocket
* motors.
*
*/
class MotorModelDatabase
{
public:
MotorModelDatabase();
~MotorModelDatabase();
// No copies
MotorModelDatabase(const MotorModelDatabase&) = delete;
MotorModelDatabase(MotorModelDatabase&&) = delete;
MotorModelDatabase& operator=(const MotorModelDatabase&) = delete;
MotorModelDatabase& operator=(MotorModelDatabase&&) = delete;
/**
* @brief Adds a single MotorModel to the database. If that MotorModel already exists, it is
* replaced.
*
* @param model MotorModel to add
*
*/
void addMotorModel(const model::MotorModel& m);
/**
* @brief Adds multiple motor models at once. Any duplicates already in the datbase are replaced.
*
* @param model MotorModels to add
*
*/
void addMotorModels(const std::vector<model::MotorModel>& models);
/**
* @brief Get the Motor Model by Common Name
*
* @param name Motor Common name
* @return std::optional<model::MotorModel>
*/
std::optional<model::MotorModel> getMotorModel(const std::string& name);
void saveMotorDatabase(const std::string& filename);
void loadMotorDatabase(const std::string& filename);
private:
// The "database" is really just a map. :)
/// motorModelMap is keyed off of the motor commonName
std::map<std::string, model::MotorModel> motorModelMap;
};
} // namespace utils
#endif // UTILS_MOTORMODELDATABASE_H

View File

@ -3,9 +3,7 @@
// C headers
// C++ headers
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
// 3rd party headers
#include <boost/property_tree/xml_parser.hpp>
@ -14,7 +12,6 @@
// qtrocket headers
#include "utils/RSEDatabaseLoader.h"
#include "QtRocket.h"
#include "Logger.h"
namespace utils {
@ -36,19 +33,6 @@ RSEDatabaseLoader::RSEDatabaseLoader(const std::string& filename)
RSEDatabaseLoader::~RSEDatabaseLoader()
{}
model::MotorModel RSEDatabaseLoader::getMotorModelByName(const std::string &name)
{
auto mm = std::find_if(motors.begin(), motors.end(),
[&name](const auto& i) { return name == i.data.commonName; });
if(mm == motors.end())
{
Logger::getInstance()->error("Unable to locate " + name + " in RSE database");
return model::MotorModel();
}
return *mm;
}
void RSEDatabaseLoader::buildAndAppendMotorModel(boost::property_tree::ptree& v)
{
model::MotorModel::MetaData mm;
@ -59,16 +43,6 @@ void RSEDatabaseLoader::buildAndAppendMotorModel(boost::property_tree::ptree& v)
mm.commonName = v.get<std::string>("<xmlattr>.code", "");
// mm.delays = extract vector from csv list
std::string delays = v.get<std::string>("<xmlattr>.delays", "1000");
std::size_t pos{0};
std::string tok;
while ((pos = delays.find(",")) != std::string::npos)
{
tok = delays.substr(0, pos);
mm.delays.push_back(std::atoi(tok.c_str()));
delays.erase(0, pos + 1);
}
mm.delays.push_back(std::atoi(delays.c_str()));
// mm.designation = What is this?
@ -82,8 +56,7 @@ void RSEDatabaseLoader::buildAndAppendMotorModel(boost::property_tree::ptree& v)
mm.length = v.get<double>("<xmlattr>.len", 0.0);
mm.manufacturer = model::MotorModel::MotorManufacturer::toEnum(v.get<std::string>("<xmlattr>.mfg", ""));
mm.maxThrust = v.get<double>("<xmlattr>.peakThrust", 0.0);
mm.totalWeight = v.get<double>("<xmlattr>.initWt", 0.0) / 1000.0; // convert g -> kg
mm.propWeight = v.get<double>("<xmlattr>.propWt", 0.0) / 1000.0; // convert g -> kg
mm.propWeight = v.get<double>("<xmlattr>.propWt", 0.0);
mm.totalImpulse = v.get<double>("<xmlattr>.Itot", 0.0);
mm.type = model::MotorModel::MotorType::toEnum(v.get<std::string>("<xmlattr>.Type"));
@ -97,9 +70,7 @@ void RSEDatabaseLoader::buildAndAppendMotorModel(boost::property_tree::ptree& v)
thrustData.push_back(std::make_pair(tdata, fdata));
}
ThrustCurve tc(thrustData);
model::MotorModel motorModel;
motorModel.addThrustCurve(tc);
motorModel.moveMetaData(std::move(mm));
motors.emplace_back(std::move(motorModel));
}

View File

@ -23,9 +23,7 @@ public:
RSEDatabaseLoader(const std::string& filename);
~RSEDatabaseLoader();
std::vector<model::MotorModel>& getMotors() { return motors; }
model::MotorModel getMotorModelByName(const std::string& name);
const std::vector<model::MotorModel>& getMotors() const { return motors; }
private:
std::vector<model::MotorModel> motors;

View File

@ -4,7 +4,6 @@
// C++ headers
// 3rd party headers
#include <json/json.h>
#include <optional>
/// \endcond
// qtrocket headers
@ -15,7 +14,7 @@ namespace utils
{
ThrustCurveAPI::ThrustCurveAPI()
: hostname("https://www.thrustcurve.org/"),
: hostname("https://www.thrustcurve.org/api/v1/"),
curlConnection()
{
@ -26,109 +25,17 @@ ThrustCurveAPI::~ThrustCurveAPI()
}
std::optional<ThrustCurve> ThrustCurveAPI::getThrustCurve(const std::string& id)
{
std::stringstream endpoint;
endpoint << hostname << "api/v1/download.json?motorId=" << id << "&data=samples";
std::vector<std::string> extraHeaders = {};
std::string res = curlConnection.get(endpoint.str(), extraHeaders);
model::MotorModel mm;
if(!res.empty())
{
try
{
Json::Reader reader;
Json::Value jsonResult;
reader.parse(res, jsonResult);
std::vector<std::pair<double, double>> samples;
for(Json::ValueConstIterator iter = jsonResult["results"].begin();
iter != jsonResult["results"].end();
++iter)
{
// if there are more than 1 items in the results list, we only want the RASP data
// Otherwise just take whatever is there
if(std::next(iter) != jsonResult["results"].end())
{
if( (*iter)["format"].asString() != "RASP")
continue;
}
for(Json::ValueConstIterator samplesIter = (*iter)["samples"].begin();
samplesIter != (*iter)["samples"].end();
++samplesIter)
{
samples.push_back(std::make_pair((*samplesIter)["time"].asDouble(),
(*samplesIter)["thrust"].asDouble()));
}
}
return ThrustCurve(samples);
}
catch(const std::exception& e)
{
std::string err("Unable to parse JSON from Thrustcurve motor data request. Error: ");
err += e.what();
Logger::getInstance()->error(err);
}
}
return std::nullopt;
}
model::MotorModel ThrustCurveAPI::getMotorData(const std::string& motorId)
{
std::stringstream endpoint;
endpoint << hostname << "api/v1/download.json?motorId=" << motorId << "&data=samples";
endpoint << hostname << "download.json?motorId=" << motorId << "&data=samples";
std::vector<std::string> extraHeaders = {};
std::string res = curlConnection.get(endpoint.str(), extraHeaders);
/// TODO: fix this
model::MotorModel mm;
if(!res.empty())
{
try
{
Json::Reader reader;
Json::Value jsonResult;
reader.parse(res, jsonResult);
std::vector<std::pair<double, double>> samples;
for(Json::ValueConstIterator iter = jsonResult["results"].begin();
iter != jsonResult["results"].end();
++iter)
{
// if there are more than 1 items in the results list, we only want the RASP data
// Otherwise just take whatever is there
if(std::next(iter) != jsonResult["results"].end())
{
if( (*iter)["format"].asString() != "RASP")
continue;
}
for(Json::ValueConstIterator samplesIter = (*iter)["samples"].begin();
samplesIter != (*iter)["samples"].end();
++samplesIter)
{
samples.push_back(std::make_pair((*samplesIter)["time"].asDouble(),
(*samplesIter)["thrust"].asDouble()));
}
}
ThrustCurve tc(samples);
mm.addThrustCurve(tc);
}
catch(const std::exception& e)
{
std::string err("Unable to parse JSON from Thrustcurve motor data request. Error: ");
err += e.what();
Logger::getInstance()->error(err);
}
}
return mm;
}
@ -136,7 +43,7 @@ ThrustcurveMetadata ThrustCurveAPI::getMetadata()
{
std::string endpoint = hostname;
endpoint += "api/v1/metadata.json";
endpoint += "metadata.json";
std::string result = curlConnection.get(endpoint, extraHeaders);
ThrustcurveMetadata ret;
@ -216,7 +123,7 @@ std::vector<model::MotorModel> ThrustCurveAPI::searchMotors(const SearchCriteria
{
std::vector<model::MotorModel> retVal;
std::string endpoint = hostname;
endpoint += "api/v1/search.json?";
endpoint += "search.json?";
for(const auto& i : c.criteria)
{
endpoint += i.first;
@ -235,9 +142,7 @@ std::vector<model::MotorModel> ThrustCurveAPI::searchMotors(const SearchCriteria
{
Json::Reader reader;
Json::Value jsonResult;
Logger::getInstance()->debug("1");
reader.parse(result, jsonResult);
Logger::getInstance()->debug("2");
for(Json::ValueConstIterator iter = jsonResult["results"].begin();
iter != jsonResult["results"].end();
@ -246,7 +151,6 @@ Logger::getInstance()->debug("2");
model::MotorModel motorModel;
model::MotorModel::MetaData mm;
mm.commonName = (*iter)["commonName"].asString();
Logger::getInstance()->debug("3");
std::string availability = (*iter)["availability"].asString();
if(availability == "regular")
@ -256,7 +160,6 @@ Logger::getInstance()->debug("3");
mm.avgThrust = (*iter)["avgThrustN"].asDouble();
mm.burnTime = (*iter)["burnTimeS"].asDouble();
Logger::getInstance()->debug("4");
// TODO fill in certOrg
// TODO fill in delays
mm.designation = (*iter)["designation"].asString();
@ -283,14 +186,7 @@ Logger::getInstance()->debug("4");
else
mm.type = model::MotorModel::MotorType(model::MotorModel::MOTORTYPE::HYBRID);
Logger::getInstance()->debug("5");
auto tc = getThrustCurve(mm.motorIdTC);
motorModel.moveMetaData(std::move(mm));
Logger::getInstance()->debug("6");
if(tc)
{
motorModel.addThrustCurve(*tc);
}
retVal.push_back(motorModel);
}
}

View File

@ -7,7 +7,6 @@
// C++ headers
#include <map>
#include <string>
#include <optional>
// 3rd party headers
/// \endcond
@ -92,8 +91,6 @@ private:
const std::string hostname;
CurlConnection curlConnection;
std::optional<ThrustCurve> getThrustCurve(const std::string& id);
// no extra headers, but CurlConnection library wants them
const std::vector<std::string> extraHeaders{};
};

21
utils/Triplet.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef TRIPLET_H
#define TRIPLET_H
/**
* The purpose of this class is to get rid of using std::tuple for coordinate triplets.
*/
template<typename T>
struct Triplet
{
Triplet(const T& a, const T& b, const T& c)
: x1(a), x2(b), x3(c)
{}
T x1;
T x2;
T x3;
};
using TripletD = Triplet<double>;
#endif // TRIPLET_H

View File

@ -6,19 +6,9 @@ namespace utils::math
namespace Constants
{
constexpr double Rstar = 8.31432;
constexpr double Rstar = 8.3144598;
constexpr const double g0 = 9.80665;
constexpr const double airMolarMass = 0.0289644;
// gamma is the ratio of the specific heat of air at constant pressure to that at
// constant volume. Used in computing the speed of sound
constexpr const double gamma = 1.4;
// beta is used in calculating the dynamic viscosity of air. Based on the 1976 US Standard
// Atmosphere report, it is empirically measured to be:
constexpr const double beta = 1.458e-6;
// Sutherland's constant
constexpr const double S = 110.4;
constexpr const double standardTemperature = 288.15;
constexpr const double standardDensity = 1.2250;
constexpr const double meanEarthRadiusWGS84 = 6371008.8;

View File

@ -1,74 +0,0 @@
#ifndef UTILS_MATH_MATHTYPES_H
#define UTILS_MATH_MATHTYPES_H
#include <Eigen/Dense>
#include <vector>
/// This is not in any namespace. These typedefs are intended to be used throughout QtRocket,
/// so keeping them in the global namespace seems to make sense.
template <int Size>
using MyMatrix = Eigen::Matrix<double, Size, Size>;
template <int Size>
using MyVector = Eigen::Matrix<double, Size, 1>;
typedef Eigen::Quaterniond Quaternion;
using Matrix3 = MyMatrix<3>;
using Matrix4 = MyMatrix<4>;
using Vector3 = MyVector<3>;
using Vector6 = MyVector<6>;
/*
namespace utils
{
std::vector<double> myVectorToStdVector(const Vector3& x)
{
return std::vector<double>{x.coeff(0), x.coeff(1), x.coeff(2)};
}
std::vector<double> myVectorToStdVector(const Vector6& x)
{
return std::vector<double>{x.coeff(0),
x.coeff(1),
x.coeff(2),
x.coeff(3),
x.coeff(4),
x.coeff(5)};
}
}
class Vector3 : public MyVector<3>
{
public:
template<typename... Args>
Vector3(Args&&... args) : MyVector<3>(std::forward<Args>(args)...)
{}
operator std::vector<double>()
{
return std::vector<double>{this->coeff(0), this->coeff(1), this->coeff(2)};
}
};
class Vector6 : public MyVector<6>
{
public:
template<typename... Args>
Vector6(Args&&... args) : MyVector<6>(std::forward<Args>(args)...)
{}
operator std::vector<double>()
{
return std::vector<double>{this->coeff(0),
this->coeff(1),
this->coeff(2),
this->coeff(3),
this->coeff(4),
this->coeff(5)};
}
};
*/
#endif // UTILS_MATH_MATHTYPES_H

25
utils/math/Quaternion.cpp Normal file
View File

@ -0,0 +1,25 @@
// qtrocket headers
#include "utils/math/Quaternion.h"
namespace math
{
Quaternion::Quaternion()
{
}
Quaternion::Quaternion(double r, const Vector3& i)
: real(r),
imag(i)
{
}
Quaternion::Quaternion(double r, double i1, double i2, double i3)
: real(r),
imag(i1, i2, i3)
{
}
} // namespace math

63
utils/math/Quaternion.h Normal file
View File

@ -0,0 +1,63 @@
#ifndef MATH_QUATERNION_H
#define MATH_QUATERNION_H
/// \cond
// C headers
// C++ headers
#include <utility>
// 3rd party headers
/// \endcond
// qtrocket headers
#include "Vector3.h"
namespace math
{
class Quaternion
{
public:
Quaternion();
~Quaternion() {}
Quaternion(double r, const Vector3& i);
Quaternion(double r, double i1, double i2, double i3);
Quaternion(const Quaternion&) = default;
Quaternion(Quaternion&&) = default;
Quaternion& operator=(const Quaternion& rhs)
{
if(this == &rhs)
return *this;
real = rhs.real;
imag = rhs.imag;
return *this;
}
Quaternion& operator=(Quaternion&& rhs)
{
if(this == &rhs)
return *this;
real = std::move(rhs.real);
imag = std::move(rhs.imag);
return *this;
}
/*
Quaternion operator-() {}
Quaternion operator-(const Quaternion& ) {}
Quaternion operator+(const Quaternion& ) {}
Quaternion operator*(double ) {}
Quaternion operator*(const Quaternion& ) {}
*/
private:
double real;
Vector3 imag;
};
} // namespace math
#endif // MATH_QUATERNION_H

View File

@ -0,0 +1,15 @@
/// \cond
// C headers
// C++ headers
// 3rd party headers
/// \endcond
namespace utils
{
namespace math
{
} // namespace math
} // namespace utils

View File

@ -19,7 +19,7 @@ namespace math
* places to ignore
*
* This is derived from the example on cppreference.com: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon
* The default ulp of 4 with a numeric_limits<double>::epsilon() of ~2e-16, so ulp of 4 yields 12
* The default ulp of 8 with a numeric_limits<double>::epsilon() of ~2e-16, so ulp of 8 yields 12
* significant figures. A ulp of 10 yields 6 significant figures.
* @param a the first double to compare
* @param b the second double to compare

68
utils/math/Vector3.cpp Normal file
View File

@ -0,0 +1,68 @@
// qtrocket headers
#include "utils/math/Vector3.h"
namespace math
{
Vector3::Vector3()
: x1(0.0),
x2(0.0),
x3(0.0)
{
}
Vector3::Vector3(const double& inX1,
const double& inX2,
const double& inX3)
: x1(inX1),
x2(inX2),
x3(inX3)
{
}
Vector3::Vector3(const Vector3& o)
: x1(o.x1),
x2(o.x2),
x3(o.x3)
{
}
Vector3::~Vector3()
{}
Vector3 Vector3::operator=(const Vector3& rhs)
{
return Vector3(rhs.x1, rhs.x2, rhs.x3);
}
Vector3 Vector3::operator-()
{
return Vector3(-x1, -x2, -x3);
}
Vector3 Vector3::operator-(const Vector3& rhs)
{
return Vector3(x1 - rhs.x1,
x2 - rhs.x2,
x3 - rhs.x3);
}
Vector3 Vector3::operator+(const Vector3& rhs)
{
return Vector3(x1 + rhs.x1,
x2 + rhs.x2,
x3 + rhs.x3);
}
Vector3 Vector3::operator*(const double& s)
{
return Vector3(x1 * s,
x2 * s,
x3 * s);
}
} // namespace math

40
utils/math/Vector3.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef MATH_VECTOR3_H
#define MATH_VECTOR3_H
namespace math
{
class Vector3
{
public:
Vector3();
Vector3(const double& inX1,
const double& inX2,
const double& inX3);
Vector3(const Vector3& o);
~Vector3();
Vector3 operator=(const Vector3& rhs);
Vector3 operator-();
Vector3 operator-(const Vector3& rhs);
Vector3 operator+(const Vector3& rhs);
Vector3 operator*(const double& s);
double getX1() { return x1; }
double getX2() { return x2; }
double getX3() { return x3; }
//private:
double x1;
double x2;
double x3;
};
} // namespace math
#endif // MATH_VECTOR3_H