diff --git a/lightpreview/glview.cpp b/lightpreview/glview.cpp index 64df735a..738a53cc 100644 --- a/lightpreview/glview.cpp +++ b/lightpreview/glview.cpp @@ -56,12 +56,16 @@ GLView::GLView(QWidget *parent) m_moveSpeed(1000), m_displayAspect(1), m_cameraOrigin(0, 0, 0), + m_cullOrigin(0, 0, 0), m_cameraFwd(0, 1, 0), m_vao(), m_indexBuffer(QOpenGLBuffer::IndexBuffer), m_leakVao(), m_portalVao(), - m_portalIndexBuffer(QOpenGLBuffer::IndexBuffer) + m_portalIndexBuffer(QOpenGLBuffer::IndexBuffer), + m_frustumVao(), + m_frustumFacesIndexBuffer(QOpenGLBuffer::IndexBuffer), + m_frustumEdgesIndexBuffer(QOpenGLBuffer::IndexBuffer) { for (auto &hullVao : m_hullVaos) { hullVao.indexBuffer = QOpenGLBuffer(QOpenGLBuffer::IndexBuffer); @@ -89,6 +93,11 @@ GLView::~GLView() m_portalIndexBuffer.destroy(); m_portalVao.destroy(); + m_frustumVbo.destroy(); + m_frustumFacesIndexBuffer.destroy(); + m_frustumEdgesIndexBuffer.destroy(); + m_frustumVao.destroy(); + for (auto &hullVao : m_hullVaos) { hullVao.vbo.destroy(); hullVao.indexBuffer.destroy(); @@ -370,9 +379,10 @@ GLView::face_visibility_key_t GLView::desiredFaceVisibility() const if (m_visCulling) { const mbsp_t &bsp = *m_bsp; const auto &world = bsp.dmodels.at(0); + const auto &origin = m_keepCullOrigin ? m_cullOrigin : m_cameraOrigin; auto *leaf = - BSP_FindLeafAtPoint(&bsp, &world, qvec3d{m_cameraOrigin.x(), m_cameraOrigin.y(), m_cameraOrigin.z()}); + BSP_FindLeafAtPoint(&bsp, &world, qvec3d{origin.x(), origin.y(), origin.z()}); int leafnum = leaf - bsp.dleafs.data(); @@ -390,7 +400,26 @@ GLView::face_visibility_key_t GLView::desiredFaceVisibility() const return result; } -void GLView::updateFaceVisibility() +bool GLView::isVolumeInFrustum(const std::array& frustum, const qvec3f& mins, const qvec3f& maxs) { + for (auto &plane : frustum) { + // Select the p-vertex (positive vertex) - the vertex of the bounding + // box most aligned with the plane normal + const auto p = qvec3f( + plane.x() > 0 ? maxs[0] : mins[0], + plane.y() > 0 ? maxs[1] : mins[1], + plane.z() > 0 ? maxs[2] : mins[2] + ); + + // Check if the p-vertex is outside the plane + if (plane.x() * p[0] + plane.y() * p[1] + plane.z() * p[2] + plane.w() < 0) { + return false; + } + } + + return true; +} + +void GLView::updateFaceVisibility(const std::array& frustum) { if (!m_bsp) return; @@ -403,7 +432,7 @@ void GLView::updateFaceVisibility() if (m_uploaded_face_visibility && *m_uploaded_face_visibility == desired) { //qDebug() << "reusing last frame visdata"; - return; + //return; } qDebug() << "looking up pvs for clusternum " << desired.clusternum; @@ -433,7 +462,7 @@ void GLView::updateFaceVisibility() // visit all world leafs: if they're visible, mark the appropriate faces BSP_VisitAllLeafs(bsp, bsp.dmodels[0], [&](const mleaf_t &leaf) { - if (Pvs_LeafVisible(&bsp, pvs, &leaf)) { + if (Pvs_LeafVisible(&bsp, pvs, &leaf) && isVolumeInFrustum(frustum, leaf.mins, leaf.maxs)) { for (int ms = 0; ms < leaf.nummarksurfaces; ++ms) { int fnum = bsp.dleaffaces[leaf.firstmarksurface + ms]; face_flags[fnum] = 16; @@ -571,6 +600,20 @@ void GLView::initializeGL() glFrontFace(GL_CW); } +std::array GLView::getFrustumPlanes(const QMatrix4x4& MVP) +{ + return { + // left + (MVP.row(3) + MVP.row(0)).normalized(), + // right + (MVP.row(3) - MVP.row(0)).normalized(), + // top + (MVP.row(3) - MVP.row(1)).normalized(), + // bottom + (MVP.row(3) + MVP.row(1)).normalized(), + }; +} + void GLView::paintGL() { // calculate frame time + update m_lastFrame @@ -588,13 +631,6 @@ void GLView::paintGL() applyMouseMotion(); applyFlyMovement(duration_seconds); - // update vis culling if needed - updateFaceVisibility(); - - // draw - glClearColor(0.1, 0.1, 0.1, 1); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - QMatrix4x4 modelMatrix; QMatrix4x4 viewMatrix; QMatrix4x4 projectionMatrix; @@ -603,6 +639,16 @@ void GLView::paintGL() QMatrix4x4 MVP = projectionMatrix * viewMatrix * modelMatrix; + const auto frustum = m_keepCullOrigin && m_keepCullFrustum ? + getFrustumPlanes(projectionMatrix * m_cullViewMatrix * modelMatrix) : getFrustumPlanes(MVP); + + // update vis culling if needed + updateFaceVisibility(frustum); + + // draw + glClearColor(0.1, 0.1, 0.1, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + QOpenGLShaderProgram *active_program = nullptr; m_program->bind(); @@ -750,6 +796,40 @@ void GLView::paintGL() m_program_wireframe->release(); } + if (m_visCulling && m_keepCullOrigin) { + const QMatrix4x4 cullMVP = projectionMatrix * viewMatrix * m_cullModelMatrix; + + m_program_simple->bind(); + m_program_simple->setUniformValue(m_program_simple_mvp_location, cullMVP); + + QOpenGLVertexArrayObject::Binder vaoBinder(&m_frustumVao); + + glEnable(GL_PRIMITIVE_RESTART); + glPrimitiveRestartIndex((GLuint)-1); + + m_frustumEdgesIndexBuffer.bind(); + m_program_simple->setUniformValue(m_program_simple_color_location, QVector4D{1.0, 1.0, 1.0, 1.0}); + glDrawElements(GL_LINE_LOOP, 30, GL_UNSIGNED_INT, 0); + m_frustumEdgesIndexBuffer.release(); + + glDisable(GL_PRIMITIVE_RESTART); + + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + m_frustumFacesIndexBuffer.bind(); + m_program_simple->setUniformValue(m_program_simple_color_location, QVector4D{1.0, 1.0, 1.0, 0.05}); + glDrawElements(GL_TRIANGLES, 24, GL_UNSIGNED_INT, 0); + m_frustumFacesIndexBuffer.release(); + + glDisable(GL_BLEND); + glEnable(GL_CULL_FACE); + + m_program_simple->release(); + } + if (m_drawLeak && num_leak_points) { m_program_simple->bind(); m_program_simple->setUniformValue(m_program_simple_mvp_location, MVP); @@ -872,6 +952,33 @@ void GLView::setVisCulling(bool viscull) update(); } +void GLView::setKeepCullFrustum(bool keepcullfrustum) +{ + m_keepCullFrustum = keepcullfrustum; + update(); +} + +void GLView::setKeepCullOrigin(bool keepcullorigin) +{ + m_keepCullOrigin = keepcullorigin; + if (keepcullorigin) { + m_cullOrigin = m_cameraOrigin; + + QMatrix4x4 rotation, position; + + m_cullViewMatrix.setToIdentity(); + m_cullViewMatrix.lookAt(m_cameraOrigin, m_cameraOrigin + m_cameraFwd, QVector3D(0, 0, 1)); + + rotation = m_cullViewMatrix.inverted(); + rotation.setColumn(3, QVector4D(0, 0, 0, 1)); + + position.translate(m_cameraOrigin); + + m_cullModelMatrix = position * rotation; + } + update(); +} + void GLView::setDrawFlat(bool drawflat) { m_drawFlat = drawflat; @@ -983,8 +1090,36 @@ void GLView::setFaceVisibilityArray(uint8_t *data) face_visibility_texture->bind(); glTexBuffer(GL_TEXTURE_BUFFER, GL_R8UI, face_visibility_buffer->bufferId()); face_visibility_texture->release(); +} - //logging::print("uploaded {} bytes face visibility texture", face_visibility_width); +std::vector GLView::getFrustumCorners(float displayAspect) { + QMatrix4x4 projectionMatrix; + projectionMatrix.perspective(90, displayAspect, 1.0f, 8192.0f); + + const QMatrix4x4 invProjectionMatrix = projectionMatrix.inverted(); + + const std::vector ndcCorners = { + QVector4D(-1.0f, -1.0f, -1.0f, 1.0f), // 0: near bottom left + QVector4D( 1.0f, -1.0f, -1.0f, 1.0f), // 1: near bottom right + QVector4D (1.0f, 1.0f, -1.0f, 1.0f), // 2: near top right + QVector4D(-1.0f, 1.0f, -1.0f, 1.0f), // 3: near top left + + QVector4D(-1.0f, -1.0f, 1.0f, 1.0f), // far bottom left + QVector4D( 1.0f, -1.0f, 1.0f, 1.0f), // far bottom right + QVector4D( 1.0f, 1.0f, 1.0f, 1.0f), // far top left + QVector4D(-1.0f, 1.0f, 1.0f, 1.0f) // far top right + }; + + std::vector corners(8); + + // Transform to world space + for (int i = 0; i < 8; i++) { + QVector4D worldSpaceCorner = invProjectionMatrix * ndcCorners[i]; + worldSpaceCorner /= worldSpaceCorner.w(); // Perspective divide + corners[i] = worldSpaceCorner.toVector3D(); + } + + return corners; } void GLView::renderBSP(const QString &file, const mbsp_t &bsp, const bspxentries_t &bspx, @@ -1035,6 +1170,13 @@ void GLView::renderBSP(const QString &file, const mbsp_t &bsp, const bspxentries hullVao.indexBuffer.allocate(0); } + m_frustumVbo.bind(); + m_frustumVbo.allocate(0); + m_frustumFacesIndexBuffer.bind(); + m_frustumFacesIndexBuffer.allocate(0); + m_frustumEdgesIndexBuffer.bind(); + m_frustumEdgesIndexBuffer.allocate(0); + num_leak_points = 0; num_portal_indices = 0; m_uploaded_face_visibility = std::nullopt; @@ -1457,6 +1599,55 @@ void GLView::renderBSP(const QString &file, const mbsp_t &bsp, const bspxentries qvec3f pos; }; + { + QOpenGLVertexArrayObject::Binder vaoBinder(&m_frustumVao); + + auto corners = getFrustumCorners(m_displayAspect); + + m_frustumVbo.create(); + m_frustumVbo.bind(); + m_frustumVbo.allocate(corners.data(), corners.size() * sizeof(QVector3D)); + + glEnableVertexAttribArray(0 /* attrib */); + glVertexAttribPointer(0 /* attrib */, 3, GL_FLOAT, GL_FALSE, sizeof(QVector3D), 0); + + // Near Plane: Far Plane: + // 3----2 7----6 + // | | | | + // | | | | + // 0----1 4----5 + GLuint faceIndices[] = { + // Left face + 0, 4, 7, 0, 7, 3, + // Right face + 1, 2, 6, 1, 6, 5, + // Top face + 2, 3, 7, 2, 7, 6, + // Bottom face + 0, 1, 5, 0, 5, 4 + }; + + GLuint edgeIndices[] = { + // Front face + 0, 1, 1, 2, 2, 3, 3, 0, (GLuint)-1, + // Back face + 4, 5, 5, 6, 6, 7, 7, 4, (GLuint)-1, + // Connecting edges + 0, 4, (GLuint)-1, + 1, 5, (GLuint)-1, + 2, 6, (GLuint)-1, + 3, 7, (GLuint)-1 + }; + + m_frustumFacesIndexBuffer.create(); + m_frustumFacesIndexBuffer.bind(); + m_frustumFacesIndexBuffer.allocate(faceIndices, sizeof(faceIndices)); + + m_frustumEdgesIndexBuffer.create(); + m_frustumEdgesIndexBuffer.bind(); + m_frustumEdgesIndexBuffer.allocate(edgeIndices, sizeof(edgeIndices)); + } + if (fs::exists(leakFile)) { QOpenGLVertexArrayObject::Binder leakVaoBinder(&m_leakVao); @@ -1608,9 +1799,20 @@ void GLView::renderBSP(const QString &file, const mbsp_t &bsp, const bspxentries update(); } +void GLView::updateFrustumVBO() +{ + if (m_frustumVbo.isCreated()) { + const std::vector corners = getFrustumCorners(m_displayAspect); + m_frustumVbo.bind(); + glBufferData(GL_ARRAY_BUFFER, sizeof(QVector3D) * corners.size(), corners.data(), GL_DYNAMIC_DRAW); + m_frustumVbo.release(); + } +} + void GLView::resizeGL(int width, int height) { m_displayAspect = static_cast(width) / static_cast(height); + updateFrustumVBO(); } void GLView::applyMouseMotion() diff --git a/lightpreview/glview.h b/lightpreview/glview.h index b79249a5..e870391b 100644 --- a/lightpreview/glview.h +++ b/lightpreview/glview.h @@ -92,6 +92,9 @@ class GLView : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core float m_displayAspect; QVector3D m_cameraOrigin; QVector3D m_cameraFwd; // unit vec + QVector3D m_cullOrigin; + QMatrix4x4 m_cullViewMatrix; + QMatrix4x4 m_cullModelMatrix; QVector3D cameraRight() const { QVector3D v = QVector3D::crossProduct(m_cameraFwd, QVector3D(0, 0, 1)); @@ -108,6 +111,8 @@ class GLView : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core bool m_showTrisSeeThrough = false; bool m_drawFlat = false; bool m_keepOrigin = false; + bool m_keepCullFrustum = true; + bool m_keepCullOrigin = false; bool m_drawPortals = false; bool m_drawLeak = false; QOpenGLTexture::Filter m_filter = QOpenGLTexture::Linear; @@ -125,6 +130,11 @@ class GLView : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core QOpenGLBuffer m_portalVbo; QOpenGLBuffer m_portalIndexBuffer; + QOpenGLVertexArrayObject m_frustumVao; + QOpenGLBuffer m_frustumVbo; + QOpenGLBuffer m_frustumFacesIndexBuffer; + QOpenGLBuffer m_frustumEdgesIndexBuffer; + struct leaf_vao_t { QOpenGLVertexArrayObject vao; QOpenGLBuffer vbo; @@ -212,6 +222,9 @@ class GLView : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core private: void setFaceVisibilityArray(uint8_t *data); + static bool isVolumeInFrustum(const std::array& frustum, const qvec3f& mins, const qvec3f& maxs); + static std::vector getFrustumCorners(float displayAspect); + static std::array getFrustumPlanes(const QMatrix4x4& MVP); public: void renderBSP(const QString &file, const mbsp_t &bsp, const bspxentries_t &bspx, @@ -228,6 +241,8 @@ class GLView : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core void setVisCulling(bool viscull); void setDrawFlat(bool drawflat); void setKeepOrigin(bool keeporigin); + void setKeepCullFrustum(bool keepfrustum); + void setKeepCullOrigin(bool keeporigin); void setDrawPortals(bool drawportals); void setDrawLeak(bool drawleak); // intensity = 0 to 200 @@ -245,7 +260,8 @@ class GLView : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core void resizeGL(int width, int height) override; private: - void updateFaceVisibility(); + void updateFrustumVBO(); + void updateFaceVisibility(const std::array& frustum); bool shouldLiveUpdate() const; void handleLoggedMessage(const QOpenGLDebugMessage &debugMessage); diff --git a/lightpreview/mainwindow.cpp b/lightpreview/mainwindow.cpp index b99a6013..89dd1f75 100644 --- a/lightpreview/mainwindow.cpp +++ b/lightpreview/mainwindow.cpp @@ -205,6 +205,8 @@ void MainWindow::createPropertiesSidebar() visculling->setChecked(true); auto *keepposition = new QCheckBox(tr("Keep Camera Pos")); + auto *keepcullfrustum = new QCheckBox(tr("Keep Cull Frustum")); + auto *keepcullposition = new QCheckBox(tr("Keep Cull Pos")); nearest = new QCheckBox(tr("Nearest Filter")); @@ -229,6 +231,8 @@ void MainWindow::createPropertiesSidebar() formLayout->addRow(showtris); formLayout->addRow(showtris_seethrough); formLayout->addRow(visculling); + formLayout->addRow(keepcullposition); + formLayout->addRow(keepcullfrustum); formLayout->addRow(keepposition); formLayout->addRow(nearest); formLayout->addRow(bspx_decoupled_lm); @@ -266,6 +270,9 @@ void MainWindow::createPropertiesSidebar() common_options->setText(s.value("common_options").toString()); qbsp_options->setText(s.value("qbsp_options").toString()); vis_checkbox->setChecked(s.value("vis_enabled").toBool()); + keepcullposition->setEnabled(vis_checkbox->isChecked()); + keepcullfrustum->setEnabled(keepcullposition->isChecked()); + keepcullfrustum->setChecked(true); vis_options->setText(s.value("vis_options").toString()); light_options->setText(s.value("light_options").toString()); nearest->setChecked(s.value("nearest").toBool()); @@ -282,7 +289,16 @@ void MainWindow::createPropertiesSidebar() 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(visculling, &QAbstractButton::toggled, this, [=](bool checked) { + glView->setVisCulling(checked); + keepcullposition->setEnabled(checked); + keepcullfrustum->setEnabled(keepcullposition->isEnabled()); + }); + connect(keepcullposition, &QAbstractButton::toggled, this, [=](bool checked) { + glView->setKeepCullOrigin(checked); + keepcullfrustum->setEnabled(checked); + }); + connect(keepcullfrustum, &QAbstractButton::toggled, this, [=](bool checked) { glView->setKeepCullFrustum(checked); }); connect(drawflat, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawFlat(checked); }); connect(hull0, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawLeafs(checked ? std::optional{0} : std::nullopt); }); connect(hull1, &QAbstractButton::toggled, this, [=](bool checked) { glView->setDrawLeafs(checked ? std::optional{1} : std::nullopt); });