diff --git a/resources.qrc b/resources.qrc
index 576efe2..699b710 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -21,5 +21,7 @@
shaders/model.frag
shaders/skybox.vert
shaders/skybox.frag
+ shaders/picking.frag
+ shaders/picking.vert
diff --git a/shaders/picking.frag b/shaders/picking.frag
new file mode 100644
index 0000000..e8a70f3
--- /dev/null
+++ b/shaders/picking.frag
@@ -0,0 +1,14 @@
+#version 330
+
+uniform uint object_type;
+uniform uint object_id;
+
+out uvec3 picking_fragment;
+
+void main() {
+ // The leading 1.0 is to indicate the presence of an object
+ // in the texture, since by default, OpenGL will, clear to 0
+ // so, we need some way to tell an object was rendered at this
+ // fragment
+ picking_fragment = uvec3(1.0, object_type, object_id);
+}
diff --git a/shaders/picking.vert b/shaders/picking.vert
new file mode 100644
index 0000000..314edc4
--- /dev/null
+++ b/shaders/picking.vert
@@ -0,0 +1,11 @@
+#version 330
+
+layout (location = 0) in vec3 in_position;
+
+uniform mat4 model;
+uniform mat4 view;
+uniform mat4 projection;
+
+void main() {
+ gl_Position = projection * view * model * vec4(in_position, 1.0);
+}
\ No newline at end of file
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0c4c2e1..b5a6151 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -37,6 +37,7 @@ target_sources(netsimulyzer PRIVATE
group/node/Node.h group/node/Node.cpp
group/node/TrailBuffer.h group/node/TrailBuffer.cpp
render/camera/Camera.h render/camera/Camera.cpp
+ render/framebuffer/PickingFramebuffer.h render/framebuffer/PickingFramebuffer.cpp
render/helper/Floor.h render/helper/Floor.cpp
render/Light.h
render/material/material.h
diff --git a/src/render/framebuffer/PickingFramebuffer.cpp b/src/render/framebuffer/PickingFramebuffer.cpp
new file mode 100644
index 0000000..cb974a2
--- /dev/null
+++ b/src/render/framebuffer/PickingFramebuffer.cpp
@@ -0,0 +1,92 @@
+/*
+ * NIST-developed software is provided by NIST as a public service. You may use,
+ * copy and distribute copies of the software in any medium, provided that you
+ * keep intact this entire notice. You may improve,modify and create derivative
+ * works of the software or any portion of the software, and you may copy and
+ * distribute such modifications or works. Modified works should carry a notice
+ * stating that you changed the software and should note the date and nature of
+ * any such change. Please explicitly acknowledge the National Institute of
+ * Standards and Technology as the source of the software.
+ *
+ * NIST-developed software is expressly provided "AS IS." NIST MAKES NO
+ * WARRANTY OF ANY KIND, EXPRESS, IMPLIED, IN FACT OR ARISING BY OPERATION OF
+ * LAW, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
+ * AND DATA ACCURACY. NIST NEITHER REPRESENTS NOR WARRANTS THAT THE
+ * OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT
+ * ANY DEFECTS WILL BE CORRECTED. NIST DOES NOT WARRANT OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF THE SOFTWARE OR THE RESULTS THEREOF,
+ * INCLUDING BUT NOT LIMITED TO THE CORRECTNESS, ACCURACY, RELIABILITY,
+ * OR USEFULNESS OF THE SOFTWARE.
+ *
+ * You are solely responsible for determining the appropriateness of using and
+ * distributing the software and you assume all risks associated with its use,
+ * including but not limited to the risks and costs of program errors,
+ * compliance with applicable laws, damage to or loss of data, programs or
+ * equipment, and the unavailability or interruption of operation. This
+ * software is not intended to be used in any situation where a failure could
+ * cause risk of injury or damage to property. The software developed by NIST
+ * employees is not subject to copyright protection within the United States.
+ *
+ * Author: Evan Black
+ */
+
+#include "PickingFramebuffer.h"
+#include
+namespace netsimulyzer {
+
+void PickingFramebuffer::generate(int width, int height) {
+ openGl.glGenFramebuffers(1, &fbo);
+ bind(GL_FRAMEBUFFER);
+
+ openGl.glGenTextures(1, &idTexture);
+ openGl.glBindTexture(GL_TEXTURE_2D, idTexture);
+ openGl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32UI, width, height, 0, GL_RGB_INTEGER, GL_UNSIGNED_INT, nullptr);
+ openGl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ openGl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ openGl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, idTexture, 0);
+
+ openGl.glGenTextures(1, &depthTexture);
+ openGl.glBindTexture(GL_TEXTURE_2D, depthTexture);
+ openGl.glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
+ openGl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
+}
+
+// NOLINT(cppcoreguidelines-pro-type-member-init)
+PickingFramebuffer::PickingFramebuffer(QOpenGLFunctions_3_3_Core &openGl, int width, int height)
+ : openGl(openGl) { // NOLINT(cppcoreguidelines-pro-type-member-init)
+ generate(width, height);
+}
+
+PickingFramebuffer::~PickingFramebuffer() {
+ openGl.glDeleteTextures(1, &idTexture);
+ openGl.glDeleteTextures(1, &depthTexture);
+ openGl.glDeleteFramebuffers(1, &fbo);
+}
+
+void PickingFramebuffer::bind(GLenum mode) const {
+ openGl.glBindFramebuffer(mode, fbo);
+}
+
+void PickingFramebuffer::unbind(GLenum mode, unsigned int defaultFbo) const {
+ openGl.glBindFramebuffer(mode, defaultFbo);
+}
+
+PickingFramebuffer::PixelInfo PickingFramebuffer::read(int x, int y) const {
+ bind(GL_READ_FRAMEBUFFER);
+ openGl.glReadBuffer(GL_COLOR_ATTACHMENT0);
+
+ PixelInfo pixelInfo; // NOLINT(cppcoreguidelines-pro-type-member-init)
+ openGl.glReadPixels(x, y, 1, 1, GL_RGB_INTEGER, GL_UNSIGNED_INT, &pixelInfo);
+
+ return pixelInfo;
+}
+
+void PickingFramebuffer::resize(int width, int height) {
+ openGl.glDeleteTextures(1, &idTexture);
+ openGl.glDeleteTextures(1, &depthTexture);
+ openGl.glDeleteFramebuffers(1, &fbo);
+ generate(width, height);
+}
+
+} // namespace netsimulyzer
diff --git a/src/render/framebuffer/PickingFramebuffer.h b/src/render/framebuffer/PickingFramebuffer.h
new file mode 100644
index 0000000..9cda538
--- /dev/null
+++ b/src/render/framebuffer/PickingFramebuffer.h
@@ -0,0 +1,72 @@
+/*
+ * NIST-developed software is provided by NIST as a public service. You may use,
+ * copy and distribute copies of the software in any medium, provided that you
+ * keep intact this entire notice. You may improve,modify and create derivative
+ * works of the software or any portion of the software, and you may copy and
+ * distribute such modifications or works. Modified works should carry a notice
+ * stating that you changed the software and should note the date and nature of
+ * any such change. Please explicitly acknowledge the National Institute of
+ * Standards and Technology as the source of the software.
+ *
+ * NIST-developed software is expressly provided "AS IS." NIST MAKES NO
+ * WARRANTY OF ANY KIND, EXPRESS, IMPLIED, IN FACT OR ARISING BY OPERATION OF
+ * LAW, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
+ * AND DATA ACCURACY. NIST NEITHER REPRESENTS NOR WARRANTS THAT THE
+ * OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT
+ * ANY DEFECTS WILL BE CORRECTED. NIST DOES NOT WARRANT OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF THE SOFTWARE OR THE RESULTS THEREOF,
+ * INCLUDING BUT NOT LIMITED TO THE CORRECTNESS, ACCURACY, RELIABILITY,
+ * OR USEFULNESS OF THE SOFTWARE.
+ *
+ * You are solely responsible for determining the appropriateness of using and
+ * distributing the software and you assume all risks associated with its use,
+ * including but not limited to the risks and costs of program errors,
+ * compliance with applicable laws, damage to or loss of data, programs or
+ * equipment, and the unavailability or interruption of operation. This
+ * software is not intended to be used in any situation where a failure could
+ * cause risk of injury or damage to property. The software developed by NIST
+ * employees is not subject to copyright protection within the United States.
+ *
+ * Author: Evan Black
+ */
+#pragma once
+#include
+
+namespace netsimulyzer {
+
+class PickingFramebuffer {
+ QOpenGLFunctions_3_3_Core &openGl;
+ unsigned int fbo;
+ unsigned int idTexture;
+ unsigned int depthTexture;
+
+ void generate(int width, int height);
+
+public:
+ struct PixelInfo {
+ unsigned int object;
+ unsigned int type;
+ unsigned int id;
+ };
+
+ PickingFramebuffer(QOpenGLFunctions_3_3_Core &openGl, int width, int height);
+ ~PickingFramebuffer();
+
+ // Disallow copying
+ PickingFramebuffer(const PickingFramebuffer &) = delete;
+ PickingFramebuffer &operator=(const PickingFramebuffer &) = delete;
+
+ void bind(GLenum mode) const;
+ void unbind(GLenum mode, unsigned int defaultFbo) const;
+
+ [[nodiscard]] PixelInfo read(int x, int y) const;
+
+ inline unsigned int getIds() {
+ return idTexture;
+ }
+
+ void resize(int width, int height);
+};
+
+} // namespace netsimulyzer
diff --git a/src/render/model/ModelCache.cpp b/src/render/model/ModelCache.cpp
index e6d3c44..123e798 100644
--- a/src/render/model/ModelCache.cpp
+++ b/src/render/model/ModelCache.cpp
@@ -261,6 +261,14 @@ void ModelRenderInfo::clear() {
meshes.clear();
}
+std::vector &ModelRenderInfo::getMeshes() {
+ return meshes;
+}
+
+std::vector &ModelRenderInfo::getTransparentMeshes() {
+ return transparentMeshes;
+}
+
ModelCache::ModelCache(TextureCache &textureCache) : textureCache(textureCache) {
}
diff --git a/src/render/model/ModelCache.h b/src/render/model/ModelCache.h
index 2f45b12..01a9213 100644
--- a/src/render/model/ModelCache.h
+++ b/src/render/model/ModelCache.h
@@ -92,6 +92,8 @@ class ModelRenderInfo : protected QOpenGLFunctions_3_3_Core {
void render(Shader &s, const Model &model);
void renderTransparent(Shader &s, const Model &model);
+ std::vector& getMeshes();
+ std::vector& getTransparentMeshes();
void clear();
};
diff --git a/src/render/renderer/Renderer.cpp b/src/render/renderer/Renderer.cpp
index 40472a4..4e5b888 100644
--- a/src/render/renderer/Renderer.cpp
+++ b/src/render/renderer/Renderer.cpp
@@ -80,6 +80,7 @@ void Renderer::init() {
initShader(modelShader, ":shader/shaders/model.vert", ":shader/shaders/model.frag");
initShader(skyBoxShader, ":shader/shaders/skybox.vert", ":shader/shaders/skybox.frag");
+ initShader(pickingShader, ":/shader/shaders/picking.vert", ":/shader/shaders/picking.frag");
}
void Renderer::setPerspective(const glm::mat4 &perspective) {
@@ -88,6 +89,7 @@ void Renderer::setPerspective(const glm::mat4 &perspective) {
gridShader.uniform("projection", perspective);
modelShader.uniform("projection", perspective);
skyBoxShader.uniform("projection", perspective);
+ pickingShader.uniform("projection", perspective);
}
void Renderer::setPointLightCount(unsigned int count) {
@@ -539,6 +541,8 @@ void Renderer::use(const Camera &cam) {
auto noTranslationView = cam.view_matrix();
noTranslationView[3] = {0.0f, 0.0f, 0.0f, 1.0f};
skyBoxShader.uniform("view", noTranslationView);
+
+ pickingShader.uniform("view", cam.view_matrix());
}
void Renderer::render(const DirectionalLight &light) {
@@ -712,4 +716,22 @@ void Renderer::render(const std::vector &wiredLinks) {
glDisable(GL_LINE_SMOOTH);
}
+void Renderer::renderPickingNode(unsigned int nodeId, const Model &m) {
+ auto &model = modelCache.get(m.getModelId());
+
+ pickingShader.uniform("model", m.getModelMatrix());
+ pickingShader.uniform("object_id", nodeId);
+ pickingShader.uniform("object_type", 1u);
+
+ pickingShader.bind();
+ auto &meshes = model.getMeshes();
+ for (auto &mesh : meshes) {
+ mesh.render();
+ }
+ auto &transparentMeshes = model.getTransparentMeshes();
+ for (auto &mesh : transparentMeshes) {
+ mesh.render();
+ }
+}
+
} // namespace netsimulyzer
diff --git a/src/render/renderer/Renderer.h b/src/render/renderer/Renderer.h
index 7c780cc..79c5efa 100644
--- a/src/render/renderer/Renderer.h
+++ b/src/render/renderer/Renderer.h
@@ -62,6 +62,7 @@ class Renderer : protected QOpenGLFunctions_3_3_Core {
Shader gridShader;
Shader modelShader;
Shader skyBoxShader;
+ Shader pickingShader;
void initShader(Shader &s, const QString &vertexPath, const QString &fragmentPath);
@@ -89,6 +90,8 @@ class Renderer : protected QOpenGLFunctions_3_3_Core {
void startTransparent();
void endTransparent();
+ void renderPickingNode(unsigned int nodeId, const Model &m);
+
void use(const Camera &cam);
void render(const DirectionalLight &light);
void render(const PointLight &light);
diff --git a/src/window/scene/SceneWidget.cpp b/src/window/scene/SceneWidget.cpp
index 6bc7bf2..e09b3b2 100644
--- a/src/window/scene/SceneWidget.cpp
+++ b/src/window/scene/SceneWidget.cpp
@@ -209,6 +209,10 @@ void SceneWidget::initializeGL() {
updatePerspective();
+ // picking FBO
+ pickingFbo = std::make_unique(openGl, width(), height());
+ pickingFbo->unbind(GL_FRAMEBUFFER, defaultFramebufferObject());
+
// Cheap hack to get Qt to repaint at a reasonable rate
// Seems to only work with the old connect syntax
QObject::connect(&timer, SIGNAL(timeout()), this, SLOT(update()));
@@ -225,9 +229,25 @@ void SceneWidget::paintGL() {
handleUndoEvents();
}
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // NOLINT(hicpp-signed-bitwise)
+ // Picking
+ pickingFbo->bind(GL_FRAMEBUFFER);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ for (auto &[key, node] : nodes) {
+ if (!node.visible())
+ continue;
+ renderer.renderPickingNode(node.getNs3Model().id, node.getModel());
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());
+ // end Picking
+
camera.move(static_cast(frameTimer.elapsed()));
renderer.use(camera);
+
+ glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (renderSkybox)
renderer.render(*skyBox);
@@ -318,6 +338,7 @@ void SceneWidget::paintGL() {
void SceneWidget::resizeGL(int w, int h) {
updatePerspective();
glViewport(0, 0, w, h);
+ pickingFbo->resize(w, h);
}
void SceneWidget::keyPressEvent(QKeyEvent *event) {
@@ -332,6 +353,21 @@ void SceneWidget::keyReleaseEvent(QKeyEvent *event) {
void SceneWidget::mousePressEvent(QMouseEvent *event) {
QWidget::mousePressEvent(event);
+
+ makeCurrent();
+
+ // OpenGL starts from the bottom left,
+ // Qt Starts at the top left,
+ // so adjust the Y coordinate accordingly
+ const auto selected = pickingFbo->read(event->x(), height() - event->y());
+ pickingFbo->unbind(GL_READ_FRAMEBUFFER, defaultFramebufferObject());
+ doneCurrent();
+
+ if (selected.object && selected.type == 1u) {
+ emit nodeSelected(selected.id);
+ return;
+ }
+
if (!camera.mouseControlsEnabled())
return;
diff --git a/src/window/scene/SceneWidget.h b/src/window/scene/SceneWidget.h
index 5e3ae7e..ce17a85 100644
--- a/src/window/scene/SceneWidget.h
+++ b/src/window/scene/SceneWidget.h
@@ -49,6 +49,7 @@
#include "../../settings/SettingsManager.h"
#include "../../util/undo-events.h"
#include "src/group/link/WiredLink.h"
+#include "src/render/framebuffer/PickingFramebuffer.h"
#include "src/render/helper/CoordinateGrid.h"
#include "src/render/helper/SkyBox.h"
#include
@@ -93,6 +94,8 @@ class SceneWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core {
bool renderBuildingOutlines = settings.get(SettingsManager::Key::RenderBuildingOutlines).value();
bool renderMotionTrails = settings.get(SettingsManager::Key::RenderMotionTrails).value();
+ std::unique_ptr pickingFbo;
+
DirectionalLight mainLight;
std::unique_ptr skyBox;
std::unique_ptr floor;
@@ -242,5 +245,6 @@ class SceneWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core {
void timeChanged(parser::nanoseconds simulationTime, parser::nanoseconds increment);
void paused();
void playing();
+ void nodeSelected(unsigned int nodeId);
};
} // namespace netsimulyzer