selection works now

Fri, 07 Feb 2020 23:59:06 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Fri, 07 Feb 2020 23:59:06 +0200
changeset 51
1a9eac27698d
parent 50
0f80a2e5e42b
child 52
eee644f88e93

selection works now

src/basics.h file | annotate | diff | comparison | revisions
src/document.cpp file | annotate | diff | comparison | revisions
src/document.ui file | annotate | diff | comparison | revisions
src/gl/compiler.cpp file | annotate | diff | comparison | revisions
src/gl/compiler.h file | annotate | diff | comparison | revisions
src/gl/partrenderer.cpp file | annotate | diff | comparison | revisions
src/gl/partrenderer.h file | annotate | diff | comparison | revisions
src/main.h file | annotate | diff | comparison | revisions
src/model.cpp file | annotate | diff | comparison | revisions
src/model.h file | annotate | diff | comparison | revisions
src/ui/canvas.cpp file | annotate | diff | comparison | revisions
src/ui/canvas.h file | annotate | diff | comparison | revisions
--- a/src/basics.h	Fri Feb 07 02:02:16 2020 +0200
+++ b/src/basics.h	Fri Feb 07 23:59:06 2020 +0200
@@ -85,5 +85,25 @@
 	return N;
 }
 
+/**
+ * @brief casts @c x to a suitable unsigned integer
+ */
+template<typename T>
+constexpr auto unsigned_cast(T x)
+	-> std::enable_if_t<std::is_integral_v<T>, std::make_unsigned_t<T>>
+{
+	return static_cast<std::make_unsigned_t<T>>(x);
+}
+
+/**
+ * @brief casts @c x to a suitable signed integer
+ */
+template<typename T>
+constexpr auto signed_cast(T x)
+	-> std::enable_if_t<std::is_integral_v<T>, std::make_signed_t<T>>
+{
+	return static_cast<std::make_signed_t<T>>(x);
+}
+
 Q_DECLARE_METATYPE(glm::vec3)
 Q_DECLARE_METATYPE(glm::mat4)
--- a/src/document.cpp	Fri Feb 07 02:02:16 2020 +0200
+++ b/src/document.cpp	Fri Feb 07 23:59:06 2020 +0200
@@ -41,6 +41,35 @@
 	this->setMouseTracking(true);
 	connect(this->ui.splitter, &QSplitter::splitterMoved, this, &Document::splitterChanged);
 	connect(this->renderer, &Canvas::newStatusText, this, &Document::newStatusText);
+	connect(this->renderer, &Canvas::selectionChanged, [&](const QSet<ldraw::Id>& newSelection)
+	{
+		QItemSelectionModel* selectionModel = this->ui.listView->selectionModel();
+		QItemSelection selection;
+		for (ldraw::Id id : newSelection)
+		{
+			QModelIndex index = this->model->lookup(id);
+			if (index != QModelIndex{})
+			{
+				selection.select(index, index);
+			}
+		}
+		selectionModel->select(selection, QItemSelectionModel::ClearAndSelect);
+	});
+	connect(this->ui.listView->selectionModel(), &QItemSelectionModel::selectionChanged,
+		[&](const QItemSelection& selected, const QItemSelection& deselected)
+	{
+		QSet<ldraw::Id> selectedIds;
+		QSet<ldraw::Id> deselectedIds;
+		for (const QModelIndex& index : selected.indexes())
+		{
+			selectedIds.insert(this->model->resolve(index));
+		}
+		for (const QModelIndex& index : deselected.indexes())
+		{
+			deselectedIds.insert(this->model->resolve(index));
+		}
+		this->renderer->handleSelectionChange(selectedIds, deselectedIds);
+	});
 }
 
 Document::~Document()
--- a/src/document.ui	Fri Feb 07 02:02:16 2020 +0200
+++ b/src/document.ui	Fri Feb 07 23:59:06 2020 +0200
@@ -19,7 +19,17 @@
      <property name="orientation">
       <enum>Qt::Horizontal</enum>
      </property>
-     <widget class="QListView" name="listView"/>
+     <widget class="QListView" name="listView">
+      <property name="alternatingRowColors">
+       <bool>true</bool>
+      </property>
+      <property name="selectionMode">
+       <enum>QAbstractItemView::ExtendedSelection</enum>
+      </property>
+      <property name="selectionBehavior">
+       <enum>QAbstractItemView::SelectRows</enum>
+      </property>
+     </widget>
      <widget class="QFrame" name="viewportFrame">
       <property name="frameShape">
        <enum>QFrame::StyledPanel</enum>
--- a/src/gl/compiler.cpp	Fri Feb 07 02:02:16 2020 +0200
+++ b/src/gl/compiler.cpp	Fri Feb 07 23:59:06 2020 +0200
@@ -32,6 +32,7 @@
 layout(location=1) in vec4 color;
 layout(location=2) in vec3 normal;
 layout(location=3) in int id;
+layout(location=4) in int selected;
 out vec4 vColor;
 out vec3 vFragPos;
 out vec3 vNormal;
@@ -62,6 +63,10 @@
 		int b = id % 0x100;
 		vColor = vec4(r / 255.0, g / 255.0, b / 255.0, 1.0);
 	}
+	else if (selected == 1)
+	{
+		vColor = vec4(selectedColor, 1.0);
+	}
 	else
 	{
 		if (fragmentStyle == FRAGSTYLE_BfcGreen)
@@ -158,7 +163,7 @@
 			object.buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
 			object.vertexArray.create();
 			object.vertexArray.bind();
-			for (int k : {0, 1, 2, 3})
+			for (int k : {0, 1, 2, 3, 4})
 			{
 				object.program->enableAttributeArray(k);
 			}
@@ -167,6 +172,7 @@
 			object.program->setAttributeBuffer(1, GL_FLOAT, offsetof(gl::Vertex, color), 4, stride);
 			object.program->setAttributeBuffer(2, GL_FLOAT, offsetof(gl::Vertex, normal), 3, stride);
 			glVertexAttribIPointer(3, 1, GL_INT, stride, reinterpret_cast<void*>(offsetof(gl::Vertex, id)));
+			glVertexAttribIPointer(4, 1, GL_INT, stride, reinterpret_cast<void*>(offsetof(gl::Vertex, selected)));
 			object.vertexArray.release();
 			object.buffer.release();
 			object.program->release();
@@ -189,6 +195,7 @@
 		auto& buffer = this->glObjects[arrayId].buffer;
 		auto& vector = vboData[arrayId];
 		this->storedVertexCounts[arrayId] = vector.size();
+		this->glObjects[arrayId].cachedData = vector; // todo: get rid of this copy
 		buffer.bind();
 		buffer.allocate(vector.data(), static_cast<int>(vector.size() * sizeof vector[0]));
 		buffer.release();
@@ -266,9 +273,9 @@
 	return boxCenter(this->boundingBox);
 }
 
-float gl::Compiler::modelDistance() const
+double gl::Compiler::modelDistance() const
 {
-	return longestMeasure(this->boundingBox);
+	return static_cast<double>(longestMeasure(this->boundingBox));
 }
 
 void gl::Compiler::bindVertexArray(gl::ArrayClass arrayClass)
@@ -285,6 +292,21 @@
 	object.vertexArray.release();
 }
 
+void gl::Compiler::setSelectedObjects(const QSet<ldraw::Id> ids)
+{
+	for (int i = 0; i < gl::NUM_ARRAY_CLASSES; i += 1)
+	{
+		auto& vector = this->glObjects[i].cachedData;
+		for (gl::Vertex& vertex : vector)
+		{
+			vertex.selected = (ids.contains({vertex.id})) ? 1 : 0;
+		}
+		const GLsizeiptr size = static_cast<int>(vector.size() * sizeof vector[0]);
+		this->glObjects[i].buffer.bind();
+		glBufferSubData(GL_ARRAY_BUFFER, 0, size, vector.data());
+	}
+}
+
 std::size_t gl::Compiler::vertexCount(const gl::ArrayClass arrayClass) const
 {
 	return this->storedVertexCounts[static_cast<int>(arrayClass)];
--- a/src/gl/compiler.h	Fri Feb 07 02:02:16 2020 +0200
+++ b/src/gl/compiler.h	Fri Feb 07 23:59:06 2020 +0200
@@ -41,6 +41,7 @@
 		glm::vec4 color;
 		glm::vec3 normal;
 		glm::int32 id;
+		glm::int32 selected = 0;
 	};
 }
 
@@ -55,11 +56,12 @@
 	std::size_t vertexCount(gl::ArrayClass arrayClass) const;
 	QColor getColorForPolygon(const gl::Polygon& polygon, const RenderPreferences& preferences);
 	glm::vec3 modelCenter() const;
-	float modelDistance() const;
+	double modelDistance() const;
 	void initialize();
 	void bindVertexArray(gl::ArrayClass arrayClass);
 	void releaseVertexArray(gl::ArrayClass arrayClass);
 	void buildShaders(int arrayId);
+	void setSelectedObjects(const QSet<ldraw::Id> ids);
 
 	static ldraw::Id idFromColor(const std::array<GLbyte, 3>& data);
 
@@ -95,6 +97,7 @@
 		QOpenGLShaderProgram* pickSceneProgram = nullptr;
 		QOpenGLBuffer buffer{QOpenGLBuffer::VertexBuffer};
 		QOpenGLVertexArrayObject vertexArray;
+		std::vector<gl::Vertex> cachedData;
 	} glObjects[gl::NUM_ARRAY_CLASSES];
 };
 
--- a/src/gl/partrenderer.cpp	Fri Feb 07 02:02:16 2020 +0200
+++ b/src/gl/partrenderer.cpp	Fri Feb 07 23:59:06 2020 +0200
@@ -115,15 +115,16 @@
 			static_cast<float>(backgroundColor.greenF()),
 			static_cast<float>(backgroundColor.blueF()),
 			1.0f);
-		this->compiler->setUniform("useLighting", true);
+		this->compiler->setUniform("useLighting", GL_TRUE);
 	}
 	else
 	{
 		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
-		this->compiler->setUniform("useLighting", false);
+		this->compiler->setUniform("useLighting", GL_FALSE);
 	}
 	this->compiler->setUniform("selectedColor", vec3FromQColor(this->renderPreferences.selectedColor));
 	this->compiler->setUniform("highlighted", this->highlighted.value);
+	this->checkForGLErrors();
 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 	glEnable(GL_DEPTH_TEST);
 	glEnable(GL_POLYGON_OFFSET_FILL);
@@ -187,7 +188,9 @@
 {
 	this->compiler->bindVertexArray(arrayClass);
 	const std::size_t vertexCount = this->compiler->vertexCount(arrayClass);
+	this->checkForGLErrors();
 	glDrawArrays(getGlTypeForArrayClass(arrayClass), 0, static_cast<GLsizei>(vertexCount));
+	this->checkForGLErrors();
 	this->compiler->releaseVertexArray(arrayClass);
 	this->checkForGLErrors();
 }
@@ -250,7 +253,9 @@
 	this->makeCurrent();
 	this->renderScene();
 	std::array<GLbyte, 3> data;
+	this->checkForGLErrors();
 	glReadPixels(where.x(), this->height() - where.y(), 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &data[0]);
+	this->checkForGLErrors();
 	this->renderPreferences.style = oldRenderStyle;
 	this->update();
 	return gl::Compiler::idFromColor(data);
@@ -280,9 +285,3 @@
 	}
 	this->update();
 }
-
-void PartRenderer::setHighlight(ldraw::Id highlightedId)
-{
-	this->highlighted = highlightedId;
-}
-
--- a/src/gl/partrenderer.h	Fri Feb 07 02:02:16 2020 +0200
+++ b/src/gl/partrenderer.h	Fri Feb 07 23:59:06 2020 +0200
@@ -22,7 +22,6 @@
 		QWidget* parent = nullptr);
 	~PartRenderer() override;
 	void setRenderPreferences(const gl::RenderPreferences& newPreferences);
-	void setHighlight(ldraw::Id highlightedId);
 protected:
 	ldraw::Id pick(const QPoint& where);
 	void initializeGL() override;
@@ -30,21 +29,21 @@
 	void paintGL() override;
 	void mouseMoveEvent(QMouseEvent* event) override;
 	void wheelEvent(QWheelEvent* event) override;
+	Model* const model;
+	DocumentManager* const documents;
+	const ldraw::ColorTable& colorTable;
+	gl::Compiler* const compiler;
+	ldraw::Id highlighted = ldraw::NULL_ID;
 private:
 	void setFragmentStyle(gl::FragmentStyle fragStyle);
 	void renderAllArrays();
 	void renderScene();
 	void updateViewMatrix();
-	Model* const model;
-	DocumentManager* const documents;
-	const ldraw::ColorTable& colorTable;
 	QPointF lastMousePosition;
-	gl::Compiler* compiler;
 	gl::RenderPreferences renderPreferences;
 	glm::mat4 projectionMatrix;
 	glm::mat4 viewMatrix;
 	glm::quat modelQuaternion;
-	ldraw::Id highlighted = ldraw::NULL_ID;
 	static constexpr double MIN_ZOOM = 0.0;
 	static constexpr double MAX_ZOOM = 3.0;
 	double zoom = 1.0;
--- a/src/main.h	Fri Feb 07 02:02:16 2020 +0200
+++ b/src/main.h	Fri Feb 07 23:59:06 2020 +0200
@@ -41,6 +41,14 @@
 		{
 			return this->value < other.value;
 		}
+		friend constexpr unsigned int qHash(ldraw::Id id)
+		{
+			return qHash(id.value);
+		}
+		friend bool operator==(ldraw::Id one, ldraw::Id other)
+		{
+			return one.value == other.value;
+		}
 	};
 	using id_t = Id;
 	constexpr id_t NULL_ID = id_t{0};
--- a/src/model.cpp	Fri Feb 07 02:02:16 2020 +0200
+++ b/src/model.cpp	Fri Feb 07 23:59:06 2020 +0200
@@ -26,7 +26,7 @@
 
 int Model::size() const
 {
-	return this->body.size();
+	return static_cast<int>(this->body.size());
 }
 
 Model::EditContext Model::edit()
@@ -41,8 +41,7 @@
 
 QVariant Model::data(const QModelIndex& index, int role) const
 {
-	const int row = index.row();
-	ldraw::Object* object = this->body[row].get();
+	const ldraw::Object* object = this->objectAt(index);
 	switch(role)
 	{
 	case Qt::DisplayRole:
@@ -64,14 +63,13 @@
 	{
 	case HeaderProperty::Name:
 		return header.name;
-	default:
-		return {};
 	}
+	return {};
 }
 
 QVariant Model::getObjectProperty(const int index, const ldraw::Property property) const
 {
-	const ldraw::Object* object = this->body[index].get();
+	const ldraw::Object* object = this->body[unsigned_cast(index)].get();
 	return object->getProperty(property);
 }
 
@@ -90,12 +88,30 @@
 	return this->cachedPolygons;
 }
 
+QModelIndex Model::lookup(ldraw::Id id) const
+{
+	// FIXME: This linear search will probably cause performance issues
+	for (std::size_t i = 0; i < this->body.size(); i += 1)
+	{
+		if (this->body[i]->id == id)
+		{
+			return this->index(static_cast<int>(i));
+		}
+	}
+	return {};
+}
+
+ldraw::Id Model::resolve(const QModelIndex& index) const
+{
+	return this->objectAt(index)->id;
+}
+
 void Model::getObjectPolygons(
 	const int index,
 	std::vector<gl::Polygon>& polygons_out,
 	ldraw::GetPolygonsContext* context) const
 {
-	const ldraw::Object* object = this->body[index].get();
+	const ldraw::Object* object = this->body[unsigned_cast(index)].get();
 	object->getPolygons(polygons_out, context);
 }
 
@@ -103,3 +119,13 @@
 {
 	this->body.push_back(std::move(object));
 }
+
+ldraw::Object* Model::objectAt(const QModelIndex& index)
+{
+	return this->body[unsigned_cast(index.row())].get();
+}
+
+const ldraw::Object* Model::objectAt(const QModelIndex& index) const
+{
+	return this->body[unsigned_cast(index.row())].get();
+}
--- a/src/model.h	Fri Feb 07 02:02:16 2020 +0200
+++ b/src/model.h	Fri Feb 07 23:59:06 2020 +0200
@@ -44,7 +44,8 @@
 	const QString& getName() const;
 	QVariant getObjectProperty(const int index, const ldraw::Property property) const;
 	std::vector<gl::Polygon> getPolygons(class DocumentManager* documents) const;
-	void getObjectPolygons(const int index, std::vector<gl::Polygon>& polygons_out, ldraw::GetPolygonsContext* context) const;
+	QModelIndex lookup(ldraw::Id id) const;
+	ldraw::Id resolve(const QModelIndex& index) const;
 signals:
 	void objectAdded(ldraw::Id id, int position);
 private:
@@ -54,6 +55,12 @@
 	void append(ModelObjectPointer&& object);
 	template<typename T, typename... Args>
 	ldraw::Id insert(int position, Args&&... args);
+	ldraw::Object* objectAt(const QModelIndex& index);
+	const ldraw::Object* objectAt(const QModelIndex& index) const;
+	void getObjectPolygons(
+		const int index,
+		std::vector<gl::Polygon>& polygons_out,
+		ldraw::GetPolygonsContext* context) const;
 	bool modified = false;
 	QString path;
 	LDHeader header;
--- a/src/ui/canvas.cpp	Fri Feb 07 02:02:16 2020 +0200
+++ b/src/ui/canvas.cpp	Fri Feb 07 23:59:06 2020 +0200
@@ -11,10 +11,39 @@
 	this->setMouseTracking(true);
 }
 
+void Canvas::handleSelectionChange(const QSet<ldraw::Id>& selectedIds, const QSet<ldraw::Id>& deselectedIds)
+{
+	this->selection.subtract(deselectedIds);
+	this->selection.unite(selectedIds);
+	this->compiler->setSelectedObjects(this->selection);
+	this->update();
+}
+
 void Canvas::mouseMoveEvent(QMouseEvent* event)
 {
 	const ldraw::Id id = this->pick(event->pos());
 	this->newStatusText("Selected: %1"_q.arg(id.value));
-	this->setHighlight(id);
+	this->highlighted = id;
+	this->totalMouseMove += (event->pos() - this->lastMousePosition).manhattanLength();
+	this->lastMousePosition = event->pos();
 	PartRenderer::mouseMoveEvent(event);
 }
+
+void Canvas::mousePressEvent(QMouseEvent* event)
+{
+	this->totalMouseMove = 0;
+	this->lastMousePosition = event->pos();
+	PartRenderer::mousePressEvent(event);
+}
+
+void Canvas::mouseReleaseEvent(QMouseEvent* event)
+{
+	if (this->totalMouseMove < (2.0 / sqrt(2)) * 5.0)
+	{
+		this->selection = {this->highlighted};
+		this->compiler->setSelectedObjects(this->selection);
+		emit selectionChanged(this->selection);
+		this->update();
+	}
+	PartRenderer::mouseReleaseEvent(event);
+}
--- a/src/ui/canvas.h	Fri Feb 07 02:02:16 2020 +0200
+++ b/src/ui/canvas.h	Fri Feb 07 23:59:06 2020 +0200
@@ -10,8 +10,17 @@
 		DocumentManager* documents,
 		const ldraw::ColorTable& colorTable,
 		QWidget* parent = nullptr);
+public slots:
+	void handleSelectionChange(const QSet<ldraw::Id>& selectedIds, const QSet<ldraw::Id>& deselectedIds);
 protected:
 	void mouseMoveEvent(QMouseEvent* event) override;
+	void mousePressEvent(QMouseEvent* event) override;
+	void mouseReleaseEvent(QMouseEvent* event) override;
 signals:
 	void newStatusText(const QString& newStatusText);
+	void selectionChanged(const QSet<ldraw::Id>& newSelection);
+private:
+	QPoint lastMousePosition;
+	int totalMouseMove = 0;
+	QSet<ldraw::Id> selection;
 };

mercurial