39 setWindowTitle(
"Physics Engine");
41 setCentralWidget(glWindow);
44 fpsLabel =
new QLabel(
this);
45 fpsLabel->setText(
"FPS: 0.0");
46 statusBar()->addPermanentWidget(fpsLabel);
49 fpsLabel->setText(QString(
"FPS: %1").arg(fps, 0,
'f', 1));
56void MainWindow::onGLInitialized() {
57 auto scene = std::make_unique<Scene>(glWindow);
58 Scene* scenePtr = scene.get();
59 sceneManager = std::make_unique<SceneManager>(glWindow, scenePtr);
60 glWindow->
setScene(std::move(scene));
64 sceneManager->defaultSetup();
69void MainWindow::setupDockWidgets() {
70 auto* infoDock =
new QDockWidget(tr(
"Scene Info"),
this);
71 infoDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
72 infoDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable);
74 auto* infoPanel =
new QWidget(infoDock);
75 auto* infoLayout =
new QFormLayout(infoPanel);
76 infoLayout->setContentsMargins(8, 8, 8, 8);
77 infoLayout->setSpacing(6);
79 cameraPositionLabel =
new QLabel(
"0, 0, 0", infoPanel);
80 cameraPositionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
81 selectedObjectLabel =
new QLabel(
"None", infoPanel);
82 selectedObjectPositionLabel =
new QLabel(
"-", infoPanel);
83 selectedObjectPositionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
84 selectedObjectDistanceLabel =
new QLabel(
"-", infoPanel);
85 simulationStateLabel =
new QLabel(
"Paused", infoPanel);
86 renderClockStateLabel =
new QLabel(
"Idle", infoPanel);
87 cameraFollowLabel =
new QLabel(
"Off", infoPanel);
89 infoLayout->addRow(
"Camera position", cameraPositionLabel);
90 infoLayout->addRow(
"Selected", selectedObjectLabel);
91 infoLayout->addRow(
"Selected position", selectedObjectPositionLabel);
92 infoLayout->addRow(
"Camera distance", selectedObjectDistanceLabel);
93 infoLayout->addRow(
"Camera follow", cameraFollowLabel);
94 infoLayout->addRow(
"Physics", simulationStateLabel);
95 infoLayout->addRow(
"Render clock", renderClockStateLabel);
97 auto* sceneInfoScrollArea =
new QScrollArea(infoDock);
98 sceneInfoScrollArea->setWidgetResizable(
true);
99 sceneInfoScrollArea->setFrameShape(QFrame::NoFrame);
100 sceneInfoScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
101 sceneInfoScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
102 sceneInfoScrollArea->setWidget(infoPanel);
104 infoDock->setWidget(sceneInfoScrollArea);
105 infoDock->setMinimumHeight(80);
106 infoDock->resize(300, 80);
107 addDockWidget(Qt::LeftDockWidgetArea, infoDock);
108 viewMenu->addAction(infoDock->toggleViewAction());
110 auto* hierarchyDock =
new QDockWidget(tr(
"Objects"),
this);
111 hierarchyDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
113 hierarchyDock->setWidget(hierarchy);
114 addDockWidget(Qt::LeftDockWidgetArea, hierarchyDock);
115 splitDockWidget(infoDock, hierarchyDock, Qt::Vertical);
116 resizeDocks({infoDock, hierarchyDock}, {120, 600}, Qt::Vertical);
117 viewMenu->addAction(hierarchyDock->toggleViewAction());
121 sceneManager->focusObject(obj);
122 glWindow->setFocus();
125 sceneManager->setCameraTarget(obj);
127 glWindow->setFocus();
130 sceneManager->clearCameraTarget();
132 glWindow->setFocus();
139 std::string requested = requestedName.toStdString();
140 if (requested.empty()) {
141 hierarchy->setObjectName(obj, obj->getName().data());
144 std::string finalName = requested;
146 if (!sceneManager->isNameUnique(requested, obj)) {
147 finalName = sceneManager->makeUniqueName(requested);
150 sceneManager->setObjectName(obj, finalName);
153 sceneManager->deleteObject(obj);
157 if (selectedInfoObject == obj)
158 selectedInfoObject = nullptr;
159 hierarchy->removeObject(obj);
160 inspector->unloadObject();
167 auto* inspectorDock =
new QDockWidget(tr(
"Inspector"),
this);
168 inspectorDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
169 QScrollArea* scrollArea =
new QScrollArea;
170 scrollArea->setWidgetResizable(
true);
171 scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
172 scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
173 scrollArea->setWidget(inspector);
174 scrollArea->setMinimumWidth(350);
175 scrollArea->setMinimumHeight(200);
177 inspectorDock->setWidget(scrollArea);
178 addDockWidget(Qt::LeftDockWidgetArea, inspectorDock);
179 viewMenu->addAction(inspectorDock->toggleViewAction());
181 auto* historyDock =
new QDockWidget(tr(
"Frame History"),
this);
182 historyDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
184 auto* tabs =
new QTabWidget(historyDock);
186 auto* tableView =
new QTableView(tabs);
187 tableView->horizontalHeader()->setStretchLastSection(
true);
190 tableView->setModel(snapshotModel);
191 tabs->addTab(tableView, tr(
"History"));
194 tabs->addTab(frameGraphPanel, tr(
"Graphs"));
196 historyDock->setWidget(tabs);
197 addDockWidget(Qt::RightDockWidgetArea, historyDock);
198 viewMenu->addAction(historyDock->toggleViewAction());
201void MainWindow::setupFileMenu() {
202 QMenu *fileMenu = menuBar()->addMenu(
"File");
203 QAction *saveAction =
new QAction(
"Save",
this);
204 fileMenu->addAction(saveAction);
205 QAction *saveAsAction =
new QAction(
"Save As",
this);
206 fileMenu->addAction(saveAsAction);
207 QAction *loadAction =
new QAction(
"Load",
this);
208 fileMenu->addAction(loadAction);
209 QAction *loadFromAction =
new QAction(
"Load From",
this);
210 fileMenu->addAction(loadFromAction);
212 connect(saveAction, &QAction::triggered,
this, [
this](){
213 if (sceneManager->saveScene(
"scene.json")) {
214 std::cout <<
"Save Success!" << std::endl;
215 statusBar()->showMessage(
"Scene saved to scene.json", 3000);
217 std::cout <<
"Save Failed!" << std::endl;
218 statusBar()->showMessage(
"Failed to save scene to scene.json", 3000);
221 connect(loadAction, &QAction::triggered,
this, [
this](){
222 if (sceneManager->loadScene(
"scene.json")) {
223 std::cout <<
"Load Success!" << std::endl;
224 statusBar()->showMessage(
"Scene loaded from scene.json", 3000);
226 std::cout <<
"Load Failed!" << std::endl;
227 statusBar()->showMessage(
"Failed to load scene from scene.json", 3000);
230 connect(saveAsAction, &QAction::triggered,
this, [
this](){
231 QFileDialog dialog(
this,
"Save Scene", QDir::currentPath(),
"JSON Files (*.json)");
232 dialog.setOption(QFileDialog::DontUseNativeDialog,
true);
233 dialog.setAcceptMode(QFileDialog::AcceptSave);
234 dialog.setDefaultSuffix(
"json");
236 if (dialog.exec() == QDialog::Accepted) {
237 const QString fileName = dialog.selectedFiles().value(0);
238 if (sceneManager->saveScene(fileName)) {
239 std::cout <<
"Save Success!" << std::endl;
240 statusBar()->showMessage(QString(
"Scene saved to %1").arg(fileName), 3000);
243 std::cout <<
"Save Failed!" << std::endl;
244 statusBar()->showMessage(QString(
"Failed to save scene to %1").arg(fileName), 3000);
248 connect(loadFromAction, &QAction::triggered,
this, [
this](){
249 QFileDialog dialog(
this,
"Load Scene", QDir::currentPath(),
"JSON Files (*.json)");
250 dialog.setOption(QFileDialog::DontUseNativeDialog,
true);
251 dialog.setFileMode(QFileDialog::ExistingFile);
253 if (dialog.exec() == QDialog::Accepted) {
254 const QString fileName = dialog.selectedFiles().value(0);
255 if (sceneManager->loadScene(fileName)) {
256 std::cout <<
"Load Success!" << std::endl;
257 statusBar()->showMessage(QString(
"Scene loaded from %1").arg(fileName), 3000);
259 std::cout <<
"Load Failed!" << std::endl;
260 statusBar()->showMessage(QString(
"Failed to load scene from %1").arg(fileName), 3000);
266void MainWindow::setupPresetMenu() {
267 QMenu* presetMenu = menuBar()->addMenu(
"Presets");
268 std::map<QString, QMenu*> categoryMenus;
271 const QString category = QString::fromUtf8(preset.category);
272 QMenu*& categoryMenu = categoryMenus[category];
274 categoryMenu = presetMenu->addMenu(category);
277 QAction* action = categoryMenu->addAction(QString::fromUtf8(preset.name));
278 action->setToolTip(QString::fromUtf8(preset.description));
280 connect(action, &QAction::triggered,
this, [
this, presetPtr]() {
281 if (sceneManager->loadPreset(*presetPtr)) {
282 snapshotModel->setSnapshots({});
283 frameGraphPanel->
clear();
285 statusBar()->showMessage(QString(
"Loaded preset: %1").arg(QString::fromUtf8(presetPtr->
name)), 3000);
287 statusBar()->showMessage(QString(
"Failed to load preset: %1").arg(QString::fromUtf8(presetPtr->
name)), 3000);
293void MainWindow::setupSettingMenu() {
294 QMenu *settingMenu = menuBar()->addMenu(
"Settings");
295 QAction *preferencesAction =
new QAction(
"Preferences",
this);
296 settingMenu->addAction(preferencesAction);
297 connect(preferencesAction, &QAction::triggered,
this, [
this]() {
302 Camera* camera = sceneManager->scene->getCamera();
303 camera->movementSpeed = camGroup.movementSpeed;
304 camera->mouseSensitivity = camGroup.mouseSensitivity;
305 camera->fov = camGroup.fov;
307 sceneManager->applyDebugSettings();
313void MainWindow::setupMenuBar() {
314 MainWindow::setupFileMenu();
315 MainWindow::setupPresetMenu();
316 viewMenu = menuBar()->addMenu(
"View");
317 MainWindow::setupSettingMenu();
320void MainWindow::loadAppSettings() {
326 Camera* camera = sceneManager->scene->getCamera();
327 camera->movementSpeed = camGroup.movementSpeed;
328 camera->mouseSensitivity = camGroup.mouseSensitivity;
329 camera->fov = camGroup.fov;
332 sceneManager->applyDebugSettings();
336void MainWindow::updateStatusPanel() {
337 if (!sceneManager || !sceneManager->scene || !sceneManager->scene->getCamera())
340 const glm::vec3 pos = sceneManager->scene->getCamera()->position;
341 cameraPositionLabel->setText(QString(
"x %1, y %2, z %3")
342 .arg(pos.x, 0,
'g', 6)
343 .arg(pos.y, 0,
'g', 6)
344 .arg(pos.z, 0,
'g', 6));
346 if (selectedInfoObject) {
347 const glm::vec3 selectedPos = selectedInfoObject->
getPosition();
348 selectedObjectLabel->setText(QString::fromStdString(selectedInfoObject->
getName()));
349 selectedObjectPositionLabel->setText(QString(
"x %1, y %2, z %3")
350 .arg(selectedPos.x, 0,
'g', 6)
351 .arg(selectedPos.y, 0,
'g', 6)
352 .arg(selectedPos.z, 0,
'g', 6));
353 selectedObjectDistanceLabel->setText(QString(
"%1 m").arg(glm::distance(pos, selectedPos), 0,
'g', 6));
355 selectedObjectLabel->setText(
"None");
356 selectedObjectPositionLabel->setText(
"-");
357 selectedObjectDistanceLabel->setText(
"-");
360 simulationStateLabel->setText(sceneManager->isPhysicsRunning() ? QString(
"Running") : QString(
"Paused"));
361 renderClockStateLabel->setText(glWindow->
isRenderClockRunning() ? QString(
"Running") : QString(
"Idle"));
362 if (
const SceneObject* followed = sceneManager->getCameraTarget()) {
363 cameraFollowLabel->setText(QString::fromStdString(followed->getName()));
365 cameraFollowLabel->setText(
"Off");
369void MainWindow::showObjectContextMenu(
const QPoint &pos,
SceneObject *obj) {
373 QAction* solveAction = contextMenu.addAction(
"Open Solver...");
374 QAction* followAction = contextMenu.addAction(
"Follow Camera");
375 QAction* clearFollowAction = contextMenu.addAction(
"Stop Camera Follow");
377 connect(solveAction, &QAction::triggered, [
this, obj]() {
381 const ProblemRouter *router = sceneManager->physicsSystem->getRouter();
384 if (dialog.exec() == QDialog::Accepted) {
385 auto knowns = dialog.getCollectedKnowns();
386 std::string unknown = dialog.getTargetUnknown();
388 sceneManager->physicsSystem->solveProblem(body, knowns, unknown);
391 qDebug() <<
"Selected object has no physics body attached.";
394 connect(followAction, &QAction::triggered, [
this, obj]() {
395 sceneManager->setCameraTarget(obj);
398 connect(clearFollowAction, &QAction::triggered, [
this]() {
399 sceneManager->clearCameraTarget();
403 contextMenu.exec(pos);
406 selectedInfoObject = current;
410 sceneManager->setSelectFor(previous,
false);
413 sceneManager->setSelectFor(current,
true);
414 sceneManager->setGizmoFor(current,
true);
418 selectedBody->withFrames(
BodyLock::LOCK, [
this](
const std::vector<ObjectSnapshot>& snapshots) {
424 frameGraphPanel->
clear();
429 frameGraphPanel->
clear();
std::variant< ObjectOptions, PointMassOptions, RigidBodyOptions > CreationOptions
void load(QSettings &settings)
static AppSettings & getInstance()
T * registerGroup(Args &&... args)
void loadSnapshots(const std::vector< ObjectSnapshot > &snapshots)
MainWindow(QWidget *parent=nullptr)
void setScene(std::unique_ptr< Scene > sc)
void fpsUpdated(double fps)
void setSceneManager(SceneManager *scm)
bool isRenderClockRunning() const
static Shader * getShader(const std::string &name)
void contextMenuRequested(const QPoint &globalPos, SceneObject *object)
void objectRemoved(SceneObject *obj)
void selectedItem(SceneObject *object)
void objectAdded(SceneObject *obj)
void objectRenamed(SceneObject *obj, const QString &newName)
glm::vec3 getPosition() const
Physics::PhysicsBody * getPhysicsBody() const
const std::string & getName() const
void setSnapshots(const std::vector< ObjectSnapshot > &snaps)
std::span< const PresetDescriptor > all()