src/ldObject.cc

Tue, 22 Apr 2014 22:28:19 +0300

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Tue, 22 Apr 2014 22:28:19 +0300
changeset 727
3faeb1174e40
parent 725
37b71daf4cb5
child 733
cc39df788660
permissions
-rw-r--r--

- added an icon for random colors

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


#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"

cfg (String, ld_defaultname, "");
cfg (String, ld_defaultuser, "");
cfg (Int, ld_defaultlicense, 0);

// List of all LDObjects
static LDObjectList g_LDObjects;

// =============================================================================
// LDObject constructors
//
LDObject::LDObject() :
	m_isHidden (false),
	m_isSelected (false),
	m_parent (null),
	m_document (null),
	m_isGLInit (false),
	qObjListEntry (null)
{
	memset (m_coords, 0, sizeof m_coords);
	chooseID();
	g_LDObjects << this;
	setRandomColor (QColor::fromRgb (rand()));
}

// =============================================================================
//
void LDObject::chooseID()
{
	int32 id = 1; // 0 shalt be null

	for (LDObject* obj : g_LDObjects)
	{
		assert (obj != this);

		if (obj->id() >= id)
			id = obj->id() + 1;
	}

	setID (id);
}

// =============================================================================
//
void LDObject::setVertexCoord (int i, Axis ax, double value)
{
	Vertex v = vertex (i);
	v[ax] = value;
	setVertex (i, v);
}

LDError::LDError() {}

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

// =============================================================================
//
QString LDSubfile::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 LDError::asText() const
{
	return contents();
}

// =============================================================================
//
QString LDVertex::asText() const
{
	return format ("0 !LDFORGE VERTEX %1 %2", color(), pos);
}

// =============================================================================
//
QString LDEmpty::asText() const
{
	return "";
}

// =============================================================================
//
const char* LDBFC::k_statementStrings[] =
{
	"CERTIFY CCW",
	"CCW",
	"CERTIFY CW",
	"CW",
	"NOCERTIFY",
	"INVERTNEXT",
	"CLIP",
	"CLIP CCW",
	"CLIP CW",
	"NOCLIP",
};

QString LDBFC::asText() const
{
	return format ("0 BFC %1", LDBFC::k_statementStrings[m_statement]);
}

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

	QList<LDTriangle*> triangles;
	triangles << tri1;
	triangles << tri2;
	return triangles;
}

// =============================================================================
//
void LDObject::replace (LDObject* other)
{
	long idx = lineNumber();
	assert (idx != -1);

	// Replace the instance of the old object with the new object
	document()->setObject (idx, other);

	// Remove the old object
	destroy();
}

// =============================================================================
//
void LDObject::swap (LDObject* other)
{
	assert (document() == other->document());
	document()->swapObjects (this, other);
}

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

// =============================================================================
//
LDQuad::LDQuad (const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3)
{
	setVertex (0, v0);
	setVertex (1, v1);
	setVertex (2, v2);
	setVertex (3, v3);
}

// =============================================================================
//
LDObject::~LDObject() {}

// =============================================================================
//
LDSubfile::~LDSubfile() {}

// =============================================================================
//
void LDObject::destroy()
{
	// If this object was selected, unselect it now
	if (isSelected())
		unselect();

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

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

	// Remove this object from the list of LDObjects
	g_LDObjects.removeOne (this);

	delete this;
}

// =============================================================================
//
static void transformObject (LDObject* obj, Matrix transform, Vertex pos, int parentcolor)
{
	switch (obj->type())
	{
		case LDObject::ELine:
		case LDObject::ECondLine:
		case LDObject::ETriangle:
		case LDObject::EQuad:

			for (int i = 0; i < obj->vertices(); ++i)
			{
				Vertex v = obj->vertex (i);
				v.transform (transform, pos);
				obj->setVertex (i, v);
			}

			break;

		case LDObject::ESubfile:
		{
			LDSubfile* ref = static_cast<LDSubfile*> (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 LDSubfile::inlineContents (bool deep, bool render)
{
	LDObjectList objs = fileInfo()->inlineContents (deep, render);

	// Transform the objects
	for (LDObject* obj : objs)
	{
		// Set the parent now so we know what inlined the object.
		obj->setParent (this);
		transformObject (obj, transform(), position(), color());
	}

	return objs;
}

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

	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.
	const long start = up ? 0 : (objs.size() - 1);
	const long end = up ? objs.size() : -1;
	const long incr = up ? 1 : -1;
	LDObjectList objsToCompile;
	LDDocument* file = objs[0]->document();

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

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

		if ((up && idx == 0) || (not up && 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.
			assert (i == start);
			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->R()->compileObject (obj);
}

// =============================================================================
//
QString LDObject::typeName (LDObject::Type type)
{
	LDObject* obj = LDObject::getDefault (type);
	QString name = obj->typeName();
	obj->destroy();
	return name;
}

// =============================================================================
//
QString LDObject::describeObjects (const LDObjectList& objs)
{
	bool firstDetails = true;
	QString text = "";

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

	for (long i = 0; i < ENumTypes; ++i)
	{
		Type objType = (Type) i;
		int count = 0;

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

		if (count == 0)
			continue;

		if (not firstDetails)
			text += ", ";

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

		// Plural of "vertex" is "vertices", correct that
		if (objType == EVertex && count != 1)
			noun = "vertices";

		text += format ("%1 %2", count, noun);
		firstDetails = false;
	}

	return text;
}

// =============================================================================
//
LDObject* LDObject::topLevelParent()
{
	if (parent() == null)
		return this;

	LDObject* it = this;

	while (it->parent() != null)
		it = it->parent();

	return it;
}

// =============================================================================
//
LDObject* LDObject::next() const
{
	long idx = lineNumber();
	assert (idx != -1);

	if (idx == (long) document()->getObjectCount() - 1)
		return null;

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

// =============================================================================
//
LDObject* LDObject::previous() const
{
	long idx = lineNumber();
	assert (idx != -1);

	if (idx == 0)
		return null;

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

// =============================================================================
//
void LDObject::move (Vertex vect)
{
	if (hasMatrix())
	{
		LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (this);
		mo->setPosition (mo->position() + vect);
	}
	elif (type() == LDObject::EVertex)
	{
		// ugh
		static_cast<LDVertex*> (this)->pos += vect;
	}
	else
	{
		for (int i = 0; i < vertices(); ++i)
			setVertex (i, vertex (i) + vect);
	}
}

// =============================================================================
//
#define CHECK_FOR_OBJ(N) \
	if (type == LDObject::E##N) \
		return new LD##N;

LDObject* LDObject::getDefault (const LDObject::Type type)
{
	CHECK_FOR_OBJ (Comment)
	CHECK_FOR_OBJ (BFC)
	CHECK_FOR_OBJ (Line)
	CHECK_FOR_OBJ (CondLine)
	CHECK_FOR_OBJ (Subfile)
	CHECK_FOR_OBJ (Triangle)
	CHECK_FOR_OBJ (Quad)
	CHECK_FOR_OBJ (Empty)
	CHECK_FOR_OBJ (BFC)
	CHECK_FOR_OBJ (Error)
	CHECK_FOR_OBJ (Vertex)
	CHECK_FOR_OBJ (Overlay)
	return null;
}

// =============================================================================
//
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
	// rev:  0 -> 3 -> 2 -> 1
	// Thus, we swap 1 and 3.
	Vertex tmp = vertex (1);
	setVertex (1, vertex (3));
	setVertex (3, tmp);
}

// =============================================================================
//
void LDSubfile::invert()
{
	// Subfiles are inverted when they're prefixed with
	// a BFC INVERTNEXT statement. Thus we need to toggle this status.
	// For flat primitives it's sufficient that the determinant is
	// flipped but I don't have a method for checking flatness yet.
	// Food for thought...

	int idx = lineNumber();

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

		if (bfc && bfc->statement() == LDBFC::InvertNext)
		{
			// This is prefixed with an invertnext, thus remove it.
			bfc->destroy();
			return;
		}
	}

	// Not inverted, thus prefix it with a new invertnext.
	LDBFC* bfc = new LDBFC (LDBFC::InvertNext);
	document()->insertObj (idx, bfc);
}

// =============================================================================
//
static void invertLine (LDObject* line)
{
	// For lines, we swap the vertices. I don't think that a
	// cond-line's control points need to be swapped, do they?
	Vertex tmp = line->vertex (0);
	line->setVertex (0, line->vertex (1));
	line->setVertex (1, tmp);
}

void LDLine::invert()
{
	invertLine (this);
}

void LDCondLine::invert()
{
	invertLine (this);
}

void LDVertex::invert() {}

// =============================================================================
//
LDLine* LDCondLine::demote()
{
	LDLine* repl = new LDLine;

	for (int i = 0; i < repl->vertices(); ++i)
		repl->setVertex (i, vertex (i));

	repl->setColor (color());

	replace (repl);
	return repl;
}

// =============================================================================
//
LDObject* LDObject::fromID (int id)
{
	for (LDObject* obj : g_LDObjects)
		if (obj->id() == id)
			return obj;

	return null;
}

// =============================================================================
//
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<class T> static void changeProperty (LDObject* obj, T* ptr, const T& val)
{
	long idx;

	if (*ptr == val)
		return;

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

// =============================================================================
//
void LDObject::setColor (const int& val)
{
	changeProperty (this, &m_color, val);
}

// =============================================================================
//
const Vertex& LDObject::vertex (int i) const
{
	return m_coords[i]->data();
}

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

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

// =============================================================================
//
static QMap<Vertex, LDSharedVertex*> g_sharedVerts;

LDSharedVertex* LDSharedVertex::getSharedVertex (const Vertex& a)
{
	auto it = g_sharedVerts.find (a);

	if (it == g_sharedVerts.end())
	{
		LDSharedVertex* v = new LDSharedVertex (a);
		g_sharedVerts[a] = v;
		return v;
	}

	return *it;
}

// =============================================================================
//
void LDSharedVertex::addRef (LDObject* a)
{
	m_refs << a;
}

// =============================================================================
//
void LDSharedVertex::delRef (LDObject* a)
{
	m_refs.removeOne (a);

	if (m_refs.empty())
	{
		g_sharedVerts.remove (m_data);
		delete this;
	}
}

// =============================================================================
//
void LDObject::select()
{
	assert (document() != null);
	document()->addToSelection (this);
}

// =============================================================================
//
void LDObject::unselect()
{
	assert (document() != null);
	document()->removeFromSelection (this);
}

// =============================================================================
//
QString getLicenseText (int id)
{
	switch (id)
	{
		case 0:
			return g_CALicense;

		case 1:
			return g_nonCALicense;

		case 2:
			return "";
	}

	assert (false);
	return "";
}

// =============================================================================
//
LDObject* LDObject::createCopy() const
{
	/*
	LDObject* copy = clone();
	copy->setFile (null);
	copy->setGLInit (false);
	copy->chooseID();
	copy->setSelected (false);
	*/

	/*
	LDObject* copy = getDefault (getType());
	copy->setColor (color());

	if (hasMatrix())
	{
		LDMatrixObject* copyMo = static_cast<LDMatrixObject*> (copy);
		const LDMatrixObject* mo = static_cast<const LDMatrixObject*> (this);
		copyMo->setPosition (mo->getPosition());
		copyMo->setTransform (mo->transform());
	}
	else
	{
		for (int i = 0; i < vertices(); ++i)
			copy->setVertex (getVertex (i));
	}

	switch (getType())
	{
		case Subfile:
		{
			LDSubfile* copyRef = static_cast<LDSubfile*> (copy);
			const LDSubfile* ref = static_cast<const LDSubfile*> (this);

			copyRef->setFileInfo (ref->fileInfo());
		}
	}
	*/

	LDObject* copy = parseLine (asText());
	return copy;
}

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

mercurial