diff --git a/lightpreview/glview.cpp b/lightpreview/glview.cpp index a39e1c87..62f40f94 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; @@ -400,12 +429,6 @@ void GLView::updateFaceVisibility() const face_visibility_key_t desired = desiredFaceVisibility(); - if (m_uploaded_face_visibility && - *m_uploaded_face_visibility == desired) { - //qDebug() << "reusing last frame visdata"; - return; - } - qDebug() << "looking up pvs for clusternum " << desired.clusternum; const int face_visibility_width = m_bsp->dfaces.size(); @@ -433,7 +456,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; @@ -461,8 +484,6 @@ void GLView::updateFaceVisibility() } setFaceVisibilityArray(face_flags.data()); - - m_uploaded_face_visibility = desired; } bool GLView::shouldLiveUpdate() const @@ -571,6 +592,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 +623,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 +631,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 +788,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 +944,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; @@ -929,7 +1028,6 @@ void GLView::setDrawTranslucencyAsOpaque(bool drawopaque) void GLView::setShowBmodels(bool bmodels) { // force re-upload of face visibility - m_uploaded_face_visibility = std::nullopt; m_showBmodels = bmodels; update(); } @@ -983,8 +1081,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,9 +1161,15 @@ 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; int32_t highest_depth = 0; @@ -1461,6 +1593,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); @@ -1612,9 +1793,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..e41d1773 100644 --- a/lightpreview/glview.h +++ b/lightpreview/glview.h @@ -85,13 +85,15 @@ private: }; face_visibility_key_t desiredFaceVisibility() const; - std::optional m_uploaded_face_visibility; bool m_visCulling = true; // camera stuff 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 +110,8 @@ private: 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 +129,11 @@ private: 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 +221,9 @@ public: 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 +240,8 @@ public: 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 +259,8 @@ protected: 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); });