src/gl/compiler.cpp

Fri, 01 Jul 2022 16:46:43 +0300

author
Teemu Piippo <teemu.s.piippo@gmail.com>
date
Fri, 01 Jul 2022 16:46:43 +0300
changeset 312
2637134bc37c
parent 309
d862721d19a3
child 333
07e65a4c6611
permissions
-rw-r--r--

Fix right click to delete not really working properly
Instead of removing the point that had been added, it would remove
the point that is being drawn, which would cause it to overwrite the
previous point using the new point, causing a bit of a delay

/*
 *  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 <QMessageBox>
#include "src/documentmanager.h"
#include "src/invert.h"
#include "src/gl/compiler.h"

constexpr char VERTEX_SHADER_SOURCE[] = R"(
#version 330 core

layout(location=0) in vec3 position;
layout(location=1) in vec4 color;
layout(location=2) in vec3 normal;
layout(location=3) in vec3 pickcolor;
layout(location=4) in float selected;
out vec4 vColor;
out vec3 vFragPos;
out vec3 vNormal;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform int fragmentStyle;
uniform vec4 selectedColor;
uniform int highlighted;

const int FRAGSTYLE_Normal = 0;
const int FRAGSTYLE_BfcGreen = 1;
const int FRAGSTYLE_BfcRed = 2;
const int FRAGSTYLE_Random = 3;
const int FRAGSTYLE_Id = 4;
const int FRAGSTYLE_Black = 5;

void main()
{
	mat3 normalMatrix = transpose(inverse(mat3(modelMatrix)));
	vNormal = normalize(normalMatrix * normal);
	if (fragmentStyle == FRAGSTYLE_Id)
	{
		vColor = vec4(pickcolor, 1.0);
	}
	else
	{
		if (fragmentStyle == FRAGSTYLE_BfcGreen)
		{
			vColor = vec4(0.2, 0.9, 0.2, 1.0);
		}
		else if (fragmentStyle == FRAGSTYLE_BfcRed)
		{
			vColor = vec4(0.9, 0.2, 0.2, 1.0);
		}
		else if (fragmentStyle == FRAGSTYLE_Black)
		{
			vColor = vec4(0.0, 0.0, 0.0, 1.0);
		}
		else
		{
			vColor = color;
		}
/*
		if (highlighted == id)
		{
			vColor = (vColor + vec4(selectedColor, 1.0) * 0.6) / 1.6;
		}
*/
		vColor = (1 - selected) * vColor + selected * selectedColor;
	}
	vFragPos = vec3(modelMatrix * vec4(position, 1.0));
	gl_Position = projectionMatrix * viewMatrix * vec4(vFragPos, 1.0);
}
)";

constexpr char FRAGMENT_SHADER_SOURCE[] = R"(
#version 330 core

in vec4 vColor;
in vec3 vFragPos;
in vec3 vNormal;
out vec4 fColor;
const vec3 lightPos = vec3(0.5, 0.5, 0.5);
const vec4 lightColor = vec4(1.0, 1.0, 1.0, 1.0);
const float ambientStrength = 0.7;
uniform bool useLighting;

void main()
{
	if (useLighting)
	{
		vec4 ambient = ambientStrength * lightColor;
		vec3 lightDirection = normalize(lightPos - vFragPos);
		vec4 diffuse = max(dot(vNormal, lightDirection), 0.0) * lightColor;
		fColor = (ambient + diffuse) * vColor;
	}
	else
	{
		fColor = vColor;
	}
}
)";

template<typename Fn>
constexpr void pointsToRender(const PolygonElement& element, Fn func)
{
	visitPolygon<void>(
		[&func](const LineSegment& edge)
		{
			func(edge.p1, glm::vec3{});
			func(edge.p2, glm::vec3{});
		},
		[&func](const Triangle& tri)
		{
			func(tri.p1, normalVector({tri.p3, tri.p1, tri.p2}));
			func(tri.p2, normalVector({tri.p1, tri.p2, tri.p3}));
			func(tri.p3, normalVector({tri.p2, tri.p3, tri.p1}));
		},
		[&func](const Quadrilateral& quad)
		{
			func(quad.p1, normalVector({quad.p4, quad.p1, quad.p2}));
			func(quad.p2, normalVector({quad.p1, quad.p2, quad.p3}));
			func(quad.p3, normalVector({quad.p2, quad.p3, quad.p4}));
			func(quad.p4, normalVector({quad.p3, quad.p4, quad.p1}));
		},
		[&func](const ConditionalEdge& cedge)
		{
			func(cedge.p1, glm::vec3{});
			func(cedge.p2, glm::vec3{});
		},
		element);
}

void gl::buildShaders(
	QOpenGLShaderProgram* shaderProgram,
	const char* vertexShaderSource,
	const char* fragmentShaderSource)
{
	shaderProgram->create();
	const bool vertexShaderCompiled = shaderProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
	QString log;
	if (not vertexShaderCompiled)
	{
		log += "\n" + QObject::tr("Vertex shader:") + "\n" + shaderProgram->log();
	}
	const bool fragmentShaderCompiled = shaderProgram->addShaderFromSourceCode(
		QOpenGLShader::Fragment,
		fragmentShaderSource);
	if (not fragmentShaderCompiled)
	{
		log += "\n" + QObject::tr("Fragment shader:") + "\n" + shaderProgram->log();
	}
	if (not vertexShaderCompiled or not fragmentShaderCompiled)
	{
		QMessageBox::critical(
			nullptr,
			QObject::tr("Shader compile error"),
			QObject::tr("Could not compile shaders.") + "\n" + log);
		std::exit(-1);
	}
	const bool linkSuccessful = shaderProgram->link();
	if (not linkSuccessful)
	{
		QMessageBox::critical(
			nullptr,
			QObject::tr("Shader link error"),
			QObject::tr("Could not link shaders: %1").arg(shaderProgram->log())
		);
	}
}

void gl::initializeModelShaders(gl::ModelShaders *modelShaders)
{
	if (not modelShaders->initialized) {
		for (auto& shader : modelShaders->shaderObjects) {
			shader.program = std::make_unique<QOpenGLShaderProgram>();
			gl::buildShaders(shader.program.get(), VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);
			shader.program->bind();
			shader.buffer.create();
			shader.buffer.bind();
			shader.buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
			shader.vertexArray.create();
			shader.vertexArray.bind();
			for (int k : {0, 1, 2, 3, 4}) {
				shader.program->enableAttributeArray(k);
			}
			using Vertex = ModelShaders::Vertex;
			constexpr std::size_t stride = sizeof(Vertex);
			shader.program->setAttributeBuffer(0, GL_FLOAT, offsetof(Vertex, position), 3, stride);
			shader.program->setAttributeBuffer(1, GL_FLOAT, offsetof(Vertex, color), 4, stride);
			shader.program->setAttributeBuffer(2, GL_FLOAT, offsetof(Vertex, normal), 3, stride);
			shader.program->setAttributeBuffer(3, GL_FLOAT, offsetof(Vertex, pickcolor), 3, stride);
			shader.program->setAttributeBuffer(4, GL_FLOAT, offsetof(Vertex, selected), 1, stride);
			shader.vertexArray.release();
			shader.buffer.release();
			shader.program->release();
		}
		modelShaders->initialized = true;
	}
}

static constexpr gl::ArrayClass classifyPolygon(const PolygonElement& element)
{
	return visitPolygon<gl::ArrayClass>(
		[](const LineSegment&) { return gl::ArrayClass::Lines; },
		[](const Triangle&) { return gl::ArrayClass::Triangles; },
		[](const Quadrilateral&) { return gl::ArrayClass::Quads; },
		[](const ConditionalEdge&) { return gl::ArrayClass::ConditionalLines; },
		element);
}

template<typename Fn>
void iterateModelPolygons(Model* model, DocumentManager* context, Fn&& fn)
{
	PolygonCache* const cache = findPolygonCacheForModel(model, context);
	if (cache != nullptr) {
		recacheIfNeeded(cache, model, context);
		for (const WithId<PolygonElement>& polygon : cache->polygons) {
			fn(polygon);
		}
	}
}

static QColor getColorForPolygon(
	const PolygonElement& polygon,
	const gl::RenderPreferences& preferences,
	const ColorTable& colorTable)
{
	QColor color;
	// For normal colors, use the polygon's color.
	if (polygon.color == MAIN_COLOR)
	{
		color = preferences.mainColor;
	}
	else if (polygon.color == EDGE_COLOR)
	{
		// Edge color is black, unless we have a dark background, in which case lines need to be bright.
		color = luma(preferences.backgroundColor) > (40.0 / 256.0) ? Qt::black : Qt::white;
	}
	else
	{
		// Not main or edge color, use the polygon's color as is.
		color = colorFace(polygon.color, colorTable).value_or(Qt::black);
	}
	return color;
}

/**
 * @brief Computes the minimum bounding box for a model
 */
BoundingBox gl::boundingBoxForModel(Model* model, DocumentManager* context)
{
	BoundingBox result = emptyBoundingBox;
	iterateModelPolygons(model, context, [&](const PolygonElement& polygon)
	{
		visitPoints([&result](const glm::vec3& p) {
			addPointToBox(result, p);
		}, polygon);
	});
	return result;
}

/**
 * @brief gl::build Creates GL vertices for objects in the model and buffers them to shaders.
 */
void gl::build(
	gl::ModelShaders* shaders,
	Model* model,
	const ColorTable& colorTable,
	DocumentManager* context,
	const gl::RenderPreferences& preferences)
{
	for (gl::ModelShaders::ShaderObject& shader : shaders->shaderObjects) {
		shader.cachedData.clear();
	}
	iterateModelPolygons(model, context, [&](const WithId<PolygonElement>& polygon)
	{
		const int index = static_cast<int>(classifyPolygon(polygon));
		std::vector<gl::ModelShaders::Vertex>& vertexBuffer = shaders->shaderObjects[index].cachedData;
		const QColor color = getColorForPolygon(polygon, preferences, colorTable);
		pointsToRender(polygon, [&](const glm::vec3& point, const glm::vec3& normal){
			gl::ModelShaders::Vertex& vertex = vertexBuffer.emplace_back();
			vertex.position = point;
			vertex.normal = normal;
			vertex.color = glm::vec4{color.redF(), color.greenF(), color.blueF(), color.alphaF()};
			vertex.id = polygon.id.value;
			vertex.pickcolor = idToColor(polygon.id.value);
		});
	});
	for (gl::ModelShaders::ShaderObject& shader : shaders->shaderObjects)
	{
		shader.vertexCount = shader.cachedData.size();
		shader.buffer.bind();
		const int bytes = static_cast<int>(shader.cachedData.size() * sizeof shader.cachedData[0]);
		shader.buffer.allocate(shader.cachedData.data(), bytes);
		shader.buffer.release();
	}
}

ElementId gl::idFromUcharColor(const std::array<GLubyte, 3>& data)
{
	return {
		static_cast<std::int32_t>(data[0]) |
		static_cast<std::int32_t>(data[1]) << 8 |
		static_cast<std::int32_t>(data[2]) << 16
	};
}

void gl::bindModelShaderVertexArray(gl::ModelShaders* shaders, gl::ArrayClass arrayClass)
{
	ModelShaders::ShaderObject& shaderObject = shaders->shaderObjects[static_cast<int>(arrayClass)];
	shaderObject.vertexArray.bind();
	shaderObject.program->bind();
}

void gl::releaseModelShaderVertexArray(gl::ModelShaders* shaders, gl::ArrayClass arrayClass)
{
	ModelShaders::ShaderObject& shaderObject = shaders->shaderObjects[static_cast<int>(arrayClass)];
	shaderObject.program->release();
	shaderObject.vertexArray.release();
}

void gl::setModelShaderSelectedObjects(gl::ModelShaders* shaders, const QSet<ElementId> &ids)
{
	for (ModelShaders::ShaderObject& object : shaders->shaderObjects)
	{
		std::vector<ModelShaders::Vertex>& vector = object.cachedData;
		for (ModelShaders::Vertex& vertex : vector)
		{
			vertex.selected = (ids.contains({vertex.id})) ? 1 : 0;
		}
		const GLsizeiptr size = static_cast<int>(vector.size() * sizeof vector[0]);
		object.buffer.bind();
		glBufferSubData(GL_ARRAY_BUFFER, 0, size, vector.data());
		object.buffer.release();
	}
}

std::size_t gl::vertexCount(const gl::ModelShaders* shaders, const gl::ArrayClass arrayClass)
{
	return shaders->shaderObjects[static_cast<int>(arrayClass)].vertexCount;
}

mercurial