Initial commit
This commit is contained in:
commit
b7d90b9afe
16245 files changed
+3396666
No files matched your search
+22
@@ -0,0 +1,22 @@
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
from mpl_toolkits.mplot3d import Axes3D
|
||||
|
||||
# Daten laden
|
||||
df = pd.read_csv('3d_flugbahnen.csv', sep=';')
|
||||
|
||||
fig = plt.figure(figsize=(10, 7))
|
||||
ax = fig.add_subplot(111, projection='3d')
|
||||
|
||||
# Jeden Schuss einzeln plotten
|
||||
for schuss_id in df['SchussID'].unique():
|
||||
daten = df[df['SchussID'] == schuss_id]
|
||||
ax.plot(daten['X'], daten['Z'], daten['Y'], alpha=0.6)
|
||||
|
||||
ax.set_xlabel('Distanz (X)')
|
||||
ax.set_ylabel('Seitlich (Z)')
|
||||
ax.set_zlabel('Höhe (Y)')
|
||||
ax.set_title('3D Flugbahnen mit Streuung')
|
||||
|
||||
plt.show()
|
||||
|
||||
+16449
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,111 @@
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <iostream>
|
||||
#include <fstream> // Für Dateioperationen
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
struct State {
|
||||
double t, x, y, z;
|
||||
};
|
||||
|
||||
class BallistikSimulator {
|
||||
private:
|
||||
const double g = 9.81;
|
||||
const double rho = 1.225;
|
||||
std::mt19937 gen;
|
||||
|
||||
|
||||
public:
|
||||
BallistikSimulator() : gen(std::random_device{}()) {}
|
||||
|
||||
std::vector<State> simuliereFlug(double v0, double winkelGrad, double masse, double cw, double flaeche, double streuung) {
|
||||
std::vector<State> trajektorie;
|
||||
double winkelRad = (winkelGrad * M_PI / 180.0);
|
||||
|
||||
// Zufallseinfluss auf den Startwinkel (Streuung)
|
||||
std::normal_distribution<double> d_winkel(0.0, streuung * 0.01);
|
||||
double variierterWinkel = winkelRad + d_winkel(gen);
|
||||
|
||||
double vx = v0 * std::cos(variierterWinkel);
|
||||
double vy = v0 * std::sin(variierterWinkel);
|
||||
double vz = d_winkel(gen) * v0; // Seitliche Abweichung
|
||||
|
||||
double x = 0, y = 0, z = 0, t = 0;
|
||||
double dt = 0.01;
|
||||
|
||||
while (y >= 0) {
|
||||
trajektorie.push_back({ t, x, y, z });
|
||||
|
||||
double v = std::sqrt(vx * vx + vy * vy + vz * vz);
|
||||
double Fw = 0.5 * rho * v * v * cw * flaeche;
|
||||
|
||||
vx += (-(Fw * (vx / v)) / masse) * dt;
|
||||
vy += (-g - (Fw * (vy / v)) / masse) * dt;
|
||||
vz += (-(Fw * (vz / v)) / masse) * dt;
|
||||
|
||||
x += vx * dt;
|
||||
y += vy * dt;
|
||||
z += vz * dt;
|
||||
t += dt;
|
||||
|
||||
if (trajektorie.size() > 20000) break;
|
||||
}
|
||||
return trajektorie;
|
||||
}
|
||||
|
||||
// Neue Funktion zum Speichern der Daten
|
||||
void speichereInCSV(const std::vector<State>& pfad, const std::string& dateiname) {
|
||||
std::ofstream datei(dateiname);
|
||||
|
||||
if (datei.is_open()) {
|
||||
// Header für die CSV-Datei
|
||||
datei << "Zeit(s);X(Distanz);Y(Hoehe);Z(Seite)\n";
|
||||
|
||||
for (const auto& p : pfad) {
|
||||
datei << p.t << ";" << p.x << ";" << p.y << ";" << p.z << "\n";
|
||||
}
|
||||
datei.close();
|
||||
std::cout << "Daten erfolgreich in " << dateiname << " gespeichert.\n";
|
||||
}
|
||||
else {
|
||||
std::cerr << "Fehler: Datei konnte nicht geöffnet werden!\n";
|
||||
}
|
||||
}
|
||||
|
||||
void exportiere3DTrajektorien(int anzahl, double v0, double winkel, double masse, double cw, double flaeche, double streuung) {
|
||||
std::ofstream datei("3d_flugbahnen.csv");
|
||||
datei << "SchussID;X;Y;Z\n";
|
||||
|
||||
for (int i = 0; i < anzahl; ++i) {
|
||||
auto pfad = simuliereFlug(v0, winkel, masse, cw, flaeche, streuung);
|
||||
for (const auto& p : pfad) {
|
||||
datei << i << ";" << p.x << ";" << p.y << ";" << p.z << "\n";
|
||||
}
|
||||
}
|
||||
datei.close();
|
||||
std::cout << "3D-Daten in '3d_flugbahnen.csv' gespeichert.\n";
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
BallistikSimulator sim;
|
||||
|
||||
// Simulations-Parameter
|
||||
double v0 = 400.0; // m/s
|
||||
double winkel = 20.0; // Grad
|
||||
double masse = 0.015; // 15 Gramm
|
||||
double cw = 0.29; // cw-Wert (Projektil)
|
||||
double flaeche = 0.00006; // Querschnittsflaeche
|
||||
double praezision = 0.2; // Streufaktor
|
||||
|
||||
// Flug simulieren
|
||||
auto pfad = sim.simuliereFlug(v0, winkel, masse, cw, flaeche, praezision);
|
||||
|
||||
// Als CSV speichern
|
||||
sim.speichereInCSV(pfad, "flugbahn.csv");
|
||||
sim.exportiere3DTrajektorien(10, v0, winkel, masse, cw, flaeche, praezision);
|
||||
|
||||
return 0;
|
||||
}
|
||||
+1653
File diff suppressed because it is too large.
Load diff
+16449
File diff suppressed because it is too large.
Load diff
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
+7
@@ -0,0 +1,7 @@
|
||||
/* @license
|
||||
Papa Parse
|
||||
v5.3.2
|
||||
https://github.com/mholt/PapaParse
|
||||
License: MIT
|
||||
*/
|
||||
!function(e,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof module&&"undefined"!=typeof exports?module.exports=t():e.Papa=t()}(this,function s(){"use strict";var f="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==f?f:{};var n=!f.document&&!!f.postMessage,o=n&&/blob:/i.test((f.location||{}).protocol),a={},h=0,b={parse:function(e,t){var i=(t=t||{}).dynamicTyping||!1;M(i)&&(t.dynamicTypingFunction=i,i={});if(t.dynamicTyping=i,t.transform=!!M(t.transform)&&t.transform,t.worker&&b.WORKERS_SUPPORTED){var r=function(){if(!b.WORKERS_SUPPORTED)return!1;var e=(i=f.URL||f.webkitURL||null,r=s.toString(),b.BLOB_URL||(b.BLOB_URL=i.createObjectURL(new Blob(["(",r,")();"],{type:"text/javascript"})))),t=new f.Worker(e);var i,r;return t.onmessage=_,t.id=h++,a[t.id]=t}();return r.userStep=t.step,r.userChunk=t.chunk,r.userComplete=t.complete,r.userError=t.error,t.step=M(t.step),t.chunk=M(t.chunk),t.complete=M(t.complete),t.error=M(t.error),delete t.worker,void r.postMessage({input:e,config:t,workerId:r.id})}var n=null;b.NODE_STREAM_INPUT,"string"==typeof e?n=t.download?new l(t):new p(t):!0===e.readable&&M(e.read)&&M(e.on)?n=new g(t):(f.File&&e instanceof File||e instanceof Object)&&(n=new c(t));return n.stream(e)},unparse:function(e,t){var n=!1,_=!0,m=",",y="\r\n",s='"',a=s+s,i=!1,r=null,o=!1;!function(){if("object"!=typeof t)return;"string"!=typeof t.delimiter||b.BAD_DELIMITERS.filter(function(e){return-1!==t.delimiter.indexOf(e)}).length||(m=t.delimiter);("boolean"==typeof t.quotes||"function"==typeof t.quotes||Array.isArray(t.quotes))&&(n=t.quotes);"boolean"!=typeof t.skipEmptyLines&&"string"!=typeof t.skipEmptyLines||(i=t.skipEmptyLines);"string"==typeof t.newline&&(y=t.newline);"string"==typeof t.quoteChar&&(s=t.quoteChar);"boolean"==typeof t.header&&(_=t.header);if(Array.isArray(t.columns)){if(0===t.columns.length)throw new Error("Option columns is empty");r=t.columns}void 0!==t.escapeChar&&(a=t.escapeChar+s);("boolean"==typeof t.escapeFormulae||t.escapeFormulae instanceof RegExp)&&(o=t.escapeFormulae instanceof RegExp?t.escapeFormulae:/^[=+\-@\t\r].*$/)}();var h=new RegExp(j(s),"g");"string"==typeof e&&(e=JSON.parse(e));if(Array.isArray(e)){if(!e.length||Array.isArray(e[0]))return u(null,e,i);if("object"==typeof e[0])return u(r||Object.keys(e[0]),e,i)}else if("object"==typeof e)return"string"==typeof e.data&&(e.data=JSON.parse(e.data)),Array.isArray(e.data)&&(e.fields||(e.fields=e.meta&&e.meta.fields||r),e.fields||(e.fields=Array.isArray(e.data[0])?e.fields:"object"==typeof e.data[0]?Object.keys(e.data[0]):[]),Array.isArray(e.data[0])||"object"==typeof e.data[0]||(e.data=[e.data])),u(e.fields||[],e.data||[],i);throw new Error("Unable to serialize unrecognized input");function u(e,t,i){var r="";"string"==typeof e&&(e=JSON.parse(e)),"string"==typeof t&&(t=JSON.parse(t));var n=Array.isArray(e)&&0<e.length,s=!Array.isArray(t[0]);if(n&&_){for(var a=0;a<e.length;a++)0<a&&(r+=m),r+=v(e[a],a);0<t.length&&(r+=y)}for(var o=0;o<t.length;o++){var h=n?e.length:t[o].length,u=!1,f=n?0===Object.keys(t[o]).length:0===t[o].length;if(i&&!n&&(u="greedy"===i?""===t[o].join("").trim():1===t[o].length&&0===t[o][0].length),"greedy"===i&&n){for(var d=[],l=0;l<h;l++){var c=s?e[l]:l;d.push(t[o][c])}u=""===d.join("").trim()}if(!u){for(var p=0;p<h;p++){0<p&&!f&&(r+=m);var g=n&&s?e[p]:p;r+=v(t[o][g],p)}o<t.length-1&&(!i||0<h&&!f)&&(r+=y)}}return r}function v(e,t){if(null==e)return"";if(e.constructor===Date)return JSON.stringify(e).slice(1,25);var i=!1;o&&"string"==typeof e&&o.test(e)&&(e="'"+e,i=!0);var r=e.toString().replace(h,a);return(i=i||!0===n||"function"==typeof n&&n(e,t)||Array.isArray(n)&&n[t]||function(e,t){for(var i=0;i<t.length;i++)if(-1<e.indexOf(t[i]))return!0;return!1}(r,b.BAD_DELIMITERS)||-1<r.indexOf(m)||" "===r.charAt(0)||" "===r.charAt(r.length-1))?s+r+s:r}}};if(b.RECORD_SEP=String.fromCharCode(30),b.UNIT_SEP=String.fromCharCode(31),b.BYTE_ORDER_MARK="\ufeff",b.BAD_DELIMITERS=["\r","\n",'"',b.BYTE_ORDER_MARK],b.WORKERS_SUPPORTED=!n&&!!f.Worker,b.NODE_STREAM_INPUT=1,b.LocalChunkSize=10485760,b.RemoteChunkSize=5242880,b.DefaultDelimiter=",",b.Parser=E,b.ParserHandle=i,b.NetworkStreamer=l,b.FileStreamer=c,b.StringStreamer=p,b.ReadableStreamStreamer=g,f.jQuery){var d=f.jQuery;d.fn.parse=function(o){var i=o.config||{},h=[];return this.each(function(e){if(!("INPUT"===d(this).prop("tagName").toUpperCase()&&"file"===d(this).attr("type").toLowerCase()&&f.FileReader)||!this.files||0===this.files.length)return!0;for(var t=0;t<this.files.length;t++)h.push({file:this.files[t],inputElem:this,instanceConfig:d.extend({},i)})}),e(),this;function e(){if(0!==h.length){var e,t,i,r,n=h[0];if(M(o.before)){var s=o.before(n.file,n.inputElem);if("object"==typeof s){if("abort"===s.action)return e="AbortError",t=n.file,i=n.inputElem,r=s.reason,void(M(o.error)&&o.error({name:e},t,i,r));if("skip"===s.action)return void u();"object"==typeof s.config&&(n.instanceConfig=d.extend(n.instanceConfig,s.config))}else if(Line truncated
|
||||
+3882
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,153 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>3D Kurven - Dynamisches Fenster</title>
|
||||
<!-- <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.2/papaparse.min.js"></script> -->
|
||||
<script src="lib/plotly-3.3.0.min.js" charset="utf-8"></script>
|
||||
<script src="lib/papaparse.min.js" charset="utf-8"></script>
|
||||
<style>
|
||||
body { margin: 0; font-family: 'Segoe UI', sans-serif; display: flex; flex-direction: column; height: 100vh; background: #111; color: white; }
|
||||
.header {
|
||||
padding: 15px;
|
||||
background: #222;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
border-bottom: 1px solid #444;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
#chart { flex-grow: 1; width: 100%; }
|
||||
.controls { display: flex; align-items: center; gap: 10px; }
|
||||
input[type="number"] {
|
||||
width: 60px;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #555;
|
||||
background: #333;
|
||||
color: white;
|
||||
}
|
||||
input[type="file"] { color: #aaa; }
|
||||
button {
|
||||
padding: 5px 15px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover { background: #0056b3; }
|
||||
.hint { font-size: 0.75em; color: #888; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<strong>3D Trajektorien</strong>
|
||||
<input type="file" id="csv-file" accept=".csv">
|
||||
|
||||
<div class="controls">
|
||||
<label for="point-count">Punkte:</label>
|
||||
<input type="number" id="point-count" value="20" min="0">
|
||||
<button id="update-btn">Anwenden</button>
|
||||
</div>
|
||||
|
||||
<span class="hint">(0 = alle Punkte)</span>
|
||||
</div>
|
||||
<div id="chart"></div>
|
||||
|
||||
<script>
|
||||
const chartDiv = document.getElementById('chart');
|
||||
const pointInput = document.getElementById('point-count');
|
||||
const updateBtn = document.getElementById('update-btn');
|
||||
let rawCSVData = null; // Speicher für die geladenen Daten
|
||||
|
||||
// 1. Datei einlesen
|
||||
document.getElementById('csv-file').addEventListener('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
Papa.parse(file, {
|
||||
header: true,
|
||||
skipEmptyLines: true,
|
||||
dynamicTyping: true,
|
||||
complete: function(results) {
|
||||
rawCSVData = results.data;
|
||||
renderData();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 2. Button-Event für die Aktualisierung
|
||||
updateBtn.addEventListener('click', renderData);
|
||||
|
||||
// Enter-Taste im Eingabefeld abfangen
|
||||
pointInput.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') renderData();
|
||||
});
|
||||
|
||||
function renderData() {
|
||||
if (!rawCSVData) return;
|
||||
|
||||
const windowSize = parseInt(pointInput.value) || 0;
|
||||
const curves = {};
|
||||
|
||||
// Daten nach ID gruppieren
|
||||
rawCSVData.forEach(row => {
|
||||
if (row.id === undefined || row.id === null) return;
|
||||
if (!curves[row.id]) {
|
||||
curves[row.id] = { x: [], y: [], z: [] };
|
||||
}
|
||||
curves[row.id].x.push(row.x);
|
||||
curves[row.id].y.push(row.y);
|
||||
curves[row.id].z.push(row.z);
|
||||
});
|
||||
|
||||
const traces = Object.keys(curves).map(id => {
|
||||
let x = curves[id].x;
|
||||
let y = curves[id].y;
|
||||
let z = curves[id].z;
|
||||
|
||||
// Fenster anwenden, wenn windowSize > 0
|
||||
if (windowSize > 0) {
|
||||
x = x.slice(-windowSize);
|
||||
y = y.slice(-windowSize);
|
||||
z = z.slice(-windowSize);
|
||||
}
|
||||
|
||||
return {
|
||||
x: x,
|
||||
y: y,
|
||||
z: z,
|
||||
mode: 'lines+markers',
|
||||
type: 'scatter3d',
|
||||
name: `ID ${id}`,
|
||||
line: { width: 4 },
|
||||
marker: {
|
||||
size: 3,
|
||||
// Verlaufseffekt: Die neuesten Punkte sind am kräftigsten
|
||||
opacity: x.map((_, i) => (i + 1) / x.length)
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const layout = {
|
||||
paper_bgcolor: '#111',
|
||||
font: { color: '#ccc' },
|
||||
scene: {
|
||||
aspectmode: 'data',
|
||||
xaxis: { gridcolor: '#333' },
|
||||
yaxis: { gridcolor: '#333' },
|
||||
zaxis: { gridcolor: '#333' }
|
||||
},
|
||||
margin: { l: 0, r: 0, b: 0, t: 0 },
|
||||
showlegend: true,
|
||||
legend: { x: 0, y: 1 }
|
||||
};
|
||||
|
||||
Plotly.newPlot(chartDiv, traces, layout, { responsive: true, displaylogo: false });
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(TrajectoryShader LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets OpenGLWidgets OpenGL)
|
||||
|
||||
qt_add_executable(TrajectoryShader
|
||||
main.cpp
|
||||
mainwindow.h
|
||||
mainwindow.cpp
|
||||
trajectorywidget.h
|
||||
trajectorywidget.cpp
|
||||
trajectorywarp.h trajectorywarp.cpp
|
||||
ballistiksimulator.h ballistiksimulator.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(TrajectoryShader
|
||||
PRIVATE
|
||||
Qt6::Widgets
|
||||
Qt6::OpenGLWidgets
|
||||
Qt6::OpenGL
|
||||
)
|
||||
|
||||
qt_finalize_executable(TrajectoryShader)
|
||||
@@ -0,0 +1,103 @@
|
||||
#define _USE_MATH_DEFINES
|
||||
#include "ballistiksimulator.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QDebug>
|
||||
|
||||
BallistikSimulator::BallistikSimulator()
|
||||
: gen(std::random_device{}())
|
||||
{
|
||||
}
|
||||
|
||||
QVector<TrajectoryPoint> BallistikSimulator::simuliereFlug(double v0,
|
||||
double winkelGrad,
|
||||
double masse,
|
||||
double cw,
|
||||
double flaeche,
|
||||
double streuung)
|
||||
{
|
||||
QVector<TrajectoryPoint> trajektorie;
|
||||
|
||||
double winkelRad = winkelGrad * M_PI / 180.0;
|
||||
|
||||
std::normal_distribution<double> d_winkel(0.0, streuung * 0.01);
|
||||
double variierterWinkel = winkelRad + d_winkel(gen);
|
||||
|
||||
double vx = v0 * std::cos(variierterWinkel);
|
||||
double vy = v0 * std::sin(variierterWinkel);
|
||||
double vz = d_winkel(gen) * v0;
|
||||
|
||||
double x = 0.0;
|
||||
double y = 0.0;
|
||||
double z = 0.0;
|
||||
double t = 0.0;
|
||||
double dt = 0.01;
|
||||
|
||||
while (y >= 0.0)
|
||||
{
|
||||
TrajectoryPoint trajectoryPoint;
|
||||
trajectoryPoint.time = t;
|
||||
trajectoryPoint.position.setX(x);
|
||||
trajectoryPoint.position.setY(y);
|
||||
trajectoryPoint.position.setZ(z);
|
||||
trajektorie.push_back(trajectoryPoint);
|
||||
|
||||
double v = std::sqrt(vx * vx + vy * vy + vz * vz);
|
||||
double Fw = 0.5 * rho * v * v * cw * flaeche;
|
||||
|
||||
vx += (-(Fw * (vx / v)) / masse) * dt;
|
||||
vy += (-g - (Fw * (vy / v)) / masse) * dt;
|
||||
vz += (-(Fw * (vz / v)) / masse) * dt;
|
||||
|
||||
x += vx * dt;
|
||||
y += vy * dt;
|
||||
z += vz * dt;
|
||||
t += dt;
|
||||
|
||||
if (trajektorie.size() > 20000)
|
||||
break;
|
||||
}
|
||||
|
||||
return trajektorie;
|
||||
}
|
||||
|
||||
void BallistikSimulator::speichereInCSV( const int pfadId,
|
||||
const QVector<TrajectoryPoint>& pfad,
|
||||
const QString& dateiname)
|
||||
{
|
||||
QFile datei(dateiname);
|
||||
|
||||
if (pfadId==0)
|
||||
{
|
||||
if (!datei.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
|
||||
qDebug() << "Datei konnte nicht geöffnet werden";
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!datei.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) {
|
||||
qDebug() << "Datei konnte nicht geöffnet werden";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QTextStream out(&datei);
|
||||
|
||||
if (pfadId == 0)
|
||||
out << "id,x,y,z\n";
|
||||
|
||||
for (const auto& p : pfad)
|
||||
{
|
||||
out << pfadId << ","
|
||||
<< p.position.x() << ","
|
||||
<< p.position.y() << ","
|
||||
<< p.position.z() << "\n";
|
||||
}
|
||||
|
||||
datei.close();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef BALLISTIKSIMULATOR_H
|
||||
#define BALLISTIKSIMULATOR_H
|
||||
|
||||
#include <random>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include "trajectorywarp.h"
|
||||
|
||||
class BallistikSimulator
|
||||
{
|
||||
public:
|
||||
BallistikSimulator();
|
||||
|
||||
QVector<TrajectoryPoint> simuliereFlug(double v0,
|
||||
double winkelGrad,
|
||||
double masse,
|
||||
double cw,
|
||||
double flaeche,
|
||||
double streuung);
|
||||
|
||||
void speichereInCSV(const int pfadId, const QVector<TrajectoryPoint> &pfad,
|
||||
const QString &dateiname);
|
||||
|
||||
private:
|
||||
const double g = 9.81;
|
||||
const double rho = 1.225;
|
||||
|
||||
std::mt19937 gen;
|
||||
};
|
||||
|
||||
#endif // BALLISTIKSIMULATOR_H
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
#include <QApplication>
|
||||
#include "ballistiksimulator.h"
|
||||
#include "trajectorywarp.h"
|
||||
#include "trajectorywidget.h"
|
||||
#include <QtMath>
|
||||
|
||||
|
||||
|
||||
Curve trajectoryPointsToCurve(const QVector<TrajectoryPoint>& trajectory,
|
||||
const QVector3D& color)
|
||||
{
|
||||
Curve curve;
|
||||
curve.color = color;
|
||||
|
||||
curve.points.reserve(trajectory.size());
|
||||
for (const auto& p : trajectory) {
|
||||
curve.points.append(p.position);
|
||||
}
|
||||
|
||||
return curve;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
|
||||
BallistikSimulator sim;
|
||||
|
||||
double v0 = 400.0;
|
||||
double winkel = 20.0;
|
||||
double masse = 0.015;
|
||||
double cw = 0.29;
|
||||
double flaeche = 0.00006;
|
||||
double praezision = 0.2;
|
||||
|
||||
QVector<TrajectoryPoint> original = sim.simuliereFlug(v0, winkel, masse, cw, flaeche, praezision);
|
||||
|
||||
sim.speichereInCSV(0,original, "flugbahn.csv");
|
||||
|
||||
//-------------------------------------------------------------
|
||||
|
||||
TrajectoryWidget widget;
|
||||
widget.resize(800, 600);
|
||||
widget.setWindowTitle("3D Trajectory Viewer");
|
||||
|
||||
QVector3D originalTarget = original.last().position;
|
||||
|
||||
// Ziel streuen
|
||||
QVector3D newTarget = originalTarget + QVector3D( 5.0, 5.0, 5.0);
|
||||
|
||||
auto warped = TrajectoryWarp::warpToNewTarget(original, newTarget);
|
||||
|
||||
sim.speichereInCSV(1,warped, "flugbahn.csv");
|
||||
|
||||
// Kurve 1
|
||||
Curve c1 = trajectoryPointsToCurve(original, QVector3D(1, 1, 0));
|
||||
|
||||
// Kurve 2
|
||||
Curve c2 = trajectoryPointsToCurve(warped, QVector3D(0, 1, 1));
|
||||
|
||||
QVector<Curve> curves;
|
||||
curves.append(c1);
|
||||
curves.append(c2);
|
||||
widget.setCurves(curves);
|
||||
widget.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "mainwindow.h"
|
||||
#include "trajectorywidget.h"
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
{
|
||||
setCentralWidget(new TrajectoryWidget(this));
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <QMainWindow>
|
||||
|
||||
class TrajectoryWidget;
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget"/>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,54 @@
|
||||
#include "trajectorywarp.h"
|
||||
|
||||
// Algorithmus: Zeitgewichtete Bézier-Korrektur (empfohlen)
|
||||
// Kerngedanke
|
||||
// Die ursprüngliche Trajektorie bleibt die Basis
|
||||
// Eine glatte Korrekturkurve wird addiert
|
||||
// Die Korrektur ist:
|
||||
// 0 am Start
|
||||
// maximal am Ziel
|
||||
// zeitlich C¹-stetig
|
||||
// Formel
|
||||
// Für jeden Punkt:
|
||||
// Pneu(t) = Palt(t) + w(t) · ΔZ
|
||||
// mit
|
||||
// ΔZ = Zneu − Zalt
|
||||
// w(t) = 3s² − 2s³ (smoothstep)
|
||||
// s = (t − t0) / (tEnd − t0)
|
||||
// Dadurch:
|
||||
// Start bleibt exakt gleich
|
||||
// Zielpunkt wird exakt erreicht
|
||||
// Bewegung bleibt glatt
|
||||
|
||||
QVector<TrajectoryPoint> TrajectoryWarp::warpToNewTarget(
|
||||
const QVector<TrajectoryPoint>& original,
|
||||
const QVector3D& newTarget)
|
||||
{
|
||||
if (original.size() < 2)
|
||||
return original;
|
||||
|
||||
QVector<TrajectoryPoint> result = original;
|
||||
|
||||
const QVector3D& oldTarget = original.last().position;
|
||||
QVector3D delta = newTarget - oldTarget;
|
||||
|
||||
double t0 = original.first().time;
|
||||
double tEnd = original.last().time;
|
||||
double dt = tEnd - t0;
|
||||
|
||||
for (int i = 0; i < result.size(); ++i)
|
||||
{
|
||||
double s = (result[i].time - t0) / dt;
|
||||
s = std::clamp(s, 0.0, 1.0);
|
||||
|
||||
double w = smoothStep(s);
|
||||
result[i].position += delta * w;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
double TrajectoryWarp::smoothStep(double x)
|
||||
{
|
||||
return x * x * (3.0 - 2.0 * x);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <QVector>
|
||||
#include <QVector3D>
|
||||
|
||||
struct TrajectoryPoint
|
||||
{
|
||||
QVector3D position;
|
||||
double time;
|
||||
};
|
||||
|
||||
class TrajectoryWarp
|
||||
{
|
||||
public:
|
||||
static QVector<TrajectoryPoint> warpToNewTarget(
|
||||
const QVector<TrajectoryPoint>& original,
|
||||
const QVector3D& newTarget
|
||||
);
|
||||
|
||||
private:
|
||||
static double smoothStep(double x);
|
||||
};
|
||||
@@ -0,0 +1,200 @@
|
||||
#include "trajectorywidget.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QMatrix4x4>
|
||||
#include <QDebug>
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
|
||||
TrajectoryWidget::TrajectoryWidget(QWidget *parent)
|
||||
: QOpenGLWidget(parent)
|
||||
{
|
||||
}
|
||||
|
||||
/* ================= OpenGL ================= */
|
||||
|
||||
void TrajectoryWidget::initializeGL()
|
||||
{
|
||||
initializeOpenGLFunctions();
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
const char *vertexShaderSrc = R"(
|
||||
#version 130
|
||||
uniform mat4 mvp;
|
||||
in vec3 position;
|
||||
void main()
|
||||
{
|
||||
gl_Position = mvp * vec4(position, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
const char *fragmentShaderSrc = R"(
|
||||
#version 130
|
||||
uniform vec3 color;
|
||||
out vec4 fragColor;
|
||||
void main()
|
||||
{
|
||||
fragColor = vec4(color, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
if (!m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc))
|
||||
qDebug() << "Vertex shader error:" << m_program.log();
|
||||
|
||||
if (!m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc))
|
||||
qDebug() << "Fragment shader error:" << m_program.log();
|
||||
|
||||
if (!m_program.link())
|
||||
qDebug() << "Shader link error:" << m_program.log();
|
||||
}
|
||||
|
||||
void TrajectoryWidget::resizeGL(int w, int h)
|
||||
{
|
||||
glViewport(0, 0, w, h);
|
||||
}
|
||||
|
||||
void TrajectoryWidget::paintGL()
|
||||
{
|
||||
glClearColor(0.15f, 0.15f, 0.2f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
if (m_curves.isEmpty())
|
||||
return;
|
||||
|
||||
QMatrix4x4 projection;
|
||||
projection.perspective(
|
||||
45.0f,
|
||||
float(width()) / float(height()),
|
||||
0.01f,
|
||||
100000.0f
|
||||
);
|
||||
|
||||
QMatrix4x4 view;
|
||||
view.translate(0, 0, m_zoom);
|
||||
view.rotate(m_rotX, 1, 0, 0);
|
||||
view.rotate(m_rotY, 0, 1, 0);
|
||||
view.translate(-m_viewCenter);
|
||||
|
||||
QMatrix4x4 mvp = projection * view;
|
||||
|
||||
m_program.bind();
|
||||
m_program.setUniformValue("mvp", mvp);
|
||||
|
||||
for (const auto &curve : m_curves) {
|
||||
if (curve.points.isEmpty())
|
||||
continue;
|
||||
|
||||
m_program.setUniformValue("color", curve.color);
|
||||
m_program.enableAttributeArray(0);
|
||||
m_program.setAttributeArray(
|
||||
0,
|
||||
GL_FLOAT,
|
||||
curve.points.constData(),
|
||||
3
|
||||
);
|
||||
|
||||
glDrawArrays(GL_LINE_STRIP, 0, curve.points.size());
|
||||
|
||||
m_program.disableAttributeArray(0);
|
||||
}
|
||||
|
||||
m_program.release();
|
||||
}
|
||||
|
||||
/* ================= Public API ================= */
|
||||
|
||||
void TrajectoryWidget::setCurves(const QVector<Curve> &curves)
|
||||
{
|
||||
m_curves = curves;
|
||||
fitViewToCurves();
|
||||
update();
|
||||
}
|
||||
|
||||
void TrajectoryWidget::addCurve(const Curve &curve)
|
||||
{
|
||||
m_curves.append(curve);
|
||||
fitViewToCurves();
|
||||
update();
|
||||
}
|
||||
|
||||
void TrajectoryWidget::clearCurves()
|
||||
{
|
||||
m_curves.clear();
|
||||
update();
|
||||
}
|
||||
|
||||
/* ================= View fitting ================= */
|
||||
|
||||
void TrajectoryWidget::fitViewToCurves()
|
||||
{
|
||||
if (m_curves.isEmpty())
|
||||
return;
|
||||
|
||||
QVector3D minP(
|
||||
std::numeric_limits<float>::max(),
|
||||
std::numeric_limits<float>::max(),
|
||||
std::numeric_limits<float>::max()
|
||||
);
|
||||
|
||||
QVector3D maxP(
|
||||
std::numeric_limits<float>::lowest(),
|
||||
std::numeric_limits<float>::lowest(),
|
||||
std::numeric_limits<float>::lowest()
|
||||
);
|
||||
|
||||
bool hasPoints = false;
|
||||
|
||||
for (const auto &curve : m_curves) {
|
||||
for (const auto &p : curve.points) {
|
||||
minP.setX(std::min(minP.x(), p.x()));
|
||||
minP.setY(std::min(minP.y(), p.y()));
|
||||
minP.setZ(std::min(minP.z(), p.z()));
|
||||
|
||||
maxP.setX(std::max(maxP.x(), p.x()));
|
||||
maxP.setY(std::max(maxP.y(), p.y()));
|
||||
maxP.setZ(std::max(maxP.z(), p.z()));
|
||||
|
||||
hasPoints = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPoints)
|
||||
return;
|
||||
|
||||
m_viewCenter = (minP + maxP) * 0.5f;
|
||||
|
||||
float dx = maxP.x() - minP.x();
|
||||
float dy = maxP.y() - minP.y();
|
||||
float dz = maxP.z() - minP.z();
|
||||
|
||||
float radius = std::max({dx, dy, dz}) * 0.5f;
|
||||
|
||||
m_zoom = -radius * 2.5f;
|
||||
if (m_zoom > -1.0f)
|
||||
m_zoom = -1.0f;
|
||||
}
|
||||
|
||||
/* ================= Interaction ================= */
|
||||
|
||||
void TrajectoryWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
m_lastMousePos = event->pos();
|
||||
}
|
||||
|
||||
void TrajectoryWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
QPoint delta = event->pos() - m_lastMousePos;
|
||||
m_lastMousePos = event->pos();
|
||||
|
||||
if (event->buttons() & Qt::LeftButton) {
|
||||
m_rotX += delta.y();
|
||||
m_rotY += delta.x();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void TrajectoryWidget::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
m_zoom += event->angleDelta().y() * 0.01f;
|
||||
update();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <QOpenGLWidget>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QVector3D>
|
||||
#include <QVector>
|
||||
#include <QPoint>
|
||||
|
||||
struct Curve {
|
||||
QVector<QVector3D> points;
|
||||
QVector3D color;
|
||||
};
|
||||
|
||||
class TrajectoryWidget : public QOpenGLWidget, protected QOpenGLFunctions
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TrajectoryWidget(QWidget *parent = nullptr);
|
||||
|
||||
void setCurves(const QVector<Curve>& curves);
|
||||
void addCurve(const Curve& curve);
|
||||
void clearCurves();
|
||||
|
||||
protected:
|
||||
void initializeGL() override;
|
||||
void resizeGL(int w, int h) override;
|
||||
void paintGL() override;
|
||||
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void wheelEvent(QWheelEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void fitViewToCurves();
|
||||
|
||||
private:
|
||||
QVector<Curve> m_curves;
|
||||
|
||||
QOpenGLShaderProgram m_program;
|
||||
float m_rotX = 20.0f;
|
||||
float m_rotY = -30.0f;
|
||||
float m_zoom = -10.0f;
|
||||
QPoint m_lastMousePos;
|
||||
QVector3D m_viewCenter{0, 0, 0};
|
||||
};
|
||||
Reference in new issue
Block a user