Exported GLRenderer cameras into a new class, GLCamera.

Tue, 14 Feb 2017 07:57:27 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Tue, 14 Feb 2017 07:57:27 +0200
changeset 1129
2b20261dc511
parent 1128
67a30f5d159c
child 1130
17f92eb2a30c

Exported GLRenderer cameras into a new class, GLCamera.

CMakeLists.txt file | annotate | diff | comparison | revisions
src/canvas.cpp file | annotate | diff | comparison | revisions
src/canvas.h file | annotate | diff | comparison | revisions
src/editmodes/abstractEditMode.cpp file | annotate | diff | comparison | revisions
src/editmodes/circleMode.cpp file | annotate | diff | comparison | revisions
src/editmodes/curvemode.cpp file | annotate | diff | comparison | revisions
src/editmodes/linePathMode.cpp file | annotate | diff | comparison | revisions
src/editmodes/rectangleMode.cpp file | annotate | diff | comparison | revisions
src/glRenderer.cpp file | annotate | diff | comparison | revisions
src/glRenderer.h file | annotate | diff | comparison | revisions
src/glcamera.cpp file | annotate | diff | comparison | revisions
src/glcamera.h file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Mon Feb 13 20:59:16 2017 +0200
+++ b/CMakeLists.txt	Tue Feb 14 07:57:27 2017 +0200
@@ -35,8 +35,9 @@
 	src/documentloader.cpp
 	src/documentmanager.cpp
 	src/editHistory.cpp
+	src/glcamera.cpp
+	src/glCompiler.cpp
 	src/glRenderer.cpp
-	src/glCompiler.cpp
 	src/grid.cpp
 	src/guiutilities.cpp
 	src/hierarchyelement.cpp
@@ -94,6 +95,7 @@
 	src/doublemap.h
 	src/editHistory.h
 	src/format.h
+	src/glcamera.h
 	src/glCompiler.h
 	src/glRenderer.h
 	src/grid.h
--- a/src/canvas.cpp	Mon Feb 13 20:59:16 2017 +0200
+++ b/src/canvas.cpp	Tue Feb 14 07:57:27 2017 +0200
@@ -19,6 +19,7 @@
 #include <QMouseEvent>
 #include "canvas.h"
 #include "documentmanager.h"
+#include "glcamera.h"
 #include "grid.h"
 #include "ldDocument.h"
 #include "mainwindow.h"
@@ -148,7 +149,7 @@
 void Canvas::mouseMoveEvent(QMouseEvent* event)
 {
 	// Calculate 3d position of the cursor
-	m_position3D = convert2dTo3d(mousePosition(), true);
+	m_position3D = currentCamera().convert2dTo3d(mousePosition(), grid());
 
 	if (not m_currentEditMode->mouseMoved(event))
 		GLRenderer::mouseMoveEvent(event);
@@ -190,7 +191,7 @@
 
 void Canvas::drawBlipCoordinates(QPainter& painter, const Vertex& pos3d) const
 {
-	drawBlipCoordinates (painter, pos3d, convert3dTo2d (pos3d));
+	drawBlipCoordinates(painter, pos3d, currentCamera().convert3dTo2d(pos3d));
 }
 
 void Canvas::drawBlipCoordinates(QPainter& painter, const Vertex& pos3d, QPointF pos) const
@@ -210,24 +211,22 @@
 
 int Canvas::depthNegateFactor() const
 {
-	return cameraInfo(camera()).negatedDepth ? -1 : 1;
+	return currentCamera().isAxisNegated(Z) ? -1 : 1;
 }
 
 // =============================================================================
 //
 void Canvas::getRelativeAxes(Axis& relativeX, Axis& relativeY) const
 {
-	const CameraInfo& camera = cameraInfo(this->camera());
-	relativeX = camera.localX;
-	relativeY = camera.localY;
+	relativeX = currentCamera().axisX();
+	relativeY = currentCamera().axisY();
 }
 
 // =============================================================================
 //
 Axis Canvas::getRelativeZ() const
 {
-	const CameraInfo& camera = cameraInfo(this->camera());
-	return static_cast<Axis>(3 - camera.localX - camera.localY);
+	return currentCamera().axisZ();
 }
 
 // =============================================================================
@@ -248,69 +247,6 @@
 		return 0.0;
 }
 
-/*
- * This converts a 2D point on the screen to a 3D point in the model. If 'snap' is true, the 3D point will snap to the current grid.
- */
-Vertex Canvas::convert2dTo3d(const QPoint& position2d, bool snap) const
-{
-	if (camera() == Camera::Free)
-	{
-		return {0, 0, 0};
-	}
-	else
-	{
-		Vertex position3d;
-		const CameraInfo& camera = cameraInfo(this->camera());
-		Axis axisX = camera.localX;
-		Axis axisY = camera.localY;
-		int signX = camera.negatedX ? -1 : 1;
-		int signY = camera.negatedY ? -1 : 1;
-
-		// Calculate cx and cy - these are the LDraw unit coords the cursor is at.
-		double cx = (-virtualWidth() + ((2 * position2d.x() * virtualWidth()) / width()) - panning(X));
-		double cy = (virtualHeight() - ((2 * position2d.y() * virtualHeight()) / height()) - panning(Y));
-
-		if (snap)
-		{
-			cx = grid()->snap(cx, Grid::Coordinate);
-			cy = grid()->snap(cy, Grid::Coordinate);
-		}
-
-		cx *= signX;
-		cy *= signY;
-		roundToDecimals(cx, 4);
-		roundToDecimals(cy, 4);
-
-		// Create the vertex from the coordinates
-		position3d.setCoordinate(axisX, cx);
-		position3d.setCoordinate(axisY, cy);
-		position3d.setCoordinate(static_cast<Axis>(3 - axisX - axisY), getDepthValue());
-		return position3d;
-	}
-}
-
-/*
- * Inverse operation for the above - convert a 3D position to a 2D screen position.
- */
-QPoint Canvas::convert3dTo2d(const Vertex& position3d) const
-{
-	if (camera() == Camera::Free)
-	{
-		return {0, 0};
-	}
-	else
-	{
-		const CameraInfo& camera = cameraInfo(this->camera());
-		Axis axisX = camera.localX;
-		Axis axisY = camera.localY;
-		int signX = camera.negatedX ? -1 : 1;
-		int signY = camera.negatedY ? -1 : 1;
-		int rx = (((position3d[axisX] * signX) + virtualWidth() + panning(X)) * width()) / (2 * virtualWidth());
-		int ry = (((position3d[axisY] * signY) - virtualHeight() + panning(Y)) * height()) / (2 * virtualHeight());
-		return {rx, -ry};
-	}
-}
-
 void Canvas::contextMenuEvent(QContextMenuEvent* event)
 {
 	m_window->spawnContextMenu(event->globalPos());
--- a/src/canvas.h	Mon Feb 13 20:59:16 2017 +0200
+++ b/src/canvas.h	Tue Feb 14 07:57:27 2017 +0200
@@ -26,8 +26,6 @@
 	Canvas(LDDocument* document, QWidget* parent = nullptr);
 	~Canvas();
 
-	Vertex convert2dTo3d(const QPoint& pos2d, bool snap) const;
-	QPoint convert3dTo2d(const Vertex& pos3d) const;
 	EditModeType currentEditModeType() const;
 	int depthNegateFactor() const;
 	LDDocument* document() const;
--- a/src/editmodes/abstractEditMode.cpp	Mon Feb 13 20:59:16 2017 +0200
+++ b/src/editmodes/abstractEditMode.cpp	Tue Feb 14 07:57:27 2017 +0200
@@ -116,7 +116,7 @@
 		// Find the closest vertex to our cursor
 		double minimumDistance = 1024.0;
 		const Vertex* closest = nullptr;
-		Vertex cursorPosition = renderer()->convert2dTo3d(data.ev->pos(), false);
+		Vertex cursorPosition = renderer()->currentCamera().convert2dTo3d(data.ev->pos());
 		QPoint cursorPosition2D = data.ev->pos();
 		const Axis depthAxis = renderer()->getRelativeZ();
 		QList<Vertex> vertices = currentDocument()->inlineVertices().toList();
@@ -133,8 +133,8 @@
 		for (const Vertex& vertex : vertices)
 		{
 			// If the vertex in 2d space is very close to the cursor then we use it regardless of depth.
-			QPoint vect2d = renderer()->convert3dTo2d(vertex) - cursorPosition2D;
-			double distance2DSquared = std::pow (vect2d.x(), 2) + std::pow (vect2d.y(), 2);
+			QPoint vect2d = renderer()->currentCamera().convert3dTo2d(vertex) - cursorPosition2D;
+			double distance2DSquared = std::pow(vect2d.x(), 2) + std::pow(vect2d.y(), 2);
 
 			if (distance2DSquared < 16.0 * 16.0)
 			{
@@ -232,7 +232,7 @@
 
 	// Convert to 2D
 	for (int i = 0; i < countof(polygon3d); ++i)
-		polygon2d[i] = renderer()->convert3dTo2d(polygon3d[i]);
+		polygon2d[i] = renderer()->currentCamera().convert3dTo2d(polygon3d[i]);
 
 	// Draw the polygon-to-be
 	painter.setBrush(m_polybrush);
--- a/src/editmodes/circleMode.cpp	Mon Feb 13 20:59:16 2017 +0200
+++ b/src/editmodes/circleMode.cpp	Tue Feb 14 07:57:27 2017 +0200
@@ -48,7 +48,7 @@
 		if (countof(m_drawedVerts) >= position + 2)
 			v1 = m_drawedVerts[position + 1];
 		else
-			v1 = renderer()->convert2dTo3d (renderer()->mousePosition(), false);
+			v1 = renderer()->currentCamera().convert2dTo3d(renderer()->mousePosition(), grid());
 
 		Axis localx, localy;
 		renderer()->getRelativeAxes(localx, localy);
@@ -193,7 +193,7 @@
 	if (not m_drawedVerts.isEmpty())
 	{
 		int divisions = m_window->ringToolHiRes() ? HighResolution : LowResolution;
-		QPointF originSpot = renderer()->convert3dTo2d(m_drawedVerts.first());
+		QPointF originSpot = renderer()->currentCamera().convert3dTo2d(m_drawedVerts.first());
 		// Line from the origin of the circle to current mouse position
 		QLineF hand1 = {originSpot, renderer()->mousePositionF()};
 		// Line from the origin spot to
@@ -218,7 +218,7 @@
 	// If we have not specified the center point of the circle yet, preview it on the screen.
 	if (m_drawedVerts.isEmpty())
 	{
-		QPoint position2d = renderer()->convert3dTo2d(renderer()->position3D());
+		QPoint position2d = renderer()->currentCamera().convert3dTo2d(renderer()->position3D());
 		renderer()->drawPoint(painter, position2d);
 		renderer()->drawBlipCoordinates(painter, renderer()->position3D(), position2d);
 		return;
@@ -240,18 +240,18 @@
 	{
 		const double sinangle (sin (angleoffset + i * angleUnit));
 		const double cosangle (cos (angleoffset + i * angleUnit));
-		Vertex v (Origin);
-		v.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * innerdistance));
-		v.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * innerdistance));
-		innerverts << v;
-		innerverts2d << renderer()->convert3dTo2d (v);
+		Vertex vertex;
+		vertex.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * innerdistance));
+		vertex.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * innerdistance));
+		innerverts << vertex;
+		innerverts2d << renderer()->currentCamera().convert3dTo2d(vertex);
 
 		if (outerdistance != -1)
 		{
-			v.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * outerdistance));
-			v.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * outerdistance));
-			outerverts << v;
-			outerverts2d << renderer()->convert3dTo2d (v);
+			vertex.setCoordinate (relX, m_drawedVerts[0][relX] + (cosangle * outerdistance));
+			vertex.setCoordinate (relY, m_drawedVerts[0][relY] + (sinangle * outerdistance));
+			outerverts << vertex;
+			outerverts2d << renderer()->currentCamera().convert3dTo2d(vertex);
 		}
 	}
 
@@ -297,7 +297,7 @@
 	painter.drawLines(lines);
 
 	// Draw the current radius in the middle of the circle.
-	QPoint origin = renderer()->convert3dTo2d (m_drawedVerts[0]);
+	QPoint origin = renderer()->currentCamera().convert3dTo2d (m_drawedVerts[0]);
 	QString label = QString::number (innerdistance);
 	painter.setPen(renderer()->textPen());
 	painter.drawText(origin.x() - (metrics.width(label) / 2), origin.y(), label);
--- a/src/editmodes/curvemode.cpp	Mon Feb 13 20:59:16 2017 +0200
+++ b/src/editmodes/curvemode.cpp	Tue Feb 14 07:57:27 2017 +0200
@@ -45,7 +45,7 @@
 			curve[3] = curve[2];
 
 		for (int i = 0; i < countof(curve); ++i)
-			curve2d[i] = renderer()->convert3dTo2d (curve[i]);
+			curve2d[i] = renderer()->currentCamera().convert3dTo2d(curve[i]);
 
 		painter.setPen (QColor (0, 112, 112));
 		if (countof(m_drawedVerts) >= 2)
@@ -71,7 +71,7 @@
 	else
 	{
 		// Even if we have nothing, still draw the vertex at the cursor
-		QPoint vertex2d = renderer()->convert3dTo2d (getCursorVertex());
+		QPoint vertex2d = renderer()->currentCamera().convert3dTo2d(getCursorVertex());
 		renderer()->drawPoint (painter, vertex2d);
 		renderer()->drawBlipCoordinates (painter, getCursorVertex(), vertex2d);
 	}
--- a/src/editmodes/linePathMode.cpp	Mon Feb 13 20:59:16 2017 +0200
+++ b/src/editmodes/linePathMode.cpp	Tue Feb 14 07:57:27 2017 +0200
@@ -31,7 +31,7 @@
 	points3d << renderer()->position3D();
 
 	for (Vertex const& vrt : points3d)
-		points << renderer()->convert3dTo2d (vrt);
+		points << renderer()->currentCamera().convert3dTo2d(vrt);
 
 	painter.setPen (renderer()->textPen());
 
--- a/src/editmodes/rectangleMode.cpp	Mon Feb 13 20:59:16 2017 +0200
+++ b/src/editmodes/rectangleMode.cpp	Tue Feb 14 07:57:27 2017 +0200
@@ -74,9 +74,9 @@
 	Vertex v0 = m_drawedVerts[0],
 		   v1 = (countof(m_drawedVerts) >= 2) ? m_drawedVerts[1] : renderer()->position3D();
 
-	const Axis localx = renderer()->getCameraAxis (false),
-			   localy = renderer()->getCameraAxis (true),
-			   localz = (Axis) (3 - localx - localy);
+	Axis localx, localy, localz;
+	renderer()->getRelativeAxes(localx, localy);
+	localz = renderer()->getRelativeZ();
 
 	for (int i = 0; i < 4; ++i)
 		m_rectangleVerts[i].setCoordinate (localz, renderer()->getDepthValue());
--- a/src/glRenderer.cpp	Mon Feb 13 20:59:16 2017 +0200
+++ b/src/glRenderer.cpp	Tue Feb 14 07:57:27 2017 +0200
@@ -37,13 +37,13 @@
 
 const CameraInfo g_cameraInfo[EnumLimits<Camera>::Count] =
 {
-	{{  1,  0, 0 }, X, Z, false, false, false }, // top
-	{{  0,  0, 0 }, X, Y, false,  true, false }, // front
-	{{  0,  1, 0 }, Z, Y,  true,  true, false }, // left
-	{{ -1,  0, 0 }, X, Z, false,  true, true }, // bottom
-	{{  0,  0, 0 }, X, Y,  true,  true, true }, // back
-	{{  0, -1, 0 }, Z, Y, false,  true, true }, // right
-	{{  1,  0, 0 }, X, Z, false, false, false }, // free (defensive dummy data)
+    {{  1,  0, 0 }, X, Z, false, false, false }, // top
+    {{  0,  0, 0 }, X, Y, false,  true, false }, // front
+    {{  0,  1, 0 }, Z, Y,  true,  true, false }, // left
+    {{ -1,  0, 0 }, X, Z, false,  true, true }, // bottom
+    {{  0,  0, 0 }, X, Y,  true,  true, true }, // back
+    {{  0, -1, 0 }, Z, Y, false,  true, true }, // right
+    {{  1,  0, 0 }, X, Z, false, false, false }, // free (defensive dummy data)
 };
 
 const QPen GLRenderer::thinBorderPen {QColor {0, 0, 0, 208}, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin};
@@ -53,7 +53,16 @@
 GLRenderer::GLRenderer(const Model* model, QWidget* parent) :
     QGLWidget {parent},
     HierarchyElement {parent},
-    m_model {model}
+    m_model {model},
+    m_cameras {
+        {1,  0, 0, X, Z, false, false, false}, // top
+        {0,  0, 0, X, Y, false,  true, false}, // front
+        {0,  1, 0, Z, Y,  true,  true, false}, // left
+        {-1,  0, 0, X, Z, false,  true, true}, // bottom
+        {0,  0, 0, X, Y,  true,  true, true}, // back
+        {0, -1, 0, Z, Y, false,  true, true}, // right
+        {GLCamera::FreeCamera}, // free
+    }
 {
 	m_camera = (Camera) m_config->camera();
 	m_compiler = new GLCompiler (this);
@@ -99,26 +108,45 @@
 void GLRenderer::calcCameraIcons()
 {
 	int i = 0;
+	const int columns = 3;
+	const int firstAtLastRow = countof(m_cameras) - (countof(m_cameras) % columns);
 
-	for (CameraIcon& info : m_cameraIcons)
+	for (CameraIcon& cameraIcon : m_cameraIcons)
 	{
-		// MATH
-		int x1 = (width() - (info.camera != Camera::Free ? 48 : 16)) + ((i % 3) * 16) - 1;
-		int y1 = ((i / 3) * 16) + 1;
+		int row = i / columns;
+		int column;
 
-		info.sourceRect = QRect (0, 0, 16, 16);
-		info.targetRect = QRect (x1, y1, 16, 16);
-		info.hitRect = QRect (
-			info.targetRect.x(),
-			info.targetRect.y(),
-			info.targetRect.width() + 1,
-			info.targetRect.height() + 1
-		);
+		if (i < firstAtLastRow)
+			column = i % columns;
+		else
+			column = i + columns - (countof(m_cameras) % columns);
+
+		int x1 = width() - 48 + (column * 16) - 1;
+		int y1 = (row * 16) + 1;
+
+		cameraIcon.sourceRect = {0, 0, 16, 16};
+		cameraIcon.targetRect = {x1, y1, 16, 16};
+		cameraIcon.hitRect = {
+		    cameraIcon.targetRect.x(),
+		    cameraIcon.targetRect.y(),
+		    cameraIcon.targetRect.width() + 1,
+		    cameraIcon.targetRect.height() + 1
+		};
 
 		++i;
 	}
 }
 
+GLCamera& GLRenderer::currentCamera()
+{
+	return m_cameras[static_cast<int>(camera())];
+}
+
+const GLCamera& GLRenderer::currentCamera() const
+{
+	return m_cameras[static_cast<int>(camera())];
+}
+
 // =============================================================================
 //
 void GLRenderer::initGLData()
@@ -187,7 +215,7 @@
 		glGetFloatv(GL_MODELVIEW_MATRIX, m_rotationMatrix.data());
 		glPopMatrix();
 	}
-	panning(X) = panning(Y) = 0.0f;
+	currentCamera().setPanning(0, 0);
 	needZoomToFit();
 }
 
@@ -334,6 +362,10 @@
 	glLoadIdentity();
 	gluPerspective (45.0f, (double) width / (double) height, 1.0f, 10000.0f);
 	glMatrixMode (GL_MODELVIEW);
+
+	// Unfortunately Qt does not provide a resized() signal so we have to manually feed the information.
+	for (GLCamera& camera : m_cameras)
+		camera.rendererResized(width, height);
 }
 
 // =============================================================================
@@ -346,9 +378,6 @@
 		zoomAllToFit();
 	}
 
-	m_virtualWidth = zoom();
-	m_virtualHeight = (height() * m_virtualWidth) / width();
-
 	if (m_config->drawWireframe() and not m_isDrawingSelectionScene)
 		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
 
@@ -366,15 +395,12 @@
 		glPushMatrix();
 
 		glLoadIdentity();
-		glOrtho (-m_virtualWidth, m_virtualWidth, -m_virtualHeight, m_virtualHeight, -100.0f, 100.0f);
+		const QSizeF& virtualSize = currentCamera().virtualSize();
+		glOrtho(-virtualSize.width(), virtualSize.width(), -virtualSize.height(), virtualSize.height(), -100.0f, 100.0f);
 		glTranslatef(panning (X), panning (Y), 0.0f);
 
 		if (camera() != Camera::Front and camera() != Camera::Back)
-		{
-			glRotatef(90.0f, g_cameraInfo[static_cast<int>(camera())].glrotate[0],
-			    g_cameraInfo[static_cast<int>(camera())].glrotate[1],
-			    g_cameraInfo[static_cast<int>(camera())].glrotate[2]);
-		}
+			glRotatef(90.0f, currentCamera().glRotate(X), currentCamera().glRotate(Y), currentCamera().glRotate(Z));
 
 		// Back camera needs to be handled differently
 		if (camera() == Camera::Back)
@@ -604,8 +630,7 @@
 
 	if (mid or (left and shift))
 	{
-		panning(X) += 0.03f * xMove * (zoom() / 7.5f);
-		panning(Y) -= 0.03f * yMove * (zoom() / 7.5f);
+		currentCamera().pan(xMove, yMove);
 		m_panning = true;
 		m_isCameraMoving = true;
 	}
@@ -657,8 +682,7 @@
 void GLRenderer::wheelEvent(QWheelEvent* ev)
 {
 	makeCurrent();
-	zoomNotch(ev->delta() > 0);
-	zoom() = qBound(0.01, zoom(), 10000.0);
+	currentCamera().zoomNotch(ev->delta() > 0);
 	m_isCameraMoving = true;
 	update();
 	ev->accept();
@@ -827,17 +851,6 @@
 
 // =============================================================================
 //
-Axis GLRenderer::getCameraAxis (bool y, Camera camid)
-{
-	if (camid == (Camera) -1)
-		camid = camera();
-
-	const CameraInfo& cameraData = cameraInfo(camid);
-	return (y) ? cameraData.localY : cameraData.localX;
-}
-
-// =============================================================================
-//
 QString GLRenderer::cameraName (Camera camera) const
 {
 	switch (camera)
@@ -862,16 +875,9 @@
 
 // =============================================================================
 //
-void GLRenderer::zoomNotch (bool inward)
-{
-	zoom() *= inward ? 0.833f : 1.2f;
-}
-
-// =============================================================================
-//
 void GLRenderer::zoomToFit()
 {
-	zoom() = 30.0f;
+	currentCamera().setZoom(30.0f);
 	bool lastfilled = false;
 	bool firstrun = true;
 	enum { black = 0xFF000000 };
@@ -887,11 +893,11 @@
 		if (zoom() > 10000.0 or zoom() < 0.0)
 		{
 			// Nothing to draw if we get here.
-			zoom() = 30.0;
+			currentCamera().setZoom(30.0);
 			break;
 		}
 
-		zoomNotch (inward);
+		currentCamera().zoomNotch(inward);
 		QVector<unsigned char> capture (4 * width() * height());
 		drawGLScene();
 		glReadPixels (0, 0, width(), height(), GL_RGBA, GL_UNSIGNED_BYTE, capture.data());
@@ -935,7 +941,7 @@
 			// last run had ideal zoom - zoom a bit back and we should reach it.
 			if (filled and not lastfilled)
 			{
-				zoomNotch (false);
+				currentCamera().zoomNotch(false);
 				break;
 			}
 
@@ -1036,19 +1042,14 @@
 	return m_camera;
 }
 
-double& GLRenderer::panning (Axis ax)
-{
-	return (ax == X) ? m_panX[static_cast<int>(camera())] : m_panY[static_cast<int>(camera())];
-}
-
 double GLRenderer::panning (Axis ax) const
 {
-	return (ax == X) ? m_panX[static_cast<int>(camera())] : m_panY[static_cast<int>(camera())];
+	return (ax == X) ? currentCamera().panningX() : currentCamera().panningY();
 }
 
-double& GLRenderer::zoom()
+double GLRenderer::zoom()
 {
-	return m_zoom[static_cast<int>(camera())];
+	return currentCamera().zoom();
 }
 
 const QGenericMatrix<4, 4, GLfloat>& GLRenderer::rotationMatrix() const
@@ -1066,16 +1067,6 @@
 	return m_lastButtons;
 }
 
-double GLRenderer::virtualHeight() const
-{
-	return m_virtualHeight;
-}
-
-double GLRenderer::virtualWidth() const
-{
-	return m_virtualWidth;
-}
-
 const Model* GLRenderer::model() const
 {
 	return m_model;
--- a/src/glRenderer.h	Mon Feb 13 20:59:16 2017 +0200
+++ b/src/glRenderer.h	Tue Feb 14 07:57:27 2017 +0200
@@ -21,6 +21,7 @@
 #include "main.h"
 #include "model.h"
 #include "glShared.h"
+#include "glcamera.h"
 
 class GLCompiler;
 class MessageManager;
@@ -56,8 +57,6 @@
 	_End
 };
 
-MAKE_ITERABLE_ENUM(Camera)
-
 struct CameraIcon
 {
 	QPixmap image;
@@ -67,6 +66,8 @@
 	Camera camera;
 };
 
+MAKE_ITERABLE_ENUM(Camera)
+
 // The main renderer object, draws the brick on the screen, manages the camera and selection picking.
 class GLRenderer : public QGLWidget, protected QOpenGLFunctions, public HierarchyElement
 {
@@ -82,10 +83,11 @@
 	QString cameraName(Camera camera) const;
 	QByteArray capturePixels();
 	GLCompiler* compiler() const;
+	GLCamera& currentCamera();
+	const GLCamera& currentCamera() const;
 	QString currentCameraName() const;
 	void drawGLScene();
 	void forgetObject(LDObject* obj);
-	Axis getCameraAxis(bool y, Camera camid = (Camera) -1);
 	void highlightCursorObject();
 	void initGLData();
 	bool isDrawOnly() const;
@@ -107,9 +109,6 @@
 	void setDrawOnly(bool value);
 	void setPicking(bool a);
 	QPen textPen() const;
-	double virtualHeight() const;
-	double virtualWidth() const;
-	void zoomNotch(bool inward);
 
 	static const QPen thinBorderPen;
 
@@ -134,8 +133,7 @@
 	Qt::MouseButtons lastButtons() const;
 	double panning (Axis ax) const;
 	const QGenericMatrix<4, 4, GLfloat>& rotationMatrix() const;
-	double& panning (Axis ax);
-	double& zoom();
+	double zoom();
 
 	template<typename... Args>
 	QString format (QString fmtstr, Args... args)
@@ -151,12 +149,8 @@
 	QTimer* m_toolTipTimer;
 	Qt::MouseButtons m_lastButtons;
 	Qt::KeyboardModifiers m_currentKeyboardModifiers;
-	double m_virtualWidth;
-	double m_virtualHeight;
 	QGenericMatrix<4, 4, GLfloat> m_rotationMatrix;
-	double m_panX[7] = {0};
-	double m_panY[7] = {0};
-	double m_zoom[7] = {30};
+	GLCamera m_cameras[7];
 	bool m_useDarkBackground = false;
 	bool m_drawToolTip = false;
 	bool m_takingScreenCapture = false;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/glcamera.cpp	Tue Feb 14 07:57:27 2017 +0200
@@ -0,0 +1,234 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2017 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "glcamera.h"
+#include "grid.h"
+#include "miscallenous.h"
+
+/*
+ * Constructs a fixed camera from parameters.
+ */
+GLCamera::GLCamera(int glRotateX, int glRotateY, int glRotateZ, Axis localX, Axis localY, bool negatedX, bool negatedY, bool negatedDepth) :
+    m_glrotate {glRotateX, glRotateY, glRotateZ},
+    m_localX {localX},
+    m_localY {localY},
+    m_negatedX {negatedX},
+    m_negatedY {negatedY},
+    m_negatedDepth {negatedDepth} {}
+
+/*
+ * Constructs a free camera.
+ */
+GLCamera::GLCamera(FreeToken) :
+    m_isFree {true} {}
+
+/*
+ * Returns OpenGL rotation information for this camera.
+ */
+int GLCamera::glRotate(Axis axis) const
+{
+	return m_glrotate[axis];
+}
+
+/*
+ * Returns whether or not the given axis is negated on this camera.
+ */
+bool GLCamera::isAxisNegated(Axis axis) const
+{
+	switch (axis)
+	{
+	case X:
+		return m_negatedX;
+
+	case Y:
+		return m_negatedY;
+
+	case Z:
+		return m_negatedDepth;
+
+	default:
+		return false;
+	}
+}
+
+/*
+ * Returns the 3D axis that is on the X axis in this camera.
+ */
+Axis GLCamera::axisX() const
+{
+	return m_localX;
+}
+
+/*
+ * Returns the 3D axis that is on the Y axis in this camera.
+ */
+Axis GLCamera::axisY() const
+{
+	return m_localY;
+}
+
+/*
+ * Returns the 3D axis that is on the Z axis in this camera (inwards).
+ */
+Axis GLCamera::axisZ() const
+{
+	return static_cast<Axis>(3 - m_localX - m_localY);
+}
+
+/*
+ * This converts a 2D point on the screen to a 3D point in the model. If 'snap' is true, the 3D point will snap to the current grid.
+ */
+Vertex GLCamera::convert2dTo3d(const QPoint& position2d, Grid* grid) const
+{
+	if (m_isFree)
+	{
+		return {0, 0, 0};
+	}
+	else
+	{
+		Vertex position3d;
+		int signX = m_negatedX ? -1 : 1;
+		int signY = m_negatedY ? -1 : 1;
+
+		// Calculate cx and cy - these are the LDraw unit coords the cursor is at.
+		double cx = -m_virtualSize.width() + (2 * position2d.x() * m_virtualSize.width() / m_size.width()) - m_panningX;
+		double cy = m_virtualSize.height() - (2 * position2d.y() * m_virtualSize.height() / m_size.height()) - m_panningY;
+
+		// If a grid was passed, snap coordinates to it.
+		if (grid)
+		{
+			cx = grid->snap(cx, Grid::Coordinate);
+			cy = grid->snap(cy, Grid::Coordinate);
+		}
+
+		roundToDecimals(cx, 4);
+		roundToDecimals(cy, 4);
+
+		// Create the vertex from the coordinates
+		position3d.setCoordinate(axisX(), cx * signX);
+		position3d.setCoordinate(axisY(), cy * signY);
+		position3d.setCoordinate(axisZ(), m_depth);
+		return position3d;
+	}
+}
+
+/*
+ * Inverse operation for the above - convert a 3D position to a 2D screen position.
+ */
+QPoint GLCamera::convert3dTo2d(const Vertex& position3d) const
+{
+	if (m_isFree)
+	{
+		return {0, 0};
+	}
+	else
+	{
+		int signX = m_negatedX ? -1 : 1;
+		int signY = m_negatedY ? -1 : 1;
+		int rx = (position3d[axisX()] * signX + m_virtualSize.width() + m_panningX) * m_size.width() / 2 / m_virtualSize.width();
+		int ry = (position3d[axisY()] * signY - m_virtualSize.height() + m_panningY) * m_size.height() / 2 / m_virtualSize.height();
+		return {rx, -ry};
+	}
+}
+
+/*
+ * Resizes the camera when the renderer is resized.
+ */
+void GLCamera::rendererResized(int width, int height)
+{
+	m_size = {width, height};
+	m_virtualSize = {m_zoom, height * m_zoom / width};
+}
+
+/*
+ * Returns the "virtual size" of the camera. Used to zoom in while keeping proportions.
+ */
+const QSizeF& GLCamera::virtualSize() const
+{
+	return m_virtualSize;
+}
+
+/*
+ * Returns the "z depth" of the camera. Since the camera provides 2D editing, this value fills in the value for the
+ * third dimension for 3D vertices.
+ */
+double GLCamera::depth() const
+{
+	return m_depth;
+}
+
+/*
+ * Returns the X-panning of this camera.
+ */
+double GLCamera::panningX() const
+{
+	return m_panningX;
+}
+
+/*
+ * Returns the Y-panning of this camera.
+ */
+double GLCamera::panningY() const
+{
+	return m_panningY;
+}
+
+/*
+ * Returns the zoom level of this camera.
+ */
+double GLCamera::zoom() const
+{
+	return m_zoom;
+}
+
+/*
+ * Explicitly sets the panning of this camera.
+ */
+void GLCamera::setPanning(double x, double y)
+{
+	m_panningX = x;
+	m_panningY = y;
+}
+
+/*
+ * Makes the camera pan by the provided mouse move input.
+ */
+void GLCamera::pan(int xMove, int yMove)
+{
+	m_panningX += 0.03f * xMove * zoom() / 7.5f;
+	m_panningY -= 0.03f * yMove * zoom() / 7.5f;
+}
+
+/*
+ * Zooms the camera in one notch (e.g. by mousewheel).
+ */
+void GLCamera::zoomNotch (bool inward)
+{
+	m_zoom *= inward ? 0.833f : 1.2f;
+	m_zoom = qBound(0.01, zoom(), 10000.0);
+	rendererResized(m_size.width(), m_size.height());
+}
+
+/*
+ * Explicitly sets the zoom of this camera.
+ */
+void GLCamera::setZoom(double zoom)
+{
+	m_zoom = zoom;
+	rendererResized(m_size.width(), m_size.height());
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/glcamera.h	Tue Feb 14 07:57:27 2017 +0200
@@ -0,0 +1,69 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2017 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include "main.h"
+
+/*
+ * Models a 2D x/y co-ordinate system that maps to a fixed camera position.
+ * Owns camera orientation information and provides 2Dā†ā†’3D translation.
+ */
+class GLCamera : public QObject
+{
+	Q_OBJECT
+
+public:
+	// This is used to construct the free camera
+	enum FreeToken { FreeCamera };
+
+	GLCamera(int glRotateX, int glRotateY, int glRotateZ, Axis localX, Axis localY, bool negatedX, bool negatedY, bool negatedDepth);
+	GLCamera(FreeToken);
+
+	Axis axisX() const;
+	Axis axisY() const;
+	Axis axisZ() const;
+	double depth() const;
+	int glRotate(Axis axis) const;
+	bool isAxisNegated(Axis axis) const;
+	Q_SLOT void rendererResized(int width, int height);
+	const QSizeF& virtualSize() const;
+	Vertex convert2dTo3d(const QPoint& pos2d, Grid* grid = nullptr) const;
+	QPoint convert3dTo2d(const Vertex& pos3d) const;
+	double panningX() const;
+	double panningY() const;
+	double zoom() const;
+	void setPanning(double x, double y);
+	void pan(int xMove, int yMove);
+	void zoomNotch(bool inward);
+	void setZoom(double zoom);
+
+private:
+	double m_panningX = 0;
+	double m_panningY = 0;
+	double m_depth = 0;
+	double m_zoom = 30;
+	QSize m_size;
+	QSizeF m_virtualSize;
+	int m_glrotate[3] = {0, 0, 0}; // GL model transformation to use
+	Axis m_localX = X; // Which axis to use for Y
+	Axis m_localY = Y; // Which axis to use for Y
+	bool m_isFree = false; // Is this the free camera?
+	bool m_negatedX = false; // Is +x to the left?
+	bool m_negatedY = false; // Is +y downwards?
+	bool m_negatedDepth = false; // is greater depth value closer to camera?
+};

mercurial