src/gldraw.cpp

changeset 183
f1b8cb53d2a2
child 185
6fea53f1ffc2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gldraw.cpp	Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,1214 @@
+/*
+ *  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 <QtGui>
+#include <QGLWidget>
+#include <GL/glu.h>
+#include "common.h"
+#include "config.h"
+#include "file.h"
+#include "gldraw.h"
+#include "bbox.h"
+#include "colors.h"
+#include "gui.h"
+#include "misc.h"
+#include "history.h"
+
+static double g_objOffset[3];
+
+static short g_pulseTick = 0;
+static const short g_numPulseTicks = 8;
+static const short g_pulseInterval = 65;
+
+static const struct staticCameraMeta {
+	const char glrotate[3];
+	const Axis axisX, axisY;
+	const bool negX, negY;
+} g_staticCameras[6] = {
+	{ { 1, 0, 0 }, X, Z, false, false },
+	{ { 0, 0, 0 }, X, Y, false, true },
+	{ { 0, 1, 0 }, Z, Y, true, true },
+	{ { -1, 0, 0 }, X, Z, false, true },
+	{ { 0, 0, 0 }, X, Y, true, true },
+	{ { 0, -1, 0 }, Z, Y, false, true },
+};
+
+cfg (str, gl_bgcolor, "#CCCCD9");
+cfg (str, gl_maincolor, "#707078");
+cfg (float, gl_maincolor_alpha, 1.0);
+cfg (int, gl_linethickness, 2);
+cfg (bool, gl_colorbfc, true);
+cfg (bool, gl_selflash, false);
+cfg (int, gl_camera, GLRenderer::Free);
+cfg (bool, gl_blackedges, true);
+cfg (bool, gl_axes, false);
+
+// CameraIcon::img is a heap-allocated QPixmap because otherwise it gets
+// initialized before program gets to main() and constructs a QApplication
+// and Qt doesn't like that.
+struct CameraIcon {
+	QPixmap* img;
+	QRect srcRect, destRect, selRect;
+	GLRenderer::Camera cam;
+} g_CameraIcons[7];
+
+const char* g_CameraNames[7] = { "Top", "Front", "Left", "Bottom", "Back", "Right", "Free" };
+
+const GLRenderer::Camera g_Cameras[7] = {
+	GLRenderer::Top,
+	GLRenderer::Front,
+	GLRenderer::Left,
+	GLRenderer::Bottom,
+	GLRenderer::Back,
+	GLRenderer::Right,
+	GLRenderer::Free
+};
+
+const struct GLAxis {
+	const QColor col;
+	const vertex vert;
+} g_GLAxes[3] = {
+	{ QColor (255, 0, 0), vertex (10000, 0, 0) },
+	{ QColor (128, 192, 0), vertex (0, 10000, 0) },
+	{ QColor (0, 160, 192), vertex (0, 0, 10000) },
+};
+	
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) {
+	resetAngles ();
+	m_picking = m_rangepick = false;
+	m_camera = (GLRenderer::Camera) gl_camera.value;
+	m_drawToolTip = false;
+	m_planeDraw = false;
+	
+	m_pulseTimer = new QTimer (this);
+	connect (m_pulseTimer, SIGNAL (timeout ()), this, SLOT (slot_timerUpdate ()));
+	
+	m_toolTipTimer = new QTimer (this);
+	m_toolTipTimer->setSingleShot (true);
+	connect (m_toolTipTimer, SIGNAL (timeout ()), this, SLOT (slot_toolTipTimer ()));
+	
+	m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
+	m_thinBorderPen = m_thickBorderPen;
+	m_thinBorderPen.setWidth (1);
+	
+	// Init camera icons
+	for (const GLRenderer::Camera cam : g_Cameras) {
+		str iconname;
+		iconname.format ("camera-%s", str (g_CameraNames[cam]).tolower ().chars ());
+		
+		CameraIcon* info = &g_CameraIcons[cam];
+		info->img = new QPixmap (getIcon (iconname));
+		info->cam = cam;
+	}
+	
+	calcCameraIconRects ();
+}
+
+// =============================================================================
+GLRenderer::~GLRenderer() {
+	for (CameraIcon& info : g_CameraIcons)
+		delete info.img;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::calcCameraIconRects () {
+	ushort i = 0;
+	
+	for (CameraIcon& info : g_CameraIcons) {
+		const long x1 = (m_width - (info.cam != Free ? 48 : 16)) + ((i % 3) * 16) - 1,
+			y1 = ((i / 3) * 16) + 1;
+		
+		info.srcRect = QRect (0, 0, 16, 16);
+		info.destRect = QRect (x1, y1, 16, 16);
+		info.selRect = QRect (info.destRect.x (), info.destRect.y (),
+			info.destRect.width () + 1, info.destRect.height () + 1);
+		++i;
+	}
+}
+
+// =============================================================================
+void GLRenderer::resetAngles () {
+	m_rotX = 30.0f;
+	m_rotY = 325.f;
+	m_panX = m_panY = m_rotZ = 0.0f;
+	
+	// Set the default zoom based on the bounding box
+	m_zoom = g_BBox.size () * 6;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::initializeGL () {
+	setBackground ();
+	
+	glEnable (GL_POLYGON_OFFSET_FILL);
+	glPolygonOffset (1.0f, 1.0f);
+	
+	glEnable (GL_DEPTH_TEST);
+	glShadeModel (GL_SMOOTH);
+	glEnable (GL_MULTISAMPLE);
+	
+	glEnable (GL_BLEND);
+	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+	
+	glEnable (GL_LINE_SMOOTH);
+	glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
+	
+	glLineWidth (gl_linethickness);
+	
+	setAutoFillBackground (false);
+	setMouseTracking (true);
+	setFocusPolicy (Qt::WheelFocus);
+	compileObjects ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+QColor GLRenderer::getMainColor () {
+	QColor col (gl_maincolor.value.chars());
+	
+	if (!col.isValid ())
+		return QColor (0, 0, 0);
+	
+	col.setAlpha (gl_maincolor_alpha * 255.f);
+	return col;
+}
+
+// -----------------------------------------------------------------------------
+void GLRenderer::setBackground () {
+	QColor col (gl_bgcolor.value.chars());
+	
+	if (!col.isValid ())
+		return;
+	
+	m_darkbg = luma (col) < 80;
+	
+	col.setAlpha (255);
+	qglClearColor (col);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+static vector<short> g_daWarnedColors;
+void GLRenderer::setObjectColor (LDObject* obj) {
+	QColor qcol;
+	
+	if (!obj->isColored())
+		return;
+	
+	if (m_picking) {
+		// Make the color by the object's index color if we're picking, so we can
+		// make the index from the color we get from the picking results.
+		long i = obj->getIndex (g_curfile);
+		
+		// If we couldn't find the index, this object must not be from this file,
+		// therefore it must be an object inlined from a subfile reference or
+		// decomposed from a radial. Find the top level parent object and use
+		// its index.
+		if (i == -1)
+			i = obj->topLevelParent ()->getIndex (g_curfile);
+		
+		// We should have the index now.
+		assert (i != -1);
+		
+		// Calculate a color based from this index. This method caters for
+		// 16777216 objects. I don't think that'll be exceeded anytime soon. :)
+		// ATM biggest is 53588.dat with 12600 lines.
+		double r = (i / (256 * 256)) % 256,
+			g = (i / 256) % 256,
+			b = i % 256;
+		
+		qglColor (QColor (r, g, b, 255));
+		return;
+	}
+	
+#if 0
+	if (gl_colorbfc &&
+		obj->getType () != LDObject::Line &&
+		obj->getType () != LDObject::CondLine)
+	{
+		if (bBackSide)
+			glColor4f (0.9f, 0.0f, 0.0f, 1.0f);
+		else
+			glColor4f (0.0f, 0.8f, 0.0f, 1.0f);
+		return;
+	}
+#endif
+	
+	if (obj->dColor == maincolor)
+		qcol = getMainColor ();
+	else {
+		color* col = getColor (obj->dColor);
+		qcol = col->qColor;
+	}
+	
+	if (obj->dColor == edgecolor) {
+		qcol = Qt::black;
+		color* col;
+		
+		if (!gl_blackedges && obj->parent != null && (col = getColor (obj->parent->dColor)) != null)
+			qcol = col->qEdge;
+	}
+	
+	if (qcol.isValid () == false) {
+		// The color was unknown. Use main color to make the object at least
+		// not appear pitch-black.
+		if (obj->dColor != edgecolor)
+			qcol = getMainColor ();
+		
+		// Warn about the unknown colors, but only once.
+		for (short i : g_daWarnedColors)
+			if (obj->dColor == i)
+				return;
+		
+		printf ("%s: Unknown color %d!\n", __func__, obj->dColor);
+		g_daWarnedColors.push_back (obj->dColor);
+		return;
+	}
+	
+	long r = qcol.red (),
+		g = qcol.green (),
+		b = qcol.blue (),
+		a = qcol.alpha ();
+	
+	// If it's selected, brighten it up, also pulse flash it if desired.
+	if (g_win->isSelected (obj)) {
+		short tick, numTicks;
+		
+		if (gl_selflash) {
+			tick = (g_pulseTick < (g_numPulseTicks / 2)) ? g_pulseTick : (g_numPulseTicks - g_pulseTick);
+			numTicks = g_numPulseTicks;
+		} else {
+			tick = 2;
+			numTicks = 5;
+		}
+		
+		const long add = ((tick * 128) / numTicks);
+		r = min (r + add, 255l);
+		g = min (g + add, 255l);
+		b = min (b + add, 255l);
+		
+		// a = 255;
+	}
+	
+	glColor4f (
+		((double) r) / 255.0f,
+		((double) g) / 255.0f,
+		((double) b) / 255.0f,
+		((double) a) / 255.0f);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::refresh () {
+	update ();
+	swapBuffers ();
+}
+
+// =============================================================================
+void GLRenderer::hardRefresh () {
+	compileObjects ();
+	refresh ();
+	
+	glLineWidth (gl_linethickness);
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::resizeGL (int w, int h) {
+	m_width = w;
+	m_height = h;
+	
+	calcCameraIconRects ();
+	
+	glViewport (0, 0, w, h);
+	glMatrixMode (GL_PROJECTION);
+	glLoadIdentity ();
+	gluPerspective (45.0f, (double)w / (double)h, 1.0f, 10000.0f);
+	glMatrixMode (GL_MODELVIEW);
+}
+
+void GLRenderer::drawGLScene () const {
+	if (g_curfile == null)
+		return;
+	
+	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+	glEnable (GL_DEPTH_TEST);
+	
+	if (m_camera != GLRenderer::Free) {
+		glMatrixMode (GL_PROJECTION);
+		glPushMatrix ();
+		
+		glLoadIdentity ();
+		glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f);
+		glTranslatef (m_panX, m_panY, 0.0f);
+		glRotatef (90.0f, g_staticCameras[m_camera].glrotate[0],
+			g_staticCameras[m_camera].glrotate[1],
+			g_staticCameras[m_camera].glrotate[2]);
+		
+		// Back camera needs to be handled differently
+		if (m_camera == GLRenderer::Back) {
+			glRotatef (180.0f, 1.0f, 0.0f, 0.0f);
+			glRotatef (180.0f, 0.0f, 0.0f, 1.0f);
+		}
+	} else {
+		glMatrixMode (GL_MODELVIEW);
+		glPushMatrix ();
+		glLoadIdentity ();
+		
+		glTranslatef (0.0f, 0.0f, -2.0f);
+		glTranslatef (m_panX, m_panY, -m_zoom);
+		glRotatef (m_rotX, 1.0f, 0.0f, 0.0f);
+		glRotatef (m_rotY, 0.0f, 1.0f, 0.0f);
+		glRotatef (m_rotZ, 0.0f, 0.0f, 1.0f);
+	}
+	
+	for (LDObject* obj : g_curfile->m_objs) {
+		if (obj->hidden ())
+			continue; // Don't draw hidden objects
+		
+		glCallList (m_picking == false ? obj->uGLList : obj->uGLPickList);
+	}
+	
+	if (gl_axes && !m_picking)
+		glCallList (m_axeslist);
+	
+	glPopMatrix ();
+	glMatrixMode (GL_MODELVIEW);
+}
+
+// =============================================================================
+vertex GLRenderer::coord_2to3 (const QPoint& pos2d, const bool snap) const {
+	vertex pos3d;
+	const staticCameraMeta* cam = &g_staticCameras[m_camera];
+	const Axis axisX = cam->axisX;
+	const Axis axisY = cam->axisY;
+	const short negXFac = cam->negX ? -1 : 1,
+		negYFac = cam->negY ? -1 : 1;
+	
+	// Calculate cx and cy - these are the LDraw unit coords the cursor is at.
+	double cx = (-m_virtWidth + ((2 * pos2d.x () * m_virtWidth) / m_width) - m_panX) - (negXFac * g_objOffset[axisX]);
+	double cy = (m_virtHeight - ((2 * pos2d.y () * m_virtHeight) / m_height) - m_panY) - (negYFac * g_objOffset[axisY]);
+	
+	if (snap) {
+		cx = Grid::snap (cx, (Grid::Config) axisX);
+		cy = Grid::snap (cy, (Grid::Config) axisY);
+	}
+	
+	cx *= negXFac;
+	cy *= negYFac;
+	
+	pos3d = g_origin;
+	pos3d[axisX] = cx;
+	pos3d[axisY] = cy;
+	return pos3d;
+}
+
+// =============================================================================
+QPoint GLRenderer::coord_3to2 (const vertex& pos3d) const {
+	/*
+	cx = (-m_virtWidth + ((2 * pos2d.x () * m_virtWidth) / m_width) - m_panX) - (negXFac * g_objOffset[axisX]);
+	
+	                                                 cx = (-vw + ((2 * x * vw) / w) - panx) - (neg * ofs)
+	                                  cx + (neg * ofs) = (-vw + ((2 * x * vw) / w) - panx)
+	                                  cx + (neg * ofs) = ((2 * x * vw) / w) - vw - panx
+	                    (cx + (neg * ofs)) + vw + panx = (2 * x * vw) / w
+	              ((cx + (neg * ofs)) + vw + panx) * w = 2 * vw * x
+	
+	x = (((cx + (neg * ofs)) + vw + panx) * w) / (2 * vw)
+	*/
+	
+	QPoint pos2d;
+	const staticCameraMeta* cam = &g_staticCameras[m_camera];
+	const Axis axisX = cam->axisX;
+	const Axis axisY = cam->axisY;
+	const short negXFac = cam->negX ? -1 : 1,
+		negYFac = cam->negY ? -1 : 1;
+	
+	short x1 = (((pos3d[axisX] + (negXFac * g_objOffset[axisX])) +
+		m_virtWidth + m_panX) * m_width) / (2 * m_virtWidth);
+	short y1 = -(((pos3d[axisY] + (negYFac * g_objOffset[axisY])) -
+		m_virtHeight + m_panY) * m_height) / (2 * m_virtHeight);
+	
+	x1 *= negXFac;
+	y1 *= negYFac;
+	
+	pos2d = QPoint (x1, y1);
+	return pos2d;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::paintEvent (QPaintEvent* ev) {
+	Q_UNUSED (ev)
+	m_virtWidth = m_zoom;
+	m_virtHeight = (m_height * m_virtWidth) / m_width;
+	drawGLScene ();
+	
+	QPainter paint (this);
+	QFontMetrics metrics = QFontMetrics (QFont ());
+	paint.setRenderHint (QPainter::Antialiasing);
+	
+	m_hoverpos = g_origin;
+	
+	if (m_camera != Free) {
+		// Calculate 3d position of the cursor
+		m_hoverpos = coord_2to3 (m_pos, true);
+		
+		// Paint the coordinates onto the screen.
+		str text;
+		text.format ("X: %s, Y: %s, Z: %s", ftoa (m_hoverpos[X]).chars (),
+			ftoa (m_hoverpos[Y]).chars (), ftoa (m_hoverpos[Z]).chars ());
+		
+		QFontMetrics metrics = QFontMetrics (font ());
+		QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
+		
+		paint.drawText (m_width - textSize.width (), m_height - 16, textSize.width (),
+			textSize.height (), Qt::AlignCenter, text);
+		
+		// If we're plane drawing, draw the vertices onto the screen.
+		if (m_planeDraw) {
+			ushort numverts = m_planeDrawVerts.size () + 1;
+			const short blipsize = 8;
+			
+			if (numverts > 0) {
+				QPoint* poly = new QPoint[numverts];
+				
+				uchar i = 0;
+				for (vertex& vert : m_planeDrawVerts) {
+					poly[i] = coord_3to2 (vert);
+					++i;
+				}
+				
+				// Draw the cursor vertex as the last one in the list.
+				if (numverts < 5)
+					poly[i] = coord_3to2 (m_hoverpos);
+				else
+					numverts = 4;
+				
+				paint.setPen (m_thinBorderPen);
+				paint.setBrush (QColor (128, 192, 0));
+				
+				// Draw vertex blips
+				for (ushort i = 0; i < numverts; ++i) {
+					QPoint& blip = poly[i];
+					paint.drawEllipse (blip.x () - blipsize / 2, blip.y () - blipsize / 2,
+						blipsize, blipsize);
+				}
+				
+				paint.setPen (m_thickBorderPen);
+				paint.setBrush (QColor (128, 192, 0, 128));
+				paint.drawPolygon (poly, numverts);
+				
+				delete[] poly;
+			}
+		}
+	}
+	
+	// Camera icons
+	if (!m_picking && m_planeDraw == false) {
+		// Draw a background for the selected camera
+		paint.setPen (m_thinBorderPen);
+		paint.setBrush (QBrush (QColor (0, 128, 160, 128)));
+		paint.drawRect (g_CameraIcons[camera ()].selRect);
+		
+		// Draw the actual icons
+		for (CameraIcon& info : g_CameraIcons)
+			paint.drawPixmap (info.destRect, *info.img, info.srcRect);
+		
+		// Draw a label for the current camera in the top left corner
+		{
+			const ushort margin = 4;
+			
+			str label;
+			label.format ("%s Camera", g_CameraNames[camera ()]);
+			paint.setBrush (Qt::black);
+			paint.drawText (QPoint (margin, margin + metrics.ascent ()), label);
+		}
+		
+		// Tool tips
+		if (m_drawToolTip) {
+			if (g_CameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false)
+				m_drawToolTip = false;
+			else {
+				QPen bord = m_thinBorderPen;
+				bord.setBrush (Qt::black);
+				
+				const ushort margin = 2;
+				ushort x0 = m_pos.x (),
+					y0 = m_pos.y ();
+				
+				str label;
+				label.format ("%s Camera", g_CameraNames[m_toolTipCamera]);
+				
+				const ushort textWidth = metrics.width (label),
+					textHeight = metrics.height (),
+					fullWidth = textWidth + (2 * margin),
+					fullHeight = textHeight + (2 * margin);
+				
+				QRect area (m_pos.x (), m_pos.y (), fullWidth, fullHeight);
+				
+				if (x0 + fullWidth > m_width)
+					x0 -= fullWidth;
+				
+				if (y0 + fullHeight > m_height)
+					y0 -= fullHeight;
+				
+				paint.setBrush (QColor (0, 128, 255, 208));
+				paint.setPen (bord);
+				paint.drawRect (x0, y0, fullWidth, fullHeight);
+				
+				paint.setBrush (Qt::black);
+				paint.drawText (QPoint (x0 + margin, y0 + margin + metrics.ascent ()), label);
+			}
+		}
+	}
+	
+	// If we're range-picking, draw a rectangle encompassing the selection area.
+	if (m_rangepick && !m_picking) {
+		const short x0 = m_rangeStart.x (),
+			y0 = m_rangeStart.y (),
+			x1 = m_pos.x (),
+			y1 = m_pos.y ();
+		
+		QRect rect (x0, y0, x1 - x0, y1 - y0);
+		QColor fillColor = (m_addpick ? "#80FF00" : "#00CCFF");
+		fillColor.setAlphaF (0.2f);
+		
+		paint.setPen (m_thickBorderPen);
+		paint.setBrush (QBrush (fillColor));
+		paint.drawRect (rect);
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::compileObjects () {
+	if (g_BBox.empty () == false) {
+		g_objOffset[X] = -(g_BBox.v0 ()[X] + g_BBox.v1 ()[X]) / 2;
+		g_objOffset[Y] = -(g_BBox.v0 ()[Y] + g_BBox.v1 ()[Y]) / 2;
+		g_objOffset[Z] = -(g_BBox.v0 ()[Z] + g_BBox.v1 ()[Z]) / 2;
+	} else {
+		// use a default bbox if we need 
+		g_objOffset[X] = g_objOffset[Y] = g_objOffset[Z] = 0;
+	}
+	
+	if (!g_curfile) {
+		printf ("renderer: no files loaded, cannot compile anything\n");
+		return;
+	}
+	
+	for (LDObject* obj : g_curfile->m_objs) {
+		GLuint* upaLists[2] = {
+			&obj->uGLList,
+			&obj->uGLPickList
+		};
+		
+		for (GLuint* upMemberList : upaLists) {
+			GLuint uList = glGenLists (1);
+			glNewList (uList, GL_COMPILE);
+			
+			m_picking = (upMemberList == &obj->uGLPickList);
+			compileOneObject (obj);
+			m_picking = false;
+			
+			glEndList ();
+			*upMemberList = uList;
+		}
+	}
+	
+	// Compile axes
+	m_axeslist = glGenLists (1);
+	glNewList (m_axeslist, GL_COMPILE);
+	glBegin (GL_LINES);
+	for (const GLAxis& ax : g_GLAxes) {
+		qglColor (ax.col);
+		compileVertex (ax.vert);
+		compileVertex (-ax.vert);
+	}
+	glEnd ();
+	glEndList ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::compileSubObject (LDObject* obj, const GLenum gltype) {
+	glBegin (gltype);
+	
+	const short numverts = (obj->getType () != LDObject::CondLine) ? obj->vertices () : 2;
+	
+	for (short i = 0; i < numverts; ++i)
+		compileVertex (obj->vaCoords[i]);
+	
+	glEnd ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::compileOneObject (LDObject* obj) {
+	setObjectColor (obj);
+	
+	switch (obj->getType ()) {
+	case LDObject::Line:
+		compileSubObject (obj, GL_LINES);
+		break;
+	
+	case LDObject::CondLine:
+		glLineStipple (1, 0x6666);
+		glEnable (GL_LINE_STIPPLE);
+		
+		compileSubObject (obj, GL_LINES);
+		
+		glDisable (GL_LINE_STIPPLE);
+		break;
+	
+	case LDObject::Triangle:
+		compileSubObject (obj, GL_TRIANGLES);
+		break;
+	
+	case LDObject::Quad:
+		compileSubObject (obj, GL_QUADS);
+		break;
+	
+	case LDObject::Subfile:
+		{
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			vector<LDObject*> objs = ref->inlineContents (true, true);
+			
+			for (LDObject* obj : objs) {
+				compileOneObject (obj);
+				delete obj;
+			}
+		}
+		break;
+	
+	case LDObject::Radial:
+		{
+			LDRadial* pRadial = static_cast<LDRadial*> (obj);
+			std::vector<LDObject*> objs = pRadial->decompose (true);
+			
+			for (LDObject* obj : objs) {
+				compileOneObject (obj);
+				delete obj;
+			}
+		}
+		break;
+	
+#if 0
+	TODO: find a proper way to draw vertices without having them be affected by zoom.
+	case LDObject::Vertex:
+		{
+			LDVertex* pVert = static_cast<LDVertex*> (obj);
+			LDTriangle* pPoly;
+			vertex* vPos = &(pVert->vPosition);
+			const double fPolyScale = max (fZoom, 1.0);
+			
+#define BIPYRAMID_COORD(N) ((((i + N) % 4) >= 2 ? 1 : -1) * 0.3f * fPolyScale)
+			
+			for (int i = 0; i < 8; ++i) {
+				pPoly = new LDTriangle;
+				pPoly->vaCoords[0] = {vPos->x, vPos->y + ((i >= 4 ? 1 : -1) * 0.4f * fPolyScale), vPos->z};
+				pPoly->vaCoords[1] = {
+					vPos->x + BIPYRAMID_COORD (0),
+					vPos->y,
+					vPos->z + BIPYRAMID_COORD (1)
+				};
+				
+				pPoly->vaCoords[2] = {
+					vPos->x + BIPYRAMID_COORD (1),
+					vPos->y,
+					vPos->z + BIPYRAMID_COORD (2)
+				};
+				
+				pPoly->dColor = pVert->dColor;
+				compileOneObject (pPoly);
+				delete pPoly;
+			}
+		}
+		break;
+#endif // 0
+	
+	default:
+		break;
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::compileVertex (const vertex& vrt) {
+	glVertex3d (
+		(vrt[X] + g_objOffset[0]),
+		-(vrt[Y] + g_objOffset[1]),
+		-(vrt[Z] + g_objOffset[2]));
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::clampAngle (double& angle) {
+	while (angle < 0)
+		angle += 360.0;
+	while (angle > 360.0)
+		angle -= 360.0;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::mouseReleaseEvent (QMouseEvent* ev) {
+	const bool wasLeft = (m_lastButtons & Qt::LeftButton) && !(ev->buttons() & Qt::LeftButton);
+	const bool wasRight = (m_lastButtons & Qt::RightButton) && !(ev->buttons() & Qt::RightButton);
+	
+	if (wasLeft) {
+		if (m_planeDraw) {
+			// If we picked an already-existing vertex, stop drawing
+			for (vertex& vert : m_planeDrawVerts) {
+				if (vert == m_hoverpos) {
+					endPlaneDraw (true);
+					return;
+				}
+			}
+			
+			// Also, if have 4 verts, also stop drawing.
+			if (m_planeDrawVerts.size () >= 4)
+				endPlaneDraw (true);
+			
+			m_planeDrawVerts.push_back (m_hoverpos);
+			
+			update ();
+			return;
+		} else {
+			if (!m_rangepick)
+				m_addpick = (m_keymods & Qt::ControlModifier);
+			
+			if (m_totalmove < 10 || m_rangepick)
+				pick (ev->x (), ev->y ());
+		}
+		
+		m_rangepick = false;
+		m_totalmove = 0;
+		return;
+	}
+	
+	if (wasRight && m_planeDraw) {
+		if (m_planeDrawVerts.size () > 0) {
+			// Remove the last vertex
+			m_planeDrawVerts.erase (m_planeDrawVerts.end () - 1);
+		} else {
+			endPlaneDraw (false);
+			return;
+		}
+		
+		update ();
+	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::mousePressEvent (QMouseEvent* ev) {
+	if (ev->buttons () & Qt::LeftButton)
+		m_totalmove = 0;
+	
+	if (ev->modifiers () & Qt::ShiftModifier) {
+		m_rangepick = true;
+		m_rangeStart.setX (ev->x ());
+		m_rangeStart.setY (ev->y ());
+		
+		m_addpick = (m_keymods & Qt::ControlModifier);
+	}
+	
+	m_lastButtons = ev->buttons ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::mouseMoveEvent (QMouseEvent* ev) {
+	int dx = ev->x () - m_pos.x ();
+	int dy = ev->y () - m_pos.y ();
+	m_totalmove += abs (dx) + abs (dy);
+	
+	if (ev->buttons () & Qt::LeftButton && !m_rangepick) {
+		m_rotX = m_rotX + (dy);
+		m_rotY = m_rotY + (dx);
+		
+		clampAngle (m_rotX);
+		clampAngle (m_rotY);
+	}
+	
+	if (ev->buttons () & Qt::MidButton) {
+		m_panX += 0.03f * dx * (m_zoom / 7.5f);
+		m_panY -= 0.03f * dy * (m_zoom / 7.5f);
+	}
+	
+	// Start the tool tip timer
+	if (!m_drawToolTip)
+		m_toolTipTimer->start (1000);
+	
+	m_pos = ev->pos ();
+	update ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::keyPressEvent (QKeyEvent* ev) {
+	m_keymods = ev->modifiers ();
+}
+
+void GLRenderer::keyReleaseEvent (QKeyEvent* ev) {
+	m_keymods = ev->modifiers ();
+}
+
+// =============================================================================
+void GLRenderer::wheelEvent (QWheelEvent* ev) {
+	m_zoom *= (ev->delta () < 0) ? 1.2f : (1.0f / 1.2f);
+	m_zoom = clamp (m_zoom, 0.01, 10000.0);
+	
+	update ();
+	ev->accept ();
+}
+
+// =============================================================================
+void GLRenderer::leaveEvent (QEvent* ev) {
+	Q_UNUSED (ev);
+	m_drawToolTip = false;
+	m_toolTipTimer->stop ();
+	update ();
+}
+
+// =============================================================================
+void GLRenderer::contextMenuEvent (QContextMenuEvent* ev) {
+	g_win->spawnContextMenu (ev->globalPos ());
+}
+
+// =============================================================================
+void GLRenderer::setCamera (const GLRenderer::Camera cam) {
+	m_camera = cam;
+	gl_camera = (int) cam;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::updateSelFlash () {
+	if (gl_selflash && g_win->sel ().size() > 0) {
+		m_pulseTimer->start (g_pulseInterval);
+		g_pulseTick = 0;
+	} else
+		m_pulseTimer->stop ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::pick (uint mouseX, uint mouseY) {
+	// Check if we selected a camera icon
+	if (!m_rangepick) {
+		QPoint pos (mouseX, mouseY);
+		
+		for (CameraIcon& info : g_CameraIcons) {
+			if (info.destRect.contains (pos)) {
+				setCamera (info.cam);
+				update ();
+				return;
+			}
+		}
+	}
+	
+	GLint viewport[4];
+	LDObject* removedObject = null;
+	
+	// Clear the selection if we do not wish to add to it.
+	if (!m_addpick) {
+		std::vector<LDObject*> oldsel = g_win->sel ();
+		g_win->sel ().clear ();
+		
+		// Recompile the prior selection to remove the highlight color
+		for (LDObject* obj : oldsel)
+			recompileObject (obj);
+	}
+	
+	m_picking = true;
+	
+	// Paint the picking scene
+	glDisable (GL_DITHER);
+	glClearColor (1.0f, 1.0f, 1.0f, 1.0f);
+	drawGLScene ();
+	
+	glGetIntegerv (GL_VIEWPORT, viewport);
+	
+	short x0 = mouseX,
+		y0 = mouseY;
+	short x1, y1;
+	
+	// Determine how big an area to read - with range picking, we pick by
+	// the area given, with single pixel picking, we use an 1 x 1 area.
+	if (m_rangepick) {
+		x1 = m_rangeStart.x ();
+		y1 = m_rangeStart.y ();
+	} else {
+		x1 = x0 + 1;
+		y1 = y0 + 1;
+	}
+	
+	// x0 and y0 must be less than x1 and y1, respectively.
+	if (x0 > x1)
+		dataswap (x0, x1);
+	
+	if (y0 > y1)
+		dataswap (y0, y1);
+	
+	// Clamp the values to ensure they're within bounds
+	x0 = max<short> (0, x0);
+	y0 = max<short> (0, y0);
+	x1 = min<short> (x1, m_width);
+	y1 = min<short> (y1, m_height);
+	
+	const short areawidth = (x1 - x0);
+	const short areaheight = (y1 - y0);
+	const long numpixels = areawidth * areaheight;
+	
+	// Allocate space for the pixel data.
+	uchar* const pixeldata = new uchar[4 * numpixels];
+	uchar* pixelptr = &pixeldata[0];
+	
+	assert (viewport[3] == m_height);
+	
+	// Read pixels from the color buffer.
+	glReadPixels (x0, viewport[3] - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata);
+	
+	// Go through each pixel read and add them to the selection.
+	for (long i = 0; i < numpixels; ++i) {
+		uint32 idx =
+			(*(pixelptr) * 0x10000) +
+			(*(pixelptr + 1) * 0x00100) +
+			(*(pixelptr + 2) * 0x00001);
+		pixelptr += 4;
+		
+		if (idx == 0xFFFFFF)
+			continue; // White is background; skip
+		
+		LDObject* obj = g_curfile->object (idx);
+		
+		// If this is an additive single pick and the object is currently selected,
+		// we remove it from selection instead.
+		if (!m_rangepick && m_addpick) {
+			bool removed = false;
+			
+			for (ulong i = 0; i < g_win->sel ().size(); ++i) {
+				if (g_win->sel ()[i] == obj) {
+					g_win->sel ().erase (g_win->sel ().begin () + i);
+					removedObject = obj;
+					removed = true;
+				}
+			}
+			
+			if (removed)
+				break;
+		}
+		
+		g_win->sel ().push_back (obj);
+	}
+	
+	delete[] pixeldata;
+	
+	// Remove duplicate entries. For this to be effective, the vector must be
+	// sorted first.
+	std::vector<LDObject*>& sel = g_win->sel ();
+	std::sort (sel.begin(), sel.end ());
+	std::vector<LDObject*>::iterator pos = std::unique (sel.begin (), sel.end ());
+	sel.resize (std::distance (sel.begin (), pos));
+	
+	// Update everything now.
+	g_win->buildObjList ();
+	
+	m_picking = false;
+	m_rangepick = false;
+	glEnable (GL_DITHER);
+	
+	setBackground ();
+	updateSelFlash ();
+	
+	for (LDObject* obj : g_win->sel ())
+		recompileObject (obj);
+	
+	if (removedObject != null)
+		recompileObject (removedObject);
+	
+	drawGLScene ();
+	swapBuffers ();
+	update ();
+}
+
+// =============================================================================
+void GLRenderer::beginPlaneDraw () {
+	if (m_camera == Free)
+		return; // Cannot draw with the free camera
+	
+	m_planeDraw = true;
+	
+	// Disable the context menu - we need the right mouse button
+	// for removing vertices.
+	setContextMenuPolicy (Qt::NoContextMenu);
+	
+	// Use the crosshair cursor when plane drawing.
+	setCursor (Qt::CrossCursor);
+	
+	// Clear the selection when beginning to draw onto a plane.
+	// FIXME: make the selection clearing stuff in ::pick a method and use it
+	// here! This code doesn't update the GL lists.
+	g_win->sel ().clear ();
+	g_win->updateSelection ();
+	update ();
+}
+
+// =============================================================================
+void GLRenderer::endPlaneDraw (bool accept) {
+	// If we accepted, clean the selection and create the object
+	if (accept) {
+		vector<vertex>& verts = m_planeDrawVerts;
+		LDObject* obj = null;
+		
+		switch (verts.size ()) {
+		case 1:
+			{
+				// 1 vertex - add a vertex object
+				obj = new LDVertex;
+				static_cast<LDVertex*> (obj)->vPosition = verts[0];
+				obj->dColor = maincolor;
+			}
+			break;
+		
+		case 2:
+			{
+				// 2 verts - make a line
+				obj = new LDLine;
+				obj->dColor = edgecolor;
+				for (ushort i = 0; i < 2; ++i)
+					obj->vaCoords[i] = verts[i];
+			}
+			break;
+			
+		case 3:
+		case 4:
+			{
+				obj = (verts.size () == 3) ?
+					static_cast<LDObject*> (new LDTriangle) :
+					static_cast<LDObject*> (new LDQuad);
+				
+				obj->dColor = maincolor;
+				for (ushort i = 0; i < obj->vertices (); ++i)
+					obj->vaCoords[i] = verts[i];
+			}
+			break;
+		}
+		
+		if (obj) {
+			g_curfile->addObject (obj);
+			recompileObject (obj);
+			g_win->refresh ();
+			
+			History::addEntry (new AddHistory ({(ulong) obj->getIndex (g_curfile)}, {obj->clone ()}));
+		}
+	}
+	
+	m_planeDraw = false;
+	m_planeDrawVerts.clear ();
+	
+	unsetCursor ();
+	
+	// Restore the context menu
+	setContextMenuPolicy (Qt::DefaultContextMenu);
+	
+	update ();
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void GLRenderer::recompileObject (LDObject* obj) {
+	// Replace the old list with the new one.
+	GLuint uList = glGenLists (1);
+	glNewList (uList, GL_COMPILE);
+	
+	compileOneObject (obj);
+	
+	glEndList ();
+	obj->uGLList = uList;
+}
+
+// =============================================================================
+uchar* GLRenderer::screencap (ushort& w, ushort& h) {
+	w = m_width;
+	h = m_height;
+	uchar* cap = new uchar[4 * w * h];
+	
+	m_screencap = true;
+	update ();
+	m_screencap = false;
+	
+	// Capture the pixels
+	glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap);
+	
+	// Restore the background
+	setBackground ();
+	
+	return cap;
+}
+
+// =============================================================================
+void GLRenderer::slot_timerUpdate () {
+	++g_pulseTick %= g_numPulseTicks;
+	
+	for (LDObject* obj : g_win->sel ())
+		recompileObject (obj);
+	
+	update ();
+}
+
+// =============================================================================
+void GLRenderer::slot_toolTipTimer () {
+	// We come here if the cursor has stayed in one place for longer than a
+	// a second. Check if we're holding it over a camera icon - if so, draw
+	// a tooltip.
+	for (CameraIcon& icon : g_CameraIcons) {
+		if (icon.destRect.contains (m_pos)) {
+			m_toolTipCamera = icon.cam;
+			m_drawToolTip = true;
+			update ();
+			break;
+		}
+	}
+}
\ No newline at end of file

mercurial