extract polygon cache out of Model

Thu, 03 Mar 2022 11:42:52 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Thu, 03 Mar 2022 11:42:52 +0200
changeset 150
b6cbba6e29a1
parent 148
e1ced2523cad
child 151
e628fc2e0c72

extract polygon cache out of Model

CMakeLists.txt file | annotate | diff | comparison | revisions
src/document.cpp file | annotate | diff | comparison | revisions
src/documentmanager.cpp file | annotate | diff | comparison | revisions
src/documentmanager.h 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/linetypes/object.h file | annotate | diff | comparison | revisions
src/linetypes/subfilereference.cpp file | annotate | diff | comparison | revisions
src/main.cpp file | annotate | diff | comparison | revisions
src/model.cpp file | annotate | diff | comparison | revisions
src/model.h file | annotate | diff | comparison | revisions
src/polygoncache.cpp file | annotate | diff | comparison | revisions
src/polygoncache.h file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Tue Nov 02 15:43:57 2021 +0200
+++ b/CMakeLists.txt	Thu Mar 03 11:42:52 2022 +0200
@@ -25,7 +25,7 @@
 source_group("3.2 Widgets" REGULAR_EXPRESSION "src/(ui|widgets)/.+\\.(cpp|h|ui)")
 source_group("3.1 Settings editor" REGULAR_EXPRESSION "src/settingseditor/.+\\.(cpp|h|ui)")
 source_group("3 User interface" REGULAR_EXPRESSION "src/(mainwindow|document|documentmanager|uiutilities)\\.(cpp|h|ui)")
-source_group("2 Model handling" REGULAR_EXPRESSION "src/(model|modeleditcontext|libraries|colors|parser|vertexmap|edithistory)\\.(cpp|h|ui)")
+source_group("2 Model handling" REGULAR_EXPRESSION "src/(model|modeleditcontext|libraries|colors|parser|vertexmap|edithistory|polygoncache)\\.(cpp|h|ui)")
 source_group("6 Editing tools" REGULAR_EXPRESSION "src/tools/.+\\.(cpp|h|ui)")
 
 set (LDFORGE_SOURCES
@@ -42,6 +42,7 @@
 	src/model.cpp
 	src/modeleditcontext.cpp
 	src/parser.cpp
+	src/polygoncache.cpp
 	src/uiutilities.cpp
 	src/version.cpp
 	src/vertexmap.cpp
@@ -95,6 +96,7 @@
 	src/model.h
 	src/modeleditcontext.h
 	src/parser.h
+	src/polygoncache.h
 	src/ring.h
 	src/uiutilities.h
 	src/utility.h
--- a/src/document.cpp	Tue Nov 02 15:43:57 2021 +0200
+++ b/src/document.cpp	Thu Mar 03 11:42:52 2022 +0200
@@ -57,7 +57,7 @@
 		QItemSelection selection;
 		for (ldraw::id_t id : newSelection)
 		{
-			QModelIndex index = this->model->lookup(id);
+			QModelIndex index = this->model->find(id);
 			if (index != QModelIndex{})
 			{
 				selection.select(index, index);
@@ -70,7 +70,7 @@
 	connect(this->ui.listView->selectionModel(), &QItemSelectionModel::selectionChanged,
 		[&](const QItemSelection& selected, const QItemSelection& deselected)
 	{
-		auto resolveIndex = [this](const QModelIndex& index){ return this->model->resolve(index); };
+		auto resolveIndex = [this](const QModelIndex& index){ return (*this->model)[index.row()]->id; };
 		auto resolve = [resolveIndex](const QItemSelection& selection)
 		{
 			return fn::map<QSet<ldraw::id_t>>(selection.indexes(), resolveIndex);
--- a/src/documentmanager.cpp	Tue Nov 02 15:43:57 2021 +0200
+++ b/src/documentmanager.cpp	Thu Mar 03 11:42:52 2022 +0200
@@ -45,8 +45,10 @@
 	const QString name = makeNewModelName();
 	this->openModels[modelId] = ModelInfo{
 		.model = std::make_unique<Model>(),
+		.id = modelId,
 		.opentype = OpenType::ManuallyOpened,
 	};
+	this->makePolygonCacheForModel(modelId);
 	return modelId;
 }
 
@@ -141,7 +143,8 @@
 	if (file.error() == QFile::NoError)
 	{
 		const ModelId modelId{++this->modelIdCounter};
-		this->openModels[modelId] = {std::move(newModel), path, openType};
+		this->openModels[modelId] = {std::move(newModel), modelId, path, openType};
+		this->makePolygonCacheForModel(modelId);
 		result = modelId;
 	}
 	else
@@ -299,6 +302,19 @@
 	return result;
 }
 
+PolygonCache *DocumentManager::getPolygonCacheForModel(ModelId modelId)
+{
+	auto it = this->polygonCaches.find(modelId);
+	if (it != this->polygonCaches.end())
+	{
+		return &it->second;
+	}
+	else
+	{
+		return nullptr;
+	}
+}
+
 /**
  * @brief Cleans up and erases models that are no longer required.
  */
@@ -311,6 +327,12 @@
 			and it->second.opentype == OpenType::AutomaticallyOpened
 			and not this->isReferencedByAnything(it->first)
 		) {
+			// Remove its polygon cache
+			const auto polygonCache = this->polygonCaches.find(it->first);
+			if (polygonCache != this->polygonCaches.end())
+			{
+				this->polygonCaches.erase(polygonCache);
+			}
 			// Remove the model
 			this->openModels.erase(it);
 			// We need to start over now. It is possible that other models that previously
@@ -345,6 +367,18 @@
 	return false;
 }
 
+void DocumentManager::makePolygonCacheForModel(const ModelId modelId)
+{
+	Model* model = this->getModelById(modelId);
+	if (model != nullptr)
+	{
+		this->polygonCaches.emplace(
+			std::piecewise_construct,
+			std::forward_as_tuple(modelId),
+			std::forward_as_tuple(model));
+	}
+}
+
 static QString findFile(QString referenceName, const QString& path, const LibraryManager& libraries)
 {
 	// Try to find the file in the same place as the model itself
--- a/src/documentmanager.h	Tue Nov 02 15:43:57 2021 +0200
+++ b/src/documentmanager.h	Thu Mar 03 11:42:52 2022 +0200
@@ -19,6 +19,7 @@
 #pragma once
 #include "libraries.h"
 #include "model.h"
+#include "polygoncache.h"
 
 class DocumentManager : public QObject
 {
@@ -55,10 +56,12 @@
 			QTextStream &errorStream);
 	bool saveModel(const ModelId modelId, QTextStream& errors);
 	std::optional<ModelId> findIdForModel(const Model* model) const;
+	PolygonCache* getPolygonCacheForModel(ModelId modelId);
 private:
 	struct ModelInfo
 	{
 		std::unique_ptr<Model> model;
+		ModelId id;
 		QString path;
 		OpenType opentype;
 		std::map<QString, ModelId> dependencies = {};
@@ -67,11 +70,13 @@
 	int modelIdCounter = 0;
 	int untitledNameCounter = 0;
 	std::map<ModelId, ModelInfo> openModels;
+	std::map<ModelId, PolygonCache> polygonCaches;
 	void loadDependenciesForModel(const ModelId modelId, const QString& path, LoadDepedenciesBag& bag);
 	void collectReferences(QSet<QString> &referenced, const QString& name, const Model* model);
 	void updateDependencies(ModelInfo* model);
 	void prune();
 	bool isReferencedByAnything(const ModelId modelId) const;
+	void makePolygonCacheForModel(const ModelId modelId);
 };
 
 QString pathToName(const QFileInfo& path);
--- a/src/gl/compiler.cpp	Tue Nov 02 15:43:57 2021 +0200
+++ b/src/gl/compiler.cpp	Thu Mar 03 11:42:52 2022 +0200
@@ -125,8 +125,9 @@
 }
 )";
 
-gl::Compiler::Compiler(const ldraw::ColorTable& colorTable, QObject* parent) :
+gl::Compiler::Compiler(Model *model, const ldraw::ColorTable& colorTable, QObject* parent) :
 	QObject{parent},
+	model{model},
 	colorTable{colorTable}
 {
 }
@@ -206,24 +207,32 @@
 	}
 }
 
-void gl::Compiler::build(Model* model, DocumentManager* context, const gl::RenderPreferences& preferences)
+void gl::Compiler::build(DocumentManager* context, const gl::RenderPreferences& preferences)
 {
 	this->boundingBox = {};
 	std::vector<Vertex> vboData[gl::NUM_POLYGON_TYPES];
-	const std::vector<gl::Polygon> polygons = model->getPolygons(context);
-	for (const gl::Polygon& polygon : polygons)
-	{
-		this->buildPolygon(polygon, vboData, preferences);
-	}
-	for (int arrayId = 0; arrayId < gl::NUM_POLYGON_TYPES; arrayId += 1)
+	std::optional<ModelId> modelId = context->findIdForModel(this->model);
+	if (modelId.has_value())
 	{
-		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();
+		PolygonCache* polygonBuilder = context->getPolygonCacheForModel(modelId.value());
+		if (polygonBuilder != nullptr)
+		{
+			const std::vector<gl::Polygon> polygons = polygonBuilder->getPolygons(context);
+			for (const gl::Polygon& polygon : polygons)
+			{
+				this->buildPolygon(polygon, vboData, preferences);
+			}
+			for (int arrayId = 0; arrayId < gl::NUM_POLYGON_TYPES; arrayId += 1)
+			{
+				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();
+			}
+		}
 	}
 }
 
--- a/src/gl/compiler.h	Tue Nov 02 15:43:57 2021 +0200
+++ b/src/gl/compiler.h	Thu Mar 03 11:42:52 2022 +0200
@@ -40,9 +40,9 @@
 {
 	Q_OBJECT
 public:
-	Compiler(const ldraw::ColorTable& colorTable, QObject* parent);
+	Compiler(Model* model, const ldraw::ColorTable& colorTable, QObject* parent);
 	~Compiler();
-	void build(Model* model, DocumentManager* context, const RenderPreferences& preferences);
+	void build(DocumentManager* context, const RenderPreferences& preferences);
 	std::size_t vertexCount(gl::ArrayClass arrayClass) const;
 	QColor getColorForPolygon(const gl::Polygon& polygon, const RenderPreferences& preferences);
 	glm::vec3 modelCenter() const;
@@ -85,6 +85,7 @@
 		glm::int32 selected = 0;
 	};
 	void buildPolygon(Polygon polygon, std::vector<Vertex>* vboData, const gl::RenderPreferences& preferences);
+	Model* const model;
 	std::size_t storedVertexCounts[gl::NUM_POLYGON_TYPES] = {0};
 	bool initialized = false;
 	BoundingBox boundingBox;
--- a/src/gl/partrenderer.cpp	Tue Nov 02 15:43:57 2021 +0200
+++ b/src/gl/partrenderer.cpp	Thu Mar 03 11:42:52 2022 +0200
@@ -38,7 +38,7 @@
 	model{model},
 	documents{documents},
 	colorTable{colorTable},
-	compiler{new gl::Compiler{this->colorTable, this}}
+	compiler{new gl::Compiler{model, this->colorTable, this}}
 {
 	this->setMouseTracking(true);
 	connect(model, &Model::rowsInserted, [&]{
@@ -117,7 +117,7 @@
 {
 	if (this->needBuild)
 	{
-		this->compiler->build(this->model, this->documents, this->renderPreferences);
+		this->compiler->build(this->documents, this->renderPreferences);
 		this->needBuild = false;
 	}
 	this->checkForGLErrors();
--- a/src/linetypes/object.h	Tue Nov 02 15:43:57 2021 +0200
+++ b/src/linetypes/object.h	Thu Mar 03 11:42:52 2022 +0200
@@ -86,6 +86,20 @@
 	virtual void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair);
 };
 
+/**
+ * @brief Tests whether the object is exactly of the specified type
+ * @tparam R Type of LDraw line type object to test for
+ * @param object Object to test
+ * @returns whether the type of the object specified by @c id is the same type as R. Returns false if it is a subclass.
+ */
+template<typename R>
+bool isA(const ldraw::Object* object)
+{
+	const std::type_info& a = typeid(*object);
+	const std::type_info& b = typeid(R);
+	return a == b;
+}
+
 template<ldraw::Property property>
 ldraw::Object::SetPropertyResult ldraw::Object::setProperty(const ldraw::PropertyType<property>& value)
 {
--- a/src/linetypes/subfilereference.cpp	Tue Nov 02 15:43:57 2021 +0200
+++ b/src/linetypes/subfilereference.cpp	Thu Mar 03 11:42:52 2022 +0200
@@ -1,6 +1,7 @@
 #include "subfilereference.h"
 #include "documentmanager.h"
 #include "invert.h"
+#include "polygoncache.h"
 
 ldraw::SubfileReference::SubfileReference
 (
@@ -45,11 +46,20 @@
 	GetPolygonsContext* context
 ) const
 {
-	Model* referencedModel = this->resolve(context->modelId, context->documents);
-	if (referencedModel != nullptr)
+	Model* dependency = this->resolve(context->modelId, context->documents);
+	PolygonCache* referencedModelPolygonBuilder = nullptr;
+	if (dependency != nullptr)
+	{
+		const auto dependencyModelId = context->documents->findIdForModel(dependency);
+		if (dependencyModelId.has_value())
+		{
+			referencedModelPolygonBuilder = context->documents->getPolygonCacheForModel(dependencyModelId.value());
+		}
+	}
+	if (referencedModelPolygonBuilder != nullptr)
 	{
 		const bool needInverting = glm::determinant(this->transformation) < 0;
-		const std::vector<gl::Polygon> modelPolygons = referencedModel->getPolygons(context->documents);
+		const std::vector<gl::Polygon> modelPolygons = referencedModelPolygonBuilder->getPolygons(context->documents);
 		polygons.reserve(polygons.size() + modelPolygons.size());
 		for (gl::Polygon polygon : modelPolygons)
 		{
--- a/src/main.cpp	Tue Nov 02 15:43:57 2021 +0200
+++ b/src/main.cpp	Thu Mar 03 11:42:52 2022 +0200
@@ -29,14 +29,7 @@
 	QCoreApplication::setOrganizationDomain("hecknology.net");
 	::qRegisterMetaTypeStreamOperators<Library>("Library");
 	::qRegisterMetaTypeStreamOperators<Libraries>("Libraries");
-	
-	glm::mat4 mat = glm::scale(glm::mat4{1}, {3, 3, 3});
-	glm::vec4 x = {1,2,3,4};
-	
 	QApplication app{argc, argv};
-	/*
-	QMessageBox::information(nullptr, "", QMetaType::typeName( qMetaTypeId<ldraw::Color>() ));
-	*/
 	MainWindow mainwindow;
 	mainwindow.show();
 	return app.exec();
--- a/src/model.cpp	Tue Nov 02 15:43:57 2021 +0200
+++ b/src/model.cpp	Thu Mar 03 11:42:52 2022 +0200
@@ -32,7 +32,6 @@
 Model::Model(QObject *parent) :
 	QAbstractListModel{parent}
 {
-	connect(this, &Model::dataChanged, [&](){ this->needRecache = true; });
 }
 
 /**
@@ -132,36 +131,11 @@
 }
 
 /**
- * @brief Gets a list of GL polygons that are used to represent this model.
- * @details Will build polygons if polygons are outdated.
- * @param documents Documents to use to resolve subfile references
- * @return vector of GL polygons
- */
-std::vector<gl::Polygon> Model::getPolygons(DocumentManager* documents) const
-{
-	if (this->needRecache)
-	{
-		this->cachedPolygons.clear();
-		const std::optional<ModelId> modelId = documents->findIdForModel(this);
-		if (modelId.has_value())
-		{
-			ldraw::GetPolygonsContext context{modelId.value(), documents};
-			for (int i = 0; i < this->size(); i += 1)
-			{
-				this->getObjectPolygons(i, this->cachedPolygons, &context);
-			}
-		}
-		this->needRecache = false;
-	}
-	return this->cachedPolygons;
-}
-
-/**
  * @brief Finds the position of the specified object in the model
  * @param id Object id to look for
  * @return model index
  */
-QModelIndex Model::lookup(ldraw::id_t id) const
+QModelIndex Model::find(ldraw::id_t id) const
 {
 	// FIXME: This linear search will probably cause performance issues
 	for (std::size_t i = 0; i < this->body.size(); i += 1)
@@ -179,7 +153,7 @@
  * @param index Position of the object in the model
  * @return id
  */
-ldraw::id_t Model::resolve(const QModelIndex& index) const
+ldraw::id_t Model::idAt(const QModelIndex& index) const
 {
 	return this->objectAt(index)->id;
 }
@@ -211,21 +185,6 @@
 #endif
 
 /**
- * @brief Gets the GL polygons of the object at the specified position in the model
- * @param index Index of object in the model
- * @param polygons_out Vector to add polygons into
- * @param context Context to use to resolve subfile references
- */
-void Model::getObjectPolygons(
-	const int index,
-	std::vector<gl::Polygon>& polygons_out,
-	ldraw::GetPolygonsContext* context) const
-{
-	const ldraw::Object* object = this->body[unsigned_cast(index)].get();
-	object->getPolygons(polygons_out, context);
-}
-
-/**
  * @brief Called by the editing context to signal to the model that editing is done.
  */
 void Model::editFinished()
@@ -239,7 +198,7 @@
  */
 void Model::objectModified(ldraw::id_t id)
 {
-	const QModelIndex index = this->lookup(id);
+	const QModelIndex index = this->find(id);
 	Q_EMIT this->dataChanged(index, index);
 }
 
@@ -250,10 +209,10 @@
 void Model::append(ModelObjectPointer&& object)
 {
 	const int position = static_cast<int>(this->body.size());
-	Q_EMIT beginInsertRows({}, position, position);
+	Q_EMIT this->beginInsertRows({}, position, position);
 	this->body.push_back(std::move(object));
-	Q_EMIT endInsertRows();
-	this->needRecache = true;
+	Q_EMIT this->endInsertRows();
+	Q_EMIT this->objectAdded(position);
 }
 
 /**
@@ -264,10 +223,11 @@
 {
 	if (position >= 0 and position < signed_cast(this->body.size()))
 	{
-		Q_EMIT beginRemoveRows({}, position, position);
+		Q_EMIT this->beginRemoveRows({}, position, position);
+		const ldraw::id_t id = this->body[position]->id;
 		this->body.erase(std::begin(this->body) + position);
-		Q_EMIT endRemoveRows();
-		this->needRecache = true;
+		Q_EMIT this->endRemoveRows();
+		Q_EMIT this->objectRemoved(id);
 	}
 }
 
@@ -303,29 +263,42 @@
 	}
 }
 
+ldraw::Object* Model::operator[](int index)
+{
+	if (index >= 0 and index < this->size())
+	{
+		return this->body[index].get();
+	}
+	else
+	{
+		throw std::out_of_range{"index out of range"};
+	}
+}
+
 /**
  * @brief Modifies the !LDRAW_ORG line so that it becomes unofficial
  */
-void Model::makeUnofficial()
+void makeUnofficial(Model& model)
 {
-	if (this->body.size() >= 4)
+	if (model.size() >= 4)
 	{
-		const ldraw::id_t id = this->body[3]->id;
-		if (this->isA<ldraw::MetaCommand>(id))
+		const ldraw::Object* ldrawOrgLine = model[3];
+		if (isA<ldraw::MetaCommand>(ldrawOrgLine))
 		{
-			const QString& body = this->body[3]->getProperty<ldraw::Property::Text>();
+			const QString& body = ldrawOrgLine->getProperty<ldraw::Property::Text>();
 			if (body.startsWith("!LDRAW_ORG ") and not body.startsWith("!LDRAW_ORG Unofficial_"))
 			{
+				// Add Unofficial_ to part type
 				QStringList tokens = body.split(" ");
 				tokens[1] = "Unofficial_" + tokens[1];
-				// Remove the UPDATE tag
+				// Remove the UPDATE tag if it's there
 				if (tokens.size() >= 4 && tokens[2] == "UPDATE")
 				{
 					tokens.removeAt(3);
 					tokens.removeAt(2);
 				}
-				EditContext editor = this->edit();
-				editor.setObjectProperty<ldraw::Property::Text>(id, tokens.join(" "));
+				Model::EditContext editor = model.edit();
+				editor.setObjectProperty<ldraw::Property::Text>(ldrawOrgLine->id, tokens.join(" "));
 			}
 		}
 	}
--- a/src/model.h	Tue Nov 02 15:43:57 2021 +0200
+++ b/src/model.h	Thu Mar 03 11:42:52 2022 +0200
@@ -37,23 +37,18 @@
 	class EditContext;
 	Model(QObject* parent = nullptr);
 	Model(const Model&) = delete;
+
 	int size() const;
 	ldraw::id_t at(int index) const;
 	EditContext edit();
 	int rowCount(const QModelIndex&) const override;
 	QVariant data(const QModelIndex& index, int role) const override;
 	QVariant getHeaderProperty(const HeaderProperty property);
-	const QString& getName() const;
 	QVariant getObjectProperty(const int index, const ldraw::Property property) const;
 	template<ldraw::Property Property>
 	ldraw::PropertyType<Property> getObjectProperty(const ldraw::id_t id) const;
-	std::vector<gl::Polygon> getPolygons(class DocumentManager* documents) const;
-	QModelIndex lookup(ldraw::id_t id) const;
-	ldraw::id_t resolve(const QModelIndex& index) const;
-	template<typename R>
-	ldraw::Id<R> checkType(ldraw::id_t id) const;
-	template<typename R>
-	bool isA(ldraw::id_t id) const;
+	QModelIndex find(ldraw::id_t id) const;
+	ldraw::id_t idAt(const QModelIndex& index) const;
 	template<typename R>
 	const R* get(ldraw::Id<R> id) const;
 	template<typename R>
@@ -67,10 +62,11 @@
 	template<typename R, typename Fn>
 	void apply(Fn f) const;
 	void save(QIODevice *device) const;
-	void makeUnofficial();
+	ldraw::Object* operator[](int index);
 Q_SIGNALS:
-	void objectAdded(ldraw::id_t id, int position);
-	void objectModified(ldraw::id_t id, int position);
+	void objectAdded(int position);
+	void objectModified(int position);
+	void objectRemoved(ldraw::id_t id);
 private:
 	using ModelObjectPointer = std::unique_ptr<ldraw::Object>;
 	template<typename T, typename... Args>
@@ -85,38 +81,19 @@
 	T* objectAt(ldraw::Id<T> id);
 	template<typename T>
 	const T* objectAt(ldraw::Id<T> id) const;
-	void getObjectPolygons(
-		const int index,
-		std::vector<gl::Polygon>& polygons_out,
-		ldraw::GetPolygonsContext* context) const;
 	void editFinished();
 	void objectModified(ldraw::id_t id);
 	bool modified = false;
 	LDHeader header;
 	std::vector<ModelObjectPointer> body;
 	std::map<ldraw::id_t, ldraw::Object*> objectsById;
-	mutable std::vector<gl::Polygon> cachedPolygons;
-	mutable bool needRecache = true;
 	/**
 	 * @brief Amount of model edit contexts active
 	 */
 	int editCounter = 0;
 };
 
-/**
- * @brief Checks whether the id is exactly of the specified type
- * @tparam R Type of LDraw line type object to test for
- * @param id Id of object to test
- * @returns whether the type of the object specified by @c id is the same type as R. Returns false if it is a subclass.
- */
-template<typename R>
-bool Model::isA(ldraw::id_t id) const
-{
-	const ldraw::Object* object = this->objectAt(this->lookup(id));
-	const std::type_info& a = typeid(*object);
-	const std::type_info& b = typeid(R);
-	return a == b;
-}
+void makeUnofficial(Model& model);
 
 /**
  * @brief Calls the specified function to all matching objects in the model
@@ -136,24 +113,6 @@
 	}
 }
 
-/**
- * \brief Checks type of object behind id
- * Checks whether the specified id refers to an object of the specified type.
- * \returns id casted to subclass if appropriate, null id otherwise
- */
-template<typename R>
-ldraw::Id<R> Model::checkType(ldraw::id_t id) const
-{
-	if (dynamic_cast<const R*>(this->objectAt(this->lookup(id))) != nullptr)
-	{
-		return ldraw::Id<R>{id.value};
-	}
-	else
-	{
-		return ldraw::NULL_ID;
-	}
-}
-
 template<typename T, typename... Args>
 ldraw::Id<T> Model::append(Args&&... args)
 {
@@ -162,9 +121,8 @@
 	this->body.push_back(std::make_unique<T>(args...));
 	ldraw::Object* pointer = this->body.back().get();
 	this->objectsById[pointer->id] = pointer;
-	Q_EMIT objectAdded(pointer->id, static_cast<int>(this->body.size() - 1));
+	Q_EMIT objectAdded(static_cast<int>(this->body.size() - 1));
 	Q_EMIT endInsertRows();
-	this->needRecache = true;
 	return ldraw::Id<T>{pointer->id.value};
 }
 
@@ -175,9 +133,8 @@
 	this->body.insert(std::begin(this->body) + position, std::make_unique<T>(args...));
 	ldraw::Object* pointer = this->body[position].get();
 	this->objectsById[pointer->id] = pointer;
-	Q_EMIT objectAdded(pointer->id, static_cast<int>(position));
+	Q_EMIT objectAdded(static_cast<int>(position));
 	Q_EMIT endInsertRows();
-	this->needRecache = true;
 	return ldraw::Id<T>{pointer->id.value};
 }
 
@@ -191,7 +148,7 @@
 Model::Get2Result<R> Model::get2(const ldraw::Id<R> id) const
 {
 	Get2Result<R> result;
-	result.index = this->lookup(id);
+	result.index = this->find(id);
 	if (result.index.isValid())
 	{
 		result.object = static_cast<const R*>(this->objectAt(result.index));
@@ -211,13 +168,13 @@
 template<typename T>
 T* Model::objectAt(ldraw::Id<T> id)
 {
-	return static_cast<T*>(this->objectAt(this->lookup(id)));
+	return static_cast<T*>(this->objectAt(this->find(id)));
 }
 
 template<typename T>
 const T* Model::objectAt(ldraw::Id<T> id) const
 {
-	return static_cast<const T*>(this->objectAt(this->lookup(id)));
+	return static_cast<const T*>(this->objectAt(this->find(id)));
 }
 
 /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/polygoncache.cpp	Thu Mar 03 11:42:52 2022 +0200
@@ -0,0 +1,51 @@
+#include "polygoncache.h"
+#include "documentmanager.h"
+
+PolygonCache::PolygonCache(Model *model) :
+	model{model}
+{
+	const auto mark = [this](){ this->needRecache = true; };
+	connect(model, &Model::dataChanged, mark);
+	connect(model, &Model::objectAdded, mark);
+	connect(model, &Model::objectRemoved, mark);
+}
+
+/**
+ * @brief Gets a list of GL polygons that are used to represent this model.
+ * @details Will build polygons if polygons are outdated.
+ * @param documents Documents to use to resolve subfile references
+ * @return vector of GL polygons
+ */
+std::vector<gl::Polygon> PolygonCache::getPolygons(DocumentManager* documents)
+{
+	if (this->needRecache)
+	{
+		this->cachedPolygons.clear();
+		const std::optional<ModelId> modelId = documents->findIdForModel(this->model);
+		if (modelId.has_value())
+		{
+			ldraw::GetPolygonsContext context{modelId.value(), documents};
+			for (int i = 0; i < this->model->size(); i += 1)
+			{
+				this->getObjectPolygons(i, this->cachedPolygons, &context);
+			}
+		}
+		this->needRecache = false;
+	}
+	return this->cachedPolygons;
+}
+
+/**
+ * @brief Gets the GL polygons of the object at the specified position in the model
+ * @param index Index of object in the model
+ * @param polygons_out Vector to add polygons into
+ * @param context Context to use to resolve subfile references
+ */
+void PolygonCache::getObjectPolygons(
+	const int index,
+	std::vector<gl::Polygon>& polygons_out,
+	ldraw::GetPolygonsContext* context) const
+{
+	const ldraw::Object* object = (*this->model)[unsigned_cast(index)];
+	object->getPolygons(polygons_out, context);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/polygoncache.h	Thu Mar 03 11:42:52 2022 +0200
@@ -0,0 +1,19 @@
+#pragma once
+#include "main.h"
+#include "model.h"
+
+class PolygonCache : QObject
+{
+	Q_OBJECT
+public:
+	PolygonCache(Model* model);
+	std::vector<gl::Polygon> getPolygons(class DocumentManager* documents);
+private:
+	void getObjectPolygons(
+		const int index,
+		std::vector<gl::Polygon>& polygons_out,
+		ldraw::GetPolygonsContext* context) const;
+	Model* const model;
+	std::vector<gl::Polygon> cachedPolygons;
+	bool needRecache = true;
+};

mercurial