ericw-tools/lightpreview/mainwindow.cpp

828 lines
26 KiB
C++

/* Copyright (C) 2017 Eric Wasylishen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
See file, 'COPYING', for details.
*/
#include "mainwindow.h"
#include <QCoreApplication>
#include <QDockWidget>
#include <QString>
#include <QDragEnterEvent>
#include <QMimeData>
#include <QFileSystemWatcher>
#include <QFileInfo>
#include <QFormLayout>
#include <QLineEdit>
#include <QSplitter>
#include <QCheckBox>
#include <QPushButton>
#include <QSettings>
#include <QMenu>
#include <QMenuBar>
#include <QFileDialog>
#include <QGroupBox>
#include <QRadioButton>
#include <QTimer>
#include <QScrollArea>
#include <QSpinBox>
#include <QScrollBar>
#include <QFrame>
#include <QLabel>
#include <QTextEdit>
#include <QStatusBar>
#include <QStringList>
#include <QThread>
#include <QApplication>
#include <common/bspfile.hh>
#include <qbsp/qbsp.hh>
#include <vis/vis.hh>
#include <light/light.hh>
#include <common/bspinfo.hh>
#include <fmt/chrono.h>
#include "glview.h"
// Recent files
static constexpr auto RECENT_SETTINGS_KEY = "recent_files";
static constexpr size_t MAX_RECENTS = 10;
static void ClearRecents()
{
QSettings s;
s.setValue(RECENT_SETTINGS_KEY, QStringList());
}
/**
* Updates the recent files settings by pushing the given file to the front
* and trimming the list to SETTINGS_MAX.
*
* @param file the file to push
* @return the new recent files list
*/
static QStringList AddRecent(const QString &file)
{
QSettings s;
QStringList recents = s.value(RECENT_SETTINGS_KEY).toStringList();
recents.removeOne(file); // no-op if not present
recents.push_front(file);
while (recents.size() > MAX_RECENTS) {
recents.pop_back();
}
s.setValue(RECENT_SETTINGS_KEY, recents);
return recents;
}
static QStringList GetRecents()
{
QSettings s;
QStringList recents = s.value(RECENT_SETTINGS_KEY).toStringList();
return recents;
}
// ETLogWidget
ETLogWidget::ETLogWidget(QWidget *parent)
: QTabWidget(parent)
{
for (size_t i = 0; i < std::size(logTabNames); i++) {
m_textEdits[i] = new QTextEdit();
auto *formLayout = new QFormLayout();
auto *form = new QWidget();
formLayout->addRow(m_textEdits[i]);
form->setLayout(formLayout);
setTabText(i, logTabNames[i]);
addTab(form, logTabNames[i]);
formLayout->setContentsMargins(0, 0, 0, 0);
}
}
// MainWindow
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
// create the menu first as it is used by other things (dock widgets)
setupMenu();
resize(640, 480);
// gl view
glView = new GLView(this);
setCentralWidget(glView);
setAcceptDrops(true);
createPropertiesSidebar();
createOutputLog();
createStatusBar();
}
void MainWindow::createPropertiesSidebar()
{
QDockWidget *dock = new QDockWidget(tr("Properties"), this);
auto *formLayout = new QFormLayout();
vis_checkbox = new QCheckBox(tr("vis"));
common_options = new QLineEdit();
qbsp_options = new QLineEdit();
vis_options = new QLineEdit();
light_options = new QLineEdit();
auto *reload_button = new QPushButton(tr("Reload"));
auto *lightmapped = new QRadioButton(tr("Lightmapped"));
lightmapped->setChecked(true);
auto *lightmap_only = new QRadioButton(tr("Lightmap Only"));
auto *fullbright = new QRadioButton(tr("Fullbright"));
auto *normals = new QRadioButton(tr("Normals"));
auto *drawflat = new QRadioButton(tr("Flat shading"));
lightmapped->setShortcut(QKeySequence("Alt+1"));
lightmap_only->setShortcut(QKeySequence("Alt+2"));
fullbright->setShortcut(QKeySequence("Alt+3"));
normals->setShortcut(QKeySequence("Alt+4"));
drawflat->setShortcut(QKeySequence("Alt+5"));
lightmapped->setToolTip("Lighmapped textures (Alt+1)");
lightmap_only->setToolTip("Lightmap only (Alt+2)");
fullbright->setToolTip("Textures without lightmap (Alt+3)");
normals->setToolTip("Visualize normals (Alt+4)");
drawflat->setToolTip("Flat-shaded polygons (Alt+5)");
auto *rendermode_layout = new QVBoxLayout();
rendermode_layout->addWidget(lightmapped);
rendermode_layout->addWidget(lightmap_only);
rendermode_layout->addWidget(fullbright);
rendermode_layout->addWidget(normals);
rendermode_layout->addWidget(drawflat);
auto *rendermode_group = new QGroupBox(tr("Render mode"));
rendermode_group->setLayout(rendermode_layout);
auto *drawportals = new QCheckBox(tr("Draw Portals (PRT)"));
auto *drawleak = new QCheckBox(tr("Draw Leak (PTS/LIN)"));
auto *showtris = new QCheckBox(tr("Show Tris"));
auto *showtris_seethrough = new QCheckBox(tr("Show Tris (See Through)"));
auto *visculling = new QCheckBox(tr("Vis Culling"));
visculling->setChecked(true);
auto *keepposition = new QCheckBox(tr("Keep Camera Pos"));
nearest = new QCheckBox(tr("Nearest Filter"));
bspx_decoupled_lm = new QCheckBox(tr("BSPX: Decoupled Lightmap"));
bspx_decoupled_lm->setChecked(true);
bspx_normals = new QCheckBox(tr("BSPX: Face Normals"));
bspx_normals->setChecked(true);
auto *draw_opaque = new QCheckBox(tr("Draw Translucency as Opaque"));
formLayout->addRow(tr("common"), common_options);
formLayout->addRow(tr("qbsp"), qbsp_options);
formLayout->addRow(vis_checkbox, vis_options);
formLayout->addRow(tr("light"), light_options);
formLayout->addRow(reload_button);
formLayout->addRow(rendermode_group);
formLayout->addRow(drawportals);
formLayout->addRow(drawleak);
formLayout->addRow(showtris);
formLayout->addRow(showtris_seethrough);
formLayout->addRow(visculling);
formLayout->addRow(keepposition);
formLayout->addRow(nearest);
formLayout->addRow(bspx_decoupled_lm);
formLayout->addRow(bspx_normals);
formLayout->addRow(draw_opaque);
lightstyles = new QVBoxLayout();
auto *lightstyles_group = new QGroupBox(tr("Lightstyles"));
lightstyles_group->setLayout(lightstyles);
auto *scrollArea = new QScrollArea();
scrollArea->setWidgetResizable(true);
scrollArea->setWidget(lightstyles_group);
scrollArea->setBackgroundRole(QPalette::Window);
scrollArea->setFrameShadow(QFrame::Plain);
scrollArea->setFrameShape(QFrame::NoFrame);
formLayout->addRow(scrollArea);
auto *form = new QWidget();
form->setLayout(formLayout);
// finish dock setup
dock->setWidget(form);
addDockWidget(Qt::LeftDockWidgetArea, dock);
viewMenu->addAction(dock->toggleViewAction());
// load state persisted in settings
QSettings s;
common_options->setText(s.value("common_options").toString());
qbsp_options->setText(s.value("qbsp_options").toString());
vis_checkbox->setChecked(s.value("vis_enabled").toBool());
vis_options->setText(s.value("vis_options").toString());
light_options->setText(s.value("light_options").toString());
nearest->setChecked(s.value("nearest").toBool());
if (nearest->isChecked()) {
glView->setMagFilter(QOpenGLTexture::Nearest);
}
// setup event handlers
connect(reload_button, &QAbstractButton::clicked, this, &MainWindow::reload);
connect(lightmap_only, &QAbstractButton::toggled, this, [=](bool checked) { glView->setLighmapOnly(checked); });
connect(fullbright, &QAbstractButton::toggled, this, [=](bool checked) { glView->setFullbright(checked); });
connect(normals, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawNormals(checked); });
connect(showtris, &QAbstractButton::toggled, this, [=](bool checked) { glView->setShowTris(checked); });
connect(showtris_seethrough, &QAbstractButton::toggled, this,
[=](bool checked) { glView->setShowTrisSeeThrough(checked); });
connect(visculling, &QAbstractButton::toggled, this, [=](bool checked) { glView->setVisCulling(checked); });
connect(drawflat, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawFlat(checked); });
connect(drawportals, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawPortals(checked); });
connect(drawleak, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawLeak(checked); });
connect(keepposition, &QAbstractButton::toggled, this, [=](bool checked) { glView->setKeepOrigin(checked); });
connect(nearest, &QAbstractButton::toggled, this,
[=](bool checked) { glView->setMagFilter(checked ? QOpenGLTexture::Nearest : QOpenGLTexture::Linear); });
connect(draw_opaque, &QAbstractButton::toggled, this,
[=](bool checked) { glView->setDrawTranslucencyAsOpaque(checked); });
connect(glView, &GLView::cameraMoved, this, &MainWindow::displayCameraPositionInfo);
// set up load timer
m_fileReloadTimer = std::make_unique<QTimer>();
m_fileReloadTimer->setSingleShot(true);
m_fileReloadTimer->connect(m_fileReloadTimer.get(), &QTimer::timeout, this, &MainWindow::fileReloadTimerExpired);
}
void MainWindow::logWidgetSetText(ETLogTab tab, const std::string &str)
{
m_outputLogWidget->setTabText((int32_t)tab, str.c_str());
}
void MainWindow::lightpreview_percent_callback(std::optional<uint32_t> percent, std::optional<duration> elapsed)
{
int32_t tabIndex = (int32_t)m_activeLogTab;
if (elapsed.has_value()) {
lightpreview_log_callback(
logging::flag::PROGRESS, fmt::format("finished in: {:.3}\n", elapsed.value()).c_str());
QMetaObject::invokeMethod(
this, std::bind(&MainWindow::logWidgetSetText, this, m_activeLogTab, ETLogWidget::logTabNames[tabIndex]));
} else {
if (percent.has_value()) {
QMetaObject::invokeMethod(
this, std::bind(&MainWindow::logWidgetSetText, this, m_activeLogTab,
fmt::format("{} [{:>3}%]", ETLogWidget::logTabNames[tabIndex], percent.value())));
} else {
QMetaObject::invokeMethod(this, std::bind(&MainWindow::logWidgetSetText, this, m_activeLogTab,
fmt::format("{} (...)", ETLogWidget::logTabNames[tabIndex])));
}
}
}
void MainWindow::lightpreview_log_callback(logging::flag flags, const char *str)
{
if (bitflags(flags) & logging::flag::PERCENT)
return;
if (QApplication::instance()->thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this,
std::bind([this, flags](const std::string &s) -> void { lightpreview_log_callback(flags, s.c_str()); },
std::string(str)));
return;
}
auto *textEdit = m_outputLogWidget->textEdit(m_activeLogTab);
const bool atBottom = textEdit->verticalScrollBar()->value() == textEdit->verticalScrollBar()->maximum();
QTextDocument *doc = textEdit->document();
QTextCursor cursor(doc);
cursor.movePosition(QTextCursor::End);
cursor.beginEditBlock();
cursor.insertBlock();
cursor.insertHtml(QString::asprintf("%s\n", str));
cursor.endEditBlock();
// scroll scrollarea to bottom if it was at bottom when we started
//(we don't want to force scrolling to bottom if user is looking at a
// higher position)
if (atBottom) {
QScrollBar *bar = textEdit->verticalScrollBar();
bar->setValue(bar->maximum());
}
}
void MainWindow::createOutputLog()
{
QDockWidget *dock = new QDockWidget(tr("Tool Logs"), this);
m_outputLogWidget = new ETLogWidget();
// finish dock widget setup
dock->setWidget(m_outputLogWidget);
addDockWidget(Qt::BottomDockWidgetArea, dock);
viewMenu->addAction(dock->toggleViewAction());
logging::set_print_callback(
std::bind(&MainWindow::lightpreview_log_callback, this, std::placeholders::_1, std::placeholders::_2));
logging::set_percent_callback(
std::bind(&MainWindow::lightpreview_percent_callback, this, std::placeholders::_1, std::placeholders::_2));
}
void MainWindow::createStatusBar()
{
statusBar();
}
/**
* Precondition: openRecentMenu is created.
*
* Clears and rebuilds the menu given the list of files that should be displayed in it.
*/
void MainWindow::updateRecentsSubmenu(const QStringList &recents)
{
openRecentMenu->clear();
for (const QString &recent : recents) {
auto *action = openRecentMenu->addAction(recent);
connect(action, &QAction::triggered, this, [this, recent]() { loadFile(recent); });
}
openRecentMenu->addSeparator();
openRecentMenu->addAction(tr("Clear Recents"), this, [this]() {
ClearRecents();
this->updateRecentsSubmenu(GetRecents());
});
}
MainWindow::~MainWindow() { }
void MainWindow::setupMenu()
{
auto *menu = menuBar()->addMenu(tr("&File"));
auto *open = menu->addAction(tr("&Open"), this, &MainWindow::fileOpen);
open->setShortcut(QKeySequence::Open);
openRecentMenu = menu->addMenu(tr("Open &Recent"));
updateRecentsSubmenu(GetRecents());
menu->addSeparator();
auto *takeScreenshot = menu->addAction(tr("Save Screenshot..."), this, &MainWindow::takeScreenshot);
menu->addSeparator();
auto *exit = menu->addAction(tr("E&xit"), this, &QWidget::close);
exit->setShortcut(QKeySequence::Quit);
// view menu
viewMenu = menuBar()->addMenu(tr("&View"));
}
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls())
event->acceptProposedAction();
}
void MainWindow::dropEvent(QDropEvent *event)
{
auto urls = event->mimeData()->urls();
if (!urls.empty()) {
const QUrl &url = urls[0];
if (url.isLocalFile()) {
loadFile(url.toLocalFile());
event->acceptProposedAction();
}
}
}
void MainWindow::showEvent(QShowEvent *event)
{
// FIXME: move command-line parsing somewhere else?
// FIXME: support more command-line options?
auto args = QCoreApplication::arguments();
if (args.size() == 2) {
QTimer::singleShot(0, this, [=] { loadFile(args.at(1)); });
}
}
void MainWindow::fileOpen()
{
// open the file browser in the directory containing the currently open file, if there is one
QString currentDir;
if (!m_mapFile.isEmpty()) {
currentDir = QFileInfo(m_mapFile).absolutePath();
}
QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), currentDir, tr("Map (*.map);; BSP (*.bsp)"));
if (!fileName.isEmpty())
loadFile(fileName);
}
void MainWindow::takeScreenshot()
{
QString fileName = QFileDialog::getSaveFileName(this, tr("Save Screenshot"), "", tr("PNG (*.png)"));
if (!fileName.isEmpty())
glView->takeScreenshot(fileName, 3840, 2160);
}
void MainWindow::fileReloadTimerExpired()
{
qint64 currentSize = QFileInfo(m_mapFile).size();
// it was rewritten...
if (currentSize != m_fileSize) {
qDebug() << "size changed since last write, restarting timer";
m_fileReloadTimer->start(150);
return;
}
// good to go? maybe?
qDebug() << "size not changed, good to go";
loadFileInternal(m_mapFile, true);
m_fileSize = -1;
}
void MainWindow::loadFile(const QString &file)
{
qDebug() << "load " << file;
// update recents
updateRecentsSubmenu(AddRecent(file));
m_mapFile = file;
if (m_watcher) {
delete m_watcher;
}
m_watcher = new QFileSystemWatcher(this);
m_fileSize = -1;
// start watching it
qDebug() << "adding path: " << m_watcher->addPath(file);
connect(m_watcher, &QFileSystemWatcher::fileChanged, this, [&](const QString &path) {
qDebug() << "got change notif for " << m_mapFile;
// check current files' size
m_fileSize = QFileInfo(m_mapFile).size();
// start timer
m_fileReloadTimer->start(150);
});
loadFileInternal(file, false);
}
std::filesystem::path MakeFSPath(const QString &string)
{
return std::filesystem::path{string.toStdU16String()};
}
bspdata_t MainWindow::QbspVisLight_Common(const std::filesystem::path &name, std::vector<std::string> extra_common_args,
std::vector<std::string> extra_qbsp_args, std::vector<std::string> extra_vis_args,
std::vector<std::string> extra_light_args, bool run_vis)
{
auto resetActiveTabText = [&]() {
QMetaObject::invokeMethod(this, std::bind(&MainWindow::logWidgetSetText, this, m_activeLogTab,
ETLogWidget::logTabNames[(int32_t)m_activeLogTab]));
};
auto bsp_path = name;
bsp_path.replace_extension(".bsp");
std::vector<std::string> args{
"", // the exe path, which we're ignoring in this case
};
for (auto &extra : extra_common_args) {
args.push_back(extra);
}
for (auto &extra : extra_qbsp_args) {
args.push_back(extra);
}
args.push_back(name.string());
// run qbsp
m_activeLogTab = ETLogTab::TAB_BSP;
InitQBSP(args);
ProcessFile();
resetActiveTabText();
// run vis
if (run_vis) {
m_activeLogTab = ETLogTab::TAB_VIS;
std::vector<std::string> vis_args{
"", // the exe path, which we're ignoring in this case
};
for (auto &extra : extra_common_args) {
vis_args.push_back(extra);
}
for (auto &extra : extra_vis_args) {
vis_args.push_back(extra);
}
vis_args.push_back(name.string());
vis_main(vis_args);
}
resetActiveTabText();
// run light
{
m_activeLogTab = ETLogTab::TAB_LIGHT;
std::vector<std::string> light_args{
"", // the exe path, which we're ignoring in this case
};
for (auto &extra : extra_common_args) {
light_args.push_back(extra);
}
for (auto &arg : extra_light_args) {
light_args.push_back(arg);
}
light_args.push_back(name.string());
light_main(light_args);
}
resetActiveTabText();
m_activeLogTab = ETLogTab::TAB_LIGHTPREVIEW;
// serialize obj
{
bspdata_t bspdata;
LoadBSPFile(bsp_path, &bspdata);
ConvertBSPFormat(&bspdata, &bspver_generic);
return bspdata;
}
}
static std::vector<std::string> ParseArgs(const QLineEdit *line_edit)
{
std::vector<std::string> result;
QString text = line_edit->text().trimmed();
if (text.isEmpty())
return result;
bool inside_quotes = false;
for (const auto &str : text.split('"')) {
qDebug() << "got token " << str << " inside quote? " << inside_quotes;
if (inside_quotes) {
result.push_back(str.toStdString());
} else {
// split by spaces
for (const auto &str2 : str.split(' ', Qt::SkipEmptyParts)) {
qDebug() << "got sub token " << str2;
result.push_back(str2.toStdString());
}
}
inside_quotes = !inside_quotes;
}
return result;
}
void MainWindow::reload()
{
if (m_mapFile.isEmpty())
return;
loadFileInternal(m_mapFile, true);
}
class QLightStyleSlider : public QFrame
{
public:
int32_t style_id;
QLightStyleSlider(int32_t style_id, GLView *glView)
: QFrame(),
style_id(style_id),
glView(glView)
{
auto *style_layout = new QHBoxLayout();
auto *style = new QSpinBox();
style->setRange(0, 200);
style->setValue(100);
style->setSingleStep(10);
// style->setTickPosition(QSlider::TicksBothSides);
// style->setTickInterval(50);
connect(style, QOverload<int>::of(&QSpinBox::valueChanged), this, &QLightStyleSlider::setValue);
auto *style_label = new QLabel();
style_label->setText(QString::asprintf("%i", style_id));
style_layout->addWidget(style_label);
style_layout->addWidget(style);
setLayout(style_layout);
setFrameShadow(QFrame::Plain);
setFrameShape(QFrame::NoFrame);
}
private:
void setValue(int value) { glView->setLightStyleIntensity(style_id, value); }
GLView *glView;
};
int MainWindow::compileMap(const QString &file, bool is_reload)
{
fs::path fs_path = MakeFSPath(file);
m_bspdata = {};
render_settings.reset();
try {
if (fs_path.extension().compare(".bsp") == 0) {
LoadBSPFile(fs_path, &m_bspdata);
auto opts = ParseArgs(light_options);
std::vector<const char *> argPtrs;
argPtrs.push_back("");
for (const std::string &arg : opts) {
argPtrs.push_back(arg.data());
}
render_settings.preinitialize(argPtrs.size(), argPtrs.data());
render_settings.initialize(argPtrs.size() - 1, argPtrs.data() + 1);
render_settings.postinitialize(argPtrs.size(), argPtrs.data());
m_bspdata.version->game->init_filesystem(fs_path, render_settings);
ConvertBSPFormat(&m_bspdata, &bspver_generic);
} else {
m_bspdata = QbspVisLight_Common(fs_path, ParseArgs(common_options), ParseArgs(qbsp_options),
ParseArgs(vis_options), ParseArgs(light_options), vis_checkbox->isChecked());
// FIXME: move to a lightpreview_settings
settings::common_settings settings;
// FIXME: copy the -path args from light
settings.paths.copy_from(::light_options.paths);
m_bspdata.loadversion->game->init_filesystem(file.toStdString(), settings);
}
} catch (const settings::parse_exception &p) {
auto *textEdit = m_outputLogWidget->textEdit(m_activeLogTab);
textEdit->append(QString::fromUtf8(p.what()) + QString::fromLatin1("\n"));
m_activeLogTab = ETLogTab::TAB_LIGHTPREVIEW;
return 1;
} catch (const settings::quit_after_help_exception &p) {
auto *textEdit = m_outputLogWidget->textEdit(m_activeLogTab);
textEdit->append(QString::fromUtf8(p.what()) + QString::fromLatin1("\n"));
m_activeLogTab = ETLogTab::TAB_LIGHTPREVIEW;
return 1;
} catch (const std::exception &other) {
auto *textEdit = m_outputLogWidget->textEdit(m_activeLogTab);
textEdit->append(QString::fromUtf8(other.what()) + QString::fromLatin1("\n"));
m_activeLogTab = ETLogTab::TAB_LIGHTPREVIEW;
return 1;
}
return 0;
}
void MainWindow::compileThreadExited()
{
// clear lightstyle widgets
while (QWidget *w = lightstyles->parentWidget()->findChild<QWidget *>(QString(), Qt::FindDirectChildrenOnly)) {
delete w;
}
delete m_compileThread;
m_compileThread = nullptr;
if (!std::holds_alternative<mbsp_t>(m_bspdata.bsp)) {
return;
}
const auto &bsp = std::get<mbsp_t>(m_bspdata.bsp);
auto ents = EntData_Parse(bsp);
// build lightmap atlas
auto atlas = build_lightmap_atlas(bsp, m_bspdata.bspx.entries, false, bspx_decoupled_lm->isChecked());
glView->renderBSP(m_mapFile, bsp, m_bspdata.bspx.entries, ents, atlas, render_settings, bspx_normals->isChecked());
if (!m_fileWasReload && !glView->getKeepOrigin()) {
for (auto &ent : ents) {
if (ent.get("classname") == "info_player_start") {
qvec3d origin;
ent.get_vector("origin", origin);
qvec3d angles{};
if (ent.has("angles")) {
ent.get_vector("angles", angles);
angles = {angles[1], -angles[0], angles[2]}; // -pitch yaw roll -> yaw pitch roll
} else if (ent.has("angle"))
angles = {ent.get_float("angle"), 0, 0};
else if (ent.has("mangle"))
ent.get_vector("mangle", angles);
glView->setCamera(origin, qv::vec_from_mangle(angles));
break;
}
}
}
// set lightstyle data
for (auto &style_entry : atlas.style_to_lightmap_atlas) {
auto *style = new QLightStyleSlider(style_entry.first, glView);
lightstyles->addWidget(style);
}
}
void MainWindow::loadFileInternal(const QString &file, bool is_reload)
{
// TODO
if (m_compileThread)
return;
qDebug() << "loadFileInternal " << file;
// just in case
m_fileReloadTimer->stop();
m_fileWasReload = is_reload;
// persist settings
QSettings s;
s.setValue("common_options", common_options->text());
s.setValue("qbsp_options", qbsp_options->text());
s.setValue("vis_enabled", vis_checkbox->isChecked());
s.setValue("vis_options", vis_options->text());
s.setValue("light_options", light_options->text());
s.setValue("nearest", nearest->isChecked());
// update title bar
setWindowFilePath(file);
setWindowTitle(QFileInfo(file).fileName() + " - lightpreview");
for (auto &edit : m_outputLogWidget->textEdits()) {
edit->clear();
}
m_compileThread = QThread::create(std::bind(&MainWindow::compileMap, this, file, is_reload));
connect(m_compileThread, &QThread::finished, this, &MainWindow::compileThreadExited);
m_compileThread->start();
}
void MainWindow::displayCameraPositionInfo()
{
const auto *bsp = std::get_if<mbsp_t>(&m_bspdata.bsp);
if (!bsp)
return;
const qvec3f point = glView->cameraPosition();
const mleaf_t *leaf = BSP_FindLeafAtPoint(bsp, &bsp->dmodels[0], point);
// TODO: display leaf info
}