src/ldtypes.cpp

Tue, 21 May 2013 14:06:06 +0300

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Tue, 21 May 2013 14:06:06 +0300
changeset 231
6ee9917b79f8
parent 217
cad8c046f27a
child 232
4e44c92e21dd
permissions
-rw-r--r--

update project file to only use no overpainting under Windows and -lGLU under *nix

/*
 *  LDForge: LDraw parts authoring CAD
 *  Copyright (C) 2013 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 "common.h"
#include "ldtypes.h"
#include "file.h"
#include "misc.h"
#include "gui.h"
#include "history.h"
#include "gldraw.h"

char const* g_saObjTypeNames[] = {
	"subfile",
	"radial",
	"quadrilateral",
	"triangle",
	"line",
	"condline",
	"vertex",
	"bfc",
	"comment",
	"unknown",
	"empty",
	"unidentified",
};

// Should probably get rid of this array sometime
char const* g_saObjTypeIcons[] = {
	"subfile",
	"radial",
	"quad",
	"triangle",
	"line",
	"condline",
	"vertex",
	"bfc",
	"comment",
	"error",
	"empty",
	"error",
};

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
// LDObject constructors
LDObject::LDObject () {
	qObjListEntry = null;
	parent = null;
	m_hidden = false;
	m_selected = false;
	m_glinit = false;
}

LDGibberish::LDGibberish (str _zContent, str _zReason) {
	contents = _zContent;
	reason = _zReason;
}

// =============================================================================
str LDComment::getContents () {
	return fmt ("0 %s", text.chars ());
}

str LDSubfile::getContents () {
	str val = fmt ("1 %d %s ", color, pos.stringRep (false).chars ());
	val += transform.stringRep ();
	val += ' ';
	val += fileName;
	return val;
}

str LDLine::getContents () {
	str val = fmt ("2 %d", color);
	
	for (ushort i = 0; i < 2; ++i)
		val += fmt  (" %s", coords[i].stringRep (false).chars ());
	
	return val;
}

str LDTriangle::getContents () {
	str val = fmt ("3 %d", color);
	
	for (ushort i = 0; i < 3; ++i)
		val += fmt  (" %s", coords[i].stringRep (false).chars ());
	
	return val;
}

str LDQuad::getContents () {
	str val = fmt ("4 %d", color);
	
	for (ushort i = 0; i < 4; ++i)
		val += fmt  (" %s", coords[i].stringRep (false).chars ());
	
	return val;
}

str LDCondLine::getContents () {
	str val = fmt ("5 %d", color);
	
	// Add the coordinates
	for (ushort i = 0; i < 4; ++i)
		val += fmt  (" %s", coords[i].stringRep (false).chars ());
	
	return val;
}

str LDGibberish::getContents () {
	return contents;
}

str LDVertex::getContents () {
	return fmt ("0 !LDFORGE VERTEX %d %s", color, pos.stringRep (false).chars());
}

str LDEmpty::getContents () {
	return str ();
}

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

str LDBFC::getContents () {
	return fmt ("0 BFC %s", LDBFC::statements[type]);
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
vector<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;
	tri1->coords[0] = coords[0];
	tri1->coords[1] = coords[1];
	tri1->coords[2] = coords[3];
	
	LDTriangle* tri2 = new LDTriangle;
	tri2->coords[0] = coords[1];
	tri2->coords[1] = coords[2];
	tri2->coords[2] = coords[3];
	
	// The triangles also inherit the quad's color
	tri1->color = tri2->color = color;
	
	vector<LDTriangle*> triangles;
	triangles.push_back (tri1);
	triangles.push_back (tri2);
	return triangles;
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
void LDObject::replace (LDObject* replacement) {
	// Replace the instance of the old object with the new object
	for (LDObject*& obj : g_curfile->m_objs) {
		if (obj == this) {
			obj = replacement;
			break;
		}
	}
	
	// Remove the old object
	delete this;
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
void LDObject::swap (LDObject* other) {
	for (LDObject*& obj : g_curfile->m_objs) {
		if (obj == this)
			obj = other;
		else if (obj == other)
			obj = this;
	}
}

LDLine::LDLine (vertex v1, vertex v2) {
	coords[0] = v1;
	coords[1] = v2;
}

LDObject::~LDObject () {
	// Remove this object from the selection array if it is there.
	for (ulong i = 0; i < g_win->sel ().size(); ++i)
		if (g_win->sel ()[i] == this)
			g_win->sel ().erase (g_win->sel ().begin() + i);
	
	// Delete the GL lists
	GL::deleteLists (this);
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
static void transformObject (LDObject* obj, matrix transform, vertex pos, short parentcolor) {
	switch (obj->getType()) {
	case LDObject::Line:
	case LDObject::CondLine:
	case LDObject::Triangle:
	case LDObject::Quad:
		for (short i = 0; i < obj->vertices (); ++i)
			obj->coords[i].transform (transform, pos);
		break;
	
	case LDObject::Subfile:
		{
			LDSubfile* ref = static_cast<LDSubfile*> (obj);
			
			matrix newMatrix = transform * ref->transform;
			ref->pos.transform (transform, pos);
			ref->transform = newMatrix;
		}
		break;
	
	default:
		break;
	}
	
	if (obj->color == maincolor)
		obj->color = parentcolor;
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
vector<LDObject*> LDSubfile::inlineContents (bool bDeepInline, bool bCache) {
	vector<LDObject*> objs, cache;
	
	// If we have this cached, just clone that
	if (bDeepInline && fileInfo->m_objCache.size ()) {
		for (LDObject* obj : fileInfo->m_objCache)
			objs.push_back (obj->clone ());
	} else {
		if (!bDeepInline)
			bCache = false;
		
		for (LDObject* obj : fileInfo->m_objs) {
			// Skip those without schemantic meaning
			switch (obj->getType ()) {
			case LDObject::Comment:
			case LDObject::Empty:
			case LDObject::Gibberish:
			case LDObject::Unidentified:
			case LDObject::Vertex:
				continue;
			
			case LDObject::BFC:
				// Filter non-INVERTNEXT statements
				if (static_cast<LDBFC*> (obj)->type != LDBFC::InvertNext)
					continue;
				break;
			
			default:
				break;
			}
			
			// Got another sub-file reference, inline it if we're deep-inlining. If not,
			// just add it into the objects normally. Also, we only cache immediate
			// subfiles and this is not one. Yay, recursion!
			if (bDeepInline && obj->getType() == LDObject::Subfile) {
				LDSubfile* ref = static_cast<LDSubfile*> (obj);
				
				vector<LDObject*> otherobjs = ref->inlineContents (true, false);
				
				for (LDObject* otherobj : otherobjs) {
					// Cache this object, if desired
					if (bCache)
						cache.push_back (otherobj->clone ());
					
					objs.push_back (otherobj);
				}
			} else {
				if (bCache)
					cache.push_back (obj->clone ());
				
				objs.push_back (obj->clone ());
			}
		}
		
		if (bCache)
			fileInfo->m_objCache = cache;
	}
	
	// Transform the objects
	for (LDObject* obj : objs) {
		// Set the parent now so we know what inlined this.
		obj->parent = this;
		
		transformObject (obj, transform, pos, color);
	}
	
	return objs;
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
long LDObject::getIndex (OpenFile* pFile) const {
	for (ulong i = 0; i < pFile->m_objs.size(); ++i)
		if (pFile->m_objs[i] == this)
			return i;
	
	return -1;
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
void LDObject::moveObjects (std::vector<LDObject*> objs, const bool bUp) {
	// If we move down, we need to iterate the array in reverse order.
	const long start = bUp ? 0 : (objs.size() - 1);
	const long end = bUp ? objs.size() : -1;
	const long incr = bUp ? 1 : -1;
	
	vector<LDObject*> objsToCompile;
	
	for (long i = start; i != end; i += incr) {
		LDObject* obj = objs[i];
		
		const long idx = obj->getIndex (g_curfile),
			target = idx + (bUp ? -1 : 1);
		
		if ((bUp == true and idx == 0) or
			(bUp == false and idx == (long)(g_curfile->m_objs.size() - 1)))
		{
			// 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.push_back (obj);
		objsToCompile.push_back (g_curfile->m_objs[target]);
		
		obj->swap (g_curfile->m_objs[target]);
	}
	
	// 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);
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
str LDObject::objectListContents (const std::vector<LDObject*>& objs) {
	bool firstDetails = true;
	str text = "";
	
	if (objs.size() == 0)
		return "nothing"; // :)
	
	for (long i = 0; i < LDObject::NumTypes; ++i) {
		LDObject::Type objType = (LDObject::Type) i;
		ulong objCount = 0;
		
		for (LDObject* obj : objs)
			if (obj->getType() == objType)
				objCount++;
		
		if (objCount == 0)
			continue;
		
		if (!firstDetails)
			text += ", ";
		
		str noun = fmt ("%s%s", g_saObjTypeNames[objType], plural (objCount));
		
		// Plural of "vertex" is "vertices". Stupid English.
		if (objType == LDObject::Vertex && objCount != 1)
			noun = "vertices";
		
		text += fmt  ("%lu %s", objCount, noun.chars ());
		firstDetails = false;
	}
	
	return text;
}

// =============================================================================
LDObject* LDObject::topLevelParent () {
	if (!parent)
		return this;
	
	LDObject* it = this;
	
	while (it->parent)
		it = it->parent;
	
	return it;
}

// =============================================================================
LDObject* LDObject::next () const {
	long idx = getIndex (g_curfile);
	assert (idx != -1);
	
	if (idx == (long) g_curfile->m_objs.size () - 1)
		return null;
	
	return g_curfile->m_objs[idx + 1];
}

// =============================================================================
LDObject* LDObject::prev () const {
	long idx = getIndex (g_curfile);
	assert (idx != -1);
	
	if (idx == 0)
		return null;
	
	return g_curfile->m_objs[idx - 1];
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
void LDObject::move (vertex vect) { (void) vect; }
void LDEmpty::move (vertex vect) { (void) vect; }
void LDBFC::move (vertex vect) { (void) vect; }
void LDComment::move (vertex vect) { (void) vect; }
void LDGibberish::move (vertex vect) { (void) vect; }

void LDVertex::move (vertex vect) {
	pos += vect;
}

void LDSubfile::move (vertex vect) {
	pos += vect;
}

void LDRadial::move (vertex vect) {
	pos += vect;
}

void LDLine::move (vertex vect) {
	for (short i = 0; i < 2; ++i)
		coords[i] += vect;
}

void LDTriangle::move (vertex vect) {
	for (short i = 0; i < 3; ++i)
		coords[i] += vect;
}

void LDQuad::move (vertex vect) {
	for (short i = 0; i < 4; ++i)
		coords[i] += vect;
}

void LDCondLine::move (vertex vect) {
	for (short i = 0; i < 4; ++i)
		coords[i] += vect;
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
static char const* g_saRadialTypeNames[] = {
	"Circle",
	"Cylinder",
	"Disc",
	"Disc Negative",
	"Ring",
	"Cone",
	null
};

char const* LDRadial::radialTypeName () {
	return g_saRadialTypeNames[radType];
}

char const* LDRadial::radialTypeName (const LDRadial::Type eType) {
	return g_saRadialTypeNames[eType];
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
std::vector<LDObject*> LDRadial::decompose (bool applyTransform) {
	std::vector<LDObject*> paObjects;
	
	for (short i = 0; i < segs; ++i) {
		double x0 = cos ((i * 2 * pi) / divs),
			x1 = cos (((i + 1) * 2 * pi) / divs),
			z0 = sin ((i * 2 * pi) / divs),
			z1 = sin (((i + 1) * 2 * pi) / divs);
		
		switch (radType) {
		case LDRadial::Circle:
			{
				vertex v0 (x0, 0.0f, z0),
					v1 (x1, 0.0f, z1);
				
				if (applyTransform) {
					v0.transform (transform, pos);
					v1.transform (transform, pos);
				}
				
				LDLine* pLine = new LDLine;
				pLine->coords[0] = v0;
				pLine->coords[1] = v1;
				pLine->color = edgecolor;
				pLine->parent = this;
				
				paObjects.push_back (pLine);
			}
			break;
		
		case LDRadial::Cylinder:
		case LDRadial::Ring:
		case LDRadial::Cone:
			{
				double x2, x3, z2, z3;
				double y0, y1, y2, y3;
				
				if (radType == LDRadial::Cylinder) {
					x2 = x1;
					x3 = x0;
					z2 = z1;
					z3 = z0;
					
					y0 = y1 = 0.0f;
					y2 = y3 = 1.0f;
				} else {
					x2 = x1 * (ringNum + 1);
					x3 = x0 * (ringNum + 1);
					z2 = z1 * (ringNum + 1);
					z3 = z0 * (ringNum + 1);
					
					x0 *= ringNum;
					x1 *= ringNum;
					z0 *= ringNum;
					z1 *= ringNum;
					
					if (radType == LDRadial::Ring) {
						y0 = y1 = y2 = y3 = 0.0f;
					} else {
						y0 = y1 = 1.0f;
						y2 = y3 = 0.0f;
					} 
				}
				
				vertex v0 (x0, y0, z0),
					v1 (x1, y1, z1),
					v2 (x2, y2, z2),
					v3 (x3, y3, z3);
				
				if (applyTransform) {
					v0.transform (transform, pos);
					v1.transform (transform, pos);
					v2.transform (transform, pos);
					v3.transform (transform, pos);
				}
				
				LDQuad* pQuad = new LDQuad;
				pQuad->coords[0] = v0;
				pQuad->coords[1] = v1;
				pQuad->coords[2] = v2;
				pQuad->coords[3] = v3;
				pQuad->color = color;
				pQuad->parent = this;
				
				paObjects.push_back (pQuad);
			}
			break;
		
		case LDRadial::Disc:
		case LDRadial::DiscNeg:
			{
				double x2, z2;
				
				if (radType == LDRadial::Disc) {
					x2 = z2 = 0.0f;
				} else {
					x2 = (x0 >= 0.0f) ? 1.0f : -1.0f;
					z2 = (z0 >= 0.0f) ? 1.0f : -1.0f;
				}
				
				vertex v0 (x0, 0.0f, z0),
					v1 (x1, 0.0f, z1),
					v2 (x2, 0.0f, z2);
				
				if (applyTransform) {
					v0.transform (transform, pos);
					v1.transform (transform, pos);
					v2.transform (transform, pos);
				}
				
				LDTriangle* pSeg = new LDTriangle;
				pSeg->coords[0] = v0;
				pSeg->coords[1] = v1;
				pSeg->coords[2] = v2;
				pSeg->color = color;
				pSeg->parent = this;
				
				paObjects.push_back (pSeg);
			}
			break;
		
		default:
			break;
		}
	}
	
	return paObjects;
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
str LDRadial::getContents () {
	return fmt ("0 !LDFORGE RADIAL %s %d %d %d %d %s %s",
		str (radialTypeName()).upper ().strip (' ').c (),
		color, segs, divs, ringNum,
		pos.stringRep (false).chars(), transform.stringRep().chars());
}

char const* g_radialNameRoots[] = {
	"edge",
	"cyli",
	"disc",
	"ndis",
	"ring",
	"con",
	null
};

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
str LDRadial::makeFileName () {
	short numer = segs,
		denom = divs;
	
	// Simplify the fractional part, but the denominator must be at least 4.
	simplify (numer, denom);
	
	if (denom < 4) {
		const short factor = (4 / denom);
		
		numer *= factor;
		denom *= factor;
	}
	
	// Compose some general information: prefix, fraction, root, ring number
	str prefix = (divs == 16) ? "" : fmt ("%d/", divs);
	str frac = fmt ("%d-%d", numer, denom);
	str root = g_radialNameRoots[radType];
	str num = (radType == Ring || radType == Cone) ? fmt ("%d", ringNum) : "";
	
	// Truncate the root if necessary (7-16rin4.dat for instance).
	// However, always keep the root at least 2 characters.
	short extra = (~frac + ~num + ~root) - 8;
	root -= min<short> (max<short> (extra, 0), 2);
	
	// Stick them all together and return the result.
	return fmt ("%s%s%s%s.dat", prefix.chars(), frac.chars (), root.chars (), num.chars ());
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
#define CHECK_FOR_OBJ(N) \
	if (type == LDObject::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 (Radial)
	CHECK_FOR_OBJ (Subfile)
	CHECK_FOR_OBJ (Triangle)
	CHECK_FOR_OBJ (Quad)
	CHECK_FOR_OBJ (Empty)
	CHECK_FOR_OBJ (BFC)
	CHECK_FOR_OBJ (Gibberish)
	CHECK_FOR_OBJ (Vertex)
	return null;
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
HistoryEntry* LDObject::invert () { return null; }
HistoryEntry* LDBFC::invert () { return null; }
HistoryEntry* LDEmpty::invert () { return null; }
HistoryEntry* LDComment::invert () { return null; }
HistoryEntry* LDGibberish::invert () { return null; }

HistoryEntry* LDTriangle::invert () {
	// Triangle goes 0 -> 1 -> 2, reversed: 0 -> 2 -> 1.
	// Thus, we swap 1 and 2.
	vertex tmp = coords[1];
	
	LDObject* oldCopy = clone ();
	coords[1] = coords[2];
	coords[2] = tmp;
	
	return new EditHistory ({(ulong) getIndex (g_curfile)}, {oldCopy}, {clone ()});
}

HistoryEntry* LDQuad::invert () {
	// Quad: 0 -> 1 -> 2 -> 3
	// rev:  0 -> 3 -> 2 -> 1
	// Thus, we swap 1 and 3.
	vertex tmp = coords[1];
	LDObject* oldCopy = clone ();
	
	coords[1] = coords[3];
	coords[3] = tmp;
	
	return new EditHistory ({(ulong) getIndex (g_curfile)}, {oldCopy}, {clone ()});
}

static HistoryEntry* invertSubfile (LDObject* obj) {
	// Subfiles and radials 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...
	
	ulong idx = obj->getIndex (g_curfile);
	
	if (idx > 0) {
		LDBFC* bfc = dynamic_cast<LDBFC*> (obj->prev ());
		
		if (bfc && bfc->type == LDBFC::InvertNext) {
			// Object is prefixed with an invertnext, thus remove it.
			HistoryEntry* history = new DelHistory ({idx - 1}, {bfc->clone ()});
			
			g_curfile->forgetObject (bfc);
			delete bfc;
			return history;
		}
	}
	
	// Not inverted, thus prefix it with a new invertnext.
	LDBFC* bfc = new LDBFC (LDBFC::InvertNext);
	g_curfile->insertObj (idx, bfc);
	
	return new AddHistory ({idx}, {bfc->clone ()});
}

HistoryEntry* LDSubfile::invert () {
	return invertSubfile (this);
}

HistoryEntry* LDRadial::invert () {
	return invertSubfile (this);
}

static HistoryEntry* 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?
	LDObject* oldCopy = line->clone ();
	vertex tmp = line->coords[0];
	
	oldCopy = line->clone ();
	line->coords[0] = line->coords[1];
	line->coords[1] = tmp;
	
	return new EditHistory ({(ulong) line->getIndex (g_curfile)}, {oldCopy}, {line->clone ()});
}

HistoryEntry* LDLine::invert () {
	return invertLine (this);
}

HistoryEntry* LDCondLine::invert () {
	return invertLine (this);
}

HistoryEntry* LDVertex::invert () { return null; }

// =============================================================================
LDLine* LDCondLine::demote () {
	LDLine* repl = new LDLine;
	memcpy (repl->coords, coords, sizeof coords);
	repl->color = color;
	
	replace (repl);
	return repl;
}

mercurial