at least VAOs work now

Wed, 22 Jan 2020 00:23:29 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Wed, 22 Jan 2020 00:23:29 +0200
changeset 26
3a9e761e4faa
parent 25
6de5ac1fb471
child 27
c57fb7a5ffa3

at least VAOs work now

CMakeLists.txt file | annotate | diff | comparison | revisions
src/basics.h file | annotate | diff | comparison | revisions
src/colors.cpp file | annotate | diff | comparison | revisions
src/colors.h file | annotate | diff | comparison | revisions
src/document.cpp file | annotate | diff | comparison | revisions
src/document.h file | annotate | diff | comparison | revisions
src/gl/common.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/gl/partrenderer.h file | annotate | diff | comparison | revisions
src/libraries.cpp file | annotate | diff | comparison | revisions
src/libraries.h file | annotate | diff | comparison | revisions
src/linetypes/object.h file | annotate | diff | comparison | revisions
src/linetypes/quadrilateral.cpp file | annotate | diff | comparison | revisions
src/linetypes/quadrilateral.h file | annotate | diff | comparison | revisions
src/linetypes/subfilereference.cpp file | annotate | diff | comparison | revisions
src/linetypes/subfilereference.h file | annotate | diff | comparison | revisions
src/linetypes/triangle.cpp file | annotate | diff | comparison | revisions
src/linetypes/triangle.h file | annotate | diff | comparison | revisions
src/main.h file | annotate | diff | comparison | revisions
src/mainwindow.cpp file | annotate | diff | comparison | revisions
src/mainwindow.h file | annotate | diff | comparison | revisions
src/maths.h file | annotate | diff | comparison | revisions
src/matrix.cpp file | annotate | diff | comparison | revisions
src/matrix.h file | annotate | diff | comparison | revisions
src/model.h file | annotate | diff | comparison | revisions
src/modeleditcontext.cpp file | annotate | diff | comparison | revisions
src/modeleditcontext.h file | annotate | diff | comparison | revisions
src/parser.cpp file | annotate | diff | comparison | revisions
src/ring.h file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Sun Jan 19 14:25:57 2020 +0200
+++ b/CMakeLists.txt	Wed Jan 22 00:23:29 2020 +0200
@@ -18,6 +18,7 @@
 include_directories(${GLUT_INCLUDE_DIR})
 
 set (LDFORGE_SOURCES
+	src/colors.cpp
 	src/document.cpp
 	src/documentmanager.cpp
 	src/libraries.cpp
--- a/src/basics.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/basics.h	Wed Jan 22 00:23:29 2020 +0200
@@ -41,6 +41,18 @@
 	Z = 2
 };
 
+enum Result
+{
+	Success = 0,
+	PartialSuccess,
+	Failure
+};
+
+constexpr bool failed(Result r)
+{
+	return r == Failure;
+}
+
 enum Winding
 {
 	NoWinding,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colors.cpp	Wed Jan 22 00:23:29 2020 +0200
@@ -0,0 +1,84 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2020 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "colors.h"
+
+const ColorTable::ColorDefinition ColorTable::unknownColor{{}, {}, "Unknown"};
+
+void ColorTable::clear()
+{
+	definitions = {};
+}
+
+Result ColorTable::load(QIODevice& device, QTextStream& errors)
+{
+	this->clear();
+	if (device.isReadable())
+	{
+		QTextStream stream{&device};
+		QString line;
+		while (stream.readLineInto(&line))
+		{
+			this->loadColorFromString(line);
+		}
+		return Success;
+	}
+	else
+	{
+		errors << "could not read colors";
+		return Failure;
+	}
+}
+
+const ColorTable::ColorDefinition& ColorTable::operator[](Color color) const
+{
+	auto it = this->definitions.find(color.index);
+	if (it != this->definitions.end())
+	{
+		return *it;
+	}
+	else
+	{
+		return unknownColor;
+	}
+}
+
+void ColorTable::loadColorFromString(const QString& string)
+{
+	const QRegExp pattern{
+		R"(^\s*0 \!COLOUR\s+([^\s]+)\s+)"_q +
+		R"(CODE\s+(\d+)\s+)"_q +
+		R"(VALUE\s+(\#[0-9a-fA-F]{3,6})\s+)"_q +
+		R"(EDGE\s+(\#[0-9a-fA-F]{3,6}))"_q +
+		R"((?:\s+ALPHA\s+(\d+))?)"_q
+	};
+	if (pattern.indexIn(string) != -1)
+	{
+		const int code = pattern.cap(2).toInt();
+		ColorDefinition& definition = definitions[code];
+		definition = {}; // in case there's an existing definition
+		definition.name = pattern.cap(1);
+		definition.faceColor = pattern.cap(3);
+		definition.edgeColor = pattern.cap(4);
+		if (not pattern.cap(5).isEmpty())
+		{
+			const int alpha = pattern.cap(5).toInt();
+			definition.faceColor.setAlpha(alpha);
+		}
+	}
+}
--- a/src/colors.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/colors.h	Wed Jan 22 00:23:29 2020 +0200
@@ -17,6 +17,7 @@
  */
 
 #pragma once
+#include <QColor>
 #include "main.h"
 
 struct Color
@@ -24,6 +25,24 @@
 	qint32 index;
 };
 
+class ColorTable
+{
+public:
+	struct ColorDefinition
+	{
+		QColor faceColor;
+		QColor edgeColor;
+		QString name;
+	};
+	void clear();
+	Result load(QIODevice& device, QTextStream& errors);
+	const ColorDefinition& operator[](Color index) const;
+	static const ColorDefinition unknownColor;
+private:
+	void loadColorFromString(const QString& string);
+	QMap<qint32, ColorDefinition> definitions;
+};
+
 inline bool operator==(const Color& one, const Color& other)
 {
 	return one.index == other.index;
--- a/src/document.cpp	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/document.cpp	Wed Jan 22 00:23:29 2020 +0200
@@ -20,11 +20,12 @@
 #include "ui_document.h"
 #include "model.h"
 
-Document::Document(Model* model, DocumentManager* documents, QWidget* parent) :
+Document::Document(Model* model, DocumentManager* documents, const ColorTable& colorTable, QWidget* parent) :
 	QWidget{parent},
 	model{model},
 	documents{documents},
-	renderer{new PartRenderer{model, documents, this}},
+	colorTable{colorTable},
+	renderer{new PartRenderer{model, documents, colorTable, this}},
 	ui{*new Ui::Document}
 {
 	this->ui.setupUi(this);
--- a/src/document.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/document.h	Wed Jan 22 00:23:29 2020 +0200
@@ -32,7 +32,7 @@
 {
 	Q_OBJECT
 public:
-	explicit Document(Model* model, DocumentManager* documents, QWidget *parent = nullptr);
+	explicit Document(Model* model, DocumentManager* documents, const ColorTable& colorTable, QWidget *parent = nullptr);
 	~Document();
 	QByteArray saveSplitterState() const;
 	void restoreSplitterState(const QByteArray& state);
@@ -41,6 +41,7 @@
 private:
 	Model* model;
 	DocumentManager* const documents;
+	const ColorTable& colorTable;
 	PartRenderer* renderer;
 	Ui::Document& ui;
 };
--- a/src/gl/common.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/gl/common.h	Wed Jan 22 00:23:29 2020 +0200
@@ -129,31 +129,27 @@
 	}
 
 	// Vbo names
-	enum class VboClass : std::uint8_t
+	enum class ArrayClass : std::uint8_t
 	{
 		Lines,
 		Triangles,
 		Quads,
 		ConditionalLines
 	};
-	constexpr int numVboClasses = 4;
+	constexpr ArrayClass ARRAY_CLASSES[] = {ArrayClass::Lines, ArrayClass::Triangles, ArrayClass::Quads, ArrayClass::ConditionalLines};
+	constexpr int NUM_ARRAY_CLASSES = countof(ARRAY_CLASSES);
+	constexpr int FLOATS_PER_VERTEX = 7;
 
 	// Types of vbo per object
 	enum class VboSubclass : std::uint8_t
 	{
-		Surfaces,
-		RegularColors,
-		PickColors,
-		BfcFrontColors,
-		BfcBackColors,
-		RandomColors,
-		Normals,
-		InvertedNormals
+		VertexData,
+		Normals
 	};
-	constexpr int numVboSubclasses = 8;
+	constexpr int numVboSubclasses = 2;
 
 	// Amount of vbos overall
-	constexpr int numVbos = gl::numVboClasses * gl::numVboSubclasses;
+	constexpr int numVbos = gl::NUM_ARRAY_CLASSES * gl::numVboSubclasses;
 
 	enum class RenderStyle
 	{
@@ -162,4 +158,9 @@
 		BfcRedGreen,
 		RandomColors
 	};
+
+	inline void* offset(const int n)
+	{
+		return reinterpret_cast<void*>(n);
+	}
 }
--- a/src/gl/compiler.cpp	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/gl/compiler.cpp	Wed Jan 22 00:23:29 2020 +0200
@@ -19,27 +19,52 @@
 #define GL_GLEXT_PROTOTYPES
 #include <GL/glu.h>
 #include <GL/glext.h>
+#include <QMessageBox>
 #include "gl/compiler.h"
 #include "documentmanager.h"
 #include "invert.h"
 #include "ring.h"
 
-gl::Compiler::Compiler(QObject* parent) :
-	QObject{parent}
+gl::Compiler::Compiler(const ColorTable& colorTable, QObject* parent) :
+	QObject{parent},
+	colorTable{colorTable}
 {
 }
 
 gl::Compiler::~Compiler()
 {
+	if (this->initialized)
+	{
+		for (int arrayId = 0; arrayId < gl::NUM_ARRAY_CLASSES; arrayId += 1)
+		{
+			glDeleteProgram(this->glObjects[arrayId].program);
+			glDeleteShader(this->glObjects[arrayId].vertexShader);
+			glDeleteProgram(this->glObjects[arrayId].fragmentShader);
+		}
+	}
+}
 
+void gl::Compiler::initialize()
+{
+	if (not this->initialized)
+	{
+		this->initializeOpenGLFunctions();
+		for (int arrayId = 0; arrayId < gl::NUM_ARRAY_CLASSES; arrayId += 1)
+		{
+			QOpenGLVertexArrayObject& vao = this->vertexArrays[arrayId];
+			vao.create();
+			vao.bind();
+			glGenBuffers(gl::numVboSubclasses, &this->storedVbo[gl::numVboSubclasses * arrayId]);
+			this->buildShaders(arrayId);
+			vao.release();
+		}
+		//glGenBuffers(countof(this->storedVbo), &this->storedVbo[0]);
+		this->initialized = true;
+	}
 }
 
 void gl::Compiler::build(Model* model, DocumentManager* context)
 {
-	if (not this->initialized)
-	{
-		this->initializeVbo();
-	}
 	this->boundingBox = {};
 	std::vector<GLfloat> vboData[gl::numVbos];
 	const std::vector<gl::Polygon> polygons = model->getPolygons(context);
@@ -47,29 +72,41 @@
 	{
 		this->buildPolygon(polygon, vboData);
 	}
-	for (int i = 0; i < gl::numVbos; i += 1)
+	/*
+	for (int index = 0; index < gl::numVbos; index += 1)
+	{
+		this->upload(index, vboData[index]);
+	}*/
+	for (int arrayId = 0; arrayId < gl::NUM_ARRAY_CLASSES; arrayId += 1)
 	{
-		this->upload(i, vboData[i]);
+		this->vertexArrays[arrayId].bind();
+		for (int i = 0; i < gl::numVboSubclasses; i += 1)
+		{
+			const int vboIndex = gl::vboIndex({static_cast<gl::ArrayClass>(arrayId), static_cast<gl::VboSubclass>(i)});
+			this->upload(vboIndex, vboData[vboIndex]);
+		}
+		this->vertexArrays[arrayId].release();
 	}
 }
 
-gl::VboClass classifyPolygon(const gl::Polygon& polygon)
+gl::ArrayClass classifyPolygon(const gl::Polygon& polygon)
 {
 	switch (polygon.type)
 	{
 	case gl::Polygon::EdgeLine:
-		return gl::VboClass::Lines;
+		return gl::ArrayClass::Lines;
 	case gl::Polygon::Triangle:
-		return gl::VboClass::Triangles;
+		return gl::ArrayClass::Triangles;
 	case gl::Polygon::Quadrilateral:
-		return gl::VboClass::Quads;
+		return gl::ArrayClass::Quads;
 	case gl::Polygon::ConditionalEdge:
-		return gl::VboClass::ConditionalLines;
+		return gl::ArrayClass::ConditionalLines;
 	}
-	return gl::VboClass::Lines;
+	return gl::ArrayClass::Lines;
 }
 
-QColor colorFromId(linetypes::Id id)
+[[maybe_unused]]
+static QColor colorFromId(linetypes::Id id)
 {
 	// Calculate a color based from this index. This method caters for
 	// 16777216 objects. I don't think that will be exceeded anytime soon.
@@ -79,129 +116,62 @@
 	return {r, g, b};
 }
 
-void writeVertex(std::vector<GLfloat>& data, const Point3D& point)
-{
-	data.push_back(static_cast<GLfloat>(point.x));
-	data.push_back(static_cast<GLfloat>(point.y));
-	data.push_back(static_cast<GLfloat>(point.z));
-}
-
 void gl::Compiler::buildPolygon(gl::Polygon polygon, std::vector<GLfloat>* vboData)
 {
-	const gl::VboClass vboClass = classifyPolygon(polygon);
-	auto vboBuffer = [&](VboSubclass subclass) -> std::vector<GLfloat>&
-	{
-		return vboData[gl::vboIndex({vboClass, subclass})];
-	};
+	const gl::ArrayClass vboClass = classifyPolygon(polygon);
+	std::vector<GLfloat>& vertexBuffer = vboData[gl::vboIndex({vboClass, gl::VboSubclass::VertexData})];
+	std::vector<GLfloat>& normalsBuffer = vboData[gl::vboIndex({vboClass, gl::VboSubclass::Normals})];
 	auto vertexRing = iter::ring(polygon.vertices, polygon.numPolygonVertices());
+	reserveMore(normalsBuffer, polygon.numPolygonVertices() * 3_z);
 	for (unsigned int i = 0; i < polygon.numPolygonVertices(); i += 1)
 	{
 		const Point3D& v1 = vertexRing[i - 1];
 		const Point3D& v2 = vertexRing[i];
 		const Point3D& v3 = vertexRing[i + 1];
 		const QVector3D normal = QVector3D::crossProduct(v3 - v2, v1 - v2).normalized();
-		std::vector<GLfloat>& data = vboBuffer(VboSubclass::Normals);
 		for (const GLfloat coord : {normal.x(), normal.y(), normal.z()})
-			data.push_back(coord);
+			normalsBuffer.push_back(coord);
 	}
-	vboBuffer(VboSubclass::Surfaces).reserve(vboBuffer(VboSubclass::Surfaces).size() + polygon.numPolygonVertices());
+	reserveMore(vertexBuffer, polygon.numPolygonVertices() * 7);
+	const QColor color = this->getColorForPolygon(polygon);
 	// Transform vertices so that they're suitable for GL rendering
 	for (unsigned int i = 0; i < polygon.numPolygonVertices(); i += 1)
 	{
+		Point3D& point = polygon.vertices[i];
 		polygon.vertices[i].y = -polygon.vertices[i].y;
 		polygon.vertices[i].z = -polygon.vertices[i].z;
 		this->boundingBox.consider(polygon.vertices[i]);
-		writeVertex(vboBuffer(VboSubclass::Surfaces), polygon.vertices[i]);
+		vertexBuffer.push_back(static_cast<GLfloat>(point.x));
+		vertexBuffer.push_back(static_cast<GLfloat>(point.y));
+		vertexBuffer.push_back(static_cast<GLfloat>(point.z));
+		vertexBuffer.push_back(static_cast<GLfloat>(color.redF()));
+		vertexBuffer.push_back(static_cast<GLfloat>(color.greenF()));
+		vertexBuffer.push_back(static_cast<GLfloat>(color.blueF()));
+		vertexBuffer.push_back(static_cast<GLfloat>(color.alphaF()));
 	}
-	this->writeColor(vboData, polygon, VboSubclass::RegularColors);
-	this->writeColor(vboData, polygon, VboSubclass::PickColors);
-	this->writeColor(vboData, polygon, VboSubclass::BfcFrontColors);
-	this->writeColor(vboData, polygon, VboSubclass::BfcBackColors);
 }
 
-QColor gl::Compiler::getColorForPolygon(const gl::Polygon& polygon, VboSubclass subclass)
+QColor gl::Compiler::getColorForPolygon(const gl::Polygon& polygon)
 {
 	QColor color;
-
-	switch (subclass)
+	// For normal colors, use the polygon's color.
+	if (polygon.color == colors::main)
 	{
-	case VboSubclass::Surfaces:
-	case VboSubclass::Normals:
-	case VboSubclass::InvertedNormals:
-		// Surface and normal VBOs contain vertex data, not colors. So we can't return anything meaningful.
-		return {};
-	case VboSubclass::BfcFrontColors:
-		// Use the constant green color for BFC front colors
-		return {64, 192, 80};
-	case VboSubclass::BfcBackColors:
-		// Use the constant red color for BFC back colors
-		return {208, 64, 64};
-	case VboSubclass::PickColors:
-		// For the picking scene, use unique picking colors provided by the model.
-		return colorFromId(polygon.id);
-	case VboSubclass::RandomColors:
-		// For the random color scene, the owner object has rolled up a random color. Use that.
-		color = {255, 64, 255}; // polygonOwner->randomColor();
-		break;
-	case VboSubclass::RegularColors:
-		// For normal colors, use the polygon's color.
-		if (polygon.color == colors::main)
-		{
-			color = {255, 255, 64}; // mainColorRepresentation();
-		}
-		else if (polygon.color == colors::edge)
-		{
-			// Edge color is black, unless we have a dark background, in which case lines need to be bright.
-			color = Qt::black; //luma(config::backgroundColor()) > 40 ? Qt::black : Qt::white;
-		}
-		else
-		{
-			// Not main or edge color, use the polygon's color as is.
-			color = {255, 255, 64}; //polygon.color.faceColor();
-		}
-		break;
+		color = {255, 255, 64}; // mainColorRepresentation();
 	}
-
-	if (color.isValid())
+	else if (polygon.color == colors::edge)
 	{
-		// We may wish to apply blending on the color to indicate selection or highlight.
-		/*
-		const double blendAlpha = 0.0;
-		if (blendAlpha != 0.0)
-		{
-			QColor selectedColor = config::selectColorBlend();
-			double denominator = blendAlpha + 1.0;
-			color.setRed((color.red() + (selectedColor.red() * blendAlpha)) / denominator);
-			color.setGreen((color.green() + (selectedColor.green() * blendAlpha)) / denominator);
-			color.setBlue((color.blue() + (selectedColor.blue() * blendAlpha)) / denominator);
-		}
-		*/
+		// Edge color is black, unless we have a dark background, in which case lines need to be bright.
+		color = Qt::black; //luma(config::backgroundColor()) > 40 ? Qt::black : Qt::white;
 	}
 	else
 	{
-		if (polygon.numPolygonVertices() == 2)
-			color = Qt::black;
-		else
-			color = {255, 255, 64};
+		// Not main or edge color, use the polygon's color as is.
+		color = this->colorTable[polygon.color].faceColor;
 	}
-
 	return color;
 }
 
-void gl::Compiler::writeColor(std::vector<GLfloat>* data, const gl::Polygon& polygon, VboSubclass subclass)
-{
-	std::vector<GLfloat>& buffer = data[gl::vboIndex({classifyPolygon(polygon), subclass})];
-	const QColor color = this->getColorForPolygon(polygon, subclass);
-	buffer.reserve(data->size() + 4 * polygon.numPolygonVertices());
-	for (unsigned int i = 0; i < polygon.numPolygonVertices(); i += 1)
-	{
-		buffer.push_back(static_cast<GLfloat>(color.redF()));
-		buffer.push_back(static_cast<GLfloat>(color.greenF()));
-		buffer.push_back(static_cast<GLfloat>(color.blueF()));
-		buffer.push_back(static_cast<GLfloat>(color.alphaF()));
-	}
-}
-
 Point3D gl::Compiler::modelCenter() const
 {
 	return boxCenter(this->boundingBox);
@@ -212,11 +182,54 @@
 	return longestMeasure(this->boundingBox);
 }
 
-void gl::Compiler::initializeVbo()
+void gl::Compiler::bindVertexArray(gl::ArrayClass arrayClass)
+{
+	this->vertexArrays[static_cast<int>(arrayClass)].bind();
+	glUseProgram(this->glObjects[static_cast<int>(arrayClass)].program);
+}
+
+void gl::Compiler::releaseVertexArray(gl::ArrayClass arrayClass)
+{
+	this->vertexArrays[static_cast<int>(arrayClass)].release();
+}
+
+void gl::Compiler::buildShaders(int arrayId)
 {
-	this->initializeOpenGLFunctions();
-	glGenBuffers(countof(this->storedVbo), &this->storedVbo[0]);
-	this->initialized = true;
+	/*
+	this->glObjects[arrayId].vertexShader = glCreateShader(GL_VERTEX_SHADER);
+	glShaderSource(this->glObjects[arrayId].vertexShader, 1, &vertexShaderSource, nullptr);
+	glCompileShader(this->glObjects[arrayId].vertexShader);
+	this->glObjects[arrayId].fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
+	glShaderSource(this->glObjects[arrayId].fragmentShader, 1, &fragmentShaderSource, nullptr);
+	glCompileShader(this->glObjects[arrayId].fragmentShader);
+	for (auto&& pair : {
+		std::make_pair(this->glObjects[arrayId].vertexShader, tr("vertex shader")),
+		std::make_pair(this->glObjects[arrayId].fragmentShader, tr("fragment shader")),
+	})
+	{
+		GLint status;
+		glGetShaderiv(this->glObjects[arrayId].fragmentShader, GL_COMPILE_STATUS, &status);
+		if (status != GL_TRUE)
+		{
+			char compileLog[512];
+			glGetShaderInfoLog(pair.first, countof(compileLog), nullptr, compileLog);
+			QMessageBox::critical(nullptr, tr("Shader compile error"), tr("Unable to compile the %1. Compile log:\n\n%2").arg(pair.second).arg(compileLog));
+			abort();
+		}
+	}
+	this->glObjects[arrayId].program = glCreateProgram();
+	glAttachShader(this->glObjects[arrayId].program, this->glObjects[arrayId].vertexShader);
+	glAttachShader(this->glObjects[arrayId].program, this->glObjects[arrayId].fragmentShader);
+	glLinkProgram(this->glObjects[arrayId].program);
+	glUseProgram(this->glObjects[arrayId].program);
+	const std::size_t size = gl::FLOATS_PER_VERTEX * sizeof(GLfloat);
+	const GLuint posAttrib = static_cast<GLuint>(glGetAttribLocation(this->glObjects[arrayId].program, "position"));
+	glEnableVertexAttribArray(posAttrib);
+	glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, size, gl::offset(0));
+	const GLuint colAttrib = static_cast<GLuint>(glGetAttribLocation(this->glObjects[arrayId].program, "color"));
+	glEnableVertexAttribArray(colAttrib);
+	glVertexAttribPointer(colAttrib, 4, GL_FLOAT, GL_FALSE, size, gl::offset(3 * sizeof(GLfloat)));
+	*/
 }
 
 ///
@@ -228,7 +241,6 @@
 {
 	glBindBuffer(GL_ARRAY_BUFFER, this->storedVbo[vboIndex]);
 	glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(data.size() * sizeof data[0]), data.data(), GL_STATIC_DRAW);
-	glBindBuffer(GL_ARRAY_BUFFER, 0);
 	this->storedVboSizes[vboIndex] = data.size();
 }
 
--- a/src/gl/compiler.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/gl/compiler.h	Wed Jan 22 00:23:29 2020 +0200
@@ -22,6 +22,9 @@
 #include "types/boundingbox.h"
 #include <QMap>
 #include <QSet>
+#include <QOpenGLVertexArrayObject>
+#include <QOpenGLBuffer>
+#include <QOpenGLShaderProgram>
 
 class Model;
 class DocumentManager;
@@ -32,7 +35,7 @@
 	class Renderer;
 	struct VboAddress
 	{
-		VboClass vboClass;
+		ArrayClass vboClass;
 		VboSubclass vboSubclass;
 	};
 	int vboIndex(const VboAddress vboAddress);
@@ -42,24 +45,34 @@
 {
 	Q_OBJECT
 public:
-	Compiler(QObject* parent);
+	Compiler(const ColorTable& colorTable, QObject* parent);
 	~Compiler();
 	void build(Model* model, DocumentManager* context);
 	void buildPolygon(Polygon polygon, std::vector<GLfloat>* vboData);
 	void upload(const int vboIndex, const std::vector<GLfloat>& data);
 	GLuint vbo(const VboAddress vboAddress) const;
 	std::size_t vboSize(const VboAddress vboAddress) const;
-	QColor getColorForPolygon(const gl::Polygon& polygon, VboSubclass subclass);
-	void writeColor(std::vector<GLfloat>* data, const gl::Polygon& polygon, VboSubclass subclass);
+	QColor getColorForPolygon(const gl::Polygon& polygon);
 	Point3D modelCenter() const;
 	double modelDistance() const;
+	void initialize();
+	void bindVertexArray(gl::ArrayClass arrayClass);
+	void releaseVertexArray(gl::ArrayClass arrayClass);
+	void buildShaders(int arrayId);
 private:
-	void initializeVbo();
-	GLuint storedVbo[gl::numVbos];
+	QOpenGLVertexArrayObject vertexArrays[gl::NUM_ARRAY_CLASSES];
+	//GLuint storedVbo[gl::numVbos];
 	bool m_vboChanged[gl::numVbos] = {true};
 	std::size_t storedVboSizes[gl::numVbos] = {0_z};
 	bool initialized = false;
 	BoundingBox boundingBox;
+	const ColorTable& colorTable;
+	struct
+	{
+		QOpenGLShaderProgram* program = nullptr;
+		QOpenGLBuffer buffer{QOpenGLBuffer::VertexBuffer};
+		QOpenGLVertexArrayObject vertexArray;
+	} glObjects[gl::NUM_ARRAY_CLASSES];
 };
 
 #define CHECK_GL_ERROR() { checkGLError(__FILE__, __LINE__); }
--- a/src/gl/partrenderer.cpp	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/gl/partrenderer.cpp	Wed Jan 22 00:23:29 2020 +0200
@@ -16,19 +16,54 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <GL/glut.h>
 #include <QMouseEvent>
-#include <GL/glut.h>
+#include <QMessageBox>
 #include "partrenderer.h"
 
-PartRenderer::PartRenderer(Model* model, DocumentManager* documents, QWidget* parent) :
+
+static const char* vertexShaderSource = R"(
+#version 330 core
+
+layout(location=0) in vec3 position;
+layout(location=1) in vec4 color;
+out vec4 vColor;
+uniform mat4 CameraTransformation;
+
+void main()
+{
+	vColor = color;
+	gl_Position = CameraTransformation * vec4(position, 1.0);
+}
+)";
+
+static const char* fragmentShaderSource = R"(
+#version 330 core
+
+in vec4 vColor;
+out vec4 fColor;
+
+void main()
+{
+	fColor = vColor;
+}
+)";
+
+PartRenderer::PartRenderer(Model* model, DocumentManager* documents, const ColorTable& colorTable, QWidget* parent) :
 	QOpenGLWidget{parent},
 	model{model},
 	documents{documents},
-	compiler{new gl::Compiler{this}}
+	colorTable{colorTable},
+	compiler{new gl::Compiler{this->colorTable, this}}
 {
 	this->setMouseTracking(true);
 }
 
+PartRenderer::~PartRenderer()
+{
+	delete this->objects.program;
+}
+
 void PartRenderer::initializeGL()
 {
 	this->initializeOpenGLFunctions();
@@ -36,12 +71,64 @@
 	{
 		abort();
 	}
+	glEnableClientState(GL_NORMAL_ARRAY);
+	glEnableClientState(GL_VERTEX_ARRAY);
+	//this->compiler->initialize();
+	//this->compiler->build(this->model, this->documents);
 	this->initializeLighting();
 	this->initialized = true;
 	this->rotation = QQuaternion::fromAxisAndAngle({1, 0, 0}, 30);
 	this->rotation *= QQuaternion::fromAxisAndAngle({0, 1, 0}, 330);
-	this->compiler->build(this->model, this->documents);
 	glLineWidth(2.0);
+	this->objects.program = new QOpenGLShaderProgram;
+	this->objects.program->create();
+	this->checkForGLErrors();
+	this->objects.program->addShaderFromSourceCode(QOpenGLShader::Vertex, ::vertexShaderSource);
+	this->checkForGLErrors();
+	this->objects.program->addShaderFromSourceCode(QOpenGLShader::Fragment, ::fragmentShaderSource);
+	this->checkForGLErrors();
+	this->objects.program->link();
+	this->checkForGLErrors();
+	this->objects.program->bind();
+	this->checkForGLErrors();
+	this->objects.buffer.create();
+	this->checkForGLErrors();
+	this->objects.buffer.bind();
+	this->checkForGLErrors();
+	this->objects.buffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
+	this->checkForGLErrors();
+	/*
+	GLfloat data[] = {
+		20.0f, 20.0f, 6.0f, 1.0f, 0.0f, 0.0f, 1.0f,
+		30.0f, 20.0f, 6.0f, 0.0f, 1.0f, 0.0f, 1.0f,
+		30.0f, 30.0f, 6.0f, 0.0f, 0.0f, 1.0f, 1.0f,
+	};
+	*/
+	GLfloat data[] = {
+		0.00f, 0.75f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
+		-0.75f, -0.75f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f,
+		0.75f, -0.75f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f
+	};
+	this->objects.buffer.allocate(data, sizeof data);
+	this->checkForGLErrors();
+	this->objects.vertexArray.create();
+	this->checkForGLErrors();
+	this->objects.vertexArray.bind();
+	this->checkForGLErrors();
+	this->objects.program->enableAttributeArray(0);
+	this->checkForGLErrors();
+	this->objects.program->enableAttributeArray(1);
+	this->checkForGLErrors();
+	this->objects.program->setAttributeBuffer(0, GL_FLOAT, 0, 3);
+	this->checkForGLErrors();
+	this->objects.program->setAttributeBuffer(1, GL_FLOAT, 3, 4);
+	this->checkForGLErrors();
+	this->objects.vertexArray.release();
+	this->checkForGLErrors();
+	this->objects.buffer.release();
+	this->checkForGLErrors();
+	this->objects.program->release();
+	this->checkForGLErrors();
 }
 
 /*
@@ -85,29 +172,27 @@
 	glMatrixMode(GL_MODELVIEW);
 }
 
-static GLenum getGlTypeForVboClass(const gl::VboClass vboClass)
+static GLenum getGlTypeForArrayClass(const gl::ArrayClass vboClass)
 {
 	switch (vboClass)
 	{
-	case gl::VboClass::Lines:
-	case gl::VboClass::ConditionalLines:
+	case gl::ArrayClass::Lines:
+	case gl::ArrayClass::ConditionalLines:
 		return GL_LINES;
-	case gl::VboClass::Triangles:
+	case gl::ArrayClass::Triangles:
 		return GL_TRIANGLES;
-	case gl::VboClass::Quads:
+	case gl::ArrayClass::Quads:
 		return GL_QUADS;
 	}
 	throw std::runtime_error{"Bad vbo class passed to getGlTypeForVboClass"};
 }
 
-// https://www.codemiles.com/c-opengl-examples/drawing-teapot-using-opengl-t9010.html?mobile=on
-#include <QMessageBox>
 void PartRenderer::paintGL()
 {
+	/*
 	glEnable (GL_BLEND);
 	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-	glEnable (GL_POLYGON_OFFSET_FILL);
-	glPolygonOffset (1.0f, 1.0f);
+	*/
 	glEnable (GL_DEPTH_TEST);
 	glShadeModel (GL_SMOOTH);
 	glEnable (GL_MULTISAMPLE);
@@ -118,6 +203,20 @@
 
 void PartRenderer::renderScene()
 {
+	glClearColor(0.8f, 0.8f, 0.8f, 1.0f);
+	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+	glMatrixMode(GL_MODELVIEW);
+	glEnable(GL_DEPTH_TEST);
+	glEnable(GL_LIGHTING);
+	glLoadIdentity();
+	//glTranslated(0.0, 0.0, -4.5 * this->compiler->modelDistance());
+	//glTranslated(0.0, 0.0, -4.5);
+	//glMultMatrixf(padMatrix(this->rotation.toRotationMatrix()).constData());
+	//xyz(glTranslatef, -this->compiler->modelCenter());
+	auto rotationMatrix = padMatrix(this->rotation.toRotationMatrix());
+	rotationMatrix(2, 3) = 0;
+	glEnable(GL_POLYGON_OFFSET_FILL);
+	glPolygonOffset(1.0f, 1.0f);
 	switch (this->renderStyle)
 	{
 	case gl::RenderStyle::Normal:
@@ -128,60 +227,58 @@
 		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
 		break;
 	}
-	glMatrixMode(GL_MODELVIEW);
-	glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
-	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-	glEnable(GL_DEPTH_TEST);
-	glEnable(GL_LIGHTING);
-	glLoadIdentity();
-	glTranslatef(0.0, 0.0, -4.5 * this->compiler->modelDistance());
-	glMultMatrixf(padMatrix(this->rotation.toRotationMatrix()).constData());
-	xyz(glTranslatef, -this->compiler->modelCenter());
-	glEnableClientState(GL_NORMAL_ARRAY);
-	glEnableClientState(GL_VERTEX_ARRAY);
-	glEnableClientState(GL_COLOR_ARRAY);
-	for (const gl::VboClass vboClass : {gl::VboClass::Lines, gl::VboClass::Triangles, gl::VboClass::Quads})
-	{
-		const GLuint vboSurfaces = this->compiler->vbo({vboClass, gl::VboSubclass::Surfaces});
-		const GLuint vboColors = this->compiler->vbo({vboClass, gl::VboSubclass::RegularColors});
-		const GLuint vboNormals = this->compiler->vbo({vboClass, gl::VboSubclass::Normals});
-		const std::size_t count = this->compiler->vboSize({vboClass, gl::VboSubclass::Surfaces}) / 3_z;
-		glBindBuffer(GL_ARRAY_BUFFER, vboSurfaces);
-		glVertexPointer(3, GL_FLOAT, 0, nullptr);
-		glBindBuffer(GL_ARRAY_BUFFER, vboColors);
-		glColorPointer(4, GL_FLOAT, 0, nullptr);
-		glBindBuffer(GL_ARRAY_BUFFER, vboNormals);
-		glNormalPointer(GL_FLOAT, 0, nullptr);
-		glDrawArrays(getGlTypeForVboClass(vboClass), 0, static_cast<GLsizei>(count));
-	}
-	glBindBuffer(GL_ARRAY_BUFFER, 0);
-	glDisableClientState(GL_NORMAL_ARRAY);
-	glDisableClientState(GL_VERTEX_ARRAY);
-	glDisableClientState(GL_COLOR_ARRAY);
-	const GLenum glError = this->glGetError();
-	if (glError != GL_NO_ERROR)
+	this->objects.program->bind();
+	this->checkForGLErrors();
+	const int cameraTransformationUniform = glGetUniformLocation(this->objects.program->programId(), "CameraTransformation");
+	this->checkForGLErrors();
+	this->objects.program->setUniformValue(cameraTransformationUniform, rotationMatrix);
+	this->checkForGLErrors();
+	this->objects.vertexArray.bind();
+	this->checkForGLErrors();
+	glDrawArrays(GL_TRIANGLES, 0, 3);
+	this->checkForGLErrors();
+	this->objects.vertexArray.release();
+	this->checkForGLErrors();
+	this->objects.program->release();
+	this->checkForGLErrors();
+#if 0
+	// Lines need to be rendered last so that anti-aliasing does not interfere with polygon rendering.
+	renderVao(gl::ArrayClass::Triangles);
+	renderVao(gl::ArrayClass::Quads);
+	renderVao(gl::ArrayClass::Lines);
+#endif
+	glDisable(GL_POLYGON_OFFSET_FILL);
+}
+
+void PartRenderer::renderVao(const gl::ArrayClass /*arrayClass*/)
+{
+	/*
+	this->compiler->bindVertexArray(arrayClass);
+	const std::size_t vertexCount = this->compiler->vboSize({arrayClass, gl::VboSubclass::VertexData}) / gl::FLOATS_PER_VERTEX;
+	glDrawArrays(getGlTypeForArrayClass(arrayClass), 0, static_cast<GLsizei>(vertexCount));
+	this->compiler->releaseVertexArray(arrayClass);
+	this->checkForGLErrors();
+	*/
+}
+
+void PartRenderer::checkForGLErrors()
+{
+	GLenum glError;
+	QStringList errors;
+	while ((glError = glGetError()) != GL_NO_ERROR)
 	{
 		const QString glErrorString = QString::fromLatin1(reinterpret_cast<const char*>(::gluErrorString(glError)));
+		errors.append(glErrorString);
+	}
+	if (not errors.isEmpty())
+	{
 		QMessageBox::critical(
 			this,
 			tr("Rendering error"),
-			QString{"Failed to render: %1"}.arg(glErrorString));
+			QString{"Failed to render.\n%1"}.arg(errors.join("\n")));
 	}
-	glDisable(GL_CULL_FACE);
 }
 
-static QPointF pointToPointF(const QPoint& point)
-{
-	return {static_cast<qreal>(point.x()), static_cast<qreal>(point.y())};
-}
-
-/*
-static QPoint pointFToPoint(const QPointF& point)
-{
-	return {static_cast<int>(std::round(point.x())), static_cast<int>(std::round(point.y()))};
-}
-*/
-
 void PartRenderer::mouseMoveEvent(QMouseEvent* event)
 {
 	const bool left = event->buttons() & Qt::LeftButton;
--- a/src/gl/partrenderer.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/gl/partrenderer.h	Wed Jan 22 00:23:29 2020 +0200
@@ -2,6 +2,10 @@
 #include <QOpenGLWidget>
 #include <QOpenGLFunctions>
 #include <QQuaternion>
+#include <QOpenGLVertexArrayObject>
+#include <QOpenGLBuffer>
+#include <QOpenGLShader>
+#include <QOpenGLShaderProgram>
 #include "main.h"
 #include "gl/common.h"
 #include "gl/compiler.h"
@@ -10,7 +14,8 @@
 {
 	Q_OBJECT
 public:
-	PartRenderer(Model* model, DocumentManager* documents, QWidget* parent = nullptr);
+	PartRenderer(Model* model, DocumentManager* documents, const ColorTable& colorTable, QWidget* parent = nullptr);
+	~PartRenderer() override;
 protected:
 	void initializeGL() override;
 	void resizeGL(int width, int height) override;
@@ -23,10 +28,19 @@
 	void renderScene();
 	Model* const model;
 	DocumentManager* const documents;
+	const ColorTable& colorTable;
 	QPointF lastMousePosition;
 	QQuaternion rotation;
 	gl::Compiler* compiler;
 	gl::RenderStyle renderStyle = gl::RenderStyle::Normal;
+	struct
+	{
+		QOpenGLShaderProgram* program = nullptr;
+		QOpenGLBuffer buffer{QOpenGLBuffer::VertexBuffer};
+		QOpenGLVertexArrayObject vertexArray;
+	} objects;
 	bool initialized = false;
 	void initializeLighting();
+	void renderVao(const gl::ArrayClass arrayClass);
+	void checkForGLErrors();
 };
--- a/src/libraries.cpp	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/libraries.cpp	Wed Jan 22 00:23:29 2020 +0200
@@ -216,6 +216,26 @@
 }
 
 /**
+ * @brief Iterates over libraries and loads LDConfig.ldr from each of them.
+ * @param errors Where to stream any encountered errors
+ * @return color table
+ */
+ColorTable LibraryManager::loadColorTable(QTextStream& errors)
+{
+	ColorTable result;
+	for (const Library& library : this->libraries)
+	{
+		const QString path = library.path.filePath("LDConfig.ldr");
+		QFile file{path};
+		if (file.open(QIODevice::ReadOnly | QIODevice::Text))
+		{
+			(void) result.load(file, errors);
+		}
+	}
+	return result;
+}
+
+/**
  * @brief Gets a human-readable string for the specified role
  * @param role Role to get a string for
  * @returns string
--- a/src/libraries.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/libraries.h	Wed Jan 22 00:23:29 2020 +0200
@@ -20,6 +20,7 @@
 #include <QDir>
 #include <QAbstractTableModel>
 #include "main.h"
+#include "colors.h"
 
 class QSettings;
 
@@ -73,6 +74,7 @@
 	int rowCount(const QModelIndex&) const override;
 	int columnCount(const QModelIndex&) const override;
 	bool isValidIndex(const int libraryIndex) const;
+	ColorTable loadColorTable(QTextStream& errors);
 private:
 	enum Column
 	{
--- a/src/linetypes/object.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/linetypes/object.h	Wed Jan 22 00:23:29 2020 +0200
@@ -64,6 +64,7 @@
 	virtual QBrush textRepresentationBackground() const;
 	virtual QFont textRepresentationFont() const;
 	virtual void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const;
+	virtual void invert() {}
 };
 
 class linetypes::ColoredObject : public Object
--- a/src/linetypes/quadrilateral.cpp	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/linetypes/quadrilateral.cpp	Wed Jan 22 00:23:29 2020 +0200
@@ -78,5 +78,12 @@
 		this->points[2],
 		this->points[3],
 		this->colorIndex,
-		this->id));
+			this->id));
 }
+
+void linetypes::Quadrilateral::invert()
+{
+	//    0 1 2 3
+	// -> 2 1 0 3
+	std::swap(this->points[0], this->points[2]);
+}
--- a/src/linetypes/quadrilateral.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/linetypes/quadrilateral.h	Wed Jan 22 00:23:29 2020 +0200
@@ -21,6 +21,7 @@
 	SetPropertyResult setProperty(Property id, const QVariant& value) override;
 	QString textRepresentation() const override;
 	void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override;
+	void invert() override;
 private:
 	Point3D points[4] = {{}};
 };
--- a/src/linetypes/subfilereference.cpp	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/linetypes/subfilereference.cpp	Wed Jan 22 00:23:29 2020 +0200
@@ -1,5 +1,6 @@
 #include "subfilereference.h"
 #include "documentmanager.h"
+#include "invert.h"
 
 linetypes::SubfileReference::SubfileReference(const Matrix4x4& transformation,
 	const QString& referenceName,
@@ -53,6 +54,7 @@
 	Model* model = this->resolve(context->documents);
 	if (model != nullptr)
 	{
+		const bool needInverting = math::det(this->transformation) < 0;
 		const std::vector<gl::Polygon> modelPolygons = model->getPolygons(context->documents);
 		polygons.reserve(polygons.size() + modelPolygons.size());
 		for (gl::Polygon polygon : modelPolygons)
@@ -61,6 +63,10 @@
 			{
 				polygon.vertices[i] = math::transform(polygon.vertices[i], this->transformation);
 			}
+			if (needInverting != this->isInverted)
+			{
+				gl::invert(polygon);
+			}
 			if (polygon.color == colors::main)
 			{
 				polygon.color = this->colorIndex;
@@ -76,6 +82,11 @@
 	return {this->transformation(0, 3), this->transformation(1, 3), this->transformation(2, 3)};
 }
 
+void linetypes::SubfileReference::invert()
+{
+	this->isInverted = not this->isInverted;
+}
+
 Model* linetypes::SubfileReference::resolve(DocumentManager* documents) const
 {
 	return documents->findModelByName(this->referenceName);
--- a/src/linetypes/subfilereference.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/linetypes/subfilereference.h	Wed Jan 22 00:23:29 2020 +0200
@@ -22,8 +22,10 @@
 	QString textRepresentation() const override;
 	void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override;
 	Point3D position() const;
+	void invert() override;
 private:
 	Model* resolve(DocumentManager* documents) const;
 	Matrix4x4 transformation;
 	QString referenceName;
+	bool isInverted = false;
 };
--- a/src/linetypes/triangle.cpp	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/linetypes/triangle.cpp	Wed Jan 22 00:23:29 2020 +0200
@@ -68,5 +68,12 @@
 		this->points[1],
 		this->points[2],
 		this->colorIndex,
-		this->id));
+			this->id));
 }
+
+void linetypes::Triangle::invert()
+{
+	//    0 1 2
+	// -> 1 0 2
+	std::swap(this->points[0], this->points[1]);
+}
--- a/src/linetypes/triangle.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/linetypes/triangle.h	Wed Jan 22 00:23:29 2020 +0200
@@ -19,6 +19,7 @@
 	SetPropertyResult setProperty(Property id, const QVariant& value) override;
 	QString textRepresentation() const override;
 	void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override;
+	void invert() override;
 private:
 	Point3D points[3] = {{}};
 };
--- a/src/main.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/main.h	Wed Jan 22 00:23:29 2020 +0200
@@ -37,6 +37,10 @@
 	struct Id
 	{
 		unsigned int value;
+		constexpr bool operator<(linetypes::Id other) const
+		{
+			return this->value < other.value;
+		}
 	};
 }
 
@@ -44,3 +48,30 @@
 {
 	return static_cast<std::size_t>(x);
 }
+
+inline QString operator""_q(const char* string, const unsigned long int length)
+{
+	Q_UNUSED(length)
+	return QString{string};
+}
+
+inline QPointF pointToPointF(const QPoint& point)
+{
+	return {static_cast<qreal>(point.x()), static_cast<qreal>(point.y())};
+}
+
+inline QPoint pointFToPoint(const QPointF& point)
+{
+	return {static_cast<int>(std::round(point.x())), static_cast<int>(std::round(point.y()))};
+}
+
+/**
+ * \brief Hints to the specified vector that a certain amount of new elements are going to be added.
+ * \param vector vector to consider
+ * \param amount amount of new elements to expect
+ */
+template<typename T>
+void reserveMore(std::vector<T>& vector, std::size_t amount)
+{
+	vector.reserve(vector.size() + amount);
+}
--- a/src/mainwindow.cpp	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/mainwindow.cpp	Wed Jan 22 00:23:29 2020 +0200
@@ -135,7 +135,7 @@
 
 void MainWindow::openModelForEditing(const QString& modelName)
 {
-	Document* document = new Document{this->documents.findModelByName(modelName), &this->documents};
+	Document* document = new Document{this->documents.findModelByName(modelName), &this->documents, this->colorTable};
 	this->ui->tabs->addTab(document, modelName);
 	this->ui->tabs->setCurrentWidget(document);
 	document->restoreSplitterState(this->documentSplitterState);
@@ -254,6 +254,7 @@
 	changeLanguage(defaultLocale.toString());
 	this->libraries.restoreFromSettings(&this->settings);
 	this->updateRecentlyOpenedDocumentsMenu();
+	this->loadColors();
 }
 
 QString MainWindow::pathToTranslation(const QString& localeCode)
@@ -261,3 +262,9 @@
 	QDir dir {":/locale"};
 	return dir.filePath(localeCode + ".qm");
 }
+
+void MainWindow::loadColors()
+{
+	QTextStream errors;
+	this->colorTable = this->libraries.loadColorTable(errors);
+}
--- a/src/mainwindow.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/mainwindow.h	Wed Jan 22 00:23:29 2020 +0200
@@ -54,6 +54,7 @@
 	uiutilities::KeySequenceMap defaultKeyboardShortcuts;
 	static constexpr int maxRecentlyOpenedFiles = 10;
 	QStringList recentlyOpenedFiles;
+	ColorTable colorTable;
 	void updateTitle();
 	void saveSettings();
 	void restoreSettings();
@@ -61,4 +62,5 @@
 	void addRecentlyOpenedFile(const QString& path);
 	void openModelForEditing(const QString& modelName);
 	static QString pathToTranslation(const QString& localeCode);
+	void loadColors();
 };
--- a/src/maths.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/maths.h	Wed Jan 22 00:23:29 2020 +0200
@@ -71,4 +71,22 @@
 	}
 	constexpr double infinity = std::numeric_limits<double>::infinity();
 	constexpr long double pi = M_PIl;
+
+	/*
+	 * Returns the empty sum. (recursion base)
+	 */
+	template<typename T>
+	constexpr T sum()
+	{
+		return {};
+	}
+
+	/*
+	 * Returns the sum of n arguments.
+	 */
+	template<typename T, typename... Rest>
+	constexpr auto sum(const T& arg, Rest&&... rest)
+	{
+		return arg + sum<T>(rest...);
+	}
 }
--- a/src/matrix.cpp	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/matrix.cpp	Wed Jan 22 00:23:29 2020 +0200
@@ -28,3 +28,54 @@
 		{0, 0, 0, 1}
 	}};
 }
+
+/*
+ * Computes the determinant of a 3×3 matrix with each variable passed in row-major order.
+ */
+qreal math::det(qreal a, qreal b, qreal c, qreal d, qreal e, qreal f, qreal g, qreal h, qreal i)
+{
+	return a*e*i + b*f*g + c*d*h - a*f*h - b*d*i - c*e*g;
+}
+
+/*
+ * Computes the determinant of a 2×2 matrix.
+ */
+qreal math::det(const Matrix<2, 2>& matrix)
+{
+	return matrix(0, 0) * matrix(1, 1) - matrix(0, 1) * matrix(1, 0);
+}
+
+/*
+ * Computes the determinant of a 3×3 matrix.
+ */
+qreal math::det(const Matrix3x3& matrix)
+{
+	return math::sum(
+		+matrix(0, 0) * matrix(1, 1) * matrix(2, 2),
+		-matrix(0, 0) * matrix(1, 2) * matrix(2, 1),
+		-matrix(0, 1) * matrix(1, 0) * matrix(2, 2),
+		+matrix(0, 1) * matrix(1, 2) * matrix(2, 0),
+		+matrix(0, 2) * matrix(1, 0) * matrix(2, 1),
+		-matrix(0, 2) * matrix(1, 1) * matrix(2, 0));
+}
+
+/*
+ * Computes the determinant of a 4×4 matrix.
+ */
+qreal math::det(const Matrix4x4& matrix)
+{
+	qreal sum = 0;
+
+	for (int column : {0, 1, 2, 3})
+	{
+		int column_1 = (column >= 1) ? 0 : 1;
+		int column_2 = (column >= 2) ? 1 : 2;
+		int column_3 = (column >= 3) ? 2 : 3;
+		sum += ((column % 1) ? -1 : 1) * math::det(
+			matrix(1, column_1), matrix(1, column_2), matrix(1, column_3),
+			matrix(2, column_1), matrix(2, column_2), matrix(2, column_3),
+			matrix(3, column_1), matrix(3, column_2), matrix(3, column_3));
+	}
+
+	return sum;
+}
--- a/src/matrix.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/matrix.h	Wed Jan 22 00:23:29 2020 +0200
@@ -168,3 +168,11 @@
 
 static constexpr Matrix3x3 identity3x3 {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}};
 static constexpr Matrix4x4 identity4x4 {{{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}}};
+
+namespace math
+{
+	qreal det(qreal a, qreal b, qreal c, qreal d, qreal e, qreal f, qreal g, qreal h, qreal i);
+	qreal det(const Matrix<2, 2>& matrix);
+	qreal det(const Matrix3x3& matrix);
+	qreal det(const Matrix4x4& matrix);
+}
--- a/src/model.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/model.h	Wed Jan 22 00:23:29 2020 +0200
@@ -50,10 +50,10 @@
 private:
 	using ModelObjectPointer = std::unique_ptr<linetypes::Object>;
 	template<typename T, typename... Args>
-	T* append(Args&&... args);
+	linetypes::Id append(Args&&... args);
 	void append(ModelObjectPointer&& object);
 	template<typename T, typename... Args>
-	T* insert(int position, Args&&... args);
+	linetypes::Id insert(int position, Args&&... args);
 	bool modified = false;
 	QString path;
 	LDHeader header;
@@ -64,25 +64,25 @@
 };
 
 template<typename T, typename... Args>
-T* Model::append(Args&&... args)
+linetypes::Id Model::append(Args&&... args)
 {
 	emit layoutAboutToBeChanged();
 	this->body.push_back(std::make_unique<T>(args...));
-	T* pointer = static_cast<T*>(this->body.back().get());
+	linetypes::Object* pointer = this->body.back().get();
 	this->objectsById[pointer->id] = pointer;
 	emit objectAdded(pointer->id, this->body.size() - 1);
 	emit layoutChanged();
-	return pointer;
+	return pointer->id;
 }
 
 template<typename T, typename... Args>
-T* Model::insert(int position, Args&&... args)
+linetypes::Id Model::insert(int position, Args&&... args)
 {
 	emit layoutAboutToBeChanged();
 	this->body.insert(position, std::make_unique<T>(args...));
-	T* pointer = static_cast<T*>(this->body[position]);
+	linetypes::Object* pointer = this->body[position].get();
 	this->objectsById[pointer->id] = pointer;
 	emit objectAdded(pointer->id, position);
 	emit layoutChanged();
-	return pointer;
+	return pointer->id;
 }
--- a/src/modeleditcontext.cpp	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/modeleditcontext.cpp	Wed Jan 22 00:23:29 2020 +0200
@@ -23,9 +23,12 @@
 {
 }
 
-void Model::EditContext::append(std::unique_ptr<linetypes::Object>&& object)
+linetypes::Id Model::EditContext::append(std::unique_ptr<linetypes::Object>&& object)
 {
+	const linetypes::Id id = object->id;
+	this->model.objectsById[id] = object.get();
 	this->model.append(std::move(object));
+	return id;
 }
 
 void Model::EditContext::setObjectProperty(
@@ -35,3 +38,13 @@
 {
 	object->setProperty(property, value);
 }
+
+void Model::EditContext::invertObject(linetypes::Id id)
+{
+	auto it = this->model.objectsById.find(id);
+	if (it != this->model.objectsById.end())
+	{
+		linetypes::Object* object = it->second;
+		object->invert();
+	}
+}
--- a/src/modeleditcontext.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/modeleditcontext.h	Wed Jan 22 00:23:29 2020 +0200
@@ -24,14 +24,15 @@
 {
 public:
 	template<typename T, typename... Args>
-	T* append(Args&&... args);
-	void append(std::unique_ptr<linetypes::Object>&& object);
+	linetypes::Id append(Args&&... args);
+	linetypes::Id append(std::unique_ptr<linetypes::Object>&& object);
 	template<typename T, typename... Args>
-	T* insert(int position, Args&&... args);
+	linetypes::Id insert(int position, Args&&... args);
 	void setObjectProperty(
 		linetypes::Object* object,
 		linetypes::Property property,
 		const QVariant &value);
+	void invertObject(linetypes::Id id);
 private:
 	EditContext(Model& model);
 	friend class Model;
@@ -39,13 +40,13 @@
 };
 
 template<typename T, typename... Args>
-T* Model::EditContext::append(Args&&... args)
+linetypes::Id Model::EditContext::append(Args&&... args)
 {
 	return this->model.append<T>(args...);
 }
 
 template<typename T, typename... Args>
-T* Model::EditContext::insert(int position, Args&&... args)
+linetypes::Id Model::EditContext::insert(int position, Args&&... args)
 {
 	return this->model.insert<T>(position, args...);
 }
--- a/src/parser.cpp	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/parser.cpp	Wed Jan 22 00:23:29 2020 +0200
@@ -256,11 +256,11 @@
 			continue;
 		}
 		std::unique_ptr<linetypes::Object> object = parseFromString(line);
+		auto id = editor.append(std::move(object));
 		if (invertNext)
 		{
-			editor.setObjectProperty(object.get(), linetypes::Property::IsInverted, true);
+			editor.invertObject(id);
 		}
-		editor.append(std::move(object));
 		invertNext = false;
 	}
 }
--- a/src/ring.h	Sun Jan 19 14:25:57 2020 +0200
+++ b/src/ring.h	Wed Jan 22 00:23:29 2020 +0200
@@ -22,15 +22,15 @@
 {
 	namespace _imp
 	{
-		template<typename T>
+		template<typename T, typename Tint>
 		class RingAdapter;
 	}
 
 	template<typename T>
-	_imp::RingAdapter<T> ring(T& collection);
+	_imp::RingAdapter<T, int> ring(T&& collection);
 
-	template<typename T>
-	_imp::RingAdapter<T> ring(T& collection, int count);
+	template<typename T, typename Tint>
+	_imp::RingAdapter<T, Tint> ring(T&& collection, Tint count);
 }
 
 /*
@@ -44,21 +44,20 @@
  *   ring(A)[5]  ==     A[5 % 4]    == A[1]
  *   ring(A)[-1] == ring(A)[-1 + 4] == A[3]
  */
-template<typename T>
+template<typename T, typename Tint>
 class iter::_imp::RingAdapter
 {
 private:
 	// The private section must come first because _collection is used in decltype() below.
-	T& collection;
-	const int count;
+	T&& collection;
+	const Tint count;
 
 public:
-	RingAdapter(T& collection, int count) :
+	RingAdapter(T&& collection, Tint count) :
 		collection {collection},
 		count {count} {}
 
-	template<typename IndexType>
-	decltype(collection[IndexType()]) operator[](IndexType index)
+	decltype(collection[Tint{}]) operator[](Tint index)
 	{
 		if (count == 0)
 		{
@@ -77,7 +76,7 @@
 		}
 	}
 
-	int size() const
+	Tint size() const
 	{
 		return this->count;
 	}
@@ -88,7 +87,7 @@
  * to be the amount of elements in the collection.
  */
 template<typename T>
-iter::_imp::RingAdapter<T> iter::ring(T& collection)
+iter::_imp::RingAdapter<T, int> iter::ring(T&& collection)
 {
 	return {collection, countof(collection)};
 }
@@ -96,14 +95,14 @@
 /*
  * Version of ring() that allows manual specification of the count.
  */
-template<typename T>
-iter::_imp::RingAdapter<T> iter::ring(T& collection, int count)
+template<typename T, typename Tint>
+iter::_imp::RingAdapter<T, Tint> iter::ring(T&& collection, Tint count)
 {
 	return {collection, count};
 }
 
-template<typename T>
-int countof(const iter::_imp::RingAdapter<T>& ring)
+template<typename T, typename Tint>
+int countof(const iter::_imp::RingAdapter<T, Tint>& ring)
 {
 	return ring.size();
 }

mercurial