Merge ../ldforge into gl

Tue, 22 Apr 2014 22:52:02 +0300

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Tue, 22 Apr 2014 22:52:02 +0300
changeset 728
2a8c981d79fa
parent 727
3faeb1174e40 (diff)
parent 663
b6601bfbe732 (current diff)
child 729
dcd7e3e86374

Merge ../ldforge into gl

src/actionsEdit.cc file | annotate | diff | comparison | revisions
src/mainWindow.h file | annotate | diff | comparison | revisions
ui/ldforge.ui file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Tue Apr 22 22:40:34 2014 +0300
+++ b/CMakeLists.txt	Tue Apr 22 22:52:02 2014 +0300
@@ -33,6 +33,7 @@
 	src/editHistory.cc
 	src/extPrograms.cc
 	src/glRenderer.cc
+	src/glCompiler.cc
 	src/ldConfig.cc
 	src/ldDocument.cc
 	src/ldObject.cc
@@ -69,6 +70,7 @@
 	src/colorSelector.h
 	src/configDialog.h
 	src/glRenderer.h
+	src/glCompiler.h
 	src/configuration.h
 	src/mainWindow.h
 	src/editHistory.h
Binary file icons/random-colors.png has changed
--- a/ldforge.qrc	Tue Apr 22 22:40:34 2014 +0300
+++ b/ldforge.qrc	Tue Apr 22 22:52:02 2014 +0300
@@ -85,6 +85,7 @@
 	<file>./icons/quad-split.png</file>
 	<file>./icons/radial-convert.png</file>
 	<file>./icons/radial.png</file>
+	<file>./icons/random-colors.png</file>
 	<file>./icons/rectifier.png</file>
 	<file>./icons/redo.png</file>
 	<file>./icons/replace-coords.png</file>
--- a/src/actions.cc	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/actions.cc	Tue Apr 22 22:52:02 2014 +0300
@@ -35,6 +35,7 @@
 #include "primitives.h"
 #include "radioGroup.h"
 #include "colors.h"
+#include "glCompiler.h"
 #include "ui_newpart.h"
 
 extern_cfg (Bool,		gl_wireframe);
@@ -43,6 +44,7 @@
 extern_cfg (String,	ld_defaultuser);
 extern_cfg (Int,		ld_defaultlicense);
 extern_cfg (Bool,		gl_drawangles);
+extern_cfg (Bool,		gl_randomcolors)
 
 // =============================================================================
 //
@@ -471,7 +473,6 @@
 
 		getCurrentDocument()->insertObj (idx, obj);
 		obj->select();
-		R()->compileObject (obj);
 		idx++;
 	}
 
@@ -847,9 +848,7 @@
 			obj->destroy();
 
 		// Compile all objects in the new subfile
-		for (LDObject* obj : doc->objects())
-			R()->compileObject (obj);
-
+		R()->compiler()->compileDocument (doc);
 		g_loadedFiles << doc;
 
 		// Add a reference to the new subfile to where the selection was
@@ -859,7 +858,6 @@
 		ref->setPosition (g_origin);
 		ref->setTransform (g_identity);
 		getCurrentDocument()->insertObj (refidx, ref);
-		R()->compileObject (ref);
 
 		// Refresh stuff
 		updateDocumentList();
@@ -871,3 +869,9 @@
 		delete doc;
 	}
 }
+
+DEFINE_ACTION (RandomColors, CTRL_SHIFT (R))
+{
+	gl_randomcolors = not gl_randomcolors;
+	R()->refresh();
+}
\ No newline at end of file
--- a/src/actionsEdit.cc	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/actionsEdit.cc	Tue Apr 22 22:52:02 2014 +0300
@@ -94,7 +94,6 @@
 		LDObject* pasted = parseLine (line);
 		getCurrentDocument()->insertObj (idx++, pasted);
 		pasted->select();
-		R()->compileObject (pasted);
 		++num;
 	}
 
@@ -123,18 +122,10 @@
 		// inlined contents.
 		long idx = obj->lineNumber();
 
-		if (idx == -1)
+		if (idx == -1 || obj->type() != LDObject::ESubfile)
 			continue;
 
-		LDObjectList objs;
-
-		if (obj->type() == LDObject::ESubfile)
-		{
-			LDSubfile::InlineFlags flags = deep ? LDSubfile::DeepCacheInline : LDSubfile::CacheInline;
-			objs = static_cast<LDSubfile*> (obj)->inlineContents (flags);
-		}
-		else
-			continue;
+		LDObjectList objs = static_cast<LDSubfile*> (obj)->inlineContents (deep, false);
 
 		// Merge in the inlined objects
 		for (LDObject* inlineobj : objs)
@@ -144,7 +135,6 @@
 			LDObject* newobj = parseLine (line);
 			getCurrentDocument()->insertObj (idx++, newobj);
 			newobj->select();
-			g_win->R()->compileObject (newobj);
 		}
 
 		// Delete the subfile now as it's been inlined.
@@ -188,13 +178,7 @@
 		// after the first one.
 		getCurrentDocument()->setObject (index, triangles[0]);
 		getCurrentDocument()->insertObj (index + 1, triangles[1]);
-
-		for (LDTriangle* t : triangles)
-			R()->compileObject (t);
-
-		// Delete this quad now, it has been split.
 		obj->destroy();
-
 		num++;
 	}
 
@@ -224,17 +208,12 @@
 		ui.errorIcon->hide();
 	}
 
-	if (not dlg->exec())
+	if (dlg->exec() == QDialog::Rejected)
 		return;
 
-	LDObject* oldobj = obj;
-
 	// Reinterpret it from the text of the input field
-	obj = parseLine (ui.code->text());
-	oldobj->replace (obj);
-
-	// Refresh
-	R()->compileObject (obj);
+	LDObject* newobj = parseLine (ui.code->text());
+	obj->replace (newobj);
 	refresh();
 }
 
@@ -263,7 +242,6 @@
 				continue;
 
 			obj->setColor (colnum);
-			R()->compileObject (obj);
 		}
 
 		refresh();
@@ -312,7 +290,6 @@
 
 			lines[i]->setColor (edgecolor);
 			getCurrentDocument()->insertObj (idx, lines[i]);
-			R()->compileObject (lines[i]);
 		}
 
 		num += numLines;
@@ -340,9 +317,7 @@
 			LDVertex* vert = new LDVertex;
 			vert->pos = obj->vertex (i);
 			vert->setColor (obj->color());
-
 			getCurrentDocument()->insertObj (++ln, vert);
-			R()->compileObject (vert);
 			++num;
 		}
 	}
@@ -394,10 +369,7 @@
 	vect[Z] *= *currentGrid().confs[Grid::Z];
 
 	for (LDObject* obj : selection())
-	{
 		obj->move (vect);
-		g_win->R()->compileObject (obj);
-	}
 
 	g_win->refresh();
 }
@@ -441,10 +413,7 @@
 	LDObjectList sel = selection();
 
 	for (LDObject* obj : sel)
-	{
 		obj->invert();
-		R()->compileObject (obj);
-	}
 
 	refresh();
 }
@@ -516,8 +485,6 @@
 			rotateVertex (v, rotpoint, transform);
 			vert->pos = v;
 		}
-
-		g_win->R()->compileObject (obj);
 	}
 
 	g_win->refresh();
@@ -593,7 +560,6 @@
 					roundToDecimals (v[ax], 3);
 
 				obj->setVertex (i, v);
-				R()->compileObject (obj);
 				num += 3;
 			}
 		}
@@ -621,7 +587,6 @@
 			col = edgecolor;
 
 		obj->setColor (col);
-		R()->compileObject (obj);
 		num++;
 	}
 
@@ -673,7 +638,6 @@
 			}
 
 			obj->setVertex (i, v);
-			R()->compileObject (obj);
 		}
 	}
 
@@ -708,7 +672,6 @@
 				v[ax] *= -1;
 
 			obj->setVertex (i, v);
-			R()->compileObject (obj);
 		}
 	}
 
@@ -727,8 +690,7 @@
 		if (obj->type() != LDObject::ECondLine)
 			continue;
 
-		LDLine* repl = static_cast<LDCondLine*> (obj)->demote();
-		R()->compileObject (repl);
+		static_cast<LDCondLine*> (obj)->demote();
 		++num;
 	}
 
@@ -768,7 +730,6 @@
 			continue;
 
 		obj->setColor (colnum);
-		R()->compileObject (obj);
 	}
 
 	print (tr ("Auto-colored: new color is [%1] %2"), colnum, getColor (colnum)->name);
--- a/src/basics.cc	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/basics.cc	Tue Apr 22 22:52:02 2014 +0300
@@ -316,7 +316,7 @@
 		case LDObject::ESubfile:
 		{
 			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline);
+			LDObjectList objs = ref->inlineContents (true, false);
 
 			for (LDObject * obj : objs)
 			{
--- a/src/colors.cc	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/colors.cc	Tue Apr 22 22:52:02 2014 +0300
@@ -74,7 +74,7 @@
 
 // =============================================================================
 // =============================================================================
-int luma (QColor& col)
+int luma (const QColor& col)
 {
 	return (0.2126f * col.red()) +
 		   (0.7152f * col.green()) +
--- a/src/colors.h	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/colors.h	Tue Apr 22 22:52:02 2014 +0300
@@ -31,7 +31,7 @@
 };
 
 void initColors();
-int luma (QColor& col);
+int luma (const QColor& col);
 
 // Safely gets a color with the given number or null if no such color.
 LDColor* getColor (int colnum);
--- a/src/extPrograms.cc	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/extPrograms.cc	Tue Apr 22 22:52:02 2014 +0300
@@ -173,7 +173,7 @@
 		if (obj->type() == LDObject::ESubfile)
 		{
 			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			LDObjectList objs = ref->inlineContents (LDSubfile::DeepInline);
+			LDObjectList objs = ref->inlineContents (true, false);
 
 			writeObjects (objs, f);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/glCompiler.cc	Tue Apr 22 22:52:02 2014 +0300
@@ -0,0 +1,390 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri 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/>.
+ */
+
+#define GL_GLEXT_PROTOTYPES
+#include <GL/glu.h>
+#include <GL/glext.h>
+#include "glCompiler.h"
+#include "ldObject.h"
+#include "colors.h"
+#include "ldDocument.h"
+#include "miscallenous.h"
+#include "glRenderer.h"
+#include "dialogs.h"
+
+struct GLErrorInfo
+{
+	GLenum	value;
+	QString	text;
+};
+
+static const GLErrorInfo g_GLErrors[] =
+{
+	{ GL_NO_ERROR,						"No error" },
+	{ GL_INVALID_ENUM,					"Unacceptable enumerator passed" },
+	{ GL_INVALID_VALUE,					"Numeric argument out of range" },
+	{ GL_INVALID_OPERATION,				"The operation is not allowed to be done in this state" },
+	{ GL_INVALID_FRAMEBUFFER_OPERATION,	"Framebuffer object is not complete"},
+	{ GL_OUT_OF_MEMORY,					"Out of memory" },
+	{ GL_STACK_UNDERFLOW,				"The operation would have caused an underflow" },
+	{ GL_STACK_OVERFLOW,				"The operation would have caused an overflow" },
+};
+
+cfg (String, gl_selectcolor, "#0080FF")
+extern_cfg (Bool, gl_blackedges);
+extern_cfg (String, gl_bgcolor);
+
+static QList<int>		g_warnedColors;
+static const QColor		g_BFCFrontColor (64, 192, 80);
+static const QColor		g_BFCBackColor (208, 64, 64);
+
+// static QMap<LDObject*, QString> g_objectOrigins;
+
+// =============================================================================
+//
+void checkGLError_private (const char* file, int line)
+{
+	QString errmsg;
+	GLenum errnum = glGetError();
+
+	if (errnum == GL_NO_ERROR)
+		return;
+
+	for (const GLErrorInfo& err : g_GLErrors)
+	{
+		if (err.value == errnum)
+		{
+			errmsg = err.text;
+			break;
+		}
+	}
+
+	print ("OpenGL ERROR: at %1:%2: %3", basename (QString (file)), line, errmsg);
+}
+
+// =============================================================================
+//
+GLCompiler::GLCompiler()
+{
+	needMerge();
+	memset (m_vboSizes, 0, sizeof m_vboSizes);
+}
+
+// =============================================================================
+//
+void GLCompiler::initialize()
+{
+	glGenBuffers (g_numVBOs, &m_vbo[0]);
+	checkGLError();
+}
+
+// =============================================================================
+//
+GLCompiler::~GLCompiler()
+{
+	glDeleteBuffers (g_numVBOs, &m_vbo[0]);
+	checkGLError();
+}
+
+// =============================================================================
+//
+uint32 GLCompiler::colorToRGB (const QColor& color)
+{
+	return
+		(color.red()   & 0xFF) << 0x00 |
+		(color.green() & 0xFF) << 0x08 |
+		(color.blue()  & 0xFF) << 0x10 |
+		(color.alpha() & 0xFF) << 0x18;
+}
+
+// =============================================================================
+//
+QColor GLCompiler::indexColorForID (int id) const
+{
+	// Calculate a color based from this index. This method caters for
+	// 16777216 objects. I don't think that will be exceeded anytime soon. :)
+	int r = (id / 0x10000) % 0x100,
+		g = (id / 0x100) % 0x100,
+		b = id % 0x100;
+
+	return QColor (r, g, b);
+}
+
+// =============================================================================
+//
+QColor GLCompiler::getColorForPolygon (LDPolygon& poly, LDObject* topobj,
+									   EVBOComplement complement) const
+{
+	QColor qcol;
+
+	switch (complement)
+	{
+		case VBOCM_Surfaces:
+		case VBOCM_NumComplements:
+			return QColor();
+
+		case VBOCM_BFCFrontColors:
+			qcol = g_BFCFrontColor;
+			break;
+
+		case VBOCM_BFCBackColors:
+			qcol = g_BFCBackColor;
+			break;
+
+		case VBOCM_PickColors:
+			return indexColorForID (topobj->id());
+
+		case VBOCM_RandomColors:
+			qcol = topobj->randomColor();
+			break;
+
+		case VBOCM_NormalColors:
+			if (poly.color == maincolor)
+			{
+				if (topobj->color() == maincolor)
+					qcol = GLRenderer::getMainColor();
+				else
+					qcol = getColor (topobj->color())->faceColor;
+			}
+			elif (poly.color == edgecolor)
+			{
+				qcol = luma (QColor (gl_bgcolor)) > 40 ? Qt::black : Qt::white;
+			}
+			else
+			{
+				LDColor* col = getColor (poly.color);
+
+				if (col)
+					qcol = col->faceColor;
+			}
+			break;
+	}
+
+	if (not qcol.isValid())
+	{
+		// The color was unknown. Use main color to make the polygon at least
+		// not appear pitch-black.
+		if (poly.num != 2 && poly.num != 5)
+			qcol = GLRenderer::getMainColor();
+		else
+			qcol = Qt::black;
+
+		// Warn about the unknown color, but only once.
+		if (not g_warnedColors.contains (poly.color))
+		{
+			print ("Unknown color %1!\n", poly.color);
+			g_warnedColors << poly.color;
+		}
+
+		return qcol;
+	}
+
+	if (topobj->isSelected())
+	{
+		// Brighten it up for the select list.
+		QColor selcolor (gl_selectcolor);
+		qcol.setRed ((qcol.red() + selcolor.red()) / 2);
+		qcol.setGreen ((qcol.green() + selcolor.green()) / 2);
+		qcol.setBlue ((qcol.blue() + selcolor.blue()) / 2);
+	}
+
+	return qcol;
+}
+
+// =============================================================================
+//
+void GLCompiler::needMerge()
+{
+	for (int i = 0; i < countof (m_vboChanged); ++i)
+		m_vboChanged[i] = true;
+}
+
+// =============================================================================
+//
+void GLCompiler::stageForCompilation (LDObject* obj)
+{
+	/*
+	g_objectOrigins[obj] = format ("%1:%2 (%3)",
+		obj->document()->getDisplayName(), obj->lineNumber(), obj->typeName());
+	*/
+
+	m_staged << obj;
+}
+
+// =============================================================================
+//
+void GLCompiler::unstage (LDObject* obj)
+{
+	m_staged.removeOne (obj);
+}
+
+// =============================================================================
+//
+void GLCompiler::compileDocument (LDDocument* doc)
+{
+	if (doc == null)
+		return;
+
+	for (LDObject* obj : doc->objects())
+		compileObject (obj);
+}
+
+// =============================================================================
+//
+void GLCompiler::compileStaged()
+{
+	removeDuplicates (m_staged);
+
+	for (LDObject* obj : m_staged)
+		compileObject (obj);
+
+	m_staged.clear();
+}
+
+// =============================================================================
+//
+void GLCompiler::prepareVBO (int vbonum)
+{
+	// Compile anything that still awaits it
+	compileStaged();
+
+	if (not m_vboChanged[vbonum])
+		return;
+
+	QVector<GLfloat> vbodata;
+
+	for (auto it = m_objectInfo.begin(); it != m_objectInfo.end(); ++it)
+	{
+		if (it.key()->document() == getCurrentDocument() && not it.key()->isHidden())
+			vbodata += it->data[vbonum];
+	}
+
+	glBindBuffer (GL_ARRAY_BUFFER, m_vbo[vbonum]);
+	glBufferData (GL_ARRAY_BUFFER, vbodata.size() * sizeof(GLfloat), vbodata.constData(), GL_STATIC_DRAW);
+	glBindBuffer (GL_ARRAY_BUFFER, 0);
+	checkGLError();
+	m_vboChanged[vbonum] = false;
+	m_vboSizes[vbonum] = vbodata.size();
+}
+
+// =============================================================================
+//
+void GLCompiler::dropObject (LDObject* obj)
+{
+	auto it = m_objectInfo.find (obj);
+
+	if (it != m_objectInfo.end())
+	{
+		m_objectInfo.erase (it);
+		needMerge();
+	}
+
+	unstage (obj);
+}
+
+// =============================================================================
+//
+void GLCompiler::compileObject (LDObject* obj)
+{
+//	print ("Compile %1\n", g_objectOrigins[obj]);
+
+	if (obj->document()->isImplicit())
+		return;
+
+	ObjectVBOInfo info;
+	info.isChanged = true;
+	dropObject (obj);
+
+	switch (obj->type())
+	{
+		// Note: We cannot split quads into triangles here, it would mess up the
+		// wireframe view. Quads must go into separate vbos.
+		case LDObject::ETriangle:
+		case LDObject::EQuad:
+		case LDObject::ELine:
+		case LDObject::ECondLine:
+		{
+			LDPolygon* poly = obj->getPolygon();
+			poly->id = obj->id();
+			compilePolygon (*poly, obj, &info);
+			delete poly;
+			break;
+		}
+
+		case LDObject::ESubfile:
+		{
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			auto data = ref->inlinePolygons();
+
+			for (LDPolygon& poly : data)
+			{
+				poly.id = obj->id();
+				compilePolygon (poly, obj, &info);
+			}
+			break;
+		}
+
+		default:
+			break;
+	}
+
+	m_objectInfo[obj] = info;
+	needMerge();
+}
+
+// =============================================================================
+//
+void GLCompiler::compilePolygon (LDPolygon& poly, LDObject* topobj, ObjectVBOInfo* objinfo)
+{
+	EVBOSurface surface;
+	int numverts;
+
+	switch (poly.num)
+	{
+		case 2:	surface = VBOSF_Lines;		numverts = 2; break;
+		case 3:	surface = VBOSF_Triangles;	numverts = 3; break;
+		case 4:	surface = VBOSF_Quads;		numverts = 4; break;
+		case 5:	surface = VBOSF_CondLines;	numverts = 2; break;
+		default: return;
+	}
+
+	for (int complement = 0; complement < VBOCM_NumComplements; ++complement)
+	{
+		const int vbonum			= vboNumber (surface, (EVBOComplement) complement);
+		QVector<GLfloat>& vbodata	= objinfo->data[vbonum];
+		const QColor color			= getColorForPolygon (poly, topobj, (EVBOComplement) complement);
+
+		for (int vert = 0; vert < numverts; ++vert)
+		{
+			if (complement == VBOCM_Surfaces)
+			{
+				// Write coordinates. Apparently Z must be flipped too?
+				vbodata	<< poly.vertices[vert].x()
+						<< -poly.vertices[vert].y()
+						<< -poly.vertices[vert].z();
+			}
+			else
+			{
+				vbodata	<< ((GLfloat) color.red()) / 255.0f
+						<< ((GLfloat) color.green()) / 255.0f
+						<< ((GLfloat) color.blue()) / 255.0f
+						<< ((GLfloat) color.alpha()) / 255.0f;
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/glCompiler.h	Tue Apr 22 22:52:02 2014 +0300
@@ -0,0 +1,83 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri 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/>.
+ */
+
+#ifndef LDFORGE_GLCOMPILER_H
+#define LDFORGE_GLCOMPILER_H
+
+#include "main.h"
+#include "glRenderer.h"
+#include "glShared.h"
+#include <QMap>
+
+// =============================================================================
+//
+class GLCompiler
+{
+public:
+	struct ObjectVBOInfo
+	{
+		QVector<GLfloat>	data[g_numVBOs];
+		bool				isChanged;
+	};
+
+	GLCompiler();
+	~GLCompiler();
+	void				compileDocument (LDDocument* doc);
+	void				dropObject (LDObject* obj);
+	void				initialize();
+	QColor				getColorForPolygon (LDPolygon& poly, LDObject* topobj,
+											EVBOComplement complement) const;
+	QColor				indexColorForID (int id) const;
+	void				needMerge();
+	void				prepareVBO (int vbonum);
+	void				stageForCompilation (LDObject* obj);
+	void				unstage (LDObject* obj);
+
+	static uint32		colorToRGB (const QColor& color);
+
+	static inline int	vboNumber (EVBOSurface surface, EVBOComplement complement)
+	{
+		return (surface * VBOCM_NumComplements) + complement;
+	}
+
+	inline GLuint		vbo (int vbonum) const
+	{
+		return m_vbo[vbonum];
+	}
+
+	inline int			vboSize (int vbonum) const
+	{
+		return m_vboSizes[vbonum];
+	}
+
+private:
+	void			compileStaged();
+	void			compileObject (LDObject* obj);
+	void			compilePolygon (LDPolygon& poly, LDObject* topobj, GLCompiler::ObjectVBOInfo* objinfo);
+
+	QMap<LDObject*, ObjectVBOInfo>		m_objectInfo;
+	LDObjectList						m_staged; // Objects that need to be compiled
+	GLuint								m_vbo[g_numVBOs];
+	bool								m_vboChanged[g_numVBOs];
+	int									m_vboSizes[g_numVBOs];
+};
+
+#define checkGLError() { checkGLError_private (__FILE__, __LINE__); }
+void checkGLError_private (const char* file, int line);
+
+#endif // LDFORGE_GLCOMPILER_H
--- a/src/glRenderer.cc	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/glRenderer.cc	Tue Apr 22 22:52:02 2014 +0300
@@ -16,12 +16,16 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#define GL_GLEXT_PROTOTYPES
+#include <GL/glu.h>
+#include <GL/glext.h>
 #include <QGLWidget>
 #include <QWheelEvent>
 #include <QMouseEvent>
 #include <QContextMenuEvent>
 #include <QInputDialog>
 #include <QToolTip>
+#include <qtextdocument.h>
 #include <QTimer>
 #include <GL/glu.h>
 
@@ -38,6 +42,7 @@
 #include "messageLog.h"
 #include "primitives.h"
 #include "misc/ringFinder.h"
+#include "glCompiler.h"
 
 static const LDFixedCameraInfo g_FixedCameras[6] =
 {
@@ -61,7 +66,6 @@
 cfg (String,	gl_bgcolor,				"#FFFFFF")
 cfg (String,	gl_maincolor,			"#A0A0A0")
 cfg (Float,		gl_maincolor_alpha,		1.0)
-cfg (String,	gl_selectcolor,			"#0080FF")
 cfg (Int,		gl_linethickness,		2)
 cfg (Bool,		gl_colorbfc,			false)
 cfg (Int,		gl_camera,				GLRenderer::EFreeCamera)
@@ -72,6 +76,7 @@
 cfg (Bool,		gl_aa,					true)
 cfg (Bool,		gl_linelengths,			true)
 cfg (Bool,		gl_drawangles,			false)
+cfg (Bool,		gl_randomcolors,		false)
 
 // argh
 const char* g_CameraNames[7] =
@@ -96,37 +101,40 @@
 	GL::EFreeCamera
 };
 
-// Definitions for visual axes, drawn on the screen
-const struct LDGLAxis
+struct LDGLAxis
 {
 	const QColor col;
 	const Vertex vert;
-} g_GLAxes[3] =
-{
-	{ QColor (255,   0,   0), Vertex (10000, 0, 0) }, // X
-	{ QColor (80,  192,   0), Vertex (0, 10000, 0) }, // Y
-	{ QColor (0,   160, 192), Vertex (0, 0, 10000) }, // Z
 };
 
-static bool g_glInvert = false;
-static QList<int> g_warnedColors;
+// Definitions for visual axes, drawn on the screen
+static const LDGLAxis g_GLAxes[3] =
+{
+	{ QColor (192,  96,  96), Vertex (10000, 0, 0) }, // X
+	{ QColor (48,  192,  48), Vertex (0, 10000, 0) }, // Y
+	{ QColor (48,  112, 192), Vertex (0, 0, 10000) }, // Z
+};
+
+static GLuint g_GLAxes_VBO;
+static GLuint g_GLAxes_ColorVBO;
 
 // =============================================================================
 //
 GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent)
 {
 	m_isPicking = m_rangepick = false;
-	m_camera = (GL::EFixedCamera) gl_camera;
+	m_camera = (EFixedCamera) gl_camera;
 	m_drawToolTip = false;
 	m_editMode = ESelectMode;
 	m_rectdraw = false;
 	m_panning = false;
+	m_compiler = new GLCompiler;
 	setDocument (null);
 	setDrawOnly (false);
 	setMessageLog (null);
 	m_width = m_height = -1;
 	m_hoverpos = g_origin;
-
+	m_compiler = new GLCompiler;
 	m_toolTipTimer = new QTimer (this);
 	m_toolTipTimer->setSingleShot (true);
 	connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer()));
@@ -157,6 +165,8 @@
 
 	for (CameraIcon& info : m_cameraIcons)
 		delete info.img;
+
+	delete m_compiler;
 }
 
 // =============================================================================
@@ -242,13 +252,46 @@
 void GLRenderer::initializeGL()
 {
 	setBackground();
-
 	glLineWidth (gl_linethickness);
-
+	glLineStipple (1, 0x6666);
 	setAutoFillBackground (false);
 	setMouseTracking (true);
 	setFocusPolicy (Qt::WheelFocus);
-	compileAllObjects();
+	compiler()->initialize();
+	initializeAxes();
+}
+
+// =============================================================================
+//
+void GLRenderer::initializeAxes()
+{
+	float axesdata[18];
+	float colordata[18];
+	memset (axesdata, 0, sizeof axesdata);
+
+	for (int i = 0; i < 3; ++i)
+	{
+		for (int j = 0; j < 3; ++j)
+		{
+			axesdata[(i * 6) + j] = g_GLAxes[i].vert.getCoordinate (j);
+			axesdata[(i * 6) + 3 + j] = -g_GLAxes[i].vert.getCoordinate (j);
+		}
+
+		for (int j = 0; j < 2; ++j)
+		{
+			colordata[(i * 6) + (j * 3) + 0] = g_GLAxes[i].col.red();
+			colordata[(i * 6) + (j * 3) + 1] = g_GLAxes[i].col.green();
+			colordata[(i * 6) + (j * 3) + 2] = g_GLAxes[i].col.blue();
+		}
+	}
+
+	glGenBuffers (1, &g_GLAxes_VBO);
+	glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_VBO);
+	glBufferData (GL_ARRAY_BUFFER, sizeof axesdata, axesdata, GL_STATIC_DRAW);
+	glGenBuffers (1, &g_GLAxes_ColorVBO);
+	glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_ColorVBO);
+	glBufferData (GL_ARRAY_BUFFER, sizeof colordata, colordata, GL_STATIC_DRAW);
+	glBindBuffer (GL_ARRAY_BUFFER, 0);
 }
 
 // =============================================================================
@@ -282,104 +325,6 @@
 
 // =============================================================================
 //
-void GLRenderer::setObjectColor (LDObject* obj, const ListType list)
-{
-	QColor qcol;
-
-	if (not obj->isColored())
-		return;
-
-	if (list == GL::PickList)
-	{
-		// Make the color by the object's ID if we're picking, so we can make the
-		// ID again from the color we get from the picking results. Be sure to use
-		// the top level parent's index since we want a subfile's children point
-		// to the subfile itself.
-		long i = obj->topLevelParent()->id();
-
-		// Calculate a color based from this index. This method caters for
-		// 16777216 objects. I don't think that'll be exceeded anytime soon. :)
-		// ATM biggest is 53588.dat with 12600 lines.
-		double r = (i / 0x10000) % 0x100,
-			   g = (i / 0x100) % 0x100,
-			   b = i % 0x100;
-
-		qglColor (QColor (r, g, b));
-		return;
-	}
-
-	if ((list == BFCFrontList || list == BFCBackList) &&
-		obj->type() != LDObject::ELine &&
-		obj->type() != LDObject::ECondLine)
-	{
-		if (list == GL::BFCFrontList)
-			qcol = QColor (40, 192, 0);
-		else
-			qcol = QColor (224, 0, 0);
-	}
-	else
-	{
-		if (obj->color() == maincolor)
-			qcol = getMainColor();
-		else
-		{
-			LDColor* col = ::getColor (obj->color());
-
-			if (col)
-				qcol = col->faceColor;
-		}
-
-		if (obj->color() == edgecolor)
-		{
-			LDColor* col;
-
-			if (not gl_blackedges && obj->parent() && (col = ::getColor (obj->parent()->color())))
-				qcol = col->edgeColor;
-			else
-				qcol = not m_darkbg ? Qt::black : Qt::white;
-		}
-
-		if (not qcol.isValid())
-		{
-			// The color was unknown. Use main color to make the object at least
-			// not appear pitch-black.
-			if (obj->color() != edgecolor)
-				qcol = getMainColor();
-
-			// Warn about the unknown colors, but only once.
-			for (int i : g_warnedColors)
-				if (obj->color() == i)
-					return;
-
-			print ("%1: Unknown color %2!\n", __func__, obj->color());
-			g_warnedColors << obj->color();
-			return;
-		}
-	}
-
-	int r = qcol.red(),
-		 g = qcol.green(),
-		 b = qcol.blue(),
-		 a = qcol.alpha();
-
-	if (obj->topLevelParent()->isSelected())
-	{
-		// Brighten it up for the select list.
-		QColor selcolor (gl_selectcolor);
-		r = (r + selcolor.red()) / 2;
-		g = (g + selcolor.green()) / 2;
-		b = (b + selcolor.blue()) / 2;
-	}
-
-	glColor4f (
-		((double) r) / 255.0f,
-		((double) g) / 255.0f,
-		((double) b) / 255.0f,
-		((double) a) / 255.0f);
-}
-
-// =============================================================================
-//
 void GLRenderer::refresh()
 {
 	update();
@@ -390,10 +335,9 @@
 //
 void GLRenderer::hardRefresh()
 {
-	compileAllObjects();
+	compiler()->compileDocument (getCurrentDocument());
 	refresh();
-
-	glLineWidth (gl_linethickness);
+	glLineWidth (gl_linethickness); // TODO: ...?
 }
 
 // =============================================================================
@@ -425,7 +369,7 @@
 	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 	glEnable (GL_DEPTH_TEST);
 
-	if (m_camera != EFreeCamera)
+	if (camera() != EFreeCamera)
 	{
 		glMatrixMode (GL_PROJECTION);
 		glPushMatrix();
@@ -434,7 +378,7 @@
 		glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f);
 		glTranslatef (pan (X), pan (Y), 0.0f);
 
-		if (m_camera != EFrontCamera && m_camera != EBackCamera)
+		if (camera() != EFrontCamera && camera() != EBackCamera)
 		{
 			glRotatef (90.0f, g_FixedCameras[camera()].glrotate[0],
 				g_FixedCameras[camera()].glrotate[1],
@@ -442,7 +386,7 @@
 		}
 
 		// Back camera needs to be handled differently
-		if (m_camera == GLRenderer::EBackCamera)
+		if (camera() == EBackCamera)
 		{
 			glRotatef (180.0f, 1.0f, 0.0f, 0.0f);
 			glRotatef (180.0f, 0.0f, 0.0f, 1.0f);
@@ -461,47 +405,95 @@
 		glRotatef (rot (Z), 0.0f, 0.0f, 1.0f);
 	}
 
-	const GL::ListType list = (not isDrawOnly() && isPicking()) ? PickList : NormalList;
-
-	if (gl_colorbfc && not isPicking() && not isDrawOnly())
-	{
-		glEnable (GL_CULL_FACE);
+	glEnableClientState (GL_VERTEX_ARRAY);
+	glEnableClientState (GL_COLOR_ARRAY);
 
-		for (LDObject* obj : document()->objects())
-		{
-			if (obj->isHidden())
-				continue;
-
-			glCullFace (GL_BACK);
-			glCallList (obj->glLists[BFCFrontList]);
-
-			glCullFace (GL_FRONT);
-			glCallList (obj->glLists[BFCBackList]);
-		}
-
-		glDisable (GL_CULL_FACE);
+	if (isPicking())
+	{
+		drawVBOs (VBOSF_Triangles, VBOCM_PickColors, GL_TRIANGLES);
+		drawVBOs (VBOSF_Quads, VBOCM_PickColors, GL_QUADS);
+		drawVBOs (VBOSF_Lines, VBOCM_PickColors, GL_LINES);
+		drawVBOs (VBOSF_CondLines, VBOCM_PickColors, GL_LINES);
 	}
 	else
 	{
-		for (LDObject* obj : document()->objects())
+		if (gl_colorbfc)
+		{
+			glEnable (GL_CULL_FACE);
+			glCullFace (GL_BACK);
+			drawVBOs (VBOSF_Triangles, VBOCM_BFCFrontColors, GL_TRIANGLES);
+			drawVBOs (VBOSF_Quads, VBOCM_BFCFrontColors, GL_QUADS);
+			glCullFace (GL_FRONT);
+			drawVBOs (VBOSF_Triangles, VBOCM_BFCBackColors, GL_TRIANGLES);
+			drawVBOs (VBOSF_Quads, VBOCM_BFCBackColors, GL_QUADS);
+			glDisable (GL_CULL_FACE);
+		}
+		else
 		{
-			if (obj->isHidden())
-				continue;
+			if (gl_randomcolors)
+			{
+				drawVBOs (VBOSF_Triangles, VBOCM_RandomColors, GL_TRIANGLES);
+				drawVBOs (VBOSF_Quads, VBOCM_RandomColors, GL_QUADS);
+			}
+			else
+			{
+				drawVBOs (VBOSF_Triangles, VBOCM_NormalColors, GL_TRIANGLES);
+				drawVBOs (VBOSF_Quads, VBOCM_NormalColors, GL_QUADS);
+			}
+		}
 
-			glCallList (obj->glLists[list]);
+		drawVBOs (VBOSF_Lines, VBOCM_NormalColors, GL_LINES);
+		glEnable (GL_LINE_STIPPLE);
+		drawVBOs (VBOSF_CondLines, VBOCM_NormalColors, GL_LINES);
+		glDisable (GL_LINE_STIPPLE);
+
+		if (gl_axes)
+		{
+			glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_VBO);
+			glVertexPointer (3, GL_FLOAT, 0, NULL);
+			glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_VBO);
+			glColorPointer (3, GL_FLOAT, 0, NULL);
+			glDrawArrays (GL_LINES, 0, 6);
+			checkGLError();
 		}
 	}
 
-	if (gl_axes && not isPicking() && not isDrawOnly())
-		glCallList (m_axeslist);
-
 	glPopMatrix();
+	glBindBuffer (GL_ARRAY_BUFFER, 0);
+	glDisableClientState (GL_VERTEX_ARRAY);
+	glDisableClientState (GL_COLOR_ARRAY);
+	checkGLError();
+	glDisable (GL_CULL_FACE);
 	glMatrixMode (GL_MODELVIEW);
 	glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
 }
 
 // =============================================================================
 //
+void GLRenderer::drawVBOs (EVBOSurface surface, EVBOComplement colors, GLenum type)
+{
+	int surfacenum = m_compiler->vboNumber (surface, VBOCM_Surfaces);
+	int colornum = m_compiler->vboNumber (surface, colors);
+	m_compiler->prepareVBO (surfacenum);
+	m_compiler->prepareVBO (colornum);
+	GLuint surfacevbo = m_compiler->vbo (surfacenum);
+	GLuint colorvbo = m_compiler->vbo (colornum);
+	GLsizei count = m_compiler->vboSize (surfacenum) / 3;
+
+	if (count > 0)
+	{
+		glBindBuffer (GL_ARRAY_BUFFER, surfacevbo);
+		glVertexPointer (3, GL_FLOAT, 0, null);
+		checkGLError();
+		glBindBuffer (GL_ARRAY_BUFFER, colorvbo);
+		glColorPointer (4, GL_FLOAT, 0, null);
+		checkGLError();
+		glDrawArrays (type, 0, count);
+		checkGLError();
+	}
+}
+
+// =============================================================================
 // This converts a 2D point on the screen to a 3D point in the model. If 'snap'
 // is true, the 3D point will snap to the current grid.
 //
@@ -510,7 +502,7 @@
 	assert (camera() != EFreeCamera);
 
 	Vertex pos3d;
-	const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera];
+	const LDFixedCameraInfo* cam = &g_FixedCameras[camera()];
 	const Axis axisX = cam->axisX;
 	const Axis axisY = cam->axisY;
 	const int negXFac = cam->negX ? -1 : 1,
@@ -547,7 +539,7 @@
 QPoint GLRenderer::coordconv3_2 (const Vertex& pos3d) const
 {
 	GLfloat m[16];
-	const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera];
+	const LDFixedCameraInfo* cam = &g_FixedCameras[camera()];
 	const Axis axisX = cam->axisX;
 	const Axis axisY = cam->axisY;
 	const int negXFac = cam->negX ? -1 : 1,
@@ -593,15 +585,27 @@
 	if (isDrawOnly())
 		return;
 
-	if (m_camera != EFreeCamera && !isPicking())
+#ifndef RELEASE
+	if (not isPicking())
+	{
+		QString text = format ("Rotation: (%1, %2, %3)\nPanning: (%4, %5), Zoom: %6",
+			rot(X), rot(Y), rot(Z), pan(X), pan(Y), zoom());
+		QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
+		paint.setPen (textpen);
+		paint.drawText ((width() - textSize.width()) / 2, height() - textSize.height(), textSize.width(),
+			textSize.height(), Qt::AlignCenter, text);
+	}
+#endif
+
+	if (camera() != EFreeCamera && !isPicking())
 	{
 		// Paint the overlay image if we have one
-		const LDGLOverlay& overlay = currentDocumentData().overlays[m_camera];
+		const LDGLOverlay& overlay = currentDocumentData().overlays[camera()];
 
 		if (overlay.img != null)
 		{
-			QPoint v0 = coordconv3_2 (currentDocumentData().overlays[m_camera].v0),
-					   v1 = coordconv3_2 (currentDocumentData().overlays[m_camera].v1);
+			QPoint v0 = coordconv3_2 (currentDocumentData().overlays[camera()].v0),
+					   v1 = coordconv3_2 (currentDocumentData().overlays[camera()].v1);
 
 			QRect targRect (v0.x(), v0.y(), abs (v1.x() - v0.x()), abs (v1.y() - v0.y())),
 				  srcRect (0, 0, overlay.img->width(), overlay.img->height());
@@ -894,141 +898,6 @@
 
 // =============================================================================
 //
-void GLRenderer::compileAllObjects()
-{
-	if (not document())
-		return;
-
-	// Compiling all is a big job, use a busy cursor
-	setCursor (Qt::BusyCursor);
-
-	m_knownVerts.clear();
-
-	for (LDObject* obj : document()->objects())
-		compileObject (obj);
-
-	// Compile axes
-	glDeleteLists (m_axeslist, 1);
-	m_axeslist = glGenLists (1);
-	glNewList (m_axeslist, GL_COMPILE);
-	glBegin (GL_LINES);
-
-	for (const LDGLAxis& ax : g_GLAxes)
-	{
-		qglColor (ax.col);
-		compileVertex (ax.vert);
-		compileVertex (-ax.vert);
-	}
-
-	glEnd();
-	glEndList();
-
-	setCursor (Qt::ArrowCursor);
-}
-
-// =============================================================================
-//
-void GLRenderer::compileSubObject (LDObject* obj, const GLenum gltype)
-{
-	glBegin (gltype);
-
-	const int numverts = (obj->type() != LDObject::ECondLine) ? obj->vertices() : 2;
-
-	if (not g_glInvert)
-	{
-		for (int i = 0; i < numverts; ++i)
-			compileVertex (obj->vertex (i));
-	}
-	else
-	{
-		for (int i = numverts - 1; i >= 0; --i)
-			compileVertex (obj->vertex (i));
-	}
-
-	glEnd();
-}
-
-// =============================================================================
-//
-void GLRenderer::compileList (LDObject* obj, const GLRenderer::ListType list)
-{
-	setObjectColor (obj, list);
-
-	switch (obj->type())
-	{
-		case LDObject::ELine:
-		{
-			compileSubObject (obj, GL_LINES);
-		} break;
-
-		case LDObject::ECondLine:
-		{
-			// Draw conditional lines with a dash pattern - however, use a full
-			// line when drawing a pick list to make selecting them easier.
-			if (list != GL::PickList)
-			{
-				glLineStipple (1, 0x6666);
-				glEnable (GL_LINE_STIPPLE);
-			}
-
-			compileSubObject (obj, GL_LINES);
-
-			glDisable (GL_LINE_STIPPLE);
-		} break;
-
-		case LDObject::ETriangle:
-		{
-			compileSubObject (obj, GL_TRIANGLES);
-		} break;
-
-		case LDObject::EQuad:
-		{
-			compileSubObject (obj, GL_QUADS);
-		} break;
-
-		case LDObject::ESubfile:
-		{
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			LDObjectList objs;
-
-			objs = ref->inlineContents (LDSubfile::DeepCacheInline | LDSubfile::RendererInline);
-			bool oldinvert = g_glInvert;
-
-			if (ref->transform().getDeterminant() < 0)
-				g_glInvert = not g_glInvert;
-
-			LDObject* prev = ref->previous();
-
-			if (prev != null &&
-				prev->type() == LDObject::EBFC &&
-				static_cast<LDBFC*> (prev)->statement() == LDBFC::InvertNext)
-			{
-				g_glInvert = not g_glInvert;
-			}
-
-			for (LDObject* obj : objs)
-			{
-				compileList (obj, list);
-				obj->destroy();
-			}
-
-			g_glInvert = oldinvert;
-		} break;
-
-		default:
-			break;
-	}
-}
-
-// =============================================================================
-//
-void GLRenderer::compileVertex (const Vertex& vrt)
-{
-	glVertex3d (vrt[X], -vrt[Y], -vrt[Z]);
-}
-
-// =============================================================================
-//
 void GLRenderer::clampAngle (double& angle) const
 {
 	while (angle < 0)
@@ -1153,9 +1022,9 @@
 
 		QPoint curspos = coordconv3_2 (m_hoverpos);
 
-		for (const Vertex& pos3d: m_knownVerts)
+		for (auto it = document()->vertices().begin(); it != document()->vertices().end(); ++it)
 		{
-			QPoint pos2d = coordconv3_2 (pos3d);
+			QPoint pos2d = coordconv3_2 (it.key());
 
 			// Measure squared distance
 			const double dx = abs (pos2d.x() - curspos.x()),
@@ -1168,7 +1037,7 @@
 			if (distsq < mindist)
 			{
 				mindist = distsq;
-				closest = pos3d;
+				closest = it.key();
 				valid = true;
 
 				// If it's only 4 pixels away, I think we found our vertex now.
@@ -1382,10 +1251,16 @@
 	// Go through each pixel read and add them to the selection.
 	for (qint32 i = 0; i < numpixels; ++i)
 	{
+		QList<float> selfloats;
+		selfloats << ((float) pixelptr[0]) / 255.0f;
+		selfloats << ((float) pixelptr[1]) / 255.0f;
+		selfloats << ((float) pixelptr[2]) / 255.0f;
+		selfloats << ((float) pixelptr[3]) / 255.0f;
+
 		qint32 idx =
 			(*(pixelptr + 0) * 0x10000) +
-			(*(pixelptr + 1) * 0x00100) +
-			(*(pixelptr + 2) * 0x00001);
+			(*(pixelptr + 1) * 0x100) +
+			*(pixelptr + 2);
 		pixelptr += 4;
 
 		if (idx == 0xFFFFFF)
@@ -1450,7 +1325,7 @@
 		case ECircleMode:
 		{
 			// Cannot draw into the free camera - use top instead.
-			if (m_camera == EFreeCamera)
+			if (camera() == EFreeCamera)
 				setCamera (ETopCamera);
 
 			// Disable the context menu - we need the right mouse button
@@ -1720,7 +1595,7 @@
 //
 void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const
 {
-	const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera];
+	const LDFixedCameraInfo* cam = &g_FixedCameras[camera()];
 	relX = cam->axisX;
 	relY = cam->axisY;
 }
@@ -1735,10 +1610,11 @@
 	{
 		for (int i = 0; i < obj->vertices(); ++i)
 			verts << obj->vertex (i);
-	} elif (obj->type() == LDObject::ESubfile)
+	}
+	elif (obj->type() == LDObject::ESubfile)
 	{
 		LDSubfile* ref = static_cast<LDSubfile*> (obj);
-		LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline);
+		LDObjectList objs = ref->inlineContents (true, false);
 
 		for (LDObject* obj : objs)
 		{
@@ -1754,28 +1630,15 @@
 //
 void GLRenderer::compileObject (LDObject* obj)
 {
-	deleteLists (obj);
-
-	for (const GL::ListType listType : g_glListTypes)
-	{
-		if (isDrawOnly() && listType != GL::NormalList)
-			continue;
-
-		GLuint list = glGenLists (1);
-		glNewList (list, GL_COMPILE);
+	compiler()->stageForCompilation (obj);
+	obj->setGLInit (true);
+}
 
-		obj->glLists[listType] = list;
-		compileList (obj, listType);
-
-		glEndList();
-	}
-
-	// Mark in known vertices of this object
-	QList<Vertex> verts = getVertices (obj);
-	m_knownVerts << verts;
-	removeDuplicates (m_knownVerts);
-
-	obj->setGLInit (true);
+// =============================================================================
+//
+void GLRenderer::forgetObject (LDObject* obj)
+{
+	compiler()->dropObject (obj);
 }
 
 // =============================================================================
@@ -1803,7 +1666,7 @@
 	// We come here if the cursor has stayed in one place for longer than a
 	// a second. Check if we're holding it over a camera icon - if so, draw
 	// a tooltip.
-for (CameraIcon & icon : m_cameraIcons)
+	for (CameraIcon & icon : m_cameraIcons)
 	{
 		if (icon.destRect.contains (m_pos))
 		{
@@ -1817,24 +1680,10 @@
 
 // =============================================================================
 //
-void GLRenderer::deleteLists (LDObject* obj)
-{
-	// Delete the lists but only if they have been initialized
-	if (not obj->isGLInit())
-		return;
-
-	for (const GL::ListType listType : g_glListTypes)
-		glDeleteLists (obj->glLists[listType], 1);
-
-	obj->setGLInit (false);
-}
-
-// =============================================================================
-//
 Axis GLRenderer::getCameraAxis (bool y, GLRenderer::EFixedCamera camid)
 {
 	if (camid == (GL::EFixedCamera) - 1)
-		camid = m_camera;
+		camid = camera();
 
 	const LDFixedCameraInfo* cam = &g_FixedCameras[camid];
 	return (y) ? cam->axisY : cam->axisX;
@@ -1850,6 +1699,7 @@
 	if (img->isNull())
 	{
 		critical (tr ("Failed to load overlay image!"));
+		currentDocumentData().overlays[cam].invalid = true;
 		delete img;
 		return false;
 	}
@@ -1862,6 +1712,7 @@
 	info.ox = x;
 	info.oy = y;
 	info.img = img;
+	info.invalid = false;
 
 	if (info.lw == 0)
 		info.lw = (info.lh * img->width()) / img->height();
@@ -1881,11 +1732,11 @@
 
 	// Set alpha of all pixels to 0.5
 	for (long i = 0; i < img->width(); ++i)
-		for (long j = 0; j < img->height(); ++j)
-		{
-			uint32 pixel = img->pixel (i, j);
-			img->setPixel (i, j, 0x80000000 | (pixel & 0x00FFFFFF));
-		}
+	for (long j = 0; j < img->height(); ++j)
+	{
+		uint32 pixel = img->pixel (i, j);
+		img->setPixel (i, j, 0x80000000 | (pixel & 0x00FFFFFF));
+	}
 
 	updateOverlayObjects();
 	return true;
@@ -1949,11 +1800,10 @@
 //
 void GLRenderer::zoomToFit()
 {
+	zoom() = 30.0f;
+
 	if (document() == null || m_width == -1 || m_height == -1)
-	{
-		zoom() = 30.0f;
 		return;
-	}
 
 	bool lastfilled = false;
 	bool firstrun = true;
@@ -1993,22 +1843,23 @@
 			if (imgdata[i] != white || imgdata[((h - 1) * w) + i] != white)
 			{
 				filled = true;
-				goto endOfLoop;
+				break;
 			}
 		}
 
 		// Left and right edges
-		for (int i = 0; i < h; ++i)
+		if (filled == false)
 		{
-			if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white)
+			for (int i = 0; i < h; ++i)
 			{
-				filled = true;
-				goto endOfLoop;
+				if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white)
+				{
+					filled = true;
+					break;
+				}
 			}
 		}
 
-endOfLoop:
-
 		delete[] cap;
 
 		if (firstrun)
@@ -2149,7 +2000,7 @@
 			delete meta.img;
 			meta.img = null;
 		}
-		elif (ovlobj && (!meta.img || meta.fname != ovlobj->fileName()))
+		elif (ovlobj && (meta.img == null || meta.fname != ovlobj->fileName()) && meta.invalid == false)
 			setupOverlay (cam, ovlobj->fileName(), ovlobj->x(),
 				ovlobj->y(), ovlobj->width(), ovlobj->height());
 	}
--- a/src/glRenderer.h	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/glRenderer.h	Tue Apr 22 22:52:02 2014 +0300
@@ -22,7 +22,9 @@
 #include "macros.h"
 #include "ldObject.h"
 #include "ldDocument.h"
+#include "glShared.h"
 
+class GLCompiler;
 class MessageManager;
 class QDialogButtonBox;
 class RadioGroup;
@@ -49,6 +51,7 @@
 					lh;
 	QString			fname;
 	QImage*			img;
+	bool			invalid;
 };
 
 struct LDFixedCameraInfo
@@ -77,12 +80,23 @@
 
 	LDGLData()
 	{
-		for (int i = 0; i < 6; ++i)
+		for (int i = 0; i < 7; ++i)
 		{
-			overlays[i].img = null;
-			depthValues[i] = 0.0f;
+			if (i < 6)
+			{
+				overlays[i].img = null;
+				overlays[i].invalid = false;
+				depthValues[i] = 0.0f;
+			}
+
+			zoom[i] = 30.0;
+			panX[i] = 0.0;
+			panY[i] = 0.0;
 		}
 
+		rotX = 0;
+		rotY = 0;
+		rotZ = 0;
 		init = false;
 	}
 };
@@ -119,10 +133,10 @@
 		// and Qt doesn't like that.
 		struct CameraIcon
 		{
-			QPixmap*			img;
-			QRect				srcRect,
-								destRect,
-								selRect;
+			QPixmap*		img;
+			QRect			srcRect,
+							destRect,
+							selRect;
 			EFixedCamera	cam;
 		};
 
@@ -132,6 +146,7 @@
 		PROPERTY (private,	bool,				isPicking,	setPicking,		STOCK_WRITE)
 		PROPERTY (public,	LDDocument*,		document,	setDocument,	CUSTOM_WRITE)
 		PROPERTY (public,	EditMode,			editMode,	setEditMode,	CUSTOM_WRITE)
+		PROPERTY (private,	GLCompiler*,		compiler,	setCompiler,	STOCK_WRITE)
 
 	public:
 		GLRenderer (QWidget* parent = null);
@@ -144,13 +159,12 @@
 
 		void           clearOverlay();
 		void           compileObject (LDObject* obj);
-		void           compileAllObjects();
 		void           drawGLScene();
 		void           endDraw (bool accept);
+		void           forgetObject (LDObject* obj);
 		Axis           getCameraAxis (bool y, EFixedCamera camid = (EFixedCamera) - 1);
 		const char*    getCameraName() const;
 		double         getDepthValue() const;
-		QColor         getMainColor();
 		LDGLOverlay&   getOverlay (int newcam);
 		uchar*         getScreencap (int& w, int& h);
 		void           hardRefresh();
@@ -169,6 +183,7 @@
 		void           zoomAllToFit();
 
 		static void    deleteLists (LDObject* obj);
+		static QColor  getMainColor();
 
 	protected:
 		void           contextMenuEvent (QContextMenuEvent* ev);
@@ -213,7 +228,6 @@
 		bool						m_rectdraw;
 		Vertex						m_rectverts[4];
 		QColor						m_bgcolor;
-		QList<Vertex>				m_knownVerts;
 
 		void           addDrawnVertex (Vertex m_hoverpos);
 		LDOverlay*     findOverlayObject (EFixedCamera cam);
@@ -243,6 +257,12 @@
 		// Convert a 2D point to a 3D point
 		Vertex         coordconv2_3 (const QPoint& pos2d, bool snap) const;
 
+		// Draw a VBO array
+		void           drawVBOs (EVBOSurface surface, EVBOComplement colors, GLenum type);
+
+		// Determine which color to draw text with
+		QColor         getTextPen() const;
+
 		// Convert a 3D point to a 2D point
 		QPoint         coordconv3_2 (const Vertex& pos3d) const;
 
@@ -294,6 +314,7 @@
 
 	private slots:
 		void           slot_toolTipTimer();
+		void initializeAxes();
 };
 
 // Alias for short namespaces
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/glShared.h	Tue Apr 22 22:52:02 2014 +0300
@@ -0,0 +1,73 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri 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/>.
+ */
+
+#ifndef LDFORGE_GLSHARED_H
+#define LDFORGE_GLSHARED_H
+#include <QString>
+
+class LDObject;
+
+struct LDPolygon
+{
+	char		num;
+	Vertex		vertices[4];
+	int			id;
+	int			color;
+
+	inline int numVertices() const
+	{
+		return (num == 5) ? 4 : num;
+	}
+};
+
+enum EVBOSurface
+{
+	VBOSF_Lines,
+	VBOSF_Triangles,
+	VBOSF_Quads,
+	VBOSF_CondLines,
+	VBOSF_NumSurfaces
+};
+
+enum EVBOComplement
+{
+	VBOCM_Surfaces,
+	VBOCM_NormalColors,
+	VBOCM_PickColors,
+	VBOCM_BFCFrontColors,
+	VBOCM_BFCBackColors,
+	VBOCM_RandomColors,
+	VBOCM_NumComplements
+};
+
+// KDevelop doesn't seem to understand some VBO stuff
+#ifdef IN_IDE_PARSER
+using GLint = int;
+using GLsizei = int;
+using GLenum = unsigned int;
+using GLuint = unsigned int;
+void glBindBuffer (GLenum, GLuint);
+void glGenBuffers (GLuint, GLuint*);
+void glDeleteBuffers (GLuint, GLuint*);
+void glBufferData (GLuint, GLuint, void*, GLuint);
+void glBufferSubData (GLenum, GLint, GLsizei, void*);
+#endif
+
+static const int g_numVBOs = VBOSF_NumSurfaces * VBOCM_NumComplements;
+
+#endif // LDFORGE_GLSHARED_H
\ No newline at end of file
--- a/src/ldDocument.cc	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/ldDocument.cc	Tue Apr 22 22:52:02 2014 +0300
@@ -19,7 +19,9 @@
 #include <QMessageBox>
 #include <QFileDialog>
 #include <QDir>
+#include <QTime>
 #include <QApplication>
+
 #include "main.h"
 #include "configuration.h"
 #include "ldDocument.h"
@@ -29,6 +31,7 @@
 #include "dialogs.h"
 #include "glRenderer.h"
 #include "misc/invokeLater.h"
+#include "glCompiler.h"
 
 cfg (String, io_ldpath, "");
 cfg (List, io_recentfiles, {});
@@ -121,6 +124,7 @@
 // =============================================================================
 //
 LDDocument::LDDocument() :
+	m_flags (0),
 	m_gldata (new LDGLData)
 {
 	setImplicit (true);
@@ -128,6 +132,7 @@
 	setTabIndex (-1);
 	setHistory (new History);
 	history()->setDocument (this);
+	m_needsReCache = true;
 }
 
 // =============================================================================
@@ -137,18 +142,14 @@
 	// Remove this file from the list of files. This MUST be done FIRST, otherwise
 	// a ton of other functions will think this file is still valid when it is not!
 	g_loadedFiles.removeOne (this);
-
+	m_flags |= DOCF_IsBeingDestroyed;
 	m_history->setIgnoring (true);
 
 	// Clear everything from the model
 	for (LDObject* obj : objects())
 		obj->destroy();
 
-	// Clear the cache as well
-	for (LDObject* obj : cache())
-		obj->destroy();
-
-	delete m_history;
+	delete history();
 	delete m_gldata;
 
 	// If we just closed the current file, we need to set the current
@@ -471,7 +472,7 @@
 
 // =============================================================================
 //
-LDDocument* openDocument (QString path, bool search)
+LDDocument* openDocument (QString path, bool search, bool implicit)
 {
 	// Convert the file name to lowercase since some parts contain uppercase
 	// file names. I'll assume here that the library will always use lowercase
@@ -497,6 +498,7 @@
 		return null;
 
 	LDDocument* load = new LDDocument;
+	load->setImplicit (implicit);
 	load->setFullPath (fullpath);
 	load->setName (LDDocument::shortenName (load->fullPath()));
 	dprint ("name: %1 (%2)", load->name(), load->fullPath());
@@ -671,7 +673,7 @@
 		return;
 	}
 
-	LDDocument* file = openDocument (path, false);
+	LDDocument* file = openDocument (path, false, false);
 
 	if (not file)
 	{
@@ -1024,7 +1026,7 @@
 
 	// If it's not loaded, try open it
 	if (not doc)
-		doc = openDocument (filename, true);
+		doc = openDocument (filename, true, true);
 
 	return doc;
 }
@@ -1066,9 +1068,7 @@
 {
 	history()->add (new AddHistory (objects().size(), obj));
 	m_objects << obj;
-
-	if (obj->type() == LDObject::EVertex)
-		m_vertices << obj;
+	addKnownVerticesOf (obj);
 
 #ifdef DEBUG
 	if (not isImplicit())
@@ -1076,6 +1076,7 @@
 #endif
 
 	obj->setDocument (this);
+	g_win->R()->compileObject (obj);
 	return getObjectCount() - 1;
 }
 
@@ -1095,6 +1096,8 @@
 	history()->add (new AddHistory (pos, obj));
 	m_objects.insert (pos, obj);
 	obj->setDocument (this);
+	g_win->R()->compileObject (obj);
+	addKnownVerticesOf (obj);
 
 #ifdef DEBUG
 	if (not isImplicit())
@@ -1104,14 +1107,65 @@
 
 // =============================================================================
 //
+void LDDocument::addKnownVerticesOf (LDObject* obj)
+{
+	if (isImplicit())
+		return;
+
+	if (obj->type() == LDObject::ESubfile)
+	{
+		LDSubfile* ref = static_cast<LDSubfile*> (obj);
+
+		for (Vertex vrt : ref->fileInfo()->inlineVertices())
+		{
+			vrt.transform (ref->transform(), ref->position());
+			addKnownVertexReference (vrt);
+		}
+	}
+	else
+	{
+		for (int i = 0; i < obj->vertices(); ++i)
+			addKnownVertexReference (obj->vertex (i));
+	}
+}
+
+// =============================================================================
+//
+void LDDocument::removeKnownVerticesOf (LDObject* obj)
+{
+	if (isImplicit())
+		return;
+
+	if (obj->type() == LDObject::ESubfile)
+	{
+		LDSubfile* ref = static_cast<LDSubfile*> (obj);
+
+		for (Vertex vrt : ref->fileInfo()->inlineVertices())
+		{
+			vrt.transform (ref->transform(), ref->position());
+			removeKnownVertexReference (vrt);
+		}
+	}
+	else
+	{
+		for (int i = 0; i < obj->vertices(); ++i)
+			removeKnownVertexReference (obj->vertex (i));
+	}
+}
+
+// =============================================================================
+//
 void LDDocument::forgetObject (LDObject* obj)
 {
 	int idx = obj->lineNumber();
 	obj->unselect();
 	assert (m_objects[idx] == obj);
 
-	if (not history()->isIgnoring())
+	if (not isImplicit() && not (flags() & DOCF_IsBeingDestroyed))
+	{
 		history()->add (new DelHistory (idx, obj));
+		removeKnownVerticesOf (obj);
+	}
 
 	m_objects.removeAt (idx);
 	obj->setDocument (null);
@@ -1119,6 +1173,44 @@
 
 // =============================================================================
 //
+void LDDocument::vertexChanged (const Vertex& a, const Vertex& b)
+{
+	removeKnownVertexReference (a);
+	addKnownVertexReference (b);
+}
+
+// =============================================================================
+//
+void LDDocument::addKnownVertexReference (const Vertex& a)
+{
+	if (isImplicit())
+		return;
+
+	auto it = m_vertices.find (a);
+
+	if (it == m_vertices.end())
+		m_vertices[a] = 1;
+	else
+		++it.value();
+}
+
+// =============================================================================
+//
+void LDDocument::removeKnownVertexReference (const Vertex& a)
+{
+	if (isImplicit())
+		return;
+
+	auto it = m_vertices.find (a);
+	assert (it != m_vertices.end());
+
+	// If there's no more references to a given vertex, it is to be removed.
+	if (--it.value() == 0)
+		m_vertices.erase (it);
+}
+
+// =============================================================================
+//
 bool safeToCloseAll()
 {
 	for (LDDocument* f : g_loadedFiles)
@@ -1142,9 +1234,12 @@
 		*m_history << new EditHistory (idx, oldcode, newcode);
 	}
 
+	removeKnownVerticesOf (m_objects[idx]);
 	m_objects[idx]->unselect();
 	m_objects[idx]->setDocument (null);
 	obj->setDocument (this);
+	addKnownVerticesOf (obj);
+	g_win->R()->compileObject (obj);
 	m_objects[idx] = obj;
 }
 
@@ -1202,81 +1297,81 @@
 
 // =============================================================================
 //
-LDObjectList LDDocument::inlineContents (LDSubfile::InlineFlags flags)
+void LDDocument::initializeCachedData()
+{
+	if (not m_needsReCache)
+		return;
+
+	LDObjectList objs = inlineContents (true, true);
+	m_storedVertices.clear();
+
+	for (LDObject* obj : objs)
+	{
+		assert (obj->type() != LDObject::ESubfile);
+		LDPolygon* data = obj->getPolygon();
+
+		if (data != null)
+		{
+			m_polygonData << *data;
+			delete data;
+		}
+
+		for (int i = 0; i < obj->vertices(); ++i)
+			m_storedVertices << obj->vertex (i);
+
+		obj->destroy();
+	}
+
+	removeDuplicates (m_storedVertices);
+	m_needsReCache = false;
+}
+
+// =============================================================================
+//
+QList<LDPolygon> LDDocument::inlinePolygons()
+{
+	initializeCachedData();
+	return polygonData();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDObjectList LDDocument::inlineContents (bool deep, bool renderinline)
 {
 	// Possibly substitute with logoed studs:
 	// stud.dat -> stud-logo.dat
 	// stud2.dat -> stud-logo2.dat
-	if (gl_logostuds && (flags & LDSubfile::RendererInline))
+	if (gl_logostuds && renderinline)
 	{
 		// Ensure logoed studs are loaded first
 		loadLogoedStuds();
 
-		if (name() == "stud.dat" && g_logoedStud)
-			return g_logoedStud->inlineContents (flags);
-		elif (name() == "stud2.dat" && g_logoedStud2)
-			return g_logoedStud2->inlineContents (flags);
+		if (name() == "stud.dat" && g_logoedStud != null)
+			return g_logoedStud->inlineContents (deep, renderinline);
+		elif (name() == "stud2.dat" && g_logoedStud2 != null)
+			return g_logoedStud2->inlineContents (deep, renderinline);
 	}
 
 	LDObjectList objs, objcache;
 
-	bool deep = flags & LDSubfile::DeepInline,
-		 doCache = flags & LDSubfile::CacheInline;
-
-	if (m_needsCache)
-	{
-		m_cache.clear();
-		doCache = true;
-	}
-
-	// If we have this cached, just create a copy of that
-	if (deep && not cache().isEmpty())
+	for (LDObject* obj : objects())
 	{
-		for (LDObject* obj : cache())
-			objs << obj->createCopy();
-	}
-	else
-	{
-		if (not deep)
-			doCache = false;
-
-		for (LDObject* obj : objects())
-		{
-			// Skip those without scemantic meaning
-			if (not obj->isScemantic())
-				continue;
+		// Skip those without scemantic meaning
+		if (not obj->isScemantic())
+			continue;
 
-			// Got another sub-file reference, inline it if we're deep-inlining. If not,
-			// just add it into the objects normally. Also, we only cache immediate
-			// subfiles and this is not one. Yay, recursion!
-			if (deep && obj->type() == LDObject::ESubfile)
-			{
-				LDSubfile* ref = static_cast<LDSubfile*> (obj);
-
-				// We only want to cache immediate subfiles, so shed the caching
-				// flag when recursing deeper in hierarchy.
-				LDObjectList otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline));
+		// Got another sub-file reference, inline it if we're deep-inlining. If not,
+		// just add it into the objects normally. Yay, recursion!
+		if (deep == true && obj->type() == LDObject::ESubfile)
+		{
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			LDObjectList otherobjs = ref->inlineContents (deep, renderinline);
 
-				for (LDObject* otherobj : otherobjs)
-				{
-					// Cache this object, if desired
-					if (doCache)
-						objcache << otherobj->createCopy();
-
-					objs << otherobj;
-				}
-			}
-			else
-			{
-				if (doCache)
-					objcache << obj->createCopy();
-
-				objs << obj->createCopy();
-			}
+			for (LDObject* otherobj : otherobjs)
+				objs << otherobj;
 		}
-
-		if (doCache)
-			setCache (objcache);
+		else
+			objs << obj->createCopy();
 	}
 
 	return objs;
@@ -1311,7 +1406,7 @@
 		g_win->buildObjList();
 		g_win->updateTitle();
 		g_win->R()->setDocument (f);
-		g_win->R()->repaint();
+		g_win->R()->compiler()->needMerge();
 		print ("Changed file to %1", f->getDisplayName());
 	}
 }
@@ -1356,8 +1451,8 @@
 	delete g_logoedStud;
 	delete g_logoedStud2;
 
-	g_logoedStud = openDocument ("stud-logo.dat", true);
-	g_logoedStud2 = openDocument ("stud2-logo.dat", true);
+	g_logoedStud = openDocument ("stud-logo.dat", true, true);
+	g_logoedStud2 = openDocument ("stud2-logo.dat", true, true);
 
 	print (LDDocument::tr ("Logoed studs loaded.\n"));
 }
@@ -1444,3 +1539,11 @@
 	if (references().isEmpty())
 		invokeLater (closeUnused);
 }
+
+// =============================================================================
+//
+QList<Vertex> LDDocument::inlineVertices()
+{
+	initializeCachedData();
+	return m_storedVertices;
+}
--- a/src/ldDocument.h	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/ldDocument.h	Tue Apr 22 22:52:02 2014 +0300
@@ -21,11 +21,13 @@
 #include "main.h"
 #include "ldObject.h"
 #include "editHistory.h"
+#include "glShared.h"
 
 class History;
 class OpenProgressDialog;
 class LDDocumentPointer;
 struct LDGLData;
+class GLCompiler;
 
 namespace LDPaths
 {
@@ -38,6 +40,17 @@
 	QString getError();
 }
 
+//
+// Flags for LDDocument
+//
+enum LDDocumentFlag
+{
+	DOCF_IsBeingDestroyed = (1 << 0),
+};
+
+Q_DECLARE_FLAGS (LDDocumentFlags, LDDocumentFlag)
+Q_DECLARE_OPERATORS_FOR_FLAGS (LDDocumentFlags)
+
 // =============================================================================
 //
 // This class stores a document either as a editable file for the user or for
@@ -53,19 +66,22 @@
 {
 	public:
 		using ReferenceList = QList<LDDocumentPointer*>;
+		using KnownVertexMap = QMap<Vertex, int>;
 
 		Q_OBJECT
 		PROPERTY (public,	QString,		name,			setName,			STOCK_WRITE)
 		PROPERTY (private,	LDObjectList,	objects, 		setObjects,			STOCK_WRITE)
 		PROPERTY (private,	LDObjectList,	cache, 			setCache,			STOCK_WRITE)
 		PROPERTY (private,	History*,		history,		setHistory,			STOCK_WRITE)
-		PROPERTY (private,	LDObjectList,	vertices,		setVertices,		STOCK_WRITE)
+		PROPERTY (private,	KnownVertexMap,	vertices,		setVertices,		STOCK_WRITE)
 		PROPERTY (private,	ReferenceList,	references,		setReferences,		STOCK_WRITE)
 		PROPERTY (public,	QString,		fullPath,		setFullPath,		STOCK_WRITE)
 		PROPERTY (public,	QString,		defaultName,	setDefaultName,		STOCK_WRITE)
 		PROPERTY (public,	bool,			isImplicit,		setImplicit,		STOCK_WRITE)
 		PROPERTY (public,	long,			savePosition,	setSavePosition,	STOCK_WRITE)
 		PROPERTY (public,	int,			tabIndex,		setTabIndex,		STOCK_WRITE)
+		PROPERTY (public,	QList<LDPolygon>,	polygonData,	setPolygonData,	STOCK_WRITE)
+		PROPERTY (private,	LDDocumentFlags,	flags,			setFlags,		STOCK_WRITE)
 
 	public:
 		LDDocument();
@@ -78,7 +94,8 @@
 		QString getDisplayName();
 		const LDObjectList& getSelection() const;
 		bool hasUnsavedChanges() const; // Does this document have unsaved changes?
-		LDObjectList inlineContents (LDSubfile::InlineFlags flags);
+		void initializeCachedData();
+		LDObjectList inlineContents (bool deep, bool renderinline);
 		void insertObj (int pos, LDObject* obj);
 		int getObjectCount() const;
 		LDObject* getObject (int pos) const;
@@ -88,6 +105,11 @@
 		void setObject (int idx, LDObject* obj);
 		void addReference (LDDocumentPointer* ptr);
 		void removeReference (LDDocumentPointer* ptr);
+		QList<LDPolygon> inlinePolygons();
+		void vertexChanged (const Vertex& a, const Vertex& b);
+		void addKnownVerticesOf(LDObject* obj);
+		void removeKnownVerticesOf (LDObject* sub);
+		QList<Vertex> inlineVertices();
 
 		inline LDDocument& operator<< (LDObject* obj)
 		{
@@ -144,12 +166,16 @@
 	private:
 		LDObjectList			m_sel;
 		LDGLData*				m_gldata;
+		QList<Vertex>			m_storedVertices;
 
-		// If set to true, next inline of this document discards the cache and
-		// re-builds it.
-		bool					m_needsCache;
+		// If set to true, next polygon inline of this document discards the
+		// stored polygon data and re-builds it.
+		bool					m_needsReCache;
 
 		static LDDocument*		m_curdoc;
+
+		void addKnownVertexReference (const Vertex& a);
+		void removeKnownVertexReference (const Vertex& a);
 };
 
 inline LDDocument* getCurrentDocument()
@@ -168,7 +194,7 @@
 
 // Opens the given file and parses the LDraw code within. Returns a pointer
 // to the opened file or null on error.
-LDDocument* openDocument (QString path, bool search);
+LDDocument* openDocument (QString path, bool search, bool implicit);
 
 // Opens the given file and returns a pointer to it, potentially looking in /parts and /p
 QFile* openLDrawFile (QString relpath, bool subdirs, QString* pathpointer = null);
--- a/src/ldObject.cc	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/ldObject.cc	Tue Apr 22 22:52:02 2014 +0300
@@ -16,6 +16,7 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+
 #include "main.h"
 #include "ldObject.h"
 #include "ldDocument.h"
@@ -24,6 +25,7 @@
 #include "editHistory.h"
 #include "glRenderer.h"
 #include "colors.h"
+#include "glCompiler.h"
 
 cfg (String, ld_defaultname, "");
 cfg (String, ld_defaultuser, "");
@@ -46,6 +48,7 @@
 	memset (m_coords, 0, sizeof m_coords);
 	chooseID();
 	g_LDObjects << this;
+	setRandomColor (QColor::fromRgb (rand()));
 }
 
 // =============================================================================
@@ -269,7 +272,7 @@
 		document()->forgetObject (this);
 
 	// Delete the GL lists
-	GL::deleteLists (this);
+	g_win->R()->forgetObject (this);
 
 	// Remove this object from the list of LDObjects
 	g_LDObjects.removeOne (this);
@@ -318,10 +321,10 @@
 }
 
 // =============================================================================
-//
-LDObjectList LDSubfile::inlineContents (InlineFlags flags)
+// -----------------------------------------------------------------------------
+LDObjectList LDSubfile::inlineContents (bool deep, bool render)
 {
-	LDObjectList objs = fileInfo()->inlineContents (flags);
+	LDObjectList objs = fileInfo()->inlineContents (deep, render);
 
 	// Transform the objects
 	for (LDObject* obj : objs)
@@ -336,6 +339,44 @@
 
 // =============================================================================
 //
+LDPolygon* LDObject::getPolygon()
+{
+	Type ot = type();
+	int num =
+		(ot == LDObject::ELine)		?	2 :
+		(ot == LDObject::ETriangle)	?	3 :
+		(ot == LDObject::EQuad)		?	4 :
+		(ot == LDObject::ECondLine)	?	5 :
+										0;
+	if (num == 0)
+		return null;
+
+	LDPolygon* data = new LDPolygon;
+	data->id = id();
+	data->num = num;
+	data->color = color();
+
+	for (int i = 0; i < data->numVertices(); ++i)
+		data->vertices[i] = vertex (i);
+
+	return data;
+}
+
+// =============================================================================
+//
+QList<LDPolygon> LDSubfile::inlinePolygons()
+{
+	QList<LDPolygon> data = fileInfo()->inlinePolygons();
+
+	for (LDPolygon& entry : data)
+		for (int i = 0; i < entry.numVertices(); ++i)
+			entry.vertices[i].transform (transform(), position());
+
+	return data;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
 long LDObject::lineNumber() const
 {
 	assert (document() != null);
@@ -646,6 +687,7 @@
 void LDOverlay::invert() {}
 
 // =============================================================================
+//
 // Hook the set accessors of certain properties to this changeProperty function.
 // It takes care of history management so we can capture low-level changes, this
 // makes history stuff work out of the box.
@@ -657,14 +699,17 @@
 	if (*ptr == val)
 		return;
 
-	if (obj->document() && (idx = obj->lineNumber()) != -1)
+	if (obj->document() != null && (idx = obj->lineNumber()) != -1)
 	{
 		QString before = obj->asText();
 		*ptr = val;
 		QString after = obj->asText();
 
 		if (before != after)
+		{
 			obj->document()->addToHistory (new EditHistory (idx, before, after));
+			g_win->R()->compileObject (obj);
+		}
 	}
 	else
 		*ptr = val;
@@ -688,6 +733,9 @@
 //
 void LDObject::setVertex (int i, const Vertex& vert)
 {
+	if (document() != null)
+		document()->vertexChanged (*m_coords[i], vert);
+
 	changeProperty (this, &m_coords[i], LDSharedVertex::getSharedVertex (vert));
 }
 
@@ -695,14 +743,26 @@
 //
 void LDMatrixObject::setPosition (const Vertex& a)
 {
+	if (linkPointer()->document() != null)
+		linkPointer()->document()->removeKnownVerticesOf (linkPointer());
+
 	changeProperty (linkPointer(), &m_position, LDSharedVertex::getSharedVertex (a));
+
+	if (linkPointer()->document() != null)
+		linkPointer()->document()->addKnownVerticesOf (linkPointer());
 }
 
 // =============================================================================
 //
 void LDMatrixObject::setTransform (const Matrix& val)
 {
+	if (linkPointer()->document() != null)
+		linkPointer()->document()->removeKnownVerticesOf (linkPointer());
+
 	changeProperty (linkPointer(), &m_transform, val);
+
+	if (linkPointer()->document() != null)
+		linkPointer()->document()->addKnownVerticesOf (linkPointer());
 }
 
 // =============================================================================
@@ -822,4 +882,27 @@
 
 	LDObject* copy = parseLine (asText());
 	return copy;
-}
\ No newline at end of file
+}
+
+// =============================================================================
+//
+void LDSubfile::setFileInfo (const LDDocumentPointer& a)
+{
+	if (document() != null)
+		document()->removeKnownVerticesOf (this);
+
+	m_fileInfo = a;
+
+	// If it's an immediate subfile reference (i.e. this subfile belongs in an
+	// explicit file), we need to pre-compile the GL polygons for the document
+	// if they don't exist already.
+	if (a != null &&
+		a->isImplicit() == false &&
+		a->polygonData().isEmpty())
+	{
+		a->initializeCachedData();
+	}
+
+	if (document() != null)
+		document()->addKnownVerticesOf (this);
+};
--- a/src/ldObject.h	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/ldObject.h	Tue Apr 22 22:52:02 2014 +0300
@@ -20,6 +20,7 @@
 #include "main.h"
 #include "basics.h"
 #include "misc/documentPointer.h"
+#include "glShared.h"
 
 #define LDOBJ(T)										\
 protected:												\
@@ -72,6 +73,7 @@
 	PROPERTY (private,		int,			id,				setID,			STOCK_WRITE)
 	PROPERTY (public,		int,			color,			setColor,		CUSTOM_WRITE)
 	PROPERTY (public,		bool,			isGLInit,		setGLInit,		STOCK_WRITE)
+	PROPERTY (private,		QColor,			randomColor,	setRandomColor,	STOCK_WRITE)
 
 	public:
 		// Object type codes.
@@ -172,6 +174,7 @@
 		// Get a description of a list of LDObjects
 		static QString describeObjects (const LDObjectList& objs);
 		static LDObject* fromID (int id);
+		LDPolygon* getPolygon();
 
 		// TODO: make these private!
 		// OpenGL list for this object
@@ -381,7 +384,7 @@
 	LDOBJ_COLORED
 	LDOBJ_SCEMANTIC
 	LDOBJ_HAS_MATRIX
-	PROPERTY (public, LDDocumentPointer, fileInfo, setFileInfo, STOCK_WRITE)
+	PROPERTY (public, LDDocumentPointer, fileInfo, setFileInfo, CUSTOM_WRITE)
 
 	public:
 		enum InlineFlag
@@ -401,7 +404,8 @@
 
 		// Inlines this subfile. Note that return type is an array of heap-allocated
 		// LDObject copies, they must be deleted manually.
-		LDObjectList inlineContents (InlineFlags flags);
+		LDObjectList inlineContents (bool deep, bool render);
+		QList<LDPolygon> inlinePolygons();
 
 	protected:
 		~LDSubfile();
--- a/src/macros.h	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/macros.h	Tue Apr 22 22:52:02 2014 +0300
@@ -17,6 +17,7 @@
  */
 
 #pragma once
+#include <type_traits>
 
 #ifndef __GNUC__
 # define __attribute__(X)
@@ -24,6 +25,20 @@
 
 // =============================================================================
 //
+#define countof(A) (safeCountOf<std::is_array<decltype(A)>::value, (sizeof A / sizeof *A)>())
+
+template<bool isArray, size_t elems>
+inline constexpr int safeCountOf() __attribute__ ((always_inline));
+
+template<bool isArray, size_t elems>
+inline constexpr int safeCountOf()
+{
+	static_assert (isArray, "parameter to countof must be an array");
+	return elems;
+}
+
+// =============================================================================
+//
 #define PROPERTY(ACCESS, TYPE, READ, WRITE, WRITETYPE)			\
 private:														\
 	TYPE m_##READ;												\
--- a/src/mainWindow.h	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/mainWindow.h	Tue Apr 22 22:52:02 2014 +0300
@@ -290,6 +290,7 @@
 		void slot_actionJumpTo();
 		void slot_actionSubfileSelection();
 		void slot_actionDrawAngles();
+		void slot_actionRandomColors();
 
 	protected:
 		void closeEvent (QCloseEvent* ev);
--- a/src/partDownloader.cc	Tue Apr 22 22:40:34 2014 +0300
+++ b/src/partDownloader.cc	Tue Apr 22 22:52:02 2014 +0300
@@ -446,13 +446,11 @@
 	}
 
 	// Try to load this file now.
-	LDDocument* f = openDocument (filePath(), false);
+	LDDocument* f = openDocument (filePath(), false, not isPrimary());
 
 	if (f == null)
 		return;
 
-	f->setImplicit (not isPrimary());
-
 	// Iterate through this file and check for errors. If there's any that stems
 	// from unknown file references, try resolve that by downloading the reference.
 	// This is why downloading a part may end up downloading multiple files, as
--- a/ui/ldforge.ui	Tue Apr 22 22:40:34 2014 +0300
+++ b/ui/ldforge.ui	Tue Apr 22 22:52:02 2014 +0300
@@ -107,6 +107,7 @@
     <addaction name="actionWireframe"/>
     <addaction name="actionBFCView"/>
     <addaction name="actionDrawAngles"/>
+    <addaction name="actionRandomColors"/>
     <addaction name="separator"/>
     <addaction name="actionSetOverlay"/>
     <addaction name="actionClearOverlay"/>
@@ -352,6 +353,7 @@
    <addaction name="actionAxes"/>
    <addaction name="actionWireframe"/>
    <addaction name="actionBFCView"/>
+   <addaction name="actionRandomColors"/>
    <addaction name="actionDrawAngles"/>
   </widget>
   <widget class="QToolBar" name="toolBar_7">
@@ -1350,6 +1352,18 @@
     <string>Draw angle information when drawing lines</string>
    </property>
   </action>
+  <action name="actionRandomColors">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="icon">
+    <iconset resource="../ldforge.qrc">
+     <normaloff>:/icons/random-colors.png</normaloff>:/icons/random-colors.png</iconset>
+   </property>
+   <property name="text">
+    <string>Random colors</string>
+   </property>
+  </action>
  </widget>
  <resources>
   <include location="../ldforge.qrc"/>

mercurial