Compare commits

...

62 Commits

Author SHA1 Message Date
3c9c8b8c6a
Merge pull request #19 from cthunter01/development
Fix build failures and add github CI actions. Automated builds on Ubuntu and Windows
2024-08-14 18:28:54 -06:00
ead8d1dc01
Merge branch 'qtrocket:development' into development 2024-08-14 18:27:26 -06:00
86587dc435
Update README.md 2024-08-14 18:25:52 -06:00
5a332ec060 Substantial refactor and cleanup of Propagator and Rocket class (now RocketModel class) 2024-03-17 10:19:39 -06:00
46eca1136f Remove model::Stage for now. It'll be good in the future, but makes initial development more complex. Prototype with single stage rocket for now while working out the physics engine 2024-03-17 08:13:24 -06:00
63a5b8995f Update CTest infrastructure to automatically run qtrocket unit tests 2024-03-15 15:17:26 -06:00
e0f3eb1c45 Exclude ubuntu-latest clang build since it's broken (not my breakage, Github's config of ubuntu-latest) 2024-03-15 13:29:30 -06:00
b72d6ff67f Add unit tests to workflow. Disable clang for now because it's broken in ubuntu-latest build container 2024-03-15 13:27:05 -06:00
1a29d0b04e Tweaking workflow. Trying to use gcc-13 on ubuntu 2024-03-15 12:58:37 -06:00
f938d5e21c fix typo in windows-latest.yml action 2024-03-15 12:41:12 -06:00
78bfecd8a3 Split github actions by OS 2024-03-15 12:39:40 -06:00
903b0ee8e6
Update cmake-multi-platform.yml 2024-03-15 12:08:08 -06:00
fb7e267bb4
Test gcc13 on ubuntu-latest: Update cmake-multi-platform.yml 2024-03-15 11:58:14 -06:00
17e87e2c40
Update cmake-multi-platform.yml 2024-03-15 11:20:38 -06:00
46c5caa1ab
Add "install Qt" to github actions: Update cmake-multi-platform.yml 2024-03-15 11:17:44 -06:00
bf83ffc9ec
Add github actions: Create cmake-multi-platform.yml 2024-03-15 11:01:15 -06:00
5db186e8da Fixed build on Fedora with boost. I'm not sure why it worked on FreeBSD but in Fedora 39 CMake didn't set boost up properly, or maybe the FetchContent(Boost) just never worked and it always found the installed boost library? But it fetches and sets up dynamically now... 2024-03-15 10:50:00 -06:00
e5c068ddf7 Remove external dependency on fmtlib in favor of std::format. Begin transition of model::Part into an interface 2024-03-14 18:06:05 -06:00
6a015d9797 Fix crash on motor load 2024-03-14 07:05:16 -06:00
f4e560fcd9 quick cleanup 2024-02-18 16:38:22 -07:00
5da279a8e6 Cleanup 2024-02-18 16:25:45 -07:00
d9cc4e4aec Remove CMakeLists.txt.user 2024-02-18 09:03:51 -07:00
9517b5b682 Added first model tests. Super basic Part test 2023-10-20 17:03:52 -06:00
41183b8397 WIP. Added Stage objects, and made Stage and Rocket a Propagatable. Changed Propagator to propagate a Propagatable instead of a Rocket. This is to allow for propagating dropped stages 2023-10-20 14:48:35 -06:00
9b807d53a4 Fixed US Standard Atmosphere class and tests 2023-10-19 10:58:01 -06:00
9391b85a07 WIP. Started building model/Part component 2023-10-19 08:33:34 -06:00
4da21707b6 Building and running again 2023-10-16 17:40:33 -06:00
505a66a1be WIP 2023-10-16 14:55:31 -06:00
d8e0341742 merge 2023-05-18 20:02:18 -06:00
22c4e79f52 WIP 2023-05-18 19:58:34 -06:00
c7b453d253 added googletest and a USStandardAtmosphereTest 2023-05-15 18:36:47 -06:00
d307bf47c6 Split large CMakeLists into multiple subdirectory libraries. This will make unit testing easier on a per-component basis 2023-05-10 21:03:02 -06:00
558211e9fe Removed the Propagator from the Rocket. It shouldn't really be there, now the Propagator belongs to QtRocket, and is paired with a particular Rocket model, but doesn't belong to that model 2023-05-06 09:20:42 -06:00
8e620cf0c1 Added Eigen dependency, so now QtRocket Vectors (and matrices and quaternions) are backed by Eigen. Cleaned up unused files also 2023-05-06 08:53:56 -06:00
821df8905a Now reads motor delays 2023-05-05 08:01:59 -06:00
42298ca801 merge with upstream 2023-05-04 16:40:20 -06:00
10c552e086 Added QtRocket custome Motor Database format (xml) 2023-05-04 16:31:51 -06:00
47919b661f Fix for motor mass curve calculation 2023-05-03 18:46:14 -06:00
c58edfa30f
Update README.md 2023-05-03 09:09:46 -06:00
6c294969fc
Update README.md 2023-05-03 09:06:35 -06:00
4e9bcb9158
Merge pull request #18 from cthunter01/development
Changing https to http in ThrustCurveAPI to work around issue in Wind…
2023-05-03 08:51:12 -06:00
14878705cc Changing https to http in ThrustCurveAPI to work around issue in Windows where curl isn't being built with SSL support 2023-05-03 08:50:19 -06:00
8b982ee598
Merge pull request #17 from cthunter01/development
Now building under windows 10 with VS 2019
2023-05-02 21:11:39 -06:00
36996abab8 Now building under windows 10 with VS 2019 2023-05-02 20:56:23 -06:00
cea4d13e6c Change SimulationOptions to Environment 2023-04-28 10:49:29 -06:00
697ef8d643
Update README.md 2023-04-27 20:38:47 -06:00
36c43b1f26 Thrustcurve.org fix, motors now load. Also cleanup to not use implicit callbacks, now all signals/slots are explicitly connected 2023-04-27 20:36:45 -06:00
6f033c3a55 Now a real README.md 2023-04-27 19:11:23 -06:00
26dca6077b Adding Aerotech motor database. Adding screenshots 2023-04-27 18:24:28 -06:00
5172dbab4f Updated MainWindow to use explicit slots instead of implicit ones 2023-04-27 17:57:18 -06:00
2d64a95b17
Merge pull request #15 from cthunter01/development
Now with ThrustCurve.org integration and better plotting capabilities
2023-04-27 12:09:05 -06:00
7823be6de0
Merge branch 'qtrocket:development' into development 2023-04-27 12:06:20 -06:00
fb6f221137 Now with ThrustCurve.org integration 2023-04-27 12:03:09 -06:00
6e30881f15
Merge pull request #13 from cthunter01/development
Transition to CMake, removed qmake project file. Application icon is …
2023-04-26 15:52:19 -06:00
eeea751fbc Transition to CMake, removed qmake project file. Application icon is missing, but otherwise builds and runs 2023-04-26 15:19:33 -06:00
1158bb4222
Merge pull request #12 from cthunter01/development
bug fixes
2023-04-26 06:54:27 -06:00
fc57b00ec4 fix simTime restart to 0.0 2023-04-26 06:43:46 -06:00
5d2becd2b4 motor model selection working with RSE files 2023-04-25 20:35:17 -06:00
89fb6d47d0 WIP 2023-04-25 19:11:28 -06:00
696e81433e
Merge pull request #11 from cthunter01/development
Add SimulationOptions class to keep track of and encapsulate various …
2023-04-24 20:52:01 -06:00
070013cff2 Add SimulationOptions class to keep track of and encapsulate various simulation options 2023-04-24 20:43:18 -06:00
6b2391f2bb
Create HELPWANTED
Add a help wanted file.
2023-04-24 07:27:53 -06:00
98 changed files with 13099 additions and 1098 deletions

View File

@ -0,0 +1,86 @@
# 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,7 +38,8 @@ build/
docs/doxygen/* docs/doxygen/*
# IDE # IDE
.vscode/qtrocket.pro.user *.swp
qtrocket.pro.user qtrocket.pro.user
.qmake.stash .qmake.stash
CMakeLists.txt.user

17
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,17 @@
{
"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
}

3
.vscode/settings.json vendored Normal file
View File

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

175
CMakeLists.txt Normal file
View File

@ -0,0 +1,175 @@
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()

13
HELPWANTED Normal file
View File

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

View File

@ -7,16 +7,19 @@
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <utility>
// 3rd party headers // 3rd party headers
/// \endcond /// \endcond
// qtrocket headers // qtrocket headers
#include "model/MotorModel.h" #include "model/MotorModel.h"
#include "model/Rocket.h" #include "model/RocketModel.h"
#include "sim/AtmosphericModel.h" #include "sim/Environment.h"
#include "sim/GravityModel.h" #include "sim/Propagator.h"
#include "utils/Logger.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. * @brief The QtRocket class is the master controller for the QtRocket application.
@ -37,15 +40,31 @@ public:
void runSim(); 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 addMotorModels(std::vector<model::MotorModel>& m);
void addRocket(std::shared_ptr<Rocket> r) { rocket = r; } void addRocket(std::shared_ptr<model::RocketModel> r) { rocket.first = r; rocket.second = std::make_shared<sim::Propagator>(r); }
std::shared_ptr<Rocket> getRocket() { return rocket; } 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); }
private: private:
QtRocket(); QtRocket();
@ -57,15 +76,20 @@ private:
static std::mutex mtx; static std::mutex mtx;
static QtRocket* instance; static QtRocket* instance;
// Motor "database(s)"
std::vector<model::MotorModel> motorModels;
utils::Logger* logger; utils::Logger* logger;
std::shared_ptr<Rocket> rocket; using Rocket = std::pair<std::shared_ptr<model::RocketModel>, std::shared_ptr<sim::Propagator>>;
std::shared_ptr<sim::AtmosphericModel> atmosphere; Rocket rocket;
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,3 +1,2 @@
# qtrocket # qtrocket
Soon to be model Rocket Simulator written in C++ and Qt Toolkit An open source model Rocket Simulator written in C++ and Qt Toolkit, coming soon

9008
data/Aerotech.rse Executable file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -2,6 +2,8 @@
#include "ui_AnalysisWindow.h" #include "ui_AnalysisWindow.h"
#include "QtRocket.h" #include "QtRocket.h"
#include "model/MotorModel.h"
#include "model/ThrustCurve.h"
AnalysisWindow::AnalysisWindow(QWidget *parent) : AnalysisWindow::AnalysisWindow(QWidget *parent) :
QDialog(parent), QDialog(parent),
@ -12,10 +14,31 @@ AnalysisWindow::AnalysisWindow(QWidget *parent) :
this->hide(); this->hide();
this->show(); 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()));
std::shared_ptr<Rocket> rocket = QtRocket::getInstance()->getRocket(); }
const std::vector<std::pair<double, std::vector<double>>>& res = rocket->getStates();
AnalysisWindow::~AnalysisWindow()
{
delete ui;
}
void AnalysisWindow::onButton_plotAltitude_clicked()
{
QtRocket* qtRocket = QtRocket::getInstance();
const std::vector<std::pair<double, StateData>>& res = qtRocket->getStates();
auto& plot = ui->plotWidget; auto& plot = ui->plotWidget;
plot->clearGraphs();
plot->setInteraction(QCP::iRangeDrag, true); plot->setInteraction(QCP::iRangeDrag, true);
plot->setInteraction(QCP::iRangeZoom, true); plot->setInteraction(QCP::iRangeZoom, true);
// generate some data: // generate some data:
@ -23,7 +46,7 @@ AnalysisWindow::AnalysisWindow(QWidget *parent) :
for (int i = 0; i < tData.size(); ++i) for (int i = 0; i < tData.size(); ++i)
{ {
tData[i] = res[i].first; tData[i] = res[i].first;
zData[i] = res[i].second[2]; zData[i] = res[i].second.position[2];
} }
// create graph and assign data to it: // create graph and assign data to it:
plot->addGraph(); plot->addGraph();
@ -37,7 +60,66 @@ AnalysisWindow::AnalysisWindow(QWidget *parent) :
plot->replot(); plot->replot();
} }
AnalysisWindow::~AnalysisWindow() void AnalysisWindow::onButton_plotVelocity_clicked()
{ {
delete ui; 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();
} }

View File

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

View File

@ -16,13 +16,68 @@
<widget class="QCustomPlot" name="plotWidget" native="true"> <widget class="QCustomPlot" name="plotWidget" native="true">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>19</x> <x>149</x>
<y>29</y> <y>29</y>
<width>961</width> <width>831</width>
<height>671</height> <height>541</height>
</rect> </rect>
</property> </property>
</widget> </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> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
@ -34,4 +89,7 @@
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections/> <connections/>
<slots>
<slot>plotAltitude()</slot>
</slots>
</ui> </ui>

View File

@ -19,8 +19,8 @@
#include "gui/AnalysisWindow.h" #include "gui/AnalysisWindow.h"
#include "gui/MainWindow.h" #include "gui/MainWindow.h"
#include "gui/ThrustCurveMotorSelector.h" #include "gui/ThrustCurveMotorSelector.h"
#include "gui/SimulationOptions.h" #include "gui/SimOptionsWindow.h"
#include "model/Rocket.h" #include "model/RocketModel.h"
#include "utils/RSEDatabaseLoader.h" #include "utils/RSEDatabaseLoader.h"
@ -31,6 +31,59 @@ MainWindow::MainWindow(QtRocket* _qtRocket, QWidget *parent)
qtRocket(_qtRocket) qtRocket(_qtRocket)
{ {
ui->setupUi(this); 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() MainWindow::~MainWindow()
@ -39,7 +92,7 @@ MainWindow::~MainWindow()
} }
void MainWindow::on_actionAbout_triggered() void MainWindow::onMenu_Help_About_triggered()
{ {
AboutWindow about; AboutWindow about;
about.setModal(true); about.setModal(true);
@ -47,33 +100,13 @@ void MainWindow::on_actionAbout_triggered()
} }
void MainWindow::onMenu_Tools_SaveMotorDatabase()
void MainWindow::on_testButton1_clicked()
{ {
auto& plot = ui->plotWindow; qtRocket->getMotorDatabase()->saveMotorDatabase("qtrocket_motors.qmd");
// 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::on_testButton2_clicked() void MainWindow::onButton_calculateTrajectory_clicked()
{ {
// Get the initial conditions // Get the initial conditions
double initialVelocity = double initialVelocity =
@ -90,70 +123,50 @@ void MainWindow::on_testButton2_clicked()
double initialVelocityX = initialVelocity * std::cos(initialAngle / 57.2958); double initialVelocityX = initialVelocity * std::cos(initialAngle / 57.2958);
double initialVelocityZ = initialVelocity * std::sin(initialAngle / 57.2958); double initialVelocityZ = initialVelocity * std::sin(initialAngle / 57.2958);
std::shared_ptr<Rocket> rocket = std::make_shared<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};
QtRocket::getInstance()->addRocket(rocket); StateData initialState;
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}; initialState.position = {0.0, 0.0, 0.0};
rocket->setInitialState(initialState); initialState.velocity = {initialVelocityX, 0.0, initialVelocityZ};
auto rocket = QtRocket::getInstance()->getRocket();
rocket->setMass(mass); rocket->setMass(mass);
rocket->setDragCoefficient(dragCoeff); rocket->setDragCoefficient(dragCoeff);
rocket->launch();
qtRocket->setInitialState(initialState);
qtRocket->launchRocket();
AnalysisWindow aWindow; AnalysisWindow aWindow;
aWindow.setModal(false); aWindow.setModal(false);
aWindow.exec(); 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::on_loadRSE_button_clicked() void MainWindow::onButton_loadRSE_button_clicked()
{ {
QString rseFile = QFileDialog::getOpenFileName(this, QString rseFile = QFileDialog::getOpenFileName(this,
tr("Import RSE Database File"), tr("Import RSE Database File"),
"/home", "/home",
tr("RSE Files (*.rse)")); tr("Rocksim Engine Files (*.rse)"));
utils::RSEDatabaseLoader loader(rseFile.toStdString()); if(!rseFile.isEmpty())
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)
{ {
std::cout << "Adding: " << motor.data.commonName << std::endl; rseDatabase.reset(new utils::RSEDatabaseLoader(rseFile.toStdString()));
engineSelector->addItem(QString(motor.data.commonName.c_str()));
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()));
}
} }
} }
void MainWindow::on_getTCMotorData_clicked() void MainWindow::onButton_getTCMotorData_clicked()
{ {
ThrustCurveMotorSelector window; ThrustCurveMotorSelector window;
window.setModal(false); window.setModal(false);
@ -162,13 +175,33 @@ void MainWindow::on_getTCMotorData_clicked()
} }
void MainWindow::on_actionSimulation_Options_triggered() void MainWindow::onMenu_Edit_SimulationOptions_triggered()
{ {
if(!simOptionsWindow) if(!simOptionsWindow)
{ {
simOptionsWindow = new SimulationOptions(this); simOptionsWindow = new SimOptionsWindow(this);
} }
simOptionsWindow->show(); 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,6 +4,7 @@
/// \cond /// \cond
// C headers // C headers
// C++ headers // C++ headers
#include <memory>
// 3rd Party headers // 3rd Party headers
#include <QMainWindow> #include <QMainWindow>
/// \endcond /// \endcond
@ -11,7 +12,8 @@
// qtrocket headers // qtrocket headers
#include "QtRocket.h" #include "QtRocket.h"
#include "gui/SimulationOptions.h" #include "gui/SimOptionsWindow.h"
#include "utils/RSEDatabaseLoader.h"
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -35,22 +37,27 @@ public:
private slots: private slots:
void on_actionAbout_triggered(); void onMenu_Help_About_triggered();
void on_testButton1_clicked(); void onButton_calculateTrajectory_clicked();
void on_testButton2_clicked(); void onButton_loadRSE_button_clicked();
void on_loadRSE_button_clicked(); void onButton_getTCMotorData_clicked();
void on_getTCMotorData_clicked(); void onMenu_Edit_SimulationOptions_triggered();
void on_actionSimulation_Options_triggered(); void onButton_setMotor_clicked();
void onMenu_File_Quit_triggered();
void onMenu_Tools_SaveMotorDatabase();
private: private:
Ui::MainWindow* ui; Ui::MainWindow* ui;
QtRocket* qtRocket; QtRocket* qtRocket;
SimulationOptions* simOptionsWindow{nullptr}; SimOptionsWindow* simOptionsWindow{nullptr};
std::unique_ptr<utils::RSEDatabaseLoader> rseDatabase;
}; };
#endif // MAINWINDOW_H #endif // MAINWINDOW_H

View File

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

74
gui/SimOptionsWindow.cpp Normal file
View File

@ -0,0 +1,74 @@
/// \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();
}

27
gui/SimOptionsWindow.h Normal file
View File

@ -0,0 +1,27 @@
#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

View File

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

View File

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

View File

@ -1,22 +0,0 @@
#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

@ -17,6 +17,23 @@ ThrustCurveMotorSelector::ThrustCurveMotorSelector(QWidget *parent) :
tcApi(new utils::ThrustCurveAPI) tcApi(new utils::ThrustCurveAPI)
{ {
ui->setupUi(this); 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->setWindowModality(Qt::NonModal);
this->hide(); this->hide();
this->show(); this->show();
@ -27,7 +44,7 @@ ThrustCurveMotorSelector::~ThrustCurveMotorSelector()
delete ui; delete ui;
} }
void ThrustCurveMotorSelector::on_getMetadata_clicked() void ThrustCurveMotorSelector::onButton_getMetadata_clicked()
{ {
// When the user clicks "Get Metadata", we want to pull in Metadata from thrustcurve.org // 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 // and populate the Manufacturer, Diameter, and Impulse Class combo boxes
@ -50,7 +67,7 @@ void ThrustCurveMotorSelector::on_getMetadata_clicked()
} }
void ThrustCurveMotorSelector::on_searchButton_clicked() void ThrustCurveMotorSelector::onButton_searchButton_clicked()
{ {
//double diameter = ui->diameter-> //double diameter = ui->diameter->
@ -75,7 +92,7 @@ void ThrustCurveMotorSelector::on_searchButton_clicked()
} }
void ThrustCurveMotorSelector::on_setMotor_clicked() void ThrustCurveMotorSelector::onButton_setMotor_clicked()
{ {
//asdf //asdf
std::string commonName = ui->motorSelection->currentText().toStdString(); std::string commonName = ui->motorSelection->currentText().toStdString();
@ -90,6 +107,34 @@ void ThrustCurveMotorSelector::on_setMotor_clicked()
return item.data.commonName == commonName; return item.data.commonName == commonName;
}); });
ThrustCurve tc = tcApi->getMotorData(mm.data.motorIdTC).getThrustCurve();
mm.addThrustCurve(tc);
QtRocket::getInstance()->getRocket()->setMotorModel(mm); 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(); ~ThrustCurveMotorSelector();
private slots: private slots:
void on_getMetadata_clicked(); void onButton_getMetadata_clicked();
void on_searchButton_clicked(); void onButton_searchButton_clicked();
void on_setMotor_clicked(); void onButton_setMotor_clicked();
private: private:
Ui::ThrustCurveMotorSelector *ui; Ui::ThrustCurveMotorSelector *ui;

View File

@ -1,11 +1,9 @@
/// \cond /// \cond
// C headers // C headers
// C++ headers // C++ headers
// 3rd party headers // 3rd party headers
/// \endcond /// \endcond
#include "QtRocket.h" #include "QtRocket.h"
#include "utils/Logger.h" #include "utils/Logger.h"
@ -14,54 +12,16 @@ int main(int argc, char *argv[])
// Instantiate logger // Instantiate logger
utils::Logger* logger = utils::Logger::getInstance(); utils::Logger* logger = utils::Logger::getInstance();
logger->setLogLevel(utils::Logger::DEBUG); logger->setLogLevel(utils::Logger::PERF_);
logger->info("Logger instantiated at PERF level");
// instantiate QtRocket // instantiate QtRocket
logger->debug("Starting QtRocket");
QtRocket* qtrocket = QtRocket::getInstance(); QtRocket* qtrocket = QtRocket::getInstance();
// Run QtRocket. This'll start the GUI thread and block until the user // Run QtRocket. This'll start the GUI thread and block until the user
// exits the program // exits the program
logger->debug("QtRocket->run()");
int retVal = qtrocket->run(argc, argv); int retVal = qtrocket->run(argc, argv);
logger->debug("Returning"); logger->debug("Returning");
return retVal; 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();
}
*/

20
model/CMakeLists.txt Normal file
View File

@ -0,0 +1,20 @@
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)

70
model/InertiaTensors.h Normal file
View File

@ -0,0 +1,70 @@
#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

View File

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

View File

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

View File

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

109
model/Part.cpp Normal file
View File

@ -0,0 +1,109 @@
#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

136
model/Part.h Normal file
View File

@ -0,0 +1,136 @@
#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

0
model/Propagatable.cpp Normal file
View File

58
model/Propagatable.h Normal file
View File

@ -0,0 +1,58 @@
#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

View File

@ -1,36 +0,0 @@
#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;
}

View File

@ -1,121 +0,0 @@
#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

79
model/RocketModel.cpp Normal file
View File

@ -0,0 +1,79 @@
// 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

116
model/RocketModel.h Normal file
View File

@ -0,0 +1,116 @@
#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,8 +8,6 @@
/// \endcond /// \endcond
#include "model/ThrustCurve.h" #include "model/ThrustCurve.h"
#include "utils/Logger.h"
ThrustCurve::ThrustCurve(std::vector<std::pair<double, double>>& tc) ThrustCurve::ThrustCurve(std::vector<std::pair<double, double>>& tc)
: thrustCurve(tc), : thrustCurve(tc),
@ -55,12 +53,6 @@ void ThrustCurve::setIgnitionTime(double t)
double ThrustCurve::getThrust(double t) double ThrustCurve::getThrust(double t)
{ {
// calculate t relative to the start time of the motor // 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; t -= ignitionTime;
if(t < 0.0 || t > maxTime) if(t < 0.0 || t > maxTime)
{ {

View File

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

View File

@ -0,0 +1,16 @@
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)

70
model/tests/PartTests.cpp Normal file
View File

@ -0,0 +1,70 @@
#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);
}

View File

@ -1,107 +0,0 @@
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.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

0
sim/Aero.cpp Normal file
View File

40
sim/Aero.h Normal file
View File

@ -0,0 +1,40 @@
#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,6 +13,10 @@ public:
virtual double getDensity(double altitude) = 0; virtual double getDensity(double altitude) = 0;
virtual double getPressure(double altitude) = 0; virtual double getPressure(double altitude) = 0;
virtual double getTemperature(double altitude) = 0; virtual double getTemperature(double altitude) = 0;
virtual double getSpeedOfSound(double altitude) = 0;
virtual double getDynamicViscosity(double altitude) = 0;
}; };
} // namespace sim } // namespace sim

28
sim/CMakeLists.txt Normal file
View File

@ -0,0 +1,28 @@
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,6 +15,10 @@ public:
double getDensity(double) override { return 1.225; } double getDensity(double) override { return 1.225; }
double getPressure(double) override { return 101325.0; } double getPressure(double) override { return 101325.0; }
double getTemperature(double) override { return 288.15; } double getTemperature(double) override { return 288.15; }
double getSpeedOfSound(double) override { return 340.294; }
double getDynamicViscosity(double) override { return 1.78938e-5; }
}; };
} // namespace sim } // namespace sim

View File

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

View File

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

View File

@ -4,14 +4,17 @@
/// \cond /// \cond
// C headers // C headers
// C++ headers // C++ headers
#include <vector> #include <utility>
// 3rd party headers // 3rd party headers
/// \endcond /// \endcond
// qtrocket headers
namespace sim namespace sim
{ {
template<typename T>
class DESolver class DESolver
{ {
public: public:
@ -19,7 +22,17 @@ public:
virtual ~DESolver() {} virtual ~DESolver() {}
virtual void setTimeStep(double ts) = 0; virtual void setTimeStep(double ts) = 0;
virtual void step(double t, const std::vector<double>& curVal, std::vector<double>& res ) = 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;
}; };
} // namespace sim } // namespace sim

111
sim/Environment.h Normal file
View File

@ -0,0 +1,111 @@
#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

View File

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

View File

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

View File

@ -13,47 +13,36 @@
// qtrocket headers // qtrocket headers
#include "Propagator.h" #include "Propagator.h"
#include "QtRocket.h"
#include "model/Rocket.h"
#include "sim/RK4Solver.h" #include "sim/RK4Solver.h"
#include "utils/Logger.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;
// This is a little strange, but I have to populate the integrator unique_ptr // dvx/dt
// with reset. make_unique() doesn't work because the compiler can't seem to dVelocity = object->getForces(currentTime) / object->getMass(currentTime);
// 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
// The state vector has components of the form: return std::make_pair(dPosition, dVelocity);
// (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);
integrator->setTimeStep(timeStep); saveStates = true;
saveStates = true;
} }
Propagator::~Propagator() Propagator::~Propagator()
@ -62,61 +51,41 @@ Propagator::~Propagator()
void Propagator::runUntilTerminate() void Propagator::runUntilTerminate()
{ {
std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now(); 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 endTime;
while(true) Vector3 currentPosition;
{ Vector3 currentVelocity;
// tempRes gets overwritten Vector3 nextPosition;
integrator->step(currentTime, currentState, tempRes); Vector3 nextVelocity;
while(true)
{
currentPosition = object->getCurrentState().position;
currentVelocity = object->getCurrentState().velocity;
std::swap(currentState, tempRes); std::tie(nextPosition, nextVelocity) = linearIntegrator->step(currentPosition, currentVelocity);
if(saveStates)
{
states.push_back(std::make_pair(currentTime, currentState));
}
if(rocket->terminateCondition(std::make_pair(currentTime, currentState)))
break;
currentTime += timeStep; StateData nextState;
} nextState.position = nextPosition;
endTime = std::chrono::steady_clock::now(); nextState.velocity = nextVelocity;
object->setCurrentState(nextState);
std::stringstream duration; if(saveStates)
duration << "runUntilTerminate time (microseconds): "; {
duration << std::chrono::duration_cast<std::chrono::microseconds>(endTime - startTime).count(); object->appendState(currentTime, nextState);
utils::Logger::getInstance()->debug(duration.str()); }
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());
} }
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 } // namespace sim

View File

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

View File

@ -4,10 +4,8 @@
/// \cond /// \cond
// C headers // C headers
// C++ headers // C++ headers
#include <cmath>
#include <functional> #include <functional>
#include <limits> #include <limits>
#include <vector>
// 3rd party headers // 3rd party headers
@ -16,84 +14,80 @@
// qtrocket headers // qtrocket headers
#include "sim/DESolver.h" #include "sim/DESolver.h"
#include "utils/Logger.h" #include "utils/Logger.h"
#include "utils/math/MathTypes.h"
namespace sim { namespace sim {
template<typename... Ts> /**
class RK4Solver : public DESolver * @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>
{ {
public: public:
RK4Solver(Ts... funcs) RK4Solver(std::function<std::pair<T, T>(T&, T&)> func)
{ {
(odes.push_back(funcs), ...); // This only works for Eigen Vector types.
temp.resize(sizeof...(Ts)); // 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;
} }
virtual ~RK4Solver() {} virtual ~RK4Solver() {}
void setTimeStep(double inTs) override { dt = inTs; halfDT = dt / 2.0; } void setTimeStep(double inTs) override { dt = inTs; halfDT = dt / 2.0; }
void step(double t, const std::vector<double>& curVal, std::vector<double>& res) override std::pair<T, T> step(T& state, T& rate) override
{ {
std::pair<T, T> res;
if(dt == std::numeric_limits<double>::quiet_NaN()) if(dt == std::numeric_limits<double>::quiet_NaN())
{ {
utils::Logger::getInstance()->error("Calling RK4Solver without setting dt first is an error"); utils::Logger::getInstance()->error("Calling RK4Solver without setting dt first is an error");
res[0] = std::numeric_limits<double>::quiet_NaN(); return res;
} }
for(size_t i = 0; i < len; ++i) std::tie(k1State, k1Rate) = odes(state, rate);
{
k1[i] = odes[i](t, curVal);
}
// compute k2 values. This involves stepping the current values forward a half-step // compute k2 values. This involves stepping the current values forward a half-step
// based on k1, so we do the stepping first // based on k1, so we do the stepping first
for(size_t i = 0; i < len; ++i) std::tie(tempState, tempRate) = std::make_pair(state + k1State*halfDT, rate + k1Rate*halfDT);
{ std::tie(k2State, k2Rate) = odes(tempState, tempRate);
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);
}
// now k4 std::tie(tempState, tempRate) = std::make_pair(state + k2State*halfDT, rate + k2Rate*halfDT);
for(size_t i = 0; i < len; ++i) std::tie(k3State, k3Rate) = odes(tempState, tempRate);
{
temp[i] = curVal[i] + k3[i]*dt;
}
for(size_t i = 0; i < len; ++i)
{
k4[i] = odes[i](t + dt, temp);
}
// now compute the result std::tie(tempState, tempRate) = std::make_pair(state + k3State*dt, rate + k3Rate*dt);
for(size_t i = 0; i < len; ++i) std::tie(k4State, k4Rate) = odes(tempState, tempRate);
{
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: private:
std::vector<std::function<double(double, const std::vector<double>&)>> odes; std::function<std::pair<T, T>(T&, T&)> odes;
static constexpr size_t len = sizeof...(Ts); T k1State;
double k1[len]; T k2State;
double k2[len]; T k3State;
double k3[len]; T k4State;
double k4[len]; T k1Rate;
T k2Rate;
T k3Rate;
T k4Rate;
std::vector<double> temp; T tempState;
T tempRate;
double dt = std::numeric_limits<double>::quiet_NaN(); double dt = std::numeric_limits<double>::quiet_NaN();
double halfDT = 0.0; double halfDT = 0.0;

View File

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

View File

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

View File

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

View File

@ -1,9 +1,15 @@
#ifndef STATEDATA_H #ifndef STATEDATA_H
#define STATEDATA_H #define STATEDATA_H
/// \cond
// C headers
// C++ headers
#include <vector>
// 3rd party headers
/// \endcond
// qtrocket headers // qtrocket headers
#include "utils/math/Vector3.h" #include "utils/math/MathTypes.h"
#include "utils/math/Quaternion.h"
/** /**
* @brief The StateData class holds physical state data. Things such as position, velocity, * @brief The StateData class holds physical state data. Things such as position, velocity,
@ -13,20 +19,76 @@
class StateData class StateData
{ {
public: public:
StateData(); 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]};
}
private: /// TODO: Put these behind an interface
math::Vector3 position{0.0, 0.0, 0.0}; //Vector3 getPosition() const
math::Vector3 velocity{0.0, 0.0, 0.0}; //{
// return position;
//}
math::Quaternion orientation{0.0, 0.0, 0.0, 0.0}; // roll, pitch, yaw //Vector3 getVelocity() const
math::Quaternion orientationRate{0.0, 0.0, 0.0, 0.0}; // roll-rate, pitch-rate, yaw-rate //{
// Necessary? // return velocity;
//math::Vector3 orientationAccel; //}
// private:
// This is an array because the integrator expects it // Intended to be used as world state data
double data[6]; 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};
}; };

View File

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

View File

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

View File

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

View File

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

15
sim/tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,15 @@
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

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

View File

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

30
utils/CMakeLists.txt Normal file
View File

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

View File

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

View File

@ -0,0 +1,134 @@
// 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

@ -0,0 +1,77 @@
#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,7 +3,9 @@
// C headers // C headers
// C++ headers // C++ headers
#include <iostream> #include <iostream>
#include <algorithm>
#include <cmath> #include <cmath>
#include <cstring>
// 3rd party headers // 3rd party headers
#include <boost/property_tree/xml_parser.hpp> #include <boost/property_tree/xml_parser.hpp>
@ -12,6 +14,7 @@
// qtrocket headers // qtrocket headers
#include "utils/RSEDatabaseLoader.h" #include "utils/RSEDatabaseLoader.h"
#include "QtRocket.h" #include "QtRocket.h"
#include "Logger.h"
namespace utils { namespace utils {
@ -33,6 +36,19 @@ RSEDatabaseLoader::RSEDatabaseLoader(const std::string& filename)
RSEDatabaseLoader::~RSEDatabaseLoader() 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) void RSEDatabaseLoader::buildAndAppendMotorModel(boost::property_tree::ptree& v)
{ {
model::MotorModel::MetaData mm; model::MotorModel::MetaData mm;
@ -43,6 +59,16 @@ void RSEDatabaseLoader::buildAndAppendMotorModel(boost::property_tree::ptree& v)
mm.commonName = v.get<std::string>("<xmlattr>.code", ""); mm.commonName = v.get<std::string>("<xmlattr>.code", "");
// mm.delays = extract vector from csv list // 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? // mm.designation = What is this?
@ -56,7 +82,8 @@ void RSEDatabaseLoader::buildAndAppendMotorModel(boost::property_tree::ptree& v)
mm.length = v.get<double>("<xmlattr>.len", 0.0); mm.length = v.get<double>("<xmlattr>.len", 0.0);
mm.manufacturer = model::MotorModel::MotorManufacturer::toEnum(v.get<std::string>("<xmlattr>.mfg", "")); mm.manufacturer = model::MotorModel::MotorManufacturer::toEnum(v.get<std::string>("<xmlattr>.mfg", ""));
mm.maxThrust = v.get<double>("<xmlattr>.peakThrust", 0.0); mm.maxThrust = v.get<double>("<xmlattr>.peakThrust", 0.0);
mm.propWeight = v.get<double>("<xmlattr>.propWt", 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.totalImpulse = v.get<double>("<xmlattr>.Itot", 0.0); mm.totalImpulse = v.get<double>("<xmlattr>.Itot", 0.0);
mm.type = model::MotorModel::MotorType::toEnum(v.get<std::string>("<xmlattr>.Type")); mm.type = model::MotorModel::MotorType::toEnum(v.get<std::string>("<xmlattr>.Type"));
@ -70,7 +97,9 @@ void RSEDatabaseLoader::buildAndAppendMotorModel(boost::property_tree::ptree& v)
thrustData.push_back(std::make_pair(tdata, fdata)); thrustData.push_back(std::make_pair(tdata, fdata));
} }
ThrustCurve tc(thrustData);
model::MotorModel motorModel; model::MotorModel motorModel;
motorModel.addThrustCurve(tc);
motorModel.moveMetaData(std::move(mm)); motorModel.moveMetaData(std::move(mm));
motors.emplace_back(std::move(motorModel)); motors.emplace_back(std::move(motorModel));
} }

View File

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

View File

@ -4,6 +4,7 @@
// C++ headers // C++ headers
// 3rd party headers // 3rd party headers
#include <json/json.h> #include <json/json.h>
#include <optional>
/// \endcond /// \endcond
// qtrocket headers // qtrocket headers
@ -14,7 +15,7 @@ namespace utils
{ {
ThrustCurveAPI::ThrustCurveAPI() ThrustCurveAPI::ThrustCurveAPI()
: hostname("https://www.thrustcurve.org/api/v1/"), : hostname("https://www.thrustcurve.org/"),
curlConnection() curlConnection()
{ {
@ -25,17 +26,109 @@ 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) model::MotorModel ThrustCurveAPI::getMotorData(const std::string& motorId)
{ {
std::stringstream endpoint; std::stringstream endpoint;
endpoint << hostname << "download.json?motorId=" << motorId << "&data=samples"; endpoint << hostname << "api/v1/download.json?motorId=" << motorId << "&data=samples";
std::vector<std::string> extraHeaders = {}; std::vector<std::string> extraHeaders = {};
std::string res = curlConnection.get(endpoint.str(), extraHeaders); std::string res = curlConnection.get(endpoint.str(), extraHeaders);
/// TODO: fix this
model::MotorModel mm; 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; return mm;
} }
@ -43,7 +136,7 @@ ThrustcurveMetadata ThrustCurveAPI::getMetadata()
{ {
std::string endpoint = hostname; std::string endpoint = hostname;
endpoint += "metadata.json"; endpoint += "api/v1/metadata.json";
std::string result = curlConnection.get(endpoint, extraHeaders); std::string result = curlConnection.get(endpoint, extraHeaders);
ThrustcurveMetadata ret; ThrustcurveMetadata ret;
@ -123,7 +216,7 @@ std::vector<model::MotorModel> ThrustCurveAPI::searchMotors(const SearchCriteria
{ {
std::vector<model::MotorModel> retVal; std::vector<model::MotorModel> retVal;
std::string endpoint = hostname; std::string endpoint = hostname;
endpoint += "search.json?"; endpoint += "api/v1/search.json?";
for(const auto& i : c.criteria) for(const auto& i : c.criteria)
{ {
endpoint += i.first; endpoint += i.first;
@ -142,7 +235,9 @@ std::vector<model::MotorModel> ThrustCurveAPI::searchMotors(const SearchCriteria
{ {
Json::Reader reader; Json::Reader reader;
Json::Value jsonResult; Json::Value jsonResult;
Logger::getInstance()->debug("1");
reader.parse(result, jsonResult); reader.parse(result, jsonResult);
Logger::getInstance()->debug("2");
for(Json::ValueConstIterator iter = jsonResult["results"].begin(); for(Json::ValueConstIterator iter = jsonResult["results"].begin();
iter != jsonResult["results"].end(); iter != jsonResult["results"].end();
@ -151,6 +246,7 @@ std::vector<model::MotorModel> ThrustCurveAPI::searchMotors(const SearchCriteria
model::MotorModel motorModel; model::MotorModel motorModel;
model::MotorModel::MetaData mm; model::MotorModel::MetaData mm;
mm.commonName = (*iter)["commonName"].asString(); mm.commonName = (*iter)["commonName"].asString();
Logger::getInstance()->debug("3");
std::string availability = (*iter)["availability"].asString(); std::string availability = (*iter)["availability"].asString();
if(availability == "regular") if(availability == "regular")
@ -160,6 +256,7 @@ std::vector<model::MotorModel> ThrustCurveAPI::searchMotors(const SearchCriteria
mm.avgThrust = (*iter)["avgThrustN"].asDouble(); mm.avgThrust = (*iter)["avgThrustN"].asDouble();
mm.burnTime = (*iter)["burnTimeS"].asDouble(); mm.burnTime = (*iter)["burnTimeS"].asDouble();
Logger::getInstance()->debug("4");
// TODO fill in certOrg // TODO fill in certOrg
// TODO fill in delays // TODO fill in delays
mm.designation = (*iter)["designation"].asString(); mm.designation = (*iter)["designation"].asString();
@ -186,7 +283,14 @@ std::vector<model::MotorModel> ThrustCurveAPI::searchMotors(const SearchCriteria
else else
mm.type = model::MotorModel::MotorType(model::MotorModel::MOTORTYPE::HYBRID); mm.type = model::MotorModel::MotorType(model::MotorModel::MOTORTYPE::HYBRID);
Logger::getInstance()->debug("5");
auto tc = getThrustCurve(mm.motorIdTC);
motorModel.moveMetaData(std::move(mm)); motorModel.moveMetaData(std::move(mm));
Logger::getInstance()->debug("6");
if(tc)
{
motorModel.addThrustCurve(*tc);
}
retVal.push_back(motorModel); retVal.push_back(motorModel);
} }
} }

View File

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

View File

@ -1,21 +0,0 @@
#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,9 +6,19 @@ namespace utils::math
namespace Constants namespace Constants
{ {
constexpr double Rstar = 8.3144598; constexpr double Rstar = 8.31432;
constexpr const double g0 = 9.80665; constexpr const double g0 = 9.80665;
constexpr const double airMolarMass = 0.0289644; 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 standardTemperature = 288.15;
constexpr const double standardDensity = 1.2250; constexpr const double standardDensity = 1.2250;
constexpr const double meanEarthRadiusWGS84 = 6371008.8; constexpr const double meanEarthRadiusWGS84 = 6371008.8;

74
utils/math/MathTypes.h Normal file
View File

@ -0,0 +1,74 @@
#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

View File

@ -1,25 +0,0 @@
// 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

View File

@ -1,63 +0,0 @@
#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

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

View File

@ -1,68 +0,0 @@
// 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

View File

@ -1,40 +0,0 @@
#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