src/glCompiler.cpp

Thu, 11 Jan 2018 15:30:30 +0200

author
Santeri Piippo
date
Thu, 11 Jan 2018 15:30:30 +0200
changeset 1232
7eb8b59577d0
parent 1231
ce0c9f2e6b9c
permissions
-rw-r--r--

renderer rework

/*
 *  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/>.
 */

#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 "guiutilities.h"
#include "documentmanager.h"

static const QColor bfcFrontColor {64, 192, 80};
static const QColor bfcBackColor {208, 64, 64};

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" },
};

ConfigOption(QString SelectColorBlend = "#0080FF")

void CheckGLErrorImpl(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(LDDocument* document, GLRenderer* renderer) :
	document {document},
	renderer {renderer} {}


void GLCompiler::initialize()
{
	initializeOpenGLFunctions();
	glGenBuffers(NumVbos, &vboIndices[0]);
	CHECK_GL_ERROR();
}


GLCompiler::~GLCompiler()
{
	glDeleteBuffers(NumVbos, &vboIndices[0]);
	CHECK_GL_ERROR();
}

/*
 * Returns a color that represents this object index. There are 256³ possible
 * colors, so that many indices can be bijectively addressed with colorss.
 */
QColor GLCompiler::indexColorForID(int id) const
{
	int red = (id / 0x10000) % 0x100;
	int green = (id / 0x100) % 0x100;
	int blue = id % 0x100;

	return {red, green, blue};
}


QColor blend(QColor baseColor, QColor blendColor, double intensity)
{
	double red = baseColor.red();
	red += blendColor.red() * intensity;
	red /= (intensity + 1.0);
	double green = baseColor.green();
	green += blendColor.green() * intensity;
	green /= (intensity + 1.0);
	double blue = baseColor.blue();
	blue += blendColor.blue() * intensity;
	blue /= (intensity + 1.0);
	return {int(round(red)), int(round(green)), int(round(blue))};
}


QColor GLCompiler::getColorForPolygon(LDPolygon& poly, LDObject* topobj, ComplementVboType complement) const
{
	QColor color;

	switch(complement)
	{
	case SurfacesVboComplement:
	case NumVboComplements:
		return {};

	case BfcFrontColorsVboComplement:
		color = bfcFrontColor;
		break;

	case BfcBackColorsVboComplement:
		color = bfcBackColor;
		break;

	case PickColorsVboComplement:
		return indexColorForID(topobj->id());

	case RandomColorsVboComplement:
		color = topobj->randomColor();
		break;

	case NormalColorsVboComplement:
		if (poly.color == MainColor)
		{
			if (topobj->color() == MainColor)
				color = mainColorRepresentation();
			else
				color = topobj->color().faceColor();
		}
		else if (poly.color == EdgeColor)
		{
			if (luma(config->backgroundColor()) > 40)
				color = Qt::black;
			else
				color = Qt::white;
		}
		else
		{
			LDColor colorInfo = poly.color;

			if (colorInfo.isValid())
				color = colorInfo.faceColor();
		}
		break;
	}

	if (not color.isValid())
	{
		// The color was unknown. Use main color to make the polygon at least
		// not appear pitch-black.
		if (poly.num != 2 and poly.num != 5)
			color = mainColorRepresentation();
		else
			color = Qt::black;

		// Warn about the unknown color, but only once.
		static QSet<int> warnedColors;
		if (not warnedColors.contains(poly.color))
		{
			print("Unknown color %1!\n", poly.color);
			warnedColors.insert(poly.color);
		}

		return color;
	}

	double blendAlpha = 0.0;

	if (topobj->isSelected())
		blendAlpha = 1.0;
	else if (topobj == renderer->objectAtCursor())
		blendAlpha = 0.5;

	color = blend(color, config->selectColorBlend(), blendAlpha);
	return color;
}


void GLCompiler::needMerge()
{
	for (bool& changed_flag : this->vboChanged)
		changed_flag = true;
}


void GLCompiler::stageForCompilation(LDObject* obj)
{
	stagedObjects << obj;
}


void GLCompiler::unstage(LDObject* obj)
{
	stagedObjects.remove(obj);
}


void GLCompiler::compileDocument()
{
	for (LDObject* object : this->document->objects())
		compileObject(object);
}


void GLCompiler::compileStaged()
{
	for (QSetIterator<LDObject*> it(stagedObjects); it.hasNext();)
		compileObject(it.next());

	stagedObjects.clear();
}


void GLCompiler::prepareVBO(int vbonum)
{
	// Compile anything that still awaits it
	compileStaged();

	if (vboChanged[vbonum])
	{
		QVector<GLfloat> vbodata;

		for (auto it = objectInfo.begin(); it != objectInfo.end();)
		{
			if (it.key() == nullptr)
			{
				it = objectInfo.erase(it);
			}
			else
			{
				if (not it.key()->isHidden())
					vbodata += it->data[vbonum];

				++it;
			}
		}

		glBindBuffer(GL_ARRAY_BUFFER, vboIndices[vbonum]);
		glBufferData(
			GL_ARRAY_BUFFER,
			vbodata.size() * sizeof(GLfloat),
			vbodata.constData(),
			GL_STATIC_DRAW);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		CHECK_GL_ERROR();
		vboChanged[vbonum] = false;
		vboSizes[vbonum] = vbodata.size();
	}
}


void GLCompiler::dropObjectInfo(LDObject* obj)
{
	if (objectInfo.contains(obj))
	{
		objectInfo.remove(obj);
		needMerge();
	}
}


void GLCompiler::compileObject(LDObject* obj)
{
	if (obj == nullptr or obj->document() == nullptr or obj->document()->isCache())
		return;

	ObjectVBOInfo info;
	dropObjectInfo(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 OBJ_Triangle:
	case OBJ_Quad:
	case OBJ_Line:
	case OBJ_CondLine:
		{
			LDPolygon* poly = obj->getPolygon();
			poly->id = obj->id();
			compilePolygon(*poly, obj, &info);
			delete poly;
			break;
		}

	case OBJ_SubfileReference:
		{
			LDSubfileReference* ref = static_cast<LDSubfileReference*>(obj);
			auto data = ref->inlinePolygons();

			for (LDPolygon& poly : data)
			{
				poly.id = obj->id();
				compilePolygon(poly, obj, &info);
			}
			break;
		}

	case OBJ_BezierCurve:
		{
			LDBezierCurve* curve = static_cast<LDBezierCurve*>(obj);
			for (LDPolygon& poly : curve->rasterizePolygons())
			{
				poly.id = obj->id();
				compilePolygon(poly, obj, &info);
			}
		}
		break;

	default:
		break;
	}

	objectInfo[obj] = info;
	needMerge();
}


void GLCompiler::compilePolygon(LDPolygon& poly, LDObject* topobj, ObjectVBOInfo* objinfo)
{
	SurfaceVboType surface;
	int numverts;

	switch(poly.num)
	{
	case 2:	surface = LinesVbo;				numverts = 2; break;
	case 3:	surface = TrianglesVbo;			numverts = 3; break;
	case 4:	surface = QuadsVbo;				numverts = 4; break;
	case 5:	surface = ConditionalLinesVbo;	numverts = 2; break;
	default: return;
	}

	for (ComplementVboType complement = FirstVboComplement; complement < NumVboComplements; ++complement)
	{
		const int vbonum = vboNumber(surface, complement);
		QVector<GLfloat>& vbodata = objinfo->data[vbonum];
		const QColor color = getColorForPolygon(poly, topobj, complement);

		for (int vert = 0; vert < numverts; ++vert)
		{
			if (complement == SurfacesVboComplement)
			{
				// 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;
			}
		}
	}
}

int GLCompiler::vboNumber(SurfaceVboType surface, ComplementVboType complement)
{
	return (surface * NumVboComplements) + complement;
}


GLuint GLCompiler::vbo(int vbonum) const
{
	return vboIndices[vbonum];
}


int GLCompiler::vboSize(int vbonum) const
{
	return vboSizes[vbonum];
}

mercurial