src/linetypes/modelobject.cpp

Thu, 23 Feb 2017 20:06:22 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Thu, 23 Feb 2017 20:06:22 +0200
changeset 1158
35ceb74fe53e
parent 1149
502c866b8512
child 1161
e6a5ff67c057
permissions
-rw-r--r--

Made the GL renderer recieve information of object deletion via signals.

/*
 *  LDForge: LDraw parts authoring CAD
 *  Copyright (C) 2013 - 2017 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 <assert.h>
#include "../documentmanager.h"
#include "../linetypes/modelobject.h"
#include "../lddocument.h"
#include "../miscallenous.h"
#include "../mainwindow.h"
#include "../editHistory.h"
#include "../canvas.h"
#include "../colors.h"
#include "../glcompiler.h"
#include "edgeline.h"

// List of all LDObjects
QMap<qint32, LDObject*> g_allObjects;

enum { MAX_LDOBJECT_IDS = (1 << 24) };

#define LDOBJ_DEFAULT_CTOR(T,BASE) \
	T :: T (Model* model) : \
	    BASE {model} {}

// =============================================================================
// LDObject constructors
//
LDObject::LDObject (Model* model) :
    m_isHidden {false},
    m_isSelected {false},
    _model {model},
    m_coords {Origin}
{
	assert(_model != nullptr);

	// Let's hope that nobody goes to create 17 million objects anytime soon...
	static qint32 nextId = 1; // 0 shalt be null
	if (nextId < MAX_LDOBJECT_IDS)
		m_id = nextId++;
	else
		m_id = 0;

	if (m_id != 0)
		g_allObjects[m_id] = this;

	m_randomColor = QColor::fromHsv (rand() % 360, rand() % 256, rand() % 96 + 128);
}

LDSubfileReference::LDSubfileReference (Model* model) :
    LDMatrixObject (model) {}

LDOBJ_DEFAULT_CTOR (LDError, LDObject)
LDOBJ_DEFAULT_CTOR (LDBfc, LDObject)
LDOBJ_DEFAULT_CTOR (LDBezierCurve, LDObject)

LDObject::~LDObject()
{
	// Remove this object from the list of LDObjects
	g_allObjects.erase(g_allObjects.find(id()));
}

// =============================================================================
//
QString LDSubfileReference::asText() const
{
	QString val = format ("1 %1 %2 ", color(), position());
	val += transformationMatrix().toString();
	val += ' ';
	val += fileInfo()->name();
	return val;
}

QString LDBezierCurve::asText() const
{
	QString result = format ("0 !LDFORGE BEZIER_CURVE %1", color());

	// Add the coordinates
	for (int i = 0; i < 4; ++i)
		result += format (" %1", vertex (i));

	return result;
}

// =============================================================================
//
QString LDError::asText() const
{
	return contents();
}

// =============================================================================
//
QString LDBfc::asText() const
{
	return format ("0 BFC %1", statementToString());
}

// =============================================================================
//
// Swap this object with another.
//
void LDObject::swap (LDObject* other)
{
	if (model() == other->model())
		model()->swapObjects (this, other);
}

int LDObject::triangleCount() const
{
	return 0;
}

int LDSubfileReference::triangleCount() const
{
	return fileInfo()->triangleCount();
}

int LDObject::numVertices() const
{
	return 0;
}

// =============================================================================
//
LDBezierCurve::LDBezierCurve(const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3, Model* model) :
    LDObject {model}
{
	setVertex (0, v0);
	setVertex (1, v1);
	setVertex (2, v2);
	setVertex (3, v3);
}

// =============================================================================
//
void LDObject::setDocument (Model* model)
{
	_model = model;
}

// =============================================================================
//
static void TransformObject (LDObject* obj, Matrix transform, Vertex pos, LDColor parentcolor)
{
	switch (obj->type())
	{
	case LDObjectType::EdgeLine:
	case LDObjectType::ConditionalEdge:
	case LDObjectType::Triangle:
	case LDObjectType::Quadrilateral:
		for (int i = 0; i < obj->numVertices(); ++i)
		{
			Vertex v = obj->vertex (i);
			v.transform (transform, pos);
			obj->setVertex (i, v);
		}
		break;

	case LDObjectType::SubfileReference:
		{
			LDSubfileReference* ref = static_cast<LDSubfileReference*> (obj);
			Matrix newMatrix = transform * ref->transformationMatrix();
			Vertex newpos = ref->position();
			newpos.transform (transform, pos);
			ref->setPosition (newpos);
			ref->setTransformationMatrix (newMatrix);
		}
		break;

	default:
		break;
	}

	if (obj->color() == MainColor)
		obj->setColor (parentcolor);
}

// =============================================================================
// -----------------------------------------------------------------------------
void LDSubfileReference::inlineContents(Model& model, bool deep, bool render)
{
	Model inlined {this->model()->documentManager()};
	fileInfo()->inlineContents(inlined, deep, render);

	// Transform the objects
	for (LDObject* object : inlined)
		TransformObject(object, transformationMatrix(), position(), color());

	model.merge(inlined);
}

// =============================================================================
//
LDPolygon* LDObject::getPolygon()
{
	LDObjectType ot = type();
	int num = (ot == LDObjectType::EdgeLine)		? 2
			: (ot == LDObjectType::Triangle)	? 3
			: (ot == LDObjectType::Quadrilateral)		? 4
			: (ot == LDObjectType::ConditionalEdge)	? 5
			: 0;

	if (num == 0)
		return nullptr;

	LDPolygon* data = new LDPolygon;
	data->id = id();
	data->num = num;
	data->color = color().index();

	for (int i = 0; i < data->numVertices(); ++i)
		data->vertices[i] = vertex (i);

	return data;
}

LDColor LDObject::defaultColor() const
{
	return MainColor;
}

bool LDObject::isColored() const
{
	return true;
}

bool LDObject::isScemantic() const
{
	return true;
}

bool LDObject::hasMatrix() const
{
	return false;
}

// =============================================================================
//
QList<LDPolygon> LDSubfileReference::inlinePolygons()
{
	QList<LDPolygon> data = fileInfo()->inlinePolygons();

	for (LDPolygon& entry : data)
	{
		for (int i = 0; i < entry.numVertices(); ++i)
			entry.vertices[i].transform (transformationMatrix(), position());
	}

	return data;
}

// =============================================================================
//
// Index (i.e. line number) of this object
//
int LDObject::lineNumber() const
{
	if (model())
	{
		for (int i = 0; i < model()->size(); ++i)
		{
			if (model()->getObject(i) == this)
				return i;
		}
	}

	return -1;
}

// =============================================================================
//
// Object after this in the current file
//
LDObject* LDObject::next() const
{
	return model()->getObject(lineNumber() + 1);
}

// =============================================================================
//
// Object prior to this in the current file
//
LDObject* LDObject::previous() const
{
	return model()->getObject(lineNumber() - 1);
}

// =============================================================================
//
// Is the previous object INVERTNEXT?
//
bool LDObject::previousIsInvertnext (LDBfc*& ptr)
{
	LDObject* prev = previous();

	if (prev and prev->type() == LDObjectType::Bfc and static_cast<LDBfc*> (prev)->statement() == BfcStatement::InvertNext)
	{
		ptr = static_cast<LDBfc*> (prev);
		return true;
	}

	return false;
}

// =============================================================================
//
// Moves this object using the given vertex as a movement List
//
void LDObject::move (Vertex vect)
{
	if (hasMatrix())
	{
		LDMatrixObject* mo = static_cast<LDMatrixObject*> (this);
		mo->setPosition (mo->position() + vect);
	}
	else
	{
		for (int i = 0; i < numVertices(); ++i)
			setVertex (i, vertex (i) + vect);
	}
}

bool LDObject::isHidden() const
{
	return m_isHidden;
}

void LDObject::setHidden (bool value)
{
	m_isHidden = value;
}

bool LDObject::isSelected() const
{
	return m_isSelected;
}

qint32 LDObject::id() const
{
	return m_id;
}

LDColor LDObject::color() const
{
	return m_color;
}

QColor LDObject::randomColor() const
{
	return m_randomColor;
}

Model* LDObject::model() const
{
	return _model;
}

// =============================================================================
//
void LDObject::invert() {}
void LDBfc::invert() {}
void LDError::invert() {}

// =============================================================================
//
void LDSubfileReference::invert()
{
	if (model() == nullptr)
		return;

	// Check whether subfile is flat
	int axisSet = (1 << X) | (1 << Y) | (1 << Z);
	Model model {this->model()->documentManager()};
	fileInfo()->inlineContents(model, true, false);

	for (LDObject* obj : model.objects())
	{
		for (int i = 0; i < obj->numVertices(); ++i)
		{
			Vertex const& vrt = obj->vertex (i);

			if (axisSet & (1 << X) and vrt.x() != 0.0)
				axisSet &= ~(1 << X);

			if (axisSet & (1 << Y) and vrt.y() != 0.0)
				axisSet &= ~(1 << Y);

			if (axisSet & (1 << Z) and vrt.z() != 0.0)
				axisSet &= ~(1 << Z);
		}

		if (axisSet == 0)
			break;
	}

	if (axisSet != 0)
	{
		// Subfile has all vertices zero on one specific plane, so it is flat.
		// Let's flip it.
		Matrix matrixModifier = Matrix::identity;

		if (axisSet & (1 << X))
			matrixModifier(0, 0) = -1;

		if (axisSet & (1 << Y))
			matrixModifier(1, 1) = -1;

		if (axisSet & (1 << Z))
			matrixModifier(2, 2) = -1;

		setTransformationMatrix (transformationMatrix() * matrixModifier);
		return;
	}

	// Subfile is not flat. Resort to invertnext.
	int idx = lineNumber();

	if (idx > 0)
	{
		LDBfc* bfc = dynamic_cast<LDBfc*> (previous());

		if (bfc and bfc->statement() == BfcStatement::InvertNext)
		{
			// This is prefixed with an invertnext, thus remove it.
			this->model()->remove(bfc);
			return;
		}
	}

	// Not inverted, thus prefix it with a new invertnext.
	this->model()->emplaceAt<LDBfc>(idx, BfcStatement::InvertNext);
}

// =============================================================================
//
void LDBezierCurve::invert()
{
	// A Bézier curve's control points probably need to be, though.
	Vertex tmp = vertex (1);
	setVertex (1, vertex (0));
	setVertex (0, tmp);
	tmp = vertex (3);
	setVertex (3, vertex (2));
	setVertex (2, tmp);
}

// =============================================================================
//
LDObject* LDObject::fromID(qint32 id)
{
	return g_allObjects.value(id);
}

// =============================================================================
//
void LDObject::setColor (LDColor color)
{
	changeProperty(&m_color, color);
}

// =============================================================================
//
// Get a vertex by index
//
const Vertex& LDObject::vertex (int i) const
{
	return m_coords[i];
}

// =============================================================================
//
// Set a vertex to the given value
//
void LDObject::setVertex (int i, const Vertex& vert)
{
	changeProperty(&m_coords[i], vert);
}

LDMatrixObject::LDMatrixObject (Model* model) :
    LDObject (model),
	m_position (Origin) {}

LDMatrixObject::LDMatrixObject (const Matrix& transform, const Vertex& pos, Model* model) :
    LDObject (model),
	m_position (pos),
	m_transformationMatrix (transform) {}

void LDMatrixObject::setCoordinate (const Axis ax, double value)
{
	Vertex v = position();

	switch (ax)
	{
		case X: v.setX (value); break;
		case Y: v.setY (value); break;
		case Z: v.setZ (value); break;
	}

	setPosition (v);
}

const Vertex& LDMatrixObject::position() const
{
	return m_position;
}

// =============================================================================
//
void LDMatrixObject::setPosition (const Vertex& a)
{
	changeProperty(&m_position, a);
}

// =============================================================================
//
const Matrix& LDMatrixObject::transformationMatrix() const
{
	return m_transformationMatrix;
}

void LDMatrixObject::setTransformationMatrix (const Matrix& val)
{
	changeProperty(&m_transformationMatrix, val);
}

LDError::LDError (QString contents, QString reason, Model* model) :
    LDObject (model),
	m_contents (contents),
	m_reason (reason) {}

QString LDError::reason() const
{
	return m_reason;
}

QString LDError::contents() const
{
	return m_contents;
}

QString LDError::fileReferenced() const
{
	return m_fileReferenced;
}

void LDError::setFileReferenced (QString value)
{
	m_fileReferenced = value;
}

LDBfc::LDBfc (const BfcStatement type, Model* model) :
    LDObject {model},
    m_statement {type} {}

BfcStatement LDBfc::statement() const
{
	return m_statement;
}

void LDBfc::setStatement (BfcStatement value)
{
	m_statement = value;
}

QString LDBfc::statementToString() const
{
	return LDBfc::statementToString (statement());
}

QString LDBfc::statementToString (BfcStatement statement)
{
	static const char* statementStrings[] =
	{
		"CERTIFY CCW",
		"CCW",
		"CERTIFY CW",
		"CW",
		"NOCERTIFY",
		"INVERTNEXT",
		"CLIP",
		"CLIP CCW",
		"CLIP CW",
		"NOCLIP",
	};

	if ((int) statement >= 0 and (int) statement < countof (statementStrings))
		return QString::fromLatin1 (statementStrings[(int) statement]);
	else
		return "";
}

Vertex LDBezierCurve::pointAt (qreal t) const
{
	if (t >= 0.0 and t <= 1.0)
	{
		Vertex result;
		result += pow (1.0 - t, 3) * vertex (0);
		result += (3 * pow (1.0 - t, 2) * t) * vertex (2);
		result += (3 * (1.0 - t) * pow (t, 2)) * vertex (3);
		result += pow (t, 3) * vertex (1);
		return result;
	}
	else
		return Vertex();
}

void LDBezierCurve::rasterize(Model& model, int segments)
{
	QVector<LDPolygon> polygons = rasterizePolygons(segments);

	for (LDPolygon& poly : polygons)
	{
		LDEdgeLine* line = model.emplace<LDEdgeLine>(poly.vertices[0], poly.vertices[1]);
		line->setColor (poly.color);
	}
}

QVector<LDPolygon> LDBezierCurve::rasterizePolygons(int segments)
{
	QVector<LDPolygon> result;
	QVector<Vertex> parms;
	parms.append (pointAt (0.0));

	for (int i = 1; i < segments; ++i)
		parms.append (pointAt (double (i) / segments));

	parms.append (pointAt (1.0));
	LDPolygon poly;
	poly.color = color().index();
	poly.id = id();
	poly.num = 2;

	for (int i = 0; i < segments; ++i)
	{
		poly.vertices[0] = parms[i];
		poly.vertices[1] = parms[i + 1];
		result << poly;
	}

	return result;
}

LDSubfileReference::LDSubfileReference(LDDocument* reference, const Matrix& transformationMatrix,
                                       const Vertex& position, Model* model) :
    LDMatrixObject {transformationMatrix, position, model},
    m_fileInfo {reference} {}

// =============================================================================
//
LDDocument* LDSubfileReference::fileInfo() const
{
	return m_fileInfo;
}

void LDSubfileReference::setFileInfo (LDDocument* newReferee)
{
	changeProperty(&m_fileInfo, newReferee);

	if (model())
		model()->recountTriangles();

	// If it's an immediate subfile reference (i.e. this subfile is in an opened document), we need to pre-compile the
	// GL polygons for the document if they don't exist already.
	if (newReferee and
		newReferee->isFrozen() == false and
		newReferee->polygonData().isEmpty())
	{
		newReferee->initializeCachedData();
	}
}

void LDObject::getVertices (QSet<Vertex>& verts) const
{
	for (int i = 0; i < numVertices(); ++i)
		verts.insert(vertex(i));
}

void LDSubfileReference::getVertices (QSet<Vertex>& verts) const
{
	verts.unite(fileInfo()->inlineVertices());
}

QString LDObject::objectListText() const
{
	if (numVertices() > 0)
	{
		QString result;

		for (int i = 0; i < numVertices(); ++i)
		{
			if (i != 0)
				result += ", ";

			result += vertex(i).toString (true);
		}

		return result;
	}
	else
	{
		return typeName();
	}
}

QString LDError::objectListText() const
{
	return "ERROR: " + asText();
}

QString LDSubfileReference::objectListText() const
{
	QString result = format ("%1 %2, (", fileInfo()->getDisplayName(), position().toString(true));

	for (int i = 0; i < 9; ++i)
		result += format("%1%2", transformationMatrix().value(i), (i != 8) ? " " : "");

	result += ')';
	return result;
}

QString LDBfc::objectListText() const
{
	return statementToString();
}

mercurial