src/ldObject.cpp

Wed, 10 Jan 2018 22:54:09 +0200

author
Santeri Piippo
date
Wed, 10 Jan 2018 22:54:09 +0200
changeset 1226
d1199d965235
parent 1224
5a31b6d4bf81
child 1231
ce0c9f2e6b9c
permissions
-rw-r--r--

drop Qt4 support

/*
 *  LDForge: LDraw parts authoring CAD
 *  Copyright (C) 2013 - 2018 Teemu Piippo
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "main.h"
#include "ldObject.h"
#include "ldDocument.h"
#include "miscallenous.h"
#include "mainwindow.h"
#include "editHistory.h"
#include "glRenderer.h"
#include "colors.h"
#include "glCompiler.h"

ConfigOption(QString DefaultName = "")
ConfigOption(QString DefaultUser = "")
ConfigOption(bool UseCaLicense = true)

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

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

#define LDOBJ_DEFAULT_CTOR(T,BASE) \
	T :: T(LDDocument* document) : \
		BASE(document) {}

// =============================================================================
// LDObject constructors
//
LDObject::LDObject(LDDocument* document) :
	m_isHidden(false),
	m_isSelected(false),
	m_isDestroyed(false),
	m_document(nullptr)
{
	if (document)
		document->addObject(this);

	memset(m_coords, 0, sizeof m_coords);

	// Let's hope that nobody goes to create 17 million objects anytime soon...
	static int32 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(LDDocument* document) :
	LDMatrixObject(document) {}

LDOBJ_DEFAULT_CTOR(LDEmpty, LDObject)
LDOBJ_DEFAULT_CTOR(LDError, LDObject)
LDOBJ_DEFAULT_CTOR(LDLine, LDObject)
LDOBJ_DEFAULT_CTOR(LDTriangle, LDObject)
LDOBJ_DEFAULT_CTOR(LDCondLine, LDLine)
LDOBJ_DEFAULT_CTOR(LDQuad, LDObject)
LDOBJ_DEFAULT_CTOR(LDOverlay, LDObject)
LDOBJ_DEFAULT_CTOR(LDBfc, LDObject)
LDOBJ_DEFAULT_CTOR(LDComment, LDObject)
LDOBJ_DEFAULT_CTOR(LDBezierCurve, LDObject)

LDObject::~LDObject()
{
	if (not m_isDestroyed)
		print("Warning: Object #%1(%2) was not destroyed before being deleted\n", id(), this);
}

// =============================================================================
//
QString LDComment::asText() const
{
	return format("0 %1", text());
}

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

// =============================================================================
//
QString LDLine::asText() const
{
	QString val = format("2 %1", color());

	for (int i = 0; i < 2; ++i)
		val += format(" %1", vertex(i));

	return val;
}

// =============================================================================
//
QString LDTriangle::asText() const
{
	QString val = format("3 %1", color());

	for (int i = 0; i < 3; ++i)
		val += format(" %1", vertex(i));

	return val;
}

// =============================================================================
//
QString LDQuad::asText() const
{
	QString val = format("4 %1", color());

	for (int i = 0; i < 4; ++i)
		val += format(" %1", vertex(i));

	return val;
}

// =============================================================================
//
QString LDCondLine::asText() const
{
	QString val = format("5 %1", color());
	
	// Add the coordinates
	for (int i = 0; i < 4; ++i)
		val += format(" %1", vertex(i));

	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 LDEmpty::asText() const
{
	return "";
}

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

// =============================================================================
//
QList<LDTriangle*> LDQuad::splitToTriangles()
{
	// Create the two triangles based on this quadrilateral:
	// 0---3       0---3    3
	// |   |       |  /    /|
	// |   |  ==>  | /    / |
	// |   |       |/    /  |
	// 1---2       1    1---2
	LDTriangle* tri1(new LDTriangle(vertex(0), vertex(1), vertex(3)));
	LDTriangle* tri2(new LDTriangle(vertex(1), vertex(2), vertex(3)));

	// The triangles also inherit the quad's color
	tri1->setColor(color());
	tri2->setColor(color());

	return {tri1, tri2};
}

// =============================================================================
//
// Replace this LDObject with another LDObject. Object is deleted in the process.
//
void LDObject::replace(LDObject* other)
{
	int idx = lineNumber();

	if (idx != -1)
	{
		// Replace the instance of the old object with the new object
		document()->setObject(idx, other);

		// Remove the old object
		destroy();
	}
}

void LDObject::replace(const LDObjectList& others)
{
	int idx = lineNumber();

	if (idx != -1 and not others.isEmpty())
	{
		for (int i = 1; i < others.size(); ++i)
			document()->insertObj(idx + i, others[i]);

		document()->setObject(idx, others[0]);
		destroy();
	}
}

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

// =============================================================================
//
LDLine::LDLine(Vertex v1, Vertex v2, LDDocument* document) :
	LDObject(document)
{
	setVertex(0, v1);
	setVertex(1, v2);
}

// =============================================================================
//
LDTriangle::LDTriangle(const Vertex& v1, const Vertex& v2, const Vertex& v3, LDDocument* document) :
	LDObject(document)
{
	setVertex(0, v1);
	setVertex(1, v2);
	setVertex(2, v3);
}

// =============================================================================
//
LDQuad::LDQuad(const Vertex& v1, const Vertex& v2, const Vertex& v3, const Vertex& v4, LDDocument* document) :
	LDObject(document)
{
	setVertex(0, v1);
	setVertex(1, v2);
	setVertex(2, v3);
	setVertex(3, v4);
}

// =============================================================================
//
LDCondLine::LDCondLine(const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3, LDDocument* document) :
	LDLine(document)
{
	setVertex(0, v0);
	setVertex(1, v1);
	setVertex(2, v2);
	setVertex(3, v3);
}

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

// =============================================================================
//
// Deletes this object
//
void LDObject::destroy()
{
	deselect();

	// If this object was associated to a file, remove it off it now
	if (document())
		document()->forgetObject(this);

	// Delete the GL lists
	if (g_win)
		g_win->renderer()->forgetObject(this);

	// Remove this object from the list of LDObjects
	g_allObjects.erase(g_allObjects.find(id()));
	m_isDestroyed = true;
	delete this;
}

// =============================================================================
//
void LDObject::setDocument(LDDocument* document)
{
	m_document = document;

	if (document == nullptr)
		m_isSelected = false;
}

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

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

	default:
		break;
	}

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

// =============================================================================
// -----------------------------------------------------------------------------
LDObjectList LDSubfileReference::inlineContents(bool deep, bool render)
{
	LDObjectList objs = fileInfo()->inlineContents(deep, render);

	// Transform the objects
	for (LDObject* obj : objs)
		TransformObject(obj, transform(), position(), color());

	return objs;
}

// =============================================================================
//
LDPolygon* LDObject::getPolygon()
{
	LDObjectType ot = type();
	int num = (ot == OBJ_Line)		? 2
			:(ot == OBJ_Triangle)	? 3
			:(ot == OBJ_Quad)		? 4
			:(ot == OBJ_CondLine)	? 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;
}

// =============================================================================
//
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(transform(), position());
	}

	return data;
}

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

	return -1;
}

// =============================================================================
//
void LDObject::moveObjects(LDObjectList objs, const bool up)
{
	if (objs.isEmpty())
		return;

	// If we move down, we need to iterate the array in reverse order.
	long const start = up ? 0 :(objs.size() - 1);
	long const end = up ? objs.size() : -1;
	long const incr = up ? 1 : -1;
	LDObjectList objsToCompile;
	LDDocument* file = objs[0]->document();

	for (long i = start; i != end; i += incr)
	{
		LDObject* obj = objs[i];

		long const idx = obj->lineNumber();
		long const target = idx +(up ? -1 : 1);

		if ((up and idx == 0) or(not up and idx == (long) file->objects().size() - 1l))
		{
			// One of the objects hit the extrema. If this happens, this should be the first
			// object to be iterated on. Thus, nothing has changed yet and it's safe to just
			// abort the entire operation.
			return;
		}

		objsToCompile << obj;
		objsToCompile << file->getObject(target);

		obj->swap(file->getObject(target));
	}

	removeDuplicates(objsToCompile);

	// The objects need to be recompiled, otherwise their pick lists are left with
	// the wrong index colors which messes up selection.
	for (LDObject* obj : objsToCompile)
		g_win->renderer()->compileObject(obj);
}

// =============================================================================
//
// Get type name by enumerator
//
QString LDObject::typeName(LDObjectType type)
{
	return LDObject::getDefault(type)->typeName();
}

// =============================================================================
//
// Get a description of a list of LDObjects
//
QString LDObject::describeObjects(const LDObjectList& objs)
{
	QString text;

	if (objs.isEmpty())
		return "nothing"; // :)

	for (LDObjectType objType = OBJ_FirstType; objType < OBJ_NumTypes; ++objType)
	{
		int count = 0;

		for (LDObject* obj : objs)
		{
			if (obj->type() == objType)
				count++;
		}

		if (count == 0)
			continue;

		if (not text.isEmpty())
			text += ", ";

		QString noun = format("%1%2", typeName(objType), plural(count));
		text += format("%1 %2", count, noun);
	}

	return text;
}

// =============================================================================
//
// Object after this in the current file
//
LDObject* LDObject::next() const
{
	int idx = lineNumber();

	if (idx == -1 or idx == document()->getObjectCount() - 1)
		return nullptr;

	return document()->getObject(idx + 1);
}

// =============================================================================
//
// Object prior to this in the current file
//
LDObject* LDObject::previous() const
{
	int idx = lineNumber();

	if (idx <= 0)
		return nullptr;

	return document()->getObject(idx - 1);
}

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

	if (prev and prev->type() == OBJ_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;
}

LDDocument* LDObject::document() const
{
	return m_document;
}

bool LDObject::isDestroyed() const
{
	return m_isDestroyed;
}

// =============================================================================
//
// Returns a default-constructed LDObject by the given type
//
LDObject* LDObject::getDefault(const LDObjectType type)
{
	switch(type)
	{
	case OBJ_Comment:		return LDSpawn<LDComment>();
	case OBJ_Bfc:			return LDSpawn<LDBfc>();
	case OBJ_Line:			return LDSpawn<LDLine>();
	case OBJ_CondLine:		return LDSpawn<LDCondLine>();
	case OBJ_SubfileReference:		return LDSpawn<LDSubfileReference>();
	case OBJ_Triangle:		return LDSpawn<LDTriangle>();
	case OBJ_Quad:			return LDSpawn<LDQuad>();
	case OBJ_Empty:			return LDSpawn<LDEmpty>();
	case OBJ_Error:			return LDSpawn<LDError>();
	case OBJ_Overlay:		return LDSpawn<LDOverlay>();
	case OBJ_BezierCurve:	return LDSpawn<LDBezierCurve>();
	case OBJ_NumTypes:		break;
	}
	return nullptr;
}

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

// =============================================================================
//
void LDTriangle::invert()
{
	// Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1.
	// Thus, we swap 1 and 2.
	Vertex tmp = vertex(1);
	setVertex(1, vertex(2));
	setVertex(2, tmp);

	return;
}

// =============================================================================
//
void LDQuad::invert()
{
	// Quad:     0 -> 1 -> 2 -> 3
	// reversed: 0 -> 3 -> 2 -> 1
	// Thus, we swap 1 and 3.
	Vertex tmp = vertex(1);
	setVertex(1, vertex(3));
	setVertex(3, tmp);
}

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

	// Check whether subfile is flat
	int axisSet = (1 << X) |(1 << Y) |(1 << Z);
	LDObjectList objs = fileInfo()->inlineContents(true, false);

	for (LDObject* obj : objs)
	{
		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 {};

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

		if (axisSet &(1 << Y))
			matrixModifier[4] = -1;

		if (axisSet &(1 << Z))
			matrixModifier[8] = -1;

		setTransform(transform() * 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.
			bfc->destroy();
			return;
		}
	}

	// Not inverted, thus prefix it with a new invertnext.
	document()->insertObj(idx, new LDBfc(BfcStatement::InvertNext));
}

// =============================================================================
//
void LDLine::invert()
{
	// For lines, we swap the vertices.
	Vertex tmp = vertex(0);
	setVertex(0, vertex(1));
	setVertex(1, tmp);
}

// =============================================================================
//
void LDCondLine::invert()
{
	// I don't think that a conditional line's control points need to be swapped, do they?
	Vertex tmp = vertex(0);
	setVertex(0, vertex(1));
	setVertex(1, tmp);
}

// =============================================================================
//
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);
}

// =============================================================================
//
LDLine* LDCondLine::toEdgeLine()
{
	LDLine* replacement = new LDLine;

	for (int i = 0; i < replacement->numVertices(); ++i)
		replacement->setVertex(i, vertex(i));

	replacement->setColor(color());
	replace(replacement);
	return replacement;
}

// =============================================================================
//
LDObject* LDObject::fromID(int id)
{
	auto it = g_allObjects.find(id);

	if (it != g_allObjects.end())
		return *it;

	return nullptr;
}

// =============================================================================
//
QString LDOverlay::asText() const
{
	return format("0 !LDFORGE OVERLAY %1 %2 %3 %4 %5 %6",
		fileName(), camera(), x(), y(), width(), height());
}

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.
//
template<typename T>
static void changeProperty(LDObject* obj, T* ptr, const T& val)
{
	int idx;

	if (*ptr == val)
		return;

	if (obj->document() and(idx = obj->lineNumber()) != -1)
	{
		QString before = obj->asText();
		*ptr = val;
		QString after = obj->asText();

		if (before != after)
		{
			obj->document()->addToHistory(new EditHistoryEntry(idx, before, after));
			g_win->renderer()->compileObject(obj);
			g_win->currentDocument()->redoVertices();
		}
	}
	else
	{
		*ptr = val;
	}
}

// =============================================================================
//
void LDObject::setColor(LDColor color)
{
	changeProperty(this, &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(this, &m_coords[i], vert);
}

LDMatrixObject::LDMatrixObject(LDDocument* document) :
	LDObject(document),
	m_position(Vertex {}) {}

LDMatrixObject::LDMatrixObject(const Matrix& transform, const Vertex& pos, LDDocument* document) :
	LDObject(document),
	m_position(pos),
	m_transform(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(this, &m_position, a);
}

// =============================================================================
//
const Matrix& LDMatrixObject::transform() const
{
	return m_transform;
}

void LDMatrixObject::setTransform(const Matrix& val)
{
	changeProperty(this, &m_transform, val);
}

LDError::LDError(QString contents, QString reason, LDDocument* document) :
	LDObject(document),
	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;
}

LDComment::LDComment(QString text, LDDocument* document) :
	LDObject(document),
	m_text(text) {}

QString LDComment::text() const
{
	return m_text;
}

void LDComment::setText(QString value)
{
	changeProperty(this, &m_text, value);
}

LDBfc::LDBfc(const BfcStatement type, LDDocument* document) :
	LDObject(document),
	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 "";
}

int LDOverlay::camera() const
{
	return m_camera;
}

void LDOverlay::setCamera(int value)
{
	m_camera = value;
}

int LDOverlay::x() const
{
	return m_x;
}

void LDOverlay::setX(int value)
{
	m_x = value;
}

int LDOverlay::y() const
{
	return m_y;
}

void LDOverlay::setY(int value)
{
	m_y = value;
}

int LDOverlay::width() const
{
	return m_width;
}

void LDOverlay::setWidth(int value)
{
	m_width = value;
}

int LDOverlay::height() const
{
	return m_height;
}

void LDOverlay::setHeight(int value)
{
	m_height = value;
}

QString LDOverlay::fileName() const
{
	return m_fileName;
}

void LDOverlay::setFileName(QString value)
{
	m_fileName = value;
}

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();
}

LDObjectList LDBezierCurve::rasterize(int segments)
{
	if (segments == 0)
		segments = gridBezierCurveSegments();

	QVector<LDPolygon> polygons = rasterizePolygons(segments);
	LDObjectList result;

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

	return result;
}

QVector<LDPolygon> LDBezierCurve::rasterizePolygons(int segments)
{
	if (segments == 0)
		segments = gridBezierCurveSegments();

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

// =============================================================================
//
// Selects this object.
//
void LDObject::select()
{
	if (not isSelected() and document())
	{
		m_isSelected = true;
		document()->addToSelection(this);
	}
}

// =============================================================================
//
// Removes this object from selection
//
void LDObject::deselect()
{
	if (isSelected() and document())
	{
		m_isSelected = false;
		document()->removeFromSelection(this);

		// If this object is inverted with INVERTNEXT, deselect the INVERTNEXT as well.
		LDBfc* invertnext;

		if (previousIsInvertnext(invertnext))
			invertnext->deselect();
	}
}

// =============================================================================
//
QString PreferredLicenseText()
{
	return config->useCaLicense() ? CALicenseText : "";
}

// =============================================================================
//
LDObject* LDObject::createCopy() const
{
	LDObject* copy = ParseLine(asText());
	return copy;
}

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

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

	// 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 (document and
		document->isCache() == false and
		document->polygonData().isEmpty())
	{
		document->initializeCachedData();
	}
};

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

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

mercurial