Added lots of code

Sun, 22 Sep 2019 11:51:41 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Sun, 22 Sep 2019 11:51:41 +0300
changeset 3
55a55a9ec2c2
parent 2
2bdc3ac5e77c
child 4
68988ebc2a68

Added lots of code

.hgignore file | annotate | diff | comparison | revisions
CMakeLists.txt file | annotate | diff | comparison | revisions
src/basics.h file | annotate | diff | comparison | revisions
src/colors.h file | annotate | diff | comparison | revisions
src/documentmanager.cpp file | annotate | diff | comparison | revisions
src/documentmanager.h file | annotate | diff | comparison | revisions
src/header.h file | annotate | diff | comparison | revisions
src/main.cpp 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/mainwindow.ui file | annotate | diff | comparison | revisions
src/model.cpp 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/modelobject.cpp file | annotate | diff | comparison | revisions
src/modelobject.h file | annotate | diff | comparison | revisions
src/objecttypes/comment.cpp file | annotate | diff | comparison | revisions
src/objecttypes/comment.h file | annotate | diff | comparison | revisions
src/objecttypes/conditionaledge.cpp file | annotate | diff | comparison | revisions
src/objecttypes/conditionaledge.h file | annotate | diff | comparison | revisions
src/objecttypes/edge.cpp file | annotate | diff | comparison | revisions
src/objecttypes/edge.h file | annotate | diff | comparison | revisions
src/objecttypes/errorline.cpp file | annotate | diff | comparison | revisions
src/objecttypes/errorline.h file | annotate | diff | comparison | revisions
src/objecttypes/modelobject.cpp file | annotate | diff | comparison | revisions
src/objecttypes/modelobject.h file | annotate | diff | comparison | revisions
src/objecttypes/polygon.cpp file | annotate | diff | comparison | revisions
src/objecttypes/polygon.h file | annotate | diff | comparison | revisions
src/objecttypes/subfilereference.cpp file | annotate | diff | comparison | revisions
src/objecttypes/subfilereference.h file | annotate | diff | comparison | revisions
src/parser.cpp file | annotate | diff | comparison | revisions
src/parser.h file | annotate | diff | comparison | revisions
src/version.h file | annotate | diff | comparison | revisions
src/vertex.cpp file | annotate | diff | comparison | revisions
src/vertex.h file | annotate | diff | comparison | revisions
--- a/.hgignore	Sat Aug 24 14:44:42 2019 +0300
+++ b/.hgignore	Sun Sep 22 11:51:41 2019 +0300
@@ -1,3 +1,4 @@
 syntax:glob
 CMakeLists.txt.user
 __pycache__
+*.orig
--- a/CMakeLists.txt	Sat Aug 24 14:44:42 2019 +0300
+++ b/CMakeLists.txt	Sun Sep 22 11:51:41 2019 +0300
@@ -18,19 +18,42 @@
 # set_source_files_properties (${CMAKE_BINARY_DIR}/configuration.cpp PROPERTIES GENERATED TRUE)
 # set_property(SOURCE configuration.cpp PROPERTY SKIP_AUTOMOC ON)
 set (LDFORGE_SOURCES
+	src/documentmanager.cpp
 	src/main.cpp
 	src/mainwindow.cpp
-        src/modelobject.cpp
-        src/uuid.cpp
+	src/model.cpp
+	src/modeleditcontext.cpp
+	src/parser.cpp
+	src/uuid.cpp
 	src/version.cpp
+	src/vertex.cpp
+	src/objecttypes/comment.cpp
+	src/objecttypes/conditionaledge.cpp
+	src/objecttypes/edge.cpp
+	src/objecttypes/errorline.cpp
+	src/objecttypes/modelobject.cpp
+	src/objecttypes/polygon.cpp
+	src/objecttypes/subfilereference.cpp
 )
 set (LDFORGE_HEADERS
+	src/basics.h
+	src/colors.h
+	src/documentmanager.h
 	src/main.h
 	src/mainwindow.h
-        src/mainwindow.h
-        src/modelobject.h
-        src/uuid.h
+	src/model.h
+	src/modeleditcontext.h
+	src/parser.h
+	src/uuid.h
 	src/version.h
+	src/vertex.h
+	src/objecttypes/comment.h
+	src/objecttypes/conditionaledge.h
+	src/objecttypes/edge.h
+	src/objecttypes/errorline.h
+	src/objecttypes/modelobject.h
+	src/objecttypes/polygon.h
+	src/objecttypes/subfilereference.h
 )
 set (LDFORGE_FORMS
 	src/mainwindow.ui
@@ -50,8 +73,9 @@
 	if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
 		set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEBUG")
 	endif()
-	set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=all -Wextra")
-	set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-implicit-fallthrough -Wno-noexcept-type")
+	set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
+	set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-implicit-fallthrough")
+	set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-noexcept-type")
 endif()
 # qt5_add_resources (LDFORGE_QRC ${LDFORGE_RESOURCES})
 qt5_wrap_ui (LDFORGE_FORMS_HEADERS ${LDFORGE_FORMS})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/basics.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,88 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2019 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/>.
+ */
+
+#pragma once
+#include <cstdio>
+#include <cstdlib>
+#include <cmath>
+#include <QMatrix4x4>
+#include <QObject>
+#include <QPointF>
+#include <QSet>
+#include <QString>
+#include <QStringList>
+#include <QVariant>
+#include <QVector>
+#include <QVector3D>
+
+using GLRotationMatrix = QMatrix4x4;
+
+enum Axis
+{
+	X,
+	Y,
+	Z
+};
+
+enum Winding
+{
+	NoWinding,
+	CounterClockwise,
+	Clockwise,
+};
+
+/*
+ * Special operator definition that implements the XOR operator for windings.
+ * However, if either winding is NoWinding, then this function returns NoWinding.
+ */
+inline Winding operator^(Winding one, Winding other)
+{
+	if (one == NoWinding or other == NoWinding)
+		return NoWinding;
+	else
+		return static_cast<Winding>(static_cast<int>(one) ^ static_cast<int>(other));
+}
+
+inline Winding& operator^=(Winding& one, Winding other)
+{
+	one = one ^ other;
+	return one;
+}
+
+template<typename T, std::size_t N>
+constexpr std::size_t countof(T(&)[N])
+{
+	return N;
+}
+
+static constexpr long double pi = M_PIl;
+
+// http://stackoverflow.com/a/18204188/3629665
+template<typename T>
+inline auto rotl10(T x)
+	-> std::enable_if_t<std::is_arithmetic_v<T>, T>
+{
+	return (x << 10) | ((x >> 22) & 0x000000ff);
+}
+
+template<typename T>
+inline auto rotl20(T x)
+	-> std::enable_if_t<std::is_arithmetic_v<T>, T>
+{
+	return (x << 20) | ((x >> 12) & 0x000000ff);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colors.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,19 @@
+#pragma once
+#include "main.h"
+
+struct Color
+{
+	qint32 index;
+};
+
+namespace colors
+{
+	static constexpr Color black {0};
+	static constexpr Color blue {1};
+	static constexpr Color green {2};
+	static constexpr Color red {4};
+	static constexpr Color yellow {14};
+	static constexpr Color white {15};
+	static constexpr Color main {16};
+	static constexpr Color edge {24};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/documentmanager.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,7 @@
+#include "documentmanager.h"
+
+Model* DocumentManager::newModel()
+{
+	openModels.emplace_back();
+	return openModels.back().get();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/documentmanager.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,16 @@
+#pragma once
+#include "main.h"
+#include "model.h"
+
+class DocumentManager
+{
+public:
+	DocumentManager() = default;
+	DocumentManager(const DocumentManager&) = delete;
+	DocumentManager(DocumentManager&&) = default;
+	DocumentManager& operator=(const DocumentManager&) = delete;
+	DocumentManager& operator=(DocumentManager&&) = default;
+	Model* newModel();
+private:
+	std::vector<std::unique_ptr<Model>> openModels;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/header.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,48 @@
+#pragma once
+#include <QDate>
+#include "main.h"
+
+struct LDHeader
+{
+	struct HistoryEntry
+	{
+		QDate date;
+		QString author;
+		QString description;
+	};
+	enum FileType
+	{
+		NoHeader,
+		Part,
+		Subpart,
+		Shortcut,
+		Primitive,
+		Primitive_8,
+		Primitive_48,
+		Configuration,
+	} type = NoHeader;
+	enum Qualifier
+	{
+		Alias = 1 << 0,
+		Physical_Color = 1 << 1,
+		Flexible_Section = 1 << 2,
+	};
+	QFlags<Qualifier> qualfiers;
+	QString description;
+	QString name;
+	QString author;
+	QString category;
+	QString cmdline;
+	QString help;
+	QString keywords;
+	QVector<HistoryEntry> history;
+	enum
+	{
+		UnspecifiedLicense,
+		CaLicense,
+		NonCaLicense
+	} license = UnspecifiedLicense;
+	static decltype(license) defaultLicense();
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QFlags<LDHeader::Qualifier>)
--- a/src/main.cpp	Sat Aug 24 14:44:42 2019 +0300
+++ b/src/main.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -1,12 +1,11 @@
 #include <QApplication>
 #include "main.h"
 #include "mainwindow.h"
-#include <QTextStream>
 
 int main(int argc, char *argv[])
 {
-    QApplication app{argc, argv};
-    MainWindow mainwindow;
-    mainwindow.show();
-    return app.exec();
+	QApplication app{argc, argv};
+	MainWindow mainwindow;
+	mainwindow.show();
+	return app.exec();
 }
--- a/src/main.h	Sat Aug 24 14:44:42 2019 +0300
+++ b/src/main.h	Sun Sep 22 11:51:41 2019 +0300
@@ -2,5 +2,5 @@
 #include <QString>
 #include <QVector>
 #include <QSet>
+#include "basics.h"
 
-static constexpr long double pi = 3.14159265358979323846264338327950288419716939937510L;
--- a/src/mainwindow.cpp	Sat Aug 24 14:44:42 2019 +0300
+++ b/src/mainwindow.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -1,13 +1,14 @@
 #include "mainwindow.h"
 #include "ui_mainwindow.h"
 #include "version.h"
+#include <QQuaternion>
 
 MainWindow::MainWindow(QWidget *parent) :
-    QMainWindow{parent},
-    ui{*new Ui_MainWindow}
+	QMainWindow{parent},
+	ui{std::make_unique<Ui_MainWindow>()}
 {
-	ui.setupUi(this);
-	connect(ui.actionQuit, &QAction::triggered, this, &QMainWindow::close);
+	ui->setupUi(this);
+	connect(ui->actionQuit, &QAction::triggered, this, &QMainWindow::close);
 	QString title = ::appName;
 	title += " ";
 	title += fullVersionString();
@@ -16,5 +17,4 @@
 
 MainWindow::~MainWindow()
 {
-    delete &this->ui;
 }
--- a/src/mainwindow.h	Sat Aug 24 14:44:42 2019 +0300
+++ b/src/mainwindow.h	Sun Sep 22 11:51:41 2019 +0300
@@ -1,12 +1,14 @@
 #pragma once
 #include <QMainWindow>
+#include <memory>
+#include <vector>
 
 class MainWindow : public QMainWindow
 {
-    Q_OBJECT
+	Q_OBJECT
 public:
-    MainWindow(QWidget *parent = nullptr);
-    ~MainWindow();
+	MainWindow(QWidget *parent = nullptr);
+	~MainWindow();
 private:
-    class Ui_MainWindow &ui;
+	std::unique_ptr<class Ui_MainWindow> ui;
 };
--- a/src/mainwindow.ui	Sat Aug 24 14:44:42 2019 +0300
+++ b/src/mainwindow.ui	Sun Sep 22 11:51:41 2019 +0300
@@ -13,7 +13,13 @@
   <property name="windowTitle">
    <string>MainWindow</string>
   </property>
-  <widget class="QWidget" name="centralwidget"/>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QVBoxLayout" name="verticalLayout">
+    <item>
+     <widget class="QTabWidget" name="tabs"/>
+    </item>
+   </layout>
+  </widget>
   <widget class="QMenuBar" name="menubar">
    <property name="geometry">
     <rect>
@@ -27,6 +33,9 @@
     <property name="title">
      <string>File</string>
     </property>
+    <addaction name="actionNewFile"/>
+    <addaction name="actionOpen"/>
+    <addaction name="separator"/>
     <addaction name="actionQuit"/>
    </widget>
    <addaction name="menuFile"/>
@@ -37,6 +46,22 @@
     <string>Quit</string>
    </property>
   </action>
+  <action name="actionOpen">
+   <property name="text">
+    <string>Open…</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+O</string>
+   </property>
+  </action>
+  <action name="actionNewFile">
+   <property name="text">
+    <string>New File</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+N</string>
+   </property>
+  </action>
  </widget>
  <resources/>
  <connections/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/model.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,17 @@
+#include "model.h"
+#include "modeleditcontext.h"
+
+Model::Model()
+{
+
+}
+
+int Model::size() const
+{
+	return this->body.size();
+}
+
+Model::EditContext Model::edit()
+{
+	return {*this};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/model.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,40 @@
+#pragma once
+#include <memory>
+#include "main.h"
+#include "header.h"
+#include "objecttypes/modelobject.h"
+
+class Model : public QObject
+{
+	Q_OBJECT
+public:
+	class EditContext;
+	Model();
+	Model(const Model &) = delete;
+	int size() const;
+	EditContext edit();
+private:
+	template<typename T, typename... Args>
+	T* append(Args&&... args);
+	template<typename T, typename... Args>
+	T* insert(int position, Args&&... args);
+	bool modified = false;
+	QString path;
+	using ModelObjectPointer = std::unique_ptr<modelobjects::BaseObject>;
+	LDHeader header;
+	std::vector<ModelObjectPointer> body;
+};
+
+template<typename T, typename... Args>
+T* Model::append(Args&&... args)
+{
+	this->body.push_back(std::make_unique<T>(args...));
+	return static_cast<T*>(this->body.back().get());
+}
+
+template<typename T, typename... Args>
+T* Model::insert(int position, Args&&... args)
+{
+	this->body.insert(position, std::make_unique<T>(args...));
+	return this->body[position];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modeleditcontext.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,14 @@
+#include "modeleditcontext.h"
+
+Model::EditContext::EditContext(Model& model) :
+	model{model}
+{
+}
+
+void Model::EditContext::setObjectProperty(
+	modelobjects::BaseObject* object,
+	modelobjects::Property property,
+	const QVariant& value)
+{
+	object->setProperty(property, value);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modeleditcontext.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,32 @@
+#pragma once
+#include "model.h"
+#include "objecttypes/modelobject.h"
+
+class Model::EditContext
+{
+public:
+	template<typename T, typename... Args>
+	modelobjects::BaseObject* append(Args&&... args);
+	template<typename T, typename... Args>
+	modelobjects::BaseObject* insert(int position, Args&&... args);
+	void setObjectProperty(
+		modelobjects::BaseObject* object,
+		modelobjects::Property property,
+		const QVariant &value);
+private:
+	EditContext(Model& model);
+	friend class Model;
+	Model& model;
+};
+
+template<typename T, typename... Args>
+modelobjects::BaseObject* Model::EditContext::append(Args&&... args)
+{
+	return this->model.append<T>(args...);
+}
+
+template<typename T, typename... Args>
+modelobjects::BaseObject* Model::EditContext::insert(int position, Args&&... args)
+{
+	return this->model.insert<T>(position, args...);
+}
--- a/src/modelobject.cpp	Sat Aug 24 14:44:42 2019 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-#include "modelobject.h"
-
-static Uuid &getUuidForNewObject()
-{
-    static Uuid running_uuid {0, 0};
-    incrementUuid(running_uuid);
-    return running_uuid;
-}
-
-ModelObject::ModelObject() :
-    id {getUuidForNewObject()}
-{
-}
-
-ModelObject::~ModelObject()
-{
-}
-
-
-Comment::Comment(QStringView text) :
-    ModelObject{},
-    storedText{text} {}
--- a/src/modelobject.h	Sat Aug 24 14:44:42 2019 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#pragma once
-#include <QString>
-#include <QStringView>
-#include "uuid.h"
-
-class ModelObject
-{
-public:
-    ModelObject();
-    virtual ~ModelObject();
-
-    const Uuid id;
-    virtual void toString(QTextStream &out) = 0;
-private:
-
-};
-
-class Comment : public ModelObject
-{
-    Comment(QStringView text);
-
-private:
-    QStringView storedText;
-};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/objecttypes/comment.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,29 @@
+#include "comment.h"
+
+modelobjects::Comment::Comment(QStringView text) :
+	BaseObject{},
+	storedText{text.toString()} {}
+
+QVariant modelobjects::Comment::getProperty(Property property) const
+{
+	switch (property)
+	{
+	case Property::Text:
+		return storedText;
+	default:
+		return BaseObject::getProperty(property);
+	}
+}
+
+auto modelobjects::Comment::setProperty(Property property, const QVariant& value)
+	-> SetPropertyResult
+{
+	switch (property)
+	{
+	case Property::Text:
+		storedText = value.toString();
+		return SetPropertyResult::Success;
+	default:
+		return BaseObject::setProperty(property, value);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/objecttypes/comment.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,20 @@
+#pragma once
+#include "modelobject.h"
+
+namespace modelobjects
+{
+	class Comment;
+}
+
+class modelobjects::Comment : public BaseObject
+{
+public:
+	Comment() = default;
+	Comment(QStringView text);
+	QVariant getProperty(Property property) const override;
+	SetPropertyResult setProperty(
+		Property property,
+		const QVariant& value) override;
+private:
+	QString storedText = "";
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/objecttypes/conditionaledge.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,42 @@
+#include "conditionaledge.h"
+
+modelobjects::ConditionalEdge::ConditionalEdge(
+	const Vertex& point_1,
+	const Vertex& point_2,
+	const Vertex& controlPoint_1,
+	const Vertex& controlPoint_2,
+	const Color color_index) :
+	Edge{point_1, point_2, color_index},
+	controlPoint_1{controlPoint_1},
+	controlPoint_2{controlPoint_2}
+{
+}
+
+QVariant modelobjects::ConditionalEdge::getProperty(Property property) const
+{
+	switch (property)
+	{
+	case Property::ControlPoint1:
+		return controlPoint_1;
+	case Property::ControlPoint2:
+		return controlPoint_2;
+	default:
+		return Edge::getProperty(property);
+	}
+}
+
+auto modelobjects::ConditionalEdge::setProperty(
+	Property property,
+	const QVariant& value)
+	-> SetPropertyResult
+{
+	switch (property)
+	{
+	case Property::ControlPoint1:
+		controlPoint_1 = value.value<Vertex>();
+	case Property::ControlPoint2:
+		controlPoint_2 = value.value<Vertex>();
+	default:
+		return Edge::setProperty(property, value);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/objecttypes/conditionaledge.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,26 @@
+#pragma once
+#include "edge.h"
+
+namespace modelobjects
+{
+	class ConditionalEdge;
+}
+
+class modelobjects::ConditionalEdge : public Edge
+{
+public:
+	ConditionalEdge() = default;
+	ConditionalEdge(
+		const Vertex& point_1,
+		const Vertex& point_2,
+		const Vertex& controlPoint_1,
+		const Vertex& controlPoint_2,
+		const Color color_index = colors::edge);
+	QVariant getProperty(Property property) const override;
+	SetPropertyResult setProperty(
+		Property property,
+		const QVariant& value) override;
+private:
+	Vertex controlPoint_1 = {};
+	Vertex controlPoint_2 = {};
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/objecttypes/edge.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,38 @@
+#include "edge.h"
+
+modelobjects::Edge::Edge(
+	const Vertex& point_1,
+	const Vertex& point_2,
+	const Color color_index) :
+	ColoredBaseObject{color_index},
+	point_1{point_1},
+	point_2{point_2} {}
+
+QVariant modelobjects::Edge::getProperty(Property property) const
+{
+	switch (property)
+	{
+	case Property::Point1:
+		return point_1;
+	case Property::Point2:
+		return point_2;
+	default:
+		return BaseClass::getProperty(property);
+	}
+}
+
+auto modelobjects::Edge::setProperty(Property property, const QVariant& value)
+	-> SetPropertyResult
+{
+	switch (property)
+	{
+	case Property::Point1:
+		point_1 = value.value<Vertex>();
+		return SetPropertyResult::Success;
+	case Property::Point2:
+		point_2 = value.value<Vertex>();
+		return SetPropertyResult::Success;
+	default:
+		return BaseClass::setProperty(property, value);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/objecttypes/edge.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,23 @@
+#pragma once
+#include "objecttypes/modelobject.h"
+
+namespace modelobjects
+{
+	class Edge;
+}
+
+class modelobjects::Edge : public ColoredBaseObject
+{
+public:
+	using BaseClass = ColoredBaseObject;
+	Edge() = default;
+	Edge(const Vertex& point_1, const Vertex& point_2,
+		 const Color color_index = colors::edge);
+	QVariant getProperty(Property property) const override;
+	SetPropertyResult setProperty(
+		Property property,
+		const QVariant& value) override;
+private:
+	Vertex point_1 = {};
+	Vertex point_2 = {};
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/objecttypes/errorline.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,32 @@
+#include "errorline.h"
+
+modelobjects::ErrorLine::ErrorLine(QStringView text) :
+	text{text.toString()}
+{
+}
+
+QVariant modelobjects::ErrorLine::getProperty(Property property) const
+{
+	switch (property)
+	{
+	case Property::Text:
+		return text;
+	default:
+		return BaseObject::getProperty(property);
+	}
+}
+
+auto modelobjects::ErrorLine::setProperty(
+	Property property,
+	const QVariant& value)
+	-> SetPropertyResult
+{
+	switch (property)
+	{
+	case Property::Text:
+		text = value.toString();
+		return SetPropertyResult::Success;
+	default:
+		return BaseObject::setProperty(property, value);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/objecttypes/errorline.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,19 @@
+#pragma once
+#include "modelobject.h"
+
+namespace modelobjects
+{
+	class ErrorLine;
+}
+
+class modelobjects::ErrorLine : public BaseObject
+{
+public:
+	ErrorLine(QStringView text = u"");
+	QVariant getProperty(Property property) const override;
+	SetPropertyResult setProperty(
+		Property property,
+		const QVariant& value) override;
+private:
+	QString text;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/objecttypes/modelobject.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,81 @@
+#include "modelobject.h"
+
+static Uuid &getUuidForNewObject()
+{
+    static Uuid running_uuid {0, 0};
+    incrementUuid(running_uuid);
+    return running_uuid;
+}
+
+modelobjects::BaseObject::BaseObject() :
+    id {getUuidForNewObject()}
+{
+}
+
+modelobjects::BaseObject::~BaseObject()
+{
+}
+
+bool modelobjects::BaseObject::hasColor() const
+{
+	return false;
+}
+
+QVariant modelobjects::BaseObject::getProperty(Property id) const
+{
+	Q_UNUSED(id);
+	return {};
+}
+
+auto modelobjects::BaseObject::setProperty(Property id, const QVariant& value)
+	-> SetPropertyResult
+{
+	Q_UNUSED(id)
+	Q_UNUSED(value)
+	return SetPropertyResult::PropertyNotHandled;
+}
+
+modelobjects::ColoredBaseObject::ColoredBaseObject(const Color color_index) :
+	color_index{color_index}
+{
+}
+
+bool modelobjects::ColoredBaseObject::hasColor() const
+{
+	return true;
+}
+
+QVariant modelobjects::ColoredBaseObject::getProperty(Property id) const
+{
+	switch (id)
+	{
+	case Property::Color:
+		return color_index.index;
+	default:
+		return BaseObject::getProperty(id);
+	}
+}
+
+auto modelobjects::ColoredBaseObject::setProperty(Property id, const QVariant& value)
+	-> SetPropertyResult
+{
+	switch (id)
+	{
+	case Property::Color:
+		{
+			bool ok;
+			const int value_int = value.toInt(&ok);
+			if (ok)
+			{
+				color_index.index = value_int;
+				return SetPropertyResult::Success;
+			}
+			else
+			{
+				return SetPropertyResult::InvalidValue;
+			}
+		}
+	default:
+		return BaseObject::setProperty(id, value);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/objecttypes/modelobject.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,61 @@
+#pragma once
+#include <QPointF>
+#include <QString>
+#include <QStringView>
+#include "main.h"
+#include "colors.h"
+#include "uuid.h"
+#include "vertex.h"
+
+namespace modelobjects
+{
+	enum class Property;
+	class BaseObject;
+	class ColoredBaseObject;
+}
+
+enum class modelobjects::Property
+{
+	Color,
+	Text,
+	Point1,
+	Point2,
+	Point3,
+	Point4,
+	ControlPoint1,
+	ControlPoint2,
+	Position,
+	Transformation,
+	ReferenceName,
+	IsInverted,
+};
+
+class modelobjects::BaseObject
+{
+public:
+	enum class SetPropertyResult
+	{
+		Success = 0,
+		PropertyNotHandled,
+		InvalidValue
+	};
+	BaseObject();
+	BaseObject(const BaseObject&) = delete;
+	virtual ~BaseObject();
+	const Uuid id;
+	//virtual void toString(QTextStream &out) = 0;
+	virtual bool hasColor() const;
+	virtual QVariant getProperty(Property id) const;
+	virtual SetPropertyResult setProperty(Property id, const QVariant& value);
+};
+
+class modelobjects::ColoredBaseObject : public BaseObject
+{
+public:
+	ColoredBaseObject(const Color color_index = colors::main);
+	bool hasColor() const override final;
+	QVariant getProperty(Property id) const override;
+	SetPropertyResult setProperty(Property id, const QVariant& value) override;
+private:
+	Color color_index = colors::main;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/objecttypes/polygon.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,97 @@
+#include "polygon.h"
+
+modelobjects::Triangle::Triangle(
+	const Vertex& point_1,
+	const Vertex& point_2,
+	const Vertex& point_3,
+	Color color_index) :
+	ColoredBaseObject{color_index},
+	points{point_1, point_2, point_3}
+{
+}
+
+QVariant modelobjects::Triangle::getProperty(const Property id) const
+{
+	switch (id)
+	{
+	case Property::Point1:
+		return points[0];
+	case Property::Point2:
+		return points[1];
+	case Property::Point3:
+		return points[2];
+	default:
+		return ColoredBaseObject::getProperty(id);
+	}
+}
+
+auto modelobjects::Triangle::setProperty(Property id, const QVariant& value)
+	-> SetPropertyResult
+{
+	switch (id)
+	{
+	case Property::Point1:
+		points[0] = value.value<Vertex>();
+		return SetPropertyResult::Success;
+	case Property::Point2:
+		points[1] = value.value<Vertex>();
+		return SetPropertyResult::Success;
+	case Property::Point3:
+		points[2] = value.value<Vertex>();
+		return SetPropertyResult::Success;
+	default:
+		return ColoredBaseObject::setProperty(id, value);
+	}
+}
+
+modelobjects::Quadrilateral::Quadrilateral(
+	const Vertex& point_1,
+	const Vertex& point_2,
+	const Vertex& point_3,
+	const Vertex& point_4,
+	Color color_index) :
+	ColoredBaseObject{color_index},
+	points{point_1, point_2, point_3, point_4}
+{
+}
+
+QVariant modelobjects::Quadrilateral::getProperty(const Property id) const
+{
+	switch (id)
+	{
+	case Property::Point1:
+		return points[0];
+	case Property::Point2:
+		return points[1];
+	case Property::Point3:
+		return points[2];
+	case Property::Point4:
+		return points[3];
+	default:
+		return ColoredBaseObject::getProperty(id);
+	}
+}
+
+auto modelobjects::Quadrilateral::setProperty(
+	const Property id,
+	const QVariant& value)
+	-> SetPropertyResult
+{
+	switch (id)
+	{
+	case Property::Point1:
+		points[0] = value.value<Vertex>();
+		return SetPropertyResult::Success;
+	case Property::Point2:
+		points[1] = value.value<Vertex>();
+		return SetPropertyResult::Success;
+	case Property::Point3:
+		points[2] = value.value<Vertex>();
+		return SetPropertyResult::Success;
+	case Property::Point4:
+		points[3] = value.value<Vertex>();
+		return SetPropertyResult::Success;
+	default:
+		return ColoredBaseObject::setProperty(id, value);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/objecttypes/polygon.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,39 @@
+#include <array>
+#include "modelobject.h"
+
+namespace modelobjects
+{
+	class Triangle;
+	class Quadrilateral;
+}
+
+class modelobjects::Triangle : public ColoredBaseObject
+{
+public:
+	Triangle() = default;
+	Triangle(
+		const Vertex &point_1,
+		const Vertex &point_2,
+		const Vertex &point_3,
+		Color color_index = colors::main);
+	QVariant getProperty(Property id) const override;
+	SetPropertyResult setProperty(Property id, const QVariant& value) override;
+private:
+	Vertex points[3] = {{}};
+};
+
+class modelobjects::Quadrilateral : public ColoredBaseObject
+{
+public:
+	Quadrilateral() = default;
+	Quadrilateral(
+		const Vertex &point_1,
+		const Vertex &point_2,
+		const Vertex &point_3,
+		const Vertex &point_4,
+		Color color_index = colors::main);
+	QVariant getProperty(Property id) const override;
+	SetPropertyResult setProperty(Property id, const QVariant& value) override;
+private:
+	Vertex points[4] = {{}};
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/objecttypes/subfilereference.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,16 @@
+#include "subfilereference.h"
+
+QVariant modelobjects::SubfileReference::getProperty(Property property) const
+{
+	switch (property)
+	{
+	case Property::Position:
+		return this->position;
+	case Property::Transformation:
+		return QVariant::fromValue(this->transformation);
+	case Property::ReferenceName:
+		return this->referenceName;
+	default:
+		return ColoredBaseObject::getProperty(property);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/objecttypes/subfilereference.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,18 @@
+#pragma once
+#include "modelobject.h"
+
+namespace modelobjects
+{
+	class SubfileReference;
+}
+
+class modelobjects::SubfileReference : ColoredBaseObject
+{
+public:
+	SubfileReference() = default;
+	QVariant getProperty(Property property) const override;
+private:
+	Vertex position;
+	QMatrix3x3 transformation;
+	QString referenceName;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/parser.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,270 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2018 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 "model.h"
+#include "parser.h"
+#include "objecttypes/comment.h"
+#include "objecttypes/conditionaledge.h"
+#include "objecttypes/edge.h"
+#include "objecttypes/errorline.h"
+#include "objecttypes/modelobject.h"
+#include "objecttypes/polygon.h"
+#include "objecttypes/subfilereference.h"
+
+/*
+ * Constructs an LDraw parser
+ */
+Parser::Parser(QIODevice& device, QObject* parent) :
+	QObject {parent},
+	device {device} {}
+
+/*
+ * Reads a single line from the device.
+ */
+QString Parser::readLine()
+{
+	return QString::fromUtf8(this->device.readLine()).trimmed();
+}
+
+const QMap<QString, decltype(LDHeader::type)> Parser::typeStrings {
+	{"Part", LDHeader::Part},
+	{"Subpart", LDHeader::Subpart},
+	{"Shortcut", LDHeader::Shortcut},
+	{"Primitive", LDHeader::Primitive},
+	{"8_Primitive", LDHeader::Primitive_8},
+	{"48_Primitive", LDHeader::Primitive_48},
+	{"Configuration", LDHeader::Configuration},
+};
+
+/*
+ * Parses a single line of the header.
+ * Possible parse results:
+ *   · ParseSuccess: the header line was parsed successfully.
+ *   · ParseFailure: the header line was parsed incorrectly and needs to be handled otherwise.
+ *   · StopParsing: the line does not belong in the header and header parsing needs to stop.
+ */
+Parser::HeaderParseResult Parser::parseHeaderLine(
+	LDHeader& header,
+	Winding& winding,
+	const QString& line
+) {
+	if (line.isEmpty())
+	{
+		return ParseSuccess;
+	}
+	else if (not line.startsWith("0") or line.startsWith("0 //"))
+	{
+		return StopParsing;
+	}
+	else if (line.startsWith("0 !LDRAW_ORG "))
+	{
+		QStringList tokens = line
+			.mid(strlen("0 !LDRAW_ORG "))
+			.split(" ", QString::SkipEmptyParts);
+
+		if (not tokens.isEmpty())
+		{
+			QString partTypeString = tokens[0];
+			// Anything that enters LDForge becomes unofficial in any case if saved.
+			// Therefore we don't need to give the Unofficial type any special
+			// consideration.
+			if (partTypeString.startsWith("Unofficial_"))
+				partTypeString = partTypeString.mid(strlen("Unofficial_"));
+			header.type = Parser::typeStrings.value(partTypeString, LDHeader::Part);
+			header.qualfiers = 0;
+			if (tokens.contains("Alias"))
+				header.qualfiers |= LDHeader::Alias;
+			if (tokens.contains("Physical_Color"))
+				header.qualfiers |= LDHeader::Physical_Color;
+			if (tokens.contains("Flexible_Section"))
+				header.qualfiers |= LDHeader::Flexible_Section;
+			return ParseSuccess;
+		}
+		else
+		{
+			return ParseFailure;
+		}
+	}
+	else if (line == "0 BFC CERTIFY CCW")
+	{
+		winding = CounterClockwise;
+		return ParseSuccess;
+	}
+	else if (line == "0 BFC CERTIFY CW")
+	{
+		winding = Clockwise;
+		return ParseSuccess;
+	}
+	else if (line == "0 BFC NOCERTIFY")
+	{
+		winding = NoWinding;
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 !HISTORY "))
+	{
+		static const QRegExp historyRegexp {
+			R"(0 !HISTORY\s+(\d{4}-\d{2}-\d{2})\s+)"
+			R"((\{[^}]+|\[[^]]+)[\]}]\s+(.+))"
+		};
+		if (historyRegexp.exactMatch(line))
+		{
+			QString dateString = historyRegexp.capturedTexts().value(1);
+			QString authorWithPrefix = historyRegexp.capturedTexts().value(2);
+			QString description = historyRegexp.capturedTexts().value(3);
+			LDHeader::HistoryEntry historyEntry;
+			historyEntry.date = QDate::fromString(dateString, Qt::ISODate);
+			historyEntry.description = description;
+
+			if (authorWithPrefix[0] == '{')
+				historyEntry.author = authorWithPrefix + "}";
+			else
+				historyEntry.author = authorWithPrefix.mid(1);
+
+			header.history.append(historyEntry);
+			return ParseSuccess;
+		}
+		else
+		{
+			return ParseFailure;
+		}
+	}
+	else if (line.startsWith("0 Author: "))
+	{
+		header.author = line.mid(strlen("0 Author: "));
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 Name: "))
+	{
+		header.name = line.mid(strlen("0 Name: "));
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 !HELP "))
+	{
+		if (not header.help.isEmpty())
+			header.help += "\n";
+		header.help += line.mid(strlen("0 !HELP "));
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 !KEYWORDS "))
+	{
+		if (not header.keywords.isEmpty())
+			header.keywords += "\n";
+		header.keywords += line.mid(strlen("0 !KEYWORDS "));
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 !CATEGORY "))
+	{
+		header.category = line.mid(strlen("0 !CATEGORY "));
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 !CMDLINE "))
+	{
+		header.cmdline = line.mid(strlen("0 !CMDLINE "));
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 !LICENSE Redistributable under CCAL version 2.0"))
+	{
+		header.license = LDHeader::CaLicense;
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 !LICENSE Not redistributable"))
+	{
+		header.license = LDHeader::NonCaLicense;
+		return ParseSuccess;
+	}
+	else
+	{
+		return ParseFailure;
+	}
+}
+
+/*
+ * Parses the header from the device given at construction and returns
+ * the resulting header structure.
+ */
+LDHeader Parser::parseHeader(Winding& winding)
+{
+	LDHeader header = {};
+
+	if (not this->device.atEnd())
+	{
+		// Parse the description
+		QString descriptionLine = this->readLine();
+		if (descriptionLine.startsWith("0 "))
+		{
+			header.description = descriptionLine.mid(strlen("0 ")).trimmed();
+
+			// Parse the rest of the header
+			while (not this->device.atEnd())
+			{
+				const QString& line = this->readLine();
+				auto result = parseHeaderLine(header, winding, line);
+
+				if (result == ParseFailure)
+				{
+					// Failed to parse this header line, add it as a comment into the body later.
+					this->bag.append(line);
+				}
+				else if (result == StopParsing)
+				{
+					// Header parsing stops, add this line to the body.
+					this->bag.append(line);
+					break;
+				}
+			}
+		}
+		else
+		{
+			this->bag.append(descriptionLine);
+		}
+	}
+
+	return header;
+}
+
+/**
+ * @brief Parses the model body into the given model.
+ * @param editor Handle to model edit context
+ */
+void Parser::parseBody(Model::EditContext& editor)
+{
+	bool invertNext = false;
+	while (not this->device.atEnd())
+		this->bag.append(this->readLine());
+	for (const QString& line : this->bag)
+	{
+		if (line == "0 BFC INVERTNEXT" or line == "0 BFC CERTIFY INVERTNEXT")
+		{
+			invertNext = true;
+			continue;
+		}
+		modelobjects::BaseObject* object = parseFromString(editor, line);
+		if (invertNext)
+		{
+			editor.setObjectProperty(object, modelobjects::Property::IsInverted, true);
+		}
+		invertNext = false;
+	}
+}
+
+modelobjects::BaseObject* Parser::parseFromString(
+	Model::EditContext& editor,
+	const QString& line)
+{
+	return editor.append<modelobjects::Comment>(line);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/parser.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,43 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2018 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/>.
+ */
+
+#pragma once
+#include "main.h"
+#include "objecttypes/modelobject.h"
+#include "model.h"
+#include "modeleditcontext.h"
+#include "header.h"
+
+class Parser : public QObject
+{
+	Q_OBJECT
+public:
+	enum { EndOfModel = -1 };
+	Parser(QIODevice& device, QObject* parent = nullptr);
+	LDHeader parseHeader(Winding& winding);
+	void parseBody(Model::EditContext& editor);
+	static modelobjects::BaseObject* parseFromString(Model::EditContext& editor,
+		const QString& line);
+	static const QMap<QString, decltype(LDHeader::type)> typeStrings;
+private:
+	enum HeaderParseResult {ParseSuccess, ParseFailure, StopParsing};
+	QString readLine();
+	HeaderParseResult parseHeaderLine(LDHeader& header, Winding& winding, const QString& line);
+	QIODevice& device;
+	QStringList bag;
+};
--- a/src/version.h	Sat Aug 24 14:44:42 2019 +0300
+++ b/src/version.h	Sun Sep 22 11:51:41 2019 +0300
@@ -1,6 +1,6 @@
 /*
  *  LDForge: LDraw parts authoring CAD
- *  Copyright (C) 2013 - 2018 Teemu Piippo
+ *  Copyright (C) 2019 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/vertex.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,215 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright(C) 2013 - 2018 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 "main.h"
+#include "vertex.h"
+
+/*
+void Vertex::transform(const Matrix& matrix, const Vertex& pos)
+{
+	double x2 = (matrix(0, 0) * x) + (matrix(0, 1) * y) + (matrix(0, 2) * z) + pos.x;
+	double y2 = (matrix(1, 0) * x) + (matrix(1, 1) * y) + (matrix(1, 2) * z) + pos.y;
+	double z2 = (matrix(2, 0) * x) + (matrix(2, 1) * y) + (matrix(2, 2) * z) + pos.z;
+	this->x = x2;
+	this->y = y2;
+	this->z = z2;
+}
+*/
+
+double& Vertex::operator[](Axis axis)
+{
+	switch (axis)
+	{
+	case X:
+		return this->x;
+	case Y:
+		return this->y;
+	case Z:
+		return this->z;
+	default:
+		throw std::runtime_error("Non-axis given to Vertex::operator[]");
+	}
+}
+
+double Vertex::operator[](Axis axis) const
+{
+	switch (axis)
+	{
+	case X:
+		return this->x;
+	case Y:
+		return this->y;
+	case Z:
+		return this->z;
+	default:
+		return 0.0;
+	}
+}
+
+void Vertex::setCoordinate(Axis axis, qreal value)
+{
+	(*this)[axis] = value;
+}
+
+Vertex VertexFromVector(const QVector3D& vector)
+{
+	return {vector.x(), vector.y(), vector.z()};
+}
+
+Vertex Vertex::operator*(qreal scalar) const
+{
+	return {this->x * scalar, this->y * scalar, this->z * scalar};
+}
+
+Vertex& Vertex::operator+=(const QVector3D& other)
+{
+	this->x += other.x();
+	this->y += other.y();
+	this->z += other.z();
+	return *this;
+}
+
+Vertex Vertex::operator+(const QVector3D& other) const
+{
+	Vertex result(*this);
+	result += other;
+	return result;
+}
+
+
+QVector3D vertexToVector(const Vertex& vertex)
+{
+	return {
+		static_cast<float>(vertex.x),
+		static_cast<float>(vertex.y),
+		static_cast<float>(vertex.z)
+	};
+}
+
+Vertex Vertex::operator-(const QVector3D& vector) const
+{
+	Vertex result = *this;
+	result -= vector;
+	return result;
+}
+
+Vertex& Vertex::operator-=(const QVector3D& vector)
+{
+	this->x -= vector.x();
+	this->y -= vector.y();
+	this->z -= vector.z();
+	return *this;
+}
+
+QVector3D Vertex::operator-(const Vertex& other) const
+{
+	return {
+		static_cast<float>(this->x - other.x),
+		static_cast<float>(this->y - other.y),
+		static_cast<float>(this->z - other.z)
+	};
+}
+
+Vertex& Vertex::operator*=(qreal scalar)
+{
+	x *= scalar;
+	y *= scalar;
+	z *= scalar;
+	return *this;
+}
+
+bool Vertex::operator==(const Vertex& other) const
+{
+	return this->x == other.x and this->y == other.y and this->z == other.z;
+}
+
+bool Vertex::operator!=(const Vertex& other) const
+{
+	return not(*this == other);
+}
+
+Vertex::operator QVariant() const
+{
+	return QVariant::fromValue<Vertex>(*this);
+}
+
+bool Vertex::operator<(const Vertex& other) const
+{
+	if (not qFuzzyCompare(this->x, other.x))
+		return this->x < other.x;
+	else if (not qFuzzyCompare(this->y, other.y))
+		return this->y < other.y;
+	else
+		return this->z < other.z;
+}
+
+/*
+ * Transforms this vertex with a tranformation matrix and returns the result.
+ */
+Vertex Vertex::transformed(const GLRotationMatrix& matrix) const
+{
+	return {
+		matrix(0, 0) * this->x
+			+ matrix(0, 1) * this->y
+			+ matrix(0, 2) * this->z,
+		matrix(1, 0) * this->x
+			+ matrix(1, 1) * this->y
+			+ matrix(1, 2) * this->z,
+		matrix(2, 0) * this->x
+			+ matrix(2, 1) * this->y
+			+ matrix(2, 2) * this->z,
+	};
+}
+
+/*
+ * Returns the distance from one vertex to another.
+ */
+qreal distance(const Vertex& one, const Vertex& other)
+{
+	return (one - other).length();
+}
+
+/*
+ * Returns a vertex with all coordinates inverted.
+ */
+Vertex operator-(const Vertex& vertex)
+{
+	return {-vertex.x, -vertex.y, -vertex.z};
+}
+
+/*
+ * Inserts this vertex into a data stream. This is needed for vertices to be
+ * stored in QSettings.
+ */
+QDataStream& operator<<(QDataStream& out, const Vertex& vertex)
+{
+	return out << vertex.x << vertex.y << vertex.z;
+}
+
+/*
+ * Takes a vertex from a data stream.
+ */
+QDataStream& operator>>(QDataStream& in, Vertex& vertex)
+{
+	return in >> vertex.x >> vertex.y >> vertex.z;
+}
+
+unsigned int qHash(const Vertex& key)
+{
+	return qHash(key.x) ^ rotl10(qHash(key.y)) ^ rotl20(qHash(key.z));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/vertex.h	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,59 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2019 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/>.
+ */
+
+#pragma once
+#include <functional>
+#include <QVector3D>
+#include "basics.h"
+
+struct Vertex
+{
+	qreal x;
+	qreal y;
+	qreal z;
+	// void transform(const class Matrix& matrix, const Vertex& pos);
+	Vertex transformed(const GLRotationMatrix& matrix) const;
+	void setCoordinate(Axis ax, qreal value);
+	Vertex& operator+=(const QVector3D& other);
+	Vertex operator+(const QVector3D& other) const;
+	QVector3D operator-(const Vertex& other) const;
+	Vertex operator-(const QVector3D& vector) const;
+	Vertex& operator-=(const QVector3D& vector);
+	Vertex& operator*=(qreal scalar);
+	Vertex operator*(qreal scalar) const;
+	bool operator<(const Vertex& other) const;
+	double& operator[](Axis ax);
+	double operator[](Axis ax) const;
+	bool operator==(const Vertex& other) const;
+	bool operator!=(const Vertex& other) const;
+	operator QVariant() const;
+};
+
+inline Vertex operator*(qreal scalar, const Vertex& vertex)
+{
+	return vertex * scalar;
+}
+
+Q_DECLARE_METATYPE(Vertex)
+qreal distance(const Vertex& one, const Vertex& other);
+Vertex vertexFromVector(const QVector3D& vector);
+QVector3D vertexToVector(const Vertex &vertex);
+unsigned int qHash(const Vertex& key);
+Vertex operator-(const Vertex& vertex);
+QDataStream& operator<<(QDataStream& out, const Vertex& vertex);
+QDataStream& operator>>(QDataStream& in, Vertex& vertex);

mercurial